Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 05dbe683 authored by Omar Sandoval's avatar Omar Sandoval Committed by Chris Mason
Browse files

Btrfs: unify subvol= and subvolid= mounting



Currently, mounting a subvolume with subvolid= takes a different code
path than mounting with subvol=. This isn't really a big deal except for
the fact that mounts done with subvolid= or the default subvolume don't
have a dentry that's connected to the dentry tree like in the subvol=
case. To unify the code paths, when given subvolid= or using the default
subvolume ID, translate it into a subvolume name by walking
ROOT_BACKREFs in the root tree and INODE_REFs in the filesystem trees.

Reviewed-by: default avatarDavid Sterba <dsterba@suse.cz>
Signed-off-by: default avatarOmar Sandoval <osandov@osandov.com>
Signed-off-by: default avatarChris Mason <clm@fb.com>
parent bb289b7b
Loading
Loading
Loading
Loading
+171 −67
Original line number Original line Diff line number Diff line
@@ -836,33 +836,153 @@ static int btrfs_parse_early_options(const char *options, fmode_t flags,
	return error;
	return error;
}
}


static struct dentry *get_default_root(struct super_block *sb,
static char *get_subvol_name_from_objectid(struct btrfs_fs_info *fs_info,
					   u64 subvol_objectid)
					   u64 subvol_objectid)
{
{
	struct btrfs_fs_info *fs_info = btrfs_sb(sb);
	struct btrfs_root *root = fs_info->tree_root;
	struct btrfs_root *root = fs_info->tree_root;
	struct btrfs_root *new_root;
	struct btrfs_root *fs_root;
	struct btrfs_dir_item *di;
	struct btrfs_root_ref *root_ref;
	struct btrfs_path *path;
	struct btrfs_inode_ref *inode_ref;
	struct btrfs_key location;
	struct btrfs_key key;
	struct inode *inode;
	struct btrfs_path *path = NULL;
	u64 dir_id;
	char *name = NULL, *ptr;
	int new = 0;
	u64 dirid;
	int len;
	int ret;

	path = btrfs_alloc_path();
	if (!path) {
		ret = -ENOMEM;
		goto err;
	}
	path->leave_spinning = 1;

	name = kmalloc(PATH_MAX, GFP_NOFS);
	if (!name) {
		ret = -ENOMEM;
		goto err;
	}
	ptr = name + PATH_MAX - 1;
	ptr[0] = '\0';


	/*
	/*
	 * We have a specific subvol we want to mount, just setup location and
	 * Walk up the subvolume trees in the tree of tree roots by root
	 * go look up the root.
	 * backrefs until we hit the top-level subvolume.
	 */
	 */
	if (subvol_objectid) {
	while (subvol_objectid != BTRFS_FS_TREE_OBJECTID) {
		location.objectid = subvol_objectid;
		key.objectid = subvol_objectid;
		location.type = BTRFS_ROOT_ITEM_KEY;
		key.type = BTRFS_ROOT_BACKREF_KEY;
		location.offset = (u64)-1;
		key.offset = (u64)-1;
		goto find_root;

		ret = btrfs_search_slot(NULL, root, &key, path, 0, 0);
		if (ret < 0) {
			goto err;
		} else if (ret > 0) {
			ret = btrfs_previous_item(root, path, subvol_objectid,
						  BTRFS_ROOT_BACKREF_KEY);
			if (ret < 0) {
				goto err;
			} else if (ret > 0) {
				ret = -ENOENT;
				goto err;
			}
		}

		btrfs_item_key_to_cpu(path->nodes[0], &key, path->slots[0]);
		subvol_objectid = key.offset;

		root_ref = btrfs_item_ptr(path->nodes[0], path->slots[0],
					  struct btrfs_root_ref);
		len = btrfs_root_ref_name_len(path->nodes[0], root_ref);
		ptr -= len + 1;
		if (ptr < name) {
			ret = -ENAMETOOLONG;
			goto err;
		}
		read_extent_buffer(path->nodes[0], ptr + 1,
				   (unsigned long)(root_ref + 1), len);
		ptr[0] = '/';
		dirid = btrfs_root_ref_dirid(path->nodes[0], root_ref);
		btrfs_release_path(path);

		key.objectid = subvol_objectid;
		key.type = BTRFS_ROOT_ITEM_KEY;
		key.offset = (u64)-1;
		fs_root = btrfs_read_fs_root_no_name(fs_info, &key);
		if (IS_ERR(fs_root)) {
			ret = PTR_ERR(fs_root);
			goto err;
		}

		/*
		 * Walk up the filesystem tree by inode refs until we hit the
		 * root directory.
		 */
		while (dirid != BTRFS_FIRST_FREE_OBJECTID) {
			key.objectid = dirid;
			key.type = BTRFS_INODE_REF_KEY;
			key.offset = (u64)-1;

			ret = btrfs_search_slot(NULL, fs_root, &key, path, 0, 0);
			if (ret < 0) {
				goto err;
			} else if (ret > 0) {
				ret = btrfs_previous_item(fs_root, path, dirid,
							  BTRFS_INODE_REF_KEY);
				if (ret < 0) {
					goto err;
				} else if (ret > 0) {
					ret = -ENOENT;
					goto err;
				}
			}

			btrfs_item_key_to_cpu(path->nodes[0], &key, path->slots[0]);
			dirid = key.offset;

			inode_ref = btrfs_item_ptr(path->nodes[0],
						   path->slots[0],
						   struct btrfs_inode_ref);
			len = btrfs_inode_ref_name_len(path->nodes[0],
						       inode_ref);
			ptr -= len + 1;
			if (ptr < name) {
				ret = -ENAMETOOLONG;
				goto err;
			}
			read_extent_buffer(path->nodes[0], ptr + 1,
					   (unsigned long)(inode_ref + 1), len);
			ptr[0] = '/';
			btrfs_release_path(path);
		}
	}

	btrfs_free_path(path);
	if (ptr == name + PATH_MAX - 1) {
		name[0] = '/';
		name[1] = '\0';
	} else {
		memmove(name, ptr, name + PATH_MAX - ptr);
	}
	return name;

err:
	btrfs_free_path(path);
	kfree(name);
	return ERR_PTR(ret);
}
}


static int get_default_subvol_objectid(struct btrfs_fs_info *fs_info, u64 *objectid)
{
	struct btrfs_root *root = fs_info->tree_root;
	struct btrfs_dir_item *di;
	struct btrfs_path *path;
	struct btrfs_key location;
	u64 dir_id;

	path = btrfs_alloc_path();
	path = btrfs_alloc_path();
	if (!path)
	if (!path)
		return ERR_PTR(-ENOMEM);
		return -ENOMEM;
	path->leave_spinning = 1;
	path->leave_spinning = 1;


	/*
	/*
@@ -874,58 +994,23 @@ static struct dentry *get_default_root(struct super_block *sb,
	di = btrfs_lookup_dir_item(NULL, root, path, dir_id, "default", 7, 0);
	di = btrfs_lookup_dir_item(NULL, root, path, dir_id, "default", 7, 0);
	if (IS_ERR(di)) {
	if (IS_ERR(di)) {
		btrfs_free_path(path);
		btrfs_free_path(path);
		return ERR_CAST(di);
		return PTR_ERR(di);
	}
	}
	if (!di) {
	if (!di) {
		/*
		/*
		 * Ok the default dir item isn't there.  This is weird since
		 * Ok the default dir item isn't there.  This is weird since
		 * it's always been there, but don't freak out, just try and
		 * it's always been there, but don't freak out, just try and
		 * mount to root most subvolume.
		 * mount the top-level subvolume.
		 */
		 */
		btrfs_free_path(path);
		btrfs_free_path(path);
		dir_id = BTRFS_FIRST_FREE_OBJECTID;
		*objectid = BTRFS_FS_TREE_OBJECTID;
		new_root = fs_info->fs_root;
		return 0;
		goto setup_root;
	}
	}


	btrfs_dir_item_key_to_cpu(path->nodes[0], di, &location);
	btrfs_dir_item_key_to_cpu(path->nodes[0], di, &location);
	btrfs_free_path(path);
	btrfs_free_path(path);

	*objectid = location.objectid;
find_root:
	return 0;
	new_root = btrfs_read_fs_root_no_name(fs_info, &location);
	if (IS_ERR(new_root))
		return ERR_CAST(new_root);

	if (!(sb->s_flags & MS_RDONLY)) {
		int ret;
		down_read(&fs_info->cleanup_work_sem);
		ret = btrfs_orphan_cleanup(new_root);
		up_read(&fs_info->cleanup_work_sem);
		if (ret)
			return ERR_PTR(ret);
	}

	dir_id = btrfs_root_dirid(&new_root->root_item);
setup_root:
	location.objectid = dir_id;
	location.type = BTRFS_INODE_ITEM_KEY;
	location.offset = 0;

	inode = btrfs_iget(sb, &location, new_root, &new);
	if (IS_ERR(inode))
		return ERR_CAST(inode);

	/*
	 * If we're just mounting the root most subvol put the inode and return
	 * a reference to the dentry.  We will have already gotten a reference
	 * to the inode in btrfs_fill_super so we're good to go.
	 */
	if (!new && d_inode(sb->s_root) == inode) {
		iput(inode);
		return dget(sb->s_root);
	}

	return d_obtain_root(inode);
}
}


static int btrfs_fill_super(struct super_block *sb,
static int btrfs_fill_super(struct super_block *sb,
@@ -1211,6 +1296,25 @@ static struct dentry *mount_subvol(const char *subvol_name, u64 subvol_objectid,
		goto out;
		goto out;
	}
	}


	if (!subvol_name) {
		if (!subvol_objectid) {
			ret = get_default_subvol_objectid(btrfs_sb(mnt->mnt_sb),
							  &subvol_objectid);
			if (ret) {
				root = ERR_PTR(ret);
				goto out;
			}
		}
		subvol_name = get_subvol_name_from_objectid(btrfs_sb(mnt->mnt_sb),
							    subvol_objectid);
		if (IS_ERR(subvol_name)) {
			root = ERR_CAST(subvol_name);
			subvol_name = NULL;
			goto out;
		}

	}

	root = mount_subtree(mnt, subvol_name);
	root = mount_subtree(mnt, subvol_name);
	/* mount_subtree() drops our reference on the vfsmount. */
	/* mount_subtree() drops our reference on the vfsmount. */
	mnt = NULL;
	mnt = NULL;
@@ -1227,6 +1331,11 @@ static struct dentry *mount_subvol(const char *subvol_name, u64 subvol_objectid,
			ret = -EINVAL;
			ret = -EINVAL;
		}
		}
		if (subvol_objectid && root_objectid != subvol_objectid) {
		if (subvol_objectid && root_objectid != subvol_objectid) {
			/*
			 * This will also catch a race condition where a
			 * subvolume which was passed by ID is renamed and
			 * another subvolume is renamed over the old location.
			 */
			pr_err("BTRFS: subvol '%s' does not match subvolid %llu\n",
			pr_err("BTRFS: subvol '%s' does not match subvolid %llu\n",
			       subvol_name, subvol_objectid);
			       subvol_name, subvol_objectid);
			ret = -EINVAL;
			ret = -EINVAL;
@@ -1306,7 +1415,6 @@ static struct dentry *btrfs_mount(struct file_system_type *fs_type, int flags,
{
{
	struct block_device *bdev = NULL;
	struct block_device *bdev = NULL;
	struct super_block *s;
	struct super_block *s;
	struct dentry *root;
	struct btrfs_fs_devices *fs_devices = NULL;
	struct btrfs_fs_devices *fs_devices = NULL;
	struct btrfs_fs_info *fs_info = NULL;
	struct btrfs_fs_info *fs_info = NULL;
	struct security_mnt_opts new_sec_opts;
	struct security_mnt_opts new_sec_opts;
@@ -1326,7 +1434,7 @@ static struct dentry *btrfs_mount(struct file_system_type *fs_type, int flags,
		return ERR_PTR(error);
		return ERR_PTR(error);
	}
	}


	if (subvol_name) {
	if (subvol_name || subvol_objectid != BTRFS_FS_TREE_OBJECTID) {
		/* mount_subvol() will free subvol_name. */
		/* mount_subvol() will free subvol_name. */
		return mount_subvol(subvol_name, subvol_objectid, flags,
		return mount_subvol(subvol_name, subvol_objectid, flags,
				    device_name, data);
				    device_name, data);
@@ -1395,23 +1503,19 @@ static struct dentry *btrfs_mount(struct file_system_type *fs_type, int flags,
		error = btrfs_fill_super(s, fs_devices, data,
		error = btrfs_fill_super(s, fs_devices, data,
					 flags & MS_SILENT ? 1 : 0);
					 flags & MS_SILENT ? 1 : 0);
	}
	}

	if (error) {
	root = !error ? get_default_root(s, subvol_objectid) : ERR_PTR(error);
	if (IS_ERR(root)) {
		deactivate_locked_super(s);
		deactivate_locked_super(s);
		error = PTR_ERR(root);
		goto error_sec_opts;
		goto error_sec_opts;
	}
	}


	fs_info = btrfs_sb(s);
	fs_info = btrfs_sb(s);
	error = setup_security_options(fs_info, s, &new_sec_opts);
	error = setup_security_options(fs_info, s, &new_sec_opts);
	if (error) {
	if (error) {
		dput(root);
		deactivate_locked_super(s);
		deactivate_locked_super(s);
		goto error_sec_opts;
		goto error_sec_opts;
	}
	}


	return root;
	return dget(s->s_root);


error_close_devices:
error_close_devices:
	btrfs_close_devices(fs_devices);
	btrfs_close_devices(fs_devices);