zio_decompress_data always ASSERTs successful decompression

This interferes with zdb_read_block trying all the decompression
algorithms when the 'd' flag is specified, as some are
expected to fail.  Also control the output when guessing
algorithms, try the more common compression types first, allow
specifying lsize/psize, and fix an uninitialized variable.

Reviewed-by: Ryan Moeller <ryan@ixsystems.com>
Reviewed-by: Brian Behlendorf <behlendorf1@llnl.gov>
Signed-off-by: Paul Zuchowski <pzuchowski@datto.com>
Closes #9612 
Closes #9630
This commit is contained in:
Paul Zuchowski 2019-12-10 18:51:58 -05:00 committed by Brian Behlendorf
parent b119e2c6f1
commit f0bf435176
6 changed files with 205 additions and 59 deletions

View File

@ -2738,7 +2738,7 @@ static const char *objset_types[DMU_OST_NUMTYPES] = {
static void
dump_objset(objset_t *os)
{
dmu_objset_stats_t dds;
dmu_objset_stats_t dds = { 0 };
uint64_t object, object_count;
uint64_t refdbytes, usedobjs, scratch;
char numbuf[32];
@ -6196,9 +6196,9 @@ dump_zpool(spa_t *spa)
#define ZDB_FLAG_BSWAP 0x0004
#define ZDB_FLAG_GBH 0x0008
#define ZDB_FLAG_INDIRECT 0x0010
#define ZDB_FLAG_PHYS 0x0020
#define ZDB_FLAG_RAW 0x0040
#define ZDB_FLAG_PRINT_BLKPTR 0x0080
#define ZDB_FLAG_RAW 0x0020
#define ZDB_FLAG_PRINT_BLKPTR 0x0040
#define ZDB_FLAG_VERBOSE 0x0080
static int flagbits[256];
@ -6329,11 +6329,30 @@ name:
return (NULL);
}
static boolean_t
zdb_parse_block_sizes(char *sizes, uint64_t *lsize, uint64_t *psize)
{
char *s0, *s1;
if (sizes == NULL)
return (B_FALSE);
s0 = strtok(sizes, "/");
if (s0 == NULL)
return (B_FALSE);
s1 = strtok(NULL, "/");
*lsize = strtoull(s0, NULL, 16);
*psize = s1 ? strtoull(s1, NULL, 16) : *lsize;
return (*lsize >= *psize && *psize > 0);
}
#define ZIO_COMPRESS_MASK(alg) (1ULL << (ZIO_COMPRESS_##alg))
/*
* Read a block from a pool and print it out. The syntax of the
* block descriptor is:
*
* pool:vdev_specifier:offset:size[:flags]
* pool:vdev_specifier:offset:[lsize/]psize[:flags]
*
* pool - The name of the pool you wish to read from
* vdev_specifier - Which vdev (see comment for zdb_vdev_lookup)
@ -6346,8 +6365,8 @@ name:
* e: Byteswap data before dumping
* g: Display data as a gang block header
* i: Display as an indirect block
* p: Do I/O to physical offset
* r: Dump raw data to stdout
* v: Verbose
*
*/
static void
@ -6356,13 +6375,12 @@ zdb_read_block(char *thing, spa_t *spa)
blkptr_t blk, *bp = &blk;
dva_t *dva = bp->blk_dva;
int flags = 0;
uint64_t offset = 0, size = 0, psize = 0, lsize = 0, blkptr_offset = 0;
uint64_t offset = 0, psize = 0, lsize = 0, blkptr_offset = 0;
zio_t *zio;
vdev_t *vd;
abd_t *pabd;
void *lbuf, *buf;
const char *s, *vdev;
char *p, *dup, *flagstr;
char *s, *p, *dup, *vdev, *flagstr, *sizes;
int i, error;
boolean_t borrowed = B_FALSE;
@ -6371,18 +6389,14 @@ zdb_read_block(char *thing, spa_t *spa)
vdev = s ? s : "";
s = strtok(NULL, ":");
offset = strtoull(s ? s : "", NULL, 16);
sizes = strtok(NULL, ":");
s = strtok(NULL, ":");
size = strtoull(s ? s : "", NULL, 16);
s = strtok(NULL, ":");
if (s)
flagstr = strdup(s);
else
flagstr = strdup("");
flagstr = strdup(s ? s : "");
s = NULL;
if (size == 0)
s = "size must not be zero";
if (!IS_P2ALIGNED(size, DEV_BSIZE))
if (!zdb_parse_block_sizes(sizes, &lsize, &psize))
s = "invalid size(s)";
if (!IS_P2ALIGNED(psize, DEV_BSIZE) || !IS_P2ALIGNED(lsize, DEV_BSIZE))
s = "size must be a multiple of sector size";
if (!IS_P2ALIGNED(offset, DEV_BSIZE))
s = "offset must be a multiple of sector size";
@ -6438,9 +6452,6 @@ zdb_read_block(char *thing, spa_t *spa)
vd->vdev_ops->vdev_op_type);
}
psize = size;
lsize = size;
pabd = abd_alloc_for_io(SPA_MAXBLOCKSIZE, B_FALSE);
lbuf = umem_alloc(SPA_MAXBLOCKSIZE, UMEM_NOFAIL);
@ -6497,30 +6508,41 @@ zdb_read_block(char *thing, spa_t *spa)
* We don't know how the data was compressed, so just try
* every decompress function at every inflated blocksize.
*/
enum zio_compress c;
void *lbuf2 = umem_alloc(SPA_MAXBLOCKSIZE, UMEM_NOFAIL);
int cfuncs[ZIO_COMPRESS_FUNCTIONS] = { 0 };
int *cfuncp = cfuncs;
uint64_t maxlsize = SPA_MAXBLOCKSIZE;
uint64_t mask = ZIO_COMPRESS_MASK(ON) | ZIO_COMPRESS_MASK(OFF) |
ZIO_COMPRESS_MASK(INHERIT) | ZIO_COMPRESS_MASK(EMPTY) |
(getenv("ZDB_NO_ZLE") ? ZIO_COMPRESS_MASK(ZLE) : 0);
*cfuncp++ = ZIO_COMPRESS_LZ4;
*cfuncp++ = ZIO_COMPRESS_LZJB;
mask |= ZIO_COMPRESS_MASK(LZ4) | ZIO_COMPRESS_MASK(LZJB);
for (int c = 0; c < ZIO_COMPRESS_FUNCTIONS; c++)
if (((1ULL << c) & mask) == 0)
*cfuncp++ = c;
/*
* XXX - On the one hand, with SPA_MAXBLOCKSIZE at 16MB,
* this could take a while and we should let the user know
* On the one hand, with SPA_MAXBLOCKSIZE at 16MB, this
* could take a while and we should let the user know
* we are not stuck. On the other hand, printing progress
* info gets old after a while. What to do?
* info gets old after a while. User can specify 'v' flag
* to see the progression.
*/
for (lsize = psize + SPA_MINBLOCKSIZE;
lsize <= SPA_MAXBLOCKSIZE; lsize += SPA_MINBLOCKSIZE) {
for (c = 0; c < ZIO_COMPRESS_FUNCTIONS; c++) {
/*
* ZLE can easily decompress non zle stream.
* So have an option to disable it.
*/
if (c == ZIO_COMPRESS_ZLE &&
getenv("ZDB_NO_ZLE"))
continue;
(void) fprintf(stderr,
"Trying %05llx -> %05llx (%s)\n",
(u_longlong_t)psize, (u_longlong_t)lsize,
zio_compress_table[c].ci_name);
if (lsize == psize)
lsize += SPA_MINBLOCKSIZE;
else
maxlsize = lsize;
for (; lsize <= maxlsize; lsize += SPA_MINBLOCKSIZE) {
for (cfuncp = cfuncs; *cfuncp; cfuncp++) {
if (flags & ZDB_FLAG_VERBOSE) {
(void) fprintf(stderr,
"Trying %05llx -> %05llx (%s)\n",
(u_longlong_t)psize,
(u_longlong_t)lsize,
zio_compress_table[*cfuncp].\
ci_name);
}
/*
* We randomize lbuf2, and decompress to both
@ -6529,27 +6551,30 @@ zdb_read_block(char *thing, spa_t *spa)
*/
VERIFY0(random_get_pseudo_bytes(lbuf2, lsize));
if (zio_decompress_data(c, pabd,
if (zio_decompress_data(*cfuncp, pabd,
lbuf, psize, lsize) == 0 &&
zio_decompress_data(c, pabd,
zio_decompress_data(*cfuncp, pabd,
lbuf2, psize, lsize) == 0 &&
bcmp(lbuf, lbuf2, lsize) == 0)
break;
}
if (c != ZIO_COMPRESS_FUNCTIONS)
if (*cfuncp != 0)
break;
}
umem_free(lbuf2, SPA_MAXBLOCKSIZE);
if (lsize > SPA_MAXBLOCKSIZE) {
if (lsize > maxlsize) {
(void) printf("Decompress of %s failed\n", thing);
goto out;
}
buf = lbuf;
size = lsize;
if (*cfuncp == ZIO_COMPRESS_ZLE) {
printf("\nZLE decompression was selected. If you "
"suspect the results are wrong,\ntry avoiding ZLE "
"by setting and exporting ZDB_NO_ZLE=\"true\"\n");
}
} else {
size = psize;
buf = abd_borrow_buf_copy(pabd, size);
buf = abd_borrow_buf_copy(pabd, lsize);
borrowed = B_TRUE;
}
@ -6557,14 +6582,14 @@ zdb_read_block(char *thing, spa_t *spa)
zdb_print_blkptr((blkptr_t *)(void *)
((uintptr_t)buf + (uintptr_t)blkptr_offset), flags);
else if (flags & ZDB_FLAG_RAW)
zdb_dump_block_raw(buf, size, flags);
zdb_dump_block_raw(buf, lsize, flags);
else if (flags & ZDB_FLAG_INDIRECT)
zdb_dump_indirect((blkptr_t *)buf, size / sizeof (blkptr_t),
zdb_dump_indirect((blkptr_t *)buf, lsize / sizeof (blkptr_t),
flags);
else if (flags & ZDB_FLAG_GBH)
zdb_dump_gbh(buf, flags);
else
zdb_dump_block(thing, buf, size, flags);
zdb_dump_block(thing, buf, lsize, flags);
/*
* If :c was specified, iterate through the checksum table to
@ -6628,7 +6653,7 @@ zdb_read_block(char *thing, spa_t *spa)
}
if (borrowed)
abd_return_buf_copy(pabd, buf, size);
abd_return_buf_copy(pabd, buf, lsize);
out:
abd_free(pabd);
@ -7064,8 +7089,8 @@ main(int argc, char **argv)
flagbits['e'] = ZDB_FLAG_BSWAP;
flagbits['g'] = ZDB_FLAG_GBH;
flagbits['i'] = ZDB_FLAG_INDIRECT;
flagbits['p'] = ZDB_FLAG_PHYS;
flagbits['r'] = ZDB_FLAG_RAW;
flagbits['v'] = ZDB_FLAG_VERBOSE;
for (int i = 0; i < argc; i++)
zdb_read_block(argv[i], spa);

View File

@ -63,7 +63,7 @@
.Op Fl A
.Op Fl e Oo Fl V Oc Op Fl p Ar path ...
.Op Fl U Ar cache
.Ar poolname vdev Ns \&: Ns Ar offset Ns \&: Ns Ar size Ns Op : Ns Ar flags
.Ar poolname vdev Ns \&: Ns Ar offset Ns \&: Ns Ar [<lsize>/]<psize> Ns Op : Ns Ar flags
.Nm
.Fl S
.Op Fl AP
@ -228,7 +228,7 @@ This option can be combined with
.Fl v
for increasing verbosity.
.It Xo
.Fl R Ar poolname vdev Ns \&: Ns Ar offset Ns \&: Ns Ar size Ns Op : Ns Ar flags
.Fl R Ar poolname vdev Ns \&: Ns Ar offset Ns \&: Ns Ar [<lsize>/]<psize> Ns Op : Ns Ar flags
.Xc
Read and display a block from the specified device.
By default the block is displayed as a hex dump, but see the description of the
@ -241,8 +241,8 @@ The block is specified in terms of a colon-separated tuple
.Ar offset
.Pq the offset within the vdev
.Ar size
.Pq the size of the block to read
and, optionally,
.Pq the physical size, or logical size / physical size
of the block to read and, optionally,
.Ar flags
.Pq a set of flags, described below .
.Pp
@ -263,6 +263,8 @@ Dump gang block header
Dump indirect block
.It Sy r
Dump raw uninterpreted block data
.It Sy v
Verbose output for guessing compression algorithm
.El
.It Fl s
Report statistics on

View File

@ -159,7 +159,6 @@ zio_decompress_data(enum zio_compress c, abd_t *src, void *dst,
* the checksum. However, for extra protection (e.g. against bitflips
* in non-ECC RAM), we handle this error (and test it).
*/
ASSERT0(ret);
if (zio_decompress_fail_fraction != 0 &&
spa_get_random(zio_decompress_fail_fraction) == 0)
ret = SET_ERROR(EINVAL);

View File

@ -94,7 +94,7 @@ tags = ['functional', 'clean_mirror']
[tests/functional/cli_root/zdb]
tests = ['zdb_001_neg', 'zdb_002_pos', 'zdb_003_pos', 'zdb_004_pos',
'zdb_005_pos', 'zdb_006_pos', 'zdb_checksum']
'zdb_005_pos', 'zdb_006_pos', 'zdb_checksum', 'zdb_decompress']
pre =
post =
tags = ['functional', 'cli_root', 'zdb']

View File

@ -6,4 +6,5 @@ dist_pkgdata_SCRIPTS = \
zdb_004_pos.ksh \
zdb_005_pos.ksh \
zdb_006_pos.ksh \
zdb_checksum.ksh
zdb_checksum.ksh \
zdb_decompress.ksh

View File

@ -0,0 +1,119 @@
#!/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) 2019 by Datto, Inc. All rights reserved.
#
. $STF_SUITE/include/libtest.shlib
#
# Description:
# zdb -R pool <DVA>:d will display the correct data and length
#
# Strategy:
# 1. Create a pool, set compression to lzjb
# 2. Write some identifiable data to a file
# 3. Run zdb -ddddddbbbbbb against the file
# 4. Record the DVA, lsize, and psize of L0 block 0
# 5. Run zdb -R with :d flag and match the data
# 6. Run zdb -R with :dr flags and match the lsize/psize
# 7. Run zdb -R with :dr flags and match the lsize
# 8. Run zdb -R with :dr flags and match the psize
#
function cleanup
{
datasetexists $TESTPOOL && destroy_pool $TESTPOOL
}
log_assert "Verify zdb -R :d flag (decompress) works as expected"
log_onexit cleanup
init_data=$TESTDIR/file1
write_count=256
blksize=4096
pattern="_match__pattern_"
verify_runnable "global"
verify_disk_count "$DISKS" 2
default_mirror_setup_noexit $DISKS
log_must zfs set recordsize=$blksize $TESTPOOL/$TESTFS
log_must zfs set compression=lzjb $TESTPOOL/$TESTFS
# 16 chars 256 times = 4k = block size
typeset four_k=""
for i in {1..$write_count}
do
four_k=$four_k$pattern
done
# write the 4k block 256 times
for i in {1..$write_count}
do
echo $four_k >> $init_data
done
sync_pool $TESTPOOL true
# get object number of file
listing=$(ls -i $init_data)
set -A array $listing
obj=${array[0]}
log_note "file $init_data has object number $obj"
output=$(zdb -ddddddbbbbbb $TESTPOOL/$TESTFS $obj 2> /dev/null \
|grep -m 1 "L0 DVA" |head -n1)
dva=$(grep -oP 'DVA\[0\]=<\K.*?(?=>)' <<< "$output")
log_note "block 0 of $init_data has a DVA of $dva"
# use the length reported by zdb -ddddddbbbbbb
size_str=$(grep -oP 'size=\K.*?(?= )' <<< "$output")
log_note "block size $size_str"
vdev=$(echo "$dva" |awk '{split($0,array,":")} END{print array[1]}')
offset=$(echo "$dva" |awk '{split($0,array,":")} END{print array[2]}')
output=$(zdb -R $TESTPOOL $vdev:$offset:$size_str:d 2> /dev/null)
echo $output |grep $pattern > /dev/null
(( $? != 0 )) && log_fail "zdb -R :d failed to decompress the data properly"
output=$(zdb -R $TESTPOOL $vdev:$offset:$size_str:dr 2> /dev/null)
echo $output |grep $four_k > /dev/null
(( $? != 0 )) && log_fail "zdb -R :dr failed to decompress the data properly"
output=$(zdb -R $TESTPOOL $vdev:$offset:$size_str:dr 2> /dev/null)
result=${#output}
(( $result != $blksize)) && log_fail \
"zdb -R failed to decompress the data to the length (${#output} != $size_str)"
# decompress using lsize
lsize=$(echo $size_str |awk '{split($0,array,"/")} END{print array[1]}')
psize=$(echo $size_str |awk '{split($0,array,"/")} END{print array[2]}')
output=$(zdb -R $TESTPOOL $vdev:$offset:$lsize:dr 2> /dev/null)
result=${#output}
(( $result != $blksize)) && log_fail \
"zdb -R failed to decompress the data (length ${#output} != $blksize)"
# Specifying psize will decompress successfully , but not always to full
# lsize since zdb has to guess lsize incrementally.
output=$(zdb -R $TESTPOOL $vdev:$offset:$psize:dr 2> /dev/null)
result=${#output}
# convert psize to decimal
psize_orig=$psize
psize=${psize%?}
psize=$((16#$psize))
(( $result < $psize)) && log_fail \
"zdb -R failed to decompress the data with psize $psize_orig\
(length ${#output} < $psize)"
log_pass "zdb -R :d flag (decompress) works as expected"