mirror of
https://git.proxmox.com/git/mirror_zfs.git
synced 2024-12-25 02:49:32 +03:00
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:
parent
2b9f8ba673
commit
8653f1de48
@ -33,6 +33,7 @@
|
||||
* under sponsorship from the FreeBSD Foundation.
|
||||
* Copyright (c) 2021 Allan Jude
|
||||
* Copyright (c) 2021 Toomas Soome <tsoome@me.com>
|
||||
* Copyright (c) 2023, Klara Inc.
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
@ -789,6 +790,9 @@ usage(void)
|
||||
"\t\t[<poolname>[/<dataset | objset id>] [<object | range> ...]]\n"
|
||||
"\t%s [-AdiPv] [-e [-V] [-p <path> ...]] [-U <cache>] [-K <key>]\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 -C [-A] [-U <cache>]\n"
|
||||
"\t%s -l [-Aqu] <device>\n"
|
||||
@ -802,7 +806,7 @@ usage(void)
|
||||
"\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, 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);
|
||||
|
@ -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
|
||||
|
@ -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) &&
|
||||
|
@ -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']
|
||||
|
@ -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 \
|
||||
|
55
tests/zfs-tests/tests/functional/cli_root/zdb/zdb_backup.ksh
Executable file
55
tests/zfs-tests/tests/functional/cli_root/zdb/zdb_backup.ksh
Executable 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"
|
Loading…
Reference in New Issue
Block a user