diff --git a/cmd/zdb/zdb.c b/cmd/zdb/zdb.c index 61f1258f72..105d368822 100644 --- a/cmd/zdb/zdb.c +++ b/cmd/zdb/zdb.c @@ -33,6 +33,7 @@ * under sponsorship from the FreeBSD Foundation. * Copyright (c) 2021 Allan Jude * Copyright (c) 2021 Toomas Soome + * Copyright (c) 2023, Klara Inc. */ #include @@ -789,6 +790,9 @@ usage(void) "\t\t[[/] [ ...]]\n" "\t%s [-AdiPv] [-e [-V] [-p ...]] [-U ] [-K ]\n" "\t\t[[/] [ ...]\n" + "\t%s -B [-e [-V] [-p ...]] [-I ]\n" + "\t\t[-o =]... [-t ] [-U ] [-x ]\n" + "\t\t[-K ] / []\n" "\t%s [-v] \n" "\t%s -C [-A] [-U ]\n" "\t%s -l [-Aqu] \n" @@ -802,7 +806,7 @@ usage(void) "\t%s -S [-AP] [-e [-V] [-p ...]] [-U ] " "\n\n", cmdname, cmdname, 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"); @@ -825,6 +829,8 @@ usage(void) (void) fprintf(stderr, " Options to control amount of output:\n"); (void) fprintf(stderr, " -b --block-stats " "block statistics\n"); + (void) fprintf(stderr, " -B --backup " + "backup stream\n"); (void) fprintf(stderr, " -c --checksum " "checksum all metadata (twice for all data) blocks\n"); (void) fprintf(stderr, " -C --config " @@ -4875,6 +4881,81 @@ dump_path(char *ds, char *path, uint64_t *retobj) return (err); } +static int +dump_backup_bytes(objset_t *os, void *buf, int len, void *arg) +{ + const char *p = (const char *)buf; + ssize_t nwritten; + + (void) os; + (void) arg; + + /* Write the data out, handling short writes and signals. */ + while ((nwritten = write(STDOUT_FILENO, p, len)) < len) { + if (nwritten < 0) { + if (errno == EINTR) + continue; + return (errno); + } + p += nwritten; + len -= nwritten; + } + + return (0); +} + +static void +dump_backup(const char *pool, uint64_t objset_id, const char *flagstr) +{ + boolean_t embed = B_FALSE; + boolean_t large_block = B_FALSE; + boolean_t compress = B_FALSE; + boolean_t raw = B_FALSE; + + const char *c; + for (c = flagstr; c != NULL && *c != '\0'; c++) { + switch (*c) { + case 'e': + embed = B_TRUE; + break; + case 'L': + large_block = B_TRUE; + break; + case 'c': + compress = B_TRUE; + break; + case 'w': + raw = B_TRUE; + break; + default: + fprintf(stderr, "dump_backup: invalid flag " + "'%c'\n", *c); + return; + } + } + + if (isatty(STDOUT_FILENO)) { + fprintf(stderr, "dump_backup: stream cannot be written " + "to a terminal\n"); + return; + } + + offset_t off = 0; + dmu_send_outparams_t out = { + .dso_outfunc = dump_backup_bytes, + .dso_dryrun = B_FALSE, + }; + + int err = dmu_send_obj(pool, objset_id, /* fromsnap */0, embed, + large_block, compress, raw, /* saved */ B_FALSE, STDOUT_FILENO, + &off, &out); + if (err != 0) { + fprintf(stderr, "dump_backup: dmu_send_obj: %s\n", + strerror(err)); + return; + } +} + static int zdb_copy_object(objset_t *os, uint64_t srcobj, char *destfile) { @@ -8695,6 +8776,7 @@ main(int argc, char **argv) struct option long_options[] = { {"ignore-assertions", no_argument, NULL, 'A'}, {"block-stats", no_argument, NULL, 'b'}, + {"backup", no_argument, NULL, 'B'}, {"checksum", no_argument, NULL, 'c'}, {"config", no_argument, NULL, 'C'}, {"datasets", no_argument, NULL, 'd'}, @@ -8736,10 +8818,11 @@ main(int argc, char **argv) }; while ((c = getopt_long(argc, argv, - "AbcCdDeEFGhiI:kK:lLmMNo:Op:PqrRsSt:uU:vVx:XYyZ", + "AbBcCdDeEFGhiI:kK:lLmMNo:Op:PqrRsSt:uU:vVx:XYyZ", long_options, NULL)) != -1) { switch (c) { case 'b': + case 'B': case 'c': case 'C': case 'd': @@ -8887,7 +8970,7 @@ main(int argc, char **argv) verbose = MAX(verbose, 1); for (c = 0; c < 256; c++) { - if (dump_all && strchr("AeEFkKlLNOPrRSXy", c) == NULL) + if (dump_all && strchr("ABeEFkKlLNOPrRSXy", c) == NULL) dump_opt[c] = 1; if (dump_opt[c]) dump_opt[c] += verbose; @@ -9073,7 +9156,8 @@ main(int argc, char **argv) checkpoint_pool, error); } - } else if (target_is_spa || dump_opt['R'] || objset_id == 0) { + } else if (target_is_spa || dump_opt['R'] || dump_opt['B'] || + objset_id == 0) { zdb_set_skip_mmp(target); error = spa_open_rewind(target, &spa, FTAG, policy, NULL); @@ -9209,7 +9293,10 @@ retry_lookup: strerror(errno)); } } - if (os != NULL) { + if (dump_opt['B']) { + dump_backup(target, objset_id, + argc > 0 ? argv[0] : NULL); + } else if (os != NULL) { dump_objset(os); } else if (zopt_object_args > 0 && !dump_opt['m']) { dump_objset(spa->spa_meta_objset); diff --git a/man/man8/zdb.8 b/man/man8/zdb.8 index 26c67dabd7..031953c543 100644 --- a/man/man8/zdb.8 +++ b/man/man8/zdb.8 @@ -14,7 +14,7 @@ .\" Copyright (c) 2017 Lawrence Livermore National Security, LLC. .\" Copyright (c) 2017 Intel Corporation. .\" -.Dd October 7, 2020 +.Dd June 4, 2023 .Dt ZDB 8 .Os . @@ -41,6 +41,13 @@ .Ar poolname Ns Op Ar / Ns Ar dataset Ns | Ns Ar objset-ID .Op Ar object Ns | Ns Ar range Ns … .Nm +.Fl B +.Op Fl e Oo Fl V Oc Oo Fl p Ar path Oc Ns … +.Op Fl U Ar cache +.Op Fl K Ar key +.Ar poolname Ns Ar / Ns Ar objset-ID +.Op Ar backup-flags +.Nm .Fl C .Op Fl A .Op Fl U Ar cache @@ -123,6 +130,22 @@ Display options: Display statistics regarding the number, size .Pq logical, physical and allocated and deduplication of blocks. +.It Fl B , -backup +Generate a backup stream, similar to +.Nm zfs Cm send , +but for the numeric objset ID, and without opening the dataset. +This can be useful in recovery scenarios if dataset metadata has become +corrupted but the dataset itself is readable. +The optional +.Ar flags +argument is a string of one or more of the letters +.Sy e , +.Sy L , +.Sy c , +and +.Sy w , +which correspond to the same flags in +.Xr zfs-send 8 . .It Fl c , -checksum Verify the checksum of all metadata blocks while printing block statistics .Po see diff --git a/module/zfs/dmu_send.c b/module/zfs/dmu_send.c index b3ebdec6b4..2d37ed2cdf 100644 --- a/module/zfs/dmu_send.c +++ b/module/zfs/dmu_send.c @@ -1955,7 +1955,7 @@ setup_featureflags(struct dmu_send_params *dspp, objset_t *os, { dsl_dataset_t *to_ds = dspp->to_ds; dsl_pool_t *dp = dspp->dp; -#ifdef _KERNEL + if (dmu_objset_type(os) == DMU_OST_ZFS) { uint64_t version; if (zfs_get_zplprop(os, ZFS_PROP_VERSION, &version) != 0) @@ -1964,7 +1964,6 @@ setup_featureflags(struct dmu_send_params *dspp, objset_t *os, if (version >= ZPL_VERSION_SA) *featureflags |= DMU_BACKUP_FEATURE_SA_SPILL; } -#endif /* raw sends imply large_block_ok */ if ((dspp->rawok || dspp->large_block_ok) && diff --git a/tests/runfiles/common.run b/tests/runfiles/common.run index 10525289a3..342f56d50d 100644 --- a/tests/runfiles/common.run +++ b/tests/runfiles/common.run @@ -128,7 +128,7 @@ tests = ['zdb_002_pos', 'zdb_003_pos', 'zdb_004_pos', 'zdb_005_pos', 'zdb_block_size_histogram', 'zdb_checksum', 'zdb_decompress', 'zdb_display_block', 'zdb_encrypted', 'zdb_label_checksum', 'zdb_object_range_neg', 'zdb_object_range_pos', 'zdb_objset_id', - 'zdb_decompress_zstd', 'zdb_recover', 'zdb_recover_2'] + 'zdb_decompress_zstd', 'zdb_recover', 'zdb_recover_2', 'zdb_backup'] pre = post = tags = ['functional', 'cli_root', 'zdb'] diff --git a/tests/zfs-tests/tests/Makefile.am b/tests/zfs-tests/tests/Makefile.am index 129893cd61..ff65dc1ac2 100644 --- a/tests/zfs-tests/tests/Makefile.am +++ b/tests/zfs-tests/tests/Makefile.am @@ -572,6 +572,7 @@ nobase_dist_datadir_zfs_tests_tests_SCRIPTS += \ functional/cli_root/zdb/zdb_006_pos.ksh \ functional/cli_root/zdb/zdb_args_neg.ksh \ functional/cli_root/zdb/zdb_args_pos.ksh \ + functional/cli_root/zdb/zdb_backup.ksh \ functional/cli_root/zdb/zdb_block_size_histogram.ksh \ functional/cli_root/zdb/zdb_checksum.ksh \ functional/cli_root/zdb/zdb_decompress.ksh \ diff --git a/tests/zfs-tests/tests/functional/cli_root/zdb/zdb_backup.ksh b/tests/zfs-tests/tests/functional/cli_root/zdb/zdb_backup.ksh new file mode 100755 index 0000000000..d98ab86ab6 --- /dev/null +++ b/tests/zfs-tests/tests/functional/cli_root/zdb/zdb_backup.ksh @@ -0,0 +1,55 @@ +#!/bin/ksh + +# +# This file and its contents are supplied under the terms of the +# Common Development and Distribution License ("CDDL"), version 1.0. +# You may only use this file in accordance with the terms of version +# 1.0 of the CDDL. +# +# A full copy of the text of the CDDL should have accompanied this +# source. A copy of the CDDL is also available via the Internet at +# http://www.illumos.org/license/CDDL. +# + +# +# Copyright (c) 2023, Klara Inc. +# + +. $STF_SUITE/include/libtest.shlib + +write_count=8 +blksize=131072 + +tmpfile=$TEST_BASE_DIR/tmpfile + +function cleanup +{ + datasetexists $TESTPOOL && destroy_pool $TESTPOOL + rm $tmpfile.1 $tmpfile.2 +} + +log_onexit cleanup + +log_assert "Verify that zfs send and zdb -B produce the same stream" + +verify_runnable "global" +verify_disk_count "$DISKS" 2 + +default_mirror_setup_noexit $DISKS +file_write -o create -w -f $TESTDIR/file -b $blksize -c $write_count + +snap=$TESTPOOL/$TESTFS@snap +log_must zfs snapshot $snap +typeset -i objsetid=$(zfs get -Ho value objsetid $snap) + +sync_pool $TESTPOOL + +log_must eval "zfs send -ecL $snap > $tmpfile.1" +log_must eval "zdb -B $TESTPOOL/$objsetid ecL > $tmpfile.2" + +typeset sum1=$(cat $tmpfile.1 | md5sum) +typeset sum2=$(cat $tmpfile.2 | md5sum) + +log_must test "$sum1" = "$sum2" + +log_pass "zfs send and zdb -B produce the same stream"