Commit bc8df7a3 authored by Alexander Larsson's avatar Alexander Larsson Committed by Amir Goldstein
Browse files

ovl: Add an alternative type of whiteout



An xattr whiteout (called "xwhiteout" in the code) is a reguar file of
zero size with the "overlay.whiteout" xattr set. A file like this in a
directory with the "overlay.whiteouts" xattrs set will be treated the
same way as a regular whiteout.

The "overlay.whiteouts" directory xattr is used in order to
efficiently handle overlay checks in readdir(), as we only need to
checks xattrs in affected directories.

The advantage of this kind of whiteout is that they can be escaped
using the standard overlay xattr escaping mechanism. So, a file with a
"overlay.overlay.whiteout" xattr would be unescaped to
"overlay.whiteout", which could then be consumed by another overlayfs
as a whiteout.

Overlayfs itself doesn't create whiteouts like this, but a userspace
mechanism could use this alternative mechanism to convert images that
may contain whiteouts to be used with overlayfs.

To work as a whiteout for both regular overlayfs mounts as well as
userxattr mounts both the "user.overlay.whiteout*" and the
"trusted.overlay.whiteout*" xattrs will need to be created.

Signed-off-by: default avatarAlexander Larsson <alexl@redhat.com>
Reviewed-by: default avatarAmir Goldstein <amir73il@gmail.com>
Signed-off-by: default avatarAmir Goldstein <amir73il@gmail.com>
parent dad02fad
Loading
Loading
Loading
Loading
+2 −2
Original line number Diff line number Diff line
@@ -477,7 +477,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);
@@ -1211,7 +1211,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)
+10 −5
Original line number Diff line number Diff line
@@ -251,7 +251,10 @@ 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 +267,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 +439,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;

@@ -1402,7 +1403,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);
		}
+15 −0
Original line number Diff line number Diff line
@@ -49,6 +49,8 @@ enum ovl_xattr {
	OVL_XATTR_UUID,
	OVL_XATTR_METACOPY,
	OVL_XATTR_PROTATTR,
	OVL_XATTR_XWHITEOUT,
	OVL_XATTR_XWHITEOUTS,
};

enum ovl_inode_flag {
@@ -473,6 +475,7 @@ 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);
@@ -480,9 +483,21 @@ 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_origin_xattr(struct ovl_fs *ofs, const struct path *path);
bool ovl_path_check_xwhiteout_xattr(struct ovl_fs *ofs, const struct path *path);
bool ovl_path_check_xwhiteouts_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)
{
+20 −7
Original line number Diff line number Diff line
@@ -25,6 +25,7 @@ struct ovl_cache_entry {
	struct ovl_cache_entry *next_maybe_whiteout;
	bool is_upper;
	bool is_whiteout;
	bool check_xwhiteout;
	char name[];
};

@@ -47,6 +48,7 @@ struct ovl_readdir_data {
	int err;
	bool is_upper;
	bool d_type_supported;
	bool in_xwhiteouts_dir;
};

struct ovl_dir_file {
@@ -162,6 +164,8 @@ static struct ovl_cache_entry *ovl_cache_entry_new(struct ovl_readdir_data *rdd,
		p->ino = 0;
	p->is_upper = rdd->is_upper;
	p->is_whiteout = false;
	/* Defer check for overlay.whiteout to ovl_iterate() */
	p->check_xwhiteout = rdd->in_xwhiteouts_dir && d_type == DT_REG;

	if (d_type == DT_CHR) {
		p->next_maybe_whiteout = rdd->first_maybe_whiteout;
@@ -301,6 +305,8 @@ static inline int ovl_dir_read(const struct path *realpath,
	if (IS_ERR(realfile))
		return PTR_ERR(realfile);

	rdd->in_xwhiteouts_dir = rdd->dentry &&
		ovl_path_check_xwhiteouts_xattr(OVL_FS(rdd->dentry->d_sb), realpath);
	rdd->first_maybe_whiteout = NULL;
	rdd->ctx.pos = 0;
	do {
@@ -447,7 +453,7 @@ static u64 ovl_remap_lower_ino(u64 ino, int xinobits, int fsid,
}

/*
 * Set d_ino for upper entries. Non-upper entries should always report
 * Set d_ino for upper entries if needed. Non-upper entries should always report
 * the uppermost real inode ino and should not call this function.
 *
 * When not all layer are on same fs, report real ino also for upper.
@@ -455,8 +461,11 @@ static u64 ovl_remap_lower_ino(u64 ino, int xinobits, int fsid,
 * When all layers are on the same fs, and upper has a reference to
 * copy up origin, call vfs_getattr() on the overlay entry to make
 * sure that d_ino will be consistent with st_ino from stat(2).
 *
 * Also checks the overlay.whiteout xattr by doing a full lookup which will return
 * negative in this case.
 */
static int ovl_cache_update_ino(const struct path *path, struct ovl_cache_entry *p)
static int ovl_cache_update(const struct path *path, struct ovl_cache_entry *p, bool update_ino)

{
	struct dentry *dir = path->dentry;
@@ -467,7 +476,7 @@ static int ovl_cache_update_ino(const struct path *path, struct ovl_cache_entry
	int xinobits = ovl_xino_bits(ofs);
	int err = 0;

	if (!ovl_same_dev(ofs))
	if (!ovl_same_dev(ofs) && !p->check_xwhiteout)
		goto out;

	if (p->name[0] == '.') {
@@ -481,6 +490,7 @@ static int ovl_cache_update_ino(const struct path *path, struct ovl_cache_entry
			goto get;
		}
	}
	/* This checks also for xwhiteouts */
	this = lookup_one(mnt_idmap(path->mnt), p->name, dir, p->len);
	if (IS_ERR_OR_NULL(this) || !this->d_inode) {
		/* Mark a stale entry */
@@ -494,6 +504,9 @@ static int ovl_cache_update_ino(const struct path *path, struct ovl_cache_entry
	}

get:
	if (!ovl_same_dev(ofs) || !update_ino)
		goto out;

	type = ovl_path_type(this);
	if (OVL_TYPE_ORIGIN(type)) {
		struct kstat stat;
@@ -572,7 +585,7 @@ static int ovl_dir_read_impure(const struct path *path, struct list_head *list,
	list_for_each_entry_safe(p, n, list, l_node) {
		if (strcmp(p->name, ".") != 0 &&
		    strcmp(p->name, "..") != 0) {
			err = ovl_cache_update_ino(path, p);
			err = ovl_cache_update(path, p, true);
			if (err)
				return err;
		}
@@ -778,13 +791,13 @@ static int ovl_iterate(struct file *file, struct dir_context *ctx)
	while (od->cursor != &od->cache->entries) {
		p = list_entry(od->cursor, struct ovl_cache_entry, l_node);
		if (!p->is_whiteout) {
			if (!p->ino) {
				err = ovl_cache_update_ino(&file->f_path, p);
			if (!p->ino || p->check_xwhiteout) {
				err = ovl_cache_update(&file->f_path, p, !p->ino);
				if (err)
					goto out;
			}
		}
		/* ovl_cache_update_ino() sets is_whiteout on stale entry */
		/* ovl_cache_update() sets is_whiteout on stale entry */
		if (!p->is_whiteout) {
			if (!dir_emit(ctx, p->name, p->len, p->ino, p->type))
				break;
+1 −1
Original line number Diff line number Diff line
@@ -585,7 +585,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)
Loading