// SPDX-License-Identifier: GPL-2.0-or-later #include #include #include #include #include #include "common.h" #include "nvme.h" #include "nbft.h" #include "fabrics.h" #include "nvme-print.h" #include "util/types.h" #include "util/logging.h" #define NBFT_SYSFS_FILENAME "NBFT*" static void print_connect_msg(nvme_ctrl_t c) { printf("device: %s\n", nvme_ctrl_get_name(c)); } static void json_connect_msg(nvme_ctrl_t c) { #ifdef CONFIG_JSONC struct json_object *root; root = json_create_object(); json_object_add_value_string(root, "device", nvme_ctrl_get_name(c)); json_print_object(root, NULL); printf("\n"); json_free_object(root); #endif } int nbft_filter(const struct dirent *dent) { return !fnmatch(NBFT_SYSFS_FILENAME, dent->d_name, FNM_PATHNAME); } int read_nbft_files(struct list_head *nbft_list, char *path) { struct dirent **dent; char filename[PATH_MAX]; int i, count, ret; struct nbft_file_entry *entry; struct nbft_info *nbft; count = scandir(path, &dent, nbft_filter, NULL); if (count < 0) return -errno; for (i = 0; i < count; i++) { snprintf(filename, sizeof(filename), "%s/%s", path, dent[i]->d_name); ret = nvme_nbft_read(&nbft, filename); if (!ret) { entry = calloc(1, sizeof(*entry)); entry->nbft = nbft; list_add_tail(nbft_list, &entry->node); } free(dent[i]); } free(dent); return 0; } void free_nbfts(struct list_head *nbft_list) { struct nbft_file_entry *entry; while ((entry = list_pop(nbft_list, struct nbft_file_entry, node))) { nvme_nbft_free(entry->nbft); free(entry); } } static bool validate_uri(struct nbft_info_discovery *dd, struct nvme_fabrics_uri *uri) { if (!uri) { fprintf(stderr, "Discovery Descriptor %d: failed to parse URI %s\n", dd->index, dd->uri); return false; } if (strcmp(uri->scheme, "nvme") != 0) { fprintf(stderr, "Discovery Descriptor %d: unsupported scheme '%s'\n", dd->index, uri->scheme); return false; } if (!uri->protocol || strcmp(uri->protocol, "tcp") != 0) { fprintf(stderr, "Discovery Descriptor %d: unsupported transport '%s'\n", dd->index, uri->protocol); return false; } return true; } /* returns 0 for success or negative errno otherwise */ static int do_connect(nvme_root_t r, nvme_host_t h, struct nvmf_disc_log_entry *e, struct nbft_info_subsystem_ns *ss, struct tr_config *trcfg, struct nvme_fabrics_config *cfg, nvme_print_flags_t flags, unsigned int verbose) { nvme_ctrl_t c; int saved_log_level = log_level; bool saved_log_pid = false; bool saved_log_tstamp = false; int ret; /* Already connected ? */ c = lookup_ctrl(h, trcfg); if (c && nvme_ctrl_get_name(c)) return 0; c = nvme_create_ctrl(r, trcfg->subsysnqn, trcfg->transport, trcfg->traddr, trcfg->host_traddr, trcfg->host_iface, trcfg->trsvcid); if (!c) return -ENOMEM; /* Pause logging for unavailable SSNSs */ if (ss && ss->unavailable && verbose < 1) { saved_log_level = nvme_get_logging_level(r, &saved_log_pid, &saved_log_tstamp); nvme_init_logging(r, -1, false, false); } if (e) { if (e->trtype == NVMF_TRTYPE_TCP && e->tsas.tcp.sectype != NVMF_TCP_SECTYPE_NONE) cfg->tls = true; } errno = 0; ret = nvmf_add_ctrl(h, c, cfg); /* Resume logging */ if (ss && ss->unavailable && verbose < 1) nvme_init_logging(r, saved_log_level, saved_log_pid, saved_log_tstamp); if (ret == -1) { nvme_free_ctrl(c); /* * In case this SSNS was marked as 'unavailable' and * our connection attempt has failed, ignore it. */ if (ss && ss->unavailable) { if (verbose >= 1) fprintf(stderr, "SSNS %d reported as unavailable, skipping\n", ss->index); return 0; } return -errno; } if (flags == NORMAL) print_connect_msg(c); else if (flags == JSON) json_connect_msg(c); return 0; } static int do_discover(struct nbft_info_discovery *dd, nvme_root_t r, nvme_host_t h, nvme_ctrl_t c, struct nvme_fabrics_config *defcfg, struct tr_config *deftrcfg, nvme_print_flags_t flags, unsigned int verbose) { struct nvmf_discovery_log *log = NULL; int i; int ret; struct nvme_get_discovery_args args = { .c = c, .args_size = sizeof(args), .max_retries = 10 /* MAX_DISC_RETRIES */, .result = 0, .timeout = NVME_DEFAULT_IOCTL_TIMEOUT, .lsp = 0, }; log = nvmf_get_discovery_wargs(&args); if (!log) { fprintf(stderr, "Discovery Descriptor %d: failed to get discovery log: %s\n", dd->index, nvme_strerror(errno)); return -errno; } for (i = 0; i < le64_to_cpu(log->numrec); i++) { struct nvmf_disc_log_entry *e = &log->entries[i]; nvme_ctrl_t cl; int tmo = defcfg->keep_alive_tmo; struct tr_config trcfg = { .subsysnqn = e->subnqn, .transport = nvmf_trtype_str(e->trtype), .traddr = e->traddr, .host_traddr = deftrcfg->host_traddr, .host_iface = deftrcfg->host_iface, .trsvcid = e->trsvcid, }; if (e->subtype == NVME_NQN_CURR) continue; /* 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) { nvme_ctrl_t child; child = nvmf_connect_disc_entry(h, e, defcfg, NULL); do_discover(dd, r, h, child, defcfg, &trcfg, flags, verbose); nvme_disconnect_ctrl(child); nvme_free_ctrl(child); } else { ret = do_connect(r, h, e, NULL, &trcfg, defcfg, flags, verbose); /* * With TCP/DHCP, it can happen that the OS * obtains a different local IP address than the * firmware had. Retry without host_traddr. */ if (ret == -ENVME_CONNECT_ADDRNOTAVAIL && !strcmp(trcfg.transport, "tcp") && strlen(dd->hfi->tcp_info.dhcp_server_ipaddr) > 0) { const char *htradr = trcfg.host_traddr; trcfg.host_traddr = NULL; ret = do_connect(r, h, e, NULL, &trcfg, defcfg, flags, verbose); if (ret == 0 && verbose >= 1) fprintf(stderr, "Discovery Descriptor %d: connect with host_traddr=\"%s\" failed, success after omitting host_traddr\n", dd->index, htradr); } if (ret) fprintf(stderr, "Discovery Descriptor %d: no controller found\n", dd->index); if (ret == -ENOMEM) break; } defcfg->keep_alive_tmo = tmo; } free(log); return 0; } int discover_from_nbft(nvme_root_t r, char *hostnqn_arg, char *hostid_arg, char *hostnqn_sys, char *hostid_sys, const char *desc, bool connect, struct nvme_fabrics_config *cfg, char *nbft_path, nvme_print_flags_t flags, unsigned int verbose) { char *hostnqn = NULL, *hostid = NULL, *host_traddr = NULL; nvme_host_t h; int ret, i; struct list_head nbft_list; struct nbft_file_entry *entry = NULL; struct nbft_info_subsystem_ns **ss; struct nbft_info_hfi *hfi; struct nbft_info_discovery **dd; if (!connect) /* to do: print discovery-type info from NBFT tables */ return 0; list_head_init(&nbft_list); ret = read_nbft_files(&nbft_list, nbft_path); if (ret) { if (ret != -ENOENT) nvme_show_perror("Failed to access ACPI tables directory"); goto out_free; } list_for_each(&nbft_list, entry, node) { if (hostnqn_arg) hostnqn = hostnqn_arg; else { hostnqn = entry->nbft->host.nqn; if (!hostnqn) hostnqn = hostnqn_sys; } if (hostid_arg) hostid = hostid_arg; else if (*entry->nbft->host.id) { hostid = (char *)util_uuid_to_string(entry->nbft->host.id); if (!hostid) hostid = hostid_sys; } h = nvme_lookup_host(r, hostnqn, hostid); if (!h) goto out_free; /* Subsystem Namespace Descriptor List */ for (ss = entry->nbft->subsystem_ns_list; ss && *ss; ss++) for (i = 0; i < (*ss)->num_hfis; i++) { hfi = (*ss)->hfis[i]; /* Skip discovery NQN records */ if (strcmp((*ss)->subsys_nqn, NVME_DISC_SUBSYS_NAME) == 0) { if (verbose >= 1) fprintf(stderr, "SSNS %d points to well-known discovery NQN, skipping\n", (*ss)->index); continue; } host_traddr = NULL; if (!cfg->host_traddr && !strncmp((*ss)->transport, "tcp", 3)) host_traddr = hfi->tcp_info.ipaddr; struct tr_config trcfg = { .subsysnqn = (*ss)->subsys_nqn, .transport = (*ss)->transport, .traddr = (*ss)->traddr, .host_traddr = host_traddr, .host_iface = NULL, .trsvcid = (*ss)->trsvcid, }; ret = do_connect(r, h, NULL, *ss, &trcfg, cfg, flags, verbose); /* * With TCP/DHCP, it can happen that the OS * obtains a different local IP address than the * firmware had. Retry without host_traddr. */ if (ret == -ENVME_CONNECT_ADDRNOTAVAIL && !strcmp(trcfg.transport, "tcp") && strlen(hfi->tcp_info.dhcp_server_ipaddr) > 0) { trcfg.host_traddr = NULL; ret = do_connect(r, h, NULL, *ss, &trcfg, cfg, flags, verbose); if (ret == 0 && verbose >= 1) fprintf(stderr, "SSNS %d: connect with host_traddr=\"%s\" failed, success after omitting host_traddr\n", (*ss)->index, host_traddr); } if (ret) fprintf(stderr, "SSNS %d: no controller found\n", (*ss)->index); if (ret == -ENOMEM) goto out_free; } /* Discovery Descriptor List */ for (dd = entry->nbft->discovery_list; dd && *dd; dd++) { nvme_ctrl_t c; bool linked = false; bool persistent = false; _cleanup_uri_ struct nvme_fabrics_uri *uri = NULL; _cleanup_free_ char *trsvcid = NULL; /* only perform discovery when no SSNS record references it */ for (ss = entry->nbft->subsystem_ns_list; ss && *ss; ss++) if ((*ss)->discovery && (*ss)->discovery->index == (*dd)->index && /* unavailable boot attempts are not discovered * and may get transferred along with a well-known * discovery NQN into an SSNS record. */ strcmp((*ss)->subsys_nqn, NVME_DISC_SUBSYS_NAME) != 0) { linked = true; break; } if (linked) continue; hfi = (*dd)->hfi; uri = nvme_parse_uri((*dd)->uri); if (!validate_uri(*dd, uri)) continue; host_traddr = NULL; if (!cfg->host_traddr && !strncmp(uri->protocol, "tcp", 3)) host_traddr = hfi->tcp_info.ipaddr; if (uri->port > 0) { if (asprintf(&trsvcid, "%d", uri->port) < 0) { errno = ENOMEM; goto out_free; } } else trsvcid = strdup(nvmf_get_default_trsvcid(uri->protocol, true)); struct tr_config trcfg = { .subsysnqn = NVME_DISC_SUBSYS_NAME, .transport = uri->protocol, .traddr = uri->host, .host_traddr = host_traddr, .host_iface = NULL, .trsvcid = trsvcid, }; /* Lookup existing discovery controller */ c = lookup_ctrl(h, &trcfg); if (c && nvme_ctrl_get_name(c)) persistent = true; if (!c) { c = nvmf_create_discover_ctrl(r, h, cfg, &trcfg); if (!c && errno == ENVME_CONNECT_ADDRNOTAVAIL && !strcmp(trcfg.transport, "tcp") && strlen(hfi->tcp_info.dhcp_server_ipaddr) > 0) { trcfg.host_traddr = NULL; c = nvmf_create_discover_ctrl(r, h, cfg, &trcfg); } } if (!c) { fprintf(stderr, "Discovery Descriptor %d: failed to add discovery controller: %s\n", (*dd)->index, nvme_strerror(errno)); if (errno == ENOMEM) goto out_free; continue; } ret = do_discover(*dd, r, h, c, cfg, &trcfg, flags, verbose); if (!persistent) nvme_disconnect_ctrl(c); nvme_free_ctrl(c); if (ret == -ENOMEM) goto out_free; } } out_free: free_nbfts(&nbft_list); return errno; }