From d230eaea612b5649a9b84ca1f5bb41455251741e Mon Sep 17 00:00:00 2001 From: Kent Overstreet Date: Thu, 2 Feb 2017 21:45:08 -0900 Subject: Add a command to dump filesystem metadata --- Makefile | 4 +- bcache.c | 10 ++- cmd_debug.c | 282 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ cmds.h | 3 + qcow2.c | 162 ++++++++++++++++++++++++++++++++++ qcow2.h | 24 +++++ tools-util.c | 9 +- 7 files changed, 488 insertions(+), 6 deletions(-) create mode 100644 cmd_debug.c create mode 100644 qcow2.c create mode 100644 qcow2.h diff --git a/Makefile b/Makefile index 6916c9b..a3bf8d8 100644 --- a/Makefile +++ b/Makefile @@ -16,7 +16,7 @@ LDFLAGS+=-O2 -g ifdef D CFLAGS+=-Werror else - CFLAGS+=-flto -Werror + CFLAGS+=-flto LDFLAGS+=-flto endif @@ -43,12 +43,14 @@ LINUX_OBJS=$(LINUX_SRCS:.c=.o) OBJS=bcache.o \ bcache-userspace-shim.o \ cmd_assemble.o \ + cmd_debug.o \ cmd_device.o \ cmd_fs.o \ cmd_fsck.o \ cmd_format.o \ cmd_run.o \ libbcache.o \ + qcow2.o \ tools-util.o \ $(LINUX_OBJS) \ $(CCANOBJS) diff --git a/bcache.c b/bcache.c index eb532f2..1fb1a55 100644 --- a/bcache.c +++ b/bcache.c @@ -45,7 +45,10 @@ static void usage(void) " device_remove Remove a device from an existing (running) filesystem\n" "\n" "Repair:\n" - " bcache fsck Check an existing filesystem for errors\n"); + " bcache fsck Check an existing filesystem for errors\n" + "Debug:\n" + " bcache dump Dump filesystem metadata to a qcow2 image\n" + " bcache list List filesystem metadata in textual form\n"); } int main(int argc, char *argv[]) @@ -91,6 +94,11 @@ int main(int argc, char *argv[]) if (!strcmp(cmd, "fsck")) return cmd_fsck(argc, argv); + if (!strcmp(cmd, "dump")) + return cmd_dump(argc, argv); + if (!strcmp(cmd, "list")) + return cmd_list(argc, argv); + usage(); return 0; } diff --git a/cmd_debug.c b/cmd_debug.c new file mode 100644 index 0000000..0813d29 --- /dev/null +++ b/cmd_debug.c @@ -0,0 +1,282 @@ +#include +#include +#include +#include + +#include "cmds.h" +#include "libbcache.h" +#include "qcow2.h" +#include "tools-util.h" + +#include "bcache.h" +#include "alloc.h" +#include "btree_cache.h" +#include "btree_iter.h" +#include "buckets.h" +#include "journal.h" +#include "super.h" + +static void dump_usage(void) +{ + puts("bcache dump - dump filesystem metadata\n" + "Usage: bcache dump [OPTION]... \n" + "\n" + "Options:\n" + " -o output Output qcow2 image(s)\n" + " -h Display this help and exit\n" + "Report bugs to "); +} + +void dump_one_device(struct cache_set *c, struct cache *ca, int fd) +{ + struct cache_sb *sb = ca->disk_sb.sb; + sparse_data data; + unsigned i; + + darray_init(data); + + /* Superblock: */ + data_add(&data, SB_SECTOR << 9, __set_bytes(sb, le16_to_cpu(sb->u64s))); + + /* Journal: */ + for (i = 0; i < bch_nr_journal_buckets(ca->disk_sb.sb); i++) + if (ca->journal.bucket_seq[i] >= c->journal.last_seq_ondisk) { + u64 bucket = journal_bucket(ca->disk_sb.sb, i); + + data_add(&data, + bucket_bytes(ca) * bucket, + bucket_bytes(ca)); + } + + /* Prios/gens: */ + for (i = 0; i < prio_buckets(ca); i++) + data_add(&data, + bucket_bytes(ca) * ca->prio_last_buckets[i], + bucket_bytes(ca)); + + /* Btree: */ + for (i = 0; i < BTREE_ID_NR; i++) { + const struct bch_extent_ptr *ptr; + struct btree_iter iter; + struct btree *b; + + for_each_btree_node(&iter, c, i, POS_MIN, 0, b) { + struct bkey_s_c_extent e = bkey_i_to_s_c_extent(&b->key); + + extent_for_each_ptr(e, ptr) + if (ptr->dev == ca->sb.nr_this_dev) + data_add(&data, + ptr->offset << 9, + b->written << 9); + } + bch_btree_iter_unlock(&iter); + } + + qcow2_write_image(ca->disk_sb.bdev->bd_fd, fd, &data, + max_t(unsigned, btree_bytes(c) / 8, block_bytes(c))); +} + +int cmd_dump(int argc, char *argv[]) +{ + DECLARE_COMPLETION_ONSTACK(shutdown); + struct cache_set_opts opts = cache_set_opts_empty(); + struct cache_set *c = NULL; + const char *err; + char *out = NULL, *buf; + unsigned i, nr_devices = 0; + bool force = false; + int fd, opt; + + opts.nochanges = true; + opts.noreplay = true; + opts.errors = BCH_ON_ERROR_CONTINUE; + fsck_err_opt = FSCK_ERR_NO; + + while ((opt = getopt(argc, argv, "o:fh")) != -1) + switch (opt) { + case 'o': + out = optarg; + break; + case 'f': + force = true; + break; + case 'h': + dump_usage(); + exit(EXIT_SUCCESS); + } + + if (optind >= argc) + die("Please supply device(s) to check"); + + if (!out) + die("Please supply output filename"); + + buf = alloca(strlen(out) + 10); + strcpy(buf, out); + + err = bch_register_cache_set(argv + optind, argc - optind, opts, &c); + if (err) + die("error opening %s: %s", argv[optind], err); + + down_read(&c->gc_lock); + + for (i = 0; i < c->sb.nr_in_set; i++) + if (c->cache[i]) + nr_devices++; + + BUG_ON(!nr_devices); + + for (i = 0; i < c->sb.nr_in_set; i++) { + int mode = O_WRONLY|O_CREAT|O_TRUNC; + + if (!force) + mode |= O_EXCL; + + if (!c->cache[i]) + continue; + + if (nr_devices > 1) + sprintf(buf, "%s.%u", out, i); + + fd = open(buf, mode, 0600); + if (fd < 0) + die("error opening %s: %s", buf, strerror(errno)); + + dump_one_device(c, c->cache[i], fd); + close(fd); + } + + up_read(&c->gc_lock); + + c->stop_completion = &shutdown; + bch_cache_set_stop(c); + closure_put(&c->cl); + wait_for_completion(&shutdown); + return 0; +} + +void list_keys(struct cache_set *c, enum btree_id btree_id, + struct bpos start, struct bpos end, int mode) +{ + struct btree_iter iter; + struct bkey_s_c k; + char buf[512]; + + for_each_btree_key(&iter, c, btree_id, start, k) { + if (bkey_cmp(k.k->p, end) > 0) + break; + + bch_bkey_val_to_text(c, bkey_type(0, btree_id), + buf, sizeof(buf), k); + puts(buf); + } + bch_btree_iter_unlock(&iter); +} + +void list_btree_formats(struct cache_set *c, enum btree_id btree_id, + struct bpos start, struct bpos end, int mode) +{ + struct btree_iter iter; + struct btree *b; + char buf[4096]; + + for_each_btree_node(&iter, c, btree_id, start, 0, b) { + if (bkey_cmp(b->key.k.p, end) > 0) + break; + + bch_print_btree_node(c, b, buf, sizeof(buf)); + puts(buf); + } + bch_btree_iter_unlock(&iter); +} + +struct bpos parse_pos(char *buf) +{ + char *s = buf; + char *inode = strsep(&s, ":"); + char *offset = strsep(&s, ":"); + struct bpos ret = { 0 }; + + if (!inode || !offset || s || + kstrtoull(inode, 10, &ret.inode) || + kstrtoull(offset, 10, &ret.offset)) + die("invalid bpos %s", buf); + + return ret; +} + +static void list_keys_usage(void) +{ + puts("bcache list_keys - list filesystem metadata to stdout\n" + "Usage: bcache list_keys [OPTION]... \n" + "\n" + "Options:\n" + " -b btree_id Integer btree id to list\n" + " -s start Start pos (as inode:offset)\n" + " -e end End pos\n" + " -m mode Mode for listing\n" + " -h Display this help and exit\n" + "Report bugs to "); +} + +int cmd_list(int argc, char *argv[]) +{ + DECLARE_COMPLETION_ONSTACK(shutdown); + struct cache_set_opts opts = cache_set_opts_empty(); + struct cache_set *c = NULL; + enum btree_id btree_id = BTREE_ID_EXTENTS; + struct bpos start = POS_MIN, end = POS_MAX; + const char *err; + int mode = 0, opt; + u64 v; + + opts.nochanges = true; + opts.norecovery = true; + opts.errors = BCH_ON_ERROR_CONTINUE; + fsck_err_opt = FSCK_ERR_NO; + + while ((opt = getopt(argc, argv, "b:s:e:m:h")) != -1) + switch (opt) { + case 'b': + if (kstrtoull(optarg, 10, &v) || + v >= BTREE_ID_NR) + die("invalid btree id"); + btree_id = v; + break; + case 's': + start = parse_pos(optarg); + break; + case 'e': + end = parse_pos(optarg); + break; + case 'm': + break; + case 'h': + list_keys_usage(); + exit(EXIT_SUCCESS); + } + + if (optind >= argc) + die("Please supply device(s) to check"); + + err = bch_register_cache_set(argv + optind, argc - optind, opts, &c); + if (err) + die("error opening %s: %s", argv[optind], err); + + switch (mode) { + case 0: + list_keys(c, btree_id, start, end, mode); + break; + case 1: + list_btree_formats(c, btree_id, start, end, mode); + break; + default: + die("Invalid mode"); + } + + c->stop_completion = &shutdown; + bch_cache_set_stop(c); + closure_put(&c->cl); + wait_for_completion(&shutdown); + return 0; +} diff --git a/cmds.h b/cmds.h index c762a2c..c0c8aa5 100644 --- a/cmds.h +++ b/cmds.h @@ -25,4 +25,7 @@ int cmd_device_remove(int argc, char *argv[]); int cmd_fsck(int argc, char *argv[]); +int cmd_dump(int argc, char *argv[]); +int cmd_list(int argc, char *argv[]); + #endif /* _CMDS_H */ diff --git a/qcow2.c b/qcow2.c new file mode 100644 index 0000000..cbc8d4c --- /dev/null +++ b/qcow2.c @@ -0,0 +1,162 @@ + +#include +#include +#include +#include + +#include "qcow2.h" +#include "tools-util.h" + +#define QCOW_MAGIC (('Q' << 24) | ('F' << 16) | ('I' << 8) | 0xfb) +#define QCOW_VERSION 2 +#define QCOW_OFLAG_COPIED (1LL << 63) + +struct qcow2_hdr { + u32 magic; + u32 version; + + u64 backing_file_offset; + u32 backing_file_size; + + u32 block_bits; + u64 size; + u32 crypt_method; + + u32 l1_size; + u64 l1_table_offset; + + u64 refcount_table_offset; + u32 refcount_table_blocks; + + u32 nb_snapshots; + u64 snapshots_offset; +}; + +struct qcow2_image { + int fd; + u32 block_size; + u64 *l1_table; + u64 l1_offset; + u32 l1_index; + u64 *l2_table; + u64 offset; +}; + +static void flush_l2(struct qcow2_image *img) +{ + if (img->l1_index != -1) { + img->l1_table[img->l1_index] = + cpu_to_be64(img->offset|QCOW_OFLAG_COPIED); + xpwrite(img->fd, img->l2_table, img->block_size, img->offset); + img->offset += img->block_size; + + memset(img->l2_table, 0, img->block_size); + img->l1_index = -1; + } +} + +static void add_l2(struct qcow2_image *img, u64 src_blk, u64 dst_offset) +{ + unsigned l2_size = img->block_size / sizeof(u64); + u64 l1_index = src_blk / l2_size; + u64 l2_index = src_blk & (l2_size - 1); + + if (img->l1_index != l1_index) { + flush_l2(img); + img->l1_index = l1_index; + } + + img->l2_table[l2_index] = cpu_to_be64(dst_offset|QCOW_OFLAG_COPIED); +} + +static int range_cmp(const void *_l, const void *_r) +{ + const struct range *l = _l, *r = _r; + + if (l->start < r->start) + return -1; + if (l->start > r->start) + return 1; + return 0; +} + +void qcow2_write_image(int infd, int outfd, sparse_data *data, + unsigned block_size) +{ + u64 image_size = get_size(NULL, infd); + unsigned l2_size = block_size / sizeof(u64); + unsigned l1_size = DIV_ROUND_UP(image_size, (u64) block_size * l2_size); + struct qcow2_hdr hdr = { 0 }; + struct qcow2_image img = { + .fd = outfd, + .block_size = block_size, + .l2_table = xcalloc(l2_size, sizeof(u64)), + .l1_table = xcalloc(l1_size, sizeof(u64)), + .l1_index = -1, + .offset = round_up(sizeof(hdr), block_size), + }; + struct range *r; + char *buf = xmalloc(block_size); + u64 src_offset, dst_offset; + sparse_data m; + + assert(is_power_of_2(block_size)); + + sort(&darray_item(*data, 0), + darray_size(*data), + sizeof(darray_item(*data, 0)), + range_cmp, NULL); + + /* Round to blocksize, merge contiguous ranges: */ + darray_init(m); + darray_foreach(r, *data) { + struct range *l = m.size ? &m.item[m.size - 1] : NULL; + + r->start = round_down(r->start, block_size); + r->end = round_up(r->end, block_size); + + if (l && l->end >= r->start) + l->end = max(l->end, r->end); + else + darray_append(m, *r); + } + darray_free(*data); + *data = m; + + /* Write data: */ + darray_foreach(r, *data) + for (src_offset = r->start; + src_offset < r->end; + src_offset += block_size) { + dst_offset = img.offset; + img.offset += img.block_size; + + xpread(infd, buf, block_size, src_offset); + xpwrite(outfd, buf, block_size, dst_offset); + + add_l2(&img, src_offset / block_size, dst_offset); + } + + flush_l2(&img); + + /* Write L1 table: */ + dst_offset = img.offset; + img.offset += round_up(l1_size * sizeof(u64), block_size); + xpwrite(img.fd, img.l1_table, l1_size * sizeof(u64), dst_offset); + + /* Write header: */ + hdr.magic = cpu_to_be32(QCOW_MAGIC); + hdr.version = cpu_to_be32(QCOW_VERSION); + hdr.block_bits = cpu_to_be32(ilog2(block_size)); + hdr.size = cpu_to_be64(image_size); + hdr.l1_size = cpu_to_be32(l1_size); + hdr.l1_table_offset = cpu_to_be64(dst_offset); + + memset(buf, 0, block_size); + memcpy(buf, &hdr, sizeof(hdr)); + xpwrite(img.fd, buf, block_size, 0); + + free(img.l2_table); + free(img.l1_table); + free(buf); +} diff --git a/qcow2.h b/qcow2.h new file mode 100644 index 0000000..c6f0b6b --- /dev/null +++ b/qcow2.h @@ -0,0 +1,24 @@ +#ifndef _QCOW2_H +#define _QCOW2_H + +#include +#include "ccan/darray/darray.h" + +struct range { + u64 start; + u64 end; +}; + +typedef darray(struct range) sparse_data; + +static inline void data_add(sparse_data *data, u64 offset, u64 size) +{ + darray_append(*data, (struct range) { + .start = offset, + .end = offset + size + }); +} + +void qcow2_write_image(int, int, sparse_data *, unsigned); + +#endif /* _QCOW2_H */ diff --git a/tools-util.c b/tools-util.c index c6e8855..0a95fbe 100644 --- a/tools-util.c +++ b/tools-util.c @@ -104,22 +104,23 @@ ssize_t read_string_list_or_die(const char *opt, const char * const list[], return v; } -/* Returns size of file or block device, in units of 512 byte sectors: */ +/* Returns size of file or block device: */ u64 get_size(const char *path, int fd) { struct stat statbuf; + u64 ret; + if (fstat(fd, &statbuf)) die("Error statting %s: %s", path, strerror(errno)); if (!S_ISBLK(statbuf.st_mode)) - return statbuf.st_size >> 9; + return statbuf.st_size; - u64 ret; if (ioctl(fd, BLKGETSIZE64, &ret)) die("Error getting block device size on %s: %s\n", path, strerror(errno)); - return ret >> 9; + return ret; } /* Returns blocksize in units of 512 byte sectors: */ -- cgit v1.2.3