#!/bin/sh # Build a bootable SchOS ISO. # # tools/posix/mkiso.sh [output.iso] # # The disc carries two El Torito boot entries and a real directory tree: # # /boot/schos.bin BIOS: cdboot + stage2 + kernel, loaded no-emulation # /boot/efiboot.img UEFI: a FAT image holding EFI/BOOT/BOOTX64.EFI # /EFI/BOOT/BOOTX64.EFI the same loader, visible as a file on the disc # /bin /etc /usr ... the staged sysroot, as an ordinary hierarchy # # --- BIOS entry ------------------------------------------------------------- # # El Torito's "no emulation" mode, which is the only one of the three that fits # how SchOS actually loads itself. # # floppy emulation caps the image at 2.88 MB; the ramdisk-root kernel # alone is bigger than that. # hard disk emulation presents the image through the BIOS's INT 13h, and # SchOS's loader does not use INT 13h -- it drives # the IDE controller's registers directly, which # cannot reach a BIOS-emulated drive. # no emulation the BIOS loads a chosen run of sectors at 0x7C00 # and gets out of the way. That is this. # # So the layout below is deliberate: -boot-load-size is 1 + STAGE2_SECTORS, # which makes the BIOS place both boot/cdboot and the whole of stage2 in memory # before anything of ours runs. That puts stage2 at 0x7E00 -- byte for byte the # arrangement bootasm.S produces on a hard disk. From there the only difference # is that stage2main.c reads the kernel back with ATAPI instead of ATA. # # STAGE2_SECTORS comes from the environment, set by the Makefile, because this # same number also lives in boot/bios/bootasm.S, boot/bios/stage2main.c and the dd offsets # in schos.img's recipe. It was previously written out by hand in three # separate spots in this file alone, and raising it for the boot logo is what # turned that from a latent hazard into a broken image. # # NOT using -boot-info-table on purpose: it patches 56 bytes over offset 8 of # the boot image, which is the middle of cdboot's code. stage2main.c finds # where it was loaded from by reading the El Torito boot catalog instead. # # --- UEFI entry ------------------------------------------------------------- # # UEFI firmware does not run El Torito no-emulation code. It wants a FAT # filesystem containing EFI/BOOT/BOOTX64.EFI, offered as a second boot entry. # So one is built with mtools and added with -eltorito-alt-boot. The same # loader is also copied into the ISO tree at /EFI/BOOT/BOOTX64.EFI, because # some firmware looks there directly when booting from a USB stick. # # --- Booting from a USB stick ----------------------------------------------- # # -isohybrid-gpt-basdat adds a GPT that marks the FAT image as a basic data # partition, so writing this .iso to a stick with dd produces something UEFI # will boot. # # That covers UEFI only, and deliberately. The classic BIOS isohybrid trick # needs syslinux's isohdpfx.bin, and even with it the BIOS path here would not # work: on a stick the firmware presents the medium as a hard disk, while # stage2main.c reads the kernel back over ATAPI. Booting BIOS off USB is what # build/usbstick.img is for. # # --- Rock Ridge and Joliet -------------------------------------------------- # # Plain ISO 9660 gives 8.3 uppercase names with no permissions, so the sysroot # would arrive as /BIN/TOYBOX.;1 with every file marked the same. Rock Ridge # restores POSIX names, permissions and symlinks for Unix readers; Joliet does # long names for Windows. Both are additions to the same image -- a reader that # understands neither still sees a valid, if ugly, ISO 9660 disc. # # -cache-inodes matters more than it looks: the sysroot is a toybox multicall # binary with a couple of hundred hard links to it. Without inode caching each # link becomes its own copy and the disc grows by two orders of magnitude. set -e BUILD=${BUILD:-build} OUT=${1:-schos.iso} STAGE2_SECTORS=${STAGE2_SECTORS:-48} STAGE2_BYTES=$((STAGE2_SECTORS * 512)) LOAD_SIZE=$((1 + STAGE2_SECTORS)) STAGING="$BUILD/iso" SYSROOT=${SYSROOT:-sysroot} CDBOOT="$BUILD/boot/cdboot" STAGE2="$BUILD/boot/stage2" KERNEL="$BUILD/kernel/kernel" EFIAPP="$BUILD/uefi/BOOTX64.EFI" for f in "$CDBOOT" "$STAGE2" "$KERNEL"; do [ -f "$f" ] || { echo "mkiso: missing $f -- run make first" >&2; exit 1; } done [ -f "$EFIAPP" ] || { echo "mkiso: missing $EFIAPP -- run 'make uefi' first" >&2; exit 1; } # The three pieces must be contiguous and at these exact sizes: stage2main.c's # readseg() finds the kernel at sector (1 + STAGE2_SECTORS) of the boot image, # so a short stage2 would silently shift the kernel out from under it. cdboot_sz=$(wc -c < "$CDBOOT") stage2_sz=$(wc -c < "$STAGE2") [ "$cdboot_sz" -eq 512 ] || { echo "mkiso: cdboot is $cdboot_sz bytes, expected 512" >&2; exit 1; } [ "$stage2_sz" -eq "$STAGE2_BYTES" ] || { echo "mkiso: stage2 is $stage2_sz bytes, expected $STAGE2_BYTES (STAGE2_SECTORS=$STAGE2_SECTORS)" >&2; exit 1; } rm -rf "$STAGING" mkdir -p "$STAGING/boot" "$STAGING/EFI/BOOT" cat "$CDBOOT" "$STAGE2" "$KERNEL" > "$STAGING/boot/schos.bin" cp "$EFIAPP" "$STAGING/EFI/BOOT/BOOTX64.EFI" # The FAT image for the UEFI El Torito entry. # # It carries the kernel as well as the loader. BOOTX64.EFI opens \schos\kernel # on the volume it was itself loaded from, and for a UEFI boot that volume is # this FAT image -- not the ISO 9660 tree around it. Without the kernel in here # the loader comes up and halts with EFI_NOT_FOUND, which is what it did. # # That means the kernel is stored twice on the disc: once inside boot/schos.bin # for the BIOS entry, once here for the UEFI one. The alternative is teaching # the loader to go looking for other volumes, which is a real feature and not a # packaging detail -- worth doing deliberately rather than to save a copy. # # Sized from what actually goes in plus slack, rather than fixed: a hardcoded # size is a thing that breaks quietly the first time the kernel grows. EFI_IMG="$STAGING/boot/efiboot.img" payload_kb=$(( ( $(wc -c < "$EFIAPP") + $(wc -c < "$KERNEL") + 1023 ) / 1024 )) img_kb=$(( ( (payload_kb + 512 + 31) / 32 ) * 32 )) [ "$img_kb" -lt 512 ] && img_kb=512 if command -v mformat >/dev/null 2>&1 && command -v mcopy >/dev/null 2>&1; then rm -f "$EFI_IMG" # Create the file first and format it with an explicit geometry. # # Not `mformat -f`: that takes one of the standard floppy capacities # (360, 720, 1440, 2880 KB) and rejects anything else -- "Bad size 512". # Giving heads, sectors-per-track and a total sector count instead lets the # image be whatever the loader actually needs, which is the point of sizing # it from the file rather than hardcoding 1.44 MB. dd if=/dev/zero of="$EFI_IMG" bs=1024 count="$img_kb" status=none mformat -i "$EFI_IMG" -h 64 -s 32 -T $((img_kb * 2)) :: mmd -i "$EFI_IMG" ::/EFI ::/EFI/BOOT ::/schos mcopy -i "$EFI_IMG" "$EFIAPP" ::/EFI/BOOT/BOOTX64.EFI mcopy -i "$EFI_IMG" "$KERNEL" ::/schos/kernel HAVE_EFI=1 else echo "mkiso: warning: mtools not found, building a BIOS-only disc" >&2 echo " Fedora: sudo dnf install mtools" >&2 rm -f "$EFI_IMG" HAVE_EFI=0 fi # The sysroot, as an ordinary hierarchy. Copied rather than linked so that # whatever ends up on the disc is a snapshot, not a view of a tree that may be # rebuilt underneath it. if [ -d "$SYSROOT" ]; then for d in "$SYSROOT"/*; do [ -e "$d" ] || continue cp -a "$d" "$STAGING/" done fi # xorriso if it is here, mkisofs otherwise. Both accept the El Torito flags # below unchanged -- xorriso's -as mkisofs mode exists precisely to be # argument-compatible with it. The fallback is not academic: there is no xorriso # package for Windows in scoop, where this is developed, but cdrtools provides # mkisofs.exe. if command -v xorriso >/dev/null 2>&1; then MKISOFS="xorriso -as mkisofs" HYBRID="-isohybrid-gpt-basdat" # mkisofs proper does not know this one elif command -v mkisofs >/dev/null 2>&1; then MKISOFS="mkisofs" HYBRID="" else echo "mkiso: need xorriso or mkisofs on PATH" >&2 echo " Windows: scoop install cdrtools" >&2 echo " Debian/Ubuntu: apt install xorriso" >&2 exit 1 fi if [ "$HAVE_EFI" = 1 ]; then EFI_ARGS="-eltorito-alt-boot -e boot/efiboot.img -no-emul-boot $HYBRID" else EFI_ARGS="" fi # shellcheck disable=SC2086 # EFI_ARGS is a deliberate word list $MKISOFS \ -quiet \ -V SCHOS \ -A "SchOS" \ -R -J -joliet-long \ -cache-inodes \ -b boot/schos.bin \ -no-emul-boot \ -boot-load-size "$LOAD_SIZE" \ $EFI_ARGS \ -o "$OUT" \ "$STAGING" echo "==> $OUT ($(wc -c < "$OUT") bytes)" if [ "$HAVE_EFI" = 1 ]; then echo " BIOS: qemu-system-x86_64 -cdrom $OUT -boot d" echo " UEFI: qemu-system-x86_64 -bios OVMF_CODE.fd -cdrom $OUT" echo " USB: dd if=$OUT of=/dev/sdX bs=4M (UEFI only -- see the header)" else echo " qemu-system-x86_64 -cdrom $OUT -boot d" fi