ovl: multi-layer lookup
authorMiklos Szeredi <mszeredi@suse.cz>
Fri, 12 Dec 2014 23:59:44 +0000 (00:59 +0100)
committerMiklos Szeredi <mszeredi@suse.cz>
Fri, 12 Dec 2014 23:59:44 +0000 (00:59 +0100)
Look up dentry in all relevant layers.

Signed-off-by: Miklos Szeredi <mszeredi@suse.cz>
fs/overlayfs/super.c

index c245043..f72b82f 100644 (file)
@@ -333,82 +333,127 @@ struct dentry *ovl_lookup(struct inode *dir, struct dentry *dentry,
                          unsigned int flags)
 {
        struct ovl_entry *oe;
-       struct dentry *upperdir;
-       struct path lowerdir;
-       struct dentry *upperdentry = NULL;
-       struct dentry *lowerdentry = NULL;
+       struct ovl_entry *poe = dentry->d_parent->d_fsdata;
+       struct path *stack = NULL;
+       struct dentry *upperdir, *upperdentry = NULL;
+       unsigned int ctr = 0;
        struct inode *inode = NULL;
+       bool upperopaque = false;
+       struct dentry *this, *prev = NULL;
+       unsigned int i;
        int err;
 
-       err = -ENOMEM;
-       oe = ovl_alloc_entry(1);
-       if (!oe)
-               goto out;
-
-       upperdir = ovl_dentry_upper(dentry->d_parent);
-       ovl_path_lower(dentry->d_parent, &lowerdir);
-
+       upperdir = ovl_upperdentry_dereference(poe);
        if (upperdir) {
-               upperdentry = ovl_lookup_real(upperdir, &dentry->d_name);
-               err = PTR_ERR(upperdentry);
-               if (IS_ERR(upperdentry))
-                       goto out_put_dir;
-
-               if (lowerdir.dentry && upperdentry) {
-                       if (ovl_is_whiteout(upperdentry)) {
-                               dput(upperdentry);
-                               upperdentry = NULL;
-                               oe->opaque = true;
-                       } else if (ovl_is_opaquedir(upperdentry)) {
-                               oe->opaque = true;
+               this = ovl_lookup_real(upperdir, &dentry->d_name);
+               err = PTR_ERR(this);
+               if (IS_ERR(this))
+                       goto out;
+
+               /*
+                * If this is not the lowermost layer, check whiteout and opaque
+                * directory.
+                */
+               if (poe->numlower && this) {
+                       if (ovl_is_whiteout(this)) {
+                               dput(this);
+                               this = NULL;
+                               upperopaque = true;
+                       } else if (ovl_is_opaquedir(this)) {
+                               upperopaque = true;
                        }
                }
+               upperdentry = prev = this;
        }
-       if (lowerdir.dentry && !oe->opaque) {
-               lowerdentry = ovl_lookup_real(lowerdir.dentry, &dentry->d_name);
-               err = PTR_ERR(lowerdentry);
-               if (IS_ERR(lowerdentry))
-                       goto out_dput_upper;
+
+       if (!upperopaque && poe->numlower) {
+               err = -ENOMEM;
+               stack = kcalloc(poe->numlower, sizeof(struct path), GFP_KERNEL);
+               if (!stack)
+                       goto out_put_upper;
        }
 
-       if (lowerdentry && upperdentry &&
-           (!S_ISDIR(upperdentry->d_inode->i_mode) ||
-            !S_ISDIR(lowerdentry->d_inode->i_mode))) {
-               dput(lowerdentry);
-               lowerdentry = NULL;
-               oe->opaque = true;
+       for (i = 0; !upperopaque && i < poe->numlower; i++) {
+               bool opaque = false;
+               struct path lowerpath = poe->lowerstack[i];
+
+               opaque = false;
+               this = ovl_lookup_real(lowerpath.dentry, &dentry->d_name);
+               err = PTR_ERR(this);
+               if (IS_ERR(this))
+                       goto out_put;
+               if (!this)
+                       continue;
+
+               /*
+                * If this is not the lowermost layer, check whiteout and opaque
+                * directory.
+                */
+               if (i < poe->numlower - 1) {
+                       if (ovl_is_whiteout(this)) {
+                               dput(this);
+                               break;
+                       } else if (ovl_is_opaquedir(this)) {
+                               opaque = true;
+                       }
+               }
+               /*
+                * If this is a non-directory then stop here.
+                *
+                * FIXME: check for opaqueness maybe better done in remove code.
+                */
+               if (!S_ISDIR(this->d_inode->i_mode)) {
+                       opaque = true;
+               } else if (prev && (!S_ISDIR(prev->d_inode->i_mode) ||
+                                   !S_ISDIR(this->d_inode->i_mode))) {
+                       if (prev == upperdentry)
+                               upperopaque = true;
+                       dput(this);
+                       break;
+               }
+               stack[ctr].dentry = this;
+               stack[ctr].mnt = lowerpath.mnt;
+               ctr++;
+               prev = this;
+               if (opaque)
+                       break;
        }
 
-       if (lowerdentry || upperdentry) {
+       oe = ovl_alloc_entry(ctr);
+       err = -ENOMEM;
+       if (!oe)
+               goto out_put;
+
+       if (upperdentry || ctr) {
                struct dentry *realdentry;
 
-               realdentry = upperdentry ? upperdentry : lowerdentry;
+               realdentry = upperdentry ? upperdentry : stack[0].dentry;
+
                err = -ENOMEM;
                inode = ovl_new_inode(dentry->d_sb, realdentry->d_inode->i_mode,
                                      oe);
                if (!inode)
-                       goto out_dput;
+                       goto out_free_oe;
                ovl_copyattr(realdentry->d_inode, inode);
        }
 
+       oe->opaque = upperopaque;
        oe->__upperdentry = upperdentry;
-       if (lowerdentry) {
-               oe->lowerstack[0].dentry = lowerdentry;
-               oe->lowerstack[0].mnt = lowerdir.mnt;
-       } else {
-               oe->numlower = 0;
-       }
+       memcpy(oe->lowerstack, stack, sizeof(struct path) * ctr);
+       kfree(stack);
        dentry->d_fsdata = oe;
        d_add(dentry, inode);
 
        return NULL;
 
-out_dput:
-       dput(lowerdentry);
-out_dput_upper:
-       dput(upperdentry);
-out_put_dir:
+out_free_oe:
        kfree(oe);
+out_put:
+       for (i = 0; i < ctr; i++)
+               dput(stack[i].dentry);
+       kfree(stack);
+out_put_upper:
+       dput(upperdentry);
 out:
        return ERR_PTR(err);
 }