aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorAlexander Block <ablock84@googlemail.com>2012-06-05 15:07:48 -0400
committerAlexander Block <ablock84@googlemail.com>2012-07-25 17:30:14 -0400
commit7069830a9e381e33d44ded45095f764844c71d24 (patch)
tree128cdd4c868bdaa436383e20930fbcc3b269de7e
parent8ea05e3a4262b9e6871c349fa3486bcfc72ffd1a (diff)
Btrfs: add btrfs_compare_trees function
This function is used to find the differences between two trees. The tree compare skips whole subtrees if it detects shared tree blocks and thus is pretty fast. Signed-off-by: Alexander Block <ablock84@googlemail.com> Reviewed-by: David Sterba <dave@jikos.cz> Reviewed-by: Arne Jansen <sensille@gmx.net> Reviewed-by: Jan Schmidt <list.btrfs@jan-o-sch.net> Reviewed-by: Alex Lyakas <alex.bolshoy.btrfs@gmail.com>
-rw-r--r--fs/btrfs/ctree.c425
-rw-r--r--fs/btrfs/ctree.h15
2 files changed, 440 insertions, 0 deletions
diff --git a/fs/btrfs/ctree.c b/fs/btrfs/ctree.c
index c82a9e4a953e..4c10fd19d481 100644
--- a/fs/btrfs/ctree.c
+++ b/fs/btrfs/ctree.c
@@ -5005,6 +5005,431 @@ out:
5005 return ret; 5005 return ret;
5006} 5006}
5007 5007
5008static void tree_move_down(struct btrfs_root *root,
5009 struct btrfs_path *path,
5010 int *level, int root_level)
5011{
5012 path->nodes[*level - 1] = read_node_slot(root, path->nodes[*level],
5013 path->slots[*level]);
5014 path->slots[*level - 1] = 0;
5015 (*level)--;
5016}
5017
5018static int tree_move_next_or_upnext(struct btrfs_root *root,
5019 struct btrfs_path *path,
5020 int *level, int root_level)
5021{
5022 int ret = 0;
5023 int nritems;
5024 nritems = btrfs_header_nritems(path->nodes[*level]);
5025
5026 path->slots[*level]++;
5027
5028 while (path->slots[*level] == nritems) {
5029 if (*level == root_level)
5030 return -1;
5031
5032 /* move upnext */
5033 path->slots[*level] = 0;
5034 free_extent_buffer(path->nodes[*level]);
5035 path->nodes[*level] = NULL;
5036 (*level)++;
5037 path->slots[*level]++;
5038
5039 nritems = btrfs_header_nritems(path->nodes[*level]);
5040 ret = 1;
5041 }
5042 return ret;
5043}
5044
5045/*
5046 * Returns 1 if it had to move up and next. 0 is returned if it moved only next
5047 * or down.
5048 */
5049static int tree_advance(struct btrfs_root *root,
5050 struct btrfs_path *path,
5051 int *level, int root_level,
5052 int allow_down,
5053 struct btrfs_key *key)
5054{
5055 int ret;
5056
5057 if (*level == 0 || !allow_down) {
5058 ret = tree_move_next_or_upnext(root, path, level, root_level);
5059 } else {
5060 tree_move_down(root, path, level, root_level);
5061 ret = 0;
5062 }
5063 if (ret >= 0) {
5064 if (*level == 0)
5065 btrfs_item_key_to_cpu(path->nodes[*level], key,
5066 path->slots[*level]);
5067 else
5068 btrfs_node_key_to_cpu(path->nodes[*level], key,
5069 path->slots[*level]);
5070 }
5071 return ret;
5072}
5073
5074static int tree_compare_item(struct btrfs_root *left_root,
5075 struct btrfs_path *left_path,
5076 struct btrfs_path *right_path,
5077 char *tmp_buf)
5078{
5079 int cmp;
5080 int len1, len2;
5081 unsigned long off1, off2;
5082
5083 len1 = btrfs_item_size_nr(left_path->nodes[0], left_path->slots[0]);
5084 len2 = btrfs_item_size_nr(right_path->nodes[0], right_path->slots[0]);
5085 if (len1 != len2)
5086 return 1;
5087
5088 off1 = btrfs_item_ptr_offset(left_path->nodes[0], left_path->slots[0]);
5089 off2 = btrfs_item_ptr_offset(right_path->nodes[0],
5090 right_path->slots[0]);
5091
5092 read_extent_buffer(left_path->nodes[0], tmp_buf, off1, len1);
5093
5094 cmp = memcmp_extent_buffer(right_path->nodes[0], tmp_buf, off2, len1);
5095 if (cmp)
5096 return 1;
5097 return 0;
5098}
5099
5100#define ADVANCE 1
5101#define ADVANCE_ONLY_NEXT -1
5102
5103/*
5104 * This function compares two trees and calls the provided callback for
5105 * every changed/new/deleted item it finds.
5106 * If shared tree blocks are encountered, whole subtrees are skipped, making
5107 * the compare pretty fast on snapshotted subvolumes.
5108 *
5109 * This currently works on commit roots only. As commit roots are read only,
5110 * we don't do any locking. The commit roots are protected with transactions.
5111 * Transactions are ended and rejoined when a commit is tried in between.
5112 *
5113 * This function checks for modifications done to the trees while comparing.
5114 * If it detects a change, it aborts immediately.
5115 */
5116int btrfs_compare_trees(struct btrfs_root *left_root,
5117 struct btrfs_root *right_root,
5118 btrfs_changed_cb_t changed_cb, void *ctx)
5119{
5120 int ret;
5121 int cmp;
5122 struct btrfs_trans_handle *trans = NULL;
5123 struct btrfs_path *left_path = NULL;
5124 struct btrfs_path *right_path = NULL;
5125 struct btrfs_key left_key;
5126 struct btrfs_key right_key;
5127 char *tmp_buf = NULL;
5128 int left_root_level;
5129 int right_root_level;
5130 int left_level;
5131 int right_level;
5132 int left_end_reached;
5133 int right_end_reached;
5134 int advance_left;
5135 int advance_right;
5136 u64 left_blockptr;
5137 u64 right_blockptr;
5138 u64 left_start_ctransid;
5139 u64 right_start_ctransid;
5140 u64 ctransid;
5141
5142 left_path = btrfs_alloc_path();
5143 if (!left_path) {
5144 ret = -ENOMEM;
5145 goto out;
5146 }
5147 right_path = btrfs_alloc_path();
5148 if (!right_path) {
5149 ret = -ENOMEM;
5150 goto out;
5151 }
5152
5153 tmp_buf = kmalloc(left_root->leafsize, GFP_NOFS);
5154 if (!tmp_buf) {
5155 ret = -ENOMEM;
5156 goto out;
5157 }
5158
5159 left_path->search_commit_root = 1;
5160 left_path->skip_locking = 1;
5161 right_path->search_commit_root = 1;
5162 right_path->skip_locking = 1;
5163
5164 spin_lock(&left_root->root_times_lock);
5165 left_start_ctransid = btrfs_root_ctransid(&left_root->root_item);
5166 spin_unlock(&left_root->root_times_lock);
5167
5168 spin_lock(&right_root->root_times_lock);
5169 right_start_ctransid = btrfs_root_ctransid(&right_root->root_item);
5170 spin_unlock(&right_root->root_times_lock);
5171
5172 trans = btrfs_join_transaction(left_root);
5173 if (IS_ERR(trans)) {
5174 ret = PTR_ERR(trans);
5175 trans = NULL;
5176 goto out;
5177 }
5178
5179 /*
5180 * Strategy: Go to the first items of both trees. Then do
5181 *
5182 * If both trees are at level 0
5183 * Compare keys of current items
5184 * If left < right treat left item as new, advance left tree
5185 * and repeat
5186 * If left > right treat right item as deleted, advance right tree
5187 * and repeat
5188 * If left == right do deep compare of items, treat as changed if
5189 * needed, advance both trees and repeat
5190 * If both trees are at the same level but not at level 0
5191 * Compare keys of current nodes/leafs
5192 * If left < right advance left tree and repeat
5193 * If left > right advance right tree and repeat
5194 * If left == right compare blockptrs of the next nodes/leafs
5195 * If they match advance both trees but stay at the same level
5196 * and repeat
5197 * If they don't match advance both trees while allowing to go
5198 * deeper and repeat
5199 * If tree levels are different
5200 * Advance the tree that needs it and repeat
5201 *
5202 * Advancing a tree means:
5203 * If we are at level 0, try to go to the next slot. If that's not
5204 * possible, go one level up and repeat. Stop when we found a level
5205 * where we could go to the next slot. We may at this point be on a
5206 * node or a leaf.
5207 *
5208 * If we are not at level 0 and not on shared tree blocks, go one
5209 * level deeper.
5210 *
5211 * If we are not at level 0 and on shared tree blocks, go one slot to
5212 * the right if possible or go up and right.
5213 */
5214
5215 left_level = btrfs_header_level(left_root->commit_root);
5216 left_root_level = left_level;
5217 left_path->nodes[left_level] = left_root->commit_root;
5218 extent_buffer_get(left_path->nodes[left_level]);
5219
5220 right_level = btrfs_header_level(right_root->commit_root);
5221 right_root_level = right_level;
5222 right_path->nodes[right_level] = right_root->commit_root;
5223 extent_buffer_get(right_path->nodes[right_level]);
5224
5225 if (left_level == 0)
5226 btrfs_item_key_to_cpu(left_path->nodes[left_level],
5227 &left_key, left_path->slots[left_level]);
5228 else
5229 btrfs_node_key_to_cpu(left_path->nodes[left_level],
5230 &left_key, left_path->slots[left_level]);
5231 if (right_level == 0)
5232 btrfs_item_key_to_cpu(right_path->nodes[right_level],
5233 &right_key, right_path->slots[right_level]);
5234 else
5235 btrfs_node_key_to_cpu(right_path->nodes[right_level],
5236 &right_key, right_path->slots[right_level]);
5237
5238 left_end_reached = right_end_reached = 0;
5239 advance_left = advance_right = 0;
5240
5241 while (1) {
5242 /*
5243 * We need to make sure the transaction does not get committed
5244 * while we do anything on commit roots. This means, we need to
5245 * join and leave transactions for every item that we process.
5246 */
5247 if (trans && btrfs_should_end_transaction(trans, left_root)) {
5248 btrfs_release_path(left_path);
5249 btrfs_release_path(right_path);
5250
5251 ret = btrfs_end_transaction(trans, left_root);
5252 trans = NULL;
5253 if (ret < 0)
5254 goto out;
5255 }
5256 /* now rejoin the transaction */
5257 if (!trans) {
5258 trans = btrfs_join_transaction(left_root);
5259 if (IS_ERR(trans)) {
5260 ret = PTR_ERR(trans);
5261 trans = NULL;
5262 goto out;
5263 }
5264
5265 spin_lock(&left_root->root_times_lock);
5266 ctransid = btrfs_root_ctransid(&left_root->root_item);
5267 spin_unlock(&left_root->root_times_lock);
5268 if (ctransid != left_start_ctransid)
5269 left_start_ctransid = 0;
5270
5271 spin_lock(&right_root->root_times_lock);
5272 ctransid = btrfs_root_ctransid(&right_root->root_item);
5273 spin_unlock(&right_root->root_times_lock);
5274 if (ctransid != right_start_ctransid)
5275 right_start_ctransid = 0;
5276
5277 if (!left_start_ctransid || !right_start_ctransid) {
5278 WARN(1, KERN_WARNING
5279 "btrfs: btrfs_compare_tree detected "
5280 "a change in one of the trees while "
5281 "iterating. This is probably a "
5282 "bug.\n");
5283 ret = -EIO;
5284 goto out;
5285 }
5286
5287 /*
5288 * the commit root may have changed, so start again
5289 * where we stopped
5290 */
5291 left_path->lowest_level = left_level;
5292 right_path->lowest_level = right_level;
5293 ret = btrfs_search_slot(NULL, left_root,
5294 &left_key, left_path, 0, 0);
5295 if (ret < 0)
5296 goto out;
5297 ret = btrfs_search_slot(NULL, right_root,
5298 &right_key, right_path, 0, 0);
5299 if (ret < 0)
5300 goto out;
5301 }
5302
5303 if (advance_left && !left_end_reached) {
5304 ret = tree_advance(left_root, left_path, &left_level,
5305 left_root_level,
5306 advance_left != ADVANCE_ONLY_NEXT,
5307 &left_key);
5308 if (ret < 0)
5309 left_end_reached = ADVANCE;
5310 advance_left = 0;
5311 }
5312 if (advance_right && !right_end_reached) {
5313 ret = tree_advance(right_root, right_path, &right_level,
5314 right_root_level,
5315 advance_right != ADVANCE_ONLY_NEXT,
5316 &right_key);
5317 if (ret < 0)
5318 right_end_reached = ADVANCE;
5319 advance_right = 0;
5320 }
5321
5322 if (left_end_reached && right_end_reached) {
5323 ret = 0;
5324 goto out;
5325 } else if (left_end_reached) {
5326 if (right_level == 0) {
5327 ret = changed_cb(left_root, right_root,
5328 left_path, right_path,
5329 &right_key,
5330 BTRFS_COMPARE_TREE_DELETED,
5331 ctx);
5332 if (ret < 0)
5333 goto out;
5334 }
5335 advance_right = ADVANCE;
5336 continue;
5337 } else if (right_end_reached) {
5338 if (left_level == 0) {
5339 ret = changed_cb(left_root, right_root,
5340 left_path, right_path,
5341 &left_key,
5342 BTRFS_COMPARE_TREE_NEW,
5343 ctx);
5344 if (ret < 0)
5345 goto out;
5346 }
5347 advance_left = ADVANCE;
5348 continue;
5349 }
5350
5351 if (left_level == 0 && right_level == 0) {
5352 cmp = btrfs_comp_cpu_keys(&left_key, &right_key);
5353 if (cmp < 0) {
5354 ret = changed_cb(left_root, right_root,
5355 left_path, right_path,
5356 &left_key,
5357 BTRFS_COMPARE_TREE_NEW,
5358 ctx);
5359 if (ret < 0)
5360 goto out;
5361 advance_left = ADVANCE;
5362 } else if (cmp > 0) {
5363 ret = changed_cb(left_root, right_root,
5364 left_path, right_path,
5365 &right_key,
5366 BTRFS_COMPARE_TREE_DELETED,
5367 ctx);
5368 if (ret < 0)
5369 goto out;
5370 advance_right = ADVANCE;
5371 } else {
5372 ret = tree_compare_item(left_root, left_path,
5373 right_path, tmp_buf);
5374 if (ret) {
5375 ret = changed_cb(left_root, right_root,
5376 left_path, right_path,
5377 &left_key,
5378 BTRFS_COMPARE_TREE_CHANGED,
5379 ctx);
5380 if (ret < 0)
5381 goto out;
5382 }
5383 advance_left = ADVANCE;
5384 advance_right = ADVANCE;
5385 }
5386 } else if (left_level == right_level) {
5387 cmp = btrfs_comp_cpu_keys(&left_key, &right_key);
5388 if (cmp < 0) {
5389 advance_left = ADVANCE;
5390 } else if (cmp > 0) {
5391 advance_right = ADVANCE;
5392 } else {
5393 left_blockptr = btrfs_node_blockptr(
5394 left_path->nodes[left_level],
5395 left_path->slots[left_level]);
5396 right_blockptr = btrfs_node_blockptr(
5397 right_path->nodes[right_level],
5398 right_path->slots[right_level]);
5399 if (left_blockptr == right_blockptr) {
5400 /*
5401 * As we're on a shared block, don't
5402 * allow to go deeper.
5403 */
5404 advance_left = ADVANCE_ONLY_NEXT;
5405 advance_right = ADVANCE_ONLY_NEXT;
5406 } else {
5407 advance_left = ADVANCE;
5408 advance_right = ADVANCE;
5409 }
5410 }
5411 } else if (left_level < right_level) {
5412 advance_right = ADVANCE;
5413 } else {
5414 advance_left = ADVANCE;
5415 }
5416 }
5417
5418out:
5419 btrfs_free_path(left_path);
5420 btrfs_free_path(right_path);
5421 kfree(tmp_buf);
5422
5423 if (trans) {
5424 if (!ret)
5425 ret = btrfs_end_transaction(trans, left_root);
5426 else
5427 btrfs_end_transaction(trans, left_root);
5428 }
5429
5430 return ret;
5431}
5432
5008/* 5433/*
5009 * this is similar to btrfs_next_leaf, but does not try to preserve 5434 * this is similar to btrfs_next_leaf, but does not try to preserve
5010 * and fixup the path. It looks for and returns the next key in the 5435 * and fixup the path. It looks for and returns the next key in the
diff --git a/fs/btrfs/ctree.h b/fs/btrfs/ctree.h
index d5f6d7458676..2fbbe738caed 100644
--- a/fs/btrfs/ctree.h
+++ b/fs/btrfs/ctree.h
@@ -2722,6 +2722,21 @@ int btrfs_search_forward(struct btrfs_root *root, struct btrfs_key *min_key,
2722 struct btrfs_key *max_key, 2722 struct btrfs_key *max_key,
2723 struct btrfs_path *path, int cache_only, 2723 struct btrfs_path *path, int cache_only,
2724 u64 min_trans); 2724 u64 min_trans);
2725enum btrfs_compare_tree_result {
2726 BTRFS_COMPARE_TREE_NEW,
2727 BTRFS_COMPARE_TREE_DELETED,
2728 BTRFS_COMPARE_TREE_CHANGED,
2729};
2730typedef int (*btrfs_changed_cb_t)(struct btrfs_root *left_root,
2731 struct btrfs_root *right_root,
2732 struct btrfs_path *left_path,
2733 struct btrfs_path *right_path,
2734 struct btrfs_key *key,
2735 enum btrfs_compare_tree_result result,
2736 void *ctx);
2737int btrfs_compare_trees(struct btrfs_root *left_root,
2738 struct btrfs_root *right_root,
2739 btrfs_changed_cb_t cb, void *ctx);
2725int btrfs_cow_block(struct btrfs_trans_handle *trans, 2740int btrfs_cow_block(struct btrfs_trans_handle *trans,
2726 struct btrfs_root *root, struct extent_buffer *buf, 2741 struct btrfs_root *root, struct extent_buffer *buf,
2727 struct extent_buffer *parent, int parent_slot, 2742 struct extent_buffer *parent, int parent_slot,