]> git.ipfire.org Git - thirdparty/kernel/linux.git/commitdiff
nfsd: wire up GET_DIR_DELEGATION handling
authorJeff Layton <jlayton@kernel.org>
Tue, 11 Nov 2025 14:12:57 +0000 (09:12 -0500)
committerChristian Brauner <brauner@kernel.org>
Wed, 12 Nov 2025 08:38:37 +0000 (09:38 +0100)
Add a new routine for acquiring a read delegation on a directory. These
are recallable-only delegations with no support for CB_NOTIFY. That will
be added in a later phase.

Since the same CB_RECALL/DELEGRETURN infrastructure is used for regular
and directory delegations, a normal nfs4_delegation is used to represent
a directory delegation.

Reviewed-by: NeilBrown <neil@brown.name>
Reviewed-by: Chuck Lever <chuck.lever@oracle.com>
Signed-off-by: Jeff Layton <jlayton@kernel.org>
Link: https://patch.msgid.link/20251111-dir-deleg-ro-v6-16-52f3feebb2f2@kernel.org
Signed-off-by: Christian Brauner <brauner@kernel.org>
fs/nfsd/nfs4proc.c
fs/nfsd/nfs4state.c
fs/nfsd/state.h

index e466cf52d7d7e1a78c3a469613a85ab3546d6d17..517968dddf4a33651313658d300f9f929f83c5af 100644 (file)
@@ -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,20 @@ 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);
+       nfsd_file_put(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;
 }
 
index da66798023aba4c36c38208cec7333db237e46e0..8f8c9385101e15b64883eabec71775f26b14f890 100644 (file)
@@ -9347,3 +9347,103 @@ out_status:
        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, *rfp;
+       int status = 0;
+
+       fp = nfsd4_alloc_file();
+       if (!fp)
+               return ERR_PTR(-ENOMEM);
+
+       nfsd4_file_init(&cstate->current_fh, fp);
+
+       rfp = nfsd4_file_hash_insert(fp, &cstate->current_fh);
+       if (unlikely(!rfp)) {
+               put_nfs4_file(fp);
+               return ERR_PTR(-ENOMEM);
+       }
+
+       if (rfp != fp) {
+               put_nfs4_file(fp);
+               fp = rfp;
+       }
+
+       /* if this client already has one, return that it's unavailable */
+       spin_lock(&state_lock);
+       spin_lock(&fp->fi_lock);
+       /* existing delegation? */
+       if (nfs4_delegation_exists(clp, fp)) {
+               status = -EAGAIN;
+       } else if (!fp->fi_deleg_file) {
+               fp->fi_deleg_file = nfsd_file_get(nf);
+               fp->fi_delegees = 1;
+       } else {
+               ++fp->fi_delegees;
+       }
+       spin_unlock(&fp->fi_lock);
+       spin_unlock(&state_lock);
+
+       if (status) {
+               put_nfs4_file(fp);
+               return ERR_PTR(status);
+       }
+
+       /* 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);
+}
index 1e736f4024263ffa9c93bcc9ec48f44566a8cc77..b052c1effdc5356487c610db9728df8ecfe851d4 100644 (file)
@@ -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 */