diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index c3c36ed81..b6acd6fb1 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -60,11 +60,11 @@ variables: # # This is done by running `ci-fairy generate-template` and possibly bumping # ".default_tag". - ALPINE_TAG: 'tag-77ec3d923fd6' - CENTOS_TAG: 'tag-7a677f4838e1' - DEBIAN_TAG: 'tag-ecad19904683' - FEDORA_TAG: 'tag-7a677f4838e1' - UBUNTU_TAG: 'tag-ecad19904683' + ALPINE_TAG: 'tag-57edf560bf4f' + CENTOS_TAG: 'tag-7ea4f50c8578' + DEBIAN_TAG: 'tag-1601ce2572c5' + FEDORA_TAG: 'tag-7ea4f50c8578' + UBUNTU_TAG: 'tag-1601ce2572c5' ALPINE_EXEC: 'bash .gitlab-ci/alpine-install.sh' CENTOS_EXEC: 'bash .gitlab-ci/fedora-install.sh' diff --git a/config.h.meson b/config.h.meson index 9ae88b26b..d41304db6 100644 --- a/config.h.meson +++ b/config.h.meson @@ -280,3 +280,8 @@ /* Define to 1 if you have history support from -lreadline. */ #mesondefine HAVE_READLINE_HISTORY +/* Define if NBFT support is enabled */ +#mesondefine WITH_NBFT + +/* Define to 1 if dlvsym() is available */ +#mesondefine HAVE_DLVSYM diff --git a/contrib/alpine/REQUIRED_PACKAGES b/contrib/alpine/REQUIRED_PACKAGES index 358214fd0..6f0e86fdd 100755 --- a/contrib/alpine/REQUIRED_PACKAGES +++ b/contrib/alpine/REQUIRED_PACKAGES @@ -24,6 +24,7 @@ apk add \ 'jansson-dev' \ 'libgudev-dev' \ 'libndp-dev' \ + 'libnvme-dev' \ 'libnl3-dev' \ 'libpsl-dev' \ 'libsoup-dev' \ diff --git a/contrib/debian/REQUIRED_PACKAGES b/contrib/debian/REQUIRED_PACKAGES index fa73b21ad..d91e54d27 100755 --- a/contrib/debian/REQUIRED_PACKAGES +++ b/contrib/debian/REQUIRED_PACKAGES @@ -56,6 +56,7 @@ install \ libndp-dev \ libnewt-dev \ libnss3-dev \ + libnvme-dev \ libpolkit-gobject-1-dev \ libpsl-dev \ libreadline-dev \ diff --git a/contrib/fedora/REQUIRED_PACKAGES b/contrib/fedora/REQUIRED_PACKAGES index 1bb7ec6b3..38a2cbbf4 100755 --- a/contrib/fedora/REQUIRED_PACKAGES +++ b/contrib/fedora/REQUIRED_PACKAGES @@ -66,6 +66,7 @@ install \ jq \ libcurl-devel \ libndp-devel \ + libnvme-devel \ libselinux-devel \ libtool \ libuuid-devel \ diff --git a/contrib/fedora/rpm/NetworkManager.spec b/contrib/fedora/rpm/NetworkManager.spec index 638da502d..788e9ead5 100644 --- a/contrib/fedora/rpm/NetworkManager.spec +++ b/contrib/fedora/rpm/NetworkManager.spec @@ -307,6 +307,7 @@ BuildRequires: libubsan BuildRequires: firewalld-filesystem BuildRequires: iproute BuildRequires: iproute-tc +BuildRequires: libnvme-devel >= 1.5 Provides: %{name}-dispatcher%{?_isa} = %{epoch}:%{version}-%{release} diff --git a/contrib/scripts/nm-ci-run.sh b/contrib/scripts/nm-ci-run.sh index 3864620f8..4881d110d 100755 --- a/contrib/scripts/nm-ci-run.sh +++ b/contrib/scripts/nm-ci-run.sh @@ -54,10 +54,15 @@ _WITH_WERROR=1 _WITH_LIBTEAM="true" _WITH_DOCS="true" _WITH_SYSTEMD_LOGIND="true" +_WITH_NBFT="true" if [ $IS_ALPINE = 1 ]; then _WITH_SYSTEMD_LOGIND="false" fi +if ! pkgconf 'libnvme >= 1.5'; then + _WITH_NBFT="false" +fi + if [ -z "${NMTST_SEED_RAND+x}" ]; then NMTST_SEED_RAND="$SRANDOM" if [ -z "$NMTST_SEED_RAND" ]; then @@ -181,6 +186,8 @@ meson setup build \ -D ifcfg_rh=false \ -D ifupdown=true \ \ + -D nbft=$_WITH_NBFT \ + \ #end export NM_TEST_CLIENT_CHECK_L10N=1 diff --git a/man/nm-initrd-generator.xml b/man/nm-initrd-generator.xml index 2d3d87553..312edff2e 100644 --- a/man/nm-initrd-generator.xml +++ b/man/nm-initrd-generator.xml @@ -154,6 +154,7 @@ + diff --git a/meson.build b/meson.build index 40386a0b4..e13cd9da0 100644 --- a/meson.build +++ b/meson.build @@ -137,6 +137,9 @@ config_h.set10('HAVE_DECL_REALLOCARRAY', cc.has_function('reallocarray', prefix: config_h.set10('HAVE_DECL_EXPLICIT_BZERO', cc.has_function('explicit_bzero', prefix: '#include ')) config_h.set10('HAVE_DECL_MEMFD_CREATE', cc.has_function('memfd_create', prefix: '#include ')) +config_h.set10('HAVE_DLVSYM', cc.has_function('dlvsym', prefix: '''#define _GNU_SOURCE + #include ''')) + # types config_h.set('SIZEOF_PID_T', cc.sizeof('pid_t', prefix : '#include ')) config_h.set('SIZEOF_UID_T', cc.sizeof('uid_t', prefix : '#include ')) @@ -929,6 +932,14 @@ if python.found() config_h.set_quoted('TEST_NM_PYTHON', python_path) endif +# libnvme (NBFT support) +enable_nbft = get_option('nbft') +if enable_nbft + libnvme_dep = dependency('libnvme', version: '>= 1.5', required: false) + assert(libnvme_dep.found(), 'NBFT support was requested, but the libnvme library is not available. Use -Dnbft=false to build without it.') +endif +config_h.set10('WITH_NBFT', enable_nbft) + data_conf = configuration_data() data_conf.set('DISTRO_NETWORK_SERVICE', (enable_ifcfg_rh ? 'network.service' : '')) data_conf.set('NM_CONFIG_DEFAULT_LOGGING_AUDIT_TEXT', config_default_logging_audit) diff --git a/meson_options.txt b/meson_options.txt index fe696aaf1..18d8bfc44 100644 --- a/meson_options.txt +++ b/meson_options.txt @@ -45,6 +45,7 @@ option('nmtui', type: 'boolean', value: true, description: 'Build nmtui') option('nm_cloud_setup', type: 'boolean', value: true, description: 'Build nm-cloud-setup, a tool for automatically configuring networking in cloud') option('bluez5_dun', type: 'boolean', value: false, description: 'enable Bluez5 DUN support') option('ebpf', type: 'combo', choices: ['auto', 'true', 'false'], description: 'Enable eBPF support') +option('nbft', type: 'boolean', value: true, description: 'Enable NBFT support in the initrd generator') # configuration plugins option('config_plugins_default', type: 'string', value: '', description: 'Default configuration option for main.plugins setting, used as fallback if the configuration option is unset') diff --git a/src/nm-initrd-generator/meson.build b/src/nm-initrd-generator/meson.build index 080fe7e42..6b02e0668 100644 --- a/src/nm-initrd-generator/meson.build +++ b/src/nm-initrd-generator/meson.build @@ -6,6 +6,7 @@ libnmi_core = static_library( 'nmi-cmdline-reader.c', 'nmi-dt-reader.c', 'nmi-ibft-reader.c', + 'nmi-nbft-reader.c', ), include_directories: [ src_inc, diff --git a/src/nm-initrd-generator/nm-initrd-generator.h b/src/nm-initrd-generator/nm-initrd-generator.h index c2baad4e5..a73ce361f 100644 --- a/src/nm-initrd-generator/nm-initrd-generator.h +++ b/src/nm-initrd-generator/nm-initrd-generator.h @@ -41,6 +41,8 @@ nmi_ibft_update_connection_from_nic(NMConnection *connection, GHashTable *nic, G NMConnection *nmi_dt_reader_parse(const char *sysfs_dir); +NMConnection **nmi_nbft_reader_parse(const char *sysfs_dir, char **hostname); + GHashTable *nmi_cmdline_reader_parse(const char *etc_connections_dir, const char *sysfs_dir, const char *const *argv, diff --git a/src/nm-initrd-generator/nmi-cmdline-reader.c b/src/nm-initrd-generator/nmi-cmdline-reader.c index d6dc1fcb7..a6cd30f10 100644 --- a/src/nm-initrd-generator/nmi-cmdline-reader.c +++ b/src/nm-initrd-generator/nmi-cmdline-reader.c @@ -1451,8 +1451,10 @@ nmi_cmdline_reader_parse(const char *etc_connections_dir, gs_unref_ptrarray GPtrArray *routes = NULL; gs_unref_ptrarray GPtrArray *znets = NULL; int i; - guint64 dhcp_timeout = 90; - guint64 dhcp_num_tries = 1; + guint64 dhcp_timeout = 90; + guint64 dhcp_num_tries = 1; + gboolean nvmf_nonbft = FALSE; + gboolean have_dracut_nbft = FALSE; reader = reader_new(); @@ -1469,7 +1471,10 @@ nmi_cmdline_reader_parse(const char *etc_connections_dir, /* pass */ } else if (nm_streq(tag, "net.ifnames")) net_ifnames = !nm_streq(argument, "0"); - else if (nm_streq(tag, "rd.peerdns")) + else if (nm_streq(tag, "ifname")) { + if (NM_STR_HAS_PREFIX(argument, "nbft")) + have_dracut_nbft = TRUE; + } else if (nm_streq(tag, "rd.peerdns")) reader->ignore_auto_dns = !_nm_utils_ascii_str_to_bool(argument, TRUE); else if (nm_streq(tag, "rd.net.timeout.dhcp")) { if (nm_streq0(argument, "infinity")) { @@ -1561,7 +1566,18 @@ nmi_cmdline_reader_parse(const char *etc_connections_dir, reader_parse_dns_backend(reader, argument); } else if (nm_streq(tag, "rd.net.dns-resolve-mode")) { reader_parse_dns_resolve_mode(reader, argument); + } else if (nm_streq(tag, "rd.nvmf.nonbft")) + nvmf_nonbft = TRUE; + } + + if (!nvmf_nonbft && !have_dracut_nbft) { + NMConnection **nbft_connections, **c; + + nbft_connections = nmi_nbft_reader_parse(sysfs_dir, &reader->hostname); + for (c = nbft_connections; c && *c; c++) { + reader_add_connection(reader, nm_connection_get_id(*c), *c); } + g_free(nbft_connections); } for (i = 0; i < reader->vlan_parents->len; i++) { diff --git a/src/nm-initrd-generator/nmi-nbft-reader.c b/src/nm-initrd-generator/nmi-nbft-reader.c new file mode 100644 index 000000000..bb2db7003 --- /dev/null +++ b/src/nm-initrd-generator/nmi-nbft-reader.c @@ -0,0 +1,414 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2024 Red Hat, Inc. + */ + +#include "libnm-core-impl/nm-default-libnm-core.h" + +#include "nm-initrd-generator.h" + +#if WITH_NBFT + +#include +#include + +#include "libnm-log-core/nm-logging.h" +#include "libnm-core-intern/nm-core-internal.h" + +/*****************************************************************************/ + +#define _NMLOG(level, domain, ...) \ + nm_log((level), \ + (domain), \ + NULL, \ + NULL, \ + "nbft-reader: " _NM_UTILS_MACRO_FIRST(__VA_ARGS__) _NM_UTILS_MACRO_REST(__VA_ARGS__)) + +/*****************************************************************************/ + +static inline gboolean +is_valid_addr(int family, const char *addr) +{ + return (addr && strlen(addr) > 0 && !nm_streq(addr, "0.0.0.0") && !nm_streq(addr, "::") + && nm_utils_ipaddr_valid(family, addr)); +} + +static int (*_nvme_nbft_read)(struct nbft_info **nbft, const char *filename); +static void (*_nvme_nbft_free)(struct nbft_info *nbft); + +static void * +load_libnvme(void) +{ + void *handle; + + handle = dlopen("libnvme.so.1", RTLD_LAZY); + if (!handle) + return NULL; + +#if HAVE_DLVSYM + _nvme_nbft_read = dlvsym(handle, "nvme_nbft_read", "LIBNVME_1_5"); + _nvme_nbft_free = dlvsym(handle, "nvme_nbft_free", "LIBNVME_1_5"); +#else + /* no dlvsym() in musl */ + _nvme_nbft_read = dlsym(handle, "nvme_nbft_read"); + _nvme_nbft_free = dlsym(handle, "nvme_nbft_free"); +#endif + + if (!_nvme_nbft_read || !_nvme_nbft_free) { + dlclose(handle); + return NULL; + } + return handle; +} + +static char * +format_conn_name(const char *table_name, struct nbft_info_hfi *hfi, gboolean is_vlan) +{ + if (is_vlan) { + nm_assert(hfi->tcp_info.vlan > 0); + return g_strdup_printf("%s connection HFI %d VLAN %d", + table_name, + hfi->index, + hfi->tcp_info.vlan); + } else + return g_strdup_printf("%s connection HFI %d", table_name, hfi->index); +} + +static NMConnection * +find_conn_for_wired_mac(GPtrArray *a, const char *hwaddr) +{ + guint i; + + for (i = 0; i < a->len; i++) { + NMConnection *con = a->pdata[i]; + NMSettingWired *s_wired; + + if (!nm_connection_is_type(con, NM_SETTING_WIRED_SETTING_NAME)) + continue; + s_wired = nm_connection_get_setting_wired(con); + if (!s_wired) + continue; + if (nm_streq(hwaddr, nm_setting_wired_get_mac_address(s_wired))) + return con; + } + return NULL; +} + +static NMConnection * +create_wired_conn(struct nbft_info_hfi *hfi, + const char *conn_name, + const char *hwaddr, + gboolean is_vlan) +{ + NMConnection *connection; + NMSetting *s_connection; + NMSetting *s_wired; + + connection = nm_simple_connection_new(); + + s_connection = nm_setting_connection_new(); + g_object_set(s_connection, + NM_SETTING_CONNECTION_TYPE, + is_vlan ? NM_SETTING_VLAN_SETTING_NAME : NM_SETTING_WIRED_SETTING_NAME, + NM_SETTING_CONNECTION_ID, + conn_name, + NM_SETTING_CONNECTION_AUTOCONNECT_PRIORITY, + NMI_AUTOCONNECT_PRIORITY_FIRMWARE, + NULL); + nm_connection_add_setting(connection, s_connection); + + /* MAC address */ + s_wired = nm_setting_wired_new(); + g_object_set(s_wired, NM_SETTING_WIRED_MAC_ADDRESS, hwaddr, NULL); + nm_connection_add_setting(connection, s_wired); + + return connection; +} + +static void +parse_hfi(GPtrArray *a, struct nbft_info_hfi *hfi, const char *table_name, char **hostname) +{ + gs_unref_object NMConnection *connection = NULL; + NMConnection *parent_connection; + NMSetting *s_vlan; + gs_free char *hwaddr = NULL; + gs_free char *conn_name = NULL; + gs_unref_object NMSetting *s_ip4 = NULL; + gs_unref_object NMSetting *s_ip6 = NULL; + nm_auto_unref_ip_address NMIPAddress *ipaddr = NULL; + guint prefix; + gs_free_error GError *error = NULL; + int family = AF_UNSPEC; + NMIPAddr addr_bin; + + /* Pre-checks */ + if (!nm_inet_parse_bin_full(family, FALSE, hfi->tcp_info.ipaddr, &family, &addr_bin)) { + _LOGW(LOGD_CORE, "NBFT: Malformed IP address: '%s'", hfi->tcp_info.ipaddr); + return; + } + + /* MAC address */ + hwaddr = g_strdup_printf("%02X:%02X:%02X:%02X:%02X:%02X", + hfi->tcp_info.mac_addr[0], + hfi->tcp_info.mac_addr[1], + hfi->tcp_info.mac_addr[2], + hfi->tcp_info.mac_addr[3], + hfi->tcp_info.mac_addr[4], + hfi->tcp_info.mac_addr[5]); + + /* First check if we need VLANs */ + if (hfi->tcp_info.vlan > 0) { + parent_connection = find_conn_for_wired_mac(a, hwaddr); + if (!parent_connection) { + /* Create new parent wired connection */ + conn_name = format_conn_name(table_name, hfi, FALSE); + parent_connection = create_wired_conn(hfi, conn_name, hwaddr, FALSE); + + s_ip4 = nm_setting_ip4_config_new(); + s_ip6 = nm_setting_ip6_config_new(); + g_object_set(s_ip4, + NM_SETTING_IP_CONFIG_METHOD, + NM_SETTING_IP4_CONFIG_METHOD_DISABLED, + NULL); + g_object_set(s_ip6, + NM_SETTING_IP_CONFIG_METHOD, + NM_SETTING_IP6_CONFIG_METHOD_DISABLED, + NULL); + nm_connection_add_setting(parent_connection, g_steal_pointer(&s_ip4)); + nm_connection_add_setting(parent_connection, g_steal_pointer(&s_ip6)); + + if (!nm_connection_normalize(parent_connection, NULL, NULL, &error)) { + _LOGW(LOGD_CORE, "Generated an invalid connection: %s", error->message); + g_object_unref(parent_connection); + return; + } + g_ptr_array_add(a, parent_connection); + } + + conn_name = format_conn_name(table_name, hfi, TRUE); + connection = create_wired_conn(hfi, conn_name, hwaddr, TRUE); + + s_vlan = nm_setting_vlan_new(); + g_object_set(s_vlan, NM_SETTING_VLAN_ID, hfi->tcp_info.vlan, NULL); + nm_connection_add_setting(connection, s_vlan); + } else { + /* No VLANS */ + conn_name = format_conn_name(table_name, hfi, FALSE); + connection = create_wired_conn(hfi, conn_name, hwaddr, FALSE); + } + + /* IP addresses */ + s_ip4 = nm_setting_ip4_config_new(); + s_ip6 = nm_setting_ip6_config_new(); + + switch (family) { + /* IPv4 */ + case AF_INET: + g_object_set(s_ip6, + NM_SETTING_IP_CONFIG_METHOD, + NM_SETTING_IP6_CONFIG_METHOD_DISABLED, + NULL); + if (is_valid_addr(AF_INET, hfi->tcp_info.dhcp_server_ipaddr)) { + g_object_set(s_ip4, + NM_SETTING_IP_CONFIG_METHOD, + NM_SETTING_IP4_CONFIG_METHOD_AUTO, + NULL); + if (hfi->tcp_info.host_name && strlen(hfi->tcp_info.host_name) > 0) { + g_object_set(s_ip4, + NM_SETTING_IP_CONFIG_DHCP_HOSTNAME, + hfi->tcp_info.host_name, + NULL); + } + } else { + g_object_set(s_ip4, + NM_SETTING_IP_CONFIG_METHOD, + NM_SETTING_IP4_CONFIG_METHOD_MANUAL, + NULL); + ipaddr = nm_ip_address_new_binary(AF_INET, + &addr_bin, + hfi->tcp_info.subnet_mask_prefix, + &error); + if (!ipaddr) { + _LOGW(LOGD_CORE, + "Cannot parse IP %s/%u: %s", + hfi->tcp_info.ipaddr, + hfi->tcp_info.subnet_mask_prefix, + error->message); + return; + } + nm_setting_ip_config_add_address(NM_SETTING_IP_CONFIG(s_ip4), ipaddr); + if (is_valid_addr(AF_INET, hfi->tcp_info.gateway_ipaddr)) { + g_object_set(s_ip4, + NM_SETTING_IP_CONFIG_GATEWAY, + hfi->tcp_info.gateway_ipaddr, + NULL); + } + if (is_valid_addr(AF_INET, hfi->tcp_info.primary_dns_ipaddr)) { + nm_setting_ip_config_add_dns(NM_SETTING_IP_CONFIG(s_ip4), + hfi->tcp_info.primary_dns_ipaddr); + } + if (is_valid_addr(AF_INET, hfi->tcp_info.secondary_dns_ipaddr)) { + nm_setting_ip_config_add_dns(NM_SETTING_IP_CONFIG(s_ip4), + hfi->tcp_info.secondary_dns_ipaddr); + } + } + break; + + /* IPv6 */ + case AF_INET6: + g_object_set(s_ip4, + NM_SETTING_IP_CONFIG_METHOD, + NM_SETTING_IP4_CONFIG_METHOD_DISABLED, + NULL); + if (is_valid_addr(AF_INET6, hfi->tcp_info.dhcp_server_ipaddr)) { + g_object_set(s_ip6, + NM_SETTING_IP_CONFIG_METHOD, + NM_SETTING_IP6_CONFIG_METHOD_AUTO, + NULL); + if (hfi->tcp_info.host_name && strlen(hfi->tcp_info.host_name) > 0) { + g_object_set(s_ip6, + NM_SETTING_IP_CONFIG_DHCP_HOSTNAME, + hfi->tcp_info.host_name, + NULL); + } + } else { + g_object_set(s_ip6, + NM_SETTING_IP_CONFIG_METHOD, + NM_SETTING_IP6_CONFIG_METHOD_MANUAL, + NULL); + prefix = hfi->tcp_info.subnet_mask_prefix; + if (prefix == 0) { + /* Buggy firmware implementations may report prefix=0 for v6 addresses, + * let's fall back to /64. + */ + _LOGW(LOGD_CORE, + "NBFT: Invalid IPv6 prefix %d, using /64 as a fallback.", + hfi->tcp_info.subnet_mask_prefix); + prefix = 64; + } + ipaddr = nm_ip_address_new_binary(AF_INET6, &addr_bin, prefix, &error); + if (!ipaddr) { + _LOGW(LOGD_CORE, + "Cannot parse IP %s/%u: %s", + hfi->tcp_info.ipaddr, + prefix, + error->message); + return; + } + nm_setting_ip_config_add_address(NM_SETTING_IP_CONFIG(s_ip6), ipaddr); + if (is_valid_addr(AF_INET6, hfi->tcp_info.gateway_ipaddr)) { + g_object_set(s_ip6, + NM_SETTING_IP_CONFIG_GATEWAY, + hfi->tcp_info.gateway_ipaddr, + NULL); + } + if (is_valid_addr(AF_INET6, hfi->tcp_info.primary_dns_ipaddr)) { + nm_setting_ip_config_add_dns(NM_SETTING_IP_CONFIG(s_ip6), + hfi->tcp_info.primary_dns_ipaddr); + } + if (is_valid_addr(AF_INET6, hfi->tcp_info.secondary_dns_ipaddr)) { + nm_setting_ip_config_add_dns(NM_SETTING_IP_CONFIG(s_ip6), + hfi->tcp_info.secondary_dns_ipaddr); + } + } + break; + default: + g_warn_if_reached(); + } + + nm_connection_add_setting(connection, g_steal_pointer(&s_ip4)); + nm_connection_add_setting(connection, g_steal_pointer(&s_ip6)); + + /* Hostname */ + if (hfi->tcp_info.host_name && strlen(hfi->tcp_info.host_name) > 0) { + g_free(*hostname); + *hostname = g_strdup(hfi->tcp_info.host_name); + } + + /* TODO: translate the following HFI struct members? + * hfi->tcp_info.pci_sbdf + * hfi->tcp_info.ip_origin + * hfi->tcp_info.dhcp_server_ipaddr + * hfi->tcp_info.this_hfi_is_default_route + * hfi->tcp_info.dhcp_override + */ + + if (!nm_connection_normalize(connection, NULL, NULL, &error)) { + _LOGW(LOGD_CORE, "Generated an invalid connection: %s", error->message); + return; + } + + g_ptr_array_add(a, g_steal_pointer(&connection)); +} + +NMConnection ** +nmi_nbft_reader_parse(const char *sysfs_dir, char **hostname) +{ + nm_auto_unref_ptrarray GPtrArray *a = NULL; + gs_free char *path = NULL; + gs_free_error GError *error = NULL; + GDir *dir; + void *libnvme_handle = NULL; + const char *entry_name; + + g_return_val_if_fail(sysfs_dir != NULL, NULL); + path = g_build_filename(sysfs_dir, "firmware", "acpi", "tables", NULL); + + dir = g_dir_open(path, 0, NULL); + if (!dir) + return NULL; + + a = g_ptr_array_new(); + + while ((entry_name = g_dir_read_name(dir))) { + gs_free char *entry_path = NULL; + struct nbft_info *nbft; + struct nbft_info_hfi **hfi; + int ret; + + if (!g_str_has_prefix(entry_name, "NBFT")) + continue; + + /* attempt to load libnvme only on the first table match, saving some I/O */ + if (!libnvme_handle && !(libnvme_handle = load_libnvme())) { + g_dir_close(dir); + return NULL; + } + + entry_path = g_build_filename(path, entry_name, NULL); + ret = _nvme_nbft_read(&nbft, entry_path); + if (ret) { + _LOGW(LOGD_CORE, "Error parsing NBFT table %s: %m", entry_path); + continue; + } + + for (hfi = nbft->hfi_list; hfi && *hfi; hfi++) { + if (!nm_streq((*hfi)->transport, "tcp")) { + _LOGW(LOGD_CORE, + "NBFT table %s, HFI descriptor %d: unsupported transport type '%s'", + entry_path, + (*hfi)->index, + (*hfi)->transport); + continue; + } + parse_hfi(a, *hfi, entry_name, hostname); + } + + _nvme_nbft_free(nbft); + } + + g_dir_close(dir); + dlclose(libnvme_handle); + g_ptr_array_add(a, NULL); /* trailing NULL-delimiter */ + return (NMConnection **) g_ptr_array_free(g_steal_pointer(&a), FALSE); +} + +#else /* WITH_NBFT */ + +NMConnection ** +nmi_nbft_reader_parse(const char *sysfs_dir, char **hostname) +{ + return NULL; +} + +#endif diff --git a/tools/nm-in-container b/tools/nm-in-container index 0e3f31da7..365b8fcf8 100755 --- a/tools/nm-in-container +++ b/tools/nm-in-container @@ -224,6 +224,7 @@ RUN dnf install -y \\ libasan \\ libcurl-devel \\ libndp-devel \\ + libnvme-devel \\ libpsl-devel \\ libselinux-devel \\ libtool \\