ovl: lookup in inode cache first when decoding lower file handle
authorAmir Goldstein <amir73il@gmail.com>
Fri, 9 Mar 2018 15:05:55 +0000 (17:05 +0200)
committerMiklos Szeredi <mszeredi@redhat.com>
Thu, 12 Apr 2018 10:04:49 +0000 (12:04 +0200)
When decoding a lower file handle, we need to check if lower file was
copied up and indexed and if it has a whiteout index, we need to check
if this is an unlinked but open non-dir before returning -ESTALE.

To find out if this is an unlinked but open non-dir we need to lookup
an overlay inode in inode cache by lower inode and that requires decoding
the lower file handle before looking in inode cache.

Before this change, if the lower inode turned out to be a directory, we
may have paid an expensive cost to reconnect that lower directory for
nothing.

After this change, we start by decoding a disconnected lower dentry and
using the lower inode for looking up an overlay inode in inode cache.
If we find overlay inode and dentry in cache, we avoid the index lookup
overhead. If we don't find an overlay inode and dentry in cache, then we
only need to decode a connected lower dentry in case the lower dentry is
a non-indexed directory.

The xfstests group overlay/exportfs tests decoding overlayfs file
handles after drop_caches with different states of the file at encode
and decode time. Overall the tests in the group call ovl_lower_fh_to_d()
89 times to decode a lower file handle.

Before this change, the tests called ovl_get_index_fh() 75 times and
reconnect_one() 61 times.
After this change, the tests call ovl_get_index_fh() 70 times and
reconnect_one() 59 times. The 2 cases where reconnect_one() was avoided
are cases where a non-upper directory file handle was encoded, then the
directory removed and then file handle was decoded.

To demonstrate the affect on decoding file handles with hot inode/dentry
cache, the drop_caches call in the tests was disabled. Without
drop_caches, there are no reconnect_one() calls at all before or after
the change. Before the change, there are 75 calls to ovl_get_index_fh(),
exactly as the case with drop_caches. After the change, there are only
10 calls to ovl_get_index_fh().

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

index 1541135..ed73e36 100644 (file)
@@ -703,25 +703,39 @@ static struct dentry *ovl_lower_fh_to_d(struct super_block *sb,
        struct ovl_path *stack = &origin;
        struct dentry *dentry = NULL;
        struct dentry *index = NULL;
-       struct inode *inode = NULL;
-       bool is_deleted = false;
+       struct inode *inode;
        int err;
 
-       /* First lookup indexed upper by fh */
+       /* First lookup overlay inode in inode cache by origin fh */
+       err = ovl_check_origin_fh(ofs, fh, false, NULL, &stack);
+       if (err)
+               return ERR_PTR(err);
+
+       if (!d_is_dir(origin.dentry) ||
+           !(origin.dentry->d_flags & DCACHE_DISCONNECTED)) {
+               inode = ovl_lookup_inode(sb, origin.dentry, false);
+               err = PTR_ERR(inode);
+               if (IS_ERR(inode))
+                       goto out_err;
+               if (inode) {
+                       dentry = d_find_any_alias(inode);
+                       iput(inode);
+                       if (dentry)
+                               goto out;
+               }
+       }
+
+       /* Then lookup indexed upper/whiteout by origin fh */
        if (ofs->indexdir) {
                index = ovl_get_index_fh(ofs, fh);
                err = PTR_ERR(index);
                if (IS_ERR(index)) {
-                       if (err != -ESTALE)
-                               return ERR_PTR(err);
-
-                       /* Found a whiteout index - treat as deleted inode */
-                       is_deleted = true;
                        index = NULL;
+                       goto out_err;
                }
        }
 
-       /* Then try to get upper dir by index */
+       /* Then try to get a connected upper dir by index */
        if (index && d_is_dir(index)) {
                struct dentry *upper = ovl_index_upper(ofs, index);
 
@@ -734,24 +748,19 @@ static struct dentry *ovl_lower_fh_to_d(struct super_block *sb,
                goto out;
        }
 
-       /* Then lookup origin by fh */
-       err = ovl_check_origin_fh(ofs, fh, true, NULL, &stack);
-       if (err) {
-               goto out_err;
-       } else if (index) {
-               err = ovl_verify_origin(index, origin.dentry, false);
+       /* Otherwise, get a connected non-upper dir or disconnected non-dir */
+       if (d_is_dir(origin.dentry) &&
+           (origin.dentry->d_flags & DCACHE_DISCONNECTED)) {
+               dput(origin.dentry);
+               origin.dentry = NULL;
+               err = ovl_check_origin_fh(ofs, fh, true, NULL, &stack);
                if (err)
                        goto out_err;
-       } else if (is_deleted) {
-               /* Lookup deleted non-dir by origin inode */
-               if (!d_is_dir(origin.dentry))
-                       inode = ovl_lookup_inode(sb, origin.dentry, false);
-               err = -ESTALE;
-               if (!inode || atomic_read(&inode->i_count) == 1)
+       }
+       if (index) {
+               err = ovl_verify_origin(index, origin.dentry, false);
+               if (err)
                        goto out_err;
-
-               /* Deleted but still open? */
-               index = dget(ovl_i_dentry_upper(inode));
        }
 
        dentry = ovl_get_dentry(sb, NULL, &origin, index);
@@ -759,7 +768,6 @@ static struct dentry *ovl_lower_fh_to_d(struct super_block *sb,
 out:
        dput(origin.dentry);
        dput(index);
-       iput(inode);
        return dentry;
 
 out_err: