From c8a9bc37af444ffa49769bad31cf16e53f7cec04 Mon Sep 17 00:00:00 2001 From: Daniel Wagner Date: Sun, 11 Oct 2026 16:32:44 +0000 Subject: [PATCH 1/4] nvme: Port to libnvme3 nvme-cli 3.0 merged libnvme into its tree and ships it as the new libnvme3 library with a reworked API, libnvme-1.x is no longer developed. Devices are now accessed through transport handles, commands are built with the nvme_init_*() helpers and errors are returned as negative errno values. libnvme3 dropped support for the JSON configuration file, the "config" extra argument is ignored for now. Following the DH-HMAC-CHAP to KX-HMAC-CHAP rename in nvme-cli, the "dhchap_key" and "dhchap_ctrl_key" extra arguments are now called "kxchap_secret" and "kxchap_ctrl_secret". The old names are kept as deprecated aliases. Note that libnvme3 refuses a second identical connection to an already connected controller unless "duplicate_connect" is set. The tests now look up controllers and namespaces in sysfs as the 'nvme list' JSON output changed in nvme-cli 3.0. Resolves: https://github.com/storaged-project/libblockdev/issues/1211 Co-Authored-By: Claude Opus 5.5 --- configure.ac | 5 +- dist/libblockdev.spec.in | 2 +- src/lib/plugin_apis/nvme.api | 23 +- src/plugins/nvme/nvme-error.c | 17 +- src/plugins/nvme/nvme-fabrics.c | 430 ++++++++++++++++---------------- src/plugins/nvme/nvme-info.c | 191 +++++++++----- src/plugins/nvme/nvme-op.c | 162 ++++++------ src/plugins/nvme/nvme-private.h | 11 +- tests/nvme_test.py | 7 +- tests/utils.py | 113 ++++----- 10 files changed, 510 insertions(+), 451 deletions(-) diff --git a/configure.ac b/configure.ac index e5d65d8b2..92df389c9 100644 --- a/configure.ac +++ b/configure.ac @@ -279,10 +279,7 @@ AS_IF([test "x$with_nvdimm" != "xno"], []) AS_IF([test "x$with_nvme" != "xno"], - [LIBBLOCKDEV_PKG_CHECK_MODULES([NVME], [libnvme >= 1.3]) - AS_IF([$PKG_CONFIG --atleast-version=1.4 libnvme], - [AC_DEFINE([HAVE_LIBNVME_1_4])], []) - ], + [LIBBLOCKDEV_PKG_CHECK_MODULES([NVME], [libnvme3 >= 1.0])], []) AS_IF([test "x$with_smart" != "xno"], diff --git a/dist/libblockdev.spec.in b/dist/libblockdev.spec.in index 5d75d0569..7e73e2271 100644 --- a/dist/libblockdev.spec.in +++ b/dist/libblockdev.spec.in @@ -413,7 +413,7 @@ with the libblockdev-nvdimm plugin/library. %if %{with_nvme} %package nvme -BuildRequires: libnvme-devel +BuildRequires: pkgconfig(libnvme3) BuildRequires: libuuid-devel Summary: The NVMe plugin for the libblockdev library Requires: %{name}-utils%{?_isa} = %{version}-%{release} diff --git a/src/lib/plugin_apis/nvme.api b/src/lib/plugin_apis/nvme.api index 85bbe2bbd..8ad4fb8d6 100644 --- a/src/lib/plugin_apis/nvme.api +++ b/src/lib/plugin_apis/nvme.api @@ -1291,14 +1291,13 @@ gboolean bd_nvme_set_host_id (const gchar *host_id, GError **error); * - `"loop"`: A NVMe over Fabrics target on the local host. * * In addition to the primary options it's possible to supply @extra arguments: - * - `"config"`: Use the specified JSON configuration file instead of the default file (see below) or - * specify `"none"` to avoid reading any configuration file. - * - `"dhchap_key"`: NVMe In-band authentication secret in ASCII format as described - * in the NVMe 2.0 specification. When not specified, the secret is by default read - * from `/etc/nvme/hostkey`. In case that file does not exist no in-band authentication - * is attempted. - * - `"dhchap_ctrl_key"`: NVMe In-band authentication controller secret for bi-directional authentication. - * When not specified, no bi-directional authentication is attempted. + * - `"config"`: Ignored, configuration files are not read anymore (see below). + * - `"kxchap_secret"`: NVMe In-band authentication (KX-HMAC-CHAP, formerly DH-HMAC-CHAP) host secret + * in ASCII format as described in the NVMe specification. When not specified, + * no in-band authentication is attempted. + * - `"kxchap_ctrl_secret"`: NVMe In-band authentication controller secret for bi-directional authentication. + * When not specified, no bi-directional authentication is attempted. + * - `"dhchap_key"`, `"dhchap_ctrl_key"`: Deprecated aliases for `"kxchap_secret"` and `"kxchap_ctrl_secret"`. * - `"nr_io_queues"`: The number of I/O queues. * - `"nr_write_queues"`: Number of additional queues that will be used for write I/O. * - `"nr_poll_queues"`: Number of additional queues that will be used for polling latency sensitive I/O. @@ -1320,12 +1319,8 @@ gboolean bd_nvme_set_host_id (const gchar *host_id, GError **error); * Boolean values can be expressed by "0"/"1", "on"/"off" or "True"/"False" case-insensitive * strings. Failed numerical or boolean string conversions will result in the option being ignored. * - * By default additional options are read from the default configuration file `/etc/nvme/config.json`. - * This follows the default behaviour of `nvme-cli`. Use the @extra `"config"` argument - * to either specify a different config file or disable use of it. The JSON configuration - * file format is documented in [https://raw.githubusercontent.com/linux-nvme/libnvme/master/doc/config-schema.json](https://raw.githubusercontent.com/linux-nvme/libnvme/master/doc/config-schema.json). - * As a rule @extra key names are kept consistent with the JSON config file schema. - * Any @extra option generally overrides particular option specified in a configuration file. + * The JSON configuration file `/etc/nvme/config.json` is not supported by libnvme3 anymore + * and is not read. * * Returns: %TRUE if the subsystem was connected successfully, %FALSE otherwise with @error set. * diff --git a/src/plugins/nvme/nvme-error.c b/src/plugins/nvme/nvme-error.c index 4bd4d7712..1287e2fa1 100644 --- a/src/plugins/nvme/nvme-error.c +++ b/src/plugins/nvme/nvme-error.c @@ -55,8 +55,8 @@ void _nvme_status_to_error (gint status, gboolean fabrics, GError **error) } if (status < 0) { - /* generic errno errors */ - switch (errno) { + /* generic errno errors, returned as negative values */ + switch (-status) { case EWOULDBLOCK: code = BD_NVME_ERROR_BUSY; break; @@ -64,7 +64,7 @@ void _nvme_status_to_error (gint status, gboolean fabrics, GError **error) code = BD_NVME_ERROR_FAILED; } g_set_error_literal (error, BD_NVME_ERROR, code, - strerror_l (errno, _C_LOCALE)); + strerror_l (-status, _C_LOCALE)); return; } else { /* NVMe status codes */ @@ -87,15 +87,16 @@ void _nvme_status_to_error (gint status, gboolean fabrics, GError **error) default: code = BD_NVME_ERROR_SC_GENERIC; } - g_set_error_literal (error, BD_NVME_ERROR, code, nvme_status_to_string (status, fabrics)); + g_set_error_literal (error, BD_NVME_ERROR, code, libnvme_status_to_string (status, fabrics)); return; } g_warn_if_reached (); } -void _nvme_fabrics_errno_to_gerror (int result, int _errno, GError **error) +void _nvme_fabrics_errno_to_gerror (int result, GError **error) { gint code; + int _errno = -result; if (error == NULL) return; @@ -143,17 +144,17 @@ void _nvme_fabrics_errno_to_gerror (int result, int _errno, GError **error) default: code = BD_NVME_ERROR_CONNECT; } - g_set_error_literal (error, BD_NVME_ERROR, code, nvme_errno_to_string (_errno)); + g_set_error_literal (error, BD_NVME_ERROR, code, libnvme_errno_to_string (_errno)); return; } else { - switch (errno) { + switch (_errno) { case EWOULDBLOCK: code = BD_NVME_ERROR_BUSY; break; default: code = BD_NVME_ERROR_FAILED; } - g_set_error_literal (error, BD_NVME_ERROR, code, strerror_l (errno, _C_LOCALE)); + g_set_error_literal (error, BD_NVME_ERROR, code, strerror_l (_errno, _C_LOCALE)); return; } g_warn_if_reached (); diff --git a/src/plugins/nvme/nvme-fabrics.c b/src/plugins/nvme/nvme-fabrics.c index 796b6bfc1..0f9a40ea3 100644 --- a/src/plugins/nvme/nvme-fabrics.c +++ b/src/plugins/nvme/nvme-fabrics.c @@ -37,113 +37,91 @@ #include "nvme-private.h" -/* nvme-cli defaults */ -#define PATH_NVMF_CONFIG "/etc/nvme/config.json" -#define MAX_DISC_RETRIES 10 - - -static void parse_extra_args (const BDExtraArg **extra, struct nvme_fabrics_config *cfg, const gchar **config_file, const gchar **hostkey, const gchar **ctrlkey, const gchar **hostsymname) { +static void parse_extra_args (const BDExtraArg **extra, struct libnvmf_context *fctx, const gchar **hostkey, const gchar **ctrlkey, const gchar **hostsymname, const gchar **keyring, const gchar **tls_key) { const BDExtraArg **extra_i; if (!extra) return; -#define SAFE_INT_CONV(target) { \ +#define SAFE_INT_CONV(setter) { \ gint64 v; \ gchar *endptr = NULL; \ \ v = g_ascii_strtoll ((*extra_i)->val, &endptr, 0); \ if (endptr != (*extra_i)->val) \ - target = v; \ + setter (fctx, v); \ } -#define SAFE_BOOL_CONV(target) { \ +#define SAFE_BOOL_CONV(setter) { \ if (g_ascii_strcasecmp ((*extra_i)->val, "on") == 0 || \ g_ascii_strcasecmp ((*extra_i)->val, "1") == 0 || \ g_ascii_strcasecmp ((*extra_i)->val, "true") == 0) \ - target = TRUE; \ + setter (fctx, TRUE); \ else \ if (g_ascii_strcasecmp ((*extra_i)->val, "off") == 0 || \ g_ascii_strcasecmp ((*extra_i)->val, "0") == 0 || \ g_ascii_strcasecmp ((*extra_i)->val, "false") == 0) \ - target = FALSE; \ + setter (fctx, FALSE); \ } for (extra_i = extra; *extra_i; extra_i++) { - if (g_strcmp0 ((*extra_i)->opt, "config") == 0 && config_file) { - if (g_ascii_strcasecmp ((*extra_i)->val, "none") == 0) - *config_file = NULL; - else - *config_file = (*extra_i)->val; - } else - if (g_strcmp0 ((*extra_i)->opt, "dhchap_key") == 0 && hostkey) + if ((g_strcmp0 ((*extra_i)->opt, "kxchap_secret") == 0 || + g_strcmp0 ((*extra_i)->opt, "dhchap_key") == 0 /* deprecated */) && hostkey) *hostkey = (*extra_i)->val; else - if (g_strcmp0 ((*extra_i)->opt, "dhchap_ctrl_key") == 0 && ctrlkey) + if ((g_strcmp0 ((*extra_i)->opt, "kxchap_ctrl_secret") == 0 || + g_strcmp0 ((*extra_i)->opt, "dhchap_ctrl_key") == 0 /* deprecated */) && ctrlkey) *ctrlkey = (*extra_i)->val; else if (g_strcmp0 ((*extra_i)->opt, "hostsymname") == 0 && hostsymname) *hostsymname = (*extra_i)->val; else + if (g_strcmp0 ((*extra_i)->opt, "keyring") == 0 && keyring) + *keyring = (*extra_i)->val; + else + if (g_strcmp0 ((*extra_i)->opt, "tls_key") == 0 && tls_key) + *tls_key = (*extra_i)->val; + else if (g_strcmp0 ((*extra_i)->opt, "nr_io_queues") == 0) - SAFE_INT_CONV (cfg->nr_io_queues) + SAFE_INT_CONV (libnvmf_context_set_nr_io_queues) else if (g_strcmp0 ((*extra_i)->opt, "nr_write_queues") == 0) - SAFE_INT_CONV (cfg->nr_write_queues) + SAFE_INT_CONV (libnvmf_context_set_nr_write_queues) else if (g_strcmp0 ((*extra_i)->opt, "nr_poll_queues") == 0) - SAFE_INT_CONV (cfg->nr_poll_queues) + SAFE_INT_CONV (libnvmf_context_set_nr_poll_queues) else if (g_strcmp0 ((*extra_i)->opt, "queue_size") == 0) - SAFE_INT_CONV (cfg->queue_size) + SAFE_INT_CONV (libnvmf_context_set_queue_size) else if (g_strcmp0 ((*extra_i)->opt, "keep_alive_tmo") == 0) - SAFE_INT_CONV (cfg->keep_alive_tmo) + SAFE_INT_CONV (libnvmf_context_set_keep_alive_tmo) else if (g_strcmp0 ((*extra_i)->opt, "reconnect_delay") == 0) - SAFE_INT_CONV (cfg->reconnect_delay) + SAFE_INT_CONV (libnvmf_context_set_reconnect_delay) else if (g_strcmp0 ((*extra_i)->opt, "ctrl_loss_tmo") == 0) - SAFE_INT_CONV (cfg->ctrl_loss_tmo) + SAFE_INT_CONV (libnvmf_context_set_ctrl_loss_tmo) else if (g_strcmp0 ((*extra_i)->opt, "fast_io_fail_tmo") == 0) - SAFE_INT_CONV (cfg->fast_io_fail_tmo) + SAFE_INT_CONV (libnvmf_context_set_fast_io_fail_tmo) else if (g_strcmp0 ((*extra_i)->opt, "tos") == 0) - SAFE_INT_CONV (cfg->tos) + SAFE_INT_CONV (libnvmf_context_set_tos) else if (g_strcmp0 ((*extra_i)->opt, "duplicate_connect") == 0) - SAFE_BOOL_CONV (cfg->duplicate_connect) + SAFE_BOOL_CONV (libnvmf_context_set_duplicate_connect) else if (g_strcmp0 ((*extra_i)->opt, "disable_sqflow") == 0) - SAFE_BOOL_CONV (cfg->disable_sqflow) + SAFE_BOOL_CONV (libnvmf_context_set_disable_sqflow) else if (g_strcmp0 ((*extra_i)->opt, "hdr_digest") == 0) - SAFE_BOOL_CONV (cfg->hdr_digest) + SAFE_BOOL_CONV (libnvmf_context_set_hdr_digest) else if (g_strcmp0 ((*extra_i)->opt, "data_digest") == 0) - SAFE_BOOL_CONV (cfg->data_digest) + SAFE_BOOL_CONV (libnvmf_context_set_data_digest) else if (g_strcmp0 ((*extra_i)->opt, "tls") == 0) - SAFE_BOOL_CONV (cfg->tls) -#ifdef HAVE_LIBNVME_1_4 - else - if (g_strcmp0 ((*extra_i)->opt, "keyring") == 0) { - int keyring; - - keyring = nvme_lookup_keyring ((*extra_i)->val); - if (keyring) { - cfg->keyring = keyring; - nvme_set_keyring (cfg->keyring); - } - } else - if (g_strcmp0 ((*extra_i)->opt, "tls_key") == 0) { - int key; - - key = nvme_lookup_key ("psk", (*extra_i)->val); - if (key) - cfg->tls_key = key; - } -#endif + SAFE_BOOL_CONV (libnvmf_context_set_tls) } #undef SAFE_INT_CONV @@ -175,14 +153,13 @@ static void parse_extra_args (const BDExtraArg **extra, struct nvme_fabrics_conf * - `"loop"`: A NVMe over Fabrics target on the local host. * * In addition to the primary options it's possible to supply @extra arguments: - * - `"config"`: Use the specified JSON configuration file instead of the default file (see below) or - * specify `"none"` to avoid reading any configuration file. - * - `"dhchap_key"`: NVMe In-band authentication secret in ASCII format as described - * in the NVMe 2.0 specification. When not specified, the secret is by default read - * from `/etc/nvme/hostkey`. In case that file does not exist no in-band authentication - * is attempted. - * - `"dhchap_ctrl_key"`: NVMe In-band authentication controller secret for bi-directional authentication. - * When not specified, no bi-directional authentication is attempted. + * - `"config"`: Ignored, configuration files are not read anymore (see below). + * - `"kxchap_secret"`: NVMe In-band authentication (KX-HMAC-CHAP, formerly DH-HMAC-CHAP) host secret + * in ASCII format as described in the NVMe specification. When not specified, + * no in-band authentication is attempted. + * - `"kxchap_ctrl_secret"`: NVMe In-band authentication controller secret for bi-directional authentication. + * When not specified, no bi-directional authentication is attempted. + * - `"dhchap_key"`, `"dhchap_ctrl_key"`: Deprecated aliases for `"kxchap_secret"` and `"kxchap_ctrl_secret"`. * - `"nr_io_queues"`: The number of I/O queues. * - `"nr_write_queues"`: Number of additional queues that will be used for write I/O. * - `"nr_poll_queues"`: Number of additional queues that will be used for polling latency sensitive I/O. @@ -204,12 +181,8 @@ static void parse_extra_args (const BDExtraArg **extra, struct nvme_fabrics_conf * Boolean values can be expressed by "0"/"1", "on"/"off" or "True"/"False" case-insensitive * strings. Failed numerical or boolean string conversions will result in the option being ignored. * - * By default additional options are read from the default configuration file `/etc/nvme/config.json`. - * This follows the default behaviour of `nvme-cli`. Use the @extra `"config"` argument - * to either specify a different config file or disable use of it. The JSON configuration - * file format is documented in [https://raw.githubusercontent.com/linux-nvme/libnvme/master/doc/config-schema.json](https://raw.githubusercontent.com/linux-nvme/libnvme/master/doc/config-schema.json). - * As a rule @extra key names are kept consistent with the JSON config file schema. - * Any @extra option generally overrides particular option specified in a configuration file. + * The JSON configuration file `/etc/nvme/config.json` is not supported by libnvme3 anymore + * and is not read. * * Returns: %TRUE if the subsystem was connected successfully, %FALSE otherwise with @error set. * @@ -217,16 +190,17 @@ static void parse_extra_args (const BDExtraArg **extra, struct nvme_fabrics_conf */ gboolean bd_nvme_connect (const gchar *subsysnqn, const gchar *transport, const gchar *transport_addr, const gchar *transport_svcid, const gchar *host_traddr, const gchar *host_iface, const gchar *host_nqn, const gchar *host_id, const BDExtraArg **extra, GError **error) { int ret; - const gchar *config_file = PATH_NVMF_CONFIG; - gchar *host_nqn_val; - gchar *host_id_val; + gchar *host_nqn_val = NULL; + gchar *host_id_val = NULL; const gchar *hostkey = NULL; const gchar *ctrlkey = NULL; const gchar *hostsymname = NULL; - nvme_root_t root; - nvme_host_t host; - nvme_ctrl_t ctrl; - struct nvme_fabrics_config cfg; + const gchar *keyring = NULL; + const gchar *tls_key = NULL; + struct libnvme_global_ctx *ctx; + struct libnvme_host *host; + struct libnvmf_context *fctx = NULL; + gboolean success = FALSE; if (subsysnqn == NULL) { g_set_error_literal (error, BD_NVME_ERROR, BD_NVME_ERROR_INVALID_ARGUMENT, @@ -244,61 +218,6 @@ gboolean bd_nvme_connect (const gchar *subsysnqn, const gchar *transport, const return FALSE; } - /* HostNQN checks */ - host_nqn_val = g_strdup (host_nqn); - host_id_val = g_strdup (host_id); - if (host_nqn_val == NULL) - host_nqn_val = nvmf_hostnqn_from_file (); - if (host_id_val == NULL) - host_id_val = nvmf_hostid_from_file (); - if (host_nqn_val == NULL) - host_nqn_val = nvmf_hostnqn_generate (); - if (host_nqn_val == NULL) { - g_set_error_literal (error, BD_NVME_ERROR, BD_NVME_ERROR_INVALID_ARGUMENT, - "Could not determine HostNQN"); - g_free (host_nqn_val); - g_free (host_id_val); - return FALSE; - } - if (host_id_val == NULL) { - /* derive hostid from hostnqn, newer kernels refuse empty hostid */ - host_id_val = g_strrstr (host_nqn_val, "uuid:"); - if (host_id_val) - host_id_val = g_strdup (host_id_val + strlen ("uuid:")); - /* TODO: in theory generating arbitrary uuid might work as a fallback */ - } - if (host_id_val == NULL) { - g_set_error (error, BD_NVME_ERROR, BD_NVME_ERROR_INVALID_ARGUMENT, - "Could not determine HostID value from HostNQN '%s'", - host_nqn_val); - g_free (host_nqn_val); - return FALSE; - } - - /* parse extra arguments */ - nvmf_default_config (&cfg); - parse_extra_args (extra, &cfg, &config_file, &hostkey, &ctrlkey, &hostsymname); - - root = nvme_scan (config_file); - g_assert (root != NULL); - nvme_init_logging (root, -1, false, false); - host = nvme_lookup_host (root, host_nqn_val, host_id_val); - if (host == NULL) { - g_set_error (error, BD_NVME_ERROR, BD_NVME_ERROR_FAILED, - "Unable to lookup host for HostNQN '%s' and HostID '%s'", - host_nqn_val, host_id_val); - g_free (host_nqn_val); - g_free (host_id_val); - nvme_free_tree (root); - return FALSE; - } - g_free (host_nqn_val); - g_free (host_id_val); - if (hostkey) - nvme_host_set_dhchap_key (host, hostkey); - if (hostsymname) - nvme_host_set_hostsymname (host, hostsymname); - /* tr_svcid defaults */ if (!transport_svcid) { if (g_strcmp0 (transport, "tcp") == 0) { @@ -311,68 +230,119 @@ gboolean bd_nvme_connect (const gchar *subsysnqn, const gchar *transport, const transport_svcid = G_STRINGIFY (NVME_RDMA_IP_PORT); } - ctrl = nvme_create_ctrl (root, subsysnqn, transport, transport_addr, host_traddr, host_iface, transport_svcid); - if (ctrl == NULL) { - _nvme_fabrics_errno_to_gerror (-1, errno, error); - g_prefix_error (error, "Error creating the controller: "); - nvme_free_tree (root); + ctx = libnvme_create_global_ctx (); + if (ctx == NULL) { + g_set_error_literal (error, BD_NVME_ERROR, BD_NVME_ERROR_FAILED, + "Failed to create libnvme context"); return FALSE; } - if (ctrlkey) - nvme_ctrl_set_dhchap_key (ctrl, ctrlkey); - ret = nvmf_add_ctrl (host, ctrl, &cfg); + /* HostNQN and HostID, read from /etc/nvme/ or generated when not specified */ + ret = libnvmf_host_get_ids (ctx, host_nqn, host_id, &host_nqn_val, &host_id_val); + if (ret != 0 || host_nqn_val == NULL || host_id_val == NULL) { + g_set_error (error, BD_NVME_ERROR, BD_NVME_ERROR_INVALID_ARGUMENT, + "Could not determine HostNQN or HostID: %s", + ret != 0 ? libnvme_strerror (-ret) : "unknown error"); + goto out; + } + + /* existing controllers need to be known to detect duplicate connections */ + libnvme_skip_namespaces (ctx); + ret = libnvme_scan_topology (ctx, NULL, NULL); + if (ret < 0) { + g_set_error (error, BD_NVME_ERROR, BD_NVME_ERROR_FAILED, + "Failed to scan topology: %s", + strerror_l (-ret, _C_LOCALE)); + goto out; + } + + ret = libnvmf_context_create (ctx, NULL, NULL, NULL, NULL, &fctx); + if (ret == 0) + ret = libnvmf_context_set_connection (fctx, subsysnqn, transport, transport_addr, transport_svcid, host_traddr, host_iface); + if (ret == 0) + ret = libnvmf_context_set_hostnqn (fctx, host_nqn_val, host_id_val); + if (ret != 0) { + _nvme_fabrics_errno_to_gerror (ret, error); + g_prefix_error (error, "Error creating the controller: "); + goto out; + } + + parse_extra_args (extra, fctx, &hostkey, &ctrlkey, &hostsymname, &keyring, &tls_key); + if (hostkey || ctrlkey || keyring || tls_key) { + ret = libnvmf_context_set_crypto (fctx, hostkey, ctrlkey, keyring, tls_key, NULL); + if (ret != 0) { + _nvme_fabrics_errno_to_gerror (ret, error); + g_prefix_error (error, "Error setting the controller keys: "); + goto out; + } + } + + if (hostsymname) { + ret = libnvme_get_host (ctx, host_nqn_val, host_id_val, &host); + if (ret != 0) { + g_set_error (error, BD_NVME_ERROR, BD_NVME_ERROR_FAILED, + "Unable to lookup host for HostNQN '%s' and HostID '%s': %s", + host_nqn_val, host_id_val, libnvme_strerror (-ret)); + goto out; + } + libnvme_host_set_hostsymname (host, hostsymname); + } + + ret = libnvmf_connect (ctx, fctx); if (ret != 0) { - _nvme_fabrics_errno_to_gerror (ret, errno, error); + _nvme_fabrics_errno_to_gerror (ret, error); g_prefix_error (error, "Error connecting the controller: "); - nvme_free_ctrl (ctrl); - nvme_free_tree (root); - return FALSE; + goto out; } - nvme_free_ctrl (ctrl); - nvme_free_tree (root); - return TRUE; + success = TRUE; + +out: + libnvmf_context_free (fctx); + libnvme_free_global_ctx (ctx); + free (host_nqn_val); + free (host_id_val); + + return success; } static gboolean _disconnect (const gchar *subsysnqn, const gchar *path, GError **error, gboolean *found) { - nvme_root_t root; - nvme_host_t host; - nvme_subsystem_t subsys; - nvme_ctrl_t ctrl; + struct libnvme_global_ctx *ctx; + struct libnvme_host *host; + struct libnvme_subsystem *subsys; + struct libnvme_ctrl *ctrl; int ret; - root = nvme_create_root (NULL, -1); - if (root == NULL) { - g_set_error (error, BD_NVME_ERROR, BD_NVME_ERROR_FAILED, - "Failed to create topology root: %s", - strerror_l (errno, _C_LOCALE)); + ctx = libnvme_create_global_ctx (); + if (ctx == NULL) { + g_set_error_literal (error, BD_NVME_ERROR, BD_NVME_ERROR_FAILED, + "Failed to create libnvme context"); return FALSE; } - ret = nvme_scan_topology (root, NULL, NULL); + ret = libnvme_scan_topology (ctx, NULL, NULL); if (ret < 0) { g_set_error (error, BD_NVME_ERROR, BD_NVME_ERROR_FAILED, "Failed to scan topology: %s", - strerror_l (errno, _C_LOCALE)); - nvme_free_tree (root); + strerror_l (-ret, _C_LOCALE)); + libnvme_free_global_ctx (ctx); return FALSE; } - nvme_for_each_host (root, host) - nvme_for_each_subsystem (host, subsys) - if (!subsysnqn || g_strcmp0 (nvme_subsystem_get_nqn (subsys), subsysnqn) == 0) - nvme_subsystem_for_each_ctrl (subsys, ctrl) - if (!path || g_strcmp0 (nvme_ctrl_get_name (ctrl), path) == 0) { - ret = nvme_disconnect_ctrl (ctrl); + libnvme_for_each_host (ctx, host) + libnvme_for_each_subsystem (host, subsys) + if (!subsysnqn || g_strcmp0 (libnvme_subsystem_get_subsysnqn (subsys), subsysnqn) == 0) + libnvme_subsystem_for_each_ctrl (subsys, ctrl) + if (!path || g_strcmp0 (libnvme_ctrl_get_name (ctrl), path) == 0) { + ret = libnvmf_disconnect_ctrl (ctrl); if (ret != 0) { g_set_error (error, BD_NVME_ERROR, BD_NVME_ERROR_FAILED, "Error disconnecting the controller: %s", - strerror_l (errno, _C_LOCALE)); - nvme_free_tree (root); + strerror_l (-ret, _C_LOCALE)); + libnvme_free_global_ctx (ctx); return FALSE; } *found = TRUE; } - nvme_free_tree (root); + libnvme_free_global_ctx (ctx); return TRUE; } @@ -455,11 +425,12 @@ gboolean bd_nvme_disconnect_by_path (const gchar *path, GError **error) { */ gchar ** bd_nvme_find_ctrls_for_ns (const gchar *ns_sysfs_path, const gchar *subsysnqn, const gchar *host_nqn, const gchar *host_id, GError **error G_GNUC_UNUSED) { GPtrArray *ptr_array; - nvme_root_t root; - nvme_host_t h; - nvme_subsystem_t s; - nvme_ctrl_t c; - nvme_ns_t n; + struct libnvme_global_ctx *ctx; + struct libnvme_host *h; + struct libnvme_subsystem *s; + struct libnvme_ctrl *c; + struct libnvme_ns *n; + int ret; char realp[PATH_MAX]; gchar *subsysnqn_p; @@ -470,33 +441,41 @@ gchar ** bd_nvme_find_ctrls_for_ns (const gchar *ns_sysfs_path, const gchar *sub ptr_array = g_ptr_array_new (); - root = nvme_scan (NULL); - g_warn_if_fail (root != NULL); + ctx = libnvme_create_global_ctx (); + if (ctx == NULL) { + g_ptr_array_add (ptr_array, NULL); /* trailing NULL element */ + g_free (subsysnqn_p); + return (gchar **) g_ptr_array_free (ptr_array, FALSE); + } + ret = libnvme_scan_topology (ctx, NULL, NULL); + if (ret < 0) + bd_utils_log_format (BD_UTILS_LOG_WARNING, "Failed to scan topology: %s", + strerror_l (-ret, _C_LOCALE)); - nvme_for_each_host (root, h) { - if (host_nqn && g_strcmp0 (nvme_host_get_hostnqn (h), host_nqn) != 0) + libnvme_for_each_host (ctx, h) { + if (host_nqn && g_strcmp0 (libnvme_host_get_hostnqn (h), host_nqn) != 0) continue; - if (host_id && g_strcmp0 (nvme_host_get_hostid (h), host_id) != 0) + if (host_id && g_strcmp0 (libnvme_host_get_hostid (h), host_id) != 0) continue; - nvme_for_each_subsystem (h, s) { + libnvme_for_each_subsystem (h, s) { gboolean found = FALSE; - if (subsysnqn && g_strcmp0 (nvme_subsystem_get_nqn (s), subsysnqn_p) != 0) + if (subsysnqn && g_strcmp0 (libnvme_subsystem_get_subsysnqn (s), subsysnqn_p) != 0) continue; - nvme_subsystem_for_each_ctrl (s, c) - nvme_ctrl_for_each_ns (c, n) - if (realpath (nvme_ns_get_sysfs_dir (n), realp) && + libnvme_subsystem_for_each_ctrl (s, c) + libnvme_ctrl_for_each_ns (c, n) + if (realpath (libnvme_ns_get_sysfs_dir (n), realp) && g_strcmp0 (realp, ns_sysfs_path) == 0) { - if (realpath (nvme_ctrl_get_sysfs_dir (c), realp)) { + if (realpath (libnvme_ctrl_get_sysfs_dir (c), realp)) { g_ptr_array_add (ptr_array, g_strdup (realp)); break; } } - nvme_subsystem_for_each_ns (s, n) - if (realpath (nvme_ns_get_sysfs_dir (n), realp) && + libnvme_subsystem_for_each_ns (s, n) + if (realpath (libnvme_ns_get_sysfs_dir (n), realp) && g_strcmp0 (realp, ns_sysfs_path) == 0) { found = TRUE; /* at least one of the namespaces match, don't care about the rest */ @@ -505,14 +484,14 @@ gchar ** bd_nvme_find_ctrls_for_ns (const gchar *ns_sysfs_path, const gchar *sub if (found) /* add all controllers in the subsystem */ - nvme_subsystem_for_each_ctrl (s, c) { - if (realpath (nvme_ctrl_get_sysfs_dir (c), realp)) { + libnvme_subsystem_for_each_ctrl (s, c) { + if (realpath (libnvme_ctrl_get_sysfs_dir (c), realp)) { g_ptr_array_add (ptr_array, g_strdup (realp)); } } } } - nvme_free_tree (root); + libnvme_free_global_ctx (ctx); g_free (subsysnqn_p); g_ptr_array_add (ptr_array, NULL); /* trailing NULL element */ @@ -538,11 +517,12 @@ gchar ** bd_nvme_find_ctrls_for_ns (const gchar *ns_sysfs_path, const gchar *sub */ gchar ** bd_nvme_find_namespaces_for_ctrl (const gchar *ctrl_sysfs_path, const gchar *subsysnqn, const gchar *host_nqn, const gchar *host_id, GError **error G_GNUC_UNUSED) { GPtrArray *ptr_array; - nvme_root_t root; - nvme_host_t h; - nvme_subsystem_t s; - nvme_ctrl_t c; - nvme_ns_t n; + struct libnvme_global_ctx *ctx; + struct libnvme_host *h; + struct libnvme_subsystem *s; + struct libnvme_ctrl *c; + struct libnvme_ns *n; + int ret; char realp[PATH_MAX]; gchar *subsysnqn_p; @@ -553,31 +533,39 @@ gchar ** bd_nvme_find_namespaces_for_ctrl (const gchar *ctrl_sysfs_path, const g ptr_array = g_ptr_array_new (); - root = nvme_scan (NULL); - g_warn_if_fail (root != NULL); + ctx = libnvme_create_global_ctx (); + if (ctx == NULL) { + g_ptr_array_add (ptr_array, NULL); /* trailing NULL element */ + g_free (subsysnqn_p); + return (gchar **) g_ptr_array_free (ptr_array, FALSE); + } + ret = libnvme_scan_topology (ctx, NULL, NULL); + if (ret < 0) + bd_utils_log_format (BD_UTILS_LOG_WARNING, "Failed to scan topology: %s", + strerror_l (-ret, _C_LOCALE)); - nvme_for_each_host (root, h) { - if (host_nqn && g_strcmp0 (nvme_host_get_hostnqn (h), host_nqn) != 0) + libnvme_for_each_host (ctx, h) { + if (host_nqn && g_strcmp0 (libnvme_host_get_hostnqn (h), host_nqn) != 0) continue; - if (host_id && g_strcmp0 (nvme_host_get_hostid (h), host_id) != 0) + if (host_id && g_strcmp0 (libnvme_host_get_hostid (h), host_id) != 0) continue; - nvme_for_each_subsystem (h, s) { - if (subsysnqn && g_strcmp0 (nvme_subsystem_get_nqn (s), subsysnqn_p) != 0) + libnvme_for_each_subsystem (h, s) { + if (subsysnqn && g_strcmp0 (libnvme_subsystem_get_subsysnqn (s), subsysnqn_p) != 0) continue; - nvme_subsystem_for_each_ctrl (s, c) { - if (realpath (nvme_ctrl_get_sysfs_dir (c), realp) && + libnvme_subsystem_for_each_ctrl (s, c) { + if (realpath (libnvme_ctrl_get_sysfs_dir (c), realp) && g_strcmp0 (realp, ctrl_sysfs_path) == 0) { /* Found matching controller, add all its namespaces */ - nvme_ctrl_for_each_ns (c, n) { - if (realpath (nvme_ns_get_sysfs_dir (n), realp)) { + libnvme_ctrl_for_each_ns (c, n) { + if (realpath (libnvme_ns_get_sysfs_dir (n), realp)) { g_ptr_array_add (ptr_array, g_strdup (realp)); } } /* Also check subsystem-level namespaces */ - nvme_subsystem_for_each_ns (s, n) { - if (realpath (nvme_ns_get_sysfs_dir (n), realp)) { + libnvme_subsystem_for_each_ns (s, n) { + if (realpath (libnvme_ns_get_sysfs_dir (n), realp)) { g_ptr_array_add (ptr_array, g_strdup (realp)); } } @@ -586,7 +574,7 @@ gchar ** bd_nvme_find_namespaces_for_ctrl (const gchar *ctrl_sysfs_path, const g } } } - nvme_free_tree (root); + libnvme_free_global_ctx (ctx); g_free (subsysnqn_p); g_ptr_array_add (ptr_array, NULL); /* trailing NULL element */ @@ -605,11 +593,19 @@ gchar ** bd_nvme_find_namespaces_for_ctrl (const gchar *ctrl_sysfs_path, const g * * Tech category: %BD_NVME_TECH_FABRICS-%BD_NVME_TECH_MODE_INITIATOR */ -gchar * bd_nvme_get_host_nqn (G_GNUC_UNUSED GError **error) { +gchar * bd_nvme_get_host_nqn (GError **error) { + struct libnvme_global_ctx *ctx; char *hostnqn; + ctx = libnvme_create_global_ctx (); + if (ctx == NULL) { + g_set_error_literal (error, BD_NVME_ERROR, BD_NVME_ERROR_FAILED, + "Failed to create libnvme context"); + return NULL; + } /* FIXME: libnvme SYSCONFDIR might be different from PACKAGE_SYSCONF_DIR */ - hostnqn = nvmf_hostnqn_from_file (); + hostnqn = libnvmf_read_hostnqn (ctx); + libnvme_free_global_ctx (ctx); return hostnqn ? hostnqn : g_strdup (""); } @@ -626,9 +622,17 @@ gchar * bd_nvme_get_host_nqn (G_GNUC_UNUSED GError **error) { * Tech category: %BD_NVME_TECH_FABRICS-%BD_NVME_TECH_MODE_INITIATOR */ gchar * bd_nvme_generate_host_nqn (GError **error) { + struct libnvme_global_ctx *ctx; char *nqn; - nqn = nvmf_hostnqn_generate (); + ctx = libnvme_create_global_ctx (); + if (ctx == NULL) { + g_set_error_literal (error, BD_NVME_ERROR, BD_NVME_ERROR_FAILED, + "Failed to create libnvme context"); + return NULL; + } + nqn = libnvmf_generate_hostnqn (ctx); + libnvme_free_global_ctx (ctx); if (!nqn) g_set_error_literal (error, BD_NVME_ERROR, BD_NVME_ERROR_INVALID_ARGUMENT, "Unable to generate Host NQN."); @@ -647,10 +651,18 @@ gchar * bd_nvme_generate_host_nqn (GError **error) { * * Tech category: %BD_NVME_TECH_FABRICS-%BD_NVME_TECH_MODE_INITIATOR */ -gchar * bd_nvme_get_host_id (G_GNUC_UNUSED GError **error) { +gchar * bd_nvme_get_host_id (GError **error) { + struct libnvme_global_ctx *ctx; char *hostid; - hostid = nvmf_hostid_from_file (); + ctx = libnvme_create_global_ctx (); + if (ctx == NULL) { + g_set_error_literal (error, BD_NVME_ERROR, BD_NVME_ERROR_FAILED, + "Failed to create libnvme context"); + return NULL; + } + hostid = libnvmf_read_hostid (ctx); + libnvme_free_global_ctx (ctx); return hostid ? hostid : g_strdup (""); } diff --git a/src/plugins/nvme/nvme-info.c b/src/plugins/nvme/nvme-info.c index b36a644b5..c832edb44 100644 --- a/src/plugins/nvme/nvme-info.c +++ b/src/plugins/nvme/nvme-info.c @@ -389,17 +389,57 @@ static guint64 int128_to_guint64 (__u8 data[16]) return result; } -gint _open_dev (const gchar *device, GError **error) { - int fd; +struct libnvme_transport_handle *_open_dev (const gchar *device, struct libnvme_global_ctx **ctx, GError **error) { + struct libnvme_transport_handle *hdl = NULL; + gchar *realp; + int ret; - fd = open (device, O_RDONLY); - if (fd < 0) { - _nvme_status_to_error (-1, FALSE, error); + /* libnvme_open() only takes the kernel device name, resolve any symlinks first */ + realp = realpath (device, NULL); + if (!realp) { + _nvme_status_to_error (-errno, FALSE, error); g_prefix_error (error, "Failed to open device '%s': ", device); - return -1; + return NULL; + } + + *ctx = libnvme_create_global_ctx (); + if (!*ctx) { + g_set_error_literal (error, BD_NVME_ERROR, BD_NVME_ERROR_FAILED, + "Failed to create libnvme context"); + free (realp); + return NULL; + } + + ret = libnvme_open (*ctx, realp, O_RDONLY, &hdl); + free (realp); + if (ret != 0) { + _nvme_status_to_error (ret, FALSE, error); + g_prefix_error (error, "Failed to open device '%s': ", device); + libnvme_free_global_ctx (*ctx); + *ctx = NULL; + return NULL; } - return fd; + return hdl; +} + +void _close_dev (struct libnvme_global_ctx *ctx, struct libnvme_transport_handle *hdl) { + libnvme_close (hdl); + libnvme_free_global_ctx (ctx); +} + +int _nvme_identify_ctrl (struct libnvme_transport_handle *hdl, struct nvme_id_ctrl *ctrl_id) { + struct libnvme_passthru_cmd cmd; + + nvme_init_identify_ctrl (&cmd, ctrl_id); + return libnvme_exec_admin_passthru (hdl, &cmd); +} + +int _nvme_identify_ns (struct libnvme_transport_handle *hdl, __u32 nsid, struct nvme_id_ns *ns_info) { + struct libnvme_passthru_cmd cmd; + + nvme_init_identify_ns (&cmd, nsid, ns_info); + return libnvme_exec_admin_passthru (hdl, &cmd); } /* backported from nvme-cli: https://github.com/linux-nvme/nvme-cli/pull/2051 */ @@ -441,7 +481,7 @@ static gchar *decode_nvme_rev (guint32 ver) { static gchar *_uuid_to_str (unsigned char uuid[NVME_UUID_LEN]) { gchar uuid_buf[NVME_UUID_LEN_STRING] = ZERO_INIT; - if (nvme_uuid_to_string (uuid, uuid_buf) == 0) + if (libnvme_uuid_to_string (uuid, uuid_buf) == 0) return g_strdup (uuid_buf); return NULL; } @@ -469,30 +509,31 @@ static gboolean _nvme_a_is_zero (const __u8 a[], int len) { */ BDNVMEControllerInfo * bd_nvme_get_controller_info (const gchar *device, GError **error) { int ret; - int fd; + struct libnvme_global_ctx *ctx; + struct libnvme_transport_handle *hdl; struct nvme_id_ctrl *ctrl_id; BDNVMEControllerInfo *info; /* open the block device */ - fd = _open_dev (device, error); - if (fd < 0) + hdl = _open_dev (device, &ctx, error); + if (!hdl) return NULL; ctrl_id = _nvme_alloc (sizeof (struct nvme_id_ctrl), error); if (!ctrl_id) { - close (fd); + _close_dev (ctx, hdl); return NULL; } /* send the NVME_IDENTIFY_CNS_CTRL ioctl */ - ret = nvme_identify_ctrl (fd, ctrl_id); + ret = _nvme_identify_ctrl (hdl, ctrl_id); if (ret != 0) { _nvme_status_to_error (ret, FALSE, error); g_prefix_error (error, "NVMe Identify Controller command error: "); - close (fd); + _close_dev (ctx, hdl); free (ctrl_id); return NULL; } - close (fd); + _close_dev (ctx, hdl); info = g_new0 (BDNVMEControllerInfo, 1); if ((ctrl_id->cmic & NVME_CTRL_CMIC_MULTI_PORT) == NVME_CTRL_CMIC_MULTI_PORT) @@ -595,7 +636,9 @@ BDNVMENamespaceInfo *bd_nvme_get_namespace_info (const gchar *device, GError **e int ret_ctrl; int ret_desc = -1; int ret_ns_ind = -1; - int fd; + struct libnvme_global_ctx *ctx; + struct libnvme_transport_handle *hdl; + struct libnvme_passthru_cmd cmd; __u32 nsid = 0; struct nvme_id_ctrl *ctrl_id; struct nvme_id_ns *ns_info; @@ -609,30 +652,30 @@ BDNVMENamespaceInfo *bd_nvme_get_namespace_info (const gchar *device, GError **e GPtrArray *ptr_array; /* open the block device */ - fd = _open_dev (device, error); - if (fd < 0) + hdl = _open_dev (device, &ctx, error); + if (!hdl) return NULL; /* get Namespace Identifier (NSID) for the @device (NVME_IOCTL_ID) */ - ret = nvme_get_nsid (fd, &nsid); + ret = libnvme_get_nsid (hdl, &nsid); if (ret != 0) { _nvme_status_to_error (ret, FALSE, error); g_prefix_error (error, "Error getting Namespace Identifier (NSID): "); - close (fd); + _close_dev (ctx, hdl); return NULL; } /* send the NVME_IDENTIFY_CNS_NS ioctl */ ns_info = _nvme_alloc (sizeof (struct nvme_id_ns), error); if (!ns_info) { - close (fd); + _close_dev (ctx, hdl); return NULL; } - ret = nvme_identify_ns (fd, nsid, ns_info); + ret = _nvme_identify_ns (hdl, nsid, ns_info); if (ret != 0) { _nvme_status_to_error (ret, FALSE, error); g_prefix_error (error, "NVMe Identify Namespace command error: "); - close (fd); + _close_dev (ctx, hdl); free (ns_info); return NULL; } @@ -640,26 +683,30 @@ BDNVMENamespaceInfo *bd_nvme_get_namespace_info (const gchar *device, GError **e /* send the NVME_IDENTIFY_CNS_CTRL ioctl */ ctrl_id = _nvme_alloc (sizeof (struct nvme_id_ctrl), error); if (!ctrl_id) { - close (fd); + _close_dev (ctx, hdl); free (ns_info); return NULL; } - ret_ctrl = nvme_identify_ctrl (fd, ctrl_id); + ret_ctrl = _nvme_identify_ctrl (hdl, ctrl_id); /* send the NVME_IDENTIFY_CNS_NS_DESC_LIST ioctl, NVMe 1.3 */ if (ret_ctrl == 0 && GUINT32_FROM_LE (ctrl_id->ver) >= 0x10300) { descs = _nvme_alloc (NVME_IDENTIFY_DATA_SIZE, NULL); - if (descs != NULL) - ret_desc = nvme_identify_ns_descs (fd, nsid, descs); + if (descs != NULL) { + nvme_init_identify_ns_descs_list (&cmd, nsid, descs); + ret_desc = libnvme_exec_admin_passthru (hdl, &cmd); + } } /* send the NVME_IDENTIFY_CNS_CSI_INDEPENDENT_ID_NS ioctl, NVMe 2.0 */ if (ret_ctrl == 0 && GUINT32_FROM_LE (ctrl_id->ver) >= 0x20000) { ns_info_ind = _nvme_alloc (sizeof (struct nvme_id_independent_id_ns), NULL); - if (ns_info_ind != NULL) - ret_ns_ind = nvme_identify_independent_identify_ns (fd, nsid, ns_info_ind); + if (ns_info_ind != NULL) { + nvme_init_identify_csi_independent_identify_id_ns (&cmd, nsid, ns_info_ind); + ret_ns_ind = libnvme_exec_admin_passthru (hdl, &cmd); + } } - close (fd); + _close_dev (ctx, hdl); info = g_new0 (BDNVMENamespaceInfo, 1); info->nsid = nsid; @@ -764,28 +811,30 @@ BDNVMENamespaceInfo *bd_nvme_get_namespace_info (const gchar *device, GError **e BDNVMESmartLog * bd_nvme_get_smart_log (const gchar *device, GError **error) { int ret; int ret_identify; - int fd; + struct libnvme_global_ctx *ctx; + struct libnvme_transport_handle *hdl; + struct libnvme_passthru_cmd cmd; struct nvme_id_ctrl *ctrl_id; struct nvme_smart_log *smart_log; BDNVMESmartLog *log; guint i; /* open the block device */ - fd = _open_dev (device, error); - if (fd < 0) + hdl = _open_dev (device, &ctx, error); + if (!hdl) return NULL; /* send the NVME_IDENTIFY_CNS_CTRL ioctl */ ctrl_id = _nvme_alloc (sizeof (struct nvme_id_ctrl), error); if (!ctrl_id) { - close (fd); + _close_dev (ctx, hdl); return NULL; } - ret_identify = nvme_identify_ctrl (fd, ctrl_id); + ret_identify = _nvme_identify_ctrl (hdl, ctrl_id); if (ret_identify != 0) { _nvme_status_to_error (ret_identify, FALSE, error); g_prefix_error (error, "NVMe Identify Controller command error: "); - close (fd); + _close_dev (ctx, hdl); free (ctrl_id); return NULL; } @@ -793,20 +842,21 @@ BDNVMESmartLog * bd_nvme_get_smart_log (const gchar *device, GError **error) { /* send the NVME_LOG_LID_SMART ioctl */ smart_log = _nvme_alloc (sizeof (struct nvme_smart_log), error); if (!smart_log) { - close (fd); + _close_dev (ctx, hdl); free (ctrl_id); return NULL; } - ret = nvme_get_log_smart (fd, NVME_NSID_ALL, FALSE /* rae */, smart_log); + nvme_init_get_log_smart (&cmd, NVME_NSID_ALL, smart_log); + ret = libnvme_get_log (hdl, &cmd, FALSE /* rae */, NVME_LOG_PAGE_PDU_SIZE); if (ret != 0) { _nvme_status_to_error (ret, FALSE, error); g_prefix_error (error, "NVMe Get Log Page - SMART / Health Information Log command error: "); - close (fd); + _close_dev (ctx, hdl); free (ctrl_id); free (smart_log); return NULL; } - close (fd); + _close_dev (ctx, hdl); log = g_new0 (BDNVMESmartLog, 1); if ((smart_log->critical_warning & NVME_SMART_CRIT_SPARE) == NVME_SMART_CRIT_SPARE) @@ -875,7 +925,9 @@ BDNVMESmartLog * bd_nvme_get_smart_log (const gchar *device, GError **error) { */ BDNVMEErrorLogEntry ** bd_nvme_get_error_log_entries (const gchar *device, GError **error) { int ret; - int fd; + struct libnvme_global_ctx *ctx; + struct libnvme_transport_handle *hdl; + struct libnvme_passthru_cmd cmd; guint elpe; struct nvme_id_ctrl *ctrl_id; struct nvme_error_log_page *err_log; @@ -883,21 +935,21 @@ BDNVMEErrorLogEntry ** bd_nvme_get_error_log_entries (const gchar *device, GErro guint i; /* open the block device */ - fd = _open_dev (device, error); - if (fd < 0) + hdl = _open_dev (device, &ctx, error); + if (!hdl) return NULL; /* find out the maximum number of error log entries as reported by the controller */ ctrl_id = _nvme_alloc (sizeof (struct nvme_id_ctrl), error); if (!ctrl_id) { - close (fd); + _close_dev (ctx, hdl); return NULL; } - ret = nvme_identify_ctrl (fd, ctrl_id); + ret = _nvme_identify_ctrl (hdl, ctrl_id); if (ret != 0) { _nvme_status_to_error (ret, FALSE, error); g_prefix_error (error, "NVMe Identify Controller command error: "); - close (fd); + _close_dev (ctx, hdl); free (ctrl_id); return NULL; } @@ -908,18 +960,19 @@ BDNVMEErrorLogEntry ** bd_nvme_get_error_log_entries (const gchar *device, GErro /* send the NVME_LOG_LID_ERROR ioctl */ err_log = _nvme_alloc (sizeof (struct nvme_error_log_page) * elpe, error); if (!err_log) { - close (fd); + _close_dev (ctx, hdl); return NULL; } - ret = nvme_get_log_error (fd, elpe, FALSE /* rae */, err_log); + nvme_init_get_log_error (&cmd, elpe, err_log); + ret = libnvme_get_log (hdl, &cmd, FALSE /* rae */, NVME_LOG_PAGE_PDU_SIZE); if (ret != 0) { _nvme_status_to_error (ret, FALSE, error); g_prefix_error (error, "NVMe Get Log Page - Error Information Log Entry command error: "); - close (fd); + _close_dev (ctx, hdl); free (err_log); return NULL; } - close (fd); + _close_dev (ctx, hdl); /* parse the log */ ptr_array = g_ptr_array_new (); @@ -963,32 +1016,35 @@ BDNVMEErrorLogEntry ** bd_nvme_get_error_log_entries (const gchar *device, GErro */ BDNVMESelfTestLog * bd_nvme_get_self_test_log (const gchar *device, GError **error) { int ret; - int fd; + struct libnvme_global_ctx *ctx; + struct libnvme_transport_handle *hdl; + struct libnvme_passthru_cmd cmd; struct nvme_self_test_log *self_test_log; BDNVMESelfTestLog *log; GPtrArray *ptr_array; guint i; /* open the block device */ - fd = _open_dev (device, error); - if (fd < 0) + hdl = _open_dev (device, &ctx, error); + if (!hdl) return NULL; /* send the NVME_LOG_LID_DEVICE_SELF_TEST ioctl */ self_test_log = _nvme_alloc (sizeof (struct nvme_self_test_log), error); if (!self_test_log) { - close (fd); + _close_dev (ctx, hdl); return NULL; } - ret = nvme_get_log_device_self_test (fd, self_test_log); + nvme_init_get_log_device_self_test (&cmd, self_test_log); + ret = libnvme_get_log (hdl, &cmd, FALSE /* rae */, NVME_LOG_PAGE_PDU_SIZE); if (ret != 0) { _nvme_status_to_error (ret, FALSE, error); g_prefix_error (error, "NVMe Get Log Page - Device Self-test Log command error: "); - close (fd); + _close_dev (ctx, hdl); free (self_test_log); return NULL; } - close (fd); + _close_dev (ctx, hdl); log = g_new0 (BDNVMESelfTestLog, 1); switch (self_test_log->current_operation & NVME_ST_CURR_OP_MASK) { @@ -1111,36 +1167,39 @@ BDNVMESelfTestLog * bd_nvme_get_self_test_log (const gchar *device, GError **err */ BDNVMESanitizeLog * bd_nvme_get_sanitize_log (const gchar *device, GError **error) { int ret; - int fd; + struct libnvme_global_ctx *ctx; + struct libnvme_transport_handle *hdl; + struct libnvme_passthru_cmd cmd; struct nvme_sanitize_log_page *sanitize_log; BDNVMESanitizeLog *log; __u16 sstat; /* open the block device */ - fd = _open_dev (device, error); - if (fd < 0) + hdl = _open_dev (device, &ctx, error); + if (!hdl) return NULL; /* send the NVME_LOG_LID_SANITIZE ioctl */ sanitize_log = _nvme_alloc (sizeof (struct nvme_sanitize_log_page), error); if (!sanitize_log) { - close (fd); + _close_dev (ctx, hdl); return NULL; } - ret = nvme_get_log_sanitize (fd, FALSE /* rae */, sanitize_log); + nvme_init_get_log_sanitize (&cmd, sanitize_log); + ret = libnvme_get_log (hdl, &cmd, FALSE /* rae */, NVME_LOG_PAGE_PDU_SIZE); if (ret != 0) { _nvme_status_to_error (ret, FALSE, error); g_prefix_error (error, "NVMe Get Log Page - Sanitize Status Log command error: "); - close (fd); + _close_dev (ctx, hdl); free (sanitize_log); return NULL; } - close (fd); + _close_dev (ctx, hdl); log = g_new0 (BDNVMESanitizeLog, 1); log->sanitize_progress = 0; sstat = GUINT16_FROM_LE (sanitize_log->sstat); - if ((sstat & NVME_SANITIZE_SSTAT_STATUS_MASK) == NVME_SANITIZE_SSTAT_STATUS_IN_PROGESS) + if ((sstat & NVME_SANITIZE_SSTAT_STATUS_MASK) == NVME_SANITIZE_SSTAT_STATUS_IN_PROGRESS) log->sanitize_progress = ((gdouble) GUINT16_FROM_LE (sanitize_log->sprog) * 100) / 0x10000; log->global_data_erased = sstat & NVME_SANITIZE_SSTAT_GLOBAL_DATA_ERASED; log->overwrite_passes = (sstat >> NVME_SANITIZE_SSTAT_COMPLETED_PASSES_SHIFT) & NVME_SANITIZE_SSTAT_COMPLETED_PASSES_MASK; @@ -1149,7 +1208,7 @@ BDNVMESanitizeLog * bd_nvme_get_sanitize_log (const gchar *device, GError **erro case NVME_SANITIZE_SSTAT_STATUS_COMPLETE_SUCCESS: log->sanitize_status = BD_NVME_SANITIZE_STATUS_SUCCESS; break; - case NVME_SANITIZE_SSTAT_STATUS_IN_PROGESS: + case NVME_SANITIZE_SSTAT_STATUS_IN_PROGRESS: log->sanitize_status = BD_NVME_SANITIZE_STATUS_IN_PROGRESS; break; case NVME_SANITIZE_SSTAT_STATUS_COMPLETED_FAILED: diff --git a/src/plugins/nvme/nvme-op.c b/src/plugins/nvme/nvme-op.c index 962722222..835b63b83 100644 --- a/src/plugins/nvme/nvme-op.c +++ b/src/plugins/nvme/nvme-op.c @@ -57,25 +57,24 @@ */ gboolean bd_nvme_device_self_test (const gchar *device, BDNVMESelfTestAction action, GError **error) { int ret; - struct nvme_dev_self_test_args args = { - .args_size = sizeof(args), - .result = NULL, - .timeout = NVME_DEFAULT_IOCTL_TIMEOUT, - .nsid = 0xffffffff, - }; + struct libnvme_global_ctx *ctx; + struct libnvme_transport_handle *hdl; + struct libnvme_passthru_cmd cmd; + __u32 nsid = NVME_NSID_ALL; + enum nvme_dst_stc stc; switch (action) { case BD_NVME_SELF_TEST_ACTION_SHORT: - args.stc = NVME_DST_STC_SHORT; + stc = NVME_DST_STC_SHORT; break; case BD_NVME_SELF_TEST_ACTION_EXTENDED: - args.stc = NVME_DST_STC_LONG; + stc = NVME_DST_STC_LONG; break; case BD_NVME_SELF_TEST_ACTION_VENDOR_SPECIFIC: - args.stc = NVME_DST_STC_VS; + stc = NVME_DST_STC_VS; break; case BD_NVME_SELF_TEST_ACTION_ABORT: - args.stc = NVME_DST_STC_ABORT; + stc = NVME_DST_STC_ABORT; break; default: g_set_error (error, BD_NVME_ERROR, BD_NVME_ERROR_INVALID_ARGUMENT, @@ -84,37 +83,38 @@ gboolean bd_nvme_device_self_test (const gchar *device, BDNVMESelfTestAction act } /* open the block device */ - args.fd = _open_dev (device, error); - if (args.fd < 0) + hdl = _open_dev (device, &ctx, error); + if (!hdl) return FALSE; /* get Namespace Identifier (NSID) for the @device (NVME_IOCTL_ID) */ - ret = nvme_get_nsid (args.fd, &args.nsid); - if (ret < 0 && errno == ENOTTY) + ret = libnvme_get_nsid (hdl, &nsid); + if (ret == -ENOTTY) /* not a block device, assuming controller character device */ - args.nsid = 0xffffffff; + nsid = NVME_NSID_ALL; else if (ret != 0) { _nvme_status_to_error (ret, FALSE, error); g_prefix_error (error, "Error getting Namespace Identifier (NSID): "); - close (args.fd); + _close_dev (ctx, hdl); return FALSE; } - ret = nvme_dev_self_test (&args); + nvme_init_dev_self_test (&cmd, nsid, stc); + ret = libnvme_exec_admin_passthru (hdl, &cmd); if (ret != 0) { _nvme_status_to_error (ret, FALSE, error); g_prefix_error (error, "NVMe Device Self-test command error: "); - close (args.fd); + _close_dev (ctx, hdl); return FALSE; } - close (args.fd); + _close_dev (ctx, hdl); return TRUE; } /* returns 0xff in case of error (the NVMe standard defines total of 16 flba records) */ -static __u8 find_lbaf_for_size (int fd, __u32 nsid, guint16 lba_data_size, guint16 metadata_size, GError **error) { +static __u8 find_lbaf_for_size (struct libnvme_transport_handle *hdl, __u32 nsid, guint16 lba_data_size, guint16 metadata_size, GError **error) { int ret; struct nvme_id_ns *ns_info; __u8 flbas = 0; @@ -124,7 +124,7 @@ static __u8 find_lbaf_for_size (int fd, __u32 nsid, guint16 lba_data_size, guint ns_info = _nvme_alloc (sizeof (struct nvme_id_ns), error); if (!ns_info) return 0xff; - ret = nvme_identify_ns (fd, nsid == 0xffffffff ? 1 : nsid, ns_info); + ret = _nvme_identify_ns (hdl, nsid == NVME_NSID_ALL ? 1 : nsid, ns_info); if (ret != 0) { _nvme_status_to_error (ret, FALSE, error); g_prefix_error (error, "NVMe Identify Namespace command error: "); @@ -185,31 +185,27 @@ gboolean bd_nvme_format (const gchar *device, guint16 lba_data_size, guint16 met int ret; gboolean ctrl_device = FALSE; struct nvme_id_ctrl *ctrl_id; - struct nvme_format_nvm_args args = { - .args_size = sizeof(args), - .result = NULL, - .timeout = NVME_DEFAULT_IOCTL_TIMEOUT, - .nsid = 0xffffffff, - .mset = NVME_FORMAT_MSET_SEPARATE /* 0 */, - .pi = NVME_FORMAT_PI_DISABLE /* 0 */, - .pil = NVME_FORMAT_PIL_LAST /* 0 */, - .ses = NVME_FORMAT_SES_NONE, - }; + struct libnvme_global_ctx *ctx; + struct libnvme_transport_handle *hdl; + struct libnvme_passthru_cmd cmd; + __u32 nsid = NVME_NSID_ALL; + __u8 lbaf; + enum nvme_cmd_format_ses ses; /* open the block device */ - args.fd = _open_dev (device, error); - if (args.fd < 0) + hdl = _open_dev (device, &ctx, error); + if (!hdl) return FALSE; - ret = nvme_get_nsid (args.fd, &args.nsid); - if (ret < 0 && errno == ENOTTY) { + ret = libnvme_get_nsid (hdl, &nsid); + if (ret == -ENOTTY) { /* not a block device, assuming controller character device */ - args.nsid = 0xffffffff; + nsid = NVME_NSID_ALL; ctrl_device = TRUE; } else if (ret != 0) { _nvme_status_to_error (ret, FALSE, error); g_prefix_error (error, "Error getting Namespace Identifier (NSID): "); - close (args.fd); + _close_dev (ctx, hdl); return FALSE; } @@ -217,14 +213,14 @@ gboolean bd_nvme_format (const gchar *device, guint16 lba_data_size, guint16 met if (! ctrl_device) { ctrl_id = _nvme_alloc (sizeof (struct nvme_id_ctrl), error); if (!ctrl_id) { - close (args.fd); + _close_dev (ctx, hdl); return FALSE; } - ret = nvme_identify_ctrl (args.fd, ctrl_id); + ret = _nvme_identify_ctrl (hdl, ctrl_id); if (ret != 0) { _nvme_status_to_error (ret, FALSE, error); g_prefix_error (error, "NVMe Identify Controller command error: "); - close (args.fd); + _close_dev (ctx, hdl); free (ctrl_id); return FALSE; } @@ -238,7 +234,7 @@ gboolean bd_nvme_format (const gchar *device, guint16 lba_data_size, guint16 met * should be called on a controller device instead */ g_set_error_literal (error, BD_NVME_ERROR, BD_NVME_ERROR_WOULD_FORMAT_ALL_NS, "The NVMe controller indicates it would format all namespaces."); - close (args.fd); + _close_dev (ctx, hdl); free (ctrl_id); return FALSE; } @@ -246,38 +242,44 @@ gboolean bd_nvme_format (const gchar *device, guint16 lba_data_size, guint16 met } /* find out the desired LBA data format index */ - args.lbaf = find_lbaf_for_size (args.fd, args.nsid, lba_data_size, metadata_size, error); - if (args.lbaf == 0xff) { - close (args.fd); + lbaf = find_lbaf_for_size (hdl, nsid, lba_data_size, metadata_size, error); + if (lbaf == 0xff) { + _close_dev (ctx, hdl); return FALSE; } switch (secure_erase) { case BD_NVME_FORMAT_SECURE_ERASE_USER_DATA: - args.ses = NVME_FORMAT_SES_USER_DATA_ERASE; + ses = NVME_FORMAT_SES_USER_DATA_ERASE; break; case BD_NVME_FORMAT_SECURE_ERASE_CRYPTO: - args.ses = NVME_FORMAT_SES_CRYPTO_ERASE; + ses = NVME_FORMAT_SES_CRYPTO_ERASE; break; case BD_NVME_FORMAT_SECURE_ERASE_NONE: default: - args.ses = NVME_FORMAT_SES_NONE; + ses = NVME_FORMAT_SES_NONE; } - ret = nvme_format_nvm (&args); + nvme_init_format_nvm (&cmd, nsid, lbaf, + NVME_FORMAT_MSET_SEPARATE /* 0 */, + NVME_FORMAT_PI_DISABLE /* 0 */, + NVME_FORMAT_PIL_LAST /* 0 */, + ses); + ret = libnvme_exec_admin_passthru (hdl, &cmd); if (ret != 0) { _nvme_status_to_error (ret, FALSE, error); g_prefix_error (error, "Format NVM command error: "); - close (args.fd); + _close_dev (ctx, hdl); return FALSE; } /* rescan the namespaces if block size has changed */ if (ctrl_device) { - if (ioctl (args.fd, NVME_IOCTL_RESCAN) < 0) { + ret = libnvme_rescan_ns (hdl); + if (ret < 0) { g_set_error (error, BD_NVME_ERROR, BD_NVME_ERROR_FAILED, - "Failed to rescan namespaces after format: %s", strerror_l (errno, _C_LOCALE)); - close (args.fd); + "Failed to rescan namespaces after format: %s", strerror_l (-ret, _C_LOCALE)); + _close_dev (ctx, hdl); return FALSE; } } else { @@ -289,23 +291,25 @@ gboolean bd_nvme_format (const gchar *device, guint16 lba_data_size, guint16 met */ int block_size = lba_data_size; - if (ioctl (args.fd, BLKBSZSET, &block_size) < 0) { + ret = libnvme_update_block_size (hdl, block_size); + if (ret < 0) { g_set_error (error, BD_NVME_ERROR, BD_NVME_ERROR_FAILED, - "Failed to set block size to %d after format: %s", block_size, strerror_l (errno, _C_LOCALE)); - close (args.fd); + "Failed to set block size to %d after format: %s", block_size, strerror_l (-ret, _C_LOCALE)); + _close_dev (ctx, hdl); return FALSE; } - if (ioctl (args.fd, BLKRRPART) < 0) { + ret = libnvme_reread_partitions (hdl); + if (ret < 0) { g_set_error (error, BD_NVME_ERROR, BD_NVME_ERROR_FAILED, - "Failed to re-read partition table after format: %s", strerror_l (errno, _C_LOCALE)); - close (args.fd); + "Failed to re-read partition table after format: %s", strerror_l (-ret, _C_LOCALE)); + _close_dev (ctx, hdl); return FALSE; } } } - close (args.fd); + _close_dev (ctx, hdl); return TRUE; } @@ -355,29 +359,23 @@ gboolean bd_nvme_format (const gchar *device, guint16 lba_data_size, guint16 met */ gboolean bd_nvme_sanitize (const gchar *device, BDNVMESanitizeAction action, gboolean no_dealloc, gint overwrite_pass_count, guint32 overwrite_pattern, gboolean overwrite_invert_pattern, GError **error) { int ret; - struct nvme_sanitize_nvm_args args = { - .args_size = sizeof(args), - .result = NULL, - .timeout = NVME_DEFAULT_IOCTL_TIMEOUT, - .ause = TRUE, - .owpass = overwrite_pass_count, - .oipbp = overwrite_invert_pattern, - .nodas = no_dealloc, - .ovrpat = GUINT32_TO_LE (overwrite_pattern), - }; + struct libnvme_global_ctx *ctx; + struct libnvme_transport_handle *hdl; + struct libnvme_passthru_cmd cmd; + enum nvme_sanitize_sanact sanact; switch (action) { case BD_NVME_SANITIZE_ACTION_EXIT_FAILURE: - args.sanact = NVME_SANITIZE_SANACT_EXIT_FAILURE; + sanact = NVME_SANITIZE_SANACT_EXIT_FAILURE; break; case BD_NVME_SANITIZE_ACTION_BLOCK_ERASE: - args.sanact = NVME_SANITIZE_SANACT_START_BLOCK_ERASE; + sanact = NVME_SANITIZE_SANACT_START_BLOCK_ERASE; break; case BD_NVME_SANITIZE_ACTION_OVERWRITE: - args.sanact = NVME_SANITIZE_SANACT_START_OVERWRITE; + sanact = NVME_SANITIZE_SANACT_START_OVERWRITE; break; case BD_NVME_SANITIZE_ACTION_CRYPTO_ERASE: - args.sanact = NVME_SANITIZE_SANACT_START_CRYPTO_ERASE; + sanact = NVME_SANITIZE_SANACT_START_CRYPTO_ERASE; break; default: g_set_error (error, BD_NVME_ERROR, BD_NVME_ERROR_INVALID_ARGUMENT, @@ -386,18 +384,26 @@ gboolean bd_nvme_sanitize (const gchar *device, BDNVMESanitizeAction action, gbo } /* open the block device */ - args.fd = _open_dev (device, error); - if (args.fd < 0) + hdl = _open_dev (device, &ctx, error); + if (!hdl) return FALSE; - ret = nvme_sanitize_nvm (&args); + nvme_init_sanitize_nvm (&cmd, sanact, + TRUE /* ause */, + overwrite_pass_count, + overwrite_invert_pattern, + no_dealloc, + FALSE /* emvs */, + FALSE /* preq */, + GUINT32_TO_LE (overwrite_pattern)); + ret = libnvme_exec_admin_passthru (hdl, &cmd); if (ret != 0) { _nvme_status_to_error (ret, FALSE, error); g_prefix_error (error, "Sanitize command error: "); - close (args.fd); + _close_dev (ctx, hdl); return FALSE; } - close (args.fd); + _close_dev (ctx, hdl); return TRUE; } diff --git a/src/plugins/nvme/nvme-private.h b/src/plugins/nvme/nvme-private.h index 01656d80c..6fd5d3352 100644 --- a/src/plugins/nvme/nvme-private.h +++ b/src/plugins/nvme/nvme-private.h @@ -1,6 +1,7 @@ #include #include #include +#include #ifndef BD_NVME_PRIVATE #define BD_NVME_PRIVATE @@ -19,11 +20,17 @@ G_GNUC_INTERNAL void _nvme_status_to_error (gint status, gboolean fabrics, GError **error); G_GNUC_INTERNAL -void _nvme_fabrics_errno_to_gerror (int result, int _errno, GError **error); +void _nvme_fabrics_errno_to_gerror (int result, GError **error); /* nvme-info.c */ G_GNUC_INTERNAL -gint _open_dev (const gchar *device, GError **error); +struct libnvme_transport_handle *_open_dev (const gchar *device, struct libnvme_global_ctx **ctx, GError **error); +G_GNUC_INTERNAL +void _close_dev (struct libnvme_global_ctx *ctx, struct libnvme_transport_handle *hdl); +G_GNUC_INTERNAL +int _nvme_identify_ctrl (struct libnvme_transport_handle *hdl, struct nvme_id_ctrl *ctrl_id); +G_GNUC_INTERNAL +int _nvme_identify_ns (struct libnvme_transport_handle *hdl, __u32 nsid, struct nvme_id_ns *ns_info); G_GNUC_INTERNAL void *_nvme_alloc (size_t len, GError **error); diff --git a/tests/nvme_test.py b/tests/nvme_test.py index a785ec822..01d0261ea 100644 --- a/tests/nvme_test.py +++ b/tests/nvme_test.py @@ -381,8 +381,13 @@ def test_connect_single_ns(self): self.assertTrue(re.match(r'/dev/nvme[0-9]+n[0-9]+', ns)) self.assertTrue(os.path.exists(ns)) + # an identical connection is refused unless explicitly requested + with self.assertRaisesRegex(GLib.GError, r"Error connecting the controller: already connected"): + BlockDev.nvme_connect(self.SUBNQN, 'loop', None, None, None, None, None, None) + # make a duplicate connection - ret = BlockDev.nvme_connect(self.SUBNQN, 'loop', None, None, None, None, None, None) + ret = BlockDev.nvme_connect(self.SUBNQN, 'loop', None, None, None, None, None, None, + [BlockDev.ExtraArg.new("duplicate_connect", "True")]) self.assertTrue(ret) # should see two controllers now diff --git a/tests/utils.py b/tests/utils.py index 847415fca..14d1e052f 100644 --- a/tests/utils.py +++ b/tests/utils.py @@ -10,7 +10,6 @@ import unittest import time import sys -import json import uuid from contextlib import contextmanager from enum import Enum @@ -313,95 +312,73 @@ def find_nvme_ctrl_devs_for_subnqn(subnqn, wait_for_ready=True): :param bool wait_for_ready: whether to wait for controllers to be ready (default: True) """ - def _check_subsys(subsys, dev_paths): - if subsys['SubsystemNQN'] == subnqn: - for ctrl in subsys['Controllers']: - if ctrl['Transport'] != 'loop': - continue - path = os.path.join('/dev/', ctrl['Controller']) - try: - st = os.lstat(path) - # nvme controller node is a character device - if stat.S_ISCHR(st.st_mode): - dev_paths += [path] - except: - pass - # Wait for controllers to be ready if requested if wait_for_ready: _wait_for_nvme_controllers_ready(subnqn) - ret, out, err = run_command("nvme list --output-format=json --verbose") - if ret != 0: - raise RuntimeError("Error getting NVMe list: '%s %s'" % (out, err)) - - decoder = json.JSONDecoder() - decoded = decoder.decode(out) - if not decoded or 'Devices' not in decoded: - return [] + # read sysfs directly, the 'nvme list' JSON output differs between nvme-cli versions dev_paths = [] - for dev in decoded['Devices']: - # nvme-cli 2.x - if 'Subsystems' in dev: - for subsys in dev['Subsystems']: - _check_subsys(subsys, dev_paths) - # nvme-cli 1.x - if 'SubsystemNQN' in dev: - _check_subsys(dev, dev_paths) + for ctrl_path in sorted(glob.glob("/sys/class/nvme/nvme*")): + try: + if read_file(os.path.join(ctrl_path, "subsysnqn")).strip() != subnqn: + continue + if read_file(os.path.join(ctrl_path, "transport")).strip() != "loop": + continue + path = os.path.join('/dev/', os.path.basename(ctrl_path)) + # nvme controller node is a character device + if stat.S_ISCHR(os.lstat(path).st_mode): + dev_paths += [path] + except: + pass return dev_paths -def find_nvme_ns_devs_for_subnqn(subnqn, wait_for_ready=True): +def find_nvme_ns_devs_for_subnqn(subnqn, wait_for_ready=True, num_expected=None, timeout=5): """ Find NVMe namespace block devices for the specified subsystem nqn :param str subnqn: subsystem nqn :param bool wait_for_ready: whether to wait for controllers to be ready (default: True) + :param int num_expected: wait until this many namespaces show up (default: don't wait) + :param int timeout: timeout in seconds when waiting for @num_expected namespaces (default: 5) """ - def _check_namespaces(node, ns_dev_paths): - for ns in node['Namespaces']: - path = os.path.join('/dev/', ns['NameSpace']) - try: - st = os.lstat(path) - if stat.S_ISBLK(st.st_mode): - ns_dev_paths += [path] - except: - pass - - def _check_subsys(subsys, ns_dev_paths): - if subsys['SubsystemNQN'] == subnqn: - if 'Namespaces' in subsys: - _check_namespaces(subsys, ns_dev_paths) - # kernel 4.18 - if 'Controllers' in subsys: - for ctrl in subsys['Controllers']: - if 'Namespaces' in ctrl: - _check_namespaces(ctrl, ns_dev_paths) - if wait_for_ready: _wait_for_nvme_controllers_ready(subnqn) - ret, out, err = run_command("nvme list --output-format=json --verbose") - if ret != 0: - raise RuntimeError("Error getting NVMe list: '%s %s'" % (out, err)) - decoder = json.JSONDecoder() - decoded = decoder.decode(out) - if not decoded or 'Devices' not in decoded: - return [] + # the kernel scans namespaces asynchronously after the controller went live + start_time = time.time() + ns_dev_paths = _find_nvme_ns_devs_for_subnqn(subnqn) + while num_expected is not None and len(ns_dev_paths) < num_expected and time.time() - start_time < timeout: + time.sleep(0.5) + os.system("udevadm settle") + ns_dev_paths = _find_nvme_ns_devs_for_subnqn(subnqn) + + return ns_dev_paths + +def _find_nvme_ns_devs_for_subnqn(subnqn): + # namespaces are listed under the subsystem with native NVMe multipath + # and under the controllers otherwise + parents = glob.glob("/sys/class/nvme-subsystem/nvme-subsys*") + glob.glob("/sys/class/nvme/nvme*") ns_dev_paths = [] - for dev in decoded['Devices']: - # nvme-cli 2.x - if 'Subsystems' in dev: - for subsys in dev['Subsystems']: - _check_subsys(subsys, ns_dev_paths) - # nvme-cli 1.x - if 'SubsystemNQN' in dev: - _check_subsys(dev, ns_dev_paths) + for parent in parents: + try: + if read_file(os.path.join(parent, "subsysnqn")).strip() != subnqn: + continue + except: + continue + for ns_path in glob.glob(os.path.join(parent, "nvme*n*")): + path = os.path.join('/dev/', os.path.basename(ns_path)) + try: + # skips the hidden per-path nvmeXcYnZ nodes + if stat.S_ISBLK(os.lstat(path).st_mode) and path not in ns_dev_paths: + ns_dev_paths += [path] + except: + pass - return ns_dev_paths + return sorted(ns_dev_paths) def get_nvme_hostnqn(): From 5dd10a2bab6228c000c82b0481500c208d25d5c3 Mon Sep 17 00:00:00 2001 From: Daniel Wagner Date: Sun, 11 Oct 2026 16:32:51 +0000 Subject: [PATCH 2/4] nvme: Support the libnvme3 fabrics configuration file libnvme3 replaced the JSON configuration with an INI based /etc/nvme/nvme-fabrics.conf. Parameters of the first connection entry matching the bd_nvme_connect() arguments are applied, extra arguments still take precedence. Legacy JSON configuration files are not supported anymore. Co-Authored-By: Claude Opus 5.5 --- src/lib/plugin_apis/nvme.api | 18 +++- src/plugins/nvme/nvme-fabrics.c | 140 ++++++++++++++++++++++++++++++-- 2 files changed, 149 insertions(+), 9 deletions(-) diff --git a/src/lib/plugin_apis/nvme.api b/src/lib/plugin_apis/nvme.api index 8ad4fb8d6..3cfc2f5d9 100644 --- a/src/lib/plugin_apis/nvme.api +++ b/src/lib/plugin_apis/nvme.api @@ -1291,12 +1291,15 @@ gboolean bd_nvme_set_host_id (const gchar *host_id, GError **error); * - `"loop"`: A NVMe over Fabrics target on the local host. * * In addition to the primary options it's possible to supply @extra arguments: - * - `"config"`: Ignored, configuration files are not read anymore (see below). + * - `"config"`: Use the specified configuration file instead of the default file (see below) or + * specify `"none"` to avoid reading any configuration file. * - `"kxchap_secret"`: NVMe In-band authentication (KX-HMAC-CHAP, formerly DH-HMAC-CHAP) host secret * in ASCII format as described in the NVMe specification. When not specified, + * the secret is taken from the configuration file, if present. Otherwise * no in-band authentication is attempted. * - `"kxchap_ctrl_secret"`: NVMe In-band authentication controller secret for bi-directional authentication. - * When not specified, no bi-directional authentication is attempted. + * When not specified, the secret is taken from the configuration file, if present. + * Otherwise no bi-directional authentication is attempted. * - `"dhchap_key"`, `"dhchap_ctrl_key"`: Deprecated aliases for `"kxchap_secret"` and `"kxchap_ctrl_secret"`. * - `"nr_io_queues"`: The number of I/O queues. * - `"nr_write_queues"`: Number of additional queues that will be used for write I/O. @@ -1319,8 +1322,15 @@ gboolean bd_nvme_set_host_id (const gchar *host_id, GError **error); * Boolean values can be expressed by "0"/"1", "on"/"off" or "True"/"False" case-insensitive * strings. Failed numerical or boolean string conversions will result in the option being ignored. * - * The JSON configuration file `/etc/nvme/config.json` is not supported by libnvme3 anymore - * and is not read. + * By default additional options are read from the default configuration file `/etc/nvme/nvme-fabrics.conf` + * (and its `/etc/nvme/nvme-fabrics.conf.d/` drop-in directory). Parameters of the first connection + * entry matching the @subsysnqn, @transport, @transport_addr, @transport_svcid, @host_traddr + * and @host_iface arguments are applied, along with its host identity when @host_nqn or @host_id + * are not specified. Use the @extra `"config"` argument to either specify a different config file + * or disable use of it. The INI configuration file format is documented + * in [https://github.com/linux-nvme/nvme-cli/blob/master/libnvme/design/CONFIG.md](https://github.com/linux-nvme/nvme-cli/blob/master/libnvme/design/CONFIG.md), + * legacy JSON configuration files are not supported anymore. + * Any @extra option generally overrides particular option specified in a configuration file. * * Returns: %TRUE if the subsystem was connected successfully, %FALSE otherwise with @error set. * diff --git a/src/plugins/nvme/nvme-fabrics.c b/src/plugins/nvme/nvme-fabrics.c index 0f9a40ea3..ae8c09af3 100644 --- a/src/plugins/nvme/nvme-fabrics.c +++ b/src/plugins/nvme/nvme-fabrics.c @@ -37,6 +37,29 @@ #include "nvme-private.h" +static const gchar *get_extra_config_file (const BDExtraArg **extra, gboolean *use_config) { + const BDExtraArg **extra_i; + const gchar *config_file = NULL; + + /* NULL means the libnvme default configuration file */ + *use_config = TRUE; + if (!extra) + return NULL; + + for (extra_i = extra; *extra_i; extra_i++) + if (g_strcmp0 ((*extra_i)->opt, "config") == 0) { + if (g_ascii_strcasecmp ((*extra_i)->val, "none") == 0) { + *use_config = FALSE; + config_file = NULL; + } else { + *use_config = TRUE; + config_file = (*extra_i)->val; + } + } + + return config_file; +} + static void parse_extra_args (const BDExtraArg **extra, struct libnvmf_context *fctx, const gchar **hostkey, const gchar **ctrlkey, const gchar **hostsymname, const gchar **keyring, const gchar **tls_key) { const BDExtraArg **extra_i; @@ -128,6 +151,54 @@ static void parse_extra_args (const BDExtraArg **extra, struct libnvmf_context * #undef SAFE_BOOL_CONV } +struct config_match_data { + const gchar *subsysnqn; + const gchar *transport; + const gchar *transport_addr; + const gchar *transport_svcid; + const gchar *host_traddr; + const gchar *host_iface; + const gchar *host_nqn; + const struct libnvmf_config_conn *conn; +}; + +static void config_match_conn (const struct libnvmf_config_conn *conn, void *user_data) { + struct config_match_data *data = user_data; + const gchar *val; + + /* first match wins */ + if (data->conn) + return; + + if (g_strcmp0 (libnvmf_config_conn_get_subsysnqn (conn), data->subsysnqn) != 0 || + g_strcmp0 (libnvmf_config_conn_get_transport (conn), data->transport) != 0 || + g_strcmp0 (libnvmf_config_conn_get_traddr (conn), data->transport_addr) != 0 || + g_strcmp0 (libnvmf_config_conn_get_host_traddr (conn), data->host_traddr) != 0 || + g_strcmp0 (libnvmf_config_conn_get_host_iface (conn), data->host_iface) != 0) + return; + + /* unset values in the config file match anything */ + val = libnvmf_config_conn_get_trsvcid (conn); + if (val && data->transport_svcid && g_strcmp0 (val, data->transport_svcid) != 0) + return; + val = libnvmf_config_conn_get_hostnqn (conn); + if (val && data->host_nqn && g_strcmp0 (val, data->host_nqn) != 0) + return; + + data->conn = conn; +} + +static const gchar *get_param_nonempty (const struct libnvmf_config_conn *conn, const gchar *key) { + const gchar *val; + + if (!conn) + return NULL; + val = libnvmf_params_get (libnvmf_config_conn_get_params (conn), key); + if (val && *val == '\0') + return NULL; + return val; +} + /** * bd_nvme_connect: @@ -153,12 +224,15 @@ static void parse_extra_args (const BDExtraArg **extra, struct libnvmf_context * * - `"loop"`: A NVMe over Fabrics target on the local host. * * In addition to the primary options it's possible to supply @extra arguments: - * - `"config"`: Ignored, configuration files are not read anymore (see below). + * - `"config"`: Use the specified configuration file instead of the default file (see below) or + * specify `"none"` to avoid reading any configuration file. * - `"kxchap_secret"`: NVMe In-band authentication (KX-HMAC-CHAP, formerly DH-HMAC-CHAP) host secret * in ASCII format as described in the NVMe specification. When not specified, + * the secret is taken from the configuration file, if present. Otherwise * no in-band authentication is attempted. * - `"kxchap_ctrl_secret"`: NVMe In-band authentication controller secret for bi-directional authentication. - * When not specified, no bi-directional authentication is attempted. + * When not specified, the secret is taken from the configuration file, if present. + * Otherwise no bi-directional authentication is attempted. * - `"dhchap_key"`, `"dhchap_ctrl_key"`: Deprecated aliases for `"kxchap_secret"` and `"kxchap_ctrl_secret"`. * - `"nr_io_queues"`: The number of I/O queues. * - `"nr_write_queues"`: Number of additional queues that will be used for write I/O. @@ -181,8 +255,15 @@ static void parse_extra_args (const BDExtraArg **extra, struct libnvmf_context * * Boolean values can be expressed by "0"/"1", "on"/"off" or "True"/"False" case-insensitive * strings. Failed numerical or boolean string conversions will result in the option being ignored. * - * The JSON configuration file `/etc/nvme/config.json` is not supported by libnvme3 anymore - * and is not read. + * By default additional options are read from the default configuration file `/etc/nvme/nvme-fabrics.conf` + * (and its `/etc/nvme/nvme-fabrics.conf.d/` drop-in directory). Parameters of the first connection + * entry matching the @subsysnqn, @transport, @transport_addr, @transport_svcid, @host_traddr + * and @host_iface arguments are applied, along with its host identity when @host_nqn or @host_id + * are not specified. Use the @extra `"config"` argument to either specify a different config file + * or disable use of it. The INI configuration file format is documented + * in [https://github.com/linux-nvme/nvme-cli/blob/master/libnvme/design/CONFIG.md](https://github.com/linux-nvme/nvme-cli/blob/master/libnvme/design/CONFIG.md), + * legacy JSON configuration files are not supported anymore. + * Any @extra option generally overrides particular option specified in a configuration file. * * Returns: %TRUE if the subsystem was connected successfully, %FALSE otherwise with @error set. * @@ -190,6 +271,8 @@ static void parse_extra_args (const BDExtraArg **extra, struct libnvmf_context * */ gboolean bd_nvme_connect (const gchar *subsysnqn, const gchar *transport, const gchar *transport_addr, const gchar *transport_svcid, const gchar *host_traddr, const gchar *host_iface, const gchar *host_nqn, const gchar *host_id, const BDExtraArg **extra, GError **error) { int ret; + const gchar *config_file; + gboolean use_config; gchar *host_nqn_val = NULL; gchar *host_id_val = NULL; const gchar *hostkey = NULL; @@ -200,6 +283,8 @@ gboolean bd_nvme_connect (const gchar *subsysnqn, const gchar *transport, const struct libnvme_global_ctx *ctx; struct libnvme_host *host; struct libnvmf_context *fctx = NULL; + struct libnvmf_config *config = NULL; + struct config_match_data match = ZERO_INIT; gboolean success = FALSE; if (subsysnqn == NULL) { @@ -237,6 +322,42 @@ gboolean bd_nvme_connect (const gchar *subsysnqn, const gchar *transport, const return FALSE; } + /* look up the connection in the configuration file */ + config_file = get_extra_config_file (extra, &use_config); + if (use_config) { + ret = libnvmf_config_read (ctx, config_file, &config); + if (ret != 0) { + if (config_file) { + g_set_error (error, BD_NVME_ERROR, BD_NVME_ERROR_INVALID_ARGUMENT, + "Failed to read configuration file '%s': %s", + config_file, libnvme_strerror (-ret)); + goto out; + } + /* not fatal for the default configuration file */ + bd_utils_log_format (BD_UTILS_LOG_WARNING, + "Failed to read the default NVMe-oF configuration file: %s", + libnvme_strerror (-ret)); + config = NULL; + } + } + if (config) { + match.subsysnqn = subsysnqn; + match.transport = transport; + match.transport_addr = transport_addr; + match.transport_svcid = transport_svcid; + match.host_traddr = host_traddr; + match.host_iface = host_iface; + match.host_nqn = host_nqn; + libnvmf_config_conn_for_each (config, config_match_conn, &match); + if (match.conn) { + if (!host_nqn) + host_nqn = libnvmf_config_conn_get_hostnqn (match.conn); + if (!host_id) + host_id = libnvmf_config_conn_get_hostid (match.conn); + hostsymname = libnvmf_config_conn_get_hostsymname (match.conn); + } + } + /* HostNQN and HostID, read from /etc/nvme/ or generated when not specified */ ret = libnvmf_host_get_ids (ctx, host_nqn, host_id, &host_nqn_val, &host_id_val); if (ret != 0 || host_nqn_val == NULL || host_id_val == NULL) { @@ -261,15 +382,23 @@ gboolean bd_nvme_connect (const gchar *subsysnqn, const gchar *transport, const ret = libnvmf_context_set_connection (fctx, subsysnqn, transport, transport_addr, transport_svcid, host_traddr, host_iface); if (ret == 0) ret = libnvmf_context_set_hostnqn (fctx, host_nqn_val, host_id_val); + if (ret == 0 && match.conn) + ret = libnvmf_context_apply_params (fctx, libnvmf_config_conn_get_params (match.conn)); if (ret != 0) { _nvme_fabrics_errno_to_gerror (ret, error); g_prefix_error (error, "Error creating the controller: "); goto out; } + /* any extra argument overrides values from the configuration file */ parse_extra_args (extra, fctx, &hostkey, &ctrlkey, &hostsymname, &keyring, &tls_key); if (hostkey || ctrlkey || keyring || tls_key) { - ret = libnvmf_context_set_crypto (fctx, hostkey, ctrlkey, keyring, tls_key, NULL); + ret = libnvmf_context_set_crypto (fctx, + hostkey ? hostkey : get_param_nonempty (match.conn, "kxchap-secret"), + ctrlkey ? ctrlkey : get_param_nonempty (match.conn, "kxchap-ctrl-secret"), + keyring ? keyring : get_param_nonempty (match.conn, "keyring"), + tls_key /* NULL keeps the current key */, + get_param_nonempty (match.conn, "tls-key-identity")); if (ret != 0) { _nvme_fabrics_errno_to_gerror (ret, error); g_prefix_error (error, "Error setting the controller keys: "); @@ -299,6 +428,7 @@ gboolean bd_nvme_connect (const gchar *subsysnqn, const gchar *transport, const out: libnvmf_context_free (fctx); + libnvmf_config_free (config); libnvme_free_global_ctx (ctx); free (host_nqn_val); free (host_id_val); From ed4073a517fddfd3fee8b3a184658aeef422a4dc Mon Sep 17 00:00:00 2001 From: Daniel Wagner Date: Sun, 11 Oct 2026 16:32:56 +0000 Subject: [PATCH 3/4] nvme: Fix byte order of the sanitize overwrite pattern Command dwords of passthru commands are passed in CPU byte order, the kernel takes care of the conversion. Converting the pattern to little endian beforehand swapped it twice on big endian architectures. Co-Authored-By: Claude Opus 5.5 --- src/plugins/nvme/nvme-op.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/plugins/nvme/nvme-op.c b/src/plugins/nvme/nvme-op.c index 835b63b83..161826b31 100644 --- a/src/plugins/nvme/nvme-op.c +++ b/src/plugins/nvme/nvme-op.c @@ -395,7 +395,7 @@ gboolean bd_nvme_sanitize (const gchar *device, BDNVMESanitizeAction action, gbo no_dealloc, FALSE /* emvs */, FALSE /* preq */, - GUINT32_TO_LE (overwrite_pattern)); + overwrite_pattern); ret = libnvme_exec_admin_passthru (hdl, &cmd); if (ret != 0) { _nvme_status_to_error (ret, FALSE, error); From a84c8691b681a594abef6a564b3104c49aa4ef0d Mon Sep 17 00:00:00 2001 From: Daniel Wagner Date: Sun, 11 Oct 2026 16:43:54 +0000 Subject: [PATCH 4/4] nvme: Only disconnect controllers owned by libblockdev libnvme3 keeps track of the owner of each connected NVMe-oF controller, e.g. boot volumes connected from the NBFT are owned by "nbft". Previously bd_nvme_disconnect() removed all matching controllers, including the ones other components depend on. Controllers connected by bd_nvme_connect() are now registered as owned by "libblockdev" (or by the new "owner" extra argument) and bd_nvme_disconnect() and bd_nvme_disconnect_by_path() only remove controllers owned by libblockdev. An already connected controller is never claimed. The new bd_nvme_disconnect_full() function allows to disconnect controllers of a different owner or, with BD_NVME_DISCONNECT_FORCE, regardless of their owner. Co-Authored-By: Claude Opus 5.5 --- docs/libblockdev-sections.txt | 2 + src/lib/plugin_apis/nvme.api | 56 ++++++++ src/plugins/nvme/nvme-fabrics.c | 244 +++++++++++++++++++++++--------- src/plugins/nvme/nvme.h | 17 +++ tests/nvme_test.py | 147 ++++++++++++++++++- 5 files changed, 401 insertions(+), 65 deletions(-) diff --git a/docs/libblockdev-sections.txt b/docs/libblockdev-sections.txt index 4a8f538d9..2afaf5d31 100644 --- a/docs/libblockdev-sections.txt +++ b/docs/libblockdev-sections.txt @@ -835,6 +835,7 @@ bd_nvme_get_sanitize_log bd_nvme_sanitize_log_free bd_nvme_sanitize_log_copy BDNVMESanitizeAction +BDNVMEDisconnectFlags bd_nvme_sanitize bd_nvme_get_host_nqn bd_nvme_get_host_id @@ -844,6 +845,7 @@ bd_nvme_set_host_id bd_nvme_connect bd_nvme_disconnect bd_nvme_disconnect_by_path +bd_nvme_disconnect_full bd_nvme_find_ctrls_for_ns bd_nvme_find_namespaces_for_ctrl diff --git a/src/lib/plugin_apis/nvme.api b/src/lib/plugin_apis/nvme.api index 3cfc2f5d9..a6f6c2514 100644 --- a/src/lib/plugin_apis/nvme.api +++ b/src/lib/plugin_apis/nvme.api @@ -32,6 +32,7 @@ GQuark bd_nvme_error_quark (void) { * @BD_NVME_ERROR_CONNECT_NODEV: Invalid interface. * @BD_NVME_ERROR_CONNECT_OPNOTSUPP: Operation not supported. * @BD_NVME_ERROR_CONNECT_REFUSED: Connection refused. + * @BD_NVME_ERROR_OWNED: The controller is not owned by the caller (see bd_nvme_disconnect()). */ /* BpG-skip-end */ typedef enum { @@ -53,6 +54,7 @@ typedef enum { BD_NVME_ERROR_CONNECT_NODEV, BD_NVME_ERROR_CONNECT_OPNOTSUPP, BD_NVME_ERROR_CONNECT_REFUSED, + BD_NVME_ERROR_OWNED, } BDNVMEError; typedef enum { @@ -1004,6 +1006,18 @@ typedef enum { BD_NVME_SANITIZE_ACTION_CRYPTO_ERASE = 3, } BDNVMESanitizeAction; +/* BpG-skip */ +/** + * BDNVMEDisconnectFlags: + * @BD_NVME_DISCONNECT_NONE: No flags. + * @BD_NVME_DISCONNECT_FORCE: Disconnect controllers regardless of their owner in the libnvme controller ownership registry. + */ +/* BpG-skip-end */ +typedef enum { + BD_NVME_DISCONNECT_NONE = 0, + BD_NVME_DISCONNECT_FORCE = 1 << 0, +} BDNVMEDisconnectFlags; + /** * bd_nvme_get_controller_info: @@ -1316,6 +1330,10 @@ gboolean bd_nvme_set_host_id (const gchar *host_id, GError **error); * - `"data_digest"`: Generates/verifies data digest (TCP). Boolean value. * - `"tls"`: Enable TLS encryption (TCP). Boolean value. * - `"hostsymname"`: TP8010: NVMe host symbolic name. + * - `"owner"`: Register a newly created controller with this owner in the libnvme controller ownership + * registry (`/run/nvme/registry/`) instead of the default `"libblockdev"`, e.g. `"udisks"`. + * An empty string leaves the controller without an owner. An already connected controller + * keeps its owner. See bd_nvme_disconnect() and bd_nvme_disconnect_full(). * - `"keyring"`: Keyring to store and lookup keys. String value. * - `"tls_key"`: TLS PSK for the connection. String value. * @@ -1347,8 +1365,16 @@ gboolean bd_nvme_connect (const gchar *subsysnqn, const gchar *transport, const * This may disconnect multiple controllers with matching @subsysnqn and %TRUE * is only returned when all controllers were disconnected successfully. * + * Only controllers owned by libblockdev according to the libnvme controller ownership + * registry are disconnected, i.e. the ones connected by bd_nvme_connect() without + * a specific `"owner"`. Controllers connected by other components, e.g. boot volumes + * connected via NBFT or connections managed by a discovery daemon, are left untouched. + * See bd_nvme_disconnect_full() to disconnect controllers of a different owner. + * * Returns: %TRUE if all matching controllers were disconnected successfully, %FALSE with @error * set in case of a disconnect error or when no matching controllers were found. + * The #BD_NVME_ERROR_OWNED error is returned when none of the matching controllers + * are owned by libblockdev. * * Tech category: %BD_NVME_TECH_FABRICS-%BD_NVME_TECH_MODE_INITIATOR */ @@ -1362,6 +1388,9 @@ gboolean bd_nvme_disconnect (const gchar *subsysnqn, GError **error); * Disconnects and removes a NVMe over Fabrics controller represented * by a block device path. * + * Only a controller owned by libblockdev according to the libnvme controller ownership + * registry is disconnected, see bd_nvme_disconnect() and bd_nvme_disconnect_full(). + * * Returns: %TRUE if the controller was disconnected successfully, * %FALSE otherwise with @error set. * @@ -1369,6 +1398,33 @@ gboolean bd_nvme_disconnect (const gchar *subsysnqn, GError **error); */ gboolean bd_nvme_disconnect_by_path (const gchar *path, GError **error); +/** + * bd_nvme_disconnect_full: + * @subsysnqn: (nullable): The name of the NVMe subsystem to disconnect. + * @path: (nullable): NVMe controller device to disconnect (e.g. `/dev/nvme0`). + * @owner: (nullable): The owner of the controllers to disconnect or %NULL for libblockdev. + * @flags: Flags to control the disconnect. + * @error: (out) (nullable): Place to store error (if any). + * + * Disconnects and removes existing NVMe over Fabrics controllers matching @subsysnqn + * or the controller represented by @path. Exactly one of @subsysnqn and @path has to + * be specified. + * + * Only controllers registered to @owner in the libnvme controller ownership registry + * (`/run/nvme/registry/`) are disconnected, see the `"owner"` argument of bd_nvme_connect(). + * With the #BD_NVME_DISCONNECT_FORCE flag all matching controllers are disconnected + * regardless of their owner, including controllers without any owner. Use with care, + * this may disconnect devices other components depend on, e.g. the root filesystem. + * + * Returns: %TRUE if all matching controllers were disconnected successfully, %FALSE with @error + * set in case of a disconnect error or when no matching controllers were found. + * The #BD_NVME_ERROR_OWNED error is returned when none of the matching controllers + * are owned by @owner. + * + * Tech category: %BD_NVME_TECH_FABRICS-%BD_NVME_TECH_MODE_INITIATOR + */ +gboolean bd_nvme_disconnect_full (const gchar *subsysnqn, const gchar *path, const gchar *owner, BDNVMEDisconnectFlags flags, GError **error); + /** * bd_nvme_find_ctrls_for_ns: * @ns_sysfs_path: NVMe namespace sysfs path. diff --git a/src/plugins/nvme/nvme-fabrics.c b/src/plugins/nvme/nvme-fabrics.c index ae8c09af3..879c7cf41 100644 --- a/src/plugins/nvme/nvme-fabrics.c +++ b/src/plugins/nvme/nvme-fabrics.c @@ -60,7 +60,7 @@ static const gchar *get_extra_config_file (const BDExtraArg **extra, gboolean *u return config_file; } -static void parse_extra_args (const BDExtraArg **extra, struct libnvmf_context *fctx, const gchar **hostkey, const gchar **ctrlkey, const gchar **hostsymname, const gchar **keyring, const gchar **tls_key) { +static void parse_extra_args (const BDExtraArg **extra, struct libnvmf_context *fctx, const gchar **owner, const gchar **hostkey, const gchar **ctrlkey, const gchar **hostsymname, const gchar **keyring, const gchar **tls_key) { const BDExtraArg **extra_i; if (!extra) @@ -71,10 +71,13 @@ static void parse_extra_args (const BDExtraArg **extra, struct libnvmf_context * gchar *endptr = NULL; \ \ v = g_ascii_strtoll ((*extra_i)->val, &endptr, 0); \ - if (endptr != (*extra_i)->val) \ + if (fctx && endptr != (*extra_i)->val) \ setter (fctx, v); \ } #define SAFE_BOOL_CONV(setter) { \ + if (!fctx) \ + ; \ + else \ if (g_ascii_strcasecmp ((*extra_i)->val, "on") == 0 || \ g_ascii_strcasecmp ((*extra_i)->val, "1") == 0 || \ g_ascii_strcasecmp ((*extra_i)->val, "true") == 0) \ @@ -95,6 +98,9 @@ static void parse_extra_args (const BDExtraArg **extra, struct libnvmf_context * g_strcmp0 ((*extra_i)->opt, "dhchap_ctrl_key") == 0 /* deprecated */) && ctrlkey) *ctrlkey = (*extra_i)->val; else + if (g_strcmp0 ((*extra_i)->opt, "owner") == 0 && owner) + *owner = (*extra_i)->val; + else if (g_strcmp0 ((*extra_i)->opt, "hostsymname") == 0 && hostsymname) *hostsymname = (*extra_i)->val; else @@ -200,6 +206,30 @@ static const gchar *get_param_nonempty (const struct libnvmf_config_conn *conn, } +/* owner registered for new connections unless specified otherwise */ +#define DEFAULT_OWNER "libblockdev" + +struct connected_data { + struct libnvme_global_ctx *ctx; + const gchar *owner; +}; + +static void connected_hook (struct libnvmf_context *fctx G_GNUC_UNUSED, struct libnvme_ctrl *c, void *user_data) { + struct connected_data *data = user_data; + int ret; + + /* Only called for a controller that has just been created. Registering the owner + * here rather than via libnvme_set_owner() avoids claiming an already connected + * controller that might be owned by someone else. */ + if (*data->owner == '\0') + return; + + ret = libnvmf_registry_update (data->ctx, libnvme_ctrl_get_name (c), "owner", data->owner); + if (ret != 0) + bd_utils_log_format (BD_UTILS_LOG_WARNING, "Failed to register owner '%s' for %s: %s", + data->owner, libnvme_ctrl_get_name (c), libnvme_strerror (-ret)); +} + /** * bd_nvme_connect: * @subsysnqn: The name for the NVMe subsystem to connect to. @@ -249,6 +279,10 @@ static const gchar *get_param_nonempty (const struct libnvmf_config_conn *conn, * - `"data_digest"`: Generates/verifies data digest (TCP). Boolean value. * - `"tls"`: Enable TLS encryption (TCP). Boolean value. * - `"hostsymname"`: TP8010: NVMe host symbolic name. + * - `"owner"`: Register a newly created controller with this owner in the libnvme controller ownership + * registry (`/run/nvme/registry/`) instead of the default `"libblockdev"`, e.g. `"udisks"`. + * An empty string leaves the controller without an owner. An already connected controller + * keeps its owner. See bd_nvme_disconnect() and bd_nvme_disconnect_full(). * - `"keyring"`: Keyring to store and lookup keys. String value. * - `"tls_key"`: TLS PSK for the connection. String value. * @@ -280,6 +314,8 @@ gboolean bd_nvme_connect (const gchar *subsysnqn, const gchar *transport, const const gchar *hostsymname = NULL; const gchar *keyring = NULL; const gchar *tls_key = NULL; + const gchar *owner = NULL; + struct connected_data hook_data; struct libnvme_global_ctx *ctx; struct libnvme_host *host; struct libnvmf_context *fctx = NULL; @@ -377,7 +413,12 @@ gboolean bd_nvme_connect (const gchar *subsysnqn, const gchar *transport, const goto out; } - ret = libnvmf_context_create (ctx, NULL, NULL, NULL, NULL, &fctx); + /* the owner is needed in the hook, look it up early */ + parse_extra_args (extra, NULL, &owner, NULL, NULL, NULL, NULL, NULL); + hook_data.ctx = ctx; + hook_data.owner = owner ? owner : DEFAULT_OWNER; + + ret = libnvmf_context_create (ctx, NULL, connected_hook, NULL, &hook_data, &fctx); if (ret == 0) ret = libnvmf_context_set_connection (fctx, subsysnqn, transport, transport_addr, transport_svcid, host_traddr, host_iface); if (ret == 0) @@ -391,7 +432,7 @@ gboolean bd_nvme_connect (const gchar *subsysnqn, const gchar *transport, const } /* any extra argument overrides values from the configuration file */ - parse_extra_args (extra, fctx, &hostkey, &ctrlkey, &hostsymname, &keyring, &tls_key); + parse_extra_args (extra, fctx, NULL, &hostkey, &ctrlkey, &hostsymname, &keyring, &tls_key); if (hostkey || ctrlkey || keyring || tls_key) { ret = libnvmf_context_set_crypto (fctx, hostkey ? hostkey : get_param_nonempty (match.conn, "kxchap-secret"), @@ -436,46 +477,6 @@ gboolean bd_nvme_connect (const gchar *subsysnqn, const gchar *transport, const return success; } -static gboolean _disconnect (const gchar *subsysnqn, const gchar *path, GError **error, gboolean *found) { - struct libnvme_global_ctx *ctx; - struct libnvme_host *host; - struct libnvme_subsystem *subsys; - struct libnvme_ctrl *ctrl; - int ret; - - ctx = libnvme_create_global_ctx (); - if (ctx == NULL) { - g_set_error_literal (error, BD_NVME_ERROR, BD_NVME_ERROR_FAILED, - "Failed to create libnvme context"); - return FALSE; - } - ret = libnvme_scan_topology (ctx, NULL, NULL); - if (ret < 0) { - g_set_error (error, BD_NVME_ERROR, BD_NVME_ERROR_FAILED, - "Failed to scan topology: %s", - strerror_l (-ret, _C_LOCALE)); - libnvme_free_global_ctx (ctx); - return FALSE; - } - libnvme_for_each_host (ctx, host) - libnvme_for_each_subsystem (host, subsys) - if (!subsysnqn || g_strcmp0 (libnvme_subsystem_get_subsysnqn (subsys), subsysnqn) == 0) - libnvme_subsystem_for_each_ctrl (subsys, ctrl) - if (!path || g_strcmp0 (libnvme_ctrl_get_name (ctrl), path) == 0) { - ret = libnvmf_disconnect_ctrl (ctrl); - if (ret != 0) { - g_set_error (error, BD_NVME_ERROR, BD_NVME_ERROR_FAILED, - "Error disconnecting the controller: %s", - strerror_l (-ret, _C_LOCALE)); - libnvme_free_global_ctx (ctx); - return FALSE; - } - *found = TRUE; - } - libnvme_free_global_ctx (ctx); - return TRUE; -} - /** * bd_nvme_disconnect: * @subsysnqn: The name of the NVMe subsystem to disconnect. @@ -485,23 +486,21 @@ static gboolean _disconnect (const gchar *subsysnqn, const gchar *path, GError * * This may disconnect multiple controllers with matching @subsysnqn and %TRUE * is only returned when all controllers were disconnected successfully. * + * Only controllers owned by libblockdev according to the libnvme controller ownership + * registry are disconnected, i.e. the ones connected by bd_nvme_connect() without + * a specific `"owner"`. Controllers connected by other components, e.g. boot volumes + * connected via NBFT or connections managed by a discovery daemon, are left untouched. + * See bd_nvme_disconnect_full() to disconnect controllers of a different owner. + * * Returns: %TRUE if all matching controllers were disconnected successfully, %FALSE with @error * set in case of a disconnect error or when no matching controllers were found. + * The #BD_NVME_ERROR_OWNED error is returned when none of the matching controllers + * are owned by libblockdev. * * Tech category: %BD_NVME_TECH_FABRICS-%BD_NVME_TECH_MODE_INITIATOR */ gboolean bd_nvme_disconnect (const gchar *subsysnqn, GError **error) { - gboolean found = FALSE; - - if (!_disconnect (subsysnqn, NULL, error, &found)) - return FALSE; - - if (!found) { - g_set_error (error, BD_NVME_ERROR, BD_NVME_ERROR_NO_MATCH, - "No subsystems matching '%s' NQN found.", subsysnqn); - return FALSE; - } - return TRUE; + return bd_nvme_disconnect_full (subsysnqn, NULL, NULL, BD_NVME_DISCONNECT_NONE, error); } /** @@ -512,28 +511,145 @@ gboolean bd_nvme_disconnect (const gchar *subsysnqn, GError **error) { * Disconnects and removes a NVMe over Fabrics controller represented * by a block device path. * + * Only a controller owned by libblockdev according to the libnvme controller ownership + * registry is disconnected, see bd_nvme_disconnect() and bd_nvme_disconnect_full(). + * * Returns: %TRUE if the controller was disconnected successfully, * %FALSE otherwise with @error set. * * Tech category: %BD_NVME_TECH_FABRICS-%BD_NVME_TECH_MODE_INITIATOR */ gboolean bd_nvme_disconnect_by_path (const gchar *path, GError **error) { - const gchar *p; + return bd_nvme_disconnect_full (NULL, path, NULL, BD_NVME_DISCONNECT_NONE, error); +} + +/** + * bd_nvme_disconnect_full: + * @subsysnqn: (nullable): The name of the NVMe subsystem to disconnect. + * @path: (nullable): NVMe controller device to disconnect (e.g. `/dev/nvme0`). + * @owner: (nullable): The owner of the controllers to disconnect or %NULL for libblockdev. + * @flags: Flags to control the disconnect. + * @error: (out) (nullable): Place to store error (if any). + * + * Disconnects and removes existing NVMe over Fabrics controllers matching @subsysnqn + * or the controller represented by @path. Exactly one of @subsysnqn and @path has to + * be specified. + * + * Only controllers registered to @owner in the libnvme controller ownership registry + * (`/run/nvme/registry/`) are disconnected, see the `"owner"` argument of bd_nvme_connect(). + * With the #BD_NVME_DISCONNECT_FORCE flag all matching controllers are disconnected + * regardless of their owner, including controllers without any owner. Use with care, + * this may disconnect devices other components depend on, e.g. the root filesystem. + * + * Returns: %TRUE if all matching controllers were disconnected successfully, %FALSE with @error + * set in case of a disconnect error or when no matching controllers were found. + * The #BD_NVME_ERROR_OWNED error is returned when none of the matching controllers + * are owned by @owner. + * + * Tech category: %BD_NVME_TECH_FABRICS-%BD_NVME_TECH_MODE_INITIATOR + */ +gboolean bd_nvme_disconnect_full (const gchar *subsysnqn, const gchar *path, const gchar *owner, BDNVMEDisconnectFlags flags, GError **error) { + struct libnvme_global_ctx *ctx; + struct libnvme_host *host; + struct libnvme_subsystem *subsys; + struct libnvme_ctrl *ctrl; + const gchar *name = NULL; + gchar *ctrl_name; gboolean found = FALSE; + gboolean disconnected = FALSE; + gchar *skipped_owner = NULL; + gboolean success = FALSE; + int ret; - p = path; - if (g_str_has_prefix (p, "/dev/")) - p += 5; + if ((subsysnqn == NULL) == (path == NULL)) { + g_set_error_literal (error, BD_NVME_ERROR, BD_NVME_ERROR_INVALID_ARGUMENT, + "Exactly one of the subsystem NQN and the controller path has to be specified"); + return FALSE; + } + if (path) { + name = path; + if (g_str_has_prefix (name, "/dev/")) + name += 5; + } + if (!owner) + owner = DEFAULT_OWNER; - if (!_disconnect (NULL, p, error, &found)) + ctx = libnvme_create_global_ctx (); + if (ctx == NULL) { + g_set_error_literal (error, BD_NVME_ERROR, BD_NVME_ERROR_FAILED, + "Failed to create libnvme context"); return FALSE; + } + ret = libnvme_scan_topology (ctx, NULL, NULL); + if (ret < 0) { + g_set_error (error, BD_NVME_ERROR, BD_NVME_ERROR_FAILED, + "Failed to scan topology: %s", + strerror_l (-ret, _C_LOCALE)); + goto out; + } + + libnvme_for_each_host (ctx, host) + libnvme_for_each_subsystem (host, subsys) + libnvme_subsystem_for_each_ctrl (subsys, ctrl) { + if (subsysnqn && g_strcmp0 (libnvme_subsystem_get_subsysnqn (subsys), subsysnqn) != 0) + continue; + if (name && g_strcmp0 (libnvme_ctrl_get_name (ctrl), name) != 0) + continue; + found = TRUE; + + /* a registry read error does not count as a match, never disconnect on error */ + if (!(flags & BD_NVME_DISCONNECT_FORCE) && + libnvmf_registry_attr_equal (ctx, libnvme_ctrl_get_name (ctrl), "owner", owner) != 0) { + if (!skipped_owner) + libnvmf_registry_retrieve (ctx, libnvme_ctrl_get_name (ctrl), "owner", &skipped_owner); + continue; + } + + /* the name is cleared by the disconnect */ + ctrl_name = g_strdup (libnvme_ctrl_get_name (ctrl)); + ret = libnvmf_disconnect_ctrl (ctrl); + if (ret != 0) { + g_set_error (error, BD_NVME_ERROR, BD_NVME_ERROR_FAILED, + "Error disconnecting the controller: %s", + strerror_l (-ret, _C_LOCALE)); + g_free (ctrl_name); + goto out; + } + /* the udev rule shipped with libnvme removes it as well, don't wait for it */ + libnvmf_registry_delete (ctx, ctrl_name); + g_free (ctrl_name); + disconnected = TRUE; + } if (!found) { - g_set_error (error, BD_NVME_ERROR, BD_NVME_ERROR_NO_MATCH, - "No controllers matching the %s device name found.", path); - return FALSE; + if (subsysnqn) + g_set_error (error, BD_NVME_ERROR, BD_NVME_ERROR_NO_MATCH, + "No subsystems matching '%s' NQN found.", subsysnqn); + else + g_set_error (error, BD_NVME_ERROR, BD_NVME_ERROR_NO_MATCH, + "No controllers matching the %s device name found.", path); + goto out; + } + if (!disconnected) { + gchar *what = subsysnqn ? g_strdup_printf ("Controllers of the '%s' subsystem are", subsysnqn) + : g_strdup_printf ("The %s controller is", path); + + if (skipped_owner) + g_set_error (error, BD_NVME_ERROR, BD_NVME_ERROR_OWNED, + "%s owned by '%s' instead of '%s', not disconnecting.", + what, skipped_owner, owner); + else + g_set_error (error, BD_NVME_ERROR, BD_NVME_ERROR_OWNED, + "%s not owned by '%s', not disconnecting.", what, owner); + g_free (what); + goto out; } - return TRUE; + success = TRUE; + +out: + free (skipped_owner); + libnvme_free_global_ctx (ctx); + return success; } diff --git a/src/plugins/nvme/nvme.h b/src/plugins/nvme/nvme.h index ae3e4f8f7..0e2e8f9f2 100644 --- a/src/plugins/nvme/nvme.h +++ b/src/plugins/nvme/nvme.h @@ -28,6 +28,7 @@ GQuark bd_nvme_error_quark (void); * @BD_NVME_ERROR_CONNECT_NODEV: Invalid interface. * @BD_NVME_ERROR_CONNECT_OPNOTSUPP: Operation not supported. * @BD_NVME_ERROR_CONNECT_REFUSED: Connection refused. + * @BD_NVME_ERROR_OWNED: The controller is not owned by the caller (see bd_nvme_disconnect()). */ typedef enum { BD_NVME_ERROR_TECH_UNAVAIL, @@ -48,6 +49,7 @@ typedef enum { BD_NVME_ERROR_CONNECT_NODEV, BD_NVME_ERROR_CONNECT_OPNOTSUPP, BD_NVME_ERROR_CONNECT_REFUSED, + BD_NVME_ERROR_OWNED, } BDNVMEError; typedef enum { @@ -536,6 +538,16 @@ typedef enum { BD_NVME_SANITIZE_ACTION_CRYPTO_ERASE = 3, } BDNVMESanitizeAction; +/** + * BDNVMEDisconnectFlags: + * @BD_NVME_DISCONNECT_NONE: No flags. + * @BD_NVME_DISCONNECT_FORCE: Disconnect controllers regardless of their owner in the libnvme controller ownership registry. + */ +typedef enum { + BD_NVME_DISCONNECT_NONE = 0, + BD_NVME_DISCONNECT_FORCE = 1 << 0, +} BDNVMEDisconnectFlags; + void bd_nvme_controller_info_free (BDNVMEControllerInfo *info); BDNVMEControllerInfo * bd_nvme_controller_info_copy (BDNVMEControllerInfo *info); @@ -622,6 +634,11 @@ gboolean bd_nvme_disconnect (const gchar *subsysn GError **error); gboolean bd_nvme_disconnect_by_path (const gchar *path, GError **error); +gboolean bd_nvme_disconnect_full (const gchar *subsysnqn, + const gchar *path, + const gchar *owner, + BDNVMEDisconnectFlags flags, + GError **error); gchar ** bd_nvme_find_ctrls_for_ns (const gchar *ns_sysfs_path, const gchar *subsysnqn, diff --git a/tests/nvme_test.py b/tests/nvme_test.py index 01d0261ea..ce62a602c 100644 --- a/tests/nvme_test.py +++ b/tests/nvme_test.py @@ -10,6 +10,7 @@ gi.require_version('GLib', '2.0') gi.require_version('BlockDev', '3.0') from gi.repository import GLib, BlockDev +from gi.module import get_introspection_module @required_plugins(("nvme", "loop")) @@ -414,6 +415,150 @@ def test_connect_single_ns(self): self.assertEqual(len(namespaces), 0) + def _get_owner(self, ctrl): + ret, out, _err = run_command("nvme registry retrieve %s -a owner" % os.path.basename(ctrl)) + return out.strip() if ret == 0 else None + + def _connect_new_ctrl(self, connect_fn): + before = set(find_nvme_ctrl_devs_for_subnqn(self.SUBNQN, wait_for_ready=False)) + connect_fn() + new = set(find_nvme_ctrl_devs_for_subnqn(self.SUBNQN)) - before + self.assertEqual(len(new), 1) + return new.pop() + + def _bd_connect(self, owner=None, duplicate=False): + extra = [] + if owner is not None: + extra.append(BlockDev.ExtraArg.new("owner", owner)) + if duplicate: + extra.append(BlockDev.ExtraArg.new("duplicate_connect", "True")) + return self._connect_new_ctrl(lambda: self.assertTrue( + BlockDev.nvme_connect(self.SUBNQN, 'loop', None, None, None, None, None, None, extra))) + + def _cli_connect(self, owner=None, duplicate=False): + cmd = "nvme connect --transport=loop --nqn=%s" % self.SUBNQN + if owner is not None: + cmd += " --owner=%s" % owner + if duplicate: + cmd += " --duplicate-connect" + + def _connect(): + ret, out, err = run_command(cmd) + if ret != 0: + raise RuntimeError("Error connecting '%s': '%s %s'" % (cmd, out, err)) + return self._connect_new_ctrl(_connect) + + def _assert_not_owned(self, fn, *args): + with self.assertRaisesRegex(GLib.GError, r"not disconnecting") as cm: + fn(*args) + # BlockDev.NVMEError is shadowed by the exception class from the overrides + self.assertEqual(cm.exception.code, get_introspection_module('BlockDev').NVMEError.OWNED) + + @tag_test(TestTags.CORE) + def test_connect_owner(self): + """Test that libblockdev only disconnects its own controllers""" + + self._setup_target(1) + self.addCleanup(self._nvme_disconnect, self.SUBNQN, ignore_errors=True) + + # libblockdev is the default owner + own = self._bd_connect() + self.assertEqual(self._get_owner(own), "libblockdev") + + # a custom owner + udisks = self._bd_connect(owner="udisks", duplicate=True) + self.assertEqual(self._get_owner(udisks), "udisks") + + # an empty owner leaves the controller unowned + unowned = self._bd_connect(owner="", duplicate=True) + self.assertIsNone(self._get_owner(unowned)) + + # only the controller owned by libblockdev is disconnected, together with its registry entry + self.assertTrue(BlockDev.nvme_disconnect(self.SUBNQN)) + self.assertFalse(os.path.exists(own)) + self.assertIsNone(self._get_owner(own)) + self.assertTrue(os.path.exists(udisks)) + self.assertTrue(os.path.exists(unowned)) + + # nothing owned by libblockdev is left + self._assert_not_owned(BlockDev.nvme_disconnect, self.SUBNQN) + self._assert_not_owned(BlockDev.nvme_disconnect_by_path, udisks) + self._assert_not_owned(BlockDev.nvme_disconnect_by_path, unowned) + self._assert_not_owned(BlockDev.nvme_disconnect_full, self.SUBNQN, None, "someone-else", + BlockDev.NVMEDisconnectFlags.NONE) + self.assertTrue(os.path.exists(udisks)) + self.assertTrue(os.path.exists(unowned)) + + # the owner can disconnect its controllers + self.assertTrue(BlockDev.nvme_disconnect_full(None, udisks, "udisks", BlockDev.NVMEDisconnectFlags.NONE)) + self.assertFalse(os.path.exists(udisks)) + self.assertTrue(os.path.exists(unowned)) + + # force disconnects regardless of the owner + self.assertTrue(BlockDev.nvme_disconnect_full(self.SUBNQN, None, None, BlockDev.NVMEDisconnectFlags.FORCE)) + self.assertFalse(os.path.exists(unowned)) + self.assertEqual(len(find_nvme_ctrl_devs_for_subnqn(self.SUBNQN)), 0) + + # owned by libblockdev again after a reconnect + own = self._bd_connect() + self.assertEqual(self._get_owner(own), "libblockdev") + self.assertTrue(BlockDev.nvme_disconnect_by_path(own)) + self.assertFalse(os.path.exists(own)) + + @tag_test(TestTags.CORE) + def test_disconnect_foreign(self): + """Test that controllers connected by others are left alone""" + + self._setup_target(1) + self.addCleanup(self._nvme_disconnect, self.SUBNQN, ignore_errors=True) + + # e.g. a boot volume connected from the NBFT and a manual 'nvme connect' + nbft = self._cli_connect(owner="nbft") + manual = self._cli_connect(duplicate=True) + self.assertEqual(self._get_owner(nbft), "nbft") + self.assertIsNone(self._get_owner(manual)) + + # an already connected controller must not be claimed by libblockdev + with self.assertRaisesRegex(GLib.GError, r"Error connecting the controller: already connected"): + BlockDev.nvme_connect(self.SUBNQN, 'loop', None, None, None, None, None, None) + self.assertEqual(self._get_owner(nbft), "nbft") + self.assertIsNone(self._get_owner(manual)) + + self._assert_not_owned(BlockDev.nvme_disconnect, self.SUBNQN) + self._assert_not_owned(BlockDev.nvme_disconnect_by_path, nbft) + self._assert_not_owned(BlockDev.nvme_disconnect_by_path, manual) + self.assertTrue(os.path.exists(nbft)) + self.assertTrue(os.path.exists(manual)) + + # a libblockdev controller next to them is disconnected without touching the others + own = self._bd_connect(duplicate=True) + self.assertTrue(BlockDev.nvme_disconnect(self.SUBNQN)) + self.assertFalse(os.path.exists(own)) + self.assertTrue(os.path.exists(nbft)) + self.assertTrue(os.path.exists(manual)) + + # explicitly forced + self.assertTrue(BlockDev.nvme_disconnect_full(None, manual, None, BlockDev.NVMEDisconnectFlags.FORCE)) + self.assertFalse(os.path.exists(manual)) + self.assertTrue(BlockDev.nvme_disconnect_full(None, nbft, "nbft", BlockDev.NVMEDisconnectFlags.NONE)) + self.assertFalse(os.path.exists(nbft)) + + @tag_test(TestTags.CORE) + def test_disconnect_full_args(self): + """Test argument validation of bd_nvme_disconnect_full()""" + + msg = r"Exactly one of the subsystem NQN and the controller path has to be specified" + with self.assertRaisesRegex(GLib.GError, msg): + BlockDev.nvme_disconnect_full(None, None, None, BlockDev.NVMEDisconnectFlags.FORCE) + with self.assertRaisesRegex(GLib.GError, msg): + BlockDev.nvme_disconnect_full(self.SUBNQN, "/dev/nvme0", None, BlockDev.NVMEDisconnectFlags.NONE) + + with self.assertRaisesRegex(GLib.GError, r"No subsystems matching '.*' NQN found."): + BlockDev.nvme_disconnect_full(self.SUBNQN + "xx", None, None, BlockDev.NVMEDisconnectFlags.FORCE) + with self.assertRaisesRegex(GLib.GError, r"No controllers matching the /dev/nvmexx device name found."): + BlockDev.nvme_disconnect_full(None, "/dev/nvmexx", None, BlockDev.NVMEDisconnectFlags.FORCE) + + @tag_test(TestTags.CORE) def test_connect_multiple_ns(self): """Test connect and disconnect multiple namespaces""" @@ -436,7 +581,7 @@ def test_connect_multiple_ns(self): for c in ctrls: self.assertTrue(re.match(r'/dev/nvme[0-9]+', c)) self.assertTrue(os.path.exists(c)) - namespaces = find_nvme_ns_devs_for_subnqn(self.SUBNQN) + namespaces = find_nvme_ns_devs_for_subnqn(self.SUBNQN, num_expected=NUM_NS) self.assertEqual(len(namespaces), NUM_NS) for ns in namespaces: self.assertTrue(re.match(r'/dev/nvme[0-9]+n[0-9]+', ns))