On Wed, Aug 16, 2023 at 6:29 PM Alexander Larsson <alexl@xxxxxxxxxx> wrote: > > This is needed to properly stack overlay filesystems, I.E, being able > to create a whiteout file on an overlay mount and then use that as > part of the lowerdir in another overlay mount. > > The way this works is that we create a regular whiteout, but set the > `overlay.nowhiteout` xattr on it. Whenever we check if a file is a > whiteout we check this xattr and don't treat it as a whiteout if it is > set. The xattr itself is then stripped and when viewed as part of the > overlayfs mount it looks like a regular whiteout. > > Signed-off-by: Alexander Larsson <alexl@xxxxxxxxxx> > --- > fs/overlayfs/dir.c | 14 ++++++++------ > fs/overlayfs/namei.c | 14 +++++++++----- > fs/overlayfs/overlayfs.h | 13 +++++++++++++ > fs/overlayfs/readdir.c | 7 ++++++- > fs/overlayfs/super.c | 2 +- > fs/overlayfs/util.c | 20 ++++++++++++++++++++ > 6 files changed, 57 insertions(+), 13 deletions(-) > > diff --git a/fs/overlayfs/dir.c b/fs/overlayfs/dir.c > index 033fc0458a3d..4ef3a473700c 100644 > --- a/fs/overlayfs/dir.c > +++ b/fs/overlayfs/dir.c > @@ -199,6 +199,12 @@ struct dentry *ovl_create_real(struct ovl_fs *ofs, struct inode *dir, > case S_IFSOCK: > err = ovl_do_mknod(ofs, dir, newdentry, attr->mode, > attr->rdev); > + > + if (!err && S_ISCHR(attr->mode) && attr->rdev == WHITEOUT_DEV) { > + err = ovl_setxattr(ofs, newdentry, OVL_XATTR_NOWHITEOUT, > + NULL, 0); > + } > + That's not an atomic way to create an escaped whiteout. You'd want to always take the ovl_create_over_whiteout() branch when creating a whiteout (even if not over a positive lower) Thanks, Amir. > break; > > case S_IFLNK: > @@ -477,7 +483,7 @@ static int ovl_create_over_whiteout(struct dentry *dentry, struct inode *inode, > goto out_unlock; > > err = -ESTALE; > - if (d_is_negative(upper) || !IS_WHITEOUT(d_inode(upper))) > + if (d_is_negative(upper) || !ovl_upper_is_whiteout(ofs, upper)) > goto out_dput; > > newdentry = ovl_create_temp(ofs, workdir, cattr); > @@ -669,10 +675,6 @@ static int ovl_mkdir(struct mnt_idmap *idmap, struct inode *dir, > static int ovl_mknod(struct mnt_idmap *idmap, struct inode *dir, > struct dentry *dentry, umode_t mode, dev_t rdev) > { > - /* Don't allow creation of "whiteout" on overlay */ > - if (S_ISCHR(mode) && rdev == WHITEOUT_DEV) > - return -EPERM; > - > return ovl_create_object(dentry, mode, rdev, NULL); > } > > @@ -1219,7 +1221,7 @@ static int ovl_rename(struct mnt_idmap *idmap, struct inode *olddir, > } > } else { > if (!d_is_negative(newdentry)) { > - if (!new_opaque || !ovl_is_whiteout(newdentry)) > + if (!new_opaque || !ovl_upper_is_whiteout(ofs, newdentry)) > goto out_dput; > } else { > if (flags & RENAME_EXCHANGE) > diff --git a/fs/overlayfs/namei.c b/fs/overlayfs/namei.c > index 80391c687c2a..e90167789a13 100644 > --- a/fs/overlayfs/namei.c > +++ b/fs/overlayfs/namei.c > @@ -251,7 +251,9 @@ static int ovl_lookup_single(struct dentry *base, struct ovl_lookup_data *d, > err = -EREMOTE; > goto out_err; > } > - if (ovl_is_whiteout(this)) { > + path.dentry = this; > + path.mnt = d->mnt; > + if (ovl_path_is_whiteout(OVL_FS(d->sb), &path)) { > d->stop = d->opaque = true; > goto put_and_out; > } > @@ -264,8 +266,6 @@ static int ovl_lookup_single(struct dentry *base, struct ovl_lookup_data *d, > goto put_and_out; > } > > - path.dentry = this; > - path.mnt = d->mnt; > if (!d_can_lookup(this)) { > if (d->is_dir || !last_element) { > d->stop = true; > @@ -438,7 +438,7 @@ int ovl_check_origin_fh(struct ovl_fs *ofs, struct ovl_fh *fh, bool connected, > else if (IS_ERR(origin)) > return PTR_ERR(origin); > > - if (upperdentry && !ovl_is_whiteout(upperdentry) && > + if (upperdentry && !ovl_upper_is_whiteout(ofs, upperdentry) && > inode_wrong_type(d_inode(upperdentry), d_inode(origin)->i_mode)) > goto invalid; > > @@ -1383,7 +1383,11 @@ bool ovl_lower_positive(struct dentry *dentry) > break; > } > } else { > - positive = !ovl_is_whiteout(this); > + struct path path = { > + .dentry = this, > + .mnt = parentpath->layer->mnt, > + }; > + positive = !ovl_path_is_whiteout(OVL_FS(dentry->d_sb), &path); > done = true; > dput(this); > } > diff --git a/fs/overlayfs/overlayfs.h b/fs/overlayfs/overlayfs.h > index 1dbd01719f63..853335ff26f7 100644 > --- a/fs/overlayfs/overlayfs.h > +++ b/fs/overlayfs/overlayfs.h > @@ -49,6 +49,7 @@ enum ovl_xattr { > OVL_XATTR_UUID, > OVL_XATTR_METACOPY, > OVL_XATTR_PROTATTR, > + OVL_XATTR_NOWHITEOUT, > }; > > enum ovl_inode_flag { > @@ -469,16 +470,28 @@ void ovl_inode_update(struct inode *inode, struct dentry *upperdentry); > void ovl_dir_modified(struct dentry *dentry, bool impurity); > u64 ovl_inode_version_get(struct inode *inode); > bool ovl_is_whiteout(struct dentry *dentry); > +bool ovl_path_is_whiteout(struct ovl_fs *ofs, const struct path *path); > struct file *ovl_path_open(const struct path *path, int flags); > int ovl_copy_up_start(struct dentry *dentry, int flags); > void ovl_copy_up_end(struct dentry *dentry); > bool ovl_already_copied_up(struct dentry *dentry, int flags); > bool ovl_path_check_dir_xattr(struct ovl_fs *ofs, const struct path *path, > enum ovl_xattr ox); > +bool ovl_path_check_nowhiteout_xattr(struct ovl_fs *ofs, const struct path *path); > bool ovl_path_check_origin_xattr(struct ovl_fs *ofs, const struct path *path); > bool ovl_init_uuid_xattr(struct super_block *sb, struct ovl_fs *ofs, > const struct path *upperpath); > > +static inline bool ovl_upper_is_whiteout(struct ovl_fs *ofs, > + struct dentry *upperdentry) > +{ > + struct path upperpath = { > + .dentry = upperdentry, > + .mnt = ovl_upper_mnt(ofs), > + }; > + return ovl_path_is_whiteout(ofs, &upperpath); > +} > + > static inline bool ovl_check_origin_xattr(struct ovl_fs *ofs, > struct dentry *upperdentry) > { > diff --git a/fs/overlayfs/readdir.c b/fs/overlayfs/readdir.c > index de39e067ae65..9cf8e7e2961c 100644 > --- a/fs/overlayfs/readdir.c > +++ b/fs/overlayfs/readdir.c > @@ -280,7 +280,12 @@ static int ovl_check_whiteouts(const struct path *path, struct ovl_readdir_data > rdd->first_maybe_whiteout = p->next_maybe_whiteout; > dentry = lookup_one(mnt_idmap(path->mnt), p->name, dir, p->len); > if (!IS_ERR(dentry)) { > - p->is_whiteout = ovl_is_whiteout(dentry); > + struct path childpath = { > + .dentry = dentry, > + .mnt = path->mnt, > + }; > + p->is_whiteout = ovl_path_is_whiteout(OVL_FS(rdd->dentry->d_sb), > + &childpath); > dput(dentry); > } > } > diff --git a/fs/overlayfs/super.c b/fs/overlayfs/super.c > index 97bc94459f7a..71c650ba5a1a 100644 > --- a/fs/overlayfs/super.c > +++ b/fs/overlayfs/super.c > @@ -688,7 +688,7 @@ static int ovl_check_rename_whiteout(struct ovl_fs *ofs) > if (IS_ERR(whiteout)) > goto cleanup_temp; > > - err = ovl_is_whiteout(whiteout); > + err = ovl_upper_is_whiteout(ofs, whiteout); > > /* Best effort cleanup of whiteout and temp file */ > if (err) > diff --git a/fs/overlayfs/util.c b/fs/overlayfs/util.c > index 0f387092450e..da6d2abf64dd 100644 > --- a/fs/overlayfs/util.c > +++ b/fs/overlayfs/util.c > @@ -575,6 +575,16 @@ bool ovl_is_whiteout(struct dentry *dentry) > return inode && IS_WHITEOUT(inode); > } > > +/* > + * Use this over ovl_is_whiteout for upper and lower files, as it also > + * handles escaped whiteout files. > + */ > +bool ovl_path_is_whiteout(struct ovl_fs *ofs, const struct path *path) > +{ > + return ovl_is_whiteout(path->dentry) && > + !ovl_path_check_nowhiteout_xattr(ofs, path); > +} > + > struct file *ovl_path_open(const struct path *path, int flags) > { > struct inode *inode = d_inode(path->dentry); > @@ -663,6 +673,14 @@ void ovl_copy_up_end(struct dentry *dentry) > ovl_inode_unlock(d_inode(dentry)); > } > > +bool ovl_path_check_nowhiteout_xattr(struct ovl_fs *ofs, const struct path *path) > +{ > + int res; > + > + res = ovl_path_getxattr(ofs, path, OVL_XATTR_NOWHITEOUT, NULL, 0); > + return res >= 0; > +} > + > bool ovl_path_check_origin_xattr(struct ovl_fs *ofs, const struct path *path) > { > int res; > @@ -760,6 +778,7 @@ bool ovl_path_check_dir_xattr(struct ovl_fs *ofs, const struct path *path, > #define OVL_XATTR_UUID_POSTFIX "uuid" > #define OVL_XATTR_METACOPY_POSTFIX "metacopy" > #define OVL_XATTR_PROTATTR_POSTFIX "protattr" > +#define OVL_XATTR_NOWHITEOUT_POSTFIX "nowhiteout" > > #define OVL_XATTR_TAB_ENTRY(x) \ > [x] = { [false] = OVL_XATTR_TRUSTED_PREFIX x ## _POSTFIX, \ > @@ -775,6 +794,7 @@ const char *const ovl_xattr_table[][2] = { > OVL_XATTR_TAB_ENTRY(OVL_XATTR_UUID), > OVL_XATTR_TAB_ENTRY(OVL_XATTR_METACOPY), > OVL_XATTR_TAB_ENTRY(OVL_XATTR_PROTATTR), > + OVL_XATTR_TAB_ENTRY(OVL_XATTR_NOWHITEOUT), > }; > > int ovl_check_setxattr(struct ovl_fs *ofs, struct dentry *upperdentry, > -- > 2.41.0 >