diff --git a/fs/nfs/client.c b/fs/nfs/client.c index 3db2f18315b8..73b95318ba48 100644 --- a/fs/nfs/client.c +++ b/fs/nfs/client.c @@ -937,19 +937,24 @@ static int nfs_probe_fsinfo(struct nfs_server *server, struct nfs_fh *mntfh, str pathinfo.fattr = fattr; nfs_fattr_init(fattr); - /* Clear before probing so a failed RPC does not retain stale bits. */ - if (clp->rpc_ops->version < 4) - server->caps &= ~(NFS_CAP_CASE_INSENSITIVE | - NFS_CAP_CASE_NONPRESERVING); + if (clp->rpc_ops->version < 4 || server->namelen == 0) { + if (clp->rpc_ops->pathconf(server, mntfh, &pathinfo) >= 0) { + if (server->namelen == 0) + server->namelen = pathinfo.max_namelen; + if (clp->rpc_ops->version < 4) { + unsigned int caps = server->caps; - if (clp->rpc_ops->pathconf(server, mntfh, &pathinfo) >= 0) { - if (server->namelen == 0) - server->namelen = pathinfo.max_namelen; - if (clp->rpc_ops->version < 4) { - if (pathinfo.case_insensitive) - server->caps |= NFS_CAP_CASE_INSENSITIVE; - if (!pathinfo.case_preserving) - server->caps |= NFS_CAP_CASE_NONPRESERVING; + caps &= ~(NFS_CAP_CASE_INSENSITIVE | + NFS_CAP_CASE_NONPRESERVING); + if (pathinfo.case_insensitive) + caps |= NFS_CAP_CASE_INSENSITIVE; + if (!pathinfo.case_preserving) + caps |= NFS_CAP_CASE_NONPRESERVING; + server->caps = caps; + } + } else if (clp->rpc_ops->version < 4) { + server->caps &= ~(NFS_CAP_CASE_INSENSITIVE | + NFS_CAP_CASE_NONPRESERVING); } } diff --git a/fs/nfsd/nfs3proc.c b/fs/nfsd/nfs3proc.c index 12b9172c6be1..aeda7a802bdf 100644 --- a/fs/nfsd/nfs3proc.c +++ b/fs/nfsd/nfs3proc.c @@ -745,6 +745,9 @@ nfsd3_proc_pathconf(struct svc_rqst *rqstp) */ resp->status = nfserr_stale; break; + case -ESTALE: + resp->status = nfserr_stale; + break; default: resp->status = nfserr_serverfault; break; diff --git a/fs/nfsd/nfs4xdr.c b/fs/nfsd/nfs4xdr.c index 319007b79d49..20355dc3f1d1 100644 --- a/fs/nfsd/nfs4xdr.c +++ b/fs/nfsd/nfs4xdr.c @@ -3883,13 +3883,16 @@ static const nfsd4_enc_attr nfsd4_enc_fattr4_encode_ops[] = { /* * Note: @fhp can be NULL; in this case, we might have to compose the filehandle - * ourselves. + * ourselves. @case_cache is NULL for callers that encode a single dentry + * (GETATTR, the buffer wrapper); READDIR passes a per-request cache so + * non-directory children share the parent's case-folding probe result. */ static __be32 nfsd4_encode_fattr4(struct svc_rqst *rqstp, struct xdr_stream *xdr, struct svc_fh *fhp, struct svc_export *exp, struct dentry *dentry, const u32 *bmval, - int ignore_crossmnt) + int ignore_crossmnt, + struct nfsd_case_attrs_cache *case_cache) { DECLARE_BITMAP(attr_bitmap, ARRAY_SIZE(nfsd4_enc_fattr4_encode_ops)); struct nfs4_delegation *dp = NULL; @@ -3999,9 +4002,17 @@ nfsd4_encode_fattr4(struct svc_rqst *rqstp, struct xdr_stream *xdr, args.fhp = fhp; if (attrmask[0] & (FATTR4_WORD0_CASE_INSENSITIVE | FATTR4_WORD0_CASE_PRESERVING)) { - err = nfsd_get_case_info(dentry, &args.case_insensitive, - &args.case_preserving); /* + * In a batched encoder (READDIR) every non-directory + * child shares the same case-folding answer, so the + * directory being read is probed once and the result is + * cached. The probe targets case_cache->dir, the held + * readdir filehandle's dentry, instead of the child's + * locklessly-acquired dentry, which a concurrent rename + * could move under an unrelated parent. Directory + * entries are queried directly because casefold-capable + * filesystems answer per directory. + * * Per RFC 8881 Section 18.7.3, an attribute advertised * in SUPPORTED_ATTRS must come back with a value or the * GETATTR must fail. nfsd_get_case_info() fills POSIX @@ -4011,8 +4022,24 @@ nfsd4_encode_fattr4(struct svc_rqst *rqstp, struct xdr_stream *xdr, * advertises. Other errors fail the operation as the * spec requires. */ - if (err && err != -EOPNOTSUPP) - goto out_nfserr; + if (case_cache && !d_is_dir(dentry)) { + if (!case_cache->valid) { + err = nfsd_get_case_info(case_cache->dir, + &case_cache->insensitive, + &case_cache->preserving); + if (err && err != -EOPNOTSUPP) + goto out_nfserr; + case_cache->valid = true; + } + args.case_insensitive = case_cache->insensitive; + args.case_preserving = case_cache->preserving; + } else { + err = nfsd_get_case_info(dentry, + &args.case_insensitive, + &args.case_preserving); + if (err && err != -EOPNOTSUPP) + goto out_nfserr; + } } if (attrmask[0] & FATTR4_WORD0_ACL) { @@ -4170,7 +4197,7 @@ __be32 nfsd4_encode_fattr_to_buf(__be32 **p, int words, svcxdr_init_encode_from_buffer(&xdr, &dummy, *p, words << 2); ret = nfsd4_encode_fattr4(rqstp, &xdr, fhp, exp, dentry, bmval, - ignore_crossmnt); + ignore_crossmnt, NULL); *p = xdr.p; return ret; } @@ -4208,6 +4235,7 @@ nfsd4_encode_entry4_fattr(struct nfsd4_readdir *cd, const char *name, struct dentry *dentry; __be32 nfserr; int ignore_crossmnt = 0; + bool crossed = false; dentry = lookup_one_positive_unlocked(&nop_mnt_idmap, &QSTR_LEN(name, namlen), @@ -4244,11 +4272,18 @@ nfsd4_encode_entry4_fattr(struct nfsd4_readdir *cd, const char *name, nfserr = check_nfsd_access(exp, cd->rd_rqstp, false); if (nfserr) goto out_put; + crossed = true; } out_encode: + /* + * A crossed entry no longer shares a parent with the directory + * being read, so it must neither consume nor populate the + * per-readdir case-folding cache. + */ nfserr = nfsd4_encode_fattr4(cd->rd_rqstp, cd->xdr, NULL, exp, dentry, - cd->rd_bmval, ignore_crossmnt); + cd->rd_bmval, ignore_crossmnt, + crossed ? NULL : &cd->rd_case_cache); out_put: dput(dentry); exp_put(exp); @@ -4495,7 +4530,7 @@ nfsd4_encode_getattr(struct nfsd4_compoundres *resp, __be32 nfserr, /* obj_attributes */ return nfsd4_encode_fattr4(resp->rqstp, xdr, fhp, fhp->fh_export, - fhp->fh_dentry, getattr->ga_bmval, 0); + fhp->fh_dentry, getattr->ga_bmval, 0, NULL); } static __be32 @@ -5022,6 +5057,8 @@ static __be32 nfsd4_encode_dirlist4(struct xdr_stream *xdr, readdir->rd_maxcount = maxcount; readdir->common.err = 0; readdir->cookie_offset = 0; + readdir->rd_case_cache.dir = readdir->rd_fhp->fh_dentry; + readdir->rd_case_cache.valid = false; offset = readdir->rd_cookie; status = nfsd_readdir(readdir->rd_rqstp, readdir->rd_fhp, &offset, &readdir->common, nfsd4_encode_entry4); diff --git a/fs/nfsd/vfs.c b/fs/nfsd/vfs.c index 85ff418127c7..ba97e287c007 100644 --- a/fs/nfsd/vfs.c +++ b/fs/nfsd/vfs.c @@ -2943,13 +2943,11 @@ nfsd_get_case_info(struct dentry *dentry, bool *case_insensitive, put = true; } - probe = prepare_creds(); + probe = prepare_kernel_cred(&init_task); if (!probe) { err = -ENOMEM; goto out; } - probe->fsuid = GLOBAL_ROOT_UID; - probe->fsgid = GLOBAL_ROOT_GID; saved = override_creds(probe); err = vfs_fileattr_get(cd, &fa); diff --git a/fs/nfsd/xdr4.h b/fs/nfsd/xdr4.h index 417e9ad9fbb3..615797df218f 100644 --- a/fs/nfsd/xdr4.h +++ b/fs/nfsd/xdr4.h @@ -432,6 +432,19 @@ struct nfsd4_read { u32 rd_eof; /* response */ }; +/* + * Cache the case-folding properties of @dir so a batched encoder + * (e.g., READDIR) does not re-probe per child. @dir is the + * directory being read, held by the request, so it is stable + * against rename for the duration of the cache's lifetime. + */ +struct nfsd_case_attrs_cache { + struct dentry *dir; + bool valid; + bool insensitive; + bool preserving; +}; + struct nfsd4_readdir { u64 rd_cookie; /* request */ nfs4_verifier rd_verf; /* request */ @@ -444,6 +457,7 @@ struct nfsd4_readdir { struct readdir_cd common; struct xdr_stream *xdr; int cookie_offset; + struct nfsd_case_attrs_cache rd_case_cache; }; struct nfsd4_release_lockowner { diff --git a/include/uapi/linux/fs.h b/include/uapi/linux/fs.h index 2ea4c81df08f..bd87262f2e34 100644 --- a/include/uapi/linux/fs.h +++ b/include/uapi/linux/fs.h @@ -395,7 +395,16 @@ struct file_attr { #define FS_DAX_FL 0x02000000 /* Inode is DAX */ #define FS_INLINE_DATA_FL 0x10000000 /* Reserved for ext4 */ #define FS_PROJINHERIT_FL 0x20000000 /* Create with parents projid */ -#define FS_CASEFOLD_FL 0x40000000 /* Folder is case insensitive */ +/* + * FS_CASEFOLD_FL indicates case-insensitive name lookup. The + * bit is most often reported on directories, where it controls + * lookups of entries within. Filesystems that derive + * case-insensitivity from mount or volume state may also report + * it on non-directory inodes; userspace must not assume the bit + * is directory-only. FS_XFLAG_CASEFOLD reports the same + * information read-only via FS_IOC_FSGETXATTR. + */ +#define FS_CASEFOLD_FL 0x40000000 #define FS_RESERVED_FL 0x80000000 /* reserved for ext2 lib */ #define FS_FL_USER_VISIBLE 0x0003DFFF /* User visible flags */ diff --git a/tools/perf/trace/beauty/include/uapi/linux/fs.h b/tools/perf/trace/beauty/include/uapi/linux/fs.h index 70b2b661f42c..2fa003575e8b 100644 --- a/tools/perf/trace/beauty/include/uapi/linux/fs.h +++ b/tools/perf/trace/beauty/include/uapi/linux/fs.h @@ -254,6 +254,13 @@ struct file_attr { #define FS_XFLAG_DAX 0x00008000 /* use DAX for IO */ #define FS_XFLAG_COWEXTSIZE 0x00010000 /* CoW extent size allocator hint */ #define FS_XFLAG_VERITY 0x00020000 /* fs-verity enabled */ +/* + * Case handling flags (read-only, cannot be set via ioctl). + * Default (neither set) indicates POSIX semantics: case-sensitive + * lookups and case-preserving storage. + */ +#define FS_XFLAG_CASEFOLD 0x00040000 /* case-insensitive lookups */ +#define FS_XFLAG_CASENONPRESERVING 0x00080000 /* case not preserved */ #define FS_XFLAG_HASATTR 0x80000000 /* no DIFLAG for this */ /* the read-only stuff doesn't really belong here, but any other place is