]> git.kernelconcepts.de Git - karo-tx-linux.git/blobdiff - fs/nfsd/nfs4state.c
Merge git://github.com/rustyrussell/linux
[karo-tx-linux.git] / fs / nfsd / nfs4state.c
index a9e71cdf4a840e404f9a1308d539f2b757816cd8..47e94e33a975d801ddefbf36d4ed793333ff141b 100644 (file)
@@ -32,7 +32,6 @@
 *
 */
 
-#include <linux/idr.h>
 #include <linux/file.h>
 #include <linux/fs.h>
 #include <linux/slab.h>
@@ -105,6 +104,11 @@ opaque_hashval(const void *ptr, int nbytes)
 
 static struct list_head del_recall_lru;
 
+static void nfsd4_free_file(struct nfs4_file *f)
+{
+       kmem_cache_free(file_slab, f);
+}
+
 static inline void
 put_nfs4_file(struct nfs4_file *fi)
 {
@@ -112,7 +116,7 @@ put_nfs4_file(struct nfs4_file *fi)
                list_del(&fi->fi_hash);
                spin_unlock(&recall_lock);
                iput(fi->fi_inode);
-               kmem_cache_free(file_slab, fi);
+               nfsd4_free_file(fi);
        }
 }
 
@@ -149,8 +153,6 @@ static struct list_head     open_ownerstr_hashtbl[OPEN_OWNER_HASH_SIZE];
 #define FILE_HASH_BITS                   8
 #define FILE_HASH_SIZE                  (1 << FILE_HASH_BITS)
 
-struct idr stateids;
-
 static unsigned int file_hashval(struct inode *ino)
 {
        /* XXX: why are we hashing on inode pointer, anyway? */
@@ -209,16 +211,16 @@ static void nfs4_file_put_access(struct nfs4_file *fp, int oflag)
 static inline int get_new_stid(struct nfs4_stid *stid)
 {
        static int min_stateid = 0;
+       struct idr *stateids = &stid->sc_client->cl_stateids;
        int new_stid;
        int error;
 
-       if (!idr_pre_get(&stateids, GFP_KERNEL))
-               return -ENOMEM;
-
-       error = idr_get_new_above(&stateids, stid, min_stateid, &new_stid);
+       error = idr_get_new_above(stateids, stid, min_stateid, &new_stid);
        /*
-        * All this code is currently serialized; the preallocation
-        * above should still be ours:
+        * Note: the necessary preallocation was done in
+        * nfs4_alloc_stateid().  The idr code caps the number of
+        * preallocations that can exist at a time, but the state lock
+        * prevents anyone from using ours before we get here:
         */
        BUG_ON(error);
        /*
@@ -237,7 +239,7 @@ static inline int get_new_stid(struct nfs4_stid *stid)
        return new_stid;
 }
 
-static inline __be32 init_stid(struct nfs4_stid *stid, struct nfs4_client *cl, unsigned char type)
+static void init_stid(struct nfs4_stid *stid, struct nfs4_client *cl, unsigned char type)
 {
        stateid_t *s = &stid->sc_stateid;
        int new_id;
@@ -246,12 +248,29 @@ static inline __be32 init_stid(struct nfs4_stid *stid, struct nfs4_client *cl, u
        stid->sc_client = cl;
        s->si_opaque.so_clid = cl->cl_clientid;
        new_id = get_new_stid(stid);
-       if (new_id < 0)
-               return nfserr_jukebox;
        s->si_opaque.so_id = (u32)new_id;
        /* Will be incremented before return to client: */
        s->si_generation = 0;
-       return 0;
+}
+
+static struct nfs4_stid *nfs4_alloc_stid(struct nfs4_client *cl, struct kmem_cache *slab)
+{
+       struct idr *stateids = &cl->cl_stateids;
+
+       if (!idr_pre_get(stateids, GFP_KERNEL))
+               return NULL;
+       /*
+        * Note: if we fail here (or any time between now and the time
+        * we actually get the new idr), we won't need to undo the idr
+        * preallocation, since the idr code caps the number of
+        * preallocated entries.
+        */
+       return kmem_cache_alloc(slab, GFP_KERNEL);
+}
+
+static struct nfs4_ol_stateid * nfs4_alloc_stateid(struct nfs4_client *clp)
+{
+       return openlockstateid(nfs4_alloc_stid(clp, stateid_slab));
 }
 
 static struct nfs4_delegation *
@@ -259,7 +278,6 @@ alloc_init_deleg(struct nfs4_client *clp, struct nfs4_ol_stateid *stp, struct sv
 {
        struct nfs4_delegation *dp;
        struct nfs4_file *fp = stp->st_file;
-       __be32 status;
 
        dprintk("NFSD alloc_init_deleg\n");
        /*
@@ -273,14 +291,10 @@ alloc_init_deleg(struct nfs4_client *clp, struct nfs4_ol_stateid *stp, struct sv
                return NULL;
        if (num_delegations > max_delegations)
                return NULL;
-       dp = kmem_cache_alloc(deleg_slab, GFP_KERNEL);
+       dp = delegstateid(nfs4_alloc_stid(clp, deleg_slab));
        if (dp == NULL)
                return dp;
-       status = init_stid(&dp->dl_stid, clp, NFS4_DELEG_STID);
-       if (status) {
-               kmem_cache_free(deleg_slab, dp);
-               return NULL;
-       }
+       init_stid(&dp->dl_stid, clp, NFS4_DELEG_STID);
        /*
         * delegation seqid's are never incremented.  The 4.1 special
         * meaning of seqid 0 isn't meaningful, really, but let's avoid
@@ -324,7 +338,9 @@ static void nfs4_put_deleg_lease(struct nfs4_file *fp)
 
 static void unhash_stid(struct nfs4_stid *s)
 {
-       idr_remove(&stateids, s->sc_stateid.si_opaque.so_id);
+       struct idr *stateids = &s->sc_client->cl_stateids;
+
+       idr_remove(stateids, s->sc_stateid.si_opaque.so_id);
 }
 
 /* Called under the state lock. */
@@ -931,9 +947,6 @@ renew_client_locked(struct nfs4_client *clp)
                return;
        }
 
-       /*
-       * 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);
@@ -1126,16 +1139,16 @@ static void gen_confirm(struct nfs4_client *clp)
        *p++ = i++;
 }
 
-static struct nfs4_stid *find_stateid(stateid_t *t)
+static struct nfs4_stid *find_stateid(struct nfs4_client *cl, stateid_t *t)
 {
-       return idr_find(&stateids, t->si_opaque.so_id);
+       return idr_find(&cl->cl_stateids, t->si_opaque.so_id);
 }
 
-static struct nfs4_stid *find_stateid_by_type(stateid_t *t, char typemask)
+static struct nfs4_stid *find_stateid_by_type(struct nfs4_client *cl, stateid_t *t, char typemask)
 {
        struct nfs4_stid *s;
 
-       s = find_stateid(t);
+       s = find_stateid(cl, t);
        if (!s)
                return NULL;
        if (typemask & s->sc_type)
@@ -1143,16 +1156,6 @@ static struct nfs4_stid *find_stateid_by_type(stateid_t *t, char typemask)
        return NULL;
 }
 
-static struct nfs4_ol_stateid *find_ol_stateid_by_type(stateid_t *t, char typemask)
-{
-       struct nfs4_stid *s;
-
-       s = find_stateid_by_type(t, typemask);
-       if (!s)
-               return NULL;
-       return openlockstateid(s);
-}
-
 static struct nfs4_client *create_client(struct xdr_netobj name, char *recdir,
                struct svc_rqst *rqstp, nfs4_verifier *verf)
 {
@@ -1175,6 +1178,7 @@ static struct nfs4_client *create_client(struct xdr_netobj name, char *recdir,
                }
        }
 
+       idr_init(&clp->cl_stateids);
        memcpy(clp->cl_recdir, recdir, HEXDIR_LEN);
        atomic_set(&clp->cl_refcount, 0);
        clp->cl_cb_state = NFSD4_CB_UNKNOWN;
@@ -1198,17 +1202,6 @@ static struct nfs4_client *create_client(struct xdr_netobj name, char *recdir,
        return clp;
 }
 
-static int check_name(struct xdr_netobj name)
-{
-       if (name.len == 0) 
-               return 0;
-       if (name.len > NFS4_OPAQUE_LIMIT) {
-               dprintk("NFSD: check_name: name too long(%d)!\n", name.len);
-               return 0;
-       }
-       return 1;
-}
-
 static void
 add_to_unconfirmed(struct nfs4_client *clp, unsigned int strhashval)
 {
@@ -1240,8 +1233,10 @@ find_confirmed_client(clientid_t *clid)
        unsigned int idhashval = clientid_hashval(clid->cl_id);
 
        list_for_each_entry(clp, &conf_id_hashtbl[idhashval], cl_idhash) {
-               if (same_clid(&clp->cl_clientid, clid))
+               if (same_clid(&clp->cl_clientid, clid)) {
+                       renew_client(clp);
                        return clp;
+               }
        }
        return NULL;
 }
@@ -1451,7 +1446,7 @@ nfsd4_exchange_id(struct svc_rqst *rqstp,
                __func__, rqstp, exid, exid->clname.len, exid->clname.data,
                addr_str, exid->flags, exid->spa_how);
 
-       if (!check_name(exid->clname) || (exid->flags & ~EXCHGID4_FLAG_MASK_A))
+       if (exid->flags & ~EXCHGID4_FLAG_MASK_A)
                return nfserr_inval;
 
        /* Currently only support SP4_NONE */
@@ -1950,8 +1945,16 @@ out:
 
                nfsd4_get_session(cstate->session);
                atomic_inc(&clp->cl_refcount);
-               if (clp->cl_cb_state == NFSD4_CB_DOWN)
-                       seq->status_flags |= SEQ4_STATUS_CB_PATH_DOWN;
+               switch (clp->cl_cb_state) {
+               case NFSD4_CB_DOWN:
+                       seq->status_flags = SEQ4_STATUS_CB_PATH_DOWN;
+                       break;
+               case NFSD4_CB_FAULT:
+                       seq->status_flags = SEQ4_STATUS_BACKCHANNEL_FAULT;
+                       break;
+               default:
+                       seq->status_flags = 0;
+               }
        }
        kfree(conn);
        spin_unlock(&client_lock);
@@ -1959,6 +1962,50 @@ out:
        return status;
 }
 
+static inline bool has_resources(struct nfs4_client *clp)
+{
+       return !list_empty(&clp->cl_openowners)
+               || !list_empty(&clp->cl_delegations)
+               || !list_empty(&clp->cl_sessions);
+}
+
+__be32
+nfsd4_destroy_clientid(struct svc_rqst *rqstp, struct nfsd4_compound_state *cstate, struct nfsd4_destroy_clientid *dc)
+{
+       struct nfs4_client *conf, *unconf, *clp;
+       int status = 0;
+
+       nfs4_lock_state();
+       unconf = find_unconfirmed_client(&dc->clientid);
+       conf = find_confirmed_client(&dc->clientid);
+
+       if (conf) {
+               clp = conf;
+
+               if (!is_client_expired(conf) && has_resources(conf)) {
+                       status = nfserr_clientid_busy;
+                       goto out;
+               }
+
+               /* rfc5661 18.50.3 */
+               if (cstate->session && conf == cstate->session->se_client) {
+                       status = nfserr_clientid_busy;
+                       goto out;
+               }
+       } else if (unconf)
+               clp = unconf;
+       else {
+               status = nfserr_stale_clientid;
+               goto out;
+       }
+
+       expire_client(clp);
+out:
+       nfs4_unlock_state();
+       dprintk("%s return %d\n", __func__, ntohl(status));
+       return status;
+}
+
 __be32
 nfsd4_reclaim_complete(struct svc_rqst *rqstp, struct nfsd4_compound_state *cstate, struct nfsd4_reclaim_complete *rc)
 {
@@ -2001,19 +2048,13 @@ __be32
 nfsd4_setclientid(struct svc_rqst *rqstp, struct nfsd4_compound_state *cstate,
                  struct nfsd4_setclientid *setclid)
 {
-       struct xdr_netobj       clname = { 
-               .len = setclid->se_namelen,
-               .data = setclid->se_name,
-       };
+       struct xdr_netobj       clname = setclid->se_name;
        nfs4_verifier           clverifier = setclid->se_verf;
        unsigned int            strhashval;
        struct nfs4_client      *conf, *unconf, *new;
        __be32                  status;
        char                    dname[HEXDIR_LEN];
        
-       if (!check_name(clname))
-               return nfserr_inval;
-
        status = nfs4_make_rec_clidname(dname, &clname);
        if (status)
                return status;
@@ -2217,30 +2258,28 @@ out:
        return status;
 }
 
+static struct nfs4_file *nfsd4_alloc_file(void)
+{
+       return kmem_cache_alloc(file_slab, GFP_KERNEL);
+}
+
 /* OPEN Share state helper functions */
-static inline struct nfs4_file *
-alloc_init_file(struct inode *ino)
+static void nfsd4_init_file(struct nfs4_file *fp, struct inode *ino)
 {
-       struct nfs4_file *fp;
        unsigned int hashval = file_hashval(ino);
 
-       fp = kmem_cache_alloc(file_slab, GFP_KERNEL);
-       if (fp) {
-               atomic_set(&fp->fi_ref, 1);
-               INIT_LIST_HEAD(&fp->fi_hash);
-               INIT_LIST_HEAD(&fp->fi_stateids);
-               INIT_LIST_HEAD(&fp->fi_delegations);
-               fp->fi_inode = igrab(ino);
-               fp->fi_had_conflict = false;
-               fp->fi_lease = NULL;
-               memset(fp->fi_fds, 0, sizeof(fp->fi_fds));
-               memset(fp->fi_access, 0, sizeof(fp->fi_access));
-               spin_lock(&recall_lock);
-               list_add(&fp->fi_hash, &file_hashtbl[hashval]);
-               spin_unlock(&recall_lock);
-               return fp;
-       }
-       return NULL;
+       atomic_set(&fp->fi_ref, 1);
+       INIT_LIST_HEAD(&fp->fi_hash);
+       INIT_LIST_HEAD(&fp->fi_stateids);
+       INIT_LIST_HEAD(&fp->fi_delegations);
+       fp->fi_inode = igrab(ino);
+       fp->fi_had_conflict = false;
+       fp->fi_lease = NULL;
+       memset(fp->fi_fds, 0, sizeof(fp->fi_fds));
+       memset(fp->fi_access, 0, sizeof(fp->fi_access));
+       spin_lock(&recall_lock);
+       list_add(&fp->fi_hash, &file_hashtbl[hashval]);
+       spin_unlock(&recall_lock);
 }
 
 static void
@@ -2347,7 +2386,7 @@ alloc_init_open_stateowner(unsigned int strhashval, struct nfs4_client *clp, str
                return NULL;
        oo->oo_owner.so_is_open_owner = 1;
        oo->oo_owner.so_seqid = open->op_seqid;
-       oo->oo_flags = 0;
+       oo->oo_flags = NFS4_OO_NEW;
        oo->oo_time = 0;
        oo->oo_last_closed_stid = NULL;
        INIT_LIST_HEAD(&oo->oo_close_lru);
@@ -2355,14 +2394,11 @@ alloc_init_open_stateowner(unsigned int strhashval, struct nfs4_client *clp, str
        return oo;
 }
 
-static inline __be32 init_open_stateid(struct nfs4_ol_stateid *stp, struct nfs4_file *fp, struct nfsd4_open *open) {
+static void init_open_stateid(struct nfs4_ol_stateid *stp, struct nfs4_file *fp, struct nfsd4_open *open) {
        struct nfs4_openowner *oo = open->op_openowner;
        struct nfs4_client *clp = oo->oo_owner.so_client;
-       __be32 status;
 
-       status = init_stid(&stp->st_stid, clp, NFS4_OPEN_STID);
-       if (status)
-               return status;
+       init_stid(&stp->st_stid, clp, NFS4_OPEN_STID);
        INIT_LIST_HEAD(&stp->st_lockowners);
        list_add(&stp->st_perstateowner, &oo->oo_owner.so_stateids);
        list_add(&stp->st_perfile, &fp->fi_stateids);
@@ -2371,11 +2407,9 @@ static inline __be32 init_open_stateid(struct nfs4_ol_stateid *stp, struct nfs4_
        stp->st_file = fp;
        stp->st_access_bmap = 0;
        stp->st_deny_bmap = 0;
-       __set_bit(open->op_share_access & ~NFS4_SHARE_WANT_MASK,
-                 &stp->st_access_bmap);
+       __set_bit(open->op_share_access, &stp->st_access_bmap);
        __set_bit(open->op_share_deny, &stp->st_deny_bmap);
        stp->st_openstp = NULL;
-       return nfs_ok;
 }
 
 static void
@@ -2399,11 +2433,15 @@ same_owner_str(struct nfs4_stateowner *sop, struct xdr_netobj *owner,
 static struct nfs4_openowner *
 find_openstateowner_str(unsigned int hashval, struct nfsd4_open *open)
 {
-       struct nfs4_stateowner *so = NULL;
+       struct nfs4_stateowner *so;
+       struct nfs4_openowner *oo;
 
        list_for_each_entry(so, &open_ownerstr_hashtbl[hashval], so_strhash) {
-               if (same_owner_str(so, &open->op_owner, &open->op_clientid))
-                       return container_of(so, struct nfs4_openowner, oo_owner);
+               if (same_owner_str(so, &open->op_owner, &open->op_clientid)) {
+                       oo = openowner(so);
+                       renew_client(oo->oo_owner.so_client);
+                       return oo;
+               }
        }
        return NULL;
 }
@@ -2427,31 +2465,6 @@ find_file(struct inode *ino)
        return NULL;
 }
 
-static inline int access_valid(u32 x, u32 minorversion)
-{
-       if ((x & NFS4_SHARE_ACCESS_MASK) < NFS4_SHARE_ACCESS_READ)
-               return 0;
-       if ((x & NFS4_SHARE_ACCESS_MASK) > NFS4_SHARE_ACCESS_BOTH)
-               return 0;
-       x &= ~NFS4_SHARE_ACCESS_MASK;
-       if (minorversion && x) {
-               if ((x & NFS4_SHARE_WANT_MASK) > NFS4_SHARE_WANT_CANCEL)
-                       return 0;
-               if ((x & NFS4_SHARE_WHEN_MASK) > NFS4_SHARE_PUSH_DELEG_WHEN_UNCONTENDED)
-                       return 0;
-               x &= ~(NFS4_SHARE_WANT_MASK | NFS4_SHARE_WHEN_MASK);
-       }
-       if (x)
-               return 0;
-       return 1;
-}
-
-static inline int deny_valid(u32 x)
-{
-       /* Note: unlike access bits, deny bits may be zero. */
-       return x <= NFS4_SHARE_DENY_BOTH;
-}
-
 /*
  * Called to check deny when READ with all zero stateid or
  * WRITE with all zero or all one stateid
@@ -2557,41 +2570,46 @@ nfsd4_process_open1(struct nfsd4_compound_state *cstate,
        struct nfs4_openowner *oo = NULL;
        __be32 status;
 
-       if (!check_name(open->op_owner))
-               return nfserr_inval;
-
        if (STALE_CLIENTID(&open->op_clientid))
                return nfserr_stale_clientid;
+       /*
+        * In case we need it later, after we've already created the
+        * file and don't want to risk a further failure:
+        */
+       open->op_file = nfsd4_alloc_file();
+       if (open->op_file == NULL)
+               return nfserr_jukebox;
 
        strhashval = open_ownerstr_hashval(clientid->cl_id, &open->op_owner);
        oo = find_openstateowner_str(strhashval, open);
        open->op_openowner = oo;
        if (!oo) {
-               /* Make sure the client's lease hasn't expired. */
                clp = find_confirmed_client(clientid);
                if (clp == NULL)
                        return nfserr_expired;
-               goto renew;
+               goto new_owner;
        }
        if (!(oo->oo_flags & NFS4_OO_CONFIRMED)) {
                /* Replace unconfirmed owners without checking for replay. */
                clp = oo->oo_owner.so_client;
                release_openowner(oo);
                open->op_openowner = NULL;
-               goto renew;
+               goto new_owner;
        }
        status = nfsd4_check_seqid(cstate, &oo->oo_owner, open->op_seqid);
        if (status)
                return status;
-renew:
-       if (open->op_openowner == NULL) {
-               oo = alloc_init_open_stateowner(strhashval, clp, open);
-               if (oo == NULL)
-                       return nfserr_jukebox;
-               open->op_openowner = oo;
-       }
-       list_del_init(&oo->oo_close_lru);
-       renew_client(oo->oo_owner.so_client);
+       clp = oo->oo_owner.so_client;
+       goto alloc_stateid;
+new_owner:
+       oo = alloc_init_open_stateowner(strhashval, clp, open);
+       if (oo == NULL)
+               return nfserr_jukebox;
+       open->op_openowner = oo;
+alloc_stateid:
+       open->op_stp = nfs4_alloc_stateid(clp);
+       if (!open->op_stp)
+               return nfserr_jukebox;
        return nfs_ok;
 }
 
@@ -2611,24 +2629,30 @@ static int share_access_to_flags(u32 share_access)
        return share_access == NFS4_SHARE_ACCESS_READ ? RD_STATE : WR_STATE;
 }
 
-static struct nfs4_delegation *find_deleg_stateid(stateid_t *s)
+static struct nfs4_delegation *find_deleg_stateid(struct nfs4_client *cl, stateid_t *s)
 {
        struct nfs4_stid *ret;
 
-       ret = find_stateid_by_type(s, NFS4_DELEG_STID);
+       ret = find_stateid_by_type(cl, s, NFS4_DELEG_STID);
        if (!ret)
                return NULL;
        return delegstateid(ret);
 }
 
+static bool nfsd4_is_deleg_cur(struct nfsd4_open *open)
+{
+       return open->op_claim_type == NFS4_OPEN_CLAIM_DELEGATE_CUR ||
+              open->op_claim_type == NFS4_OPEN_CLAIM_DELEG_CUR_FH;
+}
+
 static __be32
-nfs4_check_deleg(struct nfs4_file *fp, struct nfsd4_open *open,
+nfs4_check_deleg(struct nfs4_client *cl, struct nfs4_file *fp, struct nfsd4_open *open,
                struct nfs4_delegation **dp)
 {
        int flags;
        __be32 status = nfserr_bad_stateid;
 
-       *dp = find_deleg_stateid(&open->op_delegate_stateid);
+       *dp = find_deleg_stateid(cl, &open->op_delegate_stateid);
        if (*dp == NULL)
                goto out;
        flags = share_access_to_flags(open->op_share_access);
@@ -2636,7 +2660,7 @@ nfs4_check_deleg(struct nfs4_file *fp, struct nfsd4_open *open,
        if (status)
                *dp = NULL;
 out:
-       if (open->op_claim_type != NFS4_OPEN_CLAIM_DELEGATE_CUR)
+       if (!nfsd4_is_deleg_cur(open))
                return nfs_ok;
        if (status)
                return status;
@@ -2664,10 +2688,9 @@ nfs4_check_open(struct nfs4_file *fp, struct nfsd4_open *open, struct nfs4_ol_st
        return nfs_ok;
 }
 
-static inline struct nfs4_ol_stateid *
-nfs4_alloc_stateid(void)
+static void nfs4_free_stateid(struct nfs4_ol_stateid *s)
 {
-       return kmem_cache_alloc(stateid_slab, GFP_KERNEL);
+       kmem_cache_free(stateid_slab, s);
 }
 
 static inline int nfs4_access_to_access(u32 nfs4_access)
@@ -2688,12 +2711,6 @@ static __be32 nfs4_get_vfs_file(struct svc_rqst *rqstp, struct nfs4_file *fp,
        int oflag = nfs4_access_to_omode(open->op_share_access);
        int access = nfs4_access_to_access(open->op_share_access);
 
-       /* CLAIM_DELEGATE_CUR is used in response to a broken lease;
-        * allowing it to break the lease and return EAGAIN leaves the
-        * client unable to make progress in returning the delegation */
-       if (open->op_claim_type == NFS4_OPEN_CLAIM_DELEGATE_CUR)
-               access |= NFSD_MAY_NOT_BREAK_LEASE;
-
        if (!fp->fi_fds[oflag]) {
                status = nfsd_open(rqstp, cur_fh, S_IFREG, access,
                        &fp->fi_fds[oflag]);
@@ -2705,27 +2722,6 @@ static __be32 nfs4_get_vfs_file(struct svc_rqst *rqstp, struct nfs4_file *fp,
        return nfs_ok;
 }
 
-static __be32
-nfs4_new_open(struct svc_rqst *rqstp, struct nfs4_ol_stateid **stpp,
-               struct nfs4_file *fp, struct svc_fh *cur_fh,
-               struct nfsd4_open *open)
-{
-       struct nfs4_ol_stateid *stp;
-       __be32 status;
-
-       stp = nfs4_alloc_stateid();
-       if (stp == NULL)
-               return nfserr_jukebox;
-
-       status = nfs4_get_vfs_file(rqstp, fp, cur_fh, open);
-       if (status) {
-               kmem_cache_free(stateid_slab, stp);
-               return status;
-       }
-       *stpp = stp;
-       return 0;
-}
-
 static inline __be32
 nfsd4_truncate(struct svc_rqst *rqstp, struct svc_fh *fh,
                struct nfsd4_open *open)
@@ -2744,7 +2740,7 @@ nfsd4_truncate(struct svc_rqst *rqstp, struct svc_fh *fh,
 static __be32
 nfs4_upgrade_open(struct svc_rqst *rqstp, struct nfs4_file *fp, struct svc_fh *cur_fh, struct nfs4_ol_stateid *stp, struct nfsd4_open *open)
 {
-       u32 op_share_access = open->op_share_access & ~NFS4_SHARE_WANT_MASK;
+       u32 op_share_access = open->op_share_access;
        bool new_access;
        __be32 status;
 
@@ -2920,16 +2916,13 @@ __be32
 nfsd4_process_open2(struct svc_rqst *rqstp, struct svc_fh *current_fh, struct nfsd4_open *open)
 {
        struct nfsd4_compoundres *resp = rqstp->rq_resp;
+       struct nfs4_client *cl = open->op_openowner->oo_owner.so_client;
        struct nfs4_file *fp = NULL;
        struct inode *ino = current_fh->fh_dentry->d_inode;
        struct nfs4_ol_stateid *stp = NULL;
        struct nfs4_delegation *dp = NULL;
        __be32 status;
 
-       status = nfserr_inval;
-       if (!access_valid(open->op_share_access, resp->cstate.minorversion)
-                       || !deny_valid(open->op_share_deny))
-               goto out;
        /*
         * Lookup file; if found, lookup stateid and check open request,
         * and check for delegations in the process of being recalled.
@@ -2939,17 +2932,17 @@ nfsd4_process_open2(struct svc_rqst *rqstp, struct svc_fh *current_fh, struct nf
        if (fp) {
                if ((status = nfs4_check_open(fp, open, &stp)))
                        goto out;
-               status = nfs4_check_deleg(fp, open, &dp);
+               status = nfs4_check_deleg(cl, fp, open, &dp);
                if (status)
                        goto out;
        } else {
                status = nfserr_bad_stateid;
-               if (open->op_claim_type == NFS4_OPEN_CLAIM_DELEGATE_CUR)
+               if (nfsd4_is_deleg_cur(open))
                        goto out;
                status = nfserr_jukebox;
-               fp = alloc_init_file(ino);
-               if (fp == NULL)
-                       goto out;
+               fp = open->op_file;
+               open->op_file = NULL;
+               nfsd4_init_file(fp, ino);
        }
 
        /*
@@ -2962,14 +2955,12 @@ nfsd4_process_open2(struct svc_rqst *rqstp, struct svc_fh *current_fh, struct nf
                if (status)
                        goto out;
        } else {
-               status = nfs4_new_open(rqstp, &stp, fp, current_fh, open);
+               status = nfs4_get_vfs_file(rqstp, fp, current_fh, open);
                if (status)
                        goto out;
-               status = init_open_stateid(stp, fp, open);
-               if (status) {
-                       release_open_stateid(stp);
-                       goto out;
-               }
+               stp = open->op_stp;
+               open->op_stp = NULL;
+               init_open_stateid(stp, fp, open);
                status = nfsd4_truncate(rqstp, current_fh, open);
                if (status) {
                        release_open_stateid(stp);
@@ -3008,6 +2999,27 @@ out:
        return status;
 }
 
+void nfsd4_cleanup_open_state(struct nfsd4_open *open, __be32 status)
+{
+       if (open->op_openowner) {
+               struct nfs4_openowner *oo = open->op_openowner;
+
+               if (!list_empty(&oo->oo_owner.so_stateids))
+                       list_del_init(&oo->oo_close_lru);
+               if (oo->oo_flags & NFS4_OO_NEW) {
+                       if (status) {
+                               release_openowner(oo);
+                               open->op_openowner = NULL;
+                       } else
+                               oo->oo_flags &= ~NFS4_OO_NEW;
+               }
+       }
+       if (open->op_file)
+               nfsd4_free_file(open->op_file);
+       if (open->op_stp)
+               nfs4_free_stateid(open->op_stp);
+}
+
 __be32
 nfsd4_renew(struct svc_rqst *rqstp, struct nfsd4_compound_state *cstate,
            clientid_t *clid)
@@ -3028,7 +3040,6 @@ nfsd4_renew(struct svc_rqst *rqstp, struct nfsd4_compound_state *cstate,
                dprintk("nfsd4_renew: clientid not found!\n");
                goto out;
        }
-       renew_client(clp);
        status = nfserr_cb_path_down;
        if (!list_empty(&clp->cl_delegations)
                        && clp->cl_cb_state != NFSD4_CB_UP)
@@ -3256,7 +3267,7 @@ static int check_stateid_generation(stateid_t *in, stateid_t *ref, bool has_sess
        return nfserr_old_stateid;
 }
 
-__be32 nfs4_validate_stateid(stateid_t *stateid, bool has_session)
+__be32 nfs4_validate_stateid(struct nfs4_client *cl, stateid_t *stateid)
 {
        struct nfs4_stid *s;
        struct nfs4_ol_stateid *ols;
@@ -3265,10 +3276,10 @@ __be32 nfs4_validate_stateid(stateid_t *stateid, bool has_session)
        if (STALE_STATEID(stateid))
                return nfserr_stale_stateid;
 
-       s = find_stateid(stateid);
+       s = find_stateid(cl, stateid);
        if (!s)
                 return nfserr_stale_stateid;
-       status = check_stateid_generation(stateid, &s->sc_stateid, has_session);
+       status = check_stateid_generation(stateid, &s->sc_stateid, 1);
        if (status)
                return status;
        if (!(s->sc_type & (NFS4_OPEN_STID | NFS4_LOCK_STID)))
@@ -3280,6 +3291,24 @@ __be32 nfs4_validate_stateid(stateid_t *stateid, bool has_session)
        return nfs_ok;
 }
 
+static __be32 nfsd4_lookup_stateid(stateid_t *stateid, unsigned char typemask, struct nfs4_stid **s)
+{
+       struct nfs4_client *cl;
+
+       if (ZERO_STATEID(stateid) || ONE_STATEID(stateid))
+               return nfserr_bad_stateid;
+       if (STALE_STATEID(stateid))
+               return nfserr_stale_stateid;
+       cl = find_confirmed_client(&stateid->si_opaque.so_clid);
+       if (!cl)
+               return nfserr_expired;
+       *s = find_stateid_by_type(cl, stateid, typemask);
+       if (!*s)
+               return nfserr_bad_stateid;
+       return nfs_ok;
+
+}
+
 /*
 * Checks for stateid operations
 */
@@ -3303,18 +3332,9 @@ nfs4_preprocess_stateid_op(struct nfsd4_compound_state *cstate,
        if (ZERO_STATEID(stateid) || ONE_STATEID(stateid))
                return check_special_stateids(current_fh, stateid, flags);
 
-       status = nfserr_stale_stateid;
-       if (STALE_STATEID(stateid)) 
-               goto out;
-
-       /*
-        * We assume that any stateid that has the current boot time,
-        * but that we can't find, is expired:
-        */
-       status = nfserr_expired;
-       s = find_stateid(stateid);
-       if (!s)
-               goto out;
+       status = nfsd4_lookup_stateid(stateid, NFS4_DELEG_STID|NFS4_OPEN_STID|NFS4_LOCK_STID, &s);
+       if (status)
+               return status;
        status = check_stateid_generation(stateid, &s->sc_stateid, nfsd4_has_session(cstate));
        if (status)
                goto out;
@@ -3324,7 +3344,6 @@ nfs4_preprocess_stateid_op(struct nfsd4_compound_state *cstate,
                status = nfs4_check_delegmode(dp, flags);
                if (status)
                        goto out;
-               renew_client(dp->dl_stid.sc_client);
                if (filpp) {
                        *filpp = dp->dl_file->fi_deleg_file;
                        BUG_ON(!*filpp);
@@ -3342,7 +3361,6 @@ nfs4_preprocess_stateid_op(struct nfsd4_compound_state *cstate,
                status = nfs4_check_openmode(stp, flags);
                if (status)
                        goto out;
-               renew_client(stp->st_stateowner->so_client);
                if (filpp) {
                        if (flags & RD_STATE)
                                *filpp = find_readable_file(stp->st_file);
@@ -3374,7 +3392,7 @@ __be32
 nfsd4_test_stateid(struct svc_rqst *rqstp, struct nfsd4_compound_state *cstate,
                   struct nfsd4_test_stateid *test_stateid)
 {
-       test_stateid->ts_has_session = nfsd4_has_session(cstate);
+       /* real work is done during encoding */
        return nfs_ok;
 }
 
@@ -3384,10 +3402,11 @@ nfsd4_free_stateid(struct svc_rqst *rqstp, struct nfsd4_compound_state *cstate,
 {
        stateid_t *stateid = &free_stateid->fr_stateid;
        struct nfs4_stid *s;
+       struct nfs4_client *cl = cstate->session->se_client;
        __be32 ret = nfserr_bad_stateid;
 
        nfs4_lock_state();
-       s = find_stateid(stateid);
+       s = find_stateid(cl, stateid);
        if (!s)
                goto out;
        switch (s->sc_type) {
@@ -3419,15 +3438,6 @@ setlkflg (int type)
                RD_STATE : WR_STATE;
 }
 
-static __be32 nfs4_nospecial_stateid_checks(stateid_t *stateid)
-{
-       if (ZERO_STATEID(stateid) || ONE_STATEID(stateid))
-               return nfserr_bad_stateid;
-       if (STALE_STATEID(stateid))
-               return nfserr_stale_stateid;
-       return nfs_ok;
-}
-
 static __be32 nfs4_seqid_op_checks(struct nfsd4_compound_state *cstate, stateid_t *stateid, u32 seqid, struct nfs4_ol_stateid *stp)
 {
        struct svc_fh *current_fh = &cstate->current_fh;
@@ -3458,19 +3468,17 @@ nfs4_preprocess_seqid_op(struct nfsd4_compound_state *cstate, u32 seqid,
                         struct nfs4_ol_stateid **stpp)
 {
        __be32 status;
+       struct nfs4_stid *s;
 
        dprintk("NFSD: %s: seqid=%d stateid = " STATEID_FMT "\n", __func__,
                seqid, STATEID_VAL(stateid));
 
        *stpp = NULL;
-       status = nfs4_nospecial_stateid_checks(stateid);
+       status = nfsd4_lookup_stateid(stateid, typemask, &s);
        if (status)
                return status;
-       *stpp = find_ol_stateid_by_type(stateid, typemask);
-       if (*stpp == NULL)
-               return nfserr_expired;
+       *stpp = openlockstateid(s);
        cstate->replay_owner = (*stpp)->st_stateowner;
-       renew_client((*stpp)->st_stateowner->so_client);
 
        return nfs4_seqid_op_checks(cstate, stateid, seqid, *stpp);
 }
@@ -3531,16 +3539,29 @@ out:
        return status;
 }
 
-static inline void nfs4_file_downgrade(struct nfs4_ol_stateid *stp, unsigned int to_access)
+static inline void nfs4_stateid_downgrade_bit(struct nfs4_ol_stateid *stp, u32 access)
 {
-       int i;
+       if (!test_bit(access, &stp->st_access_bmap))
+               return;
+       nfs4_file_put_access(stp->st_file, nfs4_access_to_omode(access));
+       __clear_bit(access, &stp->st_access_bmap);
+}
 
-       for (i = 1; i < 4; i++) {
-               if (test_bit(i, &stp->st_access_bmap)
-                                       && ((i & to_access) != i)) {
-                       nfs4_file_put_access(stp->st_file, nfs4_access_to_omode(i));
-                       __clear_bit(i, &stp->st_access_bmap);
-               }
+static inline void nfs4_stateid_downgrade(struct nfs4_ol_stateid *stp, u32 to_access)
+{
+       switch (to_access) {
+       case NFS4_SHARE_ACCESS_READ:
+               nfs4_stateid_downgrade_bit(stp, NFS4_SHARE_ACCESS_WRITE);
+               nfs4_stateid_downgrade_bit(stp, NFS4_SHARE_ACCESS_BOTH);
+               break;
+       case NFS4_SHARE_ACCESS_WRITE:
+               nfs4_stateid_downgrade_bit(stp, NFS4_SHARE_ACCESS_READ);
+               nfs4_stateid_downgrade_bit(stp, NFS4_SHARE_ACCESS_BOTH);
+               break;
+       case NFS4_SHARE_ACCESS_BOTH:
+               break;
+       default:
+               BUG();
        }
 }
 
@@ -3566,9 +3587,8 @@ nfsd4_open_downgrade(struct svc_rqst *rqstp,
                        (int)cstate->current_fh.fh_dentry->d_name.len,
                        cstate->current_fh.fh_dentry->d_name.name);
 
-       if (!access_valid(od->od_share_access, cstate->minorversion)
-                       || !deny_valid(od->od_share_deny))
-               return nfserr_inval;
+       /* We don't yet support WANT bits: */
+       od->od_share_access &= NFS4_SHARE_ACCESS_MASK;
 
        nfs4_lock_state();
        status = nfs4_preprocess_confirmed_seqid_op(cstate, od->od_seqid,
@@ -3586,7 +3606,7 @@ nfsd4_open_downgrade(struct svc_rqst *rqstp,
                        stp->st_deny_bmap, od->od_share_deny);
                goto out;
        }
-       nfs4_file_downgrade(stp, od->od_share_access);
+       nfs4_stateid_downgrade(stp, od->od_share_access);
 
        reset_union_bmap_deny(od->od_share_deny, &stp->st_deny_bmap);
 
@@ -3673,6 +3693,7 @@ nfsd4_delegreturn(struct svc_rqst *rqstp, struct nfsd4_compound_state *cstate,
 {
        struct nfs4_delegation *dp;
        stateid_t *stateid = &dr->dr_stateid;
+       struct nfs4_stid *s;
        struct inode *inode;
        __be32 status;
 
@@ -3681,20 +3702,13 @@ nfsd4_delegreturn(struct svc_rqst *rqstp, struct nfsd4_compound_state *cstate,
        inode = cstate->current_fh.fh_dentry->d_inode;
 
        nfs4_lock_state();
-       status = nfserr_bad_stateid;
-       if (ZERO_STATEID(stateid) || ONE_STATEID(stateid))
-               goto out;
-       status = nfserr_stale_stateid;
-       if (STALE_STATEID(stateid))
-               goto out;
-       status = nfserr_expired;
-       dp = find_deleg_stateid(stateid);
-       if (!dp)
+       status = nfsd4_lookup_stateid(stateid, NFS4_DELEG_STID, &s);
+       if (status)
                goto out;
+       dp = delegstateid(s);
        status = check_stateid_generation(stateid, &dp->dl_stid.sc_stateid, nfsd4_has_session(cstate));
        if (status)
                goto out;
-       renew_client(dp->dl_stid.sc_client);
 
        unhash_delegation(dp);
 out:
@@ -3844,16 +3858,11 @@ alloc_init_lock_stateid(struct nfs4_lockowner *lo, struct nfs4_file *fp, struct
 {
        struct nfs4_ol_stateid *stp;
        struct nfs4_client *clp = lo->lo_owner.so_client;
-       __be32 status;
 
-       stp = nfs4_alloc_stateid();
+       stp = nfs4_alloc_stateid(clp);
        if (stp == NULL)
                return NULL;
-       status = init_stid(&stp->st_stid, clp, NFS4_LOCK_STID);
-       if (status) {
-               free_generic_stateid(stp);
-               return NULL;
-       }
+       init_stid(&stp->st_stid, clp, NFS4_LOCK_STID);
        list_add(&stp->st_perfile, &fp->fi_stateids);
        list_add(&stp->st_perstateowner, &lo->lo_owner.so_stateids);
        stp->st_stateowner = &lo->lo_owner;
@@ -4198,7 +4207,8 @@ nfsd4_locku(struct svc_rqst *rqstp, struct nfsd4_compound_state *cstate,
        memcpy(&locku->lu_stateid, &stp->st_stid.sc_stateid, sizeof(stateid_t));
 
 out:
-       nfs4_unlock_state();
+       if (!cstate->replay_owner)
+               nfs4_unlock_state();
        return status;
 
 out_nfserr:
@@ -4409,7 +4419,6 @@ nfs4_state_init(void)
        for (i = 0; i < OPEN_OWNER_HASH_SIZE; i++) {
                INIT_LIST_HEAD(&open_ownerstr_hashtbl[i]);
        }
-       idr_init(&stateids);
        for (i = 0; i < LOCK_HASH_SIZE; i++) {
                INIT_LIST_HEAD(&lock_ownerstr_hashtbl[i]);
        }