Merge patch series "Casefold Fixes"

Bring in a bunch of fixes for the casefold work.

* patches from https://patch.msgid.link/20260515153515.362266-1-cel@kernel.org:
  nfsd: Cap case-folding probe cost across READDIR entries
  nfsd: Map -ESTALE from case probe to NFS3ERR_STALE
  nfsd: Use kernel credentials for case-info probe
  fs: Clarify FS_CASEFOLD_FL semantics in UAPI header
  nfs: Skip pathconf probe when neither field is consumed
  nfs: Avoid transient zeroed case capability bits during probe
  tools headers UAPI: Sync case-sensitivity flags from linux/fs.h

Link: https://patch.msgid.link/20260515153515.362266-1-cel@kernel.org
Signed-off-by: Christian Brauner <brauner@kernel.org>
This commit is contained in:
Christian Brauner 2026-05-15 17:49:29 +02:00
commit ea3120fd51
No known key found for this signature in database
GPG Key ID: 91C61BC06578DCA2
7 changed files with 98 additions and 25 deletions

View File

@ -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);
}
}

View File

@ -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;

View File

@ -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);

View File

@ -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);

View File

@ -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 {

View File

@ -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 */

View File

@ -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