From: Jeff Mahoney <jeffm@suse.com>
Subject: overlayfs: add compatibility mode for historical format
Patch-mainline: Never, we don't want to encourage new users to use this format
References: FATE#320450

Users of overlayfs prior to its acceptance in the mainline kernel use a
different format upper layer than what new users would find.

Specifically, the original overlayfs copied up files in place and marked
whiteout objects as a combination of a specially-named symlink and an
overlayfs-specific extended attribute.  Neither of these are atomic and would
result in broken files if the system were to be interrupted while performing
certain operations.  As a result, the format was changed to operations
that are atomic prior to it be accepted into the mainline kernel.  The upstream
version performs the multi-step operations like copy ups and whiteouts in
a working directory and then renames the files into place.  The non-atomic
operations are hidden from the user until the atomic rename happens.

Fortunately, these are separate issues.  The old whiteout format can still
be safe if renamed into place like the new format is.  The old overlayfs
doesn't accept a workdir option, but we can still create a working directory
in the 'upper' file system and then use a virtual whiteout to hide it from
the user.

The main benefit for this, rather than using the original patchset using
the 'overlayfs' name, is that code divergence is made much, much smaller.
Changes to address any security issues that affect the 'new' overlayfs will 
also fix the same issue, if any, in the 'old' overlayfs.

Signed-off-by: Jeff Mahoney <jeffm@suse.com>
---
 fs/overlayfs/Kconfig     |   11 +++
 fs/overlayfs/Makefile    |    4 +
 fs/overlayfs/compat.c    |  139 +++++++++++++++++++++++++++++++++++++++++++++++
 fs/overlayfs/compat.h    |   45 +++++++++++++++
 fs/overlayfs/copy_up.c   |    9 ++-
 fs/overlayfs/dir.c       |   30 +++++++---
 fs/overlayfs/overlayfs.h |    2 
 fs/overlayfs/readdir.c   |    9 ++-
 fs/overlayfs/super.c     |  126 ++++++++++++++++++++++++++++++++++++------
 9 files changed, 345 insertions(+), 30 deletions(-)

--- a/fs/overlayfs/Kconfig
+++ b/fs/overlayfs/Kconfig
@@ -8,3 +8,14 @@ config OVERLAY_FS
 	  merged with the 'upper' object.
 
 	  For more information see Documentation/filesystems/overlayfs.txt
+config OVERLAY_FS_COMPAT
+	bool "Overlay filesystem compatibility support"
+	depends on OVERLAY_FS
+	help
+	  Versions of overlayfs prior to the implementation that was accepted
+	  in the mainline kernel used a different interface for
+	  whiteouts and didn't require the definition of a working directory.
+	  In typical file system terms, this is a disk format change.  This
+	  option allows the user to mount a file system using the 'overlayfs'
+	  file system that enables the use of the old semantics.
+
--- a/fs/overlayfs/Makefile
+++ b/fs/overlayfs/Makefile
@@ -4,4 +4,6 @@
 
 obj-$(CONFIG_OVERLAY_FS) += overlay.o
 
-overlay-objs := super.o inode.o dir.o readdir.o copy_up.o
+
+overlay-y := super.o inode.o dir.o readdir.o copy_up.o
+overlay-$(CONFIG_OVERLAY_FS_COMPAT) += compat.o
--- /dev/null
+++ b/fs/overlayfs/compat.c
@@ -0,0 +1,139 @@
+#include <linux/fs.h>
+#include <linux/xattr.h>
+#include <linux/cred.h>
+#include <linux/namei.h>
+#include <linux/mount.h>
+#include <linux/module.h>
+#include "overlayfs.h"
+#include "compat.h"
+
+static const char *ovl_whiteout_symlink = "(overlay-whiteout)";
+static const char *ovl_whiteout_xattr = "trusted.overlay.whiteout";
+
+int ovl_compat_whiteout(struct dentry *workdir, struct dentry *dentry,
+			struct dentry *whiteout)
+{
+        int err;
+        struct dentry *newdentry;
+        const struct cred *old_cred;
+        struct cred *override_cred;
+
+        /* FIXME: recheck lower dentry to see if whiteout is really needed */
+
+        err = -ENOMEM;
+        override_cred = prepare_creds();
+        if (!override_cred)
+                goto out;
+
+        /*
+         * CAP_SYS_ADMIN for setxattr
+         * CAP_DAC_OVERRIDE for symlink creation
+         * CAP_FOWNER for unlink in sticky directory
+         */
+        cap_raise(override_cred->cap_effective, CAP_SYS_ADMIN);
+        cap_raise(override_cred->cap_effective, CAP_DAC_OVERRIDE);
+        cap_raise(override_cred->cap_effective, CAP_FOWNER);
+        override_cred->fsuid = GLOBAL_ROOT_UID;
+        override_cred->fsgid = GLOBAL_ROOT_GID;
+        old_cred = override_creds(override_cred);
+
+        newdentry = lookup_one_len(whiteout->d_name.name, workdir,
+                                   whiteout->d_name.len);
+        err = PTR_ERR(newdentry);
+        if (IS_ERR(newdentry))
+                goto out_put_cred;
+        if (IS_ERR(newdentry))
+                goto out_put_cred;
+
+        /* Just been removed within the same locked region */
+        WARN_ON(newdentry->d_inode);
+
+        err = vfs_symlink(workdir->d_inode, newdentry, ovl_whiteout_symlink);
+        if (err)
+                goto out_dput;
+
+        ovl_dentry_version_inc(dentry->d_parent);
+
+        err = vfs_setxattr(newdentry, ovl_whiteout_xattr, "y", 1, 0);
+        if (err)
+                vfs_unlink(workdir->d_inode, newdentry, NULL);
+
+out_dput:
+        dput(newdentry);
+out_put_cred:
+        revert_creds(old_cred);
+        put_cred(override_cred);
+out:
+        if (err) {
+                /*
+                 * There's no way to recover from failure to whiteout.
+                 * What should we do?  Log a big fat error and... ?
+                 */
+                pr_err("overlayfs: ERROR - failed to whiteout '%s'\n",
+                       dentry->d_name.name);
+        }
+
+        return err;
+}
+
+bool ovl_compat_maybe_whiteout(struct dentry *dentry, char d_type, ino_t ino)
+{
+	struct dentry *workdir = ovl_workdir(dentry);
+
+	if (!ovl_compat_mode(dentry))
+		return false;
+
+	if (d_type == DT_LNK)
+		return true;
+
+	return workdir->d_parent->d_inode->i_ino == ino;
+}
+
+bool ovl_compat_is_whiteout(struct dentry *ovl_dir, struct dentry *dentry)
+{
+	int res;
+	char val;
+
+	if (!ovl_compat_mode(dentry))
+		return false;
+
+	if (!dentry)
+		return false;
+
+	if (!dentry->d_inode)
+		return false;
+
+	/* Hide the workdir we created */
+	if (dentry == ovl_workdir(ovl_dir)->d_parent)
+		return true;
+
+	if (!S_ISLNK(dentry->d_inode->i_mode))
+		return false;
+
+	res = vfs_getxattr(dentry, ovl_whiteout_xattr, &val, 1);
+	if (res == 1 && val == 'y')
+		return true;
+
+	return false;
+}
+
+int ovl_compat_mkdir(const char *name, struct path *path)
+{
+	struct dentry *dentry;
+	struct path tmppath;
+	int err;
+
+	dentry = kern_path_create(AT_FDCWD, name, &tmppath,
+				  LOOKUP_FOLLOW|LOOKUP_DIRECTORY);
+	if (IS_ERR(dentry))
+		return PTR_ERR(dentry);
+
+	err = vfs_mkdir(tmppath.dentry->d_inode, dentry, 0|S_IFDIR);
+	if (!err) {
+		path->mnt = mntget(tmppath.mnt);
+		path->dentry = dget(dentry);
+	}
+	done_path_create(&tmppath, dentry);
+
+	return err;
+}
--- /dev/null
+++ b/fs/overlayfs/compat.h
@@ -0,0 +1,45 @@
+#ifndef _OVL_FS_COMPAT_H_
+#define _OVL_FS_COMPAT_H_
+
+#define COMPAT_WORKDIR "..overlayfs.compat.workdir.do.not.touch"
+
+#ifdef CONFIG_OVERLAY_FS_COMPAT
+bool ovl_compat_mode(struct dentry *dentry);
+int ovl_compat_whiteout(struct dentry *workdir, struct dentry *dentry,
+			struct dentry *whiteout);
+bool ovl_compat_is_whiteout(struct dentry *ovl_dir, struct dentry *dentry);
+bool ovl_compat_maybe_whiteout(struct dentry *dentry, char d_type, ino_t ino);
+
+int ovl_compat_mkdir(const char *name, struct path *path);
+
+#else
+static inline bool ovl_compat_mode(struct dentry *dentry)
+{
+	return false;
+}
+
+static inline int ovl_compat_whiteout(struct dentry *workdir,
+				      struct dentry *dentry,
+				      struct dentry *whiteout)
+{
+	return -EINVAL;
+}
+
+static inline bool ovl_compat_is_whiteout(struct dentry *ovl_dir,
+					  struct dentry *dentry)
+{
+	return false;
+}
+
+static inline bool ovl_compat_maybe_whiteout(struct dentry *dentry,
+					     char d_type, ino_t ino)
+{
+	return false;
+}
+
+static inline int ovl_compat_mkdir(const char *name, struct path *path)
+{
+	return 0;
+}
+#endif
+#endif /* _OVL_FS_COMPAT_H_ */
--- a/fs/overlayfs/copy_up.c
+++ b/fs/overlayfs/copy_up.c
@@ -17,6 +17,7 @@
 #include <linux/sched.h>
 #include <linux/namei.h>
 #include "overlayfs.h"
+#include "compat.h"
 
 #define OVL_COPY_UP_CHUNK_SIZE (1 << 20)
 
@@ -317,10 +318,14 @@ int ovl_copy_up_one(struct dentry *paren
 	struct dentry *upperdentry;
 	const struct cred *old_cred;
 	char *link = NULL;
+	struct dentry *d, *ancestor = NULL;
 
 	if (WARN_ON(!workdir))
 		return -EROFS;
 
+	if (ovl_compat_mode(dentry))
+		ancestor = workdir->d_parent;
+
 	ovl_path_upper(parent, &parentpath);
 	upperdir = parentpath.dentry;
 
@@ -358,7 +363,9 @@ int ovl_copy_up_one(struct dentry *paren
 	old_cred = ovl_override_creds(dentry->d_sb);
 
 	err = -EIO;
-	if (lock_rename(workdir, upperdir) != NULL) {
+
+	d = lock_rename(workdir, upperdir);
+	if (d != NULL && d != ancestor) {
 		pr_err("overlayfs: failed to lock workdir+upperdir\n");
 		goto out_unlock;
 	}
--- a/fs/overlayfs/dir.c
+++ b/fs/overlayfs/dir.c
@@ -14,6 +14,7 @@
 #include <linux/cred.h>
 #include <linux/atomic.h>
 #include "overlayfs.h"
+#include "compat.h"
 
 void ovl_cleanup(struct inode *wdir, struct dentry *wdentry)
 {
@@ -63,7 +64,10 @@ static struct dentry *ovl_whiteout(struc
 	if (IS_ERR(whiteout))
 		return whiteout;
 
-	err = ovl_do_whiteout(wdir, whiteout);
+	if (!ovl_compat_mode(dentry))
+		err = ovl_do_whiteout(wdir, whiteout);
+	else
+		err = ovl_compat_whiteout(workdir, dentry, whiteout);
 	if (err) {
 		dput(whiteout);
 		whiteout = ERR_PTR(err);
@@ -193,14 +197,24 @@ out_unlock:
 }
 
 static int ovl_lock_rename_workdir(struct dentry *workdir,
-				   struct dentry *upperdir)
+				   struct dentry *upperdir,
+				   struct dentry *ovl_dir)
 {
+	struct dentry *d, *ancestor = NULL;
+
 	/* Workdir should not be the same as upperdir */
 	if (workdir == upperdir)
 		goto err;
 
-	/* Workdir should not be subdir of upperdir and vice versa */
-	if (lock_rename(workdir, upperdir) != NULL)
+	if (ovl_compat_mode(ovl_dir))
+		ancestor = workdir->d_parent;
+
+	/*
+	 * Workdir should not be subdir of upperdir and vice versa,
+	 * except in compat mode.
+	 */
+	d = lock_rename(workdir, upperdir);
+	if (d != NULL && d != ancestor)
 		goto err_unlock;
 
 	return 0;
@@ -228,7 +242,7 @@ static struct dentry *ovl_clear_empty(st
 	if (WARN_ON(!workdir))
 		return ERR_PTR(-EROFS);
 
-	err = ovl_lock_rename_workdir(workdir, upperdir);
+	err = ovl_lock_rename_workdir(workdir, upperdir, dentry);
 	if (err)
 		goto out;
 
@@ -331,7 +345,7 @@ static int ovl_create_over_whiteout(stru
 	if (WARN_ON(!workdir))
 		return -EROFS;
 
-	err = ovl_lock_rename_workdir(workdir, upperdir);
+	err = ovl_lock_rename_workdir(workdir, upperdir, dentry);
 	if (err)
 		goto out;
 
@@ -540,7 +554,7 @@ static int ovl_remove_and_whiteout(struc
 		}
 	}
 
-	err = ovl_lock_rename_workdir(workdir, upperdir);
+	err = ovl_lock_rename_workdir(workdir, upperdir, dentry);
 	if (err)
 		goto out_dput;
 
@@ -871,7 +885,7 @@ static int ovl_rename2(struct inode *old
 	} else {
 		new_create = true;
 		if (!d_is_negative(newdentry) &&
-		    (!new_opaque || !ovl_is_whiteout(newdentry)))
+		    (!new_opaque || !ovl_is_whiteout(newdentry, new_upperdir)))
 			goto out_dput;
 	}
 
--- a/fs/overlayfs/overlayfs.h
+++ b/fs/overlayfs/overlayfs.h
@@ -152,7 +152,7 @@ int ovl_want_write(struct dentry *dentry
 void ovl_drop_write(struct dentry *dentry);
 bool ovl_dentry_is_opaque(struct dentry *dentry);
 void ovl_dentry_set_opaque(struct dentry *dentry, bool opaque);
-bool ovl_is_whiteout(struct dentry *dentry);
+bool ovl_is_whiteout(struct dentry *dentry, struct dentry *ovl_dir);
 const struct cred *ovl_override_creds(struct super_block *sb);
 void ovl_dentry_update(struct dentry *dentry, struct dentry *upperdentry);
 struct dentry *ovl_lookup(struct inode *dir, struct dentry *dentry,
--- a/fs/overlayfs/readdir.c
+++ b/fs/overlayfs/readdir.c
@@ -16,6 +16,7 @@
 #include <linux/security.h>
 #include <linux/cred.h>
 #include "overlayfs.h"
+#include "compat.h"
 
 struct ovl_cache_entry {
 	unsigned int len;
@@ -98,7 +100,8 @@ static struct ovl_cache_entry *ovl_cache
 	p->ino = ino;
 	p->is_whiteout = false;
 
-	if (d_type == DT_CHR) {
+	if ((d_type == DT_CHR && !ovl_compat_mode(rdd->dentry)) ||
+	     ovl_compat_maybe_whiteout(rdd->dentry, d_type, ino)) {
 		p->next_maybe_whiteout = rdd->first_maybe_whiteout;
 		rdd->first_maybe_whiteout = p;
 	}
@@ -224,7 +227,8 @@ static int ovl_check_whiteouts(struct de
 			rdd->first_maybe_whiteout = p->next_maybe_whiteout;
 			dentry = lookup_one_len(p->name, dir, p->len);
 			if (!IS_ERR(dentry)) {
-				p->is_whiteout = ovl_is_whiteout(dentry);
+				p->is_whiteout = ovl_is_whiteout(dentry,
+								 rdd->dentry);
 				dput(dentry);
 			}
 		}
@@ -290,6 +294,7 @@ static int ovl_dir_read_merged(struct de
 		.list = list,
 		.root = RB_ROOT,
 		.is_merge = false,
+		.dentry = dentry,
 	};
 	int idx, next;
 
--- a/fs/overlayfs/super.c
+++ b/fs/overlayfs/super.c
@@ -20,6 +20,7 @@
 #include <linux/statfs.h>
 #include <linux/seq_file.h>
 #include "overlayfs.h"
+#include "compat.h"
 
 MODULE_AUTHOR("Miklos Szeredi <miklos@szeredi.hu>");
 MODULE_DESCRIPTION("Overlay filesystem");
@@ -43,6 +44,7 @@ struct ovl_fs {
 	struct ovl_config config;
 	/* creds of process who forced instantiation of super block */
 	const struct cred *creator_cred;
+	bool compat;
 };
 
 struct ovl_dir_cache;
@@ -216,6 +218,14 @@ struct dentry *ovl_workdir(struct dentry
 	return ofs->workdir;
 }
 
+#ifdef CONFIG_OVERLAY_FS_COMPAT
+bool ovl_compat_mode(struct dentry *dentry)
+{
+	struct ovl_fs *ofs = dentry->d_sb->s_fs_info;
+	return ofs->compat;
+}
+#endif
+
 bool ovl_dentry_is_opaque(struct dentry *dentry)
 {
 	struct ovl_entry *oe = dentry->d_fsdata;
@@ -259,10 +269,13 @@ u64 ovl_dentry_version_get(struct dentry
 	return oe->version;
 }
 
-bool ovl_is_whiteout(struct dentry *dentry)
+bool ovl_is_whiteout(struct dentry *dentry, struct dentry *ovl_dir)
 {
 	struct inode *inode = dentry->d_inode;
 
+	if (ovl_compat_mode(ovl_dir))
+		return ovl_compat_is_whiteout(ovl_dir, dentry);
+
 	return inode && IS_WHITEOUT(inode);
 }
 
@@ -479,7 +492,7 @@ struct dentry *ovl_lookup(struct inode *
 				err = -EREMOTE;
 				goto out;
 			}
-			if (ovl_is_whiteout(this)) {
+			if (ovl_is_whiteout(this, dentry)) {
 				dput(this);
 				this = NULL;
 				upperopaque = true;
@@ -513,7 +526,7 @@ struct dentry *ovl_lookup(struct inode *
 		}
 		if (!this)
 			continue;
-		if (ovl_is_whiteout(this)) {
+		if (ovl_is_whiteout(this, dentry)) {
 			dput(this);
 			break;
 		}
@@ -851,7 +864,8 @@ static void ovl_unescape(char *s)
 	}
 }
 
-static int ovl_mount_dir_noesc(const char *name, struct path *path)
+static int ovl_mount_dir_noesc(const char *name, struct path *path,
+			       struct ovl_fs *ufs)
 {
 	int err = -EINVAL;
 
@@ -860,6 +874,9 @@ static int ovl_mount_dir_noesc(const cha
 		goto out;
 	}
 	err = kern_path(name, LOOKUP_FOLLOW, path);
+	if (err == -ENOENT && ufs->compat)
+		err = ovl_compat_mkdir(name, path);
+
 	if (err) {
 		pr_err("overlayfs: failed to resolve '%s': %i\n", name, err);
 		goto out;
@@ -881,14 +898,15 @@ out:
 	return err;
 }
 
-static int ovl_mount_dir(const char *name, struct path *path)
+static int ovl_mount_dir(struct ovl_fs *ufs, const char *name,
+			 struct path *path)
 {
 	int err = -ENOMEM;
 	char *tmp = kstrdup(name, GFP_KERNEL);
 
 	if (tmp) {
 		ovl_unescape(tmp);
-		err = ovl_mount_dir_noesc(tmp, path);
+		err = ovl_mount_dir_noesc(tmp, path, ufs);
 
 		if (!err)
 			if (ovl_dentry_remote(path->dentry)) {
@@ -903,12 +921,12 @@ static int ovl_mount_dir(const char *nam
 }
 
 static int ovl_lower_dir(const char *name, struct path *path, long *namelen,
-			 int *stack_depth, bool *remote)
+			 int *stack_depth, bool *remote, struct ovl_fs *ufs)
 {
 	int err;
 	struct kstatfs statfs;
 
-	err = ovl_mount_dir_noesc(name, path);
+	err = ovl_mount_dir_noesc(name, path, ufs);
 	if (err)
 		goto out;
 
@@ -931,13 +949,21 @@ out:
 	return err;
 }
 
-/* Workdir should not be subdir of upperdir and vice versa */
-static bool ovl_workdir_ok(struct dentry *workdir, struct dentry *upperdir)
+/*
+ * Workdir should not be subdir of upperdir and vice versa
+ * In compatibility mode, workdir must be a subdir of upperdir
+ */
+static bool ovl_workdir_ok(struct dentry *workdir, struct dentry *upperdir,
+			   struct ovl_fs *ofs)
 {
 	bool ok = false;
+	struct dentry *ancestor = NULL;
+
+	if (ofs->compat)
+		ancestor = workdir;
 
 	if (workdir != upperdir) {
-		ok = (lock_rename(workdir, upperdir) == NULL);
+		ok = (lock_rename(workdir, upperdir) == ancestor);
 		unlock_rename(workdir, upperdir);
 	}
 	return ok;
@@ -963,6 +989,8 @@ static unsigned int ovl_split_lowerdirs(
 	return ctr;
 }
 
+static bool ovl_compat_sb(struct super_block *sb);
+
 static int ovl_fill_super(struct super_block *sb, void *data, int silent)
 {
 	struct path upperpath = { NULL, NULL };
@@ -984,6 +1012,8 @@ static int ovl_fill_super(struct super_b
 	if (!ufs)
 		goto out;
 
+	ufs->compat = ovl_compat_sb(sb);
+
 	err = ovl_parse_opt((char *) data, &ufs->config);
 	if (err)
 		goto out_free_config;
@@ -994,6 +1024,24 @@ static int ovl_fill_super(struct super_b
 		goto out_free_config;
 	}
 
+#ifdef CONFIG_OVERLAY_FS_COMPAT
+	if (ufs->compat) {
+		if (ufs->config.workdir) {
+			pr_err("overlayfs: 'workdir' is prohibited in compatibility mode\n");
+			err = -EINVAL;
+			goto out_free_config;
+		}
+
+		ufs->config.workdir = kasprintf(GFP_KERNEL, "%s/%s",
+						ufs->config.upperdir,
+						COMPAT_WORKDIR);
+		if (!ufs->config.workdir) {
+			err = -ENOMEM;
+			goto out_free_config;
+		}
+	}
+#endif
+
 	sb->s_stack_depth = 0;
 	sb->s_maxbytes = MAX_LFS_FILESIZE;
 	if (ufs->config.upperdir) {
@@ -1002,7 +1050,7 @@ static int ovl_fill_super(struct super_b
 			goto out_free_config;
 		}
 
-		err = ovl_mount_dir(ufs->config.upperdir, &upperpath);
+		err = ovl_mount_dir(ufs, ufs->config.upperdir, &upperpath);
 		if (err)
 			goto out_free_config;
 
@@ -1013,7 +1061,7 @@ static int ovl_fill_super(struct super_b
 			goto out_put_upperpath;
 		}
 
-		err = ovl_mount_dir(ufs->config.workdir, &workpath);
+		err = ovl_mount_dir(ufs, ufs->config.workdir, &workpath);
 		if (err)
 			goto out_put_upperpath;
 
@@ -1022,11 +1070,17 @@ static int ovl_fill_super(struct super_b
 			pr_err("overlayfs: workdir and upperdir must reside under the same mount\n");
 			goto out_put_workpath;
 		}
-		if (!ovl_workdir_ok(workpath.dentry, upperpath.dentry)) {
-			pr_err("overlayfs: workdir and upperdir must be separate subtrees\n");
+		if (!ovl_workdir_ok(workpath.dentry, upperpath.dentry, ufs)) {
+			if (!ufs->compat)
+				pr_err("overlayfs: workdir and upperdir must be separate subtrees\n");
+			else
+				pr_err("overlayfs: workdir must be subdir of upperdir in compatibility mode (internal error)\n");
 			goto out_put_workpath;
 		}
 		sb->s_stack_depth = upperpath.mnt->mnt_sb->s_stack_depth;
+	} else if (ufs->compat) {
+		pr_err("overlayfs: missing upperdir in compatibility mode.\n");
+		goto out_free_config;
 	}
 	err = -ENOMEM;
 	lowertmp = kstrdup(ufs->config.lowerdir, GFP_KERNEL);
@@ -1042,6 +1096,9 @@ static int ovl_fill_super(struct super_b
 	} else if (!ufs->config.upperdir && stacklen == 1) {
 		pr_err("overlayfs: at least 2 lowerdir are needed while upperdir nonexistent\n");
 		goto out_free_lowertmp;
+	} else if (ufs->compat && stacklen > 1) {
+		pr_err("overlayfs: compatibility mode is limited to one lower directory\n");
+		goto out_free_lowertmp;
 	}
 
 	stack = kcalloc(stacklen, sizeof(struct path), GFP_KERNEL);
@@ -1052,7 +1109,7 @@ static int ovl_fill_super(struct super_b
 	for (numlower = 0; numlower < stacklen; numlower++) {
 		err = ovl_lower_dir(lower, &stack[numlower],
 				    &ufs->lower_namelen, &sb->s_stack_depth,
-				    &remote);
+				    &remote, ufs);
 		if (err)
 			goto out_put_lowerpath;
 
@@ -1147,6 +1204,9 @@ static int ovl_fill_super(struct super_b
 	sb->s_root = root_dentry;
 	sb->s_fs_info = ufs;
 
+	if (ufs->compat)
+		pr_info("overlayfs: mounted file system in 'overlayfs' compatibility mode.  This mode is is offered for compatibility with a historical 'early adopter' format and may be unsupported in a future release.  Please consider using the official 'overlay' interface instead.\n");
+
 	return 0;
 
 out_free_oe:
@@ -1191,13 +1251,45 @@ static struct file_system_type ovl_fs_ty
 };
 MODULE_ALIAS_FS("overlay");
 
+#ifdef CONFIG_OVERLAY_FS_COMPAT
+static struct file_system_type ovl_compat_fs_type = {
+	.owner		= THIS_MODULE,
+	.name		= "overlayfs",
+	.mount		= ovl_mount,
+	.kill_sb	= kill_anon_super,
+};
+MODULE_ALIAS_FS("overlayfs");
+#endif
+
+static bool ovl_compat_sb(struct super_block *sb)
+{
+#ifdef CONFIG_OVERLAY_FS_COMPAT
+	return sb->s_type == &ovl_compat_fs_type;
+#else
+	return false;
+#endif
+}
+
 static int __init ovl_init(void)
 {
-	return register_filesystem(&ovl_fs_type);
+	int ret = register_filesystem(&ovl_fs_type);
+	if (ret)
+		return ret;
+
+#ifdef CONFIG_OVERLAY_FS_COMPAT
+	ret = register_filesystem(&ovl_compat_fs_type);
+	if (ret)
+		unregister_filesystem(&ovl_fs_type);
+#endif
+
+	return ret;
 }
 
 static void __exit ovl_exit(void)
 {
+#ifdef CONFIG_OVERLAY_FS_COMPAT
+	unregister_filesystem(&ovl_compat_fs_type);
+#endif
 	unregister_filesystem(&ovl_fs_type);
 }
 
