SchOS/include/posix.h
Nexoniarz c80b7bf3b2 init: PID 1 becomes a role -- schinit splits, schinitd supervises
docs/plan/init.md milestones I0 and I1.

**The split.** schinit is PID 1 and does four things: bootstrap the
filesystems, fork schinitd, fork jobs on schinitd's behalf, reap children. No
manifest parsing, no filesystem walking after boot, no allocation. It is the
one program on the machine with no supervisor, so it is written to be too
boring to fail.

schinitd is PID 2 and holds everything else -- /etc/schinit, respawn policy,
throttling -- and is restartable, which is where the bugs are allowed to be.

**Jobs are forked by PID 1, not by schinitd.** That is the load-bearing
detail. schinitd decides and asks; PID 1 acts. So every job is a child of PID
1 at all times, and a supervisor that dies orphans nothing, reparents nothing
and restarts nothing.

**/proc/schinit** is the job table, written by PID 1 whenever it changes. The
kernel holds the text and does not interpret it: a procfs generator runs in
the kernel, and walking PID 1's memory to answer a read would put a userspace
program's format into the kernel, where it would rot.

Verified the way the plan asks for -- `kill 3`, then look:

    schinitd: adopting os.schos.console-shell
    schinitd: adopting os.schos.tty2-shell

/proc/schinit byte-identical across the restart, same pids, same cookies, same
start times, and the shell being typed into never noticed.

The first attempt got I1 wrong in the instructive way: PID 1 published the
table and schinitd never read it back, so the restarted supervisor started
everything a second time -- two console shells on one terminal, which is the
split-brain this plan rejects the two-init scheme for, reached by a different
road. Publishing state is half of it; coming back and believing it is the
other half.

The protocol is include/schinit.h: fixed-size messages, versioned in the
hello, entirely internal. Fixed-size so PID 1 never allocates, and a
supervisor that has gone mad cannot make it run a parser.

Still to come: I2 (heartbeat both ways, so a hang is as detectable as a death)
and I3 (proc_reincarnate, the actual law change).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VQ7Qx8E1q1M1bbCq8QLHS2
2026-09-13 13:57:27 +02:00

367 lines
10 KiB
C

// SPDX-License-Identifier: Apache-2.0
// SchOS — POSIX-compatibility types used by kernel syscall handlers.
#pragma once
#include "types.h"
#include "date.h"
struct iovec {
void *iov_base;
uint64 iov_len;
};
// Linux's IOV_MAX. readv/writev used to cap iovcnt at 16 -- which is legal,
// since POSIX only requires 16 -- and returned EINVAL above it. But programs
// are written against the platform they run on, and on Linux that limit is
// 1024: toybox's `yes` builds a 256-entry iovec and writev()s it in a loop,
// so `yes | head -2` failed with "Invalid argument" before producing a single
// byte. Nothing here costs per-entry memory; the count is just a loop bound.
#define UIO_MAXIOV 1024
// Simplified termios (matches Linux x86_64 layout enough for basic use).
struct termios {
uint32 c_iflag;
uint32 c_oflag;
uint32 c_cflag;
uint32 c_lflag;
uint8 c_line;
uint8 c_cc[19];
uint32 c_ispeed;
uint32 c_ospeed;
};
// Terminal flags, with Linux's values. The kernel honours the ones marked
// below; the rest are accepted and stored so a program that sets them and
// reads them back sees what it wrote, which is what cfmakeraw() and every
// line-editing library actually check.
//
// c_iflag
#define IGNBRK 0000001
#define BRKINT 0000002
#define IGNPAR 0000004
#define PARMRK 0000010
#define INPCK 0000020
#define ISTRIP 0000040 // honoured: strip the eighth bit
#define INLCR 0000100 // honoured: NL -> CR on input
#define IGNCR 0000200 // honoured: drop CR
#define ICRNL 0000400 // honoured: CR -> NL on input
#define IUCLC 0001000
#define IXON 0002000
#define IXANY 0004000
#define IXOFF 0010000
#define IMAXBEL 0020000
#define IUTF8 0040000
// c_oflag
#define OPOST 0000001 // honoured: enable output processing
#define OLCUC 0000002
#define ONLCR 0000004 // honoured: NL -> CR NL on output
#define OCRNL 0000010
#define ONOCR 0000020
#define ONLRET 0000040
// c_cflag
#define CS8 0000060
#define CREAD 0000200
#define HUPCL 0002000
#define CLOCAL 0004000
// c_lflag
#define ISIG 0000001 // honoured: INTR/QUIT/SUSP generate signals
#define ICANON 0000002 // honoured: line-at-a-time with editing
#define ECHO 0000010 // honoured
#define ECHOE 0000020 // honoured: ERASE rubs the character out
#define ECHOK 0000040
#define ECHONL 0000100 // honoured: echo NL even with ECHO off
#define NOFLSH 0000200
#define TOSTOP 0000400
#define IEXTEN 0100000
// c_cc indices (Linux order). VMIN/VTIME share slots with VEOF/VEOL, which is
// why they are only meaningful in the mode the other pair is not.
#define VINTR 0
#define VQUIT 1
#define VERASE 2
#define VKILL 3
#define VEOF 4
#define VTIME 5
#define VMIN 6
#define VSWTC 7
#define VSTART 8
#define VSTOP 9
#define VSUSP 10
#define VEOL 11
#define VREPRINT 12
#define VDISCARD 13
#define VWERASE 14
#define VLNEXT 15
#define VEOL2 16
struct winsize {
uint16 ws_row;
uint16 ws_col;
uint16 ws_xpixel;
uint16 ws_ypixel;
};
struct rlimit {
uint64 rlim_cur;
uint64 rlim_max;
};
// Linux ioctl codes we handle.
#define TCGETS 0x5401
#define TCSETS 0x5402
#define TCSETSW 0x5403
#define TCSETSF 0x5404
#define TCFLSH 0x540B
// TCFLSH queue selectors. Not a pointer -- these are the argument itself.
#define TCIFLUSH 0
#define TCOFLUSH 1
#define TCIOFLUSH 2
#define TIOCGPGRP 0x540F
#define TIOCSPGRP 0x5410
#define TIOCGWINSZ 0x5413
#define TIOCSWINSZ 0x5414
#define TIOCSCTTY 0x540E
// Virtual terminal control, Linux's numbers. The argument to VT_ACTIVATE and
// VT_WAITACTIVE is a 1-based terminal number passed by value, not a pointer.
#define VT_OPENQRY 0x5600 // first free terminal, out through a pointer
#define VT_GETMODE 0x5601
#define VT_SETMODE 0x5602
#define VT_GETSTATE 0x5603 // struct vt_stat
#define VT_ACTIVATE 0x5606
#define VT_WAITACTIVE 0x5607
// VT_SETMODE modes. VT_AUTO is the kernel switching on its own; VT_PROCESS
// means a program has taken switching over and the kernel must keep its hands
// off the keyboard.
#define VT_AUTO 0x00
#define VT_PROCESS 0x01
#define VT_ACKACQ 0x02
// VT_SETCHORD / VT_GETCHORD -- which key combination the kernel switches on.
// SchOS's own, not Linux's: Linux hardcodes Ctrl+Alt+F<n> and nobody has ever
// needed to move it, because Linux is the host. A guest does need to move it,
// because the right answer depends entirely on what the host grabs first --
// and that is not something the guest can discover.
#define VT_GETCHORD 0x5620
#define VT_SETCHORD 0x5621
// Modifier bits, matched exactly. These mirror kernel/dev/kbd.h's values so
// that userspace and the keyboard driver cannot drift apart.
#define VTC_SHIFT 0x01 // kbd.h SHIFT (1<<0)
#define VTC_CTRL 0x02 // kbd.h CTL (1<<1)
#define VTC_ALT 0x04 // kbd.h ALT (1<<2)
#define VTC_RALT 0x80 // kbd.h RALT (1<<7) -- NOT (1<<3); that is
// CAPSLOCK, and bits 3..6 are the other locks
// and the E0 prefix flag
// Well-known base scancodes for the `base` field below.
#define VTC_BASE_F1 0x3B // F1..F6
#define VTC_BASE_1 0x02 // the number row, 1..6
struct vt_chord {
uint16 mods; // exactly these modifiers held, and no others
uint16 base; // scancode of the key meaning terminal 1
};
struct vt_mode {
char mode; // VT_AUTO or VT_PROCESS
char waitv; // unused here
short relsig; // signal on release -- not delivered yet
short acqsig; // signal on acquire -- not delivered yet
short frsig; // unused
};
struct vt_stat {
uint16 v_active; // active terminal, 1-based
uint16 v_signal; // unused; Linux keeps it for VT_SETMODE
uint16 v_state; // bitmask of terminals in use, bit 1 = tty1
};
#define TIOCGSERIAL 0x541E
#define FIONREAD 0x541B
// FICLONE: share another file's data blocks with this one. The argument is a
// file descriptor, not a pointer. Linux's value, so `cp --reflink` works.
#define FICLONE 0x40049409
#define TIOCEXCL 0x540C
#define TIOCNXCL 0x540D
// arch_prctl commands
#define ARCH_SET_GS 0x1001
#define ARCH_SET_FS 0x1002
#define ARCH_GET_FS 0x1003
#define ARCH_GET_GS 0x1004
// clone flags (subset)
#define CLONE_VM 0x00000100
#define CLONE_THREAD 0x00010000
#define CLONE_SIGHAND 0x00000800
// futex ops
#define FUTEX_WAIT 0
#define FUTEX_WAKE 1
#define FUTEX_WAIT_PRIVATE 128
#define FUTEX_WAKE_PRIVATE 129
// Resource limit codes (POSIX)
#define RLIMIT_AS 9
#define RLIMIT_STACK 3
#define RLIMIT_NOFILE 7
// clock IDs
#define CLOCK_REALTIME 0
#define CLOCK_MONOTONIC 1
struct timespec {
long tv_sec;
long tv_nsec;
};
struct itimerval {
struct timeval it_interval;
struct timeval it_value;
};
struct rusage {
struct timeval ru_utime;
struct timeval ru_stime;
long ru_maxrss;
long ru_ixrss;
long ru_idrss;
long ru_isrss;
long ru_minflt;
long ru_majflt;
long ru_nswap;
long ru_inblock;
long ru_oublock;
long ru_msgsnd;
long ru_msgrcv;
long ru_nsignals;
long ru_nvcsw;
long ru_nivcsw;
};
struct tms {
long tms_utime;
long tms_stime;
long tms_cutime;
long tms_cstime;
};
struct sysinfo {
long uptime;
uint64 loads[3];
uint64 totalram;
uint64 freeram;
uint64 sharedram;
uint64 bufferram;
uint64 totalswap;
uint64 freeswap;
uint16 procs;
uint16 pad;
uint32 pad2;
uint64 totalhigh;
uint64 freehigh;
uint32 mem_unit;
char _f[8];
};
// Must match Linux's x86-64 struct statfs field for field, because the programs
// reading it were compiled against Linux's.
//
// f_fsid is the trap. Linux's fsid_t is `struct { int val[2]; }` -- eight bytes
// in total, not two eight-byte words -- and declaring it as int64[2] made this
// struct one slot too long from f_fsid onwards. Everything after it landed in
// the wrong place: userland read f_frsize out of f_namelen and got 255, so df
// computed its block counts in units of 255 bytes and reported a 32 MB root
// filesystem as 1992 KB. It went unnoticed for as long as it did because every
// field here used to be an invented constant, so the numbers were meaningless
// whichever slot they came from.
struct statfs {
int64 f_type;
int64 f_bsize;
int64 f_blocks;
int64 f_bfree;
int64 f_bavail;
int64 f_files;
int64 f_ffree;
int32 f_fsid[2];
int64 f_namelen;
int64 f_frsize;
int64 f_flags;
int64 f_spare[4];
};
// at-syscall dirfd sentinel
#define AT_FDCWD (-100)
#define AT_REMOVEDIR 0x200
#define AT_SYMLINK_NOFOLLOW 0x100
#define AT_EMPTY_PATH 0x1000
#define AT_NO_AUTOMOUNT 0x800
// prctl options
#define PR_SET_NAME 15
#define PR_GET_NAME 16
#define PR_SET_DUMPABLE 4
#define PR_GET_DUMPABLE 3
// signal numbers (Linux x86_64 values), used by kill dispatch and
// default-action classification in sigsend()
#define SIGHUP 1
#define SIGINT 2
#define SIGQUIT 3
#define SIGILL 4
#define SIGABRT 6
#define SIGBUS 7
#define SIGFPE 8
#define SIGKILL 9
#define SIGUSR1 10
#define SIGSEGV 11
#define SIGUSR2 12
#define SIGPIPE 13
#define SIGALRM 14
#define SIGTERM 15
#define SIGCHLD 17
#define SIGCONT 18
#define SIGSTOP 19
// Job-control and the remaining standard signals. A shell needs the TSTP/
// TTIN/TTOU trio to implement ^Z and background-job terminal access at all;
// they were simply absent, so any reference to them failed to compile.
#define SIGTRAP 5
#define SIGTSTP 20
#define SIGTTIN 21
#define SIGTTOU 22
#define SIGURG 23
#define SIGXCPU 24
#define SIGXFSZ 25
#define SIGVTALRM 26
#define SIGPROF 27
#define SIGWINCH 28
#define SIGIO 29
#define SIGPWR 30
#define SIGSYS 31
// getrusage who
#define RUSAGE_SELF 0
#define RUSAGE_CHILDREN (-1)
// fallocate mode
#define FALLOC_FL_KEEP_SIZE 1
// poll(2) event bits (Linux values)
// waitpid() options. The kernel tests `options & 1`; this is that bit, named.
#define WNOHANG 0x001
#define POLLIN 0x001
#define POLLPRI 0x002
#define POLLOUT 0x004
#define POLLERR 0x008
#define POLLHUP 0x010
#define POLLNVAL 0x020
struct pollfd {
int fd;
short events;
short revents;
};