From 6790db81d6bf59c34ca89f901b34e9f81cbde1a5 Mon Sep 17 00:00:00 2001 From: Mike Yuan Date: Mon, 3 Feb 2025 16:18:14 +0100 Subject: [PATCH 1/5] user-runtime-dir: correct quota size calculation Follow-up for b1c95fb2e9d11fc190017dec3d64f468f9d378bc Fixes #36245 --- src/login/user-runtime-dir.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/login/user-runtime-dir.c b/src/login/user-runtime-dir.c index fb33fdd939..451e181cd8 100644 --- a/src/login/user-runtime-dir.c +++ b/src/login/user-runtime-dir.c @@ -262,7 +262,7 @@ static int apply_tmpfs_quota( uint64_t v = (scale == 0) ? 0 : (scale == UINT32_MAX) ? UINT64_MAX : - (uint64_t) ((double) (sfs.f_blocks * sfs.f_frsize) / scale * UINT32_MAX); + (uint64_t) ((double) (sfs.f_blocks * sfs.f_frsize) * scale / UINT32_MAX); v = MIN(v, limit); v /= QIF_DQBLKSIZE; From 5399910671d3d00f268ba2aa49391cbfb43bb5e3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Fri, 28 Mar 2025 18:00:38 +0100 Subject: [PATCH 2/5] TEST-46-HOMED: write zeros instead of random bytes This should be faster, and equivalent for the purposes of quota calculation. --- test/units/TEST-46-HOMED.sh | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/test/units/TEST-46-HOMED.sh b/test/units/TEST-46-HOMED.sh index 78c91044b8..3bd6ebda48 100755 --- a/test/units/TEST-46-HOMED.sh +++ b/test/units/TEST-46-HOMED.sh @@ -669,10 +669,10 @@ if findmnt -n -o options /tmp | grep -q usrquota ; then NEWPASSWORD=quux homectl create tmpfsquota --storage=subvolume --dev-shm-limit=50K -P - run0 --property=SetCredential=pam.authtok.systemd-run0:quux -u tmpfsquota dd if=/dev/urandom of=/dev/shm/quotatestfile1 bs=1024 count=30 - (! run0 --property=SetCredential=pam.authtok.systemd-run0:quux -u tmpfsquota dd if=/dev/urandom of=/dev/shm/quotatestfile2 bs=1024 count=30) + run0 --property=SetCredential=pam.authtok.systemd-run0:quux -u tmpfsquota dd if=/dev/zero of=/dev/shm/quotatestfile1 bs=1024 count=30 + (! run0 --property=SetCredential=pam.authtok.systemd-run0:quux -u tmpfsquota dd if=/dev/zero of=/dev/shm/quotatestfile2 bs=1024 count=30) run0 --property=SetCredential=pam.authtok.systemd-run0:quux -u tmpfsquota rm /dev/shm/quotatestfile1 /dev/shm/quotatestfile2 - run0 --property=SetCredential=pam.authtok.systemd-run0:quux -u tmpfsquota dd if=/dev/urandom of=/dev/shm/quotatestfile1 bs=1024 count=30 + run0 --property=SetCredential=pam.authtok.systemd-run0:quux -u tmpfsquota dd if=/dev/zero of=/dev/shm/quotatestfile1 bs=1024 count=30 run0 --property=SetCredential=pam.authtok.systemd-run0:quux -u tmpfsquota rm /dev/shm/quotatestfile1 systemctl stop user@"$(id -u tmpfsquota)".service @@ -705,7 +705,7 @@ test "$(run0 --property=SetCredential=pam.authtok.systemd-run0:quux -u subareate test "$(run0 --property=SetCredential=pam.authtok.systemd-run0:quux -u subareatest -a furb sh -c 'echo $XDG_RUNTIME_DIR')" = "/run/user/$(id -u subareatest)/Areas/furb" # Install a PK rule that allows 'subareatest' user to invoke run0 without password, just for testing -cat > /usr/share/polkit-1/rules.d/subareatest.rules <<'EOF' +cat >/usr/share/polkit-1/rules.d/subareatest.rules <<'EOF' polkit.addRule(function(action, subject) { if (action.id == "org.freedesktop.systemd1.manage-units" && subject.user == "subareatest") { From cb6b1611622ee717d4d6e9251fa41652b8ba8e2f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Fri, 28 Mar 2025 18:45:23 +0100 Subject: [PATCH 3/5] test-display-quota: add a little helper binary to show quota on tmpfs quota from quota project fails: $ quota quota: Cannot stat() mounted device tmpfs: No such file or directory quota: Cannot stat() mounted device tmpfs: No such file or directory Having this helper helped me understand what is going on with the quotas when the tests failed. I think it'd be useful to keep it around for now, even though it is not actually connected in the tests. --- src/test/meson.build | 4 ++ src/test/test-display-quota.c | 88 +++++++++++++++++++++++++++++++++++ 2 files changed, 92 insertions(+) create mode 100644 src/test/test-display-quota.c diff --git a/src/test/meson.build b/src/test/meson.build index a4c33cb50c..c9fd63f798 100644 --- a/src/test/meson.build +++ b/src/test/meson.build @@ -267,6 +267,10 @@ executables += [ 'dependencies' : lib_openssl_or_gcrypt, 'conditions' : ['HAVE_OPENSSL_OR_GCRYPT'], }, + test_template + { + 'sources' : files('test-display-quota.c'), + 'type' : 'manual', + }, test_template + { 'sources' : files('test-dlopen-so.c'), 'dependencies' : [ diff --git a/src/test/test-display-quota.c b/src/test/test-display-quota.c new file mode 100644 index 0000000000..e3b67526a3 --- /dev/null +++ b/src/test/test-display-quota.c @@ -0,0 +1,88 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include "bitfield.h" +#include "fd-util.h" +#include "log.h" +#include "main-func.h" +#include "missing_syscall.h" +#include "quota-util.h" +#include "userdb.h" + +static int show_quota(uid_t uid, const char *path) { + int r; + + _cleanup_close_ int fd = open(path, O_DIRECTORY|O_CLOEXEC); + if (fd < 0) + return log_error_errno(errno, "Failed to open '%s': %m", path); + + struct dqblk req; + r = quotactl_fd_with_fallback(fd, QCMD_FIXED(Q_GETQUOTA, USRQUOTA), uid, &req); + if (r == -ESRCH) { + log_info_errno(r, "No quota set on %s for UID "UID_FMT": %m", path, uid); + return 0; + } + if (ERRNO_IS_NEG_NOT_SUPPORTED(r)) + return log_warning_errno(r, "No UID quota support on %s: %m", path); + if (ERRNO_IS_NEG_PRIVILEGE(r)) + return log_error_errno(r, "Lacking privileges to query UID quota on %s: %m", path); + if (r < 0) + return log_error_errno(r, "Failed to query disk quota on %s for UID "UID_FMT": %m", path, uid); + + printf("** Quota on %s for UID "UID_FMT" **\n" + "block hardlimit: %"PRIu64"\n" + "block softlimit: %"PRIu64"\n" + "blocks current: %"PRIu64"\n" + "inodes hardlimit: %"PRIu64"\n" + "inodes softlimit: %"PRIu64"\n" + "inodes current: %"PRIu64"\n" + "excess block time: %"PRIu64"\n" + "excess inode time: %"PRIu64"\n" + "validity mask: 0x%"PRIx32, + path, uid, + req.dqb_bhardlimit, + req.dqb_bsoftlimit, + req.dqb_curspace, + req.dqb_ihardlimit, + req.dqb_isoftlimit, + req.dqb_curinodes, + req.dqb_btime, + req.dqb_itime, + req.dqb_valid); + + const char* fields[] = {"BLIMITS", "SPACE", "INODES", "BTIME", "ITIME"}; + bool first = true; + for (size_t i = 0; i < ELEMENTSOF(fields); i++) + if (BIT_SET(req.dqb_valid, i)) { + printf("%c%s", first ? ' ' : '|', fields[i]); + first = false; + } + printf("%s\n", first ? "(none)" : ""); + + return 0; +} + +static int run(int argc, char **argv) { + int r; + + if (argc < 2) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "This program requires at least one argument\n" + "syntax: test-display-quota USER PATH…"); + + const char *user = argv[1]; + _cleanup_(user_record_unrefp) UserRecord *ur = NULL; + r = userdb_by_name(user, /* match= */ NULL, USERDB_PARSE_NUMERIC|USERDB_SUPPRESS_SHADOW, &ur); + if (r < 0) + return log_error_errno(r, "Failed to resolve user '%s': %m", user); + + if (!uid_is_valid(ur->uid)) + return log_error_errno(SYNTHETIC_ERRNO(ENOMSG), "User '%s' lacks UID.", ur->user_name); + + r = 0; + STRV_FOREACH(path, strv_skip(argv, 2)) + RET_GATHER(r, show_quota(ur->uid, *path)); + + return r; +} + +DEFINE_MAIN_FUNCTION(run); From cb86668f2cf7bc94ed28147fab7293b4795e6ab4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Sat, 29 Mar 2025 09:24:34 +0100 Subject: [PATCH 4/5] TEST-46-HOMED: check for support on /dev/shm and /tmp separately The test fails in CI. My guess was this is because the enablement of quota on /tmp and /dev/shm is independent. The former fs is mounted by systemd in the host, while the latter is mounted in the initrd, so we can end up with quota support on one but not the other, which is the situation I had on my laptop. This wasn't actually the source of the problems in CI, but it's a reasonable change to make anyway. While at it, test both mountpoints separately. --- test/units/TEST-46-HOMED.sh | 28 +++++++++++++--------------- 1 file changed, 13 insertions(+), 15 deletions(-) diff --git a/test/units/TEST-46-HOMED.sh b/test/units/TEST-46-HOMED.sh index 3bd6ebda48..d1a6ea2fc5 100755 --- a/test/units/TEST-46-HOMED.sh +++ b/test/units/TEST-46-HOMED.sh @@ -665,24 +665,22 @@ getent passwd aliastest@myrealm getent passwd aliastest2@myrealm getent passwd aliastest3@myrealm -if findmnt -n -o options /tmp | grep -q usrquota ; then +NEWPASSWORD=quux homectl create tmpfsquota --storage=subvolume --dev-shm-limit=50K --tmp-limit=50K -P +for p in /dev/shm /tmp; do + if findmnt -n -o options "$p" | grep -q usrquota; then + run0 --property=SetCredential=pam.authtok.systemd-run0:quux -u tmpfsquota dd if=/dev/zero of="$p/quotatestfile1" bs=1024 count=30 + (! run0 --property=SetCredential=pam.authtok.systemd-run0:quux -u tmpfsquota dd if=/dev/zero of="$p/quotatestfile2" bs=1024 count=30) + run0 --property=SetCredential=pam.authtok.systemd-run0:quux -u tmpfsquota rm "$p/quotatestfile1" "$p/quotatestfile2" + run0 --property=SetCredential=pam.authtok.systemd-run0:quux -u tmpfsquota dd if=/dev/zero of="$p/quotatestfile1" bs=1024 count=30 + run0 --property=SetCredential=pam.authtok.systemd-run0:quux -u tmpfsquota rm "$p/quotatestfile1" + fi +done - NEWPASSWORD=quux homectl create tmpfsquota --storage=subvolume --dev-shm-limit=50K -P - - run0 --property=SetCredential=pam.authtok.systemd-run0:quux -u tmpfsquota dd if=/dev/zero of=/dev/shm/quotatestfile1 bs=1024 count=30 - (! run0 --property=SetCredential=pam.authtok.systemd-run0:quux -u tmpfsquota dd if=/dev/zero of=/dev/shm/quotatestfile2 bs=1024 count=30) - run0 --property=SetCredential=pam.authtok.systemd-run0:quux -u tmpfsquota rm /dev/shm/quotatestfile1 /dev/shm/quotatestfile2 - run0 --property=SetCredential=pam.authtok.systemd-run0:quux -u tmpfsquota dd if=/dev/zero of=/dev/shm/quotatestfile1 bs=1024 count=30 - run0 --property=SetCredential=pam.authtok.systemd-run0:quux -u tmpfsquota rm /dev/shm/quotatestfile1 - - systemctl stop user@"$(id -u tmpfsquota)".service - - wait_for_state tmpfsquota inactive - homectl remove tmpfsquota -fi +systemctl stop user@"$(id -u tmpfsquota)".service +wait_for_state tmpfsquota inactive +homectl remove tmpfsquota NEWPASSWORD=quux homectl create subareatest --storage=subvolume -P - run0 --property=SetCredential=pam.authtok.systemd-run0:quux -u subareatest mkdir Areas run0 --property=SetCredential=pam.authtok.systemd-run0:quux -u subareatest cp -av /etc/skel Areas/furb run0 --property=SetCredential=pam.authtok.systemd-run0:quux -u subareatest cp -av /etc/skel Areas/molb From f77a8edfefd47e481fab78e7dd75d5d088d4e5e3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Mon, 31 Mar 2025 22:50:38 +0200 Subject: [PATCH 5/5] TEST-46-HOMED: conditionally skip usrquota tests The tests were failing, because the quota was not enforced. It seems that we simply don't have privileges to set or display the quota. The test is running priviled, so this is probably some SELinux: TEST-46-HOMED.sh[117]: + /usr/lib/systemd/tests/unit-tests/manual/test-display-quota tmpfsquota /dev/shm /tmp TEST-46-HOMED.sh[1103]: Lacking privileges to query UID quota on /dev/shm: Operation not permitted TEST-46-HOMED.sh[1103]: Lacking privileges to query UID quota on /tmp: Operation not permitted If we cannot display the quota, ignore the test results. In a local run under mkosi, quota is shown and the tests pass. So this is something about how the testing-farm:fedora-rawhide-x86_64 is configured. --- test/units/TEST-46-HOMED.sh | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/test/units/TEST-46-HOMED.sh b/test/units/TEST-46-HOMED.sh index d1a6ea2fc5..f7aadf38b5 100755 --- a/test/units/TEST-46-HOMED.sh +++ b/test/units/TEST-46-HOMED.sh @@ -668,11 +668,18 @@ getent passwd aliastest3@myrealm NEWPASSWORD=quux homectl create tmpfsquota --storage=subvolume --dev-shm-limit=50K --tmp-limit=50K -P for p in /dev/shm /tmp; do if findmnt -n -o options "$p" | grep -q usrquota; then + # Check if we can display the quotas. If we cannot, than it's likely + # that PID1 was also not able to set the limits and we should not fail + # in the tests below. + /usr/lib/systemd/tests/unit-tests/manual/test-display-quota tmpfsquota "$p" || set +e + run0 --property=SetCredential=pam.authtok.systemd-run0:quux -u tmpfsquota dd if=/dev/zero of="$p/quotatestfile1" bs=1024 count=30 (! run0 --property=SetCredential=pam.authtok.systemd-run0:quux -u tmpfsquota dd if=/dev/zero of="$p/quotatestfile2" bs=1024 count=30) run0 --property=SetCredential=pam.authtok.systemd-run0:quux -u tmpfsquota rm "$p/quotatestfile1" "$p/quotatestfile2" run0 --property=SetCredential=pam.authtok.systemd-run0:quux -u tmpfsquota dd if=/dev/zero of="$p/quotatestfile1" bs=1024 count=30 run0 --property=SetCredential=pam.authtok.systemd-run0:quux -u tmpfsquota rm "$p/quotatestfile1" + + set -e fi done