boot2

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

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 }