lib-runscm.sh (10611B)
1 # lib-runscm.sh — driver-agnostic harness for run.scm-driven stages. 2 # 3 # Boot3/4/5 each drive a per-stage pipeline by invoking scheme1 against a 4 # host-generated run.scm. Two transports: 5 # DRIVER=podman → bind-mount in/ ro and out/ rw under /work in a 6 # scratch+busybox container, exec in/scheme1 7 # in/combined.scm with cwd=/work. 8 # DRIVER=seed → pack the staging dir into a cpio on a virtio-blk read- 9 # only disk (init at cpio root + in/ subtree), boot the 10 # seed kernel with init=init and combined.scm, recover 11 # outputs via the SEEDFS dump on a second virtio-blk 12 # disk. The host extractor filters to out/-prefixed 13 # entries, strips the prefix, writes to $STAGE/out/. 14 # 15 # Both drivers see the same flat namespace; run.scm uses explicit 16 # in/<name> for reads and out/<name> for writes. 17 # 18 # DSL (source as `. scripts/lib-runscm.sh`): 19 # 20 # runscm_init <staging-dir> <out-dir> 21 # runscm_scheme1 <path> # init=scheme1 (boot2) 22 # runscm_prelude <path> # scheme1/prelude.scm 23 # runscm_runscm <path> # static driver script 24 # runscm_gen <gen-script> <args...> # OR generate run.scm, 25 # # log size, register it. 26 # runscm_input <name> <host-path> # repeatable; staged at in/<name> 27 # runscm_input_tree <prefix> <src-root> # repeatable; tree under in/<prefix> 28 # runscm_export <name>... # one or more output names 29 # runscm_run [timeout-s] # default 600s 30 # 31 # Required env per driver: 32 # podman: IMAGE, PLATFORM 33 # seed: KERNEL_IMAGE, EXTRACT, optional QEMU_MEM (default 2048M) 34 # both: DRIVER=podman|seed 35 36 S_STAGE_DIR= 37 S_OUT_DIR= 38 S_SCHEME1= 39 S_PRELUDE= 40 S_RUNSCM= 41 S_EXPORTS= 42 43 runscm_init() { 44 S_STAGE_DIR=$1; S_OUT_DIR=$2 45 rm -rf "$S_STAGE_DIR" 46 mkdir -p "$S_STAGE_DIR/in" "$S_STAGE_DIR/out" "$S_OUT_DIR" 47 S_SCHEME1=; S_PRELUDE=; S_RUNSCM= 48 S_EXPORTS= 49 } 50 51 runscm_scheme1() { S_SCHEME1=$1; } 52 runscm_prelude() { S_PRELUDE=$1; } 53 runscm_runscm() { S_RUNSCM=$1; } 54 55 # runscm_gen <gen-script> <args...> 56 # Run a host-side generator that emits run.scm to $S_STAGE_DIR/run.scm, 57 # log its size, and register it as the driver script. Used by 58 # boot4/5/6 which build their run.scm dynamically. 59 runscm_gen() { 60 _gen=$1; shift 61 _runscm=$S_STAGE_DIR/run.scm 62 "$_gen" "$@" "$_runscm" 63 echo "[$BOOT_TAG] generated run.scm: $(wc -l <"$_runscm") lines, $(wc -c <"$_runscm") bytes" 64 S_RUNSCM=$_runscm 65 } 66 67 runscm_input() { 68 name=$1; src=$2 69 case "$name" in 70 */*) mkdir -p "$S_STAGE_DIR/in/$(dirname "$name")" ;; 71 esac 72 cp "$src" "$S_STAGE_DIR/in/$name" 73 } 74 75 # Stage every regular file under <src-root> into in/<prefix>/..., 76 # preserving the relative directory tree. 77 runscm_input_tree() { 78 prefix=$1; src_root=$2 79 [ -d "$src_root" ] || { echo "runscm: input_tree: $src_root not a dir" >&2; exit 2; } 80 ( cd "$src_root" && find . -type f ) | sed 's|^\./||' | sort | while read -r rel; do 81 [ -n "$rel" ] || continue 82 mkdir -p "$S_STAGE_DIR/in/$prefix/$(dirname "$rel")" 83 cp "$src_root/$rel" "$S_STAGE_DIR/in/$prefix/$rel" 84 done 85 } 86 87 # runscm_input_from_src <subpath> [<name>] 88 # Pull a file from the canonical generated source tree at 89 # build/$ARCH/src/src/<subpath>. Stages it under in/<name>; 90 # <name> defaults to basename(subpath). For the rare `bin/` case, 91 # call runscm_input directly with build/$ARCH/src/bin/<file>. 92 runscm_input_from_src() { 93 _subpath=$1; _name=${2:-} 94 [ -n "$_name" ] || _name=$(basename "$_subpath") 95 runscm_input "$_name" "build/$ARCH/src/src/$_subpath" 96 } 97 98 # runscm_input_tree_from_src <prefix> <subpath> 99 # Same as runscm_input_tree, but the source root is 100 # build/$ARCH/src/src/<subpath>. 101 runscm_input_tree_from_src() { 102 _prefix=$1; _subpath=$2 103 runscm_input_tree "$_prefix" "build/$ARCH/src/src/$_subpath" 104 } 105 106 runscm_export() { 107 for _n in "$@"; do S_EXPORTS="$S_EXPORTS $_n"; done 108 } 109 110 runscm_run() { 111 timeout=${1:-600} 112 [ -n "$S_SCHEME1" ] || { echo "runscm: scheme1 not set" >&2; exit 2; } 113 [ -n "$S_PRELUDE" ] || { echo "runscm: prelude not set" >&2; exit 2; } 114 [ -n "$S_RUNSCM" ] || { echo "runscm: run.scm not set" >&2; exit 2; } 115 cp "$S_SCHEME1" "$S_STAGE_DIR/in/scheme1" 116 chmod +x "$S_STAGE_DIR/in/scheme1" 117 cat "$S_PRELUDE" "$S_RUNSCM" > "$S_STAGE_DIR/in/combined.scm" 118 # Top-level reference copy of run.scm for human inspection. 119 # boot4/5 gen scripts already write here; skip the self-copy. 120 case "$S_RUNSCM" in 121 "$S_STAGE_DIR/run.scm") : ;; 122 *) cp "$S_RUNSCM" "$S_STAGE_DIR/run.scm" ;; 123 esac 124 125 case "${DRIVER:-podman}" in 126 podman) _runscm_run_podman "$timeout" ;; 127 seed) _runscm_run_seed "$timeout" ;; 128 *) echo "runscm: unknown DRIVER=$DRIVER (expected podman|seed)" >&2; exit 2 ;; 129 esac 130 131 for n in $S_EXPORTS; do 132 if [ ! -f "$S_STAGE_DIR/out/$n" ]; then 133 echo "[runscm/$DRIVER] FAIL: missing output '$n'" >&2 134 ls "$S_STAGE_DIR/out" >&2 || true 135 exit 5 136 fi 137 cp "$S_STAGE_DIR/out/$n" "$S_OUT_DIR/$n" 138 chmod 0700 "$S_OUT_DIR/$n" 139 done 140 } 141 142 # Podman: bind-mount in/ ro and out/ rw under /work; exec in/scheme1. 143 # Outputs land in $S_STAGE_DIR/out/ directly via the rw bind mount. 144 _runscm_run_podman() { 145 : "${IMAGE:?lib-runscm: IMAGE not set}" 146 : "${PLATFORM:?lib-runscm: PLATFORM not set}" 147 in_abs=$(cd "$S_STAGE_DIR/in" && pwd) 148 out_abs=$(cd "$S_STAGE_DIR/out" && pwd) 149 echo "[runscm/podman] scheme1 combined.scm under $IMAGE" >&2 150 podman run --rm -i --pull=never --platform "$PLATFORM" \ 151 -v "$in_abs:/work/in:ro" \ 152 -v "$out_abs:/work/out:rw" \ 153 -w /work "$IMAGE" \ 154 in/scheme1 in/combined.scm 155 } 156 157 # Seed: pack cpio with `init` at the root and the in/ subtree under it; 158 # boot kernel with init=init combined.scm; recover outputs from the 159 # SEEDFS dump on hd1; extract filters out/-prefixed entries directly 160 # into $S_STAGE_DIR/out/. 161 _runscm_run_seed() { 162 timeout=$1 163 : "${KERNEL_IMAGE:?lib-runscm: KERNEL_IMAGE not set}" 164 : "${EXTRACT:?lib-runscm: EXTRACT not set}" 165 mem=${QEMU_MEM:-2048M} 166 cp "$S_STAGE_DIR/in/scheme1" "$S_STAGE_DIR/init" 167 chmod +x "$S_STAGE_DIR/init" 168 ( cd "$S_STAGE_DIR" && { echo init; find in -type f; } | sort -u | cpio -o -H newc 2>/dev/null ) \ 169 > "$S_STAGE_DIR/initramfs.cpio" 170 sz=$(wc -c < "$S_STAGE_DIR/initramfs.cpio") 171 pad=$(( (512 - sz % 512) % 512 )) 172 if [ "$pad" -gt 0 ]; then 173 head -c "$pad" /dev/zero >> "$S_STAGE_DIR/initramfs.cpio" 174 fi 175 mv "$S_STAGE_DIR/initramfs.cpio" "$S_STAGE_DIR/in.img" 176 truncate -s 256M "$S_STAGE_DIR/out.img" 177 178 TRANSCRIPT=$S_STAGE_DIR/transcript.txt 179 echo "[runscm/seed] booting scheme1 + run.scm (timeout ${timeout}s)" >&2 180 seed_arch=${SEED_ARCH:-aarch64} 181 case "$seed_arch" in 182 aarch64) 183 qemu-system-aarch64 \ 184 -machine virt,gic-version=3,accel=hvf -cpu host -m "$mem" \ 185 -nographic -no-reboot \ 186 -global virtio-mmio.force-legacy=false \ 187 -kernel "$KERNEL_IMAGE" \ 188 -drive file="$S_STAGE_DIR/in.img",if=none,format=raw,id=hd0,readonly=on \ 189 -device virtio-blk-device,drive=hd0 \ 190 -drive file="$S_STAGE_DIR/out.img",if=none,format=raw,id=hd1 \ 191 -device virtio-blk-device,drive=hd1 \ 192 -append "init in/combined.scm" \ 193 > "$TRANSCRIPT" 2>&1 & 194 ;; 195 riscv64) 196 # No hvf accel on Apple Silicon for riscv64 — TCG only. 197 qemu-system-riscv64 \ 198 -machine virt -m "$mem" \ 199 -nographic -no-reboot \ 200 -global virtio-mmio.force-legacy=false \ 201 -kernel "$KERNEL_IMAGE" \ 202 -drive file="$S_STAGE_DIR/in.img",if=none,format=raw,id=hd0,readonly=on \ 203 -device virtio-blk-device,drive=hd0 \ 204 -drive file="$S_STAGE_DIR/out.img",if=none,format=raw,id=hd1 \ 205 -device virtio-blk-device,drive=hd1 \ 206 -append "init in/combined.scm" \ 207 > "$TRANSCRIPT" 2>&1 & 208 ;; 209 amd64) 210 # microvm + isa-debug-exit mirrors seed-kernel/run.sh: the 211 # kernel writes to port 0x501 on user exit_group(0) so QEMU 212 # exits cleanly (no `-no-reboot` triple-fault gymnastics). 213 qemu-system-x86_64 \ 214 -machine microvm,acpi=off,pic=off,pit=off,rtc=off,isa-serial=on,auto-kernel-cmdline=off \ 215 -cpu max -m "$mem" \ 216 -nodefaults -display none -serial stdio -no-reboot \ 217 -global virtio-mmio.force-legacy=false \ 218 -device isa-debug-exit,iobase=0x501,iosize=2 \ 219 -kernel "$KERNEL_IMAGE" \ 220 -drive file="$S_STAGE_DIR/in.img",if=none,format=raw,id=hd0,readonly=on \ 221 -device virtio-blk-device,drive=hd0 \ 222 -drive file="$S_STAGE_DIR/out.img",if=none,format=raw,id=hd1 \ 223 -device virtio-blk-device,drive=hd1 \ 224 -append "init in/combined.scm" \ 225 > "$TRANSCRIPT" 2>&1 & 226 ;; 227 *) 228 echo "[runscm/seed] unsupported SEED_ARCH=$seed_arch" >&2 229 exit 2 230 ;; 231 esac 232 QPID=$! 233 ( sleep "$timeout"; kill -9 $QPID 2>/dev/null ) </dev/null >/dev/null 2>&1 & 234 WATCHER=$! 235 # `disown` removes the watcher from the shell's job table so that 236 # killing it on the happy path doesn't trigger bash's 237 # "Terminated: 15 PID ( sleep … )" job-status message — that 238 # message looks like a real failure but is just a noisy SIGTERM 239 # notification fired when qemu exited normally before the watcher's 240 # sleep elapsed. 241 disown $WATCHER 2>/dev/null || true 242 wait $QPID 2>/dev/null || true 243 kill $WATCHER 2>/dev/null || true 244 245 if ! "$EXTRACT" "$S_STAGE_DIR/out" "$S_STAGE_DIR/out.img" >/dev/null 2>&1; then 246 echo "[runscm/seed] FAIL: extract-blk failed (kernel didn't reach exit?)" >&2 247 tail -40 "$TRANSCRIPT" >&2 248 exit 3 249 fi 250 EXIT_LINE=$(grep -E "user exit_group" "$TRANSCRIPT" | tail -1 || true) 251 case "$EXIT_LINE" in 252 *"exit_group(0)"*) : ;; 253 *) echo "[runscm/seed] FAIL: driver did not exit 0: $EXIT_LINE" >&2 254 tail -40 "$TRANSCRIPT" >&2 255 exit 4 ;; 256 esac 257 }