// SPDX-License-Identifier: GPL-2.0-or-later #include #include #include #include #include #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); } } /* returns 0 for success or negative errno otherwise */ static int do_connect(nvme_root_t r, nvme_host_t h, struct nbft_info_subsystem_ns *ss, struct tr_config *trcfg, const struct nvme_fabrics_config *cfg, enum nvme_print_flags 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); } 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; } 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, const struct nvme_fabrics_config *cfg, char *nbft_path, enum nvme_print_flags 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; 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; for (ss = entry->nbft->subsystem_ns_list; ss && *ss; ss++) for (i = 0; i < (*ss)->num_hfis; i++) { hfi = (*ss)->hfis[i]; if (!cfg->host_traddr) { host_traddr = NULL; if (!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, *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((*ss)->transport, "tcp") && strlen(hfi->tcp_info.dhcp_server_ipaddr) > 0) { trcfg.host_traddr = NULL; ret = do_connect(r, h, *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; } } out_free: free_nbfts(&nbft_list); return errno; }