diff options
author | Kent Overstreet <kent.overstreet@linux.dev> | 2025-09-01 14:53:13 -0400 |
---|---|---|
committer | Kent Overstreet <kent.overstreet@linux.dev> | 2025-09-01 15:11:56 -0400 |
commit | e65dd86e419d96aaa616dcb508d3564bd5721ef6 (patch) | |
tree | b307ec60ce65af9a10c8f65b4b7e8a23b55fbffc | |
parent | da8f1d04e3b89a576cbb5fdd7cd1c1c451be11d8 (diff) |
bcachefs undump
Add our own version of 'qemu-img convert', which doesn't have the l1
table size limit or require fixing our missing reflink table.
Signed-off-by: Kent Overstreet <kent.overstreet@linux.dev>
-rw-r--r-- | c_src/bcachefs.c | 1 | ||||
-rw-r--r-- | c_src/cmd_dump.c | 82 | ||||
-rw-r--r-- | c_src/cmds.h | 2 | ||||
-rw-r--r-- | c_src/qcow2.c | 51 | ||||
-rw-r--r-- | c_src/qcow2.h | 4 | ||||
-rw-r--r-- | c_src/tools-util.c | 6 | ||||
-rw-r--r-- | c_src/tools-util.h | 7 | ||||
-rw-r--r-- | src/bcachefs.rs | 1 |
8 files changed, 148 insertions, 6 deletions
diff --git a/c_src/bcachefs.c b/c_src/bcachefs.c index 58fa2549..d91e0475 100644 --- a/c_src/bcachefs.c +++ b/c_src/bcachefs.c @@ -96,6 +96,7 @@ void bcachefs_usage(void) "Debug:\n" "These commands work on offline, unmounted filesystems\n" " dump Dump filesystem metadata to a qcow2 image\n" + " undump Convert qcow2 metadata dumps to sparse raw files\n" " list List filesystem metadata in textual form\n" " list_journal List contents of journal\n" "\n" diff --git a/c_src/cmd_dump.c b/c_src/cmd_dump.c index 5f354272..93e7d743 100644 --- a/c_src/cmd_dump.c +++ b/c_src/cmd_dump.c @@ -386,3 +386,85 @@ int cmd_dump(int argc, char *argv[]) darray_exit(&dev_names); return ret; } + +static void undump_usage(void) +{ + puts("bcachefs undump - turn qcow2 images from 'bcachefs dump' back into sparse raw images\n" + "Usage: bcachefs undump [OPTION]... <files>\n" + "\n" + "Options:\n" + " -f, --force Force; overwrite when needed\n" + " -h, --help Display this help and exit\n" + "Report bugs to <linux-bcachefs@vger.kernel.org>"); +} + +struct undump { + char *in, *out; + int infd, outfd; +}; + +int cmd_undump(int argc, char *argv[]) +{ + static const struct option longopts[] = { + { "force", no_argument, NULL, 'f' }, + { "help", no_argument, NULL, 'h' }, + { NULL } + }; + bool force = false; + int opt; + + while ((opt = getopt_long(argc, argv, "fh", + longopts, NULL)) != -1) + switch (opt) { + case 'f': + force = true; + break; + case 'h': + undump_usage(); + exit(EXIT_SUCCESS); + } + args_shift(optind); + + if (!argc) { + undump_usage(); + die("Please supply file(s) to convert"); + } + + DARRAY(struct undump) files = {}; + + for (unsigned i = 0; i < argc; i++) { + unsigned len = strlen(argv[i]); + const char *suffix = ".qcow2"; + unsigned suffixlen = strlen(suffix); + + if (len <= suffixlen || + strcmp(suffix, argv[i] + len - suffixlen)) { + die("%s not a qcow2 image?", argv[i]); + } + + char *out = strdup(argv[i]); + out[len - suffixlen] = '\0'; + + if (!force && !access(out, F_OK)) + die("%s already exists", out); + + darray_push(&files, ((struct undump) { + .in = argv[i], + .out = out, + .infd = xopen(argv[i], O_RDONLY), + })); + } + + darray_for_each(files, i) + i->outfd = xopen(i->out, O_WRONLY|O_CREAT|(!force ? O_EXCL : 0), 0600); + + darray_for_each(files, i) { + qcow2_to_raw(i->infd, i->outfd); + close(i->infd); + close(i->outfd); + free(i->out); + } + + darray_exit(&files); + return 0; +} diff --git a/c_src/cmds.h b/c_src/cmds.h index 30b47286..fa4b87a9 100644 --- a/c_src/cmds.h +++ b/c_src/cmds.h @@ -34,6 +34,8 @@ int cmd_fsck(int argc, char *argv[]); int cmd_recovery_pass(int argc, char *argv[]); int cmd_dump(int argc, char *argv[]); +int cmd_undump(int argc, char *argv[]); + int cmd_list_journal(int argc, char *argv[]); int cmd_kill_btree_node(int argc, char *argv[]); diff --git a/c_src/qcow2.c b/c_src/qcow2.c index 53959a00..7afb6812 100644 --- a/c_src/qcow2.c +++ b/c_src/qcow2.c @@ -143,8 +143,7 @@ void qcow2_image_finish(struct qcow2_image *img) memset(buf, 0, img->block_size); memcpy(buf, &hdr, sizeof(hdr)); - xpwrite(img->outfd, buf, img->block_size, 0, - "qcow2 header"); + xpwrite(img->outfd, buf, img->block_size, 0, "qcow2 header"); free(img->l2_table); free(img->l1_table); @@ -160,3 +159,51 @@ void qcow2_write_image(int infd, int outfd, ranges *data, qcow2_write_ranges(&img, data); qcow2_image_finish(&img); } + +void qcow2_to_raw(int infd, int outfd) +{ + struct qcow2_hdr hdr; + + xpread(infd, &hdr, sizeof(hdr), 0); + + if (hdr.magic != cpu_to_be32(QCOW_MAGIC)) + die("not a qcow2 image"); + + if (hdr.version != cpu_to_be32(QCOW_VERSION)) + die("incorrect qcow2 version"); + + ftruncate(outfd, be64_to_cpu(hdr.size)); + + unsigned block_size = 1U << be32_to_cpu(hdr.block_bits); + + unsigned l1_size = be32_to_cpu(hdr.l1_size); + unsigned l2_size = block_size / sizeof(u64); + + __be64 *l1_table = xcalloc(l1_size, sizeof(u64)); + __be64 *l2_table = xmalloc(block_size); + void *data_buf = xmalloc(block_size); + + xpread(infd, l1_table, l1_size * sizeof(u64), be64_to_cpu(hdr.l1_table_offset)); + + for (u64 i = 0; i < l1_size; i++) { + if (!l1_table[i]) + continue; + + xpread(infd, l2_table, block_size, be64_to_cpu(l1_table[i]) & ~QCOW_OFLAG_COPIED); + + for (unsigned j = 0; j < l2_size; j++) { + u64 src_offset = be64_to_cpu(l2_table[j]) & ~QCOW_OFLAG_COPIED; + if (!src_offset) + continue; + + u64 dst_offset = (i * l2_size + j) * block_size; + + xpread(infd, data_buf, block_size, src_offset); + xpwrite(outfd, data_buf, block_size, dst_offset, "qcow2 data"); + } + } + + free(data_buf); + free(l2_table); + free(l1_table); +} diff --git a/c_src/qcow2.h b/c_src/qcow2.h index c7b35627..7ccb2773 100644 --- a/c_src/qcow2.h +++ b/c_src/qcow2.h @@ -4,6 +4,8 @@ #include <linux/types.h> #include "tools-util.h" +#define QCOW2_L1_MAX (4ULL << 20) + struct qcow2_image { int infd; int outfd; @@ -25,4 +27,6 @@ void qcow2_image_finish(struct qcow2_image *); void qcow2_write_image(int, int, ranges *, unsigned); +void qcow2_to_raw(int, int); + #endif /* _QCOW2_H */ diff --git a/c_src/tools-util.c b/c_src/tools-util.c index f48d3f21..7733b9e9 100644 --- a/c_src/tools-util.c +++ b/c_src/tools-util.c @@ -52,15 +52,15 @@ char *mprintf(const char *fmt, ...) return str; } -void xpread(int fd, void *buf, size_t count, off_t offset) +void __xpread(int fd, void *buf, size_t count, off_t offset, const char *file, unsigned line) { while (count) { ssize_t r = pread(fd, buf, count, offset); if (r < 0) - die("read error: %m"); + die("read error: %m at %s:%u", file, line); if (!r) - die("pread error: unexpected eof"); + die("pread error: unexpected eof at %s:%u", file, line); count -= r; offset += r; } diff --git a/c_src/tools-util.h b/c_src/tools-util.h index b8104002..988e2d62 100644 --- a/c_src/tools-util.h +++ b/c_src/tools-util.h @@ -28,8 +28,13 @@ void die(const char *, ...) __attribute__ ((format (printf, 1, 2))) noreturn; char *mprintf(const char *, ...) __attribute__ ((format (printf, 1, 2))); -void xpread(int, void *, size_t, off_t); + +void __xpread(int, void *, size_t, off_t, const char *, unsigned); +#define xpread(_fd, _buf, _count, _offset) \ + __xpread(_fd, _buf, _count, _offset, __FILE__, __LINE__) + void xpwrite(int, const void *, size_t, off_t, const char *); + struct stat xfstatat(int, const char *, int); struct stat xfstat(int); struct stat xstat(const char *); diff --git a/src/bcachefs.rs b/src/bcachefs.rs index 4fb0b14c..d0c1d6e8 100644 --- a/src/bcachefs.rs +++ b/src/bcachefs.rs @@ -48,6 +48,7 @@ fn handle_c_command(mut argv: Vec<String>, symlink_cmd: Option<&str>) -> i32 { "data" => c::data_cmds(argc, argv), "device" => c::device_cmds(argc, argv), "dump" => c::cmd_dump(argc, argv), + "undump" => c::cmd_undump(argc, argv), "format" => c::cmd_format(argc, argv), "fs" => c::fs_cmds(argc, argv), "fsck" => c::cmd_fsck(argc, argv), |