Btrfs: hold the commit_root_sem when getting the commit root during send

We currently rely too heavily on roots being read-only to save us from just
accessing root->commit_root.  We can easily balance blocks out from underneath a
read only root, so to save us from getting screwed make sure we only access
root->commit_root under the commit root sem.  Thanks,

Signed-off-by: Josef Bacik <jbacik@fb.com>
Signed-off-by: Chris Mason <clm@fb.com>
This commit is contained in:
Josef Bacik 2014-03-28 17:16:01 -04:00 committed by Chris Mason
parent 9e351cc862
commit 3f8a18cc53
3 changed files with 39 additions and 16 deletions

View File

@ -2769,9 +2769,13 @@ again:
* the commit roots are read only * the commit roots are read only
* so we always do read locks * so we always do read locks
*/ */
if (p->need_commit_sem)
down_read(&root->fs_info->commit_root_sem);
b = root->commit_root; b = root->commit_root;
extent_buffer_get(b); extent_buffer_get(b);
level = btrfs_header_level(b); level = btrfs_header_level(b);
if (p->need_commit_sem)
up_read(&root->fs_info->commit_root_sem);
if (!p->skip_locking) if (!p->skip_locking)
btrfs_tree_read_lock(b); btrfs_tree_read_lock(b);
} else { } else {
@ -5436,6 +5440,7 @@ int btrfs_compare_trees(struct btrfs_root *left_root,
* the right if possible or go up and right. * the right if possible or go up and right.
*/ */
down_read(&left_root->fs_info->commit_root_sem);
left_level = btrfs_header_level(left_root->commit_root); left_level = btrfs_header_level(left_root->commit_root);
left_root_level = left_level; left_root_level = left_level;
left_path->nodes[left_level] = left_root->commit_root; left_path->nodes[left_level] = left_root->commit_root;
@ -5445,6 +5450,7 @@ int btrfs_compare_trees(struct btrfs_root *left_root,
right_root_level = right_level; right_root_level = right_level;
right_path->nodes[right_level] = right_root->commit_root; right_path->nodes[right_level] = right_root->commit_root;
extent_buffer_get(right_path->nodes[right_level]); extent_buffer_get(right_path->nodes[right_level]);
up_read(&left_root->fs_info->commit_root_sem);
if (left_level == 0) if (left_level == 0)
btrfs_item_key_to_cpu(left_path->nodes[left_level], btrfs_item_key_to_cpu(left_path->nodes[left_level],

View File

@ -609,6 +609,7 @@ struct btrfs_path {
unsigned int skip_locking:1; unsigned int skip_locking:1;
unsigned int leave_spinning:1; unsigned int leave_spinning:1;
unsigned int search_commit_root:1; unsigned int search_commit_root:1;
unsigned int need_commit_sem:1;
}; };
/* /*

View File

@ -493,6 +493,7 @@ static struct btrfs_path *alloc_path_for_send(void)
return NULL; return NULL;
path->search_commit_root = 1; path->search_commit_root = 1;
path->skip_locking = 1; path->skip_locking = 1;
path->need_commit_sem = 1;
return path; return path;
} }
@ -771,29 +772,22 @@ out:
/* /*
* Helper function to retrieve some fields from an inode item. * Helper function to retrieve some fields from an inode item.
*/ */
static int get_inode_info(struct btrfs_root *root, static int __get_inode_info(struct btrfs_root *root, struct btrfs_path *path,
u64 ino, u64 *size, u64 *gen, u64 ino, u64 *size, u64 *gen, u64 *mode, u64 *uid,
u64 *mode, u64 *uid, u64 *gid, u64 *gid, u64 *rdev)
u64 *rdev)
{ {
int ret; int ret;
struct btrfs_inode_item *ii; struct btrfs_inode_item *ii;
struct btrfs_key key; struct btrfs_key key;
struct btrfs_path *path;
path = alloc_path_for_send();
if (!path)
return -ENOMEM;
key.objectid = ino; key.objectid = ino;
key.type = BTRFS_INODE_ITEM_KEY; key.type = BTRFS_INODE_ITEM_KEY;
key.offset = 0; key.offset = 0;
ret = btrfs_search_slot(NULL, root, &key, path, 0, 0); ret = btrfs_search_slot(NULL, root, &key, path, 0, 0);
if (ret < 0)
goto out;
if (ret) { if (ret) {
ret = -ENOENT; if (ret > 0)
goto out; ret = -ENOENT;
return ret;
} }
ii = btrfs_item_ptr(path->nodes[0], path->slots[0], ii = btrfs_item_ptr(path->nodes[0], path->slots[0],
@ -811,7 +805,22 @@ static int get_inode_info(struct btrfs_root *root,
if (rdev) if (rdev)
*rdev = btrfs_inode_rdev(path->nodes[0], ii); *rdev = btrfs_inode_rdev(path->nodes[0], ii);
out: return ret;
}
static int get_inode_info(struct btrfs_root *root,
u64 ino, u64 *size, u64 *gen,
u64 *mode, u64 *uid, u64 *gid,
u64 *rdev)
{
struct btrfs_path *path;
int ret;
path = alloc_path_for_send();
if (!path)
return -ENOMEM;
ret = __get_inode_info(root, path, ino, size, gen, mode, uid, gid,
rdev);
btrfs_free_path(path); btrfs_free_path(path);
return ret; return ret;
} }
@ -1085,6 +1094,7 @@ out:
struct backref_ctx { struct backref_ctx {
struct send_ctx *sctx; struct send_ctx *sctx;
struct btrfs_path *path;
/* number of total found references */ /* number of total found references */
u64 found; u64 found;
@ -1155,8 +1165,9 @@ static int __iterate_backrefs(u64 ino, u64 offset, u64 root, void *ctx_)
* There are inodes that have extents that lie behind its i_size. Don't * There are inodes that have extents that lie behind its i_size. Don't
* accept clones from these extents. * accept clones from these extents.
*/ */
ret = get_inode_info(found->root, ino, &i_size, NULL, NULL, NULL, NULL, ret = __get_inode_info(found->root, bctx->path, ino, &i_size, NULL, NULL,
NULL); NULL, NULL, NULL);
btrfs_release_path(bctx->path);
if (ret < 0) if (ret < 0)
return ret; return ret;
@ -1235,12 +1246,17 @@ static int find_extent_clone(struct send_ctx *sctx,
if (!tmp_path) if (!tmp_path)
return -ENOMEM; return -ENOMEM;
/* We only use this path under the commit sem */
tmp_path->need_commit_sem = 0;
backref_ctx = kmalloc(sizeof(*backref_ctx), GFP_NOFS); backref_ctx = kmalloc(sizeof(*backref_ctx), GFP_NOFS);
if (!backref_ctx) { if (!backref_ctx) {
ret = -ENOMEM; ret = -ENOMEM;
goto out; goto out;
} }
backref_ctx->path = tmp_path;
if (data_offset >= ino_size) { if (data_offset >= ino_size) {
/* /*
* There may be extents that lie behind the file's size. * There may be extents that lie behind the file's size.