Initial commit: Schunix as it stands before the SchOS rename

An x86-64 operating system built from xv6, now largely rewritten: SMP,
a copy-on-write root filesystem (SchFS), PCI/USB/virtio drivers, a TTF
rasteriser, and a Linux-numbered syscall ABI.

No history before this point -- the tree was never under version control.
This commit exists so the rename that follows has something to undo to.
This commit is contained in:
Nexoniarz 2026-08-09 13:47:01 +02:00
commit d0f7acf9eb
275 changed files with 51417 additions and 0 deletions

33
kernel/sys/linux_abi.c Normal file
View file

@ -0,0 +1,33 @@
// SPDX-License-Identifier: Apache-2.0
// Linux x86_64 syscall number -> Schunix syscall number translation.
// Enabled per process by sys_enable_linux_abi (see kernel/sys/sys_compat.c).
//
// This file used to be the second half of a two-place registration scheme: a
// hand-written `#define LINUX_foo N` for every Linux number, plus a hand-written
// map entry pointing it at SYS_foo. Adding a syscall meant three edits across
// two files, and forgetting this one left the syscall unreachable from every
// musl-linked program while looking perfectly registered in table.inc.
//
// The map is now generated from kernel/sys/table.inc, which carries the Linux
// number alongside the native one. There is nothing to hand-edit here.
#include "types.h"
#include "syscall.h"
#define LINUX_NSYSCALL NSYSCALLS
// Index: Linux x86_64 syscall number. Value: Schunix SYS_* number, or 0 for
// "not implemented" (syscall() then returns -ENOSYS).
//
// Syscalls declared NO_LINUX in table.inc all land on the single scratch index
// NO_LINUX. They overwrite each other there, which is harmless: no real Linux
// syscall uses that number, so the slot is never dispatched.
int linux_syscall_map[LINUX_NSYSCALL] = {
#define SYSCALL(nr, lnr, name) [lnr] = SYS_##name,
#define SYSCALL_ALIAS(lnr, name) [lnr] = SYS_##name,
#include "table.inc"
#undef SYSCALL
#undef SYSCALL_ALIAS
};
int linux_syscall_map_size = LINUX_NSYSCALL;

162
kernel/sys/sys_compat.c Normal file
View file

@ -0,0 +1,162 @@
// POSIX / Linux-ABI compatibility syscalls.
// These are stubs or thin wrappers that make musl and ksh happy
// without implementing full semantics.
#include "types.h"
#include "defs.h"
#include "param.h"
#include "memlayout.h"
#include "mmu.h"
#include "traps.h"
#include "proc.h"
#include "x86.h"
#include "syscall.h"
#include "posix.h"
#include "uaccess.h"
#include "errno.h"
extern int linux_syscall_map[];
extern int linux_syscall_map_size;
// enable_linux_abi: switch this process to Linux syscall numbering.
addr_t
sys_enable_linux_abi(void)
{
myproc()->syscall_map = linux_syscall_map;
myproc()->map_size = linux_syscall_map_size;
return 0;
}
// set_tid_address: musl calls this at startup to register its TID location.
addr_t
sys_set_tid_address(void)
{
addr_t tidp;
argaddr(0, &tidp);
if (tidp)
put_user(myproc()->pid, tidp);
return myproc()->pid;
}
// set_robust_list: robust mutex cleanup on exit — stub.
addr_t sys_set_robust_list(void) { return 0; }
// futex: used by musl for single-threaded lock primitives.
addr_t
sys_futex(void)
{
int op;
argint(1, &op);
op &= ~(FUTEX_WAIT_PRIVATE - FUTEX_WAIT);
if (op == FUTEX_WAKE || op == FUTEX_WAKE_PRIVATE)
return 0;
return -EAGAIN; // no blocking, musl retries
}
// arch_prctl: save FS_BASE for TLS.
addr_t
sys_arch_prctl(void)
{
int code;
addr_t addr;
argint(0, &code);
argaddr(1, &addr);
if (code == ARCH_SET_FS) {
myproc()->fs_base = addr;
return 0;
}
if (code == ARCH_GET_FS) {
if (put_user(myproc()->fs_base, addr) < 0)
return -EFAULT;
return 0;
}
return -EINVAL;
}
addr_t
sys_getrlimit(void)
{
int resource;
struct rlimit *rlp;
argint(0, &resource);
if (argptr(1, (char**)&rlp, sizeof(*rlp)) < 0)
return -1;
rlp->rlim_cur = (uint64)-1;
rlp->rlim_max = (uint64)-1;
return 0;
}
addr_t
sys_prlimit(void)
{
struct rlimit *oldrlp;
if (argptr(3, (char**)&oldrlp, sizeof(*oldrlp)) == 0 && oldrlp) {
oldrlp->rlim_cur = (uint64)-1;
oldrlp->rlim_max = (uint64)-1;
}
return 0;
}
// uname: return fake but plausible kernel/host info
addr_t
sys_uname(void)
{
struct utsname {
char sysname[65];
char nodename[65];
char release[65];
char version[65];
char machine[65];
char domainname[65];
};
addr_t ubuf;
argaddr(0, &ubuf);
struct utsname *u = (struct utsname*)user_ptr(ubuf, sizeof(*u));
if (!u) return -EFAULT;
memset(u, 0, sizeof(*u));
memmove(u->sysname, "Schunix", 8);
memmove(u->nodename, "schunix", 8);
memmove(u->release, "0.1.0", 6);
memmove(u->version, "#1", 3);
memmove(u->machine, "x86_64", 7);
memmove(u->domainname, "(none)", 7);
return 0;
}
// socket/bind/listen/accept/connect live in sys_net.c now that they do
// something.
// syslog: kernel logging facility — no-op for user processes.
addr_t sys_syslog(void) { return 0; }
// getrandom: fill buffer with pseudo-random bytes.
addr_t sys_getrandom(void)
{
addr_t bufaddr;
int len, flags;
argaddr(0, &bufaddr); argint(1, &len); argint(2, &flags);
if (len <= 0) return 0;
uint8 *buf = (uint8*)user_ptr(bufaddr, (uint64)len);
if (!buf) return -EFAULT;
// LCG seeded from proc pointer + pid; no lock needed (read-only fields).
uint rseed = (uint)(uintptr_t)myproc() ^ (uint)myproc()->pid;
for (int i = 0; i < len; i++) {
rseed = rseed * 1664525u + 1013904223u;
buf[i] = (uint8)(rseed >> 16);
}
return len;
}
// pselect6 / ppoll live in sys_poll.c alongside select/poll.
// epoll: not implemented.
addr_t sys_epoll_create1(void) { return -ENOSYS; }
addr_t sys_epoll_ctl(void) { return -ENOSYS; }
addr_t sys_epoll_wait(void) { return -ENOSYS; }
// signalfd / eventfd / timerfd: not implemented.
addr_t sys_signalfd4(void) { return -ENOSYS; }
addr_t sys_eventfd2(void) { return -ENOSYS; }
addr_t sys_timerfd_create(void) { return -ENOSYS; }
addr_t sys_timerfd_settime(void){ return -ENOSYS; }
addr_t sys_timerfd_gettime(void){ return -ENOSYS; }

1413
kernel/sys/sys_fs.c Normal file

File diff suppressed because it is too large Load diff

479
kernel/sys/sys_mem.c Normal file
View file

@ -0,0 +1,479 @@
#include "types.h"
#include "defs.h"
#include "param.h"
#include "stat.h"
#include "memlayout.h"
#include "mmu.h"
#include "spinlock.h"
#include "sleeplock.h"
#include "proc.h"
#include "fs.h"
#include "file.h"
#include "syscall.h"
#include "errno.h"
extern int mappages(pml4e_t *pgdir, void *va, uint64 size, uint64 pa, int perm);
#define MAP_SHARED 0x01
#define MAP_PRIVATE 0x02
#define MAP_FIXED 0x10
#define MAP_ANONYMOUS 0x20
#define PROT_NONE 0
#define PROT_WRITE 0x2
// Reject user mapping requests that fall outside user space. Without this,
// MAP_FIXED at an arbitrary address could create user-accessible PTEs over
// (or vm_unmap_range/kfree pages belonging to) the kernel half of the
// address space.
static int
bad_user_range(uint64 va, uint64 length)
{
return length == 0 || va + length < va || va + length > KERNBASE;
}
addr_t
sys_sbrk(void)
{
int n;
if (argint(0, &n) < 0)
return -1;
addr_t addr = myproc()->sz;
if (growproc(n) < 0)
return -1;
return addr;
}
addr_t
sys_brk(void)
{
addr_t addr;
if (argaddr(0, &addr) < 0 || addr == 0)
return myproc()->sz;
if (addr > myproc()->sz && growproc(addr - myproc()->sz) < 0)
return myproc()->sz;
return myproc()->sz;
}
addr_t
sys_mmap(void)
{
uint64 addr, length, offset;
int prot, flags, fd;
struct file *f = 0;
if (argaddr(0, &addr) < 0 || argaddr(1, &length) < 0 ||
argint(2, &prot) < 0 || argint(3, &flags) < 0 ||
argint(4, &fd) < 0 || argaddr(5, &offset) < 0)
return -1;
if (fd >= 0 && fd < NOFILE)
f = myproc()->ofile[fd];
length = PGROUNDUP(length);
uint64 va = addr;
if (length == 0 || length > KERNBASE)
return -EINVAL;
if (flags & MAP_FIXED) {
if (bad_user_range(va, length) || (va % PGSIZE))
return -EINVAL;
vm_unmap_range(myproc()->pgdir, va, length);
if (prot == PROT_NONE)
return va;
for (uint64 a = va; a < va + length; a += PGSIZE) {
char *mem = kalloc();
if (!mem) return -1;
memset(mem, 0, PGSIZE);
if (mappages(myproc()->pgdir, (void*)a, PGSIZE, V2P((uint64)mem), PTE_W | PTE_U) < 0)
return -1;
}
return va;
}
struct vma *v = 0;
for (int i = 0; i < NVMA; i++) {
if (!myproc()->vma[i].used) { v = &myproc()->vma[i]; break; }
}
if (!v) return -1;
// Ignore non-zero hint addresses (not MAP_FIXED); always pick a fresh VA.
// Bump-allocated: each mapping gets its own disjoint, exactly-length-sized
// range, so a single mmap() can never run into a neighboring live mapping
// regardless of size (the old scheme assigned a fixed 8MB window per vma[]
// slot index, which a single allocation bigger than 8MB — e.g. a large
// realloc() growth — could map straight past, panicking mappages("remap")
// when it hit an already-present PTE in the next slot's territory).
// munmap()'d ranges are not reclaimed for reuse; that's fine at this scale.
if (myproc()->mmap_next == 0)
myproc()->mmap_next = 0x40000000;
va = myproc()->mmap_next;
if (bad_user_range(va, length))
return -ENOMEM;
myproc()->mmap_next += length;
int is_fb = 0;
int shmid = -1;
// File-backed mapping of a regular file.
//
// This used to be ignored entirely: any file mapping silently returned
// zeroed anonymous pages, so a program that mmap'd a font or a config
// file got a buffer full of nothing and no error to explain it. Reading
// the contents in is not demand paging and never will be without a page
// cache -- but it is the difference between a wrong answer and a right
// one.
//
// MAP_SHARED that is also writable is refused rather than faked. Its
// whole contract is that stores land in the file and other mappers see
// them, and there is nothing here that could make that true; quietly
// giving back a private copy would be the same lie in a new place.
if (f && f->type == FD_INODE && f->ip->type != T_DEV) {
if ((flags & MAP_SHARED) && (prot & PROT_WRITE))
return -ENODEV;
if (!f->readable)
return -EACCES;
for (uint64 a = va; a < va + length; a += PGSIZE) {
char *mem = kalloc();
if (!mem) return -ENOMEM;
memset(mem, 0, PGSIZE);
if (mappages(myproc()->pgdir, (void*)a, PGSIZE, V2P((uint64)mem), PTE_W | PTE_U) < 0) {
kfree(mem);
return -ENOMEM;
}
}
// Read through the file's own offset-independent path, so this does
// not disturb a descriptor the caller is also reading normally.
ilock(f->ip);
readi(f->ip, 0, va, offset, length);
iunlock(f->ip);
v->addr = va; v->length = length; v->used = 1;
v->no_free = 0; v->shmid = -1;
return va;
}
if (f && f->type == FD_INODE && f->ip->type == T_DEV && f->ip->major == 2) {
// Map the framebuffer where it actually is. This used to map a
// hardcoded 0xfd000000 for a hardcoded length, so a request for more
// than the screen holds handed out user-writable PTEs to whatever
// MMIO sits above the framebuffer, and any machine that put the LFB
// somewhere else got a window onto the wrong device entirely.
uint64 fb = vga_fb_phys();
uint64 fbsz = PGROUNDUP(vga_fb_size());
if (fb == 0 || fbsz == 0)
return -ENODEV;
if (length > fbsz)
length = fbsz;
if (mappages(myproc()->pgdir, (void*)va, length, fb, PTE_W | PTE_U) < 0)
return -1;
is_fb = 1;
// From here the drawing happens with no further syscalls, so this is
// the kernel's only chance to notice that the screen no longer belongs
// to the text console. fbclose() puts it back.
fb_mapped();
} else {
for (uint64 a = va; a < va + length; a += PGSIZE) {
char *mem = kalloc();
if (!mem) return -1;
memset(mem, 0, PGSIZE);
if (mappages(myproc()->pgdir, (void*)a, PGSIZE, V2P((uint64)mem), PTE_W | PTE_U) < 0)
return -1;
}
}
v->addr = va; v->length = length; v->used = 1;
v->no_free = is_fb; v->shmid = shmid;
return va;
}
addr_t
sys_munmap(void)
{
uint64 addr, length;
if (argaddr(0, &addr) < 0 || argaddr(1, &length) < 0)
return -1;
length = PGROUNDUP(length);
if (bad_user_range(addr, length))
return -EINVAL;
for (int i = 0; i < NVMA; i++) {
if (myproc()->vma[i].used && myproc()->vma[i].addr == addr) {
if (myproc()->vma[i].shmid >= 0)
shm_detach(myproc()->vma[i].shmid);
if (myproc()->vma[i].no_free)
vm_unmap_norelease(myproc()->pgdir, addr, myproc()->vma[i].length);
else
vm_unmap_range(myproc()->pgdir, addr, myproc()->vma[i].length);
myproc()->vma[i].used = myproc()->vma[i].addr = myproc()->vma[i].length = 0;
myproc()->vma[i].no_free = 0;
myproc()->vma[i].shmid = -1;
return 0;
}
}
// Partial / untracked unmap — free pages anyway
vm_unmap_range(myproc()->pgdir, addr, length);
return 0;
}
addr_t sys_mprotect(void) { return 0; }
// ---------------------------------------------------------------------------
// Shared memory
//
// Two processes mapping the same physical pages. This is what a display
// server and its clients need: without it every frame is a copy through a
// pipe, and the copy is the entire cost of drawing anything.
//
// Deliberately simple. Pages are allocated up front rather than on demand,
// there is no swapping, and a segment lives until the last attachment goes
// away. What it does have is the property that matters: a store by one
// process is visible to the other, because it is the same page.
// ---------------------------------------------------------------------------
#define NSHM 16 // segments
#define SHM_MAXPAGES 512 // 2 MB each -- a 800x600x32 surface is 1.9 MB
#define IPC_PRIVATE 0
#define IPC_CREAT 01000
#define IPC_EXCL 02000
struct shmseg {
int used;
int key;
uint64 size; // rounded up to a page
int npages;
int nattach; // live attachments across all processes
char *pages[SHM_MAXPAGES]; // kernel virtual addresses
};
static struct shmseg shm[NSHM];
static struct spinlock shmlock;
static int shm_ready;
static void
shm_init_once(void)
{
if (!shm_ready) {
initlock(&shmlock, "shm");
shm_ready = 1;
}
}
static void
shm_free(struct shmseg *s)
{
for (int i = 0; i < s->npages; i++)
if (s->pages[i])
kfree(s->pages[i]);
memset(s, 0, sizeof(*s));
}
// fork() gives the child its own attachment to the same segment. Without
// this the count would say one attachment while two processes had it mapped,
// and the first to exit would free the pages under the other.
void
shm_ref(int id)
{
if (id < 0 || id >= NSHM)
return;
shm_init_once();
acquire(&shmlock);
if (shm[id].used)
shm[id].nattach++;
release(&shmlock);
}
// Called from munmap and from exit, once per detached mapping.
void
shm_detach(int id)
{
if (id < 0 || id >= NSHM)
return;
shm_init_once();
acquire(&shmlock);
if (shm[id].used && shm[id].nattach > 0) {
if (--shm[id].nattach == 0)
shm_free(&shm[id]);
}
release(&shmlock);
}
addr_t
sys_shmget(void)
{
int key, flags;
addr_t size;
if (argint(0, &key) < 0 || argaddr(1, &size) < 0 || argint(2, &flags) < 0)
return -EINVAL;
size = PGROUNDUP(size);
if (size == 0 || size > (uint64)SHM_MAXPAGES * PGSIZE)
return -EINVAL;
shm_init_once();
acquire(&shmlock);
if (key != IPC_PRIVATE) {
for (int i = 0; i < NSHM; i++) {
if (shm[i].used && shm[i].key == key) {
if ((flags & IPC_CREAT) && (flags & IPC_EXCL)) {
release(&shmlock);
return -EEXIST;
}
if (shm[i].size < size) {
release(&shmlock);
return -EINVAL;
}
release(&shmlock);
return i;
}
}
if (!(flags & IPC_CREAT)) {
release(&shmlock);
return -ENOENT;
}
}
for (int i = 0; i < NSHM; i++) {
if (shm[i].used)
continue;
struct shmseg *s = &shm[i];
s->npages = (int)(size / PGSIZE);
for (int pg = 0; pg < s->npages; pg++) {
s->pages[pg] = kalloc();
if (!s->pages[pg]) {
for (int j = 0; j < pg; j++) kfree(s->pages[j]);
memset(s, 0, sizeof(*s));
release(&shmlock);
return -ENOMEM;
}
// Zeroed, because a fresh segment handing out the last tenant's
// bytes would leak them between processes.
memset(s->pages[pg], 0, PGSIZE);
}
s->used = 1;
s->key = key;
s->size = size;
s->nattach = 0;
release(&shmlock);
return i;
}
release(&shmlock);
return -ENOSPC;
}
addr_t
sys_shmat(void)
{
int id, flags;
addr_t addr;
if (argint(0, &id) < 0 || argaddr(1, &addr) < 0 || argint(2, &flags) < 0)
return -EINVAL;
if (id < 0 || id >= NSHM)
return -EINVAL;
shm_init_once();
acquire(&shmlock);
if (!shm[id].used) {
release(&shmlock);
return -EINVAL;
}
uint64 len = shm[id].size;
int npages = shm[id].npages;
shm[id].nattach++; // claimed before the lock is dropped
release(&shmlock);
struct vma *v = 0;
for (int i = 0; i < NVMA; i++)
if (!myproc()->vma[i].used) { v = &myproc()->vma[i]; break; }
if (!v) { shm_detach(id); return -ENOMEM; }
// Ignore the hint and take a fresh range, the same way mmap does.
if (myproc()->mmap_next == 0)
myproc()->mmap_next = 0x40000000;
uint64 va = myproc()->mmap_next;
if (bad_user_range(va, len)) { shm_detach(id); return -ENOMEM; }
myproc()->mmap_next += len;
for (int pg = 0; pg < npages; pg++) {
if (mappages(myproc()->pgdir, (void*)(va + (uint64)pg * PGSIZE), PGSIZE,
V2P((uint64)shm[id].pages[pg]), PTE_W | PTE_U) < 0) {
vm_unmap_norelease(myproc()->pgdir, va, (uint64)pg * PGSIZE);
shm_detach(id);
return -ENOMEM;
}
}
v->addr = va; v->length = len; v->used = 1;
v->no_free = 1; // the segment owns these pages
v->shmid = id;
return va;
}
// Drop every mapping this process holds that it does not own the pages of,
// before its page tables are torn down.
//
// freevm() frees any mapped page below PHYSTOP, which is right for the heap
// and the stack and wrong for anything shared. The framebuffer survived only
// because it sits above PHYSTOP and was skipped by accident; shared memory is
// kalloc'd, sits below it, and would be handed to kfree while another process
// was still using it -- the worst kind of bug, silent and remote.
//
// Called from exit() and from exec() before the old address space goes away.
void
proc_release_shared(struct proc *p)
{
for (int i = 0; i < NVMA; i++) {
if (!p->vma[i].used || !p->vma[i].no_free)
continue;
vm_unmap_norelease(p->pgdir, p->vma[i].addr, p->vma[i].length);
if (p->vma[i].shmid >= 0)
shm_detach(p->vma[i].shmid);
p->vma[i].used = 0;
p->vma[i].no_free = 0;
p->vma[i].shmid = -1;
}
}
addr_t
sys_shmdt(void)
{
addr_t addr;
if (argaddr(0, &addr) < 0)
return -EINVAL;
for (int i = 0; i < NVMA; i++) {
if (myproc()->vma[i].used && myproc()->vma[i].addr == addr &&
myproc()->vma[i].shmid >= 0) {
vm_unmap_norelease(myproc()->pgdir, addr, myproc()->vma[i].length);
shm_detach(myproc()->vma[i].shmid);
myproc()->vma[i].used = myproc()->vma[i].addr = myproc()->vma[i].length = 0;
myproc()->vma[i].no_free = 0;
myproc()->vma[i].shmid = -1;
return 0;
}
}
return -EINVAL;
}
// Memory advice/hints — all no-ops; kernel decides on its own.
addr_t sys_madvise(void) { return 0; }
addr_t sys_msync(void) { return 0; }
addr_t sys_mincore(void) { return 0; }
addr_t sys_mlock(void) { return 0; }
addr_t sys_munlock(void) { return 0; }
addr_t sys_mlockall(void) { return 0; }
addr_t sys_munlockall(void){ return 0; }
addr_t sys_fadvise64(void) { return 0; }
// mremap: we don't support region relocation; return ENOMEM.
addr_t sys_mremap(void) { return -ENOMEM; }
// Resource limits: always report unlimited.
addr_t sys_setrlimit(void) { return 0; }
// Process priority: always report 0.
addr_t sys_getpriority(void) { return 0; }
addr_t sys_setpriority(void) { return 0; }

346
kernel/sys/sys_net.c Normal file
View file

@ -0,0 +1,346 @@
// Socket syscalls.
//
// The kernel side is in kernel/net/unix.c; this file is only the translation
// between a syscall's user-supplied `struct sockaddr` and the path string the
// socket layer actually wants, plus the file-descriptor bookkeeping.
//
// Only AF_UNIX is answered. AF_INET is recognised and refused with
// EAFNOSUPPORT rather than EINVAL, because a program that probes for IPv6 and
// falls back to IPv4 needs to be able to tell "this address family does not
// exist here" from "you passed nonsense".
#include "types.h"
#include "defs.h"
#include "param.h"
#include "mmu.h"
#include "proc.h"
#include "fs.h"
#include "spinlock.h"
#include "sleeplock.h"
#include "file.h"
#include "posix.h"
#include "syscall.h"
#include "uaccess.h"
#include "errno.h"
#include "socket.h"
// Pull the sun_path out of a user `struct sockaddr_un`, NUL-terminated.
//
// The length the caller gives is checked rather than trusted: musl passes
// `offsetof(sockaddr_un, sun_path) + strlen(path) + 1`, which is shorter than
// the full struct, so reading sizeof(struct sockaddr_un) from user memory
// would fault on a path that sits near the end of a page.
static int
fetch_sun_path(addr_t uaddr, int ulen, char *out)
{
struct sockaddr_un sa;
int plen;
if (ulen < (int)sizeof(sa.sun_family) || ulen > (int)sizeof(sa))
return -EINVAL;
memset(&sa, 0, sizeof(sa));
if (copy_from_user(&sa, uaddr, (uint64)ulen) < 0)
return -EFAULT;
if (sa.sun_family == AF_INET)
return -EAFNOSUPPORT;
if (sa.sun_family != AF_UNIX)
return -EAFNOSUPPORT;
plen = ulen - (int)sizeof(sa.sun_family);
if (plen <= 0)
return -EINVAL;
if (plen > UNIX_PATH_MAX - 1)
plen = UNIX_PATH_MAX - 1;
memmove(out, sa.sun_path, (uint)plen);
out[plen] = '\0';
// A NUL inside the copied region is fine -- it just ends the path early,
// which is what a caller passing the full struct length does.
out[UNIX_PATH_MAX - 1] = '\0';
return 0;
}
// Wrap an established socket in a file and a descriptor. Consumes the
// socket's reference on success; on failure the caller still owns it.
static int
sock_to_fd(struct socket *s)
{
struct file *f;
int fd;
if ((f = filealloc()) == 0)
return -ENFILE;
if ((fd = fdalloc(f)) < 0) {
f->ref = 0;
return -EMFILE;
}
f->type = FD_SOCKET;
f->sock = s;
f->readable = 1;
f->writable = 1;
f->off = 0;
return fd;
}
// Resolve a descriptor to the socket behind it.
static int
argsock(int n, struct socket **ps)
{
int fd;
struct file *f;
if (argint(n, &fd) < 0)
return -EBADF;
if (fd < 0 || fd >= NOFILE || !(f = myproc()->ofile[fd]))
return -EBADF;
if (f->type != FD_SOCKET)
return -ENOTSOCK;
*ps = f->sock;
return 0;
}
SYSCALL_DEFINE(socket)
{
int domain, type, protocol;
struct socket *s;
int fd;
argint(0, &domain);
argint(1, &type);
argint(2, &protocol);
if (domain != AF_UNIX)
return -EAFNOSUPPORT;
// SOCK_CLOEXEC / SOCK_NONBLOCK ride in the high bits of `type`. Neither
// is honoured yet, but masking them off means a musl program that always
// sets SOCK_CLOEXEC still gets a socket instead of EINVAL.
type &= 0xFF;
if (type != SOCK_STREAM)
return -EPROTOTYPE;
if (protocol != 0)
return -EPROTONOSUPPORT;
if ((s = socknew(domain, type)) == 0)
return -ENFILE;
if ((fd = sock_to_fd(s)) < 0) {
sockclose(s);
return fd;
}
return fd;
}
SYSCALL_DEFINE(bind)
{
struct socket *s;
addr_t uaddr;
int ulen, r;
char path[UNIX_PATH_MAX];
if ((r = argsock(0, &s)) < 0)
return r;
argaddr(1, &uaddr);
argint(2, &ulen);
if ((r = fetch_sun_path(uaddr, ulen, path)) < 0)
return r;
return sockbind(s, path);
}
SYSCALL_DEFINE(listen)
{
struct socket *s;
int backlog, r;
if ((r = argsock(0, &s)) < 0)
return r;
argint(1, &backlog);
return socklisten(s, backlog);
}
SYSCALL_DEFINE(connect)
{
struct socket *s;
addr_t uaddr;
int ulen, r;
char path[UNIX_PATH_MAX];
if ((r = argsock(0, &s)) < 0)
return r;
argaddr(1, &uaddr);
argint(2, &ulen);
if ((r = fetch_sun_path(uaddr, ulen, path)) < 0)
return r;
return sockconnect(s, path);
}
SYSCALL_DEFINE(accept)
{
struct socket *s, *c;
addr_t uaddr, ulenp;
int r, fd;
if ((r = argsock(0, &s)) < 0)
return r;
argaddr(1, &uaddr);
argaddr(2, &ulenp);
if ((c = sockaccept(s)) == 0)
return -EINVAL;
if ((fd = sock_to_fd(c)) < 0) {
sockclose(c);
return fd;
}
// The peer address of a Unix socket that never bound a name is the empty
// one: family AF_UNIX and a zero-length path. Reporting it is optional
// (both pointers may be null) but a caller that asks must not be left
// with an uninitialised struct it then prints.
if (uaddr && ulenp) {
struct sockaddr_un sa;
int len = (int)sizeof(sa.sun_family);
int want = 0;
memset(&sa, 0, sizeof(sa));
sa.sun_family = AF_UNIX;
safestrcpy(sa.sun_path, sockname(c), UNIX_PATH_MAX);
len += (int)strlen(sa.sun_path) + 1;
if (get_user(want, ulenp) < 0)
return fd; // the fd is already live; do not fail it
if (want > len)
want = len;
if (want > 0)
copy_to_user(uaddr, &sa, (uint64)want);
put_user(len, ulenp);
}
return fd;
}
SYSCALL_DEFINE(socketpair)
{
int domain, type, protocol;
addr_t usv;
struct socket *a, *b;
int fd0, fd1, r;
argint(0, &domain);
argint(1, &type);
argint(2, &protocol);
argaddr(3, &usv);
if (domain != AF_UNIX)
return -EAFNOSUPPORT;
type &= 0xFF;
if (type != SOCK_STREAM)
return -EPROTOTYPE;
if ((r = sockpair(&a, &b)) < 0)
return r;
if ((fd0 = sock_to_fd(a)) < 0) {
sockclose(a);
sockclose(b);
return fd0;
}
if ((fd1 = sock_to_fd(b)) < 0) {
myproc()->ofile[fd0] = 0;
sockclose(a);
sockclose(b);
return fd1;
}
int sv[2] = { fd0, fd1 };
if (copy_to_user(usv, sv, sizeof(sv)) < 0) {
myproc()->ofile[fd0] = 0;
myproc()->ofile[fd1] = 0;
sockclose(a);
sockclose(b);
return -EFAULT;
}
return 0;
}
// send/recv with no flags are read/write. Implementing them here rather than
// leaving them ENOSYS costs four lines and saves every ported program from
// having to be rewritten to use write().
SYSCALL_DEFINE(sendto)
{
struct socket *s;
addr_t ubuf;
int len, flags, r;
if ((r = argsock(0, &s)) < 0)
return r;
argaddr(1, &ubuf);
argint(2, &len);
argint(3, &flags);
if (len < 0)
return -EINVAL;
char *p = (char *)user_ptr(ubuf, (uint64)len);
if (len && !p)
return -EFAULT;
return sockwrite(s, p, len);
}
SYSCALL_DEFINE(recvfrom)
{
struct socket *s;
addr_t ubuf;
int len, flags, r;
if ((r = argsock(0, &s)) < 0)
return r;
argaddr(1, &ubuf);
argint(2, &len);
argint(3, &flags);
if (len < 0)
return -EINVAL;
char *p = (char *)user_ptr(ubuf, (uint64)len);
if (len && !p)
return -EFAULT;
return sockread(s, p, len);
}
// shutdown(2). Half-close is not modelled -- there is one buffer per
// direction and no per-direction flag -- so SHUT_WR and SHUT_RDWR both mean
// "the peer sees end-of-file", which is the behaviour programs depend on.
SYSCALL_DEFINE(sockshutdown)
{
struct socket *s;
int how, r;
if ((r = argsock(0, &s)) < 0)
return r;
argint(1, &how);
return sock_shutdown(s, how);
}
// getsockopt/setsockopt: accepted and ignored. A server that sets SO_REUSEADDR
// and treats failure as fatal is common enough to be worth not failing.
SYSCALL_DEFINE(setsockopt) { return 0; }
SYSCALL_DEFINE(getsockopt)
{
addr_t uval, ulen;
int zero = 0, len = 0;
argaddr(3, &uval);
argaddr(4, &ulen);
if (uval && ulen) {
if (get_user(len, ulen) == 0 && len >= (int)sizeof(int)) {
put_user(zero, uval);
len = sizeof(int);
put_user(len, ulen);
}
}
return 0;
}

292
kernel/sys/sys_poll.c Normal file
View file

@ -0,0 +1,292 @@
// poll(2) / select(2) — real readiness-based I/O multiplexing over pipes,
// the console tty, and regular files (via file_poll()). This is the
// foundation every event-loop program (dash, toybox netcat, eventually a
// display server) sits on; the previous stubs returned -ENOSYS, which made
// anything poll-driven bail out instantly.
//
// Blocking strategy: scan all fds; if nothing is ready and the timeout
// allows, sleep until the next timer tick (10ms granularity at HZ=100) and
// rescan. A pending unblocked signal or kill interrupts the wait with
// -EINTR so handlers get a chance to run, matching POSIX.
#include "types.h"
#include "defs.h"
#include "param.h"
#include "spinlock.h"
#include "sleeplock.h"
#include "fs.h"
#include "file.h"
#include "proc.h"
#include "syscall.h"
#include "posix.h"
#include "uaccess.h"
#include "errno.h"
#define POLL_MAX_FDS 64
// Sleep one timer tick, or until killed/signaled. Returns 0 to keep
// waiting, -EINTR if the wait must be abandoned.
static int
poll_block_one_tick(void)
{
if (myproc()->killed)
return -EINTR;
if (myproc()->pending_signals & ~myproc()->signal_mask)
return -EINTR;
acquire(&tickslock);
uint t0 = ticks;
while (ticks == t0 && !myproc()->killed &&
!(myproc()->pending_signals & ~myproc()->signal_mask))
sleep(&ticks, &tickslock);
release(&tickslock);
return 0;
}
// Shared core: fds[] already in kernel memory. timeout_ticks < 0 means
// wait forever. Returns number of fds with nonzero revents, 0 on timeout,
// -EINTR if interrupted.
static int
do_poll(struct pollfd *fds, int nfds, int timeout_ticks)
{
for (;;) {
int nready = 0;
for (int i = 0; i < nfds; i++) {
fds[i].revents = 0;
if (fds[i].fd < 0)
continue; // negative fd: ignore, per poll(2)
if (fds[i].fd >= NOFILE || !myproc()->ofile[fds[i].fd]) {
fds[i].revents = POLLNVAL;
nready++;
continue;
}
int rev = file_poll(myproc()->ofile[fds[i].fd], fds[i].events);
if (rev) {
fds[i].revents = (short)rev;
nready++;
}
}
if (nready > 0 || timeout_ticks == 0)
return nready;
if (poll_block_one_tick() < 0)
return -EINTR;
if (timeout_ticks > 0)
timeout_ticks--;
}
}
// poll(fds, nfds, timeout_ms)
addr_t
sys_poll(void)
{
addr_t ufds;
int nfds, timeout_ms;
struct pollfd fds[POLL_MAX_FDS];
argaddr(0, &ufds);
argint(1, &nfds);
argint(2, &timeout_ms);
if (nfds < 0 || nfds > POLL_MAX_FDS)
return -EINVAL;
if (nfds > 0 && copy_from_user(fds, ufds, nfds * sizeof(struct pollfd)) < 0)
return -EFAULT;
int timeout_ticks = timeout_ms < 0 ? -1 : (timeout_ms + 9) / 10;
int r = do_poll(fds, nfds, timeout_ticks);
if (r >= 0 && nfds > 0 &&
copy_to_user(ufds, fds, nfds * sizeof(struct pollfd)) < 0)
return -EFAULT;
return r;
}
// ppoll(fds, nfds, timespec*, sigmask) — sigmask is ignored (poll already
// breaks out for any deliverable signal); NULL timespec = wait forever.
addr_t
sys_ppoll(void)
{
addr_t ufds, utsp;
int nfds;
struct pollfd fds[POLL_MAX_FDS];
argaddr(0, &ufds);
argint(1, &nfds);
argaddr(2, &utsp);
if (nfds < 0 || nfds > POLL_MAX_FDS)
return -EINVAL;
if (nfds > 0 && copy_from_user(fds, ufds, nfds * sizeof(struct pollfd)) < 0)
return -EFAULT;
int timeout_ticks = -1;
if (utsp) {
int64 ts[2]; // {tv_sec, tv_nsec}
if (copy_from_user(ts, utsp, sizeof(ts)) < 0)
return -EFAULT;
timeout_ticks = (int)(ts[0] * 100 + ts[1] / 10000000);
}
int r = do_poll(fds, nfds, timeout_ticks);
if (r >= 0 && nfds > 0 &&
copy_to_user(ufds, fds, nfds * sizeof(struct pollfd)) < 0)
return -EFAULT;
return r;
}
// select(nfds, readfds, writefds, exceptfds, timeval*). fd_set is a bit
// array; with NOFILE=128 the interesting part fits in two 64-bit words.
#define FDSET_WORDS (NOFILE / 64)
static int
fdset_fetch(addr_t uaddr, uint64 *set)
{
if (!uaddr) {
set[0] = set[1] = 0;
return 0;
}
return copy_from_user(set, uaddr, FDSET_WORDS * sizeof(uint64));
}
static int
fdset_isset(uint64 *set, int fd) { return (set[fd / 64] >> (fd % 64)) & 1; }
static void
fdset_set(uint64 *set, int fd) { set[fd / 64] |= 1ULL << (fd % 64); }
addr_t
sys_select(void)
{
int nfds;
addr_t urd, uwr, uex, utv;
uint64 rd[FDSET_WORDS], wr[FDSET_WORDS], ex[FDSET_WORDS];
uint64 ord[FDSET_WORDS] = {0}, owr[FDSET_WORDS] = {0};
argint(0, &nfds);
argaddr(1, &urd);
argaddr(2, &uwr);
argaddr(3, &uex);
argaddr(4, &utv);
if (nfds < 0 || nfds > NOFILE)
return -EINVAL;
if (fdset_fetch(urd, rd) < 0 || fdset_fetch(uwr, wr) < 0 ||
fdset_fetch(uex, ex) < 0)
return -EFAULT;
int timeout_ticks = -1;
if (utv) {
int64 tv[2]; // {tv_sec, tv_usec}
if (copy_from_user(tv, utv, sizeof(tv)) < 0)
return -EFAULT;
timeout_ticks = (int)(tv[0] * 100 + tv[1] / 10000);
}
for (;;) {
int nready = 0;
for (int fd = 0; fd < nfds; fd++) {
int want_r = fdset_isset(rd, fd);
int want_w = fdset_isset(wr, fd);
if (!want_r && !want_w)
continue;
struct file *f = myproc()->ofile[fd];
if (!f)
return -EBADF;
int rev = file_poll(f, (want_r ? POLLIN : 0) | (want_w ? POLLOUT : 0));
// Hangup/error count as readable: a read() will not block
// (it returns EOF/error immediately) — same rule Linux uses.
if (want_r && (rev & (POLLIN | POLLHUP | POLLERR))) {
fdset_set(ord, fd);
nready++;
}
if (want_w && (rev & (POLLOUT | POLLERR))) {
fdset_set(owr, fd);
nready++;
}
}
if (nready > 0 || timeout_ticks == 0) {
if (urd && copy_to_user(urd, ord, sizeof(ord)) < 0) return -EFAULT;
if (uwr && copy_to_user(uwr, owr, sizeof(owr)) < 0) return -EFAULT;
if (uex) { // no exceptional conditions exist here; report none
uint64 zero[FDSET_WORDS] = {0};
if (copy_to_user(uex, zero, sizeof(zero)) < 0) return -EFAULT;
}
return nready;
}
if (poll_block_one_tick() < 0)
return -EINTR;
if (timeout_ticks > 0)
timeout_ticks--;
}
}
// pselect6: select with a timespec and (ignored) sigmask.
addr_t
sys_pselect6(void)
{
// Delegate: args 0-3 line up with select's; arg 4 is a timespec instead
// of a timeval and arg 5 the sigmask. Reuse sys_select's field reads but
// treat the timeout as nanoseconds.
int nfds;
addr_t urd, uwr, uex, utsp;
argint(0, &nfds);
argaddr(1, &urd);
argaddr(2, &uwr);
argaddr(3, &uex);
argaddr(4, &utsp);
// Convert the timespec (if any) into a timeval in place on the user
// stack? No — simplest correct route: temporarily run the same logic.
// sys_select re-reads the same registers, so just fix up the timeout
// interpretation here by patching r8 semantics is not possible; instead
// duplicate the small tail:
uint64 rd[FDSET_WORDS], wr[FDSET_WORDS], ex[FDSET_WORDS];
uint64 ord[FDSET_WORDS] = {0}, owr[FDSET_WORDS] = {0};
if (nfds < 0 || nfds > NOFILE)
return -EINVAL;
if (fdset_fetch(urd, rd) < 0 || fdset_fetch(uwr, wr) < 0 ||
fdset_fetch(uex, ex) < 0)
return -EFAULT;
int timeout_ticks = -1;
if (utsp) {
int64 ts[2];
if (copy_from_user(ts, utsp, sizeof(ts)) < 0)
return -EFAULT;
timeout_ticks = (int)(ts[0] * 100 + ts[1] / 10000000);
}
for (;;) {
int nready = 0;
for (int fd = 0; fd < nfds; fd++) {
int want_r = fdset_isset(rd, fd);
int want_w = fdset_isset(wr, fd);
if (!want_r && !want_w)
continue;
struct file *f = myproc()->ofile[fd];
if (!f)
return -EBADF;
int rev = file_poll(f, (want_r ? POLLIN : 0) | (want_w ? POLLOUT : 0));
if (want_r && (rev & (POLLIN | POLLHUP | POLLERR))) {
fdset_set(ord, fd);
nready++;
}
if (want_w && (rev & (POLLOUT | POLLERR))) {
fdset_set(owr, fd);
nready++;
}
}
if (nready > 0 || timeout_ticks == 0) {
if (urd && copy_to_user(urd, ord, sizeof(ord)) < 0) return -EFAULT;
if (uwr && copy_to_user(uwr, owr, sizeof(owr)) < 0) return -EFAULT;
if (uex) {
uint64 zero[FDSET_WORDS] = {0};
if (copy_to_user(uex, zero, sizeof(zero)) < 0) return -EFAULT;
}
return nready;
}
if (poll_block_one_tick() < 0)
return -EINTR;
if (timeout_ticks > 0)
timeout_ticks--;
}
}

451
kernel/sys/sys_proc.c Normal file
View file

@ -0,0 +1,451 @@
#include "types.h"
#include "defs.h"
#include "param.h"
#include "memlayout.h"
#include "mmu.h"
#include "proc.h"
#include "x86.h"
#include "syscall.h"
#include "posix.h"
#include "uaccess.h"
#include "errno.h"
addr_t sys_fork(void) { return fork(); }
addr_t sys_wait(void) { return wait(); }
// _exit(status)/exit_group(status): the low 8 bits of the argument are what
// wait() reports back through WEXITSTATUS.
addr_t
sys_exit(void)
{
int code = 0;
argint(0, &code);
exit(W_EXITCODE(code, 0));
return 0;
}
addr_t sys_exit_group(void) { return sys_exit(); }
addr_t
sys_kill(void)
{
int pid, sig;
argint(0, &pid);
argint(1, &sig);
// POSIX target selection:
// pid > 0 -> that process
// pid == 0 -> every process in the caller's own group
// pid == -1 -> every process we may signal (approximated as our group)
// pid < -1 -> the process group with id -pid
if (pid == 0 || pid == -1)
return sigsend_pgrp(myproc()->pgid, sig) < 0 ? -ESRCH : 0;
if (pid < -1)
return sigsend_pgrp(-pid, sig) < 0 ? -ESRCH : 0;
// sig == 0: existence check only
if (sig == 0)
return proc_exists(pid) ? 0 : -ESRCH;
// Raise the signal for real: pending bit + wakeup, handler delivery on
// the target's next syscall boundary, default action if unhandled.
return sigsend(pid, sig) < 0 ? -ESRCH : 0;
}
addr_t sys_getpid(void) { return myproc()->pid; }
addr_t sys_getppid(void) { return myproc()->parent ? myproc()->parent->pid : 1; }
addr_t sys_gettid(void) { return myproc()->pid; } // single-threaded: tid == pid
addr_t
sys_waitpid(void)
{
int pid, options;
addr_t statusp;
// status is optional (NULL is standard, valid usage — waitpid(pid, NULL,
// WNOHANG) — so this must NOT go through argptr(), which rejects a NULL/
// unmapped pointer outright).
if (argint(0, &pid) < 0 || argaddr(1, &statusp) < 0 || argint(2, &options) < 0)
return -1;
int status;
int wpid = waitpid(pid, &status, options);
if (wpid > 0 && statusp)
put_user(status, statusp);
return wpid;
}
addr_t
sys_wait4(void)
{
int pid, options;
addr_t statusp;
argint(0, &pid);
argaddr(1, &statusp);
argint(2, &options);
int status;
int wpid = waitpid(pid, &status, options);
if (wpid <= 0)
return wpid;
if (statusp)
put_user(status, statusp);
return wpid;
}
addr_t
sys_clone(void)
{
int flags;
argint(0, &flags);
if (flags & (CLONE_VM | CLONE_THREAD | CLONE_SIGHAND))
return -ENOSYS;
return fork();
}
addr_t
sys_sleep(void)
{
int n;
uint ticks0;
if (argint(0, &n) < 0)
return -1;
acquire(&tickslock);
ticks0 = ticks;
while (ticks - ticks0 < (uint)n) {
if (myproc()->killed) { release(&tickslock); return -1; }
sleep(&ticks, &tickslock);
}
release(&tickslock);
return 0;
}
addr_t
sys_uptime(void)
{
uint xticks;
acquire(&tickslock);
xticks = ticks;
release(&tickslock);
return xticks;
}
addr_t sys_reboot(void)
{
cprintf("System is rebooting...\n");
outb(0xCF9, 0x06);
outb(0x64, 0xFE);
return 0;
}
addr_t sys_shutdown(void)
{
cprintf("System is shutting down...\n");
outw(0x604, 0x2000);
outw(0xB004, 0x2000);
outw(0x4004, 0x3400);
return 0;
}
// --- Sessions and process groups ---------------------------------------
// Job control is defined entirely in terms of these. They used to be stubs
// returning the caller's own pid, which made every process look like its own
// group leader and left a shell's wait loop unable to make progress.
addr_t sys_setsid(void) { return proc_setsid(); }
addr_t
sys_getsid(void)
{
int pid;
argint(0, &pid);
if (pid == 0 || pid == myproc()->pid)
return myproc()->sid;
return proc_exists(pid) ? (addr_t)myproc()->sid : (addr_t)-ESRCH;
}
addr_t
sys_getpgid(void)
{
int pid;
argint(0, &pid);
if (pid == 0 || pid == myproc()->pid)
return myproc()->pgid;
int g = proc_pgid(pid);
return g < 0 ? (addr_t)-ESRCH : (addr_t)g;
}
addr_t
sys_setpgid(void)
{
int pid, pgid;
argint(0, &pid);
argint(1, &pgid);
return proc_setpgid(pid, pgid);
}
addr_t sys_getpgrp(void) { return myproc()->pgid; }
// --- User and group identity -------------------------------------------
// Tracked for real now. Nothing *enforces* permissions with these yet -- the
// on-disk format has no owner field until SchFS -- but they are no longer
// invented, so getuid() after setuid() reports what actually happened.
addr_t sys_getuid(void) { return myproc()->uid; }
addr_t sys_geteuid(void) { return myproc()->euid; }
addr_t sys_getgid(void) { return myproc()->gid; }
addr_t sys_getegid(void) { return myproc()->egid; }
// Privileged (euid 0) callers may set any id; an unprivileged caller may only
// switch between its real and saved ids. Standard POSIX rule.
addr_t
sys_setuid(void)
{
int uid;
argint(0, &uid);
if (uid < 0)
return -EINVAL;
if (myproc()->euid == 0) {
myproc()->uid = myproc()->euid = myproc()->suid = (uint)uid;
return 0;
}
if ((uint)uid != myproc()->uid && (uint)uid != myproc()->suid)
return -EPERM;
myproc()->euid = (uint)uid;
return 0;
}
addr_t
sys_setgid(void)
{
int gid;
argint(0, &gid);
if (gid < 0)
return -EINVAL;
if (myproc()->euid == 0) {
myproc()->gid = myproc()->egid = myproc()->sgid = (uint)gid;
return 0;
}
if ((uint)gid != myproc()->gid && (uint)gid != myproc()->sgid)
return -EPERM;
myproc()->egid = (uint)gid;
return 0;
}
addr_t sys_umask(void) { return 022; }
addr_t
sys_setreuid(void)
{
int ruid, euid;
argint(0, &ruid); argint(1, &euid);
if (myproc()->euid != 0)
return -EPERM;
if (ruid >= 0) myproc()->uid = (uint)ruid;
if (euid >= 0) myproc()->euid = (uint)euid;
myproc()->suid = myproc()->euid;
return 0;
}
addr_t
sys_setregid(void)
{
int rgid, egid;
argint(0, &rgid); argint(1, &egid);
if (myproc()->euid != 0)
return -EPERM;
if (rgid >= 0) myproc()->gid = (uint)rgid;
if (egid >= 0) myproc()->egid = (uint)egid;
myproc()->sgid = myproc()->egid;
return 0;
}
addr_t
sys_setresuid(void)
{
int r, e, sv;
argint(0, &r); argint(1, &e); argint(2, &sv);
if (myproc()->euid != 0)
return -EPERM;
if (r >= 0) myproc()->uid = (uint)r;
if (e >= 0) myproc()->euid = (uint)e;
if (sv >= 0) myproc()->suid = (uint)sv;
return 0;
}
addr_t
sys_setresgid(void)
{
int r, e, sv;
argint(0, &r); argint(1, &e); argint(2, &sv);
if (myproc()->euid != 0)
return -EPERM;
if (r >= 0) myproc()->gid = (uint)r;
if (e >= 0) myproc()->egid = (uint)e;
if (sv >= 0) myproc()->sgid = (uint)sv;
return 0;
}
// Linux-specific; the fs id follows the effective id here.
addr_t sys_setfsuid(void) { return myproc()->euid; }
addr_t sys_setfsgid(void) { return myproc()->egid; }
addr_t
sys_getresuid(void)
{
addr_t rp, ep, sp;
argaddr(0, &rp); argaddr(1, &ep); argaddr(2, &sp);
if (rp) put_user(myproc()->uid, rp);
if (ep) put_user(myproc()->euid, ep);
if (sp) put_user(myproc()->suid, sp);
return 0;
}
addr_t
sys_getresgid(void)
{
addr_t rp, ep, sp;
argaddr(0, &rp); argaddr(1, &ep); argaddr(2, &sp);
if (rp) put_user(myproc()->gid, rp);
if (ep) put_user(myproc()->egid, ep);
if (sp) put_user(myproc()->sgid, sp);
return 0;
}
addr_t sys_getgroups(void)
{
int size;
argint(0, &size);
return 0; // no supplementary groups
}
addr_t sys_setgroups(void) { return 0; }
// --- Process/scheduling extensions ---
addr_t sys_sched_yield(void) { return 0; }
addr_t sys_sched_getaffinity(void)
{
int pid, cpusetsize;
addr_t maskp;
argint(0, &pid); argint(1, &cpusetsize); argaddr(2, &maskp);
if (maskp && cpusetsize > 0) {
char *m = (char*)user_ptr(maskp, (uint64)cpusetsize);
if (m) { memset(m, 0, cpusetsize); m[0] = 1; } // CPU 0 only
}
return 0;
}
addr_t sys_sched_setaffinity(void) { return 0; }
addr_t sys_prctl(void)
{
int op;
addr_t arg2;
argint(0, &op);
argaddr(1, &arg2);
if (op == PR_SET_NAME) {
char *name;
if (argstr(1, &name) < 0) return -1;
safestrcpy(myproc()->name, name, sizeof(myproc()->name));
return 0;
}
if (op == PR_GET_NAME) {
char *buf = (char*)user_ptr(arg2, 16);
if (!buf) return -EFAULT;
safestrcpy(buf, myproc()->name, 16);
return 0;
}
return 0; // all other prctl ops: silently succeed
}
addr_t sys_tgkill(void)
{
int tgid, tid, sig;
argint(0, &tgid); argint(1, &tid); argint(2, &sig);
// In our single-threaded model tgid == tid == pid.
if (tgid <= 0) return 0;
if (sig == 0) return proc_exists(tgid) ? 0 : -ESRCH;
return sigsend(tgid, sig) < 0 ? -ESRCH : 0;
}
addr_t sys_tkill(void)
{
int tid, sig;
argint(0, &tid); argint(1, &sig);
if (tid <= 0) return 0;
if (sig == 0) return proc_exists(tid) ? 0 : -ESRCH;
return sigsend(tid, sig) < 0 ? -ESRCH : 0;
}
addr_t sys_pause(void)
{
// Sleep until next timer tick and return -EINTR (no real signal delivery).
acquire(&tickslock);
uint t0 = ticks;
while (ticks == t0 && !myproc()->killed)
sleep(&ticks, &tickslock);
release(&tickslock);
return -EINTR;
}
addr_t sys_getrusage(void)
{
int who;
struct rusage *ru;
argint(0, &who);
if (argptr(1, (char**)&ru, sizeof(*ru)) < 0) return -1;
memset(ru, 0, sizeof(*ru));
acquire(&tickslock);
uint xt = ticks;
release(&tickslock);
ru->ru_utime.tv_sec = (long)(xt / 100);
ru->ru_utime.tv_usec = (long)((xt % 100) * 10000);
return 0;
}
addr_t sys_times(void)
{
addr_t bufaddr;
argaddr(0, &bufaddr);
if (bufaddr) {
struct tms *t = (struct tms*)user_ptr(bufaddr, sizeof(*t));
if (t) memset(t, 0, sizeof(*t));
}
acquire(&tickslock);
uint xt = ticks;
release(&tickslock);
return (addr_t)xt; // return clock ticks since boot
}
addr_t sys_sysinfo(void)
{
addr_t bufaddr;
argaddr(0, &bufaddr);
struct sysinfo *si = (struct sysinfo*)user_ptr(bufaddr, sizeof(*si));
if (!si) return -EFAULT;
memset(si, 0, sizeof(*si));
acquire(&tickslock);
uint xt = ticks;
release(&tickslock);
si->uptime = (long)(xt / 100);
// Real values: PHYSTOP is the top of usable physical memory and the page
// allocator knows exactly how much of it is still on the free list. These
// used to be the constants 256MB/128MB, invented and always wrong.
si->totalram = (uint64)PHYSTOP;
si->freeram = kalloc_free_bytes();
si->mem_unit = 1;
si->procs = (uint16)proc_num();
return 0;
}
addr_t sys_waitid(void)
{
// Simplified: wait for any child, ignore idtype/id/options.
int wpid = wait();
if (wpid < 0) return -ECHILD;
// siginfo_t pointer (arg 2) — zero-fill if provided.
addr_t infop;
argaddr(2, &infop);
if (infop) {
char *p = (char*)user_ptr(infop, 128); // sizeof(siginfo_t)
if (p) memset(p, 0, 128);
}
return 0;
}

296
kernel/sys/sys_sig.c Normal file
View file

@ -0,0 +1,296 @@
#include "types.h"
#include "defs.h"
#include "param.h"
#include "proc.h"
#include "syscall.h"
#include "uaccess.h"
#include "errno.h"
// Linux sigset_t puts signal N at bit N-1; kernel-internal masks
// (pending_signals / signal_mask / deliver_signals) put signal N at bit N.
// Convert at the user boundary — storing user sets raw silently shifted
// every mask by one signal (blocking SIGCHLD actually blocked SIGCONT),
// which let mksh's SIGCHLD handler run inside the very critical sections
// sigprocmask() was guarding, wrecking its job table.
static uint64 sigset_from_user(uint64 uset) { return uset << 1; }
static uint64 sigset_to_user(uint64 kset) { return kset >> 1; }
addr_t
sys_sigfillset(void)
{
uint64 *set;
if (argptr(0, (char**)&set, sizeof(uint64)) < 0)
return -1;
if (set) *set = ~0ULL;
return 0;
}
addr_t
sys_sigprocmask(void)
{
int how;
addr_t setp, oldsetp;
argint(0, &how);
argaddr(1, &setp);
argaddr(2, &oldsetp);
// oldset is optional (NULL = don't return previous mask)
if (oldsetp) {
uint64 old = sigset_to_user(myproc()->signal_mask);
put_user(old, oldsetp);
}
// set is optional (NULL = just query)
if (setp) {
uint64 uset;
if (copy_from_user(&uset, setp, sizeof(uset)) == 0) {
uint64 set = sigset_from_user(uset);
if (how == 0) myproc()->signal_mask |= set; // SIG_BLOCK
else if (how == 1) myproc()->signal_mask &= ~set; // SIG_UNBLOCK
else myproc()->signal_mask = set; // SIG_SETMASK
}
}
return 0;
}
addr_t
sys_sigwait(void)
{
uint64 *set_ptr;
int *sig_ptr;
if (argptr(0, (char**)&set_ptr, sizeof(uint64)) < 0 ||
argptr(1, (char**)&sig_ptr, sizeof(int)) < 0)
return -1;
uint64 set = sigset_from_user(*set_ptr);
acquire(&tickslock);
while (!(myproc()->pending_signals & set)) {
if (myproc()->killed) { release(&tickslock); return -1; }
sleep(myproc(), &tickslock);
}
for (int i = 0; i < 64; i++) {
if ((myproc()->pending_signals & set) & (1ULL << i)) {
myproc()->pending_signals &= ~(1ULL << i);
*sig_ptr = i;
break;
}
}
release(&tickslock);
return 0;
}
// musl's struct sigaction (musl 1.2.5 signal.h) for this target:
// sa_handler (8 bytes) at offset 0, then a 128-byte sigset_t (musl uses
// 128/sizeof(long) = 16 longs regardless of NSIG), sa_flags (4 bytes) +
// padding, then sa_restorer (8 bytes) at offset 144. We only care about
// those two 8-byte fields.
#define SA_HANDLER_OFF 0
#define SA_RESTORER_OFF 144
// rt_sigaction: store the handler + restorer so deliver_signals() (called
// from syscall()'s tail, kernel/sys/syscall.c) can actually jump to user
// code instead of silently doing nothing. sig 0 is invalid (matches
// sigaction(2)); we don't validate against NSIG beyond our own array size.
//
// User accesses go through copy_from_user/put_user (uaccess.h), which are
// page-safe. An earlier version bulk-copied the ~152-byte struct through a
// single uva2ka() translation of its start address; when the user's
// act/oldact pointer landed close enough to a page boundary, the write ran
// past the mapped page into an unrelated physical page, silently crashing
// every freshly exec'd /bin/sh (mksh calls sigaction() during startup).
addr_t
sys_rt_sigaction(void)
{
int sig;
addr_t actp, oldactp;
argint(0, &sig);
argaddr(1, &actp);
argaddr(2, &oldactp);
if (sig <= 0 || sig >= 32) return -1;
if (oldactp) {
put_user(myproc()->sig_handler[sig], oldactp + SA_HANDLER_OFF);
put_user(myproc()->sig_restorer[sig], oldactp + SA_RESTORER_OFF);
}
if (actp) {
copy_from_user(&myproc()->sig_handler[sig], actp + SA_HANDLER_OFF,
sizeof(myproc()->sig_handler[sig]));
copy_from_user(&myproc()->sig_restorer[sig], actp + SA_RESTORER_OFF,
sizeof(myproc()->sig_restorer[sig]));
}
return 0;
}
// rt_sigreturn: entered via `syscall` (rax=15) from musl's __restore_rt
// trampoline, which is exactly what we set as the manufactured return
// address in deliver_signals() below — the handler function's own `ret`
// lands here once it's done. Restore the trapframe deliver_signals()
// stashed before redirecting to the handler, so syscall()'s normal
// sysretq path resumes the interrupted code exactly where it left off.
addr_t
sys_rt_sigreturn(void)
{
addr_t saved_rax = myproc()->saved_sigtf.rax;
*myproc()->tf = myproc()->saved_sigtf;
myproc()->in_signal = 0;
// If this handler interrupted a sigsuspend(), put its pre-suspend
// signal mask back now that the handler has finished.
if (myproc()->sigsusp_active) {
myproc()->signal_mask = myproc()->sigsusp_oldmask;
myproc()->sigsusp_active = 0;
}
// syscall() does `tf->rax = <our return value>` right after this
// returns — hand back the already-restored rax so that assignment is
// a harmless no-op instead of clobbering what we just restored.
return saved_rax;
}
// Called from syscall()'s tail (kernel/sys/syscall.c), right before
// returning to user mode, for every syscall a process makes (this is the
// only "check point" we have without also hooking the interrupt-return
// path in trap.c — sufficient for what actually needed this: a process
// blocked in a syscall, like sigsuspend(), waiting for its own next
// syscall return to notice a signal arrived).
//
// If there's a pending, unblocked signal with a registered handler,
// redirect tf to it: rdi = signal number (SysV ABI first arg), rip =
// handler.
//
// The return address (what the handler's own `ret` lands on) can't be
// sig_restorer[sig] — this musl build has no SA_RESTORER defined at all
// (checked: not defined anywhere in musl 1.2.5), so sigaction() never
// fills it in; it's always 0. Pushing 0 as a return address is exactly
// what crashed every mksh instance the first time a signal was ever
// actually delivered (handler ran, `ret` jumped to address 0). Instead,
// inject a tiny trampoline directly onto the user's own stack — 7 bytes
// of machine code for `mov eax, 15; syscall` (rt_sigreturn) — below the
// current stack pointer (clear of the SysV red zone) and point the
// return address at that, the standard way a kernel supplies sigreturn
// when the C library doesn't hand it one itself.
void
deliver_signals(struct trapframe *tf)
{
if (myproc()->in_signal) return; // no nested delivery
uint64 deliverable = myproc()->pending_signals & ~myproc()->signal_mask;
if (!deliverable) return;
for (int sig = 1; sig < 32; sig++) {
if (!(deliverable & (1ULL << sig))) continue;
// SIG_DFL is 0 and SIG_IGN is 1 -- neither is an address to jump to.
//
// Only the 0 case was checked here. A process that had asked to
// ignore a signal got its handler "called" at address 1, which is a
// page fault at rip=0x1 and a dead process. Nothing sent SIGPIPE
// until pipes learned to, at which point every shell that ignores
// SIGPIPE -- which is every shell -- died the first time a pipeline
// ended early. `cat big | head -c 5` was enough.
//
// The default action for a signal with no handler has already been
// applied by sigsend(); all that is left here is to stop it being
// pending, which the old `continue` never did, so an ignored signal
// was re-examined on the way out of every syscall forever after.
if (myproc()->sig_handler[sig] <= 1) {
myproc()->pending_signals &= ~(1ULL << sig);
continue;
}
uint64 tramp_addr = (tf->rsp - 256) & ~0xFULL;
uint8 *tramp = (uint8*)uva2ka(myproc()->pgdir, (char*)tramp_addr);
if (!tramp) continue; // can't deliver safely; try another pending signal
uint64 sp = tramp_addr - 8;
uint64 *retaddr = (uint64*)uva2ka(myproc()->pgdir, (char*)sp);
if (!retaddr) continue;
// mov eax, 15 ; syscall
tramp[0] = 0xb8; tramp[1] = 0x0f; tramp[2] = 0x00;
tramp[3] = 0x00; tramp[4] = 0x00;
tramp[5] = 0x0f; tramp[6] = 0x05;
*retaddr = tramp_addr;
myproc()->pending_signals &= ~(1ULL << sig);
myproc()->saved_sigtf = *tf;
myproc()->in_signal = 1;
tf->rsp = sp;
tf->rdi = (uint64)sig;
// Both return paths must point at the handler: iretq-based returns
// use tf->rip, but syscall_trapret exits via sysretq, which takes
// the user RIP from RCX and *discards* tf->rip (trapasm.S pops rcx
// from the frame, then `addq $40` throws rip/cs/rflags away). With
// only tf->rip set, the "delivered" handler never actually ran on
// the syscall path: user code resumed at the old rcx with the
// shifted rsp, and its ret fell into the sigreturn trampoline,
// silently undoing everything. exec() handles this same quirk by
// setting tf->rcx = elf.entry.
tf->rip = myproc()->sig_handler[sig];
tf->rcx = myproc()->sig_handler[sig];
return;
}
}
// sigaltstack: set/get alternate signal stack.
// We don't deliver signals, so just store/return zeros.
addr_t sys_sigaltstack(void) { return 0; }
// rt_sigpending: write the set of pending signals to user buffer.
addr_t sys_rt_sigpending(void)
{
addr_t setp;
argaddr(0, &setp);
if (setp) {
uint64 pend = sigset_to_user(myproc()->pending_signals);
put_user(pend, setp);
}
return 0;
}
// rt_sigtimedwait: wait for a signal from the set; returns -EAGAIN immediately.
addr_t sys_rt_sigtimedwait(void) { return -EAGAIN; }
// rt_sigsuspend: atomically replace the signal mask and suspend until a
// signal not blocked by the *temporary* mask is pending, then return -EINTR.
//
// The atomic mask swap is the entire point of sigsuspend(2): mksh blocks
// SIGCHLD while it inspects its job table, then calls sigsuspend() with the
// old (SIGCHLD-unblocked) mask to wait for a child. The previous stub
// ignored the mask argument and just slept one tick — SIGCHLD stayed
// blocked, deliver_signals() could never fire the handler, and the shell
// hung forever after running any external command (unreaped zombie child,
// shell parked in sigsuspend: the "can't type after a command" hang).
//
// The temporary mask must still be in effect when deliver_signals() runs at
// syscall()'s tail (that's what allows the delivery), so the old mask can't
// be restored here — sys_rt_sigreturn() restores it once the handler
// finishes, via sigsusp_oldmask/sigsusp_active.
addr_t sys_rt_sigsuspend(void)
{
addr_t maskp;
uint64 newmask;
argaddr(0, &maskp);
uint64 oldmask = myproc()->signal_mask;
if (maskp && copy_from_user(&newmask, maskp, sizeof(newmask)) == 0)
myproc()->signal_mask = sigset_from_user(newmask);
acquire(&tickslock);
while (!(myproc()->pending_signals & ~myproc()->signal_mask) && !myproc()->killed)
sleep(&ticks, &tickslock);
release(&tickslock);
// Will any pending signal actually reach a handler? If so, the mask is
// restored by sigreturn after the handler runs; otherwise (no handler
// installed, or we were killed) restore it right now.
uint64 deliverable = myproc()->pending_signals & ~myproc()->signal_mask;
int will_deliver = 0;
for (int sig = 1; sig < 32; sig++)
if ((deliverable & (1ULL << sig)) && myproc()->sig_handler[sig])
{ will_deliver = 1; break; }
if (will_deliver && !myproc()->in_signal) {
myproc()->sigsusp_oldmask = oldmask;
myproc()->sigsusp_active = 1;
} else {
myproc()->signal_mask = oldmask;
}
return -EINTR;
}

139
kernel/sys/sys_time.c Normal file
View file

@ -0,0 +1,139 @@
#include "types.h"
#include "defs.h"
#include "param.h"
#include "date.h"
#include "spinlock.h"
#include "proc.h"
#include "x86.h"
#include "syscall.h"
#include "posix.h"
#include "uaccess.h"
#include "errno.h"
addr_t
sys_alarm(void)
{
int seconds;
if (argint(0, &seconds) < 0)
return -1;
myproc()->alarm_ticks = seconds * 100;
return 0;
}
addr_t
sys_gettimeofday(void)
{
struct timeval *tv;
if (argptr(0, (char**)&tv, sizeof(*tv)) < 0)
return -1;
if (tv) {
// Real time, from the hardware clock read at boot plus uptime -- not
// uptime alone, which is what this used to return and why every machine
// thought it was 1970. See kernel/core/time.c.
uint64 sec, usec;
wallclock(&sec, &usec);
tv->tv_sec = (long)sec;
tv->tv_usec = (long)usec;
}
return 0;
}
addr_t
sys_nanosleep(void)
{
addr_t tsaddr, raw;
if (argaddr(0, &tsaddr) < 0 || fetchaddr(tsaddr, &raw) < 0)
return -1;
int64 sec = (int64)raw;
if (sec > 0) {
acquire(&tickslock);
uint t0 = ticks;
while (ticks - t0 < (uint)(sec * 100)) {
if (myproc()->killed) { release(&tickslock); return -EINTR; }
sleep(&ticks, &tickslock);
}
release(&tickslock);
}
return 0;
}
addr_t
sys_clock_gettime(void)
{
int clkid;
struct timespec *tp;
argint(0, &clkid);
if (argptr(1, (char**)&tp, sizeof(*tp)) < 0)
return -1;
// CLOCK_MONOTONIC (1) counts from boot; everything else is treated as a
// realtime clock and counts from the epoch. Returning boot-relative values
// for CLOCK_REALTIME is what made timestamps land in 1970.
uint64 sec, usec;
wallclock(&sec, &usec);
if (clkid == 1)
sec -= boot_epoch;
tp->tv_sec = (long)sec;
tp->tv_nsec = (long)(usec * 1000);
return 0;
}
addr_t sys_getitimer(void) { return 0; } // stub
addr_t
sys_setitimer(void)
{
int which;
addr_t newp;
argint(0, &which); argaddr(1, &newp);
// We don't implement interval timers; just track the alarm if it's ITIMER_REAL.
if (which == 0 && newp) {
struct itimerval iv;
if (copy_from_user(&iv, newp, sizeof(iv)) == 0)
myproc()->alarm_ticks = (int)(iv.it_value.tv_sec * 100 +
iv.it_value.tv_usec / 10000);
}
return 0;
}
addr_t
sys_time(void)
{
addr_t tloc;
argaddr(0, &tloc);
uint64 sec;
wallclock(&sec, 0);
long t = (long)sec;
if (tloc)
put_user(t, tloc);
return (addr_t)t;
}
addr_t
sys_clock_getres(void)
{
struct timespec *tp;
if (argptr(1, (char**)&tp, sizeof(*tp)) < 0) return 0;
if (tp) { tp->tv_sec = 0; tp->tv_nsec = 10000000L; } // 10ms resolution
return 0;
}
addr_t
sys_clock_nanosleep(void)
{
// Treat the same as nanosleep: arg2 is the timespec, arg1 is flags.
// Linux: clock_nanosleep(clockid, flags, rqtp, rmtp)
addr_t tsaddr, raw;
if (argaddr(2, &tsaddr) < 0 || fetchaddr(tsaddr, &raw) < 0)
return -1;
int64 sec = (int64)raw;
if (sec > 0) {
acquire(&tickslock);
uint t0 = ticks;
while (ticks - t0 < (uint)(sec * 100)) {
if (myproc()->killed) { release(&tickslock); return -EINTR; }
sleep(&ticks, &tickslock);
}
release(&tickslock);
}
return 0;
}

141
kernel/sys/syscall.c Normal file
View file

@ -0,0 +1,141 @@
#include "types.h"
#include "defs.h"
#include "param.h"
#include "memlayout.h"
#include "mmu.h"
#include "traps.h"
#include "proc.h"
#include "x86.h"
#include "syscall.h"
#include "uaccess.h"
#include "errno.h"
#include "posix.h"
// -----------------------------------------------------------------------
// Argument helpers — read syscall args from the saved register frame.
// x86-64 SysV ABI: rdi, rsi, rdx, r10, r8, r9
// -----------------------------------------------------------------------
// Note: address validation goes through uaccess.h (page-table presence +
// user bit on every page of the range). There is deliberately no
// `addr < myproc()->sz` check: that would assume every valid user pointer
// lives in the heap — false for stack addresses and mmap()'d buffers
// (both live above myproc()->sz), which made syscalls fail whenever a
// Linux-ABI program passed a string/pointer from either region.
//
// Validated pointers are returned as raw user VAs — the process page
// table is live in kernel mode, so they dereference correctly across
// page boundaries (unlike a uva2ka() alias, which is only good within
// the one page it translated).
int
fetchint(addr_t addr, int *ip)
{
return copy_from_user(ip, addr, sizeof(*ip)) ? -1 : 0;
}
int
fetchaddr(addr_t addr, addr_t *ip)
{
return copy_from_user(ip, addr, sizeof(*ip)) ? -1 : 0;
}
int
fetchstr(addr_t addr, char **pp)
{
long len = strlen_user(addr);
if (len < 0)
return -1;
*pp = (char*)addr;
return len;
}
static addr_t
fetcharg(int n)
{
switch (n) {
case 0: return myproc()->tf->rdi;
case 1: return myproc()->tf->rsi;
case 2: return myproc()->tf->rdx;
case 3: return myproc()->tf->r10;
case 4: return myproc()->tf->r8;
case 5: return myproc()->tf->r9;
}
panic("fetcharg: bad n");
return 0;
}
int argint(int n, int *ip) { *ip = (int)fetcharg(n); return 0; }
int argaddr(int n, addr_t *ip) { *ip = fetcharg(n); return 0; }
int
argptr(int n, char **pp, int size)
{
addr_t i;
if (argaddr(n, &i) < 0)
return -1;
if (size < 0)
return -1;
char *p = user_ptr(i, (uint64)size);
if (!p)
return -1;
*pp = p;
return 0;
}
int
argstr(int n, char **pp)
{
addr_t addr;
if (argaddr(n, &addr) < 0)
return -1;
return fetchstr(addr, pp);
}
// -----------------------------------------------------------------------
// Dispatch table — auto-generated from table.inc
// -----------------------------------------------------------------------
#define SYSCALL(nr, lnr, name) extern addr_t sys_##name(void);
#define SYSCALL_ALIAS(lnr, name)
#include "table.inc"
#undef SYSCALL
#undef SYSCALL_ALIAS
static addr_t (*syscalls[NSYSCALLS])(void) = {
#define SYSCALL(nr, lnr, name) [SYS_##name] = sys_##name,
#define SYSCALL_ALIAS(lnr, name)
#include "table.inc"
#undef SYSCALL
#undef SYSCALL_ALIAS
};
// -----------------------------------------------------------------------
// Main entry point called from trap.c
// -----------------------------------------------------------------------
void
syscall(struct trapframe *tf)
{
kstack_check_canary("syscall entry");
myproc()->tf = tf;
uint64 orig = myproc()->tf->rax;
uint64 num = orig;
if (myproc()->syscall_map) {
num = (num < (uint64)myproc()->map_size) ? myproc()->syscall_map[num] : 0;
}
if (num > 0 && num < NSYSCALLS && syscalls[num]) {
tf->rax = syscalls[num]();
} else {
tf->rax = -ENOSYS;
}
if (myproc()->killed)
exit(W_EXITCODE(0, myproc()->killsig ? myproc()->killsig : SIGKILL));
deliver_signals(tf);
kstack_check_canary("syscall exit");
}

282
kernel/sys/table.inc Normal file
View file

@ -0,0 +1,282 @@
// Segregated Kernel -- central syscall table. ONE line per syscall.
//
// Format: SYSCALL(nr, linux_nr, name)
// nr Schunix 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: Schunix 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)
// --- 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)
// --- 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)
// accept4() differs from accept() only in a flags argument naming
// SOCK_CLOEXEC/SOCK_NONBLOCK, neither of which is honoured yet.
SYSCALL_ALIAS( 288, accept)