diff --git a/nvme-print-binary.c b/nvme-print-binary.c index 351513b90a..17def142b0 100644 --- a/nvme-print-binary.c +++ b/nvme-print-binary.c @@ -428,6 +428,7 @@ static struct print_ops binary_print_ops = { .print_nvme_subsystem_list = NULL, .topology_ctrl = NULL, .topology_namespace = NULL, + .topology_multipath = NULL, /* status and error messages */ .connect_msg = NULL, diff --git a/nvme-print-json.c b/nvme-print-json.c index 829ba718e8..cac9e25aad 100644 --- a/nvme-print-json.c +++ b/nvme-print-json.c @@ -4743,6 +4743,7 @@ static unsigned int json_subsystem_topology_multipath(nvme_subsystem_t s, nvme_ns_t n; nvme_path_t p; unsigned int i = 0; + const char *iopolicy = nvme_subsystem_get_iopolicy(s); nvme_subsystem_for_each_ns(s, n) { struct json_object *ns_attrs; @@ -4750,19 +4751,40 @@ static unsigned int json_subsystem_topology_multipath(nvme_subsystem_t s, ns_attrs = json_create_object(); obj_add_int(ns_attrs, "NSID", nvme_ns_get_nsid(n)); + obj_add_str(ns_attrs, "Name", nvme_ns_get_name(n)); paths = json_create_array(); nvme_namespace_for_each_path(n, p) { struct json_object *path_attrs; - - nvme_ctrl_t c = nvme_path_get_ctrl(p); + struct json_object *ctrls, *ctrl_attrs; + nvme_ctrl_t c; path_attrs = json_create_object(); - obj_add_str(path_attrs, "Name", nvme_ctrl_get_name(c)); - obj_add_str(path_attrs, "Transport", nvme_ctrl_get_transport(c)); - obj_add_str(path_attrs, "Address", nvme_ctrl_get_address(c)); - obj_add_str(path_attrs, "State", nvme_ctrl_get_state(c)); + obj_add_str(path_attrs, "Path", nvme_path_get_name(p)); obj_add_str(path_attrs, "ANAState", nvme_path_get_ana_state(p)); + + /* + * For iopolicy numa exclude "Qdepth", for iopolicy + * queue-depth exclude "NUMANodes" and for iopolicy + * round-robin exclude both "Qdepth" and "NUMANodes". + */ + if (!strcmp(iopolicy, "numa")) + obj_add_str(path_attrs, "NUMANodes", + nvme_path_get_numa_nodes(p)); + else if (!strcmp(iopolicy, "queue-depth")) + obj_add_int(path_attrs, "Qdepth", + nvme_path_get_queue_depth(p)); + + c = nvme_path_get_ctrl(p); + ctrls = json_create_array(); + ctrl_attrs = json_create_object(); + obj_add_str(ctrl_attrs, "Name", nvme_ctrl_get_name(c)); + obj_add_str(ctrl_attrs, "Transport", nvme_ctrl_get_transport(c)); + obj_add_str(ctrl_attrs, "Address", nvme_ctrl_get_address(c)); + obj_add_str(ctrl_attrs, "State", nvme_ctrl_get_state(c)); + array_add_obj(ctrls, ctrl_attrs); + obj_add_array(path_attrs, "Controller", ctrls); + array_add_obj(paths, path_attrs); } obj_add_array(ns_attrs, "Paths", paths); @@ -4787,6 +4809,7 @@ static void json_print_nvme_subsystem_topology(nvme_subsystem_t s, ns_attrs = json_create_object(); obj_add_int(ns_attrs, "NSID", nvme_ns_get_nsid(n)); + obj_add_str(ns_attrs, "Name", nvme_ns_get_name(n)); ctrl = json_create_array(); ctrl_attrs = json_create_object(); @@ -5546,6 +5569,7 @@ static struct print_ops json_print_ops = { .print_nvme_subsystem_list = json_print_nvme_subsystem_list, .topology_ctrl = json_simple_topology, .topology_namespace = json_simple_topology, + .topology_multipath = json_simple_topology, /* status and error messages */ .connect_msg = json_connect_msg, diff --git a/nvme-print-stdout.c b/nvme-print-stdout.c index c597b60872..8b32eb7af8 100644 --- a/nvme-print-stdout.c +++ b/nvme-print-stdout.c @@ -20,6 +20,7 @@ #include "nvme-models.h" #include "util/suffix.h" #include "util/types.h" +#include "util/table.h" #include "logging.h" #include "common.h" @@ -1126,6 +1127,8 @@ static void stdout_subsys_config(nvme_subsystem_t s) nvme_subsystem_get_nqn(s)); printf("%*s hostnqn=%s\n", len, " ", nvme_host_get_hostnqn(nvme_subsystem_get_host(s))); + printf("%*s iopolicy=%s\n", len, " ", + nvme_subsystem_get_iopolicy(s)); if (stdout_print_ops.flags & VERBOSE) { printf("%*s model=%s\n", len, " ", @@ -1134,8 +1137,6 @@ static void stdout_subsys_config(nvme_subsystem_t s) nvme_subsystem_get_serial(s)); printf("%*s firmware=%s\n", len, " ", nvme_subsystem_get_fw_rev(s)); - printf("%*s iopolicy=%s\n", len, " ", - nvme_subsystem_get_iopolicy(s)); printf("%*s type=%s\n", len, " ", nvme_subsystem_get_type(s)); } @@ -5589,12 +5590,134 @@ static void stdout_list_items(nvme_root_t r) stdout_simple_list(r); } +static bool subsystem_iopolicy_filter(const char *name, void *arg) +{ + nvme_subsystem_t s = arg; + const char *iopolicy = nvme_subsystem_get_iopolicy(s); + + if (!strcmp(iopolicy, "queue-depth")) { + /* exclude "Nodes" for iopolicy queue-depth */ + if (!strcmp(name, "Nodes")) + return false; + } else if (!strcmp(iopolicy, "numa")) { + /* exclude "Qdepth" for iopolicy numa */ + if (!strcmp(name, "Qdepth")) + return false; + } else { /* round-robin */ + /* exclude "Nodes" and "Qdepth" for iopolicy round-robin */ + if (!strcmp(name, "Nodes") || !strcmp(name, "Qdepth")) + return false; + } + + return true; +} + +static void stdout_tabular_subsystem_topology_multipath(nvme_subsystem_t s) +{ + nvme_ns_t n; + nvme_path_t p; + nvme_ctrl_t c; + int row, col; + bool first; + struct table *t; + const char *iopolicy = nvme_subsystem_get_iopolicy(s); + struct table_column columns[] = { + {"NSHead", LEFT, 0}, + {"NSID", LEFT, 0}, + {"NSPath", LEFT, 0}, + {"ANAState", LEFT, 0}, + {"Nodes", LEFT, 0}, + {"Qdepth", LEFT, 0}, + {"Controller", LEFT, 0}, + {"TrType", LEFT, 0}, + {"Address", LEFT, 0}, + {"State", LEFT, 0}, + }; + + t = table_init(); + if (!t) { + printf("Failed to init table\n"); + return; + } + + if (table_add_columns_filter(t, columns, ARRAY_SIZE(columns), + subsystem_iopolicy_filter, (void *)s) < 0) { + printf("Failed to add columns\n"); + goto free_tbl; + } + + nvme_subsystem_for_each_ns(s, n) { + first = true; + nvme_namespace_for_each_path(n, p) { + c = nvme_path_get_ctrl(p); + + row = table_get_row_id(t); + if (row < 0) { + printf("Failed to add row\n"); + goto free_tbl; + } + /* For the first row we print actual NSHead name, + * however, for the subsequent rows we print "arrow" + * ("-->") symbol for NSHead. This "arrow" style makes + * it visually obvious that susequenet entries (if + * present) are a path under the first NSHead. + */ + col = -1; + /* col 0: NSHead */ + if (first) { + table_set_value_str(t, ++col, row, + nvme_ns_get_name(n), LEFT); + first = false; + } else + table_set_value_str(t, ++col, row, + "-->", CENTERED); + /* col 1: NSID */ + table_set_value_int(t, ++col, row, + nvme_ns_get_nsid(n), CENTERED); + /* col 2: NSPath */ + table_set_value_str(t, ++col, row, + nvme_path_get_name(p), LEFT); + /* col 3: ANAState */ + table_set_value_str(t, ++col, row, + nvme_path_get_ana_state(p), LEFT); + + if (!strcmp(iopolicy, "numa")) + /* col 4: Nodes */ + table_set_value_str(t, ++col, row, + nvme_path_get_numa_nodes(p), CENTERED); + else if (!strcmp(iopolicy, "queue-depth")) + /* col 4 : Qdepth */ + table_set_value_int(t, ++col, row, + nvme_path_get_queue_depth(p), CENTERED); + + /* col 5: Controller */ + table_set_value_str(t, ++col, row, + nvme_ctrl_get_name(c), LEFT); + /* col 6: TrType */ + table_set_value_str(t, ++col, row, + nvme_ctrl_get_transport(c), LEFT); + /* col 7: Address */ + table_set_value_str(t, ++col, row, + nvme_ctrl_get_address(c), LEFT); + /* col 8: State */ + table_set_value_str(t, ++col, row, + nvme_ctrl_get_state(c), LEFT); + + table_add_row(t, row); + } + } + table_print(t); +free_tbl: + table_free(t); +} + static void stdout_subsystem_topology_multipath(nvme_subsystem_t s, enum nvme_cli_topo_ranking ranking) { nvme_ns_t n; nvme_path_t p; nvme_ctrl_t c; + const char *iopolicy = nvme_subsystem_get_iopolicy(s); if (ranking == NVME_CLI_TOPO_NAMESPACE) { nvme_subsystem_for_each_ns(s, n) { @@ -5615,7 +5738,7 @@ static void stdout_subsystem_topology_multipath(nvme_subsystem_t s, nvme_path_get_ana_state(p)); } } - } else { + } else if (ranking == NVME_CLI_TOPO_CTRL) { /* NVME_CLI_TOPO_CTRL */ nvme_subsystem_for_each_ctrl(s, c) { printf(" +- %s %s %s\n", @@ -5636,9 +5759,125 @@ static void stdout_subsystem_topology_multipath(nvme_subsystem_t s, } } } + } else { + /* NVME_CLI_TOPO_MULTIPATH */ + nvme_subsystem_for_each_ns(s, n) { + printf(" +- %s (ns %d)\n", + nvme_ns_get_name(n), + nvme_ns_get_nsid(n)); + printf(" \\\n"); + nvme_namespace_for_each_path(n, p) { + c = nvme_path_get_ctrl(p); + + if (!strcmp(iopolicy, "numa")) { + /* + * For iopolicy numa, exclude printing + * qdepth. + */ + printf(" +- %s %s %s %s %s %s %s\n", + nvme_path_get_name(p), + nvme_path_get_ana_state(p), + nvme_path_get_numa_nodes(p), + nvme_ctrl_get_name(c), + nvme_ctrl_get_transport(c), + nvme_ctrl_get_address(c), + nvme_ctrl_get_state(c)); + + } else if (!strcmp(iopolicy, "queue-depth")) { + /* + * For iopolicy queue-depth, exclude + * printing numa nodes. + */ + printf(" +- %s %s %d %s %s %s %s\n", + nvme_path_get_name(p), + nvme_path_get_ana_state(p), + nvme_path_get_queue_depth(p), + nvme_ctrl_get_name(c), + nvme_ctrl_get_transport(c), + nvme_ctrl_get_address(c), + nvme_ctrl_get_state(c)); + + } else { /* round-robin */ + /* + * For iopolicy round-robin, exclude + * printing numa nodes and qdepth. + */ + printf(" +- %s %s %s %s %s %s\n", + nvme_path_get_name(p), + nvme_path_get_ana_state(p), + nvme_ctrl_get_name(c), + nvme_ctrl_get_transport(c), + nvme_ctrl_get_address(c), + nvme_ctrl_get_state(c)); + } + } + } } } +static void stdout_tabular_subsystem_topology(nvme_subsystem_t s) +{ + nvme_ctrl_t c; + nvme_ns_t n; + int row; + struct table *t; + struct table_column columns[] = { + {"Namespace", LEFT, 0}, + {"NSID", LEFT, 0}, + {"Controller", LEFT, 0}, + {"Trtype", LEFT, 0}, + {"Address", LEFT, 0}, + {"State", LEFT, 0}, + }; + + t = table_init(); + if (!t) { + printf("Failed to init table\n"); + return; + } + + if (table_add_columns(t, columns, ARRAY_SIZE(columns)) < 0) { + printf("Failed to add columns\n"); + goto free_tbl; + } + + nvme_subsystem_for_each_ctrl(s, c) { + nvme_ctrl_for_each_ns(c, n) { + c = nvme_ns_get_ctrl(n); + + row = table_get_row_id(t); + if (row < 0) { + printf("Failed to add row\n"); + goto free_tbl; + } + + /* col 0: Namespace */ + table_set_value_str(t, 0, row, + nvme_ns_get_name(n), LEFT); + /* col 1: NSID */ + table_set_value_int(t, 1, row, + nvme_ns_get_nsid(n), CENTERED); + /* col 2: Controller */ + table_set_value_str(t, 2, row, + nvme_ctrl_get_name(c), LEFT); + /* col 3: Trtype */ + table_set_value_str(t, 3, row, + nvme_ctrl_get_transport(c), LEFT); + /* col 4: Address */ + table_set_value_str(t, 4, row, + nvme_ctrl_get_address(c), LEFT); + /* col 5: State */ + table_set_value_str(t, 5, row, + nvme_ctrl_get_state(c), LEFT); + + table_add_row(t, row); + } + } + table_print(t); +free_tbl: + table_free(t); +} + static void stdout_subsystem_topology(nvme_subsystem_t s, enum nvme_cli_topo_ranking ranking) { @@ -5657,7 +5896,7 @@ static void stdout_subsystem_topology(nvme_subsystem_t s, nvme_ctrl_get_state(c)); } } - } else { + } else if (ranking == NVME_CLI_TOPO_CTRL) { /* NVME_CLI_TOPO_CTRL */ nvme_subsystem_for_each_ctrl(s, c) { printf(" +- %s %s %s\n", @@ -5671,6 +5910,55 @@ static void stdout_subsystem_topology(nvme_subsystem_t s, nvme_ctrl_get_state(c)); } } + } else { + /* NVME_CLI_TOPO_MULTIPATH */ + nvme_subsystem_for_each_ctrl(s, c) { + nvme_ctrl_for_each_ns(c, n) { + c = nvme_ns_get_ctrl(n); + + printf(" +- %s (ns %d)\n", + nvme_ns_get_name(n), + nvme_ns_get_nsid(n)); + printf(" \\\n"); + printf(" +- %s %s %s %s\n", + nvme_ctrl_get_name(c), + nvme_ctrl_get_transport(c), + nvme_ctrl_get_address(c), + nvme_ctrl_get_state(c)); + } + } + } +} + +static void stdout_topology_tabular(nvme_root_t r) +{ + nvme_host_t h; + nvme_subsystem_t s; + bool first = true; + + nvme_for_each_host(r, h) { + nvme_for_each_subsystem(h, s) { + bool no_ctrl = true; + nvme_ctrl_t c; + + nvme_subsystem_for_each_ctrl(s, c) + no_ctrl = false; + + if (no_ctrl) + continue; + + if (!first) + printf("\n"); + first = false; + + stdout_subsys_config(s); + printf("\n"); + + if (nvme_is_multipath(s)) + stdout_tabular_subsystem_topology_multipath(s); + else + stdout_tabular_subsystem_topology(s); + } } } @@ -5683,6 +5971,15 @@ static void stdout_simple_topology(nvme_root_t r, nvme_for_each_host(r, h) { nvme_for_each_subsystem(h, s) { + bool no_ctrl = true; + nvme_ctrl_t c; + + nvme_subsystem_for_each_ctrl(s, c) + no_ctrl = false; + + if (no_ctrl) + continue; + if (!first) printf("\n"); first = false; @@ -5708,6 +6005,11 @@ static void stdout_topology_ctrl(nvme_root_t r) stdout_simple_topology(r, NVME_CLI_TOPO_CTRL); } +static void stdout_topology_multipath(nvme_root_t r) +{ + stdout_simple_topology(r, NVME_CLI_TOPO_MULTIPATH); +} + static void stdout_message(bool error, const char *msg, va_list ap) { vfprintf(error ? stderr : stdout, msg, ap); @@ -6277,6 +6579,8 @@ static struct print_ops stdout_print_ops = { .print_nvme_subsystem_list = stdout_subsystem_list, .topology_ctrl = stdout_topology_ctrl, .topology_namespace = stdout_topology_namespace, + .topology_multipath = stdout_topology_multipath, + .topology_tabular = stdout_topology_tabular, /* status and error messages */ .connect_msg = stdout_connect_msg, diff --git a/nvme-print.c b/nvme-print.c index 3a71dffcbe..d1af8284e0 100644 --- a/nvme-print.c +++ b/nvme-print.c @@ -1551,8 +1551,15 @@ void nvme_show_topology(nvme_root_t r, { if (ranking == NVME_CLI_TOPO_NAMESPACE) nvme_print(topology_namespace, flags, r); - else + else if (ranking == NVME_CLI_TOPO_CTRL) nvme_print(topology_ctrl, flags, r); + else + nvme_print(topology_multipath, flags, r); +} + +void nvme_show_topology_tabular(nvme_root_t r, nvme_print_flags_t flags) +{ + nvme_print(topology_tabular, flags, r); } void nvme_show_message(bool error, const char *msg, ...) diff --git a/nvme-print.h b/nvme-print.h index 4ccf5e6351..a798256604 100644 --- a/nvme-print.h +++ b/nvme-print.h @@ -106,6 +106,8 @@ struct print_ops { void (*print_nvme_subsystem_list)(nvme_root_t r, bool show_ana); void (*topology_ctrl)(nvme_root_t r); void (*topology_namespace)(nvme_root_t r); + void (*topology_multipath)(nvme_root_t r); + void (*topology_tabular)(nvme_root_t r); /* status and error messages */ void (*connect_msg)(nvme_ctrl_t c); @@ -250,6 +252,7 @@ void nvme_show_list_ns(struct nvme_ns_list *ns_list, void nvme_show_topology(nvme_root_t t, enum nvme_cli_topo_ranking ranking, nvme_print_flags_t flags); +void nvme_show_topology_tabular(nvme_root_t t, nvme_print_flags_t flags); void nvme_feature_show(enum nvme_features_id fid, int sel, unsigned int result); void nvme_feature_show_fields(enum nvme_features_id fid, unsigned int result, unsigned char *buf); diff --git a/nvme.c b/nvme.c index ea5a57f1dd..3f1e286b51 100644 --- a/nvme.c +++ b/nvme.c @@ -545,6 +545,8 @@ int validate_output_format(const char *format, nvme_print_flags_t *flags) #endif /* CONFIG_JSONC */ else if (!strcmp(format, "binary")) f = BINARY; + else if (!strcmp(format, "tabular")) + f = TABULAR; else return -EINVAL; @@ -10204,9 +10206,12 @@ static int tls_key(int argc, char **argv, struct command *command, struct plugin static int show_topology_cmd(int argc, char **argv, struct command *command, struct plugin *plugin) { const char *desc = "Show the topology\n"; - const char *ranking = "Ranking order: namespace|ctrl"; + const char *output_format = "Output format: normal|json|binary|tabular"; + const char *ranking = "Ranking order: namespace|ctrl|multipath"; nvme_print_flags_t flags; _cleanup_nvme_root_ nvme_root_t r = NULL; + char *devname = NULL; + nvme_scan_filter_t filter = NULL; enum nvme_cli_topo_ranking rank; int err; @@ -10238,6 +10243,8 @@ static int show_topology_cmd(int argc, char **argv, struct command *command, str rank = NVME_CLI_TOPO_NAMESPACE; } else if (!strcmp(cfg.ranking, "ctrl")) { rank = NVME_CLI_TOPO_CTRL; + } else if (!strcmp(cfg.ranking, "multipath")) { + rank = NVME_CLI_TOPO_MULTIPATH; } else { nvme_show_error("Invalid ranking argument: %s", cfg.ranking); return -EINVAL; @@ -10249,13 +10256,29 @@ static int show_topology_cmd(int argc, char **argv, struct command *command, str return -errno; } - err = nvme_scan_topology(r, NULL, NULL); + if (optind < argc) + devname = basename(argv[optind++]); + + if (devname) { + int subsys_id, nsid; + + if (sscanf(devname, "nvme%dn%d", &subsys_id, &nsid) != 2) { + nvme_show_error("Invalid device name %s\n", devname); + return -EINVAL; + } + filter = nvme_match_device_filter; + } + + err = nvme_scan_topology(r, filter, (void *)devname); if (err < 0) { nvme_show_error("Failed to scan topology: %s", nvme_strerror(errno)); return err; } - nvme_show_topology(r, rank, flags); + if (flags & TABULAR) + nvme_show_topology_tabular(r, flags); + else + nvme_show_topology(r, rank, flags); return err; } diff --git a/nvme.h b/nvme.h index 248c5c7ca6..c4f0f0cf14 100644 --- a/nvme.h +++ b/nvme.h @@ -39,6 +39,7 @@ enum nvme_print_flags { JSON = 1 << 1, /* display in json format */ VS = 1 << 2, /* hex dump vendor specific data areas */ BINARY = 1 << 3, /* binary dump raw bytes */ + TABULAR = 1 << 4, /* prints aligned columns for easy reading */ }; typedef uint32_t nvme_print_flags_t; @@ -46,6 +47,7 @@ typedef uint32_t nvme_print_flags_t; enum nvme_cli_topo_ranking { NVME_CLI_TOPO_NAMESPACE, NVME_CLI_TOPO_CTRL, + NVME_CLI_TOPO_MULTIPATH, }; #define SYS_NVME "/sys/class/nvme" diff --git a/util/meson.build b/util/meson.build index 75aed49c30..5b402b1a32 100644 --- a/util/meson.build +++ b/util/meson.build @@ -8,7 +8,8 @@ sources += [ 'util/sighdl.c', 'util/suffix.c', 'util/types.c', - 'util/utils.c' + 'util/utils.c', + 'util/table.c' ] if json_c_dep.found() diff --git a/util/table.c b/util/table.c new file mode 100644 index 0000000000..88062699df --- /dev/null +++ b/util/table.c @@ -0,0 +1,320 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * table.c : Common APIs for printing tabular format output. + * + * Copyright (c) 2025 Nilay Shroff, IBM + * + * 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 2 + * 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. + */ + +#include +#include +#include +#include + +#include "table.h" + +static int table_get_value_width(struct value *v) +{ + char buf[64]; + int len = 0; + + switch (v->type) { + case FMT_STRING: + len = strlen((const char *)v->s); + break; + case FMT_INT: + len = snprintf(buf, sizeof(buf), "%d", v->i); + break; + default: + printf("Invalid print format!\n"); + break; + } + return len; +} + +static void table_print_centered(struct value *val, int width, enum fmt_type type) +{ + int i, len, left_pad, right_pad; + char buf[64]; + + if (type == FMT_STRING) + len = strlen(val->s); + else if (type == FMT_INT) + len = snprintf(buf, sizeof(buf), "%d", val->i); + else if (type == FMT_UNSIGNED) + len = snprintf(buf, sizeof(buf), "%u", val->u); + else if (type == FMT_LONG) + len = snprintf(buf, sizeof(buf), "%ld", val->ld); + else if (type == FMT_UNSIGNED_LONG) + len = snprintf(buf, sizeof(buf), "%lu", val->lu); + else { + fprintf(stderr, "Invalid format!\n"); + return; + } + + left_pad = (width - len) / 2; + right_pad = width - len - left_pad; + + /* add left padding */ + for (i = 0; i < left_pad; i++) + putchar(' '); + + /* print value */ + if (type == FMT_STRING) + printf("%s ", val->s); + else if (type == FMT_INT) + printf("%d ", val->i); + else if (type == FMT_UNSIGNED) + printf("%u ", val->u); + else if (type == FMT_LONG) + printf("%ld ", val->ld); + else if (type == FMT_UNSIGNED_LONG) + printf("%lu", val->lu); + + /* add right padding */ + for (i = 0; i < right_pad; i++) + putchar(' '); +} + +static void table_print_columns(const struct table *t) +{ + int col, j, width; + struct table_column *c; + struct value v; + + for (col = 0; col < t->num_columns; col++) { + c = &t->columns[col]; + width = c->width; + if (c->align == LEFT) + width *= -1; + + if (c->align == CENTERED) { + v.s = c->name; + v.align = c->align; + table_print_centered(&v, width, FMT_STRING); + } else + printf("%*s ", width, c->name); + } + + printf("\n"); + + for (col = 0; col < t->num_columns; col++) { + for (j = 0; j < t->columns[col].width; j++) + putchar('-'); + printf(" "); + } + + printf("\n"); +} + +static void table_print_rows(const struct table *t) +{ + int row, col; + struct table_column *c; + struct table_row *r; + int width; + struct value *v; + + for (row = 0; row < t->num_rows; row++) { + for (col = 0; col < t->num_columns; col++) { + c = &t->columns[col]; + r = &t->rows[row]; + v = &r->val[col]; + + width = c->width; + if (v->align == LEFT) + width *= -1; + + if (v->align == CENTERED) + table_print_centered(v, width, v->type); + else { + switch (v->type) { + case FMT_STRING: + printf("%*s ", width, v->s); + break; + + case FMT_INT: + printf("%*d ", width, v->i); + break; + + case FMT_UNSIGNED: + printf("%*u ", width, v->u); + break; + + case FMT_LONG: + printf("%*ld ", width, v->ld); + break; + + case FMT_UNSIGNED_LONG: + printf("%*lu ", width, v->lu); + break; + + default: + fprintf(stderr, "Invalid format!\n"); + break; + } + } + } + printf("\n"); + } +} + +void table_print(struct table *t) +{ + /* first print columns */ + table_print_columns(t); + + /* next print rows */ + table_print_rows(t); +} + +int table_get_row_id(struct table *t) +{ + struct table_row *new_rows; + int row = t->num_rows; + + new_rows = reallocarray(t->rows, (row + 1), sizeof(struct table_row)); + if (!new_rows) + return -ENOMEM; + + t->rows = new_rows; + t->rows[row].val = calloc(t->num_columns, sizeof(struct value)); + if (!t->rows->val) + return -ENOMEM; + + t->num_rows++; + return row; +} + +void table_add_row(struct table *t, int row_id) +{ + int col, max_width, width; + struct table_row *row = &t->rows[row_id]; + + /* Adjust the column width based on the row value. */ + for (col = 0; col < t->num_columns; col++) { + max_width = t->columns[col].width; + width = table_get_value_width(&row->val[col]); + if (width > max_width) + t->columns[col].width = width; + } +} + +struct table *table_init(void) +{ + struct table *t; + + t = malloc(sizeof(struct table)); + if (!t) + return NULL; + + memset(t, 0, sizeof(struct table)); + return t; +} + +static int table_add_column(struct table *t, struct table_column *c) +{ + struct table_column *new_columns; + int col = t->num_columns; + + new_columns = reallocarray(t->columns, t->num_columns + 1, + sizeof(struct table_column)); + if (!new_columns) + return -ENOMEM; + + t->columns = new_columns; + t->columns[col].name = strdup(c->name); + if (!t->columns[col].name) + return -ENOMEM; + t->columns[col].align = c->align; + t->columns[col].width = strlen(c->name); + t->num_columns++; + + return 0; +} + +int table_add_columns_filter(struct table *t, struct table_column *c, + int num_columns, + bool (*filter)(const char *name, void *arg), + void *arg) +{ + int col; + + if (!filter) + return table_add_columns(t, c, num_columns); + + for (col = 0; col < num_columns; col++) { + if (!filter(c[col].name, arg)) + continue; /* skip this column */ + + if (table_add_column(t, &c[col])) + goto out; + } + return 0; +out: + return -ENOMEM; +} + +int table_add_columns(struct table *t, struct table_column *c, int num_columns) +{ + int col; + + t->columns = calloc(num_columns, sizeof(struct table_column)); + if (!t->columns) + return -ENOMEM; + + for (col = 0; col < num_columns; col++) { + t->columns[col].name = strdup(c[col].name); + if (!t->columns[col].name) + goto free_col; + + t->columns[col].align = c[col].align; + t->columns[col].width = strlen(t->columns[col].name); + } + t->num_columns = num_columns; + + return 0; +free_col: + while (--col >= 0) + free(t->columns[col].name); + free(t->columns); + t->columns = NULL; + return -ENOMEM; +} + +void table_free(struct table *t) +{ + int row, col; + struct table_row *r; + struct value *v; + + /* free rows */ + for (row = 0; row < t->num_rows; row++) { + r = &t->rows[row]; + for (col = 0; col < t->num_columns; col++) { + v = &r->val[col]; + + if (v->type == FMT_STRING) + free(v->s); + } + free(r->val); + } + free(t->rows); + + /* free columns */ + for (col = 0; col < t->num_columns; col++) + free(t->columns[col].name); + free(t->columns); + + /* free table */ + free(t); +} diff --git a/util/table.h b/util/table.h new file mode 100644 index 0000000000..1c98c990de --- /dev/null +++ b/util/table.h @@ -0,0 +1,149 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +#ifndef _TABLE_H_ +#define _TABLE_H_ + +#include + +enum fmt_type { + FMT_STRING, + FMT_INT, + FMT_UNSIGNED, + FMT_LONG, + FMT_UNSIGNED_LONG, +}; + +enum alignment { + RIGHT, + LEFT, + CENTERED +}; + +struct value { + union { + char *s; + int i; + unsigned int u; + long ld; + unsigned long lu; + }; + enum alignment align; + enum fmt_type type; +}; + +struct table_row { + struct value *val; +}; + +struct table_column { + char *name; + enum alignment align; + int width; /* auto populated */ +}; + +struct table { + struct table_column *columns; + int num_columns; + struct table_row *rows; + int num_rows; +}; + +static inline int table_set_value_str(struct table *t, int col, int row, + const char *str, enum alignment align) +{ + struct table_row *r; + struct value *v; + char *s; + + if (col >= t->num_columns || row >= t->num_rows) + return -EINVAL; + + s = strdup(str); + if (!s) + return -ENOMEM; + + r = &t->rows[row]; + v = &r->val[col]; + v->s = s; + v->align = align; + v->type = FMT_STRING; + + return 0; +} + +static inline int table_set_value_int(struct table *t, int col, int row, + int i, enum alignment align) +{ + struct table_row *r; + struct value *v; + + if (col >= t->num_columns || row >= t->num_rows) + return -EINVAL; + + r = &t->rows[row]; + v = &r->val[col]; + v->i = i; + v->align = align; + v->type = FMT_INT; + + return 0; +} + +static inline int table_set_value_unsigned(struct table *t, int col, int row, + int u, enum alignment align) +{ + struct table_row *r; + struct value *v; + + if (col >= t->num_columns || row >= t->num_rows) + return -EINVAL; + + r = &t->rows[row]; + v = &r->val[col]; + v->u = u; + v->align = align; + v->type = FMT_UNSIGNED; + + return 0; +} + +static inline int table_set_value_long(struct table *t, int col, int row, + long ld, enum alignment align) +{ + struct table_row *r; + struct value *v; + + if (col >= t->num_columns || row >= t->num_rows) + return -EINVAL; + + r = &t->rows[row]; + v = &r->val[col]; + v->ld = ld; + v->align = align; + v->type = FMT_LONG; + + return 0; +} + +static inline void table_set_value_unsigned_long(struct table *t, int col, + int row, long lu, enum alignment align) +{ + struct table_row *r = &t->rows[row]; + struct value *v = &r->val[col]; + + v->lu = lu; + v->align = align; + v->type = FMT_UNSIGNED_LONG; +} + +struct table *table_init(void); +int table_add_columns(struct table *t, struct table_column *c, int num_columns); +int table_add_columns_filter(struct table *t, struct table_column *c, + int num_columns, + bool (*filter)(const char *name, void *arg), + void *arg); +int table_get_row_id(struct table *t); +void table_add_row(struct table *t, int row); +void table_print(struct table *t); +void table_free(struct table *t); + +#endif /* _TABLE_H_ */