diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2023-06-30 22:36:10 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2023-06-30 22:36:10 +0000 |
commit | 61d0a8bdffbbb7229776d2f4f2e79ed22d21551f (patch) | |
tree | 2e249969fedce45eb37ae6314ad167595900fe38 /src/nvme/nbft.c | |
parent | Releasing debian version 1.4-4. (diff) | |
download | libnvme-61d0a8bdffbbb7229776d2f4f2e79ed22d21551f.tar.xz libnvme-61d0a8bdffbbb7229776d2f4f2e79ed22d21551f.zip |
Merging upstream version 1.5.
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'src/nvme/nbft.c')
-rw-r--r-- | src/nvme/nbft.c | 726 |
1 files changed, 726 insertions, 0 deletions
diff --git a/src/nvme/nbft.c b/src/nvme/nbft.c new file mode 100644 index 0000000..a1e17cd --- /dev/null +++ b/src/nvme/nbft.c @@ -0,0 +1,726 @@ +// SPDX-License-Identifier: LGPL-2.1-or-later +/* + * This file is part of libnvme. + * Copyright (c) 2021-2022, Dell Inc. or its subsidiaries. All Rights Reserved. + * + * Authors: Stuart Hayes <Stuart_Hayes@Dell.com> + * + */ + +#include <errno.h> +#include <stdio.h> +#include <stdlib.h> + +#include <arpa/inet.h> +#include <ccan/endian/endian.h> + +#include "private.h" +#include "nbft.h" +#include "log.h" + + +#define MIN(a, b) (((a) < (b)) ? (a) : (b)) + +static __u8 csum(const __u8 *buffer, ssize_t length) +{ + int n; + __u8 sum = 0; + + for (n = 0; n < length; n++) + sum = (__u8)(sum + ((__u8 *)buffer)[n]); + return sum; +} + +static void format_ip_addr(char *buf, size_t buflen, __u8 *addr) +{ + struct in6_addr *addr_ipv6; + + addr_ipv6 = (struct in6_addr *)addr; + if (addr_ipv6->s6_addr32[0] == 0 && + addr_ipv6->s6_addr32[1] == 0 && + ntohl(addr_ipv6->s6_addr32[2]) == 0xffff) + /* ipv4 */ + inet_ntop(AF_INET, &(addr_ipv6->s6_addr32[3]), buf, buflen); + else + /* ipv6 */ + inet_ntop(AF_INET6, addr_ipv6, buf, buflen); +} + +static bool in_heap(struct nbft_header *header, struct nbft_heap_obj obj) +{ + if (le16_to_cpu(obj.length) == 0) + return true; + if (le32_to_cpu(obj.offset) < le32_to_cpu(header->heap_offset)) + return false; + if (le32_to_cpu(obj.offset) > + le32_to_cpu(header->heap_offset) + le32_to_cpu(header->heap_length)) + return false; + if (le32_to_cpu(obj.offset) + le16_to_cpu(obj.length) > + le32_to_cpu(header->heap_offset) + le32_to_cpu(header->heap_length)) + return false; + return true; +} + +/* + * Return transport_type string (NBFT Table 2) + */ +static char *trtype_to_string(__u8 transport_type) +{ + switch (transport_type) { + case 3: + return "tcp"; + default: + return "invalid"; + } +} + +#define verify(condition, message) \ + do { \ + if (!(condition)) { \ + nvme_msg(NULL, LOG_DEBUG, "file %s: " message "\n", \ + nbft->filename); \ + return -EINVAL; \ + } \ + } while (0) + +static int __get_heap_obj(struct nbft_header *header, const char *filename, + const char *descriptorname, const char *fieldname, + struct nbft_heap_obj obj, bool is_string, + char **output) +{ + if (le16_to_cpu(obj.length) == 0) + return -ENOENT; + + if (!in_heap(header, obj)) { + nvme_msg(NULL, LOG_DEBUG, + "file %s: field '%s' in descriptor '%s' has invalid offset or length\n", + filename, fieldname, descriptorname); + return -EINVAL; + } + + /* check that string is zero terminated correctly */ + *output = (char *)header + le32_to_cpu(obj.offset); + + if (is_string) { + if (strnlen(*output, le16_to_cpu(obj.length) + 1) < le16_to_cpu(obj.length)) + nvme_msg(NULL, LOG_DEBUG, + "file %s: string '%s' in descriptor '%s' is shorter (%zd) than specified length (%d)\n", + filename, fieldname, descriptorname, + strnlen(*output, le16_to_cpu(obj.length) + 1), + le16_to_cpu(obj.length)); + else if (strnlen(*output, le16_to_cpu(obj.length) + 1) > + le16_to_cpu(obj.length)) { + nvme_msg(NULL, LOG_DEBUG, + "file %s: string '%s' in descriptor '%s' is not zero terminated\n", + filename, fieldname, descriptorname); + return -EINVAL; + } + } + + return 0; +} + +#define get_heap_obj(descriptor, obj, is_string, output) \ + __get_heap_obj(header, nbft->filename, \ + stringify(descriptor), stringify(obj), \ + descriptor->obj, is_string, \ + output) + +static struct nbft_info_discovery *discovery_from_index(struct nbft_info *nbft, int i) +{ + struct nbft_info_discovery **d; + + for (d = nbft->discovery_list; d && *d; d++) { + if ((*d)->index == i) + return *d; + } + return NULL; +} + +static struct nbft_info_hfi *hfi_from_index(struct nbft_info *nbft, int i) +{ + struct nbft_info_hfi **h; + + for (h = nbft->hfi_list; h && *h; h++) { + if ((*h)->index == i) + return *h; + } + return NULL; +} + +static struct nbft_info_security *security_from_index(struct nbft_info *nbft, int i) +{ + struct nbft_info_security **s; + + for (s = nbft->security_list; s && *s; s++) { + if ((*s)->index == i) + return *s; + } + return NULL; +} + +static int read_ssns_exended_info(struct nbft_info *nbft, + struct nbft_info_subsystem_ns *ssns, + struct nbft_ssns_ext_info *raw_ssns_ei) +{ + struct nbft_header *header = (struct nbft_header *)nbft->raw_nbft; + + verify(raw_ssns_ei->structure_id == NBFT_DESC_SSNS_EXT_INFO, + "invalid ID in SSNS extended info descriptor"); + verify(raw_ssns_ei->version == 1, + "invalid version in SSNS extended info descriptor"); + verify(le16_to_cpu(raw_ssns_ei->ssns_index) == ssns->index, + "SSNS index doesn't match extended info descriptor index"); + + if (!(le32_to_cpu(raw_ssns_ei->flags) & NBFT_SSNS_EXT_INFO_VALID)) + return -EINVAL; + + if (le32_to_cpu(raw_ssns_ei->flags) & NBFT_SSNS_EXT_INFO_ADMIN_ASQSZ) + ssns->asqsz = le16_to_cpu(raw_ssns_ei->asqsz); + ssns->controller_id = le16_to_cpu(raw_ssns_ei->cntlid); + get_heap_obj(raw_ssns_ei, dhcp_root_path_str_obj, 1, &ssns->dhcp_root_path_string); + + return 0; +} + +static int read_ssns(struct nbft_info *nbft, + struct nbft_ssns *raw_ssns, + struct nbft_info_subsystem_ns **s) +{ + struct nbft_header *header = (struct nbft_header *)nbft->raw_nbft; + struct nbft_info_subsystem_ns *ssns; + __u8 *ss_hfi_indexes = NULL; + __u8 *tmp = NULL; + int i, ret; + + if (!(le16_to_cpu(raw_ssns->flags) & NBFT_SSNS_VALID)) + return -EINVAL; + + verify(raw_ssns->structure_id == NBFT_DESC_SSNS, + "invalid ID in SSNS descriptor"); + + ssns = calloc(1, sizeof(*ssns)); + if (!ssns) + return -ENOMEM; + + ssns->index = le16_to_cpu(raw_ssns->index); + + /* transport type */ + verify(raw_ssns->trtype == NBFT_TRTYPE_TCP, + "invalid transport type in SSNS descriptor"); + strncpy(ssns->transport, trtype_to_string(raw_ssns->trtype), sizeof(ssns->transport)); + + /* transport specific flags */ + if (raw_ssns->trtype == NBFT_TRTYPE_TCP) { + if (le16_to_cpu(raw_ssns->trflags) & NBFT_SSNS_PDU_HEADER_DIGEST) + ssns->pdu_header_digest_required = true; + if (le16_to_cpu(raw_ssns->trflags) & NBFT_SSNS_DATA_DIGEST) + ssns->data_digest_required = true; + } + + /* primary discovery controller */ + if (raw_ssns->primary_discovery_ctrl_index) { + ssns->discovery = discovery_from_index(nbft, + raw_ssns->primary_discovery_ctrl_index); + if (!ssns->discovery) + nvme_msg(NULL, LOG_DEBUG, + "file %s: namespace %d discovery controller not found\n", + nbft->filename, ssns->index); + } + + /* subsystem transport address */ + ret = get_heap_obj(raw_ssns, subsys_traddr_obj, 0, (char **)&tmp); + if (ret) + goto fail; + + format_ip_addr(ssns->traddr, sizeof(ssns->traddr), tmp); + + /* subsystem transport service identifier */ + ret = get_heap_obj(raw_ssns, subsys_trsvcid_obj, 1, &ssns->trsvcid); + if (ret) + goto fail; + + /* subsystem port ID */ + ssns->subsys_port_id = le16_to_cpu(raw_ssns->subsys_port_id); + + /* NSID, NID type, & NID */ + ssns->nsid = le32_to_cpu(raw_ssns->nsid); + ssns->nid_type = raw_ssns->nidt; + ssns->nid = raw_ssns->nid; + + /* security profile */ + if (raw_ssns->security_desc_index) { + ssns->security = security_from_index(nbft, raw_ssns->security_desc_index); + if (!ssns->security) + nvme_msg(NULL, LOG_DEBUG, + "file %s: namespace %d security controller not found\n", + nbft->filename, ssns->index); + } + + /* HFI descriptors */ + ret = get_heap_obj(raw_ssns, secondary_hfi_assoc_obj, 0, (char **)&ss_hfi_indexes); + if (ret) + goto fail; + + ssns->hfis = calloc(le16_to_cpu(raw_ssns->secondary_hfi_assoc_obj.length) + 2, + sizeof(*ssns->hfis)); + if (!ssns->hfis) { + ret = -ENOMEM; + goto fail; + } + ssns->hfis[0] = hfi_from_index(nbft, raw_ssns->primary_hfi_desc_index); + if (!ssns->hfis[0]) { + nvme_msg(NULL, LOG_DEBUG, + "file %s: SSNS %d: HFI %d not found\n", + nbft->filename, ssns->index, raw_ssns->primary_hfi_desc_index); + ret = -EINVAL; + goto fail; + } + for (i = 0; i < le16_to_cpu(raw_ssns->secondary_hfi_assoc_obj.length); i++) { + ssns->hfis[i + 1] = hfi_from_index(nbft, ss_hfi_indexes[i]); + if (ss_hfi_indexes[i] && !ssns->hfis[i + 1]) + nvme_msg(NULL, LOG_DEBUG, + "file %s: SSNS %d HFI %d not found\n", + nbft->filename, ssns->index, ss_hfi_indexes[i]); + else + ssns->num_hfis++; + } + + /* SSNS NQN */ + ret = get_heap_obj(raw_ssns, subsys_ns_nqn_obj, 1, &ssns->subsys_nqn); + if (ret) + goto fail; + + /* SSNS extended info */ + if (le16_to_cpu(raw_ssns->flags) & NBFT_SSNS_EXTENDED_INFO_IN_USE) { + struct nbft_ssns_ext_info *ssns_extended_info; + + if (!get_heap_obj(raw_ssns, ssns_extended_info_desc_obj, 0, + (char **)&ssns_extended_info)) + read_ssns_exended_info(nbft, ssns, ssns_extended_info); + } + + *s = ssns; + return 0; + +fail: + free(ssns); + return ret; +} + +static int read_hfi_info_tcp(struct nbft_info *nbft, + struct nbft_hfi_info_tcp *raw_hfi_info_tcp, + struct nbft_info_hfi *hfi) +{ + struct nbft_header *header = (struct nbft_header *)nbft->raw_nbft; + + if ((raw_hfi_info_tcp->flags & NBFT_HFI_INFO_TCP_VALID) == 0) + return -EINVAL; + + verify(raw_hfi_info_tcp->structure_id == NBFT_DESC_HFI_TRINFO, + "invalid ID in HFI transport descriptor"); + verify(raw_hfi_info_tcp->version == 1, + "invalid version in HFI transport descriptor"); + if (le16_to_cpu(raw_hfi_info_tcp->hfi_index) != hfi->index) + nvme_msg(NULL, LOG_DEBUG, + "file %s: HFI descriptor index %d does not match index in HFI transport descriptor\n", + nbft->filename, hfi->index); + + hfi->tcp_info.pci_sbdf = le32_to_cpu(raw_hfi_info_tcp->pci_sbdf); + memcpy(hfi->tcp_info.mac_addr, raw_hfi_info_tcp->mac_addr, + sizeof(raw_hfi_info_tcp->mac_addr)); + hfi->tcp_info.vlan = le16_to_cpu(raw_hfi_info_tcp->vlan); + hfi->tcp_info.ip_origin = raw_hfi_info_tcp->ip_origin; + format_ip_addr(hfi->tcp_info.ipaddr, sizeof(hfi->tcp_info.ipaddr), + raw_hfi_info_tcp->ip_address); + hfi->tcp_info.subnet_mask_prefix = raw_hfi_info_tcp->subnet_mask_prefix; + format_ip_addr(hfi->tcp_info.gateway_ipaddr, sizeof(hfi->tcp_info.ipaddr), + raw_hfi_info_tcp->ip_gateway); + hfi->tcp_info.route_metric = le16_to_cpu(raw_hfi_info_tcp->route_metric); + format_ip_addr(hfi->tcp_info.primary_dns_ipaddr, + sizeof(hfi->tcp_info.primary_dns_ipaddr), + raw_hfi_info_tcp->primary_dns); + format_ip_addr(hfi->tcp_info.secondary_dns_ipaddr, + sizeof(hfi->tcp_info.secondary_dns_ipaddr), + raw_hfi_info_tcp->secondary_dns); + if (raw_hfi_info_tcp->flags & NBFT_HFI_INFO_TCP_DHCP_OVERRIDE) { + hfi->tcp_info.dhcp_override = true; + format_ip_addr(hfi->tcp_info.dhcp_server_ipaddr, + sizeof(hfi->tcp_info.dhcp_server_ipaddr), + raw_hfi_info_tcp->dhcp_server); + } + get_heap_obj(raw_hfi_info_tcp, host_name_obj, 1, &hfi->tcp_info.host_name); + if (raw_hfi_info_tcp->flags & NBFT_HFI_INFO_TCP_GLOBAL_ROUTE) + hfi->tcp_info.this_hfi_is_default_route = true; + + return 0; +} + +static int read_hfi(struct nbft_info *nbft, + struct nbft_hfi *raw_hfi, + struct nbft_info_hfi **h) +{ + int ret; + struct nbft_info_hfi *hfi; + struct nbft_header *header = (struct nbft_header *)nbft->raw_nbft; + + if (!(raw_hfi->flags & NBFT_HFI_VALID)) + return -EINVAL; + + verify(raw_hfi->structure_id == NBFT_DESC_HFI, + "invalid ID in HFI descriptor"); + + hfi = calloc(1, sizeof(struct nbft_info_hfi)); + if (!hfi) + return -ENOMEM; + + hfi->index = raw_hfi->index; + + /* + * read HFI transport descriptor for this HFI + */ + if (raw_hfi->trtype == NBFT_TRTYPE_TCP) { + /* TCP */ + struct nbft_hfi_info_tcp *raw_hfi_info_tcp; + + strncpy(hfi->transport, trtype_to_string(raw_hfi->trtype), + sizeof(hfi->transport)); + + ret = get_heap_obj(raw_hfi, trinfo_obj, 0, (char **)&raw_hfi_info_tcp); + if (ret) + goto fail; + + ret = read_hfi_info_tcp(nbft, raw_hfi_info_tcp, hfi); + if (ret) + goto fail; + } else { + nvme_msg(NULL, LOG_DEBUG, + "file %s: invalid transport type %d\n", + nbft->filename, raw_hfi->trtype); + ret = -EINVAL; + goto fail; + } + + *h = hfi; + return 0; + +fail: + free(hfi); + return ret; +} + +static int read_discovery(struct nbft_info *nbft, + struct nbft_discovery *raw_discovery, + struct nbft_info_discovery **d) +{ + struct nbft_info_discovery *discovery; + struct nbft_header *header = (struct nbft_header *)nbft->raw_nbft; + + if (!(raw_discovery->flags & NBFT_DISCOVERY_VALID)) + return -EINVAL; + + verify(raw_discovery->structure_id == NBFT_DESC_DISCOVERY, + "invalid ID in discovery descriptor"); + + discovery = calloc(1, sizeof(struct nbft_info_discovery)); + if (!discovery) + return -ENOMEM; + + discovery->index = raw_discovery->index; + + if (get_heap_obj(raw_discovery, discovery_ctrl_addr_obj, 1, &discovery->uri)) + return -EINVAL; + + if (get_heap_obj(raw_discovery, discovery_ctrl_nqn_obj, 1, &discovery->nqn)) + return -EINVAL; + + discovery->hfi = hfi_from_index(nbft, raw_discovery->hfi_index); + if (raw_discovery->hfi_index && !discovery->hfi) + nvme_msg(NULL, LOG_DEBUG, + "file %s: discovery %d HFI not found\n", + nbft->filename, discovery->index); + + discovery->security = security_from_index(nbft, raw_discovery->sec_index); + if (raw_discovery->sec_index && !discovery->security) + nvme_msg(NULL, LOG_DEBUG, + "file %s: discovery %d security descriptor not found\n", + nbft->filename, discovery->index); + + *d = discovery; + return 0; +} + +static int read_security(struct nbft_info *nbft, + struct nbft_security *raw_security, + struct nbft_info_security **s) +{ + return -EINVAL; +} + +static void read_hfi_descriptors(struct nbft_info *nbft, int num_hfi, + struct nbft_hfi *raw_hfi_array, int hfi_len) +{ + int i, cnt; + + nbft->hfi_list = calloc(num_hfi + 1, sizeof(struct nbft_info_hfi)); + for (i = 0, cnt = 0; i < num_hfi; i++) { + if (read_hfi(nbft, &raw_hfi_array[i], &nbft->hfi_list[cnt]) == 0) + cnt++; + } +} + +static void read_security_descriptors(struct nbft_info *nbft, int num_sec, + struct nbft_security *raw_sec_array, int sec_len) +{ + int i, cnt; + + nbft->security_list = calloc(num_sec + 1, sizeof(struct nbft_info_security)); + for (i = 0, cnt = 0; i < num_sec; i++) { + if (read_security(nbft, &raw_sec_array[i], &nbft->security_list[cnt]) == 0) + cnt++; + } +} + +static void read_discovery_descriptors(struct nbft_info *nbft, int num_disc, + struct nbft_discovery *raw_disc_array, int disc_len) +{ + int i, cnt; + + nbft->discovery_list = calloc(num_disc + 1, sizeof(struct nbft_info_discovery)); + for (i = 0, cnt = 0; i < num_disc; i++) { + if (read_discovery(nbft, &raw_disc_array[i], &nbft->discovery_list[cnt]) == 0) + cnt++; + } +} + +static void read_ssns_descriptors(struct nbft_info *nbft, int num_ssns, + struct nbft_ssns *raw_ssns_array, int ssns_len) +{ + int i, cnt; + + nbft->subsystem_ns_list = calloc(num_ssns + 1, sizeof(struct nbft_info_subsystem_ns)); + for (i = 0, cnt = 0; i < num_ssns; i++) { + if (read_ssns(nbft, &raw_ssns_array[i], &nbft->subsystem_ns_list[cnt]) == 0) + cnt++; + } +} + +/** + * parse_raw_nbft - parses raw ACPI NBFT table and fill in abstracted nbft_info structure + * @nbft: nbft_info struct containing only raw_nbft and raw_nbft_size + * + * Returns 0 on success, errno otherwise. + */ +static int parse_raw_nbft(struct nbft_info *nbft) +{ + __u8 *raw_nbft = nbft->raw_nbft; + int raw_nbft_size = nbft->raw_nbft_size; + + struct nbft_header *header; + struct nbft_control *control; + struct nbft_host *host; + + verify(raw_nbft_size >= sizeof(struct nbft_header) + sizeof(struct nbft_control), + "table is too short"); + verify(csum(raw_nbft, raw_nbft_size) == 0, "invalid checksum"); + + /* + * header + */ + header = (struct nbft_header *)raw_nbft; + + verify(strncmp(header->signature, NBFT_HEADER_SIG, 4) == 0, "invalid signature"); + verify(le32_to_cpu(header->length) <= raw_nbft_size, "length in header exceeds table length"); + verify(header->major_revision == 1, "unsupported major revision"); + verify(header->minor_revision == 0, "unsupported minor revision"); + verify(le32_to_cpu(header->heap_length) + le32_to_cpu(header->heap_offset) <= + le32_to_cpu(header->length), "heap exceeds table length"); + + /* + * control + */ + control = (struct nbft_control *)(raw_nbft + sizeof(struct nbft_header)); + + if ((control->flags & NBFT_CONTROL_VALID) == 0) + return 0; + verify(control->structure_id == NBFT_DESC_CONTROL, + "invalid ID in control structure"); + + /* + * host + */ + verify(le32_to_cpu(control->hdesc.offset) + sizeof(struct nbft_host) <= + le32_to_cpu(header->length) && + le32_to_cpu(control->hdesc.offset) >= sizeof(struct nbft_host), + "host descriptor offset/length is invalid"); + host = (struct nbft_host *)(raw_nbft + le32_to_cpu(control->hdesc.offset)); + + verify(host->flags & NBFT_HOST_VALID, "host descriptor valid flag not set"); + verify(host->structure_id == NBFT_DESC_HOST, "invalid ID in HOST descriptor"); + nbft->host.id = (unsigned char *) &(host->host_id); + if (get_heap_obj(host, host_nqn_obj, 1, &nbft->host.nqn) != 0) + return -EINVAL; + nbft->host.host_id_configured = host->flags & NBFT_HOST_HOSTID_CONFIGURED; + nbft->host.host_nqn_configured = host->flags & NBFT_HOST_HOSTNQN_CONFIGURED; + + /* + * HFI + */ + if (control->num_hfi > 0) { + struct nbft_hfi *raw_hfi_array; + + verify(le32_to_cpu(control->hfio) + sizeof(struct nbft_hfi) * + control->num_hfi <= le32_to_cpu(header->length), + "invalid hfi descriptor list offset"); + raw_hfi_array = (struct nbft_hfi *)(raw_nbft + le32_to_cpu(control->hfio)); + read_hfi_descriptors(nbft, control->num_hfi, raw_hfi_array, + le16_to_cpu(control->hfil)); + } + + /* + * security + */ + if (control->num_sec > 0) { + struct nbft_security *raw_security_array; + + verify(le32_to_cpu(control->seco) + le16_to_cpu(control->secl) * + control->num_sec <= le32_to_cpu(header->length), + "invalid security profile desciptor list offset"); + raw_security_array = (struct nbft_security *)(raw_nbft + + le32_to_cpu(control->seco)); + read_security_descriptors(nbft, control->num_sec, + raw_security_array, + le16_to_cpu(control->secl)); + } + + /* + * discovery + */ + if (control->num_disc > 0) { + struct nbft_discovery *raw_discovery_array; + + verify(le32_to_cpu(control->disco) + le16_to_cpu(control->discl) * + control->num_disc <= le32_to_cpu(header->length), + "invalid discovery profile descriptor list offset"); + raw_discovery_array = (struct nbft_discovery *)(raw_nbft + + le32_to_cpu(control->disco)); + read_discovery_descriptors(nbft, control->num_disc, raw_discovery_array, + le16_to_cpu(control->discl)); + } + + /* + * subsystem namespace + */ + if (control->num_ssns > 0) { + struct nbft_ssns *raw_ssns_array; + + verify(le32_to_cpu(control->ssnso) + le16_to_cpu(control->ssnsl) * + control->num_ssns <= le32_to_cpu(header->length), + "invalid subsystem namespace descriptor list offset"); + raw_ssns_array = (struct nbft_ssns *)(raw_nbft + + le32_to_cpu(control->ssnso)); + read_ssns_descriptors(nbft, control->num_ssns, raw_ssns_array, + le16_to_cpu(control->ssnsl)); + } + + return 0; +} + +void nvme_nbft_free(struct nbft_info *nbft) +{ + struct nbft_info_hfi **hfi; + struct nbft_info_security **sec; + struct nbft_info_discovery **disc; + struct nbft_info_subsystem_ns **ns; + + for (hfi = nbft->hfi_list; hfi && *hfi; hfi++) + free(*hfi); + free(nbft->hfi_list); + for (disc = nbft->discovery_list; disc && *disc; disc++) + free(*disc); + free(nbft->discovery_list); + for (sec = nbft->security_list; sec && *sec; sec++) + free(*sec); + free(nbft->security_list); + for (ns = nbft->subsystem_ns_list; ns && *ns; ns++) { + free((*ns)->hfis); + free(*ns); + } + free(nbft->subsystem_ns_list); + free(nbft->raw_nbft); + free(nbft->filename); + free(nbft); +} + +int nvme_nbft_read(struct nbft_info **nbft, const char *filename) +{ + __u8 *raw_nbft = NULL; + size_t raw_nbft_size; + FILE *raw_nbft_fp = NULL; + int i; + + /* + * read in raw nbft file + */ + raw_nbft_fp = fopen(filename, "rb"); + if (raw_nbft_fp == NULL) { + nvme_msg(NULL, LOG_ERR, "Failed to open %s: %s\n", + filename, strerror(errno)); + errno = EINVAL; + return 1; + } + + i = fseek(raw_nbft_fp, 0L, SEEK_END); + if (i) { + nvme_msg(NULL, LOG_ERR, "Failed to read from %s: %s\n", + filename, strerror(errno)); + fclose(raw_nbft_fp); + errno = EINVAL; + return 1; + } + + raw_nbft_size = ftell(raw_nbft_fp); + rewind(raw_nbft_fp); + + raw_nbft = malloc(raw_nbft_size); + if (!raw_nbft) { + nvme_msg(NULL, LOG_ERR, "Failed to allocate memory for NBFT table"); + fclose(raw_nbft_fp); + errno = ENOMEM; + return 1; + } + + i = fread(raw_nbft, sizeof(*raw_nbft), raw_nbft_size, raw_nbft_fp); + if (i != raw_nbft_size) { + nvme_msg(NULL, LOG_ERR, "Failed to read from %s: %s\n", + filename, strerror(errno)); + fclose(raw_nbft_fp); + free(raw_nbft); + errno = EINVAL; + return 1; + } + fclose(raw_nbft_fp); + + /* + * alloc new struct nbft_info, add raw nbft & filename to it, and add it to the list + */ + *nbft = calloc(1, sizeof(struct nbft_info)); + if (!*nbft) { + nvme_msg(NULL, LOG_ERR, "Could not allocate memory for NBFT\n"); + free(raw_nbft); + errno = ENOMEM; + return 1; + } + + (*nbft)->filename = strdup(filename); + (*nbft)->raw_nbft = raw_nbft; + (*nbft)->raw_nbft_size = raw_nbft_size; + + if (parse_raw_nbft(*nbft)) { + nvme_msg(NULL, LOG_ERR, "Failed to parse %s\n", filename); + nvme_nbft_free(*nbft); + errno = EINVAL; + return 1; + } + return 0; +} |