Btrfs: deal with free space cache errors while replaying log

So everybody who got hit by my fsync bug will still continue to hit this
BUG_ON() in the free space cache, which is pretty heavy handed.  So I took a
file system that had this bug and fixed up all the BUG_ON()'s and leaks that
popped up when I tried to mount a broken file system like this.  With this patch
we just fail to mount instead of panicing.  Thanks,

Signed-off-by: Josef Bacik <jbacik@fusionio.com>
This commit is contained in:
Josef Bacik 2013-04-25 15:55:30 -04:00
parent 3d7b5a2882
commit b50c6e250e
3 changed files with 60 additions and 33 deletions

View file

@ -5210,9 +5210,11 @@ int btrfs_pin_extent_for_log_replay(struct btrfs_root *root,
u64 bytenr, u64 num_bytes) u64 bytenr, u64 num_bytes)
{ {
struct btrfs_block_group_cache *cache; struct btrfs_block_group_cache *cache;
int ret;
cache = btrfs_lookup_block_group(root->fs_info, bytenr); cache = btrfs_lookup_block_group(root->fs_info, bytenr);
BUG_ON(!cache); /* Logic error */ if (!cache)
return -EINVAL;
/* /*
* pull in the free space cache (if any) so that our pin * pull in the free space cache (if any) so that our pin
@ -5225,9 +5227,9 @@ int btrfs_pin_extent_for_log_replay(struct btrfs_root *root,
pin_down_extent(root, cache, bytenr, num_bytes, 0); pin_down_extent(root, cache, bytenr, num_bytes, 0);
/* remove us from the free space cache (if we're there at all) */ /* remove us from the free space cache (if we're there at all) */
btrfs_remove_free_space(cache, bytenr, num_bytes); ret = btrfs_remove_free_space(cache, bytenr, num_bytes);
btrfs_put_block_group(cache); btrfs_put_block_group(cache);
return 0; return ret;
} }
/** /**
@ -6611,40 +6613,42 @@ int btrfs_alloc_logged_file_extent(struct btrfs_trans_handle *trans,
if (!caching_ctl) { if (!caching_ctl) {
BUG_ON(!block_group_cache_done(block_group)); BUG_ON(!block_group_cache_done(block_group));
ret = btrfs_remove_free_space(block_group, start, num_bytes); ret = btrfs_remove_free_space(block_group, start, num_bytes);
BUG_ON(ret); /* -ENOMEM */ if (ret)
goto out;
} else { } else {
mutex_lock(&caching_ctl->mutex); mutex_lock(&caching_ctl->mutex);
if (start >= caching_ctl->progress) { if (start >= caching_ctl->progress) {
ret = add_excluded_extent(root, start, num_bytes); ret = add_excluded_extent(root, start, num_bytes);
BUG_ON(ret); /* -ENOMEM */
} else if (start + num_bytes <= caching_ctl->progress) { } else if (start + num_bytes <= caching_ctl->progress) {
ret = btrfs_remove_free_space(block_group, ret = btrfs_remove_free_space(block_group,
start, num_bytes); start, num_bytes);
BUG_ON(ret); /* -ENOMEM */
} else { } else {
num_bytes = caching_ctl->progress - start; num_bytes = caching_ctl->progress - start;
ret = btrfs_remove_free_space(block_group, ret = btrfs_remove_free_space(block_group,
start, num_bytes); start, num_bytes);
BUG_ON(ret); /* -ENOMEM */ if (ret)
goto out_lock;
start = caching_ctl->progress; start = caching_ctl->progress;
num_bytes = ins->objectid + ins->offset - num_bytes = ins->objectid + ins->offset -
caching_ctl->progress; caching_ctl->progress;
ret = add_excluded_extent(root, start, num_bytes); ret = add_excluded_extent(root, start, num_bytes);
BUG_ON(ret); /* -ENOMEM */
} }
out_lock:
mutex_unlock(&caching_ctl->mutex); mutex_unlock(&caching_ctl->mutex);
put_caching_control(caching_ctl); put_caching_control(caching_ctl);
if (ret)
goto out;
} }
ret = btrfs_update_reserved_bytes(block_group, ins->offset, ret = btrfs_update_reserved_bytes(block_group, ins->offset,
RESERVE_ALLOC_NO_ACCOUNT); RESERVE_ALLOC_NO_ACCOUNT);
BUG_ON(ret); /* logic error */ BUG_ON(ret); /* logic error */
btrfs_put_block_group(block_group);
ret = alloc_reserved_file_extent(trans, root, 0, root_objectid, ret = alloc_reserved_file_extent(trans, root, 0, root_objectid,
0, owner, offset, ins, 1); 0, owner, offset, ins, 1);
out:
btrfs_put_block_group(block_group);
return ret; return ret;
} }

View file

@ -1567,7 +1567,8 @@ again:
search_bytes = ctl->unit; search_bytes = ctl->unit;
search_bytes = min(search_bytes, end - search_start + 1); search_bytes = min(search_bytes, end - search_start + 1);
ret = search_bitmap(ctl, bitmap_info, &search_start, &search_bytes); ret = search_bitmap(ctl, bitmap_info, &search_start, &search_bytes);
BUG_ON(ret < 0 || search_start != *offset); if (ret < 0 || search_start != *offset)
return -EINVAL;
/* We may have found more bits than what we need */ /* We may have found more bits than what we need */
search_bytes = min(search_bytes, *bytes); search_bytes = min(search_bytes, *bytes);
@ -1973,7 +1974,6 @@ again:
re_search = true; re_search = true;
goto again; goto again;
} }
BUG_ON(ret); /* logic error */
out_lock: out_lock:
spin_unlock(&ctl->tree_lock); spin_unlock(&ctl->tree_lock);
out: out:

View file

@ -277,17 +277,19 @@ static int process_one_buffer(struct btrfs_root *log,
struct extent_buffer *eb, struct extent_buffer *eb,
struct walk_control *wc, u64 gen) struct walk_control *wc, u64 gen)
{ {
if (wc->pin) int ret = 0;
btrfs_pin_extent_for_log_replay(log->fs_info->extent_root,
eb->start, eb->len);
if (btrfs_buffer_uptodate(eb, gen, 0)) { if (wc->pin)
ret = btrfs_pin_extent_for_log_replay(log->fs_info->extent_root,
eb->start, eb->len);
if (!ret && btrfs_buffer_uptodate(eb, gen, 0)) {
if (wc->write) if (wc->write)
btrfs_write_tree_block(eb); btrfs_write_tree_block(eb);
if (wc->wait) if (wc->wait)
btrfs_wait_tree_block_writeback(eb); btrfs_wait_tree_block_writeback(eb);
} }
return 0; return ret;
} }
/* /*
@ -623,7 +625,8 @@ static noinline int replay_one_extent(struct btrfs_trans_handle *trans,
ins.objectid, ins.offset, ins.objectid, ins.offset,
0, root->root_key.objectid, 0, root->root_key.objectid,
key->objectid, offset, 0); key->objectid, offset, 0);
BUG_ON(ret); if (ret)
goto out;
} else { } else {
/* /*
* insert the extent pointer in the extent * insert the extent pointer in the extent
@ -632,7 +635,8 @@ static noinline int replay_one_extent(struct btrfs_trans_handle *trans,
ret = btrfs_alloc_logged_file_extent(trans, ret = btrfs_alloc_logged_file_extent(trans,
root, root->root_key.objectid, root, root->root_key.objectid,
key->objectid, offset, &ins); key->objectid, offset, &ins);
BUG_ON(ret); if (ret)
goto out;
} }
btrfs_release_path(path); btrfs_release_path(path);
@ -1952,11 +1956,13 @@ static int replay_one_buffer(struct btrfs_root *log, struct extent_buffer *eb,
if (S_ISDIR(mode)) { if (S_ISDIR(mode)) {
ret = replay_dir_deletes(wc->trans, ret = replay_dir_deletes(wc->trans,
root, log, path, key.objectid, 0); root, log, path, key.objectid, 0);
BUG_ON(ret); if (ret)
break;
} }
ret = overwrite_item(wc->trans, root, path, ret = overwrite_item(wc->trans, root, path,
eb, i, &key); eb, i, &key);
BUG_ON(ret); if (ret)
break;
/* for regular files, make sure corresponding /* for regular files, make sure corresponding
* orhpan item exist. extents past the new EOF * orhpan item exist. extents past the new EOF
@ -1965,12 +1971,14 @@ static int replay_one_buffer(struct btrfs_root *log, struct extent_buffer *eb,
if (S_ISREG(mode)) { if (S_ISREG(mode)) {
ret = insert_orphan_item(wc->trans, root, ret = insert_orphan_item(wc->trans, root,
key.objectid); key.objectid);
BUG_ON(ret); if (ret)
break;
} }
ret = link_to_fixup_dir(wc->trans, root, ret = link_to_fixup_dir(wc->trans, root,
path, key.objectid); path, key.objectid);
BUG_ON(ret); if (ret)
break;
} }
if (wc->stage < LOG_WALK_REPLAY_ALL) if (wc->stage < LOG_WALK_REPLAY_ALL)
continue; continue;
@ -1979,28 +1987,35 @@ static int replay_one_buffer(struct btrfs_root *log, struct extent_buffer *eb,
if (key.type == BTRFS_XATTR_ITEM_KEY) { if (key.type == BTRFS_XATTR_ITEM_KEY) {
ret = overwrite_item(wc->trans, root, path, ret = overwrite_item(wc->trans, root, path,
eb, i, &key); eb, i, &key);
BUG_ON(ret); if (ret)
break;
} else if (key.type == BTRFS_INODE_REF_KEY) { } else if (key.type == BTRFS_INODE_REF_KEY) {
ret = add_inode_ref(wc->trans, root, log, path, ret = add_inode_ref(wc->trans, root, log, path,
eb, i, &key); eb, i, &key);
BUG_ON(ret && ret != -ENOENT); if (ret && ret != -ENOENT)
break;
ret = 0;
} else if (key.type == BTRFS_INODE_EXTREF_KEY) { } else if (key.type == BTRFS_INODE_EXTREF_KEY) {
ret = add_inode_ref(wc->trans, root, log, path, ret = add_inode_ref(wc->trans, root, log, path,
eb, i, &key); eb, i, &key);
BUG_ON(ret && ret != -ENOENT); if (ret && ret != -ENOENT)
break;
ret = 0;
} else if (key.type == BTRFS_EXTENT_DATA_KEY) { } else if (key.type == BTRFS_EXTENT_DATA_KEY) {
ret = replay_one_extent(wc->trans, root, path, ret = replay_one_extent(wc->trans, root, path,
eb, i, &key); eb, i, &key);
BUG_ON(ret); if (ret)
break;
} else if (key.type == BTRFS_DIR_ITEM_KEY || } else if (key.type == BTRFS_DIR_ITEM_KEY ||
key.type == BTRFS_DIR_INDEX_KEY) { key.type == BTRFS_DIR_INDEX_KEY) {
ret = replay_one_dir_item(wc->trans, root, path, ret = replay_one_dir_item(wc->trans, root, path,
eb, i, &key); eb, i, &key);
BUG_ON(ret); if (ret)
break;
} }
} }
btrfs_free_path(path); btrfs_free_path(path);
return 0; return ret;
} }
static noinline int walk_down_log_tree(struct btrfs_trans_handle *trans, static noinline int walk_down_log_tree(struct btrfs_trans_handle *trans,
@ -2045,8 +2060,10 @@ static noinline int walk_down_log_tree(struct btrfs_trans_handle *trans,
if (*level == 1) { if (*level == 1) {
ret = wc->process_func(root, next, wc, ptr_gen); ret = wc->process_func(root, next, wc, ptr_gen);
if (ret) if (ret) {
free_extent_buffer(next);
return ret; return ret;
}
path->slots[*level]++; path->slots[*level]++;
if (wc->free) { if (wc->free) {
@ -3970,6 +3987,9 @@ again:
wc.replay_dest = btrfs_read_fs_root_no_name(fs_info, &tmp_key); wc.replay_dest = btrfs_read_fs_root_no_name(fs_info, &tmp_key);
if (IS_ERR(wc.replay_dest)) { if (IS_ERR(wc.replay_dest)) {
ret = PTR_ERR(wc.replay_dest); ret = PTR_ERR(wc.replay_dest);
free_extent_buffer(log->node);
free_extent_buffer(log->commit_root);
kfree(log);
btrfs_error(fs_info, ret, "Couldn't read target root " btrfs_error(fs_info, ret, "Couldn't read target root "
"for tree log recovery."); "for tree log recovery.");
goto error; goto error;
@ -3978,12 +3998,10 @@ again:
wc.replay_dest->log_root = log; wc.replay_dest->log_root = log;
btrfs_record_root_in_trans(trans, wc.replay_dest); btrfs_record_root_in_trans(trans, wc.replay_dest);
ret = walk_log_tree(trans, log, &wc); ret = walk_log_tree(trans, log, &wc);
BUG_ON(ret);
if (wc.stage == LOG_WALK_REPLAY_ALL) { if (!ret && wc.stage == LOG_WALK_REPLAY_ALL) {
ret = fixup_inode_link_counts(trans, wc.replay_dest, ret = fixup_inode_link_counts(trans, wc.replay_dest,
path); path);
BUG_ON(ret);
} }
key.offset = found_key.offset - 1; key.offset = found_key.offset - 1;
@ -3992,6 +4010,9 @@ again:
free_extent_buffer(log->commit_root); free_extent_buffer(log->commit_root);
kfree(log); kfree(log);
if (ret)
goto error;
if (found_key.offset == 0) if (found_key.offset == 0)
break; break;
} }
@ -4024,6 +4045,8 @@ again:
return 0; return 0;
error: error:
if (wc.trans)
btrfs_end_transaction(wc.trans, fs_info->tree_root);
btrfs_free_path(path); btrfs_free_path(path);
return ret; return ret;
} }