Nexoniarz is the sole author and SchOS was never published under Apache-2.0, so this is a straight relicence. LICENSE is the FSF GPLv2 text, every first-party SPDX header moves to GPL-2.0-or-later, and NOTICE, README.md, docs/licensing.md and the site say so. projects/schfs-linux follows the rest of the tree. The GPL gives copyleft: a fork has to ship its source. It also lifts the old "Linux drivers, never" rule, since GPL-2.0-only code combines with GPL-2.0-or-later. "Or later" leaves room for GPLv3, but one binary still has to pick one version, so docs/licensing.md now keeps Apache-2.0 and GPLv3 code out of the kernel to keep Linux code possible there. BSD 4-clause code goes from "avoid" to "no", because the advertising clause is GPL-incompatible. LDLS still ships no .ko. That boundary is now project policy rather than a legal requirement, and ldls.md, devices.md and licensing.md section 6 say so. refmod declares license=GPL, and ldls-check passes (27 checks). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
182 lines
9.1 KiB
Markdown
182 lines
9.1 KiB
Markdown
# Repository layout
|
||
|
||
Where everything is, and how to add to it.
|
||
|
||
One rule holds the whole thing together:
|
||
|
||
> **A directory contains either hand-written source or generated output. Never both.**
|
||
|
||
Everything generated lives under `build/`. Delete `build/` and you lose nothing
|
||
that can't be rebuilt (except upstream tarballs, which live in `dist/` and are
|
||
deliberately kept out of `clean`).
|
||
|
||
```
|
||
schos/
|
||
├─ boot/ uefi/ the PE/COFF loader (splash, F8 menu, kernel load),
|
||
│ common/ the UI it shares. UEFI only -- there is no BIOS path.
|
||
├─ kernel/ The kernel, by subsystem: arch/ core/ dev/ fs/ proc/ sys/
|
||
├─ include/ Headers shared by kernel, userland, and tools/mkfs
|
||
├─ lib/ First-party userland runtime: _start glue, syscall stubs,
|
||
│ the initcode blob embedded in the kernel, user.ld
|
||
├─ cmd/ First-party programs — one directory per program:
|
||
│ cmd/<name>/main.c → sysroot/bin/<name>
|
||
│ schinit and schinitd are PID 1 and its supervisor; vgpud is
|
||
│ the virtio-gpu driver, which runs out here rather than in
|
||
│ the kernel; socktest/shmtest/nettest/pcitest are suites.
|
||
├─ ports/ Third-party software — one directory per port.
|
||
│ Contains ONLY a Makefile and optional patches/.
|
||
├─ projects/ Things that run ON SchOS rather than things that ARE it:
|
||
│ the compositor, window manager, desktop, and installer.
|
||
│ Not part of the kernel build. See projects/README.md.
|
||
├─ tools/ Host-side helpers, SOURCE ONLY: mkfs.c, the SchFS test
|
||
│ suites, sign.pl, vectors.pl, and the PowerShell harnesses.
|
||
│ Their binaries build into build/tools/ -- this directory
|
||
│ used to hold both, which is the one rule this layout has.
|
||
│ tools/windows/loc.ps1 counts how much of the tree is first-party.
|
||
├─ sysroot/ Staging for the disk image. etc/ is hand-maintained;
|
||
│ bin/ lib/ sbin/ are populated by `make sysroot-install`.
|
||
├─ dist/ Upstream tarball cache. Survives `make clean`.
|
||
├─ Refrence/ A FreeBSD checkout (Refrence/freebsd-src), kept as reference
|
||
│ material and a parts bin. Never built, never in an image.
|
||
│ Read it when a specification is ambiguous; copy from it only
|
||
│ after the per-file licence check in docs/licensing.md.
|
||
│ A full clone is 2.1 GB and a previous NetBSD one was deleted
|
||
│ for that reason. Only sys/dev, sys/net and sys/netinet have
|
||
│ ever been read, so a sparse checkout is ~100 MB:
|
||
│ git clone --filter=blob:none --sparse <url> Refrence/freebsd-src
|
||
│ git -C Refrence/freebsd-src sparse-checkout set │ sys/dev sys/net sys/netinet
|
||
├─ src-trunk/ A NetBSD checkout, when present -- reference material and a
|
||
│ parts bin. NOT built, NOT installed, must never reach an
|
||
│ image. Currently absent; it was deleted for disk space and
|
||
│ can be re-fetched sparsely. Copying from it is governed by
|
||
│ the per-file licence check in docs/licensing.md §3 --
|
||
│ NetBSD is not uniformly licensed.
|
||
├─ shell.nix NixOS development shell: the whole toolchain in one file
|
||
├─ docs/ architecture/ what it is plan/ what's next
|
||
│ guides/ how to work history/ why it's like this
|
||
└─ build/ ALL generated output (see below)
|
||
```
|
||
|
||
## build/
|
||
|
||
```
|
||
build/
|
||
├─ boot/ uefi/ loader, common/ UI
|
||
├─ kernel/ object files, the linked kernel, kernel.asm
|
||
├─ lib/ libc glue objects, initcode blob
|
||
├─ cmd/ linked first-party programs
|
||
├─ fs.img the filesystem image
|
||
└─ ports/<name>/
|
||
├─ src/ extracted upstream tarball
|
||
├─ obj/ out-of-tree build directory
|
||
└─ prefix/ install prefix (musl's headers and libs land here)
|
||
```
|
||
|
||
## Adding your own program
|
||
|
||
```sh
|
||
mkdir cmd/hello
|
||
$EDITOR cmd/hello/main.c # include "types.h", "user.h", ...
|
||
```
|
||
|
||
Then add `hello` to `CMDS` in the root `Makefile`. It gets linked against
|
||
`lib/` and installed to `sysroot/bin/hello` by `make sysroot-install`.
|
||
|
||
## Adding a third-party port
|
||
|
||
```sh
|
||
mkdir ports/foo
|
||
$EDITOR ports/foo/Makefile # start with `include ../mk/port.mk`
|
||
```
|
||
|
||
`port.mk` derives everything from the directory name — it gives you `$(SRCDIR)`,
|
||
`$(BUILDDIR)`, `$(PREFIX)`, `$(DISTDIR)`, `$(SYSROOT)`, and `$(MUSL)` (the
|
||
musl install prefix that every port links against). Follow the pattern in
|
||
`ports/mksh/Makefile`: fetch tarball to `$(DISTDIR)`, extract to `$(SRCDIR)`,
|
||
build into `$(BUILDDIR)`, install into `$(SYSROOT)`. `clean` removes
|
||
`$(PORTDIR)` and nothing else.
|
||
|
||
`ports/Makefile` picks up the new directory automatically. If it links against
|
||
musl, add `foo: musl` there so ordering is right.
|
||
|
||
Local modifications to upstream go in `ports/foo/patches/*.patch` and are
|
||
applied after extraction — never by editing files under `build/`, which are
|
||
wiped on every clean.
|
||
|
||
## Ramdisk root
|
||
|
||
```sh
|
||
make # normal: root on the IDE disk (build/fs.img as hdb)
|
||
make ramroot # root from a SCHFS image linked into the kernel
|
||
```
|
||
|
||
`make ramroot` builds `build/fs-ram.img` (default 6000 blocks, override with
|
||
`RAMDISK_BLOCKS=`) and links it into the kernel, so the system boots to a shell
|
||
with **no data disk attached at all**. That is what makes destructive work on
|
||
the real disk survivable — reformatting it, or bringing up a new on-disk format
|
||
— and it is the first piece of the installer.
|
||
|
||
Use `make ramroot`, not `make RAMROOT=1`: the flag changes `CFLAGS`, which make
|
||
cannot see, so objects compiled for one setting would be silently reused for
|
||
the other and `ROOTDEV` would point at the wrong device. The target drops the
|
||
compiled objects first. Switching back is the same — plain `make` after a
|
||
`ramroot` build reuses stale objects, so run `rm -rf build/kernel build/lib
|
||
build/cmd` first (or just `make clean`).
|
||
|
||
The image costs boot time: the loader reads the kernel through the firmware's
|
||
file protocol, so a 6 MB ramroot kernel is visibly slower to start than the
|
||
350 KB default.
|
||
|
||
**What may go in it is a licensing question, not only a size one.** The image is
|
||
linked into the kernel binary, so anything in `sysroot/` when it is built becomes
|
||
part of a distributable artifact. A file staged there under a licence that
|
||
is not ours to redistribute does not merely ride along — it makes the kernel
|
||
itself undistributable. `RAMDISK_EXCLUDE` in the Makefile is the list of
|
||
paths kept out, and enforcing it in the build rather than relying on someone
|
||
remembering to unstage something first is deliberate. It has been needed once
|
||
already: a 33 MB test payload landed in `sysroot/` and both overflowed the image
|
||
and would have gone into the kernel binary.
|
||
|
||
Three knobs, all in the Makefile, none of them `FSSIZE`:
|
||
|
||
| | |
|
||
|---|---|
|
||
| `RAMDISK_BLOCKS` | image size, 6000 (× 1 KiB `BSIZE`) |
|
||
| `RAMDISK_NINODES` | inode table size, 2000 |
|
||
| `RAMDISK_EXCLUDE` | sysroot-relative paths to omit, with their subtrees |
|
||
|
||
`RAMDISK_NINODES` is worth knowing about because the inode table is
|
||
*preallocated*: mkfs's default of 12000 inodes cost 1501 of the 6000 blocks — a
|
||
quarter of the image — to describe about 260 files. At 2000 it costs 251, and
|
||
metadata overall fell from 1596 blocks to 345.
|
||
|
||
If `mkfs` reports the image is full, read its message: it now prints the
|
||
metadata breakdown and names the right knob for whichever image overflowed. It
|
||
used to say only "increase FSSIZE in param.h", which is a constant that has
|
||
nothing to do with the ramdisk root and sends you to the wrong file.
|
||
|
||
## Two things that will bite you
|
||
|
||
The second is about a bootloader that no longer exists. It is kept because the
|
||
*shape* of the bug outlived the code.
|
||
|
||
**`ld -r -b binary` derives symbol names from the input file's path.** The
|
||
kernel embeds two blobs this way (`initcode`, `entryother`), and the root
|
||
Makefile has `--redefine-sym` lines mapping e.g.
|
||
`_binary_build_lib_initcode_start` → `_binary_initcode_start`. Move either
|
||
blob's build location and those symbol names change; update the Makefile to
|
||
match or the kernel fails to link.
|
||
|
||
**`STAGE2_SECTORS` is gone, with the BIOS loader it sized.** It is recorded here
|
||
because the shape of the bug outlived it: one number that four files each
|
||
believed independently — `boot/bios/bootasm.S`, `stage2main.c`'s `readseg()`,
|
||
the `dd ... seek=` offset, and twice inside `mkiso.sh`. Raising it from 24 to 48
|
||
for the boot logo left the `dd seek=25` offset behind, so the kernel was written
|
||
directly over stage 2's own tail, and nothing said so.
|
||
|
||
The fix was to derive all five from one Makefile variable. The lesson worth
|
||
keeping is the other half: `tools/padstage2.pl` *failed the build* when stage 2
|
||
overflowed its budget rather than truncating it, which is why the overflow to
|
||
25440 bytes against a 24576 limit was found at all. A layout constant that
|
||
several artifacts must agree on wants a check that stops the build, not a
|
||
comment asking people to remember.
|