aboutsummaryrefslogtreecommitdiff
path: root/fs/nfsd/nfs4state.c
diff options
context:
space:
mode:
Diffstat (limited to 'fs/nfsd/nfs4state.c')
-rw-r--r--fs/nfsd/nfs4state.c3320
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, &current_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;
+