302 lines
10 KiB
Bash
302 lines
10 KiB
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... scripts/lib/qmp.py 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).
|
|
beDesktopUp() {
|
|
beQmp "$1" screendump "$1/mode.png" 2> /dev/null || return 1
|
|
python3 - "$1/mode.png" "$BEOS_VIDEO_MODE" <<'PY'
|
|
import sys
|
|
from PIL import Image
|
|
|
|
# The logo's red, in the 16-bit mode, and how many of its pixels show in
|
|
# the corner when Deskbar is there (about 280).
|
|
LOGO_RED = (206, 0, 49)
|
|
CORNER_W = 120
|
|
CORNER_H = 24
|
|
LOGO_PIXELS_MIN = 100
|
|
|
|
width, height = (int(n) for n in sys.argv[2].split('x'))
|
|
screen = Image.open(sys.argv[1]).convert('RGB')
|
|
if screen.size != (width, height):
|
|
sys.exit(1)
|
|
corner = screen.crop((width - CORNER_W, 0, width, CORNER_H))
|
|
sys.exit(0 if list(corner.getdata()).count(LOGO_RED) >= LOGO_PIXELS_MIN else 1)
|
|
PY
|
|
}
|
|
|
|
|
|
# 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
|
|
python3 "$JOEY_REPO/scripts/lib/qmp.py" "$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
|
|
}
|