diff options
-rw-r--r-- | cmd/zdb/zdb.c | 311 | ||||
-rw-r--r-- | man/man8/zdb.8 | 45 | ||||
-rw-r--r-- | tests/runfiles/common.run | 2 | ||||
-rw-r--r-- | tests/zfs-tests/tests/functional/cli_root/zdb/Makefile.am | 4 | ||||
-rwxr-xr-x | tests/zfs-tests/tests/functional/cli_root/zdb/zdb_object_range_neg.ksh | 72 | ||||
-rwxr-xr-x | tests/zfs-tests/tests/functional/cli_root/zdb/zdb_object_range_pos.ksh | 171 |
6 files changed, 555 insertions, 50 deletions
diff --git a/cmd/zdb/zdb.c b/cmd/zdb/zdb.c index 825966555..f00df5d57 100644 --- a/cmd/zdb/zdb.c +++ b/cmd/zdb/zdb.c @@ -110,8 +110,38 @@ uint8_t dump_opt[256]; typedef void object_viewer_t(objset_t *, uint64_t, void *data, size_t size); -uint64_t *zopt_object = NULL; -static unsigned zopt_objects = 0; +uint64_t *zopt_metaslab = NULL; +static unsigned zopt_metaslab_args = 0; + +typedef struct zopt_object_range { + uint64_t zor_obj_start; + uint64_t zor_obj_end; + uint64_t zor_flags; +} zopt_object_range_t; +zopt_object_range_t *zopt_object_ranges = NULL; +static unsigned zopt_object_args = 0; + +static int flagbits[256]; + +#define ZOR_FLAG_PLAIN_FILE 0x0001 +#define ZOR_FLAG_DIRECTORY 0x0002 +#define ZOR_FLAG_SPACE_MAP 0x0004 +#define ZOR_FLAG_ZAP 0x0008 +#define ZOR_FLAG_ALL_TYPES -1 +#define ZOR_SUPPORTED_FLAGS (ZOR_FLAG_PLAIN_FILE | \ + ZOR_FLAG_DIRECTORY | \ + ZOR_FLAG_SPACE_MAP | \ + ZOR_FLAG_ZAP) + +#define ZDB_FLAG_CHECKSUM 0x0001 +#define ZDB_FLAG_DECOMPRESS 0x0002 +#define ZDB_FLAG_BSWAP 0x0004 +#define ZDB_FLAG_GBH 0x0008 +#define ZDB_FLAG_INDIRECT 0x0010 +#define ZDB_FLAG_RAW 0x0020 +#define ZDB_FLAG_PRINT_BLKPTR 0x0040 +#define ZDB_FLAG_VERBOSE 0x0080 + uint64_t max_inflight_bytes = 256 * 1024 * 1024; /* 256MB */ static int leaked_objects = 0; static range_tree_t *mos_refd_objs; @@ -144,9 +174,9 @@ usage(void) "Usage:\t%s [-AbcdDFGhikLMPsvX] [-e [-V] [-p <path> ...]] " "[-I <inflight I/Os>]\n" "\t\t[-o <var>=<value>]... [-t <txg>] [-U <cache>] [-x <dumpdir>]\n" - "\t\t[<poolname>[/<dataset | objset id>] [<object> ...]]\n" + "\t\t[<poolname>[/<dataset | objset id>] [<object | range> ...]]\n" "\t%s [-AdiPv] [-e [-V] [-p <path> ...]] [-U <cache>]\n" - "\t\t[<poolname>[/<dataset | objset id>] [<object> ...]\n" + "\t\t[<poolname>[/<dataset | objset id>] [<object | range> ...]\n" "\t%s [-v] <bookmark>\n" "\t%s -C [-A] [-U <cache>]\n" "\t%s -l [-Aqu] <device>\n" @@ -165,8 +195,20 @@ usage(void) "separator character '/' or '@'\n"); (void) fprintf(stderr, " If dataset name is specified, only that " "dataset is dumped\n"); - (void) fprintf(stderr, " If object numbers are specified, only " - "those objects are dumped\n\n"); + (void) fprintf(stderr, " If object numbers or object number " + "ranges are specified, only those\n" + " objects or ranges are dumped.\n\n"); + (void) fprintf(stderr, + " Object ranges take the form <start>:<end>[:<flags>]\n" + " start Starting object number\n" + " end Ending object number, or -1 for no upper bound\n" + " flags Optional flags to select object types:\n" + " A All objects (this is the default)\n" + " d ZFS directories\n" + " f ZFS files \n" + " m SPA space maps\n" + " z ZAPs\n" + " - Negate effect of next flag\n\n"); (void) fprintf(stderr, " Options to control amount of output:\n"); (void) fprintf(stderr, " -b block statistics\n"); (void) fprintf(stderr, " -c checksum all metadata (twice for " @@ -1172,24 +1214,24 @@ dump_metaslabs(spa_t *spa) (void) printf("\nMetaslabs:\n"); - if (!dump_opt['d'] && zopt_objects > 0) { - c = zopt_object[0]; + if (!dump_opt['d'] && zopt_metaslab_args > 0) { + c = zopt_metaslab[0]; if (c >= children) (void) fatal("bad vdev id: %llu", (u_longlong_t)c); - if (zopt_objects > 1) { + if (zopt_metaslab_args > 1) { vd = rvd->vdev_child[c]; print_vdev_metaslab_header(vd); - for (m = 1; m < zopt_objects; m++) { - if (zopt_object[m] < vd->vdev_ms_count) + for (m = 1; m < zopt_metaslab_args; m++) { + if (zopt_metaslab[m] < vd->vdev_ms_count) dump_metaslab( - vd->vdev_ms[zopt_object[m]]); + vd->vdev_ms[zopt_metaslab[m]]); else (void) fprintf(stderr, "bad metaslab " "number %llu\n", - (u_longlong_t)zopt_object[m]); + (u_longlong_t)zopt_metaslab[m]); } (void) printf("\n"); return; @@ -2539,9 +2581,49 @@ static object_viewer_t *object_viewer[DMU_OT_NUMTYPES + 1] = { dump_unknown, /* Unknown type, must be last */ }; +static boolean_t +match_object_type(dmu_object_type_t obj_type, uint64_t flags) +{ + boolean_t match = B_TRUE; + + switch (obj_type) { + case DMU_OT_DIRECTORY_CONTENTS: + if (!(flags & ZOR_FLAG_DIRECTORY)) + match = B_FALSE; + break; + case DMU_OT_PLAIN_FILE_CONTENTS: + if (!(flags & ZOR_FLAG_PLAIN_FILE)) + match = B_FALSE; + break; + case DMU_OT_SPACE_MAP: + if (!(flags & ZOR_FLAG_SPACE_MAP)) + match = B_FALSE; + break; + default: + if (strcmp(zdb_ot_name(obj_type), "zap") == 0) { + if (!(flags & ZOR_FLAG_ZAP)) + match = B_FALSE; + break; + } + + /* + * If all bits except some of the supported flags are + * set, the user combined the all-types flag (A) with + * a negated flag to exclude some types (e.g. A-f to + * show all object types except plain files). + */ + if ((flags | ZOR_SUPPORTED_FLAGS) != ZOR_FLAG_ALL_TYPES) + match = B_FALSE; + + break; + } + + return (match); +} + static void dump_object(objset_t *os, uint64_t object, int verbosity, - boolean_t *print_header, uint64_t *dnode_slots_used) + boolean_t *print_header, uint64_t *dnode_slots_used, uint64_t flags) { dmu_buf_t *db = NULL; dmu_object_info_t doi; @@ -2598,6 +2680,13 @@ dump_object(objset_t *os, uint64_t object, int verbosity, } } + /* + * Default to showing all object types if no flags were specified. + */ + if (flags != 0 && flags != ZOR_FLAG_ALL_TYPES && + !match_object_type(doi.doi_type, flags)) + goto out; + if (dnode_slots_used) *dnode_slots_used = doi.doi_dnodesize / DNODE_MIN_SIZE; @@ -2701,6 +2790,7 @@ dump_object(objset_t *os, uint64_t object, int verbosity, } } +out: if (db != NULL) dmu_buf_rele(db, FTAG); if (dnode_held) @@ -2741,6 +2831,110 @@ count_ds_mos_objects(dsl_dataset_t *ds) static const char *objset_types[DMU_OST_NUMTYPES] = { "NONE", "META", "ZPL", "ZVOL", "OTHER", "ANY" }; +/* + * Parse a string denoting a range of object IDs of the form + * <start>[:<end>[:flags]], and store the results in zor. + * Return 0 on success. On error, return 1 and update the msg + * pointer to point to a descriptive error message. + */ +static int +parse_object_range(char *range, zopt_object_range_t *zor, char **msg) +{ + uint64_t flags = 0; + char *p, *s, *dup, *flagstr; + size_t len; + int i; + int rc = 0; + + if (strchr(range, ':') == NULL) { + zor->zor_obj_start = strtoull(range, &p, 0); + if (*p != '\0') { + *msg = "Invalid characters in object ID"; + rc = 1; + } + zor->zor_obj_end = zor->zor_obj_start; + return (rc); + } + + if (strchr(range, ':') == range) { + *msg = "Invalid leading colon"; + rc = 1; + return (rc); + } + + len = strlen(range); + if (range[len - 1] == ':') { + *msg = "Invalid trailing colon"; + rc = 1; + return (rc); + } + + dup = strdup(range); + s = strtok(dup, ":"); + zor->zor_obj_start = strtoull(s, &p, 0); + + if (*p != '\0') { + *msg = "Invalid characters in start object ID"; + rc = 1; + goto out; + } + + s = strtok(NULL, ":"); + zor->zor_obj_end = strtoull(s, &p, 0); + + if (*p != '\0') { + *msg = "Invalid characters in end object ID"; + rc = 1; + goto out; + } + + if (zor->zor_obj_start > zor->zor_obj_end) { + *msg = "Start object ID may not exceed end object ID"; + rc = 1; + goto out; + } + + s = strtok(NULL, ":"); + if (s == NULL) { + zor->zor_flags = ZOR_FLAG_ALL_TYPES; + goto out; + } else if (strtok(NULL, ":") != NULL) { + *msg = "Invalid colon-delimited field after flags"; + rc = 1; + goto out; + } + + flagstr = s; + for (i = 0; flagstr[i]; i++) { + int bit; + boolean_t negation = (flagstr[i] == '-'); + + if (negation) { + i++; + if (flagstr[i] == '\0') { + *msg = "Invalid trailing negation operator"; + rc = 1; + goto out; + } + } + bit = flagbits[(uchar_t)flagstr[i]]; + if (bit == 0) { + *msg = "Invalid flag"; + rc = 1; + goto out; + } + if (negation) + flags &= ~bit; + else + flags |= bit; + } + zor->zor_flags = flags; + +out: + free(dup); + return (rc); +} + static void dump_objset(objset_t *os) { @@ -2758,6 +2952,9 @@ dump_objset(objset_t *os) uint64_t total_slots_used = 0; uint64_t max_slot_used = 0; uint64_t dnode_slots; + uint64_t obj_start; + uint64_t obj_end; + uint64_t flags; /* make sure nicenum has enough space */ CTASSERT(sizeof (numbuf) >= NN_NUMBUF_SZ); @@ -2801,11 +2998,26 @@ dump_objset(objset_t *os) numbuf, (u_longlong_t)usedobjs, blkbuf, (dds.dds_inconsistent) ? " (inconsistent)" : ""); - if (zopt_objects != 0) { - for (i = 0; i < zopt_objects; i++) { - dump_object(os, zopt_object[i], verbosity, - &print_header, NULL); + for (i = 0; i < zopt_object_args; i++) { + obj_start = zopt_object_ranges[i].zor_obj_start; + obj_end = zopt_object_ranges[i].zor_obj_end; + flags = zopt_object_ranges[i].zor_flags; + + object = obj_start; + if (object == 0 || obj_start == obj_end) + dump_object(os, object, verbosity, &print_header, NULL, + flags); + else + object--; + + while ((dmu_object_next(os, &object, B_FALSE, 0) == 0) && + object <= obj_end) { + dump_object(os, object, verbosity, &print_header, NULL, + flags); } + } + + if (zopt_object_args > 0) { (void) printf("\n"); return; } @@ -2839,24 +3051,25 @@ dump_objset(objset_t *os) if (BP_IS_HOLE(os->os_rootbp)) return; - dump_object(os, 0, verbosity, &print_header, NULL); + dump_object(os, 0, verbosity, &print_header, NULL, 0); object_count = 0; if (DMU_USERUSED_DNODE(os) != NULL && DMU_USERUSED_DNODE(os)->dn_type != 0) { dump_object(os, DMU_USERUSED_OBJECT, verbosity, &print_header, - NULL); + NULL, 0); dump_object(os, DMU_GROUPUSED_OBJECT, verbosity, &print_header, - NULL); + NULL, 0); } if (DMU_PROJECTUSED_DNODE(os) != NULL && DMU_PROJECTUSED_DNODE(os)->dn_type != 0) dump_object(os, DMU_PROJECTUSED_OBJECT, verbosity, - &print_header, NULL); + &print_header, NULL, 0); object = 0; while ((error = dmu_object_next(os, &object, B_FALSE, 0)) == 0) { - dump_object(os, object, verbosity, &print_header, &dnode_slots); + dump_object(os, object, verbosity, &print_header, &dnode_slots, + 0); object_count++; total_slots_used += dnode_slots; max_slot_used = object + dnode_slots - 1; @@ -3360,7 +3573,7 @@ dump_path_impl(objset_t *os, uint64_t obj, char *name) return (dump_path_impl(os, child_obj, s + 1)); /*FALLTHROUGH*/ case DMU_OT_PLAIN_FILE_CONTENTS: - dump_object(os, child_obj, dump_opt['v'], &header, NULL); + dump_object(os, child_obj, dump_opt['v'], &header, NULL, 0); return (0); default: (void) fprintf(stderr, "object %llu has non-file/directory " @@ -6197,17 +6410,6 @@ dump_zpool(spa_t *spa) } } -#define ZDB_FLAG_CHECKSUM 0x0001 -#define ZDB_FLAG_DECOMPRESS 0x0002 -#define ZDB_FLAG_BSWAP 0x0004 -#define ZDB_FLAG_GBH 0x0008 -#define ZDB_FLAG_INDIRECT 0x0010 -#define ZDB_FLAG_RAW 0x0020 -#define ZDB_FLAG_PRINT_BLKPTR 0x0040 -#define ZDB_FLAG_VERBOSE 0x0080 - -static int flagbits[256]; - static void zdb_print_blkptr(blkptr_t *bp, int flags) { @@ -7130,20 +7332,41 @@ main(int argc, char **argv) argv++; argc--; if (!dump_opt['R']) { - if (argc > 0) { - zopt_objects = argc; - zopt_object = calloc(zopt_objects, sizeof (uint64_t)); - for (unsigned i = 0; i < zopt_objects; i++) { + flagbits['d'] = ZOR_FLAG_DIRECTORY; + flagbits['f'] = ZOR_FLAG_PLAIN_FILE; + flagbits['m'] = ZOR_FLAG_SPACE_MAP; + flagbits['z'] = ZOR_FLAG_ZAP; + flagbits['A'] = ZOR_FLAG_ALL_TYPES; + + if (argc > 0 && dump_opt['d']) { + zopt_object_args = argc; + zopt_object_ranges = calloc(zopt_object_args, + sizeof (zopt_object_range_t)); + for (unsigned i = 0; i < zopt_object_args; i++) { + int err; + char *msg = NULL; + + err = parse_object_range(argv[i], + &zopt_object_ranges[i], &msg); + if (err != 0) + fatal("Bad object or range: '%s': %s\n", + argv[i], msg ? msg : ""); + } + } else if (argc > 0 && dump_opt['m']) { + zopt_metaslab_args = argc; + zopt_metaslab = calloc(zopt_metaslab_args, + sizeof (uint64_t)); + for (unsigned i = 0; i < zopt_metaslab_args; i++) { errno = 0; - zopt_object[i] = strtoull(argv[i], NULL, 0); - if (zopt_object[i] == 0 && errno != 0) - fatal("bad number %s: %s", - argv[i], strerror(errno)); + zopt_metaslab[i] = strtoull(argv[i], NULL, 0); + if (zopt_metaslab[i] == 0 && errno != 0) + fatal("bad number %s: %s", argv[i], + strerror(errno)); } } if (os != NULL) { dump_objset(os); - } else if (zopt_objects > 0 && !dump_opt['m']) { + } else if (zopt_object_args > 0 && !dump_opt['m']) { dump_objset(spa->spa_meta_objset); } else { dump_zpool(spa); diff --git a/man/man8/zdb.8 b/man/man8/zdb.8 index 87d3ad8cc..a7ab6b8ba 100644 --- a/man/man8/zdb.8 +++ b/man/man8/zdb.8 @@ -31,12 +31,12 @@ .Op Fl U Ar cache .Op Fl x Ar dumpdir .Op Ar poolname[/dataset | objset ID] -.Op Ar object ... +.Op Ar object | range ... .Nm .Op Fl AdiPv .Op Fl e Oo Fl V Oc Op Fl p Ar path ... .Op Fl U Ar cache -.Ar poolname[/dataset | objset ID] Op Ar object ... +.Ar poolname[/dataset | objset ID] Op Ar object | range ... .Nm .Fl C .Op Fl A @@ -135,8 +135,45 @@ size, and object count. .Pp If specified multiple times provides greater and greater verbosity. .Pp -If object IDs are specified, display information about those specific objects -only. +If object IDs or object ID ranges are specified, display information about +those specific objects or ranges only. +.Pp +An object ID range is specified in terms of a colon-separated tuple of +the form +.Ao start Ac Ns : Ns Ao end Ac Ns Op Ns : Ns Ao flags Ac Ns . +The fields +.Ar start +and +.Ar end +are integer object identfiers that denote the upper and lower bounds +of the range. An +.Ar end +value of -1 specifies a range with no upper bound. The +.Ar flags +field optionally specifies a set of flags, described below, that control +which object types are dumped. By default, all object types are dumped. A minus +sign +.Pq - +negates the effect of the flag that follows it and has no effect unless +preceded by the +.Ar A +flag. For example, the range 0:-1:A-d will dump all object types except +for directories. +.Pp +.Bl -tag -compact +.It Sy A +Dump all objects (this is the default) +.It Sy d +Dump ZFS directory objects +.It Sy f +Dump ZFS plain file objects +.It Sy m +Dump SPA space map objects +.It Sy z +Dump ZAP objects +.It Sy - +Negate the effect of next flag +.El .It Fl D Display deduplication statistics, including the deduplication ratio .Pq Sy dedup , diff --git a/tests/runfiles/common.run b/tests/runfiles/common.run index a04f5f3cc..e1315902b 100644 --- a/tests/runfiles/common.run +++ b/tests/runfiles/common.run @@ -101,7 +101,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_decompress', - 'zdb_objset_id'] + 'zdb_object_range_neg', 'zdb_object_range_pos', 'zdb_objset_id'] pre = post = tags = ['functional', 'cli_root', 'zdb'] diff --git a/tests/zfs-tests/tests/functional/cli_root/zdb/Makefile.am b/tests/zfs-tests/tests/functional/cli_root/zdb/Makefile.am index edbebb020..e4679ae9f 100644 --- a/tests/zfs-tests/tests/functional/cli_root/zdb/Makefile.am +++ b/tests/zfs-tests/tests/functional/cli_root/zdb/Makefile.am @@ -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 diff --git a/tests/zfs-tests/tests/functional/cli_root/zdb/zdb_object_range_neg.ksh b/tests/zfs-tests/tests/functional/cli_root/zdb/zdb_object_range_neg.ksh new file mode 100755 index 000000000..430180788 --- /dev/null +++ b/tests/zfs-tests/tests/functional/cli_root/zdb/zdb_object_range_neg.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." diff --git a/tests/zfs-tests/tests/functional/cli_root/zdb/zdb_object_range_pos.ksh b/tests/zfs-tests/tests/functional/cli_root/zdb/zdb_object_range_pos.ksh new file mode 100755 index 000000000..b7f47d11a --- /dev/null +++ b/tests/zfs-tests/tests/functional/cli_root/zdb/zdb_object_range_pos.ksh @@ -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" |