This command allows to inspect raw blocks. It also shows information about where specified block is read from. --- Android.mk | 3 +- Makefile | 3 +- cmds/commands.h | 1 + cmds/inspect-dump-raw.c | 343 ++++++++++++++++++++++++++++++++++++++++ cmds/inspect.c | 1 + 5 files changed, 349 insertions(+), 2 deletions(-) create mode 100644 cmds/inspect-dump-raw.c
diff --git a/Android.mk b/Android.mk index a45e87aa..40873dd5 100644 --- a/Android.mk +++ b/Android.mk @@ -31,7 +31,8 @@ cmds_objects := cmds-subvolume.c cmds-filesystem.c cmds-device.c cmds-scrub.c \ cmds-quota.c cmds-qgroup.c cmds-replace.c cmds-check.c \ cmds-restore.c cmds-rescue.c chunk-recover.c super-recover.c \ cmds-property.c cmds-fi-usage.c cmds-inspect-dump-tree.c \ - cmds-inspect-dump-super.c cmds-inspect-tree-stats.c cmds-fi-du.c \ + cmds-inspect-dump-raw.c cmds-inspect-dump-super.c \ + cmds-inspect-tree-stats.c cmds-fi-du.c \ mkfs/common.c libbtrfs_objects := common/send-stream.c common/send-utils.c kernel-lib/rbtree.c btrfs-list.c \ crypto/crc32c.c messages.c \ diff --git a/Makefile b/Makefile index 40c5aee0..ea6e4921 100644 --- a/Makefile +++ b/Makefile @@ -165,7 +165,8 @@ cmds_objects = cmds/subvolume.o cmds/filesystem.o cmds/device.o cmds/scrub.o \ cmds/restore.o cmds/rescue.o cmds/rescue-chunk-recover.o \ cmds/rescue-super-recover.o \ cmds/property.o cmds/filesystem-usage.o cmds/inspect-dump-tree.o \ - cmds/inspect-dump-super.o cmds/inspect-tree-stats.o cmds/filesystem-du.o \ + cmds/inspect-dump-raw.o cmds/inspect-dump-super.o \ + cmds/inspect-tree-stats.o cmds/filesystem-du.o \ mkfs/common.o check/mode-common.o check/mode-lowmem.o libbtrfs_objects = common/send-stream.o common/send-utils.o kernel-lib/rbtree.o btrfs-list.o \ kernel-lib/radix-tree.o common/extent-cache.o kernel-shared/extent_io.o \ diff --git a/cmds/commands.h b/cmds/commands.h index 8fa85d6c..57a976bb 100644 --- a/cmds/commands.h +++ b/cmds/commands.h @@ -142,6 +142,7 @@ DECLARE_COMMAND(super_recover); DECLARE_COMMAND(inspect); DECLARE_COMMAND(inspect_dump_super); DECLARE_COMMAND(inspect_dump_tree); +DECLARE_COMMAND(inspect_dump_raw); DECLARE_COMMAND(inspect_tree_stats); DECLARE_COMMAND(property); DECLARE_COMMAND(send); diff --git a/cmds/inspect-dump-raw.c b/cmds/inspect-dump-raw.c new file mode 100644 index 00000000..5f41aea4 --- /dev/null +++ b/cmds/inspect-dump-raw.c @@ -0,0 +1,343 @@ +/* + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public + * License v2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this program; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 021110-1307, USA. + */ + +#include <stdio.h> +#include <stdbool.h> +#include <getopt.h> +#include <errno.h> +#include <fcntl.h> +#include <dirent.h> + +#include "kerncompat.h" +#include "kernel-shared/ctree.h" +#include "kernel-shared/disk-io.h" +#include "kernel-shared/volumes.h" +#include "common/device-scan.h" +#include "common/utils.h" +#include "cmds/commands.h" + +static const char * const cmd_inspect_dump_raw_usage[] = { + "btrfs inspect-internal dump-raw [options] <device> [<device> ..]", + "Dump blocks from a given device in raw form", + "", + "-b|--block <block_num> dump specified block", + " can be specified multiple times", + "-m|--mirror <mirror_num> use specific mirror, useful only when they differ", + "--force continue even if block is corrupted", + NULL +}; + +/* + * Helper function to record all tree block bytenr so we don't need to put + * all code into deep indent. + * + * Return >0 if we hit a duplicated bytenr (already recorded) + * Return 0 if nothing went wrong + * Return <0 if error happens (ENOMEM) + * + * For != 0 return value, all warning/error will be outputted by this function. + */ +static int dump_add_tree_block(struct cache_tree *tree, u64 bytenr) +{ + int ret; + + /* + * We don't really care about the size and we don't have + * nodesize before we open the fs, so just use 1 as size here. + */ + ret = add_cache_extent(tree, bytenr, 1); + if (ret == -EEXIST) { + warning("tree block bytenr %llu is duplicated", bytenr); + return 1; + } + if (ret < 0) { + error("failed to record tree block bytenr %llu: %d(%s)", + bytenr, ret, strerror(-ret)); + return ret; + } + return ret; +} + +int read_whole_eb_verbose(struct btrfs_fs_info *info, struct extent_buffer *eb, int mirror, u64 bytenr) +{ + 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; + char uuid_str[BTRFS_UUID_UNPARSED_SIZE]; + + 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->fd <= 0) { + kfree(multi); + return -EIO; + } + + eb->fd = device->fd; + device->total_ios++; + eb->dev_bytenr = multi->stripes[0].physical; + kfree(multi); + multi = NULL; + + + if (read_len > bytes_left) + read_len = bytes_left; + + uuid_unparse(device->uuid, uuid_str); + fprintf(stderr, "block %llu, mirror %d: reading %llu bytes from device %s at offset %llu\n", + bytenr, mirror, read_len, uuid_str, eb->dev_bytenr); + ret = read_extent_from_disk(eb, offset, read_len); + if (ret) + return -EIO; + offset += read_len; + bytes_left -= read_len; + } + return 0; +} + +static struct extent_buffer* read_tree_block_mirror(struct btrfs_fs_info *fs_info, u64 bytenr, + int mirror_num, bool force) +{ + struct extent_buffer *eb; + int ret = 0; + int num_copies; + int current_mirror = 0; + + /* + * Please note that here we can't check it against nodesize, + * as it's possible a chunk is just aligned to sectorsize but + * not aligned to nodesize. + */ + if (!IS_ALIGNED(bytenr, fs_info->sectorsize)) { + error("tree block bytenr %llu is not aligned to sectorsize %u", + bytenr, fs_info->sectorsize); + return ERR_PTR(-EINVAL); + } + + eb = btrfs_find_create_tree_block(fs_info, bytenr); + if (!eb) + return ERR_PTR(-ENOMEM); + + num_copies = btrfs_num_copies(fs_info, eb->start, eb->len); + if (mirror_num >= 0) { + if (mirror_num >= num_copies) { + error("block %llu has only %d mirror(s), can't use specified mirror", + bytenr, num_copies); + + ret = -EINVAL; + goto error; + } + fprintf(stderr, "block %llu: using mirror %d from %d mirrors\n", bytenr, mirror_num, num_copies); + current_mirror = mirror_num; + } + + while (current_mirror < num_copies) { + ret = read_whole_eb_verbose(fs_info, eb, current_mirror, bytenr); + if (ret == 0) { + if (csum_tree_block(fs_info, eb, 1) == 0 || + (force && (mirror_num >= 0 || current_mirror + 1 == num_copies)) + ) { + btrfs_set_buffer_uptodate(eb); + return eb; + } + ret = -EIO; + } + if (mirror_num >= 0) { + break; + } + current_mirror++; + } + +error: + /* + * We failed to read this tree block, it be should deleted right now + * to avoid stale cache populate the cache. + */ + free_extent_buffer_nocache(eb); + return ERR_PTR(ret); +} + +/* + * + * Return 0 if nothing wrong happened for *each* tree blocks + * Return <0 if anything wrong happened, and return value will be the last + * error. + */ +static int dump_raw_blocks(struct btrfs_fs_info *fs_info, + struct cache_tree *tree, int mirror_num, bool force) +{ + FILE *out = stdout; + struct cache_extent *ce; + struct extent_buffer *eb; + u64 bytenr; + int ret = 0; + int i = 0; + + + ce = first_cache_extent(tree); + while (ce) { + bytenr = ce->start; + + eb = read_tree_block_mirror(fs_info, bytenr, mirror_num, force); + if (!extent_buffer_uptodate(eb)) { + error("failed to read tree block %llu", bytenr); + ret = -EIO; + goto next; + } + + if (isatty(fileno(out))) { + for (i = 0; i < eb->len; i++) { + if (i % 16 == 0) { + fprintf(out, "\n%08x ", i); + } else if ((i + 8) % 16 == 0) { + fprintf(out, " "); + } + fprintf(out, "%02x ", eb->data[i] & 0xff); + } + fprintf(out, "\n"); + } else { + fwrite(eb->data, eb->len, 1, out); + } + + free_extent_buffer(eb); +next: + remove_cache_extent(tree, ce); + free(ce); + ce = first_cache_extent(tree); + } + return ret; +} + +static int cmd_inspect_dump_raw(const struct cmd_struct *cmd, + int argc, char **argv) +{ + struct btrfs_fs_info *info; + struct cache_tree block_root; /* for multiple --block parameters */ + int ret = 0; + int dev_optind; + unsigned open_ctree_flags; + u64 block_bytenr; + int mirror_num = -1; + bool force = false; + + open_ctree_flags = OPEN_CTREE_PARTIAL | OPEN_CTREE_NO_BLOCK_GROUPS; + cache_tree_init(&block_root); + optind = 0; + while (1) { + int c; + static const struct option long_options[] = { + { "block", required_argument, NULL, 'b'}, + { "mirror", required_argument, NULL, 'm'}, + { "force", no_argument, NULL, 'f'}, + { NULL, 0, NULL, 0 } + }; + + c = getopt_long(argc, argv, "b:m:", long_options, NULL); + if (c < 0) + break; + switch (c) { + case 'b': + /* + * No need to fill roots other than chunk root + */ + open_ctree_flags |= __OPEN_CTREE_RETURN_CHUNK_ROOT; + block_bytenr = arg_strtou64(optarg); + ret = dump_add_tree_block(&block_root, block_bytenr); + if (ret < 0) + goto out; + break; + case 'm': + mirror_num = arg_strtou64(optarg); + break; + case 'f': + force = true; + break; + default: + usage_unknown_option(cmd, argv); + } + } + + if (check_argc_min(argc - optind, 1)) + return 1; + + dev_optind = optind; + while (dev_optind < argc) { + int fd; + struct btrfs_fs_devices *fs_devices; + u64 num_devices; + + ret = check_arg_type(argv[optind]); + if (ret != BTRFS_ARG_BLKDEV && ret != BTRFS_ARG_REG) { + if (ret < 0) { + errno = -ret; + error("invalid argument %s: %m", argv[dev_optind]); + } else { + error("not a block device or regular file: %s", + argv[dev_optind]); + } + } + fd = open(argv[dev_optind], O_RDONLY); + if (fd < 0) { + error("cannot open %s: %m", argv[dev_optind]); + return -EINVAL; + } + ret = btrfs_scan_one_device(fd, argv[dev_optind], &fs_devices, + &num_devices, + BTRFS_SUPER_INFO_OFFSET, + SBREAD_DEFAULT); + close(fd); + if (ret < 0) { + errno = -ret; + error("device scan %s: %m", argv[dev_optind]); + return ret; + } + dev_optind++; + } + + fprintf(stderr, "%s\n", PACKAGE_STRING); + + info = open_ctree_fs_info(argv[optind], 0, 0, 0, open_ctree_flags); + if (!info) { + error("unable to open %s", argv[optind]); + ret = -EIO; + goto out; + } + + if (!cache_tree_empty(&block_root)) { + ret = dump_raw_blocks(info, &block_root, mirror_num, force); + } else { + error("No blocks specified"); + ret = -EINVAL; + } + + close_ctree(info->chunk_root); +out: + return !!ret; +} +DEFINE_SIMPLE_COMMAND(inspect_dump_raw, "dump-raw"); diff --git a/cmds/inspect.c b/cmds/inspect.c index 15f19c8a..2d133830 100644 --- a/cmds/inspect.c +++ b/cmds/inspect.c @@ -693,6 +693,7 @@ static const struct cmd_group inspect_cmd_group = { &cmd_struct_inspect_min_dev_size, &cmd_struct_inspect_dump_tree, &cmd_struct_inspect_dump_super, + &cmd_struct_inspect_dump_raw, &cmd_struct_inspect_tree_stats, NULL } -- 2.30.1