diff options
Diffstat (limited to 'fs/nfsd/nfs4state.c')
-rw-r--r-- | fs/nfsd/nfs4state.c | 3320 |
1 files changed, 3320 insertions, 0 deletions
diff --git a/fs/nfsd/nfs4state.c b/fs/nfsd/nfs4state.c new file mode 100644 index 00000000000..579f7fea796 --- /dev/null +++ b/fs/nfsd/nfs4state.c @@ -0,0 +1,3320 @@ +/* +* linux/fs/nfsd/nfs4state.c +* +* Copyright (c) 2001 The Regents of the University of Michigan. +* All rights reserved. +* +* Kendrick Smith <kmsmith@umich.edu> +* Andy Adamson <kandros@umich.edu> +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions +* are met: +* +* 1. Redistributions of source code must retain the above copyright +* notice, this list of conditions and the following disclaimer. +* 2. Redistributions in binary form must reproduce the above copyright +* notice, this list of conditions and the following disclaimer in the +* documentation and/or other materials provided with the distribution. +* 3. Neither the name of the University nor the names of its +* contributors may be used to endorse or promote products derived +* from this software without specific prior written permission. +* +* THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED +* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +* MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +* DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE +* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR +* BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +* +*/ + +#include <linux/param.h> +#include <linux/major.h> +#include <linux/slab.h> + +#include <linux/sunrpc/svc.h> +#include <linux/nfsd/nfsd.h> +#include <linux/nfsd/cache.h> +#include <linux/mount.h> +#include <linux/workqueue.h> +#include <linux/smp_lock.h> +#include <linux/kthread.h> +#include <linux/nfs4.h> +#include <linux/nfsd/state.h> +#include <linux/nfsd/xdr4.h> + +#define NFSDDBG_FACILITY NFSDDBG_PROC + +/* Globals */ +static time_t lease_time = 90; /* default lease time */ +static time_t old_lease_time = 90; /* past incarnation lease time */ +static u32 nfs4_reclaim_init = 0; +time_t boot_time; +static time_t grace_end = 0; +static u32 current_clientid = 1; +static u32 current_ownerid = 1; +static u32 current_fileid = 1; +static u32 current_delegid = 1; +static u32 nfs4_init; +stateid_t zerostateid; /* bits all 0 */ +stateid_t onestateid; /* bits all 1 */ + +/* debug counters */ +u32 list_add_perfile = 0; +u32 list_del_perfile = 0; +u32 add_perclient = 0; +u32 del_perclient = 0; +u32 alloc_file = 0; +u32 free_file = 0; +u32 vfsopen = 0; +u32 vfsclose = 0; +u32 alloc_delegation= 0; +u32 free_delegation= 0; + +/* forward declarations */ +struct nfs4_stateid * find_stateid(stateid_t *stid, int flags); +static struct nfs4_delegation * find_delegation_stateid(struct inode *ino, stateid_t *stid); +static void release_stateid_lockowners(struct nfs4_stateid *open_stp); + +/* Locking: + * + * client_sema: + * protects clientid_hashtbl[], clientstr_hashtbl[], + * unconfstr_hashtbl[], uncofid_hashtbl[]. + */ +static DECLARE_MUTEX(client_sema); + +void +nfs4_lock_state(void) +{ + down(&client_sema); +} + +void +nfs4_unlock_state(void) +{ + up(&client_sema); +} + +static inline u32 +opaque_hashval(const void *ptr, int nbytes) +{ + unsigned char *cptr = (unsigned char *) ptr; + + u32 x = 0; + while (nbytes--) { + x *= 37; + x += *cptr++; + } + return x; +} + +/* forward declarations */ +static void release_stateowner(struct nfs4_stateowner *sop); +static void release_stateid(struct nfs4_stateid *stp, int flags); +static void release_file(struct nfs4_file *fp); + +/* + * Delegation state + */ + +/* recall_lock protects the del_recall_lru */ +spinlock_t recall_lock; +static struct list_head del_recall_lru; + +static struct nfs4_delegation * +alloc_init_deleg(struct nfs4_client *clp, struct nfs4_stateid *stp, struct svc_fh *current_fh, u32 type) +{ + struct nfs4_delegation *dp; + struct nfs4_file *fp = stp->st_file; + struct nfs4_callback *cb = &stp->st_stateowner->so_client->cl_callback; + + dprintk("NFSD alloc_init_deleg\n"); + if ((dp = kmalloc(sizeof(struct nfs4_delegation), + GFP_KERNEL)) == NULL) + return dp; + INIT_LIST_HEAD(&dp->dl_del_perfile); + INIT_LIST_HEAD(&dp->dl_del_perclnt); + INIT_LIST_HEAD(&dp->dl_recall_lru); + dp->dl_client = clp; + dp->dl_file = fp; + dp->dl_flock = NULL; + get_file(stp->st_vfs_file); + dp->dl_vfs_file = stp->st_vfs_file; + dp->dl_type = type; + dp->dl_recall.cbr_dp = NULL; + dp->dl_recall.cbr_ident = cb->cb_ident; + dp->dl_recall.cbr_trunc = 0; + dp->dl_stateid.si_boot = boot_time; + dp->dl_stateid.si_stateownerid = current_delegid++; + dp->dl_stateid.si_fileid = 0; + dp->dl_stateid.si_generation = 0; + dp->dl_fhlen = current_fh->fh_handle.fh_size; + memcpy(dp->dl_fhval, ¤t_fh->fh_handle.fh_base, + current_fh->fh_handle.fh_size); + dp->dl_time = 0; + atomic_set(&dp->dl_count, 1); + list_add(&dp->dl_del_perfile, &fp->fi_del_perfile); + list_add(&dp->dl_del_perclnt, &clp->cl_del_perclnt); + alloc_delegation++; + return dp; +} + +void +nfs4_put_delegation(struct nfs4_delegation *dp) +{ + if (atomic_dec_and_test(&dp->dl_count)) { + dprintk("NFSD: freeing dp %p\n",dp); + kfree(dp); + free_delegation++; + } +} + +/* Remove the associated file_lock first, then remove the delegation. + * lease_modify() is called to remove the FS_LEASE file_lock from + * the i_flock list, eventually calling nfsd's lock_manager + * fl_release_callback. + */ +static void +nfs4_close_delegation(struct nfs4_delegation *dp) +{ + struct file *filp = dp->dl_vfs_file; + + dprintk("NFSD: close_delegation dp %p\n",dp); + dp->dl_vfs_file = NULL; + /* The following nfsd_close may not actually close the file, + * but we want to remove the lease in any case. */ + setlease(filp, F_UNLCK, &dp->dl_flock); + nfsd_close(filp); + vfsclose++; +} + +/* Called under the state lock. */ +static void +unhash_delegation(struct nfs4_delegation *dp) +{ + list_del_init(&dp->dl_del_perfile); + list_del_init(&dp->dl_del_perclnt); + spin_lock(&recall_lock); + list_del_init(&dp->dl_recall_lru); + spin_unlock(&recall_lock); + nfs4_close_delegation(dp); + nfs4_put_delegation(dp); +} + +/* + * SETCLIENTID state + */ + +/* Hash tables for nfs4_clientid state */ +#define CLIENT_HASH_BITS 4 +#define CLIENT_HASH_SIZE (1 << CLIENT_HASH_BITS) +#define CLIENT_HASH_MASK (CLIENT_HASH_SIZE - 1) + +#define clientid_hashval(id) \ + ((id) & CLIENT_HASH_MASK) +#define clientstr_hashval(name, namelen) \ + (opaque_hashval((name), (namelen)) & CLIENT_HASH_MASK) +/* + * reclaim_str_hashtbl[] holds known client info from previous reset/reboot + * used in reboot/reset lease grace period processing + * + * conf_id_hashtbl[], and conf_str_hashtbl[] hold confirmed + * setclientid_confirmed info. + * + * unconf_str_hastbl[] and unconf_id_hashtbl[] hold unconfirmed + * setclientid info. + * + * client_lru holds client queue ordered by nfs4_client.cl_time + * for lease renewal. + * + * close_lru holds (open) stateowner queue ordered by nfs4_stateowner.so_time + * for last close replay. + */ +static struct list_head reclaim_str_hashtbl[CLIENT_HASH_SIZE]; +static int reclaim_str_hashtbl_size = 0; +static struct list_head conf_id_hashtbl[CLIENT_HASH_SIZE]; +static struct list_head conf_str_hashtbl[CLIENT_HASH_SIZE]; +static struct list_head unconf_str_hashtbl[CLIENT_HASH_SIZE]; +static struct list_head unconf_id_hashtbl[CLIENT_HASH_SIZE]; +static struct list_head client_lru; +static struct list_head close_lru; + +static inline void +renew_client(struct nfs4_client *clp) +{ + /* + * Move client to the end to the LRU list. + */ + dprintk("renewing client (clientid %08x/%08x)\n", + clp->cl_clientid.cl_boot, + clp->cl_clientid.cl_id); + list_move_tail(&clp->cl_lru, &client_lru); + clp->cl_time = get_seconds(); +} + +/* SETCLIENTID and SETCLIENTID_CONFIRM Helper functions */ +static int +STALE_CLIENTID(clientid_t *clid) +{ + if (clid->cl_boot == boot_time) + return 0; + dprintk("NFSD stale clientid (%08x/%08x)\n", + clid->cl_boot, clid->cl_id); + return 1; +} + +/* + * XXX Should we use a slab cache ? + * This type of memory management is somewhat inefficient, but we use it + * anyway since SETCLIENTID is not a common operation. + */ +static inline struct nfs4_client * +alloc_client(struct xdr_netobj name) +{ + struct nfs4_client *clp; + + if ((clp = kmalloc(sizeof(struct nfs4_client), GFP_KERNEL))!= NULL) { + memset(clp, 0, sizeof(*clp)); + if ((clp->cl_name.data = kmalloc(name.len, GFP_KERNEL)) != NULL) { + memcpy(clp->cl_name.data, name.data, name.len); + clp->cl_name.len = name.len; + } + else { + kfree(clp); + clp = NULL; + } + } + return clp; +} + +static inline void +free_client(struct nfs4_client *clp) +{ + if (clp->cl_cred.cr_group_info) + put_group_info(clp->cl_cred.cr_group_info); + kfree(clp->cl_name.data); + kfree(clp); +} + +void +put_nfs4_client(struct nfs4_client *clp) +{ + if (atomic_dec_and_test(&clp->cl_count)) + free_client(clp); +} + +static void +expire_client(struct nfs4_client *clp) +{ + struct nfs4_stateowner *sop; + struct nfs4_delegation *dp; + struct nfs4_callback *cb = &clp->cl_callback; + struct rpc_clnt *clnt = clp->cl_callback.cb_client; + struct list_head reaplist; + + dprintk("NFSD: expire_client cl_count %d\n", + atomic_read(&clp->cl_count)); + + /* shutdown rpc client, ending any outstanding recall rpcs */ + if (atomic_read(&cb->cb_set) == 1 && clnt) { + rpc_shutdown_client(clnt); + clnt = clp->cl_callback.cb_client = NULL; + } + + INIT_LIST_HEAD(&reaplist); + spin_lock(&recall_lock); + while (!list_empty(&clp->cl_del_perclnt)) { + dp = list_entry(clp->cl_del_perclnt.next, struct nfs4_delegation, dl_del_perclnt); + dprintk("NFSD: expire client. dp %p, fp %p\n", dp, + dp->dl_flock); + list_del_init(&dp->dl_del_perclnt); + list_move(&dp->dl_recall_lru, &reaplist); + } + spin_unlock(&recall_lock); + while (!list_empty(&reaplist)) { + dp = list_entry(reaplist.next, struct nfs4_delegation, dl_recall_lru); + list_del_init(&dp->dl_recall_lru); + unhash_delegation(dp); + } + list_del(&clp->cl_idhash); + list_del(&clp->cl_strhash); + list_del(&clp->cl_lru); + while (!list_empty(&clp->cl_perclient)) { + sop = list_entry(clp->cl_perclient.next, struct nfs4_stateowner, so_perclient); + release_stateowner(sop); + } + put_nfs4_client(clp); +} + +static struct nfs4_client * +create_client(struct xdr_netobj name) { + struct nfs4_client *clp; + + if (!(clp = alloc_client(name))) + goto out; + atomic_set(&clp->cl_count, 1); + atomic_set(&clp->cl_callback.cb_set, 0); + clp->cl_callback.cb_parsed = 0; + INIT_LIST_HEAD(&clp->cl_idhash); + INIT_LIST_HEAD(&clp->cl_strhash); + INIT_LIST_HEAD(&clp->cl_perclient); + INIT_LIST_HEAD(&clp->cl_del_perclnt); + INIT_LIST_HEAD(&clp->cl_lru); +out: + return clp; +} + +static void +copy_verf(struct nfs4_client *target, nfs4_verifier *source) { + memcpy(target->cl_verifier.data, source->data, sizeof(target->cl_verifier.data)); +} + +static void +copy_clid(struct nfs4_client *target, struct nfs4_client *source) { + target->cl_clientid.cl_boot = source->cl_clientid.cl_boot; + target->cl_clientid.cl_id = source->cl_clientid.cl_id; +} + +static void +copy_cred(struct svc_cred *target, struct svc_cred *source) { + + target->cr_uid = source->cr_uid; + target->cr_gid = source->cr_gid; + target->cr_group_info = source->cr_group_info; + get_group_info(target->cr_group_info); +} + +static int +cmp_name(struct xdr_netobj *n1, struct xdr_netobj *n2) { + if (!n1 || !n2) + return 0; + return((n1->len == n2->len) && !memcmp(n1->data, n2->data, n2->len)); +} + +static int +cmp_verf(nfs4_verifier *v1, nfs4_verifier *v2) { + return(!memcmp(v1->data,v2->data,sizeof(v1->data))); +} + +static int +cmp_clid(clientid_t * cl1, clientid_t * cl2) { + return((cl1->cl_boot == cl2->cl_boot) && + (cl1->cl_id == cl2->cl_id)); +} + +/* XXX what about NGROUP */ +static int +cmp_creds(struct svc_cred *cr1, struct svc_cred *cr2){ + return(cr1->cr_uid == cr2->cr_uid); + +} + +static void +gen_clid(struct nfs4_client *clp) { + clp->cl_clientid.cl_boot = boot_time; + clp->cl_clientid.cl_id = current_clientid++; +} + +static void +gen_confirm(struct nfs4_client *clp) { + struct timespec tv; + u32 * p; + + tv = CURRENT_TIME; + p = (u32 *)clp->cl_confirm.data; + *p++ = tv.tv_sec; + *p++ = tv.tv_nsec; +} + +static int +check_name(struct xdr_netobj name) { + + if (name.len == 0) + return 0; + if (name.len > NFS4_OPAQUE_LIMIT) { + printk("NFSD: check_name: name too long(%d)!\n", name.len); + return 0; + } + return 1; +} + +void +add_to_unconfirmed(struct nfs4_client *clp, unsigned int strhashval) +{ + unsigned int idhashval; + + list_add(&clp->cl_strhash, &unconf_str_hashtbl[strhashval]); + idhashval = clientid_hashval(clp->cl_clientid.cl_id); + list_add(&clp->cl_idhash, &unconf_id_hashtbl[idhashval]); + list_add_tail(&clp->cl_lru, &client_lru); + clp->cl_time = get_seconds(); +} + +void +move_to_confirmed(struct nfs4_client *clp) +{ + unsigned int idhashval = clientid_hashval(clp->cl_clientid.cl_id); + unsigned int strhashval; + + dprintk("NFSD: move_to_confirm nfs4_client %p\n", clp); + list_del_init(&clp->cl_strhash); + list_del_init(&clp->cl_idhash); + list_add(&clp->cl_idhash, &conf_id_hashtbl[idhashval]); + strhashval = clientstr_hashval(clp->cl_name.data, + clp->cl_name.len); + list_add(&clp->cl_strhash, &conf_str_hashtbl[strhashval]); + renew_client(clp); +} + +static struct nfs4_client * +find_confirmed_client(clientid_t *clid) +{ + struct nfs4_client *clp; + unsigned int idhashval = clientid_hashval(clid->cl_id); + + list_for_each_entry(clp, &conf_id_hashtbl[idhashval], cl_idhash) { + if (cmp_clid(&clp->cl_clientid, clid)) + return clp; + } + return NULL; +} + +static struct nfs4_client * +find_unconfirmed_client(clientid_t *clid) +{ + struct nfs4_client *clp; + unsigned int idhashval = clientid_hashval(clid->cl_id); + + list_for_each_entry(clp, &unconf_id_hashtbl[idhashval], cl_idhash) { + if (cmp_clid(&clp->cl_clientid, clid)) + return clp; + } + return NULL; +} + +/* a helper function for parse_callback */ +static int +parse_octet(unsigned int *lenp, char **addrp) +{ + unsigned int len = *lenp; + char *p = *addrp; + int n = -1; + char c; + + for (;;) { + if (!len) + break; + len--; + c = *p++; + if (c == '.') + break; + if ((c < '0') || (c > '9')) { + n = -1; + break; + } + if (n < 0) + n = 0; + n = (n * 10) + (c - '0'); + if (n > 255) { + n = -1; + break; + } + } + *lenp = len; + *addrp = p; + return n; +} + +/* parse and set the setclientid ipv4 callback address */ +int +parse_ipv4(unsigned int addr_len, char *addr_val, unsigned int *cbaddrp, unsigned short *cbportp) +{ + int temp = 0; + u32 cbaddr = 0; + u16 cbport = 0; + u32 addrlen = addr_len; + char *addr = addr_val; + int i, shift; + + /* ipaddress */ + shift = 24; + for(i = 4; i > 0 ; i--) { + if ((temp = parse_octet(&addrlen, &addr)) < 0) { + return 0; + } + cbaddr |= (temp << shift); + if (shift > 0) + shift -= 8; + } + *cbaddrp = cbaddr; + + /* port */ + shift = 8; + for(i = 2; i > 0 ; i--) { + if ((temp = parse_octet(&addrlen, &addr)) < 0) { + return 0; + } + cbport |= (temp << shift); + if (shift > 0) + shift -= 8; + } + *cbportp = cbport; + return 1; +} + +void +gen_callback(struct nfs4_client *clp, struct nfsd4_setclientid *se) +{ + struct nfs4_callback *cb = &clp->cl_callback; + + /* Currently, we only support tcp for the callback channel */ + if ((se->se_callback_netid_len != 3) || memcmp((char *)se->se_callback_netid_val, "tcp", 3)) + goto out_err; + + if ( !(parse_ipv4(se->se_callback_addr_len, se->se_callback_addr_val, + &cb->cb_addr, &cb->cb_port))) + goto out_err; + cb->cb_prog = se->se_callback_prog; + cb->cb_ident = se->se_callback_ident; + cb->cb_parsed = 1; + return; +out_err: + printk(KERN_INFO "NFSD: this client (clientid %08x/%08x) " + "will not receive delegations\n", + clp->cl_clientid.cl_boot, clp->cl_clientid.cl_id); + + cb->cb_parsed = 0; + return; +} + +/* + * RFC 3010 has a complex implmentation description of processing a + * SETCLIENTID request consisting of 5 bullets, labeled as + * CASE0 - CASE4 below. + * + * NOTES: + * callback information will be processed in a future patch + * + * an unconfirmed record is added when: + * NORMAL (part of CASE 4): there is no confirmed nor unconfirmed record. + * CASE 1: confirmed record found with matching name, principal, + * verifier, and clientid. + * CASE 2: confirmed record found with matching name, principal, + * and there is no unconfirmed record with matching + * name and principal + * + * an unconfirmed record is replaced when: + * CASE 3: confirmed record found with matching name, principal, + * and an unconfirmed record is found with matching + * name, principal, and with clientid and + * confirm that does not match the confirmed record. + * CASE 4: there is no confirmed record with matching name and + * principal. there is an unconfirmed record with + * matching name, principal. + * + * an unconfirmed record is deleted when: + * CASE 1: an unconfirmed record that matches input name, verifier, + * and confirmed clientid. + * CASE 4: any unconfirmed records with matching name and principal + * that exist after an unconfirmed record has been replaced + * as described above. + * + */ +int +nfsd4_setclientid(struct svc_rqst *rqstp, struct nfsd4_setclientid *setclid) +{ + u32 ip_addr = rqstp->rq_addr.sin_addr.s_addr; + struct xdr_netobj clname = { + .len = setclid->se_namelen, + .data = setclid->se_name, + }; + nfs4_verifier clverifier = setclid->se_verf; + unsigned int strhashval; + struct nfs4_client * conf, * unconf, * new, * clp; + int status; + + status = nfserr_inval; + if (!check_name(clname)) + goto out; + + /* + * XXX The Duplicate Request Cache (DRC) has been checked (??) + * We get here on a DRC miss. + */ + + strhashval = clientstr_hashval(clname.data, clname.len); + + conf = NULL; + nfs4_lock_state(); + list_for_each_entry(clp, &conf_str_hashtbl[strhashval], cl_strhash) { + if (!cmp_name(&clp->cl_name, &clname)) + continue; + /* + * CASE 0: + * clname match, confirmed, different principal + * or different ip_address + */ + status = nfserr_clid_inuse; + if (!cmp_creds(&clp->cl_cred,&rqstp->rq_cred)) { + printk("NFSD: setclientid: string in use by client" + "(clientid %08x/%08x)\n", + clp->cl_clientid.cl_boot, clp->cl_clientid.cl_id); + goto out; + } + if (clp->cl_addr != ip_addr) { + printk("NFSD: setclientid: string in use by client" + "(clientid %08x/%08x)\n", + clp->cl_clientid.cl_boot, clp->cl_clientid.cl_id); + goto out; + } + + /* + * cl_name match from a previous SETCLIENTID operation + * XXX check for additional matches? + */ + conf = clp; + break; + } + unconf = NULL; + list_for_each_entry(clp, &unconf_str_hashtbl[strhashval], cl_strhash) { + if (!cmp_name(&clp->cl_name, &clname)) + continue; + /* cl_name match from a previous SETCLIENTID operation */ + unconf = clp; + break; + } + status = nfserr_resource; + if (!conf) { + /* + * CASE 4: + * placed first, because it is the normal case. + */ + if (unconf) + expire_client(unconf); + if (!(new = create_client(clname))) + goto out; + copy_verf(new, &clverifier); + new->cl_addr = ip_addr; + copy_cred(&new->cl_cred,&rqstp->rq_cred); + gen_clid(new); + gen_confirm(new); + gen_callback(new, setclid); + add_to_unconfirmed(new, strhashval); + } else if (cmp_verf(&conf->cl_verifier, &clverifier)) { + /* + * CASE 1: + * cl_name match, confirmed, principal match + * verifier match: probable callback update + * + * remove any unconfirmed nfs4_client with + * matching cl_name, cl_verifier, and cl_clientid + * + * create and insert an unconfirmed nfs4_client with same + * cl_name, cl_verifier, and cl_clientid as existing + * nfs4_client, but with the new callback info and a + * new cl_confirm + */ + if ((unconf) && + cmp_verf(&unconf->cl_verifier, &conf->cl_verifier) && + cmp_clid(&unconf->cl_clientid, &conf->cl_clientid)) { + expire_client(unconf); + } + if (!(new = create_client(clname))) + goto out; + copy_verf(new,&conf->cl_verifier); + new->cl_addr = ip_addr; + copy_cred(&new->cl_cred,&rqstp->rq_cred); + copy_clid(new, conf); + gen_confirm(new); + gen_callback(new, setclid); + add_to_unconfirmed(new,strhashval); + } else if (!unconf) { + /* + * CASE 2: + * clname match, confirmed, principal match + * verfier does not match + * no unconfirmed. create a new unconfirmed nfs4_client + * using input clverifier, clname, and callback info + * and generate a new cl_clientid and cl_confirm. + */ + if (!(new = create_client(clname))) + goto out; + copy_verf(new,&clverifier); + new->cl_addr = ip_addr; + copy_cred(&new->cl_cred,&rqstp->rq_cred); + gen_clid(new); + gen_confirm(new); + gen_callback(new, setclid); + add_to_unconfirmed(new, strhashval); + } else if (!cmp_verf(&conf->cl_confirm, &unconf->cl_confirm)) { + /* + * CASE3: + * confirmed found (name, principal match) + * confirmed verifier does not match input clverifier + * + * unconfirmed found (name match) + * confirmed->cl_confirm != unconfirmed->cl_confirm + * + * remove unconfirmed. + * + * create an unconfirmed nfs4_client + * with same cl_name as existing confirmed nfs4_client, + * but with new callback info, new cl_clientid, + * new cl_verifier and a new cl_confirm + */ + expire_client(unconf); + if (!(new = create_client(clname))) + goto out; + copy_verf(new,&clverifier); + new->cl_addr = ip_addr; + copy_cred(&new->cl_cred,&rqstp->rq_cred); + gen_clid(new); + gen_confirm(new); + gen_callback(new, setclid); + add_to_unconfirmed(new, strhashval); + } else { + /* No cases hit !!! */ + status = nfserr_inval; + goto out; + + } + setclid->se_clientid.cl_boot = new->cl_clientid.cl_boot; + setclid->se_clientid.cl_id = new->cl_clientid.cl_id; + memcpy(setclid->se_confirm.data, new->cl_confirm.data, sizeof(setclid->se_confirm.data)); + status = nfs_ok; +out: + nfs4_unlock_state(); + return status; +} + + +/* + * RFC 3010 has a complex implmentation description of processing a + * SETCLIENTID_CONFIRM request consisting of 4 bullets describing + * processing on a DRC miss, labeled as CASE1 - CASE4 below. + * + * NOTE: callback information will be processed here in a future patch + */ +int +nfsd4_setclientid_confirm(struct svc_rqst *rqstp, struct nfsd4_setclientid_confirm *setclientid_confirm) +{ + u32 ip_addr = rqstp->rq_addr.sin_addr.s_addr; + struct nfs4_client *clp, *conf = NULL, *unconf = NULL; + nfs4_verifier confirm = setclientid_confirm->sc_confirm; + clientid_t * clid = &setclientid_confirm->sc_clientid; + int status; + + if (STALE_CLIENTID(clid)) + return nfserr_stale_clientid; + /* + * XXX The Duplicate Request Cache (DRC) has been checked (??) + * We get here on a DRC miss. + */ + + nfs4_lock_state(); + clp = find_confirmed_client(clid); + if (clp) { + status = nfserr_inval; + /* + * Found a record for this clientid. If the IP addresses + * don't match, return ERR_INVAL just as if the record had + * not been found. + */ + if (clp->cl_addr != ip_addr) { + printk("NFSD: setclientid: string in use by client" + "(clientid %08x/%08x)\n", + clp->cl_clientid.cl_boot, clp->cl_clientid.cl_id); + goto out; + } + conf = clp; + } + clp = find_unconfirmed_client(clid); + if (clp) { + status = nfserr_inval; + if (clp->cl_addr != ip_addr) { + printk("NFSD: setclientid: string in use by client" + "(clientid %08x/%08x)\n", + clp->cl_clientid.cl_boot, clp->cl_clientid.cl_id); + goto out; + } + unconf = clp; + } + /* CASE 1: + * unconf record that matches input clientid and input confirm. + * conf record that matches input clientid. + * conf and unconf records match names, verifiers + */ + if ((conf && unconf) && + (cmp_verf(&unconf->cl_confirm, &confirm)) && + (cmp_verf(&conf->cl_verifier, &unconf->cl_verifier)) && + (cmp_name(&conf->cl_name,&unconf->cl_name)) && + (!cmp_verf(&conf->cl_confirm, &unconf->cl_confirm))) { + if (!cmp_creds(&conf->cl_cred, &unconf->cl_cred)) + status = nfserr_clid_inuse; + else { + expire_client(conf); + clp = unconf; + move_to_confirmed(unconf); + status = nfs_ok; + } + goto out; + } + /* CASE 2: + * conf record that matches input clientid. + * if unconf record that matches input clientid, then unconf->cl_name + * or unconf->cl_verifier don't match the conf record. + */ + if ((conf && !unconf) || + ((conf && unconf) && + (!cmp_verf(&conf->cl_verifier, &unconf->cl_verifier) || + !cmp_name(&conf->cl_name, &unconf->cl_name)))) { + if (!cmp_creds(&conf->cl_cred,&rqstp->rq_cred)) { + status = nfserr_clid_inuse; + } else { + clp = conf; + status = nfs_ok; + } + goto out; + } + /* CASE 3: + * conf record not found. + * unconf record found. + * unconf->cl_confirm matches input confirm + */ + if (!conf && unconf && cmp_verf(&unconf->cl_confirm, &confirm)) { + if (!cmp_creds(&unconf->cl_cred, &rqstp->rq_cred)) { + status = nfserr_clid_inuse; + } else { + status = nfs_ok; + clp = unconf; + move_to_confirmed(unconf); + } + goto out; + } + /* CASE 4: + * conf record not found, or if conf, then conf->cl_confirm does not + * match input confirm. + * unconf record not found, or if unconf, then unconf->cl_confirm + * does not match input confirm. + */ + if ((!conf || (conf && !cmp_verf(&conf->cl_confirm, &confirm))) && + (!unconf || (unconf && !cmp_verf(&unconf->cl_confirm, &confirm)))) { + status = nfserr_stale_clientid; + goto out; + } + /* check that we have hit one of the cases...*/ + status = nfserr_inval; + goto out; +out: + if (!status) + nfsd4_probe_callback(clp); + nfs4_unlock_state(); + return status; +} + +/* + * Open owner state (share locks) + */ + +/* hash tables for nfs4_stateowner */ +#define OWNER_HASH_BITS 8 +#define OWNER_HASH_SIZE (1 << OWNER_HASH_BITS) +#define OWNER_HASH_MASK (OWNER_HASH_SIZE - 1) + +#define ownerid_hashval(id) \ + ((id) & OWNER_HASH_MASK) +#define ownerstr_hashval(clientid, ownername) \ + (((clientid) + opaque_hashval((ownername.data), (ownername.len))) & OWNER_HASH_MASK) + +static struct list_head ownerid_hashtbl[OWNER_HASH_SIZE]; +static struct list_head ownerstr_hashtbl[OWNER_HASH_SIZE]; + +/* hash table for nfs4_file */ +#define FILE_HASH_BITS 8 +#define FILE_HASH_SIZE (1 << FILE_HASH_BITS) +#define FILE_HASH_MASK (FILE_HASH_SIZE - 1) +/* hash table for (open)nfs4_stateid */ +#define STATEID_HASH_BITS 10 +#define STATEID_HASH_SIZE (1 << STATEID_HASH_BITS) +#define STATEID_HASH_MASK (STATEID_HASH_SIZE - 1) + +#define file_hashval(x) \ + hash_ptr(x, FILE_HASH_BITS) +#define stateid_hashval(owner_id, file_id) \ + (((owner_id) + (file_id)) & STATEID_HASH_MASK) + +static struct list_head file_hashtbl[FILE_HASH_SIZE]; +static struct list_head stateid_hashtbl[STATEID_HASH_SIZE]; + +/* OPEN Share state helper functions */ +static inline struct nfs4_file * +alloc_init_file(struct inode *ino) +{ + struct nfs4_file *fp; + unsigned int hashval = file_hashval(ino); + + if ((fp = kmalloc(sizeof(struct nfs4_file),GFP_KERNEL))) { + INIT_LIST_HEAD(&fp->fi_hash); + INIT_LIST_HEAD(&fp->fi_perfile); + INIT_LIST_HEAD(&fp->fi_del_perfile); + list_add(&fp->fi_hash, &file_hashtbl[hashval]); + fp->fi_inode = igrab(ino); + fp->fi_id = current_fileid++; + alloc_file++; + return fp; + } + return NULL; +} + +static void +release_all_files(void) +{ + int i; + struct nfs4_file *fp; + + for (i=0;i<FILE_HASH_SIZE;i++) { + while (!list_empty(&file_hashtbl[i])) { + fp = list_entry(file_hashtbl[i].next, struct nfs4_file, fi_hash); + /* this should never be more than once... */ + if (!list_empty(&fp->fi_perfile) || !list_empty(&fp->fi_del_perfile)) { + printk("ERROR: release_all_files: file %p is open, creating dangling state !!!\n",fp); + } + release_file(fp); + } + } +} + +kmem_cache_t *stateowner_slab = NULL; + +static int +nfsd4_init_slabs(void) +{ + stateowner_slab = kmem_cache_create("nfsd4_stateowners", + sizeof(struct nfs4_stateowner), 0, 0, NULL, NULL); + if (stateowner_slab == NULL) { + dprintk("nfsd4: out of memory while initializing nfsv4\n"); + return -ENOMEM; + } + return 0; +} + +static void +nfsd4_free_slabs(void) +{ + int status = 0; + + if (stateowner_slab) + status = kmem_cache_destroy(stateowner_slab); + stateowner_slab = NULL; + BUG_ON(status); +} + +void +nfs4_free_stateowner(struct kref *kref) +{ + struct nfs4_stateowner *sop = + container_of(kref, struct nfs4_stateowner, so_ref); + kfree(sop->so_owner.data); + kmem_cache_free(stateowner_slab, sop); +} + +static inline struct nfs4_stateowner * +alloc_stateowner(struct xdr_netobj *owner) +{ + struct nfs4_stateowner *sop; + + if ((sop = kmem_cache_alloc(stateowner_slab, GFP_KERNEL))) { + if ((sop->so_owner.data = kmalloc(owner->len, GFP_KERNEL))) { + memcpy(sop->so_owner.data, owner->data, owner->len); + sop->so_owner.len = owner->len; + kref_init(&sop->so_ref); + return sop; + } + kmem_cache_free(stateowner_slab, sop); + } + return NULL; +} + +static struct nfs4_stateowner * +alloc_init_open_stateowner(unsigned int strhashval, struct nfs4_client *clp, struct nfsd4_open *open) { + struct nfs4_stateowner *sop; + struct nfs4_replay *rp; + unsigned int idhashval; + + if (!(sop = alloc_stateowner(&open->op_owner))) + return NULL; + idhashval = ownerid_hashval(current_ownerid); + INIT_LIST_HEAD(&sop->so_idhash); + INIT_LIST_HEAD(&sop->so_strhash); + INIT_LIST_HEAD(&sop->so_perclient); + INIT_LIST_HEAD(&sop->so_perfilestate); + INIT_LIST_HEAD(&sop->so_perlockowner); /* not used */ + INIT_LIST_HEAD(&sop->so_close_lru); + sop->so_time = 0; + list_add(&sop->so_idhash, &ownerid_hashtbl[idhashval]); + list_add(&sop->so_strhash, &ownerstr_hashtbl[strhashval]); + list_add(&sop->so_perclient, &clp->cl_perclient); + add_perclient++; + sop->so_is_open_owner = 1; + sop->so_id = current_ownerid++; + sop->so_client = clp; + sop->so_seqid = open->op_seqid; + sop->so_confirmed = 0; + rp = &sop->so_replay; + rp->rp_status = NFSERR_SERVERFAULT; + rp->rp_buflen = 0; + rp->rp_buf = rp->rp_ibuf; + return sop; +} + +static void +release_stateid_lockowners(struct nfs4_stateid *open_stp) +{ + struct nfs4_stateowner *lock_sop; + + while (!list_empty(&open_stp->st_perlockowner)) { + lock_sop = list_entry(open_stp->st_perlockowner.next, + struct nfs4_stateowner, so_perlockowner); + /* list_del(&open_stp->st_perlockowner); */ + BUG_ON(lock_sop->so_is_open_owner); + release_stateowner(lock_sop); + } +} + +static void +unhash_stateowner(struct nfs4_stateowner *sop) +{ + struct nfs4_stateid *stp; + + list_del(&sop->so_idhash); + list_del(&sop->so_strhash); + if (sop->so_is_open_owner) { + list_del(&sop->so_perclient); + del_perclient++; + } + list_del(&sop->so_perlockowner); + while (!list_empty(&sop->so_perfilestate)) { + stp = list_entry(sop->so_perfilestate.next, + struct nfs4_stateid, st_perfilestate); + if (sop->so_is_open_owner) + release_stateid(stp, OPEN_STATE); + else + release_stateid(stp, LOCK_STATE); + } +} + +static void +release_stateowner(struct nfs4_stateowner *sop) +{ + unhash_stateowner(sop); + list_del(&sop->so_close_lru); + nfs4_put_stateowner(sop); +} + +static inline void +init_stateid(struct nfs4_stateid *stp, struct nfs4_file *fp, struct nfsd4_open *open) { + struct nfs4_stateowner *sop = open->op_stateowner; + unsigned int hashval = stateid_hashval(sop->so_id, fp->fi_id); + + INIT_LIST_HEAD(&stp->st_hash); + INIT_LIST_HEAD(&stp->st_perfilestate); + INIT_LIST_HEAD(&stp->st_perlockowner); + INIT_LIST_HEAD(&stp->st_perfile); + list_add(&stp->st_hash, &stateid_hashtbl[hashval]); + list_add(&stp->st_perfilestate, &sop->so_perfilestate); + list_add_perfile++; + list_add(&stp->st_perfile, &fp->fi_perfile); + stp->st_stateowner = sop; + stp->st_file = fp; + stp->st_stateid.si_boot = boot_time; + stp->st_stateid.si_stateownerid = sop->so_id; + stp->st_stateid.si_fileid = fp->fi_id; + stp->st_stateid.si_generation = 0; + stp->st_access_bmap = 0; + stp->st_deny_bmap = 0; + __set_bit(open->op_share_access, &stp->st_access_bmap); + __set_bit(open->op_share_deny, &stp->st_deny_bmap); +} + +static void +release_stateid(struct nfs4_stateid *stp, int flags) +{ + struct file *filp = stp->st_vfs_file; + |