From 1158f9d91049edb6b32c7a620e6280a202a2a3e7 Mon Sep 17 00:00:00 2001
From: Daniel Wagner <dwagner@suse.de>
Date: Mon, 26 Sep 2022 18:17:01 +0200
Subject: [PATCH] nvme: Add show-topology command

Add nvme show-topology which displays the entire nvme topology on the
host - all the subsystems with all the underlying controllers and its
states including ANA for all nvme devices.

Signed-off-by: Daniel Wagner <dwagner@suse.de>
---
 nvme-builtin.h |    3 
 nvme-print.c   |  244 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 nvme-print.h   |    2 
 nvme.c         |   70 ++++++++++++++++
 nvme.h         |    5 +
 5 files changed, 323 insertions(+), 1 deletion(-)

--- a/nvme-builtin.h
+++ b/nvme-builtin.h
@@ -104,7 +104,8 @@ COMMAND_LIST(
 	ENTRY("virt-mgmt", "Manage Flexible Resources between Primary and Secondary Controller ", virtual_mgmt)
 	ENTRY("rpmb", "Replay Protection Memory Block commands", rpmb_cmd)
 	ENTRY("lockdown", "Submit a Lockdown command,return result", lockdown_cmd)
-	ENTRY("dim", "Send Discovery Information Management command to a Discovery Controller", dim_cmd)
+	ENTRY("dim", "Send Discovery Information Management command to a Discovery Controller", dim_cmd) \
+	ENTRY("show-topology", "Show the topology", show_topology_cmd) \
 );
 
 #endif
--- a/nvme.c
+++ b/nvme.c
@@ -7916,6 +7916,76 @@ static int check_tls_key(int argc, char
 	return 0;
 }
 
+static int show_topology_cmd(int argc, char **argv, struct command *command, struct plugin *plugin)
+{
+	const char *desc = "Show the topolog\n";
+	const char *verbose = "Increase output verbosity";
+	const char *ranking = "Ranking order: namespace|ctrl";
+	enum nvme_print_flags flags;
+	nvme_root_t r;
+	enum nvme_cli_topo_ranking rank;
+	int err;
+
+	struct config {
+		char	*output_format;
+		int	verbose;
+		char	*ranking;
+	};
+
+	struct config cfg = {
+		.output_format	= "normal",
+		.verbose	= 0,
+		.ranking	= "namespace",
+	};
+
+	OPT_ARGS(opts) = {
+		OPT_FMT("output-format", 'o', &cfg.output_format, output_format),
+		OPT_INCR("verbose",      'v', &cfg.verbose,       verbose),
+		OPT_FMT("ranking",       'r', &cfg.ranking,       ranking),
+		OPT_END()
+	};
+
+	err = argconfig_parse(argc, argv, desc, opts);
+	if (err)
+		return err;
+
+	err = flags = validate_output_format(cfg.output_format);
+	if (flags < 0)
+		return err;
+	if (cfg.verbose)
+		flags |= VERBOSE;
+
+	if (!strcmp(cfg.ranking, "namespace"))
+		rank = NVME_CLI_TOPO_NAMESPACE;
+	else if (!strcmp(cfg.ranking, "ctrl"))
+		rank = NVME_CLI_TOPO_CTRL;
+	else {
+		fprintf(stderr, "Invalid ranking argument: %s\n",
+			cfg.ranking);
+		return -EINVAL;
+	}
+
+	r = nvme_create_root(stderr, map_log_level(cfg.verbose, false));
+	if (!r) {
+		fprintf(stderr, "Failed to create topology root: %s\n",
+			nvme_strerror(errno));
+		return -errno;
+	}
+
+	err = nvme_scan_topology(r, NULL, NULL);
+	if (err < 0) {
+		fprintf(stderr, "Failed to scan topology: %s\n",
+			 nvme_strerror(errno));
+		nvme_free_tree(r);
+		return err;
+	}
+
+	nvme_show_topology(r, flags, rank);
+	nvme_free_tree(r);
+
+	return err;
+}
+
 static int discover_cmd(int argc, char **argv, struct command *command, struct plugin *plugin)
 {
 	const char *desc = "Send Get Log Page request to Discovery Controller.";
--- a/nvme.h
+++ b/nvme.h
@@ -73,6 +73,11 @@ enum nvme_print_flags {
 	BINARY	= 1 << 3,	/* binary dump raw bytes */
 };
 
+enum nvme_cli_topo_ranking {
+	NVME_CLI_TOPO_NAMESPACE,
+	NVME_CLI_TOPO_CTRL,
+};
+
 #define SYS_NVME "/sys/class/nvme"
 
 void register_extension(struct plugin *plugin);
--- a/nvme-print.c
+++ b/nvme-print.c
@@ -7487,3 +7487,247 @@ void nvme_show_list_items(nvme_root_t r,
 	else
 		nvme_show_simple_list(r);
 }
+
+static unsigned int json_subsystem_topology_multipath(nvme_subsystem_t s,
+						      json_object *namespaces)
+{
+	nvme_ns_t n;
+	nvme_path_t p;
+	unsigned int i = 0;
+
+	nvme_subsystem_for_each_ns(s, n) {
+		struct json_object *ns_attrs;
+		struct json_object *paths;
+
+		ns_attrs = json_create_object();
+	        json_object_add_value_int(ns_attrs, "NSID",
+					  nvme_ns_get_nsid(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);
+
+			path_attrs = json_create_object();
+			json_object_add_value_string(path_attrs, "Name",
+						     nvme_ctrl_get_name(c));
+			json_object_add_value_string(path_attrs, "Transport",
+						     nvme_ctrl_get_transport(c));
+			json_object_add_value_string(path_attrs, "Address",
+						     nvme_ctrl_get_address(c));
+			json_object_add_value_string(path_attrs, "State",
+						     nvme_ctrl_get_state(c));
+			json_object_add_value_string(path_attrs, "ANAState",
+						     nvme_path_get_ana_state(p));
+			json_array_add_value_object(paths, path_attrs);
+		}
+		json_object_add_value_array(ns_attrs, "Paths", paths);
+		json_array_add_value_object(namespaces, ns_attrs);
+		i++;
+	}
+
+	return i;
+}
+
+static void json_print_nvme_subsystem_topology(nvme_subsystem_t s,
+					       json_object *namespaces)
+{
+	nvme_ctrl_t c;
+	nvme_ns_t n;
+
+	nvme_subsystem_for_each_ctrl(s, c) {
+		nvme_ctrl_for_each_ns(c, n) {
+			struct json_object *ctrl_attrs;
+			struct json_object *ns_attrs;
+			struct json_object *ctrl;
+
+			ns_attrs = json_create_object();
+		        json_object_add_value_int(ns_attrs, "NSID",
+						  nvme_ns_get_nsid(n));
+
+		        ctrl = json_create_array();
+			ctrl_attrs = json_create_object();
+			json_object_add_value_string(ctrl_attrs, "Name",
+						     nvme_ctrl_get_name(c));
+			json_object_add_value_string(ctrl_attrs, "Transport",
+						     nvme_ctrl_get_transport(c));
+			json_object_add_value_string(ctrl_attrs, "Address",
+						     nvme_ctrl_get_address(c));
+			json_object_add_value_string(ctrl_attrs, "State",
+						     nvme_ctrl_get_state(c));
+
+			json_array_add_value_object(ctrl, ctrl_attrs);
+			json_object_add_value_array(ns_attrs, "Controller", ctrl);
+			json_array_add_value_object(namespaces, ns_attrs);
+		}
+	}
+}
+
+static void json_simple_topology(nvme_root_t r)
+{
+	struct json_object *host_attrs, *subsystem_attrs;
+	struct json_object *subsystems, *namespaces;
+	struct json_object *root;
+	nvme_host_t h;
+
+	root = json_create_array();
+
+	nvme_for_each_host(r, h) {
+		nvme_subsystem_t s;
+		const char *hostid;
+
+		host_attrs = json_create_object();
+		json_object_add_value_string(host_attrs, "HostNQN",
+					     nvme_host_get_hostnqn(h));
+		hostid = nvme_host_get_hostid(h);
+		if (hostid)
+			json_object_add_value_string(host_attrs, "HostID", hostid);
+		subsystems = json_create_array();
+		nvme_for_each_subsystem(h, s) {
+			subsystem_attrs = json_create_object();
+			json_object_add_value_string(subsystem_attrs, "Name",
+						     nvme_subsystem_get_name(s));
+			json_object_add_value_string(subsystem_attrs, "NQN",
+						     nvme_subsystem_get_nqn(s));
+
+			json_array_add_value_object(subsystems, subsystem_attrs);
+			namespaces = json_create_array();
+
+			if (!json_subsystem_topology_multipath(s, namespaces))
+				json_print_nvme_subsystem_topology(s, namespaces);
+
+			json_object_add_value_array(subsystem_attrs, "Namespaces",
+						    namespaces);
+		}
+		json_object_add_value_array(host_attrs, "Subsystems", subsystems);
+		json_array_add_value_object(root, host_attrs);
+	}
+	json_print_object(root, NULL);
+	printf("\n");
+	json_free_object(root);
+}
+
+static bool nvme_is_multipath(nvme_subsystem_t s)
+{
+	nvme_ns_t n;
+	nvme_path_t p;
+
+	nvme_subsystem_for_each_ns(s, n)
+		nvme_namespace_for_each_path(n, p)
+			return true;
+
+	return false;
+}
+
+static void nvme_show_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;
+
+	if (ranking == NVME_CLI_TOPO_NAMESPACE) {
+		nvme_subsystem_for_each_ns(s, n) {
+			printf(" +- ns %d\n", nvme_ns_get_nsid(n));
+			printf(" \\\n");
+
+			nvme_namespace_for_each_path(n, p) {
+				c = nvme_path_get_ctrl(p);
+
+				printf("  +- %s %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),
+				       nvme_path_get_ana_state(p));
+			}
+		}
+	} else {
+		/* NVME_CLI_TOPO_CTRL */
+		nvme_subsystem_for_each_ctrl(s, c) {
+			printf(" +- %s %s %s\n",
+			       nvme_ctrl_get_name(c),
+			       nvme_ctrl_get_transport(c),
+			       nvme_ctrl_get_address(c));
+			printf(" \\\n");
+
+			nvme_subsystem_for_each_ns(s, n) {
+				nvme_namespace_for_each_path(n, p) {
+					if (nvme_path_get_ctrl(p) != c)
+						continue;
+
+					printf("  +- ns %d %s %s\n",
+					       nvme_ns_get_nsid(n),
+					       nvme_ctrl_get_state(c),
+					       nvme_path_get_ana_state(p));
+				}
+			}
+		}
+	}
+}
+
+static void nvme_show_subsystem_topology(nvme_subsystem_t s,
+				      enum nvme_cli_topo_ranking ranking)
+{
+	nvme_ctrl_t c;
+	nvme_ns_t n;
+
+	if (ranking == NVME_CLI_TOPO_NAMESPACE) {
+		nvme_subsystem_for_each_ctrl(s, c) {
+			nvme_ctrl_for_each_ns(c, n) {
+				printf(" +- ns %d\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));
+			}
+		}
+	} else {
+		/* NVME_CLI_TOPO_CTRL */
+		nvme_subsystem_for_each_ctrl(s, c) {
+			printf(" +- %s %s %s\n",
+			       nvme_ctrl_get_name(c),
+			       nvme_ctrl_get_transport(c),
+			       nvme_ctrl_get_address(c));
+			printf(" \\\n");
+			nvme_ctrl_for_each_ns(c, n) {
+				printf("  +- ns %d %s\n",
+				       nvme_ns_get_nsid(n),
+				       nvme_ctrl_get_state(c));
+			}
+		}
+	}
+}
+
+static void nvme_show_simple_topology(nvme_root_t r,
+				      enum nvme_cli_topo_ranking ranking)
+{
+	nvme_host_t h;
+	nvme_subsystem_t s;
+
+	nvme_for_each_host(r, h) {
+		nvme_for_each_subsystem(h, s) {
+
+			printf("%s - NQN=%s\n", nvme_subsystem_get_name(s),
+			       nvme_subsystem_get_nqn(s));
+			printf("\\\n");
+
+			if (nvme_is_multipath(s))
+				nvme_show_subsystem_topology_multipath(s, ranking);
+			else
+				nvme_show_subsystem_topology(s, ranking);
+		}
+	}
+}
+
+void nvme_show_topology(nvme_root_t r, enum nvme_print_flags flags,
+			enum nvme_cli_topo_ranking ranking)
+{
+	if (flags & JSON)
+		json_simple_topology(r);
+	else
+		nvme_show_simple_topology(r, ranking);
+}
--- a/nvme-print.h
+++ b/nvme-print.h
@@ -107,6 +107,8 @@ void nvme_show_endurance_group_list(stru
 	enum nvme_print_flags flags);
 void nvme_show_list_ns(struct nvme_ns_list *ns_list,
 	enum nvme_print_flags flags);
+void nvme_show_topology(nvme_root_t t, enum nvme_print_flags flags,
+			enum nvme_cli_topo_ranking ranking);
 
 void nvme_feature_show_fields(enum nvme_features_id fid, unsigned int result, unsigned char *buf);
 void nvme_directive_show(__u8 type, __u8 oper, __u16 spec, __u32 nsid, __u32 result,
