From bf4a8911c992291e6943d5e936732c290ea2abb9 Mon Sep 17 00:00:00 2001 From: "Richard W.M. Jones" Date: Tue, 2 Aug 2022 10:38:56 +0100 Subject: [PATCH 2/3] info: Add limited colourized output For terminal, non-JSON output, colourize some of the output. We can work on making this better later. Use --no-colour (or output to a non-terminal) to disable colours. --- info/main.c | 25 ++++++++++++- info/map.c | 95 ++++++++++++++++++++++++++++++++++++++---------- info/nbdinfo.pod | 11 ++++++ info/show.c | 38 ++++++++++++++----- 4 files changed, 138 insertions(+), 31 deletions(-) diff --git a/info/main.c b/info/main.c index 870abb4623..a4550e2294 100644 --- a/info/main.c +++ b/info/main.c @@ -1,5 +1,5 @@ /* NBD client library in userspace - * Copyright (C) 2020-2021 Red Hat Inc. + * Copyright (C) 2020-2022 Red Hat Inc. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public @@ -30,6 +30,7 @@ #include +#include "ansi-colours.h" #include "version.h" #include "nbdinfo.h" @@ -37,6 +38,7 @@ const char *progname; struct nbd_handle *nbd; FILE *fp; /* output file descriptor */ +bool colour; /* --colour / --no-colour option */ bool list_all = false; /* --list option */ bool probe_content = false; /* --content / --no-content option */ bool json_output = false; /* --json option */ @@ -93,6 +95,8 @@ main (int argc, char *argv[]) HELP_OPTION = CHAR_MAX + 1, LONG_OPTIONS, SHORT_OPTIONS, + COLOUR_OPTION, + NO_COLOUR_OPTION, CONTENT_OPTION, NO_CONTENT_OPTION, JSON_OPTION, @@ -105,6 +109,14 @@ main (int argc, char *argv[]) const struct option long_options[] = { { "help", no_argument, NULL, HELP_OPTION }, { "can", required_argument, NULL, CAN_OPTION }, + { "color", no_argument, NULL, COLOUR_OPTION }, + { "colors", no_argument, NULL, COLOUR_OPTION }, + { "colour", no_argument, NULL, COLOUR_OPTION }, + { "colours", no_argument, NULL, COLOUR_OPTION }, + { "no-color", no_argument, NULL, NO_COLOUR_OPTION }, + { "no-colors", no_argument, NULL, NO_COLOUR_OPTION }, + { "no-colour", no_argument, NULL, NO_COLOUR_OPTION }, + { "no-colours", no_argument, NULL, NO_COLOUR_OPTION }, { "content", no_argument, NULL, CONTENT_OPTION }, { "no-content", no_argument, NULL, NO_CONTENT_OPTION }, { "is", required_argument, NULL, CAN_OPTION }, @@ -127,6 +139,7 @@ main (int argc, char *argv[]) bool list_okay = true; progname = argv[0]; + colour = isatty (STDOUT_FILENO); for (;;) { c = getopt_long (argc, argv, short_options, long_options, NULL); @@ -156,6 +169,14 @@ main (int argc, char *argv[]) json_output = true; break; + case COLOUR_OPTION: + colour = true; + break; + + case NO_COLOUR_OPTION: + colour = false; + break; + case CONTENT_OPTION: content_flag = true; break; @@ -288,10 +309,12 @@ main (int argc, char *argv[]) if (!json_output) { if (protocol) { + ansi_colour (ANSI_FG_MAGENTA, fp); fprintf (fp, "protocol: %s", protocol); if (tls_negotiated >= 0) fprintf (fp, " %s TLS", tls_negotiated ? "with" : "without"); fprintf (fp, "\n"); + ansi_restore (fp); } } else { diff --git a/info/map.c b/info/map.c index 39c5933bf5..a5aad95522 100644 --- a/info/map.c +++ b/info/map.c @@ -30,6 +30,7 @@ #include +#include "ansi-colours.h" #include "minmax.h" #include "vector.h" @@ -99,7 +100,9 @@ do_map (void) /* Callback handling --map. */ static void print_one_extent (uint64_t offset, uint64_t len, uint32_t type); -static char *extent_description (const char *metacontext, uint32_t type); +static void extent_description (const char *metacontext, uint32_t type, + char **descr, bool *free_descr, + const char **fg, const char **bg); static int extent_callback (void *user_data, const char *metacontext, @@ -169,15 +172,25 @@ static void print_one_extent (uint64_t offset, uint64_t len, uint32_t type) { static bool comma = false; - char *descr = extent_description (map, type); + char *descr; + bool free_descr; + const char *fg, *bg; + + extent_description (map, type, &descr, &free_descr, &fg, &bg); if (!json_output) { + if (fg) + ansi_colour (fg, fp); + if (bg) + ansi_colour (bg, fp); fprintf (fp, "%10" PRIu64 " " "%10" PRIu64 " " "%3" PRIu32, offset, len, type); if (descr) fprintf (fp, " %s", descr); + if (fg || bg) + ansi_restore (fp); fprintf (fp, "\n"); } else { @@ -196,7 +209,8 @@ print_one_extent (uint64_t offset, uint64_t len, uint32_t type) comma = true; } - free (descr); + if (free_descr) + free (descr); } /* --map --totals suboption */ @@ -237,14 +251,24 @@ print_totals (uint32_vector *entries, int64_t size) } if (c > 0) { - char *descr = extent_description (map, type); + char *descr; + bool free_descr; + const char *fg, *bg; double percent = 100.0 * c / size; + extent_description (map, type, &descr, &free_descr, &fg, &bg); + if (!json_output) { + if (fg) + ansi_colour (fg, fp); + if (bg) + ansi_colour (bg, fp); fprintf (fp, "%10" PRIu64 " %5.1f%% %3" PRIu32, c, percent, type); if (descr) fprintf (fp, " %s", descr); + if (fg || bg) + ansi_restore (fp); fprintf (fp, "\n"); } else { @@ -264,7 +288,8 @@ print_totals (uint32_vector *entries, int64_t size) comma = true; } - free (descr); + if (free_descr) + free (descr); } if (next_type == (uint64_t)UINT32_MAX + 1) @@ -275,37 +300,67 @@ print_totals (uint32_vector *entries, int64_t size) if (json_output) fprintf (fp, "\n]\n"); } -static char * -extent_description (const char *metacontext, uint32_t type) +static void +extent_description (const char *metacontext, uint32_t type, + char **descr, bool *free_descr, + const char **fg, const char **bg) { - char *ret; - if (strcmp (metacontext, "base:allocation") == 0) { switch (type) { - case 0: return strdup ("data"); - case 1: return strdup ("hole"); - case 2: return strdup ("zero"); - case 3: return strdup ("hole,zero"); + case 0: + *descr = "data"; *free_descr = false; + *fg = ANSI_FG_BOLD_BLACK; *bg = NULL; + return; + case 1: + *descr = "hole"; *free_descr = false; + *fg = *bg = NULL; + return; + case 2: + *descr = "zero"; *free_descr = false; + *fg = *bg = NULL; + return; + case 3: + *descr = "hole,zero"; *free_descr = false; + *fg = *bg = NULL; + return; } } else if (strncmp (metacontext, "qemu:dirty-bitmap:", 18) == 0) { switch (type) { - case 0: return strdup ("clean"); - case 1: return strdup ("dirty"); + case 0: + *descr = "clean"; *free_descr = false; + *fg = ANSI_FG_GREEN; *bg = NULL; + return; + case 1: + *descr = "dirty"; *free_descr = false; + *fg = ANSI_FG_RED; *bg = NULL; + return; } } else if (strcmp (metacontext, "qemu:allocation-depth") == 0) { switch (type) { - case 0: return strdup ("absent"); - case 1: return strdup ("local"); + case 0: + *descr = "absent"; *free_descr = false; + *fg = *bg = NULL; + return; + case 1: + *descr = "local"; *free_descr = false; + *fg = ANSI_FG_BRIGHT_WHITE; *bg = ANSI_BG_BLACK; + return; default: - if (asprintf (&ret, "backing depth %u", type) == -1) { + if (asprintf (descr, "backing depth %u", type) == -1) { perror ("asprintf"); exit (EXIT_FAILURE); } - return ret; + *free_descr = true; + *fg = NULL; *bg = ANSI_BG_LIGHT_GREY; + return; } } - return NULL; /* Don't know - description field will be omitted. */ + /* Don't know - description field will be omitted. */ + *descr = NULL; + *free_descr = false; + *fg = NULL; + *bg = NULL; } diff --git a/info/nbdinfo.pod b/info/nbdinfo.pod index 4733ecd1f7..7dfb9edb60 100644 --- a/info/nbdinfo.pod +++ b/info/nbdinfo.pod @@ -330,6 +330,17 @@ L, L, L, L, L, L, L, L. +=item B<--color> + +=item B<--colour> + +=item B<--no-color> + +=item B<--no-colour> + +Enable or disable ANSI colours in output. By default we use colours +if the output seems to be a terminal, and disable them if not. + =item B<--content> =item B<--no-content> diff --git a/info/show.c b/info/show.c index 3a436665bf..140220d8c6 100644 --- a/info/show.c +++ b/info/show.c @@ -28,11 +28,13 @@ #include +#include "ansi-colours.h" #include "human-size.h" #include "string-vector.h" #include "nbdinfo.h" +static void show_boolean (const char *name, bool cond); static int collect_context (void *opaque, const char *name); static char *get_content (struct nbd_handle *, int64_t size); @@ -119,6 +121,7 @@ show_one_export (struct nbd_handle *nbd, const char *desc, content = get_content (nbd, size); if (!json_output) { + ansi_colour (ANSI_FG_BRIGHT_BLUE, fp); fprintf (fp, "export="); /* Might as well use the JSON function to get an escaped string here ... */ print_json_string (export_name); @@ -133,35 +136,38 @@ show_one_export (struct nbd_handle *nbd, const char *desc, fprintf (fp, "\tcontent: %s\n", content); if (uri) fprintf (fp, "\turi: %s\n", uri); + ansi_restore (fp); + ansi_colour (ANSI_FG_BLUE, fp); if (show_context) { fprintf (fp, "\tcontexts:\n"); for (i = 0; i < contexts.len; ++i) fprintf (fp, "\t\t%s\n", contexts.ptr[i]); } + ansi_restore (fp); + ansi_colour (ANSI_FG_MAGENTA, fp); if (is_rotational >= 0) fprintf (fp, "\t%s: %s\n", "is_rotational", is_rotational ? "true" : "false"); if (is_read_only >= 0) fprintf (fp, "\t%s: %s\n", "is_read_only", is_read_only ? "true" : "false"); + ansi_restore (fp); if (can_cache >= 0) - fprintf (fp, "\t%s: %s\n", "can_cache", can_cache ? "true" : "false"); + show_boolean ("can_cache", can_cache); if (can_df >= 0) - fprintf (fp, "\t%s: %s\n", "can_df", can_df ? "true" : "false"); + show_boolean ("can_df", can_df); if (can_fast_zero >= 0) - fprintf (fp, "\t%s: %s\n", "can_fast_zero", - can_fast_zero ? "true" : "false"); + show_boolean ("can_fast_zero", can_fast_zero); if (can_flush >= 0) - fprintf (fp, "\t%s: %s\n", "can_flush", can_flush ? "true" : "false"); + show_boolean ("can_flush", can_flush); if (can_fua >= 0) - fprintf (fp, "\t%s: %s\n", "can_fua", can_fua ? "true" : "false"); + show_boolean ("can_fua", can_fua); if (can_multi_conn >= 0) - fprintf (fp, "\t%s: %s\n", "can_multi_conn", - can_multi_conn ? "true" : "false"); + show_boolean ("can_multi_conn", can_multi_conn); if (can_trim >= 0) - fprintf (fp, "\t%s: %s\n", "can_trim", can_trim ? "true" : "false"); + show_boolean ("can_trim", can_trim); if (can_zero >= 0) - fprintf (fp, "\t%s: %s\n", "can_zero", can_zero ? "true" : "false"); + show_boolean ("can_zero", can_zero); if (block_minimum > 0) fprintf (fp, "\t%s: %" PRId64 "\n", "block_size_minimum", block_minimum); if (block_preferred > 0) @@ -269,6 +275,18 @@ show_one_export (struct nbd_handle *nbd, const char *desc, return true; } +/* Used for displaying booleans in non-JSON output. */ +static void +show_boolean (const char *name, bool cond) +{ + if (cond) + ansi_colour (ANSI_FG_GREEN, fp); + else + ansi_colour (ANSI_FG_RED, fp); + fprintf (fp, "\t%s: %s\n", name, cond ? "true" : "false"); + ansi_restore (fp); +} + static int collect_context (void *opaque, const char *name) { -- 2.37.0.rc2