systemd-257-24

Resolves: RHEL-155454, RHEL-155805, RHEL-155396, RHEL-158303, RHEL-158354, RHEL-143728, RHEL-168098, RHEL-143028
This commit is contained in:
Jan Macku 2026-04-16 15:01:05 +02:00
parent 363d20f6ec
commit b7ebf97389
53 changed files with 4834 additions and 1 deletions

View File

@ -0,0 +1,89 @@
From 3061980e575f36129fe1880118ae757e6069eaa0 Mon Sep 17 00:00:00 2001
From: Luca Boccassi <luca.boccassi@gmail.com>
Date: Fri, 17 Oct 2025 14:00:23 +0100
Subject: [PATCH] ci: re-enable bpf-framework option for build and unit test
jobs
Use the same trickery we do in the package build and search for
the actual bpftool binary. For the CI job any one we find is
good enough.
When we switch all jobs to 26.04 we can drop all of this.
This reverts commit cc814110af7a453db898ea2990a0281616d5ceff.
(cherry picked from commit 3b11139c0db9dd0a37b0493a8d2ad5f531a92344)
Related: RHEL-155454
---
.github/workflows/build_test.sh | 12 ++++++++++++
.github/workflows/unit_tests.sh | 13 +++++++++++++
2 files changed, 25 insertions(+)
diff --git a/.github/workflows/build_test.sh b/.github/workflows/build_test.sh
index 113af704a8..462203be9a 100755
--- a/.github/workflows/build_test.sh
+++ b/.github/workflows/build_test.sh
@@ -47,6 +47,7 @@ PACKAGES=(
libxkbcommon-dev
libxtables-dev
libzstd-dev
+ linux-tools-generic
mold
mount
net-tools
@@ -131,6 +132,17 @@ sudo apt-get -y install "${PACKAGES[@]}"
pip3 install --user -r .github/workflows/requirements.txt --require-hashes --break-system-packages
export PATH="$HOME/.local/bin:$PATH"
+# TODO: drop after we switch to ubuntu 26.04
+bpftool_dir=$(dirname "$(find /usr/lib/linux-tools/ /usr/lib/linux-tools-* -name 'bpftool' -perm /u=x 2>/dev/null | sort -r | head -n1)")
+if [ -n "$bpftool_dir" ]; then
+ export PATH="$bpftool_dir:$PATH"
+fi
+
+if [[ -n "$CUSTOM_PYTHON" ]]; then
+ # If CUSTOM_PYTHON is set we need to pull jinja2 from pip, as a local interpreter is used
+ pip3 install --user --break-system-packages jinja2
+fi
+
$CC --version
meson --version
ninja --version
diff --git a/.github/workflows/unit_tests.sh b/.github/workflows/unit_tests.sh
index 168bcc55c3..3acaf75417 100755
--- a/.github/workflows/unit_tests.sh
+++ b/.github/workflows/unit_tests.sh
@@ -18,6 +18,7 @@ ADDITIONAL_DEPS=(
libtss2-dev
libxkbcommon-dev
libzstd-dev
+ linux-tools-generic
python3-libevdev
python3-pefile
python3-pyelftools
@@ -70,6 +71,12 @@ for phase in "${PHASES[@]}"; do
capsh --drop=all -- -c "stat $PWD/meson.build"
;;
RUN|RUN_GCC|RUN_CLANG|RUN_CLANG_RELEASE)
+ # TODO: drop after we switch to ubuntu 26.04
+ bpftool_dir=$(dirname "$(find /usr/lib/linux-tools/ /usr/lib/linux-tools-* -name 'bpftool' -perm /u=x 2>/dev/null | sort -r | head -n1)")
+ if [ -n "$bpftool_dir" ]; then
+ export PATH="$bpftool_dir:$PATH"
+ fi
+
if [[ "$phase" =~ ^RUN_CLANG ]]; then
export CC=clang
export CXX=clang++
@@ -94,6 +101,12 @@ for phase in "${PHASES[@]}"; do
TZ=GMT+12 meson test -C build --print-errorlogs
;;
RUN_ASAN_UBSAN|RUN_GCC_ASAN_UBSAN|RUN_CLANG_ASAN_UBSAN|RUN_CLANG_ASAN_UBSAN_NO_DEPS)
+ # TODO: drop after we switch to ubuntu 26.04
+ bpftool_dir=$(dirname "$(find /usr/lib/linux-tools/ /usr/lib/linux-tools-* -name 'bpftool' -perm /u=x 2>/dev/null | sort -r | head -n1)")
+ if [ -n "$bpftool_dir" ]; then
+ export PATH="$bpftool_dir:$PATH"
+ fi
+
MESON_ARGS=(--optimization=1)
if [[ "$phase" =~ ^RUN_CLANG_ASAN_UBSAN ]]; then

View File

@ -0,0 +1,32 @@
From 5886ebf6256fb0b1873828fa46d918877c18e474 Mon Sep 17 00:00:00 2001
From: Luca Boccassi <luca.boccassi@gmail.com>
Date: Fri, 17 Oct 2025 15:39:09 +0100
Subject: [PATCH] ci: add bpftool workaround to codeql job too
(cherry picked from commit e9fd2bbfffc5c2c7cd1ea0a288d5435fc15e387f)
Related: RHEL-155454
---
.github/workflows/codeql.yml | 9 ++++++++-
1 file changed, 8 insertions(+), 1 deletion(-)
diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml
index 51034783ca..443221fa11 100644
--- a/.github/workflows/codeql.yml
+++ b/.github/workflows/codeql.yml
@@ -44,7 +44,14 @@ jobs:
languages: ${{ matrix.language }}
config-file: ./.github/codeql-config.yml
- - run: sudo -E .github/workflows/unit_tests.sh SETUP
+ - run: |
+ sudo -E .github/workflows/unit_tests.sh SETUP
+ # TODO: drop after we switch to ubuntu 26.04
+ bpftool_binary=$(find /usr/lib/linux-tools/ /usr/lib/linux-tools-* -name 'bpftool' -perm /u=x 2>/dev/null | sort -r | head -n1)
+ if [ -n "$bpftool_binary" ]; then
+ sudo rm -f /usr/bin/bpftool
+ sudo ln -s "$bpftool_binary" /usr/bin/
+ fi
- name: Autobuild
uses: github/codeql-action/autobuild@f09c1c0a94de965c15400f5634aa42fac8fb8f88

View File

@ -0,0 +1,27 @@
From 9455ccb53a8cf50d9b7026e297f8afd8847a57e2 Mon Sep 17 00:00:00 2001
From: Yu Watanabe <watanabe.yu+github@gmail.com>
Date: Sat, 18 Oct 2025 10:39:13 +0900
Subject: [PATCH] ci: fix workaround about bpftool for codeql
Follow-up for e9fd2bbfffc5c2c7cd1ea0a288d5435fc15e387f.
(cherry picked from commit a6836cfa0bdf1bb1fcf05686c5af3f2b5ad97f6b)
Related: RHEL-155454
---
.github/workflows/codeql.yml | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml
index 443221fa11..cfe54d59ac 100644
--- a/.github/workflows/codeql.yml
+++ b/.github/workflows/codeql.yml
@@ -49,7 +49,7 @@ jobs:
# TODO: drop after we switch to ubuntu 26.04
bpftool_binary=$(find /usr/lib/linux-tools/ /usr/lib/linux-tools-* -name 'bpftool' -perm /u=x 2>/dev/null | sort -r | head -n1)
if [ -n "$bpftool_binary" ]; then
- sudo rm -f /usr/bin/bpftool
+ sudo rm -f /usr/{bin,sbin}/bpftool
sudo ln -s "$bpftool_binary" /usr/bin/
fi

View File

@ -0,0 +1,31 @@
From 2842f1200bcb79f211309270a0afab4ab7a89b20 Mon Sep 17 00:00:00 2001
From: Luca Boccassi <luca.boccassi@gmail.com>
Date: Sat, 18 Oct 2025 14:23:59 +0100
Subject: [PATCH] ci: add bpftool workaround to coverity too
(cherry picked from commit d29f181cf02100c146fc8691a5515a708d06ddbf)
Related: RHEL-155454
---
.github/workflows/coverity.yml | 9 ++++++++-
1 file changed, 8 insertions(+), 1 deletion(-)
diff --git a/.github/workflows/coverity.yml b/.github/workflows/coverity.yml
index 66204069c4..80d25d380a 100644
--- a/.github/workflows/coverity.yml
+++ b/.github/workflows/coverity.yml
@@ -25,6 +25,13 @@ jobs:
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
# Reuse the setup phase of the unit test script to avoid code duplication
- name: Install build dependencies
- run: sudo -E .github/workflows/unit_tests.sh SETUP
+ run: |
+ sudo -E .github/workflows/unit_tests.sh SETUP
+ # TODO: drop after we switch to ubuntu 26.04
+ bpftool_binary=$(find /usr/lib/linux-tools/ /usr/lib/linux-tools-* -name 'bpftool' -perm /u=x 2>/dev/null | sort -r | head -n1)
+ if [ -n "$bpftool_binary" ]; then
+ sudo rm -f /usr/{bin,sbin}/bpftool
+ sudo ln -s "$bpftool_binary" /usr/bin/
+ fi
- name: Build & upload the results
run: tools/coverity.sh

View File

@ -0,0 +1,36 @@
From 3e597926e21dfbaf2b4a38aa64a4da954815f2b7 Mon Sep 17 00:00:00 2001
From: Jan Macku <jamacku@redhat.com>
Date: Mon, 30 Mar 2026 14:48:42 +0200
Subject: [PATCH] Revert "man: mention RHEL documentation in systemctl's man
page"
This reverts commit fdebd2be914ce55ec4244c99adc93fc7f046c08f.
rhel-only: doc
Resolves: RHEL-155805
---
man/systemctl.xml | 10 ----------
1 file changed, 10 deletions(-)
diff --git a/man/systemctl.xml b/man/systemctl.xml
index 322505e615..fe15bd3722 100644
--- a/man/systemctl.xml
+++ b/man/systemctl.xml
@@ -2978,16 +2978,6 @@ EOF
</variablelist>
</refsect1>
- <refsect1>
- <title>Examples</title>
- <para>
- For examples how to use systemctl in comparison with old service and chkconfig commands please see:
- <ulink url="https://docs.redhat.com/en/documentation/red_hat_enterprise_linux/10/html/using_systemd_unit_files_to_customize_and_optimize_your_system/managing-systemd#managing-system-services-with-systemctl">
- Managing System Services
- </ulink>
- </para>
- </refsect1>
-
<refsect1>
<title>See Also</title>
<para><simplelist type="inline">

View File

@ -0,0 +1,162 @@
From 655ca1b4d224079a6ce87f705fb0c33ebe69726f Mon Sep 17 00:00:00 2001
From: Lennart Poettering <lennart@poettering.net>
Date: Mon, 19 May 2025 12:58:52 +0200
Subject: [PATCH] path-util: add flavour of path_startswith() that leaves a
leading slash in place
(cherry picked from commit ee19edbb9f3455db3f750089082f3e5a925e3a0c)
Related: RHEL-155396
---
src/basic/fs-util.c | 2 +-
src/basic/mkdir.c | 2 +-
src/basic/path-util.c | 39 ++++++++++++++++++++++++++++-----------
src/basic/path-util.h | 10 ++++++++--
src/test/test-path-util.c | 16 ++++++++++++++++
5 files changed, 54 insertions(+), 15 deletions(-)
diff --git a/src/basic/fs-util.c b/src/basic/fs-util.c
index 4ede324c34..21a670c9e8 100644
--- a/src/basic/fs-util.c
+++ b/src/basic/fs-util.c
@@ -66,7 +66,7 @@ int rmdir_parents(const char *path, const char *stop) {
assert(*slash == '/');
*slash = '\0';
- if (path_startswith_full(stop, p, /* accept_dot_dot= */ false))
+ if (path_startswith_full(stop, p, /* flags= */ 0))
return 0;
if (rmdir(p) < 0 && errno != ENOENT)
diff --git a/src/basic/mkdir.c b/src/basic/mkdir.c
index 3e1545a58b..6fc2a79944 100644
--- a/src/basic/mkdir.c
+++ b/src/basic/mkdir.c
@@ -149,7 +149,7 @@ int mkdir_parents_internal(const char *prefix, const char *path, mode_t mode, ui
assert(_mkdirat != mkdirat);
if (prefix) {
- p = path_startswith_full(path, prefix, /* accept_dot_dot= */ false);
+ p = path_startswith_full(path, prefix, /* flags= */ 0);
if (!p)
return -EINVAL;
diff --git a/src/basic/path-util.c b/src/basic/path-util.c
index 78ba10ed80..a4709e1b7d 100644
--- a/src/basic/path-util.c
+++ b/src/basic/path-util.c
@@ -405,8 +405,8 @@ char* path_simplify_full(char *path, PathSimplifyFlags flags) {
return path;
}
-char* path_startswith_full(const char *path, const char *prefix, bool accept_dot_dot) {
- assert(path);
+char* path_startswith_full(const char *original_path, const char *prefix, PathStartWithFlags flags) {
+ assert(original_path);
assert(prefix);
/* Returns a pointer to the start of the first component after the parts matched by
@@ -419,28 +419,45 @@ char* path_startswith_full(const char *path, const char *prefix, bool accept_dot
* Returns NULL otherwise.
*/
+ const char *path = original_path;
+
if ((path[0] == '/') != (prefix[0] == '/'))
return NULL;
for (;;) {
const char *p, *q;
- int r, k;
+ int m, n;
- r = path_find_first_component(&path, accept_dot_dot, &p);
- if (r < 0)
+ m = path_find_first_component(&path, FLAGS_SET(flags, PATH_STARTSWITH_ACCEPT_DOT_DOT), &p);
+ if (m < 0)
return NULL;
- k = path_find_first_component(&prefix, accept_dot_dot, &q);
- if (k < 0)
+ n = path_find_first_component(&prefix, FLAGS_SET(flags, PATH_STARTSWITH_ACCEPT_DOT_DOT), &q);
+ if (n < 0)
return NULL;
- if (k == 0)
- return (char*) (p ?: path);
+ if (n == 0) {
+ if (!p)
+ p = path;
+
+ if (FLAGS_SET(flags, PATH_STARTSWITH_RETURN_LEADING_SLASH)) {
+
+ if (p <= original_path)
+ return NULL;
+
+ p--;
+
+ if (*p != '/')
+ return NULL;
+ }
+
+ return (char*) p;
+ }
- if (r != k)
+ if (m != n)
return NULL;
- if (!strneq(p, q, r))
+ if (!strneq(p, q, m))
return NULL;
}
}
diff --git a/src/basic/path-util.h b/src/basic/path-util.h
index dff5a3a549..d1e9f4b785 100644
--- a/src/basic/path-util.h
+++ b/src/basic/path-util.h
@@ -52,9 +52,15 @@ int safe_getcwd(char **ret);
int path_make_absolute_cwd(const char *p, char **ret);
int path_make_relative(const char *from, const char *to, char **ret);
int path_make_relative_parent(const char *from_child, const char *to, char **ret);
-char* path_startswith_full(const char *path, const char *prefix, bool accept_dot_dot) _pure_;
+
+typedef enum PathStartWithFlags {
+ PATH_STARTSWITH_ACCEPT_DOT_DOT = 1U << 0,
+ PATH_STARTSWITH_RETURN_LEADING_SLASH = 1U << 1,
+} PathStartWithFlags;
+
+char* path_startswith_full(const char *path, const char *prefix, PathStartWithFlags flags) _pure_;
static inline char* path_startswith(const char *path, const char *prefix) {
- return path_startswith_full(path, prefix, true);
+ return path_startswith_full(path, prefix, PATH_STARTSWITH_ACCEPT_DOT_DOT);
}
int path_compare(const char *a, const char *b) _pure_;
diff --git a/src/test/test-path-util.c b/src/test/test-path-util.c
index e02bd8c857..8aa477bd69 100644
--- a/src/test/test-path-util.c
+++ b/src/test/test-path-util.c
@@ -756,6 +756,22 @@ TEST(path_startswith) {
test_path_startswith_one("/foo/bar/barfoo/", "/fo", NULL, NULL);
}
+static void test_path_startswith_return_leading_slash_one(const char *path, const char *prefix, const char *expected) {
+ const char *p;
+
+ log_debug("/* %s(%s, %s) */", __func__, path, prefix);
+
+ p = path_startswith_full(path, prefix, PATH_STARTSWITH_RETURN_LEADING_SLASH);
+ ASSERT_STREQ(p, expected);
+}
+
+TEST(path_startswith_return_leading_slash) {
+ test_path_startswith_return_leading_slash_one("/foo/bar", "/", "/foo/bar");
+ test_path_startswith_return_leading_slash_one("/foo/bar", "/foo", "/bar");
+ test_path_startswith_return_leading_slash_one("/foo/bar", "/foo/bar", NULL);
+ test_path_startswith_return_leading_slash_one("/foo/bar/", "/foo/bar", "/");
+}
+
static void test_prefix_root_one(const char *r, const char *p, const char *expected) {
_cleanup_free_ char *s = NULL;
const char *t;

View File

@ -0,0 +1,45 @@
From 6ac39e6caa209898323dda2d907b594322ba3894 Mon Sep 17 00:00:00 2001
From: Lennart Poettering <lennart@poettering.net>
Date: Thu, 22 May 2025 18:35:25 +0200
Subject: [PATCH] cgroup: port some code over to path_startswith_full()
(cherry picked from commit 482107724f64cb8dd24db3e65b6ea3151a330301)
Related: RHEL-155396
---
src/basic/cgroup-util.c | 10 ++--------
1 file changed, 2 insertions(+), 8 deletions(-)
diff --git a/src/basic/cgroup-util.c b/src/basic/cgroup-util.c
index 309dccb45a..b8a17badea 100644
--- a/src/basic/cgroup-util.c
+++ b/src/basic/cgroup-util.c
@@ -1013,13 +1013,12 @@ int cg_get_root_path(char **ret_path) {
}
int cg_shift_path(const char *cgroup, const char *root, const char **ret_shifted) {
- _cleanup_free_ char *rt = NULL;
- char *p;
int r;
assert(cgroup);
assert(ret_shifted);
+ _cleanup_free_ char *rt = NULL;
if (!root) {
/* If the root was specified let's use that, otherwise
* let's determine it from PID 1 */
@@ -1031,12 +1030,7 @@ int cg_shift_path(const char *cgroup, const char *root, const char **ret_shifted
root = rt;
}
- p = path_startswith(cgroup, root);
- if (p && p > cgroup)
- *ret_shifted = p - 1;
- else
- *ret_shifted = cgroup;
-
+ *ret_shifted = path_startswith_full(cgroup, root, PATH_STARTSWITH_RETURN_LEADING_SLASH) ?: cgroup;
return 0;
}

View File

@ -0,0 +1,95 @@
From 889e708da11d9185abb56d5aa42bb1df747217b5 Mon Sep 17 00:00:00 2001
From: Lennart Poettering <lennart@poettering.net>
Date: Fri, 23 May 2025 06:45:40 +0200
Subject: [PATCH] path-util: invert PATH_STARTSWITH_ACCEPT_DOT_DOT flag
As requested: https://github.com/systemd/systemd/pull/37572#pullrequestreview-2861928094
(cherry picked from commit ceed11e465f1c8efff1931412a85924d9de7c08d)
Related: RHEL-155396
---
src/basic/cgroup-util.c | 2 +-
src/basic/fs-util.c | 2 +-
src/basic/mkdir.c | 2 +-
src/basic/path-util.c | 4 ++--
src/basic/path-util.h | 4 ++--
5 files changed, 7 insertions(+), 7 deletions(-)
diff --git a/src/basic/cgroup-util.c b/src/basic/cgroup-util.c
index b8a17badea..925b753c39 100644
--- a/src/basic/cgroup-util.c
+++ b/src/basic/cgroup-util.c
@@ -1030,7 +1030,7 @@ int cg_shift_path(const char *cgroup, const char *root, const char **ret_shifted
root = rt;
}
- *ret_shifted = path_startswith_full(cgroup, root, PATH_STARTSWITH_RETURN_LEADING_SLASH) ?: cgroup;
+ *ret_shifted = path_startswith_full(cgroup, root, PATH_STARTSWITH_RETURN_LEADING_SLASH|PATH_STARTSWITH_REFUSE_DOT_DOT) ?: cgroup;
return 0;
}
diff --git a/src/basic/fs-util.c b/src/basic/fs-util.c
index 21a670c9e8..69e76653ab 100644
--- a/src/basic/fs-util.c
+++ b/src/basic/fs-util.c
@@ -66,7 +66,7 @@ int rmdir_parents(const char *path, const char *stop) {
assert(*slash == '/');
*slash = '\0';
- if (path_startswith_full(stop, p, /* flags= */ 0))
+ if (path_startswith_full(stop, p, PATH_STARTSWITH_REFUSE_DOT_DOT))
return 0;
if (rmdir(p) < 0 && errno != ENOENT)
diff --git a/src/basic/mkdir.c b/src/basic/mkdir.c
index 6fc2a79944..f1e5f2dc8d 100644
--- a/src/basic/mkdir.c
+++ b/src/basic/mkdir.c
@@ -149,7 +149,7 @@ int mkdir_parents_internal(const char *prefix, const char *path, mode_t mode, ui
assert(_mkdirat != mkdirat);
if (prefix) {
- p = path_startswith_full(path, prefix, /* flags= */ 0);
+ p = path_startswith_full(path, prefix, PATH_STARTSWITH_REFUSE_DOT_DOT);
if (!p)
return -EINVAL;
diff --git a/src/basic/path-util.c b/src/basic/path-util.c
index a4709e1b7d..fda6066bc6 100644
--- a/src/basic/path-util.c
+++ b/src/basic/path-util.c
@@ -428,11 +428,11 @@ char* path_startswith_full(const char *original_path, const char *prefix, PathSt
const char *p, *q;
int m, n;
- m = path_find_first_component(&path, FLAGS_SET(flags, PATH_STARTSWITH_ACCEPT_DOT_DOT), &p);
+ m = path_find_first_component(&path, !FLAGS_SET(flags, PATH_STARTSWITH_REFUSE_DOT_DOT), &p);
if (m < 0)
return NULL;
- n = path_find_first_component(&prefix, FLAGS_SET(flags, PATH_STARTSWITH_ACCEPT_DOT_DOT), &q);
+ n = path_find_first_component(&prefix, !FLAGS_SET(flags, PATH_STARTSWITH_REFUSE_DOT_DOT), &q);
if (n < 0)
return NULL;
diff --git a/src/basic/path-util.h b/src/basic/path-util.h
index d1e9f4b785..429d7ac507 100644
--- a/src/basic/path-util.h
+++ b/src/basic/path-util.h
@@ -54,13 +54,13 @@ int path_make_relative(const char *from, const char *to, char **ret);
int path_make_relative_parent(const char *from_child, const char *to, char **ret);
typedef enum PathStartWithFlags {
- PATH_STARTSWITH_ACCEPT_DOT_DOT = 1U << 0,
+ PATH_STARTSWITH_REFUSE_DOT_DOT = 1U << 0,
PATH_STARTSWITH_RETURN_LEADING_SLASH = 1U << 1,
} PathStartWithFlags;
char* path_startswith_full(const char *path, const char *prefix, PathStartWithFlags flags) _pure_;
static inline char* path_startswith(const char *path, const char *prefix) {
- return path_startswith_full(path, prefix, PATH_STARTSWITH_ACCEPT_DOT_DOT);
+ return path_startswith_full(path, prefix, 0);
}
int path_compare(const char *a, const char *b) _pure_;

View File

@ -0,0 +1,28 @@
From 9108a63c9d99242b6a5c96046b1494d3101afa2a Mon Sep 17 00:00:00 2001
From: Mike Yuan <me@yhndnzj.com>
Date: Thu, 26 Feb 2026 11:07:39 +0100
Subject: [PATCH] sd-json: fix off-by-one issue when updating parent for array
elements
Follow-up for 8525bb369a09f488ec77f94e1557ecc2343eb4ab
(cherry picked from commit 4e6e3b8707c84018051ae1885af20e06b2a5209e)
Related: RHEL-155396
---
src/libsystemd/sd-json/sd-json.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/libsystemd/sd-json/sd-json.c b/src/libsystemd/sd-json/sd-json.c
index 64b59e0f3f..97897122b8 100644
--- a/src/libsystemd/sd-json/sd-json.c
+++ b/src/libsystemd/sd-json/sd-json.c
@@ -2243,7 +2243,7 @@ _public_ int sd_json_variant_append_array(sd_json_variant **v, sd_json_variant *
if (old != *v)
/* Readjust the parent pointers to the new address */
- for (size_t i = 1; i < size; i++)
+ for (size_t i = 0; i < size; i++)
(*v)[1 + i].parent = *v;
return json_variant_array_put_element(*v, element);

View File

@ -0,0 +1,96 @@
From 3af048c981a816e0dca65597532456fb7630a417 Mon Sep 17 00:00:00 2001
From: Mike Yuan <me@yhndnzj.com>
Date: Thu, 26 Feb 2026 11:06:00 +0100
Subject: [PATCH] core/cgroup: avoid one unnecessary strjoina()
(cherry picked from commit 42aee39107fbdd7db1ccd402a2151822b2805e9f)
Related: RHEL-155396
---
src/core/cgroup.c | 28 +++++++++++++---------------
1 file changed, 13 insertions(+), 15 deletions(-)
diff --git a/src/core/cgroup.c b/src/core/cgroup.c
index 22a29666b6..88ba883f77 100644
--- a/src/core/cgroup.c
+++ b/src/core/cgroup.c
@@ -2961,12 +2961,13 @@ static int unit_update_cgroup(
return 0;
}
-static int unit_attach_pid_to_cgroup_via_bus(Unit *u, pid_t pid, const char *suffix_path) {
+static int unit_attach_pid_to_cgroup_via_bus(Unit *u, const char *cgroup_path, pid_t pid) {
_cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
- char *pp;
int r;
assert(u);
+ assert(cgroup_path);
+ assert(pid_is_valid(pid));
if (MANAGER_IS_SYSTEM(u->manager))
return -EINVAL;
@@ -2974,18 +2975,13 @@ static int unit_attach_pid_to_cgroup_via_bus(Unit *u, pid_t pid, const char *suf
if (!u->manager->system_bus)
return -EIO;
- CGroupRuntime *crt = unit_get_cgroup_runtime(u);
- if (!crt || !crt->cgroup_path)
- return -EOWNERDEAD;
-
/* Determine this unit's cgroup path relative to our cgroup root */
- pp = path_startswith(crt->cgroup_path, u->manager->cgroup_root);
+ const char *pp = path_startswith_full(cgroup_path,
+ u->manager->cgroup_root,
+ PATH_STARTSWITH_RETURN_LEADING_SLASH|PATH_STARTSWITH_REFUSE_DOT_DOT);
if (!pp)
return -EINVAL;
- pp = strjoina("/", pp, suffix_path);
- path_simplify(pp);
-
r = bus_call_method(u->manager->system_bus,
bus_systemd_mgr,
"AttachProcessesToUnit",
@@ -3026,8 +3022,10 @@ int unit_attach_pids_to_cgroup(Unit *u, Set *pids, const char *suffix_path) {
CGroupRuntime *crt = ASSERT_PTR(unit_get_cgroup_runtime(u));
if (isempty(suffix_path))
- p = crt->cgroup_path;
+ p = empty_to_root(crt->cgroup_path);
else {
+ assert(path_is_absolute(suffix_path));
+
joined = path_join(crt->cgroup_path, suffix_path);
if (!joined)
return -ENOMEM;
@@ -3045,7 +3043,7 @@ int unit_attach_pids_to_cgroup(Unit *u, Set *pids, const char *suffix_path) {
* before we use it */
r = pidref_verify(pid);
if (r < 0) {
- log_unit_info_errno(u, r, "PID " PID_FMT " vanished before we could move it to target cgroup '%s', skipping: %m", pid->pid, empty_to_root(p));
+ log_unit_info_errno(u, r, "PID " PID_FMT " vanished before we could move it to target cgroup '%s', skipping: %m", pid->pid, p);
continue;
}
@@ -3056,7 +3054,7 @@ int unit_attach_pids_to_cgroup(Unit *u, Set *pids, const char *suffix_path) {
log_unit_full_errno(u, again ? LOG_DEBUG : LOG_INFO, r,
"Couldn't move process "PID_FMT" to%s requested cgroup '%s': %m",
- pid->pid, again ? " directly" : "", empty_to_root(p));
+ pid->pid, again ? " directly" : "", p);
if (again) {
int z;
@@ -3066,9 +3064,9 @@ int unit_attach_pids_to_cgroup(Unit *u, Set *pids, const char *suffix_path) {
* Since it's more privileged it might be able to move the process across the
* leaves of a subtree whose top node is not owned by us. */
- z = unit_attach_pid_to_cgroup_via_bus(u, pid->pid, suffix_path);
+ z = unit_attach_pid_to_cgroup_via_bus(u, p, pid->pid);
if (z < 0)
- log_unit_info_errno(u, z, "Couldn't move process "PID_FMT" to requested cgroup '%s' (directly or via the system bus): %m", pid->pid, empty_to_root(p));
+ log_unit_info_errno(u, z, "Couldn't move process "PID_FMT" to requested cgroup '%s' (directly or via the system bus): %m", pid->pid, p);
else {
if (ret >= 0)
ret++; /* Count successful additions */

View File

@ -0,0 +1,29 @@
From e1c092e585f4cada10dbd79d0c31bfb9156edea0 Mon Sep 17 00:00:00 2001
From: Mike Yuan <me@yhndnzj.com>
Date: Thu, 26 Feb 2026 11:06:34 +0100
Subject: [PATCH] core: validate input cgroup path more prudently
(cherry picked from commit efa6ba2ab625aaa160ac435a09e6482fc63bdbe8)
Resolves: RHEL-155396
---
src/core/dbus-manager.c | 6 ++++++
1 file changed, 6 insertions(+)
diff --git a/src/core/dbus-manager.c b/src/core/dbus-manager.c
index 8e39d67a00..d516f30c96 100644
--- a/src/core/dbus-manager.c
+++ b/src/core/dbus-manager.c
@@ -622,6 +622,12 @@ static int method_get_unit_by_control_group(sd_bus_message *message, void *userd
if (r < 0)
return r;
+ if (!path_is_absolute(cgroup))
+ return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Control group path is not absolute: %s", cgroup);
+
+ if (!path_is_normalized(cgroup))
+ return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Control group path is not normalized: %s", cgroup);
+
u = manager_get_unit_by_cgroup(m, cgroup);
if (!u)
return sd_bus_error_setf(error, BUS_ERROR_NO_SUCH_UNIT,

View File

@ -0,0 +1,55 @@
From b29c80a11df03e849e4f2a33e8332776c4b0f637 Mon Sep 17 00:00:00 2001
From: Luca Boccassi <luca.boccassi@gmail.com>
Date: Wed, 11 Mar 2026 12:15:26 +0000
Subject: [PATCH] nspawn: apply BindUser/Ephemeral from settings file only if
trusted
Originally reported on yeswehack.com as:
YWH-PGM9780-116
Follow-up for 2f8930449079403b26c9164b8eeac78d5af2c8df
Follow-up for a2f577fca0be79b23f61f033229b64884e7d840a
(cherry picked from commit 61bceb1bff4b1f9c126b18dc971ca3e6d8c71c40)
Resolves: RHEL-158303
---
src/nspawn/nspawn.c | 18 ++++++++++++++----
1 file changed, 14 insertions(+), 4 deletions(-)
diff --git a/src/nspawn/nspawn.c b/src/nspawn/nspawn.c
index 724639df5c..acf579c007 100644
--- a/src/nspawn/nspawn.c
+++ b/src/nspawn/nspawn.c
@@ -4739,8 +4739,13 @@ static int merge_settings(Settings *settings, const char *path) {
}
if ((arg_settings_mask & SETTING_EPHEMERAL) == 0 &&
- settings->ephemeral >= 0)
- arg_ephemeral = settings->ephemeral;
+ settings->ephemeral >= 0) {
+
+ if (!arg_settings_trusted)
+ log_warning("Ignoring ephemeral setting, file %s is not trusted.", path);
+ else
+ arg_ephemeral = settings->ephemeral;
+ }
if ((arg_settings_mask & SETTING_DIRECTORY) == 0 &&
settings->root) {
@@ -4908,8 +4913,13 @@ static int merge_settings(Settings *settings, const char *path) {
}
if ((arg_settings_mask & SETTING_BIND_USER) == 0 &&
- !strv_isempty(settings->bind_user))
- strv_free_and_replace(arg_bind_user, settings->bind_user);
+ !strv_isempty(settings->bind_user)) {
+
+ if (!arg_settings_trusted)
+ log_warning("Ignoring bind user setting, file %s is not trusted.", path);
+ else
+ strv_free_and_replace(arg_bind_user, settings->bind_user);
+ }
if ((arg_settings_mask & SETTING_NOTIFY_READY) == 0 &&
settings->notify_ready >= 0)

View File

@ -0,0 +1,32 @@
From 5f8d0355128c2aaa59ffc56916d1939d6022e6db Mon Sep 17 00:00:00 2001
From: Luca Boccassi <luca.boccassi@gmail.com>
Date: Wed, 11 Mar 2026 13:27:14 +0000
Subject: [PATCH] nspawn: normalize pivot_root paths
Originally reported on yeswehack.com as:
YWH-PGM9780-116
Follow-up for b53ede699cdc5233041a22591f18863fb3fe2672
(cherry picked from commit 7b85f5498a958e5bb660c703b8f4a71cceed3373)
Resolves: RHEL-158303
---
src/nspawn/nspawn-mount.c | 4 +++-
1 file changed, 3 insertions(+), 1 deletion(-)
diff --git a/src/nspawn/nspawn-mount.c b/src/nspawn/nspawn-mount.c
index ddbdba6fb6..c233cdf600 100644
--- a/src/nspawn/nspawn-mount.c
+++ b/src/nspawn/nspawn-mount.c
@@ -1309,7 +1309,9 @@ int pivot_root_parse(char **pivot_root_new, char **pivot_root_old, const char *s
if (!path_is_absolute(root_new))
return -EINVAL;
- if (root_old && !path_is_absolute(root_old))
+ if (!path_is_normalized(root_new))
+ return -EINVAL;
+ if (root_old && (!path_is_absolute(root_old) || !path_is_normalized(root_old)))
return -EINVAL;
free_and_replace(*pivot_root_new, root_new);

View File

@ -0,0 +1,124 @@
From 193648df27a08e5564c09f82115ee0c282c9fe85 Mon Sep 17 00:00:00 2001
From: Luca Boccassi <luca.boccassi@gmail.com>
Date: Fri, 6 Mar 2026 19:32:35 +0000
Subject: [PATCH] udev: check for invalid chars in various fields received from
the kernel
(cherry picked from commit 16325b35fa6ecb25f66534a562583ce3b96d52f3)
Resolves: RHEL-158354
---
src/udev/dmi_memory_id/dmi_memory_id.c | 3 ++-
src/udev/scsi_id/scsi_id.c | 5 +++--
src/udev/udev-builtin-net_id.c | 9 +++++++++
src/udev/v4l_id/v4l_id.c | 5 ++++-
4 files changed, 18 insertions(+), 4 deletions(-)
diff --git a/src/udev/dmi_memory_id/dmi_memory_id.c b/src/udev/dmi_memory_id/dmi_memory_id.c
index e62222a307..d8370bbe3f 100644
--- a/src/udev/dmi_memory_id/dmi_memory_id.c
+++ b/src/udev/dmi_memory_id/dmi_memory_id.c
@@ -51,6 +51,7 @@
#include "string-util.h"
#include "udev-util.h"
#include "unaligned.h"
+#include "utf8.h"
#define SUPPORTED_SMBIOS_VER 0x030300
@@ -185,7 +186,7 @@ static void dmi_memory_device_string(
str = strdupa_safe(dmi_string(h, s));
str = strstrip(str);
- if (!isempty(str))
+ if (!isempty(str) && utf8_is_valid(str) && !string_has_cc(str, /* ok= */ NULL))
printf("MEMORY_DEVICE_%u_%s=%s\n", slot_num, attr_suffix, str);
}
diff --git a/src/udev/scsi_id/scsi_id.c b/src/udev/scsi_id/scsi_id.c
index b63a46a730..650bf7824f 100644
--- a/src/udev/scsi_id/scsi_id.c
+++ b/src/udev/scsi_id/scsi_id.c
@@ -27,6 +27,7 @@
#include "strv.h"
#include "strxcpyx.h"
#include "udev-util.h"
+#include "utf8.h"
static const struct option options[] = {
{ "device", required_argument, NULL, 'd' },
@@ -450,8 +451,8 @@ static int scsi_id(char *maj_min_dev) {
}
if (dev_scsi.tgpt_group[0] != '\0')
printf("ID_TARGET_PORT=%s\n", dev_scsi.tgpt_group);
- if (dev_scsi.unit_serial_number[0] != '\0')
- printf("ID_SCSI_SERIAL=%s\n", dev_scsi.unit_serial_number);
+ if (dev_scsi.unit_serial_number[0] != '\0' && utf8_is_valid(dev_scsi.unit_serial_number) && !string_has_cc(dev_scsi.unit_serial_number, /* ok= */ NULL))
+ printf("ID_SCSI_SERIAL=%s\n", serial_str);
goto out;
}
diff --git a/src/udev/udev-builtin-net_id.c b/src/udev/udev-builtin-net_id.c
index 96e792bcde..0d3c62f4b5 100644
--- a/src/udev/udev-builtin-net_id.c
+++ b/src/udev/udev-builtin-net_id.c
@@ -40,6 +40,7 @@
#include "strv.h"
#include "strxcpyx.h"
#include "udev-builtin.h"
+#include "utf8.h"
#define ONBOARD_14BIT_INDEX_MAX ((1U << 14) - 1)
#define ONBOARD_16BIT_INDEX_MAX ((1U << 16) - 1)
@@ -236,6 +237,9 @@ static int get_port_specifier(sd_device *dev, bool fallback_to_dev_id, char **re
}
}
+ if (!utf8_is_valid(phys_port_name) || string_has_cc(phys_port_name, /* ok= */ NULL))
+ return log_device_debug_errno(dev, SYNTHETIC_ERRNO(EINVAL), "Invalid phys_port_name");
+
/* Otherwise, use phys_port_name as is. */
buf = strjoin("n", phys_port_name);
if (!buf)
@@ -340,6 +344,9 @@ static int names_pci_onboard_label(UdevEvent *event, sd_device *pci_dev, const c
if (r < 0)
return log_device_debug_errno(pci_dev, r, "Failed to get PCI onboard label: %m");
+ if (!utf8_is_valid(label) || string_has_cc(label, /* ok= */ NULL))
+ return log_device_debug_errno(dev, SYNTHETIC_ERRNO(EINVAL), "Invalid label");
+
char str[ALTIFNAMSIZ];
if (snprintf_ok(str, sizeof str, "%s%s",
naming_scheme_has(NAMING_LABEL_NOPREFIX) ? "" : prefix,
@@ -1257,6 +1264,8 @@ static int names_netdevsim(UdevEvent *event, const char *prefix) {
if (isempty(phys_port_name))
return log_device_debug_errno(dev, SYNTHETIC_ERRNO(EOPNOTSUPP),
"The 'phys_port_name' attribute is empty.");
+ if (!utf8_is_valid(phys_port_name) || string_has_cc(phys_port_name, /* ok= */ NULL))
+ return log_device_debug_errno(dev, SYNTHETIC_ERRNO(EINVAL), "Invalid phys_port_name");
char str[ALTIFNAMSIZ];
if (snprintf_ok(str, sizeof str, "%si%un%s", prefix, addr, phys_port_name))
diff --git a/src/udev/v4l_id/v4l_id.c b/src/udev/v4l_id/v4l_id.c
index 5c540659f3..8e29f8898e 100644
--- a/src/udev/v4l_id/v4l_id.c
+++ b/src/udev/v4l_id/v4l_id.c
@@ -19,6 +19,8 @@
#include "build.h"
#include "fd-util.h"
#include "main-func.h"
+#include "string-util.h"
+#include "utf8.h"
static const char *arg_device = NULL;
@@ -72,7 +74,8 @@ static int run(int argc, char *argv[]) {
int capabilities;
printf("ID_V4L_VERSION=2\n");
- printf("ID_V4L_PRODUCT=%s\n", v2cap.card);
+ if (utf8_is_valid((char *)v2cap.card) && !string_has_cc((char *)v2cap.card, /* ok= */ NULL))
+ printf("ID_V4L_PRODUCT=%s\n", v2cap.card);
printf("ID_V4L_CAPABILITIES=:");
if (v2cap.capabilities & V4L2_CAP_DEVICE_CAPS)

View File

@ -0,0 +1,45 @@
From 611f421f23f2e1a560eb76bb8d922c8a942a9da5 Mon Sep 17 00:00:00 2001
From: Luca Boccassi <luca.boccassi@gmail.com>
Date: Fri, 6 Mar 2026 19:42:16 +0000
Subject: [PATCH] udev: ensure there is space for trailing NUL before calling
sprintf
sprintf will write 5 characters, as it adds a trailing NUL byte.
Reported on yeswehack.com as:
YWH-PGM9780-62
Follow-up for 8cfcf9980a3
(cherry picked from commit 69e4ba69d689748d1d515c5a8d063073df3c5821)
Related: RHEL-158354
---
src/shared/device-nodes.c | 5 +++--
1 file changed, 3 insertions(+), 2 deletions(-)
diff --git a/src/shared/device-nodes.c b/src/shared/device-nodes.c
index d08c40fe2c..20206ee7b4 100644
--- a/src/shared/device-nodes.c
+++ b/src/shared/device-nodes.c
@@ -7,6 +7,7 @@
#include "device-nodes.h"
#include "path-util.h"
+#include "stdio-util.h"
#include "string-util.h"
#include "utf8.h"
@@ -39,10 +40,10 @@ int encode_devnode_name(const char *str, char *str_enc, size_t len) {
} else if (str[i] == '\\' || !allow_listed_char_for_devnode(str[i], NULL)) {
- if (len-j < 4)
+ if (len-j < 5)
return -EINVAL;
- sprintf(&str_enc[j], "\\x%02x", (unsigned char) str[i]);
+ assert_se(snprintf_ok(&str_enc[j], 5, "\\x%02x", (unsigned char) str[i]));
j += 4;
} else {

View File

@ -0,0 +1,32 @@
From 6a324492fc5710816ef376d2e012b1661f50b291 Mon Sep 17 00:00:00 2001
From: Luca Boccassi <luca.boccassi@gmail.com>
Date: Fri, 6 Mar 2026 20:25:05 +0000
Subject: [PATCH] udev: ensure tag parsing stays within bounds
This cannot actually happen, but add a safety check nonetheless.
Reported on yeswehack.com as:
YWH-PGM9780-43
Follow-up for d7867b31836173d1a943ecb1cab6484536126411
(cherry picked from commit 45a200cd751fae382f4145760cf84fd181db1319)
Related: RHEL-158354
---
src/udev/udev-builtin-path_id.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/udev/udev-builtin-path_id.c b/src/udev/udev-builtin-path_id.c
index d6ea471482..545757dc09 100644
--- a/src/udev/udev-builtin-path_id.c
+++ b/src/udev/udev-builtin-path_id.c
@@ -667,7 +667,7 @@ static void add_id_tag(UdevEvent *event, const char *path) {
size_t i = 0;
/* compose valid udev tag name */
- for (const char *p = path; *p; p++) {
+ for (const char *p = path; *p && i < sizeof(tag) - 1; p++) {
if (ascii_isdigit(*p) ||
ascii_isalpha(*p) ||
*p == '-') {

View File

@ -0,0 +1,32 @@
From 51ee6986c85c060fbbd644d94553edc6854ee5d6 Mon Sep 17 00:00:00 2001
From: Luca Boccassi <luca.boccassi@gmail.com>
Date: Fri, 13 Mar 2026 11:10:47 +0000
Subject: [PATCH] udev: fix review mixup
The previous version in the PR changed variable and sanitized it
in place. The second version switched to skip if CCs are in the
string instead, but didn't move back to the original variable.
Because it's an existing variable, no CI caught it.
Follow-up for 16325b35fa6ecb25f66534a562583ce3b96d52f3
(cherry picked from commit 54f880b02ecf7362e630ffc885d1466df6ee6820)
Resolves: RHEL-158354
---
src/udev/scsi_id/scsi_id.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/udev/scsi_id/scsi_id.c b/src/udev/scsi_id/scsi_id.c
index 650bf7824f..854f8ffa05 100644
--- a/src/udev/scsi_id/scsi_id.c
+++ b/src/udev/scsi_id/scsi_id.c
@@ -452,7 +452,7 @@ static int scsi_id(char *maj_min_dev) {
if (dev_scsi.tgpt_group[0] != '\0')
printf("ID_TARGET_PORT=%s\n", dev_scsi.tgpt_group);
if (dev_scsi.unit_serial_number[0] != '\0' && utf8_is_valid(dev_scsi.unit_serial_number) && !string_has_cc(dev_scsi.unit_serial_number, /* ok= */ NULL))
- printf("ID_SCSI_SERIAL=%s\n", serial_str);
+ printf("ID_SCSI_SERIAL=%s\n", dev_scsi.unit_serial_number);
goto out;
}

View File

@ -0,0 +1,52 @@
From 2ce69dc53f7c6076dcbdf60ef0211f0b8ea3f876 Mon Sep 17 00:00:00 2001
From: Luca Boccassi <luca.boccassi@gmail.com>
Date: Fri, 10 Apr 2026 19:04:04 +0100
Subject: [PATCH] udev/scsi-id: check for invalid chars in various fields
received from the kernel
Follow-up for 16325b35fa6ecb25f66534a562583ce3b96d52f3
(cherry picked from commit 5f700d148c44063c0f0dbb9fc136866339cd3fa7)
Related: RHEL-158354
---
src/udev/scsi_id/scsi_id.c | 12 ++++++++----
1 file changed, 8 insertions(+), 4 deletions(-)
diff --git a/src/udev/scsi_id/scsi_id.c b/src/udev/scsi_id/scsi_id.c
index 854f8ffa05..bc350fed47 100644
--- a/src/udev/scsi_id/scsi_id.c
+++ b/src/udev/scsi_id/scsi_id.c
@@ -398,6 +398,10 @@ static int set_inq_values(struct scsi_id_device *dev_scsi, const char *path) {
return 0;
}
+static bool scsi_string_is_valid(const char *s) {
+ return !isempty(s) && utf8_is_valid(s) && !string_has_cc(s, /* ok= */ NULL);
+}
+
/*
* scsi_id: try to get an id, if one is found, printf it to stdout.
* returns a value passed to exit() - 0 if printed an id, else 1.
@@ -441,17 +445,17 @@ static int scsi_id(char *maj_min_dev) {
udev_replace_chars(serial_str, NULL);
printf("ID_SERIAL_SHORT=%s\n", serial_str);
}
- if (dev_scsi.wwn[0] != '\0') {
+ if (scsi_string_is_valid(dev_scsi.wwn)) {
printf("ID_WWN=0x%s\n", dev_scsi.wwn);
- if (dev_scsi.wwn_vendor_extension[0] != '\0') {
+ if (scsi_string_is_valid(dev_scsi.wwn_vendor_extension)) {
printf("ID_WWN_VENDOR_EXTENSION=0x%s\n", dev_scsi.wwn_vendor_extension);
printf("ID_WWN_WITH_EXTENSION=0x%s%s\n", dev_scsi.wwn, dev_scsi.wwn_vendor_extension);
} else
printf("ID_WWN_WITH_EXTENSION=0x%s\n", dev_scsi.wwn);
}
- if (dev_scsi.tgpt_group[0] != '\0')
+ if (scsi_string_is_valid(dev_scsi.tgpt_group))
printf("ID_TARGET_PORT=%s\n", dev_scsi.tgpt_group);
- if (dev_scsi.unit_serial_number[0] != '\0' && utf8_is_valid(dev_scsi.unit_serial_number) && !string_has_cc(dev_scsi.unit_serial_number, /* ok= */ NULL))
+ if (scsi_string_is_valid(dev_scsi.unit_serial_number))
printf("ID_SCSI_SERIAL=%s\n", dev_scsi.unit_serial_number);
goto out;
}

View File

@ -0,0 +1,88 @@
From de3d4ab2348dc313a08fa3b667acf42a34d19bb0 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= <zbyszek@amutable.com>
Date: Wed, 11 Mar 2026 11:27:48 +0100
Subject: [PATCH] udev-builtin-net-id: print cescaped bad attributes
Follow-up for 16325b35fa6ecb25f66534a562583ce3b96d52f3. Let's
log those bad value to make it easier to figure out why things
are not working if we reject an attribute.
(cherry picked from commit 7c4047957ef58744ecfad6d277f7c45d430f6d70)
Related: RHEL-158354
---
src/udev/udev-builtin-net_id.c | 19 ++++++++++++-------
1 file changed, 12 insertions(+), 7 deletions(-)
diff --git a/src/udev/udev-builtin-net_id.c b/src/udev/udev-builtin-net_id.c
index 0d3c62f4b5..fd39a90c87 100644
--- a/src/udev/udev-builtin-net_id.c
+++ b/src/udev/udev-builtin-net_id.c
@@ -28,6 +28,7 @@
#include "device-private.h"
#include "device-util.h"
#include "dirent-util.h"
+#include "escape.h"
#include "ether-addr-util.h"
#include "fd-util.h"
#include "fileio.h"
@@ -45,6 +46,12 @@
#define ONBOARD_14BIT_INDEX_MAX ((1U << 14) - 1)
#define ONBOARD_16BIT_INDEX_MAX ((1U << 16) - 1)
+static int log_invalid_device_attr(sd_device *dev, const char *attr, const char *value) {
+ _cleanup_free_ char *escaped = cescape(value);
+ return log_device_debug_errno(dev, SYNTHETIC_ERRNO(EINVAL),
+ "Invalid %s value '%s'.", attr, strnull(escaped));
+}
+
/* skip intermediate virtio devices */
static sd_device *device_skip_virtio(sd_device *dev) {
/* there can only ever be one virtio bus per parent device, so we can
@@ -238,7 +245,7 @@ static int get_port_specifier(sd_device *dev, bool fallback_to_dev_id, char **re
}
if (!utf8_is_valid(phys_port_name) || string_has_cc(phys_port_name, /* ok= */ NULL))
- return log_device_debug_errno(dev, SYNTHETIC_ERRNO(EINVAL), "Invalid phys_port_name");
+ return log_invalid_device_attr(dev, "phys_port_name", phys_port_name);
/* Otherwise, use phys_port_name as is. */
buf = strjoin("n", phys_port_name);
@@ -345,7 +352,7 @@ static int names_pci_onboard_label(UdevEvent *event, sd_device *pci_dev, const c
return log_device_debug_errno(pci_dev, r, "Failed to get PCI onboard label: %m");
if (!utf8_is_valid(label) || string_has_cc(label, /* ok= */ NULL))
- return log_device_debug_errno(dev, SYNTHETIC_ERRNO(EINVAL), "Invalid label");
+ return log_invalid_device_attr(dev, "label", label);
char str[ALTIFNAMSIZ];
if (snprintf_ok(str, sizeof str, "%s%s",
@@ -751,8 +758,7 @@ static int names_vio(UdevEvent *event, const char *prefix) {
"VIO bus ID and slot ID have invalid length: %s", s);
if (!in_charset(s, HEXDIGITS))
- return log_device_debug_errno(dev, SYNTHETIC_ERRNO(EINVAL),
- "VIO bus ID and slot ID contain invalid characters: %s", s);
+ return log_invalid_device_attr(dev, "VIO bus ID and slot ID", s);
/* Parse only slot ID (the last 4 hexdigits). */
r = safe_atou_full(s + 4, 16, &slotid);
@@ -808,8 +814,7 @@ static int names_platform(UdevEvent *event, const char *prefix) {
return -EOPNOTSUPP;
if (!in_charset(vendor, validchars))
- return log_device_debug_errno(dev, SYNTHETIC_ERRNO(ENOENT),
- "Platform vendor contains invalid characters: %s", vendor);
+ return log_invalid_device_attr(dev, "platform vendor", vendor);
ascii_strlower(vendor);
@@ -1265,7 +1270,7 @@ static int names_netdevsim(UdevEvent *event, const char *prefix) {
return log_device_debug_errno(dev, SYNTHETIC_ERRNO(EOPNOTSUPP),
"The 'phys_port_name' attribute is empty.");
if (!utf8_is_valid(phys_port_name) || string_has_cc(phys_port_name, /* ok= */ NULL))
- return log_device_debug_errno(dev, SYNTHETIC_ERRNO(EINVAL), "Invalid phys_port_name");
+ return log_invalid_device_attr(dev, "phys_port_name", phys_port_name);
char str[ALTIFNAMSIZ];
if (snprintf_ok(str, sizeof str, "%si%un%s", prefix, addr, phys_port_name))

View File

@ -0,0 +1,34 @@
From e1498685abfebf42a64a5545be6c925d1adefa66 Mon Sep 17 00:00:00 2001
From: Michal Sekletar <msekleta@redhat.com>
Date: Fri, 9 Jan 2026 17:18:41 +0100
Subject: [PATCH] core: only activate transaction that contain useful jobs
If no real jobs were added to the transaction, do not activate it.
The JOB_NOP anchor does not perform any useful work and activating
such transaction only wastes resources.
Fixes #9751
(cherry picked from commit bcbf80c43d107ad233edc990a60bdc40f517085a)
Resolves: RHEL-143728
---
src/core/manager.c | 5 +++++
1 file changed, 5 insertions(+)
diff --git a/src/core/manager.c b/src/core/manager.c
index f7f901521b..22efab33df 100644
--- a/src/core/manager.c
+++ b/src/core/manager.c
@@ -2297,6 +2297,11 @@ int manager_propagate_reload(Manager *m, Unit *unit, JobMode mode, sd_bus_error
tr->anchor_job,
mode == JOB_IGNORE_DEPENDENCIES ? TRANSACTION_IGNORE_ORDER : 0);
+ /* Only activate the transaction if it contains jobs other than NOP anchor.
+ * Short-circuiting here avoids unnecessary processing, such as emitting D-Bus signals. */
+ if (hashmap_size(tr->jobs) <= 1)
+ return 0;
+
r = transaction_activate(tr, m, mode, NULL, e);
if (r < 0)
return r;

View File

@ -0,0 +1,27 @@
From 4a438fbccf6b533d4546e2e9b37919684a9a2d90 Mon Sep 17 00:00:00 2001
From: Yu Watanabe <watanabe.yu+github@gmail.com>
Date: Sun, 1 Dec 2024 14:46:40 +0900
Subject: [PATCH] journald: extend STDOUT_STREAMS_MAX to 64k
Closes #35390.
(cherry picked from commit c576ba7182f54f352c03f0768c9178b173fb8bcb)
Resolves: RHEL-168098
---
src/journal/journald-stream.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/journal/journald-stream.c b/src/journal/journald-stream.c
index b019eda0d2..f039a33ad2 100644
--- a/src/journal/journald-stream.c
+++ b/src/journal/journald-stream.c
@@ -40,7 +40,7 @@
#include "unit-name.h"
#include "user-util.h"
-#define STDOUT_STREAMS_MAX 4096
+#define STDOUT_STREAMS_MAX (64*1024)
/* During the "setup" protocol phase of the stream logic let's define a different maximum line length than
* during the actual operational phase. We want to allow users to specify very short line lengths after all,

View File

@ -0,0 +1,76 @@
From 4b773b91307715d016c84542037cbfbc16dbdcfa Mon Sep 17 00:00:00 2001
From: Mike Yuan <me@yhndnzj.com>
Date: Mon, 10 Feb 2025 19:03:08 +0100
Subject: [PATCH] string-util: introduce strprepend() helper
(cherry picked from commit b40694f5fc21317af617f37bb03304c84ca993c8)
Related: RHEL-143028
---
src/basic/string-util.c | 14 ++++++++++++++
src/basic/string-util.h | 2 ++
src/test/test-string-util.c | 13 +++++++++++++
3 files changed, 29 insertions(+)
diff --git a/src/basic/string-util.c b/src/basic/string-util.c
index 7122d5145e..6d490e7444 100644
--- a/src/basic/string-util.c
+++ b/src/basic/string-util.c
@@ -46,6 +46,20 @@ char* first_word(const char *s, const char *word) {
return (char*) nw;
}
+char* strprepend(char **x, const char *s) {
+ assert(x);
+
+ if (isempty(s) && *x)
+ return *x;
+
+ char *p = strjoin(strempty(s), *x);
+ if (!p)
+ return NULL;
+
+ free_and_replace(*x, p);
+ return *x;
+}
+
char* strnappend(const char *s, const char *suffix, size_t b) {
size_t a;
char *r;
diff --git a/src/basic/string-util.h b/src/basic/string-util.h
index a1592b6e6d..7ce26b30f9 100644
--- a/src/basic/string-util.h
+++ b/src/basic/string-util.h
@@ -106,6 +106,8 @@ static inline const char* empty_or_dash_to_null(const char *p) {
char* first_word(const char *s, const char *word) _pure_;
+char* strprepend(char **x, const char *s);
+
char* strnappend(const char *s, const char *suffix, size_t length);
char* strjoin_real(const char *x, ...) _sentinel_;
diff --git a/src/test/test-string-util.c b/src/test/test-string-util.c
index b692af6cc0..0474f3a162 100644
--- a/src/test/test-string-util.c
+++ b/src/test/test-string-util.c
@@ -1335,6 +1335,19 @@ TEST(strextendn) {
x = mfree(x);
}
+TEST(strprepend) {
+ _cleanup_free_ char *x = NULL;
+
+ ASSERT_STREQ(strprepend(&x, NULL), "");
+ x = mfree(x);
+
+ ASSERT_STREQ(strprepend(&x, ""), "");
+
+ ASSERT_STREQ(strprepend(&x, "xxx"), "xxx");
+ ASSERT_STREQ(strprepend(&x, "bar"), "barxxx");
+ ASSERT_STREQ(strprepend(&x, "foo"), "foobarxxx");
+}
+
TEST(strlevenshtein) {
assert_se(strlevenshtein(NULL, NULL) == 0);
assert_se(strlevenshtein("", "") == 0);

View File

@ -0,0 +1,139 @@
From 125c2b786edaed0c11ef923e10cf0448909abf63 Mon Sep 17 00:00:00 2001
From: Lennart Poettering <lennart@poettering.net>
Date: Fri, 10 Jan 2025 11:33:03 +0100
Subject: [PATCH] missing: add quotactl_fd() wrapper
(cherry picked from commit 7adafb0832a709f4fcf1210602224cb049d02857)
Related: RHEL-143028
---
meson.build | 1 +
src/basic/missing_syscall.h | 16 ++++++++
src/basic/missing_syscall_def.h | 68 +++++++++++++++++++++++++++++++++
src/basic/missing_syscalls.py | 1 +
4 files changed, 86 insertions(+)
diff --git a/meson.build b/meson.build
index fa39da2d38..8b9818a202 100644
--- a/meson.build
+++ b/meson.build
@@ -675,6 +675,7 @@ foreach ident : [
['fsmount', '''#include <sys/mount.h>'''],
['getdents64', '''#include <dirent.h>'''],
['pidfd_spawn', '''#include <spawn.h>'''],
+ ['quotactl_fd', '''#include <sys/quota.h>'''],
]
have = cc.has_function(ident[0], prefix : ident[1], args : '-D_GNU_SOURCE')
diff --git a/src/basic/missing_syscall.h b/src/basic/missing_syscall.h
index e2cd8b4e35..8ac4c5288a 100644
--- a/src/basic/missing_syscall.h
+++ b/src/basic/missing_syscall.h
@@ -695,3 +695,19 @@ int __clone2(int (*fn)(void *), void *stack_base, size_t stack_size, int flags,
* at build time) and just define it. Once the kernel drops ia64 support, we can drop this too. */
#define HAVE_CLONE 1
#endif
+
+/* ======================================================================= */
+
+#if !HAVE_QUOTACTL_FD
+
+static inline int missing_quotactl_fd(int fd, int cmd, int id, void *addr) {
+#if defined __NR_quotactl_fd
+ return syscall(__NR_quotactl_fd, fd, cmd, id, addr);
+#else
+ errno = ENOSYS;
+ return -1;
+#endif
+}
+
+# define quotactl_fd missing_quotactl_fd
+#endif
diff --git a/src/basic/missing_syscall_def.h b/src/basic/missing_syscall_def.h
index f679422a2e..cd22058cdd 100644
--- a/src/basic/missing_syscall_def.h
+++ b/src/basic/missing_syscall_def.h
@@ -1197,3 +1197,71 @@ assert_cc(__NR_statx == systemd_NR_statx);
# endif
# endif
#endif
+
+#ifndef __IGNORE_quotactl_fd
+# if defined(__aarch64__)
+# define systemd_NR_quotactl_fd 443
+# elif defined(__alpha__)
+# define systemd_NR_quotactl_fd 553
+# elif defined(__arc__) || defined(__tilegx__)
+# define systemd_NR_quotactl_fd 443
+# elif defined(__arm__)
+# define systemd_NR_quotactl_fd 443
+# elif defined(__i386__)
+# define systemd_NR_quotactl_fd 443
+# elif defined(__ia64__)
+# define systemd_NR_quotactl_fd 1467
+# elif defined(__loongarch_lp64)
+# define systemd_NR_quotactl_fd 443
+# elif defined(__m68k__)
+# define systemd_NR_quotactl_fd 443
+# elif defined(_MIPS_SIM)
+# if _MIPS_SIM == _MIPS_SIM_ABI32
+# define systemd_NR_quotactl_fd 4443
+# elif _MIPS_SIM == _MIPS_SIM_NABI32
+# define systemd_NR_quotactl_fd 6443
+# elif _MIPS_SIM == _MIPS_SIM_ABI64
+# define systemd_NR_quotactl_fd 5443
+# else
+# error "Unknown MIPS ABI"
+# endif
+# elif defined(__hppa__)
+# define systemd_NR_quotactl_fd 443
+# elif defined(__powerpc__)
+# define systemd_NR_quotactl_fd 443
+# elif defined(__riscv)
+# if __riscv_xlen == 32
+# define systemd_NR_quotactl_fd 443
+# elif __riscv_xlen == 64
+# define systemd_NR_quotactl_fd 443
+# else
+# error "Unknown RISC-V ABI"
+# endif
+# elif defined(__s390__)
+# define systemd_NR_quotactl_fd 443
+# elif defined(__sparc__)
+# define systemd_NR_quotactl_fd 443
+# elif defined(__x86_64__)
+# if defined(__ILP32__)
+# define systemd_NR_quotactl_fd (443 | /* __X32_SYSCALL_BIT */ 0x40000000)
+# else
+# define systemd_NR_quotactl_fd 443
+# endif
+# elif !defined(missing_arch_template)
+# warning "quotactl_fd() syscall number is unknown for your architecture"
+# endif
+
+/* may be an (invalid) negative number due to libseccomp, see PR 13319 */
+# if defined __NR_quotactl_fd && __NR_quotactl_fd >= 0
+# if defined systemd_NR_quotactl_fd
+assert_cc(__NR_quotactl_fd == systemd_NR_quotactl_fd);
+# endif
+# else
+# if defined __NR_quotactl_fd
+# undef __NR_quotactl_fd
+# endif
+# if defined systemd_NR_quotactl_fd && systemd_NR_quotactl_fd >= 0
+# define __NR_quotactl_fd systemd_NR_quotactl_fd
+# endif
+# endif
+#endif
diff --git a/src/basic/missing_syscalls.py b/src/basic/missing_syscalls.py
index 3749e89c4e..a15fed0358 100644
--- a/src/basic/missing_syscalls.py
+++ b/src/basic/missing_syscalls.py
@@ -20,6 +20,7 @@ SYSCALLS = [
'pidfd_open',
'pidfd_send_signal',
'pkey_mprotect',
+ 'quotactl_fd',
'renameat2',
'setns',
'statx',

View File

@ -0,0 +1,413 @@
From 58fc6c67b9723180f1c54e8b72e7f341e77a7591 Mon Sep 17 00:00:00 2001
From: Lennart Poettering <lennart@poettering.net>
Date: Mon, 31 Mar 2025 11:47:17 +0200
Subject: [PATCH] homed: always use quotactl_fd() if its available
Let's always prefer quotactl_fd() when it's available and use quotactl()
only as as a fallback on old kernels.
This way we can operate on the fds we typically already have open, or if
needed we can open a new one, and use for multiple fs operation.
In the long run we should really focus on operating exclusively by fd
instead of by path, by device nor or otherwise. This gets us a step
closer to that.
(cherry picked from commit 5daca30b0f28ffdcfdd1e6b9df9691b89d1b6b0f)
Related: RHEL-143028
---
src/home/homed-home.c | 16 +++++++---
src/home/homed-manager.c | 12 ++++---
src/home/homework-directory.c | 6 ++--
src/home/homework-fscrypt.c | 2 +-
src/home/homework-quota.c | 57 +++++++++++++++++++++++-----------
src/home/homework-quota.h | 6 ++--
src/shared/quota-util.c | 34 +++++++++-----------
src/shared/quota-util.h | 3 +-
units/systemd-homed.service.in | 2 +-
9 files changed, 80 insertions(+), 58 deletions(-)
diff --git a/src/home/homed-home.c b/src/home/homed-home.c
index 44e3274c42..84b2a4935e 100644
--- a/src/home/homed-home.c
+++ b/src/home/homed-home.c
@@ -29,7 +29,6 @@
#include "memfd-util.h"
#include "missing_magic.h"
#include "missing_mman.h"
-#include "missing_syscall.h"
#include "mkdir.h"
#include "path-util.h"
#include "process-util.h"
@@ -2414,6 +2413,7 @@ static int home_get_disk_status_directory(
uint64_t disk_size = UINT64_MAX, disk_usage = UINT64_MAX, disk_free = UINT64_MAX,
disk_ceiling = UINT64_MAX, disk_floor = UINT64_MAX;
mode_t access_mode = MODE_INVALID;
+ _cleanup_close_ int fd = -EBADF;
statfs_f_type_t fstype = 0;
struct statfs sfs;
struct dqblk req;
@@ -2435,7 +2435,13 @@ static int home_get_disk_status_directory(
if (!path)
goto finish;
- if (statfs(path, &sfs) < 0)
+ fd = open(path, O_CLOEXEC|O_RDONLY);
+ if (fd < 0) {
+ log_debug_errno(errno, "Failed to open '%s', ignoring: %m", path);
+ goto finish;
+ }
+
+ if (fstatfs(fd, &sfs) < 0)
log_debug_errno(errno, "Failed to statfs() %s, ignoring: %m", path);
else {
disk_free = sfs.f_bsize * sfs.f_bavail;
@@ -2449,13 +2455,13 @@ static int home_get_disk_status_directory(
if (IN_SET(h->record->storage, USER_CLASSIC, USER_DIRECTORY, USER_SUBVOLUME)) {
- r = btrfs_is_subvol(path);
+ r = btrfs_is_subvol_fd(fd);
if (r < 0)
log_debug_errno(r, "Failed to determine whether %s is a btrfs subvolume: %m", path);
else if (r > 0) {
BtrfsQuotaInfo qi;
- r = btrfs_subvol_get_subtree_quota(path, 0, &qi);
+ r = btrfs_subvol_get_subtree_quota_fd(fd, /* subvol_id= */ 0, &qi);
if (r < 0)
log_debug_errno(r, "Failed to query btrfs subtree quota, ignoring: %m");
else {
@@ -2488,7 +2494,7 @@ static int home_get_disk_status_directory(
}
if (IN_SET(h->record->storage, USER_CLASSIC, USER_DIRECTORY, USER_FSCRYPT)) {
- r = quotactl_path(QCMD_FIXED(Q_GETQUOTA, USRQUOTA), path, h->uid, &req);
+ r = quotactl_fd_with_fallback(fd, QCMD_FIXED(Q_GETQUOTA, USRQUOTA), h->uid, &req);
if (r < 0) {
if (ERRNO_IS_NOT_SUPPORTED(r)) {
log_debug_errno(r, "No UID quota support on %s.", path);
diff --git a/src/home/homed-manager.c b/src/home/homed-manager.c
index 6b9e4fcf11..8d0e8514bf 100644
--- a/src/home/homed-manager.c
+++ b/src/home/homed-manager.c
@@ -55,6 +55,7 @@
#include "user-record.h"
#include "user-util.h"
#include "varlink-io.systemd.UserDatabase.h"
+#include "varlink-io.systemd.service.h"
#include "varlink-util.h"
/* Where to look for private/public keys that are used to sign the user records. We are not using
@@ -513,14 +514,15 @@ static int search_quota(uid_t uid, const char *exclude_quota_path) {
struct dqblk req;
struct stat st;
- if (stat(where, &st) < 0) {
+ _cleanup_close_ int fd = open(where, O_RDONLY|O_CLOEXEC|O_DIRECTORY);
+ if (fd < 0) {
log_full_errno(errno == ENOENT ? LOG_DEBUG : LOG_ERR, errno,
- "Failed to stat %s, ignoring: %m", where);
+ "Failed to open '%s', ignoring: %m", where);
continue;
}
- if (major(st.st_dev) == 0) {
- log_debug("Directory %s is not on a real block device, not checking quota for UID use.", where);
+ if (fstat(fd, &st) < 0) {
+ log_error_errno(errno, "Failed to stat '%s', ignoring: %m", where);
continue;
}
@@ -539,7 +541,7 @@ static int search_quota(uid_t uid, const char *exclude_quota_path) {
previous_devno = st.st_dev;
- r = quotactl_devnum(QCMD_FIXED(Q_GETQUOTA, USRQUOTA), st.st_dev, uid, &req);
+ r = quotactl_fd_with_fallback(fd, QCMD_FIXED(Q_GETQUOTA, USRQUOTA), uid, &req);
if (r < 0) {
if (ERRNO_IS_NOT_SUPPORTED(r))
log_debug_errno(r, "No UID quota support on %s, ignoring.", where);
diff --git a/src/home/homework-directory.c b/src/home/homework-directory.c
index ff88367e43..37bdba30a3 100644
--- a/src/home/homework-directory.c
+++ b/src/home/homework-directory.c
@@ -153,7 +153,7 @@ int home_create_directory_or_subvolume(UserRecord *h, HomeSetup *setup, UserReco
/* Actually configure the quota. We also ignore errors here, but we do log
* about them loudly, to keep things discoverable even though we don't
* consider lacking quota support in kernel fatal. */
- (void) home_update_quota_btrfs(h, d);
+ (void) home_update_quota_btrfs(h, /* fd= */ -EBADF, d);
}
break;
@@ -169,7 +169,7 @@ int home_create_directory_or_subvolume(UserRecord *h, HomeSetup *setup, UserReco
if (mkdir(d, 0700) < 0)
return log_error_errno(errno, "Failed to create temporary home directory %s: %m", d);
- (void) home_update_quota_classic(h, d);
+ (void) home_update_quota_classic(h, /* fd= */ -EBADF, d);
break;
default:
@@ -285,7 +285,7 @@ int home_resize_directory(
if (r < 0)
return r;
- r = home_update_quota_auto(h, NULL);
+ r = home_update_quota_auto(h, setup->root_fd, /* path= */ NULL);
if (ERRNO_IS_NEG_NOT_SUPPORTED(r))
return -ESOCKTNOSUPPORT; /* make recognizable */
if (r < 0)
diff --git a/src/home/homework-fscrypt.c b/src/home/homework-fscrypt.c
index 3a9d4ea891..6e22fbd57c 100644
--- a/src/home/homework-fscrypt.c
+++ b/src/home/homework-fscrypt.c
@@ -629,7 +629,7 @@ int home_create_fscrypt(
nr++;
}
- (void) home_update_quota_classic(h, temporary);
+ (void) home_update_quota_classic(h, setup->root_fd, temporary);
r = home_shift_uid(setup->root_fd, HOME_RUNTIME_WORK_DIR, h->uid, h->uid, &mount_fd);
if (r > 0)
diff --git a/src/home/homework-quota.c b/src/home/homework-quota.c
index c9516829d8..363be87104 100644
--- a/src/home/homework-quota.c
+++ b/src/home/homework-quota.c
@@ -4,6 +4,7 @@
#include "blockdev-util.h"
#include "btrfs-util.h"
#include "errno-util.h"
+#include "fd-util.h"
#include "format-util.h"
#include "homework-quota.h"
#include "missing_magic.h"
@@ -11,23 +12,32 @@
#include "stat-util.h"
#include "user-util.h"
-int home_update_quota_btrfs(UserRecord *h, const char *path) {
+int home_update_quota_btrfs(UserRecord *h, int fd, const char *path) {
int r;
assert(h);
assert(path);
+ _cleanup_close_ int _fd = -EBADF;
+ if (fd < 0) {
+ _fd = open(path, O_CLOEXEC|O_RDONLY);
+ if (_fd < 0)
+ return log_error_errno(errno, "Failed to open '%s': %m", path);
+
+ fd = _fd;
+ }
+
if (h->disk_size == UINT64_MAX)
return 0;
/* If the user wants quota, enable it */
- r = btrfs_quota_enable(path, true);
+ r = btrfs_quota_enable_fd(fd, true);
if (r == -ENOTTY)
return log_error_errno(r, "No btrfs quota support on subvolume %s.", path);
if (r < 0)
return log_error_errno(r, "Failed to enable btrfs quota support on %s.", path);
- r = btrfs_qgroup_set_limit(path, 0, h->disk_size);
+ r = btrfs_qgroup_set_limit_fd(fd, 0, h->disk_size);
if (r < 0)
return log_error_errno(r, "Failed to set disk quota on subvolume %s: %m", path);
@@ -36,25 +46,27 @@ int home_update_quota_btrfs(UserRecord *h, const char *path) {
return 0;
}
-int home_update_quota_classic(UserRecord *h, const char *path) {
+int home_update_quota_classic(UserRecord *h, int fd, const char *path) {
struct dqblk req;
- dev_t devno;
int r;
assert(h);
assert(uid_is_valid(h->uid));
assert(path);
+ _cleanup_close_ int _fd = -EBADF;
+ if (fd < 0) {
+ _fd = open(path, O_CLOEXEC|O_RDONLY);
+ if (_fd < 0)
+ return log_error_errno(errno, "Failed to open '%s': %m", path);
+
+ fd = _fd;
+ }
+
if (h->disk_size == UINT64_MAX)
return 0;
- r = get_block_device(path, &devno);
- if (r < 0)
- return log_error_errno(r, "Failed to determine block device of %s: %m", path);
- if (devno == 0)
- return log_error_errno(SYNTHETIC_ERRNO(ENODEV), "File system %s not backed by a block device.", path);
-
- r = quotactl_devnum(QCMD_FIXED(Q_GETQUOTA, USRQUOTA), devno, h->uid, &req);
+ r = quotactl_fd_with_fallback(fd, QCMD_FIXED(Q_GETQUOTA, USRQUOTA), h->uid, &req);
if (r == -ESRCH)
zero(req);
else if (ERRNO_IS_NEG_NOT_SUPPORTED(r))
@@ -70,7 +82,7 @@ int home_update_quota_classic(UserRecord *h, const char *path) {
req.dqb_valid = QIF_BLIMITS;
req.dqb_bsoftlimit = req.dqb_bhardlimit = h->disk_size / QIF_DQBLKSIZE;
- r = quotactl_devnum(QCMD_FIXED(Q_SETQUOTA, USRQUOTA), devno, h->uid, &req);
+ r = quotactl_fd_with_fallback(fd, QCMD_FIXED(Q_SETQUOTA, USRQUOTA), h->uid, &req);
if (r == -ESRCH)
return log_error_errno(SYNTHETIC_ERRNO(ENOTTY), "UID quota not available on %s.", path);
if (r < 0)
@@ -81,7 +93,7 @@ int home_update_quota_classic(UserRecord *h, const char *path) {
return 0;
}
-int home_update_quota_auto(UserRecord *h, const char *path) {
+int home_update_quota_auto(UserRecord *h, int fd, const char *path) {
struct statfs sfs;
int r;
@@ -96,22 +108,31 @@ int home_update_quota_auto(UserRecord *h, const char *path) {
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Home record lacks image path.");
}
- if (statfs(path, &sfs) < 0)
+ _cleanup_close_ int _fd = -EBADF;
+ if (fd < 0) {
+ _fd = open(path, O_CLOEXEC|O_RDONLY);
+ if (_fd < 0)
+ return log_error_errno(errno, "Failed to open '%s': %m", path);
+
+ fd = _fd;
+ }
+
+ if (fstatfs(fd, &sfs) < 0)
return log_error_errno(errno, "Failed to statfs() file system: %m");
if (is_fs_type(&sfs, XFS_SUPER_MAGIC) ||
is_fs_type(&sfs, EXT4_SUPER_MAGIC))
- return home_update_quota_classic(h, path);
+ return home_update_quota_classic(h, fd, path);
if (is_fs_type(&sfs, BTRFS_SUPER_MAGIC)) {
- r = btrfs_is_subvol(path);
+ r = btrfs_is_subvol_fd(fd);
if (r < 0)
return log_error_errno(r, "Failed to test if %s is a subvolume: %m", path);
if (r == 0)
return log_error_errno(SYNTHETIC_ERRNO(ENOTTY), "Directory %s is not a subvolume, cannot apply quota.", path);
- return home_update_quota_btrfs(h, path);
+ return home_update_quota_btrfs(h, fd, path);
}
return log_error_errno(SYNTHETIC_ERRNO(ENOTTY), "Type of directory %s not known, cannot apply quota.", path);
diff --git a/src/home/homework-quota.h b/src/home/homework-quota.h
index a21c9ba8b1..a03510c75e 100644
--- a/src/home/homework-quota.h
+++ b/src/home/homework-quota.h
@@ -3,6 +3,6 @@
#include "user-record.h"
-int home_update_quota_btrfs(UserRecord *h, const char *path);
-int home_update_quota_classic(UserRecord *h, const char *path);
-int home_update_quota_auto(UserRecord *h, const char *path);
+int home_update_quota_btrfs(UserRecord *h, int fd, const char *path);
+int home_update_quota_classic(UserRecord *h, int fd, const char *path);
+int home_update_quota_auto(UserRecord *h, int fd, const char *path);
diff --git a/src/shared/quota-util.c b/src/shared/quota-util.c
index 4d014f847c..a698129adf 100644
--- a/src/shared/quota-util.c
+++ b/src/shared/quota-util.c
@@ -1,42 +1,36 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
+#include <stdint.h>
#include <sys/quota.h>
#include <sys/stat.h>
#include "alloc-util.h"
#include "blockdev-util.h"
#include "device-util.h"
+#include "errno-util.h"
+#include "missing_syscall.h"
#include "quota-util.h"
-int quotactl_devnum(int cmd, dev_t devnum, int id, void *addr) {
- _cleanup_free_ char *devnode = NULL;
+int quotactl_fd_with_fallback(int fd, int cmd, int id, void *addr) {
int r;
- /* Like quotactl() but takes a dev_t instead of a path to a device node, and fixes caddr_t → void*,
- * like we should, today */
+ /* Emulates quotactl_fd() on older kernels that lack it. (i.e. kernels < 5.14) */
- r = devname_from_devnum(S_IFBLK, devnum, &devnode);
- if (r < 0)
+ r = RET_NERRNO(quotactl_fd(fd, cmd, id, addr));
+ if (!ERRNO_IS_NEG_NOT_SUPPORTED(r))
return r;
- if (quotactl(cmd, devnode, id, addr) < 0)
- return -errno;
-
- return 0;
-}
-
-int quotactl_path(int cmd, const char *path, int id, void *addr) {
dev_t devno;
- int r;
-
- /* Like quotactl() but takes a path to some fs object, and changes the backing file system. I.e. the
- * argument shouldn't be a block device but a regular file system object */
-
- r = get_block_device(path, &devno);
+ r = get_block_device_fd(fd, &devno);
if (r < 0)
return r;
if (devno == 0) /* Doesn't have a block device */
return -ENODEV;
- return quotactl_devnum(cmd, devno, id, addr);
+ _cleanup_free_ char *devnode = NULL;
+ r = devname_from_devnum(S_IFBLK, devno, &devnode);
+ if (r < 0)
+ return r;
+
+ return RET_NERRNO(quotactl(cmd, devnode, id, addr));
}
diff --git a/src/shared/quota-util.h b/src/shared/quota-util.h
index 14a390ebe4..ad97eede01 100644
--- a/src/shared/quota-util.h
+++ b/src/shared/quota-util.h
@@ -15,5 +15,4 @@ static inline int QCMD_FIXED(uint32_t cmd, uint32_t type) {
return (int) QCMD(cmd, type);
}
-int quotactl_devnum(int cmd, dev_t devnum, int id, void *addr);
-int quotactl_path(int cmd, const char *path, int id, void *addr);
+int quotactl_fd_with_fallback(int fd, int cmd, int id, void *addr);
diff --git a/units/systemd-homed.service.in b/units/systemd-homed.service.in
index b54e5d30b2..5687fc0ed4 100644
--- a/units/systemd-homed.service.in
+++ b/units/systemd-homed.service.in
@@ -33,7 +33,7 @@ StateDirectory=systemd/home
CacheDirectory=systemd/home
SystemCallArchitectures=native
SystemCallErrorNumber=EPERM
-SystemCallFilter=@system-service @mount quotactl
+SystemCallFilter=@system-service @mount quotactl quotactl_fd
TimeoutStopSec=3min
{{SERVICE_WATCHDOG}}

View File

@ -0,0 +1,400 @@
From d40f49bc7e9454abdc0fc32936294cd046846c59 Mon Sep 17 00:00:00 2001
From: Lennart Poettering <lennart@poettering.net>
Date: Tue, 14 Jan 2025 16:49:52 +0100
Subject: [PATCH] pid1: add GracefulOptions= setting to .mount units
This new setting can be used to specify mount options that shall only be
added to the mount option string if the kernel supports them.
This shall be used for adding "usrquota" to tmp.mount without breaking compat,
but is generally be useful.
[dtardon: I put the test into TEST-87-AUX-UTILS-VM, because that's where
3f9539a97f3b4747ff22a530bac39dec24ac58af, which we have already
backported, would have moved it.]
(cherry picked from commit 09fbff57fcde47782a73f23b3d5cfdcd0e8f699b)
Related: RHEL-143028
---
man/org.freedesktop.systemd1.xml | 7 ++++
man/systemd.mount.xml | 16 +++++++++
src/core/dbus-mount.c | 25 +++++++++++++
src/core/load-fragment-gperf.gperf.in | 1 +
src/core/load-fragment.c | 45 ++++++++++++++++++++++++
src/core/load-fragment.h | 1 +
src/core/mount.c | 44 +++++++++++++++++++++++
src/core/mount.h | 2 ++
src/shared/bus-unit-util.c | 19 +++++-----
test/units/TEST-87-AUX-UTILS-VM.mount.sh | 7 ++++
10 files changed, 159 insertions(+), 8 deletions(-)
diff --git a/man/org.freedesktop.systemd1.xml b/man/org.freedesktop.systemd1.xml
index 0c7c52d563..71d2a11d29 100644
--- a/man/org.freedesktop.systemd1.xml
+++ b/man/org.freedesktop.systemd1.xml
@@ -7004,6 +7004,8 @@ node /org/freedesktop/systemd1/unit/home_2emount {
readonly s Result = '...';
readonly u UID = ...;
readonly u GID = ...;
+ @org.freedesktop.DBus.Property.EmitsChangedSignal("const")
+ readonly as GracefulOptions = ['...', ...];
@org.freedesktop.DBus.Property.EmitsChangedSignal("invalidates")
readonly a(sasbttttuii) ExecMount = [...];
@org.freedesktop.DBus.Property.EmitsChangedSignal("invalidates")
@@ -7604,6 +7606,8 @@ node /org/freedesktop/systemd1/unit/home_2emount {
<!--property GID is not documented!-->
+ <!--property GracefulOptions is not documented!-->
+
<!--property ExecUnmount is not documented!-->
<!--property ExecRemount is not documented!-->
@@ -8150,6 +8154,8 @@ node /org/freedesktop/systemd1/unit/home_2emount {
<variablelist class="dbus-property" generated="True" extra-ref="GID"/>
+ <variablelist class="dbus-property" generated="True" extra-ref="GracefulOptions"/>
+
<variablelist class="dbus-property" generated="True" extra-ref="ExecMount"/>
<variablelist class="dbus-property" generated="True" extra-ref="ExecUnmount"/>
@@ -12380,6 +12386,7 @@ $ gdbus introspect --system --dest org.freedesktop.systemd1 \
<varname>ManagedOOMMemoryPressureDurationUSec</varname>,
<varname>ProtectControlGroupsEx</varname>, and
<varname>PrivatePIDs</varname> were added in version 257.</para>
+ <para><varname>GracefulOptions</varname> were added in version 258.</para>
</refsect2>
<refsect2>
<title>Swap Unit Objects</title>
diff --git a/man/systemd.mount.xml b/man/systemd.mount.xml
index 20e724d540..9b394f6079 100644
--- a/man/systemd.mount.xml
+++ b/man/systemd.mount.xml
@@ -650,6 +650,22 @@
<citerefentry><refentrytitle>systemd-system.conf</refentrytitle><manvolnum>5</manvolnum></citerefentry>.
</para></listitem>
</varlistentry>
+
+ <varlistentry>
+ <term><varname>GracefulOptions=</varname></term>
+
+ <listitem><para>Additional mount options that shall be appended to <varname>Options=</varname> if
+ supported by the kernel. This may be used to configure mount options that are optional and only
+ enabled on kernels that support them. Note that this is supported only for native kernel mount
+ options (i.e. explicitly not for mount options implemented in userspace, such as those processed by
+ <command>/usr/bin/mount</command> itself, by FUSE or by mount helpers such as
+ <command>mount.nfs</command>).</para>
+
+ <para>May be specified multiple times. If specified multiple times, all listed, supported mount
+ options are combined. If an empty string is assigned, the list is reset.</para>
+
+ <xi:include href="version-info.xml" xpointer="v258"/></listitem>
+ </varlistentry>
</variablelist>
<xi:include href="systemd.service.xml" xpointer="shared-unit-options" />
diff --git a/src/core/dbus-mount.c b/src/core/dbus-mount.c
index d59aa06a11..855300d025 100644
--- a/src/core/dbus-mount.c
+++ b/src/core/dbus-mount.c
@@ -75,6 +75,7 @@ const sd_bus_vtable bus_mount_vtable[] = {
SD_BUS_PROPERTY("Result", "s", property_get_result, offsetof(Mount, result), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
SD_BUS_PROPERTY("UID", "u", bus_property_get_uid, offsetof(Unit, ref_uid), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
SD_BUS_PROPERTY("GID", "u", bus_property_get_gid, offsetof(Unit, ref_gid), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
+ SD_BUS_PROPERTY("GracefulOptions", "as", NULL, offsetof(Mount, graceful_options), SD_BUS_VTABLE_PROPERTY_CONST),
BUS_EXEC_COMMAND_VTABLE("ExecMount", offsetof(Mount, exec_command[MOUNT_EXEC_MOUNT]), SD_BUS_VTABLE_PROPERTY_EMITS_INVALIDATION),
BUS_EXEC_COMMAND_VTABLE("ExecUnmount", offsetof(Mount, exec_command[MOUNT_EXEC_UNMOUNT]), SD_BUS_VTABLE_PROPERTY_EMITS_INVALIDATION),
BUS_EXEC_COMMAND_VTABLE("ExecRemount", offsetof(Mount, exec_command[MOUNT_EXEC_REMOUNT]), SD_BUS_VTABLE_PROPERTY_EMITS_INVALIDATION),
@@ -150,6 +151,30 @@ static int bus_mount_set_transient_property(
if (streq(name, "ReadWriteOnly"))
return bus_set_transient_bool(u, name, &m->read_write_only, message, flags, error);
+ if (streq(name, "GracefulOptions")) {
+ _cleanup_strv_free_ char **add = NULL;
+ r = sd_bus_message_read_strv(message, &add);
+ if (r < 0)
+ return r;
+
+ if (!UNIT_WRITE_FLAGS_NOOP(flags)) {
+
+ if (strv_isempty(add)) {
+ m->graceful_options = strv_free(m->graceful_options);
+ unit_write_settingf(u, flags, name, "GracefulOptions=");
+ } else {
+ r = strv_extend_strv(&m->graceful_options, add, /* filter_duplicates= */ false);
+ if (r < 0)
+ return r;
+
+ STRV_FOREACH(a, add)
+ unit_write_settingf(u, flags|UNIT_ESCAPE_SPECIFIERS, name, "GracefulOptions=%s", *a);
+ }
+ }
+
+ return 1;
+ }
+
return 0;
}
diff --git a/src/core/load-fragment-gperf.gperf.in b/src/core/load-fragment-gperf.gperf.in
index d7564b3767..5b67d7b554 100644
--- a/src/core/load-fragment-gperf.gperf.in
+++ b/src/core/load-fragment-gperf.gperf.in
@@ -546,6 +546,7 @@ Mount.SloppyOptions, config_parse_bool,
Mount.LazyUnmount, config_parse_bool, 0, offsetof(Mount, lazy_unmount)
Mount.ForceUnmount, config_parse_bool, 0, offsetof(Mount, force_unmount)
Mount.ReadWriteOnly, config_parse_bool, 0, offsetof(Mount, read_write_only)
+Mount.GracefulOptions, config_parse_mount_graceful_options, 0, offsetof(Mount, graceful_options)
{{ EXEC_CONTEXT_CONFIG_ITEMS('Mount') }}
{{ CGROUP_CONTEXT_CONFIG_ITEMS('Mount') }}
{{ KILL_CONTEXT_CONFIG_ITEMS('Mount') }}
diff --git a/src/core/load-fragment.c b/src/core/load-fragment.c
index 90f054ec9e..9007846e64 100644
--- a/src/core/load-fragment.c
+++ b/src/core/load-fragment.c
@@ -6121,6 +6121,51 @@ int config_parse_mount_node(
return config_parse_string(unit, filename, line, section, section_line, lvalue, ltype, path, data, userdata);
}
+int config_parse_mount_graceful_options(
+ const char *unit,
+ const char *filename,
+ unsigned line,
+ const char *section,
+ unsigned section_line,
+ const char *lvalue,
+ int ltype,
+ const char *rvalue,
+ void *data,
+ void *userdata) {
+
+ const Unit *u = ASSERT_PTR(userdata);
+ char ***sv = ASSERT_PTR(data);
+ int r;
+
+ assert(filename);
+ assert(lvalue);
+ assert(rvalue);
+
+ if (isempty(rvalue)) {
+ *sv = strv_free(*sv);
+ return 1;
+ }
+
+ _cleanup_free_ char *resolved = NULL;
+ r = unit_full_printf(u, rvalue, &resolved);
+ if (r < 0) {
+ log_syntax(unit, LOG_WARNING, filename, line, r, "Failed to resolve unit specifiers in '%s', ignoring: %m", rvalue);
+ return 0;
+ }
+
+ _cleanup_strv_free_ char **strv = NULL;
+
+ r = strv_split_full(&strv, resolved, ",", EXTRACT_RETAIN_ESCAPE|EXTRACT_UNESCAPE_SEPARATORS);
+ if (r < 0)
+ return log_syntax_parse_error(unit, filename, line, r, lvalue, rvalue);
+
+ r = strv_extend_strv_consume(sv, TAKE_PTR(strv), /* filter_duplicates = */ false);
+ if (r < 0)
+ return log_oom();
+
+ return 1;
+}
+
static int merge_by_names(Unit *u, Set *names, const char *id) {
char *k;
int r;
diff --git a/src/core/load-fragment.h b/src/core/load-fragment.h
index 8ac962a94b..673088a42d 100644
--- a/src/core/load-fragment.h
+++ b/src/core/load-fragment.h
@@ -164,6 +164,7 @@ CONFIG_PARSER_PROTOTYPE(config_parse_open_file);
CONFIG_PARSER_PROTOTYPE(config_parse_memory_pressure_watch);
CONFIG_PARSER_PROTOTYPE(config_parse_cgroup_nft_set);
CONFIG_PARSER_PROTOTYPE(config_parse_mount_node);
+CONFIG_PARSER_PROTOTYPE(config_parse_mount_graceful_options);
/* gperf prototypes */
const struct ConfigPerfItem* load_fragment_gperf_lookup(const char *key, GPERF_LEN_TYPE length);
diff --git a/src/core/mount.c b/src/core/mount.c
index e5d02965be..136c3f1453 100644
--- a/src/core/mount.c
+++ b/src/core/mount.c
@@ -231,6 +231,8 @@ static void mount_done(Unit *u) {
mount_unwatch_control_pid(m);
m->timer_event_source = sd_event_source_disable_unref(m->timer_event_source);
+
+ m->graceful_options = strv_free(m->graceful_options);
}
static int update_parameters_proc_self_mountinfo(
@@ -1061,6 +1063,44 @@ fail:
mount_enter_dead_or_mounted(m, MOUNT_FAILURE_RESOURCES, /* flush_result = */ false);
}
+static int mount_append_graceful_options(Mount *m, const MountParameters *p, char **opts) {
+ int r;
+
+ assert(m);
+ assert(p);
+ assert(opts);
+
+ if (strv_isempty(m->graceful_options))
+ return 0;
+
+ if (!p->fstype) {
+ log_unit_warning(UNIT(m), "GracefulOptions= used but file system type not known, suppressing all graceful options.");
+ return 0;
+ }
+
+ STRV_FOREACH(o, m->graceful_options) {
+ _cleanup_free_ char *k = NULL, *v = NULL;
+
+ r = split_pair(*o, "=", &k, &v);
+ if (r < 0 && r != -EINVAL) /* EINVAL → not a key/value pair */
+ return r;
+
+ r = mount_option_supported(p->fstype, k ?: *o, v);
+ if (r < 0)
+ log_unit_warning_errno(UNIT(m), r, "GracefulOptions=%s specified, but cannot determine availability, suppressing.", *o);
+ else if (r == 0)
+ log_unit_info(UNIT(m), "GracefulOptions=%s specified, but option is not available, suppressing.", *o);
+ else {
+ log_unit_debug(UNIT(m), "GracefulOptions=%s specified and supported, appending to mount option string.", *o);
+
+ if (!strextend_with_separator(opts, ",", *o))
+ return -ENOMEM;
+ }
+ }
+
+ return 0;
+}
+
static int mount_set_mount_command(Mount *m, ExecCommand *c, const MountParameters *p) {
int r;
@@ -1095,6 +1135,10 @@ static int mount_set_mount_command(Mount *m, ExecCommand *c, const MountParamete
if (r < 0)
return r;
+ r = mount_append_graceful_options(m, p, &opts);
+ if (r < 0)
+ return r;
+
if (!isempty(opts)) {
r = exec_command_append(c, "-o", opts, NULL);
if (r < 0)
diff --git a/src/core/mount.h b/src/core/mount.h
index 9a8b6bb4c0..28cc7785d8 100644
--- a/src/core/mount.h
+++ b/src/core/mount.h
@@ -88,6 +88,8 @@ struct Mount {
sd_event_source *timer_event_source;
unsigned n_retry_umount;
+
+ char **graceful_options;
};
extern const UnitVTable mount_vtable;
diff --git a/src/shared/bus-unit-util.c b/src/shared/bus-unit-util.c
index 06bfb90c8f..8667d10077 100644
--- a/src/shared/bus-unit-util.c
+++ b/src/shared/bus-unit-util.c
@@ -147,7 +147,7 @@ static int bus_append_string(sd_bus_message *m, const char *field, const char *e
return 1;
}
-static int bus_append_strv(sd_bus_message *m, const char *field, const char *eq, ExtractFlags flags) {
+static int bus_append_strv(sd_bus_message *m, const char *field, const char *eq, const char *separator, ExtractFlags flags) {
const char *p;
int r;
@@ -170,7 +170,7 @@ static int bus_append_strv(sd_bus_message *m, const char *field, const char *eq,
for (p = eq;;) {
_cleanup_free_ char *word = NULL;
- r = extract_first_word(&p, &word, NULL, flags);
+ r = extract_first_word(&p, &word, separator, flags);
if (r == 0)
break;
if (r == -ENOMEM)
@@ -607,12 +607,12 @@ static int bus_append_cgroup_property(sd_bus_message *m, const char *field, cons
return bus_append_cg_blkio_weight_parse(m, field, eq);
if (streq(field, "DisableControllers"))
- return bus_append_strv(m, "DisableControllers", eq, EXTRACT_UNQUOTE);
+ return bus_append_strv(m, "DisableControllers", eq, /* separator= */ NULL, EXTRACT_UNQUOTE);
if (streq(field, "Delegate")) {
r = parse_boolean(eq);
if (r < 0)
- return bus_append_strv(m, "DelegateControllers", eq, EXTRACT_UNQUOTE);
+ return bus_append_strv(m, "DelegateControllers", eq, /* separator= */ NULL, EXTRACT_UNQUOTE);
r = sd_bus_message_append(m, "(sv)", "Delegate", "b", r);
if (r < 0)
@@ -1110,7 +1110,7 @@ static int bus_append_execute_property(sd_bus_message *m, const char *field, con
"ConfigurationDirectory",
"SupplementaryGroups",
"SystemCallArchitectures"))
- return bus_append_strv(m, field, eq, EXTRACT_UNQUOTE);
+ return bus_append_strv(m, field, eq, /* separator= */ NULL, EXTRACT_UNQUOTE);
if (STR_IN_SET(field, "SyslogLevel",
"LogLevelMax"))
@@ -1169,7 +1169,7 @@ static int bus_append_execute_property(sd_bus_message *m, const char *field, con
if (STR_IN_SET(field, "Environment",
"UnsetEnvironment",
"PassEnvironment"))
- return bus_append_strv(m, field, eq, EXTRACT_UNQUOTE|EXTRACT_CUNESCAPE);
+ return bus_append_strv(m, field, eq, /* separator= */ NULL, EXTRACT_UNQUOTE|EXTRACT_CUNESCAPE);
if (streq(field, "EnvironmentFile")) {
if (isempty(eq))
@@ -2309,6 +2309,9 @@ static int bus_append_mount_property(sd_bus_message *m, const char *field, const
"ReadwriteOnly"))
return bus_append_parse_boolean(m, field, eq);
+ if (streq(field, "GracefulOptions"))
+ return bus_append_strv(m, field, eq, /* separator= */ ",", 0);
+
return 0;
}
@@ -2591,7 +2594,7 @@ static int bus_append_socket_property(sd_bus_message *m, const char *field, cons
return bus_append_string(m, field, eq);
if (streq(field, "Symlinks"))
- return bus_append_strv(m, field, eq, EXTRACT_UNQUOTE);
+ return bus_append_strv(m, field, eq, /* separator= */ NULL, EXTRACT_UNQUOTE);
if (streq(field, "SocketProtocol"))
return bus_append_parse_ip_protocol(m, field, eq);
@@ -2725,7 +2728,7 @@ static int bus_append_unit_property(sd_bus_message *m, const char *field, const
"RequiresMountsFor",
"WantsMountsFor",
"Markers"))
- return bus_append_strv(m, field, eq, EXTRACT_UNQUOTE);
+ return bus_append_strv(m, field, eq, /* separator= */ NULL, EXTRACT_UNQUOTE);
t = condition_type_from_string(field);
if (t >= 0)
diff --git a/test/units/TEST-87-AUX-UTILS-VM.mount.sh b/test/units/TEST-87-AUX-UTILS-VM.mount.sh
index 3075d0fe2e..c1bfbdc1a2 100755
--- a/test/units/TEST-87-AUX-UTILS-VM.mount.sh
+++ b/test/units/TEST-87-AUX-UTILS-VM.mount.sh
@@ -180,3 +180,10 @@ systemctl status "$WORK_DIR/mnt"
touch "$WORK_DIR/mnt/hello"
[[ "$(stat -c "%U:%G" "$WORK_DIR/mnt/hello")" == "testuser:testuser" ]]
systemd-umount LABEL=owner-vfat
+
+# Mkae sure that graceful mount options work
+GRACEFULTEST="/tmp/graceful/$RANDOM"
+systemd-mount --tmpfs -p GracefulOptions=idefinitelydontexist,nr_inodes=4711,idonexisteither "$GRACEFULTEST"
+findmnt -n -o options "$GRACEFULTEST"
+findmnt -n -o options "$GRACEFULTEST" | grep -q nr_inodes=4711
+umount "$GRACEFULTEST"

View File

@ -0,0 +1,75 @@
From 75f712d4fb8d5b05f28eda98e9ae44512ba6d7f8 Mon Sep 17 00:00:00 2001
From: Lennart Poettering <lennart@poettering.net>
Date: Tue, 14 Jan 2025 16:51:49 +0100
Subject: [PATCH] pid1: enable usrquota support on /dev/shm
(cherry picked from commit 8f5131fb9e7979022521d685e69b6419f0884677)
Related: RHEL-143028
---
src/shared/mount-setup.c | 33 +++++++++++++++++++++++++--------
1 file changed, 25 insertions(+), 8 deletions(-)
diff --git a/src/shared/mount-setup.c b/src/shared/mount-setup.c
index 73be3b5dce..db8c2b61d2 100644
--- a/src/shared/mount-setup.c
+++ b/src/shared/mount-setup.c
@@ -34,11 +34,12 @@
#include "virt.h"
typedef enum MountMode {
- MNT_NONE = 0,
- MNT_FATAL = 1 << 0,
- MNT_IN_CONTAINER = 1 << 1,
- MNT_CHECK_WRITABLE = 1 << 2,
- MNT_FOLLOW_SYMLINK = 1 << 3,
+ MNT_NONE = 0,
+ MNT_FATAL = 1 << 0,
+ MNT_IN_CONTAINER = 1 << 1,
+ MNT_CHECK_WRITABLE = 1 << 2,
+ MNT_FOLLOW_SYMLINK = 1 << 3,
+ MNT_USRQUOTA_GRACEFUL = 1 << 4,
} MountMode;
typedef struct MountPoint {
@@ -92,7 +93,7 @@ static const MountPoint mount_table[] = {
mac_smack_use, MNT_FATAL },
#endif
{ "tmpfs", "/dev/shm", "tmpfs", "mode=01777", MS_NOSUID|MS_NODEV|MS_STRICTATIME,
- NULL, MNT_FATAL|MNT_IN_CONTAINER },
+ NULL, MNT_FATAL|MNT_IN_CONTAINER|MNT_USRQUOTA_GRACEFUL },
{ "devpts", "/dev/pts", "devpts", "mode=" STRINGIFY(TTY_MODE) ",gid=" STRINGIFY(TTY_GID), MS_NOSUID|MS_NOEXEC,
NULL, MNT_IN_CONTAINER },
#if ENABLE_SMACK
@@ -189,13 +190,29 @@ static int mount_one(const MountPoint *p, bool relabel) {
else
(void) mkdir_p(p->where, 0755);
+ _cleanup_free_ char *extend_options = NULL;
+ const char *o = p->options;
+ if (FLAGS_SET(p->mode, MNT_USRQUOTA_GRACEFUL)) {
+ r = mount_option_supported(p->type, "usrquota", /* value= */ NULL);
+ if (r < 0)
+ log_warning_errno(r, "Unable to determine whether %s supports 'usrquota' mount option, assuming not: %m", p->type);
+ else if (r == 0)
+ log_info("Not enabling 'usrquota' on '%s' as kernel lacks support for it.", p->where);
+ else {
+ if (!strextend_with_separator(&extend_options, ",", p->options ?: POINTER_MAX, "usrquota"))
+ return log_oom();
+
+ o = extend_options;
+ }
+ }
+
log_debug("Mounting %s to %s of type %s with options %s.",
p->what,
p->where,
p->type,
- strna(p->options));
+ strna(o));
- r = mount_verbose_full(priority, p->what, p->where, p->type, p->flags, p->options, FLAGS_SET(p->mode, MNT_FOLLOW_SYMLINK));
+ r = mount_verbose_full(priority, p->what, p->where, p->type, p->flags, o, FLAGS_SET(p->mode, MNT_FOLLOW_SYMLINK));
if (r < 0)
return FLAGS_SET(p->mode, MNT_FATAL) ? r : 0;

View File

@ -0,0 +1,24 @@
From bc192261e4801ad27a8610fea4e10010d705bfc0 Mon Sep 17 00:00:00 2001
From: Lennart Poettering <lennart@poettering.net>
Date: Tue, 14 Jan 2025 16:52:04 +0100
Subject: [PATCH] units: enable usrquota support on /tmp/
(cherry picked from commit c9f805674ace3e2d611596b871c2d12190457d87)
Related: RHEL-143028
---
units/tmp.mount | 1 +
1 file changed, 1 insertion(+)
diff --git a/units/tmp.mount b/units/tmp.mount
index d7beaa8d14..d347af56d3 100644
--- a/units/tmp.mount
+++ b/units/tmp.mount
@@ -23,6 +23,7 @@ What=tmpfs
Where=/tmp
Type=tmpfs
Options=mode=1777,strictatime,nosuid,nodev,size=50%%,nr_inodes=1m
+GracefulOptions=usrquota
# Make 'systemctl enable tmp.mount' work:
[Install]

View File

@ -0,0 +1,71 @@
From b9cfb8c02ec36304e0a3ba730363a6dd747dd26a Mon Sep 17 00:00:00 2001
From: Lennart Poettering <lennart@poettering.net>
Date: Tue, 14 Jan 2025 16:51:27 +0100
Subject: [PATCH] nspawn: enable usrquota support on /tmp/ and /dev/shm/
(cherry picked from commit 611ae598889471830b2f1d7251c271b79884b1c4)
Related: RHEL-143028
---
src/nspawn/nspawn-mount.c | 21 +++++++++++++++++++--
src/nspawn/nspawn-mount.h | 1 +
2 files changed, 20 insertions(+), 2 deletions(-)
diff --git a/src/nspawn/nspawn-mount.c b/src/nspawn/nspawn-mount.c
index c233cdf600..6bd506f960 100644
--- a/src/nspawn/nspawn-mount.c
+++ b/src/nspawn/nspawn-mount.c
@@ -592,7 +592,7 @@ int mount_all(const char *dest,
/* Then we list outer child mounts (i.e. mounts applied *before* entering user namespacing when we are privileged) */
{ "tmpfs", "/tmp", "tmpfs", "mode=01777" NESTED_TMPFS_LIMITS, MS_NOSUID|MS_NODEV|MS_STRICTATIME,
- MOUNT_FATAL|MOUNT_APPLY_TMPFS_TMP|MOUNT_MKDIR },
+ MOUNT_FATAL|MOUNT_APPLY_TMPFS_TMP|MOUNT_MKDIR|MOUNT_USRQUOTA_GRACEFUL },
{ "tmpfs", "/sys", "tmpfs", "mode=0555" TMPFS_LIMITS_SYS, MS_NOSUID|MS_NOEXEC|MS_NODEV,
MOUNT_FATAL|MOUNT_APPLY_APIVFS_NETNS|MOUNT_MKDIR|MOUNT_PRIVILEGED },
{ "sysfs", "/sys", "sysfs", NULL, SYS_DEFAULT_MOUNT_FLAGS,
@@ -602,7 +602,7 @@ int mount_all(const char *dest,
{ "tmpfs", "/dev", "tmpfs", "mode=0755" TMPFS_LIMITS_PRIVATE_DEV, MS_NOSUID|MS_STRICTATIME,
MOUNT_FATAL|MOUNT_MKDIR },
{ "tmpfs", "/dev/shm", "tmpfs", "mode=01777" NESTED_TMPFS_LIMITS, MS_NOSUID|MS_NODEV|MS_STRICTATIME,
- MOUNT_FATAL|MOUNT_MKDIR },
+ MOUNT_FATAL|MOUNT_MKDIR|MOUNT_USRQUOTA_GRACEFUL },
{ "tmpfs", "/run", "tmpfs", "mode=0755" TMPFS_LIMITS_RUN, MS_NOSUID|MS_NODEV|MS_STRICTATIME,
MOUNT_FATAL|MOUNT_MKDIR },
{ "/run/host", "/run/host", NULL, NULL, MS_BIND,
@@ -710,6 +710,23 @@ int mount_all(const char *dest,
o = options;
}
+ if (FLAGS_SET(m->mount_settings, MOUNT_USRQUOTA_GRACEFUL)) {
+ r = mount_option_supported(m->type, /* key= */ "usrquota", /* value= */ NULL);
+ if (r < 0)
+ log_warning_errno(r, "Failed to determine if '%s' supports 'usrquota', assuming it doesn't: %m", m->type);
+ else if (r == 0)
+ log_info("Kernel doesn't support 'usrquota' on '%s', not including in mount options for '%s'.", m->type, m->where);
+ else {
+ _cleanup_free_ char *joined = NULL;
+
+ if (!strextend_with_separator(&joined, ",", o ?: POINTER_MAX, "usrquota"))
+ return log_oom();
+
+ free_and_replace(options, joined);
+ o = options;
+ }
+ }
+
if (FLAGS_SET(m->mount_settings, MOUNT_PREFIX_ROOT)) {
/* Optionally prefix the mount source with the root dir. This is useful in bind
* mounts to be created within the container image before we transition into it. Note
diff --git a/src/nspawn/nspawn-mount.h b/src/nspawn/nspawn-mount.h
index 5f66bc7328..529fa16658 100644
--- a/src/nspawn/nspawn-mount.h
+++ b/src/nspawn/nspawn-mount.h
@@ -21,6 +21,7 @@ typedef enum MountSettingsMask {
MOUNT_PREFIX_ROOT = 1 << 10,/* if set, prefix the source path with the container's root directory */
MOUNT_FOLLOW_SYMLINKS = 1 << 11,/* if set, we'll follow symlinks for the mount target */
MOUNT_PRIVILEGED = 1 << 12,/* if set, we'll only mount this in the outer child if we are running in privileged mode */
+ MOUNT_USRQUOTA_GRACEFUL = 1 << 13,/* if set, append "usrquota" to mount options if kernel tmpfs supports that */
} MountSettingsMask;
typedef enum CustomMountType {

View File

@ -0,0 +1,31 @@
From 4a4f0b5dcb42533971116fd8f467f9866f50d58c Mon Sep 17 00:00:00 2001
From: Luca Boccassi <luca.boccassi@gmail.com>
Date: Mon, 20 Jan 2025 19:05:11 +0000
Subject: [PATCH] nspawn: downgrade log message about usrquota to debug
This is shown every time nspawn is started, which is annoying
and there's nothing a user can do about it, since it depends on
an extremely new kernel. Downgrade to debug.
Follow-up for 611ae598889471830b2f1d7251c271b79884b1c4
(cherry picked from commit 8fa7863a93047d310ec6600f939590f07854b497)
Related: RHEL-143028
---
src/nspawn/nspawn-mount.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/nspawn/nspawn-mount.c b/src/nspawn/nspawn-mount.c
index 6bd506f960..cd5a634ec0 100644
--- a/src/nspawn/nspawn-mount.c
+++ b/src/nspawn/nspawn-mount.c
@@ -715,7 +715,7 @@ int mount_all(const char *dest,
if (r < 0)
log_warning_errno(r, "Failed to determine if '%s' supports 'usrquota', assuming it doesn't: %m", m->type);
else if (r == 0)
- log_info("Kernel doesn't support 'usrquota' on '%s', not including in mount options for '%s'.", m->type, m->where);
+ log_debug("Kernel doesn't support 'usrquota' on '%s', not including in mount options for '%s'.", m->type, m->where);
else {
_cleanup_free_ char *joined = NULL;

View File

@ -0,0 +1,24 @@
From 2cc61dd003b3798a2beb19160501961056f8ee96 Mon Sep 17 00:00:00 2001
From: Mike Yuan <me@yhndnzj.com>
Date: Sat, 8 Mar 2025 19:13:44 +0100
Subject: [PATCH] mount-setup: drop outdated comment
(cherry picked from commit 80b7f60d2e7883cec3185172a85901d72fd5cdb3)
Related: RHEL-143028
---
src/shared/mount-setup.c | 1 -
1 file changed, 1 deletion(-)
diff --git a/src/shared/mount-setup.c b/src/shared/mount-setup.c
index db8c2b61d2..93e646d045 100644
--- a/src/shared/mount-setup.c
+++ b/src/shared/mount-setup.c
@@ -179,7 +179,6 @@ static int mount_one(const MountPoint *p, bool relabel) {
if (r > 0)
return 0;
- /* Skip securityfs in a container */
if (!FLAGS_SET(p->mode, MNT_IN_CONTAINER) && detect_container() > 0)
return 0;

View File

@ -0,0 +1,40 @@
From d5642d888c6bc1b8014b727b6b1b4851a0829239 Mon Sep 17 00:00:00 2001
From: Mike Yuan <me@yhndnzj.com>
Date: Thu, 6 Mar 2025 13:14:13 +0100
Subject: [PATCH] mount-setup: tune down log level if usrquota is not
supported, apply usrquota when smack is in use too
Follow-up for 8f5131fb9e7979022521d685e69b6419f0884677
(cherry picked from commit b05c495eedaa73d4e911f9617a9afba04c95395b)
Related: RHEL-143028
---
src/shared/mount-setup.c | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/src/shared/mount-setup.c b/src/shared/mount-setup.c
index 93e646d045..4c5151c7db 100644
--- a/src/shared/mount-setup.c
+++ b/src/shared/mount-setup.c
@@ -90,7 +90,7 @@ static const MountPoint mount_table[] = {
{ "smackfs", "/sys/fs/smackfs", "smackfs", "smackfsdef=*", MS_NOSUID|MS_NOEXEC|MS_NODEV,
mac_smack_use, MNT_FATAL },
{ "tmpfs", "/dev/shm", "tmpfs", "mode=01777,smackfsroot=*", MS_NOSUID|MS_NODEV|MS_STRICTATIME,
- mac_smack_use, MNT_FATAL },
+ mac_smack_use, MNT_FATAL|MNT_USRQUOTA_GRACEFUL },
#endif
{ "tmpfs", "/dev/shm", "tmpfs", "mode=01777", MS_NOSUID|MS_NODEV|MS_STRICTATIME,
NULL, MNT_FATAL|MNT_IN_CONTAINER|MNT_USRQUOTA_GRACEFUL },
@@ -194,9 +194,9 @@ static int mount_one(const MountPoint *p, bool relabel) {
if (FLAGS_SET(p->mode, MNT_USRQUOTA_GRACEFUL)) {
r = mount_option_supported(p->type, "usrquota", /* value= */ NULL);
if (r < 0)
- log_warning_errno(r, "Unable to determine whether %s supports 'usrquota' mount option, assuming not: %m", p->type);
+ log_full_errno(priority, r, "Unable to determine whether %s supports 'usrquota' mount option, assuming not: %m", p->type);
else if (r == 0)
- log_info("Not enabling 'usrquota' on '%s' as kernel lacks support for it.", p->where);
+ log_debug("Not enabling 'usrquota' on '%s' as kernel lacks support for it.", p->where);
else {
if (!strextend_with_separator(&extend_options, ",", p->options ?: POINTER_MAX, "usrquota"))
return log_oom();

View File

@ -0,0 +1,89 @@
From 72d7385b4390e3c4def7e488aef31b84e1bb5f5d Mon Sep 17 00:00:00 2001
From: Lennart Poettering <lennart@poettering.net>
Date: Thu, 23 Jan 2025 11:40:36 +0100
Subject: [PATCH] devnum-util: add macros to safely convert dev_t to pointers
and back
Sometimes it's nice being able to store dev_t as pointer values in
hashmaps/tables, instead of having to allocate memory for them and using
devt_hash_ops. After all dev_t is weird on Linux/glibc: glibc defines it
as 64bit entity (which hence appears as something we cannot encode in a
pointer value for compat with 32bit archs) but it actually is 32bit in
the kernel apis. Hence we can safely cut off the upper 32bit, and still
retain compat with all archs.
But let's hide this in new macros, and validate this is all correct via
a test.
(cherry picked from commit d15811d7e5bb3fc3afc1c14e4d5bb3b18fddedc1)
Related: RHEL-143028
---
src/basic/devnum-util.h | 10 ++++++++--
src/test/test-devnum-util.c | 17 +++++++++++++++++
2 files changed, 25 insertions(+), 2 deletions(-)
diff --git a/src/basic/devnum-util.h b/src/basic/devnum-util.h
index e109de9913..0efca56780 100644
--- a/src/basic/devnum-util.h
+++ b/src/basic/devnum-util.h
@@ -9,6 +9,9 @@
int parse_devnum(const char *s, dev_t *ret);
+#define DEVNUM_MAJOR_MAX ((UINT32_C(1) << 12) - 1U)
+#define DEVNUM_MINOR_MAX ((UINT32_C(1) << 20) - 1U)
+
/* glibc and the Linux kernel have different ideas about the major/minor size. These calls will check whether the
* specified major is valid by the Linux kernel's standards, not by glibc's. Linux has 20bits of minor, and 12 bits of
* major space. See MINORBITS in linux/kdev_t.h in the kernel sources. (If you wonder why we define _y here, instead of
@@ -18,14 +21,14 @@ int parse_devnum(const char *s, dev_t *ret);
#define DEVICE_MAJOR_VALID(x) \
({ \
typeof(x) _x = (x), _y = 0; \
- _x >= _y && _x < (UINT32_C(1) << 12); \
+ _x >= _y && _x <= DEVNUM_MAJOR_MAX; \
\
})
#define DEVICE_MINOR_VALID(x) \
({ \
typeof(x) _x = (x), _y = 0; \
- _x >= _y && _x < (UINT32_C(1) << 20); \
+ _x >= _y && _x <= DEVNUM_MINOR_MAX; \
})
int device_path_make_major_minor(mode_t mode, dev_t devnum, char **ret);
@@ -54,3 +57,6 @@ static inline char *format_devnum(dev_t d, char buf[static DEVNUM_STR_MAX]) {
static inline bool devnum_is_zero(dev_t d) {
return major(d) == 0 && minor(d) == 0;
}
+
+#define DEVNUM_TO_PTR(u) ((void*) (uintptr_t) (u))
+#define PTR_TO_DEVNUM(p) ((dev_t) ((uintptr_t) (p)))
diff --git a/src/test/test-devnum-util.c b/src/test/test-devnum-util.c
index ebef794001..782f15d86f 100644
--- a/src/test/test-devnum-util.c
+++ b/src/test/test-devnum-util.c
@@ -121,4 +121,21 @@ TEST(devnum_format_str) {
test_devnum_format_str_one(makedev(4095, 1048575), "4095:1048575");
}
+TEST(devnum_to_ptr) {
+ dev_t m = makedev(0, 0);
+ ASSERT_EQ(major(m), 0U);
+ ASSERT_EQ(minor(m), 0U);
+ ASSERT_EQ(m, PTR_TO_DEVNUM(DEVNUM_TO_PTR(m)));
+
+ m = makedev(DEVNUM_MAJOR_MAX, DEVNUM_MINOR_MAX);
+ ASSERT_EQ(major(m), DEVNUM_MAJOR_MAX);
+ ASSERT_EQ(minor(m), DEVNUM_MINOR_MAX);
+ ASSERT_EQ(m, PTR_TO_DEVNUM(DEVNUM_TO_PTR(m)));
+
+ m = makedev(5, 8);
+ ASSERT_EQ(major(m), 5U);
+ ASSERT_EQ(minor(m), 8U);
+ ASSERT_EQ(m, PTR_TO_DEVNUM(DEVNUM_TO_PTR(m)));
+}
+
DEFINE_TEST_MAIN(LOG_INFO);

View File

@ -0,0 +1,284 @@
From 34ee2cbe4a0b8bdd1d48c31d6735f9d9b551e524 Mon Sep 17 00:00:00 2001
From: Lennart Poettering <lennart@poettering.net>
Date: Fri, 10 Jan 2025 11:33:59 +0100
Subject: [PATCH] user-record: add fields for setting limits on /tmp/ and
/dev/shm/
(cherry picked from commit 72b932aac0fbd818208d78ec01256bb946401881)
Related: RHEL-143028
---
docs/USER_RECORD.md | 35 ++++++++++++-----
src/shared/user-record-show.c | 24 ++++++++++++
src/shared/user-record.c | 71 +++++++++++++++++++++++++++++++++++
src/shared/user-record.h | 17 +++++++++
4 files changed, 138 insertions(+), 9 deletions(-)
diff --git a/docs/USER_RECORD.md b/docs/USER_RECORD.md
index 90675dd532..689dd4e599 100644
--- a/docs/USER_RECORD.md
+++ b/docs/USER_RECORD.md
@@ -626,6 +626,19 @@ is allowed to edit.
`selfModifiablePrivileged` → Similar to `selfModifiableFields`, but it lists fields in
the `privileged` section that the user is allowed to edit.
+`tmpLimit` → A numeric value encoding a disk quota limit in bytes enforced on
+`/tmp/` on login, in case it is backed by volatile file system (such as
+`tmpfs`).
+
+`tmpLimitScale` → Similar, but encodes a relative value, normalized to
+`UINT32_MAX` as 100%. This value is applied relative to the file system
+size. If both `tmpLimit` and `tmpLimitScale` are set, the lower of the two
+should be enforced. If neither field is set the implementation might apply a
+default limit.
+
+`devShmLimit`, `devShmLimitScale` → Similar to the previous two, but apply to
+`/dev/shm/` rather than `/tmp/`.
+
`privileged` → An object, which contains the fields of the `privileged` section
of the user record, see below.
@@ -777,22 +790,26 @@ These four are the only fields specific to this section. All other fields that
may be used in this section are identical to the equally named ones in the
`regular` section (i.e. at the top-level object). Specifically, these are:
-`blobDirectory`, `blobManifest`, `iconName`, `location`, `shell`, `umask`, `environment`, `timeZone`,
-`preferredLanguage`, `additionalLanguages`, `niceLevel`, `resourceLimits`, `locked`, `notBeforeUSec`,
-`notAfterUSec`, `storage`, `diskSize`, `diskSizeRelative`, `skeletonDirectory`,
-`accessMode`, `tasksMax`, `memoryHigh`, `memoryMax`, `cpuWeight`, `ioWeight`,
+`blobDirectory`, `blobManifest`, `iconName`, `location`, `shell`, `umask`,
+`environment`, `timeZone`, `preferredLanguage`, `additionalLanguages`,
+`niceLevel`, `resourceLimits`, `locked`, `notBeforeUSec`, `notAfterUSec`,
+`storage`, `diskSize`, `diskSizeRelative`, `skeletonDirectory`, `accessMode`,
+`tasksMax`, `memoryHigh`, `memoryMax`, `cpuWeight`, `ioWeight`,
`mountNoDevices`, `mountNoSuid`, `mountNoExecute`, `cifsDomain`,
`cifsUserName`, `cifsService`, `cifsExtraMountOptions`, `imagePath`, `uid`,
`gid`, `memberOf`, `fileSystemType`, `partitionUuid`, `luksUuid`,
`fileSystemUuid`, `luksDiscard`, `luksOfflineDiscard`, `luksCipher`,
`luksCipherMode`, `luksVolumeKeySize`, `luksPbkdfHashAlgorithm`,
-`luksPbkdfType`, `luksPbkdfForceIterations`, `luksPbkdfTimeCostUSec`, `luksPbkdfMemoryCost`,
-`luksPbkdfParallelThreads`, `luksSectorSize`, `autoResizeMode`, `rebalanceWeight`,
-`rateLimitIntervalUSec`, `rateLimitBurst`, `enforcePasswordPolicy`,
-`autoLogin`, `preferredSessionType`, `preferredSessionLauncher`, `stopDelayUSec`, `killProcesses`,
+`luksPbkdfType`, `luksPbkdfForceIterations`, `luksPbkdfTimeCostUSec`,
+`luksPbkdfMemoryCost`, `luksPbkdfParallelThreads`, `luksSectorSize`,
+`autoResizeMode`, `rebalanceWeight`, `rateLimitIntervalUSec`, `rateLimitBurst`,
+`enforcePasswordPolicy`, `autoLogin`, `preferredSessionType`,
+`preferredSessionLauncher`, `stopDelayUSec`, `killProcesses`,
`passwordChangeMinUSec`, `passwordChangeMaxUSec`, `passwordChangeWarnUSec`,
`passwordChangeInactiveUSec`, `passwordChangeNow`, `pkcs11TokenUri`,
-`fido2HmacCredential`, `selfModifiableFields`, `selfModifiableBlobs`, `selfModifiablePrivileged`.
+`fido2HmacCredential`, `selfModifiableFields`, `selfModifiableBlobs`,
+`selfModifiablePrivileged`, `tmpLimit`, `tmpLimitScale`, `devShmLimit`,
+`devShmLimitScale`.
## Fields in the `binding` section
diff --git a/src/shared/user-record-show.c b/src/shared/user-record-show.c
index dab75d1f79..a6173aabb8 100644
--- a/src/shared/user-record-show.c
+++ b/src/shared/user-record-show.c
@@ -7,6 +7,7 @@
#include "hashmap.h"
#include "hexdecoct.h"
#include "path-util.h"
+#include "percent-util.h"
#include "pretty-print.h"
#include "process-util.h"
#include "rlimit-util.h"
@@ -54,6 +55,26 @@ static void dump_self_modifiable(
printf("%13s %s\n", i == value ? heading : "", *i);
}
+static void show_tmpfs_limit(const char *tmpfs, const TmpfsLimit *limit, uint32_t scale) {
+ assert(tmpfs);
+ assert(limit);
+
+ if (!limit->is_set)
+ return;
+
+ printf(" %s Limit:", tmpfs);
+
+ if (limit->limit != UINT64_MAX)
+ printf(" %s", FORMAT_BYTES(limit->limit));
+ if (limit->limit == UINT64_MAX || limit->limit_scale != UINT32_MAX) {
+ if (limit->limit != UINT64_MAX)
+ printf(" or");
+
+ printf(" %i%%", UINT32_SCALE_TO_PERCENT(scale));
+ }
+ printf("\n");
+}
+
void user_record_show(UserRecord *hr, bool show_full_group_info) {
_cleanup_strv_free_ char **langs = NULL;
const char *hd, *ip, *shell;
@@ -371,6 +392,9 @@ void user_record_show(UserRecord *hr, bool show_full_group_info) {
if (hr->io_weight != UINT64_MAX)
printf(" IO Weight: %" PRIu64 "\n", hr->io_weight);
+ show_tmpfs_limit("TMP", &hr->tmp_limit, user_record_tmp_limit_scale(hr));
+ show_tmpfs_limit("SHM", &hr->dev_shm_limit, user_record_dev_shm_limit_scale(hr));
+
if (hr->access_mode != MODE_INVALID)
printf(" Access Mode: 0%03o\n", user_record_access_mode(hr));
diff --git a/src/shared/user-record.c b/src/shared/user-record.c
index bfea52427a..6a1813c402 100644
--- a/src/shared/user-record.c
+++ b/src/shared/user-record.c
@@ -15,6 +15,7 @@
#include "locale-util.h"
#include "memory-util.h"
#include "path-util.h"
+#include "percent-util.h"
#include "pkcs11-util.h"
#include "rlimit-util.h"
#include "sha256.h"
@@ -95,6 +96,8 @@ UserRecord* user_record_new(void) {
.drop_caches = -1,
.auto_resize_mode = _AUTO_RESIZE_MODE_INVALID,
.rebalance_weight = REBALANCE_WEIGHT_UNSET,
+ .tmp_limit = TMPFS_LIMIT_NULL,
+ .dev_shm_limit = TMPFS_LIMIT_NULL,
};
return h;
@@ -982,6 +985,40 @@ static int dispatch_rebalance_weight(const char *name, sd_json_variant *variant,
return 0;
}
+static int dispatch_tmpfs_limit(const char *name, sd_json_variant *variant, sd_json_dispatch_flags_t flags, void *userdata) {
+ TmpfsLimit *limit = ASSERT_PTR(userdata);
+ int r;
+
+ if (sd_json_variant_is_null(variant)) {
+ *limit = TMPFS_LIMIT_NULL;
+ return 0;
+ }
+
+ r = sd_json_dispatch_uint64(name, variant, flags, &limit->limit);
+ if (r < 0)
+ return r;
+
+ limit->is_set = true;
+ return 0;
+}
+
+static int dispatch_tmpfs_limit_scale(const char *name, sd_json_variant *variant, sd_json_dispatch_flags_t flags, void *userdata) {
+ TmpfsLimit *limit = ASSERT_PTR(userdata);
+ int r;
+
+ if (sd_json_variant_is_null(variant)) {
+ *limit = TMPFS_LIMIT_NULL;
+ return 0;
+ }
+
+ r = sd_json_dispatch_uint32(name, variant, flags, &limit->limit_scale);
+ if (r < 0)
+ return r;
+
+ limit->is_set = true;
+ return 0;
+}
+
static int dispatch_privileged(const char *name, sd_json_variant *variant, sd_json_dispatch_flags_t flags, void *userdata) {
static const sd_json_dispatch_field privileged_dispatch_table[] = {
@@ -1299,6 +1336,10 @@ static int dispatch_per_machine(const char *name, sd_json_variant *variant, sd_j
{ "selfModifiableFields", SD_JSON_VARIANT_ARRAY, sd_json_dispatch_strv, offsetof(UserRecord, self_modifiable_fields), SD_JSON_STRICT },
{ "selfModifiableBlobs", SD_JSON_VARIANT_ARRAY, sd_json_dispatch_strv, offsetof(UserRecord, self_modifiable_blobs), SD_JSON_STRICT },
{ "selfModifiablePrivileged", SD_JSON_VARIANT_ARRAY, sd_json_dispatch_strv, offsetof(UserRecord, self_modifiable_privileged), SD_JSON_STRICT },
+ { "tmpLimit", _SD_JSON_VARIANT_TYPE_INVALID, dispatch_tmpfs_limit, offsetof(UserRecord, tmp_limit), 0, },
+ { "tmpLimitScale", _SD_JSON_VARIANT_TYPE_INVALID, dispatch_tmpfs_limit_scale, offsetof(UserRecord, tmp_limit), 0, },
+ { "devShmLimit", _SD_JSON_VARIANT_TYPE_INVALID, dispatch_tmpfs_limit, offsetof(UserRecord, dev_shm_limit), 0, },
+ { "devShmLimitScale", _SD_JSON_VARIANT_TYPE_INVALID, dispatch_tmpfs_limit_scale, offsetof(UserRecord, dev_shm_limit), 0, },
{},
};
@@ -1650,6 +1691,10 @@ int user_record_load(UserRecord *h, sd_json_variant *v, UserRecordLoadFlags load
{ "selfModifiableFields", SD_JSON_VARIANT_ARRAY, sd_json_dispatch_strv, offsetof(UserRecord, self_modifiable_fields), SD_JSON_STRICT },
{ "selfModifiableBlobs", SD_JSON_VARIANT_ARRAY, sd_json_dispatch_strv, offsetof(UserRecord, self_modifiable_blobs), SD_JSON_STRICT },
{ "selfModifiablePrivileged", SD_JSON_VARIANT_ARRAY, sd_json_dispatch_strv, offsetof(UserRecord, self_modifiable_privileged), SD_JSON_STRICT },
+ { "tmpLimit", _SD_JSON_VARIANT_TYPE_INVALID, dispatch_tmpfs_limit, offsetof(UserRecord, tmp_limit), 0, },
+ { "tmpLimitScale", _SD_JSON_VARIANT_TYPE_INVALID, dispatch_tmpfs_limit_scale, offsetof(UserRecord, tmp_limit), 0, },
+ { "devShmLimit", _SD_JSON_VARIANT_TYPE_INVALID, dispatch_tmpfs_limit, offsetof(UserRecord, dev_shm_limit), 0, },
+ { "devShmLimitScale", _SD_JSON_VARIANT_TYPE_INVALID, dispatch_tmpfs_limit_scale, offsetof(UserRecord, dev_shm_limit), 0, },
{ "secret", SD_JSON_VARIANT_OBJECT, dispatch_secret, 0, 0 },
{ "privileged", SD_JSON_VARIANT_OBJECT, dispatch_privileged, 0, 0 },
@@ -2163,6 +2208,32 @@ int user_record_languages(UserRecord *h, char ***ret) {
return 0;
}
+uint32_t user_record_tmp_limit_scale(UserRecord *h) {
+ assert(h);
+
+ if (h->tmp_limit.is_set)
+ return h->tmp_limit.limit_scale;
+
+ /* By default grant regular users only 80% quota */
+ if (user_record_disposition(h) == USER_REGULAR)
+ return UINT32_SCALE_FROM_PERCENT(80);
+
+ return UINT32_MAX;
+}
+
+uint32_t user_record_dev_shm_limit_scale(UserRecord *h) {
+ assert(h);
+
+ if (h->dev_shm_limit.is_set)
+ return h->dev_shm_limit.limit_scale;
+
+ /* By default grant regular users only 80% quota */
+ if (user_record_disposition(h) == USER_REGULAR)
+ return UINT32_SCALE_FROM_PERCENT(80);
+
+ return UINT32_MAX;
+}
+
const char** user_record_self_modifiable_fields(UserRecord *h) {
/* As a rule of thumb: a setting is safe if it cannot be used by a
* user to give themselves some unfair advantage over other users on
diff --git a/src/shared/user-record.h b/src/shared/user-record.h
index a3d8f4eb07..8c709750ae 100644
--- a/src/shared/user-record.h
+++ b/src/shared/user-record.h
@@ -232,6 +232,19 @@ typedef enum AutoResizeMode {
#define REBALANCE_WEIGHT_MAX UINT64_C(10000)
#define REBALANCE_WEIGHT_UNSET UINT64_MAX
+typedef struct TmpfsLimit {
+ /* Absolute and relative tmpfs limits */
+ uint64_t limit;
+ uint32_t limit_scale;
+ bool is_set;
+} TmpfsLimit;
+
+#define TMPFS_LIMIT_NULL \
+ (TmpfsLimit) { \
+ .limit = UINT64_MAX, \
+ .limit_scale = UINT32_MAX, \
+ } \
+
typedef struct UserRecord {
/* The following three fields are not part of the JSON record */
unsigned n_ref;
@@ -392,6 +405,8 @@ typedef struct UserRecord {
char **self_modifiable_blobs;
char **self_modifiable_privileged;
+ TmpfsLimit tmp_limit, dev_shm_limit;
+
sd_json_variant *json;
} UserRecord;
@@ -439,6 +454,8 @@ uint64_t user_record_rebalance_weight(UserRecord *h);
uint64_t user_record_capability_bounding_set(UserRecord *h);
uint64_t user_record_capability_ambient_set(UserRecord *h);
int user_record_languages(UserRecord *h, char ***ret);
+uint32_t user_record_tmp_limit_scale(UserRecord *h);
+uint32_t user_record_dev_shm_limit_scale(UserRecord *h);
const char **user_record_self_modifiable_fields(UserRecord *h);
const char **user_record_self_modifiable_blobs(UserRecord *h);

View File

@ -0,0 +1,108 @@
From 801e4945fff10fd4250202fed1101f7e236ddd41 Mon Sep 17 00:00:00 2001
From: Lennart Poettering <lennart@poettering.net>
Date: Thu, 23 Jan 2025 22:30:41 +0100
Subject: [PATCH] user-runtime-dir: some smaller modernizations/refactorings
(cherry picked from commit 9ef12bc1d73aa1c2d65ff006550180624e688a5c)
Related: RHEL-143028
---
src/login/user-runtime-dir.c | 44 +++++++++++++++++++-----------------
1 file changed, 23 insertions(+), 21 deletions(-)
diff --git a/src/login/user-runtime-dir.c b/src/login/user-runtime-dir.c
index b242f83429..f39c1ad225 100644
--- a/src/login/user-runtime-dir.c
+++ b/src/login/user-runtime-dir.c
@@ -92,39 +92,38 @@ static int user_mkdir_runtime_path(
uid, gid, runtime_dir_size, runtime_dir_inodes,
mac_smack_use() ? ",smackfsroot=*" : "");
+ _cleanup_free_ char *d = strdup(runtime_path);
+ if (!d)
+ return log_oom();
+
r = mkdir_label(runtime_path, 0700);
if (r < 0 && r != -EEXIST)
return log_error_errno(r, "Failed to create %s: %m", runtime_path);
+ _cleanup_(rmdir_and_freep) char *destroy = TAKE_PTR(d); /* auto-destroy */
+
r = mount_nofollow_verbose(LOG_DEBUG, "tmpfs", runtime_path, "tmpfs", MS_NODEV|MS_NOSUID, options);
if (r < 0) {
- if (!ERRNO_IS_PRIVILEGE(r)) {
- log_error_errno(r, "Failed to mount per-user tmpfs directory %s: %m", runtime_path);
- goto fail;
- }
+ if (!ERRNO_IS_PRIVILEGE(r))
+ return log_error_errno(r, "Failed to mount per-user tmpfs directory %s: %m", runtime_path);
log_debug_errno(r,
"Failed to mount per-user tmpfs directory %s.\n"
"Assuming containerized execution, ignoring: %m", runtime_path);
r = chmod_and_chown(runtime_path, 0700, uid, gid);
- if (r < 0) {
- log_error_errno(r, "Failed to change ownership and mode of \"%s\": %m", runtime_path);
- goto fail;
- }
+ if (r < 0)
+ return log_error_errno(r, "Failed to change ownership and mode of \"%s\": %m", runtime_path);
}
+ destroy = mfree(destroy); /* deactivate auto-destroy */
+
r = label_fix(runtime_path, 0);
if (r < 0)
log_warning_errno(r, "Failed to fix label of \"%s\", ignoring: %m", runtime_path);
}
return 0;
-
-fail:
- /* Try to clean up, but ignore errors */
- (void) rmdir(runtime_path);
- return r;
}
static int user_remove_runtime_path(const char *runtime_path) {
@@ -139,9 +138,9 @@ static int user_remove_runtime_path(const char *runtime_path) {
/* Ignore cases where the directory isn't mounted, as that's quite possible, if we lacked the permissions to
* mount something */
- r = umount2(runtime_path, MNT_DETACH);
- if (r < 0 && !IN_SET(errno, EINVAL, ENOENT))
- log_debug_errno(errno, "Failed to unmount user runtime directory %s, ignoring: %m", runtime_path);
+ r = RET_NERRNO(umount2(runtime_path, MNT_DETACH));
+ if (r < 0 && !IN_SET(r, -EINVAL, -ENOENT))
+ log_debug_errno(r, "Failed to unmount user runtime directory %s, ignoring: %m", runtime_path);
r = rm_rf(runtime_path, REMOVE_ROOT);
if (r < 0 && r != -ENOENT)
@@ -206,7 +205,10 @@ static int run(int argc, char *argv[]) {
if (argc != 3)
return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
"This program takes two arguments.");
- if (!STR_IN_SET(argv[1], "start", "stop"))
+
+ const char *verb = argv[1], *user = argv[2];
+
+ if (!STR_IN_SET(verb, "start", "stop"))
return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
"First argument must be either \"start\" or \"stop\".");
@@ -216,10 +218,10 @@ static int run(int argc, char *argv[]) {
if (r < 0)
return r;
- if (streq(argv[1], "start"))
- return do_mount(argv[2]);
- if (streq(argv[1], "stop"))
- return do_umount(argv[2]);
+ if (streq(verb, "start"))
+ return do_mount(user);
+ if (streq(verb, "stop"))
+ return do_umount(user);
assert_not_reached();
}

View File

@ -0,0 +1,304 @@
From 4aad3334b4618edc47d4813bdbed9b3ca6b86aec Mon Sep 17 00:00:00 2001
From: Lennart Poettering <lennart@poettering.net>
Date: Fri, 10 Jan 2025 11:34:18 +0100
Subject: [PATCH] user-runtime-dir: enforce /tmp/ and /dev/shm/ quota
Enforce the quota on these two tmpfs at the same place where we mount
the per-user $XDG_RUNTIME_DIR. Conceptually these are very similar
concepts, and it makes sure to enforce the limits at the same place with
the same lifecycle.
(cherry picked from commit b1c95fb2e9d11fc190017dec3d64f468f9d378bc)
Resolves: RHEL-143028
---
README | 2 +
man/user@.service.xml | 13 +--
src/login/user-runtime-dir.c | 191 ++++++++++++++++++++++++++++++-----
3 files changed, 173 insertions(+), 33 deletions(-)
diff --git a/README b/README
index b9a58389ad..7d38f17b5a 100644
--- a/README
+++ b/README
@@ -41,6 +41,8 @@ REQUIREMENTS:
≥ 5.3 for bounded loops in BPF program
≥ 5.4 for pidfd and signed Verity images
≥ 5.7 for CLONE_INTO_CGROUP, BPF links and the BPF LSM hook
+ ≥ 5.14 for quotactl_fd()
+ ≥ 6.6 for quota support on tmpfs
⛔ Kernel versions below 3.15 ("minimum baseline") are not supported at
all, and are missing required functionality (e.g. CLOCK_BOOTTIME
diff --git a/man/user@.service.xml b/man/user@.service.xml
index e9cbda4833..f2e83c1b16 100644
--- a/man/user@.service.xml
+++ b/man/user@.service.xml
@@ -42,12 +42,13 @@
<citerefentry><refentrytitle>systemd.special</refentrytitle><manvolnum>7</manvolnum></citerefentry> for a
list of units that form the basis of the unit hierarchies of system and user units.</para>
- <para><filename>user@<replaceable>UID</replaceable>.service</filename> is accompanied by the
- system unit <filename>user-runtime-dir@<replaceable>UID</replaceable>.service</filename>, which
- creates the user's runtime directory
- <filename>/run/user/<replaceable>UID</replaceable></filename>, and then removes it when this
- unit is stopped. <filename>user-runtime-dir@<replaceable>UID</replaceable>.service</filename>
- executes the <filename>systemd-user-runtime-dir</filename> binary to do the actual work.</para>
+ <para><filename>user@<replaceable>UID</replaceable>.service</filename> is accompanied by the system unit
+ <filename>user-runtime-dir@<replaceable>UID</replaceable>.service</filename>, which creates the user's
+ runtime directory <filename>/run/user/<replaceable>UID</replaceable></filename> when started, and removes
+ it when it is stopped. It also might apply runtime quota settings on <filename>/tmp/</filename> and/or
+ <filename>/dev/shm/</filename> for the
+ user. <filename>user-runtime-dir@<replaceable>UID</replaceable>.service</filename> executes the
+ <filename>systemd-user-runtime-dir</filename> binary to do the actual work.</para>
<para>User processes may be started by the <filename>user@.service</filename> instance, in which
case they will be part of that unit in the system hierarchy. They may also be started elsewhere,
diff --git a/src/login/user-runtime-dir.c b/src/login/user-runtime-dir.c
index f39c1ad225..94117c95db 100644
--- a/src/login/user-runtime-dir.c
+++ b/src/login/user-runtime-dir.c
@@ -8,15 +8,20 @@
#include "bus-error.h"
#include "bus-locator.h"
#include "dev-setup.h"
+#include "devnum-util.h"
+#include "fd-util.h"
#include "format-util.h"
#include "fs-util.h"
#include "label-util.h"
#include "limits-util.h"
#include "main-func.h"
+#include "missing_magic.h"
+#include "missing_syscall.h"
#include "mkdir-label.h"
#include "mount-util.h"
#include "mountpoint-util.h"
#include "path-util.h"
+#include "quota-util.h"
#include "rm-rf.h"
#include "selinux-util.h"
#include "smack-util.h"
@@ -24,6 +29,7 @@
#include "string-util.h"
#include "strv.h"
#include "user-util.h"
+#include "userdb.h"
static int acquire_runtime_dir_properties(uint64_t *ret_size, uint64_t *ret_inodes) {
_cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
@@ -126,6 +132,26 @@ static int user_mkdir_runtime_path(
return 0;
}
+static int do_mount(UserRecord *ur) {
+ int r;
+
+ assert(ur);
+
+ if (!uid_is_valid(ur->uid) || !gid_is_valid(ur->gid))
+ return log_error_errno(SYNTHETIC_ERRNO(ENOMSG), "User '%s' lacks UID or GID, refusing.", ur->user_name);
+
+ uint64_t runtime_dir_size, runtime_dir_inodes;
+ r = acquire_runtime_dir_properties(&runtime_dir_size, &runtime_dir_inodes);
+ if (r < 0)
+ return r;
+
+ char runtime_path[STRLEN("/run/user/") + DECIMAL_STR_MAX(uid_t)];
+ xsprintf(runtime_path, "/run/user/" UID_FMT, ur->uid);
+
+ log_debug("Will mount %s owned by "UID_FMT":"GID_FMT, runtime_path, ur->uid, ur->gid);
+ return user_mkdir_runtime_path(runtime_path, ur->uid, ur->gid, runtime_dir_size, runtime_dir_inodes);
+}
+
static int user_remove_runtime_path(const char *runtime_path) {
int r;
@@ -149,31 +175,6 @@ static int user_remove_runtime_path(const char *runtime_path) {
return 0;
}
-static int do_mount(const char *user) {
- char runtime_path[STRLEN("/run/user/") + DECIMAL_STR_MAX(uid_t)];
- uint64_t runtime_dir_size, runtime_dir_inodes;
- uid_t uid;
- gid_t gid;
- int r;
-
- r = get_user_creds(&user, &uid, &gid, NULL, NULL, 0);
- if (r < 0)
- return log_error_errno(r,
- r == -ESRCH ? "No such user \"%s\"" :
- r == -ENOMSG ? "UID \"%s\" is invalid or has an invalid main group"
- : "Failed to look up user \"%s\": %m",
- user);
-
- r = acquire_runtime_dir_properties(&runtime_dir_size, &runtime_dir_inodes);
- if (r < 0)
- return r;
-
- xsprintf(runtime_path, "/run/user/" UID_FMT, uid);
-
- log_debug("Will mount %s owned by "UID_FMT":"GID_FMT, runtime_path, uid, gid);
- return user_mkdir_runtime_path(runtime_path, uid, gid, runtime_dir_size, runtime_dir_inodes);
-}
-
static int do_umount(const char *user) {
char runtime_path[STRLEN("/run/user/") + DECIMAL_STR_MAX(uid_t)];
uid_t uid;
@@ -197,6 +198,126 @@ static int do_umount(const char *user) {
return user_remove_runtime_path(runtime_path);
}
+static int apply_tmpfs_quota(
+ char **paths,
+ uid_t uid,
+ uint64_t limit,
+ uint32_t scale) {
+
+ _cleanup_set_free_ Set *processed = NULL;
+ int r;
+
+ assert(uid_is_valid(uid));
+
+ STRV_FOREACH(p, paths) {
+ _cleanup_close_ int fd = open(*p, O_DIRECTORY|O_CLOEXEC);
+ if (fd < 0) {
+ log_warning_errno(errno, "Failed to open '%s' in order to set quota, ignoring: %m", *p);
+ continue;
+ }
+
+ struct stat st;
+ if (fstat(fd, &st) < 0) {
+ log_warning_errno(errno, "Failed to stat '%s' in order to set quota, ignoring: %m", *p);
+ continue;
+ }
+
+ /* Cover for bind mounted or symlinked /var/tmp/ + /tmp/ */
+ if (set_contains(processed, DEVNUM_TO_PTR(st.st_dev))) {
+ log_debug("Not setting quota on '%s', since already processed.", *p);
+ continue;
+ }
+
+ /* Remember we already dealt with this fs, even if the subsequent operation fails, since
+ * there's no point in appyling quota twice, regardless if it succeeds or not. */
+ if (set_ensure_put(&processed, /* hash_ops= */ NULL, DEVNUM_TO_PTR(st.st_dev)) < 0)
+ return log_oom();
+
+ struct statfs sfs;
+ if (fstatfs(fd, &sfs) < 0) {
+ log_warning_errno(errno, "Failed to statfs '%s' in order to set quota, ignoring: %m", *p);
+ continue;
+ }
+
+ if (!is_fs_type(&sfs, TMPFS_MAGIC)) {
+ log_debug("Not setting quota on '%s', since not tmpfs.", *p);
+ continue;
+ }
+
+ struct dqblk req;
+ r = RET_NERRNO(quotactl_fd(fd, QCMD_FIXED(Q_GETQUOTA, USRQUOTA), uid, &req));
+ if (r == -ESRCH)
+ zero(req);
+ else if (ERRNO_IS_NEG_NOT_SUPPORTED(r)) {
+ log_debug_errno(r, "No UID quota support on %s, not setting quota: %m", *p);
+ continue;
+ } else if (ERRNO_IS_NEG_PRIVILEGE(r)) {
+ log_debug_errno(r, "Lacking privileges to query UID quota on %s, not setting quota: %m", *p);
+ continue;
+ } else if (r < 0) {
+ log_warning_errno(r, "Failed to query disk quota on %s for UID " UID_FMT ", ignoring: %m", *p, uid);
+ continue;
+ }
+
+ uint64_t v =
+ (scale == 0) ? 0 :
+ (scale == UINT32_MAX) ? UINT64_MAX :
+ (uint64_t) ((double) (sfs.f_blocks * sfs.f_frsize) / scale * UINT32_MAX);
+
+ v = MIN(v, limit);
+ v /= QIF_DQBLKSIZE;
+
+ if (FLAGS_SET(req.dqb_valid, QIF_BLIMITS) && v == req.dqb_bhardlimit) {
+ /* Shortcut things if everything is set up properly already */
+ log_debug("Configured quota on '%s' already matches the intended setting, not updating quota.", *p);
+ continue;
+ }
+
+ req.dqb_valid = QIF_BLIMITS;
+ req.dqb_bsoftlimit = req.dqb_bhardlimit = v;
+
+ r = RET_NERRNO(quotactl_fd(fd, QCMD_FIXED(Q_SETQUOTA, USRQUOTA), uid, &req));
+ if (r == -ESRCH) {
+ log_debug_errno(r, "Not setting UID quota on %s since UID quota is not supported: %m", *p);
+ continue;
+ } else if (ERRNO_IS_NEG_PRIVILEGE(r)) {
+ log_debug_errno(r, "Lacking privileges to set UID quota on %s, skipping: %m", *p);
+ continue;
+ } else if (r < 0) {
+ log_warning_errno(r, "Failed to set disk quota on %s for UID " UID_FMT ", ignoring: %m", *p, uid);
+ continue;
+ }
+
+ log_info("Successfully configured disk quota for UID " UID_FMT " on %s to %s", uid, *p, FORMAT_BYTES(v * QIF_DQBLKSIZE));
+ }
+
+ return 0;
+}
+
+static int do_tmpfs_quota(UserRecord *ur) {
+ int r;
+
+ assert(ur);
+
+ if (user_record_is_root(ur)) {
+ log_debug("Not applying tmpfs quota to root user.");
+ return 0;
+ }
+
+ if (!uid_is_valid(ur->uid))
+ return log_error_errno(SYNTHETIC_ERRNO(ENOMSG), "User '%s' lacks UID, refusing.", ur->user_name);
+
+ r = apply_tmpfs_quota(STRV_MAKE("/tmp", "/var/tmp"), ur->uid, ur->tmp_limit.limit, user_record_tmp_limit_scale(ur));
+ if (r < 0)
+ return r;
+
+ r = apply_tmpfs_quota(STRV_MAKE("/dev/shm"), ur->uid, ur->dev_shm_limit.limit, user_record_dev_shm_limit_scale(ur));
+ if (r < 0)
+ return r;
+
+ return 0;
+}
+
static int run(int argc, char *argv[]) {
int r;
@@ -218,10 +339,26 @@ static int run(int argc, char *argv[]) {
if (r < 0)
return r;
- if (streq(verb, "start"))
- return do_mount(user);
+ if (streq(verb, "start")) {
+ _cleanup_(user_record_unrefp) UserRecord *ur = NULL;
+ r = userdb_by_name(user, /* match= */ NULL, USERDB_PARSE_NUMERIC|USERDB_SUPPRESS_SHADOW, &ur);
+ if (r == -ESRCH)
+ return log_error_errno(r, "User '%s' does not exist: %m", user);
+ if (r < 0)
+ return log_error_errno(r, "Failed to resolve user '%s': %m", user);
+
+ /* We do two things here: mount the per-user XDG_RUNTIME_DIR, and set up tmpfs quota on /tmp/
+ * and /dev/shm/. */
+
+ r = 0;
+ RET_GATHER(r, do_mount(ur));
+ RET_GATHER(r, do_tmpfs_quota(ur));
+ return r;
+ }
+
if (streq(verb, "stop"))
return do_umount(user);
+
assert_not_reached();
}

View File

@ -0,0 +1,129 @@
From aa2f389a0844384dc0ecfda291ddb5e4f2afbc40 Mon Sep 17 00:00:00 2001
From: Lennart Poettering <lennart@poettering.net>
Date: Fri, 10 Jan 2025 15:31:44 +0100
Subject: [PATCH] homectl: add support for configuring tmpfs limits
(cherry picked from commit 2b2aebf4dd695d52cf8b6cbd38ba57affdc5a6bb)
Resolves: RHEL-143028
---
man/homectl.xml | 16 +++++++++++++
src/home/homectl.c | 57 ++++++++++++++++++++++++++++++++++++++++++++++
2 files changed, 73 insertions(+)
diff --git a/man/homectl.xml b/man/homectl.xml
index d08972c421..adad89c54a 100644
--- a/man/homectl.xml
+++ b/man/homectl.xml
@@ -792,6 +792,22 @@
<xi:include href="version-info.xml" xpointer="v245"/></listitem>
</varlistentry>
+ <varlistentry>
+ <term><option>--tmp-limit=<replaceable>BYTES</replaceable></option></term>
+ <term><option>--tmp-limit=<replaceable>PERCENT</replaceable></option></term>
+ <term><option>--dev-shm-limit=<replaceable>BYTES</replaceable></option></term>
+ <term><option>--dev-shm-limit=<replaceable>PERCENT</replaceable></option></term>
+
+ <listitem><para>Controls the per-user quota on <filename>/tmp/</filename> and
+ <filename>/dev/shm/</filename> that is applied when the user logs in. Takes either an absolute value
+ in bytes (with the usual K, M, G, T suffixes to the base of 1024), or a percentage. In the latter
+ case the limit is applied relative to the size of the respective file system. This limit is only
+ applied if the relevant file system is <literal>tmpfs</literal> and has no effect otherwise. Note
+ that if these options are not used, a default quota might still be enforced (typically 80%.)</para>
+
+ <xi:include href="version-info.xml" xpointer="v258"/></listitem>
+ </varlistentry>
+
<varlistentry>
<term><option>--storage=<replaceable>STORAGE</replaceable></option></term>
diff --git a/src/home/homectl.c b/src/home/homectl.c
index 6d5aec1602..c99663ffea 100644
--- a/src/home/homectl.c
+++ b/src/home/homectl.c
@@ -2854,6 +2854,9 @@ static int help(int argc, char *argv[], void *userdata) {
" --memory-max=BYTES Set maximum memory limit\n"
" --cpu-weight=WEIGHT Set CPU weight\n"
" --io-weight=WEIGHT Set IO weight\n"
+ " --tmp-limit=BYTES|PERCENT Set limit on /tmp/\n"
+ " --dev-shm-limit=BYTES|PERCENT\n"
+ " Set limit on /dev/shm/\n"
"\n%4$sStorage User Record Properties:%5$s\n"
" --storage=STORAGE Storage type to use (luks, fscrypt, directory,\n"
" subvolume, cifs)\n"
@@ -3002,6 +3005,8 @@ static int parse_argv(int argc, char *argv[]) {
ARG_PROMPT_NEW_USER,
ARG_AVATAR,
ARG_LOGIN_BACKGROUND,
+ ARG_TMP_LIMIT,
+ ARG_DEV_SHM_LIMIT,
ARG_MATCH,
};
@@ -3103,6 +3108,8 @@ static int parse_argv(int argc, char *argv[]) {
{ "blob", required_argument, NULL, 'b' },
{ "avatar", required_argument, NULL, ARG_AVATAR },
{ "login-background", required_argument, NULL, ARG_LOGIN_BACKGROUND },
+ { "tmp-limit", required_argument, NULL, ARG_TMP_LIMIT },
+ { "dev-shm-limit", required_argument, NULL, ARG_DEV_SHM_LIMIT },
{ "match", required_argument, NULL, ARG_MATCH },
{}
};
@@ -4541,6 +4548,56 @@ static int parse_argv(int argc, char *argv[]) {
break;
}
+ case ARG_TMP_LIMIT:
+ case ARG_DEV_SHM_LIMIT: {
+ const char *field =
+ c == ARG_TMP_LIMIT ? "tmpLimit" :
+ c == ARG_DEV_SHM_LIMIT ? "devShmLimit" : NULL;
+ const char *field_scale =
+ c == ARG_TMP_LIMIT ? "tmpLimitScale" :
+ c == ARG_DEV_SHM_LIMIT ? "devShmLimitScale" : NULL;
+
+ assert(field);
+ assert(field_scale);
+
+ if (isempty(optarg)) {
+ r = drop_from_identity(field);
+ if (r < 0)
+ return r;
+ r = drop_from_identity(field_scale);
+ if (r < 0)
+ return r;
+ break;
+ }
+
+ r = parse_permyriad(optarg);
+ if (r < 0) {
+ uint64_t u;
+
+ r = parse_size(optarg, 1024, &u);
+ if (r < 0)
+ return log_error_errno(r, "Failed to parse %s/%s parameter: %s", field, field_scale, optarg);
+
+ r = sd_json_variant_set_field_unsigned(&arg_identity_extra, field, u);
+ if (r < 0)
+ return log_error_errno(r, "Failed to set %s field: %m", field);
+
+ r = drop_from_identity(field_scale);
+ if (r < 0)
+ return r;
+ } else {
+ r = sd_json_variant_set_field_unsigned(&arg_identity_extra, field_scale, UINT32_SCALE_FROM_PERMYRIAD(r));
+ if (r < 0)
+ return log_error_errno(r, "Failed to set %s field: %m", field_scale);
+
+ r = drop_from_identity(field);
+ if (r < 0)
+ return r;
+ }
+
+ break;
+ }
+
case ARG_MATCH:
if (streq(optarg, "any"))
match_identity = &arg_identity_extra;

View File

@ -0,0 +1,40 @@
From 2425eb1611a0176aa096296d28e612be41c917ee Mon Sep 17 00:00:00 2001
From: Lennart Poettering <lennart@poettering.net>
Date: Wed, 15 Jan 2025 00:25:22 +0100
Subject: [PATCH] test: add test case for tmpfs quota logic + PAMName=
ask-password logic
(cherry picked from commit d58d449fc6b828429f2e4d06779a45318eb17f27)
Related: RHEL-143028
---
test/units/TEST-46-HOMED.sh | 16 ++++++++++++++++
1 file changed, 16 insertions(+)
diff --git a/test/units/TEST-46-HOMED.sh b/test/units/TEST-46-HOMED.sh
index 8de170a1c9..3663e53908 100755
--- a/test/units/TEST-46-HOMED.sh
+++ b/test/units/TEST-46-HOMED.sh
@@ -652,6 +652,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 -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 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 rm /dev/shm/quotatestfile1
+
+ systemctl stop user@"$(id -u tmpfsquota)".service
+
+ wait_for_state tmpfsquota inactive
+ homectl remove tmpfsquota
+fi
+
systemd-analyze log-level info
touch /testok

52
0647-update-TODO.patch Normal file
View File

@ -0,0 +1,52 @@
From aedee1c91632bc2f50e23733a94f3ca106960f05 Mon Sep 17 00:00:00 2001
From: Lennart Poettering <lennart@poettering.net>
Date: Wed, 15 Jan 2025 09:44:52 +0100
Subject: [PATCH] update TODO
(cherry picked from commit 0054b7dce986b73e6cb10bf4ae51a1dd5ef57191)
Related: RHEL-143028
---
TODO | 8 --------
1 file changed, 8 deletions(-)
diff --git a/TODO b/TODO
index d6c2cef2f1..4ed9f5a834 100644
--- a/TODO
+++ b/TODO
@@ -257,10 +257,6 @@ Features:
* pcrlock: add support for multi-profile UKIs
-* logind: when logging in use new tmpfs quota support to configure quota on
- /tmp/ + /dev/shm/. But do so only in case of tmpfs, because otherwise quota
- is persistent and any persistent settings mean we don#t have to reapply them.
-
* initrd: when transitioning from initrd to host, validate that
/lib/modules/`uname -r` exists, refuse otherwise
@@ -1447,8 +1443,6 @@ Features:
* rework recursive read-only remount to use new mount API
-* PAM: pick up authentication token from credentials
-
* when mounting disk images: if IMAGE_ID/IMAGE_VERSION is set in os-release
data in the image, make sure the image filename actually matches this, so
that images cannot be misused.
@@ -1515,7 +1509,6 @@ Features:
- pass creds via keyring?
- pass creds via memfd?
- acquire + decrypt creds from pkcs11?
- - make PAMName= acquire pw via creds logic
- make macsec code in networkd read key via creds logic (copy logic from
wireguard)
- make gatewayd/remote read key via creds logic
@@ -2378,7 +2371,6 @@ Features:
- maybe make automatic, read-only, time-based reflink-copies of LUKS disk
images (and btrfs snapshots of subvolumes) (think: time machine)
- distinguish destroy / remove (i.e. currently we can unregister a user, unregister+remove their home directory, but not just remove their home directory)
- - in systemd's PAMName= logic: query passwords with ssh-askpassword, so that we can make "loginctl set-linger" mode work
- fingerprint authentication, pattern authentication, …
- make sure "classic" user records can also be managed by homed
- make size of $XDG_RUNTIME_DIR configurable in user record

View File

@ -0,0 +1,31 @@
From b1731e6bef1276d5081284fe7d0aa46a62e6749c Mon Sep 17 00:00:00 2001
From: Mike Yuan <me@yhndnzj.com>
Date: Sun, 9 Feb 2025 19:59:13 +0100
Subject: [PATCH] core/dbus-service: fix alignment
(cherry picked from commit 5fe4c30ca78679551edd7152b70d66b481453b66)
Related: RHEL-143028
---
src/core/dbus-service.c | 8 ++++----
1 file changed, 4 insertions(+), 4 deletions(-)
diff --git a/src/core/dbus-service.c b/src/core/dbus-service.c
index 85eb99f218..a1c452e29f 100644
--- a/src/core/dbus-service.c
+++ b/src/core/dbus-service.c
@@ -393,10 +393,10 @@ const sd_bus_vtable bus_service_vtable[] = {
SD_BUS_VTABLE_UNPRIVILEGED),
SD_BUS_METHOD_WITH_ARGS("MountImage",
- SD_BUS_ARGS("s", source, "s", destination, "b", read_only, "b", mkdir, "a(ss)", options),
- SD_BUS_NO_RESULT,
- bus_service_method_mount_image,
- SD_BUS_VTABLE_UNPRIVILEGED),
+ SD_BUS_ARGS("s", source, "s", destination, "b", read_only, "b", mkdir, "a(ss)", options),
+ SD_BUS_NO_RESULT,
+ bus_service_method_mount_image,
+ SD_BUS_VTABLE_UNPRIVILEGED),
SD_BUS_METHOD_WITH_ARGS("DumpFileDescriptorStore",
SD_BUS_NO_ARGS,

View File

@ -0,0 +1,25 @@
From 31d4a35652e852905137c532f2f8bd1fa2391dc0 Mon Sep 17 00:00:00 2001
From: Mike Yuan <me@yhndnzj.com>
Date: Sun, 9 Feb 2025 20:25:21 +0100
Subject: [PATCH] core/mount: filter out "fail" option as well
(cherry picked from commit 7e9a78d6bea96bf8e9de615f6013c72f912f3e2d)
Related: RHEL-143028
---
src/core/mount.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/core/mount.c b/src/core/mount.c
index 136c3f1453..786c3ae5f5 100644
--- a/src/core/mount.c
+++ b/src/core/mount.c
@@ -1131,7 +1131,7 @@ static int mount_set_mount_command(Mount *m, ExecCommand *c, const MountParamete
}
_cleanup_free_ char *opts = NULL;
- r = fstab_filter_options(p->options, "nofail\0" "noauto\0" "auto\0", NULL, NULL, NULL, &opts);
+ r = fstab_filter_options(p->options, "nofail\0" "fail\0" "noauto\0" "auto\0", NULL, NULL, NULL, &opts);
if (r < 0)
return r;

View File

@ -0,0 +1,155 @@
From db653b7fc6bc1fb503152c34f0d3e374d115a236 Mon Sep 17 00:00:00 2001
From: Mike Yuan <me@yhndnzj.com>
Date: Sun, 9 Feb 2025 20:41:20 +0100
Subject: [PATCH] core/mount: check parameters_fragment first in
mount_enter_(re)mounting()
I.e. don't perform any action if we can't spawn mount task anyway.
Later the same check would be added to mount_can_start/reload(),
so this makes things more coherent too.
(cherry picked from commit 65bc0c03b97065847b187af2092458977173def4)
Related: RHEL-143028
---
src/core/mount.c | 85 +++++++++++++++++++++++-------------------------
1 file changed, 41 insertions(+), 44 deletions(-)
diff --git a/src/core/mount.c b/src/core/mount.c
index 786c3ae5f5..b317af03a7 100644
--- a/src/core/mount.c
+++ b/src/core/mount.c
@@ -1160,7 +1160,12 @@ static void mount_enter_mounting(Mount *m) {
goto fail;
p = get_mount_parameters_fragment(m);
- if (p && mount_is_bind(p)) {
+ if (!p) {
+ r = log_unit_warning_errno(UNIT(m), SYNTHETIC_ERRNO(ENOENT), "No mount parameters to operate on.");
+ goto fail;
+ }
+
+ if (mount_is_bind(p)) {
r = is_dir(p->what, /* follow = */ true);
if (r < 0 && r != -ENOENT)
log_unit_info_errno(UNIT(m), r, "Failed to determine type of bind mount source '%s', ignoring: %m", p->what);
@@ -1176,7 +1181,7 @@ static void mount_enter_mounting(Mount *m) {
log_unit_warning_errno(UNIT(m), r, "Failed to create mount point '%s', ignoring: %m", m->where);
/* If we are asked to create an OverlayFS, create the upper/work directories if they are missing */
- if (p && streq_ptr(p->fstype, "overlay")) {
+ if (streq_ptr(p->fstype, "overlay")) {
_cleanup_strv_free_ char **dirs = NULL;
r = fstab_filter_options(
@@ -1205,13 +1210,9 @@ static void mount_enter_mounting(Mount *m) {
if (source_is_dir)
unit_warn_if_dir_nonempty(UNIT(m), m->where);
- unit_warn_leftover_processes(UNIT(m), /* start = */ true);
-
- m->control_command_id = MOUNT_EXEC_MOUNT;
- m->control_command = m->exec_command + MOUNT_EXEC_MOUNT;
/* Create the source directory for bind-mounts if needed */
- if (p && mount_is_bind(p)) {
+ if (mount_is_bind(p)) {
r = mkdir_p_label(p->what, m->directory_mode);
/* mkdir_p_label() can return -EEXIST if the target path exists and is not a directory - which is
* totally OK, in case the user wants us to overmount a non-directory inode. Also -EROFS can be
@@ -1223,19 +1224,18 @@ static void mount_enter_mounting(Mount *m) {
r, "Failed to make bind mount source '%s', ignoring: %m", p->what);
}
- if (p) {
- r = mount_set_mount_command(m, m->control_command, p);
- if (r < 0) {
- log_unit_warning_errno(UNIT(m), r, "Failed to prepare mount command line: %m");
- goto fail;
- }
- } else {
- r = log_unit_warning_errno(UNIT(m), SYNTHETIC_ERRNO(ENOENT), "No mount parameters to operate on.");
+ mount_unwatch_control_pid(m);
+ unit_warn_leftover_processes(UNIT(m), /* start = */ true);
+
+ m->control_command_id = MOUNT_EXEC_MOUNT;
+ m->control_command = m->exec_command + MOUNT_EXEC_MOUNT;
+
+ r = mount_set_mount_command(m, m->control_command, p);
+ if (r < 0) {
+ log_unit_warning_errno(UNIT(m), r, "Failed to prepare mount command line: %m");
goto fail;
}
- mount_unwatch_control_pid(m);
-
r = mount_spawn(m, m->control_command, &m->control_pid);
if (r < 0) {
log_unit_warning_errno(UNIT(m), r, "Failed to spawn 'mount' task: %m");
@@ -1265,42 +1265,39 @@ static void mount_enter_remounting(Mount *m) {
assert(m);
- /* Reset reload result when we are about to start a new remount operation */
+ p = get_mount_parameters_fragment(m);
+ if (!p) {
+ r = log_unit_warning_errno(UNIT(m), SYNTHETIC_ERRNO(ENOENT), "No mount parameters to operate on.");
+ goto fail;
+ }
+
+ mount_unwatch_control_pid(m);
m->reload_result = MOUNT_SUCCESS;
m->control_command_id = MOUNT_EXEC_REMOUNT;
m->control_command = m->exec_command + MOUNT_EXEC_REMOUNT;
- p = get_mount_parameters_fragment(m);
- if (p) {
- const char *o;
-
- if (p->options)
- o = strjoina("remount,", p->options);
- else
- o = "remount";
-
- r = exec_command_set(m->control_command, MOUNT_PATH,
- p->what, m->where,
- "-o", o, NULL);
- if (r >= 0 && m->sloppy_options)
- r = exec_command_append(m->control_command, "-s", NULL);
- if (r >= 0 && m->read_write_only)
- r = exec_command_append(m->control_command, "-w", NULL);
- if (r >= 0 && p->fstype)
- r = exec_command_append(m->control_command, "-t", p->fstype, NULL);
- if (r < 0) {
- log_unit_warning_errno(UNIT(m), r, "Failed to prepare remount command line: %m");
- goto fail;
- }
+ const char *o;
- } else {
- r = log_unit_warning_errno(UNIT(m), SYNTHETIC_ERRNO(ENOENT), "No mount parameters to operate on.");
+ if (p->options)
+ o = strjoina("remount,", p->options);
+ else
+ o = "remount";
+
+ r = exec_command_set(m->control_command, MOUNT_PATH,
+ p->what, m->where,
+ "-o", o, NULL);
+ if (r >= 0 && m->sloppy_options)
+ r = exec_command_append(m->control_command, "-s", NULL);
+ if (r >= 0 && m->read_write_only)
+ r = exec_command_append(m->control_command, "-w", NULL);
+ if (r >= 0 && p->fstype)
+ r = exec_command_append(m->control_command, "-t", p->fstype, NULL);
+ if (r < 0) {
+ log_unit_warning_errno(UNIT(m), r, "Failed to prepare remount command line: %m");
goto fail;
}
- mount_unwatch_control_pid(m);
-
r = mount_spawn(m, m->control_command, &m->control_pid);
if (r < 0) {
log_unit_warning_errno(UNIT(m), r, "Failed to spawn 'remount' task: %m");

View File

@ -0,0 +1,49 @@
From 341da00c9aea8c3987cbf7042c5392bd7b8f2181 Mon Sep 17 00:00:00 2001
From: Mike Yuan <me@yhndnzj.com>
Date: Mon, 10 Feb 2025 20:22:09 +0100
Subject: [PATCH] core/mount: report accurate can_start and can_reload
(cherry picked from commit 74c0d9726cfbd4ecdf1807adf5433fcf4b1d86b4)
Related: RHEL-143028
---
src/core/mount.c | 11 +++++++++++
1 file changed, 11 insertions(+)
diff --git a/src/core/mount.c b/src/core/mount.c
index b317af03a7..6b38ee9431 100644
--- a/src/core/mount.c
+++ b/src/core/mount.c
@@ -1393,6 +1393,12 @@ static int mount_reload(Unit *u) {
return 1;
}
+static bool mount_can_reload(Unit *u) {
+ Mount *m = ASSERT_PTR(MOUNT(u));
+
+ return get_mount_parameters_fragment(m);
+}
+
static int mount_serialize(Unit *u, FILE *f, FDSet *fds) {
Mount *m = ASSERT_PTR(MOUNT(u));
@@ -2334,6 +2340,9 @@ static int mount_test_startable(Unit *u) {
return r;
}
+ if (!get_mount_parameters_fragment(m))
+ return -ENOENT;
+
return true;
}
@@ -2448,7 +2457,9 @@ const UnitVTable mount_vtable = {
.start = mount_start,
.stop = mount_stop,
+
.reload = mount_reload,
+ .can_reload = mount_can_reload,
.clean = mount_clean,
.can_clean = mount_can_clean,

View File

@ -0,0 +1,67 @@
From 3aa54774b2d343c63feb3f7eafa5b489e526a8dc Mon Sep 17 00:00:00 2001
From: Mike Yuan <me@yhndnzj.com>
Date: Wed, 12 Feb 2025 14:56:34 +0100
Subject: [PATCH] core/mount: trivial coding style cleanups
(cherry picked from commit c7c6cf2031c1c0eb1839d2ea83b5a8de4af6da00)
Related: RHEL-143028
---
src/core/mount.c | 7 +++----
src/core/mount.h | 2 +-
2 files changed, 4 insertions(+), 5 deletions(-)
diff --git a/src/core/mount.c b/src/core/mount.c
index 6b38ee9431..8ec18d8952 100644
--- a/src/core/mount.c
+++ b/src/core/mount.c
@@ -45,9 +45,9 @@ static const UnitActiveState state_translation_table[_MOUNT_STATE_MAX] = {
[MOUNT_MOUNTING_DONE] = UNIT_ACTIVATING,
[MOUNT_MOUNTED] = UNIT_ACTIVE,
[MOUNT_REMOUNTING] = UNIT_RELOADING,
- [MOUNT_UNMOUNTING] = UNIT_DEACTIVATING,
[MOUNT_REMOUNTING_SIGTERM] = UNIT_RELOADING,
[MOUNT_REMOUNTING_SIGKILL] = UNIT_RELOADING,
+ [MOUNT_UNMOUNTING] = UNIT_DEACTIVATING,
[MOUNT_UNMOUNTING_SIGTERM] = UNIT_DEACTIVATING,
[MOUNT_UNMOUNTING_SIGKILL] = UNIT_DEACTIVATING,
[MOUNT_FAILED] = UNIT_FAILED,
@@ -1038,6 +1038,8 @@ static void mount_enter_unmounting(Mount *m) {
MOUNT_UNMOUNTING_SIGKILL))
m->n_retry_umount = 0;
+ mount_unwatch_control_pid(m);
+
m->control_command_id = MOUNT_EXEC_UNMOUNT;
m->control_command = m->exec_command + MOUNT_EXEC_UNMOUNT;
@@ -1047,8 +1049,6 @@ static void mount_enter_unmounting(Mount *m) {
goto fail;
}
- mount_unwatch_control_pid(m);
-
r = mount_spawn(m, m->control_command, &m->control_pid);
if (r < 0) {
log_unit_warning_errno(UNIT(m), r, "Failed to spawn 'umount' task: %m");
@@ -1056,7 +1056,6 @@ static void mount_enter_unmounting(Mount *m) {
}
mount_set_state(m, MOUNT_UNMOUNTING);
-
return;
fail:
diff --git a/src/core/mount.h b/src/core/mount.h
index 28cc7785d8..a8ae25e638 100644
--- a/src/core/mount.h
+++ b/src/core/mount.h
@@ -81,7 +81,7 @@ struct Mount {
MountState state, deserialized_state;
- ExecCommand* control_command;
+ ExecCommand *control_command;
MountExecCommand control_command_id;
PidRef control_pid;

View File

@ -0,0 +1,73 @@
From f9204b8dccc68a3256f1e03ac9aaa1db1721f9df Mon Sep 17 00:00:00 2001
From: Mike Yuan <me@yhndnzj.com>
Date: Mon, 10 Feb 2025 20:24:22 +0100
Subject: [PATCH] core/dbus-mount: add missing ReloadResult and CleanResult
properties
(cherry picked from commit 0fa062f9836ea5ed09bc42026d2d576dfb52c409)
Related: RHEL-143028
---
man/org.freedesktop.systemd1.xml | 14 +++++++++++++-
src/core/dbus-mount.c | 2 ++
2 files changed, 15 insertions(+), 1 deletion(-)
diff --git a/man/org.freedesktop.systemd1.xml b/man/org.freedesktop.systemd1.xml
index 71d2a11d29..4e9d72be25 100644
--- a/man/org.freedesktop.systemd1.xml
+++ b/man/org.freedesktop.systemd1.xml
@@ -7002,6 +7002,8 @@ node /org/freedesktop/systemd1/unit/home_2emount {
@org.freedesktop.DBus.Property.EmitsChangedSignal("const")
readonly b ReadWriteOnly = ...;
readonly s Result = '...';
+ readonly s ReloadResult = '...';
+ readonly s CleanResult = '...';
readonly u UID = ...;
readonly u GID = ...;
@org.freedesktop.DBus.Property.EmitsChangedSignal("const")
@@ -7602,6 +7604,10 @@ node /org/freedesktop/systemd1/unit/home_2emount {
<!--property ReadWriteOnly is not documented!-->
+ <!--property ReloadResult is not documented!-->
+
+ <!--property CleanResult is not documented!-->
+
<!--property UID is not documented!-->
<!--property GID is not documented!-->
@@ -8150,6 +8156,10 @@ node /org/freedesktop/systemd1/unit/home_2emount {
<variablelist class="dbus-property" generated="True" extra-ref="Result"/>
+ <variablelist class="dbus-property" generated="True" extra-ref="ReloadResult"/>
+
+ <variablelist class="dbus-property" generated="True" extra-ref="CleanResult"/>
+
<variablelist class="dbus-property" generated="True" extra-ref="UID"/>
<variablelist class="dbus-property" generated="True" extra-ref="GID"/>
@@ -12386,7 +12396,9 @@ $ gdbus introspect --system --dest org.freedesktop.systemd1 \
<varname>ManagedOOMMemoryPressureDurationUSec</varname>,
<varname>ProtectControlGroupsEx</varname>, and
<varname>PrivatePIDs</varname> were added in version 257.</para>
- <para><varname>GracefulOptions</varname> were added in version 258.</para>
+ <para><varname>GracefulOptions</varname>,
+ <varname>ReloadResult</varname>, and
+ <varname>CleanResult</varname> were added in version 258.</para>
</refsect2>
<refsect2>
<title>Swap Unit Objects</title>
diff --git a/src/core/dbus-mount.c b/src/core/dbus-mount.c
index 855300d025..72bd4c697a 100644
--- a/src/core/dbus-mount.c
+++ b/src/core/dbus-mount.c
@@ -73,6 +73,8 @@ const sd_bus_vtable bus_mount_vtable[] = {
SD_BUS_PROPERTY("ForceUnmount", "b", bus_property_get_bool, offsetof(Mount, force_unmount), SD_BUS_VTABLE_PROPERTY_CONST),
SD_BUS_PROPERTY("ReadWriteOnly", "b", bus_property_get_bool, offsetof(Mount, read_write_only), SD_BUS_VTABLE_PROPERTY_CONST),
SD_BUS_PROPERTY("Result", "s", property_get_result, offsetof(Mount, result), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
+ SD_BUS_PROPERTY("ReloadResult", "s", property_get_result, offsetof(Mount, reload_result), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
+ SD_BUS_PROPERTY("CleanResult", "s", property_get_result, offsetof(Mount, clean_result), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
SD_BUS_PROPERTY("UID", "u", bus_property_get_uid, offsetof(Unit, ref_uid), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
SD_BUS_PROPERTY("GID", "u", bus_property_get_gid, offsetof(Unit, ref_gid), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
SD_BUS_PROPERTY("GracefulOptions", "as", NULL, offsetof(Mount, graceful_options), SD_BUS_VTABLE_PROPERTY_CONST),

View File

@ -0,0 +1,49 @@
From 3f7834fccb42fa5ffffc17c93e43a1bbced00964 Mon Sep 17 00:00:00 2001
From: Mike Yuan <me@yhndnzj.com>
Date: Tue, 11 Feb 2025 18:13:01 +0100
Subject: [PATCH] bus-unit-util: add missing assertions
(cherry picked from commit 85f759baeeab59f25080bd19b2f338ae2d05ad8b)
Related: RHEL-143028
---
src/shared/bus-unit-util.c | 10 ++++++----
1 file changed, 6 insertions(+), 4 deletions(-)
diff --git a/src/shared/bus-unit-util.c b/src/shared/bus-unit-util.c
index 8667d10077..9128c06555 100644
--- a/src/shared/bus-unit-util.c
+++ b/src/shared/bus-unit-util.c
@@ -148,9 +148,11 @@ static int bus_append_string(sd_bus_message *m, const char *field, const char *e
}
static int bus_append_strv(sd_bus_message *m, const char *field, const char *eq, const char *separator, ExtractFlags flags) {
- const char *p;
int r;
+ assert(m);
+ assert(field);
+
r = sd_bus_message_open_container(m, 'r', "sv");
if (r < 0)
return bus_log_create_error(r);
@@ -167,16 +169,16 @@ static int bus_append_strv(sd_bus_message *m, const char *field, const char *eq,
if (r < 0)
return bus_log_create_error(r);
- for (p = eq;;) {
+ for (const char *p = eq;;) {
_cleanup_free_ char *word = NULL;
r = extract_first_word(&p, &word, separator, flags);
- if (r == 0)
- break;
if (r == -ENOMEM)
return log_oom();
if (r < 0)
return log_error_errno(r, "Invalid syntax: %s", eq);
+ if (r == 0)
+ break;
r = sd_bus_message_append_basic(m, 's', word);
if (r < 0)

View File

@ -0,0 +1,439 @@
From 4c474d4d10e0c52e108bbb75ad3aad0ca2dca469 Mon Sep 17 00:00:00 2001
From: Mike Yuan <me@yhndnzj.com>
Date: Tue, 11 Feb 2025 18:43:25 +0100
Subject: [PATCH] core/mount: rework GracefulOptions= to be just
x-systemd.graceful-option=
09fbff57fcde47782a73f23b3d5cfdcd0e8f699b introduced new knob
for such functionality. However, that seems unnecessary.
The mount option string is ubiquitous in that all of fstab,
kernel cmdline, credentials, systemd-mount, ... speak it.
And we already have x-systemd.device-bound= that's parsed
by pid1 instead of fstab-generator. It feels hence more natural
for graceful options to be an extension of that, rather than
its own property.
There's also one nice side effect that the setting itself
is now more graceful for systemd versions not supporting
such feature.
(cherry picked from commit 0d76f1c4237a3e4adda828c694721fd709374b36)
Related: RHEL-143028
---
man/org.freedesktop.systemd1.xml | 9 +---
man/systemd.mount.xml | 29 +++++------
src/core/dbus-mount.c | 26 +---------
src/core/load-fragment-gperf.gperf.in | 1 -
src/core/load-fragment.c | 45 -----------------
src/core/load-fragment.h | 1 -
src/core/mount.c | 62 +++++++++++-------------
src/core/mount.h | 2 -
src/shared/bus-unit-util.c | 3 --
test/units/TEST-87-AUX-UTILS-VM.mount.sh | 4 +-
units/tmp.mount | 3 +-
11 files changed, 47 insertions(+), 138 deletions(-)
diff --git a/man/org.freedesktop.systemd1.xml b/man/org.freedesktop.systemd1.xml
index 4e9d72be25..0b6be4c755 100644
--- a/man/org.freedesktop.systemd1.xml
+++ b/man/org.freedesktop.systemd1.xml
@@ -7006,8 +7006,6 @@ node /org/freedesktop/systemd1/unit/home_2emount {
readonly s CleanResult = '...';
readonly u UID = ...;
readonly u GID = ...;
- @org.freedesktop.DBus.Property.EmitsChangedSignal("const")
- readonly as GracefulOptions = ['...', ...];
@org.freedesktop.DBus.Property.EmitsChangedSignal("invalidates")
readonly a(sasbttttuii) ExecMount = [...];
@org.freedesktop.DBus.Property.EmitsChangedSignal("invalidates")
@@ -7612,8 +7610,6 @@ node /org/freedesktop/systemd1/unit/home_2emount {
<!--property GID is not documented!-->
- <!--property GracefulOptions is not documented!-->
-
<!--property ExecUnmount is not documented!-->
<!--property ExecRemount is not documented!-->
@@ -8164,8 +8160,6 @@ node /org/freedesktop/systemd1/unit/home_2emount {
<variablelist class="dbus-property" generated="True" extra-ref="GID"/>
- <variablelist class="dbus-property" generated="True" extra-ref="GracefulOptions"/>
-
<variablelist class="dbus-property" generated="True" extra-ref="ExecMount"/>
<variablelist class="dbus-property" generated="True" extra-ref="ExecUnmount"/>
@@ -12396,8 +12390,7 @@ $ gdbus introspect --system --dest org.freedesktop.systemd1 \
<varname>ManagedOOMMemoryPressureDurationUSec</varname>,
<varname>ProtectControlGroupsEx</varname>, and
<varname>PrivatePIDs</varname> were added in version 257.</para>
- <para><varname>GracefulOptions</varname>,
- <varname>ReloadResult</varname>, and
+ <para><varname>ReloadResult</varname>, and
<varname>CleanResult</varname> were added in version 258.</para>
</refsect2>
<refsect2>
diff --git a/man/systemd.mount.xml b/man/systemd.mount.xml
index 9b394f6079..e84b6a9d48 100644
--- a/man/systemd.mount.xml
+++ b/man/systemd.mount.xml
@@ -284,6 +284,19 @@
<xi:include href="version-info.xml" xpointer="v220"/></listitem>
</varlistentry>
+ <varlistentry>
+ <term><option>x-systemd.graceful-option=</option></term>
+
+ <listitem><para>Additional mount option that shall be appended if supported by the kernel.
+ This may be used to configure mount options that are optional and only enabled on kernels that
+ support them. Note that this is supported only for native kernel mount options (i.e. explicitly not
+ for mount options implemented in userspace, such as those processed by
+ <command>/usr/bin/mount</command> itself, by FUSE or by mount helpers such as
+ <command>mount.nfs</command>). This option may be specified more than once.</para>
+
+ <xi:include href="version-info.xml" xpointer="v258"/></listitem>
+ </varlistentry>
+
<varlistentry>
<term><option>x-systemd.device-bound=</option></term>
@@ -650,22 +663,6 @@
<citerefentry><refentrytitle>systemd-system.conf</refentrytitle><manvolnum>5</manvolnum></citerefentry>.
</para></listitem>
</varlistentry>
-
- <varlistentry>
- <term><varname>GracefulOptions=</varname></term>
-
- <listitem><para>Additional mount options that shall be appended to <varname>Options=</varname> if
- supported by the kernel. This may be used to configure mount options that are optional and only
- enabled on kernels that support them. Note that this is supported only for native kernel mount
- options (i.e. explicitly not for mount options implemented in userspace, such as those processed by
- <command>/usr/bin/mount</command> itself, by FUSE or by mount helpers such as
- <command>mount.nfs</command>).</para>
-
- <para>May be specified multiple times. If specified multiple times, all listed, supported mount
- options are combined. If an empty string is assigned, the list is reset.</para>
-
- <xi:include href="version-info.xml" xpointer="v258"/></listitem>
- </varlistentry>
</variablelist>
<xi:include href="systemd.service.xml" xpointer="shared-unit-options" />
diff --git a/src/core/dbus-mount.c b/src/core/dbus-mount.c
index 72bd4c697a..6b30b90f7f 100644
--- a/src/core/dbus-mount.c
+++ b/src/core/dbus-mount.c
@@ -77,7 +77,7 @@ const sd_bus_vtable bus_mount_vtable[] = {
SD_BUS_PROPERTY("CleanResult", "s", property_get_result, offsetof(Mount, clean_result), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
SD_BUS_PROPERTY("UID", "u", bus_property_get_uid, offsetof(Unit, ref_uid), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
SD_BUS_PROPERTY("GID", "u", bus_property_get_gid, offsetof(Unit, ref_gid), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
- SD_BUS_PROPERTY("GracefulOptions", "as", NULL, offsetof(Mount, graceful_options), SD_BUS_VTABLE_PROPERTY_CONST),
+
BUS_EXEC_COMMAND_VTABLE("ExecMount", offsetof(Mount, exec_command[MOUNT_EXEC_MOUNT]), SD_BUS_VTABLE_PROPERTY_EMITS_INVALIDATION),
BUS_EXEC_COMMAND_VTABLE("ExecUnmount", offsetof(Mount, exec_command[MOUNT_EXEC_UNMOUNT]), SD_BUS_VTABLE_PROPERTY_EMITS_INVALIDATION),
BUS_EXEC_COMMAND_VTABLE("ExecRemount", offsetof(Mount, exec_command[MOUNT_EXEC_REMOUNT]), SD_BUS_VTABLE_PROPERTY_EMITS_INVALIDATION),
@@ -153,30 +153,6 @@ static int bus_mount_set_transient_property(
if (streq(name, "ReadWriteOnly"))
return bus_set_transient_bool(u, name, &m->read_write_only, message, flags, error);
- if (streq(name, "GracefulOptions")) {
- _cleanup_strv_free_ char **add = NULL;
- r = sd_bus_message_read_strv(message, &add);
- if (r < 0)
- return r;
-
- if (!UNIT_WRITE_FLAGS_NOOP(flags)) {
-
- if (strv_isempty(add)) {
- m->graceful_options = strv_free(m->graceful_options);
- unit_write_settingf(u, flags, name, "GracefulOptions=");
- } else {
- r = strv_extend_strv(&m->graceful_options, add, /* filter_duplicates= */ false);
- if (r < 0)
- return r;
-
- STRV_FOREACH(a, add)
- unit_write_settingf(u, flags|UNIT_ESCAPE_SPECIFIERS, name, "GracefulOptions=%s", *a);
- }
- }
-
- return 1;
- }
-
return 0;
}
diff --git a/src/core/load-fragment-gperf.gperf.in b/src/core/load-fragment-gperf.gperf.in
index 5b67d7b554..d7564b3767 100644
--- a/src/core/load-fragment-gperf.gperf.in
+++ b/src/core/load-fragment-gperf.gperf.in
@@ -546,7 +546,6 @@ Mount.SloppyOptions, config_parse_bool,
Mount.LazyUnmount, config_parse_bool, 0, offsetof(Mount, lazy_unmount)
Mount.ForceUnmount, config_parse_bool, 0, offsetof(Mount, force_unmount)
Mount.ReadWriteOnly, config_parse_bool, 0, offsetof(Mount, read_write_only)
-Mount.GracefulOptions, config_parse_mount_graceful_options, 0, offsetof(Mount, graceful_options)
{{ EXEC_CONTEXT_CONFIG_ITEMS('Mount') }}
{{ CGROUP_CONTEXT_CONFIG_ITEMS('Mount') }}
{{ KILL_CONTEXT_CONFIG_ITEMS('Mount') }}
diff --git a/src/core/load-fragment.c b/src/core/load-fragment.c
index 9007846e64..90f054ec9e 100644
--- a/src/core/load-fragment.c
+++ b/src/core/load-fragment.c
@@ -6121,51 +6121,6 @@ int config_parse_mount_node(
return config_parse_string(unit, filename, line, section, section_line, lvalue, ltype, path, data, userdata);
}
-int config_parse_mount_graceful_options(
- const char *unit,
- const char *filename,
- unsigned line,
- const char *section,
- unsigned section_line,
- const char *lvalue,
- int ltype,
- const char *rvalue,
- void *data,
- void *userdata) {
-
- const Unit *u = ASSERT_PTR(userdata);
- char ***sv = ASSERT_PTR(data);
- int r;
-
- assert(filename);
- assert(lvalue);
- assert(rvalue);
-
- if (isempty(rvalue)) {
- *sv = strv_free(*sv);
- return 1;
- }
-
- _cleanup_free_ char *resolved = NULL;
- r = unit_full_printf(u, rvalue, &resolved);
- if (r < 0) {
- log_syntax(unit, LOG_WARNING, filename, line, r, "Failed to resolve unit specifiers in '%s', ignoring: %m", rvalue);
- return 0;
- }
-
- _cleanup_strv_free_ char **strv = NULL;
-
- r = strv_split_full(&strv, resolved, ",", EXTRACT_RETAIN_ESCAPE|EXTRACT_UNESCAPE_SEPARATORS);
- if (r < 0)
- return log_syntax_parse_error(unit, filename, line, r, lvalue, rvalue);
-
- r = strv_extend_strv_consume(sv, TAKE_PTR(strv), /* filter_duplicates = */ false);
- if (r < 0)
- return log_oom();
-
- return 1;
-}
-
static int merge_by_names(Unit *u, Set *names, const char *id) {
char *k;
int r;
diff --git a/src/core/load-fragment.h b/src/core/load-fragment.h
index 673088a42d..8ac962a94b 100644
--- a/src/core/load-fragment.h
+++ b/src/core/load-fragment.h
@@ -164,7 +164,6 @@ CONFIG_PARSER_PROTOTYPE(config_parse_open_file);
CONFIG_PARSER_PROTOTYPE(config_parse_memory_pressure_watch);
CONFIG_PARSER_PROTOTYPE(config_parse_cgroup_nft_set);
CONFIG_PARSER_PROTOTYPE(config_parse_mount_node);
-CONFIG_PARSER_PROTOTYPE(config_parse_mount_graceful_options);
/* gperf prototypes */
const struct ConfigPerfItem* load_fragment_gperf_lookup(const char *key, GPERF_LEN_TYPE length);
diff --git a/src/core/mount.c b/src/core/mount.c
index 8ec18d8952..037902c91c 100644
--- a/src/core/mount.c
+++ b/src/core/mount.c
@@ -231,8 +231,6 @@ static void mount_done(Unit *u) {
mount_unwatch_control_pid(m);
m->timer_event_source = sd_event_source_disable_unref(m->timer_event_source);
-
- m->graceful_options = strv_free(m->graceful_options);
}
static int update_parameters_proc_self_mountinfo(
@@ -1062,22 +1060,25 @@ fail:
mount_enter_dead_or_mounted(m, MOUNT_FAILURE_RESOURCES, /* flush_result = */ false);
}
-static int mount_append_graceful_options(Mount *m, const MountParameters *p, char **opts) {
+static int mount_apply_graceful_options(Mount *m, const MountParameters *p, char **opts) {
+ _cleanup_strv_free_ char **graceful = NULL;
+ _cleanup_free_ char *filtered = NULL;
int r;
assert(m);
assert(p);
assert(opts);
- if (strv_isempty(m->graceful_options))
- return 0;
+ r = fstab_filter_options(*opts, "x-systemd.graceful-option\0", NULL, NULL, &graceful, &filtered);
+ if (r <= 0)
+ return r;
if (!p->fstype) {
- log_unit_warning(UNIT(m), "GracefulOptions= used but file system type not known, suppressing all graceful options.");
+ log_unit_warning(UNIT(m), "x-systemd.graceful-option= used but file system type not known, suppressing all graceful options.");
return 0;
}
- STRV_FOREACH(o, m->graceful_options) {
+ STRV_FOREACH(o, graceful) {
_cleanup_free_ char *k = NULL, *v = NULL;
r = split_pair(*o, "=", &k, &v);
@@ -1086,21 +1087,22 @@ static int mount_append_graceful_options(Mount *m, const MountParameters *p, cha
r = mount_option_supported(p->fstype, k ?: *o, v);
if (r < 0)
- log_unit_warning_errno(UNIT(m), r, "GracefulOptions=%s specified, but cannot determine availability, suppressing.", *o);
+ log_unit_warning_errno(UNIT(m), r,
+ "x-systemd.graceful-option=%s specified, but cannot determine availability, suppressing: %m", *o);
else if (r == 0)
- log_unit_info(UNIT(m), "GracefulOptions=%s specified, but option is not available, suppressing.", *o);
+ log_unit_info(UNIT(m), "x-systemd.graceful-option=%s specified, but option is not available, suppressing.", *o);
else {
- log_unit_debug(UNIT(m), "GracefulOptions=%s specified and supported, appending to mount option string.", *o);
+ log_unit_debug(UNIT(m), "x-systemd.graceful-option=%s specified and supported, appending to mount option string.", *o);
- if (!strextend_with_separator(opts, ",", *o))
+ if (!strextend_with_separator(&filtered, ",", *o))
return -ENOMEM;
}
}
- return 0;
+ return free_and_replace(*opts, filtered);
}
-static int mount_set_mount_command(Mount *m, ExecCommand *c, const MountParameters *p) {
+static int mount_set_mount_command(Mount *m, ExecCommand *c, const MountParameters *p, bool remount) {
int r;
assert(m);
@@ -1134,10 +1136,19 @@ static int mount_set_mount_command(Mount *m, ExecCommand *c, const MountParamete
if (r < 0)
return r;
- r = mount_append_graceful_options(m, p, &opts);
+ r = mount_apply_graceful_options(m, p, &opts);
if (r < 0)
return r;
+ if (remount) {
+ if (isempty(opts)) {
+ opts = strdup("remount");
+ if (!opts)
+ return -ENOMEM;
+ } else if (!strprepend(&opts, "remount,"))
+ return -ENOMEM;
+ }
+
if (!isempty(opts)) {
r = exec_command_append(c, "-o", opts, NULL);
if (r < 0)
@@ -1229,9 +1240,9 @@ static void mount_enter_mounting(Mount *m) {
m->control_command_id = MOUNT_EXEC_MOUNT;
m->control_command = m->exec_command + MOUNT_EXEC_MOUNT;
- r = mount_set_mount_command(m, m->control_command, p);
+ r = mount_set_mount_command(m, m->control_command, p, /* remount = */ false);
if (r < 0) {
- log_unit_warning_errno(UNIT(m), r, "Failed to prepare mount command line: %m");
+ log_unit_error_errno(UNIT(m), r, "Failed to prepare mount command line: %m");
goto fail;
}
@@ -1276,24 +1287,9 @@ static void mount_enter_remounting(Mount *m) {
m->control_command_id = MOUNT_EXEC_REMOUNT;
m->control_command = m->exec_command + MOUNT_EXEC_REMOUNT;
- const char *o;
-
- if (p->options)
- o = strjoina("remount,", p->options);
- else
- o = "remount";
-
- r = exec_command_set(m->control_command, MOUNT_PATH,
- p->what, m->where,
- "-o", o, NULL);
- if (r >= 0 && m->sloppy_options)
- r = exec_command_append(m->control_command, "-s", NULL);
- if (r >= 0 && m->read_write_only)
- r = exec_command_append(m->control_command, "-w", NULL);
- if (r >= 0 && p->fstype)
- r = exec_command_append(m->control_command, "-t", p->fstype, NULL);
+ r = mount_set_mount_command(m, m->control_command, p, /* remount = */ true);
if (r < 0) {
- log_unit_warning_errno(UNIT(m), r, "Failed to prepare remount command line: %m");
+ log_unit_error_errno(UNIT(m), r, "Failed to prepare remount command line: %m");
goto fail;
}
diff --git a/src/core/mount.h b/src/core/mount.h
index a8ae25e638..7fd643f6fa 100644
--- a/src/core/mount.h
+++ b/src/core/mount.h
@@ -88,8 +88,6 @@ struct Mount {
sd_event_source *timer_event_source;
unsigned n_retry_umount;
-
- char **graceful_options;
};
extern const UnitVTable mount_vtable;
diff --git a/src/shared/bus-unit-util.c b/src/shared/bus-unit-util.c
index 9128c06555..bc58d43140 100644
--- a/src/shared/bus-unit-util.c
+++ b/src/shared/bus-unit-util.c
@@ -2311,9 +2311,6 @@ static int bus_append_mount_property(sd_bus_message *m, const char *field, const
"ReadwriteOnly"))
return bus_append_parse_boolean(m, field, eq);
- if (streq(field, "GracefulOptions"))
- return bus_append_strv(m, field, eq, /* separator= */ ",", 0);
-
return 0;
}
diff --git a/test/units/TEST-87-AUX-UTILS-VM.mount.sh b/test/units/TEST-87-AUX-UTILS-VM.mount.sh
index c1bfbdc1a2..8ad7ae6fc5 100755
--- a/test/units/TEST-87-AUX-UTILS-VM.mount.sh
+++ b/test/units/TEST-87-AUX-UTILS-VM.mount.sh
@@ -181,9 +181,9 @@ touch "$WORK_DIR/mnt/hello"
[[ "$(stat -c "%U:%G" "$WORK_DIR/mnt/hello")" == "testuser:testuser" ]]
systemd-umount LABEL=owner-vfat
-# Mkae sure that graceful mount options work
+# Make sure that graceful mount options work
GRACEFULTEST="/tmp/graceful/$RANDOM"
-systemd-mount --tmpfs -p GracefulOptions=idefinitelydontexist,nr_inodes=4711,idonexisteither "$GRACEFULTEST"
+systemd-mount --tmpfs --options="x-systemd.graceful-option=idefinitelydontexist,x-systemd.graceful-option=nr_inodes=4711,x-systemd.graceful-option=idonexisteither" "$GRACEFULTEST"
findmnt -n -o options "$GRACEFULTEST"
findmnt -n -o options "$GRACEFULTEST" | grep -q nr_inodes=4711
umount "$GRACEFULTEST"
diff --git a/units/tmp.mount b/units/tmp.mount
index d347af56d3..373b131211 100644
--- a/units/tmp.mount
+++ b/units/tmp.mount
@@ -22,8 +22,7 @@ After=swap.target
What=tmpfs
Where=/tmp
Type=tmpfs
-Options=mode=1777,strictatime,nosuid,nodev,size=50%%,nr_inodes=1m
-GracefulOptions=usrquota
+Options=mode=1777,strictatime,nosuid,nodev,size=50%%,nr_inodes=1m,x-systemd.graceful-option=usrquota
# Make 'systemctl enable tmp.mount' work:
[Install]

View File

@ -0,0 +1,48 @@
From 61d7212e99466780278b2f75be35c5d19cc93087 Mon Sep 17 00:00:00 2001
From: Mike Yuan <me@yhndnzj.com>
Date: Wed, 12 Feb 2025 15:44:13 +0100
Subject: [PATCH] mountpoint-util: assume fsopen() works in
mount_option_supported()
Our baseline includes it now.
(cherry picked from commit c2198d0c3fdbebd30b0653f2e3d148372c5fff31)
Related: RHEL-143028
---
src/basic/mountpoint-util.c | 12 ++++--------
1 file changed, 4 insertions(+), 8 deletions(-)
diff --git a/src/basic/mountpoint-util.c b/src/basic/mountpoint-util.c
index e8471d5974..76f1a6d6b4 100644
--- a/src/basic/mountpoint-util.c
+++ b/src/basic/mountpoint-util.c
@@ -803,14 +803,10 @@ int mount_option_supported(const char *fstype, const char *key, const char *valu
assert(key);
fd = fsopen(fstype, FSOPEN_CLOEXEC);
- if (fd < 0) {
- if (ERRNO_IS_NOT_SUPPORTED(errno))
- return -EAGAIN; /* new mount API not available → don't know */
-
+ if (fd < 0)
return log_debug_errno(errno, "Failed to open superblock context for '%s': %m", fstype);
- }
- /* Various file systems have not been converted to the new mount API yet. For such file systems
+ /* Various file systems support fs context only in recent kernels (e.g. btrfs). For older kernels
* fsconfig() with FSCONFIG_SET_STRING/FSCONFIG_SET_FLAG never fail. Which sucks, because we want to
* use it for testing support, after all. Let's hence do a check if the file system got converted yet
* first. */
@@ -819,9 +815,9 @@ int mount_option_supported(const char *fstype, const char *key, const char *valu
* the new mount API yet. If it returns EINVAL the mount option doesn't exist, but the fstype
* is converted. */
if (errno == EOPNOTSUPP)
- return -EAGAIN; /* FSCONFIG_SET_FD not supported on the fs, hence not converted to new mount API → don't know */
+ return -EAGAIN; /* fs not converted to new mount API → don't know */
if (errno != EINVAL)
- return log_debug_errno(errno, "Failed to check if file system has been converted to new mount API: %m");
+ return log_debug_errno(errno, "Failed to check if file system '%s' has been converted to new mount API: %m", fstype);
/* So FSCONFIG_SET_FD worked, but the option didn't exist (we got EINVAL), this means the fs
* is converted. Let's now ask the actual question we wonder about. */

View File

@ -0,0 +1,30 @@
From 84f15472bcaa058a19bd63434cfb74eeb91d9cc6 Mon Sep 17 00:00:00 2001
From: Mike Yuan <me@yhndnzj.com>
Date: Tue, 11 Feb 2025 19:44:59 +0100
Subject: [PATCH] core/mount: log only once about fs not supporting new mount
API
(cherry picked from commit f565e5a94a778979172705e8c3a8581ec9f15865)
Related: RHEL-143028
---
src/core/mount.c | 6 ++++++
1 file changed, 6 insertions(+)
diff --git a/src/core/mount.c b/src/core/mount.c
index 037902c91c..530d567a96 100644
--- a/src/core/mount.c
+++ b/src/core/mount.c
@@ -1086,6 +1086,12 @@ static int mount_apply_graceful_options(Mount *m, const MountParameters *p, char
return r;
r = mount_option_supported(p->fstype, k ?: *o, v);
+ if (r == -EAGAIN) {
+ log_unit_warning_errno(UNIT(m), r,
+ "x-systemd.graceful-option= used but not supported by file system %s, suppressing all.",
+ p->fstype);
+ break;
+ }
if (r < 0)
log_unit_warning_errno(UNIT(m), r,
"x-systemd.graceful-option=%s specified, but cannot determine availability, suppressing: %m", *o);

View File

@ -0,0 +1,29 @@
From 94ce567ebb8175392a2dd7416b15e6561d100cbc Mon Sep 17 00:00:00 2001
From: Mike Yuan <me@yhndnzj.com>
Date: Mon, 3 Feb 2025 16:18:14 +0100
Subject: [PATCH] user-runtime-dir: correct quota size calculation
Follow-up for b1c95fb2e9d11fc190017dec3d64f468f9d378bc
Fixes #36245
(cherry picked from commit 6790db81d6bf59c34ca89f901b34e9f81cbde1a5)
Related: RHEL-143028
---
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 94117c95db..590b445d41 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;

View File

@ -0,0 +1,135 @@
From 73f66207da99d5f577a4be1cbcf34030e5b4a9b4 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= <zbyszek@in.waw.pl>
Date: Fri, 28 Mar 2025 18:45:23 +0100
Subject: [PATCH] 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.
(cherry picked from commit cb6b1611622ee717d4d6e9251fa41652b8ba8e2f)
Related: RHEL-143028
---
src/test/meson.build | 4 ++
src/test/test-display-quota.c | 90 +++++++++++++++++++++++++++++++++++
2 files changed, 94 insertions(+)
create mode 100644 src/test/test-display-quota.c
diff --git a/src/test/meson.build b/src/test/meson.build
index 5dd9e3b8bd..0f5de6a5d0 100644
--- a/src/test/meson.build
+++ b/src/test/meson.build
@@ -277,6 +277,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..57e8352826
--- /dev/null
+++ b/src/test/test-display-quota.c
@@ -0,0 +1,90 @@
+/* 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 "strv.h"
+#include "userdb.h"
+#include "user-util.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);

View File

@ -0,0 +1,61 @@
From e94a7c655a3493ddcd7bb68ac1b9112f8976e57c Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= <zbyszek@in.waw.pl>
Date: Sat, 29 Mar 2025 09:24:34 +0100
Subject: [PATCH] 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.
(cherry picked from commit cb86668f2cf7bc94ed28147fab7293b4795e6ab4)
Related: RHEL-143028
---
test/units/TEST-46-HOMED.sh | 27 +++++++++++++--------------
1 file changed, 13 insertions(+), 14 deletions(-)
diff --git a/test/units/TEST-46-HOMED.sh b/test/units/TEST-46-HOMED.sh
index 3663e53908..4af79e971c 100755
--- a/test/units/TEST-46-HOMED.sh
+++ b/test/units/TEST-46-HOMED.sh
@@ -652,21 +652,20 @@ 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 -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 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 rm /dev/shm/quotatestfile1
-
- systemctl stop user@"$(id -u tmpfsquota)".service
+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
- wait_for_state tmpfsquota inactive
- homectl remove tmpfsquota
-fi
+systemctl stop user@"$(id -u tmpfsquota)".service
+wait_for_state tmpfsquota inactive
+homectl remove tmpfsquota
systemd-analyze log-level info

View File

@ -0,0 +1,46 @@
From 05fcb907f1a80d1d1b6a2899a1499faf47aed5d3 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= <zbyszek@in.waw.pl>
Date: Mon, 31 Mar 2025 22:50:38 +0200
Subject: [PATCH] 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.
(cherry picked from commit f77a8edfefd47e481fab78e7dd75d5d088d4e5e3)
Related: RHEL-143028
---
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 4af79e971c..998a52c76a 100755
--- a/test/units/TEST-46-HOMED.sh
+++ b/test/units/TEST-46-HOMED.sh
@@ -655,11 +655,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

View File

@ -48,7 +48,7 @@ Url: https://systemd.io
# Allow users to specify the version and release when building the rpm by
# setting the %%version_override and %%release_override macros.
Version: %{?version_override}%{!?version_override:257}
Release: 23%{?dist}
Release: 24%{?dist}
%global stable %(c="%version"; [ "$c" = "${c#*.*}" ]; echo $?)
@ -719,6 +719,58 @@ Patch0606: 0606-TEST-35-LOGIN-test-coldplug-without-fdstore-on-kerne.patch
Patch0607: 0607-logind-fix-potential-fd-leak-in-deliver_session_lead.patch
Patch0608: 0608-Revert-coredump-lock-down-EnterNamespace-mount-even-.patch
Patch0609: 0609-coredump-add-compat-support-for-SYSTEMD_COREDUMP_ALL.patch
Patch0610: 0610-ci-re-enable-bpf-framework-option-for-build-and-unit.patch
Patch0611: 0611-ci-add-bpftool-workaround-to-codeql-job-too.patch
Patch0612: 0612-ci-fix-workaround-about-bpftool-for-codeql.patch
Patch0613: 0613-ci-add-bpftool-workaround-to-coverity-too.patch
Patch0614: 0614-Revert-man-mention-RHEL-documentation-in-systemctl-s.patch
Patch0615: 0615-path-util-add-flavour-of-path_startswith-that-leaves.patch
Patch0616: 0616-cgroup-port-some-code-over-to-path_startswith_full.patch
Patch0617: 0617-path-util-invert-PATH_STARTSWITH_ACCEPT_DOT_DOT-flag.patch
Patch0618: 0618-sd-json-fix-off-by-one-issue-when-updating-parent-fo.patch
Patch0619: 0619-core-cgroup-avoid-one-unnecessary-strjoina.patch
Patch0620: 0620-core-validate-input-cgroup-path-more-prudently.patch
Patch0621: 0621-nspawn-apply-BindUser-Ephemeral-from-settings-file-o.patch
Patch0622: 0622-nspawn-normalize-pivot_root-paths.patch
Patch0623: 0623-udev-check-for-invalid-chars-in-various-fields-recei.patch
Patch0624: 0624-udev-ensure-there-is-space-for-trailing-NUL-before-c.patch
Patch0625: 0625-udev-ensure-tag-parsing-stays-within-bounds.patch
Patch0626: 0626-udev-fix-review-mixup.patch
Patch0627: 0627-udev-scsi-id-check-for-invalid-chars-in-various-fiel.patch
Patch0628: 0628-udev-builtin-net-id-print-cescaped-bad-attributes.patch
Patch0629: 0629-core-only-activate-transaction-that-contain-useful-j.patch
Patch0630: 0630-journald-extend-STDOUT_STREAMS_MAX-to-64k.patch
Patch0631: 0631-string-util-introduce-strprepend-helper.patch
Patch0632: 0632-missing-add-quotactl_fd-wrapper.patch
Patch0633: 0633-homed-always-use-quotactl_fd-if-its-available.patch
Patch0634: 0634-pid1-add-GracefulOptions-setting-to-.mount-units.patch
Patch0635: 0635-pid1-enable-usrquota-support-on-dev-shm.patch
Patch0636: 0636-units-enable-usrquota-support-on-tmp.patch
Patch0637: 0637-nspawn-enable-usrquota-support-on-tmp-and-dev-shm.patch
Patch0638: 0638-nspawn-downgrade-log-message-about-usrquota-to-debug.patch
Patch0639: 0639-mount-setup-drop-outdated-comment.patch
Patch0640: 0640-mount-setup-tune-down-log-level-if-usrquota-is-not-s.patch
Patch0641: 0641-devnum-util-add-macros-to-safely-convert-dev_t-to-po.patch
Patch0642: 0642-user-record-add-fields-for-setting-limits-on-tmp-and.patch
Patch0643: 0643-user-runtime-dir-some-smaller-modernizations-refacto.patch
Patch0644: 0644-user-runtime-dir-enforce-tmp-and-dev-shm-quota.patch
Patch0645: 0645-homectl-add-support-for-configuring-tmpfs-limits.patch
Patch0646: 0646-test-add-test-case-for-tmpfs-quota-logic-PAMName-ask.patch
Patch0647: 0647-update-TODO.patch
Patch0648: 0648-core-dbus-service-fix-alignment.patch
Patch0649: 0649-core-mount-filter-out-fail-option-as-well.patch
Patch0650: 0650-core-mount-check-parameters_fragment-first-in-mount_.patch
Patch0651: 0651-core-mount-report-accurate-can_start-and-can_reload.patch
Patch0652: 0652-core-mount-trivial-coding-style-cleanups.patch
Patch0653: 0653-core-dbus-mount-add-missing-ReloadResult-and-CleanRe.patch
Patch0654: 0654-bus-unit-util-add-missing-assertions.patch
Patch0655: 0655-core-mount-rework-GracefulOptions-to-be-just-x-syste.patch
Patch0656: 0656-mountpoint-util-assume-fsopen-works-in-mount_option_.patch
Patch0657: 0657-core-mount-log-only-once-about-fs-not-supporting-new.patch
Patch0658: 0658-user-runtime-dir-correct-quota-size-calculation.patch
Patch0659: 0659-test-display-quota-add-a-little-helper-binary-to-sho.patch
Patch0660: 0660-TEST-46-HOMED-check-for-support-on-dev-shm-and-tmp-s.patch
Patch0661: 0661-TEST-46-HOMED-conditionally-skip-usrquota-tests.patch
# Downstream-only patches (90009999)
%endif
@ -1670,6 +1722,60 @@ rm -f .file-list-*
rm -f %{name}.lang
%changelog
* Thu Apr 16 2026 systemd maintenance team <systemd-maint@redhat.com> - 257-24
- ci: re-enable bpf-framework option for build and unit test jobs (RHEL-155454)
- ci: add bpftool workaround to codeql job too (RHEL-155454)
- ci: fix workaround about bpftool for codeql (RHEL-155454)
- ci: add bpftool workaround to coverity too (RHEL-155454)
- Revert "man: mention RHEL documentation in systemctl's man page" (RHEL-155805)
- path-util: add flavour of path_startswith() that leaves a leading slash in place (RHEL-155396)
- cgroup: port some code over to path_startswith_full() (RHEL-155396)
- path-util: invert PATH_STARTSWITH_ACCEPT_DOT_DOT flag (RHEL-155396)
- sd-json: fix off-by-one issue when updating parent for array elements (RHEL-155396)
- core/cgroup: avoid one unnecessary strjoina() (RHEL-155396)
- core: validate input cgroup path more prudently (RHEL-155396)
- nspawn: apply BindUser/Ephemeral from settings file only if trusted (RHEL-158303)
- nspawn: normalize pivot_root paths (RHEL-158303)
- udev: check for invalid chars in various fields received from the kernel (RHEL-158354)
- udev: ensure there is space for trailing NUL before calling sprintf (RHEL-158354)
- udev: ensure tag parsing stays within bounds (RHEL-158354)
- udev: fix review mixup (RHEL-158354)
- udev/scsi-id: check for invalid chars in various fields received from the kernel (RHEL-158354)
- udev-builtin-net-id: print cescaped bad attributes (RHEL-158354)
- core: only activate transaction that contain useful jobs (RHEL-143728)
- journald: extend STDOUT_STREAMS_MAX to 64k (RHEL-168098)
- string-util: introduce strprepend() helper (RHEL-143028)
- missing: add quotactl_fd() wrapper (RHEL-143028)
- homed: always use quotactl_fd() if its available (RHEL-143028)
- pid1: add GracefulOptions= setting to .mount units (RHEL-143028)
- pid1: enable usrquota support on /dev/shm (RHEL-143028)
- units: enable usrquota support on /tmp/ (RHEL-143028)
- nspawn: enable usrquota support on /tmp/ and /dev/shm/ (RHEL-143028)
- nspawn: downgrade log message about usrquota to debug (RHEL-143028)
- mount-setup: drop outdated comment (RHEL-143028)
- mount-setup: tune down log level if usrquota is not supported, apply usrquota when smack is in use too (RHEL-143028)
- devnum-util: add macros to safely convert dev_t to pointers and back (RHEL-143028)
- user-record: add fields for setting limits on /tmp/ and /dev/shm/ (RHEL-143028)
- user-runtime-dir: some smaller modernizations/refactorings (RHEL-143028)
- user-runtime-dir: enforce /tmp/ and /dev/shm/ quota (RHEL-143028)
- homectl: add support for configuring tmpfs limits (RHEL-143028)
- test: add test case for tmpfs quota logic + PAMName= ask-password logic (RHEL-143028)
- update TODO (RHEL-143028)
- core/dbus-service: fix alignment (RHEL-143028)
- core/mount: filter out "fail" option as well (RHEL-143028)
- core/mount: check parameters_fragment first in mount_enter_(re)mounting() (RHEL-143028)
- core/mount: report accurate can_start and can_reload (RHEL-143028)
- core/mount: trivial coding style cleanups (RHEL-143028)
- core/dbus-mount: add missing ReloadResult and CleanResult properties (RHEL-143028)
- bus-unit-util: add missing assertions (RHEL-143028)
- core/mount: rework GracefulOptions= to be just x-systemd.graceful-option= (RHEL-143028)
- mountpoint-util: assume fsopen() works in mount_option_supported() (RHEL-143028)
- core/mount: log only once about fs not supporting new mount API (RHEL-143028)
- user-runtime-dir: correct quota size calculation (RHEL-143028)
- test-display-quota: add a little helper binary to show quota on tmpfs (RHEL-143028)
- TEST-46-HOMED: check for support on /dev/shm and /tmp separately (RHEL-143028)
- TEST-46-HOMED: conditionally skip usrquota tests (RHEL-143028)
* Tue Feb 17 2026 systemd maintenance team <systemd-maint@redhat.com> - 257-23
- test-journal-dump: dump the headers of journal files (RHEL-106795)
- journal: store counts, not byte sizes, in table size constants (RHEL-106795)