diff options
author | Allan Jude <[email protected]> | 2021-01-28 00:36:01 -0500 |
---|---|---|
committer | GitHub <[email protected]> | 2021-01-27 21:36:01 -0800 |
commit | 393e69241eea8b5f7f817200ad283b7d5b5ceb70 (patch) | |
tree | da4b11a62ce85f415bf8436d0aca24919925daae /cmd | |
parent | b2c5904a7839b93fa30bba261a5981161acea336 (diff) |
Add zdb -r <dataset> <object-id | file> <output>
While you can use zdb -R poolname vdev:offset:[<lsize>/]<psize>[:flags]
to extract individual DVAs from a vdev, it would be handy for be able
copy an entire file out of the pool.
Given a file or object number, add support to copy the contents to a
file. Useful for debugging and recovery.
Reviewed-by: Jorgen Lundman <[email protected]>
Reviewed-by: Brian Behlendorf <[email protected]>
Signed-off-by: Allan Jude <[email protected]>
Closes #11027
Diffstat (limited to 'cmd')
-rw-r--r-- | cmd/zdb/zdb.c | 117 |
1 files changed, 106 insertions, 11 deletions
diff --git a/cmd/zdb/zdb.c b/cmd/zdb/zdb.c index 77d7441e8..f7a6e17d7 100644 --- a/cmd/zdb/zdb.c +++ b/cmd/zdb/zdb.c @@ -31,6 +31,7 @@ * * [1] Portions of this software were developed by Allan Jude * under sponsorship from the FreeBSD Foundation. + * Copyright (c) 2021 Allan Jude */ #include <stdio.h> @@ -755,13 +756,14 @@ usage(void) "\t%s -m [-AFLPX] [-e [-V] [-p <path> ...]] [-t <txg>] " "[-U <cache>]\n\t\t<poolname> [<vdev> [<metaslab> ...]]\n" "\t%s -O <dataset> <path>\n" + "\t%s -r <dataset> <path> <destination>\n" "\t%s -R [-A] [-e [-V] [-p <path> ...]] [-U <cache>]\n" "\t\t<poolname> <vdev>:<offset>:<size>[:<flags>]\n" "\t%s -E [-A] word0:word1:...:word15\n" "\t%s -S [-AP] [-e [-V] [-p <path> ...]] [-U <cache>] " "<poolname>\n\n", cmdname, cmdname, cmdname, cmdname, cmdname, cmdname, cmdname, - cmdname, cmdname, cmdname); + cmdname, cmdname, cmdname, cmdname); (void) fprintf(stderr, " Dataset name must include at least one " "separator character '/' or '@'\n"); @@ -800,6 +802,7 @@ usage(void) (void) fprintf(stderr, " -m metaslabs\n"); (void) fprintf(stderr, " -M metaslab groups\n"); (void) fprintf(stderr, " -O perform object lookups by path\n"); + (void) fprintf(stderr, " -r copy an object by path to file\n"); (void) fprintf(stderr, " -R read and display block from a " "device\n"); (void) fprintf(stderr, " -s report stats on zdb's I/O\n"); @@ -4490,7 +4493,7 @@ static char curpath[PATH_MAX]; * for the last one. */ static int -dump_path_impl(objset_t *os, uint64_t obj, char *name) +dump_path_impl(objset_t *os, uint64_t obj, char *name, uint64_t *retobj) { int err; boolean_t header = B_TRUE; @@ -4540,10 +4543,15 @@ dump_path_impl(objset_t *os, uint64_t obj, char *name) switch (doi.doi_type) { case DMU_OT_DIRECTORY_CONTENTS: if (s != NULL && *(s + 1) != '\0') - return (dump_path_impl(os, child_obj, s + 1)); + return (dump_path_impl(os, child_obj, s + 1, retobj)); /*FALLTHROUGH*/ case DMU_OT_PLAIN_FILE_CONTENTS: - dump_object(os, child_obj, dump_opt['v'], &header, NULL, 0); + if (retobj != NULL) { + *retobj = child_obj; + } else { + dump_object(os, child_obj, dump_opt['v'], &header, + NULL, 0); + } return (0); default: (void) fprintf(stderr, "object %llu has non-file/directory " @@ -4558,7 +4566,7 @@ dump_path_impl(objset_t *os, uint64_t obj, char *name) * Dump the blocks for the object specified by path inside the dataset. */ static int -dump_path(char *ds, char *path) +dump_path(char *ds, char *path, uint64_t *retobj) { int err; objset_t *os; @@ -4578,13 +4586,90 @@ dump_path(char *ds, char *path) (void) snprintf(curpath, sizeof (curpath), "dataset=%s path=/", ds); - err = dump_path_impl(os, root_obj, path); + err = dump_path_impl(os, root_obj, path, retobj); close_objset(os, FTAG); return (err); } static int +zdb_copy_object(objset_t *os, uint64_t srcobj, char *destfile) +{ + int err = 0; + uint64_t size, readsize, oursize, offset; + ssize_t writesize; + sa_handle_t *hdl; + + (void) printf("Copying object %" PRIu64 " to file %s\n", srcobj, + destfile); + + VERIFY3P(os, ==, sa_os); + if ((err = sa_handle_get(os, srcobj, NULL, SA_HDL_PRIVATE, &hdl))) { + (void) printf("Failed to get handle for SA znode\n"); + return (err); + } + if ((err = sa_lookup(hdl, sa_attr_table[ZPL_SIZE], &size, 8))) { + (void) sa_handle_destroy(hdl); + return (err); + } + (void) sa_handle_destroy(hdl); + + (void) printf("Object %" PRIu64 " is %" PRIu64 " bytes\n", srcobj, + size); + if (size == 0) { + return (EINVAL); + } + + int fd = open(destfile, O_WRONLY | O_CREAT | O_TRUNC, 0644); + /* + * We cap the size at 1 mebibyte here to prevent + * allocation failures and nigh-infinite printing if the + * object is extremely large. + */ + oursize = MIN(size, 1 << 20); + offset = 0; + char *buf = kmem_alloc(oursize, KM_NOSLEEP); + if (buf == NULL) { + return (ENOMEM); + } + + while (offset < size) { + readsize = MIN(size - offset, 1 << 20); + err = dmu_read(os, srcobj, offset, readsize, buf, 0); + if (err != 0) { + (void) printf("got error %u from dmu_read\n", err); + kmem_free(buf, oursize); + return (err); + } + if (dump_opt['v'] > 3) { + (void) printf("Read offset=%" PRIu64 " size=%" PRIu64 + " error=%d\n", offset, readsize, err); + } + + writesize = write(fd, buf, readsize); + if (writesize < 0) { + err = errno; + break; + } else if (writesize != readsize) { + /* Incomplete write */ + (void) fprintf(stderr, "Short write, only wrote %llu of" + " %" PRIu64 " bytes, exiting...\n", + (u_longlong_t)writesize, readsize); + break; + } + + offset += readsize; + } + + (void) close(fd); + + if (buf != NULL) + kmem_free(buf, oursize); + + return (err); +} + +static int dump_label(const char *dev) { char path[MAXPATHLEN]; @@ -8167,6 +8252,7 @@ main(int argc, char **argv) nvlist_t *policy = NULL; uint64_t max_txg = UINT64_MAX; int64_t objset_id = -1; + uint64_t object; int flags = ZFS_IMPORT_MISSING_LOG; int rewind = ZPOOL_NEVER_REWIND; char *spa_config_path_env, *objset_str; @@ -8195,7 +8281,7 @@ main(int argc, char **argv) zfs_btree_verify_intensity = 3; while ((c = getopt(argc, argv, - "AbcCdDeEFGhiI:klLmMo:Op:PqRsSt:uU:vVx:XYyZ")) != -1) { + "AbcCdDeEFGhiI:klLmMo:Op:PqrRsSt:uU:vVx:XYyZ")) != -1) { switch (c) { case 'b': case 'c': @@ -8210,6 +8296,7 @@ main(int argc, char **argv) case 'm': case 'M': case 'O': + case 'r': case 'R': case 's': case 'S': @@ -8299,7 +8386,7 @@ main(int argc, char **argv) (void) fprintf(stderr, "-p option requires use of -e\n"); usage(); } - if (dump_opt['d']) { + if (dump_opt['d'] || dump_opt['r']) { /* <pool>[/<dataset | objset id> is accepted */ if (argv[2] && (objset_str = strchr(argv[2], '/')) != NULL && objset_str++ != NULL) { @@ -8358,7 +8445,7 @@ main(int argc, char **argv) verbose = MAX(verbose, 1); for (c = 0; c < 256; c++) { - if (dump_all && strchr("AeEFklLOPRSXy", c) == NULL) + if (dump_all && strchr("AeEFklLOPrRSXy", c) == NULL) dump_opt[c] = 1; if (dump_opt[c]) dump_opt[c] += verbose; @@ -8394,7 +8481,13 @@ main(int argc, char **argv) if (argc != 2) usage(); dump_opt['v'] = verbose + 3; - return (dump_path(argv[0], argv[1])); + return (dump_path(argv[0], argv[1], NULL)); + } + if (dump_opt['r']) { + if (argc != 3) + usage(); + dump_opt['v'] = verbose; + error = dump_path(argv[0], argv[1], &object); } if (dump_opt['X'] || dump_opt['F']) @@ -8572,7 +8665,9 @@ main(int argc, char **argv) argv++; argc--; - if (!dump_opt['R']) { + if (dump_opt['r']) { + error = zdb_copy_object(os, object, argv[1]); + } else if (!dump_opt['R']) { flagbits['d'] = ZOR_FLAG_DIRECTORY; flagbits['f'] = ZOR_FLAG_PLAIN_FILE; flagbits['m'] = ZOR_FLAG_SPACE_MAP; |