On Fri, Apr 24, 2020 at 05:49:00PM +0300, Amir Goldstein wrote: > I didn't mean we need to check if link_max is valid range for upper fs. > We anyway use minimum of user requested value and upper fs max. > > Frankly, I think there are only two sane options for system wide configuration: > 1. disable whiteout link > 2. enable whiteout link with ofs->workdir->d_sb->s_max_links And that one doesn't work, see for example ext4, which defines EXT4_LINK_MAX but doesn't set s_max_links. This could be fixed, but using EMLINK to detect the max-link condition is simpler and more reliable. And I don't really see a reason to disable whiteout hard links. What scenario would that be useful in? Updated patch below. Changes from v5: - fix a missing dput on shutdown - don't poass workdir to ovl_cleanup_and_whiteout/ovl_whiteout - flatten out retry loop in ovl_whiteout - use EMLINK to distinguish max-links from misc failure Thanks, Miklos --- From: Chengguang Xu <cgxu519@xxxxxxxxxxxx> Subject: ovl: whiteout inode sharing Date: Fri, 24 Apr 2020 10:55:17 +0800 Share inode with different whiteout files for saving inode and speeding up delete operation. If EMLINK is encountered when linking a shared whiteout, create a new one. In case of any other error, disable sharing for this super block. Note: ofs->whiteout is protected by inode lock on workdir. Signed-off-by: Chengguang Xu <cgxu519@xxxxxxxxxxxx> Reviewed-by: Amir Goldstein <amir73il@xxxxxxxxx> Signed-off-by: Miklos Szeredi <mszeredi@xxxxxxxxxx> --- fs/overlayfs/dir.c | 49 +++++++++++++++++++++++++++++++++++------------ fs/overlayfs/overlayfs.h | 2 - fs/overlayfs/ovl_entry.h | 3 ++ fs/overlayfs/readdir.c | 2 - fs/overlayfs/super.c | 4 +++ fs/overlayfs/util.c | 3 +- 6 files changed, 48 insertions(+), 15 deletions(-) --- a/fs/overlayfs/dir.c +++ b/fs/overlayfs/dir.c @@ -62,35 +62,59 @@ struct dentry *ovl_lookup_temp(struct de } /* caller holds i_mutex on workdir */ -static struct dentry *ovl_whiteout(struct dentry *workdir) +static struct dentry *ovl_whiteout(struct ovl_fs *ofs) { int err; struct dentry *whiteout; + struct dentry *workdir = ofs->workdir; struct inode *wdir = workdir->d_inode; - whiteout = ovl_lookup_temp(workdir); - if (IS_ERR(whiteout)) - return whiteout; + if (!ofs->whiteout) { + whiteout = ovl_lookup_temp(workdir); + if (IS_ERR(whiteout)) + return whiteout; + + err = ovl_do_whiteout(wdir, whiteout); + if (err) { + dput(whiteout); + return ERR_PTR(err); + } + ofs->whiteout = whiteout; + } - err = ovl_do_whiteout(wdir, whiteout); - if (err) { + if (ofs->share_whiteout) { + whiteout = ovl_lookup_temp(workdir); + if (IS_ERR(whiteout)) + goto fallback; + + err = ovl_do_link(ofs->whiteout, wdir, whiteout); + if (!err) + goto success; + + if (err != -EMLINK) { + pr_warn("Failed to link whiteout - disabling whiteout inode sharing(nlink=%u, err=%i)\n", + ofs->whiteout->d_inode->i_nlink, err); + ofs->share_whiteout = false; + } dput(whiteout); - whiteout = ERR_PTR(err); } - +fallback: + whiteout = ofs->whiteout; + ofs->whiteout = NULL; +success: return whiteout; } /* Caller must hold i_mutex on both workdir and dir */ -int ovl_cleanup_and_whiteout(struct dentry *workdir, struct inode *dir, +int ovl_cleanup_and_whiteout(struct ovl_fs *ofs, struct inode *dir, struct dentry *dentry) { - struct inode *wdir = workdir->d_inode; + struct inode *wdir = ofs->workdir->d_inode; struct dentry *whiteout; int err; int flags = 0; - whiteout = ovl_whiteout(workdir); + whiteout = ovl_whiteout(ofs); err = PTR_ERR(whiteout); if (IS_ERR(whiteout)) return err; @@ -715,6 +739,7 @@ static bool ovl_matches_upper(struct den static int ovl_remove_and_whiteout(struct dentry *dentry, struct list_head *list) { + struct ovl_fs *ofs = OVL_FS(dentry->d_sb); struct dentry *workdir = ovl_workdir(dentry); struct dentry *upperdir = ovl_dentry_upper(dentry->d_parent); struct dentry *upper; @@ -748,7 +773,7 @@ static int ovl_remove_and_whiteout(struc goto out_dput_upper; } - err = ovl_cleanup_and_whiteout(workdir, d_inode(upperdir), upper); + err = ovl_cleanup_and_whiteout(ofs, d_inode(upperdir), upper); if (err) goto out_d_drop; --- a/fs/overlayfs/overlayfs.h +++ b/fs/overlayfs/overlayfs.h @@ -455,7 +455,7 @@ static inline void ovl_copyflags(struct /* dir.c */ extern const struct inode_operations ovl_dir_inode_operations; -int ovl_cleanup_and_whiteout(struct dentry *workdir, struct inode *dir, +int ovl_cleanup_and_whiteout(struct ovl_fs *ofs, struct inode *dir, struct dentry *dentry); struct ovl_cattr { dev_t rdev; --- a/fs/overlayfs/ovl_entry.h +++ b/fs/overlayfs/ovl_entry.h @@ -68,6 +68,7 @@ struct ovl_fs { /* Did we take the inuse lock? */ bool upperdir_locked; bool workdir_locked; + bool share_whiteout; /* Traps in ovl inode cache */ struct inode *upperdir_trap; struct inode *workbasedir_trap; @@ -77,6 +78,8 @@ struct ovl_fs { int xino_mode; /* For allocation of non-persistent inode numbers */ atomic_long_t last_ino; + /* Whiteout dentry cache */ + struct dentry *whiteout; }; static inline struct ovl_fs *OVL_FS(struct super_block *sb) --- a/fs/overlayfs/readdir.c +++ b/fs/overlayfs/readdir.c @@ -1154,7 +1154,7 @@ int ovl_indexdir_cleanup(struct ovl_fs * * 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, dir, index); } else { /* Cleanup orphan index entries */ err = ovl_cleanup(dir, index); --- a/fs/overlayfs/util.c +++ b/fs/overlayfs/util.c @@ -707,7 +707,8 @@ static void ovl_cleanup_index(struct den 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(OVL_FS(dentry->d_sb), + dir, index); } else { /* Cleanup orphan index entries */ err = ovl_cleanup(dir, index); --- a/fs/overlayfs/super.c +++ b/fs/overlayfs/super.c @@ -217,6 +217,7 @@ static void ovl_free_fs(struct ovl_fs *o iput(ofs->indexdir_trap); iput(ofs->workdir_trap); iput(ofs->upperdir_trap); + dput(ofs->whiteout); dput(ofs->indexdir); dput(ofs->workdir); if (ofs->workdir_locked) @@ -1776,6 +1777,9 @@ static int ovl_fill_super(struct super_b if (!cred) goto out_err; + /* Is there a reason anyone would want not to share whiteouts? */ + ofs->share_whiteout = true; + ofs->config.index = ovl_index_def; ofs->config.nfs_export = ovl_nfs_export_def; ofs->config.xino = ovl_xino_def();