]> asedeno.scripts.mit.edu Git - linux.git/commitdiff
ovl: lookup connected ancestor of dir in inode cache
authorAmir Goldstein <amir73il@gmail.com>
Thu, 18 Jan 2018 14:39:13 +0000 (16:39 +0200)
committerMiklos Szeredi <mszeredi@redhat.com>
Wed, 24 Jan 2018 10:26:05 +0000 (11:26 +0100)
Decoding a dir file handle requires walking backward up to layer root and
for lower dir also checking the index to see if any of the parents have
been copied up.

Lookup overlay ancestor dentry in inode/dentry cache by decoded real
parents to shortcut looking up all the way back to layer root.

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

index 361174810ce8bf9ddb08b6687bf2eee7f345d538..092e6e8c9258db968f404b13c49c02e539c8634d 100644 (file)
@@ -294,6 +294,88 @@ static struct dentry *ovl_lookup_real_one(struct dentry *connected,
        goto out;
 }
 
+/*
+ * Lookup an indexed or hashed overlay dentry by real inode.
+ */
+static struct dentry *ovl_lookup_real_inode(struct super_block *sb,
+                                           struct dentry *real,
+                                           struct ovl_layer *layer)
+{
+       struct dentry *this = NULL;
+       struct inode *inode;
+
+       inode = ovl_lookup_inode(sb, real, !layer->idx);
+       if (IS_ERR(inode))
+               return ERR_CAST(inode);
+       if (inode) {
+               this = d_find_any_alias(inode);
+               iput(inode);
+       }
+
+       /* TODO: use index when looking up by origin inode */
+       if (!this)
+               return NULL;
+
+       if (WARN_ON(ovl_dentry_real_at(this, layer->idx) != real)) {
+               dput(this);
+               this = ERR_PTR(-EIO);
+       }
+
+       return this;
+}
+
+/*
+ * Lookup an indexed or hashed overlay dentry, whose real dentry is an
+ * ancestor of @real.
+ */
+static struct dentry *ovl_lookup_real_ancestor(struct super_block *sb,
+                                              struct dentry *real,
+                                              struct ovl_layer *layer)
+{
+       struct dentry *next, *parent = NULL;
+       struct dentry *ancestor = ERR_PTR(-EIO);
+
+       if (real == layer->mnt->mnt_root)
+               return dget(sb->s_root);
+
+       /* Find the topmost indexed or hashed ancestor */
+       next = dget(real);
+       for (;;) {
+               parent = dget_parent(next);
+
+               /*
+                * Lookup a matching overlay dentry in inode/dentry
+                * cache or in index by real inode.
+                */
+               ancestor = ovl_lookup_real_inode(sb, next, layer);
+               if (ancestor)
+                       break;
+
+               if (parent == layer->mnt->mnt_root) {
+                       ancestor = dget(sb->s_root);
+                       break;
+               }
+
+               /*
+                * If @real has been moved out of the layer root directory,
+                * we will eventully hit the real fs root. This cannot happen
+                * by legit overlay rename, so we return error in that case.
+                */
+               if (parent == next) {
+                       ancestor = ERR_PTR(-EXDEV);
+                       break;
+               }
+
+               dput(next);
+               next = parent;
+       }
+
+       dput(parent);
+       dput(next);
+
+       return ancestor;
+}
+
 /*
  * Lookup a connected overlay dentry whose real dentry is @real.
  * If @real is on upper layer, we lookup a child overlay dentry with the same
@@ -306,9 +388,10 @@ static struct dentry *ovl_lookup_real(struct super_block *sb,
        struct dentry *connected;
        int err = 0;
 
-       /* TODO: use index when looking up by lower real dentry */
+       connected = ovl_lookup_real_ancestor(sb, real, layer);
+       if (IS_ERR(connected))
+               return connected;
 
-       connected = dget(sb->s_root);
        while (!err) {
                struct dentry *next, *this;
                struct dentry *parent = NULL;
@@ -365,11 +448,15 @@ static struct dentry *ovl_lookup_real(struct super_block *sb,
                         * overlay rename of child away from 'connected' parent.
                         * In this case, we need to restart the lookup from the
                         * top, because we cannot trust that 'real_connected' is
-                        * still an ancestor of 'real'.
+                        * still an ancestor of 'real'. There is a good chance
+                        * that the renamed overlay ancestor is now in cache, so
+                        * ovl_lookup_real_ancestor() will find it and we can
+                        * continue to connect exactly from where lookup failed.
                         */
                        if (err == -ECHILD) {
-                               this = dget(sb->s_root);
-                               err = 0;
+                               this = ovl_lookup_real_ancestor(sb, real,
+                                                               layer);
+                               err = IS_ERR(this) ? PTR_ERR(this) : 0;
                        }
                        if (!err) {
                                dput(connected);
@@ -494,7 +581,7 @@ static struct dentry *ovl_lower_fh_to_d(struct super_block *sb,
        } else if (is_deleted) {
                /* Lookup deleted non-dir by origin inode */
                if (!d_is_dir(origin.dentry))
-                       inode = ovl_lookup_inode(sb, origin.dentry);
+                       inode = ovl_lookup_inode(sb, origin.dentry, false);
                err = -ESTALE;
                if (!inode || atomic_read(&inode->i_count) == 1)
                        goto out_err;
index 416dc06835db4312f1d0263e648858a97ce2a130..fcd97b783fa1ffa82dc7ec2eb4b82f03a0be50ef 100644 (file)
@@ -614,9 +614,15 @@ static int ovl_inode_set(struct inode *inode, void *data)
 }
 
 static bool ovl_verify_inode(struct inode *inode, struct dentry *lowerdentry,
-                            struct dentry *upperdentry)
+                            struct dentry *upperdentry, bool strict)
 {
-       if (S_ISDIR(inode->i_mode)) {
+       /*
+        * For directories, @strict verify from lookup path performs consistency
+        * checks, so NULL lower/upper in dentry must match NULL lower/upper in
+        * inode. Non @strict verify from NFS handle decode path passes NULL for
+        * 'unknown' lower/upper.
+        */
+       if (S_ISDIR(inode->i_mode) && strict) {
                /* Real lower dir moved to upper layer under us? */
                if (!lowerdentry && ovl_inode_lower(inode))
                        return false;
@@ -645,15 +651,17 @@ static bool ovl_verify_inode(struct inode *inode, struct dentry *lowerdentry,
        return true;
 }
 
-struct inode *ovl_lookup_inode(struct super_block *sb, struct dentry *origin)
+struct inode *ovl_lookup_inode(struct super_block *sb, struct dentry *real,
+                              bool is_upper)
 {
-       struct inode *inode, *key = d_inode(origin);
+       struct inode *inode, *key = d_inode(real);
 
        inode = ilookup5(sb, (unsigned long) key, ovl_inode_test, key);
        if (!inode)
                return NULL;
 
-       if (!ovl_verify_inode(inode, origin, NULL)) {
+       if (!ovl_verify_inode(inode, is_upper ? NULL : real,
+                             is_upper ? real : NULL, false)) {
                iput(inode);
                return ERR_PTR(-ESTALE);
        }
@@ -704,7 +712,8 @@ struct inode *ovl_get_inode(struct super_block *sb, struct dentry *upperdentry,
                         * Verify that the underlying files stored in the inode
                         * match those in the dentry.
                         */
-                       if (!ovl_verify_inode(inode, lowerdentry, upperdentry)) {
+                       if (!ovl_verify_inode(inode, lowerdentry, upperdentry,
+                                             true)) {
                                iput(inode);
                                inode = ERR_PTR(-ESTALE);
                                goto out;
index a5d415aec1315a66356dc7df932fdc84ca0f0932..bf17bf97c50f35324cb2c7ab9aebe8942d3b3451 100644 (file)
@@ -323,7 +323,8 @@ int ovl_update_time(struct inode *inode, struct timespec *ts, int flags);
 bool ovl_is_private_xattr(const char *name);
 
 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 *origin);
+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 dentry *lowerdentry, struct dentry *index,
                            unsigned int numlower);