static struct kmem_cache *file_slab;
 static struct kmem_cache *stateid_slab;
 static struct kmem_cache *deleg_slab;
+static struct kmem_cache *odstate_slab;
 
 static void free_session(struct nfsd4_session *);
 
        if (atomic_dec_and_lock(&fi->fi_ref, &state_lock)) {
                hlist_del_rcu(&fi->fi_hash);
                spin_unlock(&state_lock);
+               WARN_ON_ONCE(!list_empty(&fi->fi_clnt_odstate));
                WARN_ON_ONCE(!list_empty(&fi->fi_delegations));
                call_rcu(&fi->fi_rcu, nfsd4_free_file_rcu);
        }
                __nfs4_file_put_access(fp, O_RDONLY);
 }
 
+/*
+ * Allocate a new open/delegation state counter. This is needed for
+ * pNFS for proper return on close semantics.
+ *
+ * Note that we only allocate it for pNFS-enabled exports, otherwise
+ * all pointers to struct nfs4_clnt_odstate are always NULL.
+ */
+static struct nfs4_clnt_odstate *
+alloc_clnt_odstate(struct nfs4_client *clp)
+{
+       struct nfs4_clnt_odstate *co;
+
+       co = kmem_cache_zalloc(odstate_slab, GFP_KERNEL);
+       if (co) {
+               co->co_client = clp;
+               atomic_set(&co->co_odcount, 1);
+       }
+       return co;
+}
+
+static void
+hash_clnt_odstate_locked(struct nfs4_clnt_odstate *co)
+{
+       struct nfs4_file *fp = co->co_file;
+
+       lockdep_assert_held(&fp->fi_lock);
+       list_add(&co->co_perfile, &fp->fi_clnt_odstate);
+}
+
+static inline void
+get_clnt_odstate(struct nfs4_clnt_odstate *co)
+{
+       if (co)
+               atomic_inc(&co->co_odcount);
+}
+
+static void
+put_clnt_odstate(struct nfs4_clnt_odstate *co)
+{
+       struct nfs4_file *fp;
+
+       if (!co)
+               return;
+
+       fp = co->co_file;
+       if (atomic_dec_and_lock(&co->co_odcount, &fp->fi_lock)) {
+               list_del(&co->co_perfile);
+               spin_unlock(&fp->fi_lock);
+
+               nfsd4_return_all_file_layouts(co->co_client, fp);
+               kmem_cache_free(odstate_slab, co);
+       }
+}
+
+static struct nfs4_clnt_odstate *
+find_or_hash_clnt_odstate(struct nfs4_file *fp, struct nfs4_clnt_odstate *new)
+{
+       struct nfs4_clnt_odstate *co;
+       struct nfs4_client *cl;
+
+       if (!new)
+               return NULL;
+
+       cl = new->co_client;
+
+       spin_lock(&fp->fi_lock);
+       list_for_each_entry(co, &fp->fi_clnt_odstate, co_perfile) {
+               if (co->co_client == cl) {
+                       get_clnt_odstate(co);
+                       goto out;
+               }
+       }
+       co = new;
+       co->co_file = fp;
+       hash_clnt_odstate_locked(new);
+out:
+       spin_unlock(&fp->fi_lock);
+       return co;
+}
+
 struct nfs4_stid *nfs4_alloc_stid(struct nfs4_client *cl,
                                         struct kmem_cache *slab)
 {
 }
 
 static struct nfs4_delegation *
-alloc_init_deleg(struct nfs4_client *clp, struct svc_fh *current_fh)
+alloc_init_deleg(struct nfs4_client *clp, struct svc_fh *current_fh,
+                struct nfs4_clnt_odstate *odstate)
 {
        struct nfs4_delegation *dp;
        long n;
        INIT_LIST_HEAD(&dp->dl_perfile);
        INIT_LIST_HEAD(&dp->dl_perclnt);
        INIT_LIST_HEAD(&dp->dl_recall_lru);
+       dp->dl_clnt_odstate = odstate;
+       get_clnt_odstate(odstate);
        dp->dl_type = NFS4_OPEN_DELEGATE_READ;
        dp->dl_retries = 1;
        nfsd4_init_cb(&dp->dl_recall, dp->dl_stid.sc_client,
        spin_lock(&state_lock);
        unhash_delegation_locked(dp);
        spin_unlock(&state_lock);
+       put_clnt_odstate(dp->dl_clnt_odstate);
        nfs4_put_deleg_lease(dp->dl_stid.sc_file);
        nfs4_put_stid(&dp->dl_stid);
 }
 
        WARN_ON(!list_empty(&dp->dl_recall_lru));
 
+       put_clnt_odstate(dp->dl_clnt_odstate);
        nfs4_put_deleg_lease(dp->dl_stid.sc_file);
 
        if (clp->cl_minorversion == 0)
 {
        struct nfs4_ol_stateid *stp = openlockstateid(stid);
 
+       put_clnt_odstate(stp->st_clnt_odstate);
        release_all_access(stp);
        if (stp->st_stateowner)
                nfs4_put_stateowner(stp->st_stateowner);
        while (!list_empty(&reaplist)) {
                dp = list_entry(reaplist.next, struct nfs4_delegation, dl_recall_lru);
                list_del_init(&dp->dl_recall_lru);
+               put_clnt_odstate(dp->dl_clnt_odstate);
                nfs4_put_deleg_lease(dp->dl_stid.sc_file);
                nfs4_put_stid(&dp->dl_stid);
        }
        spin_lock_init(&fp->fi_lock);
        INIT_LIST_HEAD(&fp->fi_stateids);
        INIT_LIST_HEAD(&fp->fi_delegations);
+       INIT_LIST_HEAD(&fp->fi_clnt_odstate);
        fh_copy_shallow(&fp->fi_fhandle, fh);
        fp->fi_deleg_file = NULL;
        fp->fi_had_conflict = false;
 void
 nfsd4_free_slabs(void)
 {
+       kmem_cache_destroy(odstate_slab);
        kmem_cache_destroy(openowner_slab);
        kmem_cache_destroy(lockowner_slab);
        kmem_cache_destroy(file_slab);
                        sizeof(struct nfs4_delegation), 0, 0, NULL);
        if (deleg_slab == NULL)
                goto out_free_stateid_slab;
+       odstate_slab = kmem_cache_create("nfsd4_odstate",
+                       sizeof(struct nfs4_clnt_odstate), 0, 0, NULL);
+       if (odstate_slab == NULL)
+               goto out_free_deleg_slab;
        return 0;
 
+out_free_deleg_slab:
+       kmem_cache_destroy(deleg_slab);
 out_free_stateid_slab:
        kmem_cache_destroy(stateid_slab);
 out_free_file_slab:
        open->op_stp = nfs4_alloc_open_stateid(clp);
        if (!open->op_stp)
                return nfserr_jukebox;
+
+       if (nfsd4_has_session(cstate) &&
+           (cstate->current_fh.fh_export->ex_flags & NFSEXP_PNFS)) {
+               open->op_odstate = alloc_clnt_odstate(clp);
+               if (!open->op_odstate)
+                       return nfserr_jukebox;
+       }
+
        return nfs_ok;
 }
 
 
 static struct nfs4_delegation *
 nfs4_set_delegation(struct nfs4_client *clp, struct svc_fh *fh,
-                   struct nfs4_file *fp)
+                   struct nfs4_file *fp, struct nfs4_clnt_odstate *odstate)
 {
        int status;
        struct nfs4_delegation *dp;
        if (fp->fi_had_conflict)
                return ERR_PTR(-EAGAIN);
 
-       dp = alloc_init_deleg(clp, fh);
+       dp = alloc_init_deleg(clp, fh, odstate);
        if (!dp)
                return ERR_PTR(-ENOMEM);
 
        spin_unlock(&state_lock);
 out:
        if (status) {
+               put_clnt_odstate(dp->dl_clnt_odstate);
                nfs4_put_stid(&dp->dl_stid);
                return ERR_PTR(status);
        }
                default:
                        goto out_no_deleg;
        }
-       dp = nfs4_set_delegation(clp, fh, stp->st_stid.sc_file);
+       dp = nfs4_set_delegation(clp, fh, stp->st_stid.sc_file, stp->st_clnt_odstate);
        if (IS_ERR(dp))
                goto out_no_deleg;
 
                        release_open_stateid(stp);
                        goto out;
                }
+
+               stp->st_clnt_odstate = find_or_hash_clnt_odstate(fp,
+                                                       open->op_odstate);
+               if (stp->st_clnt_odstate == open->op_odstate)
+                       open->op_odstate = NULL;
        }
        update_stateid(&stp->st_stid.sc_stateid);
        memcpy(&open->op_stateid, &stp->st_stid.sc_stateid, sizeof(stateid_t));
                kmem_cache_free(file_slab, open->op_file);
        if (open->op_stp)
                nfs4_put_stid(&open->op_stp->st_stid);
+       if (open->op_odstate)
+               kmem_cache_free(odstate_slab, open->op_odstate);
 }
 
 __be32
        update_stateid(&stp->st_stid.sc_stateid);
        memcpy(&close->cl_stateid, &stp->st_stid.sc_stateid, sizeof(stateid_t));
 
-       nfsd4_return_all_file_layouts(stp->st_stateowner->so_client,
-                                     stp->st_stid.sc_file);
-
        nfsd4_close_open_stateid(stp);
 
        /* put reference from nfs4_preprocess_seqid_op */
        list_for_each_safe(pos, next, &reaplist) {
                dp = list_entry (pos, struct nfs4_delegation, dl_recall_lru);
                list_del_init(&dp->dl_recall_lru);
+               put_clnt_odstate(dp->dl_clnt_odstate);
                nfs4_put_deleg_lease(dp->dl_stid.sc_file);
                nfs4_put_stid(&dp->dl_stid);
        }