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:
commit
d0f7acf9eb
275 changed files with 51417 additions and 0 deletions
33
kernel/sys/linux_abi.c
Normal file
33
kernel/sys/linux_abi.c
Normal 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
162
kernel/sys/sys_compat.c
Normal 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
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
479
kernel/sys/sys_mem.c
Normal 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
346
kernel/sys/sys_net.c
Normal 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
292
kernel/sys/sys_poll.c
Normal 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
451
kernel/sys/sys_proc.c
Normal 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
296
kernel/sys/sys_sig.c
Normal 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
139
kernel/sys/sys_time.c
Normal 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
141
kernel/sys/syscall.c
Normal 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
282
kernel/sys/table.inc
Normal 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)
|
||||
Loading…
Reference in a new issue