From: Chuck Lever Enable upper layers such as NFSD to retrieve case sensitivity information from file systems by adding case_insensitive and case_nonpreserving boolean fields to struct file_kattr. The case_insensitive and case_nonpreserving fields in struct file_kattr default to false (POSIX semantics: case-sensitive and case-preserving), allowing filesystems to set them only when behavior differs from the default. Case sensitivity information is exported to userspace via the existing fa_xflags field using the new FS_XFLAG_CASEFOLD and FS_XFLAG_CASENONPRESERVING flags. Signed-off-by: Chuck Lever --- fs/file_attr.c | 6 ++++++ include/linux/fileattr.h | 6 +++++- include/uapi/linux/fs.h | 2 ++ 3 files changed, 13 insertions(+), 1 deletion(-) diff --git a/fs/file_attr.c b/fs/file_attr.c index 13cdb31a3e94..2f83f3c6a170 100644 --- a/fs/file_attr.c +++ b/fs/file_attr.c @@ -84,6 +84,8 @@ int vfs_fileattr_get(struct dentry *dentry, struct file_kattr *fa) struct inode *inode = d_inode(dentry); int error; + memset(fa, 0, sizeof(*fa)); + if (!inode->i_op->fileattr_get) return -ENOIOCTLCMD; @@ -106,6 +108,10 @@ static void fileattr_to_file_attr(const struct file_kattr *fa, fattr->fa_nextents = fa->fsx_nextents; fattr->fa_projid = fa->fsx_projid; fattr->fa_cowextsize = fa->fsx_cowextsize; + if (fa->case_insensitive) + fattr->fa_xflags |= FS_XFLAG_CASEFOLD; + if (fa->case_nonpreserving) + fattr->fa_xflags |= FS_XFLAG_CASENONPRESERVING; } /** diff --git a/include/linux/fileattr.h b/include/linux/fileattr.h index f89dcfad3f8f..7f2e557255ce 100644 --- a/include/linux/fileattr.h +++ b/include/linux/fileattr.h @@ -16,7 +16,8 @@ /* Read-only inode flags */ #define FS_XFLAG_RDONLY_MASK \ - (FS_XFLAG_PREALLOC | FS_XFLAG_HASATTR) + (FS_XFLAG_PREALLOC | FS_XFLAG_HASATTR | \ + FS_XFLAG_CASEFOLD | FS_XFLAG_CASENONPRESERVING) /* Flags to indicate valid value of fsx_ fields */ #define FS_XFLAG_VALUES_MASK \ @@ -51,6 +52,9 @@ struct file_kattr { /* selectors: */ bool flags_valid:1; bool fsx_valid:1; + /* case sensitivity behavior: */ + bool case_insensitive:1; + bool case_nonpreserving:1; }; int copy_fsxattr_to_user(const struct file_kattr *fa, struct fsxattr __user *ufa); diff --git a/include/uapi/linux/fs.h b/include/uapi/linux/fs.h index 66ca526cf786..919148beaa8c 100644 --- a/include/uapi/linux/fs.h +++ b/include/uapi/linux/fs.h @@ -253,6 +253,8 @@ struct file_attr { #define FS_XFLAG_FILESTREAM 0x00004000 /* use filestream allocator */ #define FS_XFLAG_DAX 0x00008000 /* use DAX for IO */ #define FS_XFLAG_COWEXTSIZE 0x00010000 /* CoW extent size allocator hint */ +#define FS_XFLAG_CASEFOLD 0x00020000 /* case-insensitive lookups */ +#define FS_XFLAG_CASENONPRESERVING 0x00040000 /* 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 -- 2.52.0 From: Chuck Lever Report FAT's case sensitivity behavior via the file_kattr boolean fields. FAT filesystems are case-insensitive by default. MSDOS supports a 'nocase' mount option that enables case-sensitive behavior; check this option when reporting case sensitivity. VFAT long filename entries preserve case; without VFAT, only uppercased 8.3 short names are stored. MSDOS with 'nocase' also preserves case since the name-formatting code skips upcasing when 'nocase' is set. Check both options when reporting case preservation. Signed-off-by: Chuck Lever --- fs/fat/fat.h | 3 +++ fs/fat/file.c | 20 ++++++++++++++++++++ fs/fat/namei_msdos.c | 1 + fs/fat/namei_vfat.c | 1 + 4 files changed, 25 insertions(+) diff --git a/fs/fat/fat.h b/fs/fat/fat.h index d3e426de5f01..9e208eeb46c4 100644 --- a/fs/fat/fat.h +++ b/fs/fat/fat.h @@ -10,6 +10,8 @@ #include #include +struct file_kattr; + /* * vfat shortname flags */ @@ -407,6 +409,7 @@ extern void fat_truncate_blocks(struct inode *inode, loff_t offset); extern int fat_getattr(struct mnt_idmap *idmap, const struct path *path, struct kstat *stat, u32 request_mask, unsigned int flags); +int fat_fileattr_get(struct dentry *dentry, struct file_kattr *fa); extern int fat_file_fsync(struct file *file, loff_t start, loff_t end, int datasync); diff --git a/fs/fat/file.c b/fs/fat/file.c index 4fc49a614fb8..328f9e3c9fa3 100644 --- a/fs/fat/file.c +++ b/fs/fat/file.c @@ -16,6 +16,7 @@ #include #include #include +#include #include "fat.h" static long fat_fallocate(struct file *file, int mode, @@ -395,6 +396,24 @@ void fat_truncate_blocks(struct inode *inode, loff_t offset) fat_flush_inodes(inode->i_sb, inode, NULL); } +int fat_fileattr_get(struct dentry *dentry, struct file_kattr *fa) +{ + struct msdos_sb_info *sbi = MSDOS_SB(dentry->d_sb); + + /* + * FAT filesystems are case-insensitive by default. MSDOS + * supports a 'nocase' mount option for case-sensitive behavior. + * + * VFAT long filename entries preserve case. Without VFAT, only + * uppercased 8.3 short names are stored. MSDOS with 'nocase' + * also preserves case. + */ + fa->case_insensitive = !sbi->options.nocase; + fa->case_nonpreserving = !sbi->options.isvfat && !sbi->options.nocase; + return 0; +} +EXPORT_SYMBOL_GPL(fat_fileattr_get); + int fat_getattr(struct mnt_idmap *idmap, const struct path *path, struct kstat *stat, u32 request_mask, unsigned int flags) { @@ -574,5 +593,6 @@ EXPORT_SYMBOL_GPL(fat_setattr); const struct inode_operations fat_file_inode_operations = { .setattr = fat_setattr, .getattr = fat_getattr, + .fileattr_get = fat_fileattr_get, .update_time = fat_update_time, }; diff --git a/fs/fat/namei_msdos.c b/fs/fat/namei_msdos.c index 0b920ee40a7f..380add5c6c66 100644 --- a/fs/fat/namei_msdos.c +++ b/fs/fat/namei_msdos.c @@ -640,6 +640,7 @@ static const struct inode_operations msdos_dir_inode_operations = { .rename = msdos_rename, .setattr = fat_setattr, .getattr = fat_getattr, + .fileattr_get = fat_fileattr_get, .update_time = fat_update_time, }; diff --git a/fs/fat/namei_vfat.c b/fs/fat/namei_vfat.c index 5dbc4cbb8fce..6cf513f97afa 100644 --- a/fs/fat/namei_vfat.c +++ b/fs/fat/namei_vfat.c @@ -1180,6 +1180,7 @@ static const struct inode_operations vfat_dir_inode_operations = { .rename = vfat_rename2, .setattr = fat_setattr, .getattr = fat_getattr, + .fileattr_get = fat_fileattr_get, .update_time = fat_update_time, }; -- 2.52.0 From: Chuck Lever Report exFAT's case sensitivity behavior via the file_kattr boolean fields. exFAT is always case-insensitive (using an upcase table for comparison) and always preserves case at rest. Acked-by: Namjae Jeon Signed-off-by: Chuck Lever --- fs/exfat/exfat_fs.h | 2 ++ fs/exfat/file.c | 16 ++++++++++++++-- fs/exfat/namei.c | 1 + 3 files changed, 17 insertions(+), 2 deletions(-) diff --git a/fs/exfat/exfat_fs.h b/fs/exfat/exfat_fs.h index 176fef62574c..11c782a28843 100644 --- a/fs/exfat/exfat_fs.h +++ b/fs/exfat/exfat_fs.h @@ -468,6 +468,8 @@ int exfat_setattr(struct mnt_idmap *idmap, struct dentry *dentry, int exfat_getattr(struct mnt_idmap *idmap, const struct path *path, struct kstat *stat, unsigned int request_mask, unsigned int query_flags); +struct file_kattr; +int exfat_fileattr_get(struct dentry *dentry, struct file_kattr *fa); int exfat_file_fsync(struct file *file, loff_t start, loff_t end, int datasync); long exfat_ioctl(struct file *filp, unsigned int cmd, unsigned long arg); long exfat_compat_ioctl(struct file *filp, unsigned int cmd, diff --git a/fs/exfat/file.c b/fs/exfat/file.c index 536c8078f0c1..79938072e1cb 100644 --- a/fs/exfat/file.c +++ b/fs/exfat/file.c @@ -12,6 +12,7 @@ #include #include #include +#include #include "exfat_raw.h" #include "exfat_fs.h" @@ -281,6 +282,16 @@ int exfat_getattr(struct mnt_idmap *idmap, const struct path *path, return 0; } +int exfat_fileattr_get(struct dentry *dentry, struct file_kattr *fa) +{ + /* + * exFAT is always case-insensitive (using upcase table). + * Case is preserved at rest (the default). + */ + fa->case_insensitive = true; + return 0; +} + int exfat_setattr(struct mnt_idmap *idmap, struct dentry *dentry, struct iattr *attr) { @@ -775,6 +786,7 @@ const struct file_operations exfat_file_operations = { }; const struct inode_operations exfat_file_inode_operations = { - .setattr = exfat_setattr, - .getattr = exfat_getattr, + .setattr = exfat_setattr, + .getattr = exfat_getattr, + .fileattr_get = exfat_fileattr_get, }; diff --git a/fs/exfat/namei.c b/fs/exfat/namei.c index dfe957493d49..a3a854ddc83a 100644 --- a/fs/exfat/namei.c +++ b/fs/exfat/namei.c @@ -1323,4 +1323,5 @@ const struct inode_operations exfat_dir_inode_operations = { .rename = exfat_rename, .setattr = exfat_setattr, .getattr = exfat_getattr, + .fileattr_get = exfat_fileattr_get, }; -- 2.52.0 From: Chuck Lever Report NTFS case sensitivity behavior via the file_kattr boolean fields. NTFS always preserves case at rest. Case sensitivity depends on mount options: with "nocase", NTFS is case-insensitive; otherwise it is case-sensitive. Signed-off-by: Chuck Lever --- fs/ntfs3/file.c | 22 ++++++++++++++++++++++ fs/ntfs3/inode.c | 1 + fs/ntfs3/namei.c | 2 ++ fs/ntfs3/ntfs_fs.h | 1 + 4 files changed, 26 insertions(+) diff --git a/fs/ntfs3/file.c b/fs/ntfs3/file.c index 2e7b2e566ebe..434a2d48db02 100644 --- a/fs/ntfs3/file.c +++ b/fs/ntfs3/file.c @@ -146,6 +146,27 @@ long ntfs_compat_ioctl(struct file *filp, u32 cmd, unsigned long arg) } #endif +/* + * ntfs_fileattr_get - inode_operations::fileattr_get + */ +int ntfs_fileattr_get(struct dentry *dentry, struct file_kattr *fa) +{ + struct inode *inode = d_inode(dentry); + struct ntfs_sb_info *sbi = inode->i_sb->s_fs_info; + + /* Avoid any operation if inode is bad. */ + if (unlikely(is_bad_ni(ntfs_i(inode)))) + return -EINVAL; + + /* + * NTFS preserves case (the default). Case sensitivity depends on + * mount options: with "nocase", NTFS is case-insensitive; + * otherwise it is case-sensitive. + */ + fa->case_insensitive = sbi->options && sbi->options->nocase; + return 0; +} + /* * ntfs_getattr - inode_operations::getattr */ @@ -1460,6 +1481,7 @@ const struct inode_operations ntfs_file_inode_operations = { .get_acl = ntfs_get_acl, .set_acl = ntfs_set_acl, .fiemap = ntfs_fiemap, + .fileattr_get = ntfs_fileattr_get, }; const struct file_operations ntfs_file_operations = { diff --git a/fs/ntfs3/inode.c b/fs/ntfs3/inode.c index 0a9ac5efeb67..205083e8a6e0 100644 --- a/fs/ntfs3/inode.c +++ b/fs/ntfs3/inode.c @@ -2089,6 +2089,7 @@ const struct inode_operations ntfs_link_inode_operations = { .get_link = ntfs_get_link, .setattr = ntfs_setattr, .listxattr = ntfs_listxattr, + .fileattr_get = ntfs_fileattr_get, }; const struct address_space_operations ntfs_aops = { diff --git a/fs/ntfs3/namei.c b/fs/ntfs3/namei.c index 3b24ca02de61..d09414711016 100644 --- a/fs/ntfs3/namei.c +++ b/fs/ntfs3/namei.c @@ -519,6 +519,7 @@ const struct inode_operations ntfs_dir_inode_operations = { .getattr = ntfs_getattr, .listxattr = ntfs_listxattr, .fiemap = ntfs_fiemap, + .fileattr_get = ntfs_fileattr_get, }; const struct inode_operations ntfs_special_inode_operations = { @@ -527,6 +528,7 @@ const struct inode_operations ntfs_special_inode_operations = { .listxattr = ntfs_listxattr, .get_acl = ntfs_get_acl, .set_acl = ntfs_set_acl, + .fileattr_get = ntfs_fileattr_get, }; const struct dentry_operations ntfs_dentry_ops = { diff --git a/fs/ntfs3/ntfs_fs.h b/fs/ntfs3/ntfs_fs.h index a4559c9f64e6..a578b75f31fc 100644 --- a/fs/ntfs3/ntfs_fs.h +++ b/fs/ntfs3/ntfs_fs.h @@ -504,6 +504,7 @@ extern const struct file_operations ntfs_dir_operations; extern const struct file_operations ntfs_legacy_dir_operations; /* Globals from file.c */ +int ntfs_fileattr_get(struct dentry *dentry, struct file_kattr *fa); int ntfs_getattr(struct mnt_idmap *idmap, const struct path *path, struct kstat *stat, u32 request_mask, u32 flags); int ntfs_setattr(struct mnt_idmap *idmap, struct dentry *dentry, -- 2.52.0 From: Chuck Lever Report HFS case sensitivity behavior via the file_kattr boolean fields. HFS is always case-insensitive (using Mac OS Roman case folding) and always preserves case at rest. Reviewed-by: Viacheslav Dubeyko Signed-off-by: Chuck Lever --- fs/hfs/dir.c | 1 + fs/hfs/hfs_fs.h | 2 ++ fs/hfs/inode.c | 12 ++++++++++++ 3 files changed, 15 insertions(+) diff --git a/fs/hfs/dir.c b/fs/hfs/dir.c index 86a6b317b474..552156896105 100644 --- a/fs/hfs/dir.c +++ b/fs/hfs/dir.c @@ -321,4 +321,5 @@ const struct inode_operations hfs_dir_inode_operations = { .rmdir = hfs_remove, .rename = hfs_rename, .setattr = hfs_inode_setattr, + .fileattr_get = hfs_fileattr_get, }; diff --git a/fs/hfs/hfs_fs.h b/fs/hfs/hfs_fs.h index e94dbc04a1e4..a25cdda8ab34 100644 --- a/fs/hfs/hfs_fs.h +++ b/fs/hfs/hfs_fs.h @@ -177,6 +177,8 @@ extern int hfs_get_block(struct inode *inode, sector_t block, extern const struct address_space_operations hfs_aops; extern const struct address_space_operations hfs_btree_aops; +struct file_kattr; +int hfs_fileattr_get(struct dentry *dentry, struct file_kattr *fa); int hfs_write_begin(const struct kiocb *iocb, struct address_space *mapping, loff_t pos, unsigned int len, struct folio **foliop, void **fsdata); diff --git a/fs/hfs/inode.c b/fs/hfs/inode.c index 524db1389737..c52bc52712eb 100644 --- a/fs/hfs/inode.c +++ b/fs/hfs/inode.c @@ -18,6 +18,7 @@ #include #include #include +#include #include "hfs_fs.h" #include "btree.h" @@ -698,6 +699,16 @@ static int hfs_file_fsync(struct file *filp, loff_t start, loff_t end, return ret; } +int hfs_fileattr_get(struct dentry *dentry, struct file_kattr *fa) +{ + /* + * HFS is always case-insensitive (using Mac OS Roman case + * folding). Case is preserved at rest (the default). + */ + fa->case_insensitive = true; + return 0; +} + static const struct file_operations hfs_file_operations = { .llseek = generic_file_llseek, .read_iter = generic_file_read_iter, @@ -714,4 +725,5 @@ static const struct inode_operations hfs_file_inode_operations = { .lookup = hfs_file_lookup, .setattr = hfs_inode_setattr, .listxattr = generic_listxattr, + .fileattr_get = hfs_fileattr_get, }; -- 2.52.0 From: Chuck Lever Add case sensitivity reporting to the existing hfsplus_fileattr_get() function. HFS+ always preserves case at rest. Case sensitivity depends on how the volume was formatted: HFSX volumes may be either case-sensitive or case-insensitive, indicated by the HFSPLUS_SB_CASEFOLD superblock flag. Reviewed-by: Viacheslav Dubeyko Signed-off-by: Chuck Lever --- fs/hfsplus/inode.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/fs/hfsplus/inode.c b/fs/hfsplus/inode.c index 7ae6745ca7ae..7889d37f5c85 100644 --- a/fs/hfsplus/inode.c +++ b/fs/hfsplus/inode.c @@ -694,6 +694,7 @@ int hfsplus_fileattr_get(struct dentry *dentry, struct file_kattr *fa) { struct inode *inode = d_inode(dentry); struct hfsplus_inode_info *hip = HFSPLUS_I(inode); + struct hfsplus_sb_info *sbi = HFSPLUS_SB(inode->i_sb); unsigned int flags = 0; if (inode->i_flags & S_IMMUTABLE) @@ -705,6 +706,12 @@ int hfsplus_fileattr_get(struct dentry *dentry, struct file_kattr *fa) fileattr_fill_flags(fa, flags); + /* + * HFS+ preserves case (the default). Case sensitivity depends + * on how the filesystem was formatted: HFSX volumes may be + * either case-sensitive or case-insensitive. + */ + fa->case_insensitive = test_bit(HFSPLUS_SB_CASEFOLD, &sbi->flags); return 0; } -- 2.52.0 From: Chuck Lever Report ext4's case sensitivity behavior via file_kattr boolean fields. ext4 always preserves case at rest. Case sensitivity is a per-directory setting in ext4. If the queried inode is a casefolded directory, report case-insensitive; otherwise report case-sensitive (standard POSIX behavior). Reviewed-by: Jan Kara Signed-off-by: Chuck Lever --- fs/ext4/ioctl.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/fs/ext4/ioctl.c b/fs/ext4/ioctl.c index 7ce0fc40aec2..213769d217c3 100644 --- a/fs/ext4/ioctl.c +++ b/fs/ext4/ioctl.c @@ -996,6 +996,12 @@ int ext4_fileattr_get(struct dentry *dentry, struct file_kattr *fa) if (ext4_has_feature_project(inode->i_sb)) fa->fsx_projid = from_kprojid(&init_user_ns, ei->i_projid); + /* + * ext4 preserves case (the default). If this inode is a + * casefolded directory, report case-insensitive; otherwise + * report case-sensitive (standard POSIX behavior). + */ + fa->case_insensitive = IS_CASEFOLDED(inode); return 0; } -- 2.52.0 From: Chuck Lever Upper layers such as NFSD need to query whether a filesystem is case-sensitive. Populate the case_insensitive and case_preserving fields in xfs_fileattr_get(). XFS always preserves case. XFS is case-sensitive by default, but supports ASCII case-insensitive lookups when formatted with the ASCIICI feature flag. Reviewed-by: "Darrick J. Wong" Signed-off-by: Chuck Lever --- fs/xfs/xfs_ioctl.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/fs/xfs/xfs_ioctl.c b/fs/xfs/xfs_ioctl.c index 59eaad774371..e8061fe109e9 100644 --- a/fs/xfs/xfs_ioctl.c +++ b/fs/xfs/xfs_ioctl.c @@ -516,6 +516,12 @@ xfs_fileattr_get( xfs_fill_fsxattr(ip, XFS_DATA_FORK, fa); xfs_iunlock(ip, XFS_ILOCK_SHARED); + /* + * XFS preserves case (the default). It is case-sensitive by + * default, but can be formatted with ASCII case-insensitive + * mode enabled. + */ + fa->case_insensitive = xfs_has_asciici(ip->i_mount); return 0; } -- 2.52.0 From: Chuck Lever Upper layers such as NFSD need a way to query whether a filesystem handles filenames in a case-sensitive manner. The file_kattr structure now provides case_insensitive and case_preserving fields for this purpose, but CIFS does not yet report its case sensitivity behavior through this interface. Implement cifs_fileattr_get() to report CIFS/SMB case handling behavior. CIFS servers (typically Windows or Samba) are usually case-insensitive but case-preserving, meaning they ignore case during lookups but store filenames exactly as provided. The implementation reports case sensitivity based on the nocase mount option, which reflects whether the client expects the server to perform case-insensitive comparisons. When nocase is set, the mount is reported as case-insensitive. The case_preserving field is always set to true since SMB servers preserve filename case at rest. The callback is registered in all three inode_operations structures (directory, file, and symlink) to ensure consistent reporting across all inode types. Signed-off-by: Chuck Lever --- fs/smb/client/cifsfs.c | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/fs/smb/client/cifsfs.c b/fs/smb/client/cifsfs.c index d9664634144d..563eece79b13 100644 --- a/fs/smb/client/cifsfs.c +++ b/fs/smb/client/cifsfs.c @@ -30,6 +30,7 @@ #include #include #include +#include #include #include #include "cifsfs.h" @@ -1193,6 +1194,20 @@ struct file_system_type smb3_fs_type = { MODULE_ALIAS_FS("smb3"); MODULE_ALIAS("smb3"); +static 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); + + /* + * CIFS/SMB servers are typically case-insensitive and + * case-preserving (the default). The nocase mount option + * reflects what the client expects from the server. + */ + fa->case_insensitive = tcon->nocase; + return 0; +} + const struct inode_operations cifs_dir_inode_ops = { .create = cifs_create, .atomic_open = cifs_atomic_open, @@ -1210,6 +1225,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 = { @@ -1220,6 +1236,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, @@ -1254,6 +1271,7 @@ const struct inode_operations cifs_symlink_inode_ops = { .setattr = cifs_setattr, .permission = cifs_permission, .listxattr = cifs_listxattr, + .fileattr_get = cifs_fileattr_get, }; /* -- 2.52.0 From: Chuck Lever An NFS server re-exporting an NFS mount point needs to report the case sensitivity behavior of the underlying filesystem to its clients. Without this, re-export servers cannot accurately convey case handling semantics, potentially causing client applications to make incorrect assumptions about filename collisions and lookups. The NFS client already retrieves case sensitivity information from servers during mount via PATHCONF (NFSv3) or the FATTR4_CASE_INSENSITIVE/FATTR4_CASE_PRESERVING attributes (NFSv4). Expose this information through fileattr_get by reporting the case_insensitive and case_preserving flags in file_kattr. NFSv2 lacks PATHCONF support, so mounts using that protocol version default to standard POSIX behavior: case-sensitive and case- preserving. Signed-off-by: Chuck Lever --- fs/nfs/client.c | 9 +++++++-- fs/nfs/inode.c | 18 ++++++++++++++++++ fs/nfs/internal.h | 3 +++ fs/nfs/nfs3proc.c | 2 ++ fs/nfs/nfs3xdr.c | 7 +++++-- fs/nfs/nfs4proc.c | 2 ++ fs/nfs/proc.c | 3 +++ fs/nfs/symlink.c | 3 +++ include/linux/nfs_xdr.h | 2 ++ 9 files changed, 45 insertions(+), 4 deletions(-) diff --git a/fs/nfs/client.c b/fs/nfs/client.c index 2aaea9c98c2c..da437d89e14a 100644 --- a/fs/nfs/client.c +++ b/fs/nfs/client.c @@ -935,13 +935,18 @@ static int nfs_probe_fsinfo(struct nfs_server *server, struct nfs_fh *mntfh, str /* Get some general file system info */ if (server->namelen == 0) { - struct nfs_pathconf pathinfo; + struct nfs_pathconf pathinfo = { }; pathinfo.fattr = fattr; nfs_fattr_init(fattr); - if (clp->rpc_ops->pathconf(server, mntfh, &pathinfo) >= 0) + if (clp->rpc_ops->pathconf(server, mntfh, &pathinfo) >= 0) { server->namelen = pathinfo.max_namelen; + if (pathinfo.case_insensitive) + server->caps |= NFS_CAP_CASE_INSENSITIVE; + if (pathinfo.case_preserving) + server->caps |= NFS_CAP_CASE_PRESERVING; + } } if (clp->rpc_ops->discover_trunking != NULL && diff --git a/fs/nfs/inode.c b/fs/nfs/inode.c index 84049f3cd340..ee8234f8e84a 100644 --- a/fs/nfs/inode.c +++ b/fs/nfs/inode.c @@ -41,6 +41,7 @@ #include #include #include +#include #include "nfs4_fs.h" #include "callback.h" @@ -1100,6 +1101,23 @@ int nfs_getattr(struct mnt_idmap *idmap, const struct path *path, } EXPORT_SYMBOL_GPL(nfs_getattr); +/** + * nfs_fileattr_get - Retrieve file attributes + * @dentry: object to query + * @fa: file attributes to fill in + * + * Return: 0 on success + */ +int nfs_fileattr_get(struct dentry *dentry, struct file_kattr *fa) +{ + struct inode *inode = d_inode(dentry); + + fa->case_insensitive = nfs_server_capable(inode, NFS_CAP_CASE_INSENSITIVE); + fa->case_nonpreserving = !nfs_server_capable(inode, NFS_CAP_CASE_PRESERVING); + return 0; +} +EXPORT_SYMBOL_GPL(nfs_fileattr_get); + static void nfs_init_lock_context(struct nfs_lock_context *l_ctx) { refcount_set(&l_ctx->count, 1); diff --git a/fs/nfs/internal.h b/fs/nfs/internal.h index 2e596244799f..a843e076aad7 100644 --- a/fs/nfs/internal.h +++ b/fs/nfs/internal.h @@ -453,6 +453,9 @@ extern void nfs_set_cache_invalid(struct inode *inode, unsigned long flags); extern bool nfs_check_cache_invalid(struct inode *, unsigned long); extern int nfs_wait_bit_killable(struct wait_bit_key *key, int mode); +struct file_kattr; +int nfs_fileattr_get(struct dentry *dentry, struct file_kattr *fa); + #if IS_ENABLED(CONFIG_NFS_LOCALIO) /* localio.c */ struct nfs_local_dio { diff --git a/fs/nfs/nfs3proc.c b/fs/nfs/nfs3proc.c index 1181f9cc6dbd..60344a83f400 100644 --- a/fs/nfs/nfs3proc.c +++ b/fs/nfs/nfs3proc.c @@ -1048,6 +1048,7 @@ static const struct inode_operations nfs3_dir_inode_operations = { .permission = nfs_permission, .getattr = nfs_getattr, .setattr = nfs_setattr, + .fileattr_get = nfs_fileattr_get, #ifdef CONFIG_NFS_V3_ACL .listxattr = nfs3_listxattr, .get_inode_acl = nfs3_get_acl, @@ -1059,6 +1060,7 @@ static const struct inode_operations nfs3_file_inode_operations = { .permission = nfs_permission, .getattr = nfs_getattr, .setattr = nfs_setattr, + .fileattr_get = nfs_fileattr_get, #ifdef CONFIG_NFS_V3_ACL .listxattr = nfs3_listxattr, .get_inode_acl = nfs3_get_acl, diff --git a/fs/nfs/nfs3xdr.c b/fs/nfs/nfs3xdr.c index e17d72908412..e745e78faab0 100644 --- a/fs/nfs/nfs3xdr.c +++ b/fs/nfs/nfs3xdr.c @@ -2276,8 +2276,11 @@ static int decode_pathconf3resok(struct xdr_stream *xdr, if (unlikely(!p)) return -EIO; result->max_link = be32_to_cpup(p++); - result->max_namelen = be32_to_cpup(p); - /* ignore remaining fields */ + result->max_namelen = be32_to_cpup(p++); + p++; /* ignore no_trunc */ + p++; /* ignore chown_restricted */ + result->case_insensitive = be32_to_cpup(p++) != 0; + result->case_preserving = be32_to_cpup(p) != 0; return 0; } diff --git a/fs/nfs/nfs4proc.c b/fs/nfs/nfs4proc.c index ec1ce593dea2..e119c6ff61f0 100644 --- a/fs/nfs/nfs4proc.c +++ b/fs/nfs/nfs4proc.c @@ -11041,6 +11041,7 @@ static const struct inode_operations nfs4_dir_inode_operations = { .getattr = nfs_getattr, .setattr = nfs_setattr, .listxattr = nfs4_listxattr, + .fileattr_get = nfs_fileattr_get, }; static const struct inode_operations nfs4_file_inode_operations = { @@ -11048,6 +11049,7 @@ static const struct inode_operations nfs4_file_inode_operations = { .getattr = nfs_getattr, .setattr = nfs_setattr, .listxattr = nfs4_listxattr, + .fileattr_get = nfs_fileattr_get, }; static struct nfs_server *nfs4_clone_server(struct nfs_server *source, diff --git a/fs/nfs/proc.c b/fs/nfs/proc.c index 39df80e4ae6f..48f02a80b800 100644 --- a/fs/nfs/proc.c +++ b/fs/nfs/proc.c @@ -597,6 +597,7 @@ nfs_proc_pathconf(struct nfs_server *server, struct nfs_fh *fhandle, { info->max_link = 0; info->max_namelen = NFS2_MAXNAMLEN; + info->case_preserving = true; return 0; } @@ -718,12 +719,14 @@ static const struct inode_operations nfs_dir_inode_operations = { .permission = nfs_permission, .getattr = nfs_getattr, .setattr = nfs_setattr, + .fileattr_get = nfs_fileattr_get, }; static const struct inode_operations nfs_file_inode_operations = { .permission = nfs_permission, .getattr = nfs_getattr, .setattr = nfs_setattr, + .fileattr_get = nfs_fileattr_get, }; const struct nfs_rpc_ops nfs_v2_clientops = { diff --git a/fs/nfs/symlink.c b/fs/nfs/symlink.c index 58146e935402..74a072896f8d 100644 --- a/fs/nfs/symlink.c +++ b/fs/nfs/symlink.c @@ -22,6 +22,8 @@ #include #include +#include "internal.h" + /* Symlink caching in the page cache is even more simplistic * and straight-forward than readdir caching. */ @@ -74,4 +76,5 @@ const struct inode_operations nfs_symlink_inode_operations = { .get_link = nfs_get_link, .getattr = nfs_getattr, .setattr = nfs_setattr, + .fileattr_get = nfs_fileattr_get, }; diff --git a/include/linux/nfs_xdr.h b/include/linux/nfs_xdr.h index 79fe2dfb470f..5f061a9db2c2 100644 --- a/include/linux/nfs_xdr.h +++ b/include/linux/nfs_xdr.h @@ -182,6 +182,8 @@ struct nfs_pathconf { struct nfs_fattr *fattr; /* Post-op attributes */ __u32 max_link; /* max # of hard links */ __u32 max_namelen; /* max name length */ + bool case_insensitive; + bool case_preserving; }; struct nfs4_change_info { -- 2.52.0 From: Chuck Lever NFS and other remote filesystem protocols need to determine whether a local filesystem performs case-insensitive lookups so they can provide correct semantics to clients. Without this information, f2fs exports cannot properly advertise their filename case behavior. Report f2fs case sensitivity behavior via the file_kattr boolean fields. Like ext4, f2fs supports per-directory case folding via the casefold flag (IS_CASEFOLDED). Files are always case-preserving. Reviewed-by: Chao Yu Signed-off-by: Chuck Lever --- fs/f2fs/file.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/fs/f2fs/file.c b/fs/f2fs/file.c index d7047ca6b98d..5d4c129c9802 100644 --- a/fs/f2fs/file.c +++ b/fs/f2fs/file.c @@ -3439,6 +3439,12 @@ int f2fs_fileattr_get(struct dentry *dentry, struct file_kattr *fa) if (f2fs_sb_has_project_quota(F2FS_I_SB(inode))) fa->fsx_projid = from_kprojid(&init_user_ns, fi->i_projid); + /* + * f2fs preserves case (the default). If this inode is a + * casefolded directory, report case-insensitive; otherwise + * report case-sensitive (standard POSIX behavior). + */ + fa->case_insensitive = IS_CASEFOLDED(inode); return 0; } -- 2.52.0 From: Chuck Lever Upper layers such as NFSD need a way to query whether a filesystem handles filenames in a case-sensitive manner. The file_kattr structure now provides case_insensitive and case_preserving fields for this purpose, but vboxsf does not yet report its case sensitivity behavior through this interface. Implement vboxsf_fileattr_get() to report the case handling behavior of VirtualBox shared folders. The case sensitivity property is queried from the VirtualBox host service at mount time and cached in struct vboxsf_sbi. The host determines case sensitivity based on the underlying host filesystem (for example, Windows NTFS is case-insensitive while Linux ext4 is case-sensitive). VirtualBox shared folders always preserve filename case exactly as provided by the guest. The host interface does not expose a case_preserving property, so this is hardcoded to true. The callback is registered in all three inode_operations structures (directory, file, and symlink) to ensure consistent reporting across all inode types. Signed-off-by: Chuck Lever --- fs/vboxsf/dir.c | 1 + fs/vboxsf/file.c | 6 ++++-- fs/vboxsf/super.c | 4 ++++ fs/vboxsf/utils.c | 30 ++++++++++++++++++++++++++++++ fs/vboxsf/vfsmod.h | 6 ++++++ 5 files changed, 45 insertions(+), 2 deletions(-) diff --git a/fs/vboxsf/dir.c b/fs/vboxsf/dir.c index 42bedc4ec7af..c5bd3271aa96 100644 --- a/fs/vboxsf/dir.c +++ b/fs/vboxsf/dir.c @@ -477,4 +477,5 @@ const struct inode_operations vboxsf_dir_iops = { .symlink = vboxsf_dir_symlink, .getattr = vboxsf_getattr, .setattr = vboxsf_setattr, + .fileattr_get = vboxsf_fileattr_get, }; diff --git a/fs/vboxsf/file.c b/fs/vboxsf/file.c index 4bebd947314a..06308e38a70d 100644 --- a/fs/vboxsf/file.c +++ b/fs/vboxsf/file.c @@ -223,7 +223,8 @@ const struct file_operations vboxsf_reg_fops = { const struct inode_operations vboxsf_reg_iops = { .getattr = vboxsf_getattr, - .setattr = vboxsf_setattr + .setattr = vboxsf_setattr, + .fileattr_get = vboxsf_fileattr_get, }; static int vboxsf_read_folio(struct file *file, struct folio *folio) @@ -390,5 +391,6 @@ static const char *vboxsf_get_link(struct dentry *dentry, struct inode *inode, } const struct inode_operations vboxsf_lnk_iops = { - .get_link = vboxsf_get_link + .get_link = vboxsf_get_link, + .fileattr_get = vboxsf_fileattr_get, }; diff --git a/fs/vboxsf/super.c b/fs/vboxsf/super.c index 241647b060ee..fcabeca2a339 100644 --- a/fs/vboxsf/super.c +++ b/fs/vboxsf/super.c @@ -185,6 +185,10 @@ static int vboxsf_fill_super(struct super_block *sb, struct fs_context *fc) if (err) goto fail_unmap; + err = vboxsf_query_case_sensitive(sbi); + if (err) + goto fail_unmap; + sb->s_magic = VBOXSF_SUPER_MAGIC; sb->s_blocksize = 1024; sb->s_maxbytes = MAX_LFS_FILESIZE; diff --git a/fs/vboxsf/utils.c b/fs/vboxsf/utils.c index 9515bbf0b54c..6205fad21381 100644 --- a/fs/vboxsf/utils.c +++ b/fs/vboxsf/utils.c @@ -11,6 +11,7 @@ #include #include #include +#include #include "vfsmod.h" struct inode *vboxsf_new_inode(struct super_block *sb) @@ -567,3 +568,32 @@ int vboxsf_dir_read_all(struct vboxsf_sbi *sbi, struct vboxsf_dir_info *sf_d, return err; } + +int vboxsf_query_case_sensitive(struct vboxsf_sbi *sbi) +{ + struct shfl_volinfo volinfo; + u32 buf_len; + int err; + + buf_len = sizeof(volinfo); + err = vboxsf_fsinfo(sbi->root, 0, SHFL_INFO_GET | SHFL_INFO_VOLUME, + &buf_len, &volinfo); + if (err) + return err; + + sbi->case_insensitive = !volinfo.properties.case_sensitive; + return 0; +} + +int vboxsf_fileattr_get(struct dentry *dentry, struct file_kattr *fa) +{ + struct vboxsf_sbi *sbi = VBOXSF_SBI(dentry->d_sb); + + /* + * VirtualBox shared folders preserve filename case exactly as + * provided by the guest (the default). The host interface does + * not expose a case-preservation property. + */ + fa->case_insensitive = sbi->case_insensitive; + return 0; +} diff --git a/fs/vboxsf/vfsmod.h b/fs/vboxsf/vfsmod.h index 05973eb89d52..b61afd0ce842 100644 --- a/fs/vboxsf/vfsmod.h +++ b/fs/vboxsf/vfsmod.h @@ -47,6 +47,7 @@ struct vboxsf_sbi { u32 next_generation; u32 root; int bdi_id; + bool case_insensitive; }; /* per-inode information */ @@ -111,6 +112,11 @@ void vboxsf_dir_info_free(struct vboxsf_dir_info *p); int vboxsf_dir_read_all(struct vboxsf_sbi *sbi, struct vboxsf_dir_info *sf_d, u64 handle); +int vboxsf_query_case_sensitive(struct vboxsf_sbi *sbi); + +struct file_kattr; +int vboxsf_fileattr_get(struct dentry *dentry, struct file_kattr *fa); + /* from vboxsf_wrappers.c */ int vboxsf_connect(void); void vboxsf_disconnect(void); -- 2.52.0 From: Chuck Lever Upper layers such as NFSD need a way to query whether a filesystem handles filenames in a case-sensitive manner so they can provide correct semantics to remote clients. Without this information, NFS exports of ISO 9660 filesystems cannot properly advertise their filename case behavior. Implement isofs_fileattr_get() to report ISO 9660 case handling behavior. The 'check=r' (relaxed) mount option enables case-insensitive lookups, and this setting determines the value reported through the file_kattr structure. By default, Joliet extensions operate in relaxed mode while plain ISO 9660 uses strict (case-sensitive) mode. All ISO 9660 variants are case-preserving, meaning filenames are stored exactly as they appear on the disc. The callback is registered only on isofs_dir_inode_operations because isofs has no custom inode_operations for regular files, and symlinks use the generic page_symlink_inode_operations. Reviewed-by: Jan Kara Signed-off-by: Chuck Lever --- fs/isofs/dir.c | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/fs/isofs/dir.c b/fs/isofs/dir.c index 09df40b612fb..7f95ddeb5991 100644 --- a/fs/isofs/dir.c +++ b/fs/isofs/dir.c @@ -12,6 +12,7 @@ * isofs directory handling functions */ #include +#include #include "isofs.h" int isofs_name_translate(struct iso_directory_record *de, char *new, struct inode *inode) @@ -266,6 +267,15 @@ static int isofs_readdir(struct file *file, struct dir_context *ctx) return result; } +static int isofs_fileattr_get(struct dentry *dentry, struct file_kattr *fa) +{ + struct isofs_sb_info *sbi = ISOFS_SB(dentry->d_sb); + + /* ISO 9660 preserves case (the default). */ + fa->case_insensitive = sbi->s_check == 'r'; + return 0; +} + const struct file_operations isofs_dir_operations = { .llseek = generic_file_llseek, @@ -279,6 +289,7 @@ const struct file_operations isofs_dir_operations = const struct inode_operations isofs_dir_inode_operations = { .lookup = isofs_lookup, + .fileattr_get = isofs_fileattr_get, }; -- 2.52.0 From: Chuck Lever The hard-coded MSDOS_SUPER_MAGIC check in nfsd3_proc_pathconf() only recognizes FAT filesystems as case-insensitive. Modern filesystems like F2FS, exFAT, and CIFS support case-insensitive directories, but NFSv3 clients cannot discover this capability. Query the export's actual case behavior through ->fileattr_get instead. This allows NFSv3 clients to correctly handle case sensitivity for any filesystem that implements the fileattr interface. Filesystems without ->fileattr_get continue to report the default POSIX behavior (case-sensitive, case-preserving). This change assumes the ("fat: Implement fileattr_get for case sensitivity") has been applied, which ensures FAT filesystems report their case behavior correctly via the fileattr interface. Signed-off-by: Chuck Lever --- fs/nfsd/nfs3proc.c | 18 ++++++++++-------- fs/nfsd/vfs.c | 25 +++++++++++++++++++++++++ fs/nfsd/vfs.h | 2 ++ 3 files changed, 37 insertions(+), 8 deletions(-) diff --git a/fs/nfsd/nfs3proc.c b/fs/nfsd/nfs3proc.c index 42adc5461db0..9be0aca01de0 100644 --- a/fs/nfsd/nfs3proc.c +++ b/fs/nfsd/nfs3proc.c @@ -717,17 +717,19 @@ nfsd3_proc_pathconf(struct svc_rqst *rqstp) if (resp->status == nfs_ok) { struct super_block *sb = argp->fh.fh_dentry->d_sb; + bool case_insensitive, case_preserving; - /* Note that we don't care for remote fs's here */ - switch (sb->s_magic) { - case EXT2_SUPER_MAGIC: + if (sb->s_magic == EXT2_SUPER_MAGIC) { resp->p_link_max = EXT2_LINK_MAX; resp->p_name_max = EXT2_NAME_LEN; - break; - case MSDOS_SUPER_MAGIC: - resp->p_case_insensitive = 1; - resp->p_case_preserving = 0; - break; + } + + resp->status = nfsd_get_case_info(&argp->fh, + &case_insensitive, + &case_preserving); + if (resp->status == nfs_ok) { + resp->p_case_insensitive = case_insensitive; + resp->p_case_preserving = case_preserving; } } diff --git a/fs/nfsd/vfs.c b/fs/nfsd/vfs.c index 168d3ccc8155..f077032b42c3 100644 --- a/fs/nfsd/vfs.c +++ b/fs/nfsd/vfs.c @@ -32,6 +32,7 @@ #include #include #include +#include #include "xdr3.h" @@ -2871,3 +2872,27 @@ nfsd_permission(struct svc_cred *cred, struct svc_export *exp, return err? nfserrno(err) : 0; } + +/** + * nfsd_get_case_info - get case sensitivity info for a file handle + * @fhp: file handle that has already been verified + * @case_insensitive: output, true if the filesystem is case-insensitive + * @case_preserving: output, true if the filesystem preserves case + * + * Returns nfs_ok on success, or an nfserr on failure. + */ +__be32 +nfsd_get_case_info(struct svc_fh *fhp, bool *case_insensitive, + bool *case_preserving) +{ + struct file_kattr fa = {}; + int err; + + err = vfs_fileattr_get(fhp->fh_dentry, &fa); + if (err && err != -ENOIOCTLCMD) + return nfserrno(err); + + *case_insensitive = fa.case_insensitive; + *case_preserving = !fa.case_nonpreserving; + return nfs_ok; +} diff --git a/fs/nfsd/vfs.h b/fs/nfsd/vfs.h index e192dca4a679..1ff62eecec09 100644 --- a/fs/nfsd/vfs.h +++ b/fs/nfsd/vfs.h @@ -155,6 +155,8 @@ __be32 nfsd_readdir(struct svc_rqst *, struct svc_fh *, loff_t *, struct readdir_cd *, nfsd_filldir_t); __be32 nfsd_statfs(struct svc_rqst *, struct svc_fh *, struct kstatfs *, int access); +__be32 nfsd_get_case_info(struct svc_fh *fhp, bool *case_insensitive, + bool *case_preserving); __be32 nfsd_permission(struct svc_cred *cred, struct svc_export *exp, struct dentry *dentry, int acc); -- 2.52.0 From: Chuck Lever 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. Signed-off-by: Chuck Lever --- fs/nfsd/nfs4xdr.c | 30 ++++++++++++++++++++++++++---- 1 file changed, 26 insertions(+), 4 deletions(-) diff --git a/fs/nfsd/nfs4xdr.c b/fs/nfsd/nfs4xdr.c index 51ef97c25456..167bede81273 100644 --- a/fs/nfsd/nfs4xdr.c +++ b/fs/nfsd/nfs4xdr.c @@ -2933,6 +2933,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, @@ -3131,6 +3133,18 @@ 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_filehandle(struct xdr_stream *xdr, const struct nfsd4_fattr_args *args) { @@ -3487,8 +3501,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, @@ -3674,8 +3688,9 @@ nfsd4_encode_fattr4(struct svc_rqst *rqstp, struct xdr_stream *xdr, if (err) goto out_nfserr; } - if ((attrmask[0] & (FATTR4_WORD0_FILEHANDLE | FATTR4_WORD0_FSID)) && - !fhp) { + if ((attrmask[0] & (FATTR4_WORD0_FILEHANDLE | FATTR4_WORD0_FSID | + FATTR4_WORD0_CASE_INSENSITIVE | + FATTR4_WORD0_CASE_PRESERVING)) && !fhp) { tempfh = kmalloc(sizeof(struct svc_fh), GFP_KERNEL); status = nfserr_jukebox; if (!tempfh) @@ -3687,6 +3702,13 @@ 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)) { + status = nfsd_get_case_info(args.fhp, &args.case_insensitive, + &args.case_preserving); + if (status != nfs_ok) + goto out; + } if (attrmask[0] & FATTR4_WORD0_ACL) { err = nfsd4_get_nfs4_acl(rqstp, dentry, &args.acl); -- 2.52.0 From: Chuck Lever ksmbd hard-codes FILE_CASE_SENSITIVE_SEARCH and FILE_CASE_PRESERVED_NAMES in FS_ATTRIBUTE_INFORMATION responses, incorrectly indicating all exports are case-sensitive. This breaks clients accessing case-insensitive filesystems like exFAT or ext4/f2fs directories with casefold enabled. Query actual case behavior via vfs_fileattr_get() and report accurate attributes to SMB clients. Filesystems without ->fileattr_get continue reporting default POSIX behavior (case-sensitive, case-preserving). SMB's FS_ATTRIBUTE_INFORMATION reports per-share attributes from the share root, not per-file. Shares mixing casefold and non-casefold directories report the root directory's behavior. Acked-by: Namjae Jeon Signed-off-by: Chuck Lever --- fs/smb/server/smb2pdu.c | 25 +++++++++++++++++++------ 1 file changed, 19 insertions(+), 6 deletions(-) diff --git a/fs/smb/server/smb2pdu.c b/fs/smb/server/smb2pdu.c index 2fcd0d4d1fb0..f579093bb418 100644 --- a/fs/smb/server/smb2pdu.c +++ b/fs/smb/server/smb2pdu.c @@ -13,6 +13,7 @@ #include #include #include +#include #include "glob.h" #include "smbfsctl.h" @@ -5486,16 +5487,28 @@ static int smb2_get_info_filesystem(struct ksmbd_work *work, case FS_ATTRIBUTE_INFORMATION: { FILE_SYSTEM_ATTRIBUTE_INFO *info; + struct file_kattr fa = {}; size_t sz; + u32 attrs; + int err; info = (FILE_SYSTEM_ATTRIBUTE_INFO *)rsp->Buffer; - info->Attributes = cpu_to_le32(FILE_SUPPORTS_OBJECT_IDS | - FILE_PERSISTENT_ACLS | - FILE_UNICODE_ON_DISK | - FILE_CASE_PRESERVED_NAMES | - FILE_CASE_SENSITIVE_SEARCH | - FILE_SUPPORTS_BLOCK_REFCOUNTING); + attrs = FILE_SUPPORTS_OBJECT_IDS | + FILE_PERSISTENT_ACLS | + FILE_UNICODE_ON_DISK | + FILE_SUPPORTS_BLOCK_REFCOUNTING; + err = vfs_fileattr_get(path.dentry, &fa); + if (err && err != -ENOIOCTLCMD) { + path_put(&path); + return err; + } + if (!fa.case_insensitive) + attrs |= FILE_CASE_SENSITIVE_SEARCH; + if (!fa.case_nonpreserving) + attrs |= FILE_CASE_PRESERVED_NAMES; + + info->Attributes = cpu_to_le32(attrs); info->Attributes |= cpu_to_le32(server_conf.share_fake_fscaps); if (test_share_config_flag(work->tcon->share_conf, -- 2.52.0