/* client.c: NFS client sharing and management code * * Copyright (C) 2006 Red Hat, Inc. All Rights Reserved. * Written by David Howells (dhowells@redhat.com) * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version * 2 of the License, or (at your option) any later version. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "nfs4_fs.h" #include "callback.h" #include "delegation.h" #include "iostat.h" #include "internal.h" #define NFSDBG_FACILITY NFSDBG_CLIENT static DEFINE_SPINLOCK(nfs_client_lock); static LIST_HEAD(nfs_client_list); static DECLARE_WAIT_QUEUE_HEAD(nfs_client_active_wq); /* * RPC cruft for NFS */ static struct rpc_version *nfs_version[5] = { [2] = &nfs_version2, #ifdef CONFIG_NFS_V3 [3] = &nfs_version3, #endif #ifdef CONFIG_NFS_V4 [4] = &nfs_version4, #endif }; struct rpc_program nfs_program = { .name = "nfs", .number = NFS_PROGRAM, .nrvers = ARRAY_SIZE(nfs_version), .version = nfs_version, .stats = &nfs_rpcstat, .pipe_dir_name = "/nfs", }; struct rpc_stat nfs_rpcstat = { .program = &nfs_program }; #ifdef CONFIG_NFS_V3_ACL static struct rpc_stat nfsacl_rpcstat = { &nfsacl_program }; static struct rpc_version * nfsacl_version[] = { [3] = &nfsacl_version3, }; struct rpc_program nfsacl_program = { .name = "nfsacl", .number = NFS_ACL_PROGRAM, .nrvers = ARRAY_SIZE(nfsacl_version), .version = nfsacl_version, .stats = &nfsacl_rpcstat, }; #endif /* CONFIG_NFS_V3_ACL */ /* * Allocate a shared client record * * Since these are allocated/deallocated very rarely, we don't * bother putting them in a slab cache... */ static struct nfs_client *nfs_alloc_client(const char *hostname, const struct sockaddr_in *addr, int nfsversion) { struct nfs_client *clp; int error; if ((clp = kzalloc(sizeof(*clp), GFP_KERNEL)) == NULL) goto error_0; error = rpciod_up(); if (error < 0) { dprintk("%s: couldn't start rpciod! Error = %d\n", __FUNCTION__, error); __set_bit(NFS_CS_RPCIOD, &clp->cl_res_state); goto error_1; } if (nfsversion == 4) { if (nfs_callback_up() < 0) goto error_2; __set_bit(NFS_CS_CALLBACK, &clp->cl_res_state); } atomic_set(&clp->cl_count, 1); clp->cl_cons_state = NFS_CS_INITING; clp->cl_nfsversion = nfsversion; memcpy(&clp->cl_addr, addr, sizeof(clp->cl_addr)); if (hostname) { clp->cl_hostname = kstrdup(hostname, GFP_KERNEL); if (!clp->cl_hostname) goto error_3; } INIT_LIST_HEAD(&clp->cl_superblocks); clp->cl_rpcclient = ERR_PTR(-EINVAL); #ifdef CONFIG_NFS_V4 init_rwsem(&clp->cl_sem); INIT_LIST_HEAD(&clp->cl_delegations); INIT_LIST_HEAD(&clp->cl_state_owners); INIT_LIST_HEAD(&clp->cl_unused); spin_lock_init(&clp->cl_lock); INIT_WORK(&clp->cl_renewd, nfs4_renew_state, clp); rpc_init_wait_queue(&clp->cl_rpcwaitq, "NFS client"); clp->cl_boot_time = CURRENT_TIME; clp->cl_state = 1 << NFS4CLNT_LEASE_EXPIRED; #endif return clp; error_3: nfs_callback_down(); __clear_bit(NFS_CS_CALLBACK, &clp->cl_res_state); error_2: rpciod_down(); __clear_bit(NFS_CS_RPCIOD, &clp->cl_res_state); error_1: kfree(clp); error_0: return NULL; } /* * Destroy a shared client record */ static void nfs_free_client(struct nfs_client *clp) { dprintk("--> nfs_free_client(%d)\n", clp->cl_nfsversion); #ifdef CONFIG_NFS_V4 if (__test_and_clear_bit(NFS_CS_IDMAP, &clp->cl_res_state)) { while (!list_empty(&clp->cl_unused)) { struct nfs4_state_owner *sp; sp = list_entry(clp->cl_unused.next, struct nfs4_state_owner, so_list); list_del(&sp->so_list); kfree(sp); } BUG_ON(!list_empty(&clp->cl_state_owners)); nfs_idmap_delete(clp); } #endif /* -EIO all pending I/O */ if (!IS_ERR(clp->cl_rpcclient)) rpc_shutdown_client(clp->cl_rpcclient); if (__test_and_clear_bit(NFS_CS_CALLBACK, &clp->cl_res_state)) nfs_callback_down(); if (__test_and_clear_bit(NFS_CS_RPCIOD, &clp->cl_res_state)) rpciod_down(); kfree(clp->cl_hostname); kfree(clp); dprintk("<-- nfs_free_client()\n"); } /* * Release a reference to a shared client record */ void nfs_put_client(struct nfs_client *clp) { dprintk("--> nfs_put_client({%d})\n", atomic_read(&clp->cl_count)); if (atomic_dec_and_lock(&clp->cl_count, &nfs_client_lock)) { list_del(&clp->cl_share_link); spin_unlock(&nfs_client_lock); BUG_ON(!list_empty(&clp->cl_superblocks)); nfs_free_client(clp); } } /* * Find a client by address * - caller must hold nfs_client_lock */ static struct nfs_client *__nfs_find_client(const struct sockaddr_in *addr, int nfsversion) { struct nfs_client *clp; list_for_each_entry(clp, &nfs_client_list, cl_share_link) { /* Different NFS versions cannot share the same nfs_client */ if (clp->cl_nfsversion != nfsversion) continue; if (memcmp(&clp->cl_addr.sin_addr, &addr->sin_addr, sizeof(clp->cl_addr.sin_addr)) != 0) continue; if (clp->cl_addr.sin_port == addr->sin_port) goto found; } return NULL; found: atomic_inc(&clp->cl_count); return clp; } /* * Find a client by IP address and protocol version * - returns NULL if no such client */ struct nfs_client *nfs_find_client(const struct sockaddr_in *addr, int nfsversion) { struct nfs_client *clp; spin_lock(&nfs_client_lock); clp = __nfs_find_client(addr, nfsversion); spin_unlock(&nfs_client_lock); BUG_ON(clp->cl_cons_state == 0); return clp; } /* * Look up a client by IP address and protocol version * - creates a new record if one doesn't yet exist */ struct nfs_client *nfs_get_client(const char *hostname, const struct sockaddr_in *addr, int nfsversion) { struct nfs_client *clp, *new = NULL; int error; dprintk("--> nfs_get_client(%s,"NIPQUAD_FMT":%d,%d)\n", hostname ?: "", NIPQUAD(addr->sin_addr), addr->sin_port, nfsversion); /* see if the client already exists */ do { spin_lock(&nfs_client_lock); clp = __nfs_find_client(addr, nfsversion); if (clp) goto found_client; if (new) goto install_client; spin_unlock(&nfs_client_lock); new = nfs_alloc_client(hostname, addr, nfsversion); } while (new); return ERR_PTR(-ENOMEM); /* install a new client and return with it unready */ install_client: clp = new; list_add(&clp->cl_share_link, &nfs_client_list); spin_unlock(&nfs_client_lock); dprintk("--> nfs_get_client() = %p [new]\n", clp); return clp; /* found an existing client * - make sure it's ready before returning */ found_client: spin_unlock(&nfs_client_lock); if (new) nfs_free_client(new); if (clp->cl_cons_state == NFS_CS_INITING) { DECLARE_WAITQUEUE(myself, current); add_wait_queue(&nfs_client_active_wq, &myself); for (;;) { set_current_state(TASK_INTERRUPTIBLE); if (signal_pending(current) || clp->cl_cons_state > NFS_CS_READY) break; schedule(); } remove_wait_queue(&nfs_client_active_wq, &myself); if (signal_pending(current)) { nfs_put_client(clp); return ERR_PTR(-ERESTARTSYS); } } if (clp->cl_cons_state < NFS_CS_READY) { error = clp->cl_cons_state; nfs_put_client(clp); return ERR_PTR(error); } dprintk("--> nfs_get_client() = %p [share]\n", clp); return clp; } /* * Mark a server as ready or failed */ void nfs_mark_client_ready(struct nfs_client *clp, int state) { clp->cl_cons_state = state; wake_up_all(&nfs_client_active_wq); } /* * Initialise the timeout values for a connection */ static void nfs_init_timeout_values(struct rpc_timeout *to, int proto, unsigned int timeo, unsigned int retrans) { to->to_initval = timeo * HZ / 10; to->to_retries = retrans; if (!to->to_retries) to->to_retries = 2; switch (proto) { case IPPROTO_TCP: if (!to->to_initval) to->to_initval = 60 * HZ; if (to->to_initval > NFS_MAX_TCP_TIMEOUT) to->to_initval = NFS_MAX_TCP_TIMEOUT; to->to_increment = to->to_initval; to->to_maxval = to->to_initval + (to->to_increment * to->to_retries); to->to_exponential = 0; break; case IPPROTO_UDP: default: if (!to->to_initval) to->to_initval = 11 * HZ / 10; if (to->to_initval > NFS_MAX_UDP_TIMEOUT) to->to_initval = NFS_MAX_UDP_TIMEOUT; to->to_maxval = NFS_MAX_UDP_TIMEOUT; to->to_exponential = 1; break; } } /* * Create an RPC client handle */ int nfs_create_rpc_client(struct nfs_client *clp, int proto, unsigned int timeo, unsigned int retrans, rpc_authflavor_t flavor) { struct rpc_timeout timeparms; struct rpc_xprt *xprt = NULL; struct rpc_clnt *clnt = NULL; if (!IS_ERR(clp->cl_rpcclient)) return 0; nfs_init_timeout_values(&timeparms, proto, timeo, retrans); clp->retrans_timeo = timeparms.to_initval; clp->retrans_count = timeparms.to_retries; /* create transport and client */ xprt = xprt_create_proto(proto, &clp->cl_addr, &timeparms); if (IS_ERR(xprt)) { dprintk("%s: cannot create RPC transport. Error = %ld\n", __FUNCTION__, PTR_ERR(xprt)); return PTR_ERR(xprt); } /* Bind to a reserved port! */ xprt->resvport = 1; /* Create the client RPC handle */ clnt = rpc_create_client(xprt, clp->cl_hostname, &nfs_program, clp->rpc_ops->version, RPC_AUTH_UNIX); if (IS_ERR(clnt)) { dprintk("%s: cannot create RPC client. Error = %ld\n", __FUNCTION__, PTR_ERR(clnt)); return PTR_ERR(clnt); } clnt->cl_intr = 1; clnt->cl_softrtry = 1; clp->cl_rpcclient = clnt; return 0; }