aboutsummaryrefslogtreecommitdiffstats
path: root/fs/overlayfs/copy_up.c
diff options
context:
space:
mode:
authorAmir Goldstein <amir73il@gmail.com>2017-06-20 15:25:46 +0300
committerMiklos Szeredi <mszeredi@redhat.com>2017-07-04 22:03:19 +0200
commit59be09712ab98a3060f13e31343c7abb9bc4583d (patch)
tree5915d35f0fb5e0e7682b46d7042b20220ea30055 /fs/overlayfs/copy_up.c
parentovl: move copy up lock out (diff)
downloadlinux-dev-59be09712ab98a3060f13e31343c7abb9bc4583d.tar.xz
linux-dev-59be09712ab98a3060f13e31343c7abb9bc4583d.zip
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>
Diffstat (limited to 'fs/overlayfs/copy_up.c')
-rw-r--r--fs/overlayfs/copy_up.c125
1 files changed, 97 insertions, 28 deletions
diff --git a/fs/overlayfs/copy_up.c b/fs/overlayfs/copy_up.c
index 0d9de353f42b..9f5a47338e59 100644
--- a/fs/overlayfs/copy_up.c
+++ b/fs/overlayfs/copy_up.c
@@ -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 (err)
- return err;
+ 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->destdir) != NULL) {
+ pr_err("overlayfs: failed to lock workdir+upperdir\n");
+ } else {
+ err = ovl_copy_up_locked(c);
+ unlock_rename(c->workdir, c->destdir);
+ }
}
- err = -EIO;
- if (lock_rename(c->workdir, c->upperdir) != NULL) {
- pr_err("overlayfs: failed to lock workdir+upperdir\n");
- } else {
- err = ovl_copy_up_locked(c);
- unlock_rename(c->workdir, c->upperdir);
+ 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 {
- err = ovl_do_copy_up(&ctx);
+ 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);