]> www.infradead.org Git - users/sagi/nvme-cli.git/commitdiff
nvme: Introduce new 'list-subsys' command
authorJohannes Thumshirn <jthumshirn@suse.de>
Wed, 13 Dec 2017 14:34:17 +0000 (15:34 +0100)
committerKeith Busch <keith.busch@intel.com>
Wed, 13 Dec 2017 15:23:17 +0000 (08:23 -0700)
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@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@suse.de>
Documentation/nvme-list-subsys.txt [new file with mode: 0644]
completions/bash-nvme-completion.sh
nvme-builtin.h
nvme-print.c
nvme-print.h
nvme.c
nvme.h

diff --git a/Documentation/nvme-list-subsys.txt b/Documentation/nvme-list-subsys.txt
new file mode 100644 (file)
index 0000000..c7de7ef
--- /dev/null
@@ -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@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@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
index da16f0e2b14594d920b81344f0bdf131662b574f..5abc30e8086a16f128ad5f5e1a61d3e4fcb5968f 100644 (file)
@@ -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=""
index 1a5ab361e9f30f40397672272735b11ca731f86e..23e8dd9ebcdaa3171404a86871ed950a5dfd07ff 100644 (file)
@@ -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
index 7eb7e6c6c196c6e499ce4e07bc172a3f9c8b343c..73c6f0793580328e62811c72d143e3c49d4fc42a 100644 (file)
@@ -1881,6 +1881,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)));
index d110b980c461075aee021edcf9eaffb7a7bb32d8..64bdb7edcbeb0ec40533858ba3a9869eb9930c74 100644 (file)
@@ -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 6912837afe70830d160bd1357578617a98fcefe4..a948160ab537fbca7dc45b7d555ddbdbf1496cc0 100644 (file)
--- 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 ec68f1748128993137715f3e81cb97f2702212ad..b134be1e251c3a5b82bf121ea60e16c9591df60b 100644 (file)
--- 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,