|
@@ -836,33 +836,153 @@ out:
|
|
return error;
|
|
return error;
|
|
}
|
|
}
|
|
|
|
|
|
-static struct dentry *get_default_root(struct super_block *sb,
|
|
|
|
- u64 subvol_objectid)
|
|
|
|
|
|
+static char *get_subvol_name_from_objectid(struct btrfs_fs_info *fs_info,
|
|
|
|
+ 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_dir_item *di;
|
|
|
|
- struct btrfs_path *path;
|
|
|
|
- struct btrfs_key location;
|
|
|
|
- struct inode *inode;
|
|
|
|
- u64 dir_id;
|
|
|
|
- int new = 0;
|
|
|
|
|
|
+ struct btrfs_root *fs_root;
|
|
|
|
+ struct btrfs_root_ref *root_ref;
|
|
|
|
+ struct btrfs_inode_ref *inode_ref;
|
|
|
|
+ struct btrfs_key key;
|
|
|
|
+ struct btrfs_path *path = NULL;
|
|
|
|
+ char *name = NULL, *ptr;
|
|
|
|
+ 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
|
|
|
|
- * go look up the root.
|
|
|
|
|
|
+ * Walk up the subvolume trees in the tree of tree roots by root
|
|
|
|
+ * backrefs until we hit the top-level subvolume.
|
|
*/
|
|
*/
|
|
- if (subvol_objectid) {
|
|
|
|
- location.objectid = subvol_objectid;
|
|
|
|
- location.type = BTRFS_ROOT_ITEM_KEY;
|
|
|
|
- location.offset = (u64)-1;
|
|
|
|
- goto find_root;
|
|
|
|
|
|
+ while (subvol_objectid != BTRFS_FS_TREE_OBJECTID) {
|
|
|
|
+ key.objectid = subvol_objectid;
|
|
|
|
+ key.type = BTRFS_ROOT_BACKREF_KEY;
|
|
|
|
+ key.offset = (u64)-1;
|
|
|
|
+
|
|
|
|
+ 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;
|
|
|
|
- new_root = fs_info->fs_root;
|
|
|
|
- goto setup_root;
|
|
|
|
|
|
+ *objectid = BTRFS_FS_TREE_OBJECTID;
|
|
|
|
+ return 0;
|
|
}
|
|
}
|
|
|
|
|
|
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);
|
|
-
|
|
|
|
-find_root:
|
|
|
|
- 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);
|
|
|
|
|
|
+ *objectid = location.objectid;
|
|
|
|
+ return 0;
|
|
}
|
|
}
|
|
|
|
|
|
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);
|
|
}
|
|
}
|
|
-
|
|
|
|
- root = !error ? get_default_root(s, subvol_objectid) : ERR_PTR(error);
|
|
|
|
- if (IS_ERR(root)) {
|
|
|
|
|
|
+ if (error) {
|
|
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);
|