SchOS/kernel/sys/table.inc
Nexoniarz a79dfac32b memfd: a shared memory object you reach by descriptor
Gate 1, item 2, and the thing a Wayland buffer crosses on.

memfd_create(2) is the same segment machinery shmget() already had -- pages,
refcounts, no_free VMAs, the fork and exit accounting -- reached by
**descriptor** rather than by System V key. That difference is the entire
point and the reason it exists on Linux at all: two processes share memory by
passing the fd over a socket, so neither has to agree on a path or race to
invent a key. It is also why SCM_RIGHTS had to be item 1.

docs/plan/foundations.md proposed exactly this and it was right: what memfd
adds on top of shmget is a segment that starts empty and is sized by
ftruncate(), and a file that owns one reference to it. FD_MEMFD, and
read/write/lseek/fstat all learn about it -- a descriptor that refuses read()
is a surprise nobody needs.

Three things are refused rather than faked, each the honest version of
something this kernel cannot do:

  - MAP_PRIVATE on a memfd. Every caller is sharing on purpose; a private
    mapping of an anonymous object is an expensive MAP_ANONYMOUS.
  - Mapping past the end. Linux faults those pages in; there is no handler
    here that could.
  - Shrinking while mapped. Linux truncates the pages out from under the
    mapper; here a freed page would stay mapped to memory somebody else now
    owns.

Writable MAP_SHARED of a *real file* is still ENODEV. That needs a page cache,
and an anonymous object has no such contract to break -- which is precisely
why memfd is the narrow answer.

**cmd/memfdtest**, seventeen checks, written so a permissive implementation
cannot pass: an mmap that quietly returned private zeroed pages satisfies every
return code and fails the one assertion that matters, because the reader is a
different process. It ends with the combination the plan singled out -- a memfd
sent over a socketpair to a receiver that closes every inherited copy first,
maps only the descriptor that arrived, reads what the sender wrote and writes
back where the sender can see it.

Two things were found by writing that test, both of which had been refusing
everything that was not FD_INODE:

  - sys_fstat() never reached filestat(), so the memfd branch there was
    unreachable from the syscall that needs it. It also refused pipes and
    sockets, and returned -1 (which musl reads as EPERM) rather than an errno.
  - sys_lseek() the same, and SEEK_END needs a length that is not in an inode.

Verified: 40 consecutive runs leave MemFree at exactly the value it started
at -- 936656 kB before and after -- so nothing leaks across creation, dual
mapping, descriptor passing and exit.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-22 09:47:31 +02:00

304 lines
11 KiB
SQL

// Segregated Kernel -- central syscall table. ONE line per syscall.
//
// Format: SYSCALL(nr, linux_nr, name)
// nr SchOS native syscall number
// linux_nr Linux x86_64 syscall number, or NO_LINUX if none
// name implemented as sys_<name>() in kernel/sys/sys_*.c
//
// To add a syscall:
// 1. Add ONE line here.
// 2. Write the body with SYSCALL_DEFINE(<name>) in a kernel/sys/sys_*.c.
//
// Everything else is generated from this file: the SYS_* constants
// (include/syscall.h), the dispatch table (kernel/sys/syscall.c), and the
// Linux ABI translation map (kernel/sys/linux_abi.c). The Linux number used
// to live in a second hand-maintained table in linux_abi.c; it does not any
// more, so a syscall can no longer be half-registered.
//
// A syscall reachable under more than one Linux number (e.g. stat/newstat)
// gets extra SYSCALL_ALIAS(linux_nr, name) lines.
// --- process ---
SYSCALL( 1, 57, fork)
SYSCALL( 2, 60, exit)
SYSCALL( 3, NO_LINUX, wait)
SYSCALL( 6, 62, kill)
SYSCALL( 7, 59, exec)
SYSCALL( 11, 39, getpid)
SYSCALL( 13, NO_LINUX, sleep)
SYSCALL( 14, NO_LINUX, uptime)
SYSCALL( 29, NO_LINUX, waitpid)
SYSCALL( 30, 112, setsid)
SYSCALL( 31, NO_LINUX, reboot)
SYSCALL( 32, NO_LINUX, shutdown)
SYSCALL( 66, 56, clone)
SYSCALL( 67, 231, exit_group)
SYSCALL( 68, 61, wait4)
SYSCALL( 83, 105, setuid)
SYSCALL( 84, 106, setgid)
SYSCALL( 85, 124, getsid)
SYSCALL( 89, 110, getppid)
SYSCALL( 91, 121, getpgid)
SYSCALL( 94, 186, gettid)
SYSCALL( 92, 109, setpgid)
SYSCALL( 93, 111, getpgrp)
// --- identity ---
SYSCALL( 47, 102, getuid)
SYSCALL( 48, 107, geteuid)
SYSCALL( 69, 104, getgid)
SYSCALL( 70, 108, getegid)
SYSCALL( 71, 95, umask)
// --- memory ---
SYSCALL( 12, NO_LINUX, sbrk)
SYSCALL( 35, 9, mmap)
SYSCALL( 36, 11, munmap)
// Shared memory. These carried NO_LINUX while they were ENOSYS stubs, which
// meant a musl-linked program calling shmget() never reached them at all --
// it got "bad syscall" from the ABI translation rather than an answer. Now
// that they do something, they need the numbers programs actually use.
SYSCALL( 37, 29, shmget)
SYSCALL( 38, 30, shmat)
SYSCALL( 39, 67, shmdt)
SYSCALL( 59, 10, mprotect)
SYSCALL( 74, 12, brk)
// --- filesystem ---
SYSCALL( 4, 22, pipe)
SYSCALL( 5, 0, read)
SYSCALL( 8, 5, fstat)
SYSCALL( 9, 80, chdir)
SYSCALL( 10, 32, dup)
SYSCALL( 15, 2, open)
SYSCALL( 16, 1, write)
SYSCALL( 17, 133, mknod)
SYSCALL( 18, 87, unlink)
SYSCALL( 19, 86, link)
SYSCALL( 20, 83, mkdir)
SYSCALL( 21, 3, close)
SYSCALL( 24, 8, lseek)
SYSCALL( 33, 33, dup2)
SYSCALL( 34, 72, fcntl)
// `delete` -- the recycle bin. It hides a file instead of removing it, so the
// blocks are never freed and a program that deletes a temp file gets no space
// back. Inherited from the third-party filesystem in kernel/fs/fs.c and
// orphaned by the decision to keep only Linux syscall numbering, so nothing
// reaches it. Its effect on read and write has been removed; the entry stays
// only because sys_delete() is still compiled, and both die with fs.c.
SYSCALL( 51, NO_LINUX, delete)
SYSCALL( 52, NO_LINUX, show)
SYSCALL( 53, NO_LINUX, showstat)
SYSCALL( 54, 77, ftruncate)
SYSCALL( 55, 76, truncate)
SYSCALL( 56, 82, rename)
SYSCALL( 57, 88, symlink)
SYSCALL( 58, 89, readlink)
SYSCALL( 61, 79, getcwd)
SYSCALL( 73, 19, readv)
SYSCALL( 72, 20, writev)
SYSCALL( 76, 4, stat)
SYSCALL( 77, 6, lstat)
SYSCALL( 78, 21, access)
SYSCALL( 79, 257, openat)
SYSCALL( 80, 81, fchdir)
SYSCALL( 86, 292, dup3)
SYSCALL( 87, 293, pipe2)
// --- device / I/O ---
SYSCALL( 22, NO_LINUX, vga_sync)
SYSCALL( 23, NO_LINUX, getmouse)
SYSCALL( 45, 23, select)
SYSCALL( 46, 7, poll)
SYSCALL( 60, 16, ioctl)
// --- sockets ---
//
// These carried NO_LINUX while they were ENOSYS stubs, which meant a
// musl-linked program calling socket() never reached them at all -- the ABI
// translation answered "bad syscall" before the stub could answer ENOSYS.
// Now that they do something they need the numbers programs actually use.
//
// `shutdown` is deliberately not the name here: SchOS already has a native
// shutdown() that powers the machine off (syscall 32), and two syscalls
// cannot share a sys_ function name. Linux 48 maps to sockshutdown instead.
SYSCALL( 40, 41, socket)
SYSCALL( 41, 49, bind)
SYSCALL( 42, 50, listen)
SYSCALL( 43, 43, accept)
SYSCALL( 44, 42, connect)
SYSCALL(187, 44, sendto)
SYSCALL(188, 45, recvfrom)
SYSCALL(189, 48, sockshutdown)
SYSCALL(190, 53, socketpair)
SYSCALL(191, 54, setsockopt)
SYSCALL(192, 55, getsockopt)
// accept4() differs from accept() only by a flags argument carrying
// SOCK_CLOEXEC/SOCK_NONBLOCK. It was an alias for accept, which silently
// dropped both -- and it cannot be an alias now that they are honoured: accept
// takes three arguments, so reading a fourth register from an accept() caller
// reads whatever it last left there and invents a flag out of stack litter.
SYSCALL(198, 288, accept4)
// sendmsg/recvmsg exist for the one thing only they can do: carry a descriptor
// from one process to another (SCM_RIGHTS). docs/plan/foundations.md item 1.
SYSCALL(199, 46, sendmsg)
SYSCALL(200, 47, recvmsg)
// memfd_create: an anonymous shared memory object reached by descriptor.
// docs/plan/foundations.md item 2 -- the thing a Wayland buffer crosses on,
// and the reason the descriptor passing above had to exist first.
SYSCALL(201, 319, memfd_create)
// --- time ---
SYSCALL( 28, 37, alarm)
SYSCALL( 49, 96, gettimeofday)
SYSCALL( 50, 35, nanosleep)
SYSCALL( 88, 228, clock_gettime)
// --- signals ---
SYSCALL( 25, NO_LINUX, sigfillset)
SYSCALL( 26, 14, sigprocmask)
SYSCALL( 27, NO_LINUX, sigwait)
SYSCALL( 90, 13, rt_sigaction)
// --- POSIX / Linux-ABI compat ---
SYSCALL( 62, 158, arch_prctl)
SYSCALL( 95, 63, uname)
SYSCALL( 96, 217, getdents64)
SYSCALL( 63, 218, set_tid_address)
SYSCALL( 64, 273, set_robust_list)
SYSCALL( 65, 202, futex)
SYSCALL( 75, 302, prlimit)
SYSCALL( 81, NO_LINUX, enable_linux_abi)
SYSCALL( 82, 97, getrlimit)
// --- signal extensions ---
SYSCALL( 97, 15, rt_sigreturn)
SYSCALL( 98, 131, sigaltstack)
SYSCALL( 99, 127, rt_sigpending)
SYSCALL(100, 128, rt_sigtimedwait)
SYSCALL(101, 130, rt_sigsuspend)
// --- process extensions ---
SYSCALL(102, 157, prctl)
SYSCALL(103, 234, tgkill)
SYSCALL(104, 200, tkill)
SYSCALL(105, 34, pause)
SYSCALL(106, NO_LINUX, getitimer)
SYSCALL(107, NO_LINUX, setitimer)
SYSCALL(108, 98, getrusage)
SYSCALL(109, 100, times)
SYSCALL(110, 99, sysinfo)
SYSCALL(111, 24, sched_yield)
SYSCALL(112, 204, sched_getaffinity)
SYSCALL(113, 203, sched_setaffinity)
SYSCALL(114, 247, waitid)
// --- identity extensions ---
SYSCALL(115, 113, setreuid)
SYSCALL(116, 114, setregid)
SYSCALL(117, 118, getresuid)
SYSCALL(118, 120, getresgid)
SYSCALL(119, 117, setresuid)
SYSCALL(120, 119, setresgid)
SYSCALL(121, 115, getgroups)
SYSCALL(122, 116, setgroups)
SYSCALL(123, 122, setfsuid)
SYSCALL(124, 123, setfsgid)
// --- filesystem extensions ---
SYSCALL(125, 90, chmod)
SYSCALL(126, 91, fchmod)
SYSCALL(127, 92, chown)
SYSCALL(128, 93, fchown)
SYSCALL(129, 94, lchown)
SYSCALL(130, 84, rmdir)
SYSCALL(131, NO_LINUX, fsync)
SYSCALL(132, NO_LINUX, fdatasync)
SYSCALL(133, 162, sync)
SYSCALL(134, 17, pread)
SYSCALL(135, 18, pwrite)
SYSCALL(148, 132, utime)
SYSCALL(149, 235, utimes)
SYSCALL(150, 261, utimensat)
SYSCALL(151, 187, sendfile)
SYSCALL(152, NO_LINUX, preadv2)
SYSCALL(153, NO_LINUX, pwritev2)
SYSCALL(154, 285, fallocate)
SYSCALL(155, 137, statfs)
SYSCALL(156, 138, fstatfs)
SYSCALL(157, 161, chroot)
// --- attributes, and the index over them (SchFS phase 5) ---
//
// Linux's numbers for the xattr calls, so a program that already knows how to
// set an extended attribute needs no changes. schfs_query has no Linux
// equivalent -- it is the one genuinely new call -- so it is native-only.
SYSCALL(193, 188, setxattr)
SYSCALL(194, 191, getxattr)
SYSCALL(195, 197, removexattr)
SYSCALL(196, NO_LINUX, schfs_index)
SYSCALL(197, NO_LINUX, schfs_query)
// --- memory extensions ---
SYSCALL(136, 28, madvise)
SYSCALL(137, NO_LINUX, mremap)
SYSCALL(138, NO_LINUX, msync)
SYSCALL(139, NO_LINUX, mincore)
SYSCALL(140, 149, mlock)
SYSCALL(141, 150, munlock)
SYSCALL(142, 151, mlockall)
SYSCALL(143, 152, munlockall)
SYSCALL(144, 221, fadvise64)
SYSCALL(145, 160, setrlimit)
SYSCALL(146, NO_LINUX, getpriority)
SYSCALL(147, NO_LINUX, setpriority)
// --- at-syscalls ---
SYSCALL(158, 258, mkdirat)
SYSCALL(159, 259, mknodat)
SYSCALL(160, 263, unlinkat)
SYSCALL(161, 264, renameat)
SYSCALL(162, 265, linkat)
SYSCALL(163, 266, symlinkat)
SYSCALL(164, 267, readlinkat)
SYSCALL(165, 268, fchmodat)
SYSCALL(166, 269, faccessat)
SYSCALL(167, 262, fstatat)
SYSCALL(168, 260, fchownat)
SYSCALL(169, 316, renameat2)
// --- time extensions ---
SYSCALL(170, 201, time)
SYSCALL(171, 229, clock_getres)
SYSCALL(172, 230, clock_nanosleep)
// --- misc ---
SYSCALL(173, 103, syslog)
SYSCALL(174, 318, getrandom)
SYSCALL(175, 270, pselect6)
SYSCALL(176, 271, ppoll)
// --- epoll / eventfd / signalfd / timerfd ---
SYSCALL(177, 291, epoll_create1)
SYSCALL(178, 233, epoll_ctl)
SYSCALL(179, 232, epoll_wait)
SYSCALL(180, 282, signalfd4)
SYSCALL(181, 284, eventfd2)
SYSCALL(182, 283, timerfd_create)
SYSCALL(183, 286, timerfd_settime)
SYSCALL(184, 287, timerfd_gettime)
// --- mounts ---
SYSCALL(185, 165, mount)
SYSCALL(186, 166, umount2)
// --- additional Linux numbers routed to an existing handler ---
SYSCALL_ALIAS( 58, fork)
SYSCALL_ALIAS( 280, utimensat)
SYSCALL_ALIAS( 281, epoll_wait)
SYSCALL_ALIAS( 289, signalfd4)
SYSCALL_ALIAS( 290, eventfd2)
SYSCALL_ALIAS( 295, readv)
SYSCALL_ALIAS( 296, writev)