]> git.proxmox.com Git - mirror_zfs.git/commitdiff
Colorize zpool status output
authorTony Hutter <hutter2@llnl.gov>
Fri, 20 Dec 2019 00:26:07 +0000 (16:26 -0800)
committerBrian Behlendorf <behlendorf1@llnl.gov>
Fri, 20 Dec 2019 00:26:07 +0000 (16:26 -0800)
If the ZFS_COLOR env variable is set, then use ANSI color
output in zpool status:

- Column headers are bold
- Degraded or offline pools/vdevs are yellow
- Non-zero error counters and faulted vdevs/pools are red
- The 'status:' and 'action:' sections are yellow if they're
  displaying a warning.

This also includes a new 'faketty' function in libtest.shlib that is
compatible with FreeBSD (code provided by @freqlabs).

Reviewed-by: Jorgen Lundman <lundman@lundman.net>
Reviewed-by: Brian Behlendorf <behlendorf1@llnl.gov>
Reviewed-by: Ryan Moeller <ryan@ixsystems.com>
Signed-off-by: Tony Hutter <hutter2@llnl.gov>
Closes #9340

cmd/zpool/zpool_main.c
include/libzutil.h
lib/libzfs/libzfs_util.c
man/man8/zpool.8
tests/runfiles/common.run
tests/zfs-tests/include/commands.cfg
tests/zfs-tests/include/libtest.shlib
tests/zfs-tests/tests/functional/cli_root/zpool/Makefile.am
tests/zfs-tests/tests/functional/cli_root/zpool/setup.ksh
tests/zfs-tests/tests/functional/cli_root/zpool/zpool_colors.ksh [new file with mode: 0755]

index 0ac8c2a5e4eeb995e4e5f276cef187c3f643bf3a..f7696caac51529fa93fd00e13618881b7fa599c9 100644 (file)
@@ -2033,6 +2033,28 @@ print_status_trim(vdev_stat_t *vs, boolean_t verbose)
        }
 }
 
+/*
+ * Return the color associated with a health string.  This includes returning
+ * NULL for no color change.
+ */
+static char *
+health_str_to_color(const char *health)
+{
+       if (strcmp(health, gettext("FAULTED")) == 0 ||
+           strcmp(health, gettext("SUSPENDED")) == 0 ||
+           strcmp(health, gettext("UNAVAIL")) == 0) {
+               return (ANSI_RED);
+       }
+
+       if (strcmp(health, gettext("OFFLINE")) == 0 ||
+           strcmp(health, gettext("DEGRADED")) == 0 ||
+           strcmp(health, gettext("REMOVED")) == 0) {
+               return (ANSI_YELLOW);
+       }
+
+       return (NULL);
+}
+
 /*
  * Print out configuration state as requested by status_callback.
  */
@@ -2051,6 +2073,7 @@ print_status_config(zpool_handle_t *zhp, status_cbdata_t *cb, const char *name,
        const char *state;
        char *type;
        char *path = NULL;
+       char *rcolor = NULL, *wcolor = NULL, *ccolor = NULL;
 
        if (nvlist_lookup_nvlist_array(nv, ZPOOL_CONFIG_CHILDREN,
            &child, &children) != 0)
@@ -2065,34 +2088,54 @@ print_status_config(zpool_handle_t *zhp, status_cbdata_t *cb, const char *name,
                return;
 
        state = zpool_state_to_name(vs->vs_state, vs->vs_aux);
+
        if (isspare) {
                /*
                 * For hot spares, we use the terms 'INUSE' and 'AVAILABLE' for
                 * online drives.
                 */
                if (vs->vs_aux == VDEV_AUX_SPARED)
-                       state = "INUSE";
+                       state = gettext("INUSE");
                else if (vs->vs_state == VDEV_STATE_HEALTHY)
-                       state = "AVAIL";
+                       state = gettext("AVAIL");
        }
 
-       (void) printf("\t%*s%-*s  %-8s", depth, "", cb->cb_namewidth - depth,
+       printf_color(health_str_to_color(state),
+           "\t%*s%-*s  %-8s", depth, "", cb->cb_namewidth - depth,
            name, state);
 
        if (!isspare) {
+               if (vs->vs_read_errors)
+                       rcolor = ANSI_RED;
+
+               if (vs->vs_write_errors)
+                       wcolor = ANSI_RED;
+
+               if (vs->vs_checksum_errors)
+                       ccolor = ANSI_RED;
+
                if (cb->cb_literal) {
-                       printf(" %5llu %5llu %5llu",
-                           (u_longlong_t)vs->vs_read_errors,
-                           (u_longlong_t)vs->vs_write_errors,
+                       printf(" ");
+                       printf_color(rcolor, "%5llu",
+                           (u_longlong_t)vs->vs_read_errors);
+                       printf(" ");
+                       printf_color(wcolor, "%5llu",
+                           (u_longlong_t)vs->vs_write_errors);
+                       printf(" ");
+                       printf_color(ccolor, "%5llu",
                            (u_longlong_t)vs->vs_checksum_errors);
                } else {
                        zfs_nicenum(vs->vs_read_errors, rbuf, sizeof (rbuf));
                        zfs_nicenum(vs->vs_write_errors, wbuf, sizeof (wbuf));
                        zfs_nicenum(vs->vs_checksum_errors, cbuf,
                            sizeof (cbuf));
-                       printf(" %5s %5s %5s", rbuf, wbuf, cbuf);
+                       printf(" ");
+                       printf_color(rcolor, "%5s", rbuf);
+                       printf(" ");
+                       printf_color(wcolor, "%5s", wbuf);
+                       printf(" ");
+                       printf_color(ccolor, "%5s", cbuf);
                }
-
                if (cb->cb_print_slow_ios) {
                        if (children == 0)  {
                                /* Only leafs vdevs have slow IOs */
@@ -2107,16 +2150,15 @@ print_status_config(zpool_handle_t *zhp, status_cbdata_t *cb, const char *name,
                        else
                                printf(" %5s", rbuf);
                }
-
        }
 
        if (nvlist_lookup_uint64(nv, ZPOOL_CONFIG_NOT_PRESENT,
            &notpresent) == 0) {
                verify(nvlist_lookup_string(nv, ZPOOL_CONFIG_PATH, &path) == 0);
-               (void) printf("  was %s", path);
+               (void) printf("  %s %s", gettext("was"), path);
        } else if (vs->vs_aux != 0) {
                (void) printf("  ");
-
+               color_start(ANSI_RED);
                switch (vs->vs_aux) {
                case VDEV_AUX_OPEN_FAILED:
                        (void) printf(gettext("cannot open"));
@@ -2188,6 +2230,7 @@ print_status_config(zpool_handle_t *zhp, status_cbdata_t *cb, const char *name,
                        (void) printf(gettext("corrupted data"));
                        break;
                }
+               color_end();
        }
 
        /* The root vdev has the scrub/resilver stats */
@@ -2462,14 +2505,16 @@ show_import(nvlist_t *config)
        case ZPOOL_STATUS_MISSING_DEV_R:
        case ZPOOL_STATUS_MISSING_DEV_NR:
        case ZPOOL_STATUS_BAD_GUID_SUM:
-               (void) printf(gettext(" status: One or more devices are "
+               printf_color(ANSI_BOLD, gettext("status: "));
+               printf_color(ANSI_YELLOW, gettext("One or more devices are "
                    "missing from the system.\n"));
                break;
 
        case ZPOOL_STATUS_CORRUPT_LABEL_R:
        case ZPOOL_STATUS_CORRUPT_LABEL_NR:
-               (void) printf(gettext(" status: One or more devices contains "
-                   "corrupted data.\n"));
+               printf_color(ANSI_BOLD, gettext("status: "));
+               printf_color(ANSI_YELLOW, gettext("One or more devices contains"
+                   " corrupted data.\n"));
                break;
 
        case ZPOOL_STATUS_CORRUPT_DATA:
@@ -2478,78 +2523,96 @@ show_import(nvlist_t *config)
                break;
 
        case ZPOOL_STATUS_OFFLINE_DEV:
-               (void) printf(gettext(" status: One or more devices "
+               printf_color(ANSI_BOLD, gettext("status: "));
+               printf_color(ANSI_YELLOW, gettext("One or more devices "
                    "are offlined.\n"));
                break;
 
        case ZPOOL_STATUS_CORRUPT_POOL:
-               (void) printf(gettext(" status: The pool metadata is "
+               printf_color(ANSI_BOLD, gettext("status: "));
+               printf_color(ANSI_YELLOW, gettext("The pool metadata is "
                    "corrupted.\n"));
                break;
 
        case ZPOOL_STATUS_VERSION_OLDER:
-               (void) printf(gettext(" status: The pool is formatted using a "
-                   "legacy on-disk version.\n"));
+               printf_color(ANSI_BOLD, gettext("status: "));
+               printf_color(ANSI_YELLOW, gettext("The pool is formatted using "
+                   "a legacy on-disk version.\n"));
                break;
 
        case ZPOOL_STATUS_VERSION_NEWER:
-               (void) printf(gettext(" status: The pool is formatted using an "
-                   "incompatible version.\n"));
+               printf_color(ANSI_BOLD, gettext("status: "));
+               printf_color(ANSI_YELLOW, gettext("The pool is formatted using "
+                   "an incompatible version.\n"));
                break;
 
        case ZPOOL_STATUS_FEAT_DISABLED:
-               (void) printf(gettext(" status: Some supported features are "
+               printf_color(ANSI_BOLD, gettext("status: "));
+               printf_color(ANSI_YELLOW, gettext("Some supported features are "
                    "not enabled on the pool.\n"));
                break;
 
        case ZPOOL_STATUS_UNSUP_FEAT_READ:
-               (void) printf(gettext("status: The pool uses the following "
+               printf_color(ANSI_BOLD, gettext("status: "));
+               printf_color(ANSI_YELLOW, gettext("The pool uses the following "
                    "feature(s) not supported on this system:\n"));
+               color_start(ANSI_YELLOW);
                zpool_print_unsup_feat(config);
+               color_end();
                break;
 
        case ZPOOL_STATUS_UNSUP_FEAT_WRITE:
-               (void) printf(gettext("status: The pool can only be accessed "
-                   "in read-only mode on this system. It\n\tcannot be "
-                   "accessed in read-write mode because it uses the "
+               printf_color(ANSI_BOLD, gettext("status: "));
+               printf_color(ANSI_YELLOW, gettext("The pool can only be "
+                   "accessed in read-only mode on this system. It\n\tcannot be"
+                   " accessed in read-write mode because it uses the "
                    "following\n\tfeature(s) not supported on this system:\n"));
+               color_start(ANSI_YELLOW);
                zpool_print_unsup_feat(config);
+               color_end();
                break;
 
        case ZPOOL_STATUS_HOSTID_ACTIVE:
-               (void) printf(gettext(" status: The pool is currently "
+               printf_color(ANSI_BOLD, gettext("status: "));
+               printf_color(ANSI_YELLOW, gettext("The pool is currently "
                    "imported by another system.\n"));
                break;
 
        case ZPOOL_STATUS_HOSTID_REQUIRED:
-               (void) printf(gettext(" status: The pool has the "
+               printf_color(ANSI_BOLD, gettext("status: "));
+               printf_color(ANSI_YELLOW, gettext("The pool has the "
                    "multihost property on.  It cannot\n\tbe safely imported "
                    "when the system hostid is not set.\n"));
                break;
 
        case ZPOOL_STATUS_HOSTID_MISMATCH:
-               (void) printf(gettext(" status: The pool was last accessed by "
-                   "another system.\n"));
+               printf_color(ANSI_BOLD, gettext("status: "));
+               printf_color(ANSI_YELLOW, gettext("The pool was last accessed "
+                   "by another system.\n"));
                break;
 
        case ZPOOL_STATUS_FAULTED_DEV_R:
        case ZPOOL_STATUS_FAULTED_DEV_NR:
-               (void) printf(gettext(" status: One or more devices are "
+               printf_color(ANSI_BOLD, gettext("status: "));
+               printf_color(ANSI_YELLOW, gettext("One or more devices are "
                    "faulted.\n"));
                break;
 
        case ZPOOL_STATUS_BAD_LOG:
-               (void) printf(gettext(" status: An intent log record cannot be "
-                   "read.\n"));
+               printf_color(ANSI_BOLD, gettext("status: "));
+               printf_color(ANSI_YELLOW, gettext("An intent log record cannot "
+                   "be read.\n"));
                break;
 
        case ZPOOL_STATUS_RESILVERING:
-               (void) printf(gettext(" status: One or more devices were being "
-                   "resilvered.\n"));
+               printf_color(ANSI_BOLD, gettext("status: "));
+               printf_color(ANSI_YELLOW, gettext("One or more devices were "
+                   "being resilvered.\n"));
                break;
 
        case ZPOOL_STATUS_ERRATA:
-               (void) printf(gettext(" status: Errata #%d detected.\n"),
+               printf_color(ANSI_BOLD, gettext("status: "));
+               printf_color(ANSI_YELLOW, gettext("Errata #%d detected.\n"),
                    errata);
                break;
 
@@ -2644,13 +2707,15 @@ show_import(nvlist_t *config)
                            "backup.\n"));
                        break;
                case ZPOOL_STATUS_UNSUP_FEAT_READ:
-                       (void) printf(gettext("action: The pool cannot be "
+                       printf_color(ANSI_BOLD, gettext("action: "));
+                       printf_color(ANSI_YELLOW, gettext("The pool cannot be "
                            "imported. Access the pool on a system that "
                            "supports\n\tthe required feature(s), or recreate "
                            "the pool from backup.\n"));
                        break;
                case ZPOOL_STATUS_UNSUP_FEAT_WRITE:
-                       (void) printf(gettext("action: The pool cannot be "
+                       printf_color(ANSI_BOLD, gettext("action: "));
+                       printf_color(ANSI_YELLOW, gettext("The pool cannot be "
                            "imported in read-write mode. Import the pool "
                            "with\n"
                            "\t\"-o readonly=on\", access the pool on a system "
@@ -3720,7 +3785,7 @@ print_cmd_columns(vdev_cmd_data_list_t *vcdl, int use_dashes)
                        for (j = 0; j < vcdl->uniq_cols_width[i]; j++)
                                printf("-");
                } else {
-                       printf("%*s", vcdl->uniq_cols_width[i],
+                       printf_color(ANSI_BOLD, "%*s", vcdl->uniq_cols_width[i],
                            vcdl->uniq_cols[i]);
                }
        }
@@ -7024,7 +7089,9 @@ print_scan_status(pool_scan_stat_t *ps)
        char processed_buf[7], scanned_buf[7], issued_buf[7], total_buf[7];
        char srate_buf[7], irate_buf[7];
 
-       (void) printf(gettext("  scan: "));
+       printf("  ");
+       printf_color(ANSI_BOLD, gettext("scan:"));
+       printf(" ");
 
        /* If there's never been a scan, there's not much to say. */
        if (ps == NULL || ps->pss_func == POOL_SCAN_NONE ||
@@ -7503,38 +7570,52 @@ status_callback(zpool_handle_t *zhp, void *data)
 
        health = zpool_get_state_str(zhp);
 
-       (void) printf(gettext("  pool: %s\n"), zpool_get_name(zhp));
-       (void) printf(gettext(" state: %s\n"), health);
+       printf("  ");
+       printf_color(ANSI_BOLD, gettext("pool:"));
+       printf(" %s\n", zpool_get_name(zhp));
+       printf(" ");
+       printf_color(ANSI_BOLD, gettext("state: "));
+
+       printf_color(health_str_to_color(health), "%s", health);
+
+       printf("\n");
 
        switch (reason) {
        case ZPOOL_STATUS_MISSING_DEV_R:
-               (void) printf(gettext("status: One or more devices could not "
-                   "be opened.  Sufficient replicas exist for\n\tthe pool to "
-                   "continue functioning in a degraded state.\n"));
-               (void) printf(gettext("action: Attach the missing device and "
-                   "online it using 'zpool online'.\n"));
+               printf_color(ANSI_BOLD, gettext("status: "));
+               printf_color(ANSI_YELLOW, gettext("One or more devices could "
+                   "not be opened.  Sufficient replicas exist for\n\tthe pool "
+                   "to continue functioning in a degraded state.\n"));
+               printf_color(ANSI_BOLD, gettext("action: "));
+               printf_color(ANSI_YELLOW, gettext("Attach the missing device "
+                   "and online it using 'zpool online'.\n"));
                break;
 
        case ZPOOL_STATUS_MISSING_DEV_NR:
-               (void) printf(gettext("status: One or more devices could not "
-                   "be opened.  There are insufficient\n\treplicas for the "
-                   "pool to continue functioning.\n"));
-               (void) printf(gettext("action: Attach the missing device and "
-                   "online it using 'zpool online'.\n"));
+               printf_color(ANSI_BOLD, gettext("status: "));
+               printf_color(ANSI_YELLOW, gettext("One or more devices could "
+                   "not be opened.  There are insufficient\n\treplicas for the"
+                   " pool to continue functioning.\n"));
+               printf_color(ANSI_BOLD, gettext("action: "));
+               printf_color(ANSI_YELLOW, gettext("Attach the missing device "
+                   "and online it using 'zpool online'.\n"));
                break;
 
        case ZPOOL_STATUS_CORRUPT_LABEL_R:
-               (void) printf(gettext("status: One or more devices could not "
-                   "be used because the label is missing or\n\tinvalid.  "
+               printf_color(ANSI_BOLD, gettext("status: "));
+               printf_color(ANSI_YELLOW, gettext("One or more devices could "
+                   "not be used because the label is missing or\n\tinvalid.  "
                    "Sufficient replicas exist for the pool to continue\n\t"
                    "functioning in a degraded state.\n"));
-               (void) printf(gettext("action: Replace the device using "
+               printf_color(ANSI_BOLD, gettext("action: "));
+               printf_color(ANSI_YELLOW, gettext("Replace the device using "
                    "'zpool replace'.\n"));
                break;
 
        case ZPOOL_STATUS_CORRUPT_LABEL_NR:
-               (void) printf(gettext("status: One or more devices could not "
-                   "be used because the label is missing \n\tor invalid.  "
+               printf_color(ANSI_BOLD, gettext("status: "));
+               printf_color(ANSI_YELLOW, gettext("One or more devices could "
+                   "not be used because the label is missing \n\tor invalid.  "
                    "There are insufficient replicas for the pool to "
                    "continue\n\tfunctioning.\n"));
                zpool_explain_recover(zpool_get_handle(zhp),
@@ -7542,175 +7623,209 @@ status_callback(zpool_handle_t *zhp, void *data)
                break;
 
        case ZPOOL_STATUS_FAILING_DEV:
-               (void) printf(gettext("status: One or more devices has "
+               printf_color(ANSI_BOLD, gettext("status: "));
+               printf_color(ANSI_YELLOW, gettext("One or more devices has "
                    "experienced an unrecoverable error.  An\n\tattempt was "
                    "made to correct the error.  Applications are "
                    "unaffected.\n"));
-               (void) printf(gettext("action: Determine if the device needs "
-                   "to be replaced, and clear the errors\n\tusing "
-                   "'zpool clear' or replace the device with 'zpool "
+               printf_color(ANSI_BOLD, gettext("action: "));
+                       printf_color(ANSI_YELLOW, gettext("Determine if the "
+                   "device needs to be replaced, and clear the errors\n\tusing"
+                   " 'zpool clear' or replace the device with 'zpool "
                    "replace'.\n"));
                break;
 
        case ZPOOL_STATUS_OFFLINE_DEV:
-               (void) printf(gettext("status: One or more devices has "
+               printf_color(ANSI_BOLD, gettext("status: "));
+               printf_color(ANSI_YELLOW, gettext("One or more devices has "
                    "been taken offline by the administrator.\n\tSufficient "
                    "replicas exist for the pool to continue functioning in "
                    "a\n\tdegraded state.\n"));
-               (void) printf(gettext("action: Online the device using "
-                   "'zpool online' or replace the device with\n\t'zpool "
+               printf_color(ANSI_BOLD, gettext("action: "));
+               printf_color(ANSI_YELLOW, gettext("Online the device "
+                   "using 'zpool online' or replace the device with\n\t'zpool "
                    "replace'.\n"));
                break;
 
        case ZPOOL_STATUS_REMOVED_DEV:
-               (void) printf(gettext("status: One or more devices has "
+               printf_color(ANSI_BOLD, gettext("status: "));
+               printf_color(ANSI_YELLOW, gettext("One or more devices has "
                    "been removed by the administrator.\n\tSufficient "
                    "replicas exist for the pool to continue functioning in "
                    "a\n\tdegraded state.\n"));
-               (void) printf(gettext("action: Online the device using "
-                   "'zpool online' or replace the device with\n\t'zpool "
+               printf_color(ANSI_BOLD, gettext("action: "));
+               printf_color(ANSI_YELLOW, gettext("Online the device "
+                   "using zpool online' or replace the device with\n\t'zpool "
                    "replace'.\n"));
                break;
 
        case ZPOOL_STATUS_RESILVERING:
-               (void) printf(gettext("status: One or more devices is "
+               printf_color(ANSI_BOLD, gettext("status: "));
+               printf_color(ANSI_YELLOW, gettext("One or more devices is "
                    "currently being resilvered.  The pool will\n\tcontinue "
                    "to function, possibly in a degraded state.\n"));
-               (void) printf(gettext("action: Wait for the resilver to "
+               printf_color(ANSI_BOLD, gettext("action: "));
+               printf_color(ANSI_YELLOW, gettext("Wait for the resilver to "
                    "complete.\n"));
                break;
 
        case ZPOOL_STATUS_CORRUPT_DATA:
-               (void) printf(gettext("status: One or more devices has "
+               printf_color(ANSI_BOLD, gettext("status: "));
+               printf_color(ANSI_YELLOW, gettext("One or more devices has "
                    "experienced an error resulting in data\n\tcorruption.  "
                    "Applications may be affected.\n"));
-               (void) printf(gettext("action: Restore the file in question "
-                   "if possible.  Otherwise restore the\n\tentire pool from "
+               printf_color(ANSI_BOLD, gettext("action: "));
+               printf_color(ANSI_YELLOW, gettext("Restore the file in question"
+                   " if possible.  Otherwise restore the\n\tentire pool from "
                    "backup.\n"));
                break;
 
        case ZPOOL_STATUS_CORRUPT_POOL:
-               (void) printf(gettext("status: The pool metadata is corrupted "
-                   "and the pool cannot be opened.\n"));
+               printf_color(ANSI_BOLD, gettext("status: "));
+               printf_color(ANSI_YELLOW, gettext("The pool metadata is "
+                   "corrupted and the pool cannot be opened.\n"));
                zpool_explain_recover(zpool_get_handle(zhp),
                    zpool_get_name(zhp), reason, config);
                break;
 
        case ZPOOL_STATUS_VERSION_OLDER:
-               (void) printf(gettext("status: The pool is formatted using a "
-                   "legacy on-disk format.  The pool can\n\tstill be used, "
+               printf_color(ANSI_BOLD, gettext("status: "));
+               printf_color(ANSI_YELLOW, gettext("The pool is formatted using "
+                   "a legacy on-disk format.  The pool can\n\tstill be used, "
                    "but some features are unavailable.\n"));
-               (void) printf(gettext("action: Upgrade the pool using 'zpool "
-                   "upgrade'.  Once this is done, the\n\tpool will no longer "
-                   "be accessible on software that does not support\n\t"
+               printf_color(ANSI_BOLD, gettext("action: "));
+               printf_color(ANSI_YELLOW, gettext("Upgrade the pool using "
+                   "'zpool upgrade'.  Once this is done, the\n\tpool will no "
+                   "longer be accessible on software that does not support\n\t"
                    "feature flags.\n"));
                break;
 
        case ZPOOL_STATUS_VERSION_NEWER:
-               (void) printf(gettext("status: The pool has been upgraded to a "
-                   "newer, incompatible on-disk version.\n\tThe pool cannot "
-                   "be accessed on this system.\n"));
-               (void) printf(gettext("action: Access the pool from a system "
-                   "running more recent software, or\n\trestore the pool from "
-                   "backup.\n"));
+               printf_color(ANSI_BOLD, gettext("status: "));
+               printf_color(ANSI_YELLOW, gettext("The pool has been upgraded "
+                   "to a newer, incompatible on-disk version.\n\tThe pool "
+                   "cannot be accessed on this system.\n"));
+               printf_color(ANSI_BOLD, gettext("action: "));
+               printf_color(ANSI_YELLOW, gettext("Access the pool from a "
+                   "system running more recent software, or\n\trestore the "
+                   "pool from backup.\n"));
                break;
 
        case ZPOOL_STATUS_FEAT_DISABLED:
-               (void) printf(gettext("status: Some supported features are not "
-                   "enabled on the pool. The pool can\n\tstill be used, but "
-                   "some features are unavailable.\n"));
-               (void) printf(gettext("action: Enable all features using "
+               printf_color(ANSI_BOLD, gettext("status: "));
+               printf_color(ANSI_YELLOW, gettext("Some supported features are "
+                   "not enabled on the pool. The pool can\n\tstill be used, "
+                   "but some features are unavailable.\n"));
+               printf_color(ANSI_BOLD, gettext("action: "));
+               printf_color(ANSI_YELLOW, gettext("Enable all features using "
                    "'zpool upgrade'. Once this is done,\n\tthe pool may no "
                    "longer be accessible by software that does not support\n\t"
                    "the features. See zpool-features(5) for details.\n"));
                break;
 
        case ZPOOL_STATUS_UNSUP_FEAT_READ:
-               (void) printf(gettext("status: The pool cannot be accessed on "
-                   "this system because it uses the\n\tfollowing feature(s) "
-                   "not supported on this system:\n"));
+               printf_color(ANSI_BOLD, gettext("status: "));
+               printf_color(ANSI_YELLOW, gettext("The pool cannot be accessed "
+                   "on this system because it uses the\n\tfollowing feature(s)"
+                   " not supported on this system:\n"));
                zpool_print_unsup_feat(config);
                (void) printf("\n");
-               (void) printf(gettext("action: Access the pool from a system "
-                   "that supports the required feature(s),\n\tor restore the "
-                   "pool from backup.\n"));
+               printf_color(ANSI_BOLD, gettext("action: "));
+               printf_color(ANSI_YELLOW, gettext("Access the pool from a "
+                   "system that supports the required feature(s),\n\tor "
+                   "restore the pool from backup.\n"));
                break;
 
        case ZPOOL_STATUS_UNSUP_FEAT_WRITE:
-               (void) printf(gettext("status: The pool can only be accessed "
-                   "in read-only mode on this system. It\n\tcannot be "
-                   "accessed in read-write mode because it uses the "
+               printf_color(ANSI_BOLD, gettext("status: "));
+               printf_color(ANSI_YELLOW, gettext("The pool can only be "
+                   "accessed in read-only mode on this system. It\n\tcannot be"
+                   " accessed in read-write mode because it uses the "
                    "following\n\tfeature(s) not supported on this system:\n"));
                zpool_print_unsup_feat(config);
                (void) printf("\n");
-               (void) printf(gettext("action: The pool cannot be accessed in "
-                   "read-write mode. Import the pool with\n"
+               printf_color(ANSI_BOLD, gettext("action: "));
+               printf_color(ANSI_YELLOW, gettext("The pool cannot be accessed "
+                   "in read-write mode. Import the pool with\n"
                    "\t\"-o readonly=on\", access the pool from a system that "
                    "supports the\n\trequired feature(s), or restore the "
                    "pool from backup.\n"));
                break;
 
        case ZPOOL_STATUS_FAULTED_DEV_R:
-               (void) printf(gettext("status: One or more devices are "
+               printf_color(ANSI_BOLD, gettext("status: "));
+               printf_color(ANSI_YELLOW, gettext("One or more devices are "
                    "faulted in response to persistent errors.\n\tSufficient "
                    "replicas exist for the pool to continue functioning "
                    "in a\n\tdegraded state.\n"));
-               (void) printf(gettext("action: Replace the faulted device, "
+               printf_color(ANSI_BOLD, gettext("action: "));
+               printf_color(ANSI_YELLOW, gettext("Replace the faulted device, "
                    "or use 'zpool clear' to mark the device\n\trepaired.\n"));
                break;
 
        case ZPOOL_STATUS_FAULTED_DEV_NR:
-               (void) printf(gettext("status: One or more devices are "
+               printf_color(ANSI_BOLD, gettext("status: "));
+               printf_color(ANSI_YELLOW, gettext("One or more devices are "
                    "faulted in response to persistent errors.  There are "
                    "insufficient replicas for the pool to\n\tcontinue "
                    "functioning.\n"));
-               (void) printf(gettext("action: Destroy and re-create the pool "
-                   "from a backup source.  Manually marking the device\n"
+               printf_color(ANSI_BOLD, gettext("action: "));
+               printf_color(ANSI_YELLOW, gettext("Destroy and re-create the "
+                   "pool from a backup source.  Manually marking the device\n"
                    "\trepaired using 'zpool clear' may allow some data "
                    "to be recovered.\n"));
                break;
 
        case ZPOOL_STATUS_IO_FAILURE_MMP:
-               (void) printf(gettext("status: The pool is suspended because "
-                   "multihost writes failed or were delayed;\n\tanother "
-                   "system could import the pool undetected.\n"));
-               (void) printf(gettext("action: Make sure the pool's devices "
-                   "are connected, then reboot your system and\n\timport the "
+               printf_color(ANSI_BOLD, gettext("status: "));
+               printf_color(ANSI_YELLOW, gettext("The pool is suspended "
+                   "because multihost writes failed or were delayed;\n\t"
+                   "another system could import the pool undetected.\n"));
+               printf_color(ANSI_BOLD, gettext("action: "));
+               printf_color(ANSI_YELLOW, gettext("Make sure the pool's devices"
+                   " are connected, then reboot your system and\n\timport the "
                    "pool.\n"));
                break;
 
        case ZPOOL_STATUS_IO_FAILURE_WAIT:
        case ZPOOL_STATUS_IO_FAILURE_CONTINUE:
-               (void) printf(gettext("status: One or more devices are "
+               printf_color(ANSI_BOLD, gettext("status: "));
+               printf_color(ANSI_YELLOW, gettext("One or more devices are "
                    "faulted in response to IO failures.\n"));
-               (void) printf(gettext("action: Make sure the affected devices "
-                   "are connected, then run 'zpool clear'.\n"));
+               printf_color(ANSI_BOLD, gettext("action: "));
+               printf_color(ANSI_YELLOW, gettext("Make sure the affected "
+                   "devices are connected, then run 'zpool clear'.\n"));
                break;
 
        case ZPOOL_STATUS_BAD_LOG:
-               (void) printf(gettext("status: An intent log record "
+               printf_color(ANSI_BOLD, gettext("status: "));
+               printf_color(ANSI_YELLOW, gettext("An intent log record "
                    "could not be read.\n"
                    "\tWaiting for administrator intervention to fix the "
                    "faulted pool.\n"));
-               (void) printf(gettext("action: Either restore the affected "
+               printf_color(ANSI_BOLD, gettext("action: "));
+               printf_color(ANSI_YELLOW, gettext("Either restore the affected "
                    "device(s) and run 'zpool online',\n"
                    "\tor ignore the intent log records by running "
                    "'zpool clear'.\n"));
                break;
 
        case ZPOOL_STATUS_HOSTID_MISMATCH:
-               (void) printf(gettext("status: Mismatch between pool hostid "
-                   "and system hostid on imported pool.\n\tThis pool was "
+               printf_color(ANSI_BOLD, gettext("status: "));
+               printf_color(ANSI_YELLOW, gettext("Mismatch between pool hostid"
+                   " and system hostid on imported pool.\n\tThis pool was "
                    "previously imported into a system with a different "
                    "hostid,\n\tand then was verbatim imported into this "
                    "system.\n"));
-               (void) printf(gettext("action: Export this pool on all systems "
-                   "on which it is imported.\n"
+               printf_color(ANSI_BOLD, gettext("action: "));
+               printf_color(ANSI_YELLOW, gettext("Export this pool on all "
+                   "systems on which it is imported.\n"
                    "\tThen import it to correct the mismatch.\n"));
                break;
 
        case ZPOOL_STATUS_ERRATA:
-               (void) printf(gettext("status: Errata #%d detected.\n"),
+               printf_color(ANSI_BOLD, gettext("status: "));
+               printf_color(ANSI_YELLOW, gettext("Errata #%d detected.\n"),
                    errata);
 
                switch (errata) {
@@ -7718,16 +7833,18 @@ status_callback(zpool_handle_t *zhp, void *data)
                        break;
 
                case ZPOOL_ERRATA_ZOL_2094_SCRUB:
-                       (void) printf(gettext("action: To correct the issue "
-                           "run 'zpool scrub'.\n"));
+                       printf_color(ANSI_BOLD, gettext("action: "));
+                       printf_color(ANSI_YELLOW, gettext("To correct the issue"
+                           " run 'zpool scrub'.\n"));
                        break;
 
                case ZPOOL_ERRATA_ZOL_6845_ENCRYPTION:
                        (void) printf(gettext("\tExisting encrypted datasets "
                            "contain an on-disk incompatibility\n\twhich "
                            "needs to be corrected.\n"));
-                       (void) printf(gettext("action: To correct the issue "
-                           "backup existing encrypted datasets to new\n\t"
+                       printf_color(ANSI_BOLD, gettext("action: "));
+                       printf_color(ANSI_YELLOW, gettext("To correct the issue"
+                           " backup existing encrypted datasets to new\n\t"
                            "encrypted datasets and destroy the old ones. "
                            "'zfs mount -o ro' can\n\tbe used to temporarily "
                            "mount existing encrypted datasets readonly.\n"));
@@ -7738,13 +7855,14 @@ status_callback(zpool_handle_t *zhp, void *data)
                            "and bookmarks contain an on-disk\n\tincompat"
                            "ibility. This may cause on-disk corruption if "
                            "they are used\n\twith 'zfs recv'.\n"));
-                       (void) printf(gettext("action: To correct the issue, "
-                           "enable the bookmark_v2 feature. No additional\n\t"
-                           "action is needed if there are no encrypted "
-                           "snapshots or bookmarks.\n\tIf preserving the "
-                           "encrypted snapshots and bookmarks is required, "
-                           "use\n\ta non-raw send to backup and restore them. "
-                           "Alternately, they may be\n\tremoved to resolve "
+                       printf_color(ANSI_BOLD, gettext("action: "));
+                       printf_color(ANSI_YELLOW, gettext("To correct the"
+                           "issue, enable the bookmark_v2 feature. No "
+                           "additional\n\taction is needed if there are no "
+                           "encrypted snapshots or bookmarks.\n\tIf preserving"
+                           "the encrypted snapshots and bookmarks is required,"
+                           " use\n\ta non-raw send to backup and restore them."
+                           " Alternately, they may be\n\tremoved to resolve "
                            "the incompatibility.\n"));
                        break;
 
@@ -7764,9 +7882,11 @@ status_callback(zpool_handle_t *zhp, void *data)
                assert(reason == ZPOOL_STATUS_OK);
        }
 
-       if (msgid != NULL)
-               (void) printf(gettext("   see: http://zfsonlinux.org/msg/%s\n"),
-                   msgid);
+       if (msgid != NULL) {
+               printf("   ");
+               printf_color(ANSI_BOLD, gettext("see:"));
+               printf(gettext(" http://zfsonlinux.org/msg/%s\n"), msgid);
+       }
 
        if (config != NULL) {
                uint64_t nerr;
@@ -7782,7 +7902,6 @@ status_callback(zpool_handle_t *zhp, void *data)
                    ZPOOL_CONFIG_SCAN_STATS, (uint64_t **)&ps, &c);
                (void) nvlist_lookup_uint64_array(nvroot,
                    ZPOOL_CONFIG_REMOVAL_STATS, (uint64_t **)&prs, &c);
-
                print_scan_status(ps);
                print_checkpoint_scan_warning(ps, pcs);
                print_removal_status(zhp, prs);
@@ -7793,13 +7912,16 @@ status_callback(zpool_handle_t *zhp, void *data)
                if (cbp->cb_namewidth < 10)
                        cbp->cb_namewidth = 10;
 
+               color_start(ANSI_BOLD);
                (void) printf(gettext("config:\n\n"));
                (void) printf(gettext("\t%-*s  %-8s %5s %5s %5s"),
                    cbp->cb_namewidth, "NAME", "STATE", "READ", "WRITE",
                    "CKSUM");
+               color_end();
 
-               if (cbp->cb_print_slow_ios)
-                       (void) printf(" %5s", gettext("SLOW"));
+               if (cbp->cb_print_slow_ios) {
+                       printf_color(ANSI_BOLD, " %5s", gettext("SLOW"));
+               }
 
                if (cbp->vcdl != NULL)
                        print_cmd_columns(cbp->vcdl, 0);
index 3dd4fcfb23e5f417a55114b91df4479c67c51326..98998e1950b560420924ae9c72d0926a2cc85597 100644 (file)
@@ -146,6 +146,18 @@ extern int zpool_history_unpack(char *, uint64_t, uint64_t *, nvlist_t ***,
 struct zfs_cmd;
 int zfs_ioctl_fd(int fd, unsigned long request, struct zfs_cmd *zc);
 
+/*
+ * List of colors to use
+ */
+#define        ANSI_RED        "\033[0;31m"
+#define        ANSI_YELLOW     "\033[0;33m"
+#define        ANSI_RESET      "\033[0m"
+#define        ANSI_BOLD       "\033[1m"
+
+void color_start(char *color);
+void color_end(void);
+int printf_color(char *color, char *format, ...);
+
 #ifdef __cplusplus
 }
 #endif
index 4a545a0274366e96966e8fb4591b081758edaf3a..2ce3ad1062dc3c626e01991bd607ab39a3cd8ec2 100644 (file)
@@ -1886,3 +1886,95 @@ zfs_version_print(void)
 
        return (0);
 }
+
+/*
+ * Return 1 if the user requested ANSI color output, and our terminal supports
+ * it.  Return 0 for no color.
+ */
+static int
+use_color(void)
+{
+       static int use_color = -1;
+       char *term;
+
+       /*
+        * Optimization:
+        *
+        * For each zpool invocation, we do a single check to see if we should
+        * be using color or not, and cache that value for the lifetime of the
+        * the zpool command.  That makes it cheap to call use_color() when
+        * we're printing with color.  We assume that the settings are not going
+        * to change during the invocation of a zpool command (the user isn't
+        * going to change the ZFS_COLOR value while zpool is running, for
+        * example).
+        */
+       if (use_color != -1) {
+               /*
+                * We've already figured out if we should be using color or
+                * not.  Return the cached value.
+                */
+               return (use_color);
+       }
+
+       term = getenv("TERM");
+       /*
+        * The user sets the ZFS_COLOR env var set to enable zpool ANSI color
+        * output.  However if NO_COLOR is set (https://no-color.org/) then
+        * don't use it.  Also, don't use color if terminal doesn't support
+        * it.
+        */
+       if (libzfs_envvar_is_set("ZFS_COLOR") &&
+           !libzfs_envvar_is_set("NO_COLOR") &&
+           isatty(STDOUT_FILENO) && term && strcmp("dumb", term) != 0 &&
+           strcmp("unknown", term) != 0) {
+               /* Color supported */
+               use_color = 1;
+       } else {
+               use_color = 0;
+       }
+
+       return (use_color);
+}
+
+/*
+ * color_start() and color_end() are used for when you want to colorize a block
+ * of text.  For example:
+ *
+ * color_start(ANSI_RED_FG)
+ * printf("hello");
+ * printf("world");
+ * color_end();
+ */
+void
+color_start(char *color)
+{
+       if (use_color())
+               printf("%s", color);
+}
+
+void
+color_end(void)
+{
+       if (use_color())
+               printf(ANSI_RESET);
+}
+
+/* printf() with a color.  If color is NULL, then do a normal printf. */
+int
+printf_color(char *color, char *format, ...)
+{
+       va_list aptr;
+       int rc;
+
+       if (color)
+               color_start(color);
+
+       va_start(aptr, format);
+       rc = vprintf(format, aptr);
+       va_end(aptr);
+
+       if (color)
+               color_end();
+
+       return (rc);
+}
index 4b15785c2921c91bbc2c95b5e9cdaabd63795e71..272e4bc1ce4d30bfa3424588adba21c049070c64 100644 (file)
@@ -453,6 +453,12 @@ Cause
 to dump core on exit for the purposes of running
 .Sy ::findleaks .
 .El
+.Bl -tag -width "ZFS_COLOR"
+.It Ev ZFS_COLOR
+Use ANSI color in
+.Nm zpool status
+output.
+.El
 .Bl -tag -width "ZPOOL_IMPORT_PATH"
 .It Ev ZPOOL_IMPORT_PATH
 The search path for devices or files to use with the pool. This is a colon-separated list of directories in which
index 87849af653b2b8a743524bc82741e6e8b61f211e..a3396ac07ffc1dfb8d1e323d7c53c3472adbd880 100644 (file)
@@ -282,7 +282,7 @@ tests = ['zfs_upgrade_001_pos', 'zfs_upgrade_002_pos', 'zfs_upgrade_003_pos',
 tags = ['functional', 'cli_root', 'zfs_upgrade']
 
 [tests/functional/cli_root/zpool]
-tests = ['zpool_001_neg', 'zpool_002_pos', 'zpool_003_pos']
+tests = ['zpool_001_neg', 'zpool_002_pos', 'zpool_003_pos', 'zpool_colors']
 tags = ['functional', 'cli_root', 'zpool']
 
 [tests/functional/cli_root/zpool_add]
index 64b34f477344474c4c788a3040e422343c752e0e..d993a8576c767a8650504576b4becf55a93aa116 100644 (file)
@@ -77,6 +77,7 @@ export SYSTEM_FILES_COMMON='arp
     rm
     rmdir
     scp
+    script
     sed
     seq
     setfacl
index 5b5f3189c5b21018928b2caf6c913441a4418c69..df794418c9fca1ec99d129e5c47df321cf50534a 100644 (file)
@@ -3916,3 +3916,18 @@ function stat_size #<path>
                ;;
        esac
 }
+
+# Run a command as if it was being run in a TTY.
+#
+# Usage:
+#
+#    faketty command
+#
+function faketty
+{
+    if is_freebsd; then
+        script -q /dev/null "$@"
+    else
+        script --return --quiet -c "$*" /dev/null
+    fi
+}
index 2d0046c53a17e0edd809c46e82d1efda11d2b214..327f236211586c5f046b189e4951e91883b2e66b 100644 (file)
@@ -4,4 +4,5 @@ dist_pkgdata_SCRIPTS = \
        cleanup.ksh \
        zpool_001_neg.ksh \
        zpool_002_pos.ksh \
-       zpool_003_pos.ksh
+       zpool_003_pos.ksh \
+       zpool_colors.ksh
index 6a9af3bc28c3c755ffd2fefbe7638de5753488c1..4e3b6b0e9f474115c723deed5de70f9406822ed4 100755 (executable)
@@ -29,4 +29,4 @@
 
 DISK=${DISKS%% *}
 
-default_setup $DISK
+default_mirror_setup $DISKS
diff --git a/tests/zfs-tests/tests/functional/cli_root/zpool/zpool_colors.ksh b/tests/zfs-tests/tests/functional/cli_root/zpool/zpool_colors.ksh
new file mode 100755 (executable)
index 0000000..0ec1e84
--- /dev/null
@@ -0,0 +1,91 @@
+#!/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) 2019 Lawrence Livermore National Security, LLC.
+
+. $STF_SUITE/include/libtest.shlib
+
+#
+# DESCRIPTION:
+#      Test that zpool status colored output works.
+#
+# STRATEGY:
+# 1. Create a pool with a bunch of errors and force fault one of the vdevs.
+# 2. Look for 'pool:' in bold.
+# 3. Look for 'DEGRADED' in yellow
+# 3. Look for 'FAULTED' in red
+#
+
+verify_runnable "both"
+
+function cleanup
+{
+       zinject -c all
+}
+
+log_onexit cleanup
+
+log_assert "Test colorized zpool status output"
+
+DISK2="$(echo $DISKS | cut -d' ' -f2)"
+DISK3="$(echo $DISKS | cut -d' ' -f3)"
+
+log_must dd if=/dev/urandom of=/$TESTDIR/testfile bs=10M count=1
+
+log_must zpool sync
+
+log_must zpool offline -f $TESTPOOL $DISK3
+log_must wait_for_degraded $TESTPOOL
+log_must zinject -d $DISK2 -e io -T read -f 20 $TESTPOOL
+log_must zinject -d $DISK2 -e io -T write -f 20 $TESTPOOL
+
+
+log_must zpool scrub -w $TESTPOOL
+log_must zinject -c all
+
+
+# Use 'script' to fake zpool status into thinking it's running in a tty.
+# Log the output here in case it's needed for postmortem.
+log_note "$(faketty TERM=xterm-256color ZFS_COLOR=1 zpool status)"
+
+# Replace the escape codes with "ESC" so they're easier to grep
+out="$(faketty TERM=xterm-256color ZFS_COLOR=1 zpool status | \
+    grep -E 'pool:|DEGRADED' | \
+    sed -r 's/\s+//g;'$(echo -e 's/\033/ESC/g'))"
+
+log_note "$(echo $out)"
+
+log_note "Look for 'pool:' in bold"
+log_must eval "echo \"$out\" | grep -q 'ESC\[1mpool:ESC\[0m' "
+
+log_note "Look for 'DEGRADED' in yellow"
+log_must eval "echo \"$out\" | grep -q 'ESC\[0;33mDEGRADEDESC\[0m'"
+
+#
+# The escape code for 'FAULTED' is a little more tricky.  The line starts like
+# this:
+#
+# <start red escape code> loop2  FAULTED <end escape code>
+#
+# Luckily, awk counts the start and end escape codes as separate fields, so
+# we can easily remove the vdev field to get what we want.
+#
+out="$(faketty TERM=xterm-256color ZFS_COLOR=1 zpool status \
+    | awk '/FAULTED/{print $1$3$4}' | sed -r $(echo -e 's/\033/ESC/g'))"
+
+log_note "Look for 'FAULTED' in red"
+log_must eval "echo \"$out\" | grep -q 'ESC\[0;31mFAULTEDESC\[0m'"
+
+log_pass "zpool status displayed colors"