Reported from a real laptop booting through Ventoy: the F8 menu appears,
selecting Boot SchOS gives a black screen and a white dash in the corner.
Both halves of that are bugs.
The disc could not be chainloaded. Booting it directly, firmware runs the
El Torito FAT image and the loader opens \schos\kernel on that volume,
which is where mkiso.sh put it. Ventoy does not use El Torito at all: it
mounts the ISO 9660 tree and runs \EFI\BOOT\BOOTX64.EFI from there. The
loader then looks for the kernel on the volume it was itself loaded from
-- the ISO tree -- and the kernel was only ever inside the FAT image. It
is now in both. The comment in mkiso.sh had even described this failure,
as the reason the kernel is in the FAT image, without noticing the ISO
tree had the same requirement.
The error was invisible. print() redirects to the serial port once the
splash is up, which is right for a development machine and useless on a
laptop: the loader died with a perfectly good explanation and sent it to
a wire nobody was holding. What is left on screen is the firmware's own
cursor -- the white dash. die() now draws the message on the framebuffer
too, over a cleared band so it does not land on the logo.
Verified by reproducing the exact failure: the ISO tree extracted and
booted as a plain volume, as a chainloader presents it. With the kernel
removed it dies visibly with "no kernel at \schos\kernel or \kernel, EFI
status 0x000E" -- EFI_NOT_FOUND, which is what the laptop was hitting
silently. With it present, that same volume boots through to a shell.
110 [ok] on the ESP path, the ISO check passes, no failures.
./mkiso.sh from tools/posix/ failed with "missing build/kernel/kernel --
run make first" on a tree that was built perfectly well: the paths were
relative to the caller's directory rather than the repo. mkiso.sh and
schcom_test.sh now resolve the root from their own location, the way
memory-sync.sh already did. An output path given to mkiso.sh is made
absolute first, so `mkiso.sh out.iso` still writes where the caller is
standing and not where the repo is.
mutate.sh had it worse: it began with `cd /mnt/d/DEV/SchOS`, an absolute
WSL path from the Windows machine it was written on, so it could not run
against any other checkout at all.
Fixing that exposed why it mattered. Every one of its nine mutations
reported "rejected at compile time" -- and none of them had reached a
compiler. The host tools moved to build/tools/ during the bt/ -> tools/
migration and this script still asked for build/mkschfs, so make answered
"No rule to make target" and the harness read that as the mutation being
caught by the build. A mutation test whose every case is silently skipped
is precisely the thing its own header warns about: a test that cannot
fail, believed anyway.
With the paths corrected all nine are caught by the suites, which is what
this was always supposed to demonstrate:
file_read ignores the end-of-file clamp -> extracted file differs
dirent_walk advances past the next entry -> entries lost
dir_lookup returns the wrong objectid -> fsck: no such file
extents point one block past their data -> mkschfs write fails
an overwrite drops the tail of the split -> FAIL: CoW keeps the run
truncate leaves stale bytes in the tail block -> FAIL: reads as zeros
committed frees never return to the free map -> FAIL: create hog -> -3
hard links do not bump nlink -> FAIL: nlink=1, expected 2
data checksum covers only the first 16 bytes -> FAIL: tree walk
The build failure is now printed rather than swallowed, so the next time
a target is renamed it says so instead of reporting nine passes. Two
mutations also reported a bare "caught:" with no reason -- the suite had
failed but nothing matched the quote patterns -- so the fallback is now
the last thing the suite actually said.
schfs-check and schfs-test still pass, and the tree restores clean.
1449 lines of boot/bios/, tools/sign.pl and tools/padstage2.pl, plus the
build rules, the disk image and the ISO entry that existed to carry them.
Everything since roughly 2012 boots UEFI, and CSM -- the firmware-side
BIOS emulation that lets a legacy loader run on such a machine at all --
has been going away since Intel dropped support for it in 2020. So this
is not a path being retired early; it is one whose remaining hardware is
already the exception.
The cost was never the code, it was the tax on everything near it. Stage
2 had a 65-sector ceiling it overflowed silently -- 25440 bytes against
24576 -- and because the kernel is written at sector 1 + STAGE2_SECTORS,
an overflowing loader was overwritten by the very kernel it was loading.
The resolution list could not be shared with UEFI either: VBE mode
setting is INT 10h, so matching it needed unreal mode, which is the
riskiest code in the tree for a feature that already worked elsewhere.
What replaces schos.img is the staged ESP, handed to QEMU as a virtual
FAT volume. That is a development arrangement and not an installable
disk; a real GPT image with a real FAT partition is the installer's job,
and until it exists nothing here writes a disk a machine boots unaided.
Secure Boot is untouched too -- the loader is unsigned.
The ISO keeps one El Torito entry, the FAT image holding loader and
kernel. It never booted from a USB stick via BIOS anyway: the firmware
presents the stick as a hard disk while stage2main.c read the kernel back
over ATAPI.
The 32-bit multiboot entry in entry.S now has no caller and is left in
place. Deleting kernel entry code is its own change with its own way of
failing silently, and it should not ride along with this one.
Ported with it: run.ps1, the six screenshot tools, and schcom_test.sh,
each with its own OVMF varstore copy so no run inherits another's UEFI
variables.
Two things this shook out. OVMF now belongs in `all`: nothing boots
without firmware, `make clean` wipes build/ including the copy fetched
from the host, and a clean followed by a build left every run script
throwing. And it cannot be listed as a prerequisite at the top of the
file -- make expands prerequisites when it reads the line, and
$(OVMF_CODE) is defined further down, so naming it there appends nothing
and says nothing.
Verified from a clean tree: 110 [ok] and no failures on the full suite,
the ISO boots with no hard disk attached, schfs-check passes, schcom's
end-to-end pixel test passes, and fbshot captures a real 1280x800 frame.
The disc carried one file -- boot/schos.bin -- with 8.3 uppercase names,
one El Torito entry, and no way to boot a UEFI machine. Now:
/boot/schos.bin BIOS entry, unchanged: no-emulation, 1+STAGE2_SECTORS
/boot/efiboot.img UEFI entry: a FAT image built with mtools
/EFI/BOOT/BOOTX64.EFI the loader, also visible as a file
/bin /etc /usr ... the staged sysroot as an ordinary tree
Rock Ridge and Joliet restore real names, permissions and symlinks;
-cache-inodes matters more than it looks, because the sysroot is a toybox
multicall binary with a couple of hundred hard links to it and without
inode caching each link becomes its own copy.
-isohybrid-gpt-basdat adds a GPT so the .iso can be dd'd to a stick and
booted by UEFI. That is UEFI only and deliberately so: the BIOS isohybrid
trick needs syslinux's isohdpfx.bin, and even with it this loader reads
the kernel back over ATAPI, which a stick presented as a hard disk cannot
serve. usbstick.img is what that is for.
The FAT image carries the kernel as well as the loader, because
BOOTX64.EFI opens \schos\kernel on the volume it was loaded from, and for
a UEFI boot that is the FAT image rather than the ISO 9660 tree. Without
it the loader halted with EFI_NOT_FOUND.
Also fixes `make iso` and `make ramroot`, which were already broken:
fs-ram.img was still built by the v1 mkfs while the kernel accepts only
SchFS as root, so the ramdisk was a format it could not mount and it
panicked with "schfs root: no SchFS volume on the root device". It is a
mkschfs image now, which needed --exclude in mkschfs -- dropping the
exclude list was not an option, because the ramdisk is linked into the
kernel binary and what goes in it is a licensing question. RAMDISK_NINODES
is gone with the v1 image: SchFS has no preallocated inode table.
Verified: BIOS ISO boots to a shell and runs commands over serial
(241 files in /bin, /etc/motd reads back). UEFI ISO boots to a prompt.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
boot/bios/ now holds the MBR, the El Torito stub and the real-mode stage
2; boot/uefi/ is empty and documented.
Separate directories rather than #ifdefs, because the two share almost
nothing. A BIOS loader lives in 16-bit real mode, reaches the firmware
through software interrupts, and walks itself up into long mode. A UEFI
loader is a 64-bit PE that the firmware has already placed in long mode
with paging on, calling services through a table of function pointers.
What they do share is the hand-off contract, which boot/README.md now
writes down: ELF segments placed, long mode, a 24-byte-entry memory map
at E820_MAP_ADDR, framebuffer geometry at VBE_INFO_ADDR.
That contract is the point of documenting it now. UEFI's GetMemoryMap
returns EFI_MEMORY_DESCRIPTORs, and the right move is to translate them
into the entries kernel/arch/x86_64/e820.c already parses rather than
teach the kernel a second format.
Build output paths under $(BUILD)/boot are unchanged; only sources moved.
tools/windows/ .ps1 (pwsh, so usable from Linux and macOS too)
tools/posix/ .sh
tools/python/ .py
tools/ .c and .pl
The .c and .pl files stay at the top level because they are not
host-specific: the build compiles or invokes them on every platform.
Call sites updated in the Makefile, ports/mk/port.mk, docs and the
project memory. The one thing that does not survive a move on its own is
a script deriving the repo root from its own location -- all nine
PowerShell tools used Split-Path $PSScriptRoot -Parent, and memory-sync.sh
used dirname/.., both of which quietly become tools/ one level deeper.
Two levels now.
Two pre-existing Windows failures surfaced while verifying, both real and
neither caused by the move:
- schfs_fsck.c did not compile under mingw at all: --extract uses
POSIX mkdir(path,mode), symlink() and link(), none of which mingw
has. That took the whole tool with it, and `make schfs-check` with
it. mkdir maps to _mkdir; symlink and link fail loudly rather than
writing a regular file in their place, because an extracted tree
that differs from the image while reporting success defeats the
point of --extract.
- the round-trip test then failed at `ln -s`, since Git Bash cannot
create symlinks without developer mode. It skips with a message now.
Not a fudge: mkschfs's mingw build cannot see a symlink either, so
the test would have compared a tree with none against an image with
none and called it a pass -- coverage reporting itself as present
while being absent.
Verified on Windows: build clean, all seven bootcheck suites green,
make schfs-check passes.