Currently overlayfs only allows specifying layers through path names. This is inconvenient for users such as systemd that want to assemble an overlayfs mount purely based on file descriptors. This introduces the new mount options: lowerdir_fd+ datadir_fd+ upperdir_fd workdir_fd which can be used as follows: fsconfig(fd_overlay, FSCONFIG_SET_FD, "upperdir_fd+", NULL, fd_upper); fsconfig(fd_overlay, FSCONFIG_SET_FD, "workdir_fd+", NULL, fd_work); fsconfig(fd_overlay, FSCONFIG_SET_FD, "lowerdir_fd+", NULL, fd_lower1); fsconfig(fd_overlay, FSCONFIG_SET_FD, "lowerdir_fd+", NULL, fd_lower2); Since the mount api doesn't allow overloading of mount option parameters (except for strings and flags). Making this work for arbitrary parameters would be quite ugly or file descriptors would have to be special cased. Neither is very appealing. I do prefer the *_fd mount options because they aren't ambiguous. Signed-off-by: Christian Brauner <brauner@xxxxxxxxxx> --- fs/overlayfs/params.c | 132 +++++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 109 insertions(+), 23 deletions(-) diff --git a/fs/overlayfs/params.c b/fs/overlayfs/params.c index e42546c6c5dfbea930414856d791e3e4424a999e..2da7f231401ef034bb62d72a5d34f4a7e9179f8b 100644 --- a/fs/overlayfs/params.c +++ b/fs/overlayfs/params.c @@ -59,6 +59,10 @@ enum ovl_opt { Opt_metacopy, Opt_verity, Opt_volatile, + Opt_lowerdir_add_fd, + Opt_datadir_add_fd, + Opt_upperdir_fd, + Opt_workdir_fd, }; static const struct constant_table ovl_parameter_bool[] = { @@ -155,6 +159,10 @@ const struct fs_parameter_spec ovl_parameter_spec[] = { fsparam_enum("metacopy", Opt_metacopy, ovl_parameter_bool), fsparam_enum("verity", Opt_verity, ovl_parameter_verity), fsparam_flag("volatile", Opt_volatile), + fsparam_fd("lowerdir_fd+", Opt_lowerdir_add_fd), + fsparam_fd("datadir_fd+", Opt_datadir_add_fd), + fsparam_fd("upperdir_fd", Opt_upperdir_fd), + fsparam_fd("workdir_fd", Opt_workdir_fd), {} }; @@ -343,19 +351,27 @@ static void ovl_add_layer(struct fs_context *fc, enum ovl_opt layer, switch (layer) { case Opt_workdir: + fallthrough; + case Opt_workdir_fd: swap(config->workdir, *pname); swap(ctx->work, *path); break; case Opt_upperdir: + fallthrough; + case Opt_upperdir_fd: swap(config->upperdir, *pname); swap(ctx->upper, *path); break; case Opt_datadir_add: + fallthrough; + case Opt_datadir_add_fd: ctx->nr_data++; fallthrough; case Opt_lowerdir: fallthrough; case Opt_lowerdir_add: + fallthrough; + case Opt_lowerdir_add_fd: WARN_ON(ctx->nr >= ctx->capacity); l = &ctx->lower[ctx->nr++]; memset(l, 0, sizeof(*l)); @@ -367,43 +383,96 @@ static void ovl_add_layer(struct fs_context *fc, enum ovl_opt layer, } } -static int ovl_parse_layer(struct fs_context *fc, const char *layer_name, enum ovl_opt layer) +static inline bool is_upper_layer(enum ovl_opt layer) { - char *name = kstrdup(layer_name, GFP_KERNEL); - bool upper = (layer == Opt_upperdir || layer == Opt_workdir); - struct path path; - int err; + return layer == Opt_upperdir || layer == Opt_upperdir_fd || + layer == Opt_workdir || layer == Opt_workdir_fd; +} + +static inline bool is_layer_fd(enum ovl_opt layer) +{ + return layer == Opt_upperdir_fd || layer == Opt_workdir_fd || + layer == Opt_lowerdir_add_fd || layer == Opt_datadir_add_fd; +} + +/* Handle non-file descriptor-based layer options that require path lookup. */ +static inline int ovl_kern_path(const char *layer_name, struct path *layer_path, + enum ovl_opt layer) +{ + switch (layer) { + case Opt_upperdir: + fallthrough; + case Opt_workdir: + fallthrough; + case Opt_lowerdir: + return ovl_mount_dir(layer_name, layer_path); + case Opt_lowerdir_add: + fallthrough; + case Opt_datadir_add: + return ovl_mount_dir_noesc(layer_name, layer_path); + default: + WARN_ON_ONCE(true); + return -EINVAL; + } + + return 0; +} + +static int ovl_do_parse_layer(struct fs_context *fc, const char *layer_name, + struct path *layer_path, enum ovl_opt layer) +{ + char *name __free(kfree) = kstrdup(layer_name, GFP_KERNEL); + bool upper; + int err = 0; if (!name) return -ENOMEM; - if (upper || layer == Opt_lowerdir) - err = ovl_mount_dir(name, &path); - else - err = ovl_mount_dir_noesc(name, &path); - if (err) - goto out_free; - - err = ovl_mount_dir_check(fc, &path, layer, name, upper); + upper = is_upper_layer(layer); + err = ovl_mount_dir_check(fc, layer_path, layer, name, upper); if (err) - goto out_put; + return err; if (!upper) { err = ovl_ctx_realloc_lower(fc); if (err) - goto out_put; + return err; } /* 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); + ovl_add_layer(fc, layer, layer_path, &name); return err; } +static int ovl_parse_layer(struct fs_context *fc, struct fs_parameter *param, + enum ovl_opt layer) +{ + struct path path __free(path_put) = {}; + char *buf __free(kfree) = NULL; + char *layer_name; + int err = 0; + + if (is_layer_fd(layer)) { + buf = kmalloc(PATH_MAX, GFP_KERNEL_ACCOUNT); + if (!buf) + return -ENOMEM; + + path = param->file->f_path; + path_get(&path); + + layer_name = d_path(&path, buf, PATH_MAX); + if (IS_ERR(layer_name)) + return PTR_ERR(layer_name); + } else { + layer_name = param->string; + err = ovl_kern_path(layer_name, &path, layer); + } + if (err) + return err; + + return ovl_do_parse_layer(fc, layer_name, &path, layer); +} + static void ovl_reset_lowerdirs(struct ovl_fs_context *ctx) { struct ovl_fs_context_layer *l = ctx->lower; @@ -474,7 +543,13 @@ static int ovl_parse_param_lowerdir(const char *name, struct fs_context *fc) iter = dup; for (nr = 0; nr < nr_lower; nr++) { - err = ovl_parse_layer(fc, iter, Opt_lowerdir); + struct path path __free(path_put) = {}; + + err = ovl_kern_path(iter, &path, Opt_lowerdir); + if (err) + goto out_err; + + err = ovl_do_parse_layer(fc, iter, &path, Opt_lowerdir); if (err) goto out_err; @@ -552,10 +627,21 @@ static int ovl_parse_param(struct fs_context *fc, struct fs_parameter *param) err = ovl_parse_param_lowerdir(param->string, fc); break; case Opt_lowerdir_add: + fallthrough; case Opt_datadir_add: + fallthrough; case Opt_upperdir: + fallthrough; case Opt_workdir: - err = ovl_parse_layer(fc, param->string, opt); + fallthrough; + case Opt_lowerdir_add_fd: + fallthrough; + case Opt_datadir_add_fd: + fallthrough; + case Opt_upperdir_fd: + fallthrough; + case Opt_workdir_fd: + err = ovl_parse_layer(fc, param, opt); break; case Opt_default_permissions: config->default_permissions = true; -- 2.45.2