diff options
Diffstat (limited to 'fs')
-rw-r--r-- | fs/btrfs/ctree.c | 425 | ||||
-rw-r--r-- | fs/btrfs/ctree.h | 15 |
2 files changed, 440 insertions, 0 deletions
diff --git a/fs/btrfs/ctree.c b/fs/btrfs/ctree.c index c82a9e4a953..4c10fd19d48 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 | ||
5008 | static 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 | |||
5018 | static 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 | */ | ||
5049 | static 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 | |||
5074 | static 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 | */ | ||
5116 | int 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 | |||
5418 | out: | ||
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 d5f6d745867..2fbbe738cae 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); |
2725 | enum btrfs_compare_tree_result { | ||
2726 | BTRFS_COMPARE_TREE_NEW, | ||
2727 | BTRFS_COMPARE_TREE_DELETED, | ||
2728 | BTRFS_COMPARE_TREE_CHANGED, | ||
2729 | }; | ||
2730 | typedef 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); | ||
2737 | int btrfs_compare_trees(struct btrfs_root *left_root, | ||
2738 | struct btrfs_root *right_root, | ||
2739 | btrfs_changed_cb_t cb, void *ctx); | ||
2725 | int btrfs_cow_block(struct btrfs_trans_handle *trans, | 2740 | int 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, |