From d96454969b5e904f454a6e1486fbdbb6c1c2ecd8 Mon Sep 17 00:00:00 2001 From: Jeremy Huntwork Date: Sat, 31 Oct 2026 22:30:38 -0400 Subject: [PATCH] Add a disk-backed Mere dinit boot test Assemble a disposable ext4 disk from a prepared Mere profile and store without host mounts. Boot the profile's kernel directly with dinit as PID 1, check the mounted root and init identity, and request shutdown through dinit using a minimal test service graph. Document inputs, host requirements, serial result checks, and the boundary between this developer harness and production boot configuration. No initramfs or bootloader is introduced. Part of #634. --- test/qemu/README.md | 40 ++++++++++++++++++ test/qemu/dinit-smoke | 94 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 134 insertions(+) create mode 100755 test/qemu/dinit-smoke diff --git a/test/qemu/README.md b/test/qemu/README.md index b6d134c4..62a046f6 100644 --- a/test/qemu/README.md +++ b/test/qemu/README.md @@ -62,3 +62,43 @@ Track harness work in jhuntwork/merelinux#635, distro boot work in #634, and the overall dinit transition in #633. A Mere disk-backed guest without an initramfs is a separate next step. Extend guest scenarios for package-manager execution and service behavior rather than treating this environment probe as that evidence. + +## Disk-backed Mere/dinit smoke test + +`dinit-smoke` copies an existing named profile and its store into a disposable +ext4 image. It boots the profile's own `boot/vmlinux`, runs dinit directly as +PID 1, verifies the mounted root and PID 1 identity, then requests power-off +through dinit. It supplies a minimal test-only service graph. It does not install +or select the distro's production boot services, verify a bootloader, or test +package-manager service reconciliation. + +Prepare an alternate storage root with the official repository config and key, +then install the three test inputs: + +```sh +mere --root "$storage" install -p guest linux busybox dinit +QEMU_TIMEOUT=120 sh test/qemu/dinit-smoke "$storage" guest > dinit-guest.log 2>&1 +``` + +The prepared root must have a named profile with logical `/mere/store` links and +`boot/vmlinux`, `/bin/dinit`, `/bin/sh`, mount utilities, and dinit's `shutdown`. +Use a dedicated test profile. Its `etc-defaults` are deliberately not activated: +the harness supplies its own boot, probe, and shutdown services under `/etc`. + +Additional host dependencies are `readlink`, `truncate`, and `mke2fs` supporting +`-d` directory population. No host mounts or loop devices are used. The kernel +must have ext4, IDE/ATA disk support, serial console, and automatic devtmpfs +mounting built in. QEMU loads the kernel directly with `root=/dev/sda` and +`init=/bin/dinit`; no initramfs is passed. + +`QEMU` and `QEMU_TIMEOUT` work as above. `QEMU_DISK_SIZE` defaults to `384M` and +must accommodate the copied store. The launcher reports success only after the +boot marker, dinit's kernel shutdown message, and kernel power-off message appear +and QEMU exits successfully. The copied root and disk are removed afterward. + +The initial successful inputs were Mere linux 6.18.42-2, BusyBox 1.37.0-10, and +dinit 0.22.1-1 (x86_64), with QEMU 8.2.2 TCG. Dinit stopped the test services and +powered the guest off. The test does not establish complete filesystem shutdown +ordering or persistent-disk integrity; those need explicit boot/shutdown services +and further checks under #634. These input versions describe observed evidence, +not a frozen dependency set. diff --git a/test/qemu/dinit-smoke b/test/qemu/dinit-smoke new file mode 100755 index 00000000..0316791f --- /dev/null +++ b/test/qemu/dinit-smoke @@ -0,0 +1,94 @@ +#!/bin/sh +# Boot an existing Mere profile on an ext4 disk with dinit as PID 1. +set -eu +if [ "$#" -ne 2 ]; then + printf 'Usage: %s STORAGE_ROOT PROFILE_NAME\n' "$0" >&2 + exit 2 +fi +storage=$1 +profile=$storage/mere/profiles/$2/root +qemu=${QEMU:-qemu-system-x86_64} +for dir in "$storage/mere/store" "$profile"; do + if [ ! -d "$dir" ]; then + printf 'Missing directory: %s\n' "$dir" >&2 + exit 2 + fi +done +work=$(mktemp -d) +cleanup() { + # Copied store directories retain read-only modes. Change only directories + # in the disposable copy; never follow its absolute store symlinks. + find "$work" -type d -exec chmod u+w {} + + rm -rf "$work" +} +trap cleanup 0 +trap 'exit 130' INT +trap 'exit 143' TERM +root=$work/root +mkdir -p "$root/mere" "$root/etc/dinit.d" "$root/proc" "$root/sys" \ + "$root/dev" "$root/run" "$root/tmp" "$root/var/log" +cp -RPp "$storage/mere/store" "$root/mere/" +for dir in bin sbin usr lib boot; do + if [ -d "$profile/$dir" ]; then + cp -RPp "$profile/$dir" "$root/" + fi +done +# Resolve the kernel's logical symlink against this guest's store, not the host. +kernel_target=$(readlink "$root/boot/vmlinux") +case $kernel_target in + /mere/store/*) kernel=$root$kernel_target ;; + *) printf 'Expected a logical store link for boot/vmlinux\n' >&2; exit 2 ;; +esac +[ -r "$kernel" ] +: > "$root/etc/fstab" +cat > "$root/etc/dinit.d/boot" <<'SERVICE' +type = internal +depends-on = shutdown-probe +SERVICE +cat > "$root/etc/dinit.d/probe" <<'SERVICE' +type = scripted +command = /bin/sh /etc/guest-probe +options = starts-on-console +SERVICE +cat > "$root/etc/dinit.d/shutdown-probe" <<'SERVICE' +type = process +command = /bin/sh /etc/shutdown-probe +depends-on = probe +options = starts-on-console +restart = false +SERVICE +cat > "$root/etc/guest-probe" <<'GUEST' +#!/bin/sh +set -eu +export PATH=/bin:/sbin:/usr/bin:/usr/sbin +mount -t proc proc /proc +mount -t sysfs sysfs /sys +printf 'MERE_KERNEL: '; uname -r +printf 'PID1: '; cat /proc/1/comm +[ "$(cat /proc/1/comm)" = dinit ] +printf 'ROOT: '; mount | grep ' on / ' +printf 'DINIT_BOOT_PASS\n' +sync +GUEST +cat > "$root/etc/shutdown-probe" <<'GUEST' +#!/bin/sh +export PATH=/bin:/sbin:/usr/bin:/usr/sbin +printf 'SHUTDOWN_PROBE_START\n' +sleep 1 +exec shutdown -p +GUEST +truncate -s "${QEMU_DISK_SIZE:-384M}" "$work/root.ext4" +mke2fs -q -t ext4 -F -d "$root" "$work/root.ext4" +timeout "${QEMU_TIMEOUT:-120}" "$qemu" \ + -accel tcg,thread=single -m 256 -smp 1 -nodefaults \ + -display none -serial stdio -monitor none -no-reboot \ + -kernel "$kernel" -drive "file=$work/root.ext4,format=raw,if=ide" \ + -append 'console=ttyS0 root=/dev/sda rootfstype=ext4 rw init=/bin/dinit panic=-1' \ + > "$work/serial.log" 2>&1 || { + cat "$work/serial.log" + exit 1 + } +cat "$work/serial.log" +grep -q '^DINIT_BOOT_PASS' "$work/serial.log" +grep -q 'Issuing shutdown via kernel' "$work/serial.log" +grep -q 'reboot: Power down' "$work/serial.log"