Skip to content

Commit 393e692

Browse files
authored
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 <lundman@lundman.net> Reviewed-by: Brian Behlendorf <behlendorf1@llnl.gov> Signed-off-by: Allan Jude <allan@klarasystems.com> Closes #11027
1 parent b2c5904 commit 393e692

File tree

7 files changed

+240
-15
lines changed

7 files changed

+240
-15
lines changed

cmd/zdb/zdb.c

Lines changed: 106 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
*
3232
* [1] Portions of this software were developed by Allan Jude
3333
* under sponsorship from the FreeBSD Foundation.
34+
* Copyright (c) 2021 Allan Jude
3435
*/
3536

3637
#include <stdio.h>
@@ -755,13 +756,14 @@ usage(void)
755756
"\t%s -m [-AFLPX] [-e [-V] [-p <path> ...]] [-t <txg>] "
756757
"[-U <cache>]\n\t\t<poolname> [<vdev> [<metaslab> ...]]\n"
757758
"\t%s -O <dataset> <path>\n"
759+
"\t%s -r <dataset> <path> <destination>\n"
758760
"\t%s -R [-A] [-e [-V] [-p <path> ...]] [-U <cache>]\n"
759761
"\t\t<poolname> <vdev>:<offset>:<size>[:<flags>]\n"
760762
"\t%s -E [-A] word0:word1:...:word15\n"
761763
"\t%s -S [-AP] [-e [-V] [-p <path> ...]] [-U <cache>] "
762764
"<poolname>\n\n",
763765
cmdname, cmdname, cmdname, cmdname, cmdname, cmdname, cmdname,
764-
cmdname, cmdname, cmdname);
766+
cmdname, cmdname, cmdname, cmdname);
765767

766768
(void) fprintf(stderr, " Dataset name must include at least one "
767769
"separator character '/' or '@'\n");
@@ -800,6 +802,7 @@ usage(void)
800802
(void) fprintf(stderr, " -m metaslabs\n");
801803
(void) fprintf(stderr, " -M metaslab groups\n");
802804
(void) fprintf(stderr, " -O perform object lookups by path\n");
805+
(void) fprintf(stderr, " -r copy an object by path to file\n");
803806
(void) fprintf(stderr, " -R read and display block from a "
804807
"device\n");
805808
(void) fprintf(stderr, " -s report stats on zdb's I/O\n");
@@ -4490,7 +4493,7 @@ static char curpath[PATH_MAX];
44904493
* for the last one.
44914494
*/
44924495
static int
4493-
dump_path_impl(objset_t *os, uint64_t obj, char *name)
4496+
dump_path_impl(objset_t *os, uint64_t obj, char *name, uint64_t *retobj)
44944497
{
44954498
int err;
44964499
boolean_t header = B_TRUE;
@@ -4540,10 +4543,15 @@ dump_path_impl(objset_t *os, uint64_t obj, char *name)
45404543
switch (doi.doi_type) {
45414544
case DMU_OT_DIRECTORY_CONTENTS:
45424545
if (s != NULL && *(s + 1) != '\0')
4543-
return (dump_path_impl(os, child_obj, s + 1));
4546+
return (dump_path_impl(os, child_obj, s + 1, retobj));
45444547
/*FALLTHROUGH*/
45454548
case DMU_OT_PLAIN_FILE_CONTENTS:
4546-
dump_object(os, child_obj, dump_opt['v'], &header, NULL, 0);
4549+
if (retobj != NULL) {
4550+
*retobj = child_obj;
4551+
} else {
4552+
dump_object(os, child_obj, dump_opt['v'], &header,
4553+
NULL, 0);
4554+
}
45474555
return (0);
45484556
default:
45494557
(void) fprintf(stderr, "object %llu has non-file/directory "
@@ -4558,7 +4566,7 @@ dump_path_impl(objset_t *os, uint64_t obj, char *name)
45584566
* Dump the blocks for the object specified by path inside the dataset.
45594567
*/
45604568
static int
4561-
dump_path(char *ds, char *path)
4569+
dump_path(char *ds, char *path, uint64_t *retobj)
45624570
{
45634571
int err;
45644572
objset_t *os;
@@ -4578,12 +4586,89 @@ dump_path(char *ds, char *path)
45784586

45794587
(void) snprintf(curpath, sizeof (curpath), "dataset=%s path=/", ds);
45804588

4581-
err = dump_path_impl(os, root_obj, path);
4589+
err = dump_path_impl(os, root_obj, path, retobj);
45824590

45834591
close_objset(os, FTAG);
45844592
return (err);
45854593
}
45864594

4595+
static int
4596+
zdb_copy_object(objset_t *os, uint64_t srcobj, char *destfile)
4597+
{
4598+
int err = 0;
4599+
uint64_t size, readsize, oursize, offset;
4600+
ssize_t writesize;
4601+
sa_handle_t *hdl;
4602+
4603+
(void) printf("Copying object %" PRIu64 " to file %s\n", srcobj,
4604+
destfile);
4605+
4606+
VERIFY3P(os, ==, sa_os);
4607+
if ((err = sa_handle_get(os, srcobj, NULL, SA_HDL_PRIVATE, &hdl))) {
4608+
(void) printf("Failed to get handle for SA znode\n");
4609+
return (err);
4610+
}
4611+
if ((err = sa_lookup(hdl, sa_attr_table[ZPL_SIZE], &size, 8))) {
4612+
(void) sa_handle_destroy(hdl);
4613+
return (err);
4614+
}
4615+
(void) sa_handle_destroy(hdl);
4616+
4617+
(void) printf("Object %" PRIu64 " is %" PRIu64 " bytes\n", srcobj,
4618+
size);
4619+
if (size == 0) {
4620+
return (EINVAL);
4621+
}
4622+
4623+
int fd = open(destfile, O_WRONLY | O_CREAT | O_TRUNC, 0644);
4624+
/*
4625+
* We cap the size at 1 mebibyte here to prevent
4626+
* allocation failures and nigh-infinite printing if the
4627+
* object is extremely large.
4628+
*/
4629+
oursize = MIN(size, 1 << 20);
4630+
offset = 0;
4631+
char *buf = kmem_alloc(oursize, KM_NOSLEEP);
4632+
if (buf == NULL) {
4633+
return (ENOMEM);
4634+
}
4635+
4636+
while (offset < size) {
4637+
readsize = MIN(size - offset, 1 << 20);
4638+
err = dmu_read(os, srcobj, offset, readsize, buf, 0);
4639+
if (err != 0) {
4640+
(void) printf("got error %u from dmu_read\n", err);
4641+
kmem_free(buf, oursize);
4642+
return (err);
4643+
}
4644+
if (dump_opt['v'] > 3) {
4645+
(void) printf("Read offset=%" PRIu64 " size=%" PRIu64
4646+
" error=%d\n", offset, readsize, err);
4647+
}
4648+
4649+
writesize = write(fd, buf, readsize);
4650+
if (writesize < 0) {
4651+
err = errno;
4652+
break;
4653+
} else if (writesize != readsize) {
4654+
/* Incomplete write */
4655+
(void) fprintf(stderr, "Short write, only wrote %llu of"
4656+
" %" PRIu64 " bytes, exiting...\n",
4657+
(u_longlong_t)writesize, readsize);
4658+
break;
4659+
}
4660+
4661+
offset += readsize;
4662+
}
4663+
4664+
(void) close(fd);
4665+
4666+
if (buf != NULL)
4667+
kmem_free(buf, oursize);
4668+
4669+
return (err);
4670+
}
4671+
45874672
static int
45884673
dump_label(const char *dev)
45894674
{
@@ -8167,6 +8252,7 @@ main(int argc, char **argv)
81678252
nvlist_t *policy = NULL;
81688253
uint64_t max_txg = UINT64_MAX;
81698254
int64_t objset_id = -1;
8255+
uint64_t object;
81708256
int flags = ZFS_IMPORT_MISSING_LOG;
81718257
int rewind = ZPOOL_NEVER_REWIND;
81728258
char *spa_config_path_env, *objset_str;
@@ -8195,7 +8281,7 @@ main(int argc, char **argv)
81958281
zfs_btree_verify_intensity = 3;
81968282

81978283
while ((c = getopt(argc, argv,
8198-
"AbcCdDeEFGhiI:klLmMo:Op:PqRsSt:uU:vVx:XYyZ")) != -1) {
8284+
"AbcCdDeEFGhiI:klLmMo:Op:PqrRsSt:uU:vVx:XYyZ")) != -1) {
81998285
switch (c) {
82008286
case 'b':
82018287
case 'c':
@@ -8210,6 +8296,7 @@ main(int argc, char **argv)
82108296
case 'm':
82118297
case 'M':
82128298
case 'O':
8299+
case 'r':
82138300
case 'R':
82148301
case 's':
82158302
case 'S':
@@ -8299,7 +8386,7 @@ main(int argc, char **argv)
82998386
(void) fprintf(stderr, "-p option requires use of -e\n");
83008387
usage();
83018388
}
8302-
if (dump_opt['d']) {
8389+
if (dump_opt['d'] || dump_opt['r']) {
83038390
/* <pool>[/<dataset | objset id> is accepted */
83048391
if (argv[2] && (objset_str = strchr(argv[2], '/')) != NULL &&
83058392
objset_str++ != NULL) {
@@ -8358,7 +8445,7 @@ main(int argc, char **argv)
83588445
verbose = MAX(verbose, 1);
83598446

83608447
for (c = 0; c < 256; c++) {
8361-
if (dump_all && strchr("AeEFklLOPRSXy", c) == NULL)
8448+
if (dump_all && strchr("AeEFklLOPrRSXy", c) == NULL)
83628449
dump_opt[c] = 1;
83638450
if (dump_opt[c])
83648451
dump_opt[c] += verbose;
@@ -8394,7 +8481,13 @@ main(int argc, char **argv)
83948481
if (argc != 2)
83958482
usage();
83968483
dump_opt['v'] = verbose + 3;
8397-
return (dump_path(argv[0], argv[1]));
8484+
return (dump_path(argv[0], argv[1], NULL));
8485+
}
8486+
if (dump_opt['r']) {
8487+
if (argc != 3)
8488+
usage();
8489+
dump_opt['v'] = verbose;
8490+
error = dump_path(argv[0], argv[1], &object);
83988491
}
83998492

84008493
if (dump_opt['X'] || dump_opt['F'])
@@ -8572,7 +8665,9 @@ main(int argc, char **argv)
85728665

85738666
argv++;
85748667
argc--;
8575-
if (!dump_opt['R']) {
8668+
if (dump_opt['r']) {
8669+
error = zdb_copy_object(os, object, argv[1]);
8670+
} else if (!dump_opt['R']) {
85768671
flagbits['d'] = ZOR_FLAG_DIRECTORY;
85778672
flagbits['f'] = ZOR_FLAG_PLAIN_FILE;
85788673
flagbits['m'] = ZOR_FLAG_SPACE_MAP;

man/man8/zdb.8

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
.\" Copyright (c) 2017 Lawrence Livermore National Security, LLC.
1616
.\" Copyright (c) 2017 Intel Corporation.
1717
.\"
18-
.Dd April 14, 2019
18+
.Dd October 7, 2020
1919
.Dt ZDB 8 SMM
2020
.Os
2121
.Sh NAME
@@ -60,6 +60,9 @@
6060
.Fl O
6161
.Ar dataset path
6262
.Nm
63+
.Fl r
64+
.Ar dataset path destination
65+
.Nm
6366
.Fl R
6467
.Op Fl A
6568
.Op Fl e Oo Fl V Oc Op Fl p Ar path ...
@@ -274,6 +277,19 @@ must be relative to the root of
274277
This option can be combined with
275278
.Fl v
276279
for increasing verbosity.
280+
.It Fl r Ar dataset path destination
281+
Copy the specified
282+
.Ar path
283+
inside of the
284+
.Ar dataset
285+
to the specified destination.
286+
Specified
287+
.Ar path
288+
must be relative to the root of
289+
.Ar dataset .
290+
This option can be combined with
291+
.Fl v
292+
for increasing verbosity.
277293
.It Xo
278294
.Fl R Ar poolname vdev Ns \&: Ns Ar offset Ns \&: Ns Ar [<lsize>/]<psize> Ns Op : Ns Ar flags
279295
.Xc

tests/runfiles/common.run

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -119,7 +119,7 @@ tests = ['zdb_002_pos', 'zdb_003_pos', 'zdb_004_pos', 'zdb_005_pos',
119119
'zdb_006_pos', 'zdb_args_neg', 'zdb_args_pos',
120120
'zdb_block_size_histogram', 'zdb_checksum', 'zdb_decompress',
121121
'zdb_display_block', 'zdb_object_range_neg', 'zdb_object_range_pos',
122-
'zdb_objset_id', 'zdb_decompress_zstd']
122+
'zdb_objset_id', 'zdb_decompress_zstd', 'zdb_recover', 'zdb_recover_2']
123123
pre =
124124
post =
125125
tags = ['functional', 'cli_root', 'zdb']

tests/zfs-tests/tests/functional/cli_root/zdb/Makefile.am

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,4 +14,6 @@ dist_pkgdata_SCRIPTS = \
1414
zdb_object_range_neg.ksh \
1515
zdb_object_range_pos.ksh \
1616
zdb_display_block.ksh \
17-
zdb_objset_id.ksh
17+
zdb_objset_id.ksh \
18+
zdb_recover.ksh \
19+
zdb_recover_2.ksh

tests/zfs-tests/tests/functional/cli_root/zdb/zdb_args_neg.ksh

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ set -A args "create" "add" "destroy" "import fakepool" \
5656
"add mirror fakepool" "add raidz fakepool" \
5757
"add raidz1 fakepool" "add raidz2 fakepool" \
5858
"setvprop" "blah blah" "-%" "--?" "-*" "-=" \
59-
"-a" "-f" "-g" "-j" "-n" "-o" "-p" "-p /tmp" "-r" \
59+
"-a" "-f" "-g" "-j" "-n" "-o" "-p" "-p /tmp" \
6060
"-t" "-w" "-z" "-E" "-H" "-I" "-J" "-K" \
6161
"-N" "-Q" "-R" "-T" "-W"
6262

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
#!/bin/ksh
2+
3+
#
4+
# This file and its contents are supplied under the terms of the
5+
# Common Development and Distribution License ("CDDL"), version 1.0.
6+
# You may only use this file in accordance with the terms of version
7+
# 1.0 of the CDDL.
8+
#
9+
# A full copy of the text of the CDDL should have accompanied this
10+
# source. A copy of the CDDL is also available via the Internet at
11+
# http://www.illumos.org/license/CDDL.
12+
#
13+
14+
#
15+
# Copyright (c) 2021 by Allan Jude.
16+
#
17+
18+
. $STF_SUITE/include/libtest.shlib
19+
20+
#
21+
# Description:
22+
# zdb -r <dataset> <path> <destination>
23+
# Will extract <path> (relative to <dataset>) to the file <destination>
24+
# Similar to -R, except it does the work for you to find each record
25+
#
26+
# Strategy:
27+
# 1. Create a pool
28+
# 2. Write some data to a file
29+
# 3. Extract the file
30+
# 4. Compare the file to the original
31+
#
32+
33+
function cleanup
34+
{
35+
datasetexists $TESTPOOL && destroy_pool $TESTPOOL
36+
rm $tmpfile
37+
}
38+
39+
log_assert "Verify zdb -r <dataset> <path> <dest> extract the correct data."
40+
log_onexit cleanup
41+
init_data=$TESTDIR/file1
42+
tmpfile="$TEST_BASE_DIR/zdb-recover"
43+
write_count=8
44+
blksize=131072
45+
verify_runnable "global"
46+
verify_disk_count "$DISKS" 2
47+
48+
default_mirror_setup_noexit $DISKS
49+
file_write -o create -w -f $init_data -b $blksize -c $write_count
50+
log_must zpool sync $TESTPOOL
51+
52+
output=$(zdb -r $TESTPOOL/$TESTFS file1 $tmpfile)
53+
log_must cmp $init_data $tmpfile
54+
55+
log_pass "zdb -r <dataset> <path> <dest> extracts the correct data."

0 commit comments

Comments
 (0)