]> git.ipfire.org Git - thirdparty/kernel/linux.git/commitdiff
nfsd: Implement NFSv4 FATTR4_CASE_INSENSITIVE and FATTR4_CASE_PRESERVING
authorChuck Lever <chuck.lever@oracle.com>
Thu, 7 May 2026 08:53:07 +0000 (04:53 -0400)
committerChristian Brauner <brauner@kernel.org>
Mon, 11 May 2026 14:50:30 +0000 (16:50 +0200)
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>
fs/nfsd/nfs4xdr.c

index 2a0946c630e1d0aeef47f10c5b699a7bc8e3c5b0..319007b79d49f9113c1b586a61fac934a7f6875f 100644 (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);