Files
MontaukOS/kernel/src/Api/Process.hpp
T

327 lines
12 KiB
C++

/*
* 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 <Sched/Scheduler.hpp>
#include <Timekeeping/ApicTimer.hpp>
#include <Memory/Paging.hpp>
#include <Memory/PageFrameAllocator.hpp>
#include <Memory/HHDM.hpp>
#include <Fs/Vfs.hpp>
#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, &copy, userOverride);
if (childPid < 0) return childPid;
if (inheritRedirection)
return InheritRedirection(childPid, parent, parentSlot);
return childPid;
}
// The calling process's own capability masks.
static int Sys_GetCaps(SpawnCapabilities* out) {
auto* proc = Sched::GetCurrentProcessPtr();
if (proc == nullptr || out == nullptr) return -1;
out->effective = proc->effectiveCaps;
out->delegable = proc->delegableCaps;
return 0;
}
// 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].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;
}
};