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(); +}