diff --git a/configure.ac b/configure.ac index e5d65d8b..92df389c 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 5d75d056..7e73e227 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/docs/libblockdev-sections.txt b/docs/libblockdev-sections.txt index 4a8f538d..2afaf5d3 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 85bbe2bb..a6f6c251 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: @@ -1291,14 +1305,16 @@ 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 + * - `"config"`: Use the specified 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. + * - `"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, 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. * - `"nr_poll_queues"`: Number of additional queues that will be used for polling latency sensitive I/O. @@ -1314,17 +1330,24 @@ 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. * * 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. + * 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. @@ -1342,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 */ @@ -1357,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. * @@ -1364,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-error.c b/src/plugins/nvme/nvme-error.c index 4bd4d771..1287e2fa 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 796b6bfc..879c7cf4 100644 --- a/src/plugins/nvme/nvme-fabrics.c +++ b/src/plugins/nvme/nvme-fabrics.c @@ -37,119 +37,198 @@ #include "nvme-private.h" -/* nvme-cli defaults */ -#define PATH_NVMF_CONFIG "/etc/nvme/config.json" -#define MAX_DISC_RETRIES 10 +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; + } + } -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) { + return config_file; +} + +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) 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; \ + if (fctx && endptr != (*extra_i)->val) \ + setter (fctx, v); \ } -#define SAFE_BOOL_CONV(target) { \ +#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) \ - 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, "owner") == 0 && owner) + *owner = (*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 #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; +} + + +/* 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: @@ -175,14 +254,16 @@ 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 + * - `"config"`: Use the specified 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. + * - `"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, 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. * - `"nr_poll_queues"`: Number of additional queues that will be used for polling latency sensitive I/O. @@ -198,17 +279,24 @@ static void parse_extra_args (const BDExtraArg **extra, struct nvme_fabrics_conf * - `"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. * * 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. + * 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. @@ -217,16 +305,23 @@ 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; + const gchar *config_file; + gboolean use_config; + 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; + const gchar *owner = NULL; + struct connected_data hook_data; + 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) { g_set_error_literal (error, BD_NVME_ERROR, BD_NVME_ERROR_INVALID_ARGUMENT, @@ -244,61 +339,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,69 +351,130 @@ 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); - if (ret != 0) { - _nvme_fabrics_errno_to_gerror (ret, errno, error); - g_prefix_error (error, "Error connecting the controller: "); - nvme_free_ctrl (ctrl); - nvme_free_tree (root); - 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); + } } - nvme_free_ctrl (ctrl); - nvme_free_tree (root); - - return TRUE; -} - -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; - 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)); - return FALSE; + /* 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; } - ret = nvme_scan_topology (root, NULL, NULL); + + /* 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 (errno, _C_LOCALE)); - nvme_free_tree (root); - return FALSE; + strerror_l (-ret, _C_LOCALE)); + goto out; } - 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); - 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); - return FALSE; - } - *found = TRUE; - } - nvme_free_tree (root); - return TRUE; + + /* 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) + 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, 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"), + 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: "); + 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, error); + g_prefix_error (error, "Error connecting the controller: "); + goto out; + } + + success = TRUE; + +out: + libnvmf_context_free (fctx); + libnvmf_config_free (config); + libnvme_free_global_ctx (ctx); + free (host_nqn_val); + free (host_id_val); + + return success; } /** @@ -385,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); } /** @@ -412,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; } @@ -455,11 +671,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 +687,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 +730,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 +763,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 +779,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 +820,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 +839,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 +868,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 +897,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 b36a644b..c832edb4 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 96272222..161826b3 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 */, + 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 01656d80..6fd5d335 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/src/plugins/nvme/nvme.h b/src/plugins/nvme/nvme.h index ae3e4f8f..0e2e8f9f 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 a785ec82..ce62a602 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")) @@ -381,8 +382,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 @@ -409,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""" @@ -431,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)) diff --git a/tests/utils.py b/tests/utils.py index 847415fc..14d1e052 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():