/* * Process.hpp * SYS_EXIT, SYS_YIELD, SYS_SLEEP_MS, SYS_GETPID, * SYS_WAITPID, SYS_SPAWN, SYS_GETARGS, SYS_PROCLIST, SYS_KILL syscalls * Copyright (c) 2026 Daniel Hammer */ #pragma once #include #include #include #include #include #include #include "Syscall.hpp" #include "Common.hpp" #include "WinServer.hpp" #include "Path.hpp" namespace montauk::abi { static void Sys_Exit(int exitCode) { Sched::SetProcessExitCode(Sched::GetCurrentPid(), exitCode & 0xFF); Sched::ExitProcess(); } static void Sys_Yield() { Sched::Schedule(); } static void Sys_SleepMs(uint64_t ms) { Sched::BlockForSleep(ms); } static int Sys_GetPid() { return Sched::GetCurrentPid(); } // Returns 0..255 for a normal exit, 256+signal for killed/crashed, // 0 when the pid is unknown or its ledger entry has been evicted. static int Sys_WaitPid(int pid) { Sched::BlockOnPid(pid); return Sched::LookupExitCode(pid); } // Hand a freshly spawned child the parent's redirected console. Both spawn // syscalls need this: a console tool launched from a GUI terminal must read // its keys from the terminal's mailbox and write its output back up the // stream, whether or not it also carries a capability grant. The child is // created suspended (startReady == false) so its first instruction cannot // run before the channels exist, and is started here once they do. // Returns childPid, or kills the child and returns -1 on failure. static int InheritRedirection(int childPid, Sched::Process* parent, int parentSlot) { auto* child = Sched::GetProcessByPid(childPid); int childSlot = Ipc::SlotForPid(childPid); if (child == nullptr || childSlot < 0 || parentSlot < 0) { Sched::KillProcess(childPid); return -1; } child->ioOutHandle = DuplicateHandleBetweenSlots(parentSlot, parent->ioOutHandle, childSlot); child->ioInHandle = DuplicateHandleBetweenSlots(parentSlot, parent->ioInHandle, childSlot); child->ioKeyHandle = DuplicateHandleBetweenSlots(parentSlot, parent->ioKeyHandle, childSlot); if (child->ioOutHandle < 0 || child->ioInHandle < 0 || child->ioKeyHandle < 0 || !ConfigureRedirWaitsetForSlot(childSlot, child)) { Sched::KillProcess(childPid); return -1; } child->redirected = true; child->parentPid = parent->pid; child->termCols = parent->termCols; child->termRows = parent->termRows; if (Sched::StartProcess(childPid) < 0) { Sched::KillProcess(childPid); return -1; } return childPid; } static int Sys_Spawn(const char* path, const char* args, const char* environment = nullptr, uint32_t environmentLength = 0) { char resolved[256]; if (!ResolveProcessPath(path, resolved, sizeof(resolved))) return -1; auto* parent = Sched::GetCurrentProcessPtr(); int parentSlot = Ipc::CurrentSlot(); bool inheritRedirection = parent && parent->redirected; int childPid = Sched::Spawn(resolved, args, !inheritRedirection, environment, environmentLength); if (childPid < 0) return childPid; if (inheritRedirection) return InheritRedirection(childPid, parent, parentSlot); return childPid; } static int Sys_SpawnCaps(const char* path, const char* args, const char* user, const SpawnCapabilities* requested) { auto* parent = Sched::GetCurrentProcessPtr(); if (parent == nullptr || requested == nullptr) return -1; // Snapshot all security-sensitive userspace inputs before evaluating // them. This prevents another thread from changing a mask or owner // name between validation and process creation. SpawnCapabilities copy = *requested; char childUser[32]; const char* userOverride = nullptr; if (user != nullptr) { if (!Sched::HasCapability(CAP_USER_ADMIN)) return SYS_ERR_PERMISSION; int i = 0; for (; i < 31 && user[i]; i++) childUser[i] = user[i]; childUser[i] = '\0'; userOverride = childUser; } // Authority may only diminish down the process tree. In particular, // possessing a capability is insufficient to pass it: the parent must // also hold it in its delegable set. if (!ValidCapabilityDelegation(copy, parent->delegableCaps)) { return SYS_ERR_PERMISSION; } char resolved[256]; if (!ResolveProcessPath(path, resolved, sizeof(resolved))) return -1; int parentSlot = Ipc::CurrentSlot(); bool inheritRedirection = parent->redirected; int childPid = Sched::Spawn(resolved, args, !inheritRedirection, nullptr, 0, ©, userOverride); if (childPid < 0) return childPid; if (inheritRedirection) return InheritRedirection(childPid, parent, parentSlot); return childPid; } // Copy the absolute path this process was spawned from (argv[0]). static int Sys_GetExecPath(char* buf, uint64_t maxLen) { auto* proc = Sched::GetCurrentProcessPtr(); if (proc == nullptr || buf == nullptr || maxLen == 0) return -1; int i = 0; for (; i < (int)maxLen - 1 && proc->name[i]; i++) { buf[i] = proc->name[i]; } buf[i] = '\0'; return i; } static int Sys_GetArgs(char* buf, uint64_t maxLen) { auto* proc = Sched::GetCurrentProcessPtr(); if (proc == nullptr || buf == nullptr || maxLen == 0) return -1; int i = 0; for (; i < (int)maxLen - 1 && proc->args[i]; i++) { buf[i] = proc->args[i]; } buf[i] = '\0'; return i; } static int Sys_GetEnviron(char* buf, uint64_t maxLen) { auto* proc = Sched::GetCurrentProcessPtr(); if (proc == nullptr || buf == nullptr || maxLen == 0) return -1; if (maxLen < proc->environmentLength) return -2; for (uint32_t i = 0; i < proc->environmentLength; i++) buf[i] = proc->environment[i]; return (int)proc->environmentLength; } static int Sys_SetEnviron(const char* buf, uint64_t len) { auto* proc = Sched::GetCurrentProcessPtr(); if (proc == nullptr || buf == nullptr || len == 0 || len > Sched::Process::EnvironmentBytes || buf[len - 1] != '\0') return -1; for (uint64_t i = 0; i < len; i++) proc->environment[i] = buf[i]; proc->environmentLength = (uint32_t)len; return 0; } static int Sys_ProcList(ProcInfo* buf, int maxCount) { if (buf == nullptr || maxCount <= 0) return 0; int count = 0; for (int i = 0; i < Sched::MaxProcesses && count < maxCount; i++) { auto* proc = Sched::GetProcessSlot(i); if (!proc || proc->state == Sched::ProcessState::Free) continue; // Skip sibling-thread slots; only show one entry per process. if (proc->primarySlot != i) continue; buf[count].pid = (int32_t)proc->pid; buf[count].parentPid = (int32_t)proc->parentPid; buf[count].state = (uint8_t)proc->state; buf[count]._pad[0] = 0; buf[count]._pad[1] = 0; buf[count]._pad[2] = 0; { int j = 0; for (; j < 63 && proc->name[j]; j++) buf[count].name[j] = proc->name[j]; buf[count].name[j] = '\0'; } buf[count].heapUsed = Sched::g_allocatedPages[i] * 0x1000; buf[count].cpuTimeMs = proc->cpuTimeMs; buf[count].permittedCaps = proc->permittedCaps; buf[count].effectiveCaps = proc->effectiveCaps; buf[count].delegableCaps = proc->delegableCaps; count++; } return count; } static int Sys_Kill(int pid) { if (!Sched::HasCapability(CAP_PROCESS_ADMIN)) { int ancestor = pid; bool descendant = false; for (int depth = 0; depth < Sched::MaxProcesses; depth++) { auto* target = Sched::GetProcessByPid(ancestor); if (target == nullptr || target->parentPid < 0) break; if (target->parentPid == Sched::GetCurrentPid()) { descendant = true; break; } ancestor = target->parentPid; } if (!descendant) return SYS_ERR_PERMISSION; } return Sched::KillProcess(pid); } static int Sys_SetSession() { return Sched::CreateSession(); } static int Sys_KillSession(int sessionId) { return Sched::KillSession(sessionId); } static int Sys_SetUser(int pid, const char* name) { if (name == nullptr) return -1; auto* target = Sched::GetProcessByPid(pid); if (target == nullptr) return -1; int i = 0; for (; i < 31 && name[i]; i++) target->user[i] = name[i]; target->user[i] = '\0'; return 0; } static int Sys_GetUser(char* buf, uint64_t maxLen) { auto* proc = Sched::GetCurrentProcessPtr(); if (proc == nullptr || buf == nullptr || maxLen == 0) return -1; int i = 0; for (; i < (int)maxLen - 1 && proc->user[i]; i++) buf[i] = proc->user[i]; buf[i] = '\0'; return i; } static int Sys_GetCwd(char* buf, uint64_t maxLen) { auto* proc = Sched::GetCurrentProcessPtr(); if (proc == nullptr || buf == nullptr || maxLen == 0) return -1; const char* cwd = proc->cwd[0] ? proc->cwd : "0:/"; uint64_t cwdLen = 0; while (cwd[cwdLen] != '\0') cwdLen++; // Reserve -1 for non-size failures so libc can distinguish them // from a buffer that can be retried with a larger allocation. if (maxLen <= cwdLen) return -2; for (uint64_t i = 0; i <= cwdLen; i++) buf[i] = cwd[i]; return (int)cwdLen; } // ==================================================================== // Threading // ==================================================================== static int Sys_ThreadSpawn(uint64_t entry, uint64_t arg, uint64_t userStackTop) { return Sched::SpawnThread(entry, arg, userStackTop); } [[noreturn]] static void Sys_ThreadExit(int exitCode) { Sched::ExitCurrentThread(exitCode); } static int Sys_ThreadJoin(int tid, int* outExitCode) { return Sched::JoinThread(tid, outExitCode); } static int Sys_ThreadSelf() { return Sched::GetCurrentTid(); } static int Sys_Chdir(const char* path) { auto* proc = Sched::GetCurrentProcessPtr(); if (proc == nullptr || path == nullptr) return -1; char resolved[256]; if (!ResolveProcessPath(path, resolved, sizeof(resolved))) return -1; // ReadDir doubles as the directory-existence probe: it fails // for nonexistent paths and for regular files (directories are // not openable as files anymore, so the old open-based check // would reject every valid target). { const char* entries[1]; if (Fs::Vfs::VfsReadDir(resolved, entries, 1) < 0) return -1; } int i = 0; for (; i < 255 && resolved[i]; i++) proc->cwd[i] = resolved[i]; proc->cwd[i] = '\0'; return 0; } };