|
@@ -1874,6 +1874,141 @@ out:
|
|
|
return ret;
|
|
|
}
|
|
|
|
|
|
+/*
|
|
|
+ * Helper function to do recursive generation-aware depth-first search, to
|
|
|
+ * locate all new tree blocks in a subtree of reloc tree.
|
|
|
+ *
|
|
|
+ * E.g. (OO = Old tree blocks, NN = New tree blocks, whose gen == last_snapshot)
|
|
|
+ * reloc tree
|
|
|
+ * L2 NN (a)
|
|
|
+ * / \
|
|
|
+ * L1 OO NN (b)
|
|
|
+ * / \ / \
|
|
|
+ * L0 OO OO OO NN
|
|
|
+ * (c) (d)
|
|
|
+ * If we pass:
|
|
|
+ * @dst_path = [ nodes[1] = NN(b), nodes[0] = NULL ],
|
|
|
+ * @cur_level = 1
|
|
|
+ * @root_level = 1
|
|
|
+ *
|
|
|
+ * We will iterate through tree blocks NN(b), NN(d) and info qgroup to trace
|
|
|
+ * above tree blocks along with their counter parts in file tree.
|
|
|
+ * While during search, old tree blocsk OO(c) will be skiped as tree block swap
|
|
|
+ * won't affect OO(c).
|
|
|
+ */
|
|
|
+static int qgroup_trace_new_subtree_blocks(struct btrfs_trans_handle* trans,
|
|
|
+ struct extent_buffer *src_eb,
|
|
|
+ struct btrfs_path *dst_path,
|
|
|
+ int cur_level, int root_level,
|
|
|
+ u64 last_snapshot)
|
|
|
+{
|
|
|
+ struct btrfs_fs_info *fs_info = trans->fs_info;
|
|
|
+ struct extent_buffer *eb;
|
|
|
+ bool need_cleanup = false;
|
|
|
+ int ret = 0;
|
|
|
+ int i;
|
|
|
+
|
|
|
+ /* Level sanity check */
|
|
|
+ if (cur_level < 0 || cur_level >= BTRFS_MAX_LEVEL ||
|
|
|
+ root_level < 0 || root_level >= BTRFS_MAX_LEVEL ||
|
|
|
+ root_level < cur_level) {
|
|
|
+ btrfs_err_rl(fs_info,
|
|
|
+ "%s: bad levels, cur_level=%d root_level=%d",
|
|
|
+ __func__, cur_level, root_level);
|
|
|
+ return -EUCLEAN;
|
|
|
+ }
|
|
|
+
|
|
|
+ /* Read the tree block if needed */
|
|
|
+ if (dst_path->nodes[cur_level] == NULL) {
|
|
|
+ struct btrfs_key first_key;
|
|
|
+ int parent_slot;
|
|
|
+ u64 child_gen;
|
|
|
+ u64 child_bytenr;
|
|
|
+
|
|
|
+ /*
|
|
|
+ * dst_path->nodes[root_level] must be initialized before
|
|
|
+ * calling this function.
|
|
|
+ */
|
|
|
+ if (cur_level == root_level) {
|
|
|
+ btrfs_err_rl(fs_info,
|
|
|
+ "%s: dst_path->nodes[%d] not initialized, root_level=%d cur_level=%d",
|
|
|
+ __func__, root_level, root_level, cur_level);
|
|
|
+ return -EUCLEAN;
|
|
|
+ }
|
|
|
+
|
|
|
+ /*
|
|
|
+ * We need to get child blockptr/gen from parent before we can
|
|
|
+ * read it.
|
|
|
+ */
|
|
|
+ eb = dst_path->nodes[cur_level + 1];
|
|
|
+ parent_slot = dst_path->slots[cur_level + 1];
|
|
|
+ child_bytenr = btrfs_node_blockptr(eb, parent_slot);
|
|
|
+ child_gen = btrfs_node_ptr_generation(eb, parent_slot);
|
|
|
+ btrfs_node_key_to_cpu(eb, &first_key, parent_slot);
|
|
|
+
|
|
|
+ /* This node is old, no need to trace */
|
|
|
+ if (child_gen < last_snapshot)
|
|
|
+ goto out;
|
|
|
+
|
|
|
+ eb = read_tree_block(fs_info, child_bytenr, child_gen,
|
|
|
+ cur_level, &first_key);
|
|
|
+ if (IS_ERR(eb)) {
|
|
|
+ ret = PTR_ERR(eb);
|
|
|
+ goto out;
|
|
|
+ } else if (!extent_buffer_uptodate(eb)) {
|
|
|
+ free_extent_buffer(eb);
|
|
|
+ ret = -EIO;
|
|
|
+ goto out;
|
|
|
+ }
|
|
|
+
|
|
|
+ dst_path->nodes[cur_level] = eb;
|
|
|
+ dst_path->slots[cur_level] = 0;
|
|
|
+
|
|
|
+ btrfs_tree_read_lock(eb);
|
|
|
+ btrfs_set_lock_blocking_rw(eb, BTRFS_READ_LOCK);
|
|
|
+ dst_path->locks[cur_level] = BTRFS_READ_LOCK_BLOCKING;
|
|
|
+ need_cleanup = true;
|
|
|
+ }
|
|
|
+
|
|
|
+ /* Now record this tree block and its counter part for qgroups */
|
|
|
+ ret = qgroup_trace_extent_swap(trans, src_eb, dst_path, cur_level,
|
|
|
+ root_level);
|
|
|
+ if (ret < 0)
|
|
|
+ goto cleanup;
|
|
|
+
|
|
|
+ eb = dst_path->nodes[cur_level];
|
|
|
+
|
|
|
+ if (cur_level > 0) {
|
|
|
+ /* Iterate all child tree blocks */
|
|
|
+ for (i = 0; i < btrfs_header_nritems(eb); i++) {
|
|
|
+ /* Skip old tree blocks as they won't be swapped */
|
|
|
+ if (btrfs_node_ptr_generation(eb, i) < last_snapshot)
|
|
|
+ continue;
|
|
|
+ dst_path->slots[cur_level] = i;
|
|
|
+
|
|
|
+ /* Recursive call (at most 7 times) */
|
|
|
+ ret = qgroup_trace_new_subtree_blocks(trans, src_eb,
|
|
|
+ dst_path, cur_level - 1, root_level,
|
|
|
+ last_snapshot);
|
|
|
+ if (ret < 0)
|
|
|
+ goto cleanup;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+cleanup:
|
|
|
+ if (need_cleanup) {
|
|
|
+ /* Clean up */
|
|
|
+ btrfs_tree_unlock_rw(dst_path->nodes[cur_level],
|
|
|
+ dst_path->locks[cur_level]);
|
|
|
+ free_extent_buffer(dst_path->nodes[cur_level]);
|
|
|
+ dst_path->nodes[cur_level] = NULL;
|
|
|
+ dst_path->slots[cur_level] = 0;
|
|
|
+ dst_path->locks[cur_level] = 0;
|
|
|
+ }
|
|
|
+out:
|
|
|
+ return ret;
|
|
|
+}
|
|
|
+
|
|
|
int btrfs_qgroup_trace_subtree(struct btrfs_trans_handle *trans,
|
|
|
struct extent_buffer *root_eb,
|
|
|
u64 root_gen, int root_level)
|