nfsd: Implement NFSv4 FATTR4_CASE_INSENSITIVE and FATTR4_CASE_PRESERVING

NFSD currently provides NFSv4 clients with hard-coded responses
indicating all exported filesystems are case-sensitive and
case-preserving. This is incorrect for case-insensitive filesystems
and ext4 directories with casefold enabled.

Query the underlying filesystem's actual case sensitivity via
nfsd_get_case_info() and return accurate values to clients. This
supports per-directory settings for filesystems that allow mixing
case-sensitive and case-insensitive directories within an export.

The helper queries the parent dentry for non-directory filehandles
because case-folding is a per-directory property. That resolution
has the same corner cases here as for NFSv3 PATHCONF: single-file
exports query an unexported parent, disconnected dentries report
defaults until reconnected, and hardlinked files track whichever
alias the dcache currently holds.

Reviewed-by: Roland Mainz <roland.mainz@nrubsig.org>
Signed-off-by: Chuck Lever <chuck.lever@oracle.com>
Link: https://patch.msgid.link/20260507-case-sensitivity-v14-14-e62cc8200435@oracle.com
Signed-off-by: Christian Brauner <brauner@kernel.org>
This commit is contained in:
Chuck Lever
2026-05-07 04:53:07 -04:00
committed by Christian Brauner
parent 211cb2ba48
commit 01ee7c3d2e

View File

@@ -3158,6 +3158,8 @@ struct nfsd4_fattr_args {
u32 rdattr_err;
bool contextsupport;
bool ignore_crossmnt;
bool case_insensitive;
bool case_preserving;
};
typedef __be32(*nfsd4_enc_attr)(struct xdr_stream *xdr,
@@ -3356,6 +3358,33 @@ static __be32 nfsd4_encode_fattr4_acl(struct xdr_stream *xdr,
return nfs_ok;
}
static __be32 nfsd4_encode_fattr4_case_insensitive(struct xdr_stream *xdr,
const struct nfsd4_fattr_args *args)
{
return nfsd4_encode_bool(xdr, args->case_insensitive);
}
static __be32 nfsd4_encode_fattr4_case_preserving(struct xdr_stream *xdr,
const struct nfsd4_fattr_args *args)
{
return nfsd4_encode_bool(xdr, args->case_preserving);
}
static __be32 nfsd4_encode_fattr4_homogeneous(struct xdr_stream *xdr,
const struct nfsd4_fattr_args *args)
{
/*
* Casefold-capable filesystems (e.g. ext4 or f2fs with the
* casefold feature) attach a Unicode encoding at mount time
* but apply case folding per directory. The per-file-system
* case_insensitive and case_preserving values can therefore
* legitimately differ across objects that share the same fsid.
* Report FATTR4_HOMOGENEOUS = FALSE on such filesystems to
* keep that variation consistent with RFC 8881 Section 5.8.2.16.
*/
return nfsd4_encode_bool(xdr, !sb_has_encoding(args->dentry->d_sb));
}
static __be32 nfsd4_encode_fattr4_filehandle(struct xdr_stream *xdr,
const struct nfsd4_fattr_args *args)
{
@@ -3748,8 +3777,8 @@ static const nfsd4_enc_attr nfsd4_enc_fattr4_encode_ops[] = {
[FATTR4_ACLSUPPORT] = nfsd4_encode_fattr4_aclsupport,
[FATTR4_ARCHIVE] = nfsd4_encode_fattr4__noop,
[FATTR4_CANSETTIME] = nfsd4_encode_fattr4__true,
[FATTR4_CASE_INSENSITIVE] = nfsd4_encode_fattr4__false,
[FATTR4_CASE_PRESERVING] = nfsd4_encode_fattr4__true,
[FATTR4_CASE_INSENSITIVE] = nfsd4_encode_fattr4_case_insensitive,
[FATTR4_CASE_PRESERVING] = nfsd4_encode_fattr4_case_preserving,
[FATTR4_CHOWN_RESTRICTED] = nfsd4_encode_fattr4__true,
[FATTR4_FILEHANDLE] = nfsd4_encode_fattr4_filehandle,
[FATTR4_FILEID] = nfsd4_encode_fattr4_fileid,
@@ -3758,7 +3787,7 @@ static const nfsd4_enc_attr nfsd4_enc_fattr4_encode_ops[] = {
[FATTR4_FILES_TOTAL] = nfsd4_encode_fattr4_files_total,
[FATTR4_FS_LOCATIONS] = nfsd4_encode_fattr4_fs_locations,
[FATTR4_HIDDEN] = nfsd4_encode_fattr4__noop,
[FATTR4_HOMOGENEOUS] = nfsd4_encode_fattr4__true,
[FATTR4_HOMOGENEOUS] = nfsd4_encode_fattr4_homogeneous,
[FATTR4_MAXFILESIZE] = nfsd4_encode_fattr4_maxfilesize,
[FATTR4_MAXLINK] = nfsd4_encode_fattr4_maxlink,
[FATTR4_MAXNAME] = nfsd4_encode_fattr4_maxname,
@@ -3968,6 +3997,23 @@ nfsd4_encode_fattr4(struct svc_rqst *rqstp, struct xdr_stream *xdr,
args.fhp = tempfh;
} else
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);
/*
* 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
* defaults and returns -EOPNOTSUPP when the underlying
* filesystem does not expose case state; encode those
* defaults so the reply agrees with what SUPPORTED_ATTRS
* advertises. Other errors fail the operation as the
* spec requires.
*/
if (err && err != -EOPNOTSUPP)
goto out_nfserr;
}
if (attrmask[0] & FATTR4_WORD0_ACL) {
err = nfsd4_get_nfs4_acl(rqstp, dentry, &args.acl);