From cf375672d0a144d8c8463fde87fabc88e14f77e7 Mon Sep 17 00:00:00 2001 From: Jim Garlick Date: Tue, 24 Oct 2017 15:16:45 -0700 Subject: [PATCH] cmd/flux-kvs: handle non-JSON values in "dir" cmd Problem: the KVS allows non-JSON values to be stored, but flux kvs dir -R exits with an error if it encounters one. For each value, if it decodes as JSON, format it like before. But if it doesn't, print it directly instead of exiting. If the output is a terminal, truncate long values so they fit within the terminal width (reusing logic from the ls subcommand). Also truncate values where they contain unprintable characters or newlines. Indicate truncated values by appending "...". Fixes #1158 --- src/cmd/flux-kvs.c | 128 +++++++++++++++++++++++++++++++++------------ 1 file changed, 95 insertions(+), 33 deletions(-) diff --git a/src/cmd/flux-kvs.c b/src/cmd/flux-kvs.c index a40105c56a2f..1148c8781825 100644 --- a/src/cmd/flux-kvs.c +++ b/src/cmd/flux-kvs.c @@ -52,7 +52,9 @@ int cmd_move (optparse_t *p, int argc, char **argv); int cmd_dir (optparse_t *p, int argc, char **argv); int cmd_ls (optparse_t *p, int argc, char **argv); -static void dump_kvs_dir (const flux_kvsdir_t *dir, bool Ropt, bool dopt); +static int get_window_width (optparse_t *p, int fd); +static void dump_kvs_dir (const flux_kvsdir_t *dir, int maxcol, + bool Ropt, bool dopt); #define min(a,b) ((a)<(b)?(a):(b)) @@ -83,6 +85,9 @@ static struct optparse_option dir_opts[] = { { .name = "directory", .key = 'd', .has_arg = 0, .usage = "List directory entries and not values", }, + { .name = "width", .key = 'w', .has_arg = 1, + .usage = "Set output width to COLS. 0 means no limit", + }, OPTPARSE_TABLE_END }; @@ -154,7 +159,7 @@ static struct optparse_subcommand subcommands[] = { put_opts }, { "dir", - "[-R] [-d] [key]", + "[-R] [-d] [-w COLS] [key]", "Display all keys under directory", cmd_dir, 0, @@ -304,37 +309,85 @@ int main (int argc, char *argv[]) return (exitval); } -static void output_key_json_object (const char *key, json_t *o) +static void kv_printf (const char *key, int maxcol, const char *fmt, ...) +{ + va_list ap; + int rc; + char *val, *kv, *p; + bool overflow = false; + + assert (maxcol == 0 || maxcol > 3); + + va_start (ap, fmt); + rc = vasprintf (&val, fmt, ap); + va_end (ap); + + if (rc < 0) + log_err_exit ("%s", __FUNCTION__); + + if (asprintf (&kv, "%s%s%s", key ? key : "", + key ? " = " : "", + val) < 0) + log_err_exit ("%s", __FUNCTION__); + + /* There will be no truncation of output if maxcol = 0. + * If truncation is enabled, ensure that at most maxcol columns are used. + * Truncate earlier if value contains a convenient newline break. + * Finally, truncate on first non-printable character. + */ + if (maxcol != 0) { + if (strlen (kv) > maxcol - 3) { + kv[maxcol - 3] = '\0'; + overflow = true; + } + if ((p = strchr (kv, '\n'))) { + *p = '\0'; + overflow = true; + } + for (p = kv; *p != '\0'; p++) { + if (!isprint (*p)) { + *p = '\0'; + overflow = true; + break; + } + } + } + printf ("%s%s\n", kv, + overflow ? "..." : ""); + + free (val); + free (kv); +} + +static void output_key_json_object (const char *key, json_t *o, int maxcol) { char *s; - if (key) - printf ("%s = ", key); switch (json_typeof (o)) { case JSON_NULL: - printf ("nil\n"); + kv_printf (key, maxcol, "nil"); break; case JSON_TRUE: - printf ("true\n"); + kv_printf (key, maxcol, "true"); break; case JSON_FALSE: - printf ("false\n"); + kv_printf (key, maxcol, "false"); break; case JSON_REAL: - printf ("%f\n", json_real_value (o)); + kv_printf (key, maxcol, "%f", json_real_value (o)); break; case JSON_INTEGER: - printf ("%lld\n", (long long)json_integer_value (o)); + kv_printf (key, maxcol, "%lld", (long long)json_integer_value (o)); break; case JSON_STRING: - printf ("%s\n", json_string_value (o)); + kv_printf (key, maxcol, "%s", json_string_value (o)); break; case JSON_ARRAY: case JSON_OBJECT: default: if (!(s = json_dumps (o, JSON_SORT_KEYS))) log_msg_exit ("json_dumps failed"); - printf ("%s\n", s); + kv_printf (key, maxcol, "%s", s); free (s); break; } @@ -352,7 +405,7 @@ static void output_key_json_str (const char *key, if (!(o = json_loads (json_str, JSON_DECODE_ANY, &error))) log_msg_exit ("%s: %s (line %d column %d)", arg, error.text, error.line, error.column); - output_key_json_object (key, o); + output_key_json_object (key, o, 0); json_decref (o); } @@ -656,7 +709,7 @@ static void watch_dump_kvsdir (flux_kvsdir_t *dir, bool Ropt, bool dopt, return; } - dump_kvs_dir (dir, Ropt, dopt); + dump_kvs_dir (dir, 0, Ropt, dopt); printf ("%s\n", WATCH_DIR_SEPARATOR); fflush (stdout); } @@ -811,23 +864,24 @@ int cmd_dropcache (optparse_t *p, int argc, char **argv) return (0); } -static void dump_kvs_val (const char *key, const char *json_str) +static void dump_kvs_val (const char *key, int maxcol, const char *value) { json_t *o; - json_error_t error; - if (!json_str) - json_str = "null"; - if (!(o = json_loads (json_str, JSON_DECODE_ANY, &error))) { - printf ("%s: %s (line %d column %d)\n", - key, error.text, error.line, error.column); - return; + if (!value) { + kv_printf (key, maxcol, ""); + } + else if ((o = json_loads (value, JSON_DECODE_ANY, NULL))) { + output_key_json_object (key, o, maxcol); + json_decref (o); + } + else { + kv_printf (key, maxcol, value); } - output_key_json_object (key, o); - json_decref (o); } -static void dump_kvs_dir (const flux_kvsdir_t *dir, bool Ropt, bool dopt) +static void dump_kvs_dir (const flux_kvsdir_t *dir, int maxcol, + bool Ropt, bool dopt) { const char *rootref = flux_kvsdir_rootref (dir); flux_t *h = flux_kvsdir_handle (dir); @@ -853,17 +907,23 @@ static void dump_kvs_dir (const flux_kvsdir_t *dir, bool Ropt, bool dopt) if (!(f = flux_kvs_lookupat (h, FLUX_KVS_READDIR, key, rootref)) || flux_kvs_lookup_get_dir (f, &ndir) < 0) log_err_exit ("%s", key); - dump_kvs_dir (ndir, Ropt, dopt); + dump_kvs_dir (ndir, maxcol, Ropt, dopt); flux_future_destroy (f); } else printf ("%s.\n", key); } else { if (!dopt) { - const char *json_str; - if (!(f = flux_kvs_lookupat (h, 0, key, rootref)) - || flux_kvs_lookup_get (f, &json_str) < 0) + const char *value; + const void *buf; + int len; + if (!(f = flux_kvs_lookupat (h, 0, key, rootref))) + log_err_exit ("%s", key); + if (flux_kvs_lookup_get (f, &value) == 0) // null terminated + dump_kvs_val (key, maxcol, value); + else if (flux_kvs_lookup_get_raw (f, &buf, &len) == 0) + kv_printf (key, maxcol, "%.*s", len, buf); + else log_err_exit ("%s", key); - dump_kvs_val (key, json_str); flux_future_destroy (f); } else @@ -877,6 +937,7 @@ static void dump_kvs_dir (const flux_kvsdir_t *dir, bool Ropt, bool dopt) int cmd_dir (optparse_t *p, int argc, char **argv) { flux_t *h = (flux_t *)optparse_get_data (p, "flux_handle"); + int maxcol = get_window_width (p, STDOUT_FILENO); bool Ropt; bool dopt; const char *key; @@ -897,7 +958,7 @@ int cmd_dir (optparse_t *p, int argc, char **argv) if (!(f = flux_kvs_lookup (h, FLUX_KVS_READDIR, key)) || flux_kvs_lookup_get_dir (f, &dir) < 0) log_err_exit ("%s", key); - dump_kvs_dir (dir, Ropt, dopt); + dump_kvs_dir (dir, maxcol, Ropt, dopt); flux_future_destroy (f); return (0); } @@ -948,8 +1009,6 @@ static int get_window_width (optparse_t *p, int fd) int width; const char *s; - if (optparse_hasopt (p, "1")) - return 1; if ((width = optparse_get_int (p, "width", -1)) >= 0) return width; if (ioctl (fd, TIOCGWINSZ, &w) == 0) @@ -1181,6 +1240,9 @@ int cmd_ls (optparse_t *p, int argc, char **argv) bool print_vspace = false; // print vertical space before label int rc = 0; + if (optparse_hasopt (p, "1")) + win_width = 1; + if (!(dirs = zlist_new ()) || !(singles = zlist_new ())) log_err_exit ("zlist_new");