SchOS/cmd/init/main.c
Nexoniarz 66152ad2f2 Give userspace a raw disk: /dev/hda and /dev/hdb
Nothing outside the kernel could touch a disk. sys_mount does not even
look at the device it is handed -- it mounts the one SchFS volume found
at boot and ignores `source` -- so a program had no way to read a
partition table, format a volume or write a boot sector. An installer is
a program that needs all three, so this is the first piece of it.

A character device in devsw rather than a separate block layer, because
that is where the offset already lives: fileread/filewrite pass f->off
and advance it by what comes back, which is everything a seekable device
needs. The minor is the IDE device number, the same numbering iderw()
uses; two schemes for the same disks would be a bug waiting to happen.

Writes bypass every filesystem above them, which is inherent to a raw
device and why an installer must target a disk it has not mounted.

The nodes are created unconditionally, like every other entry in /dev.
That matters more than it looks: with no node, `echo x > /dev/hdb`
silently creates an ordinary file of that name and appears to work. It
did, and it agreed with itself on read-back; only the host's copy of the
disk showed nothing had been written.

st_rdev was never filled, so every node in /dev reported "0, 0" under
ls -l. Harmless while nothing read it, wrong as soon as something has to
tell two disks apart. Filled with the full Linux dev_t encoding rather
than (major << 8 | minor), which only works while both stay small.

Verified against the host as an independent oracle, both directions: the
guest wrote to /dev/hdb and the host saw those bytes at offset 0; the
host wrote at offset 4096 and the guest read them back with dd skip=.
ls -l now shows 1,1 for console, 2,0 for fb0 and 8,1 for hdb.

Known limit, commented in disk.c: devsw passes the offset as a 32-bit
uint, so a raw disk is addressable to 4 GB. That must be widened before
anyone installs onto a real disk.

bootcheck 108 [ok] with -Fb -Tty -Usb -Sock -Evdev -Ps.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-09 22:19:40 +02:00

134 lines
5 KiB
C

// SPDX-License-Identifier: Apache-2.0
// SchOS — minimal init (PID 1)
#include "types.h"
#include "user.h"
#include "fcntl.h"
static void
xputs(int fd, const char *s)
{
int n = 0;
while (s[n]) n++;
write(fd, (void *)s, n);
}
int
main(void)
{
if (getpid() != 1)
exit();
// Open console as fd 0; dup to fd 1 (stdout) and fd 2 (stderr).
if (open("console", O_RDWR) < 0) {
mknod("console", 1, 1);
open("console", O_RDWR);
}
dup(0);
dup(0);
// --- /dev ------------------------------------------------------------
// Minor numbers under major 1 follow Linux's mem-major layout where it
// costs nothing (null=2, zero=5, full=7, random=8, urandom=9).
// /dev/hole (was minor 4) is gone: it backed an abandoned experiment and
// nothing has referenced it since.
mkdir("/dev");
mknod("/dev/console", 1, 1);
mknod("/dev/null", 1, 2);
mknod("/dev/keyboard", 1, 3);
mknod("/dev/zero", 1, 5);
mknod("/dev/full", 1, 7);
mknod("/dev/random", 1, 8);
mknod("/dev/urandom", 1, 9);
mknod("/dev/fb0", 2, 0);
mknod("/dev/mouse", 3, 1);
mknod("/dev/tty", 1, 1); // alias for console — must pass TCGETS ioctl
mknod("/dev/ttyS0", 4, 64);
mknod("/dev/stdin", 1, 1);
mknod("/dev/stdout", 1, 1);
mknod("/dev/stderr", 1, 1);
// Raw input events, Linux evdev layout (kernel/dev/input.c). /dev/keyboard
// and /dev/mouse above are the old character-and-position interfaces; these
// are the ones with key releases and button transitions, which is what a
// game or a compositor needs and what neither of the others can express.
mkdir("/dev/input");
mknod("/dev/input/event0", 7, 0); // keyboard
mknod("/dev/input/event1", 7, 1); // mouse
// Raw disks (kernel/dev/disk.c). The minor is the IDE device number, so
// hda is the disk the kernel booted from and hdb the second channel.
//
// Created unconditionally, the same way every other node here is: opening
// one when no drive is there fails with ENXIO, which is a better answer
// than a missing path, and it keeps this list a description of the device
// model rather than of one machine's hardware.
mknod("/dev/hda", 8, 0);
mknod("/dev/hdb", 8, 1);
// --- /proc -----------------------------------------------------------
// Synthesised status files, major 6 (kernel/dev/procfs.c). These are the
// global ones; per-process /proc/<pid>/ directories need synthetic
// directory support in the filesystem, which does not exist yet.
mkdir("/proc");
// /proc is a real filesystem now (kernel/fs/procfs.c), not a handful of
// mknod'd nodes: it has to be, because one directory per process cannot be
// created ahead of time. This is what makes `ps` and `top` work.
//
// The mknod'd global files below still happen, and are simply covered by
// the mount. They stay for now so that a failure here degrades to the old
// behaviour instead of losing /proc entirely.
mount("proc", "/proc", "proc", 0, 0);
mknod("/proc/meminfo", 6, 1);
mknod("/proc/uptime", 6, 2);
mknod("/proc/cpuinfo", 6, 3);
mknod("/proc/version", 6, 4);
mknod("/proc/stat", 6, 5);
mknod("/proc/loadavg", 6, 6);
mknod("/proc/mounts", 6, 7);
mknod("/proc/filesystems", 6, 8);
// SchFS volume summary: label, generation, free space, and the root
// directory. Reachable whether or not the volume is mounted into the
// namespace, which is what makes it useful when a mount has gone wrong.
mknod("/proc/schfs", 6, 9);
// Write 'c' here to crash on purpose. The crash screen is otherwise the
// one part of the system that can only be seen by having a bug.
mknod("/proc/sysrq", 6, 10);
mknod("/proc/pci", 6, 11);
mknod("/proc/interrupts", 6, 12);
mkdir("/sys");
xputs(1, "\nSchOS\n");
xputs(1, "init: ready (pid 1)\n");
for (;;) {
int pid = fork();
if (pid < 0) {
xputs(2, "init: fork failed\n");
sleep(100);
continue;
}
if (pid == 0) {
// exec.c auto-enables Linux ABI for musl binaries (entry >= 0x100000).
// Do NOT call enable_linux_abi() here — it corrupts SchOS syscall
// numbers for the exec/exit calls that follow in this child.
// Standardized naming: the shell is always /bin/sh, regardless of
// which implementation backs it (dash, mksh, etc.) — no ksh/mksh/
// other aliases are looked up here.
static char *argv[] = { "sh", 0 };
exec("/bin/sh", argv);
xputs(2, "init: no shell found; place sh in /bin\n");
exit();
}
// Reap zombies; the loop restarts the shell when it exits.
int wpid;
while ((wpid = wait()) >= 0 && wpid != pid)
;
// Brief pause before restarting so we don't spin on a missing shell.
sleep(100);
}
return 0;
}