コード例 #1
0
ファイル: inode.c プロジェクト: itsmerajit/kernel_otus
static int esdfs_permission(struct inode *inode, int mask)
{
	struct esdfs_sb_info *sbi = ESDFS_SB(inode->i_sb);
	struct inode *lower_inode;
	int err;

	/* First, check the upper permissions */
	err = generic_permission(inode, mask);

	/* Basic checking of the lower inode (can't override creds here) */
	lower_inode = esdfs_lower_inode(inode);
	if (lower_inode->i_uid != sbi->lower_perms.uid ||
	    lower_inode->i_gid != sbi->lower_perms.gid ||
	    S_ISSOCK(lower_inode->i_mode) ||
	    S_ISLNK(lower_inode->i_mode) ||
	    S_ISBLK(lower_inode->i_mode) ||
	    S_ISCHR(lower_inode->i_mode) ||
	    S_ISFIFO(lower_inode->i_mode))
		err = -EACCES;

	/* Finally, check the derived permissions */
	if (!err && ESDFS_DERIVE_PERMS(ESDFS_SB(inode->i_sb)))
		err = esdfs_check_derived_permission(inode, mask);

	return err;
}
コード例 #2
0
static void esdfs_kill_sb(struct super_block *sb)
{
	if (ESDFS_SB(sb)->obb_parent)
		dput(ESDFS_SB(sb)->obb_parent);

	generic_shutdown_super(sb);
}
コード例 #3
0
ファイル: inode.c プロジェクト: itsmerajit/kernel_otus
static int esdfs_create(struct inode *dir, struct dentry *dentry,
			 umode_t mode, struct nameidata *nd)
{
	int err = 0;
	struct dentry *lower_dentry;
	struct dentry *lower_parent_dentry = NULL;
	struct path lower_path, saved_path;
	struct inode *lower_inode;
	int mask;
	const struct cred *creds;

	/*
	 * Need to recheck derived permissions unified mode to prevent certain
	 * applications from creating files at the root.
	 */
	if (test_opt(ESDFS_SB(dir->i_sb), DERIVE_UNIFIED) &&
	    esdfs_check_derived_permission(dir, ESDFS_MAY_CREATE) != 0)
		return -EACCES;

	creds = esdfs_override_creds(ESDFS_SB(dir->i_sb), &mask);
	if (!creds)
		return -ENOMEM;

	esdfs_get_lower_path(dentry, &lower_path);
	lower_dentry = lower_path.dentry;
	lower_parent_dentry = lock_parent(lower_dentry);

	err = mnt_want_write(lower_path.mnt);
	if (err)
		goto out_unlock;

	esdfs_set_lower_mode(ESDFS_SB(dir->i_sb), &mode);

	lower_inode = esdfs_lower_inode(dir);
	pathcpy(&saved_path, &nd->path);
	pathcpy(&nd->path, &lower_path);
	err = vfs_create(lower_inode, lower_dentry, mode, nd);
	pathcpy(&nd->path, &saved_path);
	if (err)
		goto out;

	err = esdfs_interpose(dentry, dir->i_sb, &lower_path);
	if (err)
		goto out;
	fsstack_copy_attr_times(dir, esdfs_lower_inode(dir));
	fsstack_copy_inode_size(dir, lower_parent_dentry->d_inode);

out:
	mnt_drop_write(lower_path.mnt);
out_unlock:
	unlock_dir(lower_parent_dentry);
	esdfs_put_lower_path(dentry, &lower_path);
	esdfs_revert_creds(creds, &mask);
	return err;
}
コード例 #4
0
ファイル: inode.c プロジェクト: itsmerajit/kernel_otus
static int esdfs_mkdir(struct inode *dir, struct dentry *dentry, umode_t mode)
{
	int err = 0;
	struct dentry *lower_dentry;
	struct dentry *lower_parent_dentry = NULL;
	struct path lower_path;
	int mask;
	const struct cred *creds =
			esdfs_override_creds(ESDFS_SB(dir->i_sb), &mask);
	if (!creds)
		return -ENOMEM;

	esdfs_get_lower_path(dentry, &lower_path);
	lower_dentry = lower_path.dentry;
	lower_parent_dentry = lock_parent(lower_dentry);

	mode |= S_IFDIR;
	esdfs_set_lower_mode(ESDFS_SB(dir->i_sb), &mode);

	err = mnt_want_write(lower_path.mnt);
	if (err)
		goto out_unlock;
	err = vfs_mkdir(lower_parent_dentry->d_inode, lower_dentry, mode);
	if (err)
		goto out;

	err = esdfs_interpose(dentry, dir->i_sb, &lower_path);
	if (err)
		goto out;

	fsstack_copy_attr_times(dir, esdfs_lower_inode(dir));
	fsstack_copy_inode_size(dir, lower_parent_dentry->d_inode);
	/* update number of links on parent directory */
	set_nlink(dir, esdfs_lower_inode(dir)->i_nlink);

	if (ESDFS_DERIVE_PERMS(ESDFS_SB(dir->i_sb)))
		err = esdfs_derive_mkdir_contents(dentry);

out:
	mnt_drop_write(lower_path.mnt);
out_unlock:
	unlock_dir(lower_parent_dentry);
	esdfs_put_lower_path(dentry, &lower_path);
	esdfs_revert_creds(creds, &mask);
	return err;
}
コード例 #5
0
ファイル: inode.c プロジェクト: itsmerajit/kernel_otus
static int esdfs_unlink(struct inode *dir, struct dentry *dentry)
{
	int err;
	struct dentry *lower_dentry;
	struct inode *lower_dir_inode = esdfs_lower_inode(dir);
	struct dentry *lower_dir_dentry;
	struct path lower_path;
	const struct cred *creds;

	creds = esdfs_override_creds(ESDFS_SB(dir->i_sb), NULL);
	if (!creds)
		return -ENOMEM;

	esdfs_get_lower_path(dentry, &lower_path);
	lower_dentry = lower_path.dentry;
	dget(lower_dentry);
	lower_dir_dentry = lock_parent(lower_dentry);

	err = mnt_want_write(lower_path.mnt);
	if (err)
		goto out_unlock;
	err = vfs_unlink(lower_dir_inode, lower_dentry);

	/*
	 * Note: unlinking on top of NFS can cause silly-renamed files.
	 * Trying to delete such files results in EBUSY from NFS
	 * below.  Silly-renamed files will get deleted by NFS later on, so
	 * we just need to detect them here and treat such EBUSY errors as
	 * if the upper file was successfully deleted.
	 */
	if (err == -EBUSY && lower_dentry->d_flags & DCACHE_NFSFS_RENAMED)
		err = 0;
	if (err)
		goto out;
	fsstack_copy_attr_times(dir, lower_dir_inode);
	fsstack_copy_inode_size(dir, lower_dir_inode);
	set_nlink(dentry->d_inode,
		  esdfs_lower_inode(dentry->d_inode)->i_nlink);
	dentry->d_inode->i_ctime = dir->i_ctime;
	d_drop(dentry); /* this is needed, else LTP fails (VFS won't do it) */
out:
	mnt_drop_write(lower_path.mnt);
out_unlock:
	unlock_dir(lower_dir_dentry);
	dput(lower_dentry);
	esdfs_put_lower_path(dentry, &lower_path);
	esdfs_revert_creds(creds, NULL);
	return err;
}
コード例 #6
0
ファイル: super.c プロジェクト: itsmerajit/kernel_otus
/* final actions when unmounting a file system */
static void esdfs_put_super(struct super_block *sb)
{
	struct esdfs_sb_info *spd;
	struct super_block *s;

	spd = ESDFS_SB(sb);
	if (!spd)
		return;

	/* decrement lower super references */
	s = esdfs_lower_super(sb);
	esdfs_set_lower_super(sb, NULL);
	atomic_dec(&s->s_active);

	kfree(spd);
	sb->s_fs_info = NULL;
}
コード例 #7
0
ファイル: inode.c プロジェクト: itsmerajit/kernel_otus
static int esdfs_rmdir(struct inode *dir, struct dentry *dentry)
{
	struct dentry *lower_dentry;
	struct dentry *lower_dir_dentry;
	int err;
	struct path lower_path;
	const struct cred *creds =
			esdfs_override_creds(ESDFS_SB(dir->i_sb), NULL);
	if (!creds)
		return -ENOMEM;

	/* Never remove a pseudo link target.  Only the source. */
	if (ESDFS_DENTRY_HAS_STUB(dentry))
		esdfs_get_lower_stub_path(dentry, &lower_path);
	else
		esdfs_get_lower_path(dentry, &lower_path);
	lower_dentry = lower_path.dentry;
	lower_dir_dentry = lock_parent(lower_dentry);

	err = mnt_want_write(lower_path.mnt);
	if (err)
		goto out_unlock;
	err = vfs_rmdir(lower_dir_dentry->d_inode, lower_dentry);
	if (err)
		goto out;

	d_drop(dentry);	/* drop our dentry on success (why not VFS's job?) */
	if (dentry->d_inode)
		clear_nlink(dentry->d_inode);
	fsstack_copy_attr_times(dir, lower_dir_dentry->d_inode);
	fsstack_copy_inode_size(dir, lower_dir_dentry->d_inode);
	set_nlink(dir, lower_dir_dentry->d_inode->i_nlink);

out:
	mnt_drop_write(lower_path.mnt);
out_unlock:
	unlock_dir(lower_dir_dentry);
	esdfs_put_lower_path(dentry, &lower_path);
	esdfs_revert_creds(creds, NULL);
	return err;
}
コード例 #8
0
ファイル: super.c プロジェクト: itsmerajit/kernel_otus
static int esdfs_show_options(struct seq_file *seq, struct dentry *root)
{
	struct esdfs_sb_info *sbi = ESDFS_SB(root->d_sb);

	if (memcmp(&sbi->lower_perms,
		   &esdfs_perms_table[ESDFS_PERMS_LOWER_DEFAULT],
		   sizeof(struct esdfs_perms)))
		seq_printf(seq, ",lower=%u:%u:%ho:%ho",
				sbi->lower_perms.uid,
				sbi->lower_perms.gid,
				sbi->lower_perms.fmask,
				sbi->lower_perms.dmask);

	if (memcmp(&sbi->upper_perms,
		   &esdfs_perms_table[ESDFS_PERMS_UPPER_LEGACY],
		   sizeof(struct esdfs_perms)))
		seq_printf(seq, ",upper=%u:%u:%ho:%ho",
				sbi->upper_perms.uid,
				sbi->upper_perms.gid,
				sbi->upper_perms.fmask,
				sbi->upper_perms.dmask);

	if (test_opt(sbi, DERIVE_PUBLIC))
		seq_puts(seq, ",derive=public");
	else if (test_opt(sbi, DERIVE_MULTI))
		seq_puts(seq, ",derive=multi");
	else if (test_opt(sbi, DERIVE_UNIFIED))
		seq_puts(seq, ",derive=unified");
	else if (test_opt(sbi, DERIVE_LEGACY))
		seq_puts(seq, ",derive=legacy");
	else
		seq_puts(seq, ",derive=none");

	if (test_opt(sbi, DERIVE_CONFINE))
		seq_puts(seq, ",confine");
	else
		seq_puts(seq, ",noconfine");

	return 0;
}
コード例 #9
0
ファイル: inode.c プロジェクト: itsmerajit/kernel_otus
static int esdfs_getattr(struct vfsmount *mnt, struct dentry *dentry,
		 struct kstat *stat)
{
	int err;
	struct path lower_path;
	struct vfsmount *lower_mount;
	struct dentry *lower_dentry;
	struct kstat lower_stat;
	struct inode *lower_inode;
	struct inode *inode = dentry->d_inode;
	const struct cred *creds =
			esdfs_override_creds(ESDFS_SB(inode->i_sb), NULL);
	if (!creds)
		return -ENOMEM;

	esdfs_get_lower_path(dentry, &lower_path);
	lower_mount = lower_path.mnt;
	lower_dentry = lower_path.dentry;

	/* We need the lower getattr to calculate stat->blocks for us. */
	err = vfs_getattr(lower_mount, lower_dentry, &lower_stat);
	if (err)
		goto out;

	lower_inode = esdfs_lower_inode(inode);
	esdfs_copy_attr(inode, lower_inode);
	fsstack_copy_inode_size(inode, lower_inode);
	generic_fillattr(inode, stat);

	stat->blocks = lower_stat.blocks;

out:
	esdfs_put_lower_path(dentry, &lower_path);
	esdfs_revert_creds(creds, NULL);
	return err;
}
コード例 #10
0
ファイル: inode.c プロジェクト: itsmerajit/kernel_otus
static int esdfs_setattr(struct dentry *dentry, struct iattr *ia)
{
	int err = 0;
	struct dentry *lower_dentry;
	struct inode *inode;
	struct inode *lower_inode;
	struct path lower_path;
	struct iattr lower_ia;
	const struct cred *creds;

	/* We don't allow chmod or chown, so skip those */
	ia->ia_valid &= ~(ATTR_UID | ATTR_GID | ATTR_MODE);
	if (!ia->ia_valid)
		return 0;

	inode = dentry->d_inode;

	/*
	 * Check if user has permission to change inode.  We don't check if
	 * this user can change the lower inode: that should happen when
	 * calling notify_change on the lower inode.
	 */
	err = inode_change_ok(inode, ia);
	if (err)
		return err;

	creds = esdfs_override_creds(ESDFS_SB(dentry->d_inode->i_sb), NULL);
	if (!creds)
		return -ENOMEM;

	esdfs_get_lower_path(dentry, &lower_path);
	lower_dentry = lower_path.dentry;
	lower_inode = esdfs_lower_inode(inode);

	/* prepare our own lower struct iattr (with the lower file) */
	memcpy(&lower_ia, ia, sizeof(lower_ia));
	if (ia->ia_valid & ATTR_FILE)
		lower_ia.ia_file = esdfs_lower_file(ia->ia_file);

	/*
	 * If shrinking, first truncate upper level to cancel writing dirty
	 * pages beyond the new eof; and also if its' maxbytes is more
	 * limiting (fail with -EFBIG before making any change to the lower
	 * level).  There is no need to vmtruncate the upper level
	 * afterwards in the other cases: we fsstack_copy_inode_size from
	 * the lower level.
	 */
	if (ia->ia_valid & ATTR_SIZE) {
		err = inode_newsize_ok(inode, ia->ia_size);
		if (err)
			goto out;
		truncate_setsize(inode, ia->ia_size);
	}

	/*
	 * mode change is for clearing setuid/setgid bits. Allow lower fs
	 * to interpret this in its own way.
	 */
	if (lower_ia.ia_valid & (ATTR_KILL_SUID | ATTR_KILL_SGID))
		lower_ia.ia_valid &= ~ATTR_MODE;

	/* notify the (possibly copied-up) lower inode */
	/*
	 * Note: we use lower_dentry->d_inode, because lower_inode may be
	 * unlinked (no inode->i_sb and i_ino==0.  This happens if someone
	 * tries to open(), unlink(), then ftruncate() a file.
	 */
	mutex_lock(&lower_dentry->d_inode->i_mutex);
	err = notify_change(lower_dentry, &lower_ia); /* note: lower_ia */
	mutex_unlock(&lower_dentry->d_inode->i_mutex);
	if (err)
		goto out;

	/* get attributes from the lower inode */
	esdfs_copy_attr(inode, lower_inode);
	/*
	 * Not running fsstack_copy_inode_size(inode, lower_inode), because
	 * VFS should update our inode size, and notify_change on
	 * lower_inode should update its size.
	 */

out:
	esdfs_put_lower_path(dentry, &lower_path);
	esdfs_revert_creds(creds, NULL);
	return err;
}
コード例 #11
0
ファイル: inode.c プロジェクト: itsmerajit/kernel_otus
/*
 * The locking rules in esdfs_rename are complex.  We could use a simpler
 * superblock-level name-space lock for renames and copy-ups.
 */
static int esdfs_rename(struct inode *old_dir, struct dentry *old_dentry,
			 struct inode *new_dir, struct dentry *new_dentry)
{
	int err = 0;
	struct dentry *lower_old_dentry = NULL;
	struct dentry *lower_new_dentry = NULL;
	struct dentry *lower_old_dir_dentry = NULL;
	struct dentry *lower_new_dir_dentry = NULL;
	struct dentry *trap = NULL;
	struct path lower_old_path, lower_new_path;
	int mask;
	const struct cred *creds =
			esdfs_override_creds(ESDFS_SB(old_dir->i_sb), &mask);
	if (!creds)
		return -ENOMEM;

	/* Never rename to or from a pseudo hard link target. */
	if (ESDFS_DENTRY_HAS_STUB(old_dentry))
		esdfs_get_lower_stub_path(old_dentry, &lower_old_path);
	else
		esdfs_get_lower_path(old_dentry, &lower_old_path);
	if (ESDFS_DENTRY_HAS_STUB(new_dentry))
		esdfs_get_lower_stub_path(new_dentry, &lower_new_path);
	else
		esdfs_get_lower_path(new_dentry, &lower_new_path);
	lower_old_dentry = lower_old_path.dentry;
	lower_new_dentry = lower_new_path.dentry;
	esdfs_get_lower_parent(old_dentry, lower_old_dentry,
			       &lower_old_dir_dentry);
	esdfs_get_lower_parent(new_dentry, lower_new_dentry,
			       &lower_new_dir_dentry);

	trap = lock_rename(lower_old_dir_dentry, lower_new_dir_dentry);
	/* source should not be ancestor of target */
	if (trap == lower_old_dentry) {
		err = -EINVAL;
		goto out;
	}
	/* target should not be ancestor of source */
	if (trap == lower_new_dentry) {
		err = -ENOTEMPTY;
		goto out;
	}

	err = mnt_want_write(lower_old_path.mnt);
	if (err)
		goto out;
	err = mnt_want_write(lower_new_path.mnt);
	if (err)
		goto out_drop_old_write;

	err = vfs_rename(lower_old_dir_dentry->d_inode, lower_old_dentry,
			 lower_new_dir_dentry->d_inode, lower_new_dentry);
	if (err)
		goto out_err;

	esdfs_copy_attr(new_dir, lower_new_dir_dentry->d_inode);
	fsstack_copy_inode_size(new_dir, lower_new_dir_dentry->d_inode);
	if (new_dir != old_dir) {
		esdfs_copy_attr(old_dir,
				      lower_old_dir_dentry->d_inode);
		fsstack_copy_inode_size(old_dir,
					lower_old_dir_dentry->d_inode);
	}

	/* Drop any old links */
	if (ESDFS_DENTRY_HAS_STUB(old_dentry))
		d_drop(old_dentry);
	if (ESDFS_DENTRY_HAS_STUB(new_dentry))
		d_drop(new_dentry);
out_err:
	mnt_drop_write(lower_new_path.mnt);
out_drop_old_write:
	mnt_drop_write(lower_old_path.mnt);
out:
	unlock_rename(lower_old_dir_dentry, lower_new_dir_dentry);
	esdfs_put_lower_parent(old_dentry, &lower_old_dir_dentry);
	esdfs_put_lower_parent(new_dentry, &lower_new_dir_dentry);
	esdfs_put_lower_path(old_dentry, &lower_old_path);
	esdfs_put_lower_path(new_dentry, &lower_new_path);
	esdfs_revert_creds(creds, &mask);
	return err;
}
コード例 #12
0
static int parse_options(struct super_block *sb, char *options)
{
	struct esdfs_sb_info *sbi = ESDFS_SB(sb);
	substring_t args[MAX_OPT_ARGS];
	char *p;

	if (!options)
		return 0;

	while ((p = strsep(&options, ",")) != NULL) {
		int token;
		if (!*p)
			continue;
		/*
		 * Initialize args struct so we know whether arg was
		 * found; some options take optional arguments.
		 */
		args[0].to = args[0].from = NULL;
		token = match_token(p, esdfs_tokens, args);

		switch (token) {
		case Opt_lower_perms:
			if (args->from) {
				int ret;
				char *perms = match_strdup(args);

				ret = parse_perms(&sbi->lower_perms, perms);
				kfree(perms);

				if (ret)
					return -EINVAL;
			} else
				return -EINVAL;
			break;
		case Opt_upper_perms:
			if (args->from) {
				int ret;
				char *perms = match_strdup(args);

				ret = parse_perms(&sbi->upper_perms, perms);
				kfree(perms);

				if (ret)
					return -EINVAL;
			} else
				return -EINVAL;
			break;
		case Opt_derive_none:
			clear_opt(sbi, DERIVE_LEGACY);
			clear_opt(sbi, DERIVE_UNIFIED);
			break;
		case Opt_derive_legacy:
			set_opt(sbi, DERIVE_LEGACY);
			clear_opt(sbi, DERIVE_UNIFIED);
			break;
		case Opt_derive_unified:
			clear_opt(sbi, DERIVE_LEGACY);
			set_opt(sbi, DERIVE_UNIFIED);
			break;
		case Opt_split:
			set_opt(sbi, DERIVE_SPLIT);
			break;
		case Opt_nosplit:
			clear_opt(sbi, DERIVE_SPLIT);
			break;
		default:
			esdfs_msg(sb, KERN_ERR, "unrecognized mount option \"%s\" or missing value\n",
				p);
			return -EINVAL;
		}
	}
	return 0;
}
コード例 #13
0
/*
 * There is no need to lock the esdfs_super_info's rwsem as there is no
 * way anyone can have a reference to the superblock at this point in time.
 */
static int esdfs_read_super(struct super_block *sb, const char *dev_name,
		void *raw_data, int silent)
{
	int err = 0;
	struct super_block *lower_sb;
	struct path lower_path;
	struct esdfs_sb_info *sbi;
	struct inode *inode;

	if (!dev_name) {
		esdfs_msg(sb, KERN_ERR, "missing dev_name argument\n");
		err = -EINVAL;
		goto out;
	}

	/* parse lower path */
	err = kern_path(dev_name, LOOKUP_FOLLOW | LOOKUP_DIRECTORY,
			&lower_path);
	if (err) {
		esdfs_msg(sb, KERN_ERR, "error accessing lower directory '%s'\n",
			dev_name);
		goto out;
	}

	/* allocate superblock private data */
	sb->s_fs_info = kzalloc(sizeof(struct esdfs_sb_info), GFP_KERNEL);
	sbi = ESDFS_SB(sb);
	if (!sbi) {
		esdfs_msg(sb, KERN_CRIT, "read_super: out of memory\n");
		err = -ENOMEM;
		goto out_pput;
	}

	/* set defaults and then parse the mount options */
	memcpy(&sbi->lower_perms,
	       &esdfs_perms_table[ESDFS_PERMS_LOWER_DEFAULT],
	       sizeof(struct esdfs_perms));
	memcpy(&sbi->upper_perms,
	       &esdfs_perms_table[ESDFS_PERMS_UPPER_LEGACY],
	       sizeof(struct esdfs_perms));
	err = parse_options(sb, (char *)raw_data);
	if (err)
		goto out_free;

	/* set the lower superblock field of upper superblock */
	lower_sb = lower_path.dentry->d_sb;
	atomic_inc(&lower_sb->s_active);
	esdfs_set_lower_super(sb, lower_sb);

	/* inherit maxbytes from lower file system */
	sb->s_maxbytes = lower_sb->s_maxbytes;

	/*
	 * Our c/m/atime granularity is 1 ns because we may stack on file
	 * systems whose granularity is as good.
	 */
	sb->s_time_gran = 1;

	sb->s_op = &esdfs_sops;

	/* get a new inode and allocate our root dentry */
	inode = esdfs_iget(sb, lower_path.dentry->d_inode);
	if (IS_ERR(inode)) {
		err = PTR_ERR(inode);
		goto out_sput;
	}
	sb->s_root = d_make_root(inode);
	if (!sb->s_root) {
		err = -ENOMEM;
		goto out_iput;
	}
	d_set_d_op(sb->s_root, &esdfs_dops);

	/* link the upper and lower dentries */
	sb->s_root->d_fsdata = NULL;
	err = new_dentry_private_data(sb->s_root);
	if (err)
		goto out_freeroot;

	/* if get here: cannot have error */

	/* set the lower dentries for s_root */
	esdfs_set_lower_path(sb->s_root, &lower_path);
#ifdef CONFIG_SECURITY_SELINUX
	security_secctx_to_secid(ESDFS_LOWER_SECCTX,
				 strlen(ESDFS_LOWER_SECCTX),
				 &sbi->lower_secid);
#endif
	/*
	 * No need to call interpose because we already have a positive
	 * dentry, which was instantiated by d_make_root.  Just need to
	 * d_rehash it.
	 */
	d_rehash(sb->s_root);
	if (!silent)
		esdfs_msg(sb, KERN_INFO, "mounted on top of %s type %s\n",
			dev_name, lower_sb->s_type->name);

	if (!ESDFS_DERIVE_PERMS(sbi))
		goto out;

	/* let user know that we ignore this option in derived mode */
	if (memcmp(&sbi->upper_perms,
		   &esdfs_perms_table[ESDFS_PERMS_UPPER_LEGACY],
		   sizeof(struct esdfs_perms)))
		esdfs_msg(sb, KERN_WARNING, "'upper' mount option ignored in derived mode\n");

	/* all derived modes start with the same, basic root */
	memcpy(&sbi->upper_perms,
	       &esdfs_perms_table[ESDFS_PERMS_UPPER_DERIVED],
	       sizeof(struct esdfs_perms));

	/*
	 * In Android 3.0 all user conent in the emulated storage tree was
	 * stored in /data/media.  Android 4.2 introduced multi-user support,
	 * which required that the primary user's content be migrated from
	 * /data/media to /data/media/0.  The framework then uses bind mounts
	 * to create per-process namespaces to isolate each user's tree at
	 * /data/media/N.  This approach of having each user in a common root
	 * is now considered "legacy" by the sdcard service.
	 */
	if (test_opt(sbi, DERIVE_LEGACY)) {
		ESDFS_I(inode)->tree = ESDFS_TREE_ROOT_LEGACY;
		sbi->obb_parent = dget(sb->s_root);
	/*
	 * Android 4.4 reorganized this sturcture yet again, so that the
	 * primary user's content was again at the root.  Secondary users'
	 * content is found in Android/user/N.  Emulated internal storage still
	 * seems to use the legacy tree, but secondary external storage uses
	 * this method.
	 */
	} else if (test_opt(sbi, DERIVE_UNIFIED))
		ESDFS_I(inode)->tree = ESDFS_TREE_ROOT;
	/*
	 * Later versions of Android organize user content using quantum
	 * entanglement, which has a low probability of being supported by
	 * this driver.
	 */
	else
		esdfs_msg(sb, KERN_WARNING, "unsupported derived permissions mode\n");

	/* initialize root inode */
	esdfs_derive_perms(sb->s_root);

	goto out;

out_freeroot:
	dput(sb->s_root);
out_iput:
	iput(inode);
out_sput:
	/* drop refs we took earlier */
	atomic_dec(&lower_sb->s_active);
out_free:
	kfree(ESDFS_SB(sb));
	sb->s_fs_info = NULL;
out_pput:
	path_put(&lower_path);

out:
	return err;
}