Commit 2c47e605 authored by Yan Zheng's avatar Yan Zheng Committed by Chris Mason

Btrfs: update backrefs while dropping snapshot

The new backref format has restriction on type of backref item.  If a tree
block isn't referenced by its owner tree, full backrefs must be used for the
pointers in it. When a tree block loses its owner tree's reference, backrefs
for the pointers in it should be updated to full backrefs. Current
btrfs_drop_snapshot misses the code that updates backrefs, so it's unsafe for
general use.

This patch adds backrefs update code to btrfs_drop_snapshot.  It isn't a
problem in the restricted form btrfs_drop_snapshot is used today, but for
general snapshot deletion this update is required.
Signed-off-by: default avatarYan Zheng <zheng.yan@oracle.com>
Signed-off-by: default avatarChris Mason <chris.mason@oracle.com>
parent a970b0a1
...@@ -2076,8 +2076,7 @@ static inline int btrfs_insert_empty_item(struct btrfs_trans_handle *trans, ...@@ -2076,8 +2076,7 @@ static inline int btrfs_insert_empty_item(struct btrfs_trans_handle *trans,
int btrfs_next_leaf(struct btrfs_root *root, struct btrfs_path *path); int btrfs_next_leaf(struct btrfs_root *root, struct btrfs_path *path);
int btrfs_prev_leaf(struct btrfs_root *root, struct btrfs_path *path); int btrfs_prev_leaf(struct btrfs_root *root, struct btrfs_path *path);
int btrfs_leaf_free_space(struct btrfs_root *root, struct extent_buffer *leaf); int btrfs_leaf_free_space(struct btrfs_root *root, struct extent_buffer *leaf);
int btrfs_drop_snapshot(struct btrfs_trans_handle *trans, struct btrfs_root int btrfs_drop_snapshot(struct btrfs_root *root, int update_ref);
*root);
int btrfs_drop_subtree(struct btrfs_trans_handle *trans, int btrfs_drop_subtree(struct btrfs_trans_handle *trans,
struct btrfs_root *root, struct btrfs_root *root,
struct extent_buffer *node, struct extent_buffer *node,
......
...@@ -990,15 +990,13 @@ static inline int extent_ref_type(u64 parent, u64 owner) ...@@ -990,15 +990,13 @@ static inline int extent_ref_type(u64 parent, u64 owner)
return type; return type;
} }
static int find_next_key(struct btrfs_path *path, struct btrfs_key *key) static int find_next_key(struct btrfs_path *path, int level,
struct btrfs_key *key)
{ {
int level; for (; level < BTRFS_MAX_LEVEL; level++) {
BUG_ON(!path->keep_locks);
for (level = 0; level < BTRFS_MAX_LEVEL; level++) {
if (!path->nodes[level]) if (!path->nodes[level])
break; break;
btrfs_assert_tree_locked(path->nodes[level]);
if (path->slots[level] + 1 >= if (path->slots[level] + 1 >=
btrfs_header_nritems(path->nodes[level])) btrfs_header_nritems(path->nodes[level]))
continue; continue;
...@@ -1158,7 +1156,8 @@ int lookup_inline_extent_backref(struct btrfs_trans_handle *trans, ...@@ -1158,7 +1156,8 @@ int lookup_inline_extent_backref(struct btrfs_trans_handle *trans,
* For simplicity, we just do not add new inline back * For simplicity, we just do not add new inline back
* ref if there is any kind of item for this block * ref if there is any kind of item for this block
*/ */
if (find_next_key(path, &key) == 0 && key.objectid == bytenr && if (find_next_key(path, 0, &key) == 0 &&
key.objectid == bytenr &&
key.type < BTRFS_BLOCK_GROUP_ITEM_KEY) { key.type < BTRFS_BLOCK_GROUP_ITEM_KEY) {
err = -EAGAIN; err = -EAGAIN;
goto out; goto out;
...@@ -4128,6 +4127,7 @@ struct extent_buffer *btrfs_alloc_free_block(struct btrfs_trans_handle *trans, ...@@ -4128,6 +4127,7 @@ struct extent_buffer *btrfs_alloc_free_block(struct btrfs_trans_handle *trans,
return buf; return buf;
} }
#if 0
int btrfs_drop_leaf_ref(struct btrfs_trans_handle *trans, int btrfs_drop_leaf_ref(struct btrfs_trans_handle *trans,
struct btrfs_root *root, struct extent_buffer *leaf) struct btrfs_root *root, struct extent_buffer *leaf)
{ {
...@@ -4171,8 +4171,6 @@ int btrfs_drop_leaf_ref(struct btrfs_trans_handle *trans, ...@@ -4171,8 +4171,6 @@ int btrfs_drop_leaf_ref(struct btrfs_trans_handle *trans,
return 0; return 0;
} }
#if 0
static noinline int cache_drop_leaf_ref(struct btrfs_trans_handle *trans, static noinline int cache_drop_leaf_ref(struct btrfs_trans_handle *trans,
struct btrfs_root *root, struct btrfs_root *root,
struct btrfs_leaf_ref *ref) struct btrfs_leaf_ref *ref)
...@@ -4553,262 +4551,471 @@ static noinline int walk_down_tree(struct btrfs_trans_handle *trans, ...@@ -4553,262 +4551,471 @@ static noinline int walk_down_tree(struct btrfs_trans_handle *trans,
} }
#endif #endif
struct walk_control {
u64 refs[BTRFS_MAX_LEVEL];
u64 flags[BTRFS_MAX_LEVEL];
struct btrfs_key update_progress;
int stage;
int level;
int shared_level;
int update_ref;
int keep_locks;
};
#define DROP_REFERENCE 1
#define UPDATE_BACKREF 2
/* /*
* helper function for drop_subtree, this function is similar to * hepler to process tree block while walking down the tree.
* walk_down_tree. The main difference is that it checks reference *
* counts while tree blocks are locked. * when wc->stage == DROP_REFERENCE, this function checks
* reference count of the block. if the block is shared and
* we need update back refs for the subtree rooted at the
* block, this function changes wc->stage to UPDATE_BACKREF
*
* when wc->stage == UPDATE_BACKREF, this function updates
* back refs for pointers in the block.
*
* NOTE: return value 1 means we should stop walking down.
*/ */
static noinline int walk_down_tree(struct btrfs_trans_handle *trans, static noinline int walk_down_proc(struct btrfs_trans_handle *trans,
struct btrfs_root *root, struct btrfs_root *root,
struct btrfs_path *path, int *level) struct btrfs_path *path,
struct walk_control *wc)
{ {
struct extent_buffer *next; int level = wc->level;
struct extent_buffer *cur; struct extent_buffer *eb = path->nodes[level];
struct extent_buffer *parent; struct btrfs_key key;
u64 bytenr; u64 flag = BTRFS_BLOCK_FLAG_FULL_BACKREF;
u64 ptr_gen;
u64 refs;
u64 flags;
u32 blocksize;
int ret; int ret;
cur = path->nodes[*level]; if (wc->stage == UPDATE_BACKREF &&
ret = btrfs_lookup_extent_info(trans, root, cur->start, cur->len, btrfs_header_owner(eb) != root->root_key.objectid)
&refs, &flags); return 1;
/*
* when reference count of tree block is 1, it won't increase
* again. once full backref flag is set, we never clear it.
*/
if ((wc->stage == DROP_REFERENCE && wc->refs[level] != 1) ||
(wc->stage == UPDATE_BACKREF && !(wc->flags[level] & flag))) {
BUG_ON(!path->locks[level]);
ret = btrfs_lookup_extent_info(trans, root,
eb->start, eb->len,
&wc->refs[level],
&wc->flags[level]);
BUG_ON(ret); BUG_ON(ret);
if (refs > 1) BUG_ON(wc->refs[level] == 0);
goto out; }
BUG_ON(!(flags & BTRFS_BLOCK_FLAG_FULL_BACKREF)); if (wc->stage == DROP_REFERENCE &&
wc->update_ref && wc->refs[level] > 1) {
BUG_ON(eb == root->node);
BUG_ON(path->slots[level] > 0);
if (level == 0)
btrfs_item_key_to_cpu(eb, &key, path->slots[level]);
else
btrfs_node_key_to_cpu(eb, &key, path->slots[level]);
if (btrfs_header_owner(eb) == root->root_key.objectid &&
btrfs_comp_cpu_keys(&key, &wc->update_progress) >= 0) {
wc->stage = UPDATE_BACKREF;
wc->shared_level = level;
}
}
while (*level >= 0) { if (wc->stage == DROP_REFERENCE) {
cur = path->nodes[*level]; if (wc->refs[level] > 1)
if (*level == 0) { return 1;
ret = btrfs_drop_leaf_ref(trans, root, cur);
if (path->locks[level] && !wc->keep_locks) {
btrfs_tree_unlock(eb);
path->locks[level] = 0;
}
return 0;
}
/* wc->stage == UPDATE_BACKREF */
if (!(wc->flags[level] & flag)) {
BUG_ON(!path->locks[level]);
ret = btrfs_inc_ref(trans, root, eb, 1);
BUG_ON(ret); BUG_ON(ret);
clean_tree_block(trans, root, cur); ret = btrfs_dec_ref(trans, root, eb, 0);
break; BUG_ON(ret);
ret = btrfs_set_disk_extent_flags(trans, root, eb->start,
eb->len, flag, 0);
BUG_ON(ret);
wc->flags[level] |= flag;
} }
if (path->slots[*level] >= btrfs_header_nritems(cur)) {
clean_tree_block(trans, root, cur); /*
break; * the block is shared by multiple trees, so it's not good to
* keep the tree lock
*/
if (path->locks[level] && level > 0) {
btrfs_tree_unlock(eb);
path->locks[level] = 0;
} }
return 0;
}
bytenr = btrfs_node_blockptr(cur, path->slots[*level]); /*
blocksize = btrfs_level_size(root, *level - 1); * hepler to process tree block while walking up the tree.
ptr_gen = btrfs_node_ptr_generation(cur, path->slots[*level]); *
* when wc->stage == DROP_REFERENCE, this function drops
* reference count on the block.
*
* when wc->stage == UPDATE_BACKREF, this function changes
* wc->stage back to DROP_REFERENCE if we changed wc->stage
* to UPDATE_BACKREF previously while processing the block.
*
* NOTE: return value 1 means we should stop walking up.
*/
static noinline int walk_up_proc(struct btrfs_trans_handle *trans,
struct btrfs_root *root,
struct btrfs_path *path,
struct walk_control *wc)
{
int ret = 0;
int level = wc->level;
struct extent_buffer *eb = path->nodes[level];
u64 parent = 0;
next = read_tree_block(root, bytenr, blocksize, ptr_gen); if (wc->stage == UPDATE_BACKREF) {
btrfs_tree_lock(next); BUG_ON(wc->shared_level < level);
btrfs_set_lock_blocking(next); if (level < wc->shared_level)
goto out;
ret = btrfs_lookup_extent_info(trans, root, bytenr, blocksize, BUG_ON(wc->refs[level] <= 1);
&refs, &flags); ret = find_next_key(path, level + 1, &wc->update_progress);
BUG_ON(ret); if (ret > 0)
if (refs > 1) { wc->update_ref = 0;
parent = path->nodes[*level];
ret = btrfs_free_extent(trans, root, bytenr, wc->stage = DROP_REFERENCE;
blocksize, parent->start, wc->shared_level = -1;
btrfs_header_owner(parent), path->slots[level] = 0;
*level - 1, 0);
/*
* check reference count again if the block isn't locked.
* we should start walking down the tree again if reference
* count is one.
*/
if (!path->locks[level]) {
BUG_ON(level == 0);
btrfs_tree_lock(eb);
btrfs_set_lock_blocking(eb);
path->locks[level] = 1;
ret = btrfs_lookup_extent_info(trans, root,
eb->start, eb->len,
&wc->refs[level],
&wc->flags[level]);
BUG_ON(ret); BUG_ON(ret);
path->slots[*level]++; BUG_ON(wc->refs[level] == 0);
btrfs_tree_unlock(next); if (wc->refs[level] == 1) {
free_extent_buffer(next); btrfs_tree_unlock(eb);
continue; path->locks[level] = 0;
return 1;
}
} else {
BUG_ON(level != 0);
}
} }
BUG_ON(!(flags & BTRFS_BLOCK_FLAG_FULL_BACKREF)); /* wc->stage == DROP_REFERENCE */
BUG_ON(wc->refs[level] > 1 && !path->locks[level]);
*level = btrfs_header_level(next); if (wc->refs[level] == 1) {
path->nodes[*level] = next; if (level == 0) {
path->slots[*level] = 0; if (wc->flags[level] & BTRFS_BLOCK_FLAG_FULL_BACKREF)
path->locks[*level] = 1; ret = btrfs_dec_ref(trans, root, eb, 1);
cond_resched(); else
ret = btrfs_dec_ref(trans, root, eb, 0);
BUG_ON(ret);
} }
out: /* make block locked assertion in clean_tree_block happy */
if (path->nodes[*level] == root->node) if (!path->locks[level] &&
parent = path->nodes[*level]; btrfs_header_generation(eb) == trans->transid) {
btrfs_tree_lock(eb);
btrfs_set_lock_blocking(eb);
path->locks[level] = 1;
}
clean_tree_block(trans, root, eb);
}
if (eb == root->node) {
if (wc->flags[level] & BTRFS_BLOCK_FLAG_FULL_BACKREF)
parent = eb->start;
else else
parent = path->nodes[*level + 1]; BUG_ON(root->root_key.objectid !=
bytenr = path->nodes[*level]->start; btrfs_header_owner(eb));
blocksize = path->nodes[*level]->len; } else {
if (wc->flags[level + 1] & BTRFS_BLOCK_FLAG_FULL_BACKREF)
parent = path->nodes[level + 1]->start;
else
BUG_ON(root->root_key.objectid !=
btrfs_header_owner(path->nodes[level + 1]));
}
ret = btrfs_free_extent(trans, root, bytenr, blocksize, parent->start, ret = btrfs_free_extent(trans, root, eb->start, eb->len, parent,
btrfs_header_owner(parent), *level, 0); root->root_key.objectid, level, 0);
BUG_ON(ret); BUG_ON(ret);
out:
wc->refs[level] = 0;
wc->flags[level] = 0;
return ret;
}
if (path->locks[*level]) { static noinline int walk_down_tree(struct btrfs_trans_handle *trans,
btrfs_tree_unlock(path->nodes[*level]); struct btrfs_root *root,
path->locks[*level] = 0; struct btrfs_path *path,
struct walk_control *wc)
{
struct extent_buffer *next;
struct extent_buffer *cur;
u64 bytenr;
u64 ptr_gen;
u32 blocksize;
int level = wc->level;
int ret;
while (level >= 0) {
cur = path->nodes[level];
BUG_ON(path->slots[level] >= btrfs_header_nritems(cur));
ret = walk_down_proc(trans, root, path, wc);
if (ret > 0)
break;
if (level == 0)
break;
bytenr = btrfs_node_blockptr(cur, path->slots[level]);
blocksize = btrfs_level_size(root, level - 1);
ptr_gen = btrfs_node_ptr_generation(cur, path->slots[level]);
next = read_tree_block(root, bytenr, blocksize, ptr_gen);
btrfs_tree_lock(next);
btrfs_set_lock_blocking(next);
level--;
BUG_ON(level != btrfs_header_level(next));
path->nodes[level] = next;
path->slots[level] = 0;
path->locks[level] = 1;
wc->level = level;
} }
free_extent_buffer(path->nodes[*level]);
path->nodes[*level] = NULL;
*level += 1;
cond_resched();
return 0; return 0;
} }
/*
* helper for dropping snapshots. This walks back up the tree in the path
* to find the first node higher up where we haven't yet gone through
* all the slots
*/
static noinline int walk_up_tree(struct btrfs_trans_handle *trans, static noinline int walk_up_tree(struct btrfs_trans_handle *trans,
struct btrfs_root *root, struct btrfs_root *root,
struct btrfs_path *path, struct btrfs_path *path,
int *level, int max_level) struct walk_control *wc, int max_level)
{ {
struct btrfs_root_item *root_item = &root->root_item; int level = wc->level;
int i;
int slot;
int ret; int ret;
for (i = *level; i < max_level && path->nodes[i]; i++) { path->slots[level] = btrfs_header_nritems(path->nodes[level]);
slot = path->slots[i]; while (level < max_level && path->nodes[level]) {
if (slot + 1 < btrfs_header_nritems(path->nodes[i])) { wc->level = level;
/* if (path->slots[level] + 1 <
* there is more work to do in this level. btrfs_header_nritems(path->nodes[level])) {
* Update the drop_progress marker to reflect path->slots[level]++;
* the work we've done so far, and then bump
* the slot number
*/
path->slots[i]++;
WARN_ON(*level == 0);
if (max_level == BTRFS_MAX_LEVEL) {
btrfs_node_key(path->nodes[i],
&root_item->drop_progress,
path->slots[i]);
root_item->drop_level = i;
}
*level = i;
return 0; return 0;
} else { } else {
struct extent_buffer *parent; ret = walk_up_proc(trans, root, path, wc);
if (ret > 0)
/* return 0;
* this whole node is done, free our reference
* on it and go up one level
*/
if (path->nodes[*level] == root->node)
parent = path->nodes[*level];
else
parent = path->nodes[*level + 1];
clean_tree_block(trans, root, path->nodes[i]); if (path->locks[level]) {
ret = btrfs_free_extent(trans, root, btrfs_tree_unlock(path->nodes[level]);
path->nodes[i]->start, path->locks[level] = 0;
path->nodes[i]->len,
parent->start,
btrfs_header_owner(parent),
*level, 0);
BUG_ON(ret);
if (path->locks[*level]) {
btrfs_tree_unlock(path->nodes[i]);
path->locks[i] = 0;
} }
free_extent_buffer(path->nodes[i]); free_extent_buffer(path->nodes[level]);
path->nodes[i] = NULL; path->nodes[level] = NULL;
*level = i + 1; level++;
} }
} }
return 1; return 1;
} }
/* /*
* drop the reference count on the tree rooted at 'snap'. This traverses * drop a subvolume tree.
* the tree freeing any blocks that have a ref count of zero after being *
* decremented. * this function traverses the tree freeing any blocks that only
* referenced by the tree.
*
* when a shared tree block is found. this function decreases its
* reference count by one. if update_ref is true, this function
* also make sure backrefs for the shared block and all lower level
* blocks are properly updated.
*/ */
int btrfs_drop_snapshot(struct btrfs_trans_handle *trans, struct btrfs_root int btrfs_drop_snapshot(struct btrfs_root *root, int update_ref)
*root)
{ {
int ret = 0;
int wret;
int level;
struct btrfs_path *path; struct btrfs_path *path;
int update_count; struct btrfs_trans_handle *trans;
struct btrfs_root *tree_root = root->fs_info->tree_root;
struct btrfs_root_item *root_item = &root->root_item; struct btrfs_root_item *root_item = &root->root_item;
struct walk_control *wc;
struct btrfs_key key;
int err = 0;
int ret;
int level;
path = btrfs_alloc_path(); path = btrfs_alloc_path();
BUG_ON(!path); BUG_ON(!path);
level = btrfs_header_level(root->node); wc = kzalloc(sizeof(*wc), GFP_NOFS);
BUG_ON(!wc);
trans = btrfs_start_transaction(tree_root, 1);
if (btrfs_disk_key_objectid(&root_item->drop_progress) == 0) { if (btrfs_disk_key_objectid(&root_item->drop_progress) == 0) {
level = btrfs_header_level(root->node);
path->nodes[level] = btrfs_lock_root_node(root); path->nodes[level] = btrfs_lock_root_node(root);
btrfs_set_lock_blocking(path->nodes[level]); btrfs_set_lock_blocking(path->nodes[level]);
path->slots[level] = 0; path->slots[level] = 0;
path->locks[level] = 1; path->locks[level] = 1;
memset(&wc->update_progress, 0,
sizeof(wc->update_progress));
} else { } else {
struct btrfs_key key;
struct btrfs_disk_key found_key;
struct extent_buffer *node;
btrfs_disk_key_to_cpu(&key, &root_item->drop_progress); btrfs_disk_key_to_cpu(&key, &root_item->drop_progress);
memcpy(&wc->update_progress, &key,
sizeof(wc->update_progress));
level = root_item->drop_level; level = root_item->drop_level;
BUG_ON(level == 0);
path->lowest_level = level; path->lowest_level = level;
wret = btrfs_search_slot(NULL, root, &key, path, 0, 0); ret = btrfs_search_slot(NULL, root, &key, path, 0, 0);
if (wret < 0) { path->lowest_level = 0;
ret = wret; if (ret < 0) {
err = ret;
goto out; goto out;
} }
node = path->nodes[level]; btrfs_node_key_to_cpu(path->nodes[level], &key,
btrfs_node_key(node, &found_key, path->slots[level]); path->slots[level]);
WARN_ON(memcmp(&found_key, &root_item->drop_progress, WARN_ON(memcmp(&key, &wc->update_progress, sizeof(key)));
sizeof(found_key)));
/* /*
* unlock our path, this is safe because only this * unlock our path, this is safe because only this
* function is allowed to delete this snapshot * function is allowed to delete this snapshot
*/ */
btrfs_unlock_up_safe(path, 0); btrfs_unlock_up_safe(path, 0);
level = btrfs_header_level(root->node);
while (1) {
btrfs_tree_lock(path->nodes[level]);
btrfs_set_lock_blocking(path->nodes[level]);
ret = btrfs_lookup_extent_info(trans, root,
path->nodes[level]->start,
path->nodes[level]->len,
&wc->refs[level],
&wc->flags[level]);
BUG_ON(ret);
BUG_ON(wc->refs[level] == 0);
if (level == root_item->drop_level)
break;
btrfs_tree_unlock(path->nodes[level]);
WARN_ON(wc->refs[level] != 1);
level--;
}
} }
wc->level = level;
wc->shared_level = -1;
wc->stage = DROP_REFERENCE;
wc->update_ref = update_ref;
wc->keep_locks = 0;
while (1) { while (1) {
unsigned long update; ret = walk_down_tree(trans, root, path, wc);
wret = walk_down_tree(trans, root, path, &level); if (ret < 0) {
if (wret > 0) err = ret;
break; break;
if (wret < 0) }
ret = wret;
wret = walk_up_tree(trans, root, path, &level, ret = walk_up_tree(trans, root, path, wc, BTRFS_MAX_LEVEL);
BTRFS_MAX_LEVEL); if (ret < 0) {
if (wret > 0) err = ret;
break; break;
if (wret < 0) }
ret = wret;
if (trans->transaction->in_commit || if (ret > 0) {
trans->transaction->delayed_refs.flushing) { BUG_ON(wc->stage != DROP_REFERENCE);
ret = -EAGAIN;
break; break;
} }
for (update_count = 0; update_count < 16; update_count++) {
if (wc->stage == DROP_REFERENCE) {
level = wc->level;
btrfs_node_key(path->nodes[level],
&root_item->drop_progress,
path->slots[level]);
root_item->drop_level = level;
}
BUG_ON(wc->level == 0);
if (trans->transaction->in_commit ||
trans->transaction->delayed_refs.flushing) {
ret = btrfs_update_root(trans, tree_root,
&root->root_key,
root_item);
BUG_ON(ret);
btrfs_end_transaction(trans, tree_root);
trans = btrfs_start_transaction(tree_root, 1);
} else {
unsigned long update;
update = trans->delayed_ref_updates; update = trans->delayed_ref_updates;
trans->delayed_ref_updates = 0; trans->delayed_ref_updates = 0;
if (update) if (update)
btrfs_run_delayed_refs(trans, root, update); btrfs_run_delayed_refs(trans, tree_root,
else update);
break;
} }
} }
btrfs_release_path(root, path);
BUG_ON(err);
ret = btrfs_del_root(trans, tree_root, &root->root_key);
BUG_ON(ret);
free_extent_buffer(root->node);
free_extent_buffer(root->commit_root);
kfree(root);
out: out:
btrfs_end_transaction(trans, tree_root);
kfree(wc);
btrfs_free_path(path); btrfs_free_path(path);
return ret; return err;
} }
/*
* drop subtree rooted at tree block 'node'.
*
* NOTE: this function will unlock and release tree block 'node'
*/
int btrfs_drop_subtree(struct btrfs_trans_handle *trans, int btrfs_drop_subtree(struct btrfs_trans_handle *trans,
struct btrfs_root *root, struct btrfs_root *root,
struct extent_buffer *node, struct extent_buffer *node,
struct extent_buffer *parent) struct extent_buffer *parent)
{ {
struct btrfs_path *path; struct btrfs_path *path;
struct walk_control *wc;
int level; int level;
int parent_level; int parent_level;
int ret = 0; int ret = 0;
int wret; int wret;
BUG_ON(root->root_key.objectid != BTRFS_TREE_RELOC_OBJECTID);
path = btrfs_alloc_path(); path = btrfs_alloc_path();
BUG_ON(!path); BUG_ON(!path);
wc = kzalloc(sizeof(*wc), GFP_NOFS);
BUG_ON(!wc);
btrfs_assert_tree_locked(parent); btrfs_assert_tree_locked(parent);
parent_level = btrfs_header_level(parent); parent_level = btrfs_header_level(parent);
extent_buffer_get(parent); extent_buffer_get(parent);
...@@ -4817,24 +5024,33 @@ int btrfs_drop_subtree(struct btrfs_trans_handle *trans, ...@@ -4817,24 +5024,33 @@ int btrfs_drop_subtree(struct btrfs_trans_handle *trans,
btrfs_assert_tree_locked(node); btrfs_assert_tree_locked(node);
level = btrfs_header_level(node); level = btrfs_header_level(node);
extent_buffer_get(node);
path->nodes[level] = node; path->nodes[level] = node;
path->slots[level] = 0; path->slots[level] = 0;
path->locks[level] = 1;
wc->refs[parent_level] = 1;
wc->flags[parent_level] = BTRFS_BLOCK_FLAG_FULL_BACKREF;
wc->level = level;
wc->shared_level = -1;
wc->stage = DROP_REFERENCE;
wc->update_ref = 0;
wc->keep_locks = 1;
while (1) { while (1) {
wret = walk_down_tree(trans, root, path, &level); wret = walk_down_tree(trans, root, path, wc);
if (wret < 0) if (wret < 0) {
ret = wret; ret = wret;
if (wret != 0)
break; break;
}
wret = walk_up_tree(trans, root, path, &level, parent_level); wret = walk_up_tree(trans, root, path, wc, parent_level);
if (wret < 0) if (wret < 0)
ret = wret; ret = wret;
if (wret != 0) if (wret != 0)
break; break;
} }
kfree(wc);
btrfs_free_path(path); btrfs_free_path(path);
return ret; return ret;
} }
......
...@@ -1788,7 +1788,7 @@ static void merge_func(struct btrfs_work *work) ...@@ -1788,7 +1788,7 @@ static void merge_func(struct btrfs_work *work)
btrfs_end_transaction(trans, root); btrfs_end_transaction(trans, root);
} }
btrfs_drop_dead_root(reloc_root); btrfs_drop_snapshot(reloc_root, 0);
if (atomic_dec_and_test(async->num_pending)) if (atomic_dec_and_test(async->num_pending))
complete(async->done); complete(async->done);
...@@ -2075,9 +2075,6 @@ static int do_relocation(struct btrfs_trans_handle *trans, ...@@ -2075,9 +2075,6 @@ static int do_relocation(struct btrfs_trans_handle *trans,
ret = btrfs_drop_subtree(trans, root, eb, upper->eb); ret = btrfs_drop_subtree(trans, root, eb, upper->eb);
BUG_ON(ret); BUG_ON(ret);
btrfs_tree_unlock(eb);
free_extent_buffer(eb);
} }
if (!lowest) { if (!lowest) {
btrfs_tree_unlock(upper->eb); btrfs_tree_unlock(upper->eb);
......
...@@ -593,6 +593,7 @@ int btrfs_defrag_root(struct btrfs_root *root, int cacheonly) ...@@ -593,6 +593,7 @@ int btrfs_defrag_root(struct btrfs_root *root, int cacheonly)
return 0; return 0;
} }
#if 0
/* /*
* when dropping snapshots, we generate a ton of delayed refs, and it makes * when dropping snapshots, we generate a ton of delayed refs, and it makes
* sense not to join the transaction while it is trying to flush the current * sense not to join the transaction while it is trying to flush the current
...@@ -681,6 +682,7 @@ int btrfs_drop_dead_root(struct btrfs_root *root) ...@@ -681,6 +682,7 @@ int btrfs_drop_dead_root(struct btrfs_root *root)
btrfs_btree_balance_dirty(tree_root, nr); btrfs_btree_balance_dirty(tree_root, nr);
return ret; return ret;
} }
#endif
/* /*
* new snapshots need to be created at a very specific time in the * new snapshots need to be created at a very specific time in the
...@@ -1081,7 +1083,7 @@ int btrfs_clean_old_snapshots(struct btrfs_root *root) ...@@ -1081,7 +1083,7 @@ int btrfs_clean_old_snapshots(struct btrfs_root *root)
while (!list_empty(&list)) { while (!list_empty(&list)) {
root = list_entry(list.next, struct btrfs_root, root_list); root = list_entry(list.next, struct btrfs_root, root_list);
list_del_init(&root->root_list); list_del_init(&root->root_list);
btrfs_drop_dead_root(root); btrfs_drop_snapshot(root, 0);
} }
return 0; return 0;
} }
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