Commit b10cdcdc authored by Amir Goldstein's avatar Amir Goldstein Committed by Miklos Szeredi
Browse files

ovl: untangle copy up call chain



In an attempt to dedup ~100 LOC, we ended up creating a tangled call chain,
whose branches merge and diverge in several points according to the
immutable c->tmpfile copy up mode.

This call chain was hard to analyse for locking correctness because the
locking requirements for the c->tmpfile flow were very different from the
locking requirements for the !c->tmpfile flow (i.e. directory vs.  regulare
file copy up).

Split the copy up helpers of the c->tmpfile flow from those of the
!c->tmpfile (i.e. workdir) flow and remove the c->tmpfile mode from copy up
context.

Suggested-by: default avatarAl Viro <viro@zeniv.linux.org.uk>
Signed-off-by: default avatarAmir Goldstein <amir73il@gmail.com>
Signed-off-by: default avatarMiklos Szeredi <mszeredi@redhat.com>
parent 007ea448
Loading
Loading
Loading
Loading
+159 −86
Original line number Diff line number Diff line
@@ -395,7 +395,6 @@ struct ovl_copy_up_ctx {
	struct dentry *destdir;
	struct qstr destname;
	struct dentry *workdir;
	bool tmpfile;
	bool origin;
	bool indexed;
	bool metacopy;
@@ -440,30 +439,58 @@ static int ovl_link_up(struct ovl_copy_up_ctx *c)
	return err;
}

static int ovl_install_temp(struct ovl_copy_up_ctx *c, struct dentry *temp,
			    struct dentry **newdentry)
static int ovl_copy_up_inode(struct ovl_copy_up_ctx *c, struct dentry *temp)
{
	int err;
	struct dentry *upper;
	struct inode *udir = d_inode(c->destdir);

	upper = lookup_one_len(c->destname.name, c->destdir, c->destname.len);
	if (IS_ERR(upper))
		return PTR_ERR(upper);
	err = ovl_copy_xattr(c->lowerpath.dentry, temp);
	if (err)
		return err;

	if (c->tmpfile)
		err = ovl_do_link(temp, udir, upper);
	else
		err = ovl_do_rename(d_inode(c->workdir), temp, udir, upper, 0);
	/*
	 * Store identifier of lower inode in upper inode xattr to
	 * allow lookup of the copy up origin inode.
	 *
	 * Don't set origin when we are breaking the association with a lower
	 * hard link.
	 */
	if (c->origin) {
		err = ovl_set_origin(c->dentry, c->lowerpath.dentry, temp);
		if (err)
			return err;
	}

	if (S_ISREG(c->stat.mode) && !c->metacopy) {
		struct path upperpath, datapath;

		ovl_path_upper(c->dentry, &upperpath);
		BUG_ON(upperpath.dentry != NULL);
		upperpath.dentry = temp;

		ovl_path_lowerdata(c->dentry, &datapath);
		err = ovl_copy_up_data(&datapath, &upperpath, c->stat.size);
		if (err)
			return err;
	}

	if (c->metacopy) {
		err = ovl_check_setxattr(c->dentry, temp, OVL_XATTR_METACOPY,
					 NULL, 0, -EOPNOTSUPP);
		if (err)
			return err;
	}

	inode_lock(temp->d_inode);
	if (c->metacopy)
		err = ovl_set_size(temp, &c->stat);
	if (!err)
		*newdentry = dget(c->tmpfile ? upper : temp);
	dput(upper);
		err = ovl_set_attr(temp, &c->stat);
	inode_unlock(temp->d_inode);

	return err;
}

static struct dentry *ovl_get_tmpfile(struct ovl_copy_up_ctx *c)
static struct dentry *ovl_get_workdir_temp(struct ovl_copy_up_ctx *c)
{
	int err;
	struct dentry *temp;
@@ -483,9 +510,6 @@ static struct dentry *ovl_get_tmpfile(struct ovl_copy_up_ctx *c)
	if (new_creds)
		old_creds = override_creds(new_creds);

	if (c->tmpfile)
		temp = ovl_do_tmpfile(c->workdir, c->stat.mode);
	else
	temp = ovl_create_temp(c->workdir, &cattr);

	if (new_creds) {
@@ -496,61 +520,136 @@ static struct dentry *ovl_get_tmpfile(struct ovl_copy_up_ctx *c)
	return temp;
}

static int ovl_copy_up_inode(struct ovl_copy_up_ctx *c, struct dentry *temp)
/*
 * Move temp file from workdir into place on upper dir.
 * Used when copying up directories and when upper fs doesn't support O_TMPFILE.
 *
 * Caller must hold ovl_lock_rename_workdir().
 */
static int ovl_rename_temp(struct ovl_copy_up_ctx *c, struct dentry *temp,
			   struct dentry **newdentry)
{
	int err;
	struct dentry *upper;
	struct inode *udir = d_inode(c->destdir);

	upper = lookup_one_len(c->destname.name, c->destdir, c->destname.len);
	if (IS_ERR(upper))
		return PTR_ERR(upper);

	err = ovl_do_rename(d_inode(c->workdir), temp, udir, upper, 0);
	if (!err)
		*newdentry = dget(temp);
	dput(upper);

	err = ovl_copy_xattr(c->lowerpath.dentry, temp);
	if (err)
	return err;
}

/*
	 * Store identifier of lower inode in upper inode xattr to
	 * allow lookup of the copy up origin inode.
	 *
	 * Don't set origin when we are breaking the association with a lower
	 * hard link.
 * Copyup using workdir to prepare temp file.  Used when copying up directories,
 * special files or when upper fs doesn't support O_TMPFILE.
 */
	if (c->origin) {
		err = ovl_set_origin(c->dentry, c->lowerpath.dentry, temp);
static int ovl_copy_up_workdir(struct ovl_copy_up_ctx *c)
{
	struct inode *inode;
	struct dentry *newdentry = NULL;
	struct dentry *temp;
	int err;

	err = ovl_lock_rename_workdir(c->workdir, c->destdir);
	if (err)
		return err;
	}

	if (S_ISREG(c->stat.mode) && !c->metacopy) {
		struct path upperpath, datapath;
	temp = ovl_get_workdir_temp(c);
	err = PTR_ERR(temp);
	if (IS_ERR(temp))
		goto unlock;

		ovl_path_upper(c->dentry, &upperpath);
		BUG_ON(upperpath.dentry != NULL);
		upperpath.dentry = temp;
	err = ovl_copy_up_inode(c, temp);
	if (err)
		goto cleanup;

		ovl_path_lowerdata(c->dentry, &datapath);
		err = ovl_copy_up_data(&datapath, &upperpath, c->stat.size);
	if (S_ISDIR(c->stat.mode) && c->indexed) {
		err = ovl_create_index(c->dentry, c->lowerpath.dentry, temp);
		if (err)
			return err;
			goto cleanup;
	}

	if (c->metacopy) {
		err = ovl_check_setxattr(c->dentry, temp, OVL_XATTR_METACOPY,
					 NULL, 0, -EOPNOTSUPP);
	err = ovl_rename_temp(c, temp, &newdentry);
	if (err)
		goto cleanup;

	if (!c->metacopy)
		ovl_set_upperdata(d_inode(c->dentry));
	inode = d_inode(c->dentry);
	ovl_inode_update(inode, newdentry);
	if (S_ISDIR(inode->i_mode))
		ovl_set_flag(OVL_WHITEOUTS, inode);
out_dput:
	dput(temp);
unlock:
	unlock_rename(c->workdir, c->destdir);

	return err;

cleanup:
	ovl_cleanup(d_inode(c->workdir), temp);
	goto out_dput;
}

	inode_lock(temp->d_inode);
	if (c->metacopy)
		err = ovl_set_size(temp, &c->stat);
static struct dentry *ovl_get_tmpfile(struct ovl_copy_up_ctx *c)
{
	int err;
	struct dentry *temp;
	const struct cred *old_creds = NULL;
	struct cred *new_creds = NULL;

	err = security_inode_copy_up(c->dentry, &new_creds);
	if (err < 0)
		return ERR_PTR(err);

	if (new_creds)
		old_creds = override_creds(new_creds);

	temp = ovl_do_tmpfile(c->workdir, c->stat.mode);

	if (new_creds) {
		revert_creds(old_creds);
		put_cred(new_creds);
	}

	return temp;
}

/* Link O_TMPFILE into place on upper dir */
static int ovl_link_tmpfile(struct ovl_copy_up_ctx *c, struct dentry *temp,
			    struct dentry **newdentry)
{
	int err;
	struct dentry *upper;
	struct inode *udir = d_inode(c->destdir);

	inode_lock_nested(udir, I_MUTEX_PARENT);

	upper = lookup_one_len(c->destname.name, c->destdir, c->destname.len);
	err = PTR_ERR(upper);
	if (IS_ERR(upper))
		goto unlock;

	err = ovl_do_link(temp, udir, upper);
	if (!err)
		err = ovl_set_attr(temp, &c->stat);
	inode_unlock(temp->d_inode);
		*newdentry = dget(upper);
	dput(upper);

unlock:
	inode_unlock(udir);

	return err;
}

static int ovl_copy_up_locked(struct ovl_copy_up_ctx *c)
/* Copyup using O_TMPFILE which does not require cross dir locking */
static int ovl_copy_up_tmpfile(struct ovl_copy_up_ctx *c)
{
	struct inode *udir = c->destdir->d_inode;
	struct inode *inode;
	struct dentry *newdentry = NULL;
	struct dentry *temp;
	int err;
@@ -563,35 +662,17 @@ static int ovl_copy_up_locked(struct ovl_copy_up_ctx *c)
	if (err)
		goto out;

	if (S_ISDIR(c->stat.mode) && c->indexed) {
		err = ovl_create_index(c->dentry, c->lowerpath.dentry, temp);
		if (err)
			goto out;
	}

	if (c->tmpfile) {
		inode_lock_nested(udir, I_MUTEX_PARENT);
		err = ovl_install_temp(c, temp, &newdentry);
		inode_unlock(udir);
	} else {
		err = ovl_install_temp(c, temp, &newdentry);
	}
	err = ovl_link_tmpfile(c, temp, &newdentry);
	if (err)
		goto out;

	if (!c->metacopy)
		ovl_set_upperdata(d_inode(c->dentry));
	inode = d_inode(c->dentry);
	ovl_inode_update(inode, newdentry);
	if (S_ISDIR(inode->i_mode))
		ovl_set_flag(OVL_WHITEOUTS, inode);
	ovl_inode_update(d_inode(c->dentry), newdentry);

out:
	if (err && !c->tmpfile)
		ovl_cleanup(d_inode(c->workdir), temp);
	dput(temp);
	return err;

}

/*
@@ -645,18 +726,10 @@ static int ovl_do_copy_up(struct ovl_copy_up_ctx *c)
	}

	/* Should we copyup with O_TMPFILE or with workdir? */
	if (S_ISREG(c->stat.mode) && ofs->tmpfile) {
		c->tmpfile = true;
		err = ovl_copy_up_locked(c);
	} else {
		err = ovl_lock_rename_workdir(c->workdir, c->destdir);
		if (!err) {
			err = ovl_copy_up_locked(c);
			unlock_rename(c->workdir, c->destdir);
		}
	}


	if (S_ISREG(c->stat.mode) && ofs->tmpfile)
		err = ovl_copy_up_tmpfile(c);
	else
		err = ovl_copy_up_workdir(c);
	if (err)
		goto out;