diff options
-rw-r--r-- | convert/main.c | 180 |
1 files changed, 180 insertions, 0 deletions
diff --git a/convert/main.c b/convert/main.c index 69279506..d4cda478 100644 --- a/convert/main.c +++ b/convert/main.c @@ -88,6 +88,7 @@ #include <fcntl.h> #include <unistd.h> #include <getopt.h> +#include <stdbool.h> #include "ctree.h" #include "disk-io.h" @@ -1543,6 +1544,185 @@ static int read_reserved_ranges(struct btrfs_root *root, u64 ino, return ret; } +static bool is_subset_of_reserved_ranges(u64 start, u64 len) +{ + int i; + bool ret = false; + + for (i = 0; i < ARRAY_SIZE(btrfs_reserved_ranges); i++) { + struct simple_range *range = &btrfs_reserved_ranges[i]; + + if (start >= range->start && start + len <= range_end(range)) { + ret = true; + break; + } + } + return ret; +} + +static bool is_chunk_direct_mapped(struct btrfs_fs_info *fs_info, u64 start) +{ + struct cache_extent *ce; + struct map_lookup *map; + bool ret = false; + + ce = search_cache_extent(&fs_info->mapping_tree.cache_tree, start); + if (!ce) + goto out; + if (ce->start > start || ce->start + ce->size < start) + goto out; + + map = container_of(ce, struct map_lookup, ce); + + /* Not SINGLE chunk */ + if (map->num_stripes != 1) + goto out; + + /* Chunk's logical doesn't match with phisical, not 1:1 mapped */ + if (map->ce.start != map->stripes[0].physical) + goto out; + ret = true; +out: + return ret; +} + +/* + * Iterate all file extents of the convert image. + * + * All file extents except ones in btrfs_reserved_ranges must be mapped 1:1 + * on disk. (Means thier file_offset must match their on disk bytenr) + * + * File extents in reserved ranges can be relocated to other place, and in + * that case we will read them out for later use. + */ +static int check_image_file_extents(struct btrfs_root *image_root, u64 ino, + u64 total_size, char *reserved_ranges[]) +{ + struct btrfs_key key; + struct btrfs_path path; + struct btrfs_fs_info *fs_info = image_root->fs_info; + u64 checked_bytes = 0; + int ret; + + key.objectid = ino; + key.offset = 0; + key.type = BTRFS_EXTENT_DATA_KEY; + + btrfs_init_path(&path); + ret = btrfs_search_slot(NULL, image_root, &key, &path, 0, 0); + /* + * It's possible that some fs doesn't store any (including sb) + * data into 0~1M range, and NO_HOLES is enabled. + * + * So we only need to check if ret < 0 + */ + if (ret < 0) { + error("failed to iterate file extents at offset 0: %s", + strerror(-ret)); + btrfs_release_path(&path); + return ret; + } + + /* Loop from the first file extents */ + while (1) { + struct btrfs_file_extent_item *fi; + struct extent_buffer *leaf = path.nodes[0]; + u64 disk_bytenr; + u64 file_offset; + u64 ram_bytes; + int slot = path.slots[0]; + + if (slot >= btrfs_header_nritems(leaf)) + goto next; + btrfs_item_key_to_cpu(leaf, &key, slot); + + /* + * Iteration is done, exit normally, we have extra check out of + * the loop + */ + if (key.objectid != ino || key.type != BTRFS_EXTENT_DATA_KEY) { + ret = 0; + break; + } + file_offset = key.offset; + fi = btrfs_item_ptr(leaf, slot, struct btrfs_file_extent_item); + if (btrfs_file_extent_type(leaf, fi) != BTRFS_FILE_EXTENT_REG) { + ret = -EINVAL; + error( + "ino %llu offset %llu doesn't have a regular file extent", + ino, file_offset); + break; + } + if (btrfs_file_extent_compression(leaf, fi) || + btrfs_file_extent_encryption(leaf, fi) || + btrfs_file_extent_other_encoding(leaf, fi)) { + ret = -EINVAL; + error( + "ino %llu offset %llu doesn't have a plain file extent", + ino, file_offset); + break; + } + + disk_bytenr = btrfs_file_extent_disk_bytenr(leaf, fi); + ram_bytes = btrfs_file_extent_ram_bytes(leaf, fi); + + checked_bytes += ram_bytes; + /* Skip hole */ + if (disk_bytenr == 0) + goto next; + + /* + * Most file extents must be 1:1 mapped, which means 2 things: + * 1) File extent file offset == disk_bytenr + * 2) That data chunk's logical == chunk's physical + * + * So file extent's file offset == physical position on disk. + * + * And after rolling back btrfs reserved range, other part + * remains what old fs used to be. + */ + if (file_offset != disk_bytenr || + !is_chunk_direct_mapped(fs_info, disk_bytenr)) { + /* + * Only file extent in btrfs reserved ranges are + * allowed to be non-1:1 mapped + */ + if (!is_subset_of_reserved_ranges(file_offset, + ram_bytes)) { + ret = -EINVAL; + error( + "ino %llu offset %llu file extent should not be relocated", + ino, file_offset); + break; + } + } +next: + ret = btrfs_next_item(image_root, &path); + if (ret) { + if (ret > 0) + ret = 0; + break; + } + } + btrfs_release_path(&path); + /* + * For HOLES mode (without NO_HOLES), we must ensure file extents + * cover the whole range of the image + */ + if (!ret && !btrfs_fs_incompat(fs_info, NO_HOLES)) { + if (checked_bytes != total_size) { + ret = -EINVAL; + error("inode %llu has some file extents not checked", + ino); + } + } + + /* So far so good, read old data located in btrfs reserved ranges */ + ret = read_reserved_ranges(image_root, ino, total_size, + reserved_ranges); + return ret; +} + static int do_rollback(const char *devname) { int fd = -1; |