Sharing inode with different whiteout files for saving inode and speeding up deleting operation. Signed-off-by: Chengguang Xu <cgxu519@xxxxxxxxxxxx> Reviewed-by: Amir Goldstein <amir73il@xxxxxxxxx> --- 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. v3->v4: - Disable the feature after link failure. - Add mount option(whiteout link max) for overlayfs instance. v4->v5: - Add a warning when mount option exceeds module parameter. fs/overlayfs/dir.c | 47 ++++++++++++++++++++++++++++++++++------ fs/overlayfs/overlayfs.h | 10 +++++++-- fs/overlayfs/ovl_entry.h | 5 +++++ fs/overlayfs/readdir.c | 3 ++- fs/overlayfs/super.c | 27 +++++++++++++++++++++++ fs/overlayfs/util.c | 3 ++- 6 files changed, 84 insertions(+), 11 deletions(-) diff --git a/fs/overlayfs/dir.c b/fs/overlayfs/dir.c index 279009dee366..8b7d8854f31f 100644 --- a/fs/overlayfs/dir.c +++ b/fs/overlayfs/dir.c @@ -62,35 +62,67 @@ 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; + err = 0; + if (should_link) { + if (ovl_whiteout_linkable(ofs)) { + err = ovl_do_link(ofs->whiteout, wdir, whiteout); + if (!err) + return whiteout; + } else if (ofs->whiteout) { + dput(whiteout); + whiteout = ofs->whiteout; + ofs->whiteout = NULL; + return whiteout; + } + + if (err) { + pr_warn("Failed to link whiteout - disabling whiteout inode sharing(nlink=%u, err=%i)\n", + ofs->whiteout->d_inode->i_nlink, err); + ofs->whiteout_link_max = 0; + should_link = false; + ovl_cleanup(wdir, ofs->whiteout); + dput(ofs->whiteout); + ofs->whiteout = NULL; + } + } + 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 +747,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 +781,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..3b127c997a6d 100644 --- a/fs/overlayfs/overlayfs.h +++ b/fs/overlayfs/overlayfs.h @@ -225,6 +225,12 @@ 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 && + 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 +461,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..c805c35e0594 100644 --- a/fs/overlayfs/ovl_entry.h +++ b/fs/overlayfs/ovl_entry.h @@ -17,6 +17,7 @@ struct ovl_config { bool nfs_export; int xino; bool metacopy; + unsigned int whiteout_link_max; }; struct ovl_sb { @@ -77,6 +78,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 20f5310d3ee4..bf22fb7792c1 100644 --- a/fs/overlayfs/readdir.c +++ b/fs/overlayfs/readdir.c @@ -1154,7 +1154,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 f57aa348dcd6..af837e2910aa 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); @@ -358,6 +363,10 @@ static int ovl_show_options(struct seq_file *m, struct dentry *dentry) if (ofs->config.metacopy != ovl_metacopy_def) seq_printf(m, ",metacopy=%s", ofs->config.metacopy ? "on" : "off"); + if (ofs->config.whiteout_link_max != ovl_whiteout_link_max_def) + seq_printf(m, ",whiteout_link_max=%u", + ofs->config.whiteout_link_max); + return 0; } @@ -398,6 +407,7 @@ enum { OPT_XINO_AUTO, OPT_METACOPY_ON, OPT_METACOPY_OFF, + OPT_WHITEOUT_LINK_MAX, OPT_ERR, }; @@ -416,6 +426,7 @@ static const match_table_t ovl_tokens = { {OPT_XINO_AUTO, "xino=auto"}, {OPT_METACOPY_ON, "metacopy=on"}, {OPT_METACOPY_OFF, "metacopy=off"}, + {OPT_WHITEOUT_LINK_MAX, "whiteout_link_max=%u"}, {OPT_ERR, NULL} }; @@ -469,6 +480,7 @@ static int ovl_parse_opt(char *opt, struct ovl_config *config) { char *p; int err; + int link_max; bool metacopy_opt = false, redirect_opt = false; bool nfs_export_opt = false, index_opt = false; @@ -560,6 +572,16 @@ static int ovl_parse_opt(char *opt, struct ovl_config *config) metacopy_opt = true; break; + case OPT_WHITEOUT_LINK_MAX: + if (match_int(&args[0], &link_max)) + return -EINVAL; + if (link_max < ovl_whiteout_link_max_def) { + config->whiteout_link_max = link_max; + pr_warn("mount option whiteout_link_max=%u exceeds module parameter, set it equal to module parameter %u", + link_max, ovl_whiteout_link_max_def); + } + break; + default: pr_err("unrecognized mount option \"%s\" or missing value\n", p); @@ -1269,6 +1291,10 @@ static int ovl_make_workdir(struct super_block *sb, struct ovl_fs *ofs, pr_warn("NFS export requires \"index=on\", falling back to nfs_export=off.\n"); ofs->config.nfs_export = false; } + + ofs->whiteout_link_max = min_not_zero( + ofs->workdir->d_sb->s_max_links, + ofs->config.whiteout_link_max ?: 1); out: mnt_drop_write(mnt); return err; @@ -1768,6 +1794,7 @@ static int ovl_fill_super(struct super_block *sb, void *data, int silent) ofs->config.nfs_export = ovl_nfs_export_def; ofs->config.xino = ovl_xino_def(); ofs->config.metacopy = ovl_metacopy_def; + ofs->config.whiteout_link_max = ovl_whiteout_link_max_def; err = ovl_parse_opt((char *) data, &ofs->config); if (err) goto out_err; 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