git: 4f9fa31c5c2c - main - ctladm: Add nvlist command to list active NVMeoF associations

From: John Baldwin <jhb_at_FreeBSD.org>
Date: Fri, 03 May 2024 00:16:12 UTC
The branch main has been updated by jhb:

URL: https://cgit.FreeBSD.org/src/commit/?id=4f9fa31c5c2ce78482677331e396cf104ae9b33f

commit 4f9fa31c5c2ce78482677331e396cf104ae9b33f
Author:     John Baldwin <jhb@FreeBSD.org>
AuthorDate: 2024-05-02 23:35:02 +0000
Commit:     John Baldwin <jhb@FreeBSD.org>
CommitDate: 2024-05-02 23:38:39 +0000

    ctladm: Add nvlist command to list active NVMeoF associations
    
    Reviewed by:    imp
    Sponsored by:   Chelsio Communications
    Differential Revision:  https://reviews.freebsd.org/D44728
---
 usr.sbin/ctladm/ctladm.8 |  17 +++-
 usr.sbin/ctladm/ctladm.c | 254 ++++++++++++++++++++++++++++++++++++++++++++++-
 2 files changed, 269 insertions(+), 2 deletions(-)

diff --git a/usr.sbin/ctladm/ctladm.8 b/usr.sbin/ctladm/ctladm.8
index ba2712cb278c..6e084c30de36 100644
--- a/usr.sbin/ctladm/ctladm.8
+++ b/usr.sbin/ctladm/ctladm.8
@@ -35,7 +35,7 @@
 .\"
 .\" $Id: //depot/users/kenm/FreeBSD-test2/usr.sbin/ctladm/ctladm.8#3 $
 .\"
-.Dd December 27, 2023
+.Dd May 2, 2024
 .Dt CTLADM 8
 .Os
 .Sh NAME
@@ -198,6 +198,10 @@
 .Ic isterminate
 .Aq Fl a | Fl c Ar connection-id | Fl i Ar name | Fl p Ar portal
 .Nm
+.Ic nvlist
+.Op Fl v
+.Op Fl x
+.Nm
 .Ic help
 .Sh DESCRIPTION
 The
@@ -861,6 +865,17 @@ Specify initiator name.
 .It Fl p
 Specify initiator portal (hostname or IP address).
 .El
+.It Ic nvlist
+Get a list of currently running NVMeoF associations.
+This includes host and controller names and the unique controller IDs.
+.Bl -tag -width 11n
+.It Fl v
+Verbose mode.
+.It Fl x
+Dump the raw XML.
+The sessions list information from the kernel comes in XML format, and this
+option allows the display of the raw XML data.
+.El
 .It Ic help
 Display
 .Nm
diff --git a/usr.sbin/ctladm/ctladm.c b/usr.sbin/ctladm/ctladm.c
index 36cedf548ae5..d0456986fd12 100644
--- a/usr.sbin/ctladm/ctladm.c
+++ b/usr.sbin/ctladm/ctladm.c
@@ -71,6 +71,7 @@
 #include <cam/ctl/ctl_ioctl.h>
 #include <cam/ctl/ctl_util.h>
 #include <cam/ctl/ctl_scsi_all.h>
+#include <dev/nvmf/nvmf_proto.h>
 #include <camlib.h>
 #include <libutil.h>
 #include "ctladm.h"
@@ -113,7 +114,8 @@ typedef enum {
 	CTLADM_CMD_ISLIST,
 	CTLADM_CMD_ISLOGOUT,
 	CTLADM_CMD_ISTERMINATE,
-	CTLADM_CMD_LUNMAP
+	CTLADM_CMD_LUNMAP,
+	CTLADM_CMD_NVLIST
 } ctladm_cmdfunction;
 
 typedef enum {
@@ -179,6 +181,7 @@ static struct ctladm_opts option_table[] = {
 	{"lunmap", CTLADM_CMD_LUNMAP, CTLADM_ARG_NONE, "p:l:L:"},
 	{"modesense", CTLADM_CMD_MODESENSE, CTLADM_ARG_NEED_TL, "P:S:dlm:c:"},
 	{"modify", CTLADM_CMD_MODIFY, CTLADM_ARG_NONE, "b:l:o:s:"},
+	{"nvlist", CTLADM_CMD_NVLIST, CTLADM_ARG_NONE, "vx"},
 	{"port", CTLADM_CMD_PORT, CTLADM_ARG_NONE, "lo:O:d:crp:qt:w:W:x"},
 	{"portlist", CTLADM_CMD_PORTLIST, CTLADM_ARG_NONE, "f:ilp:qvx"},
 	{"prin", CTLADM_CMD_PRES_IN, CTLADM_ARG_NEED_TL, "a:"},
@@ -3818,6 +3821,251 @@ cctl_lunmap(int fd, int argc, char **argv, char *combinedopt)
 	return (retval);
 }
 
+struct cctl_nvlist_conn {
+	int connection_id;
+	char *hostnqn;
+	char *subnqn;
+	int trtype;
+	STAILQ_ENTRY(cctl_nvlist_conn) links;
+};
+
+struct cctl_nvlist_data {
+	int num_conns;
+	STAILQ_HEAD(,cctl_nvlist_conn) conn_list;
+	struct cctl_nvlist_conn *cur_conn;
+	u_int level;
+	struct sbuf *cur_sb[32];
+};
+
+static void
+cctl_nvlist_start_element(void *user_data, const char *name, const char **attr)
+{
+	int i;
+	struct cctl_nvlist_data *nvlist;
+	struct cctl_nvlist_conn *cur_conn;
+
+	nvlist = (struct cctl_nvlist_data *)user_data;
+	cur_conn = nvlist->cur_conn;
+	nvlist->level++;
+	if ((u_int)nvlist->level >= nitems(nvlist->cur_sb))
+		errx(1, "%s: too many nesting levels, %zd max", __func__,
+		    nitems(nvlist->cur_sb));
+
+	nvlist->cur_sb[nvlist->level] = sbuf_new_auto();
+	if (nvlist->cur_sb[nvlist->level] == NULL)
+		err(1, "%s: Unable to allocate sbuf", __func__);
+
+	if (strcmp(name, "connection") == 0) {
+		if (cur_conn != NULL)
+			errx(1, "%s: improper connection element nesting",
+			    __func__);
+
+		cur_conn = calloc(1, sizeof(*cur_conn));
+		if (cur_conn == NULL)
+			err(1, "%s: cannot allocate %zd bytes", __func__,
+			    sizeof(*cur_conn));
+
+		nvlist->num_conns++;
+		nvlist->cur_conn = cur_conn;
+
+		STAILQ_INSERT_TAIL(&nvlist->conn_list, cur_conn, links);
+
+		for (i = 0; attr[i] != NULL; i += 2) {
+			if (strcmp(attr[i], "id") == 0) {
+				cur_conn->connection_id =
+				    strtoull(attr[i+1], NULL, 0);
+			} else {
+				errx(1,
+				    "%s: invalid connection attribute %s = %s",
+				     __func__, attr[i], attr[i+1]);
+			}
+		}
+	}
+}
+
+static void
+cctl_nvlist_end_element(void *user_data, const char *name)
+{
+	struct cctl_nvlist_data *nvlist;
+	struct cctl_nvlist_conn *cur_conn;
+	char *str;
+
+	nvlist = (struct cctl_nvlist_data *)user_data;
+	cur_conn = nvlist->cur_conn;
+
+	if ((cur_conn == NULL) && (strcmp(name, "ctlnvmflist") != 0))
+		errx(1, "%s: cur_conn == NULL! (name = %s)", __func__, name);
+
+	if (nvlist->cur_sb[nvlist->level] == NULL)
+		errx(1, "%s: no valid sbuf at level %d (name %s)", __func__,
+		     nvlist->level, name);
+
+	sbuf_finish(nvlist->cur_sb[nvlist->level]);
+	str = strdup(sbuf_data(nvlist->cur_sb[nvlist->level]));
+	if (str == NULL)
+		err(1, "%s can't allocate %zd bytes for string", __func__,
+		    sbuf_len(nvlist->cur_sb[nvlist->level]));
+
+	sbuf_delete(nvlist->cur_sb[nvlist->level]);
+	nvlist->cur_sb[nvlist->level] = NULL;
+	nvlist->level--;
+
+	if (strcmp(name, "hostnqn") == 0) {
+		cur_conn->hostnqn = str;
+		str = NULL;
+	} else if (strcmp(name, "subnqn") == 0) {
+		cur_conn->subnqn = str;
+		str = NULL;
+	} else if (strcmp(name, "trtype") == 0) {
+		cur_conn->trtype = atoi(str);
+		str = NULL;
+	} else if (strcmp(name, "connection") == 0) {
+		nvlist->cur_conn = NULL;
+	} else if (strcmp(name, "ctlnvmflist") == 0) {
+		/* Nothing. */
+	} else {
+		/*
+		 * Unknown element; ignore it for forward compatibility.
+		 */
+	}
+
+	free(str);
+}
+
+static void
+cctl_nvlist_char_handler(void *user_data, const XML_Char *str, int len)
+{
+	struct cctl_nvlist_data *nvlist;
+
+	nvlist = (struct cctl_nvlist_data *)user_data;
+
+	sbuf_bcat(nvlist->cur_sb[nvlist->level], str, len);
+}
+
+static const char *
+nvmf_transport_descr(u_int trtype)
+{
+	static char buf[16];
+
+	switch (trtype) {
+	case NVMF_TRTYPE_RDMA:
+		return ("RDMA");
+	case NVMF_TRTYPE_FC:
+		return ("Fibre Channel");
+	case NVMF_TRTYPE_TCP:
+		return ("TCP");
+	default:
+		snprintf(buf, sizeof(buf), "%#x", trtype);
+		return (buf);
+	}
+}
+
+static int
+cctl_nvlist(int fd, int argc, char **argv, char *combinedopt)
+{
+	struct ctl_nvmf req;
+	struct cctl_nvlist_data nvlist;
+	struct cctl_nvlist_conn *conn;
+	XML_Parser parser;
+	char *conn_str;
+	int conn_len;
+	int dump_xml = 0;
+	int c, retval, verbose = 0;
+
+	retval = 0;
+	conn_len = 4096;
+
+	bzero(&nvlist, sizeof(nvlist));
+	STAILQ_INIT(&nvlist.conn_list);
+
+	while ((c = getopt(argc, argv, combinedopt)) != -1) {
+		switch (c) {
+		case 'v':
+			verbose = 1;
+			break;
+		case 'x':
+			dump_xml = 1;
+			break;
+		default:
+			break;
+		}
+	}
+
+retry:
+	conn_str = malloc(conn_len);
+
+	bzero(&req, sizeof(req));
+	req.type = CTL_NVMF_LIST;
+	req.data.list.alloc_len = conn_len;
+	req.data.list.conn_xml = conn_str;
+
+	if (ioctl(fd, CTL_NVMF, &req) == -1) {
+		warn("%s: error issuing CTL_NVMF ioctl", __func__);
+		retval = 1;
+		goto bailout;
+	}
+
+	if (req.status == CTL_NVMF_ERROR) {
+		warnx("%s: error returned from CTL_NVMF ioctl:\n%s",
+		      __func__, req.error_str);
+	} else if (req.status == CTL_NVMF_LIST_NEED_MORE_SPACE) {
+		conn_len = conn_len << 1;
+		goto retry;
+	}
+
+	if (dump_xml != 0) {
+		printf("%s", conn_str);
+		goto bailout;
+	}
+
+	parser = XML_ParserCreate(NULL);
+	if (parser == NULL) {
+		warn("%s: Unable to create XML parser", __func__);
+		retval = 1;
+		goto bailout;
+	}
+
+	XML_SetUserData(parser, &nvlist);
+	XML_SetElementHandler(parser, cctl_nvlist_start_element,
+	    cctl_nvlist_end_element);
+	XML_SetCharacterDataHandler(parser, cctl_nvlist_char_handler);
+
+	retval = XML_Parse(parser, conn_str, strlen(conn_str), 1);
+	if (retval != 1) {
+		warnx("%s: Unable to parse XML: Error %d", __func__,
+		    XML_GetErrorCode(parser));
+		XML_ParserFree(parser);
+		retval = 1;
+		goto bailout;
+	}
+	retval = 0;
+	XML_ParserFree(parser);
+
+	if (verbose != 0) {
+		STAILQ_FOREACH(conn, &nvlist.conn_list, links) {
+			printf("%-25s %d\n", "Controller ID:", conn->connection_id);
+			printf("%-25s %s\n", "Host NQN:", conn->hostnqn);
+			printf("%-25s %s\n", "Subsystem NQN:", conn->subnqn);
+			printf("%-25s %s\n", "Transport:",
+			    nvmf_transport_descr(conn->trtype));
+			printf("\n");
+		}
+	} else {
+		printf("%4s %-16s %-36s %-36s\n", "ID", "Transport", "HostNQN",
+		    "SubNQN");
+		STAILQ_FOREACH(conn, &nvlist.conn_list, links) {
+			printf("%4u %-16s %-36s %-36s\n",
+			    conn->connection_id,
+			    nvmf_transport_descr(conn->trtype),
+			    conn->hostnqn, conn->subnqn);
+		}
+	}
+bailout:
+	free(conn_str);
+
+	return (retval);
+}
+
 void
 usage(int error)
 {
@@ -3864,6 +4112,7 @@ usage(int error)
 "         ctladm islist      [-v | -x]\n"
 "         ctladm islogout    <-a | -c connection-id | -i name | -p portal>\n"
 "         ctladm isterminate <-a | -c connection-id | -i name | -p portal>\n"
+"         ctladm nvlist      [-v | -x]\n"
 "         ctladm dumpooa\n"
 "         ctladm dumpstructs\n"
 "         ctladm help\n"
@@ -4260,6 +4509,9 @@ main(int argc, char **argv)
 	case CTLADM_CMD_ISTERMINATE:
 	        retval = cctl_isterminate(fd, argc, argv, combinedopt);
 		break;
+	case CTLADM_CMD_NVLIST:
+	        retval = cctl_nvlist(fd, argc, argv, combinedopt);
+		break;
 	case CTLADM_CMD_HELP:
 	default:
 		usage(retval);