]> git.ipfire.org Git - thirdparty/kernel/linux.git/blobdiff - fs/overlayfs/params.c
ovl: refactor layer parsing helpers
[thirdparty/kernel/linux.git] / fs / overlayfs / params.c
index 95b751507ac87f15733f2f38a05bf418e7b62093..88725982b6dd3f1e3232c578dd28e38328473ae4 100644 (file)
@@ -43,7 +43,7 @@ module_param_named(metacopy, ovl_metacopy_def, bool, 0644);
 MODULE_PARM_DESC(metacopy,
                 "Default to on or off for the metadata only copy up feature");
 
-enum {
+enum ovl_opt {
        Opt_lowerdir,
        Opt_upperdir,
        Opt_workdir,
@@ -157,6 +157,34 @@ const struct fs_parameter_spec ovl_parameter_spec[] = {
        {}
 };
 
+static char *ovl_next_opt(char **s)
+{
+       char *sbegin = *s;
+       char *p;
+
+       if (sbegin == NULL)
+               return NULL;
+
+       for (p = sbegin; *p; p++) {
+               if (*p == '\\') {
+                       p++;
+                       if (!*p)
+                               break;
+               } else if (*p == ',') {
+                       *p = '\0';
+                       *s = p + 1;
+                       return sbegin;
+               }
+       }
+       *s = NULL;
+       return sbegin;
+}
+
+static int ovl_parse_monolithic(struct fs_context *fc, void *data)
+{
+       return vfs_parse_monolithic_sep(fc, data, ovl_next_opt);
+}
+
 static ssize_t ovl_parse_param_split_lowerdirs(char *str)
 {
        ssize_t nr_layers = 1, nr_colons = 0;
@@ -164,7 +192,8 @@ static ssize_t ovl_parse_param_split_lowerdirs(char *str)
 
        for (s = d = str;; s++, d++) {
                if (*s == '\\') {
-                       s++;
+                       /* keep esc chars in split lowerdir */
+                       *d++ = *s++;
                } else if (*s == ':') {
                        bool next_colon = (*(s + 1) == ':');
 
@@ -209,19 +238,8 @@ static int ovl_mount_dir_noesc(const char *name, struct path *path)
                pr_err("failed to resolve '%s': %i\n", name, err);
                goto out;
        }
-       err = -EINVAL;
-       if (ovl_dentry_weird(path->dentry)) {
-               pr_err("filesystem on '%s' not supported\n", name);
-               goto out_put;
-       }
-       if (!d_is_dir(path->dentry)) {
-               pr_err("'%s' not a directory\n", name);
-               goto out_put;
-       }
        return 0;
 
-out_put:
-       path_put_init(path);
 out:
        return err;
 }
@@ -247,68 +265,96 @@ static int ovl_mount_dir(const char *name, struct path *path)
        if (tmp) {
                ovl_unescape(tmp);
                err = ovl_mount_dir_noesc(tmp, path);
-
-               if (!err && path->dentry->d_flags & DCACHE_OP_REAL) {
-                       pr_err("filesystem on '%s' not supported as upperdir\n",
-                              tmp);
-                       path_put_init(path);
-                       err = -EINVAL;
-               }
                kfree(tmp);
        }
        return err;
 }
 
-static int ovl_parse_param_upperdir(const char *name, struct fs_context *fc,
-                                   bool workdir)
+static int ovl_mount_dir_check(struct fs_context *fc, const struct path *path,
+                              enum ovl_opt layer, const char *name, bool upper)
 {
-       int err;
-       struct ovl_fs *ofs = fc->s_fs_info;
-       struct ovl_config *config = &ofs->config;
-       struct ovl_fs_context *ctx = fc->fs_private;
-       struct path path;
-       char *dup;
+       if (ovl_dentry_weird(path->dentry))
+               return invalfc(fc, "filesystem on %s not supported", name);
 
-       err = ovl_mount_dir(name, &path);
-       if (err)
-               return err;
+       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
         * as the mount attributes could change.
         */
-       if (__mnt_is_readonly(path.mnt)) {
-               path_put(&path);
-               return -EINVAL;
+       if (upper) {
+               if (path->dentry->d_flags & DCACHE_OP_REAL)
+                       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);
        }
+       return 0;
+}
 
-       dup = kstrdup(name, GFP_KERNEL);
-       if (!dup) {
-               path_put(&path);
-               return -ENOMEM;
-       }
+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;
 
-       if (workdir) {
-               kfree(config->workdir);
-               config->workdir = dup;
-               path_put(&ctx->work);
-               ctx->work = path;
-       } else {
-               kfree(config->upperdir);
-               config->upperdir = dup;
-               path_put(&ctx->upper);
-               ctx->upper = path;
+       switch (layer) {
+       case Opt_workdir:
+               swap(config->workdir, *pname);
+               swap(ctx->work, *path);
+               break;
+       case Opt_upperdir:
+               swap(config->upperdir, *pname);
+               swap(ctx->upper, *path);
+               break;
+       default:
+               WARN_ON(1);
        }
-       return 0;
 }
 
-static void ovl_parse_param_drop_lowerdir(struct ovl_fs_context *ctx)
+static int ovl_parse_layer(struct fs_context *fc, struct fs_parameter *param,
+                          enum ovl_opt layer)
 {
-       for (size_t nr = 0; nr < ctx->nr; nr++) {
-               path_put(&ctx->lower[nr].path);
-               kfree(ctx->lower[nr].name);
-               ctx->lower[nr].name = NULL;
+       char *name = kstrdup(param->string, GFP_KERNEL);
+       bool upper = (layer == Opt_upperdir || layer == Opt_workdir);
+       struct path path;
+       int err;
+
+       if (!name)
+               return -ENOMEM;
+
+       err = ovl_mount_dir(name, &path);
+       if (err)
+               goto out_free;
+
+       err = ovl_mount_dir_check(fc, &path, layer, name, upper);
+       if (err)
+               goto out_put;
+
+       /* Store the user provided path string in ctx to show in mountinfo */
+       ovl_add_layer(fc, layer, &path, &name);
+
+out_put:
+       path_put(&path);
+out_free:
+       kfree(name);
+       return err;
+}
+
+static void ovl_reset_lowerdirs(struct ovl_fs_context *ctx)
+{
+       struct ovl_fs_context_layer *l = ctx->lower;
+
+       // Reset old user provided lowerdir string
+       kfree(ctx->lowerdir_all);
+       ctx->lowerdir_all = NULL;
+
+       for (size_t nr = 0; nr < ctx->nr; nr++, l++) {
+               path_put(&l->path);
+               kfree(l->name);
+               l->name = NULL;
        }
        ctx->nr = 0;
        ctx->nr_data = 0;
@@ -317,25 +363,19 @@ static void ovl_parse_param_drop_lowerdir(struct ovl_fs_context *ctx)
 /*
  * Parse lowerdir= mount option:
  *
- * (1) lowerdir=/lower1:/lower2:/lower3::/data1::/data2
+ * e.g.: lowerdir=/lower1:/lower2:/lower3::/data1::/data2
  *     Set "/lower1", "/lower2", and "/lower3" as lower layers and
  *     "/data1" and "/data2" as data lower layers. Any existing lower
  *     layers are replaced.
- * (2) lowerdir=:/lower4
- *     Append "/lower4" to current stack of lower layers. This requires
- *     that there already is at least one lower layer configured.
- * (3) lowerdir=::/lower5
- *     Append data "/lower5" as data lower layer. This requires that
- *     there's at least one regular lower layer present.
  */
 static int ovl_parse_param_lowerdir(const char *name, struct fs_context *fc)
 {
        int err;
        struct ovl_fs_context *ctx = fc->fs_private;
        struct ovl_fs_context_layer *l;
-       char *dup = NULL, *dup_iter;
+       char *dup = NULL, *iter;
        ssize_t nr_lower = 0, nr = 0, nr_data = 0;
-       bool append = false, data_layer = false;
+       bool data_layer = false;
 
        /*
         * Ensure we're backwards compatible with mount(2)
@@ -343,56 +383,21 @@ static int ovl_parse_param_lowerdir(const char *name, struct fs_context *fc)
         */
 
        /* drop all existing lower layers */
-       if (!*name) {
-               ovl_parse_param_drop_lowerdir(ctx);
-               return 0;
-       }
-
-       if (strncmp(name, "::", 2) == 0) {
-               /*
-                * This is a data layer.
-                * There must be at least one regular lower layer
-                * specified.
-                */
-               if (ctx->nr == 0) {
-                       pr_err("data lower layers without regular lower layers not allowed");
-                       return -EINVAL;
-               }
+       ovl_reset_lowerdirs(ctx);
 
-               /* Skip the leading "::". */
-               name += 2;
-               data_layer = true;
-               /*
-                * A data layer is automatically an append as there
-                * must've been at least one regular lower layer.
-                */
-               append = true;
-       } else if (*name == ':') {
-               /*
-                * This is a regular lower layer.
-                * If users want to append a layer enforce that they
-                * have already specified a first layer before. It's
-                * better to be strict.
-                */
-               if (ctx->nr == 0) {
-                       pr_err("cannot append layer if no previous layer has been specified");
-                       return -EINVAL;
-               }
-
-               /*
-                * Once a sequence of data layers has started regular
-                * lower layers are forbidden.
-                */
-               if (ctx->nr_data > 0) {
-                       pr_err("regular lower layers cannot follow data lower layers");
-                       return -EINVAL;
-               }
+       if (!*name)
+               return 0;
 
-               /* Skip the leading ":". */
-               name++;
-               append = true;
+       if (*name == ':') {
+               pr_err("cannot append lower layer");
+               return -EINVAL;
        }
 
+       // Store user provided lowerdir string to show in mount options
+       ctx->lowerdir_all = kstrdup(name, GFP_KERNEL);
+       if (!ctx->lowerdir_all)
+               return -ENOMEM;
+
        dup = kstrdup(name, GFP_KERNEL);
        if (!dup)
                return -ENOMEM;
@@ -402,36 +407,11 @@ static int ovl_parse_param_lowerdir(const char *name, struct fs_context *fc)
        if (nr_lower < 0)
                goto out_err;
 
-       if ((nr_lower > OVL_MAX_STACK) ||
-           (append && (size_add(ctx->nr, nr_lower) > OVL_MAX_STACK))) {
+       if (nr_lower > OVL_MAX_STACK) {
                pr_err("too many lower directories, limit is %d\n", OVL_MAX_STACK);
                goto out_err;
        }
 
-       if (!append)
-               ovl_parse_param_drop_lowerdir(ctx);
-
-       /*
-        * (1) append
-        *
-        * We want nr <= nr_lower <= capacity We know nr > 0 and nr <=
-        * capacity. If nr == 0 this wouldn't be append. If nr +
-        * nr_lower is <= capacity then nr <= nr_lower <= capacity
-        * already holds. If nr + nr_lower exceeds capacity, we realloc.
-        *
-        * (2) replace
-        *
-        * Ensure we're backwards compatible with mount(2) which allows
-        * "lowerdir=/a:/b:/c,lowerdir=/d:/e:/f" causing the last
-        * specified lowerdir mount option to win.
-        *
-        * We want nr <= nr_lower <= capacity We know either (i) nr == 0
-        * or (ii) nr > 0. We also know nr_lower > 0. The capacity
-        * could've been changed multiple times already so we only know
-        * nr <= capacity. If nr + nr_lower > capacity we realloc,
-        * otherwise nr <= nr_lower <= capacity holds already.
-        */
-       nr_lower += ctx->nr;
        if (nr_lower > ctx->capacity) {
                err = -ENOMEM;
                l = krealloc_array(ctx->lower, nr_lower, sizeof(*ctx->lower),
@@ -443,41 +423,21 @@ static int ovl_parse_param_lowerdir(const char *name, struct fs_context *fc)
                ctx->capacity = nr_lower;
        }
 
-       /*
-        *   (3) By (1) and (2) we know nr <= nr_lower <= capacity.
-        *   (4) If ctx->nr == 0 => replace
-        *       We have verified above that the lowerdir mount option
-        *       isn't an append, i.e., the lowerdir mount option
-        *       doesn't start with ":" or "::".
-        * (4.1) The lowerdir mount options only contains regular lower
-        *       layers ":".
-        *       => Nothing to verify.
-        * (4.2) The lowerdir mount options contains regular ":" and
-        *       data "::" layers.
-        *       => We need to verify that data lower layers "::" aren't
-        *          followed by regular ":" lower layers
-        *   (5) If ctx->nr > 0 => append
-        *       We know that there's at least one regular layer
-        *       otherwise we would've failed when parsing the previous
-        *       lowerdir mount option.
-        * (5.1) The lowerdir mount option is a regular layer ":" append
-        *       => We need to verify that no data layers have been
-        *          specified before.
-        * (5.2) The lowerdir mount option is a data layer "::" append
-        *       We know that there's at least one regular layer or
-        *       other data layers. => There's nothing to verify.
-        */
-       dup_iter = dup;
-       for (nr = ctx->nr; nr < nr_lower; nr++) {
-               l = &ctx->lower[nr];
+       iter = dup;
+       l = ctx->lower;
+       for (nr = 0; nr < nr_lower; nr++, l++) {
                memset(l, 0, sizeof(*l));
 
-               err = ovl_mount_dir_noesc(dup_iter, &l->path);
+               err = ovl_mount_dir(iter, &l->path);
+               if (err)
+                       goto out_put;
+
+               err = ovl_mount_dir_check(fc, &l->path, Opt_lowerdir, iter, false);
                if (err)
                        goto out_put;
 
                err = -ENOMEM;
-               l->name = kstrdup(dup_iter, GFP_KERNEL_ACCOUNT);
+               l->name = kstrdup(iter, GFP_KERNEL_ACCOUNT);
                if (!l->name)
                        goto out_put;
 
@@ -489,8 +449,8 @@ static int ovl_parse_param_lowerdir(const char *name, struct fs_context *fc)
                        break;
 
                err = -EINVAL;
-               dup_iter = strchr(dup_iter, '\0') + 1;
-               if (*dup_iter) {
+               iter = strchr(iter, '\0') + 1;
+               if (*iter) {
                        /*
                         * This is a regular layer so we require that
                         * there are no data layers.
@@ -506,7 +466,7 @@ static int ovl_parse_param_lowerdir(const char *name, struct fs_context *fc)
 
                /* This is a data lower layer. */
                data_layer = true;
-               dup_iter++;
+               iter++;
        }
        ctx->nr = nr_lower;
        ctx->nr_data += nr_data;
@@ -514,21 +474,7 @@ static int ovl_parse_param_lowerdir(const char *name, struct fs_context *fc)
        return 0;
 
 out_put:
-       /*
-        * We know nr >= ctx->nr < nr_lower. If we failed somewhere
-        * we want to undo until nr == ctx->nr. This is correct for
-        * both ctx->nr == 0 and ctx->nr > 0.
-        */
-       for (; nr >= ctx->nr; nr--) {
-               l = &ctx->lower[nr];
-               kfree(l->name);
-               l->name = NULL;
-               path_put(&l->path);
-
-               /* don't overflow */
-               if (nr == 0)
-                       break;
-       }
+       ovl_reset_lowerdirs(ctx);
 
 out_err:
        kfree(dup);
@@ -574,10 +520,8 @@ static int ovl_parse_param(struct fs_context *fc, struct fs_parameter *param)
                err = ovl_parse_param_lowerdir(param->string, fc);
                break;
        case Opt_upperdir:
-               fallthrough;
        case Opt_workdir:
-               err = ovl_parse_param_upperdir(param->string, fc,
-                                              (Opt_workdir == opt));
+               err = ovl_parse_layer(fc, param, opt);
                break;
        case Opt_default_permissions:
                config->default_permissions = true;
@@ -634,7 +578,7 @@ static int ovl_get_tree(struct fs_context *fc)
 
 static inline void ovl_fs_context_free(struct ovl_fs_context *ctx)
 {
-       ovl_parse_param_drop_lowerdir(ctx);
+       ovl_reset_lowerdirs(ctx);
        path_put(&ctx->upper);
        path_put(&ctx->work);
        kfree(ctx->lower);
@@ -682,6 +626,7 @@ static int ovl_reconfigure(struct fs_context *fc)
 }
 
 static const struct fs_context_operations ovl_context_ops = {
+       .parse_monolithic = ovl_parse_monolithic,
        .parse_param = ovl_parse_param,
        .get_tree    = ovl_get_tree,
        .reconfigure = ovl_reconfigure,
@@ -949,17 +894,13 @@ int ovl_show_options(struct seq_file *m, struct dentry *dentry)
 {
        struct super_block *sb = dentry->d_sb;
        struct ovl_fs *ofs = OVL_FS(sb);
-       size_t nr, nr_merged_lower = ofs->numlayer - ofs->numdatalayer;
-       char **lowerdatadirs = &ofs->config.lowerdirs[nr_merged_lower];
-
-       /* lowerdirs[] starts from offset 1 */
-       seq_printf(m, ",lowerdir=%s", ofs->config.lowerdirs[1]);
-       /* dump regular lower layers */
-       for (nr = 2; nr < nr_merged_lower; nr++)
-               seq_printf(m, ":%s", ofs->config.lowerdirs[nr]);
-       /* dump data lower layers */
-       for (nr = 0; nr < ofs->numdatalayer; nr++)
-               seq_printf(m, "::%s", lowerdatadirs[nr]);
+       char **lowerdirs = ofs->config.lowerdirs;
+
+       /*
+        * lowerdirs[0] holds the colon separated list that user provided
+        * with lowerdir mount option.
+        */
+       seq_show_option(m, "lowerdir", lowerdirs[0]);
        if (ofs->config.upperdir) {
                seq_show_option(m, "upperdir", ofs->config.upperdir);
                seq_show_option(m, "workdir", ofs->config.workdir);