[Devel] [PATCH v2] overlayfs: add dynamic path resolving in mount options
Alexander Mikhalitsyn
alexander.mikhalitsyn at virtuozzo.com
Mon May 25 16:36:45 MSK 2020
Thank you! I've fixed issues and sent 3rd version of the patch.
________________________________________
From: Pavel Tikhomirov <ptikhomirov at virtuozzo.com>
Sent: Monday, May 25, 2020 12:45
To: Alexander Mikhalitsyn; devel at openvz.org
Cc: Konstantin Khorenko; Vasiliy Averin
Subject: Re: [PATCH v2] overlayfs: add dynamic path resolving in mount options
On 5/22/20 7:14 PM, Alexander Mikhalitsyn wrote:
> This patch adds OVERLAY_FS_DYNAMIC_RESOLVE_PATH_OPTIONS compile-time option,
> and "dyn_path_opts" runtime module option. These options corresponds
> "dynamic path resolving in lowerdir, upperdir, workdir mount options" mode.
> If enabled, user may see real full paths relatively to the mount namespace
> in lowerdir, upperdir, workdir options (/proc/mounts, /proc/<fd>/mountinfo).
>
> This patch is very helpful to checkpoint/restore functionality of overlayfs
> mounts. With this patch and CRIU it's real to C/R Docker containers with
> overlayfs storage driver.
>
> Note: d_path function from dcache.c is used to resolve full path in mount
> namespace. This function also adds "(deleted)" suffix if dentry was deleted.
> So, If one of dentries in lowerdir, upperdir, workdir options is deleted,
> we will see "(deleted)" suffix in corresponding path.
>
> https://jira.sw.ru/browse/PSBM-58614
>
> Signed-off-by: Alexander Mikhalitsyn <alexander.mikhalitsyn at virtuozzo.com>
> ---
> fs/overlayfs/Kconfig | 30 ++++++++++++++++++
> fs/overlayfs/overlayfs.h | 3 ++
> fs/overlayfs/ovl_entry.h | 6 ++--
> fs/overlayfs/super.c | 82 ++++++++++++++++++++++++++----------------------
> fs/overlayfs/util.c | 47 +++++++++++++++++++++++++++
> 5 files changed, 128 insertions(+), 40 deletions(-)
>
> diff --git a/fs/overlayfs/Kconfig b/fs/overlayfs/Kconfig
> index 9384164..15e8fa5 100644
> --- a/fs/overlayfs/Kconfig
> +++ b/fs/overlayfs/Kconfig
> @@ -103,3 +103,33 @@ config OVERLAY_FS_XINO_AUTO
> For more information, see Documentation/filesystems/overlayfs.txt
>
> If unsure, say N.
> +
> +config OVERLAY_FS_DYNAMIC_RESOLVE_PATH_OPTIONS
> + bool "Overlayfs: all mount paths options resolves dynamically on options show"
> + default y
> + depends on OVERLAY_FS
> + help
> + This option helps checkpoint/restore of overlayfs mounts.
> + If N selected, old behavior is saved. In this case lowerdir, upperdir,
> + workdir options shows in /proc/fd/mountinfo, /proc/mounts as it given
> + by user on mount. User may specify relative paths in these options, then
> + we couldn't determine from options which full paths correspond these
> + relative paths. Also, after pivot_root syscall these paths (even full)
> + will not rebuild according to root change.
> +
> + If this config option is enabled then overlay filesystems lowerdir, upperdir,
> + workdir options paths will dynamically recalculated as full paths in corresponding
> + mount namespaces by default.
> +
> + It's also possible to change this behavior on overlayfs module loading.
> +
> + Disable this to get a backward compatible with previous kernels configuration,
> + but in this case checkpoint/restore functionality for overlayfs mounts
> + will not work.
> +
> + If backward compatibility is not an issue, then it is safe and
> + recommended to say Y here.
> +
> + For more information, see Documentation/filesystems/overlayfs.txt
> +
> + If unsure, say N.
> \ No newline at end of file
> diff --git a/fs/overlayfs/overlayfs.h b/fs/overlayfs/overlayfs.h
> index fa999d9..055816e 100644
> --- a/fs/overlayfs/overlayfs.h
> +++ b/fs/overlayfs/overlayfs.h
> @@ -253,6 +253,9 @@ int ovl_nlink_start(struct dentry *dentry, bool *locked);
> void ovl_nlink_end(struct dentry *dentry, bool locked);
> int ovl_lock_rename_workdir(struct dentry *workdir, struct dentry *upperdir);
>
> +int print_path_option(struct seq_file *m, const char *name, struct path *path);
> +int print_paths_option(struct seq_file *m, const char *name, struct ovl_fs *ofs);
> +
> static inline bool ovl_is_impuredir(struct dentry *dentry)
> {
> return ovl_check_dir_xattr(dentry, OVL_XATTR_IMPURE);
> diff --git a/fs/overlayfs/ovl_entry.h b/fs/overlayfs/ovl_entry.h
> index 05459d3..a2738e9 100644
> --- a/fs/overlayfs/ovl_entry.h
> +++ b/fs/overlayfs/ovl_entry.h
> @@ -43,13 +43,15 @@ struct ovl_path {
> /* private information held for overlayfs's superblock */
> struct ovl_fs {
> struct vfsmount *upper_mnt;
> + struct path upperpath;
> unsigned int numlower;
> /* Number of unique lower sb that differ from upper sb */
> unsigned int numlowerfs;
> + struct path *lowerpaths;
> struct ovl_layer *lower_layers;
> struct ovl_sb *lower_fs;
> - /* workbasedir is the path at workdir= mount option */
> - struct dentry *workbasedir;
> + /* workbasepath is the path at workdir= mount option */
> + struct path workbasepath;
> /* workdir is the 'work' directory under workbasedir */
> struct dentry *workdir;
> /* index directory listing overlay inodes by origin file handle */
> diff --git a/fs/overlayfs/super.c b/fs/overlayfs/super.c
> index 50965ca..6325f80 100644
> --- a/fs/overlayfs/super.c
> +++ b/fs/overlayfs/super.c
> @@ -57,6 +57,10 @@ module_param_named(xino_auto, ovl_xino_auto_def, bool, 0644);
> MODULE_PARM_DESC(ovl_xino_auto_def,
> "Auto enable xino feature");
>
> +static bool ovl_dyn_path_opts = IS_ENABLED(CONFIG_OVERLAY_FS_DYNAMIC_RESOLVE_PATH_OPTIONS);
> +module_param_named(dyn_path_opts, ovl_dyn_path_opts, bool, 0444);
> +MODULE_PARM_DESC(dyn_path_opts, "dyn_path_opts feature enabled");
> +
> static void ovl_entry_stack_free(struct ovl_entry *oe)
> {
> unsigned int i;
> @@ -245,11 +249,15 @@ static void ovl_free_fs(struct ovl_fs *ofs)
> dput(ofs->indexdir);
> dput(ofs->workdir);
> if (ofs->workdir_locked)
> - ovl_inuse_unlock(ofs->workbasedir);
> - dput(ofs->workbasedir);
> + ovl_inuse_unlock(ofs->workbasepath.dentry);
> + path_put(&ofs->workbasepath);
> if (ofs->upperdir_locked)
> ovl_inuse_unlock(ofs->upper_mnt->mnt_root);
> mntput(ofs->upper_mnt);
> + path_put(&ofs->upperpath);
> + for (i = 0; i < ofs->numlower; i++)
> + path_put(&ofs->lowerpaths[i]);
Though I was wrong about workbasepath, I still have a bad feeling about
refcount but in different place for lowerpaths and different maner. Imagine:
ovl_fill_super
ovl_get_lowerstack
ofs->lowerpaths = kcalloc()
for () # stacklen times
# first iter
ovl_lower_dir
ovl_mount_dir_noesc
kern_path # should get reference on path somethere inside
# second iter
ovl_lower_dir # fail
goto out_err
# does not get to ovl_get_lower_layers and thus no ofs->numlower++
goto out_err
ovl_free_fs
for () # ofs->numlower times - zero
# don't path_put(&ofs->lowerpaths[0])
Looks like a reference is leaked on this path. Correct me if I'm wrong.
> + kfree(ofs->lowerpaths);
> for (i = 0; i < ofs->numlower; i++)
> mntput(ofs->lower_layers[i].mnt);
> for (i = 0; i < ofs->numlowerfs; i++)
> @@ -368,11 +376,20 @@ static int ovl_show_options(struct seq_file *m, struct dentry *dentry)
> struct super_block *sb = dentry->d_sb;
> struct ovl_fs *ofs = sb->s_fs_info;
>
> - seq_show_option(m, "lowerdir", ofs->config.lowerdir);
> - if (ofs->config.upperdir) {
> - seq_show_option(m, "upperdir", ofs->config.upperdir);
> - seq_show_option(m, "workdir", ofs->config.workdir);
> + if (ovl_dyn_path_opts) {
> + print_paths_option(m, "lowerdir", ofs);
> + if (ofs->config.upperdir) {
> + print_path_option(m, "upperdir", &ofs->upperpath);
> + print_path_option(m, "workdir", &ofs->workbasepath);
> + }
> + } else {
> + seq_show_option(m, "lowerdir", ofs->config.lowerdir);
> + if (ofs->config.upperdir) {
> + seq_show_option(m, "upperdir", ofs->config.upperdir);
> + seq_show_option(m, "workdir", ofs->config.workdir);
> + }
> }
> +
> if (ofs->config.default_permissions)
> seq_puts(m, ",default_permissions");
> if (strcmp(ofs->config.redirect_mode, ovl_redirect_mode_def()) != 0)
> @@ -586,7 +603,7 @@ static int ovl_parse_opt(char *opt, struct ovl_config *config)
> static struct dentry *ovl_workdir_create(struct ovl_fs *ofs,
> const char *name, bool persist)
> {
> - struct inode *dir = ofs->workbasedir->d_inode;
> + struct inode *dir = ofs->workbasepath.dentry->d_inode;
> struct vfsmount *mnt = ofs->upper_mnt;
> struct dentry *work;
> int err;
> @@ -597,7 +614,7 @@ static struct dentry *ovl_workdir_create(struct ovl_fs *ofs,
> locked = true;
>
> retry:
> - work = lookup_one_len(name, ofs->workbasedir, strlen(name));
> + work = lookup_one_len(name, ofs->workbasepath.dentry, strlen(name));
>
> if (!IS_ERR(work)) {
> struct iattr attr = {
> @@ -1009,7 +1026,7 @@ out:
> return err;
> }
>
> -static int ovl_make_workdir(struct ovl_fs *ofs, struct path *workpath)
> +static int ovl_make_workdir(struct ovl_fs *ofs, struct path *workbasepath)
> {
> struct vfsmount *mnt = ofs->upper_mnt;
> struct dentry *temp;
> @@ -1030,7 +1047,7 @@ static int ovl_make_workdir(struct ovl_fs *ofs, struct path *workpath)
> * workdir. This check requires successful creation of workdir in
> * previous step.
> */
> - err = ovl_check_d_type_supported(workpath);
> + err = ovl_check_d_type_supported(workbasepath);
> if (err < 0)
> goto out;
>
> @@ -1087,26 +1104,23 @@ out:
> static int ovl_get_workdir(struct ovl_fs *ofs, struct path *upperpath)
> {
> int err;
> - struct path workpath = { };
>
> - err = ovl_mount_dir(ofs->config.workdir, &workpath);
> + err = ovl_mount_dir(ofs->config.workdir, &ofs->workbasepath);
> if (err)
> goto out;
>
> err = -EINVAL;
> - if (upperpath->mnt != workpath.mnt) {
> + if (upperpath->mnt != ofs->workbasepath.mnt) {
> pr_err("overlayfs: workdir and upperdir must reside under the same mount\n");
> goto out;
> }
> - if (!ovl_workdir_ok(workpath.dentry, upperpath->dentry)) {
> + if (!ovl_workdir_ok(ofs->workbasepath.dentry, upperpath->dentry)) {
> pr_err("overlayfs: workdir and upperdir must be separate subtrees\n");
> goto out;
> }
>
> - ofs->workbasedir = dget(workpath.dentry);
> -
> err = -EBUSY;
> - if (ovl_inuse_trylock(ofs->workbasedir)) {
> + if (ovl_inuse_trylock(ofs->workbasepath.dentry)) {
> ofs->workdir_locked = true;
> } else if (ofs->config.index) {
> pr_err("overlayfs: workdir is in-use by another mount, mount with '-o index=off' to override exclusive workdir protection.\n");
> @@ -1115,14 +1129,12 @@ static int ovl_get_workdir(struct ovl_fs *ofs, struct path *upperpath)
> pr_warn("overlayfs: workdir is in-use by another mount, accessing files from both mounts will result in undefined behavior.\n");
> }
>
> - err = ovl_make_workdir(ofs, &workpath);
> + err = ovl_make_workdir(ofs, &ofs->workbasepath);
> if (err)
> goto out;
>
> err = 0;
> out:
> - path_put(&workpath);
> -
> return err;
> }
>
> @@ -1290,7 +1302,6 @@ static struct ovl_entry *ovl_get_lowerstack(struct super_block *sb,
> {
> int err;
> char *lowertmp, *lower;
> - struct path *stack = NULL;
> unsigned int stacklen, numlower = 0, i;
> bool remote = false;
> struct ovl_entry *oe;
> @@ -1316,14 +1327,14 @@ static struct ovl_entry *ovl_get_lowerstack(struct super_block *sb,
> }
>
> err = -ENOMEM;
> - stack = kcalloc(stacklen, sizeof(struct path), GFP_KERNEL);
> - if (!stack)
> + ofs->lowerpaths = kcalloc(stacklen, sizeof(struct path), GFP_KERNEL);
> + if (!ofs->lowerpaths)
> goto out_err;
>
> err = -EINVAL;
> lower = lowertmp;
> for (numlower = 0; numlower < stacklen; numlower++) {
> - err = ovl_lower_dir(lower, &stack[numlower], ofs,
> + err = ovl_lower_dir(lower, &ofs->lowerpaths[numlower], ofs,
> overlay_stack_depth, &remote);
> if (err)
> goto out_err;
> @@ -1338,7 +1349,7 @@ static struct ovl_entry *ovl_get_lowerstack(struct super_block *sb,
> goto out_err;
> }
>
> - err = ovl_get_lower_layers(ofs, stack, numlower);
> + err = ovl_get_lower_layers(ofs, ofs->lowerpaths, numlower);
> if (err)
> goto out_err;
>
> @@ -1348,7 +1359,7 @@ static struct ovl_entry *ovl_get_lowerstack(struct super_block *sb,
> goto out_err;
>
> for (i = 0; i < numlower; i++) {
> - oe->lowerstack[i].dentry = dget(stack[i].dentry);
> + oe->lowerstack[i].dentry = dget(ofs->lowerpaths[i].dentry);
> oe->lowerstack[i].layer = &ofs->lower_layers[i];
> }
>
> @@ -1358,9 +1369,6 @@ static struct ovl_entry *ovl_get_lowerstack(struct super_block *sb,
> sb->s_d_op = &ovl_dentry_operations.ops;
>
> out:
> - for (i = 0; i < numlower; i++)
> - path_put(&stack[i]);
> - kfree(stack);
> kfree(lowertmp);
>
> return oe;
> @@ -1372,7 +1380,6 @@ out_err:
>
> static int ovl_fill_super(struct super_block *sb, void *data, int silent)
> {
> - struct path upperpath = { };
> struct dentry *root_dentry;
> struct ovl_entry *oe;
> struct ovl_fs *ofs;
> @@ -1423,11 +1430,11 @@ static int ovl_fill_super(struct super_block *sb, void *data, int silent)
> goto out_err;
> }
>
> - err = ovl_get_upper(ofs, &upperpath);
> + err = ovl_get_upper(ofs, &ofs->upperpath);
> if (err)
> goto out_err;
>
> - err = ovl_get_workdir(ofs, &upperpath);
> + err = ovl_get_workdir(ofs, &ofs->upperpath);
> if (err)
> goto out_err;
>
> @@ -1454,7 +1461,7 @@ static int ovl_fill_super(struct super_block *sb, void *data, int silent)
> sb->s_flags |= MS_RDONLY;
>
> if (!(ovl_force_readonly(ofs)) && ofs->config.index) {
> - err = ovl_get_indexdir(ofs, oe, &upperpath);
> + err = ovl_get_indexdir(ofs, oe, &ofs->upperpath);
> if (err)
> goto out_free_oe;
>
> @@ -1495,17 +1502,16 @@ static int ovl_fill_super(struct super_block *sb, void *data, int silent)
>
> root_dentry->d_fsdata = oe;
>
> - mntput(upperpath.mnt);
> - if (upperpath.dentry) {
> + if (ofs->upperpath.dentry) {
> ovl_dentry_set_upper_alias(root_dentry);
> - if (ovl_is_impuredir(upperpath.dentry))
> + if (ovl_is_impuredir(ofs->upperpath.dentry))
> ovl_set_flag(OVL_IMPURE, d_inode(root_dentry));
> }
>
> /* Root is always merge -> can have whiteouts */
> ovl_set_flag(OVL_WHITEOUTS, d_inode(root_dentry));
> ovl_dentry_set_flag(OVL_E_CONNECTED, root_dentry);
> - ovl_inode_init(d_inode(root_dentry), upperpath.dentry,
> + ovl_inode_init(d_inode(root_dentry), dget(ofs->upperpath.dentry),
> ovl_dentry_lower(root_dentry));
>
> sb->s_root = root_dentry;
> @@ -1516,7 +1522,7 @@ out_free_oe:
> ovl_entry_stack_free(oe);
> kfree(oe);
> out_err:
> - path_put(&upperpath);
> + path_put(&ofs->upperpath);
> ovl_free_fs(ofs);
> out:
> return err;
> diff --git a/fs/overlayfs/util.c b/fs/overlayfs/util.c
> index 838c371..7e364fd 100644
> --- a/fs/overlayfs/util.c
> +++ b/fs/overlayfs/util.c
> @@ -17,6 +17,7 @@
> #include <linux/uuid.h>
> #include <linux/namei.h>
> #include <linux/ratelimit.h>
> +#include <linux/seq_file.h>
> #include "overlayfs.h"
>
> int ovl_want_write(struct dentry *dentry)
> @@ -678,3 +679,49 @@ err:
> pr_err("overlayfs: failed to lock workdir+upperdir\n");
> return -EIO;
> }
> +
> +int print_path_option(struct seq_file *m, const char *name, struct path *path)
> +{
> + char *tmp;
> + char *pathname;
> +
> + tmp = (char*)__get_free_page(GFP_TEMPORARY);
> + if (!tmp)
> + return -ENOMEM;
> +
> + pathname = d_path(path, tmp, PAGE_SIZE);
> + if (IS_ERR(pathname))
> + goto out;
> +
> + seq_show_option(m, name, pathname);
> + out:
> + free_page((unsigned long)tmp);
> +
> + return 0;
> +}
> +
> +int print_paths_option(struct seq_file *m, const char *name, struct ovl_fs *ofs)
> +{
> + char *tmp;
> + char *pathname;
> + int i;
> +
> + tmp = (char*)__get_free_page(GFP_TEMPORARY);
> + if (!tmp)
> + return -ENOMEM;
> +
> + seq_printf(m, ",%s=", name);
The function seq_show_option also insures that name and value do not
contain special characters ",= \t\n\\" maybe we also need to do it for
consistency and print them with something like seq_escape(m, name, ",=:
\t\n\\")? Except that looks good.
> +
> + for (i = 0; i < ofs->numlower; i++) {
> + pathname = d_path(&ofs->lowerpaths[i], tmp, PAGE_SIZE);
> + if (IS_ERR(pathname))
> + goto out;
> +
> + seq_printf(m, "%s%s", (i ? ":" : ""), pathname);
The same.
> + }
> +
> + out:
> + free_page((unsigned long)tmp);
> +
> + return 0;
> +}
>
--
Best regards, Tikhomirov Pavel
Software Developer, Virtuozzo.
More information about the Devel
mailing list