boot2

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

lib-runscm.sh (9849B)


      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, PODMAN_PLATFORM_ARGS (may be empty)
     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     in_abs=$(cd "$S_STAGE_DIR/in"  && pwd)
    129     out_abs=$(cd "$S_STAGE_DIR/out" && pwd)
    130     echo "[runscm/podman] scheme1 combined.scm under $IMAGE" >&2
    131     podman run --rm -i --pull=never $PODMAN_PLATFORM_ARGS \
    132         -v "$in_abs:/work/in:ro" \
    133         -v "$out_abs:/work/out:rw" \
    134         -w /work "$IMAGE" \
    135         in/scheme1 in/combined.scm
    136 }
    137 
    138 # Seed: pack cpio with `init` at the root and the in/ subtree under it;
    139 # boot kernel with init=init combined.scm; recover outputs from the
    140 # SEEDFS dump on hd1; extract filters out/-prefixed entries directly
    141 # into $S_STAGE_DIR/out/.
    142 _runscm_run_seed() {
    143     timeout=$1
    144     : "${KERNEL_IMAGE:?lib-runscm: KERNEL_IMAGE not set}"
    145     : "${EXTRACT:?lib-runscm: EXTRACT not set}"
    146     mem=${QEMU_MEM:-2048M}
    147     cp "$S_STAGE_DIR/in/scheme1" "$S_STAGE_DIR/init"
    148     chmod +x "$S_STAGE_DIR/init"
    149     ( cd "$S_STAGE_DIR" && { echo init; find in -type f; } | sort -u | cpio -o -H newc 2>/dev/null ) \
    150         > "$S_STAGE_DIR/initramfs.cpio"
    151     sz=$(wc -c < "$S_STAGE_DIR/initramfs.cpio")
    152     pad=$(( (512 - sz % 512) % 512 ))
    153     if [ "$pad" -gt 0 ]; then
    154         head -c "$pad" /dev/zero >> "$S_STAGE_DIR/initramfs.cpio"
    155     fi
    156     mv "$S_STAGE_DIR/initramfs.cpio" "$S_STAGE_DIR/in.img"
    157     truncate -s 256M "$S_STAGE_DIR/out.img"
    158 
    159     TRANSCRIPT=$S_STAGE_DIR/transcript.txt
    160     echo "[runscm/seed] booting scheme1 + run.scm (timeout ${timeout}s)" >&2
    161     seed_arch=${SEED_ARCH:-aarch64}
    162     case "$seed_arch" in
    163         aarch64)
    164             qemu-system-aarch64 \
    165                 -machine virt,gic-version=3,accel=hvf -cpu host -m "$mem" \
    166                 -nographic -no-reboot \
    167                 -global virtio-mmio.force-legacy=false \
    168                 -kernel "$KERNEL_IMAGE" \
    169                 -drive file="$S_STAGE_DIR/in.img",if=none,format=raw,id=hd0,readonly=on \
    170                 -device virtio-blk-device,drive=hd0 \
    171                 -drive file="$S_STAGE_DIR/out.img",if=none,format=raw,id=hd1 \
    172                 -device virtio-blk-device,drive=hd1 \
    173                 -append "init in/combined.scm" \
    174                 > "$TRANSCRIPT" 2>&1 &
    175             ;;
    176         riscv64)
    177             # No hvf accel on Apple Silicon for riscv64 — TCG only.
    178             qemu-system-riscv64 \
    179                 -machine virt -m "$mem" \
    180                 -nographic -no-reboot \
    181                 -global virtio-mmio.force-legacy=false \
    182                 -kernel "$KERNEL_IMAGE" \
    183                 -drive file="$S_STAGE_DIR/in.img",if=none,format=raw,id=hd0,readonly=on \
    184                 -device virtio-blk-device,drive=hd0 \
    185                 -drive file="$S_STAGE_DIR/out.img",if=none,format=raw,id=hd1 \
    186                 -device virtio-blk-device,drive=hd1 \
    187                 -append "init in/combined.scm" \
    188                 > "$TRANSCRIPT" 2>&1 &
    189             ;;
    190         amd64)
    191             # microvm + isa-debug-exit mirrors seed-kernel/run.sh: the
    192             # kernel writes to port 0x501 on user exit_group(0) so QEMU
    193             # exits cleanly (no `-no-reboot` triple-fault gymnastics).
    194             qemu-system-x86_64 \
    195                 -machine microvm,acpi=off,pic=off,pit=off,rtc=off,isa-serial=on,auto-kernel-cmdline=off \
    196                 -cpu max -m "$mem" \
    197                 -nodefaults -display none -serial stdio -no-reboot \
    198                 -global virtio-mmio.force-legacy=false \
    199                 -device isa-debug-exit,iobase=0x501,iosize=2 \
    200                 -kernel "$KERNEL_IMAGE" \
    201                 -drive file="$S_STAGE_DIR/in.img",if=none,format=raw,id=hd0,readonly=on \
    202                 -device virtio-blk-device,drive=hd0 \
    203                 -drive file="$S_STAGE_DIR/out.img",if=none,format=raw,id=hd1 \
    204                 -device virtio-blk-device,drive=hd1 \
    205                 -append "init in/combined.scm" \
    206                 > "$TRANSCRIPT" 2>&1 &
    207             ;;
    208         *)
    209             echo "[runscm/seed] unsupported SEED_ARCH=$seed_arch" >&2
    210             exit 2
    211             ;;
    212     esac
    213     QPID=$!
    214     ( sleep "$timeout"; kill -9 $QPID 2>/dev/null ) </dev/null >/dev/null 2>&1 &
    215     WATCHER=$!
    216     # `disown` removes the watcher from the shell's job table so that
    217     # killing it on the happy path doesn't trigger bash's
    218     # "Terminated: 15  PID  ( sleep … )" job-status message — that
    219     # message looks like a real failure but is just a noisy SIGTERM
    220     # notification fired when qemu exited normally before the watcher's
    221     # sleep elapsed.
    222     disown $WATCHER 2>/dev/null || true
    223     wait $QPID 2>/dev/null || true
    224     kill $WATCHER 2>/dev/null || true
    225 
    226     if ! "$EXTRACT" "$S_STAGE_DIR/out" "$S_STAGE_DIR/out.img" >/dev/null 2>&1; then
    227         echo "[runscm/seed] FAIL: extract-blk failed (kernel didn't reach exit?)" >&2
    228         tail -40 "$TRANSCRIPT" >&2
    229         exit 3
    230     fi
    231     EXIT_LINE=$(grep -E "user exit_group" "$TRANSCRIPT" | tail -1 || true)
    232     case "$EXIT_LINE" in
    233       *"exit_group(0)"*) : ;;
    234       *) echo "[runscm/seed] FAIL: driver did not exit 0: $EXIT_LINE" >&2
    235          tail -40 "$TRANSCRIPT" >&2
    236          exit 4 ;;
    237     esac
    238 }