From 6b6239eaf50450c08eb647c1d31aa11c4c417be6 Mon Sep 17 00:00:00 2001 From: Vojtech Trefny Date: Tue, 7 Oct 2025 14:55:50 +0200 Subject: [PATCH 1/2] fs: Log mount and unmount calls libmount logs only when debug is enabled so we get no logs for mount and unmount calls. We are now using FS plugin in blivet and having the mounting logged is useful for debugging. --- src/plugins/fs/mount.c | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/plugins/fs/mount.c b/src/plugins/fs/mount.c index 59dbaf127..ef83c22cb 100644 --- a/src/plugins/fs/mount.c +++ b/src/plugins/fs/mount.c @@ -29,6 +29,8 @@ #include #include +#include + #include "fs.h" #include "mount.h" @@ -173,6 +175,8 @@ static gboolean do_unmount (MountArgs *args, GError **error) { } } + bd_utils_log_format (BD_UTILS_LOG_INFO, "Unmounting %s", args->spec); + ret = mnt_context_umount (cxt); #ifdef LIBMOUNT_NEW_ERR_API success = get_unmount_error_new (cxt, ret, args->spec, error); @@ -424,6 +428,12 @@ static gboolean do_mount (MountArgs *args, GError **error) { mnt_context_enable_rwonly_mount (cxt, TRUE); #endif + bd_utils_log_format (BD_UTILS_LOG_INFO, "Mounting %s (fstype %s) to %s with options: %s", + args->device ? args->device : "unspecified device", + args->fstype ? args->fstype : "auto", + args->mountpoint ? args->mountpoint : "unspecified mountpoint", + args->options ? args->options : "none"); + ret = mnt_context_mount (cxt); /* we need to always do some libmount magic to check if the mount really From 10b3cdfa791a0171a5c3bb68764628c599741b5d Mon Sep 17 00:00:00 2001 From: Vojtech Trefny Date: Tue, 7 Oct 2025 15:26:49 +0200 Subject: [PATCH 2/2] fs: Preserve XFS quota options in temporary mounts If XFS was previously mounted with the quota options and then we are mounting it for resize, the quota mount options need to be preserved or XFS would need to recheck the quotas during the next mount which may take a long time, depending on filesystem size. --- src/plugins/fs/generic.c | 71 +++++++++++++++++++++++++++++++++- tests/fs_tests/fs_test.py | 4 +- tests/fs_tests/generic_test.py | 55 ++++++++++++++++++++++---- tests/utils.py | 21 ++++++---- 4 files changed, 133 insertions(+), 18 deletions(-) diff --git a/src/plugins/fs/generic.c b/src/plugins/fs/generic.c index 0fc2a3093..9c11fc88b 100644 --- a/src/plugins/fs/generic.c +++ b/src/plugins/fs/generic.c @@ -652,6 +652,57 @@ gchar* bd_fs_get_fstype (const gchar *device, GError **error) { return fstype; } +/* taken from kernel source: fs/xfs/libxfs/xfs_log_format.h: */ + #define _XFS_UQUOTA_ACCT 0x0001 /* user quota accounting ON */ + #define _XFS_UQUOTA_ENFD 0x0002 /* user quota limits enforced */ + #define _XFS_GQUOTA_ACCT 0x0040 /* group quota accounting ON */ + #define _XFS_GQUOTA_ENFD 0x0080 /* group quota limits enforced */ + #define _XFS_PQUOTA_ACCT 0x0008 /* project quota accounting ON */ + #define _XFS_PQUOTA_ENFD 0x0200 /* project quota limits enforced */ + +static gboolean xfs_quota_mount_options (const gchar *device, GString *options, GError **error) { + g_autofree gchar *output = NULL; + gboolean success = FALSE; + const gchar *args[8] = {"xfs_db", "-r", device, "-c", "sb 0", "-c", "p qflags", NULL}; + guint64 flags = 0; + + success = bd_utils_exec_and_capture_output (args, NULL, &output, error); + if (!success) { + /* error is already populated */ + return FALSE; + } + + if (!g_str_has_prefix (output, "qflags = ")) { + g_set_error (error, BD_FS_ERROR, BD_FS_ERROR_FAIL, + "Failed to parse XFS quota flags for %s from %s.", device, output); + return FALSE; + } + + flags = g_ascii_strtoull (output + 9, NULL, 16); + if (flags & _XFS_UQUOTA_ACCT) { + if (flags & _XFS_UQUOTA_ENFD) + g_string_append_printf (options, "uquota,"); + else + g_string_append_printf (options, "uqnoenforce,"); + } + + if (flags & _XFS_GQUOTA_ACCT) { + if (flags & _XFS_GQUOTA_ENFD) + g_string_append_printf (options, "gquota,"); + else + g_string_append_printf (options, "gqnoenforce,"); + } + + if (flags & _XFS_PQUOTA_ACCT) { + if (flags & _XFS_PQUOTA_ENFD) + g_string_append_printf (options, "pquota,"); + else + g_string_append_printf (options, "pqnoenforce,"); + } + + return TRUE; +} + /** * fs_mount: * @device: the device to mount for an FS operation @@ -672,6 +723,8 @@ static gchar* fs_mount (const gchar *device, gchar *fstype, gboolean read_only, gchar *mountpoint = NULL; gboolean ret = FALSE; GError *l_error = NULL; + GString *options = NULL; + g_autofree gchar *options_str = NULL; mountpoint = bd_fs_get_mountpoint (device, &l_error); if (!mountpoint) { @@ -683,7 +736,23 @@ static gchar* fs_mount (const gchar *device, gchar *fstype, gboolean read_only, "Failed to create temporary directory for mounting '%s'.", device); return NULL; } - ret = bd_fs_mount (device, mountpoint, fstype, read_only ? "nosuid,nodev,ro" : "nosuid,nodev", NULL, &l_error); + + options = g_string_new (NULL); + if (g_strcmp0 (fstype, "xfs") == 0) { + ret = xfs_quota_mount_options (device, options, &l_error); + if (!ret) { + bd_utils_log_format (BD_UTILS_LOG_INFO, "Failed to get XFS mount options: %s", + l_error->message); + g_clear_error (&l_error); + } + } + if (read_only) + g_string_append_printf (options, "nosuid,nodev,ro"); + else + g_string_append_printf (options, "nosuid,nodev"); + options_str = g_string_free (options, FALSE); + + ret = bd_fs_mount (device, mountpoint, fstype, options_str, NULL, &l_error); if (!ret) { g_propagate_prefixed_error (error, l_error, "Failed to mount '%s': ", device); if (g_rmdir (mountpoint) != 0) diff --git a/tests/fs_tests/fs_test.py b/tests/fs_tests/fs_test.py index 43d2f5193..2a8b8b5d6 100644 --- a/tests/fs_tests/fs_test.py +++ b/tests/fs_tests/fs_test.py @@ -13,8 +13,8 @@ @contextmanager -def mounted(device, where, ro=False): - utils.mount(device, where, ro) +def mounted(device, where, ro=False, options=None): + utils.mount(device, where, ro, options) try: yield diff --git a/tests/fs_tests/generic_test.py b/tests/fs_tests/generic_test.py index 9c0cbc0eb..f5407c86f 100644 --- a/tests/fs_tests/generic_test.py +++ b/tests/fs_tests/generic_test.py @@ -911,6 +911,15 @@ def test_udf_generic_set_uuid(self): class GenericResize(GenericTestCase): + log = "" + + 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" + def _test_generic_resize(self, mkfs_function, fstype, size_delta=0, min_size=130*1024**2): # clean the device succ = BlockDev.fs_clean(self.loop_devs[0]) @@ -1024,35 +1033,65 @@ def test_xfs_generic_resize(self): with self.assertRaises(GLib.GError): succ = BlockDev.fs_resize(lv, 40 * 1024**2) - self._lvresize("libbd_fs_tests", "generic_test", "400M") - # should grow to 400 MiB (full size of the LV) + self._lvresize("libbd_fs_tests", "generic_test", "380M") + # should grow to 375 MiB (full size of the LV) with mounted(lv, self.mount_dir): succ = BlockDev.fs_resize(lv, 0) self.assertTrue(succ) with mounted(lv, self.mount_dir): fi = BlockDev.fs_xfs_get_info(lv) self.assertTrue(fi) - self.assertEqual(fi.block_size * fi.block_count, 400 * 1024**2) + self.assertEqual(fi.block_size * fi.block_count, 380 * 1024**2) - self._lvresize("libbd_fs_tests", "generic_test", "450M") - # grow just to 430 MiB + self._lvresize("libbd_fs_tests", "generic_test", "400M") + # grow just to 390 MiB with mounted(lv, self.mount_dir): - succ = BlockDev.fs_resize(lv, 430 * 1024**2) + succ = BlockDev.fs_resize(lv, 390 * 1024**2) self.assertTrue(succ) with mounted(lv, self.mount_dir): fi = BlockDev.fs_xfs_get_info(lv) self.assertTrue(fi) - self.assertEqual(fi.block_size * fi.block_count, 430 * 1024**2) + self.assertEqual(fi.block_size * fi.block_count, 390 * 1024**2) - # should grow to 450 MiB (full size of the LV) + # should grow to 400 MiB (full size of the LV) with mounted(lv, self.mount_dir): succ = BlockDev.fs_resize(lv, 0, "xfs") self.assertTrue(succ) with mounted(lv, self.mount_dir): fi = BlockDev.fs_xfs_get_info(lv) self.assertTrue(fi) + self.assertEqual(fi.block_size * fi.block_count, 400 * 1024**2) + + self._lvresize("libbd_fs_tests", "generic_test", "420M") + # unmounted resize (should get automounted) + succ = BlockDev.fs_resize(lv, 0, "xfs") + self.assertTrue(succ) + with mounted(lv, self.mount_dir): + fi = BlockDev.fs_xfs_get_info(lv) + self.assertTrue(fi) + self.assertEqual(fi.block_size * fi.block_count, 420 * 1024**2) + + # enable XFS quotas + with mounted(lv, self.mount_dir, options="uquota,gquota,pquota"): + pass + + self._lvresize("libbd_fs_tests", "generic_test", "450M") + succ = BlockDev.utils_init_logging(self.my_log_func) + self.assertTrue(succ) + BlockDev.utils_set_log_level(BlockDev.UTILS_LOG_INFO) + + succ = BlockDev.fs_resize(lv, 0, "xfs") + self.assertTrue(succ) + with mounted(lv, self.mount_dir): + fi = BlockDev.fs_xfs_get_info(lv) + self.assertTrue(fi) self.assertEqual(fi.block_size * fi.block_count, 450 * 1024**2) + # quota options should be preserved during temp mount + self.assertIn("with options: uquota,gquota,pquota", self.log) + + BlockDev.utils_init_logging(None) + def _can_resize_f2fs(self): ret, out, _err = utils.run_command("resize.f2fs -V") if ret != 0: diff --git a/tests/utils.py b/tests/utils.py index 1307a09d5..b2c9ef20b 100644 --- a/tests/utils.py +++ b/tests/utils.py @@ -277,12 +277,12 @@ def clean_scsi_debug(scsi_debug_dev): def _wait_for_nvme_controllers_ready(subnqn, timeout=3): """ Wait for NVMe controllers with matching subsystem NQN to be in live state - + :param str subnqn: subsystem nqn to match controllers against :param int timeout: timeout in seconds (default: 3) """ start_time = time.time() - + while time.time() - start_time < timeout: try: for ctrl_path in glob.glob("/sys/class/nvme/nvme*/"): @@ -297,12 +297,12 @@ def _wait_for_nvme_controllers_ready(subnqn, timeout=3): return except: continue - + except: pass - + time.sleep(1) - + os.system("udevadm settle") def find_nvme_ctrl_devs_for_subnqn(subnqn, wait_for_ready=True): @@ -756,11 +756,18 @@ def run(cmd_string): return subprocess.call(cmd_string, close_fds=True, shell=True) -def mount(device, where, ro=False): +def mount(device, where, ro=False, options=None): if not os.path.isdir(where): os.makedirs(where) + if ro: - os.system("mount -oro %s %s" % (device, where)) + if options: + options += ",ro" + else: + options = "ro" + + if options: + os.system("mount -o %s %s %s" % (options, device, where)) else: os.system("mount %s %s" % (device, where))