test: verify bootctl link-auto and io.systemd.BootControl.LinkAuto

Add a TEST-87 testcase exercising "bootctl link-auto" and the equivalent
io.systemd.BootControl.LinkAuto() Varlink method: a UKI plus extras are staged
below the search directories and we assert the kernel and sidecar resources
are linked into $BOOT. Covered: plain kernel.efi + extras.d/, versioned
kernel.efi.v/ and extras .v/ resolved via vpick, directory priority
(/etc wins over /run), the no-op case when nothing is staged, and the Varlink
method including its empty reply when there is nothing to link.
This commit is contained in:
Lennart Poettering
2026-06-24 13:06:20 +02:00
parent 89dd06505b
commit 9ac1b6c124
+158
View File
@@ -710,4 +710,162 @@ EOF
'{"id":"foo*.conf"}')
}
cleanup_link_auto() {
rm -rf /run/systemd/uki /etc/systemd/uki
if [[ -n "${LINK_WORKDIR:-}" ]]; then
rm -rf "$LINK_WORKDIR"
unset LINK_WORKDIR
fi
restore_esp
}
testcase_bootctl_link_auto() {
if ! command -v ukify >/dev/null; then
echo "ukify not found, skipping."
return 0
fi
backup_esp
LINK_WORKDIR="$(mktemp --directory /tmp/test-bootctl-link-auto.XXXXXXXXXX)"
trap cleanup_link_auto RETURN ERR
# Ensure loader/entries directory is present
bootctl install --make-entry-directory=yes
local ESP
ESP="$(bootctl --print-esp-path)"
cat >"$LINK_WORKDIR/os-release" <<'EOF'
ID=testos
NAME="Test OS"
PRETTY_NAME="Test OS"
EOF
echo "fake-kernel" >"$LINK_WORKDIR/vmlinuz"
echo "fake-initrd" >"$LINK_WORKDIR/initrd"
# Two distinct UKIs, so we can tell which one was picked up.
ukify build \
--linux "$LINK_WORKDIR/vmlinuz" \
--initrd "$LINK_WORKDIR/initrd" \
--os-release "@$LINK_WORKDIR/os-release" \
--uname "1.2.3-testkernel" \
--cmdline "quiet uki=a" \
--output "$LINK_WORKDIR/uki_a.efi"
ukify build \
--linux "$LINK_WORKDIR/vmlinuz" \
--initrd "$LINK_WORKDIR/initrd" \
--os-release "@$LINK_WORKDIR/os-release" \
--uname "1.2.3-testkernel" \
--cmdline "quiet uki=b" \
--output "$LINK_WORKDIR/uki_b.efi"
local TOKEN="systemdtest"
local BOOTCTL=(bootctl "--entry-token=literal:$TOKEN")
local ENTRY="$ESP/loader/entries/${TOKEN}-commit_1.conf"
# --- Test 1: link-auto picks up kernel.efi + extras.d/ from /run/systemd/uki/ ---
rm -rf /run/systemd/uki
mkdir -p /run/systemd/uki/extras.d
cp "$LINK_WORKDIR/uki_a.efi" /run/systemd/uki/kernel.efi
echo "sysext-data" >/run/systemd/uki/extras.d/hello.sysext.raw
echo "confext-data" >/run/systemd/uki/extras.d/hello.confext.raw
echo "cred-data" >/run/systemd/uki/extras.d/hello.cred
"${BOOTCTL[@]}" link-auto
test -f "$ENTRY"
test -f "$ESP/$TOKEN/kernel.efi"
cmp "$LINK_WORKDIR/uki_a.efi" "$ESP/$TOKEN/kernel.efi"
test -f "$ESP/$TOKEN/hello.sysext.raw"
test -f "$ESP/$TOKEN/hello.confext.raw"
test -f "$ESP/$TOKEN/hello.cred"
grep "^uki /${TOKEN}/kernel.efi\$" "$ENTRY" >/dev/null
grep "^extra /${TOKEN}/hello.sysext.raw\$" "$ENTRY" >/dev/null
grep "^extra /${TOKEN}/hello.confext.raw\$" "$ENTRY" >/dev/null
grep "^extra /${TOKEN}/hello.cred\$" "$ENTRY" >/dev/null
"${BOOTCTL[@]}" unlink "${TOKEN}-commit_1.conf"
test ! -e "$ENTRY"
test ! -e "$ESP/$TOKEN/kernel.efi"
# --- Test 2: versioned kernel.efi.v/ and extras .v/ are resolved via vpick ---
rm -rf /run/systemd/uki
mkdir -p /run/systemd/uki/kernel.efi.v /run/systemd/uki/extras.d/hello.sysext.raw.v
cp "$LINK_WORKDIR/uki_a.efi" /run/systemd/uki/kernel.efi.v/kernel_1.0.efi
cp "$LINK_WORKDIR/uki_a.efi" /run/systemd/uki/kernel.efi.v/kernel_2.0.efi
echo "sysext-1" >/run/systemd/uki/extras.d/hello.sysext.raw.v/hello_1.0.sysext.raw
echo "sysext-2" >/run/systemd/uki/extras.d/hello.sysext.raw.v/hello_2.0.sysext.raw
"${BOOTCTL[@]}" link-auto
test -f "$ENTRY"
# vpick must select the newest version
test -f "$ESP/$TOKEN/kernel_2.0.efi"
test ! -e "$ESP/$TOKEN/kernel_1.0.efi"
test -f "$ESP/$TOKEN/hello_2.0.sysext.raw"
test ! -e "$ESP/$TOKEN/hello_1.0.sysext.raw"
grep "^uki /${TOKEN}/kernel_2.0.efi\$" "$ENTRY" >/dev/null
grep "^extra /${TOKEN}/hello_2.0.sysext.raw\$" "$ENTRY" >/dev/null
"${BOOTCTL[@]}" unlink "${TOKEN}-commit_1.conf"
# --- Test 3: priority — /etc/systemd/uki/ wins over /run/systemd/uki/ ---
rm -rf /run/systemd/uki /etc/systemd/uki
mkdir -p /run/systemd/uki/extras.d /etc/systemd/uki/extras.d
cp "$LINK_WORKDIR/uki_b.efi" /run/systemd/uki/kernel.efi
cp "$LINK_WORKDIR/uki_a.efi" /etc/systemd/uki/kernel.efi
echo "run-cred" >/run/systemd/uki/extras.d/hello.cred
echo "etc-cred" >/etc/systemd/uki/extras.d/hello.cred
"${BOOTCTL[@]}" link-auto
test -f "$ESP/$TOKEN/kernel.efi"
# The /etc copy (uki_a) must win over the /run copy (uki_b)
cmp "$LINK_WORKDIR/uki_a.efi" "$ESP/$TOKEN/kernel.efi"
cmp <(echo "etc-cred") "$ESP/$TOKEN/hello.cred"
"${BOOTCTL[@]}" unlink "${TOKEN}-commit_1.conf"
rm -rf /etc/systemd/uki
# --- Test 4: with nothing staged, link-auto is a successful no-op ---
rm -rf /run/systemd/uki
"${BOOTCTL[@]}" link-auto
# No entries referencing our token should remain.
local leftover
leftover="$(find "$ESP/loader/entries/" -name "*$TOKEN*" -print -quit 2>/dev/null)"
test -z "$leftover"
# === Varlink coverage: io.systemd.BootControl.LinkAuto ===
local BOOTCTL_BIN vreply vid vtoken
BOOTCTL_BIN="$(type -p bootctl)"
# --- Test 5: LinkAuto discovers kernel.efi + extras ---
rm -rf /run/systemd/uki
mkdir -p /run/systemd/uki/extras.d
cp "$LINK_WORKDIR/uki_a.efi" /run/systemd/uki/kernel.efi
echo "cred-data" >/run/systemd/uki/extras.d/hello.cred
vreply="$(varlinkctl call --json=short "$BOOTCTL_BIN" io.systemd.BootControl.LinkAuto '{}')"
vid="$(echo "$vreply" | jq -r '.ids[0]')"
test -n "$vid"
test "$vid" != "null"
vtoken="${vid%%-commit_*}"
test -n "$vtoken"
test -f "$ESP/loader/entries/$vid"
test -f "$ESP/$vtoken/kernel.efi"
test -f "$ESP/$vtoken/hello.cred"
grep "^uki /$vtoken/kernel.efi\$" "$ESP/loader/entries/$vid" >/dev/null
varlinkctl call --quiet "$BOOTCTL_BIN" io.systemd.BootControl.Unlink "{\"id\":\"$vid\"}"
test ! -e "$ESP/loader/entries/$vid"
test ! -e "$ESP/$vtoken/kernel.efi"
test ! -e "$ESP/$vtoken/hello.cred"
# --- Test 6: LinkAuto with nothing staged returns an empty id list ---
rm -rf /run/systemd/uki
vreply="$(varlinkctl call --json=short "$BOOTCTL_BIN" io.systemd.BootControl.LinkAuto '{}')"
assert_eq "$(echo "$vreply" | jq -r '.ids | length')" "0"
}
run_testcases