#!/usr/bin/env bash set -euo pipefail [[ $# -eq 1 ]] || { echo "usage: test-native-recovery-beacon-grub-qemu.sh " >&2 exit 64 } [[ ${EUID} -eq 0 ]] || { echo "the GRUB/QEMU recovery gate requires root for a disposable loop disk" >&2 exit 77 } source_root=$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd) receipt=$(readlink -m "$1") test_root=$(mktemp -d) disk=${test_root}/grub-recovery.img mount_root=${test_root}/mnt loop_device= cleanup() { if mountpoint -q "${mount_root}"; then umount "${mount_root}" fi if [[ -n ${loop_device} ]]; then losetup -d "${loop_device}" fi if [[ ${GHOS_KEEP_TEST_ROOT:-0} != 1 ]]; then rm -rf "${test_root}" else echo "GHOS_TEST_ROOT=${test_root}" >&2 fi } trap cleanup EXIT for command_name in grub-install losetup mkfs.ext4 mount mountpoint nasm \ parted qemu-system-x86_64 timeout udevadm; do command -v "${command_name}" >/dev/null done "${source_root}/scripts/render-native-recovery-beacon.sh" "${test_root}/beacon" truncate -s 128M "${disk}" parted -s "${disk}" mklabel gpt parted -s "${disk}" mkpart bios_grub 1MiB 2MiB parted -s "${disk}" set 1 bios_grub on parted -s "${disk}" mkpart root ext4 2MiB 100% loop_device=$(losetup --find --show --partscan "${disk}") root_partition=${loop_device}p2 udevadm settle --timeout=10 for _ in {1..50}; do [[ -b ${root_partition} ]] && break sleep 0.1 done [[ -b ${root_partition} ]] mkfs.ext4 -q -F "${root_partition}" mkdir -p "${mount_root}" mount "${root_partition}" "${mount_root}" grub-install \ --target=i386-pc \ --boot-directory="${mount_root}/boot" \ --no-floppy \ --recheck \ "${loop_device}" >/dev/null install -m 0644 /dev/stdin "${mount_root}/boot/grub/grub.cfg" <<'EOF' serial --unit=0 --speed=115200 terminal_input serial terminal_output serial set timeout=0 set timeout_style=hidden set default="guanghu-native-once" insmod loadenv set guanghu_recovery= if load_env --file '(hd0)68+2' guanghu_recovery; then if [ "${guanghu_recovery}" = "ubuntu" ]; then set default="ubuntu-recovery-test" fi fi menuentry 'Guanghu native default test' --id 'guanghu-native-once' { insmod chain chainloader (hd0)80+1 boot } menuentry 'Ubuntu recovery test' --id 'ubuntu-recovery-test' { insmod chain chainloader (hd0)81+1 boot } EOF install -m 0644 /dev/stdin "${test_root}/exit-sector.asm" <<'EOF' bits 16 org 0x7c00 mov dx, 0xf4 mov al, EXIT_VALUE out dx, al cli hlt times 510 - ($ - $$) db 0 dw 0xaa55 EOF nasm -f bin -DEXIT_VALUE=0x10 "${test_root}/exit-sector.asm" \ -o "${test_root}/native-exit.bin" nasm -f bin -DEXIT_VALUE=0x11 "${test_root}/exit-sector.asm" \ -o "${test_root}/ubuntu-exit.bin" dd if="${test_root}/native-exit.bin" of="${loop_device}" \ bs=512 seek=80 count=1 conv=notrunc status=none dd if="${test_root}/ubuntu-exit.bin" of="${loop_device}" \ bs=512 seek=81 count=1 conv=notrunc status=none sync umount "${mount_root}" losetup -d "${loop_device}" loop_device= run_qemu() { local stage=$1 local expected_status=$2 set +e timeout 20 qemu-system-x86_64 \ -machine pc,accel=tcg \ -m 64M \ -drive "format=raw,file=${disk}" \ -display none \ -monitor none \ -serial null \ -no-reboot \ -device isa-debug-exit,iobase=0xf4,iosize=0x04 local observed_status=$? set -e printf 'GHNRP_QEMU_STAGE=%s EXPECTED=%s OBSERVED=%s\n' \ "${stage}" "${expected_status}" "${observed_status}" [[ ${observed_status} -eq ${expected_status} ]] } dd if="${test_root}/beacon/guanghu-recovery-clear.env" of="${disk}" \ bs=512 seek=68 count=2 conv=notrunc,fsync status=none run_qemu clear_selects_native 33 dd if="${test_root}/beacon/guanghu-recovery-active.env" of="${disk}" \ bs=512 seek=68 count=2 conv=notrunc,fsync status=none run_qemu active_selects_hosted_recovery 35 "${source_root}/scripts/clear-native-recovery-beacon.sh" \ "${disk}" \ "${test_root}/beacon/guanghu-recovery-clear.env" >/dev/null dd if="${disk}" of="${test_root}/beacon.after-ubuntu.env" \ bs=512 skip=68 count=2 status=none if grep -aFq 'guanghu_recovery=ubuntu' \ "${test_root}/beacon.after-ubuntu.env"; then echo "hosted recovery did not consume the Guanghu recovery beacon" >&2 exit 1 fi cmp \ "${test_root}/beacon/guanghu-recovery-clear.env" \ "${test_root}/beacon.after-ubuntu.env" run_qemu consumed_selects_native_again 33 mkdir -p "$(dirname "${receipt}")" observed_at=$(date --iso-8601=seconds) active_sha=$(sha256sum \ "${test_root}/beacon/guanghu-recovery-active.env" | awk '{print $1}') consumed_sha=$(sha256sum \ "${test_root}/beacon.after-ubuntu.env" | awk '{print $1}') cat >"${receipt}" <