Merge tag 'ovl-fixes-4.18' of git://git.kernel.org/pub/scm/linux/kernel/git/mszeredi/vfs
authorLinus Torvalds <torvalds@linux-foundation.org>
Thu, 7 Jun 2018 15:53:50 +0000 (08:53 -0700)
committerLinus Torvalds <torvalds@linux-foundation.org>
Thu, 7 Jun 2018 15:53:50 +0000 (08:53 -0700)
Pull overlayfs fixes from Miklos Szeredi:
 "This contains a fix for the vfs_mkdir() issue discovered by Al, as
  well as other fixes and cleanups"

* tag 'ovl-fixes-4.18' of git://git.kernel.org/pub/scm/linux/kernel/git/mszeredi/vfs:
  ovl: use inode_insert5() to hash a newly created inode
  ovl: Pass argument to ovl_get_inode() in a structure
  vfs: factor out inode_insert5()
  ovl: clean up copy-up error paths
  ovl: return EIO on internal error
  ovl: make ovl_create_real() cope with vfs_mkdir() safely
  ovl: create helper ovl_create_temp()
  ovl: return dentry from ovl_create_real()
  ovl: struct cattr cleanups
  ovl: strip debug argument from ovl_do_ helpers
  ovl: remove WARN_ON() real inode attributes mismatch
  ovl: Kconfig documentation fixes
  ovl: update documentation for unionmount-testsuite

Documentation/filesystems/overlayfs.txt
fs/inode.c
fs/overlayfs/Kconfig
fs/overlayfs/copy_up.c
fs/overlayfs/dir.c
fs/overlayfs/export.c
fs/overlayfs/inode.c
fs/overlayfs/namei.c
fs/overlayfs/overlayfs.h
fs/overlayfs/super.c
include/linux/fs.h

index 961b287ef3233ef5bac93ab76f7ed30377d80e34..72615a2c07529ab487e3c9aa4604ed9f0755e66d 100644 (file)
@@ -429,11 +429,12 @@ This verification may cause significant overhead in some cases.
 Testsuite
 ---------
 
-There's testsuite developed by David Howells at:
+There's a testsuite originally developed by David Howells and currently
+maintained by Amir Goldstein at:
 
-  git://git.infradead.org/users/dhowells/unionmount-testsuite.git
+  https://github.com/amir73il/unionmount-testsuite.git
 
 Run as root:
 
   # cd unionmount-testsuite
-  # ./run --ov
+  # ./run --ov --verify
index 3b55391072f3d353ba6ad50219171e49d91127d7..0df41bb77e0f426bde8901b3124071bf89701da0 100644 (file)
@@ -1003,6 +1003,70 @@ void unlock_two_nondirectories(struct inode *inode1, struct inode *inode2)
 }
 EXPORT_SYMBOL(unlock_two_nondirectories);
 
+/**
+ * inode_insert5 - obtain an inode from a mounted file system
+ * @inode:     pre-allocated inode to use for insert to cache
+ * @hashval:   hash value (usually inode number) to get
+ * @test:      callback used for comparisons between inodes
+ * @set:       callback used to initialize a new struct inode
+ * @data:      opaque data pointer to pass to @test and @set
+ *
+ * Search for the inode specified by @hashval and @data in the inode cache,
+ * and if present it is return it with an increased reference count. This is
+ * a variant of iget5_locked() for callers that don't want to fail on memory
+ * allocation of inode.
+ *
+ * If the inode is not in cache, insert the pre-allocated inode to cache and
+ * return it locked, hashed, and with the I_NEW flag set. The file system gets
+ * to fill it in before unlocking it via unlock_new_inode().
+ *
+ * Note both @test and @set are called with the inode_hash_lock held, so can't
+ * sleep.
+ */
+struct inode *inode_insert5(struct inode *inode, unsigned long hashval,
+                           int (*test)(struct inode *, void *),
+                           int (*set)(struct inode *, void *), void *data)
+{
+       struct hlist_head *head = inode_hashtable + hash(inode->i_sb, hashval);
+       struct inode *old;
+
+again:
+       spin_lock(&inode_hash_lock);
+       old = find_inode(inode->i_sb, head, test, data);
+       if (unlikely(old)) {
+               /*
+                * Uhhuh, somebody else created the same inode under us.
+                * Use the old inode instead of the preallocated one.
+                */
+               spin_unlock(&inode_hash_lock);
+               wait_on_inode(old);
+               if (unlikely(inode_unhashed(old))) {
+                       iput(old);
+                       goto again;
+               }
+               return old;
+       }
+
+       if (set && unlikely(set(inode, data))) {
+               inode = NULL;
+               goto unlock;
+       }
+
+       /*
+        * Return the locked inode with I_NEW set, the
+        * caller is responsible for filling in the contents
+        */
+       spin_lock(&inode->i_lock);
+       inode->i_state |= I_NEW;
+       hlist_add_head(&inode->i_hash, head);
+       spin_unlock(&inode->i_lock);
+unlock:
+       spin_unlock(&inode_hash_lock);
+
+       return inode;
+}
+EXPORT_SYMBOL(inode_insert5);
+
 /**
  * iget5_locked - obtain an inode from a mounted file system
  * @sb:                super block of file system
@@ -1027,66 +1091,18 @@ struct inode *iget5_locked(struct super_block *sb, unsigned long hashval,
                int (*test)(struct inode *, void *),
                int (*set)(struct inode *, void *), void *data)
 {
-       struct hlist_head *head = inode_hashtable + hash(sb, hashval);
-       struct inode *inode;
-again:
-       spin_lock(&inode_hash_lock);
-       inode = find_inode(sb, head, test, data);
-       spin_unlock(&inode_hash_lock);
+       struct inode *inode = ilookup5(sb, hashval, test, data);
 
-       if (inode) {
-               wait_on_inode(inode);
-               if (unlikely(inode_unhashed(inode))) {
-                       iput(inode);
-                       goto again;
-               }
-               return inode;
-       }
+       if (!inode) {
+               struct inode *new = new_inode(sb);
 
-       inode = alloc_inode(sb);
-       if (inode) {
-               struct inode *old;
-
-               spin_lock(&inode_hash_lock);
-               /* We released the lock, so.. */
-               old = find_inode(sb, head, test, data);
-               if (!old) {
-                       if (set(inode, data))
-                               goto set_failed;
-
-                       spin_lock(&inode->i_lock);
-                       inode->i_state = I_NEW;
-                       hlist_add_head(&inode->i_hash, head);
-                       spin_unlock(&inode->i_lock);
-                       inode_sb_list_add(inode);
-                       spin_unlock(&inode_hash_lock);
-
-                       /* Return the locked inode with I_NEW set, the
-                        * caller is responsible for filling in the contents
-                        */
-                       return inode;
-               }
-
-               /*
-                * Uhhuh, somebody else created the same inode under
-                * us. Use the old inode instead of the one we just
-                * allocated.
-                */
-               spin_unlock(&inode_hash_lock);
-               destroy_inode(inode);
-               inode = old;
-               wait_on_inode(inode);
-               if (unlikely(inode_unhashed(inode))) {
-                       iput(inode);
-                       goto again;
+               if (new) {
+                       inode = inode_insert5(new, hashval, test, set, data);
+                       if (unlikely(inode != new))
+                               iput(new);
                }
        }
        return inode;
-
-set_failed:
-       spin_unlock(&inode_hash_lock);
-       destroy_inode(inode);
-       return NULL;
 }
 EXPORT_SYMBOL(iget5_locked);
 
@@ -1427,43 +1443,13 @@ EXPORT_SYMBOL(insert_inode_locked);
 int insert_inode_locked4(struct inode *inode, unsigned long hashval,
                int (*test)(struct inode *, void *), void *data)
 {
-       struct super_block *sb = inode->i_sb;
-       struct hlist_head *head = inode_hashtable + hash(sb, hashval);
+       struct inode *old = inode_insert5(inode, hashval, test, NULL, data);
 
-       while (1) {
-               struct inode *old = NULL;
-
-               spin_lock(&inode_hash_lock);
-               hlist_for_each_entry(old, head, i_hash) {
-                       if (old->i_sb != sb)
-                               continue;
-                       if (!test(old, data))
-                               continue;
-                       spin_lock(&old->i_lock);
-                       if (old->i_state & (I_FREEING|I_WILL_FREE)) {
-                               spin_unlock(&old->i_lock);
-                               continue;
-                       }
-                       break;
-               }
-               if (likely(!old)) {
-                       spin_lock(&inode->i_lock);
-                       inode->i_state |= I_NEW;
-                       hlist_add_head(&inode->i_hash, head);
-                       spin_unlock(&inode->i_lock);
-                       spin_unlock(&inode_hash_lock);
-                       return 0;
-               }
-               __iget(old);
-               spin_unlock(&old->i_lock);
-               spin_unlock(&inode_hash_lock);
-               wait_on_inode(old);
-               if (unlikely(!inode_unhashed(old))) {
-                       iput(old);
-                       return -EBUSY;
-               }
+       if (old != inode) {
                iput(old);
+               return -EBUSY;
        }
+       return 0;
 }
 EXPORT_SYMBOL(insert_inode_locked4);
 
index 17032631c5cf62cc200c8bf2f897bb3e4c0c96cb..9384164253ac6e825c0174e66039088f5a0931a3 100644 (file)
@@ -11,7 +11,7 @@ config OVERLAY_FS
          For more information see Documentation/filesystems/overlayfs.txt
 
 config OVERLAY_FS_REDIRECT_DIR
-       bool "Overlayfs: turn on redirect dir feature by default"
+       bool "Overlayfs: turn on redirect directory feature by default"
        depends on OVERLAY_FS
        help
          If this config option is enabled then overlay filesystems will use
@@ -46,7 +46,7 @@ config OVERLAY_FS_INDEX
        depends on OVERLAY_FS
        help
          If this config option is enabled then overlay filesystems will use
-         the inodes index dir to map lower inodes to upper inodes by default.
+         the index directory to map lower inodes to upper inodes by default.
          In this case it is still possible to turn off index globally with the
          "index=off" module option or on a filesystem instance basis with the
          "index=off" mount option.
@@ -66,7 +66,7 @@ config OVERLAY_FS_NFS_EXPORT
        depends on OVERLAY_FS_INDEX
        help
          If this config option is enabled then overlay filesystems will use
-         the inodes index dir to decode overlay NFS file handles by default.
+         the index directory to decode overlay NFS file handles by default.
          In this case, it is still possible to turn off NFS export support
          globally with the "nfs_export=off" module option or on a filesystem
          instance basis with the "nfs_export=off" mount option.
index 8bede0742619007c92c63cd1cc5e388a48d04463..ddaddb4ce4c3dfe515b438fd6aafc51885eba362 100644 (file)
@@ -365,17 +365,14 @@ static int ovl_create_index(struct dentry *dentry, struct dentry *origin,
        if (err)
                return err;
 
-       temp = ovl_lookup_temp(indexdir);
+       temp = ovl_create_temp(indexdir, OVL_CATTR(S_IFDIR | 0));
+       err = PTR_ERR(temp);
        if (IS_ERR(temp))
-               goto temp_err;
-
-       err = ovl_do_mkdir(dir, temp, S_IFDIR, true);
-       if (err)
-               goto out;
+               goto free_name;
 
        err = ovl_set_upper_fh(upper, temp);
        if (err)
-               goto out_cleanup;
+               goto out;
 
        index = lookup_one_len(name.name, indexdir, name.len);
        if (IS_ERR(index)) {
@@ -384,23 +381,13 @@ static int ovl_create_index(struct dentry *dentry, struct dentry *origin,
                err = ovl_do_rename(dir, temp, dir, index, 0);
                dput(index);
        }
-
-       if (err)
-               goto out_cleanup;
-
 out:
+       if (err)
+               ovl_cleanup(dir, temp);
        dput(temp);
+free_name:
        kfree(name.name);
        return err;
-
-temp_err:
-       err = PTR_ERR(temp);
-       temp = NULL;
-       goto out;
-
-out_cleanup:
-       ovl_cleanup(dir, temp);
-       goto out;
 }
 
 struct ovl_copy_up_ctx {
@@ -439,8 +426,7 @@ static int ovl_link_up(struct ovl_copy_up_ctx *c)
                               c->dentry->d_name.len);
        err = PTR_ERR(upper);
        if (!IS_ERR(upper)) {
-               err = ovl_do_link(ovl_dentry_upper(c->dentry), udir, upper,
-                                 true);
+               err = ovl_do_link(ovl_dentry_upper(c->dentry), udir, upper);
                dput(upper);
 
                if (!err) {
@@ -470,7 +456,7 @@ static int ovl_install_temp(struct ovl_copy_up_ctx *c, struct dentry *temp,
                return PTR_ERR(upper);
 
        if (c->tmpfile)
-               err = ovl_do_link(temp, udir, upper, true);
+               err = ovl_do_link(temp, udir, upper);
        else
                err = ovl_do_rename(d_inode(c->workdir), temp, udir, upper, 0);
 
@@ -481,13 +467,13 @@ static int ovl_install_temp(struct ovl_copy_up_ctx *c, struct dentry *temp,
        return err;
 }
 
-static int ovl_get_tmpfile(struct ovl_copy_up_ctx *c, struct dentry **tempp)
+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;
-       struct cattr cattr = {
+       struct ovl_cattr cattr = {
                /* Can't properly set mode on creation because of the umask */
                .mode = c->stat.mode & S_IFMT,
                .rdev = c->stat.rdev,
@@ -495,41 +481,24 @@ static int ovl_get_tmpfile(struct ovl_copy_up_ctx *c, struct dentry **tempp)
        };
 
        err = security_inode_copy_up(c->dentry, &new_creds);
+       temp = ERR_PTR(err);
        if (err < 0)
                goto out;
 
        if (new_creds)
                old_creds = override_creds(new_creds);
 
-       if (c->tmpfile) {
+       if (c->tmpfile)
                temp = ovl_do_tmpfile(c->workdir, c->stat.mode);
-               if (IS_ERR(temp))
-                       goto temp_err;
-       } else {
-               temp = ovl_lookup_temp(c->workdir);
-               if (IS_ERR(temp))
-                       goto temp_err;
-
-               err = ovl_create_real(d_inode(c->workdir), temp, &cattr,
-                                     NULL, true);
-               if (err) {
-                       dput(temp);
-                       goto out;
-               }
-       }
-       err = 0;
-       *tempp = temp;
+       else
+               temp = ovl_create_temp(c->workdir, &cattr);
 out:
        if (new_creds) {
                revert_creds(old_creds);
                put_cred(new_creds);
        }
 
-       return err;
-
-temp_err:
-       err = PTR_ERR(temp);
-       goto out;
+       return temp;
 }
 
 static int ovl_copy_up_inode(struct ovl_copy_up_ctx *c, struct dentry *temp)
@@ -579,21 +548,21 @@ static int ovl_copy_up_locked(struct ovl_copy_up_ctx *c)
        struct inode *udir = c->destdir->d_inode;
        struct inode *inode;
        struct dentry *newdentry = NULL;
-       struct dentry *temp = NULL;
+       struct dentry *temp;
        int err;
 
-       err = ovl_get_tmpfile(c, &temp);
-       if (err)
-               goto out;
+       temp = ovl_get_tmpfile(c);
+       if (IS_ERR(temp))
+               return PTR_ERR(temp);
 
        err = ovl_copy_up_inode(c, temp);
        if (err)
-               goto out_cleanup;
+               goto out;
 
        if (S_ISDIR(c->stat.mode) && c->indexed) {
                err = ovl_create_index(c->dentry, c->lowerpath.dentry, temp);
                if (err)
-                       goto out_cleanup;
+                       goto out;
        }
 
        if (c->tmpfile) {
@@ -604,7 +573,7 @@ static int ovl_copy_up_locked(struct ovl_copy_up_ctx *c)
                err = ovl_install_temp(c, temp, &newdentry);
        }
        if (err)
-               goto out_cleanup;
+               goto out;
 
        inode = d_inode(c->dentry);
        ovl_inode_update(inode, newdentry);
@@ -612,13 +581,11 @@ static int ovl_copy_up_locked(struct ovl_copy_up_ctx *c)
                ovl_set_flag(OVL_WHITEOUTS, inode);
 
 out:
+       if (err && !c->tmpfile)
+               ovl_cleanup(d_inode(c->workdir), temp);
        dput(temp);
        return err;
 
-out_cleanup:
-       if (!c->tmpfile)
-               ovl_cleanup(d_inode(c->workdir), temp);
-       goto out;
 }
 
 /*
index 839709c7803a59bdd2ee965d4ef768e60eae20a7..f480b1a2cd2e5ada2a7a3c978b00d541839b635a 100644 (file)
@@ -43,7 +43,7 @@ int ovl_cleanup(struct inode *wdir, struct dentry *wdentry)
        return err;
 }
 
-struct dentry *ovl_lookup_temp(struct dentry *workdir)
+static struct dentry *ovl_lookup_temp(struct dentry *workdir)
 {
        struct dentry *temp;
        char name[20];
@@ -114,36 +114,72 @@ kill_whiteout:
        goto out;
 }
 
-int ovl_create_real(struct inode *dir, struct dentry *newdentry,
-                   struct cattr *attr, struct dentry *hardlink, bool debug)
+static int ovl_mkdir_real(struct inode *dir, struct dentry **newdentry,
+                         umode_t mode)
 {
        int err;
+       struct dentry *d, *dentry = *newdentry;
 
+       err = ovl_do_mkdir(dir, dentry, mode);
+       if (err)
+               return err;
+
+       if (likely(!d_unhashed(dentry)))
+               return 0;
+
+       /*
+        * vfs_mkdir() may succeed and leave the dentry passed
+        * to it unhashed and negative. If that happens, try to
+        * lookup a new hashed and positive dentry.
+        */
+       d = lookup_one_len(dentry->d_name.name, dentry->d_parent,
+                          dentry->d_name.len);
+       if (IS_ERR(d)) {
+               pr_warn("overlayfs: failed lookup after mkdir (%pd2, err=%i).\n",
+                       dentry, err);
+               return PTR_ERR(d);
+       }
+       dput(dentry);
+       *newdentry = d;
+
+       return 0;
+}
+
+struct dentry *ovl_create_real(struct inode *dir, struct dentry *newdentry,
+                              struct ovl_cattr *attr)
+{
+       int err;
+
+       if (IS_ERR(newdentry))
+               return newdentry;
+
+       err = -ESTALE;
        if (newdentry->d_inode)
-               return -ESTALE;
+               goto out;
 
-       if (hardlink) {
-               err = ovl_do_link(hardlink, dir, newdentry, debug);
+       if (attr->hardlink) {
+               err = ovl_do_link(attr->hardlink, dir, newdentry);
        } else {
                switch (attr->mode & S_IFMT) {
                case S_IFREG:
-                       err = ovl_do_create(dir, newdentry, attr->mode, debug);
+                       err = ovl_do_create(dir, newdentry, attr->mode);
                        break;
 
                case S_IFDIR:
-                       err = ovl_do_mkdir(dir, newdentry, attr->mode, debug);
+                       /* mkdir is special... */
+                       err =  ovl_mkdir_real(dir, &newdentry, attr->mode);
                        break;
 
                case S_IFCHR:
                case S_IFBLK:
                case S_IFIFO:
                case S_IFSOCK:
-                       err = ovl_do_mknod(dir, newdentry,
-                                          attr->mode, attr->rdev, debug);
+                       err = ovl_do_mknod(dir, newdentry, attr->mode,
+                                          attr->rdev);
                        break;
 
                case S_IFLNK:
-                       err = ovl_do_symlink(dir, newdentry, attr->link, debug);
+                       err = ovl_do_symlink(dir, newdentry, attr->link);
                        break;
 
                default:
@@ -155,9 +191,20 @@ int ovl_create_real(struct inode *dir, struct dentry *newdentry,
                 * Not quite sure if non-instantiated dentry is legal or not.
                 * VFS doesn't seem to care so check and warn here.
                 */
-               err = -ENOENT;
+               err = -EIO;
        }
-       return err;
+out:
+       if (err) {
+               dput(newdentry);
+               return ERR_PTR(err);
+       }
+       return newdentry;
+}
+
+struct dentry *ovl_create_temp(struct dentry *workdir, struct ovl_cattr *attr)
+{
+       return ovl_create_real(d_inode(workdir), ovl_lookup_temp(workdir),
+                              attr);
 }
 
 static int ovl_set_opaque_xerr(struct dentry *dentry, struct dentry *upper,
@@ -182,24 +229,54 @@ static int ovl_set_opaque(struct dentry *dentry, struct dentry *upperdentry)
        return ovl_set_opaque_xerr(dentry, upperdentry, -EIO);
 }
 
-/* Common operations required to be done after creation of file on upper */
-static void ovl_instantiate(struct dentry *dentry, struct inode *inode,
-                           struct dentry *newdentry, bool hardlink)
+/*
+ * Common operations required to be done after creation of file on upper.
+ * If @hardlink is false, then @inode is a pre-allocated inode, we may or
+ * may not use to instantiate the new dentry.
+ */
+static int ovl_instantiate(struct dentry *dentry, struct inode *inode,
+                          struct dentry *newdentry, bool hardlink)
 {
+       struct ovl_inode_params oip = {
+               .upperdentry = newdentry,
+               .newinode = inode,
+       };
+
        ovl_dentry_version_inc(dentry->d_parent, false);
        ovl_dentry_set_upper_alias(dentry);
        if (!hardlink) {
-               ovl_inode_update(inode, newdentry);
-               ovl_copyattr(newdentry->d_inode, inode);
+               /*
+                * ovl_obtain_alias() can be called after ovl_create_real()
+                * and before we get here, so we may get an inode from cache
+                * with the same real upperdentry that is not the inode we
+                * pre-allocated.  In this case we will use the cached inode
+                * to instantiate the new dentry.
+                *
+                * XXX: if we ever use ovl_obtain_alias() to decode directory
+                * file handles, need to use ovl_get_inode_locked() and
+                * d_instantiate_new() here to prevent from creating two
+                * hashed directory inode aliases.
+                */
+               inode = ovl_get_inode(dentry->d_sb, &oip);
+               if (WARN_ON(IS_ERR(inode)))
+                       return PTR_ERR(inode);
        } else {
                WARN_ON(ovl_inode_real(inode) != d_inode(newdentry));
                dput(newdentry);
                inc_nlink(inode);
        }
+
        d_instantiate(dentry, inode);
+       if (inode != oip.newinode) {
+               pr_warn_ratelimited("overlayfs: newly created inode found in cache (%pd2)\n",
+                                   dentry);
+       }
+
        /* Force lookup of new upper hardlink to find its lower */
        if (hardlink)
                d_drop(dentry);
+
+       return 0;
 }
 
 static bool ovl_type_merge(struct dentry *dentry)
@@ -213,38 +290,42 @@ static bool ovl_type_origin(struct dentry *dentry)
 }
 
 static int ovl_create_upper(struct dentry *dentry, struct inode *inode,
-                           struct cattr *attr, struct dentry *hardlink)
+                           struct ovl_cattr *attr)
 {
        struct dentry *upperdir = ovl_dentry_upper(dentry->d_parent);
        struct inode *udir = upperdir->d_inode;
        struct dentry *newdentry;
        int err;
 
-       if (!hardlink && !IS_POSIXACL(udir))
+       if (!attr->hardlink && !IS_POSIXACL(udir))
                attr->mode &= ~current_umask();
 
        inode_lock_nested(udir, I_MUTEX_PARENT);
-       newdentry = lookup_one_len(dentry->d_name.name, upperdir,
-                                  dentry->d_name.len);
+       newdentry = ovl_create_real(udir,
+                                   lookup_one_len(dentry->d_name.name,
+                                                  upperdir,
+                                                  dentry->d_name.len),
+                                   attr);
        err = PTR_ERR(newdentry);
        if (IS_ERR(newdentry))
                goto out_unlock;
-       err = ovl_create_real(udir, newdentry, attr, hardlink, false);
-       if (err)
-               goto out_dput;
 
        if (ovl_type_merge(dentry->d_parent) && d_is_dir(newdentry)) {
                /* Setting opaque here is just an optimization, allow to fail */
                ovl_set_opaque(dentry, newdentry);
        }
 
-       ovl_instantiate(dentry, inode, newdentry, !!hardlink);
-       newdentry = NULL;
-out_dput:
-       dput(newdentry);
+       err = ovl_instantiate(dentry, inode, newdentry, !!attr->hardlink);
+       if (err)
+               goto out_cleanup;
 out_unlock:
        inode_unlock(udir);
        return err;
+
+out_cleanup:
+       ovl_cleanup(udir, newdentry);
+       dput(newdentry);
+       goto out_unlock;
 }
 
 static struct dentry *ovl_clear_empty(struct dentry *dentry,
@@ -280,16 +361,11 @@ static struct dentry *ovl_clear_empty(struct dentry *dentry,
        if (upper->d_parent->d_inode != udir)
                goto out_unlock;
 
-       opaquedir = ovl_lookup_temp(workdir);
+       opaquedir = ovl_create_temp(workdir, OVL_CATTR(stat.mode));
        err = PTR_ERR(opaquedir);
        if (IS_ERR(opaquedir))
                goto out_unlock;
 
-       err = ovl_create_real(wdir, opaquedir,
-                             &(struct cattr){.mode = stat.mode}, NULL, true);
-       if (err)
-               goto out_dput;
-
        err = ovl_copy_xattr(upper, opaquedir);
        if (err)
                goto out_cleanup;
@@ -319,7 +395,6 @@ static struct dentry *ovl_clear_empty(struct dentry *dentry,
 
 out_cleanup:
        ovl_cleanup(wdir, opaquedir);
-out_dput:
        dput(opaquedir);
 out_unlock:
        unlock_rename(workdir, upperdir);
@@ -354,8 +429,7 @@ out_free:
 }
 
 static int ovl_create_over_whiteout(struct dentry *dentry, struct inode *inode,
-                                   struct cattr *cattr,
-                                   struct dentry *hardlink)
+                                   struct ovl_cattr *cattr)
 {
        struct dentry *workdir = ovl_workdir(dentry);
        struct inode *wdir = workdir->d_inode;
@@ -365,6 +439,7 @@ static int ovl_create_over_whiteout(struct dentry *dentry, struct inode *inode,
        struct dentry *newdentry;
        int err;
        struct posix_acl *acl, *default_acl;
+       bool hardlink = !!cattr->hardlink;
 
        if (WARN_ON(!workdir))
                return -EROFS;
@@ -380,20 +455,16 @@ static int ovl_create_over_whiteout(struct dentry *dentry, struct inode *inode,
        if (err)
                goto out;
 
-       newdentry = ovl_lookup_temp(workdir);
-       err = PTR_ERR(newdentry);
-       if (IS_ERR(newdentry))
-               goto out_unlock;
-
        upper = lookup_one_len(dentry->d_name.name, upperdir,
                               dentry->d_name.len);
        err = PTR_ERR(upper);
        if (IS_ERR(upper))
-               goto out_dput;
+               goto out_unlock;
 
-       err = ovl_create_real(wdir, newdentry, cattr, hardlink, true);
-       if (err)
-               goto out_dput2;
+       newdentry = ovl_create_temp(workdir, cattr);
+       err = PTR_ERR(newdentry);
+       if (IS_ERR(newdentry))
+               goto out_dput;
 
        /*
         * mode could have been mutilated due to umask (e.g. sgid directory)
@@ -439,12 +510,11 @@ static int ovl_create_over_whiteout(struct dentry *dentry, struct inode *inode,
                if (err)
                        goto out_cleanup;
        }
-       ovl_instantiate(dentry, inode, newdentry, !!hardlink);
-       newdentry = NULL;
-out_dput2:
-       dput(upper);
+       err = ovl_instantiate(dentry, inode, newdentry, hardlink);
+       if (err)
+               goto out_cleanup;
 out_dput:
-       dput(newdentry);
+       dput(upper);
 out_unlock:
        unlock_rename(workdir, upperdir);
 out:
@@ -456,12 +526,12 @@ out:
 
 out_cleanup:
        ovl_cleanup(wdir, newdentry);
-       goto out_dput2;
+       dput(newdentry);
+       goto out_dput;
 }
 
 static int ovl_create_or_link(struct dentry *dentry, struct inode *inode,
-                             struct cattr *attr, struct dentry *hardlink,
-                             bool origin)
+                             struct ovl_cattr *attr, bool origin)
 {
        int err;
        const struct cred *old_cred;
@@ -489,7 +559,7 @@ static int ovl_create_or_link(struct dentry *dentry, struct inode *inode,
        if (override_cred) {
                override_cred->fsuid = inode->i_uid;
                override_cred->fsgid = inode->i_gid;
-               if (!hardlink) {
+               if (!attr->hardlink) {
                        err = security_dentry_create_files_as(dentry,
                                        attr->mode, &dentry->d_name, old_cred,
                                        override_cred);
@@ -502,21 +572,12 @@ static int ovl_create_or_link(struct dentry *dentry, struct inode *inode,
                put_cred(override_cred);
 
                if (!ovl_dentry_is_whiteout(dentry))
-                       err = ovl_create_upper(dentry, inode, attr,
-                                               hardlink);
+                       err = ovl_create_upper(dentry, inode, attr);
                else
-                       err = ovl_create_over_whiteout(dentry, inode, attr,
-                                                       hardlink);
+                       err = ovl_create_over_whiteout(dentry, inode, attr);
        }
 out_revert_creds:
        revert_creds(old_cred);
-       if (!err) {
-               struct inode *realinode = d_inode(ovl_dentry_upper(dentry));
-
-               WARN_ON(inode->i_mode != realinode->i_mode);
-               WARN_ON(!uid_eq(inode->i_uid, realinode->i_uid));
-               WARN_ON(!gid_eq(inode->i_gid, realinode->i_gid));
-       }
        return err;
 }
 
@@ -525,7 +586,7 @@ static int ovl_create_object(struct dentry *dentry, int mode, dev_t rdev,
 {
        int err;
        struct inode *inode;
-       struct cattr attr = {
+       struct ovl_cattr attr = {
                .rdev = rdev,
                .link = link,
        };
@@ -534,6 +595,7 @@ static int ovl_create_object(struct dentry *dentry, int mode, dev_t rdev,
        if (err)
                goto out;
 
+       /* Preallocate inode to be used by ovl_get_inode() */
        err = -ENOMEM;
        inode = ovl_new_inode(dentry->d_sb, mode, rdev);
        if (!inode)
@@ -542,8 +604,9 @@ static int ovl_create_object(struct dentry *dentry, int mode, dev_t rdev,
        inode_init_owner(inode, dentry->d_parent->d_inode, mode);
        attr.mode = inode->i_mode;
 
-       err = ovl_create_or_link(dentry, inode, &attr, NULL, false);
-       if (err)
+       err = ovl_create_or_link(dentry, inode, &attr, false);
+       /* Did we end up using the preallocated inode? */
+       if (inode != d_inode(dentry))
                iput(inode);
 
 out_drop_write:
@@ -601,8 +664,9 @@ static int ovl_link(struct dentry *old, struct inode *newdir,
        inode = d_inode(old);
        ihold(inode);
 
-       err = ovl_create_or_link(new, inode, NULL, ovl_dentry_upper(old),
-                                ovl_type_origin(old));
+       err = ovl_create_or_link(new, inode,
+                       &(struct ovl_cattr) {.hardlink = ovl_dentry_upper(old)},
+                       ovl_type_origin(old));
        if (err)
                iput(inode);
 
index 425a94672300c78fc69393851f35795305c81d48..9941ece61a14bcc096cec0514e8c2f9ec9ca9ed0 100644 (file)
@@ -300,12 +300,18 @@ static struct dentry *ovl_obtain_alias(struct super_block *sb,
        struct dentry *dentry;
        struct inode *inode;
        struct ovl_entry *oe;
+       struct ovl_inode_params oip = {
+               .lowerpath = lowerpath,
+               .index = index,
+               .numlower = !!lower
+       };
 
        /* We get overlay directory dentries with ovl_lookup_real() */
        if (d_is_dir(upper ?: lower))
                return ERR_PTR(-EIO);
 
-       inode = ovl_get_inode(sb, dget(upper), lowerpath, index, !!lower);
+       oip.upperdentry = dget(upper);
+       inode = ovl_get_inode(sb, &oip);
        if (IS_ERR(inode)) {
                dput(upper);
                return ERR_CAST(inode);
index 6e3815fb006b8237a313e2b26069f6232c3d4609..1db5b3b458a19c2a45bebf0d571e4091574c2393 100644 (file)
@@ -749,15 +749,26 @@ static bool ovl_hash_bylower(struct super_block *sb, struct dentry *upper,
        return true;
 }
 
-struct inode *ovl_get_inode(struct super_block *sb, struct dentry *upperdentry,
-                           struct ovl_path *lowerpath, struct dentry *index,
-                           unsigned int numlower)
+static struct inode *ovl_iget5(struct super_block *sb, struct inode *newinode,
+                              struct inode *key)
 {
+       return newinode ? inode_insert5(newinode, (unsigned long) key,
+                                        ovl_inode_test, ovl_inode_set, key) :
+                         iget5_locked(sb, (unsigned long) key,
+                                      ovl_inode_test, ovl_inode_set, key);
+}
+
+struct inode *ovl_get_inode(struct super_block *sb,
+                           struct ovl_inode_params *oip)
+{
+       struct dentry *upperdentry = oip->upperdentry;
+       struct ovl_path *lowerpath = oip->lowerpath;
        struct inode *realinode = upperdentry ? d_inode(upperdentry) : NULL;
        struct inode *inode;
        struct dentry *lowerdentry = lowerpath ? lowerpath->dentry : NULL;
-       bool bylower = ovl_hash_bylower(sb, upperdentry, lowerdentry, index);
-       int fsid = bylower ? lowerpath->layer->fsid : 0;
+       bool bylower = ovl_hash_bylower(sb, upperdentry, lowerdentry,
+                                       oip->index);
+       int fsid = bylower ? oip->lowerpath->layer->fsid : 0;
        bool is_dir;
        unsigned long ino = 0;
 
@@ -774,8 +785,7 @@ struct inode *ovl_get_inode(struct super_block *sb, struct dentry *upperdentry,
                                                      upperdentry);
                unsigned int nlink = is_dir ? 1 : realinode->i_nlink;
 
-               inode = iget5_locked(sb, (unsigned long) key,
-                                    ovl_inode_test, ovl_inode_set, key);
+               inode = ovl_iget5(sb, oip->newinode, key);
                if (!inode)
                        goto out_nomem;
                if (!(inode->i_state & I_NEW)) {
@@ -811,12 +821,12 @@ struct inode *ovl_get_inode(struct super_block *sb, struct dentry *upperdentry,
        if (upperdentry && ovl_is_impuredir(upperdentry))
                ovl_set_flag(OVL_IMPURE, inode);
 
-       if (index)
+       if (oip->index)
                ovl_set_flag(OVL_INDEX, inode);
 
        /* Check for non-merge dir that may have whiteouts */
        if (is_dir) {
-               if (((upperdentry && lowerdentry) || numlower > 1) ||
+               if (((upperdentry && lowerdentry) || oip->numlower > 1) ||
                    ovl_check_origin_xattr(upperdentry ?: lowerdentry)) {
                        ovl_set_flag(OVL_WHITEOUTS, inode);
                }
index 2dba29eadde6b11d1d9211e921691b7d144d8a41..08801b45df00dcb346be697f8a6a407f5afabde0 100644 (file)
@@ -1004,8 +1004,14 @@ struct dentry *ovl_lookup(struct inode *dir, struct dentry *dentry,
                upperdentry = dget(index);
 
        if (upperdentry || ctr) {
-               inode = ovl_get_inode(dentry->d_sb, upperdentry, stack, index,
-                                     ctr);
+               struct ovl_inode_params oip = {
+                       .upperdentry = upperdentry,
+                       .lowerpath = stack,
+                       .index = index,
+                       .numlower = ctr,
+               };
+
+               inode = ovl_get_inode(dentry->d_sb, &oip);
                err = PTR_ERR(inode);
                if (IS_ERR(inode))
                        goto out_free_oe;
index e0b7de799f6b887a22cfe6a99faeec1430e50208..3c5e9f18b0d9f368025a299124def2e47f9bc4c4 100644 (file)
@@ -86,6 +86,7 @@ struct ovl_fh {
 static inline int ovl_do_rmdir(struct inode *dir, struct dentry *dentry)
 {
        int err = vfs_rmdir(dir, dentry);
+
        pr_debug("rmdir(%pd2) = %i\n", dentry, err);
        return err;
 }
@@ -93,56 +94,52 @@ static inline int ovl_do_rmdir(struct inode *dir, struct dentry *dentry)
 static inline int ovl_do_unlink(struct inode *dir, struct dentry *dentry)
 {
        int err = vfs_unlink(dir, dentry, NULL);
+
        pr_debug("unlink(%pd2) = %i\n", dentry, err);
        return err;
 }
 
 static inline int ovl_do_link(struct dentry *old_dentry, struct inode *dir,
-                             struct dentry *new_dentry, bool debug)
+                             struct dentry *new_dentry)
 {
        int err = vfs_link(old_dentry, dir, new_dentry, NULL);
-       if (debug) {
-               pr_debug("link(%pd2, %pd2) = %i\n",
-                        old_dentry, new_dentry, err);
-       }
+
+       pr_debug("link(%pd2, %pd2) = %i\n", old_dentry, new_dentry, err);
        return err;
 }
 
 static inline int ovl_do_create(struct inode *dir, struct dentry *dentry,
-                            umode_t mode, bool debug)
+                               umode_t mode)
 {
        int err = vfs_create(dir, dentry, mode, true);
-       if (debug)
-               pr_debug("create(%pd2, 0%o) = %i\n", dentry, mode, err);
+
+       pr_debug("create(%pd2, 0%o) = %i\n", dentry, mode, err);
        return err;
 }
 
 static inline int ovl_do_mkdir(struct inode *dir, struct dentry *dentry,
-                              umode_t mode, bool debug)
+                              umode_t mode)
 {
        int err = vfs_mkdir(dir, dentry, mode);
-       if (debug)
-               pr_debug("mkdir(%pd2, 0%o) = %i\n", dentry, mode, err);
+       pr_debug("mkdir(%pd2, 0%o) = %i\n", dentry, mode, err);
        return err;
 }
 
 static inline int ovl_do_mknod(struct inode *dir, struct dentry *dentry,
-                              umode_t mode, dev_t dev, bool debug)
+                              umode_t mode, dev_t dev)
 {
        int err = vfs_mknod(dir, dentry, mode, dev);
-       if (debug) {
-               pr_debug("mknod(%pd2, 0%o, 0%o) = %i\n",
-                        dentry, mode, dev, err);
-       }
+
+       pr_debug("mknod(%pd2, 0%o, 0%o) = %i\n", dentry, mode, dev, err);
        return err;
 }
 
 static inline int ovl_do_symlink(struct inode *dir, struct dentry *dentry,
-                                const char *oldname, bool debug)
+                                const char *oldname)
 {
        int err = vfs_symlink(dir, dentry, oldname);
-       if (debug)
-               pr_debug("symlink(\"%s\", %pd2) = %i\n", oldname, dentry, err);
+
+       pr_debug("symlink(\"%s\", %pd2) = %i\n", oldname, dentry, err);
        return err;
 }
 
@@ -168,11 +165,8 @@ static inline int ovl_do_rename(struct inode *olddir, struct dentry *olddentry,
 {
        int err;
 
-       pr_debug("rename(%pd2, %pd2, 0x%x)\n",
-                olddentry, newdentry, flags);
-
+       pr_debug("rename(%pd2, %pd2, 0x%x)\n", olddentry, newdentry, flags);
        err = vfs_rename(olddir, olddentry, newdir, newdentry, NULL, flags);
-
        if (err) {
                pr_debug("...rename(%pd2, %pd2, ...) = %i\n",
                         olddentry, newdentry, err);
@@ -334,12 +328,18 @@ int ovl_open_maybe_copy_up(struct dentry *dentry, unsigned int file_flags);
 int ovl_update_time(struct inode *inode, struct timespec *ts, int flags);
 bool ovl_is_private_xattr(const char *name);
 
+struct ovl_inode_params {
+       struct inode *newinode;
+       struct dentry *upperdentry;
+       struct ovl_path *lowerpath;
+       struct dentry *index;
+       unsigned int numlower;
+};
 struct inode *ovl_new_inode(struct super_block *sb, umode_t mode, dev_t rdev);
 struct inode *ovl_lookup_inode(struct super_block *sb, struct dentry *real,
                               bool is_upper);
-struct inode *ovl_get_inode(struct super_block *sb, struct dentry *upperdentry,
-                           struct ovl_path *lowerpath, struct dentry *index,
-                           unsigned int numlower);
+struct inode *ovl_get_inode(struct super_block *sb,
+                           struct ovl_inode_params *oip);
 static inline void ovl_copyattr(struct inode *from, struct inode *to)
 {
        to->i_uid = from->i_uid;
@@ -352,18 +352,21 @@ static inline void ovl_copyattr(struct inode *from, struct inode *to)
 
 /* dir.c */
 extern const struct inode_operations ovl_dir_inode_operations;
-struct dentry *ovl_lookup_temp(struct dentry *workdir);
 int ovl_cleanup_and_whiteout(struct dentry *workdir, struct inode *dir,
                             struct dentry *dentry);
-struct cattr {
+struct ovl_cattr {
        dev_t rdev;
        umode_t mode;
        const char *link;
+       struct dentry *hardlink;
 };
-int ovl_create_real(struct inode *dir, struct dentry *newdentry,
-                   struct cattr *attr,
-                   struct dentry *hardlink, bool debug);
+
+#define OVL_CATTR(m) (&(struct ovl_cattr) { .mode = (m) })
+
+struct dentry *ovl_create_real(struct inode *dir, struct dentry *newdentry,
+                              struct ovl_cattr *attr);
 int ovl_cleanup(struct inode *dir, struct dentry *dentry);
+struct dentry *ovl_create_temp(struct dentry *workdir, struct ovl_cattr *attr);
 
 /* copy_up.c */
 int ovl_copy_up(struct dentry *dentry);
index e8551c97de51c0676416d982aad60b9ca72c0373..704b373114671cca9d8136a9bbed769b0a75dd69 100644 (file)
@@ -611,11 +611,10 @@ retry:
                        goto retry;
                }
 
-               err = ovl_create_real(dir, work,
-                                     &(struct cattr){.mode = S_IFDIR | 0},
-                                     NULL, true);
-               if (err)
-                       goto out_dput;
+               work = ovl_create_real(dir, work, OVL_CATTR(attr.ia_mode));
+               err = PTR_ERR(work);
+               if (IS_ERR(work))
+                       goto out_err;
 
                /*
                 * Try to remove POSIX ACL xattrs from workdir.  We are good if:
index 7ef7193488b843085eec6ed6d80acb0a2c1c9e2b..77333ed3a4880b27ac4ebb7bfb8f34c2faedae3a 100644 (file)
@@ -2886,6 +2886,10 @@ extern struct inode *ilookup5(struct super_block *sb, unsigned long hashval,
                int (*test)(struct inode *, void *), void *data);
 extern struct inode *ilookup(struct super_block *sb, unsigned long ino);
 
+extern struct inode *inode_insert5(struct inode *inode, unsigned long hashval,
+               int (*test)(struct inode *, void *),
+               int (*set)(struct inode *, void *),
+               void *data);
 extern struct inode * iget5_locked(struct super_block *, unsigned long, int (*test)(struct inode *, void *), int (*set)(struct inode *, void *), void *);
 extern struct inode * iget_locked(struct super_block *, unsigned long);
 extern struct inode *find_inode_nowait(struct super_block *,