System Call Interface Progress

Status: In Progress (Linux process semantics verified from ring 3) Last review: 2026-08-25

Implemented

  • Portable, host-tested handlers (docs/abi/ A2, 2026-09-29). kernel/abi/linux/ reaches the kernel only through include/vibeos/ksvc.h, a services interface written for every personality rather than for Linux; the architecture implements it in kernel/arch/x86_64/ksvc.c and the host tests in tests/kernel/ksvc_fake.c. tests/kernel/linux_abi_tests.c runs the handlers through linux_syscall, and every PARTIAL row of the registry carries a gap expectation that fails until its phase closes it. check-abi-layering.py keeps it so. The details are in docs/abi/phases.md, A2.
  • Descriptors as Linux has them (docs/abi/ A3, 2026-09-30). A descriptor names a counted open file description (include/vibeos/file.h), so dup, dup2, dup3 and fork share its offset and flags; a process has up to 1024 of them, 0-2 included, with FD_CLOEXEC honoured at exec; fcntl (no record locks) and close_range are served. Each file type - regular file, directory, pipe end, socket, console - has its own operations in kernel/abi/files/, which any personality uses. Details in docs/abi/phases.md, A3.
  • Paths (docs/abi/ A4, 2026-09-30). Each process has a working directory and a root; a path is normalised against them and walked through the mount table by kernel/fs/path.c, which every personality shares. chdir, fchdir, getcwd, mkdirat and unlinkat are served, every *at call honours its directory descriptor, exec resolves relative paths, and the dynamic loader is opened at the path PT_INTERP names - the substitution to EFI/BOOT/LDMUSL.SO is gone. Details in docs/abi/phases.md, A4.
  • Layouts and numbers from Linux's headers (docs/abi/ A5, 2026-09-30). The structures the Linux handlers copy to and from user memory, and the constants they compare arguments against, are declared once in include/vibeos/linux_layout.h instead of as byte offsets and bare numbers in each handler. tests/kernel/linux_layout_tests.c compares every field, size, errno value and constant with the host's uapi headers (the C library's for what uapi does not export), and check-linux-layout.py fails the build when a declaration has no comparison. Details in docs/abi/phases.md, A5.
  • IPC, L7 (closed 2026-10-12). Six steps (docs/abi/phases.md), measured against tests/corpus/ltp-l7.txt (0 of 81 on the parent). Steps 1 to 5, the 21 calls in five files under one lock: ipc.c - Linux's numbering (an index handed out cyclically, as since 5.4, and a sequence that moves when the cycle restarts; msg_next_id and its kin), ipcperms, the STAT and INFO commands, message queues by type; sem.c - semop whole or not at all, waiter counts, SEM_UNDO given back at the process's end; shm.c - a segment is an unnamed /tmp file, an attachment a shared mapping whose regions carry the segment's number, so the attach count is read off every process's regions rather than kept; mqueue.c - messages by priority in pages the sender allocates, mq_notify by signal; splice.c - through a kernel page and vibeos_pipe_peek, so a short write loses nothing. /proc takes files from its owner and its tunables are written as Linux's are. The registry's sixteen tables became 32 (a seventeenth was a panic at boot). Step 6: tests/corpus/sysvipc.c, a producer and consumer over SysV shared memory and semaphores and a round trip through each kind of queue, under musl and glibc, compared with Linux; the run found vmsplice waiting to put in everything, where Linux puts in what fits. LTP 64 of 81. Left: SIGEV_THREAD (netlink), a 4 KiB pipe, a sleep of at least a tick, fields x86-64 does not have, shmctl01's fork refused with EAGAIN (unexplained). Open and not L7's: THREADS.ELF's C6_SCHED failed once in about eighteen boots, a pinned child not moved to its core in three seconds; the stage now prints where it was.
  • Threads and scheduling, L6 (closed 2026-10-12). Four steps (docs/abi/phases.md), measured against tests/corpus/ltp-l6.txt (16 of 67 on the parent). Step 1: futex finished - bitsets, requeue, WAKE_OP on a fault-tolerant user compare-exchange (vibeos_uaccess_cmpxchg32), the futex2 calls with a sleeper on up to 128 words, robust lists walked at exit; THREADS.ELF's C6_ROBUST and C6_COND at boot. Step 2: the sched_* family over a REALTIME class added to the scheduler's policy (priority before virtual time; the torture's model learned it), affinity over the pickers' mask, getcpu, sched_rr_get_interval, ioprio, membarrier through the shootdown IPI; C6_SCHED. Step 3: clone makes a process on its parent's memory - posix_spawn and vfork work - with CLONE_VFORK, CLONE_PARENT and the tid words for a forking clone, and the task table is 64 slots; C6_VFORK. Step 4: a thread pool under musl and glibc in the corpus, compared with Linux, which found three defects: a fatal signal ended one thread rather than the process (now hw_task_kill_group, C6_ABORT), the TLB quarantine leaked frames when full (a shootdown releases them), and the mm lock's waiters did not answer a flush under TCG. LTP 53 of 67; what is left needs more than 64 tasks, SCHED_DEADLINE, or another phase.
  • Sockets, L5 (closed 2026-10-06). Four steps (docs/abi/phases.md). Steps 1 to 3: a socket type carries a neutral table of socket operations (include/vibeos/sockops.h) - bind, listen, connect, accept, sendmsg, recvmsg, shutdown, names, options - filled by IP (kernel/abi/files/socket.c, over a stack that learned addressed binds, EADDRINUSE and SO_REUSEADDR, ephemeral ports, connected UDP, MSG_PEEK and MSG_TRUNC) and by local sockets (kernel/abi/files/unixsock.c: stream and datagram, a socket node in the filesystem through a new VFS mknod or a name in the abstract namespace, SO_PEERCRED, SCM_RIGHTS carrying A3's counted descriptions); the options every socket keeps alike live in the description (sockopt.c); kernel/abi/linux/net.c only translates sockaddr, msghdr, cmsg and mmsghdr. readv and writev on a socket are one receive or send. Step 4: BusyBox wget against the host, a DNS lookup through musl's resolver and QEMU's, and SOCKETS.ELF's checks - a Unix-socket server and client among them - at boot (tests/corpus/run-l5.sh). Its LTP run found there was no loopback: the stack has one now (a ring the timer's poll feeds back to the input path), and a SYN to a closed port is reset; its boots found that a half-closed connection was reclaimed under an open descriptor (L4's nc check had been green on timing), and it waits for the close now. LTP for the phase 44 of 64 (26 before the fixes); the rest network namespaces, IPv6/SCTP/RDS, SysV semaphores, O_PATH, MSG_MORE corking.
  • Event loops, L4 (closed 2026-10-06). The plan, in seven steps (docs/abi/phases.md): one readiness engine for poll, ppoll, select and pselect6, with sockets saying what they can do; eventfd; timerfd; signalfd; epoll; inotify; and the programs - BusyBox httpd serving from the guest, nc, tail -f - with LTP's 78 tests for L4's calls (tests/corpus/ltp-l4.txt). No wait queue: every wait here looks, gives up the core and looks again, and the event loops ask each description's ready(). Step 1 (2026-10-05): poll, ppoll, select and pselect6 on one engine (kernel/abi/linux/poll.c), sockets that say what they can do (vibeos_inet_ready), a wait never shorter than asked, and EVENTS.ELF at boot. Steps 2 to 4 (2026-10-05): eventfd (a counter in the description, changed by compare-exchange), timerfd (a deadline and a period counted by whoever looks - nothing fires it) and signalfd (pending signals taken as Linux's records), each a file type with a ready() and a pipe's wait; LTP 16 of 20, the rest a time namespace, libaio and a fuzzy-sync race that needs L6. Step 5 (2026-10-05): epoll (kernel/abi/linux/epoll.c) - poll with a set that outlives the call: entries belong to the description and go with its last reference (vibeos_file_on_release), level, edge (a rise in ready), one-shot, nested up to five, ELOOP for a cycle; LTP 16 of 25, then epoll_ctl04 fixed (the nesting chain counted only below), the rest socketpair (L5), listen unbound and F_SETPIPE_SZ. Step 6 (2026-10-05): inotify (kernel/abi/linux/inotify.c) on vibeos_fs_notify, which the file layer calls once per operation on a name and the regular-file type for open, read, write and close; a watch names a node. Its LTP run found that a process renaming its own working directory lost it (cwd is a path here): a renamed directory now takes working directories, roots and open descriptions along. Step 7 (2026-10-06): the programs at boot (tests/corpus/run-l4.sh) - tail -f, BusyBox httpd fetched from the host through a forwarded port, nc both ways - with setsockopt (SO_REUSEADDR, SO_KEEPALIVE) and shutdown taken from L5; the TCP flush no longer counts a FIN as data (it looped for ever with the network lock held), and a wait on a socket hears a signal. LTP for the phase 58 of 83.
  • Processes, credentials and time, L2 (closed 2026-10-04). All seven steps. Step 1: a process has real, effective, saved and filesystem ids and supplementary groups (include/vibeos/cred.h, kernel/sched/cred.c); the sixteen get and set calls; and L1's file operations ask who is asking - search permission in the walk, read/write on open, the directory's permission and the sticky bit for names, ownership for chmod/chown/times, with access answering for the real user. Registry: 143 done, 23 partial. LTP's L1 list after it: 154 passed (131 before it), 16 failed, 144 broken, 65 not applicable, 2 did not run. What is left is mostly a loop device, alarm, and files under /proc and /dev. Step 2: signals as Linux delivers them - a siginfo_t and ucontext frame with the vector registers, read back by rt_sigreturn; a fault given to a handler that asked for it; sigaltstack, rt_sigpending, rt_sigsuspend, pause, rt_sigtimedwait, rt_sigqueueinfo, rt_tgsigqueueinfo; SIGCHLD when a child ends. LTP after it: not measured in this step. The first test of the signal list, pause01, waits for its child to show as sleeping in /proc//stat (step 6), and LTP's own timeout is setitimer (step 3), so the first test that waits forever takes the rest of its boot: 3 of 48 reported (kill08 passed; kill05 needs System V shared memory; kill06 failed with ESRCH killing a process group, not yet looked into). Measured again with step 3, when the timeout works. Step 3: timers - alarm, the three interval timers and POSIX timers on the machine's and the CPU's clocks, fired by the tick from a portable table (kernel/sched/ptimer.c); clock_getres, gettimeofday, times. LTP after it: the timer tests and step 2's signal tests together (tests/corpus/ltp-l2.txt's subset, 70 tests): 37 passed, 1 failed (times03: no system time), 24 broken, 7 did not run (kill10 takes the rest of its boot: step 5). Before this step the same list ran 3 of 48. Of the broken: /proc//stat and /proc/cpuinfo (step 6), setrlimit (step 4), clock_settime refused, System V shared memory, signal 64, and LTP's musl restorer. Step 4: resource limits in the process state, NOFILE, FSIZE, DATA, NPROC and CPU enforced; getrusage, getpriority/setpriority, personality - and the scheduler's policy, never initialised on the machine since it was written, now runs (M-084). LTP after it: the limit tests (22): 17 passed; the rest need 512 MB free (getrusage03), /proc/cpuinfo (getrusage04, step 6), sched_getaffinity (nice05, L6), select (personality02), /bin/true on the volume (setrlimit04), and useradd (setpriority01, which then waits out its timeout - not looked into). With step 3's 70 alongside: 54 of 94 passed. Step 5: one engine under wait4 and waitid (by pid, group or pidfd; WNOWAIT), pidfds (kernel/abi/files/pidfd.c), clone3 and execveat. LTP after it: the step's own 41 (build-gcc-Release/ltp-l2s5.txt): 12 passed, 2 failed, 26 broken, 1 not applicable. It found two defects in this step's clone3 - the exit signal a caller names is ignored and SIGCHLD sent instead (clone301), and five of the argument checks Linux makes are missing (clone302) - and two in older layers that decide most of the rest. Eight tests waited out their timeout; waitid04, the one read, passed its checks and then hung in LTP's checkpoint, a futex shared between processes through a MAP_SHARED page, which the futex table keys per process. And an orphan is never given to init here, so the children of a test killed by its timeout stay zombies nobody reaps: the task table filled, and every fork after that was EAGAIN (seven tests). All three are fixed in the next commits. The rest want /proc (step 6), unshare, cgroups, a loop device, core dumps, ns_last_pid, or LTP resources that are not staged (execveat01, execveat02). Step 6: /proc about processes (self, /stat, status, cmdline, maps, fd/, exe; cpuinfo, mounts), /dev (null, zero, full, random, urandom, tty), a ChaCha20 pool behind getrandom; and the three defects step 5's LTP found - a futex shared through a MAP_SHARED page, orphans never adopted by init, clone's exit signal. LTP after it: 41 of 79 passed (ltp-l2s6.txt), step 5's 41 alone 23 (12 before); it found orphaned process groups never sent SIGHUP and SIGCONT, which step 7 takes. Step 7, the programs: eight BusyBox workloads more in the corpus - traps, kill and wait, timeout, time, ps, /proc, /dev, limits - compared with Linux; kill's session rule replaced by Linux's (BusyBox's timeout); a stop holds until SIGCONT or SIGKILL; orphaned process groups with a stopped member hung up; /proc/ gives the leader's state, and /proc/ and task/ exist. LTP for L2's own 188: 106 passed, none left unrun.
  • Memory, L3 (closed 2026-10-02). Step 6, the programs: SQLite's shell and Lua built against glibc and dynamically linked run with the host's own ld-linux-x86-64.so.2 and libc.so.6 staged on the boot volume; SQLite runs in write-ahead-log mode with mmap_size set, which needs a shared mapping of a file. Eighteen corpus workloads, compared with Linux's answers. LTP for L3's syscalls: 31 of 63 pass, with two left out because they hang (mincore04, mmap18). It found three more wrong answers, fixed: MAP_SHARED_VALIDATE did not refuse an unknown flag, MAP_LOCKED was ignored, and msync(MS_INVALIDATE) of a locked page was not EBUSY. Not fixed and said: a fault in ring 3 kills the task instead of delivering SIGSEGV or SIGBUS to a handler that asked for it (mmap05, mmap13, and why mmap18 hangs); mincore03 expects an untouched page not to be resident; mremap01 aborts after mremap refuses to grow a shared mapping of a file - most likely in its own cleanup, which was not confirmed. The rest want setrlimit, /proc/self/maps and status, shmget, seals, mount or a loop device. Steps 1 to 5: Step 4: madvise (a discarded private page reads zeros; speed advice is a no-op; advice this kernel cannot keep is EINVAL), mincore, mlock/mlock2/munlock/mlockall/munlockall (a mark in the entry that page-out refuses), mremap (shrink, grow in place, move - entries moved, not copied) and memfd_create (a /tmp file that goes with its last descriptor). Registry: 132 done, 24 partial; the four new partial rows name their gaps. Step 3: LTP's 382 tests for L1's syscalls run through scripts/dev/ltp-run.sh (131 passed, 10 failed, 177 broken, 57 not applicable, 7 did not run). To start at all they needed /proc (kernel/fs/procfs.c: meminfo, sys/kernel/pid_max), /etc/passwd and /etc/group, nanosleep/clock_nanosleep, and interrupted calls run again under SA_RESTART. They found and this fixed: F_GETLK's choice of lock, an unjudged read of a path's first byte (M-082), ENOENT for an empty path, EINVAL and EISDIR in preadv, EFBIG in fallocate, the description pool, socket's errno. What is left is mostly L2's: credentials, alarm, /dev. Registry: 127 done, 20 partial. Step 2: MAP_SHARED - a shared page is marked in its entry and a fork maps the same frame into the child instead of making it copy-on-write; a shared mapping of a file maps the filesystem's own pages (share_page, tmpfs only; FAT answers ENODEV), so the mapping and read/write are one memory; msync validates and returns; a shared page is never paged out. Registry: 125 done, 20 partial. Step 5, taken early: the user stack grows - four pages at exec, a zeroed page for any address in the two megabytes below that the program or the kernel reaches, a guard page under them; the range check accepts an untouched stack page; the gate asserts stack_grown and a ring-3 self-test with a hundred-kilobyte frame. It replaced the 64 pages L1 step 8 mapped at exec, which had made the open swap defect M-070 frequent enough to make the gate flaky. Step 1: mmap places a mapping at a fixed address (MAP_FIXED, MAP_FIXED_NOREPLACE), honours a free hint, steps its arena over what is already mapped, and maps a file privately - the file's bytes in the process's own pages. The fake kernel gained real page tables (kernel/mm over static memory), so the memory handlers are host-tested for the first time. Shared mappings are step 2, and are what LTP's harness waits for. On the way: a race in fork - a read-only page evicted between fork's read of its entry and fork's reference - found and fixed in kernel/mm/vmspace.c; and M-070, the swap-slot double release, seen three times in about sixty boots since L1 step 8 and still open, now with instrumentation that says why a page-in failed.
  • Files and paths, L1 (closed 2026-10-01, LTP pending L3). All eight steps. Step 8: tests/corpus/run-l1.sh runs fifteen corpus workloads in the guest - twelve BusyBox ones, SQLite on a file and in memory, a Lua script - and the gate compares every line with what the same script printed on Linux (tests/corpus/l1-expected.txt, written by scripts/dev/corpus-expect.sh). Running them found four defects outside the step's syscalls: execve refused /proc/self/exe (how BusyBox's shell runs its applets), a forked child kept the slot's previous program path, the user stack was 16 KiB (now 256, still not growing), and FAT files were reported non-executable. LTP is staged by scripts/dev/ltp-run.sh and every test breaks at its harness's mmap(MAP_SHARED) of a file: L3. Step 7: the console is a terminal (include/vibeos/tty.h): modes in Linux's numbering, ICANON, ECHO, ICRNL and ONLCR honoured, a line discipline that waits for a finished line or hands over bytes as typed; TCGETS, the TCSETS forms, the window size, and FIOCLEX/FIONCLEX/FIONBIO/FIONREAD. A terminal made BusyBox's shell interactive, which needed poll (minimal, PARTIAL, L4's) and a writev gathered into one write. Found on the way: the native shell's write had made an empty file since step 4 and nothing asserted its contents - now notes_file_not_read_back. Step 6: advisory locks in a personality-neutral table (kernel/fs/filelock.c) - fcntl's record locks owned by a process, the OFD forms and flock owned by a description, EDEADLK for a wait that could not end, POSIX's close-drops-everything rule - tortured nightly against a model; directories read by position through one readdir operation and formatted by the personality, so getdents64 lists whole names with their real type and inode and getdents is the same reader in the older record; the twelve xattr calls; close_range's UNSHARE for a single-threaded process. 124 done, 19 partial. Step 5: names and metadata in kernel/abi/linux/names.c - stat/lstat/statx, the access family, rename*, rmdir, link*, symlink*, readlink, the chmod, chown and utime families, statfs/fstatfs, mknod*, openat2 - thirty-two rows, the registry at 108 done and 21 partial. What Linux decides before a filesystem is asked (EXDEV, EBUSY for a mount's root, a directory into itself) is decided in the handler; st_dev is the mount, so two filesystems are two devices; the shell's mv, ln, chmod and stat are gated at boot. Step 4: the FAT driver (kernel/fs/fat.c, out of the architecture) writes at an offset, truncates, creates long names, renames, removes directories and grows them; a node is identified by where its directory entry sits; the 512-byte whole-file path in kernel/abi/files/regular.c is deleted and pwrite64, pwritev, truncate, ftruncate are done (79 done, 18 partial). Proved by host tests, by mtools reading a volume the driver worked on (scripts/dev/verify-fat-mtools.sh), by scripts/dev/fat-fsck.py, and at boot: the shell writes on the boot volume and the gate checks the image once the machine has stopped. (Step 3: writes through a descriptor at an offset, O_CREAT/O_EXCL/O_TRUNC/O_APPEND/O_RDWR, the umask, and twenty rows from pread64 to copy_file_range; the registry is at 75 done and 22 partial). Steps 1 and 2: the filesystem interface carries what stat reports and has write-at-offset, truncate, create, rmdir, rename, link, symlink, readlink, setattr, statfs and sync as optional operations; vibeos_path_walk follows symbolic links; and kernel/fs/tmpfs.c is a complete in-memory filesystem at /tmp, host-tested and tortured nightly against a model. The syscalls themselves come from step 3 on. Details in docs/abi/phases.md, L1.
  • Real on-metal ring-3 -> ring-0 syscall entry (kernel/arch/x86_64/arch_hw.c + isr.S), two paths into one Linux-ABI dispatcher (vibeos_x86_64_linux_syscall):
    • Native syscall/sysret (the real Linux x86-64 fast path): EFER.SCE enabled, STAR/LSTAR/SFMASK programmed, and an LSTAR trampoline that switches to a kernel stack, marshals the Linux ABI (nr in rax; args in rdi/rsi/rdx) into the C ABI, dispatches, and sysretqs back to ring 3.
    • int 0x80 gate (DPL 3) as a legacy/bring-up path into the same dispatcher.
    • User GDT segments ordered data-then-code (for SYSRET) + a TSS with RSP0.
    • Linux syscall translation covers the runtime subset used by the user programs, including console I/O, process lifecycle and FAT-backed file operations; unsupported calls return -ENOSYS and are named in the serial log, so a missing number is visible rather than silent.
    • The opening sequence of a C runtime. A static libc runs a fixed set of syscalls before it reaches main and dies if any of them fails. Implemented: arch_prctl (ARCH_SET_FS/ARCH_GET_FS), set_tid_address, set_robust_list, gettid, ioctl, writev, readv, uname, clock_gettime, prlimit64, futex, sched_yield, rt_sigprocmask, rt_sigaction, and the getuid/getgid family. rseq and getrandom return -ENOSYS deliberately: both have mandatory fallbacks, and a fake success would be worse than a refusal.
    • Thread-local storage is real. arch_prctl(ARCH_SET_FS) writes MSR_FS_BASE, the value is per task, and it is reloaded on every context switch - without that, one program reads another's thread state. A base outside user memory is refused before the write, because a non-canonical value would fault in ring 0. Verified from ring 3 by reading the TLS self-pointer back through %fs:0 after the task has been preempted and rescheduled many times, not by checking that the syscall returned zero.
    • mmap, mprotect and munmap act on the page tables. mprotect grants and revokes write permission on the real entries and flushes the TLB; munmap unmaps and frees the frames, so a use-after-unmap faults here as it would on Linux. MAP_FIXED and file-backed mappings are refused rather than quietly ignored - a program that asks for a specific address and silently gets another one corrupts itself later, far from the call.
    • What a program does once it is running, beyond the startup sequence: fstat, newfstatat, openat, getcwd, readlinkat, prctl, setuid/setgid, getppid, time, and clone for the flags that mean fork. These are the calls that separate "a libc starts" from "a program works": BusyBox reaches the filesystem through openat, fstat, read and getdents64, and the boot fails on busybox_file_operations_failed if any of them regresses.
    • Signal delivery is implemented, not stubbed. rt_sigaction and rt_sigprocmask install real dispositions and masks; kill, tkill and tgkill raise; a raise sets a pending bit and wakes a task blocked in read(). Delivery happens on the way back to user space, where the process's own stack exists: the frame goes below the 128-byte red zone, aligned as a call site, with the C library's SA_RESTORER trampoline as the return address. rt_sigreturn restores that frame but forces cs, ss and rflags back to safe values and checks a magic word first, because the frame is user-writable and nothing read out of it may decide privilege. SIGKILL and SIGSTOP are neither catchable nor blockable. Linux sigset_t bits are numbered from zero (bit 0 is signal 1) and are converted only in hw_sigset_from_user/hw_sigset_to_user. A wait status is not an exit code: exit code in the high byte, signal in the low seven bits. Gated - the boot fails on signal_delivery_broken if tests/linux/musl_signal.c, built by musl-gcc against a real libc, does not report SIG_OK.
    • Pipes and descriptor plumbing: pipe, pipe2, dup, dup2, inheritance across fork with the pipe's reader/writer counts adjusted, release on exit, and SIGPIPE plus EPIPE for a write with no reader. A pipe outlives its last writer so a reader can drain it. dup2 onto 0, 1 or 2 records a standard-descriptor redirection, since the console is not an entry in the descriptor table. Gated: ls /EFI/BOOT | wc -l runs in BusyBox's shell and the boot fails on pipeline_did_not_complete.
    • Verified in QEMU (GCC and Clang): a real user ELF is loaded (parsed by the portable, host-tested kernel/core/elf.c), run in ring 3, and prints via a native syscall, then exit returns cleanly to the kernel. This hardware front end will route into the portable dispatcher and the Linux personality (user/compat/linux) next.
  • Central syscall dispatcher in kernel/core/syscall.c (portable model).
  • ABI helper mappings in include/vibeos/syscall_abi.h.
  • Rights/policy lookup layer in kernel/core/syscall_policy.c.
  • Process/thread lifecycle syscall family.
  • Scheduler observability and runtime-control syscall family.
  • Waitset ownership/stats/wake-policy syscall paths.
  • Security/audit/policy control syscalls with kernel-vs-user authorization checks.
  • ABI version/compatibility syscalls (VIBEOS_SYSCALL_ABI_VERSION_GET, VIBEOS_SYSCALL_ABI_COMPAT_CHECK) with compatibility helpers in syscall_abi.h.
  • The on-metal dispatcher validates user pointers before dereference for the implemented Linux-ABI subset.

Pending

  • nanosleep and poll. A shell that waits on several descriptors at once needs poll; the pipeline path currently blocks on one at a time.
  • clone with CLONE_VM/CLONE_THREAD returns -ENOSYS, and real futexes belong with real threads rather than before them.
  • Signal gaps: no SA_SIGINFO siginfo_t, no alternate signal stack (sigaltstack), no rt_sigsuspend/rt_sigpending, and a handler installed without SA_RESTORER cannot be entered at all - the process is terminated instead of being given a kernel-supplied trampoline. A three-argument SA_SIGINFO handler is entered with its second and third arguments zeroed.
  • openat accepts only AT_FDCWD and there is no per-process working directory, so relative paths resolve against the volume root.
  • sendfile is refused; callers are expected to take their read-and-write fallback.
  • Copy-on-write statistics are printed ([MM] COW_STATS) but no gate asserts them.
  • An entropy source, so getrandom can answer and AT_RANDOM can carry something better than nothing.
  • Stronger multi-tenant authorization model beyond relation-based checks.
  • Syscall ABI versioning strategy with compatibility negotiation.
  • Per-syscall latency telemetry and regression thresholds.
  • Consolidate the hardware Linux-ABI dispatcher and portable syscall policy dispatcher behind one audited authorization boundary.

Next checkpoint

  • poll and nanosleep, so a shell can wait on more than one descriptor and a program can sleep without spinning.
  • Consolidate the runtime and portable dispatch paths, then add negative pointer/permission tests for every exposed syscall.