]> www.infradead.org Git - users/hch/misc.git/commitdiff
ovl: Create ovl_casefold() to support casefolded strncmp()
authorAndré Almeida <andrealmeid@igalia.com>
Fri, 22 Aug 2025 14:17:07 +0000 (11:17 -0300)
committerAmir Goldstein <amir73il@gmail.com>
Tue, 23 Sep 2025 10:29:35 +0000 (12:29 +0200)
To add overlayfs support casefold layers, 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 case-insentive name is then used in the rb-tree search/insertion
operation. If the name is found in the rb-tree, the name can be
discarded and the buffer is freed. If the name isn't found, it's then
stored at struct ovl_cache_entry to be used later.

Reviewed-by: Amir Goldstein <amir73il@gmail.com>
Signed-off-by: André Almeida <andrealmeid@igalia.com>
Signed-off-by: Amir Goldstein <amir73il@gmail.com>
fs/overlayfs/readdir.c

index b65cdfce31ce27172d28d879559f1008b9c87320..1d58f618962ffac4d5941a54c29459d31b202626 100644 (file)
@@ -27,6 +27,8 @@ struct ovl_cache_entry {
        bool is_upper;
        bool is_whiteout;
        bool check_xwhiteout;
+       const char *c_name;
+       int c_len;
        char name[];
 };
 
@@ -45,6 +47,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;
@@ -66,6 +69,31 @@ 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 ovl_readdir_data *rdd, const char *str, int len,
+                       char **dst)
+{
+       const struct qstr qstr = { .name = str, .len = len };
+       char *cf_name;
+       int cf_len;
+
+       if (!IS_ENABLED(CONFIG_UNICODE) || !rdd->map || is_dot_dotdot(str, len))
+               return 0;
+
+       cf_name = kmalloc(NAME_MAX, GFP_KERNEL);
+       if (!cf_name) {
+               rdd->err = -ENOMEM;
+               return -ENOMEM;
+       }
+
+       cf_len = utf8_casefold(rdd->map, &qstr, cf_name, NAME_MAX);
+       if (cf_len > 0)
+               *dst = cf_name;
+       else
+               kfree(cf_name);
+
+       return cf_len;
+}
+
 static bool ovl_cache_entry_find_link(const char *name, int len,
                                      struct rb_node ***link,
                                      struct rb_node **parent)
@@ -79,10 +107,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->c_name, len);
                if (cmp > 0)
                        newp = &tmp->node.rb_right;
-               else if (cmp < 0 || len < tmp->len)
+               else if (cmp < 0 || len < tmp->c_len)
                        newp = &tmp->node.rb_left;
                else
                        found = true;
@@ -101,10 +129,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->c_name, len);
                if (cmp > 0)
                        node = p->node.rb_right;
-               else if (cmp < 0 || len < p->len)
+               else if (cmp < 0 || len < p->c_len)
                        node = p->node.rb_left;
                else
                        return p;
@@ -145,6 +173,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 *c_name, int c_len,
                                                   u64 ino, unsigned int d_type)
 {
        struct ovl_cache_entry *p;
@@ -167,6 +196,14 @@ static struct ovl_cache_entry *ovl_cache_entry_new(struct ovl_readdir_data *rdd,
        /* Defer check for overlay.whiteout to ovl_iterate() */
        p->check_xwhiteout = rdd->in_xwhiteouts_dir && d_type == DT_REG;
 
+       if (c_name && c_name != name) {
+               p->c_name = c_name;
+               p->c_len = c_len;
+       } else {
+               p->c_name = p->name;
+               p->c_len = len;
+       }
+
        if (d_type == DT_CHR) {
                p->next_maybe_whiteout = rdd->first_maybe_whiteout;
                rdd->first_maybe_whiteout = p;
@@ -174,48 +211,62 @@ static struct ovl_cache_entry *ovl_cache_entry_new(struct ovl_readdir_data *rdd,
        return p;
 }
 
-static bool ovl_cache_entry_add_rb(struct ovl_readdir_data *rdd,
-                                 const char *name, int len, u64 ino,
+/* Return 0 for found, 1 for added, <0 for error */
+static int ovl_cache_entry_add_rb(struct ovl_readdir_data *rdd,
+                                 const char *name, int len,
+                                 const char *c_name, int c_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))
-               return true;
+       if (ovl_cache_entry_find_link(c_name, c_len, &newp, &parent))
+               return 0;
 
-       p = ovl_cache_entry_new(rdd, name, len, ino, d_type);
+       p = ovl_cache_entry_new(rdd, name, len, c_name, c_len, ino, d_type);
        if (p == NULL) {
                rdd->err = -ENOMEM;
-               return false;
+               return -ENOMEM;
        }
 
        list_add_tail(&p->l_node, rdd->list);
        rb_link_node(&p->node, parent, newp);
        rb_insert_color(&p->node, rdd->root);
 
-       return true;
+       return 1;
 }
 
-static bool ovl_fill_lowest(struct ovl_readdir_data *rdd,
+/* Return 0 for found, 1 for added, <0 for error */
+static int ovl_fill_lowest(struct ovl_readdir_data *rdd,
                           const char *name, int namelen,
+                          const char *c_name, int c_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, c_name, c_len);
        if (p) {
                list_move_tail(&p->l_node, &rdd->middle);
+               return 0;
        } else {
-               p = ovl_cache_entry_new(rdd, name, namelen, ino, d_type);
+               p = ovl_cache_entry_new(rdd, name, namelen, c_name, c_len,
+                                       ino, d_type);
                if (p == NULL)
                        rdd->err = -ENOMEM;
                else
                        list_add_tail(&p->l_node, &rdd->middle);
        }
 
-       return rdd->err == 0;
+       return rdd->err ?: 1;
+}
+
+static void ovl_cache_entry_free(struct ovl_cache_entry *p)
+{
+       if (p->c_name != p->name)
+               kfree(p->c_name);
+       kfree(p);
 }
 
 void ovl_cache_free(struct list_head *list)
@@ -224,7 +275,7 @@ void ovl_cache_free(struct list_head *list)
        struct ovl_cache_entry *n;
 
        list_for_each_entry_safe(p, n, list, l_node)
-               kfree(p);
+               ovl_cache_entry_free(p);
 
        INIT_LIST_HEAD(list);
 }
@@ -260,12 +311,39 @@ 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);
+       struct ovl_fs *ofs = OVL_FS(rdd->dentry->d_sb);
+       const char *c_name = NULL;
+       char *cf_name = NULL;
+       int c_len = 0, ret;
+
+       if (ofs->casefold)
+               c_len = ovl_casefold(rdd, name, namelen, &cf_name);
+
+       if (rdd->err)
+               return false;
+
+       if (c_len <= 0) {
+               c_name = name;
+               c_len = namelen;
+       } else {
+               c_name = cf_name;
+       }
 
        rdd->count++;
        if (!rdd->is_lowest)
-               return ovl_cache_entry_add_rb(rdd, name, namelen, ino, d_type);
+               ret = ovl_cache_entry_add_rb(rdd, name, namelen, c_name, c_len, ino, d_type);
        else
-               return ovl_fill_lowest(rdd, name, namelen, offset, ino, d_type);
+               ret = ovl_fill_lowest(rdd, name, namelen, c_name, c_len, offset, ino, d_type);
+
+       /*
+        * If ret == 1, that means that c_name is being used as part of struct
+        * ovl_cache_entry and will be freed at ovl_cache_free(). Otherwise,
+        * c_name was found in the rb-tree so we can free it here.
+        */
+       if (ret != 1 && c_name != name)
+               kfree(c_name);
+
+       return ret >= 0;
 }
 
 static int ovl_check_whiteouts(const struct path *path, struct ovl_readdir_data *rdd)
@@ -357,12 +435,18 @@ 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;
+       struct ovl_fs *ofs = OVL_FS(dentry->d_sb);
 
        for (idx = 0; idx != -1; idx = next) {
                next = ovl_path_next(idx, dentry, &realpath, &layer);
+
+               if (ofs->casefold)
+                       rdd.map = sb_encoding(realpath.dentry->d_sb);
+
                rdd.is_upper = ovl_dentry_upper(dentry) == realpath.dentry;
                rdd.in_xwhiteouts_dir = layer->has_xwhiteouts &&
                                        ovl_dentry_has_xwhiteouts(dentry);
@@ -555,7 +639,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;
@@ -595,7 +679,7 @@ static int ovl_dir_read_impure(const struct path *path,  struct list_head *list,
                }
                if (p->ino == p->real_ino) {
                        list_del(&p->l_node);
-                       kfree(p);
+                       ovl_cache_entry_free(p);
                } else {
                        struct rb_node **newp = &root->rb_node;
                        struct rb_node *parent = NULL;
@@ -1023,7 +1107,7 @@ int ovl_check_empty_dir(struct dentry *dentry, struct list_head *list)
 
 del_entry:
                list_del(&p->l_node);
-               kfree(p);
+               ovl_cache_entry_free(p);
        }
 
        return err;