ovl: copy up of disconnected dentries
authorAmir Goldstein <amir73il@gmail.com>
Sun, 15 Oct 2017 15:00:20 +0000 (18:00 +0300)
committerMiklos Szeredi <mszeredi@redhat.com>
Wed, 24 Jan 2018 10:25:58 +0000 (11:25 +0100)
With NFS export, some operations on decoded file handles (e.g. open,
link, setattr, xattr_set) may call copy up with a disconnected non-dir.
In this case, we will copy up lower inode to index dir without
linking it to upper dir.

Signed-off-by: Amir Goldstein <amir73il@gmail.com>
Signed-off-by: Miklos Szeredi <mszeredi@redhat.com>
fs/overlayfs/copy_up.c
fs/overlayfs/inode.c
fs/overlayfs/util.c

index 8ef25d8..d855f50 100644 (file)
@@ -450,7 +450,10 @@ static int ovl_link_up(struct ovl_copy_up_ctx *c)
                }
        }
        inode_unlock(udir);
-       ovl_set_nlink_upper(c->dentry);
+       if (err)
+               return err;
+
+       err = ovl_set_nlink_upper(c->dentry);
 
        return err;
 }
@@ -655,6 +658,9 @@ static int ovl_do_copy_up(struct ovl_copy_up_ctx *c)
                err = ovl_get_index_name(c->lowerpath.dentry, &c->destname);
                if (err)
                        return err;
+       } else if (WARN_ON(!c->parent)) {
+               /* Disconnected dentry must be copied up to index dir */
+               return -EIO;
        } else {
                /*
                 * Mark parent "impure" because it may now contain non-pure
@@ -677,12 +683,17 @@ static int ovl_do_copy_up(struct ovl_copy_up_ctx *c)
                }
        }
 
-       if (!err && c->indexed)
+
+       if (err)
+               goto out;
+
+       if (c->indexed)
                ovl_set_flag(OVL_INDEX, d_inode(c->dentry));
 
        if (to_index) {
-               kfree(c->destname.name);
-       } else if (!err) {
+               /* Initialize nlink for copy up of disconnected dentry */
+               err = ovl_set_nlink_upper(c->dentry);
+       } else {
                struct inode *udir = d_inode(c->destdir);
 
                /* Restore timestamps on parent (best effort) */
@@ -693,6 +704,9 @@ static int ovl_do_copy_up(struct ovl_copy_up_ctx *c)
                ovl_dentry_set_upper_alias(c->dentry);
        }
 
+out:
+       if (to_index)
+               kfree(c->destname.name);
        return err;
 }
 
@@ -717,14 +731,17 @@ static int ovl_copy_up_one(struct dentry *parent, struct dentry *dentry,
        if (err)
                return err;
 
-       ovl_path_upper(parent, &parentpath);
-       ctx.destdir = parentpath.dentry;
-       ctx.destname = dentry->d_name;
+       if (parent) {
+               ovl_path_upper(parent, &parentpath);
+               ctx.destdir = parentpath.dentry;
+               ctx.destname = dentry->d_name;
 
-       err = vfs_getattr(&parentpath, &ctx.pstat,
-                         STATX_ATIME | STATX_MTIME, AT_STATX_SYNC_AS_STAT);
-       if (err)
-               return err;
+               err = vfs_getattr(&parentpath, &ctx.pstat,
+                                 STATX_ATIME | STATX_MTIME,
+                                 AT_STATX_SYNC_AS_STAT);
+               if (err)
+                       return err;
+       }
 
        /* maybe truncate regular file. this has no effect on dirs */
        if (flags & O_TRUNC)
@@ -745,7 +762,7 @@ static int ovl_copy_up_one(struct dentry *parent, struct dentry *dentry,
        } else {
                if (!ovl_dentry_upper(dentry))
                        err = ovl_do_copy_up(&ctx);
-               if (!err && !ovl_dentry_has_upper_alias(dentry))
+               if (!err && parent && !ovl_dentry_has_upper_alias(dentry))
                        err = ovl_link_up(&ctx);
                ovl_copy_up_end(dentry);
        }
@@ -758,10 +775,19 @@ int ovl_copy_up_flags(struct dentry *dentry, int flags)
 {
        int err = 0;
        const struct cred *old_cred = ovl_override_creds(dentry->d_sb);
+       bool disconnected = (dentry->d_flags & DCACHE_DISCONNECTED);
+
+       /*
+        * With NFS export, copy up can get called for a disconnected non-dir.
+        * In this case, we will copy up lower inode to index dir without
+        * linking it to upper dir.
+        */
+       if (WARN_ON(disconnected && d_is_dir(dentry)))
+               return -EIO;
 
        while (!err) {
                struct dentry *next;
-               struct dentry *parent;
+               struct dentry *parent = NULL;
 
                /*
                 * Check if copy-up has happened as well as for upper alias (in
@@ -777,12 +803,12 @@ int ovl_copy_up_flags(struct dentry *dentry, int flags)
                 *      with rename.
                 */
                if (ovl_dentry_upper(dentry) &&
-                   ovl_dentry_has_upper_alias(dentry))
+                   (ovl_dentry_has_upper_alias(dentry) || disconnected))
                        break;
 
                next = dget(dentry);
                /* find the topmost dentry not yet copied up */
-               for (;;) {
+               for (; !disconnected;) {
                        parent = dget_parent(next);
 
                        if (ovl_dentry_upper(parent))
index f8f7fac..bfd7c76 100644 (file)
@@ -351,8 +351,10 @@ struct posix_acl *ovl_get_acl(struct inode *inode, int type)
 
 static bool ovl_open_need_copy_up(struct dentry *dentry, int flags)
 {
+       /* Copy up of disconnected dentry does not set upper alias */
        if (ovl_dentry_upper(dentry) &&
-           ovl_dentry_has_upper_alias(dentry))
+           (ovl_dentry_has_upper_alias(dentry) ||
+            (dentry->d_flags & DCACHE_DISCONNECTED)))
                return false;
 
        if (special_file(d_inode(dentry)->i_mode))
index aa2234d..68541eb 100644 (file)
@@ -229,9 +229,10 @@ void ovl_dentry_set_opaque(struct dentry *dentry)
 }
 
 /*
- * For hard links it's possible for ovl_dentry_upper() to return positive, while
- * there's no actual upper alias for the inode.  Copy up code needs to know
- * about the existence of the upper alias, so it can't use ovl_dentry_upper().
+ * For hard links and decoded file handles, it's possible for ovl_dentry_upper()
+ * to return positive, while there's no actual upper alias for the inode.
+ * Copy up code needs to know about the existence of the upper alias, so it
+ * can't use ovl_dentry_upper().
  */
 bool ovl_dentry_has_upper_alias(struct dentry *dentry)
 {