Add a new routine for acquiring a read delegation on a directory. Since
the same CB_RECALL/DELEGRETURN infrastrure is used for regular and
directory delegations, we can just use a normal nfs4_delegation to
represent it.

Signed-off-by: Jeff Layton <[email protected]>
---
 fs/nfsd/nfs4proc.c  | 21 +++++++++++++-
 fs/nfsd/nfs4state.c | 82 +++++++++++++++++++++++++++++++++++++++++++++++++++++
 fs/nfsd/state.h     |  5 ++++
 3 files changed, 107 insertions(+), 1 deletion(-)

diff --git a/fs/nfsd/nfs4proc.c b/fs/nfsd/nfs4proc.c
index 
e466cf52d7d7e1a78c3a469613a85ab3546d6d17..277022d437cec18e527c836f108f0e97c6844b23
 100644
--- a/fs/nfsd/nfs4proc.c
+++ b/fs/nfsd/nfs4proc.c
@@ -2341,6 +2341,13 @@ nfsd4_get_dir_delegation(struct svc_rqst *rqstp,
                         union nfsd4_op_u *u)
 {
        struct nfsd4_get_dir_delegation *gdd = &u->get_dir_delegation;
+       struct nfs4_delegation *dd;
+       struct nfsd_file *nf;
+       __be32 status;
+
+       status = nfsd_file_acquire_dir(rqstp, &cstate->current_fh, &nf);
+       if (status != nfs_ok)
+               return status;
 
        /*
         * RFC 8881, section 18.39.3 says:
@@ -2354,7 +2361,19 @@ nfsd4_get_dir_delegation(struct svc_rqst *rqstp,
         * return NFS4_OK with a non-fatal status of GDD4_UNAVAIL in this
         * situation.
         */
-       gdd->gddrnf_status = GDD4_UNAVAIL;
+       dd = nfsd_get_dir_deleg(cstate, gdd, nf);
+       if (IS_ERR(dd)) {
+               int err = PTR_ERR(dd);
+
+               if (err != -EAGAIN)
+                       return nfserrno(err);
+               gdd->gddrnf_status = GDD4_UNAVAIL;
+               return nfs_ok;
+       }
+
+       gdd->gddrnf_status = GDD4_OK;
+       memcpy(&gdd->gddr_stateid, &dd->dl_stid.sc_stateid, 
sizeof(gdd->gddr_stateid));
+       nfs4_put_stid(&dd->dl_stid);
        return nfs_ok;
 }
 
diff --git a/fs/nfsd/nfs4state.c b/fs/nfsd/nfs4state.c
index 
87857b351cd92c509ab7101645e17474f2dabcd4..d1d586ec0e4e2bef908dc0671c34edab9cad5ba2
 100644
--- a/fs/nfsd/nfs4state.c
+++ b/fs/nfsd/nfs4state.c
@@ -9421,3 +9421,85 @@ nfsd4_deleg_getattr_conflict(struct svc_rqst *rqstp, 
struct dentry *dentry,
        nfs4_put_stid(&dp->dl_stid);
        return status;
 }
+
+/**
+ * nfsd_get_dir_deleg - attempt to get a directory delegation
+ * @cstate: compound state
+ * @gdd: GET_DIR_DELEGATION arg/resp structure
+ * @nf: nfsd_file opened on the directory
+ *
+ * Given a GET_DIR_DELEGATION request @gdd, attempt to acquire a delegation
+ * on the directory to which @nf refers. Note that this does not set up any
+ * sort of async notifications for the delegation.
+ */
+struct nfs4_delegation *
+nfsd_get_dir_deleg(struct nfsd4_compound_state *cstate,
+                  struct nfsd4_get_dir_delegation *gdd,
+                  struct nfsd_file *nf)
+{
+       struct nfs4_client *clp = cstate->clp;
+       struct nfs4_delegation *dp;
+       struct file_lease *fl;
+       struct nfs4_file *fp;
+       int status = 0;
+
+       fp = nfsd4_alloc_file();
+       if (!fp)
+               return ERR_PTR(-ENOMEM);
+
+       nfsd4_file_init(&cstate->current_fh, fp);
+       fp->fi_deleg_file = nf;
+       fp->fi_delegees = 1;
+
+       /* if this client already has one, return that it's unavailable */
+       spin_lock(&state_lock);
+       spin_lock(&fp->fi_lock);
+       if (nfs4_delegation_exists(clp, fp))
+               status = -EAGAIN;
+       spin_unlock(&fp->fi_lock);
+       spin_unlock(&state_lock);
+
+       if (status)
+               goto out_delegees;
+
+       /* Try to set up the lease */
+       status = -ENOMEM;
+       dp = alloc_init_deleg(clp, fp, NULL, NFS4_OPEN_DELEGATE_READ);
+       if (!dp)
+               goto out_delegees;
+
+       fl = nfs4_alloc_init_lease(dp);
+       if (!fl)
+               goto out_put_stid;
+
+       status = kernel_setlease(nf->nf_file,
+                                fl->c.flc_type, &fl, NULL);
+       if (fl)
+               locks_free_lease(fl);
+       if (status)
+               goto out_put_stid;
+
+       /*
+        * Now, try to hash it. This can fail if we race another nfsd task
+        * trying to set a delegation on the same file. If that happens,
+        * then just say UNAVAIL.
+        */
+       spin_lock(&state_lock);
+       spin_lock(&clp->cl_lock);
+       spin_lock(&fp->fi_lock);
+       status = hash_delegation_locked(dp, fp);
+       spin_unlock(&fp->fi_lock);
+       spin_unlock(&clp->cl_lock);
+       spin_unlock(&state_lock);
+
+       if (!status)
+               return dp;
+
+       /* Something failed. Drop the lease and clean up the stid */
+       kernel_setlease(fp->fi_deleg_file->nf_file, F_UNLCK, NULL, (void 
**)&dp);
+out_put_stid:
+       nfs4_put_stid(&dp->dl_stid);
+out_delegees:
+       put_deleg_file(fp);
+       return ERR_PTR(status);
+}
diff --git a/fs/nfsd/state.h b/fs/nfsd/state.h
index 
1e736f4024263ffa9c93bcc9ec48f44566a8cc77..b052c1effdc5356487c610db9728df8ecfe851d4
 100644
--- a/fs/nfsd/state.h
+++ b/fs/nfsd/state.h
@@ -867,4 +867,9 @@ static inline bool try_to_expire_client(struct nfs4_client 
*clp)
 
 extern __be32 nfsd4_deleg_getattr_conflict(struct svc_rqst *rqstp,
                struct dentry *dentry, struct nfs4_delegation **pdp);
+
+struct nfsd4_get_dir_delegation;
+struct nfs4_delegation *nfsd_get_dir_deleg(struct nfsd4_compound_state *cstate,
+                                               struct nfsd4_get_dir_delegation 
*gdd,
+                                               struct nfsd_file *nf);
 #endif   /* NFSD4_STATE_H */

-- 
2.51.0


Reply via email to