* [PATCH RFC v3 0/7] ovl: Enable support for casefold filesystems
@ 2025-08-08 20:58 André Almeida
2025-08-08 20:58 ` [PATCH RFC v3 1/7] ovl: Store casefold name for case-insentive dentries André Almeida
` (7 more replies)
0 siblings, 8 replies; 18+ messages in thread
From: André Almeida @ 2025-08-08 20:58 UTC (permalink / raw)
To: Miklos Szeredi, Amir Goldstein, Theodore Tso,
Gabriel Krisman Bertazi
Cc: linux-unionfs, linux-kernel, linux-fsdevel, Alexander Viro,
Christian Brauner, Jan Kara, kernel-dev, André Almeida
Hi all,
We would like to support the usage of casefold filesystems with
overlayfs to be used with container tools. This use case requires a
simple setup, where every layer will have the same encoding setting
(i.e. Unicode version and flags), using one upper and one lower layer.
* Implementation
When merge layers, ovl uses a red-black tree to check if a given dentry
name from a lower layers already exists in the upper layer. For merging
case-insensitive names, we need to store then in tree casefolded.
However, when displaying to the user the dentry name, we need to respect
the name chosen when the file was created (e.g. Picture.PNG, instead of
picture.png). To achieve this, I create a new field for cache entries
that stores the casefolded names and a function ovl_strcmp() that uses
this name for searching the rb_tree. For composing the layer, ovl uses
the original name, keeping it consistency with whatever name the user
created.
The rest of the patches are mostly for checking if casefold is being
consistently used across the layers and dropping the mount restrictions
that prevented case-insensitive filesystems to be mounted.
Thanks for the feedback!
---
Changes in v3:
- Rebased on top of vfs-6.18.misc branch
- Added more guards for casefolding things inside of IS_ENABLED(UNICODE)
- Refactor the strncmp() patch to do a single kmalloc() per rb_tree operation
- Instead of casefolding the cache entry name everytime per strncmp(),
casefold it once and reuse it for every strncmp().
- Created ovl_dentry_ci_operations to not override dentry ops set by
ovl_dentry_operations
- Instead of setting encoding just when there's a upper layer, set it
for any first layer (ofs->fs[0].sb), regardless of it being upper or
not.
- Rewrote the patch that set inode flags
- Check if every dentry is consistent with the root dentry regarding
casefold
v2: https://lore.kernel.org/r/20250805-tonyk-overlayfs-v2-0-0e54281da318@igalia.com
Changes in v2:
- Almost a full rewritten from the v1.
v1: https://lore.kernel.org/lkml/20250409-tonyk-overlayfs-v1-0-3991616fe9a3@igalia.com/
---
André Almeida (7):
ovl: Store casefold name for case-insentive dentries
ovl: Create ovl_casefold() to support casefolded strncmp()
fs: Create sb_same_encoding() helper
ovl: Ensure that all mount points have the same encoding
ovl: Set case-insensitive dentry operations for ovl sb
ovl: Add S_CASEFOLD as part of the inode flag to be copied
ovl: Support case-insensitive lookup
fs/overlayfs/namei.c | 17 +++---
fs/overlayfs/overlayfs.h | 2 +-
fs/overlayfs/ovl_entry.h | 1 +
fs/overlayfs/params.c | 7 +--
fs/overlayfs/readdir.c | 133 ++++++++++++++++++++++++++++++++++++++++++-----
fs/overlayfs/super.c | 39 ++++++++++++++
fs/overlayfs/util.c | 8 +--
include/linux/fs.h | 19 +++++++
8 files changed, 195 insertions(+), 31 deletions(-)
---
base-commit: 0fdf709a849f773c9b23b0d9fff2a25de056ddd5
change-id: 20250409-tonyk-overlayfs-591f5e4d407a
Best regards,
--
André Almeida <andrealmeid@igalia.com>
^ permalink raw reply [flat|nested] 18+ messages in thread
* [PATCH RFC v3 1/7] ovl: Store casefold name for case-insentive dentries
2025-08-08 20:58 [PATCH RFC v3 0/7] ovl: Enable support for casefold filesystems André Almeida
@ 2025-08-08 20:58 ` André Almeida
2025-08-09 12:36 ` Amir Goldstein
2025-08-08 20:58 ` [PATCH RFC v3 2/7] ovl: Create ovl_casefold() to support casefolded strncmp() André Almeida
` (6 subsequent siblings)
7 siblings, 1 reply; 18+ messages in thread
From: André Almeida @ 2025-08-08 20:58 UTC (permalink / raw)
To: Miklos Szeredi, Amir Goldstein, Theodore Tso,
Gabriel Krisman Bertazi
Cc: linux-unionfs, linux-kernel, linux-fsdevel, Alexander Viro,
Christian Brauner, Jan Kara, kernel-dev, André Almeida
In order to make case-insentive mounting points work, overlayfs needs
the casefolded version of its dentries so the search and insertion in
the struct ovl_readdir_data's red-black compares the dentry names in a
case-insentive fashion.
If a dentry is casefolded, compute and store it's casefolded name and
it's Unicode map. If utf8_casefold() fails, set it's name pointer as
NULL so it can be ignored and fallback to the original name.
Signed-off-by: André Almeida <andrealmeid@igalia.com>
---
Changes from v3:
- Guard all the casefolding inside of IS_ENABLED(UNICODE)
---
fs/overlayfs/readdir.c | 41 ++++++++++++++++++++++++++++++++++++++++-
1 file changed, 40 insertions(+), 1 deletion(-)
diff --git a/fs/overlayfs/readdir.c b/fs/overlayfs/readdir.c
index b65cdfce31ce27172d28d879559f1008b9c87320..2f42fec97f76c2000f76e15c60975db567b2c6d6 100644
--- a/fs/overlayfs/readdir.c
+++ b/fs/overlayfs/readdir.c
@@ -16,6 +16,8 @@
#include <linux/overflow.h>
#include "overlayfs.h"
+#define OVL_NAME_LEN 255
+
struct ovl_cache_entry {
unsigned int len;
unsigned int type;
@@ -27,6 +29,8 @@ struct ovl_cache_entry {
bool is_upper;
bool is_whiteout;
bool check_xwhiteout;
+ char *cf_name;
+ int cf_len;
char name[];
};
@@ -45,6 +49,7 @@ struct ovl_readdir_data {
struct list_head *list;
struct list_head middle;
struct ovl_cache_entry *first_maybe_whiteout;
+ struct unicode_map *map;
int count;
int err;
bool is_upper;
@@ -166,6 +171,31 @@ static struct ovl_cache_entry *ovl_cache_entry_new(struct ovl_readdir_data *rdd,
p->is_whiteout = false;
/* Defer check for overlay.whiteout to ovl_iterate() */
p->check_xwhiteout = rdd->in_xwhiteouts_dir && d_type == DT_REG;
+ p->cf_name = NULL;
+ p->cf_len = 0;
+
+#if IS_ENABLED(CONFIG_UNICODE)
+ if (rdd->map && !is_dot_dotdot(name, len)) {
+ const struct qstr str = { .name = name, .len = len };
+ int ret;
+
+ p->cf_name = kmalloc(OVL_NAME_LEN, GFP_KERNEL);
+
+ if (!p->cf_name) {
+ kfree(p);
+ return NULL;
+ }
+
+ ret = utf8_casefold(rdd->map, &str, p->cf_name, OVL_NAME_LEN);
+
+ if (ret < 0) {
+ kfree(p->cf_name);
+ p->cf_name = NULL;
+ } else {
+ p->cf_len = ret;
+ }
+ }
+#endif
if (d_type == DT_CHR) {
p->next_maybe_whiteout = rdd->first_maybe_whiteout;
@@ -223,8 +253,10 @@ void ovl_cache_free(struct list_head *list)
struct ovl_cache_entry *p;
struct ovl_cache_entry *n;
- list_for_each_entry_safe(p, n, list, l_node)
+ list_for_each_entry_safe(p, n, list, l_node) {
+ kfree(p->cf_name);
kfree(p);
+ }
INIT_LIST_HEAD(list);
}
@@ -357,12 +389,19 @@ static int ovl_dir_read_merged(struct dentry *dentry, struct list_head *list,
.list = list,
.root = root,
.is_lowest = false,
+ .map = NULL,
};
int idx, next;
const struct ovl_layer *layer;
for (idx = 0; idx != -1; idx = next) {
next = ovl_path_next(idx, dentry, &realpath, &layer);
+
+#if IS_ENABLED(CONFIG_UNICODE)
+ if (ovl_dentry_casefolded(realpath.dentry))
+ rdd.map = realpath.dentry->d_sb->s_encoding;
+#endif
+
rdd.is_upper = ovl_dentry_upper(dentry) == realpath.dentry;
rdd.in_xwhiteouts_dir = layer->has_xwhiteouts &&
ovl_dentry_has_xwhiteouts(dentry);
--
2.50.1
^ permalink raw reply related [flat|nested] 18+ messages in thread
* [PATCH RFC v3 2/7] ovl: Create ovl_casefold() to support casefolded strncmp()
2025-08-08 20:58 [PATCH RFC v3 0/7] ovl: Enable support for casefold filesystems André Almeida
2025-08-08 20:58 ` [PATCH RFC v3 1/7] ovl: Store casefold name for case-insentive dentries André Almeida
@ 2025-08-08 20:58 ` André Almeida
2025-08-09 15:13 ` Amir Goldstein
2025-08-08 20:58 ` [PATCH RFC v3 3/7] fs: Create sb_same_encoding() helper André Almeida
` (5 subsequent siblings)
7 siblings, 1 reply; 18+ messages in thread
From: André Almeida @ 2025-08-08 20:58 UTC (permalink / raw)
To: Miklos Szeredi, Amir Goldstein, Theodore Tso,
Gabriel Krisman Bertazi
Cc: linux-unionfs, linux-kernel, linux-fsdevel, Alexander Viro,
Christian Brauner, Jan Kara, kernel-dev, André Almeida
To add overlayfs support casefold filesystems, create a new function
ovl_casefold(), to be able to do case-insensitive strncmp().
ovl_casefold() allocates a new buffer and stores the casefolded version
of the string on it. If the allocation or the casefold operation fails,
fallback to use the original string. The caller of the function is
responsible of freeing the buffer.
The other string to be compared is casefolded in a previous step and
stored at `struct ovl_cache_entry` member `char *cf_name`.
Finally, set the strncmp() parameters to the casefold versions of the
names to achieve case-insensitive support.
For the non-casefold names, nothing changes and the rb_tree
search/insert functions just ignores this change.
Signed-off-by: André Almeida <andrealmeid@igalia.com>
---
Changes from v2:
- Refactor the patch to do a single kmalloc() per rb_tree operation
- Instead of casefolding the cache entry name everytime per strncmp(),
casefold it once and reuse it for every strncmp().
---
fs/overlayfs/readdir.c | 92 +++++++++++++++++++++++++++++++++++++++++++-------
1 file changed, 80 insertions(+), 12 deletions(-)
diff --git a/fs/overlayfs/readdir.c b/fs/overlayfs/readdir.c
index 2f42fec97f76c2000f76e15c60975db567b2c6d6..422f991393dfae12bcacf326414b7ee19e486ac8 100644
--- a/fs/overlayfs/readdir.c
+++ b/fs/overlayfs/readdir.c
@@ -71,20 +71,58 @@ static struct ovl_cache_entry *ovl_cache_entry_from_node(struct rb_node *n)
return rb_entry(n, struct ovl_cache_entry, node);
}
+static int ovl_casefold(struct unicode_map *map, const char *str, int len, char **dst)
+{
+#if IS_ENABLED(CONFIG_UNICODE)
+ const struct qstr qstr = { .name = str, .len = len };
+ int cf_len;
+
+ if (!map || is_dot_dotdot(str, len))
+ return -1;
+
+ *dst = kmalloc(OVL_NAME_LEN, GFP_KERNEL);
+
+ if (dst) {
+ cf_len = utf8_casefold(map, &qstr, *dst, OVL_NAME_LEN);
+
+ if (cf_len > 0)
+ return cf_len;
+ }
+#endif
+
+ return -1;
+}
+
static bool ovl_cache_entry_find_link(const char *name, int len,
struct rb_node ***link,
- struct rb_node **parent)
+ struct rb_node **parent,
+ struct unicode_map *map)
{
+ int ret;
+ char *dst = NULL;
bool found = false;
+ const char *str = name;
struct rb_node **newp = *link;
+ ret = ovl_casefold(map, name, len, &dst);
+
+ if (ret > 0) {
+ str = dst;
+ len = ret;
+ }
+
while (!found && *newp) {
int cmp;
+ char *aux;
struct ovl_cache_entry *tmp;
*parent = *newp;
+
tmp = ovl_cache_entry_from_node(*newp);
- cmp = strncmp(name, tmp->name, len);
+
+ aux = tmp->cf_name ? tmp->cf_name : tmp->name;
+
+ cmp = strncmp(str, aux, len);
if (cmp > 0)
newp = &tmp->node.rb_right;
else if (cmp < 0 || len < tmp->len)
@@ -94,27 +132,50 @@ static bool ovl_cache_entry_find_link(const char *name, int len,
}
*link = newp;
+ kfree(dst);
+
return found;
}
static struct ovl_cache_entry *ovl_cache_entry_find(struct rb_root *root,
- const char *name, int len)
+ const char *name, int len,
+ struct unicode_map *map)
{
struct rb_node *node = root->rb_node;
- int cmp;
+ struct ovl_cache_entry *p;
+ const char *str = name;
+ bool found = false;
+ char *dst = NULL;
+ int cmp, ret;
+
+ ret = ovl_casefold(map, name, len, &dst);
+
+ if (ret > 0) {
+ str = dst;
+ len = ret;
+ }
+
+ while (!found && node) {
+ char *aux;
- while (node) {
- struct ovl_cache_entry *p = ovl_cache_entry_from_node(node);
+ p = ovl_cache_entry_from_node(node);
- cmp = strncmp(name, p->name, len);
+ aux = p->cf_name ? p->cf_name : p->name;
+
+ cmp = strncmp(str, aux, len);
if (cmp > 0)
node = p->node.rb_right;
else if (cmp < 0 || len < p->len)
node = p->node.rb_left;
else
- return p;
+ found = true;
}
+ kfree(dst);
+
+ if (found)
+ return p;
+
return NULL;
}
@@ -212,7 +273,7 @@ static bool ovl_cache_entry_add_rb(struct ovl_readdir_data *rdd,
struct rb_node *parent = NULL;
struct ovl_cache_entry *p;
- if (ovl_cache_entry_find_link(name, len, &newp, &parent))
+ if (ovl_cache_entry_find_link(name, len, &newp, &parent, rdd->map))
return true;
p = ovl_cache_entry_new(rdd, name, len, ino, d_type);
@@ -234,7 +295,7 @@ static bool ovl_fill_lowest(struct ovl_readdir_data *rdd,
{
struct ovl_cache_entry *p;
- p = ovl_cache_entry_find(rdd->root, name, namelen);
+ p = ovl_cache_entry_find(rdd->root, name, namelen, rdd->map);
if (p) {
list_move_tail(&p->l_node, &rdd->middle);
} else {
@@ -640,7 +701,8 @@ static int ovl_dir_read_impure(const struct path *path, struct list_head *list,
struct rb_node *parent = NULL;
if (WARN_ON(ovl_cache_entry_find_link(p->name, p->len,
- &newp, &parent)))
+ &newp, &parent,
+ rdd.map)))
return -EIO;
rb_link_node(&p->node, parent, newp);
@@ -701,6 +763,7 @@ struct ovl_readdir_translate {
struct dir_context *orig_ctx;
struct ovl_dir_cache *cache;
struct dir_context ctx;
+ struct unicode_map *map;
u64 parent_ino;
int fsid;
int xinobits;
@@ -721,7 +784,7 @@ static bool ovl_fill_real(struct dir_context *ctx, const char *name,
} else if (rdt->cache) {
struct ovl_cache_entry *p;
- p = ovl_cache_entry_find(&rdt->cache->root, name, namelen);
+ p = ovl_cache_entry_find(&rdt->cache->root, name, namelen, rdt->map);
if (p)
ino = p->ino;
} else if (rdt->xinobits) {
@@ -763,11 +826,16 @@ static int ovl_iterate_real(struct file *file, struct dir_context *ctx)
.orig_ctx = ctx,
.xinobits = ovl_xino_bits(ofs),
.xinowarn = ovl_xino_warn(ofs),
+ .map = NULL,
};
if (rdt.xinobits && lower_layer)
rdt.fsid = lower_layer->fsid;
+#if IS_ENABLED(CONFIG_UNICODE)
+ rdt.map = dir->d_sb->s_encoding;
+#endif
+
if (OVL_TYPE_MERGE(ovl_path_type(dir->d_parent))) {
struct kstat stat;
struct path statpath = file->f_path;
--
2.50.1
^ permalink raw reply related [flat|nested] 18+ messages in thread
* [PATCH RFC v3 3/7] fs: Create sb_same_encoding() helper
2025-08-08 20:58 [PATCH RFC v3 0/7] ovl: Enable support for casefold filesystems André Almeida
2025-08-08 20:58 ` [PATCH RFC v3 1/7] ovl: Store casefold name for case-insentive dentries André Almeida
2025-08-08 20:58 ` [PATCH RFC v3 2/7] ovl: Create ovl_casefold() to support casefolded strncmp() André Almeida
@ 2025-08-08 20:58 ` André Almeida
2025-08-09 10:01 ` Amir Goldstein
2025-08-08 20:58 ` [PATCH RFC v3 4/7] ovl: Ensure that all mount points have the same encoding André Almeida
` (4 subsequent siblings)
7 siblings, 1 reply; 18+ messages in thread
From: André Almeida @ 2025-08-08 20:58 UTC (permalink / raw)
To: Miklos Szeredi, Amir Goldstein, Theodore Tso,
Gabriel Krisman Bertazi
Cc: linux-unionfs, linux-kernel, linux-fsdevel, Alexander Viro,
Christian Brauner, Jan Kara, kernel-dev, André Almeida
For cases where a file lookup can go to different mount points (like in
overlayfs), both super blocks must have the same encoding and the same
flags. To help with that, create a sb_same_encoding() function.
Signed-off-by: André Almeida <andrealmeid@igalia.com>
---
Changes from v2:
- Simplify the code. Instead of `if (cond) return true`, just do `return
cond`;
---
include/linux/fs.h | 19 +++++++++++++++++++
1 file changed, 19 insertions(+)
diff --git a/include/linux/fs.h b/include/linux/fs.h
index db49a17376d124785b87dd7f35672fc6e5434f47..d1fe69f233c046a960a60072d5ac3f6286d32c17 100644
--- a/include/linux/fs.h
+++ b/include/linux/fs.h
@@ -3746,6 +3746,25 @@ static inline bool sb_has_encoding(const struct super_block *sb)
#endif
}
+/*
+ * Compare if two super blocks have the same encoding and flags
+ */
+static inline bool sb_same_encoding(const struct super_block *sb1,
+ const struct super_block *sb2)
+{
+#if IS_ENABLED(CONFIG_UNICODE)
+ if (sb1->s_encoding == sb2->s_encoding)
+ return true;
+
+ return (sb1->s_encoding && sb2->s_encoding &&
+ (sb1->s_encoding->version == sb2->s_encoding->version) &&
+ (sb1->s_encoding_flags == sb2->s_encoding_flags));
+#else
+ return true;
+#endif
+}
+
+
int may_setattr(struct mnt_idmap *idmap, struct inode *inode,
unsigned int ia_valid);
int setattr_prepare(struct mnt_idmap *, struct dentry *, struct iattr *);
--
2.50.1
^ permalink raw reply related [flat|nested] 18+ messages in thread
* [PATCH RFC v3 4/7] ovl: Ensure that all mount points have the same encoding
2025-08-08 20:58 [PATCH RFC v3 0/7] ovl: Enable support for casefold filesystems André Almeida
` (2 preceding siblings ...)
2025-08-08 20:58 ` [PATCH RFC v3 3/7] fs: Create sb_same_encoding() helper André Almeida
@ 2025-08-08 20:58 ` André Almeida
2025-08-09 10:07 ` Amir Goldstein
2025-08-08 20:58 ` [PATCH RFC v3 5/7] ovl: Set case-insensitive dentry operations for ovl sb André Almeida
` (3 subsequent siblings)
7 siblings, 1 reply; 18+ messages in thread
From: André Almeida @ 2025-08-08 20:58 UTC (permalink / raw)
To: Miklos Szeredi, Amir Goldstein, Theodore Tso,
Gabriel Krisman Bertazi
Cc: linux-unionfs, linux-kernel, linux-fsdevel, Alexander Viro,
Christian Brauner, Jan Kara, kernel-dev, André Almeida
When mounting different mount points with casefold support, they should
use the same encoding version and have the same flags to avoid any kind
of incompatibility issues.
Signed-off-by: André Almeida <andrealmeid@igalia.com>
---
fs/overlayfs/super.c | 11 +++++++++++
1 file changed, 11 insertions(+)
diff --git a/fs/overlayfs/super.c b/fs/overlayfs/super.c
index df85a76597e910d00323018f1d2cd720c5db921d..bcb7f5dbf9a32e4aa09bc41596be443851e21200 100644
--- a/fs/overlayfs/super.c
+++ b/fs/overlayfs/super.c
@@ -998,6 +998,7 @@ static int ovl_get_layers(struct super_block *sb, struct ovl_fs *ofs,
int err;
unsigned int i;
size_t nr_merged_lower;
+ struct super_block *sb1 = NULL;
ofs->fs = kcalloc(ctx->nr + 2, sizeof(struct ovl_sb), GFP_KERNEL);
if (ofs->fs == NULL)
@@ -1024,6 +1025,8 @@ static int ovl_get_layers(struct super_block *sb, struct ovl_fs *ofs,
if (ovl_upper_mnt(ofs)) {
ofs->fs[0].sb = ovl_upper_mnt(ofs)->mnt_sb;
ofs->fs[0].is_lower = false;
+
+ sb1 = ofs->fs[0].sb;
}
nr_merged_lower = ctx->nr - ctx->nr_data;
@@ -1067,6 +1070,9 @@ static int ovl_get_layers(struct super_block *sb, struct ovl_fs *ofs,
return err;
}
+ if (!sb1)
+ sb1 = mnt->mnt_sb;
+
/*
* Make lower layers R/O. That way fchmod/fchown on lower file
* will fail instead of modifying lower fs.
@@ -1083,6 +1089,11 @@ static int ovl_get_layers(struct super_block *sb, struct ovl_fs *ofs,
l->name = NULL;
ofs->numlayer++;
ofs->fs[fsid].is_lower = true;
+
+ if (!sb_same_encoding(sb1, mnt->mnt_sb)) {
+ pr_err("all layers must have the same encoding\n");
+ return -EINVAL;
+ }
}
/*
--
2.50.1
^ permalink raw reply related [flat|nested] 18+ messages in thread
* [PATCH RFC v3 5/7] ovl: Set case-insensitive dentry operations for ovl sb
2025-08-08 20:58 [PATCH RFC v3 0/7] ovl: Enable support for casefold filesystems André Almeida
` (3 preceding siblings ...)
2025-08-08 20:58 ` [PATCH RFC v3 4/7] ovl: Ensure that all mount points have the same encoding André Almeida
@ 2025-08-08 20:58 ` André Almeida
2025-08-09 11:50 ` Amir Goldstein
2025-08-08 20:58 ` [PATCH RFC v3 6/7] ovl: Add S_CASEFOLD as part of the inode flag to be copied André Almeida
` (2 subsequent siblings)
7 siblings, 1 reply; 18+ messages in thread
From: André Almeida @ 2025-08-08 20:58 UTC (permalink / raw)
To: Miklos Szeredi, Amir Goldstein, Theodore Tso,
Gabriel Krisman Bertazi
Cc: linux-unionfs, linux-kernel, linux-fsdevel, Alexander Viro,
Christian Brauner, Jan Kara, kernel-dev, André Almeida
For filesystems with encoding (i.e. with case-insensitive support), set
the dentry operations for the super block as ovl_dentry_ci_operations.
Also, use the first layer encoding as the ovl super block encoding.
Signed-off-by: André Almeida <andrealmeid@igalia.com>
---
Changes from v2:
- Create ovl_dentry_ci_operations to not override dentry ops set by
ovl_dentry_operations
- Create a new function for this
- Instead of setting encoding just when there's a upper layer, set it
for any first layer (ofs->fs[0].sb), regardless of it being upper or
not.
---
fs/overlayfs/super.c | 28 ++++++++++++++++++++++++++++
1 file changed, 28 insertions(+)
diff --git a/fs/overlayfs/super.c b/fs/overlayfs/super.c
index bcb7f5dbf9a32e4aa09bc41596be443851e21200..68091bf8368a880d62d9425552613497d6e90b6b 100644
--- a/fs/overlayfs/super.c
+++ b/fs/overlayfs/super.c
@@ -161,6 +161,16 @@ static const struct dentry_operations ovl_dentry_operations = {
.d_weak_revalidate = ovl_dentry_weak_revalidate,
};
+#if IS_ENABLED(CONFIG_UNICODE)
+static const struct dentry_operations ovl_dentry_ci_operations = {
+ .d_real = ovl_d_real,
+ .d_revalidate = ovl_dentry_revalidate,
+ .d_weak_revalidate = ovl_dentry_weak_revalidate,
+ .d_hash = generic_ci_d_hash,
+ .d_compare = generic_ci_d_compare,
+};
+#endif
+
static struct kmem_cache *ovl_inode_cachep;
static struct inode *ovl_alloc_inode(struct super_block *sb)
@@ -1318,6 +1328,21 @@ static struct dentry *ovl_get_root(struct super_block *sb,
return root;
}
+/*
+ * Set the ovl sb encoding as the same one used by the first layer
+ */
+static void ovl_set_sb_ci_ops(struct super_block *ovl_sb, struct super_block *fs_sb)
+{
+#if IS_ENABLED(CONFIG_UNICODE)
+ if (sb_has_encoding(fs_sb)) {
+ ovl_sb->s_encoding = fs_sb->s_encoding;
+ ovl_sb->s_encoding_flags = fs_sb->s_encoding_flags;
+ }
+
+ set_default_d_op(ovl_sb, &ovl_dentry_ci_operations);
+#endif
+}
+
int ovl_fill_super(struct super_block *sb, struct fs_context *fc)
{
struct ovl_fs *ofs = sb->s_fs_info;
@@ -1423,12 +1448,15 @@ int ovl_fill_super(struct super_block *sb, struct fs_context *fc)
sb->s_stack_depth = upper_sb->s_stack_depth;
sb->s_time_gran = upper_sb->s_time_gran;
+
}
oe = ovl_get_lowerstack(sb, ctx, ofs, layers);
err = PTR_ERR(oe);
if (IS_ERR(oe))
goto out_err;
+ ovl_set_sb_ci_ops(sb, ofs->fs[0].sb);
+
/* If the upper fs is nonexistent, we mark overlayfs r/o too */
if (!ovl_upper_mnt(ofs))
sb->s_flags |= SB_RDONLY;
--
2.50.1
^ permalink raw reply related [flat|nested] 18+ messages in thread
* [PATCH RFC v3 6/7] ovl: Add S_CASEFOLD as part of the inode flag to be copied
2025-08-08 20:58 [PATCH RFC v3 0/7] ovl: Enable support for casefold filesystems André Almeida
` (4 preceding siblings ...)
2025-08-08 20:58 ` [PATCH RFC v3 5/7] ovl: Set case-insensitive dentry operations for ovl sb André Almeida
@ 2025-08-08 20:58 ` André Almeida
2025-08-09 9:51 ` Amir Goldstein
2025-08-08 20:58 ` [PATCH RFC v3 7/7] ovl: Support case-insensitive lookup André Almeida
2025-08-09 9:17 ` [PATCH RFC v3 0/7] ovl: Enable support for casefold filesystems Amir Goldstein
7 siblings, 1 reply; 18+ messages in thread
From: André Almeida @ 2025-08-08 20:58 UTC (permalink / raw)
To: Miklos Szeredi, Amir Goldstein, Theodore Tso,
Gabriel Krisman Bertazi
Cc: linux-unionfs, linux-kernel, linux-fsdevel, Alexander Viro,
Christian Brauner, Jan Kara, kernel-dev, André Almeida
To keep ovl's inodes consistent with their real inodes, add the
S_CASEFOLD flag as part of the flags that need to be copied.
Signed-off-by: André Almeida <andrealmeid@igalia.com>
---
Changes from v2:
- Instead of manually setting the flag if the realpath dentry is
casefolded, just add this flag as part of the flags that need to be
copied.
---
fs/overlayfs/overlayfs.h | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/fs/overlayfs/overlayfs.h b/fs/overlayfs/overlayfs.h
index bb0d7ded8e763a4a7a6fc506d966ed2f3bdb4f06..8a9a67d2933173c61b0fa0af5634d91e092e00b2 100644
--- a/fs/overlayfs/overlayfs.h
+++ b/fs/overlayfs/overlayfs.h
@@ -822,7 +822,7 @@ struct inode *ovl_get_inode(struct super_block *sb,
void ovl_copyattr(struct inode *to);
/* vfs inode flags copied from real to ovl inode */
-#define OVL_COPY_I_FLAGS_MASK (S_SYNC | S_NOATIME | S_APPEND | S_IMMUTABLE)
+#define OVL_COPY_I_FLAGS_MASK (S_SYNC | S_NOATIME | S_APPEND | S_IMMUTABLE | S_CASEFOLD)
/* vfs inode flags read from overlay.protattr xattr to ovl inode */
#define OVL_PROT_I_FLAGS_MASK (S_APPEND | S_IMMUTABLE)
--
2.50.1
^ permalink raw reply related [flat|nested] 18+ messages in thread
* [PATCH RFC v3 7/7] ovl: Support case-insensitive lookup
2025-08-08 20:58 [PATCH RFC v3 0/7] ovl: Enable support for casefold filesystems André Almeida
` (5 preceding siblings ...)
2025-08-08 20:58 ` [PATCH RFC v3 6/7] ovl: Add S_CASEFOLD as part of the inode flag to be copied André Almeida
@ 2025-08-08 20:58 ` André Almeida
2025-08-09 10:45 ` Amir Goldstein
2025-08-09 9:17 ` [PATCH RFC v3 0/7] ovl: Enable support for casefold filesystems Amir Goldstein
7 siblings, 1 reply; 18+ messages in thread
From: André Almeida @ 2025-08-08 20:58 UTC (permalink / raw)
To: Miklos Szeredi, Amir Goldstein, Theodore Tso,
Gabriel Krisman Bertazi
Cc: linux-unionfs, linux-kernel, linux-fsdevel, Alexander Viro,
Christian Brauner, Jan Kara, kernel-dev, André Almeida
Drop the restriction for casefold dentries to enable support for
case-insensitive filesystems in overlayfs.
Support case-insensitive filesystems with the condition that they should
be uniformly enabled across the stack and the layers (i.e. if the root
mount dir has casefold enabled, so should all the dirs bellow for every
layer).
Signed-off-by: André Almeida <andrealmeid@igalia.com>
---
Changes from v2:
- Create new ovl_fs flag, bool casefold
- Check if casefolded dentry is consistent with the root dentry
---
fs/overlayfs/namei.c | 17 +++++++++--------
fs/overlayfs/ovl_entry.h | 1 +
fs/overlayfs/params.c | 7 ++-----
fs/overlayfs/util.c | 8 ++++----
4 files changed, 16 insertions(+), 17 deletions(-)
diff --git a/fs/overlayfs/namei.c b/fs/overlayfs/namei.c
index 76d6248b625e7c58e09685e421aef616aadea40a..08b34e52b36f93d4da09e4d13b51d23dc99ca6d6 100644
--- a/fs/overlayfs/namei.c
+++ b/fs/overlayfs/namei.c
@@ -239,13 +239,14 @@ static int ovl_lookup_single(struct dentry *base, struct ovl_lookup_data *d,
char val;
/*
- * We allow filesystems that are case-folding capable but deny composing
- * ovl stack from case-folded directories. If someone has enabled case
- * folding on a directory on underlying layer, the warranty of the ovl
- * stack is voided.
+ * We allow filesystems that are case-folding capable as long as the
+ * layers are consistently enabled in the stack, enabled for every dir
+ * or disabled in all dirs. If someone has enabled case folding on a
+ * directory on underlying layer, the warranty of the ovl stack is
+ * voided.
*/
- if (ovl_dentry_casefolded(base)) {
- warn = "case folded parent";
+ if (ofs->casefold != ovl_dentry_casefolded(base)) {
+ warn = "parent wrong casefold";
err = -ESTALE;
goto out_warn;
}
@@ -259,8 +260,8 @@ static int ovl_lookup_single(struct dentry *base, struct ovl_lookup_data *d,
goto out_err;
}
- if (ovl_dentry_casefolded(this)) {
- warn = "case folded child";
+ if (ofs->casefold != ovl_dentry_casefolded(this)) {
+ warn = "child wrong casefold";
err = -EREMOTE;
goto out_warn;
}
diff --git a/fs/overlayfs/ovl_entry.h b/fs/overlayfs/ovl_entry.h
index 4c1bae935ced274f93a0d23fe10d34455e226ec4..1d4828dbcf7ac4ba9657221e601bbf79d970d225 100644
--- a/fs/overlayfs/ovl_entry.h
+++ b/fs/overlayfs/ovl_entry.h
@@ -91,6 +91,7 @@ struct ovl_fs {
struct mutex whiteout_lock;
/* r/o snapshot of upperdir sb's only taken on volatile mounts */
errseq_t errseq;
+ bool casefold;
};
/* Number of lower layers, not including data-only layers */
diff --git a/fs/overlayfs/params.c b/fs/overlayfs/params.c
index f4e7fff909ac49e2f8c58a76273426c1158a7472..afa1c29515a9729bfe88c8166da4aefa6cddc5a5 100644
--- a/fs/overlayfs/params.c
+++ b/fs/overlayfs/params.c
@@ -277,16 +277,13 @@ static int ovl_mount_dir_check(struct fs_context *fc, const struct path *path,
enum ovl_opt layer, const char *name, bool upper)
{
struct ovl_fs_context *ctx = fc->fs_private;
+ struct ovl_fs *ovl = fc->s_fs_info;
if (!d_is_dir(path->dentry))
return invalfc(fc, "%s is not a directory", name);
- /*
- * Allow filesystems that are case-folding capable but deny composing
- * ovl stack from case-folded directories.
- */
if (ovl_dentry_casefolded(path->dentry))
- return invalfc(fc, "case-insensitive directory on %s not supported", name);
+ ovl->casefold = true;
if (ovl_dentry_weird(path->dentry))
return invalfc(fc, "filesystem on %s not supported", name);
diff --git a/fs/overlayfs/util.c b/fs/overlayfs/util.c
index a33115e7384c129c543746326642813add63f060..7a6ee058568283453350153c1720c35e11ad4d1b 100644
--- a/fs/overlayfs/util.c
+++ b/fs/overlayfs/util.c
@@ -210,11 +210,11 @@ bool ovl_dentry_weird(struct dentry *dentry)
return true;
/*
- * Allow filesystems that are case-folding capable but deny composing
- * ovl stack from case-folded directories.
+ * Exceptionally for casefold dentries, we accept that they have their
+ * own hash and compare operations
*/
- if (sb_has_encoding(dentry->d_sb))
- return IS_CASEFOLDED(d_inode(dentry));
+ if (ovl_dentry_casefolded(dentry))
+ return false;
return dentry->d_flags & (DCACHE_OP_HASH | DCACHE_OP_COMPARE);
}
--
2.50.1
^ permalink raw reply related [flat|nested] 18+ messages in thread
* Re: [PATCH RFC v3 0/7] ovl: Enable support for casefold filesystems
2025-08-08 20:58 [PATCH RFC v3 0/7] ovl: Enable support for casefold filesystems André Almeida
` (6 preceding siblings ...)
2025-08-08 20:58 ` [PATCH RFC v3 7/7] ovl: Support case-insensitive lookup André Almeida
@ 2025-08-09 9:17 ` Amir Goldstein
2025-08-09 16:08 ` Amir Goldstein
7 siblings, 1 reply; 18+ messages in thread
From: Amir Goldstein @ 2025-08-09 9:17 UTC (permalink / raw)
To: André Almeida
Cc: Miklos Szeredi, Theodore Tso, Gabriel Krisman Bertazi,
linux-unionfs, linux-kernel, linux-fsdevel, Alexander Viro,
Christian Brauner, Jan Kara, kernel-dev
On Fri, Aug 8, 2025 at 10:59 PM André Almeida <andrealmeid@igalia.com> wrote:
>
> Hi all,
>
> We would like to support the usage of casefold filesystems with
> overlayfs to be used with container tools. This use case requires a
> simple setup, where every layer will have the same encoding setting
> (i.e. Unicode version and flags), using one upper and one lower layer.
>
> * Implementation
>
> When merge layers, ovl uses a red-black tree to check if a given dentry
> name from a lower layers already exists in the upper layer. For merging
> case-insensitive names, we need to store then in tree casefolded.
> However, when displaying to the user the dentry name, we need to respect
> the name chosen when the file was created (e.g. Picture.PNG, instead of
> picture.png). To achieve this, I create a new field for cache entries
> that stores the casefolded names and a function ovl_strcmp() that uses
> this name for searching the rb_tree. For composing the layer, ovl uses
> the original name, keeping it consistency with whatever name the user
> created.
>
> The rest of the patches are mostly for checking if casefold is being
> consistently used across the layers and dropping the mount restrictions
> that prevented case-insensitive filesystems to be mounted.
>
> Thanks for the feedback!
Hi Andre,
This v3 is getting close.
Still some small details to fix.
With v4, please include a link to fstest so I can test your work.
I already had a draft for the casefold capable and disabled layers
in 6.17 but I did not post it yet.
Decide it would be better to test the final result after your work.
I'd started to adapt the test to the expected behavior for 6.18,
but it's just an untested draft for you to extend:
https://github.com/amir73il/xfstests/commits/ovl-casefold/
Please make sure that you run at least check -overlay -g overlay/quick
sanity check for v4 and that you run check -g casefold,
which should run the new generic test.
>
> ---
> Changes in v3:
> - Rebased on top of vfs-6.18.misc branch
> - Added more guards for casefolding things inside of IS_ENABLED(UNICODE)
There are too many ifdefs in overlayfs c code to my taste.
Need to see how we can reduce them a bit..
Thanks,
Amir.
> - Refactor the strncmp() patch to do a single kmalloc() per rb_tree operation
> - Instead of casefolding the cache entry name everytime per strncmp(),
> casefold it once and reuse it for every strncmp().
> - Created ovl_dentry_ci_operations to not override dentry ops set by
> ovl_dentry_operations
> - Instead of setting encoding just when there's a upper layer, set it
> for any first layer (ofs->fs[0].sb), regardless of it being upper or
> not.
> - Rewrote the patch that set inode flags
> - Check if every dentry is consistent with the root dentry regarding
> casefold
> v2: https://lore.kernel.org/r/20250805-tonyk-overlayfs-v2-0-0e54281da318@igalia.com
>
> Changes in v2:
> - Almost a full rewritten from the v1.
> v1: https://lore.kernel.org/lkml/20250409-tonyk-overlayfs-v1-0-3991616fe9a3@igalia.com/
>
> ---
> André Almeida (7):
> ovl: Store casefold name for case-insentive dentries
> ovl: Create ovl_casefold() to support casefolded strncmp()
> fs: Create sb_same_encoding() helper
> ovl: Ensure that all mount points have the same encoding
> ovl: Set case-insensitive dentry operations for ovl sb
> ovl: Add S_CASEFOLD as part of the inode flag to be copied
> ovl: Support case-insensitive lookup
>
> fs/overlayfs/namei.c | 17 +++---
> fs/overlayfs/overlayfs.h | 2 +-
> fs/overlayfs/ovl_entry.h | 1 +
> fs/overlayfs/params.c | 7 +--
> fs/overlayfs/readdir.c | 133 ++++++++++++++++++++++++++++++++++++++++++-----
> fs/overlayfs/super.c | 39 ++++++++++++++
> fs/overlayfs/util.c | 8 +--
> include/linux/fs.h | 19 +++++++
> 8 files changed, 195 insertions(+), 31 deletions(-)
> ---
> base-commit: 0fdf709a849f773c9b23b0d9fff2a25de056ddd5
> change-id: 20250409-tonyk-overlayfs-591f5e4d407a
>
> Best regards,
> --
> André Almeida <andrealmeid@igalia.com>
>
^ permalink raw reply [flat|nested] 18+ messages in thread
* Re: [PATCH RFC v3 6/7] ovl: Add S_CASEFOLD as part of the inode flag to be copied
2025-08-08 20:58 ` [PATCH RFC v3 6/7] ovl: Add S_CASEFOLD as part of the inode flag to be copied André Almeida
@ 2025-08-09 9:51 ` Amir Goldstein
2025-08-10 13:02 ` Amir Goldstein
0 siblings, 1 reply; 18+ messages in thread
From: Amir Goldstein @ 2025-08-09 9:51 UTC (permalink / raw)
To: André Almeida
Cc: Miklos Szeredi, Theodore Tso, Gabriel Krisman Bertazi,
linux-unionfs, linux-kernel, linux-fsdevel, Alexander Viro,
Christian Brauner, Jan Kara, kernel-dev
On Fri, Aug 8, 2025 at 10:59 PM André Almeida <andrealmeid@igalia.com> wrote:
>
> To keep ovl's inodes consistent with their real inodes, add the
> S_CASEFOLD flag as part of the flags that need to be copied.
>
> Signed-off-by: André Almeida <andrealmeid@igalia.com>
> ---
> Changes from v2:
> - Instead of manually setting the flag if the realpath dentry is
> casefolded, just add this flag as part of the flags that need to be
> copied.
> ---
> fs/overlayfs/overlayfs.h | 2 +-
> 1 file changed, 1 insertion(+), 1 deletion(-)
>
> diff --git a/fs/overlayfs/overlayfs.h b/fs/overlayfs/overlayfs.h
> index bb0d7ded8e763a4a7a6fc506d966ed2f3bdb4f06..8a9a67d2933173c61b0fa0af5634d91e092e00b2 100644
> --- a/fs/overlayfs/overlayfs.h
> +++ b/fs/overlayfs/overlayfs.h
> @@ -822,7 +822,7 @@ struct inode *ovl_get_inode(struct super_block *sb,
> void ovl_copyattr(struct inode *to);
>
> /* vfs inode flags copied from real to ovl inode */
> -#define OVL_COPY_I_FLAGS_MASK (S_SYNC | S_NOATIME | S_APPEND | S_IMMUTABLE)
> +#define OVL_COPY_I_FLAGS_MASK (S_SYNC | S_NOATIME | S_APPEND | S_IMMUTABLE | S_CASEFOLD)
> /* vfs inode flags read from overlay.protattr xattr to ovl inode */
> #define OVL_PROT_I_FLAGS_MASK (S_APPEND | S_IMMUTABLE)
>
Ok, this is simpler, but it's too simple.
OVL_COPY_I_FLAGS_MASK is used in copy up with the assumption that
all copied i_flags are related to fileattr flags, so you need something like:
diff --git a/fs/overlayfs/copy_up.c b/fs/overlayfs/copy_up.c
index 27396fe63f6d..66bd43a99d2e 100644
--- a/fs/overlayfs/copy_up.c
+++ b/fs/overlayfs/copy_up.c
@@ -670,7 +670,7 @@ static int ovl_copy_up_metadata(struct
ovl_copy_up_ctx *c, struct dentry *temp)
if (err)
return err;
- if (inode->i_flags & OVL_COPY_I_FLAGS_MASK &&
+ if (inode->i_flags & OVL_FATTR_I_FLAGS_MASK &&
(S_ISREG(c->stat.mode) || S_ISDIR(c->stat.mode))) {
/*
* Copy the fileattr inode flags that are the source of already
diff --git a/fs/overlayfs/overlayfs.h b/fs/overlayfs/overlayfs.h
index bb0d7ded8e76..f014802cfe55 100644
--- a/fs/overlayfs/overlayfs.h
+++ b/fs/overlayfs/overlayfs.h
@@ -821,10 +821,14 @@ struct inode *ovl_get_inode(struct super_block *sb,
struct ovl_inode_params *oip);
void ovl_copyattr(struct inode *to);
-/* vfs inode flags copied from real to ovl inode */
-#define OVL_COPY_I_FLAGS_MASK (S_SYNC | S_NOATIME | S_APPEND | S_IMMUTABLE)
-/* vfs inode flags read from overlay.protattr xattr to ovl inode */
+/* vfs fileattr flags read from overlay.protattr xattr to ovl inode */
#define OVL_PROT_I_FLAGS_MASK (S_APPEND | S_IMMUTABLE)
+/* vfs fileattr flags copied from real to ovl inode */
+#define OVL_FATTR_I_FLAGS_MASK (OVL_PROT_I_FLAGS_MASK | \
+ S_SYNC | S_NOATIME)
+/* vfs inode flags copied from real to ovl inode */
+#define OVL_COPY_I_FLAGS_MASK (OVL_FATTR_I_FLAGS_MASK | \
+ S_CASEFOLD)
In addition we want a sanity check that S_CASEFOLD is always consistent
with the ovl global casefold state.
I think a WARN_ON assertion is enough even without failing:
diff --git a/fs/overlayfs/super.c b/fs/overlayfs/super.c
index df85a76597e9..53914b4039c0 100644
--- a/fs/overlayfs/super.c
+++ b/fs/overlayfs/super.c
@@ -1300,6 +1300,7 @@ static struct dentry *ovl_get_root(struct super_block *sb,
ovl_dentry_set_flag(OVL_E_CONNECTED, root);
ovl_set_upperdata(d_inode(root));
ovl_inode_init(d_inode(root), &oip, ino, fsid);
+ WARN_ON(IS_CASEFOLDED(d_inode(root)) != ofs->casefold);
ovl_dentry_init_flags(root, upperdentry, oe, DCACHE_OP_WEAK_REVALIDATE);
/* root keeps a reference of upperdentry */
dget(upperdentry);
diff --git a/fs/overlayfs/inode.c b/fs/overlayfs/inode.c
index ecb9f2019395..ad97daf6641b 100644
--- a/fs/overlayfs/inode.c
+++ b/fs/overlayfs/inode.c
@@ -1277,6 +1277,7 @@ struct inode *ovl_get_inode(struct super_block *sb,
}
ovl_fill_inode(inode, realinode->i_mode, realinode->i_rdev);
ovl_inode_init(inode, oip, ino, fsid);
+ WARN_ON_ONCE(IS_CASEFOLDED(inode) != ofs->casefold);
if (upperdentry && ovl_is_impuredir(sb, upperdentry))
ovl_set_flag(OVL_IMPURE, inode);
Thanks,
Amir.
^ permalink raw reply related [flat|nested] 18+ messages in thread
* Re: [PATCH RFC v3 3/7] fs: Create sb_same_encoding() helper
2025-08-08 20:58 ` [PATCH RFC v3 3/7] fs: Create sb_same_encoding() helper André Almeida
@ 2025-08-09 10:01 ` Amir Goldstein
0 siblings, 0 replies; 18+ messages in thread
From: Amir Goldstein @ 2025-08-09 10:01 UTC (permalink / raw)
To: André Almeida
Cc: Miklos Szeredi, Theodore Tso, Gabriel Krisman Bertazi,
linux-unionfs, linux-kernel, linux-fsdevel, Alexander Viro,
Christian Brauner, Jan Kara, kernel-dev
On Fri, Aug 8, 2025 at 10:59 PM André Almeida <andrealmeid@igalia.com> wrote:
>
> For cases where a file lookup can go to different mount points (like in
s/can go to different mount points/can look in different filesystems/
> overlayfs), both super blocks must have the same encoding and the same
> flags. To help with that, create a sb_same_encoding() function.
>
> Signed-off-by: André Almeida <andrealmeid@igalia.com>
With wording fixed feel free to add:
Reviewed-by: Amir Goldstein <amir73il@gmail.com>
Thanks,
Amir.
> ---
> Changes from v2:
> - Simplify the code. Instead of `if (cond) return true`, just do `return
> cond`;
> ---
> include/linux/fs.h | 19 +++++++++++++++++++
> 1 file changed, 19 insertions(+)
>
> diff --git a/include/linux/fs.h b/include/linux/fs.h
> index db49a17376d124785b87dd7f35672fc6e5434f47..d1fe69f233c046a960a60072d5ac3f6286d32c17 100644
> --- a/include/linux/fs.h
> +++ b/include/linux/fs.h
> @@ -3746,6 +3746,25 @@ static inline bool sb_has_encoding(const struct super_block *sb)
> #endif
> }
>
> +/*
> + * Compare if two super blocks have the same encoding and flags
> + */
> +static inline bool sb_same_encoding(const struct super_block *sb1,
> + const struct super_block *sb2)
> +{
> +#if IS_ENABLED(CONFIG_UNICODE)
> + if (sb1->s_encoding == sb2->s_encoding)
> + return true;
> +
> + return (sb1->s_encoding && sb2->s_encoding &&
> + (sb1->s_encoding->version == sb2->s_encoding->version) &&
> + (sb1->s_encoding_flags == sb2->s_encoding_flags));
> +#else
> + return true;
> +#endif
> +}
> +
> +
> int may_setattr(struct mnt_idmap *idmap, struct inode *inode,
> unsigned int ia_valid);
> int setattr_prepare(struct mnt_idmap *, struct dentry *, struct iattr *);
>
> --
> 2.50.1
>
^ permalink raw reply [flat|nested] 18+ messages in thread
* Re: [PATCH RFC v3 4/7] ovl: Ensure that all mount points have the same encoding
2025-08-08 20:58 ` [PATCH RFC v3 4/7] ovl: Ensure that all mount points have the same encoding André Almeida
@ 2025-08-09 10:07 ` Amir Goldstein
0 siblings, 0 replies; 18+ messages in thread
From: Amir Goldstein @ 2025-08-09 10:07 UTC (permalink / raw)
To: André Almeida
Cc: Miklos Szeredi, Theodore Tso, Gabriel Krisman Bertazi,
linux-unionfs, linux-kernel, linux-fsdevel, Alexander Viro,
Christian Brauner, Jan Kara, kernel-dev
On Fri, Aug 8, 2025 at 10:59 PM André Almeida <andrealmeid@igalia.com> wrote:
>
> When mounting different mount points with casefold support, they should
Different mount points in incorrect terminology, please use:
When merging layers from different filesystems with casefold enabled,
all layers should...
> use the same encoding version and have the same flags to avoid any kind
> of incompatibility issues.
>
> Signed-off-by: André Almeida <andrealmeid@igalia.com>
> ---
> fs/overlayfs/super.c | 11 +++++++++++
> 1 file changed, 11 insertions(+)
>
> diff --git a/fs/overlayfs/super.c b/fs/overlayfs/super.c
> index df85a76597e910d00323018f1d2cd720c5db921d..bcb7f5dbf9a32e4aa09bc41596be443851e21200 100644
> --- a/fs/overlayfs/super.c
> +++ b/fs/overlayfs/super.c
> @@ -998,6 +998,7 @@ static int ovl_get_layers(struct super_block *sb, struct ovl_fs *ofs,
> int err;
> unsigned int i;
> size_t nr_merged_lower;
> + struct super_block *sb1 = NULL;
>
> ofs->fs = kcalloc(ctx->nr + 2, sizeof(struct ovl_sb), GFP_KERNEL);
> if (ofs->fs == NULL)
> @@ -1024,6 +1025,8 @@ static int ovl_get_layers(struct super_block *sb, struct ovl_fs *ofs,
> if (ovl_upper_mnt(ofs)) {
> ofs->fs[0].sb = ovl_upper_mnt(ofs)->mnt_sb;
> ofs->fs[0].is_lower = false;
> +
> + sb1 = ofs->fs[0].sb;
> }
>
> nr_merged_lower = ctx->nr - ctx->nr_data;
> @@ -1067,6 +1070,9 @@ static int ovl_get_layers(struct super_block *sb, struct ovl_fs *ofs,
> return err;
> }
>
> + if (!sb1)
> + sb1 = mnt->mnt_sb;
> +
> /*
> * Make lower layers R/O. That way fchmod/fchown on lower file
> * will fail instead of modifying lower fs.
> @@ -1083,6 +1089,11 @@ static int ovl_get_layers(struct super_block *sb, struct ovl_fs *ofs,
> l->name = NULL;
> ofs->numlayer++;
> ofs->fs[fsid].is_lower = true;
> +
> + if (!sb_same_encoding(sb1, mnt->mnt_sb)) {
> + pr_err("all layers must have the same encoding\n");
> + return -EINVAL;
> + }
> }
This condition is too strict IMO.
It is only needed if ofs->casefold is enabled.
When casefolding is not enabled for the ovl mount,
we need not care about the layers encoding.
This is related to another comment I have on path 7 -
ofs->casefold config should be introduced much earlier in the
series, so that it could be used in conditions like this one and
in the S_CASEFOLD assertions after copying inode flags.
Thanks,
Amir.
^ permalink raw reply [flat|nested] 18+ messages in thread
* Re: [PATCH RFC v3 7/7] ovl: Support case-insensitive lookup
2025-08-08 20:58 ` [PATCH RFC v3 7/7] ovl: Support case-insensitive lookup André Almeida
@ 2025-08-09 10:45 ` Amir Goldstein
0 siblings, 0 replies; 18+ messages in thread
From: Amir Goldstein @ 2025-08-09 10:45 UTC (permalink / raw)
To: André Almeida
Cc: Miklos Szeredi, Theodore Tso, Gabriel Krisman Bertazi,
linux-unionfs, linux-kernel, linux-fsdevel, Alexander Viro,
Christian Brauner, Jan Kara, kernel-dev
On Fri, Aug 8, 2025 at 10:59 PM André Almeida <andrealmeid@igalia.com> wrote:
>
> Drop the restriction for casefold dentries to enable support for
> case-insensitive filesystems in overlayfs.
>
> Support case-insensitive filesystems with the condition that they should
> be uniformly enabled across the stack and the layers (i.e. if the root
> mount dir has casefold enabled, so should all the dirs bellow for every
> layer).
>
> Signed-off-by: André Almeida <andrealmeid@igalia.com>
> ---
> Changes from v2:
> - Create new ovl_fs flag, bool casefold
> - Check if casefolded dentry is consistent with the root dentry
> ---
> fs/overlayfs/namei.c | 17 +++++++++--------
> fs/overlayfs/ovl_entry.h | 1 +
> fs/overlayfs/params.c | 7 ++-----
> fs/overlayfs/util.c | 8 ++++----
> 4 files changed, 16 insertions(+), 17 deletions(-)
>
> diff --git a/fs/overlayfs/namei.c b/fs/overlayfs/namei.c
> index 76d6248b625e7c58e09685e421aef616aadea40a..08b34e52b36f93d4da09e4d13b51d23dc99ca6d6 100644
> --- a/fs/overlayfs/namei.c
> +++ b/fs/overlayfs/namei.c
> @@ -239,13 +239,14 @@ static int ovl_lookup_single(struct dentry *base, struct ovl_lookup_data *d,
> char val;
>
> /*
> - * We allow filesystems that are case-folding capable but deny composing
> - * ovl stack from case-folded directories. If someone has enabled case
> - * folding on a directory on underlying layer, the warranty of the ovl
> - * stack is voided.
> + * We allow filesystems that are case-folding capable as long as the
> + * layers are consistently enabled in the stack, enabled for every dir
> + * or disabled in all dirs. If someone has enabled case folding on a
If someone has modified case folding....
> + * directory on underlying layer, the warranty of the ovl stack is
> + * voided.
> */
> - if (ovl_dentry_casefolded(base)) {
> - warn = "case folded parent";
> + if (ofs->casefold != ovl_dentry_casefolded(base)) {
> + warn = "parent wrong casefold";
> err = -ESTALE;
> goto out_warn;
> }
> @@ -259,8 +260,8 @@ static int ovl_lookup_single(struct dentry *base, struct ovl_lookup_data *d,
> goto out_err;
> }
>
> - if (ovl_dentry_casefolded(this)) {
> - warn = "case folded child";
> + if (ofs->casefold != ovl_dentry_casefolded(this)) {
> + warn = "child wrong casefold";
> err = -EREMOTE;
> goto out_warn;
> }
> diff --git a/fs/overlayfs/ovl_entry.h b/fs/overlayfs/ovl_entry.h
> index 4c1bae935ced274f93a0d23fe10d34455e226ec4..1d4828dbcf7ac4ba9657221e601bbf79d970d225 100644
> --- a/fs/overlayfs/ovl_entry.h
> +++ b/fs/overlayfs/ovl_entry.h
> @@ -91,6 +91,7 @@ struct ovl_fs {
> struct mutex whiteout_lock;
> /* r/o snapshot of upperdir sb's only taken on volatile mounts */
> errseq_t errseq;
> + bool casefold;
Better introduce this in an earlier patch, even if it is only set in the
last patch, so that other code can use it, like the inode S_CASEFOLD
assertion.
> };
>
> /* Number of lower layers, not including data-only layers */
> diff --git a/fs/overlayfs/params.c b/fs/overlayfs/params.c
> index f4e7fff909ac49e2f8c58a76273426c1158a7472..afa1c29515a9729bfe88c8166da4aefa6cddc5a5 100644
> --- a/fs/overlayfs/params.c
> +++ b/fs/overlayfs/params.c
> @@ -277,16 +277,13 @@ static int ovl_mount_dir_check(struct fs_context *fc, const struct path *path,
> enum ovl_opt layer, const char *name, bool upper)
> {
> struct ovl_fs_context *ctx = fc->fs_private;
> + struct ovl_fs *ovl = fc->s_fs_info;
ofs, not ovl
>
> if (!d_is_dir(path->dentry))
> return invalfc(fc, "%s is not a directory", name);
>
> - /*
> - * Allow filesystems that are case-folding capable but deny composing
> - * ovl stack from case-folded directories.
> - */
> if (ovl_dentry_casefolded(path->dentry))
> - return invalfc(fc, "case-insensitive directory on %s not supported", name);
> + ovl->casefold = true;
>
The problem with removing this invalf() is that it is more useful to userspace
than the kernel logs in ovl_fill_supper(), so I prefer to leave this descriptive
configuration error here, something like this:
diff --git a/fs/overlayfs/params.c b/fs/overlayfs/params.c
index f4e7fff909ac..57035f0f594e 100644
--- a/fs/overlayfs/params.c
+++ b/fs/overlayfs/params.c
@@ -277,16 +277,24 @@ static int ovl_mount_dir_check(struct fs_context
*fc, const struct path *path,
enum ovl_opt layer, const char *name, bool upper)
{
struct ovl_fs_context *ctx = fc->fs_private;
+ struct ovl_fs *ofs = fc->s_fs_info;
+ bool is_casefolded = ovl_dentry_casefolded(path->dentry);
if (!d_is_dir(path->dentry))
return invalfc(fc, "%s is not a directory", name);
/*
* Allow filesystems that are case-folding capable but deny composing
- * ovl stack from case-folded directories.
+ * ovl stack from inconsistent case-folded directories.
*/
- if (ovl_dentry_casefolded(path->dentry))
- return invalfc(fc, "case-insensitive directory on %s
not supported", name);
+ if (!ctx->casefold_set) {
+ ofs->casefold = is_casefolded;
+ ctx->casefold_set = true;
+ }
+ if (ofs->casefold != is_casefolded)
+ return invalfc(fc, "case-%ssensitive directory on %s
is inconsistent",
+ is_casefolded ? "in" : "", name);
+ }
if (ovl_dentry_weird(path->dentry))
return invalfc(fc, "filesystem on %s not supported", name);
diff --git a/fs/overlayfs/params.h b/fs/overlayfs/params.h
index c96d93982021..ffd53cdd8482 100644
--- a/fs/overlayfs/params.h
+++ b/fs/overlayfs/params.h
@@ -33,6 +33,7 @@ struct ovl_fs_context {
struct ovl_opt_set set;
struct ovl_fs_context_layer *lower;
char *lowerdir_all; /* user provided lowerdir string */
+ bool casefold_set;
};
Thanks,
Amir.
^ permalink raw reply related [flat|nested] 18+ messages in thread
* Re: [PATCH RFC v3 5/7] ovl: Set case-insensitive dentry operations for ovl sb
2025-08-08 20:58 ` [PATCH RFC v3 5/7] ovl: Set case-insensitive dentry operations for ovl sb André Almeida
@ 2025-08-09 11:50 ` Amir Goldstein
0 siblings, 0 replies; 18+ messages in thread
From: Amir Goldstein @ 2025-08-09 11:50 UTC (permalink / raw)
To: André Almeida
Cc: Miklos Szeredi, Theodore Tso, Gabriel Krisman Bertazi,
linux-unionfs, linux-kernel, linux-fsdevel, Alexander Viro,
Christian Brauner, Jan Kara, kernel-dev
On Fri, Aug 8, 2025 at 10:59 PM André Almeida <andrealmeid@igalia.com> wrote:
>
> For filesystems with encoding (i.e. with case-insensitive support), set
> the dentry operations for the super block as ovl_dentry_ci_operations.
> Also, use the first layer encoding as the ovl super block encoding.
>
> Signed-off-by: André Almeida <andrealmeid@igalia.com>
> ---
> Changes from v2:
> - Create ovl_dentry_ci_operations to not override dentry ops set by
> ovl_dentry_operations
> - Create a new function for this
> - Instead of setting encoding just when there's a upper layer, set it
> for any first layer (ofs->fs[0].sb), regardless of it being upper or
> not.
> ---
> fs/overlayfs/super.c | 28 ++++++++++++++++++++++++++++
> 1 file changed, 28 insertions(+)
>
> diff --git a/fs/overlayfs/super.c b/fs/overlayfs/super.c
> index bcb7f5dbf9a32e4aa09bc41596be443851e21200..68091bf8368a880d62d9425552613497d6e90b6b 100644
> --- a/fs/overlayfs/super.c
> +++ b/fs/overlayfs/super.c
> @@ -161,6 +161,16 @@ static const struct dentry_operations ovl_dentry_operations = {
> .d_weak_revalidate = ovl_dentry_weak_revalidate,
> };
>
> +#if IS_ENABLED(CONFIG_UNICODE)
> +static const struct dentry_operations ovl_dentry_ci_operations = {
> + .d_real = ovl_d_real,
> + .d_revalidate = ovl_dentry_revalidate,
> + .d_weak_revalidate = ovl_dentry_weak_revalidate,
> + .d_hash = generic_ci_d_hash,
> + .d_compare = generic_ci_d_compare,
> +};
> +#endif
> +
> static struct kmem_cache *ovl_inode_cachep;
>
> static struct inode *ovl_alloc_inode(struct super_block *sb)
> @@ -1318,6 +1328,21 @@ static struct dentry *ovl_get_root(struct super_block *sb,
> return root;
> }
>
> +/*
> + * Set the ovl sb encoding as the same one used by the first layer
> + */
> +static void ovl_set_sb_ci_ops(struct super_block *ovl_sb, struct super_block *fs_sb)
> +{
> +#if IS_ENABLED(CONFIG_UNICODE)
> + if (sb_has_encoding(fs_sb)) {
> + ovl_sb->s_encoding = fs_sb->s_encoding;
> + ovl_sb->s_encoding_flags = fs_sb->s_encoding_flags;
> + }
> +
> + set_default_d_op(ovl_sb, &ovl_dentry_ci_operations);
I don't like it that set_default_d_op() is called twice and if anything this
helper should have been called only for the ofs->casefold enabled case.
what I suggest it to split to two helpers, first set dentry_ops based on
ofs->casefold determined before ovl_fill_super() is called:
diff --git a/fs/overlayfs/super.c b/fs/overlayfs/super.c
index df85a76597e9..00647440a566 100644
--- a/fs/overlayfs/super.c
+++ b/fs/overlayfs/super.c
@@ -1307,6 +1307,19 @@ static struct dentry *ovl_get_root(struct
super_block *sb,
return root;
}
+static void ovl_set_d_op(struct super_block *sb)
+{
+ struct ovl_fs *ofs = sb->s_fs_info;
+
+#if IS_ENABLED(CONFIG_UNICODE)
+ if (ofs->casefold) {
+ set_default_d_op(ovl_sb, &ovl_dentry_ci_operations);
+ return;
+ }
+#endif
+ set_default_d_op(sb, &ovl_dentry_operations);
+}
+
int ovl_fill_super(struct super_block *sb, struct fs_context *fc)
{
struct ovl_fs *ofs = sb->s_fs_info;
@@ -1322,7 +1335,7 @@ int ovl_fill_super(struct super_block *sb,
struct fs_context *fc)
if (WARN_ON(fc->user_ns != current_user_ns()))
goto out_err;
- set_default_d_op(sb, &ovl_dentry_operations);
+ ovl_set_d_op(sb);
> +#endif
> +}
> +
> int ovl_fill_super(struct super_block *sb, struct fs_context *fc)
> {
> struct ovl_fs *ofs = sb->s_fs_info;
> @@ -1423,12 +1448,15 @@ int ovl_fill_super(struct super_block *sb, struct fs_context *fc)
>
> sb->s_stack_depth = upper_sb->s_stack_depth;
> sb->s_time_gran = upper_sb->s_time_gran;
> +
stray new line.
> }
> oe = ovl_get_lowerstack(sb, ctx, ofs, layers);
> err = PTR_ERR(oe);
> if (IS_ERR(oe))
> goto out_err;
>
> + ovl_set_sb_ci_ops(sb, ofs->fs[0].sb);
> +
This is wrong because ofs->fs[0].sb is NULL on overlay without an upper dir.
Please consider doing this as part of patch 4 instead of using the
local sb1 var:
--- a/fs/overlayfs/super.c
+++ b/fs/overlayfs/super.c
@@ -991,6 +991,19 @@ static int ovl_get_data_fsid(struct ovl_fs *ofs)
}
+/*
+ * Set the ovl sb encoding as the same one used by the first layer
+ */
+static void ovl_set_encoding(struct super_block *sb, struct super_block *fs_sb)
+{
+#if IS_ENABLED(CONFIG_UNICODE)
+ if (sb_has_encoding(fs_sb)) {
+ sb->s_encoding = fs_sb->s_encoding;
+ sb->s_encoding_flags = fs_sb->s_encoding_flags;
+ }
+#endif
+}
+
static int ovl_get_layers(struct super_block *sb, struct ovl_fs *ofs,
struct ovl_fs_context *ctx, struct ovl_layer *layers)
{
@@ -1024,6 +1036,8 @@ static int ovl_get_layers(struct super_block
*sb, struct ovl_fs *ofs,
if (ovl_upper_mnt(ofs)) {
ofs->fs[0].sb = ovl_upper_mnt(ofs)->mnt_sb;
ofs->fs[0].is_lower = false;
+ if (ofs->casefold)
+ ovl_set_encoding(sb, ofs->fs[0].sb);
}
nr_merged_lower = ctx->nr - ctx->nr_data;
@@ -1083,6 +1097,17 @@ static int ovl_get_layers(struct super_block
*sb, struct ovl_fs *ofs,
l->name = NULL;
ofs->numlayer++;
ofs->fs[fsid].is_lower = true;
+
+ if (ofs->casefold) {
+ if (!ovl_upper_mnt(ofs) && !sb_has_encoding(sb))
+ ovl_set_encoding(sb, ofs->fs[fsid].sb);
+
+ if (!sb_has_encoding(sb) ||
+ !sb_same_encoding(sb, mnt->mnt_sb)) {
+ pr_err("all layers must have the same
encoding\n");
+ return -EINVAL;
+ }
+ }
}
Thanks,
Amir.
^ permalink raw reply related [flat|nested] 18+ messages in thread
* Re: [PATCH RFC v3 1/7] ovl: Store casefold name for case-insentive dentries
2025-08-08 20:58 ` [PATCH RFC v3 1/7] ovl: Store casefold name for case-insentive dentries André Almeida
@ 2025-08-09 12:36 ` Amir Goldstein
0 siblings, 0 replies; 18+ messages in thread
From: Amir Goldstein @ 2025-08-09 12:36 UTC (permalink / raw)
To: André Almeida
Cc: Miklos Szeredi, Theodore Tso, Gabriel Krisman Bertazi,
linux-unionfs, linux-kernel, linux-fsdevel, Alexander Viro,
Christian Brauner, Jan Kara, kernel-dev
On Fri, Aug 8, 2025 at 10:59 PM André Almeida <andrealmeid@igalia.com> wrote:
>
> In order to make case-insentive mounting points work, overlayfs needs
Terminology:
not case-insensitive mounting points
case-insensitive layers.
> the casefolded version of its dentries so the search and insertion in
Terminology:
overlayfs needs the casefolded names
the objects in the rb tree correspond to dirent readdir results not to dentries
which are dcache objects.
> the struct ovl_readdir_data's red-black compares the dentry names in a
> case-insentive fashion.
>
> If a dentry is casefolded, compute and store it's casefolded name and
We are not doing per-dir casefolding so should say:
"If overlay mount is casefolded, compute and store the casefolded name..."
> it's Unicode map. If utf8_casefold() fails, set it's name pointer as
> NULL so it can be ignored and fallback to the original name.
>
> Signed-off-by: André Almeida <andrealmeid@igalia.com>
> ---
> Changes from v3:
> - Guard all the casefolding inside of IS_ENABLED(UNICODE)
> ---
> fs/overlayfs/readdir.c | 41 ++++++++++++++++++++++++++++++++++++++++-
> 1 file changed, 40 insertions(+), 1 deletion(-)
>
> diff --git a/fs/overlayfs/readdir.c b/fs/overlayfs/readdir.c
> index b65cdfce31ce27172d28d879559f1008b9c87320..2f42fec97f76c2000f76e15c60975db567b2c6d6 100644
> --- a/fs/overlayfs/readdir.c
> +++ b/fs/overlayfs/readdir.c
> @@ -16,6 +16,8 @@
> #include <linux/overflow.h>
> #include "overlayfs.h"
>
> +#define OVL_NAME_LEN 255
> +
This is very arbitrary.
Either use the existing constant NAME_MAX or
use the max of all layers ofs->namelen.
I think ofs->namelen will always be <= NAME_MAX,
so it's fine to use NAME_MAX if we never expose
the cf_name to userspace.
> struct ovl_cache_entry {
> unsigned int len;
> unsigned int type;
> @@ -27,6 +29,8 @@ struct ovl_cache_entry {
> bool is_upper;
> bool is_whiteout;
> bool check_xwhiteout;
> + char *cf_name;
> + int cf_len;
> char name[];
> };
>
> @@ -45,6 +49,7 @@ struct ovl_readdir_data {
> struct list_head *list;
> struct list_head middle;
> struct ovl_cache_entry *first_maybe_whiteout;
> + struct unicode_map *map;
> int count;
> int err;
> bool is_upper;
> @@ -166,6 +171,31 @@ static struct ovl_cache_entry *ovl_cache_entry_new(struct ovl_readdir_data *rdd,
> p->is_whiteout = false;
> /* Defer check for overlay.whiteout to ovl_iterate() */
> p->check_xwhiteout = rdd->in_xwhiteouts_dir && d_type == DT_REG;
> + p->cf_name = NULL;
> + p->cf_len = 0;
> +
> +#if IS_ENABLED(CONFIG_UNICODE)
Whenever possible in C code you should use:
if (IS_ENABLED(CONFIG_UNICODE) && rdd->map && ...
> + if (rdd->map && !is_dot_dotdot(name, len)) {
> + const struct qstr str = { .name = name, .len = len };
> + int ret;
> +
> + p->cf_name = kmalloc(OVL_NAME_LEN, GFP_KERNEL);
> +
> + if (!p->cf_name) {
> + kfree(p);
> + return NULL;
> + }
> +
> + ret = utf8_casefold(rdd->map, &str, p->cf_name, OVL_NAME_LEN);
> +
> + if (ret < 0) {
> + kfree(p->cf_name);
> + p->cf_name = NULL;
> + } else {
> + p->cf_len = ret;
> + }
> + }
> +#endif
>
> if (d_type == DT_CHR) {
> p->next_maybe_whiteout = rdd->first_maybe_whiteout;
> @@ -223,8 +253,10 @@ void ovl_cache_free(struct list_head *list)
> struct ovl_cache_entry *p;
> struct ovl_cache_entry *n;
>
> - list_for_each_entry_safe(p, n, list, l_node)
> + list_for_each_entry_safe(p, n, list, l_node) {
> + kfree(p->cf_name);
> kfree(p);
> + }
>
> INIT_LIST_HEAD(list);
> }
> @@ -357,12 +389,19 @@ static int ovl_dir_read_merged(struct dentry *dentry, struct list_head *list,
> .list = list,
> .root = root,
> .is_lowest = false,
> + .map = NULL,
> };
> int idx, next;
> const struct ovl_layer *layer;
>
> for (idx = 0; idx != -1; idx = next) {
> next = ovl_path_next(idx, dentry, &realpath, &layer);
> +
> +#if IS_ENABLED(CONFIG_UNICODE)
> + if (ovl_dentry_casefolded(realpath.dentry))
> + rdd.map = realpath.dentry->d_sb->s_encoding;
> +#endif
> +
We are not doing per-dir casefolding, so this should be
if (ofs->casefold)
and I'd rather avoid this ifdef, so how about another vfs helper:
diff --git a/include/linux/fs.h b/include/linux/fs.h
index 2ec4807d4ea8..3f4c89367908 100644
--- a/include/linux/fs.h
+++ b/include/linux/fs.h
@@ -3738,15 +3738,20 @@ static inline bool
generic_ci_validate_strict_name(struct inode *dir, struct qst
}
#endif
-static inline bool sb_has_encoding(const struct super_block *sb)
+static inline struct unicode_map *sb_encoding(const struct super_block *sb)
{
#if IS_ENABLED(CONFIG_UNICODE)
- return !!sb->s_encoding;
+ return sb->s_encoding;
#else
- return false;
+ return NULL;
#endif
}
+static inline bool sb_has_encoding(const struct super_block *sb)
+{
+ return !!sb_encoding(sb);
+}
+
Thanks,
Amir.
^ permalink raw reply related [flat|nested] 18+ messages in thread
* Re: [PATCH RFC v3 2/7] ovl: Create ovl_casefold() to support casefolded strncmp()
2025-08-08 20:58 ` [PATCH RFC v3 2/7] ovl: Create ovl_casefold() to support casefolded strncmp() André Almeida
@ 2025-08-09 15:13 ` Amir Goldstein
0 siblings, 0 replies; 18+ messages in thread
From: Amir Goldstein @ 2025-08-09 15:13 UTC (permalink / raw)
To: André Almeida, Miklos Szeredi
Cc: Theodore Tso, Gabriel Krisman Bertazi, linux-unionfs,
linux-kernel, linux-fsdevel, Alexander Viro, Christian Brauner,
Jan Kara, kernel-dev
On Fri, Aug 8, 2025 at 10:59 PM André Almeida <andrealmeid@igalia.com> wrote:
>
> To add overlayfs support casefold filesystems, create a new function
> ovl_casefold(), to be able to do case-insensitive strncmp().
>
> ovl_casefold() allocates a new buffer and stores the casefolded version
> of the string on it. If the allocation or the casefold operation fails,
> fallback to use the original string. The caller of the function is
> responsible of freeing the buffer.
>
> The other string to be compared is casefolded in a previous step and
> stored at `struct ovl_cache_entry` member `char *cf_name`.
>
> Finally, set the strncmp() parameters to the casefold versions of the
> names to achieve case-insensitive support.
>
> For the non-casefold names, nothing changes and the rb_tree
> search/insert functions just ignores this change.
>
> Signed-off-by: André Almeida <andrealmeid@igalia.com>
> ---
> Changes from v2:
> - Refactor the patch to do a single kmalloc() per rb_tree operation
> - Instead of casefolding the cache entry name everytime per strncmp(),
> casefold it once and reuse it for every strncmp().
> ---
> fs/overlayfs/readdir.c | 92 +++++++++++++++++++++++++++++++++++++++++++-------
> 1 file changed, 80 insertions(+), 12 deletions(-)
>
> diff --git a/fs/overlayfs/readdir.c b/fs/overlayfs/readdir.c
> index 2f42fec97f76c2000f76e15c60975db567b2c6d6..422f991393dfae12bcacf326414b7ee19e486ac8 100644
> --- a/fs/overlayfs/readdir.c
> +++ b/fs/overlayfs/readdir.c
> @@ -71,20 +71,58 @@ static struct ovl_cache_entry *ovl_cache_entry_from_node(struct rb_node *n)
> return rb_entry(n, struct ovl_cache_entry, node);
> }
>
> +static int ovl_casefold(struct unicode_map *map, const char *str, int len, char **dst)
> +{
> +#if IS_ENABLED(CONFIG_UNICODE)
> + const struct qstr qstr = { .name = str, .len = len };
> + int cf_len;
> +
> + if (!map || is_dot_dotdot(str, len))
if (!IS_ENABLED(CONFIG_UNICODE) || !map ||...
> + return -1;
Better return 0 for no casefolding.
> +
> + *dst = kmalloc(OVL_NAME_LEN, GFP_KERNEL);
> +
> + if (dst) {
> + cf_len = utf8_casefold(map, &qstr, *dst, OVL_NAME_LEN);
> +
> + if (cf_len > 0)
> + return cf_len;
need to kfree(*dst) in the error case
caller only responsible of free on success
> + }
> +#endif
> +
> + return -1;
Better return 0 for no casefolding.
> +}
> +
> static bool ovl_cache_entry_find_link(const char *name, int len,
> struct rb_node ***link,
> - struct rb_node **parent)
> + struct rb_node **parent,
> + struct unicode_map *map)
> {
> + int ret;
> + char *dst = NULL;
> bool found = false;
> + const char *str = name;
> struct rb_node **newp = *link;
>
> + ret = ovl_casefold(map, name, len, &dst);
> +
> + if (ret > 0) {
> + str = dst;
> + len = ret;
> + }
> +
> while (!found && *newp) {
> int cmp;
> + char *aux;
> struct ovl_cache_entry *tmp;
>
> *parent = *newp;
> +
> tmp = ovl_cache_entry_from_node(*newp);
> - cmp = strncmp(name, tmp->name, len);
> +
> + aux = tmp->cf_name ? tmp->cf_name : tmp->name;
> +
> + cmp = strncmp(str, aux, len);
> if (cmp > 0)
> newp = &tmp->node.rb_right;
> else if (cmp < 0 || len < tmp->len)
> @@ -94,27 +132,50 @@ static bool ovl_cache_entry_find_link(const char *name, int len,
> }
> *link = newp;
>
> + kfree(dst);
> +
> return found;
> }
>
> static struct ovl_cache_entry *ovl_cache_entry_find(struct rb_root *root,
> - const char *name, int len)
> + const char *name, int len,
> + struct unicode_map *map)
> {
> struct rb_node *node = root->rb_node;
> - int cmp;
> + struct ovl_cache_entry *p;
> + const char *str = name;
> + bool found = false;
> + char *dst = NULL;
> + int cmp, ret;
> +
> + ret = ovl_casefold(map, name, len, &dst);
> +
> + if (ret > 0) {
> + str = dst;
> + len = ret;
> + }
> +
> + while (!found && node) {
> + char *aux;
>
> - while (node) {
> - struct ovl_cache_entry *p = ovl_cache_entry_from_node(node);
> + p = ovl_cache_entry_from_node(node);
>
> - cmp = strncmp(name, p->name, len);
> + aux = p->cf_name ? p->cf_name : p->name;
> +
> + cmp = strncmp(str, aux, len);
> if (cmp > 0)
> node = p->node.rb_right;
> else if (cmp < 0 || len < p->len)
> node = p->node.rb_left;
> else
> - return p;
> + found = true;
> }
>
> + kfree(dst);
> +
> + if (found)
> + return p;
> +
> return NULL;
> }
>
> @@ -212,7 +273,7 @@ static bool ovl_cache_entry_add_rb(struct ovl_readdir_data *rdd,
> struct rb_node *parent = NULL;
> struct ovl_cache_entry *p;
>
> - if (ovl_cache_entry_find_link(name, len, &newp, &parent))
> + if (ovl_cache_entry_find_link(name, len, &newp, &parent, rdd->map))
> return true;
>
> p = ovl_cache_entry_new(rdd, name, len, ino, d_type);
> @@ -234,7 +295,7 @@ static bool ovl_fill_lowest(struct ovl_readdir_data *rdd,
> {
> struct ovl_cache_entry *p;
>
> - p = ovl_cache_entry_find(rdd->root, name, namelen);
> + p = ovl_cache_entry_find(rdd->root, name, namelen, rdd->map);
> if (p) {
> list_move_tail(&p->l_node, &rdd->middle);
> } else {
IMO, it is nicer to pass the cf_name to the low level rb tree lookup
helpers and call ovl_casefold() only in high level ovl_fill_merge().
This also deduplicates the code from patch 1 of storing the cf_name in
ovl_cache_entry_new().
something like this (untested) WDYT?
diff --git a/fs/overlayfs/readdir.c b/fs/overlayfs/readdir.c
index b65cdfce31ce..71efb29ad30f 100644
--- a/fs/overlayfs/readdir.c
+++ b/fs/overlayfs/readdir.c
@@ -79,10 +79,10 @@ static bool ovl_cache_entry_find_link(const char
*name, int len,
*parent = *newp;
tmp = ovl_cache_entry_from_node(*newp);
- cmp = strncmp(name, tmp->name, len);
+ cmp = strncmp(name, tmp->cf_name, len);
if (cmp > 0)
newp = &tmp->node.rb_right;
- else if (cmp < 0 || len < tmp->len)
+ else if (cmp < 0 || len < tmp->cf_len)
newp = &tmp->node.rb_left;
else
found = true;
@@ -101,10 +101,10 @@ static struct ovl_cache_entry
*ovl_cache_entry_find(struct rb_root *root,
while (node) {
struct ovl_cache_entry *p = ovl_cache_entry_from_node(node);
- cmp = strncmp(name, p->name, len);
+ cmp = strncmp(name, p->cf_name, len);
if (cmp > 0)
node = p->node.rb_right;
- else if (cmp < 0 || len < p->len)
+ else if (cmp < 0 || len < p->cf_len)
node = p->node.rb_left;
else
return p;
@@ -145,6 +145,7 @@ static bool ovl_calc_d_ino(struct ovl_readdir_data *rdd,
static struct ovl_cache_entry *ovl_cache_entry_new(struct
ovl_readdir_data *rdd,
const char *name, int len,
+ const char
*cf_name, int cf_len,
u64 ino, unsigned int d_type)
{
struct ovl_cache_entry *p;
@@ -156,6 +157,13 @@ static struct ovl_cache_entry
*ovl_cache_entry_new(struct ovl_readdir_data *rdd,
memcpy(p->name, name, len);
p->name[len] = '\0';
p->len = len;
+ if (cf_name && cf_name != name) {
+ p->cf_name == cf_name
+ p->cf_len = cf_len;
+ } else {
+ p->cf_name = p->name;
+ p->cf_len = len;
+ }
p->type = d_type;
p->real_ino = ino;
p->ino = ino;
@@ -175,17 +183,18 @@ static struct ovl_cache_entry
*ovl_cache_entry_new(struct ovl_readdir_data *rdd,
}
static bool ovl_cache_entry_add_rb(struct ovl_readdir_data *rdd,
- const char *name, int len, u64 ino,
- unsigned int d_type)
+ const char *name, int len,
+ const char *cf_name, int cf_len,
+ u64 ino, unsigned int d_type)
{
struct rb_node **newp = &rdd->root->rb_node;
struct rb_node *parent = NULL;
struct ovl_cache_entry *p;
- if (ovl_cache_entry_find_link(name, len, &newp, &parent))
+ if (ovl_cache_entry_find_link(cf_name, cf_len, &newp, &parent))
return true;
- p = ovl_cache_entry_new(rdd, name, len, ino, d_type);
+ p = ovl_cache_entry_new(rdd, name, len, cf_name, cf_len, ino, d_type);
if (p == NULL) {
rdd->err = -ENOMEM;
return false;
@@ -200,15 +209,17 @@ static bool ovl_cache_entry_add_rb(struct
ovl_readdir_data *rdd,
static bool ovl_fill_lowest(struct ovl_readdir_data *rdd,
const char *name, int namelen,
+ const char *cf_name, int cf_len,
loff_t offset, u64 ino, unsigned int d_type)
{
struct ovl_cache_entry *p;
- p = ovl_cache_entry_find(rdd->root, name, namelen);
+ p = ovl_cache_entry_find(rdd->root, cf_name, cf_len);
if (p) {
list_move_tail(&p->l_node, &rdd->middle);
} else {
- p = ovl_cache_entry_new(rdd, name, namelen, ino, d_type);
+ p = ovl_cache_entry_new(rdd, name, namelen, cf_name, cf_len,
+ ino, d_type);
if (p == NULL)
rdd->err = -ENOMEM;
else
@@ -223,8 +234,11 @@ void ovl_cache_free(struct list_head *list)
struct ovl_cache_entry *p;
struct ovl_cache_entry *n;
- list_for_each_entry_safe(p, n, list, l_node)
+ list_for_each_entry_safe(p, n, list, l_node) {
+ if (p->cf_name != p->name)
+ kfree(p->cf_name);
kfree(p);
+ }
INIT_LIST_HEAD(list);
}
@@ -260,12 +274,26 @@ static bool ovl_fill_merge(struct dir_context
*ctx, const char *name,
{
struct ovl_readdir_data *rdd =
container_of(ctx, struct ovl_readdir_data, ctx);
+ char *cf_name;
+ int cf_len = 0;
+
+ if (ofs->casefold)
+ cf_len = ovl_casefold(rdd->map, name, namelen, &cf_name);
+
+ /* lookup in rb tree by casefolded name or orig name */
+ if (cf_len <= 0) {
+ cf_name = name;
+ cf_len = namelen;
+ }
rdd->count++;
- if (!rdd->is_lowest)
- return ovl_cache_entry_add_rb(rdd, name, namelen, ino, d_type);
- else
- return ovl_fill_lowest(rdd, name, namelen, offset, ino, d_type);
+ if (!rdd->is_lowest) {
+ return ovl_cache_entry_add_rb(rdd, name, namelen,
cf_name, cf_len,
+ ino, d_type);
+ } else {
+ return ovl_fill_lowest(rdd, name, namelen, cf_name, cf_len,
+ offset, ino, d_type);
+ }
}
static int ovl_check_whiteouts(const struct path *path, struct
ovl_readdir_data *rdd)
@@ -555,7 +583,7 @@ static bool ovl_fill_plain(struct dir_context
*ctx, const char *name,
container_of(ctx, struct ovl_readdir_data, ctx);
rdd->count++;
- p = ovl_cache_entry_new(rdd, name, namelen, ino, d_type);
+ p = ovl_cache_entry_new(rdd, name, namelen, NULL, 0, ino, d_type);
if (p == NULL) {
rdd->err = -ENOMEM;
return false;
> @@ -640,7 +701,8 @@ static int ovl_dir_read_impure(const struct path *path, struct list_head *list,
All the changes below this point related to code paths of
ovl_iterate_real(), ovl_dir_read_impure() and struct ovl_readdir_translate
are irrelevant to casefolding and not needed.
This code iterates a single layer "real directory" and "translates" real ino to
ovl ino in some cases, but it does not compare any names between layers.
Thanks,
Amir.
^ permalink raw reply related [flat|nested] 18+ messages in thread
* Re: [PATCH RFC v3 0/7] ovl: Enable support for casefold filesystems
2025-08-09 9:17 ` [PATCH RFC v3 0/7] ovl: Enable support for casefold filesystems Amir Goldstein
@ 2025-08-09 16:08 ` Amir Goldstein
0 siblings, 0 replies; 18+ messages in thread
From: Amir Goldstein @ 2025-08-09 16:08 UTC (permalink / raw)
To: André Almeida
Cc: Miklos Szeredi, Theodore Tso, Gabriel Krisman Bertazi,
linux-unionfs, linux-kernel, linux-fsdevel, Alexander Viro,
Christian Brauner, Jan Kara, kernel-dev
On Sat, Aug 9, 2025 at 11:17 AM Amir Goldstein <amir73il@gmail.com> wrote:
>
> On Fri, Aug 8, 2025 at 10:59 PM André Almeida <andrealmeid@igalia.com> wrote:
> >
> > Hi all,
> >
> > We would like to support the usage of casefold filesystems with
> > overlayfs to be used with container tools. This use case requires a
> > simple setup, where every layer will have the same encoding setting
> > (i.e. Unicode version and flags), using one upper and one lower layer.
> >
> > * Implementation
> >
> > When merge layers, ovl uses a red-black tree to check if a given dentry
> > name from a lower layers already exists in the upper layer. For merging
> > case-insensitive names, we need to store then in tree casefolded.
> > However, when displaying to the user the dentry name, we need to respect
> > the name chosen when the file was created (e.g. Picture.PNG, instead of
> > picture.png). To achieve this, I create a new field for cache entries
> > that stores the casefolded names and a function ovl_strcmp() that uses
> > this name for searching the rb_tree. For composing the layer, ovl uses
> > the original name, keeping it consistency with whatever name the user
> > created.
> >
> > The rest of the patches are mostly for checking if casefold is being
> > consistently used across the layers and dropping the mount restrictions
> > that prevented case-insensitive filesystems to be mounted.
> >
> > Thanks for the feedback!
>
> Hi Andre,
>
> This v3 is getting close.
> Still some small details to fix.
> With v4, please include a link to fstest so I can test your work.
> I already had a draft for the casefold capable and disabled layers
> in 6.17 but I did not post it yet.
> Decide it would be better to test the final result after your work.
> I'd started to adapt the test to the expected behavior for 6.18,
> but it's just an untested draft for you to extend:
> https://github.com/amir73il/xfstests/commits/ovl-casefold/
>
> Please make sure that you run at least check -overlay -g overlay/quick
> sanity check for v4 and that you run check -g casefold,
> which should run the new generic test.
>
Also, please remove the RFC label for v4.
We are pass the point of RFC.
Thanks,
Amir.
^ permalink raw reply [flat|nested] 18+ messages in thread
* Re: [PATCH RFC v3 6/7] ovl: Add S_CASEFOLD as part of the inode flag to be copied
2025-08-09 9:51 ` Amir Goldstein
@ 2025-08-10 13:02 ` Amir Goldstein
0 siblings, 0 replies; 18+ messages in thread
From: Amir Goldstein @ 2025-08-10 13:02 UTC (permalink / raw)
To: André Almeida
Cc: Miklos Szeredi, Theodore Tso, Gabriel Krisman Bertazi,
linux-unionfs, linux-kernel, linux-fsdevel, Alexander Viro,
Christian Brauner, Jan Kara, kernel-dev
On Sat, Aug 9, 2025 at 11:51 AM Amir Goldstein <amir73il@gmail.com> wrote:
>
> On Fri, Aug 8, 2025 at 10:59 PM André Almeida <andrealmeid@igalia.com> wrote:
> >
> > To keep ovl's inodes consistent with their real inodes, add the
> > S_CASEFOLD flag as part of the flags that need to be copied.
> >
> > Signed-off-by: André Almeida <andrealmeid@igalia.com>
> > ---
> > Changes from v2:
> > - Instead of manually setting the flag if the realpath dentry is
> > casefolded, just add this flag as part of the flags that need to be
> > copied.
> > ---
> > fs/overlayfs/overlayfs.h | 2 +-
> > 1 file changed, 1 insertion(+), 1 deletion(-)
> >
> > diff --git a/fs/overlayfs/overlayfs.h b/fs/overlayfs/overlayfs.h
> > index bb0d7ded8e763a4a7a6fc506d966ed2f3bdb4f06..8a9a67d2933173c61b0fa0af5634d91e092e00b2 100644
> > --- a/fs/overlayfs/overlayfs.h
> > +++ b/fs/overlayfs/overlayfs.h
> > @@ -822,7 +822,7 @@ struct inode *ovl_get_inode(struct super_block *sb,
> > void ovl_copyattr(struct inode *to);
> >
> > /* vfs inode flags copied from real to ovl inode */
> > -#define OVL_COPY_I_FLAGS_MASK (S_SYNC | S_NOATIME | S_APPEND | S_IMMUTABLE)
> > +#define OVL_COPY_I_FLAGS_MASK (S_SYNC | S_NOATIME | S_APPEND | S_IMMUTABLE | S_CASEFOLD)
> > /* vfs inode flags read from overlay.protattr xattr to ovl inode */
> > #define OVL_PROT_I_FLAGS_MASK (S_APPEND | S_IMMUTABLE)
> >
>
> Ok, this is simpler, but it's too simple.
> OVL_COPY_I_FLAGS_MASK is used in copy up with the assumption that
> all copied i_flags are related to fileattr flags, so you need something like:
>
> diff --git a/fs/overlayfs/copy_up.c b/fs/overlayfs/copy_up.c
> index 27396fe63f6d..66bd43a99d2e 100644
> --- a/fs/overlayfs/copy_up.c
> +++ b/fs/overlayfs/copy_up.c
> @@ -670,7 +670,7 @@ static int ovl_copy_up_metadata(struct
> ovl_copy_up_ctx *c, struct dentry *temp)
> if (err)
> return err;
>
> - if (inode->i_flags & OVL_COPY_I_FLAGS_MASK &&
> + if (inode->i_flags & OVL_FATTR_I_FLAGS_MASK &&
> (S_ISREG(c->stat.mode) || S_ISDIR(c->stat.mode))) {
> /*
> * Copy the fileattr inode flags that are the source of already
Which reminds me that you also need to verify that a copied up directory
conforms to the ofs->casefold expectation, because there is no code
to make the copied up directory casefolded.
We can assume that layers check has already verified that upperdir/workdir
are casefold correct, but we need to verify that $workdir/work/$tmpdir created
by ovl_create_temp() has inherited the expected casefolding.
Same goes for ovl_mkdir(), we must verify that the new created dentry/inode
conform to the expected ofs->casefold.
I think this check in ovl_create_real() should cover both cases:
diff --git a/fs/overlayfs/dir.c b/fs/overlayfs/dir.c
index 70b8687dc45e..be8c5d02302d 100644
--- a/fs/overlayfs/dir.c
+++ b/fs/overlayfs/dir.c
@@ -187,6 +187,11 @@ struct dentry *ovl_create_real(struct ovl_fs
*ofs, struct dentry *parent,
/* mkdir is special... */
newdentry = ovl_do_mkdir(ofs, dir, newdentry,
attr->mode);
err = PTR_ERR_OR_ZERO(newdentry);
+ /* expect to inherit casefolding from
workdir/upperdir */
+ if (!err && ofs->casefold !=
ovl_dentry_casefolded(newdentry)) {
+ dput(newdentry);
+ err = -EINVAL;
+ }
break;
Thanks,
Amir.
^ permalink raw reply related [flat|nested] 18+ messages in thread
end of thread, other threads:[~2025-08-10 13:02 UTC | newest]
Thread overview: 18+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2025-08-08 20:58 [PATCH RFC v3 0/7] ovl: Enable support for casefold filesystems André Almeida
2025-08-08 20:58 ` [PATCH RFC v3 1/7] ovl: Store casefold name for case-insentive dentries André Almeida
2025-08-09 12:36 ` Amir Goldstein
2025-08-08 20:58 ` [PATCH RFC v3 2/7] ovl: Create ovl_casefold() to support casefolded strncmp() André Almeida
2025-08-09 15:13 ` Amir Goldstein
2025-08-08 20:58 ` [PATCH RFC v3 3/7] fs: Create sb_same_encoding() helper André Almeida
2025-08-09 10:01 ` Amir Goldstein
2025-08-08 20:58 ` [PATCH RFC v3 4/7] ovl: Ensure that all mount points have the same encoding André Almeida
2025-08-09 10:07 ` Amir Goldstein
2025-08-08 20:58 ` [PATCH RFC v3 5/7] ovl: Set case-insensitive dentry operations for ovl sb André Almeida
2025-08-09 11:50 ` Amir Goldstein
2025-08-08 20:58 ` [PATCH RFC v3 6/7] ovl: Add S_CASEFOLD as part of the inode flag to be copied André Almeida
2025-08-09 9:51 ` Amir Goldstein
2025-08-10 13:02 ` Amir Goldstein
2025-08-08 20:58 ` [PATCH RFC v3 7/7] ovl: Support case-insensitive lookup André Almeida
2025-08-09 10:45 ` Amir Goldstein
2025-08-09 9:17 ` [PATCH RFC v3 0/7] ovl: Enable support for casefold filesystems Amir Goldstein
2025-08-09 16:08 ` Amir Goldstein
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).