summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorCoprDistGit <infra@openeuler.org>2024-08-06 02:22:06 +0000
committerCoprDistGit <infra@openeuler.org>2024-08-06 02:22:06 +0000
commit4f2b83cd074dd4ca966271611c2c7426796ee08f (patch)
tree0c9836ea826f294860bacdef312a61abdcdae3bc
parentd4519e44a5f6efd56e0943973dc3975e737e7139 (diff)
automatic import of libblockdevopeneuler24.03_LTS
-rw-r--r--.gitignore1
-rw-r--r--0001-lvm-devices-file-support.patch1808
-rw-r--r--0002-Add-support-for-creating-and-activating-integrity-de.patch963
-rw-r--r--0003-NVMe-plugin-backport.patch6467
-rw-r--r--0004-Fix-double-free-in-write_escrow_data_file.patch59
-rw-r--r--0005-nvme-Fix-namespace-identifiers.patch506
-rw-r--r--0006-Allow-resizing-of-inactive-LVs-with-latest-LVM.patch219
-rw-r--r--0007-tests-Fix-test_swapon_pagesize-on-systems-with-64k-p.patch41
-rw-r--r--0008-part-Fix-segfault-when-adding-a-partition-too-big-fo.patch32
-rw-r--r--0009-Fix-issues-in-tests-when-running-in-FIPS-mode.patch70
-rw-r--r--0010-lvm-Add-a-function-to-activate-LVs-in-shared-mode.patch300
-rw-r--r--0011-nvme_libblockdev-3.0.4_backport.patch1395
-rw-r--r--libblockdev-gcc11.patch15
-rw-r--r--libblockdev.spec2409
-rw-r--r--sources1
15 files changed, 14286 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore
index e69de29..4e49b0d 100644
--- a/.gitignore
+++ b/.gitignore
@@ -0,0 +1 @@
+/libblockdev-2.28.tar.gz
diff --git a/0001-lvm-devices-file-support.patch b/0001-lvm-devices-file-support.patch
new file mode 100644
index 0000000..9e38b41
--- /dev/null
+++ b/0001-lvm-devices-file-support.patch
@@ -0,0 +1,1808 @@
+From 94d707dd225104ba14422eeb43c73b1f742b12da Mon Sep 17 00:00:00 2001
+From: Vojtech Trefny <vtrefny@redhat.com>
+Date: Tue, 13 Jul 2021 13:22:05 +0200
+Subject: [PATCH 1/7] lvm: Allow configuring global "device filter" for LVM
+ commands
+
+Starting with 2.03.12 LVM introduces a new system for telling LVM
+which devices it should use. The old device filters in config are
+no longer working and we need to use either the system.devices
+config file in /etc/lvm/devices (default behaviour) or specify
+all allowed devices using the new --devices option. Because this
+option must be specified for every call which might be incovenient
+for our users, this commit introduces a new function to configure
+this globally, which we already do for the --config option.
+---
+ src/lib/plugin_apis/lvm.api | 23 +++
+ src/plugins/lvm-dbus.c | 75 ++++++++-
+ src/plugins/lvm.c | 97 ++++++++++--
+ src/plugins/lvm.h | 4 +
+ tests/library_test.py | 304 ++++++++++++++++++++----------------
+ tests/lvm_dbus_tests.py | 47 +++++-
+ tests/lvm_test.py | 50 ++++++
+ tests/overrides_test.py | 23 ++-
+ 8 files changed, 470 insertions(+), 153 deletions(-)
+
+diff --git a/src/lib/plugin_apis/lvm.api b/src/lib/plugin_apis/lvm.api
+index c695c111..23f68b81 100644
+--- a/src/lib/plugin_apis/lvm.api
++++ b/src/lib/plugin_apis/lvm.api
+@@ -601,6 +601,7 @@ typedef enum {
+ BD_LVM_TECH_CACHE_CALCS,
+ BD_LVM_TECH_GLOB_CONF,
+ BD_LVM_TECH_VDO,
++ BD_LVM_TECH_DEVICES,
+ } BDLVMTech;
+
+ typedef enum {
+@@ -1214,6 +1215,28 @@ gboolean bd_lvm_set_global_config (const gchar *new_config, GError **error);
+ */
+ gchar* bd_lvm_get_global_config (GError **error);
+
++/**
++ * bd_lvm_set_devices_filter:
++ * @devices: (allow-none) (array zero-terminated=1): list of devices for lvm commands to work on
++ * @error: (out): place to store error (if any)
++ *
++ * Returns: whether the devices filter was successfully set or not
++ *
++ * Tech category: %BD_LVM_TECH_DEVICES no mode (it is ignored)
++ */
++gboolean bd_lvm_set_devices_filter (const gchar **devices, GError **error);
++
++/**
++ * bd_lvm_get_devices_filter:
++ * @error: (out): place to store error (if any)
++ *
++ * Returns: (transfer full) (array zero-terminated=1): a copy of a string representation of
++ * the currently set LVM devices filter
++ *
++ * Tech category: %BD_LVM_TECH_DEVICES no mode (it is ignored)
++ */
++gchar** bd_lvm_get_devices_filter (GError **error);
++
+ /**
+ * bd_lvm_cache_get_default_md_size:
+ * @cache_size: size of the cache to determine MD size for
+diff --git a/src/plugins/lvm-dbus.c b/src/plugins/lvm-dbus.c
+index 51572c9a..b47ed0ef 100644
+--- a/src/plugins/lvm-dbus.c
++++ b/src/plugins/lvm-dbus.c
+@@ -35,6 +35,8 @@
+ static GMutex global_config_lock;
+ static gchar *global_config_str = NULL;
+
++static gchar *global_devices_str = NULL;
++
+ #define LVM_BUS_NAME "com.redhat.lvmdbus1"
+ #define LVM_OBJ_PREFIX "/com/redhat/lvmdbus1"
+ #define MANAGER_OBJ "/com/redhat/lvmdbus1/Manager"
+@@ -241,11 +243,20 @@ static gboolean setup_dbus_connection (GError **error) {
+ return TRUE;
+ }
+
++static volatile guint avail_deps = 0;
+ static volatile guint avail_dbus_deps = 0;
+ static volatile guint avail_features = 0;
+ static volatile guint avail_module_deps = 0;
+ static GMutex deps_check_lock;
+
++#define DEPS_LVMDEVICES 0
++#define DEPS_LVMDEVICES_MASK (1 << DEPS_LVMDEVICES)
++#define DEPS_LAST 1
++
++static const UtilDep deps[DEPS_LAST] = {
++ {"lvmdevices", NULL, NULL, NULL},
++};
++
+ #define DBUS_DEPS_LVMDBUSD 0
+ #define DBUS_DEPS_LVMDBUSD_MASK (1 << DBUS_DEPS_LVMDBUSD)
+ #define DBUS_DEPS_LAST 1
+@@ -378,6 +389,8 @@ gboolean bd_lvm_is_tech_avail (BDLVMTech tech, guint64 mode, GError **error) {
+ return check_dbus_deps (&avail_dbus_deps, DBUS_DEPS_LVMDBUSD_MASK, dbus_deps, DBUS_DEPS_LAST, &deps_check_lock, error) &&
+ check_features (&avail_features, FEATURES_VDO_MASK, features, FEATURES_LAST, &deps_check_lock, error) &&
+ check_module_deps (&avail_module_deps, MODULE_DEPS_VDO_MASK, module_deps, MODULE_DEPS_LAST, &deps_check_lock, error);
++ case BD_LVM_TECH_DEVICES:
++ return check_deps (&avail_deps, DEPS_LVMDEVICES_MASK, deps, DEPS_LAST, &deps_check_lock, error);
+ default:
+ /* everything is supported by this implementation of the plugin */
+ return check_dbus_deps (&avail_dbus_deps, DBUS_DEPS_LVMDBUSD_MASK, dbus_deps, DBUS_DEPS_LAST, &deps_check_lock, error);
+@@ -515,6 +528,7 @@ static gboolean unbox_params_and_add (GVariant *params, GVariantBuilder *builder
+
+ static GVariant* call_lvm_method (const gchar *obj, const gchar *intf, const gchar *method, GVariant *params, GVariant *extra_params, const BDExtraArg **extra_args, guint64 *task_id, guint64 *progress_id, gboolean lock_config, GError **error) {
+ GVariant *config = NULL;
++ GVariant *devices = NULL;
+ GVariant *param = NULL;
+ GVariantIter iter;
+ GVariantBuilder builder;
+@@ -536,8 +550,8 @@ static GVariant* call_lvm_method (const gchar *obj, const gchar *intf, const gch
+ if (lock_config)
+ g_mutex_lock (&global_config_lock);
+
+- if (global_config_str || extra_params || extra_args) {
+- if (global_config_str || extra_args) {
++ if (global_config_str || global_devices_str || extra_params || extra_args) {
++ if (global_config_str || global_devices_str || extra_args) {
+ /* add the global config to the extra_params */
+ g_variant_builder_init (&extra_builder, G_VARIANT_TYPE_DICTIONARY);
+
+@@ -558,6 +572,11 @@ static GVariant* call_lvm_method (const gchar *obj, const gchar *intf, const gch
+ g_variant_builder_add (&extra_builder, "{sv}", "--config", config);
+ added_extra = TRUE;
+ }
++ if (global_devices_str) {
++ devices = g_variant_new ("s", global_devices_str);
++ g_variant_builder_add (&extra_builder, "{sv}", "--devices", devices);
++ added_extra = TRUE;
++ }
+
+ if (added_extra)
+ config_extra_params = g_variant_builder_end (&extra_builder);
+@@ -2654,6 +2673,58 @@ gchar* bd_lvm_get_global_config (GError **error UNUSED) {
+ return ret;
+ }
+
++/**
++ * bd_lvm_set_devices_filter:
++ * @devices: (allow-none) (array zero-terminated=1): list of devices for lvm commands to work on
++ * @error: (out): place to store error (if any)
++ *
++ * Returns: whether the devices filter was successfully set or not
++ *
++ * Tech category: %BD_LVM_TECH_DEVICES no mode (it is ignored)
++ */
++gboolean bd_lvm_set_devices_filter (const gchar **devices, GError **error) {
++ if (!bd_lvm_is_tech_avail (BD_LVM_TECH_DEVICES, 0, error))
++ return FALSE;
++
++ g_mutex_lock (&global_config_lock);
++
++ /* first free the old value */
++ g_free (global_devices_str);
++
++ /* now store the new one */
++ if (!devices || !(*devices))
++ global_devices_str = NULL;
++ else
++ global_devices_str = g_strjoinv (",", (gchar **) devices);
++
++ g_mutex_unlock (&global_config_lock);
++ return TRUE;
++}
++
++/**
++ * bd_lvm_get_devices_filter:
++ * @error: (out): place to store error (if any)
++ *
++ * Returns: (transfer full) (array zero-terminated=1): a copy of a string representation of
++ * the currently set LVM devices filter
++ *
++ * Tech category: %BD_LVM_TECH_DEVICES no mode (it is ignored)
++ */
++gchar** bd_lvm_get_devices_filter (GError **error UNUSED) {
++ gchar **ret = NULL;
++
++ g_mutex_lock (&global_config_lock);
++
++ if (global_devices_str)
++ ret = g_strsplit (global_devices_str, ",", -1);
++ else
++ ret = NULL;
++
++ g_mutex_unlock (&global_config_lock);
++
++ return ret;
++}
++
+ /**
+ * bd_lvm_cache_get_default_md_size:
+ * @cache_size: size of the cache to determine MD size for
+diff --git a/src/plugins/lvm.c b/src/plugins/lvm.c
+index 26af0d19..42ee0f90 100644
+--- a/src/plugins/lvm.c
++++ b/src/plugins/lvm.c
+@@ -34,6 +34,8 @@
+ static GMutex global_config_lock;
+ static gchar *global_config_str = NULL;
+
++static gchar *global_devices_str = NULL;
++
+ /**
+ * SECTION: lvm
+ * @short_description: plugin for operations with LVM
+@@ -212,10 +214,13 @@ static GMutex deps_check_lock;
+
+ #define DEPS_LVM 0
+ #define DEPS_LVM_MASK (1 << DEPS_LVM)
+-#define DEPS_LAST 1
++#define DEPS_LVMDEVICES 1
++#define DEPS_LVMDEVICES_MASK (1 << DEPS_LVMDEVICES)
++#define DEPS_LAST 2
+
+ static const UtilDep deps[DEPS_LAST] = {
+ {"lvm", LVM_MIN_VERSION, "version", "LVM version:\\s+([\\d\\.]+)"},
++ {"lvmdevices", NULL, NULL, NULL},
+ };
+
+ #define FEATURES_VDO 0
+@@ -327,6 +332,8 @@ gboolean bd_lvm_is_tech_avail (BDLVMTech tech, guint64 mode, GError **error) {
+ case BD_LVM_TECH_VDO:
+ return check_features (&avail_features, FEATURES_VDO_MASK, features, FEATURES_LAST, &deps_check_lock, error) &&
+ check_module_deps (&avail_module_deps, MODULE_DEPS_VDO_MASK, module_deps, MODULE_DEPS_LAST, &deps_check_lock, error);
++ case BD_LVM_TECH_DEVICES:
++ return check_deps (&avail_deps, DEPS_LVMDEVICES_MASK, deps, DEPS_LAST, &deps_check_lock, error);
+ default:
+ /* everything is supported by this implementation of the plugin */
+ return check_deps (&avail_deps, DEPS_LVM_MASK, deps, DEPS_LAST, &deps_check_lock, error);
+@@ -337,6 +344,8 @@ static gboolean call_lvm_and_report_error (const gchar **args, const BDExtraArg
+ gboolean success = FALSE;
+ guint i = 0;
+ guint args_length = g_strv_length ((gchar **) args);
++ g_autofree gchar *config_arg = NULL;
++ g_autofree gchar *devices_arg = NULL;
+
+ if (!check_deps (&avail_deps, DEPS_LVM_MASK, deps, DEPS_LAST, &deps_check_lock, error))
+ return FALSE;
+@@ -345,20 +354,26 @@ static gboolean call_lvm_and_report_error (const gchar **args, const BDExtraArg
+ if (lock_config)
+ g_mutex_lock (&global_config_lock);
+
+- /* allocate enough space for the args plus "lvm", "--config" and NULL */
+- const gchar **argv = g_new0 (const gchar*, args_length + 3);
++ /* allocate enough space for the args plus "lvm", "--config", "--devices" and NULL */
++ const gchar **argv = g_new0 (const gchar*, args_length + 4);
+
+ /* construct argv from args with "lvm" prepended */
+ argv[0] = "lvm";
+ for (i=0; i < args_length; i++)
+ argv[i+1] = args[i];
+- argv[args_length + 1] = global_config_str ? g_strdup_printf("--config=%s", global_config_str) : NULL;
+- argv[args_length + 2] = NULL;
++ if (global_config_str) {
++ config_arg = g_strdup_printf("--config=%s", global_config_str);
++ argv[++args_length] = config_arg;
++ }
++ if (global_devices_str) {
++ devices_arg = g_strdup_printf("--devices=%s", global_devices_str);
++ argv[++args_length] = devices_arg;
++ }
++ argv[++args_length] = NULL;
+
+ success = bd_utils_exec_and_report_error (argv, extra, error);
+ if (lock_config)
+ g_mutex_unlock (&global_config_lock);
+- g_free ((gchar *) argv[args_length + 1]);
+ g_free (argv);
+
+ return success;
+@@ -368,6 +383,8 @@ static gboolean call_lvm_and_capture_output (const gchar **args, const BDExtraAr
+ gboolean success = FALSE;
+ guint i = 0;
+ guint args_length = g_strv_length ((gchar **) args);
++ g_autofree gchar *config_arg = NULL;
++ g_autofree gchar *devices_arg = NULL;
+
+ if (!check_deps (&avail_deps, DEPS_LVM_MASK, deps, DEPS_LAST, &deps_check_lock, error))
+ return FALSE;
+@@ -375,19 +392,25 @@ static gboolean call_lvm_and_capture_output (const gchar **args, const BDExtraAr
+ /* don't allow global config string changes during the run */
+ g_mutex_lock (&global_config_lock);
+
+- /* allocate enough space for the args plus "lvm", "--config" and NULL */
+- const gchar **argv = g_new0 (const gchar*, args_length + 3);
++ /* allocate enough space for the args plus "lvm", "--config", "--devices" and NULL */
++ const gchar **argv = g_new0 (const gchar*, args_length + 4);
+
+ /* construct argv from args with "lvm" prepended */
+ argv[0] = "lvm";
+ for (i=0; i < args_length; i++)
+ argv[i+1] = args[i];
+- argv[args_length + 1] = global_config_str ? g_strdup_printf("--config=%s", global_config_str) : NULL;
+- argv[args_length + 2] = NULL;
++ if (global_config_str) {
++ config_arg = g_strdup_printf("--config=%s", global_config_str);
++ argv[++args_length] = config_arg;
++ }
++ if (global_devices_str) {
++ devices_arg = g_strdup_printf("--devices=%s", global_devices_str);
++ argv[++args_length] = devices_arg;
++ }
++ argv[++args_length] = NULL;
+
+ success = bd_utils_exec_and_capture_output (argv, extra, output, error);
+ g_mutex_unlock (&global_config_lock);
+- g_free ((gchar *) argv[args_length + 1]);
+ g_free (argv);
+
+ return success;
+@@ -2033,6 +2056,58 @@ gchar* bd_lvm_get_global_config (GError **error UNUSED) {
+ return ret;
+ }
+
++/**
++ * bd_lvm_set_devices_filter:
++ * @devices: (allow-none) (array zero-terminated=1): list of devices for lvm commands to work on
++ * @error: (out): place to store error (if any)
++ *
++ * Returns: whether the devices filter was successfully set or not
++ *
++ * Tech category: %BD_LVM_TECH_DEVICES no mode (it is ignored)
++ */
++gboolean bd_lvm_set_devices_filter (const gchar **devices, GError **error) {
++ if (!bd_lvm_is_tech_avail (BD_LVM_TECH_DEVICES, 0, error))
++ return FALSE;
++
++ g_mutex_lock (&global_config_lock);
++
++ /* first free the old value */
++ g_free (global_devices_str);
++
++ /* now store the new one */
++ if (!devices || !(*devices))
++ global_devices_str = NULL;
++ else
++ global_devices_str = g_strjoinv (",", (gchar **) devices);
++
++ g_mutex_unlock (&global_config_lock);
++ return TRUE;
++}
++
++/**
++ * bd_lvm_get_devices_filter:
++ * @error: (out): place to store error (if any)
++ *
++ * Returns: (transfer full) (array zero-terminated=1): a copy of a string representation of
++ * the currently set LVM devices filter
++ *
++ * Tech category: %BD_LVM_TECH_DEVICES no mode (it is ignored)
++ */
++gchar** bd_lvm_get_devices_filter (GError **error UNUSED) {
++ gchar **ret = NULL;
++
++ g_mutex_lock (&global_config_lock);
++
++ if (global_devices_str)
++ ret = g_strsplit (global_devices_str, ",", -1);
++ else
++ ret = NULL;
++
++ g_mutex_unlock (&global_config_lock);
++
++ return ret;
++}
++
+ /**
+ * bd_lvm_cache_get_default_md_size:
+ * @cache_size: size of the cache to determine MD size for
+diff --git a/src/plugins/lvm.h b/src/plugins/lvm.h
+index 2162d769..8063693f 100644
+--- a/src/plugins/lvm.h
++++ b/src/plugins/lvm.h
+@@ -216,6 +216,7 @@ typedef enum {
+ BD_LVM_TECH_CACHE_CALCS,
+ BD_LVM_TECH_GLOB_CONF,
+ BD_LVM_TECH_VDO,
++ BD_LVM_TECH_DEVICES,
+ } BDLVMTech;
+
+ typedef enum {
+@@ -289,6 +290,9 @@ gboolean bd_lvm_thsnapshotcreate (const gchar *vg_name, const gchar *origin_name
+ gboolean bd_lvm_set_global_config (const gchar *new_config, GError **error);
+ gchar* bd_lvm_get_global_config (GError **error);
+
++gboolean bd_lvm_set_devices_filter (const gchar **devices, GError **error);
++gchar** bd_lvm_get_devices_filter (GError **error);
++
+ guint64 bd_lvm_cache_get_default_md_size (guint64 cache_size, GError **error);
+ const gchar* bd_lvm_cache_get_mode_str (BDLVMCacheMode mode, GError **error);
+ BDLVMCacheMode bd_lvm_cache_get_mode_from_str (const gchar *mode_str, GError **error);
+diff --git a/tests/library_test.py b/tests/library_test.py
+index 08e44fdc..efd17bd2 100644
+--- a/tests/library_test.py
++++ b/tests/library_test.py
+@@ -13,18 +13,178 @@ class LibraryOpsTestCase(unittest.TestCase):
+ # all plugins except for 'btrfs', 'fs' and 'mpath' -- these don't have all
+ # 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",
++ "kbd", "loop",
+ "mdraid", "part", "swap"))
+
++ @classmethod
++ def setUpClass(cls):
++ if not BlockDev.is_initialized():
++ BlockDev.init(cls.requested_plugins, None)
++ else:
++ BlockDev.reinit(cls.requested_plugins, True, None)
++
++ @classmethod
++ def tearDownClass(cls):
++ BlockDev.switch_init_checks(True)
++
++ def my_log_func(self, level, msg):
++ # not much to verify here
++ self.assertTrue(isinstance(level, int))
++ self.assertTrue(isinstance(msg, str))
++
++ self.log += msg + "\n"
++
++ @tag_test(TestTags.CORE)
++ def test_logging_setup(self):
++ """Verify that setting up logging works as expected"""
++
++ self.assertTrue(BlockDev.reinit(self.requested_plugins, False, self.my_log_func))
++
++ succ = BlockDev.utils_exec_and_report_error(["true"])
++ self.assertTrue(succ)
++
++ # reinit with no logging function should change nothing about logging
++ self.assertTrue(BlockDev.reinit(self.requested_plugins, False, None))
++
++ succ, out = BlockDev.utils_exec_and_capture_output(["echo", "hi"])
++ self.assertTrue(succ)
++ self.assertEqual(out, "hi\n")
++
++ match = re.search(r'Running \[(\d+)\] true', self.log)
++ self.assertIsNot(match, None)
++ task_id1 = match.group(1)
++ match = re.search(r'Running \[(\d+)\] echo hi', self.log)
++ self.assertIsNot(match, None)
++ task_id2 = match.group(1)
++
++ self.assertIn("...done [%s] (exit code: 0)" % task_id1, self.log)
++ self.assertIn("stdout[%s]:" % task_id1, self.log)
++ self.assertIn("stderr[%s]:" % task_id1, self.log)
++
++ self.assertIn("stdout[%s]: hi" % task_id2, self.log)
++ self.assertIn("stderr[%s]:" % task_id2, self.log)
++ self.assertIn("...done [%s] (exit code: 0)" % task_id2, self.log)
++
++ @tag_test(TestTags.CORE)
++ def test_require_plugins(self):
++ """Verify that loading only required plugins works as expected"""
++
++ ps = BlockDev.PluginSpec()
++ ps.name = BlockDev.Plugin.SWAP
++ ps.so_name = ""
++ self.assertTrue(BlockDev.reinit([ps], True, None))
++ self.assertEqual(BlockDev.get_available_plugin_names(), ["swap"])
++ self.assertTrue(BlockDev.reinit(self.requested_plugins, True, None))
++
++ @tag_test(TestTags.CORE)
++ def test_not_implemented(self):
++ """Verify that unloaded/unimplemented functions report errors"""
++
++ # should be loaded and working
++ self.assertTrue(BlockDev.md_canonicalize_uuid("3386ff85:f5012621:4a435f06:1eb47236"))
++
++ ps = BlockDev.PluginSpec()
++ ps.name = BlockDev.Plugin.SWAP
++ ps.so_name = ""
++ self.assertTrue(BlockDev.reinit([ps], True, None))
++ self.assertEqual(BlockDev.get_available_plugin_names(), ["swap"])
++
++ # no longer loaded
++ with self.assertRaises(GLib.GError):
++ BlockDev.md_canonicalize_uuid("3386ff85:f5012621:4a435f06:1eb47236")
++
++ self.assertTrue(BlockDev.reinit(self.requested_plugins, True, None))
++
++ # loaded again
++ self.assertTrue(BlockDev.md_canonicalize_uuid("3386ff85:f5012621:4a435f06:1eb47236"))
++
++ def test_ensure_init(self):
++ """Verify that ensure_init just returns when already initialized"""
++
++ # the library is already initialized, ensure_init() shonuld do nothing
++ avail_plugs = BlockDev.get_available_plugin_names()
++ self.assertTrue(BlockDev.ensure_init(self.requested_plugins, None))
++ self.assertEqual(avail_plugs, BlockDev.get_available_plugin_names())
++
++ # reinit with a subset of plugins
++ plugins = BlockDev.plugin_specs_from_names(["swap", "part"])
++ self.assertTrue(BlockDev.reinit(plugins, True, None))
++ self.assertEqual(set(BlockDev.get_available_plugin_names()), set(["swap", "part"]))
++
++ # ensure_init with the same subset -> nothing should change
++ self.assertTrue(BlockDev.ensure_init(plugins, None))
++ self.assertEqual(set(BlockDev.get_available_plugin_names()), set(["swap", "part"]))
++
++ # ensure_init with more plugins -> extra plugins should be loaded
++ plugins = BlockDev.plugin_specs_from_names(["swap", "part", "crypto"])
++ self.assertTrue(BlockDev.ensure_init(plugins, None))
++ self.assertEqual(set(BlockDev.get_available_plugin_names()), set(["swap", "part", "crypto"]))
++
++ # reinit to unload all plugins
++ self.assertTrue(BlockDev.reinit([], True, None))
++ self.assertEqual(BlockDev.get_available_plugin_names(), [])
++
++ # ensure_init to load all plugins back
++ self.assertTrue(BlockDev.ensure_init(self.requested_plugins, None))
++ self.assertGreaterEqual(len(BlockDev.get_available_plugin_names()), 7)
++
++ def test_try_reinit(self):
++ """Verify that try_reinit() works as expected"""
++
++ # try reinitializing with only some utilities being available and thus
++ # only some plugins able to load
++ with fake_path("tests/lib_missing_utils", keep_utils=["swapon", "swapoff", "mkswap", "swaplabel"]):
++ succ, loaded = BlockDev.try_reinit(self.requested_plugins, True, None)
++ self.assertFalse(succ)
++ for plug_name in ("swap", "crypto"):
++ self.assertIn(plug_name, loaded)
++
++ # reset back to all plugins
++ self.assertTrue(BlockDev.reinit(self.requested_plugins, True, None))
++
++ # now the same with a subset of plugins requested
++ plugins = BlockDev.plugin_specs_from_names(["swap", "crypto"])
++ with fake_path("tests/lib_missing_utils", keep_utils=["swapon", "swapoff", "mkswap", "swaplabel"]):
++ succ, loaded = BlockDev.try_reinit(plugins, True, None)
++ self.assertTrue(succ)
++ self.assertEqual(set(loaded), set(["swap", "crypto"]))
++
++ def test_non_en_init(self):
++ """Verify that the library initializes with lang different from en_US"""
++
++ orig_lang = os.environ.get("LANG")
++ os.environ["LANG"] = "cs.CZ_UTF-8"
++ self.assertTrue(BlockDev.reinit(self.requested_plugins, True, None))
++ if orig_lang:
++ os.environ["LANG"] = orig_lang
++ else:
++ del os.environ["LANG"]
++ self.assertTrue(BlockDev.reinit(self.requested_plugins, True, None))
++
++
++class PluginsTestCase(unittest.TestCase):
++ # only LVM plugin for this test
++ requested_plugins = BlockDev.plugin_specs_from_names(("lvm",))
++
+ orig_config_dir = ""
+
+ @classmethod
+ def setUpClass(cls):
++ BlockDev.switch_init_checks(False)
+ if not BlockDev.is_initialized():
+ BlockDev.init(cls.requested_plugins, None)
+ else:
+ BlockDev.reinit(cls.requested_plugins, True, None)
+
++ try:
++ cls.devices_avail = BlockDev.lvm_is_tech_avail(BlockDev.LVMTech.DEVICES, 0)
++ except:
++ cls.devices_avail = False
++
++ @classmethod
++ def tearDownClass(cls):
++ BlockDev.switch_init_checks(True)
++
+ def setUp(self):
+ self.orig_config_dir = os.environ.get("LIBBLOCKDEV_CONFIG_DIR", "")
+ self.addCleanup(self._clean_up)
+@@ -185,6 +345,12 @@ class LibraryOpsTestCase(unittest.TestCase):
+ def test_plugin_fallback(self):
+ """Verify that fallback when loading plugins works as expected"""
+
++ if not self.devices_avail:
++ self.skipTest("skipping plugin fallback test: missing some LVM dependencies")
++
++ BlockDev.switch_init_checks(True)
++ self.addCleanup(BlockDev.switch_init_checks, False)
++
+ # library should be successfully initialized
+ self.assertTrue(BlockDev.is_initialized())
+
+@@ -206,7 +372,7 @@ class LibraryOpsTestCase(unittest.TestCase):
+
+ # now reinit the library with the config preferring the new build
+ orig_conf_dir = os.environ.get("LIBBLOCKDEV_CONFIG_DIR")
+- os.environ["LIBBLOCKDEV_CONFIG_DIR"] = "tests/plugin_prio_conf.d"
++ os.environ["LIBBLOCKDEV_CONFIG_DIR"] = "tests/test_configs/plugin_prio_conf.d"
+ self.assertTrue(BlockDev.reinit(self.requested_plugins, True, None))
+
+ # the original plugin should be loaded because the new one should fail
+@@ -243,139 +409,9 @@ class LibraryOpsTestCase(unittest.TestCase):
+
+ self.assertEqual(BlockDev.lvm_get_max_lv_size(), orig_max_size)
+
+- def my_log_func(self, level, msg):
+- # not much to verify here
+- self.assertTrue(isinstance(level, int))
+- self.assertTrue(isinstance(msg, str))
+-
+- self.log += msg + "\n"
+-
+- @tag_test(TestTags.CORE)
+- def test_logging_setup(self):
+- """Verify that setting up logging works as expected"""
+-
+- self.assertTrue(BlockDev.reinit(self.requested_plugins, False, self.my_log_func))
+-
+- succ = BlockDev.utils_exec_and_report_error(["true"])
+- self.assertTrue(succ)
+-
+- # reinit with no logging function should change nothing about logging
+- self.assertTrue(BlockDev.reinit(self.requested_plugins, False, None))
+-
+- succ, out = BlockDev.utils_exec_and_capture_output(["echo", "hi"])
+- self.assertTrue(succ)
+- self.assertEqual(out, "hi\n")
+-
+- match = re.search(r'Running \[(\d+)\] true', self.log)
+- self.assertIsNot(match, None)
+- task_id1 = match.group(1)
+- match = re.search(r'Running \[(\d+)\] echo hi', self.log)
+- self.assertIsNot(match, None)
+- task_id2 = match.group(1)
+-
+- self.assertIn("...done [%s] (exit code: 0)" % task_id1, self.log)
+- self.assertIn("stdout[%s]:" % task_id1, self.log)
+- self.assertIn("stderr[%s]:" % task_id1, self.log)
+-
+- self.assertIn("stdout[%s]: hi" % task_id2, self.log)
+- self.assertIn("stderr[%s]:" % task_id2, self.log)
+- self.assertIn("...done [%s] (exit code: 0)" % task_id2, self.log)
+-
+- @tag_test(TestTags.CORE)
+- def test_require_plugins(self):
+- """Verify that loading only required plugins works as expected"""
+-
+- ps = BlockDev.PluginSpec()
+- ps.name = BlockDev.Plugin.SWAP
+- ps.so_name = ""
+- self.assertTrue(BlockDev.reinit([ps], True, None))
+- self.assertEqual(BlockDev.get_available_plugin_names(), ["swap"])
+- self.assertTrue(BlockDev.reinit(self.requested_plugins, True, None))
+-
+- @tag_test(TestTags.CORE)
+- def test_not_implemented(self):
+- """Verify that unloaded/unimplemented functions report errors"""
+-
+- # should be loaded and working
+- self.assertTrue(BlockDev.lvm_get_max_lv_size() > 0)
+
+- ps = BlockDev.PluginSpec()
+- ps.name = BlockDev.Plugin.SWAP
+- ps.so_name = ""
+- self.assertTrue(BlockDev.reinit([ps], True, None))
+- self.assertEqual(BlockDev.get_available_plugin_names(), ["swap"])
+-
+- # no longer loaded
+- with self.assertRaises(GLib.GError):
+- BlockDev.lvm_get_max_lv_size()
+-
+- self.assertTrue(BlockDev.reinit(self.requested_plugins, True, None))
+-
+- # loaded again
+- self.assertTrue(BlockDev.lvm_get_max_lv_size() > 0)
+-
+- def test_ensure_init(self):
+- """Verify that ensure_init just returns when already initialized"""
+-
+- # the library is already initialized, ensure_init() shonuld do nothing
+- avail_plugs = BlockDev.get_available_plugin_names()
+- self.assertTrue(BlockDev.ensure_init(self.requested_plugins, None))
+- self.assertEqual(avail_plugs, BlockDev.get_available_plugin_names())
+-
+- # reinit with a subset of plugins
+- plugins = BlockDev.plugin_specs_from_names(["swap", "lvm"])
+- self.assertTrue(BlockDev.reinit(plugins, True, None))
+- self.assertEqual(set(BlockDev.get_available_plugin_names()), set(["swap", "lvm"]))
+-
+- # ensure_init with the same subset -> nothing should change
+- self.assertTrue(BlockDev.ensure_init(plugins, None))
+- self.assertEqual(set(BlockDev.get_available_plugin_names()), set(["swap", "lvm"]))
+-
+- # ensure_init with more plugins -> extra plugins should be loaded
+- plugins = BlockDev.plugin_specs_from_names(["swap", "lvm", "crypto"])
+- self.assertTrue(BlockDev.ensure_init(plugins, None))
+- self.assertEqual(set(BlockDev.get_available_plugin_names()), set(["swap", "lvm", "crypto"]))
+-
+- # reinit to unload all plugins
+- self.assertTrue(BlockDev.reinit([], True, None))
+- self.assertEqual(BlockDev.get_available_plugin_names(), [])
+-
+- # ensure_init to load all plugins back
+- self.assertTrue(BlockDev.ensure_init(self.requested_plugins, None))
+- self.assertGreaterEqual(len(BlockDev.get_available_plugin_names()), 8)
+-
+- def test_try_reinit(self):
+- """Verify that try_reinit() works as expected"""
+-
+- # try reinitializing with only some utilities being available and thus
+- # only some plugins able to load
+- with fake_path("tests/lib_missing_utils", keep_utils=["swapon", "swapoff", "mkswap", "lvm", "swaplabel"]):
+- succ, loaded = BlockDev.try_reinit(self.requested_plugins, True, None)
+- self.assertFalse(succ)
+- for plug_name in ("swap", "lvm", "crypto"):
+- self.assertIn(plug_name, loaded)
+-
+- # reset back to all plugins
+- self.assertTrue(BlockDev.reinit(self.requested_plugins, True, None))
+-
+- # now the same with a subset of plugins requested
+- plugins = BlockDev.plugin_specs_from_names(["lvm", "swap", "crypto"])
+- with fake_path("tests/lib_missing_utils", keep_utils=["swapon", "swapoff", "mkswap", "lvm","swaplabel"]):
+- succ, loaded = BlockDev.try_reinit(plugins, True, None)
+- self.assertTrue(succ)
+- self.assertEqual(set(loaded), set(["swap", "lvm", "crypto"]))
+-
+- def test_non_en_init(self):
+- """Verify that the library initializes with lang different from en_US"""
+-
+- orig_lang = os.environ.get("LANG")
+- os.environ["LANG"] = "cs.CZ_UTF-8"
+- self.assertTrue(BlockDev.reinit(self.requested_plugins, True, None))
+- if orig_lang:
+- os.environ["LANG"] = orig_lang
+- else:
+- del os.environ["LANG"]
+- self.assertTrue(BlockDev.reinit(self.requested_plugins, True, None))
++class DepChecksTestCase(unittest.TestCase):
++ requested_plugins = BlockDev.plugin_specs_from_names(( "swap",))
+
+ def test_dep_checks_disabled(self):
+ """Verify that disabling runtime dep checks works"""
+diff --git a/tests/lvm_dbus_tests.py b/tests/lvm_dbus_tests.py
+index 3fb7946a..ae26c6d2 100644
+--- a/tests/lvm_dbus_tests.py
++++ b/tests/lvm_dbus_tests.py
+@@ -50,6 +50,11 @@ class LVMTestCase(unittest.TestCase):
+ else:
+ BlockDev.reinit([cls.ps, cls.ps2], True, None)
+
++ try:
++ cls.devices_avail = BlockDev.lvm_is_tech_avail(BlockDev.LVMTech.DEVICES, 0)
++ except:
++ cls.devices_avail = False
++
+ @classmethod
+ def _get_lvm_version(cls):
+ _ret, out, _err = run_command("lvm version")
+@@ -61,8 +66,7 @@ class LVMTestCase(unittest.TestCase):
+ @unittest.skipUnless(lvm_dbus_running, "LVM DBus not running")
+ class LvmNoDevTestCase(LVMTestCase):
+
+- def __init__(self, *args, **kwargs):
+- super(LvmNoDevTestCase, self).__init__(*args, **kwargs)
++ def setUp(self):
+ self._log = ""
+
+ @tag_test(TestTags.NOSTORAGE)
+@@ -250,6 +254,45 @@ class LvmNoDevTestCase(LVMTestCase):
+ succ = BlockDev.lvm_set_global_config(None)
+ self.assertTrue(succ)
+
++ @tag_test(TestTags.NOSTORAGE)
++ def test_get_set_global_devices_filter(self):
++ """Verify that getting and setting LVM devices filter works as expected"""
++ if not self.devices_avail:
++ self.skipTest("skipping LVM devices filter test: not supported")
++
++ # setup logging
++ self.assertTrue(BlockDev.reinit([self.ps], False, self._store_log))
++
++ # no global config set initially
++ self.assertListEqual(BlockDev.lvm_get_devices_filter(), [])
++
++ # set and try to get back
++ succ = BlockDev.lvm_set_devices_filter(["/dev/sda"])
++ self.assertTrue(succ)
++ self.assertListEqual(BlockDev.lvm_get_devices_filter(), ["/dev/sda"])
++
++ # reset and try to get back
++ succ = BlockDev.lvm_set_devices_filter(None)
++ self.assertTrue(succ)
++ self.assertListEqual(BlockDev.lvm_get_devices_filter(), [])
++
++ # set twice and try to get back twice
++ succ = BlockDev.lvm_set_devices_filter(["/dev/sda"])
++ self.assertTrue(succ)
++ succ = BlockDev.lvm_set_devices_filter(["/dev/sdb"])
++ self.assertTrue(succ)
++ self.assertEqual(BlockDev.lvm_get_devices_filter(), ["/dev/sdb"])
++
++ # set something sane and check it's really used
++ succ = BlockDev.lvm_set_devices_filter(["/dev/sdb", "/dev/sdc"])
++ BlockDev.lvm_pvscan()
++ self.assertIn("'--devices'", self._log)
++ self.assertIn("'/dev/sdb,/dev/sdc'", self._log)
++
++ # reset back to default
++ succ = BlockDev.lvm_set_devices_filter(None)
++ self.assertTrue(succ)
++
+ @tag_test(TestTags.NOSTORAGE)
+ def test_cache_get_default_md_size(self):
+ """Verify that default cache metadata size is calculated properly"""
+diff --git a/tests/lvm_test.py b/tests/lvm_test.py
+index 7be8f1ab..11d8c94e 100644
+--- a/tests/lvm_test.py
++++ b/tests/lvm_test.py
+@@ -39,10 +39,17 @@ class LVMTestCase(unittest.TestCase):
+ ps.so_name = "libbd_lvm.so.2"
+ cls.requested_plugins = [ps]
+
++ BlockDev.switch_init_checks(False)
+ if not BlockDev.is_initialized():
+ BlockDev.init(cls.requested_plugins, None)
+ else:
+ BlockDev.reinit(cls.requested_plugins, True, None)
++ BlockDev.switch_init_checks(True)
++
++ try:
++ cls.devices_avail = BlockDev.lvm_is_tech_avail(BlockDev.LVMTech.DEVICES, 0)
++ except:
++ cls.devices_avail = False
+
+ @classmethod
+ def _get_lvm_version(cls):
+@@ -56,6 +63,8 @@ class LVMTestCase(unittest.TestCase):
+ class LvmNoDevTestCase(LVMTestCase):
+ def __init__(self, *args, **kwargs):
+ super(LvmNoDevTestCase, self).__init__(*args, **kwargs)
++
++ def setUp(self):
+ self._log = ""
+
+ @tag_test(TestTags.NOSTORAGE)
+@@ -236,6 +245,44 @@ class LvmNoDevTestCase(LVMTestCase):
+ succ = BlockDev.lvm_set_global_config(None)
+ self.assertTrue(succ)
+
++ @tag_test(TestTags.NOSTORAGE)
++ def test_get_set_global_devices_filter(self):
++ """Verify that getting and setting LVM devices filter works as expected"""
++ if not self.devices_avail:
++ self.skipTest("skipping LVM devices filter test: not supported")
++
++ # setup logging
++ self.assertTrue(BlockDev.reinit(self.requested_plugins, False, self._store_log))
++
++ # no global config set initially
++ self.assertListEqual(BlockDev.lvm_get_devices_filter(), [])
++
++ # set and try to get back
++ succ = BlockDev.lvm_set_devices_filter(["/dev/sda"])
++ self.assertTrue(succ)
++ self.assertListEqual(BlockDev.lvm_get_devices_filter(), ["/dev/sda"])
++
++ # reset and try to get back
++ succ = BlockDev.lvm_set_devices_filter(None)
++ self.assertTrue(succ)
++ self.assertListEqual(BlockDev.lvm_get_devices_filter(), [])
++
++ # set twice and try to get back twice
++ succ = BlockDev.lvm_set_devices_filter(["/dev/sda"])
++ self.assertTrue(succ)
++ succ = BlockDev.lvm_set_devices_filter(["/dev/sdb"])
++ self.assertTrue(succ)
++ self.assertEqual(BlockDev.lvm_get_devices_filter(), ["/dev/sdb"])
++
++ # set something sane and check it's really used
++ succ = BlockDev.lvm_set_devices_filter(["/dev/sdb", "/dev/sdc"])
++ BlockDev.lvm_lvs(None)
++ self.assertIn("--devices=/dev/sdb,/dev/sdc", self._log)
++
++ # reset back to default
++ succ = BlockDev.lvm_set_devices_filter(None)
++ self.assertTrue(succ)
++
+ @tag_test(TestTags.NOSTORAGE)
+ def test_cache_get_default_md_size(self):
+ """Verify that default cache metadata size is calculated properly"""
+@@ -1406,6 +1453,9 @@ class LvmPVVGcachedThpoolstatsTestCase(LvmPVVGLVTestCase):
+
+ class LVMUnloadTest(LVMTestCase):
+ def setUp(self):
++ if not self.devices_avail:
++ self.skipTest("skipping LVM unload test: missing some LVM dependencies")
++
+ # make sure the library is initialized with all plugins loaded for other
+ # tests
+ self.addCleanup(BlockDev.reinit, self.requested_plugins, True, None)
+diff --git a/tests/overrides_test.py b/tests/overrides_test.py
+index 8e7f5a5a..d3faf3cf 100644
+--- a/tests/overrides_test.py
++++ b/tests/overrides_test.py
+@@ -15,10 +15,12 @@ class OverridesTest(unittest.TestCase):
+
+ @classmethod
+ def setUpClass(cls):
++ BlockDev.switch_init_checks(False)
+ if not BlockDev.is_initialized():
+ BlockDev.init(cls.requested_plugins, None)
+ else:
+ BlockDev.reinit(cls.requested_plugins, True, None)
++ BlockDev.switch_init_checks(True)
+
+ class OverridesTestCase(OverridesTest):
+ @tag_test(TestTags.NOSTORAGE, TestTags.CORE)
+@@ -65,7 +67,20 @@ class OverridesTestCase(OverridesTest):
+ self.assertEqual(BlockDev.lvm_get_thpool_padding(11 * 1024**2),
+ expected_padding)
+
+-class OverridesUnloadTestCase(OverridesTest):
++class OverridesUnloadTestCase(unittest.TestCase):
++ # all plugins except for 'btrfs', 'fs' and 'mpath' -- these don't have all
++ # 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"))
++
++ @classmethod
++ def setUpClass(cls):
++ if not BlockDev.is_initialized():
++ BlockDev.init(cls.requested_plugins, None)
++ else:
++ BlockDev.reinit(cls.requested_plugins, True, None)
++
+ def tearDown(self):
+ # make sure the library is initialized with all plugins loaded for other
+ # tests
+@@ -80,7 +95,7 @@ class OverridesUnloadTestCase(OverridesTest):
+
+ # no longer loaded
+ with self.assertRaises(BlockDev.BlockDevNotImplementedError):
+- BlockDev.lvm.get_max_lv_size()
++ BlockDev.md.canonicalize_uuid("3386ff85:f5012621:4a435f06:1eb47236")
+
+ # load the plugins back
+ self.assertTrue(BlockDev.reinit(self.requested_plugins, True, None))
+@@ -92,9 +107,9 @@ class OverridesUnloadTestCase(OverridesTest):
+
+ # the exception should be properly inherited from two classes
+ with self.assertRaises(NotImplementedError):
+- BlockDev.lvm.get_max_lv_size()
++ BlockDev.md.canonicalize_uuid("3386ff85:f5012621:4a435f06:1eb47236")
+ with self.assertRaises(BlockDev.BlockDevError):
+- BlockDev.lvm.get_max_lv_size()
++ BlockDev.md.canonicalize_uuid("3386ff85:f5012621:4a435f06:1eb47236")
+
+ # load the plugins back
+ self.assertTrue(BlockDev.reinit(self.requested_plugins, True, None))
+--
+2.37.3
+
+
+From 707de091b8848b95cc78faa4299119844aab4172 Mon Sep 17 00:00:00 2001
+From: Vojtech Trefny <vtrefny@redhat.com>
+Date: Tue, 13 Jul 2021 13:27:32 +0200
+Subject: [PATCH 2/7] lvm: Add functions for managing LVM devices file
+
+Currently covers only --adddev and --deldev from the lvmdevices
+command.
+---
+ src/lib/plugin_apis/lvm.api | 26 +++++++++++++++
+ src/plugins/lvm-dbus.c | 52 +++++++++++++++++++++++++++++
+ src/plugins/lvm.c | 52 +++++++++++++++++++++++++++++
+ src/plugins/lvm.h | 3 ++
+ src/python/gi/overrides/BlockDev.py | 15 +++++++++
+ tests/lvm_dbus_tests.py | 37 +++++++++++++++++++-
+ tests/lvm_test.py | 37 +++++++++++++++++++-
+ 7 files changed, 220 insertions(+), 2 deletions(-)
+
+diff --git a/src/lib/plugin_apis/lvm.api b/src/lib/plugin_apis/lvm.api
+index 23f68b81..b869afcc 100644
+--- a/src/lib/plugin_apis/lvm.api
++++ b/src/lib/plugin_apis/lvm.api
+@@ -1685,4 +1685,30 @@ GHashTable* bd_lvm_vdo_get_stats_full (const gchar *vg_name, const gchar *pool_n
+ */
+ BDLVMVDOStats* bd_lvm_vdo_get_stats (const gchar *vg_name, const gchar *pool_name, GError **error);
+
++/**
++ * bd_lvm_devices_add:
++ * @device: device (PV) to add to the devices file
++ * @devices_file: (allow-none): LVM devices file or %NULL for default
++ * @extra: (allow-none) (array zero-terminated=1): extra options for the lvmdevices command
++ * @error: (out): place to store error (if any)
++ *
++ * Returns: whether the @device was successfully added to @devices_file or not
++ *
++ * Tech category: %BD_LVM_TECH_DEVICES no mode (it is ignored)
++ */
++gboolean bd_lvm_devices_add (const gchar *device, const gchar *devices_file, const BDExtraArg **extra, GError **error);
++
++/**
++ * bd_lvm_devices_delete:
++ * @device: device (PV) to delete from the devices file
++ * @devices_file: (allow-none): LVM devices file or %NULL for default
++ * @extra: (allow-none) (array zero-terminated=1): extra options for the lvmdevices command
++ * @error: (out): place to store error (if any)
++ *
++ * Returns: whether the @device was successfully removed from @devices_file or not
++ *
++ * Tech category: %BD_LVM_TECH_DEVICES no mode (it is ignored)
++ */
++gboolean bd_lvm_devices_delete (const gchar *device, const gchar *devices_file, const BDExtraArg **extra, GError **error);
++
+ #endif /* BD_LVM_API */
+diff --git a/src/plugins/lvm-dbus.c b/src/plugins/lvm-dbus.c
+index b47ed0ef..86ca28ca 100644
+--- a/src/plugins/lvm-dbus.c
++++ b/src/plugins/lvm-dbus.c
+@@ -3950,3 +3950,55 @@ BDLVMVDOStats* bd_lvm_vdo_get_stats (const gchar *vg_name, const gchar *pool_nam
+
+ return stats;
+ }
++
++/**
++ * bd_lvm_devices_add:
++ * @device: device (PV) to add to the devices file
++ * @devices_file: (allow-none): LVM devices file or %NULL for default
++ * @extra: (allow-none) (array zero-terminated=1): extra options for the lvmdevices command
++ * @error: (out): place to store error (if any)
++ *
++ * Returns: whether the @device was successfully added to @devices_file or not
++ *
++ * Tech category: %BD_LVM_TECH_DEVICES no mode (it is ignored)
++ */
++gboolean bd_lvm_devices_add (const gchar *device, const gchar *devices_file, const BDExtraArg **extra, GError **error) {
++ const gchar *args[5] = {"lvmdevices", "--adddev", device, NULL, NULL};
++ g_autofree gchar *devfile = NULL;
++
++ if (!bd_lvm_is_tech_avail (BD_LVM_TECH_DEVICES, 0, error))
++ return FALSE;
++
++ if (devices_file) {
++ devfile = g_strdup_printf ("--devicesfile=%s", devices_file);
++ args[3] = devfile;
++ }
++
++ return bd_utils_exec_and_report_error (args, extra, error);
++}
++
++/**
++ * bd_lvm_devices_delete:
++ * @device: device (PV) to delete from the devices file
++ * @devices_file: (allow-none): LVM devices file or %NULL for default
++ * @extra: (allow-none) (array zero-terminated=1): extra options for the lvmdevices command
++ * @error: (out): place to store error (if any)
++ *
++ * Returns: whether the @device was successfully removed from @devices_file or not
++ *
++ * Tech category: %BD_LVM_TECH_DEVICES no mode (it is ignored)
++ */
++gboolean bd_lvm_devices_delete (const gchar *device, const gchar *devices_file, const BDExtraArg **extra, GError **error) {
++ const gchar *args[5] = {"lvmdevices", "--deldev", device, NULL, NULL};
++ g_autofree gchar *devfile = NULL;
++
++ if (!bd_lvm_is_tech_avail (BD_LVM_TECH_DEVICES, 0, error))
++ return FALSE;
++
++ if (devices_file) {
++ devfile = g_strdup_printf ("--devicesfile=%s", devices_file);
++ args[3] = devfile;
++ }
++
++ return bd_utils_exec_and_report_error (args, extra, error);
++}
+diff --git a/src/plugins/lvm.c b/src/plugins/lvm.c
+index 42ee0f90..3bd8fae1 100644
+--- a/src/plugins/lvm.c
++++ b/src/plugins/lvm.c
+@@ -3250,3 +3250,55 @@ BDLVMVDOStats* bd_lvm_vdo_get_stats (const gchar *vg_name, const gchar *pool_nam
+
+ return stats;
+ }
++
++/**
++ * bd_lvm_devices_add:
++ * @device: device (PV) to add to the devices file
++ * @devices_file: (allow-none): LVM devices file or %NULL for default
++ * @extra: (allow-none) (array zero-terminated=1): extra options for the lvmdevices command
++ * @error: (out): place to store error (if any)
++ *
++ * Returns: whether the @device was successfully added to @devices_file or not
++ *
++ * Tech category: %BD_LVM_TECH_DEVICES no mode (it is ignored)
++ */
++gboolean bd_lvm_devices_add (const gchar *device, const gchar *devices_file, const BDExtraArg **extra, GError **error) {
++ const gchar *args[5] = {"lvmdevices", "--adddev", device, NULL, NULL};
++ g_autofree gchar *devfile = NULL;
++
++ if (!bd_lvm_is_tech_avail (BD_LVM_TECH_DEVICES, 0, error))
++ return FALSE;
++
++ if (devices_file) {
++ devfile = g_strdup_printf ("--devicesfile=%s", devices_file);
++ args[3] = devfile;
++ }
++
++ return bd_utils_exec_and_report_error (args, extra, error);
++}
++
++/**
++ * bd_lvm_devices_delete:
++ * @device: device (PV) to delete from the devices file
++ * @devices_file: (allow-none): LVM devices file or %NULL for default
++ * @extra: (allow-none) (array zero-terminated=1): extra options for the lvmdevices command
++ * @error: (out): place to store error (if any)
++ *
++ * Returns: whether the @device was successfully removed from @devices_file or not
++ *
++ * Tech category: %BD_LVM_TECH_DEVICES no mode (it is ignored)
++ */
++gboolean bd_lvm_devices_delete (const gchar *device, const gchar *devices_file, const BDExtraArg **extra, GError **error) {
++ const gchar *args[5] = {"lvmdevices", "--deldev", device, NULL, NULL};
++ g_autofree gchar *devfile = NULL;
++
++ if (!bd_lvm_is_tech_avail (BD_LVM_TECH_DEVICES, 0, error))
++ return FALSE;
++
++ if (devices_file) {
++ devfile = g_strdup_printf ("--devicesfile=%s", devices_file);
++ args[3] = devfile;
++ }
++
++ return bd_utils_exec_and_report_error (args, extra, error);
++}
+diff --git a/src/plugins/lvm.h b/src/plugins/lvm.h
+index 8063693f..5ca2a9d7 100644
+--- a/src/plugins/lvm.h
++++ b/src/plugins/lvm.h
+@@ -333,4 +333,7 @@ BDLVMVDOWritePolicy bd_lvm_get_vdo_write_policy_from_str (const gchar *policy_st
+ BDLVMVDOStats* bd_lvm_vdo_get_stats (const gchar *vg_name, const gchar *pool_name, GError **error);
+ GHashTable* bd_lvm_vdo_get_stats_full (const gchar *vg_name, const gchar *pool_name, GError **error);
+
++gboolean bd_lvm_devices_add (const gchar *device, const gchar *devices_file, const BDExtraArg **extra, GError **error);
++gboolean bd_lvm_devices_delete (const gchar *device, const gchar *devices_file, const BDExtraArg **extra, GError **error);
++
+ #endif /* BD_LVM */
+diff --git a/src/python/gi/overrides/BlockDev.py b/src/python/gi/overrides/BlockDev.py
+index ea059060..8574ab04 100644
+--- a/src/python/gi/overrides/BlockDev.py
++++ b/src/python/gi/overrides/BlockDev.py
+@@ -724,6 +724,21 @@ def lvm_vdo_pool_convert(vg_name, lv_name, pool_name, virtual_size, index_memory
+ return _lvm_vdo_pool_convert(vg_name, lv_name, pool_name, virtual_size, index_memory, compression, deduplication, write_policy, extra)
+ __all__.append("lvm_vdo_pool_convert")
+
++_lvm_devices_add = BlockDev.lvm_devices_add
++@override(BlockDev.lvm_devices_add)
++def lvm_devices_add(device, devices_file=None, extra=None, **kwargs):
++ extra = _get_extra(extra, kwargs)
++ return _lvm_devices_add(device, devices_file, extra)
++__all__.append("lvm_devices_add")
++
++_lvm_devices_delete = BlockDev.lvm_devices_delete
++@override(BlockDev.lvm_devices_delete)
++def lvm_devices_delete(device, devices_file=None, extra=None, **kwargs):
++ extra = _get_extra(extra, kwargs)
++ return _lvm_devices_delete(device, devices_file, extra)
++__all__.append("lvm_devices_delete")
++
++
+ _md_get_superblock_size = BlockDev.md_get_superblock_size
+ @override(BlockDev.md_get_superblock_size)
+ def md_get_superblock_size(size, version=None):
+diff --git a/tests/lvm_dbus_tests.py b/tests/lvm_dbus_tests.py
+index ae26c6d2..82e4761d 100644
+--- a/tests/lvm_dbus_tests.py
++++ b/tests/lvm_dbus_tests.py
+@@ -12,7 +12,7 @@ from contextlib import contextmanager
+ from distutils.version import LooseVersion
+ from itertools import chain
+
+-from utils import create_sparse_tempfile, create_lio_device, delete_lio_device, run_command, TestTags, tag_test
++from utils import create_sparse_tempfile, create_lio_device, delete_lio_device, run_command, TestTags, tag_test, read_file
+ from gi.repository import BlockDev, GLib
+
+ import dbus
+@@ -1785,3 +1785,38 @@ class LvmConfigTestPvremove(LvmPVonlyTestCase):
+ BlockDev.lvm_set_global_config("")
+ succ = BlockDev.lvm_pvremove(self.loop_dev)
+ self.assertTrue(succ)
++
++
++class LvmTestDevicesFile(LvmPVonlyTestCase):
++ devicefile = "bd_lvm_dbus_tests.devices"
++
++ @classmethod
++ def tearDownClass(cls):
++ shutil.rmtree("/etc/lvm/devices/" + cls.devicefile, ignore_errors=True)
++
++ super(LvmTestDevicesFile, cls).tearDownClass()
++
++ def test_devices_add_delete(self):
++ if not self.devices_avail:
++ self.skipTest("skipping LVM devices file test: not supported")
++
++ succ = BlockDev.lvm_pvcreate(self.loop_dev)
++ self.assertTrue(succ)
++
++ with self.assertRaises(GLib.GError):
++ BlockDev.lvm_devices_add("/non/existing/device", self.devicefile)
++
++ with self.assertRaises(GLib.GError):
++ BlockDev.lvm_devices_delete(self.loop_dev, self.devicefile)
++
++ succ = BlockDev.lvm_devices_add(self.loop_dev, self.devicefile)
++ self.assertTrue(succ)
++
++ dfile = read_file("/etc/lvm/devices/" + self.devicefile)
++ self.assertIn(self.loop_dev, dfile)
++
++ succ = BlockDev.lvm_devices_delete(self.loop_dev, self.devicefile)
++ self.assertTrue(succ)
++
++ dfile = read_file("/etc/lvm/devices/" + self.devicefile)
++ self.assertNotIn(self.loop_dev, dfile)
+diff --git a/tests/lvm_test.py b/tests/lvm_test.py
+index 11d8c94e..6ddeaa6a 100644
+--- a/tests/lvm_test.py
++++ b/tests/lvm_test.py
+@@ -11,7 +11,7 @@ import time
+ from contextlib import contextmanager
+ from distutils.version import LooseVersion
+
+-from utils import create_sparse_tempfile, create_lio_device, delete_lio_device, fake_utils, fake_path, TestTags, tag_test, run_command
++from utils import create_sparse_tempfile, create_lio_device, delete_lio_device, fake_utils, fake_path, TestTags, tag_test, run_command, read_file
+ from gi.repository import BlockDev, GLib
+
+
+@@ -1765,3 +1765,38 @@ class LvmConfigTestPvremove(LvmPVonlyTestCase):
+ BlockDev.lvm_set_global_config("")
+ succ = BlockDev.lvm_pvremove(self.loop_dev)
+ self.assertTrue(succ)
++
++
++class LvmTestDevicesFile(LvmPVonlyTestCase):
++ devicefile = "bd_lvm_test.devices"
++
++ @classmethod
++ def tearDownClass(cls):
++ shutil.rmtree("/etc/lvm/devices/" + cls.devicefile, ignore_errors=True)
++
++ super(LvmTestDevicesFile, cls).tearDownClass()
++
++ def test_devices_add_delete(self):
++ if not self.devices_avail:
++ self.skipTest("skipping LVM devices file test: not supported")
++
++ succ = BlockDev.lvm_pvcreate(self.loop_dev)
++ self.assertTrue(succ)
++
++ with self.assertRaises(GLib.GError):
++ BlockDev.lvm_devices_add("/non/existing/device", self.devicefile)
++
++ with self.assertRaises(GLib.GError):
++ BlockDev.lvm_devices_delete(self.loop_dev, self.devicefile)
++
++ succ = BlockDev.lvm_devices_add(self.loop_dev, self.devicefile)
++ self.assertTrue(succ)
++
++ dfile = read_file("/etc/lvm/devices/" + self.devicefile)
++ self.assertIn(self.loop_dev, dfile)
++
++ succ = BlockDev.lvm_devices_delete(self.loop_dev, self.devicefile)
++ self.assertTrue(succ)
++
++ dfile = read_file("/etc/lvm/devices/" + self.devicefile)
++ self.assertNotIn(self.loop_dev, dfile)
+--
+2.37.3
+
+
+From 4c832576df8918c269db8fe2cb7eb74e45628d6c Mon Sep 17 00:00:00 2001
+From: Vojtech Trefny <vtrefny@redhat.com>
+Date: Fri, 15 Oct 2021 13:18:54 +0200
+Subject: [PATCH 3/7] lvm: Report special error when system.devices file is not
+ enabled
+
+This can be disabled either in LVM by a compile time option or
+by a lvm.conf option so we should report a specific error for this
+case so users can distinguish between the feature not being enabled
+and not being supported at all.
+---
+ src/lib/plugin_apis/lvm.api | 1 +
+ src/plugins/lvm-dbus.c | 70 +++++++++++++++++++++++++++++++++++++
+ src/plugins/lvm.c | 60 +++++++++++++++++++++++++++++++
+ src/plugins/lvm.h | 1 +
+ tests/lvm_dbus_tests.py | 15 ++++++++
+ tests/lvm_test.py | 15 ++++++++
+ 6 files changed, 162 insertions(+)
+
+diff --git a/src/lib/plugin_apis/lvm.api b/src/lib/plugin_apis/lvm.api
+index b869afcc..b8cde70b 100644
+--- a/src/lib/plugin_apis/lvm.api
++++ b/src/lib/plugin_apis/lvm.api
+@@ -44,6 +44,7 @@ typedef enum {
+ BD_LVM_ERROR_FAIL,
+ BD_LVM_ERROR_NOT_SUPPORTED,
+ BD_LVM_ERROR_VDO_POLICY_INVAL,
++ BD_LVM_ERROR_DEVICES_DISABLED,
+ } BDLVMError;
+
+ typedef enum {
+diff --git a/src/plugins/lvm-dbus.c b/src/plugins/lvm-dbus.c
+index 86ca28ca..7f48e422 100644
+--- a/src/plugins/lvm-dbus.c
++++ b/src/plugins/lvm-dbus.c
+@@ -3951,6 +3951,64 @@ BDLVMVDOStats* bd_lvm_vdo_get_stats (const gchar *vg_name, const gchar *pool_nam
+ return stats;
+ }
+
++/* check whether the LVM devices file is enabled by LVM
++ * we use the existence of the "lvmdevices" command to check whether the feature is available
++ * or not, but this can still be disabled either in LVM or in lvm.conf
++ */
++static gboolean _lvm_devices_enabled () {
++ const gchar *args[6] = {"lvmconfig", "--typeconfig", NULL, "devices/use_devicesfile", NULL, NULL};
++ gboolean ret = FALSE;
++ GError *loc_error = NULL;
++ gchar *output = NULL;
++ gboolean enabled = FALSE;
++ gint scanned = 0;
++ g_autofree gchar *config_arg = NULL;
++
++ /* try current config first -- if we get something from this it means the feature is
++ explicitly enabled or disabled by system lvm.conf or using the --config option */
++ args[2] = "current";
++
++ /* make sure to include the global config from us when getting the current config value */
++ g_mutex_lock (&global_config_lock);
++ if (global_config_str) {
++ config_arg = g_strdup_printf ("--config=%s", global_config_str);
++ args[4] = config_arg;
++ }
++
++ ret = bd_utils_exec_and_capture_output (args, NULL, &output, &loc_error);
++ g_mutex_unlock (&global_config_lock);
++ if (ret) {
++ scanned = sscanf (output, "use_devicesfile=%u", &enabled);
++ g_free (output);
++ if (scanned != 1)
++ return FALSE;
++
++ return enabled;
++ } else {
++ g_clear_error (&loc_error);
++ g_free (output);
++ }
++
++ output = NULL;
++
++ /* now try default */
++ args[2] = "default";
++ ret = bd_utils_exec_and_capture_output (args, NULL, &output, &loc_error);
++ if (ret) {
++ scanned = sscanf (output, "# use_devicesfile=%u", &enabled);
++ g_free (output);
++ if (scanned != 1)
++ return FALSE;
++
++ return enabled;
++ } else {
++ g_clear_error (&loc_error);
++ g_free (output);
++ }
++
++ return FALSE;
++}
++
+ /**
+ * bd_lvm_devices_add:
+ * @device: device (PV) to add to the devices file
+@@ -3969,6 +4027,12 @@ gboolean bd_lvm_devices_add (const gchar *device, const gchar *devices_file, con
+ if (!bd_lvm_is_tech_avail (BD_LVM_TECH_DEVICES, 0, error))
+ return FALSE;
+
++ if (!_lvm_devices_enabled ()) {
++ g_set_error (error, BD_LVM_ERROR, BD_LVM_ERROR_DEVICES_DISABLED,
++ "LVM devices file not enabled.");
++ return FALSE;
++ }
++
+ if (devices_file) {
+ devfile = g_strdup_printf ("--devicesfile=%s", devices_file);
+ args[3] = devfile;
+@@ -3995,6 +4059,12 @@ gboolean bd_lvm_devices_delete (const gchar *device, const gchar *devices_file,
+ if (!bd_lvm_is_tech_avail (BD_LVM_TECH_DEVICES, 0, error))
+ return FALSE;
+
++ if (!_lvm_devices_enabled ()) {
++ g_set_error (error, BD_LVM_ERROR, BD_LVM_ERROR_DEVICES_DISABLED,
++ "LVM devices file not enabled.");
++ return FALSE;
++ }
++
+ if (devices_file) {
+ devfile = g_strdup_printf ("--devicesfile=%s", devices_file);
+ args[3] = devfile;
+diff --git a/src/plugins/lvm.c b/src/plugins/lvm.c
+index 3bd8fae1..73d5005f 100644
+--- a/src/plugins/lvm.c
++++ b/src/plugins/lvm.c
+@@ -3251,6 +3251,54 @@ BDLVMVDOStats* bd_lvm_vdo_get_stats (const gchar *vg_name, const gchar *pool_nam
+ return stats;
+ }
+
++/* check whether the LVM devices file is enabled by LVM
++ * we use the existence of the "lvmdevices" command to check whether the feature is available
++ * or not, but this can still be disabled either in LVM or in lvm.conf
++ */
++static gboolean _lvm_devices_enabled () {
++ const gchar *args[5] = {"config", "--typeconfig", NULL, "devices/use_devicesfile", NULL};
++ gboolean ret = FALSE;
++ GError *loc_error = NULL;
++ gchar *output = NULL;
++ gboolean enabled = FALSE;
++ gint scanned = 0;
++
++ /* try current config first -- if we get something from this it means the feature is
++ explicitly enabled or disabled by system lvm.conf or using the --config option */
++ args[2] = "current";
++ ret = call_lvm_and_capture_output (args, NULL, &output, &loc_error);
++ if (ret) {
++ scanned = sscanf (output, "use_devicesfile=%u", &enabled);
++ g_free (output);
++ if (scanned != 1)
++ return FALSE;
++
++ return enabled;
++ } else {
++ g_clear_error (&loc_error);
++ g_free (output);
++ }
++
++ output = NULL;
++
++ /* now try default */
++ args[2] = "default";
++ ret = call_lvm_and_capture_output (args, NULL, &output, &loc_error);
++ if (ret) {
++ scanned = sscanf (output, "# use_devicesfile=%u", &enabled);
++ g_free (output);
++ if (scanned != 1)
++ return FALSE;
++
++ return enabled;
++ } else {
++ g_clear_error (&loc_error);
++ g_free (output);
++ }
++
++ return FALSE;
++}
++
+ /**
+ * bd_lvm_devices_add:
+ * @device: device (PV) to add to the devices file
+@@ -3269,6 +3317,12 @@ gboolean bd_lvm_devices_add (const gchar *device, const gchar *devices_file, con
+ if (!bd_lvm_is_tech_avail (BD_LVM_TECH_DEVICES, 0, error))
+ return FALSE;
+
++ if (!_lvm_devices_enabled ()) {
++ g_set_error (error, BD_LVM_ERROR, BD_LVM_ERROR_DEVICES_DISABLED,
++ "LVM devices file not enabled.");
++ return FALSE;
++ }
++
+ if (devices_file) {
+ devfile = g_strdup_printf ("--devicesfile=%s", devices_file);
+ args[3] = devfile;
+@@ -3295,6 +3349,12 @@ gboolean bd_lvm_devices_delete (const gchar *device, const gchar *devices_file,
+ if (!bd_lvm_is_tech_avail (BD_LVM_TECH_DEVICES, 0, error))
+ return FALSE;
+
++ if (!_lvm_devices_enabled ()) {
++ g_set_error (error, BD_LVM_ERROR, BD_LVM_ERROR_DEVICES_DISABLED,
++ "LVM devices file not enabled.");
++ return FALSE;
++ }
++
+ if (devices_file) {
+ devfile = g_strdup_printf ("--devicesfile=%s", devices_file);
+ args[3] = devfile;
+diff --git a/src/plugins/lvm.h b/src/plugins/lvm.h
+index 5ca2a9d7..fabf091f 100644
+--- a/src/plugins/lvm.h
++++ b/src/plugins/lvm.h
+@@ -53,6 +53,7 @@ typedef enum {
+ BD_LVM_ERROR_FAIL,
+ BD_LVM_ERROR_NOT_SUPPORTED,
+ BD_LVM_ERROR_VDO_POLICY_INVAL,
++ BD_LVM_ERROR_DEVICES_DISABLED,
+ } BDLVMError;
+
+ typedef enum {
+diff --git a/tests/lvm_dbus_tests.py b/tests/lvm_dbus_tests.py
+index 82e4761d..792c1cc8 100644
+--- a/tests/lvm_dbus_tests.py
++++ b/tests/lvm_dbus_tests.py
+@@ -1820,3 +1820,18 @@ class LvmTestDevicesFile(LvmPVonlyTestCase):
+
+ dfile = read_file("/etc/lvm/devices/" + self.devicefile)
+ self.assertNotIn(self.loop_dev, dfile)
++
++ def test_devices_enabled(self):
++ if not self.devices_avail:
++ self.skipTest("skipping LVM devices file test: not supported")
++
++ self.addCleanup(BlockDev.lvm_set_global_config, "")
++
++ # checking if the feature is enabled or disabled is hard so lets just disable
++ # the devices file using the global config and check lvm_devices_add fails
++ # with the correct exception message
++ succ = BlockDev.lvm_set_global_config("devices { use_devicesfile=0 }")
++ self.assertTrue(succ)
++
++ with self.assertRaisesRegex(GLib.GError, "LVM devices file not enabled."):
++ BlockDev.lvm_devices_add("", self.devicefile)
+diff --git a/tests/lvm_test.py b/tests/lvm_test.py
+index 6ddeaa6a..73fb1030 100644
+--- a/tests/lvm_test.py
++++ b/tests/lvm_test.py
+@@ -1800,3 +1800,18 @@ class LvmTestDevicesFile(LvmPVonlyTestCase):
+
+ dfile = read_file("/etc/lvm/devices/" + self.devicefile)
+ self.assertNotIn(self.loop_dev, dfile)
++
++ def test_devices_enabled(self):
++ if not self.devices_avail:
++ self.skipTest("skipping LVM devices file test: not supported")
++
++ self.addCleanup(BlockDev.lvm_set_global_config, "")
++
++ # checking if the feature is enabled or disabled is hard so lets just disable
++ # the devices file using the global config and check lvm_devices_add fails
++ # with the correct exception message
++ succ = BlockDev.lvm_set_global_config("devices { use_devicesfile=0 }")
++ self.assertTrue(succ)
++
++ with self.assertRaisesRegex(GLib.GError, "LVM devices file not enabled."):
++ BlockDev.lvm_devices_add("", self.devicefile)
+--
+2.37.3
+
+
+From 2fdec5f7e42de869d4b2ec80dce597d22dd57617 Mon Sep 17 00:00:00 2001
+From: Vojtech Trefny <vtrefny@redhat.com>
+Date: Fri, 15 Oct 2021 14:21:03 +0200
+Subject: [PATCH 4/7] lvm: Force enable LVM devices file for LvmTestDevicesFile
+
+This feauture might be disabled in lvm.conf so to be able to test
+it we need to override this. The correct handling of the disabled
+state is checked in a separate test case.
+---
+ tests/lvm_dbus_tests.py | 8 ++++++++
+ tests/lvm_test.py | 8 ++++++++
+ 2 files changed, 16 insertions(+)
+
+diff --git a/tests/lvm_dbus_tests.py b/tests/lvm_dbus_tests.py
+index 792c1cc8..e55535cc 100644
+--- a/tests/lvm_dbus_tests.py
++++ b/tests/lvm_dbus_tests.py
+@@ -1800,6 +1800,12 @@ class LvmTestDevicesFile(LvmPVonlyTestCase):
+ if not self.devices_avail:
+ self.skipTest("skipping LVM devices file test: not supported")
+
++ self.addCleanup(BlockDev.lvm_set_global_config, "")
++
++ # force-enable the feature, it might be disabled by default
++ succ = BlockDev.lvm_set_global_config("devices { use_devicesfile=1 }")
++ self.assertTrue(succ)
++
+ succ = BlockDev.lvm_pvcreate(self.loop_dev)
+ self.assertTrue(succ)
+
+@@ -1821,6 +1827,8 @@ class LvmTestDevicesFile(LvmPVonlyTestCase):
+ dfile = read_file("/etc/lvm/devices/" + self.devicefile)
+ self.assertNotIn(self.loop_dev, dfile)
+
++ BlockDev.lvm_set_global_config("")
++
+ def test_devices_enabled(self):
+ if not self.devices_avail:
+ self.skipTest("skipping LVM devices file test: not supported")
+diff --git a/tests/lvm_test.py b/tests/lvm_test.py
+index 73fb1030..907b4f59 100644
+--- a/tests/lvm_test.py
++++ b/tests/lvm_test.py
+@@ -1780,6 +1780,12 @@ class LvmTestDevicesFile(LvmPVonlyTestCase):
+ if not self.devices_avail:
+ self.skipTest("skipping LVM devices file test: not supported")
+
++ self.addCleanup(BlockDev.lvm_set_global_config, "")
++
++ # force-enable the feature, it might be disabled by default
++ succ = BlockDev.lvm_set_global_config("devices { use_devicesfile=1 }")
++ self.assertTrue(succ)
++
+ succ = BlockDev.lvm_pvcreate(self.loop_dev)
+ self.assertTrue(succ)
+
+@@ -1801,6 +1807,8 @@ class LvmTestDevicesFile(LvmPVonlyTestCase):
+ dfile = read_file("/etc/lvm/devices/" + self.devicefile)
+ self.assertNotIn(self.loop_dev, dfile)
+
++ BlockDev.lvm_set_global_config("")
++
+ def test_devices_enabled(self):
+ if not self.devices_avail:
+ self.skipTest("skipping LVM devices file test: not supported")
+--
+2.37.3
+
+
+From 1809a41c0b2b99c8d6a077b5aa70834686980181 Mon Sep 17 00:00:00 2001
+From: Vojtech Trefny <vtrefny@redhat.com>
+Date: Fri, 12 Nov 2021 14:51:39 +0100
+Subject: [PATCH 5/7] tests: Fix resetting global LVM config after LVM devices
+ file test
+
+We need to set the config to None/NULL not to an empty string.
+---
+ tests/lvm_dbus_tests.py | 6 +++---
+ tests/lvm_test.py | 6 +++---
+ 2 files changed, 6 insertions(+), 6 deletions(-)
+
+diff --git a/tests/lvm_dbus_tests.py b/tests/lvm_dbus_tests.py
+index e55535cc..8ae670d5 100644
+--- a/tests/lvm_dbus_tests.py
++++ b/tests/lvm_dbus_tests.py
+@@ -1800,7 +1800,7 @@ class LvmTestDevicesFile(LvmPVonlyTestCase):
+ if not self.devices_avail:
+ self.skipTest("skipping LVM devices file test: not supported")
+
+- self.addCleanup(BlockDev.lvm_set_global_config, "")
++ self.addCleanup(BlockDev.lvm_set_global_config, None)
+
+ # force-enable the feature, it might be disabled by default
+ succ = BlockDev.lvm_set_global_config("devices { use_devicesfile=1 }")
+@@ -1827,13 +1827,13 @@ class LvmTestDevicesFile(LvmPVonlyTestCase):
+ dfile = read_file("/etc/lvm/devices/" + self.devicefile)
+ self.assertNotIn(self.loop_dev, dfile)
+
+- BlockDev.lvm_set_global_config("")
++ BlockDev.lvm_set_global_config(None)
+
+ def test_devices_enabled(self):
+ if not self.devices_avail:
+ self.skipTest("skipping LVM devices file test: not supported")
+
+- self.addCleanup(BlockDev.lvm_set_global_config, "")
++ self.addCleanup(BlockDev.lvm_set_global_config, None)
+
+ # checking if the feature is enabled or disabled is hard so lets just disable
+ # the devices file using the global config and check lvm_devices_add fails
+diff --git a/tests/lvm_test.py b/tests/lvm_test.py
+index 907b4f59..095e4bac 100644
+--- a/tests/lvm_test.py
++++ b/tests/lvm_test.py
+@@ -1780,7 +1780,7 @@ class LvmTestDevicesFile(LvmPVonlyTestCase):
+ if not self.devices_avail:
+ self.skipTest("skipping LVM devices file test: not supported")
+
+- self.addCleanup(BlockDev.lvm_set_global_config, "")
++ self.addCleanup(BlockDev.lvm_set_global_config, None)
+
+ # force-enable the feature, it might be disabled by default
+ succ = BlockDev.lvm_set_global_config("devices { use_devicesfile=1 }")
+@@ -1807,13 +1807,13 @@ class LvmTestDevicesFile(LvmPVonlyTestCase):
+ dfile = read_file("/etc/lvm/devices/" + self.devicefile)
+ self.assertNotIn(self.loop_dev, dfile)
+
+- BlockDev.lvm_set_global_config("")
++ BlockDev.lvm_set_global_config(None)
+
+ def test_devices_enabled(self):
+ if not self.devices_avail:
+ self.skipTest("skipping LVM devices file test: not supported")
+
+- self.addCleanup(BlockDev.lvm_set_global_config, "")
++ self.addCleanup(BlockDev.lvm_set_global_config, None)
+
+ # checking if the feature is enabled or disabled is hard so lets just disable
+ # the devices file using the global config and check lvm_devices_add fails
+--
+2.37.3
+
+
+From 1c2f1d20a3cfa522b78ab007e8e4f9a5a4bb579d Mon Sep 17 00:00:00 2001
+From: Vojtech Trefny <vtrefny@redhat.com>
+Date: Fri, 12 Nov 2021 15:10:45 +0100
+Subject: [PATCH 6/7] lvm: Do not set global config to and empty string
+
+If we set it to an empty string we end up running "--config"
+without a parameter and lvm will use whatever is next parameter
+like the device path for pvremove.
+---
+ tests/lvm_dbus_tests.py | 12 ++++++++++++
+ tests/lvm_test.py | 12 ++++++++++++
+ 2 files changed, 24 insertions(+)
+
+diff --git a/tests/lvm_dbus_tests.py b/tests/lvm_dbus_tests.py
+index 8ae670d5..61c898c1 100644
+--- a/tests/lvm_dbus_tests.py
++++ b/tests/lvm_dbus_tests.py
+@@ -1843,3 +1843,15 @@ class LvmTestDevicesFile(LvmPVonlyTestCase):
+
+ with self.assertRaisesRegex(GLib.GError, "LVM devices file not enabled."):
+ BlockDev.lvm_devices_add("", self.devicefile)
++
++
++class LvmConfigTestPvremove(LvmPVonlyTestCase):
++
++ @tag_test(TestTags.REGRESSION)
++ def test_set_empty_config(self):
++ succ = BlockDev.lvm_pvcreate(self.loop_dev)
++ self.assertTrue(succ)
++
++ BlockDev.lvm_set_global_config("")
++ succ = BlockDev.lvm_pvremove(self.loop_dev)
++ self.assertTrue(succ)
+diff --git a/tests/lvm_test.py b/tests/lvm_test.py
+index 095e4bac..36ff10ec 100644
+--- a/tests/lvm_test.py
++++ b/tests/lvm_test.py
+@@ -1823,3 +1823,15 @@ class LvmTestDevicesFile(LvmPVonlyTestCase):
+
+ with self.assertRaisesRegex(GLib.GError, "LVM devices file not enabled."):
+ BlockDev.lvm_devices_add("", self.devicefile)
++
++
++class LvmConfigTestPvremove(LvmPVonlyTestCase):
++
++ @tag_test(TestTags.REGRESSION)
++ def test_set_empty_config(self):
++ succ = BlockDev.lvm_pvcreate(self.loop_dev)
++ self.assertTrue(succ)
++
++ BlockDev.lvm_set_global_config("")
++ succ = BlockDev.lvm_pvremove(self.loop_dev)
++ self.assertTrue(succ)
+--
+2.37.3
+
+
+From 05cfb84777c5472550673a1f2150ca357718b3f2 Mon Sep 17 00:00:00 2001
+From: Vojtech Trefny <vtrefny@redhat.com>
+Date: Fri, 26 Nov 2021 15:19:55 +0100
+Subject: [PATCH 7/7] lvm: Use "lvmconfig full" to get valid config instead of
+ "current"
+
+"lvmconfig current" doesn't work together with --config even if we
+don't override the "use_devicefile" key. "lvmconfig full" seems to
+be working in all cases.
+---
+ src/plugins/lvm-dbus.c | 4 ++--
+ src/plugins/lvm.c | 4 ++--
+ 2 files changed, 4 insertions(+), 4 deletions(-)
+
+diff --git a/src/plugins/lvm-dbus.c b/src/plugins/lvm-dbus.c
+index 7f48e422..d4b542e2 100644
+--- a/src/plugins/lvm-dbus.c
++++ b/src/plugins/lvm-dbus.c
+@@ -3964,9 +3964,9 @@ static gboolean _lvm_devices_enabled () {
+ gint scanned = 0;
+ g_autofree gchar *config_arg = NULL;
+
+- /* try current config first -- if we get something from this it means the feature is
++ /* try full config first -- if we get something from this it means the feature is
+ explicitly enabled or disabled by system lvm.conf or using the --config option */
+- args[2] = "current";
++ args[2] = "full";
+
+ /* make sure to include the global config from us when getting the current config value */
+ g_mutex_lock (&global_config_lock);
+diff --git a/src/plugins/lvm.c b/src/plugins/lvm.c
+index 73d5005f..03211f8a 100644
+--- a/src/plugins/lvm.c
++++ b/src/plugins/lvm.c
+@@ -3263,9 +3263,9 @@ static gboolean _lvm_devices_enabled () {
+ gboolean enabled = FALSE;
+ gint scanned = 0;
+
+- /* try current config first -- if we get something from this it means the feature is
++ /* try full config first -- if we get something from this it means the feature is
+ explicitly enabled or disabled by system lvm.conf or using the --config option */
+- args[2] = "current";
++ args[2] = "full";
+ ret = call_lvm_and_capture_output (args, NULL, &output, &loc_error);
+ if (ret) {
+ scanned = sscanf (output, "use_devicesfile=%u", &enabled);
+--
+2.37.3
+
diff --git a/0002-Add-support-for-creating-and-activating-integrity-de.patch b/0002-Add-support-for-creating-and-activating-integrity-de.patch
new file mode 100644
index 0000000..163c812
--- /dev/null
+++ b/0002-Add-support-for-creating-and-activating-integrity-de.patch
@@ -0,0 +1,963 @@
+From 77e6a109043e87f88d2bd2b47d1cefce0eb9f5a9 Mon Sep 17 00:00:00 2001
+From: Vojtech Trefny <vtrefny@redhat.com>
+Date: Mon, 20 Sep 2021 16:38:16 +0200
+Subject: [PATCH 1/3] Add support for creating and activating integrity devices
+
+This adds support for create, open and close actions for standalone
+integrity devices using cryptsetup.
+---
+ configure.ac | 2 +-
+ src/lib/plugin_apis/crypto.api | 157 +++++++++++++++++
+ src/plugins/crypto.c | 261 +++++++++++++++++++++++++++-
+ src/plugins/crypto.h | 41 +++++
+ src/python/gi/overrides/BlockDev.py | 24 +++
+ tests/crypto_test.py | 96 +++++++++-
+ 6 files changed, 573 insertions(+), 8 deletions(-)
+
+diff --git a/configure.ac b/configure.ac
+index 79bd97d8..79bf8045 100644
+--- a/configure.ac
++++ b/configure.ac
+@@ -210,7 +210,7 @@ AS_IF([test "x$with_crypto" != "xno"],
+ AS_IF([$PKG_CONFIG --atleast-version=2.0.3 libcryptsetup],
+ [AC_DEFINE([LIBCRYPTSETUP_2])], [])
+ AS_IF([$PKG_CONFIG --atleast-version=2.3.0 libcryptsetup],
+- [AC_DEFINE([LIBCRYPTSETUP_BITLK])], [])
++ [AC_DEFINE([LIBCRYPTSETUP_23])], [])
+ AS_IF([$PKG_CONFIG --atleast-version=2.4.0 libcryptsetup],
+ [AC_DEFINE([LIBCRYPTSETUP_24])], [])
+ AS_IF([test "x$with_escrow" != "xno"],
+diff --git a/src/lib/plugin_apis/crypto.api b/src/lib/plugin_apis/crypto.api
+index ef0217fe..40e32c89 100644
+--- a/src/lib/plugin_apis/crypto.api
++++ b/src/lib/plugin_apis/crypto.api
+@@ -1,5 +1,6 @@
+ #include <glib.h>
+ #include <blockdev/utils.h>
++#include <libcryptsetup.h>
+
+ #define BD_CRYPTO_LUKS_METADATA_SIZE G_GUINT64_CONSTANT (2097152ULL) // 2 MiB
+
+@@ -245,6 +246,115 @@ GType bd_crypto_luks_extra_get_type () {
+ return type;
+ }
+
++#define BD_CRYPTO_TYPE_INTEGRITY_EXTRA (bd_crypto_integrity_extra_get_type ())
++GType bd_crypto_integrity_extra_get_type();
++
++/**
++ * BDCryptoIntegrityExtra:
++ * @sector_size: integrity sector size
++ * @journal_size: size of journal in bytes
++ * @journal_watermark: journal flush watermark in percents; in bitmap mode sectors-per-bit
++ * @journal_commit_time: journal commit time (or bitmap flush time) in ms
++ * @interleave_sectors: number of interleave sectors (power of two)
++ * @tag_size: tag size per-sector in bytes
++ * @buffer_sectors: number of sectors in one buffer
++ */
++typedef struct BDCryptoIntegrityExtra {
++ guint32 sector_size;
++ guint64 journal_size;
++ guint journal_watermark;
++ guint journal_commit_time;
++ guint32 interleave_sectors;
++ guint32 tag_size;
++ guint32 buffer_sectors;
++} BDCryptoIntegrityExtra;
++
++/**
++ * bd_crypto_integrity_extra_copy: (skip)
++ * @extra: (allow-none): %BDCryptoIntegrityExtra to copy
++ *
++ * Creates a new copy of @extra.
++ */
++BDCryptoIntegrityExtra* bd_crypto_integrity_extra_copy (BDCryptoIntegrityExtra *extra) {
++ if (extra == NULL)
++ return NULL;
++
++ BDCryptoIntegrityExtra *new_extra = g_new0 (BDCryptoIntegrityExtra, 1);
++
++ new_extra->sector_size = extra->sector_size;
++ new_extra->journal_size = extra->journal_size;
++ new_extra->journal_watermark = extra->journal_watermark;
++ new_extra->journal_commit_time = extra->journal_commit_time;
++ new_extra->interleave_sectors = extra->interleave_sectors;
++ new_extra->tag_size = extra->tag_size;
++ new_extra->buffer_sectors = extra->buffer_sectors;
++
++ return new_extra;
++}
++
++/**
++ * bd_crypto_integrity_extra_free: (skip)
++ * @extra: (allow-none): %BDCryptoIntegrityExtra to free
++ *
++ * Frees @extra.
++ */
++void bd_crypto_integrity_extra_free (BDCryptoIntegrityExtra *extra) {
++ if (extra == NULL)
++ return;
++
++ g_free (extra);
++}
++
++/**
++ * bd_crypto_integrity_extra_new: (constructor)
++ * @sector_size: integrity sector size, 0 for default (512)
++ * @journal_size: size of journal in bytes
++ * @journal_watermark: journal flush watermark in percents; in bitmap mode sectors-per-bit
++ * @journal_commit_time: journal commit time (or bitmap flush time) in ms
++ * @interleave_sectors: number of interleave sectors (power of two)
++ * @tag_size: tag size per-sector in bytes
++ * @buffer_sectors: number of sectors in one buffer
++ *
++ * Returns: (transfer full): a new Integrity extra argument
++ */
++BDCryptoIntegrityExtra* bd_crypto_integrity_extra_new (guint64 sector_size, guint64 journal_size, guint journal_watermark, guint journal_commit_time, guint64 interleave_sectors, guint64 tag_size, guint64 buffer_sectors) {
++ BDCryptoIntegrityExtra *ret = g_new0 (BDCryptoIntegrityExtra, 1);
++ ret->sector_size = sector_size;
++ ret->journal_size = journal_size;
++ ret->journal_watermark = journal_watermark;
++ ret->journal_commit_time = journal_commit_time;
++ ret->interleave_sectors = interleave_sectors;
++ ret->tag_size = tag_size;
++ ret->buffer_sectors = buffer_sectors;
++
++ return ret;
++}
++
++GType bd_crypto_integrity_extra_get_type () {
++ static GType type = 0;
++
++ if (G_UNLIKELY(type == 0)) {
++ type = g_boxed_type_register_static("BDCryptoIntegrityExtra",
++ (GBoxedCopyFunc) bd_crypto_integrity_extra_copy,
++ (GBoxedFreeFunc) bd_crypto_integrity_extra_free);
++ }
++
++ return type;
++}
++
++typedef enum {
++ BD_CRYPTO_INTEGRITY_OPEN_NO_JOURNAL = CRYPT_ACTIVATE_NO_JOURNAL,
++ BD_CRYPTO_INTEGRITY_OPEN_RECOVERY = CRYPT_ACTIVATE_RECOVERY,
++#ifdef CRYPT_ACTIVATE_NO_JOURNAL_BITMAP
++ BD_CRYPTO_INTEGRITY_OPEN_NO_JOURNAL_BITMAP = CRYPT_ACTIVATE_NO_JOURNAL_BITMAP,
++#endif
++ BD_CRYPTO_INTEGRITY_OPEN_RECALCULATE = CRYPT_ACTIVATE_RECALCULATE,
++#ifdef CRYPT_ACTIVATE_RECALCULATE_RESET
++ BD_CRYPTO_INTEGRITY_OPEN_RECALCULATE_RESET = CRYPT_ACTIVATE_RECALCULATE_RESET,
++#endif
++ BD_CRYPTO_INTEGRITY_OPEN_ALLOW_DISCARDS = CRYPT_ACTIVATE_ALLOW_DISCARDS,
++} BDCryptoIntegrityOpenFlags;
++
+ #define BD_CRYPTO_TYPE_LUKS_INFO (bd_crypto_luks_info_get_type ())
+ GType bd_crypto_luks_info_get_type();
+
+@@ -857,6 +967,53 @@ BDCryptoLUKSInfo* bd_crypto_luks_info (const gchar *luks_device, GError **error)
+ */
+ BDCryptoIntegrityInfo* bd_crypto_integrity_info (const gchar *device, GError **error);
+
++/**
++ * bd_crypto_integrity_format:
++ * @device: a device to format as integrity
++ * @algorithm: integrity algorithm specification (e.g. "crc32c" or "sha256") or %NULL to use the default
++ * @wipe: whether to wipe the device after format; a device that is not initially wiped will contain invalid checksums
++ * @key_data: (allow-none) (array length=key_size): integrity key or %NULL if not needed
++ * @key_size: size the integrity key and @key_data
++ * @extra: (allow-none): extra arguments for integrity format creation
++ * @error: (out): place to store error (if any)
++ *
++ * Formats the given @device as integrity according to the other parameters given.
++ *
++ * Returns: whether the given @device was successfully formatted as integrity or not
++ * (the @error) contains the error in such cases)
++ *
++ * Tech category: %BD_CRYPTO_TECH_INTEGRITY-%BD_CRYPTO_TECH_MODE_CREATE
++ */
++gboolean bd_crypto_integrity_format (const gchar *device, const gchar *algorithm, gboolean wipe, const guint8* key_data, gsize key_size, BDCryptoIntegrityExtra *extra, GError **error);
++
++/**
++ * bd_crypto_integrity_open:
++ * @device: integrity device to open
++ * @name: name for the opened @device
++ * @algorithm: (allow-none): integrity algorithm specification (e.g. "crc32c" or "sha256") or %NULL to use the default
++ * @key_data: (allow-none) (array length=key_size): integrity key or %NULL if not needed
++ * @key_size: size the integrity key and @key_data
++ * @flags: flags for the integrity device activation
++ * @extra: (allow-none): extra arguments for integrity open
++ * @error: (out): place to store error (if any)
++ *
++ * Returns: whether the @device was successfully opened or not
++ *
++ * Tech category: %BD_CRYPTO_TECH_INTEGRITY-%BD_CRYPTO_TECH_MODE_OPEN_CLOSE
++ */
++gboolean bd_crypto_integrity_open (const gchar *device, const gchar *name, const gchar *algorithm, const guint8* key_data, gsize key_size, BDCryptoIntegrityOpenFlags flags, BDCryptoIntegrityExtra *extra, GError **error);
++
++/**
++ * bd_crypto_integrity_close:
++ * @integrity_device: integrity device to close
++ * @error: (out): place to store error (if any)
++ *
++ * Returns: whether the given @integrity_device was successfully closed or not
++ *
++ * Tech category: %BD_CRYPTO_TECH_INTEGRITY-%BD_CRYPTO_TECH_MODE_OPEN_CLOSE
++ */
++gboolean bd_crypto_integrity_close (const gchar *integrity_device, GError **error);
++
+ /**
+ * bd_crypto_device_seems_encrypted:
+ * @device: the queried device
+diff --git a/src/plugins/crypto.c b/src/plugins/crypto.c
+index 51908140..8549cf23 100644
+--- a/src/plugins/crypto.c
++++ b/src/plugins/crypto.c
+@@ -50,6 +50,18 @@
+
+ #define SECTOR_SIZE 512
+
++#define DEFAULT_LUKS_KEYSIZE_BITS 256
++#define DEFAULT_LUKS_CIPHER "aes-xts-plain64"
++
++#ifdef LIBCRYPTSETUP_23
++/* 0 for autodetect since 2.3.0 */
++#define DEFAULT_INTEGRITY_TAG_SIZE 0
++#else
++/* we need some sane default for older versions, users should specify tag size when using
++ other algorithms than the default crc32c */
++#define DEFAULT_INTEGRITY_TAG_SIZE 4
++#endif
++
+ #define UNUSED __attribute__((unused))
+
+ /**
+@@ -146,6 +158,43 @@ BDCryptoLUKSExtra* bd_crypto_luks_extra_new (guint64 data_alignment, const gchar
+ return ret;
+ }
+
++BDCryptoIntegrityExtra* bd_crypto_integrity_extra_new (guint64 sector_size, guint64 journal_size, guint journal_watermark, guint journal_commit_time, guint64 interleave_sectors, guint64 tag_size, guint64 buffer_sectors) {
++ BDCryptoIntegrityExtra *ret = g_new0 (BDCryptoIntegrityExtra, 1);
++ ret->sector_size = sector_size;
++ ret->journal_size = journal_size;
++ ret->journal_watermark = journal_watermark;
++ ret->journal_commit_time = journal_commit_time;
++ ret->interleave_sectors = interleave_sectors;
++ ret->tag_size = tag_size;
++ ret->buffer_sectors = buffer_sectors;
++
++ return ret;
++}
++
++BDCryptoIntegrityExtra* bd_crypto_integrity_extra_copy (BDCryptoIntegrityExtra *extra) {
++ if (extra == NULL)
++ return NULL;
++
++ BDCryptoIntegrityExtra *new_extra = g_new0 (BDCryptoIntegrityExtra, 1);
++
++ new_extra->sector_size = extra->sector_size;
++ new_extra->journal_size = extra->journal_size;
++ new_extra->journal_watermark = extra->journal_watermark;
++ new_extra->journal_commit_time = extra->journal_commit_time;
++ new_extra->interleave_sectors = extra->interleave_sectors;
++ new_extra->tag_size = extra->tag_size;
++ new_extra->buffer_sectors = extra->buffer_sectors;
++
++ return new_extra;
++}
++
++void bd_crypto_integrity_extra_free (BDCryptoIntegrityExtra *extra) {
++ if (extra == NULL)
++ return;
++
++ g_free (extra);
++}
++
+ void bd_crypto_luks_info_free (BDCryptoLUKSInfo *info) {
+ if (info == NULL)
+ return;
+@@ -346,15 +395,15 @@ gboolean bd_crypto_is_tech_avail (BDCryptoTech tech, guint64 mode, GError **erro
+ "Integrity technology requires libcryptsetup >= 2.0");
+ return FALSE;
+ #endif
+- ret = mode & (BD_CRYPTO_TECH_MODE_QUERY);
++ ret = mode & (BD_CRYPTO_TECH_MODE_CREATE|BD_CRYPTO_TECH_MODE_OPEN_CLOSE|BD_CRYPTO_TECH_MODE_QUERY);
+ if (ret != mode) {
+ g_set_error (error, BD_CRYPTO_ERROR, BD_CRYPTO_ERROR_TECH_UNAVAIL,
+- "Only 'query' supported for Integrity");
++ "Only 'create', 'open' and 'query' supported for Integrity");
+ return FALSE;
+ } else
+ return TRUE;
+ case BD_CRYPTO_TECH_BITLK:
+-#ifndef LIBCRYPTSETUP_BITLK
++#ifndef LIBCRYPTSETUP_23
+ g_set_error (error, BD_CRYPTO_ERROR, BD_CRYPTO_ERROR_TECH_UNAVAIL,
+ "BITLK technology requires libcryptsetup >= 2.3.0");
+ return FALSE;
+@@ -2035,6 +2084,208 @@ BDCryptoIntegrityInfo* bd_crypto_integrity_info (const gchar *device, GError **e
+ }
+ #endif
+
++static int _wipe_progress (guint64 size, guint64 offset, void *usrptr) {
++ /* "convert" the progress from 0-100 to 50-100 because wipe starts at 50 in bd_crypto_integrity_format */
++ gdouble progress = 50 + (((gdouble) offset / size) * 100) / 2;
++ bd_utils_report_progress (*(guint64 *) usrptr, progress, "Integrity device wipe in progress");
++
++ return 0;
++}
++
++/**
++ * bd_crypto_integrity_format:
++ * @device: a device to format as integrity
++ * @algorithm: integrity algorithm specification (e.g. "crc32c" or "sha256")
++ * @wipe: whether to wipe the device after format; a device that is not initially wiped will contain invalid checksums
++ * @key_data: (allow-none) (array length=key_size): integrity key or %NULL if not needed
++ * @key_size: size the integrity key and @key_data
++ * @extra: (allow-none): extra arguments for integrity format creation
++ * @error: (out): place to store error (if any)
++ *
++ * Formats the given @device as integrity according to the other parameters given.
++ *
++ * Returns: whether the given @device was successfully formatted as integrity or not
++ * (the @error) contains the error in such cases)
++ *
++ * Tech category: %BD_CRYPTO_TECH_INTEGRITY-%BD_CRYPTO_TECH_MODE_CREATE
++ */
++gboolean bd_crypto_integrity_format (const gchar *device, const gchar *algorithm, gboolean wipe, const guint8* key_data, gsize key_size, BDCryptoIntegrityExtra *extra, GError **error) {
++ struct crypt_device *cd = NULL;
++ gint ret;
++ guint64 progress_id = 0;
++ gchar *msg = NULL;
++ struct crypt_params_integrity params = ZERO_INIT;
++ g_autofree gchar *tmp_name = NULL;
++ g_autofree gchar *tmp_path = NULL;
++ g_autofree gchar *dev_name = NULL;
++
++ msg = g_strdup_printf ("Started formatting '%s' as integrity device", device);
++ progress_id = bd_utils_report_started (msg);
++ g_free (msg);
++
++ ret = crypt_init (&cd, device);
++ if (ret != 0) {
++ g_set_error (error, BD_CRYPTO_ERROR, BD_CRYPTO_ERROR_DEVICE,
++ "Failed to initialize device: %s", strerror_l (-ret, c_locale));
++ bd_utils_report_finished (progress_id, (*error)->message);
++ return FALSE;
++ }
++
++ if (extra) {
++ params.sector_size = extra->sector_size;
++ params.journal_size = extra->journal_size;
++ params.journal_watermark = extra->journal_watermark;
++ params.journal_commit_time = extra->journal_commit_time;
++ params.interleave_sectors = extra->interleave_sectors;
++ params.tag_size = extra->tag_size;
++ params.buffer_sectors = extra->buffer_sectors;
++ }
++
++ params.integrity_key_size = key_size;
++ params.integrity = algorithm;
++ params.tag_size = params.tag_size ? params.tag_size : DEFAULT_INTEGRITY_TAG_SIZE;
++
++ ret = crypt_format (cd, CRYPT_INTEGRITY, NULL, NULL, NULL, NULL, 0, &params);
++ if (ret != 0) {
++ g_set_error (error, BD_CRYPTO_ERROR, BD_CRYPTO_ERROR_FORMAT_FAILED,
++ "Failed to format device: %s", strerror_l (-ret, c_locale));
++ crypt_free (cd);
++ bd_utils_report_finished (progress_id, (*error)->message);
++ return FALSE;
++ }
++
++ if (wipe) {
++ bd_utils_report_progress (progress_id, 50, "Format created");
++
++ dev_name = g_path_get_basename (device);
++ tmp_name = g_strdup_printf ("bd-temp-integrity-%s-%d", dev_name, g_random_int ());
++ tmp_path = g_strdup_printf ("%s/%s", crypt_get_dir (), tmp_name);
++
++ ret = crypt_activate_by_volume_key (cd, tmp_name, (const char *) key_data, key_size,
++ CRYPT_ACTIVATE_PRIVATE | CRYPT_ACTIVATE_NO_JOURNAL);
++ if (ret != 0) {
++ g_set_error (error, BD_CRYPTO_ERROR, BD_CRYPTO_ERROR_DEVICE,
++ "Failed to activate the newly created integrity device for wiping: %s",
++ strerror_l (-ret, c_locale));
++ crypt_free (cd);
++ bd_utils_report_finished (progress_id, (*error)->message);
++ return FALSE;
++ }
++
++ bd_utils_report_progress (progress_id, 50, "Starting to wipe the newly created integrity device");
++ ret = crypt_wipe (cd, tmp_path, CRYPT_WIPE_ZERO, 0, 0, 1048576,
++ 0, &_wipe_progress, &progress_id);
++ bd_utils_report_progress (progress_id, 100, "Wipe finished");
++ if (ret != 0) {
++ g_set_error (error, BD_CRYPTO_ERROR, BD_CRYPTO_ERROR_DEVICE,
++ "Failed to wipe the newly created integrity device: %s",
++ strerror_l (-ret, c_locale));
++
++ ret = crypt_deactivate (cd, tmp_name);
++ if (ret != 0)
++ g_warning ("Failed to deactivate temporary device %s", tmp_name);
++
++ crypt_free (cd);
++ bd_utils_report_finished (progress_id, (*error)->message);
++ return FALSE;
++ }
++
++ ret = crypt_deactivate (cd, tmp_name);
++ if (ret != 0)
++ g_warning ("Failed to deactivate temporary device %s", tmp_name);
++
++ } else
++ bd_utils_report_finished (progress_id, "Completed");
++
++ crypt_free (cd);
++
++ return TRUE;
++}
++
++/**
++ * bd_crypto_integrity_open:
++ * @device: integrity device to open
++ * @name: name for the opened @device
++ * @algorithm: (allow-none): integrity algorithm specification (e.g. "crc32c" or "sha256") or %NULL to use the default
++ * @key_data: (allow-none) (array length=key_size): integrity key or %NULL if not needed
++ * @key_size: size the integrity key and @key_data
++ * @flags: flags for the integrity device activation
++ * @extra: (allow-none): extra arguments for integrity open
++ * @error: (out): place to store error (if any)
++ *
++ * Returns: whether the @device was successfully opened or not
++ *
++ * Tech category: %BD_CRYPTO_TECH_INTEGRITY-%BD_CRYPTO_TECH_MODE_OPEN_CLOSE
++ */
++gboolean bd_crypto_integrity_open (const gchar *device, const gchar *name, const gchar *algorithm, const guint8* key_data, gsize key_size, BDCryptoIntegrityOpenFlags flags, BDCryptoIntegrityExtra *extra, GError **error) {
++ struct crypt_device *cd = NULL;
++ gint ret = 0;
++ guint64 progress_id = 0;
++ gchar *msg = NULL;
++ struct crypt_params_integrity params = ZERO_INIT;
++
++ params.integrity = algorithm;
++ params.integrity_key_size = key_size;
++
++ if (extra) {
++ params.sector_size = extra->sector_size;
++ params.journal_size = extra->journal_size;
++ params.journal_watermark = extra->journal_watermark;
++ params.journal_commit_time = extra->journal_commit_time;
++ params.interleave_sectors = extra->interleave_sectors;
++ params.tag_size = extra->tag_size;
++ params.buffer_sectors = extra->buffer_sectors;
++ }
++
++ msg = g_strdup_printf ("Started opening '%s' integrity device", device);
++ progress_id = bd_utils_report_started (msg);
++ g_free (msg);
++
++ ret = crypt_init (&cd, device);
++ if (ret != 0) {
++ g_set_error (error, BD_CRYPTO_ERROR, BD_CRYPTO_ERROR_DEVICE,
++ "Failed to initialize device: %s", strerror_l (-ret, c_locale));
++ bd_utils_report_finished (progress_id, (*error)->message);
++ return FALSE;
++ }
++
++ ret = crypt_load (cd, CRYPT_INTEGRITY, &params);
++ if (ret != 0) {
++ g_set_error (error, BD_CRYPTO_ERROR, BD_CRYPTO_ERROR_DEVICE,
++ "Failed to load device's parameters: %s", strerror_l (-ret, c_locale));
++ crypt_free (cd);
++ bd_utils_report_finished (progress_id, (*error)->message);
++ return FALSE;
++ }
++
++ ret = crypt_activate_by_volume_key (cd, name, (const char *) key_data, key_size, flags);
++ if (ret < 0) {
++ g_set_error (error, BD_CRYPTO_ERROR, BD_CRYPTO_ERROR_DEVICE,
++ "Failed to activate device: %s", strerror_l (-ret, c_locale));
++
++ crypt_free (cd);
++ bd_utils_report_finished (progress_id, (*error)->message);
++ return FALSE;
++ }
++
++ crypt_free (cd);
++ bd_utils_report_finished (progress_id, "Completed");
++ return TRUE;
++}
++
++/**
++ * bd_crypto_integrity_close:
++ * @integrity_device: integrity device to close
++ * @error: (out): place to store error (if any)
++ *
++ * Returns: whether the given @integrity_device was successfully closed or not
++ *
++ * Tech category: %BD_CRYPTO_TECH_INTEGRITY-%BD_CRYPTO_TECH_MODE_OPEN_CLOSE
++ */
++gboolean bd_crypto_integrity_close (const gchar *integrity_device, GError **error) {
++ return _crypto_close (integrity_device, "integrity", error);
++}
++
+ /**
+ * bd_crypto_device_seems_encrypted:
+ * @device: the queried device
+@@ -2471,7 +2722,7 @@ gboolean bd_crypto_escrow_device (const gchar *device, const gchar *passphrase,
+ *
+ * Tech category: %BD_CRYPTO_TECH_BITLK-%BD_CRYPTO_TECH_MODE_OPEN_CLOSE
+ */
+-#ifndef LIBCRYPTSETUP_BITLK
++#ifndef LIBCRYPTSETUP_23
+ gboolean bd_crypto_bitlk_open (const gchar *device UNUSED, const gchar *name UNUSED, const guint8* pass_data UNUSED, gsize data_len UNUSED, gboolean read_only UNUSED, GError **error) {
+ /* this will return FALSE and set error, because BITLK technology is not available */
+ return bd_crypto_is_tech_avail (BD_CRYPTO_TECH_BITLK, BD_CRYPTO_TECH_MODE_OPEN_CLOSE, error);
+@@ -2541,7 +2792,7 @@ gboolean bd_crypto_bitlk_open (const gchar *device, const gchar *name, const gui
+ *
+ * Tech category: %BD_CRYPTO_TECH_BITLK-%BD_CRYPTO_TECH_MODE_OPEN_CLOSE
+ */
+-#ifndef LIBCRYPTSETUP_BITLK
++#ifndef LIBCRYPTSETUP_23
+ gboolean bd_crypto_bitlk_close (const gchar *bitlk_device UNUSED, GError **error) {
+ /* this will return FALSE and set error, because BITLK technology is not available */
+ return bd_crypto_is_tech_avail (BD_CRYPTO_TECH_BITLK, BD_CRYPTO_TECH_MODE_OPEN_CLOSE, error);
+diff --git a/src/plugins/crypto.h b/src/plugins/crypto.h
+index 1c8f47ea..6c1d40dd 100644
+--- a/src/plugins/crypto.h
++++ b/src/plugins/crypto.h
+@@ -122,6 +122,43 @@ void bd_crypto_luks_extra_free (BDCryptoLUKSExtra *extra);
+ BDCryptoLUKSExtra* bd_crypto_luks_extra_copy (BDCryptoLUKSExtra *extra);
+ BDCryptoLUKSExtra* bd_crypto_luks_extra_new (guint64 data_alignment, const gchar *data_device, const gchar *integrity, guint64 sector_size, const gchar *label, const gchar *subsystem, BDCryptoLUKSPBKDF *pbkdf);
+
++/**
++ * BDCryptoIntegrityExtra:
++ * @sector_size: integrity sector size
++ * @journal_size: size of journal in bytes
++ * @journal_watermark: journal flush watermark in percents; in bitmap mode sectors-per-bit
++ * @journal_commit_time: journal commit time (or bitmap flush time) in ms
++ * @interleave_sectors: number of interleave sectors (power of two)
++ * @tag_size: tag size per-sector in bytes
++ * @buffer_sectors: number of sectors in one buffer
++ */
++typedef struct BDCryptoIntegrityExtra {
++ guint32 sector_size;
++ guint64 journal_size;
++ guint journal_watermark;
++ guint journal_commit_time;
++ guint32 interleave_sectors;
++ guint32 tag_size;
++ guint32 buffer_sectors;
++} BDCryptoIntegrityExtra;
++
++void bd_crypto_integrity_extra_free (BDCryptoIntegrityExtra *extra);
++BDCryptoIntegrityExtra* bd_crypto_integrity_extra_copy (BDCryptoIntegrityExtra *extra);
++BDCryptoIntegrityExtra* bd_crypto_integrity_extra_new (guint64 sector_size, guint64 journal_size, guint journal_watermark, guint journal_commit_time, guint64 interleave_sectors, guint64 tag_size, guint64 buffer_sectors);
++
++typedef enum {
++ BD_CRYPTO_INTEGRITY_OPEN_NO_JOURNAL = CRYPT_ACTIVATE_NO_JOURNAL,
++ BD_CRYPTO_INTEGRITY_OPEN_RECOVERY = CRYPT_ACTIVATE_RECOVERY,
++#ifdef CRYPT_ACTIVATE_NO_JOURNAL_BITMAP
++ BD_CRYPTO_INTEGRITY_OPEN_NO_JOURNAL_BITMAP = CRYPT_ACTIVATE_NO_JOURNAL_BITMAP,
++#endif
++ BD_CRYPTO_INTEGRITY_OPEN_RECALCULATE = CRYPT_ACTIVATE_RECALCULATE,
++#ifdef CRYPT_ACTIVATE_RECALCULATE_RESET
++ BD_CRYPTO_INTEGRITY_OPEN_RECALCULATE_RESET = CRYPT_ACTIVATE_RECALCULATE_RESET,
++#endif
++ BD_CRYPTO_INTEGRITY_OPEN_ALLOW_DISCARDS = CRYPT_ACTIVATE_ALLOW_DISCARDS,
++} BDCryptoIntegrityOpenFlags;
++
+ /**
+ * BDCryptoLUKSInfo:
+ * @version: LUKS version
+@@ -215,6 +252,10 @@ gboolean bd_crypto_luks_header_restore (const gchar *device, const gchar *backup
+ BDCryptoLUKSInfo* bd_crypto_luks_info (const gchar *luks_device, GError **error);
+ BDCryptoIntegrityInfo* bd_crypto_integrity_info (const gchar *device, GError **error);
+
++gboolean bd_crypto_integrity_format (const gchar *device, const gchar *algorithm, gboolean wipe, const guint8* key_data, gsize key_size, BDCryptoIntegrityExtra *extra, GError **error);
++gboolean bd_crypto_integrity_open (const gchar *device, const gchar *name, const gchar *algorithm, const guint8* key_data, gsize key_size, BDCryptoIntegrityOpenFlags flags, BDCryptoIntegrityExtra *extra, GError **error);
++gboolean bd_crypto_integrity_close (const gchar *integrity_device, GError **error);
++
+ gboolean bd_crypto_device_seems_encrypted (const gchar *device, GError **error);
+ gboolean bd_crypto_tc_open (const gchar *device, const gchar *name, const guint8* pass_data, gsize data_len, gboolean read_only, GError **error);
+ gboolean bd_crypto_tc_open_full (const gchar *device, const gchar *name, const guint8* pass_data, gsize data_len, const gchar **keyfiles, gboolean hidden, gboolean system, gboolean veracrypt, guint32 veracrypt_pim, gboolean read_only, GError **error);
+diff --git a/src/python/gi/overrides/BlockDev.py b/src/python/gi/overrides/BlockDev.py
+index 8574ab04..8bd03cf8 100644
+--- a/src/python/gi/overrides/BlockDev.py
++++ b/src/python/gi/overrides/BlockDev.py
+@@ -276,6 +276,30 @@ def crypto_bitlk_open(device, name, passphrase, read_only=False):
+ __all__.append("crypto_bitlk_open")
+
+
++class CryptoIntegrityExtra(BlockDev.CryptoIntegrityExtra):
++ def __new__(cls, sector_size=0, journal_size=0, journal_watermark=0, journal_commit_time=0, interleave_sectors=0, tag_size=0, buffer_sectors=0):
++ ret = BlockDev.CryptoIntegrityExtra.new(sector_size, journal_size, journal_watermark, journal_commit_time, interleave_sectors, tag_size, buffer_sectors)
++ ret.__class__ = cls
++ return ret
++ def __init__(self, *args, **kwargs): # pylint: disable=unused-argument
++ super(CryptoIntegrityExtra, self).__init__() #pylint: disable=bad-super-call
++CryptoIntegrityExtra = override(CryptoIntegrityExtra)
++__all__.append("CryptoIntegrityExtra")
++
++
++_crypto_integrity_format = BlockDev.crypto_integrity_format
++@override(BlockDev.crypto_integrity_format)
++def crypto_integrity_format(device, algorithm=None, wipe=True, key_data=None, extra=None):
++ return _crypto_integrity_format(device, algorithm, wipe, key_data, extra)
++__all__.append("crypto_integrity_format")
++
++_crypto_integrity_open = BlockDev.crypto_integrity_open
++@override(BlockDev.crypto_integrity_open)
++def crypto_integrity_open(device, name, algorithm, key_data=None, flags=0, extra=None):
++ return _crypto_integrity_open(device, name, algorithm, key_data, flags, extra)
++__all__.append("crypto_integrity_open")
++
++
+ _dm_create_linear = BlockDev.dm_create_linear
+ @override(BlockDev.dm_create_linear)
+ def dm_create_linear(map_name, device, length, uuid=None):
+diff --git a/tests/crypto_test.py b/tests/crypto_test.py
+index 5e02c00d..a8fc8579 100644
+--- a/tests/crypto_test.py
++++ b/tests/crypto_test.py
+@@ -2,6 +2,7 @@ import unittest
+ import os
+ import tempfile
+ import overrides_hack
++import secrets
+ import shutil
+ import subprocess
+ import six
+@@ -34,6 +35,8 @@ class CryptoTestCase(unittest.TestCase):
+
+ requested_plugins = BlockDev.plugin_specs_from_names(("crypto", "loop"))
+
++ _dm_name = "libblockdevTestLUKS"
++
+ @classmethod
+ def setUpClass(cls):
+ unittest.TestCase.setUpClass()
+@@ -64,7 +67,7 @@ class CryptoTestCase(unittest.TestCase):
+
+ def _clean_up(self):
+ try:
+- BlockDev.crypto_luks_close("libblockdevTestLUKS")
++ BlockDev.crypto_luks_close(self._dm_name)
+ except:
+ pass
+
+@@ -1029,7 +1032,7 @@ class CryptoTestLuksSectorSize(CryptoTestCase):
+ self.assertTrue(succ)
+
+
+-class CryptoTestIntegrity(CryptoTestCase):
++class CryptoTestLUKS2Integrity(CryptoTestCase):
+ @tag_test(TestTags.SLOW)
+ @unittest.skipUnless(HAVE_LUKS2, "LUKS 2 not supported")
+ def test_luks2_integrity(self):
+@@ -1216,3 +1219,92 @@ class CryptoTestBitlk(CryptoTestCase):
+ succ = BlockDev.crypto_bitlk_close("libblockdevTestBitlk")
+ self.assertTrue(succ)
+ self.assertFalse(os.path.exists("/dev/mapper/libblockdevTestBitlk"))
++
++
++class CryptoTestIntegrity(CryptoTestCase):
++
++ _dm_name = "libblockdevTestIntegrity"
++
++ @unittest.skipUnless(HAVE_LUKS2, "Integrity not supported")
++ def test_integrity(self):
++ # basic format+open+close test
++ succ = BlockDev.crypto_integrity_format(self.loop_dev, "sha256", False)
++ self.assertTrue(succ)
++
++ succ = BlockDev.crypto_integrity_open(self.loop_dev, self._dm_name, "sha256")
++ self.assertTrue(succ)
++ self.assertTrue(os.path.exists("/dev/mapper/%s" % self._dm_name))
++
++ info = BlockDev.crypto_integrity_info(self._dm_name)
++ self.assertEqual(info.algorithm, "sha256")
++
++ succ = BlockDev.crypto_integrity_close(self._dm_name)
++ self.assertTrue(succ)
++ self.assertFalse(os.path.exists("/dev/mapper/%s" % self._dm_name))
++
++ # same now with a keyed algorithm
++ key = list(secrets.token_bytes(64))
++
++ succ = BlockDev.crypto_integrity_format(self.loop_dev, "hmac(sha256)", False, key)
++ self.assertTrue(succ)
++
++ succ = BlockDev.crypto_integrity_open(self.loop_dev, self._dm_name, "hmac(sha256)", key)
++ self.assertTrue(succ)
++ self.assertTrue(os.path.exists("/dev/mapper/%s" % self._dm_name))
++
++ info = BlockDev.crypto_integrity_info(self._dm_name)
++ self.assertEqual(info.algorithm, "hmac(sha256)")
++
++ succ = BlockDev.crypto_integrity_close(self._dm_name)
++ self.assertTrue(succ)
++ self.assertFalse(os.path.exists("/dev/mapper/%s" % self._dm_name))
++
++ # same with some custom parameters
++ extra = BlockDev.CryptoIntegrityExtra(sector_size=4096, interleave_sectors=65536)
++ succ = BlockDev.crypto_integrity_format(self.loop_dev, "crc32c", wipe=False, extra=extra)
++ self.assertTrue(succ)
++
++ succ = BlockDev.crypto_integrity_open(self.loop_dev, self._dm_name, "crc32c")
++ self.assertTrue(succ)
++ self.assertTrue(os.path.exists("/dev/mapper/%s" % self._dm_name))
++
++ info = BlockDev.crypto_integrity_info(self._dm_name)
++ self.assertEqual(info.algorithm, "crc32c")
++ self.assertEqual(info.sector_size, 4096)
++ self.assertEqual(info.interleave_sectors, 65536)
++
++ succ = BlockDev.crypto_integrity_close(self._dm_name)
++ self.assertTrue(succ)
++ self.assertFalse(os.path.exists("/dev/mapper/%s" % self._dm_name))
++
++ @tag_test(TestTags.SLOW)
++ @unittest.skipUnless(HAVE_LUKS2, "Integrity not supported")
++ def test_integrity_wipe(self):
++ # also check that wipe progress reporting works
++ progress_log = []
++
++ def _my_progress_func(_task, _status, completion, msg):
++ progress_log.append((completion, msg))
++
++ succ = BlockDev.utils_init_prog_reporting(_my_progress_func)
++ self.assertTrue(succ)
++ self.addCleanup(BlockDev.utils_init_prog_reporting, None)
++
++ succ = BlockDev.crypto_integrity_format(self.loop_dev, "sha256", True)
++ self.assertTrue(succ)
++
++ # at least one message "Integrity device wipe in progress" should be logged
++ self.assertTrue(any(prog[1] == "Integrity device wipe in progress" for prog in progress_log))
++
++ succ = BlockDev.crypto_integrity_open(self.loop_dev, self._dm_name, "sha256")
++ self.assertTrue(succ)
++ self.assertTrue(os.path.exists("/dev/mapper/%s" % self._dm_name))
++
++ # check the devices was wiped and the checksums recalculated
++ # (mkfs reads some blocks first so without checksums it would fail)
++ ret, _out, err = run_command("mkfs.ext2 /dev/mapper/%s " % self._dm_name)
++ self.assertEqual(ret, 0, msg="Failed to create ext2 filesystem on integrity: %s" % err)
++
++ succ = BlockDev.crypto_integrity_close(self._dm_name)
++ self.assertTrue(succ)
++ self.assertFalse(os.path.exists("/dev/mapper/%s" % self._dm_name))
+--
+2.37.3
+
+
+From ad4ac36520ec96af2a7b043189bbdf18cc3cffb9 Mon Sep 17 00:00:00 2001
+From: Vojtech Trefny <vtrefny@redhat.com>
+Date: Thu, 30 Sep 2021 16:01:40 +0200
+Subject: [PATCH 2/3] Create smaller test images for integrity tests
+
+We are going to overwrite the entire device in test_integrity_wipe
+so we need to make sure the sparse actually fits to /tmp which
+can be smaller than 1 GiB.
+---
+ tests/crypto_test.py | 6 ++++--
+ 1 file changed, 4 insertions(+), 2 deletions(-)
+
+diff --git a/tests/crypto_test.py b/tests/crypto_test.py
+index a8fc8579..9758bf81 100644
+--- a/tests/crypto_test.py
++++ b/tests/crypto_test.py
+@@ -36,6 +36,7 @@ class CryptoTestCase(unittest.TestCase):
+ requested_plugins = BlockDev.plugin_specs_from_names(("crypto", "loop"))
+
+ _dm_name = "libblockdevTestLUKS"
++ _sparse_size = 1024**3
+
+ @classmethod
+ def setUpClass(cls):
+@@ -49,8 +50,8 @@ class CryptoTestCase(unittest.TestCase):
+
+ def setUp(self):
+ self.addCleanup(self._clean_up)
+- self.dev_file = create_sparse_tempfile("crypto_test", 1024**3)
+- self.dev_file2 = create_sparse_tempfile("crypto_test2", 1024**3)
++ self.dev_file = create_sparse_tempfile("crypto_test", self._sparse_size)
++ self.dev_file2 = create_sparse_tempfile("crypto_test2", self._sparse_size)
+ try:
+ self.loop_dev = create_lio_device(self.dev_file)
+ except RuntimeError as e:
+@@ -1224,6 +1225,7 @@ class CryptoTestBitlk(CryptoTestCase):
+ class CryptoTestIntegrity(CryptoTestCase):
+
+ _dm_name = "libblockdevTestIntegrity"
++ _sparse_size = 100 * 1024**2
+
+ @unittest.skipUnless(HAVE_LUKS2, "Integrity not supported")
+ def test_integrity(self):
+--
+2.37.3
+
+
+From 048a803be5186b30c0f0a7e67020486990ba6b81 Mon Sep 17 00:00:00 2001
+From: Vojtech Trefny <vtrefny@redhat.com>
+Date: Wed, 20 Oct 2021 10:27:41 +0200
+Subject: [PATCH 3/3] crypto: Do not use libcryptsetup flags directly in
+ crypto.h
+
+We can "translate" our flags in the implementation instead to
+avoid including libcryptsetup.h in our header and API files.
+---
+ src/lib/plugin_apis/crypto.api | 17 ++++++-----------
+ src/plugins/crypto.c | 34 +++++++++++++++++++++++++++++++++-
+ src/plugins/crypto.h | 16 ++++++----------
+ tests/crypto_test.py | 14 ++++++++++++++
+ 4 files changed, 59 insertions(+), 22 deletions(-)
+
+diff --git a/src/lib/plugin_apis/crypto.api b/src/lib/plugin_apis/crypto.api
+index 40e32c89..cf87979d 100644
+--- a/src/lib/plugin_apis/crypto.api
++++ b/src/lib/plugin_apis/crypto.api
+@@ -1,6 +1,5 @@
+ #include <glib.h>
+ #include <blockdev/utils.h>
+-#include <libcryptsetup.h>
+
+ #define BD_CRYPTO_LUKS_METADATA_SIZE G_GUINT64_CONSTANT (2097152ULL) // 2 MiB
+
+@@ -343,16 +342,12 @@ GType bd_crypto_integrity_extra_get_type () {
+ }
+
+ typedef enum {
+- BD_CRYPTO_INTEGRITY_OPEN_NO_JOURNAL = CRYPT_ACTIVATE_NO_JOURNAL,
+- BD_CRYPTO_INTEGRITY_OPEN_RECOVERY = CRYPT_ACTIVATE_RECOVERY,
+-#ifdef CRYPT_ACTIVATE_NO_JOURNAL_BITMAP
+- BD_CRYPTO_INTEGRITY_OPEN_NO_JOURNAL_BITMAP = CRYPT_ACTIVATE_NO_JOURNAL_BITMAP,
+-#endif
+- BD_CRYPTO_INTEGRITY_OPEN_RECALCULATE = CRYPT_ACTIVATE_RECALCULATE,
+-#ifdef CRYPT_ACTIVATE_RECALCULATE_RESET
+- BD_CRYPTO_INTEGRITY_OPEN_RECALCULATE_RESET = CRYPT_ACTIVATE_RECALCULATE_RESET,
+-#endif
+- BD_CRYPTO_INTEGRITY_OPEN_ALLOW_DISCARDS = CRYPT_ACTIVATE_ALLOW_DISCARDS,
++ BD_CRYPTO_INTEGRITY_OPEN_NO_JOURNAL = 1 << 0,
++ BD_CRYPTO_INTEGRITY_OPEN_RECOVERY = 1 << 1,
++ BD_CRYPTO_INTEGRITY_OPEN_NO_JOURNAL_BITMAP = 1 << 2,
++ BD_CRYPTO_INTEGRITY_OPEN_RECALCULATE = 1 << 3,
++ BD_CRYPTO_INTEGRITY_OPEN_RECALCULATE_RESET = 1 << 4,
++ BD_CRYPTO_INTEGRITY_OPEN_ALLOW_DISCARDS = 1 << 5,
+ } BDCryptoIntegrityOpenFlags;
+
+ #define BD_CRYPTO_TYPE_LUKS_INFO (bd_crypto_luks_info_get_type ())
+diff --git a/src/plugins/crypto.c b/src/plugins/crypto.c
+index 8549cf23..35c38410 100644
+--- a/src/plugins/crypto.c
++++ b/src/plugins/crypto.c
+@@ -2223,6 +2223,7 @@ gboolean bd_crypto_integrity_open (const gchar *device, const gchar *name, const
+ guint64 progress_id = 0;
+ gchar *msg = NULL;
+ struct crypt_params_integrity params = ZERO_INIT;
++ guint32 activate_flags = 0;
+
+ params.integrity = algorithm;
+ params.integrity_key_size = key_size;
+@@ -2237,6 +2238,37 @@ gboolean bd_crypto_integrity_open (const gchar *device, const gchar *name, const
+ params.buffer_sectors = extra->buffer_sectors;
+ }
+
++
++ if (flags & BD_CRYPTO_INTEGRITY_OPEN_NO_JOURNAL)
++ activate_flags |= CRYPT_ACTIVATE_NO_JOURNAL;
++ if (flags & BD_CRYPTO_INTEGRITY_OPEN_RECOVERY)
++ activate_flags |= CRYPT_ACTIVATE_RECOVERY;
++ if (flags & BD_CRYPTO_INTEGRITY_OPEN_RECALCULATE)
++ activate_flags |= CRYPT_ACTIVATE_RECALCULATE;
++ if (flags & BD_CRYPTO_INTEGRITY_OPEN_ALLOW_DISCARDS)
++ activate_flags |= CRYPT_ACTIVATE_ALLOW_DISCARDS;
++ if (flags & BD_CRYPTO_INTEGRITY_OPEN_NO_JOURNAL_BITMAP) {
++#ifndef CRYPT_ACTIVATE_NO_JOURNAL_BITMAP
++ g_set_error (error, BD_CRYPTO_ERROR, BD_CRYPTO_ERROR_TECH_UNAVAIL,
++ "Cannot activate %s with bitmap, installed version of cryptsetup doesn't support this option.", device);
++ bd_utils_report_finished (progress_id, (*error)->message);
++ return FALSE;
++#else
++ activate_flags |= CRYPT_ACTIVATE_NO_JOURNAL_BITMAP;
++#endif
++ }
++
++ if (flags & BD_CRYPTO_INTEGRITY_OPEN_RECALCULATE_RESET) {
++#ifndef CRYPT_ACTIVATE_RECALCULATE_RESET
++ g_set_error (error, BD_CRYPTO_ERROR, BD_CRYPTO_ERROR_TECH_UNAVAIL,
++ "Cannot reset integrity recalculation while activating %s, installed version of cryptsetup doesn't support this option.", device);
++ bd_utils_report_finished (progress_id, (*error)->message);
++ return FALSE;
++#else
++ activate_flags |= CRYPT_ACTIVATE_RECALCULATE_RESET;
++#endif
++ }
++
+ msg = g_strdup_printf ("Started opening '%s' integrity device", device);
+ progress_id = bd_utils_report_started (msg);
+ g_free (msg);
+@@ -2258,7 +2290,7 @@ gboolean bd_crypto_integrity_open (const gchar *device, const gchar *name, const
+ return FALSE;
+ }
+
+- ret = crypt_activate_by_volume_key (cd, name, (const char *) key_data, key_size, flags);
++ ret = crypt_activate_by_volume_key (cd, name, (const char *) key_data, key_size, activate_flags);
+ if (ret < 0) {
+ g_set_error (error, BD_CRYPTO_ERROR, BD_CRYPTO_ERROR_DEVICE,
+ "Failed to activate device: %s", strerror_l (-ret, c_locale));
+diff --git a/src/plugins/crypto.h b/src/plugins/crypto.h
+index 6c1d40dd..536accf9 100644
+--- a/src/plugins/crypto.h
++++ b/src/plugins/crypto.h
+@@ -147,16 +147,12 @@ BDCryptoIntegrityExtra* bd_crypto_integrity_extra_copy (BDCryptoIntegrityExtra *
+ BDCryptoIntegrityExtra* bd_crypto_integrity_extra_new (guint64 sector_size, guint64 journal_size, guint journal_watermark, guint journal_commit_time, guint64 interleave_sectors, guint64 tag_size, guint64 buffer_sectors);
+
+ typedef enum {
+- BD_CRYPTO_INTEGRITY_OPEN_NO_JOURNAL = CRYPT_ACTIVATE_NO_JOURNAL,
+- BD_CRYPTO_INTEGRITY_OPEN_RECOVERY = CRYPT_ACTIVATE_RECOVERY,
+-#ifdef CRYPT_ACTIVATE_NO_JOURNAL_BITMAP
+- BD_CRYPTO_INTEGRITY_OPEN_NO_JOURNAL_BITMAP = CRYPT_ACTIVATE_NO_JOURNAL_BITMAP,
+-#endif
+- BD_CRYPTO_INTEGRITY_OPEN_RECALCULATE = CRYPT_ACTIVATE_RECALCULATE,
+-#ifdef CRYPT_ACTIVATE_RECALCULATE_RESET
+- BD_CRYPTO_INTEGRITY_OPEN_RECALCULATE_RESET = CRYPT_ACTIVATE_RECALCULATE_RESET,
+-#endif
+- BD_CRYPTO_INTEGRITY_OPEN_ALLOW_DISCARDS = CRYPT_ACTIVATE_ALLOW_DISCARDS,
++ BD_CRYPTO_INTEGRITY_OPEN_NO_JOURNAL = 1 << 0,
++ BD_CRYPTO_INTEGRITY_OPEN_RECOVERY = 1 << 1,
++ BD_CRYPTO_INTEGRITY_OPEN_NO_JOURNAL_BITMAP = 1 << 2,
++ BD_CRYPTO_INTEGRITY_OPEN_RECALCULATE = 1 << 3,
++ BD_CRYPTO_INTEGRITY_OPEN_RECALCULATE_RESET = 1 << 4,
++ BD_CRYPTO_INTEGRITY_OPEN_ALLOW_DISCARDS = 1 << 5,
+ } BDCryptoIntegrityOpenFlags;
+
+ /**
+diff --git a/tests/crypto_test.py b/tests/crypto_test.py
+index 9758bf81..94b89131 100644
+--- a/tests/crypto_test.py
++++ b/tests/crypto_test.py
+@@ -1279,6 +1279,20 @@ class CryptoTestIntegrity(CryptoTestCase):
+ self.assertTrue(succ)
+ self.assertFalse(os.path.exists("/dev/mapper/%s" % self._dm_name))
+
++ # open with flags
++ succ = BlockDev.crypto_integrity_open(self.loop_dev, self._dm_name, "crc32c",
++ flags=BlockDev.CryptoIntegrityOpenFlags.ALLOW_DISCARDS)
++ self.assertTrue(succ)
++ self.assertTrue(os.path.exists("/dev/mapper/%s" % self._dm_name))
++
++ # check that discard is enabled for the mapped device
++ _ret, out, _err = run_command("dmsetup table %s" % self._dm_name)
++ self.assertIn("allow_discards", out)
++
++ succ = BlockDev.crypto_integrity_close(self._dm_name)
++ self.assertTrue(succ)
++ self.assertFalse(os.path.exists("/dev/mapper/%s" % self._dm_name))
++
+ @tag_test(TestTags.SLOW)
+ @unittest.skipUnless(HAVE_LUKS2, "Integrity not supported")
+ def test_integrity_wipe(self):
+--
+2.37.3
+
diff --git a/0003-NVMe-plugin-backport.patch b/0003-NVMe-plugin-backport.patch
new file mode 100644
index 0000000..083baa7
--- /dev/null
+++ b/0003-NVMe-plugin-backport.patch
@@ -0,0 +1,6467 @@
+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
+
diff --git a/0004-Fix-double-free-in-write_escrow_data_file.patch b/0004-Fix-double-free-in-write_escrow_data_file.patch
new file mode 100644
index 0000000..4a5ee67
--- /dev/null
+++ b/0004-Fix-double-free-in-write_escrow_data_file.patch
@@ -0,0 +1,59 @@
+From 7a0e344d0642f76992c943158621d8ee7e5caea3 Mon Sep 17 00:00:00 2001
+From: Vojtech Trefny <vtrefny@redhat.com>
+Date: Tue, 15 Nov 2022 13:21:25 +0100
+Subject: [PATCH 1/2] crypto: Fix GError overwrite from libvolume_key
+
+---
+ src/plugins/crypto.c | 7 ++++---
+ 1 file changed, 4 insertions(+), 3 deletions(-)
+
+diff --git a/src/plugins/crypto.c b/src/plugins/crypto.c
+index 35c38410..9064c8e3 100644
+--- a/src/plugins/crypto.c
++++ b/src/plugins/crypto.c
+@@ -2552,13 +2552,14 @@ static gboolean write_escrow_data_file (struct libvk_volume *volume, struct libv
+ GIOChannel *out_file = NULL;
+ GIOStatus status = G_IO_STATUS_ERROR;
+ gsize bytes_written = 0;
++ GError *l_error = NULL;
+
+ packet_data = libvk_volume_create_packet_asymmetric_with_format (volume, &packet_data_size, secret_type, cert,
+- ui, LIBVK_PACKET_FORMAT_ASYMMETRIC_WRAP_SECRET_ONLY, error);
+-
++ ui, LIBVK_PACKET_FORMAT_ASYMMETRIC_WRAP_SECRET_ONLY, &l_error);
+ if (!packet_data) {
+ g_set_error (error, BD_CRYPTO_ERROR, BD_CRYPTO_ERROR_ESCROW_FAILED,
+- "Failed to get escrow data");
++ "Failed to get escrow data: %s", l_error->message);
++ g_clear_error (&l_error);
+ libvk_volume_free (volume);
+ return FALSE;
+ }
+--
+2.38.1
+
+
+From 25bf34c4c03e37eb3782dfccf459b9a3f795ddb3 Mon Sep 17 00:00:00 2001
+From: Vojtech Trefny <vtrefny@redhat.com>
+Date: Wed, 16 Nov 2022 10:26:06 +0100
+Subject: [PATCH 2/2] crypto: Fix double free in write_escrow_data_file
+
+---
+ src/plugins/crypto.c | 1 -
+ 1 file changed, 1 deletion(-)
+
+diff --git a/src/plugins/crypto.c b/src/plugins/crypto.c
+index 9064c8e3..2086209e 100644
+--- a/src/plugins/crypto.c
++++ b/src/plugins/crypto.c
+@@ -2560,7 +2560,6 @@ static gboolean write_escrow_data_file (struct libvk_volume *volume, struct libv
+ g_set_error (error, BD_CRYPTO_ERROR, BD_CRYPTO_ERROR_ESCROW_FAILED,
+ "Failed to get escrow data: %s", l_error->message);
+ g_clear_error (&l_error);
+- libvk_volume_free (volume);
+ return FALSE;
+ }
+
+--
+2.38.1
+
diff --git a/0005-nvme-Fix-namespace-identifiers.patch b/0005-nvme-Fix-namespace-identifiers.patch
new file mode 100644
index 0000000..7334ff1
--- /dev/null
+++ b/0005-nvme-Fix-namespace-identifiers.patch
@@ -0,0 +1,506 @@
+From b25fd9caca9b2fb34e5a4d7d4bee0031e4758d0a Mon Sep 17 00:00:00 2001
+From: Tomas Bzatek <tbzatek@redhat.com>
+Date: Thu, 22 Sep 2022 16:31:28 +0200
+Subject: [PATCH 1/5] nvme: Avoid sending NVME_IDENTIFY_CNS_NS_DESC_LIST on
+ older devices
+
+Turned out this Identify feature was introduced only with the NVMe 1.3
+specification. To find out device supported NVMe revision an extra
+Identify Controller call is needed.
+---
+ src/plugins/nvme/nvme-info.c | 14 +++++++++++---
+ 1 file changed, 11 insertions(+), 3 deletions(-)
+
+diff --git a/src/plugins/nvme/nvme-info.c b/src/plugins/nvme/nvme-info.c
+index fdd90459..112b4054 100644
+--- a/src/plugins/nvme/nvme-info.c
++++ b/src/plugins/nvme/nvme-info.c
+@@ -431,7 +431,7 @@ BDNVMEControllerInfo * bd_nvme_get_controller_info (const gchar *device, GError
+ if (fd < 0)
+ return NULL;
+
+- /* send the NVME_IDENTIFY_CNS_NS + NVME_IDENTIFY_CNS_CTRL ioctl */
++ /* send the NVME_IDENTIFY_CNS_CTRL ioctl */
+ ret = nvme_identify_ctrl (fd, &ctrl_id);
+ if (ret != 0) {
+ _nvme_status_to_error (ret, FALSE, error);
+@@ -539,9 +539,11 @@ BDNVMEControllerInfo * bd_nvme_get_controller_info (const gchar *device, GError
+ */
+ BDNVMENamespaceInfo *bd_nvme_get_namespace_info (const gchar *device, GError **error) {
+ int ret;
+- int ret_desc;
++ int ret_ctrl;
++ int ret_desc = -1;
+ int fd;
+ __u32 nsid = 0;
++ struct nvme_id_ctrl ctrl_id = ZERO_INIT;
+ struct nvme_id_ns ns_info = ZERO_INIT;
+ uint8_t desc[NVME_IDENTIFY_DATA_SIZE] = ZERO_INIT;
+ guint8 flbas;
+@@ -565,7 +567,6 @@ BDNVMENamespaceInfo *bd_nvme_get_namespace_info (const gchar *device, GError **e
+ }
+
+ /* 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);
+@@ -573,6 +574,13 @@ BDNVMENamespaceInfo *bd_nvme_get_namespace_info (const gchar *device, GError **e
+ close (fd);
+ return NULL;
+ }
++
++ /* send the NVME_IDENTIFY_CNS_CTRL ioctl */
++ ret_ctrl = nvme_identify_ctrl (fd, &ctrl_id);
++
++ /* send the NVME_IDENTIFY_CNS_NS_DESC_LIST ioctl, NVMe 1.3 */
++ if (ret_ctrl == 0 && GUINT32_FROM_LE (ctrl_id.ver) >= 0x10300)
++ ret_desc = nvme_identify_ns_descs (fd, nsid, (struct nvme_ns_id_desc *) &desc);
+ close (fd);
+
+ info = g_new0 (BDNVMENamespaceInfo, 1);
+--
+2.39.0
+
+
+From e6f7d0c4562623b03df96dc6b89ab00d8e4d6b90 Mon Sep 17 00:00:00 2001
+From: Tomas Bzatek <tbzatek@redhat.com>
+Date: Thu, 22 Sep 2022 16:56:26 +0200
+Subject: [PATCH 2/5] nvme: Add BD_NVME_NS_FEAT_ROTATIONAL
+
+A NVMe 2.0 feature indicating rotational medium on a namespace level.
+Further information can be found in the Rotational Media Information Log
+page (Log Identifier 16h) that is not implemented in libblockdev yet.
+---
+ src/lib/plugin_apis/nvme.api | 2 ++
+ src/plugins/nvme/nvme-info.c | 10 ++++++++++
+ src/plugins/nvme/nvme.h | 2 ++
+ tests/nvme_test.py | 1 +
+ 4 files changed, 15 insertions(+)
+
+diff --git a/src/lib/plugin_apis/nvme.api b/src/lib/plugin_apis/nvme.api
+index 79247a01..7bc2cf9e 100644
+--- a/src/lib/plugin_apis/nvme.api
++++ b/src/lib/plugin_apis/nvme.api
+@@ -317,12 +317,14 @@ GType bd_nvme_lba_format_get_type () {
+ * 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.
++ * @BD_NVME_NS_FEAT_ROTATIONAL: indicates a rotational medium.
+ */
+ /* 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,
++ BD_NVME_NS_FEAT_ROTATIONAL = 1 << 3,
+ } BDNVMENamespaceFeature;
+
+ #define BD_NVME_TYPE_NAMESPACE_INFO (bd_nvme_namespace_info_get_type ())
+diff --git a/src/plugins/nvme/nvme-info.c b/src/plugins/nvme/nvme-info.c
+index 112b4054..c574a6f3 100644
+--- a/src/plugins/nvme/nvme-info.c
++++ b/src/plugins/nvme/nvme-info.c
+@@ -541,10 +541,12 @@ BDNVMENamespaceInfo *bd_nvme_get_namespace_info (const gchar *device, GError **e
+ int ret;
+ int ret_ctrl;
+ int ret_desc = -1;
++ int ret_ns_ind = -1;
+ int fd;
+ __u32 nsid = 0;
+ struct nvme_id_ctrl ctrl_id = ZERO_INIT;
+ struct nvme_id_ns ns_info = ZERO_INIT;
++ struct nvme_id_independent_id_ns ns_info_ind = ZERO_INIT;
+ uint8_t desc[NVME_IDENTIFY_DATA_SIZE] = ZERO_INIT;
+ guint8 flbas;
+ guint i;
+@@ -581,6 +583,10 @@ BDNVMENamespaceInfo *bd_nvme_get_namespace_info (const gchar *device, GError **e
+ /* send the NVME_IDENTIFY_CNS_NS_DESC_LIST ioctl, NVMe 1.3 */
+ if (ret_ctrl == 0 && GUINT32_FROM_LE (ctrl_id.ver) >= 0x10300)
+ ret_desc = nvme_identify_ns_descs (fd, nsid, (struct nvme_ns_id_desc *) &desc);
++
++ /* send the NVME_IDENTIFY_CNS_CSI_INDEPENDENT_ID_NS ioctl, NVMe 2.0 */
++ if (ret_ctrl == 0 && GUINT32_FROM_LE (ctrl_id.ver) >= 0x20000)
++ ret_ns_ind = nvme_identify_independent_identify_ns (fd, nsid, &ns_info_ind);
+ close (fd);
+
+ info = g_new0 (BDNVMENamespaceInfo, 1);
+@@ -627,6 +633,10 @@ BDNVMENamespaceInfo *bd_nvme_get_namespace_info (const gchar *device, GError **e
+ }
+ }
+ }
++ if (ret_ns_ind == 0) {
++ if ((ns_info_ind.nsfeat & 1 << 4) == 1 << 4)
++ info->features |= BD_NVME_NS_FEAT_ROTATIONAL;
++ }
+
+ /* translate the LBA Format array */
+ ptr_array = g_ptr_array_new ();
+diff --git a/src/plugins/nvme/nvme.h b/src/plugins/nvme/nvme.h
+index a7d30d79..ad456a82 100644
+--- a/src/plugins/nvme/nvme.h
++++ b/src/plugins/nvme/nvme.h
+@@ -202,11 +202,13 @@ typedef struct BDNVMELBAFormat {
+ * 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.
++ * @BD_NVME_NS_FEAT_ROTATIONAL: indicates a rotational medium.
+ */
+ 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,
++ BD_NVME_NS_FEAT_ROTATIONAL = 1 << 3,
+ } BDNVMENamespaceFeature;
+
+ /**
+diff --git a/tests/nvme_test.py b/tests/nvme_test.py
+index a46f7422..f205e539 100644
+--- a/tests/nvme_test.py
++++ b/tests/nvme_test.py
+@@ -73,6 +73,7 @@ class NVMeTestCase(NVMeTest):
+ self.assertFalse(info.features & BlockDev.NVMENamespaceFeature.THIN)
+ self.assertTrue (info.features & BlockDev.NVMENamespaceFeature.MULTIPATH_SHARED)
+ self.assertFalse(info.features & BlockDev.NVMENamespaceFeature.FORMAT_PROGRESS)
++ self.assertFalse(info.features & BlockDev.NVMENamespaceFeature.ROTATIONAL)
+ self.assertEqual(info.eui64, "0000000000000000")
+ self.assertEqual(info.format_progress_remaining, 0)
+ self.assertEqual(len(info.lba_formats), 1)
+--
+2.39.0
+
+
+From 4ff0df937dcd357623e7b7d960c08c476b1deffb Mon Sep 17 00:00:00 2001
+From: Tomas Bzatek <tbzatek@redhat.com>
+Date: Fri, 9 Dec 2022 16:13:43 +0100
+Subject: [PATCH 3/5] nvme: Fix namespace identifiers
+
+Use Namespace Identification Descriptor list (CNS 03h) data when available
+and NVM Command Set Identify Namespace Data Structure (CNS 00h) as a fallback.
+
+Also, if the CNS 00h EUI64 or NGUID fields equal to zero, return NULL
+instead of zeroes:
+ "If the controller is not able to provide a ... identifier in this field,
+ then this field shall be cleared to 0h."
+---
+ src/plugins/nvme/nvme-info.c | 26 +++++++++++++++++++-------
+ tests/nvme_test.py | 2 +-
+ 2 files changed, 20 insertions(+), 8 deletions(-)
+
+diff --git a/src/plugins/nvme/nvme-info.c b/src/plugins/nvme/nvme-info.c
+index c574a6f3..ac189abe 100644
+--- a/src/plugins/nvme/nvme-info.c
++++ b/src/plugins/nvme/nvme-info.c
+@@ -603,12 +603,7 @@ BDNVMENamespaceInfo *bd_nvme_get_namespace_info (const gchar *device, GError **e
+ 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;
+@@ -620,8 +615,14 @@ BDNVMENamespaceInfo *bd_nvme_get_namespace_info (const gchar *device, GError **e
+
+ switch (d->nidt) {
+ case NVME_NIDT_EUI64:
++ info->eui64 = g_malloc0 (d->nidl * 2 + 1);
++ for (i = 0; i < d->nidl; i++)
++ snprintf (info->eui64 + i * 2, 3, "%02x", d->nid[i]);
++ break;
+ case NVME_NIDT_NGUID:
+- /* already have these from nvme_identify_ns() */
++ info->nguid = g_malloc0 (d->nidl * 2 + 1);
++ for (i = 0; i < d->nidl; i++)
++ snprintf (info->nguid + i * 2, 3, "%02x", d->nid[i]);
+ break;
+ case NVME_NIDT_UUID:
+ uuid_unparse (d->nid, uuid_buf);
+@@ -633,6 +634,17 @@ BDNVMENamespaceInfo *bd_nvme_get_namespace_info (const gchar *device, GError **e
+ }
+ }
+ }
++
++ if (info->nguid == NULL && ns_info.nguid[G_N_ELEMENTS (ns_info.nguid) - 1] > 0) {
++ 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]);
++ }
++ if (info->eui64 == NULL && ns_info.eui64[G_N_ELEMENTS (ns_info.eui64) - 1] > 0) {
++ 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_ns_ind == 0) {
+ if ((ns_info_ind.nsfeat & 1 << 4) == 1 << 4)
+ info->features |= BD_NVME_NS_FEAT_ROTATIONAL;
+diff --git a/tests/nvme_test.py b/tests/nvme_test.py
+index f205e539..a1822be6 100644
+--- a/tests/nvme_test.py
++++ b/tests/nvme_test.py
+@@ -74,7 +74,7 @@ class NVMeTestCase(NVMeTest):
+ self.assertTrue (info.features & BlockDev.NVMENamespaceFeature.MULTIPATH_SHARED)
+ self.assertFalse(info.features & BlockDev.NVMENamespaceFeature.FORMAT_PROGRESS)
+ self.assertFalse(info.features & BlockDev.NVMENamespaceFeature.ROTATIONAL)
+- self.assertEqual(info.eui64, "0000000000000000")
++ self.assertIsNone(info.eui64)
+ self.assertEqual(info.format_progress_remaining, 0)
+ self.assertEqual(len(info.lba_formats), 1)
+ self.assertGreater(len(info.nguid), 0)
+--
+2.39.0
+
+
+From cdbb9a37a19d3f388910f68c4c384bafae8901ae Mon Sep 17 00:00:00 2001
+From: Tomas Bzatek <tbzatek@redhat.com>
+Date: Wed, 11 Jan 2023 18:19:36 +0100
+Subject: [PATCH 4/5] nvme: Use libnvme-1.2's nvme_uuid_to_string()
+
+This also bumps libnvme dependency to 1.2
+---
+ configure.ac | 5 +----
+ src/plugins/nvme/Makefile.am | 4 ++--
+ src/plugins/nvme/nvme-error.c | 3 ---
+ src/plugins/nvme/nvme-fabrics.c | 1 -
+ src/plugins/nvme/nvme-info.c | 17 ++++++++++-------
+ src/plugins/nvme/nvme-op.c | 1 -
+ src/plugins/nvme/nvme.c | 1 -
+ 7 files changed, 13 insertions(+), 19 deletions(-)
+
+diff --git a/configure.ac b/configure.ac
+index ec789c91..fbd70473 100644
+--- a/configure.ac
++++ b/configure.ac
+@@ -281,10 +281,7 @@ AS_IF([test "x$with_nvdimm" != "xno"],
+ [])
+
+ 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])], [])
+- ],
++ [LIBBLOCKDEV_PKG_CHECK_MODULES([NVME], [libnvme >= 1.2])],
+ [])
+
+ AS_IF([test "x$with_vdo" != "xno"],
+diff --git a/src/plugins/nvme/Makefile.am b/src/plugins/nvme/Makefile.am
+index b4a10ce0..a8a856d4 100644
+--- a/src/plugins/nvme/Makefile.am
++++ b/src/plugins/nvme/Makefile.am
+@@ -2,8 +2,8 @@ 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_CFLAGS = $(GLIB_CFLAGS) $(GIO_CFLAGS) $(NVME_CFLAGS) -Wall -Wextra -Werror
++libbd_nvme_la_LIBADD = ${builddir}/../../utils/libbd_utils.la $(GLIB_LIBS) $(GIO_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)"\"
+
+diff --git a/src/plugins/nvme/nvme-error.c b/src/plugins/nvme/nvme-error.c
+index 86f0d6a3..cb95a46d 100644
+--- a/src/plugins/nvme/nvme-error.c
++++ b/src/plugins/nvme/nvme-error.c
+@@ -28,7 +28,6 @@
+ #include <malloc.h>
+
+ #include <libnvme.h>
+-#include <uuid/uuid.h>
+
+ #include <blockdev/utils.h>
+ #include <check_deps.h>
+@@ -123,7 +122,6 @@ void _nvme_fabrics_errno_to_gerror (int result, int _errno, GError **error)
+ 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;
+@@ -139,7 +137,6 @@ void _nvme_fabrics_errno_to_gerror (int result, int _errno, GError **error)
+ case ENVME_CONNECT_OPNOTSUPP:
+ code = BD_NVME_ERROR_CONNECT_OPNOTSUPP;
+ break;
+-#endif
+ default:
+ code = BD_NVME_ERROR_CONNECT;
+ }
+diff --git a/src/plugins/nvme/nvme-fabrics.c b/src/plugins/nvme/nvme-fabrics.c
+index 20ed57f5..bba7392d 100644
+--- a/src/plugins/nvme/nvme-fabrics.c
++++ b/src/plugins/nvme/nvme-fabrics.c
+@@ -30,7 +30,6 @@
+ #include <glib/gstdio.h>
+
+ #include <libnvme.h>
+-#include <uuid/uuid.h>
+
+ #include <blockdev/utils.h>
+ #include <check_deps.h>
+diff --git a/src/plugins/nvme/nvme-info.c b/src/plugins/nvme/nvme-info.c
+index ac189abe..18719d51 100644
+--- a/src/plugins/nvme/nvme-info.c
++++ b/src/plugins/nvme/nvme-info.c
+@@ -28,7 +28,6 @@
+ #include <malloc.h>
+
+ #include <libnvme.h>
+-#include <uuid/uuid.h>
+
+ #include <blockdev/utils.h>
+ #include <check_deps.h>
+@@ -408,6 +407,14 @@ static gchar *decode_nvme_rev (guint32 ver) {
+ return g_strdup_printf ("%u.%u.%u", mjr, mnr, ter);
+ }
+
++static gchar *_uuid_to_str (unsigned char uuid[NVME_UUID_LEN]) {
++ gchar uuid_buf[NVME_UUID_LEN_STRING] = ZERO_INIT;
++
++ if (nvme_uuid_to_string (uuid, uuid_buf) == 0)
++ return g_strdup (uuid_buf);
++ return NULL;
++}
++
+ /**
+ * bd_nvme_get_controller_info:
+ * @device: a NVMe controller device (e.g. `/dev/nvme0`)
+@@ -461,9 +468,7 @@ BDNVMEControllerInfo * bd_nvme_get_controller_info (const gchar *device, GError
+ 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->fguid = _uuid_to_str (ctrl_id.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));
+@@ -607,7 +612,6 @@ BDNVMENamespaceInfo *bd_nvme_get_namespace_info (const gchar *device, GError **e
+ 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;
+@@ -625,8 +629,7 @@ BDNVMENamespaceInfo *bd_nvme_get_namespace_info (const gchar *device, GError **e
+ snprintf (info->nguid + i * 2, 3, "%02x", d->nid[i]);
+ break;
+ case NVME_NIDT_UUID:
+- uuid_unparse (d->nid, uuid_buf);
+- info->uuid = g_strdup (uuid_buf);
++ info->uuid = _uuid_to_str (d->nid);
+ break;
+ case NVME_NIDT_CSI:
+ /* unused */
+diff --git a/src/plugins/nvme/nvme-op.c b/src/plugins/nvme/nvme-op.c
+index 4568c453..c9e92697 100644
+--- a/src/plugins/nvme/nvme-op.c
++++ b/src/plugins/nvme/nvme-op.c
+@@ -29,7 +29,6 @@
+ #include <linux/fs.h>
+
+ #include <libnvme.h>
+-#include <uuid/uuid.h>
+
+ #include <blockdev/utils.h>
+ #include <check_deps.h>
+diff --git a/src/plugins/nvme/nvme.c b/src/plugins/nvme/nvme.c
+index 00f2f76e..4a32ac4e 100644
+--- a/src/plugins/nvme/nvme.c
++++ b/src/plugins/nvme/nvme.c
+@@ -28,7 +28,6 @@
+ #include <malloc.h>
+
+ #include <libnvme.h>
+-#include <uuid/uuid.h>
+
+ #include <blockdev/utils.h>
+ #include <check_deps.h>
+--
+2.39.0
+
+
+From 64263599ec39b6b0f20d8e16c1169afcf66f5d9a Mon Sep 17 00:00:00 2001
+From: Tomas Bzatek <tbzatek@redhat.com>
+Date: Thu, 12 Jan 2023 16:01:28 +0100
+Subject: [PATCH 5/5] nvme: Fix zeroed struct fields detection
+
+As often stated in the NVMe specification, fields of features that
+are either not implemented or not valid are typically cleared to zero (0h).
+---
+ src/plugins/nvme/nvme-info.c | 20 +++++++++++++++-----
+ tests/nvme_test.py | 2 +-
+ 2 files changed, 16 insertions(+), 6 deletions(-)
+
+diff --git a/src/plugins/nvme/nvme-info.c b/src/plugins/nvme/nvme-info.c
+index 18719d51..85f94a32 100644
+--- a/src/plugins/nvme/nvme-info.c
++++ b/src/plugins/nvme/nvme-info.c
+@@ -415,6 +415,15 @@ static gchar *_uuid_to_str (unsigned char uuid[NVME_UUID_LEN]) {
+ return NULL;
+ }
+
++static gboolean _nvme_a_is_zero (const __u8 a[], int len) {
++ int i;
++
++ for (i = 0; i < len; i++)
++ if (a[i] > 0)
++ return FALSE;
++ return TRUE;
++}
++
+ /**
+ * bd_nvme_get_controller_info:
+ * @device: a NVMe controller device (e.g. `/dev/nvme0`)
+@@ -468,7 +477,8 @@ BDNVMEControllerInfo * bd_nvme_get_controller_info (const gchar *device, GError
+ 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);
+- info->fguid = _uuid_to_str (ctrl_id.fguid);
++ if (!_nvme_a_is_zero (ctrl_id.fguid, sizeof (ctrl_id.fguid)))
++ info->fguid = _uuid_to_str (ctrl_id.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));
+@@ -638,14 +648,14 @@ BDNVMENamespaceInfo *bd_nvme_get_namespace_info (const gchar *device, GError **e
+ }
+ }
+
+- if (info->nguid == NULL && ns_info.nguid[G_N_ELEMENTS (ns_info.nguid) - 1] > 0) {
++ if (info->nguid == NULL && !_nvme_a_is_zero (ns_info.nguid, sizeof (ns_info.nguid))) {
+ info->nguid = g_malloc0 (sizeof (ns_info.nguid) * 2 + 1);
+- for (i = 0; i < G_N_ELEMENTS (ns_info.nguid); i++)
++ for (i = 0; i < sizeof (ns_info.nguid); i++)
+ snprintf (info->nguid + i * 2, 3, "%02x", ns_info.nguid[i]);
+ }
+- if (info->eui64 == NULL && ns_info.eui64[G_N_ELEMENTS (ns_info.eui64) - 1] > 0) {
++ if (info->eui64 == NULL && !_nvme_a_is_zero (ns_info.eui64, sizeof (ns_info.eui64))) {
+ info->eui64 = g_malloc0 (sizeof (ns_info.eui64) * 2 + 1);
+- for (i = 0; i < G_N_ELEMENTS (ns_info.eui64); i++)
++ for (i = 0; i < sizeof (ns_info.eui64); i++)
+ snprintf (info->eui64 + i * 2, 3, "%02x", ns_info.eui64[i]);
+ }
+ if (ret_ns_ind == 0) {
+diff --git a/tests/nvme_test.py b/tests/nvme_test.py
+index a1822be6..a1494d9a 100644
+--- a/tests/nvme_test.py
++++ b/tests/nvme_test.py
+@@ -116,7 +116,7 @@ class NVMeTestCase(NVMeTest):
+ 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.assertIsNone(info.fguid)
+ self.assertEqual(info.pci_vendor_id, 0)
+ self.assertEqual(info.pci_subsys_vendor_id, 0)
+ self.assertIn("Linux", info.model_number)
+--
+2.39.0
+
diff --git a/0006-Allow-resizing-of-inactive-LVs-with-latest-LVM.patch b/0006-Allow-resizing-of-inactive-LVs-with-latest-LVM.patch
new file mode 100644
index 0000000..194ec15
--- /dev/null
+++ b/0006-Allow-resizing-of-inactive-LVs-with-latest-LVM.patch
@@ -0,0 +1,219 @@
+From 08d0ab8b93907ed3e2c7588dcaecb76bc4b26055 Mon Sep 17 00:00:00 2001
+From: Vojtech Trefny <vtrefny@redhat.com>
+Date: Mon, 27 Feb 2023 11:29:29 +0100
+Subject: [PATCH 1/2] Include LVM cli in the LVM DBus plugin dependencies
+
+Strictly speaking the lvm command is not needed by the plugin, but
+the LVM DBus daemon uses it so it must be present on the system
+and we are already calling "lvm segtypes" from the plugin so if
+the command is not available for us (for example not in $PATH) the
+plugin wouldn't load anyway so an extra check isn't going to
+change anything.
+---
+ src/plugins/lvm-dbus.c | 15 ++++++++++++---
+ 1 file changed, 12 insertions(+), 3 deletions(-)
+
+diff --git a/src/plugins/lvm-dbus.c b/src/plugins/lvm-dbus.c
+index d4b542e2..8496a697 100644
+--- a/src/plugins/lvm-dbus.c
++++ b/src/plugins/lvm-dbus.c
+@@ -249,11 +249,14 @@ static volatile guint avail_features = 0;
+ static volatile guint avail_module_deps = 0;
+ static GMutex deps_check_lock;
+
+-#define DEPS_LVMDEVICES 0
++#define DEPS_LVM 0
++#define DEPS_LVM_MASK (1 << DEPS_LVM)
++#define DEPS_LVMDEVICES 1
+ #define DEPS_LVMDEVICES_MASK (1 << DEPS_LVMDEVICES)
+-#define DEPS_LAST 1
++#define DEPS_LAST 2
+
+ static const UtilDep deps[DEPS_LAST] = {
++ {"lvm", LVM_MIN_VERSION, "version", "LVM version:\\s+([\\d\\.]+)"},
+ {"lvmdevices", NULL, NULL, NULL},
+ };
+
+@@ -2121,6 +2124,7 @@ gboolean bd_lvm_lvresize (const gchar *vg_name, const gchar *lv_name, guint64 si
+ GVariantBuilder builder;
+ GVariantType *type = NULL;
+ GVariant *params = NULL;
++ GVariant *extra_params = NULL;
+
+ g_variant_builder_init (&builder, G_VARIANT_TYPE_TUPLE);
+ g_variant_builder_add_value (&builder, g_variant_new ("t", size));
+@@ -2130,7 +2134,12 @@ gboolean bd_lvm_lvresize (const gchar *vg_name, const gchar *lv_name, guint64 si
+ params = g_variant_builder_end (&builder);
+ g_variant_builder_clear (&builder);
+
+- call_lv_method_sync (vg_name, lv_name, "Resize", params, NULL, extra, TRUE, error);
++ g_variant_builder_init (&builder, G_VARIANT_TYPE_DICTIONARY);
++ g_variant_builder_add (&builder, "{sv}", "--fs", g_variant_new ("s", "ignore"));
++ extra_params = g_variant_builder_end (&builder);
++ g_variant_builder_clear (&builder);
++
++ call_lv_method_sync (vg_name, lv_name, "Resize", params, extra_params, extra, TRUE, error);
+ return (*error == NULL);
+ }
+
+--
+2.39.2
+
+
+From cfb23f424c2f318efea7d9fd60ec1bcdb365ee35 Mon Sep 17 00:00:00 2001
+From: Vojtech Trefny <vtrefny@redhat.com>
+Date: Mon, 27 Feb 2023 14:00:21 +0100
+Subject: [PATCH 2/2] Allow resizing of inactive LVs with latest LVM
+
+Latest LVM doesn't allow resizing of inactive LVs without the
+"--fs ignore" option to protect users from corrupting their
+filesystems. As a low level API we don't really want to offer this
+kind of protection and we should allow to resize an inactive LV.
+---
+ src/plugins/lvm-dbus.c | 28 ++++++++++++++++++++++++----
+ src/plugins/lvm.c | 31 ++++++++++++++++++++++++++++---
+ tests/lvm_dbus_tests.py | 4 ++++
+ tests/lvm_test.py | 4 ++++
+ 4 files changed, 60 insertions(+), 7 deletions(-)
+
+diff --git a/src/plugins/lvm-dbus.c b/src/plugins/lvm-dbus.c
+index 8496a697..28f3bb25 100644
+--- a/src/plugins/lvm-dbus.c
++++ b/src/plugins/lvm-dbus.c
+@@ -32,6 +32,8 @@
+ #define SECTOR_SIZE 512
+ #define VDO_POOL_SUFFIX "vpool"
+
++#define LVM_VERSION_FSRESIZE "2.03.19"
++
+ static GMutex global_config_lock;
+ static gchar *global_config_str = NULL;
+
+@@ -2125,6 +2127,14 @@ gboolean bd_lvm_lvresize (const gchar *vg_name, const gchar *lv_name, guint64 si
+ GVariantType *type = NULL;
+ GVariant *params = NULL;
+ GVariant *extra_params = NULL;
++ gboolean success = FALSE;
++ BDLVMLVdata *lvinfo = NULL;
++ GError *l_error = NULL;
++
++ lvinfo = bd_lvm_lvinfo (vg_name, lv_name, error);
++ if (!lvinfo)
++ /* error is already populated */
++ return FALSE;
+
+ g_variant_builder_init (&builder, G_VARIANT_TYPE_TUPLE);
+ g_variant_builder_add_value (&builder, g_variant_new ("t", size));
+@@ -2134,10 +2144,20 @@ gboolean bd_lvm_lvresize (const gchar *vg_name, const gchar *lv_name, guint64 si
+ params = g_variant_builder_end (&builder);
+ g_variant_builder_clear (&builder);
+
+- g_variant_builder_init (&builder, G_VARIANT_TYPE_DICTIONARY);
+- g_variant_builder_add (&builder, "{sv}", "--fs", g_variant_new ("s", "ignore"));
+- extra_params = g_variant_builder_end (&builder);
+- g_variant_builder_clear (&builder);
++ if (lvinfo->attr[4] != 'a') {
++ /* starting with 2.03.19 we need to add extra option to allow resizing of inactive LVs */
++ success = bd_utils_check_util_version (deps[DEPS_LVM].name, LVM_VERSION_FSRESIZE,
++ deps[DEPS_LVM].ver_arg, deps[DEPS_LVM].ver_regexp, &l_error);
++ if (success) {
++ g_variant_builder_init (&builder, G_VARIANT_TYPE_DICTIONARY);
++ g_variant_builder_add (&builder, "{sv}", "--fs", g_variant_new ("s", "ignore"));
++ extra_params = g_variant_builder_end (&builder);
++ g_variant_builder_clear (&builder);
++ } else
++ g_clear_error (&l_error);
++ }
++
++ bd_lvm_lvdata_free (lvinfo);
+
+ call_lv_method_sync (vg_name, lv_name, "Resize", params, extra_params, extra, TRUE, error);
+ return (*error == NULL);
+diff --git a/src/plugins/lvm.c b/src/plugins/lvm.c
+index 03211f8a..f1e2941b 100644
+--- a/src/plugins/lvm.c
++++ b/src/plugins/lvm.c
+@@ -31,6 +31,8 @@
+ #define SECTOR_SIZE 512
+ #define VDO_POOL_SUFFIX "vpool"
+
++#define LVM_VERSION_FSRESIZE "2.03.19"
++
+ static GMutex global_config_lock;
+ static gchar *global_config_str = NULL;
+
+@@ -1606,15 +1608,38 @@ gboolean bd_lvm_lvrename (const gchar *vg_name, const gchar *lv_name, const gcha
+ * Tech category: %BD_LVM_TECH_BASIC-%BD_LVM_TECH_MODE_MODIFY
+ */
+ gboolean bd_lvm_lvresize (const gchar *vg_name, const gchar *lv_name, guint64 size, const BDExtraArg **extra, GError **error) {
+- const gchar *args[6] = {"lvresize", "--force", "-L", NULL, NULL, NULL};
++ const gchar *args[8] = {"lvresize", "--force", "-L", NULL, NULL, NULL, NULL, NULL};
+ gboolean success = FALSE;
++ guint8 next_arg = 4;
++ g_autofree gchar *lvspec = NULL;
++ BDLVMLVdata *lvinfo = NULL;
++ GError *l_error = NULL;
++
++ lvinfo = bd_lvm_lvinfo (vg_name, lv_name, error);
++ if (!lvinfo)
++ /* error is already populated */
++ return FALSE;
+
+ args[3] = g_strdup_printf ("%"G_GUINT64_FORMAT"K", size/1024);
+- args[4] = g_strdup_printf ("%s/%s", vg_name, lv_name);
++
++ if (lvinfo->attr[4] != 'a') {
++ /* starting with 2.03.19 we need to add extra option to allow resizing of inactive LVs */
++ success = bd_utils_check_util_version (deps[DEPS_LVM].name, LVM_VERSION_FSRESIZE,
++ deps[DEPS_LVM].ver_arg, deps[DEPS_LVM].ver_regexp, &l_error);
++ if (success) {
++ args[next_arg++] = "--fs";
++ args[next_arg++] = "ignore";
++ } else
++ g_clear_error (&l_error);
++ }
++
++ bd_lvm_lvdata_free (lvinfo);
++
++ lvspec = g_strdup_printf ("%s/%s", vg_name, lv_name);
++ args[next_arg++] = lvspec;
+
+ success = call_lvm_and_report_error (args, extra, TRUE, error);
+ g_free ((gchar *) args[3]);
+- g_free ((gchar *) args[4]);
+
+ return success;
+ }
+diff --git a/tests/lvm_dbus_tests.py b/tests/lvm_dbus_tests.py
+index 61c898c1..fc12b55d 100644
+--- a/tests/lvm_dbus_tests.py
++++ b/tests/lvm_dbus_tests.py
+@@ -944,6 +944,10 @@ class LvmTestLVresize(LvmPVVGLVTestCase):
+ succ = BlockDev.lvm_lvdeactivate("testVG", "testLV", None)
+ self.assertTrue(succ)
+
++ # try to resize when deactivated
++ succ = BlockDev.lvm_lvresize("testVG", "testLV", 768 * 1024**2, None)
++ self.assertTrue(succ)
++
+ @unittest.skipUnless(lvm_dbus_running, "LVM DBus not running")
+ class LvmTestLVrename(LvmPVVGLVTestCase):
+ def test_lvrename(self):
+diff --git a/tests/lvm_test.py b/tests/lvm_test.py
+index 36ff10ec..7ede4b59 100644
+--- a/tests/lvm_test.py
++++ b/tests/lvm_test.py
+@@ -877,6 +877,10 @@ class LvmTestLVresize(LvmPVVGLVTestCase):
+ succ = BlockDev.lvm_lvdeactivate("testVG", "testLV", None)
+ self.assertTrue(succ)
+
++ # try to resize when deactivated
++ succ = BlockDev.lvm_lvresize("testVG", "testLV", 768 * 1024**2, None)
++ self.assertTrue(succ)
++
+ class LvmTestLVrename(LvmPVVGLVTestCase):
+ def test_lvrename(self):
+ """Verify that it's possible to rename an LV"""
+--
+2.39.2
+
diff --git a/0007-tests-Fix-test_swapon_pagesize-on-systems-with-64k-p.patch b/0007-tests-Fix-test_swapon_pagesize-on-systems-with-64k-p.patch
new file mode 100644
index 0000000..79e285d
--- /dev/null
+++ b/0007-tests-Fix-test_swapon_pagesize-on-systems-with-64k-p.patch
@@ -0,0 +1,41 @@
+From 2c59bc22d30ebfc16d5d06b1f31c4d7bbede68e9 Mon Sep 17 00:00:00 2001
+From: Vojtech Trefny <vtrefny@redhat.com>
+Date: Mon, 31 Oct 2022 12:43:17 +0100
+Subject: [PATCH] tests: Fix test_swapon_pagesize on systems with 64k pages
+
+---
+ tests/swap_test.py | 12 ++++++++++--
+ 1 file changed, 10 insertions(+), 2 deletions(-)
+
+diff --git a/tests/swap_test.py b/tests/swap_test.py
+index 0a0f333d..e350f8e8 100644
+--- a/tests/swap_test.py
++++ b/tests/swap_test.py
+@@ -1,5 +1,6 @@
+ import unittest
+ import os
++import resource
+ import overrides_hack
+
+ from utils import create_sparse_tempfile, create_lio_device, delete_lio_device, fake_utils, fake_path, run_command, run, TestTags, tag_test
+@@ -102,8 +103,15 @@ class SwapTestCase(SwapTest):
+ def test_swapon_pagesize(self):
+ """Verify that activating swap with different pagesize fails"""
+
+- # create swap with 64k pagesize
+- ret, out, err = run_command("mkswap --pagesize 65536 %s" % self.loop_dev)
++ # pick some wrong page size: 8k on 64k and 64k everywhere else
++ pagesize = resource.getpagesize()
++ if pagesize == 65536:
++ wrong_pagesize = 8192
++ else:
++ wrong_pagesize = 65536
++
++ # create swap with "wrong" pagesize
++ ret, out, err = run_command("mkswap --pagesize %s %s" % (wrong_pagesize, self.loop_dev))
+ if ret != 0:
+ self.fail("Failed to prepare swap for pagesize test: %s %s" % (out, err))
+
+--
+2.39.2
+
diff --git a/0008-part-Fix-segfault-when-adding-a-partition-too-big-fo.patch b/0008-part-Fix-segfault-when-adding-a-partition-too-big-fo.patch
new file mode 100644
index 0000000..84b753d
--- /dev/null
+++ b/0008-part-Fix-segfault-when-adding-a-partition-too-big-fo.patch
@@ -0,0 +1,32 @@
+From 9c96e621e9abb0649118d2e1731a09b1fa139579 Mon Sep 17 00:00:00 2001
+From: Vojtech Trefny <vtrefny@redhat.com>
+Date: Wed, 19 Apr 2023 09:50:38 +0200
+Subject: [PATCH] part: Fix segfault when adding a partition too big for MSDOS
+
+Resolves: rhbz#2185564
+---
+ src/plugins/part.c | 8 ++++++++
+ 1 file changed, 8 insertions(+)
+
+diff --git a/src/plugins/part.c b/src/plugins/part.c
+index 8b2285f5..28e20c28 100644
+--- a/src/plugins/part.c
++++ b/src/plugins/part.c
+@@ -841,6 +841,14 @@ static gboolean resize_part (PedPartition *part, PedDevice *dev, PedDisk *disk,
+ constr = ped_constraint_any (dev);
+
+ geom = ped_disk_get_max_partition_geometry (disk, part, constr);
++ if (!geom) {
++ set_parted_error (error, BD_PART_ERROR_FAIL);
++ g_prefix_error (error, "Failed to create geometry for partition on device '%s'", dev->path);
++ ped_constraint_destroy (constr);
++ finish_alignment_constraint (disk, orig_flag_state);
++ return FALSE;
++ }
++
+ if (!ped_geometry_set_start (geom, start)) {
+ set_parted_error (error, BD_PART_ERROR_FAIL);
+ g_prefix_error (error, "Failed to set partition start on device '%s'", dev->path);
+--
+2.40.1
+
diff --git a/0009-Fix-issues-in-tests-when-running-in-FIPS-mode.patch b/0009-Fix-issues-in-tests-when-running-in-FIPS-mode.patch
new file mode 100644
index 0000000..ed81651
--- /dev/null
+++ b/0009-Fix-issues-in-tests-when-running-in-FIPS-mode.patch
@@ -0,0 +1,70 @@
+From bc8c4fa2b3ba76647de9742c28bae751757dc2dd Mon Sep 17 00:00:00 2001
+From: Vojtech Trefny <vtrefny@redhat.com>
+Date: Thu, 18 May 2023 14:45:42 +0200
+Subject: [PATCH 1/2] tests: Use longer passphrase for LUKS in dm_test
+
+The short passphrase doesn't work when running in FIPS mode.
+---
+ tests/dm_test.py | 4 ++--
+ 1 file changed, 2 insertions(+), 2 deletions(-)
+
+diff --git a/tests/dm_test.py b/tests/dm_test.py
+index 936e3055..3b491d89 100644
+--- a/tests/dm_test.py
++++ b/tests/dm_test.py
+@@ -59,8 +59,8 @@ class DevMapperGetSubsystemFromName(DevMapperTestCase):
+ def test_get_subsystem_from_name_crypt(self):
+ """Verify that it is possible to get luks device subsystem from its name"""
+ self.addCleanup(self._destroy_crypt)
+- run("echo \"key\" | cryptsetup luksFormat %s -" %self.loop_dev)
+- run("echo \"key\" | cryptsetup open %s libbd_dm_tests-subsystem_crypt --key-file=-" %self.loop_dev)
++ run("echo \"supersecretkey\" | cryptsetup luksFormat %s -" %self.loop_dev)
++ run("echo \"supersecretkey\" | cryptsetup open %s libbd_dm_tests-subsystem_crypt --key-file=-" %self.loop_dev)
+ subsystem = BlockDev.dm_get_subsystem_from_name("libbd_dm_tests-subsystem_crypt")
+ self.assertEqual(subsystem, "CRYPT")
+
+--
+2.40.1
+
+
+From b1f6d1484a980885b9870d27d2b113c98400851b Mon Sep 17 00:00:00 2001
+From: Vojtech Trefny <vtrefny@redhat.com>
+Date: Thu, 18 May 2023 14:56:32 +0200
+Subject: [PATCH 2/2] tests: Skip crypto tests with argon2 in FIPS mode
+
+argon is not available when running in FIPS mode.
+---
+ tests/crypto_test.py | 17 +++++++++++++++++
+ 1 file changed, 17 insertions(+)
+
+diff --git a/tests/crypto_test.py b/tests/crypto_test.py
+index 94b89131..91ea1f35 100644
+--- a/tests/crypto_test.py
++++ b/tests/crypto_test.py
+@@ -175,6 +175,23 @@ class CryptoTestFormat(CryptoTestCase):
+ self.fail("Failed to get pbkdf information from:\n%s %s" % (out, err))
+ self.assertEqual(m.group(1), "pbkdf2")
+
++ def _is_fips_enabled(self):
++ if not os.path.exists("/proc/sys/crypto/fips_enabled"):
++ # if the file doesn't exist, we are definitely not in FIPS mode
++ return False
++
++ with open("/proc/sys/crypto/fips_enabled", "r") as f:
++ enabled = f.read()
++ return enabled.strip() == "1"
++
++ @tag_test(TestTags.SLOW, TestTags.CORE)
++ @unittest.skipUnless(HAVE_LUKS2, "LUKS 2 not supported")
++ def test_luks2_format_pbkdf_options(self):
++ """Verify that formatting device as LUKS 2 works"""
++
++ if self._is_fips_enabled():
++ self.skipTest("FIPS mode is enabled, cannot use argon2, skipping")
++
+ # different options for argon2 -- all parameters set
+ pbkdf = BlockDev.CryptoLUKSPBKDF(type="argon2id", max_memory_kb=100*1024, iterations=10, parallel_threads=1)
+ extra = BlockDev.CryptoLUKSExtra(pbkdf=pbkdf)
+--
+2.40.1
+
diff --git a/0010-lvm-Add-a-function-to-activate-LVs-in-shared-mode.patch b/0010-lvm-Add-a-function-to-activate-LVs-in-shared-mode.patch
new file mode 100644
index 0000000..b2e4add
--- /dev/null
+++ b/0010-lvm-Add-a-function-to-activate-LVs-in-shared-mode.patch
@@ -0,0 +1,300 @@
+From 6bdbafc79e5bcdf2087148c6caa88a6c50c1e94a Mon Sep 17 00:00:00 2001
+From: Vojtech Trefny <vtrefny@redhat.com>
+Date: Mon, 24 Apr 2023 11:57:18 +0200
+Subject: [PATCH] lvm: Add a function to activate LVs in shared mode
+
+Needed by the new blivet feature to support shared LVM setups.
+---
+ src/lib/plugin_apis/lvm.api | 16 +++++++++
+ src/plugins/lvm-dbus.c | 51 ++++++++++++++++++++-------
+ src/plugins/lvm.c | 53 ++++++++++++++++++++++-------
+ src/plugins/lvm.h | 1 +
+ src/python/gi/overrides/BlockDev.py | 5 ++-
+ tests/lvm_dbus_tests.py | 18 +++++++---
+ tests/lvm_test.py | 18 +++++++---
+ 7 files changed, 124 insertions(+), 38 deletions(-)
+
+diff --git a/src/lib/plugin_apis/lvm.api b/src/lib/plugin_apis/lvm.api
+index b8cde70b..640eee49 100644
+--- a/src/lib/plugin_apis/lvm.api
++++ b/src/lib/plugin_apis/lvm.api
+@@ -1057,6 +1057,22 @@ gboolean bd_lvm_lvresize (const gchar *vg_name, const gchar *lv_name, guint64 si
+ */
+ gboolean bd_lvm_lvactivate (const gchar *vg_name, const gchar *lv_name, gboolean ignore_skip, const BDExtraArg **extra, GError **error);
+
++/**
++ * bd_lvm_lvactivate_shared:
++ * @vg_name: name of the VG containing the to-be-activated LV
++ * @lv_name: name of the to-be-activated LV
++ * @ignore_skip: whether to ignore the skip flag or not
++ * @shared: whether to activate the LV in shared mode
++ * @extra: (allow-none) (array zero-terminated=1): extra options for the LV activation
++ * (just passed to LVM as is)
++ * @error: (out): place to store error (if any)
++ *
++ * Returns: whether the @vg_name/@lv_name LV was successfully activated or not
++ *
++ * Tech category: %BD_LVM_TECH_BASIC-%BD_LVM_TECH_MODE_MODIFY
++ */
++gboolean bd_lvm_lvactivate_shared (const gchar *vg_name, const gchar *lv_name, gboolean ignore_skip, gboolean shared, const BDExtraArg **extra, GError **error);
++
+ /**
+ * bd_lvm_lvdeactivate:
+ * @vg_name: name of the VG containing the to-be-deactivated LV
+diff --git a/src/plugins/lvm-dbus.c b/src/plugins/lvm-dbus.c
+index 28f3bb25..46e09833 100644
+--- a/src/plugins/lvm-dbus.c
++++ b/src/plugins/lvm-dbus.c
+@@ -2163,6 +2163,27 @@ gboolean bd_lvm_lvresize (const gchar *vg_name, const gchar *lv_name, guint64 si
+ return (*error == NULL);
+ }
+
++static gboolean _lvm_lvactivate (const gchar *vg_name, const gchar *lv_name, gboolean ignore_skip, gboolean shared, const BDExtraArg **extra, GError **error) {
++ GVariant *params = NULL;
++ GVariantBuilder builder;
++ GVariant *extra_params = NULL;
++
++ if (shared)
++ params = g_variant_new ("(t)", (guint64) 1 << 6);
++ else
++ params = g_variant_new ("(t)", (guint64) 0);
++
++ if (ignore_skip) {
++ g_variant_builder_init (&builder, G_VARIANT_TYPE_DICTIONARY);
++ g_variant_builder_add (&builder, "{sv}", "-K", g_variant_new ("s", ""));
++ extra_params = g_variant_builder_end (&builder);
++ g_variant_builder_clear (&builder);
++ }
++ call_lv_method_sync (vg_name, lv_name, "Activate", params, extra_params, extra, TRUE, error);
++
++ return (*error == NULL);
++}
++
+ /**
+ * bd_lvm_lvactivate:
+ * @vg_name: name of the VG containing the to-be-activated LV
+@@ -2177,19 +2198,25 @@ gboolean bd_lvm_lvresize (const gchar *vg_name, const gchar *lv_name, guint64 si
+ * Tech category: %BD_LVM_TECH_BASIC-%BD_LVM_TECH_MODE_MODIFY
+ */
+ gboolean bd_lvm_lvactivate (const gchar *vg_name, const gchar *lv_name, gboolean ignore_skip, const BDExtraArg **extra, GError **error) {
+- GVariant *params = g_variant_new ("(t)", (guint64) 0);
+- GVariantBuilder builder;
+- GVariant *extra_params = NULL;
+-
+- if (ignore_skip) {
+- g_variant_builder_init (&builder, G_VARIANT_TYPE_DICTIONARY);
+- g_variant_builder_add (&builder, "{sv}", "-K", g_variant_new ("s", ""));
+- extra_params = g_variant_builder_end (&builder);
+- g_variant_builder_clear (&builder);
+- }
+- call_lv_method_sync (vg_name, lv_name, "Activate", params, extra_params, extra, TRUE, error);
++ return _lvm_lvactivate (vg_name, lv_name, ignore_skip, FALSE, extra, error);
++}
+
+- return (*error == NULL);
++/**
++ * bd_lvm_lvactivate_shared:
++ * @vg_name: name of the VG containing the to-be-activated LV
++ * @lv_name: name of the to-be-activated LV
++ * @ignore_skip: whether to ignore the skip flag or not
++ * @shared: whether to activate the LV in shared mode
++ * @extra: (allow-none) (array zero-terminated=1): extra options for the LV activation
++ * (just passed to LVM as is)
++ * @error: (out): place to store error (if any)
++ *
++ * Returns: whether the @vg_name/@lv_name LV was successfully activated or not
++ *
++ * Tech category: %BD_LVM_TECH_BASIC-%BD_LVM_TECH_MODE_MODIFY
++ */
++gboolean bd_lvm_lvactivate_shared (const gchar *vg_name, const gchar *lv_name, gboolean ignore_skip, gboolean shared, const BDExtraArg **extra, GError **error) {
++ return _lvm_lvactivate (vg_name, lv_name, ignore_skip, shared, extra, error);
+ }
+
+ /**
+diff --git a/src/plugins/lvm.c b/src/plugins/lvm.c
+index f1e2941b..0db3bf4a 100644
+--- a/src/plugins/lvm.c
++++ b/src/plugins/lvm.c
+@@ -1644,6 +1644,28 @@ gboolean bd_lvm_lvresize (const gchar *vg_name, const gchar *lv_name, guint64 si
+ return success;
+ }
+
++static gboolean _lvm_lvactivate (const gchar *vg_name, const gchar *lv_name, gboolean ignore_skip, gboolean shared, const BDExtraArg **extra, GError **error) {
++ const gchar *args[5] = {"lvchange", NULL, NULL, NULL, NULL};
++ guint8 next_arg = 2;
++ gboolean success = FALSE;
++
++ if (shared)
++ args[1] = "-asy";
++ else
++ args[1] = "-ay";
++
++ if (ignore_skip) {
++ args[next_arg] = "-K";
++ next_arg++;
++ }
++ args[next_arg] = g_strdup_printf ("%s/%s", vg_name, lv_name);
++
++ success = call_lvm_and_report_error (args, extra, TRUE, error);
++ g_free ((gchar *) args[next_arg]);
++
++ return success;
++}
++
+ /**
+ * bd_lvm_lvactivate:
+ * @vg_name: name of the VG containing the to-be-activated LV
+@@ -1658,20 +1680,25 @@ gboolean bd_lvm_lvresize (const gchar *vg_name, const gchar *lv_name, guint64 si
+ * Tech category: %BD_LVM_TECH_BASIC-%BD_LVM_TECH_MODE_MODIFY
+ */
+ gboolean bd_lvm_lvactivate (const gchar *vg_name, const gchar *lv_name, gboolean ignore_skip, const BDExtraArg **extra, GError **error) {
+- const gchar *args[5] = {"lvchange", "-ay", NULL, NULL, NULL};
+- guint8 next_arg = 2;
+- gboolean success = FALSE;
+-
+- if (ignore_skip) {
+- args[next_arg] = "-K";
+- next_arg++;
+- }
+- args[next_arg] = g_strdup_printf ("%s/%s", vg_name, lv_name);
+-
+- success = call_lvm_and_report_error (args, extra, TRUE, error);
+- g_free ((gchar *) args[next_arg]);
++ return _lvm_lvactivate (vg_name, lv_name, ignore_skip, FALSE, extra, error);
++}
+
+- return success;
++/**
++ * bd_lvm_lvactivate_shared:
++ * @vg_name: name of the VG containing the to-be-activated LV
++ * @lv_name: name of the to-be-activated LV
++ * @ignore_skip: whether to ignore the skip flag or not
++ * @shared: whether to activate the LV in shared mode
++ * @extra: (allow-none) (array zero-terminated=1): extra options for the LV activation
++ * (just passed to LVM as is)
++ * @error: (out): place to store error (if any)
++ *
++ * Returns: whether the @vg_name/@lv_name LV was successfully activated or not
++ *
++ * Tech category: %BD_LVM_TECH_BASIC-%BD_LVM_TECH_MODE_MODIFY
++ */
++gboolean bd_lvm_lvactivate_shared (const gchar *vg_name, const gchar *lv_name, gboolean ignore_skip, gboolean shared, const BDExtraArg **extra, GError **error) {
++ return _lvm_lvactivate (vg_name, lv_name, ignore_skip, shared, extra, error);
+ }
+
+ /**
+diff --git a/src/plugins/lvm.h b/src/plugins/lvm.h
+index fabf091f..c85c043d 100644
+--- a/src/plugins/lvm.h
++++ b/src/plugins/lvm.h
+@@ -277,6 +277,7 @@ gboolean bd_lvm_lvremove (const gchar *vg_name, const gchar *lv_name, gboolean f
+ gboolean bd_lvm_lvrename (const gchar *vg_name, const gchar *lv_name, const gchar *new_name, const BDExtraArg **extra, GError **error);
+ gboolean bd_lvm_lvresize (const gchar *vg_name, const gchar *lv_name, guint64 size, const BDExtraArg **extra, GError **error);
+ gboolean bd_lvm_lvactivate (const gchar *vg_name, const gchar *lv_name, gboolean ignore_skip, const BDExtraArg **extra, GError **error);
++gboolean bd_lvm_lvactivate_shared (const gchar *vg_name, const gchar *lv_name, gboolean ignore_skip, gboolean shared, const BDExtraArg **extra, GError **error);
+ gboolean bd_lvm_lvdeactivate (const gchar *vg_name, const gchar *lv_name, const BDExtraArg **extra, GError **error);
+ gboolean bd_lvm_lvsnapshotcreate (const gchar *vg_name, const gchar *origin_name, const gchar *snapshot_name, guint64 size, const BDExtraArg **extra, GError **error);
+ gboolean bd_lvm_lvsnapshotmerge (const gchar *vg_name, const gchar *snapshot_name, const BDExtraArg **extra, GError **error);
+diff --git a/src/python/gi/overrides/BlockDev.py b/src/python/gi/overrides/BlockDev.py
+index 795e0de4..3e074260 100644
+--- a/src/python/gi/overrides/BlockDev.py
++++ b/src/python/gi/overrides/BlockDev.py
+@@ -605,11 +605,10 @@ def lvm_lvresize(vg_name, lv_name, size, extra=None, **kwargs):
+ return _lvm_lvresize(vg_name, lv_name, size, extra)
+ __all__.append("lvm_lvresize")
+
+-_lvm_lvactivate = BlockDev.lvm_lvactivate
+ @override(BlockDev.lvm_lvactivate)
+-def lvm_lvactivate(vg_name, lv_name, ignore_skip=False, extra=None, **kwargs):
++def lvm_lvactivate(vg_name, lv_name, ignore_skip=False, shared=False, extra=None, **kwargs):
+ extra = _get_extra(extra, kwargs)
+- return _lvm_lvactivate(vg_name, lv_name, ignore_skip, extra)
++ return BlockDev.lvm_lvactivate_shared(vg_name, lv_name, ignore_skip, shared, extra)
+ __all__.append("lvm_lvactivate")
+
+ _lvm_lvdeactivate = BlockDev.lvm_lvdeactivate
+diff --git a/tests/lvm_dbus_tests.py b/tests/lvm_dbus_tests.py
+index fc12b55d..a821636e 100644
+--- a/tests/lvm_dbus_tests.py
++++ b/tests/lvm_dbus_tests.py
+@@ -873,15 +873,15 @@ class LvmTestLVactivateDeactivate(LvmPVVGLVTestCase):
+ self.assertTrue(succ)
+
+ with self.assertRaises(GLib.GError):
+- BlockDev.lvm_lvactivate("nonexistingVG", "testLV", True, None)
++ BlockDev.lvm_lvactivate("nonexistingVG", "testLV", True)
+
+ with self.assertRaises(GLib.GError):
+- BlockDev.lvm_lvactivate("testVG", "nonexistingLV", True, None)
++ BlockDev.lvm_lvactivate("testVG", "nonexistingLV", True)
+
+ with self.assertRaises(GLib.GError):
+- BlockDev.lvm_lvactivate("nonexistingVG", "nonexistingLV", True, None)
++ BlockDev.lvm_lvactivate("nonexistingVG", "nonexistingLV", True)
+
+- succ = BlockDev.lvm_lvactivate("testVG", "testLV", True, None)
++ succ = BlockDev.lvm_lvactivate("testVG", "testLV", True)
+ self.assertTrue(succ)
+
+ with self.assertRaises(GLib.GError):
+@@ -896,7 +896,15 @@ class LvmTestLVactivateDeactivate(LvmPVVGLVTestCase):
+ succ = BlockDev.lvm_lvdeactivate("testVG", "testLV", None)
+ self.assertTrue(succ)
+
+- succ = BlockDev.lvm_lvactivate("testVG", "testLV", True, None)
++ succ = BlockDev.lvm_lvactivate("testVG", "testLV", True)
++ self.assertTrue(succ)
++
++ succ = BlockDev.lvm_lvdeactivate("testVG", "testLV", None)
++ self.assertTrue(succ)
++
++ # try activating in shared mode, unfortunately no way to check whether it really
++ # works or not
++ succ = BlockDev.lvm_lvactivate("testVG", "testLV", True, True)
+ self.assertTrue(succ)
+
+ succ = BlockDev.lvm_lvdeactivate("testVG", "testLV", None)
+diff --git a/tests/lvm_test.py b/tests/lvm_test.py
+index 7ede4b59..63f43afb 100644
+--- a/tests/lvm_test.py
++++ b/tests/lvm_test.py
+@@ -807,15 +807,15 @@ class LvmTestLVactivateDeactivate(LvmPVVGLVTestCase):
+ self.assertTrue(succ)
+
+ with self.assertRaises(GLib.GError):
+- BlockDev.lvm_lvactivate("nonexistingVG", "testLV", True, None)
++ BlockDev.lvm_lvactivate("nonexistingVG", "testLV", True)
+
+ with self.assertRaises(GLib.GError):
+- BlockDev.lvm_lvactivate("testVG", "nonexistingLV", True, None)
++ BlockDev.lvm_lvactivate("testVG", "nonexistingLV", True)
+
+ with self.assertRaises(GLib.GError):
+- BlockDev.lvm_lvactivate("nonexistingVG", "nonexistingLV", True, None)
++ BlockDev.lvm_lvactivate("nonexistingVG", "nonexistingLV", True)
+
+- succ = BlockDev.lvm_lvactivate("testVG", "testLV", True, None)
++ succ = BlockDev.lvm_lvactivate("testVG", "testLV", True)
+ self.assertTrue(succ)
+
+ with self.assertRaises(GLib.GError):
+@@ -830,7 +830,15 @@ class LvmTestLVactivateDeactivate(LvmPVVGLVTestCase):
+ succ = BlockDev.lvm_lvdeactivate("testVG", "testLV", None)
+ self.assertTrue(succ)
+
+- succ = BlockDev.lvm_lvactivate("testVG", "testLV", True, None)
++ succ = BlockDev.lvm_lvactivate("testVG", "testLV", True)
++ self.assertTrue(succ)
++
++ succ = BlockDev.lvm_lvdeactivate("testVG", "testLV", None)
++ self.assertTrue(succ)
++
++ # try activating in shared mode, unfortunately no way to check whether it really
++ # works or not
++ succ = BlockDev.lvm_lvactivate("testVG", "testLV", True, True)
+ self.assertTrue(succ)
+
+ succ = BlockDev.lvm_lvdeactivate("testVG", "testLV", None)
+--
+2.41.0
+
diff --git a/0011-nvme_libblockdev-3.0.4_backport.patch b/0011-nvme_libblockdev-3.0.4_backport.patch
new file mode 100644
index 0000000..fae7aae
--- /dev/null
+++ b/0011-nvme_libblockdev-3.0.4_backport.patch
@@ -0,0 +1,1395 @@
+From 2c4324b658931882bfb664e05673cd5c9082429d Mon Sep 17 00:00:00 2001
+From: Tomas Bzatek <tbzatek@redhat.com>
+Date: Fri, 7 Oct 2022 15:42:44 +0200
+Subject: [PATCH] nvme: Rework the disconnect device topology crawl
+
+This is more sane regarding to memory ownership and
+the scanned topology tree.
+---
+ src/plugins/nvme/nvme-fabrics.c | 93 +++++++++++++++++----------------
+ tests/nvme_test.py | 2 +-
+ 2 files changed, 50 insertions(+), 45 deletions(-)
+
+diff --git a/src/plugins/nvme/nvme-fabrics.c b/src/plugins/nvme/nvme-fabrics.c
+index 20ed57f5d..26e2fa6ec 100644
+--- a/src/plugins/nvme/nvme-fabrics.c
++++ b/src/plugins/nvme/nvme-fabrics.c
+@@ -281,6 +281,47 @@ gboolean bd_nvme_connect (const gchar *subsysnqn, const gchar *transport, const
+ return TRUE;
+ }
+
++static gboolean _disconnect (const gchar *subsysnqn, const gchar *path, GError **error, gboolean *found) {
++ nvme_root_t root;
++ nvme_host_t host;
++ nvme_subsystem_t subsys;
++ nvme_ctrl_t ctrl;
++ int ret;
++
++ root = nvme_create_root (NULL, -1);
++ if (root == NULL) {
++ g_set_error (error, BD_NVME_ERROR, BD_NVME_ERROR_FAILED,
++ "Failed to create topology root: %s",
++ strerror_l (errno, _C_LOCALE));
++ return FALSE;
++ }
++ ret = nvme_scan_topology (root, NULL, NULL);
++ if (ret < 0) {
++ g_set_error (error, BD_NVME_ERROR, BD_NVME_ERROR_FAILED,
++ "Failed to scan topology: %s",
++ strerror_l (errno, _C_LOCALE));
++ nvme_free_tree (root);
++ return FALSE;
++ }
++ nvme_for_each_host (root, host)
++ nvme_for_each_subsystem (host, subsys)
++ if (!subsysnqn || g_strcmp0 (nvme_subsystem_get_nqn (subsys), subsysnqn) == 0)
++ nvme_subsystem_for_each_ctrl (subsys, ctrl)
++ if (!path || g_strcmp0 (nvme_ctrl_get_name (ctrl), path) == 0) {
++ ret = nvme_disconnect_ctrl (ctrl);
++ if (ret != 0) {
++ g_set_error (error, BD_NVME_ERROR, BD_NVME_ERROR_FAILED,
++ "Error disconnecting the controller: %s",
++ strerror_l (errno, _C_LOCALE));
++ nvme_free_tree (root);
++ return FALSE;
++ }
++ *found = TRUE;
++ }
++ nvme_free_tree (root);
++ return TRUE;
++}
++
+ /**
+ * bd_nvme_disconnect:
+ * @subsysnqn: The name of the NVMe subsystem to disconnect.
+@@ -296,37 +337,16 @@ gboolean bd_nvme_connect (const gchar *subsysnqn, const gchar *transport, const
+ * 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 (!_disconnect (subsysnqn, NULL, error, &found))
++ return FALSE;
++
+ if (!found) {
+ g_set_error (error, BD_NVME_ERROR, BD_NVME_ERROR_NO_MATCH,
+ "No subsystems matching '%s' NQN found.", subsysnqn);
+ return FALSE;
+ }
+-
+ return TRUE;
+ }
+
+@@ -344,36 +364,21 @@ gboolean bd_nvme_disconnect (const gchar *subsysnqn, GError **error) {
+ * 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;
++ gboolean found = FALSE;
+
+ 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);
++ if (!_disconnect (NULL, p, error, &found))
+ 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);
++ if (!found) {
++ g_set_error (error, BD_NVME_ERROR, BD_NVME_ERROR_NO_MATCH,
++ "No controllers matching the %s device name found.", path);
+ return FALSE;
+ }
+- nvme_free_tree (root);
+-
+ return TRUE;
+ }
+
+diff --git a/tests/nvme_test.py b/tests/nvme_test.py
+index f205e5391..696d594c9 100644
+--- a/tests/nvme_test.py
++++ b/tests/nvme_test.py
+@@ -387,7 +387,7 @@ def test_connect_single_ns(self):
+ # 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"):
++ with self.assertRaisesRegexp(GLib.GError, r"No controllers matching the /dev/nvme.*xx device name found."):
+ BlockDev.nvme_disconnect_by_path(ctrls[0] + "xx")
+ # should disconnect both connections as long the SUBNQN matches
+ BlockDev.nvme_disconnect(self.SUBNQN)
+From ac1e0c50dd7bab21a7837c806b82bf34090127d1 Mon Sep 17 00:00:00 2001
+From: Tomas Bzatek <tbzatek@redhat.com>
+Date: Sun, 5 Feb 2023 21:51:42 +0100
+Subject: [PATCH] nvme: Strip trailing whitespaces from subsysnqn when matching
+ the device tree
+
+---
+ src/plugins/nvme/nvme-fabrics.c | 9 ++++++++-
+ 1 file changed, 8 insertions(+), 1 deletion(-)
+
+diff --git a/src/plugins/nvme/nvme-fabrics.c b/src/plugins/nvme/nvme-fabrics.c
+index 5cc7f670d..27f6ed444 100644
+--- a/src/plugins/nvme/nvme-fabrics.c
++++ b/src/plugins/nvme/nvme-fabrics.c
+@@ -721,6 +721,12 @@ gchar ** bd_nvme_find_ctrls_for_ns (const gchar *ns_sysfs_path, const gchar *sub
+ nvme_ctrl_t c;
+ nvme_ns_t n;
+ char realp[PATH_MAX];
++ gchar *subsysnqn_p;
++
++ /* libnvme strips trailing spaces and newlines when reading values from sysfs */
++ subsysnqn_p = g_strdup (subsysnqn);
++ if (subsysnqn_p)
++ g_strchomp (subsysnqn_p);
+
+ ptr_array = g_ptr_array_new ();
+
+@@ -736,7 +742,7 @@ gchar ** bd_nvme_find_ctrls_for_ns (const gchar *ns_sysfs_path, const gchar *sub
+ nvme_for_each_subsystem (h, s) {
+ gboolean found = FALSE;
+
+- if (subsysnqn && g_strcmp0 (nvme_subsystem_get_nqn (s), subsysnqn) != 0)
++ if (subsysnqn && g_strcmp0 (nvme_subsystem_get_nqn (s), subsysnqn_p) != 0)
+ continue;
+
+ nvme_subsystem_for_each_ctrl (s, c)
+@@ -767,6 +773,7 @@ gchar ** bd_nvme_find_ctrls_for_ns (const gchar *ns_sysfs_path, const gchar *sub
+ }
+ }
+ nvme_free_tree (root);
++ g_free (subsysnqn_p);
+
+ g_ptr_array_add (ptr_array, NULL); /* trailing NULL element */
+ return (gchar **) g_ptr_array_free (ptr_array, FALSE);
+From 29b4defc50451dd54f1a7a988b92bdf5fadb22df Mon Sep 17 00:00:00 2001
+From: Tomas Bzatek <tbzatek@redhat.com>
+Date: Mon, 6 Feb 2023 11:29:43 +0100
+Subject: [PATCH] nvme: Return NULL in case the supported NVMe revision is not
+ reported
+
+...as is often the case for older, pre-1.2 NVMe devices.
+---
+ src/lib/plugin_apis/nvme.api | 2 +-
+ src/plugins/nvme/nvme-info.c | 2 ++
+ src/plugins/nvme/nvme.h | 2 +-
+ 3 files changed, 4 insertions(+), 2 deletions(-)
+
+diff --git a/src/lib/plugin_apis/nvme.api b/src/lib/plugin_apis/nvme.api
+index 7bc2cf9ef..6e73870e5 100644
+--- a/src/lib/plugin_apis/nvme.api
++++ b/src/lib/plugin_apis/nvme.api
+@@ -142,7 +142,7 @@ GType bd_nvme_controller_info_get_type ();
+ * @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.
++ * @nvme_ver: The NVM Express base specification that the controller implementation supports or %NULL when not reported by the device.
+ * @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
+diff --git a/src/plugins/nvme/nvme-info.c b/src/plugins/nvme/nvme-info.c
+index ec5cae332..23ee8afde 100644
+--- a/src/plugins/nvme/nvme-info.c
++++ b/src/plugins/nvme/nvme-info.c
+@@ -401,6 +401,8 @@ static gchar *decode_nvme_rev (guint32 ver) {
+ if (mjr >= 2 || mnr >= 2)
+ ter = ver & 0xFF;
+
++ if (mjr == 0 && mnr == 0)
++ return NULL;
+ if (ter == 0)
+ return g_strdup_printf ("%u.%u", mjr, mnr);
+ else
+diff --git a/src/plugins/nvme/nvme.h b/src/plugins/nvme/nvme.h
+index ad456a82e..fa6f6abcc 100644
+--- a/src/plugins/nvme/nvme.h
++++ b/src/plugins/nvme/nvme.h
+@@ -129,7 +129,7 @@ typedef enum {
+ * @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.
++ * @nvme_ver: The NVM Express base specification that the controller implementation supports or %NULL when not reported by the device.
+ * @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
+From 5a709aa3475ee083237c3faf4fff8c316767bd71 Mon Sep 17 00:00:00 2001
+From: Tomas Bzatek <tbzatek@redhat.com>
+Date: Wed, 26 Apr 2023 18:39:57 +0200
+Subject: [PATCH] nvme: Add support for ENVME_CONNECT_CONNREFUSED
+
+Requires libnvme-1.3
+---
+ configure.ac | 2 +-
+ src/lib/plugin_apis/nvme.api | 2 ++
+ src/plugins/nvme/nvme-error.c | 3 +++
+ src/plugins/nvme/nvme.h | 2 ++
+ 4 files changed, 8 insertions(+), 1 deletion(-)
+
+diff --git a/configure.ac b/configure.ac
+index 727465c9b..16fe3a0c2 100644
+--- a/configure.ac
++++ b/configure.ac
+@@ -238,7 +238,7 @@ AS_IF([test "x$with_nvdimm" != "xno"],
+ [])
+
+ AS_IF([test "x$with_nvme" != "xno"],
+- [LIBBLOCKDEV_PKG_CHECK_MODULES([NVME], [libnvme >= 1.2])],
++ [LIBBLOCKDEV_PKG_CHECK_MODULES([NVME], [libnvme >= 1.3])],
+ [])
+
+ AS_IF([test "x$with_vdo" != "xno"],
+diff --git a/src/lib/plugin_apis/nvme.api b/src/lib/plugin_apis/nvme.api
+index 6e73870e5..1099da7db 100644
+--- a/src/lib/plugin_apis/nvme.api
++++ b/src/lib/plugin_apis/nvme.api
+@@ -31,6 +31,7 @@ GQuark bd_nvme_error_quark (void) {
+ * @BD_NVME_ERROR_CONNECT_ADDRINUSE: HostNQN already in use.
+ * @BD_NVME_ERROR_CONNECT_NODEV: Invalid interface.
+ * @BD_NVME_ERROR_CONNECT_OPNOTSUPP: Operation not supported.
++ * @BD_NVME_ERROR_CONNECT_REFUSED: Connection refused.
+ */
+ /* BpG-skip-end */
+ typedef enum {
+@@ -51,6 +52,7 @@ typedef enum {
+ BD_NVME_ERROR_CONNECT_ADDRINUSE,
+ BD_NVME_ERROR_CONNECT_NODEV,
+ BD_NVME_ERROR_CONNECT_OPNOTSUPP,
++ BD_NVME_ERROR_CONNECT_REFUSED,
+ } BDNVMEError;
+
+ typedef enum {
+diff --git a/src/plugins/nvme/nvme-error.c b/src/plugins/nvme/nvme-error.c
+index cb95a46dc..4bd4d7712 100644
+--- a/src/plugins/nvme/nvme-error.c
++++ b/src/plugins/nvme/nvme-error.c
+@@ -137,6 +137,9 @@ void _nvme_fabrics_errno_to_gerror (int result, int _errno, GError **error)
+ case ENVME_CONNECT_OPNOTSUPP:
+ code = BD_NVME_ERROR_CONNECT_OPNOTSUPP;
+ break;
++ case ENVME_CONNECT_CONNREFUSED:
++ code = BD_NVME_ERROR_CONNECT_REFUSED;
++ break;
+ default:
+ code = BD_NVME_ERROR_CONNECT;
+ }
+diff --git a/src/plugins/nvme/nvme.h b/src/plugins/nvme/nvme.h
+index 438d66334..afd41b267 100644
+--- a/src/plugins/nvme/nvme.h
++++ b/src/plugins/nvme/nvme.h
+@@ -27,6 +27,7 @@ GQuark bd_nvme_error_quark (void);
+ * @BD_NVME_ERROR_CONNECT_ADDRINUSE: HostNQN already in use.
+ * @BD_NVME_ERROR_CONNECT_NODEV: Invalid interface.
+ * @BD_NVME_ERROR_CONNECT_OPNOTSUPP: Operation not supported.
++ * @BD_NVME_ERROR_CONNECT_REFUSED: Connection refused.
+ */
+ typedef enum {
+ BD_NVME_ERROR_TECH_UNAVAIL,
+@@ -46,6 +47,7 @@ typedef enum {
+ BD_NVME_ERROR_CONNECT_ADDRINUSE,
+ BD_NVME_ERROR_CONNECT_NODEV,
+ BD_NVME_ERROR_CONNECT_OPNOTSUPP,
++ BD_NVME_ERROR_CONNECT_REFUSED,
+ } BDNVMEError;
+
+ typedef enum {
+From b19600de73233317cd9de68dba356ec4bb80a0f3 Mon Sep 17 00:00:00 2001
+From: Tomas Bzatek <tbzatek@redhat.com>
+Date: Thu, 27 Apr 2023 17:32:25 +0200
+Subject: [PATCH] nvme: Add support for 'keyring' and 'tls_key' fabrics
+ attributes
+
+Requires libnvme-1.4
+---
+ configure.ac | 5 ++++-
+ src/lib/plugin_apis/nvme.api | 2 ++
+ src/plugins/nvme/nvme-fabrics.c | 21 +++++++++++++++++++++
+ 3 files changed, 27 insertions(+), 1 deletion(-)
+
+diff --git a/configure.ac b/configure.ac
+index 16fe3a0c2..12d007a53 100644
+--- a/configure.ac
++++ b/configure.ac
+@@ -238,7 +238,10 @@ AS_IF([test "x$with_nvdimm" != "xno"],
+ [])
+
+ AS_IF([test "x$with_nvme" != "xno"],
+- [LIBBLOCKDEV_PKG_CHECK_MODULES([NVME], [libnvme >= 1.3])],
++ [LIBBLOCKDEV_PKG_CHECK_MODULES([NVME], [libnvme >= 1.3])
++ AS_IF([$PKG_CONFIG --atleast-version=1.4 libnvme],
++ [AC_DEFINE([HAVE_LIBNVME_1_4])], [])
++ ],
+ [])
+
+ AS_IF([test "x$with_vdo" != "xno"],
+diff --git a/src/lib/plugin_apis/nvme.api b/src/lib/plugin_apis/nvme.api
+index 1099da7db..667dbe9e0 100644
+--- a/src/lib/plugin_apis/nvme.api
++++ b/src/lib/plugin_apis/nvme.api
+@@ -1415,6 +1415,8 @@ gboolean bd_nvme_set_host_id (const gchar *host_id, GError **error);
+ * - `"data_digest"`: Generates/verifies data digest (TCP). Boolean value.
+ * - `"tls"`: Enable TLS encryption (TCP). Boolean value.
+ * - `"hostsymname"`: TP8010: NVMe host symbolic name.
++ * - `"keyring"`: Keyring to store and lookup keys. String value.
++ * - `"tls_key"`: TLS PSK for the connection. String value.
+ *
+ * Boolean values can be expressed by "0"/"1", "on"/"off" or "True"/"False" case-insensitive
+ * strings. Failed numerical or boolean string conversions will result in the option being ignored.
+diff --git a/src/plugins/nvme/nvme-fabrics.c b/src/plugins/nvme/nvme-fabrics.c
+index 27f6ed444..0c64fb513 100644
+--- a/src/plugins/nvme/nvme-fabrics.c
++++ b/src/plugins/nvme/nvme-fabrics.c
+@@ -125,6 +125,25 @@ static void parse_extra_args (const BDExtraArg **extra, struct nvme_fabrics_conf
+ else
+ if (g_strcmp0 ((*extra_i)->opt, "tls") == 0)
+ SAFE_BOOL_CONV (cfg->tls)
++#ifdef HAVE_LIBNVME_1_4
++ else
++ if (g_strcmp0 ((*extra_i)->opt, "keyring") == 0) {
++ int keyring;
++
++ keyring = nvme_lookup_keyring ((*extra_i)->val);
++ if (keyring) {
++ cfg->keyring = keyring;
++ nvme_set_keyring (cfg->keyring);
++ }
++ } else
++ if (g_strcmp0 ((*extra_i)->opt, "tls_key") == 0) {
++ int key;
++
++ key = nvme_lookup_key ("psk", (*extra_i)->val);
++ if (key)
++ cfg->tls_key = key;
++ }
++#endif
+ }
+
+ #undef SAFE_INT_CONV
+@@ -179,6 +198,8 @@ static void parse_extra_args (const BDExtraArg **extra, struct nvme_fabrics_conf
+ * - `"data_digest"`: Generates/verifies data digest (TCP). Boolean value.
+ * - `"tls"`: Enable TLS encryption (TCP). Boolean value.
+ * - `"hostsymname"`: TP8010: NVMe host symbolic name.
++ * - `"keyring"`: Keyring to store and lookup keys. String value.
++ * - `"tls_key"`: TLS PSK for the connection. String value.
+ *
+ * Boolean values can be expressed by "0"/"1", "on"/"off" or "True"/"False" case-insensitive
+ * strings. Failed numerical or boolean string conversions will result in the option being ignored.
+From 941448f5ec9f465266e1baf5de23e36692a17274 Mon Sep 17 00:00:00 2001
+From: Tomas Bzatek <tbzatek@redhat.com>
+Date: Thu, 27 Apr 2023 19:05:00 +0200
+Subject: [PATCH] nvme: Fix 128-bit int conversion
+
+Still using target 64-bit int and expecting overflow. This change
+at least handles endianness conversion properly.
+
+Futher experiments reveal shortcomings during gobject-introspection
+data type translation so until 128-bit int is properly supported
+throughout the stack, we can only hope numbers won't be that high.
+---
+ src/plugins/nvme/nvme-info.c | 17 ++++++++++++++---
+ 1 file changed, 14 insertions(+), 3 deletions(-)
+
+diff --git a/src/plugins/nvme/nvme-info.c b/src/plugins/nvme/nvme-info.c
+index 23ee8afde..bbc7de4f2 100644
+--- a/src/plugins/nvme/nvme-info.c
++++ b/src/plugins/nvme/nvme-info.c
+@@ -365,15 +365,26 @@ BDNVMESanitizeLog * bd_nvme_sanitize_log_copy (BDNVMESanitizeLog *log) {
+ }
+
+
+-static guint64 int128_to_guint64 (__u8 *data)
++/* can't use real __int128 due to gobject-introspection */
++static guint64 int128_to_guint64 (__u8 data[16])
+ {
+ int i;
++ __u8 d[16];
+ guint64 result = 0;
+
+- /* FIXME: would overflow, need to find 128-bit int */
++ /* endianness conversion */
++#if G_BYTE_ORDER == G_BIG_ENDIAN
++ for (i = 0; i < 16; i++)
++ d[i] = data[15 - i];
++#else
++ memcpy (d, data, sizeof (d));
++#endif
++
++ /* FIXME: would overflow */
++ /* https://github.com/linux-nvme/libnvme/issues/475 */
+ for (i = 0; i < 16; i++) {
+ result *= 256;
+- result += data[15 - i];
++ result += d[15 - i];
+ }
+ return result;
+ }
+From 5f118338e52a9096fdbac5ee9aa9c1e43f8b038b Mon Sep 17 00:00:00 2001
+From: Vojtech Trefny <vtrefny@redhat.com>
+Date: Fri, 23 Jun 2023 13:05:24 +0200
+Subject: [PATCH] docs: Change NVMe plugin description to be consistent with
+ other plugins
+
+---
+ src/plugins/nvme/nvme.c | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+diff --git a/src/plugins/nvme/nvme.c b/src/plugins/nvme/nvme.c
+index c0f4759de..c319f088c 100644
+--- a/src/plugins/nvme/nvme.c
++++ b/src/plugins/nvme/nvme.c
+@@ -36,7 +36,7 @@
+
+ /**
+ * SECTION: nvme
+- * @short_description: NVMe device reporting and management.
++ * @short_description: plugin for NVMe device reporting and management
+ * @title: NVMe
+ * @include: nvme.h
+ *
+From 0f91582182f1680e979ff5b6e0d7c48a19917abc Mon Sep 17 00:00:00 2001
+From: Tomas Bzatek <tbzatek@redhat.com>
+Date: Thu, 29 Jun 2023 14:42:41 +0200
+Subject: [PATCH] nvme: Mark private symbols as hidden
+
+---
+ src/plugins/nvme/nvme-private.h | 3 +++
+ 1 file changed, 3 insertions(+)
+
+diff --git a/src/plugins/nvme/nvme-private.h b/src/plugins/nvme/nvme-private.h
+index 3d4b2a996..8b6d13253 100644
+--- a/src/plugins/nvme/nvme-private.h
++++ b/src/plugins/nvme/nvme-private.h
+@@ -16,10 +16,13 @@
+ #define _C_LOCALE (locale_t) 0
+
+ /* nvme-error.c */
++G_GNUC_INTERNAL
+ void _nvme_status_to_error (gint status, gboolean fabrics, GError **error);
++G_GNUC_INTERNAL
+ void _nvme_fabrics_errno_to_gerror (int result, int _errno, GError **error);
+
+ /* nvme-info.c */
++G_GNUC_INTERNAL
+ gint _open_dev (const gchar *device, GError **error);
+
+ #endif /* BD_NVME_PRIVATE */
+From ddfb806da578541c7a5a0947e29b81e33f594b11 Mon Sep 17 00:00:00 2001
+From: Tomas Bzatek <tbzatek@redhat.com>
+Date: Tue, 8 Aug 2023 16:51:26 +0200
+Subject: [PATCH] nvme: Use interim buffer for nvme_get_log_sanitize()
+
+Certain drives tend to return more data than expected resulting
+in stack corruption. Use an interim buffer large enough to handle
+the excess bytes.
+
+https://github.com/storaged-project/udisks/issues/1152
+https://github.com/linux-nvme/libnvme/issues/684
+---
+ src/plugins/nvme/nvme-info.c | 25 +++++++++++++++----------
+ 1 file changed, 15 insertions(+), 10 deletions(-)
+
+diff --git a/src/plugins/nvme/nvme-info.c b/src/plugins/nvme/nvme-info.c
+index bbc7de4f2..2a3d87cff 100644
+--- a/src/plugins/nvme/nvme-info.c
++++ b/src/plugins/nvme/nvme-info.c
+@@ -1026,7 +1026,8 @@ BDNVMESelfTestLog * bd_nvme_get_self_test_log (const gchar *device, GError **err
+ BDNVMESanitizeLog * bd_nvme_get_sanitize_log (const gchar *device, GError **error) {
+ int ret;
+ int fd;
+- struct nvme_sanitize_log_page sanitize_log = ZERO_INIT;
++ char buf[65536] = ZERO_INIT;
++ struct nvme_sanitize_log_page *sanitize_log;
+ BDNVMESanitizeLog *log;
+ __u16 sstat;
+
+@@ -1036,7 +1037,7 @@ BDNVMESanitizeLog * bd_nvme_get_sanitize_log (const gchar *device, GError **erro
+ return NULL;
+
+ /* send the NVME_LOG_LID_SANITIZE ioctl */
+- ret = nvme_get_log_sanitize (fd, FALSE /* rae */, &sanitize_log);
++ ret = nvme_get_log_sanitize (fd, FALSE /* rae */, (struct nvme_sanitize_log_page *) &buf);
+ if (ret != 0) {
+ _nvme_status_to_error (ret, FALSE, error);
+ g_prefix_error (error, "NVMe Get Log Page - Sanitize Status Log command error: ");
+@@ -1045,12 +1046,16 @@ BDNVMESanitizeLog * bd_nvme_get_sanitize_log (const gchar *device, GError **erro
+ }
+ close (fd);
+
+- sstat = GUINT16_FROM_LE (sanitize_log.sstat);
++ /* need to use interim buffer that is large enough for broken drives
++ * returning more data than expected
++ */
++ sanitize_log = (struct nvme_sanitize_log_page *) &buf;
+
+ log = g_new0 (BDNVMESanitizeLog, 1);
+ log->sanitize_progress = 0;
++ sstat = GUINT16_FROM_LE (sanitize_log->sstat);
+ if ((sstat & NVME_SANITIZE_SSTAT_STATUS_MASK) == NVME_SANITIZE_SSTAT_STATUS_IN_PROGESS)
+- log->sanitize_progress = ((gdouble) GUINT16_FROM_LE (sanitize_log.sprog) * 100) / 0x10000;
++ 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;
+
+@@ -1073,12 +1078,12 @@ BDNVMESanitizeLog * bd_nvme_get_sanitize_log (const gchar *device, GError **erro
+ 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);
++ 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;
+ }
+From c9c3740e20dc891bddb3cfea79e589ad2bf4419f Mon Sep 17 00:00:00 2001
+From: Tomas Bzatek <tbzatek@redhat.com>
+Date: Wed, 16 Aug 2023 16:03:11 +0200
+Subject: [PATCH] nvme: Generate HostID when missing
+
+Newer kernels tend to refuse connection when no HostID is supplied.
+Even non-matching UUIDs within HostNQN/HostID seems to be working
+fine with kernel 6.5-rc6.
+
+This should bring us closer to the new HostNQN <--> HostID
+mapping validation introduced in kernel-6.5.
+---
+ src/plugins/nvme/nvme-fabrics.c | 41 +++++++++++++++++++++++++--------
+ 1 file changed, 32 insertions(+), 9 deletions(-)
+
+diff --git a/src/plugins/nvme/nvme-fabrics.c b/src/plugins/nvme/nvme-fabrics.c
+index c79d9e6de..1877845f2 100644
+--- a/src/plugins/nvme/nvme-fabrics.c
++++ b/src/plugins/nvme/nvme-fabrics.c
+@@ -161,7 +161,7 @@ static void parse_extra_args (const BDExtraArg **extra, struct nvme_fabrics_conf
+ * @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`)
++ * @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).
+ *
+@@ -244,18 +244,41 @@ gboolean bd_nvme_connect (const gchar *subsysnqn, const gchar *transport, const
+ return FALSE;
+ }
+
+- /* parse extra arguments */
+- nvmf_default_config (&cfg);
+- parse_extra_args (extra, &cfg, &config_file, &hostkey, &ctrlkey, &hostsymname);
+-
++ /* HostNQN checks */
+ host_nqn_val = g_strdup (host_nqn);
++ host_id_val = g_strdup (host_id);
+ if (host_nqn_val == NULL)
+ host_nqn_val = nvmf_hostnqn_from_file ();
+- if (host_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 ();
++ if (host_nqn_val == NULL)
++ host_nqn_val = nvmf_hostnqn_generate ();
++ if (host_nqn_val == NULL) {
++ g_set_error_literal (error, BD_NVME_ERROR, BD_NVME_ERROR_INVALID_ARGUMENT,
++ "Could not determine HostNQN");
++ g_free (host_nqn_val);
++ g_free (host_id_val);
++ return FALSE;
++ }
++ if (host_id_val == NULL) {
++ /* derive hostid from hostnqn, newer kernels refuse empty hostid */
++ host_id_val = g_strrstr (host_nqn_val, "uuid:");
++ if (host_id_val)
++ host_id_val = g_strdup (host_id_val + strlen ("uuid:"));
++ /* TODO: in theory generating arbitrary uuid might work as a fallback */
++ }
++ if (host_id_val == NULL) {
++ g_set_error (error, BD_NVME_ERROR, BD_NVME_ERROR_INVALID_ARGUMENT,
++ "Could not determine HostID value from HostNQN '%s'",
++ host_nqn_val);
++ g_free (host_nqn_val);
++ g_free (host_id_val);
++ return FALSE;
++ }
++
++ /* parse extra arguments */
++ nvmf_default_config (&cfg);
++ parse_extra_args (extra, &cfg, &config_file, &hostkey, &ctrlkey, &hostsymname);
+
+ root = nvme_scan (config_file);
+ g_assert (root != NULL);
+@@ -263,7 +286,7 @@ gboolean bd_nvme_connect (const gchar *subsysnqn, const gchar *transport, const
+ 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'",
++ "Unable to lookup host for HostNQN '%s' and HostID '%s'",
+ host_nqn_val, host_id_val);
+ g_free (host_nqn_val);
+ g_free (host_id_val);
+From 2ae0d949eb87142b0212e5953a0e5ad1a146ed6b Mon Sep 17 00:00:00 2001
+From: Tomas Bzatek <tbzatek@redhat.com>
+Date: Thu, 5 Oct 2023 17:27:57 +0200
+Subject: [PATCH] nvme: Rework memory allocation for device ioctls
+
+Make sure to provide pagesize-aligned and large enough buffer
+for ioctls that may involve DMA buffer mapping.
+---
+ src/plugins/nvme/nvme-info.c | 311 +++++++++++++++++++-------------
+ src/plugins/nvme/nvme-op.c | 30 ++-
+ src/plugins/nvme/nvme-private.h | 2 +
+ 3 files changed, 204 insertions(+), 139 deletions(-)
+
+diff --git a/src/plugins/nvme/nvme-info.c b/src/plugins/nvme/nvme-info.c
+index 2a3d87cff..eaf77473c 100644
+--- a/src/plugins/nvme/nvme-info.c
++++ b/src/plugins/nvme/nvme-info.c
+@@ -402,6 +402,21 @@ gint _open_dev (const gchar *device, GError **error) {
+ return fd;
+ }
+
++/* backported from nvme-cli: https://github.com/linux-nvme/nvme-cli/pull/2051 */
++#define ROUND_UP(N, S) ((((N) + (S) - 1) / (S)) * (S))
++
++void *_nvme_alloc (size_t len)
++{
++ size_t _len = ROUND_UP (len, 0x1000);
++ void *p;
++
++ if (posix_memalign ((void *) &p, getpagesize (), _len))
++ return NULL;
++
++ memset (p, 0, _len);
++ return p;
++}
++
+ static gchar *decode_nvme_rev (guint32 ver) {
+ guint16 mjr;
+ guint8 mnr, ter = 0;
+@@ -452,7 +467,7 @@ static gboolean _nvme_a_is_zero (const __u8 a[], int len) {
+ BDNVMEControllerInfo * bd_nvme_get_controller_info (const gchar *device, GError **error) {
+ int ret;
+ int fd;
+- struct nvme_id_ctrl ctrl_id = ZERO_INIT;
++ struct nvme_id_ctrl *ctrl_id;
+ BDNVMEControllerInfo *info;
+
+ /* open the block device */
+@@ -460,53 +475,56 @@ BDNVMEControllerInfo * bd_nvme_get_controller_info (const gchar *device, GError
+ if (fd < 0)
+ return NULL;
+
++ ctrl_id = _nvme_alloc (sizeof (struct nvme_id_ctrl));
++ g_warn_if_fail (ctrl_id != NULL);
+ /* send the NVME_IDENTIFY_CNS_CTRL ioctl */
+- ret = nvme_identify_ctrl (fd, &ctrl_id);
++ 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);
++ free (ctrl_id);
+ return NULL;
+ }
+ close (fd);
+
+ info = g_new0 (BDNVMEControllerInfo, 1);
+- if ((ctrl_id.cmic & NVME_CTRL_CMIC_MULTI_PORT) == NVME_CTRL_CMIC_MULTI_PORT)
++ 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)
++ 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)
++ 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)
++ 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)
++ 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)
++ 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)
++ 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)
++ 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);
+- if (!_nvme_a_is_zero (ctrl_id.fguid, sizeof (ctrl_id.fguid)))
+- info->fguid = _uuid_to_str (ctrl_id.fguid);
+- info->model_number = g_strndup (ctrl_id.mn, sizeof (ctrl_id.mn));
++ 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);
++ if (!_nvme_a_is_zero (ctrl_id->fguid, sizeof (ctrl_id->fguid)))
++ info->fguid = _uuid_to_str (ctrl_id->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));
++ 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));
++ 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));
++ 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)
++ 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)
++ 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)
++ 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) {
++ switch (ctrl_id->cntrltype) {
+ case NVME_CTRL_CNTRLTYPE_IO:
+ info->controller_type = BD_NVME_CTRL_TYPE_IO;
+ break;
+@@ -519,36 +537,37 @@ BDNVMEControllerInfo * bd_nvme_get_controller_info (const gchar *device, GError
+ 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);
++ 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)
++ 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)
++ 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)
++ 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)
++ 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->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)
++ 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)
++ 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));
++ info->subsysnqn = g_strndup (ctrl_id->subnqn, sizeof (ctrl_id->subnqn));
+ g_strstrip (info->subsysnqn);
+
++ free (ctrl_id);
+ return info;
+ }
+
+@@ -572,10 +591,10 @@ BDNVMENamespaceInfo *bd_nvme_get_namespace_info (const gchar *device, GError **e
+ int ret_ns_ind = -1;
+ int fd;
+ __u32 nsid = 0;
+- struct nvme_id_ctrl ctrl_id = ZERO_INIT;
+- struct nvme_id_ns ns_info = ZERO_INIT;
+- struct nvme_id_independent_id_ns ns_info_ind = ZERO_INIT;
+- uint8_t desc[NVME_IDENTIFY_DATA_SIZE] = ZERO_INIT;
++ struct nvme_id_ctrl *ctrl_id;
++ struct nvme_id_ns *ns_info;
++ struct nvme_id_independent_id_ns *ns_info_ind = NULL;
++ struct nvme_ns_id_desc *descs = NULL;
+ guint8 flbas;
+ guint i;
+ guint len;
+@@ -597,44 +616,55 @@ BDNVMENamespaceInfo *bd_nvme_get_namespace_info (const gchar *device, GError **e
+ }
+
+ /* send the NVME_IDENTIFY_CNS_NS ioctl */
+- ret = nvme_identify_ns (fd, nsid, &ns_info);
++ ns_info = _nvme_alloc (sizeof (struct nvme_id_ns));
++ g_warn_if_fail (ns_info != NULL);
++ 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);
++ free (ns_info);
+ return NULL;
+ }
+
+ /* send the NVME_IDENTIFY_CNS_CTRL ioctl */
+- ret_ctrl = nvme_identify_ctrl (fd, &ctrl_id);
++ ctrl_id = _nvme_alloc (sizeof (struct nvme_id_ctrl));
++ g_warn_if_fail (ctrl_id != NULL);
++ ret_ctrl = nvme_identify_ctrl (fd, ctrl_id);
+
+ /* send the NVME_IDENTIFY_CNS_NS_DESC_LIST ioctl, NVMe 1.3 */
+- if (ret_ctrl == 0 && GUINT32_FROM_LE (ctrl_id.ver) >= 0x10300)
+- ret_desc = nvme_identify_ns_descs (fd, nsid, (struct nvme_ns_id_desc *) &desc);
++ if (ret_ctrl == 0 && GUINT32_FROM_LE (ctrl_id->ver) >= 0x10300) {
++ descs = _nvme_alloc (NVME_IDENTIFY_DATA_SIZE);
++ g_warn_if_fail (descs != NULL);
++ ret_desc = nvme_identify_ns_descs (fd, nsid, descs);
++ }
+
+ /* send the NVME_IDENTIFY_CNS_CSI_INDEPENDENT_ID_NS ioctl, NVMe 2.0 */
+- if (ret_ctrl == 0 && GUINT32_FROM_LE (ctrl_id.ver) >= 0x20000)
+- ret_ns_ind = nvme_identify_independent_identify_ns (fd, nsid, &ns_info_ind);
++ if (ret_ctrl == 0 && GUINT32_FROM_LE (ctrl_id->ver) >= 0x20000) {
++ ns_info_ind = _nvme_alloc (sizeof (struct nvme_id_independent_id_ns));
++ g_warn_if_fail (ns_info_ind != NULL);
++ ret_ns_ind = nvme_identify_independent_identify_ns (fd, nsid, ns_info_ind);
++ }
+ 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->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)
++ 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)
++ 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->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;
+
+ if (ret_desc == 0) {
+ for (i = 0; i < NVME_IDENTIFY_DATA_SIZE; i += len) {
+- struct nvme_ns_id_desc *d = (void *) desc + i;
++ struct nvme_ns_id_desc *d = descs + i;
+
+ if (!d->nidl)
+ break;
+@@ -661,29 +691,29 @@ BDNVMENamespaceInfo *bd_nvme_get_namespace_info (const gchar *device, GError **e
+ }
+ }
+
+- if (info->nguid == NULL && !_nvme_a_is_zero (ns_info.nguid, sizeof (ns_info.nguid))) {
+- info->nguid = g_malloc0 (sizeof (ns_info.nguid) * 2 + 1);
+- for (i = 0; i < sizeof (ns_info.nguid); i++)
+- snprintf (info->nguid + i * 2, 3, "%02x", ns_info.nguid[i]);
++ if (info->nguid == NULL && !_nvme_a_is_zero (ns_info->nguid, sizeof (ns_info->nguid))) {
++ info->nguid = g_malloc0 (sizeof (ns_info->nguid) * 2 + 1);
++ for (i = 0; i < sizeof (ns_info->nguid); i++)
++ snprintf (info->nguid + i * 2, 3, "%02x", ns_info->nguid[i]);
+ }
+- if (info->eui64 == NULL && !_nvme_a_is_zero (ns_info.eui64, sizeof (ns_info.eui64))) {
+- info->eui64 = g_malloc0 (sizeof (ns_info.eui64) * 2 + 1);
+- for (i = 0; i < sizeof (ns_info.eui64); i++)
+- snprintf (info->eui64 + i * 2, 3, "%02x", ns_info.eui64[i]);
++ if (info->eui64 == NULL && !_nvme_a_is_zero (ns_info->eui64, sizeof (ns_info->eui64))) {
++ info->eui64 = g_malloc0 (sizeof (ns_info->eui64) * 2 + 1);
++ for (i = 0; i < sizeof (ns_info->eui64); i++)
++ snprintf (info->eui64 + i * 2, 3, "%02x", ns_info->eui64[i]);
+ }
+ if (ret_ns_ind == 0) {
+- if ((ns_info_ind.nsfeat & 1 << 4) == 1 << 4)
++ if ((ns_info_ind->nsfeat & 1 << 4) == 1 << 4)
+ info->features |= BD_NVME_NS_FEAT_ROTATIONAL;
+ }
+
+ /* 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++) {
++ 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;
++ 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;
+@@ -694,6 +724,10 @@ BDNVMENamespaceInfo *bd_nvme_get_namespace_info (const gchar *device, GError **e
+ g_ptr_array_add (ptr_array, NULL); /* trailing NULL element */
+ info->lba_formats = (BDNVMELBAFormat **) g_ptr_array_free (ptr_array, FALSE);
+
++ free (ctrl_id);
++ free (ns_info);
++ free (ns_info_ind);
++ free (descs);
+ return info;
+ }
+
+@@ -714,8 +748,8 @@ 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;
++ struct nvme_id_ctrl *ctrl_id;
++ struct nvme_smart_log *smart_log;
+ BDNVMESmartLog *log;
+ guint i;
+
+@@ -724,59 +758,66 @@ BDNVMESmartLog * bd_nvme_get_smart_log (const gchar *device, GError **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);
++ /* send the NVME_IDENTIFY_CNS_CTRL ioctl */
++ ctrl_id = _nvme_alloc (sizeof (struct nvme_id_ctrl));
++ g_warn_if_fail (ctrl_id != NULL);
++ 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);
++ free (ctrl_id);
+ return NULL;
+ }
+
+ /* send the NVME_LOG_LID_SMART ioctl */
+- ret = nvme_get_log_smart (fd, NVME_NSID_ALL, FALSE /* rae */, &smart_log);
++ smart_log = _nvme_alloc (sizeof (struct nvme_smart_log));
++ g_warn_if_fail (smart_log != NULL);
++ 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);
++ free (ctrl_id);
++ free (smart_log);
+ return NULL;
+ }
+ close (fd);
+
+ log = g_new0 (BDNVMESmartLog, 1);
+- if ((smart_log.critical_warning & NVME_SMART_CRIT_SPARE) == NVME_SMART_CRIT_SPARE)
++ 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)
++ 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)
++ 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)
++ 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)
++ 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)
++ 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);
++ 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);
++ 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
+@@ -784,6 +825,8 @@ BDNVMESmartLog * bd_nvme_get_smart_log (const gchar *device, GError **error) {
+ * Power State attributes. Subject to re-evaluation in the future.
+ */
+
++ free (ctrl_id);
++ free (smart_log);
+ return log;
+ }
+
+@@ -810,7 +853,7 @@ BDNVMEErrorLogEntry ** bd_nvme_get_error_log_entries (const gchar *device, GErro
+ int ret;
+ int fd;
+ guint elpe;
+- struct nvme_id_ctrl ctrl_id = ZERO_INIT;
++ struct nvme_id_ctrl *ctrl_id;
+ struct nvme_error_log_page *err_log;
+ GPtrArray *ptr_array;
+ guint i;
+@@ -821,23 +864,29 @@ BDNVMEErrorLogEntry ** bd_nvme_get_error_log_entries (const gchar *device, GErro
+ return NULL;
+
+ /* find out the maximum number of error log entries as reported by the controller */
+- ret = nvme_identify_ctrl (fd, &ctrl_id);
++ ctrl_id = _nvme_alloc (sizeof (struct nvme_id_ctrl));
++ g_warn_if_fail (ctrl_id != NULL);
++ 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);
++ free (ctrl_id);
+ return NULL;
+ }
+
++ elpe = ctrl_id->elpe + 1;
++ free (ctrl_id);
++
+ /* send the NVME_LOG_LID_ERROR ioctl */
+- elpe = ctrl_id.elpe + 1;
+- err_log = g_new0 (struct nvme_error_log_page, elpe);
++ err_log = _nvme_alloc (sizeof (struct nvme_error_log_page) * elpe);
++ g_warn_if_fail (err_log != NULL);
+ 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);
++ free (err_log);
+ return NULL;
+ }
+ close (fd);
+@@ -863,7 +912,7 @@ BDNVMEErrorLogEntry ** bd_nvme_get_error_log_entries (const gchar *device, GErro
+ }
+ }
+ g_ptr_array_add (ptr_array, NULL); /* trailing NULL element */
+- g_free (err_log);
++ free (err_log);
+
+ return (BDNVMEErrorLogEntry **) g_ptr_array_free (ptr_array, FALSE);
+ }
+@@ -885,7 +934,7 @@ BDNVMEErrorLogEntry ** bd_nvme_get_error_log_entries (const gchar *device, GErro
+ BDNVMESelfTestLog * bd_nvme_get_self_test_log (const gchar *device, GError **error) {
+ int ret;
+ int fd;
+- struct nvme_self_test_log self_test_log = ZERO_INIT;
++ struct nvme_self_test_log *self_test_log;
+ BDNVMESelfTestLog *log;
+ GPtrArray *ptr_array;
+ guint i;
+@@ -896,17 +945,20 @@ BDNVMESelfTestLog * bd_nvme_get_self_test_log (const gchar *device, GError **err
+ return NULL;
+
+ /* send the NVME_LOG_LID_DEVICE_SELF_TEST ioctl */
+- ret = nvme_get_log_device_self_test (fd, &self_test_log);
++ self_test_log = _nvme_alloc (sizeof (struct nvme_self_test_log));
++ g_warn_if_fail (self_test_log != NULL);
++ 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);
++ free (self_test_log);
+ return NULL;
+ }
+ close (fd);
+
+ log = g_new0 (BDNVMESelfTestLog, 1);
+- switch (self_test_log.current_operation & NVME_ST_CURR_OP_MASK) {
++ 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;
+@@ -921,8 +973,8 @@ BDNVMESelfTestLog * bd_nvme_get_self_test_log (const gchar *device, GError **err
+ 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;
++ 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++) {
+@@ -930,8 +982,8 @@ BDNVMESelfTestLog * bd_nvme_get_self_test_log (const gchar *device, GError **err
+ 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;
++ 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;
+
+@@ -987,21 +1039,22 @@ BDNVMESelfTestLog * bd_nvme_get_self_test_log (const gchar *device, GError **err
+ 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,
++ 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);
++ free (self_test_log);
+
+ return log;
+ }
+@@ -1026,7 +1079,6 @@ BDNVMESelfTestLog * bd_nvme_get_self_test_log (const gchar *device, GError **err
+ BDNVMESanitizeLog * bd_nvme_get_sanitize_log (const gchar *device, GError **error) {
+ int ret;
+ int fd;
+- char buf[65536] = ZERO_INIT;
+ struct nvme_sanitize_log_page *sanitize_log;
+ BDNVMESanitizeLog *log;
+ __u16 sstat;
+@@ -1037,20 +1089,18 @@ BDNVMESanitizeLog * bd_nvme_get_sanitize_log (const gchar *device, GError **erro
+ return NULL;
+
+ /* send the NVME_LOG_LID_SANITIZE ioctl */
+- ret = nvme_get_log_sanitize (fd, FALSE /* rae */, (struct nvme_sanitize_log_page *) &buf);
++ sanitize_log = _nvme_alloc (sizeof (struct nvme_sanitize_log_page));
++ g_warn_if_fail (sanitize_log != NULL);
++ 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);
++ free (sanitize_log);
+ return NULL;
+ }
+ close (fd);
+
+- /* need to use interim buffer that is large enough for broken drives
+- * returning more data than expected
+- */
+- sanitize_log = (struct nvme_sanitize_log_page *) &buf;
+-
+ log = g_new0 (BDNVMESanitizeLog, 1);
+ log->sanitize_progress = 0;
+ sstat = GUINT16_FROM_LE (sanitize_log->sstat);
+@@ -1085,5 +1135,6 @@ BDNVMESanitizeLog * bd_nvme_get_sanitize_log (const gchar *device, GError **erro
+ 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);
+
++ free (sanitize_log);
+ return log;
+ }
+diff --git a/src/plugins/nvme/nvme-op.c b/src/plugins/nvme/nvme-op.c
+index c9e92697c..dbef4f3a2 100644
+--- a/src/plugins/nvme/nvme-op.c
++++ b/src/plugins/nvme/nvme-op.c
+@@ -116,30 +116,37 @@ gboolean bd_nvme_device_self_test (const gchar *device, BDNVMESelfTestAction act
+ /* 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;
++ struct nvme_id_ns *ns_info;
+ __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);
++ ns_info = _nvme_alloc (sizeof (struct nvme_id_ns));
++ g_warn_if_fail (ns_info != NULL);
++ 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: ");
++ free (ns_info);
+ return 0xff;
+ }
+
+ /* return currently used lbaf */
+ if (lba_data_size == 0) {
+- nvme_id_ns_flbas_to_lbaf_inuse (ns_info.flbas, &flbas);
+- return flbas;
++ nvme_id_ns_flbas_to_lbaf_inuse (ns_info->flbas, &flbas);
++ free (ns_info);
++ 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)
++ 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) {
++ free (ns_info);
+ 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");
++ free (ns_info);
+ return 0xff;
+ }
+
+@@ -176,7 +183,7 @@ static __u8 find_lbaf_for_size (int fd, __u32 nsid, guint16 lba_data_size, guint
+ 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_id_ctrl *ctrl_id;
+ struct nvme_format_nvm_args args = {
+ .args_size = sizeof(args),
+ .result = NULL,
+@@ -207,11 +214,14 @@ gboolean bd_nvme_format (const gchar *device, guint16 lba_data_size, guint16 met
+
+ /* check the FNA controller bit when formatting a single namespace */
+ if (! ctrl_device) {
+- ret = nvme_identify_ctrl (args.fd, &ctrl_id);
++ ctrl_id = _nvme_alloc (sizeof (struct nvme_id_ctrl));
++ g_warn_if_fail (ctrl_id != NULL);
++ 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);
++ free (ctrl_id);
+ return FALSE;
+ }
+ /* from nvme-cli:
+@@ -219,14 +229,16 @@ gboolean bd_nvme_format (const gchar *device, guint16 lba_data_size, guint16 met
+ * 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) {
++ 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);
++ free (ctrl_id);
+ return FALSE;
+ }
++ free (ctrl_id);
+ }
+
+ /* find out the desired LBA data format index */
+diff --git a/src/plugins/nvme/nvme-private.h b/src/plugins/nvme/nvme-private.h
+index 8b6d13253..0d15fbb64 100644
+--- a/src/plugins/nvme/nvme-private.h
++++ b/src/plugins/nvme/nvme-private.h
+@@ -24,5 +24,7 @@ void _nvme_fabrics_errno_to_gerror (int result, int _errno, GError **error);
+ /* nvme-info.c */
+ G_GNUC_INTERNAL
+ gint _open_dev (const gchar *device, GError **error);
++G_GNUC_INTERNAL
++void *_nvme_alloc (size_t len);
+
+ #endif /* BD_NVME_PRIVATE */
diff --git a/libblockdev-gcc11.patch b/libblockdev-gcc11.patch
new file mode 100644
index 0000000..6e5fe35
--- /dev/null
+++ b/libblockdev-gcc11.patch
@@ -0,0 +1,15 @@
+diff --git a/src/plugins/kbd.c b/src/plugins/kbd.c
+index a2908ec..97abd3b 100644
+--- a/src/plugins/kbd.c
++++ b/src/plugins/kbd.c
+@@ -732,6 +732,10 @@ static gboolean wait_for_file (const char *filename) {
+ *
+ * Tech category: %BD_KBD_TECH_BCACHE-%BD_KBD_TECH_MODE_CREATE
+ */
++/* This triggers a known false positive warning in gcc-11. It's being
++ addressed upstream, but until the fix is available, this works around
++ the false positive. */
++__attribute__ ((optimize ("-O1")))
+ gboolean bd_kbd_bcache_create (const gchar *backing_device, const gchar *cache_device, const BDExtraArg **extra, const gchar **bcache_device, GError **error) {
+ const gchar *argv[6] = {"make-bcache", "-B", backing_device, "-C", cache_device, NULL};
+ gboolean success = FALSE;
diff --git a/libblockdev.spec b/libblockdev.spec
new file mode 100644
index 0000000..aed59c6
--- /dev/null
+++ b/libblockdev.spec
@@ -0,0 +1,2409 @@
+%define with_python2 1
+%define with_python3 1
+%define with_gtk_doc 1
+%define with_bcache 1
+%define with_btrfs 1
+%define with_crypto 1
+%define with_dm 1
+%define with_loop 1
+%define with_lvm 1
+%define with_lvm_dbus 1
+%define with_mdraid 1
+%define with_mpath 1
+%define with_swap 1
+%define with_kbd 1
+%define with_part 1
+%define with_fs 1
+%define with_nvdimm 1
+%define with_vdo 0
+%define with_gi 1
+%define with_escrow 1
+%define with_dmraid 1
+%define with_tools 1
+%define with_nvme 1
+
+# python2 is not available on RHEL > 7 and not needed on Fedora > 29
+%if 0%{?rhel} > 7 || 0%{?fedora} > 29 || %{with_python2} == 0
+%define with_python2 0
+%define python2_copts --without-python2
+%endif
+
+# python3 is not available on older RHEL
+%if (! 0%{?fedora} && 0%{?rhel} <= 7) || %{with_python3} == 0
+%define with_python3 0
+%define python3_copts --without-python3
+%endif
+
+# bcache is not available on RHEL
+%if (0%{?rhel}) || %{with_bcache} == 0
+%define with_bcache 0
+%define bcache_copts --without-bcache
+%endif
+
+# lvm_dbus is not available on older RHEL
+%if (! 0%{?fedora} && 0%{?rhel} <= 7) || %{with_lvm_dbus} == 0
+%define with_lvm_dbus 0
+%define lvm_dbus_copts --without-lvm-dbus
+%endif
+
+# vdo is not available on non-x86_64 on older RHEL
+%if (0%{?rhel} && 0%{?rhel} <= 7)
+%ifnarch x86_64
+%define with_vdo 0
+%define vdo_copts --without-vdo
+%endif
+%endif
+
+# btrfs is not available on RHEL > 7
+%if 0%{?rhel} > 7 || %{with_btrfs} == 0
+%define with_btrfs 0
+%define btrfs_copts --without-btrfs
+%endif
+
+# dmraid is not available on RHEL > 7
+%if 0%{?rhel} > 7
+%define with_dmraid 0
+%endif
+
+%if %{with_btrfs} != 1
+%define btrfs_copts --without-btrfs
+%endif
+%if %{with_crypto} != 1
+%define crypto_copts --without-crypto
+%else
+%if %{with_escrow} != 1
+%define crypto_copts --without-escrow
+%endif
+%endif
+%if %{with_dm} != 1
+%define dm_copts --without-dm
+%else
+%if %{with_dmraid} != 1
+%define dm_copts --without-dmraid
+%endif
+%endif
+%if %{with_loop} != 1
+%define loop_copts --without-loop
+%endif
+%if %{with_lvm} != 1
+%define lvm_copts --without-lvm
+%endif
+%if %{with_lvm_dbus} != 1
+%define lvm_dbus_copts --without-lvm_dbus
+%endif
+%if %{with_mdraid} != 1
+%define mdraid_copts --without-mdraid
+%endif
+%if %{with_mpath} != 1
+%define mpath_copts --without-mpath
+%endif
+%if %{with_swap} != 1
+%define swap_copts --without-swap
+%endif
+%if %{with_kbd} != 1
+%define kbd_copts --without-kbd
+%endif
+%if %{with_part} != 1
+%define part_copts --without-part
+%endif
+%if %{with_fs} != 1
+%define fs_copts --without-fs
+%endif
+%if %{with_nvdimm} != 1
+%define nvdimm_copts --without-nvdimm
+%endif
+%if %{with_vdo} != 1
+%define vdo_copts --without-vdo
+%endif
+%if %{with_tools} != 1
+%define tools_copts --without-tools
+%endif
+%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} %{?nvme_copts} %{?vdo_copts} %{?tools_copts} %{?gi_copts}
+
+Name: libblockdev
+Version: 2.28
+Release: 9%{?dist}
+Summary: A library for low-level manipulation with block devices
+License: LGPLv2+
+URL: https://github.com/storaged-project/libblockdev
+Source0: https://github.com/storaged-project/libblockdev/releases/download/%{version}-%{release}/%{name}-%{version}.tar.gz
+Patch0: libblockdev-gcc11.patch
+Patch1: 0001-lvm-devices-file-support.patch
+Patch2: 0002-Add-support-for-creating-and-activating-integrity-de.patch
+Patch3: 0003-NVMe-plugin-backport.patch
+Patch4: 0004-Fix-double-free-in-write_escrow_data_file.patch
+Patch5: 0005-nvme-Fix-namespace-identifiers.patch
+Patch6: 0006-Allow-resizing-of-inactive-LVs-with-latest-LVM.patch
+Patch7: 0007-tests-Fix-test_swapon_pagesize-on-systems-with-64k-p.patch
+Patch8: 0008-part-Fix-segfault-when-adding-a-partition-too-big-fo.patch
+Patch9: 0009-Fix-issues-in-tests-when-running-in-FIPS-mode.patch
+Patch10: 0010-lvm-Add-a-function-to-activate-LVs-in-shared-mode.patch
+Patch11: 0011-nvme_libblockdev-3.0.4_backport.patch
+
+BuildRequires: make
+BuildRequires: glib2-devel
+%if %{with_gi}
+BuildRequires: gobject-introspection-devel
+%endif
+%if %{with_python2}
+BuildRequires: python2-devel
+%else
+# Obsolete the python2 subpackage to avoid errors on upgrade
+Obsoletes: python2-blockdev < %{version}-%{release}
+%endif
+%if %{with_python3}
+BuildRequires: python3-devel
+%endif
+%if %{with_gtk_doc}
+BuildRequires: gtk-doc
+%endif
+BuildRequires: glib2-doc
+BuildRequires: autoconf-archive
+
+Requires: %{name}-utils%{?_isa} = %{version}-%{release}
+
+# Needed for the escrow tests in tests/crypto_test.py, but not used to build
+# BuildRequires: volume_key
+# BuildRequires: nss-tools
+
+# Needed for python 2 vs. 3 compatibility in the tests, but not used to build
+# BuildRequires: python2-six
+# BuildRequires: python3-six
+
+%description
+The libblockdev is a C library with GObject introspection support that can be
+used for doing low-level operations with block devices like setting up LVM,
+BTRFS, LUKS or MD RAID. The library uses plugins (LVM, BTRFS,...) and serves as
+a thin wrapper around its plugins' functionality. All the plugins, however, can
+be used as standalone libraries. One of the core principles of libblockdev is
+that it is stateless from the storage configuration's perspective (e.g. it has
+no information about VGs when creating an LV).
+
+%package devel
+Summary: Development files for libblockdev
+Requires: %{name}%{?_isa} = %{version}-%{release}
+Requires: glib2-devel
+Requires: %{name}-utils-devel%{?_isa} = %{version}-%{release}
+
+%description devel
+This package contains header files and pkg-config files needed for development
+with the libblockdev library.
+
+%if %{with_python2}
+%package -n python2-blockdev
+Summary: Python2 gobject-introspection bindings for libblockdev
+Requires: %{name}%{?_isa} = %{version}-%{release}
+
+%if 0%{?fedora} <= 26 || 0%{?rhel} <= 7
+Requires: pygobject3-base
+%else
+Requires: python2-gobject-base
+%endif
+%{?python_provide:%python_provide python2-blockdev}
+
+%description -n python2-blockdev
+This package contains enhancements to the gobject-introspection bindings for
+libblockdev in Python2.
+%endif
+
+%if %{with_python3}
+%package -n python3-blockdev
+Summary: Python3 gobject-introspection bindings for libblockdev
+Requires: %{name}%{?_isa} = %{version}-%{release}
+Requires: python3-gobject-base
+%{?python_provide:%python_provide python3-blockdev}
+
+%description -n python3-blockdev
+This package contains enhancements to the gobject-introspection bindings for
+libblockdev in Python3.
+%endif
+
+%package utils
+BuildRequires: kmod-devel
+Summary: A library with utility functions for the libblockdev library
+
+%description utils
+The libblockdev-utils is a library providing utility functions used by the
+libblockdev library and its plugins.
+
+%package utils-devel
+Summary: Development files for libblockdev-utils
+Requires: %{name}-utils%{?_isa} = %{version}-%{release}
+Requires: glib2-devel
+
+%description utils-devel
+This package contains header files and pkg-config files needed for development
+with the libblockdev-utils library.
+
+
+%if %{with_btrfs}
+%package btrfs
+BuildRequires: libbytesize-devel
+Summary: The BTRFS plugin for the libblockdev library
+Requires: %{name}-utils%{?_isa} = %{version}-%{release}
+Requires: btrfs-progs
+
+%description btrfs
+The libblockdev library plugin (and in the same time a standalone library)
+providing the BTRFS-related functionality.
+
+%package btrfs-devel
+Summary: Development files for the libblockdev-btrfs plugin/library
+Requires: %{name}-btrfs%{?_isa} = %{version}-%{release}
+Requires: glib2-devel
+Requires: %{name}-utils-devel%{?_isa} = %{version}-%{release}
+
+%description btrfs-devel
+This package contains header files and pkg-config files needed for development
+with the libblockdev-btrfs plugin/library.
+%endif
+
+
+%if %{with_crypto}
+%package crypto
+Requires: %{name}-utils%{?_isa} = %{version}-%{release}
+BuildRequires: cryptsetup-devel
+BuildRequires: libblkid-devel
+
+%if %{with_escrow}
+BuildRequires: volume_key-devel >= 0.3.9-7
+BuildRequires: nss-devel
+%endif
+
+Summary: The crypto plugin for the libblockdev library
+
+%description crypto
+The libblockdev library plugin (and in the same time a standalone library)
+providing the functionality related to encrypted devices (LUKS).
+
+%package crypto-devel
+Summary: Development files for the libblockdev-crypto plugin/library
+Requires: %{name}-crypto%{?_isa} = %{version}-%{release}
+Requires: %{name}-utils-devel%{?_isa} = %{version}-%{release}
+Requires: glib2-devel
+
+%description crypto-devel
+This package contains header files and pkg-config files needed for development
+with the libblockdev-crypto plugin/library.
+%endif
+
+
+%if %{with_dm}
+%package dm
+BuildRequires: device-mapper-devel
+%if %{with_dmraid}
+BuildRequires: dmraid-devel
+%endif
+BuildRequires: systemd-devel
+Summary: The Device Mapper plugin for the libblockdev library
+Requires: %{name}-utils%{?_isa} = %{version}-%{release}
+Requires: device-mapper
+%if %{with_dmraid}
+Requires: dmraid
+%endif
+
+%description dm
+The libblockdev library plugin (and in the same time a standalone library)
+providing the functionality related to Device Mapper.
+
+%package dm-devel
+Summary: Development files for the libblockdev-dm plugin/library
+Requires: %{name}-dm%{?_isa} = %{version}-%{release}
+Requires: glib2-devel
+Requires: device-mapper-devel
+Requires: systemd-devel
+%if %{with_dmraid}
+Requires: dmraid-devel
+%endif
+Requires: %{name}-utils-devel%{?_isa} = %{version}-%{release}
+
+%description dm-devel
+This package contains header files and pkg-config files needed for development
+with the libblockdev-dm plugin/library.
+%endif
+
+
+%if %{with_fs}
+%package fs
+BuildRequires: parted-devel
+BuildRequires: libblkid-devel
+BuildRequires: libmount-devel
+Summary: The FS plugin for the libblockdev library
+Requires: %{name}-utils%{?_isa} = %{version}-%{release}
+
+%description fs
+The libblockdev library plugin (and in the same time a standalone library)
+providing the functionality related to operations with file systems.
+
+%package fs-devel
+Summary: Development files for the libblockdev-fs plugin/library
+Requires: %{name}-fs%{?_isa} = %{version}-%{release}
+Requires: %{name}-utils-devel%{?_isa} = %{version}-%{release}
+Requires: glib2-devel
+Requires: xfsprogs
+Requires: dosfstools
+
+%description fs-devel
+This package contains header files and pkg-config files needed for development
+with the libblockdev-fs plugin/library.
+%endif
+
+
+%if %{with_kbd}
+%package kbd
+BuildRequires: libbytesize-devel
+Summary: The KBD plugin for the libblockdev library
+Requires: %{name}-utils%{?_isa} = %{version}-%{release}
+%if %{with_bcache}
+Requires: bcache-tools >= 1.0.8
+%endif
+
+%description kbd
+The libblockdev library plugin (and in the same time a standalone library)
+providing the functionality related to kernel block devices (namely zRAM and
+Bcache).
+
+%package kbd-devel
+Summary: Development files for the libblockdev-kbd plugin/library
+Requires: %{name}-kbd%{?_isa} = %{version}-%{release}
+Requires: %{name}-utils-devel%{?_isa} = %{version}-%{release}
+Requires: glib2-devel
+
+%description kbd-devel
+This package contains header files and pkg-config files needed for development
+with the libblockdev-kbd plugin/library.
+%endif
+
+
+%if %{with_loop}
+%package loop
+Summary: The loop plugin for the libblockdev library
+Requires: %{name}-utils%{?_isa} = %{version}-%{release}
+
+%description loop
+The libblockdev library plugin (and in the same time a standalone library)
+providing the functionality related to loop devices.
+
+%package loop-devel
+Summary: Development files for the libblockdev-loop plugin/library
+Requires: %{name}-loop%{?_isa} = %{version}-%{release}
+Requires: %{name}-utils-devel%{?_isa} = %{version}-%{release}
+Requires: glib2-devel
+
+%description loop-devel
+This package contains header files and pkg-config files needed for development
+with the libblockdev-loop plugin/library.
+%endif
+
+
+%if %{with_lvm}
+%package lvm
+BuildRequires: device-mapper-devel
+Summary: The LVM plugin for the libblockdev library
+Requires: %{name}-utils%{?_isa} = %{version}-%{release}
+Requires: lvm2
+
+%description lvm
+The libblockdev library plugin (and in the same time a standalone library)
+providing the LVM-related functionality.
+
+%package lvm-devel
+Summary: Development files for the libblockdev-lvm plugin/library
+Requires: %{name}-lvm%{?_isa} = %{version}-%{release}
+Requires: %{name}-utils-devel%{?_isa} = %{version}-%{release}
+Requires: glib2-devel
+
+%description lvm-devel
+This package contains header files and pkg-config files needed for development
+with the libblockdev-lvm plugin/library.
+%endif
+
+%if %{with_lvm_dbus}
+%package lvm-dbus
+BuildRequires: device-mapper-devel
+Summary: The LVM plugin for the libblockdev library
+Requires: %{name}-utils%{?_isa} = %{version}-%{release}
+Requires: lvm2-dbusd >= 2.02.156
+
+%description lvm-dbus
+The libblockdev library plugin (and in the same time a standalone library)
+providing the LVM-related functionality utilizing the LVM DBus API.
+
+%package lvm-dbus-devel
+Summary: Development files for the libblockdev-lvm-dbus plugin/library
+Requires: %{name}-lvm-dbus%{?_isa} = %{version}-%{release}
+Requires: %{name}-utils-devel%{?_isa} = %{version}-%{release}
+Requires: glib2-devel
+
+%description lvm-dbus-devel
+This package contains header files and pkg-config files needed for development
+with the libblockdev-lvm-dbus plugin/library.
+%endif
+
+
+%if %{with_mdraid}
+%package mdraid
+BuildRequires: libbytesize-devel
+Summary: The MD RAID plugin for the libblockdev library
+Requires: %{name}-utils%{?_isa} = %{version}-%{release}
+Requires: mdadm
+
+%description mdraid
+The libblockdev library plugin (and in the same time a standalone library)
+providing the functionality related to MD RAID.
+
+%package mdraid-devel
+Summary: Development files for the libblockdev-mdraid plugin/library
+Requires: %{name}-mdraid%{?_isa} = %{version}-%{release}
+Requires: %{name}-utils-devel%{?_isa} = %{version}-%{release}
+Requires: glib2-devel
+
+%description mdraid-devel
+This package contains header files and pkg-config files needed for development
+with the libblockdev-mdraid plugin/library.
+%endif
+
+
+%if %{with_mpath}
+%package mpath
+BuildRequires: device-mapper-devel
+Summary: The multipath plugin for the libblockdev library
+Requires: %{name}-utils%{?_isa} = %{version}-%{release}
+Requires: device-mapper-multipath
+
+%description mpath
+The libblockdev library plugin (and in the same time a standalone library)
+providing the functionality related to multipath devices.
+
+%package mpath-devel
+Summary: Development files for the libblockdev-mpath plugin/library
+Requires: %{name}-mpath%{?_isa} = %{version}-%{release}
+Requires: %{name}-utils-devel%{?_isa} = %{version}-%{release}
+Requires: glib2-devel
+
+%description mpath-devel
+This package contains header files and pkg-config files needed for development
+with the libblockdev-mpath plugin/library.
+%endif
+
+%if %{with_nvdimm}
+%package nvdimm
+BuildRequires: ndctl-devel
+BuildRequires: libuuid-devel
+Summary: The NVDIMM plugin for the libblockdev library
+Requires: %{name}-utils%{?_isa} = %{version}-%{release}
+Requires: ndctl
+
+%description nvdimm
+The libblockdev library plugin (and in the same time a standalone library)
+providing the functionality related to operations with NVDIMM devices.
+
+%package nvdimm-devel
+Summary: Development files for the libblockdev-nvdimm plugin/library
+Requires: %{name}-nvdimm%{?_isa} = %{version}-%{release}
+Requires: %{name}-utils-devel%{?_isa} = %{version}-%{release}
+Requires: glib2-devel
+
+%description nvdimm-devel
+This package contains header files and pkg-config files needed for development
+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} = %{version}-%{release}
+
+%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} = %{version}-%{release}
+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
+Summary: The partitioning plugin for the libblockdev library
+Requires: %{name}-utils%{?_isa} = %{version}-%{release}
+Requires: gdisk
+Requires: util-linux
+
+%description part
+The libblockdev library plugin (and in the same time a standalone library)
+providing the functionality related to partitioning.
+
+%package part-devel
+Summary: Development files for the libblockdev-part plugin/library
+Requires: %{name}-part%{?_isa} = %{version}-%{release}
+Requires: %{name}-utils-devel%{?_isa} = %{version}-%{release}
+Requires: glib2-devel
+
+%description part-devel
+This package contains header files and pkg-config files needed for development
+with the libblockdev-part plugin/library.
+%endif
+
+
+%if %{with_swap}
+%package swap
+BuildRequires: libblkid-devel
+Summary: The swap plugin for the libblockdev library
+Requires: %{name}-utils%{?_isa} = %{version}-%{release}
+Requires: util-linux
+
+%description swap
+The libblockdev library plugin (and in the same time a standalone library)
+providing the functionality related to swap devices.
+
+%package swap-devel
+Summary: Development files for the libblockdev-swap plugin/library
+Requires: %{name}-swap%{?_isa} = %{version}-%{release}
+Requires: %{name}-utils-devel%{?_isa} = %{version}-%{release}
+Requires: glib2-devel
+
+%description swap-devel
+This package contains header files and pkg-config files needed for development
+with the libblockdev-swap plugin/library.
+%endif
+
+
+%if %{with_vdo}
+%package vdo
+BuildRequires: libbytesize-devel
+BuildRequires: libyaml-devel
+Summary: The vdo plugin for the libblockdev library
+Requires: %{name}-utils%{?_isa} = %{version}-%{release}
+
+# weak dependencies doesn't work on older RHEL
+%if (0%{?rhel} && 0%{?rhel} <= 7)
+Requires: vdo
+Requires: kmod-kvdo
+%else
+# we want to build the plugin everywhere but the dependencies might not be
+# available so just use weak dependency
+Recommends: vdo
+Recommends: kmod-kvdo
+%endif
+
+%description vdo
+The libblockdev library plugin (and in the same time a standalone library)
+providing the functionality related to VDO devices.
+
+%package vdo-devel
+Summary: Development files for the libblockdev-vdo plugin/library
+Requires: %{name}-vdo%{?_isa} = %{version}-%{release}
+Requires: %{name}-utils-devel%{?_isa} = %{version}-%{release}
+Requires: glib2-devel
+
+%description vdo-devel
+This package contains header files and pkg-config files needed for development
+with the libblockdev-vdo plugin/library.
+%endif
+
+%if %{with_tools}
+%package tools
+Summary: Various nice tools based on libblockdev
+Requires: %{name} = %{version}-%{release}
+Requires: %{name}-lvm = %{version}-%{release}
+BuildRequires: libbytesize-devel
+%if %{with_lvm_dbus} == 1
+Recommends: %{name}-lvm-dbus
+%endif
+
+%description tools
+Various nice storage-related tools based on libblockdev.
+
+%endif
+
+%ifarch s390 s390x
+%package s390
+Summary: The s390 plugin for the libblockdev library
+Requires: s390utils
+Requires: %{name}-utils%{?_isa} = %{version}-%{release}
+
+%description s390
+The libblockdev library plugin (and in the same time a standalone library)
+providing the functionality related to s390 devices.
+
+%package s390-devel
+Summary: Development files for the libblockdev-s390 plugin/library
+Requires: %{name}-s390%{?_isa} = %{version}-%{release}
+Requires: %{name}-utils-devel%{?_isa} = %{version}-%{release}
+Requires: glib2-devel
+
+%description s390-devel
+This package contains header files and pkg-config files needed for development
+with the libblockdev-s390 plugin/library.
+%endif
+
+%package plugins-all
+Summary: Meta-package that pulls all the libblockdev plugins as dependencies
+Requires: %{name}%{?_isa} = %{version}-%{release}
+
+%if %{with_btrfs}
+Requires: %{name}-btrfs%{?_isa} = %{version}-%{release}
+%endif
+
+%if %{with_crypto}
+Requires: %{name}-crypto%{?_isa} = %{version}-%{release}
+%endif
+
+%if %{with_dm}
+Requires: %{name}-dm%{?_isa} = %{version}-%{release}
+%endif
+
+%if %{with_fs}
+Requires: %{name}-fs%{?_isa} = %{version}-%{release}
+%endif
+
+%if %{with_kbd}
+Requires: %{name}-kbd%{?_isa} = %{version}-%{release}
+%endif
+
+%if %{with_loop}
+Requires: %{name}-loop%{?_isa} = %{version}-%{release}
+%endif
+
+%if %{with_lvm}
+Requires: %{name}-lvm%{?_isa} = %{version}-%{release}
+%endif
+
+%if %{with_mdraid}
+Requires: %{name}-mdraid%{?_isa} = %{version}-%{release}
+%endif
+
+%if %{with_mpath}
+Requires: %{name}-mpath%{?_isa} = %{version}-%{release}
+%endif
+
+%if %{with_nvdimm}
+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
+
+%if %{with_swap}
+Requires: %{name}-swap%{?_isa} = %{version}-%{release}
+%endif
+
+%if %{with_vdo}
+Requires: %{name}-vdo%{?_isa} = %{version}-%{release}
+%endif
+
+%ifarch s390 s390x
+Requires: %{name}-s390%{?_isa} = %{version}-%{release}
+%endif
+
+%description plugins-all
+A meta-package that pulls all the libblockdev plugins as dependencies.
+
+
+%prep
+%setup -q -n %{name}-%{version}
+%patch0 -p1
+%patch1 -p1
+%patch2 -p1
+%patch3 -p1
+%patch4 -p1
+%patch5 -p1
+%patch6 -p1
+%patch7 -p1
+%patch8 -p1
+%patch9 -p1
+%patch10 -p1
+%patch11 -p1
+
+%build
+autoreconf -ivf
+%configure %{?configure_opts}
+%{__make} %{?_smp_mflags}
+
+%install
+%{make_install}
+find %{buildroot} -type f -name "*.la" | xargs %{__rm}
+
+
+%ldconfig_scriptlets
+%ldconfig_scriptlets utils
+
+%if %{with_btrfs}
+%ldconfig_scriptlets btrfs
+%endif
+
+%if %{with_crypto}
+%ldconfig_scriptlets crypto
+%endif
+
+%if %{with_dm}
+%ldconfig_scriptlets dm
+%endif
+
+%if %{with_fs}
+%ldconfig_scriptlets fs
+%endif
+
+%if %{with_loop}
+%ldconfig_scriptlets loop
+%endif
+
+%if %{with_lvm}
+%ldconfig_scriptlets lvm
+%endif
+
+%if %{with_lvm_dbus}
+%ldconfig_scriptlets lvm-dbus
+%endif
+
+%if %{with_mdraid}
+%ldconfig_scriptlets mdraid
+%endif
+
+%if %{with_mpath}
+%ldconfig_scriptlets mpath
+%endif
+
+%if %{with_nvdimm}
+%ldconfig_scriptlets nvdimm
+%endif
+
+%if %{with_nvme}
+%ldconfig_scriptlets nvme
+%endif
+
+%if %{with_part}
+%ldconfig_scriptlets part
+%endif
+
+%if %{with_swap}
+%ldconfig_scriptlets swap
+%endif
+
+%if %{with_vdo}
+%ldconfig_scriptlets vdo
+%endif
+
+%ifarch s390 s390x
+%ldconfig_scriptlets s390
+%endif
+
+%if %{with_kbd}
+%ldconfig_scriptlets kbd
+%endif
+
+
+%files
+%{!?_licensedir:%global license %%doc}
+%license LICENSE
+%{_libdir}/libblockdev.so.*
+%if %{with_gi}
+%{_libdir}/girepository*/BlockDev*.typelib
+%endif
+%dir %{_sysconfdir}/libblockdev
+%dir %{_sysconfdir}/libblockdev/conf.d
+%config %{_sysconfdir}/libblockdev/conf.d/00-default.cfg
+
+%files devel
+%doc features.rst specs.rst
+%{_libdir}/libblockdev.so
+%dir %{_includedir}/blockdev
+%{_includedir}/blockdev/blockdev.h
+%{_includedir}/blockdev/plugins.h
+%{_libdir}/pkgconfig/blockdev.pc
+%if %{with_gtk_doc}
+%{_datadir}/gtk-doc/html/libblockdev
+%endif
+%if %{with_gi}
+%{_datadir}/gir*/BlockDev*.gir
+%endif
+
+%if %{with_python2}
+%files -n python2-blockdev
+%{python2_sitearch}/gi/overrides/*
+%endif
+
+%if %{with_python3}
+%files -n python3-blockdev
+%{python3_sitearch}/gi/overrides/BlockDev*
+%{python3_sitearch}/gi/overrides/__pycache__/BlockDev*
+%endif
+
+%files utils
+%{_libdir}/libbd_utils.so.*
+%{_libdir}/libbd_part_err.so.*
+
+%files utils-devel
+%{_libdir}/libbd_utils.so
+%{_libdir}/libbd_part_err.so
+%{_libdir}/pkgconfig/blockdev-utils.pc
+%dir %{_includedir}/blockdev
+%{_includedir}/blockdev/utils.h
+%{_includedir}/blockdev/sizes.h
+%{_includedir}/blockdev/exec.h
+%{_includedir}/blockdev/extra_arg.h
+%{_includedir}/blockdev/dev_utils.h
+%{_includedir}/blockdev/module.h
+%{_includedir}/blockdev/dbus.h
+
+
+%if %{with_btrfs}
+%files btrfs
+%{_libdir}/libbd_btrfs.so.*
+
+%files btrfs-devel
+%{_libdir}/libbd_btrfs.so
+%dir %{_includedir}/blockdev
+%{_includedir}/blockdev/btrfs.h
+%endif
+
+
+%if %{with_crypto}
+%files crypto
+%{_libdir}/libbd_crypto.so.*
+
+%files crypto-devel
+%{_libdir}/libbd_crypto.so
+%dir %{_includedir}/blockdev
+%{_includedir}/blockdev/crypto.h
+%endif
+
+
+%if %{with_dm}
+%files dm
+%{_libdir}/libbd_dm.so.*
+
+%files dm-devel
+%{_libdir}/libbd_dm.so
+%dir %{_includedir}/blockdev
+%{_includedir}/blockdev/dm.h
+%endif
+
+
+%if %{with_fs}
+%files fs
+%{_libdir}/libbd_fs.so.*
+
+%files fs-devel
+%{_libdir}/libbd_fs.so
+%dir %{_includedir}/blockdev
+%dir %{_includedir}/blockdev/fs
+%{_includedir}/blockdev/fs.h
+%{_includedir}/blockdev/fs/*.h
+%endif
+
+
+%if %{with_kbd}
+%files kbd
+%{_libdir}/libbd_kbd.so.*
+
+%files kbd-devel
+%{_libdir}/libbd_kbd.so
+%dir %{_includedir}/blockdev
+%{_includedir}/blockdev/kbd.h
+%endif
+
+
+%if %{with_loop}
+%files loop
+%{_libdir}/libbd_loop.so.*
+
+%files loop-devel
+%{_libdir}/libbd_loop.so
+%dir %{_includedir}/blockdev
+%{_includedir}/blockdev/loop.h
+%endif
+
+
+%if %{with_lvm}
+%files lvm
+%{_libdir}/libbd_lvm.so.*
+
+%files lvm-devel
+%{_libdir}/libbd_lvm.so
+%dir %{_includedir}/blockdev
+%{_includedir}/blockdev/lvm.h
+%endif
+
+
+%if %{with_lvm_dbus}
+%files lvm-dbus
+%{_libdir}/libbd_lvm-dbus.so.*
+%config %{_sysconfdir}/libblockdev/conf.d/10-lvm-dbus.cfg
+
+%files lvm-dbus-devel
+%{_libdir}/libbd_lvm-dbus.so
+%dir %{_includedir}/blockdev
+%{_includedir}/blockdev/lvm.h
+%endif
+
+
+%if %{with_mdraid}
+%files mdraid
+%{_libdir}/libbd_mdraid.so.*
+
+%files mdraid-devel
+%{_libdir}/libbd_mdraid.so
+%dir %{_includedir}/blockdev
+%{_includedir}/blockdev/mdraid.h
+%endif
+
+
+%if %{with_mpath}
+%files mpath
+%{_libdir}/libbd_mpath.so.*
+
+%files mpath-devel
+%{_libdir}/libbd_mpath.so
+%dir %{_includedir}/blockdev
+%{_includedir}/blockdev/mpath.h
+%endif
+
+
+%if %{with_nvdimm}
+%files nvdimm
+%{_libdir}/libbd_nvdimm.so.*
+
+%files nvdimm-devel
+%{_libdir}/libbd_nvdimm.so
+%dir %{_includedir}/blockdev
+%{_includedir}/blockdev/nvdimm.h
+%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.*
+
+%files part-devel
+%{_libdir}/libbd_part.so
+%dir %{_includedir}/blockdev
+%{_includedir}/blockdev/part.h
+%endif
+
+
+%if %{with_swap}
+%files swap
+%{_libdir}/libbd_swap.so.*
+
+%files swap-devel
+%{_libdir}/libbd_swap.so
+%dir %{_includedir}/blockdev
+%{_includedir}/blockdev/swap.h
+%endif
+
+
+%if %{with_vdo}
+%files vdo
+%{_libdir}/libbd_vdo.so.*
+
+%files vdo-devel
+%{_libdir}/libbd_vdo.so
+%dir %{_includedir}/blockdev
+%{_includedir}/blockdev/vdo.h
+%endif
+
+%if %{with_tools}
+%files tools
+%{_bindir}/lvm-cache-stats
+%endif
+
+%ifarch s390 s390x
+%files s390
+%{_libdir}/libbd_s390.so.*
+
+%files s390-devel
+%{_libdir}/libbd_s390.so
+%dir %{_includedir}/blockdev
+%{_includedir}/blockdev/s390.h
+%endif
+
+%files plugins-all
+
+%changelog
+* Wed Nov 01 2023 Tomas Bzatek <tbzatek@redhat.com> - 2.28-9
+- nvme: HostID fixes for TP4126
+ Resolves: RHEL-1375
+- nvme: Stack smashing fixes
+ Resolves: RHEL-13127
+ Resolves: RHEL-8037
+
+* Tue Oct 17 2023 Vojtech Trefny <vtrefny@redhat.com> - 2.28-8
+- lvm: Add a function to activate LVs in shared mode
+ Resolves: RHEL-14018
+
+* Wed May 24 2023 Vojtech Trefny <vtrefny@redhat.com> - 2.28-7
+- Fix issues in tests when running in FIPS mode
+ Resolves: rhbz#2188749
+ Resolves: rhbz#2188603
+
+* Tue May 16 2023 Vojtech Trefny <vtrefny@redhat.com> - 2.28-6
+- Fix segfault when adding a partition too big for MSDOS
+ Resolves: rhbz#2185564
+
+* Mon Apr 03 2023 Vojtech Trefny <vtrefny@redhat.com> - 2.28-5
+- Allow resizing of inactive LVs with latest LVM
+ Resolves: rhbz#2161181
+- Fix test_swapon_pagesize on systems with 64k pages
+ Resolves: rhbz#2168220
+
+* Thu Jan 05 2023 Vojtech Trefny <vtrefny@redhat.com> - 2.28-4
+- nvme: Fix namespace identifiers
+ Resolves: rhbz#2151535
+- nvme: Avoid sending NVME_IDENTIFY_CNS_NS_DESC_LIST on older devices
+ Related: rhbz#2151535
+
+* Mon Nov 28 2022 Vojtech Trefny <vtrefny@redhat.com> - 2.28-3
+- Fix double free in write_escrow_data_file
+ Resolves: rhbz#2143226
+
+* Thu Sep 22 2022 Vojtech Trefny <vtrefny@redhat.com> - 2.28-2
+- NVMe plugin backport
+ Resolves: rhbz#2123338
+
+* Wed Sep 14 2022 Vojtech Trefny <vtrefny@redhat.com> - 2.28-1
+- Rebase to the latest upstream release 2.28
+ Resolves: rhbz#2123346
+- Add dependency on device-mapper-multipath to libblockdev-mpath
+ Resolves: rhbz#2121072
+- Fix spec issues found by rpminspect
+ Resolves: rhbz#2116544
+
+* Mon Aug 08 2022 Vojtech Trefny <vtrefny@redhat.com> - 2.25-14
+- tests: Fix expected extended partition flags with new parted
+ Related: rhbz#2109026
+- mdraid: Fix copy-paste error when checking return value
+ Related: rhbz#2109026
+
+* Mon Aug 08 2022 Vojtech Trefny <vtrefny@redhat.com> - 2.25-13
+- Fix getting UUID for DDF containers
+ Resolves: rhbz#2109026
+
+* Wed May 11 2022 Vojtech Trefny <vtrefny@redhat.com> - 2.25-12
+- tests: Lower expected free space on newly created Ext filesystems
+ Resolves: rhbz#2065943
+
+* Mon Jan 10 2022 Vojtech Trefny <vtrefny@redhat.com> - 2.25-11
+- tests: Wait for raid and mirrored LVs to be synced before removing
+ Resolves: rhbz#2030647
+- spec: Require the same version of utils for lvm-devel and lvm-dbus-devel
+ Resolves: rhbz#2028113
+
+* Wed Dec 08 2021 Vojtech Trefny <vtrefny@redhat.com> - 2.25-10
+- Fix reading statistics for VDO pools with VDO 8
+ Resolves: rhbz#1994220
+- vdo_stats: Default to 100 % savings for invalid savings values
+ Resolves: rhbz#2025880
+- Add support for creating and unlocking standalone integrity devices
+ Resolves: rhbz#2011365
+
+* Tue Nov 30 2021 Vojtech Trefny <vtrefny@redhat.com> - 2.25-9
+- Fix patch for 'Add support LVM devices file management'
+ Resolves: rhbz#1983705
+
+* Tue Nov 30 2021 Vojtech Trefny <vtrefny@redhat.com> - 2.25-8
+- Add support LVM devices file management
+ Resolves: rhbz#1983705
+
+* Mon Aug 09 2021 Mohan Boddu <mboddu@redhat.com> - 2.25-7
+- Rebuilt for IMA sigs, glibc 2.34, aarch64 flags
+ Related: rhbz#1991688
+
+* Tue Jun 29 2021 Vojtech Trefny <vtrefny@redhat.com> - 2.25-6
+- Add workarounds for some LVM test issues
+ Resolves: rhbz#1976174
+- Adapt tests to xfsprogs 5.12 changes
+ Resolves: rhbz#1976176
+
+* Tue May 18 2021 Vojtech Trefny <vtrefny@redhat.com> - 2.25-5
+- Adapt to dosfstools 4.2 changes
+ Resolves: rhbz#1960624
+
+* Thu May 13 2021 Vojtech Trefny <vtrefny@redhat.com> - 2.25-4
+- Memory leaks fixes backport
+ Resolves: rhbz#1938757
+- Fix default key size for non XTS ciphers
+ Resolves: rhbz#1954005
+
+* Fri Apr 16 2021 Mohan Boddu <mboddu@redhat.com> - 2.25-3
+- Rebuilt for RHEL 9 BETA on Apr 15th 2021. Related: rhbz#1947937
+
+* Tue Jan 26 2021 Fedora Release Engineering <releng@fedoraproject.org> - 2.25-2
+- Rebuilt for https://fedoraproject.org/wiki/Fedora_34_Mass_Rebuild
+
+* Mon Jan 11 2021 Vojtech Trefny <vtrefny@redhat.com> - 2.25-1
+- loop: Retry LOOP_SET_STATUS64 on EAGAIN (vtrefny)
+- Fix max size limit for LVM thinpool metadata (vtrefny)
+- lvm: Use the UNUSED macro instead of __attribute__((unused)) (vtrefny)
+- lvm: Do not use thin_metadata_size to recommend thin metadata size (vtrefny)
+- lvm: Set thin metadata limits to match limits LVM uses in lvcreate (vtrefny)
+- Mark all GIR file constants as guint64 (vtrefny)
+- lvm: Fix bd_lvm_vdopooldata_* symbols (tbzatek)
+- fs: Fix compile error in ext_repair caused by cherry pick from master (vtrefny)
+- README: Use CI status image for 2.x-branch on 2.x (vtrefny)
+- fs: Do not report error when errors were fixed by e2fsck (vtrefny)
+- tests: Add null-byte exec tests (tbzatek)
+- tests: Add bufferbloat exec tests (tbzatek)
+- exec: Clarify the BDUtilsProgExtract callback documentation (tbzatek)
+- exec: Use non-blocking read and process the buffer manually (tbzatek)
+- exec: Fix polling for stdout and stderr (tbzatek)
+- exec: Fix setting locale for util calls (vtrefny)
+
+* Thu Oct 01 2020 Vojtech Trefny <vtrefny@redhat.com> - 2.24-7
+- Do not build VDO plugin
+
+* Thu Sep 17 2020 Vojtech Trefny <vtrefny@redhat.com> - 2.24-6
+- exec: Fix setting locale for util calls
+
+* Thu Aug 20 2020 Vojtech Trefny <vtrefny@redhat.com> - 2.24-5
+- dm: Fix comparing DM RAID member devices UUID
+
+* Wed Aug 19 2020 Jeff Law <law@redhat.com> - 2.24-4
+- Work around gcc-11 false positive warning
+
+* Mon Jul 27 2020 Hans de Goede <hdegoede@redhat.com> - 2.24-3
+- Change -mpath subpackage Requires: device-mapper-multipath into Recommends
+- Related: rhbz#1857393
+
+* Sat May 23 2020 Miro Hrončok <mhroncok@redhat.com> - 2.24-2
+- Rebuilt for Python 3.9
+
+* Fri May 22 2020 Vojtech Trefny <vtrefny@redhat.com> - 2.24-1
+- Mark VDO plugin as deprecated since 2.24 (vtrefny)
+- Fix multiple uninitialized values discovered by coverity (vtrefny)
+- fs: Fix potential NULL pointer dereference in mount.c (vtrefny)
+- utils: Remove deadcode in exec.c (vtrefny)
+- Do not check VDO saving percent value in LVM DBus tests (vtrefny)
+- Use libblkid to get label and UUID for XFS filesystems (vtrefny)
+- Do not open devices as read-write for read-only fs operations (vtrefny)
+- Create a common function to get label and uuid of a filesystem (vtrefny)
+- lvm: Fix getting cache stats for cache thinpools (vtrefny)
+- Do not skip LVM VDO tests when the kvdo module is already loaded (vtrefny)
+- tests: Skip LVM VDO tests if kvdo module cannot be loaded (vtrefny)
+- lvm-dbus: Add LVM VDO pools to bd_lvm_lvs (vtrefny)
+- lvm: Add a function to get VDO pool name for a VDO LV (vtrefny)
+- lvm-dbus: Get data LV name for LVM VDO pools too (vtrefny)
+- Add functions to get VDO stats for LVM VDO volumes (vtrefny)
+- Move VDO statistics code to a separate file (vtrefny)
+- Fix copy-paste bug in lvm.api (vtrefny)
+- exec: Disable encoding when reading data from stdout/stderr (vtrefny)
+- Add function to get LVM VDO write policy from a string (vtrefny)
+- Add extra parameters for creating LVM VDO volumes (vtrefny)
+- Allow calling LVM functions without locking global_config_lock (vtrefny)
+- Fix getting VDO data in the LVM DBus plugin (vtrefny)
+- Fix getting string representation of unknown VDO state index (vtrefny)
+- Add write policy and index size to LVM VDO data (vtrefny)
+- Fix converting to VDO pool without name for the VDO LV (vtrefny)
+- Add some helper functions to get LVM VDO mode and state strings (vtrefny)
+- Add support for creating and managing VDO LVs with LVM (vtrefny)
+- Fix LVM plugin so names in tests (vtrefny)
+- Do not hardcode pylint executable name in Makefile (vtrefny)
+- Add a function to check if a tool supports given feature (vtrefny)
+- configure.ac: Avoid more bashisms (gentoo)
+- mount: Fix a memleak (tbzatek)
+- exec: Fix a memleak (tbzatek)
+- vdo: Fix a memleak (tbzatek)
+- configure.ac: Avoid bashisms (polynomial-c)
+- tests: Specify loader for yaml.load in VDO tests (vtrefny)
+- lvm-dbus: Fix memory leak in bd_lvm_thlvpoolname (vtrefny)
+- lvm-dbus: Do not activate LVs during pvscan --cache (vtrefny)
+- vdo: Run "vdo create" with "--force" (vtrefny)
+- Fix typo in (un)mount error messages (vtrefny)
+- utils: Add functions to get and check current linux kernel version (tbzatek)
+- ext: Return empty string instead of "<none>" for empty UUID (vtrefny)
+- Add support for BitLocker encrypted devices using cryptsetup (vtrefny)
+- Add a helper function for closing an active crypto device (vtrefny)
+- Manually remove symlinks not removed by udev in tests (vtrefny)
+- Fix memory leak in LVM DBus plugin (vtrefny)
+- Fix expected cache pool name with newest LVM (vtrefny)
+- fs: Fix checking for UID/GID == 0 (vtrefny)
+- Fixed a number of memory leaks in lvm-dbus plugin (mthompson)
+- exec.c: Fix reading outputs with null bytes (vtrefny)
+- Fix linking against utils on Debian (vtrefny)
+- Add new function 'bd_fs_wipe_force' to control force wipe (vtrefny)
+- Use 'explicit_bzero' to erase passphrases from key files (vtrefny)
+- Sync spec with downstream (vtrefny)
+
+* Wed Jan 29 2020 Fedora Release Engineering <releng@fedoraproject.org> - 2.23-2
+- Rebuilt for https://fedoraproject.org/wiki/Fedora_32_Mass_Rebuild
+
+* Mon Sep 09 2019 Vojtech Trefny <vtrefny@redhat.com> - 2.23-1
+- Fix how we get process exit code from g_spawn_sync (vtrefny)
+- Skip tests for old-style LVM snapshots on recent Fedora (vtrefny)
+- Fix skipping NTFS read-only test case on systems without NTFS (vtrefny)
+- Fix LVM_MAX_LV_SIZE in the GIR file (vtrefny)
+- Print skipped test "results" to stderr instead of stdout (vtrefny)
+- Move the NTFS read-only device test to a separate test case (vtrefny)
+- Fix parsing distro version from CPE name (vtrefny)
+- Use 'kmod_module_probe_insert_module' function for loading modules (vtrefny)
+- Hide filesystem-specific is_tech_available functions (vtrefny)
+- Mark LVM global config locks as static (vtrefny)
+- Remove unused 'get_PLUGIN_num_functions' and 'get_PLUGIN_functions' functions (vtrefny)
+- Mark 'private' plugin management functions as static (vtrefny)
+- Ignore coverity deadcode warning in 'bd_fs_is_tech_avail' (vtrefny)
+- Ignore coverity deadcode warnings in the generated code (vtrefny)
+- Use the new config file for skipping tests (vtrefny)
+- Skip bcache tests if make-bcache is not installed (vtrefny)
+- Add ability to read tests to skip from a config file (vtrefny)
+- Mark 'test_set_bitmap_location' as unstable (vtrefny)
+- Force LVM cli plugin in lvm_test (vtrefny)
+- Add a special test tag for library tests that recompile plugins (vtrefny)
+- Allow running tests against installed libblockdev (vtrefny)
+- Remove duplicate test case (vtrefny)
+- Use the new test tags in tests (vtrefny)
+- Use test tags for skipping tests (vtrefny)
+- Add a decorator for "tagging" tests (vtrefny)
+- Add function for (un)freezing filesystems (vtrefny)
+- Add a function to check whether a path is a mounpoint or not (vtrefny)
+- Skip bcache tests on all Debian versions (vtrefny)
+
+* Fri Aug 16 2019 Miro Hrončok <mhroncok@redhat.com> - 2.22-3
+- Rebuilt for Python 3.8
+
+* Thu Jul 25 2019 Fedora Release Engineering <releng@fedoraproject.org> - 2.22-2
+- Rebuilt for https://fedoraproject.org/wiki/Fedora_31_Mass_Rebuild
+
+* Wed Jun 12 2019 Vojtech Trefny <vtrefny@redhat.com> - 2.22-1
+- tests: Fix Debian testing "version" for skipping (vtrefny)
+- module: Fix libkmod related leak (tbzatek)
+- btrfs: Fix number of memory leaks (tbzatek)
+- mdraid: Fix leaking BDMDExamineData.metadata (tbzatek)
+- mdraid: Fix leaking error (tbzatek)
+- part: Fix leaking string in args (tbzatek)
+- ext: Fix leaking string (tbzatek)
+- part: Fix leaking objects (tbzatek)
+- kbd: Fix g_match_info_fetch() leaks (tbzatek)
+- ext: Fix g_match_info_fetch() leaks (tbzatek)
+- ext: Fix g_strsplit() leaks (tbzatek)
+- s390: Fix g_strsplit() leaks (tbzatek)
+- mdraid: Fix g_strsplit() leaks (tbzatek)
+- exec: Fix some memory leaks (tbzatek)
+- lvm: Fix leaking BDLVMPVdata.vg_uuid (tbzatek)
+- lvm: Use g_ptr_array_free() for creating lists (tbzatek)
+- lvm: Fix some obvious memory leaks (tbzatek)
+- Remove device-mapper-multipath dependency from fs and part plugins (vtrefny)
+- bd_fs_xfs_get_info: Allow passing error == NULL (tbzatek)
+- tests: Fix removing targetcli lun (vtrefny)
+- Use existing cryptsetup API for changing keyslot passphrase (vtrefny)
+- New function to get supported sector sizes for NVDIMM namespaces (vtrefny)
+- Allow skiping tests only based on architecture (vtrefny)
+- Sync spec file with python2 obsoletion added downstream (awilliam)
+- Sync spec with downstream (vtrefny)
+
+* Tue Apr 16 2019 Vojtech Trefny <vtrefny@redhat.com> - 2.21-3
+- Remove device-mapper-multipath dependency from fs and part plugins
+
+* Thu Feb 28 2019 Adam Williamson <awilliam@redhat.com> - 2.21-2
+- Obsolete the python2 subpackage if we're not building it
+
+* Thu Feb 21 2019 Vojtech Trefny <vtrefny@redhat.com> - 2.21-1
+- Fix checking swap status on lvm/md (vtrefny)
+- tests: Stop skipping some tests on Debian testing (vtrefny)
+- tests: Remove some old/irrelevant skips (vtrefny)
+- Use 512bit keys in LUKS by default (vratislav.podzimek)
+- Add 'autoconf-archive' to build requires (vtrefny)
+- vagrant: remove F27 and add F29 (vtrefny)
+- vagrant: install 'autoconf-archive' on Ubuntu (vtrefny)
+- Enable cryptsetup debug messages when compiled using --enable-debug (vtrefny)
+- lvm-dbus: Do not pass extra arguments enclosed in a tuple (vtrefny)
+- crypto: Do not try to use keyring on systems without keyring support (vtrefny)
+- Fix LUKS2 resize password test (vtrefny)
+- Use cryptsetup to check LUKS2 label (vtrefny)
+- Skip LUKS2+integrity test on systems without dm-integrity module (vtrefny)
+- Add custom error message for wrong passphrase for open (vtrefny)
+- Use major/minor macros from sys/sysmacros.h instead of linux/kdev_t.h (vtrefny)
+- crypto_test.py: Use blkid instead of lsblk to check luks label (vtrefny)
+- Skip VDO grow physical test (vtrefny)
+- Add libblkid-devel as a build dependency for the swap plugin (vtrefny)
+- Add error codes and Python exceptions for swapon fails (vtrefny)
+- Use libblkid to check swap status before swapon (vtrefny)
+- Add a new subpackage with the tool(s) (v.podzimek)
+- Document what the 'tools' directory contains (v.podzimek)
+- Make building tools optional (v.podzimek)
+- Add a tool for getting cached LVM statistics (v.podzimek)
+- Discard messages from libdevmapper in the LVM plugins (v.podzimek)
+
+* Fri Feb 01 2019 Fedora Release Engineering <releng@fedoraproject.org> - 2.20-3
+- Rebuilt for https://fedoraproject.org/wiki/Fedora_30_Mass_Rebuild
+
+* Mon Oct 08 2018 Vojtech Trefny <vtrefny@redhat.com> - 2.20-2
+- Use libblkid to check swap status before swapon (vtrefny)
+- Add error codes and Python exceptions for swapon fails (vtrefny)
+
+* Wed Sep 26 2018 Vojtech Trefny <vtrefny@redhat.com> - 2.20-1
+- Use unsafe caching for storage for devel/testing VMs (v.podzimek)
+- Require newer version of cryptsetup for LUKS2 tests (vtrefny)
+- Skip nvdimm tests on systems without ndctl (vtrefny)
+- Add Ubuntu 18.04 VM configuration to the vagrant template (vtrefny)
+- Add some missing test dependencies to the vagrant template (vtrefny)
+- Fix how/where the bcache tests are skipped (v.podzimek)
+- Document what the 'misc' directory contains (v.podzimek)
+- Add a Vagrantfile template (v.podzimek)
+- Fix the error message when deleting partition fails (vpodzime)
+- Fix build of plugins by changing linking order (devurandom)
+- Fix how we check zram stats from /sys/block/zram0/stat (vtrefny)
+- lvm-dbus: Fix parsing extra arguments for LVM methods calls (vtrefny)
+- Skip MDTestAddRemove on Debian (vtrefny)
+- Skip NTFS mount test on Debian testing (vtrefny)
+- Skip bcache tests on Debian testing (vtrefny)
+- tests: Try harder to get distribution version (vtrefny)
+- Mark the function stubs as static (v.podzimek)
+- Build the dm plugin without dmraid support on newer RHEL (vtrefny)
+- Fix skipping zram tests on Fedora 27 (vtrefny)
+- kbd: Check for zram module availability in 'bd_kbd_is_tech_avail' (vtrefny)
+- Always build the VDO plugin (vtrefny)
+- Do not require 'dmraid' package if built without dmraid support (vtrefny)
+- Fix licence header in dbus.c (vtrefny)
+- Fix spacing in NEWS.rst (vtrefny)
+
+* Fri Aug 10 2018 Vojtech Trefny <vtrefny@redhat.com> - 2.19-1
+- Use python interpreter explicitly when running boilerplate_generator.py (vtrefny)
+- vdo: Implement bd_vdo_get_stats() (tbzatek)
+- Add test for is_tech_available with multiple dependencies (vtrefny)
+- lvm-dbus.c: Check for 'lvmdbus' dependency in 'bd_lvm_is_tech_avail' (vtrefny)
+- lvm.c: Check for 'lvm' dependency in 'bd_lvm_is_tech_avail' (vtrefny)
+- Fix licence headers in sources (vtrefny)
+- Fix three memory leaks in lvm-dbus.c (vtrefny)
+- Ignore "bad-super-call" pylint warning in BlockDev.py (vtrefny)
+- Fix running pylint in tests (vtrefny)
+- Fix vdo configuration options definition in spec file (vtrefny)
+- Fix calling BlockDev.reinit in swap tests (vtrefny)
+- Fix how we check zram stats from /sys/block/zram0/mm_stat (vtrefny)
+- Skip VDO tests also when the 'kvdo' module is not available (vtrefny)
+- Add version to tests that should be skipped on CentOS/RHEL 7 (vtrefny)
+- Skip btrfs tests if btrfs module is not available (vtrefny)
+- Do not build KBD plugin with bcache support on RHEL (vtrefny)
+- Do not build btrfs plugin on newer RHEL (vtrefny)
+- fs: Properly close both ends of the pipe (tbzatek)
+- Make sure library_test works after fixing -Wstrict-prototypes (vtrefny)
+- Make sure library tests properly clean after themselves (vtrefny)
+- pkg-config: add -L${libdir} and -I${includedir} (max.kellermann)
+- plugins/kbd: make wait_for_file() static (max.kellermann)
+- plugins/lvm{,-dbus}: get_lv_type_from_flags() returns const string (max.kellermann)
+- plugins/dm: add explicit cast to work around -Wdiscarded-qualifiers (max.kellermann)
+- plugins/crypto: work around -Wdiscarded-qualifiers (max.kellermann)
+- plugins/check_deps: make all strings and `UtilDep` instances `const` (max.kellermann)
+- exec: make `msg` parameters const (max.kellermann)
+- fix -Wstrict-prototypes (max.kellermann)
+- module.c: Accept kernel modules if they are built-in (marco.guerri.dev)
+- BlockDev.py Convert dictionary keys to set before using them (vtrefny)
+- Skip 'test_cache_pool_create_remove' on CentOS 7 (vtrefny)
+- Re-order libbd_crypto_la_LIBADD to fix libtool issue (tom)
+- acinclude.m4: Use AS_EXIT to fail in LIBBLOCKDEV_FAILURES (vtrefny)
+- configure.ac: Fix missing parenthesis in blkid version check (vtrefny)
+- Allow specifying extra options for PBKDF when creating LUKS2 (vtrefny)
+- Reintroduce python2 support for Fedora 29 (vtrefny)
+- Use versioned command for Python 2 (vtrefny)
+- Fix few wrong names in doc strings (vtrefny)
+- Make sure all our free and copy functions work with NULL (vtrefny)
+- Use libblkid in bd_crypto_is_luks (vtrefny)
+- vdo: Properly destroy the yaml parser (tbzatek)
+- Add a simple test case for bd_crypto_tc_open (vtrefny)
+- Add Python override for bd_crypto_tc_open_full (vtrefny)
+- Show simple summary after configure (vtrefny)
+- Do not build VDO plugin on non-x86_64 architectures (vtrefny)
+- Sync spec with downstream (vtrefny)
+
+* Fri Jul 13 2018 Fedora Release Engineering <releng@fedoraproject.org> - 2.18-4
+- Rebuilt for https://fedoraproject.org/wiki/Fedora_29_Mass_Rebuild
+
+* Mon Jul 09 2018 Vojtech Trefny <vtrefny@redhat.com> - 2.18-3
+- Reitroduce python2 support for Fedora 29
+
+* Mon Jul 02 2018 Miro Hrončok <mhroncok@redhat.com> - 2.18-2
+- Rebuilt for Python 3.7
+
+* Wed Jun 20 2018 Vojtech Trefny <vtrefny@redhat.com> - 2.18-1
+- Add VDO to features.rst (vtrefny)
+- Remove roadmap.rst (vtrefny)
+- vdo: Add tests for bd_vdo_grow_physical() (tbzatek)
+- Do not try to build VDO plugin on Fedora (vtrefny)
+- Introduce reporting function per thread (kailueke)
+- vdo: Implement bd_vdo_grow_physical() (tbzatek)
+- Correct arguments for ext4 repair with progress (kailueke)
+- Clarify that checking an RW-mounted XFS file system is impossible (v.podzimek)
+- vdo: Resolve real device file path (tbzatek)
+- Adjust to new NVDIMM namespace modes (vtrefny)
+- Use xfs_repair instead of xfs_db in bd_fs_xfs_check() (v.podzimek)
+- Allow compiling libblockdev without libdmraid (vtrefny)
+- Only require plugins we really need in LVM dbus tests (vtrefny)
+- Add tests for VDO plugin (vtrefny)
+- Add decimal units definition to utils/sizes.h (vtrefny)
+- Add basic VDO plugin functionality (vtrefny)
+- Add the VDO plugin (vtrefny)
+- Always check for error when (un)mounting (vtrefny)
+- Fix off-by-one error when counting TCRYPT keyfiles (segfault)
+- Add 'bd_dm_is_tech_avail' to header file (vtrefny)
+- Fix release number in NEWS.rst (vtrefny)
+- Update specs.rst and features.rst (vtrefny)
+
+* Fri Jun 15 2018 Miro Hrončok <mhroncok@redhat.com> - 2.17-2
+- Rebuilt for Python 3.7
+
+* Tue Apr 24 2018 Vojtech Trefny <vtrefny@redhat.com> - 2.17-1
+- Redirect cryptsetup log to libblockdev log (vtrefny)
+- Add a generic logging function for libblockdev (vtrefny)
+- Add functions to resize LUKS 2 (vtrefny)
+- Add function to get information about LUKS 2 integrity devices (vtrefny)
+- Add function to get information about a LUKS device (vtrefny)
+- Add a basic test for creating LUKS 2 format (vtrefny)
+- Use libblockdev function to create LUKS 2 in tests (vtrefny)
+- Add support for creating LUKS 2 format (vtrefny)
+- Skip bcache tests on Rawhide (vtrefny)
+- Allow building libblockdev without Python 2 support (vtrefny)
+- Allow compiling libblockdev crypto plugin without escrow support (vtrefny)
+- Require at least libndctl 58.4 (vtrefny)
+- New function for luks metadata size (japokorn)
+- Add functions to backup and restore LUKS header (vtrefny)
+- Add function for killing keyslot on a LUKS device (vtrefny)
+- Add functions to suspend and resume a LUKS device (vtrefny)
+- Use '=' instead of '==' to compare using 'test' (v.podzimek)
+- lvm-dbus: Check returned job object for error (vtrefny)
+- Get sector size for non-block NVDIMM namespaces too (vtrefny)
+- Fix memory leaks discovered by clang (vtrefny)
+- Add new functions to docs/libblockdev-sections.txt (segfault)
+- Make a link point to the relevant section (segfault)
+- Don't use VeraCrypt PIM if compiled against libcryptsetup < 2.0 (segfault)
+- Make keyfiles parameter to bd_crypto_tc_open_full zero terminated (segfault)
+- Add function bd_crypto_device_seems_encrypted (segfault)
+- Support VeraCrypt PIM (segfault)
+- Support TCRYPT system volumes (segfault)
+- Support TCRYPT hidden containers (segfault)
+- Support TCRYPT keyfiles (segfault)
+- Support unlocking VeraCrypt volumes (segfault)
+- Enforce ZERO_INIT gcc backwards compatibility (bjornpagen)
+- Add function for getting NVDIMM namespace name from devname or path (vtrefny)
+- Add --without-xyz to DISTCHECK_CONFIGURE_FLAGS for disabled plugins (vtrefny)
+- Add tests for the NVDIMM plugin (vtrefny)
+- Add the NVDIMM plugin (vtrefny)
+- Fix build with clang (bjornpagen)
+- s390: don't hardcode paths, search PATH (flokli)
+- Fix build against musl libc (bjornpagen)
+- Fix python2-gobject-base dependency on Fedora 26 and older (vtrefny)
+- Sync the spec file with downstream (vtrefny)
+
+* Wed Apr 11 2018 Vojtech Trefny <vtrefny@redhat.com> - 2.16-3
+- Add the NVDIMM plugin (vtrefny)
+- Add tests for the NVDIMM plugin (vtrefny)
+- Add --without-xyz to DISTCHECK_CONFIGURE_FLAGS for disabled plugins (vtrefny)
+- Add function for getting NVDIMM namespace name from devname or path (vtrefny)
+
+* Fri Feb 09 2018 Igor Gnatenko <ignatenkobrain@fedoraproject.org> - 2.16-2
+- Escape macros in %%changelog
+
+* Thu Feb 08 2018 Vojtech Trefny <vtrefny@redhat.com> - 2.16-1
+- Add tests for progress report (jtulak)
+- Add e2fsck progress (jtulak)
+- Add progress reporting infrastructure for Ext fsck (jtulak)
+- Add a function to test if prog. reporting was initialized (jtulak)
+- Add support for LUKS 2 opening and key management (vtrefny)
+- Fix few more links for project and documentation website (vtrefny)
+- Sync the spec file with downstream (vpodzime)
+- Check if 'journalctl' is available before trying to use it in tests (vtrefny)
+- Update 'Testing libblockdev' section in documentation (vtrefny)
+- Fix link to online documentation (vtrefny)
+- Fix how the new kernel module functions are added to docs (vpodzime)
+
+* Wed Feb 07 2018 Fedora Release Engineering <releng@fedoraproject.org> - 2.15-4
+- Rebuilt for https://fedoraproject.org/wiki/Fedora_28_Mass_Rebuild
+
+* Wed Feb 07 2018 Iryna Shcherbina <ishcherb@redhat.com> - 2.15-3
+- Update Python 2 dependency declarations to new packaging standards
+ (See https://fedoraproject.org/wiki/FinalizingFedoraSwitchtoPython3)
+
+* Sat Feb 03 2018 Igor Gnatenko <ignatenkobrain@fedoraproject.org> - 2.15-2
+- Switch to %%ldconfig_scriptlets
+
+* Fri Dec 01 2017 Vratislav Podzimek <vpodzime@redhat.com> - 2.15-1
+- Do not use the 'btrfs' plugin in overrides tests (vpodzime)
+- Do not use the btrfs plugin in library tests (vpodzime)
+- Check for btrfs module availability in btrfs module (vtrefny)
+- Move kernel modules (un)loading and checking into utils (vtrefny)
+- Free locale struct in kbd plugin (vtrefny)
+- Add test for setting partition flags on GPT (vtrefny)
+- Use only sgdisk to set flags on GPT (vtrefny)
+- Move the fs.h file to its original place (vpodzime)
+- Add a HACKING.rst file (vpodzime)
+- Mark bcache tests as unstable (vpodzime)
+- Fix memory leaks in bd_fs_vfat_get_info() (vpodzime)
+- Revert the behaviour of bd_fs_check_deps() (vpodzime)
+- Split the bd_fs_is_tech_avail() implementation (vpodzime)
+- Split the FS plugin source into multiple files (vpodzime)
+- Fix bd_s390_dasd_format (vponcova)
+- Mark unstable tests as such (vpodzime)
+- bd_s390_dasd_is_ldl should be true only for LDL DADSs (vponcova)
+- Do not lie about tag creation (vpodzime)
+
+* Wed Nov 08 2017 Zbigniew Jędrzejewski-Szmek <zbyszek@in.waw.pl> - 2.14-2
+- Rebuild for cryptsetup-2.0.0
+
+* Tue Oct 31 2017 Vratislav Podzimek <vpodzime@redhat.com> - 2.14-1
+- Support the legacy boot GPT flag (intrigeri)
+- Respect the version in the blockdev.pc file (vpodzime)
+- Add pkgconfig definitions for the utils library (vpodzime)
+- fs.c: Fix potential NULL pointer dereference (vtrefny)
+- dm.c: Fix uninitialized values in various dm plugin functions (vtrefny)
+- dm.c: Check return values of dm_task_set_name/run/get_info functions (vtrefny)
+- fs.c: Fix multiple "forward NULL" warnings in 'bd_fs_ntfs_get_info' (vtrefny)
+- lvm-dbus.c: Fix multiple "use after free" coverity warnings (vtrefny)
+- Fix duplicate 'const' in generated functions (vtrefny)
+- Add some test cases for NTFS (kailueke)
+- Add function wrappers for NTFS tools (kailueke)
+- exec.c: Fix error message in 'bd_utils_exec_and_report_progress' (vtrefny)
+- crypto.c: Fix waiting for enough entropy (vtrefny)
+- Ignore some coverity false positive errors (vtrefny)
+- exec.c: Ignore errors from 'g_io_channel_shutdown' (vtrefny)
+- part.c: Check if we've found a place to put new logical partitions (vtrefny)
+- kbd.c: Fix potential string overflow in 'bd_kbd_bcache_create' (vtrefny)
+- exec.c: Fix resource leaks in 'bd_utils_exec_and_report_progress' (vtrefny)
+- fs.c: Fix "forward null" in 'do_mount' and 'bd_fs_xfs_get_info' (vtrefny)
+- part.c: Fix possible NULL pointer dereference (vtrefny)
+- crypto.c: Use right key buffer in 'bd_crypto_luks_add_key' (vtrefny)
+- exec.c: Fix "use after free" in 'bd_utils_check_util_version' (vtrefny)
+- kbd.c: Fix double free in 'bd_kbd_zram_get_stats' (vtrefny)
+- part.c: Check if file discriptor is >= 0 before closing it (vtrefny)
+- mdraid.c: Fix resource leaks (vtrefny)
+- lvm.c: Fix "use after free" in 'bd_lvm_get_thpool_meta_size' (vtrefny)
+- fs.c: Fix for loop condition in 'bd_fs_get_fstype' (vtrefny)
+- fs.c: Check sscanf return value in 'bd_fs_vfat_get_info' (vtrefny)
+- fs.c: Fix resource leaks in 'bd_fs_get_fstype' (vtrefny)
+- blockdev.c.in: Fix unused variables (vtrefny)
+- Use libbytesize to parse bcache block size (vtrefny)
+- Use system values in KbdTestBcacheStatusTest (vtrefny)
+- Fix BSSize memory leaks in btrfs and mdraid plugins (vtrefny)
+- Skip btrfs subvolume tests with btrfs-progs 4.13.2 (vtrefny)
+- Added function to get DM device subsystem (japokorn)
+- Sync spec with downstream (vpodzime)
+
+* Fri Sep 29 2017 Vratislav Podzimek <vpodzime@redhat.com> - 2.13-1
+- Fix the rpmlog and shortlog targets (vpodzime)
+- Add a function for enabling/disabling plugins' init checks (vpodzime)
+- Assign functions to tech-mode categories (vpodzime)
+- Add missing items to particular sections in the documentation (vpodzime)
+- Add a basic test for the runtime dependency checking (vpodzime)
+- Simplify what WITH_BD_BCACHE changes in the KBD plugin (vpodzime)
+- Add functions for querying available technologies (vpodzime)
+- Dynamically check for the required utilities (vpodzime)
+- Use shorter prefix for tempfiles (vtrefny)
+- Try harder when waiting for lio device to show up (vtrefny)
+- Better handle old and new zram sysfs api in tests (vtrefny)
+- Skip btrfs tests on CentOS 7 aarch64 (vtrefny)
+- Add new function for setting swap label (vtrefny)
+- Use only one git tag for new releases (vtrefny)
+- Fix source URL in spec file (vtrefny)
+- Add NEWS.rst file (vtrefny)
+- Do not include s390utils/vtoc.h in s390 plugin (vtrefny)
+- Use "AC_CANONICAL_BUILD" to check architecture instead of "uname" (vtrefny)
+- Bypass error proxy in s390 test (vtrefny)
+- Fix zFCP LUN max length (vtrefny)
+- Do not run g_clear_error after setting it (vtrefny)
+- Allow compiling libblockdev without s390 plugin (vtrefny)
+- Add a function for getting plugin name (vpodzime)
+
+* Thu Sep 28 2017 Troy Dawson <tdawson@redhat.com> - 2.12-3
+- Cleanup spec file conditionals correctly
+
+* Wed Sep 27 2017 Troy Dawson <tdawson@redhat.com> - 2.12-2
+- Cleanup spec file conditionals
+
+* Wed Aug 30 2017 Vratislav Podzimek <vpodzime@redhat.com> - 2.12-1
+- Own directories /etc/libblockdev and /etc/libblockdev/conf.d (vtrefny)
+- Wait for resized partition (kailueke)
+- Make sure the device is opened for libparted (vpodzime)
+- Fix label check in swap_test (vtrefny)
+- Use "run_tests" script for running tests from Makefile (vtrefny)
+- Add a script for running tests (vtrefny)
+- Tests: Move library initialization to setUpClass method (vtrefny)
+- Stop skipping FAT resize tests on rawhide (vtrefny)
+- Close filesystem before closing the partition during FAT resize (vtrefny)
+- Use mountpoint for "xfs_info" calls (vtrefny)
+- Use libmount cache when parsing /proc/mounts (vtrefny)
+- Add some space for the CI status (vpodzime)
+- Confirm the force when creating PVs in FS tests (vpodzime)
+- Skip vgremove tests on 32bit Debian (vtrefny)
+- Fix names of backing files in tests (vtrefny)
+- Fix checking for available locales (vtrefny)
+- Skip dependency checking in mpath tests on Debian (vtrefny)
+- Skip zRAM tests on Debian (vtrefny)
+- Skip the test for device escrow on Debian too (vtrefny)
+- Skip free region tests on Debian too (vtrefny)
+- Fix redirecting command output to /dev/null in tests (vtrefny)
+- Try harder to unmount devices in test cleanup (vtrefny)
+- Require only plugins that are needed for given test (vtrefny)
+- Try to get distribution info from "PrettyName" if "CPEName" isn't available (vtrefny)
+- Use -ff when creating PVs in FS tests (vpodzime)
+- Sync spec with downstream (vpodzime)
+
+* Mon Jul 31 2017 Vratislav Podzimek <vpodzime@redhat.com> - 2.11-1
+- Make the KbdZRAMDevicesTestCase inherit from KbdZRAMTestCase (vpodzime)
+- Allow non-source directory builds (kailueke)
+- Add a way to disable runtime dependency checks (vpodzime)
+- Link to GObject even if no plugin is activated (kailueke)
+- Skip zram tests on Rawhide (vpodzime)
+- Keep most utilities available for tests (vpodzime)
+- Use new libmount function to get (un)mount error message (vtrefny)
+- Update the documentation URL (vpodzime)
+
+* Wed Jul 26 2017 Fedora Release Engineering <releng@fedoraproject.org> - 2.10-2
+- Rebuilt for https://fedoraproject.org/wiki/Fedora_27_Mass_Rebuild
+
+* Wed Jul 05 2017 Vratislav Podzimek <vpodzime@redhat.com> - 2.10-1
+- Only enable partition size tolerance with alignment (vpodzime)
+- Limit the requested partition size to maximum possible (vpodzime)
+- Do not verify vfat FS' size after generic resize (vpodzime)
+- Specify tolerance for partition size (kailueke)
+- Only use the exact constraint if not using any other (vpodzime)
+- Check resulting FS size in tests for generic FS resize (vpodzime)
+- Query setting FS label support and generic relabeling (kailueke)
+- Do not strictly require all FS utilities (vpodzime)
+- Compile everything with the C99 standard (vpodzime)
+- Add partition resize function (kailueke)
+- Generic Check and Repair Functions (kailueke)
+- Query functions for FS resize and repair support (kailueke)
+- Update the project/source URL in the spec file (vpodzime)
+- Add functions for opening/closing TrueCrypt/VeraCrypt volumes (vpodzime)
+- Adapt to a change in behaviour in new libmount (vpodzime)
+- Try RO mount also if we get EACCES (vpodzime)
+- Size in bytes for xfs_resize_device (kailueke)
+- src/plugins/Makefile.am: Remove hard coded include path in /usr prefix (tristan.vanberkom)
+- Fixed include for libvolume_key.h (tristan.vanberkom)
+- Ignore parted warnings if possible (squimrel)
+- bcache tests: Remove FEELINGLUCKY checks (tasleson)
+- kbd.c: Code review corrections (tasleson)
+- kbd.c: Make bd_kbd_bcache_create work without abort (tasleson)
+
+* Tue Jun 13 2017 Vratislav Podzimek <vpodzime@redhat.com> - 2.9-1
+- Fix hardcoded reference to gcc (timo.gurr)
+- Catch and ignore partial failures in LVM tests' cleanups (vpodzime)
+- Fix hardcoded reference to pkg-config (timo.gurr)
+- Make GObject introspection optional (vpodzime)
+- Do not link libraries required by plugins to the GI files (vpodzime)
+- Make sure the whole build status image is shown (vpodzime)
+- Show CI status in README (at the GH repo's front page) (vpodzime)
+- Always require the libudev pkg (tgurr)
+- Make sure we give kernel time to fully setup zram device(s) (vpodzime)
+- fs_test.py: Close FDs when calling utilities (tasleson)
+- crypto.c: Correct segmentation fault (tasleson)
+
+* Tue Jun 06 2017 Vratislav Podzimek <vpodzime@redhat.com> - 2.8-1
+- Temporarily skip vfat generic resize test on rawhide (vtrefny)
+- Use "safeprobe" in "bd_fs_wipe" (vtrefny)
+- Add a generic filesystem resize function (vtrefny)
+- Add a function to get mountpoint for a device (vtrefny)
+- Add a function to get filesystem type for a device (vtrefny)
+- Only include the LVM DBus config when shipping LVM DBus (vpodzime)
+- Skip the LVM DBus vgreduce tests on Rawhide (vpodzime)
+- Do not build the lvm-dbus plugin on RHEL/CentOS (vpodzime)
+- Give zRAM more time to settle before trying to remove a device (vpodzime)
+- Put zram tests adding/removing devices into a separate class (vpodzime)
+- Skip LVM cache and RAID tests on Rawhide (vpodzime)
+- Fix the skip_on decorator factory for tests (vpodzime)
+- Use 'blkid -p' instead of lsblk to get device's FS type (vpodzime)
+- Improve the lvm_set_global_config test (vpodzime)
+- Pass '-y' to pvresize (vpodzime)
+- Create a copy of os.environ for a child process (vpodzime)
+- Revert "Use the "C.UTF-8" locale instead of just "C"" (vpodzime)
+- Fix how we create vfat FS in tests (vpodzime)
+- Skip the test if requiring unavailable locales (vpodzime)
+- Use the "C.UTF-8" locale instead of just "C" (vpodzime)
+- Add functions for working with ext2 and ext3 filesystems (vtrefny)
+- Link to gobject when lvm or btrfs is enabled (andreas)
+- Link to libm where needed (andreas)
+- Add a function for cleaning a device (vtrefny)
+- Add few code exaples to documentation (vtrefny)
+- Use a special exception for no fs signature when doing wipe (vpodzime)
+- One more incompatible os.symlink() call (vpodzime)
+- Do not use pass-by-name in the os.symlink() call (vpodzime)
+- Ignore previous errors when falling back to using ioctl() (vpodzime)
+- Use ioctl() call to teardown loop devices (vpodzime)
+- Resolve the device to remove for mdadm (vpodzime)
+- Add a function for getting device symlinks (vpodzime)
+- Use the new resolve_device() function where appropriate (vpodzime)
+- Add the resolve_device() function to the utils library (vpodzime)
+- First try to read the 'autoclear' flag from /sys/ (vpodzime)
+
+* Wed Apr 26 2017 Vratislav Podzimek <vpodzime@redhat.com> - 2.7-1
+- Skip btrfs min size tests on Fedora 25 (vtrefny)
+- Make sure the loop device doesn't disappear during tests (vpodzime)
+- Close the loop device when autoclear is (un)set (vpodzime)
+- Do not enforce Python 3 for running tests in CI (vpodzime)
+- Revert "Use different BTRFS_MIN_MEMBER_SIZE on aarch64" (vtrefny)
+- Use both 'old' and 'new' sysfs files to read zRAM stats (vtrefny)
+- Check if libparted-fs-resize pkgconfig is available (vpodzime)
+- Do not try to get name for inactive partitions (vtrefny)
+- Skip tests for getting free regions on CentOS/RHEL (vpodzime)
+- Free the container holding the specs of free regions (vpodzime)
+- Open loop devices as O_RDONLY when getting flags (vpodzime)
+- Resolve maximum partition size when we know its start (vpodzime)
+- Use --id instead of --part-type when setting partition id (vpodzime)
+- Fix mdadm command for removing failed device from an array (vtrefny)
+- Skip bcache tests on CentOS/RHEL 7 (vpodzime)
+- Use six.assertRaisesRegex in the FS tests (vpodzime)
+- Use mkdtemp() instead of TemporaryDirectory() (vpodzime)
+- Fix installation without specifying --exec-prefix (vpodzime)
+- Add options to force mkfs.ext4/vfat to create a FS on the whole device (vpodzime)
+- Skip the test for device escrow on CentOS/RHEL (vpodzime)
+- Define DEVNULL on our own if not in subprocess (vpodzime)
+- Remove the patches from the spec file (vpodzime)
+- Sync the spec file with downstream (vpodzime)
+- Stop skipping zRAM stats tests (vtrefny)
+- Add more tests for zRAM stats (vtrefny)
+- Fix reading zRAM properties from sysfs (vtrefny)
+
+* Wed Apr 12 2017 Vratislav Podzimek <vpodzime@redhat.com> - 2.6-3
+- Do not try to parse 'raid_spec' for 'bd_md_activate' (vtrefny)
+ Resolves: rhbz#1439111
+
+* Tue Apr 11 2017 Vratislav Podzimek <vpodzime@redhat.com> - 2.6-2
+- Make sure the returned thpool MD size is valid (vpodzime)
+
+* Wed Mar 15 2017 Vratislav Podzimek <vpodzime@redhat.com> - 2.6-1
+- Move the part_err library before part and fs (vtrefny)
+- Fix BuildRequires for crypto and dm packages (vtrefny)
+- Fix mounting read-only devices (vtrefny)
+- Fix the bd_s390_dasd_is_ldl function. (vponcova)
+- Add the bd_s390_dasd_is_fba function to check if DASD is FBA (vponcova)
+- Disable MD RAID tests on 32bit systems (vpodzime)
+- Fix error message when mounting with a wrong fs type (vtrefny)
+- Only create RPMs for requested/configured plugins (vpodzime)
+- Only check dependencies of plugins to be built (vpodzime)
+- Only build and distribute plugins if configured so (vpodzime)
+- Fix format-security and unused-result compiler warnings (vtrefny)
+- Add an AC macro for modular builds (vpodzime)
+- Add functions for mounting and unmounting filesystems (vtrefny)
+
+* Mon Mar 06 2017 Vratislav Podzimek <vpodzime@redhat.com> - 2.5-1
+- Do not try to get GVariant after not adding anything to its builder (vpodzime)
+- Replace NULL with "" when building ExtraArg (vpodzime)
+- Replace NULL with "" when adding it as a 's' GVariant (vpodzime)
+- Make sure we don't try to add NULL as GVariant to DBus params (vpodzime)
+- Add function for getting recommended thpool metadata size (vpodzime)
+- Make udev settle after we create a LIO device (vpodzime)
+- Always use '--yes' for lvremove (vpodzime)
+
+* Tue Feb 21 2017 Vratislav Podzimek <vpodzime@redhat.com> - 2.4-1
+- Update specs.rst to use present-tense and current API (agrover)
+- Add functions using BLOBs as LUKS passphrases (vpodzime)
+- Make sure the _error_quark() functions are in the library (vtrefny)
+- Return a special error when trying to wipe empty device (vtrefny)
+- Adapt tests to use LIO devices instead of loop devices (vpodzime)
+- Add functions for creating and deleting LIO devices (vpodzime)
+- MDRAID: Allow path as input for functions that work with sysfs (vtrefny)
+
+* Wed Feb 15 2017 Vratislav Podzimek <vtrefny@redhat.com> - 2.3-3
+- Rebuild with changelog fixed up
+
+* Tue Feb 14 2017 Vratislav Podzimek <vpodzime@redhat.com> - 2.3-1
+- Allow specifying raid 'name' in multiple way when calling md functions (vtrefny)
+- Allow using both path and raid name in bd_md_set_bitmap_location (vtrefny)
+- Fix potential memory issues in s390 sanitizate functions (vpodzime)
+- Try multiple times when probing device for wiping (vpodzime)
+- Check for libvolume_key.h and dmraid.h in configure.ac (vpodzime)
+- Define our own macro for testing required header files (vpodzime)
+- Include blockdev/utils.h in mdraid.h (vtrefny)
+- Fix misspelling (agrover)
+- Skip the bcache tests even on x86_64 (vpodzime)
+- Take a break between bcache creation tests (vpodzime)
+- Make sure ./configure fails if there are some soft failures (vpodzime)
+- Improve the error message on missing GI support (vpodzime)
+- Only require bcache-tools if supporting bcache (vpodzime)
+- Skip bcache tests on non-x86_64 architectures (vpodzime)
+- Try harder to register a new bcache device (vpodzime)
+- Reimplement swapon/swapoff functions by using syscalls (vpodzime)
+- Make sure bcache functions are correctly provided or not (vpodzime)
+- Changelog fixup (vpodzime)
+
+* Fri Feb 10 2017 Fedora Release Engineering <releng@fedoraproject.org> - 2.2-3
+- Rebuilt for https://fedoraproject.org/wiki/Fedora_26_Mass_Rebuild
+
+* Wed Jan 11 2017 Vratislav Podzimek <vpodzime@redhat.com> - 2.2-2
+- Rebuild with changelog fixed up
+
+* Wed Jan 11 2017 Vratislav Podzimek <vpodzime@redhat.com> - 2.2-1
+- Use the .in file as source when bumping version (vpodzime)
+- Run pylint based on the python version and make it optional (vpodzime)
+- Disable python3 and bcache on RHEL (vpodzime)
+- Make bcache support optional (vpodzime)
+- Teach boileplate_generator.py to skip things based on patterns (vpodzime)
+- Require lower versions of some utilities (vpodzime)
+- Do not require python3 for the boilerplate generation script (vpodzime)
+- Use a proper initialization value for 'GPollFD fds[2]' (vpodzime)
+- Deal with older parted and libblkid (vpodzime)
+- Make python3 and gtk-doc optional (vpodzime)
+- Bump the version of the utils library (vpodzime)
+- Fix docstring for 'bd_md_node_from_name' (vtrefny)
+- Add tests for added mdraid methods (vtrefny)
+- Skip 'MDTestNominateDenominateActive' unless feeling lucky (vtrefny)
+- MDRaid tests: change 'wait_for_resync' to wait for given action (vtrefny)
+- Add functionality need by storaged to mdraid plugin (vtrefny)
+- Move 'echo_str_to_file' method to utils (vtrefny)
+- Add a function to setup a loop device from a file descriptor (vpodzime)
+- Add functions to get/set the autoclear flag on a loop device (vpodzime)
+- Fix checking /proc/mdstat for resync action (vtrefny)
+- Adapt the test config files to version 2.x (vpodzime)
+
+* Mon Dec 12 2016 Charalampos Stratakis <cstratak@redhat.com> - 2.1-3
+- Rebuild for Python 3.6
+
+* Tue Nov 15 2016 Vratislav Podzimek <vpodzime@redhat.com> - 2.1-2
+- Rebuild for a chain-build with storaged (vpodzime)
+
+* Thu Nov 10 2016 Vratislav Podzimek <vpodzime@redhat.com> - 2.1-1
+- Do not require an exclusive lock on the device to commit part stuff (vpodzime)
+- Prevent failure if there are no btrfs subvolumes (vpodzime)
+- Fix the test for getting version of a failing utility (vpodzime)
+- Also run the utils tests (vpodzime)
+- Bump the version of the pkgconfig module (vpodzime)
+- Include utils.h in plugins that need it (vpodzime)
+- Fix dependency check in fs plugin (vtrefny)
+- Add support for setting part id (part type) on msdos part tables (vtrefny)
+- Trim the extra info for MD RAID's name (vpodzime)
+- Add xfsprogs and dosfstools as dependencies of the fs plugin (vpodzime)
+- Fix md_name_from_node to work with the "/dev/" prefix (vpodzime)
+- New major upstream release
+
+* Wed Nov 9 2016 Vratislav Podzimek <vpodzime@redhat.com> - 1.9-8
+- Revert "Prevent issues between libparted and udev" (vpodzime)
+- Revert "Open the device file as RDWR when committing parts" (vpodzime)
+
+* Thu Oct 27 2016 Vratislav Podzimek <vpodzime@redhat.com> - 1.9-7
+- Open the device file as RDWR when committing parts (vpodzime)
+- Handle mdadm --examine output during migration (adamw)
+ Resolves: rhbz#1381996
+
+* Mon Oct 24 2016 Vratislav Podzimek <vpodzime@redhat.com> - 1.9-6
+- Prevent issues between libparted and udev (vpodzime)
+
+* Mon Oct 10 2016 Vratislav Podzimek <vpodzime@redhat.com> - 1.9-5
+- Make sure all object paths are passed and extracted as such (vpodzime)
+ Resolves: rhbz#1374973
+
+* Tue Oct 4 2016 Vratislav Podzimek <vpodzime@redhat.com> - 1.9-4
+- Do not report volume name for FW RAID container device (vpodzime)
+ Related: rhbz#1379865
+- Search for just "UUID" in mdadm --examine output (vpodzime)
+ Related: rhbz#1379865
+- Use 'mdadm --examine --export' to get MD RAID level (vpodzime)
+ Related: rhbz#1379865
+
+* Mon Oct 3 2016 Vratislav Podzimek <vpodzime@redhat.com> - 1.9-3
+- Try to search for "RAID Level" in mdadm's output (vpodzime)
+ Resolves: rhbz#1379865
+- Fix the number passed to LVM DBus as a job-creation timeout (vpodzime)
+ Resolves: rhbz#1378970
+
+* Mon Aug 29 2016 Vratislav Podzimek <vpodzime@redhat.com> - 1.9-2
+- Explicitly cast number constants for GVariants (vpodzime)
+
+* Wed Jul 27 2016 Vratislav Podzimek <vpodzime@redhat.com> - 1.9-1
+- Add functions for creating thin/cache pools from existing LVs (vpodzime)
+- Add the new mpath_get_members() function to the docs (vpodzime)
+
+* Tue Jul 19 2016 Fedora Release Engineering <rel-eng@lists.fedoraproject.org> - 1.8-2
+- https://fedoraproject.org/wiki/Changes/Automatic_Provides_for_Python_RPM_Packages
+
+* Wed Jun 29 2016 Vratislav Podzimek <vpodzime@redhat.com> - 1.8-1
+- Add a function to get all mpath member devices (vpodzime)
+- Fix backport issues in the zfcp-related functionality (#1348442) (vpodzime)
+- Revert "Fix a few const params in the s390 plugin." (vpodzime)
+- Fix creation of the version-only tags (vpodzime)
+
+* Wed Jun 15 2016 Vratislav Podzimek <vpodzime@redhat.com> - 1.7-1
+- Include the LV roles in the LVdata (vpodzime)
+- Add a few missing items to the documentation (vpodzime)
+- Document fields of the structures (vpodzime)
+- Report (meta)data LV name properly for cache pools in lvm-dbus (vpodzime)
+- Add information about related LVs to LVMLVdata (vpodzime)
+- Remove unused code for getting supported functions (vpodzime)
+- Add zFCP functionality to s390 plugin (sbueno+anaconda)
+- Fix a few const params in the s390 plugin. (sbueno+anaconda)
+
+* Wed Jun 01 2016 Vratislav Podzimek <vpodzime@redhat.com> - 1.6-1
+- Ignore merge commits when creating changelog (vpodzime)
+- Only take the number of the first %%changelog line found (vpodzime)
+- Add some more detailed description to the part plugin (vpodzime)
+- Fix a few extra issues with the const types (vpodzime)
+- Add function for getting best free region (vpodzime)
+- Add function for getting free regions (vpodzime)
+- Fix the error message when setting part flag fails (vpodzime)
+- Add function for setting disk flags (vpodzime)
+- Add function for getting information about disk(s) (vpodzime)
+- Do not set nonsense partition paths (vpodzime)
+- Add function for getting partition by position (vpodzime)
+- Indicate if there was error from parted or not in set_parted_error() (vpodzime)
+- Minor fixes for the bd_part_get_part_spec() function (vpodzime)
+- Add support for extra GPT flags (vpodzime)
+- Add functionality for partition types (GUIDs) (vpodzime)
+- Add functionality for partition names (vpodzime)
+- Do not destroy disk objects we didn't get (vpodzime)
+- Add a function for setting multiple partition flags at once (vpodzime)
+- Remove the unused definition USE_PYTHON3 from configure.ac (vpodzime)
+- Use different BTRFS_MIN_MEMBER_SIZE on aarch64 (vpodzime)
+- Better release memory from parted objects on failures (vpodzime)
+- Rework how we do optimal alignment (vpodzime)
+- Do not try to destroy object we didn't get (vpodzime)
+- Don't pass sizes in bytes to LVM (#1317373) (vpodzime)
+- Add the libbytesize-devel build requires (vpodzime)
+- Search for the LVM DBus service in both active and activatable names (vpodzime)
+- Adapt to another stupid change in btrfs-progs (vpodzime)
+- Add the XFS-related functions to the documentation (vpodzime)
+- Add tests for the XFS-related functions (vpodzime)
+- Add support for the XFS file system to the FS plugin (vpodzime)
+- Add chunk_size to BDMDExamineData (vtrefny)
+- Add the subpackage for the FS plugin (vpodzime)
+- Add the FS plugin to the docs (vpodzime)
+- Add tests for the ext4 functionality in the fs plugin (vpodzime)
+- Add the FS plugin and the ext4 support (vpodzime)
+- Add a function for running utility reporting error and exit code (vpodzime)
+- Add the subpackage for the part plugin (vpodzime)
+- Add a missing BuildRequires for parted-devel (vpodzime)
+- Tag as both libblockdev-$version and just $version (vpodzime)
+- Add the 'part' plugin to documentation (vpodzime)
+- Add tests for the newly added part plugin (vpodzime)
+- Add the part plugin with storaged-required functionality (vpodzime)
+
+* Mon Mar 21 2016 Vratislav Podzimek <vpodzime@redhat.com> - 1.5-1
+- Merge pull request #72 from vpodzime/master-faster_tests (vpodzime)
+- Ignore all .bak files (vpodzime)
+- Use python3-pylint and skip Python 2 tests (vpodzime)
+- Try a bit harder when deactivating MD arrays in tests (vpodzime)
+- Recompile only the LVM plugin in tests (vpodzime)
+- Merge pull request #65 from vpodzime/master-loc_indep_error (vpodzime)
+- Merge pull request #70 from vojtechtrefny/master-chunk_size (vpodzime)
+- Add bd_md_create_with_chunk_size() function (vtrefny)
+- Merge pull request #68 from vpodzime/master-no_intro_data (vpodzime)
+- Merge pull request #71 from vpodzime/master-ipython3 (vpodzime)
+- Run coverage with the right config directories (vpodzime)
+- Merge pull request #67 from phatina/master (vpodzime)
+- Merge pull request #69 from vpodzime/master-lvm_dbus_autostart (vpodzime)
+- Use ipython3 for debugging and testing sessions (vpodzime)
+- Don't expect to always get introspection data from DBus (vpodzime)
+- Make invocation of tests configurable (phatina)
+- Make error messages locale agnostic (vpodzime)
+
+* Tue Mar 15 2016 Vratislav Podzimek <vpodzime@redhat.com> - 1.4-5
+- Search for the LVM DBus service in activatable names (vpodzime)
+- Better check for the LVM DBus API (vpodzime)
+
+* Wed Mar 9 2016 Vratislav Podzimek <vpodzime@redhat.com> - 1.4-4
+- Do not try to get object path of NULL in vgreduce (vpodzime)
+
+* Tue Mar 1 2016 Peter Robinson <pbrobinson@fedoraproject.org> 1.4-3
+- Depend on python3-gobject-base not python3-gobject so as to not pull in X components
+
+* Thu Feb 25 2016 Vratislav Podzimek <vpodzime@redhat.com> - 1.4-2
+- Add/fix the requirement for the LVM DBus daemon
+
+* Thu Feb 25 2016 Vratislav Podzimek <vpodzime@redhat.com> - 1.4-1
+- Merge pull request #62 from vpodzime/master-clean_up (vpodzime)
+- Use addCleanup() instead of tearDown() in tests (vpodzime)
+- Merge pull request #58 from vpodzime/master-lvm_dbus_pr (vpodzime)
+- Add the VG renaming functionality (vpodzime)
+- Packaging of the lvm-dbus plugin (vpodzime)
+- The LVM DBus plugin (vpodzime)
+- Add more generic functions for logging (vpodzime)
+- Use MAX(a, b) instead of CLAMP(b, a, b) (vpodzime)
+- Merge pull request #59 from vpodzime/master-vgrename (vpodzime)
+- Add a function for renaming VGs (vpodzime)
+- Merge pull request #57 from clumens/master (vpodzime)
+- Fix error reporting when running "make test". (clumens)
+- Merge pull request #54 from vojtechtrefny/master-pvsize (vpodzime)
+- Do not try to create a PV with 4KiB metadata space (vpodzime)
+- Add pv_info to BDLVMPVdata (vtrefny)
+- btrfs now requires at least 128MiB device(s) (vpodzime)
+- Merge pull request #52 from vpodzime/master (vpodzime)
+- Round size in thpoolcreate() to KiB (vpodzime)
+- Sync the %%changelog in spec with downstream (vpodzime)
+
+* Wed Nov 25 2015 Vratislav Podzimek <vpodzime@redhat.com> - 1.3-4
+- Create the cache pool before the to-be-cached LV (vpodzime)
+
+* Thu Nov 05 2015 Robert Kuska <rkuska@redhat.com> - 1.3-3
+- Rebuilt for Python3.5 rebuild
+
+* Wed Nov 04 2015 Vratislav Podzimek <vpodzime@redhat.com> - 1.3-2
+- Fix the annotation of bd_try_init in blockdev.c (vpodzime)
+
+* Mon Oct 26 2015 Vratislav Podzimek <vpodzime@redhat.com> - 1.3-1
+- Add missing python GI requires (vpodzime)
+- Merge pull request #49 from dashea/libblockdev-python (vpodzime)
+- Merge pull request #50 from vpodzime/master-fix_striped_lv (vpodzime)
+- Merge pull request #46 from vpodzime/master-bcache_destroy (vpodzime)
+- Merge pull request #39 from vpodzime/master-lvm_physical_space (vpodzime)
+- Add a missing ldconfig that rpmlint found. (dshea)
+- Move python files to separate packages (#1256758) (dshea)
+- Fix lvcreate calls for striped LVs (vpodzime)
+- Merge pull request #48 from vojtechtrefny/master_pvfree (vpodzime)
+- Add pv_free to BDLVMPVdata (vtrefny)
+- Merge pull request #47 from atodorov/add_coverage_report (vpodzime)
+- Produce coverage report in CI (atodorov)
+- Check bcache device's state before trying to detach the cache in destroy() (vpodzime)
+- Fix URLs in the spec (vpodzime)
+- Fix the int-float less-than comparison (vpodzime)
+- Fix the calculation of physical space taken by an LV (vpodzime)
+
+* Wed Sep 23 2015 Vratislav Podzimek <vpodzime@redhat.com> - 1.2-1
+- Merge pull request #40 from vpodzime/master-config_support (vpodzime)
+- Add tests for configuration support (vpodzime)
+- Add a function for getting the loaded soname for a plugin (vpodzime)
+- Add the default configuration (vpodzime)
+- Load and respect configuration files when loading plugins (vpodzime)
+- Add functions for finding and processing configuration files (vpodzime)
+- Merge pull request #38 from vpodzime/master-md_superblock_size (vpodzime)
+- Better document how MD RAID superblock size should be calculated (vpodzime)
+- Merge pull request #36 from phatina/master (vpodzime)
+- BTRFS: allow an arbitrary label to be set for a btrfs volume (phatina)
+- Merge pull request #32 from phatina/master (vpodzime)
+- BTRFS: fix parsing empty partition label (phatina)
+- Merge pull request #35 from vpodzime/master (vpodzime)
+- Define env variables for sudo via the env utility (vpodzime)
+- Merge pull request #34 from dashea/python3-tests (vpodzime)
+- Use unittest.addCleanup to simplify crypto_test. (dshea)
+- Run tests with both python2 and python3 in the ci target. (dshea)
+- Fix python3 issues in the unittests. (dshea)
+- Do not run all tests in the 'ci' target (vpodzime)
+- Merge pull request #33 from clumens/master (vpodzime)
+- Add a new makefile target that does everything needed for jenkins. (clumens)
+- Synchronize the .spec file with downstream (vpodzime)
+
+* Fri Jul 24 2015 Vratislav Podzimek <vpodzime@redhat.com> - 1.1-2
+- Explicitly specify the type of the cert_data parameter (#1246096) (vpodzime)
+
+* Fri Jun 19 2015 Vratislav Podzimek <vpodzime@redhat.com> - 1.1-1
+- Clean generated boilerplate code on 'make clean' (vpodzime)
+- Merge pull request #31 from atodorov/use_lang_c (vpodzime)
+- tests: use LANG=C in test_backup_passphrase() (atodorov)
+- Merge pull request #30 from atodorov/makefile_updates (vpodzime)
+- Makefile.am: - add separate check target - add coverage targets - make it possible to test with Python3 (atodorov)
+- Merge pull request #29 from atodorov/fix_issue_28 (vpodzime)
+- Merge pull request #27 from atodorov/fix_docs_url (vpodzime)
+- Merge pull request #26 from atodorov/test_docs (vpodzime)
+- Change the modified sources back in tearDown() method as well. Closes #28. (atodorov)
+- update URL to on-line documentation (atodorov)
+- add test documentation (atodorov)
+- Merge pull request #22 from dashea/escrow-tests (vpodzime)
+- Merge pull request #25 from dashea/python-dep (vpodzime)
+- Filter the python files from automatic rpm requires (dshea)
+- Added tests for escrow packets and backup passphrases (dshea)
+- Free leaked contexts from crypto_init (dshea)
+- Cooperate with volume_key's memory management (dshea)
+- Fix inheritance in the LVM tests to prevent multiple runs of some tests (vpodzime)
+- Make the regexp for testing crypto_generate_backup_passphrase() stricter (vpodzime)
+- Leave room in the backup passphrase for a trailing 0 (dshea)
+- Add functions to get names of data/metadata internal LVs (vpodzime)
+- Allow getting info for an internal LV (vpodzime)
+- Gather information about all LVs (vpodzime)
+- Round requested size to KBs in lvresize() (#1221247) (vpodzime)
+- Add overrides for the ensure_init() function (vpodzime)
+- Change the default value of the 'reload' parameter of try_reinit() (vpodzime)
+- Merge pull request #21 from vpodzime/master-thpool_size_discard (vpodzime)
+- Add overrides for the lvm_is_valid_thpool_chunk_size() function (vpodzime)
+
+* Wed Jun 17 2015 Fedora Release Engineering <rel-eng@lists.fedoraproject.org> - 1.0-2
+- Rebuilt for https://fedoraproject.org/wiki/Fedora_23_Mass_Rebuild
+
+* Thu May 21 2015 Vratislav Podzimek <vpodzime@redhat.com> - 1.0-1
+- Adapt the release helper targets to autotools (vpodzime)
+- Fixes of paths in Makefile.am's inspired by build failures on s390 (vpodzime)
+- Add an s390-specific BuildRequires (vpodzime)
+- Distribute also the boilerplate_generator.py script (vpodzime)
+- Fix path to the generated blockdev.pc file (vpodzime)
+- Adapt tests that compile stuff to autotools (vpodzime)
+- Merge pull request #18 from vpodzime/master-autotools (vpodzime)
+- Merge pull request #20 from dashea/gtkdoc-sections (vpodzime)
+- Use the autotools building system instead of scons (vpodzime)
+- Add the two new functions to the 'blockdev' docs section (vpodzime)
+- Fix the line defining the docs file for the s390 section (vpodzime)
+- Add a missing #include to the kbd.api file (vpodzime)
+- Prevent s390-specific stuff from being used on other architectures (vpodzime)
+- Update the documentation of the is_initialized() function (vpodzime)
+- Merge pull request #19 from vpodzime/master-ensure_init (vpodzime)
+- Remove private macros from the gtkdoc sections file. (dshea)
+- Terminate ifdef statements for arch check. (sbueno+anaconda)
+- Return early from the init functions if setting up logging fails (vpodzime)
+- Add tests for the new and modified init functions (vpodzime)
+- Add new try_init() and try_reinit() functions (vpodzime)
+- Fix for adding number of loaded plugins (vpodzime)
+- Fix for ensure_init() (vpodzime)
+- Rename the try_init() function to ensure_init() and improve it (vpodzime)
+- Check number of loaded plugins and library initialization state (vpodzime)
+- Make 'reload' default to True instead of False in overrides (vpodzime)
+- Add the s390 plugin test file. (sbueno+anaconda)
+- Add the s390 plugin functions. (sbueno+anaconda)
+- Add the s390 plugin. (sbueno+anaconda)
+- Fix a typo in the spec file. (sbueno+anaconda)
+- Require the kmod-devel package for the build process (vpodzime)
+- Merge pull request #16 from dashea/escrow-encoding (vpodzime)
+- Merge pull request #13 from vpodzime/master-lvm_cache (vpodzime)
+- Merge pull request #12 from vpodzime/master-kbd_plugin (vpodzime)
+- Merge pull request #14 from vpodzime/master-better_is_multipath (vpodzime)
+- Use g_strdup() instead of g_strdup_printf() to just dup a string (vpodzime)
+- Fix the spelling of "escrow" (dshea)
+- Make the crypto plugin string parameters const (dshea)
+- Set encoding to NULL before writing the escrow packet. (dshea)
+- Get cache stats directly from the device mapper (vpodzime)
+- Reimplement the is_mpath_member() function using device mapper (vpodzime)
+- Add the LVM cache related symbols to the LVM section in the documentation (vpodzime)
+- Update the list of LVM cache related functions in features.rst (vpodzime)
+- Add tests for functions related to the LVM cache technology (vpodzime)
+- Implement the lvm_cache_stats() function (vpodzime)
+- Implement the lvm_cache_pool_name function (vpodzime)
+- Implement the lvm_cache_create_cached_lv() function (vpodzime)
+- Implement lvm_cache_attach/detach() functions (vpodzime)
+- Implement the lvm_cache_create_pool() function plus two support functions (vpodzime)
+- Implement the lvm_cache_get_default_md_size() function (vpodzime)
+- Add the 'type' parameter to the lvm_lvcreate function (vpodzime)
+- Teach boilerplate_generator to work with enum return types (vpodzime)
+- Teach boilerplate_generator to work with 'const' return types (vpodzime)
+- Add subpackages for the KBD plugin and its devel files (vpodzime)
+- Add provided symbols to the documentation section of the KBD plugin (vpodzime)
+- Implement the bcache_get_backing/cache_device functions (vpodzime)
+- Exclude bcache tests from the normal 'test' target (vpodzime)
+- Add some more and prolong some of the waits in KBD tests (vpodzime)
+- Zero all newly allocated structures (vpodzime)
+- Implement the bcache_status function and all it wants (vpodzime)
+- Fix for the zram stats (vpodzime)
+- Add bcache_get_mode and bcache_set_mode functions (vpodzime)
+- Teach boilerplate_generator to work with enum return types (vpodzime)
+- Teach boilerplate_generator to work with 'const' return types (vpodzime)
+- Add the zram_get_stats function (vpodzime)
+- Add the check() function for the KBD plugin (vpodzime)
+- Add ErrorProxy instance for the KBD plugin (vpodzime)
+- Add tests for bcache_create/attach/detach/destroy functions (vpodzime)
+- Add the 'rebuild' Makefile target (vpodzime)
+- Add bcache_create, bcache_attach, bcache_detach and bcache_destroy functions (vpodzime)
+- Implement a helper function to echo string into a file (vpodzime)
+- Add tests for zram_create_devices and zram_destroy_devices functions (vpodzime)
+- Add the zram_destroy_devices function to the KBD plugin (vpodzime)
+- Add first function to the KBD plugin: zram_create_devices (vpodzime)
+- Add the KernelBlockDevices plugin (vpodzime)
+
+* Wed May 13 2015 Vratislav Podzimek <vpodzime@redhat.com> - 0.13-1
+- Prevent a leaky test from running in Jenkins (vpodzime)
+- Try harder when cleaning up after MD RAID tests (vpodzime)
+- Improve the MD RAID activate/deactivate test (vpodzime)
+- One more @contextmanager that needs try-finally (vpodzime)
+- Do not require metadata version to be reported by 'mdadm --examine' (#1217900) (vpodzime)
+- Make sure we always set things back in context managers (vpodzime)
+- Make the release date for version 1.0 more realistic (vpodzime)
+- Merge pull request #11 from vpodzime/master (vpodzime)
+- Run utilities with LC_ALL=C (vpodzime) (#1219033)
+- Free GMatchInfo instance even in case of no match (vpodzime)
+- Resolve /dev/md/ symlinks when checking swap status. (dlehman)
+
+* Fri Apr 24 2015 Vratislav Podzimek <vpodzime@redhat.com> - 0.12-1
+- Require minimum version of libblockdev-utils in some plugins (vpodzime)
+- Report both stdout and stderr if exit code != 0 (vpodzime)
+
+* Fri Apr 17 2015 Vratislav Podzimek <vpodzime@redhat.com> - 0.11-1
+- Fix issues with using overriden functions over ErrorProxy (vpodzime)
+- Update the roadmap.rst and features.rst with new stuff (vpodzime)
+- Fix two minor issues with docs generation (vpodzime)
+
+* Thu Apr 16 2015 Vratislav Podzimek <vpodzime@redhat.com> - 0.10-1
+- Fix return type of the unload_plugins() function (vpodzime)
+- Close the DL handle when check() or init() fail (vpodzime)
+- Add one more check to the reload test (vpodzime)
+- Drop reference to check() and init() functions (vpodzime)
+- Add more cats to tests (vpodzime)
+- Make regexp for getting btrfs version more generic (vpodzime)
+- Merge pull request #8 from vpodzime/master-check_functions (vpodzime)
+- Fix parameters passed to unoverridden swapon function (vpodzime)
+- Implement and test swap plugin's check function (vpodzime)
+- Implement and test MD RAID plugin's check function (vpodzime)
+- Implement and test mpath plugin's check function (vpodzime)
+- Try harder to get util's version (vpodzime)
+- Implement and test loop plugin's check function (vpodzime)
+- Implement and test DM plugin's check function (vpodzime)
+- Implement and test BTRFS plugin's check function (vpodzime)
+- Implement and test LVM plugin's check function (vpodzime)
+- Init logging before loading plugins (vpodzime)
+- Add function for utility availability checking (vpodzime)
+- Fix default value for the fake_utils' path argument (vpodzime)
+- Add ErrorProxy instance for the utils functions (vpodzime)
+- Add function for version comparison (vpodzime)
+- Merge pull request #9 from clumens/master (vpodzime)
+- Disable pylint checking on the new exception proxy. (clumens)
+- Fix XRules application and add a test for it (vpodzime)
+- Raise NotImplementedError when an unavailable function is called (vpodzime)
+- Merge pull request #4 from vpodzime/master-error_proxy (vpodzime)
+- Merge branch 'master' into master-error_proxy (vpodzime)
+- Merge pull request #5 from vpodzime/master-not_implemented_error (vpodzime)
+- Add a simple test for unloaded/unavailable functions (vpodzime)
+- Unload the plugins properly when reinit() is called (vpodzime)
+- Raise error/exception when an unimplemented function is called (#1201475) (vpodzime)
+- Do an ugly but necessary hack to make local GI overrides work (vpodzime)
+- Add the __dir__ method to ErrorProxy (vpodzime)
+- Add a rationale for the ErrorProxy to the overrides' docstring (vpodzime)
+- Add some basic info about GI overrides to the documentation (vpodzime)
+- Use pylint to check for errors in python overrides (vpodzime)
+- Add the first small test for the ErrorProxy (vpodzime)
+- Put the GI overrides in a special dir so that they are preferred (vpodzime)
+- Add a cache for attributes already resolved by ErrorProxy (vpodzime)
+- Implement the ErrorProxy python class and use it (vpodzime)
+
+* Tue Apr 07 2015 Vratislav Podzimek <vpodzime@redhat.com> - 0.9-1
+- Merge pull request #7 from vpodzime/master-fw_raid_fixes (vpodzime)
+- Try a bit harder when trying to determine MD RAID name (#1207317) (vpodzime)
+- Don't be naïve about mdadm --detail telling us what we want (#1207317) (vpodzime)
+- Ignore libblockdev tarballs (vpodzime)
+- Implement a test of btrfs_list_subvolumes on data from bug report (vpodzime)
+- Implement a context manager for running tests with fake utils (vpodzime)
+- Do not try to cannonicalize MD UUIDs if we didn't get them (#1207317) (vpodzime)
+- Fix the table in roadmap.rst (vpodzime)
+- Enrich the roadmap.rst file and add info about new plans (vpodzime)
+- Sync spec file with downstream (vpodzime)
+
+* Fri Mar 27 2015 Vratislav Podzimek <vpodzime@redhat.com> - 0.8-1
+- Merge pull request #6 from vpodzime/master-sort_btrfs_subvolumes (vpodzime)
+- Don't be naïve about mdadm providing us data we would like (#1206394) (vpodzime)
+- Sort BTRFS subvolumes in a way that child never appears before parent (#1201120) (vpodzime)
+- Let libcryptsetup handle LUKSname->/dev/mapper/LUKSname for us (vpodzime)
+- Fix the crypto_luks_resize and create a test for it (vpodzime)
+- Add targets to create the SRPM and RPM files easily (vpodzime)
+- Don't round up to multiple of PE size bigger than max value of the rtype (vpodzime)
+- Mark majority of MD RAID tests as slow (vpodzime)
+- Merge pull request #1 from dashea/file-paths (vpodzime)
+- Don't report error for no loop device associated with given file (vpodzime)
+- Skip the detail_data.clean check when running tests in Jenkins (vpodzime)
+- Make package file paths more specific (dshea)
+- Implement and use MD RAID-specific wait for tests (vpodzime)
+- Try to give MD RAID time to sync things before querying them (vpodzime)
+- Fix the default value of the BDMDDetailData.clean field (vpodzime)
+- Do cleanup after every single MD RAID tests (vpodzime)
+- Do cleanup after every single LVM test (vpodzime)
+- Do cleanup after every single BTRFS test (vpodzime)
+- Make sure the LUKS device is closed and removed after tests (vpodzime)
+- Make sure DM maps from tests are removed after tests (vpodzime)
+- Make sure that loop devices are deactivated after tests (vpodzime)
+- Make the tearDown method of the mpath test case better visible (vpodzime)
+- Make sure that the swap is deactivated after tests (vpodzime)
+- Fix docstrings in tests' utils helper functions (vpodzime)
+- Improve the logging tests in utils_test.py (vpodzime)
+- Update the features.rst file (vpodzime)
+- Update the roadmap (vpodzime)
+- Don't check if we get a mountpoint for BTRFS operations (vpodzime)
+
+* Sun Mar 22 2015 Peter Robinson <pbrobinson@fedoraproject.org> 0.7-2
+- Ship license as per packaging guidelines
+- plugins-all should depend on base library too
+- Add dev docs
+
+* Fri Feb 27 2015 Vratislav Podzimek <vpodzime@redhat.com> - 0.7-1
+- Be ready for mdadm --examine to not provide some of the values we want (vpodzime)
+- Add exit code information to exec logging (vpodzime)
+- Improve and add tests (vpodzime)
+- Mark the test_force_plugin and test_reload as slow (vpodzime)
+- Make sure we get some devices when creating btrfs volume (vpodzime)
+- Add override for the lvremove function (vpodzime)
+- Do not create LUKS format with no passphrase and no key file (vpodzime)
+- Make sure we use the /dev/mapper/... path for luks_status (vpodzime)
+
+* Thu Feb 19 2015 Vratislav Podzimek <vpodzime@redhat.com> - 0.6-1
+- Don't report error when non-existing swap's status is queried (vpodzime)
+- Make libblockdev-plugins-all pull the same version of plugins (vpodzime)
+- Don't report error when asked for a backing file of an uknown loop (vpodzime)
+- Fix accidental change in the spec's changelog (vpodzime)
+
+* Mon Feb 16 2015 Vratislav Podzimek <vpodzime@redhat.com> - 0.5-1
+- Add tests for what we can easily test from the mpath plugin (vpodzime)
+- Add link to sources to the documentation (vpodzime)
+- Add missing symbols into the libblockdev-sections.txt file (vpodzime)
+- Do not build docs for testing (vpodzime)
+- Add the bd_try_init function (vpodzime)
+- Log stdout and stderr output when running processes (vpodzime)
+- Allow a subset of plugins to be load instead of all (vpodzime)
+- Make sure devmapper doesn't spam stdout with tons of messages (vpodzime)
+- Let debug messages go to stderr when running ipython (vpodzime)
+- Give plugins a way to initialize themselves (vpodzime)
+- Give plugins a way how to check if they could run properly (vpodzime)
+- Allow a subset of plugins to be load instead of all [TEST NEEDED] (vpodzime)
+- Make sure we use the whole /dev/mapper path for cryptsetup (vpodzime)
+- Fix vg_pv_count parsing when getting info about PV (vpodzime)
+- Set default values to data structures if real values are not available (vpodzime)
+- Fix the parameter name specifying pool metadata size (vpodzime)
+- Activate LUKS as ReadWrite in luks_open (vpodzime)
+- Make sure we pass key_size to cryptsetup in bytes (vpodzime)
+- Add the min_entropy parameter to luks_format Python overrides (vpodzime)
+- Pass size in KiB instead of B to lvcreate (vpodzime)
+- Add underscore into dataalignment and metadatasize parameter names (vpodzime)
+- Don't report error if non-mpath device is tested for being mpath member (vpodzime)
+- Fix name of the invoked utility in mpath_set_friendly_names (vpodzime)
+
+* Sat Jan 31 2015 Vratislav Podzimek <vpodzime@redhat.com> - 0.4-1
+- Improve the test for lvm_set_global_config (vpodzime)
+- Fix some minor issues in the spec file (vpodzime)
+- Fix issues with the LVM global config str (vpodzime)
+- Add couple more Python overrides (vpodzime)
+- Fix the name of the lvm_thlvpoolname() function in the header file (vpodzime)
+- Use assertEqual instead of assertTrue(a == b) (vpodzime)
+- Add the min_entropy parameter to luks_format (vpodzime)
+- Move internal dmraid-related macros into the source file (vpodzime)
+- Add an override for the md_add function (vpodzime)
+- Fix parameters in luks_open python overrides (vpodzime)
+- Prevent init() from being done multiple times and provide a test function (vpodzime)
+- Add the roadmap.rst document (vpodzime)
+- Remove an extra parenthesis in one of the docstrings (vpodzime)
+- Move the mddetail function next to the mdexamine function (vpodzime)
+- Add some more constants required by blivet (vpodzime)
+
+* Wed Jan 21 2015 Vratislav Podzimek <vpodzime@redhat.com> - 0.3-1
+- Require volume_key-devel in a version that fixes build issues (vpodzime)
+- Fix Python 2 devel package name in BuildRequires (vpodzime)
+- Generate docs for the library and all plugins (vpodzime)
+- Make doc comments better for documentation generation (vpodzime)
+- Fix parameter names in function prototypes (vpodzime)
+- Add the metadatasize parameter to pvcreate (vpodzime)
+- Add the dataalignment parameter to lvm_pvcreate (vpodzime)
+- Export non-internal constants via introspection (vpodzime)
+- Expand size constants in the GI-scanned files (vpodzime)
+- Fix usage printing in the boilerplate_generator (vpodzime)
+- Add the build directory to .gitignore (vpodzime)
+- Add the md_run function (vpodzime)
+- Fix some issues in Python overrides (vpodzime)
+- Add the escrow_device function to the crypto plugin (vpodzime)
+- Fix version of GI files in the Makefile (vpodzime)
+- Make the order of release target's dependencies more explicit (vpodzime)
+
+* Mon Jan 12 2015 Vratislav Podzimek <vpodzime@redhat.com> - 0.2-1
+- Fix dependencies of the release target (vpodzime)
+- Python overrides for the GI-generated bindings (vpodzime)
+- Pass version info to the code and use it to load plugins (vpodzime)
+
+* Wed Dec 10 2014 Vratislav Podzimek <vpodzime@redhat.com> - 0.1-1
+- Initial release
diff --git a/sources b/sources
new file mode 100644
index 0000000..ae58040
--- /dev/null
+++ b/sources
@@ -0,0 +1 @@
+6476e5967753ee03d8e65f4d7837a4b6 libblockdev-2.28.tar.gz