joeylib2/scripts/lib/beGate.sh

299 lines
10 KiB
Bash

# shellcheck shell=bash
# Part of JoeyLib, Copyright (c) 2026 Scott Duensing, under the MIT License.
# See LICENSE.md for license information.
# SPDX-License-Identifier: MIT
#
# Shared by the Be API ports' gates (scripts/bench-be.sh,
# scripts/verify-be-*.sh): run a program of a Be API port -- haiku, haiku32
# (Haiku R1/beta6) or beos (BeOS R5) -- in that port's VM under QEMU. The VM
# (component haikuVm, haikuVm32 or beosVm) boots its system disk in QEMU's
# snapshot mode, so nothing is ever written to it, and runs its job runner
# (installer/lib/haiku.sh and installer/lib/beos.sh say how): the program
# and a job.sh go to the machine on a second disk, a BFS volume made here,
# and the program's files come back on it. The machine powers off when the
# job is done, so QEMU exits. Haiku runs under KVM when /dev/kvm can be
# opened; BeOS R5 on QEMU's own Pentium II, a processor it knew. Source
# after joeyEnv.sh.
#
# beNeed PORT exit 2 unless PORT's VM, QEMU and the BFS
# tools are installed; sets BE_PORT
# beJobDisk DISK DIR a job disk holding DIR's files and folders
# beJobGet DISK PATH DEST copy PATH off the job disk to DEST
# beJobScript FOLDER PROGRAM UNTIL SECONDS [ENV...]
# print a job.sh that runs FOLDER/PROGRAM with
# the ENV settings (NAME=VALUE) until its
# joeylog.txt has a line starting UNTIL (empty:
# until it exits) or SECONDS pass
# beQmp WORK ARGS... tests/gate/qmp.c on the VM's QMP socket
# beStart WORK DISK [QEMU-ARGS...]
# boot the VM with the job disk, on BE_DISPLAY
# (default none: headless); sets BE_PID. A
# Haiku VM's HD Audio card plays to
# BE_AUDIODEV (default none: nowhere; a QEMU
# -audiodev whose id is beSound). BeOS R5 is
# taken through its boot loader's menu to the
# fail-safe (VESA) video mode BEOS_VIDEO_MODE:
# its own drivers know none of QEMU's cards,
# and without it the screen is 16 greys.
# Serial port: WORK/serial.log, QMP:
# WORK/qmp.sock
# beStop stop QEMU
# beWaitExit WORK SECONDS wait for QEMU to end (the job runner powers
# the machine off)
# beWaitJob WORK SECONDS wait until the job is about to run: Haiku's
# runner says so on the serial port
# (HAIKU_JOB_STARTED); BeOS R5's has no port
# to say it on, so its desktop up in
# BEOS_VIDEO_MODE, Deskbar and all, stands in
# (the runner starts the job a few seconds
# later)
# The waits fail when QEMU ends first, time runs out or the machine stops
# in its kernel debugger.
# shellcheck source=installer/lib/haiku.sh
source "$JOEY_REPO/installer/lib/haiku.sh"
BE_PORT=""
BE_PID=""
BE_QEMU=""
BE_DISPLAY="${BE_DISPLAY:-none}"
BE_AUDIODEV="${BE_AUDIODEV:-none,id=beSound}"
# Haiku's machine: memory (MB) and processors.
HAIKU_VM_MEMORY_MB=1024
HAIKU_VM_CPUS=2
# BeOS R5's machine: memory (MB) and processor.
BEOS_VM_MEMORY_MB=256
BEOS_VM_CPU="pentium2"
# Its boot loader's menu: open it (space, pressed while the loader starts),
# go to "Select fail-safe video mode", choose the mode BEOS_VIDEO_ROWS rows
# below Default in the mode list (the list QEMU 11.1.2's VGA BIOS gives:
# 1024x768x16, room for a program's window at its default scale, 3), back
# on the main menu choose "Continue booting".
BEOS_VIDEO_MODE="1024x768"
BEOS_VIDEO_ROWS=23
BEOS_MENU_OPEN_PRESSES=10
BEOS_MENU_SETTLE_SECONDS=2
BEOS_MENU_TO_VIDEO="down down ret"
BEOS_MENU_CONTINUE="ret down ret"
# What a kernel debugger session starts with, on the serial port.
BE_KDL_BANNER="Welcome to Kernel Debugging Land"
# beCrashed WORK -- the machine stopped in its kernel debugger.
beCrashed() {
if grep -a -q "$BE_KDL_BANNER" "$1/serial.log" 2> /dev/null; then
echo "$(basename "$0"): the VM stopped in its kernel debugger (${1}/serial.log)" >&2
return 0
fi
return 1
}
# beDesktopUp WORK -- BeOS R5's desktop is up in BEOS_VIDEO_MODE: the
# screen is that size and Deskbar, which R5's boot starts last, shows its
# logo in the top right corner (the boot shows a bare desktop for some
# seconds before it). The logo's red in the 16-bit mode, the corner looked
# at, and how many of the logo's pixels show there with Deskbar up (about
# 280).
BE_LOGO_RED=(206 0 49)
BE_LOGO_CORNER_W=120
BE_LOGO_CORNER_H=24
BE_LOGO_PIXELS_MIN=100
beDesktopUp() {
local probe
local width
local red
beQmp "$1" screendump "$1/mode.png" 2> /dev/null || return 1
probe=$(joeyTool pngProbe) || return 1
[ "$("$probe" size "$1/mode.png")" = "$BEOS_VIDEO_MODE" ] || return 1
width=${BEOS_VIDEO_MODE%x*}
red=$("$probe" count "$1/mode.png" $((width - BE_LOGO_CORNER_W)) 0 "$BE_LOGO_CORNER_W" "$BE_LOGO_CORNER_H" "${BE_LOGO_RED[@]}") || return 1
[ "$red" -ge "$BE_LOGO_PIXELS_MIN" ]
}
# The disk is sized for what goes on it, plus room for what the program
# writes (saves, its log).
beJobDisk() {
local disk="$1"
local dir="$2"
local entry
local sizeMb
local commands=""
local slackMb=32
sizeMb=$(( $(du -sm "$dir" | cut -f1) * 2 + slackMb ))
haikuBfsNew "$disk" "${sizeMb}M" "$HAIKU_JOB_VOLUME" || return 1
for entry in "$dir"/*; do
commands+="cp -r :$entry /myfs/$(basename "$entry")"$'\n'
done
haikuBfs "$disk" <<< "$commands" > /dev/null
}
beJobGet() {
haikuBfs "$1" <<< "cp -r /myfs/$2 :$3" > /dev/null
}
beJobScript() {
local folder="$1"
local program="$2"
local until="$3"
local seconds="$4"
local setting
shift 4
printf 'cd %s\n' "$folder"
for setting in "$@"; do
printf 'export %s\n' "$setting"
done
cat <<EOF
./$program &
pid=\$!
waited=0
while [ \$waited -lt $seconds ] && kill -0 \$pid 2> /dev/null; do
if [ -n "$until" ] && grep -q "^$until" joeylog.txt 2> /dev/null; then
break
fi
sleep 1
waited=\$((waited + 1))
done
kill \$pid 2> /dev/null
wait \$pid
EOF
}
beNeed() {
joeyNeed QEMU_DIR qemu
joeyNeed HAIKU_TOOLS haikuTools
case "$1" in
haiku)
joeyNeed HAIKU_VM_IMAGE haikuVm
BE_QEMU="$QEMU_DIR/bin/qemu-system-x86_64"
;;
haiku32)
joeyNeed HAIKU32_VM_IMAGE haikuVm32
BE_QEMU="$QEMU_DIR/bin/qemu-system-i386"
;;
beos)
joeyNeed BEOS_VM_DIR beosVm
BE_QEMU="$QEMU_DIR/bin/qemu-system-i386"
;;
*)
echo "$(basename "$0"): unknown Be API port '$1' (haiku, haiku32 or beos)" >&2
exit 2
;;
esac
BE_PORT="$1"
}
beQmp() {
local work="$1"
shift
"$(joeyTool qmp)" "$work/qmp.sock" "$@"
}
beStart() {
local work="$1"
local disk="$2"
shift 2
rm -f "$work/serial.log" "$work/qmp.sock"
case "$BE_PORT" in
beos)
beStartBeos "$work" "$disk" "$@"
;;
*)
beStartHaiku "$work" "$disk" "$@"
;;
esac
}
# beStartBeos WORK DISK [QEMU-ARGS...] -- boot the BeOS R5 VM (its CD's
# boot floppy, which boots the system disk) and take it through the boot
# menu.
beStartBeos() {
local work="$1"
local disk="$2"
local press
local presses=()
local rows=()
shift 2
"$BE_QEMU" -machine pc -cpu "$BEOS_VM_CPU" -m "$BEOS_VM_MEMORY_MB" \
-drive "file=$BEOS_VM_DIR/floppy.img,format=raw,if=floppy,readonly=on" \
-drive "file=$BEOS_VM_DIR/system.img,format=raw,index=0,media=disk,snapshot=on" \
-drive "file=$disk,format=raw,index=1,media=disk" -boot a -vga std \
-serial "file:$work/serial.log" -qmp "unix:$work/qmp.sock,server,nowait" -no-reboot \
-display "$BE_DISPLAY" "$@" > "$work/qemu.log" 2>&1 &
BE_PID=$!
while [ ! -S "$work/qmp.sock" ] && joeyAlive "$BE_PID"; do
sleep 0.1
done
for ((press = 0; press < BEOS_MENU_OPEN_PRESSES; press++)); do
presses+=(spc)
done
for ((press = 0; press < BEOS_VIDEO_ROWS; press++)); do
rows+=(down)
done
beQmp "$work" keys "${presses[@]}" || return 1
sleep "$BEOS_MENU_SETTLE_SECONDS"
# shellcheck disable=SC2086
beQmp "$work" keys $BEOS_MENU_TO_VIDEO "${rows[@]}" $BEOS_MENU_CONTINUE
}
beStartHaiku() {
local work="$1"
local disk="$2"
local image="$HAIKU_VM_IMAGE"
local accel=(-accel tcg)
shift 2
if [ "$BE_PORT" = haiku32 ]; then
image="$HAIKU32_VM_IMAGE"
fi
if [ -r /dev/kvm ] && [ -w /dev/kvm ]; then
accel=(-enable-kvm -cpu host)
fi
"$BE_QEMU" "${accel[@]}" -m "$HAIKU_VM_MEMORY_MB" -smp "$HAIKU_VM_CPUS" \
-drive "file=$image,format=raw,if=ide,snapshot=on" \
-drive "file=$disk,format=raw,if=ide" \
-serial "file:$work/serial.log" -qmp "unix:$work/qmp.sock,server,nowait" -no-reboot \
-display "$BE_DISPLAY" -audiodev "$BE_AUDIODEV" -device intel-hda -device hda-duplex,audiodev=beSound \
"$@" > "$work/qemu.log" 2>&1 &
BE_PID=$!
}
beStop() {
joeyStop "$BE_PID"
BE_PID=""
}
beWaitExit() {
local waited
for ((waited = 0; waited < $2; waited++)); do
joeyAlive "$BE_PID" || return 0
beCrashed "$1" && return 1
sleep 1
done
return 1
}
beWaitJob() {
local waited
for ((waited = 0; waited < $2; waited++)); do
if [ "$BE_PORT" = beos ]; then
beDesktopUp "$1" && return 0
elif grep -q "^$HAIKU_JOB_STARTED" "$1/serial.log" 2> /dev/null; then
return 0
fi
joeyAlive "$BE_PID" || return 1
beCrashed "$1" && return 1
sleep 1
done
return 1
}