summaryrefslogtreecommitdiff
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
parentfd210b7d67ee3768bf1ad3e07d55797d4b45fcc1 (diff)
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>
-rw-r--r--fs/overlayfs/copy_up.c125
-rw-r--r--fs/overlayfs/inode.c13
-rw-r--r--fs/overlayfs/util.c2
3 files changed, 103 insertions, 37 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);
diff --git a/fs/overlayfs/inode.c b/fs/overlayfs/inode.c
index d9fe07defca3..44d262a0a77e 100644
--- a/fs/overlayfs/inode.c
+++ b/fs/overlayfs/inode.c
@@ -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);
diff --git a/fs/overlayfs/util.c b/fs/overlayfs/util.c
index 38fa75228c66..a290be449b8b 100644
--- a/fs/overlayfs/util.c
+++ b/fs/overlayfs/util.c
@@ -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);
}