For shmem, casefolding is a mount option. Report the current encoding and casefolding flags in /proc/mounts, so userspace can easily check if casefolding is supported withour relying on parsing dmesg or trying to set +F. Reported-by: Daniel Tang Link: https://lore.kernel.org/linux-fsdevel/5193732.31r3eYUQgx@daniel-desktop3/#R Assisted-by: LLM Signed-off-by: Gabriel Krisman Bertazi --- mm/shmem.c | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/mm/shmem.c b/mm/shmem.c index 897fa2b61346..2b93fcbf5127 100644 --- a/mm/shmem.c +++ b/mm/shmem.c @@ -4916,6 +4916,17 @@ static int shmem_show_options(struct seq_file *seq, struct dentry *root) if (sbinfo->qlimits.grpquota_ihardlimit) seq_printf(seq, ",grpquota_inode_hardlimit=%lld", sbinfo->qlimits.grpquota_ihardlimit); +#endif +#if IS_ENABLED(CONFIG_UNICODE) + if (root->d_sb->s_encoding) { + unsigned int version = root->d_sb->s_encoding->version; + + seq_printf(seq, ",casefold=utf8-%u.%u.%u", + unicode_major(version), unicode_minor(version), + unicode_rev(version)); + if (sb_has_strict_encoding(root->d_sb)) + seq_puts(seq, ",strict_encoding"); + } #endif return 0; } -- 2.55.0 Daniel Tang pointed out that shmem doesn't list casefold as a mount option, even when it is mounted casefolded. While fixing it, I noticed f2fs actually already provides that information via sysfs, but ext4 doesn't. This patch reproduces the same interface in ext4 to have feature parity, so users don't need to rely on parsing dmesg or trying +F on a temporary directory just to check for support. Assisted-by: LLM Signed-off-by: Gabriel Krisman Bertazi --- Documentation/ABI/testing/sysfs-fs-ext4 | 21 ++++++++++++++++++ fs/ext4/sysfs.c | 29 +++++++++++++++++++++++++ 2 files changed, 50 insertions(+) diff --git a/Documentation/ABI/testing/sysfs-fs-ext4 b/Documentation/ABI/testing/sysfs-fs-ext4 index 2edd0a6672d3..8abc5706f459 100644 --- a/Documentation/ABI/testing/sysfs-fs-ext4 +++ b/Documentation/ABI/testing/sysfs-fs-ext4 @@ -116,3 +116,24 @@ Contact: "Theodore Ts'o" Description: This file is read-only and shows the pid of journal thread in current pid-namespace or 0 if task is unreachable. + +What: /sys/fs/ext4//encoding +Date: September 2026 +Contact: "Gabriel Krisman Bertazi" +Description: + Displays name and version of the encoding set for the + filesystem. If no encoding is set, displays (none) + +What: /sys/fs/ext4//encoding_flags +Date: September 2026 +Contact: "Gabriel Krisman Bertazi" +Description: + This is a read-only entry to show the value of + sb.s_encoding_flags, the value is hexadecimal. + + ============================ ========== + Flag_Name Flag_Value + ============================ ========== + SB_ENC_STRICT_MODE_FL 0x00000001 + SB_ENC_NO_COMPAT_FALLBACK_FL 0x00000002 + ============================ ========== diff --git a/fs/ext4/sysfs.c b/fs/ext4/sysfs.c index 923b375e017f..bc1c59d67eed 100644 --- a/fs/ext4/sysfs.c +++ b/fs/ext4/sysfs.c @@ -32,6 +32,8 @@ typedef enum { attr_clusters_in_group, attr_mb_order, attr_feature, + attr_encoding, + attr_encoding_flags, attr_pointer_pi, attr_pointer_ui, attr_pointer_ul, @@ -161,6 +163,25 @@ static ssize_t err_report_sec_store(struct ext4_sb_info *sbi, return count; } +static ssize_t encoding_show(struct ext4_sb_info *sbi, char *buf) +{ +#if IS_ENABLED(CONFIG_UNICODE) + struct super_block *sb = sbi->s_sb; + + if (ext4_has_feature_casefold(sb)) + return sysfs_emit(buf, "UTF-8 (%d.%d.%d)\n", + unicode_major(sb->s_encoding->version), + unicode_minor(sb->s_encoding->version), + unicode_rev(sb->s_encoding->version)); +#endif + return sysfs_emit(buf, "(none)\n"); +} + +static ssize_t encoding_flags_show(struct ext4_sb_info *sbi, char *buf) +{ + return sysfs_emit(buf, "%x\n", le16_to_cpu(sbi->s_es->s_encoding_flags)); +} + static ssize_t journal_task_show(struct ext4_sb_info *sbi, char *buf) { if (!sbi->s_journal) @@ -283,6 +304,8 @@ EXT4_RO_ATTR_ES_STRING(last_error_func, s_last_error_func, 32); EXT4_ATTR(first_error_time, 0444, first_error_time); EXT4_ATTR(last_error_time, 0444, last_error_time); EXT4_ATTR(journal_task, 0444, journal_task); +EXT4_ATTR_FUNC(encoding, 0444); +EXT4_ATTR_FUNC(encoding_flags, 0444); EXT4_RW_ATTR_SBI_UI(mb_prefetch, s_mb_prefetch); EXT4_RW_ATTR_SBI_UI(mb_prefetch_limit, s_mb_prefetch_limit); EXT4_RW_ATTR_SBI_UL(last_trim_minblks, s_last_trim_minblks); @@ -333,6 +356,8 @@ static struct attribute *ext4_attrs[] = { ATTR_LIST(first_error_time), ATTR_LIST(last_error_time), ATTR_LIST(journal_task), + ATTR_LIST(encoding), + ATTR_LIST(encoding_flags), #ifdef CONFIG_EXT4_DEBUG ATTR_LIST(simulate_fail), #endif @@ -483,6 +508,10 @@ static ssize_t ext4_attr_show(struct kobject *kobj, return print_tstamp(buf, sbi->s_es, s_last_error_time); case attr_journal_task: return journal_task_show(sbi, buf); + case attr_encoding: + return encoding_show(sbi, buf); + case attr_encoding_flags: + return encoding_flags_show(sbi, buf); default: return ext4_generic_attr_show(a, sbi, buf); } -- 2.55.0 In preparation to expose encoding settings in sysfs, add a subtree for overlayfs under /sys/fs. The only remarkable detail is how we reference the volume. Use major:minor, which is not very human-friendly but it is at least discoverable. I considered uuid, but that would be a problem if mounted with uuid=off. Assisted-by: LLM Signed-off-by: Gabriel Krisman Bertazi --- Documentation/ABI/testing/sysfs-fs-overlay | 6 ++ MAINTAINERS | 1 + fs/overlayfs/Makefile | 2 +- fs/overlayfs/overlayfs.h | 5 + fs/overlayfs/ovl_entry.h | 7 ++ fs/overlayfs/super.c | 26 +++++- fs/overlayfs/sysfs.c | 101 +++++++++++++++++++++ 7 files changed, 144 insertions(+), 4 deletions(-) create mode 100644 Documentation/ABI/testing/sysfs-fs-overlay create mode 100644 fs/overlayfs/sysfs.c diff --git a/Documentation/ABI/testing/sysfs-fs-overlay b/Documentation/ABI/testing/sysfs-fs-overlay new file mode 100644 index 000000000000..9060c39679ae --- /dev/null +++ b/Documentation/ABI/testing/sysfs-fs-overlay @@ -0,0 +1,6 @@ +What: /sys/fs/overlay// +Date: September 2026 +Contact: linux-unionfs@vger.kernel.org +Description: + A directory is created for every mounted overlayfs instance. + is set to the device major:minor. diff --git a/MAINTAINERS b/MAINTAINERS index c2414447892c..e49f84c4c691 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -20586,6 +20586,7 @@ M: Amir Goldstein L: linux-unionfs@vger.kernel.org S: Supported T: git git://git.kernel.org/pub/scm/linux/kernel/git/overlayfs/vfs.git +F: Documentation/ABI/testing/sysfs-fs-overlay F: Documentation/filesystems/overlayfs.rst F: fs/overlayfs/ diff --git a/fs/overlayfs/Makefile b/fs/overlayfs/Makefile index 5648954f8588..fd4e747820c2 100644 --- a/fs/overlayfs/Makefile +++ b/fs/overlayfs/Makefile @@ -6,4 +6,4 @@ obj-$(CONFIG_OVERLAY_FS) += overlay.o overlay-objs := super.o namei.o util.o inode.o file.o dir.o readdir.o \ - copy_up.o export.o params.o xattrs.o + copy_up.o export.o params.o xattrs.o sysfs.o diff --git a/fs/overlayfs/overlayfs.h b/fs/overlayfs/overlayfs.h index e0d8c6152e9f..91d3894d9bd2 100644 --- a/fs/overlayfs/overlayfs.h +++ b/fs/overlayfs/overlayfs.h @@ -939,6 +939,11 @@ extern const struct export_operations ovl_export_fid_operations; /* super.c */ int ovl_fill_super(struct super_block *sb, struct fs_context *fc); +int __init ovl_sysfs_init(void); +void ovl_sysfs_exit(void); +int ovl_sysfs_register(struct ovl_fs *ofs); +void ovl_sysfs_unregister(struct ovl_fs *ofs); + /* Will this overlay be forced to mount/remount ro? */ static inline bool ovl_force_readonly(struct ovl_fs *ofs) { diff --git a/fs/overlayfs/ovl_entry.h b/fs/overlayfs/ovl_entry.h index 80cad4ea96a3..c14253e75826 100644 --- a/fs/overlayfs/ovl_entry.h +++ b/fs/overlayfs/ovl_entry.h @@ -5,6 +5,9 @@ * Copyright (C) 2016 Red Hat, Inc. */ +#include +#include + struct ovl_config { char *upperdir; char *workdir; @@ -56,6 +59,7 @@ struct ovl_entry { /* private information held for overlayfs's superblock */ struct ovl_fs { + struct super_block *sb; unsigned int numlayer; /* Number of unique fs among layers including upper fs */ unsigned int numfs; @@ -92,6 +96,9 @@ struct ovl_fs { /* r/o snapshot of upperdir sb's only taken on volatile mounts */ errseq_t errseq; bool casefold; + struct kobject kobj; + struct completion kobj_unregister; + bool kobj_registered; }; /* Number of lower layers, not including data-only layers */ diff --git a/fs/overlayfs/super.c b/fs/overlayfs/super.c index bd0a3f9039d2..d195dace135d 100644 --- a/fs/overlayfs/super.c +++ b/fs/overlayfs/super.c @@ -226,8 +226,10 @@ static void ovl_put_super(struct super_block *sb) { struct ovl_fs *ofs = OVL_FS(sb); - if (ofs) + if (ofs) { + ovl_sysfs_unregister(ofs); ovl_free_fs(ofs); + } } /* Sync real dirty inodes in upper filesystem (if it exists) */ @@ -1548,6 +1550,7 @@ int ovl_fill_super(struct super_block *sb, struct fs_context *fc) if (fc->user_ns != current_user_ns()) goto out_err; + ofs->sb = sb; ovl_set_d_op(sb); if (!ofs->creator_cred) { @@ -1559,6 +1562,13 @@ int ovl_fill_super(struct super_block *sb, struct fs_context *fc) with_ovl_creds(sb) err = ovl_fill_super_creds(fc, sb); + if (err) + goto out_err; + /* + * ->s_root is now set (by ovl_fill_super_creds), so if + * ovl_sysfs_register fails it will be cleaned by ovl_put_super. + */ + return ovl_sysfs_register(ofs); out_err: if (err) { @@ -1598,10 +1608,19 @@ static int __init ovl_init(void) if (ovl_inode_cachep == NULL) return -ENOMEM; + err = ovl_sysfs_init(); + if (err) + goto out_destroy_cache; + err = register_filesystem(&ovl_fs_type); - if (!err) - return 0; + if (err) + goto out_sysfs_exit; + + return 0; +out_sysfs_exit: + ovl_sysfs_exit(); +out_destroy_cache: kmem_cache_destroy(ovl_inode_cachep); return err; @@ -1610,6 +1629,7 @@ static int __init ovl_init(void) static void __exit ovl_exit(void) { unregister_filesystem(&ovl_fs_type); + ovl_sysfs_exit(); /* * Make sure all delayed rcu free inodes are flushed before we diff --git a/fs/overlayfs/sysfs.c b/fs/overlayfs/sysfs.c new file mode 100644 index 000000000000..be83b57f637c --- /dev/null +++ b/fs/overlayfs/sysfs.c @@ -0,0 +1,101 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Sysfs interface for overlayfs. + */ +#include +#include +#include +#include +#include +#include "overlayfs.h" + +static struct kset *ovl_kset; + +struct ovl_sysfs_attr { + struct attribute attr; + ssize_t (*show)(struct ovl_fs *ofs, char *buf); +}; + +#define OVL_SYSFS_ATTR_RO(_name) \ + static struct ovl_sysfs_attr ovl_sysfs_attr_##_name = { \ + .attr = { .name = __stringify(_name), .mode = 0444 }, \ + .show = ovl_##_name##_show, \ + } + +static struct attribute *ovl_sysfs_attrs[] = { + NULL, +}; +ATTRIBUTE_GROUPS(ovl_sysfs); + +static ssize_t ovl_sysfs_show(struct kobject *kobj, struct attribute *attr, + char *buf) +{ + struct ovl_fs *ofs = container_of(kobj, struct ovl_fs, kobj); + struct ovl_sysfs_attr *a = container_of(attr, struct ovl_sysfs_attr, + attr); + + return a->show(ofs, buf); +} + +static const struct sysfs_ops ovl_sysfs_ops = { + .show = ovl_sysfs_show, +}; + +static void ovl_sysfs_release(struct kobject *kobj) +{ + struct ovl_fs *ofs = container_of(kobj, struct ovl_fs, kobj); + + complete(&ofs->kobj_unregister); +} + +static const struct kobj_type ovl_sysfs_ktype = { + .default_groups = ovl_sysfs_groups, + .sysfs_ops = &ovl_sysfs_ops, + .release = ovl_sysfs_release, +}; + +int ovl_sysfs_register(struct ovl_fs *ofs) +{ + struct super_block *sb = ofs->sb; + int err; + + init_completion(&ofs->kobj_unregister); + ofs->kobj.kset = ovl_kset; + err = kobject_init_and_add(&ofs->kobj, &ovl_sysfs_ktype, NULL, "%u:%u", + MAJOR(sb->s_dev), MINOR(sb->s_dev)); + if (err) { + kobject_put(&ofs->kobj); + wait_for_completion(&ofs->kobj_unregister); + pr_warn("failed to create sysfs volume %u:%u (err=%d).\n", + MAJOR(sb->s_dev), MINOR(sb->s_dev), err); + return err; + } + + ofs->kobj_registered = true; + kobject_uevent(&ofs->kobj, KOBJ_ADD); + return 0; +} + +void ovl_sysfs_unregister(struct ovl_fs *ofs) +{ + if (!ofs->kobj_registered) + return; + + ofs->kobj_registered = false; + kobject_del(&ofs->kobj); + kobject_put(&ofs->kobj); + wait_for_completion(&ofs->kobj_unregister); +} + +int __init ovl_sysfs_init(void) +{ + ovl_kset = kset_create_and_add("overlay", NULL, fs_kobj); + if (!ovl_kset) + return -ENOMEM; + return 0; +} + +void ovl_sysfs_exit(void) +{ + kset_unregister(ovl_kset); +} -- 2.55.0 This patch reproduces the same interface from ext4/f2fs to have feature parity, so users don't need to rely on parsing dmesg or trying +F on a temporary directory just to check for support. Note there is no encoding_flags file, as none are supported by ovl. Assisted-by: LLM Signed-off-by: Gabriel Krisman Bertazi --- Documentation/ABI/testing/sysfs-fs-overlay | 7 +++++++ fs/overlayfs/sysfs.c | 15 +++++++++++++++ 2 files changed, 22 insertions(+) diff --git a/Documentation/ABI/testing/sysfs-fs-overlay b/Documentation/ABI/testing/sysfs-fs-overlay index 9060c39679ae..7f3c6403c4f8 100644 --- a/Documentation/ABI/testing/sysfs-fs-overlay +++ b/Documentation/ABI/testing/sysfs-fs-overlay @@ -4,3 +4,10 @@ Contact: linux-unionfs@vger.kernel.org Description: A directory is created for every mounted overlayfs instance. is set to the device major:minor. + +What: /sys/fs/overlay//encoding +Date: September 2026 +Contact: linux-unionfs@vger.kernel.org +Description: + Displays name and version of the encoding set for the + overlay. If no encoding is set, displays (none) diff --git a/fs/overlayfs/sysfs.c b/fs/overlayfs/sysfs.c index be83b57f637c..2144cbb418bc 100644 --- a/fs/overlayfs/sysfs.c +++ b/fs/overlayfs/sysfs.c @@ -22,7 +22,22 @@ struct ovl_sysfs_attr { .show = ovl_##_name##_show, \ } +static ssize_t ovl_encoding_show(struct ovl_fs *ofs, char *buf) +{ + struct unicode_map *um = sb_encoding(ofs->sb); + + if (!um) + return sysfs_emit(buf, "(none)\n"); + + return sysfs_emit(buf, "UTF-8 (%d.%d.%d)\n", + unicode_major(um->version), + unicode_minor(um->version), + unicode_rev(um->version)); +} +OVL_SYSFS_ATTR_RO(encoding); + static struct attribute *ovl_sysfs_attrs[] = { + &ovl_sysfs_attr_encoding.attr, NULL, }; ATTRIBUTE_GROUPS(ovl_sysfs); -- 2.55.0