#!/bin/bash # Boots IMAGE headless and waits for the self-test (init's SMP checks, then # spawntest) to report; the machine halts itself when done. Twice, on one # fresh keystore: the first boot sets up its first user at the gatekeeper, # the second gets the passcode wrong once and then right. Exit 0 only if # both sign in and both say "SMPTEST PASS". set -uo pipefail IMAGE="${1:?usage: test-qemu.sh IMAGE}" SMP="${SMP:-4}" KERNEL="target/riscv64gc-unknown-none-elf/release/adapter-riscv" LOG="$(mktemp -t kos-test-qemu.XXXXXX)" KEYS="${LOG}.keys" MON="${LOG}.mon" truncate -s 64K "${KEYS}" # Types `lines` (one per argument) at the gatekeeper once it waits. owner() { for _ in $(seq 300); do grep -aq "gatekeeper: waiting" "${LOG}" 2>/dev/null && break; sleep 0.2; done for line in "$@"; do for ((i = 0; i < ${#line}; i++)); do echo "sendkey ${line:i:1}" | socat - "unix-connect:${MON}" >/dev/null 2>&1; sleep 0.05 done echo "sendkey ret" | socat - "unix-connect:${MON}" >/dev/null 2>&1; sleep 0.3 done } # One boot; `owner` types the arguments. Its serial output lands in LOG. boot() { : > "${LOG}"; rm -f "${MON}" owner "$@" & timeout ${QT:-120} qemu-system-riscv64 -machine virt -m 512M -smp "${SMP}" -bios default \ -kernel "${KERNEL}" \ -drive "file=${IMAGE},if=none,format=raw,id=hd0" -device virtio-blk-device,drive=hd0 \ -drive "file=${KEYS},if=none,format=raw,id=ks" -device virtio-blk-device,drive=ks \ -device virtio-rng-device -device virtio-gpu-device -device virtio-keyboard-device \ -display none -serial "file:${LOG}" -monitor "unix:${MON},server,nowait" local s=$? wait return $s } # First boot: the first user's name, a passcode (twice), no duress # passcode (twice empty). boot tester 1234 1234 "" "" status=$? first_ok=0 grep -aq "init: tester is in" "${LOG}" && grep -aq "SMPTEST PASS" "${LOG}" && first_ok=1 echo "test-qemu: first boot: first user set up: $([ ${first_ok} -eq 1 ] && echo ok || echo FAIL)" # Second boot: the keystore remembers; a wrong guess, then the passcode, # then on past the menu. boot tester 9999 tester 1234 "" [ ${status} -eq 0 ] && status=$? second_ok=0 grep -aq "init: tester is in" "${LOG}" && grep -aq "from the last boot reads back: ok" "${LOG}" && second_ok=1 echo "test-qemu: second boot: wrong passcode refused, right one signs in, sealed file kept: $([ ${second_ok} -eq 1 ] && echo ok || echo FAIL)" rm -f "${KEYS}" "${MON}" grep -a "smptest:\|spawntest:\|Hello\|SMPTEST" "${LOG}" # What k-os wrote must be ext4 that the real tools accept. disk_ok=1 if command -v e2fsck >/dev/null && command -v debugfs >/dev/null; then if ! e2fsck -fn "${IMAGE}" >"${LOG}.fsck" 2>&1; then echo "test-qemu: e2fsck rejects the disk:"; cat "${LOG}.fsck"; disk_ok=0 elif [ "$(debugfs -R 'cat /selftest.txt' "${IMAGE}" 2>/dev/null)" != "written by k-os on ext4" ]; then echo "test-qemu: /selftest.txt is not on the disk"; disk_ok=0 fi # The one user's folder: users/<16 hex digits>. user="$(debugfs -R 'ls /users' "${IMAGE}" 2>/dev/null | grep -aoE '[0-9a-f]{16}' | head -1)" readme="/users/${user}/home/readme.txt" if [ "${disk_ok}" -eq 0 ]; then : elif [ -z "${user}" ]; then echo "test-qemu: no user's folder on the disk"; disk_ok=0 elif debugfs -R "cat ${readme}" "${IMAGE}" 2>/dev/null | grep -aq "Welcome"; then echo "test-qemu: ${readme} is plaintext on the disk"; disk_ok=0 elif ! debugfs -R "cat ${readme}" "${IMAGE}" 2>/dev/null | head -c 8 | grep -aq "KOSFILE1"; then echo "test-qemu: ${readme} is not a sealed file"; disk_ok=0 else echo "test-qemu: e2fsck clean, file on disk, home sealed" fi rm -f "${LOG}.fsck" fi if [ "${disk_ok}" -eq 1 ] && [ ${first_ok} -eq 1 ] && [ ${second_ok} -eq 1 ] && grep -aq "SMPTEST PASS" "${LOG}"; then echo "test-qemu: pass (${SMP} harts)" rm -f "${LOG}" exit 0 fi [ "${status}" -eq 124 ] && echo "test-qemu: timed out" echo "test-qemu: FAIL, last serial output (${LOG}):" tail -20 "${LOG}" exit 1