[PATCH nvme-cli v2] nvme: Introduce new 'list-subsys' command
Johannes Thumshirn
jthumshirn at suse.de
Wed Dec 13 06:34:17 PST 2017
Introduce a 'nvme list-subsys' command to give basic information about
connected NVMe subsystems.
Here's an example output of a host connected to two subsystems on the
target with two paths to each subsystem:
root at host# nvme list-subsys
nvme-subsys0 - NQN=nvmf-test
\
+- nvme0 rdma traddr=1.1.1.3 trsvcid=4420 host_traddr=1.1.1.1
+- nvme1 rdma traddr=1.1.1.3 trsvcid=4420 host_traddr=1.1.1.2
nvme-subsys1 - NQN=nvmf-test2
\
+- nvme2 rdma traddr=1.1.1.3 trsvcid=4420 host_traddr=1.1.1.2
+- nvme3 rdma traddr=1.1.1.3 trsvcid=4420 host_traddr=1.1.1.1
Signed-off-by: Johannes Thumshirn <jthumshirn at suse.de>
---
Changes to v1:
- Fix manpage build
- Check return of asprintf()
---
Documentation/nvme-list-subsys.txt | 81 +++++++++
completions/bash-nvme-completion.sh | 2 +-
nvme-builtin.h | 1 +
nvme-print.c | 49 ++++++
nvme-print.h | 1 +
nvme.c | 340 ++++++++++++++++++++++++++++++++++++
nvme.h | 13 ++
7 files changed, 486 insertions(+), 1 deletion(-)
create mode 100644 Documentation/nvme-list-subsys.txt
diff --git a/Documentation/nvme-list-subsys.txt b/Documentation/nvme-list-subsys.txt
new file mode 100644
index 000000000000..c7de7efba654
--- /dev/null
+++ b/Documentation/nvme-list-subsys.txt
@@ -0,0 +1,81 @@
+nvme-list-subsys(1)
+===================
+
+NAME
+----
+nvme-list-subsys - List all NVMe subsystems
+
+SYNOPSIS
+--------
+[verse]
+'nvme list-subsys' [-o <fmt> | --output-format=<fmt>]
+
+DESCRIPTION
+-----------
+Scan the sysfs tree for NVM Express subsystems and return the controllers
+for those subsystems as well as some pertinent information about them.
+
+OPTIONS
+-------
+-o <format>::
+--output-format=<format>::
+ Set the reporting format to 'normal' or 'json'. Only one output
+ format can be used at a time.
+
+EXAMPLES
+--------
+root at host# nvme list-subsys
+nvme-subsys0 - NQN=nvmf-test
+\
+ +- nvme0 rdma traddr=1.1.1.3 trsvcid=4420 host_traddr=1.1.1.1
+ +- nvme1 rdma traddr=1.1.1.3 trsvcid=4420 host_traddr=1.1.1.2
+nvme-subsys1 - NQN=nvmf-test2
+\
+ +- nvme2 rdma traddr=1.1.1.3 trsvcid=4420 host_traddr=1.1.1.2
+ +- nvme3 rdma traddr=1.1.1.3 trsvcid=4420 host_traddr=1.1.1.1
+
+root at host# nvme list-subsys -o json
+{
+ "Subsystems" : [
+ {
+ "Name" : "nvme-subsys0",
+ "NQN" : "nvmf-test"
+ },
+ {
+ "Paths" : [
+ {
+ "Name" : "nvme0",
+ "Transport" : "rdma",
+ "Address" : "traddr=1.1.1.3 trsvcid=4420 host_traddr=1.1.1.1"
+ },
+ {
+ "Name" : "nvme1",
+ "Transport" : "rdma",
+ "Address" : "traddr=1.1.1.3 trsvcid=4420 host_traddr=1.1.1.2"
+ }
+ ]
+ },
+ {
+ "Name" : "nvme-subsys1",
+ "NQN" : "nvmf-test2"
+ },
+ {
+ "Paths" : [
+ {
+ "Name" : "nvme2",
+ "Transport" : "rdma",
+ "Address" : "traddr=1.1.1.3 trsvcid=4420 host_traddr=1.1.1.2"
+ },
+ {
+ "Name" : "nvme3",
+ "Transport" : "rdma",
+ "Address" : "traddr=1.1.1.3 trsvcid=4420 host_traddr=1.1.1.1"
+ }
+ ]
+ }
+ ]
+}
+
+NVME
+----
+Part of the nvme-user suite
diff --git a/completions/bash-nvme-completion.sh b/completions/bash-nvme-completion.sh
index da16f0e2b145..5abc30e8086a 100644
--- a/completions/bash-nvme-completion.sh
+++ b/completions/bash-nvme-completion.sh
@@ -11,7 +11,7 @@ _cmds="list id-ctrl id-ns list-ns create-ns delete-ns \
resv-report dsm flush compare read write write-zeroes \
write-uncor reset subsystem-reset show-regs discover \
connect-all connect disconnect version help \
- intel lnvm memblaze"
+ intel lnvm memblaze list-subsys"
nvme_list_opts () {
local opts=""
diff --git a/nvme-builtin.h b/nvme-builtin.h
index 1a5ab361e9f3..23e8dd9ebcda 100644
--- a/nvme-builtin.h
+++ b/nvme-builtin.h
@@ -56,6 +56,7 @@ COMMAND_LIST(
ENTRY("gen-hostnqn", "Generate NVMeoF host NQN", gen_hostnqn_cmd)
ENTRY("dir-receive", "Submit a Directive Receive command, return results", dir_receive)
ENTRY("dir-send", "Submit a Directive Send command, return results", dir_send)
+ ENTRY("list-subsys", "List nvme subsystems", list_subsys)
);
#endif
diff --git a/nvme-print.c b/nvme-print.c
index d50c3b6084fb..81fa4f50ec9f 100644
--- a/nvme-print.c
+++ b/nvme-print.c
@@ -1867,6 +1867,55 @@ void json_smart_log(struct nvme_smart_log *smart, unsigned int nsid, const char
json_free_object(root);
}
+void json_print_nvme_subsystem_list(struct subsys_list_item *slist, int n)
+{
+ struct json_object *root;
+ struct json_array *subsystems;
+ struct json_object *subsystem_attrs;
+ struct json_array *paths;
+ struct json_object *path_attrs;
+ struct json_object *path_object;
+ int i, j;
+
+ root = json_create_object();
+ subsystems = json_create_array();
+
+ for (i = 0; i < n; i++) {
+ subsystem_attrs = json_create_object();
+
+ json_object_add_value_string(subsystem_attrs,
+ "Name", slist[i].name);
+ json_object_add_value_string(subsystem_attrs,
+ "NQN", slist[i].subsysnqn);
+
+ json_array_add_value_object(subsystems, subsystem_attrs);
+
+ paths = json_create_array();
+ path_object = json_create_object();
+
+ for (j = 0; j < slist[i].nctrls; j++) {
+ path_attrs = json_create_object();
+ json_object_add_value_string(path_attrs, "Name",
+ slist[i].ctrls[j].name);
+ json_object_add_value_string(path_attrs, "Transport",
+ slist[i].ctrls[j].transport);
+ json_object_add_value_string(path_attrs, "Address",
+ slist[i].ctrls[j].address);
+ json_array_add_value_object(paths, path_attrs);
+ }
+ if (j) {
+ json_object_add_value_array(path_object, "Paths",
+ paths);
+ json_array_add_value_object(subsystems, path_object);
+ }
+
+ }
+
+ if (i)
+ json_object_add_value_array(root, "Subsystems", subsystems);
+ json_print_object(root, NULL);
+}
+
void show_registers_cap(struct nvme_bar_cap *cap)
{
printf("\tMemory Page Size Maximum (MPSMAX): %u bytes\n", 1 << (12 + ((cap->mpsmax_mpsmin & 0xf0) >> 4)));
diff --git a/nvme-print.h b/nvme-print.h
index d110b980c461..64bdb7edcbeb 100644
--- a/nvme-print.h
+++ b/nvme-print.h
@@ -43,6 +43,7 @@ void json_smart_log(struct nvme_smart_log *smart, unsigned int nsid, const char
void json_fw_log(struct nvme_firmware_log_page *fw_log, const char *devname);
void json_print_list_items(struct list_item *items, unsigned amnt);
void json_nvme_id_ns_descs(void *data);
+void json_print_nvme_subsystem_list(struct subsys_list_item *slist, int n);
#endif
diff --git a/nvme.c b/nvme.c
index 7830ea2de4ac..323aca65ee1e 100644
--- a/nvme.c
+++ b/nvme.c
@@ -826,6 +826,346 @@ static void *get_registers(void)
return membase;
}
+static const char *subsys_dir = "/sys/class/nvme-subsystem/";
+
+static char *get_nvme_subsnqn(char *path)
+{
+ char sspath[319];
+ char *subsysnqn;
+ int fd;
+ int ret;
+
+ snprintf(sspath, sizeof(sspath), "%s/subsysnqn", path);
+
+ fd = open(sspath, O_RDONLY);
+ if (fd < 0)
+ return NULL;
+
+ subsysnqn = calloc(1, 256);
+ if (!subsysnqn)
+ goto close_fd;
+
+ ret = read(fd, subsysnqn, 256);
+ if (ret < 0) {
+ free(subsysnqn);
+ subsysnqn = NULL;
+ }
+
+ if (subsysnqn[strlen(subsysnqn) - 1] == '\n')
+ subsysnqn[strlen(subsysnqn) - 1] = '\0';
+
+close_fd:
+ close(fd);
+
+ return subsysnqn;
+}
+
+static char *get_nvme_ctrl_transport(char *path)
+{
+ char *trpath;
+ char *transport;
+ int fd;
+ ssize_t ret;
+
+ ret = asprintf(&trpath, "%s/transport", path);
+ if (ret < 0)
+ return NULL;
+
+ transport = calloc(1, 1024);
+ if (!transport)
+ goto err_free_trpath;
+
+ fd = open(trpath, O_RDONLY);
+ if (fd < 0)
+ goto err_free_tr;
+
+ ret = read(fd, transport, 1024);
+ if (ret < 0)
+ goto err_close_fd;
+
+ if (transport[strlen(transport) - 1] == '\n')
+ transport[strlen(transport) - 1] = '\0';
+
+ close(fd);
+ free(trpath);
+
+ return transport;
+
+err_close_fd:
+ close(fd);
+err_free_tr:
+ free(transport);
+err_free_trpath:
+ free(trpath);
+
+ return NULL;
+}
+
+static char *get_nvme_ctrl_address(char *path)
+{
+ char *addrpath;
+ char *address;
+ int fd;
+ ssize_t ret;
+ int i;
+
+ ret = asprintf(&addrpath, "%s/address", path);
+ if (ret < 0)
+ return NULL;
+
+ address = calloc(1, 1024);
+ if (!address)
+ goto err_free_addrpath;
+
+ fd = open(addrpath, O_RDONLY);
+ if (fd < 0)
+ goto err_free_addr;
+
+ ret = read(fd, address, 1024);
+ if (ret < 0)
+ goto err_close_fd;
+
+ if (address[strlen(address) - 1] == '\n')
+ address[strlen(address) - 1] = '\0';
+
+ for (i = 0; i < strlen(address); i++) {
+ if (address[i] == ',' )
+ address[i] = ' ';
+ }
+
+ close(fd);
+ free(addrpath);
+
+ return address;
+
+err_close_fd:
+ close(fd);
+err_free_addr:
+ free(address);
+err_free_addrpath:
+ free(addrpath);
+
+ return NULL;
+}
+static int scan_ctrls_filter(const struct dirent *d)
+{
+ int id, nsid;
+
+ if (d->d_name[0] == '.')
+ return 0;
+
+ if (strstr(d->d_name, "nvme")) {
+ if (sscanf(d->d_name, "nvme%dn%d", &id, &nsid) == 2)
+ return 0;
+ return 1;
+ }
+
+ return 0;
+}
+
+void print_nvme_subsystem(struct subsys_list_item *item)
+{
+ int i;
+
+ printf("%s - NQN=%s\n", item->name, item->subsysnqn);
+ printf("\\\n");
+
+ for (i = 0; i < item->nctrls; i++) {
+ printf(" +- %s %s %s\n", item->ctrls[i].name,
+ item->ctrls[i].transport,
+ item->ctrls[i].address);
+ }
+
+}
+
+void print_nvme_subsystem_list(struct subsys_list_item *slist, int n)
+{
+ int i;
+
+ for (i = 0; i < n; i++)
+ print_nvme_subsystem(&slist[i]);
+}
+
+int get_nvme_subsystem_info(char *name, char *path,
+ struct subsys_list_item *item)
+{
+ char ctrl_path[512];
+ struct dirent **ctrls;
+ int n, i;
+
+ item->subsysnqn = get_nvme_subsnqn(path);
+ if (!item->subsysnqn)
+ return 1;
+
+ item->name = strdup(name);
+
+ n = scandir(path, &ctrls, scan_ctrls_filter, alphasort);
+ if (n < 0)
+ goto free_subysynqn;
+
+ item->ctrls = calloc(n, sizeof(struct ctrl_list_item));
+ if (!item->ctrls)
+ goto free_ctrls;
+
+ item->nctrls = n;
+
+ for (i = 0; i < n; i++) {
+ item->ctrls[i].name = strdup(ctrls[i]->d_name);
+
+ snprintf(ctrl_path, sizeof(ctrl_path), "%s/%s", path,
+ item->ctrls[i].name);
+
+ item->ctrls[i].address = get_nvme_ctrl_address(ctrl_path);
+ if (!item->ctrls[i].address) {
+ free(item->ctrls[i].name);
+ goto free_ctrl_list;
+ }
+
+ item->ctrls[i].transport = get_nvme_ctrl_transport(ctrl_path);
+ if (!item->ctrls[i].transport) {
+ free(item->ctrls[i].name);
+ free(item->ctrls[i].address);
+ goto free_ctrl_list;
+ }
+ }
+
+ for (i = 0; i < n; i++)
+ free(ctrls[i]);
+ free(ctrls);
+
+ return 0;
+
+free_ctrl_list:
+ free(item->ctrls);
+
+free_ctrls:
+ for (i = 0; i < n; i++)
+ free(ctrls[i]);
+ free(ctrls);
+
+free_subysynqn:
+ free(item->subsysnqn);
+ free(item->name);
+
+ return 1;
+}
+
+static int scan_subsys_filter(const struct dirent *d)
+{
+ char path[310];
+ struct stat ss;
+ int id;
+ int tmp;
+
+ if (d->d_name[0] == '.')
+ return 0;
+
+ /* sanity checking, probably unneeded */
+ if (strstr(d->d_name, "nvme-subsys")) {
+ snprintf(path, sizeof(path), "%s%s", subsys_dir, d->d_name);
+ if (stat(path, &ss))
+ return 0;
+ if (!S_ISDIR(ss.st_mode))
+ return 0;
+ tmp = sscanf(d->d_name, "nvme-subsys%d", &id);
+ if (tmp != 1)
+ return 0;
+ return 1;
+ }
+
+ return 0;
+}
+
+static void free_subsys_list_item(struct subsys_list_item *item)
+{
+ int i;
+
+ for (i = 0; i < item->nctrls; i++) {
+ free(item->ctrls[i].name);
+ free(item->ctrls[i].transport);
+ free(item->ctrls[i].address);
+ }
+
+ free(item->ctrls);
+ free(item->subsysnqn);
+ free(item->name);
+}
+
+static void free_subsys_list(struct subsys_list_item *slist, int n)
+{
+ int i;
+
+ for (i = 0; i < n; i++)
+ free_subsys_list_item(&slist[i]);
+
+ free(slist);
+}
+
+static int list_subsys(int argc, char **argv, struct command *cmd,
+ struct plugin *plugin)
+{
+ char path[310];
+ struct dirent **subsys;
+ struct subsys_list_item *slist;
+ int fmt, n, i, ret = 0;
+ const char *desc = "Retrieve information for subsystems";
+ struct config {
+ char *output_format;
+ };
+
+ struct config cfg = {
+ .output_format = "normal",
+ };
+
+ const struct argconfig_commandline_options opts[] = {
+ {"output-format", 'o', "FMT", CFG_STRING, &cfg.output_format,
+ required_argument, "Output Format: normal|json"},
+ {NULL}
+ };
+
+ ret = argconfig_parse(argc, argv, desc, opts, &cfg, sizeof(cfg));
+ if (ret < 0)
+ return ret;
+
+ fmt = validate_output_format(cfg.output_format);
+
+ if (fmt != JSON && fmt != NORMAL)
+ return -EINVAL;
+ n = scandir(subsys_dir, &subsys, scan_subsys_filter, alphasort);
+ if (n < 0) {
+ fprintf(stderr, "no NVMe subsystem(s) detected.\n");
+ return n;
+ }
+
+ slist = calloc(n, sizeof(struct subsys_list_item));
+ if (!slist) {
+ ret = ENOMEM;
+ goto free_subsys;
+ }
+
+ for (i = 0; i < n; i++) {
+ snprintf(path, sizeof(path), "%s%s", subsys_dir,
+ subsys[i]->d_name);
+ ret = get_nvme_subsystem_info(subsys[i]->d_name, path, &slist[i]);
+ if (ret)
+ goto free_subsys;
+ }
+
+ if (fmt == JSON)
+ json_print_nvme_subsystem_list(slist, n);
+ else
+ print_nvme_subsystem_list(slist, n);
+
+free_subsys:
+ free_subsys_list(slist, n);
+
+ for (i = 0; i < n; i++)
+ free(subsys[i]);
+ free(subsys);
+
+ return ret;
+}
+
static void print_list_item(struct list_item list_item)
{
long long int lba = 1 << list_item.ns.lbaf[(list_item.ns.flbas & 0x0f)].ds;
diff --git a/nvme.h b/nvme.h
index ec68f1748128..b134be1e251c 100644
--- a/nvme.h
+++ b/nvme.h
@@ -117,6 +117,19 @@ struct list_item {
unsigned block;
};
+struct ctrl_list_item {
+ char *name;
+ char *address;
+ char *transport;
+};
+
+struct subsys_list_item {
+ char *name;
+ char *subsysnqn;
+ int nctrls;
+ struct ctrl_list_item *ctrls;
+};
+
enum {
NORMAL,
JSON,
--
2.13.6
More information about the Linux-nvme
mailing list