SchOS/include/user.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

156 lines
No EOL
5.2 KiB
C

#pragma once
#ifndef USER_H
#define USER_H
#include "types.h"
#include "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);
// 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);
int mount(const char *src, const char *tgt, const char *fstype, uint64 flags, void *data);
// 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);
// --- ulib.c ---
int stat(const char*, struct stat*);
char* gets(char*, int max);
#endif // USER_H