ovl: implement index dir copy up

Implement a copy up method for non-dir objects using index dir to
prevent breaking lower hardlinks on copy up.

This method requires that the inodes index dir feature was enabled and
that all underlying fs support file handle encoding/decoding.

On the first lower hardlink copy up, upper file is created in index dir,
named after the hex representation of the lower origin inode file handle.
On the second lower hardlink copy up, upper file is found in index dir,
by the same lower handle key.
On either case, the upper indexed inode is then linked to the copy up
upper path.

The index entry remains linked for future lower hardlink copy up and for
lower to upper inode map, that is needed for exporting overlayfs to NFS.

Signed-off-by: Amir Goldstein <amir73il@gmail.com>
Signed-off-by: Miklos Szeredi <mszeredi@redhat.com>
This commit is contained in:
Amir Goldstein 2017-06-20 15:25:46 +03:00 committed by Miklos Szeredi
parent fd210b7d67
commit 59be09712a
3 changed files with 103 additions and 37 deletions

View File

@ -316,6 +316,29 @@ static int ovl_set_origin(struct dentry *dentry, struct dentry *lower,
return err;
}
static int ovl_link_up(struct dentry *parent, struct dentry *dentry)
{
int err;
struct dentry *upper;
struct dentry *upperdir = ovl_dentry_upper(parent);
struct inode *udir = d_inode(upperdir);
inode_lock_nested(udir, I_MUTEX_PARENT);
upper = lookup_one_len(dentry->d_name.name, upperdir,
dentry->d_name.len);
err = PTR_ERR(upper);
if (!IS_ERR(upper)) {
err = ovl_do_link(ovl_dentry_upper(dentry), udir, upper, true);
dput(upper);
if (!err)
ovl_dentry_set_upper_alias(dentry);
}
inode_unlock(udir);
return err;
}
struct ovl_copy_up_ctx {
struct dentry *parent;
struct dentry *dentry;
@ -323,9 +346,11 @@ struct ovl_copy_up_ctx {
struct kstat stat;
struct kstat pstat;
const char *link;
struct dentry *upperdir;
struct dentry *destdir;
struct qstr destname;
struct dentry *workdir;
bool tmpfile;
bool origin;
};
static int ovl_install_temp(struct ovl_copy_up_ctx *c, struct dentry *temp,
@ -333,10 +358,9 @@ static int ovl_install_temp(struct ovl_copy_up_ctx *c, struct dentry *temp,
{
int err;
struct dentry *upper;
struct inode *udir = d_inode(c->upperdir);
struct inode *udir = d_inode(c->destdir);
upper = lookup_one_len(c->dentry->d_name.name, c->upperdir,
c->dentry->d_name.len);
upper = lookup_one_len(c->destname.name, c->destdir, c->destname.len);
if (IS_ERR(upper))
return PTR_ERR(upper);
@ -345,11 +369,8 @@ static int ovl_install_temp(struct ovl_copy_up_ctx *c, struct dentry *temp,
else
err = ovl_do_rename(d_inode(c->workdir), temp, udir, upper, 0);
/* Restore timestamps on parent (best effort) */
if (!err) {
ovl_set_timestamps(c->upperdir, &c->pstat);
if (!err)
*newdentry = dget(c->tmpfile ? upper : temp);
}
dput(upper);
return err;
@ -439,7 +460,7 @@ static int ovl_copy_up_inode(struct ovl_copy_up_ctx *c, struct dentry *temp)
* Don't set origin when we are breaking the association with a lower
* hard link.
*/
if (S_ISDIR(c->stat.mode) || c->stat.nlink == 1) {
if (c->origin) {
err = ovl_set_origin(c->dentry, c->lowerpath.dentry, temp);
if (err)
return err;
@ -450,7 +471,7 @@ static int ovl_copy_up_inode(struct ovl_copy_up_ctx *c, struct dentry *temp)
static int ovl_copy_up_locked(struct ovl_copy_up_ctx *c)
{
struct inode *udir = c->upperdir->d_inode;
struct inode *udir = c->destdir->d_inode;
struct dentry *newdentry = NULL;
struct dentry *temp = NULL;
int err;
@ -473,7 +494,6 @@ static int ovl_copy_up_locked(struct ovl_copy_up_ctx *c)
if (err)
goto out_cleanup;
ovl_dentry_set_upper_alias(c->dentry);
ovl_inode_update(d_inode(c->dentry), newdentry);
out:
dput(temp);
@ -498,24 +518,57 @@ static int ovl_do_copy_up(struct ovl_copy_up_ctx *c)
{
int err;
struct ovl_fs *ofs = c->dentry->d_sb->s_fs_info;
bool indexed = false;
/* Mark parent "impure" because it may now contain non-pure upper */
err = ovl_set_impure(c->parent, c->upperdir);
if (ovl_indexdir(c->dentry->d_sb) && !S_ISDIR(c->stat.mode) &&
c->stat.nlink > 1)
indexed = true;
if (S_ISDIR(c->stat.mode) || c->stat.nlink == 1 || indexed)
c->origin = true;
if (indexed) {
c->destdir = ovl_indexdir(c->dentry->d_sb);
err = ovl_get_index_name(c->lowerpath.dentry, &c->destname);
if (err)
return err;
} else {
/*
* Mark parent "impure" because it may now contain non-pure
* upper
*/
err = ovl_set_impure(c->parent, c->destdir);
if (err)
return err;
}
/* Should we copyup with O_TMPFILE or with workdir? */
if (S_ISREG(c->stat.mode) && ofs->tmpfile) {
c->tmpfile = true;
return ovl_copy_up_locked(c);
}
err = ovl_copy_up_locked(c);
} else {
err = -EIO;
if (lock_rename(c->workdir, c->upperdir) != NULL) {
if (lock_rename(c->workdir, c->destdir) != NULL) {
pr_err("overlayfs: failed to lock workdir+upperdir\n");
} else {
err = ovl_copy_up_locked(c);
unlock_rename(c->workdir, c->upperdir);
unlock_rename(c->workdir, c->destdir);
}
}
if (indexed) {
if (!err)
ovl_set_flag(OVL_INDEX, d_inode(c->dentry));
kfree(c->destname.name);
} else if (!err) {
struct inode *udir = d_inode(c->destdir);
/* Restore timestamps on parent (best effort) */
inode_lock(udir);
ovl_set_timestamps(c->destdir, &c->pstat);
inode_unlock(udir);
ovl_dentry_set_upper_alias(c->dentry);
}
return err;
@ -543,7 +596,8 @@ static int ovl_copy_up_one(struct dentry *parent, struct dentry *dentry,
return err;
ovl_path_upper(parent, &parentpath);
ctx.upperdir = parentpath.dentry;
ctx.destdir = parentpath.dentry;
ctx.destname = dentry->d_name;
err = vfs_getattr(&parentpath, &ctx.pstat,
STATX_ATIME | STATX_MTIME, AT_STATX_SYNC_AS_STAT);
@ -567,7 +621,10 @@ static int ovl_copy_up_one(struct dentry *parent, struct dentry *dentry,
if (err > 0)
err = 0;
} else {
if (!ovl_dentry_upper(dentry))
err = ovl_do_copy_up(&ctx);
if (!err && !ovl_dentry_has_upper_alias(dentry))
err = ovl_link_up(parent, dentry);
ovl_copy_up_end(dentry);
}
do_delayed_call(&done);
@ -583,9 +640,22 @@ int ovl_copy_up_flags(struct dentry *dentry, int flags)
while (!err) {
struct dentry *next;
struct dentry *parent;
enum ovl_path_type type = ovl_path_type(dentry);
if (OVL_TYPE_UPPER(type))
/*
* Check if copy-up has happened as well as for upper alias (in
* case of hard links) is there.
*
* Both checks are lockless:
* - false negatives: will recheck under oi->lock
* - false positives:
* + ovl_dentry_upper() uses memory barriers to ensure the
* upper dentry is up-to-date
* + ovl_dentry_has_upper_alias() relies on locking of
* upper parent i_rwsem to prevent reordering copy-up
* with rename.
*/
if (ovl_dentry_upper(dentry) &&
ovl_dentry_has_upper_alias(dentry))
break;
next = dget(dentry);
@ -593,8 +663,7 @@ int ovl_copy_up_flags(struct dentry *dentry, int flags)
for (;;) {
parent = dget_parent(next);
type = ovl_path_type(parent);
if (OVL_TYPE_UPPER(type))
if (ovl_dentry_upper(parent))
break;
dput(next);

View File

@ -305,13 +305,13 @@ struct posix_acl *ovl_get_acl(struct inode *inode, int type)
return acl;
}
static bool ovl_open_need_copy_up(int flags, enum ovl_path_type type,
struct dentry *realdentry)
static bool ovl_open_need_copy_up(struct dentry *dentry, int flags)
{
if (OVL_TYPE_UPPER(type))
if (ovl_dentry_upper(dentry) &&
ovl_dentry_has_upper_alias(dentry))
return false;
if (special_file(realdentry->d_inode->i_mode))
if (special_file(d_inode(dentry)->i_mode))
return false;
if (!(OPEN_FMODE(flags) & FMODE_WRITE) && !(flags & O_TRUNC))
@ -323,11 +323,8 @@ static bool ovl_open_need_copy_up(int flags, enum ovl_path_type type,
int ovl_open_maybe_copy_up(struct dentry *dentry, unsigned int file_flags)
{
int err = 0;
struct path realpath;
enum ovl_path_type type;
type = ovl_path_real(dentry, &realpath);
if (ovl_open_need_copy_up(file_flags, type, realpath.dentry)) {
if (ovl_open_need_copy_up(dentry, file_flags)) {
err = ovl_want_write(dentry);
if (!err) {
err = ovl_copy_up_flags(dentry, file_flags);

View File

@ -302,7 +302,7 @@ int ovl_copy_up_start(struct dentry *dentry)
int err;
err = mutex_lock_interruptible(&oi->lock);
if (!err && ovl_dentry_upper(dentry)) {
if (!err && ovl_dentry_has_upper_alias(dentry)) {
err = 1; /* Already copied up */
mutex_unlock(&oi->lock);
}