/* * 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "util/parser.h" #include "nvme-ioctl.h" #include "nvme-status.h" #include "fabrics.h" #include "nvme.h" #include "util/argconfig.h" #include "common.h" #include "util/log.h" #include "util/cleanup.h" #ifdef HAVE_SYSTEMD #include #define NVME_HOSTNQN_ID SD_ID128_MAKE(c7,f4,61,81,12,be,49,32,8c,83,10,6f,9d,dd,d8,6b) #endif #define NVMF_HOSTID_SIZE 36 /* default to 600 seconds of reconnect attempts before giving up */ #define NVMF_DEF_CTRL_LOSS_TMO 600 const char *conarg_nqn = "nqn"; const char *conarg_transport = "transport"; const char *conarg_traddr = "traddr"; const char *conarg_trsvcid = "trsvcid"; const char *conarg_host_traddr = "host_traddr"; const char *conarg_host_iface = "host_iface"; struct fabrics_config fabrics_cfg = { .ctrl_loss_tmo = -1, .fast_io_fail_tmo = -1, .output_format = "normal", }; struct connect_args { char *subsysnqn; char *transport; char *traddr; char *trsvcid; char *host_traddr; char *host_iface; struct connect_args *next; struct connect_args *tail; }; struct connect_args *tracked_ctrls; #define PATH_NVME_FABRICS "/dev/nvme-fabrics" #define PATH_NVMF_DISC "/etc/nvme/discovery.conf" #define PATH_NVMF_HOSTNQN "/etc/nvme/hostnqn" #define PATH_NVMF_HOSTID "/etc/nvme/hostid" #define MAX_DISC_ARGS 10 #define MAX_DISC_RETRIES 10 enum { OPT_INSTANCE, OPT_CNTLID, OPT_ERR }; static const match_table_t opt_tokens = { { OPT_INSTANCE, "instance=%d" }, { OPT_CNTLID, "cntlid=%d" }, { OPT_ERR, NULL }, }; const char *arg_str(const char * const *strings, size_t array_size, size_t idx) { if (idx < array_size && strings[idx]) return strings[idx]; return "unrecognized"; } const char * const trtypes[] = { [NVMF_TRTYPE_RDMA] = "rdma", [NVMF_TRTYPE_FC] = "fc", [NVMF_TRTYPE_TCP] = "tcp", [NVMF_TRTYPE_LOOP] = "loop", }; static const char *trtype_str(__u8 trtype) { return arg_str(trtypes, ARRAY_SIZE(trtypes), trtype); } static const char * const adrfams[] = { [NVMF_ADDR_FAMILY_PCI] = "pci", [NVMF_ADDR_FAMILY_IP4] = "ipv4", [NVMF_ADDR_FAMILY_IP6] = "ipv6", [NVMF_ADDR_FAMILY_IB] = "infiniband", [NVMF_ADDR_FAMILY_FC] = "fibre-channel", [NVMF_ADDR_FAMILY_LOOP] = "loop", }; static inline const char *adrfam_str(__u8 adrfam) { return arg_str(adrfams, ARRAY_SIZE(adrfams), adrfam); } static const char * const subtypes[] = { [NVME_NQN_DISC] = "discovery subsystem", [NVME_NQN_NVME] = "nvme subsystem", }; static inline const char *subtype_str(__u8 subtype) { return arg_str(subtypes, ARRAY_SIZE(subtypes), subtype); } static const char * const treqs[] = { [NVMF_TREQ_NOT_SPECIFIED] = "not specified", [NVMF_TREQ_REQUIRED] = "required", [NVMF_TREQ_NOT_REQUIRED] = "not required", [NVMF_TREQ_DISABLE_SQFLOW] = "not specified, " "sq flow control disable supported", }; static inline const char *treq_str(__u8 treq) { return arg_str(treqs, ARRAY_SIZE(treqs), treq); } static const char * const sectypes[] = { [NVMF_TCP_SECTYPE_NONE] = "none", [NVMF_TCP_SECTYPE_TLS] = "tls", }; static inline const char *sectype_str(__u8 sectype) { return arg_str(sectypes, ARRAY_SIZE(sectypes), sectype); } static const char * const prtypes[] = { [NVMF_RDMA_PRTYPE_NOT_SPECIFIED] = "not specified", [NVMF_RDMA_PRTYPE_IB] = "infiniband", [NVMF_RDMA_PRTYPE_ROCE] = "roce", [NVMF_RDMA_PRTYPE_ROCEV2] = "roce-v2", [NVMF_RDMA_PRTYPE_IWARP] = "iwarp", }; static inline const char *prtype_str(__u8 prtype) { return arg_str(prtypes, ARRAY_SIZE(prtypes), prtype); } static const char * const qptypes[] = { [NVMF_RDMA_QPTYPE_CONNECTED] = "connected", [NVMF_RDMA_QPTYPE_DATAGRAM] = "datagram", }; static inline const char *qptype_str(__u8 qptype) { return arg_str(qptypes, ARRAY_SIZE(qptypes), qptype); } static const char * const cms[] = { [NVMF_RDMA_CMS_RDMA_CM] = "rdma-cm", }; static const char *cms_str(__u8 cm) { return arg_str(cms, ARRAY_SIZE(cms), cm); } /* * parse strings with connect arguments to find a particular field. * If field found, return string containing field value. If field * not found, return an empty string. */ char *parse_conn_arg(const char *conargs, const char delim, const char *field) { char *s, *e; size_t cnt; /* * There are field name overlaps: traddr and host_traddr. * By chance, both connect arg strings are set up to * have traddr field followed by host_traddr field. Thus field * name matching doesn't overlap in the searches. Technically, * as is, the loop and delimiter checking isn't necessary. * However, better to be prepared. */ do { s = strstr(conargs, field); if (!s) goto empty_field; /* validate prior character is delimiter */ if (s == conargs || *(s - 1) == delim) { /* match requires next character to be assignment */ s += strlen(field); if (*s == '=') /* match */ break; } /* field overlap: seek to delimiter and keep looking */ conargs = strchr(s, delim); if (!conargs) goto empty_field; conargs++; /* skip delimiter */ } while (1); s++; /* skip assignment character */ e = strchr(s, delim); if (e) cnt = e - s; else cnt = strlen(s); return strndup(s, cnt); empty_field: return strdup("\0"); } int ctrl_instance(const char *device) { char d[64]; const char *p; int ret, instance; p = strrchr(device, '/'); if (p == NULL) p = device; else p++; ret = sscanf(p, "nvme%d", &instance); if (ret <= 0) return -EINVAL; if (snprintf(d, sizeof(d), "nvme%d", instance) <= 0 || strcmp(p, d)) return -EINVAL; return instance; } /* * Given a controller name, create a connect_args with its * attributes and compare the attributes against the connect args * given. * Return true/false based on whether it matches */ static bool ctrl_matches_connectargs(const char *name, struct connect_args *args) { struct connect_args cargs; bool found = false; char *path = NULL, *addr; int ret; bool persistent = true; ret = asprintf(&path, "%s/%s", SYS_NVME, name); if (ret < 0) return found; addr = nvme_get_ctrl_attr(path, "address"); if (!addr) { fprintf(stderr, "nvme_get_ctrl_attr failed\n"); return found; } cargs.subsysnqn = nvme_get_ctrl_attr(path, "subsysnqn"); cargs.transport = nvme_get_ctrl_attr(path, "transport"); cargs.traddr = parse_conn_arg(addr, ' ', conarg_traddr); cargs.trsvcid = parse_conn_arg(addr, ' ', conarg_trsvcid); cargs.host_traddr = parse_conn_arg(addr, ' ', conarg_host_traddr); cargs.host_iface = parse_conn_arg(addr, ' ', conarg_host_iface); if (!strcmp(cargs.subsysnqn, NVME_DISC_SUBSYS_NAME)) { char *kato_str = nvme_get_ctrl_attr(path, "kato"), *p; unsigned int kato = 0; /* * When looking up discovery controllers we have to skip * any non-persistent controllers (ie those with a zero * kato value). Otherwise the controller will vanish from * underneath us as they are owned by another program. * * On older kernels, the 'kato' attribute isn't present. * Assume a persistent controller for these installations. */ if (kato_str) { kato = strtoul(kato_str, &p, 0); if (p == kato_str) kato = 0; free(kato_str); persistent = (kato != 0); } } if (persistent && !strcmp(cargs.subsysnqn, args->subsysnqn) && !strcmp(cargs.transport, args->transport) && (!strcmp(cargs.traddr, args->traddr) || !strcmp(args->traddr, "none")) && (!strcmp(cargs.trsvcid, args->trsvcid) || !strcmp(args->trsvcid, "none")) && (!strcmp(cargs.host_traddr, args->host_traddr) || !strcmp(args->host_traddr, "none")) && (!strcmp(cargs.host_iface, args->host_iface) || !strcmp(args->host_iface, "none"))) found = true; free(cargs.subsysnqn); free(cargs.transport); free(cargs.traddr); free(cargs.trsvcid); free(cargs.host_traddr); free(cargs.host_iface); free(addr); free(path); return found; } /* * Look through the system to find an existing controller whose * attributes match the connect arguments specified * If found, a string containing the controller name (ex: "nvme?") * is returned. * If not found, a NULL is returned. */ static char *find_ctrl_with_connectargs(struct connect_args *args) { struct dirent **devices; char *devname = NULL; int i, n; n = scandir(SYS_NVME, &devices, scan_ctrls_filter, alphasort); if (n < 0) { msg(LOG_ERR, "no NVMe controller(s) detected.\n"); return NULL; } for (i = 0; i < n; i++) { if (ctrl_matches_connectargs(devices[i]->d_name, args)) { devname = strdup(devices[i]->d_name); if (devname == NULL) msg(LOG_ERR, "no memory for ctrl name %s\n", devices[i]->d_name); goto cleanup_devices; } } cleanup_devices: for (i = 0; i < n; i++) free(devices[i]); free(devices); return devname; } static struct connect_args *extract_connect_args(char *argstr) { struct connect_args *cargs; cargs = calloc(1, sizeof(*cargs)); if (!cargs) return NULL; cargs->subsysnqn = parse_conn_arg(argstr, ',', conarg_nqn); cargs->transport = parse_conn_arg(argstr, ',', conarg_transport); cargs->traddr = parse_conn_arg(argstr, ',', conarg_traddr); cargs->trsvcid = parse_conn_arg(argstr, ',', conarg_trsvcid); cargs->host_traddr = parse_conn_arg(argstr, ',', conarg_host_traddr); cargs->host_iface = parse_conn_arg(argstr, ',', conarg_host_iface); return cargs; } static void destruct_connect_args(struct connect_args *cargs) { free(cargs->subsysnqn); free(cargs->transport); free(cargs->traddr); free(cargs->trsvcid); free(cargs->host_traddr); free(cargs->host_iface); } static void free_connect_args(struct connect_args *cargs) { destruct_connect_args(cargs); free(cargs); } static void track_ctrl(char *argstr) { struct connect_args *cargs; cargs = extract_connect_args(argstr); if (!cargs) return; if (!tracked_ctrls) tracked_ctrls = cargs; else tracked_ctrls->tail->next = cargs; tracked_ctrls->tail = cargs; } static int add_ctrl(const char *argstr) { substring_t args[MAX_OPT_ARGS]; char buf[BUF_SIZE], *options, *p; int token, ret, fd, len = strlen(argstr); fd = open(PATH_NVME_FABRICS, O_RDWR); if (fd < 0) { msg(LOG_ERR, "Failed to open %s: %s\n", PATH_NVME_FABRICS, strerror(errno)); ret = -errno; goto out; } ret = write(fd, argstr, len); if (ret != len) { if (errno != EALREADY) msg(LOG_NOTICE, "Failed to write to %s: %s\n", PATH_NVME_FABRICS, strerror(errno)); ret = -errno; goto out_close; } len = read(fd, buf, BUF_SIZE); if (len < 0) { msg(LOG_ERR, "Failed to read from %s: %s\n", PATH_NVME_FABRICS, strerror(errno)); ret = -errno; goto out_close; } buf[len] = '\0'; options = buf; while ((p = strsep(&options, ",\n")) != NULL) { if (!*p) continue; token = match_token(p, opt_tokens, args); switch (token) { case OPT_INSTANCE: if (match_int(args, &token)) goto out_fail; ret = token; track_ctrl((char *)argstr); goto out_close; default: /* ignore */ break; } } out_fail: msg(LOG_ERR, "Failed to parse ctrl info for \"%s\"\n", argstr); ret = -EINVAL; out_close: close(fd); out: return ret; } static int remove_ctrl_by_path(char *sysfs_path) { int ret, fd; fd = open(sysfs_path, O_WRONLY); if (fd < 0) { ret = -errno; msg(LOG_ERR, "Failed to open %s: %s\n", sysfs_path, strerror(errno)); goto out; } if (write(fd, "1", 1) != 1) { ret = -errno; goto out_close; } ret = 0; out_close: close(fd); out: return ret; } int remove_ctrl(int instance) { char *sysfs_path; int ret; if (asprintf(&sysfs_path, "/sys/class/nvme/nvme%d/delete_controller", instance) < 0) { ret = -errno; goto out; } ret = remove_ctrl_by_path(sysfs_path); free(sysfs_path); out: return ret; } enum { DISC_OK, DISC_NO_LOG, DISC_GET_NUMRECS, DISC_GET_LOG, DISC_RETRY_EXHAUSTED, DISC_NOT_EQUAL, }; static int nvmf_get_log_page_discovery(const char *dev_path, struct nvmf_disc_rsp_page_hdr **logp, int *numrec, int *status) { struct nvmf_disc_rsp_page_hdr *log; unsigned int hdr_size; unsigned long genctr; int error, fd, max_retries = MAX_DISC_RETRIES, retries = 0; fd = open(dev_path, O_RDWR); if (fd < 0) { error = -errno; msg(LOG_ERR, "Failed to open %s: %s\n", dev_path, strerror(errno)); goto out; } /* first get_log_page we just need numrec entry from discovery hdr. * host supplies its desired bytes via dwords, per NVMe spec. */ hdr_size = round_up((offsetof(struct nvmf_disc_rsp_page_hdr, numrec) + sizeof(log->numrec)), sizeof(__u32)); /* * Issue first get log page w/numdl small enough to retrieve numrec. * We just want to know how many records to retrieve. */ log = calloc(1, hdr_size); if (!log) { perror("could not alloc memory for discovery log header"); error = -ENOMEM; goto out_close; } error = nvme_discovery_log(fd, log, hdr_size); if (error) { error = DISC_GET_NUMRECS; goto out_free_log; } do { unsigned int log_size; /* check numrec limits */ *numrec = le64_to_cpu(log->numrec); genctr = le64_to_cpu(log->genctr); free(log); if (*numrec == 0) { error = DISC_NO_LOG; goto out_close; } /* we are actually retrieving the entire discovery tables * for the second get_log_page(), per * NVMe spec so no need to round_up(), or there is something * seriously wrong with the standard */ log_size = sizeof(struct nvmf_disc_rsp_page_hdr) + sizeof(struct nvmf_disc_rsp_page_entry) * *numrec; /* allocate discovery log pages based on page_hdr->numrec */ log = calloc(1, log_size); if (!log) { perror("could not alloc memory for discovery log page"); error = -ENOMEM; goto out_close; } /* * issue new get_log_page w/numdl+numdh set to get all records, * up to MAX_DISC_LOGS. */ error = nvme_discovery_log(fd, log, log_size); if (error) { error = DISC_GET_LOG; goto out_free_log; } /* * The above call to nvme_discovery_log() might result * in several calls (with different offsets), so we need * to fetch the header again to have the most up-to-date * value for the generation counter */ genctr = le64_to_cpu(log->genctr); error = nvme_discovery_log(fd, log, hdr_size); if (error) { error = DISC_GET_LOG; goto out_free_log; } } while (genctr != le64_to_cpu(log->genctr) && ++retries < max_retries); /* * If genctr is still different with the one in the log entry, it * means the retires have been exhausted to max_retries. Then it * should be retried by the caller or the user. */ if (genctr != le64_to_cpu(log->genctr)) { error = DISC_RETRY_EXHAUSTED; goto out_free_log; } if (*numrec != le64_to_cpu(log->numrec)) { error = DISC_NOT_EQUAL; goto out_free_log; } /* needs to be freed by the caller */ *logp = log; error = DISC_OK; goto out_close; out_free_log: free(log); out_close: close(fd); out: *status = nvme_status_to_errno(error, true); return error; } static int space_strip_len(int max, const char *str) { int i; for (i = max - 1; i >= 0; i--) if (str[i] != '\0' && str[i] != ' ') break; return i + 1; } static void print_discovery_log(struct nvmf_disc_rsp_page_hdr *log, int numrec, int instance) { int i; printf("\n"); if (fabrics_cfg.persistent) printf("Persistent device: nvme%d\n", instance); printf("Discovery Log Number of Records %d, " "Generation counter %"PRIu64"\n", numrec, le64_to_cpu(log->genctr)); for (i = 0; i < numrec; i++) { struct nvmf_disc_rsp_page_entry *e = &log->entries[i]; printf("=====Discovery Log Entry %d======\n", i); printf("trtype: %s\n", trtype_str(e->trtype)); printf("adrfam: %s\n", adrfam_str(e->adrfam)); printf("subtype: %s\n", subtype_str(e->subtype)); printf("treq: %s\n", treq_str(e->treq)); printf("portid: %d\n", e->portid); printf("trsvcid: %.*s\n", space_strip_len(NVMF_TRSVCID_SIZE, e->trsvcid), e->trsvcid); printf("subnqn: %s\n", e->subnqn); printf("traddr: %.*s\n", space_strip_len(NVMF_TRADDR_SIZE, e->traddr), e->traddr); switch (e->trtype) { case NVMF_TRTYPE_RDMA: printf("rdma_prtype: %s\n", prtype_str(e->tsas.rdma.prtype)); printf("rdma_qptype: %s\n", qptype_str(e->tsas.rdma.qptype)); printf("rdma_cms: %s\n", cms_str(e->tsas.rdma.cms)); printf("rdma_pkey: 0x%04x\n", le16_to_cpu(e->tsas.rdma.pkey)); break; case NVMF_TRTYPE_TCP: printf("sectype: %s\n", sectype_str(e->tsas.tcp.sectype)); break; } } } static void json_discovery_log(struct nvmf_disc_rsp_page_hdr *log, int numrec, int instance) { struct json_object *root; struct json_object *entries; char *dev_name = NULL; int i; if (asprintf(&dev_name, "nvme%d", instance) < 0) return; root = json_create_object(); entries = json_create_array(); json_object_add_value_string(root, "device", dev_name); json_object_add_value_uint(root, "genctr", le64_to_cpu(log->genctr)); json_object_add_value_array(root, "records", entries); for (i = 0; i < numrec; i++) { struct nvmf_disc_rsp_page_entry *e = &log->entries[i]; struct json_object *entry = json_create_object(); nvme_strip_spaces(e->trsvcid, NVMF_TRSVCID_SIZE); nvme_strip_spaces(e->subnqn, NVMF_NQN_SIZE); nvme_strip_spaces(e->traddr, NVMF_TRADDR_SIZE); json_object_add_value_string(entry, "trtype", trtype_str(e->trtype)); json_object_add_value_string(entry, "adrfam", adrfam_str(e->adrfam)); json_object_add_value_string(entry, "subtype", subtype_str(e->subtype)); json_object_add_value_string(entry,"treq", treq_str(e->treq)); json_object_add_value_uint(entry, "portid", e->portid); json_object_add_value_string(entry, "trsvcid", e->trsvcid); json_object_add_value_string(entry, "subnqn", e->subnqn); json_object_add_value_string(entry, "traddr", e->traddr); switch (e->trtype) { case NVMF_TRTYPE_RDMA: json_object_add_value_string(entry, "rdma_prtype", prtype_str(e->tsas.rdma.prtype)); json_object_add_value_string(entry, "rdma_qptype", qptype_str(e->tsas.rdma.qptype)); json_object_add_value_string(entry, "rdma_cms", cms_str(e->tsas.rdma.cms)); json_object_add_value_uint(entry, "rdma_pkey", le16_to_cpu(e->tsas.rdma.pkey)); break; case NVMF_TRTYPE_TCP: json_object_add_value_string(entry, "sectype", sectype_str(e->tsas.tcp.sectype)); break; } json_array_add_value_object(entries, entry); } json_print_object(root, NULL); printf("\n"); json_free_object(root); free(dev_name); } static void save_discovery_log(struct nvmf_disc_rsp_page_hdr *log, int numrec) { int fd; int len, ret; fd = open(fabrics_cfg.raw, O_CREAT|O_RDWR|O_TRUNC, S_IRUSR|S_IWUSR); if (fd < 0) { msg(LOG_ERR, "failed to open %s: %s\n", fabrics_cfg.raw, strerror(errno)); return; } len = sizeof(struct nvmf_disc_rsp_page_hdr) + numrec * sizeof(struct nvmf_disc_rsp_page_entry); ret = write(fd, log, len); if (ret < 0) msg(LOG_ERR, "failed to write to %s: %s\n", fabrics_cfg.raw, strerror(errno)); else printf("Discovery log is saved to %s\n", fabrics_cfg.raw); close(fd); } static void print_connect_msg(int instance) { printf("device: nvme%d\n", instance); } static void json_connect_msg(int instance) { struct json_object *root; char *dev_name = NULL; if (asprintf(&dev_name, "nvme%d", instance) < 0) return; root = json_create_object(); json_object_add_value_string(root, "device", dev_name); json_print_object(root, NULL); printf("\n"); json_free_object(root); free(dev_name); } static char *hostnqn_read_file(void) { FILE *f; char hostnqn[NVMF_NQN_SIZE]; char *ret = NULL; f = fopen(PATH_NVMF_HOSTNQN, "r"); if (f == NULL) return false; if (fgets(hostnqn, sizeof(hostnqn), f) == NULL || !strlen(hostnqn)) goto out; ret = strndup(hostnqn, strcspn(hostnqn, "\n")); out: fclose(f); return ret; } static char *hostnqn_generate_systemd(void) { #ifdef HAVE_SYSTEMD sd_id128_t id; char *ret; if (sd_id128_get_machine_app_specific(NVME_HOSTNQN_ID, &id) < 0) return NULL; if (asprintf(&ret, "nqn.2014-08.org.nvmexpress:uuid:" SD_ID128_FORMAT_STR "\n", SD_ID128_FORMAT_VAL(id)) == -1) ret = NULL; return ret; #else return NULL; #endif } static char *hostnqn_read_dmi(void) { char uuid[16]; char *ret = NULL; if (uuid_from_dmi(uuid) < 0) return NULL; if (asprintf(&ret, "nqn.2014-08.org.nvmexpress:uuid:%s", uuid) == -1) return NULL; return ret; } /* returns an allocated string or NULL */ char *hostnqn_read(void) { char *ret; ret = hostnqn_read_file(); if (ret) return ret; ret = hostnqn_read_dmi(); if (ret) return ret; ret = hostnqn_generate_systemd(); if (ret) return ret; return NULL; } static int nvmf_hostnqn_file(void) { fabrics_cfg.hostnqn = hostnqn_read(); return fabrics_cfg.hostnqn != NULL; } static int nvmf_hostid_file(void) { FILE *f; char hostid[NVMF_HOSTID_SIZE + 1]; int ret = false; f = fopen(PATH_NVMF_HOSTID, "r"); if (f == NULL) return false; if (fgets(hostid, sizeof(hostid), f) == NULL) goto out; fabrics_cfg.hostid = strdup(hostid); if (!fabrics_cfg.hostid) goto out; ret = true; out: fclose(f); return ret; } static int add_bool_argument(char **argstr, int *max_len, char *arg_str, bool arg) { int len; if (arg) { len = snprintf(*argstr, *max_len, ",%s", arg_str); if (len < 0) return -EINVAL; *argstr += len; *max_len -= len; } return 0; } static int add_int_argument(char **argstr, int *max_len, char *arg_str, int arg, bool allow_zero) { int len; if ((arg && !allow_zero) || (arg != -1 && allow_zero)) { len = snprintf(*argstr, *max_len, ",%s=%d", arg_str, arg); if (len < 0) return -EINVAL; *argstr += len; *max_len -= len; } return 0; } static int add_argument(char **argstr, int *max_len, char *arg_str, const char *arg) { int len; if (arg && strcmp(arg, "none")) { len = snprintf(*argstr, *max_len, ",%s=%s", arg_str, arg); if (len < 0) return -EINVAL; *argstr += len; *max_len -= len; } return 0; } int build_options(char *argstr, int max_len, bool discover) { int len; if (!fabrics_cfg.transport) { msg(LOG_ERR, "need a transport (-t) argument\n"); return -EINVAL; } if (strncmp(fabrics_cfg.transport, "loop", 4)) { if (!fabrics_cfg.traddr) { msg(LOG_ERR, "need a address (-a) argument\n"); return -EINVAL; } /* Use the default ctrl loss timeout if unset */ if (fabrics_cfg.ctrl_loss_tmo == -1) fabrics_cfg.ctrl_loss_tmo = NVMF_DEF_CTRL_LOSS_TMO; } /* always specify nqn as first arg - this will init the string */ len = snprintf(argstr, max_len, "nqn=%s", fabrics_cfg.nqn); if (len < 0) return -EINVAL; argstr += len; max_len -= len; if (add_argument(&argstr, &max_len, "transport", fabrics_cfg.transport) || add_argument(&argstr, &max_len, "traddr", fabrics_cfg.traddr) || add_argument(&argstr, &max_len, "host_traddr", fabrics_cfg.host_traddr) || add_argument(&argstr, &max_len, "host_iface", fabrics_cfg.host_iface) || add_argument(&argstr, &max_len, "trsvcid", fabrics_cfg.trsvcid) || ((fabrics_cfg.hostnqn || nvmf_hostnqn_file()) && add_argument(&argstr, &max_len, "hostnqn", fabrics_cfg.hostnqn)) || ((fabrics_cfg.hostid || nvmf_hostid_file()) && add_argument(&argstr, &max_len, "hostid", fabrics_cfg.hostid)) || (!discover && add_int_argument(&argstr, &max_len, "nr_io_queues", fabrics_cfg.nr_io_queues, false)) || add_int_argument(&argstr, &max_len, "nr_write_queues", fabrics_cfg.nr_write_queues, false) || add_int_argument(&argstr, &max_len, "nr_poll_queues", fabrics_cfg.nr_poll_queues, false) || (!discover && add_int_argument(&argstr, &max_len, "queue_size", fabrics_cfg.queue_size, false)) || add_int_argument(&argstr, &max_len, "keep_alive_tmo", fabrics_cfg.keep_alive_tmo, false) || add_int_argument(&argstr, &max_len, "reconnect_delay", fabrics_cfg.reconnect_delay, false) || (strncmp(fabrics_cfg.transport, "loop", 4) && add_int_argument(&argstr, &max_len, "ctrl_loss_tmo", fabrics_cfg.ctrl_loss_tmo, true)) || add_int_argument(&argstr, &max_len, "fast_io_fail_tmo", fabrics_cfg.fast_io_fail_tmo, true) || add_int_argument(&argstr, &max_len, "tos", fabrics_cfg.tos, true) || add_bool_argument(&argstr, &max_len, "duplicate_connect", fabrics_cfg.duplicate_connect) || add_bool_argument(&argstr, &max_len, "disable_sqflow", fabrics_cfg.disable_sqflow) || add_bool_argument(&argstr, &max_len, "hdr_digest", fabrics_cfg.hdr_digest) || add_bool_argument(&argstr, &max_len, "data_digest", fabrics_cfg.data_digest)) return -EINVAL; return 0; } static void set_discovery_kato(struct fabrics_config *cfg) { /* Set kato to NVMF_DEF_DISC_TMO for persistent controllers */ if (cfg->persistent && !cfg->keep_alive_tmo) cfg->keep_alive_tmo = NVMF_DEF_DISC_TMO; /* Set kato to zero for non-persistent controllers */ else if (!cfg->persistent && (cfg->keep_alive_tmo > 0)) cfg->keep_alive_tmo = 0; } static void discovery_trsvcid(struct fabrics_config *fabrics_cfg, bool discover) { if (!strcmp(fabrics_cfg->transport, "tcp")) { if (discover) { /* Default port for NVMe/TCP discovery controllers */ fabrics_cfg->trsvcid = __stringify(NVME_DISC_IP_PORT); } else { /* Default port for NVMe/TCP io controllers */ fabrics_cfg->trsvcid = __stringify(NVME_RDMA_IP_PORT); } } else if (!strcmp(fabrics_cfg->transport, "rdma")) { /* Default port for NVMe/RDMA controllers */ fabrics_cfg->trsvcid = __stringify(NVME_RDMA_IP_PORT); } } static bool traddr_is_hostname(struct fabrics_config *fabrics_cfg) { char addrstr[NVMF_TRADDR_SIZE]; if (!fabrics_cfg->traddr || !fabrics_cfg->transport) return false; if (strcmp(fabrics_cfg->transport, "tcp") && strcmp(fabrics_cfg->transport, "rdma")) return false; if (inet_pton(AF_INET, fabrics_cfg->traddr, addrstr) > 0 || inet_pton(AF_INET6, fabrics_cfg->traddr, addrstr) > 0) return false; return true; } static int hostname2traddr(struct fabrics_config *fabrics_cfg) { struct addrinfo *host_info, hints = {.ai_family = AF_UNSPEC}; char addrstr[NVMF_TRADDR_SIZE]; const char *p; int ret; ret = getaddrinfo(fabrics_cfg->traddr, NULL, &hints, &host_info); if (ret) { msg(LOG_ERR, "failed to resolve host %s info\n", fabrics_cfg->traddr); return ret; } switch (host_info->ai_family) { case AF_INET: p = inet_ntop(host_info->ai_family, &(((struct sockaddr_in *)host_info->ai_addr)->sin_addr), addrstr, NVMF_TRADDR_SIZE); break; case AF_INET6: p = inet_ntop(host_info->ai_family, &(((struct sockaddr_in6 *)host_info->ai_addr)->sin6_addr), addrstr, NVMF_TRADDR_SIZE); break; default: msg(LOG_ERR, "unrecognized address family (%d) %s\n", host_info->ai_family, fabrics_cfg->traddr); ret = -EINVAL; goto free_addrinfo; } if (!p) { msg(LOG_ERR, "failed to get traddr for %s\n", fabrics_cfg->traddr); ret = -errno; goto free_addrinfo; } fabrics_cfg->traddr = strdup(addrstr); free_addrinfo: freeaddrinfo(host_info); return ret; } static int connect_ctrl(struct nvmf_disc_rsp_page_entry *e) { char argstr[BUF_SIZE], *p; const char *transport; bool discover, disable_sqflow = true; int len, ret; retry: p = argstr; discover = false; switch (e->subtype) { case NVME_NQN_DISC: discover = true; case NVME_NQN_NVME: break; default: msg(LOG_ERR, "skipping unsupported subtype %d\n", e->subtype); return -EINVAL; } len = sprintf(p, "nqn=%s", e->subnqn); if (len < 0) return -EINVAL; p += len; if (fabrics_cfg.hostnqn && strcmp(fabrics_cfg.hostnqn, "none")) { len = sprintf(p, ",hostnqn=%s", fabrics_cfg.hostnqn); if (len < 0) return -EINVAL; p += len; } if (fabrics_cfg.hostid && strcmp(fabrics_cfg.hostid, "none")) { len = sprintf(p, ",hostid=%s", fabrics_cfg.hostid); if (len < 0) return -EINVAL; p += len; } if (fabrics_cfg.queue_size && !discover) { len = sprintf(p, ",queue_size=%d", fabrics_cfg.queue_size); if (len < 0) return -EINVAL; p += len; } if (fabrics_cfg.nr_io_queues && !discover) { len = sprintf(p, ",nr_io_queues=%d", fabrics_cfg.nr_io_queues); if (len < 0) return -EINVAL; p += len; } if (fabrics_cfg.nr_write_queues) { len = sprintf(p, ",nr_write_queues=%d", fabrics_cfg.nr_write_queues); if (len < 0) return -EINVAL; p += len; } if (fabrics_cfg.nr_poll_queues) { len = sprintf(p, ",nr_poll_queues=%d", fabrics_cfg.nr_poll_queues); if (len < 0) return -EINVAL; p += len; } if (fabrics_cfg.host_traddr && strcmp(fabrics_cfg.host_traddr, "none")) { len = sprintf(p, ",host_traddr=%s", fabrics_cfg.host_traddr); if (len < 0) return -EINVAL; p+= len; } if (fabrics_cfg.host_iface && strcmp(fabrics_cfg.host_iface, "none")) { len = sprintf(p, ",host_iface=%s", fabrics_cfg.host_iface); if (len < 0) return -EINVAL; p+= len; } if (fabrics_cfg.reconnect_delay) { len = sprintf(p, ",reconnect_delay=%d", fabrics_cfg.reconnect_delay); if (len < 0) return -EINVAL; p += len; } if ((e->trtype != NVMF_TRTYPE_LOOP) && (fabrics_cfg.ctrl_loss_tmo >= -1)) { len = sprintf(p, ",ctrl_loss_tmo=%d", fabrics_cfg.ctrl_loss_tmo); if (len < 0) return -EINVAL; p += len; } if (fabrics_cfg.fast_io_fail_tmo) { len = sprintf(p, ",fast_io_fail_tmo=%d", fabrics_cfg.fast_io_fail_tmo); if (len < 0) return -EINVAL; p += len; } if (fabrics_cfg.tos != -1) { len = sprintf(p, ",tos=%d", fabrics_cfg.tos); if (len < 0) return -EINVAL; p += len; } if (fabrics_cfg.keep_alive_tmo) { len = sprintf(p, ",keep_alive_tmo=%d", fabrics_cfg.keep_alive_tmo); if (len < 0) return -EINVAL; p += len; } transport = trtype_str(e->trtype); if (!strcmp(transport, "unrecognized")) { msg(LOG_ERR, "skipping unsupported transport %d\n", e->trtype); return -EINVAL; } len = sprintf(p, ",transport=%s", transport); if (len < 0) return -EINVAL; p += len; if (fabrics_cfg.hdr_digest) { len = sprintf(p, ",hdr_digest"); if (len < 0) return -EINVAL; p += len; } if (fabrics_cfg.data_digest) { len = sprintf(p, ",data_digest"); if (len < 0) return -EINVAL; p += len; } switch (e->trtype) { case NVMF_TRTYPE_RDMA: case NVMF_TRTYPE_TCP: switch (e->adrfam) { case NVMF_ADDR_FAMILY_IP4: case NVMF_ADDR_FAMILY_IP6: /* FALLTHRU */ len = sprintf(p, ",traddr=%.*s", space_strip_len(NVMF_TRADDR_SIZE, e->traddr), e->traddr); if (len < 0) return -EINVAL; p += len; len = sprintf(p, ",trsvcid=%.*s", space_strip_len(NVMF_TRSVCID_SIZE, e->trsvcid), e->trsvcid); if (len < 0) return -EINVAL; p += len; break; default: msg(LOG_ERR, "skipping unsupported adrfam\n"); return -EINVAL; } break; case NVMF_TRTYPE_FC: switch (e->adrfam) { case NVMF_ADDR_FAMILY_FC: len = sprintf(p, ",traddr=%.*s", space_strip_len(NVMF_TRADDR_SIZE, e->traddr), e->traddr); if (len < 0) return -EINVAL; p += len; break; default: msg(LOG_ERR, "skipping unsupported adrfam\n"); return -EINVAL; } break; } if (e->treq & NVMF_TREQ_DISABLE_SQFLOW && disable_sqflow) { len = sprintf(p, ",disable_sqflow"); if (len < 0) return -EINVAL; p += len; } if (discover) { enum nvme_print_flags flags; flags = validate_output_format(fabrics_cfg.output_format); if (flags < 0) flags = NORMAL; ret = do_discover(argstr, true, flags); } else ret = add_ctrl(argstr); if (ret == -EINVAL && disable_sqflow && e->treq & NVMF_TREQ_DISABLE_SQFLOW) { /* disable_sqflow param might not be supported, try without it */ disable_sqflow = false; goto retry; } return ret; } static bool cargs_match_found(struct nvmf_disc_rsp_page_entry *entry) { struct connect_args cargs __cleanup__(destruct_connect_args) = { NULL, }; struct connect_args *c = tracked_ctrls; cargs.traddr = strdup(entry->traddr); cargs.transport = strdup(trtype_str(entry->trtype)); cargs.subsysnqn = strdup(entry->subnqn); cargs.trsvcid = strdup(entry->trsvcid); cargs.host_traddr = strdup(fabrics_cfg.host_traddr ?: "\0"); cargs.host_iface = strdup(fabrics_cfg.host_iface ?: "\0"); /* check if we have a match in the discovery recursion */ while (c) { if (!strcmp(cargs.subsysnqn, c->subsysnqn) && !strcmp(cargs.transport, c->transport) && !strcmp(cargs.traddr, c->traddr) && !strcmp(cargs.trsvcid, c->trsvcid) && !strcmp(cargs.host_traddr, c->host_traddr) && !strcmp(cargs.host_iface, c->host_iface)) return true; c = c->next; } /* check if we have a matching existing controller */ return find_ctrl_with_connectargs(&cargs) != NULL; } static bool should_connect(struct nvmf_disc_rsp_page_entry *entry) { int len; if (cargs_match_found(entry)) return false; /* skip connect if the transport type doesn't match */ if (strcmp(fabrics_cfg.transport, trtype_str(entry->trtype))) return false; if (!fabrics_cfg.matching_only || !fabrics_cfg.traddr) return true; len = space_strip_len(NVMF_TRADDR_SIZE, entry->traddr); return !strncmp(fabrics_cfg.traddr, entry->traddr, len); } static int connect_ctrls(struct nvmf_disc_rsp_page_hdr *log, int numrec) { int i; int instance; int ret = 0; for (i = 0; i < numrec; i++) { if (!should_connect(&log->entries[i])) continue; instance = connect_ctrl(&log->entries[i]); /* clean success */ if (instance >= 0) continue; /* already connected print message */ if (instance == -EALREADY) { const char *traddr = log->entries[i].traddr; msg(LOG_NOTICE, "traddr=%.*s is already connected\n", space_strip_len(NVMF_TRADDR_SIZE, traddr), traddr); continue; } /* * don't error out. The Discovery Log may contain * devices that aren't necessarily connectable via * the system/host transport port. Let those items * fail and continue on to the next log element. */ } return ret; } static void nvmf_get_host_identifiers(int ctrl_instance) { char *path; if (asprintf(&path, "%s/nvme%d", SYS_NVME, ctrl_instance) < 0) return; fabrics_cfg.hostnqn = nvme_get_ctrl_attr(path, "hostnqn"); fabrics_cfg.hostid = nvme_get_ctrl_attr(path, "hostid"); } static DEFINE_CLEANUP_FUNC(cleanup_log, struct nvmf_disc_rsp_page_hdr *, free); int do_discover(char *argstr, bool connect, enum nvme_print_flags flags) { struct nvmf_disc_rsp_page_hdr *log __cleanup__(cleanup_log) = NULL; char *dev_name; int instance, numrec = 0, ret, err; int status = 0; struct connect_args *cargs; cargs = extract_connect_args(argstr); if (!cargs) return -ENOMEM; if (fabrics_cfg.device && !ctrl_matches_connectargs(fabrics_cfg.device, cargs)) { free(fabrics_cfg.device); fabrics_cfg.device = NULL; } if (!fabrics_cfg.device) fabrics_cfg.device = find_ctrl_with_connectargs(cargs); free_connect_args(cargs); if (!fabrics_cfg.device) { instance = add_ctrl(argstr); } else { instance = ctrl_instance(fabrics_cfg.device); nvmf_get_host_identifiers(instance); } if (instance < 0) return instance; if (asprintf(&dev_name, "/dev/nvme%d", instance) < 0) return -errno; ret = nvmf_get_log_page_discovery(dev_name, &log, &numrec, &status); free(dev_name); if (!fabrics_cfg.device && !fabrics_cfg.persistent) { err = remove_ctrl(instance); if (err) return err; } switch (ret) { case DISC_OK: if (connect) ret = connect_ctrls(log, numrec); else if (fabrics_cfg.raw || flags == BINARY) save_discovery_log(log, numrec); else if (flags == JSON) json_discovery_log(log, numrec, instance); else print_discovery_log(log, numrec, instance); break; case DISC_GET_NUMRECS: msg(LOG_ERR, "Get number of discovery log entries failed.\n"); ret = status; break; case DISC_GET_LOG: msg(LOG_ERR, "Get discovery log entries failed.\n"); ret = status; break; case DISC_NO_LOG: fprintf(stdout, "No discovery log entries to fetch.\n"); ret = DISC_OK; break; case DISC_RETRY_EXHAUSTED: fprintf(stdout, "Discovery retries exhausted.\n"); ret = -EAGAIN; break; case DISC_NOT_EQUAL: msg(LOG_ERR, "Numrec values of last two get discovery log page not equal\n"); ret = -EBADSLT; break; default: msg(LOG_ERR, "Get discovery log page failed: %d\n", ret); break; } return ret; } static int discover_from_conf_file(const char *desc, char *argstr, const struct argconfig_commandline_options *opts, bool connect) { FILE *f; char line[256], *ptr, *all_args, *args, **argv; int argc, err, ret = 0; f = fopen(PATH_NVMF_DISC, "r"); if (f == NULL) { msg(LOG_ERR, "No discover params given and no %s\n", PATH_NVMF_DISC); return -EINVAL; } while (fgets(line, sizeof(line), f) != NULL) { enum nvme_print_flags flags; if (line[0] == '#' || line[0] == '\n') continue; args = strdup(line); if (!args) { msg(LOG_ERR, "failed to strdup args\n"); ret = -ENOMEM; goto out; } all_args = args; argv = calloc(MAX_DISC_ARGS, BUF_SIZE); if (!argv) { msg(LOG_ERR, "failed to allocate argv vector: %m\n"); free(args); ret = -ENOMEM; goto out; } argc = 0; argv[argc++] = "discover"; while ((ptr = strsep(&args, " =\n")) != NULL) argv[argc++] = ptr; err = argconfig_parse(argc, argv, desc, opts); if (err) goto free_and_continue; if (!fabrics_cfg.transport || !fabrics_cfg.traddr) goto free_and_continue; err = flags = validate_output_format(fabrics_cfg.output_format); if (err < 0) goto free_and_continue; set_discovery_kato(&fabrics_cfg); if (traddr_is_hostname(&fabrics_cfg)) { ret = hostname2traddr(&fabrics_cfg); if (ret) goto out; } if (!fabrics_cfg.trsvcid) discovery_trsvcid(&fabrics_cfg, true); err = build_options(argstr, BUF_SIZE, true); if (err) { ret = err; goto free_and_continue; } err = do_discover(argstr, connect, flags); if (err) ret = err; free_and_continue: free(all_args); free(argv); fabrics_cfg.transport = fabrics_cfg.traddr = fabrics_cfg.trsvcid = fabrics_cfg.host_traddr = fabrics_cfg.host_iface = NULL; } out: fclose(f); return ret; } int fabrics_discover(const char *desc, int argc, char **argv, bool connect) { char argstr[BUF_SIZE]; int ret; enum nvme_print_flags flags; bool quiet = false; OPT_ARGS(opts) = { OPT_LIST("transport", 't', &fabrics_cfg.transport, "transport type"), OPT_LIST("traddr", 'a', &fabrics_cfg.traddr, "transport address"), OPT_LIST("trsvcid", 's', &fabrics_cfg.trsvcid, "transport service id (e.g. IP port)"), OPT_LIST("host-traddr", 'w', &fabrics_cfg.host_traddr, "host traddr (e.g. FC WWN's or IP source address)"), OPT_LIST("host-iface", 'f', &fabrics_cfg.host_iface, "host transport interface (e.g. IP eth1, enp2s0)"), OPT_LIST("hostnqn", 'q', &fabrics_cfg.hostnqn, "user-defined hostnqn (if default not used)"), OPT_LIST("hostid", 'I', &fabrics_cfg.hostid, "user-defined hostid (if default not used)"), OPT_LIST("raw", 'r', &fabrics_cfg.raw, "raw output file"), OPT_LIST("device", 'd', &fabrics_cfg.device, "existing discovery controller device"), OPT_INT("keep-alive-tmo", 'k', &fabrics_cfg.keep_alive_tmo, "keep alive timeout period in seconds"), OPT_INT("reconnect-delay", 'c', &fabrics_cfg.reconnect_delay, "reconnect timeout period in seconds"), OPT_INT("ctrl-loss-tmo", 'l', &fabrics_cfg.ctrl_loss_tmo, "controller loss timeout period in seconds"), OPT_INT("fast_io_fail_tmo",'f',&fabrics_cfg.fast_io_fail_tmo, "fast I/O fail timeout (default off)"), OPT_INT("tos", 'T', &fabrics_cfg.tos, "type of service"), OPT_FLAG("hdr_digest", 'g', &fabrics_cfg.hdr_digest, "enable transport protocol header digest (TCP transport)"), OPT_FLAG("data_digest", 'G', &fabrics_cfg.data_digest, "enable transport protocol data digest (TCP transport)"), OPT_INT("nr-io-queues", 'i', &fabrics_cfg.nr_io_queues, "number of io queues to use (default is core count)"), OPT_INT("nr-write-queues", 'W', &fabrics_cfg.nr_write_queues, "number of write queues to use (default 0)"), OPT_INT("nr-poll-queues", 'P', &fabrics_cfg.nr_poll_queues, "number of poll queues to use (default 0)"), OPT_INT("queue-size", 'Q', &fabrics_cfg.queue_size, "number of io queue elements to use (default 128)"), OPT_FLAG("persistent", 'p', &fabrics_cfg.persistent, "persistent discovery connection"), OPT_FLAG("quiet", 'S', &quiet, "suppress already connected errors"), OPT_FLAG("matching", 'm', &fabrics_cfg.matching_only, "connect only records matching the traddr"), OPT_FMT("output-format", 'o', &fabrics_cfg.output_format, output_format), OPT_END() }; fabrics_cfg.tos = -1; ret = argconfig_parse(argc, argv, desc, opts); if (ret) goto out; ret = flags = validate_output_format(fabrics_cfg.output_format); if (ret < 0) goto out; if (fabrics_cfg.device && strcmp(fabrics_cfg.device, "none")) { fabrics_cfg.device = strdup(fabrics_cfg.device); if (!fabrics_cfg.device) { ret = -ENOMEM; goto out; } } if (quiet) log_level = LOG_WARNING; if (fabrics_cfg.device && !strcmp(fabrics_cfg.device, "none")) fabrics_cfg.device = NULL; fabrics_cfg.nqn = NVME_DISC_SUBSYS_NAME; if (!fabrics_cfg.transport && !fabrics_cfg.traddr) { ret = discover_from_conf_file(desc, argstr, opts, connect); } else { set_discovery_kato(&fabrics_cfg); if (traddr_is_hostname(&fabrics_cfg)) { ret = hostname2traddr(&fabrics_cfg); if (ret) goto out; } if (!fabrics_cfg.trsvcid) discovery_trsvcid(&fabrics_cfg, true); ret = build_options(argstr, BUF_SIZE, true); if (ret) goto out; ret = do_discover(argstr, connect, flags); } out: return nvme_status_to_errno(ret, true); } int fabrics_connect(const char *desc, int argc, char **argv) { char argstr[BUF_SIZE]; int instance, ret; enum nvme_print_flags flags = -1; OPT_ARGS(opts) = { OPT_LIST("transport", 't', &fabrics_cfg.transport, "transport type"), OPT_LIST("nqn", 'n', &fabrics_cfg.nqn, "nqn name"), OPT_LIST("traddr", 'a', &fabrics_cfg.traddr, "transport address"), OPT_LIST("trsvcid", 's', &fabrics_cfg.trsvcid, "transport service id (e.g. IP port)"), OPT_LIST("host-traddr", 'w', &fabrics_cfg.host_traddr, "host traddr (e.g. FC WWN's or IP source address)"), OPT_LIST("host-iface", 'f', &fabrics_cfg.host_iface, "host transport interface (e.g. IP eth1, enp2s0)"), OPT_LIST("hostnqn", 'q', &fabrics_cfg.hostnqn, "user-defined hostnqn"), OPT_LIST("hostid", 'I', &fabrics_cfg.hostid, "user-defined hostid (if default not used)"), OPT_INT("nr-io-queues", 'i', &fabrics_cfg.nr_io_queues, "number of io queues to use (default is core count)"), OPT_INT("nr-write-queues", 'W', &fabrics_cfg.nr_write_queues, "number of write queues to use (default 0)"), OPT_INT("nr-poll-queues", 'P', &fabrics_cfg.nr_poll_queues, "number of poll queues to use (default 0)"), OPT_INT("queue-size", 'Q', &fabrics_cfg.queue_size, "number of io queue elements to use (default 128)"), OPT_INT("keep-alive-tmo", 'k', &fabrics_cfg.keep_alive_tmo, "keep alive timeout period in seconds"), OPT_INT("reconnect-delay", 'c', &fabrics_cfg.reconnect_delay, "reconnect timeout period in seconds"), OPT_INT("ctrl-loss-tmo", 'l', &fabrics_cfg.ctrl_loss_tmo, "controller loss timeout period in seconds"), OPT_INT("fast_io_fail_tmo", 'f', &fabrics_cfg.fast_io_fail_tmo, "fast I/O fail timeout (default off)"), OPT_INT("tos", 'T', &fabrics_cfg.tos, "type of service"), OPT_FLAG("duplicate-connect", 'D', &fabrics_cfg.duplicate_connect, "allow duplicate connections between same transport host and subsystem port"), OPT_FLAG("disable-sqflow", 'd', &fabrics_cfg.disable_sqflow, "disable controller sq flow control (default false)"), OPT_FLAG("hdr-digest", 'g', &fabrics_cfg.hdr_digest, "enable transport protocol header digest (TCP transport)"), OPT_FLAG("data-digest", 'G', &fabrics_cfg.data_digest, "enable transport protocol data digest (TCP transport)"), OPT_FMT("output-format", 'o', &fabrics_cfg.output_format, "Output format: normal|json"), OPT_END() }; fabrics_cfg.output_format = ""; fabrics_cfg.tos = -1; ret = argconfig_parse(argc, argv, desc, opts); if (ret) goto out; if (!strcmp(fabrics_cfg.output_format, "")) flags = -1; else if (!strcmp(fabrics_cfg.output_format, "normal")) flags = NORMAL; else if (!strcmp(fabrics_cfg.output_format, "json")) flags = JSON; else { ret = -EINVAL; goto out; } if (traddr_is_hostname(&fabrics_cfg)) { ret = hostname2traddr(&fabrics_cfg); if (ret) goto out; } if (!fabrics_cfg.trsvcid) discovery_trsvcid(&fabrics_cfg, false); ret = build_options(argstr, BUF_SIZE, false); if (ret) goto out; if (!fabrics_cfg.nqn) { msg(LOG_ERR, "need a -n argument\n"); ret = -EINVAL; goto out; } instance = add_ctrl(argstr); if (instance < 0) ret = instance; else { if (flags == NORMAL) print_connect_msg(instance); else if (flags == JSON) json_connect_msg(instance); } out: return nvme_status_to_errno(ret, true); } static int scan_sys_nvme_filter(const struct dirent *d) { if (!strcmp(d->d_name, ".")) return 0; if (!strcmp(d->d_name, "..")) return 0; return 1; } /* * Returns 1 if disconnect occurred, 0 otherwise. */ static int disconnect_subsys(const char *nqn, char *ctrl) { char *sysfs_nqn_path = NULL, *sysfs_del_path = NULL; char subsysnqn[NVMF_NQN_SIZE] = {}; int fd, ret = 0; if (asprintf(&sysfs_nqn_path, "%s/%s/subsysnqn", SYS_NVME, ctrl) < 0) goto free; if (asprintf(&sysfs_del_path, "%s/%s/delete_controller", SYS_NVME, ctrl) < 0) goto free; fd = open(sysfs_nqn_path, O_RDONLY); if (fd < 0) { msg(LOG_ERR, "Failed to open %s: %s\n", sysfs_nqn_path, strerror(errno)); goto free; } if (read(fd, subsysnqn, NVMF_NQN_SIZE) < 0) goto close; subsysnqn[strcspn(subsysnqn, "\n")] = '\0'; if (strcmp(subsysnqn, nqn)) goto close; if (!remove_ctrl_by_path(sysfs_del_path)) ret = 1; close: close(fd); free: free(sysfs_del_path); free(sysfs_nqn_path); return ret; } /* * Returns the number of controllers successfully disconnected. */ static int disconnect_by_nqn(const char *nqn) { struct dirent **devices = NULL; int i, n, ret = 0; if (strlen(nqn) > NVMF_NQN_SIZE) return -EINVAL; n = scandir(SYS_NVME, &devices, scan_sys_nvme_filter, alphasort); if (n < 0) return n; for (i = 0; i < n; i++) ret += disconnect_subsys(nqn, devices[i]->d_name); for (i = 0; i < n; i++) free(devices[i]); free(devices); return ret; } static int disconnect_by_device(const char *device) { int instance; instance = ctrl_instance(device); if (instance < 0) return instance; return remove_ctrl(instance); } int fabrics_disconnect(const char *desc, int argc, char **argv) { const char *nqn = "nqn name"; const char *device = "nvme device"; int ret; OPT_ARGS(opts) = { OPT_LIST("nqn", 'n', &fabrics_cfg.nqn, nqn), OPT_LIST("device", 'd', &fabrics_cfg.device, device), OPT_END() }; ret = argconfig_parse(argc, argv, desc, opts); if (ret) goto out; if (!fabrics_cfg.nqn && !fabrics_cfg.device) { msg(LOG_ERR, "need a -n or -d argument\n"); ret = -EINVAL; goto out; } if (fabrics_cfg.nqn) { ret = disconnect_by_nqn(fabrics_cfg.nqn); if (ret < 0) msg(LOG_ERR, "Failed to disconnect by NQN: %s\n", fabrics_cfg.nqn); else { printf("NQN:%s disconnected %d controller(s)\n", fabrics_cfg.nqn, ret); ret = 0; } } if (fabrics_cfg.device) { ret = disconnect_by_device(fabrics_cfg.device); if (ret) msg(LOG_ERR, "Failed to disconnect by device name: %s\n", fabrics_cfg.device); } out: return nvme_status_to_errno(ret, true); } int fabrics_disconnect_all(const char *desc, int argc, char **argv) { struct nvme_topology t = { }; int i, j, err; OPT_ARGS(opts) = { OPT_END() }; err = argconfig_parse(argc, argv, desc, opts); if (err) goto out; err = scan_subsystems(&t, NULL, 0, 0, NULL); if (err) { msg(LOG_ERR, "Failed to scan namespaces\n"); goto out; } for (i = 0; i < t.nr_subsystems; i++) { struct nvme_subsystem *s = &t.subsystems[i]; for (j = 0; j < s->nr_ctrls; j++) { struct nvme_ctrl *c = &s->ctrls[j]; if (!c->transport || !strcmp(c->transport, "pcie")) continue; err = disconnect_by_device(c->name); if (err) goto free; } } free: free_topology(&t); out: return nvme_status_to_errno(err, true); }