diff --git a/kernel/src/Api/BuildNo.hpp b/kernel/src/Api/BuildNo.hpp index 4ecbc75..394cbc7 100644 --- a/kernel/src/Api/BuildNo.hpp +++ b/kernel/src/Api/BuildNo.hpp @@ -12,4 +12,4 @@ #pragma once -#define MONTAUK_BUILD_NUMBER 13 +#define MONTAUK_BUILD_NUMBER 15 diff --git a/kernel/src/Api/Process.hpp b/kernel/src/Api/Process.hpp index 57a0ecd..cbf959e 100644 --- a/kernel/src/Api/Process.hpp +++ b/kernel/src/Api/Process.hpp @@ -20,7 +20,7 @@ namespace montauk::abi { static void Sys_Exit(int exitCode) { - (void)exitCode; + Sched::SetProcessExitCode(Sched::GetCurrentPid(), exitCode & 0xFF); Sched::ExitProcess(); } @@ -36,8 +36,11 @@ namespace montauk::abi { return Sched::GetCurrentPid(); } - static void Sys_WaitPid(int pid) { + // 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); } static int Sys_Spawn(const char* path, const char* args) { diff --git a/kernel/src/Api/Syscall.cpp b/kernel/src/Api/Syscall.cpp index 0b54a95..c8614d3 100644 --- a/kernel/src/Api/Syscall.cpp +++ b/kernel/src/Api/Syscall.cpp @@ -47,7 +47,8 @@ namespace montauk::abi { static constexpr uint64_t kMaxPrintableStringBytes = 4096; static constexpr uint64_t kMaxPathBytes = 256; - static constexpr uint64_t kMaxArgsBytes = 256; + // Large enough for compiler driver command lines (cc1/ld invocations). + static constexpr uint64_t kMaxArgsBytes = 4096; static constexpr uint64_t kMaxWindowTitleBytes = 256; static constexpr uint64_t kMaxHostnameBytes = 256; static constexpr uint64_t kMaxUserNameBytes = 32; @@ -140,8 +141,7 @@ namespace montauk::abi { return (int64_t)Sys_Spawn((const char*)frame->arg1, UserMemory::IsUserPtr(frame->arg2) ? (const char*)frame->arg2 : nullptr); case SYS_WAITPID: - Sys_WaitPid((int)frame->arg1); - return 0; + return Sys_WaitPid((int)frame->arg1); case SYS_FBINFO: if (!UserMemory::Writable(frame->arg1)) return -1; Sys_FbInfo((FbInfo*)frame->arg1); diff --git a/kernel/src/Hal/IDT.cpp b/kernel/src/Hal/IDT.cpp index 4bdb01e..6585805 100644 --- a/kernel/src/Hal/IDT.cpp +++ b/kernel/src/Hal/IDT.cpp @@ -143,6 +143,16 @@ namespace Hal { CrashReport::AddReport(rep); Sched::SpawnCrashPad(proc->pid); + // Record a killed-by-signal exit code (256+sig) so a parent + // blocked in SYS_WAITPID can tell a crash from a clean exit. + { + int sig = 11; /* SIGSEGV */ + if (i == 0x00) sig = 8; /* #DE -> SIGFPE */ + else if (i == 0x06) sig = 4; /* #UD -> SIGILL */ + else if (i == 0x10 || i == 0x13) sig = 8; /* x87/SIMD FP */ + Sched::SetProcessExitCode(proc->pid, 256 + sig); + } + Sched::ExitProcess(); __builtin_unreachable(); } else { diff --git a/kernel/src/Sched/Scheduler.cpp b/kernel/src/Sched/Scheduler.cpp index 6a110b1..575c84a 100644 --- a/kernel/src/Sched/Scheduler.cpp +++ b/kernel/src/Sched/Scheduler.cpp @@ -43,6 +43,15 @@ namespace Sched { static Process processTable[MaxProcesses]; static int nextPid = 0; + // Exit-code ledger. Pids are monotonic and never recycled, so entries + // cannot alias. Written under schedLock during process teardown; read + // by SYS_WAITPID after the waiter unblocks. Codes: 0..255 = normal + // exit status, 256+signal = killed or crashed. + static constexpr int ExitLedgerSize = 128; + static int exitLedgerPid[ExitLedgerSize]; + static int exitLedgerCode[ExitLedgerSize]; + static int exitLedgerHead = 0; + // The scheduler lock MUST be a Spinlock (interrupt-disabling). // It is held ACROSS context switches to prevent the race where // another CPU picks up a process whose RSP hasn't been saved yet. @@ -212,6 +221,11 @@ namespace Sched { } nextPid = 0; + for (int i = 0; i < ExitLedgerSize; i++) { + exitLedgerPid[i] = -1; + exitLedgerCode[i] = 0; + } + exitLedgerHead = 0; Hal::RegisterIrqHandler(Hal::IRQ_RESCHEDULE, RescheduleIpiHandler); Kt::KernelLogStream(Kt::OK, "Sched") << "Initialized (" << MaxProcesses @@ -389,7 +403,7 @@ namespace Sched { proc.args[0] = '\0'; if (args != nullptr) { int i = 0; - for (; i < 255 && args[i]; i++) { + for (; i < 4095 && args[i]; i++) { proc.args[i] = args[i]; } proc.args[i] = '\0'; @@ -1152,6 +1166,12 @@ namespace Sched { proc.runningOnCpu = -1; proc.reapReady = true; + // Publish the exit code before waking waiters so SYS_WAITPID + // observes it the moment it unblocks. + exitLedgerPid[exitLedgerHead] = exitingPid; + exitLedgerCode[exitLedgerHead] = proc.exitCode; + exitLedgerHead = (exitLedgerHead + 1) % ExitLedgerSize; + // Wake any processes blocked on this PID for (int i = 0; i < MaxProcesses; i++) { if (processTable[i].state == ProcessState::Blocked && @@ -1232,6 +1252,8 @@ namespace Sched { return -1; } + processTable[primarySlot_].exitCode = 256 + 9; /* killed (SIGKILL) */ + // Flag the main thread so its next tick (or scheduler dispatch) // routes through ExitProcess, which sweeps every sibling thread // and tears down the address space. If the main thread is parked @@ -1263,6 +1285,33 @@ namespace Sched { return 0; } + int LookupExitCode(int pid) { + schedLock.Acquire(); + int code = 0; + for (int i = 0; i < ExitLedgerSize; i++) { + if (exitLedgerPid[i] == pid) { + code = exitLedgerCode[i]; + break; + } + } + schedLock.Release(); + return code; + } + + void SetProcessExitCode(int pid, int code) { + schedLock.Acquire(); + for (int i = 0; i < MaxProcesses; i++) { + if (processTable[i].pid != pid) continue; + // Only a process (primary slot) carries a process exit code; + // sibling-thread exit values go through the join path. + if (processTable[i].primarySlot == i) { + processTable[i].exitCode = code; + } + break; + } + schedLock.Release(); + } + void BlockOnPid(int pid) { // If the target is already dead, return immediately if (!IsAlive(pid)) return; diff --git a/kernel/src/Sched/Scheduler.hpp b/kernel/src/Sched/Scheduler.hpp index 48811f4..41ced12 100644 --- a/kernel/src/Sched/Scheduler.hpp +++ b/kernel/src/Sched/Scheduler.hpp @@ -54,7 +54,7 @@ namespace Sched { uint64_t userStackTop; // User-space stack top uint64_t heapNext; // Simple bump allocator for user heap uint32_t readdirCursor; // Next SYS_READDIR scratch slot - char args[256]; // Command-line arguments (set by parent via Spawn) + char args[4096]; // Command-line arguments (set by parent via Spawn) char user[32]; // Owner user name (inherited from parent on spawn) char cwd[256]; // Absolute current working directory @@ -158,6 +158,8 @@ namespace Sched { // Block the current process until the given PID exits. void BlockOnPid(int pid); + int LookupExitCode(int pid); + void SetProcessExitCode(int pid, int code); // Block the current process for the given number of milliseconds. void BlockForSleep(uint64_t ms); diff --git a/programs/include/libc/errno.h b/programs/include/libc/errno.h index 7767205..3459869 100644 --- a/programs/include/libc/errno.h +++ b/programs/include/libc/errno.h @@ -51,6 +51,7 @@ extern int errno; #define EWOULDBLOCK EAGAIN #define EOVERFLOW 75 #define ETIMEDOUT 110 +#define ENOTSUP 95 #ifdef __cplusplus } diff --git a/programs/include/libc/spawn.h b/programs/include/libc/spawn.h new file mode 100644 index 0000000..bfe17bc --- /dev/null +++ b/programs/include/libc/spawn.h @@ -0,0 +1,60 @@ +#ifndef _LIBC_SPAWN_H +#define _LIBC_SPAWN_H + +#pragma once + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/* + * posix_spawn for MontaukOS, layered on SYS_SPAWN. + * + * Limitations (kernel spawn model): + * - argv is joined into a single args string; arguments containing + * spaces are rejected with EINVAL (no quoting in the kernel). + * - envp is ignored (no environment transfer on spawn). + * - file actions must be empty: stdio redirection needs kernel + * support that does not exist yet, so any recorded action makes + * posix_spawn fail with ENOTSUP rather than misbehave silently. +*/ + +typedef struct { + short flags; +} posix_spawnattr_t; + +typedef struct { + int action_count; +} posix_spawn_file_actions_t; + +int posix_spawnattr_init(posix_spawnattr_t *attr); +int posix_spawnattr_destroy(posix_spawnattr_t *attr); +int posix_spawnattr_setflags(posix_spawnattr_t *attr, short flags); +int posix_spawnattr_getflags(const posix_spawnattr_t *attr, short *flags); + +int posix_spawn_file_actions_init(posix_spawn_file_actions_t *actions); +int posix_spawn_file_actions_destroy(posix_spawn_file_actions_t *actions); +int posix_spawn_file_actions_adddup2(posix_spawn_file_actions_t *actions, + int fd, int newfd); +int posix_spawn_file_actions_addclose(posix_spawn_file_actions_t *actions, + int fd); +int posix_spawn_file_actions_addopen(posix_spawn_file_actions_t *actions, + int fd, const char *path, int oflag, + mode_t mode); + +int posix_spawn(pid_t *pid, const char *path, + const posix_spawn_file_actions_t *actions, + const posix_spawnattr_t *attr, + char *const argv[], char *const envp[]); +int posix_spawnp(pid_t *pid, const char *file, + const posix_spawn_file_actions_t *actions, + const posix_spawnattr_t *attr, + char *const argv[], char *const envp[]); + +#ifdef __cplusplus +} +#endif + +#endif /* _LIBC_SPAWN_H */ diff --git a/programs/include/libc/sys/wait.h b/programs/include/libc/sys/wait.h index 169855a..8d31a16 100644 --- a/programs/include/libc/sys/wait.h +++ b/programs/include/libc/sys/wait.h @@ -9,12 +9,12 @@ extern "C" { #endif -/* Status decoding. The Montauk kernel does not yet report exit codes - through SYS_WAITPID, so waited children always decode as exit 0. */ +/* POSIX status decoding. waitpid() encodes a normal exit as code<<8 + and a killed/crashed child as the signal number in the low bits. */ #define WIFEXITED(s) (((s) & 0x7F) == 0) #define WEXITSTATUS(s) (((s) >> 8) & 0xFF) -#define WIFSIGNALED(s) (0) -#define WTERMSIG(s) (0) +#define WIFSIGNALED(s) (((s) & 0x7F) != 0) +#define WTERMSIG(s) ((s) & 0x7F) #define WIFSTOPPED(s) (0) #define WSTOPSIG(s) (0) diff --git a/programs/include/montauk/syscall.h b/programs/include/montauk/syscall.h index dac2896..88c44d6 100644 --- a/programs/include/montauk/syscall.h +++ b/programs/include/montauk/syscall.h @@ -321,7 +321,9 @@ namespace montauk { } // Process management - inline void waitpid(int pid) { syscall1(montauk::abi::SYS_WAITPID, (uint64_t)pid); } + // Blocks until pid exits. Returns 0..255 for a normal exit, + // 256+signal when the process was killed or crashed. + inline int waitpid(int pid) { return (int)syscall1(montauk::abi::SYS_WAITPID, (uint64_t)pid); } // Framebuffer inline void fb_info(montauk::abi::FbInfo* info) { syscall1(montauk::abi::SYS_FBINFO, (uint64_t)info); } diff --git a/programs/lib/libc/crt/crt1.c b/programs/lib/libc/crt/crt1.c index 4113025..0a16c6a 100644 --- a/programs/lib/libc/crt/crt1.c +++ b/programs/lib/libc/crt/crt1.c @@ -37,23 +37,24 @@ static inline long _sys2(long nr, long a1, long a2) { extern int main(int argc, char** argv); void _start(void) { - char argbuf[256]; + /* Static: 4 KiB args + 256 argv slots would crowd a 32 KiB stack. */ + static char argbuf[4096]; + static char* argv[256]; int len = (int)_sys2(SYS_GETARGS, (long)argbuf, (long)sizeof(argbuf)); - char* argv[32]; int argc = 0; argv[argc++] = (char*)"prog"; if (len > 0) { - if (len > 255) { - len = 255; + if (len > (int)sizeof(argbuf) - 1) { + len = (int)sizeof(argbuf) - 1; } argbuf[len] = '\0'; char* p = argbuf; - while (*p != '\0' && argc < 31) { + while (*p != '\0' && argc < 255) { while (*p == ' ') { p++; } diff --git a/programs/lib/libc/crt1.o b/programs/lib/libc/crt1.o index 5186a5a..a95f5da 100644 Binary files a/programs/lib/libc/crt1.o and b/programs/lib/libc/crt1.o differ diff --git a/programs/lib/libc/libc.c b/programs/lib/libc/libc.c index 1ad0397..79c86e1 100644 --- a/programs/lib/libc/libc.c +++ b/programs/lib/libc/libc.c @@ -21,6 +21,7 @@ #include #include #include +#include #include #include @@ -3404,17 +3405,21 @@ int dup2(int oldfd, int newfd) { return newfd; } -/* SYS_WAITPID blocks until the child exits but does not report its - exit code yet, so successful waits decode as exit status 0. */ +/* SYS_WAITPID blocks until the child exits and returns 0..255 for a + normal exit or 256+signal for a killed/crashed child. */ pid_t waitpid(pid_t pid, int *status, int options) { (void)options; if (pid <= 0) { errno = ECHILD; /* no process groups / wait-any support */ return -1; } - _zos_syscall1(SYS_WAITPID, (long)pid); + long code = _zos_syscall1(SYS_WAITPID, (long)pid); if (status != NULL) { - *status = 0; + if (code >= 256) { + *status = (int)((code - 256) & 0x7F); /* signaled */ + } else { + *status = (int)((code & 0xFF) << 8); /* exited */ + } } return pid; } @@ -3448,6 +3453,154 @@ long pathconf(const char *path, int name) { } } +/* ======================================================================== + posix_spawn (spawn.h) over SYS_SPAWN + ======================================================================== */ + +int posix_spawnattr_init(posix_spawnattr_t *attr) { + if (attr == NULL) return EINVAL; + attr->flags = 0; + return 0; +} + +int posix_spawnattr_destroy(posix_spawnattr_t *attr) { + (void)attr; + return 0; +} + +int posix_spawnattr_setflags(posix_spawnattr_t *attr, short flags) { + if (attr == NULL) return EINVAL; + attr->flags = flags; + return 0; +} + +int posix_spawnattr_getflags(const posix_spawnattr_t *attr, short *flags) { + if (attr == NULL || flags == NULL) return EINVAL; + *flags = attr->flags; + return 0; +} + +int posix_spawn_file_actions_init(posix_spawn_file_actions_t *actions) { + if (actions == NULL) return EINVAL; + actions->action_count = 0; + return 0; +} + +int posix_spawn_file_actions_destroy(posix_spawn_file_actions_t *actions) { + (void)actions; + return 0; +} + +/* Actions are counted but cannot be honored yet (no kernel support for + stdio redirection on spawn); posix_spawn refuses non-empty sets. */ +int posix_spawn_file_actions_adddup2(posix_spawn_file_actions_t *actions, + int fd, int newfd) { + (void)fd; (void)newfd; + if (actions == NULL) return EINVAL; + actions->action_count++; + return 0; +} + +int posix_spawn_file_actions_addclose(posix_spawn_file_actions_t *actions, + int fd) { + (void)fd; + if (actions == NULL) return EINVAL; + actions->action_count++; + return 0; +} + +int posix_spawn_file_actions_addopen(posix_spawn_file_actions_t *actions, + int fd, const char *path, int oflag, + mode_t mode) { + (void)fd; (void)path; (void)oflag; (void)mode; + if (actions == NULL) return EINVAL; + actions->action_count++; + return 0; +} + +int posix_spawn(pid_t *pid, const char *path, + const posix_spawn_file_actions_t *actions, + const posix_spawnattr_t *attr, + char *const argv[], char *const envp[]) { + (void)attr; + (void)envp; /* no environment transfer on Montauk spawn */ + + if (path == NULL) { + return EINVAL; + } + if (actions != NULL && actions->action_count > 0) { + return ENOTSUP; /* redirection needs kernel support */ + } + + /* Join argv[1..] into the kernel's single args string. The kernel + tokenizer has no quoting, so embedded spaces cannot round-trip. */ + static char argsbuf[4096]; + size_t o = 0; + argsbuf[0] = '\0'; + if (argv != NULL) { + for (int i = 1; argv[i] != NULL; i++) { + size_t alen = strlen(argv[i]); + for (size_t k = 0; k < alen; k++) { + if (argv[i][k] == ' ') { + return EINVAL; + } + } + if (o + alen + 2 >= sizeof(argsbuf)) { + return E2BIG; + } + if (o > 0) { + argsbuf[o++] = ' '; + } + memcpy(argsbuf + o, argv[i], alen); + o += alen; + } + } + argsbuf[o] = '\0'; + + long child = _zos_syscall2(SYS_SPAWN, (long)path, (long)argsbuf); + if (child < 0) { + return ENOENT; + } + if (pid != NULL) { + *pid = (pid_t)child; + } + return 0; +} + +int posix_spawnp(pid_t *pid, const char *file, + const posix_spawn_file_actions_t *actions, + const posix_spawnattr_t *attr, + char *const argv[], char *const envp[]) { + if (file == NULL) { + return EINVAL; + } + + int has_slash = 0; + for (const char *p = file; *p; p++) { + if (*p == '/') { + has_slash = 1; + break; + } + } + if (has_slash) { + return posix_spawn(pid, file, actions, attr, argv, envp); + } + + /* Bare name: search the SDK and system tool directories. */ + static const char *prefixes[] = { "/sdk/bin/", "/os/", "" }; + char candidate[512]; + for (int pi = 0; pi < 3; pi++) { + for (int ext = 0; ext < 2; ext++) { + snprintf(candidate, sizeof(candidate), "%s%s%s", + prefixes[pi], file, ext ? "" : ".elf"); + if (access(candidate, F_OK) == 0) { + return posix_spawn(pid, candidate, actions, attr, argv, envp); + } + } + } + return ENOENT; +} + /* The libc environment lives in name/value slots (getenv/setenv), not NAME=VALUE strings, and exec* is unimplemented, so nothing can consume a populated environ yet. An empty, valid vector satisfies diff --git a/programs/lib/libc/obj/libc.o b/programs/lib/libc/obj/libc.o index c056514..adcb219 100644 Binary files a/programs/lib/libc/obj/libc.o and b/programs/lib/libc/obj/libc.o differ diff --git a/programs/lib/libjpeg/libjpeg.a b/programs/lib/libjpeg/libjpeg.a index 5a4ee17..a61cc2f 100644 Binary files a/programs/lib/libjpeg/libjpeg.a and b/programs/lib/libjpeg/libjpeg.a differ diff --git a/programs/lib/libjpegwrite/libjpegwrite.a b/programs/lib/libjpegwrite/libjpegwrite.a index 4f52a5a..7ec51c2 100644 Binary files a/programs/lib/libjpegwrite/libjpegwrite.a and b/programs/lib/libjpegwrite/libjpegwrite.a differ diff --git a/programs/lib/tls/libtls.a b/programs/lib/tls/libtls.a index 3284474..fa96053 100644 Binary files a/programs/lib/tls/libtls.a and b/programs/lib/tls/libtls.a differ diff --git a/programs/lib/tls/obj/tls.o b/programs/lib/tls/obj/tls.o index 684d4b5..1b4ca61 100644 Binary files a/programs/lib/tls/obj/tls.o and b/programs/lib/tls/obj/tls.o differ diff --git a/programs/src/shell/exec.cpp b/programs/src/shell/exec.cpp index 2187ae6..9747d7f 100644 --- a/programs/src/shell/exec.cpp +++ b/programs/src/shell/exec.cpp @@ -17,11 +17,33 @@ static bool file_exists(const char* path) { // ---- Try to spawn an ELF at the given path ---- +static void print_exit_code(int code) { + if (code == 0) return; + char buf[16]; + int n = 0; + if (code >= 256) { + montauk::print("[terminated by signal "); + code -= 256; + } else { + montauk::print("[exit code "); + } + if (code == 0) buf[n++] = '0'; + while (code > 0 && n < 15) { + buf[n++] = (char)('0' + code % 10); + code /= 10; + } + while (n > 0) { + char c[2] = { buf[--n], 0 }; + montauk::print(c); + } + montauk::print("]\n"); +} + static bool try_exec(const char* path, const char* args) { if (!file_exists(path)) return false; int pid = montauk::spawn(path, args); if (pid < 0) return false; - montauk::waitpid(pid); + print_exit_code(montauk::waitpid(pid)); return true; }