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>
134 lines
5 KiB
C
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;
|
|
}
|