# 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 < /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 }