fix: inherit process environment and untrack libc objects

This commit is contained in:
2026-08-11 13:08:40 +02:00
parent 0a91131cb0
commit 3ef98a9fd5
26 changed files with 254 additions and 36 deletions
+2 -1
View File
@@ -17,7 +17,8 @@ programs/src/*/obj/
programs/libs/*/obj/
programs/lib/libc/liblibc.a
programs/lib/libc/liblibc-full.a
programs/lib/libc/obj/libc-merged.o
programs/lib/libc/*.o
programs/lib/libc/obj/
programs/lib/libloader/liblibloader.a
programs/gui/icons/
CLAUDE.md
+1 -1
View File
@@ -12,4 +12,4 @@
#pragma once
#define MONTAUK_BUILD_NUMBER 104
#define MONTAUK_BUILD_NUMBER 106
+22 -2
View File
@@ -43,14 +43,16 @@ namespace montauk::abi {
return Sched::LookupExitCode(pid);
}
static int Sys_Spawn(const char* path, const char* args) {
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);
int childPid = Sched::Spawn(resolved, args, !inheritRedirection,
environment, environmentLength);
if (childPid < 0) return childPid;
if (inheritRedirection) {
@@ -107,6 +109,24 @@ namespace montauk::abi {
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;
+17
View File
@@ -149,8 +149,25 @@ namespace montauk::abi {
if (frame->arg2 != 0 && !UserMemory::String(frame->arg2, kMaxArgsBytes)) return -1;
return (int64_t)Sys_Spawn((const char*)frame->arg1,
UserMemory::IsUserPtr(frame->arg2) ? (const char*)frame->arg2 : nullptr);
case SYS_SPAWN_ENV:
if (!UserMemory::String(frame->arg1, kMaxPathBytes)) return -1;
if (frame->arg2 != 0 && !UserMemory::String(frame->arg2, kMaxArgsBytes)) return -1;
if (frame->arg3 == 0 || frame->arg4 == 0 ||
frame->arg4 > Sched::Process::EnvironmentBytes ||
!UserMemory::Range(frame->arg3, frame->arg4, false) ||
((const char*)frame->arg3)[frame->arg4 - 1] != '\0') return -1;
return (int64_t)Sys_Spawn((const char*)frame->arg1,
UserMemory::IsUserPtr(frame->arg2) ? (const char*)frame->arg2 : nullptr,
(const char*)frame->arg3, (uint32_t)frame->arg4);
case SYS_WAITPID:
return Sys_WaitPid((int)frame->arg1);
case SYS_GETENVIRON:
if (!UserMemory::Range(frame->arg1, frame->arg2, true)) return -1;
return Sys_GetEnviron((char*)frame->arg1, frame->arg2);
case SYS_SETENVIRON:
if (frame->arg2 == 0 || frame->arg2 > Sched::Process::EnvironmentBytes ||
!UserMemory::Range(frame->arg1, frame->arg2, false)) return -1;
return Sys_SetEnviron((const char*)frame->arg1, frame->arg2);
case SYS_FBINFO:
if (!UserMemory::Writable<FbInfo>(frame->arg1)) return -1;
Sys_FbInfo((FbInfo*)frame->arg1);
+3
View File
@@ -315,6 +315,9 @@ namespace montauk::abi {
static constexpr uint64_t SYS_MMAP_ANON = 168;
static constexpr uint64_t SYS_MUNMAP = 169;
static constexpr uint64_t SYS_MPROTECT = 170;
static constexpr uint64_t SYS_GETENVIRON = 171;
static constexpr uint64_t SYS_SETENVIRON = 172;
static constexpr uint64_t SYS_SPAWN_ENV = 173;
// Tunable parameters (for SYS_SDR_SETPARAM / SYS_SDR_GETPARAM).
static constexpr int SDR_PARAM_FREQ = 0; // center frequency, Hz
+24 -1
View File
@@ -294,6 +294,8 @@ namespace Sched {
processTable[i].heapNext = 0;
processTable[i].readdirCursor = 0;
processTable[i].args[0] = '\0';
processTable[i].environment[0] = '\0';
processTable[i].environmentLength = 1;
processTable[i].user[0] = '\0';
processTable[i].cwd[0] = '\0';
processTable[i].runningOnCpu = -1;
@@ -337,7 +339,8 @@ namespace Sched {
<< " process slots, " << (uint64_t)TimeSliceMs << " ms time slice)";
}
int Spawn(const char* vfsPath, const char* args, bool startReady) {
int Spawn(const char* vfsPath, const char* args, bool startReady,
const char* environment, uint32_t environmentLength) {
schedLock.Acquire();
int slot = -1;
@@ -511,6 +514,9 @@ namespace Sched {
auto* currentCpu = Smp::GetCurrentCpuData();
int parentSlot = currentCpu ? currentCpu->currentSlot : -1;
int parentPrimarySlot = parentSlot;
if (parentPrimarySlot >= 0 && processTable[parentPrimarySlot].primarySlot >= 0)
parentPrimarySlot = processTable[parentPrimarySlot].primarySlot;
// Copy arguments string into process
proc.args[0] = '\0';
@@ -522,6 +528,23 @@ namespace Sched {
proc.args[i] = '\0';
}
// posix_spawn(envp) supplies an explicit snapshot. Plain spawn calls
// omit it and inherit the environment stored on the parent process.
if (environment != nullptr && environmentLength > 0 &&
environmentLength <= Process::EnvironmentBytes) {
for (uint32_t i = 0; i < environmentLength; i++)
proc.environment[i] = environment[i];
proc.environmentLength = environmentLength;
} else if (parentPrimarySlot >= 0) {
auto& parent = processTable[parentPrimarySlot];
for (uint32_t i = 0; i < parent.environmentLength; i++)
proc.environment[i] = parent.environment[i];
proc.environmentLength = parent.environmentLength;
} else {
proc.environment[0] = '\0';
proc.environmentLength = 1;
}
// Inherit user string from parent, or default to "system" if no parent
{
if (parentSlot >= 0) {
+5 -1
View File
@@ -65,6 +65,7 @@ namespace Sched {
};
struct Process {
static constexpr uint32_t EnvironmentBytes = 4096;
int pid;
ProcessState state;
int waitingForPid; // PID this process is blocked on (-1 if none)
@@ -82,6 +83,8 @@ namespace Sched {
uint64_t heapNext; // Simple bump allocator for user heap
uint32_t readdirCursor; // Next SYS_READDIR scratch slot
char args[4096]; // Command-line arguments (set by parent via Spawn)
char environment[EnvironmentBytes]; // NUL-separated NAME=VALUE entries
uint32_t environmentLength;
char user[32]; // Owner user name (inherited from parent on spawn)
char cwd[256]; // Absolute current working directory
@@ -139,7 +142,8 @@ namespace Sched {
};
void Initialize();
int Spawn(const char* vfsPath, const char* args = nullptr, bool startReady = true);
int Spawn(const char* vfsPath, const char* args = nullptr, bool startReady = true,
const char* environment = nullptr, uint32_t environmentLength = 0);
int StartProcess(int pid);
void Schedule();
+3
View File
@@ -233,6 +233,9 @@ namespace montauk::abi {
static constexpr uint64_t SYS_MMAP_ANON = 168;
static constexpr uint64_t SYS_MUNMAP = 169;
static constexpr uint64_t SYS_MPROTECT = 170;
static constexpr uint64_t SYS_GETENVIRON = 171;
static constexpr uint64_t SYS_SETENVIRON = 172;
static constexpr uint64_t SYS_SPAWN_ENV = 173;
// Tunable parameters (for SYS_SDR_SETPARAM / SYS_SDR_GETPARAM).
static constexpr int SDR_PARAM_FREQ = 0; // center frequency, Hz
+3
View File
@@ -73,6 +73,9 @@ extern "C" {
#define MTK_SYS_MOUSESTATE 47
#define MTK_SYS_SETMOUSEBOUNDS 48
#define MTK_SYS_SPAWN_REDIR 49
#define MTK_SYS_GETENVIRON 171
#define MTK_SYS_SETENVIRON 172
#define MTK_SYS_SPAWN_ENV 173
#define MTK_SYS_CHILDIO_READ 50
#define MTK_SYS_CHILDIO_WRITE 51
#define MTK_SYS_CHILDIO_WRITEKEY 52
+1 -1
View File
@@ -15,7 +15,7 @@ extern "C" {
* 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).
* - envp is copied into the child; NULL inherits the current environment.
* - 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.
+6
View File
@@ -34,8 +34,10 @@ static inline long _sys2(long nr, long a1, long a2) {
#define SYS_EXIT 0
#define SYS_GETARGS 25
#define SYS_GETEXECPATH 151
#define SYS_GETENVIRON 171
extern int main(int argc, char** argv);
extern void __libc_init_environ(const char *blob, unsigned long len);
/* Global constructor/destructor tables. The linker provides these
boundary symbols whenever .preinit_array/.init_array/.fini_array
@@ -72,9 +74,13 @@ static void _run_fini_array(void) {
void _start(void) {
/* Static: 4 KiB args + 256 argv slots would crowd a 32 KiB stack. */
static char argbuf[4096];
static char envbuf[4096];
static char pathbuf[256];
static char* argv[256];
int len = (int)_sys2(SYS_GETARGS, (long)argbuf, (long)sizeof(argbuf));
int envlen = (int)_sys2(SYS_GETENVIRON, (long)envbuf, (long)sizeof(envbuf));
if (envlen > 0)
__libc_init_environ(envbuf, (unsigned long)envlen);
int argc = 0;
Binary file not shown.
Binary file not shown.
Binary file not shown.
+134 -20
View File
@@ -123,6 +123,9 @@ static inline long _zos_syscall4(long nr, long a1, long a2, long a3, long a4) {
#define SYS_MMAP_ANON 168
#define SYS_MUNMAP 169
#define SYS_MPROTECT 170
#define SYS_GETENVIRON 171
#define SYS_SETENVIRON 172
#define SYS_SPAWN_ENV 173
/* ========================================================================
errno
@@ -164,9 +167,15 @@ struct _DIR {
struct _env_entry {
char *name;
char *value;
char *string;
};
static struct _env_entry _env_entries[64];
static char *_environ_vector[65];
char **environ = _environ_vector;
static int _environ_initialized;
static int _environ_loading;
void __libc_init_environ(const char *blob, size_t len);
static sighandler_t _signal_handlers[32];
static const char *_weekday_short[] = {
@@ -354,6 +363,39 @@ static int _alloc_env_slot(void) {
return -1;
}
static void _rebuild_environ(void) {
int out = 0;
for (int i = 0; i < (int)(sizeof(_env_entries) / sizeof(_env_entries[0])); i++) {
if (_env_entries[i].string != NULL)
_environ_vector[out++] = _env_entries[i].string;
}
_environ_vector[out] = NULL;
}
static int _sync_environ_to_kernel(void) {
static char blob[4096];
size_t out = 0;
for (int i = 0; i < (int)(sizeof(_env_entries) / sizeof(_env_entries[0])); i++) {
if (_env_entries[i].string == NULL) continue;
size_t len = strlen(_env_entries[i].string) + 1;
if (out + len >= sizeof(blob)) return -1;
memcpy(blob + out, _env_entries[i].string, len);
out += len;
}
blob[out++] = '\0';
return _zos_syscall2(SYS_SETENVIRON, (long)blob, (long)out) < 0 ? -1 : 0;
}
static void _ensure_environ_initialized(void) {
if (_environ_initialized) return;
static char blob[4096];
int len = (int)_zos_syscall2(SYS_GETENVIRON, (long)blob, (long)sizeof(blob));
if (len > 0)
__libc_init_environ(blob, (size_t)len);
else
_environ_initialized = 1;
}
/* ========================================================================
string.h functions
======================================================================== */
@@ -1053,6 +1095,7 @@ unsigned long strtoul(const char *nptr, char **endptr, int base) {
char *getenv(const char *name) {
if (name == NULL || name[0] == '\0') return NULL;
_ensure_environ_initialized();
int slot = _find_env_slot(name);
if (slot < 0) return NULL;
@@ -1064,36 +1107,58 @@ int setenv(const char *name, const char *value, int overwrite) {
errno = EINVAL;
return -1;
}
_ensure_environ_initialized();
int slot = _find_env_slot(name);
if (slot >= 0 && !overwrite) {
return 0;
}
size_t required = 1 + strlen(name) + strlen(value) + 2; /* final NUL, '=', entry NUL */
for (int i = 0; i < (int)(sizeof(_env_entries) / sizeof(_env_entries[0])); i++) {
if (i != slot && _env_entries[i].string != NULL)
required += strlen(_env_entries[i].string) + 1;
}
if (required >= 4096) {
errno = E2BIG;
return -1;
}
if (slot < 0) {
slot = _alloc_env_slot();
if (slot < 0) {
errno = ENOMEM;
return -1;
}
} else {
free(_env_entries[slot].name);
free(_env_entries[slot].value);
_env_entries[slot].name = NULL;
_env_entries[slot].value = NULL;
}
_env_entries[slot].name = strdup(name);
_env_entries[slot].value = strdup(value);
if (_env_entries[slot].name == NULL || _env_entries[slot].value == NULL) {
free(_env_entries[slot].name);
free(_env_entries[slot].value);
_env_entries[slot].name = NULL;
_env_entries[slot].value = NULL;
char *new_name = strdup(name);
char *new_value = strdup(value);
char *new_string = (char *)malloc(strlen(name) + strlen(value) + 2);
if (new_name == NULL || new_value == NULL || new_string == NULL) {
free(new_name);
free(new_value);
free(new_string);
errno = ENOMEM;
return -1;
}
strcpy(new_string, name);
strcat(new_string, "=");
strcat(new_string, value);
free(_env_entries[slot].name);
free(_env_entries[slot].value);
free(_env_entries[slot].string);
_env_entries[slot].name = new_name;
_env_entries[slot].value = new_value;
_env_entries[slot].string = new_string;
_rebuild_environ();
if (!_environ_loading && _sync_environ_to_kernel() < 0) {
errno = EIO;
return -1;
}
return 0;
}
@@ -1102,14 +1167,22 @@ int unsetenv(const char *name) {
errno = EINVAL;
return -1;
}
_ensure_environ_initialized();
int slot = _find_env_slot(name);
if (slot < 0) return 0;
free(_env_entries[slot].name);
free(_env_entries[slot].value);
free(_env_entries[slot].string);
_env_entries[slot].name = NULL;
_env_entries[slot].value = NULL;
_env_entries[slot].string = NULL;
_rebuild_environ();
if (_sync_environ_to_kernel() < 0) {
errno = EIO;
return -1;
}
return 0;
}
@@ -4114,7 +4187,6 @@ int posix_spawn(pid_t *pid, const char *path,
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;
@@ -4148,7 +4220,24 @@ int posix_spawn(pid_t *pid, const char *path,
}
argsbuf[o] = '\0';
long child = _zos_syscall2(SYS_SPAWN, (long)path, (long)argsbuf);
long child;
if (envp == NULL) {
child = _zos_syscall2(SYS_SPAWN, (long)path, (long)argsbuf);
} else {
static char envbuf[4096];
size_t envlen = 0;
for (int i = 0; envp[i] != NULL; i++) {
const char *eq = strchr(envp[i], '=');
if (eq == NULL || eq == envp[i]) return EINVAL;
size_t len = strlen(envp[i]) + 1;
if (envlen + len >= sizeof(envbuf)) return E2BIG;
memcpy(envbuf + envlen, envp[i], len);
envlen += len;
}
envbuf[envlen++] = '\0';
child = _zos_syscall4(SYS_SPAWN_ENV, (long)path, (long)argsbuf,
(long)envbuf, (long)envlen);
}
if (child < 0) {
return ENOENT;
}
@@ -4192,12 +4281,37 @@ int posix_spawnp(pid_t *pid, const char *file,
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
POSIX-shaped code until the posix_spawn work builds the real one. */
static char *_environ_empty[] = { NULL };
char **environ = _environ_empty;
/* Called by crt1 before constructors. The kernel stores the inherited
environment as consecutive NAME=VALUE strings followed by an extra NUL. */
void __libc_init_environ(const char *blob, size_t len) {
if (_environ_initialized || blob == NULL || len == 0) return;
_environ_initialized = 1;
_environ_loading = 1;
size_t off = 0;
while (off < len && blob[off] != '\0') {
size_t end = off;
while (end < len && blob[end] != '\0') end++;
if (end == len) break;
size_t eq = off;
while (eq < end && blob[eq] != '=') eq++;
if (eq > off && eq < end) {
size_t name_len = eq - off;
char *name = (char *)malloc(name_len + 1);
if (name == NULL) break;
memcpy(name, blob + off, name_len);
name[name_len] = '\0';
if (setenv(name, blob + eq + 1, 1) < 0) {
free(name);
break;
}
free(name);
}
off = end + 1;
}
_environ_loading = 0;
}
/* No fork/exec process model on Montauk (spawn-style only, see
SYS_SPAWN). These exist so POSIX code links; they fail cleanly at
Binary file not shown.
Binary file not shown.
Binary file not shown.
+4
View File
@@ -59,6 +59,10 @@
The argument string is set by the parent when calling spawn().
If no arguments were provided, the buffer will be empty.
A spawned child inherits a snapshot of its parent's environment. Libc
exposes it through getenv(), setenv(), and environ. posix_spawn() can
instead provide an explicit envp for the child.
.SH NOTES
The _start() entry point receives no argc/argv. Use getargs()
to retrieve the argument string passed by the parent process.
+7 -1
View File
@@ -39,7 +39,8 @@
int montauk::getpid();
.B SYS_SPAWN (20)
Spawn a new process from an ELF binary on the VFS.
Spawn a new process from an ELF binary on the VFS. The child inherits
a snapshot of the caller's environment.
int montauk::spawn(const char* path, const char* args = nullptr);
.B SYS_WAITPID (23)
@@ -50,6 +51,11 @@
Get the argument string passed to this process at spawn time.
int montauk::getargs(char* buf, uint64_t maxLen);
.B SYS_GETENVIRON (171), SYS_SETENVIRON (172), SYS_SPAWN_ENV (173)
Libc process-environment transport. Environment data is encoded as
consecutive NAME=VALUE strings with a final empty string. Applications
normally use getenv(3), setenv(3), environ, and posix_spawn(3).
.B SYS_PROCLIST (61)
List running processes (pid, parent, state, name, heap usage,
accumulated CPU time).
+8
View File
@@ -39,6 +39,9 @@ static inline long _sys2(long nr, long a1, long a2) {
#define SYS_GETSIZE 8
#define SYS_GETARGS 25
#define SYS_GETENVIRON 171
void __libc_init_environ(const char *blob, unsigned long len);
/* fdopen, strtoll and strtoull used to be defined here; they moved
into the Montauk libc with the native binutils port. */
@@ -50,6 +53,11 @@ static inline long _sys2(long nr, long a1, long a2) {
int main(int argc, char **argv);
void _start(void) {
static char envbuf[4096];
int envlen = (int)_sys2(SYS_GETENVIRON, (long)envbuf, sizeof(envbuf));
if (envlen > 0)
__libc_init_environ(envbuf, (unsigned long)envlen);
/* Get command-line arguments from kernel */
char argbuf[256];
int len = (int)_sys2(SYS_GETARGS, (long)argbuf, sizeof(argbuf));
+1 -1
View File
@@ -32,7 +32,7 @@ void exit(int code); // Terminate process (nore
void yield(); // Yield CPU to scheduler
void sleep_ms(uint64_t ms); // Sleep for milliseconds
int getpid(); // Get current process ID
int spawn(const char* path, const char* args); // Spawn child process (-1 on error)
int spawn(const char* path, const char* args); // Spawn child, inheriting environment
int waitpid(int pid); // Wait for process to exit
int kill(int pid); // Kill a process
int proclist(ProcInfo* buf, int max); // List all processes (returns count)
+3
View File
@@ -208,6 +208,9 @@ namespace montauk::abi {
// Absolute path of the running executable (for argv[0]).
static constexpr uint64_t SYS_GETEXECPATH = 151; // (index, flags) -> new front index; index=-1 queries support (1/0); flags bit0 = wait vsync
static constexpr uint64_t SYS_GETENVIRON = 171;
static constexpr uint64_t SYS_SETENVIRON = 172;
static constexpr uint64_t SYS_SPAWN_ENV = 173;
// Path metadata (size, timestamps, mode). (const char* path, FileStat* out) -> 0, -1 on error/unsupported.
static constexpr uint64_t SYS_STAT = 152;
+3
View File
@@ -73,6 +73,9 @@ extern "C" {
#define MTK_SYS_MOUSESTATE 47
#define MTK_SYS_SETMOUSEBOUNDS 48
#define MTK_SYS_SPAWN_REDIR 49
#define MTK_SYS_GETENVIRON 171
#define MTK_SYS_SETENVIRON 172
#define MTK_SYS_SPAWN_ENV 173
#define MTK_SYS_CHILDIO_READ 50
#define MTK_SYS_CHILDIO_WRITE 51
#define MTK_SYS_CHILDIO_WRITEKEY 52
+1 -1
View File
@@ -15,7 +15,7 @@ extern "C" {
* 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).
* - envp is copied into the child; NULL inherits the current environment.
* - 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.
+6 -6
View File
@@ -66,12 +66,12 @@
prefix_from_env ("PATH", &path);
+#ifdef __montauk__
+ /* MontaukOS does not pass environment variables across spawn yet, so
+ COMPILER_PATH and PATH above are always empty. Derive the compiler
+ search path from argv[0] instead: the driver invokes collect2 by its
+ full path (the kernel provides the real exec path in argv[0]), and
+ `ld` lives in the same libexec directory. Also seed PATH with the
+ SDK bin directories so the nm/strip/tooldir fallbacks work. */
+ /* Keep a fallback for early boot and deliberately minimal environments.
+ Derive the compiler search path from argv[0]: the driver invokes
+ collect2 by its full path (the kernel provides the real exec path in
+ argv[0]), and `ld` lives in the same libexec directory. Also seed PATH
+ with the SDK bin directories when the caller did not provide one. */
+
+ if (cpath.plist == NULL && argv[0] != NULL)
+ {
+ const char *slash = strrchr (argv[0], '/');