Skip to content

Commit 10b3cdf

Browse files
committed
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.
1 parent 6b6239e commit 10b3cdf

File tree

4 files changed

+133
-18
lines changed

4 files changed

+133
-18
lines changed

src/plugins/fs/generic.c

Lines changed: 70 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -652,6 +652,57 @@ gchar* bd_fs_get_fstype (const gchar *device, GError **error) {
652652
return fstype;
653653
}
654654

655+
/* taken from kernel source: fs/xfs/libxfs/xfs_log_format.h: */
656+
#define _XFS_UQUOTA_ACCT 0x0001 /* user quota accounting ON */
657+
#define _XFS_UQUOTA_ENFD 0x0002 /* user quota limits enforced */
658+
#define _XFS_GQUOTA_ACCT 0x0040 /* group quota accounting ON */
659+
#define _XFS_GQUOTA_ENFD 0x0080 /* group quota limits enforced */
660+
#define _XFS_PQUOTA_ACCT 0x0008 /* project quota accounting ON */
661+
#define _XFS_PQUOTA_ENFD 0x0200 /* project quota limits enforced */
662+
663+
static gboolean xfs_quota_mount_options (const gchar *device, GString *options, GError **error) {
664+
g_autofree gchar *output = NULL;
665+
gboolean success = FALSE;
666+
const gchar *args[8] = {"xfs_db", "-r", device, "-c", "sb 0", "-c", "p qflags", NULL};
667+
guint64 flags = 0;
668+
669+
success = bd_utils_exec_and_capture_output (args, NULL, &output, error);
670+
if (!success) {
671+
/* error is already populated */
672+
return FALSE;
673+
}
674+
675+
if (!g_str_has_prefix (output, "qflags = ")) {
676+
g_set_error (error, BD_FS_ERROR, BD_FS_ERROR_FAIL,
677+
"Failed to parse XFS quota flags for %s from %s.", device, output);
678+
return FALSE;
679+
}
680+
681+
flags = g_ascii_strtoull (output + 9, NULL, 16);
682+
if (flags & _XFS_UQUOTA_ACCT) {
683+
if (flags & _XFS_UQUOTA_ENFD)
684+
g_string_append_printf (options, "uquota,");
685+
else
686+
g_string_append_printf (options, "uqnoenforce,");
687+
}
688+
689+
if (flags & _XFS_GQUOTA_ACCT) {
690+
if (flags & _XFS_GQUOTA_ENFD)
691+
g_string_append_printf (options, "gquota,");
692+
else
693+
g_string_append_printf (options, "gqnoenforce,");
694+
}
695+
696+
if (flags & _XFS_PQUOTA_ACCT) {
697+
if (flags & _XFS_PQUOTA_ENFD)
698+
g_string_append_printf (options, "pquota,");
699+
else
700+
g_string_append_printf (options, "pqnoenforce,");
701+
}
702+
703+
return TRUE;
704+
}
705+
655706
/**
656707
* fs_mount:
657708
* @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,
672723
gchar *mountpoint = NULL;
673724
gboolean ret = FALSE;
674725
GError *l_error = NULL;
726+
GString *options = NULL;
727+
g_autofree gchar *options_str = NULL;
675728

676729
mountpoint = bd_fs_get_mountpoint (device, &l_error);
677730
if (!mountpoint) {
@@ -683,7 +736,23 @@ static gchar* fs_mount (const gchar *device, gchar *fstype, gboolean read_only,
683736
"Failed to create temporary directory for mounting '%s'.", device);
684737
return NULL;
685738
}
686-
ret = bd_fs_mount (device, mountpoint, fstype, read_only ? "nosuid,nodev,ro" : "nosuid,nodev", NULL, &l_error);
739+
740+
options = g_string_new (NULL);
741+
if (g_strcmp0 (fstype, "xfs") == 0) {
742+
ret = xfs_quota_mount_options (device, options, &l_error);
743+
if (!ret) {
744+
bd_utils_log_format (BD_UTILS_LOG_INFO, "Failed to get XFS mount options: %s",
745+
l_error->message);
746+
g_clear_error (&l_error);
747+
}
748+
}
749+
if (read_only)
750+
g_string_append_printf (options, "nosuid,nodev,ro");
751+
else
752+
g_string_append_printf (options, "nosuid,nodev");
753+
options_str = g_string_free (options, FALSE);
754+
755+
ret = bd_fs_mount (device, mountpoint, fstype, options_str, NULL, &l_error);
687756
if (!ret) {
688757
g_propagate_prefixed_error (error, l_error, "Failed to mount '%s': ", device);
689758
if (g_rmdir (mountpoint) != 0)

tests/fs_tests/fs_test.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,8 @@
1313

1414

1515
@contextmanager
16-
def mounted(device, where, ro=False):
17-
utils.mount(device, where, ro)
16+
def mounted(device, where, ro=False, options=None):
17+
utils.mount(device, where, ro, options)
1818

1919
try:
2020
yield

tests/fs_tests/generic_test.py

Lines changed: 47 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -911,6 +911,15 @@ def test_udf_generic_set_uuid(self):
911911

912912

913913
class GenericResize(GenericTestCase):
914+
log = ""
915+
916+
def my_log_func(self, level, msg):
917+
# not much to verify here
918+
self.assertTrue(isinstance(level, int))
919+
self.assertTrue(isinstance(msg, str))
920+
921+
self.log += msg + "\n"
922+
914923
def _test_generic_resize(self, mkfs_function, fstype, size_delta=0, min_size=130*1024**2):
915924
# clean the device
916925
succ = BlockDev.fs_clean(self.loop_devs[0])
@@ -1024,35 +1033,65 @@ def test_xfs_generic_resize(self):
10241033
with self.assertRaises(GLib.GError):
10251034
succ = BlockDev.fs_resize(lv, 40 * 1024**2)
10261035

1027-
self._lvresize("libbd_fs_tests", "generic_test", "400M")
1028-
# should grow to 400 MiB (full size of the LV)
1036+
self._lvresize("libbd_fs_tests", "generic_test", "380M")
1037+
# should grow to 375 MiB (full size of the LV)
10291038
with mounted(lv, self.mount_dir):
10301039
succ = BlockDev.fs_resize(lv, 0)
10311040
self.assertTrue(succ)
10321041
with mounted(lv, self.mount_dir):
10331042
fi = BlockDev.fs_xfs_get_info(lv)
10341043
self.assertTrue(fi)
1035-
self.assertEqual(fi.block_size * fi.block_count, 400 * 1024**2)
1044+
self.assertEqual(fi.block_size * fi.block_count, 380 * 1024**2)
10361045

1037-
self._lvresize("libbd_fs_tests", "generic_test", "450M")
1038-
# grow just to 430 MiB
1046+
self._lvresize("libbd_fs_tests", "generic_test", "400M")
1047+
# grow just to 390 MiB
10391048
with mounted(lv, self.mount_dir):
1040-
succ = BlockDev.fs_resize(lv, 430 * 1024**2)
1049+
succ = BlockDev.fs_resize(lv, 390 * 1024**2)
10411050
self.assertTrue(succ)
10421051
with mounted(lv, self.mount_dir):
10431052
fi = BlockDev.fs_xfs_get_info(lv)
10441053
self.assertTrue(fi)
1045-
self.assertEqual(fi.block_size * fi.block_count, 430 * 1024**2)
1054+
self.assertEqual(fi.block_size * fi.block_count, 390 * 1024**2)
10461055

1047-
# should grow to 450 MiB (full size of the LV)
1056+
# should grow to 400 MiB (full size of the LV)
10481057
with mounted(lv, self.mount_dir):
10491058
succ = BlockDev.fs_resize(lv, 0, "xfs")
10501059
self.assertTrue(succ)
10511060
with mounted(lv, self.mount_dir):
10521061
fi = BlockDev.fs_xfs_get_info(lv)
10531062
self.assertTrue(fi)
1063+
self.assertEqual(fi.block_size * fi.block_count, 400 * 1024**2)
1064+
1065+
self._lvresize("libbd_fs_tests", "generic_test", "420M")
1066+
# unmounted resize (should get automounted)
1067+
succ = BlockDev.fs_resize(lv, 0, "xfs")
1068+
self.assertTrue(succ)
1069+
with mounted(lv, self.mount_dir):
1070+
fi = BlockDev.fs_xfs_get_info(lv)
1071+
self.assertTrue(fi)
1072+
self.assertEqual(fi.block_size * fi.block_count, 420 * 1024**2)
1073+
1074+
# enable XFS quotas
1075+
with mounted(lv, self.mount_dir, options="uquota,gquota,pquota"):
1076+
pass
1077+
1078+
self._lvresize("libbd_fs_tests", "generic_test", "450M")
1079+
succ = BlockDev.utils_init_logging(self.my_log_func)
1080+
self.assertTrue(succ)
1081+
BlockDev.utils_set_log_level(BlockDev.UTILS_LOG_INFO)
1082+
1083+
succ = BlockDev.fs_resize(lv, 0, "xfs")
1084+
self.assertTrue(succ)
1085+
with mounted(lv, self.mount_dir):
1086+
fi = BlockDev.fs_xfs_get_info(lv)
1087+
self.assertTrue(fi)
10541088
self.assertEqual(fi.block_size * fi.block_count, 450 * 1024**2)
10551089

1090+
# quota options should be preserved during temp mount
1091+
self.assertIn("with options: uquota,gquota,pquota", self.log)
1092+
1093+
BlockDev.utils_init_logging(None)
1094+
10561095
def _can_resize_f2fs(self):
10571096
ret, out, _err = utils.run_command("resize.f2fs -V")
10581097
if ret != 0:

tests/utils.py

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -277,12 +277,12 @@ def clean_scsi_debug(scsi_debug_dev):
277277
def _wait_for_nvme_controllers_ready(subnqn, timeout=3):
278278
"""
279279
Wait for NVMe controllers with matching subsystem NQN to be in live state
280-
280+
281281
:param str subnqn: subsystem nqn to match controllers against
282282
:param int timeout: timeout in seconds (default: 3)
283283
"""
284284
start_time = time.time()
285-
285+
286286
while time.time() - start_time < timeout:
287287
try:
288288
for ctrl_path in glob.glob("/sys/class/nvme/nvme*/"):
@@ -297,12 +297,12 @@ def _wait_for_nvme_controllers_ready(subnqn, timeout=3):
297297
return
298298
except:
299299
continue
300-
300+
301301
except:
302302
pass
303-
303+
304304
time.sleep(1)
305-
305+
306306
os.system("udevadm settle")
307307

308308
def find_nvme_ctrl_devs_for_subnqn(subnqn, wait_for_ready=True):
@@ -756,11 +756,18 @@ def run(cmd_string):
756756
return subprocess.call(cmd_string, close_fds=True, shell=True)
757757

758758

759-
def mount(device, where, ro=False):
759+
def mount(device, where, ro=False, options=None):
760760
if not os.path.isdir(where):
761761
os.makedirs(where)
762+
762763
if ro:
763-
os.system("mount -oro %s %s" % (device, where))
764+
if options:
765+
options += ",ro"
766+
else:
767+
options = "ro"
768+
769+
if options:
770+
os.system("mount -o %s %s %s" % (options, device, where))
764771
else:
765772
os.system("mount %s %s" % (device, where))
766773

0 commit comments

Comments
 (0)