Skip to content

Commit 6ada640

Browse files
Merge pull request #1132 from vojtechtrefny/master_xfs-mount-quota
Preserve XFS quota options in temporary mounts
2 parents 1483ed1 + 10b3cdf commit 6ada640

File tree

5 files changed

+143
-18
lines changed

5 files changed

+143
-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)

src/plugins/fs/mount.c

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,8 @@
2929
#include <errno.h>
3030
#include <string.h>
3131

32+
#include <blockdev/utils.h>
33+
3234
#include "fs.h"
3335
#include "mount.h"
3436

@@ -173,6 +175,8 @@ static gboolean do_unmount (MountArgs *args, GError **error) {
173175
}
174176
}
175177

178+
bd_utils_log_format (BD_UTILS_LOG_INFO, "Unmounting %s", args->spec);
179+
176180
ret = mnt_context_umount (cxt);
177181
#ifdef LIBMOUNT_NEW_ERR_API
178182
success = get_unmount_error_new (cxt, ret, args->spec, error);
@@ -424,6 +428,12 @@ static gboolean do_mount (MountArgs *args, GError **error) {
424428
mnt_context_enable_rwonly_mount (cxt, TRUE);
425429
#endif
426430

431+
bd_utils_log_format (BD_UTILS_LOG_INFO, "Mounting %s (fstype %s) to %s with options: %s",
432+
args->device ? args->device : "unspecified device",
433+
args->fstype ? args->fstype : "auto",
434+
args->mountpoint ? args->mountpoint : "unspecified mountpoint",
435+
args->options ? args->options : "none");
436+
427437
ret = mnt_context_mount (cxt);
428438

429439
/* we need to always do some libmount magic to check if the mount really

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)