]> git.proxmox.com Git - mirror_zfs.git/commitdiff
Dump unique configurations and Uberblocks in zdb -lu
authorOlaf Faaland <faaland1@llnl.gov>
Tue, 7 Mar 2017 00:01:45 +0000 (16:01 -0800)
committerBrian Behlendorf <behlendorf1@llnl.gov>
Tue, 7 Mar 2017 00:01:45 +0000 (16:01 -0800)
For zdb -l, detect when the configuration nvlist in some label l (l>0)
is the same as a configuration already dumped.  If so, do not dump it.

Make a similar check when dumping Uberblocks for zdb -lu.  Check whether
a label already dumped contains an identical Uberblock.  If so, do not
dump the Uberblock.

When dumping a configuration or Uberblock, state which labels it is
found in (0-3), for example: labels = 1 2 3

Detecting redundant uberblocks or configurations is accomplished by
calculating checksums of the uberblocks and the packed nvlists
containing the configuration.

If there is nothing unique to be dumped for a label (ie the
configuration and uberblocks have checksums matching those already
dumped) print nothing for that label.

With additional l's or u's, increase verbosity as follows:

-l      Dump each unique configuration only once.
        Indicate which labels it appears in.
-ll     In addition, dump label space usage stats.
-lll    Dump every configuration, unique or not.

-u      Dump each unique, valid, uberblock only once.
        Indicate which labels it appears in.
-uu     In addition, state which slots are invalid.
-uuu    Dump every uberblock, unique or not.
-uuuu   Dump the uberblock blockpointer (used to be -uuu)

Make exit values conform to the manual page.  Failing to unpack a
configuration nvlist is considered an error, as well as failing to open
or read from the device.

Add three tests, zdb_00{3,4,5}_pos to verify the above functionality.

An example of the output:
------------------------------------
LABEL 0
------------------------------------
    version: 5000
    name: 'pool'
    state: 1
    txg: 880
    < ... redacted ... >
    features_for_read:
com.delphix:hole_birth
com.delphix:embedded_data
    labels = 0
    Uberblock[0]
magic = 0000000000bab10c
version = 5000
txg = 0
guid_sum = 3038694082047428541
timestamp = 1487715500 UTC = Tue Feb 21 14:18:20 2017
labels = 0 1 2 3
    Uberblock[4]
magic = 0000000000bab10c
version = 5000
txg = 772
guid_sum = 9045970794941528051
timestamp = 1487727291 UTC = Tue Feb 21 17:34:51 2017
labels = 0
    < ... redacted ... >
------------------------------------
LABEL 1
------------------------------------
    version: 5000
    name: 'pool'
    state: 1
    txg: 14
    < ... redacted ... >
com.delphix:embedded_data
    labels = 1 2 3
    Uberblock[4]
magic = 0000000000bab10c
version = 5000
txg = 4
guid_sum = 7793930272573252584
timestamp = 1487727521 UTC = Tue Feb 21 17:38:41 2017
labels = 1 2 3
    < ... redacted ... >

Reviewed-by: Tim Chase <tim@chase2k.com>
Reviewed-by: Don Brady <don.brady@intel.com>
Reviewed-by: Brian Behlendorf <behlendorf1@llnl.gov>
Signed-off-by: Olaf Faaland <faaland1@llnl.gov>
Closes #5738

cmd/zdb/zdb.c
man/man8/zdb.8
tests/runfiles/linux.run
tests/zfs-tests/tests/functional/cli_root/zdb/Makefile.am
tests/zfs-tests/tests/functional/cli_root/zdb/zdb_003_pos.ksh [new file with mode: 0755]
tests/zfs-tests/tests/functional/cli_root/zdb/zdb_004_pos.ksh [new file with mode: 0755]
tests/zfs-tests/tests/functional/cli_root/zdb/zdb_005_pos.ksh [new file with mode: 0755]

index 94b359029c3ebaaec3f4417bb03b884ddf6ffd81..82c2b5733b8474b2df0f4c0846091c6d0d6c2829 100644 (file)
@@ -2127,7 +2127,7 @@ dump_uberblock(uberblock_t *ub, const char *header, const char *footer)
        (void) printf("\tguid_sum = %llu\n", (u_longlong_t)ub->ub_guid_sum);
        (void) printf("\ttimestamp = %llu UTC = %s",
            (u_longlong_t)ub->ub_timestamp, asctime(localtime(&timestamp)));
-       if (dump_opt['u'] >= 3) {
+       if (dump_opt['u'] >= 4) {
                char blkbuf[BP_SPRINTF_LEN];
                snprintf_blkptr(blkbuf, sizeof (blkbuf), &ub->ub_rootbp);
                (void) printf("\trootbp = %s\n", blkbuf);
@@ -2205,31 +2205,6 @@ dump_cachefile(const char *cachefile)
        nvlist_free(config);
 }
 
-#define        ZDB_MAX_UB_HEADER_SIZE 32
-
-static void
-dump_label_uberblocks(vdev_label_t *lbl, uint64_t ashift)
-{
-       vdev_t vd;
-       vdev_t *vdp = &vd;
-       char header[ZDB_MAX_UB_HEADER_SIZE];
-       int i;
-
-       vd.vdev_ashift = ashift;
-       vdp->vdev_top = vdp;
-
-       for (i = 0; i < VDEV_UBERBLOCK_COUNT(vdp); i++) {
-               uint64_t uoff = VDEV_UBERBLOCK_OFFSET(vdp, i);
-               uberblock_t *ub = (void *)((char *)lbl + uoff);
-
-               if (uberblock_verify(ub))
-                       continue;
-               (void) snprintf(header, ZDB_MAX_UB_HEADER_SIZE,
-                   "Uberblock[%d]\n", i);
-               dump_uberblock(ub, header, "");
-       }
-}
-
 /*
  * ZFS label nvlist stats
  */
@@ -2363,18 +2338,182 @@ dump_nvlist_stats(nvlist_t *nvl, size_t cap)
        nvlist_free(stats.zns_boolean);
 }
 
+typedef struct cksum_record {
+       zio_cksum_t cksum;
+       boolean_t labels[VDEV_LABELS];
+       avl_node_t link;
+} cksum_record_t;
+
 static int
-dump_label(const char *dev)
+cksum_record_compare(const void *x1, const void *x2)
 {
-       int fd;
+       const cksum_record_t *l = (cksum_record_t *)x1;
+       const cksum_record_t *r = (cksum_record_t *)x2;
+       int arraysize = ARRAY_SIZE(l->cksum.zc_word);
+       int difference;
+
+       for (int i = 0; i < arraysize; i++) {
+               difference = AVL_CMP(l->cksum.zc_word[i], r->cksum.zc_word[i]);
+               if (difference)
+                       break;
+       }
+
+       return (difference);
+}
+
+static cksum_record_t *
+cksum_record_alloc(zio_cksum_t *cksum, int l)
+{
+       cksum_record_t *rec;
+
+       rec = umem_zalloc(sizeof (*rec), UMEM_NOFAIL);
+       rec->cksum = *cksum;
+       rec->labels[l] = B_TRUE;
+
+       return (rec);
+}
+
+static cksum_record_t *
+cksum_record_lookup(avl_tree_t *tree, zio_cksum_t *cksum)
+{
+       cksum_record_t lookup = { .cksum = *cksum };
+       avl_index_t where;
+
+       return (avl_find(tree, &lookup, &where));
+}
+
+static cksum_record_t *
+cksum_record_insert(avl_tree_t *tree, zio_cksum_t *cksum, int l)
+{
+       cksum_record_t *rec;
+
+       rec = cksum_record_lookup(tree, cksum);
+       if (rec) {
+               rec->labels[l] = B_TRUE;
+       } else {
+               rec = cksum_record_alloc(cksum, l);
+               avl_add(tree, rec);
+       }
+
+       return (rec);
+}
+
+static int
+first_label(cksum_record_t *rec)
+{
+       for (int i = 0; i < VDEV_LABELS; i++)
+               if (rec->labels[i])
+                       return (i);
+
+       return (-1);
+}
+
+static void
+print_label_numbers(char *prefix, cksum_record_t *rec)
+{
+       printf("%s", prefix);
+       for (int i = 0; i < VDEV_LABELS; i++)
+               if (rec->labels[i] == B_TRUE)
+                       printf("%d ", i);
+       printf("\n");
+}
+
+#define        MAX_UBERBLOCK_COUNT (VDEV_UBERBLOCK_RING >> UBERBLOCK_SHIFT)
+
+typedef struct label {
        vdev_label_t label;
+       nvlist_t *config_nv;
+       cksum_record_t *config;
+       cksum_record_t *uberblocks[MAX_UBERBLOCK_COUNT];
+       boolean_t header_printed;
+       boolean_t read_failed;
+} label_t;
+
+static void
+print_label_header(label_t *label, int l)
+{
+
+       if (dump_opt['q'])
+               return;
+
+       if (label->header_printed == B_TRUE)
+               return;
+
+       (void) printf("------------------------------------\n");
+       (void) printf("LABEL %d\n", l);
+       (void) printf("------------------------------------\n");
+
+       label->header_printed = B_TRUE;
+}
+
+static void
+dump_config_from_label(label_t *label, size_t buflen, int l)
+{
+       if (dump_opt['q'])
+               return;
+
+       if ((dump_opt['l'] < 3) && (first_label(label->config) != l))
+               return;
+
+       print_label_header(label, l);
+       dump_nvlist(label->config_nv, 4);
+       print_label_numbers("    labels = ", label->config);
+
+       if (dump_opt['l'] >= 2)
+               dump_nvlist_stats(label->config_nv, buflen);
+}
+
+#define        ZDB_MAX_UB_HEADER_SIZE 32
+
+static void
+dump_label_uberblocks(label_t *label, uint64_t ashift, int label_num)
+{
+
+       vdev_t vd;
+       char header[ZDB_MAX_UB_HEADER_SIZE];
+
+       vd.vdev_ashift = ashift;
+       vd.vdev_top = &vd;
+
+       for (int i = 0; i < VDEV_UBERBLOCK_COUNT(&vd); i++) {
+               uint64_t uoff = VDEV_UBERBLOCK_OFFSET(&vd, i);
+               uberblock_t *ub = (void *)((char *)&label->label + uoff);
+               cksum_record_t *rec = label->uberblocks[i];
+
+               if (rec == NULL) {
+                       if (dump_opt['u'] >= 2) {
+                               print_label_header(label, label_num);
+                               (void) printf("    Uberblock[%d] invalid\n", i);
+                       }
+                       continue;
+               }
+
+               if ((dump_opt['u'] < 3) && (first_label(rec) != label_num))
+                       continue;
+
+               print_label_header(label, label_num);
+               (void) snprintf(header, ZDB_MAX_UB_HEADER_SIZE,
+                   "    Uberblock[%d]\n", i);
+               dump_uberblock(ub, header, "");
+               print_label_numbers("        labels = ", rec);
+       }
+}
+
+static int
+dump_label(const char *dev)
+{
        char path[MAXPATHLEN];
-       char *buf = label.vl_vdev_phys.vp_nvlist;
-       size_t buflen = sizeof (label.vl_vdev_phys.vp_nvlist);
-       struct stat64 statbuf;
+       label_t labels[VDEV_LABELS];
        uint64_t psize, ashift;
-       boolean_t label_found = B_FALSE;
-       int l;
+       struct stat64 statbuf;
+       boolean_t config_found = B_FALSE;
+       boolean_t error = B_FALSE;
+       avl_tree_t config_tree;
+       avl_tree_t uberblock_tree;
+       void *node, *cookie;
+       int fd;
+
+       bzero(labels, sizeof (labels));
 
        (void) strlcpy(path, dev, sizeof (path));
 
@@ -2390,52 +2529,118 @@ dump_label(const char *dev)
                exit(1);
        }
 
+       avl_create(&config_tree, cksum_record_compare,
+           sizeof (cksum_record_t), offsetof(cksum_record_t, link));
+       avl_create(&uberblock_tree, cksum_record_compare,
+           sizeof (cksum_record_t), offsetof(cksum_record_t, link));
+
        psize = statbuf.st_size;
        psize = P2ALIGN(psize, (uint64_t)sizeof (vdev_label_t));
+       ashift = SPA_MINBLOCKSHIFT;
 
-       for (l = 0; l < VDEV_LABELS; l++) {
-               nvlist_t *config = NULL;
-
-               if (!dump_opt['q']) {
-                       (void) printf("------------------------------------\n");
-                       (void) printf("LABEL %d\n", l);
-                       (void) printf("------------------------------------\n");
-               }
-
-               if (pread64(fd, &label, sizeof (label),
-                   vdev_label_offset(psize, l, 0)) != sizeof (label)) {
+       /*
+        * 1. Read the label from disk
+        * 2. Unpack the configuration and insert in config tree.
+        * 3. Traverse all uberblocks and insert in uberblock tree.
+        */
+       for (int l = 0; l < VDEV_LABELS; l++) {
+               label_t *label = &labels[l];
+               char *buf = label->label.vl_vdev_phys.vp_nvlist;
+               size_t buflen = sizeof (label->label.vl_vdev_phys.vp_nvlist);
+               nvlist_t *config;
+               cksum_record_t *rec;
+               zio_cksum_t cksum;
+               vdev_t vd;
+
+               if (pread64(fd, &label->label, sizeof (label->label),
+                   vdev_label_offset(psize, l, 0)) != sizeof (label->label)) {
                        if (!dump_opt['q'])
                                (void) printf("failed to read label %d\n", l);
+                       label->read_failed = B_TRUE;
+                       error = B_TRUE;
                        continue;
                }
 
-               if (nvlist_unpack(buf, buflen, &config, 0) != 0) {
-                       if (!dump_opt['q'])
-                               (void) printf("failed to unpack label %d\n", l);
-                       ashift = SPA_MINBLOCKSHIFT;
-               } else {
+               label->read_failed = B_FALSE;
+
+               if (nvlist_unpack(buf, buflen, &config, 0) == 0) {
                        nvlist_t *vdev_tree = NULL;
+                       size_t size;
 
-                       if (!dump_opt['q']) {
-                               dump_nvlist(config, 4);
-                               if (l == 3 && dump_opt['l'] >= 2)
-                                       dump_nvlist_stats(config, buflen);
-                       }
                        if ((nvlist_lookup_nvlist(config,
                            ZPOOL_CONFIG_VDEV_TREE, &vdev_tree) != 0) ||
                            (nvlist_lookup_uint64(vdev_tree,
                            ZPOOL_CONFIG_ASHIFT, &ashift) != 0))
                                ashift = SPA_MINBLOCKSHIFT;
-                       nvlist_free(config);
-                       label_found = B_TRUE;
+
+                       if (nvlist_size(config, &size, NV_ENCODE_XDR) != 0)
+                               size = buflen;
+
+                       fletcher_4_native_varsize(buf, size, &cksum);
+                       rec = cksum_record_insert(&config_tree, &cksum, l);
+
+                       label->config = rec;
+                       label->config_nv = config;
+                       config_found = B_TRUE;
+               } else {
+                       error = B_TRUE;
                }
+
+               vd.vdev_ashift = ashift;
+               vd.vdev_top = &vd;
+
+               for (int i = 0; i < VDEV_UBERBLOCK_COUNT(&vd); i++) {
+                       uint64_t uoff = VDEV_UBERBLOCK_OFFSET(&vd, i);
+                       uberblock_t *ub = (void *)((char *)label + uoff);
+
+                       if (uberblock_verify(ub))
+                               continue;
+
+                       fletcher_4_native_varsize(ub, sizeof (*ub), &cksum);
+                       rec = cksum_record_insert(&uberblock_tree, &cksum, l);
+
+                       label->uberblocks[i] = rec;
+               }
+       }
+
+       /*
+        * Dump the label and uberblocks.
+        */
+       for (int l = 0; l < VDEV_LABELS; l++) {
+               label_t *label = &labels[l];
+               size_t buflen = sizeof (label->label.vl_vdev_phys.vp_nvlist);
+
+               if (label->read_failed == B_TRUE)
+                       continue;
+
+               if (label->config_nv) {
+                       dump_config_from_label(label, buflen, l);
+               } else {
+                       if (!dump_opt['q'])
+                               (void) printf("failed to unpack label %d\n", l);
+               }
+
                if (dump_opt['u'])
-                       dump_label_uberblocks(&label, ashift);
+                       dump_label_uberblocks(label, ashift, l);
+
+               nvlist_free(label->config_nv);
        }
 
+       cookie = NULL;
+       while ((node = avl_destroy_nodes(&config_tree, &cookie)) != NULL)
+               umem_free(node, sizeof (cksum_record_t));
+
+       cookie = NULL;
+       while ((node = avl_destroy_nodes(&uberblock_tree, &cookie)) != NULL)
+               umem_free(node, sizeof (cksum_record_t));
+
+       avl_destroy(&config_tree);
+       avl_destroy(&uberblock_tree);
+
        (void) close(fd);
 
-       return (label_found ? 0 : 2);
+       return (config_found == B_FALSE ? 2 :
+           (error == B_TRUE ? 1 : 0));
 }
 
 static uint64_t dataset_feature_count[SPA_FEATURES];
index 02137e97faa598cb2b6f0549be1ab4763be6bc15..667f6b1714a10549d15cd06b2dd5981c2aa029a9 100644 (file)
@@ -177,13 +177,16 @@ transaction type.
 .ad
 .sp .6
 .RS 4n
-Read the vdev labels from the specified device. \fBzdb -l\fR will return 0 if
-valid label was found, 1 if error occured, and 2 if no valid labels were found.
+Read the vdev labels from the specified device and dump the unique
+configuration nvlist(s). \fBzdb -l\fR will return 1 if an error occured, 2 if
+no configuration nvlist could be unpacked (errors or not), and 0 otherwise.
+Specify multiple times to increase verbosity.
 .P
 If the \fB-u\fR option is also specified, also display the uberblocks on this
-device.
+device.  Specify multiple times to increase verbosity.
 .P
-If the \fB-q\fR option is also specified, don't print the labels.
+If the \fB-q\fR option is also specified, don't dump the configurations or the
+uberblocks.
 .RE
 
 .sp
index d49d8913d64ea36346aa8d9b393a0fced7cf5002..cba8e06e1a4408bcc061d49f00773cb7499ac587 100644 (file)
@@ -71,7 +71,8 @@ tests = [ 'clean_mirror_001_pos', 'clean_mirror_002_pos',
     'clean_mirror_003_pos', 'clean_mirror_004_pos']
 
 [tests/functional/cli_root/zdb]
-tests = ['zdb_001_neg', 'zdb_002_pos']
+tests = ['zdb_001_neg', 'zdb_002_pos', 'zdb_003_pos', 'zdb_004_pos',
+    'zdb_005_pos']
 pre =
 post =
 
index c5bd009f4a0275c5da9e068b2e898827ed9718a5..51170fbc894dd8b8cfc26f863105ddc41889e1fc 100644 (file)
@@ -1,4 +1,7 @@
 pkgdatadir = $(datadir)/@PACKAGE@/zfs-tests/tests/functional/cli_root/zdb
 dist_pkgdata_SCRIPTS = \
        zdb_001_neg.ksh \
-       zdb_002_pos.ksh
+       zdb_002_pos.ksh \
+       zdb_003_pos.ksh \
+       zdb_004_pos.ksh \
+       zdb_005_pos.ksh
diff --git a/tests/zfs-tests/tests/functional/cli_root/zdb/zdb_003_pos.ksh b/tests/zfs-tests/tests/functional/cli_root/zdb/zdb_003_pos.ksh
new file mode 100755 (executable)
index 0000000..d801828
--- /dev/null
@@ -0,0 +1,58 @@
+#!/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) 2017 by Lawrence Livermore National Security, LLC.
+#
+
+. $STF_SUITE/include/libtest.shlib
+
+#
+# Description:
+# zdb will not produce redundant dumps of configurations
+#
+# Strategy:
+# 1. Create a pool with two vdevs
+# 2. Copy label 1 from the first vdev to the second vdev
+# 3. Collect zdb -l output for both vdevs
+# 4. Verify that the correct number of configs is dumped for each
+#
+
+log_assert "Verify zdb does not produce redundant dumps of configurations"
+log_onexit cleanup
+
+function cleanup
+{
+       datasetexists $TESTPOOL && destroy_pool $TESTPOOL
+}
+
+verify_runnable "global"
+verify_disk_count "$DISKS" 2
+
+config_count=(1 2)
+set -A DISK $DISKS
+
+default_mirror_setup_noexit $DISKS
+log_must $DD if=/dev/${DISK[0]} of=/dev/${DISK[1]} bs=1K count=256 conv=notrunc
+
+for x in 0 1 ; do
+       config_count=$($ZDB -l $DEV_RDSKDIR/${DISK[$x]} | $GREP -c features_for_read)
+       (( $? != 0)) && log_fail "failed to get config_count from DISK[$x]"
+       log_note "vdev $x: message_count $config_count"
+       [ $config_count -ne ${config_count[$x]} ] && \
+               log_fail "zdb produces an incorrect number of configuration dumps."
+done
+
+cleanup
+
+log_pass "zdb produces unique dumps of configurations."
diff --git a/tests/zfs-tests/tests/functional/cli_root/zdb/zdb_004_pos.ksh b/tests/zfs-tests/tests/functional/cli_root/zdb/zdb_004_pos.ksh
new file mode 100755 (executable)
index 0000000..09e2194
--- /dev/null
@@ -0,0 +1,78 @@
+#!/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) 2017 by Lawrence Livermore National Security, LLC.
+#
+
+. $STF_SUITE/include/libtest.shlib
+
+#
+# Description:
+# zdb will not produce redundant dumps of uberblocks
+#
+# Strategy:
+# 1. Create a pool with two vdevs, A and B
+# 2. Offline vdev A
+# 3. Do some I/O
+# 4. Export the pool
+# 5. Copy label 1 from vdev A to vdev B
+# 6. Collect zdb -lu output for vdev B
+# 7. Verify labels 0 and 1 have unique Uberblocks, but 2 and 3 have none
+#
+
+log_assert "Verify zdb produces unique dumps of uberblocks"
+log_onexit cleanup
+
+function cleanup
+{
+       datasetexists $TESTPOOL && destroy_pool $TESTPOOL
+       for DISK in $DISKS; do
+               $ZPOOL labelclear -f $DEV_RDSKDIR/$DISK
+       done
+}
+
+verify_runnable "global"
+verify_disk_count "$DISKS" 2
+set -A DISK $DISKS
+
+default_mirror_setup_noexit $DISKS
+log_must $ZPOOL offline $TESTPOOL ${DISK[0]}
+log_must $DD if=/dev/urandom of=$TESTDIR/testfile bs=1K count=2
+log_must $ZPOOL export $TESTPOOL
+log_must $DD if=$DEV_RDSKDIR/${DISK[0]} of=$DEV_RDSKDIR/${DISK[1]} bs=1K count=256 conv=notrunc
+
+ubs=$($ZDB -lu $DEV_RDSKDIR/${DISK[1]} | $GREP -e LABEL -e Uberblock -e 'labels = ')
+log_note "vdev 1: ubs $ubs"
+
+ub_dump_counts=$($ZDB -lu $DEV_RDSKDIR/${DISK[1]} | \
+       $AWK '  /LABEL/ {label=$NF; blocks[label]=0};
+               /Uberblock/ {blocks[label]++};
+               END {print blocks[0],blocks[1],blocks[2],blocks[3]}')
+(( $? != 0)) && log_fail "failed to get ub_dump_counts from DISK[1]"
+log_note "vdev 1: ub_dump_counts $ub_dump_counts"
+
+set -A dump_count $ub_dump_counts
+for label in 0 1 2 3; do
+       if [[ $label -lt 2 ]]; then
+               [[ ${dump_count[$label]} -eq 0 ]] && \
+                   log_fail "zdb incorrectly dumps duplicate uberblocks"
+       else
+               [[ ${dump_count[$label]} -ne 0 ]] && \
+                   log_fail "zdb incorrectly dumps duplicate uberblocks"
+       fi
+done
+
+cleanup
+
+log_pass "zdb produces unique dumps of uberblocks"
diff --git a/tests/zfs-tests/tests/functional/cli_root/zdb/zdb_005_pos.ksh b/tests/zfs-tests/tests/functional/cli_root/zdb/zdb_005_pos.ksh
new file mode 100755 (executable)
index 0000000..ba97885
--- /dev/null
@@ -0,0 +1,64 @@
+#!/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) 2017 by Lawrence Livermore National Security, LLC.
+#
+
+. $STF_SUITE/include/libtest.shlib
+
+#
+# Description:
+# zdb -l exit codes are correct
+#
+# Strategy:
+# 1. Create a pool
+# 2. Overwrite label 0 on vdev[1] with dd
+# 3. Create an empty file
+# 3. Run zdb -l on vdev[0] and verify exit value 0
+# 4. Run zdb -l on vdev[1] and verify exit value 1
+# 5. Run zdb -l on the empty file and verify exit value 2
+#
+
+log_assert "Verify zdb -l exit codes are correct"
+log_onexit cleanup
+
+function cleanup
+{
+       datasetexists $TESTPOOL && destroy_pool $TESTPOOL
+}
+
+verify_runnable "global"
+verify_disk_count "$DISKS" 2
+
+set -A DISK $DISKS
+
+default_mirror_setup_noexit $DISKS
+log_must $DD if=/dev/zero of=$DEV_RDSKDIR/${DISK[1]} bs=1K count=256 conv=notrunc
+log_must $TRUNCATE -s 0 $TEMPFILE
+
+$ZDB -l $DEV_RDSKDIR/${DISK[0]}
+[[ $? -ne 0 ]] &&
+       log_fail "zdb -l exit codes are incorrect."
+
+$ZDB -l $DEV_RDSKDIR/${DISK[1]}
+[[ $? -ne 1 ]] &&
+       log_fail "zdb -l exit codes are incorrect."
+
+$ZDB -l $TEMPFILE
+[[ $? -ne 2 ]] &&
+       log_fail "zdb -l exit codes are incorrect."
+
+cleanup
+
+log_pass "zdb -l exit codes are correct."