319 lines
12 KiB
C++
319 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, ©, 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;
|
|
}
|
|
};
|