Commit 33c66f43 authored by Yan Zheng's avatar Yan Zheng Committed by Chris Mason

Btrfs: fix locking issue in btrfs_find_next_key

When walking up the tree, btrfs_find_next_key assumes the upper level tree
block is properly locked. This isn't always true even path->keep_locks is 1.
This is because btrfs_find_next_key may advance path->slots[] several times
instead of only once.

When 'path->slots[level] >= btrfs_header_nritems(path->nodes[level])' is found,
we can't guarantee the original value of 'path->slots[level]' is
'btrfs_header_nritems(path->nodes[level]) - 1'. If it's not, the tree block at
'level + 1' isn't locked.

This patch fixes the issue by explicitly checking the locking state,
re-searching the tree if it's not locked.
Signed-off-by: default avatarYan Zheng <zheng.yan@oracle.com>
Signed-off-by: default avatarChris Mason <chris.mason@oracle.com>
parent e457afec
...@@ -1701,6 +1701,7 @@ int btrfs_search_slot(struct btrfs_trans_handle *trans, struct btrfs_root ...@@ -1701,6 +1701,7 @@ int btrfs_search_slot(struct btrfs_trans_handle *trans, struct btrfs_root
struct extent_buffer *b; struct extent_buffer *b;
int slot; int slot;
int ret; int ret;
int err;
int level; int level;
int lowest_unlock = 1; int lowest_unlock = 1;
u8 lowest_level = 0; u8 lowest_level = 0;
...@@ -1737,8 +1738,6 @@ int btrfs_search_slot(struct btrfs_trans_handle *trans, struct btrfs_root ...@@ -1737,8 +1738,6 @@ int btrfs_search_slot(struct btrfs_trans_handle *trans, struct btrfs_root
p->locks[level] = 1; p->locks[level] = 1;
if (cow) { if (cow) {
int wret;
/* /*
* if we don't really need to cow this block * if we don't really need to cow this block
* then we don't want to set the path blocking, * then we don't want to set the path blocking,
...@@ -1749,12 +1748,12 @@ int btrfs_search_slot(struct btrfs_trans_handle *trans, struct btrfs_root ...@@ -1749,12 +1748,12 @@ int btrfs_search_slot(struct btrfs_trans_handle *trans, struct btrfs_root
btrfs_set_path_blocking(p); btrfs_set_path_blocking(p);
wret = btrfs_cow_block(trans, root, b, err = btrfs_cow_block(trans, root, b,
p->nodes[level + 1], p->nodes[level + 1],
p->slots[level + 1], &b); p->slots[level + 1], &b);
if (wret) { if (err) {
free_extent_buffer(b); free_extent_buffer(b);
ret = wret; ret = err;
goto done; goto done;
} }
} }
...@@ -1793,41 +1792,45 @@ int btrfs_search_slot(struct btrfs_trans_handle *trans, struct btrfs_root ...@@ -1793,41 +1792,45 @@ int btrfs_search_slot(struct btrfs_trans_handle *trans, struct btrfs_root
ret = bin_search(b, key, level, &slot); ret = bin_search(b, key, level, &slot);
if (level != 0) { if (level != 0) {
if (ret && slot > 0) int dec = 0;
if (ret && slot > 0) {
dec = 1;
slot -= 1; slot -= 1;
}
p->slots[level] = slot; p->slots[level] = slot;
ret = setup_nodes_for_search(trans, root, p, b, level, err = setup_nodes_for_search(trans, root, p, b, level,
ins_len); ins_len);
if (ret == -EAGAIN) if (err == -EAGAIN)
goto again; goto again;
else if (ret) if (err) {
ret = err;
goto done; goto done;
}
b = p->nodes[level]; b = p->nodes[level];
slot = p->slots[level]; slot = p->slots[level];
unlock_up(p, level, lowest_unlock); unlock_up(p, level, lowest_unlock);
/* this is only true while dropping a snapshot */
if (level == lowest_level) { if (level == lowest_level) {
ret = 0; if (dec)
p->slots[level]++;
goto done; goto done;
} }
ret = read_block_for_search(trans, root, p, err = read_block_for_search(trans, root, p,
&b, level, slot, key); &b, level, slot, key);
if (ret == -EAGAIN) if (err == -EAGAIN)
goto again; goto again;
if (err) {
if (ret == -EIO) ret = err;
goto done; goto done;
}
if (!p->skip_locking) { if (!p->skip_locking) {
int lret;
btrfs_clear_path_blocking(p, NULL); btrfs_clear_path_blocking(p, NULL);
lret = btrfs_try_spin_lock(b); err = btrfs_try_spin_lock(b);
if (!lret) { if (!err) {
btrfs_set_path_blocking(p); btrfs_set_path_blocking(p);
btrfs_tree_lock(b); btrfs_tree_lock(b);
btrfs_clear_path_blocking(p, b); btrfs_clear_path_blocking(p, b);
...@@ -1837,16 +1840,14 @@ int btrfs_search_slot(struct btrfs_trans_handle *trans, struct btrfs_root ...@@ -1837,16 +1840,14 @@ int btrfs_search_slot(struct btrfs_trans_handle *trans, struct btrfs_root
p->slots[level] = slot; p->slots[level] = slot;
if (ins_len > 0 && if (ins_len > 0 &&
btrfs_leaf_free_space(root, b) < ins_len) { btrfs_leaf_free_space(root, b) < ins_len) {
int sret;
btrfs_set_path_blocking(p); btrfs_set_path_blocking(p);
sret = split_leaf(trans, root, key, err = split_leaf(trans, root, key,
p, ins_len, ret == 0); p, ins_len, ret == 0);
btrfs_clear_path_blocking(p, NULL); btrfs_clear_path_blocking(p, NULL);
BUG_ON(sret > 0); BUG_ON(err > 0);
if (sret) { if (err) {
ret = sret; ret = err;
goto done; goto done;
} }
} }
...@@ -4042,10 +4043,9 @@ int btrfs_search_forward(struct btrfs_root *root, struct btrfs_key *min_key, ...@@ -4042,10 +4043,9 @@ int btrfs_search_forward(struct btrfs_root *root, struct btrfs_key *min_key,
* calling this function. * calling this function.
*/ */
int btrfs_find_next_key(struct btrfs_root *root, struct btrfs_path *path, int btrfs_find_next_key(struct btrfs_root *root, struct btrfs_path *path,
struct btrfs_key *key, int lowest_level, struct btrfs_key *key, int level,
int cache_only, u64 min_trans) int cache_only, u64 min_trans)
{ {
int level = lowest_level;
int slot; int slot;
struct extent_buffer *c; struct extent_buffer *c;
...@@ -4058,11 +4058,40 @@ int btrfs_find_next_key(struct btrfs_root *root, struct btrfs_path *path, ...@@ -4058,11 +4058,40 @@ int btrfs_find_next_key(struct btrfs_root *root, struct btrfs_path *path,
c = path->nodes[level]; c = path->nodes[level];
next: next:
if (slot >= btrfs_header_nritems(c)) { if (slot >= btrfs_header_nritems(c)) {
level++; int ret;
if (level == BTRFS_MAX_LEVEL) int orig_lowest;
struct btrfs_key cur_key;
if (level + 1 >= BTRFS_MAX_LEVEL ||
!path->nodes[level + 1])
return 1; return 1;
continue;
if (path->locks[level + 1]) {
level++;
continue;
}
slot = btrfs_header_nritems(c) - 1;
if (level == 0)
btrfs_item_key_to_cpu(c, &cur_key, slot);
else
btrfs_node_key_to_cpu(c, &cur_key, slot);
orig_lowest = path->lowest_level;
btrfs_release_path(root, path);
path->lowest_level = level;
ret = btrfs_search_slot(NULL, root, &cur_key, path,
0, 0);
path->lowest_level = orig_lowest;
if (ret < 0)
return ret;
c = path->nodes[level];
slot = path->slots[level];
if (ret == 0)
slot++;
goto next;
} }
if (level == 0) if (level == 0)
btrfs_item_key_to_cpu(c, key, slot); btrfs_item_key_to_cpu(c, key, slot);
else { else {
......
...@@ -670,6 +670,8 @@ static struct backref_node *build_backref_tree(struct reloc_control *rc, ...@@ -670,6 +670,8 @@ static struct backref_node *build_backref_tree(struct reloc_control *rc,
err = ret; err = ret;
goto out; goto out;
} }
if (ret > 0 && path2->slots[level] > 0)
path2->slots[level]--;
eb = path2->nodes[level]; eb = path2->nodes[level];
WARN_ON(btrfs_node_blockptr(eb, path2->slots[level]) != WARN_ON(btrfs_node_blockptr(eb, path2->slots[level]) !=
...@@ -1609,6 +1611,7 @@ static noinline_for_stack int merge_reloc_root(struct reloc_control *rc, ...@@ -1609,6 +1611,7 @@ static noinline_for_stack int merge_reloc_root(struct reloc_control *rc,
BUG_ON(level == 0); BUG_ON(level == 0);
path->lowest_level = level; path->lowest_level = level;
ret = btrfs_search_slot(NULL, reloc_root, &key, path, 0, 0); ret = btrfs_search_slot(NULL, reloc_root, &key, path, 0, 0);
path->lowest_level = 0;
if (ret < 0) { if (ret < 0) {
btrfs_free_path(path); btrfs_free_path(path);
return ret; return ret;
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment