diff options
author | Qu Wenruo <wqu@suse.com> | 2020-06-24 18:02:55 +0200 |
---|---|---|
committer | Tom Rini <trini@konsulko.com> | 2020-09-07 20:57:27 -0400 |
commit | 75b0817babe733ecf8d503c3ecce371e9991ca76 (patch) | |
tree | 9d8fa004e9265b872cb2ce9df1e2f49c15bf1d87 /fs/btrfs/disk-io.c | |
parent | b1f0067aba5cc51540922e4187d5e8c8f77b1431 (diff) | |
download | u-boot-75b0817babe733ecf8d503c3ecce371e9991ca76.tar.gz u-boot-75b0817babe733ecf8d503c3ecce371e9991ca76.tar.xz u-boot-75b0817babe733ecf8d503c3ecce371e9991ca76.zip |
fs: btrfs: Crossport read_tree_block() from btrfs-progs
This is the one of the basic stone function for btrfs, which:
- Resolves the chunk mappings
- Reads data from disk
- Does various sanity check
With read_tree_block(), we can finally crossport needed btrfs btree
operations to U-Boot.
Signed-off-by: Qu Wenruo <wqu@suse.com>
Reviewed-by: Marek BehĂșn <marek.behun@nic.cz>
Diffstat (limited to 'fs/btrfs/disk-io.c')
-rw-r--r-- | fs/btrfs/disk-io.c | 348 |
1 files changed, 348 insertions, 0 deletions
diff --git a/fs/btrfs/disk-io.c b/fs/btrfs/disk-io.c index edebf741fd..65b4020ebe 100644 --- a/fs/btrfs/disk-io.c +++ b/fs/btrfs/disk-io.c @@ -7,8 +7,113 @@ #include "disk-io.h" #include "ctree.h" #include "btrfs.h" +#include "volumes.h" +#include "extent-io.h" #include "crypto/hash.h" +/* specified errno for check_tree_block */ +#define BTRFS_BAD_BYTENR (-1) +#define BTRFS_BAD_FSID (-2) +#define BTRFS_BAD_LEVEL (-3) +#define BTRFS_BAD_NRITEMS (-4) + +/* Calculate max possible nritems for a leaf/node */ +static u32 max_nritems(u8 level, u32 nodesize) +{ + + if (level == 0) + return ((nodesize - sizeof(struct btrfs_header)) / + sizeof(struct btrfs_item)); + return ((nodesize - sizeof(struct btrfs_header)) / + sizeof(struct btrfs_key_ptr)); +} + +static int check_tree_block(struct btrfs_fs_info *fs_info, + struct extent_buffer *buf) +{ + + struct btrfs_fs_devices *fs_devices = fs_info->fs_devices; + u32 nodesize = fs_info->nodesize; + bool fsid_match = false; + int ret = BTRFS_BAD_FSID; + + if (buf->start != btrfs_header_bytenr(buf)) + return BTRFS_BAD_BYTENR; + if (btrfs_header_level(buf) >= BTRFS_MAX_LEVEL) + return BTRFS_BAD_LEVEL; + if (btrfs_header_nritems(buf) > max_nritems(btrfs_header_level(buf), + nodesize)) + return BTRFS_BAD_NRITEMS; + + /* Only leaf can be empty */ + if (btrfs_header_nritems(buf) == 0 && + btrfs_header_level(buf) != 0) + return BTRFS_BAD_NRITEMS; + + while (fs_devices) { + /* + * Checking the incompat flag is only valid for the current + * fs. For seed devices it's forbidden to have their uuid + * changed so reading ->fsid in this case is fine + */ + if (fs_devices == fs_info->fs_devices && + btrfs_fs_incompat(fs_info, METADATA_UUID)) + fsid_match = !memcmp_extent_buffer(buf, + fs_devices->metadata_uuid, + btrfs_header_fsid(), + BTRFS_FSID_SIZE); + else + fsid_match = !memcmp_extent_buffer(buf, + fs_devices->fsid, + btrfs_header_fsid(), + BTRFS_FSID_SIZE); + + + if (fsid_match) { + ret = 0; + break; + } + fs_devices = fs_devices->seed; + } + return ret; +} + +static void print_tree_block_error(struct btrfs_fs_info *fs_info, + struct extent_buffer *eb, + int err) +{ + char fs_uuid[BTRFS_UUID_UNPARSED_SIZE] = {'\0'}; + char found_uuid[BTRFS_UUID_UNPARSED_SIZE] = {'\0'}; + u8 buf[BTRFS_UUID_SIZE]; + + if (!err) + return; + + fprintf(stderr, "bad tree block %llu, ", eb->start); + switch (err) { + case BTRFS_BAD_FSID: + read_extent_buffer(eb, buf, btrfs_header_fsid(), + BTRFS_UUID_SIZE); + uuid_unparse(buf, found_uuid); + uuid_unparse(fs_info->fs_devices->metadata_uuid, fs_uuid); + fprintf(stderr, "fsid mismatch, want=%s, have=%s\n", + fs_uuid, found_uuid); + break; + case BTRFS_BAD_BYTENR: + fprintf(stderr, "bytenr mismatch, want=%llu, have=%llu\n", + eb->start, btrfs_header_bytenr(eb)); + break; + case BTRFS_BAD_LEVEL: + fprintf(stderr, "bad level, %u > %d\n", + btrfs_header_level(eb), BTRFS_MAX_LEVEL); + break; + case BTRFS_BAD_NRITEMS: + fprintf(stderr, "invalid nr_items: %u\n", + btrfs_header_nritems(eb)); + break; + } +} + int btrfs_csum_data(u16 csum_type, const u8 *data, u8 *out, size_t len) { memset(out, 0, BTRFS_CSUM_SIZE); @@ -233,3 +338,246 @@ int btrfs_read_superblock(void) return 0; } + +static int __csum_tree_block_size(struct extent_buffer *buf, u16 csum_size, + int verify, int silent, u16 csum_type) +{ + u8 result[BTRFS_CSUM_SIZE]; + u32 len; + + len = buf->len - BTRFS_CSUM_SIZE; + btrfs_csum_data(csum_type, (u8 *)buf->data + BTRFS_CSUM_SIZE, + result, len); + + if (verify) { + if (memcmp_extent_buffer(buf, result, 0, csum_size)) { + /* FIXME: format */ + if (!silent) + printk("checksum verify failed on %llu found %08X wanted %08X\n", + (unsigned long long)buf->start, + result[0], + buf->data[0]); + return 1; + } + } else { + write_extent_buffer(buf, result, 0, csum_size); + } + return 0; +} + +int csum_tree_block_size(struct extent_buffer *buf, u16 csum_size, int verify, + u16 csum_type) +{ + return __csum_tree_block_size(buf, csum_size, verify, 0, csum_type); +} + +static int csum_tree_block(struct btrfs_fs_info *fs_info, + struct extent_buffer *buf, int verify) +{ + u16 csum_size = btrfs_super_csum_size(fs_info->super_copy); + u16 csum_type = btrfs_super_csum_type(fs_info->super_copy); + + return csum_tree_block_size(buf, csum_size, verify, csum_type); +} + +struct extent_buffer *btrfs_find_tree_block(struct btrfs_fs_info *fs_info, + u64 bytenr, u32 blocksize) +{ + return find_extent_buffer(&fs_info->extent_cache, + bytenr, blocksize); +} + +struct extent_buffer* btrfs_find_create_tree_block( + struct btrfs_fs_info *fs_info, u64 bytenr) +{ + return alloc_extent_buffer(fs_info, bytenr, fs_info->nodesize); +} + +static int verify_parent_transid(struct extent_io_tree *io_tree, + struct extent_buffer *eb, u64 parent_transid, + int ignore) +{ + int ret; + + if (!parent_transid || btrfs_header_generation(eb) == parent_transid) + return 0; + + if (extent_buffer_uptodate(eb) && + btrfs_header_generation(eb) == parent_transid) { + ret = 0; + goto out; + } + printk("parent transid verify failed on %llu wanted %llu found %llu\n", + (unsigned long long)eb->start, + (unsigned long long)parent_transid, + (unsigned long long)btrfs_header_generation(eb)); + if (ignore) { + eb->flags |= EXTENT_BAD_TRANSID; + printk("Ignoring transid failure\n"); + return 0; + } + + ret = 1; +out: + clear_extent_buffer_uptodate(eb); + return ret; + +} + +int btrfs_buffer_uptodate(struct extent_buffer *buf, u64 parent_transid) +{ + int ret; + + ret = extent_buffer_uptodate(buf); + if (!ret) + return ret; + + ret = verify_parent_transid(&buf->fs_info->extent_cache, buf, + parent_transid, 1); + return !ret; +} + +int btrfs_set_buffer_uptodate(struct extent_buffer *eb) +{ + return set_extent_buffer_uptodate(eb); +} + +int read_whole_eb(struct btrfs_fs_info *info, struct extent_buffer *eb, int mirror) +{ + unsigned long offset = 0; + struct btrfs_multi_bio *multi = NULL; + struct btrfs_device *device; + int ret = 0; + u64 read_len; + unsigned long bytes_left = eb->len; + + while (bytes_left) { + read_len = bytes_left; + device = NULL; + + ret = btrfs_map_block(info, READ, eb->start + offset, + &read_len, &multi, mirror, NULL); + if (ret) { + printk("Couldn't map the block %Lu\n", eb->start + offset); + kfree(multi); + return -EIO; + } + device = multi->stripes[0].dev; + + if (!device->desc || !device->part) { + kfree(multi); + return -EIO; + } + + if (read_len > bytes_left) + read_len = bytes_left; + + ret = read_extent_from_disk(device->desc, device->part, + multi->stripes[0].physical, eb, + offset, read_len); + kfree(multi); + multi = NULL; + + if (ret) + return -EIO; + offset += read_len; + bytes_left -= read_len; + } + return 0; +} + +struct extent_buffer* read_tree_block(struct btrfs_fs_info *fs_info, u64 bytenr, + u64 parent_transid) +{ + int ret; + struct extent_buffer *eb; + u64 best_transid = 0; + u32 sectorsize = fs_info->sectorsize; + int mirror_num = 1; + int good_mirror = 0; + int candidate_mirror = 0; + int num_copies; + int ignore = 0; + + /* + * Don't even try to create tree block for unaligned tree block + * bytenr. + * Such unaligned tree block will free overlapping extent buffer, + * causing use-after-free bugs for fuzzed images. + */ + if (bytenr < sectorsize || !IS_ALIGNED(bytenr, sectorsize)) { + error("tree block bytenr %llu is not aligned to sectorsize %u", + bytenr, sectorsize); + return ERR_PTR(-EIO); + } + + eb = btrfs_find_create_tree_block(fs_info, bytenr); + if (!eb) + return ERR_PTR(-ENOMEM); + + if (btrfs_buffer_uptodate(eb, parent_transid)) + return eb; + + num_copies = btrfs_num_copies(fs_info, eb->start, eb->len); + while (1) { + ret = read_whole_eb(fs_info, eb, mirror_num); + if (ret == 0 && csum_tree_block(fs_info, eb, 1) == 0 && + check_tree_block(fs_info, eb) == 0 && + verify_parent_transid(&fs_info->extent_cache, eb, + parent_transid, ignore) == 0) { + /* + * check_tree_block() is less strict to allow btrfs + * check to get raw eb with bad key order and fix it. + * But we still need to try to get a good copy if + * possible, or bad key order can go into tools like + * btrfs ins dump-tree. + */ + if (btrfs_header_level(eb)) + ret = btrfs_check_node(fs_info, NULL, eb); + else + ret = btrfs_check_leaf(fs_info, NULL, eb); + if (!ret || candidate_mirror == mirror_num) { + btrfs_set_buffer_uptodate(eb); + return eb; + } + if (candidate_mirror <= 0) + candidate_mirror = mirror_num; + } + if (ignore) { + if (candidate_mirror > 0) { + mirror_num = candidate_mirror; + continue; + } + if (check_tree_block(fs_info, eb)) + print_tree_block_error(fs_info, eb, + check_tree_block(fs_info, eb)); + else + fprintf(stderr, "Csum didn't match\n"); + ret = -EIO; + break; + } + if (num_copies == 1) { + ignore = 1; + continue; + } + if (btrfs_header_generation(eb) > best_transid) { + best_transid = btrfs_header_generation(eb); + good_mirror = mirror_num; + } + mirror_num++; + if (mirror_num > num_copies) { + if (candidate_mirror > 0) + mirror_num = candidate_mirror; + else + mirror_num = good_mirror; + ignore = 1; + continue; + } + } + /* + * We failed to read this tree block, it be should deleted right now + * to avoid stale cache populate the cache. + */ + free_extent_buffer(eb); + return ERR_PTR(ret); +} |