From be9e1b6ce725b4e9014bda27ca1756a9da7b4ee8 Mon Sep 17 00:00:00 2001 From: Tony Hutter Date: Wed, 18 Sep 2019 14:23:53 -0700 Subject: [PATCH] Colorize zpool status output 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. Signed-off-by: Tony Hutter --- cmd/zpool/zpool_main.c | 386 ++++++++---- config/ax_require_defined.m4 | 37 ++ config/ax_with_curses.m4 | 582 ++++++++++++++++++ config/user.m4 | 1 + include/libzutil.h | 12 + lib/libzfs/Makefile.am | 2 +- lib/libzfs/libzfs_util.c | 106 ++++ man/man8/zpool.8 | 6 + .../functional/cli_root/zpool/Makefile.am | 3 +- .../tests/functional/cli_root/zpool/setup.ksh | 2 +- .../cli_root/zpool/zpool_colors.ksh | 78 +++ 11 files changed, 1079 insertions(+), 136 deletions(-) create mode 100644 config/ax_require_defined.m4 create mode 100644 config/ax_with_curses.m4 create mode 100755 tests/zfs-tests/tests/functional/cli_root/zpool/zpool_colors.ksh diff --git a/cmd/zpool/zpool_main.c b/cmd/zpool/zpool_main.c index 7a95add94a20..d7cfdd6926c7 100644 --- a/cmd/zpool/zpool_main.c +++ b/cmd/zpool/zpool_main.c @@ -2033,6 +2033,26 @@ 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, "FAULTED") == 0 || + strcmp(health, "SUSPENDED") == 0 || + strcmp(health, "UNAVAIL") == 0) + return (ANSI_RED); + + if (strcmp(health, "OFFLINE") == 0 || + strcmp(health, "DEGRADED") == 0 || + strcmp(health, "REMOVED") == 0) + return (ANSI_YELLOW); + + return (NULL); +} + /* * Print out configuration state as requested by status_callback. */ @@ -2051,6 +2071,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,6 +2086,7 @@ 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 @@ -2076,23 +2098,42 @@ print_status_config(zpool_handle_t *zhp, status_cbdata_t *cb, const char *name, state = "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,7 +2148,6 @@ 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, @@ -2116,7 +2156,7 @@ print_status_config(zpool_handle_t *zhp, status_cbdata_t *cb, const char *name, (void) printf(" was %s", 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 +2228,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 +2503,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 +2521,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 +2705,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 +3783,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 +7087,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 +7568,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 +7621,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 +7831,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 +7853,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 +7880,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 +7900,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 +7910,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); diff --git a/config/ax_require_defined.m4 b/config/ax_require_defined.m4 new file mode 100644 index 000000000000..17c3eab7dafd --- /dev/null +++ b/config/ax_require_defined.m4 @@ -0,0 +1,37 @@ +# =========================================================================== +# https://www.gnu.org/software/autoconf-archive/ax_require_defined.html +# =========================================================================== +# +# SYNOPSIS +# +# AX_REQUIRE_DEFINED(MACRO) +# +# DESCRIPTION +# +# AX_REQUIRE_DEFINED is a simple helper for making sure other macros have +# been defined and thus are available for use. This avoids random issues +# where a macro isn't expanded. Instead the configure script emits a +# non-fatal: +# +# ./configure: line 1673: AX_CFLAGS_WARN_ALL: command not found +# +# It's like AC_REQUIRE except it doesn't expand the required macro. +# +# Here's an example: +# +# AX_REQUIRE_DEFINED([AX_CHECK_LINK_FLAG]) +# +# LICENSE +# +# Copyright (c) 2014 Mike Frysinger +# +# Copying and distribution of this file, with or without modification, are +# permitted in any medium without royalty provided the copyright notice +# and this notice are preserved. This file is offered as-is, without any +# warranty. + +#serial 2 + +AC_DEFUN([AX_REQUIRE_DEFINED], [dnl + m4_ifndef([$1], [m4_fatal([macro ]$1[ is not defined; is a m4 file missing?])]) +])dnl AX_REQUIRE_DEFINED diff --git a/config/ax_with_curses.m4 b/config/ax_with_curses.m4 new file mode 100644 index 000000000000..dcdc1297f89d --- /dev/null +++ b/config/ax_with_curses.m4 @@ -0,0 +1,582 @@ +# =========================================================================== +# https://www.gnu.org/software/autoconf-archive/ax_with_curses.html +# =========================================================================== +# +# SYNOPSIS +# +# AX_WITH_CURSES +# +# DESCRIPTION +# +# This macro checks whether a SysV or X/Open-compatible Curses library is +# present, along with the associated header file. The NcursesW +# (wide-character) library is searched for first, followed by Ncurses, +# then the system-default plain Curses. The first library found is the +# one returned. Finding libraries will first be attempted by using +# pkg-config, and should the pkg-config files not be available, will +# fallback to combinations of known flags itself. +# +# The following options are understood: --with-ncursesw, --with-ncurses, +# --without-ncursesw, --without-ncurses. The "--with" options force the +# macro to use that particular library, terminating with an error if not +# found. The "--without" options simply skip the check for that library. +# The effect on the search pattern is: +# +# (no options) - NcursesW, Ncurses, Curses +# --with-ncurses --with-ncursesw - NcursesW only [*] +# --without-ncurses --with-ncursesw - NcursesW only [*] +# --with-ncursesw - NcursesW only [*] +# --with-ncurses --without-ncursesw - Ncurses only [*] +# --with-ncurses - NcursesW, Ncurses [**] +# --without-ncurses --without-ncursesw - Curses only +# --without-ncursesw - Ncurses, Curses +# --without-ncurses - NcursesW, Curses +# +# [*] If the library is not found, abort the configure script. +# +# [**] If the second library (Ncurses) is not found, abort configure. +# +# The following preprocessor symbols may be defined by this macro if the +# appropriate conditions are met: +# +# HAVE_CURSES - if any SysV or X/Open Curses library found +# HAVE_CURSES_ENHANCED - if library supports X/Open Enhanced functions +# HAVE_CURSES_COLOR - if library supports color (enhanced functions) +# HAVE_CURSES_OBSOLETE - if library supports certain obsolete features +# HAVE_NCURSESW - if NcursesW (wide char) library is to be used +# HAVE_NCURSES - if the Ncurses library is to be used +# +# HAVE_CURSES_H - if is present and should be used +# HAVE_NCURSESW_H - if should be used +# HAVE_NCURSES_H - if should be used +# HAVE_NCURSESW_CURSES_H - if should be used +# HAVE_NCURSES_CURSES_H - if should be used +# +# (These preprocessor symbols are discussed later in this document.) +# +# The following output variables are defined by this macro; they are +# precious and may be overridden on the ./configure command line: +# +# CURSES_LIBS - library to add to xxx_LDADD +# CURSES_CFLAGS - include paths to add to xxx_CPPFLAGS +# +# In previous versions of this macro, the flags CURSES_LIB and +# CURSES_CPPFLAGS were defined. These have been renamed, in keeping with +# AX_WITH_CURSES's close bigger brother, PKG_CHECK_MODULES, which should +# eventually supersede the use of AX_WITH_CURSES. Neither the library +# listed in CURSES_LIBS, nor the flags in CURSES_CFLAGS are added to LIBS, +# respectively CPPFLAGS, by default. You need to add both to the +# appropriate xxx_LDADD/xxx_CPPFLAGS line in your Makefile.am. For +# example: +# +# prog_LDADD = @CURSES_LIBS@ +# prog_CPPFLAGS = @CURSES_CFLAGS@ +# +# If CURSES_LIBS is set on the configure command line (such as by running +# "./configure CURSES_LIBS=-lmycurses"), then the only header searched for +# is . If the user needs to specify an alternative path for a +# library (such as for a non-standard NcurseW), the user should use the +# LDFLAGS variable. +# +# The following shell variables may be defined by this macro: +# +# ax_cv_curses - set to "yes" if any Curses library found +# ax_cv_curses_enhanced - set to "yes" if Enhanced functions present +# ax_cv_curses_color - set to "yes" if color functions present +# ax_cv_curses_obsolete - set to "yes" if obsolete features present +# +# ax_cv_ncursesw - set to "yes" if NcursesW library found +# ax_cv_ncurses - set to "yes" if Ncurses library found +# ax_cv_plaincurses - set to "yes" if plain Curses library found +# ax_cv_curses_which - set to "ncursesw", "ncurses", "plaincurses" or "no" +# +# These variables can be used in your configure.ac to determine the level +# of support you need from the Curses library. For example, if you must +# have either Ncurses or NcursesW, you could include: +# +# AX_WITH_CURSES +# if test "x$ax_cv_ncursesw" != xyes && test "x$ax_cv_ncurses" != xyes; then +# AC_MSG_ERROR([requires either NcursesW or Ncurses library]) +# fi +# +# If any Curses library will do (but one must be present and must support +# color), you could use: +# +# AX_WITH_CURSES +# if test "x$ax_cv_curses" != xyes || test "x$ax_cv_curses_color" != xyes; then +# AC_MSG_ERROR([requires an X/Open-compatible Curses library with color]) +# fi +# +# Certain preprocessor symbols and shell variables defined by this macro +# can be used to determine various features of the Curses library. In +# particular, HAVE_CURSES and ax_cv_curses are defined if the Curses +# library found conforms to the traditional SysV and/or X/Open Base Curses +# definition. Any working Curses library conforms to this level. +# +# HAVE_CURSES_ENHANCED and ax_cv_curses_enhanced are defined if the +# library supports the X/Open Enhanced Curses definition. In particular, +# the wide-character types attr_t, cchar_t and wint_t, the functions +# wattr_set() and wget_wch() and the macros WA_NORMAL and _XOPEN_CURSES +# are checked. The Ncurses library does NOT conform to this definition, +# although NcursesW does. +# +# HAVE_CURSES_COLOR and ax_cv_curses_color are defined if the library +# supports color functions and macros such as COLOR_PAIR, A_COLOR, +# COLOR_WHITE, COLOR_RED and init_pair(). These are NOT part of the +# X/Open Base Curses definition, but are part of the Enhanced set of +# functions. The Ncurses library DOES support these functions, as does +# NcursesW. +# +# HAVE_CURSES_OBSOLETE and ax_cv_curses_obsolete are defined if the +# library supports certain features present in SysV and BSD Curses but not +# defined in the X/Open definition. In particular, the functions +# getattrs(), getcurx() and getmaxx() are checked. +# +# To use the HAVE_xxx_H preprocessor symbols, insert the following into +# your system.h (or equivalent) header file: +# +# #if defined HAVE_NCURSESW_CURSES_H +# # include +# #elif defined HAVE_NCURSESW_H +# # include +# #elif defined HAVE_NCURSES_CURSES_H +# # include +# #elif defined HAVE_NCURSES_H +# # include +# #elif defined HAVE_CURSES_H +# # include +# #else +# # error "SysV or X/Open-compatible Curses header file required" +# #endif +# +# For previous users of this macro: you should not need to change anything +# in your configure.ac or Makefile.am, as the previous (serial 10) +# semantics are still valid. However, you should update your system.h (or +# equivalent) header file to the fragment shown above. You are encouraged +# also to make use of the extended functionality provided by this version +# of AX_WITH_CURSES, as well as in the additional macros +# AX_WITH_CURSES_PANEL, AX_WITH_CURSES_MENU and AX_WITH_CURSES_FORM. +# +# LICENSE +# +# Copyright (c) 2009 Mark Pulford +# Copyright (c) 2009 Damian Pietras +# Copyright (c) 2012 Reuben Thomas +# Copyright (c) 2011 John Zaitseff +# +# This program is free software: you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by the +# Free Software Foundation, either version 3 of the License, or (at your +# option) any later version. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General +# Public License for more details. +# +# You should have received a copy of the GNU General Public License along +# with this program. If not, see . +# +# As a special exception, the respective Autoconf Macro's copyright owner +# gives unlimited permission to copy, distribute and modify the configure +# scripts that are the output of Autoconf when processing the Macro. You +# need not follow the terms of the GNU General Public License when using +# or distributing such scripts, even though portions of the text of the +# Macro appear in them. The GNU General Public License (GPL) does govern +# all other use of the material that constitutes the Autoconf Macro. +# +# This special exception to the GPL applies to versions of the Autoconf +# Macro released by the Autoconf Archive. When you make and distribute a +# modified version of the Autoconf Macro, you may extend this special +# exception to the GPL to apply to your modified version as well. + +#serial 18 + +# internal function to factorize common code that is used by both ncurses +# and ncursesw +AC_DEFUN([_FIND_CURSES_FLAGS], [ + AC_MSG_CHECKING([for $1 via pkg-config]) + + AX_REQUIRE_DEFINED([PKG_CHECK_EXISTS]) + _PKG_CONFIG([_ax_cv_$1_libs], [libs], [$1]) + _PKG_CONFIG([_ax_cv_$1_cppflags], [cflags], [$1]) + + AS_IF([test "x$pkg_failed" = "xyes" || test "x$pkg_failed" = "xuntried"],[ + AC_MSG_RESULT([no]) + # No suitable .pc file found, have to find flags via fallback + AC_CACHE_CHECK([for $1 via fallback], [ax_cv_$1], [ + AS_ECHO() + pkg_cv__ax_cv_$1_libs="-l$1" + pkg_cv__ax_cv_$1_cppflags="-D_GNU_SOURCE $CURSES_CFLAGS" + LIBS="$ax_saved_LIBS $pkg_cv__ax_cv_$1_libs" + CPPFLAGS="$ax_saved_CPPFLAGS $pkg_cv__ax_cv_$1_cppflags" + + AC_MSG_CHECKING([for initscr() with $pkg_cv__ax_cv_$1_libs]) + AC_LINK_IFELSE([AC_LANG_CALL([], [initscr])], + [ + AC_MSG_RESULT([yes]) + AC_MSG_CHECKING([for nodelay() with $pkg_cv__ax_cv_$1_libs]) + AC_LINK_IFELSE([AC_LANG_CALL([], [nodelay])],[ + ax_cv_$1=yes + ],[ + AC_MSG_RESULT([no]) + m4_if( + [$1],[ncursesw],[pkg_cv__ax_cv_$1_libs="$pkg_cv__ax_cv_$1_libs -ltinfow"], + [$1],[ncurses],[pkg_cv__ax_cv_$1_libs="$pkg_cv__ax_cv_$1_libs -ltinfo"] + ) + LIBS="$ax_saved_LIBS $pkg_cv__ax_cv_$1_libs" + + AC_MSG_CHECKING([for nodelay() with $pkg_cv__ax_cv_$1_libs]) + AC_LINK_IFELSE([AC_LANG_CALL([], [nodelay])],[ + ax_cv_$1=yes + ],[ + ax_cv_$1=no + ]) + ]) + ],[ + ax_cv_$1=no + ]) + ]) + ],[ + AC_MSG_RESULT([yes]) + # Found .pc file, using its information + LIBS="$ax_saved_LIBS $pkg_cv__ax_cv_$1_libs" + CPPFLAGS="$ax_saved_CPPFLAGS $pkg_cv__ax_cv_$1_cppflags" + ax_cv_$1=yes + ]) +]) + +AU_ALIAS([MP_WITH_CURSES], [AX_WITH_CURSES]) +AC_DEFUN([AX_WITH_CURSES], [ + AC_ARG_VAR([CURSES_LIBS], [linker library for Curses, e.g. -lcurses]) + AC_ARG_VAR([CURSES_CFLAGS], [preprocessor flags for Curses, e.g. -I/usr/include/ncursesw]) + AC_ARG_WITH([ncurses], [AS_HELP_STRING([--with-ncurses], + [force the use of Ncurses or NcursesW])], + [], [with_ncurses=check]) + AC_ARG_WITH([ncursesw], [AS_HELP_STRING([--without-ncursesw], + [do not use NcursesW (wide character support)])], + [], [with_ncursesw=check]) + + ax_saved_LIBS=$LIBS + ax_saved_CPPFLAGS=$CPPFLAGS + + AS_IF([test "x$with_ncurses" = xyes || test "x$with_ncursesw" = xyes], + [ax_with_plaincurses=no], [ax_with_plaincurses=check]) + + ax_cv_curses_which=no + + # Test for NcursesW + AS_IF([test "x$CURSES_LIBS" = x && test "x$with_ncursesw" != xno], [ + _FIND_CURSES_FLAGS([ncursesw]) + + AS_IF([test "x$ax_cv_ncursesw" = xno && test "x$with_ncursesw" = xyes], [ + AC_MSG_ERROR([--with-ncursesw specified but could not find NcursesW library]) + ]) + + AS_IF([test "x$ax_cv_ncursesw" = xyes], [ + ax_cv_curses=yes + ax_cv_curses_which=ncursesw + CURSES_LIBS="$pkg_cv__ax_cv_ncursesw_libs" + CURSES_CFLAGS="$pkg_cv__ax_cv_ncursesw_cppflags" + AC_DEFINE([HAVE_NCURSESW], [1], [Define to 1 if the NcursesW library is present]) + AC_DEFINE([HAVE_CURSES], [1], [Define to 1 if a SysV or X/Open compatible Curses library is present]) + + AC_CACHE_CHECK([for working ncursesw/curses.h], [ax_cv_header_ncursesw_curses_h], [ + AC_LINK_IFELSE([AC_LANG_PROGRAM([[ + @%:@define _XOPEN_SOURCE_EXTENDED 1 + @%:@include + ]], [[ + chtype a = A_BOLD; + int b = KEY_LEFT; + chtype c = COLOR_PAIR(1) & A_COLOR; + attr_t d = WA_NORMAL; + cchar_t e; + wint_t f; + int g = getattrs(stdscr); + int h = getcurx(stdscr) + getmaxx(stdscr); + initscr(); + init_pair(1, COLOR_WHITE, COLOR_RED); + wattr_set(stdscr, d, 0, NULL); + wget_wch(stdscr, &f); + ]])], + [ax_cv_header_ncursesw_curses_h=yes], + [ax_cv_header_ncursesw_curses_h=no]) + ]) + AS_IF([test "x$ax_cv_header_ncursesw_curses_h" = xyes], [ + ax_cv_curses_enhanced=yes + ax_cv_curses_color=yes + ax_cv_curses_obsolete=yes + AC_DEFINE([HAVE_CURSES_ENHANCED], [1], [Define to 1 if library supports X/Open Enhanced functions]) + AC_DEFINE([HAVE_CURSES_COLOR], [1], [Define to 1 if library supports color (enhanced functions)]) + AC_DEFINE([HAVE_CURSES_OBSOLETE], [1], [Define to 1 if library supports certain obsolete features]) + AC_DEFINE([HAVE_NCURSESW_CURSES_H], [1], [Define to 1 if is present]) + ]) + + AC_CACHE_CHECK([for working ncursesw.h], [ax_cv_header_ncursesw_h], [ + AC_LINK_IFELSE([AC_LANG_PROGRAM([[ + @%:@define _XOPEN_SOURCE_EXTENDED 1 + @%:@include + ]], [[ + chtype a = A_BOLD; + int b = KEY_LEFT; + chtype c = COLOR_PAIR(1) & A_COLOR; + attr_t d = WA_NORMAL; + cchar_t e; + wint_t f; + int g = getattrs(stdscr); + int h = getcurx(stdscr) + getmaxx(stdscr); + initscr(); + init_pair(1, COLOR_WHITE, COLOR_RED); + wattr_set(stdscr, d, 0, NULL); + wget_wch(stdscr, &f); + ]])], + [ax_cv_header_ncursesw_h=yes], + [ax_cv_header_ncursesw_h=no]) + ]) + AS_IF([test "x$ax_cv_header_ncursesw_h" = xyes], [ + ax_cv_curses_enhanced=yes + ax_cv_curses_color=yes + ax_cv_curses_obsolete=yes + AC_DEFINE([HAVE_CURSES_ENHANCED], [1], [Define to 1 if library supports X/Open Enhanced functions]) + AC_DEFINE([HAVE_CURSES_COLOR], [1], [Define to 1 if library supports color (enhanced functions)]) + AC_DEFINE([HAVE_CURSES_OBSOLETE], [1], [Define to 1 if library supports certain obsolete features]) + AC_DEFINE([HAVE_NCURSESW_H], [1], [Define to 1 if is present]) + ]) + + AC_CACHE_CHECK([for working ncurses.h], [ax_cv_header_ncurses_h_with_ncursesw], [ + AC_LINK_IFELSE([AC_LANG_PROGRAM([[ + @%:@define _XOPEN_SOURCE_EXTENDED 1 + @%:@include + ]], [[ + chtype a = A_BOLD; + int b = KEY_LEFT; + chtype c = COLOR_PAIR(1) & A_COLOR; + attr_t d = WA_NORMAL; + cchar_t e; + wint_t f; + int g = getattrs(stdscr); + int h = getcurx(stdscr) + getmaxx(stdscr); + initscr(); + init_pair(1, COLOR_WHITE, COLOR_RED); + wattr_set(stdscr, d, 0, NULL); + wget_wch(stdscr, &f); + ]])], + [ax_cv_header_ncurses_h_with_ncursesw=yes], + [ax_cv_header_ncurses_h_with_ncursesw=no]) + ]) + AS_IF([test "x$ax_cv_header_ncurses_h_with_ncursesw" = xyes], [ + ax_cv_curses_enhanced=yes + ax_cv_curses_color=yes + ax_cv_curses_obsolete=yes + AC_DEFINE([HAVE_CURSES_ENHANCED], [1], [Define to 1 if library supports X/Open Enhanced functions]) + AC_DEFINE([HAVE_CURSES_COLOR], [1], [Define to 1 if library supports color (enhanced functions)]) + AC_DEFINE([HAVE_CURSES_OBSOLETE], [1], [Define to 1 if library supports certain obsolete features]) + AC_DEFINE([HAVE_NCURSES_H], [1], [Define to 1 if is present]) + ]) + + AS_IF([test "x$ax_cv_header_ncursesw_curses_h" = xno && test "x$ax_cv_header_ncursesw_h" = xno && test "x$ax_cv_header_ncurses_h_with_ncursesw" = xno], [ + AC_MSG_WARN([could not find a working ncursesw/curses.h, ncursesw.h or ncurses.h]) + ]) + ]) + ]) + unset pkg_cv__ax_cv_ncursesw_libs + unset pkg_cv__ax_cv_ncursesw_cppflags + + # Test for Ncurses + AS_IF([test "x$CURSES_LIBS" = x && test "x$with_ncurses" != xno && test "x$ax_cv_curses_which" = xno], [ + _FIND_CURSES_FLAGS([ncurses]) + + AS_IF([test "x$ax_cv_ncurses" = xno && test "x$with_ncurses" = xyes], [ + AC_MSG_ERROR([--with-ncurses specified but could not find Ncurses library]) + ]) + + AS_IF([test "x$ax_cv_ncurses" = xyes], [ + ax_cv_curses=yes + ax_cv_curses_which=ncurses + CURSES_LIBS="$pkg_cv__ax_cv_ncurses_libs" + CURSES_CFLAGS="$pkg_cv__ax_cv_ncurses_cppflags" + AC_DEFINE([HAVE_NCURSES], [1], [Define to 1 if the Ncurses library is present]) + AC_DEFINE([HAVE_CURSES], [1], [Define to 1 if a SysV or X/Open compatible Curses library is present]) + + AC_CACHE_CHECK([for working ncurses/curses.h], [ax_cv_header_ncurses_curses_h], [ + AC_LINK_IFELSE([AC_LANG_PROGRAM([[ + @%:@include + ]], [[ + chtype a = A_BOLD; + int b = KEY_LEFT; + chtype c = COLOR_PAIR(1) & A_COLOR; + int g = getattrs(stdscr); + int h = getcurx(stdscr) + getmaxx(stdscr); + initscr(); + init_pair(1, COLOR_WHITE, COLOR_RED); + ]])], + [ax_cv_header_ncurses_curses_h=yes], + [ax_cv_header_ncurses_curses_h=no]) + ]) + AS_IF([test "x$ax_cv_header_ncurses_curses_h" = xyes], [ + ax_cv_curses_color=yes + ax_cv_curses_obsolete=yes + AC_DEFINE([HAVE_CURSES_COLOR], [1], [Define to 1 if library supports color (enhanced functions)]) + AC_DEFINE([HAVE_CURSES_OBSOLETE], [1], [Define to 1 if library supports certain obsolete features]) + AC_DEFINE([HAVE_NCURSES_CURSES_H], [1], [Define to 1 if is present]) + ]) + + AC_CACHE_CHECK([for working ncurses.h], [ax_cv_header_ncurses_h], [ + AC_LINK_IFELSE([AC_LANG_PROGRAM([[ + @%:@include + ]], [[ + chtype a = A_BOLD; + int b = KEY_LEFT; + chtype c = COLOR_PAIR(1) & A_COLOR; + int g = getattrs(stdscr); + int h = getcurx(stdscr) + getmaxx(stdscr); + initscr(); + init_pair(1, COLOR_WHITE, COLOR_RED); + ]])], + [ax_cv_header_ncurses_h=yes], + [ax_cv_header_ncurses_h=no]) + ]) + AS_IF([test "x$ax_cv_header_ncurses_h" = xyes], [ + ax_cv_curses_color=yes + ax_cv_curses_obsolete=yes + AC_DEFINE([HAVE_CURSES_COLOR], [1], [Define to 1 if library supports color (enhanced functions)]) + AC_DEFINE([HAVE_CURSES_OBSOLETE], [1], [Define to 1 if library supports certain obsolete features]) + AC_DEFINE([HAVE_NCURSES_H], [1], [Define to 1 if is present]) + ]) + + AS_IF([test "x$ax_cv_header_ncurses_curses_h" = xno && test "x$ax_cv_header_ncurses_h" = xno], [ + AC_MSG_WARN([could not find a working ncurses/curses.h or ncurses.h]) + ]) + ]) + ]) + unset pkg_cv__ax_cv_ncurses_libs + unset pkg_cv__ax_cv_ncurses_cppflags + + # Test for plain Curses (or if CURSES_LIBS was set by user) + AS_IF([test "x$with_plaincurses" != xno && test "x$ax_cv_curses_which" = xno], [ + AS_IF([test "x$CURSES_LIBS" != x], [ + LIBS="$ax_saved_LIBS $CURSES_LIBS" + ], [ + LIBS="$ax_saved_LIBS -lcurses" + ]) + + AC_CACHE_CHECK([for Curses library], [ax_cv_plaincurses], [ + AC_LINK_IFELSE([AC_LANG_CALL([], [initscr])], + [ax_cv_plaincurses=yes], [ax_cv_plaincurses=no]) + ]) + + AS_IF([test "x$ax_cv_plaincurses" = xyes], [ + ax_cv_curses=yes + ax_cv_curses_which=plaincurses + AS_IF([test "x$CURSES_LIBS" = x], [ + CURSES_LIBS="-lcurses" + ]) + AC_DEFINE([HAVE_CURSES], [1], [Define to 1 if a SysV or X/Open compatible Curses library is present]) + + # Check for base conformance (and header file) + + AC_CACHE_CHECK([for working curses.h], [ax_cv_header_curses_h], [ + AC_LINK_IFELSE([AC_LANG_PROGRAM([[ + @%:@include + ]], [[ + chtype a = A_BOLD; + int b = KEY_LEFT; + initscr(); + ]])], + [ax_cv_header_curses_h=yes], + [ax_cv_header_curses_h=no]) + ]) + AS_IF([test "x$ax_cv_header_curses_h" = xyes], [ + AC_DEFINE([HAVE_CURSES_H], [1], [Define to 1 if is present]) + + # Check for X/Open Enhanced conformance + + AC_CACHE_CHECK([for X/Open Enhanced Curses conformance], [ax_cv_plaincurses_enhanced], [ + AC_LINK_IFELSE([AC_LANG_PROGRAM([[ + @%:@define _XOPEN_SOURCE_EXTENDED 1 + @%:@include + @%:@ifndef _XOPEN_CURSES + @%:@error "this Curses library is not enhanced" + "this Curses library is not enhanced" + @%:@endif + ]], [[ + chtype a = A_BOLD; + int b = KEY_LEFT; + chtype c = COLOR_PAIR(1) & A_COLOR; + attr_t d = WA_NORMAL; + cchar_t e; + wint_t f; + initscr(); + init_pair(1, COLOR_WHITE, COLOR_RED); + wattr_set(stdscr, d, 0, NULL); + wget_wch(stdscr, &f); + ]])], + [ax_cv_plaincurses_enhanced=yes], + [ax_cv_plaincurses_enhanced=no]) + ]) + AS_IF([test "x$ax_cv_plaincurses_enhanced" = xyes], [ + ax_cv_curses_enhanced=yes + ax_cv_curses_color=yes + AC_DEFINE([HAVE_CURSES_ENHANCED], [1], [Define to 1 if library supports X/Open Enhanced functions]) + AC_DEFINE([HAVE_CURSES_COLOR], [1], [Define to 1 if library supports color (enhanced functions)]) + ]) + + # Check for color functions + + AC_CACHE_CHECK([for Curses color functions], [ax_cv_plaincurses_color], [ + AC_LINK_IFELSE([AC_LANG_PROGRAM([[ + @%:@define _XOPEN_SOURCE_EXTENDED 1 + @%:@include + ]], [[ + chtype a = A_BOLD; + int b = KEY_LEFT; + chtype c = COLOR_PAIR(1) & A_COLOR; + initscr(); + init_pair(1, COLOR_WHITE, COLOR_RED); + ]])], + [ax_cv_plaincurses_color=yes], + [ax_cv_plaincurses_color=no]) + ]) + AS_IF([test "x$ax_cv_plaincurses_color" = xyes], [ + ax_cv_curses_color=yes + AC_DEFINE([HAVE_CURSES_COLOR], [1], [Define to 1 if library supports color (enhanced functions)]) + ]) + + # Check for obsolete functions + + AC_CACHE_CHECK([for obsolete Curses functions], [ax_cv_plaincurses_obsolete], [ + AC_LINK_IFELSE([AC_LANG_PROGRAM([[ + @%:@include + ]], [[ + chtype a = A_BOLD; + int b = KEY_LEFT; + int g = getattrs(stdscr); + int h = getcurx(stdscr) + getmaxx(stdscr); + initscr(); + ]])], + [ax_cv_plaincurses_obsolete=yes], + [ax_cv_plaincurses_obsolete=no]) + ]) + AS_IF([test "x$ax_cv_plaincurses_obsolete" = xyes], [ + ax_cv_curses_obsolete=yes + AC_DEFINE([HAVE_CURSES_OBSOLETE], [1], [Define to 1 if library supports certain obsolete features]) + ]) + ]) + + AS_IF([test "x$ax_cv_header_curses_h" = xno], [ + AC_MSG_WARN([could not find a working curses.h]) + ]) + ]) + ]) + + AS_IF([test "x$ax_cv_curses" != xyes], [ax_cv_curses=no]) + AS_IF([test "x$ax_cv_curses_enhanced" != xyes], [ax_cv_curses_enhanced=no]) + AS_IF([test "x$ax_cv_curses_color" != xyes], [ax_cv_curses_color=no]) + AS_IF([test "x$ax_cv_curses_obsolete" != xyes], [ax_cv_curses_obsolete=no]) + + LIBS=$ax_saved_LIBS + CPPFLAGS=$ax_saved_CPPFLAGS + + unset ax_saved_LIBS + unset ax_saved_CPPFLAGS +])dnl diff --git a/config/user.m4 b/config/user.m4 index 3d97e9a418c3..1b4d68962b0f 100644 --- a/config/user.m4 +++ b/config/user.m4 @@ -2,6 +2,7 @@ dnl # dnl # Default ZFS user configuration dnl # AC_DEFUN([ZFS_AC_CONFIG_USER], [ + AX_WITH_CURSES ZFS_AC_CONFIG_USER_GETTEXT ZFS_AC_CONFIG_USER_MOUNT_HELPER ZFS_AC_CONFIG_USER_UDEV diff --git a/include/libzutil.h b/include/libzutil.h index 4a8df01324d9..ea2bce4e4b10 100644 --- a/include/libzutil.h +++ b/include/libzutil.h @@ -142,6 +142,18 @@ extern void zpool_dump_ddt(const ddt_stat_t *, const ddt_histogram_t *); extern int zpool_history_unpack(char *, uint64_t, uint64_t *, nvlist_t ***, uint_t *); +/* + * List of colors to use ("FG" for forground color, "BG" for background) + */ +#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 diff --git a/lib/libzfs/Makefile.am b/lib/libzfs/Makefile.am index da42307bb6fb..646271b5f578 100644 --- a/lib/libzfs/Makefile.am +++ b/lib/libzfs/Makefile.am @@ -71,7 +71,7 @@ endif libzfs_la_LDFLAGS = -version-info 2:0:0 -libzfs_la_LIBADD += -lm $(LIBSSL) +libzfs_la_LIBADD += -lm $(LIBSSL) $(CURSES_LIBS) EXTRA_DIST = $(libzfs_pc_DATA) $(USER_C) diff --git a/lib/libzfs/libzfs_util.c b/lib/libzfs/libzfs_util.c index ae66db39a4b6..6037f947d919 100644 --- a/lib/libzfs/libzfs_util.c +++ b/lib/libzfs/libzfs_util.c @@ -46,6 +46,14 @@ #include #include +#ifdef HAVE_CURSES_COLOR +#include +#include +#ifdef lines +#undef lines +#endif +#endif + #include #include @@ -1886,3 +1894,101 @@ zfs_version_print(void) return (0); } + +/* + * Return 1 if the user request ANSI color output, and our terminal supports + * it. Return 0 for no color. + */ +static int +use_color(void) +{ +#ifdef HAVE_CURSES_COLOR + static int use_color = -1; + + /* + * 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); + } + + /* + * 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. + */ + if (libzfs_envvar_is_set("ZFS_COLOR") && + !libzfs_envvar_is_set("NO_COLOR")) { + /* + * User has requested color, check if our terminal supports it. + * If it's not supported, then silently don't use it. + */ + if (setupterm(NULL, fileno(stdout), (int *)0) == 0 && + tigetnum("colors") >= 16) { + /* Color supported */ + use_color = 1; + } else { + use_color = 0; + } + return (use_color); + } +#endif + return (0); +} + +/* + * 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(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, ...) +{ + char buf[4090]; + va_list aptr; + int rc; + + if (color) + color_start(color); + + va_start(aptr, format); + vsprintf(buf, format, aptr); + va_end(aptr); + rc = printf(buf); + + if (color) + color_end(); + + return (rc); +} diff --git a/man/man8/zpool.8 b/man/man8/zpool.8 index 467d2411d400..f2ed8b58e0f6 100644 --- a/man/man8/zpool.8 +++ b/man/man8/zpool.8 @@ -2861,6 +2861,12 @@ or by setting .Sy ZFS_VDEV_DEVID_OPT_OUT . .El +.Bl -tag -width "ZFS_COLOR" +.It Ev ZFS_COLOR +Use ANSI color in +.Nm zpool status +output. +.El .Bl -tag -width "ZPOOL_SCRIPTS_AS_ROOT" .It Ev ZPOOL_SCRIPTS_AS_ROOT Allow a privileged user to run the diff --git a/tests/zfs-tests/tests/functional/cli_root/zpool/Makefile.am b/tests/zfs-tests/tests/functional/cli_root/zpool/Makefile.am index 2d0046c53a17..327f23621158 100644 --- a/tests/zfs-tests/tests/functional/cli_root/zpool/Makefile.am +++ b/tests/zfs-tests/tests/functional/cli_root/zpool/Makefile.am @@ -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 diff --git a/tests/zfs-tests/tests/functional/cli_root/zpool/setup.ksh b/tests/zfs-tests/tests/functional/cli_root/zpool/setup.ksh index 6a9af3bc28c3..4e3b6b0e9f47 100755 --- a/tests/zfs-tests/tests/functional/cli_root/zpool/setup.ksh +++ b/tests/zfs-tests/tests/functional/cli_root/zpool/setup.ksh @@ -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 index 000000000000..47dd85862ca7 --- /dev/null +++ b/tests/zfs-tests/tests/functional/cli_root/zpool/zpool_colors.ksh @@ -0,0 +1,78 @@ +#!/bin/ksh -p +# +# CDDL HEADER START +# +# The contents of this file are subject to the terms of the +# Common Development and Distribution License (the "License"). +# You may not use this file except in compliance with the License. +# +# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE +# or http://www.opensolaris.org/os/licensing. +# See the License for the specific language governing permissions +# and limitations under the License. +# +# When distributing Covered Code, include this CDDL HEADER in each +# file and include the License file at usr/src/OPENSOLARIS.LICENSE. +# If applicable, add the following below this CDDL HEADER, with the +# fields enclosed by brackets "[]" replaced with your own identifying +# information: Portions Copyright [yyyy] [name of copyright owner] +# +# CDDL HEADER END +# + +# +# Copyright 2007 Sun Microsystems, Inc. All rights reserved. +# Use is subject to license terms. +# + +# +# Copyright (c) 2012, 2016 by Delphix. All rights reserved. +# + +. $STF_SUITE/include/libtest.shlib + +# +# DESCRIPTION: +# Verify debugging features of zpool such as ABORT and freeze/unfreeze +# should run successfully. +# +# STRATEGY: +# 1. Create an array containing each zpool options. +# 2. For each element, execute the zpool command. +# 3. Verify it run successfully. +# + +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 export $TESTPOOL +log_must zpool import $TESTPOOL + +log_note "$(ZFS_COLOR=1 zpool status)" +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 $TESTPOOL +log_must wait_scrubbed $TESTPOOL +log_must zinject -c all + +out="$(ZFS_COLOR=1 zpool status)" + + +log_pass "zpool status displayed colors"