Build and tools work on Linux, macOS and BSD as well as Windows

Developed from both Windows and Linux, so nothing in the build should
care which host it runs on. It did, in four places.

Toolchain: one path instead of two. clang is a cross-compiler by
construction, so a single LLVM install targets bare-metal ELF, 16/32-bit
code below 1 MB, and Linux/musl userland from any host -- which is how
the Windows build already worked. The GNU branch beside it had drifted
into passing --no-warn-rwx-segments, a hard error on lld, which is the
default linker on both FreeBSD and macOS; and it required a brewed
x86_64-elf-gcc before anything built on a Mac at all. GNU is kept as a
fallback (USE_GNU=1) but clang+lld is chosen whenever both are present.
Host tools use the host's own cc; only mingw needs naming explicitly.

tools/mkschfs.c included <sys/sysmacros.h> unconditionally on non-mingw
hosts. That header is glibc/musl only -- macOS and the BSDs have neither
the header nor a need for it, since major()/minor() come from
<sys/types.h> there. Now guarded on __linux__.

Ports verified tarballs with `sha256sum -c -`, which does not exist on
macOS (shasum), FreeBSD/NetBSD (sha256), or OpenBSD (cksum -a sha256),
and each spells its check mode differently. tools/sha256check.sh picks
whichever of the five the host has and compares digests itself.

nproc is coreutils, so absent on macOS and BSD; falls through to sysctl
-n hw.ncpu before defaulting to 4.

The PowerShell tools now run under pwsh on any OS: QEMU is found on PATH
(the scoop location is a Windows-only fallback), and path literals use
forward slashes -- Join-Path $root 'build\x' produces a *filename
containing a backslash* on a Unix host. The backslash sweep is limited
to known directory prefixes on purpose: bootcheck sends
printf 'caf\303\251' to the guest to test UTF-8, and a blanket
substitution would have quietly changed what that asserts.

Also fixes six tools left booting build/fs.img, which stopped being
bootable when SchFS became the root filesystem: crashshot, dispshot,
fbshot, splashshot, textshot and ttyshot all take build/schfs.img now,
and the -SchFS switches that used to select it are gone.

Verified on Windows: full build, and bootcheck green. The Linux, macOS
and BSD paths are written from their documented behaviour and are NOT
executed here -- this machine has no WSL and no second host.
This commit is contained in:
Nexoniarz 2026-08-09 14:24:53 +02:00
commit cda5908660
15 changed files with 427 additions and 105 deletions

140
Makefile
View file

@ -8,7 +8,10 @@ SYSROOT = sysroot
# there is no reason to compile one file at a time. Override with `make -j1` # there is no reason to compile one file at a time. Override with `make -j1`
# when you want readable output from a failing build. # when you want readable output from a failing build.
# ----------------------------------------------------------------------- # -----------------------------------------------------------------------
NPROC := $(shell nproc 2>/dev/null || echo 4) # nproc is coreutils, so it exists on Linux and under Git Bash but not on
# macOS or the BSDs, where the same number comes from sysctl. Falling through
# to 4 is a working build either way, just a slower one.
NPROC := $(shell nproc 2>/dev/null || sysctl -n hw.ncpu 2>/dev/null || echo 4)
MAKEFLAGS += -j$(NPROC) MAKEFLAGS += -j$(NPROC)
# Kernel object files — organized by subsystem # Kernel object files — organized by subsystem
@ -92,31 +95,29 @@ CMDBINS = $(addprefix $(BUILD)/cmd/, $(CMDS))
UNAME_S := $(shell uname -s) UNAME_S := $(shell uname -s)
UNAME_P := $(shell uname -p) UNAME_P := $(shell uname -p)
# Windows, under Git Bash or MSYS2 — WINHOST. # Host platform.
#
# WINHOST Windows, under Git Bash or MSYS2
# MACHOST macOS
# BSDHOST FreeBSD / OpenBSD / NetBSD / DragonFly
# (else) Linux
#
# All four are supported hosts: this is developed from both Windows and Linux,
# and nothing here should care which.
# #
# Building natively on Windows rather than in WSL, because WSL2 is a real Linux # Building natively on Windows rather than in WSL, because WSL2 is a real Linux
# kernel in a Hyper-V VM and so hard-requires virtualisation; turning that on # kernel in a Hyper-V VM and so hard-requires virtualisation; turning that on
# puts the Hyper-V hypervisor underneath the whole machine and demotes VMware # puts the Hyper-V hypervisor underneath the whole machine and demotes VMware
# to running nested on top of it, which is a real and permanent cost to pay for # to running nested on top of it, which is a real and permanent cost to pay for
# a build environment. # a build environment.
#
# The usual answer is a cross binutils+gcc, which is a day of work that then
# rots. clang does not need any of it: it is a cross-compiler by construction,
# so one ordinary LLVM install targets both the bare-metal kernel and Linux
# userland, and llvm-objcopy/objdump stand in for binutils. Everything else the
# build shells out to — perl, sed, dd, sha256sum — comes with Git Bash.
ifneq (,$(filter MINGW% MSYS% CYGWIN%,$(UNAME_S))) ifneq (,$(filter MINGW% MSYS% CYGWIN%,$(UNAME_S)))
WINHOST := 1 WINHOST := 1
endif endif
ifeq ($(UNAME_S),Darwin)
ifeq ($(UNAME_S),Linux) MACHOST := 1
ifeq ($(UNAME_P),aarch64) endif
TOOLPREFIX = x86_64-linux-gnu- ifneq (,$(filter FreeBSD OpenBSD NetBSD DragonFly,$(UNAME_S)))
else BSDHOST := 1
TOOLPREFIX =
endif
else ifeq ($(UNAME_S),Darwin)
TOOLPREFIX = x86_64-elf-
endif endif
QEMU = qemu-system-x86_64 QEMU = qemu-system-x86_64
@ -133,10 +134,40 @@ QEMU = qemu-system-x86_64
# virtio-gpu wins the display class, stdvga carries the splash and text console. # virtio-gpu wins the display class, stdvga carries the splash and text console.
QEMU_DISPLAY = -vga std -device virtio-gpu-pci QEMU_DISPLAY = -vga std -device virtio-gpu-pci
ifdef WINHOST # -----------------------------------------------------------------------
# An explicit --target is what makes clang emit ELF instead of the COFF it # Cross-compilation: clang everywhere it can be had
# would default to on this host. Without it every object is silently the #
# wrong format and the first thing to complain is the linker. # The kernel, the bootloader and the ports all need a compiler targeting
# something other than the host -- bare-metal ELF, 16/32-bit code below one
# megabyte, and Linux/musl userland respectively. The traditional answer is a
# cross binutils+gcc per host, which is a day of work per platform that then
# rots, and on macOS means brewing x86_64-elf-* before anything builds at all.
#
# clang needs none of it: it is a cross-compiler by construction, so one
# ordinary LLVM install produces every target from every host. That was already
# how the Windows build worked; using it on all four hosts collapses what used
# to be two divergent toolchain branches -- one of which had drifted into
# passing GNU-ld-only flags to what is nowadays lld on FreeBSD and macOS -- into
# a single path that gets exercised on every platform instead of one.
#
# GNU is kept as a fallback for hosts without clang, but it is the exception.
# -----------------------------------------------------------------------
HAVE_CLANG := $(shell command -v clang 2>/dev/null)
HAVE_LLD := $(shell command -v ld.lld 2>/dev/null)
ifeq ($(origin USE_GNU), undefined)
ifneq (,$(HAVE_CLANG))
ifneq (,$(HAVE_LLD))
USE_CLANG := 1
endif
endif
endif
ifdef USE_CLANG
# An explicit --target is what makes clang emit ELF rather than whatever the
# host defaults to -- COFF on Windows, Mach-O on macOS. Without it every
# object is silently the wrong format and the first thing to complain is the
# linker.
CC = clang --target=x86_64-elf CC = clang --target=x86_64-elf
CC32 = clang --target=i386-elf CC32 = clang --target=i386-elf
LD = ld.lld LD = ld.lld
@ -151,49 +182,66 @@ ifdef WINHOST
# and refuses, because for a hosted program that would be a bug. Saying the # and refuses, because for a hosted program that would be a bug. Saying the
# base is zero is how you tell it this is not a hosted program. # base is zero is how you tell it this is not a hosted program.
LD_LOWBASE = --image-base=0 LD_LOWBASE = --image-base=0
# Host tools (mkfs, mkschfs, the SchFS suites) are ordinary Windows programs. # ports/ build *Linux userland* against musl -- a third target, distinct from
# They must be built with mingw-w64 rather than clang's default MSVC target: # both the kernel (bare-metal ELF) and the host tools (native).
# tools/mkfs.c includes <unistd.h> and <dirent.h>, and the Windows SDK has
# neither.
HOSTCC = gcc
# mingw-w64's gcc warns about things glibc's does not (notably %lld/%zu in
# printf against msvcrt). Those warnings are about the host build only and
# say nothing about the image, so they must not stop it.
HOSTWARN = -Wall
# MinGW gcc appends .exe when the -o name has no extension, so a rule
# whose target lacks it is never considered built -- mkfs was recompiled on
# every single invocation of make, forever, because of this.
EXE = .exe
# `python3` is not on PATH from the python.org installer; `python` is.
PYTHON = python
# Compiler for ports/, which build *Linux userland* against musl -- a third
# target, distinct from both the kernel (bare-metal ELF) and the host tools
# (Windows PE). mingw's gcc cannot do it: it targets Windows, so linking it
# against musl gives undefined `stderr` and `__errno_location`.
# #
# -nostdlib with the crt files named explicitly, because clang otherwise # -nostdlib with the crt files named explicitly, because clang otherwise
# reaches for gcc's crtbeginT.o and libgcc, which do not exist on this host. # reaches for gcc's crtbeginT.o and libgcc, which need not exist on any
# particular host.
MUSLPREFIX = $(abspath $(BUILD))/ports/musl/prefix MUSLPREFIX = $(abspath $(BUILD))/ports/musl/prefix
PORTCC = clang --target=x86_64-unknown-linux-musl -fuse-ld=lld PORTCC = clang --target=x86_64-unknown-linux-musl -fuse-ld=lld
PORTCRT = -nostdlib $(MUSLPREFIX)/lib/crt1.o $(MUSLPREFIX)/lib/crti.o PORTCRT = -nostdlib $(MUSLPREFIX)/lib/crt1.o $(MUSLPREFIX)/lib/crti.o
PORTEND = $(MUSLPREFIX)/lib/crtn.o PORTEND = $(MUSLPREFIX)/lib/crtn.o
else else
# GNU fallback. Needs a cross toolchain anywhere the host's own gcc does not
# already emit x86_64 ELF -- notably macOS, where TOOLPREFIX has to name a
# brewed x86_64-elf-gcc.
ifdef MACHOST
TOOLPREFIX ?= x86_64-elf-
else ifeq ($(UNAME_P),aarch64)
TOOLPREFIX ?= x86_64-linux-gnu-
else
TOOLPREFIX ?=
endif
CC = $(TOOLPREFIX)gcc CC = $(TOOLPREFIX)gcc
CC32 = $(CC) -m32 CC32 = $(CC) -m32
LD = $(TOOLPREFIX)ld LD = $(TOOLPREFIX)ld
OBJCOPY = $(TOOLPREFIX)objcopy OBJCOPY = $(TOOLPREFIX)objcopy
OBJDUMP = $(TOOLPREFIX)objdump OBJDUMP = $(TOOLPREFIX)objdump
LDFLAGS = -m elf_x86_64 -nostdlib --no-warn-rwx-segments LDFLAGS = -m elf_x86_64 -nostdlib --no-warn-rwx-segments
HOSTCC = gcc
HOSTWARN = -Werror -Wall
EXE =
PYTHON = python3
LD_LOWBASE = LD_LOWBASE =
PORTCC = gcc PORTCC = $(TOOLPREFIX)gcc
PORTCRT = PORTCRT =
PORTEND = PORTEND =
endif endif
# -----------------------------------------------------------------------
# Host tools: mkfs, mkschfs, the SchFS suites, qrtest. Ordinary programs for
# the machine doing the building, so they use its native compiler.
# -----------------------------------------------------------------------
HOSTCC ?= cc
ifdef WINHOST
# mingw-w64's gcc, not clang: clang defaults to an MSVC target here, and
# tools/mkfs.c includes <unistd.h> and <dirent.h>, which the Windows SDK
# does not have.
HOSTCC = gcc
# mingw-w64's gcc warns about things glibc's does not (notably %lld/%zu in
# printf against msvcrt). Those warnings are about the host build only and
# say nothing about the image, so they must not stop it.
HOSTWARN = -Wall
# MinGW gcc appends .exe when the -o name has no extension, so a rule whose
# target lacks it is never considered built -- mkfs was recompiled on every
# single invocation of make, forever, because of this.
EXE = .exe
# `python3` is not on PATH from the python.org installer; `python` is.
PYTHON = python
else
HOSTWARN = -Werror -Wall
EXE =
PYTHON = python3
endif
# Host tools are generated output and live under build/, per docs/layout.md. # Host tools are generated output and live under build/, per docs/layout.md.
# $(EXE) matters: MinGW appends it, and a rule whose target lacks it never # $(EXE) matters: MinGW appends it, and a rule whose target lacks it never
# looks satisfied. # looks satisfied.

View file

@ -23,6 +23,11 @@ SYSROOT := $(TOPDIR)/sysroot
DISTDIR := $(TOPDIR)/dist DISTDIR := $(TOPDIR)/dist
BUILDROOT := $(TOPDIR)/build/ports BUILDROOT := $(TOPDIR)/build/ports
# Verifying a downloaded tarball. Not `sha256sum -c -`: that command does not
# exist on macOS or the BSDs, and each of them spells it differently. The
# helper picks whichever of the five possibilities the host actually has.
SHA256CHECK := $(TOPDIR)/tools/sha256check.sh
# Port name is the directory name — no per-port boilerplate needed. # Port name is the directory name — no per-port boilerplate needed.
PORT := $(notdir $(CURDIR)) PORT := $(notdir $(CURDIR))
PORTDIR := $(BUILDROOT)/$(PORT) PORTDIR := $(BUILDROOT)/$(PORT)

View file

@ -14,7 +14,7 @@ TARBALL := $(DISTDIR)/musl-$(MUSL_VERSION).tar.gz
$(TARBALL): | $(DISTDIR) $(TARBALL): | $(DISTDIR)
curl -fsSL -o $@ $(MUSL_URL) curl -fsSL -o $@ $(MUSL_URL)
echo "$(MUSL_SHA256) $@" | sha256sum -c - sh $(SHA256CHECK) "$(MUSL_SHA256)" "$@"
download: $(TARBALL) download: $(TARBALL)

View file

@ -26,7 +26,7 @@ LDFLAGS := -static -L$(MUSL)/lib -lc
$(TARBALL): | $(DISTDIR) $(TARBALL): | $(DISTDIR)
curl -fsSL -o $@ $(TOYBOX_URL) curl -fsSL -o $@ $(TOYBOX_URL)
echo "$(TOYBOX_SHA256) $@" | sha256sum -c - sh $(SHA256CHECK) "$(TOYBOX_SHA256)" "$@"
download: $(TARBALL) download: $(TARBALL)

View file

@ -17,8 +17,8 @@
# the pacing below -- not superstition, just the difference between a reliable # the pacing below -- not superstition, just the difference between a reliable
# check and a flaky one. # check and a flaky one.
# #
# tools\bootcheck.ps1 # tools/bootcheck.ps1
# tools\bootcheck.ps1 -Tty -Ps # tools/bootcheck.ps1 -Tty -Ps
param( param(
[switch]$Fb, [switch]$Fb,
@ -49,16 +49,39 @@ param(
# else in this suite touches. # else in this suite touches.
[switch]$Ps, [switch]$Ps,
[int]$BootSeconds = 20, [int]$BootSeconds = 20,
[string]$Qemu = "$env:USERPROFILE\scoop\apps\qemu\current\qemu-system-x86_64.exe" [string]$Qemu = (Find-Qemu)
) )
# QEMU, on whichever host this is.
#
# PowerShell 7 runs on Linux, macOS and the BSDs as well as Windows, so these
# scripts are usable from all of them -- but the old default was a hardcoded
# scoop path under $env:USERPROFILE, which exists on exactly one of those. PATH
# first, because a package-managed qemu is on it everywhere; the scoop location
# is only a fallback for the Windows install that does not add itself to PATH.
function Find-Qemu {
$exe = if ($IsWindows -or $env:OS -eq 'Windows_NT') { 'qemu-system-x86_64.exe' }
else { 'qemu-system-x86_64' }
$onPath = Get-Command $exe -ErrorAction SilentlyContinue
if ($onPath) { return $onPath.Source }
if ($env:USERPROFILE) {
$scoop = Join-Path (Join-Path $env:USERPROFILE 'scoop/apps/qemu/current') $exe
if (Test-Path $scoop) { return $scoop }
}
foreach ($p in @('/usr/bin', '/usr/local/bin', '/opt/homebrew/bin')) {
$c = Join-Path $p $exe
if (Test-Path $c) { return $c }
}
throw "qemu-system-x86_64 not found on PATH. Install QEMU, or pass -Qemu <path>."
}
$ErrorActionPreference = 'Stop' $ErrorActionPreference = 'Stop'
$root = Split-Path $PSScriptRoot -Parent $root = Split-Path $PSScriptRoot -Parent
$hda = Join-Path $root 'schos.img' $hda = Join-Path $root 'schos.img'
# The root filesystem. SchFS is the only root there is, so this is not a # The root filesystem. SchFS is the only root there is, so this is not a
# choice -- a kernel handed anything else on device 1 panics in forkret(). # choice -- a kernel handed anything else on device 1 panics in forkret().
$hdb = Join-Path $root 'build\schfs.img' $hdb = Join-Path $root 'build/schfs.img'
$log = Join-Path $root 'build\bootcheck.log' $log = Join-Path $root 'build/bootcheck.log'
Remove-Item -Force $log -ErrorAction SilentlyContinue Remove-Item -Force $log -ErrorAction SilentlyContinue
@ -196,7 +219,7 @@ $usbDevices = if ($Usb) {
# there, which failed three USB checks that had nothing wrong with USB. # there, which failed three USB checks that had nothing wrong with USB.
# It also stops the same file being attached twice, once as the root # It also stops the same file being attached twice, once as the root
# disk and once as a USB device. # disk and once as a USB device.
"-drive id=usbstick,file=`"$(Join-Path $root 'build\fs.img')`",format=raw,if=none" "-drive id=usbstick,file=`"$(Join-Path $root 'build/fs.img')`",format=raw,if=none"
'-device usb-storage,bus=xhci.0,drive=usbstick') '-device usb-storage,bus=xhci.0,drive=usbstick')
} else { @() } } else { @() }

View file

@ -9,23 +9,48 @@
# shell. screendump writes a PPM; the PNG conversion is optional and skipped if # shell. screendump writes a PPM; the PNG conversion is optional and skipped if
# ImageMagick is not around. # ImageMagick is not around.
# #
# tools\crashshot.ps1 crash the ramdisk-root build # tools/crashshot.ps1 crash the ramdisk-root build
# tools\crashshot.ps1 -SchFS with a SchFS volume attached
param( param(
[switch]$SchFS,
[int]$BootSeconds = 20, [int]$BootSeconds = 20,
[string]$Out = "build\crashscreen.ppm", [string]$Out = "build/crashscreen.ppm",
[string]$Qemu = "$env:USERPROFILE\scoop\apps\qemu\current\qemu-system-x86_64.exe" [string]$Qemu = (Find-Qemu)
) )
# QEMU, on whichever host this is.
#
# PowerShell 7 runs on Linux, macOS and the BSDs as well as Windows, so these
# scripts are usable from all of them -- but the old default was a hardcoded
# scoop path under $env:USERPROFILE, which exists on exactly one of those. PATH
# first, because a package-managed qemu is on it everywhere; the scoop location
# is only a fallback for the Windows install that does not add itself to PATH.
function Find-Qemu {
$exe = if ($IsWindows -or $env:OS -eq 'Windows_NT') { 'qemu-system-x86_64.exe' }
else { 'qemu-system-x86_64' }
$onPath = Get-Command $exe -ErrorAction SilentlyContinue
if ($onPath) { return $onPath.Source }
if ($env:USERPROFILE) {
$scoop = Join-Path (Join-Path $env:USERPROFILE 'scoop/apps/qemu/current') $exe
if (Test-Path $scoop) { return $scoop }
}
foreach ($p in @('/usr/bin', '/usr/local/bin', '/opt/homebrew/bin')) {
$c = Join-Path $p $exe
if (Test-Path $c) { return $c }
}
throw "qemu-system-x86_64 not found on PATH. Install QEMU, or pass -Qemu <path>."
}
$ErrorActionPreference = 'Stop' $ErrorActionPreference = 'Stop'
$root = Split-Path $PSScriptRoot -Parent $root = Split-Path $PSScriptRoot -Parent
$hda = Join-Path $root 'schos.img' $hda = Join-Path $root 'schos.img'
$hdb = if ($SchFS) { Join-Path $root 'build\schfs.img' } else { Join-Path $root 'build\fs.img' } # The root filesystem. SchFS is the only root there is: the kernel mounts
$log = Join-Path $root 'build\crashshot.log' # device 1 in forkret() and panics if it is not a SchFS volume, so this is
# not a choice and there is no -SchFS switch any more. It used to default to
# build/fs.img, which stopped being bootable the moment SchFS became root.
$hdb = Join-Path $root 'build/schfs.img'
$log = Join-Path $root 'build/crashshot.log'
$ppm = Join-Path $root $Out $ppm = Join-Path $root $Out
$mon = Join-Path $root 'build\qemu-mon.sock' $mon = Join-Path $root 'build/qemu-mon.sock'
Remove-Item -Force $log, $ppm -ErrorAction SilentlyContinue Remove-Item -Force $log, $ppm -ErrorAction SilentlyContinue

View file

@ -14,23 +14,48 @@
# shell. screendump writes a PPM; the PNG conversion is optional and skipped if # shell. screendump writes a PPM; the PNG conversion is optional and skipped if
# ImageMagick is not around. # ImageMagick is not around.
# #
# tools\crashshot.ps1 crash the ramdisk-root build # tools/crashshot.ps1 crash the ramdisk-root build
# tools\crashshot.ps1 -SchFS with a SchFS volume attached
param( param(
[switch]$SchFS,
[int]$BootSeconds = 20, [int]$BootSeconds = 20,
[string]$Out = "build\dispshot.ppm", [string]$Out = "build/dispshot.ppm",
[string]$Qemu = "$env:USERPROFILE\scoop\apps\qemu\current\qemu-system-x86_64.exe" [string]$Qemu = (Find-Qemu)
) )
# QEMU, on whichever host this is.
#
# PowerShell 7 runs on Linux, macOS and the BSDs as well as Windows, so these
# scripts are usable from all of them -- but the old default was a hardcoded
# scoop path under $env:USERPROFILE, which exists on exactly one of those. PATH
# first, because a package-managed qemu is on it everywhere; the scoop location
# is only a fallback for the Windows install that does not add itself to PATH.
function Find-Qemu {
$exe = if ($IsWindows -or $env:OS -eq 'Windows_NT') { 'qemu-system-x86_64.exe' }
else { 'qemu-system-x86_64' }
$onPath = Get-Command $exe -ErrorAction SilentlyContinue
if ($onPath) { return $onPath.Source }
if ($env:USERPROFILE) {
$scoop = Join-Path (Join-Path $env:USERPROFILE 'scoop/apps/qemu/current') $exe
if (Test-Path $scoop) { return $scoop }
}
foreach ($p in @('/usr/bin', '/usr/local/bin', '/opt/homebrew/bin')) {
$c = Join-Path $p $exe
if (Test-Path $c) { return $c }
}
throw "qemu-system-x86_64 not found on PATH. Install QEMU, or pass -Qemu <path>."
}
$ErrorActionPreference = 'Stop' $ErrorActionPreference = 'Stop'
$root = Split-Path $PSScriptRoot -Parent $root = Split-Path $PSScriptRoot -Parent
$hda = Join-Path $root 'schos.img' $hda = Join-Path $root 'schos.img'
$hdb = if ($SchFS) { Join-Path $root 'build\schfs.img' } else { Join-Path $root 'build\fs.img' } # The root filesystem. SchFS is the only root there is: the kernel mounts
$log = Join-Path $root 'build\crashshot.log' # device 1 in forkret() and panics if it is not a SchFS volume, so this is
# not a choice and there is no -SchFS switch any more. It used to default to
# build/fs.img, which stopped being bootable the moment SchFS became root.
$hdb = Join-Path $root 'build/schfs.img'
$log = Join-Path $root 'build/crashshot.log'
$ppm = Join-Path $root $Out $ppm = Join-Path $root $Out
$mon = Join-Path $root 'build\qemu-mon.sock' $mon = Join-Path $root 'build/qemu-mon.sock'
Remove-Item -Force $log, $ppm -ErrorAction SilentlyContinue Remove-Item -Force $log, $ppm -ErrorAction SilentlyContinue

View file

@ -6,22 +6,49 @@
# every byte accepted, and the only symptom is a picture that shears # every byte accepted, and the only symptom is a picture that shears
# progressively down the screen. That is a thing you have to look at. # progressively down the screen. That is a thing you have to look at.
# #
# tools\fbshot.ps1 gradient through mmap # tools/fbshot.ps1 gradient through mmap
# tools\fbshot.ps1 -Mode bars colour bars through write() # tools/fbshot.ps1 -Mode bars colour bars through write()
param( param(
[ValidateSet('fill', 'bars')] [ValidateSet('fill', 'bars')]
[string]$Mode = 'fill', [string]$Mode = 'fill',
[int]$BootSeconds = 20, [int]$BootSeconds = 20,
[string]$Qemu = "$env:USERPROFILE\scoop\apps\qemu\current\qemu-system-x86_64.exe" [string]$Qemu = (Find-Qemu)
) )
# QEMU, on whichever host this is.
#
# PowerShell 7 runs on Linux, macOS and the BSDs as well as Windows, so these
# scripts are usable from all of them -- but the old default was a hardcoded
# scoop path under $env:USERPROFILE, which exists on exactly one of those. PATH
# first, because a package-managed qemu is on it everywhere; the scoop location
# is only a fallback for the Windows install that does not add itself to PATH.
function Find-Qemu {
$exe = if ($IsWindows -or $env:OS -eq 'Windows_NT') { 'qemu-system-x86_64.exe' }
else { 'qemu-system-x86_64' }
$onPath = Get-Command $exe -ErrorAction SilentlyContinue
if ($onPath) { return $onPath.Source }
if ($env:USERPROFILE) {
$scoop = Join-Path (Join-Path $env:USERPROFILE 'scoop/apps/qemu/current') $exe
if (Test-Path $scoop) { return $scoop }
}
foreach ($p in @('/usr/bin', '/usr/local/bin', '/opt/homebrew/bin')) {
$c = Join-Path $p $exe
if (Test-Path $c) { return $c }
}
throw "qemu-system-x86_64 not found on PATH. Install QEMU, or pass -Qemu <path>."
}
$ErrorActionPreference = 'Stop' $ErrorActionPreference = 'Stop'
$root = Split-Path $PSScriptRoot -Parent $root = Split-Path $PSScriptRoot -Parent
$hda = Join-Path $root 'schos.img' $hda = Join-Path $root 'schos.img'
$hdb = Join-Path $root 'build\fs.img' # The root filesystem. SchFS is the only root there is: the kernel mounts
$log = Join-Path $root 'build\fbshot.log' # device 1 in forkret() and panics if it is not a SchFS volume, so this is
$ppm = Join-Path $root "build\fb-$Mode.ppm" # not a choice and there is no -SchFS switch any more. It used to default to
# build/fs.img, which stopped being bootable the moment SchFS became root.
$hdb = Join-Path $root 'build/schfs.img'
$log = Join-Path $root 'build/fbshot.log'
$ppm = Join-Path $root "build/fb-$Mode.ppm"
Remove-Item -Force $log, $ppm -ErrorAction SilentlyContinue Remove-Item -Force $log, $ppm -ErrorAction SilentlyContinue

View file

@ -6,9 +6,9 @@
# counts only first-party code, and separates the code from the comments so the # counts only first-party code, and separates the code from the comments so the
# number means something. # number means something.
# #
# tools\loc.ps1 the summary table # tools/loc.ps1 the summary table
# tools\loc.ps1 -PerFile every file, largest first # tools/loc.ps1 -PerFile every file, largest first
# tools\loc.ps1 -All include third-party trees, for comparison # tools/loc.ps1 -All include third-party trees, for comparison
# #
# Comment detection is deliberately simple: it handles // and /* */ for C and # Comment detection is deliberately simple: it handles // and /* */ for C and
# # for scripts, and does not try to understand a // inside a string literal. # # for scripts, and does not try to understand a // inside a string literal.

View file

@ -4,9 +4,9 @@
# El Torito image. The ramdisk root is the point: the ISO carries a complete # El Torito image. The ramdisk root is the point: the ISO carries a complete
# userland inside the kernel, so it boots with no hard disk attached at all. # userland inside the kernel, so it boots with no hard disk attached at all.
# #
# tools\makeiso.ps1 build it # tools/makeiso.ps1 build it
# tools\makeiso.ps1 -Run build it, then boot it under QEMU to check # tools/makeiso.ps1 -Run build it, then boot it under QEMU to check
# tools\makeiso.ps1 -Clean force a full rebuild first # tools/makeiso.ps1 -Clean force a full rebuild first
# #
# Afterwards the ISO is schos.iso in the repository root; point a VM's optical # Afterwards the ISO is schos.iso in the repository root; point a VM's optical
# drive at it and boot from CD. # drive at it and boot from CD.
@ -14,9 +14,32 @@
param( param(
[switch]$Run, [switch]$Run,
[switch]$Clean, [switch]$Clean,
[string]$Qemu = "$env:USERPROFILE\scoop\apps\qemu\current\qemu-system-x86_64.exe" [string]$Qemu = (Find-Qemu)
) )
# QEMU, on whichever host this is.
#
# PowerShell 7 runs on Linux, macOS and the BSDs as well as Windows, so these
# scripts are usable from all of them -- but the old default was a hardcoded
# scoop path under $env:USERPROFILE, which exists on exactly one of those. PATH
# first, because a package-managed qemu is on it everywhere; the scoop location
# is only a fallback for the Windows install that does not add itself to PATH.
function Find-Qemu {
$exe = if ($IsWindows -or $env:OS -eq 'Windows_NT') { 'qemu-system-x86_64.exe' }
else { 'qemu-system-x86_64' }
$onPath = Get-Command $exe -ErrorAction SilentlyContinue
if ($onPath) { return $onPath.Source }
if ($env:USERPROFILE) {
$scoop = Join-Path (Join-Path $env:USERPROFILE 'scoop/apps/qemu/current') $exe
if (Test-Path $scoop) { return $scoop }
}
foreach ($p in @('/usr/bin', '/usr/local/bin', '/opt/homebrew/bin')) {
$c = Join-Path $p $exe
if (Test-Path $c) { return $c }
}
throw "qemu-system-x86_64 not found on PATH. Install QEMU, or pass -Qemu <path>."
}
$ErrorActionPreference = 'Stop' $ErrorActionPreference = 'Stop'
$root = Split-Path $PSScriptRoot -Parent $root = Split-Path $PSScriptRoot -Parent
Push-Location $root Push-Location $root
@ -117,7 +140,7 @@ Write-Host " RAM: 64 MB is plenty. The old 224 MB floor is gone."
if ($Run) { if ($Run) {
Write-Host "" Write-Host ""
Write-Host "==> booting the ISO under QEMU, no disk attached" -ForegroundColor Cyan Write-Host "==> booting the ISO under QEMU, no disk attached" -ForegroundColor Cyan
$log = Join-Path $root 'build\isoboot.log' $log = Join-Path $root 'build/isoboot.log'
if (Test-Path $log) { Clear-Content $log } if (Test-Path $log) { Clear-Content $log }
$psi = New-Object System.Diagnostics.ProcessStartInfo $psi = New-Object System.Diagnostics.ProcessStartInfo

View file

@ -55,7 +55,12 @@
#define S_ISLNK(m) 0 #define S_ISLNK(m) 0
static long readlink(const char *p, char *b, unsigned long n) static long readlink(const char *p, char *b, unsigned long n)
{ (void)p; (void)b; (void)n; return -1; } { (void)p; (void)b; (void)n; return -1; }
#else #elif defined(__linux__)
/* glibc and musl both put major()/minor() here, and glibc warns and then
errors if you take them from <sys/types.h> instead. Linux only: macOS and
the BSDs have no <sys/sysmacros.h> at all and define both in <sys/types.h>,
which is already included above -- including it unconditionally is a build
failure on every non-Linux Unix. */
#include <sys/sysmacros.h> #include <sys/sysmacros.h>
#endif #endif

62
tools/sha256check.sh Normal file
View file

@ -0,0 +1,62 @@
#!/bin/sh
# Verify a file's SHA-256 against an expected hex digest, on any host.
#
# tools/sha256check.sh <expected-hex> <file>
#
# There is no one checksum command across the supported hosts. Linux and Git
# Bash have coreutils' sha256sum; macOS ships shasum(1) and no sha256sum;
# FreeBSD and NetBSD ship sha256(1) with a different output format again;
# OpenBSD has sha256 too but usually reached through cksum -a sha256. A recipe
# that hardcodes any one of them builds on some of the hosts this project
# supports and fails on the rest, and the failure arrives only when someone
# first runs `make ports` on a Mac.
#
# Compares digests rather than trusting a -c flag, because the -c input format
# differs between the implementations as well.
set -eu
if [ $# -ne 2 ]; then
echo "usage: $0 <expected-sha256> <file>" >&2
exit 2
fi
expected="$1"
file="$2"
if [ ! -f "$file" ]; then
echo "sha256check: no such file: $file" >&2
exit 1
fi
actual=
if command -v sha256sum >/dev/null 2>&1; then
actual=$(sha256sum "$file" | cut -d' ' -f1)
elif command -v shasum >/dev/null 2>&1; then
actual=$(shasum -a 256 "$file" | cut -d' ' -f1)
elif command -v sha256 >/dev/null 2>&1; then
# FreeBSD/NetBSD: -q prints the digest alone.
actual=$(sha256 -q "$file")
elif command -v cksum >/dev/null 2>&1 && cksum -a sha256 /dev/null >/dev/null 2>&1; then
# OpenBSD.
actual=$(cksum -a sha256 "$file" | sed 's/.*= *//')
elif command -v openssl >/dev/null 2>&1; then
actual=$(openssl dgst -sha256 "$file" | sed 's/.*= *//')
else
echo "sha256check: no usable checksum tool found" >&2
echo " looked for: sha256sum, shasum, sha256, cksum, openssl" >&2
exit 1
fi
# Case-insensitive: openssl and sha256 have been known to differ here.
expected_lc=$(printf '%s' "$expected" | tr 'A-Z' 'a-z')
actual_lc=$(printf '%s' "$actual" | tr 'A-Z' 'a-z')
if [ "$expected_lc" != "$actual_lc" ]; then
echo "sha256check: checksum mismatch for $file" >&2
echo " expected: $expected_lc" >&2
echo " actual: $actual_lc" >&2
exit 1
fi
echo "$file: OK"

View file

@ -10,19 +10,42 @@
# Same shape as tools/crashshot.ps1: monitor on TCP because QEMU on Windows has # Same shape as tools/crashshot.ps1: monitor on TCP because QEMU on Windows has
# no unix sockets, serial to a logfile so the transcript survives. # no unix sockets, serial to a logfile so the transcript survives.
# #
# tools\splashshot.ps1 frames every 400ms for 8s # tools/splashshot.ps1 frames every 400ms for 8s
# tools\splashshot.ps1 -Every 200 -For 4 # tools/splashshot.ps1 -Every 200 -For 4
param( param(
[int]$Every = 400, # milliseconds between frames [int]$Every = 400, # milliseconds between frames
[int]$For = 8, # seconds to keep capturing [int]$For = 8, # seconds to keep capturing
[string]$OutDir = "build\shots", [string]$OutDir = "build/shots",
[string]$Qemu = "$env:USERPROFILE\scoop\apps\qemu\current\qemu-system-x86_64.exe" [string]$Qemu = (Find-Qemu)
) )
# QEMU, on whichever host this is.
#
# PowerShell 7 runs on Linux, macOS and the BSDs as well as Windows, so these
# scripts are usable from all of them -- but the old default was a hardcoded
# scoop path under $env:USERPROFILE, which exists on exactly one of those. PATH
# first, because a package-managed qemu is on it everywhere; the scoop location
# is only a fallback for the Windows install that does not add itself to PATH.
function Find-Qemu {
$exe = if ($IsWindows -or $env:OS -eq 'Windows_NT') { 'qemu-system-x86_64.exe' }
else { 'qemu-system-x86_64' }
$onPath = Get-Command $exe -ErrorAction SilentlyContinue
if ($onPath) { return $onPath.Source }
if ($env:USERPROFILE) {
$scoop = Join-Path (Join-Path $env:USERPROFILE 'scoop/apps/qemu/current') $exe
if (Test-Path $scoop) { return $scoop }
}
foreach ($p in @('/usr/bin', '/usr/local/bin', '/opt/homebrew/bin')) {
$c = Join-Path $p $exe
if (Test-Path $c) { return $c }
}
throw "qemu-system-x86_64 not found on PATH. Install QEMU, or pass -Qemu <path>."
}
$ErrorActionPreference = 'Stop' $ErrorActionPreference = 'Stop'
$root = Split-Path $PSScriptRoot -Parent $root = Split-Path $PSScriptRoot -Parent
$log = Join-Path $root 'build\splashshot.log' $log = Join-Path $root 'build/splashshot.log'
$dir = Join-Path $root $OutDir $dir = Join-Path $root $OutDir
Remove-Item -Recurse -Force $dir -ErrorAction SilentlyContinue Remove-Item -Recurse -Force $dir -ErrorAction SilentlyContinue
@ -30,7 +53,11 @@ New-Item -ItemType Directory -Force $dir | Out-Null
Remove-Item -Force $log -ErrorAction SilentlyContinue Remove-Item -Force $log -ErrorAction SilentlyContinue
$hda = Join-Path $root 'schos.img' $hda = Join-Path $root 'schos.img'
$hdb = Join-Path $root 'build\fs.img' # The root filesystem. SchFS is the only root there is: the kernel mounts
# device 1 in forkret() and panics if it is not a SchFS volume, so this is
# not a choice and there is no -SchFS switch any more. It used to default to
# build/fs.img, which stopped being bootable the moment SchFS became root.
$hdb = Join-Path $root 'build/schfs.img'
$monPort = 55556 # not crashshot's port, so the two can run back to back $monPort = 55556 # not crashshot's port, so the two can run back to back
@ -42,7 +69,7 @@ $psi.Arguments = @(
# developer already has open would otherwise make this fail with "Failed to # developer already has open would otherwise make this fail with "Failed to
# get write lock" -- and a capture script is exactly the thing you want to # get write lock" -- and a capture script is exactly the thing you want to
# run without first shutting down what you were looking at. Copying was the # run without first shutting down what you were looking at. Copying was the
# first fix and it meant duplicating build\fs.img, all 153 MB of it, on # first fix and it meant duplicating build/fs.img, all 153 MB of it, on
# every run. -snapshot keeps writes in a temporary overlay, so the originals # every run. -snapshot keeps writes in a temporary overlay, so the originals
# are untouched either way. # are untouched either way.
"-drive file=`"$hda`",format=raw,index=0,media=disk,file.locking=off" "-drive file=`"$hda`",format=raw,index=0,media=disk,file.locking=off"

View file

@ -14,23 +14,48 @@
# shell. screendump writes a PPM; the PNG conversion is optional and skipped if # shell. screendump writes a PPM; the PNG conversion is optional and skipped if
# ImageMagick is not around. # ImageMagick is not around.
# #
# tools\crashshot.ps1 crash the ramdisk-root build # tools/crashshot.ps1 crash the ramdisk-root build
# tools\crashshot.ps1 -SchFS with a SchFS volume attached
param( param(
[switch]$SchFS,
[int]$BootSeconds = 20, [int]$BootSeconds = 20,
[string]$Out = "build\textshot.ppm", [string]$Out = "build/textshot.ppm",
[string]$Qemu = "$env:USERPROFILE\scoop\apps\qemu\current\qemu-system-x86_64.exe" [string]$Qemu = (Find-Qemu)
) )
# QEMU, on whichever host this is.
#
# PowerShell 7 runs on Linux, macOS and the BSDs as well as Windows, so these
# scripts are usable from all of them -- but the old default was a hardcoded
# scoop path under $env:USERPROFILE, which exists on exactly one of those. PATH
# first, because a package-managed qemu is on it everywhere; the scoop location
# is only a fallback for the Windows install that does not add itself to PATH.
function Find-Qemu {
$exe = if ($IsWindows -or $env:OS -eq 'Windows_NT') { 'qemu-system-x86_64.exe' }
else { 'qemu-system-x86_64' }
$onPath = Get-Command $exe -ErrorAction SilentlyContinue
if ($onPath) { return $onPath.Source }
if ($env:USERPROFILE) {
$scoop = Join-Path (Join-Path $env:USERPROFILE 'scoop/apps/qemu/current') $exe
if (Test-Path $scoop) { return $scoop }
}
foreach ($p in @('/usr/bin', '/usr/local/bin', '/opt/homebrew/bin')) {
$c = Join-Path $p $exe
if (Test-Path $c) { return $c }
}
throw "qemu-system-x86_64 not found on PATH. Install QEMU, or pass -Qemu <path>."
}
$ErrorActionPreference = 'Stop' $ErrorActionPreference = 'Stop'
$root = Split-Path $PSScriptRoot -Parent $root = Split-Path $PSScriptRoot -Parent
$hda = Join-Path $root 'schos.img' $hda = Join-Path $root 'schos.img'
$hdb = if ($SchFS) { Join-Path $root 'build\schfs.img' } else { Join-Path $root 'build\fs.img' } # The root filesystem. SchFS is the only root there is: the kernel mounts
$log = Join-Path $root 'build\crashshot.log' # device 1 in forkret() and panics if it is not a SchFS volume, so this is
# not a choice and there is no -SchFS switch any more. It used to default to
# build/fs.img, which stopped being bootable the moment SchFS became root.
$hdb = Join-Path $root 'build/schfs.img'
$log = Join-Path $root 'build/crashshot.log'
$ppm = Join-Path $root $Out $ppm = Join-Path $root $Out
$mon = Join-Path $root 'build\qemu-mon.sock' $mon = Join-Path $root 'build/qemu-mon.sock'
Remove-Item -Force $log, $ppm -ErrorAction SilentlyContinue Remove-Item -Force $log, $ppm -ErrorAction SilentlyContinue

View file

@ -7,19 +7,46 @@
# framebuffer. So the serial transcript can never tell you whether the escape # framebuffer. So the serial transcript can never tell you whether the escape
# parser works -- it contains the raw bytes either way. Only a picture can. # parser works -- it contains the raw bytes either way. Only a picture can.
# #
# tools\ttyshot.ps1 # tools/ttyshot.ps1
param( param(
[int]$BootSeconds = 22, [int]$BootSeconds = 22,
[string]$Out = "build\ttyshot.ppm", [string]$Out = "build/ttyshot.ppm",
[string]$Qemu = "$env:USERPROFILE\scoop\apps\qemu\current\qemu-system-x86_64.exe" [string]$Qemu = (Find-Qemu)
) )
# QEMU, on whichever host this is.
#
# PowerShell 7 runs on Linux, macOS and the BSDs as well as Windows, so these
# scripts are usable from all of them -- but the old default was a hardcoded
# scoop path under $env:USERPROFILE, which exists on exactly one of those. PATH
# first, because a package-managed qemu is on it everywhere; the scoop location
# is only a fallback for the Windows install that does not add itself to PATH.
function Find-Qemu {
$exe = if ($IsWindows -or $env:OS -eq 'Windows_NT') { 'qemu-system-x86_64.exe' }
else { 'qemu-system-x86_64' }
$onPath = Get-Command $exe -ErrorAction SilentlyContinue
if ($onPath) { return $onPath.Source }
if ($env:USERPROFILE) {
$scoop = Join-Path (Join-Path $env:USERPROFILE 'scoop/apps/qemu/current') $exe
if (Test-Path $scoop) { return $scoop }
}
foreach ($p in @('/usr/bin', '/usr/local/bin', '/opt/homebrew/bin')) {
$c = Join-Path $p $exe
if (Test-Path $c) { return $c }
}
throw "qemu-system-x86_64 not found on PATH. Install QEMU, or pass -Qemu <path>."
}
$ErrorActionPreference = 'Stop' $ErrorActionPreference = 'Stop'
$root = Split-Path $PSScriptRoot -Parent $root = Split-Path $PSScriptRoot -Parent
$hda = Join-Path $root 'schos.img' $hda = Join-Path $root 'schos.img'
$hdb = Join-Path $root 'build\fs.img' # The root filesystem. SchFS is the only root there is: the kernel mounts
$log = Join-Path $root 'build\ttyshot.log' # device 1 in forkret() and panics if it is not a SchFS volume, so this is
# not a choice and there is no -SchFS switch any more. It used to default to
# build/fs.img, which stopped being bootable the moment SchFS became root.
$hdb = Join-Path $root 'build/schfs.img'
$log = Join-Path $root 'build/ttyshot.log'
$ppm = Join-Path $root $Out $ppm = Join-Path $root $Out
$monPort = 55571 $monPort = 55571