ctladm: Add nvlist command to list active NVMeoF associations

Reviewed by:	imp
Sponsored by:	Chelsio Communications
Differential Revision:	https://reviews.freebsd.org/D44728
This commit is contained in:
John Baldwin 2024-05-02 16:35:02 -07:00
parent 5fd68977a5
commit 4f9fa31c5c
2 changed files with 269 additions and 2 deletions

View file

@ -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

View file

@ -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);