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/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 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))