diff mbox series

[3/3] systemd: add native hwdb generator for hosts without STATX_MNT_ID

Message ID 20260625134210.4046622-4-daniel.turull@ericsson.com
State New
Headers show
Series systemd: update to 261 and be compatible with rhel8 | expand

Commit Message

Daniel Turull June 25, 2026, 1:41 p.m. UTC
From: Daniel Turull <daniel.turull@ericsson.com>

systemd 261 requires STATX_MNT_ID (kernel >= 5.8) for path resolution.
On older hosts (e.g. RHEL 8 with kernel 4.18), the QEMU-emulated
udevadm hwdb fails during image construction.

Add systemd-hwdb-native recipe that builds systemd-hwdb natively with:
- A patch restoring /proc/self/fdinfo mount-ID fallback for kernels
  lacking STATX_MNT_ID (applied only to native recipes)
- A patch forcing compat mode in hwdb generation to avoid embedding
  build-host paths in hwdb.bin (reproducibility)

Update the update_udev_hwdb intercept to prefer the native
systemd-hwdb over QEMU emulation, with a test -s check to catch
silent failures from either path.

Tested on RHEL 8.10 and Ubuntu 22.04.5

AI-Generated: Claude-opus-4.6
Signed-off-by: Daniel Turull <daniel.turull@ericsson.com>

---
I'm not sure who should be the maitainer of the new native recipe.
is it Qi Chen, who is the maintainer for the rest of systemd recipes,
unassigned or me? The question I'm asking is because all need to be
updated at the same time.
---
 meta/conf/distro/include/maintainers.inc      |   1 +
 .../systemd/systemd-hwdb-native_261.bb        |  32 ++++
 .../systemd/systemd-systemctl-native_261.bb   |   3 +
 ...idfd_open-and-STATX_MNT_ID-on-older-.patch | 176 ++++++++++++++++++
 ...t-mode-for-reproducible-cross-builds.patch |  36 ++++
 meta/recipes-core/systemd/systemd_261.bb      |   2 +-
 scripts/postinst-intercepts/update_udev_hwdb  |  24 ++-
 7 files changed, 269 insertions(+), 5 deletions(-)
 create mode 100644 meta/recipes-core/systemd/systemd-hwdb-native_261.bb
 create mode 100644 meta/recipes-core/systemd/systemd/Handle-missing-pidfd_open-and-STATX_MNT_ID-on-older-.patch
 create mode 100644 meta/recipes-core/systemd/systemd/hwdb-use-compat-mode-for-reproducible-cross-builds.patch
diff mbox series

Patch

diff --git a/meta/conf/distro/include/maintainers.inc b/meta/conf/distro/include/maintainers.inc
index f757fafdcb..86bf4d14ee 100644
--- a/meta/conf/distro/include/maintainers.inc
+++ b/meta/conf/distro/include/maintainers.inc
@@ -831,6 +831,7 @@  RECIPE_MAINTAINER:pn-systemd-boot-native = "Viswanath Kraleti <quic_vkraleti@qui
 RECIPE_MAINTAINER:pn-systemd-bootchart = "Chen Qi <Qi.Chen@windriver.com>"
 RECIPE_MAINTAINER:pn-systemd-bootconf = "Chen Qi <Qi.Chen@windriver.com>"
 RECIPE_MAINTAINER:pn-systemd-conf = "Chen Qi <Qi.Chen@windriver.com>"
+RECIPE_MAINTAINER:pn-systemd-hwdb-native = "Unassigned <unassigned@yoctoproject.org>"
 RECIPE_MAINTAINER:pn-systemd-machine-units = "Chen Qi <Qi.Chen@windriver.com>"
 RECIPE_MAINTAINER:pn-systemd-serialgetty = "Chen Qi <Qi.Chen@windriver.com>"
 RECIPE_MAINTAINER:pn-systemd-systemctl-native = "Chen Qi <Qi.Chen@windriver.com>"
diff --git a/meta/recipes-core/systemd/systemd-hwdb-native_261.bb b/meta/recipes-core/systemd/systemd-hwdb-native_261.bb
new file mode 100644
index 0000000000..0b3ff2ba03
--- /dev/null
+++ b/meta/recipes-core/systemd/systemd-hwdb-native_261.bb
@@ -0,0 +1,32 @@ 
+# SPDX-License-Identifier: MIT
+FILESEXTRAPATHS:prepend := "${THISDIR}/systemd:"
+
+SUMMARY = "Hardware database management tool from systemd"
+
+require systemd.inc
+
+DEPENDS = "gperf-native libcap-native util-linux-native python3-jinja2-native"
+
+# TODO: Remove STATX_MNT_ID patch once minimum supported build host kernel is >= 5.8 (RHEL 8 EOL: 2029)
+SRC_URI += "file://Handle-missing-pidfd_open-and-STATX_MNT_ID-on-older-.patch \
+            file://hwdb-use-compat-mode-for-reproducible-cross-builds.patch \
+           "
+
+inherit pkgconfig meson native
+
+MESON_TARGET = "systemd-hwdb:executable"
+
+# Override prefix so compiled-in UDEVLIBEXECDIR (/usr/lib/udev) matches the
+# target rootfs layout. This allows --root $D --usr to find hwdb.d source
+# files and write hwdb.bin to the correct location.
+EXTRA_OEMESON += "--prefix /usr"
+EXTRA_OEMESON += "-Dhwdb=true -Dlink-udev-shared=false"
+EXTRA_OEMESON += "-Dpam=disabled -Daudit=disabled -Dselinux=disabled"
+EXTRA_OEMESON += "-Dacl=disabled -Dapparmor=disabled -Dseccomp=disabled"
+EXTRA_OEMESON += "-Dlibcryptsetup=disabled -Dlibcurl=disabled -Dlibfido2=disabled"
+EXTRA_OEMESON += "-Dpcre2=disabled -Dp11kit=disabled -Dopenssl=disabled"
+
+do_install() {
+    install -d ${D}${bindir}
+    install -m 0755 ${B}/systemd-hwdb ${D}${bindir}/systemd-hwdb
+}
diff --git a/meta/recipes-core/systemd/systemd-systemctl-native_261.bb b/meta/recipes-core/systemd/systemd-systemctl-native_261.bb
index 686448cf1e..a6ad6901e0 100644
--- a/meta/recipes-core/systemd/systemd-systemctl-native_261.bb
+++ b/meta/recipes-core/systemd/systemd-systemctl-native_261.bb
@@ -6,6 +6,9 @@  require systemd.inc
 
 DEPENDS = "gperf-native libcap-native util-linux-native python3-jinja2-native"
 
+# TODO: Remove STATX_MNT_ID patch once minimum supported build host kernel is >= 5.8 (RHEL 8 EOL: 2029)
+SRC_URI += "file://Handle-missing-pidfd_open-and-STATX_MNT_ID-on-older-.patch"
+
 inherit pkgconfig meson native
 
 MESON_TARGET = "systemctl:executable"
diff --git a/meta/recipes-core/systemd/systemd/Handle-missing-pidfd_open-and-STATX_MNT_ID-on-older-.patch b/meta/recipes-core/systemd/systemd/Handle-missing-pidfd_open-and-STATX_MNT_ID-on-older-.patch
new file mode 100644
index 0000000000..c63423cc96
--- /dev/null
+++ b/meta/recipes-core/systemd/systemd/Handle-missing-pidfd_open-and-STATX_MNT_ID-on-older-.patch
@@ -0,0 +1,176 @@ 
+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 pidfd_open and STATX_MNT_ID on older kernels
+
+On hosts lacking pidfd_open (kernel < 5.3) or STATX_MNT_ID (kernel < 5.8,
+e.g. RHEL 8), native tools (systemctl --root, systemd-hwdb --root) fail
+during path resolution. Fix by:
+
+- Treating ENOSYS/EOPNOTSUPP from pidfd_open as graceful fallback.
+- Adding fd_get_mount_id() to read mnt_id from /proc/self/fdinfo (available
+  since kernel 3.15) and using it as fallback when statx returns -EUNATCH 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 native recipes (systemd-systemctl-native,
+systemd-hwdb-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 +
+ src/basic/pidref.c  |  4 +--
+ 4 files changed, 83 insertions(+), 5 deletions(-)
+
+--- a/src/basic/pidref.c	2026-06-25 14:01:12.007875484 +0200
++++ b/src/basic/pidref.c	2026-06-25 14:01:55.098770206 +0200
+@@ -106,8 +106,8 @@ int pidref_set_pid(PidRef *pidref, pid_t
+ 
+         fd = pidfd_open(pid, 0);
+         if (fd < 0) {
+-                /* Graceful fallback in case the kernel is out of fds */
+-                if (!ERRNO_IS_RESOURCE(errno))
++                /* Graceful fallback in case the kernel is out of fds or lacks pidfd support */
++                if (!ERRNO_IS_RESOURCE(errno) && !ERRNO_IS_NOT_SUPPORTED(errno))
+                         return log_debug_errno(errno, "Failed to open pidfd for pid " PID_FMT ": %m", pid);
+ 
+                 fd = -EBADF;
+--- 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) {
diff --git a/meta/recipes-core/systemd/systemd/hwdb-use-compat-mode-for-reproducible-cross-builds.patch b/meta/recipes-core/systemd/systemd/hwdb-use-compat-mode-for-reproducible-cross-builds.patch
new file mode 100644
index 0000000000..bb90105cbd
--- /dev/null
+++ b/meta/recipes-core/systemd/systemd/hwdb-use-compat-mode-for-reproducible-cross-builds.patch
@@ -0,0 +1,36 @@ 
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: Daniel Turull <daniel.turull@ericsson.com>
+Date: Wed, 25 Jun 2026 10:00:00 +0200
+Subject: [PATCH] hwdb: use compat mode to avoid embedding source paths
+
+Use compat=true in systemd-hwdb's verb_update() so that source
+filenames, line numbers, and priorities are not embedded in hwdb.bin.
+
+Without this, when --root $D is used during cross-compilation, the
+absolute build paths (e.g. /tmp/work/.../rootfs/usr/lib/udev/hwdb.d/...)
+are written into the database, causing:
+- Non-reproducible builds (different TMPDIR → different hwdb.bin)
+- Build directory path leakage into the target image
+
+The compat format matches what udevadm hwdb (the deprecated path)
+has always produced, and is the expected format for cross-built images.
+
+Upstream-Status: Inappropriate [oe specific]
+
+AI-Generated: Claude Opus 4.6
+Signed-off-by: Daniel Turull <daniel.turull@ericsson.com>
+---
+ src/hwdb/hwdb.c | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+--- a/src/hwdb/hwdb.c
++++ b/src/hwdb/hwdb.c
+@@ -27,7 +27,7 @@ static int verb_update(int argc, char *argv[], uintptr_t _data, void *userdata)
+         if (hwdb_bypass())
+                 return 0;
+ 
+-        return hwdb_update(arg_root, arg_hwdb_bin_dir, arg_strict, false);
++        return hwdb_update(arg_root, arg_hwdb_bin_dir, arg_strict, true);
+ }
+ 
+ static int help(void) {
diff --git a/meta/recipes-core/systemd/systemd_261.bb b/meta/recipes-core/systemd/systemd_261.bb
index eedce348c3..22bd4ca5fc 100644
--- a/meta/recipes-core/systemd/systemd_261.bb
+++ b/meta/recipes-core/systemd/systemd_261.bb
@@ -910,7 +910,7 @@  pkg_prerm:${PN}:libc-glibc () {
 	fi
 }
 
-PACKAGE_WRITE_DEPS += "qemuwrapper-cross"
+PACKAGE_WRITE_DEPS += "qemuwrapper-cross systemd-hwdb-native"
 
 pkg_postinst:udev-hwdb () {
 	if test -n "$D"; then
diff --git a/scripts/postinst-intercepts/update_udev_hwdb b/scripts/postinst-intercepts/update_udev_hwdb
index 8b3f5de791..d7a4ffc294 100644
--- a/scripts/postinst-intercepts/update_udev_hwdb
+++ b/scripts/postinst-intercepts/update_udev_hwdb
@@ -19,7 +19,23 @@  case "${PREFERRED_PROVIDER_udev}" in
 		;;
 esac
 
-rm -f $D${UDEVLIBDIR}/udev/hwdb.bin
-PSEUDO_UNLOAD=1 ${binprefix}qemuwrapper -L $D $D${UDEVADM} hwdb --update --root $D ${UDEV_EXTRA_ARGS} ||
-	PSEUDO_UNLOAD=1 qemuwrapper -L $D $D${UDEVADM} hwdb --update --root $D ${UDEV_EXTRA_ARGS}
-chown root:root $D${UDEVLIBDIR}/udev/hwdb.bin
+hwdb_bin="$D${UDEVLIBDIR}/udev/hwdb.bin"
+rm -f "$hwdb_bin"
+
+# Use native systemd-hwdb to generate hwdb.bin at build time.
+# This avoids QEMU user-mode emulation and works on host kernels < 5.8
+# (e.g. RHEL 8) where systemd 261+ would fail due to missing STATX_MNT_ID.
+NATIVE_HWDB="${STAGING_DIR_NATIVE}/usr/bin/systemd-hwdb"
+if test -x "$NATIVE_HWDB" && test "${PREFERRED_PROVIDER_udev}" = "systemd"; then
+	PSEUDO_UNLOAD=1 $NATIVE_HWDB update --root $D ${UDEV_EXTRA_ARGS}
+else
+	PSEUDO_UNLOAD=1 ${binprefix}qemuwrapper -L $D $D${UDEVADM} hwdb --update --root $D ${UDEV_EXTRA_ARGS} ||
+		PSEUDO_UNLOAD=1 qemuwrapper -L $D $D${UDEVADM} hwdb --update --root $D ${UDEV_EXTRA_ARGS}
+fi
+
+if ! test -s "$hwdb_bin"; then
+	echo "ERROR: hwdb.bin was not created at $hwdb_bin" >&2
+	echo "The hwdb generation command exited successfully but produced no output." >&2
+	exit 1
+fi
+chown root:root "$hwdb_bin"