summaryrefslogtreecommitdiffstats
path: root/fabrics.c
diff options
context:
space:
mode:
Diffstat (limited to 'fabrics.c')
-rw-r--r--fabrics.c1448
1 files changed, 1448 insertions, 0 deletions
diff --git a/fabrics.c b/fabrics.c
new file mode 100644
index 0000000..1a53b23
--- /dev/null
+++ b/fabrics.c
@@ -0,0 +1,1448 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (C) 2016 Intel Corporation. All rights reserved.
+ * Copyright (c) 2016 HGST, a Western Digital Company.
+ * Copyright (c) 2016 Samsung Electronics Co., Ltd.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License version
+ * 2 as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ *
+ * This file implements the discovery controller feature of NVMe over
+ * Fabrics specification standard.
+ */
+
+#include <errno.h>
+#include <getopt.h>
+#include <fcntl.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <stdbool.h>
+#include <stdint.h>
+#include <unistd.h>
+#include <dirent.h>
+#include <inttypes.h>
+#include <libgen.h>
+#include <sys/stat.h>
+#include <stddef.h>
+#include <syslog.h>
+#include <time.h>
+
+#include <sys/types.h>
+#include <linux/types.h>
+
+#include "common.h"
+#include "nvme.h"
+#include "nbft.h"
+#include "libnvme.h"
+#include "nvme-print.h"
+#include "fabrics.h"
+
+#define PATH_NVMF_DISC SYSCONFDIR "/nvme/discovery.conf"
+#define PATH_NVMF_CONFIG SYSCONFDIR "/nvme/config.json"
+#define PATH_NVMF_RUNDIR RUNDIR "/nvme"
+#define MAX_DISC_ARGS 32
+#define MAX_DISC_RETRIES 10
+
+#define NVMF_DEF_DISC_TMO 30
+
+/* Name of file to output log pages in their raw format */
+static char *raw;
+static bool persistent;
+static bool quiet;
+static bool dump_config;
+
+static const char *nvmf_tport = "transport type";
+static const char *nvmf_traddr = "transport address";
+static const char *nvmf_nqn = "subsystem nqn";
+static const char *nvmf_trsvcid = "transport service id (e.g. IP port)";
+static const char *nvmf_htraddr = "host traddr (e.g. FC WWN's)";
+static const char *nvmf_hiface = "host interface (for tcp transport)";
+static const char *nvmf_hostnqn = "user-defined hostnqn";
+static const char *nvmf_hostid = "user-defined hostid (if default not used)";
+static const char *nvmf_hostkey = "user-defined dhchap key (if default not used)";
+static const char *nvmf_ctrlkey = "user-defined dhchap controller key (for bi-directional authentication)";
+static const char *nvmf_nr_io_queues = "number of io queues to use (default is core count)";
+static const char *nvmf_nr_write_queues = "number of write queues to use (default 0)";
+static const char *nvmf_nr_poll_queues = "number of poll queues to use (default 0)";
+static const char *nvmf_queue_size = "number of io queue elements to use (default 128)";
+static const char *nvmf_keep_alive_tmo = "keep alive timeout period in seconds";
+static const char *nvmf_reconnect_delay = "reconnect timeout period in seconds";
+static const char *nvmf_ctrl_loss_tmo = "controller loss timeout period in seconds";
+static const char *nvmf_tos = "type of service";
+static const char *nvmf_keyring = "Keyring for TLS key lookup";
+static const char *nvmf_tls_key = "TLS key to use";
+static const char *nvmf_dup_connect = "allow duplicate connections between same transport host and subsystem port";
+static const char *nvmf_disable_sqflow = "disable controller sq flow control (default false)";
+static const char *nvmf_hdr_digest = "enable transport protocol header digest (TCP transport)";
+static const char *nvmf_data_digest = "enable transport protocol data digest (TCP transport)";
+static const char *nvmf_tls = "enable TLS";
+static const char *nvmf_concat = "enable secure concatenation";
+static const char *nvmf_config_file = "Use specified JSON configuration file or 'none' to disable";
+static const char *nvmf_context = "execution context identification string";
+
+#define NVMF_ARGS(n, c, ...) \
+ struct argconfig_commandline_options n[] = { \
+ OPT_STRING("transport", 't', "STR", &transport, nvmf_tport), \
+ OPT_STRING("nqn", 'n', "STR", &subsysnqn, nvmf_nqn), \
+ OPT_STRING("traddr", 'a', "STR", &traddr, nvmf_traddr), \
+ OPT_STRING("trsvcid", 's', "STR", &trsvcid, nvmf_trsvcid), \
+ OPT_STRING("host-traddr", 'w', "STR", &c.host_traddr, nvmf_htraddr), \
+ OPT_STRING("host-iface", 'f', "STR", &c.host_iface, nvmf_hiface), \
+ OPT_STRING("hostnqn", 'q', "STR", &hostnqn, nvmf_hostnqn), \
+ OPT_STRING("hostid", 'I', "STR", &hostid, nvmf_hostid), \
+ OPT_STRING("dhchap-secret", 'S', "STR", &hostkey, nvmf_hostkey), \
+ OPT_INT("nr-io-queues", 'i', &c.nr_io_queues, nvmf_nr_io_queues), \
+ OPT_INT("nr-write-queues", 'W', &c.nr_write_queues, nvmf_nr_write_queues), \
+ OPT_INT("nr-poll-queues", 'P', &c.nr_poll_queues, nvmf_nr_poll_queues), \
+ OPT_INT("queue-size", 'Q', &c.queue_size, nvmf_queue_size), \
+ OPT_INT("keep-alive-tmo", 'k', &c.keep_alive_tmo, nvmf_keep_alive_tmo), \
+ OPT_INT("reconnect-delay", 'c', &c.reconnect_delay, nvmf_reconnect_delay), \
+ OPT_INT("ctrl-loss-tmo", 'l', &c.ctrl_loss_tmo, nvmf_ctrl_loss_tmo), \
+ OPT_INT("tos", 'T', &c.tos, nvmf_tos), \
+ OPT_INT("keyring", 0, &c.keyring, nvmf_keyring), \
+ OPT_INT("tls_key", 0, &c.tls_key, nvmf_tls_key), \
+ OPT_FLAG("duplicate-connect", 'D', &c.duplicate_connect, nvmf_dup_connect), \
+ OPT_FLAG("disable-sqflow", 'd', &c.disable_sqflow, nvmf_disable_sqflow), \
+ OPT_FLAG("hdr-digest", 'g', &c.hdr_digest, nvmf_hdr_digest), \
+ OPT_FLAG("data-digest", 'G', &c.data_digest, nvmf_data_digest), \
+ OPT_FLAG("tls", 0, &c.tls, nvmf_tls), \
+ OPT_FLAG("concat", 0, &c.concat, nvmf_concat), \
+ __VA_ARGS__, \
+ OPT_END() \
+ }
+
+static bool is_persistent_discovery_ctrl(nvme_host_t h, nvme_ctrl_t c)
+{
+ if (nvme_host_is_pdc_enabled(h, DEFAULT_PDC_ENABLED))
+ return nvme_ctrl_is_unique_discovery_ctrl(c);
+
+ return false;
+}
+
+nvme_ctrl_t lookup_ctrl(nvme_host_t h, struct tr_config *trcfg)
+{
+ nvme_subsystem_t s;
+ nvme_ctrl_t c;
+
+ nvme_for_each_subsystem(h, s) {
+ c = nvme_ctrl_find(s,
+ trcfg->transport,
+ trcfg->traddr,
+ trcfg->trsvcid,
+ trcfg->subsysnqn,
+ trcfg->host_traddr,
+ trcfg->host_iface);
+ if (c)
+ return c;
+ }
+
+ return NULL;
+}
+
+static int set_discovery_kato(struct nvme_fabrics_config *cfg)
+{
+ int tmo = cfg->keep_alive_tmo;
+
+ /* Set kato to NVMF_DEF_DISC_TMO for persistent controllers */
+ if (persistent && !cfg->keep_alive_tmo)
+ cfg->keep_alive_tmo = NVMF_DEF_DISC_TMO;
+ /* Set kato to zero for non-persistent controllers */
+ else if (!persistent && (cfg->keep_alive_tmo > 0))
+ cfg->keep_alive_tmo = 0;
+
+ return tmo;
+}
+
+static nvme_ctrl_t __create_discover_ctrl(nvme_root_t r, nvme_host_t h,
+ struct nvme_fabrics_config *cfg,
+ struct tr_config *trcfg)
+{
+ nvme_ctrl_t c;
+ int tmo, ret;
+
+ c = nvme_create_ctrl(r, trcfg->subsysnqn, trcfg->transport,
+ trcfg->traddr, trcfg->host_traddr,
+ trcfg->host_iface, trcfg->trsvcid);
+ if (!c)
+ return NULL;
+
+ nvme_ctrl_set_discovery_ctrl(c, true);
+ nvme_ctrl_set_unique_discovery_ctrl(c,
+ strcmp(trcfg->subsysnqn, NVME_DISC_SUBSYS_NAME));
+ tmo = set_discovery_kato(cfg);
+
+ errno = 0;
+ ret = nvmf_add_ctrl(h, c, cfg);
+
+ cfg->keep_alive_tmo = tmo;
+ if (ret) {
+ nvme_free_ctrl(c);
+ return NULL;
+ }
+
+ return c;
+}
+
+static nvme_ctrl_t create_discover_ctrl(nvme_root_t r, nvme_host_t h,
+ struct nvme_fabrics_config *cfg,
+ struct tr_config *trcfg)
+{
+ nvme_ctrl_t c;
+
+ c = __create_discover_ctrl(r, h, cfg, trcfg);
+ if (!c)
+ return NULL;
+
+ if (nvme_ctrl_is_unique_discovery_ctrl(c))
+ return c;
+
+ /* Find out the name of discovery controller */
+ struct nvme_id_ctrl id = { 0 };
+
+ if (nvme_ctrl_identify(c, &id)) {
+ fprintf(stderr, "failed to identify controller, error %s\n",
+ nvme_strerror(errno));
+ nvme_disconnect_ctrl(c);
+ nvme_free_ctrl(c);
+ return NULL;
+ }
+
+ if (!strcmp(id.subnqn, NVME_DISC_SUBSYS_NAME))
+ return c;
+
+ /*
+ * The subsysnqn is not the well-known name. Prefer the unique
+ * subsysnqn over the well-known one.
+ */
+ nvme_disconnect_ctrl(c);
+ nvme_free_ctrl(c);
+
+ trcfg->subsysnqn = id.subnqn;
+ return __create_discover_ctrl(r, h, cfg, trcfg);
+}
+
+static void save_discovery_log(char *raw, struct nvmf_discovery_log *log)
+{
+ uint64_t numrec = le64_to_cpu(log->numrec);
+ int fd, len, ret;
+
+ fd = open(raw, O_CREAT | O_RDWR | O_TRUNC, 0600);
+ if (fd < 0) {
+ fprintf(stderr, "failed to open %s: %s\n", raw, strerror(errno));
+ return;
+ }
+
+ len = sizeof(struct nvmf_discovery_log) + numrec * sizeof(struct nvmf_disc_log_entry);
+
+ ret = write(fd, log, len);
+ if (ret < 0)
+ fprintf(stderr, "failed to write to %s: %s\n",
+ raw, strerror(errno));
+ else
+ printf("Discovery log is saved to %s\n", raw);
+
+ close(fd);
+}
+
+static int __discover(nvme_ctrl_t c, struct nvme_fabrics_config *defcfg,
+ char *raw, bool connect, bool persistent,
+ enum nvme_print_flags flags)
+{
+ struct nvmf_discovery_log *log = NULL;
+ nvme_subsystem_t s = nvme_ctrl_get_subsystem(c);
+ nvme_host_t h = nvme_subsystem_get_host(s);
+ uint64_t numrec;
+
+ struct nvme_get_discovery_args args = {
+ .c = c,
+ .args_size = sizeof(args),
+ .max_retries = MAX_DISC_RETRIES,
+ .result = 0,
+ .timeout = NVME_DEFAULT_IOCTL_TIMEOUT,
+ .lsp = 0,
+ };
+
+ log = nvmf_get_discovery_wargs(&args);
+ if (!log) {
+ fprintf(stderr, "failed to get discovery log: %s\n",
+ nvme_strerror(errno));
+ return -errno;
+ }
+
+ numrec = le64_to_cpu(log->numrec);
+ if (raw)
+ save_discovery_log(raw, log);
+ else if (!connect) {
+ nvme_show_discovery_log(log, numrec, flags);
+ } else if (connect) {
+ int i;
+
+ for (i = 0; i < numrec; i++) {
+ struct nvmf_disc_log_entry *e = &log->entries[i];
+ nvme_ctrl_t cl;
+ bool discover = false;
+ bool disconnect;
+ nvme_ctrl_t child;
+ int tmo = defcfg->keep_alive_tmo;
+
+ struct tr_config trcfg = {
+ .subsysnqn = e->subnqn,
+ .transport = nvmf_trtype_str(e->trtype),
+ .traddr = e->traddr,
+ .host_traddr = defcfg->host_traddr,
+ .host_iface = defcfg->host_iface,
+ .trsvcid = e->trsvcid,
+ };
+
+ /* Already connected ? */
+ cl = lookup_ctrl(h, &trcfg);
+ if (cl && nvme_ctrl_get_name(cl))
+ continue;
+
+ /* Skip connect if the transport types don't match */
+ if (strcmp(nvme_ctrl_get_transport(c),
+ nvmf_trtype_str(e->trtype)))
+ continue;
+
+ if (e->subtype == NVME_NQN_DISC ||
+ e->subtype == NVME_NQN_CURR) {
+ __u16 eflags = le16_to_cpu(e->eflags);
+ /*
+ * Does this discovery controller return the
+ * same information?
+ */
+ if (eflags & NVMF_DISC_EFLAGS_DUPRETINFO)
+ continue;
+
+ /* Are we supposed to keep the discovery controller around? */
+ disconnect = !persistent;
+
+ if (strcmp(e->subnqn, NVME_DISC_SUBSYS_NAME)) {
+ /*
+ * Does this discovery controller doesn't
+ * support explicit persistent connection?
+ */
+ if (!(eflags & NVMF_DISC_EFLAGS_EPCSD))
+ disconnect = true;
+ else
+ disconnect = false;
+ }
+
+ set_discovery_kato(defcfg);
+ } else {
+ /* NVME_NQN_NVME */
+ disconnect = false;
+ }
+
+ errno = 0;
+ child = nvmf_connect_disc_entry(h, e, defcfg,
+ &discover);
+
+ defcfg->keep_alive_tmo = tmo;
+
+ if (child) {
+ if (discover)
+ __discover(child, defcfg, raw,
+ true, persistent, flags);
+
+ if (disconnect) {
+ nvme_disconnect_ctrl(child);
+ nvme_free_ctrl(child);
+ }
+ } else if (errno == ENVME_CONNECT_ALREADY && !quiet) {
+ char *traddr = log->entries[i].traddr;
+
+ fprintf(stderr,
+ "traddr=%s is already connected\n",
+ traddr);
+ }
+ }
+ }
+
+ free(log);
+ return 0;
+}
+
+static char *get_default_trsvcid(const char *transport,
+ bool discovery_ctrl)
+{
+ if (!transport)
+ return NULL;
+ if (!strcmp(transport, "tcp")) {
+ if (discovery_ctrl)
+ /* Default port for NVMe/TCP discovery controllers */
+ return stringify(NVME_DISC_IP_PORT);
+ /* Default port for NVMe/TCP io controllers */
+ return stringify(NVME_RDMA_IP_PORT);
+ } else if (!strcmp(transport, "rdma")) {
+ /* Default port for NVMe/RDMA controllers */
+ return stringify(NVME_RDMA_IP_PORT);
+ }
+
+ return NULL;
+}
+
+static int discover_from_conf_file(nvme_root_t r, nvme_host_t h,
+ const char *desc, bool connect,
+ const struct nvme_fabrics_config *defcfg)
+{
+ char *transport = NULL, *traddr = NULL, *trsvcid = NULL;
+ char *hostnqn = NULL, *hostid = NULL, *hostkey = NULL;
+ char *subsysnqn = NULL;
+ char *ptr, **argv, *p, line[4096];
+ int argc, ret = 0;
+ unsigned int verbose = 0;
+ FILE *f;
+ enum nvme_print_flags flags;
+ char *format = "normal";
+ struct nvme_fabrics_config cfg;
+ bool force = false;
+
+ NVMF_ARGS(opts, cfg,
+ OPT_FMT("output-format", 'o', &format, output_format),
+ OPT_FILE("raw", 'r', &raw, "save raw output to file"),
+ OPT_FLAG("persistent", 'p', &persistent, "persistent discovery connection"),
+ OPT_FLAG("quiet", 'S', &quiet, "suppress already connected errors"),
+ OPT_INCR("verbose", 'v', &verbose, "Increase logging verbosity"),
+ OPT_FLAG("force", 0, &force, "Force persistent discovery controller creation"));
+
+ nvmf_default_config(&cfg);
+
+ ret = validate_output_format(format, &flags);
+ if (ret < 0) {
+ nvme_show_error("Invalid output format");
+ return ret;
+ }
+
+ f = fopen(PATH_NVMF_DISC, "r");
+ if (f == NULL) {
+ fprintf(stderr, "No params given and no %s\n", PATH_NVMF_DISC);
+ errno = ENOENT;
+ return -1;
+ }
+
+ argv = calloc(MAX_DISC_ARGS, sizeof(char *));
+ if (!argv) {
+ ret = -1;
+ goto out;
+ }
+
+ argv[0] = "discover";
+ memset(line, 0, sizeof(line));
+ while (fgets(line, sizeof(line), f) != NULL) {
+ nvme_ctrl_t c;
+
+ if (line[0] == '#' || line[0] == '\n')
+ continue;
+
+ argc = 1;
+ p = line;
+ while ((ptr = strsep(&p, " =\n")) != NULL)
+ argv[argc++] = ptr;
+ argv[argc] = NULL;
+
+ memcpy(&cfg, defcfg, sizeof(cfg));
+ subsysnqn = NVME_DISC_SUBSYS_NAME;
+ ret = argconfig_parse(argc, argv, desc, opts);
+ if (ret)
+ goto next;
+ if (!transport && !traddr)
+ goto next;
+
+ if (!trsvcid)
+ trsvcid = get_default_trsvcid(transport, true);
+
+ struct tr_config trcfg = {
+ .subsysnqn = subsysnqn,
+ .transport = transport,
+ .traddr = traddr,
+ .host_traddr = cfg.host_traddr,
+ .host_iface = cfg.host_iface,
+ .trsvcid = trsvcid,
+ };
+
+ if (!force) {
+ c = lookup_ctrl(h, &trcfg);
+ if (c) {
+ __discover(c, &cfg, raw, connect,
+ true, flags);
+ goto next;
+ }
+ }
+
+ c = create_discover_ctrl(r, h, &cfg, &trcfg);
+ if (!c)
+ goto next;
+
+ __discover(c, &cfg, raw, connect, persistent, flags);
+ if (!(persistent || is_persistent_discovery_ctrl(h, c)))
+ ret = nvme_disconnect_ctrl(c);
+ nvme_free_ctrl(c);
+
+next:
+ memset(&cfg, 0, sizeof(cfg));
+ }
+ free(argv);
+out:
+ fclose(f);
+ return ret;
+}
+
+static int discover_from_json_config_file(nvme_root_t r, nvme_host_t h,
+ const char *desc, bool connect,
+ const struct nvme_fabrics_config *defcfg,
+ enum nvme_print_flags flags,
+ bool force)
+{
+ const char *transport, *traddr, *host_traddr, *host_iface, *trsvcid, *subsysnqn;
+ nvme_subsystem_t s;
+ nvme_ctrl_t c, cn;
+ struct nvme_fabrics_config cfg;
+ int ret = 0;
+
+ nvme_for_each_subsystem(h, s) {
+ nvme_subsystem_for_each_ctrl(s, c) {
+ transport = nvme_ctrl_get_transport(c);
+ traddr = nvme_ctrl_get_traddr(c);
+ host_traddr = nvme_ctrl_get_host_traddr(c);
+ host_iface = nvme_ctrl_get_host_iface(c);
+
+ if (!transport && !traddr)
+ continue;
+
+ /* ignore none fabric transports */
+ if (strcmp(transport, "tcp") &&
+ strcmp(transport, "rdma") &&
+ strcmp(transport, "fc"))
+ continue;
+
+ /* ignore if no host_traddr for fc */
+ if (!strcmp(transport, "fc")) {
+ if (!host_traddr) {
+ fprintf(stderr, "host_traddr required for fc\n");
+ continue;
+ }
+ }
+
+ /* ignore if host_iface set for any transport other than tcp */
+ if (!strcmp(transport, "rdma") || !strcmp(transport, "fc")) {
+ if (host_iface) {
+ fprintf(stderr, "host_iface not permitted for rdma or fc\n");
+ continue;
+ }
+ }
+
+ trsvcid = nvme_ctrl_get_trsvcid(c);
+ if (!trsvcid || !strcmp(trsvcid, ""))
+ trsvcid = get_default_trsvcid(transport, true);
+
+ if (force)
+ subsysnqn = nvme_ctrl_get_subsysnqn(c);
+ else
+ subsysnqn = NVME_DISC_SUBSYS_NAME;
+
+ if (nvme_ctrl_is_persistent(c))
+ persistent = true;
+
+ memcpy(&cfg, defcfg, sizeof(cfg));
+
+ struct tr_config trcfg = {
+ .subsysnqn = subsysnqn,
+ .transport = transport,
+ .traddr = traddr,
+ .host_traddr = host_traddr,
+ .host_iface = host_iface,
+ .trsvcid = trsvcid,
+ };
+
+ if (!force) {
+ cn = lookup_ctrl(h, &trcfg);
+ if (cn) {
+ __discover(cn, &cfg, raw, connect,
+ true, flags);
+ continue;
+ }
+ }
+
+ cn = create_discover_ctrl(r, h, &cfg, &trcfg);
+ if (!cn)
+ continue;
+
+ __discover(cn, &cfg, raw, connect, persistent, flags);
+ if (!(persistent || is_persistent_discovery_ctrl(h, cn)))
+ ret = nvme_disconnect_ctrl(cn);
+ nvme_free_ctrl(cn);
+ }
+ }
+
+ return ret;
+}
+
+static int nvme_read_volatile_config(nvme_root_t r)
+{
+ char *filename, *ext;
+ struct dirent *dir;
+ DIR *d;
+ int ret = -ENOENT;
+
+ d = opendir(PATH_NVMF_RUNDIR);
+ if (!d)
+ return -ENOTDIR;
+
+ while ((dir = readdir(d))) {
+ if (dir->d_type != DT_REG)
+ continue;
+
+ ext = strchr(dir->d_name, '.');
+ if (!ext || strcmp("json", ext + 1))
+ continue;
+
+ if (asprintf(&filename, "%s/%s", PATH_NVMF_RUNDIR, dir->d_name) < 0) {
+ ret = -ENOMEM;
+ break;
+ }
+
+ if (nvme_read_config(r, filename))
+ ret = 0;
+
+ free(filename);
+ }
+ closedir(d);
+
+ return ret;
+}
+
+char *nvmf_hostid_from_hostnqn(const char *hostnqn)
+{
+ const char *uuid;
+
+ if (!hostnqn)
+ return NULL;
+
+ uuid = strstr(hostnqn, "uuid:");
+ if (!uuid)
+ return NULL;
+
+ return strdup(uuid + strlen("uuid:"));
+}
+
+void nvmf_check_hostid_and_hostnqn(const char *hostid, const char *hostnqn, unsigned int verbose)
+{
+ char *hostid_from_file, *hostid_from_hostnqn;
+
+ if (!hostid)
+ return;
+
+ hostid_from_file = nvmf_hostid_from_file();
+ if (hostid_from_file && strcmp(hostid_from_file, hostid)) {
+ if (verbose)
+ fprintf(stderr,
+ "warning: use generated hostid instead of hostid file\n");
+ free(hostid_from_file);
+ }
+
+ if (!hostnqn)
+ return;
+
+ hostid_from_hostnqn = nvmf_hostid_from_hostnqn(hostnqn);
+ if (hostid_from_hostnqn && strcmp(hostid_from_hostnqn, hostid)) {
+ if (verbose)
+ fprintf(stderr,
+ "warning: use hostid which does not match uuid in hostnqn\n");
+ free(hostid_from_hostnqn);
+ }
+}
+
+int nvmf_discover(const char *desc, int argc, char **argv, bool connect)
+{
+ char *subsysnqn = NVME_DISC_SUBSYS_NAME;
+ char *hostnqn = NULL, *hostid = NULL, *hostkey = NULL;
+ char *hostnqn_arg, *hostid_arg;
+ char *transport = NULL, *traddr = NULL, *trsvcid = NULL;
+ char *config_file = PATH_NVMF_CONFIG;
+ char *hnqn = NULL, *hid = NULL;
+ char *context = NULL;
+ enum nvme_print_flags flags;
+ nvme_root_t r;
+ nvme_host_t h;
+ nvme_ctrl_t c = NULL;
+ unsigned int verbose = 0;
+ int ret;
+ char *format = "normal";
+ struct nvme_fabrics_config cfg;
+ char *device = NULL;
+ bool force = false;
+ bool json_config = false;
+ bool nbft = false, nonbft = false;
+ char *nbft_path = NBFT_SYSFS_PATH;
+
+ NVMF_ARGS(opts, cfg,
+ OPT_STRING("device", 'd', "DEV", &device, "use existing discovery controller device"),
+ OPT_FMT("output-format", 'o', &format, output_format),
+ OPT_FILE("raw", 'r', &raw, "save raw output to file"),
+ OPT_FLAG("persistent", 'p', &persistent, "persistent discovery connection"),
+ OPT_FLAG("quiet", 'S', &quiet, "suppress already connected errors"),
+ OPT_STRING("config", 'J', "FILE", &config_file, nvmf_config_file),
+ OPT_INCR("verbose", 'v', &verbose, "Increase logging verbosity"),
+ OPT_FLAG("dump-config", 'O', &dump_config, "Dump configuration file to stdout"),
+ OPT_FLAG("force", 0, &force, "Force persistent discovery controller creation"),
+ OPT_FLAG("nbft", 0, &nbft, "Only look at NBFT tables"),
+ OPT_FLAG("no-nbft", 0, &nonbft, "Do not look at NBFT tables"),
+ OPT_STRING("nbft-path", 0, "STR", &nbft_path, "user-defined path for NBFT tables"),
+ OPT_STRING("context", 0, "STR", &context, nvmf_context));
+
+ nvmf_default_config(&cfg);
+
+ ret = argconfig_parse(argc, argv, desc, opts);
+ if (ret)
+ return ret;
+
+ ret = validate_output_format(format, &flags);
+ if (ret < 0) {
+ nvme_show_error("Invalid output format");
+ return ret;
+ }
+
+ if (!strcmp(config_file, "none"))
+ config_file = NULL;
+
+ r = nvme_create_root(stderr, map_log_level(verbose, quiet));
+ if (!r) {
+ fprintf(stderr, "Failed to create topology root: %s\n",
+ nvme_strerror(errno));
+ return -errno;
+ }
+ if (context)
+ nvme_root_set_application(r, context);
+ ret = nvme_scan_topology(r, NULL, NULL);
+ if (ret < 0) {
+ fprintf(stderr, "Failed to scan topology: %s\n",
+ nvme_strerror(errno));
+ nvme_free_tree(r);
+ return ret;
+ }
+
+ if (!nvme_read_config(r, config_file))
+ json_config = true;
+ if (!nvme_read_volatile_config(r))
+ json_config = true;
+
+ hostnqn_arg = hostnqn;
+ hostid_arg = hostid;
+ if (!hostnqn)
+ hostnqn = hnqn = nvmf_hostnqn_from_file();
+ if (!hostnqn) {
+ hostnqn = hnqn = nvmf_hostnqn_generate();
+ hostid = hid = nvmf_hostid_from_hostnqn(hostnqn);
+ }
+ if (!hostid)
+ hostid = hid = nvmf_hostid_from_file();
+ if (!hostid && hostnqn)
+ hostid = hid = nvmf_hostid_from_hostnqn(hostnqn);
+ nvmf_check_hostid_and_hostnqn(hostid, hostnqn, verbose);
+ h = nvme_lookup_host(r, hostnqn, hostid);
+ if (!h) {
+ ret = ENOMEM;
+ goto out_free;
+ }
+ if (device) {
+ if (!strcmp(device, "none"))
+ device = NULL;
+ else if (!strncmp(device, "/dev/", 5))
+ device += 5;
+ }
+ if (hostkey)
+ nvme_host_set_dhchap_key(h, hostkey);
+
+ if (!device && !transport && !traddr) {
+ if (!nonbft)
+ discover_from_nbft(r, hostnqn_arg, hostid_arg,
+ hostnqn, hostid, desc, connect,
+ &cfg, nbft_path, flags, verbose);
+
+ if (nbft)
+ goto out_free;
+
+ if (json_config)
+ ret = discover_from_json_config_file(r, h, desc,
+ connect, &cfg,
+ flags, force);
+ if (ret || access(PATH_NVMF_DISC, F_OK))
+ goto out_free;
+
+ ret = discover_from_conf_file(r, h, desc, connect, &cfg);
+ goto out_free;
+ }
+
+ if (!trsvcid)
+ trsvcid = get_default_trsvcid(transport, true);
+
+ struct tr_config trcfg = {
+ .subsysnqn = subsysnqn,
+ .transport = transport,
+ .traddr = traddr,
+ .host_traddr = cfg.host_traddr,
+ .host_iface = cfg.host_iface,
+ .trsvcid = trsvcid,
+ };
+
+ if (device && !force) {
+ c = nvme_scan_ctrl(r, device);
+ if (c) {
+ /* Check if device matches command-line options */
+ if (!nvme_ctrl_config_match(c, transport, traddr, trsvcid, subsysnqn,
+ cfg.host_traddr, cfg.host_iface)) {
+ fprintf(stderr,
+ "ctrl device %s found, ignoring non matching command-line options\n",
+ device);
+ }
+
+ if (!nvme_ctrl_is_discovery_ctrl(c)) {
+ fprintf(stderr,
+ "ctrl device %s found, ignoring non discovery controller\n",
+ device);
+
+ nvme_free_ctrl(c);
+ c = NULL;
+ persistent = false;
+ } else {
+ /*
+ * If the controller device is found it must
+ * be persistent, and shouldn't be disconnected
+ * on exit.
+ */
+ persistent = true;
+ /*
+ * When --host-traddr/--host-iface are not specified on the
+ * command line, use the discovery controller's (c) host-
+ * traddr/host-iface for the connections to controllers
+ * returned in the Discovery Log Pages. This is essential
+ * when invoking "connect-all" with --device to reuse an
+ * existing persistent discovery controller (as is done
+ * for the udev rules). This ensures that host-traddr/
+ * host-iface are consistent with the discovery controller (c).
+ */
+ if (!cfg.host_traddr)
+ cfg.host_traddr = (char *)nvme_ctrl_get_host_traddr(c);
+ if (!cfg.host_iface)
+ cfg.host_iface = (char *)nvme_ctrl_get_host_iface(c);
+ }
+ } else {
+ /*
+ * No controller found, fall back to create one.
+ * But that controller cannot be persistent.
+ */
+ fprintf(stderr,
+ "ctrl device %s not found%s\n", device,
+ persistent ? ", ignoring --persistent" : "");
+ persistent = false;
+ }
+ }
+ if (!c && !force) {
+ c = lookup_ctrl(h, &trcfg);
+ if (c)
+ persistent = true;
+ }
+ if (!c) {
+ /* No device or non-matching device, create a new controller */
+ c = create_discover_ctrl(r, h, &cfg, &trcfg);
+ if (!c) {
+ if (errno != ENVME_CONNECT_IGNORED)
+ fprintf(stderr,
+ "failed to add controller, error %s\n",
+ nvme_strerror(errno));
+ ret = errno;
+ goto out_free;
+ }
+ }
+
+ ret = __discover(c, &cfg, raw, connect, persistent, flags);
+ if (!(persistent || is_persistent_discovery_ctrl(h, c)))
+ nvme_disconnect_ctrl(c);
+ nvme_free_ctrl(c);
+
+out_free:
+ free(hnqn);
+ free(hid);
+ if (dump_config)
+ nvme_dump_config(r);
+ nvme_free_tree(r);
+
+ return ret;
+}
+
+int nvmf_connect(const char *desc, int argc, char **argv)
+{
+ char *subsysnqn = NULL;
+ char *transport = NULL, *traddr = NULL;
+ char *trsvcid = NULL, *hostnqn = NULL, *hostid = NULL;
+ char *hostkey = NULL, *ctrlkey = NULL;
+ char *hnqn = NULL, *hid = NULL;
+ char *config_file = PATH_NVMF_CONFIG;
+ char *context = NULL;
+ unsigned int verbose = 0;
+ nvme_root_t r;
+ nvme_host_t h;
+ nvme_ctrl_t c;
+ int ret;
+ enum nvme_print_flags flags;
+ struct nvme_fabrics_config cfg = { 0 };
+ char *format = "normal";
+
+
+ NVMF_ARGS(opts, cfg,
+ OPT_STRING("dhchap-ctrl-secret", 'C', "STR", &ctrlkey, nvmf_ctrlkey),
+ OPT_STRING("config", 'J', "FILE", &config_file, nvmf_config_file),
+ OPT_INCR("verbose", 'v', &verbose, "Increase logging verbosity"),
+ OPT_FLAG("dump-config", 'O', &dump_config, "Dump JSON configuration to stdout"),
+ OPT_FMT("output-format", 'o', &format, "Output format: normal|json"),
+ OPT_STRING("context", 0, "STR", &context, nvmf_context));
+
+ nvmf_default_config(&cfg);
+
+ ret = argconfig_parse(argc, argv, desc, opts);
+ if (ret)
+ return ret;
+
+ ret = validate_output_format(format, &flags);
+ if (ret < 0) {
+ nvme_show_error("Invalid output format");
+ return ret;
+ }
+
+ if (!subsysnqn) {
+ fprintf(stderr,
+ "required argument [--nqn | -n] not specified\n");
+ return -EINVAL;
+ }
+
+ if (!transport) {
+ fprintf(stderr,
+ "required argument [--transport | -t] not specified\n");
+ return -EINVAL;
+ }
+
+ if (strcmp(transport, "loop")) {
+ if (!traddr) {
+ fprintf(stderr,
+ "required argument [--traddr | -a] not specified for transport %s\n",
+ transport);
+ return -EINVAL;
+ }
+ }
+
+ if (!strcmp(config_file, "none"))
+ config_file = NULL;
+
+ r = nvme_create_root(stderr, map_log_level(verbose, quiet));
+ if (!r) {
+ fprintf(stderr, "Failed to create topology root: %s\n",
+ nvme_strerror(errno));
+ return -errno;
+ }
+ if (context)
+ nvme_root_set_application(r, context);
+ ret = nvme_scan_topology(r, NULL, NULL);
+ if (ret < 0) {
+ fprintf(stderr, "Failed to scan topology: %s\n",
+ nvme_strerror(errno));
+ nvme_free_tree(r);
+ return ret;
+ }
+ nvme_read_config(r, config_file);
+ nvme_read_volatile_config(r);
+
+ if (!hostnqn)
+ hostnqn = hnqn = nvmf_hostnqn_from_file();
+ if (!hostnqn) {
+ hostnqn = hnqn = nvmf_hostnqn_generate();
+ hostid = hid = nvmf_hostid_from_hostnqn(hostnqn);
+ }
+ if (!hostid)
+ hostid = hid = nvmf_hostid_from_file();
+ if (!hostid && hostnqn)
+ hostid = hid = nvmf_hostid_from_hostnqn(hostnqn);
+ nvmf_check_hostid_and_hostnqn(hostid, hostnqn, verbose);
+ h = nvme_lookup_host(r, hostnqn, hostid);
+ if (!h) {
+ errno = ENOMEM;
+ goto out_free;
+ }
+ if (hostkey)
+ nvme_host_set_dhchap_key(h, hostkey);
+ if (!trsvcid)
+ trsvcid = get_default_trsvcid(transport, false);
+
+ struct tr_config trcfg = {
+ .subsysnqn = subsysnqn,
+ .transport = transport,
+ .traddr = traddr,
+ .host_traddr = cfg.host_traddr,
+ .host_iface = cfg.host_iface,
+ .trsvcid = trsvcid,
+ };
+
+ c = lookup_ctrl(h, &trcfg);
+ if (c && nvme_ctrl_get_name(c)) {
+ fprintf(stderr, "already connected\n");
+ errno = EALREADY;
+ goto out_free;
+ }
+
+ c = nvme_create_ctrl(r, subsysnqn, transport, traddr,
+ cfg.host_traddr, cfg.host_iface, trsvcid);
+ if (!c) {
+ errno = ENOMEM;
+ goto out_free;
+ }
+ if (ctrlkey)
+ nvme_ctrl_set_dhchap_key(c, ctrlkey);
+
+ errno = 0;
+ ret = nvmf_add_ctrl(h, c, &cfg);
+ if (ret)
+ fprintf(stderr, "could not add new controller: %s\n",
+ nvme_strerror(errno));
+ else {
+ errno = 0;
+ if (flags != -EINVAL)
+ nvme_show_connect_msg(c, flags);
+ }
+
+out_free:
+ free(hnqn);
+ free(hid);
+ if (dump_config)
+ nvme_dump_config(r);
+ nvme_free_tree(r);
+ return -errno;
+}
+
+static nvme_ctrl_t lookup_nvme_ctrl(nvme_root_t r, const char *name)
+{
+ nvme_host_t h;
+ nvme_subsystem_t s;
+ nvme_ctrl_t c;
+
+ nvme_for_each_host(r, h) {
+ nvme_for_each_subsystem(h, s) {
+ nvme_subsystem_for_each_ctrl(s, c) {
+ if (!strcmp(nvme_ctrl_get_name(c), name))
+ return c;
+ }
+ }
+ }
+ return NULL;
+}
+
+static void nvmf_disconnect_nqn(nvme_root_t r, char *nqn)
+{
+ int i = 0;
+ char *n = nqn;
+ char *p;
+ nvme_host_t h;
+ nvme_subsystem_t s;
+ nvme_ctrl_t c;
+
+ while ((p = strsep(&n, ",")) != NULL) {
+ if (!strlen(p))
+ continue;
+ nvme_for_each_host(r, h) {
+ nvme_for_each_subsystem(h, s) {
+ if (strcmp(nvme_subsystem_get_nqn(s), p))
+ continue;
+ nvme_subsystem_for_each_ctrl(s, c) {
+ if (!nvme_disconnect_ctrl(c))
+ i++;
+ }
+ }
+ }
+ }
+ printf("NQN:%s disconnected %d controller(s)\n", nqn, i);
+}
+
+int nvmf_disconnect(const char *desc, int argc, char **argv)
+{
+ const char *device = "nvme device handle";
+ nvme_root_t r;
+ nvme_ctrl_t c;
+ char *p;
+ int ret;
+
+ struct config {
+ char *nqn;
+ char *device;
+ unsigned int verbose;
+ };
+
+ struct config cfg = { 0 };
+
+ OPT_ARGS(opts) = {
+ OPT_STRING("nqn", 'n', "NAME", &cfg.nqn, nvmf_nqn),
+ OPT_STRING("device", 'd', "DEV", &cfg.device, device),
+ OPT_INCR("verbose", 'v', &cfg.verbose, "Increase logging verbosity"),
+ OPT_END()
+ };
+
+ ret = argconfig_parse(argc, argv, desc, opts);
+ if (ret)
+ return ret;
+
+ if (!cfg.nqn && !cfg.device) {
+ fprintf(stderr,
+ "Neither device name [--device | -d] nor NQN [--nqn | -n] provided\n");
+ 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;
+ }
+ ret = nvme_scan_topology(r, NULL, NULL);
+ if (ret < 0) {
+ fprintf(stderr, "Failed to scan topology: %s\n",
+ nvme_strerror(errno));
+ nvme_free_tree(r);
+ return ret;
+ }
+
+ if (cfg.nqn)
+ nvmf_disconnect_nqn(r, cfg.nqn);
+
+ if (cfg.device) {
+ char *d;
+
+ d = cfg.device;
+ while ((p = strsep(&d, ",")) != NULL) {
+ if (!strncmp(p, "/dev/", 5))
+ p += 5;
+ c = lookup_nvme_ctrl(r, p);
+ if (!c) {
+ fprintf(stderr,
+ "Did not find device %s\n", p);
+ nvme_free_tree(r);
+ return -errno;
+ }
+ ret = nvme_disconnect_ctrl(c);
+ if (ret)
+ fprintf(stderr,
+ "Failed to disconnect %s: %s\n",
+ p, nvme_strerror(errno));
+ }
+ }
+ nvme_free_tree(r);
+
+ return 0;
+}
+
+int nvmf_disconnect_all(const char *desc, int argc, char **argv)
+{
+ nvme_host_t h;
+ nvme_subsystem_t s;
+ nvme_root_t r;
+ nvme_ctrl_t c;
+ int ret;
+
+ struct config {
+ char *transport;
+ unsigned int verbose;
+ };
+
+ struct config cfg = { 0 };
+
+ OPT_ARGS(opts) = {
+ OPT_STRING("transport", 'r', "STR", (char *)&cfg.transport, nvmf_tport),
+ OPT_INCR("verbose", 'v', &cfg.verbose, "Increase logging verbosity"),
+ OPT_END()
+ };
+
+ ret = argconfig_parse(argc, argv, desc, opts);
+ if (ret)
+ return ret;
+
+ 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;
+ }
+ ret = nvme_scan_topology(r, NULL, NULL);
+ if (ret < 0) {
+ fprintf(stderr, "Failed to scan topology: %s\n",
+ nvme_strerror(errno));
+ nvme_free_tree(r);
+ return ret;
+ }
+
+ nvme_for_each_host(r, h) {
+ nvme_for_each_subsystem(h, s) {
+ nvme_subsystem_for_each_ctrl(s, c) {
+ if (cfg.transport &&
+ strcmp(cfg.transport,
+ nvme_ctrl_get_transport(c)))
+ continue;
+ else if (!strcmp(nvme_ctrl_get_transport(c),
+ "pcie"))
+ continue;
+ if (nvme_disconnect_ctrl(c))
+ fprintf(stderr,
+ "failed to disconnect %s\n",
+ nvme_ctrl_get_name(c));
+ }
+ }
+ }
+ nvme_free_tree(r);
+
+ return 0;
+}
+
+int nvmf_config(const char *desc, int argc, char **argv)
+{
+ char *subsysnqn = NULL;
+ char *transport = NULL, *traddr = NULL;
+ char *trsvcid = NULL, *hostnqn = NULL, *hostid = NULL;
+ char *hnqn = NULL, *hid = NULL;
+ char *hostkey = NULL, *ctrlkey = NULL;
+ char *config_file = PATH_NVMF_CONFIG;
+ unsigned int verbose = 0;
+ nvme_root_t r;
+ int ret;
+ struct nvme_fabrics_config cfg;
+ bool scan_tree = false, modify_config = false, update_config = false;
+
+ NVMF_ARGS(opts, cfg,
+ OPT_STRING("dhchap-ctrl-secret", 'C', "STR", &ctrlkey, nvmf_ctrlkey),
+ OPT_STRING("config", 'J', "FILE", &config_file, nvmf_config_file),
+ OPT_INCR("verbose", 'v', &verbose, "Increase logging verbosity"),
+ OPT_FLAG("scan", 'R', &scan_tree, "Scan current NVMeoF topology"),
+ OPT_FLAG("modify", 'M', &modify_config, "Modify JSON configuration file"),
+ OPT_FLAG("dump", 'O', &dump_config, "Dump JSON configuration to stdout"),
+ OPT_FLAG("update", 'U', &update_config, "Update JSON configuration file"));
+
+ nvmf_default_config(&cfg);
+
+ ret = argconfig_parse(argc, argv, desc, opts);
+ if (ret)
+ return ret;
+
+ if (!strcmp(config_file, "none"))
+ config_file = NULL;
+
+ r = nvme_create_root(stderr, map_log_level(verbose, quiet));
+ if (!r) {
+ fprintf(stderr, "Failed to create topology root: %s\n",
+ nvme_strerror(errno));
+ return -errno;
+ }
+ if (scan_tree) {
+ ret = nvme_scan_topology(r, NULL, NULL);
+ if (ret < 0) {
+ fprintf(stderr, "Failed to scan topology: %s\n",
+ nvme_strerror(errno));
+ nvme_free_tree(r);
+ return ret;
+ }
+ }
+ nvme_read_config(r, config_file);
+
+ if (modify_config) {
+ nvme_host_t h;
+ nvme_subsystem_t s;
+ nvme_ctrl_t c;
+
+ if (!subsysnqn) {
+ fprintf(stderr,
+ "required argument [--nqn | -n] needed with --modify\n");
+ return -EINVAL;
+ }
+
+ if (!transport) {
+ fprintf(stderr,
+ "required argument [--transport | -t] needed with --modify\n");
+ return -EINVAL;
+ }
+
+ if (!hostnqn)
+ hostnqn = hnqn = nvmf_hostnqn_from_file();
+ if (!hostid && hnqn)
+ hostid = hid = nvmf_hostid_from_file();
+ h = nvme_lookup_host(r, hostnqn, hostid);
+ if (!h) {
+ fprintf(stderr, "Failed to lookup host '%s': %s\n",
+ hostnqn, nvme_strerror(errno));
+ goto out;
+ }
+ if (hostkey)
+ nvme_host_set_dhchap_key(h, hostkey);
+ s = nvme_lookup_subsystem(h, NULL, subsysnqn);
+ if (!s) {
+ fprintf(stderr, "Failed to lookup subsystem '%s': %s\n",
+ subsysnqn, nvme_strerror(errno));
+ goto out;
+ }
+ c = nvme_lookup_ctrl(s, transport, traddr,
+ cfg.host_traddr, cfg.host_iface,
+ trsvcid, NULL);
+ if (!c) {
+ fprintf(stderr, "Failed to lookup controller: %s\n",
+ nvme_strerror(errno));
+ goto out;
+ }
+ nvmf_update_config(c, &cfg);
+ if (ctrlkey)
+ nvme_ctrl_set_dhchap_key(c, ctrlkey);
+ }
+
+ if (update_config)
+ nvme_update_config(r);
+
+ if (dump_config)
+ nvme_dump_config(r);
+
+out:
+ if (hid)
+ free(hid);
+ if (hnqn)
+ free(hnqn);
+ nvme_free_tree(r);
+ return -errno;
+}
+
+static void dim_operation(nvme_ctrl_t c, enum nvmf_dim_tas tas, const char *name)
+{
+ static const char * const task[] = {
+ [NVMF_DIM_TAS_REGISTER] = "register",
+ [NVMF_DIM_TAS_DEREGISTER] = "deregister",
+ };
+ const char *t;
+ int status;
+ __u32 result;
+
+ t = (tas > NVMF_DIM_TAS_DEREGISTER || !task[tas]) ? "reserved" : task[tas];
+ status = nvmf_register_ctrl(c, tas, &result);
+ if (status == NVME_SC_SUCCESS) {
+ printf("%s DIM %s command success\n", name, t);
+ } else if (status < NVME_SC_SUCCESS) {
+ fprintf(stderr, "%s DIM %s command error. Status:0x%04x - %s\n",
+ name, t, status, nvme_status_to_string(status, false));
+ } else {
+ fprintf(stderr, "%s DIM %s command error. Result:0x%04x, Status:0x%04x - %s\n",
+ name, t, result, status, nvme_status_to_string(status, false));
+ }
+}
+
+int nvmf_dim(const char *desc, int argc, char **argv)
+{
+ enum nvmf_dim_tas tas;
+ nvme_root_t r;
+ nvme_ctrl_t c;
+ char *p;
+ int ret;
+
+ struct {
+ char *nqn;
+ char *device;
+ char *tas;
+ unsigned int verbose;
+ } cfg = { 0 };
+
+ OPT_ARGS(opts) = {
+ OPT_STRING("nqn", 'n', "NAME", &cfg.nqn, "Comma-separated list of DC nqn"),
+ OPT_STRING("device", 'd', "DEV", &cfg.device, "Comma-separated list of DC nvme device handle."),
+ OPT_STRING("task", 't', "TASK", &cfg.tas, "[register|deregister]"),
+ OPT_INCR("verbose", 'v', &cfg.verbose, "Increase logging verbosity"),
+ OPT_END()
+ };
+
+ ret = argconfig_parse(argc, argv, desc, opts);
+ if (ret)
+ return ret;
+
+ if (!cfg.nqn && !cfg.device) {
+ fprintf(stderr,
+ "Neither device name [--device | -d] nor NQN [--nqn | -n] provided\n");
+ return -EINVAL;
+ }
+
+ if (!cfg.tas) {
+ fprintf(stderr,
+ "Task [--task | -t] must be specified\n");
+ return -EINVAL;
+ }
+
+ /* Allow partial name (e.g. "reg" for "register" */
+ if (strstarts("register", cfg.tas)) {
+ tas = NVMF_DIM_TAS_REGISTER;
+ } else if (strstarts("deregister", cfg.tas)) {
+ tas = NVMF_DIM_TAS_DEREGISTER;
+ } else {
+ fprintf(stderr, "Invalid --task: %s\n", cfg.tas);
+ 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;
+ }
+ ret = nvme_scan_topology(r, NULL, NULL);
+ if (ret < 0) {
+ fprintf(stderr, "Failed to scan topology: %s\n",
+ nvme_strerror(errno));
+ nvme_free_tree(r);
+ return ret;
+ }
+
+ if (cfg.nqn) {
+ nvme_host_t h;
+ nvme_subsystem_t s;
+ char *n = cfg.nqn;
+
+ while ((p = strsep(&n, ",")) != NULL) {
+ if (!strlen(p))
+ continue;
+ nvme_for_each_host(r, h) {
+ nvme_for_each_subsystem(h, s) {
+ if (strcmp(nvme_subsystem_get_nqn(s), p))
+ continue;
+ nvme_subsystem_for_each_ctrl(s, c) {
+ dim_operation(c, tas, p);
+ }
+ }
+ }
+ }
+ }
+
+ if (cfg.device) {
+ char *d = cfg.device;
+
+ while ((p = strsep(&d, ",")) != NULL) {
+ if (!strncmp(p, "/dev/", 5))
+ p += 5;
+ c = nvme_scan_ctrl(r, p);
+ if (!c) {
+ fprintf(stderr,
+ "Did not find device %s: %s\n",
+ p, nvme_strerror(errno));
+ nvme_free_tree(r);
+ return -errno;
+ }
+ dim_operation(c, tas, p);
+ }
+ }
+
+ nvme_free_tree(r);
+
+ return 0;
+}