]> git.ipfire.org Git - thirdparty/kernel/linux.git/commitdiff
cifs: Implement fileattr_get for case sensitivity
authorChuck Lever <chuck.lever@oracle.com>
Thu, 7 May 2026 08:53:02 +0000 (04:53 -0400)
committerChristian Brauner <brauner@kernel.org>
Mon, 11 May 2026 14:50:29 +0000 (16:50 +0200)
Upper layers such as NFSD need a way to query whether a filesystem
handles filenames in a case-sensitive manner. Report CIFS/SMB case
handling behavior via FS_XFLAG_CASEFOLD and
FS_XFLAG_CASENONPRESERVING.

The authoritative source is the server itself: at mount time CIFS
issues QueryFSInfo(FS_ATTRIBUTE_INFORMATION) and caches the reply
on the tcon. That reply carries FILE_CASE_SENSITIVE_SEARCH and
FILE_CASE_PRESERVED_NAMES, which reflect whatever case handling
the share actually implements after SMB3.1.1 POSIX extensions
negotiation. Translating those two bits into the VFS flags lets
cifs_fileattr_get report what the server advertises rather than
what the client was asked to pretend.

QueryFSInfo is best-effort; the mount completes even if the server
does not answer. MaxPathNameComponentLength is zero in that case
and is used as the "no reply received" sentinel. When no reply is
available, fall back to the nocase mount option so that the reported
behavior agrees with the dentry comparison operations installed on
the superblock.

The callback is registered on cifs_dir_inode_ops so that NFSD,
ksmbd, and other consumers querying case handling against a
directory get a definitive answer, and on cifs_file_inode_ops to
preserve FS_COMPR_FL reporting on regular files. cifs_set_ops()
also installs cifs_namespace_inode_operations on DFS referral
directories that carry IS_AUTOMOUNT; register the same callback
there so the answer does not depend on whether the directory is
a referral point.

Registering fileattr_get routes FS_IOC_GETFLAGS through
vfs_fileattr_get() and short-circuits the syscall's fallback to
cifs_ioctl(). That fallback invoked CIFSGetExtAttr() under
CONFIG_CIFS_POSIX and CONFIG_CIFS_ALLOW_INSECURE_LEGACY on servers
advertising CIFS_UNIX_EXTATTR_CAP, surfacing the SMB1 Unix-extension
immutable, append, and nodump bits. cifs_fileattr_get carries over
only FS_COMPR_FL from cached cifsAttrs; the SMB1 extattr fetch is
not reproduced. SMB1 is deprecated, and acquiring a netfid from
within a dentry-only callback is not worth preserving a path tied
to an insecure legacy dialect.

Acked-by: Steve French <stfrench@microsoft.com>
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-9-e62cc8200435@oracle.com
Signed-off-by: Christian Brauner <brauner@kernel.org>
fs/smb/client/cifsfs.c
fs/smb/client/cifsfs.h
fs/smb/client/namespace.c

index 9f76b0347fa9d71df496eb2584b92f0759931069..59e54f8bebb2f27f40b35e9a43029ec54e251091 100644 (file)
@@ -30,6 +30,7 @@
 #include <linux/xattr.h>
 #include <linux/mm.h>
 #include <linux/key-type.h>
+#include <linux/fileattr.h>
 #include <uapi/linux/magic.h>
 #include <net/ipv6.h>
 #include "cifsfs.h"
@@ -1162,6 +1163,56 @@ struct file_system_type smb3_fs_type = {
 MODULE_ALIAS_FS("smb3");
 MODULE_ALIAS("smb3");
 
+int cifs_fileattr_get(struct dentry *dentry, struct file_kattr *fa)
+{
+       struct cifs_sb_info *cifs_sb = CIFS_SB(dentry->d_sb);
+       struct cifs_tcon *tcon = cifs_sb_master_tcon(cifs_sb);
+       struct inode *inode = d_inode(dentry);
+       u32 attrs;
+
+       /* Preserve FS_COMPR_FL previously reported by cifs_ioctl(). */
+       if (CIFS_I(inode)->cifsAttrs & ATTR_COMPRESSED)
+               fa->flags |= FS_COMPR_FL;
+
+       /*
+        * FS_CASEFOLD_FL is defined by UAPI as a folder attribute,
+        * and userspace tools (e.g., lsattr) display it only on
+        * directories. Confine the case-handling bits to directories
+        * to match that convention; for non-directories the share's
+        * case semantics are still discoverable through the parent.
+        */
+       if (!S_ISDIR(inode->i_mode))
+               return 0;
+
+       /*
+        * The server's FS_ATTRIBUTE_INFORMATION response, cached on
+        * the tcon at mount, reflects the share's case-handling
+        * semantics after any POSIX extensions negotiation. Prefer
+        * it over the client-local nocase mount option, which only
+        * governs dentry comparison on this superblock.
+        *
+        * QueryFSInfo is best-effort at mount; when it did not
+        * populate fsAttrInfo, MaxPathNameComponentLength remains
+        * zero. In that case fall back to nocase so the reporting
+        * matches the comparison behavior installed on the sb.
+        */
+       if (le32_to_cpu(tcon->fsAttrInfo.MaxPathNameComponentLength) == 0) {
+               if (tcon->nocase) {
+                       fa->fsx_xflags |= FS_XFLAG_CASEFOLD;
+                       fa->flags |= FS_CASEFOLD_FL;
+               }
+               return 0;
+       }
+       attrs = le32_to_cpu(tcon->fsAttrInfo.Attributes);
+       if (!(attrs & FILE_CASE_SENSITIVE_SEARCH)) {
+               fa->fsx_xflags |= FS_XFLAG_CASEFOLD;
+               fa->flags |= FS_CASEFOLD_FL;
+       }
+       if (!(attrs & FILE_CASE_PRESERVED_NAMES))
+               fa->fsx_xflags |= FS_XFLAG_CASENONPRESERVING;
+       return 0;
+}
+
 const struct inode_operations cifs_dir_inode_ops = {
        .create = cifs_create,
        .atomic_open = cifs_atomic_open,
@@ -1180,6 +1231,7 @@ const struct inode_operations cifs_dir_inode_ops = {
        .listxattr = cifs_listxattr,
        .get_acl = cifs_get_acl,
        .set_acl = cifs_set_acl,
+       .fileattr_get = cifs_fileattr_get,
 };
 
 const struct inode_operations cifs_file_inode_ops = {
@@ -1190,6 +1242,7 @@ const struct inode_operations cifs_file_inode_ops = {
        .fiemap = cifs_fiemap,
        .get_acl = cifs_get_acl,
        .set_acl = cifs_set_acl,
+       .fileattr_get = cifs_fileattr_get,
 };
 
 const char *cifs_get_link(struct dentry *dentry, struct inode *inode,
index c455b15f277821da870eac8866e7cd11946dff6e..9d85224fafab526dc19db96d947cb2c4022d6c8c 100644 (file)
@@ -89,6 +89,9 @@ extern const struct inode_operations cifs_file_inode_ops;
 extern const struct inode_operations cifs_symlink_inode_ops;
 extern const struct inode_operations cifs_namespace_inode_operations;
 
+struct file_kattr;
+int cifs_fileattr_get(struct dentry *dentry, struct file_kattr *fa);
+
 
 /* Functions related to files and directories */
 extern const struct netfs_request_ops cifs_req_ops;
index 52a520349cb76b0331fe5fad7d4580480ce7ef6f..52a51b032fae3b36334e4bcb72471395456a7ddf 100644 (file)
@@ -294,4 +294,5 @@ struct vfsmount *cifs_d_automount(struct path *path)
 }
 
 const struct inode_operations cifs_namespace_inode_operations = {
+       .fileattr_get   = cifs_fileattr_get,
 };