zdb: add -B option to generate backup stream

This is more-or-less like `zfs send`, but specifying the snapshot by its
objset id for situations where it can't be referenced any other way.

Sponsored-By: Klara, Inc.
Reviewed-by: Tino Reichardt <milky-zfs@mcmilk.de>
Reviewed-by: WHR <msl0000023508@gmail.com>
Signed-off-by: Rob Norris <rob.norris@klarasystems.com>
Closes #14642
This commit is contained in:
Rob Norris 2023-03-15 18:18:10 +11:00 committed by Brian Behlendorf
parent 2b9f8ba673
commit 8653f1de48
6 changed files with 174 additions and 9 deletions

View File

@ -33,6 +33,7 @@
* under sponsorship from the FreeBSD Foundation. * under sponsorship from the FreeBSD Foundation.
* Copyright (c) 2021 Allan Jude * Copyright (c) 2021 Allan Jude
* Copyright (c) 2021 Toomas Soome <tsoome@me.com> * Copyright (c) 2021 Toomas Soome <tsoome@me.com>
* Copyright (c) 2023, Klara Inc.
*/ */
#include <stdio.h> #include <stdio.h>
@ -789,6 +790,9 @@ usage(void)
"\t\t[<poolname>[/<dataset | objset id>] [<object | range> ...]]\n" "\t\t[<poolname>[/<dataset | objset id>] [<object | range> ...]]\n"
"\t%s [-AdiPv] [-e [-V] [-p <path> ...]] [-U <cache>] [-K <key>]\n" "\t%s [-AdiPv] [-e [-V] [-p <path> ...]] [-U <cache>] [-K <key>]\n"
"\t\t[<poolname>[/<dataset | objset id>] [<object | range> ...]\n" "\t\t[<poolname>[/<dataset | objset id>] [<object | range> ...]\n"
"\t%s -B [-e [-V] [-p <path> ...]] [-I <inflight I/Os>]\n"
"\t\t[-o <var>=<value>]... [-t <txg>] [-U <cache>] [-x <dumpdir>]\n"
"\t\t[-K <key>] <poolname>/<objset id> [<backupflags>]\n"
"\t%s [-v] <bookmark>\n" "\t%s [-v] <bookmark>\n"
"\t%s -C [-A] [-U <cache>]\n" "\t%s -C [-A] [-U <cache>]\n"
"\t%s -l [-Aqu] <device>\n" "\t%s -l [-Aqu] <device>\n"
@ -802,7 +806,7 @@ usage(void)
"\t%s -S [-AP] [-e [-V] [-p <path> ...]] [-U <cache>] " "\t%s -S [-AP] [-e [-V] [-p <path> ...]] [-U <cache>] "
"<poolname>\n\n", "<poolname>\n\n",
cmdname, cmdname, cmdname, cmdname, cmdname, cmdname, cmdname, 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 " (void) fprintf(stderr, " Dataset name must include at least one "
"separator character '/' or '@'\n"); "separator character '/' or '@'\n");
@ -825,6 +829,8 @@ usage(void)
(void) fprintf(stderr, " Options to control amount of output:\n"); (void) fprintf(stderr, " Options to control amount of output:\n");
(void) fprintf(stderr, " -b --block-stats " (void) fprintf(stderr, " -b --block-stats "
"block statistics\n"); "block statistics\n");
(void) fprintf(stderr, " -B --backup "
"backup stream\n");
(void) fprintf(stderr, " -c --checksum " (void) fprintf(stderr, " -c --checksum "
"checksum all metadata (twice for all data) blocks\n"); "checksum all metadata (twice for all data) blocks\n");
(void) fprintf(stderr, " -C --config " (void) fprintf(stderr, " -C --config "
@ -4875,6 +4881,81 @@ dump_path(char *ds, char *path, uint64_t *retobj)
return (err); 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 static int
zdb_copy_object(objset_t *os, uint64_t srcobj, char *destfile) zdb_copy_object(objset_t *os, uint64_t srcobj, char *destfile)
{ {
@ -8695,6 +8776,7 @@ main(int argc, char **argv)
struct option long_options[] = { struct option long_options[] = {
{"ignore-assertions", no_argument, NULL, 'A'}, {"ignore-assertions", no_argument, NULL, 'A'},
{"block-stats", no_argument, NULL, 'b'}, {"block-stats", no_argument, NULL, 'b'},
{"backup", no_argument, NULL, 'B'},
{"checksum", no_argument, NULL, 'c'}, {"checksum", no_argument, NULL, 'c'},
{"config", no_argument, NULL, 'C'}, {"config", no_argument, NULL, 'C'},
{"datasets", no_argument, NULL, 'd'}, {"datasets", no_argument, NULL, 'd'},
@ -8736,10 +8818,11 @@ main(int argc, char **argv)
}; };
while ((c = getopt_long(argc, 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) { long_options, NULL)) != -1) {
switch (c) { switch (c) {
case 'b': case 'b':
case 'B':
case 'c': case 'c':
case 'C': case 'C':
case 'd': case 'd':
@ -8887,7 +8970,7 @@ main(int argc, char **argv)
verbose = MAX(verbose, 1); verbose = MAX(verbose, 1);
for (c = 0; c < 256; c++) { for (c = 0; c < 256; c++) {
if (dump_all && strchr("AeEFkKlLNOPrRSXy", c) == NULL) if (dump_all && strchr("ABeEFkKlLNOPrRSXy", c) == NULL)
dump_opt[c] = 1; dump_opt[c] = 1;
if (dump_opt[c]) if (dump_opt[c])
dump_opt[c] += verbose; dump_opt[c] += verbose;
@ -9073,7 +9156,8 @@ main(int argc, char **argv)
checkpoint_pool, error); 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); zdb_set_skip_mmp(target);
error = spa_open_rewind(target, &spa, FTAG, policy, error = spa_open_rewind(target, &spa, FTAG, policy,
NULL); NULL);
@ -9209,7 +9293,10 @@ retry_lookup:
strerror(errno)); 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); dump_objset(os);
} else if (zopt_object_args > 0 && !dump_opt['m']) { } else if (zopt_object_args > 0 && !dump_opt['m']) {
dump_objset(spa->spa_meta_objset); dump_objset(spa->spa_meta_objset);

View File

@ -14,7 +14,7 @@
.\" Copyright (c) 2017 Lawrence Livermore National Security, LLC. .\" Copyright (c) 2017 Lawrence Livermore National Security, LLC.
.\" Copyright (c) 2017 Intel Corporation. .\" Copyright (c) 2017 Intel Corporation.
.\" .\"
.Dd October 7, 2020 .Dd June 4, 2023
.Dt ZDB 8 .Dt ZDB 8
.Os .Os
. .
@ -41,6 +41,13 @@
.Ar poolname Ns Op Ar / Ns Ar dataset Ns | Ns Ar objset-ID .Ar poolname Ns Op Ar / Ns Ar dataset Ns | Ns Ar objset-ID
.Op Ar object Ns | Ns Ar range Ns .Op Ar object Ns | Ns Ar range Ns
.Nm .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 .Fl C
.Op Fl A .Op Fl A
.Op Fl U Ar cache .Op Fl U Ar cache
@ -123,6 +130,22 @@ Display options:
Display statistics regarding the number, size Display statistics regarding the number, size
.Pq logical, physical and allocated .Pq logical, physical and allocated
and deduplication of blocks. 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 .It Fl c , -checksum
Verify the checksum of all metadata blocks while printing block statistics Verify the checksum of all metadata blocks while printing block statistics
.Po see .Po see

View File

@ -1955,7 +1955,7 @@ setup_featureflags(struct dmu_send_params *dspp, objset_t *os,
{ {
dsl_dataset_t *to_ds = dspp->to_ds; dsl_dataset_t *to_ds = dspp->to_ds;
dsl_pool_t *dp = dspp->dp; dsl_pool_t *dp = dspp->dp;
#ifdef _KERNEL
if (dmu_objset_type(os) == DMU_OST_ZFS) { if (dmu_objset_type(os) == DMU_OST_ZFS) {
uint64_t version; uint64_t version;
if (zfs_get_zplprop(os, ZFS_PROP_VERSION, &version) != 0) 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) if (version >= ZPL_VERSION_SA)
*featureflags |= DMU_BACKUP_FEATURE_SA_SPILL; *featureflags |= DMU_BACKUP_FEATURE_SA_SPILL;
} }
#endif
/* raw sends imply large_block_ok */ /* raw sends imply large_block_ok */
if ((dspp->rawok || dspp->large_block_ok) && if ((dspp->rawok || dspp->large_block_ok) &&

View File

@ -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_block_size_histogram', 'zdb_checksum', 'zdb_decompress',
'zdb_display_block', 'zdb_encrypted', 'zdb_label_checksum', 'zdb_display_block', 'zdb_encrypted', 'zdb_label_checksum',
'zdb_object_range_neg', 'zdb_object_range_pos', 'zdb_objset_id', '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 = pre =
post = post =
tags = ['functional', 'cli_root', 'zdb'] tags = ['functional', 'cli_root', 'zdb']

View File

@ -572,6 +572,7 @@ nobase_dist_datadir_zfs_tests_tests_SCRIPTS += \
functional/cli_root/zdb/zdb_006_pos.ksh \ functional/cli_root/zdb/zdb_006_pos.ksh \
functional/cli_root/zdb/zdb_args_neg.ksh \ functional/cli_root/zdb/zdb_args_neg.ksh \
functional/cli_root/zdb/zdb_args_pos.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_block_size_histogram.ksh \
functional/cli_root/zdb/zdb_checksum.ksh \ functional/cli_root/zdb/zdb_checksum.ksh \
functional/cli_root/zdb/zdb_decompress.ksh \ functional/cli_root/zdb/zdb_decompress.ksh \

View File

@ -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"