SchOS/kernel/sys/syscall.c
Nexoniarz fd9d392416 Pseudo-terminals, and what tmux and htop needed around them
- /dev/ptmx and /dev/pts/N: Unix98 pairs with a full line discipline,
  window sizes, job control and hangup (kernel/dev/pty.c).
- The console speaks scroll regions, insert/delete line, SU/SD, RI/IND/NEL,
  DECSC/DECRC; swallows charset designations and private CSI sequences;
  and LF only returns the carriage under ONLCR.
- setsid() drops the controlling terminal, so a pane's pty becomes its ctty.
- pipe2() honours O_NONBLOCK and O_CLOEXEC (libevent's signal pipe).
- File modes are stored and enforced: chmod, fchmod(at), umask, creation
  modes; /tmp is 1777.
- /proc/stat has real user/system/idle; /proc/<pid>/task; kthread flag.
- SO_PEERCRED, IOV_MAX 1024.
- /proc/strace: name a program and its syscalls go to the kernel log.
- World gains htop, btop, tmux, mc, ncdu, fastfetch, games, X toys, NetSurf.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01S1giNvrHTrPMPw7V6x1kQ4
2026-10-25 02:11:10 +01:00

225 lines
6.6 KiB
C

#include "core/types.h"
#include "core/defs.h"
#include "core/param.h"
#include "arch/memlayout.h"
#include "arch/mmu.h"
#include "arch/traps.h"
#include "core/proc.h"
#include "arch/x86.h"
#include "core/syscall.h"
#include "core/uaccess.h"
#include "uapi/errno.h"
#include "uapi/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;
}
// /proc/strace. Which argument of a Linux syscall is a path, by number, so
// the trace can show the name rather than an address: that is most of what
// one wants from a trace of a program that will not start.
static int
path_arg(uint64 nr)
{
switch (nr) {
case 2: case 4: case 6: case 21: case 59: case 76: case 80: case 82:
case 83: case 84: case 85: case 86: case 87: case 88: case 89: case 90:
case 92: case 137: case 161:
return 0;
case 257: case 258: case 260: case 262: case 263: case 264: case 267:
case 268: case 269: case 280: case 316: case 332: case 437: case 439:
return 1;
}
return -1;
}
static void strace_call(struct trapframe *tf);
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)
{
if (myproc()->strace) {
strace_call(tf);
return;
}
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");
}
// syscall() for a traced process: the same dispatch, with a line in the log
// for each call -- the Linux number, four arguments, a path where there is
// one, and what came back. The path is copied before the call runs, since
// execve replaces the memory it is in.
static void
strace_call(struct trapframe *tf)
{
struct proc *p = myproc();
uint64 orig = tf->rax, num = orig;
uint64 a0 = tf->rdi, a1 = tf->rsi, a2 = tf->rdx, a3 = tf->r10;
char path[64];
int pa = p->syscall_map ? path_arg(orig) : -1;
path[0] = 0;
if (pa >= 0) {
addr_t u = pa == 0 ? a0 : a1;
int i;
for (i = 0; i < (int)sizeof(path) - 1; i++) {
char c;
if (copy_from_user(&c, u + i, 1) < 0 || c == 0)
break;
path[i] = c;
}
path[i] = 0;
}
// The result line is written when the call returns, so a call that never
// does would leave no trace at all. Everything that can wait gets a line
// on the way in as well.
switch (orig) {
case 0: case 2: case 7: case 23: case 35: case 43: case 45: case 47:
case 59: case 61: case 202: case 232: case 257: case 270: case 271:
case 281: case 288:
if (p->syscall_map)
kinfo("strace %d %s: %d(%lx, %lx, %lx, %lx)%s%s%s ...\n",
p->pid, p->name, (int)orig, a0, a1, a2, a3,
pa >= 0 ? " \"" : "", path, pa >= 0 ? "\"" : "");
}
kstack_check_canary("syscall entry");
p->tf = tf;
if (p->syscall_map)
num = (num < (uint64)p->map_size) ? p->syscall_map[num] : 0;
if (num > 0 && num < NSYSCALLS && syscalls[num])
tf->rax = syscalls[num]();
else
tf->rax = -ENOSYS;
// A successful execve has replaced the program: its name is the new one.
kinfo("strace %d %s: %s%d(%lx, %lx, %lx, %lx)%s%s%s = %ld\n",
p->pid, p->name, p->syscall_map ? "" : "native ", (int)orig,
a0, a1, a2, a3, pa >= 0 ? " \"" : "", path, pa >= 0 ? "\"" : "",
(long)tf->rax);
if (p->killed)
exit(W_EXITCODE(0, p->killsig ? p->killsig : SIGKILL));
deliver_signals(tf);
kstack_check_canary("syscall exit");
}