git: e4c7b2b6053f - main - nfsv4: Add support to NFSv4 for named attributes
- Go to: [ bottom of page ] [ top of archives ] [ this month ]
Date: Mon, 05 May 2025 23:10:03 UTC
The branch main has been updated by rmacklem: URL: https://cgit.FreeBSD.org/src/commit/?id=e4c7b2b6053f263acd7a2fb326cd98f7b7193872 commit e4c7b2b6053f263acd7a2fb326cd98f7b7193872 Author: Rick Macklem <rmacklem@FreeBSD.org> AuthorDate: 2025-05-05 23:06:48 +0000 Commit: Rick Macklem <rmacklem@FreeBSD.org> CommitDate: 2025-05-05 23:06:48 +0000 nfsv4: Add support to NFSv4 for named attributes NFSv4 supports a feature called named attributes, that are essentially Solaris style extended attributes. Commits starting with 2ec2ba7e232d added Solaris style extended attribute support. This patch uses the Solaris style extended attribute support to provide support for NFSv4. Since nfsv4_loadattr() needed an additional argument, many file are affected, although many in a trivial way. For the NFSv4 server to support named attributes, ZFS must be patched and only ZFS file systems support these Solaris style extended attributes. --- sys/fs/nfs/nfs_commonsubs.c | 102 ++++-------- sys/fs/nfs/nfs_var.h | 7 +- sys/fs/nfs/nfsproto.h | 1 + sys/fs/nfsclient/nfs_clcomsubs.c | 3 +- sys/fs/nfsclient/nfs_clport.c | 2 +- sys/fs/nfsclient/nfs_clrpcops.c | 80 +++++++-- sys/fs/nfsclient/nfs_clvfsops.c | 16 +- sys/fs/nfsclient/nfs_clvnops.c | 338 ++++++++++++++++++++++++++++----------- sys/fs/nfsclient/nfsnode.h | 1 + sys/fs/nfsserver/nfs_nfsdport.c | 56 +++++-- sys/fs/nfsserver/nfs_nfsdserv.c | 95 +++++++++-- sys/fs/nfsserver/nfs_nfsdstate.c | 2 +- sys/fs/nfsserver/nfs_nfsdsubs.c | 13 +- 13 files changed, 501 insertions(+), 215 deletions(-) diff --git a/sys/fs/nfs/nfs_commonsubs.c b/sys/fs/nfs/nfs_commonsubs.c index 3e70eb50a54e..39fa6f091eee 100644 --- a/sys/fs/nfs/nfs_commonsubs.c +++ b/sys/fs/nfs/nfs_commonsubs.c @@ -223,7 +223,6 @@ static int nfs_bigreply[NFSV42_NPROCS] = { 0, 0, 0, 1, 0, 1, 1, 0, 0, 0, 0, /* local functions */ static int nfsrv_skipace(struct nfsrv_descript *nd, int *acesizep); -static bool nfs_test_namedattr(struct nfsrv_descript *nd, struct vnode *vp); static void nfsv4_wanted(struct nfsv4lock *lp); static uint32_t nfsv4_filesavail(struct statfs *, struct mount *); static int nfsrv_getuser(int procnum, uid_t uid, gid_t gid, char *name); @@ -1282,70 +1281,6 @@ nfsmout: return (error); } -/* - * Check to see if a named attribute exists for this file. - */ -static bool -nfs_test_namedattr(struct nfsrv_descript *nd, struct vnode *vp) -{ - struct uio io; - struct iovec iv; - struct componentname cn; - struct vnode *dvp; - struct dirent *dp; - int eofflag, error; - char *buf, *cp, *endcp; - bool ret; - - if (vp == NULL || (vp->v_mount->mnt_flag & MNT_NAMEDATTR) == 0) - return (false); - NFSNAMEICNDSET(&cn, nd->nd_cred, LOOKUP, OPENNAMED | ISLASTCN | - NOFOLLOW | LOCKLEAF); - cn.cn_lkflags = LK_SHARED; - cn.cn_nameptr = "."; - cn.cn_namelen = 1; - error = VOP_LOOKUP(vp, &dvp, &cn); - if (error != 0) - return (false); - - /* Now we have to read the directory, looking for a valid entry. */ - buf = malloc(DIRBLKSIZ, M_TEMP, M_WAITOK); - ret = false; - io.uio_offset = 0; - io.uio_segflg = UIO_SYSSPACE; - io.uio_rw = UIO_READ; - io.uio_td = NULL; - do { - iv.iov_base = buf; - iv.iov_len = DIRBLKSIZ; - io.uio_iov = &iv; - io.uio_iovcnt = 1; - io.uio_resid = DIRBLKSIZ; - error = VOP_READDIR(dvp, &io, nd->nd_cred, &eofflag, NULL, - NULL); - if (error != 0 || io.uio_resid == DIRBLKSIZ) - break; - cp = buf; - endcp = &buf[DIRBLKSIZ - io.uio_resid]; - while (cp < endcp) { - dp = (struct dirent *)cp; - if (dp->d_fileno != 0 && dp->d_type != DT_WHT && - ((dp->d_namlen == 1 && dp->d_name[0] != '.') || - (dp->d_namlen == 2 && (dp->d_name[0] != '.' || - dp->d_name[1] != '.')) || dp->d_namlen > 2)) { - ret = true; - break; - } - cp += dp->d_reclen; - } - if (ret) - break; - } while (eofflag == 0); - vput(dvp); - free(buf, M_TEMP); - return (ret); -} - /* * Get the attributes for V4. * If the compare flag is true, test for any attribute changes, @@ -1361,7 +1296,8 @@ nfsv4_loadattr(struct nfsrv_descript *nd, vnode_t vp, struct nfsvattr *nap, struct nfsfh **nfhpp, fhandle_t *fhp, int fhsize, struct nfsv3_pathconf *pc, struct statfs *sbp, struct nfsstatfs *sfp, struct nfsfsinfo *fsp, NFSACL_T *aclp, int compare, int *retcmpp, - u_int32_t *leasep, u_int32_t *rderrp, NFSPROC_T *p, struct ucred *cred) + u_int32_t *leasep, u_int32_t *rderrp, bool *has_namedattrp, + NFSPROC_T *p, struct ucred *cred) { u_int32_t *tl; int i = 0, j, k, l = 0, m, bitpos, attrsum = 0; @@ -1451,6 +1387,8 @@ nfsv4_loadattr(struct nfsrv_descript *nd, vnode_t vp, sfp->sf_tbytes = UINT64_MAX; sfp->sf_abytes = UINT64_MAX; } + if (has_namedattrp != NULL) + *has_namedattrp = false; } /* @@ -1581,13 +1519,25 @@ nfsv4_loadattr(struct nfsrv_descript *nd, vnode_t vp, break; case NFSATTRBIT_NAMEDATTR: NFSM_DISSECT(tl, u_int32_t *, NFSX_UNSIGNED); - if (compare && !(*retcmpp)) { - bool named_attr; - - named_attr = nfs_test_namedattr(nd, vp); - if ((named_attr && *tl != newnfs_true) || - (!named_attr && *tl != newnfs_false)) - *retcmpp = NFSERR_NOTSAME; + if (compare) { + if (!(*retcmpp)) { + long has_named_attr; + + if (vp == NULL || VOP_PATHCONF(vp, + _PC_HAS_NAMEDATTR, &has_named_attr) + != 0) + has_named_attr = 0; + if ((has_named_attr != 0 && + *tl != newnfs_true) || + (has_named_attr == 0 && + *tl != newnfs_false)) + *retcmpp = NFSERR_NOTSAME; + } + } else if (has_namedattrp != NULL) { + if (*tl == newnfs_true) + *has_namedattrp = true; + else + *has_namedattrp = false; } attrsum += NFSX_UNSIGNED; break; @@ -2684,6 +2634,7 @@ nfsv4_fillattr(struct nfsrv_descript *nd, struct mount *mp, vnode_t vp, size_t atsiz; bool xattrsupp; short irflag; + long has_named_attr; #ifdef QUOTA struct dqblk dqb; uid_t savuid; @@ -2840,7 +2791,10 @@ nfsv4_fillattr(struct nfsrv_descript *nd, struct mount *mp, vnode_t vp, break; case NFSATTRBIT_NAMEDATTR: NFSM_BUILD(tl, u_int32_t *, NFSX_UNSIGNED); - if (nfs_test_namedattr(nd, vp)) + if (VOP_PATHCONF(vp, _PC_HAS_NAMEDATTR, &has_named_attr) + != 0) + has_named_attr = 0; + if (has_named_attr != 0) *tl = newnfs_true; else *tl = newnfs_false; diff --git a/sys/fs/nfs/nfs_var.h b/sys/fs/nfs/nfs_var.h index a95d9245c098..b41e46d758ed 100644 --- a/sys/fs/nfs/nfs_var.h +++ b/sys/fs/nfs/nfs_var.h @@ -340,7 +340,7 @@ int nfsv4_loadattr(struct nfsrv_descript *, vnode_t, struct nfsvattr *, struct nfsfh **, fhandle_t *, int, struct nfsv3_pathconf *, struct statfs *, struct nfsstatfs *, struct nfsfsinfo *, NFSACL_T *, - int, int *, u_int32_t *, u_int32_t *, NFSPROC_T *, struct ucred *); + int, int *, u_int32_t *, u_int32_t *, bool *, NFSPROC_T *, struct ucred *); int nfsv4_lock(struct nfsv4lock *, int, int *, struct mtx *, struct mount *); void nfsv4_unlock(struct nfsv4lock *, int); void nfsv4_relref(struct nfsv4lock *); @@ -515,7 +515,7 @@ int nfsrpc_statfs(vnode_t, struct nfsstatfs *, struct nfsfsinfo *, uint32_t *, struct ucred *, NFSPROC_T *, struct nfsvattr *, int *); int nfsrpc_fsinfo(vnode_t, struct nfsfsinfo *, struct ucred *, NFSPROC_T *, struct nfsvattr *, int *); -int nfsrpc_pathconf(vnode_t, struct nfsv3_pathconf *, +int nfsrpc_pathconf(vnode_t, struct nfsv3_pathconf *, bool *, struct ucred *, NFSPROC_T *, struct nfsvattr *, int *); int nfsrpc_renew(struct nfsclclient *, struct nfsclds *, struct ucred *, NFSPROC_T *); @@ -568,6 +568,9 @@ int nfsrpc_listextattr(vnode_t, uint64_t *, struct uio *, size_t *, bool *, int nfsrpc_rmextattr(vnode_t, const char *, struct nfsvattr *, int *, struct ucred *, NFSPROC_T *); void nfsrpc_bindconnsess(CLIENT *, void *, struct ucred *); +int nfsrpc_openattr(struct nfsmount *, struct vnode *, uint8_t *, int, + bool, struct ucred *, NFSPROC_T *, struct nfsvattr *, struct nfsfh **, + int *); /* nfs_clstate.c */ int nfscl_open(vnode_t, u_int8_t *, int, u_int32_t, int, diff --git a/sys/fs/nfs/nfsproto.h b/sys/fs/nfs/nfsproto.h index d0660cafdedb..eff53e1a384e 100644 --- a/sys/fs/nfs/nfsproto.h +++ b/sys/fs/nfs/nfsproto.h @@ -1414,6 +1414,7 @@ struct nfsv3_sattr { * NFSGETATTRBIT_PATHCONF0 - bits 0<->31 */ #define NFSGETATTRBIT_PATHCONF0 (NFSATTRBIT_GETATTR0 | \ + NFSATTRBM_NAMEDATTR | \ NFSATTRBM_CASEINSENSITIVE | \ NFSATTRBM_CASEPRESERVING | \ NFSATTRBM_CHOWNRESTRICTED | \ diff --git a/sys/fs/nfsclient/nfs_clcomsubs.c b/sys/fs/nfsclient/nfs_clcomsubs.c index 270f39d03c90..bca0bdcd0df1 100644 --- a/sys/fs/nfsclient/nfs_clcomsubs.c +++ b/sys/fs/nfsclient/nfs_clcomsubs.c @@ -271,7 +271,8 @@ nfsm_loadattr(struct nfsrv_descript *nd, struct nfsvattr *nap) if (nd->nd_flag & ND_NFSV4) { error = nfsv4_loadattr(nd, NULL, nap, NULL, NULL, 0, NULL, - NULL, NULL, NULL, NULL, 0, NULL, NULL, NULL, NULL, NULL); + NULL, NULL, NULL, NULL, 0, NULL, NULL, NULL, NULL, NULL, + NULL); } else if (nd->nd_flag & ND_NFSV3) { NFSM_DISSECT(fp, struct nfs_fattr *, NFSX_V3FATTR); nap->na_type = nfsv34tov_type(fp->fa_type); diff --git a/sys/fs/nfsclient/nfs_clport.c b/sys/fs/nfsclient/nfs_clport.c index 4e3a699fb170..ce7221c133cf 100644 --- a/sys/fs/nfsclient/nfs_clport.c +++ b/sys/fs/nfsclient/nfs_clport.c @@ -828,7 +828,7 @@ nfscl_wcc_data(struct nfsrv_descript *nd, struct vnode *vp, == (ND_NFSV4 | ND_V4WCCATTR)) { error = nfsv4_loadattr(nd, NULL, &nfsva, NULL, NULL, 0, NULL, NULL, NULL, NULL, NULL, 0, - NULL, NULL, NULL, NULL, NULL); + NULL, NULL, NULL, NULL, NULL, NULL); if (error) return (error); /* diff --git a/sys/fs/nfsclient/nfs_clrpcops.c b/sys/fs/nfsclient/nfs_clrpcops.c index 07f046a82d8b..0fb08df74a54 100644 --- a/sys/fs/nfsclient/nfs_clrpcops.c +++ b/sys/fs/nfsclient/nfs_clrpcops.c @@ -392,7 +392,8 @@ nfsrpc_open(vnode_t vp, int amode, struct ucred *cred, NFSPROC_T *p) mode |= NFSV4OPEN_ACCESSWRITE; if (NFSHASNFSV4N(nmp)) { if (!NFSHASPNFS(nmp) && nfscl_enablecallb != 0 && - nfs_numnfscbd > 0) { + nfs_numnfscbd > 0 && + (vn_irflag_read(vp) & VIRF_NAMEDATTR) == 0) { if ((mode & NFSV4OPEN_ACCESSWRITE) != 0) mode |= NFSV4OPEN_WANTWRITEDELEG; else @@ -695,7 +696,7 @@ nfsrpc_openrpc(struct nfsmount *nmp, vnode_t vp, u_int8_t *nfhp, int fhlen, ("nfsrpc_openrpc: Getattr repstat")); error = nfsv4_loadattr(nd, NULL, &nfsva, NULL, NULL, 0, NULL, NULL, NULL, NULL, NULL, 0, - NULL, NULL, NULL, p, cred); + NULL, NULL, NULL, NULL, p, cred); if (error) goto nfsmout; } @@ -1354,7 +1355,7 @@ nfsrpc_getattrnovp(struct nfsmount *nmp, u_int8_t *fhp, int fhlen, int syscred, if ((nd->nd_flag & ND_NFSV4) != 0) error = nfsv4_loadattr(nd, NULL, nap, NULL, NULL, 0, NULL, NULL, NULL, NULL, NULL, 0, NULL, leasep, NULL, - NULL, NULL); + NULL, NULL, NULL); else error = nfsm_loadattr(nd, nap); } else @@ -3478,7 +3479,7 @@ nfsrpc_readdir(vnode_t vp, struct uio *uiop, nfsuint64 *cookiep, nfsva.na_mntonfileno = UINT64_MAX; error = nfsv4_loadattr(nd, NULL, &nfsva, NULL, NULL, 0, NULL, NULL, NULL, NULL, NULL, 0, - NULL, NULL, NULL, p, cred); + NULL, NULL, NULL, NULL, p, cred); if (error) { dotdotfileid = dotfileid; } else if (gotmnton) { @@ -3728,7 +3729,7 @@ nfsrpc_readdir(vnode_t vp, struct uio *uiop, nfsuint64 *cookiep, nfsva.na_mntonfileno = UINT64_MAX; error = nfsv4_loadattr(nd, NULL, &nfsva, NULL, NULL, 0, NULL, NULL, NULL, NULL, NULL, 0, - NULL, NULL, &rderr, p, cred); + NULL, NULL, &rderr, NULL, p, cred); if (error) goto nfsmout; NFSM_DISSECT(tl, u_int32_t *, NFSX_UNSIGNED); @@ -3878,13 +3879,16 @@ nfsrpc_readdirplus(vnode_t vp, struct uio *uiop, nfsuint64 *cookiep, size_t tresid; u_int32_t *tl2 = NULL, rderr; struct timespec dctime, ts; - bool attr_ok, validentry; + bool attr_ok, named_dir, validentry; KASSERT(uiop->uio_iovcnt == 1 && (uiop->uio_resid & (DIRBLKSIZ - 1)) == 0, ("nfs readdirplusrpc bad uio")); KASSERT(uiop->uio_segflg == UIO_SYSSPACE, ("nfsrpc_readdirplus: uio userspace")); + named_dir = false; + if ((vp->v_irflag & VIRF_NAMEDDIR) != 0) + named_dir = true; ncookie.lval[0] = ncookie.lval[1] = 0; timespecclear(&dctime); *attrflagp = 0; @@ -3950,7 +3954,7 @@ nfsrpc_readdirplus(vnode_t vp, struct uio *uiop, nfsuint64 *cookiep, nfsva.na_mntonfileno = UINT64_MAX; error = nfsv4_loadattr(nd, NULL, &nfsva, NULL, NULL, 0, NULL, NULL, NULL, NULL, NULL, 0, - NULL, NULL, NULL, p, cred); + NULL, NULL, NULL, NULL, p, cred); if (error) { dotdotfileid = dotfileid; } else if (gotmnton) { @@ -4217,7 +4221,7 @@ nfsrpc_readdirplus(vnode_t vp, struct uio *uiop, nfsuint64 *cookiep, nfsva.na_mntonfileno = 0xffffffff; error = nfsv4_loadattr(nd, NULL, &nfsva, &nfhp, NULL, 0, NULL, NULL, NULL, NULL, NULL, 0, - NULL, NULL, &rderr, p, cred); + NULL, NULL, &rderr, NULL, p, cred); if (error) goto nfsmout; } @@ -4322,7 +4326,8 @@ nfsrpc_readdirplus(vnode_t vp, struct uio *uiop, nfsuint64 *cookiep, if (cnp->cn_namelen <= NCHNAMLEN && ndp->ni_dvp != ndp->ni_vp && (newvp->v_type != VDIR || - dctime.tv_sec != 0)) { + dctime.tv_sec != 0) && + !named_dir) { cache_enter_time_flags(ndp->ni_dvp, ndp->ni_vp, cnp, &nfsva.na_ctime, @@ -4879,7 +4884,7 @@ nfsrpc_statfs(vnode_t vp, struct nfsstatfs *sbp, struct nfsfsinfo *fsp, if (nd->nd_repstat == 0) { error = nfsv4_loadattr(nd, NULL, nap, NULL, NULL, 0, NULL, NULL, sbp, fsp, NULL, 0, NULL, leasep, NULL, - p, cred); + NULL, p, cred); if (!error) { nmp->nm_fsid[0] = nap->na_filesid[0]; nmp->nm_fsid[1] = nap->na_filesid[1]; @@ -4932,7 +4937,7 @@ nfsmout: * nfs pathconf rpc */ int -nfsrpc_pathconf(vnode_t vp, struct nfsv3_pathconf *pc, +nfsrpc_pathconf(vnode_t vp, struct nfsv3_pathconf *pc, bool *has_namedattrp, struct ucred *cred, NFSPROC_T *p, struct nfsvattr *nap, int *attrflagp) { struct nfsrv_descript nfsd, *nd = &nfsd; @@ -4942,6 +4947,7 @@ nfsrpc_pathconf(vnode_t vp, struct nfsv3_pathconf *pc, int error; struct nfsnode *np; + *has_namedattrp = false; *attrflagp = 0; nmp = VFSTONFS(vp->v_mount); if (NFSHASNFSV4(nmp)) { @@ -4968,8 +4974,8 @@ nfsrpc_pathconf(vnode_t vp, struct nfsv3_pathconf *pc, return (error); if (nd->nd_repstat == 0) { error = nfsv4_loadattr(nd, NULL, nap, NULL, NULL, 0, - pc, NULL, NULL, NULL, NULL, 0, NULL, NULL, NULL, p, - cred); + pc, NULL, NULL, NULL, NULL, 0, NULL, NULL, NULL, + has_namedattrp, p, cred); if (!error) *attrflagp = 1; } else { @@ -5264,7 +5270,7 @@ nfsrpc_getacl(vnode_t vp, struct ucred *cred, NFSPROC_T *p, struct acl *aclp) return (error); if (!nd->nd_repstat) error = nfsv4_loadattr(nd, vp, NULL, NULL, NULL, 0, NULL, - NULL, NULL, NULL, aclp, 0, NULL, NULL, NULL, p, cred); + NULL, NULL, NULL, aclp, 0, NULL, NULL, NULL, NULL, p, cred); else error = nd->nd_repstat; m_freem(nd->nd_mrep); @@ -8364,7 +8370,7 @@ nfsrpc_openlayoutrpc(struct nfsmount *nmp, vnode_t vp, u_int8_t *nfhp, if (*++tl == 0) { error = nfsv4_loadattr(nd, NULL, &nfsva, NULL, NULL, 0, NULL, NULL, NULL, NULL, NULL, 0, - NULL, NULL, NULL, p, cred); + NULL, NULL, NULL, NULL, p, cred); if (error != 0) goto nfsmout; if (ndp != NULL) { @@ -9529,6 +9535,50 @@ nfsmout: m_freem(nd->nd_mrep); } +/* + * nfs opeattr rpc + */ +int +nfsrpc_openattr(struct nfsmount *nmp, struct vnode *vp, uint8_t *fhp, int fhlen, + bool createit, struct ucred *cred, NFSPROC_T *p, struct nfsvattr *nap, + struct nfsfh **nfhpp, int *attrflagp) +{ + uint32_t *tl; + struct nfsrv_descript nfsd, *nd = &nfsd; + nfsattrbit_t attrbits; + int error = 0; + + *attrflagp = 0; + nfscl_reqstart(nd, NFSPROC_OPENATTR, nmp, fhp, fhlen, NULL, NULL, 0, 0, + cred); + NFSM_BUILD(tl, uint32_t *, NFSX_UNSIGNED); + if (createit) + *tl = newnfs_true; + else + *tl = newnfs_false; + NFSGETATTR_ATTRBIT(&attrbits); + NFSM_BUILD(tl, uint32_t *, 2 * NFSX_UNSIGNED); + *tl++ = txdr_unsigned(NFSV4OP_GETFH); + *tl = txdr_unsigned(NFSV4OP_GETATTR); + (void)nfsrv_putattrbit(nd, &attrbits); + error = newnfs_request(nd, nmp, NULL, &nmp->nm_sockreq, vp, p, cred, + NFS_PROG, NFS_VER4, NULL, 1, NULL, NULL); + if (error != 0) + return (error); + if (nd->nd_repstat == 0) { + NFSM_DISSECT(tl, uint32_t *, 2 * NFSX_UNSIGNED); + error = nfsm_getfh(nd, nfhpp); + if (error != 0) + goto nfsmout; + error = nfscl_postop_attr(nd, nap, attrflagp); + } +nfsmout: + m_freem(nd->nd_mrep); + if (error == 0 && nd->nd_repstat != 0) + error = nd->nd_repstat; + return (error); +} + /* * Do roughly what nfs_statfs() does for NFSv4, but when called with a shared * locked vnode. diff --git a/sys/fs/nfsclient/nfs_clvfsops.c b/sys/fs/nfsclient/nfs_clvfsops.c index 6bc67b342cbe..0bd05c03885b 100644 --- a/sys/fs/nfsclient/nfs_clvfsops.c +++ b/sys/fs/nfsclient/nfs_clvfsops.c @@ -1801,12 +1801,18 @@ mountnfs(struct nfs_args *argp, struct mount *mp, struct sockaddr *nam, if (argp->flags & NFSMNT_NFSV3) ncl_fsinfo(nmp, *vpp, cred, td); - /* Mark if the mount point supports NFSv4 ACLs. */ - if ((argp->flags & NFSMNT_NFSV4) != 0 && nfsrv_useacl != 0 && - ret == 0 && - NFSISSET_ATTRBIT(&nfsva.na_suppattr, NFSATTRBIT_ACL)) { + /* + * Mark if the mount point supports NFSv4 ACLs and + * named attributes. + */ + if ((argp->flags & NFSMNT_NFSV4) != 0) { MNT_ILOCK(mp); - mp->mnt_flag |= MNT_NFS4ACLS; + if (ret == 0 && nfsrv_useacl != 0 && + NFSISSET_ATTRBIT(&nfsva.na_suppattr, + NFSATTRBIT_ACL)) + mp->mnt_flag |= MNT_NFS4ACLS; + if (nmp->nm_minorvers > 0) + mp->mnt_flag |= MNT_NAMEDATTR; MNT_IUNLOCK(mp); } diff --git a/sys/fs/nfsclient/nfs_clvnops.c b/sys/fs/nfsclient/nfs_clvnops.c index a0bb781e5e5d..20603d376901 100644 --- a/sys/fs/nfsclient/nfs_clvnops.c +++ b/sys/fs/nfsclient/nfs_clvnops.c @@ -113,6 +113,8 @@ static vop_write_t nfsfifo_write; static vop_close_t nfsfifo_close; static int nfs_setattrrpc(struct vnode *, struct vattr *, struct ucred *, struct thread *); +static int nfs_get_namedattrdir(struct vnode *, struct componentname *, + struct vnode **); static vop_lookup_t nfs_lookup; static vop_create_t nfs_create; static vop_mknod_t nfs_mknod; @@ -1193,6 +1195,40 @@ nfs_setattrrpc(struct vnode *vp, struct vattr *vap, struct ucred *cred, return (error); } +/* + * Get a named attribute directory for the vnode. + */ +static int +nfs_get_namedattrdir(struct vnode *vp, struct componentname *cnp, + struct vnode **vpp) +{ + struct nfsfh *nfhp; + struct nfsnode *np; + struct vnode *newvp; + struct nfsvattr nfsva; + int attrflag, error; + + attrflag = 0; + *vpp = NULL; + np = VTONFS(vp); + error = nfsrpc_openattr(VFSTONFS(vp->v_mount), vp, np->n_fhp->nfh_fh, + np->n_fhp->nfh_len, (cnp->cn_flags & CREATENAMED), + cnp->cn_cred, curthread, &nfsva, &nfhp, &attrflag); + if (error == NFSERR_NOTSUPP) + error = ENOATTR; + if (error == 0) + error = nfscl_nget(vp->v_mount, vp, nfhp, cnp, curthread, &np, + cnp->cn_lkflags); + if (error != 0) + return (error); + newvp = NFSTOV(np); + vn_irflag_set_cond(newvp, VIRF_NAMEDDIR); + if (attrflag != 0) + (void)nfscl_loadattrcache(&newvp, &nfsva, NULL, 0, 1); + *vpp = newvp; + return (0); +} + /* * nfs lookup call, one step at a time... * First look in cache @@ -1205,7 +1241,7 @@ nfs_lookup(struct vop_lookup_args *ap) struct vnode *dvp = ap->a_dvp; struct vnode **vpp = ap->a_vpp; struct mount *mp = dvp->v_mount; - int flags = cnp->cn_flags; + uint64_t flags = cnp->cn_flags; struct vnode *newvp; struct nfsmount *nmp; struct nfsnode *np, *newnp; @@ -1216,15 +1252,57 @@ nfs_lookup(struct vop_lookup_args *ap) struct vattr vattr; struct timespec nctime, ts; uint32_t openmode; + bool is_nameddir, needs_nameddir, opennamed; + dattrflag = 0; *vpp = NULLVP; + nmp = VFSTONFS(mp); + opennamed = (flags & (OPENNAMED | ISLASTCN)) == (OPENNAMED | ISLASTCN); + if (opennamed && (!NFSHASNFSV4(nmp) || !NFSHASNFSV4N(nmp))) + return (ENOATTR); + is_nameddir = (vn_irflag_read(dvp) & VIRF_NAMEDDIR) != 0; + if ((is_nameddir && (flags & ISLASTCN) == 0 && (cnp->cn_namelen > 1 || + *cnp->cn_nameptr != '.')) || + (opennamed && !is_nameddir && (flags & ISDOTDOT) != 0)) + return (ENOATTR); if ((flags & ISLASTCN) && (mp->mnt_flag & MNT_RDONLY) && (cnp->cn_nameiop == DELETE || cnp->cn_nameiop == RENAME)) return (EROFS); + np = VTONFS(dvp); + + needs_nameddir = false; + if (opennamed || is_nameddir) { + cnp->cn_flags &= ~MAKEENTRY; + if (!is_nameddir) + needs_nameddir = true; + } + + /* + * If the named attribute directory is needed, acquire it now. + */ + newvp = NULLVP; + if (needs_nameddir) { + KASSERT(np->n_v4 == NULL, ("nfs_lookup: O_NAMEDATTR when" + " n_v4 not NULL")); + error = nfs_get_namedattrdir(dvp, cnp, &newvp); + if (error != 0) + goto handle_error; + if (cnp->cn_namelen == 1 && *cnp->cn_nameptr == '.') { + *vpp = newvp; + return (0); + } + dvp = newvp; + np = VTONFS(dvp); + newvp = NULLVP; + } else if (opennamed && cnp->cn_namelen == 1 && + *cnp->cn_nameptr == '.') { + VREF(dvp); + *vpp = dvp; + return (0); + } + if (dvp->v_type != VDIR) return (ENOTDIR); - nmp = VFSTONFS(mp); - np = VTONFS(dvp); /* For NFSv4, wait until any remove is done. */ NFSLOCKNODE(np); @@ -1237,77 +1315,83 @@ nfs_lookup(struct vop_lookup_args *ap) error = vn_dir_check_exec(dvp, cnp); if (error != 0) return (error); - error = cache_lookup(dvp, vpp, cnp, &nctime, &ncticks); - if (error > 0 && error != ENOENT) - return (error); - if (error == -1) { - /* - * Lookups of "." are special and always return the - * current directory. cache_lookup() already handles - * associated locking bookkeeping, etc. - */ - if (cnp->cn_namelen == 1 && cnp->cn_nameptr[0] == '.') { - return (0); - } - /* - * We only accept a positive hit in the cache if the - * change time of the file matches our cached copy. - * Otherwise, we discard the cache entry and fallback - * to doing a lookup RPC. We also only trust cache - * entries for less than nm_nametimeo seconds. - * - * To better handle stale file handles and attributes, - * clear the attribute cache of this node if it is a - * leaf component, part of an open() call, and not - * locally modified before fetching the attributes. - * This should allow stale file handles to be detected - * here where we can fall back to a LOOKUP RPC to - * recover rather than having nfs_open() detect the - * stale file handle and failing open(2) with ESTALE. - */ - newvp = *vpp; - newnp = VTONFS(newvp); - if (!(nmp->nm_flag & NFSMNT_NOCTO) && - (flags & (ISLASTCN | ISOPEN)) == (ISLASTCN | ISOPEN) && - !(newnp->n_flag & NMODIFIED)) { - NFSLOCKNODE(newnp); - newnp->n_attrstamp = 0; - KDTRACE_NFS_ATTRCACHE_FLUSH_DONE(newvp); - NFSUNLOCKNODE(newnp); - } - if (nfscl_nodeleg(newvp, 0) == 0 || - ((u_int)(ticks - ncticks) < (nmp->nm_nametimeo * hz) && - VOP_GETATTR(newvp, &vattr, cnp->cn_cred) == 0 && - timespeccmp(&vattr.va_ctime, &nctime, ==))) { - NFSINCRGLOBAL(nfsstatsv1.lookupcache_hits); - return (0); - } - cache_purge(newvp); - if (dvp != newvp) - vput(newvp); - else - vrele(newvp); - *vpp = NULLVP; - } else if (error == ENOENT) { - if (VN_IS_DOOMED(dvp)) - return (ENOENT); - /* - * We only accept a negative hit in the cache if the - * modification time of the parent directory matches - * the cached copy in the name cache entry. - * Otherwise, we discard all of the negative cache - * entries for this directory. We also only trust - * negative cache entries for up to nm_negnametimeo - * seconds. - */ - if ((u_int)(ticks - ncticks) < (nmp->nm_negnametimeo * hz) && - VOP_GETATTR(dvp, &vattr, cnp->cn_cred) == 0 && - timespeccmp(&vattr.va_mtime, &nctime, ==)) { - NFSINCRGLOBAL(nfsstatsv1.lookupcache_hits); - return (ENOENT); + if (!opennamed && !is_nameddir) { + error = cache_lookup(dvp, vpp, cnp, &nctime, &ncticks); + if (error > 0 && error != ENOENT) + return (error); + if (error == -1) { + /* + * Lookups of "." are special and always return the + * current directory. cache_lookup() already handles + * associated locking bookkeeping, etc. + */ + if (cnp->cn_namelen == 1 && cnp->cn_nameptr[0] == '.') { + return (0); + } + + /* + * We only accept a positive hit in the cache if the + * change time of the file matches our cached copy. + * Otherwise, we discard the cache entry and fallback + * to doing a lookup RPC. We also only trust cache + * entries for less than nm_nametimeo seconds. + * + * To better handle stale file handles and attributes, + * clear the attribute cache of this node if it is a + * leaf component, part of an open() call, and not + * locally modified before fetching the attributes. + * This should allow stale file handles to be detected + * here where we can fall back to a LOOKUP RPC to + * recover rather than having nfs_open() detect the + * stale file handle and failing open(2) with ESTALE. + */ + newvp = *vpp; + newnp = VTONFS(newvp); + if (!(nmp->nm_flag & NFSMNT_NOCTO) && + (flags & (ISLASTCN | ISOPEN)) == + (ISLASTCN | ISOPEN) && + !(newnp->n_flag & NMODIFIED)) { + NFSLOCKNODE(newnp); + newnp->n_attrstamp = 0; + KDTRACE_NFS_ATTRCACHE_FLUSH_DONE(newvp); + NFSUNLOCKNODE(newnp); + } + if (nfscl_nodeleg(newvp, 0) == 0 || + ((u_int)(ticks - ncticks) < + (nmp->nm_nametimeo * hz) && + VOP_GETATTR(newvp, &vattr, cnp->cn_cred) == 0 && + timespeccmp(&vattr.va_ctime, &nctime, ==))) { + NFSINCRGLOBAL(nfsstatsv1.lookupcache_hits); + return (0); + } + cache_purge(newvp); + if (dvp != newvp) + vput(newvp); + else + vrele(newvp); + *vpp = NULLVP; + } else if (error == ENOENT) { + if (VN_IS_DOOMED(dvp)) + return (ENOENT); + /* + * We only accept a negative hit in the cache if the + * modification time of the parent directory matches + * the cached copy in the name cache entry. + * Otherwise, we discard all of the negative cache + * entries for this directory. We also only trust + * negative cache entries for up to nm_negnametimeo + * seconds. + */ + if ((u_int)(ticks - ncticks) < + (nmp->nm_negnametimeo * hz) && + VOP_GETATTR(dvp, &vattr, cnp->cn_cred) == 0 && + timespeccmp(&vattr.va_mtime, &nctime, ==)) { + NFSINCRGLOBAL(nfsstatsv1.lookupcache_hits); + return (ENOENT); + } + cache_purge_negative(dvp); } - cache_purge_negative(dvp); } openmode = 0; @@ -1328,7 +1412,7 @@ nfs_lookup(struct vop_lookup_args *ap) if (NFSHASNFSV4N(nmp) && NFSHASONEOPENOWN(nmp) && !NFSHASPNFS(nmp) && (nmp->nm_privflag & NFSMNTP_DELEGISSUED) == 0 && (!NFSMNT_RDONLY(mp) || (flags & OPENWRITE) == 0) && - (flags & (ISLASTCN | ISOPEN)) == (ISLASTCN | ISOPEN)) { + (flags & (ISLASTCN | ISOPEN | OPENNAMED))) == (ISLASTCN | ISOPEN)) { if ((flags & OPENREAD) != 0) openmode |= NFSV4OPEN_ACCESSREAD; if ((flags & OPENWRITE) != 0) @@ -1337,7 +1421,6 @@ nfs_lookup(struct vop_lookup_args *ap) NFSUNLOCKMNT(nmp); #endif - newvp = NULLVP; NFSINCRGLOBAL(nfsstatsv1.lookupcache_misses); nanouptime(&ts); error = nfsrpc_lookup(dvp, cnp->cn_nameptr, cnp->cn_namelen, @@ -1345,6 +1428,11 @@ nfs_lookup(struct vop_lookup_args *ap) openmode); if (dattrflag) (void) nfscl_loadattrcache(&dvp, &dnfsva, NULL, 0, 1); + if (needs_nameddir) { + vput(dvp); + dvp = ap->a_dvp; + } +handle_error: if (error) { if (newvp != NULLVP) { vput(newvp); @@ -1353,13 +1441,14 @@ nfs_lookup(struct vop_lookup_args *ap) if (error != ENOENT) { if (NFS_ISV4(dvp)) - error = nfscl_maperr(td, error, (uid_t)0, - (gid_t)0); + error = nfscl_maperr(td, error, + (uid_t)0, (gid_t)0); return (error); } /* The requested file was not found. */ - if ((cnp->cn_nameiop == CREATE || cnp->cn_nameiop == RENAME) && + if ((cnp->cn_nameiop == CREATE || + cnp->cn_nameiop == RENAME) && (flags & ISLASTCN)) { /* * XXX: UFS does a full VOP_ACCESS(dvp, @@ -1400,7 +1489,8 @@ nfs_lookup(struct vop_lookup_args *ap) free(nfhp, M_NFSFH); return (EISDIR); } - error = nfscl_nget(mp, dvp, nfhp, cnp, td, &np, LK_EXCLUSIVE); + error = nfscl_nget(mp, dvp, nfhp, cnp, td, &np, + LK_EXCLUSIVE); if (error) return (error); newvp = NFSTOV(np); @@ -1421,7 +1511,8 @@ nfs_lookup(struct vop_lookup_args *ap) } NFSUNLOCKNODE(np); if (attrflag) - (void) nfscl_loadattrcache(&newvp, &nfsva, NULL, 0, 1); + (void) nfscl_loadattrcache(&newvp, &nfsva, NULL, + 0, 1); *vpp = newvp; return (0); } @@ -1462,19 +1553,23 @@ nfs_lookup(struct vop_lookup_args *ap) if (error != 0) return (error); if (attrflag) - (void) nfscl_loadattrcache(&newvp, &nfsva, NULL, 0, 1); + (void) nfscl_loadattrcache(&newvp, &nfsva, NULL, + 0, 1); } else if (NFS_CMPFH(np, nfhp->nfh_fh, nfhp->nfh_len)) { free(nfhp, M_NFSFH); VREF(dvp); newvp = dvp; if (attrflag) - (void) nfscl_loadattrcache(&newvp, &nfsva, NULL, 0, 1); + (void) nfscl_loadattrcache(&newvp, &nfsva, NULL, + 0, 1); } else { error = nfscl_nget(mp, dvp, nfhp, cnp, td, &np, cnp->cn_lkflags); if (error) return (error); newvp = NFSTOV(np); + if (opennamed) + vn_irflag_set_cond(newvp, VIRF_NAMEDATTR); /* * If n_localmodtime >= time before RPC, then * a file modification operation, such as @@ -1492,8 +1587,10 @@ nfs_lookup(struct vop_lookup_args *ap) } NFSUNLOCKNODE(np); if (attrflag) - (void) nfscl_loadattrcache(&newvp, &nfsva, NULL, 0, 1); - else if ((flags & (ISLASTCN | ISOPEN)) == (ISLASTCN | ISOPEN) && + (void)nfscl_loadattrcache(&newvp, &nfsva, NULL, + 0, 1); + else if ((flags & (ISLASTCN | ISOPEN)) == + (ISLASTCN | ISOPEN) && !(np->n_flag & NMODIFIED)) { /* * Flush the attribute cache when opening a @@ -1754,6 +1851,7 @@ nfs_create(struct vop_create_args *ap) nfsquad_t cverf; int error = 0, attrflag, dattrflag, fmode = 0; struct vattr vattr; + bool is_nameddir, needs_nameddir, opennamed; /* * Oops, not for me.. @@ -1767,6 +1865,32 @@ nfs_create(struct vop_create_args *ap) fmode |= O_EXCL; dnp = VTONFS(dvp); nmp = VFSTONFS(dvp->v_mount); + needs_nameddir = false; + if (NFSHASNFSV4(nmp) && NFSHASNFSV4N(nmp)) { + opennamed = (cnp->cn_flags & (OPENNAMED | ISLASTCN)) == + (OPENNAMED | ISLASTCN); + is_nameddir = (vn_irflag_read(dvp) & VIRF_NAMEDDIR) != 0; + if (opennamed || is_nameddir) { + cnp->cn_flags &= ~MAKEENTRY; + if (!is_nameddir) + needs_nameddir = true; + } + } + + /* + * If the named attribute directory is needed, acquire it now. + */ + if (needs_nameddir) { + KASSERT(dnp->n_v4 == NULL, ("nfs_create: O_NAMEDATTR when" + " n_v4 not NULL")); + error = nfs_get_namedattrdir(dvp, cnp, &newvp); + if (error != 0) + return (error); + dvp = newvp; + dnp = VTONFS(dvp); + newvp = NULL; + } + again: /* For NFSv4, wait until any remove is done. */ NFSLOCKNODE(dnp); @@ -1849,6 +1973,8 @@ again: KDTRACE_NFS_ATTRCACHE_FLUSH_DONE(dvp); } NFSUNLOCKNODE(dnp); + if (needs_nameddir) + vput(dvp); return (error); } @@ -4375,25 +4501,48 @@ nfs_pathconf(struct vop_pathconf_args *ap) struct nfsmount *nmp; struct thread *td = curthread; off_t off; - bool eof; + bool eof, has_namedattr, named_enabled; int attrflag, error; + struct nfsnode *np; + nmp = VFSTONFS(vp->v_mount); + np = VTONFS(vp); + named_enabled = false; + has_namedattr = false; if ((NFS_ISV34(vp) && (ap->a_name == _PC_LINK_MAX || ap->a_name == _PC_NAME_MAX || ap->a_name == _PC_CHOWN_RESTRICTED || ap->a_name == _PC_NO_TRUNC)) || - (NFS_ISV4(vp) && ap->a_name == _PC_ACL_NFS4)) { + (NFS_ISV4(vp) && (ap->a_name == _PC_ACL_NFS4 || + ap->a_name == _PC_HAS_NAMEDATTR))) { /* * Since only the above 4 a_names are returned by the NFSv3 * Pathconf RPC, there is no point in doing it for others. * For NFSv4, the Pathconf RPC (actually a Getattr Op.) can - * be used for _PC_NFS4_ACL as well. + * be used for _PC_ACL_NFS4 and _PC_HAS_NAMEDATTR as well. */ - error = nfsrpc_pathconf(vp, &pc, td->td_ucred, td, &nfsva, - &attrflag); + error = nfsrpc_pathconf(vp, &pc, &has_namedattr, td->td_ucred, + td, &nfsva, &attrflag); if (attrflag != 0) (void) nfscl_loadattrcache(&vp, &nfsva, NULL, 0, 1); if (error != 0) return (error); + } else if (NFS_ISV4(vp) && ap->a_name == _PC_NAMEDATTR_ENABLED && + (np->n_flag & NNAMEDNOTSUPP) == 0) { + struct nfsfh *nfhp; + + error = nfsrpc_openattr(nmp, vp, np->n_fhp->nfh_fh, + np->n_fhp->nfh_len, false, td->td_ucred, td, &nfsva, &nfhp, + &attrflag); + named_enabled = true; + if (error == 0) { + free(nfhp, M_NFSFH); + } else if (error == NFSERR_NOTSUPP) { + named_enabled = false; + NFSLOCKNODE(np); + np->n_flag |= NNAMEDNOTSUPP; + NFSUNLOCKNODE(np); + } + error = 0; } else { /* * For NFSv2 (or NFSv3 when not one of the above 4 a_names), @@ -4476,7 +4625,6 @@ nfs_pathconf(struct vop_pathconf_args *ap) case _PC_MIN_HOLE_SIZE: /* Only some NFSv4.2 servers support Seek for Holes. */ *** 443 LINES SKIPPED ***