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