From 3b3b27b01275a9aac1fb4406518bbbc66fd5ae47 Mon Sep 17 00:00:00 2001 From: CentOS Sources Date: Jan 11 2022 17:41:21 +0000 Subject: import libblockdev-2.25-10.el9 --- diff --git a/SOURCES/0007-lvm-devices-file-support.patch b/SOURCES/0007-lvm-devices-file-support.patch new file mode 100644 index 0000000..bc7c55c --- /dev/null +++ b/SOURCES/0007-lvm-devices-file-support.patch @@ -0,0 +1,1908 @@ +From e364883416785d51ff8eb132b63bd802ab0ccfe9 Mon Sep 17 00:00:00 2001 +From: Vojtech Trefny +Date: Tue, 13 Jul 2021 13:22:05 +0200 +Subject: [PATCH 1/8] 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 | 74 ++++++++- + 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, 469 insertions(+), 153 deletions(-) + +diff --git a/src/lib/plugin_apis/lvm.api b/src/lib/plugin_apis/lvm.api +index 563c104..62f602f 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 144551f..d1726ed 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" +@@ -247,6 +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_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 +@@ -385,6 +395,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); +@@ -522,6 +534,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; +@@ -543,8 +556,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); + +@@ -565,6 +578,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); +@@ -2651,6 +2669,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 2be1dbd..c0d8198 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; +@@ -2018,6 +2041,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 2162d76..8063693 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 08e44fd..efd17bd 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 4882da8..35ace37 100644 +--- a/tests/lvm_dbus_tests.py ++++ b/tests/lvm_dbus_tests.py +@@ -33,6 +33,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") +@@ -44,8 +49,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) +@@ -227,6 +231,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 eb94c91..b37a879 100644 +--- a/tests/lvm_test.py ++++ b/tests/lvm_test.py +@@ -22,10 +22,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): +@@ -39,6 +46,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) +@@ -213,6 +222,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""" +@@ -1335,6 +1382,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 8e7f5a5..d3faf3c 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.31.1 + + +From bebd74962db6fb7b5314be411a4d02b21554d80f Mon Sep 17 00:00:00 2001 +From: Vojtech Trefny +Date: Tue, 13 Jul 2021 13:27:32 +0200 +Subject: [PATCH 2/8] 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 62f602f..bce2920 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 d1726ed..44d2794 100644 +--- a/src/plugins/lvm-dbus.c ++++ b/src/plugins/lvm-dbus.c +@@ -3938,3 +3938,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 c0d8198..94c6a22 100644 +--- a/src/plugins/lvm.c ++++ b/src/plugins/lvm.c +@@ -3235,3 +3235,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 8063693..5ca2a9d 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 f768c8b..715a262 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 35ace37..fb1a9ed 100644 +--- a/tests/lvm_dbus_tests.py ++++ b/tests/lvm_dbus_tests.py +@@ -10,7 +10,7 @@ import subprocess + 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 +@@ -1696,3 +1696,38 @@ class LVMVDOTest(LVMTestCase): + + full_stats = BlockDev.lvm_vdo_get_stats_full("testVDOVG", "vdoPool") + self.assertIn("writeAmplificationRatio", full_stats.keys()) ++ ++ ++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 b37a879..786434f 100644 +--- a/tests/lvm_test.py ++++ b/tests/lvm_test.py +@@ -9,7 +9,7 @@ import shutil + import subprocess + 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 + + +@@ -1682,3 +1682,38 @@ class LVMVDOTest(LVMTestCase): + + full_stats = BlockDev.lvm_vdo_get_stats_full("testVDOVG", "vdoPool") + self.assertIn("writeAmplificationRatio", full_stats.keys()) ++ ++ ++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.31.1 + + +From 8d8cbe7169cb94b01e7064a0d00b7d86baf5e652 Mon Sep 17 00:00:00 2001 +From: Vojtech Trefny +Date: Fri, 15 Oct 2021 13:18:54 +0200 +Subject: [PATCH 3/8] 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 bce2920..b96bcfd 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 44d2794..22204d5 100644 +--- a/src/plugins/lvm-dbus.c ++++ b/src/plugins/lvm-dbus.c +@@ -3939,6 +3939,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 +@@ -3957,6 +4015,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; +@@ -3983,6 +4047,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 94c6a22..605fcb0 100644 +--- a/src/plugins/lvm.c ++++ b/src/plugins/lvm.c +@@ -3236,6 +3236,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 +@@ -3254,6 +3302,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; +@@ -3280,6 +3334,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 5ca2a9d..fabf091 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 fb1a9ed..c411c9e 100644 +--- a/tests/lvm_dbus_tests.py ++++ b/tests/lvm_dbus_tests.py +@@ -1731,3 +1731,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 786434f..315dd07 100644 +--- a/tests/lvm_test.py ++++ b/tests/lvm_test.py +@@ -1717,3 +1717,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.31.1 + + +From 81df85e7ea6e129e78074b6967f80c505d1b08f0 Mon Sep 17 00:00:00 2001 +From: Vojtech Trefny +Date: Fri, 15 Oct 2021 14:21:03 +0200 +Subject: [PATCH 4/8] 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 c411c9e..9cfc647 100644 +--- a/tests/lvm_dbus_tests.py ++++ b/tests/lvm_dbus_tests.py +@@ -1711,6 +1711,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) + +@@ -1732,6 +1738,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 315dd07..ea3b7f8 100644 +--- a/tests/lvm_test.py ++++ b/tests/lvm_test.py +@@ -1697,6 +1697,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) + +@@ -1718,6 +1724,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.31.1 + + +From 01237b62bb8ad67def7c937185c42152503fbc6f Mon Sep 17 00:00:00 2001 +From: Vojtech Trefny +Date: Fri, 12 Nov 2021 14:51:39 +0100 +Subject: [PATCH 5/8] 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 9cfc647..d422869 100644 +--- a/tests/lvm_dbus_tests.py ++++ b/tests/lvm_dbus_tests.py +@@ -1711,7 +1711,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 }") +@@ -1738,13 +1738,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 ea3b7f8..882cdf2 100644 +--- a/tests/lvm_test.py ++++ b/tests/lvm_test.py +@@ -1697,7 +1697,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 }") +@@ -1724,13 +1724,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.31.1 + + +From 2f33f2af18efa0b337f8383cb6f137d6211fe7fb Mon Sep 17 00:00:00 2001 +From: Vojtech Trefny +Date: Fri, 12 Nov 2021 15:10:45 +0100 +Subject: [PATCH 6/8] 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. +--- + src/plugins/lvm-dbus.c | 5 ++++- + src/plugins/lvm.c | 5 ++++- + tests/lvm_dbus_tests.py | 12 ++++++++++++ + tests/lvm_test.py | 12 ++++++++++++ + 4 files changed, 32 insertions(+), 2 deletions(-) + +diff --git a/src/plugins/lvm-dbus.c b/src/plugins/lvm-dbus.c +index 22204d5..b7bd019 100644 +--- a/src/plugins/lvm-dbus.c ++++ b/src/plugins/lvm-dbus.c +@@ -2644,7 +2644,10 @@ gboolean bd_lvm_set_global_config (const gchar *new_config, GError **error UNUSE + g_free (global_config_str); + + /* now store the new one */ +- global_config_str = g_strdup (new_config); ++ if (!new_config || g_strcmp0 (new_config, "") == 0) ++ global_config_str = NULL; ++ else ++ global_config_str = g_strdup (new_config); + + g_mutex_unlock (&global_config_lock); + return TRUE; +diff --git a/src/plugins/lvm.c b/src/plugins/lvm.c +index 605fcb0..124fce7 100644 +--- a/src/plugins/lvm.c ++++ b/src/plugins/lvm.c +@@ -2016,7 +2016,10 @@ gboolean bd_lvm_set_global_config (const gchar *new_config, GError **error UNUSE + g_free (global_config_str); + + /* now store the new one */ +- global_config_str = g_strdup (new_config); ++ if (!new_config || g_strcmp0 (new_config, "") == 0) ++ global_config_str = NULL; ++ else ++ global_config_str = g_strdup (new_config); + + g_mutex_unlock (&global_config_lock); + return TRUE; +diff --git a/tests/lvm_dbus_tests.py b/tests/lvm_dbus_tests.py +index d422869..5516afe 100644 +--- a/tests/lvm_dbus_tests.py ++++ b/tests/lvm_dbus_tests.py +@@ -1754,3 +1754,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 882cdf2..e349817 100644 +--- a/tests/lvm_test.py ++++ b/tests/lvm_test.py +@@ -1740,3 +1740,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.31.1 + + +From 2a4e610027a2c2a315054b84a323ce973939ca2d Mon Sep 17 00:00:00 2001 +From: Vojtech Trefny +Date: Tue, 16 Mar 2021 12:05:37 +0100 +Subject: [PATCH 7/8] vdo: Do not use g_memdup in bd_vdo_stats_copy + +g_memdup is deprecated and the replacement g_memdup2 is not yet +available so lets just do the copy manually. +--- + src/lib/plugin_apis/vdo.api | 17 ++++++++++++++++- + src/plugins/vdo.c | 17 ++++++++++++++++- + 2 files changed, 32 insertions(+), 2 deletions(-) + +diff --git a/src/lib/plugin_apis/vdo.api b/src/lib/plugin_apis/vdo.api +index 936f8e0..312de4e 100644 +--- a/src/lib/plugin_apis/vdo.api ++++ b/src/lib/plugin_apis/vdo.api +@@ -170,7 +170,22 @@ void bd_vdo_stats_free (BDVDOStats *stats) { + * Deprecated: 2.24: Use LVM-VDO integration instead. + */ + BDVDOStats* bd_vdo_stats_copy (BDVDOStats *stats) { +- return g_memdup (stats, sizeof (BDVDOStats)); ++ if (stats == NULL) ++ return NULL; ++ ++ BDVDOStats *new_stats = g_new0 (BDVDOStats, 1); ++ ++ new_stats->block_size = stats->block_size; ++ new_stats->logical_block_size = stats->logical_block_size; ++ new_stats->physical_blocks = stats->physical_blocks; ++ new_stats->data_blocks_used = stats->data_blocks_used; ++ new_stats->overhead_blocks_used = stats->overhead_blocks_used; ++ new_stats->logical_blocks_used = stats->logical_blocks_used; ++ new_stats->used_percent = stats->used_percent; ++ new_stats->saving_percent = stats->saving_percent; ++ new_stats->write_amplification_ratio = stats->write_amplification_ratio; ++ ++ return new_stats; + } + + GType bd_vdo_stats_get_type () { +diff --git a/src/plugins/vdo.c b/src/plugins/vdo.c +index 2352394..d443099 100644 +--- a/src/plugins/vdo.c ++++ b/src/plugins/vdo.c +@@ -81,7 +81,22 @@ void bd_vdo_stats_free (BDVDOStats *stats) { + } + + BDVDOStats* bd_vdo_stats_copy (BDVDOStats *stats) { +- return g_memdup (stats, sizeof (BDVDOStats)); ++ if (stats == NULL) ++ return NULL; ++ ++ BDVDOStats *new_stats = g_new0 (BDVDOStats, 1); ++ ++ new_stats->block_size = stats->block_size; ++ new_stats->logical_block_size = stats->logical_block_size; ++ new_stats->physical_blocks = stats->physical_blocks; ++ new_stats->data_blocks_used = stats->data_blocks_used; ++ new_stats->overhead_blocks_used = stats->overhead_blocks_used; ++ new_stats->logical_blocks_used = stats->logical_blocks_used; ++ new_stats->used_percent = stats->used_percent; ++ new_stats->saving_percent = stats->saving_percent; ++ new_stats->write_amplification_ratio = stats->write_amplification_ratio; ++ ++ return new_stats; + } + + +-- +2.31.1 + + +From 577ea466e3b7af464137e087907ba980ad3994ee Mon Sep 17 00:00:00 2001 +From: Vojtech Trefny +Date: Fri, 26 Nov 2021 15:19:55 +0100 +Subject: [PATCH 8/8] 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 b7bd019..825c5e9 100644 +--- a/src/plugins/lvm-dbus.c ++++ b/src/plugins/lvm-dbus.c +@@ -3955,9 +3955,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 124fce7..21320f3 100644 +--- a/src/plugins/lvm.c ++++ b/src/plugins/lvm.c +@@ -3251,9 +3251,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.31.1 + diff --git a/SOURCES/0008-lvm-Fix-reading-statistics-for-VDO-pools-with-VDO-8.patch b/SOURCES/0008-lvm-Fix-reading-statistics-for-VDO-pools-with-VDO-8.patch new file mode 100644 index 0000000..4d3796b --- /dev/null +++ b/SOURCES/0008-lvm-Fix-reading-statistics-for-VDO-pools-with-VDO-8.patch @@ -0,0 +1,80 @@ +From e0fcbae856454dba9df3f8df800d74fde66731e5 Mon Sep 17 00:00:00 2001 +From: Vojtech Trefny +Date: Tue, 31 Aug 2021 14:07:23 +0200 +Subject: [PATCH] lvm: Fix reading statistics for VDO pools with VDO 8 + +The statistics are no longer available in /sys/kvdo, in the latest +version of kvdo we need to use /sys/block//vdo/statistics. + +Resolves: rhbz#1994220 +--- + src/plugins/vdo_stats.c | 42 +++++++++++++++++++++++++++++++++++++---- + 1 file changed, 38 insertions(+), 4 deletions(-) + +diff --git a/src/plugins/vdo_stats.c b/src/plugins/vdo_stats.c +index ed04b51..2e244aa 100644 +--- a/src/plugins/vdo_stats.c ++++ b/src/plugins/vdo_stats.c +@@ -133,6 +133,23 @@ static void add_computed_stats (GHashTable *stats) { + add_journal_stats (stats); + } + ++static gchar* _dm_node_from_name (const gchar *map_name, GError **error) { ++ gchar *dev_path = NULL; ++ gchar *ret = NULL; ++ gchar *dev_mapper_path = g_strdup_printf ("/dev/mapper/%s", map_name); ++ ++ dev_path = bd_utils_resolve_device (dev_mapper_path, error); ++ g_free (dev_mapper_path); ++ if (!dev_path) ++ /* error is already populated */ ++ return NULL; ++ ++ ret = g_path_get_basename (dev_path); ++ g_free (dev_path); ++ ++ return ret; ++} ++ + GHashTable __attribute__ ((visibility ("hidden"))) + *vdo_get_stats_full (const gchar *name, GError **error) { + GHashTable *stats; +@@ -141,14 +158,31 @@ GHashTable __attribute__ ((visibility ("hidden"))) + const gchar *direntry; + gchar *s; + gchar *val = NULL; ++ g_autofree gchar *dm_node = NULL; + +- /* TODO: does the `name` need to be escaped? */ +- stats_dir = g_build_path (G_DIR_SEPARATOR_S, VDO_SYS_PATH, name, "statistics", NULL); ++ /* try "new" (kvdo >= 8) path first -- /sys/block/dm-X/vdo/statistics */ ++ dm_node = _dm_node_from_name (name, error); ++ if (dm_node == NULL) { ++ g_prefix_error (error, "Failed to get DM node for %s: ", name); ++ return NULL; ++ } ++ ++ stats_dir = g_build_path (G_DIR_SEPARATOR_S, "/sys/block", dm_node, "vdo/statistics", NULL); + dir = g_dir_open (stats_dir, 0, error); + if (dir == NULL) { +- g_prefix_error (error, "Error reading statistics from %s: ", stats_dir); ++ g_debug ("Failed to read VDO stats using the new API, falling back to %s: %s", ++ VDO_SYS_PATH, (*error)->message); + g_free (stats_dir); +- return NULL; ++ g_clear_error (error); ++ ++ /* lets try /sys/kvdo */ ++ stats_dir = g_build_path (G_DIR_SEPARATOR_S, VDO_SYS_PATH, name, "statistics", NULL); ++ dir = g_dir_open (stats_dir, 0, error); ++ if (dir == NULL) { ++ g_prefix_error (error, "Error reading statistics from %s: ", stats_dir); ++ g_free (stats_dir); ++ return NULL; ++ } + } + + stats = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free); +-- +2.31.1 + diff --git a/SOURCES/0009-vdo_stats-Default-to-100-savings-for-invalid-savings.patch b/SOURCES/0009-vdo_stats-Default-to-100-savings-for-invalid-savings.patch new file mode 100644 index 0000000..f1ada49 --- /dev/null +++ b/SOURCES/0009-vdo_stats-Default-to-100-savings-for-invalid-savings.patch @@ -0,0 +1,30 @@ +From 940346f14ee5644f2593817b4196c18de8a713f0 Mon Sep 17 00:00:00 2001 +From: Vojtech Trefny +Date: Mon, 22 Nov 2021 14:16:02 +0100 +Subject: [PATCH] vdo_stats: Default to 100 % savings for invalid savings + values + +We are currently using "-1" when VDO logical_blocks_used is 0 +which doesn't match the LVM logic which returns 100 so to make +both values in vdo_info and vdo_stats equal we should return 100 +in this case too. +--- + src/plugins/vdo_stats.c | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/src/plugins/vdo_stats.c b/src/plugins/vdo_stats.c +index 3ec2d60..f3f0390 100644 +--- a/src/plugins/vdo_stats.c ++++ b/src/plugins/vdo_stats.c +@@ -96,7 +96,7 @@ static void add_block_stats (GHashTable *stats) { + g_hash_table_replace (stats, g_strdup ("oneKBlocksUsed"), g_strdup_printf ("%"G_GINT64_FORMAT, (data_blocks_used + overhead_blocks_used) * block_size / 1024)); + g_hash_table_replace (stats, g_strdup ("oneKBlocksAvailable"), g_strdup_printf ("%"G_GINT64_FORMAT, (physical_blocks - data_blocks_used - overhead_blocks_used) * block_size / 1024)); + g_hash_table_replace (stats, g_strdup ("usedPercent"), g_strdup_printf ("%.0f", 100.0 * (gfloat) (data_blocks_used + overhead_blocks_used) / (gfloat) physical_blocks + 0.5)); +- savings = (logical_blocks_used > 0) ? (gint64) (100.0 * (gfloat) (logical_blocks_used - data_blocks_used) / (gfloat) logical_blocks_used) : -1; ++ savings = (logical_blocks_used > 0) ? (gint64) (100.0 * (gfloat) (logical_blocks_used - data_blocks_used) / (gfloat) logical_blocks_used) : 100; + g_hash_table_replace (stats, g_strdup ("savings"), g_strdup_printf ("%"G_GINT64_FORMAT, savings)); + if (savings >= 0) + g_hash_table_replace (stats, g_strdup ("savingPercent"), g_strdup_printf ("%"G_GINT64_FORMAT, savings)); +-- +2.31.1 + diff --git a/SOURCES/0010-Add-support-for-creating-and-activating-integrity-de.patch b/SOURCES/0010-Add-support-for-creating-and-activating-integrity-de.patch new file mode 100644 index 0000000..77d6bc8 --- /dev/null +++ b/SOURCES/0010-Add-support-for-creating-and-activating-integrity-de.patch @@ -0,0 +1,966 @@ +From 37f1aff5f5f967d6a4440d176f3de877aab789ac Mon Sep 17 00:00:00 2001 +From: Vojtech Trefny +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 | 4 +- + 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 | 97 ++++++++++- + 6 files changed, 576 insertions(+), 8 deletions(-) + +diff --git a/configure.ac b/configure.ac +index abe1412..13830ae 100644 +--- a/configure.ac ++++ b/configure.ac +@@ -210,7 +210,9 @@ 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"], + [LIBBLOCKDEV_PKG_CHECK_MODULES([NSS], [nss >= 3.18.0]) + LIBBLOCKDEV_CHECK_HEADER([volume_key/libvolume_key.h], [$GLIB_CFLAGS $NSS_CFLAGS], [libvolume_key.h not available])], +diff --git a/src/lib/plugin_apis/crypto.api b/src/lib/plugin_apis/crypto.api +index ef0217f..40e32c8 100644 +--- a/src/lib/plugin_apis/crypto.api ++++ b/src/lib/plugin_apis/crypto.api +@@ -1,5 +1,6 @@ + #include + #include ++#include + + #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 4fad9a8..b1b0700 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, ¶ms); ++ 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, ¶ms); ++ 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 +@@ -2472,7 +2723,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); +@@ -2542,7 +2793,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 a38724d..166e558 100644 +--- a/src/plugins/crypto.h ++++ b/src/plugins/crypto.h +@@ -116,6 +116,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 +@@ -209,6 +246,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 715a262..71bcd31 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 0aecc03..1c6832e 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 +@@ -42,6 +43,8 @@ class CryptoTestCase(unittest.TestCase): + + requested_plugins = BlockDev.plugin_specs_from_names(("crypto", "loop")) + ++ _dm_name = "libblockdevTestLUKS" ++ + @classmethod + def setUpClass(cls): + unittest.TestCase.setUpClass() +@@ -72,7 +75,7 @@ class CryptoTestCase(unittest.TestCase): + + def _clean_up(self): + try: +- BlockDev.crypto_luks_close("libblockdevTestLUKS") ++ BlockDev.crypto_luks_close(self._dm_name) + except: + pass + +@@ -964,7 +967,8 @@ class CryptoTestInfo(CryptoTestCase): + succ = BlockDev.crypto_luks_close("libblockdevTestLUKS") + 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): +@@ -1151,3 +1155,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.31.1 + + +From 4dcb7a42a2cb33f7a63021d72889c9a9688adfd3 Mon Sep 17 00:00:00 2001 +From: Vojtech Trefny +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 1c6832e..b7ec251 100644 +--- a/tests/crypto_test.py ++++ b/tests/crypto_test.py +@@ -44,6 +44,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): +@@ -57,8 +58,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: +@@ -1160,6 +1161,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.31.1 + + +From 3b82f9085c0df2e58b673716cdefd747495738e2 Mon Sep 17 00:00:00 2001 +From: Vojtech Trefny +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 40e32c8..cf87979 100644 +--- a/src/lib/plugin_apis/crypto.api ++++ b/src/lib/plugin_apis/crypto.api +@@ -1,6 +1,5 @@ + #include + #include +-#include + + #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 b1b0700..8a4d64a 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 166e558..b5f133c 100644 +--- a/src/plugins/crypto.h ++++ b/src/plugins/crypto.h +@@ -141,16 +141,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 b7ec251..673d8b8 100644 +--- a/tests/crypto_test.py ++++ b/tests/crypto_test.py +@@ -1215,6 +1215,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.31.1 + diff --git a/SPECS/libblockdev.spec b/SPECS/libblockdev.spec index 83c9d0d..b56671a 100644 --- a/SPECS/libblockdev.spec +++ b/SPECS/libblockdev.spec @@ -125,7 +125,7 @@ Name: libblockdev Version: 2.25 -Release: 7%{?dist} +Release: 10%{?dist} Summary: A library for low-level manipulation with block devices License: LGPLv2+ URL: https://github.com/storaged-project/libblockdev @@ -137,6 +137,10 @@ Patch3: 0003-Memory-leaks-fixes-backport.patch Patch4: 0004-Adapt-to-dosfstools-4.2-changes.patch Patch5: 0005-Add-workarounds-for-some-LVM-test-issues.patch Patch6: 0006-Misc-test-fixes-backport.patch +Patch7: 0007-lvm-devices-file-support.patch +Patch8: 0008-lvm-Fix-reading-statistics-for-VDO-pools-with-VDO-8.patch +Patch9: 0009-vdo_stats-Default-to-100-savings-for-invalid-savings.patch +Patch10: 0010-Add-support-for-creating-and-activating-integrity-de.patch BuildRequires: make BuildRequires: glib2-devel @@ -695,6 +699,10 @@ A meta-package that pulls all the libblockdev plugins as dependencies. %patch4 -p1 %patch5 -p1 %patch6 -p1 +%patch7 -p1 +%patch8 -p1 +%patch9 -p1 +%patch10 -p1 %build autoreconf -ivf @@ -998,6 +1006,22 @@ find %{buildroot} -type f -name "*.la" | xargs %{__rm} %files plugins-all %changelog +* Wed Dec 08 2021 Vojtech Trefny - 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 - 2.25-9 +- Fix patch for 'Add support LVM devices file management' + Resolves: rhbz#1983705 + +* Tue Nov 30 2021 Vojtech Trefny - 2.25-8 +- Add support LVM devices file management + Resolves: rhbz#1983705 + * Mon Aug 09 2021 Mohan Boddu - 2.25-7 - Rebuilt for IMA sigs, glibc 2.34, aarch64 flags Related: rhbz#1991688