#include <linux/fs.h>
 #include <linux/namei.h>
 #include <linux/xattr.h>
+#include <linux/ratelimit.h>
 #include "overlayfs.h"
 #include "ovl_entry.h"
 
        bool opaque;
        bool stop;
        bool last;
+       char *redirect;
 };
 
+static int ovl_check_redirect(struct dentry *dentry, struct ovl_lookup_data *d,
+                             size_t prelen, const char *post)
+{
+       int res;
+       char *s, *next, *buf = NULL;
+
+       res = vfs_getxattr(dentry, OVL_XATTR_REDIRECT, NULL, 0);
+       if (res < 0) {
+               if (res == -ENODATA || res == -EOPNOTSUPP)
+                       return 0;
+               goto fail;
+       }
+       buf = kzalloc(prelen + res + strlen(post) + 1, GFP_TEMPORARY);
+       if (!buf)
+               return -ENOMEM;
+
+       if (res == 0)
+               goto invalid;
+
+       res = vfs_getxattr(dentry, OVL_XATTR_REDIRECT, buf, res);
+       if (res < 0)
+               goto fail;
+       if (res == 0)
+               goto invalid;
+       if (buf[0] == '/') {
+               for (s = buf; *s++ == '/'; s = next) {
+                       next = strchrnul(s, '/');
+                       if (s == next)
+                               goto invalid;
+               }
+       } else {
+               if (strchr(buf, '/') != NULL)
+                       goto invalid;
+
+               memmove(buf + prelen, buf, res);
+               memcpy(buf, d->name.name, prelen);
+       }
+
+       strcat(buf, post);
+       kfree(d->redirect);
+       d->redirect = buf;
+       d->name.name = d->redirect;
+       d->name.len = strlen(d->redirect);
+
+       return 0;
+
+err_free:
+       kfree(buf);
+       return 0;
+fail:
+       pr_warn_ratelimited("overlayfs: failed to get redirect (%i)\n", res);
+       goto err_free;
+invalid:
+       pr_warn_ratelimited("overlayfs: invalid redirect (%s)\n", buf);
+       goto err_free;
+}
+
 static bool ovl_is_opaquedir(struct dentry *dentry)
 {
        int res;
 
 static int ovl_lookup_single(struct dentry *base, struct ovl_lookup_data *d,
                             const char *name, unsigned int namelen,
+                            size_t prelen, const char *post,
                             struct dentry **ret)
 {
        struct dentry *this;
                d->stop = d->opaque = true;
                goto out;
        }
+       err = ovl_check_redirect(this, d, prelen, post);
+       if (err)
+               goto out_err;
 out:
        *ret = this;
        return 0;
 static int ovl_lookup_layer(struct dentry *base, struct ovl_lookup_data *d,
                            struct dentry **ret)
 {
-       return ovl_lookup_single(base, d, d->name.name, d->name.len, ret);
+       const char *s = d->name.name;
+       struct dentry *dentry = NULL;
+       int err;
+
+       if (*s != '/')
+               return ovl_lookup_single(base, d, d->name.name, d->name.len,
+                                        0, "", ret);
+
+       while (*s++ == '/' && !IS_ERR_OR_NULL(base) && d_can_lookup(base)) {
+               const char *next = strchrnul(s, '/');
+               size_t slen = strlen(s);
+
+               if (WARN_ON(slen > d->name.len) ||
+                   WARN_ON(strcmp(d->name.name + d->name.len - slen, s)))
+                       return -EIO;
+
+               err = ovl_lookup_single(base, d, s, next - s,
+                                       d->name.len - slen, next, &base);
+               dput(dentry);
+               if (err)
+                       return err;
+               dentry = base;
+               s = next;
+       }
+       *ret = dentry;
+       return 0;
 }
 
 /*
        unsigned int ctr = 0;
        struct inode *inode = NULL;
        bool upperopaque = false;
+       char *upperredirect = NULL;
        struct dentry *this;
        unsigned int i;
        int err;
                .opaque = false,
                .stop = false,
                .last = !poe->numlower,
+               .redirect = NULL,
        };
 
        if (dentry->d_name.len > ofs->namelen)
                        err = -EREMOTE;
                        goto out;
                }
+
+               if (d.redirect) {
+                       upperredirect = kstrdup(d.redirect, GFP_KERNEL);
+                       if (!upperredirect)
+                               goto out_put_upper;
+                       if (d.redirect[0] == '/')
+                               poe = dentry->d_sb->s_root->d_fsdata;
+               }
                upperopaque = d.opaque;
        }
 
        if (!d.stop && poe->numlower) {
                err = -ENOMEM;
-               stack = kcalloc(poe->numlower, sizeof(struct path),
+               stack = kcalloc(ofs->numlower, sizeof(struct path),
                                GFP_TEMPORARY);
                if (!stack)
                        goto out_put_upper;
                stack[ctr].dentry = this;
                stack[ctr].mnt = lowerpath.mnt;
                ctr++;
+
+               if (d.stop)
+                       break;
+
+               if (d.redirect &&
+                   d.redirect[0] == '/' &&
+                   poe != dentry->d_sb->s_root->d_fsdata) {
+                       poe = dentry->d_sb->s_root->d_fsdata;
+
+                       /* Find the current layer on the root dentry */
+                       for (i = 0; i < poe->numlower; i++)
+                               if (poe->lowerstack[i].mnt == lowerpath.mnt)
+                                       break;
+                       if (WARN_ON(i == poe->numlower))
+                               break;
+               }
        }
 
        oe = ovl_alloc_entry(ctr);
 
        revert_creds(old_cred);
        oe->opaque = upperopaque;
+       oe->redirect = upperredirect;
        oe->__upperdentry = upperdentry;
        memcpy(oe->lowerstack, stack, sizeof(struct path) * ctr);
        kfree(stack);
+       kfree(d.redirect);
        dentry->d_fsdata = oe;
        d_add(dentry, inode);
 
        kfree(stack);
 out_put_upper:
        dput(upperdentry);
+       kfree(upperredirect);
 out:
+       kfree(d.redirect);
        revert_creds(old_cred);
        return ERR_PTR(err);
 }