Blob Blame History Raw
From 35d0b1d43a901151a47478205b4e0cfcb8e350ed Mon Sep 17 00:00:00 2001
From: Vojtech Trefny <vtrefny@redhat.com>
Date: Thu, 22 Sep 2022 11:33:43 +0200
Subject: [PATCH] NVMe plugin backport

Resolves: rhbz#2123338
---
 Makefile.am                         |    7 +-
 configure.ac                        |   23 +-
 data/conf.d/00-default.cfg          |    3 +
 dist/libblockdev.spec.in            |   48 +-
 docs/libblockdev-docs.xml.in        |    1 +
 docs/libblockdev-sections.txt       |   70 ++
 features.rst                        |   12 +
 include/blockdev/Makefile.am        |    1 +
 specs.rst                           |    1 +
 src/lib/Makefile.am                 |    1 +
 src/lib/blockdev.c.in               |   18 +-
 src/lib/plugin_apis/nvme.api        | 1549 +++++++++++++++++++++++++++
 src/lib/plugins.h                   |    1 +
 src/plugins/Makefile.am             |    8 +-
 src/plugins/nvme/Makefile.am        |   22 +
 src/plugins/nvme/nvme-error.c       |  160 +++
 src/plugins/nvme/nvme-fabrics.c     |  918 ++++++++++++++++
 src/plugins/nvme/nvme-info.c        | 1028 ++++++++++++++++++
 src/plugins/nvme/nvme-op.c          |  388 +++++++
 src/plugins/nvme/nvme-private.h     |   25 +
 src/plugins/nvme/nvme.c             |  103 ++
 src/plugins/nvme/nvme.h             |  700 ++++++++++++
 src/python/gi/overrides/BlockDev.py |   23 +
 tests/library_test.py               |    3 +-
 tests/nvme_test.py                  |  638 +++++++++++
 tests/overrides_test.py             |    3 +-
 tests/run_tests.py                  |    3 +-
 tests/utils.py                      |  240 +++++
 28 files changed, 5986 insertions(+), 11 deletions(-)
 create mode 100644 src/lib/plugin_apis/nvme.api
 create mode 100644 src/plugins/nvme/Makefile.am
 create mode 100644 src/plugins/nvme/nvme-error.c
 create mode 100644 src/plugins/nvme/nvme-fabrics.c
 create mode 100644 src/plugins/nvme/nvme-info.c
 create mode 100644 src/plugins/nvme/nvme-op.c
 create mode 100644 src/plugins/nvme/nvme-private.h
 create mode 100644 src/plugins/nvme/nvme.c
 create mode 100644 src/plugins/nvme/nvme.h
 create mode 100644 tests/nvme_test.py

diff --git a/Makefile.am b/Makefile.am
index 13090e36..324dad4c 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -40,6 +40,10 @@ if !WITH_NVDIMM
 DISTCHECK_CONFIGURE_FLAGS += --without-nvdimm
 endif

+if !WITH_NVME
+DISTCHECK_CONFIGURE_FLAGS += --without-nvme
+endif
+
 if !WITH_SWAP
 DISTCHECK_CONFIGURE_FLAGS += --without-swap
 endif
@@ -68,7 +72,7 @@ MAINTAINERCLEANFILES = Makefile.in aclocal.m4 config.guess config.sub \
     configure depcomp install-sh ltmain.sh missing py-compile compile ar-lib \
     m4/*.m4

-LIBDIRS = src/utils/.libs:src/plugins/.libs:src/plugins/fs/.libs:src/lib/.libs
+LIBDIRS = src/utils/.libs:src/plugins/.libs:src/plugins/fs/.libs:src/plugins/nvme/.libs:src/lib/.libs
 GIDIR = src/lib

 if WITH_PYTHON3
@@ -93,6 +97,7 @@ PLUGINS = btrfs \
 	mdraid \
 	mpath \
 	nvdimm \
+	nvme \
 	part \
 	s390 \
 	swap \
diff --git a/configure.ac b/configure.ac
index 79bf8045..ec789c91 100644
--- a/configure.ac
+++ b/configure.ac
@@ -21,6 +21,7 @@ LT_INIT
 AC_CONFIG_FILES([Makefile src/Makefile \
                           src/plugins/Makefile \
                           src/plugins/fs/Makefile \
+                          src/plugins/nvme/Makefile \
                           src/utils/Makefile \
                           src/utils/blockdev-utils.pc \
                           src/lib/Makefile \
@@ -181,6 +182,17 @@ AS_IF([test "x$with_tools" != "xno"],
       [AC_SUBST([WITH_TOOLS], [1])],
       [])

+AC_ARG_WITH([nvme],
+    AS_HELP_STRING([--with-nvme], [support nvme @<:@default=yes@:>@]),
+    [],
+    [with_nvme=yes])
+
+AC_SUBST([WITH_NVME], [0])
+AM_CONDITIONAL(WITH_NVME, test "x$with_nvme" != "xno")
+AS_IF([test "x$with_nvme" != "xno"],
+      [AC_DEFINE([WITH_BD_NVME], [], [Define if nvme is supported]) AC_SUBST([WITH_NVME], [1])],
+      [])
+
 LIBBLOCKDEV_PLUGIN([BTRFS], [btrfs])
 LIBBLOCKDEV_PLUGIN([CRYPTO], [crypto])
 LIBBLOCKDEV_PLUGIN([DM], [dm])
@@ -194,6 +206,7 @@ LIBBLOCKDEV_PLUGIN([KBD], [kbd])
 LIBBLOCKDEV_PLUGIN([PART], [part])
 LIBBLOCKDEV_PLUGIN([FS], [fs])
 LIBBLOCKDEV_PLUGIN([NVDIMM], [nvdimm])
+LIBBLOCKDEV_PLUGIN([NVME], [nvme])
 LIBBLOCKDEV_PLUGIN([VDO], [vdo])

 AM_CONDITIONAL(WITH_PART_O_WITH_FS, test "x$with_part" != "xno" -o "x$with_fs" != "xno")
@@ -267,8 +280,15 @@ AS_IF([test "x$with_nvdimm" != "xno"],
              [AC_DEFINE([LIBNDCTL_NEW_MODES])], [])]
       [])

+AS_IF([test "x$with_nvme" != "xno"],
+      [LIBBLOCKDEV_PKG_CHECK_MODULES([NVME], [libnvme >= 1.0])
+      AS_IF([$PKG_CONFIG --atleast-version=1.1 libnvme],
+            [AC_DEFINE([HAVE_LIBNVME_1_1])], [])
+      ],
+      [])
+
 AS_IF([test "x$with_vdo" != "xno"],
-      [LIBBLOCKDEV_PKG_CHECK_MODULES([YAML], [yaml-0.1])]
+      [LIBBLOCKDEV_PKG_CHECK_MODULES([YAML], [yaml-0.1])],
       [])

 AC_SUBST([skip_patterns], [$skip_patterns])
@@ -332,6 +352,7 @@ echo "
         MDRAID plugin:              ${with_mdraid}
         MPath plugin                ${with_mpath}
         NVDIMM plugin:              ${with_nvdimm}
+        NVMe plugin:                ${with_nvme}
         Part plugin:                ${with_part}
         S390 plugin:                ${s390_info}
         Swap plugin:                ${with_swap}
diff --git a/data/conf.d/00-default.cfg b/data/conf.d/00-default.cfg
index 2a559204..696fc30b 100644
--- a/data/conf.d/00-default.cfg
+++ b/data/conf.d/00-default.cfg
@@ -42,6 +42,9 @@ sonames=libbd_mpath.so.2
 [nvdimm]
 sonames=libbd_nvdimm.so.2

+[nvme]
+sonames=libbd_nvme.so.2
+
 [swap]
 sonames=libbd_swap.so.2

diff --git a/dist/libblockdev.spec.in b/dist/libblockdev.spec.in
index e03737c8..d854cf87 100644
--- a/dist/libblockdev.spec.in
+++ b/dist/libblockdev.spec.in
@@ -20,6 +20,7 @@
 %define with_escrow @WITH_ESCROW@
 %define with_dmraid @WITH_DMRAID@
 %define with_tools @WITH_TOOLS@
+%define with_nvme @WITH_NVME@

 # python2 is not available on RHEL > 7 and not needed on Fedora > 29
 %if 0%{?rhel} > 7 || 0%{?fedora} > 29 || %{with_python2} == 0
@@ -120,8 +121,11 @@
 %if %{with_gi} != 1
 %define gi_copts --disable-introspection
 %endif
+%if %{with_nvme} != 1
+%define nvme_copts --without-nvme
+%endif

-%define configure_opts %{?python2_copts} %{?python3_copts} %{?bcache_copts} %{?lvm_dbus_copts} %{?btrfs_copts} %{?crypto_copts} %{?dm_copts} %{?loop_copts} %{?lvm_copts} %{?lvm_dbus_copts} %{?mdraid_copts} %{?mpath_copts} %{?swap_copts} %{?kbd_copts} %{?part_copts} %{?fs_copts} %{?nvdimm_copts} %{?vdo_copts} %{?tools_copts} %{?gi_copts}
+%define configure_opts %{?python2_copts} %{?python3_copts} %{?bcache_copts} %{?lvm_dbus_copts} %{?btrfs_copts} %{?crypto_copts} %{?dm_copts} %{?loop_copts} %{?lvm_copts} %{?lvm_dbus_copts} %{?mdraid_copts} %{?mpath_copts} %{?swap_copts} %{?kbd_copts} %{?part_copts} %{?fs_copts} %{?nvdimm_copts} %{?nvme_copts} %{?vdo_copts} %{?tools_copts} %{?gi_copts}

 Name:        libblockdev
 Version:     2.28
@@ -495,6 +499,29 @@ with the libblockdev-nvdimm plugin/library.
 %endif


+%if %{with_nvme}
+%package nvme
+BuildRequires: libnvme-devel
+BuildRequires: libuuid-devel
+Summary:     The NVMe plugin for the libblockdev library
+Requires: %{name}-utils%{?_isa} >= 0.11
+
+%description nvme
+The libblockdev library plugin (and in the same time a standalone library)
+providing the functionality related to operations with NVMe devices.
+
+%package nvme-devel
+Summary:     Development files for the libblockdev-nvme plugin/library
+Requires: %{name}-nvme%{?_isa} = %{version}-%{release}
+Requires: %{name}-utils-devel%{?_isa}
+Requires: glib2-devel
+
+%description nvme-devel
+This package contains header files and pkg-config files needed for development
+with the libblockdev-nvme plugin/library.
+%endif
+
+
 %if %{with_part}
 %package part
 BuildRequires: parted-devel
@@ -654,6 +681,10 @@ Requires: %{name}-mpath%{?_isa} = %{version}-%{release}
 Requires: %{name}-nvdimm%{?_isa} = %{version}-%{release}
 %endif

+%if %{with_nvme}
+Requires: %{name}-nvme%{?_isa} = %{version}-%{release}
+%endif
+
 %if %{with_part}
 Requires: %{name}-part%{?_isa} = %{version}-%{release}
 %endif
@@ -730,6 +761,10 @@ find %{buildroot} -type f -name "*.la" | xargs %{__rm}
 %ldconfig_scriptlets nvdimm
 %endif

+%if %{with_nvme}
+%ldconfig_scriptlets nvme
+%endif
+
 %if %{with_part}
 %ldconfig_scriptlets part
 %endif
@@ -929,6 +964,17 @@ find %{buildroot} -type f -name "*.la" | xargs %{__rm}
 %endif


+%if %{with_nvme}
+%files nvme
+%{_libdir}/libbd_nvme.so.*
+
+%files nvme-devel
+%{_libdir}/libbd_nvme.so
+%dir %{_includedir}/blockdev
+%{_includedir}/blockdev/nvme.h
+%endif
+
+
 %if %{with_part}
 %files part
 %{_libdir}/libbd_part.so.*
diff --git a/docs/libblockdev-docs.xml.in b/docs/libblockdev-docs.xml.in
index f5b07e55..066e1475 100644
--- a/docs/libblockdev-docs.xml.in
+++ b/docs/libblockdev-docs.xml.in
@@ -30,6 +30,7 @@
     <xi:include href="xml/mdraid.xml"/>
     <xi:include href="xml/mpath.xml"/>
     <xi:include href="xml/nvdimm.xml"/>
+    <xi:include href="xml/nvme.xml"/>
     <xi:include href="xml/plugins.xml"/>
     <xi:include href="xml/part.xml"/>
     <xi:include href="xml/swap.xml"/>
diff --git a/docs/libblockdev-sections.txt b/docs/libblockdev-sections.txt
index 512820c2..540e2b96 100644
--- a/docs/libblockdev-sections.txt
+++ b/docs/libblockdev-sections.txt
@@ -642,6 +642,76 @@ BDNVDIMMTechMode
 bd_nvdimm_is_tech_avail
 </SECTION>

+<SECTION>
+<FILE>nvme</FILE>
+bd_nvme_check_deps
+bd_nvme_close
+bd_nvme_init
+bd_nvme_error_quark
+BD_NVME_ERROR
+BDNVMEError
+BDNVMETech
+BDNVMETechMode
+bd_nvme_is_tech_avail
+BDNVMEControllerFeature
+BDNVMEControllerType
+BDNVMEControllerInfo
+bd_nvme_get_controller_info
+bd_nvme_controller_info_free
+bd_nvme_controller_info_copy
+BDNVMELBAFormatRelativePerformance
+BDNVMELBAFormat
+bd_nvme_lba_format_free
+bd_nvme_lba_format_copy
+BDNVMENamespaceFeature
+BDNVMENamespaceInfo
+bd_nvme_get_namespace_info
+bd_nvme_namespace_info_free
+bd_nvme_namespace_info_copy
+BDNVMESmartCriticalWarning
+BDNVMESmartLog
+bd_nvme_get_smart_log
+bd_nvme_smart_log_free
+bd_nvme_smart_log_copy
+BDNVMETransportType
+BDNVMEErrorLogEntry
+bd_nvme_get_error_log_entries
+bd_nvme_error_log_entry_free
+bd_nvme_error_log_entry_copy
+BDNVMESelfTestLog
+BDNVMESelfTestLogEntry
+BDNVMESelfTestAction
+BDNVMESelfTestResult
+bd_nvme_get_self_test_log
+bd_nvme_self_test_log_free
+bd_nvme_self_test_log_copy
+bd_nvme_self_test_log_entry_free
+bd_nvme_self_test_log_entry_copy
+bd_nvme_device_self_test
+BDNVMEFormatSecureErase
+bd_nvme_format
+BDNVMESanitizeStatus
+BDNVMESanitizeLog
+bd_nvme_get_sanitize_log
+bd_nvme_sanitize_log_free
+bd_nvme_sanitize_log_copy
+BDNVMESanitizeAction
+bd_nvme_sanitize
+bd_nvme_get_host_nqn
+bd_nvme_get_host_id
+bd_nvme_generate_host_nqn
+bd_nvme_set_host_nqn
+bd_nvme_set_host_id
+bd_nvme_connect
+bd_nvme_disconnect
+bd_nvme_disconnect_by_path
+BDNVMEDiscoveryLogEntry
+BDNVMEAddressFamily
+BDNVMETCPSecurity
+bd_nvme_discover
+bd_nvme_find_ctrls_for_ns
+</SECTION>
+
 <SECTION>
 <FILE>vdo</FILE>
 bd_vdo_check_deps
diff --git a/features.rst b/features.rst
index 67fa8de8..a223c26c 100644
--- a/features.rst
+++ b/features.rst
@@ -303,6 +303,18 @@ NVDIMM
    * namespace_reconfigure
    * list_namespaces

+NVMe
+-----
+
+:supported technologies:
+   NVMe controller and namespace information
+
+:functions:
+   * get_controller_info
+   * get_namespace_info
+   * get_smart_log
+   * get_error_log_entries
+
 VDO
 ---

diff --git a/include/blockdev/Makefile.am b/include/blockdev/Makefile.am
index 3e290505..e6246748 100644
--- a/include/blockdev/Makefile.am
+++ b/include/blockdev/Makefile.am
@@ -1,5 +1,6 @@
 all-local:
 	for header in ${srcdir}/../../src/plugins/*.h; do ln -sf $${header} ./; done
+	for header in ${srcdir}/../../src/plugins/nvme/nvme.h; do ln -sf $${header} ./; done
 	for header in ${srcdir}/../../src/utils/*.h; do ln -sf $${header} ./; done
 	for header in ${srcdir}/../../src/lib/*.h; do ln -sf $${header} ./; done
 	mkdir -p fs;
diff --git a/specs.rst b/specs.rst
index 904adc3c..9a0cf702 100644
--- a/specs.rst
+++ b/specs.rst
@@ -23,6 +23,7 @@ modules as well as udisks2. It supports the following storage technologies:
 * multipath
 * DASD
 * NVDIMM namespaces
+* NVMe
 * VDO volumes

 Of course some additional technologies may be supported in the future.
diff --git a/src/lib/Makefile.am b/src/lib/Makefile.am
index 6dfb5765..19cb2f11 100644
--- a/src/lib/Makefile.am
+++ b/src/lib/Makefile.am
@@ -32,6 +32,7 @@ GIHEADERS = ${builddir}/plugin_apis/kbd.h \
 	${builddir}/plugin_apis/part.h \
 	${builddir}/plugin_apis/fs.h \
 	${builddir}/plugin_apis/nvdimm.h \
+	${builddir}/plugin_apis/nvme.h \
 	${builddir}/plugin_apis/vdo.h

 GIHEADERS += $(wildcard ${srcdir}/../utils/*.[ch])
diff --git a/src/lib/blockdev.c.in b/src/lib/blockdev.c.in
index e96cc77f..447c1e23 100644
--- a/src/lib/blockdev.c.in
+++ b/src/lib/blockdev.c.in
@@ -27,6 +27,8 @@
 #include "plugin_apis/fs.c"
 #include "plugin_apis/nvdimm.h"
 #include "plugin_apis/nvdimm.c"
+#include "plugin_apis/nvme.h"
+#include "plugin_apis/nvme.c"
 #include "plugin_apis/vdo.h"
 #include "plugin_apis/vdo.c"

@@ -64,7 +66,8 @@ static gchar * default_plugin_so[BD_PLUGIN_UNDEF] = {
     "libbd_dm.so."@MAJOR_VER@, "libbd_mdraid.so."@MAJOR_VER@,
     "libbd_kbd.so."@MAJOR_VER@,"libbd_s390.so."@MAJOR_VER@,
     "libbd_part.so."@MAJOR_VER@, "libbd_fs.so."@MAJOR_VER@,
-    "libbd_nvdimm.so."@MAJOR_VER@, "libbd_vdo.so."@MAJOR_VER@
+    "libbd_nvdimm.so."@MAJOR_VER@, "libbd_nvme.so."@MAJOR_VER@,
+    "libbd_vdo.so."@MAJOR_VER@
 };
 static BDPluginStatus plugins[BD_PLUGIN_UNDEF] = {
     {{BD_PLUGIN_LVM, NULL}, NULL},
@@ -80,10 +83,10 @@ static BDPluginStatus plugins[BD_PLUGIN_UNDEF] = {
     {{BD_PLUGIN_PART, NULL}, NULL},
     {{BD_PLUGIN_FS, NULL}, NULL},
     {{BD_PLUGIN_NVDIMM, NULL}, NULL},
-    {{BD_PLUGIN_VDO, NULL}, NULL},
+    {{BD_PLUGIN_NVME, NULL}, NULL},
 };
 static gchar* plugin_names[BD_PLUGIN_UNDEF] = {
-    "lvm", "btrfs", "swap", "loop", "crypto", "mpath", "dm", "mdraid", "kbd", "s390", "part", "fs", "nvdimm", "vdo"
+    "lvm", "btrfs", "swap", "loop", "crypto", "mpath", "dm", "mdraid", "kbd", "s390", "part", "fs", "nvdimm", "nvme", "vdo"
 };

 static void set_plugin_so_name (BDPlugin name, const gchar *so_name) {
@@ -238,6 +241,10 @@ static void unload_plugins (void) {
         g_warning ("Failed to close the nvdimm plugin");
     plugins[BD_PLUGIN_NVDIMM].handle = NULL;

+    if (plugins[BD_PLUGIN_NVME].handle && !unload_nvme (plugins[BD_PLUGIN_NVME].handle))
+        g_warning ("Failed to close the nvme plugin");
+    plugins[BD_PLUGIN_NVME].handle = NULL;
+
     if (plugins[BD_PLUGIN_VDO].handle && !unload_vdo (plugins[BD_PLUGIN_VDO].handle))
         g_warning ("Failed to close the VDO plugin");
     plugins[BD_PLUGIN_VDO].handle = NULL;
@@ -281,6 +288,8 @@ static void do_load (GSList **plugins_sonames) {
         load_plugin_from_sonames (BD_PLUGIN_FS, load_fs_from_plugin, &(plugins[BD_PLUGIN_FS].handle), plugins_sonames[BD_PLUGIN_FS]);
     if (!plugins[BD_PLUGIN_NVDIMM].handle && plugins_sonames[BD_PLUGIN_NVDIMM])
         load_plugin_from_sonames (BD_PLUGIN_NVDIMM, load_nvdimm_from_plugin, &(plugins[BD_PLUGIN_NVDIMM].handle), plugins_sonames[BD_PLUGIN_NVDIMM]);
+    if (!plugins[BD_PLUGIN_NVME].handle && plugins_sonames[BD_PLUGIN_NVME])
+        load_plugin_from_sonames (BD_PLUGIN_NVME, load_nvme_from_plugin, &(plugins[BD_PLUGIN_NVME].handle), plugins_sonames[BD_PLUGIN_NVME]);
     if (!plugins[BD_PLUGIN_VDO].handle && plugins_sonames[BD_PLUGIN_VDO])
         load_plugin_from_sonames (BD_PLUGIN_VDO, load_vdo_from_plugin, &(plugins[BD_PLUGIN_VDO].handle), plugins_sonames[BD_PLUGIN_VDO]);
 }
@@ -291,7 +300,8 @@ static gboolean load_plugins (BDPluginSpec **require_plugins, gboolean reload, g
     GError *error = NULL;
     GSequence *config_files = NULL;
     GSList *plugins_sonames[BD_PLUGIN_UNDEF] = {NULL, NULL, NULL, NULL, NULL,
-                                                NULL, NULL, NULL, NULL, NULL};
+                                                NULL, NULL, NULL, NULL, NULL,
+                                                NULL};
     BDPlugin plugin_name = BD_PLUGIN_UNDEF;
     guint64 required_plugins_mask = 0;

diff --git a/src/lib/plugin_apis/nvme.api b/src/lib/plugin_apis/nvme.api
new file mode 100644
index 00000000..79247a01
--- /dev/null
+++ b/src/lib/plugin_apis/nvme.api
@@ -0,0 +1,1549 @@
+#include <glib.h>
+#include <glib-object.h>
+#include <uuid/uuid.h>
+#include <blockdev/utils.h>
+
+#ifndef BD_NVME_API
+#define BD_NVME_API
+
+GQuark bd_nvme_error_quark (void) {
+    return g_quark_from_static_string ("g-bd-nvme-error-quark");
+}
+
+#define BD_NVME_ERROR bd_nvme_error_quark ()
+/* BpG-skip */
+/**
+ * BDNVMEError:
+ * @BD_NVME_ERROR_TECH_UNAVAIL: NVMe support not available.
+ * @BD_NVME_ERROR_FAILED: General error.
+ * @BD_NVME_ERROR_BUSY: The device is temporarily unavailable or in an inconsistent state.
+ * @BD_NVME_ERROR_INVALID_ARGUMENT: Invalid argument.
+ * @BD_NVME_ERROR_WOULD_FORMAT_ALL_NS: The NVMe controller indicates that it would format all namespaces in the NVM subsystem.
+ * @BD_NVME_ERROR_SC_GENERIC: Generic NVMe Command Status Code.
+ * @BD_NVME_ERROR_SC_CMD_SPECIFIC: NVMe Command Specific error.
+ * @BD_NVME_ERROR_SC_MEDIA: Media and Data Integrity Errors: media specific errors that occur in the NVM or data integrity type errors.
+ * @BD_NVME_ERROR_SC_PATH: Path related error.
+ * @BD_NVME_ERROR_SC_VENDOR_SPECIFIC: NVMe Vendor specific error.
+ * @BD_NVME_ERROR_NO_MATCH: No matching resource found (e.g. a Fabrics Controller).
+ * @BD_NVME_ERROR_CONNECT: General connection error.
+ * @BD_NVME_ERROR_CONNECT_ALREADY: Already connected.
+ * @BD_NVME_ERROR_CONNECT_INVALID: Invalid argument specified.
+ * @BD_NVME_ERROR_CONNECT_ADDRINUSE: HostNQN already in use.
+ * @BD_NVME_ERROR_CONNECT_NODEV: Invalid interface.
+ * @BD_NVME_ERROR_CONNECT_OPNOTSUPP: Operation not supported.
+ */
+/* BpG-skip-end */
+typedef enum {
+    BD_NVME_ERROR_TECH_UNAVAIL,
+    BD_NVME_ERROR_FAILED,
+    BD_NVME_ERROR_BUSY,
+    BD_NVME_ERROR_INVALID_ARGUMENT,
+    BD_NVME_ERROR_WOULD_FORMAT_ALL_NS,
+    BD_NVME_ERROR_SC_GENERIC,
+    BD_NVME_ERROR_SC_CMD_SPECIFIC,
+    BD_NVME_ERROR_SC_MEDIA,
+    BD_NVME_ERROR_SC_PATH,
+    BD_NVME_ERROR_SC_VENDOR_SPECIFIC,
+    BD_NVME_ERROR_NO_MATCH,
+    BD_NVME_ERROR_CONNECT,
+    BD_NVME_ERROR_CONNECT_ALREADY,
+    BD_NVME_ERROR_CONNECT_INVALID,
+    BD_NVME_ERROR_CONNECT_ADDRINUSE,
+    BD_NVME_ERROR_CONNECT_NODEV,
+    BD_NVME_ERROR_CONNECT_OPNOTSUPP,
+} BDNVMEError;
+
+typedef enum {
+    BD_NVME_TECH_NVME = 0,
+    BD_NVME_TECH_FABRICS,
+} BDNVMETech;
+
+typedef enum {
+    BD_NVME_TECH_MODE_INFO         = 1 << 0,
+    BD_NVME_TECH_MODE_MANAGE       = 1 << 1,
+    BD_NVME_TECH_MODE_INITIATOR    = 1 << 2,
+} BDNVMETechMode;
+
+
+/* BpG-skip */
+/**
+ * BDNVMEControllerFeature:
+ * @BD_NVME_CTRL_FEAT_MULTIPORT: if set, then the NVM subsystem may contain more than one NVM subsystem port, otherwise it's single-port only.
+ * @BD_NVME_CTRL_FEAT_MULTICTRL: if set, then the NVM subsystem may contain two or more controllers, otherwise contains only single controller.
+ * @BD_NVME_CTRL_FEAT_SRIOV: if set, then the controller is associated with an SR-IOV Virtual Function, otherwise it's associated with a PCI Function or a Fabrics connection.
+ * @BD_NVME_CTRL_FEAT_ANA_REPORTING: indicates that the NVM subsystem supports Asymmetric Namespace Access (ANA) Reporting.
+ * @BD_NVME_CTRL_FEAT_FORMAT: indicates that the controller supports the Format NVM command.
+ * @BD_NVME_CTRL_FEAT_FORMAT_ALL_NS: if set, then a format (excluding secure erase) of any namespace results in a format of all namespaces
+ *                                   in an NVM subsystem with all namespaces in an NVM subsystem configured with the same attributes.
+ *                                   If not set, then the controller supports format on a per namespace basis.
+ * @BD_NVME_CTRL_FEAT_NS_MGMT: indicates that the controller supports the Namespace Management and Attachment capability.
+ * @BD_NVME_CTRL_FEAT_SELFTEST: indicates that the controller supports the Device Self-test command.
+ * @BD_NVME_CTRL_FEAT_SELFTEST_SINGLE: indicates that the NVM subsystem supports only one device self-test operation in progress at a time.
+ * @BD_NVME_CTRL_FEAT_SANITIZE_CRYPTO: indicates that the controller supports the Crypto Erase sanitize operation.
+ * @BD_NVME_CTRL_FEAT_SANITIZE_BLOCK: indicates that the controller supports the Block Erase sanitize operation.
+ * @BD_NVME_CTRL_FEAT_SANITIZE_OVERWRITE: indicates that the controller supports the Overwrite sanitize operation.
+ * @BD_NVME_CTRL_FEAT_SECURE_ERASE_ALL_NS: if set, then any secure erase performed as part of a format operation
+ *                                         results in a secure erase of all namespaces in the NVM subsystem. If not set,
+ *                                         then any secure erase performed as part of a format results in a secure erase
+ *                                         of the particular namespace specified.
+ * @BD_NVME_CTRL_FEAT_SECURE_ERASE_CRYPTO: indicates that the cryptographic erase is supported.
+ * @BD_NVME_CTRL_FEAT_STORAGE_DEVICE: indicates that the NVM subsystem is part of an NVMe Storage Device.
+ * @BD_NVME_CTRL_FEAT_ENCLOSURE: indicates that the NVM subsystem is part of an NVMe Enclosure.
+ * @BD_NVME_CTRL_FEAT_MGMT_PCIE: indicates that the NVM subsystem contains a Management Endpoint on a PCIe port.
+ * @BD_NVME_CTRL_FEAT_MGMT_SMBUS: indicates that the NVM subsystem contains a Management Endpoint on an SMBus/I2C port.
+ */
+/* BpG-skip-end */
+typedef enum {
+    BD_NVME_CTRL_FEAT_MULTIPORT           = 1 << 0,
+    BD_NVME_CTRL_FEAT_MULTICTRL           = 1 << 1,
+    BD_NVME_CTRL_FEAT_SRIOV               = 1 << 2,
+    BD_NVME_CTRL_FEAT_ANA_REPORTING       = 1 << 3,
+    BD_NVME_CTRL_FEAT_FORMAT              = 1 << 4,
+    BD_NVME_CTRL_FEAT_FORMAT_ALL_NS       = 1 << 5,
+    BD_NVME_CTRL_FEAT_NS_MGMT             = 1 << 6,
+    BD_NVME_CTRL_FEAT_SELFTEST            = 1 << 7,
+    BD_NVME_CTRL_FEAT_SELFTEST_SINGLE     = 1 << 8,
+    BD_NVME_CTRL_FEAT_SANITIZE_CRYPTO     = 1 << 9,
+    BD_NVME_CTRL_FEAT_SANITIZE_BLOCK      = 1 << 10,
+    BD_NVME_CTRL_FEAT_SANITIZE_OVERWRITE  = 1 << 11,
+    BD_NVME_CTRL_FEAT_SECURE_ERASE_ALL_NS = 1 << 12,
+    BD_NVME_CTRL_FEAT_SECURE_ERASE_CRYPTO = 1 << 13,
+    BD_NVME_CTRL_FEAT_STORAGE_DEVICE      = 1 << 14,
+    BD_NVME_CTRL_FEAT_ENCLOSURE           = 1 << 15,
+    BD_NVME_CTRL_FEAT_MGMT_PCIE           = 1 << 16,
+    BD_NVME_CTRL_FEAT_MGMT_SMBUS          = 1 << 17,
+} BDNVMEControllerFeature;
+
+/* BpG-skip */
+/**
+ * BDNVMEControllerType:
+ * @BD_NVME_CTRL_TYPE_UNKNOWN: Controller type not reported (as reported by older NVMe-compliant devices).
+ * @BD_NVME_CTRL_TYPE_IO: I/O controller.
+ * @BD_NVME_CTRL_TYPE_DISCOVERY: Discovery controller.
+ * @BD_NVME_CTRL_TYPE_ADMIN: Administrative controller.
+ */
+/* BpG-skip-end */
+typedef enum {
+    BD_NVME_CTRL_TYPE_UNKNOWN = 0,
+    BD_NVME_CTRL_TYPE_IO,
+    BD_NVME_CTRL_TYPE_DISCOVERY,
+    BD_NVME_CTRL_TYPE_ADMIN,
+} BDNVMEControllerType;
+
+#define BD_NVME_TYPE_CONTROLLER_INFO (bd_nvme_controller_info_get_type ())
+GType bd_nvme_controller_info_get_type ();
+
+/**
+ * BDNVMEControllerInfo:
+ * @pci_vendor_id: The PCI Vendor ID.
+ * @pci_subsys_vendor_id: The PCI Subsystem Vendor ID.
+ * @ctrl_id: Controller ID, the NVM subsystem unique controller identifier associated with the controller.
+ * @fguid: FRU GUID, a 128-bit value that is globally unique for a given Field Replaceable Unit.
+ * @model_number: The model number.
+ * @serial_number: The serial number.
+ * @firmware_ver: The currently active firmware revision.
+ * @nvme_ver: The NVM Express base specification that the controller implementation supports.
+ * @features: features and capabilities present for this controller, see #BDNVMEControllerFeature.
+ * @controller_type: The controller type.
+ * @selftest_ext_time: Extended Device Self-test Time, if #BD_NVME_CTRL_FEAT_SELFTEST is supported then this field
+ *                     indicates the nominal amount of time in one minute units that the controller takes
+ *                     to complete an extended device self-test operation when in power state 0.
+ * @hmb_pref_size: Host Memory Buffer Preferred Size indicates the preferred size that the host
+ *                 is requested to allocate for the Host Memory Buffer feature in bytes.
+ * @hmb_min_size: Host Memory Buffer Minimum Size indicates the minimum size that the host
+ *                is requested to allocate for the Host Memory Buffer feature in bytes.
+ * @size_total: Total NVM Capacity in the NVM subsystem in bytes.
+ * @size_unalloc: Unallocated NVM Capacity in the NVM subsystem in bytes.
+ * @num_namespaces: Maximum Number of Allowed Namespaces supported by the NVM subsystem.
+ * @subsysnqn: NVM Subsystem NVMe Qualified Name, UTF-8 null terminated string.
+ */
+typedef struct BDNVMEControllerInfo {
+    guint16 pci_vendor_id;
+    guint16 pci_subsys_vendor_id;
+    guint16 ctrl_id;
+    gchar *fguid;
+    gchar *model_number;
+    gchar *serial_number;
+    gchar *firmware_ver;
+    gchar *nvme_ver;
+    guint64 features;
+    BDNVMEControllerType controller_type;
+    gint selftest_ext_time;
+    guint64 hmb_pref_size;
+    guint64 hmb_min_size;
+    guint64 size_total;
+    guint64 size_unalloc;
+    guint num_namespaces;
+    gchar *subsysnqn;
+} BDNVMEControllerInfo;
+
+/**
+ * bd_nvme_controller_info_free: (skip)
+ * @info: (nullable): %BDNVMEControllerInfo to free
+ *
+ * Frees @info.
+ */
+void bd_nvme_controller_info_free (BDNVMEControllerInfo *info) {
+    if (info == NULL)
+        return;
+
+    g_free (info->fguid);
+    g_free (info->subsysnqn);
+    g_free (info->model_number);
+    g_free (info->serial_number);
+    g_free (info->firmware_ver);
+    g_free (info->nvme_ver);
+    g_free (info);
+}
+
+/**
+ * bd_nvme_controller_info_copy: (skip)
+ * @info: (nullable): %BDNVMEControllerInfo to copy
+ *
+ * Creates a new copy of @info.
+ */
+BDNVMEControllerInfo * bd_nvme_controller_info_copy (BDNVMEControllerInfo *info) {
+    BDNVMEControllerInfo *new_info;
+
+    if (info == NULL)
+        return NULL;
+
+    new_info = g_new0 (BDNVMEControllerInfo, 1);
+    memcpy (new_info, info, sizeof (BDNVMEControllerInfo));
+    new_info->fguid = g_strdup (info->fguid);
+    new_info->subsysnqn = g_strdup (info->subsysnqn);
+    new_info->model_number = g_strdup (info->model_number);
+    new_info->serial_number = g_strdup (info->serial_number);
+    new_info->firmware_ver = g_strdup (info->firmware_ver);
+    new_info->nvme_ver = g_strdup (info->nvme_ver);
+
+    return new_info;
+}
+
+GType bd_nvme_controller_info_get_type () {
+    static GType type = 0;
+
+    if (G_UNLIKELY (type == 0)) {
+        type = g_boxed_type_register_static ("BDNVMEControllerInfo",
+                                             (GBoxedCopyFunc) bd_nvme_controller_info_copy,
+                                             (GBoxedFreeFunc) bd_nvme_controller_info_free);
+    }
+    return type;
+}
+
+
+/* BpG-skip */
+/**
+ * BDNVMELBAFormatRelativePerformance:
+ * Performance index of the LBA format relative to other LBA formats supported by the controller.
+ * @BD_NVME_LBA_FORMAT_RELATIVE_PERFORMANCE_UNKNOWN: Unknown relative performance index.
+ * @BD_NVME_LBA_FORMAT_RELATIVE_PERFORMANCE_BEST: Best performance.
+ * @BD_NVME_LBA_FORMAT_RELATIVE_PERFORMANCE_BETTER: Better performance.
+ * @BD_NVME_LBA_FORMAT_RELATIVE_PERFORMANCE_GOOD: Good performance.
+ * @BD_NVME_LBA_FORMAT_RELATIVE_PERFORMANCE_DEGRADED: Degraded performance.
+ */
+/* BpG-skip-end */
+typedef enum {
+    BD_NVME_LBA_FORMAT_RELATIVE_PERFORMANCE_UNKNOWN = 0,
+    BD_NVME_LBA_FORMAT_RELATIVE_PERFORMANCE_BEST = 1,
+    BD_NVME_LBA_FORMAT_RELATIVE_PERFORMANCE_BETTER = 2,
+    BD_NVME_LBA_FORMAT_RELATIVE_PERFORMANCE_GOOD = 3,
+    BD_NVME_LBA_FORMAT_RELATIVE_PERFORMANCE_DEGRADED = 4
+} BDNVMELBAFormatRelativePerformance;
+
+#define BD_NVME_TYPE_LBA_FORMAT (bd_nvme_lba_format_get_type ())
+GType bd_nvme_lba_format_get_type ();
+
+/**
+ * BDNVMELBAFormat:
+ * Namespace LBA Format Data Structure.
+ * @data_size: LBA data size (i.e. a sector size) in bytes.
+ * @metadata_size: metadata size in bytes or `0` in case of no metadata support.
+ * @relative_performance: Relative Performance index, see #BDNVMELBAFormatRelativePerformance.
+ */
+typedef struct BDNVMELBAFormat {
+    guint16 data_size;
+    guint16 metadata_size;
+    BDNVMELBAFormatRelativePerformance relative_performance;
+} BDNVMELBAFormat;
+
+/**
+ * bd_nvme_lba_format_free: (skip)
+ * @fmt: (nullable): %BDNVMELBAFormat to free
+ *
+ * Frees @fmt.
+ */
+void bd_nvme_lba_format_free (BDNVMELBAFormat *fmt) {
+    g_free (fmt);
+}
+
+/**
+ * bd_nvme_lba_format_copy: (skip)
+ * @fmt: (nullable): %BDNVMELBAFormat to copy
+ *
+ * Creates a new copy of @fmt.
+ */
+BDNVMELBAFormat * bd_nvme_lba_format_copy (BDNVMELBAFormat *fmt) {
+    BDNVMELBAFormat *new_fmt;
+
+    if (fmt == NULL)
+        return NULL;
+
+    new_fmt = g_new0 (BDNVMELBAFormat, 1);
+    new_fmt->data_size = fmt->data_size;
+    new_fmt->metadata_size = fmt->metadata_size;
+    new_fmt->relative_performance = fmt->relative_performance;
+
+    return new_fmt;
+}
+
+GType bd_nvme_lba_format_get_type () {
+    static GType type = 0;
+
+    if (G_UNLIKELY (type == 0)) {
+        type = g_boxed_type_register_static ("BDNVMELBAFormat",
+                                             (GBoxedCopyFunc) bd_nvme_lba_format_copy,
+                                             (GBoxedFreeFunc) bd_nvme_lba_format_free);
+    }
+    return type;
+}
+
+/* BpG-skip */
+/**
+ * BDNVMENamespaceFeature:
+ * @BD_NVME_NS_FEAT_THIN: indicates that the namespace supports thin provisioning. Specifically, the Namespace Capacity
+ *                        reported may be less than the Namespace Size.
+ * @BD_NVME_NS_FEAT_MULTIPATH_SHARED: indicates the capability to attach the namespace to two or more controllers
+ *                                    in the NVM subsystem concurrently.
+ * @BD_NVME_NS_FEAT_FORMAT_PROGRESS: indicates the capability to report the percentage of the namespace
+ *                                   that remains to be formatted.
+ */
+/* BpG-skip-end */
+typedef enum {
+    BD_NVME_NS_FEAT_THIN             = 1 << 0,
+    BD_NVME_NS_FEAT_MULTIPATH_SHARED = 1 << 1,
+    BD_NVME_NS_FEAT_FORMAT_PROGRESS  = 1 << 2,
+} BDNVMENamespaceFeature;
+
+#define BD_NVME_TYPE_NAMESPACE_INFO (bd_nvme_namespace_info_get_type ())
+GType bd_nvme_namespace_info_get_type ();
+
+/**
+ * BDNVMENamespaceInfo:
+ * @nsid: The Namespace Identifier (NSID).
+ * @eui64: IEEE Extended Unique Identifier: a 64-bit IEEE Extended Unique Identifier (EUI-64)
+ *         that is globally unique and assigned to the namespace when the namespace is created.
+ *         Remains fixed throughout the life of the namespace and is preserved across namespace
+ *         and controller operations.
+ * @nguid: Namespace Globally Unique Identifier: a 128-bit value that is globally unique and
+ *         assigned to the namespace when the namespace is created. Remains fixed throughout
+ *         the life of the namespace and is preserved across namespace and controller operations.
+ * @uuid: Namespace 128-bit Universally Unique Identifier (UUID) as specified in RFC 4122.
+ * @nsize: Namespace Size: total size of the namespace in logical blocks. The number of logical blocks
+ *         is based on the formatted LBA size (see @current_lba_format).
+ * @ncap: Namespace Capacity: maximum number of logical blocks that may be allocated in the namespace
+ *        at any point in time. The number of logical blocks is based on the formatted LBA size (see @current_lba_format).
+ * @nuse: Namespace Utilization: current number of logical blocks allocated in the namespace.
+ *        This field is smaller than or equal to the Namespace Capacity. The number of logical
+ *        blocks is based on the formatted LBA size (see @current_lba_format).
+ * @features: features and capabilities present for this namespace, see #BDNVMENamespaceFeature.
+ * @format_progress_remaining: The percentage value remaining of a format operation in progress.
+ * @write_protected: %TRUE if the namespace is currently write protected and all write access to the namespace shall fail.
+ * @lba_formats: (array zero-terminated=1) (element-type BDNVMELBAFormat): A list of supported LBA Formats.
+ * @current_lba_format: A LBA Format currently used for the namespace. Contains zeroes in case of
+ *                      an invalid or no supported LBA Format reported.
+ */
+typedef struct BDNVMENamespaceInfo {
+    guint32 nsid;
+    gchar *eui64;
+    gchar *uuid;
+    gchar *nguid;
+    guint64 nsize;
+    guint64 ncap;
+    guint64 nuse;
+    guint64 features;
+    guint8 format_progress_remaining;
+    gboolean write_protected;
+    BDNVMELBAFormat **lba_formats;
+    BDNVMELBAFormat current_lba_format;
+} BDNVMENamespaceInfo;
+
+/**
+ * bd_nvme_namespace_info_free: (skip)
+ * @info: (nullable): %BDNVMENamespaceInfo to free
+ *
+ * Frees @info.
+ */
+void bd_nvme_namespace_info_free (BDNVMENamespaceInfo *info) {
+    BDNVMELBAFormat **lba_formats;
+
+    if (info == NULL)
+        return;
+
+    g_free (info->eui64);
+    g_free (info->uuid);
+    g_free (info->nguid);
+
+    for (lba_formats = info->lba_formats; lba_formats && *lba_formats; lba_formats++)
+        bd_nvme_lba_format_free (*lba_formats);
+    g_free (info->lba_formats);
+    g_free (info);
+}
+
+/**
+ * bd_nvme_namespace_info_copy: (skip)
+ * @info: (nullable): %BDNVMENamespaceInfo to copy
+ *
+ * Creates a new copy of @info.
+ */
+BDNVMENamespaceInfo * bd_nvme_namespace_info_copy (BDNVMENamespaceInfo *info) {
+    BDNVMENamespaceInfo *new_info;
+    BDNVMELBAFormat **lba_formats;
+    GPtrArray *ptr_array;
+
+    if (info == NULL)
+        return NULL;
+
+    new_info = g_new0 (BDNVMENamespaceInfo, 1);
+    memcpy (new_info, info, sizeof (BDNVMENamespaceInfo));
+    new_info->eui64 = g_strdup (info->eui64);
+    new_info->uuid = g_strdup (info->uuid);
+    new_info->nguid = g_strdup (info->nguid);
+
+    ptr_array = g_ptr_array_new ();
+    for (lba_formats = info->lba_formats; lba_formats && *lba_formats; lba_formats++)
+        g_ptr_array_add (ptr_array, bd_nvme_lba_format_copy (*lba_formats));
+    g_ptr_array_add (ptr_array, NULL);
+    new_info->lba_formats = (BDNVMELBAFormat **) g_ptr_array_free (ptr_array, FALSE);
+
+    return new_info;
+}
+
+GType bd_nvme_namespace_info_get_type () {
+    static GType type = 0;
+
+    if (G_UNLIKELY (type == 0)) {
+        type = g_boxed_type_register_static ("BDNVMENamespaceInfo",
+                                             (GBoxedCopyFunc) bd_nvme_namespace_info_copy,
+                                             (GBoxedFreeFunc) bd_nvme_namespace_info_free);
+    }
+    return type;
+}
+
+
+/* BpG-skip */
+/**
+ * BDNVMESmartCriticalWarning:
+ * @BD_NVME_SMART_CRITICAL_WARNING_SPARE: the available spare capacity has fallen below the threshold.
+ * @BD_NVME_SMART_CRITICAL_WARNING_TEMPERATURE: a temperature is either greater than or equal to an over temperature threshold;
+ *                                              or less than or equal to an under temperature threshold.
+ * @BD_NVME_SMART_CRITICAL_WARNING_DEGRADED: the NVM subsystem reliability has been degraded due to  significant media
+ *                                           related errors or any internal error that degrades NVM subsystem reliability.
+ * @BD_NVME_SMART_CRITICAL_WARNING_READONLY: all of the media has been placed in read only mode. Unrelated to the write
+ *                                           protection state of a namespace.
+ * @BD_NVME_SMART_CRITICAL_WARNING_VOLATILE_MEM: the volatile memory backup device has failed. Valid only if the controller
+ *                                               has a volatile memory backup solution.
+ * @BD_NVME_SMART_CRITICAL_WARNING_PMR_READONLY: Persistent Memory Region has become read-only or unreliable.
+ */
+/* BpG-skip-end */
+typedef enum {
+    BD_NVME_SMART_CRITICAL_WARNING_SPARE        = 1 << 0,
+    BD_NVME_SMART_CRITICAL_WARNING_TEMPERATURE  = 1 << 1,
+    BD_NVME_SMART_CRITICAL_WARNING_DEGRADED     = 1 << 2,
+    BD_NVME_SMART_CRITICAL_WARNING_READONLY     = 1 << 3,
+    BD_NVME_SMART_CRITICAL_WARNING_VOLATILE_MEM = 1 << 4,
+    BD_NVME_SMART_CRITICAL_WARNING_PMR_READONLY = 1 << 5,
+} BDNVMESmartCriticalWarning;
+
+#define BD_NVME_TYPE_SMART_LOG (bd_nvme_smart_log_get_type ())
+GType bd_nvme_smart_log_get_type ();
+
+/**
+ * BDNVMESmartLog:
+ * @critical_warning: critical warnings for the state of the controller, see #BDNVMESmartCriticalWarning.
+ * @avail_spare: Available Spare: a normalized percentage (0% to 100%) of the remaining spare capacity available.
+ * @spare_thresh: Available Spare Threshold: a normalized percentage (0% to 100%) of the available spare threshold.
+ * @percent_used: Percentage Used: a vendor specific estimate of the percentage drive life used based on the
+ *                actual usage and the manufacturer's prediction. A value of 100 indicates that the estimated
+ *                endurance has been consumed, but may not indicate an NVM subsystem failure.
+ *                The value is allowed to exceed 100.
+ * @total_data_read: An estimated calculation of total data read in bytes based on calculation of data
+ *                   units read from the host. A value of 0 indicates that the number of Data Units Read
+ *                   is not reported.
+ * @total_data_written: An estimated calculation of total data written in bytes based on calculation
+ *                      of data units written by the host. A value of 0 indicates that the number
+ *                      of Data Units Written is not reported.
+ * @ctrl_busy_time: Amount of time the controller is busy with I/O commands, reported in minutes.
+ * @power_cycles: The number of power cycles.
+ * @power_on_hours: The number of power-on hours, excluding a non-operational power state.
+ * @unsafe_shutdowns: The number of unsafe shutdowns as a result of a Shutdown Notification not received prior to loss of power.
+ * @media_errors: Media and Data Integrity Errors: the number of occurrences where the controller detected
+ *                an unrecovered data integrity error (e.g. uncorrectable ECC, CRC checksum failure, or LBA tag mismatch).
+ * @num_err_log_entries: Number of Error Information Log Entries: the number of Error Information log
+ *                       entries over the life of the controller.
+ * @temperature: Composite Temperature: temperature in Kelvins that represents the current composite
+ *               temperature of the controller and associated namespaces or 0 when not applicable.
+ * @temp_sensors: Temperature Sensor 1-8: array of the current temperature reported by temperature sensors
+ *                1-8 in Kelvins or 0 when the particular sensor is not available.
+ * @wctemp: Warning Composite Temperature Threshold (WCTEMP): indicates the minimum Composite Temperature (@temperature)
+ *          value that indicates an overheating condition during which controller operation continues.
+ *          A value of 0 indicates that no warning temperature threshold value is reported by the controller.
+ * @cctemp: Critical Composite Temperature Threshold (CCTEMP): indicates the minimum Composite Temperature (@temperature)
+ *          value that indicates a critical overheating condition (e.g., may prevent continued normal operation,
+ *          possibility of data loss, automatic device shutdown, extreme performance throttling, or permanent damage).
+ *          A value of 0 indicates that no critical temperature threshold value is reported by the controller.
+ * @warning_temp_time: Warning Composite Temperature Time: the amount of time in minutes that the Composite Temperature (@temperature)
+ *                     is greater than or equal to the Warning Composite Temperature Threshold (@wctemp) and less than the
+ *                     Critical Composite Temperature Threshold (@cctemp).
+ * @critical_temp_time: Critical Composite Temperature Time: the amount of time in minutes that the Composite Temperature (@temperature)
+ *                      is greater than or equal to the Critical Composite Temperature Threshold (@cctemp).
+ */
+typedef struct BDNVMESmartLog {
+    guint critical_warning;
+    guint8 avail_spare;
+    guint8 spare_thresh;
+    guint8 percent_used;
+    guint64 total_data_read;
+    guint64 total_data_written;
+    guint64 ctrl_busy_time;
+    guint64 power_cycles;
+    guint64 power_on_hours;
+    guint64 unsafe_shutdowns;
+    guint64 media_errors;
+    guint64 num_err_log_entries;
+    guint16 temperature;
+    guint16 temp_sensors[8];
+    guint16 wctemp;
+    guint16 cctemp;
+    guint warning_temp_time;
+    guint critical_temp_time;
+} BDNVMESmartLog;
+
+/**
+ * bd_nvme_smart_log_free: (skip)
+ * @log: (nullable): %BDNVMESmartLog to free
+ *
+ * Frees @log.
+ */
+void bd_nvme_smart_log_free (BDNVMESmartLog *log) {
+    g_free (log);
+}
+
+/**
+ * bd_nvme_smart_log_copy: (skip)
+ * @log: (nullable): %BDNVMESmartLog to copy
+ *
+ * Creates a new copy of @log.
+ */
+BDNVMESmartLog * bd_nvme_smart_log_copy (BDNVMESmartLog *log) {
+    BDNVMESmartLog *new_log;
+
+    if (log == NULL)
+        return NULL;
+
+    new_log = g_new0 (BDNVMESmartLog, 1);
+    memcpy (new_log, log, sizeof (BDNVMESmartLog));
+
+    return new_log;
+}
+
+GType bd_nvme_smart_log_get_type () {
+    static GType type = 0;
+
+    if (G_UNLIKELY (type == 0)) {
+        type = g_boxed_type_register_static ("BDNVMESmartLog",
+                                             (GBoxedCopyFunc) bd_nvme_smart_log_copy,
+                                             (GBoxedFreeFunc) bd_nvme_smart_log_free);
+    }
+    return type;
+}
+
+
+/* BpG-skip */
+/**
+ * BDNVMETransportType:
+ * Transport Type.
+ * @BD_NVME_TRANSPORT_TYPE_UNSPECIFIED: Not indicated
+ * @BD_NVME_TRANSPORT_TYPE_RDMA: RDMA Transport
+ * @BD_NVME_TRANSPORT_TYPE_FC: Fibre Channel Transport
+ * @BD_NVME_TRANSPORT_TYPE_TCP: TCP Transport
+ * @BD_NVME_TRANSPORT_TYPE_LOOP: Intra-host Transport (loopback)
+ */
+/* BpG-skip-end */
+typedef enum {
+    BD_NVME_TRANSPORT_TYPE_UNSPECIFIED = 0,
+    BD_NVME_TRANSPORT_TYPE_RDMA        = 1,
+    BD_NVME_TRANSPORT_TYPE_FC          = 2,
+    BD_NVME_TRANSPORT_TYPE_TCP         = 3,
+    BD_NVME_TRANSPORT_TYPE_LOOP        = 254
+} BDNVMETransportType;
+
+/* BpG-skip */
+/**
+ * BDNVMEAddressFamily:
+ * Address Family.
+ * @BD_NVME_ADDRESS_FAMILY_PCI: PCI Express.
+ * @BD_NVME_ADDRESS_FAMILY_INET: AF_INET: IPv4 address family.
+ * @BD_NVME_ADDRESS_FAMILY_INET6: AF_INET6: IPv6 address family.
+ * @BD_NVME_ADDRESS_FAMILY_IB: AF_IB: InfiniBand address family.
+ * @BD_NVME_ADDRESS_FAMILY_FC: Fibre Channel address family.
+ * @BD_NVME_ADDRESS_FAMILY_LOOP: Intra-host Transport (loopback).
+ */
+/* BpG-skip-end */
+typedef enum {
+    BD_NVME_ADDRESS_FAMILY_PCI   = 0,
+    BD_NVME_ADDRESS_FAMILY_INET  = 1,
+    BD_NVME_ADDRESS_FAMILY_INET6 = 2,
+    BD_NVME_ADDRESS_FAMILY_IB    = 3,
+    BD_NVME_ADDRESS_FAMILY_FC    = 4,
+    BD_NVME_ADDRESS_FAMILY_LOOP  = 254
+} BDNVMEAddressFamily;
+
+#define BD_NVME_TYPE_ERROR_LOG_ENTRY (bd_nvme_error_log_entry_get_type ())
+GType bd_nvme_error_log_entry_get_type ();
+
+/**
+ * BDNVMEErrorLogEntry:
+ * @error_count: internal error counter, a unique identifier for the error.
+ * @command_id: the Command Identifier of the command that the error is associated with or `0xffff` if the error is not specific to a particular command.
+ * @command_specific: Command Specific Information specific to @command_id.
+ * @command_status: the Status code for the command that completed.
+ * @command_error: translated command error in the BD_NVME_ERROR domain or %NULL in case @command_status indicates success.
+ * @lba: the first LBA that experienced the error condition.
+ * @nsid: the NSID of the namespace that the error is associated with.
+ * @transport_type: type of the transport associated with the error.
+ */
+typedef struct BDNVMEErrorLogEntry {
+    guint64 error_count;
+    guint16 command_id;
+    guint64 command_specific;
+    guint16 command_status;
+    GError *command_error;
+    guint64 lba;
+    guint32 nsid;
+    BDNVMETransportType transport_type;
+} BDNVMEErrorLogEntry;
+
+/**
+ * bd_nvme_error_log_entry_free: (skip)
+ * @entry: (nullable): %BDNVMEErrorLogEntry to free
+ *
+ * Frees @entry.
+ */
+void bd_nvme_error_log_entry_free (BDNVMEErrorLogEntry *entry) {
+    if (entry == NULL)
+        return;
+
+    if (entry->command_error)
+        g_error_free (entry->command_error);
+    g_free (entry);
+}
+
+/**
+ * bd_nvme_error_log_entry_copy: (skip)
+ * @entry: (nullable): %BDNVMEErrorLogEntry to copy
+ *
+ * Creates a new copy of @entry.
+ */
+BDNVMEErrorLogEntry * bd_nvme_error_log_entry_copy (BDNVMEErrorLogEntry *entry) {
+    BDNVMEErrorLogEntry *new_entry;
+
+    if (entry == NULL)
+        return NULL;
+
+    new_entry = g_new0 (BDNVMEErrorLogEntry, 1);
+    memcpy (new_entry, entry, sizeof (BDNVMEErrorLogEntry));
+    if (entry->command_error)
+        new_entry->command_error = g_error_copy (entry->command_error);
+
+    return new_entry;
+}
+
+GType bd_nvme_error_log_entry_get_type () {
+    static GType type = 0;
+
+    if (G_UNLIKELY (type == 0)) {
+        type = g_boxed_type_register_static ("BDNVMEErrorLogEntry",
+                                             (GBoxedCopyFunc) bd_nvme_error_log_entry_copy,
+                                             (GBoxedFreeFunc) bd_nvme_error_log_entry_free);
+    }
+    return type;
+}
+
+
+/* BpG-skip */
+/**
+ * BDNVMESelfTestAction:
+ * Action taken by the Device Self-test command.
+ * @BD_NVME_SELF_TEST_ACTION_NOT_RUNNING: No device self-test operation in progress. Not a valid argument for bd_nvme_device_self_test().
+ * @BD_NVME_SELF_TEST_ACTION_SHORT: Start a short device self-test operation.
+ * @BD_NVME_SELF_TEST_ACTION_EXTENDED: Start an extended device self-test operation.
+ * @BD_NVME_SELF_TEST_ACTION_VENDOR_SPECIFIC: Start a vendor specific device self-test operation.
+ * @BD_NVME_SELF_TEST_ACTION_ABORT: Abort the device self-test operation. Only valid for bd_nvme_device_self_test().
+ */
+/* BpG-skip-end */
+typedef enum {
+    BD_NVME_SELF_TEST_ACTION_NOT_RUNNING     = 0,
+    BD_NVME_SELF_TEST_ACTION_SHORT           = 1,
+    BD_NVME_SELF_TEST_ACTION_EXTENDED        = 2,
+    BD_NVME_SELF_TEST_ACTION_VENDOR_SPECIFIC = 3,
+    BD_NVME_SELF_TEST_ACTION_ABORT           = 4,
+} BDNVMESelfTestAction;
+
+/* BpG-skip */
+/**
+ * BDNVMESelfTestResult:
+ * Self-test log entry result value.
+ * @BD_NVME_SELF_TEST_RESULT_NO_ERROR: Operation completed without error.
+ * @BD_NVME_SELF_TEST_RESULT_ABORTED: Operation was aborted by a Device Self-test command.
+ * @BD_NVME_SELF_TEST_RESULT_CTRL_RESET: Operation was aborted by a Controller Level Reset.
+ * @BD_NVME_SELF_TEST_RESULT_NS_REMOVED: Operation was aborted due to a removal of a namespace from the namespace inventory.
+ * @BD_NVME_SELF_TEST_RESULT_ABORTED_FORMAT: Operation was aborted due to the processing of a Format NVM command.
+ * @BD_NVME_SELF_TEST_RESULT_FATAL_ERROR: A fatal error or unknown test error occurred while the controller was executing the device self-test operation and the operation did not complete.
+ * @BD_NVME_SELF_TEST_RESULT_UNKNOWN_SEG_FAIL: Operation completed with a segment that failed and the segment that failed is not known.
+ * @BD_NVME_SELF_TEST_RESULT_KNOWN_SEG_FAIL: Operation completed with one or more failed segments and the first segment that failed is indicated in the Segment Number field.
+ * @BD_NVME_SELF_TEST_RESULT_ABORTED_UNKNOWN: Operation was aborted for unknown reason.
+ * @BD_NVME_SELF_TEST_RESULT_ABORTED_SANITIZE: Operation was aborted due to a sanitize operation.
+ */
+/* BpG-skip-end */
+typedef enum {
+    BD_NVME_SELF_TEST_RESULT_NO_ERROR         = 0,
+    BD_NVME_SELF_TEST_RESULT_ABORTED          = 1,
+    BD_NVME_SELF_TEST_RESULT_CTRL_RESET       = 2,
+    BD_NVME_SELF_TEST_RESULT_NS_REMOVED       = 3,
+    BD_NVME_SELF_TEST_RESULT_ABORTED_FORMAT   = 4,
+    BD_NVME_SELF_TEST_RESULT_FATAL_ERROR      = 5,
+    BD_NVME_SELF_TEST_RESULT_UNKNOWN_SEG_FAIL = 6,
+    BD_NVME_SELF_TEST_RESULT_KNOWN_SEG_FAIL   = 7,
+    BD_NVME_SELF_TEST_RESULT_ABORTED_UNKNOWN  = 8,
+    BD_NVME_SELF_TEST_RESULT_ABORTED_SANITIZE = 9,
+} BDNVMESelfTestResult;
+
+/**
+ * bd_nvme_self_test_result_to_string:
+ * @result: A %BDNVMESelfTestResult.
+ * @error: (out) (optional): place to store error (if any)
+ *
+ * Returns: (transfer none): A string representation of @result for use as an identifier string
+ *                           or %NULL when the code is unknown.
+ */
+const gchar * bd_nvme_self_test_result_to_string (BDNVMESelfTestResult result, GError **error);
+
+#define BD_NVME_TYPE_SELF_TEST_LOG_ENTRY (bd_nvme_self_test_log_entry_get_type ())
+GType bd_nvme_self_test_log_entry_get_type ();
+
+/**
+ * BDNVMESelfTestLogEntry:
+ * @result: Result of the device self-test operation.
+ * @action: The Self-test Code value (action) that was specified in the Device Self-test command that started this device self-test operation.
+ * @segment: Segment number where the first self-test failure occurred. Valid only when @result is set to #BD_NVME_SELF_TEST_RESULT_KNOWN_SEG_FAIL.
+ * @power_on_hours: Number of power-on hours at the time the device self-test operation was completed or aborted. Does not include time that the controller was powered and in a low power state condition.
+ * @nsid: Namespace ID that the Failing LBA occurred on.
+ * @failing_lba: LBA of the logical block that caused the test to fail. If the device encountered more than one failed logical block during the test, then this field only indicates one of those failed logical blocks.
+ * @status_code_error: Translated NVMe Command Status Code representing additional information related to errors or conditions.
+ */
+typedef struct BDNVMESelfTestLogEntry {
+    BDNVMESelfTestResult result;
+    BDNVMESelfTestAction action;
+    guint8 segment;
+    guint64 power_on_hours;
+    guint32 nsid;
+    guint64 failing_lba;
+    GError *status_code_error;
+} BDNVMESelfTestLogEntry;
+
+/**
+ * bd_nvme_self_test_log_entry_free: (skip)
+ * @entry: (nullable): %BDNVMESelfTestLogEntry to free
+ *
+ * Frees @entry.
+ */
+void bd_nvme_self_test_log_entry_free (BDNVMESelfTestLogEntry *entry) {
+    if (entry == NULL)
+        return;
+
+    if (entry->status_code_error)
+        g_error_free (entry->status_code_error);
+    g_free (entry);
+}
+
+/**
+ * bd_nvme_self_test_log_entry_copy: (skip)
+ * @entry: (nullable): %BDNVMESelfTestLogEntry to copy
+ *
+ * Creates a new copy of @entry.
+ */
+BDNVMESelfTestLogEntry * bd_nvme_self_test_log_entry_copy (BDNVMESelfTestLogEntry *entry) {
+    BDNVMESelfTestLogEntry *new_entry;
+
+    if (entry == NULL)
+        return NULL;
+
+    new_entry = g_new0 (BDNVMESelfTestLogEntry, 1);
+    memcpy (new_entry, entry, sizeof (BDNVMESelfTestLogEntry));
+    if (entry->status_code_error)
+        new_entry->status_code_error = g_error_copy (entry->status_code_error);
+
+    return new_entry;
+}
+
+GType bd_nvme_self_test_log_entry_get_type () {
+    static GType type = 0;
+
+    if (G_UNLIKELY (type == 0)) {
+        type = g_boxed_type_register_static ("BDNVMESelfTestLogEntry",
+                                             (GBoxedCopyFunc) bd_nvme_self_test_log_entry_copy,
+                                             (GBoxedFreeFunc) bd_nvme_self_test_log_entry_free);
+    }
+    return type;
+}
+
+
+#define BD_NVME_TYPE_SELF_TEST_LOG (bd_nvme_self_test_log_get_type ())
+GType bd_nvme_self_test_log_get_type ();
+
+/**
+ * BDNVMESelfTestLog:
+ * @current_operation: Current running device self-test operation. There's no corresponding record in @entries for a device self-test operation that is in progress.
+ * @current_operation_completion: Percentage of the currently running device self-test operation. Only valid when @current_operation is other than #BD_NVME_SELF_TEST_ACTION_NOT_RUNNING.
+ * @entries: (array zero-terminated=1) (element-type BDNVMESelfTestLogEntry): Self-test log entries for the last 20 operations, sorted from newest (first element) to oldest.
+ */
+typedef struct BDNVMESelfTestLog {
+    BDNVMESelfTestAction current_operation;
+    guint8 current_operation_completion;
+    BDNVMESelfTestLogEntry **entries;
+} BDNVMESelfTestLog;
+
+/**
+ * bd_nvme_self_test_log_free: (skip)
+ * @log: (nullable): %BDNVMESelfTestLog to free
+ *
+ * Frees @log.
+ */
+void bd_nvme_self_test_log_free (BDNVMESelfTestLog *log) {
+    BDNVMESelfTestLogEntry **entries;
+
+    if (log == NULL)
+        return;
+
+    for (entries = log->entries; entries && *entries; entries++)
+        bd_nvme_self_test_log_entry_free (*entries);
+    g_free (log->entries);
+    g_free (log);
+}
+
+/**
+ * bd_nvme_self_test_log_copy: (skip)
+ * @log: (nullable): %BDNVMESelfTestLog to copy
+ *
+ * Creates a new copy of @log.
+ */
+BDNVMESelfTestLog * bd_nvme_self_test_log_copy (BDNVMESelfTestLog *log) {
+    BDNVMESelfTestLog *new_log;
+    BDNVMESelfTestLogEntry **entries;
+    GPtrArray *ptr_array;
+
+    if (log == NULL)
+        return NULL;
+
+    new_log = g_new0 (BDNVMESelfTestLog, 1);
+    memcpy (new_log, log, sizeof (BDNVMESelfTestLog));
+
+    ptr_array = g_ptr_array_new ();
+    for (entries = log->entries; entries && *entries; entries++)
+        g_ptr_array_add (ptr_array, bd_nvme_self_test_log_entry_copy (*entries));
+    g_ptr_array_add (ptr_array, NULL);
+    new_log->entries = (BDNVMESelfTestLogEntry **) g_ptr_array_free (ptr_array, FALSE);
+
+    return new_log;
+}
+
+GType bd_nvme_self_test_log_get_type () {
+    static GType type = 0;
+
+    if (G_UNLIKELY (type == 0)) {
+        type = g_boxed_type_register_static ("BDNVMESelfTestLog",
+                                             (GBoxedCopyFunc) bd_nvme_self_test_log_copy,
+                                             (GBoxedFreeFunc) bd_nvme_self_test_log_free);
+    }
+    return type;
+}
+
+
+/* BpG-skip */
+/**
+ * BDNVMEFormatSecureErase:
+ * Optional Format NVM secure erase action.
+ * @BD_NVME_FORMAT_SECURE_ERASE_NONE: No secure erase operation requested.
+ * @BD_NVME_FORMAT_SECURE_ERASE_USER_DATA: User Data Erase: All user data shall be erased, contents of the user data after the erase is indeterminate
+ *                                         (e.g., the user data may be zero filled, one filled, etc.). If a User Data Erase is requested and all affected
+ *                                         user data is encrypted, then the controller is allowed to use a cryptographic erase to perform the requested User Data Erase.
+ * @BD_NVME_FORMAT_SECURE_ERASE_CRYPTO: Cryptographic Erase: All user data shall be erased cryptographically. This is accomplished by deleting the encryption key.
+ */
+/* BpG-skip-end */
+typedef enum {
+    BD_NVME_FORMAT_SECURE_ERASE_NONE      = 0,
+    BD_NVME_FORMAT_SECURE_ERASE_USER_DATA = 1,
+    BD_NVME_FORMAT_SECURE_ERASE_CRYPTO    = 2,
+} BDNVMEFormatSecureErase;
+
+
+/* BpG-skip */
+/**
+ * BDNVMESanitizeStatus:
+ * @BD_NVME_SANITIZE_STATUS_NEVER_SANITIZED: The NVM subsystem has never been sanitized.
+ * @BD_NVME_SANITIZE_STATUS_IN_PROGESS: A sanitize operation is currently in progress.
+ * @BD_NVME_SANITIZE_STATUS_SUCCESS: The most recent sanitize operation completed successfully including any additional media modification.
+ * @BD_NVME_SANITIZE_STATUS_SUCCESS_NO_DEALLOC: The most recent sanitize operation for which No-Deallocate After Sanitize was requested has completed successfully with deallocation of all user data.
+ * @BD_NVME_SANITIZE_STATUS_FAILED: The most recent sanitize operation failed.
+ */
+/* BpG-skip-end */
+typedef enum {
+    BD_NVME_SANITIZE_STATUS_NEVER_SANITIZED = 0,
+    BD_NVME_SANITIZE_STATUS_IN_PROGESS = 1,
+    BD_NVME_SANITIZE_STATUS_SUCCESS = 2,
+    BD_NVME_SANITIZE_STATUS_SUCCESS_NO_DEALLOC = 3,
+    BD_NVME_SANITIZE_STATUS_FAILED = 4,
+} BDNVMESanitizeStatus;
+
+
+#define BD_NVME_TYPE_SANITIZE_LOG (bd_nvme_sanitize_log_get_type ())
+GType bd_nvme_sanitize_log_get_type ();
+
+/**
+ * BDNVMESanitizeLog:
+ * @sanitize_progress: The percentage complete of the sanitize operation.
+ * @sanitize_status: The status of the most recent sanitize operation.
+ * @global_data_erased: Indicates that no user data has been written either since the drive was manufactured and
+ *                      has never been sanitized or since the most recent successful sanitize operation.
+ * @overwrite_passes: Number of completed passes if the most recent sanitize operation was an Overwrite.
+ * @time_for_overwrite: Estimated time in seconds needed to complete an Overwrite sanitize operation with 16 passes in the background.
+ *                      A value of -1 means that no time estimate is reported. A value of 0 means that the operation is expected
+ *                      to be completed in the background when the Sanitize command is completed.
+ * @time_for_block_erase: Estimated time in seconds needed to complete a Block Erase sanitize operation in the background.
+ *                        A value of -1 means that no time estimate is reported. A value of 0 means that the operation is expected
+ *                        to be completed in the background when the Sanitize command is completed.
+ * @time_for_crypto_erase: Estimated time in seconds needed to complete a Crypto Erase sanitize operation in the background.
+ *                         A value of -1 means that no time estimate is reported. A value of 0 means that the operation is expected
+ *                         to be completed in the background when the Sanitize command is completed.
+ * @time_for_overwrite_nd: Estimated time in seconds needed to complete an Overwrite sanitize operation and the associated
+ *                         additional media modification in the background when the No-Deallocate After Sanitize or
+ *                         the No-Deallocate Modifies Media After Sanitize features have been requested.
+ * @time_for_block_erase_nd: Estimated time in seconds needed to complete a Block Erase sanitize operation and the associated
+ *                           additional media modification in the background when the No-Deallocate After Sanitize or
+ *                           the No-Deallocate Modifies Media After Sanitize features have been requested.
+ * @time_for_crypto_erase_nd: Estimated time in seconds needed to complete a Crypto Erase sanitize operation and the associated
+ *                            additional media modification in the background when the No-Deallocate After Sanitize or
+ *                            the No-Deallocate Modifies Media After Sanitize features have been requested.
+ */
+typedef struct BDNVMESanitizeLog {
+    gdouble sanitize_progress;
+    BDNVMESanitizeStatus sanitize_status;
+    gboolean global_data_erased;
+    guint8 overwrite_passes;
+    gint64 time_for_overwrite;
+    gint64 time_for_block_erase;
+    gint64 time_for_crypto_erase;
+    gint64 time_for_overwrite_nd;
+    gint64 time_for_block_erase_nd;
+    gint64 time_for_crypto_erase_nd;
+} BDNVMESanitizeLog;
+
+/**
+ * bd_nvme_sanitize_log_free: (skip)
+ * @log: (nullable): %BDNVMESanitizeLog to free
+ *
+ * Frees @log.
+ */
+void bd_nvme_sanitize_log_free (BDNVMESanitizeLog *log) {
+    if (log == NULL)
+        return;
+
+    g_free (log);
+}
+
+/**
+ * bd_nvme_sanitize_log_copy: (skip)
+ * @log: (nullable): %BDNVMESanitizeLog to copy
+ *
+ * Creates a new copy of @log.
+ */
+BDNVMESanitizeLog * bd_nvme_sanitize_log_copy (BDNVMESanitizeLog *log) {
+    BDNVMESanitizeLog *new_log;
+
+    if (log == NULL)
+        return NULL;
+
+    new_log = g_new0 (BDNVMESanitizeLog, 1);
+    memcpy (new_log, log, sizeof (BDNVMESanitizeLog));
+
+    return new_log;
+}
+
+GType bd_nvme_sanitize_log_get_type () {
+    static GType type = 0;
+
+    if (G_UNLIKELY (type == 0)) {
+        type = g_boxed_type_register_static ("BDNVMESanitizeLog",
+                                             (GBoxedCopyFunc) bd_nvme_sanitize_log_copy,
+                                             (GBoxedFreeFunc) bd_nvme_sanitize_log_free);
+    }
+    return type;
+}
+
+
+/* BpG-skip */
+/**
+ * BDNVMESanitizeAction:
+ * @BD_NVME_SANITIZE_ACTION_EXIT_FAILURE: Exit Failure Mode.
+ * @BD_NVME_SANITIZE_ACTION_BLOCK_ERASE: Start a Block Erase sanitize operation - a low-level block erase method that is specific to the media.
+ * @BD_NVME_SANITIZE_ACTION_OVERWRITE: Start an Overwrite sanitize operation - writing a fixed data pattern or related patterns in multiple passes.
+ * @BD_NVME_SANITIZE_ACTION_CRYPTO_ERASE: Start a Crypto Erase sanitize operation - changing the media encryption keys for all locations on the media.
+ */
+/* BpG-skip-end */
+typedef enum {
+    BD_NVME_SANITIZE_ACTION_EXIT_FAILURE = 0,
+    BD_NVME_SANITIZE_ACTION_BLOCK_ERASE = 1,
+    BD_NVME_SANITIZE_ACTION_OVERWRITE = 2,
+    BD_NVME_SANITIZE_ACTION_CRYPTO_ERASE = 3,
+} BDNVMESanitizeAction;
+
+
+/* BpG-skip */
+/**
+ * BDNVMETCPSecurity:
+ * @BD_NVME_TCP_SECURITY_NONE: No Security, the host shall set up a normal TCP connection.
+ * @BD_NVME_TCP_SECURITY_TLS12: Transport Layer Security (TLS) version 1.2 (NVMe-oF 1.1).
+ * @BD_NVME_TCP_SECURITY_TLS13: Transport Layer Security (TLS) version 1.3+. The TLS version and cipher is negotiated on every connection.
+ */
+/* BpG-skip-end */
+typedef enum {
+    BD_NVME_TCP_SECURITY_NONE  = 0,
+    BD_NVME_TCP_SECURITY_TLS12 = 1,
+    BD_NVME_TCP_SECURITY_TLS13 = 2
+} BDNVMETCPSecurity;
+
+
+#define BD_NVME_TYPE_DISCOVERY_LOG_ENTRY (bd_nvme_discovery_log_entry_get_type ())
+GType bd_nvme_discovery_log_entry_get_type ();
+
+/**
+ * BDNVMEDiscoveryLogEntry:
+ * @transport_type: The NVMe Transport type.
+ * @address_family: The address family.
+ * @sq_flow_control_disable: Indicates that the controller is capable of disabling SQ flow control.
+ * @sq_flow_control_required: Indicates that the controller requires use of SQ flow control.
+ * @port_id: A NVM subsystem port. Different NVMe Transports or address families may utilize the same Port ID value (eg. a Port ID may support both iWARP and RoCE).
+ * @ctrl_id: A Controller ID. Special value of `0xFFFF` indicates a dynamic controller model and a value of `0xFFFE` indicates a temporary ID in a static controller model that should be replaced by a real ID after a connection is established.
+ * @transport_addr: Transport Address.
+ * @transport_svcid: Transport Service Identifier.
+ * @subsys_nqn: Subsystem Qualified Name. For a Discovery Service the value should be the well-known Discovery Service NQN (`nqn.2014-08.org.nvmexpress.discovery`).
+ * @tcp_security: NVMe/TCP transport port security.
+ */
+typedef struct BDNVMEDiscoveryLogEntry {
+    BDNVMETransportType transport_type;
+    BDNVMEAddressFamily address_family;
+    gboolean sq_flow_control_disable;
+    gboolean sq_flow_control_required;
+    guint16 port_id;
+    guint16 ctrl_id;
+    gchar *transport_addr;
+    gchar *transport_svcid;
+    gchar *subsys_nqn;
+    BDNVMETCPSecurity tcp_security;
+} BDNVMEDiscoveryLogEntry;
+
+/**
+ * bd_nvme_discovery_log_entry_free: (skip)
+ * @entry: (nullable): %BDNVMEDiscoveryLogEntry to free
+ *
+ * Frees @entry.
+ */
+void bd_nvme_discovery_log_entry_free (BDNVMEDiscoveryLogEntry *entry) {
+    if (entry == NULL)
+        return;
+
+    g_free (entry->transport_addr);
+    g_free (entry->transport_svcid);
+    g_free (entry->subsys_nqn);
+    g_free (entry);
+}
+
+/**
+ * bd_nvme_discovery_log_entry_copy: (skip)
+ * @entry: (nullable): %BDNVMEDiscoveryLogEntry to copy
+ *
+ * Creates a new copy of @entry.
+ */
+BDNVMEDiscoveryLogEntry * bd_nvme_discovery_log_entry_copy (BDNVMEDiscoveryLogEntry *entry) {
+    BDNVMEDiscoveryLogEntry *new_entry;
+
+    if (entry == NULL)
+        return NULL;
+
+    new_entry = g_new0 (BDNVMEDiscoveryLogEntry, 1);
+    memcpy (new_entry, entry, sizeof (BDNVMEDiscoveryLogEntry));
+    new_entry->transport_addr = g_strdup (entry->transport_addr);
+    new_entry->transport_svcid = g_strdup (entry->transport_svcid);
+    new_entry->subsys_nqn = g_strdup (entry->subsys_nqn);
+
+    return new_entry;
+}
+
+GType bd_nvme_discovery_log_entry_get_type () {
+    static GType type = 0;
+
+    if (G_UNLIKELY (type == 0)) {
+        type = g_boxed_type_register_static ("BDNVMEDiscoveryLogEntry",
+                                             (GBoxedCopyFunc) bd_nvme_discovery_log_entry_copy,
+                                             (GBoxedFreeFunc) bd_nvme_discovery_log_entry_free);
+    }
+    return type;
+}
+
+
+/**
+ * bd_nvme_get_controller_info:
+ * @device: a NVMe controller device (e.g. `/dev/nvme0`)
+ * @error: (out) (nullable): place to store error (if any)
+ *
+ * Retrieves information about the NVMe controller (the Identify Controller command)
+ * as specified by the @device block device path.
+ *
+ * Returns: (transfer full): information about given controller or %NULL in case of an error (with @error set).
+ *
+ * Tech category: %BD_NVME_TECH_NVME-%BD_NVME_TECH_MODE_INFO
+ */
+BDNVMEControllerInfo * bd_nvme_get_controller_info (const gchar *device, GError **error);
+
+/**
+ * bd_nvme_get_namespace_info:
+ * @device: a NVMe namespace device (e.g. `/dev/nvme0n1`)
+ * @error: (out) (nullable): place to store error (if any)
+ *
+ * Retrieves information about the NVMe namespace (the Identify Namespace command)
+ * as specified by the @device block device path.
+ *
+ * Returns: (transfer full): information about given namespace or %NULL in case of an error (with @error set).
+ *
+ * Tech category: %BD_NVME_TECH_NVME-%BD_NVME_TECH_MODE_INFO
+ */
+BDNVMENamespaceInfo * bd_nvme_get_namespace_info (const gchar *device, GError **error);
+
+/**
+ * bd_nvme_get_smart_log:
+ * @device: a NVMe controller device (e.g. `/dev/nvme0`)
+ * @error: (out) (nullable): place to store error (if any)
+ *
+ * Retrieves drive SMART and general health information (Log Identifier `02h`).
+ * The information provided is over the life of the controller and is retained across power cycles.
+ *
+ * Returns: (transfer full): health log data or %NULL in case of an error (with @error set).
+ *
+ * Tech category: %BD_NVME_TECH_NVME-%BD_NVME_TECH_MODE_INFO
+ */
+BDNVMESmartLog * bd_nvme_get_smart_log (const gchar *device, GError **error);
+
+/**
+ * bd_nvme_get_error_log_entries:
+ * @device: a NVMe controller device (e.g. `/dev/nvme0`)
+ * @error: (out) (nullable): place to store error (if any)
+ *
+ * Retrieves Error Information Log (Log Identifier `01h`) entries, used to describe
+ * extended error information for a command that completed with error or to report
+ * an error that is not specific to a particular command. This log is global to the
+ * controller. The ordering of the entries is based on the time when the error
+ * occurred, with the most recent error being returned as the first log entry.
+ * As the number of entries is typically limited by the drive implementation, only
+ * most recent entries are provided.
+ *
+ * Returns: (transfer full) (array zero-terminated=1): null-terminated list
+ *          of error entries or %NULL in case of an error (with @error set).
+ *
+ * Tech category: %BD_NVME_TECH_NVME-%BD_NVME_TECH_MODE_INFO
+ */
+BDNVMEErrorLogEntry ** bd_nvme_get_error_log_entries (const gchar *device, GError **error);
+
+/**
+ * bd_nvme_get_self_test_log:
+ * @device: a NVMe controller device (e.g. `/dev/nvme0`)
+ * @error: (out) (nullable): place to store error (if any)
+ *
+ * Retrieves drive self-test log (Log Identifier `06h`). Provides the status of a self-test operation
+ * in progress and the percentage complete of that operation, along with the results of the last
+ * 20 device self-test operations.
+ *
+ * Returns: (transfer full): self-test log data or %NULL in case of an error (with @error set).
+ *
+ * Tech category: %BD_NVME_TECH_NVME-%BD_NVME_TECH_MODE_INFO
+ */
+BDNVMESelfTestLog * bd_nvme_get_self_test_log (const gchar *device, GError **error);
+
+/**
+ * bd_nvme_get_sanitize_log:
+ * @device: a NVMe controller device (e.g. `/dev/nvme0`)
+ * @error: (out) (nullable): place to store error (if any)
+ *
+ * Retrieves the drive sanitize status log (Log Identifier `81h`) that includes information
+ * about the most recent sanitize operation and the sanitize operation time estimates.
+ *
+ * As advised in the NVMe specification whitepaper the host should limit polling
+ * to retrieve progress of a running sanitize operations (e.g. to at most once every
+ * several minutes) to avoid interfering with the progress of the sanitize operation itself.
+ *
+ * Returns: (transfer full): sanitize log data or %NULL in case of an error (with @error set).
+ *
+ * Tech category: %BD_NVME_TECH_NVME-%BD_NVME_TECH_MODE_INFO
+ */
+BDNVMESanitizeLog * bd_nvme_get_sanitize_log (const gchar *device, GError **error);
+
+/**
+ * bd_nvme_device_self_test:
+ * @device: a NVMe controller or namespace device (e.g. `/dev/nvme0`)
+ * @action: self-test action to take.
+ * @error: (out) (nullable): place to store error (if any)
+ *
+ * Initiates or aborts the Device Self-test operation on the controller or a namespace,
+ * distinguished by the @device path specified. In case a controller device
+ * is specified then the self-test operation would include all active namespaces.
+ *
+ * To abort a running operation, pass #BD_NVME_SELF_TEST_ACTION_ABORT as @action.
+ * To retrieve progress of a current running operation, check the self-test log using
+ * bd_nvme_get_self_test_log().
+ *
+ * Returns: %TRUE if the device self-test command was issued successfully,
+ *          %FALSE otherwise with @error set.
+ *
+ * Tech category: %BD_NVME_TECH_NVME-%BD_NVME_TECH_MODE_MANAGE
+ */
+gboolean bd_nvme_device_self_test (const gchar *device, BDNVMESelfTestAction action, GError **error);
+
+/**
+ * bd_nvme_format:
+ * @device: NVMe namespace or controller device to format (e.g. `/dev/nvme0n1`)
+ * @lba_data_size: desired LBA data size (i.e. a sector size) in bytes or `0` to keep current. See #BDNVMELBAFormat and bd_nvme_get_namespace_info().
+ * @metadata_size: desired metadata size in bytes or `0` for default. See #BDNVMELBAFormat and bd_nvme_get_namespace_info().
+ * @secure_erase: optional secure erase action to take.
+ * @error: (out) (nullable): place to store error (if any)
+ *
+ * Performs low level format of the NVM media, destroying all data and metadata for either
+ * a specific namespace or all attached namespaces to the controller. Use this command
+ * to change LBA sector size. Optional secure erase method can be specified as well.
+ *
+ * Supported LBA data sizes for a given namespace can be listed using the bd_nvme_get_namespace_info()
+ * call. In case of a special value `0` the current LBA format for a given namespace will be
+ * retained. When called on a controller device the first namespace is used as a reference.
+ *
+ * Note that the NVMe controller may define a Format NVM attribute indicating that the format
+ * operation would apply to all namespaces and a format (excluding secure erase) of any
+ * namespace results in a format of all namespaces in the NVM subsystem. In such case and
+ * when @device is a namespace block device the #BD_NVME_ERROR_WOULD_FORMAT_ALL_NS error
+ * is returned to prevent further damage. This is then supposed to be handled by the caller
+ * and bd_nvme_format() is supposed to be called on a controller device instead.
+ *
+ * This call blocks until the format operation has finished. To retrieve progress
+ * of a current running operation, check the namespace info using bd_nvme_get_namespace_info().
+ *
+ * Returns: %TRUE if the format command finished successfully, %FALSE otherwise with @error set.
+ *
+ * Tech category: %BD_NVME_TECH_NVME-%BD_NVME_TECH_MODE_MANAGE
+ */
+gboolean bd_nvme_format (const gchar *device, guint16 lba_data_size, guint16 metadata_size, BDNVMEFormatSecureErase secure_erase, GError **error);
+
+/**
+ * bd_nvme_sanitize:
+ * @device: NVMe namespace or controller device to format (e.g. `/dev/nvme0n1`)
+ * @action: the sanitize action to perform.
+ * @no_dealloc: instruct the controller to not deallocate the affected media area.
+ * @overwrite_pass_count: number of overwrite passes [1-15] or 0 for the default (16 passes).
+ * @overwrite_pattern: a 32-bit pattern used for the Overwrite sanitize operation.
+ * @overwrite_invert_pattern: invert the overwrite pattern between passes.
+ * @error: (out) (nullable): place to store error (if any)
+ *
+ * Starts a sanitize operation or recovers from a previously failed sanitize operation.
+ * By definition, a sanitize operation alters all user data in the NVM subsystem such
+ * that recovery of any previous user data from any cache, the non-volatile media,
+ * or any Controller Memory Buffer is not possible. The scope of a sanitize operation
+ * is all locations in the NVM subsystem that are able to contain user data, including
+ * caches, Persistent Memory Regions, and unallocated or deallocated areas of the media.
+ *
+ * Once started, a sanitize operation is not able to be aborted and continues after
+ * a Controller Level Reset including across power cycles. Once the sanitize operation
+ * has run the media affected may not be immediately ready for use unless additional
+ * media modification mechanism is run. This is often vendor specific and also depends
+ * on the sanitize method (@action) used. Callers to this sanitize operation should
+ * set @no_dealloc to %TRUE for the added convenience.
+ *
+ * The controller also ignores Critical Warning(s) in the SMART / Health Information
+ * log page (e.g., read only mode) and attempts to complete the sanitize operation requested.
+ *
+ * This call returns immediately and the actual sanitize operation is performed
+ * in the background. Use bd_nvme_get_sanitize_log() to retrieve status and progress
+ * of a running sanitize operation. In case a sanitize operation fails the controller
+ * may restrict its operation until a subsequent sanitize operation is started
+ * (i.e. retried) or an #BD_NVME_SANITIZE_ACTION_EXIT_FAILURE action is used
+ * to acknowledge the failure explicitly.
+ *
+ * The @overwrite_pass_count, @overwrite_pattern and @overwrite_invert_pattern
+ * arguments are only valid when @action is #BD_NVME_SANITIZE_ACTION_OVERWRITE.
+ *
+ * The sanitize operation is set to run under the Allow Unrestricted Sanitize Exit
+ * mode.
+ *
+ * Returns: %TRUE if the format command finished successfully, %FALSE otherwise with @error set.
+ *
+ * Tech category: %BD_NVME_TECH_NVME-%BD_NVME_TECH_MODE_MANAGE
+ */
+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);
+
+/**
+ * bd_nvme_get_host_nqn:
+ * @error: (out) (nullable): Place to store error (if any).
+ *
+ * Reads the Host NQN (NVM Qualified Name) value from the global `/etc/nvme/hostnqn`
+ * file. An empty string is an indication that no Host NQN has been set.
+ *
+ * Returns: (transfer full): the Host NQN string or an empty string if none set.
+ *
+ * Tech category: %BD_NVME_TECH_FABRICS-%BD_NVME_TECH_MODE_INITIATOR
+ */
+gchar * bd_nvme_get_host_nqn (GError **error);
+
+/**
+ * bd_nvme_generate_host_nqn:
+ * @error: (out) (nullable): Place to store error (if any).
+ *
+ * Compute new Host NQN (NVM Qualified Name) value for the current system. This
+ * takes in account various system identifiers (DMI, device tree) with the goal
+ * of a stable unique identifier whenever feasible.
+ *
+ * Returns: (transfer full): the Host NQN string or %NULL with @error set.
+ *
+ * Tech category: %BD_NVME_TECH_FABRICS-%BD_NVME_TECH_MODE_INITIATOR
+ */
+gchar * bd_nvme_generate_host_nqn (GError **error);
+
+/**
+ * bd_nvme_get_host_id:
+ * @error: (out) (nullable): Place to store error (if any).
+ *
+ * Reads the Host ID value from the global `/etc/nvme/hostid` file. An empty
+ * string is an indication that no Host ID has been set.
+ *
+ * Returns: (transfer full): the Host ID string or an empty string if none set.
+ *
+ * Tech category: %BD_NVME_TECH_FABRICS-%BD_NVME_TECH_MODE_INITIATOR
+ */
+gchar * bd_nvme_get_host_id (GError **error);
+
+/**
+ * bd_nvme_set_host_nqn:
+ * @host_nqn: The Host NVM Qualified Name.
+ * @error: (out) (nullable): Place to store error (if any).
+ *
+ * Writes the Host NQN (NVM Qualified Name) value to the system `/etc/nvme/hostnqn` file.
+ * No validation of the string is performed.
+ *
+ * Returns: %TRUE if the value was set successfully or %FALSE otherwise with @error set.
+ *
+ * Tech category: %BD_NVME_TECH_FABRICS-%BD_NVME_TECH_MODE_INITIATOR
+ */
+gboolean bd_nvme_set_host_nqn (const gchar *host_nqn, GError **error);
+
+/**
+ * bd_nvme_set_host_id:
+ * @host_id: The Host ID.
+ * @error: (out) (nullable): Place to store error (if any).
+ *
+ * Writes the Host ID value to the system `/etc/nvme/hostid` file.
+ * No validation of the string is performed.
+ *
+ * Returns: %TRUE if the value was set successfully or %FALSE otherwise with @error set.
+ *
+ * Tech category: %BD_NVME_TECH_FABRICS-%BD_NVME_TECH_MODE_INITIATOR
+ */
+gboolean bd_nvme_set_host_id (const gchar *host_id, GError **error);
+
+/**
+ * bd_nvme_connect:
+ * @subsysnqn: The name for the NVMe subsystem to connect to.
+ * @transport: The network fabric used for a NVMe-over-Fabrics network.
+ * @transport_addr: (nullable): The network address of the Controller. For transports using IP addressing (e.g. `rdma`) this should be an IP-based address.
+ * @transport_svcid: (nullable): The transport service id.  For transports using IP addressing (e.g. `rdma`) this field is the port number. By default, the IP port number for the `RDMA` transport is `4420`.
+ * @host_traddr: (nullable): The network address used on the host to connect to the Controller. For TCP, this sets the source address on the socket.
+ * @host_iface: (nullable): The network interface used on the host to connect to the Controller (e.g. IP `eth1`, `enp2s0`). This forces the connection to be made on a specific interface instead of letting the system decide.
+ * @host_nqn: (nullable): Overrides the default Host NQN that identifies the NVMe Host. If this option is %NULL, the default is read from `/etc/nvme/hostnqn` first.
+ *                        If that does not exist, the autogenerated NQN value from the NVMe Host kernel module is used next. The Host NQN uniquely identifies the NVMe Host.
+ * @host_id: (nullable): User-defined host UUID or %NULL to use default (as defined in `/etc/nvme/hostid`)
+ * @extra: (nullable) (array zero-terminated=1): Additional arguments.
+ * @error: (out) (nullable): Place to store error (if any).
+ *
+ * Creates a transport connection to a remote system (specified by @transport_addr and @transport_svcid)
+ * and creates a NVMe over Fabrics controller for the NVMe subsystem specified by the @subsysnqn option.
+ *
+ * Valid values for @transport include:
+ * - `"rdma"`: An rdma network (RoCE, iWARP, Infiniband, basic rdma, etc.)
+ * - `"fc"`: A Fibre Channel network.
+ * - `"tcp"`: A TCP/IP network.
+ * - `"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.
+ * - `"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.
+ * - `"queue_size"`: Number of elements in the I/O queues.
+ * - `"keep_alive_tmo"`: The keep alive timeout (in seconds).
+ * - `"reconnect_delay"`: The delay (in seconds) before reconnect is attempted after a connect loss.
+ * - `"ctrl_loss_tmo"`: The controller loss timeout period (in seconds). A special value of `-1` will cause reconnecting forever.
+ * - `"fast_io_fail_tmo"`: Fast I/O Fail timeout (in seconds).
+ * - `"tos"`: Type of service.
+ * - `"duplicate_connect"`: Allow duplicated connections between same transport host and subsystem port. Boolean value.
+ * - `"disable_sqflow"`: Disables SQ flow control to omit head doorbell update for submission queues when sending nvme completions. Boolean value.
+ * - `"hdr_digest"`: Generates/verifies header digest (TCP). Boolean value.
+ * - `"data_digest"`: Generates/verifies data digest (TCP). Boolean value.
+ * - `"tls"`: Enable TLS encryption (TCP). Boolean value.
+ * - `"hostsymname"`: TP8010: NVMe host symbolic name.
+ *
+ * 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.
+ *
+ * Returns: %TRUE if the subsystem was connected successfully, %FALSE otherwise with @error set.
+ *
+ * Tech category: %BD_NVME_TECH_FABRICS-%BD_NVME_TECH_MODE_INITIATOR
+ */
+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);
+
+/**
+ * bd_nvme_disconnect:
+ * @subsysnqn: The name of the NVMe subsystem to disconnect.
+ * @error: (out) (nullable): Place to store error (if any).
+ *
+ * Disconnects and removes one or more existing NVMe over Fabrics controllers.
+ * This may disconnect multiple controllers with matching @subsysnqn and %TRUE
+ * is only returned when all controllers were disconnected successfully.
+ *
+ * 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.
+ *
+ * Tech category: %BD_NVME_TECH_FABRICS-%BD_NVME_TECH_MODE_INITIATOR
+ */
+gboolean bd_nvme_disconnect (const gchar *subsysnqn, GError **error);
+
+/**
+ * bd_nvme_disconnect_by_path:
+ * @path: NVMe controller device to disconnect (e.g. `/dev/nvme0`).
+ * @error: (out) (nullable): Place to store error (if any).
+ *
+ * Disconnects and removes a NVMe over Fabrics controller represented
+ * by a block device path.
+ *
+ * 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);
+
+/**
+ * bd_nvme_discover:
+ * @discovery_ctrl: (nullable): Use existing discovery controller device or %NULL to establish a new connection.
+ * @persistent: Persistent discovery connection.
+ * @transport: The network fabric used for a NVMe-over-Fabrics network.
+ * @transport_addr: (nullable): The network address of the Controller. For transports using IP addressing (e.g. `rdma`) this should be an IP-based address.
+ * @transport_svcid: (nullable): The transport service id.  For transports using IP addressing (e.g. `rdma`) this field is the port number. By default, the IP port number for the `RDMA` transport is `4420`.
+ * @host_traddr: (nullable): The network address used on the host to connect to the Controller. For TCP, this sets the source address on the socket.
+ * @host_iface: (nullable): The network interface used on the host to connect to the Controller (e.g. IP `eth1`, `enp2s0`). This forces the connection to be made on a specific interface instead of letting the system decide.
+ * @host_nqn: (nullable): Overrides the default Host NQN that identifies the NVMe Host. If this option is %NULL, the default is read from `/etc/nvme/hostnqn` first.
+ *                        If that does not exist, the autogenerated NQN value from the NVMe Host kernel module is used next. The Host NQN uniquely identifies the NVMe Host.
+ * @host_id: (nullable): User-defined host UUID or %NULL to use default (as defined in `/etc/nvme/hostid`)
+ * @extra: (nullable) (array zero-terminated=1): Additional arguments.
+ * @error: (out) (nullable): Place to store error (if any).
+ *
+ * Performs Discovery request on a Discovery Controller. If no connection to a Discovery Controller
+ * exists (i.e. @discovery_ctrl is %NULL) a new connection is established as specified by the @transport,
+ * @transport_addr and optionally @transport_svcid arguments.
+ *
+ * Note that the `nvme-cli`'s `/etc/nvme/discovery.conf` config file is not used at the moment.
+ *
+ * Valid values for @transport include:
+ * - `"rdma"`: An rdma network (RoCE, iWARP, Infiniband, basic rdma, etc.)
+ * - `"fc"`: A Fibre Channel network.
+ * - `"tcp"`: A TCP/IP network.
+ * - `"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.
+ * - `"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.
+ * - `"queue_size"`: Number of elements in the I/O queues.
+ * - `"keep_alive_tmo"`: The keep alive timeout (in seconds).
+ * - `"reconnect_delay"`: The delay (in seconds) before reconnect is attempted after a connect loss.
+ * - `"ctrl_loss_tmo"`: The controller loss timeout period (in seconds). A special value of `-1` will cause reconnecting forever.
+ * - `"fast_io_fail_tmo"`: Fast I/O Fail timeout (in seconds).
+ * - `"tos"`: Type of service.
+ * - `"duplicate_connect"`: Allow duplicated connections between same transport host and subsystem port. Boolean value.
+ * - `"disable_sqflow"`: Disables SQ flow control to omit head doorbell update for submission queues when sending nvme completions. Boolean value.
+ * - `"hdr_digest"`: Generates/verifies header digest (TCP). Boolean value.
+ * - `"data_digest"`: Generates/verifies data digest (TCP). Boolean value.
+ * - `"tls"`: Enable TLS encryption (TCP). Boolean value.
+ * - `"hostsymname"`: TP8010: NVMe host symbolic name.
+ *
+ * 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.
+ *
+ * Returns: (transfer full) (array zero-terminated=1): null-terminated list
+ *          of discovery log entries or %NULL in case of an error (with @error set).
+ *
+ * Tech category: %BD_NVME_TECH_FABRICS-%BD_NVME_TECH_MODE_INITIATOR
+ */
+BDNVMEDiscoveryLogEntry ** bd_nvme_discover (const gchar *discovery_ctrl, gboolean persistent, 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);
+
+/**
+ * bd_nvme_find_ctrls_for_ns:
+ * @ns_sysfs_path: NVMe namespace device file.
+ * @subsysnqn: (nullable): Limit matching to the specified subsystem NQN.
+ * @host_nqn: (nullable): Limit matching to the specified host NQN.
+ * @host_id: (nullable): Limit matching to the specified host ID.
+ * @error: (out) (nullable): Place to store error (if any).
+ *
+ * A convenient utility function to look up all controllers associated
+ *  with a NVMe subsystem the specified namespace is part of.
+ *
+ * Returns: (transfer full) (array zero-terminated=1): list of controller sysfs paths
+ *          or %NULL in case of an error (with @error set).
+ *
+ * Tech category: %BD_NVME_TECH_FABRICS-%BD_NVME_TECH_MODE_INITIATOR
+ */
+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);
+
+#endif  /* BD_NVME_API */
diff --git a/src/lib/plugins.h b/src/lib/plugins.h
index 8a23b6a2..d19bdc2c 100644
--- a/src/lib/plugins.h
+++ b/src/lib/plugins.h
@@ -18,6 +18,7 @@ typedef enum {
     BD_PLUGIN_PART,
     BD_PLUGIN_FS,
     BD_PLUGIN_NVDIMM,
+    BD_PLUGIN_NVME,
     BD_PLUGIN_VDO,
     BD_PLUGIN_UNDEF
 } BDPlugin;
diff --git a/src/plugins/Makefile.am b/src/plugins/Makefile.am
index 8e8b3062..a2bfe2dc 100644
--- a/src/plugins/Makefile.am
+++ b/src/plugins/Makefile.am
@@ -1,5 +1,11 @@
+SUBDIRS = .
+
 if WITH_FS
-SUBDIRS = . fs
+SUBDIRS += fs
+endif
+
+if WITH_NVME
+SUBDIRS += nvme
 endif

 lib_LTLIBRARIES =
diff --git a/src/plugins/nvme/Makefile.am b/src/plugins/nvme/Makefile.am
new file mode 100644
index 00000000..b4a10ce0
--- /dev/null
+++ b/src/plugins/nvme/Makefile.am
@@ -0,0 +1,22 @@
+AUTOMAKE_OPTIONS = subdir-objects
+
+lib_LTLIBRARIES = libbd_nvme.la
+
+libbd_nvme_la_CFLAGS = $(GLIB_CFLAGS) $(GIO_CFLAGS) $(UUID_CFLAGS) $(NVME_CFLAGS) -Wall -Wextra -Werror
+libbd_nvme_la_LIBADD = ${builddir}/../../utils/libbd_utils.la $(GLIB_LIBS) $(GIO_LIBS) $(UUID_LIBS) $(NVME_LIBS)
+libbd_nvme_la_LDFLAGS = -L${srcdir}/../../utils/ -version-info 2:0:0 -Wl,--no-undefined
+libbd_nvme_la_CPPFLAGS = -I${builddir}/../../../include/ -I${srcdir}/../ -I. -DPACKAGE_SYSCONF_DIR=\""$(sysconfdir)"\"
+
+libbd_nvme_la_SOURCES = \
+	nvme.h \
+	nvme.c \
+	nvme-private.h \
+	nvme-info.c \
+	nvme-error.c \
+	nvme-op.c \
+	nvme-fabrics.c \
+	../check_deps.c \
+	../check_deps.h
+
+libbd_nvmeincludedir = $(includedir)/blockdev
+libbd_nvmeinclude_HEADERS = nvme.h
diff --git a/src/plugins/nvme/nvme-error.c b/src/plugins/nvme/nvme-error.c
new file mode 100644
index 00000000..86f0d6a3
--- /dev/null
+++ b/src/plugins/nvme/nvme-error.c
@@ -0,0 +1,160 @@
+/*
+ * Copyright (C) 2014-2021 Red Hat, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see <http://www.gnu.org/licenses/>.
+ *
+ * Author: Tomas Bzatek <tbzatek@redhat.com>
+ */
+
+#include <glib.h>
+#include <string.h>
+#include <stdio.h>
+#include <unistd.h>
+#include <sys/ioctl.h>
+#include <sys/stat.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <malloc.h>
+
+#include <libnvme.h>
+#include <uuid/uuid.h>
+
+#include <blockdev/utils.h>
+#include <check_deps.h>
+#include "nvme.h"
+#include "nvme-private.h"
+
+
+/**
+ * bd_nvme_error_quark: (skip)
+ */
+GQuark bd_nvme_error_quark (void)
+{
+    return g_quark_from_static_string ("g-bd-nvme-error-quark");
+}
+
+void _nvme_status_to_error (gint status, gboolean fabrics, GError **error)
+{
+    gint code;
+
+    if (error == NULL)
+        return;
+    if (status == 0) {
+        g_clear_error (error);
+        return;
+    }
+
+    if (status < 0) {
+        /* generic errno errors */
+        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));
+        return;
+    } else {
+        /* NVMe status codes */
+        switch (nvme_status_code_type (status)) {
+            case NVME_SCT_GENERIC:
+                code = BD_NVME_ERROR_SC_GENERIC;
+                break;
+            case NVME_SCT_CMD_SPECIFIC:
+                code = BD_NVME_ERROR_SC_CMD_SPECIFIC;
+                break;
+            case NVME_SCT_MEDIA:
+                code = BD_NVME_ERROR_SC_MEDIA;
+                break;
+            case NVME_SCT_PATH:
+                code = BD_NVME_ERROR_SC_PATH;
+                break;
+            case NVME_SCT_VS:
+                code = BD_NVME_ERROR_SC_VENDOR_SPECIFIC;
+                break;
+            default:
+                code = BD_NVME_ERROR_SC_GENERIC;
+        }
+        g_set_error_literal (error, BD_NVME_ERROR, code, nvme_status_to_string (status, fabrics));
+        return;
+    }
+    g_warn_if_reached ();
+}
+
+void _nvme_fabrics_errno_to_gerror (int result, int _errno, GError **error)
+{
+    gint code;
+
+    if (error == NULL)
+        return;
+    if (result == 0) {
+        g_clear_error (error);
+        return;
+    }
+
+    if (_errno >= ENVME_CONNECT_RESOLVE) {
+        switch (_errno) {
+            case ENVME_CONNECT_ADDRFAM:
+            case ENVME_CONNECT_TRADDR:
+            case ENVME_CONNECT_TARG:
+            case ENVME_CONNECT_AARG:
+            case ENVME_CONNECT_INVAL_TR:
+                code = BD_NVME_ERROR_INVALID_ARGUMENT;
+                break;
+            case ENVME_CONNECT_RESOLVE:
+            case ENVME_CONNECT_OPEN:
+            case ENVME_CONNECT_WRITE:
+            case ENVME_CONNECT_READ:
+            case ENVME_CONNECT_PARSE:
+            case ENVME_CONNECT_LOOKUP_SUBSYS_NAME:
+            case ENVME_CONNECT_LOOKUP_SUBSYS:
+                code = BD_NVME_ERROR_CONNECT;
+                break;
+#ifdef HAVE_LIBNVME_1_1
+            case ENVME_CONNECT_ALREADY:
+                code = BD_NVME_ERROR_CONNECT_ALREADY;
+                break;
+            case ENVME_CONNECT_INVAL:
+                code = BD_NVME_ERROR_CONNECT_INVALID;
+                break;
+            case ENVME_CONNECT_ADDRINUSE:
+                code = BD_NVME_ERROR_CONNECT_ADDRINUSE;
+                break;
+            case ENVME_CONNECT_NODEV:
+                code = BD_NVME_ERROR_CONNECT_NODEV;
+                break;
+            case ENVME_CONNECT_OPNOTSUPP:
+                code = BD_NVME_ERROR_CONNECT_OPNOTSUPP;
+                break;
+#endif
+            default:
+                code = BD_NVME_ERROR_CONNECT;
+        }
+        g_set_error_literal (error, BD_NVME_ERROR, code, nvme_errno_to_string (_errno));
+        return;
+    } else {
+        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));
+        return;
+    }
+    g_warn_if_reached ();
+}
diff --git a/src/plugins/nvme/nvme-fabrics.c b/src/plugins/nvme/nvme-fabrics.c
new file mode 100644
index 00000000..20ed57f5
--- /dev/null
+++ b/src/plugins/nvme/nvme-fabrics.c
@@ -0,0 +1,918 @@
+/*
+ * Copyright (C) 2014-2021 Red Hat, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see <http://www.gnu.org/licenses/>.
+ *
+ * Author: Tomas Bzatek <tbzatek@redhat.com>
+ */
+
+#include <glib.h>
+#include <string.h>
+#include <stdio.h>
+#include <unistd.h>
+#include <sys/ioctl.h>
+#include <sys/stat.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <malloc.h>
+#include <linux/fs.h>
+#include <glib/gstdio.h>
+
+#include <libnvme.h>
+#include <uuid/uuid.h>
+
+#include <blockdev/utils.h>
+#include <check_deps.h>
+#include "nvme.h"
+#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) {
+    const BDExtraArg **extra_i;
+
+    if (!extra)
+        return;
+
+#define SAFE_INT_CONV(target) { \
+        gint64 v; \
+        gchar *endptr = NULL; \
+        \
+        v = g_ascii_strtoll ((*extra_i)->val, &endptr, 0); \
+        if (endptr != (*extra_i)->val) \
+            target = v; \
+    }
+#define SAFE_BOOL_CONV(target) { \
+        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; \
+        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; \
+    }
+
+    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)
+            *hostkey = (*extra_i)->val;
+        else
+        if (g_strcmp0 ((*extra_i)->opt, "dhchap_ctrl_key") == 0 && 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, "nr_io_queues") == 0)
+            SAFE_INT_CONV (cfg->nr_io_queues)
+        else
+        if (g_strcmp0 ((*extra_i)->opt, "nr_write_queues") == 0)
+            SAFE_INT_CONV (cfg->nr_write_queues)
+        else
+        if (g_strcmp0 ((*extra_i)->opt, "nr_poll_queues") == 0)
+            SAFE_INT_CONV (cfg->nr_poll_queues)
+        else
+        if (g_strcmp0 ((*extra_i)->opt, "queue_size") == 0)
+            SAFE_INT_CONV (cfg->queue_size)
+        else
+        if (g_strcmp0 ((*extra_i)->opt, "keep_alive_tmo") == 0)
+            SAFE_INT_CONV (cfg->keep_alive_tmo)
+        else
+        if (g_strcmp0 ((*extra_i)->opt, "reconnect_delay") == 0)
+            SAFE_INT_CONV (cfg->reconnect_delay)
+        else
+        if (g_strcmp0 ((*extra_i)->opt, "ctrl_loss_tmo") == 0)
+            SAFE_INT_CONV (cfg->ctrl_loss_tmo)
+        else
+        if (g_strcmp0 ((*extra_i)->opt, "fast_io_fail_tmo") == 0)
+            SAFE_INT_CONV (cfg->fast_io_fail_tmo)
+        else
+        if (g_strcmp0 ((*extra_i)->opt, "tos") == 0)
+            SAFE_INT_CONV (cfg->tos)
+        else
+        if (g_strcmp0 ((*extra_i)->opt, "duplicate_connect") == 0)
+            SAFE_BOOL_CONV (cfg->duplicate_connect)
+        else
+        if (g_strcmp0 ((*extra_i)->opt, "disable_sqflow") == 0)
+            SAFE_BOOL_CONV (cfg->disable_sqflow)
+        else
+        if (g_strcmp0 ((*extra_i)->opt, "hdr_digest") == 0)
+            SAFE_BOOL_CONV (cfg->hdr_digest)
+        else
+        if (g_strcmp0 ((*extra_i)->opt, "data_digest") == 0)
+            SAFE_BOOL_CONV (cfg->data_digest)
+        else
+        if (g_strcmp0 ((*extra_i)->opt, "tls") == 0)
+            SAFE_BOOL_CONV (cfg->tls)
+    }
+
+#undef SAFE_INT_CONV
+#undef SAFE_BOOL_CONV
+}
+
+
+/**
+ * bd_nvme_connect:
+ * @subsysnqn: The name for the NVMe subsystem to connect to.
+ * @transport: The network fabric used for a NVMe-over-Fabrics network.
+ * @transport_addr: (nullable): The network address of the Controller. For transports using IP addressing (e.g. `rdma`) this should be an IP-based address.
+ * @transport_svcid: (nullable): The transport service id.  For transports using IP addressing (e.g. `rdma`) this field is the port number. By default, the IP port number for the `RDMA` transport is `4420`.
+ * @host_traddr: (nullable): The network address used on the host to connect to the Controller. For TCP, this sets the source address on the socket.
+ * @host_iface: (nullable): The network interface used on the host to connect to the Controller (e.g. IP `eth1`, `enp2s0`). This forces the connection to be made on a specific interface instead of letting the system decide.
+ * @host_nqn: (nullable): Overrides the default Host NQN that identifies the NVMe Host. If this option is %NULL, the default is read from `/etc/nvme/hostnqn` first.
+ *                        If that does not exist, the autogenerated NQN value from the NVMe Host kernel module is used next. The Host NQN uniquely identifies the NVMe Host.
+ * @host_id: (nullable): User-defined host UUID or %NULL to use default (as defined in `/etc/nvme/hostid`)
+ * @extra: (nullable) (array zero-terminated=1): Additional arguments.
+ * @error: (out) (nullable): Place to store error (if any).
+ *
+ * Creates a transport connection to a remote system (specified by @transport_addr and @transport_svcid)
+ * and creates a NVMe over Fabrics controller for the NVMe subsystem specified by the @subsysnqn option.
+ *
+ * Valid values for @transport include:
+ * - `"rdma"`: An rdma network (RoCE, iWARP, Infiniband, basic rdma, etc.)
+ * - `"fc"`: A Fibre Channel network.
+ * - `"tcp"`: A TCP/IP network.
+ * - `"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.
+ * - `"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.
+ * - `"queue_size"`: Number of elements in the I/O queues.
+ * - `"keep_alive_tmo"`: The keep alive timeout (in seconds).
+ * - `"reconnect_delay"`: The delay (in seconds) before reconnect is attempted after a connect loss.
+ * - `"ctrl_loss_tmo"`: The controller loss timeout period (in seconds). A special value of `-1` will cause reconnecting forever.
+ * - `"fast_io_fail_tmo"`: Fast I/O Fail timeout (in seconds).
+ * - `"tos"`: Type of service.
+ * - `"duplicate_connect"`: Allow duplicated connections between same transport host and subsystem port. Boolean value.
+ * - `"disable_sqflow"`: Disables SQ flow control to omit head doorbell update for submission queues when sending nvme completions. Boolean value.
+ * - `"hdr_digest"`: Generates/verifies header digest (TCP). Boolean value.
+ * - `"data_digest"`: Generates/verifies data digest (TCP). Boolean value.
+ * - `"tls"`: Enable TLS encryption (TCP). Boolean value.
+ * - `"hostsymname"`: TP8010: NVMe host symbolic name.
+ *
+ * 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.
+ *
+ * Returns: %TRUE if the subsystem was connected successfully, %FALSE otherwise with @error set.
+ *
+ * Tech category: %BD_NVME_TECH_FABRICS-%BD_NVME_TECH_MODE_INITIATOR
+ */
+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 *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;
+
+    if (subsysnqn == NULL) {
+        g_set_error_literal (error, BD_NVME_ERROR, BD_NVME_ERROR_INVALID_ARGUMENT,
+                             "Invalid value specified for the subsysnqn argument");
+        return FALSE;
+    }
+    if (transport == NULL) {
+        g_set_error_literal (error, BD_NVME_ERROR, BD_NVME_ERROR_INVALID_ARGUMENT,
+                             "Invalid value specified for the transport argument");
+        return FALSE;
+    }
+    if (transport_addr == NULL && !g_str_equal (transport, "loop") && !g_str_equal (transport, "pcie")) {
+        g_set_error_literal (error, BD_NVME_ERROR, BD_NVME_ERROR_INVALID_ARGUMENT,
+                             "Invalid value specified for the transport address argument");
+        return FALSE;
+    }
+
+    /* parse extra arguments */
+    nvmf_default_config (&cfg);
+    parse_extra_args (extra, &cfg, &config_file, &hostkey, &ctrlkey, &hostsymname);
+
+    host_nqn_val = g_strdup (host_nqn);
+    if (host_nqn_val == NULL)
+        host_nqn_val = nvmf_hostnqn_from_file ();
+    if (host_nqn_val == NULL)
+        host_nqn_val = nvmf_hostnqn_generate ();
+    host_id_val = g_strdup (host_id);
+    if (host_id_val == NULL)
+        host_id_val = nvmf_hostid_from_file ();
+
+    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 nqn '%s' and id '%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);
+
+    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);
+        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;
+    }
+    nvme_free_ctrl (ctrl);
+    nvme_free_tree (root);
+
+    return TRUE;
+}
+
+/**
+ * bd_nvme_disconnect:
+ * @subsysnqn: The name of the NVMe subsystem to disconnect.
+ * @error: (out) (nullable): Place to store error (if any).
+ *
+ * Disconnects and removes one or more existing NVMe over Fabrics controllers.
+ * This may disconnect multiple controllers with matching @subsysnqn and %TRUE
+ * is only returned when all controllers were disconnected successfully.
+ *
+ * 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.
+ *
+ * Tech category: %BD_NVME_TECH_FABRICS-%BD_NVME_TECH_MODE_INITIATOR
+ */
+gboolean bd_nvme_disconnect (const gchar *subsysnqn, GError **error) {
+    nvme_root_t root;
+    nvme_host_t host;
+    nvme_subsystem_t subsys;
+    nvme_ctrl_t ctrl;
+    gboolean found = FALSE;
+
+    root = nvme_scan (NULL);
+    nvme_init_logging (root, -1, false, false);
+    nvme_for_each_host (root, host)
+        nvme_for_each_subsystem (host, subsys)
+            if (g_strcmp0 (nvme_subsystem_get_nqn (subsys), subsysnqn) == 0)
+                nvme_subsystem_for_each_ctrl (subsys, ctrl) {
+                    int ret;
+
+                    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);
+    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;
+}
+
+/**
+ * bd_nvme_disconnect_by_path:
+ * @path: NVMe controller device to disconnect (e.g. `/dev/nvme0`).
+ * @error: (out) (nullable): Place to store error (if any).
+ *
+ * Disconnects and removes a NVMe over Fabrics controller represented
+ * by a block device path.
+ *
+ * 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) {
+    nvme_root_t root;
+    nvme_ctrl_t ctrl;
+    const gchar *p;
+    int ret;
+
+    p = path;
+    if (g_str_has_prefix (p, "/dev/"))
+        p += 5;
+
+    root = nvme_scan (NULL);
+    nvme_init_logging (root, -1, false, false);
+    ctrl = nvme_scan_ctrl (root, p);
+    if (!ctrl) {
+        g_set_error (error, BD_NVME_ERROR, BD_NVME_ERROR_NO_MATCH,
+                     "Unable to match a NVMeoF controller for the specified block device %s.",
+                     path);
+        nvme_free_tree (root);
+        return FALSE;
+    }
+
+    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;
+    }
+    nvme_free_tree (root);
+
+    return TRUE;
+}
+
+
+/**
+ * bd_nvme_discovery_log_entry_free: (skip)
+ * @entry: (nullable): %BDNVMEDiscoveryLogEntry to free
+ *
+ * Frees @entry.
+ */
+void bd_nvme_discovery_log_entry_free (BDNVMEDiscoveryLogEntry *entry) {
+    if (entry == NULL)
+        return;
+
+    g_free (entry->transport_addr);
+    g_free (entry->transport_svcid);
+    g_free (entry->subsys_nqn);
+    g_free (entry);
+}
+
+/**
+ * bd_nvme_discovery_log_entry_copy: (skip)
+ * @entry: (nullable): %BDNVMEDiscoveryLogEntry to copy
+ *
+ * Creates a new copy of @entry.
+ */
+BDNVMEDiscoveryLogEntry * bd_nvme_discovery_log_entry_copy (BDNVMEDiscoveryLogEntry *entry) {
+    BDNVMEDiscoveryLogEntry *new_entry;
+
+    if (entry == NULL)
+        return NULL;
+
+    new_entry = g_new0 (BDNVMEDiscoveryLogEntry, 1);
+    memcpy (new_entry, entry, sizeof (BDNVMEDiscoveryLogEntry));
+    new_entry->transport_addr = g_strdup (entry->transport_addr);
+    new_entry->transport_svcid = g_strdup (entry->transport_svcid);
+    new_entry->subsys_nqn = g_strdup (entry->subsys_nqn);
+
+    return new_entry;
+}
+
+/**
+ * bd_nvme_discover:
+ * @discovery_ctrl: (nullable): Use existing discovery controller device or %NULL to establish a new connection.
+ * @persistent: Persistent discovery connection.
+ * @transport: The network fabric used for a NVMe-over-Fabrics network.
+ * @transport_addr: (nullable): The network address of the Controller. For transports using IP addressing (e.g. `rdma`) this should be an IP-based address.
+ * @transport_svcid: (nullable): The transport service id.  For transports using IP addressing (e.g. `rdma`) this field is the port number. By default, the IP port number for the `RDMA` transport is `4420`.
+ * @host_traddr: (nullable): The network address used on the host to connect to the Controller. For TCP, this sets the source address on the socket.
+ * @host_iface: (nullable): The network interface used on the host to connect to the Controller (e.g. IP `eth1`, `enp2s0`). This forces the connection to be made on a specific interface instead of letting the system decide.
+ * @host_nqn: (nullable): Overrides the default Host NQN that identifies the NVMe Host. If this option is %NULL, the default is read from `/etc/nvme/hostnqn` first.
+ *                        If that does not exist, the autogenerated NQN value from the NVMe Host kernel module is used next. The Host NQN uniquely identifies the NVMe Host.
+ * @host_id: (nullable): User-defined host UUID or %NULL to use default (as defined in `/etc/nvme/hostid`)
+ * @extra: (nullable) (array zero-terminated=1): Additional arguments.
+ * @error: (out) (nullable): Place to store error (if any).
+ *
+ * Performs Discovery request on a Discovery Controller. If no connection to a Discovery Controller
+ * exists (i.e. @discovery_ctrl is %NULL) a new connection is established as specified by the @transport,
+ * @transport_addr and optionally @transport_svcid arguments.
+ *
+ * Note that the `nvme-cli`'s `/etc/nvme/discovery.conf` config file is not used at the moment.
+ *
+ * Valid values for @transport include:
+ * - `"rdma"`: An rdma network (RoCE, iWARP, Infiniband, basic rdma, etc.)
+ * - `"fc"`: A Fibre Channel network.
+ * - `"tcp"`: A TCP/IP network.
+ * - `"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.
+ * - `"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.
+ * - `"queue_size"`: Number of elements in the I/O queues.
+ * - `"keep_alive_tmo"`: The keep alive timeout (in seconds).
+ * - `"reconnect_delay"`: The delay (in seconds) before reconnect is attempted after a connect loss.
+ * - `"ctrl_loss_tmo"`: The controller loss timeout period (in seconds). A special value of `-1` will cause reconnecting forever.
+ * - `"fast_io_fail_tmo"`: Fast I/O Fail timeout (in seconds).
+ * - `"tos"`: Type of service.
+ * - `"duplicate_connect"`: Allow duplicated connections between same transport host and subsystem port. Boolean value.
+ * - `"disable_sqflow"`: Disables SQ flow control to omit head doorbell update for submission queues when sending nvme completions. Boolean value.
+ * - `"hdr_digest"`: Generates/verifies header digest (TCP). Boolean value.
+ * - `"data_digest"`: Generates/verifies data digest (TCP). Boolean value.
+ * - `"tls"`: Enable TLS encryption (TCP). Boolean value.
+ * - `"hostsymname"`: TP8010: NVMe host symbolic name.
+ *
+ * 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.
+ *
+ * Returns: (transfer full) (array zero-terminated=1): null-terminated list
+ *          of discovery log entries or %NULL in case of an error (with @error set).
+ *
+ * Tech category: %BD_NVME_TECH_FABRICS-%BD_NVME_TECH_MODE_INITIATOR
+ */
+BDNVMEDiscoveryLogEntry ** bd_nvme_discover (const gchar *discovery_ctrl, gboolean persistent, 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 *hostkey = NULL;
+    const gchar *hostsymname = NULL;
+    nvme_root_t root;
+    nvme_host_t host;
+    nvme_ctrl_t ctrl = NULL;
+    struct nvme_fabrics_config cfg;
+    struct nvmf_discovery_log *log = NULL;
+    GPtrArray *ptr_array;
+    guint64 i;
+
+    if (discovery_ctrl && strncmp (discovery_ctrl, "/dev/", 5) != 0) {
+        g_set_error_literal (error, BD_NVME_ERROR, BD_NVME_ERROR_INVALID_ARGUMENT,
+                             "Invalid discovery controller device specified");
+        return NULL;
+    }
+    if (transport == NULL) {
+        g_set_error_literal (error, BD_NVME_ERROR, BD_NVME_ERROR_INVALID_ARGUMENT,
+                             "Invalid value specified for the transport argument");
+        return NULL;
+    }
+    if (transport_addr == NULL && !g_str_equal (transport, "loop") && !g_str_equal (transport, "pcie")) {
+        g_set_error_literal (error, BD_NVME_ERROR, BD_NVME_ERROR_INVALID_ARGUMENT,
+                             "Invalid value specified for the transport address argument");
+        return NULL;
+    }
+    /* TODO: nvme-cli defaults to parsing /etc/nvme/discovery.conf to retrieve missing arguments */
+
+    /* parse extra arguments */
+    nvmf_default_config (&cfg);
+    parse_extra_args (extra, &cfg, &config_file, &hostkey, NULL, &hostsymname);
+
+    host_nqn_val = g_strdup (host_nqn);
+    if (host_nqn_val == NULL)
+        host_nqn_val = nvmf_hostnqn_from_file ();
+    if (host_nqn_val == NULL)
+        host_nqn_val = nvmf_hostnqn_generate ();
+    host_id_val = g_strdup (host_id);
+    if (host_id_val == NULL)
+        host_id_val = nvmf_hostid_from_file ();
+
+    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 nqn '%s' and id '%s'",
+                     host_nqn_val, host_id_val);
+        g_free (host_nqn_val);
+        g_free (host_id_val);
+        nvme_free_tree (root);
+        return NULL;
+    }
+    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);
+
+    if (persistent && !cfg.keep_alive_tmo)
+        cfg.keep_alive_tmo = 30;
+
+    /* check the supplied discovery controller validity */
+    if (discovery_ctrl) {
+        ctrl = nvme_scan_ctrl (root, discovery_ctrl + 5);
+        if (!ctrl) {
+            g_set_error (error, BD_NVME_ERROR, BD_NVME_ERROR_NO_MATCH,
+                         "Couldn't access the discovery controller device specified: %s",
+                         strerror_l (errno, _C_LOCALE));
+            nvme_free_tree (root);
+            return NULL;
+        }
+        if (g_strcmp0 (nvme_ctrl_get_subsysnqn (ctrl), NVME_DISC_SUBSYS_NAME) != 0 ||
+            g_strcmp0 (nvme_ctrl_get_transport (ctrl), transport) != 0 ||
+            (transport_addr && g_strcmp0 (nvme_ctrl_get_traddr (ctrl), transport_addr) != 0) ||
+            (host_traddr && g_strcmp0 (nvme_ctrl_get_host_traddr (ctrl), host_traddr) != 0) ||
+            (host_iface && g_strcmp0 (nvme_ctrl_get_host_iface (ctrl), host_iface) != 0) ||
+            (transport_svcid && g_strcmp0 (nvme_ctrl_get_trsvcid (ctrl), transport_svcid) != 0)) {
+            g_set_error_literal (error, BD_NVME_ERROR, BD_NVME_ERROR_NO_MATCH,
+                                 "The existing discovery controller device specified doesn't match the specified transport arguments");
+            nvme_free_tree (root);
+            return NULL;
+        }
+        /* existing discovery controllers need to be persistent */
+        persistent = TRUE;
+    }
+    if (!ctrl) {
+        ctrl = nvme_create_ctrl (root, NVME_DISC_SUBSYS_NAME, 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);
+            return NULL;
+        }
+        nvme_ctrl_set_discovery_ctrl (ctrl, TRUE);
+        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 NULL;
+        }
+    }
+
+    /* connected, perform actual discovery */
+    ret = nvmf_get_discovery_log (ctrl, &log, MAX_DISC_RETRIES);
+    if (ret != 0) {
+        _nvme_status_to_error (ret, TRUE, error);
+        g_prefix_error (error, "NVMe Get Log Page - Discovery Log Page command error: ");
+        if (!persistent)
+            nvme_disconnect_ctrl (ctrl);
+        nvme_free_ctrl (ctrl);
+        nvme_free_tree (root);
+        return NULL;
+    }
+
+    ptr_array = g_ptr_array_new ();
+    for (i = 0; i < GUINT64_FROM_LE (log->numrec); i++) {
+        BDNVMEDiscoveryLogEntry *entry;
+        gchar *s;
+
+        entry = g_new0 (BDNVMEDiscoveryLogEntry, 1);
+        switch (log->entries[i].trtype) {
+            case NVMF_TRTYPE_RDMA:
+                entry->transport_type = BD_NVME_TRANSPORT_TYPE_RDMA;
+                break;
+            case NVMF_TRTYPE_FC:
+                entry->transport_type = BD_NVME_TRANSPORT_TYPE_FC;
+                break;
+            case NVMF_TRTYPE_TCP:
+                entry->transport_type = BD_NVME_TRANSPORT_TYPE_TCP;
+                break;
+            case NVMF_TRTYPE_LOOP:
+                entry->transport_type = BD_NVME_TRANSPORT_TYPE_LOOP;
+                break;
+            case NVMF_TRTYPE_UNSPECIFIED:
+            default:
+                entry->transport_type = BD_NVME_TRANSPORT_TYPE_UNSPECIFIED;
+        }
+        switch (log->entries[i].adrfam) {
+            case NVMF_ADDR_FAMILY_PCI:
+                entry->address_family = BD_NVME_ADDRESS_FAMILY_PCI;
+                break;
+            case NVMF_ADDR_FAMILY_IP4:
+                entry->address_family = BD_NVME_ADDRESS_FAMILY_INET;
+                break;
+            case NVMF_ADDR_FAMILY_IP6:
+                entry->address_family = BD_NVME_ADDRESS_FAMILY_INET6;
+                break;
+            case NVMF_ADDR_FAMILY_IB:
+                entry->address_family = BD_NVME_ADDRESS_FAMILY_IB;
+                break;
+            case NVMF_ADDR_FAMILY_FC:
+                entry->address_family = BD_NVME_ADDRESS_FAMILY_FC;
+                break;
+            case NVMF_ADDR_FAMILY_LOOP:
+                entry->address_family = BD_NVME_ADDRESS_FAMILY_LOOP;
+                break;
+        }
+        entry->sq_flow_control_disable = (log->entries[i].treq & NVMF_TREQ_DISABLE_SQFLOW) == NVMF_TREQ_DISABLE_SQFLOW;
+        entry->sq_flow_control_required = (log->entries[i].treq & NVMF_TREQ_REQUIRED) == NVMF_TREQ_REQUIRED;
+        entry->port_id = GUINT16_FROM_LE (log->entries[i].portid);
+        entry->ctrl_id = GUINT16_FROM_LE (log->entries[i].cntlid);
+        s = g_strndup (log->entries[i].trsvcid, NVMF_TRSVCID_SIZE);
+        entry->transport_svcid = g_strdup (g_strstrip (s));
+        g_free (s);
+        s = g_strndup (log->entries[i].traddr, NVMF_TRADDR_SIZE);
+        entry->transport_addr = g_strdup (g_strstrip (s));
+        g_free (s);
+        s = g_strndup (log->entries[i].subnqn, NVME_NQN_LENGTH);
+        entry->subsys_nqn = g_strdup (g_strstrip (s));
+        g_free (s);
+
+        if (entry->transport_type == BD_NVME_TRANSPORT_TYPE_RDMA) {
+            /* TODO: expose any of the struct nvmf_disc_log_entry.tsas.rdma attributes? */
+        }
+
+        if (entry->transport_type == BD_NVME_TRANSPORT_TYPE_TCP)
+            switch (log->entries[i].tsas.tcp.sectype) {
+                case NVMF_TCP_SECTYPE_NONE:
+                    entry->tcp_security = BD_NVME_TCP_SECURITY_NONE;
+                    break;
+                case NVMF_TCP_SECTYPE_TLS:
+                    entry->tcp_security = BD_NVME_TCP_SECURITY_TLS12;
+                    break;
+                case NVMF_TCP_SECTYPE_TLS13:
+                    entry->tcp_security = BD_NVME_TCP_SECURITY_TLS13;
+                    break;
+            }
+
+        g_ptr_array_add (ptr_array, entry);
+    }
+    g_free (log);
+    g_ptr_array_add (ptr_array, NULL);  /* trailing NULL element */
+
+    if (!persistent)
+        nvme_disconnect_ctrl (ctrl);
+    nvme_free_ctrl (ctrl);
+    nvme_free_tree (root);
+
+    return (BDNVMEDiscoveryLogEntry **) g_ptr_array_free (ptr_array, FALSE);
+}
+
+
+/**
+ * bd_nvme_find_ctrls_for_ns:
+ * @ns_sysfs_path: NVMe namespace device file.
+ * @subsysnqn: (nullable): Limit matching to the specified subsystem NQN.
+ * @host_nqn: (nullable): Limit matching to the specified host NQN.
+ * @host_id: (nullable): Limit matching to the specified host ID.
+ * @error: (out) (nullable): Place to store error (if any).
+ *
+ * A convenient utility function to look up all controllers associated
+ *  with a NVMe subsystem the specified namespace is part of.
+ *
+ * Returns: (transfer full) (array zero-terminated=1): list of controller sysfs paths
+ *          or %NULL in case of an error (with @error set).
+ *
+ * Tech category: %BD_NVME_TECH_FABRICS-%BD_NVME_TECH_MODE_INITIATOR
+ */
+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;
+    char realp[PATH_MAX];
+
+    ptr_array = g_ptr_array_new ();
+
+    root = nvme_scan (NULL);
+    g_warn_if_fail (root != NULL);
+
+    nvme_for_each_host (root, h) {
+        if (host_nqn && g_strcmp0 (nvme_host_get_hostnqn (h), host_nqn) != 0)
+            continue;
+        if (host_id && g_strcmp0 (nvme_host_get_hostid (h), host_id) != 0)
+            continue;
+
+        nvme_for_each_subsystem (h, s) {
+            gboolean found = FALSE;
+
+            if (subsysnqn && g_strcmp0 (nvme_subsystem_get_nqn (s), subsysnqn) != 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) &&
+                        g_strcmp0 (realp, ns_sysfs_path) == 0) {
+                        if (realpath (nvme_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) &&
+                    g_strcmp0 (realp, ns_sysfs_path) == 0) {
+                    found = TRUE;
+                    /* at least one of the namespaces match, don't care about the rest */
+                    break;
+                }
+
+            if (found)
+                /* add all controllers in the subsystem */
+                nvme_subsystem_for_each_ctrl (s, c) {
+                    if (realpath (nvme_ctrl_get_sysfs_dir (c), realp)) {
+                        g_ptr_array_add (ptr_array, g_strdup (realp));
+                    }
+                }
+        }
+    }
+    nvme_free_tree (root);
+
+    g_ptr_array_add (ptr_array, NULL);  /* trailing NULL element */
+    return (gchar **) g_ptr_array_free (ptr_array, FALSE);
+}
+
+
+/**
+ * bd_nvme_get_host_nqn:
+ * @error: (out) (nullable): Place to store error (if any).
+ *
+ * Reads the Host NQN (NVM Qualified Name) value from the global `/etc/nvme/hostnqn`
+ * file. An empty string is an indication that no Host NQN has been set.
+ *
+ * Returns: (transfer full): the Host NQN string or an empty string if none set.
+ *
+ * Tech category: %BD_NVME_TECH_FABRICS-%BD_NVME_TECH_MODE_INITIATOR
+ */
+gchar * bd_nvme_get_host_nqn (G_GNUC_UNUSED GError **error) {
+    char *hostnqn;
+
+    /* FIXME: libnvme SYSCONFDIR might be different from PACKAGE_SYSCONF_DIR */
+    hostnqn = nvmf_hostnqn_from_file ();
+    return hostnqn ? hostnqn : g_strdup ("");
+}
+
+/**
+ * bd_nvme_generate_host_nqn:
+ * @error: (out) (nullable): Place to store error (if any).
+ *
+ * Compute new Host NQN (NVM Qualified Name) value for the current system. This
+ * takes in account various system identifiers (DMI, device tree) with the goal
+ * of a stable unique identifier whenever feasible.
+ *
+ * Returns: (transfer full): the Host NQN string or %NULL with @error set.
+ *
+ * Tech category: %BD_NVME_TECH_FABRICS-%BD_NVME_TECH_MODE_INITIATOR
+ */
+gchar * bd_nvme_generate_host_nqn (GError **error) {
+    char *nqn;
+
+    nqn = nvmf_hostnqn_generate ();
+    if (!nqn)
+        g_set_error_literal (error, BD_NVME_ERROR, BD_NVME_ERROR_INVALID_ARGUMENT,
+                             "Unable to generate Host NQN.");
+
+    return nqn;
+}
+
+/**
+ * bd_nvme_get_host_id:
+ * @error: (out) (nullable): Place to store error (if any).
+ *
+ * Reads the Host ID value from the global `/etc/nvme/hostid` file. An empty
+ * string is an indication that no Host ID has been set.
+ *
+ * Returns: (transfer full): the Host ID string or an empty string if none set.
+ *
+ * Tech category: %BD_NVME_TECH_FABRICS-%BD_NVME_TECH_MODE_INITIATOR
+ */
+gchar * bd_nvme_get_host_id (G_GNUC_UNUSED GError **error) {
+    char *hostid;
+
+    hostid = nvmf_hostid_from_file ();
+    return hostid ? hostid : g_strdup ("");
+}
+
+/**
+ * bd_nvme_set_host_nqn:
+ * @host_nqn: The Host NVM Qualified Name.
+ * @error: (out) (nullable): Place to store error (if any).
+ *
+ * Writes the Host NQN (NVM Qualified Name) value to the system `/etc/nvme/hostnqn` file.
+ * No validation of the string is performed.
+ *
+ * Returns: %TRUE if the value was set successfully or %FALSE otherwise with @error set.
+ *
+ * Tech category: %BD_NVME_TECH_FABRICS-%BD_NVME_TECH_MODE_INITIATOR
+ */
+gboolean bd_nvme_set_host_nqn (const gchar *host_nqn, GError **error) {
+    gchar *path;
+    gchar *filename;
+    gchar *s;
+    gboolean ret;
+
+    g_return_val_if_fail (host_nqn != NULL, FALSE);
+
+    path = g_build_path (G_DIR_SEPARATOR_S, PACKAGE_SYSCONF_DIR, "nvme", NULL);
+    if (g_mkdir_with_parents (path, 0755) != 0 && errno != EEXIST) {
+        g_set_error (error, G_FILE_ERROR, g_file_error_from_errno (errno),
+                     "Error creating %s: %s",
+                     path, strerror_l (errno, _C_LOCALE));
+        g_free (path);
+        return FALSE;
+    }
+    filename = g_build_filename (path, "hostnqn", NULL);
+    if (host_nqn[strlen (host_nqn) - 1] != '\n')
+        s = g_strdup_printf ("%s\n", host_nqn);
+    else
+        s = g_strdup (host_nqn);
+    ret = g_file_set_contents (filename, s, -1, error);
+    if (ret)
+        g_chmod (filename, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH);
+
+    g_free (s);
+    g_free (path);
+    g_free (filename);
+
+    return ret;
+}
+
+/**
+ * bd_nvme_set_host_id:
+ * @host_id: The Host ID.
+ * @error: (out) (nullable): Place to store error (if any).
+ *
+ * Writes the Host ID value to the system `/etc/nvme/hostid` file.
+ * No validation of the string is performed.
+ *
+ * Returns: %TRUE if the value was set successfully or %FALSE otherwise with @error set.
+ *
+ * Tech category: %BD_NVME_TECH_FABRICS-%BD_NVME_TECH_MODE_INITIATOR
+ */
+gboolean bd_nvme_set_host_id (const gchar *host_id, GError **error) {
+    gchar *path;
+    gchar *filename;
+    gchar *s;
+    gboolean ret;
+
+    g_return_val_if_fail (host_id != NULL, FALSE);
+
+    path = g_build_path (G_DIR_SEPARATOR_S, PACKAGE_SYSCONF_DIR, "nvme", NULL);
+    if (g_mkdir_with_parents (path, 0755) != 0 && errno != EEXIST) {
+        g_set_error (error, G_FILE_ERROR, g_file_error_from_errno (errno),
+                     "Error creating %s: %s",
+                     path, strerror_l (errno, _C_LOCALE));
+        g_free (path);
+        return FALSE;
+    }
+    filename = g_build_filename (path, "hostid", NULL);
+    if (host_id[strlen (host_id) - 1] != '\n')
+        s = g_strdup_printf ("%s\n", host_id);
+    else
+        s = g_strdup (host_id);
+    ret = g_file_set_contents (filename, s, -1, error);
+    if (ret)
+        g_chmod (filename, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH);
+
+    g_free (s);
+    g_free (path);
+    g_free (filename);
+
+    return ret;
+}
diff --git a/src/plugins/nvme/nvme-info.c b/src/plugins/nvme/nvme-info.c
new file mode 100644
index 00000000..fdd90459
--- /dev/null
+++ b/src/plugins/nvme/nvme-info.c
@@ -0,0 +1,1028 @@
+/*
+ * Copyright (C) 2014-2021 Red Hat, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see <http://www.gnu.org/licenses/>.
+ *
+ * Author: Tomas Bzatek <tbzatek@redhat.com>
+ */
+
+#include <glib.h>
+#include <string.h>
+#include <stdio.h>
+#include <unistd.h>
+#include <sys/ioctl.h>
+#include <sys/stat.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <malloc.h>
+
+#include <libnvme.h>
+#include <uuid/uuid.h>
+
+#include <blockdev/utils.h>
+#include <check_deps.h>
+#include "nvme.h"
+#include "nvme-private.h"
+
+
+/**
+ * bd_nvme_controller_info_free: (skip)
+ * @info: (nullable): %BDNVMEControllerInfo to free
+ *
+ * Frees @info.
+ */
+void bd_nvme_controller_info_free (BDNVMEControllerInfo *info) {
+    if (info == NULL)
+        return;
+
+    g_free (info->fguid);
+    g_free (info->subsysnqn);
+    g_free (info->model_number);
+    g_free (info->serial_number);
+    g_free (info->firmware_ver);
+    g_free (info->nvme_ver);
+    g_free (info);
+}
+
+/**
+ * bd_nvme_controller_info_copy: (skip)
+ * @info: (nullable): %BDNVMEControllerInfo to copy
+ *
+ * Creates a new copy of @info.
+ */
+BDNVMEControllerInfo * bd_nvme_controller_info_copy (BDNVMEControllerInfo *info) {
+    BDNVMEControllerInfo *new_info;
+
+    if (info == NULL)
+        return NULL;
+
+    new_info = g_new0 (BDNVMEControllerInfo, 1);
+    memcpy (new_info, info, sizeof (BDNVMEControllerInfo));
+    new_info->fguid = g_strdup (info->fguid);
+    new_info->subsysnqn = g_strdup (info->subsysnqn);
+    new_info->model_number = g_strdup (info->model_number);
+    new_info->serial_number = g_strdup (info->serial_number);
+    new_info->firmware_ver = g_strdup (info->firmware_ver);
+    new_info->nvme_ver = g_strdup (info->nvme_ver);
+
+    return new_info;
+}
+
+/**
+ * bd_nvme_lba_format_free: (skip)
+ * @fmt: (nullable): %BDNVMELBAFormat to free
+ *
+ * Frees @fmt.
+ */
+void bd_nvme_lba_format_free (BDNVMELBAFormat *fmt) {
+    g_free (fmt);
+}
+
+/**
+ * bd_nvme_lba_format_copy: (skip)
+ * @fmt: (nullable): %BDNVMELBAFormat to copy
+ *
+ * Creates a new copy of @fmt.
+ */
+BDNVMELBAFormat * bd_nvme_lba_format_copy (BDNVMELBAFormat *fmt) {
+    BDNVMELBAFormat *new_fmt;
+
+    if (fmt == NULL)
+        return NULL;
+
+    new_fmt = g_new0 (BDNVMELBAFormat, 1);
+    new_fmt->data_size = fmt->data_size;
+    new_fmt->metadata_size = fmt->metadata_size;
+    new_fmt->relative_performance = fmt->relative_performance;
+
+    return new_fmt;
+}
+
+/**
+ * bd_nvme_namespace_info_free: (skip)
+ * @info: (nullable): %BDNVMENamespaceInfo to free
+ *
+ * Frees @info.
+ */
+void bd_nvme_namespace_info_free (BDNVMENamespaceInfo *info) {
+    BDNVMELBAFormat **lba_formats;
+
+    if (info == NULL)
+        return;
+
+    g_free (info->eui64);
+    g_free (info->uuid);
+    g_free (info->nguid);
+
+    for (lba_formats = info->lba_formats; lba_formats && *lba_formats; lba_formats++)
+        bd_nvme_lba_format_free (*lba_formats);
+    g_free (info->lba_formats);
+    g_free (info);
+}
+
+/**
+ * bd_nvme_namespace_info_copy: (skip)
+ * @info: (nullable): %BDNVMENamespaceInfo to copy
+ *
+ * Creates a new copy of @info.
+ */
+BDNVMENamespaceInfo * bd_nvme_namespace_info_copy (BDNVMENamespaceInfo *info) {
+    BDNVMENamespaceInfo *new_info;
+    BDNVMELBAFormat **lba_formats;
+    GPtrArray *ptr_array;
+
+    if (info == NULL)
+        return NULL;
+
+    new_info = g_new0 (BDNVMENamespaceInfo, 1);
+    memcpy (new_info, info, sizeof (BDNVMENamespaceInfo));
+    new_info->eui64 = g_strdup (info->eui64);
+    new_info->uuid = g_strdup (info->uuid);
+    new_info->nguid = g_strdup (info->nguid);
+
+    ptr_array = g_ptr_array_new ();
+    for (lba_formats = info->lba_formats; lba_formats && *lba_formats; lba_formats++)
+        g_ptr_array_add (ptr_array, bd_nvme_lba_format_copy (*lba_formats));
+    g_ptr_array_add (ptr_array, NULL);
+    new_info->lba_formats = (BDNVMELBAFormat **) g_ptr_array_free (ptr_array, FALSE);
+
+    return new_info;
+}
+
+/**
+ * bd_nvme_smart_log_free: (skip)
+ * @log: (nullable): %BDNVMESmartLog to free
+ *
+ * Frees @log.
+ */
+void bd_nvme_smart_log_free (BDNVMESmartLog *log) {
+    g_free (log);
+}
+
+/**
+ * bd_nvme_smart_log_copy: (skip)
+ * @log: (nullable): %BDNVMESmartLog to copy
+ *
+ * Creates a new copy of @log.
+ */
+BDNVMESmartLog * bd_nvme_smart_log_copy (BDNVMESmartLog *log) {
+    BDNVMESmartLog *new_log;
+
+    if (log == NULL)
+        return NULL;
+
+    new_log = g_new0 (BDNVMESmartLog, 1);
+    memcpy (new_log, log, sizeof (BDNVMESmartLog));
+
+    return new_log;
+}
+
+/**
+ * bd_nvme_error_log_entry_free: (skip)
+ * @entry: (nullable): %BDNVMEErrorLogEntry to free
+ *
+ * Frees @entry.
+ */
+void bd_nvme_error_log_entry_free (BDNVMEErrorLogEntry *entry) {
+    if (entry == NULL)
+        return;
+
+    if (entry->command_error)
+        g_error_free (entry->command_error);
+    g_free (entry);
+}
+
+/**
+ * bd_nvme_error_log_entry_copy: (skip)
+ * @entry: (nullable): %BDNVMEErrorLogEntry to copy
+ *
+ * Creates a new copy of @entry.
+ */
+BDNVMEErrorLogEntry * bd_nvme_error_log_entry_copy (BDNVMEErrorLogEntry *entry) {
+    BDNVMEErrorLogEntry *new_entry;
+
+    if (entry == NULL)
+        return NULL;
+
+    new_entry = g_new0 (BDNVMEErrorLogEntry, 1);
+    memcpy (new_entry, entry, sizeof (BDNVMEErrorLogEntry));
+    if (entry->command_error)
+        new_entry->command_error = g_error_copy (entry->command_error);
+
+    return new_entry;
+}
+
+/**
+ * bd_nvme_self_test_log_entry_free: (skip)
+ * @entry: (nullable): %BDNVMESelfTestLogEntry to free
+ *
+ * Frees @entry.
+ */
+void bd_nvme_self_test_log_entry_free (BDNVMESelfTestLogEntry *entry) {
+    if (entry == NULL)
+        return;
+
+    if (entry->status_code_error)
+        g_error_free (entry->status_code_error);
+    g_free (entry);
+}
+
+/**
+ * bd_nvme_self_test_log_entry_copy: (skip)
+ * @entry: (nullable): %BDNVMESelfTestLogEntry to copy
+ *
+ * Creates a new copy of @entry.
+ */
+BDNVMESelfTestLogEntry * bd_nvme_self_test_log_entry_copy (BDNVMESelfTestLogEntry *entry) {
+    BDNVMESelfTestLogEntry *new_entry;
+
+    if (entry == NULL)
+        return NULL;
+
+    new_entry = g_new0 (BDNVMESelfTestLogEntry, 1);
+    memcpy (new_entry, entry, sizeof (BDNVMESelfTestLogEntry));
+    if (entry->status_code_error)
+        new_entry->status_code_error = g_error_copy (entry->status_code_error);
+
+    return new_entry;
+}
+
+/**
+ * bd_nvme_self_test_result_to_string:
+ * @result: A %BDNVMESelfTestResult.
+ * @error: (out) (optional): place to store error (if any)
+ *
+ * Returns: (transfer none): A string representation of @result for use as an identifier string
+ *                           or %NULL when the code is unknown.
+ */
+const gchar * bd_nvme_self_test_result_to_string (BDNVMESelfTestResult result, GError **error) {
+    static const gchar * const str[] = {
+        [BD_NVME_SELF_TEST_RESULT_NO_ERROR] = "success",
+        [BD_NVME_SELF_TEST_RESULT_ABORTED] = "aborted",
+        [BD_NVME_SELF_TEST_RESULT_CTRL_RESET] = "ctrl_reset",
+        [BD_NVME_SELF_TEST_RESULT_NS_REMOVED] = "ns_removed",
+        [BD_NVME_SELF_TEST_RESULT_ABORTED_FORMAT] = "aborted_format",
+        [BD_NVME_SELF_TEST_RESULT_FATAL_ERROR] = "fatal_error",
+        [BD_NVME_SELF_TEST_RESULT_UNKNOWN_SEG_FAIL] = "unknown_seg_fail",
+        [BD_NVME_SELF_TEST_RESULT_KNOWN_SEG_FAIL] = "known_seg_fail",
+        [BD_NVME_SELF_TEST_RESULT_ABORTED_UNKNOWN] = "aborted_unknown",
+        [BD_NVME_SELF_TEST_RESULT_ABORTED_SANITIZE] = "aborted_sanitize"
+    };
+
+    if (result >= G_N_ELEMENTS (str)) {
+        g_set_error (error, BD_NVME_ERROR, BD_NVME_ERROR_INVALID_ARGUMENT,
+                     "Invalid result code %d", result);
+        return NULL;
+    }
+
+    return str[result];
+}
+
+/**
+ * bd_nvme_self_test_log_free: (skip)
+ * @log: (nullable): %BDNVMESelfTestLog to free
+ *
+ * Frees @log.
+ */
+void bd_nvme_self_test_log_free (BDNVMESelfTestLog *log) {
+    BDNVMESelfTestLogEntry **entries;
+
+    if (log == NULL)
+        return;
+
+    for (entries = log->entries; entries && *entries; entries++)
+        bd_nvme_self_test_log_entry_free (*entries);
+    g_free (log->entries);
+    g_free (log);
+}
+
+/**
+ * bd_nvme_self_test_log_copy: (skip)
+ * @log: (nullable): %BDNVMESelfTestLog to copy
+ *
+ * Creates a new copy of @log.
+ */
+BDNVMESelfTestLog * bd_nvme_self_test_log_copy (BDNVMESelfTestLog *log) {
+    BDNVMESelfTestLog *new_log;
+    BDNVMESelfTestLogEntry **entries;
+    GPtrArray *ptr_array;
+
+    if (log == NULL)
+        return NULL;
+
+    new_log = g_new0 (BDNVMESelfTestLog, 1);
+    memcpy (new_log, log, sizeof (BDNVMESelfTestLog));
+
+    ptr_array = g_ptr_array_new ();
+    for (entries = log->entries; entries && *entries; entries++)
+        g_ptr_array_add (ptr_array, bd_nvme_self_test_log_entry_copy (*entries));
+    g_ptr_array_add (ptr_array, NULL);
+    new_log->entries = (BDNVMESelfTestLogEntry **) g_ptr_array_free (ptr_array, FALSE);
+
+    return new_log;
+}
+
+
+/**
+ * bd_nvme_sanitize_log_free: (skip)
+ * @log: (nullable): %BDNVMESanitizeLog to free
+ *
+ * Frees @log.
+ */
+void bd_nvme_sanitize_log_free (BDNVMESanitizeLog *log) {
+    if (log == NULL)
+        return;
+
+    g_free (log);
+}
+
+/**
+ * bd_nvme_sanitize_log_copy: (skip)
+ * @log: (nullable): %BDNVMESanitizeLog to copy
+ *
+ * Creates a new copy of @log.
+ */
+BDNVMESanitizeLog * bd_nvme_sanitize_log_copy (BDNVMESanitizeLog *log) {
+    BDNVMESanitizeLog *new_log;
+
+    if (log == NULL)
+        return NULL;
+
+    new_log = g_new0 (BDNVMESanitizeLog, 1);
+    memcpy (new_log, log, sizeof (BDNVMESanitizeLog));
+
+    return new_log;
+}
+
+
+static guint64 int128_to_guint64 (__u8 *data)
+{
+    int i;
+    guint64 result = 0;
+
+    /* FIXME: would overflow, need to find 128-bit int */
+    for (i = 0; i < 16; i++) {
+        result *= 256;
+        result += data[15 - i];
+    }
+    return result;
+}
+
+gint _open_dev (const gchar *device, GError **error) {
+    int fd;
+
+    fd = open (device, O_RDONLY);
+    if (fd < 0) {
+        _nvme_status_to_error (-1, FALSE, error);
+        g_prefix_error (error, "Failed to open device '%s': ", device);
+        return -1;
+    }
+
+    return fd;
+}
+
+static gchar *decode_nvme_rev (guint32 ver) {
+    guint16 mjr;
+    guint8 mnr, ter = 0;
+
+    mjr = ver >> 16;
+    mnr = (ver >> 8) & 0xFF;
+    /* 'ter' is only valid for >= 1.2.1 */
+    if (mjr >= 2 || mnr >= 2)
+        ter = ver & 0xFF;
+
+    if (ter == 0)
+        return g_strdup_printf ("%u.%u", mjr, mnr);
+    else
+        return g_strdup_printf ("%u.%u.%u", mjr, mnr, ter);
+}
+
+/**
+ * bd_nvme_get_controller_info:
+ * @device: a NVMe controller device (e.g. `/dev/nvme0`)
+ * @error: (out) (nullable): place to store error (if any)
+ *
+ * Retrieves information about the NVMe controller (the Identify Controller command)
+ * as specified by the @device block device path.
+ *
+ * Returns: (transfer full): information about given controller or %NULL in case of an error (with @error set).
+ *
+ * Tech category: %BD_NVME_TECH_NVME-%BD_NVME_TECH_MODE_INFO
+ */
+BDNVMEControllerInfo * bd_nvme_get_controller_info (const gchar *device, GError **error) {
+    int ret;
+    int fd;
+    struct nvme_id_ctrl ctrl_id = ZERO_INIT;
+    BDNVMEControllerInfo *info;
+
+    /* open the block device */
+    fd = _open_dev (device, error);
+    if (fd < 0)
+        return NULL;
+
+    /* send the NVME_IDENTIFY_CNS_NS + NVME_IDENTIFY_CNS_CTRL ioctl */
+    ret = nvme_identify_ctrl (fd, &ctrl_id);
+    if (ret != 0) {
+        _nvme_status_to_error (ret, FALSE, error);
+        g_prefix_error (error, "NVMe Identify Controller command error: ");
+        close (fd);
+        return NULL;
+    }
+    close (fd);
+
+    info = g_new0 (BDNVMEControllerInfo, 1);
+    if ((ctrl_id.cmic & NVME_CTRL_CMIC_MULTI_PORT) == NVME_CTRL_CMIC_MULTI_PORT)
+        info->features |= BD_NVME_CTRL_FEAT_MULTIPORT;
+    if ((ctrl_id.cmic & NVME_CTRL_CMIC_MULTI_CTRL) == NVME_CTRL_CMIC_MULTI_CTRL)
+        info->features |= BD_NVME_CTRL_FEAT_MULTICTRL;
+    if ((ctrl_id.cmic & NVME_CTRL_CMIC_MULTI_SRIOV) == NVME_CTRL_CMIC_MULTI_SRIOV)
+        info->features |= BD_NVME_CTRL_FEAT_SRIOV;
+    if ((ctrl_id.cmic & NVME_CTRL_CMIC_MULTI_ANA_REPORTING) == NVME_CTRL_CMIC_MULTI_ANA_REPORTING)
+        info->features |= BD_NVME_CTRL_FEAT_ANA_REPORTING;
+    if ((ctrl_id.nvmsr & NVME_CTRL_NVMSR_NVMESD) == NVME_CTRL_NVMSR_NVMESD)
+        info->features |= BD_NVME_CTRL_FEAT_STORAGE_DEVICE;
+    if ((ctrl_id.nvmsr & NVME_CTRL_NVMSR_NVMEE) == NVME_CTRL_NVMSR_NVMEE)
+        info->features |= BD_NVME_CTRL_FEAT_ENCLOSURE;
+    if ((ctrl_id.mec & NVME_CTRL_MEC_PCIEME) == NVME_CTRL_MEC_PCIEME)
+        info->features |= BD_NVME_CTRL_FEAT_MGMT_PCIE;
+    if ((ctrl_id.mec & NVME_CTRL_MEC_SMBUSME) == NVME_CTRL_MEC_SMBUSME)
+        info->features |= BD_NVME_CTRL_FEAT_MGMT_SMBUS;
+    info->pci_vendor_id = GUINT16_FROM_LE (ctrl_id.vid);
+    info->pci_subsys_vendor_id = GUINT16_FROM_LE (ctrl_id.ssvid);
+    info->ctrl_id = GUINT16_FROM_LE (ctrl_id.cntlid);
+    /* TODO: decode fguid as 128-bit hex string? */
+    info->fguid = g_strdup_printf ("%-.*s", (int) sizeof (ctrl_id.fguid), ctrl_id.fguid);
+    g_strstrip (info->fguid);
+    info->model_number = g_strndup (ctrl_id.mn, sizeof (ctrl_id.mn));
+    g_strstrip (info->model_number);
+    info->serial_number = g_strndup (ctrl_id.sn, sizeof (ctrl_id.sn));
+    g_strstrip (info->serial_number);
+    info->firmware_ver = g_strndup (ctrl_id.fr, sizeof (ctrl_id.fr));
+    g_strstrip (info->firmware_ver);
+    info->nvme_ver = decode_nvme_rev (GUINT32_FROM_LE (ctrl_id.ver));
+    /* TODO: vwci: VPD Write Cycle Information */
+    if ((ctrl_id.oacs & NVME_CTRL_OACS_FORMAT) == NVME_CTRL_OACS_FORMAT)
+        info->features |= BD_NVME_CTRL_FEAT_FORMAT;
+    if ((ctrl_id.oacs & NVME_CTRL_OACS_NS_MGMT) == NVME_CTRL_OACS_NS_MGMT)
+        info->features |= BD_NVME_CTRL_FEAT_NS_MGMT;
+    if ((ctrl_id.oacs & NVME_CTRL_OACS_SELF_TEST) == NVME_CTRL_OACS_SELF_TEST)
+        info->features |= BD_NVME_CTRL_FEAT_SELFTEST;
+    switch (ctrl_id.cntrltype) {
+        case NVME_CTRL_CNTRLTYPE_IO:
+            info->controller_type = BD_NVME_CTRL_TYPE_IO;
+            break;
+        case NVME_CTRL_CNTRLTYPE_DISCOVERY:
+            info->controller_type = BD_NVME_CTRL_TYPE_DISCOVERY;
+            break;
+        case NVME_CTRL_CNTRLTYPE_ADMIN:
+            info->controller_type = BD_NVME_CTRL_TYPE_ADMIN;
+            break;
+        default:
+            info->controller_type = BD_NVME_CTRL_TYPE_UNKNOWN;
+    }
+    info->hmb_pref_size = GUINT32_FROM_LE (ctrl_id.hmpre) * 4096LL;
+    info->hmb_min_size = GUINT32_FROM_LE (ctrl_id.hmmin) * 4096LL;
+    info->size_total = int128_to_guint64 (ctrl_id.tnvmcap);
+    info->size_unalloc = int128_to_guint64 (ctrl_id.unvmcap);
+    info->selftest_ext_time = GUINT16_FROM_LE (ctrl_id.edstt);
+    /* TODO: lpa: Log Page Attributes - NVME_CTRL_LPA_PERSETENT_EVENT: Persistent Event log */
+    if ((ctrl_id.dsto & NVME_CTRL_DSTO_ONE_DST) == NVME_CTRL_DSTO_ONE_DST)
+        info->features |= BD_NVME_CTRL_FEAT_SELFTEST_SINGLE;
+    if ((ctrl_id.sanicap & NVME_CTRL_SANICAP_CES) == NVME_CTRL_SANICAP_CES)
+        info->features |= BD_NVME_CTRL_FEAT_SANITIZE_CRYPTO;
+    if ((ctrl_id.sanicap & NVME_CTRL_SANICAP_BES) == NVME_CTRL_SANICAP_BES)
+        info->features |= BD_NVME_CTRL_FEAT_SANITIZE_BLOCK;
+    if ((ctrl_id.sanicap & NVME_CTRL_SANICAP_OWS) == NVME_CTRL_SANICAP_OWS)
+        info->features |= BD_NVME_CTRL_FEAT_SANITIZE_OVERWRITE;
+    /* struct nvme_id_ctrl.nn: If the &struct nvme_id_ctrl.mnan field is cleared to 0h,
+     * then the struct nvme_id_ctrl.nn field also indicates the maximum number of namespaces
+     * supported by the NVM subsystem.
+     */
+    info->num_namespaces = GUINT32_FROM_LE (ctrl_id.mnan) == 0 ? GUINT32_FROM_LE (ctrl_id.nn) : GUINT32_FROM_LE (ctrl_id.mnan);
+    if ((ctrl_id.fna & NVME_CTRL_FNA_FMT_ALL_NAMESPACES) == NVME_CTRL_FNA_FMT_ALL_NAMESPACES)
+        info->features |= BD_NVME_CTRL_FEAT_FORMAT_ALL_NS;
+    if ((ctrl_id.fna & NVME_CTRL_FNA_SEC_ALL_NAMESPACES) == NVME_CTRL_FNA_SEC_ALL_NAMESPACES)
+        info->features |= BD_NVME_CTRL_FEAT_SECURE_ERASE_ALL_NS;
+    if ((ctrl_id.fna & NVME_CTRL_FNA_CRYPTO_ERASE) == NVME_CTRL_FNA_CRYPTO_ERASE)
+        info->features |= BD_NVME_CTRL_FEAT_SECURE_ERASE_CRYPTO;
+    /* TODO: enum nvme_id_ctrl_oncs: NVME_CTRL_ONCS_WRITE_UNCORRECTABLE, NVME_CTRL_ONCS_WRITE_ZEROES... */
+    /* TODO: nwpc: Namespace Write Protection Capabilities */
+    info->subsysnqn = g_strndup (ctrl_id.subnqn, sizeof (ctrl_id.subnqn));
+    g_strstrip (info->subsysnqn);
+
+    return info;
+}
+
+
+/**
+ * bd_nvme_get_namespace_info:
+ * @device: a NVMe namespace device (e.g. `/dev/nvme0n1`)
+ * @error: (out) (nullable): place to store error (if any)
+ *
+ * Retrieves information about the NVMe namespace (the Identify Namespace command)
+ * as specified by the @device block device path.
+ *
+ * Returns: (transfer full): information about given namespace or %NULL in case of an error (with @error set).
+ *
+ * Tech category: %BD_NVME_TECH_NVME-%BD_NVME_TECH_MODE_INFO
+ */
+BDNVMENamespaceInfo *bd_nvme_get_namespace_info (const gchar *device, GError **error) {
+    int ret;
+    int ret_desc;
+    int fd;
+    __u32 nsid = 0;
+    struct nvme_id_ns ns_info = ZERO_INIT;
+    uint8_t desc[NVME_IDENTIFY_DATA_SIZE] = ZERO_INIT;
+    guint8 flbas;
+    guint i;
+    guint len;
+    BDNVMENamespaceInfo *info;
+    GPtrArray *ptr_array;
+
+    /* open the block device */
+    fd = _open_dev (device, error);
+    if (fd < 0)
+        return NULL;
+
+    /* get Namespace Identifier (NSID) for the @device (NVME_IOCTL_ID) */
+    ret = nvme_get_nsid (fd, &nsid);
+    if (ret != 0) {
+        _nvme_status_to_error (ret, FALSE, error);
+        g_prefix_error (error, "Error getting Namespace Identifier (NSID): ");
+        close (fd);
+        return NULL;
+    }
+
+    /* send the NVME_IDENTIFY_CNS_NS ioctl */
+    ret_desc = nvme_identify_ns_descs (fd, nsid, (struct nvme_ns_id_desc *) &desc);
+    ret = nvme_identify_ns (fd, nsid, &ns_info);
+    if (ret != 0) {
+        _nvme_status_to_error (ret, FALSE, error);
+        g_prefix_error (error, "NVMe Identify Namespace command error: ");
+        close (fd);
+        return NULL;
+    }
+    close (fd);
+
+    info = g_new0 (BDNVMENamespaceInfo, 1);
+    info->nsid = nsid;
+    info->nsize = GUINT64_FROM_LE (ns_info.nsze);
+    info->ncap = GUINT64_FROM_LE (ns_info.ncap);
+    info->nuse = GUINT64_FROM_LE (ns_info.nuse);
+    if ((ns_info.nsfeat & NVME_NS_FEAT_THIN) == NVME_NS_FEAT_THIN)
+        info->features |= BD_NVME_NS_FEAT_THIN;
+    if ((ns_info.nmic & NVME_NS_NMIC_SHARED) == NVME_NS_NMIC_SHARED)
+        info->features |= BD_NVME_NS_FEAT_MULTIPATH_SHARED;
+    if ((ns_info.fpi & NVME_NS_FPI_SUPPORTED) == NVME_NS_FPI_SUPPORTED)
+        info->features |= BD_NVME_NS_FEAT_FORMAT_PROGRESS;
+    info->format_progress_remaining = ns_info.fpi & NVME_NS_FPI_REMAINING;
+    /* TODO: what the ns_info.nvmcap really stands for? */
+    info->write_protected = (ns_info.nsattr & NVME_NS_NSATTR_WRITE_PROTECTED) == NVME_NS_NSATTR_WRITE_PROTECTED;
+    info->nguid = g_malloc0 (sizeof (ns_info.nguid) * 2 + 1);
+    for (i = 0; i < G_N_ELEMENTS (ns_info.nguid); i++)
+        snprintf (info->nguid + i * 2, 3, "%02x", ns_info.nguid[i]);
+    info->eui64 = g_malloc0 (sizeof (ns_info.eui64) * 2 + 1);
+    for (i = 0; i < G_N_ELEMENTS (ns_info.eui64); i++)
+        snprintf (info->eui64 + i * 2, 3, "%02x", ns_info.eui64[i]);
+    if (ret_desc == 0) {
+        for (i = 0; i < NVME_IDENTIFY_DATA_SIZE; i += len) {
+            struct nvme_ns_id_desc *d = (void *) desc + i;
+            gchar uuid_buf[37] = ZERO_INIT;
+
+            if (!d->nidl)
+                break;
+            len = d->nidl + sizeof (*d);
+
+            switch (d->nidt) {
+                case NVME_NIDT_EUI64:
+                case NVME_NIDT_NGUID:
+                    /* already have these from nvme_identify_ns() */
+                    break;
+                case NVME_NIDT_UUID:
+                    uuid_unparse (d->nid, uuid_buf);
+                    info->uuid = g_strdup (uuid_buf);
+                    break;
+                case NVME_NIDT_CSI:
+                    /* unused */
+                    break;
+            }
+        }
+    }
+
+    /* translate the LBA Format array */
+    ptr_array = g_ptr_array_new ();
+    nvme_id_ns_flbas_to_lbaf_inuse (ns_info.flbas, &flbas);
+    for (i = 0; i <= ns_info.nlbaf + ns_info.nulbaf; i++) {
+        BDNVMELBAFormat *lbaf = g_new0 (BDNVMELBAFormat, 1);
+        lbaf->data_size = 1 << ns_info.lbaf[i].ds;
+        lbaf->metadata_size = GUINT16_FROM_LE (ns_info.lbaf[i].ms);
+        lbaf->relative_performance = ns_info.lbaf[i].rp + 1;
+        g_ptr_array_add (ptr_array, lbaf);
+        if (i == flbas) {
+            info->current_lba_format.data_size = lbaf->data_size;
+            info->current_lba_format.metadata_size = lbaf->metadata_size;
+            info->current_lba_format.relative_performance = lbaf->relative_performance;
+        }
+    }
+    g_ptr_array_add (ptr_array, NULL);  /* trailing NULL element */
+    info->lba_formats = (BDNVMELBAFormat **) g_ptr_array_free (ptr_array, FALSE);
+
+    return info;
+}
+
+
+/**
+ * bd_nvme_get_smart_log:
+ * @device: a NVMe controller device (e.g. `/dev/nvme0`)
+ * @error: (out) (nullable): place to store error (if any)
+ *
+ * Retrieves drive SMART and general health information (Log Identifier `02h`).
+ * The information provided is over the life of the controller and is retained across power cycles.
+ *
+ * Returns: (transfer full): health log data or %NULL in case of an error (with @error set).
+ *
+ * Tech category: %BD_NVME_TECH_NVME-%BD_NVME_TECH_MODE_INFO
+ */
+BDNVMESmartLog * bd_nvme_get_smart_log (const gchar *device, GError **error) {
+    int ret;
+    int ret_identify;
+    int fd;
+    struct nvme_id_ctrl ctrl_id = ZERO_INIT;
+    struct nvme_smart_log smart_log = ZERO_INIT;
+    BDNVMESmartLog *log;
+    guint i;
+
+    /* open the block device */
+    fd = _open_dev (device, error);
+    if (fd < 0)
+        return NULL;
+
+    /* send the NVME_IDENTIFY_CNS_NS + NVME_IDENTIFY_CNS_CTRL ioctl */
+    ret_identify = nvme_identify_ctrl (fd, &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);
+        return NULL;
+    }
+
+    /* send the NVME_LOG_LID_SMART ioctl */
+    ret = nvme_get_log_smart (fd, NVME_NSID_ALL, FALSE /* rae */, &smart_log);
+    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);
+        return NULL;
+    }
+    close (fd);
+
+    log = g_new0 (BDNVMESmartLog, 1);
+    if ((smart_log.critical_warning & NVME_SMART_CRIT_SPARE) == NVME_SMART_CRIT_SPARE)
+        log->critical_warning |= BD_NVME_SMART_CRITICAL_WARNING_SPARE;
+    if ((smart_log.critical_warning & NVME_SMART_CRIT_TEMPERATURE) == NVME_SMART_CRIT_TEMPERATURE)
+        log->critical_warning |= BD_NVME_SMART_CRITICAL_WARNING_TEMPERATURE;
+    if ((smart_log.critical_warning & NVME_SMART_CRIT_DEGRADED) == NVME_SMART_CRIT_DEGRADED)
+        log->critical_warning |= BD_NVME_SMART_CRITICAL_WARNING_DEGRADED;
+    if ((smart_log.critical_warning & NVME_SMART_CRIT_MEDIA) == NVME_SMART_CRIT_MEDIA)
+        log->critical_warning |= BD_NVME_SMART_CRITICAL_WARNING_READONLY;
+    if ((smart_log.critical_warning & NVME_SMART_CRIT_VOLATILE_MEMORY) == NVME_SMART_CRIT_VOLATILE_MEMORY)
+        log->critical_warning |= BD_NVME_SMART_CRITICAL_WARNING_VOLATILE_MEM;
+    if ((smart_log.critical_warning & NVME_SMART_CRIT_PMR_RO) == NVME_SMART_CRIT_PMR_RO)
+        log->critical_warning |= BD_NVME_SMART_CRITICAL_WARNING_PMR_READONLY;
+    log->avail_spare = smart_log.avail_spare;
+    log->spare_thresh = smart_log.spare_thresh;
+    log->percent_used = smart_log.percent_used;
+    log->total_data_read = int128_to_guint64 (smart_log.data_units_read) * 1000 * 512;
+    log->total_data_written = int128_to_guint64 (smart_log.data_units_written) * 1000 * 512;
+    log->ctrl_busy_time = int128_to_guint64 (smart_log.ctrl_busy_time);
+    log->power_cycles = int128_to_guint64 (smart_log.power_cycles);
+    log->power_on_hours = int128_to_guint64 (smart_log.power_on_hours);
+    log->unsafe_shutdowns = int128_to_guint64 (smart_log.unsafe_shutdowns);
+    log->media_errors = int128_to_guint64 (smart_log.media_errors);
+    log->num_err_log_entries = int128_to_guint64 (smart_log.num_err_log_entries);
+
+    log->temperature = (smart_log.temperature[1] << 8) | smart_log.temperature[0];
+    for (i = 0; i < G_N_ELEMENTS (smart_log.temp_sensor); i++)
+        log->temp_sensors[i] = GUINT16_FROM_LE (smart_log.temp_sensor[i]);
+    log->warning_temp_time = GUINT32_FROM_LE (smart_log.warning_temp_time);
+    log->critical_temp_time = GUINT32_FROM_LE (smart_log.critical_comp_time);
+
+    if (ret_identify == 0) {
+        log->wctemp = GUINT16_FROM_LE (ctrl_id.wctemp);
+        log->cctemp = GUINT16_FROM_LE (ctrl_id.cctemp);
+    }
+
+    /* FIXME: intentionally not providing Host Controlled Thermal Management attributes
+     *        at the moment (an optional NVMe feature), along with intentionally not providing
+     *        Power State attributes. Subject to re-evaluation in the future.
+     */
+
+    return log;
+}
+
+
+/**
+ * bd_nvme_get_error_log_entries:
+ * @device: a NVMe controller device (e.g. `/dev/nvme0`)
+ * @error: (out) (nullable): place to store error (if any)
+ *
+ * Retrieves Error Information Log (Log Identifier `01h`) entries, used to describe
+ * extended error information for a command that completed with error or to report
+ * an error that is not specific to a particular command. This log is global to the
+ * controller. The ordering of the entries is based on the time when the error
+ * occurred, with the most recent error being returned as the first log entry.
+ * As the number of entries is typically limited by the drive implementation, only
+ * most recent entries are provided.
+ *
+ * Returns: (transfer full) (array zero-terminated=1): null-terminated list
+ *          of error entries or %NULL in case of an error (with @error set).
+ *
+ * Tech category: %BD_NVME_TECH_NVME-%BD_NVME_TECH_MODE_INFO
+ */
+BDNVMEErrorLogEntry ** bd_nvme_get_error_log_entries (const gchar *device, GError **error) {
+    int ret;
+    int fd;
+    guint elpe;
+    struct nvme_id_ctrl ctrl_id = ZERO_INIT;
+    struct nvme_error_log_page *err_log;
+    GPtrArray *ptr_array;
+    guint i;
+
+    /* open the block device */
+    fd = _open_dev (device, error);
+    if (fd < 0)
+        return NULL;
+
+    /* find out the maximum number of error log entries as reported by the controller */
+    ret = nvme_identify_ctrl (fd, &ctrl_id);
+    if (ret != 0) {
+        _nvme_status_to_error (ret, FALSE, error);
+        g_prefix_error (error, "NVMe Identify Controller command error: ");
+        close (fd);
+        return NULL;
+    }
+
+    /* send the NVME_LOG_LID_ERROR ioctl */
+    elpe = ctrl_id.elpe + 1;
+    err_log = g_new0 (struct nvme_error_log_page, elpe);
+    ret = nvme_get_log_error (fd, elpe, FALSE /* rae */, err_log);
+    if (ret != 0) {
+        _nvme_status_to_error (ret, FALSE, error);
+        g_prefix_error (error, "NVMe Get Log Page - Error Information Log Entry command error: ");
+        g_free (err_log);
+        close (fd);
+        return NULL;
+    }
+    close (fd);
+
+    /* parse the log */
+    ptr_array = g_ptr_array_new ();
+    for (i = 0; i < elpe; i++) {
+        if (GUINT64_FROM_LE (err_log[i].error_count) > 0) {
+            BDNVMEErrorLogEntry *entry;
+
+            entry = g_new0 (BDNVMEErrorLogEntry, 1);
+            entry->error_count = GUINT64_FROM_LE (err_log[i].error_count);
+            entry->command_id = err_log[i].cmdid;
+            entry->command_specific = GUINT64_FROM_LE (err_log[i].cs);
+            entry->command_status = GUINT16_FROM_LE (err_log[i].status_field) >> 1;
+            _nvme_status_to_error (GUINT16_FROM_LE (err_log[i].status_field) >> 1, FALSE, &entry->command_error);
+            entry->lba = GUINT64_FROM_LE (err_log[i].lba);
+            entry->nsid = err_log[i].nsid;
+            entry->transport_type = err_log[i].trtype;
+            /* not providing Transport Type Specific Information here on purpose */
+
+            g_ptr_array_add (ptr_array, entry);
+        }
+    }
+    g_ptr_array_add (ptr_array, NULL);  /* trailing NULL element */
+    g_free (err_log);
+
+    return (BDNVMEErrorLogEntry **) g_ptr_array_free (ptr_array, FALSE);
+}
+
+
+/**
+ * bd_nvme_get_self_test_log:
+ * @device: a NVMe controller device (e.g. `/dev/nvme0`)
+ * @error: (out) (nullable): place to store error (if any)
+ *
+ * Retrieves drive self-test log (Log Identifier `06h`). Provides the status of a self-test operation
+ * in progress and the percentage complete of that operation, along with the results of the last
+ * 20 device self-test operations.
+ *
+ * Returns: (transfer full): self-test log data or %NULL in case of an error (with @error set).
+ *
+ * Tech category: %BD_NVME_TECH_NVME-%BD_NVME_TECH_MODE_INFO
+ */
+BDNVMESelfTestLog * bd_nvme_get_self_test_log (const gchar *device, GError **error) {
+    int ret;
+    int fd;
+    struct nvme_self_test_log self_test_log = ZERO_INIT;
+    BDNVMESelfTestLog *log;
+    GPtrArray *ptr_array;
+    guint i;
+
+    /* open the block device */
+    fd = _open_dev (device, error);
+    if (fd < 0)
+        return NULL;
+
+    /* send the NVME_LOG_LID_DEVICE_SELF_TEST ioctl */
+    ret = nvme_get_log_device_self_test (fd, &self_test_log);
+    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);
+        return NULL;
+    }
+    close (fd);
+
+    log = g_new0 (BDNVMESelfTestLog, 1);
+    switch (self_test_log.current_operation & NVME_ST_CURR_OP_MASK) {
+        case NVME_ST_CURR_OP_NOT_RUNNING:
+            log->current_operation = BD_NVME_SELF_TEST_ACTION_NOT_RUNNING;
+            break;
+        case NVME_ST_CURR_OP_SHORT:
+            log->current_operation = BD_NVME_SELF_TEST_ACTION_SHORT;
+            break;
+        case NVME_ST_CURR_OP_EXTENDED:
+            log->current_operation = BD_NVME_SELF_TEST_ACTION_EXTENDED;
+            break;
+        case NVME_ST_CURR_OP_VS:
+        case NVME_ST_CURR_OP_RESERVED:
+        default:
+            log->current_operation = BD_NVME_SELF_TEST_ACTION_VENDOR_SPECIFIC;
+    }
+    if ((self_test_log.current_operation & NVME_ST_CURR_OP_MASK) > 0)
+        log->current_operation_completion = self_test_log.completion & NVME_ST_CURR_OP_CMPL_MASK;
+
+    ptr_array = g_ptr_array_new ();
+    for (i = 0; i < NVME_LOG_ST_MAX_RESULTS; i++) {
+        BDNVMESelfTestLogEntry *entry;
+        guint8 dsts;
+        guint8 code;
+
+        dsts = self_test_log.result[i].dsts & NVME_ST_RESULT_MASK;
+        code = self_test_log.result[i].dsts >> NVME_ST_CODE_SHIFT;
+        if (dsts == NVME_ST_RESULT_NOT_USED)
+            continue;
+
+        entry = g_new0 (BDNVMESelfTestLogEntry, 1);
+        switch (dsts) {
+            case NVME_ST_RESULT_NO_ERR:
+                entry->result = BD_NVME_SELF_TEST_RESULT_NO_ERROR;
+                break;
+            case NVME_ST_RESULT_ABORTED:
+                entry->result = BD_NVME_SELF_TEST_RESULT_ABORTED;
+                break;
+            case NVME_ST_RESULT_CLR:
+                entry->result = BD_NVME_SELF_TEST_RESULT_CTRL_RESET;
+                break;
+            case NVME_ST_RESULT_NS_REMOVED:
+                entry->result = BD_NVME_SELF_TEST_RESULT_NS_REMOVED;
+                break;
+            case NVME_ST_RESULT_ABORTED_FORMAT:
+                entry->result = BD_NVME_SELF_TEST_RESULT_ABORTED_FORMAT;
+                break;
+            case NVME_ST_RESULT_FATAL_ERR:
+                entry->result = BD_NVME_SELF_TEST_RESULT_FATAL_ERROR;
+                break;
+            case NVME_ST_RESULT_UNKNOWN_SEG_FAIL:
+                entry->result = BD_NVME_SELF_TEST_RESULT_UNKNOWN_SEG_FAIL;
+                break;
+            case NVME_ST_RESULT_KNOWN_SEG_FAIL:
+                entry->result = BD_NVME_SELF_TEST_RESULT_KNOWN_SEG_FAIL;
+                break;
+            case NVME_ST_RESULT_ABORTED_UNKNOWN:
+                entry->result = BD_NVME_SELF_TEST_RESULT_ABORTED_UNKNOWN;
+                break;
+            case NVME_ST_RESULT_ABORTED_SANITIZE:
+                entry->result = BD_NVME_SELF_TEST_RESULT_ABORTED_SANITIZE;
+                break;
+            default:
+                g_warning ("Unhandled self-test log entry result code: %d", dsts);
+                g_free (entry);
+                continue;
+        }
+        switch (code) {
+            case NVME_ST_CODE_SHORT:
+                entry->action = BD_NVME_SELF_TEST_ACTION_SHORT;
+                break;
+            case NVME_ST_CODE_EXTENDED:
+                entry->action = BD_NVME_SELF_TEST_ACTION_EXTENDED;
+                break;
+            case NVME_ST_CODE_VS:
+            case NVME_ST_CODE_RESERVED:
+                entry->action = BD_NVME_SELF_TEST_ACTION_VENDOR_SPECIFIC;
+                break;
+            default:
+                g_warning ("Unhandled self-test log entry action code: %d", code);
+                entry->action = BD_NVME_SELF_TEST_ACTION_VENDOR_SPECIFIC;
+        }
+        entry->segment = self_test_log.result[i].seg;
+        entry->power_on_hours = GUINT64_FROM_LE (self_test_log.result[i].poh);
+        if (self_test_log.result[i].vdi & NVME_ST_VALID_DIAG_INFO_NSID)
+            entry->nsid = GUINT32_FROM_LE (self_test_log.result[i].nsid);
+        if (self_test_log.result[i].vdi & NVME_ST_VALID_DIAG_INFO_FLBA)
+            entry->failing_lba = GUINT64_FROM_LE (self_test_log.result[i].flba);
+        if ((self_test_log.result[i].vdi & NVME_ST_VALID_DIAG_INFO_SC) &&
+            (self_test_log.result[i].vdi & NVME_ST_VALID_DIAG_INFO_SCT))
+            _nvme_status_to_error ((self_test_log.result[i].sct & 7) << 8 | self_test_log.result[i].sc,
+                                   FALSE, &entry->status_code_error);
+
+        g_ptr_array_add (ptr_array, entry);
+    }
+    g_ptr_array_add (ptr_array, NULL);
+    log->entries = (BDNVMESelfTestLogEntry **) g_ptr_array_free (ptr_array, FALSE);
+
+    return log;
+}
+
+
+/**
+ * bd_nvme_get_sanitize_log:
+ * @device: a NVMe controller device (e.g. `/dev/nvme0`)
+ * @error: (out) (nullable): place to store error (if any)
+ *
+ * Retrieves the drive sanitize status log (Log Identifier `81h`) that includes information
+ * about the most recent sanitize operation and the sanitize operation time estimates.
+ *
+ * As advised in the NVMe specification whitepaper the host should limit polling
+ * to retrieve progress of a running sanitize operations (e.g. to at most once every
+ * several minutes) to avoid interfering with the progress of the sanitize operation itself.
+ *
+ * Returns: (transfer full): sanitize log data or %NULL in case of an error (with @error set).
+ *
+ * Tech category: %BD_NVME_TECH_NVME-%BD_NVME_TECH_MODE_INFO
+ */
+BDNVMESanitizeLog * bd_nvme_get_sanitize_log (const gchar *device, GError **error) {
+    int ret;
+    int fd;
+    struct nvme_sanitize_log_page sanitize_log = ZERO_INIT;
+    BDNVMESanitizeLog *log;
+    __u16 sstat;
+
+    /* open the block device */
+    fd = _open_dev (device, error);
+    if (fd < 0)
+        return NULL;
+
+    /* send the NVME_LOG_LID_SANITIZE ioctl */
+    ret = nvme_get_log_sanitize (fd, FALSE /* rae */, &sanitize_log);
+    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);
+        return NULL;
+    }
+    close (fd);
+
+    sstat = GUINT16_FROM_LE (sanitize_log.sstat);
+
+    log = g_new0 (BDNVMESanitizeLog, 1);
+    log->sanitize_progress = 0;
+    if ((sstat & NVME_SANITIZE_SSTAT_STATUS_MASK) == NVME_SANITIZE_SSTAT_STATUS_IN_PROGESS)
+        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;
+
+    switch (sstat & NVME_SANITIZE_SSTAT_STATUS_MASK) {
+        case NVME_SANITIZE_SSTAT_STATUS_COMPLETE_SUCCESS:
+            log->sanitize_status = BD_NVME_SANITIZE_STATUS_SUCCESS;
+            break;
+        case NVME_SANITIZE_SSTAT_STATUS_IN_PROGESS:
+            log->sanitize_status = BD_NVME_SANITIZE_STATUS_IN_PROGESS;
+            break;
+        case NVME_SANITIZE_SSTAT_STATUS_COMPLETED_FAILED:
+            log->sanitize_status = BD_NVME_SANITIZE_STATUS_FAILED;
+            break;
+        case NVME_SANITIZE_SSTAT_STATUS_ND_COMPLETE_SUCCESS:
+            log->sanitize_status = BD_NVME_SANITIZE_STATUS_SUCCESS_NO_DEALLOC;
+            break;
+        case NVME_SANITIZE_SSTAT_STATUS_NEVER_SANITIZED:
+        default:
+            log->sanitize_status = BD_NVME_SANITIZE_STATUS_NEVER_SANITIZED;
+            break;
+    }
+
+    log->time_for_overwrite = (GUINT32_FROM_LE (sanitize_log.eto) == 0xffffffff) ? -1 : (gint64) GUINT32_FROM_LE (sanitize_log.eto);
+    log->time_for_block_erase = (GUINT32_FROM_LE (sanitize_log.etbe) == 0xffffffff) ? -1 : (gint64) GUINT32_FROM_LE (sanitize_log.etbe);
+    log->time_for_crypto_erase = (GUINT32_FROM_LE (sanitize_log.etce) == 0xffffffff) ? -1 : (gint64) GUINT32_FROM_LE (sanitize_log.etce);
+    log->time_for_overwrite_nd = (GUINT32_FROM_LE (sanitize_log.etond) == 0xffffffff) ? -1 : (gint64) GUINT32_FROM_LE (sanitize_log.etond);
+    log->time_for_block_erase_nd = (GUINT32_FROM_LE (sanitize_log.etbend) == 0xffffffff) ? -1 : (gint64) GUINT32_FROM_LE (sanitize_log.etbend);
+    log->time_for_crypto_erase_nd = (GUINT32_FROM_LE (sanitize_log.etcend) == 0xffffffff) ? -1 : (gint64) GUINT32_FROM_LE (sanitize_log.etcend);
+
+    return log;
+}
diff --git a/src/plugins/nvme/nvme-op.c b/src/plugins/nvme/nvme-op.c
new file mode 100644
index 00000000..4568c453
--- /dev/null
+++ b/src/plugins/nvme/nvme-op.c
@@ -0,0 +1,388 @@
+/*
+ * Copyright (C) 2014-2021 Red Hat, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see <http://www.gnu.org/licenses/>.
+ *
+ * Author: Tomas Bzatek <tbzatek@redhat.com>
+ */
+
+#include <glib.h>
+#include <string.h>
+#include <stdio.h>
+#include <unistd.h>
+#include <sys/ioctl.h>
+#include <sys/stat.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <malloc.h>
+#include <linux/fs.h>
+
+#include <libnvme.h>
+#include <uuid/uuid.h>
+
+#include <blockdev/utils.h>
+#include <check_deps.h>
+#include "nvme.h"
+#include "nvme-private.h"
+
+
+/**
+ * bd_nvme_device_self_test:
+ * @device: a NVMe controller or namespace device (e.g. `/dev/nvme0`)
+ * @action: self-test action to take.
+ * @error: (out) (nullable): place to store error (if any)
+ *
+ * Initiates or aborts the Device Self-test operation on the controller or a namespace,
+ * distinguished by the @device path specified. In case a controller device
+ * is specified then the self-test operation would include all active namespaces.
+ *
+ * To abort a running operation, pass #BD_NVME_SELF_TEST_ACTION_ABORT as @action.
+ * To retrieve progress of a current running operation, check the self-test log using
+ * bd_nvme_get_self_test_log().
+ *
+ * Returns: %TRUE if the device self-test command was issued successfully,
+ *          %FALSE otherwise with @error set.
+ *
+ * Tech category: %BD_NVME_TECH_NVME-%BD_NVME_TECH_MODE_MANAGE
+ */
+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,
+    };
+
+    switch (action) {
+        case BD_NVME_SELF_TEST_ACTION_SHORT:
+            args.stc = NVME_DST_STC_SHORT;
+            break;
+        case BD_NVME_SELF_TEST_ACTION_EXTENDED:
+            args.stc = NVME_DST_STC_LONG;
+            break;
+        case BD_NVME_SELF_TEST_ACTION_VENDOR_SPECIFIC:
+            args.stc = NVME_DST_STC_VS;
+            break;
+        case BD_NVME_SELF_TEST_ACTION_ABORT:
+            args.stc = NVME_DST_STC_ABORT;
+            break;
+        default:
+            g_set_error (error, BD_NVME_ERROR, BD_NVME_ERROR_INVALID_ARGUMENT,
+                         "Invalid value specified for the self-test action: %d", action);
+            return FALSE;
+    }
+
+    /* open the block device */
+    args.fd = _open_dev (device, error);
+    if (args.fd < 0)
+        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)
+        /* not a block device, assuming controller character device */
+        args.nsid = 0xffffffff;
+    else if (ret != 0) {
+        _nvme_status_to_error (ret, FALSE, error);
+        g_prefix_error (error, "Error getting Namespace Identifier (NSID): ");
+        close (args.fd);
+        return FALSE;
+    }
+
+    ret = nvme_dev_self_test (&args);
+    if (ret != 0) {
+        _nvme_status_to_error (ret, FALSE, error);
+        g_prefix_error (error, "NVMe Device Self-test command error: ");
+        close (args.fd);
+        return FALSE;
+    }
+    close (args.fd);
+
+    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) {
+    int ret;
+    struct nvme_id_ns ns_info = ZERO_INIT;
+    __u8 flbas = 0;
+    guint i;
+
+    /* TODO: find first attached namespace instead of hardcoding NSID = 1 */
+    ret = nvme_identify_ns (fd, nsid == 0xffffffff ? 1 : nsid, &ns_info);
+    if (ret != 0) {
+        _nvme_status_to_error (ret, FALSE, error);
+        g_prefix_error (error, "NVMe Identify Namespace command error: ");
+        return 0xff;
+    }
+
+    /* return currently used lbaf */
+    if (lba_data_size == 0) {
+       nvme_id_ns_flbas_to_lbaf_inuse (ns_info.flbas, &flbas);
+       return flbas;
+    }
+
+    for (i = 0; i <= ns_info.nlbaf + ns_info.nulbaf; i++)
+        if (1UL << ns_info.lbaf[i].ds == lba_data_size && GUINT16_FROM_LE (ns_info.lbaf[i].ms) == metadata_size)
+            return i;
+
+    g_set_error_literal (error, BD_NVME_ERROR, BD_NVME_ERROR_INVALID_ARGUMENT,
+                         "Couldn't match desired LBA data block size in a device supported LBA format data sizes");
+    return 0xff;
+}
+
+/**
+ * bd_nvme_format:
+ * @device: NVMe namespace or controller device to format (e.g. `/dev/nvme0n1`)
+ * @lba_data_size: desired LBA data size (i.e. a sector size) in bytes or `0` to keep current. See #BDNVMELBAFormat and bd_nvme_get_namespace_info().
+ * @metadata_size: desired metadata size in bytes or `0` for default. See #BDNVMELBAFormat and bd_nvme_get_namespace_info().
+ * @secure_erase: optional secure erase action to take.
+ * @error: (out) (nullable): place to store error (if any)
+ *
+ * Performs low level format of the NVM media, destroying all data and metadata for either
+ * a specific namespace or all attached namespaces to the controller. Use this command
+ * to change LBA sector size. Optional secure erase method can be specified as well.
+ *
+ * Supported LBA data sizes for a given namespace can be listed using the bd_nvme_get_namespace_info()
+ * call. In case of a special value `0` the current LBA format for a given namespace will be
+ * retained. When called on a controller device the first namespace is used as a reference.
+ *
+ * Note that the NVMe controller may define a Format NVM attribute indicating that the format
+ * operation would apply to all namespaces and a format (excluding secure erase) of any
+ * namespace results in a format of all namespaces in the NVM subsystem. In such case and
+ * when @device is a namespace block device the #BD_NVME_ERROR_WOULD_FORMAT_ALL_NS error
+ * is returned to prevent further damage. This is then supposed to be handled by the caller
+ * and bd_nvme_format() is supposed to be called on a controller device instead.
+ *
+ * This call blocks until the format operation has finished. To retrieve progress
+ * of a current running operation, check the namespace info using bd_nvme_get_namespace_info().
+ *
+ * Returns: %TRUE if the format command finished successfully, %FALSE otherwise with @error set.
+ *
+ * Tech category: %BD_NVME_TECH_NVME-%BD_NVME_TECH_MODE_MANAGE
+ */
+gboolean bd_nvme_format (const gchar *device, guint16 lba_data_size, guint16 metadata_size, BDNVMEFormatSecureErase secure_erase, GError **error) {
+    int ret;
+    gboolean ctrl_device = FALSE;
+    struct nvme_id_ctrl ctrl_id = ZERO_INIT;
+    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,
+    };
+
+    /* open the block device */
+    args.fd = _open_dev (device, error);
+    if (args.fd < 0)
+        return FALSE;
+
+    ret = nvme_get_nsid (args.fd, &args.nsid);
+    if (ret < 0 && errno == ENOTTY) {
+        /* not a block device, assuming controller character device */
+        args.nsid = 0xffffffff;
+        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);
+        return FALSE;
+    }
+
+    /* check the FNA controller bit when formatting a single namespace */
+    if (! ctrl_device) {
+        ret = nvme_identify_ctrl (args.fd, &ctrl_id);
+        if (ret != 0) {
+            _nvme_status_to_error (ret, FALSE, error);
+            g_prefix_error (error, "NVMe Identify Controller command error: ");
+            close (args.fd);
+            return FALSE;
+        }
+        /* from nvme-cli:
+         * FNA bit 0 set to 1: all namespaces ... shall be configured with the same
+         * attributes and a format (excluding secure erase) of any namespace results in a
+         * format of all namespaces.
+         */
+        if ((ctrl_id.fna & NVME_CTRL_FNA_FMT_ALL_NAMESPACES) == NVME_CTRL_FNA_FMT_ALL_NAMESPACES) {
+            /* tell user that it would format other namespaces and that bd_nvme_format()
+             * 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);
+            return FALSE;
+        }
+    }
+
+    /* 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);
+        return FALSE;
+    }
+
+    switch (secure_erase) {
+        case BD_NVME_FORMAT_SECURE_ERASE_USER_DATA:
+            args.ses = NVME_FORMAT_SES_USER_DATA_ERASE;
+            break;
+        case BD_NVME_FORMAT_SECURE_ERASE_CRYPTO:
+            args.ses = NVME_FORMAT_SES_CRYPTO_ERASE;
+            break;
+        case BD_NVME_FORMAT_SECURE_ERASE_NONE:
+        default:
+            args.ses = NVME_FORMAT_SES_NONE;
+    }
+
+    ret = nvme_format_nvm (&args);
+    if (ret != 0) {
+        _nvme_status_to_error (ret, FALSE, error);
+        g_prefix_error (error, "Format NVM command error: ");
+        close (args.fd);
+        return FALSE;
+    }
+
+    /* rescan the namespaces if block size has changed */
+    if (ctrl_device) {
+        if (ioctl (args.fd, NVME_IOCTL_RESCAN) < 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);
+            return FALSE;
+        }
+    } else {
+        if (lba_data_size != 0) {
+            /* from nvme-cli:
+             * If block size has been changed by the format command up there, we should notify it to
+             * kernel blkdev to update its own block size to the given one because blkdev will not
+             * update by itself without re-opening fd.
+             */
+            int block_size = lba_data_size;
+
+            if (ioctl (args.fd, BLKBSZSET, &block_size) < 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);
+                return FALSE;
+            }
+
+            if (ioctl (args.fd, BLKRRPART) < 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);
+                return FALSE;
+            }
+        }
+    }
+
+    close (args.fd);
+    return TRUE;
+}
+
+/**
+ * bd_nvme_sanitize:
+ * @device: NVMe namespace or controller device to format (e.g. `/dev/nvme0n1`)
+ * @action: the sanitize action to perform.
+ * @no_dealloc: instruct the controller to not deallocate the affected media area.
+ * @overwrite_pass_count: number of overwrite passes [1-15] or 0 for the default (16 passes).
+ * @overwrite_pattern: a 32-bit pattern used for the Overwrite sanitize operation.
+ * @overwrite_invert_pattern: invert the overwrite pattern between passes.
+ * @error: (out) (nullable): place to store error (if any)
+ *
+ * Starts a sanitize operation or recovers from a previously failed sanitize operation.
+ * By definition, a sanitize operation alters all user data in the NVM subsystem such
+ * that recovery of any previous user data from any cache, the non-volatile media,
+ * or any Controller Memory Buffer is not possible. The scope of a sanitize operation
+ * is all locations in the NVM subsystem that are able to contain user data, including
+ * caches, Persistent Memory Regions, and unallocated or deallocated areas of the media.
+ *
+ * Once started, a sanitize operation is not able to be aborted and continues after
+ * a Controller Level Reset including across power cycles. Once the sanitize operation
+ * has run the media affected may not be immediately ready for use unless additional
+ * media modification mechanism is run. This is often vendor specific and also depends
+ * on the sanitize method (@action) used. Callers to this sanitize operation should
+ * set @no_dealloc to %TRUE for the added convenience.
+ *
+ * The controller also ignores Critical Warning(s) in the SMART / Health Information
+ * log page (e.g., read only mode) and attempts to complete the sanitize operation requested.
+ *
+ * This call returns immediately and the actual sanitize operation is performed
+ * in the background. Use bd_nvme_get_sanitize_log() to retrieve status and progress
+ * of a running sanitize operation. In case a sanitize operation fails the controller
+ * may restrict its operation until a subsequent sanitize operation is started
+ * (i.e. retried) or an #BD_NVME_SANITIZE_ACTION_EXIT_FAILURE action is used
+ * to acknowledge the failure explicitly.
+ *
+ * The @overwrite_pass_count, @overwrite_pattern and @overwrite_invert_pattern
+ * arguments are only valid when @action is #BD_NVME_SANITIZE_ACTION_OVERWRITE.
+ *
+ * The sanitize operation is set to run under the Allow Unrestricted Sanitize Exit
+ * mode.
+ *
+ * Returns: %TRUE if the format command finished successfully, %FALSE otherwise with @error set.
+ *
+ * Tech category: %BD_NVME_TECH_NVME-%BD_NVME_TECH_MODE_MANAGE
+ */
+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),
+    };
+
+    switch (action) {
+        case BD_NVME_SANITIZE_ACTION_EXIT_FAILURE:
+            args.sanact = NVME_SANITIZE_SANACT_EXIT_FAILURE;
+            break;
+        case BD_NVME_SANITIZE_ACTION_BLOCK_ERASE:
+            args.sanact = NVME_SANITIZE_SANACT_START_BLOCK_ERASE;
+            break;
+        case BD_NVME_SANITIZE_ACTION_OVERWRITE:
+            args.sanact = NVME_SANITIZE_SANACT_START_OVERWRITE;
+            break;
+        case BD_NVME_SANITIZE_ACTION_CRYPTO_ERASE:
+            args.sanact = NVME_SANITIZE_SANACT_START_CRYPTO_ERASE;
+            break;
+        default:
+            g_set_error (error, BD_NVME_ERROR, BD_NVME_ERROR_INVALID_ARGUMENT,
+                         "Invalid value specified for the sanitize action: %d", action);
+            return FALSE;
+    }
+
+    /* open the block device */
+    args.fd = _open_dev (device, error);
+    if (args.fd < 0)
+        return FALSE;
+
+    ret = nvme_sanitize_nvm (&args);
+    if (ret != 0) {
+        _nvme_status_to_error (ret, FALSE, error);
+        g_prefix_error (error, "Sanitize command error: ");
+        close (args.fd);
+        return FALSE;
+    }
+
+    close (args.fd);
+    return TRUE;
+}
diff --git a/src/plugins/nvme/nvme-private.h b/src/plugins/nvme/nvme-private.h
new file mode 100644
index 00000000..3d4b2a99
--- /dev/null
+++ b/src/plugins/nvme/nvme-private.h
@@ -0,0 +1,25 @@
+#include <glib.h>
+#include <glib-object.h>
+#include <blockdev/utils.h>
+
+#ifndef BD_NVME_PRIVATE
+#define BD_NVME_PRIVATE
+
+/* TODO: move to a common libblockdev header */
+#ifdef __clang__
+#define ZERO_INIT {}
+#else
+#define ZERO_INIT {0}
+#endif
+
+/* "C" locale to get the locale-agnostic error messages */
+#define _C_LOCALE (locale_t) 0
+
+/* nvme-error.c */
+void _nvme_status_to_error (gint status, gboolean fabrics, GError **error);
+void _nvme_fabrics_errno_to_gerror (int result, int _errno, GError **error);
+
+/* nvme-info.c */
+gint _open_dev (const gchar *device, GError **error);
+
+#endif  /* BD_NVME_PRIVATE */
diff --git a/src/plugins/nvme/nvme.c b/src/plugins/nvme/nvme.c
new file mode 100644
index 00000000..00f2f76e
--- /dev/null
+++ b/src/plugins/nvme/nvme.c
@@ -0,0 +1,103 @@
+/*
+ * Copyright (C) 2014-2021 Red Hat, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see <http://www.gnu.org/licenses/>.
+ *
+ * Author: Tomas Bzatek <tbzatek@redhat.com>
+ */
+
+#include <glib.h>
+#include <string.h>
+#include <stdio.h>
+#include <unistd.h>
+#include <sys/ioctl.h>
+#include <sys/stat.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <malloc.h>
+
+#include <libnvme.h>
+#include <uuid/uuid.h>
+
+#include <blockdev/utils.h>
+#include <check_deps.h>
+#include "nvme.h"
+#include "nvme-private.h"
+
+/**
+ * SECTION: nvme
+ * @short_description: NVMe device reporting and management.
+ * @title: NVMe
+ * @include: nvme.h
+ *
+ * A plugin for NVMe device reporting and management, based around libnvme.
+ */
+
+
+/**
+ * bd_nvme_check_deps:
+ *
+ * Returns: whether the plugin's runtime dependencies are satisfied or not
+ *
+ * Function checking plugin's runtime dependencies.
+ *
+ */
+gboolean bd_nvme_check_deps (void) {
+    /* no runtime dependencies */
+    return TRUE;
+}
+
+/**
+ * bd_nvme_init:
+ *
+ * Initializes the plugin. **This function is called automatically by the
+ * library's initialization functions.**
+ *
+ */
+gboolean bd_nvme_init (void) {
+    /* nothing to do here */
+    return TRUE;
+};
+
+/**
+ * bd_nvme_close:
+ *
+ * Cleans up after the plugin. **This function is called automatically by the
+ * library's functions that unload it.**
+ *
+ */
+void bd_nvme_close (void) {
+    /* nothing to do here */
+}
+
+/**
+ * bd_nvme_is_tech_avail:
+ * @tech: the queried tech
+ * @mode: a bit mask of queried modes of operation (#BDNVMETechMode) for @tech
+ * @error: (out) (nullable): place to store error (details about why the @tech-@mode combination is not available)
+ *
+ * Returns: whether the @tech-@mode combination is available -- supported by the
+ *          plugin implementation and having all the runtime dependencies available
+ */
+gboolean bd_nvme_is_tech_avail (BDNVMETech tech, G_GNUC_UNUSED guint64 mode, GError **error) {
+    switch (tech) {
+        case BD_NVME_TECH_NVME:
+            return TRUE;
+        case BD_NVME_TECH_FABRICS:
+            return TRUE;
+        default:
+            g_set_error (error, BD_NVME_ERROR, BD_NVME_ERROR_TECH_UNAVAIL, "Unknown technology");
+            return FALSE;
+    }
+}
diff --git a/src/plugins/nvme/nvme.h b/src/plugins/nvme/nvme.h
new file mode 100644
index 00000000..a7d30d79
--- /dev/null
+++ b/src/plugins/nvme/nvme.h
@@ -0,0 +1,700 @@
+#include <glib.h>
+#include <glib-object.h>
+#include <blockdev/utils.h>
+
+#ifndef BD_NVME
+#define BD_NVME
+
+GQuark bd_nvme_error_quark (void);
+#define BD_NVME_ERROR bd_nvme_error_quark ()
+
+/**
+ * BDNVMEError:
+ * @BD_NVME_ERROR_TECH_UNAVAIL: NVMe support not available.
+ * @BD_NVME_ERROR_FAILED: General error.
+ * @BD_NVME_ERROR_BUSY: The device is temporarily unavailable or in an inconsistent state.
+ * @BD_NVME_ERROR_INVALID_ARGUMENT: Invalid argument.
+ * @BD_NVME_ERROR_WOULD_FORMAT_ALL_NS: The NVMe controller indicates that it would format all namespaces in the NVM subsystem.
+ * @BD_NVME_ERROR_SC_GENERIC: Generic NVMe Command Status Code.
+ * @BD_NVME_ERROR_SC_CMD_SPECIFIC: NVMe Command Specific error.
+ * @BD_NVME_ERROR_SC_MEDIA: Media and Data Integrity Errors: media specific errors that occur in the NVM or data integrity type errors.
+ * @BD_NVME_ERROR_SC_PATH: Path related error.
+ * @BD_NVME_ERROR_SC_VENDOR_SPECIFIC: NVMe Vendor specific error.
+ * @BD_NVME_ERROR_NO_MATCH: No matching resource found (e.g. a Fabrics Controller).
+ * @BD_NVME_ERROR_CONNECT: General connection error.
+ * @BD_NVME_ERROR_CONNECT_ALREADY: Already connected.
+ * @BD_NVME_ERROR_CONNECT_INVALID: Invalid argument specified.
+ * @BD_NVME_ERROR_CONNECT_ADDRINUSE: HostNQN already in use.
+ * @BD_NVME_ERROR_CONNECT_NODEV: Invalid interface.
+ * @BD_NVME_ERROR_CONNECT_OPNOTSUPP: Operation not supported.
+ */
+typedef enum {
+    BD_NVME_ERROR_TECH_UNAVAIL,
+    BD_NVME_ERROR_FAILED,
+    BD_NVME_ERROR_BUSY,
+    BD_NVME_ERROR_INVALID_ARGUMENT,
+    BD_NVME_ERROR_WOULD_FORMAT_ALL_NS,
+    BD_NVME_ERROR_SC_GENERIC,
+    BD_NVME_ERROR_SC_CMD_SPECIFIC,
+    BD_NVME_ERROR_SC_MEDIA,
+    BD_NVME_ERROR_SC_PATH,
+    BD_NVME_ERROR_SC_VENDOR_SPECIFIC,
+    BD_NVME_ERROR_NO_MATCH,
+    BD_NVME_ERROR_CONNECT,
+    BD_NVME_ERROR_CONNECT_ALREADY,
+    BD_NVME_ERROR_CONNECT_INVALID,
+    BD_NVME_ERROR_CONNECT_ADDRINUSE,
+    BD_NVME_ERROR_CONNECT_NODEV,
+    BD_NVME_ERROR_CONNECT_OPNOTSUPP,
+} BDNVMEError;
+
+typedef enum {
+    BD_NVME_TECH_NVME = 0,
+    BD_NVME_TECH_FABRICS,
+} BDNVMETech;
+
+typedef enum {
+    BD_NVME_TECH_MODE_INFO         = 1 << 0,
+    BD_NVME_TECH_MODE_MANAGE       = 1 << 1,
+    BD_NVME_TECH_MODE_INITIATOR    = 1 << 2,
+} BDNVMETechMode;
+
+/**
+ * BDNVMEControllerFeature:
+ * @BD_NVME_CTRL_FEAT_MULTIPORT: if set, then the NVM subsystem may contain more than one NVM subsystem port, otherwise it's single-port only.
+ * @BD_NVME_CTRL_FEAT_MULTICTRL: if set, then the NVM subsystem may contain two or more controllers, otherwise contains only single controller.
+ * @BD_NVME_CTRL_FEAT_SRIOV: if set, then the controller is associated with an SR-IOV Virtual Function, otherwise it's associated with a PCI Function or a Fabrics connection.
+ * @BD_NVME_CTRL_FEAT_ANA_REPORTING: indicates that the NVM subsystem supports Asymmetric Namespace Access (ANA) Reporting.
+ * @BD_NVME_CTRL_FEAT_FORMAT: indicates that the controller supports the Format NVM command.
+ * @BD_NVME_CTRL_FEAT_FORMAT_ALL_NS: if set, then a format (excluding secure erase) of any namespace results in a format of all namespaces
+ *                                   in an NVM subsystem with all namespaces in an NVM subsystem configured with the same attributes.
+ *                                   If not set, then the controller supports format on a per namespace basis.
+ * @BD_NVME_CTRL_FEAT_NS_MGMT: indicates that the controller supports the Namespace Management and Attachment capability.
+ * @BD_NVME_CTRL_FEAT_SELFTEST: indicates that the controller supports the Device Self-test command.
+ * @BD_NVME_CTRL_FEAT_SELFTEST_SINGLE: indicates that the NVM subsystem supports only one device self-test operation in progress at a time.
+ * @BD_NVME_CTRL_FEAT_SANITIZE_CRYPTO: indicates that the controller supports the Crypto Erase sanitize operation.
+ * @BD_NVME_CTRL_FEAT_SANITIZE_BLOCK: indicates that the controller supports the Block Erase sanitize operation.
+ * @BD_NVME_CTRL_FEAT_SANITIZE_OVERWRITE: indicates that the controller supports the Overwrite sanitize operation.
+ * @BD_NVME_CTRL_FEAT_SECURE_ERASE_ALL_NS: if set, then any secure erase performed as part of a format operation
+ *                                         results in a secure erase of all namespaces in the NVM subsystem. If not set,
+ *                                         then any secure erase performed as part of a format results in a secure erase
+ *                                         of the particular namespace specified.
+ * @BD_NVME_CTRL_FEAT_SECURE_ERASE_CRYPTO: indicates that the cryptographic erase is supported.
+ * @BD_NVME_CTRL_FEAT_STORAGE_DEVICE: indicates that the NVM subsystem is part of an NVMe Storage Device.
+ * @BD_NVME_CTRL_FEAT_ENCLOSURE: indicates that the NVM subsystem is part of an NVMe Enclosure.
+ * @BD_NVME_CTRL_FEAT_MGMT_PCIE: indicates that the NVM subsystem contains a Management Endpoint on a PCIe port.
+ * @BD_NVME_CTRL_FEAT_MGMT_SMBUS: indicates that the NVM subsystem contains a Management Endpoint on an SMBus/I2C port.
+ */
+typedef enum {
+    BD_NVME_CTRL_FEAT_MULTIPORT           = 1 << 0,
+    BD_NVME_CTRL_FEAT_MULTICTRL           = 1 << 1,
+    BD_NVME_CTRL_FEAT_SRIOV               = 1 << 2,
+    BD_NVME_CTRL_FEAT_ANA_REPORTING       = 1 << 3,
+    BD_NVME_CTRL_FEAT_FORMAT              = 1 << 4,
+    BD_NVME_CTRL_FEAT_FORMAT_ALL_NS       = 1 << 5,
+    BD_NVME_CTRL_FEAT_NS_MGMT             = 1 << 6,
+    BD_NVME_CTRL_FEAT_SELFTEST            = 1 << 7,
+    BD_NVME_CTRL_FEAT_SELFTEST_SINGLE     = 1 << 8,
+    BD_NVME_CTRL_FEAT_SANITIZE_CRYPTO     = 1 << 9,
+    BD_NVME_CTRL_FEAT_SANITIZE_BLOCK      = 1 << 10,
+    BD_NVME_CTRL_FEAT_SANITIZE_OVERWRITE  = 1 << 11,
+    BD_NVME_CTRL_FEAT_SECURE_ERASE_ALL_NS = 1 << 12,
+    BD_NVME_CTRL_FEAT_SECURE_ERASE_CRYPTO = 1 << 13,
+    BD_NVME_CTRL_FEAT_STORAGE_DEVICE      = 1 << 14,
+    BD_NVME_CTRL_FEAT_ENCLOSURE           = 1 << 15,
+    BD_NVME_CTRL_FEAT_MGMT_PCIE           = 1 << 16,
+    BD_NVME_CTRL_FEAT_MGMT_SMBUS          = 1 << 17,
+} BDNVMEControllerFeature;
+
+/**
+ * BDNVMEControllerType:
+ * @BD_NVME_CTRL_TYPE_UNKNOWN: Controller type not reported (as reported by older NVMe-compliant devices).
+ * @BD_NVME_CTRL_TYPE_IO: I/O controller.
+ * @BD_NVME_CTRL_TYPE_DISCOVERY: Discovery controller.
+ * @BD_NVME_CTRL_TYPE_ADMIN: Administrative controller.
+ */
+typedef enum {
+    BD_NVME_CTRL_TYPE_UNKNOWN = 0,
+    BD_NVME_CTRL_TYPE_IO,
+    BD_NVME_CTRL_TYPE_DISCOVERY,
+    BD_NVME_CTRL_TYPE_ADMIN,
+} BDNVMEControllerType;
+
+/**
+ * BDNVMEControllerInfo:
+ * @pci_vendor_id: The PCI Vendor ID.
+ * @pci_subsys_vendor_id: The PCI Subsystem Vendor ID.
+ * @ctrl_id: Controller ID, the NVM subsystem unique controller identifier associated with the controller.
+ * @fguid: FRU GUID, a 128-bit value that is globally unique for a given Field Replaceable Unit.
+ * @model_number: The model number.
+ * @serial_number: The serial number.
+ * @firmware_ver: The currently active firmware revision.
+ * @nvme_ver: The NVM Express base specification that the controller implementation supports.
+ * @features: features and capabilities present for this controller, see #BDNVMEControllerFeature.
+ * @controller_type: The controller type.
+ * @selftest_ext_time: Extended Device Self-test Time, if #BD_NVME_CTRL_FEAT_SELFTEST is supported then this field
+ *                     indicates the nominal amount of time in one minute units that the controller takes
+ *                     to complete an extended device self-test operation when in power state 0.
+ * @hmb_pref_size: Host Memory Buffer Preferred Size indicates the preferred size that the host
+ *                 is requested to allocate for the Host Memory Buffer feature in bytes.
+ * @hmb_min_size: Host Memory Buffer Minimum Size indicates the minimum size that the host
+ *                is requested to allocate for the Host Memory Buffer feature in bytes.
+ * @size_total: Total NVM Capacity in the NVM subsystem in bytes.
+ * @size_unalloc: Unallocated NVM Capacity in the NVM subsystem in bytes.
+ * @num_namespaces: Maximum Number of Allowed Namespaces supported by the NVM subsystem.
+ * @subsysnqn: NVM Subsystem NVMe Qualified Name, UTF-8 null terminated string.
+ */
+typedef struct BDNVMEControllerInfo {
+    guint16 pci_vendor_id;
+    guint16 pci_subsys_vendor_id;
+    guint16 ctrl_id;
+    gchar *fguid;
+    gchar *model_number;
+    gchar *serial_number;
+    gchar *firmware_ver;
+    gchar *nvme_ver;
+    guint64 features;
+    BDNVMEControllerType controller_type;
+    gint selftest_ext_time;
+    guint64 hmb_pref_size;
+    guint64 hmb_min_size;
+    guint64 size_total;
+    guint64 size_unalloc;
+    guint num_namespaces;
+    gchar *subsysnqn;
+} BDNVMEControllerInfo;
+
+/**
+ * BDNVMELBAFormatRelativePerformance:
+ * Performance index of the LBA format relative to other LBA formats supported by the controller.
+ * @BD_NVME_LBA_FORMAT_RELATIVE_PERFORMANCE_UNKNOWN: Unknown relative performance index.
+ * @BD_NVME_LBA_FORMAT_RELATIVE_PERFORMANCE_BEST: Best performance.
+ * @BD_NVME_LBA_FORMAT_RELATIVE_PERFORMANCE_BETTER: Better performance.
+ * @BD_NVME_LBA_FORMAT_RELATIVE_PERFORMANCE_GOOD: Good performance.
+ * @BD_NVME_LBA_FORMAT_RELATIVE_PERFORMANCE_DEGRADED: Degraded performance.
+ */
+typedef enum {
+    BD_NVME_LBA_FORMAT_RELATIVE_PERFORMANCE_UNKNOWN = 0,
+    BD_NVME_LBA_FORMAT_RELATIVE_PERFORMANCE_BEST = 1,
+    BD_NVME_LBA_FORMAT_RELATIVE_PERFORMANCE_BETTER = 2,
+    BD_NVME_LBA_FORMAT_RELATIVE_PERFORMANCE_GOOD = 3,
+    BD_NVME_LBA_FORMAT_RELATIVE_PERFORMANCE_DEGRADED = 4
+} BDNVMELBAFormatRelativePerformance;
+
+/**
+ * BDNVMELBAFormat:
+ * Namespace LBA Format Data Structure.
+ * @data_size: LBA data size (i.e. a sector size) in bytes.
+ * @metadata_size: metadata size in bytes or `0` in case of no metadata support.
+ * @relative_performance: Relative Performance index, see #BDNVMELBAFormatRelativePerformance.
+ */
+typedef struct BDNVMELBAFormat {
+    guint16 data_size;
+    guint16 metadata_size;
+    BDNVMELBAFormatRelativePerformance relative_performance;
+} BDNVMELBAFormat;
+
+/**
+ * BDNVMENamespaceFeature:
+ * @BD_NVME_NS_FEAT_THIN: indicates that the namespace supports thin provisioning. Specifically, the Namespace Capacity
+ *                        reported may be less than the Namespace Size.
+ * @BD_NVME_NS_FEAT_MULTIPATH_SHARED: indicates the capability to attach the namespace to two or more controllers
+ *                                    in the NVM subsystem concurrently.
+ * @BD_NVME_NS_FEAT_FORMAT_PROGRESS: indicates the capability to report the percentage of the namespace
+ *                                   that remains to be formatted.
+ */
+typedef enum {
+    BD_NVME_NS_FEAT_THIN             = 1 << 0,
+    BD_NVME_NS_FEAT_MULTIPATH_SHARED = 1 << 1,
+    BD_NVME_NS_FEAT_FORMAT_PROGRESS  = 1 << 2,
+} BDNVMENamespaceFeature;
+
+/**
+ * BDNVMENamespaceInfo:
+ * @nsid: The Namespace Identifier (NSID).
+ * @eui64: IEEE Extended Unique Identifier: a 64-bit IEEE Extended Unique Identifier (EUI-64)
+ *         that is globally unique and assigned to the namespace when the namespace is created.
+ *         Remains fixed throughout the life of the namespace and is preserved across namespace
+ *         and controller operations.
+ * @nguid: Namespace Globally Unique Identifier: a 128-bit value that is globally unique and
+ *         assigned to the namespace when the namespace is created. Remains fixed throughout
+ *         the life of the namespace and is preserved across namespace and controller operations.
+ * @uuid: Namespace 128-bit Universally Unique Identifier (UUID) as specified in RFC 4122.
+ * @nsize: Namespace Size: total size of the namespace in logical blocks. The number of logical blocks
+ *         is based on the formatted LBA size (see @current_lba_format).
+ * @ncap: Namespace Capacity: maximum number of logical blocks that may be allocated in the namespace
+ *        at any point in time. The number of logical blocks is based on the formatted LBA size (see @current_lba_format).
+ * @nuse: Namespace Utilization: current number of logical blocks allocated in the namespace.
+ *        This field is smaller than or equal to the Namespace Capacity. The number of logical
+ *        blocks is based on the formatted LBA size (see @current_lba_format).
+ * @features: features and capabilities present for this namespace, see #BDNVMENamespaceFeature.
+ * @format_progress_remaining: The percentage value remaining of a format operation in progress.
+ * @write_protected: %TRUE if the namespace is currently write protected and all write access to the namespace shall fail.
+ * @lba_formats: (array zero-terminated=1) (element-type BDNVMELBAFormat): A list of supported LBA Formats.
+ * @current_lba_format: A LBA Format currently used for the namespace. Contains zeroes in case of
+ *                      an invalid or no supported LBA Format reported.
+ */
+typedef struct BDNVMENamespaceInfo {
+    guint32 nsid;
+    gchar *eui64;
+    gchar *uuid;
+    gchar *nguid;
+    guint64 nsize;
+    guint64 ncap;
+    guint64 nuse;
+    guint64 features;
+    guint8 format_progress_remaining;
+    gboolean write_protected;
+    BDNVMELBAFormat **lba_formats;
+    BDNVMELBAFormat current_lba_format;
+} BDNVMENamespaceInfo;
+
+/**
+ * BDNVMESmartCriticalWarning:
+ * @BD_NVME_SMART_CRITICAL_WARNING_SPARE: the available spare capacity has fallen below the threshold.
+ * @BD_NVME_SMART_CRITICAL_WARNING_TEMPERATURE: a temperature is either greater than or equal to an over temperature threshold;
+ *                                              or less than or equal to an under temperature threshold.
+ * @BD_NVME_SMART_CRITICAL_WARNING_DEGRADED: the NVM subsystem reliability has been degraded due to  significant media
+ *                                           related errors or any internal error that degrades NVM subsystem reliability.
+ * @BD_NVME_SMART_CRITICAL_WARNING_READONLY: all of the media has been placed in read only mode. Unrelated to the write
+ *                                           protection state of a namespace.
+ * @BD_NVME_SMART_CRITICAL_WARNING_VOLATILE_MEM: the volatile memory backup device has failed. Valid only if the controller
+ *                                               has a volatile memory backup solution.
+ * @BD_NVME_SMART_CRITICAL_WARNING_PMR_READONLY: Persistent Memory Region has become read-only or unreliable.
+ */
+typedef enum {
+    BD_NVME_SMART_CRITICAL_WARNING_SPARE        = 1 << 0,
+    BD_NVME_SMART_CRITICAL_WARNING_TEMPERATURE  = 1 << 1,
+    BD_NVME_SMART_CRITICAL_WARNING_DEGRADED     = 1 << 2,
+    BD_NVME_SMART_CRITICAL_WARNING_READONLY     = 1 << 3,
+    BD_NVME_SMART_CRITICAL_WARNING_VOLATILE_MEM = 1 << 4,
+    BD_NVME_SMART_CRITICAL_WARNING_PMR_READONLY = 1 << 5,
+} BDNVMESmartCriticalWarning;
+
+/**
+ * BDNVMESmartLog:
+ * @critical_warning: critical warnings for the state of the controller, see #BDNVMESmartCriticalWarning.
+ * @avail_spare: Available Spare: a normalized percentage (0% to 100%) of the remaining spare capacity available.
+ * @spare_thresh: Available Spare Threshold: a normalized percentage (0% to 100%) of the available spare threshold.
+ * @percent_used: Percentage Used: a vendor specific estimate of the percentage drive life used based on the
+ *                actual usage and the manufacturer's prediction. A value of 100 indicates that the estimated
+ *                endurance has been consumed, but may not indicate an NVM subsystem failure.
+ *                The value is allowed to exceed 100.
+ * @total_data_read: An estimated calculation of total data read in bytes based on calculation of data
+ *                   units read from the host. A value of 0 indicates that the number of Data Units Read
+ *                   is not reported.
+ * @total_data_written: An estimated calculation of total data written in bytes based on calculation
+ *                      of data units written by the host. A value of 0 indicates that the number
+ *                      of Data Units Written is not reported.
+ * @ctrl_busy_time: Amount of time the controller is busy with I/O commands, reported in minutes.
+ * @power_cycles: The number of power cycles.
+ * @power_on_hours: The number of power-on hours, excluding a non-operational power state.
+ * @unsafe_shutdowns: The number of unsafe shutdowns as a result of a Shutdown Notification not received prior to loss of power.
+ * @media_errors: Media and Data Integrity Errors: the number of occurrences where the controller detected
+ *                an unrecovered data integrity error (e.g. uncorrectable ECC, CRC checksum failure, or LBA tag mismatch).
+ * @num_err_log_entries: Number of Error Information Log Entries: the number of Error Information log
+ *                       entries over the life of the controller.
+ * @temperature: Composite Temperature: temperature in Kelvins that represents the current composite
+ *               temperature of the controller and associated namespaces or 0 when not applicable.
+ * @temp_sensors: Temperature Sensor 1-8: array of the current temperature reported by temperature sensors
+ *                1-8 in Kelvins or 0 when the particular sensor is not available.
+ * @wctemp: Warning Composite Temperature Threshold (WCTEMP): indicates the minimum Composite Temperature (@temperature)
+ *          value that indicates an overheating condition during which controller operation continues.
+ *          A value of 0 indicates that no warning temperature threshold value is reported by the controller.
+ * @cctemp: Critical Composite Temperature Threshold (CCTEMP): indicates the minimum Composite Temperature (@temperature)
+ *          value that indicates a critical overheating condition (e.g., may prevent continued normal operation,
+ *          possibility of data loss, automatic device shutdown, extreme performance throttling, or permanent damage).
+ *          A value of 0 indicates that no critical temperature threshold value is reported by the controller.
+ * @warning_temp_time: Warning Composite Temperature Time: the amount of time in minutes that the Composite Temperature (@temperature)
+ *                     is greater than or equal to the Warning Composite Temperature Threshold (@wctemp) and less than the
+ *                     Critical Composite Temperature Threshold (@cctemp).
+ * @critical_temp_time: Critical Composite Temperature Time: the amount of time in minutes that the Composite Temperature (@temperature)
+ *                      is greater than or equal to the Critical Composite Temperature Threshold (@cctemp).
+ */
+typedef struct BDNVMESmartLog {
+    guint critical_warning;
+    guint8 avail_spare;
+    guint8 spare_thresh;
+    guint8 percent_used;
+    guint64 total_data_read;
+    guint64 total_data_written;
+    guint64 ctrl_busy_time;
+    guint64 power_cycles;
+    guint64 power_on_hours;
+    guint64 unsafe_shutdowns;
+    guint64 media_errors;
+    guint64 num_err_log_entries;
+    guint16 temperature;
+    guint16 temp_sensors[8];
+    guint16 wctemp;
+    guint16 cctemp;
+    guint warning_temp_time;
+    guint critical_temp_time;
+} BDNVMESmartLog;
+
+/**
+ * BDNVMETransportType:
+ * Transport Type.
+ * @BD_NVME_TRANSPORT_TYPE_UNSPECIFIED: Not indicated
+ * @BD_NVME_TRANSPORT_TYPE_RDMA: RDMA Transport
+ * @BD_NVME_TRANSPORT_TYPE_FC: Fibre Channel Transport
+ * @BD_NVME_TRANSPORT_TYPE_TCP: TCP Transport
+ * @BD_NVME_TRANSPORT_TYPE_LOOP: Intra-host Transport (loopback)
+ */
+typedef enum {
+    BD_NVME_TRANSPORT_TYPE_UNSPECIFIED = 0,
+    BD_NVME_TRANSPORT_TYPE_RDMA        = 1,
+    BD_NVME_TRANSPORT_TYPE_FC          = 2,
+    BD_NVME_TRANSPORT_TYPE_TCP         = 3,
+    BD_NVME_TRANSPORT_TYPE_LOOP        = 254
+} BDNVMETransportType;
+
+/**
+ * BDNVMEAddressFamily:
+ * Address Family.
+ * @BD_NVME_ADDRESS_FAMILY_PCI: PCI Express.
+ * @BD_NVME_ADDRESS_FAMILY_INET: AF_INET: IPv4 address family.
+ * @BD_NVME_ADDRESS_FAMILY_INET6: AF_INET6: IPv6 address family.
+ * @BD_NVME_ADDRESS_FAMILY_IB: AF_IB: InfiniBand address family.
+ * @BD_NVME_ADDRESS_FAMILY_FC: Fibre Channel address family.
+ * @BD_NVME_ADDRESS_FAMILY_LOOP: Intra-host Transport (loopback).
+ */
+typedef enum {
+    BD_NVME_ADDRESS_FAMILY_PCI   = 0,
+    BD_NVME_ADDRESS_FAMILY_INET  = 1,
+    BD_NVME_ADDRESS_FAMILY_INET6 = 2,
+    BD_NVME_ADDRESS_FAMILY_IB    = 3,
+    BD_NVME_ADDRESS_FAMILY_FC    = 4,
+    BD_NVME_ADDRESS_FAMILY_LOOP  = 254
+} BDNVMEAddressFamily;
+
+/**
+ * BDNVMEErrorLogEntry:
+ * @error_count: internal error counter, a unique identifier for the error.
+ * @command_id: the Command Identifier of the command that the error is associated with or `0xffff` if the error is not specific to a particular command.
+ * @command_specific: Command Specific Information specific to @command_id.
+ * @command_status: the Status code for the command that completed.
+ * @command_error: translated command error in the BD_NVME_ERROR domain or %NULL in case @command_status indicates success.
+ * @lba: the first LBA that experienced the error condition.
+ * @nsid: the NSID of the namespace that the error is associated with.
+ * @transport_type: type of the transport associated with the error.
+ */
+typedef struct BDNVMEErrorLogEntry {
+    guint64 error_count;
+    guint16 command_id;
+    guint64 command_specific;
+    guint16 command_status;
+    GError *command_error;
+    guint64 lba;
+    guint32 nsid;
+    BDNVMETransportType transport_type;
+} BDNVMEErrorLogEntry;
+
+/**
+ * BDNVMESelfTestAction:
+ * Action taken by the Device Self-test command.
+ * @BD_NVME_SELF_TEST_ACTION_NOT_RUNNING: No device self-test operation in progress. Not a valid argument for bd_nvme_device_self_test().
+ * @BD_NVME_SELF_TEST_ACTION_SHORT: Start a short device self-test operation.
+ * @BD_NVME_SELF_TEST_ACTION_EXTENDED: Start an extended device self-test operation.
+ * @BD_NVME_SELF_TEST_ACTION_VENDOR_SPECIFIC: Start a vendor specific device self-test operation.
+ * @BD_NVME_SELF_TEST_ACTION_ABORT: Abort the device self-test operation. Only valid for bd_nvme_device_self_test().
+ */
+typedef enum {
+    BD_NVME_SELF_TEST_ACTION_NOT_RUNNING     = 0,
+    BD_NVME_SELF_TEST_ACTION_SHORT           = 1,
+    BD_NVME_SELF_TEST_ACTION_EXTENDED        = 2,
+    BD_NVME_SELF_TEST_ACTION_VENDOR_SPECIFIC = 3,
+    BD_NVME_SELF_TEST_ACTION_ABORT           = 4,
+} BDNVMESelfTestAction;
+
+/**
+ * BDNVMESelfTestResult:
+ * @BD_NVME_SELF_TEST_RESULT_NO_ERROR: Operation completed without error.
+ * @BD_NVME_SELF_TEST_RESULT_ABORTED: Operation was aborted by a Device Self-test command.
+ * @BD_NVME_SELF_TEST_RESULT_CTRL_RESET: Operation was aborted by a Controller Level Reset.
+ * @BD_NVME_SELF_TEST_RESULT_NS_REMOVED: Operation was aborted due to a removal of a namespace from the namespace inventory.
+ * @BD_NVME_SELF_TEST_RESULT_ABORTED_FORMAT: Operation was aborted due to the processing of a Format NVM command.
+ * @BD_NVME_SELF_TEST_RESULT_FATAL_ERROR: A fatal error or unknown test error occurred while the controller was executing the device self-test operation and the operation did not complete.
+ * @BD_NVME_SELF_TEST_RESULT_UNKNOWN_SEG_FAIL: Operation completed with a segment that failed and the segment that failed is not known.
+ * @BD_NVME_SELF_TEST_RESULT_KNOWN_SEG_FAIL: Operation completed with one or more failed segments and the first segment that failed is indicated in the Segment Number field.
+ * @BD_NVME_SELF_TEST_RESULT_ABORTED_UNKNOWN: Operation was aborted for unknown reason.
+ * @BD_NVME_SELF_TEST_RESULT_ABORTED_SANITIZE: Operation was aborted due to a sanitize operation.
+ */
+typedef enum {
+    BD_NVME_SELF_TEST_RESULT_NO_ERROR         = 0,
+    BD_NVME_SELF_TEST_RESULT_ABORTED          = 1,
+    BD_NVME_SELF_TEST_RESULT_CTRL_RESET       = 2,
+    BD_NVME_SELF_TEST_RESULT_NS_REMOVED       = 3,
+    BD_NVME_SELF_TEST_RESULT_ABORTED_FORMAT   = 4,
+    BD_NVME_SELF_TEST_RESULT_FATAL_ERROR      = 5,
+    BD_NVME_SELF_TEST_RESULT_UNKNOWN_SEG_FAIL = 6,
+    BD_NVME_SELF_TEST_RESULT_KNOWN_SEG_FAIL   = 7,
+    BD_NVME_SELF_TEST_RESULT_ABORTED_UNKNOWN  = 8,
+    BD_NVME_SELF_TEST_RESULT_ABORTED_SANITIZE = 9,
+} BDNVMESelfTestResult;
+
+/**
+ * BDNVMESelfTestLogEntry:
+ * @result: Result of the device self-test operation.
+ * @action: The Self-test Code value (action) that was specified in the Device Self-test command that started this device self-test operation.
+ * @segment: Segment number where the first self-test failure occurred. Valid only when @result is set to #BD_NVME_SELF_TEST_RESULT_KNOWN_SEG_FAIL.
+ * @power_on_hours: Number of power-on hours at the time the device self-test operation was completed or aborted. Does not include time that the controller was powered and in a low power state condition.
+ * @nsid: Namespace ID that the Failing LBA occurred on.
+ * @failing_lba: LBA of the logical block that caused the test to fail. If the device encountered more than one failed logical block during the test, then this field only indicates one of those failed logical blocks.
+ * @status_code_error: Translated NVMe Command Status Code representing additional information related to errors or conditions.
+ */
+typedef struct BDNVMESelfTestLogEntry {
+    BDNVMESelfTestResult result;
+    BDNVMESelfTestAction action;
+    guint8 segment;
+    guint64 power_on_hours;
+    guint32 nsid;
+    guint64 failing_lba;
+    GError *status_code_error;
+} BDNVMESelfTestLogEntry;
+
+/**
+ * BDNVMESelfTestLog:
+ * @current_operation: Current running device self-test operation. There's no corresponding record in @entries for a device self-test operation that is in progress.
+ * @current_operation_completion: Percentage of the currently running device self-test operation. Only valid when @current_operation is other than #BD_NVME_SELF_TEST_ACTION_NOT_RUNNING.
+ * @entries: (array zero-terminated=1) (element-type BDNVMESelfTestLogEntry): Self-test log entries for the last 20 operations, sorted from newest (first element) to oldest.
+ */
+typedef struct BDNVMESelfTestLog {
+    BDNVMESelfTestAction current_operation;
+    guint8 current_operation_completion;
+    BDNVMESelfTestLogEntry **entries;
+} BDNVMESelfTestLog;
+
+/**
+ * BDNVMEFormatSecureErase:
+ * Optional Format NVM secure erase action.
+ * @BD_NVME_FORMAT_SECURE_ERASE_NONE: No secure erase operation requested.
+ * @BD_NVME_FORMAT_SECURE_ERASE_USER_DATA: User Data Erase: All user data shall be erased, contents of the user data after the erase is indeterminate
+ *                                         (e.g., the user data may be zero filled, one filled, etc.). If a User Data Erase is requested and all affected
+ *                                         user data is encrypted, then the controller is allowed to use a cryptographic erase to perform the requested User Data Erase.
+ * @BD_NVME_FORMAT_SECURE_ERASE_CRYPTO: Cryptographic Erase: All user data shall be erased cryptographically. This is accomplished by deleting the encryption key.
+ */
+typedef enum {
+    BD_NVME_FORMAT_SECURE_ERASE_NONE      = 0,
+    BD_NVME_FORMAT_SECURE_ERASE_USER_DATA = 1,
+    BD_NVME_FORMAT_SECURE_ERASE_CRYPTO    = 2,
+} BDNVMEFormatSecureErase;
+
+/**
+ * BDNVMESanitizeStatus:
+ * @BD_NVME_SANITIZE_STATUS_NEVER_SANITIZED: The NVM subsystem has never been sanitized.
+ * @BD_NVME_SANITIZE_STATUS_IN_PROGESS: A sanitize operation is currently in progress.
+ * @BD_NVME_SANITIZE_STATUS_SUCCESS: The most recent sanitize operation completed successfully including any additional media modification.
+ * @BD_NVME_SANITIZE_STATUS_SUCCESS_NO_DEALLOC: The most recent sanitize operation for which No-Deallocate After Sanitize was requested has completed successfully with deallocation of all user data.
+ * @BD_NVME_SANITIZE_STATUS_FAILED: The most recent sanitize operation failed.
+ */
+typedef enum {
+    BD_NVME_SANITIZE_STATUS_NEVER_SANITIZED = 0,
+    BD_NVME_SANITIZE_STATUS_IN_PROGESS = 1,
+    BD_NVME_SANITIZE_STATUS_SUCCESS = 2,
+    BD_NVME_SANITIZE_STATUS_SUCCESS_NO_DEALLOC = 3,
+    BD_NVME_SANITIZE_STATUS_FAILED = 4,
+} BDNVMESanitizeStatus;
+
+/**
+ * BDNVMESanitizeLog:
+ * @sanitize_progress: The percentage complete of the sanitize operation.
+ * @sanitize_status: The status of the most recent sanitize operation.
+ * @global_data_erased: Indicates that no user data has been written either since the drive was manufactured and
+ *                      has never been sanitized or since the most recent successful sanitize operation.
+ * @overwrite_passes: Number of completed passes if the most recent sanitize operation was an Overwrite.
+ * @time_for_overwrite: Estimated time in seconds needed to complete an Overwrite sanitize operation with 16 passes in the background.
+ *                      A value of -1 means that no time estimate is reported. A value of 0 means that the operation is expected
+ *                      to be completed in the background when the Sanitize command is completed.
+ * @time_for_block_erase: Estimated time in seconds needed to complete a Block Erase sanitize operation in the background.
+ *                        A value of -1 means that no time estimate is reported. A value of 0 means that the operation is expected
+ *                        to be completed in the background when the Sanitize command is completed.
+ * @time_for_crypto_erase: Estimated time in seconds needed to complete a Crypto Erase sanitize operation in the background.
+ *                         A value of -1 means that no time estimate is reported. A value of 0 means that the operation is expected
+ *                         to be completed in the background when the Sanitize command is completed.
+ * @time_for_overwrite_nd: Estimated time in seconds needed to complete an Overwrite sanitize operation and the associated
+ *                         additional media modification in the background when the No-Deallocate After Sanitize or
+ *                         the No-Deallocate Modifies Media After Sanitize features have been requested.
+ * @time_for_block_erase_nd: Estimated time in seconds needed to complete a Block Erase sanitize operation and the associated
+ *                           additional media modification in the background when the No-Deallocate After Sanitize or
+ *                           the No-Deallocate Modifies Media After Sanitize features have been requested.
+ * @time_for_crypto_erase_nd: Estimated time in seconds needed to complete a Crypto Erase sanitize operation and the associated
+ *                            additional media modification in the background when the No-Deallocate After Sanitize or
+ *                            the No-Deallocate Modifies Media After Sanitize features have been requested.
+ */
+typedef struct BDNVMESanitizeLog {
+    gdouble sanitize_progress;
+    BDNVMESanitizeStatus sanitize_status;
+    gboolean global_data_erased;
+    guint8 overwrite_passes;
+    gint64 time_for_overwrite;
+    gint64 time_for_block_erase;
+    gint64 time_for_crypto_erase;
+    gint64 time_for_overwrite_nd;
+    gint64 time_for_block_erase_nd;
+    gint64 time_for_crypto_erase_nd;
+} BDNVMESanitizeLog;
+
+/**
+ * BDNVMESanitizeAction:
+ * @BD_NVME_SANITIZE_ACTION_EXIT_FAILURE: Exit Failure Mode.
+ * @BD_NVME_SANITIZE_ACTION_BLOCK_ERASE: Start a Block Erase sanitize operation - a low-level block erase method that is specific to the media.
+ * @BD_NVME_SANITIZE_ACTION_OVERWRITE: Start an Overwrite sanitize operation - writing a fixed data pattern or related patterns in multiple passes.
+ * @BD_NVME_SANITIZE_ACTION_CRYPTO_ERASE: Start a Crypto Erase sanitize operation - changing the media encryption keys for all locations on the media.
+ */
+typedef enum {
+    BD_NVME_SANITIZE_ACTION_EXIT_FAILURE = 0,
+    BD_NVME_SANITIZE_ACTION_BLOCK_ERASE = 1,
+    BD_NVME_SANITIZE_ACTION_OVERWRITE = 2,
+    BD_NVME_SANITIZE_ACTION_CRYPTO_ERASE = 3,
+} BDNVMESanitizeAction;
+
+/**
+ * BDNVMETCPSecurity:
+ * @BD_NVME_TCP_SECURITY_NONE: No Security, the host shall set up a normal TCP connection.
+ * @BD_NVME_TCP_SECURITY_TLS12: Transport Layer Security (TLS) version 1.2 (NVMe-oF 1.1).
+ * @BD_NVME_TCP_SECURITY_TLS13: Transport Layer Security (TLS) version 1.3+. The TLS version and cipher is negotiated on every connection.
+ */
+typedef enum {
+    BD_NVME_TCP_SECURITY_NONE  = 0,
+    BD_NVME_TCP_SECURITY_TLS12 = 1,
+    BD_NVME_TCP_SECURITY_TLS13 = 2
+} BDNVMETCPSecurity;
+
+/**
+ * BDNVMEDiscoveryLogEntry:
+ * @transport_type: The NVMe Transport type.
+ * @address_family: The address family.
+ * @sq_flow_control_disable: Indicates that the controller is capable of disabling SQ flow control.
+ * @sq_flow_control_required: Indicates that the controller requires use of SQ flow control.
+ * @port_id: A NVM subsystem port. Different NVMe Transports or address families may utilize the same Port ID value (eg. a Port ID may support both iWARP and RoCE).
+ * @ctrl_id: A Controller ID. Special value of `0xFFFF` indicates a dynamic controller model and a value of `0xFFFE` indicates a temporary ID in a static controller model that should be replaced by a real ID after a connection is established.
+ * @transport_addr: Transport Address.
+ * @transport_svcid: Transport Service Identifier.
+ * @subsys_nqn: Subsystem Qualified Name. For a Discovery Service the value should be the well-known Discovery Service NQN (`nqn.2014-08.org.nvmexpress.discovery`).
+ * @tcp_security: NVMe/TCP transport port security.
+ */
+typedef struct BDNVMEDiscoveryLogEntry {
+    BDNVMETransportType transport_type;
+    BDNVMEAddressFamily address_family;
+    gboolean sq_flow_control_disable;
+    gboolean sq_flow_control_required;
+    guint16 port_id;
+    guint16 ctrl_id;
+    gchar *transport_addr;
+    gchar *transport_svcid;
+    gchar *subsys_nqn;
+    BDNVMETCPSecurity tcp_security;
+} BDNVMEDiscoveryLogEntry;
+
+
+void bd_nvme_controller_info_free (BDNVMEControllerInfo *info);
+BDNVMEControllerInfo * bd_nvme_controller_info_copy (BDNVMEControllerInfo *info);
+
+void bd_nvme_lba_format_free (BDNVMELBAFormat *fmt);
+BDNVMELBAFormat * bd_nvme_lba_format_copy (BDNVMELBAFormat *fmt);
+
+void bd_nvme_namespace_info_free (BDNVMENamespaceInfo *info);
+BDNVMENamespaceInfo * bd_nvme_namespace_info_copy (BDNVMENamespaceInfo *info);
+
+void bd_nvme_smart_log_free (BDNVMESmartLog *log);
+BDNVMESmartLog * bd_nvme_smart_log_copy (BDNVMESmartLog *log);
+
+void bd_nvme_error_log_entry_free (BDNVMEErrorLogEntry *entry);
+BDNVMEErrorLogEntry * bd_nvme_error_log_entry_copy (BDNVMEErrorLogEntry *entry);
+
+void bd_nvme_self_test_log_entry_free (BDNVMESelfTestLogEntry *entry);
+BDNVMESelfTestLogEntry * bd_nvme_self_test_log_entry_copy (BDNVMESelfTestLogEntry *entry);
+const gchar * bd_nvme_self_test_result_to_string (BDNVMESelfTestResult result, GError **error);
+
+void bd_nvme_self_test_log_free (BDNVMESelfTestLog *log);
+BDNVMESelfTestLog * bd_nvme_self_test_log_copy (BDNVMESelfTestLog *log);
+
+void bd_nvme_sanitize_log_free (BDNVMESanitizeLog *log);
+BDNVMESanitizeLog * bd_nvme_sanitize_log_copy (BDNVMESanitizeLog *log);
+
+void bd_nvme_discovery_log_entry_free (BDNVMEDiscoveryLogEntry *entry);
+BDNVMEDiscoveryLogEntry * bd_nvme_discovery_log_entry_copy (BDNVMEDiscoveryLogEntry *entry);
+
+/*
+ * If using the plugin as a standalone library, the following functions should
+ * be called to:
+ *
+ * check_deps() - check plugin's dependencies, returning TRUE if satisfied
+ * init()       - initialize the plugin, returning TRUE on success
+ * close()      - clean after the plugin at the end or if no longer used
+ *
+ */
+gboolean bd_nvme_check_deps (void);
+gboolean bd_nvme_init (void);
+void     bd_nvme_close (void);
+
+gboolean bd_nvme_is_tech_avail (BDNVMETech tech, guint64 mode, GError **error);
+
+
+BDNVMEControllerInfo * bd_nvme_get_controller_info   (const gchar *device, GError **error);
+BDNVMENamespaceInfo *  bd_nvme_get_namespace_info    (const gchar *device, GError **error);
+BDNVMESmartLog *       bd_nvme_get_smart_log         (const gchar *device, GError **error);
+BDNVMEErrorLogEntry ** bd_nvme_get_error_log_entries (const gchar *device, GError **error);
+BDNVMESelfTestLog *    bd_nvme_get_self_test_log     (const gchar *device, GError **error);
+BDNVMESanitizeLog *    bd_nvme_get_sanitize_log      (const gchar *device, GError **error);
+
+gboolean               bd_nvme_device_self_test      (const gchar                  *device,
+                                                      BDNVMESelfTestAction          action,
+                                                      GError                      **error);
+
+gboolean               bd_nvme_format                (const gchar                  *device,
+                                                      guint16                       lba_data_size,
+                                                      guint16                       metadata_size,
+                                                      BDNVMEFormatSecureErase       secure_erase,
+                                                      GError                      **error);
+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);
+
+gchar *                bd_nvme_get_host_nqn          (GError           **error);
+gchar *                bd_nvme_generate_host_nqn     (GError           **error);
+gchar *                bd_nvme_get_host_id           (GError           **error);
+gboolean               bd_nvme_set_host_nqn          (const gchar       *host_nqn,
+                                                      GError           **error);
+gboolean               bd_nvme_set_host_id           (const gchar       *host_id,
+                                                      GError           **error);
+
+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);
+gboolean               bd_nvme_disconnect            (const gchar       *subsysnqn,
+                                                      GError           **error);
+gboolean               bd_nvme_disconnect_by_path    (const gchar       *path,
+                                                      GError           **error);
+BDNVMEDiscoveryLogEntry ** bd_nvme_discover          (const gchar       *discovery_ctrl,
+                                                      gboolean           persistent,
+                                                      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);
+
+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);
+
+
+#endif  /* BD_NVME */
diff --git a/src/python/gi/overrides/BlockDev.py b/src/python/gi/overrides/BlockDev.py
index 8bd03cf8..795e0de4 100644
--- a/src/python/gi/overrides/BlockDev.py
+++ b/src/python/gi/overrides/BlockDev.py
@@ -51,6 +51,7 @@ bd_plugins = { "lvm": BlockDev.Plugin.LVM,
                "fs": BlockDev.Plugin.FS,
                "s390": BlockDev.Plugin.S390,
                "nvdimm": BlockDev.Plugin.NVDIMM,
+               "nvme": BlockDev.Plugin.NVME,
                "vdo": BlockDev.Plugin.VDO,
 }

@@ -877,6 +878,21 @@ def nvdimm_namespace_disable(namespace, extra=None, **kwargs):
 __all__.append("nvdimm_namespace_disable")


+_nvme_connect = BlockDev.nvme_connect
+@override(BlockDev.nvme_connect)
+def nvme_connect(subsysnqn, transport, transport_addr, transport_svcid, host_traddr, host_iface, host_nqn, host_id, extra=None, **kwargs):
+    extra = _get_extra(extra, kwargs)
+    return _nvme_connect(subsysnqn, transport, transport_addr, transport_svcid, host_traddr, host_iface, host_nqn, host_id, extra)
+__all__.append("nvme_connect")
+
+_nvme_discover = BlockDev.nvme_discover
+@override(BlockDev.nvme_discover)
+def nvme_discover(discovery_ctrl, persistent, transport, transport_addr, transport_svcid, host_traddr, host_iface, host_nqn, host_id, extra=None, **kwargs):
+    extra = _get_extra(extra, kwargs)
+    return _nvme_discover(discovery_ctrl, persistent, transport, transport_addr, transport_svcid, host_traddr, host_iface, host_nqn, host_id, extra)
+__all__.append("nvme_discover")
+
+
 _vdo_create = BlockDev.vdo_create
 @override(BlockDev.vdo_create)
 def vdo_create(name, backing_device, logical_size=0, index_memory=0, compression=True, deduplication=True, write_policy=BlockDev.VDOWritePolicy.AUTO, extra=None, **kwargs):
@@ -1175,6 +1191,10 @@ class NVDIMMError(BlockDevError):
     pass
 __all__.append("NVDIMMError")

+class NVMEError(BlockDevError):
+    pass
+__all__.append("NVMEError")
+
 class VDOError(BlockDevError):
     pass
 __all__.append("VDOError")
@@ -1228,6 +1248,9 @@ __all__.append("fs")
 nvdimm = ErrorProxy("nvdimm", BlockDev, [(GLib.Error, NVDIMMError)], [not_implemented_rule])
 __all__.append("nvdimm")

+nvme = ErrorProxy("nvme", BlockDev, [(GLib.Error, NVMEError)], [not_implemented_rule])
+__all__.append("nvme")
+
 s390 = ErrorProxy("s390", BlockDev, [(GLib.Error, S390Error)], [not_implemented_rule])
 __all__.append("s390")

diff --git a/tests/library_test.py b/tests/library_test.py
index efd17bd2..73e122a6 100644
--- a/tests/library_test.py
+++ b/tests/library_test.py
@@ -14,7 +14,8 @@ class LibraryOpsTestCase(unittest.TestCase):
     # the dependencies on CentOS/Debian and we don't need them for this test
     requested_plugins = BlockDev.plugin_specs_from_names(("crypto", "dm",
                                                           "kbd", "loop",
-                                                          "mdraid", "part", "swap"))
+                                                          "mdraid", "part", "swap",
+                                                          "nvme"))

     @classmethod
     def setUpClass(cls):
diff --git a/tests/nvme_test.py b/tests/nvme_test.py
new file mode 100644
index 00000000..a46f7422
--- /dev/null
+++ b/tests/nvme_test.py
@@ -0,0 +1,638 @@
+import unittest
+import os
+import re
+import overrides_hack
+
+from utils import create_sparse_tempfile, create_nvmet_device, delete_nvmet_device, setup_nvme_target, teardown_nvme_target, find_nvme_ctrl_devs_for_subnqn, find_nvme_ns_devs_for_subnqn, get_nvme_hostnqn, run_command, TestTags, tag_test, read_file, write_file
+from gi.repository import BlockDev, GLib
+from distutils.spawn import find_executable
+
+
+class NVMeTest(unittest.TestCase):
+    requested_plugins = BlockDev.plugin_specs_from_names(("nvme", "loop"))
+
+    @classmethod
+    def setUpClass(cls):
+        if not find_executable("nvme"):
+            raise unittest.SkipTest("nvme executable (nvme-cli package) not found in $PATH, skipping.")
+        if not find_executable("nvmetcli"):
+            raise unittest.SkipTest("nvmetcli executable not found in $PATH, skipping.")
+        ret, out, err = run_command("modprobe nvme-fabrics")
+        if ret != 0:
+            raise unittest.SkipTest("nvme-fabrics kernel module unavailable, skipping.")
+
+        if not BlockDev.is_initialized():
+            BlockDev.init(cls.requested_plugins, None)
+        else:
+            BlockDev.reinit(cls.requested_plugins, True, None)
+
+
+class NVMeTestCase(NVMeTest):
+    def setUp(self):
+        self.dev_file = None
+        self.loop_dev = None
+        self.nvme_dev = None
+        self.nvme_ns_dev = None
+
+        self.addCleanup(self._clean_up)
+        self.dev_file = create_sparse_tempfile("nvme_test", 1024**3)
+
+        ret, loop = BlockDev.loop_setup(self.dev_file)
+        if not ret:
+            raise RuntimeError("Failed to setup loop device %s for testing" % self.dev_file)
+        self.loop_dev = "/dev/%s" % loop
+
+        self.nvme_dev = create_nvmet_device(self.loop_dev)
+        self.nvme_ns_dev = self.nvme_dev + "n1"
+
+    def _clean_up(self):
+        if self.nvme_dev:
+            try:
+                delete_nvmet_device(self.nvme_dev)
+            except RuntimeError:
+                # just move on, we can do no better here
+                pass
+
+        # detach the loop device
+        BlockDev.loop_teardown(self.loop_dev)
+        if self.dev_file:
+            os.unlink(self.dev_file)
+
+    @tag_test(TestTags.CORE)
+    def test_ns_info(self):
+        """Test namespace info"""
+
+        with self.assertRaisesRegexp(GLib.GError, r".*Failed to open device .*': No such file or directory"):
+            BlockDev.nvme_get_namespace_info("/dev/nonexistent")
+
+        with self.assertRaisesRegexp(GLib.GError, r"Error getting Namespace Identifier \(NSID\): Inappropriate ioctl for device"):
+            BlockDev.nvme_get_namespace_info(self.nvme_dev)
+
+        # not much information can be gathered from loop-based NVMe target devices...
+        info = BlockDev.nvme_get_namespace_info(self.nvme_ns_dev)
+        self.assertFalse(info.features & BlockDev.NVMENamespaceFeature.THIN)
+        self.assertTrue (info.features & BlockDev.NVMENamespaceFeature.MULTIPATH_SHARED)
+        self.assertFalse(info.features & BlockDev.NVMENamespaceFeature.FORMAT_PROGRESS)
+        self.assertEqual(info.eui64, "0000000000000000")
+        self.assertEqual(info.format_progress_remaining, 0)
+        self.assertEqual(len(info.lba_formats), 1)
+        self.assertGreater(len(info.nguid), 0)
+        self.assertEqual(info.nsid, 1)
+        self.assertEqual(info.ncap, 2097152)
+        self.assertEqual(info.nsize, 2097152)
+        self.assertEqual(info.nuse, 2097152)
+        self.assertGreater(len(info.uuid), 0)
+        self.assertFalse(info.write_protected)
+        self.assertEqual(info.current_lba_format.data_size, 512)
+        self.assertEqual(info.current_lba_format.metadata_size, 0)
+        self.assertEqual(info.current_lba_format.relative_performance, BlockDev.NVMELBAFormatRelativePerformance.BEST)
+
+    @tag_test(TestTags.CORE)
+    def test_ctrl_info(self):
+        """Test controller info"""
+
+        with self.assertRaisesRegexp(GLib.GError, r".*Failed to open device .*': No such file or directory"):
+            BlockDev.nvme_get_controller_info("/dev/nonexistent")
+
+        info = BlockDev.nvme_get_controller_info(self.nvme_dev)
+        self.assertEqual(info.ctrl_id, 1)
+
+        self.assertTrue (info.features & BlockDev.NVMEControllerFeature.MULTIPORT)
+        self.assertTrue (info.features & BlockDev.NVMEControllerFeature.MULTICTRL)
+        self.assertFalse(info.features & BlockDev.NVMEControllerFeature.SRIOV)
+        self.assertTrue (info.features & BlockDev.NVMEControllerFeature.ANA_REPORTING)
+        self.assertFalse(info.features & BlockDev.NVMEControllerFeature.FORMAT)
+        self.assertFalse(info.features & BlockDev.NVMEControllerFeature.FORMAT_ALL_NS)
+        self.assertFalse(info.features & BlockDev.NVMEControllerFeature.NS_MGMT)
+        self.assertFalse(info.features & BlockDev.NVMEControllerFeature.SELFTEST)
+        self.assertFalse(info.features & BlockDev.NVMEControllerFeature.SELFTEST_SINGLE)
+        self.assertFalse(info.features & BlockDev.NVMEControllerFeature.SANITIZE_CRYPTO)
+        self.assertFalse(info.features & BlockDev.NVMEControllerFeature.SANITIZE_BLOCK)
+        self.assertFalse(info.features & BlockDev.NVMEControllerFeature.SANITIZE_OVERWRITE)
+        self.assertFalse(info.features & BlockDev.NVMEControllerFeature.SECURE_ERASE_ALL_NS)
+        self.assertFalse(info.features & BlockDev.NVMEControllerFeature.SECURE_ERASE_CRYPTO)
+        self.assertFalse(info.features & BlockDev.NVMEControllerFeature.STORAGE_DEVICE)
+        self.assertFalse(info.features & BlockDev.NVMEControllerFeature.ENCLOSURE)
+        self.assertFalse(info.features & BlockDev.NVMEControllerFeature.MGMT_PCIE)
+        self.assertFalse(info.features & BlockDev.NVMEControllerFeature.MGMT_SMBUS)
+        self.assertEqual(info.fguid, "")
+        self.assertEqual(info.pci_vendor_id, 0)
+        self.assertEqual(info.pci_subsys_vendor_id, 0)
+        self.assertIn("Linux", info.model_number)
+        self.assertGreater(len(info.serial_number), 0)
+        self.assertGreater(len(info.firmware_ver), 0)
+        self.assertGreater(len(info.nvme_ver), 0)
+        self.assertEqual(info.hmb_min_size, 0)
+        self.assertEqual(info.hmb_pref_size, 0)
+        self.assertEqual(info.num_namespaces, 1024)
+        self.assertEqual(info.selftest_ext_time, 0)
+        self.assertEqual(info.size_total, 0)
+        self.assertEqual(info.size_unalloc, 0)
+        self.assertEqual(info.subsysnqn, "libblockdev_subnqn")
+
+    @tag_test(TestTags.CORE)
+    def test_smart_log(self):
+        """Test SMART health log"""
+
+        with self.assertRaisesRegexp(GLib.GError, r".*Failed to open device .*': No such file or directory"):
+            BlockDev.nvme_get_smart_log("/dev/nonexistent")
+
+        log = BlockDev.nvme_get_smart_log(self.nvme_dev)
+
+        self.assertEqual(log.avail_spare, 0)
+        self.assertEqual(log.cctemp, 0)
+        self.assertEqual(log.critical_temp_time, 0)
+        self.assertEqual(log.ctrl_busy_time, 0)
+        self.assertEqual(log.media_errors, 0)
+        # self.assertEqual(log.num_err_log_entries, 0)
+        self.assertEqual(log.percent_used, 0)
+        self.assertEqual(log.power_cycles, 0)
+        self.assertEqual(log.power_on_hours, 0)
+        self.assertEqual(log.spare_thresh, 0)
+        self.assertEqual(log.temp_sensors, [0, 0, 0, 0, 0, 0, 0, 0])
+        self.assertEqual(log.temperature, 0)
+        self.assertGreater(log.total_data_read, 1)
+        self.assertEqual(log.unsafe_shutdowns, 0)
+        self.assertEqual(log.warning_temp_time, 0)
+        self.assertEqual(log.wctemp, 0)
+        self.assertFalse(log.critical_warning & BlockDev.NVMESmartCriticalWarning.SPARE)
+        self.assertFalse(log.critical_warning & BlockDev.NVMESmartCriticalWarning.TEMPERATURE)
+        self.assertFalse(log.critical_warning & BlockDev.NVMESmartCriticalWarning.DEGRADED)
+        self.assertFalse(log.critical_warning & BlockDev.NVMESmartCriticalWarning.READONLY)
+        self.assertFalse(log.critical_warning & BlockDev.NVMESmartCriticalWarning.VOLATILE_MEM)
+        self.assertFalse(log.critical_warning & BlockDev.NVMESmartCriticalWarning.PMR_READONLY)
+
+
+    @tag_test(TestTags.CORE)
+    def test_error_log(self):
+        """Test error log retrieval"""
+
+        with self.assertRaisesRegexp(GLib.GError, r".*Failed to open device .*': No such file or directory"):
+            BlockDev.nvme_get_error_log_entries("/dev/nonexistent")
+
+        log = BlockDev.nvme_get_error_log_entries(self.nvme_dev)
+        self.assertIsNotNone(log)
+        # TODO: find a way to spoof drive errors
+
+
+    @tag_test(TestTags.CORE)
+    def test_self_test_log(self):
+        """Test self-test log retrieval"""
+
+        with self.assertRaisesRegexp(GLib.GError, r".*Failed to open device .*': No such file or directory"):
+            BlockDev.nvme_get_self_test_log("/dev/nonexistent")
+
+        message = r"NVMe Get Log Page - Device Self-test Log command error: Invalid Field in Command: A reserved coded value or an unsupported value in a defined field|NVMe Get Log Page - Device Self-test Log command error: unrecognized"
+        with self.assertRaisesRegexp(GLib.GError, message):
+            # Cannot retrieve self-test log on a nvme target loop devices
+            BlockDev.nvme_get_self_test_log(self.nvme_dev)
+
+        self.assertEqual(BlockDev.nvme_self_test_result_to_string(BlockDev.NVMESelfTestResult.NO_ERROR), "success")
+        self.assertEqual(BlockDev.nvme_self_test_result_to_string(BlockDev.NVMESelfTestResult.ABORTED), "aborted")
+        self.assertEqual(BlockDev.nvme_self_test_result_to_string(BlockDev.NVMESelfTestResult.CTRL_RESET), "ctrl_reset")
+        self.assertEqual(BlockDev.nvme_self_test_result_to_string(BlockDev.NVMESelfTestResult.NS_REMOVED), "ns_removed")
+        self.assertEqual(BlockDev.nvme_self_test_result_to_string(BlockDev.NVMESelfTestResult.ABORTED_FORMAT), "aborted_format")
+        self.assertEqual(BlockDev.nvme_self_test_result_to_string(BlockDev.NVMESelfTestResult.FATAL_ERROR), "fatal_error")
+        self.assertEqual(BlockDev.nvme_self_test_result_to_string(BlockDev.NVMESelfTestResult.UNKNOWN_SEG_FAIL), "unknown_seg_fail")
+        self.assertEqual(BlockDev.nvme_self_test_result_to_string(BlockDev.NVMESelfTestResult.KNOWN_SEG_FAIL), "known_seg_fail")
+        self.assertEqual(BlockDev.nvme_self_test_result_to_string(BlockDev.NVMESelfTestResult.ABORTED_UNKNOWN), "aborted_unknown")
+        self.assertEqual(BlockDev.nvme_self_test_result_to_string(BlockDev.NVMESelfTestResult.ABORTED_SANITIZE), "aborted_sanitize")
+
+
+    @tag_test(TestTags.CORE)
+    def test_self_test(self):
+        """Test issuing the self-test command"""
+
+        with self.assertRaisesRegexp(GLib.GError, r".*Failed to open device .*': No such file or directory"):
+            BlockDev.nvme_device_self_test("/dev/nonexistent", BlockDev.NVMESelfTestAction.SHORT)
+
+        message = r"Invalid value specified for the self-test action"
+        with self.assertRaisesRegexp(GLib.GError, message):
+            BlockDev.nvme_device_self_test(self.nvme_dev, BlockDev.NVMESelfTestAction.NOT_RUNNING)
+        with self.assertRaisesRegexp(GLib.GError, message):
+            BlockDev.nvme_device_self_test(self.nvme_ns_dev, BlockDev.NVMESelfTestAction.NOT_RUNNING)
+
+        message = r"NVMe Device Self-test command error: Invalid Command Opcode: A reserved coded value or an unsupported value in the command opcode field|NVMe Device Self-test command error: Invalid Queue Identifier: The creation of the I/O Completion Queue failed due to an invalid queue identifier specified as part of the command"
+        with self.assertRaisesRegexp(GLib.GError, message):
+            BlockDev.nvme_device_self_test(self.nvme_dev, BlockDev.NVMESelfTestAction.SHORT)
+        with self.assertRaisesRegexp(GLib.GError, message):
+            BlockDev.nvme_device_self_test(self.nvme_ns_dev, BlockDev.NVMESelfTestAction.SHORT)
+
+        with self.assertRaisesRegexp(GLib.GError, message):
+            BlockDev.nvme_device_self_test(self.nvme_dev, BlockDev.NVMESelfTestAction.EXTENDED)
+        with self.assertRaisesRegexp(GLib.GError, message):
+            BlockDev.nvme_device_self_test(self.nvme_ns_dev, BlockDev.NVMESelfTestAction.EXTENDED)
+
+        with self.assertRaisesRegexp(GLib.GError, message):
+            BlockDev.nvme_device_self_test(self.nvme_dev, BlockDev.NVMESelfTestAction.VENDOR_SPECIFIC)
+        with self.assertRaisesRegexp(GLib.GError, message):
+            BlockDev.nvme_device_self_test(self.nvme_ns_dev, BlockDev.NVMESelfTestAction.VENDOR_SPECIFIC)
+
+        with self.assertRaisesRegexp(GLib.GError, message):
+            BlockDev.nvme_device_self_test(self.nvme_dev, BlockDev.NVMESelfTestAction.ABORT)
+        with self.assertRaisesRegexp(GLib.GError, message):
+            BlockDev.nvme_device_self_test(self.nvme_ns_dev, BlockDev.NVMESelfTestAction.ABORT)
+
+
+    @tag_test(TestTags.CORE)
+    def test_format(self):
+        """Test issuing the format command"""
+
+        with self.assertRaisesRegexp(GLib.GError, r".*Failed to open device .*': No such file or directory"):
+            BlockDev.nvme_format("/dev/nonexistent", 0, 0, BlockDev.NVMEFormatSecureErase.NONE)
+
+        message = r"Couldn't match desired LBA data block size in a device supported LBA format data sizes"
+        with self.assertRaisesRegexp(GLib.GError, message):
+            BlockDev.nvme_format(self.nvme_ns_dev, 123, 0, BlockDev.NVMEFormatSecureErase.NONE)
+        with self.assertRaisesRegexp(GLib.GError, message):
+            BlockDev.nvme_format(self.nvme_dev, 123, 0, BlockDev.NVMEFormatSecureErase.NONE)
+
+        # format doesn't really work on the kernel loop target
+        message = r"Format NVM command error: Invalid Command Opcode: A reserved coded value or an unsupported value in the command opcode field|Format NVM command error: Invalid Queue Identifier: The creation of the I/O Completion Queue failed due to an invalid queue identifier specified as part of the command"
+        with self.assertRaisesRegexp(GLib.GError, message):
+            BlockDev.nvme_format(self.nvme_ns_dev, 0, 0, BlockDev.NVMEFormatSecureErase.NONE)
+        with self.assertRaisesRegexp(GLib.GError, message):
+            BlockDev.nvme_format(self.nvme_dev, 0, 0, BlockDev.NVMEFormatSecureErase.NONE)
+
+
+    @tag_test(TestTags.CORE)
+    def test_sanitize_log(self):
+        """Test sanitize log retrieval"""
+
+        with self.assertRaisesRegexp(GLib.GError, r".*Failed to open device .*': No such file or directory"):
+            BlockDev.nvme_get_sanitize_log("/dev/nonexistent")
+
+        message = r"NVMe Get Log Page - Sanitize Status Log command error: Invalid Field in Command: A reserved coded value or an unsupported value in a defined field|NVMe Get Log Page - Sanitize Status Log command error: unrecognized"
+        with self.assertRaisesRegexp(GLib.GError, message):
+            # Cannot retrieve sanitize log on a nvme target loop devices
+            BlockDev.nvme_get_sanitize_log(self.nvme_dev)
+        with self.assertRaisesRegexp(GLib.GError, message):
+            BlockDev.nvme_get_sanitize_log(self.nvme_ns_dev)
+
+
+    @tag_test(TestTags.CORE)
+    def test_sanitize(self):
+        """Test issuing the sanitize command"""
+
+        message = r".*Failed to open device .*': No such file or directory"
+        with self.assertRaisesRegexp(GLib.GError, message):
+            BlockDev.nvme_sanitize("/dev/nonexistent", BlockDev.NVMESanitizeAction.BLOCK_ERASE, False, 0, 0, False)
+
+        message = r"Sanitize command error: Invalid Command Opcode: A reserved coded value or an unsupported value in the command opcode field|Sanitize command error: Invalid Queue Identifier: The creation of the I/O Completion Queue failed due to an invalid queue identifier specified as part of the command"
+        for i in [BlockDev.NVMESanitizeAction.BLOCK_ERASE, BlockDev.NVMESanitizeAction.CRYPTO_ERASE, BlockDev.NVMESanitizeAction.OVERWRITE, BlockDev.NVMESanitizeAction.EXIT_FAILURE]:
+            with self.assertRaisesRegexp(GLib.GError, message):
+                BlockDev.nvme_sanitize(self.nvme_dev, i, False, 0, 0, False)
+            with self.assertRaisesRegexp(GLib.GError, message):
+                BlockDev.nvme_sanitize(self.nvme_ns_dev, i, False, 0, 0, False)
+
+
+class NVMeFabricsTestCase(NVMeTest):
+    SUBNQN = 'libblockdev_nvme'
+
+    def setUp(self):
+        self.loop_devs = []
+        self.dev_files = []
+        self.hostnqn = get_nvme_hostnqn()
+
+    def _setup_target(self, num_devices):
+        self.addCleanup(self._clean_up)
+        for i in range(num_devices):
+            self.dev_files += [create_sparse_tempfile("nvmeof_test%d" % i, 1024**3)]
+
+            ret, loop = BlockDev.loop_setup(self.dev_files[i])
+            if not ret:
+                raise RuntimeError("Failed to setup loop device %s for testing" % self.dev_files[i])
+            self.loop_devs += ["/dev/%s" % loop]
+        setup_nvme_target(self.loop_devs, self.SUBNQN)
+
+    def _clean_up(self):
+        teardown_nvme_target()
+
+        # detach loop devices
+        for i in self.loop_devs:
+            BlockDev.loop_teardown(i)
+        for i in self.dev_files:
+            os.unlink(i)
+
+    def _safe_unlink(self, f):
+        try:
+            os.unlink(f)
+        except FileNotFoundError:
+            pass
+
+    def _nvme_disconnect(self, subnqn, ignore_errors=False):
+        ret, out, err = run_command("nvme disconnect --nqn=%s" % subnqn)
+        if not ignore_errors and (ret != 0 or 'disconnected 0 ' in out):
+            raise RuntimeError("Error disconnecting the '%s' subsystem NQN: '%s %s'" % (subnqn, out, err))
+
+    def _get_sysconf_dir(self):
+        try:
+            makefile = read_file(os.path.join(os.environ['LIBBLOCKDEV_PROJ_DIR'], 'Makefile'))
+            r = re.search(r'sysconfdir = (.*)', makefile)
+            return r.group(1)
+        except:
+            return None
+
+    @tag_test(TestTags.CORE)
+    def test_connect_single_ns(self):
+        """Test simple connect and disconnect"""
+
+        # test that no device node exists for given subsystem nqn
+        ctrls = find_nvme_ctrl_devs_for_subnqn(self.SUBNQN)
+        self.assertEqual(len(ctrls), 0)
+
+        # nothing to disconnect
+        with self.assertRaisesRegexp(GLib.GError, r"No subsystems matching '.*' NQN found."):
+            BlockDev.nvme_disconnect(self.SUBNQN)
+
+        # nothing to connect to
+        msg = r'Error connecting the controller: '
+        with self.assertRaisesRegexp(GLib.GError, msg):
+            BlockDev.nvme_connect(self.SUBNQN, 'loop', None, None, None, None, self.hostnqn, None)
+        with self.assertRaisesRegexp(GLib.GError, msg):
+            BlockDev.nvme_connect(self.SUBNQN, 'loop', '127.0.0.1', None, None, None, self.hostnqn, None)
+        with self.assertRaisesRegexp(GLib.GError, msg):
+            BlockDev.nvme_connect(self.SUBNQN, 'loop', None, None, None, None, None, None)
+
+        self._setup_target(1)
+
+        # make a connection
+        ret = BlockDev.nvme_connect(self.SUBNQN, 'loop', None, None, None, None, self.hostnqn, None)
+        self.addCleanup(self._nvme_disconnect, self.SUBNQN, ignore_errors=True)
+        self.assertTrue(ret)
+
+        ctrls = find_nvme_ctrl_devs_for_subnqn(self.SUBNQN)
+        self.assertEqual(len(ctrls), 1)
+        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)
+        self.assertEqual(len(namespaces), 1)
+        for ns in namespaces:
+            self.assertTrue(re.match(r'/dev/nvme[0-9]+n[0-9]+', ns))
+            self.assertTrue(os.path.exists(ns))
+
+        # make a duplicate connection
+        ret = BlockDev.nvme_connect(self.SUBNQN, 'loop', None, None, None, None, self.hostnqn, None)
+        self.assertTrue(ret)
+
+        # should see two controllers now
+        ctrls = find_nvme_ctrl_devs_for_subnqn(self.SUBNQN)
+        self.assertEqual(len(ctrls), 2)
+        for c in ctrls:
+            self.assertTrue(re.match(r'/dev/nvme[0-9]+', c))
+            self.assertTrue(os.path.exists(c))
+
+        # disconnect
+        with self.assertRaisesRegexp(GLib.GError, r"No subsystems matching '.*' NQN found."):
+            BlockDev.nvme_disconnect(self.SUBNQN + "xx")
+        with self.assertRaisesRegexp(GLib.GError, r"Unable to match a NVMeoF controller for the specified block device /dev/nvme.*xx"):
+            BlockDev.nvme_disconnect_by_path(ctrls[0] + "xx")
+        # should disconnect both connections as long the SUBNQN matches
+        BlockDev.nvme_disconnect(self.SUBNQN)
+        for c in ctrls:
+            self.assertFalse(os.path.exists(c))
+        for ns in namespaces:
+            self.assertFalse(os.path.exists(ns))
+        ctrls = find_nvme_ctrl_devs_for_subnqn(self.SUBNQN)
+        self.assertEqual(len(ctrls), 0)
+        namespaces = find_nvme_ns_devs_for_subnqn(self.SUBNQN)
+        self.assertEqual(len(namespaces), 0)
+
+
+    @tag_test(TestTags.CORE)
+    def test_connect_multiple_ns(self):
+        """Test connect and disconnect multiple namespaces"""
+
+        NUM_NS = 3
+
+        # test that no device node exists for given subsystem nqn
+        ctrls = find_nvme_ctrl_devs_for_subnqn(self.SUBNQN)
+        self.assertEqual(len(ctrls), 0)
+
+        self._setup_target(NUM_NS)
+
+        # make a connection
+        ret = BlockDev.nvme_connect(self.SUBNQN, 'loop', None, None, None, None, self.hostnqn, None)
+        self.addCleanup(self._nvme_disconnect, self.SUBNQN, ignore_errors=True)
+        self.assertTrue(ret)
+
+        ctrls = find_nvme_ctrl_devs_for_subnqn(self.SUBNQN)
+        self.assertEqual(len(ctrls), 1)
+        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)
+        self.assertEqual(len(namespaces), NUM_NS)
+        for ns in namespaces:
+            self.assertTrue(re.match(r'/dev/nvme[0-9]+n[0-9]+', ns))
+            self.assertTrue(os.path.exists(ns))
+
+            # verify the sysfs paths
+            ret, ns_sysfs_path, err = run_command("udevadm info --query=path %s" % ns)
+            if ret != 0:
+                raise RuntimeError("Error getting udev info for %s: '%s'" % (ns,  err))
+            self.assertIsNotNone(ns_sysfs_path)
+            self.assertGreater(len(ns_sysfs_path), 0)
+            ns_sysfs_path = "/sys" + ns_sysfs_path
+            ret, ctrl_sysfs_path, err = run_command("udevadm info --query=path %s" % ctrls[0])
+            if ret != 0:
+                raise RuntimeError("Error getting udev info for %s: '%s'" % (ctrls[0],  err))
+            self.assertIsNotNone(ctrl_sysfs_path)
+            self.assertGreater(len(ctrl_sysfs_path), 0)
+            ctrl_sysfs_path = "/sys" + ctrl_sysfs_path
+
+            ctrl_sysfs_paths = BlockDev.nvme_find_ctrls_for_ns(ns_sysfs_path, None, None, None)
+            self.assertIsNotNone(ctrl_sysfs_paths)
+            self.assertEqual(len(ctrl_sysfs_paths), 1)
+            self.assertEqual(ctrl_sysfs_path, ctrl_sysfs_paths[0])
+
+            ctrl_sysfs_paths = BlockDev.nvme_find_ctrls_for_ns(ns_sysfs_path + "xxx", None, None, None)
+            self.assertEqual(len(ctrl_sysfs_paths), 0)
+            ctrl_sysfs_paths = BlockDev.nvme_find_ctrls_for_ns(ns_sysfs_path, self.SUBNQN, None, None)
+            self.assertEqual(len(ctrl_sysfs_paths), 1)
+            self.assertEqual(ctrl_sysfs_path, ctrl_sysfs_paths[0])
+            ctrl_sysfs_paths = BlockDev.nvme_find_ctrls_for_ns(ns_sysfs_path, self.SUBNQN, self.hostnqn, None)
+            self.assertEqual(len(ctrl_sysfs_paths), 1)
+            self.assertEqual(ctrl_sysfs_path, ctrl_sysfs_paths[0])
+
+            ctrl_sysfs_paths = BlockDev.nvme_find_ctrls_for_ns(ns_sysfs_path, "unknownsubsysnqn", None, None)
+            self.assertEqual(len(ctrl_sysfs_paths), 0)
+            ctrl_sysfs_paths = BlockDev.nvme_find_ctrls_for_ns(ns_sysfs_path, None, "unknownhostnqn", None)
+            self.assertEqual(len(ctrl_sysfs_paths), 0)
+            ctrl_sysfs_paths = BlockDev.nvme_find_ctrls_for_ns(ns_sysfs_path, self.SUBNQN, "unknownhostnqn", None)
+            self.assertEqual(len(ctrl_sysfs_paths), 0)
+            ctrl_sysfs_paths = BlockDev.nvme_find_ctrls_for_ns(ns_sysfs_path, None, None, "unknownhostid")
+            self.assertEqual(len(ctrl_sysfs_paths), 0)
+            ctrl_sysfs_paths = BlockDev.nvme_find_ctrls_for_ns(ns_sysfs_path, self.SUBNQN, self.hostnqn, "unknownhostid")
+            self.assertEqual(len(ctrl_sysfs_paths), 0)
+
+        # disconnect
+        BlockDev.nvme_disconnect_by_path(ctrls[0])
+        for c in ctrls:
+            self.assertFalse(os.path.exists(c))
+        for ns in namespaces:
+            self.assertFalse(os.path.exists(ns))
+        ctrls = find_nvme_ctrl_devs_for_subnqn(self.SUBNQN)
+        self.assertEqual(len(ctrls), 0)
+        namespaces = find_nvme_ns_devs_for_subnqn(self.SUBNQN)
+        self.assertEqual(len(namespaces), 0)
+
+
+    @tag_test(TestTags.CORE)
+    def test_discovery(self):
+        """Test discovery"""
+
+        DISCOVERY_NQN = 'nqn.2014-08.org.nvmexpress.discovery'
+
+        # nvme target unavailable
+        ctrls = find_nvme_ctrl_devs_for_subnqn(DISCOVERY_NQN)
+        self.assertEqual(len(ctrls), 0)
+        with self.assertRaisesRegexp(GLib.GError, r"Invalid discovery controller device specified"):
+            BlockDev.nvme_discover('nonsense', True, 'loop', None, None, None, None, self.hostnqn, None)
+        with self.assertRaisesRegexp(GLib.GError, r"Couldn't access the discovery controller device specified"):
+            BlockDev.nvme_discover('/dev/nvmenonsense', True, 'loop', None, None, None, None, self.hostnqn, None)
+        with self.assertRaisesRegexp(GLib.GError, r"Error connecting the controller: (Input/output error|No such file or directory|failed to write to nvme-fabrics device)"):
+            BlockDev.nvme_discover(None, False, 'loop', None, None, None, None, self.hostnqn, None)
+
+        self._setup_target(1)
+
+        # non-persistent discovery connection
+        entries = BlockDev.nvme_discover(None, False, 'loop', None, None, None, None, self.hostnqn, None)
+        self.assertGreater(len(entries), 0)
+        self.assertEqual(entries[0].transport_type, BlockDev.NVMETransportType.LOOP)
+        self.assertEqual(entries[0].address_family, BlockDev.NVMEAddressFamily.PCI)
+        self.assertEqual(entries[0].port_id, 1)
+        self.assertEqual(entries[0].ctrl_id, 65535)
+        self.assertIn(self.SUBNQN, [entry.subsys_nqn for entry in entries])
+        self.assertEqual(entries[0].tcp_security, BlockDev.NVMETCPSecurity.NONE)
+        ctrls = find_nvme_ctrl_devs_for_subnqn(DISCOVERY_NQN)
+        self.assertEqual(len(ctrls), 0)
+
+        # persistent discovery connection
+        entries = BlockDev.nvme_discover(None, True, 'loop', None, None, None, None, self.hostnqn, None)
+        self.addCleanup(self._nvme_disconnect, DISCOVERY_NQN, ignore_errors=True)
+        self.assertGreater(len(entries), 0)
+        self.assertIn(self.SUBNQN, [entry.subsys_nqn for entry in entries])
+        ctrls = find_nvme_ctrl_devs_for_subnqn(DISCOVERY_NQN)
+        self.assertEqual(len(ctrls), 1)
+
+        # reuse the persistent connection
+        entries = BlockDev.nvme_discover(ctrls[0], False, 'loop', None, None, None, None, self.hostnqn, None)
+        self.assertGreater(len(entries), 0)
+        self.assertIn(self.SUBNQN, [entry.subsys_nqn for entry in entries])
+        ctrls = find_nvme_ctrl_devs_for_subnqn(DISCOVERY_NQN)
+        self.assertEqual(len(ctrls), 1)
+
+        # close the persistent connection
+        BlockDev.nvme_disconnect(DISCOVERY_NQN)
+
+
+    @tag_test(TestTags.CORE)
+    def test_host_nqn(self):
+        """Test Host NQN/ID manipulation and a simple connect"""
+
+        HOSTNQN_PATH = '/etc/nvme/hostnqn'
+        HOSTID_PATH = '/etc/nvme/hostid'
+        FAKE_HOSTNQN1 = 'nqn.2014-08.org.nvmexpress:uuid:ffffffff-ffff-ffff-ffff-ffffffffffff'
+        FAKE_HOSTNQN2 = 'nqn.2014-08.org.nvmexpress:uuid:beefbeef-beef-beef-beef-beefdeadbeef'
+        FAKE_HOSTID1 = 'eeeeeeee-eeee-eeee-eeee-eeeeeeeeeeee'
+        FAKE_HOSTID2 = 'beefbeef-beef-beef-beef-beefdeadbeef'
+
+        # libnvme might have been configured with a different prefix than libblockdev
+        sysconf_dir = self._get_sysconf_dir()
+        if sysconf_dir != '/etc':
+            self.skipTest("libblockdev was not configured with standard prefix (/usr)")
+
+        # test that no device node exists for given subsystem nqn
+        ctrls = find_nvme_ctrl_devs_for_subnqn(self.SUBNQN)
+        self.assertEqual(len(ctrls), 0)
+
+        # save hostnqn and hostid files
+        try:
+            saved_hostnqn = read_file(HOSTNQN_PATH)
+            self.addCleanup(write_file, HOSTNQN_PATH, saved_hostnqn)
+        except:
+            self.addCleanup(self._safe_unlink, HOSTNQN_PATH)
+            pass
+        try:
+            saved_hostid = read_file(HOSTID_PATH)
+            self.addCleanup(write_file, HOSTID_PATH, saved_hostid)
+        except:
+            self.addCleanup(self._safe_unlink, HOSTID_PATH)
+            pass
+
+        self._safe_unlink(HOSTNQN_PATH)
+        self._safe_unlink(HOSTID_PATH)
+        hostnqn = BlockDev.nvme_get_host_nqn()
+        self.assertEqual(len(hostnqn), 0)
+        hostnqn = BlockDev.nvme_generate_host_nqn()
+        self.assertTrue(hostnqn.startswith('nqn.2014-08.org.nvmexpress:uuid:'))
+        hostid = BlockDev.nvme_get_host_id()
+        self.assertEqual(len(hostid), 0)
+
+        # connection without hostnqn set
+        self._setup_target(1)
+        ret = BlockDev.nvme_connect(self.SUBNQN, 'loop', None, None, None, None, None, None)
+        self.addCleanup(self._nvme_disconnect, self.SUBNQN, ignore_errors=True)
+        self.assertTrue(ret)
+        ctrls = find_nvme_ctrl_devs_for_subnqn(self.SUBNQN)
+        self.assertEqual(len(ctrls), 1)
+        sysfs_hostnqn = read_file('/sys/class/nvme/%s/hostnqn' % os.path.basename(ctrls[0]))
+        self.assertEqual(sysfs_hostnqn.strip(), BlockDev.nvme_generate_host_nqn())
+        BlockDev.nvme_disconnect(self.SUBNQN)
+
+        ctrls = find_nvme_ctrl_devs_for_subnqn(self.SUBNQN)
+        self.assertEqual(len(ctrls), 0)
+
+        ret = BlockDev.nvme_connect(self.SUBNQN, 'loop', None, None, None, None, FAKE_HOSTNQN1, FAKE_HOSTID1)
+        self.assertTrue(ret)
+        ctrls = find_nvme_ctrl_devs_for_subnqn(self.SUBNQN)
+        self.assertEqual(len(ctrls), 1)
+        sysfs_hostnqn = read_file('/sys/class/nvme/%s/hostnqn' % os.path.basename(ctrls[0]))
+        sysfs_hostid = read_file('/sys/class/nvme/%s/hostid' % os.path.basename(ctrls[0]))
+        self.assertEqual(sysfs_hostnqn.strip(), FAKE_HOSTNQN1)
+        self.assertEqual(sysfs_hostid.strip(), FAKE_HOSTID1)
+        BlockDev.nvme_disconnect(self.SUBNQN)
+
+        ctrls = find_nvme_ctrl_devs_for_subnqn(self.SUBNQN)
+        self.assertEqual(len(ctrls), 0)
+
+        # fill with custom IDs
+        ret = BlockDev.nvme_set_host_nqn(FAKE_HOSTNQN1)
+        self.assertTrue(ret)
+        ret = BlockDev.nvme_set_host_id(FAKE_HOSTID1)
+        self.assertTrue(ret)
+        hostnqn = BlockDev.nvme_get_host_nqn()
+        self.assertEqual(hostnqn, FAKE_HOSTNQN1)
+        hostid = BlockDev.nvme_get_host_id()
+        self.assertEqual(hostid, FAKE_HOSTID1)
+
+        ret = BlockDev.nvme_connect(self.SUBNQN, 'loop', None, None, None, None, None, None)
+        self.assertTrue(ret)
+        ctrls = find_nvme_ctrl_devs_for_subnqn(self.SUBNQN)
+        self.assertEqual(len(ctrls), 1)
+        sysfs_hostnqn = read_file('/sys/class/nvme/%s/hostnqn' % os.path.basename(ctrls[0]))
+        sysfs_hostid = read_file('/sys/class/nvme/%s/hostid' % os.path.basename(ctrls[0]))
+        self.assertEqual(sysfs_hostnqn.strip(), FAKE_HOSTNQN1)
+        self.assertEqual(sysfs_hostid.strip(), FAKE_HOSTID1)
+        BlockDev.nvme_disconnect(self.SUBNQN)
+
+        ctrls = find_nvme_ctrl_devs_for_subnqn(self.SUBNQN)
+        self.assertEqual(len(ctrls), 0)
+
+        ret = BlockDev.nvme_connect(self.SUBNQN, 'loop', None, None, None, None, FAKE_HOSTNQN2, FAKE_HOSTID2)
+        self.assertTrue(ret)
+        ctrls = find_nvme_ctrl_devs_for_subnqn(self.SUBNQN)
+        self.assertEqual(len(ctrls), 1)
+        sysfs_hostnqn = read_file('/sys/class/nvme/%s/hostnqn' % os.path.basename(ctrls[0]))
+        sysfs_hostid = read_file('/sys/class/nvme/%s/hostid' % os.path.basename(ctrls[0]))
+        self.assertEqual(sysfs_hostnqn.strip(), FAKE_HOSTNQN2)
+        self.assertEqual(sysfs_hostid.strip(), FAKE_HOSTID2)
+        BlockDev.nvme_disconnect(self.SUBNQN)
+
+        ctrls = find_nvme_ctrl_devs_for_subnqn(self.SUBNQN)
+        self.assertEqual(len(ctrls), 0)
+
+        self._safe_unlink(HOSTNQN_PATH)
+        self._safe_unlink(HOSTID_PATH)
diff --git a/tests/overrides_test.py b/tests/overrides_test.py
index d3faf3cf..903348cd 100644
--- a/tests/overrides_test.py
+++ b/tests/overrides_test.py
@@ -11,7 +11,8 @@ class OverridesTest(unittest.TestCase):
     # the dependencies on CentOS/Debian and we don't need them for this test
     requested_plugins = BlockDev.plugin_specs_from_names(("crypto", "dm",
                                                           "kbd", "loop", "lvm",
-                                                          "mdraid", "part", "swap"))
+                                                          "mdraid", "part", "swap",
+                                                          "nvme"))

     @classmethod
     def setUpClass(cls):
diff --git a/tests/run_tests.py b/tests/run_tests.py
index 65c5529c..27e699c6 100644
--- a/tests/run_tests.py
+++ b/tests/run_tests.py
@@ -14,7 +14,7 @@ import yaml

 from distutils.spawn import find_executable

-LIBDIRS = 'src/utils/.libs:src/plugins/.libs:src/plugins/fs/.libs:src/lib/.libs'
+LIBDIRS = 'src/utils/.libs:src/plugins/.libs:src/plugins/fs/.libs:src/lib/.libs:src/plugins/nvme/.libs'
 GIDIR = 'src/lib'

 SKIP_CONFIG = 'skip.yml'
@@ -188,6 +188,7 @@ if __name__ == '__main__':

     testdir = os.path.abspath(os.path.dirname(__file__))
     projdir = os.path.abspath(os.path.normpath(os.path.join(testdir, '..')))
+    os.environ['LIBBLOCKDEV_PROJ_DIR'] = projdir

     args = parse_args()
     if args.installed:
diff --git a/tests/utils.py b/tests/utils.py
index 584fde5c..354453c3 100644
--- a/tests/utils.py
+++ b/tests/utils.py
@@ -1,6 +1,7 @@
 from __future__ import print_function

 import os
+import stat
 import re
 import glob
 import subprocess
@@ -9,6 +10,8 @@ import dbus
 import unittest
 import time
 import sys
+import json
+import uuid
 from contextlib import contextmanager
 from enum import Enum
 from itertools import chain
@@ -21,6 +24,7 @@ except ImportError:
     DEVNULL = open("/dev/null", "w")

 _lio_devs = dict()
+_nvmet_devs = dict()

 def create_sparse_tempfile(name, size):
     """ Create a temporary sparse file.
@@ -209,6 +213,242 @@ def delete_lio_device(dev_path):
     else:
         raise RuntimeError("Unknown device '%s'" % dev_path)

+def find_nvme_ctrl_devs_for_subnqn(subnqn):
+    """
+    Find NVMe controller devices for the specified subsystem nqn
+
+    :param str subnqn: subsystem nqn
+    """
+
+    def _check_subsys(subsys, dev_paths):
+        if subsys['SubsystemNQN'] == subnqn:
+            for ctrl in subsys['Controllers']:
+                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
+
+    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 []
+
+    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)
+
+    return dev_paths
+
+
+def find_nvme_ns_devs_for_subnqn(subnqn):
+    """
+    Find NVMe namespace block devices for the specified subsystem nqn
+
+    :param str subnqn: subsystem nqn
+    """
+
+    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)
+
+    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 []
+
+    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)
+
+    return ns_dev_paths
+
+
+def get_nvme_hostnqn():
+    """
+    Retrieves NVMe host NQN string from /etc/nvme/hostnqn or uses nvme-cli to generate
+    new one (stable, typically generated from machine DMI data) when not available.
+    """
+
+    hostnqn = None
+    try:
+        hostnqn = read_file('/etc/nvme/hostnqn')
+    except:
+        pass
+
+    if hostnqn is None or len(hostnqn.strip()) < 1:
+        ret, hostnqn, err = run_command('nvme gen-hostnqn')
+        if ret != 0:
+            raise RuntimeError("Cannot get host NQN: '%s %s'" % (hostnqn, err))
+
+    return hostnqn.strip()
+
+
+def setup_nvme_target(dev_paths, subnqn):
+    """
+    Sets up a new NVMe target loop device (using nvmetcli) on top of the
+    :param:`dev_paths` backing block devices.
+
+    :param set dev_paths: set of backing block device paths
+    :param str subnqn: Subsystem NQN
+    """
+
+    # modprobe required nvme target modules
+    for module in ['nvmet', 'nvme-loop']:
+        ret, out, err = run_command("modprobe %s" % module)
+        if ret != 0:
+            raise RuntimeError("Cannot load required module %s: '%s %s'" % (module, out, err))
+
+    # create a JSON file for nvmetcli
+    with tempfile.NamedTemporaryFile(mode='wt',delete=False) as tmp:
+        tcli_json_file = tmp.name
+        namespaces = ",".join(["""
+        {{
+          "device": {{
+            "nguid": "{nguid}",
+            "path": "{path}"
+          }},
+          "enable": 1,
+          "nsid": {nsid}
+        }}
+        """.format(nguid=uuid.uuid4(), path=dev_path, nsid=i) for i, dev_path in enumerate(dev_paths, start=1)])
+
+        json = """
+{
+  "ports": [
+    {
+      "addr": {
+        "adrfam": "",
+        "traddr": "",
+        "treq": "not specified",
+        "trsvcid": "",
+        "trtype": "loop"
+      },
+      "portid": 1,
+      "referrals": [],
+      "subsystems": [
+        "%s"
+      ]
+    }
+  ],
+  "subsystems": [
+    {
+      "attr": {
+        "allow_any_host": "1"
+      },
+      "namespaces": [
+%s
+      ],
+      "nqn": "%s"
+    }
+  ]
+}
+"""
+        tmp.write(json % (subnqn, namespaces, subnqn))
+
+    # export the loop device on the target
+    ret, out, err = run_command("nvmetcli restore %s" % tcli_json_file)
+    os.unlink(tcli_json_file)
+    if ret != 0:
+        raise RuntimeError("Error setting up the NVMe target: '%s %s'" % (out, err))
+
+
+def teardown_nvme_target():
+    """
+    Tear down any previously set up kernel nvme target.
+    """
+    ret, out, err = run_command("nvmetcli clear")
+    if ret != 0:
+        raise RuntimeError("Error clearing the NVMe target: '%s %s'" % (out, err))
+
+
+def create_nvmet_device(dev_path):
+    """
+    Creates a new NVMe target loop device (using nvmetcli) on top of the
+    :param:`dev_path` backing block device and initiates a connection to it.
+
+    :param str dev_path: backing block device path
+    :returns: path of the NVMe controller device (e.g. "/dev/nvme0")
+    :rtype: str
+    """
+
+    SUBNQN = 'libblockdev_subnqn'
+    hostnqn = get_nvme_hostnqn()
+
+    setup_nvme_target([dev_path], SUBNQN)
+
+    # connect initiator to the newly created target
+    (ret, out, err) = run_command("nvme connect --transport=loop --hostnqn=%s --nqn=%s" % (hostnqn, SUBNQN))
+    if ret != 0:
+        raise RuntimeError("Error connecting to the NVMe target: '%s %s'" % (out, err))
+
+    nvme_devs = find_nvme_ctrl_devs_for_subnqn(SUBNQN)
+    if len(nvme_devs) != 1:
+        raise RuntimeError("Error looking up block device for the '%s' nqn" % SUBNQN)
+
+    _nvmet_devs[nvme_devs[0]] = (SUBNQN, dev_path)
+    return nvme_devs[0]
+
+
+def delete_nvmet_device(nvme_dev):
+    """
+    Logout and tear down previously created NVMe target device.
+
+    :param str nvme_dev: path of the NVMe device to delete
+    """
+    if nvme_dev in _nvmet_devs:
+        subnqn, dev_path = _nvmet_devs[nvme_dev]
+
+        # disconnect the initiator
+        ret, out, err = run_command("nvme disconnect --nqn=%s" % subnqn)
+        if ret != 0:
+            raise RuntimeError("Error disconnecting the '%s' nqn: '%s %s'" % (subnqn, out, err))
+
+        # clear the target
+        teardown_nvme_target()
+    else:
+        raise RuntimeError("Unknown device '%s'" % nvme_dev)
+
+
 def read_file(filename):
     with open(filename, "r") as f:
         content = f.read()
--
2.37.3