#include "types.h" #include "defs.h" #include "param.h" #include "memlayout.h" #include "hwinfo.h" #include "mmu.h" #include "proc.h" #include "x86.h" #include "syscall.h" #include "posix.h" #include "uaccess.h" #include "errno.h" #include "acpi.h" #include "fat.h" #include "spinlock.h" #include "sleeplock.h" #include "fs.h" #include "buf.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; } // Defined with getrusage() further down; wait4() needs it too. static void rusage_fill(struct rusage *ru, uint64 ut, uint64 st); 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, 0, 0); if (wpid > 0 && statusp) put_user(status, statusp); return wpid; } // wait4(2) -- and its fourth argument is a `struct rusage *`. // // It was read off the stack and thrown away. So a caller that passed a buffer // got it back untouched, and read whatever had been there before: zsh's // `time` reported 16777216.01s user for every command, which is 2^24 seconds, // which is uninitialised memory that happened to look like a number. The // figure did not vary with the workload because it was never written. // // wait3() is the same syscall with pid -1, and it is the one shells actually // call, so this is the path every `time` goes through. addr_t sys_wait4(void) { int pid, options; addr_t statusp, rup; uint64 ut = 0, st = 0; argint(0, &pid); argaddr(1, &statusp); argint(2, &options); argaddr(3, &rup); int status; int wpid = waitpid(pid, &status, options, &ut, &st); if (wpid <= 0) return wpid; if (statusp) put_user(status, statusp); if (rup) { struct rusage *ru = (struct rusage*)user_ptr(rup, sizeof(*ru)); if (ru) rusage_fill(ru, ut, st); } 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"); // A stick left mounted should come back readable, and marked clean. schfs_ksync_all(); fat_ksync_all(1); // The FADT's reset register first: it is the one the board's designer // wired up. Then the PCI reset port and the keyboard controller, which // are what nearly every PC has had since there were PCs. acpi_reboot(); outb(0xCF9, 0x06); outb(0x64, 0xFE); return 0; } addr_t sys_shutdown(void) { cprintf("System is shutting down...\n"); // S5, through the firmware's own PM1 registers and its _PTS. The port // writes after it are QEMU's, Bochs' and VirtualBox's fixed addresses, // kept for a machine whose AML did not load; a real board answers none // of them. schfs_ksync_all(); fat_ksync_all(1); acpi_poweroff(); 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 --- // A real yield. It returned 0 without giving anything up, which cost nothing // while every timer tick was a yield anyway; with the fair scheduler a tick // only preempts once the slice is spent, so a spin-and-yield loop (musl's // locks, a polling driver in userland) would hold the CPU for all of it. addr_t sys_sched_yield(void) { yield(); return 0; } // Every processor that started, since nothing here pins a process anywhere. // // This wrote "CPU 0" and returned 0 -- and the return value is the number of // bytes of mask the kernel wrote, which musl uses to clear the rest. Zero // meant all of it, so the mask came back empty and nproc, make -j and every // thread pool sized from sysconf(_SC_NPROCESSORS_ONLN) saw one processor. addr_t sys_sched_getaffinity(void) { int pid, cpusetsize; addr_t maskp; uchar m[(NCPU + 63) / 64 * 8]; argint(0, &pid); argint(1, &cpusetsize); argaddr(2, &maskp); if (cpusetsize < (int)sizeof m || (cpusetsize & 7)) return -EINVAL; memset(m, 0, sizeof m); for (int i = 0; i < ncpu && i < NCPU; i++) if (cpus[i].started) m[i / 8] |= 1 << (i % 8); if (copy_to_user(maskp, m, sizeof m) < 0) return -EFAULT; return sizeof m; } 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; } // getrusage(2). // // This used to ignore `who` entirely and report the machine's *uptime* as the // caller's user time. Every process of every age answered with the same // number, and RUSAGE_CHILDREN answered with it too -- so a shell timing a // command got the age of the machine, and `time` on a pipeline was // meaningless rather than merely imprecise. // // Ticks, converted at the boundary. The kernel counts whole timer ticks // because that is what the sampling gives it; microseconds are what the // interface asks for, so the multiply happens here rather than pretending to a // resolution the counter does not have. static void rusage_fill(struct rusage *ru, uint64 ut, uint64 st) { memset(ru, 0, sizeof(*ru)); ru->ru_utime.tv_sec = (long)(ut / HZ); ru->ru_utime.tv_usec = (long)((ut % HZ) * (1000000 / HZ)); ru->ru_stime.tv_sec = (long)(st / HZ); ru->ru_stime.tv_usec = (long)((st % HZ) * (1000000 / HZ)); } addr_t sys_getrusage(void) { int who; struct rusage *ru; argint(0, &who); if (argptr(1, (char**)&ru, sizeof(*ru)) < 0) return -EFAULT; switch (who) { case RUSAGE_SELF: rusage_fill(ru, myproc()->utime, myproc()->stime); return 0; case RUSAGE_CHILDREN: rusage_fill(ru, myproc()->cutime, myproc()->cstime); return 0; default: // RUSAGE_THREAD is -1 and would be RUSAGE_SELF here, since a process // has exactly one thread. It is refused rather than aliased: answering // it would imply threads exist. return -EINVAL; } } addr_t sys_times(void) { addr_t bufaddr; argaddr(0, &bufaddr); // The tms fields are in clock ticks, which is what the kernel counts, so // this one needs no conversion -- USER_HZ and HZ are both 100. They used // to be memset to zero, which is why `times()` agreed with nothing. if (bufaddr) { struct tms *t = (struct tms*)user_ptr(bufaddr, sizeof(*t)); if (t) { t->tms_utime = (long)myproc()->utime; t->tms_stime = (long)myproc()->stime; t->tms_cutime = (long)myproc()->cutime; t->tms_cstime = (long)myproc()->cstime; } } // USER_HZ units, measured from the TSC. // // musl answers sysconf(_SC_CLK_TCK) with 100 unconditionally, so a shell's // `time` builtin divides whatever this returns by 100. Deriving it from // the tick count made `time` under-report whenever ticks were being lost // -- which is how a 20-second command came to print 3.12s. uint64 tsec, tusec; monotonic(&tsec, &tusec); return (addr_t)(tsec * USER_HZ + (tusec * USER_HZ) / 1000000); } 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)); // TSC, like everything else that reports elapsed time. uint64 upsec, upusec; monotonic(&upsec, &upusec); si->uptime = (long)upsec; // Real values: the RAM the page allocator was given, and how much of it is // still on the free list. These used to be the constants 256MB/128MB, and // then PHYSTOP, an address, which counted the PCI hole as memory. si->totalram = ram_total_bytes(); si->freeram = kalloc_free_bytes(); // The buffer cache, so free(1) does not count it as used by nothing. uint nbuf, maxbuf; bcache_size(&nbuf, &maxbuf); si->bufferram = (uint64)nbuf * BSIZE; 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; }