zdb: add support for object ranges for zdb -d

Allow a range of object identifiers to dump with -d. This may
be useful when dumping a large dataset and you want to break
it up into multiple phases, or to resume where a previous scan
left off. Object type selection flags are supported to reduce
the performance overhead of verbosely dumping unwanted objects,
and to reduce the amount of post-processing work needed to
filter out unwanted objects from zdb output.

This change extends existing syntax in a backward-compatible
way. That is, the base case of a range is to specify a single
object identifier to dump. Ranges and object identifiers can
be intermixed as command line parameters.

Usage synopsis:

    Object ranges take the form <start>:<end>[:<flags>]
        start    Starting object number
        end      Ending object number, or -1 for no upper bound
        flags    Optional flags to select object types:
         A    All objects (this is the default)
         d    ZFS directories
         f    ZFS files
         m    SPA space maps
         z    ZAPs
         -    Negate effect of next flag

Examples:

 # Dump all file objects
 zdb -dd tank/fish 0:-1:f

 # Dump all file and directory objects
 zdb -dd tank/fish 0:-1:fd

 # Dump all types except file and directory objects
 zdb -dd tank/fish 0:-1:A-f-d

 # Dump object IDs in a specific range
 zdb -dd tank/fish 1000:2000

Reviewed-by: Brian Behlendorf <behlendorf1@llnl.gov>
Reviewed-by: Ryan Moeller <ryan@ixsystems.com>
Reviewed-by: Paul Zuchowski <pzuchowski@datto.com>
Signed-off-by: Ned Bass <bass6@llnl.gov>
Closes #9832
This commit is contained in:
Ned Bass
2020-01-24 11:00:46 -08:00
committed by Brian Behlendorf
parent 8e9e90bba3
commit a3403164d7
6 changed files with 555 additions and 50 deletions
@@ -8,4 +8,6 @@ dist_pkgdata_SCRIPTS = \
zdb_006_pos.ksh \
zdb_checksum.ksh \
zdb_decompress.ksh \
zdb_objset_id.ksh
zdb_objset_id.ksh \
zdb_object_range_neg.ksh \
zdb_object_range_pos.ksh
@@ -0,0 +1,72 @@
#!/bin/ksh -p
#
# CDDL HEADER START
#
# 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.
#
# CDDL HEADER END
#
# Copyright (c) 2020 Lawrence Livermore National Security, LLC.
. $STF_SUITE/include/libtest.shlib
#
# Description:
# A badly formed object range parameter passed to zdb -dd should
# return an error.
#
# Strategy:
# 1. Create a pool
# 2. Run zdb -dd with assorted invalid object range arguments and
# confirm it fails as expected
# 3. Run zdb -dd with an invalid object identifier and
# confirm it fails as expected
function cleanup
{
datasetexists $TESTPOOL && destroy_pool $TESTPOOL
}
log_assert "Execute zdb using invalid object range parameters."
log_onexit cleanup
verify_runnable "both"
verify_disk_count "$DISKS" 2
default_mirror_setup_noexit $DISKS
log_must zpool sync
set -A bad_flags a b c e g h i j k l n o p q r s t u v w x y \
B C D E F G H I J K L M N O P Q R S T U V W X Y Z \
0 1 2 3 4 5 6 7 8 9 _ - + % . , :
typeset -i i=0
while [[ $i -lt ${#bad_flags[*]} ]]; do
log_mustnot zdb -dd $TESTPOOL 0:1:${bad_flags[i]}
log_mustnot zdb -dd $TESTPOOL 0:1:A-${bad_flags[i]}
((i = i + 1))
done
set -A bad_ranges ":" "::" ":::" ":0" "0:" "0:1:" "0:1::" "0::f" "0a:1" \
"a0:1" "a:1" "0:a" "0:1a" "0:a1" "a:b0" "a:0b" "0:1:A-" "1:0" \
"0:1:f:f" "0:1:f:"
i=0
while [[ $i -lt ${#bad_ranges[*]} ]]; do
log_mustnot zdb -dd $TESTPOOL ${bad_ranges[i]}
((i = i + 1))
done
# Specifying a non-existent object identifier returns an error
obj_id_highest=$(zdb -P -dd $TESTPOOL/$TESTFS 2>/dev/null |
egrep "^ +-?([0-9]+ +){7}" | sort -n | tail -n 1 | awk '{print $1}')
obj_id_invalid=$(( $obj_id_highest + 1 ))
log_mustnot zdb -dd $TESTPOOL/$TESTFS $obj_id_invalid
log_pass "Badly formed zdb object range parameters fail as expected."
@@ -0,0 +1,171 @@
#!/bin/ksh -p
#
# CDDL HEADER START
#
# 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.
#
# CDDL HEADER END
#
# Copyright (c) 2020 Lawrence Livermore National Security, LLC.
. $STF_SUITE/include/libtest.shlib
#
# Description:
# Object range parameters passed to zdb -dd work correctly.
#
# Strategy:
# 1. Create a pool
# 2. Create some files
# 3. Run zdb -dd with assorted object range arguments and verify output
function cleanup
{
datasetexists $TESTPOOL && destroy_pool $TESTPOOL
}
#
# Print objects in @dataset with identifiers greater than or equal to
# @begin and less than or equal to @end, without using object range
# parameters.
#
function get_object_list_range
{
dataset=$1
begin=$2
end=$3
get_object_list $dataset |
while read line; do
obj=$(echo $line | awk '{print $1}')
if [[ $obj -ge $begin && $obj -le $end ]] ; then
echo "$line"
elif [[ $obj -gt $end ]] ; then
break
fi
done
}
#
# Print just the list of objects from 'zdb -dd' with leading whitespace
# trimmed, discarding other zdb output, sorted by object identifier.
# Caller must pass in the dataset argument at minimum.
#
function get_object_list
{
zdb -P -dd $@ 2>/dev/null |
egrep "^ +-?([0-9]+ +){7}" |
sed 's/^[[:space:]]*//' |
sort -n
}
log_assert "Verify zdb -dd object range arguments work correctly."
log_onexit cleanup
verify_runnable "both"
verify_disk_count "$DISKS" 2
default_mirror_setup_noexit $DISKS
for x in $(seq 0 7); do
touch $TESTDIR/file$x
mkdir $TESTDIR/dir$x
done
log_must zpool sync
# Get list of all objects, but filter out user/group objects which don't
# appear when using object or object range arguments
all_objects=$(get_object_list $TESTPOOL/$TESTFS | grep -v 'used$')
# Range 0:-1 gets all objects
expected=$all_objects
actual=$(get_object_list $TESTPOOL/$TESTFS 0:-1)
log_must test "\n$actual\n" == "\n$expected\n"
# Range 0:-1:A gets all objects
expected=$all_objects
actual=$(get_object_list $TESTPOOL/$TESTFS 0:-1:A)
log_must test "\n$actual\n" == "\n$expected\n"
# Range 0:-1:f must output all file objects
expected=$(grep "ZFS plain file" <<< $all_objects)
actual=$(get_object_list $TESTPOOL/$TESTFS 0:-1:f)
log_must test "\n$actual\n" == "\n$expected\n"
# Range 0:-1:d must output all directory objects
expected=$(grep "ZFS directory" <<< $all_objects)
actual=$(get_object_list $TESTPOOL/$TESTFS 0:-1:d)
log_must test "\n$actual\n" == "\n$expected\n"
# Range 0:-1:df must output all directory and file objects
expected=$(grep -e "ZFS directory" -e "ZFS plain file" <<< $all_objects)
actual=$(get_object_list $TESTPOOL/$TESTFS 0:-1:df)
log_must test "\n$actual\n" == "\n$expected\n"
# Range 0:-1:A-f-d must output all non-files and non-directories
expected=$(grep -v -e "ZFS plain file" -e "ZFS directory" <<< $all_objects)
actual=$(get_object_list $TESTPOOL/$TESTFS 0:-1:A-f-d)
log_must test "\n$actual\n" == "\n$expected\n"
# Specifying multiple ranges works
set -A obj_ids $(ls -i $TESTDIR | awk '{print $1}' | sort -n)
start1=${obj_ids[0]}
end1=${obj_ids[5]}
start2=${obj_ids[8]}
end2=${obj_ids[13]}
expected=$(get_object_list_range $TESTPOOL/$TESTFS $start1 $end1;
get_object_list_range $TESTPOOL/$TESTFS $start2 $end2)
actual=$(get_object_list $TESTPOOL/$TESTFS $start1:$end1 $start2:$end2)
log_must test "\n$actual\n" == "\n$expected\n"
# Combining ranges with individual object IDs works
expected=$(get_object_list_range $TESTPOOL/$TESTFS $start1 $end1;
get_object_list $TESTPOOL/$TESTFS $start2 $end2)
actual=$(get_object_list $TESTPOOL/$TESTFS $start1:$end1 $start2 $end2)
log_must test "\n$actual\n" == "\n$expected\n"
# Hex conversion must work for ranges and individual object identifiers
# (this test uses expected result from previous test).
start1_hex=$(printf "0x%x" $start1)
end1_hex=$(printf "0x%x" $end1)
start2_hex=$(printf "0x%x" $start2)
end2_hex=$(printf "0x%x" $end2)
actual=$(get_object_list $TESTPOOL/$TESTFS $start1_hex:$end1_hex \
$start2_hex $end2_hex)
log_must test "\n$actual\n" == "\n$expected\n"
# Specifying individual object IDs works
objects="$start1 $end1 $start2 $end2"
expected="$objects"
actual=$(get_object_list $TESTPOOL/$TESTFS $objects | awk '{print $1}' | xargs)
log_must test "$actual" == "$expected"
# Get all objects in the meta-objset to test m (spacemap) and z (zap) flags
all_mos_objects=$(get_object_list $TESTPOOL 0:-1)
# Range 0:-1:m must output all space map objects
expected=$(grep "SPA space map" <<< $all_mos_objects)
actual=$(get_object_list $TESTPOOL 0:-1:m)
log_must test "\n$actual\n" == "\n$expected\n"
# Range 0:-1:z must output all zap objects
expected=$(grep "zap" <<< $all_mos_objects)
actual=$(get_object_list $TESTPOOL 0:-1:z)
log_must test "\n$actual\n" == "\n$expected\n"
# Range 0:-1:A-m-z must output all non-space maps and non-zaps
expected=$(grep -v -e "zap" -e "SPA space map" <<< $all_mos_objects)
actual=$(get_object_list $TESTPOOL 0:-1:A-m-z)
log_must test "\n$actual\n" == "\n$expected\n"
# Range 0:-1:mz must output all space maps and zaps
expected=$(grep -e "SPA space map" -e "zap" <<< $all_mos_objects)
actual=$(get_object_list $TESTPOOL 0:-1:mz)
log_must test "\n$actual\n" == "\n$expected\n"
log_pass "zdb -dd object range arguments work correctly"