1 /*
2  * Copyright (C) 2015-2016 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 #include <assert.h>
18 #include <inttypes.h>
19 #include <lk/compiler.h>
20 #include <stdbool.h>
21 #include <stdint.h>
22 #include <stdio.h>
23 #include <string.h>
24 
25 #include "array.h"
26 #include "block_allocator.h"
27 #include "block_set.h"
28 #include "debug.h"
29 #include "transaction.h"
30 
31 static bool print_check_errors;
32 
33 /**
34  * block_range_init_from_path
35  * @range:      range object to initialize.
36  * @path:       Block tree path.
37  *
38  * Helper function to initialize a block_range from a tree entry.
39  */
block_range_init_from_path(struct block_range * range,struct block_tree_path * path)40 static void block_range_init_from_path(struct block_range* range,
41                                        struct block_tree_path* path) {
42     if (!path->count) {
43         block_range_clear(range);
44         return;
45     }
46     range->start = block_tree_path_get_key(path);
47     range->end = block_tree_path_get_data(path);
48     assert(!range->end == !range->start);
49 }
50 
51 /**
52  * block_range_extend - Extend range if possible
53  * @range:      range object to initialize.
54  * @add_range:  Range to add
55  *
56  * Return: %true if @add_range was adjacent to @range, %false otherwise.
57  */
block_range_extend(struct block_range * range,struct block_range add_range)58 static bool block_range_extend(struct block_range* range,
59                                struct block_range add_range) {
60     pr_write("%" PRIu64 "-%" PRIu64 ", %" PRIu64 "-%" PRIu64 "\n", range->start,
61              range->end - 1, add_range.start, add_range.end - 1);
62     assert(!block_range_empty(add_range));
63     assert(!block_range_overlap(*range, add_range));
64     if (block_range_empty(*range)) {
65         pr_write("failed, *range is empty\n");
66         return false;
67     }
68     if (add_range.end == range->start) {
69         range->start = add_range.start;
70     } else if (add_range.start == range->end) {
71         range->end = add_range.end;
72     } else {
73         pr_write("failed\n");
74         return false;
75     }
76 
77     pr_write("now %" PRIu64 "-%" PRIu64 "\n", range->start, range->end - 1);
78 
79     return true;
80 }
81 
82 /**
83  * block_range_shrink - Shrink range if possible
84  * @range:          range object to initialize.
85  * @remove_range:   Range to remove
86  *
87  * Return: %true if @remove_range was at either end of @range, %false otherwise.
88  */
block_range_shrink(struct block_range * range,struct block_range remove_range)89 static bool block_range_shrink(struct block_range* range,
90                                struct block_range remove_range) {
91     pr_write("%" PRIu64 "-%" PRIu64 ", %" PRIu64 "-%" PRIu64 "\n", range->start,
92              range->end - 1, remove_range.start, remove_range.end - 1);
93     assert(block_range_is_sub_range(*range, remove_range));
94     if (remove_range.start == range->start) {
95         assert(!block_range_empty(*range));
96         range->start = remove_range.end;
97     } else if (remove_range.end == range->end) {
98         assert(!block_range_empty(*range));
99         range->end = remove_range.start;
100     } else {
101         pr_write("%s: failed\n", __func__);
102         return false;
103     }
104 
105     pr_write("now %" PRIu64 "-%" PRIu64 "\n", range->start, range->end - 1);
106 
107     return true;
108 }
109 
110 /**
111  * block_set_print_ranges - Print block-set range
112  * @tr:         Transaction object.
113  * @range:      Range to print.
114  */
block_set_print_range(struct block_range range,int * split_line)115 static void block_set_print_range(struct block_range range, int* split_line) {
116     if (*split_line >= 8) {
117         *split_line = 0;
118         printf("\n");
119     }
120     (*split_line)++;
121     if (range.start == range.end - 1) {
122         printf("    %3" PRIu64 "      ", range.start);
123     } else {
124         printf("    %3" PRIu64 " - %3" PRIu64 "", range.start, range.end - 1);
125     }
126     (*split_line)++;
127 }
128 
129 /**
130  * block_set_print_ranges - Print block-set ranges
131  * @tr:         Transaction object.
132  * @set:        Block-set object.
133  */
block_set_print_ranges(struct transaction * tr,struct block_set * set)134 static void block_set_print_ranges(struct transaction* tr,
135                                    struct block_set* set) {
136     struct block_tree_path path;
137     struct block_range range;
138     int split_line = 0;
139 
140     printf("set:\n");
141 
142     block_tree_walk(tr, &set->block_tree, 0, true, &path);
143     block_range_init_from_path(&range, &path);
144     while (!block_range_empty(range)) {
145         block_set_print_range(range, &split_line);
146         block_tree_path_next(&path);
147         block_range_init_from_path(&range, &path);
148     }
149     printf("\n");
150 
151     if (!block_range_empty(set->initial_range)) {
152         printf("initial_range: ");
153         split_line = 0;
154         block_set_print_range(set->initial_range, &split_line);
155         printf("\n");
156     }
157 }
158 
159 /**
160  * block_set_print - Print block-set ranges and the tree that stored them
161  * @tr:         Transaction object.
162  * @set:        Block-set object.
163  */
block_set_print(struct transaction * tr,struct block_set * set)164 void block_set_print(struct transaction* tr, struct block_set* set) {
165     printf("block_tree:\n");
166     block_tree_print(tr, &set->block_tree);
167     block_set_print_ranges(tr, set);
168 }
169 
170 /**
171  * block_set_check_ranges - Check that ranges in block-set are valid
172  * @tr:         Transaction object.
173  * @set:        Block-set object.
174  *
175  * Check that ranges in @set are valid, sorted, non-overlapping, non-adjacent
176  * and only contain blocks that are valid for @tr.
177  *
178  * Return: %true if the ranges in @set are valid, %false otherwise.
179  */
block_set_check_ranges(struct transaction * tr,struct block_set * set)180 static bool block_set_check_ranges(struct transaction* tr,
181                                    struct block_set* set) {
182     struct block_tree_path path;
183     data_block_t min = tr->fs->min_block_num;
184     data_block_t max = tr->fs->dev->block_count;
185     struct block_range range;
186 
187     block_tree_walk(tr, &set->block_tree, 0, true, &path);
188     block_range_init_from_path(&range, &path);
189     while (!block_range_empty(range)) {
190         if (range.start < min) {
191             pr_err("bad range start %" PRIu64 " < %" PRIu64 "\n", range.start,
192                    min);
193             return false;
194         }
195         if (range.end <= range.start) {
196             pr_err("bad range end %" PRIu64 " <= start %" PRIu64 "\n",
197                    range.end, range.start);
198             return false;
199         }
200         if (range.end > max) {
201             pr_err("bad range end %" PRIu64 " > max %" PRIu64 "\n", range.end,
202                    max);
203             return false;
204         }
205         min = range.end + 1;
206         block_tree_path_next(&path);
207         block_range_init_from_path(&range, &path);
208     }
209 
210     return true;
211 }
212 
213 /**
214  * block_set_check - Check block-set tree and ranges
215  * @tr:         Transaction object.
216  * @set:        Block-set object.
217  *
218  * Return: %true if the tree and ranges in @set are valid, %false otherwise.
219  */
block_set_check(struct transaction * tr,struct block_set * set)220 bool block_set_check(struct transaction* tr, struct block_set* set) {
221     bool valid;
222 
223     valid = block_tree_check(tr, &set->block_tree);
224     if (!valid) {
225         return false;
226     }
227     valid = block_set_check_ranges(tr, set);
228     if (!valid && print_check_errors) {
229         printf("%s: invalid block_set:\n", __func__);
230         block_set_print(tr, set);
231     }
232     return valid;
233 }
234 
235 /**
236  * block_set_find_next_block - find a block in or not in set
237  * @tr:         Transaction object.
238  * @set:        Block-set object.
239  * @min_block:  Block number to start search at.
240  * @in_set:     If %true look for a block in @set. If %false, look for a block
241  *              not in @set.
242  *
243  * Return: First block in or not in @set >= @min_block, or 0 if no match is
244  * is found.
245  */
block_set_find_next_block(struct transaction * tr,struct block_set * set,data_block_t min_block,bool in_set)246 data_block_t block_set_find_next_block(struct transaction* tr,
247                                        struct block_set* set,
248                                        data_block_t min_block,
249                                        bool in_set) {
250     struct block_tree_path path;
251     struct block_range range;
252 
253     block_tree_walk(tr, &set->block_tree, min_block, true, &path);
254     block_range_init_from_path(&range, &path);
255     if (!block_range_empty(range) && range.end <= min_block) {
256         block_tree_path_next(&path);
257         block_range_init_from_path(&range, &path);
258     }
259     if (block_range_empty(range)) {
260         range = set->initial_range;
261         if (range.end <= min_block) {
262             block_range_clear(&range);
263         }
264     }
265 
266     pr_read("set at %" PRIu64 ", %" PRIu64 " in_set %d, [%" PRIu64 "-%" PRIu64
267             "]\n",
268             block_mac_to_block(tr, &set->block_tree.root), min_block, in_set,
269             range.start, range.end - 1);
270 
271     /* The block tree walk should not find an empty range that is not 0, 0 */
272     assert(!block_range_empty(range) || !range.start);
273 
274     if (block_in_range(range, min_block) == in_set) {
275         pr_read("%" PRIu64 " in_set %d, return min_block, %" PRIu64 "\n",
276                 min_block, in_set, min_block);
277         return min_block;
278     } else {
279         if (!in_set) {
280             assert(!block_range_empty(range));
281             pr_read("%" PRIu64 " in_set %d, return range.end, %" PRIu64 "\n",
282                     min_block, in_set, range.end);
283             return range.end;
284         } else {
285             pr_read("%" PRIu64 " in_set %d, return range.start, %" PRIu64 "\n",
286                     min_block, in_set, range.start);
287             return range.start;
288         }
289     }
290 }
291 
292 /**
293  * block_set_find_next_range - find a range in set
294  * @tr:         Transaction object.
295  * @set:        Block-set object.
296  * @min_block:  Block number to start search at.
297  *
298  * Return: First block range in @set >= @min_block, or and empty range if no
299  * match is is found. If the matching range in @set starts before @min_block,
300  * the returned range will start at @min_block, not at start of the range in
301  * @set.
302  */
block_set_find_next_range(struct transaction * tr,struct block_set * set,data_block_t min_block)303 struct block_range block_set_find_next_range(struct transaction* tr,
304                                              struct block_set* set,
305                                              data_block_t min_block) {
306     struct block_range range;
307     range.start = block_set_find_next_block(tr, set, min_block, true);
308     if (range.start) {
309         range.end = block_set_find_next_block(tr, set, range.start, false);
310     } else {
311         range.end = 0;
312     }
313     return range;
314 }
315 
316 /**
317  * block_set_block_in_set - Check if block is in set
318  * @tr:         Transaction object.
319  * @set:        Block-set object.
320  * @block:      Block number.
321  *
322  * Return: %true if @block is in @set, %false if @block is not on set/
323  */
block_set_block_in_set(struct transaction * tr,struct block_set * set,data_block_t block)324 bool block_set_block_in_set(struct transaction* tr,
325                             struct block_set* set,
326                             data_block_t block) {
327     return block_set_find_next_block(tr, set, block, true) == block;
328 }
329 
330 /**
331  * block_set_range_in_set - Check if block range is in set
332  * @tr:         Transaction object.
333  * @set:        Block-set object.
334  * @range:      Block range to check.
335  *
336  * Return: %true if all of @range is in @set, %false if a block in @range is not
337  * in @set.
338  */
block_set_range_in_set(struct transaction * tr,struct block_set * set,struct block_range range)339 bool block_set_range_in_set(struct transaction* tr,
340                             struct block_set* set,
341                             struct block_range range) {
342     return block_set_find_next_block(tr, set, range.start, true) ==
343                    range.start &&
344            block_set_find_next_block(tr, set, range.start, false) >= range.end;
345 }
346 
347 /**
348  * block_set_range_in_set - Check if block range is not set
349  * @tr:         Transaction object.
350  * @set:        Block-set object.
351  * @range:      Block range to check.
352  *
353  * Return: %true if all of @range is not in @set, %false if a block in @range is
354  * in @set.
355  */
block_set_range_not_in_set(struct transaction * tr,struct block_set * set,struct block_range range)356 bool block_set_range_not_in_set(struct transaction* tr,
357                                 struct block_set* set,
358                                 struct block_range range) {
359     data_block_t block = block_set_find_next_block(tr, set, range.start, true);
360     return !block || block >= range.end;
361 }
362 
363 /**
364  * block_set_overlap - Check if block sets have any overlap
365  * @tr:         Transaction object.
366  * @set_a:      Block-set object.
367  * @set_b:      Block-set object.
368  *
369  * Return: %true a block is in both @set_s and @set_b, %false if no such block
370  * exists.
371  */
block_set_overlap(struct transaction * tr,struct block_set * set_a,struct block_set * set_b)372 bool block_set_overlap(struct transaction* tr,
373                        struct block_set* set_a,
374                        struct block_set* set_b) {
375     struct block_range range_a;
376     struct block_range range_b = BLOCK_RANGE_INITIAL_VALUE(range_b);
377 
378     while (true) {
379         range_a = block_set_find_next_range(tr, set_a, range_b.start);
380         if (block_range_empty(range_a)) {
381             return false; /* No overlap as there a no more ranges in set_a */
382         }
383         if (block_in_range(range_b, range_a.start)) {
384             return true;
385         }
386 
387         /* range_a must start after range_b except first iteration */
388         assert(range_a.start >= range_b.start);
389 
390         range_b = block_set_find_next_range(tr, set_b, range_a.start);
391         if (block_range_empty(range_b)) {
392             return false; /* No overlap as there a no more ranges in set_b */
393         }
394         if (block_in_range(range_a, range_b.start)) {
395             return true;
396         }
397 
398         assert(range_b.start > range_a.start);
399     }
400 }
401 
402 /**
403  * block_set_add_range - Add a range to block set
404  * @tr:         Transaction object.
405  * @set:        Block-set object.
406  * @range:      Block range to add.
407  *
408  * Add @range to block set b+tree either by extending an existing range in the
409  * tree, or by adding a new range to the tree. If an existing tree entry is
410  * extended check if the next entry can be merged.
411  */
block_set_add_range(struct transaction * tr,struct block_set * set,struct block_range range)412 void block_set_add_range(struct transaction* tr,
413                          struct block_set* set,
414                          struct block_range range) {
415     struct block_range tree_range;
416     struct block_range new_tree_range;
417     bool extended;
418     struct block_tree_path path;
419     bool extend_left = false;
420     bool merge;
421 
422     assert(!block_range_empty(range));
423     assert(block_range_empty(set->initial_range));
424 
425     if (tr->failed) {
426         pr_warn("transaction failed, ignore\n");
427         return;
428     }
429 
430     pr_write("set %" PRIu64 ", add %" PRIu64 "-%" PRIu64 ", updating %d\n",
431              block_mac_to_block(tr, &set->block_tree.root), range.start,
432              range.end - 1, set->updating);
433 
434     assert(!set->updating);
435     set->updating = true;
436 
437     full_assert(block_set_check(tr, set));
438     assert(block_set_range_not_in_set(tr, set, range));
439 
440     block_tree_walk(tr, &set->block_tree, range.start - 1, true, &path);
441     block_range_init_from_path(&tree_range, &path);
442 
443     if (!block_range_empty(tree_range) && tree_range.end < range.start) {
444         block_tree_path_next(&path);
445         block_range_init_from_path(&tree_range, &path);
446         if (tree_range.start == range.end) {
447             extend_left = true;
448         } else {
449             /* rewind */
450             block_tree_walk(tr, &set->block_tree, range.start - 1, true, &path);
451             block_range_init_from_path(&tree_range, &path);
452         }
453     }
454     if (tr->failed) {
455         pr_warn("transaction failed, abort\n");
456         return;
457     }
458 
459     assert(!tree_range.end || !block_range_overlap(tree_range, range));
460     new_tree_range = tree_range;
461     extended = block_range_extend(&new_tree_range, range);
462     if (!extended) {
463         assert(!extend_left);
464         /* TODO: use path for insert point */
465         block_tree_insert(tr, &set->block_tree, range.start, range.end);
466     } else {
467         block_tree_path_next(&path);
468         if (tr->failed) {
469             pr_warn("transaction failed, abort\n");
470             return;
471         }
472         merge = block_tree_path_get_key(&path) == new_tree_range.end;
473         if (merge) {
474             assert(block_tree_path_get_data(&path) > new_tree_range.end);
475             new_tree_range.end = block_tree_path_get_data(&path);
476         }
477         /* TODO: use path? */
478         block_tree_update(tr, &set->block_tree, tree_range.start,
479                           tree_range.end, new_tree_range.start,
480                           new_tree_range.end);
481         if (tr->failed) {
482             pr_warn("transaction failed, abort\n");
483             return;
484         }
485         if (merge) {
486             /* set has overlapping ranges at this point, TODO: check that set is
487              * readable in this state */
488             block_tree_remove(tr, &set->block_tree,
489                               block_tree_path_get_key(&path),
490                               block_tree_path_get_data(&path));
491         }
492     }
493 
494     if (tr->failed) {
495         pr_warn("transaction failed, abort\n");
496         return;
497     }
498 
499     assert(set->updating);
500     set->updating = false;
501 
502     full_assert(block_set_check(tr, set));
503     pr_write("set %" PRIu64 ", add %" PRIu64 "-%" PRIu64 " done\n",
504              block_mac_to_block(tr, &set->block_tree.root), range.start,
505              range.end - 1);
506 }
507 
508 /**
509  * block_set_remove_range - Remove a range from block set
510  * @tr:         Transaction object.
511  * @set:        Block-set object.
512  * @range:      Block range to remove.
513  *
514  * Remove @range from block set b+tree either by shrinking an existing range
515  * in the tree, or by splitting an existing range in the tree.
516  */
block_set_remove_range(struct transaction * tr,struct block_set * set,struct block_range range)517 void block_set_remove_range(struct transaction* tr,
518                             struct block_set* set,
519                             struct block_range range) {
520     struct block_tree_path path;
521     struct block_range tree_range;
522     struct block_range new_tree_range;
523     bool shrunk;
524 
525     assert(!block_range_empty(range));
526     assert(block_range_empty(set->initial_range));
527 
528     if (tr->failed) {
529         pr_warn("transaction failed, ignore\n");
530         return;
531     }
532 
533     pr_write("set %" PRIu64 ", remove %" PRIu64 "-%" PRIu64 ", updating %d\n",
534              block_mac_to_block(tr, &set->block_tree.root), range.start,
535              range.end - 1, set->updating);
536 
537     assert(block_set_range_in_set(tr, set, range) || tr->failed);
538 
539     assert(!set->updating);
540     set->updating = true;
541 
542     full_assert(block_set_check(tr, set));
543 
544     block_tree_walk(tr, &set->block_tree, range.start, true, &path);
545     block_range_init_from_path(&tree_range, &path);
546 
547     if (tr->failed) {
548         pr_warn("transaction failed, abort\n");
549         return;
550     }
551 
552     assert(path.count > 0);
553 
554     assert(block_range_is_sub_range(tree_range, range));
555     new_tree_range = tree_range;
556     shrunk = block_range_shrink(&new_tree_range, range);
557     if (!shrunk) {
558         block_tree_insert(tr, &set->block_tree, range.end, tree_range.end);
559         if (tr->failed) {
560             pr_warn("transaction failed, abort\n");
561             return;
562         }
563         new_tree_range.end = range.start;
564     }
565     if (block_range_empty(new_tree_range)) {
566         block_tree_remove(tr, &set->block_tree, tree_range.start,
567                           tree_range.end);
568     } else {
569         /* TODO: use path? */
570         block_tree_update(tr, &set->block_tree, tree_range.start,
571                           tree_range.end, new_tree_range.start,
572                           new_tree_range.end);
573     }
574 
575     if (tr->failed) {
576         pr_warn("transaction failed, abort\n");
577         return;
578     }
579 
580     assert(set->updating);
581     set->updating = false;
582 
583     full_assert(block_set_check(tr, set));
584 
585     pr_write("set %" PRIu64 ", remove %" PRIu64 "-%" PRIu64 " done\n",
586              block_mac_to_block(tr, &set->block_tree.root), range.start,
587              range.end - 1);
588 }
589 
590 /**
591  * block_set_add_block - Add block to block set
592  * @tr:         Transaction object.
593  * @set:        Block-set object.
594  * @block:      Block to add.
595  */
block_set_add_block(struct transaction * tr,struct block_set * set,data_block_t block)596 void block_set_add_block(struct transaction* tr,
597                          struct block_set* set,
598                          data_block_t block) {
599     struct block_range range;
600 
601     block_range_init_single(&range, block);
602     block_set_add_range(tr, set, range);
603 }
604 
605 /**
606  * block_set_remove_block - Remove block from block set
607  * @tr:         Transaction object.
608  * @set:        Block-set object.
609  * @block:      Block to remove.
610  */
block_set_remove_block(struct transaction * tr,struct block_set * set,data_block_t block)611 void block_set_remove_block(struct transaction* tr,
612                             struct block_set* set,
613                             data_block_t block) {
614     struct block_range range;
615 
616     block_range_init_single(&range, block);
617     block_set_remove_range(tr, set, range);
618 }
619 
620 /**
621  * block_set_init - Initialize block set
622  * @fs:         File system state object.
623  * @set:        Block-set object.
624  *
625  * Clear block set, get block_num_size and mac_size from @fs and intialize
626  * tree.
627  */
block_set_init(struct fs * fs,struct block_set * set)628 void block_set_init(struct fs* fs, struct block_set* set) {
629     assert(fs);
630     assert(fs->dev);
631 
632     memset(set, 0, sizeof(*set));
633     block_tree_init(&set->block_tree, fs->dev->block_size, fs->block_num_size,
634                     fs->block_num_size + fs->mac_size, fs->block_num_size);
635 }
636 
637 /**
638  * block_set_add_initial_range - Add a range to an empty block set without
639  * updating tree
640  * @set:        Block-set object.
641  * @range:      Block range to add.
642  *
643  */
block_set_add_initial_range(struct block_set * set,struct block_range range)644 void block_set_add_initial_range(struct block_set* set,
645                                  struct block_range range) {
646     assert(block_range_empty(set->initial_range));
647     set->initial_range = range;
648 }
649 
650 /**
651  * block_set_copy - Initialize a writable copy of a copy-on-write block set
652  * @tr:         Transaction object.
653  * @dest:       New writable block-set object.
654  * @src:        Read-only block-set object.
655  */
block_set_copy(struct transaction * tr,struct block_set * dest,const struct block_set * src)656 void block_set_copy(struct transaction* tr,
657                     struct block_set* dest,
658                     const struct block_set* src) {
659     assert(src->block_tree.copy_on_write);
660     assert(!src->block_tree.allow_copy_on_write);
661 
662     block_set_init(tr->fs, dest);
663     dest->block_tree.copy_on_write = true;
664     dest->block_tree.allow_copy_on_write = true;
665     dest->block_tree.root = src->block_tree.root;
666     if (!block_mac_valid(tr, &dest->block_tree.root)) {
667         /* special case for newly created empty fs */
668         assert(!block_range_empty(src->initial_range));
669         block_set_add_range(tr, dest, src->initial_range);
670     } else {
671         assert(block_range_empty(src->initial_range));
672     }
673 }
674 
675 /**
676  * block_set_copy_ro_fs - Initialize a read-only copy of a block set
677  * @fs:         File-system that contains @src.
678  * @dest:       Output read-only copy of block-set object.
679  * @src:        Read-only block-set object.
680  */
block_set_copy_ro_fs(struct fs * fs,struct block_set * dest,const struct block_set * src)681 void block_set_copy_ro_fs(struct fs* fs,
682                           struct block_set* dest,
683                           const struct block_set* src) {
684     block_set_init(fs, dest);
685     dest->block_tree.root = src->block_tree.root;
686     if (!block_range_empty(src->initial_range)) {
687         block_set_add_initial_range(dest, src->initial_range);
688     }
689 }
690 
691 /**
692  * block_set_copy_ro - Initialize a read-only copy of a block set
693  * @tr:         Transaction object.
694  * @dest:       Output read-only copy of block-set object.
695  * @src:        Read-only block-set object.
696  */
block_set_copy_ro(struct transaction * tr,struct block_set * dest,const struct block_set * src)697 void block_set_copy_ro(struct transaction* tr,
698                        struct block_set* dest,
699                        const struct block_set* src) {
700     block_set_copy_ro_fs(tr->fs, dest, src);
701 }
702