]> git.proxmox.com Git - mirror_zfs.git/commitdiff
zdb: add support for object ranges for zdb -d
authorNed Bass <bass6@llnl.gov>
Fri, 24 Jan 2020 19:00:46 +0000 (11:00 -0800)
committerBrian Behlendorf <behlendorf1@llnl.gov>
Fri, 24 Jan 2020 19:00:46 +0000 (11:00 -0800)
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

cmd/zdb/zdb.c
man/man8/zdb.8
tests/runfiles/common.run
tests/zfs-tests/tests/functional/cli_root/zdb/Makefile.am
tests/zfs-tests/tests/functional/cli_root/zdb/zdb_object_range_neg.ksh [new file with mode: 0755]
tests/zfs-tests/tests/functional/cli_root/zdb/zdb_object_range_pos.ksh [new file with mode: 0755]

index 8259665551e754371216deed262673a03b315aed..f00df5d57a92b4447d7f2a3784c36b759e8cee02 100644 (file)
@@ -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);
index 87d3ad8cc58e0715c7fab154653f3e18a810fc80..a7ab6b8ba42bc11eef84a47bb1bc1de7a3b3dc1a 100644 (file)
 .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 ,
index a04f5f3cc46def02d5c7f95a573636f5a41fd9c8..e1315902bf06224fc3e2cb10cc4636fe2193a86e 100644 (file)
@@ -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']
index edbebb0208e42ff59a7d50da92026f9c02b73ec5..e4679ae9faa736b32014143f9a55df19dbb74255 100644 (file)
@@ -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 (executable)
index 0000000..4301807
--- /dev/null
@@ -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 (executable)
index 0000000..b7f47d1
--- /dev/null
@@ -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"