Skip to content

Commit

Permalink
Btrfs: fix wrong handle at error path of create_snapshot() when the c…
Browse files Browse the repository at this point in the history
…ommit fails

There are several bugs at error path of create_snapshot() when the
transaction commitment failed.
- access the freed transaction handler. At the end of the
  transaction commitment, the transaction handler was freed, so we
  should not access it after the transaction commitment.
- we were not aware of the error which happened during the snapshot
  creation if we submitted a async transaction commitment.
- pending snapshot access vs pending snapshot free. when something
  wrong happened after we submitted a async transaction commitment,
  the transaction committer would cleanup the pending snapshots and
  free them. But the snapshot creators were not aware of it, they
  would access the freed pending snapshots.

This patch fixes the above problems by:
- remove the dangerous code that accessed the freed handler
- assign ->error if the error happens during the snapshot creation
- the transaction committer doesn't free the pending snapshots,
  just assigns the error number and evicts them before we unblock
  the transaction.

Reported-by: Dan Carpenter <dan.carpenter@oracle.com>
Signed-off-by: Miao Xie <miaox@cn.fujitsu.com>
Signed-off-by: Josef Bacik <jbacik@fusionio.com>
  • Loading branch information
Miao Xie authored and Josef Bacik committed Mar 4, 2013
1 parent 9bf7a48 commit aec8030
Show file tree
Hide file tree
Showing 3 changed files with 41 additions and 39 deletions.
16 changes: 7 additions & 9 deletions fs/btrfs/disk-io.c
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ static void btrfs_destroy_ordered_operations(struct btrfs_transaction *t,
static void btrfs_destroy_ordered_extents(struct btrfs_root *root);
static int btrfs_destroy_delayed_refs(struct btrfs_transaction *trans,
struct btrfs_root *root);
static void btrfs_destroy_pending_snapshots(struct btrfs_transaction *t);
static void btrfs_evict_pending_snapshots(struct btrfs_transaction *t);
static void btrfs_destroy_delalloc_inodes(struct btrfs_root *root);
static int btrfs_destroy_marked_extents(struct btrfs_root *root,
struct extent_io_tree *dirty_pages,
Expand Down Expand Up @@ -3687,7 +3687,7 @@ int btrfs_destroy_delayed_refs(struct btrfs_transaction *trans,
return ret;
}

static void btrfs_destroy_pending_snapshots(struct btrfs_transaction *t)
static void btrfs_evict_pending_snapshots(struct btrfs_transaction *t)
{
struct btrfs_pending_snapshot *snapshot;
struct list_head splice;
Expand All @@ -3700,10 +3700,8 @@ static void btrfs_destroy_pending_snapshots(struct btrfs_transaction *t)
snapshot = list_entry(splice.next,
struct btrfs_pending_snapshot,
list);

snapshot->error = -ECANCELED;
list_del_init(&snapshot->list);

kfree(snapshot);
}
}

Expand Down Expand Up @@ -3840,6 +3838,8 @@ void btrfs_cleanup_one_transaction(struct btrfs_transaction *cur_trans,
cur_trans->blocked = 1;
wake_up(&root->fs_info->transaction_blocked_wait);

btrfs_evict_pending_snapshots(cur_trans);

cur_trans->blocked = 0;
wake_up(&root->fs_info->transaction_wait);

Expand All @@ -3849,8 +3849,6 @@ void btrfs_cleanup_one_transaction(struct btrfs_transaction *cur_trans,
btrfs_destroy_delayed_inodes(root);
btrfs_assert_delayed_root_empty(root);

btrfs_destroy_pending_snapshots(cur_trans);

btrfs_destroy_marked_extents(root, &cur_trans->dirty_pages,
EXTENT_DIRTY);
btrfs_destroy_pinned_extent(root,
Expand Down Expand Up @@ -3894,6 +3892,8 @@ int btrfs_cleanup_transaction(struct btrfs_root *root)
if (waitqueue_active(&root->fs_info->transaction_blocked_wait))
wake_up(&root->fs_info->transaction_blocked_wait);

btrfs_evict_pending_snapshots(t);

t->blocked = 0;
smp_mb();
if (waitqueue_active(&root->fs_info->transaction_wait))
Expand All @@ -3907,8 +3907,6 @@ int btrfs_cleanup_transaction(struct btrfs_root *root)
btrfs_destroy_delayed_inodes(root);
btrfs_assert_delayed_root_empty(root);

btrfs_destroy_pending_snapshots(t);

btrfs_destroy_delalloc_inodes(root);

spin_lock(&root->fs_info->trans_lock);
Expand Down
6 changes: 1 addition & 5 deletions fs/btrfs/ioctl.c
Original file line number Diff line number Diff line change
Expand Up @@ -596,12 +596,8 @@ static int create_snapshot(struct btrfs_root *root, struct inode *dir,
ret = btrfs_commit_transaction(trans,
root->fs_info->extent_root);
}
if (ret) {
/* cleanup_transaction has freed this for us */
if (trans->aborted)
pending_snapshot = NULL;
if (ret)
goto fail;
}

ret = pending_snapshot->error;
if (ret)
Expand Down
58 changes: 33 additions & 25 deletions fs/btrfs/transaction.c
Original file line number Diff line number Diff line change
Expand Up @@ -1053,7 +1053,12 @@ int btrfs_defrag_root(struct btrfs_root *root)

/*
* new snapshots need to be created at a very specific time in the
* transaction commit. This does the actual creation
* transaction commit. This does the actual creation.
*
* Note:
* If the error which may affect the commitment of the current transaction
* happens, we should return the error number. If the error which just affect
* the creation of the pending snapshots, just return 0.
*/
static noinline int create_pending_snapshot(struct btrfs_trans_handle *trans,
struct btrfs_fs_info *fs_info,
Expand All @@ -1072,7 +1077,7 @@ static noinline int create_pending_snapshot(struct btrfs_trans_handle *trans,
struct extent_buffer *tmp;
struct extent_buffer *old;
struct timespec cur_time = CURRENT_TIME;
int ret;
int ret = 0;
u64 to_reserve = 0;
u64 index = 0;
u64 objectid;
Expand All @@ -1081,40 +1086,36 @@ static noinline int create_pending_snapshot(struct btrfs_trans_handle *trans,

path = btrfs_alloc_path();
if (!path) {
ret = pending->error = -ENOMEM;
return ret;
pending->error = -ENOMEM;
return 0;
}

new_root_item = kmalloc(sizeof(*new_root_item), GFP_NOFS);
if (!new_root_item) {
ret = pending->error = -ENOMEM;
pending->error = -ENOMEM;
goto root_item_alloc_fail;
}

ret = btrfs_find_free_objectid(tree_root, &objectid);
if (ret) {
pending->error = ret;
pending->error = btrfs_find_free_objectid(tree_root, &objectid);
if (pending->error)
goto no_free_objectid;
}

btrfs_reloc_pre_snapshot(trans, pending, &to_reserve);

if (to_reserve > 0) {
ret = btrfs_block_rsv_add(root, &pending->block_rsv,
to_reserve,
BTRFS_RESERVE_NO_FLUSH);
if (ret) {
pending->error = ret;
pending->error = btrfs_block_rsv_add(root,
&pending->block_rsv,
to_reserve,
BTRFS_RESERVE_NO_FLUSH);
if (pending->error)
goto no_free_objectid;
}
}

ret = btrfs_qgroup_inherit(trans, fs_info, root->root_key.objectid,
objectid, pending->inherit);
if (ret) {
pending->error = ret;
pending->error = btrfs_qgroup_inherit(trans, fs_info,
root->root_key.objectid,
objectid, pending->inherit);
if (pending->error)
goto no_free_objectid;
}

key.objectid = objectid;
key.offset = (u64)-1;
Expand Down Expand Up @@ -1142,7 +1143,7 @@ static noinline int create_pending_snapshot(struct btrfs_trans_handle *trans,
dentry->d_name.len, 0);
if (dir_item != NULL && !IS_ERR(dir_item)) {
pending->error = -EEXIST;
goto fail;
goto dir_item_existed;
} else if (IS_ERR(dir_item)) {
ret = PTR_ERR(dir_item);
btrfs_abort_transaction(trans, root, ret);
Expand Down Expand Up @@ -1273,6 +1274,8 @@ static noinline int create_pending_snapshot(struct btrfs_trans_handle *trans,
if (ret)
btrfs_abort_transaction(trans, root, ret);
fail:
pending->error = ret;
dir_item_existed:
trans->block_rsv = rsv;
trans->bytes_reserved = 0;
no_free_objectid:
Expand All @@ -1288,12 +1291,17 @@ static noinline int create_pending_snapshot(struct btrfs_trans_handle *trans,
static noinline int create_pending_snapshots(struct btrfs_trans_handle *trans,
struct btrfs_fs_info *fs_info)
{
struct btrfs_pending_snapshot *pending;
struct btrfs_pending_snapshot *pending, *next;
struct list_head *head = &trans->transaction->pending_snapshots;
int ret = 0;

list_for_each_entry(pending, head, list)
create_pending_snapshot(trans, fs_info, pending);
return 0;
list_for_each_entry_safe(pending, next, head, list) {
list_del(&pending->list);
ret = create_pending_snapshot(trans, fs_info, pending);
if (ret)
break;
}
return ret;
}

static void update_super_roots(struct btrfs_root *root)
Expand Down

0 comments on commit aec8030

Please sign in to comment.