[RFC][PATCH v3] overlay: hardlink all whiteout files into a single one

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



Now each whiteout file will be assigned a new inode. To reduce the
overhead of allocating and freeing inodes in upper fs, creating a
singleton whiteout file under workdir and hardlink all whiteout files
into it.

The effect is obvious: under a KVM VM, the time used for removing the
linux kernel source tree is reduced from 17s to 10s.

Got the idea from aufs4.

Signed-off-by: Hou Tao <houtao1@xxxxxxxxxx>
---
v3:
    * rebased on 4.16-rc3
    * add a limitation on the hardlinks of singleton whiteout, and it
      can be adjusted by module param singleton_wt_link_max. During
      the mount procoess, if the value of limit is less than or equal to 1,
      the singleon whiteout will be disabled (suggested by Amir)
    * rename ovl_make_singleton_whiteout_nolock() to
      ovl_make_singleton_whiteout_locked() (also from Amir)
    * check errors more strictly: treat singleton whiteout creation
      failure as an error and abort the mount or unlink procedure
    * remove unnecessary mnt_want_write() from ovl_make_singleton_whiteout(),
      because mnt_want_write() has been invoked by its caller.
    * document the lock assertions for newly-added helpers
v2:
    * address the comments from Miklos and Amir: handle -EMLINK and -EXDEV
      when hard-linking whiteout file to the singleton

    * move the singleton whiteout from workbasedir to workdir to simplify
      the lock of inodes
v1:
    * https://www.spinics.net/lists/linux-unionfs/msg04023.html
---
 fs/overlayfs/dir.c       | 150 +++++++++++++++++++++++++++++++++++++++++++++--
 fs/overlayfs/overlayfs.h |   5 +-
 fs/overlayfs/ovl_entry.h |   5 ++
 fs/overlayfs/readdir.c   |   2 +-
 fs/overlayfs/super.c     |   5 ++
 fs/overlayfs/util.c      |   4 +-
 6 files changed, 161 insertions(+), 10 deletions(-)

diff --git a/fs/overlayfs/dir.c b/fs/overlayfs/dir.c
index 839709c7803a..c87360d22f92 100644
--- a/fs/overlayfs/dir.c
+++ b/fs/overlayfs/dir.c
@@ -24,6 +24,12 @@ module_param_named(redirect_max, ovl_redirect_max, ushort, 0644);
 MODULE_PARM_DESC(ovl_redirect_max,
 		 "Maximum length of absolute redirect xattr value");
 
+static unsigned int ovl_singleton_wt_link_max = 255;
+module_param_named(singleton_whiteout_link_max,
+		ovl_singleton_wt_link_max, uint, 0644);
+MODULE_PARM_DESC(ovl_singleton_wt_link_max,
+		"Maximum hardlinks of a whiteout singleton");
+
 int ovl_cleanup(struct inode *wdir, struct dentry *wdentry)
 {
 	int err;
@@ -62,18 +68,149 @@ struct dentry *ovl_lookup_temp(struct dentry *workdir)
 	return temp;
 }
 
+#define OVL_SINGLETON_WHITEOUT_NAME "whiteout"
+
+/* caller holds write lock (i_rwsem) of dir */
+static int ovl_make_singleton_whiteout_locked(struct ovl_fs *ofs,
+		struct dentry *dir, const char *name)
+{
+	int err = 0;
+	struct inode *inode = d_inode(dir);
+	struct dentry *whiteout;
+
+	whiteout = lookup_one_len(name, dir, strlen(name));
+	if (IS_ERR(whiteout))
+		return PTR_ERR(whiteout);
+
+	if (ovl_is_whiteout(whiteout)) {
+		ofs->whiteout = whiteout;
+	} else if (!whiteout->d_inode) {
+		err = ovl_do_whiteout(inode, whiteout);
+		if (!err)
+			ofs->whiteout = whiteout;
+		else
+			dput(whiteout);
+	} else {
+		/*
+		 * fallback to creating new whiteout file if
+		 * a non-whiteout file already exists
+		 */
+		dput(whiteout);
+	}
+
+	return err;
+}
+
+/*
+ * create a new whiteout file under workdir if it doesn't exist.
+ * If a non-whiteout file already exists, no error will be reported
+ * and the needed whiteout file will be newly created instead of
+ * being linked to a singleton whiteout.
+ */
+int ovl_make_singleton_whiteout(struct ovl_fs *ofs)
+{
+	int err;
+	struct inode *dir;
+
+	/* disable singleton whiteout */
+	if (ovl_singleton_wt_link_max <= 1)
+		return 0;
+
+	dir = d_inode(ofs->workdir);
+	inode_lock_nested(dir, I_MUTEX_PARENT);
+
+	err = ovl_make_singleton_whiteout_locked(ofs, ofs->workdir,
+			OVL_SINGLETON_WHITEOUT_NAME);
+
+	inode_unlock(dir);
+
+	return err;
+}
+
+/*
+ * caller holds i_mutex of workdir to ensure the operations
+ * on the singletion whiteout are serialized.
+ */
+static int ovl_link_to_singleton_whiteout(struct ovl_fs *ofs,
+		struct dentry *whiteout, bool *create_instead)
+{
+	int err;
+	struct inode *wdir = d_inode(ofs->workdir);
+	struct dentry *singleton;
+	bool retried = false;
+
+	*create_instead = false;
+retry:
+	singleton = ofs->whiteout;
+	/* Create a new singletion whiteout when the limit is exceeded */
+	if (1 < ovl_singleton_wt_link_max &&
+		singleton->d_inode->i_nlink >= ovl_singleton_wt_link_max)
+		err = -EMLINK;
+	else
+		err = ovl_do_link(singleton, wdir, whiteout, false);
+
+	if (!err) {
+		goto out;
+	} else if (err == -EMLINK && !retried) {
+		/*
+		 * The singleton already has the maximum number of links to it,
+		 * so remove the old singleton and create a new one
+		 */
+		ofs->whiteout = NULL;
+		err = ovl_do_unlink(wdir, singleton);
+		if (err) {
+			dput(singleton);
+			goto out;
+		}
+
+		dput(singleton);
+		err = ovl_make_singleton_whiteout_locked(ofs, ofs->workdir,
+				OVL_SINGLETON_WHITEOUT_NAME);
+		if (err)
+			goto out;
+
+		retried = true;
+		if (ofs->whiteout)
+			goto retry;
+		else
+			goto out_fallback;
+	} else if (err == -EXDEV) {
+		/*
+		 * upper fs may have a project id different than singleton,
+		 * so fall back to create whiteout directly
+		 */
+		goto out_fallback;
+	} else {
+		goto out;
+	}
+
+out_fallback:
+	*create_instead = true;
+out:
+	return err;
+}
+
 /* caller holds i_mutex on workdir */
-static struct dentry *ovl_whiteout(struct dentry *workdir)
+static struct dentry *ovl_whiteout(struct ovl_fs *ofs, struct dentry *workdir)
 {
 	int err;
 	struct dentry *whiteout;
 	struct inode *wdir = workdir->d_inode;
+	bool create_instead;
 
 	whiteout = ovl_lookup_temp(workdir);
 	if (IS_ERR(whiteout))
 		return whiteout;
 
-	err = ovl_do_whiteout(wdir, whiteout);
+	if (workdir == ofs->workdir && ofs->whiteout)
+		err = ovl_link_to_singleton_whiteout(ofs, whiteout,
+				&create_instead);
+	else
+		create_instead = true;
+
+	if (create_instead)
+		err = ovl_do_whiteout(wdir, whiteout);
+
 	if (err) {
 		dput(whiteout);
 		whiteout = ERR_PTR(err);
@@ -83,15 +220,15 @@ static struct dentry *ovl_whiteout(struct dentry *workdir)
 }
 
 /* Caller must hold i_mutex on both workdir and dir */
-int ovl_cleanup_and_whiteout(struct dentry *workdir, struct inode *dir,
-			     struct dentry *dentry)
+int ovl_cleanup_and_whiteout(struct ovl_fs *ofs, struct dentry *workdir,
+		struct inode *dir, struct dentry *dentry)
 {
 	struct inode *wdir = workdir->d_inode;
 	struct dentry *whiteout;
 	int err;
 	int flags = 0;
 
-	whiteout = ovl_whiteout(workdir);
+	whiteout = ovl_whiteout(ofs, workdir);
 	err = PTR_ERR(whiteout);
 	if (IS_ERR(whiteout))
 		return err;
@@ -621,6 +758,7 @@ static bool ovl_matches_upper(struct dentry *dentry, struct dentry *upper)
 static int ovl_remove_and_whiteout(struct dentry *dentry,
 				   struct list_head *list)
 {
+	struct ovl_fs *ofs = dentry->d_sb->s_fs_info;
 	struct dentry *workdir = ovl_workdir(dentry);
 	struct dentry *upperdir = ovl_dentry_upper(dentry->d_parent);
 	struct dentry *upper;
@@ -654,7 +792,7 @@ static int ovl_remove_and_whiteout(struct dentry *dentry,
 		goto out_dput_upper;
 	}
 
-	err = ovl_cleanup_and_whiteout(workdir, d_inode(upperdir), upper);
+	err = ovl_cleanup_and_whiteout(ofs, workdir, d_inode(upperdir), upper);
 	if (err)
 		goto out_d_drop;
 
diff --git a/fs/overlayfs/overlayfs.h b/fs/overlayfs/overlayfs.h
index 0df25a9c94bd..d46894598f05 100644
--- a/fs/overlayfs/overlayfs.h
+++ b/fs/overlayfs/overlayfs.h
@@ -343,8 +343,8 @@ static inline void ovl_copyattr(struct inode *from, struct inode *to)
 /* dir.c */
 extern const struct inode_operations ovl_dir_inode_operations;
 struct dentry *ovl_lookup_temp(struct dentry *workdir);
-int ovl_cleanup_and_whiteout(struct dentry *workdir, struct inode *dir,
-			     struct dentry *dentry);
+int ovl_cleanup_and_whiteout(struct ovl_fs *ofs, struct dentry *workdir,
+		struct inode *dir, struct dentry *dentry);
 struct cattr {
 	dev_t rdev;
 	umode_t mode;
@@ -354,6 +354,7 @@ int ovl_create_real(struct inode *dir, struct dentry *newdentry,
 		    struct cattr *attr,
 		    struct dentry *hardlink, bool debug);
 int ovl_cleanup(struct inode *dir, struct dentry *dentry);
+int ovl_make_singleton_whiteout(struct ovl_fs *ofs);
 
 /* copy_up.c */
 int ovl_copy_up(struct dentry *dentry);
diff --git a/fs/overlayfs/ovl_entry.h b/fs/overlayfs/ovl_entry.h
index bfef6edcc111..5a7929f8a778 100644
--- a/fs/overlayfs/ovl_entry.h
+++ b/fs/overlayfs/ovl_entry.h
@@ -43,6 +43,11 @@ struct ovl_fs {
 	struct dentry *workdir;
 	/* index directory listing overlay inodes by origin file handle */
 	struct dentry *indexdir;
+	/*
+	 * the singleton whiteout file under workdir: all newly created
+	 * whiteout files will be linked to it if possible
+	 */
+	struct dentry *whiteout;
 	long namelen;
 	/* pathnames of lower and upper dirs, for show_options */
 	struct ovl_config config;
diff --git a/fs/overlayfs/readdir.c b/fs/overlayfs/readdir.c
index c11f5c0906c3..4858ea6e8b99 100644
--- a/fs/overlayfs/readdir.c
+++ b/fs/overlayfs/readdir.c
@@ -1084,7 +1084,7 @@ int ovl_indexdir_cleanup(struct ovl_fs *ofs)
 			 * Whiteout orphan index to block future open by
 			 * handle after overlay nlink dropped to zero.
 			 */
-			err = ovl_cleanup_and_whiteout(indexdir, dir, index);
+			err = ovl_cleanup_and_whiteout(ofs, indexdir, dir, index);
 		} else {
 			/* Cleanup orphan index entries */
 			err = ovl_cleanup(dir, index);
diff --git a/fs/overlayfs/super.c b/fs/overlayfs/super.c
index 9ee37c76091d..0b26523eaf43 100644
--- a/fs/overlayfs/super.c
+++ b/fs/overlayfs/super.c
@@ -229,6 +229,7 @@ static void ovl_free_fs(struct ovl_fs *ofs)
 	unsigned i;
 
 	dput(ofs->indexdir);
+	dput(ofs->whiteout);
 	dput(ofs->workdir);
 	if (ofs->workdir_locked)
 		ovl_inuse_unlock(ofs->workbasedir);
@@ -961,6 +962,10 @@ static int ovl_make_workdir(struct ovl_fs *ofs, struct path *workpath)
 	if (!ofs->workdir)
 		goto out;
 
+	err = ovl_make_singleton_whiteout(ofs);
+	if (err < 0)
+		goto out;
+
 	/*
 	 * Upper should support d_type, else whiteouts are visible.  Given
 	 * workdir and upper are on same fs, we can do iterate_dir() on
diff --git a/fs/overlayfs/util.c b/fs/overlayfs/util.c
index 930784a26623..4b8bbb63bcec 100644
--- a/fs/overlayfs/util.c
+++ b/fs/overlayfs/util.c
@@ -533,8 +533,10 @@ static void ovl_cleanup_index(struct dentry *dentry)
 	if (IS_ERR(index)) {
 		index = NULL;
 	} else if (ovl_index_all(dentry->d_sb)) {
+		struct ovl_fs *ofs = dentry->d_sb->s_fs_info;
+
 		/* Whiteout orphan index to block future open by handle */
-		err = ovl_cleanup_and_whiteout(indexdir, dir, index);
+		err = ovl_cleanup_and_whiteout(ofs, indexdir, dir, index);
 	} else {
 		/* Cleanup orphan index entries */
 		err = ovl_cleanup(dir, index);
-- 
2.13.6

--
To unsubscribe from this list: send the line "unsubscribe linux-unionfs" in
the body of a message to majordomo@xxxxxxxxxxxxxxx
More majordomo info at  http://vger.kernel.org/majordomo-info.html



[Index of Archives]     [Linux Filesystems Devel]     [Linux NFS]     [Linux NILFS]     [Linux USB Devel]     [Linux Audio Users]     [Yosemite News]     [Linux Kernel]     [Linux SCSI]

  Powered by Linux