boot2

Playing with the boostrap
git clone https://git.ryansepassi.com/git/boot2.git
Log | Files | Refs | README

lib-runscm.sh (9873B)


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