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;
"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"
"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 "
(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;
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;
}
}
+ /*
+ * 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;
}
}
+out:
if (db != NULL)
dmu_buf_rele(db, FTAG);
if (dnode_held)
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)
{
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);
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;
}
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;
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 "
}
}
-#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)
{
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);
--- /dev/null
+#!/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"