/* 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); /* * 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); }