diff mbox series

[v5,2/2] systemd-tools-native: fix systemctl/hwdb --root on RHEL 8 hosts

Message ID 20260722131640.4155716-3-daniel.turull@ericsson.com
State Under Review
Headers show
Series systemd: upgrade to 261.1 and be compatible with rhel8 | expand

Commit Message

Daniel Turull July 22, 2026, 1:16 p.m. UTC
From: Daniel Turull <daniel.turull@ericsson.com>

The native systemctl and systemd-hwdb tools run offline against the
target rootfs (--root $D) during do_rootfs. On build hosts with a
kernel older than 5.8 (e.g. RHEL 8, kernel 4.18) statx() does not
support STATX_MNT_ID, so path resolution fails with -EUNATCH. This
breaks the offline "systemctl --root" enable/preset invoked by the
postinst of any package shipping a systemd service, e.g.:

  Failed to preset all units: Protocol driver not attached.

which in turn fails do_rootfs:

  ERROR: Postinstall scriptlets of ['busybox-syslog',
  'systemd-networkd'] have failed. [...] Deferring to first boot via
  'exit 1' is no longer supported.

- Add Handle-missing-STATX_MNT_ID-on-older-kernels.patch, which reads
  mnt_id from /proc/self/fdinfo (available since kernel 3.15) as a
  fallback when statx() lacks STATX_MNT_ID, restoring the behaviour
  removed upstream after systemd 259
- Apply it only to systemd-tools-native, where /proc/self/fdinfo is
  guaranteed available; the target systemd recipe is untouched

Tested on rhel8.10, qemux86-64 with systemd testsuite.

RESULTS:
RESULTS - ping.PingTest.test_ping: PASSED (0.00s)
RESULTS - ssh.SSHTest.test_ssh: PASSED (0.48s)
RESULTS - systemd.SystemdBasicTests.test_systemd_basic: PASSED (0.48s)
RESULTS - systemd.SystemdBasicTests.test_systemd_failed: PASSED (0.95s)
RESULTS - systemd.SystemdBasicTests.test_systemd_list: PASSED (0.66s)
RESULTS - systemd.SystemdJournalTests.test_systemd_boot_time: PASSED (0.68s)
RESULTS - systemd.SystemdJournalTests.test_systemd_journal: PASSED (0.68s)
RESULTS - systemd.SystemdServiceTests.test_systemd_coredump_minidebuginfo: SKIPPED (0.00s)
RESULTS - systemd.SystemdServiceTests.test_systemd_disable_enable: SKIPPED (0.00s)
RESULTS - systemd.SystemdServiceTests.test_systemd_disable_enable_ro: SKIPPED (0.00s)
RESULTS - systemd.SystemdServiceTests.test_systemd_status: SKIPPED (0.00s)
RESULTS - systemd.SystemdServiceTests.test_systemd_stop_start: SKIPPED (0.00s)
SUMMARY:
core-image-minimal () - Ran 12 tests in 3.923s
core-image-minimal - OK - All required tests passed (successes=7, skipped=5, failures=0, errors=0)

Assisted-by: kiro:claude-opus-4.8
Signed-off-by: Daniel Turull <daniel.turull@ericsson.com>
---
 .../systemd/systemd-tools-native_261.1.bb     |   3 +
 ...issing-STATX_MNT_ID-on-older-kernels.patch | 160 ++++++++++++++++++
 2 files changed, 163 insertions(+)
 create mode 100644 meta/recipes-core/systemd/systemd/Handle-missing-STATX_MNT_ID-on-older-kernels.patch
diff mbox series

Patch

diff --git a/meta/recipes-core/systemd/systemd-tools-native_261.1.bb b/meta/recipes-core/systemd/systemd-tools-native_261.1.bb
index 744e874a1a..4b088afc9f 100644
--- a/meta/recipes-core/systemd/systemd-tools-native_261.1.bb
+++ b/meta/recipes-core/systemd/systemd-tools-native_261.1.bb
@@ -6,6 +6,9 @@  SUMMARY = "native tools from systemd"
 
 SRC_URI += "file://0001-hwdb-strip-the-root-from-filenames-when-generating-h.patch"
 
+# TODO: Remove STATX_MNT_ID patch once minimum supported build host kernel is >= 5.8 (RHEL 8 EOL: 2029)
+SRC_URI += "file://Handle-missing-STATX_MNT_ID-on-older-kernels.patch"
+
 # We don't actually need jinja to generate code, but it's checked for at configure time
 DEPENDS = "gperf-native python3-jinja2-native"
 
diff --git a/meta/recipes-core/systemd/systemd/Handle-missing-STATX_MNT_ID-on-older-kernels.patch b/meta/recipes-core/systemd/systemd/Handle-missing-STATX_MNT_ID-on-older-kernels.patch
new file mode 100644
index 0000000000..9d74872034
--- /dev/null
+++ b/meta/recipes-core/systemd/systemd/Handle-missing-STATX_MNT_ID-on-older-kernels.patch
@@ -0,0 +1,160 @@ 
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: Daniel Turull <daniel.turull@ericsson.com>
+Date: Mon, 23 Jun 2026 12:00:00 +0200
+Subject: [PATCH] Handle missing STATX_MNT_ID on older kernels
+
+On hosts lacking STATX_MNT_ID (kernel < 5.8, e.g. RHEL 8), native tools
+(systemctl --root, systemd-hwdb --root) fail during path resolution with
+-EUNATCH. Fix by adding fd_get_mount_id() to read mnt_id from
+/proc/self/fdinfo (available since kernel 3.15) and using it as a fallback
+when statx() does not return STATX_MNT_ID in fds_inode_and_mount_same() and
+chase_statx().
+
+This restores the /proc/self/fdinfo fallback that existed in systemd 259
+(fd_fdinfo_mnt_id in mountpoint-util.c) but was removed upstream in 260+.
+
+This patch is only applied to the native tools recipe
+(systemd-tools-native) where /proc/self/fdinfo is guaranteed available.
+Do NOT apply to the target systemd recipe.
+
+Upstream-Status: Inappropriate [oe specific]
+
+Assisted-by: kiro:claude-opus-4.6
+Signed-off-by: Daniel Turull <daniel.turull@ericsson.com>
+---
+ src/basic/chase.c   | 20 ++++++++++++++-
+ src/basic/fd-util.c | 63 +++++++++++++++++++++++++++++++++++++++++++--
+ src/basic/fd-util.h |  1 +
+ 3 files changed, 82 insertions(+), 2 deletions(-)
+
+--- a/src/basic/fd-util.h	2026-06-25 14:01:12.009875526 +0200
++++ b/src/basic/fd-util.h	2026-06-25 14:01:20.909060415 +0200
+@@ -188,6 +188,7 @@ static inline int dir_fd_is_root_or_cwd(
+ }
+ 
+ int fds_inode_and_mount_same(int fd1, int fd2);
++int fd_get_mount_id(int fd, uint64_t *ret);
+ 
+ int resolve_xat_fdroot(int *fd, const char **path, char **ret_buffer);
+ 
+--- a/src/basic/fd-util.c	2026-06-25 14:01:12.011875567 +0200
++++ b/src/basic/fd-util.c	2026-06-25 14:01:40.007456905 +0200
+@@ -1082,6 +1082,38 @@ int path_is_root_at(int dir_fd, const ch
+         return fds_inode_and_mount_same(dir_fd, XAT_FDROOT);
+ }
+ 
++int fd_get_mount_id(int fd, uint64_t *ret) {
++        char path[STRLEN("/proc/self/fdinfo/") + DECIMAL_STR_MAX(int)];
++        _cleanup_close_ int real_fd = -EBADF;
++        _cleanup_free_ char *p = NULL;
++        uint64_t mnt_id;
++        int r;
++
++        assert(ret);
++
++        /* /proc/self/fdinfo/ requires a real fd; resolve AT_FDCWD/XAT_FDROOT via O_PATH. */
++        if (fd == AT_FDCWD || fd == XAT_FDROOT) {
++                real_fd = open(fd == XAT_FDROOT ? "/" : ".", O_PATH|O_CLOEXEC);
++                if (real_fd < 0)
++                        return -errno;
++                fd = real_fd;
++        }
++
++        assert(fd >= 0);
++        xsprintf(path, "/proc/self/fdinfo/%i", fd);
++
++        r = get_proc_field(path, "mnt_id", &p);
++        if (r < 0)
++                return r;
++
++        r = safe_atou64(p, &mnt_id);
++        if (r < 0)
++                return r;
++
++        *ret = mnt_id;
++        return 0;
++}
++
+ int fds_inode_and_mount_same(int fd1, int fd2) {
+         struct statx sx1, sx2;
+         int r;
+@@ -1092,7 +1124,20 @@ int fds_inode_and_mount_same(int fd1, in
+         r = xstatx(fd1, /* path = */ NULL, AT_EMPTY_PATH,
+                    STATX_TYPE|STATX_INO|STATX_MNT_ID,
+                    &sx1);
+-        if (r < 0)
++        if (r == -EUNATCH) {
++                uint64_t mnt_id;
++
++                /* Kernel lacks STATX_MNT_ID; fall back to /proc/self/fdinfo. */
++                r = xstatx(fd1, /* path = */ NULL, AT_EMPTY_PATH,
++                           STATX_TYPE|STATX_INO, &sx1);
++                if (r < 0)
++                        return r;
++                r = fd_get_mount_id(fd1, &mnt_id);
++                if (r < 0)
++                        return r;
++                sx1.stx_mnt_id = mnt_id;
++                sx1.stx_mask |= STATX_MNT_ID;
++        } else if (r < 0)
+                 return r;
+ 
+         if (fd1 == fd2) /* Shortcut things if fds are the same (only after validating the fd) */
+@@ -1101,7 +1146,19 @@ int fds_inode_and_mount_same(int fd1, in
+         r = xstatx(fd2, /* path = */ NULL, AT_EMPTY_PATH,
+                    STATX_TYPE|STATX_INO|STATX_MNT_ID,
+                    &sx2);
+-        if (r < 0)
++        if (r == -EUNATCH) {
++                uint64_t mnt_id;
++
++                r = xstatx(fd2, /* path = */ NULL, AT_EMPTY_PATH,
++                           STATX_TYPE|STATX_INO, &sx2);
++                if (r < 0)
++                        return r;
++                r = fd_get_mount_id(fd2, &mnt_id);
++                if (r < 0)
++                        return r;
++                sx2.stx_mnt_id = mnt_id;
++                sx2.stx_mask |= STATX_MNT_ID;
++        } else if (r < 0)
+                 return r;
+ 
+         r = statx_mount_same(&sx1, &sx2);
+--- a/src/basic/chase.c	2026-06-25 14:01:12.013875609 +0200
++++ b/src/basic/chase.c	2026-06-25 14:01:47.117604514 +0200
+@@ -40,7 +40,9 @@
+         (CHASE_MUST_BE_DIRECTORY|CHASE_MUST_BE_REGULAR|CHASE_MUST_BE_SOCKET)
+ 
+ static int chase_statx(int fd, struct statx *ret) {
+-        return xstatx_full(fd,
++        int r;
++
++        r = xstatx_full(fd,
+                         /* path= */ NULL,
+                         /* statx_flags= */ 0,
+                         XSTATX_MNT_ID_BEST,
+@@ -48,6 +50,23 @@ static int chase_statx(int fd, struct st
+                         /* optional_mask= */ 0,
+                         /* mandatory_attributes= */ 0,
+                         ret);
++        if (r == -EUNATCH) {
++                uint64_t mnt_id;
++
++                /* Kernel lacks STATX_MNT_ID; fall back to /proc/self/fdinfo. */
++                r = xstatx(fd, /* path= */ NULL, /* statx_flags= */ 0,
++                           STATX_TYPE|STATX_UID|STATX_INO,
++                           ret);
++                if (r < 0)
++                        return r;
++                r = fd_get_mount_id(fd, &mnt_id);
++                if (r < 0)
++                        return r;
++                ret->stx_mnt_id = mnt_id;
++                ret->stx_mask |= STATX_MNT_ID;
++        }
++
++        return r;
+ }
+ 
+ static int chase_openat2(int root_fd, int dir_fd, const char *path, ChaseFlags chase_flags) {