include: one folder per subsystem, and includes that name it

Sixty-five headers sat side by side in include/, so `#include "irq.h"`
said nothing about where irq.h was or what it belonged to, and errno.h,
stat.h and fcntl.h at the top shadowed the system's for any host tool
built with -I./include. They are in folders now -- arch/ core/ fs/
dev/ gfx/ net/ uapi/ (and ldls/ for the Linux driver layer) -- and
every include names the folder, as Linux's do: "core/proc.h".

Done mechanically and checked the hard way: the kernel built from this
tree disassembles identically to the one built from the tree before
it. The full image boots, and userland is the same set of programs.
Paths in docs and comments follow; docs/guides/layout.md has a table
of what is where, and tools/fix-includes.py rewrites old-style
includes from the folders in the tree, for code written before this.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01S1giNvrHTrPMPw7V6x1kQ4
This commit is contained in:
Nexoniarz 2026-10-23 15:05:12 +02:00
commit a1c965da1e
267 changed files with 1560 additions and 1440 deletions

66
include/uapi/errno.h Normal file
View file

@ -0,0 +1,66 @@
// Error numbers, matching the Linux x86_64 ABI values so that musl-linked
// programs interpret syscall failures correctly. Kernel code returns these
// negated (e.g. `return -ENOSYS;`), exactly like Linux does internally.
#ifndef SCHOS_ERRNO_H
#define SCHOS_ERRNO_H
#define EPERM 1 // Operation not permitted
#define ENOENT 2 // No such file or directory
#define ESRCH 3 // No such process
#define EINTR 4 // Interrupted system call
#define EIO 5 // I/O error
#define ENXIO 6 // No such device or address
#define E2BIG 7 // Argument list too long
#define ENOEXEC 8 // Exec format error
#define EBADF 9 // Bad file descriptor
#define ECHILD 10 // No child processes
#define EAGAIN 11 // Try again
#define ENOMEM 12 // Out of memory
#define EACCES 13 // Permission denied
#define EFAULT 14 // Bad address
#define ENOTBLK 15 // Block device required
#define EBUSY 16 // Device or resource busy
#define EEXIST 17 // File exists
#define EXDEV 18 // Cross-device link
#define ENODEV 19 // No such device
#define ENOTDIR 20 // Not a directory
#define EISDIR 21 // Is a directory
#define EINVAL 22 // Invalid argument
#define ENFILE 23 // File table overflow
#define EMFILE 24 // Too many open files
#define ENOTTY 25 // Not a typewriter
#define EFBIG 27 // File too large
#define ENOSPC 28 // No space left on device
#define ESPIPE 29 // Illegal seek
#define EROFS 30 // Read-only file system
#define EMLINK 31 // Too many links
#define EPIPE 32 // Broken pipe
#define ERANGE 34 // Math result not representable
#define ENAMETOOLONG 36 // File name too long
#define ENOSYS 38 // Function not implemented
#define ENOTEMPTY 39 // Directory not empty
#define ELOOP 40 // Too many symbolic links encountered
// Sockets. Same Linux x86_64 values, so strerror() in a musl-linked program
// prints the message the programmer expected rather than a neighbouring one.
#define ENOTSOCK 88 // Socket operation on non-socket
#define EDESTADDRREQ 89 // Destination address required
#define EMSGSIZE 90 // Message too long
#define EPROTOTYPE 91 // Protocol wrong type for socket
#define ENOPROTOOPT 92 // Protocol not available
#define EPROTONOSUPPORT 93 // Protocol not supported
#define EOPNOTSUPP 95 // Operation not supported on transport endpoint
#define EAFNOSUPPORT 97 // Address family not supported by protocol
#define EADDRINUSE 98 // Address already in use
#define EADDRNOTAVAIL 99 // Cannot assign requested address
#define ECONNRESET 104 // Connection reset by peer
#define EISCONN 106 // Transport endpoint is already connected
#define ENODATA 61 // No data available -- no such attribute
#define ENOBUFS 105 // No buffer space available
#define ENOTSUP 95 // Operation not supported
#define ENETDOWN 100 // Network is down
#define ENETUNREACH 101 // Network is unreachable
#define ENOTCONN 107 // Transport endpoint is not connected
#define ECONNREFUSED 111 // Connection refused
#endif // SCHOS_ERRNO_H

43
include/uapi/fcntl.h Normal file
View file

@ -0,0 +1,43 @@
#pragma once
// open(2) flags.
//
// These are the *Linux x86-64* values, and they have to be: every program in
// the userland is built against a libc that sends Linux flag values, and the
// kernel reads them with these constants. They disagreed for a long time, with
// results that looked like filesystem corruption rather than like a mistake --
// Linux O_APPEND (0x400) landed on SchOS's O_TRUNC, so `cmd >> file`
// truncated the file it was meant to extend, and Linux O_TRUNC (0x200) landed
// on O_CREATE, so `cmd > file` left whatever was past the new end in place.
// Neither failure pointed anywhere near a header.
//
// Do not tidy these into a neat sequence. They are an external interface.
#define O_RDONLY 0x000000
#define O_WRONLY 0x000001
#define O_RDWR 0x000002
#define O_ACCMODE 0x000003
#define O_CREATE 0x000040 /* Linux O_CREAT (0100) */
#define O_CREAT O_CREATE /* the spelling every program uses */
#define O_EXCL 0x000080 /* Linux O_EXCL (0200) */
#define O_NOCTTY 0x000100 /* Linux O_NOCTTY (0400) */
#define O_TRUNC 0x000200 /* Linux O_TRUNC (01000) */
#define O_APPEND 0x000400 /* Linux O_APPEND (02000) */
#define O_NONBLOCK 0x000800 /* Linux O_NONBLOCK (04000); no-op here */
#define O_DIRECTORY 0x010000 /* Linux O_DIRECTORY (0200000) */
#define O_NOFOLLOW 0x020000 /* Linux O_NOFOLLOW (0400000) */
#define O_CLOEXEC 0x080000 /* Linux O_CLOEXEC (02000000) */
// SchOS-only, placed above every flag Linux defines so it can never be
// mistaken for one. 0x40000 was used before and collides with Linux O_NOATIME.
#define O_EXTENT 0x8000000 /* sparse-extent hint */
#define F_DUPFD 0
#define F_GETFD 1
#define F_SETFD 2
#define F_GETFL 3
#define F_SETFL 4
#define F_DUPFD_CLOEXEC 1030 // Linux value
#define FD_CLOEXEC 1

423
include/uapi/posix.h Normal file
View file

@ -0,0 +1,423 @@
// SPDX-License-Identifier: GPL-2.0-or-later
// SchOS — POSIX-compatibility types used by kernel syscall handlers.
#pragma once
#include "core/types.h"
#include "core/date.h"
// struct iovec is declared in two headers on purpose, and guarded so that
// including both is not an error.
//
// posix.h has it because readv/writev need it; this file has it because a
// userspace program including <socket.h> for sendmsg must get a complete
// struct msghdr without also having to know about a kernel header. Both
// definitions are identical and both are Linux's layout.
//
// The guard is not cosmetic. C23 permits redeclaring a struct with identical
// members and GCC 15 defaults to C23, so the duplicate was invisible on the
// gcc path -- while clang defaults to gnu17 and rejected it, which broke the
// one toolchain shell.nix exists to provide and which the UEFI loader needs.
#ifndef SCH_IOVEC_DEFINED
#define SCH_IOVEC_DEFINED
struct iovec {
void *iov_base;
uint64 iov_len;
};
#endif
// 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
// KB_GETLAYOUT / KB_SETLAYOUT / KB_LISTLAYOUT -- which keyboard layout the
// console uses. SchOS's own, and deliberately not Linux's KDGKBENT: that
// interface hands over one key of one table at a time and expects userspace to
// own the whole map, which means a `loadkeys` that ships its own data files and
// a boot that is broken until one is loaded. The layouts live in the kernel
// here, and userspace names the one it wants.
#define KB_GETLAYOUT 0x5622 // fill in the active layout
#define KB_SETLAYOUT 0x5623 // switch to the layout named in `name`
#define KB_LISTLAYOUT 0x5624 // describe layout `index`; -EINVAL past end
struct kb_layout {
uint16 index; // in for KB_LISTLAYOUT, out for KB_GETLAYOUT
char name[16]; // "us", "pl", "de"
char desc[96]; // human-readable, for `loadkeys -l`
};
// 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
// sigprocmask(2)'s `how`. Linux's values. The kernel has always implemented
// these -- sys_sigprocmask() branches on 0, 1 and 2 with the names in comments
// -- but never named them, so anything outside the kernel had to spell the
// number and hope it had the right one.
#define SIG_BLOCK 0
#define SIG_UNBLOCK 1
#define SIG_SETMASK 2
#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)
// mmap(2) protection and flags.
//
// These lived only in kernel/sys/sys_mem.c, so every userspace caller wrote
// its own copy -- cmd/fbinfo had one and cmd/pcitest wanted a third. Two
// private copies of a number the kernel compares against is a mismatch
// waiting to happen, and the symptom would be a mapping with the wrong
// permissions rather than an error.
#define PROT_NONE 0x0
#define PROT_READ 0x1
#define PROT_WRITE 0x2
#define PROT_EXEC 0x4
#define MAP_SHARED 0x01
#define MAP_PRIVATE 0x02
#define MAP_FIXED 0x10
#define MAP_ANONYMOUS 0x20
// 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;
};

143
include/uapi/schinit.h Normal file
View file

@ -0,0 +1,143 @@
// SPDX-License-Identifier: GPL-2.0-or-later
//
// The protocol between PID 1 and the supervisor.
//
// docs/architecture/init.md warns about this file specifically: two
// processes that must agree is a compatibility surface where there was none,
// and docs/plan/schapi.md §3 is about how those go wrong. So the rules are
// written here rather than assumed.
//
// **Fixed-size messages.** Every message is exactly one struct, and PID 1
// reads sizeof(struct) or nothing. No length prefix to get wrong, no
// partial-parse state to be caught in, and -- the reason that matters --
// **PID 1 never allocates**. A supervisor that has gone mad cannot make PID 1
// allocate, block on a malloc, or run a parser.
//
// **Versioned in the first message.** SCHINIT_PROTO travels in the hello. A
// mismatch is a refusal, not a best-effort interpretation.
//
// **Entirely internal.** This is not SchAPI. Nothing but schinit and
// schinitd may speak it, and it may change shape whenever both change
// together, which is what keeping it in one header is for.
//
// The asymmetry is the design, not an accident: the supervisor *asks*, PID 1
// *acts*. Every job on the machine is therefore a child of PID 1 at all times,
// so a supervisor that dies orphans nothing and reparents nothing.
#ifndef SCHINIT_H
#define SCHINIT_H
#define SCHINIT_PROTO 1
#define SCHINIT_MAXARGS 16
#define SCHINIT_MAXENVV 16
#define SCHINIT_MAXLABEL 64
#define SCHINIT_MAXPATH 128
// One flat buffer for argv and envp strings, copied whole. Pointers cannot
// cross a process boundary, so the spec carries text and PID 1 rebuilds the
// arrays on its own stack after exec'ing -- which is the only place they are
// ever valid.
#define SCHINIT_STRBUF 1024
// supervisor -> PID 1
#define SCHINIT_HELLO 1 // struct schinit_hello
#define SCHINIT_SPAWN 2 // struct schinit_spawn
#define SCHINIT_PONG 3 // struct schinit_hello -- answer to a PING
// PID 1 -> supervisor
#define SCHINIT_WELCOME 128 // struct schinit_hello, echoed with PID 1's version
#define SCHINIT_SPAWNED 129 // struct schinit_reply -- pid, or negative on failure
#define SCHINIT_EXITED 130 // struct schinit_reply -- a child has been reaped
#define SCHINIT_PING 131 // struct schinit_hello -- answer within the deadline
// Exit statuses a job can use to tell the supervisor something.
//
// KeepAlive restarts a job that crashed or was killed, which is the whole
// point of it for a driver. It must not restart one that has looked at the
// machine and reported there is nothing here for it to do: a driver whose
// device is absent fails identically every time, and a supervisor that tries
// anyway spends the rest of the uptime doing so -- 15 spawns in 45 seconds,
// on hardware with no virtio-gpu, each one printing a line to the console.
//
// 78 is sysexits.h's EX_CONFIG, "something was found in an unconfigured or
// misconfigured state", which is exactly what a driver with no device has
// found. Borrowed rather than invented, so a program written against any
// other Unix already means the right thing by it.
//
// Note what this is *not*: it is not a new manifest key and not a new policy
// knob. The wait status has always travelled from PID 1 to the supervisor in
// struct schinit_reply, and note_exit() has always thrown it away. This is
// the other half of a thing that was already built.
#define SCHINIT_EXIT_NORESTART 78
// The heartbeat.
//
// docs/architecture/init.md, "The heartbeat" -- the failure the two-init scheme
// could not see:
// **the common way an init breaks is not dying, it is hanging.** wait() never
// returns for a process that is merely stuck, so a supervisor watching a
// wedged PID 1 -- or PID 1 watching a wedged supervisor -- reports everything
// normal forever.
//
// So each must be heard from. PID 1 sends a PING every interval and expects a
// PONG; silence past the deadline means the other end is stuck rather than
// busy, and stuck is the case worth acting on.
//
// Measured in seconds of uptime(), which counts ticks. Ticks can be *lost*
// when interrupts are off too long -- see kernel/core/time.c -- which makes a
// tick-measured deadline longer in real time than it looks, never shorter.
// That is the safe direction: a machine under load waits longer before
// declaring a hang, rather than declaring one that has not happened.
#define SCHINIT_PING_SECS 2
#define SCHINIT_DEADLINE_SECS 10
struct schinit_hello {
unsigned int op;
unsigned int proto;
unsigned int generation; // how many times PID 1 has been re-exec'd
unsigned int reserved;
};
// A job, flattened. `args` and `envs` index into `strbuf`, which holds
// NUL-terminated strings back to back.
struct schinit_spawn {
unsigned int op;
unsigned int cookie; // echoed in the reply, so the supervisor
// can match a spawn to the job it asked for
char label[SCHINIT_MAXLABEL];
char program[SCHINIT_MAXPATH];
char stdin_path[SCHINIT_MAXPATH];
char stdout_path[SCHINIT_MAXPATH];
char stderr_path[SCHINIT_MAXPATH];
unsigned int own_tty; // claim the terminal as controlling
unsigned int nargs;
unsigned int nenvs;
unsigned short args[SCHINIT_MAXARGS]; // offsets into strbuf
unsigned short envs[SCHINIT_MAXENVV];
char strbuf[SCHINIT_STRBUF];
};
struct schinit_reply {
unsigned int op;
unsigned int cookie;
int pid; // SPAWNED: the child, or negative errno
int status; // EXITED: wait status
};
// The largest message, which is what a reader must have room for. PID 1 reads
// into one of these on its own stack and never anywhere else.
union schinit_msg {
unsigned int op;
struct schinit_hello hello;
struct schinit_spawn spawn;
struct schinit_reply reply;
};
// The descriptor the supervisor inherits. Fixed rather than passed, because
// the supervisor is exec'd by PID 1 and there is nowhere to pass it -- and a
// well-known number is one less thing for the two to disagree about.
#define SCHINIT_SOCK_FD 3
#endif // SCHINIT_H

31
include/uapi/stat.h Normal file
View file

@ -0,0 +1,31 @@
#pragma once
#include "core/types.h"
#define T_DIR 1 // Directory
#define T_FILE 2 // File
#define T_DEV 3 // Device
#define T_SYMLINK 4 // Divesymlink
#define T_EXTENT 5 // bmap & itrunc
// A bound AF_UNIX socket. The node is a *marker*: it is what makes a socket
// findable by path, stat()able and unlink()able, and it holds no data. The
// live binding lives in kernel/net/unix.c and is matched by (dev, inum), not
// by this node's contents -- so a socket file left behind by a crash is
// exactly what Linux leaves behind, a dead file that connect() refuses until
// something binds to it again.
#define T_SOCK 6
struct stat {
int32 type; // File type (T_DIR, T_FILE, or T_DEV)
int32 dev; // File system's disk device ID
uint64 ino; // Inode number
int32 nlink; // Number of hard links to file
uint32 pad; // Padding for 64-bit alignment
uint64 size; // Size of file in bytes
uint rwmode; // Permissions
uint supermode; // Mode
uint showmode; // Visiblity
uint atime; // Last access time
uint mtime; // Last modification time
uint ctime; // Last metadata-change time
uint addrs[12+3]; // Block Addresses
};

170
include/uapi/user.h Normal file
View file

@ -0,0 +1,170 @@
#pragma once
#ifndef USER_H
#define USER_H
#include "core/types.h"
#include "core/date.h"
struct stat;
struct rtcdate;
struct mouse_data;
// --- system calls ---
int fork(void);
void exit(void) __attribute__((noreturn));
int wait(void);
int pipe(int*);
int write(int, void*, int);
int read(int, void*, int);
int close(int);
int kill(int);
int exec(char*, char**); /* execve(path, argv, 0) */
int execve(char*, char**, char**);
int open(char*, int);
int mknod(char*, short, short);
int unlink(char*);
int fstat(int fd, struct stat*);
int link(char*, char*);
int mkdir(char*);
int chdir(char*);
int dup(int);
int dup2(int, int);
int fcntl(int, int, int);
long lseek(int, long, int);
int getpid(void);
char* sbrk(uint64);
int sleep(int);
int uptime(void);
int vga_sync(void*); // legacy no-op syscall; the name is ABI, not this layer
int getmouse(struct mouse_data*);
int reboot(void);
int shutdown(void);
int sigfillset(sigset_t *set);
int sigprocmask(int how, const sigset_t *set, sigset_t *oldset);
int sigwait(const sigset_t *set, int *sig);
int alarm(int seconds);
int waitpid(int pid, int *status, int options);
int setsid(void);
void* mmap(void*, uint64, int, int, int, uint64);
// memfd_create(2). The flags argument exists for MFD_CLOEXEC and MFD_ALLOW_
// SEALING, neither of which this kernel implements; pass 0.
int memfd_create(const char *name, uint flags);
// Shared memory. shmat returns the attach address, or a small negative value
// on failure — there is no errno in this runtime, so check for < 0.
int shmget(int key, uint64 size, int flags);
void* shmat(int id, void *addr, int flags);
int shmdt(void *addr);
int munmap(void*, uint64);
int delete(char*);
int show(char*);
int showstat(char*, struct stat*);
int ftruncate(int, int);
int truncate(char*, int);
int rename(const char*, const char*);
int symlink(char*, char*);
int readlink(char*, char*, int);
int getuid(void);
int geteuid(void);
int nanosleep(void*);
int mprotect(void*, uint64, int);
int ioctl(int, uint64, void*);
// Declared at file scope, not inside the prototype: struct pollfd lives in
// posix.h, and a tag first seen in a parameter list is scoped to that list --
// so the prototype would describe a different, incompatible type.
struct pollfd;
int poll(struct pollfd*, int, int);
// Attributes on a file, and the index over them. Only SchFS carries these;
// anything else answers EOPNOTSUPP.
//
// schfs_query returns how many object ids it wrote, or -1 if the attribute has
// no index -- it will not fall back to walking the filesystem, because a query
// that silently becomes a full scan is worse than one that says it cannot help.
int setxattr(const char *path, const char *name, const void *val, int size,
int flags);
int getxattr(const char *path, const char *name, void *val, int size);
int removexattr(const char *path, const char *name);
int schfs_index(const char *name);
int schfs_query(const char *name, const char *value,
unsigned long long *out, int cap);
char* getcwd(char*, int);
int arch_prctl(int, uint64);
int set_tid_address(int*);
int set_robust_list(void*, int);
int futex(int*, int, int, void*, int*, int);
int clone(uint64, void*, void*, void*, void*);
void exit_group(int) __attribute__((noreturn));
int wait4(int, int*, int, void*);
int getgid(void);
int getegid(void);
int umask(int);
int writev(int, void*, int);
int readv(int, void*, int);
void* brk(void*);
int prlimit(int, int, void*, void*);
int lstat(const char*, struct stat*);
int access(const char*, int);
int openat(int, const char*, int, ...);
int fchdir(int);
int enable_linux_abi(void);
int getrlimit(int, void*);
int setuid(int);
int setgid(int);
int getsid(int);
int dup3(int, int, int);
int pipe2(int*, int);
int clock_gettime(int, void*);
int getppid(void);
int rt_sigaction(int, void*, void*, uint64);
// The syscall, not glibc's wrapper: the argument is a pointer to one 64-bit
// sigset with signal N at bit N-1, which is Linux's layout and not the
// kernel's internal one. Returns -EINTR always, as sigsuspend must.
int rt_sigsuspend(const uint64 *mask);
int mount(const char *src, const char *tgt, const char *fstype, uint64 flags, void *data);
int umount2(const char *tgt, int flags);
int sync(void);
int fsync(int fd);
// Directory reading. The kernel has had getdents64 since the Linux ABI landed,
// but nothing declared it here, so first-party programs had no way to list a
// directory at all -- which is what schinit needs to find its job files.
//
// Records are packed: step by d_reclen, never by sizeof, because d_name is a
// flexible array and each record is only as long as its name needs.
struct dirent64 {
uint64 d_ino;
int64 d_off;
ushort d_reclen;
uchar d_type;
char d_name[];
};
#define DT_DIR 4
#define DT_REG 8
int getdents64(int fd, void *buf, int count);
// Sockets. `struct sockaddr` and the AF_*/SOCK_* constants are in socket.h;
// include it as well as this header to use them.
struct sockaddr;
int socket(int domain, int type, int protocol);
int bind(int fd, const struct sockaddr *addr, int addrlen);
int listen(int fd, int backlog);
int accept(int fd, struct sockaddr *addr, int *addrlen);
int accept4(int fd, struct sockaddr *addr, int *addrlen, int flags);
int connect(int fd, const struct sockaddr *addr, int addrlen);
int socketpair(int domain, int type, int protocol, int sv[2]);
int sendto(int fd, const void *buf, int len, int flags, const struct sockaddr*, int);
int recvfrom(int fd, void *buf, int len, int flags, struct sockaddr*, int*);
int sockshutdown(int fd, int how);
int setsockopt(int fd, int level, int opt, const void *val, int len);
int getsockopt(int fd, int level, int opt, void *val, int *len);
// The only calls that can move a descriptor between processes (SCM_RIGHTS).
struct msghdr;
int sendmsg(int fd, const struct msghdr *msg, int flags);
int recvmsg(int fd, struct msghdr *msg, int flags);
// --- ulib.c ---
int stat(const char*, struct stat*);
char* gets(char*, int max);
#endif // USER_H