hwreport writes one folder to the first read-write FAT mount, or mounts the first USB stick that takes vfat itself and unmounts it afterwards: summary.txt (machine, firmware, CPU, memory, every PCI and USB device with its driver or "-- no driver"), dmesg.txt, lspci.txt in the shape of lspci -nnk -xxx, lsusb.txt, dmi.txt, memmap.txt, the ACPI tables as raw .dat files iasl -d reads, and copies of /proc. Serials, the UUID and the MSDM/SLIC tables (a Windows licence key) are left out. It reads /sys, which is new: sysfs at Linux's paths and in Linux's formats -- /sys/bus/pci/devices with uevent, config and resource, /sys/bus/usb/devices, /sys/firmware/acpi/tables, dmi/tables, memmap and efi, /sys/class/dmi/id. toybox's lspci and lsusb work unmodified against it. The USB core now keeps a table of attached devices for it. The machine's name comes from SMBIOS (kernel/dev/dmi.c). The UEFI loader hands over the entry point from the configuration table, the kernel falls back to the 0xF0000 scan, and the log gets Linux's "DMI:" line. Two bugs found on the way: - The loader handed over at most 40 memory-map entries, in firmware order. A laptop's UEFI map is 60-150 descriptors with RAM above 4 GB listed last, so that is what was dropped. The entries now have their own area at 0x31000 with room for 256, are sorted before merging, and the kernel's 32 usable regions keep the largest when there are more. - MemTotal and sysinfo's totalram were PHYSTOP, an address, which counts the PCI hole: a 6 GB machine reported 8. Now the usable RAM, summed. usb.ids moves to /usr/share/hwdata, where lsusb looks for it, and pci.ids joins it (`make pci-ids`, BSD-3-Clause like usb.ids). Checked under QEMU q35: the report written to a FAT32 stick, fsck.fat clean, DSDT.dat disassembled by iasl on the host, lspci -k and lsusb showing names, 6 GB of RAM seen as 6137 MB. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01S1giNvrHTrPMPw7V6x1kQ4
532 lines
15 KiB
C
532 lines
15 KiB
C
#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"
|
|
|
|
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 ---
|
|
|
|
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;
|
|
}
|
|
|
|
// 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();
|
|
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;
|
|
}
|