[v6] overlayfs: add dynamic path resolving in mount options

Submitted by Alexander Mikhalitsyn on May 26, 2020, 9:47 a.m.

Details

Message ID 20200526094748.25659-1-alexander.mikhalitsyn@virtuozzo.com
State New
Series "overlayfs: add dynamic path resolving in mount options"
Headers show

Commit Message

Alexander Mikhalitsyn May 26, 2020, 9:47 a.m.
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@virtuozzo.com>

v2: print_paths_option helper now uses only one additional page, code
refactored according review comments

v3: print_paths_option helper now correctly escapes special symbols in
paths, fixed ofs->lowerpaths[i] paths leak on error path in
ovl_get_lowerstack function

v4: fixed ofs->lowerpaths[i] paths double put on error path in
ovl_get_lowerstack function

v5: removed redundant path_put_init(&ofs->upperpath) in ovl_fill_super
function

v6: print_path_option, print_paths_option reworked using seq_path() helper

---
 fs/overlayfs/Kconfig     | 30 +++++++++++++++++
 fs/overlayfs/overlayfs.h |  4 +++
 fs/overlayfs/ovl_entry.h |  6 ++--
 fs/overlayfs/super.c     | 83 ++++++++++++++++++++++++++----------------------
 fs/overlayfs/util.c      | 21 ++++++++++++
 5 files changed, 104 insertions(+), 40 deletions(-)

Patch hide | download patch | download mbox

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..f508cd2 100644
--- a/fs/overlayfs/overlayfs.h
+++ b/fs/overlayfs/overlayfs.h
@@ -253,6 +253,10 @@  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);
 
+void print_path_option(struct seq_file *m, const char *name, struct path *path);
+void print_paths_option(struct seq_file *m, const char *name,
+			struct path *paths, unsigned int num);
+
 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..7c4f18a 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]);
+	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->lowerpaths, ofs->numlower);
+		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,21 +1369,19 @@  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;
 
 out_err:
+	for (i = 0; i < numlower; i++)
+		path_put_init(&ofs->lowerpaths[i]);
 	oe = ERR_PTR(err);
 	goto out;
 }
 
 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 +1432,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 +1463,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 +1504,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 +1524,6 @@  out_free_oe:
 	ovl_entry_stack_free(oe);
 	kfree(oe);
 out_err:
-	path_put(&upperpath);
 	ovl_free_fs(ofs);
 out:
 	return err;
diff --git a/fs/overlayfs/util.c b/fs/overlayfs/util.c
index 838c371..bb670ee 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,23 @@  err:
 	pr_err("overlayfs: failed to lock workdir+upperdir\n");
 	return -EIO;
 }
+
+void print_path_option(struct seq_file *m, const char *name, struct path *path)
+{
+	seq_show_option(m, name, "");
+	seq_path(m, path, ", \t\n\\");
+}
+
+void print_paths_option(struct seq_file *m, const char *name,
+			struct path *paths, unsigned int num)
+{
+	int i;
+
+	seq_show_option(m, name, "");
+
+	for (i = 0; i < num; i++) {
+		if (i)
+			seq_putc(m, ':');
+		seq_path(m, &paths[i], ", \t\n\\");
+	}
+}

Comments

Kirill Tkhai May 26, 2020, 4:45 p.m.
On 26.05.2020 12:47, 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@virtuozzo.com>

Reviewed-by: Kirill Tkhai <ktkhai@virtuozzo.com>

> 
> v2: print_paths_option helper now uses only one additional page, code
> refactored according review comments
> 
> v3: print_paths_option helper now correctly escapes special symbols in
> paths, fixed ofs->lowerpaths[i] paths leak on error path in
> ovl_get_lowerstack function
> 
> v4: fixed ofs->lowerpaths[i] paths double put on error path in
> ovl_get_lowerstack function
> 
> v5: removed redundant path_put_init(&ofs->upperpath) in ovl_fill_super
> function
> 
> v6: print_path_option, print_paths_option reworked using seq_path() helper
> 
> ---
>  fs/overlayfs/Kconfig     | 30 +++++++++++++++++
>  fs/overlayfs/overlayfs.h |  4 +++
>  fs/overlayfs/ovl_entry.h |  6 ++--
>  fs/overlayfs/super.c     | 83 ++++++++++++++++++++++++++----------------------
>  fs/overlayfs/util.c      | 21 ++++++++++++
>  5 files changed, 104 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..f508cd2 100644
> --- a/fs/overlayfs/overlayfs.h
> +++ b/fs/overlayfs/overlayfs.h
> @@ -253,6 +253,10 @@ 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);
>  
> +void print_path_option(struct seq_file *m, const char *name, struct path *path);
> +void print_paths_option(struct seq_file *m, const char *name,
> +			struct path *paths, unsigned int num);
> +
>  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..7c4f18a 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]);
> +	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->lowerpaths, ofs->numlower);
> +		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,21 +1369,19 @@ 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;
>  
>  out_err:
> +	for (i = 0; i < numlower; i++)
> +		path_put_init(&ofs->lowerpaths[i]);
>  	oe = ERR_PTR(err);
>  	goto out;
>  }
>  
>  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 +1432,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 +1463,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 +1504,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 +1524,6 @@ out_free_oe:
>  	ovl_entry_stack_free(oe);
>  	kfree(oe);
>  out_err:
> -	path_put(&upperpath);
>  	ovl_free_fs(ofs);
>  out:
>  	return err;
> diff --git a/fs/overlayfs/util.c b/fs/overlayfs/util.c
> index 838c371..bb670ee 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,23 @@ err:
>  	pr_err("overlayfs: failed to lock workdir+upperdir\n");
>  	return -EIO;
>  }
> +
> +void print_path_option(struct seq_file *m, const char *name, struct path *path)
> +{
> +	seq_show_option(m, name, "");
> +	seq_path(m, path, ", \t\n\\");
> +}
> +
> +void print_paths_option(struct seq_file *m, const char *name,
> +			struct path *paths, unsigned int num)
> +{
> +	int i;
> +
> +	seq_show_option(m, name, "");
> +
> +	for (i = 0; i < num; i++) {
> +		if (i)
> +			seq_putc(m, ':');
> +		seq_path(m, &paths[i], ", \t\n\\");
> +	}
> +}
>
Pavel Tikhomirov May 27, 2020, 7:11 a.m.
On 5/26/20 12:47 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.

We might also need to note here: even if we print real full paths 
relative to mount namespace, there is still a chance that we won't be 
able to find from where to take this path. With overmounts involved we 
can have multiple different directories which resolve to exactly the 
same path, so to determine which directory it really is we also need to 
know mount id for it.

We can also discuss: If we can print mount id along with the path? Will 
it break old api if we do it? Maybe someone understands where else we 
can print mount ids safely.

> 
> 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@virtuozzo.com>
> 
> v2: print_paths_option helper now uses only one additional page, code
> refactored according review comments
> 
> v3: print_paths_option helper now correctly escapes special symbols in
> paths, fixed ofs->lowerpaths[i] paths leak on error path in
> ovl_get_lowerstack function
> 
> v4: fixed ofs->lowerpaths[i] paths double put on error path in
> ovl_get_lowerstack function
> 
> v5: removed redundant path_put_init(&ofs->upperpath) in ovl_fill_super
> function
> 
> v6: print_path_option, print_paths_option reworked using seq_path() helper
> 
> ---
>   fs/overlayfs/Kconfig     | 30 +++++++++++++++++
>   fs/overlayfs/overlayfs.h |  4 +++
>   fs/overlayfs/ovl_entry.h |  6 ++--
>   fs/overlayfs/super.c     | 83 ++++++++++++++++++++++++++----------------------
>   fs/overlayfs/util.c      | 21 ++++++++++++
>   5 files changed, 104 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..f508cd2 100644
> --- a/fs/overlayfs/overlayfs.h
> +++ b/fs/overlayfs/overlayfs.h
> @@ -253,6 +253,10 @@ 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);
>   
> +void print_path_option(struct seq_file *m, const char *name, struct path *path);
> +void print_paths_option(struct seq_file *m, const char *name,
> +			struct path *paths, unsigned int num);
> +
>   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..7c4f18a 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]);
> +	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->lowerpaths, ofs->numlower);
> +		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,21 +1369,19 @@ 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;
>   
>   out_err:
> +	for (i = 0; i < numlower; i++)
> +		path_put_init(&ofs->lowerpaths[i]);
>   	oe = ERR_PTR(err);
>   	goto out;
>   }
>   
>   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 +1432,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 +1463,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 +1504,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 +1524,6 @@ out_free_oe:
>   	ovl_entry_stack_free(oe);
>   	kfree(oe);
>   out_err:
> -	path_put(&upperpath);
>   	ovl_free_fs(ofs);
>   out:
>   	return err;
> diff --git a/fs/overlayfs/util.c b/fs/overlayfs/util.c
> index 838c371..bb670ee 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,23 @@ err:
>   	pr_err("overlayfs: failed to lock workdir+upperdir\n");
>   	return -EIO;
>   }
> +
> +void print_path_option(struct seq_file *m, const char *name, struct path *path)
> +{
> +	seq_show_option(m, name, "");
> +	seq_path(m, path, ", \t\n\\");
> +}
> +
> +void print_paths_option(struct seq_file *m, const char *name,
> +			struct path *paths, unsigned int num)
> +{
> +	int i;
> +
> +	seq_show_option(m, name, "");
> +
> +	for (i = 0; i < num; i++) {
> +		if (i)
> +			seq_putc(m, ':');
> +		seq_path(m, &paths[i], ", \t\n\\");
> +	}
> +}
>