The branch main has been updated by rmacklem:

URL: 
https://cgit.FreeBSD.org/src/commit/?id=2c186228096a01f940fcc323814826572e0a156b

commit 2c186228096a01f940fcc323814826572e0a156b
Author:     Rick Macklem <rmack...@freebsd.org>
AuthorDate: 2025-06-22 21:14:15 +0000
Commit:     Rick Macklem <rmack...@freebsd.org>
CommitDate: 2025-06-22 21:14:15 +0000

    nfsd: Change the NFSv4.1/4.2 server to not recall delegations
    
    NFSv4.0 required that a server recall delegations for a file
    being removed (or renamed destination) since there was no way for the
    server to know if client doing the remove (or rename destination) was
    the same one that holds the delegation.
    
    For NFSv4.1/4.2, the server does know which client is doing
    the remove (or rename over), since the Sequence operation that
    is the first operation in the compound identies the client via
    the session.
    
    This patch implements an optimization where the recalls of
    delegations are only done when the client holding the delegation
    is not the same as the one doing the remove (or rename destination).
    Since the Linux knfsd already implements this optimization, it
    should be ok for extant clients.  However, this patch adds a
    new sysctl called vfs.nfsd.recalldeleg which disables this
    optimization when set to true/1, just in case it causes grief for
    some extant NFSv4.1/4.2 client.
    
    This only affects NFSv4.1/4.2 behaviour when delegations are
    enabled (vfs.nfsd.issue_delegations=1).
---
 sys/fs/nfs/nfs_var.h             |   9 +--
 sys/fs/nfsserver/nfs_nfsdport.c  | 119 ++++++++++++++++++++++++++++++---------
 sys/fs/nfsserver/nfs_nfsdserv.c  |  11 ++--
 sys/fs/nfsserver/nfs_nfsdstate.c |  26 +++++++++
 4 files changed, 129 insertions(+), 36 deletions(-)

diff --git a/sys/fs/nfs/nfs_var.h b/sys/fs/nfs/nfs_var.h
index b41e46d758ed..626946a70774 100644
--- a/sys/fs/nfs/nfs_var.h
+++ b/sys/fs/nfs/nfs_var.h
@@ -169,6 +169,7 @@ int nfsrv_mdscopymr(char *, char *, char *, char *, int *, 
char *, NFSPROC_T *,
     struct vnode **, struct vnode **, struct pnfsdsfile **, struct nfsdevice 
**,
     struct nfsdevice **);
 void nfsrv_marknospc(char *, bool);
+void nfsrv_removedeleg(fhandle_t *, struct nfsrv_descript *, NFSPROC_T *);
 
 /* nfs_nfsdserv.c */
 int nfsrvd_access(struct nfsrv_descript *, int,
@@ -710,12 +711,12 @@ int nfsvno_symlink(struct nameidata *, struct nfsvattr *, 
char *, int, int,
     uid_t, struct ucred *, NFSPROC_T *, struct nfsexstuff *);
 int nfsvno_getsymlink(struct nfsrv_descript *, struct nfsvattr *,
     NFSPROC_T *, char **, int *);
-int nfsvno_removesub(struct nameidata *, int, struct ucred *, NFSPROC_T *,
-    struct nfsexstuff *);
+int nfsvno_removesub(struct nameidata *, bool, struct nfsrv_descript *,
+    NFSPROC_T *, struct nfsexstuff *);
 int nfsvno_rmdirsub(struct nameidata *, int, struct ucred *, NFSPROC_T *,
     struct nfsexstuff *);
-int nfsvno_rename(struct nameidata *, struct nameidata *, u_int32_t,
-    u_int32_t, struct ucred *, NFSPROC_T *);
+int nfsvno_rename(struct nameidata *, struct nameidata *,
+    struct nfsrv_descript *, NFSPROC_T *);
 int nfsvno_link(struct nameidata *, vnode_t, nfsquad_t, struct ucred *,
     NFSPROC_T *, struct nfsexstuff *);
 int nfsvno_fsync(vnode_t, u_int64_t, int, struct ucred *, NFSPROC_T *);
diff --git a/sys/fs/nfsserver/nfs_nfsdport.c b/sys/fs/nfsserver/nfs_nfsdport.c
index 6819c8a980c6..3bf54d82b959 100644
--- a/sys/fs/nfsserver/nfs_nfsdport.c
+++ b/sys/fs/nfsserver/nfs_nfsdport.c
@@ -188,6 +188,10 @@ SYSCTL_INT(_vfs_nfsd, OID_AUTO, enable_stringtouid,
 static int nfsrv_pnfsgetdsattr = 1;
 SYSCTL_INT(_vfs_nfsd, OID_AUTO, pnfsgetdsattr, CTLFLAG_RW,
     &nfsrv_pnfsgetdsattr, 0, "When set getattr gets DS attributes via RPC");
+static bool nfsrv_recalldeleg = false;
+SYSCTL_BOOL(_vfs_nfsd, OID_AUTO, recalldeleg, CTLFLAG_RW,
+    &nfsrv_recalldeleg, 0,
+    "When set remove/rename recalls delegations for same client");
 
 /*
  * nfsrv_dsdirsize can only be increased and only when the nfsd threads are
@@ -1483,32 +1487,61 @@ nfsmout:
  * Remove a non-directory object.
  */
 int
-nfsvno_removesub(struct nameidata *ndp, int is_v4, struct ucred *cred,
+nfsvno_removesub(struct nameidata *ndp, bool is_v4, struct nfsrv_descript *nd,
     struct thread *p, struct nfsexstuff *exp)
 {
-       struct vnode *vp, *dsdvp[NFSDEV_MAXMIRRORS];
-       int error = 0, mirrorcnt;
+       struct vnode *vp, *dsdvp[NFSDEV_MAXMIRRORS], *newvp;
+       struct mount *mp;
+       int error = 0, mirrorcnt, ret;
        char fname[PNFS_FILENAME_LEN + 1];
        fhandle_t fh;
 
        vp = ndp->ni_vp;
        dsdvp[0] = NULL;
-       if (vp->v_type == VDIR)
+       if (vp->v_type == VDIR) {
                error = NFSERR_ISDIR;
-       else if (is_v4)
-               error = nfsrv_checkremove(vp, 1, NULL, (nfsquad_t)((u_quad_t)0),
-                   p);
+       } else if (is_v4) {
+               if (nfsrv_recalldeleg || (nd->nd_flag & ND_NFSV41) == 0)
+                       error = nfsrv_checkremove(vp, 1, NULL,
+                           (nfsquad_t)((u_quad_t)0), p);
+               else
+                       error = nfsrv_checkremove(vp, 1, NULL, nd->nd_clientid,
+                           p);
+       }
        if (error == 0)
                nfsrv_pnfsremovesetup(vp, p, dsdvp, &mirrorcnt, fname, &fh);
        if (!error)
                error = VOP_REMOVE(ndp->ni_dvp, vp, &ndp->ni_cnd);
        if (error == 0 && dsdvp[0] != NULL)
                nfsrv_pnfsremove(dsdvp, mirrorcnt, fname, &fh, p);
+       if (is_v4 && (nd->nd_flag & ND_NFSV41) != 0 && error == 0)
+               error = nfsvno_getfh(vp, &fh, p);
        if (ndp->ni_dvp == vp)
                vrele(ndp->ni_dvp);
        else
                vput(ndp->ni_dvp);
        vput(vp);
+
+       /* Use ret to determine if the file still exists. */
+       if (is_v4 && (nd->nd_flag & ND_NFSV41) != 0 && error == 0) {
+               mp = vfs_busyfs(&fh.fh_fsid);
+               if (mp != NULL) {
+                       /* Find out if the file still exists. */
+                       ret = VFS_FHTOVP(mp, &fh.fh_fid, LK_SHARED, &newvp);
+                       if (ret == 0)
+                               vput(newvp);
+                       else
+                               ret = ESTALE;
+                       vfs_unbusy(mp);
+               } else {
+                       ret = ESTALE;
+               }
+               if (ret == ESTALE) {
+                       /* Get rid of any delegation. */
+                       nfsrv_removedeleg(&fh, nd, p);
+               }
+       }
+
        nfsvno_relpathbuf(ndp);
        NFSEXITCODE(error);
        return (error);
@@ -1559,33 +1592,34 @@ out:
  */
 int
 nfsvno_rename(struct nameidata *fromndp, struct nameidata *tondp,
-    u_int32_t ndstat, u_int32_t ndflag, struct ucred *cred, struct thread *p)
+    struct nfsrv_descript *nd, struct thread *p)
 {
-       struct vnode *fvp, *tvp, *tdvp, *dsdvp[NFSDEV_MAXMIRRORS];
-       int error = 0, mirrorcnt;
+       struct vnode *fvp, *tvp, *tdvp, *dsdvp[NFSDEV_MAXMIRRORS], *newvp;
+       struct mount *mp;
+       int error = 0, mirrorcnt, ret;
        char fname[PNFS_FILENAME_LEN + 1];
-       fhandle_t fh;
+       fhandle_t fh, fh2;
 
        dsdvp[0] = NULL;
        fvp = fromndp->ni_vp;
-       if (ndstat) {
+       if (nd->nd_repstat != 0) {
                vrele(fromndp->ni_dvp);
                vrele(fvp);
-               error = ndstat;
+               error = nd->nd_repstat;
                goto out1;
        }
        tdvp = tondp->ni_dvp;
        tvp = tondp->ni_vp;
        if (tvp != NULL) {
                if (fvp->v_type == VDIR && tvp->v_type != VDIR) {
-                       error = (ndflag & ND_NFSV2) ? EISDIR : EEXIST;
+                       error = (nd->nd_flag & ND_NFSV2) ? EISDIR : EEXIST;
                        goto out;
                } else if (fvp->v_type != VDIR && tvp->v_type == VDIR) {
-                       error = (ndflag & ND_NFSV2) ? ENOTDIR : EEXIST;
+                       error = (nd->nd_flag & ND_NFSV2) ? ENOTDIR : EEXIST;
                        goto out;
                }
                if (tvp->v_type == VDIR && tvp->v_mountedhere) {
-                       error = (ndflag & ND_NFSV2) ? ENOTEMPTY : EXDEV;
+                       error = (nd->nd_flag & ND_NFSV2) ? ENOTEMPTY : EXDEV;
                        goto out;
                }
 
@@ -1604,15 +1638,15 @@ nfsvno_rename(struct nameidata *fromndp, struct 
nameidata *tondp,
                }
        }
        if (fvp->v_type == VDIR && fvp->v_mountedhere) {
-               error = (ndflag & ND_NFSV2) ? ENOTEMPTY : EXDEV;
+               error = (nd->nd_flag & ND_NFSV2) ? ENOTEMPTY : EXDEV;
                goto out;
        }
        if (fvp->v_mount != tdvp->v_mount) {
-               error = (ndflag & ND_NFSV2) ? ENOTEMPTY : EXDEV;
+               error = (nd->nd_flag & ND_NFSV2) ? ENOTEMPTY : EXDEV;
                goto out;
        }
        if (fvp == tdvp) {
-               error = (ndflag & ND_NFSV2) ? ENOTEMPTY : EINVAL;
+               error = (nd->nd_flag & ND_NFSV2) ? ENOTEMPTY : EINVAL;
                goto out;
        }
        if (fvp == tvp) {
@@ -1623,16 +1657,25 @@ nfsvno_rename(struct nameidata *fromndp, struct 
nameidata *tondp,
                error = -1;
                goto out;
        }
-       if (ndflag & ND_NFSV4) {
+       if (nd->nd_flag & ND_NFSV4) {
                if (NFSVOPLOCK(fvp, LK_EXCLUSIVE) == 0) {
-                       error = nfsrv_checkremove(fvp, 0, NULL,
-                           (nfsquad_t)((u_quad_t)0), p);
+                       if (nfsrv_recalldeleg || (nd->nd_flag & ND_NFSV41) == 0)
+                               error = nfsrv_checkremove(fvp, 0, NULL,
+                                   (nfsquad_t)((u_quad_t)0), p);
+                       else
+                               error = nfsrv_checkremove(fvp, 0, NULL,
+                                   nd->nd_clientid, p);
                        NFSVOPUNLOCK(fvp);
                } else
                        error = EPERM;
-               if (tvp && !error)
-                       error = nfsrv_checkremove(tvp, 1, NULL,
-                           (nfsquad_t)((u_quad_t)0), p);
+               if (tvp && !error) {
+                       if (nfsrv_recalldeleg || (nd->nd_flag & ND_NFSV41) == 0)
+                               error = nfsrv_checkremove(tvp, 1, NULL,
+                                   (nfsquad_t)((u_quad_t)0), p);
+                       else
+                               error = nfsrv_checkremove(tvp, 1, NULL,
+                                   nd->nd_clientid, p);
+               }
        } else {
                /*
                 * For NFSv2 and NFSv3, try to get rid of the delegation, so
@@ -1644,7 +1687,11 @@ nfsvno_rename(struct nameidata *fromndp, struct 
nameidata *tondp,
                nfsd_recalldelegation(fvp, p);
        }
        if (error == 0 && tvp != NULL) {
-               nfsrv_pnfsremovesetup(tvp, p, dsdvp, &mirrorcnt, fname, &fh);
+               if ((nd->nd_flag & ND_NFSV41) != 0)
+                       error = nfsvno_getfh(tvp, &fh2, p);
+               if (error == 0)
+                       nfsrv_pnfsremovesetup(tvp, p, dsdvp, &mirrorcnt, fname,
+                           &fh);
                NFSD_DEBUG(4, "nfsvno_rename: pnfsremovesetup"
                    " dsdvp=%p\n", dsdvp[0]);
        }
@@ -1676,6 +1723,26 @@ out:
                NFSD_DEBUG(4, "nfsvno_rename: pnfsremove\n");
        }
 
+       /* Use ret to determine if the file still exists. */
+       if ((nd->nd_flag & ND_NFSV41) != 0 && error == 0) {
+               mp = vfs_busyfs(&fh2.fh_fsid);
+               if (mp != NULL) {
+                       /* Find out if the file still exists. */
+                       ret = VFS_FHTOVP(mp, &fh2.fh_fid, LK_SHARED, &newvp);
+                       if (ret == 0)
+                               vput(newvp);
+                       else
+                               ret = ESTALE;
+                       vfs_unbusy(mp);
+               } else {
+                       ret = ESTALE;
+               }
+               if (ret == ESTALE) {
+                       /* Get rid of any delegation. */
+                       nfsrv_removedeleg(&fh2, nd, p);
+               }
+       }
+
        nfsvno_relpathbuf(tondp);
 out1:
        nfsvno_relpathbuf(fromndp);
diff --git a/sys/fs/nfsserver/nfs_nfsdserv.c b/sys/fs/nfsserver/nfs_nfsdserv.c
index 88f812548445..e54cc594d611 100644
--- a/sys/fs/nfsserver/nfs_nfsdserv.c
+++ b/sys/fs/nfsserver/nfs_nfsdserv.c
@@ -1599,14 +1599,14 @@ nfsrvd_remove(struct nfsrv_descript *nd, __unused int 
isdgram,
                                nd->nd_repstat = nfsvno_rmdirsub(&named, 1,
                                    nd->nd_cred, p, exp);
                        else
-                               nd->nd_repstat = nfsvno_removesub(&named, 1,
-                                   nd->nd_cred, p, exp);
+                               nd->nd_repstat = nfsvno_removesub(&named, true,
+                                   nd, p, exp);
                } else if (nd->nd_procnum == NFSPROC_RMDIR) {
                        nd->nd_repstat = nfsvno_rmdirsub(&named, 0,
                            nd->nd_cred, p, exp);
                } else {
-                       nd->nd_repstat = nfsvno_removesub(&named, 0,
-                           nd->nd_cred, p, exp);
+                       nd->nd_repstat = nfsvno_removesub(&named, false, nd, p,
+                           exp);
                }
        }
        if (!(nd->nd_flag & ND_NFSV2)) {
@@ -1770,8 +1770,7 @@ nfsrvd_rename(struct nfsrv_descript *nd, int isdgram,
        if (fromnd.ni_vp->v_type == VDIR)
                tond.ni_cnd.cn_flags |= WILLBEDIR;
        nd->nd_repstat = nfsvno_namei(nd, &tond, tdp, 0, &tnes, &tdirp);
-       nd->nd_repstat = nfsvno_rename(&fromnd, &tond, nd->nd_repstat,
-           nd->nd_flag, nd->nd_cred, p);
+       nd->nd_repstat = nfsvno_rename(&fromnd, &tond, nd, p);
        if (fdirp)
                fdiraft_ret = nfsvno_getattr(fdirp, &fdiraft, nd, p, 0, NULL);
        if (tdirp)
diff --git a/sys/fs/nfsserver/nfs_nfsdstate.c b/sys/fs/nfsserver/nfs_nfsdstate.c
index c952c0976c8c..2e27817389dd 100644
--- a/sys/fs/nfsserver/nfs_nfsdstate.c
+++ b/sys/fs/nfsserver/nfs_nfsdstate.c
@@ -9009,3 +9009,29 @@ nfsrv_issuedelegation(struct vnode *vp, struct nfsclient 
*clp,
                nfsrv_delegatecnt++;
        }
 }
+
+/*
+ * Find and remove any delegations for the fh.
+ */
+void
+nfsrv_removedeleg(fhandle_t *fhp, struct nfsrv_descript *nd, NFSPROC_T *p)
+{
+       struct nfsclient *clp;
+       struct nfsstate *stp, *nstp;
+       struct nfslockfile *lfp;
+       int error;
+
+       NFSLOCKSTATE();
+       error = nfsrv_getclient(nd->nd_clientid, CLOPS_RENEW, &clp, NULL,
+           (nfsquad_t)((u_quad_t)0), 0, nd, p);
+       if (error == 0)
+               error = nfsrv_getlockfile(NFSLCK_CHECK, NULL, &lfp, fhp, 0);
+       /*
+        * Now we must free any delegations.
+        */
+       if (error == 0) {
+               LIST_FOREACH_SAFE(stp, &lfp->lf_deleg, ls_file, nstp)
+                       nfsrv_freedeleg(stp);
+       }
+       NFSUNLOCKSTATE();
+}

Reply via email to