diff --git a/sdk_container/src/third_party/coreos-overlay/coreos/user-patches/sys-kernel/dracut/001-dracut-post-106.patch b/sdk_container/src/third_party/coreos-overlay/coreos/user-patches/sys-kernel/dracut/001-dracut-post-106.patch new file mode 100644 index 0000000000..a9811e6dd8 --- /dev/null +++ b/sdk_container/src/third_party/coreos-overlay/coreos/user-patches/sys-kernel/dracut/001-dracut-post-106.patch @@ -0,0 +1,980 @@ +From 62c75393ea18b65ba0f7f224070c3bb94d3bd930 Mon Sep 17 00:00:00 2001 +From: Jo Zzsi +Date: Fri, 7 Feb 2025 20:24:39 -0500 +Subject: [PATCH 01/22] fix(systemd-sysusers): always silence stdout + +systemd-sysusers does not have quiet option, so +always silence stdout (but not stderr). + +Fixes: https://github.com/dracut-ng/dracut-ng/issues/1195 +--- + modules.d/60systemd-sysusers/module-setup.sh | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/modules.d/60systemd-sysusers/module-setup.sh b/modules.d/60systemd-sysusers/module-setup.sh +index 05680553..977695e6 100755 +--- a/modules.d/60systemd-sysusers/module-setup.sh ++++ b/modules.d/60systemd-sysusers/module-setup.sh +@@ -15,5 +15,5 @@ check() { + install() { + inst_sysusers basic.conf + +- systemd-sysusers --root="$initdir" ++ systemd-sysusers --root="$initdir" > /dev/null + } +-- +2.48.1 + + +From 9b822c31e3c096a276904c0d6ebfd379ec443e23 Mon Sep 17 00:00:00 2001 +From: Brian Fjeldstad +Date: Tue, 4 Feb 2025 22:09:04 +0000 +Subject: [PATCH 02/22] fix(dracut): avoid mktemp collisions with find filter + +--- + dracut.sh | 4 ++-- + 1 file changed, 2 insertions(+), 2 deletions(-) + +diff --git a/dracut.sh b/dracut.sh +index 88b14f3e..ef959021 100755 +--- a/dracut.sh ++++ b/dracut.sh +@@ -1324,10 +1324,10 @@ if findmnt --raw -n --target "$tmpdir" --output=options | grep -q noexec; then + noexec=1 + fi + +-DRACUT_TMPDIR="$(mktemp -p "$TMPDIR/" -d -t dracut.XXXXXX)" ++DRACUT_TMPDIR="$(mktemp -p "$TMPDIR/" -d -t dracut.dXXXXXX)" + readonly DRACUT_TMPDIR + [ -d "$DRACUT_TMPDIR" ] || { +- printf "%s\n" "dracut[F]: mktemp -p '$TMPDIR/' -d -t dracut.XXXXXX failed." >&2 ++ printf "%s\n" "dracut[F]: mktemp -p '$TMPDIR/' -d -t dracut.dXXXXXX failed." >&2 + exit 1 + } + +-- +2.48.1 + + +From 89da4257a6ffa737a69f7095bb41d5ae3f247d82 Mon Sep 17 00:00:00 2001 +From: Benjamin Drung +Date: Wed, 12 Feb 2025 11:10:30 +0100 +Subject: [PATCH 03/22] fix(dracut-lib): support "set -e" in setdebug + +A `return` statement will return with the exit code of the previous +command if no exit code is specified. In case `/usr/lib/initrd-release` +does not exist, `setdebug` will return with the exit code 1. + +Return this function with code 0 in that case to support `set -e` users. + +Fixes: 2b125c69cc80 ("base/dracut-lib.sh: do not setdebug, if not in initramfs") +--- + modules.d/99base/dracut-lib.sh | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/modules.d/99base/dracut-lib.sh b/modules.d/99base/dracut-lib.sh +index acedea98..05c361c6 100755 +--- a/modules.d/99base/dracut-lib.sh ++++ b/modules.d/99base/dracut-lib.sh +@@ -340,7 +340,7 @@ splitsep() { + } + + setdebug() { +- [ -f /usr/lib/initrd-release ] || return ++ [ -f /usr/lib/initrd-release ] || return 0 + if [ -z "$RD_DEBUG" ]; then + if [ -e /proc/cmdline ]; then + RD_DEBUG=no +-- +2.48.1 + + +From 57911e76e2826fa6d9f2b80915cf99c6eb0e05b0 Mon Sep 17 00:00:00 2001 +From: You-Sheng Yang +Date: Wed, 22 Jan 2025 23:37:53 +0800 +Subject: [PATCH 04/22] fix(dracut-install): install compressed blobs that + match wildcard fwpath + +dracut-install tries to invoke `glob()` with full path expanded from +"intel/ish/ish_*.bin", but while all the binaries were compressed, this +matches no file and none of the custom ISH firmware blobs will be +installed. + +Closes: #1150 +Bug-Ubuntu: https://bugs.launchpad.net/bugs/2095518 +Signed-off-by: You-Sheng Yang +--- + src/install/dracut-install.c | 42 +++++++++++++++++++++++++++--------- + 1 file changed, 32 insertions(+), 10 deletions(-) + +diff --git a/src/install/dracut-install.c b/src/install/dracut-install.c +index 96bc2eb6..bacbe86e 100644 +--- a/src/install/dracut-install.c ++++ b/src/install/dracut-install.c +@@ -1437,12 +1437,15 @@ static int install_all(int argc, char **argv) + return r; + } + +-static int install_firmware_fullpath(const char *fwpath) ++static int install_firmware_fullpath(const char *fwpath, bool maybe_compressed) + { + const char *fw = fwpath; + _cleanup_free_ char *fwpath_compressed = NULL; + int ret; + if (access(fwpath, F_OK) != 0) { ++ if (!maybe_compressed) ++ return 1; ++ + _asprintf(&fwpath_compressed, "%s.zst", fwpath); + if (access(fwpath_compressed, F_OK) != 0) { + strcpy(fwpath_compressed + strlen(fwpath) + 1, "xz"); +@@ -1460,6 +1463,23 @@ static int install_firmware_fullpath(const char *fwpath) + return ret; + } + ++static bool install_firmware_glob(const char *fwpath) ++{ ++ size_t i; ++ _cleanup_globfree_ glob_t globbuf; ++ bool found = false; ++ int ret; ++ ++ glob(fwpath, 0, NULL, &globbuf); ++ for (i = 0; i < globbuf.gl_pathc; i++) { ++ ret = install_firmware_fullpath(globbuf.gl_pathv[i], false); ++ if (ret == 0) ++ found = true; ++ } ++ ++ return found; ++} ++ + static int install_firmware(struct kmod_module *mod) + { + struct kmod_list *l = NULL; +@@ -1490,17 +1510,19 @@ static int install_firmware(struct kmod_module *mod) + + if (strpbrk(value, "*?[") != NULL + && access(fwpath, F_OK) != 0) { +- size_t i; +- _cleanup_globfree_ glob_t globbuf; +- +- glob(fwpath, 0, NULL, &globbuf); +- for (i = 0; i < globbuf.gl_pathc; i++) { +- ret = install_firmware_fullpath(globbuf.gl_pathv[i]); +- if (ret == 0) +- found_this = true; ++ found_this = install_firmware_glob(fwpath); ++ if (!found_this) { ++ _cleanup_free_ char *fwpath_compressed = NULL; ++ ++ _asprintf(&fwpath_compressed, "%s.zst", fwpath); ++ found_this = install_firmware_glob(fwpath_compressed); ++ if (!found_this) { ++ strcpy(fwpath_compressed + strlen(fwpath) + 1, "xz"); ++ found_this = install_firmware_glob(fwpath_compressed); ++ } + } + } else { +- ret = install_firmware_fullpath(fwpath); ++ ret = install_firmware_fullpath(fwpath, true); + if (ret == 0) + found_this = true; + } +-- +2.48.1 + + +From ddbeed81b2d43a03a16dc60ff76fd0355d4be5b9 Mon Sep 17 00:00:00 2001 +From: Mark Harmstone +Date: Thu, 23 Jan 2025 11:39:13 +0000 +Subject: [PATCH 05/22] feat(btrfs): also install btrfstune + +--- + modules.d/90btrfs/module-setup.sh | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/modules.d/90btrfs/module-setup.sh b/modules.d/90btrfs/module-setup.sh +index 5d881332..80bba155 100755 +--- a/modules.d/90btrfs/module-setup.sh ++++ b/modules.d/90btrfs/module-setup.sh +@@ -55,6 +55,6 @@ install() { + inst_hook initqueue/timeout 10 "$moddir/btrfs_timeout.sh" + fi + +- inst_multiple -o btrfsck btrfs-zero-log ++ inst_multiple -o btrfsck btrfs-zero-log btrfstune + inst "$(command -v btrfs)" /sbin/btrfs + } +-- +2.48.1 + + +From cb8fb9641feec8ee3e0ce249da98becc6cdbb98b Mon Sep 17 00:00:00 2001 +From: Benjamin Drung +Date: Fri, 21 Feb 2025 23:49:04 +0100 +Subject: [PATCH 06/22] fix(systemd-sysusers): silence "Creating " on stderr + +dracut prints 20 lines when creating users and groups even with +`--quiet` option. Sample output: + +``` +Creating group 'nobody' with GID 65534. +Creating group 'audio' with GID 997. +Creating group 'disk' with GID 995. +Creating group 'input' with GID 994. +Creating group 'kmem' with GID 993. +Creating group 'kvm' with GID 992. +Creating group 'lp' with GID 991. +Creating group 'optical' with GID 990. +Creating group 'render' with GID 989. +Creating group 'sgx' with GID 988. +Creating group 'storage' with GID 987. +Creating group 'tty' with GID 5. +Creating group 'uucp' with GID 986. +Creating group 'video' with GID 985. +Creating group 'users' with GID 984. +Creating group 'systemd-journal' with GID 983. +Creating user 'root' (Super User) with UID 0 and GID 0. +Creating user 'nobody' (Kernel Overflow User) with UID 65534 and GID 65534. +Creating group 'nobody' with GID 65534. +Creating group 'audio' with GID 997. +``` + +Filter "Creating " messages from stderr, but keep the other messages on +stderr and all messages on stdout untouched. + +Fixes: https://github.com/dracut-ng/dracut-ng/issues/1195 +Fixes: f3dacc013d90 ("feat(systemd-sysusers): run systemd-sysusers as part of the build process") +--- + modules.d/60systemd-sysusers/module-setup.sh | 6 +++++- + 1 file changed, 5 insertions(+), 1 deletion(-) + +diff --git a/modules.d/60systemd-sysusers/module-setup.sh b/modules.d/60systemd-sysusers/module-setup.sh +index 977695e6..0bddd19d 100755 +--- a/modules.d/60systemd-sysusers/module-setup.sh ++++ b/modules.d/60systemd-sysusers/module-setup.sh +@@ -15,5 +15,9 @@ check() { + install() { + inst_sysusers basic.conf + +- systemd-sysusers --root="$initdir" > /dev/null ++ # redirect stdout temporarily to FD 3 to use filter stderr ++ { ++ set -o pipefail ++ systemd-sysusers --root="$initdir" 2>&1 >&3 | grep -v "^Creating " >&2 ++ } 3>&1 + } +-- +2.48.1 + + +From f3fffa1edce2fd5e542c115296c9b0856611faa7 Mon Sep 17 00:00:00 2001 +From: Antonio Alvarez Feijoo +Date: Thu, 20 Feb 2025 11:20:36 +0100 +Subject: [PATCH 07/22] fix(systemd-veritysetup): install dm-verity kernel + module + +--- + modules.d/01systemd-veritysetup/module-setup.sh | 5 +++++ + 1 file changed, 5 insertions(+) + +diff --git a/modules.d/01systemd-veritysetup/module-setup.sh b/modules.d/01systemd-veritysetup/module-setup.sh +index fecfecc8..9dad8d4f 100755 +--- a/modules.d/01systemd-veritysetup/module-setup.sh ++++ b/modules.d/01systemd-veritysetup/module-setup.sh +@@ -26,6 +26,11 @@ depends() { + + } + ++# Install kernel module(s). ++installkernel() { ++ instmods dm-verity ++} ++ + # Install the required file(s) and directories for the module in the initramfs. + install() { + +-- +2.48.1 + + +From 3d5bab815570d2a271a45ceb9135f7cb3bde11f1 Mon Sep 17 00:00:00 2001 +From: Martin Wilck +Date: Wed, 26 Feb 2025 14:54:51 +0100 +Subject: [PATCH 08/22] fix(iscsi): don't require network setup for qedi + +This adds the logic of cc2c48a ("fix(iscsi): don't require network setup +for bnx2i") for the qedi iSCSI offload driver. Testing has shown +that for qedi, network setup in the initrd is even more superfluous +as it is for bnx2i. qedi devices are usually separate PCI functions +that don't show up as ethernet interfaces at all. + +While at it, simplify the conditional a bit. + +Signed-off-by: Martin Wilck +--- + modules.d/95iscsi/parse-iscsiroot.sh | 5 +++-- + 1 file changed, 3 insertions(+), 2 deletions(-) + +diff --git a/modules.d/95iscsi/parse-iscsiroot.sh b/modules.d/95iscsi/parse-iscsiroot.sh +index 2dace3a6..a388bec1 100755 +--- a/modules.d/95iscsi/parse-iscsiroot.sh ++++ b/modules.d/95iscsi/parse-iscsiroot.sh +@@ -79,8 +79,9 @@ fi + + # iscsi_firmware does not need argument checking + if [ -n "$iscsi_firmware" ]; then +- if [ "$root" != "dhcp" ] && [ "$netroot" != "dhcp" ]; then +- [ -z "$netroot" ] && [ "$iscsi_transport" != bnx2i ] && netroot=iscsi: ++ if [ "$root" != "dhcp" ] && [ -z "$netroot" ] \ ++ && [ "$iscsi_transport" != bnx2i ] && [ "$iscsi_transport" != qedi ]; then ++ netroot=iscsi: + fi + modprobe -b -q iscsi_boot_sysfs 2> /dev/null + modprobe -b -q iscsi_ibft +-- +2.48.1 + + +From fcde3355456323be9674aac1d00e3c66683b7f99 Mon Sep 17 00:00:00 2001 +From: Martin Wilck +Date: Wed, 26 Feb 2025 14:59:44 +0100 +Subject: [PATCH 09/22] fix(iscsi): make sure services are shut down when + switching root + +When systemd prepares switching root, it starts 'initrd-cleanup.service', +which runs 'systemctl --no-block isolate initrd-switch-root.target'. +This will stop all units on which initrd-switch-root.target does not +depend, including iscsid.service and iscsiuio.service. But systemd +doesn't guarantee a time ordering in this case. It can happen that +systemd switches root (i.e. restarts itself on the new root) before +iscsiuio is actually stopped, or at least before PID 1 receives +the notification that it has stopped. In this case, it considers +iscsiuio still running, and will not start it later in the boot +sequence when iscsid is coming up. + +A typical log excerpt with systemd.log_level=debug looks like this: + +[ 36.470761] worker2 systemd[1]: initrd-cleanup.service: Trying to enqueue job initrd-cleanup.service/start/replace +[ 36.765241] worker2 systemd[1]: initrd-switch-root.target: Trying to enqueue job initrd-switch-root.target/start/isolate +[ 36.765337] worker2 systemd[1]: iscsid.service: Installed new job iscsid.service/stop as 139 +[ 36.765535] worker2 systemd[1]: iscsiuio.service: Installed new job iscsiuio.service/stop as 138 +[ 36.824789] worker2 systemd[1]: iscsid.socket: stopping held back, waiting for: iscsid.service +[ 36.824813] worker2 systemd[1]: iscsiuio.socket: stopping held back, waiting for: iscsiuio.service +[ 36.888759] worker2 systemd[1]: iscsid.service: Thawing unit. +[ 36.888882] worker2 systemd[1]: iscsid.service: Changed running -> stop-sigterm +[ 36.889355] worker2 systemd[1]: Stopping Open-iSCSI... +[ 36.889413] worker2 systemd[1]: iscsiuio.service: stopping held back, waiting for: iscsid.service +[ 37.512072] worker2 systemd[1]: Reached target Switch Root. +[ 37.549512] worker2 @ystemctl[1614]: Switching root - root: /sysroot; init: n/a +[ 37.577264] worker2 systemd[1]: Switching root. + +When iscsid is started later on in the real root, it resets all existing iSCSI +connections, causing the root FS to come offline. In iSCSI offload scenarios +if iscsiuio is already running, it will re-establish the session after a few +seconds. But if iscsiuio has not been started at this point in time, it can't +be loaded any more from the root FS, and booting fails. + +To avoid this problem, add "Conflicts" and a "Before" dependencies against +initrd-cleanup.service to the iSCSI service units. + +See also https://github.com/systemd/systemd/issues/3436 + +Signed-off-by: Martin Wilck +--- + modules.d/95iscsi/module-setup.sh | 8 ++++---- + 1 file changed, 4 insertions(+), 4 deletions(-) + +diff --git a/modules.d/95iscsi/module-setup.sh b/modules.d/95iscsi/module-setup.sh +index 1b2ea110..3bb9a63d 100755 +--- a/modules.d/95iscsi/module-setup.sh ++++ b/modules.d/95iscsi/module-setup.sh +@@ -234,8 +234,8 @@ install() { + { + echo "[Unit]" + echo "DefaultDependencies=no" +- echo "Conflicts=shutdown.target" +- echo "Before=shutdown.target" ++ echo "Conflicts=shutdown.target initrd-cleanup.service" ++ echo "Before=shutdown.target initrd-cleanup.service" + } > "${initdir}/$systemdsystemunitdir/iscsid.service.d/dracut.conf" + + mkdir -p "${initdir}/$systemdsystemunitdir/iscsid.socket.d" +@@ -250,8 +250,8 @@ install() { + { + echo "[Unit]" + echo "DefaultDependencies=no" +- echo "Conflicts=shutdown.target" +- echo "Before=shutdown.target" ++ echo "Conflicts=shutdown.target initrd-cleanup.service" ++ echo "Before=shutdown.target initrd-cleanup.service" + } > "${initdir}/$systemdsystemunitdir/iscsiuio.service.d/dracut.conf" + + mkdir -p "${initdir}/$systemdsystemunitdir/iscsiuio.socket.d" +-- +2.48.1 + + +From 20cc20d2ac9c2908da6735b04dba49c1cb1b0bab Mon Sep 17 00:00:00 2001 +From: Xinhui Yang +Date: Sat, 1 Mar 2025 00:54:31 +0800 +Subject: [PATCH 10/22] fix(90kernel-modules): explicitly include + xhci-pci-renesas + +Since Linux v6.12-rc1 (commit 25f51b76f90f), xhci-pci no longer depends +on xhci-pci-renesas, causing the Renesas driver to be omitted during +initramfs generation (when built as a module). + +This makes platforms with such xHCI controllers unavailable during +initrd, and unable to boot from a USB drive. There are SuperSpeed ports +routed through such controller on some platforms, too, which also +renders the USB keyboard and mouse unusable. + +Here's a snippet of the kernel log from such platform, showing a +keyboard and a mouse being detected only after the initrd switched root: + +[ 9.352608] systemd-journald[187]: Received SIGTERM from PID 1 (systemd). +[ 9.500146] systemd[1]: systemd 257.2 running in system mode (OMITTED) +... +[ 11.187756] xhci-pci-renesas 0000:04:00.0: xHCI Host Controller +[ 11.187870] xhci-pci-renesas 0000:04:00.0: new USB bus registered, assigned bus number 7 +[ 11.193261] xhci-pci-renesas 0000:04:00.0: hcc params 0x014051cf hci version 0x100 quirks 0x0000000100000010 +[ 11.194806] xhci-pci-renesas 0000:04:00.0: xHCI Host Controller +[ 11.196601] xhci-pci-renesas 0000:04:00.0: new USB bus registered, assigned bus number 8 +[ 11.196613] xhci-pci-renesas 0000:04:00.0: Host supports USB 3.0 SuperSpeed +[ 11.196927] usb usb7: New USB device found, idVendor=1d6b, idProduct=0002, bcdDevice= 6.13 +[ 11.196931] usb usb7: New USB device strings: Mfr=3, Product=2, SerialNumber=1 +[ 11.196935] usb usb7: Product: xHCI Host Controller +[ 11.196938] usb usb7: Manufacturer: Linux 6.13.3-aosc-main xhci-hcd +[ 11.196941] usb usb7: SerialNumber: 0000:04:00.0 +[ 11.199598] hub 7-0:1.0: USB hub found +[ 11.199630] hub 7-0:1.0: 4 ports detected +... +[ 11.439561] usb 7-2: new high-speed USB device number 2 using xhci-pci-renesas +[ 11.568361] usb 7-2: New USB device found, idVendor=1532, idProduct=0114, bcdDevice= 1.00 +[ 11.568369] usb 7-2: New USB device strings: Mfr=1, Product=2, SerialNumber=0 +[ 11.568372] usb 7-2: Product: DeathStalker Ultimate +[ 11.568376] usb 7-2: Manufacturer: Razer +[ 11.600474] input: Razer DeathStalker Ultimate as /devices/pci0000:00/0000:00:0e.0/0000:04:00.0/usb7/7-2/7-2:1.0/0003:1532:0114.0001/input/input12 +[ 11.600686] hid-generic 0003:1532:0114.0001: input,hidraw0: USB HID v1.11 Mouse [Razer DeathStalker Ultimate] on usb-0000:04:00.0-2/input0 +[ 11.601137] input: Razer DeathStalker Ultimate Keyboard as /devices/pci0000:00/0000:00:0e.0/0000:04:00.0/usb7/7-2/7-2:1.1/0003:1532:0114.0002/input/input13 +[ 11.652148] input: Razer DeathStalker Ultimate as /devices/pci0000:00/0000:00:0e.0/0000:04:00.0/usb7/7-2/7-2:1.1/0003:1532:0114.0002/input/input14 +[ 11.652409] hid-generic 0003:1532:0114.0002: input,hidraw1: USB HID v1.11 Keyboard [Razer DeathStalker Ultimate] on usb-0000:04:00.0-2/input1 +[ 11.653054] input: Razer DeathStalker Ultimate as /devices/pci0000:00/0000:00:0e.0/0000:04:00.0/usb7/7-2/7-2:1.2/0003:1532:0114.0003/input/input15 +[ 11.703768] hid-generic 0003:1532:0114.0003: input,hidraw2: USB HID v1.11 Keyboard [Razer DeathStalker Ultimate] on usb-0000:04:00.0-2/input2 +--- + modules.d/90kernel-modules/module-setup.sh | 5 ++++- + 1 file changed, 4 insertions(+), 1 deletion(-) + +diff --git a/modules.d/90kernel-modules/module-setup.sh b/modules.d/90kernel-modules/module-setup.sh +index f159f0be..1ac91d02 100755 +--- a/modules.d/90kernel-modules/module-setup.sh ++++ b/modules.d/90kernel-modules/module-setup.sh +@@ -39,12 +39,15 @@ installkernel() { + hostonly='' instmods \ + hid_generic unix + ++ # xhci-pci-renesas is needed for the USB to be available during ++ # initrd on platforms with such USB controllers since Linux ++ # 6.12-rc1 (commit 25f51b76f90f). + hostonly=$(optional_hostonly) instmods \ + ehci-hcd ehci-pci ehci-platform \ + ohci-hcd ohci-pci \ + uhci-hcd \ + usbhid \ +- xhci-hcd xhci-pci xhci-plat-hcd \ ++ xhci-hcd xhci-pci xhci-pci-renesas xhci-plat-hcd \ + "=drivers/hid" \ + "=drivers/tty/serial" \ + "=drivers/input/serio" \ +-- +2.48.1 + + +From 4402aeb271933e6b542f5d9a4ff13f6e8b97e6c2 Mon Sep 17 00:00:00 2001 +From: Antonio Alvarez Feijoo +Date: Wed, 26 Feb 2025 08:20:09 +0100 +Subject: [PATCH 11/22] feat(systemd-integritysetup): add + remote-integritysetup.target + +Required since https://github.com/systemd/systemd/commit/810708f4b820543b8585a36e84ccca4bc5b18fee +--- + modules.d/01systemd-integritysetup/module-setup.sh | 10 ++++++++-- + 1 file changed, 8 insertions(+), 2 deletions(-) + +diff --git a/modules.d/01systemd-integritysetup/module-setup.sh b/modules.d/01systemd-integritysetup/module-setup.sh +index 3d176404..dffc88ac 100755 +--- a/modules.d/01systemd-integritysetup/module-setup.sh ++++ b/modules.d/01systemd-integritysetup/module-setup.sh +@@ -26,6 +26,7 @@ depends() { + + } + ++# Install kernel module(s). + installkernel() { + instmods dm-integrity + } +@@ -36,9 +37,11 @@ install() { + inst_multiple -o \ + "$systemdutildir"/systemd-integritysetup \ + "$systemdutildir"/system-generators/systemd-integritysetup-generator \ ++ "$systemdsystemunitdir"/remote-integritysetup.target \ + "$systemdsystemunitdir"/integritysetup-pre.target \ + "$systemdsystemunitdir"/integritysetup.target \ +- "$systemdsystemunitdir"/sysinit.target.wants/integritysetup.target ++ "$systemdsystemunitdir"/sysinit.target.wants/integritysetup.target \ ++ "$systemdsystemunitdir"/initrd-root-device.target.wants/remote-integritysetup.target + + # Install the hosts local user configurations if enabled. + if [[ $hostonly ]]; then +@@ -48,8 +51,11 @@ install() { + "$systemdsystemconfdir/integritysetup.target.wants/*.target" \ + "$systemdsystemconfdir"/integritysetup-pre.target \ + "$systemdsystemconfdir/integritysetup-pre.target.wants/*.target" \ ++ "$systemdsystemconfdir"/remote-integritysetup.target \ ++ "$systemdsystemconfdir/remote-integritysetup.target.wants/*.target" \ + "$systemdsystemconfdir"/sysinit.target.wants/integritysetup.target \ +- "$systemdsystemconfdir/sysinit.target.wants/integritysetup.target.wants/*.target" ++ "$systemdsystemconfdir/sysinit.target.wants/integritysetup.target.wants/*.target" \ ++ "$systemdsystemconfdir"/initrd-root-device.target.wants/remote-integritysetup.target + fi + + # Install required libraries. +-- +2.48.1 + + +From c43b79056ffdb7b410e70550a8ad8d137b4720c0 Mon Sep 17 00:00:00 2001 +From: Benjamin Marzinski +Date: Wed, 26 Mar 2025 18:04:25 -0400 +Subject: [PATCH 13/22] fix(multipath): skip default multipath.conf with + mpathconf + +Commit 1e802f15f creates a default multipath.conf file with +"find_multipaths strict" when run in non-hostonly mode if there are no +multipath devices and no multipath.conf. Unfortunately for systems that +want to use mpathconf to create a multipath.conf file (e.g. Fedora and +Centos) either through multipathd-configure.service or multipathd.sh, +this default file keeps that from occurring. To make sure mpathconf is +called to create the config file, do not install a default config file +if mpathconf is installed. + +Fixes: ("fix(multipath): include module with find_multipaths strict") +Signed-off-by: Benjamin Marzinski +--- + modules.d/90multipath/module-setup.sh | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/modules.d/90multipath/module-setup.sh b/modules.d/90multipath/module-setup.sh +index 5fdbb9a7..a05df018 100755 +--- a/modules.d/90multipath/module-setup.sh ++++ b/modules.d/90multipath/module-setup.sh +@@ -122,7 +122,7 @@ install() { + fi + } + +- [[ $hostonly ]] || { ++ [[ $hostonly ]] || mpathconf_installed || { + for_each_host_dev_and_slaves is_mpath \ + || [[ -f /etc/multipath.conf ]] || { + cat > "${initdir}"/etc/multipath.conf << EOF +-- +2.48.1 + + +From e6b2c882af61a804f7658ed6e2f84f02277c7b8a Mon Sep 17 00:00:00 2001 +From: Jo Zzsi +Date: Mon, 24 Mar 2025 09:12:13 -0400 +Subject: [PATCH 14/22] chore(network-legacy): no need to call chmod on ifup.sh + +This is a small optimization, with the goal of avoiding +calling chmod for a file that is already guaranteed to be +an executable. +--- + modules.d/35network-legacy/ifup.sh | 1 - + 1 file changed, 1 deletion(-) + +diff --git a/modules.d/35network-legacy/ifup.sh b/modules.d/35network-legacy/ifup.sh +index 1cd27b14..59629f11 100755 +--- a/modules.d/35network-legacy/ifup.sh ++++ b/modules.d/35network-legacy/ifup.sh +@@ -47,7 +47,6 @@ do_dhcp_parallel() { + echo 'dhcp=dhclient' >> /run/NetworkManager/conf.d/10-dracut-dhclient.conf + fi + +- chmod +x /sbin/dhcp-multi.sh + /sbin/dhcp-multi.sh "$netif" "$DO_VLAN" "$@" & + return 0 + } +-- +2.48.1 + + +From ddc1f54d3ec96c55c444af22a0a964cb48266a21 Mon Sep 17 00:00:00 2001 +From: Jo Zzsi +Date: Mon, 24 Mar 2025 09:23:22 -0400 +Subject: [PATCH 15/22] perf(base): move the chmod dependency from base to + systemd + +base dracut module no longer requires chmod. +--- + modules.d/00systemd/module-setup.sh | 1 + + modules.d/95ssh-client/module-setup.sh | 2 +- + modules.d/99base/module-setup.sh | 1 - + 3 files changed, 2 insertions(+), 2 deletions(-) + +diff --git a/modules.d/00systemd/module-setup.sh b/modules.d/00systemd/module-setup.sh +index 1f35a73c..283a39af 100755 +--- a/modules.d/00systemd/module-setup.sh ++++ b/modules.d/00systemd/module-setup.sh +@@ -84,6 +84,7 @@ install() { + "$systemdsystemunitdir"/-.slice \ + systemctl \ + echo swapoff \ ++ chmod \ + mount umount reboot poweroff \ + systemd-run systemd-escape \ + systemd-cgls +diff --git a/modules.d/95ssh-client/module-setup.sh b/modules.d/95ssh-client/module-setup.sh +index 75fc94f3..662ad177 100755 +--- a/modules.d/95ssh-client/module-setup.sh ++++ b/modules.d/95ssh-client/module-setup.sh +@@ -65,7 +65,7 @@ inst_sshenv() { + install() { + local _nsslibs + +- inst_multiple ssh scp ++ inst_multiple ssh scp chmod + inst_sshenv + + _nsslibs=$( +diff --git a/modules.d/99base/module-setup.sh b/modules.d/99base/module-setup.sh +index 4a86e90d..12194964 100755 +--- a/modules.d/99base/module-setup.sh ++++ b/modules.d/99base/module-setup.sh +@@ -9,7 +9,6 @@ depends() { + # called by dracut + install() { + inst_multiple \ +- chmod \ + cp \ + dmesg \ + flock \ +-- +2.48.1 + + +From 2ae73d639834758a88b34033693bd97a7b1ed2f0 Mon Sep 17 00:00:00 2001 +From: Benjamin Drung +Date: Thu, 3 Apr 2025 14:14:07 +0200 +Subject: [PATCH 16/22] feat: add simpledrm module (as subset of drm module) + +Plymouth doesn't always show a splash screen if DRM drivers are +installed in initrd. + +Provide a `simpledrm` module that only installs the SimpleDRM module +and the potentially needed privacy screen providers. This `simpledrm` +module is a subset of the `drm` module. It could be used instead of +`drm` to avoid pulling in drivers like amdgpu, nouveau, or nvidia-drm. + +Bug-Ubuntu: https://launchpad.net/bugs/2105377 +--- + modules.d/45simpledrm/module-setup.sh | 28 +++++++++++++++++++++++++++ + 1 file changed, 28 insertions(+) + create mode 100755 modules.d/45simpledrm/module-setup.sh + +diff --git a/modules.d/45simpledrm/module-setup.sh b/modules.d/45simpledrm/module-setup.sh +new file mode 100755 +index 00000000..aa5fcd33 +--- /dev/null ++++ b/modules.d/45simpledrm/module-setup.sh +@@ -0,0 +1,28 @@ ++#!/bin/bash ++ ++# called by dracut ++check() { ++ return 255 ++} ++ ++# called by dracut ++installkernel() { ++ # Include simple DRM driver ++ instmods simpledrm ++ ++ if [[ $hostonly ]]; then ++ # if there is a privacy screen then its driver must be loaded before the ++ # kms driver will bind, otherwise its probe() will return -EPROBE_DEFER ++ # note privacy screens always register, even with e.g. nokmsboot ++ for i in /sys/class/drm/privacy_screen-*/device/driver/module; do ++ [[ -L $i ]] || continue ++ modlink=$(readlink "$i") ++ modname=$(basename "$modlink") ++ instmods "$modname" ++ done ++ else ++ # include privacy screen providers (see above comment) ++ # atm all providers live under drivers/platform/x86 ++ dracut_instmods -o -s "drm_privacy_screen_register" "=drivers/platform/x86" ++ fi ++} +-- +2.48.1 + + +From 1b5669c1d89e0cc1134ad5b0aa5c091144d24b84 Mon Sep 17 00:00:00 2001 +From: Antonio Alvarez Feijoo +Date: Fri, 4 Apr 2025 10:18:07 +0200 +Subject: [PATCH 17/22] feat(systemd): add new systemd-validatefs@.service + +Introduced in https://github.com/systemd/systemd/commit/0bdd5ccc8145af8dae9779751d3e7a34c4fa6aa5 +Used internally in fstab-generator (new `x-systemd.validatefs` mount option) and +gpt-auto-generator: https://github.com/systemd/systemd/commit/f872373a26dcaa0818b49220abfe35611d12fa82 +--- + modules.d/00systemd/module-setup.sh | 2 ++ + 1 file changed, 2 insertions(+) + +diff --git a/modules.d/00systemd/module-setup.sh b/modules.d/00systemd/module-setup.sh +index 283a39af..8f25475e 100755 +--- a/modules.d/00systemd/module-setup.sh ++++ b/modules.d/00systemd/module-setup.sh +@@ -35,6 +35,7 @@ install() { + "$systemdutildir"/systemd-shutdown \ + "$systemdutildir"/systemd-reply-password \ + "$systemdutildir"/systemd-fsck \ ++ "$systemdutildir"/systemd-validatefs \ + "$systemdutildir"/systemd-volatile-root \ + "$systemdutildir"/systemd-sysroot-fstab-check \ + "$systemdutildir"/system-generators/systemd-debug-generator \ +@@ -76,6 +77,7 @@ install() { + "$systemdsystemunitdir"/systemd-reboot.service \ + "$systemdsystemunitdir"/systemd-kexec.service \ + "$systemdsystemunitdir"/systemd-fsck@.service \ ++ "$systemdsystemunitdir"/systemd-validatefs@.service \ + "$systemdsystemunitdir"/systemd-volatile-root.service \ + "$systemdsystemunitdir"/ctrl-alt-del.target \ + "$systemdsystemunitdir"/syslog.socket \ +-- +2.48.1 + + +From e8f72ed9bed9f80c976867953a3eb92e62f9df2f Mon Sep 17 00:00:00 2001 +From: Antonio Alvarez Feijoo +Date: Mon, 3 Mar 2025 15:22:14 +0100 +Subject: [PATCH 18/22] chore(multipath): remove `rd_NO_MULTIPATH` kernel + command line option + +Deprecated since 778b3543609d8c9d32df7111229f4072d00d02f0 (Nov 25, 2014). +--- + modules.d/90multipath/multipathd.service | 1 - + modules.d/90multipath/multipathd.sh | 2 +- + 2 files changed, 1 insertion(+), 2 deletions(-) + +diff --git a/modules.d/90multipath/multipathd.service b/modules.d/90multipath/multipathd.service +index 1680cdfb..3248fa97 100644 +--- a/modules.d/90multipath/multipathd.service ++++ b/modules.d/90multipath/multipathd.service +@@ -11,7 +11,6 @@ Conflicts=shutdown.target + Conflicts=initrd-cleanup.service + ConditionKernelCommandLine=!nompath + ConditionKernelCommandLine=!rd.multipath=0 +-ConditionKernelCommandLine=!rd_NO_MULTIPATH + ConditionKernelCommandLine=!multipath=off + ConditionVirtualization=!container + +diff --git a/modules.d/90multipath/multipathd.sh b/modules.d/90multipath/multipathd.sh +index e17fd921..68bd0383 100755 +--- a/modules.d/90multipath/multipathd.sh ++++ b/modules.d/90multipath/multipathd.sh +@@ -8,7 +8,7 @@ if [ "$(getarg rd.multipath)" = "default" ] && [ ! -e /etc/multipath.conf ]; the + mpathconf --enable + fi + +-if getargbool 1 rd.multipath -d -n rd_NO_MULTIPATH && [ -e /etc/multipath.conf ]; then ++if getargbool 1 rd.multipath && [ -e /etc/multipath.conf ]; then + modprobe dm-multipath + multipathd -B || multipathd + need_shutdown +-- +2.48.1 + + +From 5e87b68cfb706b499a4d6814e3414d954db46083 Mon Sep 17 00:00:00 2001 +From: Antonio Alvarez Feijoo +Date: Mon, 3 Mar 2025 15:23:41 +0100 +Subject: [PATCH 19/22] refactor(multipath): remove custom multipathd.service + +Install `multipathd.service` provided by upstream, and add a dropin to support +`rd.multipath=0`. +--- + modules.d/90multipath/module-setup.sh | 3 ++- + modules.d/90multipath/multipathd-dracut.conf | 2 ++ + modules.d/90multipath/multipathd.service | 26 -------------------- + 3 files changed, 4 insertions(+), 27 deletions(-) + create mode 100644 modules.d/90multipath/multipathd-dracut.conf + delete mode 100644 modules.d/90multipath/multipathd.service + +diff --git a/modules.d/90multipath/module-setup.sh b/modules.d/90multipath/module-setup.sh +index a05df018..5a7f91fa 100755 +--- a/modules.d/90multipath/module-setup.sh ++++ b/modules.d/90multipath/module-setup.sh +@@ -91,6 +91,7 @@ install() { + [[ -d $config_dir ]] || config_dir=/etc/multipath/conf.d + + inst_multiple \ ++ "$systemdsystemunitdir"/multipathd.service \ + pkill \ + kpartx \ + dmsetup \ +@@ -151,7 +152,7 @@ EOF + inst_simple "${moddir}/multipathd-configure.service" "${systemdsystemunitdir}/multipathd-configure.service" + $SYSTEMCTL -q --root "$initdir" enable multipathd-configure.service + fi +- inst_simple "${moddir}/multipathd.service" "${systemdsystemunitdir}/multipathd.service" ++ inst_simple "$moddir/multipathd-dracut.conf" "$systemdsystemunitdir/multipathd.service.d/multipathd-dracut.conf" + $SYSTEMCTL -q --root "$initdir" enable multipathd.service + else + inst_hook pre-trigger 02 "$moddir/multipathd.sh" +diff --git a/modules.d/90multipath/multipathd-dracut.conf b/modules.d/90multipath/multipathd-dracut.conf +new file mode 100644 +index 00000000..783b05d5 +--- /dev/null ++++ b/modules.d/90multipath/multipathd-dracut.conf +@@ -0,0 +1,2 @@ ++[Unit] ++ConditionKernelCommandLine=!rd.multipath=0 +diff --git a/modules.d/90multipath/multipathd.service b/modules.d/90multipath/multipathd.service +deleted file mode 100644 +index 3248fa97..00000000 +--- a/modules.d/90multipath/multipathd.service ++++ /dev/null +@@ -1,26 +0,0 @@ +-[Unit] +-Description=Device-Mapper Multipath Device Controller +-Before=lvm2-activation-early.service +-Before=local-fs-pre.target blk-availability.service shutdown.target +-Wants=systemd-udevd-kernel.socket +-After=systemd-udevd-kernel.socket +-After=multipathd.socket systemd-remount-fs.service +-Before=initrd-cleanup.service +-DefaultDependencies=no +-Conflicts=shutdown.target +-Conflicts=initrd-cleanup.service +-ConditionKernelCommandLine=!nompath +-ConditionKernelCommandLine=!rd.multipath=0 +-ConditionKernelCommandLine=!multipath=off +-ConditionVirtualization=!container +- +-[Service] +-Type=notify +-NotifyAccess=main +-ExecStartPre=-/sbin/modprobe dm-multipath +-ExecStart=/sbin/multipathd -d -s +-ExecReload=/sbin/multipathd reconfigure +-TasksMax=infinity +- +-[Install] +-WantedBy=sysinit.target +-- +2.48.1 + + +From 6b30662e6e4720428f0efb0ab85c80303dd34afd Mon Sep 17 00:00:00 2001 +From: Antonio Alvarez Feijoo +Date: Tue, 25 Mar 2025 15:20:48 +0100 +Subject: [PATCH 20/22] fix(nfs): libnfsidmap plugins not added in some + distributions + +`nfs-utils` can be configured using `--with-pluginpath` to avoid using the +default `/usr/lib/libnfsidmap`. For example, Fedora sets +`--with-pluginpath=%{_libdir}/libnfsidmap`, which is covered by the current +glob, but openSUSE sets `--with-pluginpath=%{_libdir}/libnfsidmap-1.0.0`. + +Also, remove reference to the old `libnfsidmap_.so` path. +--- + modules.d/95nfs/module-setup.sh | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/modules.d/95nfs/module-setup.sh b/modules.d/95nfs/module-setup.sh +index 039b4e4c..6c76faf4 100755 +--- a/modules.d/95nfs/module-setup.sh ++++ b/modules.d/95nfs/module-setup.sh +@@ -139,5 +139,5 @@ install() { + + dracut_need_initqueue + +- inst_libdir_file 'libnfsidmap_nsswitch.so*' 'libnfsidmap/*.so' 'libnfsidmap*.so*' ++ inst_libdir_file 'libnfsidmap*/*.so' 'libnfsidmap*.so*' + } +-- +2.48.1 + + +From 2f5a759f490bb813ec24a685f015b15ff196783b Mon Sep 17 00:00:00 2001 +From: Antonio Alvarez Feijoo +Date: Tue, 1 Apr 2025 14:33:38 +0200 +Subject: [PATCH 21/22] fix(nfs): use `DRACUT_CP` instead of `cp` + +Using `cp` directly ignores `DRACUT_NO_XATTR`. +--- + modules.d/95nfs/module-setup.sh | 12 +++++++++--- + 1 file changed, 9 insertions(+), 3 deletions(-) + +diff --git a/modules.d/95nfs/module-setup.sh b/modules.d/95nfs/module-setup.sh +index 6c76faf4..d097cd38 100755 +--- a/modules.d/95nfs/module-setup.sh ++++ b/modules.d/95nfs/module-setup.sh +@@ -120,9 +120,15 @@ install() { + mkdir -m 0770 -p "$initdir/var/lib/rpcbind" + + # use the same directory permissions as the host +- [ -d "/var/lib/nfs/statd" ] && cp -a --attributes-only "$dracutsysrootdir"/var/lib/nfs/statd "${initdir}"/var/lib/nfs/ && rm -rf "${initdir}"/var/lib/nfs/statd/* +- [ -d "/var/lib/nfs/statd/sm" ] && cp -a --attributes-only "$dracutsysrootdir"/var/lib/nfs/statd/sm "${initdir}"/var/lib/nfs/statd/ && rm -rf "${initdir}"/var/lib/nfs/statd/sm/* +- [ -d "/var/lib/nfs/sm" ] && cp -a --attributes-only "$dracutsysrootdir"/var/lib/nfs/sm "${initdir}"/var/lib/nfs/ && rm -rf "${initdir}"/var/lib/nfs/sm/* ++ [[ -d "$dracutsysrootdir"/var/lib/nfs/statd ]] \ ++ && $DRACUT_CP -L --preserve=ownership -t "$initdir"/var/lib/nfs "$dracutsysrootdir"/var/lib/nfs/statd \ ++ && rm -rf "$initdir"/var/lib/nfs/statd/* ++ [[ -d "$dracutsysrootdir"/var/lib/nfs/statd/sm ]] \ ++ && $DRACUT_CP -L --preserve=ownership -t "$initdir"/var/lib/nfs/statd "$dracutsysrootdir"/var/lib/nfs/statd/sm \ ++ && rm -rf "$initdir"/var/lib/nfs/statd/sm/* ++ [[ -d "$dracutsysrootdir"/var/lib/nfs/sm ]] \ ++ && $DRACUT_CP -L --preserve=ownership -t "$initdir"/var/lib/nfs "$dracutsysrootdir"/var/lib/nfs/sm \ ++ && rm -rf "$initdir"/var/lib/nfs/sm/* + + # Rather than copy the passwd file in, just set a user for rpcbind + # We'll save the state and restart the daemon from the root anyway +-- +2.48.1 + + +From 7eaa8536fae73aa65fae604820f10e842a18bc88 Mon Sep 17 00:00:00 2001 +From: Antonio Alvarez Feijoo +Date: Tue, 1 Apr 2025 14:34:04 +0200 +Subject: [PATCH 22/22] fix(nfs): add possible `statd` user and group + +Some distributions use the `statd` user (openSUSE, Ubuntu) and group (openSUSE) +to handle `rpc.statd` directories. +--- + modules.d/95nfs/module-setup.sh | 4 ++-- + 1 file changed, 2 insertions(+), 2 deletions(-) + +diff --git a/modules.d/95nfs/module-setup.sh b/modules.d/95nfs/module-setup.sh +index d097cd38..b34c75c0 100755 +--- a/modules.d/95nfs/module-setup.sh ++++ b/modules.d/95nfs/module-setup.sh +@@ -136,10 +136,10 @@ install() { + local _confdir + for _confdir in etc usr/lib; do + +- grep -sE '^(nfsnobody|_rpc|rpc|rpcuser):' "${dracutsysrootdir}/${_confdir}/passwd" \ ++ grep -sE '^(nfsnobody|_rpc|rpc|rpcuser|statd):' "${dracutsysrootdir}/${_confdir}/passwd" \ + >> "$initdir/${_confdir}/passwd" + +- grep -sE '^(nogroup|rpc|nobody):' "${dracutsysrootdir}/${_confdir}/group" \ ++ grep -sE '^(nogroup|rpc|nobody|statd):' "${dracutsysrootdir}/${_confdir}/group" \ + >> "$initdir/${_confdir}/group" + done + +-- +2.48.1 + diff --git a/sdk_container/src/third_party/coreos-overlay/coreos/user-patches/sys-kernel/dracut/002-dracut-sysroot.patch b/sdk_container/src/third_party/coreos-overlay/coreos/user-patches/sys-kernel/dracut/002-dracut-sysroot.patch new file mode 100644 index 0000000000..bcb0e3054f --- /dev/null +++ b/sdk_container/src/third_party/coreos-overlay/coreos/user-patches/sys-kernel/dracut/002-dracut-sysroot.patch @@ -0,0 +1,2781 @@ +From 7e6a4cf62af3d8e05dd2dec6bb3301fe52031bf2 Mon Sep 17 00:00:00 2001 +From: James Le Cuirot +Date: Thu, 5 Sep 2024 11:55:35 +0100 +Subject: [PATCH 01/12] fix(dracut): respect PKG_CONFIG env var instead of + hardcoding pkg-config + +When using a sysroot, we should use pkg-config data from the sysroot. +While we could set PKG_CONFIG_LIBDIR, the lib directory can vary. +Distributions typically set up pkg-config wrappers for this purpose and +it is customary to respect the PKG_CONFIG variable in build systems. +Users can still set simply PKG_CONFIG_LIBDIR instead if they prefer that +approach. + +Signed-off-by: James Le Cuirot +--- + dracut-init.sh | 1 + + dracut.sh | 2 +- + man/dracut.8.adoc | 7 +++++++ + 3 files changed, 9 insertions(+), 1 deletion(-) + +diff --git a/dracut-init.sh b/dracut-init.sh +index be8eb9fb..de3ae324 100755 +--- a/dracut-init.sh ++++ b/dracut-init.sh +@@ -81,6 +81,7 @@ export srcmods + DRACUT_LDD=${DRACUT_LDD:-ldd} + DRACUT_TESTBIN=${DRACUT_TESTBIN:-/bin/sh} + DRACUT_LDCONFIG=${DRACUT_LDCONFIG:-ldconfig} ++PKG_CONFIG=${PKG_CONFIG:-pkg-config} + + # shellcheck source=./dracut-functions.sh + . "$dracutbasedir"/dracut-functions.sh +diff --git a/dracut.sh b/dracut.sh +index ef959021..aff721f0 100755 +--- a/dracut.sh ++++ b/dracut.sh +@@ -1489,7 +1489,7 @@ set_global_var() { + local _pkgvar="${2%:*}" + local _var="${2#*:}" + [[ -z ${!_var} || ! -d ${dracutsysrootdir}${!_var} ]] \ +- && export "$_var"="$(pkg-config "$_pkgconfig" --variable="$_pkgvar" 2> /dev/null)" ++ && export "$_var"="$($PKG_CONFIG "$_pkgconfig" --variable="$_pkgvar" 2> /dev/null)" + if [[ -z ${!_var} || ! -d ${dracutsysrootdir}${!_var} ]]; then + shift 2 + if (($# == 1)); then +diff --git a/man/dracut.8.adoc b/man/dracut.8.adoc +index a6d044a9..ba33ab19 100644 +--- a/man/dracut.8.adoc ++++ b/man/dracut.8.adoc +@@ -655,6 +655,13 @@ _DRACUT_LDD_:: + Default: + _ldd_ + ++_PKG_CONFIG_:: ++ sets the _pkg-config_ program path and options. Optional. ++ Most useful together with **--sysroot**. +++ ++Default: ++ _pkg-config_ ++ + _DRACUT_TESTBIN_:: + sets the initially tested binary for detecting library paths. + Optional. Used for **--sysroot**. In the cross-compiled sysroot, +-- +2.48.1 + + +From e5d3ef60f880bd9a35b0b13c667252bdb209a54e Mon Sep 17 00:00:00 2001 +From: James Le Cuirot +Date: Thu, 5 Sep 2024 12:19:09 +0100 +Subject: [PATCH 02/12] feat(dracut): set systemdversion global var using + pkg-config + +This falls back to 0 if the version cannot be determined. The version +isn't a regular pkg-config variable like the others, but we still want +the ability to override this through the Dracut config, so make +"modversion" a special case. + +Signed-off-by: James Le Cuirot +--- + dracut.sh | 12 +++++++++--- + 1 file changed, 9 insertions(+), 3 deletions(-) + +diff --git a/dracut.sh b/dracut.sh +index aff721f0..45527bcd 100755 +--- a/dracut.sh ++++ b/dracut.sh +@@ -1488,9 +1488,14 @@ set_global_var() { + local _pkgconfig="$1" + local _pkgvar="${2%:*}" + local _var="${2#*:}" +- [[ -z ${!_var} || ! -d ${dracutsysrootdir}${!_var} ]] \ +- && export "$_var"="$($PKG_CONFIG "$_pkgconfig" --variable="$_pkgvar" 2> /dev/null)" +- if [[ -z ${!_var} || ! -d ${dracutsysrootdir}${!_var} ]]; then ++ if [[ $_pkgvar == modversion ]]; then ++ local _vararg=--modversion ++ else ++ local _vararg=--variable=$_pkgvar ++ fi ++ [[ -z ${!_var} || ($3 == /* && ! -d ${dracutsysrootdir}${!_var}) ]] \ ++ && export "$_var"="$($PKG_CONFIG "$_pkgconfig" "$_vararg" 2> /dev/null)" ++ if [[ -z ${!_var} || ($3 == /* && ! -d ${dracutsysrootdir}${!_var}) ]]; then + shift 2 + if (($# == 1)); then + export "$_var"="$1" +@@ -1550,6 +1555,7 @@ set_global_var "systemd" "sysusers" "/usr/lib/sysusers.d" + set_global_var "systemd" "sysusersconfdir" "/etc/sysusers.d" + set_global_var "systemd" "tmpfilesdir" "/lib/tmpfiles.d" "/usr/lib/tmpfiles.d" + set_global_var "systemd" "tmpfilesconfdir" "/etc/tmpfiles.d" ++set_global_var "systemd" "modversion:systemdversion" "0" + + # libkmod global variables + set_global_var "libkmod" "depmodd" "/usr/lib/depmod.d" +-- +2.48.1 + + +From 80c8d6909788d718d0aa48bd70af049c43f0c67f Mon Sep 17 00:00:00 2001 +From: James Le Cuirot +Date: Mon, 10 Mar 2025 13:11:05 +0000 +Subject: [PATCH 03/12] fix(dracut-install): plug memory leak on kerneldir + +Signed-off-by: James Le Cuirot +--- + src/install/dracut-install.c | 5 +++++ + 1 file changed, 5 insertions(+) + +diff --git a/src/install/dracut-install.c b/src/install/dracut-install.c +index bacbe86e..9bfddb83 100644 +--- a/src/install/dracut-install.c ++++ b/src/install/dracut-install.c +@@ -68,6 +68,7 @@ static bool arg_modalias = false; + static bool arg_resolvelazy = false; + static bool arg_resolvedeps = false; + static bool arg_hostonly = false; ++static bool arg_kerneldir = false; + static bool no_xattr = false; + static char *destrootdir = NULL; + static char *sysrootdir = NULL; +@@ -1202,6 +1203,7 @@ static int parse_argv(int argc, char *argv[]) + break; + case ARG_KERNELDIR: + kerneldir = optarg; ++ arg_kerneldir = true; + break; + case ARG_FIRMWAREDIRS: + firmwaredirs = strv_split(optarg, ":"); +@@ -2407,6 +2409,9 @@ int main(int argc, char **argv) + finish1: + free(destrootdir); + finish2: ++ if (!arg_kerneldir) ++ free(kerneldir); ++ + if (logfile_f) + fclose(logfile_f); + +-- +2.48.1 + + +From fb77a76ab8736807a9b5305528fde5275ce15c5b Mon Sep 17 00:00:00 2001 +From: James Le Cuirot +Date: Wed, 5 Mar 2025 17:29:51 +0000 +Subject: [PATCH 04/12] fix(dracut-install): rework broken destination + existence logic + +The return code of `stat` is checked twice when it only needs to be +checked once. The `dst_exists` condition will also never be true, making +the variable redundant. + +Signed-off-by: James Le Cuirot +--- + src/install/dracut-install.c | 26 ++++++++------------------ + 1 file changed, 8 insertions(+), 18 deletions(-) + +diff --git a/src/install/dracut-install.c b/src/install/dracut-install.c +index 9bfddb83..83041e40 100644 +--- a/src/install/dracut-install.c ++++ b/src/install/dracut-install.c +@@ -821,7 +821,6 @@ static int dracut_install(const char *orig_src, const char *orig_dst, bool isdir + bool src_islink = false; + bool src_isdir = false; + mode_t src_mode = 0; +- bool dst_exists = true; + char *i = NULL; + const char *src, *dst; + +@@ -871,15 +870,13 @@ static int dracut_install(const char *orig_src, const char *orig_dst, bool isdir + _asprintf(&fulldstpath, "%s/%s", destrootdir, (dst[0] == '/' ? (dst + 1) : dst)); + + ret = stat(fulldstpath, &sb); +- if (ret != 0) { +- dst_exists = false; +- if (errno != ENOENT) { +- log_error("ERROR: stat '%s': %m", fulldstpath); ++ ++ if (ret == 0) { ++ if (src_isdir && !S_ISDIR(sb.st_mode)) { ++ log_error("dest dir '%s' already exists but is not a directory", fulldstpath); + return 1; + } +- } + +- if (ret == 0) { + if (resolvedeps && S_ISREG(sb.st_mode) && (sb.st_mode & (S_IXUSR | S_IXGRP | S_IXOTH))) { + log_debug("'%s' already exists, but checking for any deps", fulldstpath); + if (sysrootdirlen && (strncmp(fulldstpath, sysrootdir, sysrootdirlen) == 0)) +@@ -888,9 +885,11 @@ static int dracut_install(const char *orig_src, const char *orig_dst, bool isdir + ret = resolve_deps(fullsrcpath); + } else + log_debug("'%s' already exists", fulldstpath); +- +- /* dst does already exist */ + } else { ++ if (errno != ENOENT) { ++ log_error("ERROR: stat '%s': %m", fulldstpath); ++ return 1; ++ } + + /* check destination directory */ + fulldstdir = strndup(fulldstpath, dir_len(fulldstpath)); +@@ -923,15 +922,6 @@ static int dracut_install(const char *orig_src, const char *orig_dst, bool isdir + } + + if (src_isdir) { +- if (dst_exists) { +- if (S_ISDIR(sb.st_mode)) { +- log_debug("dest dir '%s' already exists", fulldstpath); +- return 0; +- } +- log_error("dest dir '%s' already exists but is not a directory", fulldstpath); +- return 1; +- } +- + log_info("mkdir '%s'", fulldstpath); + ret = dracut_mkdir(fulldstpath); + if (ret == 0) { +-- +2.48.1 + + +From 9bc6e43632ed5c9807e40aab684fe37a6d8b3bb0 Mon Sep 17 00:00:00 2001 +From: James Le Cuirot +Date: Tue, 18 Feb 2025 17:24:05 +0000 +Subject: [PATCH 05/12] feat(dracut-install): parse ELF .note.dlopen entries + for extra deps + +Unlike traditional DT_NEEDED dependencies, there has not been a way to +determine what libraries an ELF may dlopen until recently. systemd has +documented a convention to declare such dependencies using JSON in the +ELF metadata. See https://systemd.io/ELF_DLOPEN_METADATA/ for details. + +This metadata references sonames rather than full paths, so Dracut needs +to determine the full paths by itself. It cannot use ldd to do this as +that relies on DT_NEEDED. ldconfig can show the paths for all sonames in +the cache, but that relies on the cache having already been generated, +it isn't cross-friendly, and musl doesn't even have ldconfig. It +therefore makes sense for Dracut to parse the ELF headers directly. This +also paves the way for removing the dependency on ldd entirely, making +Dracut more cross-friendly as a whole. + +To avoid adding an entirely new dependency, the JSON parsing is done by +libsystemd's sd-json API. This has been exposed since systemd v257. If +libsystemd is too old or not present at all, then this dlopen handling +is simply skipped. This is currently not an issue for non-systemd +distributions as systemd is the only project using this convention. If +that were to change, libsystemd can still be used without the rest of +systemd, as demonstrated by Gentoo. + +The metadata itself has only been included by systemd since v256. If an +earlier version is detected, Dracut will unconditionally install the +same libraries that it did before. + +There are different structs for 32-bit and 64-bit ELF headers, so this +new code makes heavy use of C macros to avoid a lot of code duplication. +One macro is also used heavily for endian conversion, as almost every +field needs to be adjusted. + +See the code comments for the remaining details. + +Closes: https://github.com/dracut-ng/dracut-ng/issues/154 +Signed-off-by: James Le Cuirot +--- + Makefile | 4 +- + configure | 3 + + dracut.sh | 11 + + modules.d/00systemd/module-setup.sh | 1 - + modules.d/01systemd-bsod/module-setup.sh | 4 +- + modules.d/01systemd-coredump/module-setup.sh | 10 +- + .../01systemd-integritysetup/module-setup.sh | 5 +- + modules.d/01systemd-journald/module-setup.sh | 12 +- + .../01systemd-veritysetup/module-setup.sh | 5 +- + src/install/dracut-install.c | 572 +++++++++++++++++- + 10 files changed, 592 insertions(+), 35 deletions(-) + +diff --git a/Makefile b/Makefile +index 5ce30d5f..d53d6a41 100644 +--- a/Makefile ++++ b/Makefile +@@ -53,7 +53,7 @@ manpages = $(man1pages) $(man5pages) $(man7pages) $(man8pages) + all: dracut.pc dracut-install src/skipcpio/skipcpio dracut-util + + %.o : %.c +- $(CC) -c $(CFLAGS) $(CPPFLAGS) $(KMOD_CFLAGS) $< -o $@ ++ $(CC) -c $(CFLAGS) $(CPPFLAGS) $(KMOD_CFLAGS) $(SYSTEMD_CFLAGS) $(if $(SYSTEMD_LIBS),-DHAVE_SYSTEMD) $< -o $@ + + DRACUT_INSTALL_OBJECTS = \ + src/install/dracut-install.o \ +@@ -72,7 +72,7 @@ src/install/util.o: src/install/util.c src/install/util.h src/install/macro.h sr + src/install/strv.o: src/install/strv.c src/install/strv.h src/install/util.h src/install/macro.h src/install/log.h + + src/install/dracut-install: $(DRACUT_INSTALL_OBJECTS) +- $(CC) $(LDFLAGS) -o $@ $(DRACUT_INSTALL_OBJECTS) $(LDLIBS) $(FTS_LIBS) $(KMOD_LIBS) ++ $(CC) $(LDFLAGS) -o $@ $(DRACUT_INSTALL_OBJECTS) $(LDLIBS) $(FTS_LIBS) $(KMOD_LIBS) $(SYSTEMD_LIBS) + + dracut-install: src/install/dracut-install + ln -fs $< $@ +diff --git a/configure b/configure +index 5095078b..8a966333 100755 +--- a/configure ++++ b/configure +@@ -191,6 +191,9 @@ bindir ?= ${bindir:-${prefix}/bin} + KMOD_CFLAGS ?= $(${PKG_CONFIG} --cflags " libkmod >= 23 ") ${KMOD_CFLAGS_EXTRA} + KMOD_LIBS ?= $(${PKG_CONFIG} --libs " libkmod >= 23 ") + FTS_LIBS ?= ${FTS_LIBS} ++# For the sd-json API, which was added in systemd v257. This is optional. ++SYSTEMD_CFLAGS ?= $(${PKG_CONFIG} --cflags "libsystemd >= 257") ++SYSTEMD_LIBS ?= $(${PKG_CONFIG} --libs "libsystemd >= 257") + EOF + + { +diff --git a/dracut.sh b/dracut.sh +index 45527bcd..e544cafb 100755 +--- a/dracut.sh ++++ b/dracut.sh +@@ -1561,6 +1561,17 @@ set_global_var "systemd" "modversion:systemdversion" "0" + set_global_var "libkmod" "depmodd" "/usr/lib/depmod.d" + set_global_var "libkmod" "depmodconfdir" "/etc/depmod.d" + ++# Modules should check for JSON support in dracut-install before using it. ++DRACUT_INSTALL_JSON= ++$DRACUT_INSTALL --json-supported &> /dev/null && DRACUT_INSTALL_JSON=1 ++ ++# systemd started declaring its dlopen dependencies in v256. Checking for these ++# requires JSON support in dracut-install, provided by libsystemd v257. The ++# version in the sysroot may be different to the one used by dracut-install. ++USE_SYSTEMD_DLOPEN_DEPS= ++# shellcheck disable=SC2034 # USE_SYSTEMD_DLOPEN_DEPS is used in modules ++[[ $DRACUT_INSTALL_JSON && ${systemdversion%%.*} -ge 256 ]] && USE_SYSTEMD_DLOPEN_DEPS=1 ++ + if [[ $no_kernel != yes ]] && [[ -d $srcmods ]]; then + if ! [[ -f $srcmods/modules.dep ]]; then + if [[ -n "$(find "$srcmods" -name '*.ko*')" ]]; then +diff --git a/modules.d/00systemd/module-setup.sh b/modules.d/00systemd/module-setup.sh +index 8f25475e..482bdfa1 100755 +--- a/modules.d/00systemd/module-setup.sh ++++ b/modules.d/00systemd/module-setup.sh +@@ -144,7 +144,6 @@ EOF + # Install library file(s) + _arch=${DRACUT_ARCH:-$(uname -m)} + inst_libdir_file \ +- {"tls/$_arch/",tls/,"$_arch/",}"libgcrypt.so*" \ + {"tls/$_arch/",tls/,"$_arch/",}"libbpf.so*" \ + {"tls/$_arch/",tls/,"$_arch/",}"libnss_*" \ + {"tls/$_arch/",tls/,"$_arch/",}"systemd/libsystemd*.so" +diff --git a/modules.d/01systemd-bsod/module-setup.sh b/modules.d/01systemd-bsod/module-setup.sh +index 91b28d7f..cf562ca6 100755 +--- a/modules.d/01systemd-bsod/module-setup.sh ++++ b/modules.d/01systemd-bsod/module-setup.sh +@@ -26,5 +26,7 @@ install() { + "$systemdsystemunitdir"/initrd.target.wants/systemd-bsod.service \ + "$systemdutildir"/systemd-bsod + +- inst_libdir_file "libqrencode.so*" ++ if [[ ! $USE_SYSTEMD_DLOPEN_DEPS ]]; then ++ inst_libdir_file "libqrencode.so*" ++ fi + } +diff --git a/modules.d/01systemd-coredump/module-setup.sh b/modules.d/01systemd-coredump/module-setup.sh +index 6acbe75f..3083f851 100755 +--- a/modules.d/01systemd-coredump/module-setup.sh ++++ b/modules.d/01systemd-coredump/module-setup.sh +@@ -44,10 +44,12 @@ install() { + + # Install library file(s) + _arch=${DRACUT_ARCH:-$(uname -m)} +- inst_libdir_file \ +- {"tls/$_arch/",tls/,"$_arch/",}"liblz4.so.*" \ +- {"tls/$_arch/",tls/,"$_arch/",}"liblzma.so.*" \ +- {"tls/$_arch/",tls/,"$_arch/",}"libzstd.so.*" ++ if [[ ! $USE_SYSTEMD_DLOPEN_DEPS ]]; then ++ inst_libdir_file \ ++ {"tls/$_arch/",tls/,"$_arch/",}"liblz4.so.*" \ ++ {"tls/$_arch/",tls/,"$_arch/",}"liblzma.so.*" \ ++ {"tls/$_arch/",tls/,"$_arch/",}"libzstd.so.*" ++ fi + + # Install the hosts local user configurations if enabled. + if [[ $hostonly ]]; then +diff --git a/modules.d/01systemd-integritysetup/module-setup.sh b/modules.d/01systemd-integritysetup/module-setup.sh +index dffc88ac..804b856e 100755 +--- a/modules.d/01systemd-integritysetup/module-setup.sh ++++ b/modules.d/01systemd-integritysetup/module-setup.sh +@@ -60,6 +60,7 @@ install() { + + # Install required libraries. + _arch=${DRACUT_ARCH:-$(uname -m)} +- inst_libdir_file {"tls/$_arch/",tls/,"$_arch/",}"libcryptsetup.so.*" +- ++ if [[ ! $USE_SYSTEMD_DLOPEN_DEPS ]]; then ++ inst_libdir_file {"tls/$_arch/",tls/,"$_arch/",}"libcryptsetup.so.*" ++ fi + } +diff --git a/modules.d/01systemd-journald/module-setup.sh b/modules.d/01systemd-journald/module-setup.sh +index 77d6a2e9..9f546d1a 100755 +--- a/modules.d/01systemd-journald/module-setup.sh ++++ b/modules.d/01systemd-journald/module-setup.sh +@@ -53,11 +53,13 @@ install() { + + # Install library file(s) + _arch=${DRACUT_ARCH:-$(uname -m)} +- inst_libdir_file \ +- {"tls/$_arch/",tls/,"$_arch/",}"libgcrypt.so*" \ +- {"tls/$_arch/",tls/,"$_arch/",}"liblz4.so.*" \ +- {"tls/$_arch/",tls/,"$_arch/",}"liblzma.so.*" \ +- {"tls/$_arch/",tls/,"$_arch/",}"libzstd.so.*" ++ if [[ ! $USE_SYSTEMD_DLOPEN_DEPS ]]; then ++ inst_libdir_file \ ++ {"tls/$_arch/",tls/,"$_arch/",}"libgcrypt.so*" \ ++ {"tls/$_arch/",tls/,"$_arch/",}"liblz4.so.*" \ ++ {"tls/$_arch/",tls/,"$_arch/",}"liblzma.so.*" \ ++ {"tls/$_arch/",tls/,"$_arch/",}"libzstd.so.*" ++ fi + + # Install the hosts local user configurations if enabled. + if [[ $hostonly ]]; then +diff --git a/modules.d/01systemd-veritysetup/module-setup.sh b/modules.d/01systemd-veritysetup/module-setup.sh +index 9dad8d4f..e3b95303 100755 +--- a/modules.d/01systemd-veritysetup/module-setup.sh ++++ b/modules.d/01systemd-veritysetup/module-setup.sh +@@ -60,6 +60,7 @@ install() { + + # Install required libraries. + _arch=${DRACUT_ARCH:-$(uname -m)} +- inst_libdir_file {"tls/$_arch/",tls/,"$_arch/",}"libcryptsetup.so.*" +- ++ if [[ ! $USE_SYSTEMD_DLOPEN_DEPS ]]; then ++ inst_libdir_file {"tls/$_arch/",tls/,"$_arch/",}"libcryptsetup.so.*" ++ fi + } +diff --git a/src/install/dracut-install.c b/src/install/dracut-install.c +index 83041e40..8769d1a1 100644 +--- a/src/install/dracut-install.c ++++ b/src/install/dracut-install.c +@@ -23,8 +23,10 @@ + #define _GNU_SOURCE + #endif + #include ++#include + #include + #include ++#include + #include + #include + #include +@@ -43,6 +45,11 @@ + #include + #include + #include ++#include ++ ++#ifdef HAVE_SYSTEMD ++#include ++#endif + + #include "log.h" + #include "hashmap.h" +@@ -168,6 +175,25 @@ static inline void destroy_hashmap(Hashmap **hashmap) + + #define _cleanup_destroy_hashmap_ _cleanup_(destroy_hashmap) + ++/* Check whether the given key exists in the hash before duplicating and ++ inserting it. Assumes the value has already been duplicated and is no longer ++ needed if the insertion fails. */ ++static int hashmap_put_strdup_key(Hashmap *h, const char *key, char *value) ++{ ++ if (hashmap_get(h, key)) ++ return 0; ++ ++ char *nkey = strdup(key); ++ ++ if (nkey && hashmap_put(h, nkey, value) != -ENOMEM) ++ return 0; ++ ++ log_error("Out of memory"); ++ free(nkey); ++ free(value); ++ return -ENOMEM; ++} ++ + static size_t dir_len(char const *file) + { + size_t length; +@@ -517,7 +543,7 @@ static char *get_real_file(const char *src, bool fullyresolve) + if (lstat(fullsrcpath, &sb) < 0) + return NULL; + +- switch (sb.st_mode & S_IFMT) { ++ switch (sb.st_mode &S_IFMT) { + case S_IFDIR: + case S_IFREG: + return strdup(fullsrcpath); +@@ -561,18 +587,328 @@ static char *get_real_file(const char *src, bool fullyresolve) + return TAKE_PTR(abspath); + } + +-static int resolve_deps(const char *src) ++/* Check that the ELF header (ehdr) matches the other given ELF header in bits, ++ endianness, OS ABI, and soname, where B is 64 or 32 bit. The SYSV and GNU OS ++ ABIs are compatible, so allow either. Returns libpath if there is a match. */ ++#define CHECK_LIB_MATCH_FOR_BITS(B, match) do { \ ++ if (!match) \ ++ goto finish; \ ++\ ++ Elf##B##_Ehdr *ehdr = (Elf##B##_Ehdr *)map; \ ++ if (ehdr->e_ident[EI_CLASS] == match->e_ident[EI_CLASS] && \ ++ ehdr->e_ident[EI_DATA] == match->e_ident[EI_DATA] && \ ++ (ehdr->e_ident[EI_OSABI] == match->e_ident[EI_OSABI] || \ ++ ehdr->e_ident[EI_OSABI] == ELFOSABI_SYSV || \ ++ ehdr->e_ident[EI_OSABI] == ELFOSABI_GNU) && \ ++ ehdr->e_machine == match->e_machine) { \ ++ if (strcmp(basename, soname) == 0) { \ ++ munmap(map, sb.st_size); \ ++ return libpath; \ ++ } \ ++ } \ ++} while (0) ++ ++/* Check that the given path (dirname + basename) with the given soname matches ++ the given (64 or 32 bit) ELF header. Returns the path if there is a match. */ ++static char *check_lib_match(const char *dirname, const char *basename, const char *soname, const Elf64_Ehdr *match64, ++ const Elf32_Ehdr *match32) ++{ ++ char *libpath = NULL; ++ _asprintf(&libpath, "%s/%s", dirname, basename); ++ ++ _cleanup_close_ int fd = open(libpath, O_RDONLY | O_CLOEXEC); ++ if (fd < 0) ++ goto finish2; ++ ++ struct stat sb; ++ if (fstat(fd, &sb) < 0) ++ goto finish2; ++ ++ void *map = mmap(NULL, sb.st_size, PROT_READ, MAP_PRIVATE, fd, 0); ++ if (map == MAP_FAILED) ++ goto finish2; ++ ++ unsigned char *e_ident = (unsigned char *)map; ++ if (e_ident[EI_MAG0] != ELFMAG0 || ++ e_ident[EI_MAG1] != ELFMAG1 || ++ e_ident[EI_MAG2] != ELFMAG2 || ++ e_ident[EI_MAG3] != ELFMAG3) ++ goto finish; ++ ++ switch (e_ident[EI_CLASS]) { ++ case ELFCLASS32: ++ CHECK_LIB_MATCH_FOR_BITS(32, match32); ++ break; ++ case ELFCLASS64: ++ CHECK_LIB_MATCH_FOR_BITS(64, match64); ++ break; ++ } ++ ++finish: ++ munmap(map, sb.st_size); ++finish2: ++ free(libpath); ++ return NULL; ++} ++ ++/* Search the given library directory (within the sysroot) for a library ++ matching the given soname and (64 or 32 bit) ELF header. Returns the path ++ (with the sysroot) if there is a match. */ ++static char *search_libdir(const char *libdir, const char *soname, const Elf64_Ehdr *match64, const Elf32_Ehdr *match32) ++{ ++ _cleanup_free_ char *sysroot_libdir; ++ _asprintf(&sysroot_libdir, "%s%s", sysrootdir ?: "", libdir); ++ log_debug("Searching '%s' to find %s", sysroot_libdir, soname); ++ ++ /* First check for a filename matching the soname. This is likely to ++ succeed and is very much faster than checking the sonames of every ++ library in the directory below. */ ++ char *res = check_lib_match(sysroot_libdir, soname, soname, match64, match32); ++ if (res) ++ return res; ++ ++ _cleanup_closedir_ DIR *dirp = opendir(sysroot_libdir); ++ if (!dirp) ++ return NULL; ++ ++ struct dirent *entry; ++ while ((entry = readdir(dirp)) != NULL) { ++ if (entry->d_type != DT_REG && entry->d_type != DT_LNK) ++ continue; ++ ++ if (fnmatch("*.so*", entry->d_name, 0) != 0) ++ continue; ++ ++ res = check_lib_match(sysroot_libdir, entry->d_name, soname, match64, match32); ++ if (res) ++ return res; ++ } ++ ++ return NULL; ++} ++ ++/* Read the given ldconf file(s) (within the sysroot, can be a glob pattern) to ++ search for a library matching the given soname and (64 or 32 bit) ELF header. ++ Returns the path (with the sysroot) if there is a match. */ ++static char *search_via_ldconf(const char *conf_pattern, const char *soname, const Elf64_Ehdr *match64, ++ const Elf32_Ehdr *match32) ++{ ++ char line[PATH_MAX]; ++ const char *include_prefix = "include "; ++ size_t include_prefix_len = strlen(include_prefix); ++ ++ _cleanup_free_ char *sysroot_conf_pattern = NULL; ++ _asprintf(&sysroot_conf_pattern, "%s%s", sysrootdir ?: "", conf_pattern); ++ log_debug("Reading '%s' to find %s", sysroot_conf_pattern, soname); ++ ++ _cleanup_globfree_ glob_t globbuf; ++ if (glob(sysroot_conf_pattern, 0, NULL, &globbuf) == 0) { ++ for (size_t i = 0; i < globbuf.gl_pathc; i++) { ++ char *conf_path = globbuf.gl_pathv[i]; ++ _cleanup_fclose_ FILE *file = fopen(conf_path, "r"); ++ if (!file) { ++ log_error("ERROR: cannot open '%s': %m", conf_path); ++ return NULL; ++ } ++ ++ const char *conf_dir = dirname(conf_path); ++ ++ while (fgets(line, sizeof(line), file)) { ++ /* glibc and musl separate with newlines. */ ++ char *newline = strchr(line, '\n'); ++ if (newline) ++ *newline = '\0'; ++ ++ /* musl also separates with colons. Do the same ++ with glibc for simplicity. */ ++ char *colon = strchr(line, ':'); ++ if (colon) ++ *colon = '\0'; ++ ++ /* Ignore any comments. */ ++ char *comment = strchr(line, '#'); ++ if (comment) ++ *comment = '\0'; ++ ++ /* Skip empty lines. */ ++ if (line[0] == '\0') ++ continue; ++ ++ char *result; ++ if (strncmp(line, include_prefix, include_prefix_len) == 0) { ++ const char *include_path = line + include_prefix_len; ++ /* include directives can be absolute or ++ relative. Prepend the current file's ++ directory if relative. */ ++ if (include_path[0] == '/') { ++ result = search_via_ldconf(include_path, soname, match64, match32); ++ } else { ++ _cleanup_free_ char *abs_include_path = NULL; ++ _asprintf(&abs_include_path, "%s/%s", conf_dir + sysrootdirlen, include_path); ++ result = search_via_ldconf(abs_include_path, soname, match64, match32); ++ } ++ } else { ++ result = search_libdir(line, soname, match64, match32); ++ } ++ if (result) ++ return result; ++ } ++ } ++ } ++ ++ return NULL; ++} ++ ++/* Expand $ORIGIN and $LIB variables in the given R(UN)PATH entry. $ORIGIN ++ expands to the directory of the given src path. $LIB expands to lib if ++ match64 is NULL or lib64 otherwise. Returns a newly allocated string even if ++ no expansion was necessary. */ ++static char *expand_runpath(char *input, const char *src, const Elf64_Ehdr *match64) ++{ ++ regex_t regex; ++ regmatch_t rmatch[3]; /* 0: full match, 1: without brackets, 2: with brackets */ ++ ++ if (regcomp(®ex, "\\$([A-Z]+|\\{([A-Z]+)\\})", REG_EXTENDED) != 0) { ++ log_error("ERROR: Could not compile RUNPATH regex"); ++ return NULL; ++ } ++ ++ char *result = NULL, *current = input; ++ int offset = 0; ++ ++ while (regexec(®ex, current + offset, 3, rmatch, 0) == 0) { ++ char *varname = NULL; ++ _cleanup_free_ char *varval = NULL; ++ size_t varname_len, varval_len; ++ ++ /* Determine which group matched, with or without brackets. */ ++ int rgroup = rmatch[1].rm_so != -1 ? 1 : 2; ++ varname_len = rmatch[rgroup].rm_eo - rmatch[rgroup].rm_so; ++ varname = current + offset + rmatch[rgroup].rm_so; ++ ++ if (strncmp(varname, "ORIGIN", varname_len) == 0) { ++ varval = dirname_malloc(src); ++ } else if (strncmp(varname, "LIB", varname_len) == 0) { ++ varval = strdup(match64 ? "lib64" : "lib"); ++ } else { ++ /* If the variable is unrecognised, leave it as-is. */ ++ offset += rmatch[0].rm_eo; ++ continue; ++ } ++ ++ if (!varval) ++ goto oom; ++ ++ varval_len = strlen(varval); ++ size_t prefix_len = offset + rmatch[0].rm_so; ++ size_t suffix_len = strlen(current) - (offset + rmatch[0].rm_eo); ++ ++ char *replaced = realloc(result, prefix_len + varval_len + suffix_len + 1); ++ if (!replaced) ++ goto oom; ++ ++ result = replaced; ++ strcpy(result + prefix_len, varval); ++ strcpy(result + prefix_len + varval_len, current + offset + rmatch[0].rm_eo); ++ ++ current = result; ++ offset = prefix_len + varval_len; ++ } ++ ++ regfree(®ex); ++ return result ?: strdup(current); ++ ++oom: ++ log_error("Out of memory"); ++ free(result); ++ regfree(®ex); ++ return NULL; ++} ++ ++/* Adjust the endianness of the given value of the given SIZE using ELF header ++ ehdr. The size sadly cannot be determined automatically using sizeof because ++ that is expanded using the C compiler rather than the preprocessor. */ ++#define ELF_BYTESWAP(SIZE, value) (ehdr->e_ident[EI_DATA] == ELFDATA2MSB ? be##SIZE##toh(value) : le##SIZE##toh(value)) ++ ++/* Get a pointer to the ELF header map's section header string table, where B is ++ 64 or 32 bit. Sanity checks the ELF structure to avoid crashes. */ ++#define PARSE_ELF_START(B, map) \ ++ Elf##B##_Ehdr *ehdr = (Elf##B##_Ehdr *)map; \ ++\ ++ if (sizeof(Elf##B##_Ehdr) > src_len || \ ++ ELF_BYTESWAP(B, ehdr->e_shoff) > src_len || \ ++ ELF_BYTESWAP(16, ehdr->e_shstrndx) >= ELF_BYTESWAP(16, ehdr->e_shnum)) \ ++ break; \ ++\ ++ Elf##B##_Shdr *shdr = (Elf##B##_Shdr *)((char *)map + ELF_BYTESWAP(B, ehdr->e_shoff)); \ ++ const char *shstrtab = (char *)map + ELF_BYTESWAP(B, shdr[ELF_BYTESWAP(16, ehdr->e_shstrndx)].sh_offset); ++ ++/* Expand the R(UN)PATH of the ELF header map and search it for a library ++ matching soname and match64/match32. map must point to the same header as ++ match64/match32. Returns the path (with the sysroot) if there is a match. */ ++#define FIND_LIBRARY_RUNPATH_FOR_BITS(B, map) do { \ ++ PARSE_ELF_START(B, map); \ ++ bool seen_runpath = false; \ ++\ ++ for (size_t i = 0; i < ELF_BYTESWAP(16, ehdr->e_shnum); i++) { \ ++ if (strcmp(&shstrtab[ELF_BYTESWAP(32, shdr[i].sh_name)], ".dynamic") != 0) \ ++ continue; \ ++\ ++ Elf##B##_Dyn *dyn = (Elf##B##_Dyn *)((char *)map + ELF_BYTESWAP(B, shdr[i].sh_offset)); \ ++ for (Elf##B##_Dyn *d = dyn; ELF_BYTESWAP(32, d->d_tag) != DT_NULL; d++) { \ ++ if (ELF_BYTESWAP(B, d->d_tag) == DT_RUNPATH) \ ++ seen_runpath = true; /* RUNPATH has precedence over RPATH. */ \ ++ else if (seen_runpath || ELF_BYTESWAP(B, d->d_tag) != DT_RPATH) \ ++ continue; \ ++\ ++ char *runpath = (char *)map + ELF_BYTESWAP(B, shdr[ELF_BYTESWAP(32, shdr[i].sh_link)].sh_offset) + ELF_BYTESWAP(B, d->d_un.d_val); \ ++ _cleanup_free_ char *expanded = expand_runpath(runpath, src, match64); \ ++ if (!expanded) \ ++ continue; \ ++\ ++ for (char *token = strtok(expanded, ":"); token; token = strtok(NULL, ":")) { \ ++ char *res = search_libdir(token, soname, match64, match32); \ ++ if (res) \ ++ return res; \ ++ } \ ++ } \ ++ } \ ++} while (0) ++ ++/* Given an soname and (64 or 32 bit) ELF header, search for a matching library ++ in the R(UN)PATH of that header, the directories referenced by ldconf files, ++ and some default locations. src must be the path (with the sysroot) to the ++ ELF file and src_len must be that file's length in bytes. Returns the path ++ (with the sysroot) if there is a match. */ ++static char *find_library(const char *soname, const char *src, size_t src_len, const Elf64_Ehdr *match64, ++ const Elf32_Ehdr *match32) ++{ ++ if (match64) ++ FIND_LIBRARY_RUNPATH_FOR_BITS(64, match64); ++ else if (match32) ++ FIND_LIBRARY_RUNPATH_FOR_BITS(32, match32); ++ ++ /* There is no definitive way to determine the libc so just check for ++ musl and glibc ldconf files. musl hardcodes its default locations. It ++ is impossible to determine glibc's default locations, but this set is ++ practically universal. It is safe to check lib64 for 32-bit libraries ++ because we include the class (64-bit or 32-bit) when matching. */ ++ return search_via_ldconf("/etc/ld-musl-*.path", soname, match64, match32) ?: ++ search_via_ldconf("/etc/ld.so.conf", soname, match64, match32) ?: ++ search_libdir("/lib64", soname, match64, match32) ?: ++ search_libdir("/usr/lib64", soname, match64, match32) ?: ++ search_libdir("/usr/local/lib64", soname, match64, match32) ?: ++ search_libdir("/lib", soname, match64, match32) ?: ++ search_libdir("/usr/lib", soname, match64, match32) ?: ++ search_libdir("/usr/local/lib", soname, match64, match32); ++} ++ ++static int resolve_deps_ldd(const char *src, const char *fullsrcpath) + { + int ret = 0, err; + + _cleanup_free_ char *buf = NULL; + size_t linesize = LINE_MAX + 1; +- _cleanup_free_ char *fullsrcpath = NULL; +- +- fullsrcpath = get_real_file(src, true); +- log_debug("resolve_deps('%s') -> get_real_file('%s', true) = '%s'", src, src, fullsrcpath); +- if (!fullsrcpath) +- return 0; + + buf = malloc(linesize); + if (buf == NULL) +@@ -704,6 +1040,195 @@ static int resolve_deps(const char *src) + return ret; + } + ++#ifdef HAVE_SYSTEMD ++ ++/* Parse the given .note.dlopen JSON (https://systemd.io/ELF_DLOPEN_METADATA/) ++ in the given note index and find each dependent library, ensuring it matches ++ the given (64 or 32 bit) ELF header. Each library found is added to deps. ++ Dependencies already found in this chain must be given in pdeps. Failure to ++ parse the JSON or find a library is considered non-fatal. */ ++static void resolve_deps_dlopen_parse_json(Hashmap *pdeps, Hashmap *deps, const char *fullsrcpath, size_t src_len, ++ const char *json, size_t note_idx, const Elf64_Ehdr *match64, const Elf32_Ehdr *match32) ++{ ++ _cleanup_(sd_json_variant_unrefp) sd_json_variant *dlopen_json = NULL; ++ if (sd_json_parse(json, 0, &dlopen_json, NULL, NULL) != 0 || !sd_json_variant_is_array(dlopen_json)) { ++ log_warning("WARNING: .note.dlopen entry #%zd is not a JSON array in '%s'", note_idx, fullsrcpath); ++ return; ++ } ++ ++ for (size_t entry_idx = 0; entry_idx < sd_json_variant_elements(dlopen_json); entry_idx++) { ++ sd_json_variant *entry = sd_json_variant_by_index(dlopen_json, entry_idx); ++ sd_json_variant *sonames = sd_json_variant_by_key(entry, "soname"); ++ if (!sonames || !sd_json_variant_is_array(sonames)) { ++ log_warning("WARNING: soname array missing from .note.dlopen entry #%zd.%zd in '%s'", note_idx, entry_idx, fullsrcpath); ++ return; ++ } ++ ++ for (size_t soname_idx = 0; soname_idx < sd_json_variant_elements(sonames); soname_idx++) { ++ sd_json_variant *soname_json = sd_json_variant_by_index(sonames, soname_idx); ++ if (!sd_json_variant_is_string(soname_json)) { ++ log_warning("WARNING: soname #%zd of .note.dlopen entry #%zd.%zd is not a string in '%s'", soname_idx, note_idx, ++ entry_idx, fullsrcpath); ++ return; ++ } ++ ++ const char *soname = sd_json_variant_string(soname_json); ++ if (hashmap_get(pdeps, soname)) ++ continue; ++ ++ char *library = find_library(soname, fullsrcpath, src_len, match64, match32); ++ if (!library || hashmap_put_strdup_key(deps, soname, library) < 0) ++ log_warning("WARNING: could not locate dlopen dependency %s requested by '%s'", soname, fullsrcpath); ++ } ++ } ++} ++ ++/* Given the ELF header map, also represented by match64/match32 and where B is ++ 64 or 32 bit, check .note.dlopen entries for dependencies. See above. */ ++#define RESOLVE_DEPS_DLOPEN_FOR_BITS(B, match64, match32) do { \ ++ PARSE_ELF_START(B, map); \ ++ size_t note_idx = -1; \ ++\ ++ for (size_t i = 0; i < ELF_BYTESWAP(16, ehdr->e_shnum); i++) { \ ++ if ((char*)shdr + i * sizeof(Elf##B##_Shdr) > (char*)map + src_len) \ ++ break; \ ++ if (strcmp(&shstrtab[ELF_BYTESWAP(32, shdr[i].sh_name)], ".note.dlopen") != 0) \ ++ continue; \ ++\ ++ const char *note_offset = (char *)map + ELF_BYTESWAP(B, shdr[i].sh_offset); \ ++ const char *note_end = note_offset + ELF_BYTESWAP(32, shdr[i].sh_size); \ ++\ ++ if (note_offset < (char*)map || note_end > (char*)map + src_len || note_end < note_offset) \ ++ continue; \ ++\ ++ while (note_offset < note_end) { \ ++ Elf##B##_Nhdr *nhdr = (Elf##B##_Nhdr *)note_offset; \ ++ note_offset += sizeof(Elf##B##_Nhdr); \ ++\ ++ /* We don't need the name, checking the type is enough. */ \ ++ note_offset += (ELF_BYTESWAP(32, nhdr->n_namesz) + 3) & ~3; /* Align to 4 bytes */ \ ++\ ++ const char *note_desc = note_offset; \ ++ note_offset += (ELF_BYTESWAP(32, nhdr->n_descsz) + 3) & ~3; /* Align to 4 bytes */ \ ++ if (note_offset > (char*)map + src_len) \ ++ break; \ ++\ ++ if (ELF_BYTESWAP(32, nhdr->n_type) != 0x407c0c0a) \ ++ continue; \ ++\ ++ note_idx++; \ ++ resolve_deps_dlopen_parse_json(pdeps, deps, fullsrcpath, src_len, note_desc, note_idx, match64, match32); \ ++ } \ ++ } \ ++} while (0) ++ ++static int resolve_deps(const char *src, Hashmap *pdeps); ++ ++static int resolve_deps_dlopen(const char *src, const char *fullsrcpath, Hashmap *pdeps) ++{ ++ _cleanup_close_ int fd = open(fullsrcpath, O_RDONLY | O_CLOEXEC); ++ if (fd < 0) { ++ log_error("ERROR: cannot open '%s': %m", fullsrcpath); ++ return -errno; ++ } ++ ++ struct stat sb; ++ if (fstat(fd, &sb) < 0) { ++ log_error("ERROR: cannot stat '%s': %m", fullsrcpath); ++ return -errno; ++ } ++ ++ size_t src_len = sb.st_size; ++ void *map = mmap(NULL, src_len, PROT_READ, MAP_PRIVATE, fd, 0); ++ if (map == MAP_FAILED) { ++ log_error("ERROR: cannot mmap '%s': %m", fullsrcpath); ++ return -errno; ++ } ++ ++ /* It would be easiest to blindly install dependencies as we find them ++ depth-first, but this does not work in practise. We need to track ++ which dependencies are already found to avoid loops. We also need to ++ install them breadth-first because of how RUNPATH works. systemd is a ++ good example. libsystemd-core depends on libsystemd-shared. Neither ++ is in the default library path, but libsystemd-core lacks a RUNPATH, ++ so it cannot find libsystemd-shared by itself. See for yourself with ++ ldd. It must be found in the context of an executable with a RUNPATH ++ that also depends on libsystemd-shared, such as systemd-executor. The ++ RUNPATH only applies to direct dependencies, not subdependencies, so ++ libsystemd-shared needs to be found as a direct dependency of ++ systemd-executor before we check libsystemd-core's dependencies. ++ Therefore, pdeps above holds the dependencies we have already found, ++ deps holds the dependencies found in this iteration, and ndeps is ++ used to combine them into the next iteration's pdeps. */ ++ Hashmap *ndeps = hashmap_new(string_hash_func, string_compare_func); ++ Hashmap *deps = hashmap_new(string_hash_func, string_compare_func); ++ int ret = 0; ++ ++ unsigned char *e_ident = (unsigned char *)map; ++ if (e_ident[EI_MAG0] != ELFMAG0 || ++ e_ident[EI_MAG1] != ELFMAG1 || ++ e_ident[EI_MAG2] != ELFMAG2 || ++ e_ident[EI_MAG3] != ELFMAG3) ++ goto finish; ++ ++ switch (e_ident[EI_CLASS]) { ++ case ELFCLASS32: ++ RESOLVE_DEPS_DLOPEN_FOR_BITS(32, NULL, ehdr); ++ break; ++ case ELFCLASS64: ++ RESOLVE_DEPS_DLOPEN_FOR_BITS(64, ehdr, NULL); ++ break; ++ default: ++ log_error("ERROR: '%s' has an unknown ELF class", fullsrcpath); ++ ret = -1; ++ } ++ ++ if (hashmap_merge(ndeps, pdeps) < 0 || hashmap_merge(ndeps, deps) < 0) ++ goto finish; ++ ++ char *key, *library; ++ Iterator i; ++ HASHMAP_FOREACH(library, deps, i) { ++ ret += library_install(src, library); ++ ret += resolve_deps(library, ndeps); ++ } ++ ++finish: ++ munmap(map, src_len); ++ hashmap_free(ndeps); ++ ++ HASHMAP_FOREACH(library, deps, i) { ++ item_free(library); ++ } ++ ++ while ((key = hashmap_steal_first_key(deps))) ++ item_free(key); ++ ++ hashmap_free(deps); ++ return ret; ++} ++ ++#endif ++ ++/* Recursively check the given file for dependencies and install them. pdeps is ++ for dependencies already found in this chain and should initially be NULL. ++ Both ELF binaries and scripts with shebangs are handled. */ ++static int resolve_deps(const char *src, Hashmap *pdeps) ++{ ++ _cleanup_free_ char *fullsrcpath = NULL; ++ ++ fullsrcpath = get_real_file(src, true); ++ log_debug("resolve_deps('%s') -> get_real_file('%s', true) = '%s'", src, src, fullsrcpath); ++ if (!fullsrcpath) ++ return 0; ++ ++ return resolve_deps_ldd(src, fullsrcpath) ++#ifdef HAVE_SYSTEMD ++ ?: resolve_deps_dlopen(src, fullsrcpath, pdeps) ++#endif ++ ; ++} ++ + /* Install "..hmac" file for FIPS self-checks */ + static int hmac_install(const char *src, const char *dst, const char *hmacpath) + { +@@ -880,9 +1405,9 @@ static int dracut_install(const char *orig_src, const char *orig_dst, bool isdir + if (resolvedeps && S_ISREG(sb.st_mode) && (sb.st_mode & (S_IXUSR | S_IXGRP | S_IXOTH))) { + log_debug("'%s' already exists, but checking for any deps", fulldstpath); + if (sysrootdirlen && (strncmp(fulldstpath, sysrootdir, sysrootdirlen) == 0)) +- ret = resolve_deps(fulldstpath + sysrootdirlen); ++ ret = resolve_deps(fulldstpath + sysrootdirlen, NULL); + else +- ret = resolve_deps(fullsrcpath); ++ ret = resolve_deps(fullsrcpath, NULL); + } else + log_debug("'%s' already exists", fulldstpath); + } else { +@@ -975,9 +1500,9 @@ static int dracut_install(const char *orig_src, const char *orig_dst, bool isdir + if (resolvedeps) { + /* ensure fullsrcpath contains sysrootdir */ + if (sysrootdirlen && (strncmp(fullsrcpath, sysrootdir, sysrootdirlen) == 0)) +- ret += resolve_deps(fullsrcpath + sysrootdirlen); ++ ret += resolve_deps(fullsrcpath + sysrootdirlen, NULL); + else +- ret += resolve_deps(fullsrcpath); ++ ret += resolve_deps(fullsrcpath, NULL); + } + if (arg_hmac) { + /* copy .hmac files also */ +@@ -1058,10 +1583,11 @@ static void usage(int status) + " -S --mod-filter-nosymbol Exclude kernel modules by symbol regexp\n" + " -N --mod-filter-noname Exclude kernel modules by name regexp\n" + "\n" +- " -v --verbose Show more output\n" +- " --debug Show debug output\n" +- " --version Show package version\n" +- " -h --help Show this help\n" ++ " --json-supported Show whether this build supports JSON\n" ++ " -v --verbose Show more output\n" ++ " --debug Show debug output\n" ++ " --version Show package version\n" ++ " -h --help Show this help\n" + "\n", program_invocation_short_name, program_invocation_short_name, program_invocation_short_name); + exit(status); + } +@@ -1076,7 +1602,8 @@ static int parse_argv(int argc, char *argv[]) + ARG_MODALIAS, + ARG_KERNELDIR, + ARG_FIRMWAREDIRS, +- ARG_DEBUG ++ ARG_DEBUG, ++ ARG_JSON_SUPPORTED, + }; + + static struct option const options[] = { +@@ -1104,6 +1631,7 @@ static int parse_argv(int argc, char *argv[]) + {"silent", no_argument, NULL, ARG_SILENT}, + {"kerneldir", required_argument, NULL, ARG_KERNELDIR}, + {"firmwaredirs", required_argument, NULL, ARG_FIRMWAREDIRS}, ++ {"json-supported", no_argument, NULL, ARG_JSON_SUPPORTED}, + {NULL, 0, NULL, 0} + }; + +@@ -1207,6 +1735,14 @@ static int parse_argv(int argc, char *argv[]) + case 'h': + usage(EXIT_SUCCESS); + break; ++ case ARG_JSON_SUPPORTED: ++#ifdef HAVE_SYSTEMD ++ puts("JSON is supported"); ++ return 0; ++#else ++ puts("JSON is not supported"); ++ return -1; ++#endif + default: + usage(EXIT_FAILURE); + } +@@ -1293,7 +1829,7 @@ static int resolve_lazy(int argc, char **argv) + item = strdup(p); + hashmap_put(items, item, item); + +- ret += resolve_deps(src); ++ ret += resolve_deps(src, NULL); + } + return ret; + } +-- +2.48.1 + + +From 2af7fbafaa02d7edfe387829bb49ed3d5bb47454 Mon Sep 17 00:00:00 2001 +From: James Le Cuirot +Date: Tue, 4 Mar 2025 11:09:32 +0000 +Subject: [PATCH 06/12] feat(dracut-install): extend new ELF parsing code to + replace ldd calls + +Now that dracut-install has its own ELF parsing code to handle dlopen +dependencies, it is only a small additional step to also handle +traditional DT_NEEDED dependencies, removing the need to call ldd, which +is not cross-friendly. + +This avoids the earlier issue seen with musl in #1087. + +We should no longer directly install libsystemd*.so because +libsystemd-core does not have the RUNPATH to find libsystemd-shared by +itself. Both get pulled in by the main systemd binary anyway. ldd had +the same issue, dracut-install silently ignored the failure. + +Signed-off-by: James Le Cuirot +--- + dracut.sh | 6 +- + modules.d/00systemd/module-setup.sh | 3 +- + src/install/dracut-install.c | 288 ++++++++++------------------ + 3 files changed, 106 insertions(+), 191 deletions(-) + +diff --git a/dracut.sh b/dracut.sh +index e544cafb..75d92738 100755 +--- a/dracut.sh ++++ b/dracut.sh +@@ -1357,11 +1357,7 @@ if [[ $early_microcode == yes ]] || { [[ $acpi_override == yes ]] && [[ -d $acpi + mkdir "$early_cpio_dir" + fi + +-if ${DRACUT_LDD:-ldd} "${dracutsysrootdir}/bin/sh" | grep -q musl &> /dev/null; then +- musl=1 +-fi +- +-[[ "$dracutsysrootdir" ]] || [[ "$noexec" ]] || [[ "$musl" ]] || export DRACUT_RESOLVE_LAZY="1" ++[[ "$dracutsysrootdir" ]] || [[ "$noexec" ]] || export DRACUT_RESOLVE_LAZY="1" + + if [[ $print_cmdline ]]; then + stdloglvl=0 +diff --git a/modules.d/00systemd/module-setup.sh b/modules.d/00systemd/module-setup.sh +index 482bdfa1..747f09f0 100755 +--- a/modules.d/00systemd/module-setup.sh ++++ b/modules.d/00systemd/module-setup.sh +@@ -145,6 +145,5 @@ EOF + _arch=${DRACUT_ARCH:-$(uname -m)} + inst_libdir_file \ + {"tls/$_arch/",tls/,"$_arch/",}"libbpf.so*" \ +- {"tls/$_arch/",tls/,"$_arch/",}"libnss_*" \ +- {"tls/$_arch/",tls/,"$_arch/",}"systemd/libsystemd*.so" ++ {"tls/$_arch/",tls/,"$_arch/",}"libnss_*" + } +diff --git a/src/install/dracut-install.c b/src/install/dracut-install.c +index 8769d1a1..f194532f 100644 +--- a/src/install/dracut-install.c ++++ b/src/install/dracut-install.c +@@ -84,7 +84,6 @@ static char *kerneldir = NULL; + static size_t kerneldirlen = 0; + static char **firmwaredirs = NULL; + static char **pathdirs; +-static char *ldd = NULL; + static char *logdir = NULL; + static char *logfile = NULL; + FILE *logfile_f = NULL; +@@ -903,143 +902,6 @@ static char *find_library(const char *soname, const char *src, size_t src_len, c + search_libdir("/usr/local/lib", soname, match64, match32); + } + +-static int resolve_deps_ldd(const char *src, const char *fullsrcpath) +-{ +- int ret = 0, err; +- +- _cleanup_free_ char *buf = NULL; +- size_t linesize = LINE_MAX + 1; +- +- buf = malloc(linesize); +- if (buf == NULL) +- return -errno; +- +- if (strstr(src, ".so") == NULL) { +- _cleanup_close_ int fd = -1; +- fd = open(fullsrcpath, O_RDONLY | O_CLOEXEC); +- if (fd < 0) +- return -errno; +- +- ret = read(fd, buf, linesize - 1); +- if (ret == -1) +- return -errno; +- +- buf[ret] = '\0'; +- if (buf[0] == '#' && buf[1] == '!') { +- /* we have a shebang */ +- char *p, *q; +- for (p = &buf[2]; *p && isspace(*p); p++) ; +- for (q = p; *q && (!isspace(*q)); q++) ; +- *q = '\0'; +- log_debug("Script install: '%s'", p); +- ret = dracut_install(p, p, false, true, false); +- if (ret != 0) +- log_error("ERROR: failed to install '%s'", p); +- return ret; +- } +- } +- +- int fds[2]; +- FILE *fptr; +- if (pipe2(fds, O_CLOEXEC) == -1 || (fptr = fdopen(fds[0], "r")) == NULL) { +- log_error("ERROR: pipe stream initialization for '%s' failed: %m", ldd); +- exit(EXIT_FAILURE); +- } +- +- log_debug("%s %s", ldd, fullsrcpath); +- pid_t ldd_pid; +- if ((ldd_pid = fork()) == 0) { +- dup2(fds[1], 1); +- dup2(fds[1], 2); +- putenv("LC_ALL=C"); +- execlp(ldd, ldd, fullsrcpath, (char *)NULL); +- _exit(errno == ENOENT ? 127 : 126); +- } +- close(fds[1]); +- +- ret = 0; +- +- while (getline(&buf, &linesize, fptr) >= 0) { +- char *p; +- +- log_debug("ldd: '%s'", buf); +- +- if (strstr(buf, "you do not have execution permission")) { +- log_error("%s", buf); +- ret += 1; +- break; +- } +- +- /* errors from cross-compiler-ldd */ +- if (strstr(buf, "unable to find sysroot")) { +- log_error("%s", buf); +- ret += 1; +- break; +- } +- +- /* musl ldd */ +- if (strstr(buf, "Not a valid dynamic program")) +- break; +- +- /* glibc */ +- if (strstr(buf, "cannot execute binary file")) +- continue; +- +- if (strstr(buf, "not a dynamic executable")) +- break; +- +- if (strstr(buf, "loader cannot load itself")) +- break; +- +- if (strstr(buf, "not regular file")) +- break; +- +- if (strstr(buf, "cannot read header")) +- break; +- +- if (strstr(buf, "cannot be preloaded")) +- continue; +- +- if (strstr(buf, destrootdir)) +- break; +- +- p = buf; +- if (strchr(p, '$')) { +- /* take ldd variable expansion into account */ +- p = strstr(p, "=>"); +- if (!p) +- p = buf; +- } +- p = strchr(p, '/'); +- +- if (p) { +- char *q; +- +- for (q = p; *q && *q != ' ' && *q != '\n'; q++) ; +- *q = '\0'; +- +- ret += library_install(src, p); +- +- } +- } +- +- fclose(fptr); +- while (waitpid(ldd_pid, &err, 0) == -1) { +- if (errno != EINTR) { +- log_error("ERROR: waitpid() failed: %m"); +- return 1; +- } +- } +- err = WIFSIGNALED(err) ? 128 + WTERMSIG(err) : WEXITSTATUS(err); +- /* ldd has error conditions we largely don't care about ("not a dynamic executable", &c.): +- only error out on hard errors (ENOENT, ENOEXEC, signals) */ +- if (err >= 126) { +- log_error("ERROR: '%s %s' failed with %d", ldd, fullsrcpath, err); +- return err; +- } else +- return ret; +-} +- + #ifdef HAVE_SYSTEMD + + /* Parse the given .note.dlopen JSON (https://systemd.io/ELF_DLOPEN_METADATA/) +@@ -1122,10 +984,79 @@ static void resolve_deps_dlopen_parse_json(Hashmap *pdeps, Hashmap *deps, const + } \ + } while (0) + +-static int resolve_deps(const char *src, Hashmap *pdeps); ++#endif + +-static int resolve_deps_dlopen(const char *src, const char *fullsrcpath, Hashmap *pdeps) ++/* Given the ELF header map, also represented by match64/match32 and where B is ++ 64 or 32 bit, check PT_INTERP and DT_NEEDED entries for dependencies. */ ++#define RESOLVE_DEPS_NEEDED_FOR_BITS(B, match64, match32) do { \ ++ PARSE_ELF_START(B, map); \ ++\ ++ if (ELF_BYTESWAP(16, ehdr->e_type) == ET_EXEC || ELF_BYTESWAP(16, ehdr->e_type) == ET_DYN) { \ ++ for (size_t ph_idx = 0; ph_idx < ELF_BYTESWAP(16, ehdr->e_phnum); ph_idx++) { \ ++ Elf##B##_Phdr *phdr = (Elf##B##_Phdr *)((char *)map + ELF_BYTESWAP(B, ehdr->e_phoff) + ph_idx * ELF_BYTESWAP(16, ehdr->e_phentsize)); \ ++ if ((char *)phdr < (char *)map || (char *)phdr + sizeof(Elf##B##_Phdr) > (char *)map + src_len) \ ++ break; \ ++ if (ELF_BYTESWAP(32, phdr->p_type) != PT_INTERP) \ ++ continue; \ ++\ ++ const char *interpreter = (const char *)map + ELF_BYTESWAP(B, phdr->p_offset); \ ++ if (interpreter < (char *)map || interpreter > (char *)map + src_len) \ ++ break; \ ++ if (hashmap_get(pdeps, interpreter)) \ ++ continue; \ ++\ ++ char *value = strdup(interpreter); \ ++ if (!value || hashmap_put_strdup_key(deps, interpreter, value) < 0) { \ ++ log_error("ERROR: could not handle interpreter for '%s'", fullsrcpath); \ ++ ret = -1; \ ++ } \ ++ break; \ ++ } \ ++ } \ ++\ ++ for (size_t i = 0; i < ELF_BYTESWAP(16, ehdr->e_shnum); i++) { \ ++ if ((char*)&shdr[i] < (char*)map || (char*)&shdr[i] + sizeof(Elf##B##_Shdr) > (char*)map + src_len) \ ++ break; \ ++ if (strcmp(&shstrtab[ELF_BYTESWAP(32, shdr[i].sh_name)], ".dynamic") != 0) \ ++ continue; \ ++\ ++ Elf##B##_Dyn *dyn = (Elf##B##_Dyn *)((char *)map + ELF_BYTESWAP(B, shdr[i].sh_offset)); \ ++ if ((char *)dyn < (char *)map || (char *)dyn > (char *)map + src_len) \ ++ break; \ ++\ ++ for (Elf##B##_Dyn *d = dyn; ELF_BYTESWAP(32, d->d_tag) != DT_NULL; d++) { \ ++ if ((char *)d < (char *)map || (char *)d + sizeof(Elf##B##_Dyn) > (char *)map + src_len) \ ++ break; \ ++ if (ELF_BYTESWAP(B, d->d_tag) != DT_NEEDED) \ ++ continue; \ ++\ ++ const char *soname = (char *)map + ELF_BYTESWAP(B, shdr[ELF_BYTESWAP(32, shdr[i].sh_link)].sh_offset) + ELF_BYTESWAP(B, d->d_un.d_val); \ ++ if ((char *)soname < (char *)map || (char *)soname > (char *)map + src_len) \ ++ break; \ ++ if (hashmap_get(pdeps, soname)) \ ++ continue; \ ++\ ++ char* library = find_library(soname, fullsrcpath, src_len, match64, match32); \ ++ if (!library || hashmap_put_strdup_key(deps, soname, library) < 0) { \ ++ log_error("ERROR: could not locate dependency %s requested by '%s'", soname, fullsrcpath); \ ++ ret = -1; \ ++ } \ ++ } \ ++ } \ ++} while (0) ++ ++/* Recursively check the given file for dependencies and install them. pdeps is ++ for dependencies already found in this chain and should initially be NULL. ++ Both ELF binaries and scripts with shebangs are handled. */ ++static int resolve_deps(const char *src, Hashmap *pdeps) + { ++ _cleanup_free_ char *fullsrcpath = NULL; ++ ++ fullsrcpath = get_real_file(src, true); ++ log_debug("resolve_deps('%s') -> get_real_file('%s', true) = '%s'", src, src, fullsrcpath); ++ if (!fullsrcpath) ++ return 0; ++ + _cleanup_close_ int fd = open(fullsrcpath, O_RDONLY | O_CLOEXEC); + if (fd < 0) { + log_error("ERROR: cannot open '%s': %m", fullsrcpath); +@@ -1164,6 +1095,18 @@ static int resolve_deps_dlopen(const char *src, const char *fullsrcpath, Hashmap + Hashmap *deps = hashmap_new(string_hash_func, string_compare_func); + int ret = 0; + ++ char *shebang = (char *)map; ++ if (shebang[0] == '#' && shebang[1] == '!') { ++ char *p, *q; ++ for (p = &shebang[2]; *p && isspace(*p); p++) ; ++ for (q = p; *q && (!isspace(*q)); q++) ; ++ char *interpreter = strndup(p, q - p); ++ log_debug("Script install: '%s'", interpreter); ++ ret = dracut_install(interpreter, interpreter, false, true, false); ++ free(interpreter); ++ goto finish; ++ } ++ + unsigned char *e_ident = (unsigned char *)map; + if (e_ident[EI_MAG0] != ELFMAG0 || + e_ident[EI_MAG1] != ELFMAG1 || +@@ -1173,10 +1116,16 @@ static int resolve_deps_dlopen(const char *src, const char *fullsrcpath, Hashmap + + switch (e_ident[EI_CLASS]) { + case ELFCLASS32: ++ RESOLVE_DEPS_NEEDED_FOR_BITS(32, NULL, ehdr); ++#ifdef HAVE_SYSTEMD + RESOLVE_DEPS_DLOPEN_FOR_BITS(32, NULL, ehdr); ++#endif + break; + case ELFCLASS64: ++ RESOLVE_DEPS_NEEDED_FOR_BITS(64, ehdr, NULL); ++#ifdef HAVE_SYSTEMD + RESOLVE_DEPS_DLOPEN_FOR_BITS(64, ehdr, NULL); ++#endif + break; + default: + log_error("ERROR: '%s' has an unknown ELF class", fullsrcpath); +@@ -1208,27 +1157,6 @@ finish: + return ret; + } + +-#endif +- +-/* Recursively check the given file for dependencies and install them. pdeps is +- for dependencies already found in this chain and should initially be NULL. +- Both ELF binaries and scripts with shebangs are handled. */ +-static int resolve_deps(const char *src, Hashmap *pdeps) +-{ +- _cleanup_free_ char *fullsrcpath = NULL; +- +- fullsrcpath = get_real_file(src, true); +- log_debug("resolve_deps('%s') -> get_real_file('%s', true) = '%s'", src, src, fullsrcpath); +- if (!fullsrcpath) +- return 0; +- +- return resolve_deps_ldd(src, fullsrcpath) +-#ifdef HAVE_SYSTEMD +- ?: resolve_deps_dlopen(src, fullsrcpath, pdeps) +-#endif +- ; +-} +- + /* Install "..hmac" file for FIPS self-checks */ + static int hmac_install(const char *src, const char *dst, const char *hmacpath) + { +@@ -1346,7 +1274,7 @@ static int dracut_install(const char *orig_src, const char *orig_dst, bool isdir + bool src_islink = false; + bool src_isdir = false; + mode_t src_mode = 0; +- char *i = NULL; ++ char *hash_path = NULL; + const char *src, *dst; + + if (sysrootdirlen) { +@@ -1381,8 +1309,10 @@ static int dracut_install(const char *orig_src, const char *orig_dst, bool isdir + + if (lstat(fullsrcpath, &sb) < 0) { + if (!isdir) { +- i = strdup(src); +- hashmap_put(items_failed, i, i); ++ hash_path = strdup(src); ++ if (!hash_path) ++ return -ENOMEM; ++ hashmap_put(items_failed, hash_path, hash_path); + /* src does not exist */ + return 1; + } +@@ -1392,6 +1322,15 @@ static int dracut_install(const char *orig_src, const char *orig_dst, bool isdir + src_mode = sb.st_mode; + } + ++ /* The install hasn't succeeded yet, but mark this item as successful ++ now. If it fails once, it will probably fail every time. Doing this ++ could avoid dependency loops, but this is actually handled elsewhere. ++ It also avoids an elusive memory leak detected by valgrind. */ ++ hash_path = strdup(dst); ++ if (!hash_path) ++ return -ENOMEM; ++ hashmap_put(items, hash_path, hash_path); ++ + _asprintf(&fulldstpath, "%s/%s", destrootdir, (dst[0] == '/' ? (dst + 1) : dst)); + + ret = stat(fulldstpath, &sb); +@@ -1448,15 +1387,7 @@ static int dracut_install(const char *orig_src, const char *orig_dst, bool isdir + + if (src_isdir) { + log_info("mkdir '%s'", fulldstpath); +- ret = dracut_mkdir(fulldstpath); +- if (ret == 0) { +- i = strdup(dst); +- if (!i) +- return -ENOMEM; +- +- hashmap_put(items, i, i); +- } +- return ret; ++ return dracut_mkdir(fulldstpath); + } + + /* ready to install src */ +@@ -1525,12 +1456,6 @@ static int dracut_install(const char *orig_src, const char *orig_dst, bool isdir + } + + if (ret == 0) { +- i = strdup(dst); +- if (!i) +- return -ENOMEM; +- +- hashmap_put(items, i, i); +- + if (logfile_f) + dracut_log_cp(src); + } +@@ -2848,11 +2773,6 @@ int main(int argc, char **argv) + + log_debug("PATH=%s", path); + +- ldd = getenv("DRACUT_LDD"); +- if (isempty(ldd)) +- ldd = "ldd"; +- log_debug("LDD=%s", ldd); +- + env_no_xattr = getenv("DRACUT_NO_XATTR"); + if (env_no_xattr != NULL) + no_xattr = true; +-- +2.48.1 + + +From 4166b0c62428e0363e54c99d5c6a1748d408305a Mon Sep 17 00:00:00 2001 +From: James Le Cuirot +Date: Tue, 4 Mar 2025 17:21:27 +0000 +Subject: [PATCH 07/12] feat(dracut-install): add --dry-run option to replace + external ldd usage + +To remove the remaining use of ldd, we need a way to show which +libraries a binary requires. I initially considered adding another small +tool, sharing code with dracut-install, but then I realised that adding +a --dry-run option to dracut-install would also meet that need with a +lot less effort. + +It simply shows what would be installed and doesn't require you to +specify a destination directory. It is similar to the existing --logdir +option, but that cannot log to stdout and includes additional output. + +Signed-off-by: James Le Cuirot +--- + src/install/dracut-install.c | 65 +++++++++++++++++++++++++----------- + 1 file changed, 46 insertions(+), 19 deletions(-) + +diff --git a/src/install/dracut-install.c b/src/install/dracut-install.c +index f194532f..46bc0a25 100644 +--- a/src/install/dracut-install.c ++++ b/src/install/dracut-install.c +@@ -72,6 +72,7 @@ static bool arg_silent = false; + static bool arg_all = false; + static bool arg_module = false; + static bool arg_modalias = false; ++static bool arg_dry_run = false; + static bool arg_resolvelazy = false; + static bool arg_resolvedeps = false; + static bool arg_hostonly = false; +@@ -294,6 +295,9 @@ static char *convert_abs_rel(const char *from, const char *target) + + static int ln_r(const char *src, const char *dst) + { ++ if (arg_dry_run) ++ return 0; ++ + int ret; + _cleanup_free_ const char *points_to = convert_abs_rel(src, dst); + +@@ -373,6 +377,9 @@ static bool use_clone = true; + + static int cp(const char *src, const char *dst) + { ++ if (arg_dry_run) ++ return 0; ++ + pid_t pid; + int ret = 0; + +@@ -1189,6 +1196,9 @@ static int hmac_install(const char *src, const char *dst, const char *hmacpath) + + void mark_hostonly(const char *path) + { ++ if (arg_dry_run) ++ return; ++ + _cleanup_free_ char *fulldstpath = NULL; + _cleanup_fclose_ FILE *f = NULL; + +@@ -1226,6 +1236,9 @@ static bool check_hashmap(Hashmap *hm, const char *item) + + static int dracut_mkdir(const char *src) + { ++ if (arg_dry_run) ++ return 0; ++ + _cleanup_free_ char *parent = NULL; + char *path; + struct stat sb; +@@ -1333,7 +1346,8 @@ static int dracut_install(const char *orig_src, const char *orig_dst, bool isdir + + _asprintf(&fulldstpath, "%s/%s", destrootdir, (dst[0] == '/' ? (dst + 1) : dst)); + +- ret = stat(fulldstpath, &sb); ++ errno = ENOENT; ++ ret = arg_dry_run ? -1 : stat(fulldstpath, &sb); + + if (ret == 0) { + if (src_isdir && !S_ISDIR(sb.st_mode)) { +@@ -1362,7 +1376,7 @@ static int dracut_install(const char *orig_src, const char *orig_dst, bool isdir + return 1; + } + +- ret = access(fulldstdir, F_OK); ++ ret = arg_dry_run ? 0 : access(fulldstdir, F_OK); + + if (ret < 0) { + _cleanup_free_ char *dname = NULL; +@@ -1405,12 +1419,12 @@ static int dracut_install(const char *orig_src, const char *orig_dst, bool isdir + return 1; + } + +- if (faccessat(AT_FDCWD, abspath, F_OK, AT_SYMLINK_NOFOLLOW) != 0) { ++ if (!arg_dry_run && faccessat(AT_FDCWD, abspath, F_OK, AT_SYMLINK_NOFOLLOW) != 0) { + log_debug("lstat '%s': %m", abspath); + return 1; + } + +- if (faccessat(AT_FDCWD, fulldstpath, F_OK, AT_SYMLINK_NOFOLLOW) != 0) { ++ if (!arg_dry_run && faccessat(AT_FDCWD, fulldstpath, F_OK, AT_SYMLINK_NOFOLLOW) != 0) { + _cleanup_free_ char *absdestpath = NULL; + + _asprintf(&absdestpath, "%s/%s", destrootdir, +@@ -1456,6 +1470,9 @@ static int dracut_install(const char *orig_src, const char *orig_dst, bool isdir + } + + if (ret == 0) { ++ if (arg_dry_run) ++ puts(src); ++ + if (logfile_f) + dracut_log_cp(src); + } +@@ -1486,6 +1503,7 @@ static void usage(int status) + " -d --dir SOURCE is a directory\n" + " -l --ldd Also install shebang executables and libraries\n" + " -L --logdir Log files, which were installed from the host to \n" ++ " -n --dry-run Don't actually copy files, just show what would be installed\n" + " -R --resolvelazy Only install shebang executables and libraries\n" + " for all SOURCE files\n" + " -H --hostonly Mark all SOURCE files as hostonly\n\n" +@@ -1557,6 +1575,7 @@ static int parse_argv(int argc, char *argv[]) + {"kerneldir", required_argument, NULL, ARG_KERNELDIR}, + {"firmwaredirs", required_argument, NULL, ARG_FIRMWAREDIRS}, + {"json-supported", no_argument, NULL, ARG_JSON_SUPPORTED}, ++ {"dry-run", no_argument, NULL, 'n'}, + {NULL, 0, NULL, 0} + }; + +@@ -1668,6 +1687,9 @@ static int parse_argv(int argc, char *argv[]) + puts("JSON is not supported"); + return -1; + #endif ++ case 'n': ++ arg_dry_run = true; ++ break; + default: + usage(EXIT_FAILURE); + } +@@ -2781,24 +2803,28 @@ int main(int argc, char **argv) + + umask(0022); + +- if (destrootdir == NULL || strlen(destrootdir) == 0) { +- destrootdir = getenv("DESTROOTDIR"); ++ if (arg_dry_run) { ++ destrootdir = "/nonexistent"; ++ } else { + if (destrootdir == NULL || strlen(destrootdir) == 0) { +- log_error("Environment DESTROOTDIR or argument -D is not set!"); +- usage(EXIT_FAILURE); ++ destrootdir = getenv("DESTROOTDIR"); ++ if (destrootdir == NULL || strlen(destrootdir) == 0) { ++ log_error("Environment DESTROOTDIR or argument -D is not set!"); ++ usage(EXIT_FAILURE); ++ } + } +- } + +- if (strcmp(destrootdir, "/") == 0) { +- log_error("Environment DESTROOTDIR or argument -D is set to '/'!"); +- usage(EXIT_FAILURE); +- } ++ if (strcmp(destrootdir, "/") == 0) { ++ log_error("Environment DESTROOTDIR or argument -D is set to '/'!"); ++ usage(EXIT_FAILURE); ++ } + +- i = destrootdir; +- if (!(destrootdir = realpath(i, NULL))) { +- log_error("Environment DESTROOTDIR or argument -D is set to '%s': %m", i); +- r = EXIT_FAILURE; +- goto finish2; ++ i = destrootdir; ++ if (!(destrootdir = realpath(i, NULL))) { ++ log_error("Environment DESTROOTDIR or argument -D is set to '%s': %m", i); ++ r = EXIT_FAILURE; ++ goto finish2; ++ } + } + + items = hashmap_new(string_hash_func, string_compare_func); +@@ -2853,7 +2879,8 @@ int main(int argc, char **argv) + r = EXIT_SUCCESS; + + finish1: +- free(destrootdir); ++ if (!arg_dry_run) ++ free(destrootdir); + finish2: + if (!arg_kerneldir) + free(kerneldir); +-- +2.48.1 + + +From 6cdb49857ff1c9f798e35eded3673bd0a6e2c349 Mon Sep 17 00:00:00 2001 +From: James Le Cuirot +Date: Wed, 5 Mar 2025 11:53:18 +0000 +Subject: [PATCH 08/12] feat(dracut): replace ldd with dracut-install --dry-run + or header check + +One instance checks whether */lib64/* is used by /bin/sh and another +checks whether libusb is used by scdaemon. These can be handled by the +new dracut-install --dry-run option. + +find_binary currently uses ldd to check whether a given *.so* is a valid +ELF. ldd exits successfully even when libraries are missing, so it is +sufficient to replace this check with one that looks at the first 4 +bytes. + +Closes: https://github.com/dracut-ng/dracut-ng/issues/338 +Closes: https://github.com/dracut-ng/dracut-ng/issues/1257 +Signed-off-by: James Le Cuirot +--- + dracut-functions.sh | 10 ++++- + dracut-init.sh | 63 ++++++++++++--------------- + man/dracut.8.adoc | 7 --- + modules.d/91crypt-gpg/module-setup.sh | 2 +- + 4 files changed, 36 insertions(+), 46 deletions(-) + +diff --git a/dracut-functions.sh b/dracut-functions.sh +index 94f0228f..e6b8e958 100755 +--- a/dracut-functions.sh ++++ b/dracut-functions.sh +@@ -44,6 +44,12 @@ trim() { + printf "%s" "$var" + } + ++# is_elf ++# Returns success if the given path is an ELF. Only checks the first 4 bytes. ++is_elf() { ++ [[ $(head --bytes=4 "$1") == $'\x7fELF' ]] ++} ++ + # find a binary. If we were not passed the full path directly, + # search in the usual places to find the binary. + find_binary() { +@@ -56,13 +62,13 @@ find_binary() { + if [[ $1 == *.so* ]]; then + for l in $libdirs; do + _path="${l}${_delim}${1}" +- if { $DRACUT_LDD "${dracutsysrootdir}${_path}" &> /dev/null; }; then ++ if is_elf "${dracutsysrootdir}${_path}"; then + printf "%s\n" "${_path}" + return 0 + fi + done + _path="${_delim}${1}" +- if { $DRACUT_LDD "${dracutsysrootdir}${_path}" &> /dev/null; }; then ++ if is_elf "${dracutsysrootdir}${_path}"; then + printf "%s\n" "${_path}" + return 0 + fi +diff --git a/dracut-init.sh b/dracut-init.sh +index de3ae324..cd821163 100755 +--- a/dracut-init.sh ++++ b/dracut-init.sh +@@ -78,7 +78,6 @@ export srcmods + export hookdirs + } + +-DRACUT_LDD=${DRACUT_LDD:-ldd} + DRACUT_TESTBIN=${DRACUT_TESTBIN:-/bin/sh} + DRACUT_LDCONFIG=${DRACUT_LDCONFIG:-ldconfig} + PKG_CONFIG=${PKG_CONFIG:-pkg-config} +@@ -86,9 +85,35 @@ PKG_CONFIG=${PKG_CONFIG:-pkg-config} + # shellcheck source=./dracut-functions.sh + . "$dracutbasedir"/dracut-functions.sh + ++if ! [[ $DRACUT_INSTALL ]]; then ++ DRACUT_INSTALL=$(find_binary dracut-install) ++fi ++ ++if ! [[ $DRACUT_INSTALL ]] && [[ -x $dracutbasedir/dracut-install ]]; then ++ DRACUT_INSTALL=$dracutbasedir/dracut-install ++elif ! [[ $DRACUT_INSTALL ]] && [[ -x $dracutbasedir/src/install/dracut-install ]]; then ++ DRACUT_INSTALL=$dracutbasedir/src/install/dracut-install ++fi ++ ++# Test if dracut-install is a standalone executable with no options. ++# E.g. DRACUT_INSTALL may be set externally as: ++# DRACUT_INSTALL="valgrind dracut-install" ++# or ++# DRACUT_INSTALL="dracut-install --debug" ++# in which case the string cannot be tested for being executable. ++DRINSTALLPARTS=0 ++for i in $DRACUT_INSTALL; do ++ DRINSTALLPARTS=$((DRINSTALLPARTS + 1)) ++done ++ ++if [[ $DRINSTALLPARTS == 1 ]] && ! command -v "$DRACUT_INSTALL" > /dev/null 2>&1; then ++ dfatal "dracut-install not found!" ++ exit 10 ++fi ++ + # Detect lib paths + if ! [[ $libdirs ]]; then +- if [[ $("$DRACUT_LDD" "$dracutsysrootdir$DRACUT_TESTBIN") == */lib64/* ]] &> /dev/null \ ++ if [[ $($DRACUT_INSTALL ${dracutsysrootdir:+-r "$dracutsysrootdir"} --dry-run -R "$DRACUT_TESTBIN") == */lib64/* ]] &> /dev/null \ + && [[ -d $dracutsysrootdir/lib64 ]]; then + libdirs+=" /lib64" + [[ -d $dracutsysrootdir/usr/lib64 ]] && libdirs+=" /usr/lib64" +@@ -105,14 +130,6 @@ if ! [[ $libdirs ]]; then + export libdirs + fi + +-# ldd needs LD_LIBRARY_PATH pointing to the libraries within the sysroot directory +-if [[ -n $dracutsysrootdir ]]; then +- for lib in $libdirs; do +- LD_LIBRARY_PATH="${LD_LIBRARY_PATH:+"$LD_LIBRARY_PATH":}$dracutsysrootdir$lib" +- done +- export LD_LIBRARY_PATH +-fi +- + # helper function for check() in module-setup.sh + # to check for required installed binaries + # issues a standardized warning message +@@ -205,32 +222,6 @@ dracut_module_path() { + return 1 + } + +-if ! [[ $DRACUT_INSTALL ]]; then +- DRACUT_INSTALL=$(find_binary dracut-install) +-fi +- +-if ! [[ $DRACUT_INSTALL ]] && [[ -x $dracutbasedir/dracut-install ]]; then +- DRACUT_INSTALL=$dracutbasedir/dracut-install +-elif ! [[ $DRACUT_INSTALL ]] && [[ -x $dracutbasedir/src/install/dracut-install ]]; then +- DRACUT_INSTALL=$dracutbasedir/src/install/dracut-install +-fi +- +-# Test if dracut-install is a standalone executable with no options. +-# E.g. DRACUT_INSTALL may be set externally as: +-# DRACUT_INSTALL="valgrind dracut-install" +-# or +-# DRACUT_INSTALL="dracut-install --debug" +-# in which case the string cannot be tested for being executable. +-DRINSTALLPARTS=0 +-for i in $DRACUT_INSTALL; do +- DRINSTALLPARTS=$((DRINSTALLPARTS + 1)) +-done +- +-if [[ $DRINSTALLPARTS == 1 ]] && ! command -v "$DRACUT_INSTALL" > /dev/null 2>&1; then +- dfatal "dracut-install not found!" +- exit 10 +-fi +- + if [[ $hostonly == "-h" ]]; then + if ! [[ $DRACUT_KERNEL_MODALIASES ]] || ! [[ -f $DRACUT_KERNEL_MODALIASES ]]; then + export DRACUT_KERNEL_MODALIASES="${DRACUT_TMPDIR}/modaliases" +diff --git a/man/dracut.8.adoc b/man/dracut.8.adoc +index ba33ab19..1dd6ae76 100644 +--- a/man/dracut.8.adoc ++++ b/man/dracut.8.adoc +@@ -648,13 +648,6 @@ _DRACUT_LDCONFIG_:: + Default: + _ldconfig_ + +-_DRACUT_LDD_:: +- sets the _ldd_ program path and options. Optional. +- Used for **--sysroot**. +-+ +-Default: +- _ldd_ +- + _PKG_CONFIG_:: + sets the _pkg-config_ program path and options. Optional. + Most useful together with **--sysroot**. +diff --git a/modules.d/91crypt-gpg/module-setup.sh b/modules.d/91crypt-gpg/module-setup.sh +index 501869a2..df4c2c52 100755 +--- a/modules.d/91crypt-gpg/module-setup.sh ++++ b/modules.d/91crypt-gpg/module-setup.sh +@@ -57,7 +57,7 @@ sc_supported() { + if [[ ${gpgMajor} -gt 2 || ${gpgMajor} -eq 2 && ${gpgMinor} -ge 1 ]] \ + && require_binaries gpg-agent \ + && require_binaries gpg-connect-agent \ +- && ($DRACUT_LDD "${dracutsysrootdir}${scdaemon}" | grep libusb > /dev/null); then ++ && [[ $($DRACUT_INSTALL ${dracutsysrootdir:+-r "$dracutsysrootdir"} --dry-run -R "${scdaemon}") == *libusb* ]]; then + return 0 + else + return 1 +-- +2.48.1 + + +From b52ce3eb8996efac35b6ecc883c184de003fa6c8 Mon Sep 17 00:00:00 2001 +From: James Le Cuirot +Date: Wed, 26 Mar 2025 13:02:35 +0000 +Subject: [PATCH 09/12] feat(dracut): allow users to choose which dlopen + dependencies they want + +Handling dlopen dependencies is nice, but installing these +unconditionally will install more than before rather than less, leading +to bigger images and unhappy users. + +This introduces the add_dlopen_features and omit_dlopen_features +configuration options. Modules that are successfully loaded set the +default set of features to add_dlopen_features in the config() function. +Users can request additional features by appending to this variable. +They can also omit features by appending to omit_dlopen_features, which +takes precedence. + +Signed-off-by: James Le Cuirot +--- + dracut-init.sh | 51 +++-- + dracut.sh | 10 + + man/dracut.conf.5.adoc | 13 ++ + modules.d/00systemd/module-setup.sh | 5 + + modules.d/01systemd-bsod/module-setup.sh | 5 + + modules.d/01systemd-coredump/module-setup.sh | 5 + + .../01systemd-integritysetup/module-setup.sh | 5 + + modules.d/01systemd-journald/module-setup.sh | 5 + + .../01systemd-veritysetup/module-setup.sh | 5 + + src/install/dracut-install.c | 182 +++++++++++++++++- + 10 files changed, 266 insertions(+), 20 deletions(-) + +diff --git a/dracut-init.sh b/dracut-init.sh +index cd821163..d650fac8 100755 +--- a/dracut-init.sh ++++ b/dracut-init.sh +@@ -674,6 +674,15 @@ inst_opt_decompress() { + done + } + ++module_functions=( ++ check ++ depends ++ cmdline ++ config ++ install ++ installkernel ++) ++ + # module_check [] [] + # execute the check() function of module-setup.sh of + # or the "check" script, if module-setup.sh is not found +@@ -686,7 +695,7 @@ module_check() { + [[ -z $_moddir ]] && _moddir=$(dracut_module_path "$1") + [ $# -ge 2 ] && _forced=$2 + [[ -f $_moddir/module-setup.sh ]] || return 1 +- unset check depends cmdline install installkernel ++ unset "${module_functions[@]}" + check() { true; } + # shellcheck disable=SC1090 + . "$_moddir"/module-setup.sh +@@ -696,7 +705,7 @@ module_check() { + # shellcheck disable=SC2086 + moddir="$_moddir" check $hostonly + _ret=$? +- unset check depends cmdline install installkernel ++ unset "${module_functions[@]}" + hostonly=$_hostonly + return $_ret + } +@@ -711,13 +720,13 @@ module_check_mount() { + export mount_needs=1 + [[ -z $_moddir ]] && _moddir=$(dracut_module_path "$1") + [[ -f $_moddir/module-setup.sh ]] || return 1 +- unset check depends cmdline install installkernel ++ unset "${module_functions[@]}" + check() { false; } + # shellcheck disable=SC1090 + . "$_moddir"/module-setup.sh + moddir=$_moddir check 0 + _ret=$? +- unset check depends cmdline install installkernel ++ unset "${module_functions[@]}" + unset mount_needs + return "$_ret" + } +@@ -730,13 +739,13 @@ module_depends() { + local _ret + [[ -z $_moddir ]] && _moddir=$(dracut_module_path "$1") + [[ -f $_moddir/module-setup.sh ]] || return 1 +- unset check depends cmdline install installkernel ++ unset "${module_functions[@]}" + depends() { true; } + # shellcheck disable=SC1090 + . "$_moddir"/module-setup.sh + moddir=$_moddir depends + _ret=$? +- unset check depends cmdline install installkernel ++ unset "${module_functions[@]}" + return $_ret + } + +@@ -748,13 +757,31 @@ module_cmdline() { + local _ret + [[ -z $_moddir ]] && _moddir=$(dracut_module_path "$1") + [[ -f $_moddir/module-setup.sh ]] || return 1 +- unset check depends cmdline install installkernel ++ unset "${module_functions[@]}" + cmdline() { true; } + # shellcheck disable=SC1090 + . "$_moddir"/module-setup.sh + moddir="$_moddir" cmdline + _ret=$? +- unset check depends cmdline install installkernel ++ unset "${module_functions[@]}" ++ return $_ret ++} ++ ++# module_config [] ++# execute the config() function of module-setup.sh of ++# or the "config" script, if module-setup.sh is not found ++module_config() { ++ local _moddir=$2 ++ local _ret ++ [[ -z $_moddir ]] && _moddir=$(dracut_module_path "$1") ++ [[ -f $_moddir/module-setup.sh ]] || return 1 ++ unset "${module_functions[@]}" ++ config() { true; } ++ # shellcheck disable=SC1090 ++ . "$_moddir"/module-setup.sh ++ moddir="$_moddir" config ++ _ret=$? ++ unset "${module_functions[@]}" + return $_ret + } + +@@ -766,13 +793,13 @@ module_install() { + local _ret + [[ -z $_moddir ]] && _moddir=$(dracut_module_path "$1") + [[ -f $_moddir/module-setup.sh ]] || return 1 +- unset check depends cmdline install installkernel ++ unset "${module_functions[@]}" + install() { true; } + # shellcheck disable=SC1090 + . "$_moddir"/module-setup.sh + moddir="$_moddir" install + _ret=$? +- unset check depends cmdline install installkernel ++ unset "${module_functions[@]}" + return $_ret + } + +@@ -784,13 +811,13 @@ module_installkernel() { + local _ret + [[ -z $_moddir ]] && _moddir=$(dracut_module_path "$1") + [[ -f $_moddir/module-setup.sh ]] || return 1 +- unset check depends cmdline install installkernel ++ unset "${module_functions[@]}" + installkernel() { true; } + # shellcheck disable=SC1090 + . "$_moddir"/module-setup.sh + moddir="$_moddir" installkernel + _ret=$? +- unset check depends cmdline install installkernel ++ unset "${module_functions[@]}" + return $_ret + } + +diff --git a/dracut.sh b/dracut.sh +index 75d92738..737bf58c 100755 +--- a/dracut.sh ++++ b/dracut.sh +@@ -927,6 +927,9 @@ export DRACUT_LOG_LEVEL=warning + + [[ $dracutbasedir ]] || dracutbasedir="$dracutsysrootdir"/usr/lib/dracut + ++# These config variables needs to be exported for dracut-install. ++export add_dlopen_features="" omit_dlopen_features="" ++ + # if we were not passed a config file, try the default one + if [[ -z $conffile ]]; then + if [[ $allowlocal ]]; then +@@ -2007,6 +2010,13 @@ dracut_module_included "squash-lib" && mkdir -p "$squashdir" + + _isize=0 #initramfs size + modules_loaded=" " ++# Allow all modules to update the config. Do this before installing anything. ++for moddir in "$dracutbasedir/modules.d"/[0-9][0-9]*; do ++ _d_mod=${moddir##*/} ++ _d_mod=${_d_mod#[0-9][0-9]} ++ [[ $mods_to_load == *\ $_d_mod\ * ]] || continue ++ module_config "$_d_mod" "$moddir" ++done + # source our modules. + for moddir in "$dracutbasedir/modules.d"/[0-9][0-9]*; do + _d_mod=${moddir##*/} +diff --git a/man/dracut.conf.5.adoc b/man/dracut.conf.5.adoc +index ae8a6903..5a282853 100644 +--- a/man/dracut.conf.5.adoc ++++ b/man/dracut.conf.5.adoc +@@ -75,6 +75,19 @@ This option forces dracut to only include the specified kernel modules. + In most cases the "--add-drivers" option is what you want to use. + This option is not recommended to use (use at your own risk). + ++*add_dlopen_features+=*" __:__[__,__...] ... ":: ++Specify a space-separated list of binaries matching _pattern_ against a ++comma-separated list of features to install dependencies for. For example, ++"libsystemd-shared-*.so:idn,ip4tc" will install the dependencies for systemd's ++international domain name and iptables support. _pattern_ should match the ++soname for libraries or the filename for executables. ++ ++*omit_dlopen_features+=*" __:__[__,__...] ... ":: ++Specify a space-separated list of binaries matching _pattern_ against a ++comma-separated list of features to omit dependencies for. Some dracut modules ++add certain features by default. This takes precedence over add_dlopen_features ++above. ++ + *filesystems+=*" ____ ":: + Specify a space-separated list of kernel filesystem modules to exclusively + include in the generic initramfs. +diff --git a/modules.d/00systemd/module-setup.sh b/modules.d/00systemd/module-setup.sh +index 747f09f0..1506f64f 100755 +--- a/modules.d/00systemd/module-setup.sh ++++ b/modules.d/00systemd/module-setup.sh +@@ -14,6 +14,11 @@ check() { + return 255 + } + ++# Config adjustments before installing anything. ++config() { ++ add_dlopen_features+=" libsystemd-shared-*.so:kmod " ++} ++ + installkernel() { + hostonly='' instmods autofs4 ipv6 dmi-sysfs + instmods -s efivarfs +diff --git a/modules.d/01systemd-bsod/module-setup.sh b/modules.d/01systemd-bsod/module-setup.sh +index cf562ca6..f702792c 100755 +--- a/modules.d/01systemd-bsod/module-setup.sh ++++ b/modules.d/01systemd-bsod/module-setup.sh +@@ -19,6 +19,11 @@ depends() { + return 0 + } + ++# Config adjustments before installing anything. ++config() { ++ add_dlopen_features+=" libsystemd-shared-*.so:qrencode " ++} ++ + # Install the required file(s) for the module in the initramfs. + install() { + inst_multiple \ +diff --git a/modules.d/01systemd-coredump/module-setup.sh b/modules.d/01systemd-coredump/module-setup.sh +index 3083f851..4014b074 100755 +--- a/modules.d/01systemd-coredump/module-setup.sh ++++ b/modules.d/01systemd-coredump/module-setup.sh +@@ -26,6 +26,11 @@ depends() { + + } + ++# Config adjustments before installing anything. ++config() { ++ add_dlopen_features+=" libsystemd-shared-*.so:lz4,lzma,zstd " ++} ++ + # Install the required file(s) and directories for the module in the initramfs. + install() { + +diff --git a/modules.d/01systemd-integritysetup/module-setup.sh b/modules.d/01systemd-integritysetup/module-setup.sh +index 804b856e..aba99371 100755 +--- a/modules.d/01systemd-integritysetup/module-setup.sh ++++ b/modules.d/01systemd-integritysetup/module-setup.sh +@@ -26,6 +26,11 @@ depends() { + + } + ++# Config adjustments before installing anything. ++config() { ++ add_dlopen_features+=" libsystemd-shared-*.so:cryptsetup " ++} ++ + # Install kernel module(s). + installkernel() { + instmods dm-integrity +diff --git a/modules.d/01systemd-journald/module-setup.sh b/modules.d/01systemd-journald/module-setup.sh +index 9f546d1a..807c7ab3 100755 +--- a/modules.d/01systemd-journald/module-setup.sh ++++ b/modules.d/01systemd-journald/module-setup.sh +@@ -26,6 +26,11 @@ depends() { + + } + ++# Config adjustments before installing anything. ++config() { ++ add_dlopen_features+=" libsystemd-shared-*.so:gcrypt,lz4,lzma,zstd " ++} ++ + # Install the required file(s) and directories for the module in the initramfs. + install() { + +diff --git a/modules.d/01systemd-veritysetup/module-setup.sh b/modules.d/01systemd-veritysetup/module-setup.sh +index e3b95303..1f0c0355 100755 +--- a/modules.d/01systemd-veritysetup/module-setup.sh ++++ b/modules.d/01systemd-veritysetup/module-setup.sh +@@ -26,6 +26,11 @@ depends() { + + } + ++# Config adjustments before installing anything. ++config() { ++ add_dlopen_features+=" libsystemd-shared-*.so:cryptsetup " ++} ++ + # Install kernel module(s). + installkernel() { + instmods dm-verity +diff --git a/src/install/dracut-install.c b/src/install/dracut-install.c +index 46bc0a25..0f431a40 100644 +--- a/src/install/dracut-install.c ++++ b/src/install/dracut-install.c +@@ -94,6 +94,9 @@ static Hashmap *modules_loaded = NULL; + static Hashmap *modules_suppliers = NULL; + static Hashmap *processed_suppliers = NULL; + static Hashmap *modalias_to_kmod = NULL; ++static Hashmap *add_dlopen_features = NULL; ++static Hashmap *omit_dlopen_features = NULL; ++static Hashmap *dlopen_features[2] = {NULL}; + static regex_t mod_filter_path; + static regex_t mod_filter_nopath; + static regex_t mod_filter_symbol; +@@ -913,11 +916,14 @@ static char *find_library(const char *soname, const char *src, size_t src_len, c + + /* Parse the given .note.dlopen JSON (https://systemd.io/ELF_DLOPEN_METADATA/) + in the given note index and find each dependent library, ensuring it matches +- the given (64 or 32 bit) ELF header. Each library found is added to deps. +- Dependencies already found in this chain must be given in pdeps. Failure to +- parse the JSON or find a library is considered non-fatal. */ +-static void resolve_deps_dlopen_parse_json(Hashmap *pdeps, Hashmap *deps, const char *fullsrcpath, size_t src_len, +- const char *json, size_t note_idx, const Elf64_Ehdr *match64, const Elf32_Ehdr *match32) ++ the given (64 or 32 bit) ELF header. Dependencies are skipped if the ++ corresponding feature is present in omit_dlopen_features or missing from ++ add_dlopen_features. Those hashmaps are keyed by wildcard patterns, which are ++ compared against the source's soname or filename. Each library found is added ++ to deps. Dependencies already found in this chain must be given in pdeps. ++ Failure to parse the JSON or find a library is considered non-fatal. */ ++static void resolve_deps_dlopen_parse_json(Hashmap *pdeps, Hashmap *deps, const char *src_soname, char *fullsrcpath, ++ size_t src_len, const char *json, size_t note_idx, const Elf64_Ehdr *match64, const Elf32_Ehdr *match32) + { + _cleanup_(sd_json_variant_unrefp) sd_json_variant *dlopen_json = NULL; + if (sd_json_parse(json, 0, &dlopen_json, NULL, NULL) != 0 || !sd_json_variant_is_array(dlopen_json)) { +@@ -927,6 +933,28 @@ static void resolve_deps_dlopen_parse_json(Hashmap *pdeps, Hashmap *deps, const + + for (size_t entry_idx = 0; entry_idx < sd_json_variant_elements(dlopen_json); entry_idx++) { + sd_json_variant *entry = sd_json_variant_by_index(dlopen_json, entry_idx); ++ sd_json_variant *feature_json = sd_json_variant_by_key(entry, "feature"); ++ ++ if (feature_json && sd_json_variant_is_string(feature_json)) { ++ const char *feature = sd_json_variant_string(feature_json); ++ const char *name = src_soname ?: basename(fullsrcpath); ++ ++ Iterator i; ++ char ***features; ++ const char *pattern; ++ HASHMAP_FOREACH_KEY(features, pattern, omit_dlopen_features, i) { ++ if (fnmatch(pattern, name, 0) == 0 && strv_contains(*features, feature)) ++ goto skip; ++ } ++ int skip = 1; ++ HASHMAP_FOREACH_KEY(features, pattern, add_dlopen_features, i) { ++ if (fnmatch(pattern, name, 0) == 0 && strv_contains(*features, feature)) ++ skip = 0; ++ } ++ if (skip) ++ goto skip; ++ } ++ + sd_json_variant *sonames = sd_json_variant_by_key(entry, "soname"); + if (!sonames || !sd_json_variant_is_array(sonames)) { + log_warning("WARNING: soname array missing from .note.dlopen entry #%zd.%zd in '%s'", note_idx, entry_idx, fullsrcpath); +@@ -949,6 +977,7 @@ static void resolve_deps_dlopen_parse_json(Hashmap *pdeps, Hashmap *deps, const + if (!library || hashmap_put_strdup_key(deps, soname, library) < 0) + log_warning("WARNING: could not locate dlopen dependency %s requested by '%s'", soname, fullsrcpath); + } ++skip: + } + } + +@@ -956,7 +985,32 @@ static void resolve_deps_dlopen_parse_json(Hashmap *pdeps, Hashmap *deps, const + 64 or 32 bit, check .note.dlopen entries for dependencies. See above. */ + #define RESOLVE_DEPS_DLOPEN_FOR_BITS(B, match64, match32) do { \ + PARSE_ELF_START(B, map); \ ++ const char *soname = NULL; \ + size_t note_idx = -1; \ ++\ ++ for (size_t i = 0; !soname && i < ELF_BYTESWAP(16, ehdr->e_shnum); i++) { \ ++ if ((char*)&shdr[i] < (char*)map || (char*)&shdr[i] + sizeof(Elf##B##_Shdr) > (char*)map + src_len) \ ++ break; \ ++ if (strcmp(&shstrtab[ELF_BYTESWAP(32, shdr[i].sh_name)], ".dynamic") != 0) \ ++ continue; \ ++\ ++ Elf##B##_Dyn *dyn = (Elf##B##_Dyn *)((char *)map + ELF_BYTESWAP(B, shdr[i].sh_offset)); \ ++ if ((char *)dyn < (char *)map || (char *)dyn > (char *)map + src_len) \ ++ break; \ ++\ ++ for (Elf##B##_Dyn *d = dyn; !soname && ELF_BYTESWAP(32, d->d_tag) != DT_NULL; d++) { \ ++ if ((char *)d < (char *)map || (char *)d + sizeof(Elf##B##_Dyn) > (char *)map + src_len) \ ++ break; \ ++ if (ELF_BYTESWAP(B, d->d_tag) != DT_SONAME) \ ++ continue; \ ++\ ++ soname = (char *)map + ELF_BYTESWAP(B, shdr[ELF_BYTESWAP(32, shdr[i].sh_link)].sh_offset) + ELF_BYTESWAP(B, d->d_un.d_val); \ ++ if ((char *)soname < (char *)map || (char *)soname > (char *)map + src_len) { \ ++ soname = NULL; \ ++ break; \ ++ } \ ++ } \ ++ } \ + \ + for (size_t i = 0; i < ELF_BYTESWAP(16, ehdr->e_shnum); i++) { \ + if ((char*)shdr + i * sizeof(Elf##B##_Shdr) > (char*)map + src_len) \ +@@ -986,7 +1040,7 @@ static void resolve_deps_dlopen_parse_json(Hashmap *pdeps, Hashmap *deps, const + continue; \ + \ + note_idx++; \ +- resolve_deps_dlopen_parse_json(pdeps, deps, fullsrcpath, src_len, note_desc, note_idx, match64, match32); \ ++ resolve_deps_dlopen_parse_json(pdeps, deps, soname, fullsrcpath, src_len, note_desc, note_idx, match64, match32); \ + } \ + } \ + } while (0) +@@ -1102,6 +1156,11 @@ static int resolve_deps(const char *src, Hashmap *pdeps) + Hashmap *deps = hashmap_new(string_hash_func, string_compare_func); + int ret = 0; + ++ if (!ndeps || !deps) { ++ ret = -1; ++ goto finish; ++ } ++ + char *shebang = (char *)map; + if (shebang[0] == '#' && shebang[1] == '!') { + char *p, *q; +@@ -1139,8 +1198,10 @@ static int resolve_deps(const char *src, Hashmap *pdeps) + ret = -1; + } + +- if (hashmap_merge(ndeps, pdeps) < 0 || hashmap_merge(ndeps, deps) < 0) ++ if (hashmap_merge(ndeps, pdeps) < 0 || hashmap_merge(ndeps, deps) < 0) { ++ ret = -1; + goto finish; ++ } + + char *key, *library; + Iterator i; +@@ -2751,6 +2812,87 @@ static int install_modules(int argc, char **argv) + return EXIT_SUCCESS; + } + ++/* Parse the add_dlopen_features and omit_dlopen_features environment variables, ++ and store their contents in the corresponding char* -> char*** hashmaps. Each ++ variable holds multiple entries, separated by whitespace, and each entry ++ takes the form "libfoo.so.*:feature1,feature2". */ ++static int parse_dlopen_features() ++{ ++ const char *add_env = getenv("add_dlopen_features"); ++ const char *omit_env = getenv("omit_dlopen_features"); ++ const char *envs[] = {add_env, omit_env}; ++ char **features_array; ++ ++ for (size_t i = 0; i < 2; i++) { ++ if (!envs[i]) ++ continue; ++ ++ /* We cannot let strtok modify the environment. */ ++ _cleanup_free_ char *env_copy = strdup(envs[i]); ++ if (!env_copy) ++ return -ENOMEM; ++ ++ for (char *token = strtok(env_copy, " \t\n"); token; token = strtok(NULL, " \t\n")) { ++ char *colon = strchr(token, ':'); ++ if (!colon) { ++ log_warning("Invalid format in dlopen features: '%s'", token); ++ continue; ++ } ++ ++ *colon = '\0'; ++ const char *key = token; ++ const char *features = colon + 1; ++ ++ features_array = strv_split(features, ","); ++ if (!features_array) ++ return -ENOMEM; ++ ++ /* There may be entries with the same name/pattern. */ ++ char ***existing = hashmap_get(dlopen_features[i], key); ++ ++ if (existing) { ++ char **feature; ++ STRV_FOREACH(feature, features_array) { ++ /* Free feature if already present. */ ++ if (strv_contains(*existing, *feature)) ++ free(*feature); ++ /* Otherwise push onto existing array ++ without duplicating the string. */ ++ else if (strv_push(existing, *feature) == -ENOMEM) ++ goto oom; ++ } ++ /* All features have been freed or pushed to the ++ existing array, so just free array itself. */ ++ free(features_array); ++ } else { ++ /* The hashmaps store strvs as char*** rather ++ than char** because strv_push above calls ++ realloc. The latter would then leave the ++ hashmap with a stale pointer. */ ++ char ***features_arrayp = (char ***) malloc(sizeof(char ***)); ++ char *nkey = strdup(key); ++ if (!features_arrayp || !nkey) { ++ free(features_arrayp); ++ goto oom; ++ } ++ *features_arrayp = features_array; ++ if (hashmap_put(dlopen_features[i], nkey, features_arrayp) == -ENOMEM) { ++ free(features_arrayp); ++ free(nkey); ++ goto oom; ++ } ++ } ++ } ++ } ++ ++ return 0; ++ ++oom: ++ log_error("Out of memory"); ++ strv_free(features_array); ++ return -ENOMEM; ++} ++ + int main(int argc, char **argv) + { + int r; +@@ -2832,7 +2974,11 @@ int main(int argc, char **argv) + processed_suppliers = hashmap_new(string_hash_func, string_compare_func); + modalias_to_kmod = hashmap_new(string_hash_func, string_compare_func); + +- if (!items || !items_failed || !processed_suppliers || !modules_loaded) { ++ dlopen_features[0] = add_dlopen_features = hashmap_new(string_hash_func, string_compare_func); ++ dlopen_features[1] = omit_dlopen_features = hashmap_new(string_hash_func, string_compare_func); ++ ++ if (!items || !items_failed || !processed_suppliers || !modules_loaded || ++ !add_dlopen_features || !omit_dlopen_features) { + log_error("Out of memory"); + r = EXIT_FAILURE; + goto finish1; +@@ -2864,6 +3010,11 @@ int main(int argc, char **argv) + } + } + ++ if (parse_dlopen_features() < 0) { ++ r = EXIT_FAILURE; ++ goto finish1; ++ } ++ + if (arg_module) { + r = install_modules(argc - optind, &argv[optind]); + } else if (arg_resolvelazy) { +@@ -2908,6 +3059,21 @@ finish2: + while ((i = hashmap_steal_first(processed_suppliers))) + item_free(i); + ++ for (size_t j = 0; j < 2; j++) { ++ char ***array; ++ Iterator it; ++ ++ HASHMAP_FOREACH(array, dlopen_features[j], it) { ++ strv_free(*array); ++ free(array); ++ } ++ ++ while ((i = hashmap_steal_first_key(dlopen_features[j]))) ++ item_free(i); ++ ++ hashmap_free(dlopen_features[j]); ++ } ++ + /* + * Note: modalias_to_kmod's values are freed implicitly by the kmod context destruction + * in kmod_unref(). +-- +2.48.1 + + +From 4cba538e9218b33c068f9a7ac463133152e0b9b4 Mon Sep 17 00:00:00 2001 +From: James Le Cuirot +Date: Fri, 14 Mar 2025 14:08:00 +0000 +Subject: [PATCH 10/12] fix: add $dracutsysrootdir to paths where it should be + present + +inst_simpl is sometimes called with the sysroot (particular via moddir) +and sometimes without. dracut-install knows how to handle this, so the +inst_simpl existence check needs to handle it too. + +Signed-off-by: James Le Cuirot +--- + dracut-init.sh | 8 ++++++-- + dracut.sh | 2 +- + modules.d/01systemd-cryptsetup/module-setup.sh | 2 +- + modules.d/01systemd-sysext/module-setup.sh | 2 +- + modules.d/03modsign/module-setup.sh | 2 +- + modules.d/95iscsi/module-setup.sh | 2 +- + 6 files changed, 11 insertions(+), 7 deletions(-) + +diff --git a/dracut-init.sh b/dracut-init.sh +index d650fac8..394ad658 100755 +--- a/dracut-init.sh ++++ b/dracut-init.sh +@@ -267,7 +267,11 @@ inst_simple() { + shift + fi + [[ -e ${dstdir}/"${2:-$1}" ]] && return 0 # already there +- [[ -e $1 ]] || return 1 # no source ++ if [[ $1 == /* ]]; then ++ [[ -e $dracutsysrootdir/${1#"$dracutsysrootdir"} ]] || return 1 # no source ++ else ++ [[ -e $1 ]] || return 1 # no source ++ fi + if $DRACUT_INSTALL ${dracutsysrootdir:+-r "$dracutsysrootdir"} ${dstdir:+-D "$dstdir"} ${loginstall:+-L "$loginstall"} ${_hostonly_install:+-H} "$@"; then + return 0 + else +@@ -526,7 +530,7 @@ build_ld_cache() { + local dstdir="${dstdir:-"$initdir"}" + + for f in "$dracutsysrootdir"/etc/ld.so.conf "$dracutsysrootdir"/etc/ld.so.conf.d/*; do +- [[ -f $f ]] && inst_simple "${f#"$dracutsysrootdir"}" ++ [[ -f $f ]] && inst_simple "${f}" + done + if ! $DRACUT_LDCONFIG -r "$initdir" -f /etc/ld.so.conf; then + if [[ $EUID == 0 ]]; then +diff --git a/dracut.sh b/dracut.sh +index 737bf58c..58fe08c5 100755 +--- a/dracut.sh ++++ b/dracut.sh +@@ -1114,7 +1114,7 @@ drivers_dir="${drivers_dir%"${drivers_dir##*[!/]}"}" + [[ $ro_mnt_l ]] && ro_mnt="yes" + [[ $early_microcode_l ]] && early_microcode=$early_microcode_l + [[ $early_microcode ]] || early_microcode=yes +-[[ $early_microcode_image_dir ]] || early_microcode_image_dir=('/boot') ++[[ $early_microcode_image_dir ]] || early_microcode_image_dir=("$dracutsysrootdir"/boot) + [[ $early_microcode_image_name ]] \ + || early_microcode_image_name=('intel-uc.img' 'intel-ucode.img' 'amd-uc.img' 'amd-ucode.img' 'early_ucode.cpio' 'microcode.cpio') + [[ $logfile_l ]] && logfile="$logfile_l" +diff --git a/modules.d/01systemd-cryptsetup/module-setup.sh b/modules.d/01systemd-cryptsetup/module-setup.sh +index 023c65d1..dd8618a2 100755 +--- a/modules.d/01systemd-cryptsetup/module-setup.sh ++++ b/modules.d/01systemd-cryptsetup/module-setup.sh +@@ -70,7 +70,7 @@ install() { + _luksfile="/run/cryptsetup-keys.d/$_mapper.key" + fi + +- find "$systemdsystemunitdir" "$systemdsystemconfdir" -type f -name "*.socket" | while read -r socket_unit; do ++ find "$dracutsysrootdir$systemdsystemunitdir" "$dracutsysrootdir$systemdsystemconfdir" -type f -name "*.socket" | while read -r socket_unit; do + # systemd-cryptsetup utility only supports SOCK_STREAM (ListenStream) sockets, so we ignore + # other types like SOCK_DGRAM (ListenDatagram), SOCK_SEQPACKET (ListenSequentialPacket), etc. + if ! grep -E -q "^ListenStream\s*=\s*$_luksfile$" "$socket_unit"; then +diff --git a/modules.d/01systemd-sysext/module-setup.sh b/modules.d/01systemd-sysext/module-setup.sh +index 379d0aa1..cc52d855 100755 +--- a/modules.d/01systemd-sysext/module-setup.sh ++++ b/modules.d/01systemd-sysext/module-setup.sh +@@ -29,7 +29,7 @@ install() { + local _suffix= + + # systemd >= v258 +- [[ -e "$systemdsystemunitdir"/systemd-sysext-initrd.service ]] && _suffix="-initrd" ++ [[ -e "$dracutsysrootdir$systemdsystemunitdir"/systemd-sysext-initrd.service ]] && _suffix="-initrd" + + # It's intended to work only with raw binary disk images contained in + # regular files, but not with directory trees. +diff --git a/modules.d/03modsign/module-setup.sh b/modules.d/03modsign/module-setup.sh +index 7a22a752..7fffad83 100755 +--- a/modules.d/03modsign/module-setup.sh ++++ b/modules.d/03modsign/module-setup.sh +@@ -28,6 +28,6 @@ install() { + + for x in "$dracutsysrootdir"/lib/modules/keys/*; do + [[ ${x} == "$dracutsysrootdir/lib/modules/keys/*" ]] && break +- inst_simple "${x#"$dracutsysrootdir"}" ++ inst_simple "${x}" + done + } +diff --git a/modules.d/95iscsi/module-setup.sh b/modules.d/95iscsi/module-setup.sh +index 3bb9a63d..ba57dbbe 100755 +--- a/modules.d/95iscsi/module-setup.sh ++++ b/modules.d/95iscsi/module-setup.sh +@@ -220,7 +220,7 @@ install() { + "$systemdsystemunitdir"/iscsiuio.socket \ + "$systemdsystemunitdir"/sockets.target.wants/iscsid.socket \ + "$systemdsystemunitdir"/sockets.target.wants/iscsiuio.socket +- if grep -q '^ExecStartPre=/usr/lib/open-iscsi/startup-checks.sh$' "$systemdsystemunitdir/iscsid.service"; then ++ if grep -q '^ExecStartPre=/usr/lib/open-iscsi/startup-checks.sh$' "$dracutsysrootdir$systemdsystemunitdir/iscsid.service"; then + inst_simple /usr/lib/open-iscsi/startup-checks.sh + fi + +-- +2.48.1 + + +From 03c766c1e93026e4a454a56ccf87d6aba39d903d Mon Sep 17 00:00:00 2001 +From: James Le Cuirot +Date: Tue, 1 Apr 2025 11:51:19 +0100 +Subject: [PATCH 11/12] fix: don't use command -v to find binaries in the + sysroot + +If the binaries were missing outside the sysroot, "" was passed to +dracut-install, which then created an empty directory instead! + +dracut-install will automatically search the sysroot for a named binary +if it is given without a path anyway. + +Signed-off-by: James Le Cuirot +--- + modules.d/90btrfs/module-setup.sh | 2 +- + modules.d/90dmraid/module-setup.sh | 2 +- + modules.d/90mdraid/module-setup.sh | 4 ++-- + modules.d/90multipath/module-setup.sh | 4 ++-- + 4 files changed, 6 insertions(+), 6 deletions(-) + +diff --git a/modules.d/90btrfs/module-setup.sh b/modules.d/90btrfs/module-setup.sh +index 80bba155..fbf3c1db 100755 +--- a/modules.d/90btrfs/module-setup.sh ++++ b/modules.d/90btrfs/module-setup.sh +@@ -56,5 +56,5 @@ install() { + fi + + inst_multiple -o btrfsck btrfs-zero-log btrfstune +- inst "$(command -v btrfs)" /sbin/btrfs ++ inst btrfs /sbin/btrfs + } +diff --git a/modules.d/90dmraid/module-setup.sh b/modules.d/90dmraid/module-setup.sh +index 482ae96a..e7c6be60 100755 +--- a/modules.d/90dmraid/module-setup.sh ++++ b/modules.d/90dmraid/module-setup.sh +@@ -73,7 +73,7 @@ install() { + + inst_multiple dmraid + inst_multiple -o kpartx +- inst "$(command -v partx)" /sbin/partx ++ inst partx /sbin/partx + + inst "$moddir/dmraid.sh" /sbin/dmraid_scan + +diff --git a/modules.d/90mdraid/module-setup.sh b/modules.d/90mdraid/module-setup.sh +index b0ab8411..0d0a57f6 100755 +--- a/modules.d/90mdraid/module-setup.sh ++++ b/modules.d/90mdraid/module-setup.sh +@@ -67,8 +67,8 @@ cmdline() { + install() { + inst_multiple cat expr + inst_multiple -o mdmon +- inst "$(command -v partx)" /sbin/partx +- inst "$(command -v mdadm)" /sbin/mdadm ++ inst partx /sbin/partx ++ inst mdadm /sbin/mdadm + + if [[ $hostonly_cmdline == "yes" ]]; then + local _raidconf +diff --git a/modules.d/90multipath/module-setup.sh b/modules.d/90multipath/module-setup.sh +index 5a7f91fa..a16313c0 100755 +--- a/modules.d/90multipath/module-setup.sh ++++ b/modules.d/90multipath/module-setup.sh +@@ -61,7 +61,7 @@ installkernel() { + } + + mpathconf_installed() { +- command -v mpathconf &> /dev/null ++ find_binary mpathconf &> /dev/null + } + + # called by dracut +@@ -136,7 +136,7 @@ EOF + } + } + +- inst "$(command -v partx)" /sbin/partx ++ inst partx /sbin/partx + + inst_libdir_file "libmultipath*" "multipath/*" + inst_libdir_file 'libgcc_s.so*' +-- +2.48.1 + + +From 1ceb679410a75c8d245e692471aa5d7fa16df7b7 Mon Sep 17 00:00:00 2001 +From: James Le Cuirot +Date: Thu, 10 Apr 2025 11:37:01 +0100 +Subject: [PATCH 12/12] fix(systemd-cryptsetup): don't pull in + fido2/pkcs11/tpm2-tss if omitted + +These modules have some large dependencies. Allow users to explicitly +omit them if desired. Other modules like systemd-udevd also do this. + +Signed-off-by: James Le Cuirot +--- + modules.d/01systemd-cryptsetup/module-setup.sh | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/modules.d/01systemd-cryptsetup/module-setup.sh b/modules.d/01systemd-cryptsetup/module-setup.sh +index dd8618a2..719df0e8 100755 +--- a/modules.d/01systemd-cryptsetup/module-setup.sh ++++ b/modules.d/01systemd-cryptsetup/module-setup.sh +@@ -33,7 +33,7 @@ depends() { + elif [[ ! $hostonly ]]; then + for module in fido2 pkcs11 tpm2-tss; do + module_check $module > /dev/null 2>&1 +- if [[ $? == 255 ]]; then ++ if [[ $? == 255 ]] && ! [[ " $omit_dracutmodules " == *\ $module\ * ]]; then + deps+=" $module" + fi + done +-- +2.48.1 + diff --git a/sdk_container/src/third_party/coreos-overlay/coreos/user-patches/sys-kernel/dracut/README.md b/sdk_container/src/third_party/coreos-overlay/coreos/user-patches/sys-kernel/dracut/README.md index 9e3f344bd4..e9801411e5 100644 --- a/sdk_container/src/third_party/coreos-overlay/coreos/user-patches/sys-kernel/dracut/README.md +++ b/sdk_container/src/third_party/coreos-overlay/coreos/user-patches/sys-kernel/dracut/README.md @@ -1,2 +1,11 @@ +`001-dracut-post-106.patch` is the merged upstream changes from v106 to current +main for some potentially important fixes and to provide a clean base for +`002-dracut-sysroot.patch`. This can be dropped when bumping to v107. + +`002-dracut-sysroot.patch` is Chewi's new Dracut improvements, which allow it to +parse the ELF .note.dlopen dependency metadata used by JSON and reliably +determine dependencies across foreign architectures. They will hopefully be +merged in v108. See https://github.com/dracut-ng/dracut-ng/pull/1260. + `050-change-network-dep-iscsi.patch` is a Flatcar-specific dependency tweak to use flatcar-network instead of network.