ovl: support multiple lower layers
authorMiklos Szeredi <mszeredi@suse.cz>
Fri, 12 Dec 2014 23:59:52 +0000 (00:59 +0100)
committerMiklos Szeredi <mszeredi@suse.cz>
Fri, 12 Dec 2014 23:59:52 +0000 (00:59 +0100)
Allow "lowerdir=" option to contain multiple lower directories separated by
a colon (e.g. "lowerdir=/bin:/usr/bin").  Colon characters in filenames can
be escaped with a backslash.

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

index a27c950ece61b0d312fbba2a3757fab71c5b3616..b37092886dcc32079036fdb5c096d0a334746aa4 100644 (file)
@@ -159,6 +159,18 @@ overlay filesystem (though an operation on the name of the file such as
 rename or unlink will of course be noticed and handled).
 
 
+Multiple lower layers
+---------------------
+
+Multiple lower layers can now be given using the the colon (":") as a
+separator character between the directory names.  For example:
+
+  mount -t overlay overlay -olowerdir=/lower1:/lower2:/lower3 /merged
+
+As the example shows, "upperdir=" and "workdir=" may be omitted.  In that case
+the overlay will be read-only.
+
+
 Non-standard behavior
 ---------------------
 
index 35bb0adf10cfe8e041ca7fb8d7358a392e5b952c..5c495a17a5a3e2dff1ca52782f54a5e7bec646cd 100644 (file)
@@ -60,6 +60,8 @@ struct ovl_entry {
        struct path lowerstack[];
 };
 
+#define OVL_MAX_STACK 500
+
 const char *ovl_opaque_xattr = "trusted.overlay.opaque";
 
 static struct dentry *__ovl_dentry_lower(struct ovl_entry *oe)
@@ -692,8 +694,12 @@ static bool ovl_is_allowed_fs_type(struct dentry *root)
 
 static int ovl_mount_dir_noesc(const char *name, struct path *path)
 {
-       int err;
+       int err = -EINVAL;
 
+       if (!*name) {
+               pr_err("overlayfs: empty lowerdir\n");
+               goto out;
+       }
        err = kern_path(name, LOOKUP_FOLLOW, path);
        if (err) {
                pr_err("overlayfs: failed to resolve '%s': %i\n", name, err);
@@ -735,7 +741,7 @@ static int ovl_lower_dir(const char *name, struct path *path, long *namelen,
        int err;
        struct kstatfs statfs;
 
-       err = ovl_mount_dir(name, path);
+       err = ovl_mount_dir_noesc(name, path);
        if (err)
                goto out;
 
@@ -767,15 +773,38 @@ static bool ovl_workdir_ok(struct dentry *workdir, struct dentry *upperdir)
        return ok;
 }
 
+static unsigned int ovl_split_lowerdirs(char *str)
+{
+       unsigned int ctr = 1;
+       char *s, *d;
+
+       for (s = d = str;; s++, d++) {
+               if (*s == '\\') {
+                       s++;
+               } else if (*s == ':') {
+                       *d = '\0';
+                       ctr++;
+                       continue;
+               }
+               *d = *s;
+               if (!*s)
+                       break;
+       }
+       return ctr;
+}
+
 static int ovl_fill_super(struct super_block *sb, void *data, int silent)
 {
-       struct path lowerpath;
        struct path upperpath = { NULL, NULL };
        struct path workpath = { NULL, NULL };
        struct dentry *root_dentry;
        struct ovl_entry *oe;
        struct ovl_fs *ufs;
-       struct vfsmount *mnt;
+       struct path *stack = NULL;
+       char *lowertmp;
+       char *lower;
+       unsigned int numlower;
+       unsigned int stacklen = 0;
        unsigned int i;
        int err;
 
@@ -820,12 +849,30 @@ static int ovl_fill_super(struct super_block *sb, void *data, int silent)
                }
                sb->s_stack_depth = upperpath.mnt->mnt_sb->s_stack_depth;
        }
-
-       err = ovl_lower_dir(ufs->config.lowerdir, &lowerpath,
-                           &ufs->lower_namelen, &sb->s_stack_depth);
-       if (err)
+       err = -ENOMEM;
+       lowertmp = kstrdup(ufs->config.lowerdir, GFP_KERNEL);
+       if (!lowertmp)
                goto out_put_workpath;
 
+       err = -EINVAL;
+       stacklen = ovl_split_lowerdirs(lowertmp);
+       if (stacklen > OVL_MAX_STACK)
+               goto out_free_lowertmp;
+
+       stack = kcalloc(stacklen, sizeof(struct path), GFP_KERNEL);
+       if (!stack)
+               goto out_free_lowertmp;
+
+       lower = lowertmp;
+       for (numlower = 0; numlower < stacklen; numlower++) {
+               err = ovl_lower_dir(lower, &stack[numlower],
+                                   &ufs->lower_namelen, &sb->s_stack_depth);
+               if (err)
+                       goto out_put_lowerpath;
+
+               lower = strchr(lower, '\0') + 1;
+       }
+
        err = -EINVAL;
        sb->s_stack_depth++;
        if (sb->s_stack_depth > FILESYSTEM_MAX_STACK_DEPTH) {
@@ -850,24 +897,25 @@ static int ovl_fill_super(struct super_block *sb, void *data, int silent)
                }
        }
 
-       ufs->lower_mnt = kcalloc(1, sizeof(struct vfsmount *), GFP_KERNEL);
+       ufs->lower_mnt = kcalloc(numlower, sizeof(struct vfsmount *), GFP_KERNEL);
        if (ufs->lower_mnt == NULL)
                goto out_put_workdir;
+       for (i = 0; i < numlower; i++) {
+               struct vfsmount *mnt = clone_private_mount(&stack[i]);
 
-       mnt = clone_private_mount(&lowerpath);
-       err = PTR_ERR(mnt);
-       if (IS_ERR(mnt)) {
-               pr_err("overlayfs: failed to clone lowerpath\n");
-               goto out_put_lower_mnt;
-       }
-       /*
-        * Make lower_mnt R/O.  That way fchmod/fchown on lower file
-        * will fail instead of modifying lower fs.
-        */
-       mnt->mnt_flags |= MNT_READONLY;
+               if (IS_ERR(mnt)) {
+                       pr_err("overlayfs: failed to clone lowerpath\n");
+                       goto out_put_lower_mnt;
+               }
+               /*
+                * Make lower_mnt R/O.  That way fchmod/fchown on lower file
+                * will fail instead of modifying lower fs.
+                */
+               mnt->mnt_flags |= MNT_READONLY;
 
-       ufs->lower_mnt[0] = mnt;
-       ufs->numlower = 1;
+               ufs->lower_mnt[ufs->numlower] = mnt;
+               ufs->numlower++;
+       }
 
        /* If the upper fs is r/o or nonexistent, we mark overlayfs r/o too */
        if (!ufs->upper_mnt || (ufs->upper_mnt->mnt_sb->s_flags & MS_RDONLY))
@@ -876,7 +924,7 @@ static int ovl_fill_super(struct super_block *sb, void *data, int silent)
        sb->s_d_op = &ovl_dentry_operations;
 
        err = -ENOMEM;
-       oe = ovl_alloc_entry(1);
+       oe = ovl_alloc_entry(numlower);
        if (!oe)
                goto out_put_lower_mnt;
 
@@ -885,12 +933,16 @@ static int ovl_fill_super(struct super_block *sb, void *data, int silent)
                goto out_free_oe;
 
        mntput(upperpath.mnt);
-       mntput(lowerpath.mnt);
+       for (i = 0; i < numlower; i++)
+               mntput(stack[i].mnt);
        path_put(&workpath);
+       kfree(lowertmp);
 
        oe->__upperdentry = upperpath.dentry;
-       oe->lowerstack[0].dentry = lowerpath.dentry;
-       oe->lowerstack[0].mnt = ufs->lower_mnt[0];
+       for (i = 0; i < numlower; i++) {
+               oe->lowerstack[i].dentry = stack[i].dentry;
+               oe->lowerstack[i].mnt = ufs->lower_mnt[i];
+       }
 
        root_dentry->d_fsdata = oe;
 
@@ -912,7 +964,11 @@ out_put_workdir:
 out_put_upper_mnt:
        mntput(ufs->upper_mnt);
 out_put_lowerpath:
-       path_put(&lowerpath);
+       for (i = 0; i < numlower; i++)
+               path_put(&stack[i]);
+       kfree(stack);
+out_free_lowertmp:
+       kfree(lowertmp);
 out_put_workpath:
        path_put(&workpath);
 out_put_upperpath: