Sharing inode with different whiteout files for saving inode and speeding up deleting operation. Signed-off-by: Chengguang Xu <cgxu519@xxxxxxxxxxxx> --- v1->v2: - Address Amir's comments in v1 v2->v3: - Address Amir's comments in v2 - Rebase on Amir's "Overlayfs use index dir as work dir" patch set - Keep at most one whiteout tmpfile in work dir fs/overlayfs/dir.c | 35 ++++++++++++++++++++++++++++------- fs/overlayfs/overlayfs.h | 9 +++++++-- fs/overlayfs/ovl_entry.h | 4 ++++ fs/overlayfs/readdir.c | 3 ++- fs/overlayfs/super.c | 9 +++++++++ fs/overlayfs/util.c | 3 ++- 6 files changed, 52 insertions(+), 11 deletions(-) diff --git a/fs/overlayfs/dir.c b/fs/overlayfs/dir.c index 279009dee366..dbe5e54dcb16 100644 --- a/fs/overlayfs/dir.c +++ b/fs/overlayfs/dir.c @@ -62,35 +62,55 @@ struct dentry *ovl_lookup_temp(struct dentry *workdir) } /* 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; + bool retried = false; + bool should_link = (ofs->whiteout_link_max > 1); struct dentry *whiteout; struct inode *wdir = workdir->d_inode; +retry: whiteout = ovl_lookup_temp(workdir); if (IS_ERR(whiteout)) return whiteout; + if (should_link && ofs->whiteout) { + err = ovl_do_link(ofs->whiteout, wdir, whiteout); + if (err || !ovl_whiteout_linkable(ofs)) { + ovl_cleanup(wdir, ofs->whiteout); + dput(ofs->whiteout); + ofs->whiteout = NULL; + } + + if (!err) + return whiteout; + } + err = ovl_do_whiteout(wdir, whiteout); if (err) { dput(whiteout); - whiteout = ERR_PTR(err); + return ERR_PTR(err); } - return whiteout; + if (!should_link || retried) + return whiteout; + + ofs->whiteout = whiteout; + retried = true; + goto retry; } /* 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; @@ -715,6 +735,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; @@ -748,7 +769,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 e00b1ff6dea9..3d7e0e342dae 100644 --- a/fs/overlayfs/overlayfs.h +++ b/fs/overlayfs/overlayfs.h @@ -225,6 +225,11 @@ static inline bool ovl_open_flags_need_copy_up(int flags) return ((OPEN_FMODE(flags) & FMODE_WRITE) || (flags & O_TRUNC)); } +static inline bool ovl_whiteout_linkable(struct ovl_fs *ofs) +{ + return ofs->whiteout->d_inode->i_nlink <= ofs->whiteout_link_max; +} + /* util.c */ int ovl_want_write(struct dentry *dentry); void ovl_drop_write(struct dentry *dentry); @@ -455,8 +460,8 @@ static inline void ovl_copyflags(struct inode *from, struct inode *to) /* dir.c */ extern const struct inode_operations ovl_dir_inode_operations; -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 ovl_cattr { dev_t rdev; umode_t mode; diff --git a/fs/overlayfs/ovl_entry.h b/fs/overlayfs/ovl_entry.h index 5762d802fe01..de5f230b6e6b 100644 --- a/fs/overlayfs/ovl_entry.h +++ b/fs/overlayfs/ovl_entry.h @@ -77,6 +77,10 @@ struct ovl_fs { int xino_mode; /* For allocation of non-persistent inode numbers */ atomic_long_t last_ino; + /* Whiteout dentry cache */ + struct dentry *whiteout; + /* Whiteout max link count */ + unsigned int whiteout_link_max; }; static inline struct ovl_fs *OVL_FS(struct super_block *sb) diff --git a/fs/overlayfs/readdir.c b/fs/overlayfs/readdir.c index d6b601caf122..eb4683e58cff 100644 --- a/fs/overlayfs/readdir.c +++ b/fs/overlayfs/readdir.c @@ -1147,7 +1147,8 @@ 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 b91b23a0366c..b6f2393ec111 100644 --- a/fs/overlayfs/super.c +++ b/fs/overlayfs/super.c @@ -26,6 +26,10 @@ struct ovl_dir_cache; #define OVL_MAX_STACK 500 +static unsigned int ovl_whiteout_link_max_def = 60000; +module_param_named(whiteout_link_max, ovl_whiteout_link_max_def, uint, 0644); +MODULE_PARM_DESC(whiteout_link_max, "Maximum count of whiteout file link"); + static bool ovl_redirect_dir_def = IS_ENABLED(CONFIG_OVERLAY_FS_REDIRECT_DIR); module_param_named(redirect_dir, ovl_redirect_dir_def, bool, 0644); MODULE_PARM_DESC(redirect_dir, @@ -219,6 +223,7 @@ static void ovl_free_fs(struct ovl_fs *ofs) iput(ofs->upperdir_trap); dput(ofs->indexdir); dput(ofs->workdir); + dput(ofs->whiteout); if (ofs->workdir_locked) ovl_inuse_unlock(ofs->workbasedir); dput(ofs->workbasedir); @@ -1762,6 +1767,10 @@ static int ovl_fill_super(struct super_block *sb, void *data, int silent) if (!ofs->workdir) sb->s_flags |= SB_RDONLY; + else + ofs->whiteout_link_max = min_not_zero( + ofs->workdir->d_sb->s_max_links, + ovl_whiteout_link_max_def ?: 1); sb->s_stack_depth = ofs->upper_mnt->mnt_sb->s_stack_depth; sb->s_time_gran = ofs->upper_mnt->mnt_sb->s_time_gran; diff --git a/fs/overlayfs/util.c b/fs/overlayfs/util.c index 36b60788ee47..18df65ee81a8 100644 --- a/fs/overlayfs/util.c +++ b/fs/overlayfs/util.c @@ -669,6 +669,7 @@ bool ovl_need_index(struct dentry *dentry) /* Caller must hold OVL_I(inode)->lock */ static void ovl_cleanup_index(struct dentry *dentry) { + struct ovl_fs *ofs = dentry->d_sb->s_fs_info; struct dentry *indexdir = ovl_indexdir(dentry->d_sb); struct inode *dir = indexdir->d_inode; struct dentry *lowerdentry = ovl_dentry_lower(dentry); @@ -707,7 +708,7 @@ static void ovl_cleanup_index(struct dentry *dentry) index = NULL; } else if (ovl_index_all(dentry->d_sb)) { /* 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.20.1