mount -t overlay overlay -olowerdir=/a\:lower\:\:dir /merged
 
-Since kernel version v6.5, directory names containing colons can also
-be provided as lower layer using the fsconfig syscall from new mount api:
+Since kernel version v6.8, directory names containing colons can also
+be configured as lower layer using the "lowerdir+" mount options and the
+fsconfig syscall from new mount api.  For example:
 
-  fsconfig(fs_fd, FSCONFIG_SET_STRING, "lowerdir", "/a:lower::dir", 0);
+  fsconfig(fs_fd, FSCONFIG_SET_STRING, "lowerdir+", "/a:lower::dir", 0);
 
 In the latter case, colons in lower layer directory names will be escaped
 as an octal characters (\072) when displayed in /proc/self/mountinfo.
 when a "metacopy" file in one of the lower layers above it, has a "redirect"
 to the absolute path of the "lower data" file in the "data-only" lower layer.
 
+Since kernel version v6.8, "data-only" lower layers can also be added using
+the "datadir+" mount options and the fsconfig syscall from new mount api.
+For example:
+
+  fsconfig(fs_fd, FSCONFIG_SET_STRING, "lowerdir+", "/l1", 0);
+  fsconfig(fs_fd, FSCONFIG_SET_STRING, "lowerdir+", "/l2", 0);
+  fsconfig(fs_fd, FSCONFIG_SET_STRING, "lowerdir+", "/l3", 0);
+  fsconfig(fs_fd, FSCONFIG_SET_STRING, "datadir+", "/do1", 0);
+  fsconfig(fs_fd, FSCONFIG_SET_STRING, "datadir+", "/do2", 0);
+
 
 fs-verity support
 ----------------------
 
 
 enum ovl_opt {
        Opt_lowerdir,
+       Opt_lowerdir_add,
+       Opt_datadir_add,
        Opt_upperdir,
        Opt_workdir,
        Opt_default_permissions,
 #define fsparam_string_empty(NAME, OPT) \
        __fsparam(fs_param_is_string, NAME, OPT, fs_param_can_be_empty, NULL)
 
+
 const struct fs_parameter_spec ovl_parameter_spec[] = {
        fsparam_string_empty("lowerdir",    Opt_lowerdir),
+       fsparam_string("lowerdir+",         Opt_lowerdir_add),
+       fsparam_string("datadir+",          Opt_datadir_add),
        fsparam_string("upperdir",          Opt_upperdir),
        fsparam_string("workdir",           Opt_workdir),
        fsparam_flag("default_permissions", Opt_default_permissions),
 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;
+
        if (ovl_dentry_weird(path->dentry))
                return invalfc(fc, "filesystem on %s not supported", name);
 
        if (!d_is_dir(path->dentry))
                return invalfc(fc, "%s is not a directory", name);
 
+
        /*
         * Check whether upper path is read-only here to report failures
         * early. Don't forget to recheck when the superblock is created
                        return invalfc(fc, "filesystem on %s not supported as upperdir", name);
                if (__mnt_is_readonly(path->mnt))
                        return invalfc(fc, "filesystem on %s is read-only", name);
+       } else {
+               if (ctx->lowerdir_all && layer != Opt_lowerdir)
+                       return invalfc(fc, "lowerdir+ and datadir+ cannot follow lowerdir");
+               if (ctx->nr_data && layer == Opt_lowerdir_add)
+                       return invalfc(fc, "regular lower layers cannot follow data layers");
+               if (ctx->nr == OVL_MAX_STACK)
+                       return invalfc(fc, "too many lower directories, limit is %d",
+                                      OVL_MAX_STACK);
        }
        return 0;
 }
 
+static int ovl_ctx_realloc_lower(struct fs_context *fc)
+{
+       struct ovl_fs_context *ctx = fc->fs_private;
+       struct ovl_fs_context_layer *l;
+       size_t nr;
+
+       if (ctx->nr < ctx->capacity)
+               return 0;
+
+       nr = min_t(size_t, max(4096 / sizeof(*l), ctx->capacity * 2),
+                  OVL_MAX_STACK);
+       l = krealloc_array(ctx->lower, nr, sizeof(*l), GFP_KERNEL_ACCOUNT);
+       if (!l)
+               return -ENOMEM;
+
+       ctx->lower = l;
+       ctx->capacity = nr;
+       return 0;
+}
+
 static void ovl_add_layer(struct fs_context *fc, enum ovl_opt layer,
                         struct path *path, char **pname)
 {
        struct ovl_fs *ofs = fc->s_fs_info;
        struct ovl_config *config = &ofs->config;
        struct ovl_fs_context *ctx = fc->fs_private;
+       struct ovl_fs_context_layer *l;
 
        switch (layer) {
        case Opt_workdir:
                swap(config->upperdir, *pname);
                swap(ctx->upper, *path);
                break;
+       case Opt_datadir_add:
+               ctx->nr_data++;
+               fallthrough;
+       case Opt_lowerdir_add:
+               WARN_ON(ctx->nr >= ctx->capacity);
+               l = &ctx->lower[ctx->nr++];
+               memset(l, 0, sizeof(*l));
+               swap(l->name, *pname);
+               swap(l->path, *path);
+               break;
        default:
                WARN_ON(1);
        }
        if (!name)
                return -ENOMEM;
 
-       err = ovl_mount_dir(name, &path);
+       if (upper)
+               err = ovl_mount_dir(name, &path);
+       else
+               err = ovl_mount_dir_noesc(name, &path);
        if (err)
                goto out_free;
 
        if (err)
                goto out_put;
 
+       if (!upper) {
+               err = ovl_ctx_realloc_lower(fc);
+               if (err)
+                       goto out_put;
+       }
+
        /* Store the user provided path string in ctx to show in mountinfo */
        ovl_add_layer(fc, layer, &path, &name);
 
        case Opt_lowerdir:
                err = ovl_parse_param_lowerdir(param->string, fc);
                break;
+       case Opt_lowerdir_add:
+       case Opt_datadir_add:
        case Opt_upperdir:
        case Opt_workdir:
                err = ovl_parse_layer(fc, param, opt);
 {
        struct super_block *sb = dentry->d_sb;
        struct ovl_fs *ofs = OVL_FS(sb);
+       size_t nr, nr_merged_lower, nr_lower = 0;
        char **lowerdirs = ofs->config.lowerdirs;
 
        /*
         * lowerdirs[0] holds the colon separated list that user provided
         * with lowerdir mount option.
+        * lowerdirs[1..numlayer] hold the lowerdir paths that were added
+        * using the lowerdir+ and datadir+ mount options.
+        * For now, we do not allow mixing the legacy lowerdir mount option
+        * with the new lowerdir+ and datadir+ mount options.
         */
-       seq_show_option(m, "lowerdir", lowerdirs[0]);
+       if (lowerdirs[0]) {
+               seq_show_option(m, "lowerdir", lowerdirs[0]);
+       } else {
+               nr_lower = ofs->numlayer;
+               nr_merged_lower = nr_lower - ofs->numdatalayer;
+       }
+       for (nr = 1; nr < nr_lower; nr++) {
+               if (nr < nr_merged_lower)
+                       seq_show_option(m, "lowerdir+", lowerdirs[nr]);
+               else
+                       seq_show_option(m, "datadir+", lowerdirs[nr]);
+       }
        if (ofs->config.upperdir) {
                seq_show_option(m, "upperdir", ofs->config.upperdir);
                seq_show_option(m, "workdir", ofs->config.workdir);