diff --git a/src/sched.c b/src/sched.c index b096172..e36828a 100644 --- a/src/sched.c +++ b/src/sched.c @@ -837,6 +837,179 @@ const char* currentThreadGetEnv(const char* name) return value + 1; } } + return nullptr; +} + +bool currentThreadSetEnv(const char* name, const char* value) +{ + Thread* self = &threads[currentIndex]; + uint64 nameLen = strlen(name); + uint64 valueLen = strlen(value); + if (nameLen + 1 + valueLen + 1 > envEntryMax) + return false; + + int slot = -1; + for (int i = 0; i < self->envCount; i++) { + if (envNameMatches(self->env[i], name)) { + slot = i; + break; + } + } + if (slot < 0) { + if (self->envCount >= envMaxVars) + return false; + slot = self->envCount++; + } + + memcpy(self->env[slot], name, nameLen); + self->env[slot][nameLen] = '='; + memcpy(self->env[slot] + nameLen + 1, value, valueLen); + self->env[slot][nameLen + 1 + valueLen] = 0; + return true; +} + +bool currentThreadUnsetEnv(const char* name) +{ + Thread* self = &threads[currentIndex]; + int slot = -1; + for (int i = 0; i < self->envCount; i++) { + if (envNameMatches(self->env[i], name)) { + slot = i; + break; + } + } + if (slot < 0) + return false; + + int last = self->envCount - 1; + if (slot != last) + memmove(self->env[slot], self->env[slot + 1], (uint64)(last - slot) * envEntryMax); + self->envCount--; + return true; +} + +int currentThreadEnvCount(void) +{ + return threads[currentIndex].envCount; +} + +const char* currentThreadEnvEntry(int index) +{ + if (index < 0 || index >= threads[currentIndex].envCount) + return nullptr; + return threads[currentIndex].env[index]; +} + +bool threadStop(uint32 id) +{ + if (id == 0 || id >= maxThreads) + return false; + + ThreadState state = threads[id].state; + if (state == threadDead || state == threadUnused) + return false; + if (state == threadStopped) { + /* Idempotent, matching real kill(pid, SIGSTOP) sent to an already- + stopped process: the state transition already happened, so there + is no new event for the parent's SIGCHLD to report -- raising + again here would be a second notification for a single stop. */ + return true; + } + + threads[id].state = threadStopped; + kprintf("[sched] pid=%u '%s' stopped\n", id, threads[id].name); + + /* Real job-control SIGCHLD-on-stop, the same asynchronous notification + threadExit/threadKill above already raise on death (Thread.parentId, + signal.h's sigRaiseInfo) -- a shell watching a background job's + parentId finds out the instant it stops, not only when it eventually + dies or exits. Only reached on the actual ...->Stopped transition + above, never on the already-Stopped no-op return just above. + siCodeChldStopped/id/sigStop: real Unix's WIFSTOPPED(status) + + WSTOPSIG(status) would report whichever signal actually caused the + stop -- this function is called from exactly two places (signal.c's + sigRaise sigStop branch, a real SIGSTOP; isr.c's sigTstp-default- + disposition branch, a real SIGTSTP) and does not itself know which + one, so si_status always reports sigStop here regardless. A known, + documented imprecision for the SIGTSTP path (which also has no live + ring-3 demo of its own yet -- see signal.h's file comment) -- not a + fabricated distinction either path never had. */ + sigRaiseInfo(threads[id].parentId, sigChld, siCodeChldStopped, (int)id, sigStop); + return true; +} + +bool threadContinue(uint32 id) +{ + if (id == 0 || id >= maxThreads) + return false; + if (threads[id].state != threadStopped) + return false; + + threads[id].state = threadRunnable; + kprintf("[sched] pid=%u '%s' continued\n", id, threads[id].name); + + /* Same job-control SIGCHLD contract as threadStop above, mirrored for + SIGCONT: real job-control shells rely on this to notice a + backgrounded job resumed without polling its state. Only reached + past the "was it actually Stopped" guard above, so this never fires + for a SIGCONT sent to a target that was never stopped in the first + place, matching real Unix (nothing to report). siCodeChldContinued/ + id/sigCont: real Unix's WIFCONTINUED(status), si_status unused for + this one (POSIX defines none for CLD_CONTINUED either) -- sigCont + here is honest, not a placeholder, unlike threadStop's own sigStop + above: threadContinue really is only ever reached via a genuine + SIGCONT (signal.c's sigRaise sigCont branch is its only caller with + a live path to a Stopped target). */ + sigRaiseInfo(threads[id].parentId, sigChld, siCodeChldContinued, (int)id, sigCont); + return true; +} + +bool threadWakeForSignal(uint32 id) +{ + if (id == 0 || id >= maxThreads) + return false; + + ThreadState state = threads[id].state; + if (state != threadSleeping && state != threadBlocked) + return false; + + threads[id].state = threadRunnable; + kprintf("[sig] pid=%u '%s' woken early for a signal\n", id, threads[id].name); + return true; +} + +uint32 threadCapacity(void) +{ + return maxThreads; +} + +/* Gives a dead/discarded user thread's whole elfSlotSize slot back to the + pmm. Verified, not just invoked blindly: a byte inside the slot's stack + region (always mapped for any user thread, whatever its image looks + like -- see loadImageByPath/spawnUser) is checked present BEFORE the + unmap (proving there was really something to reclaim, not a no-op on an + already-empty slot) and absent AFTER (proving the unmap actually took + the frame back), logged so the boot gate can hold the wiring itself + accountable rather than trusting that calling pagingUnmapUserRange was + enough. Kernel threads (userSlotBase 0) are silently skipped. */ +static void reclaimUserSlot(uint32 id, const Thread* thread) +{ + if (thread->userSlotBase == 0) + return; + + uint64 slotBase = thread->userSlotBase; + uint64 stackProbe = slotBase + elfSlotStackOffset - 1; + bool wasMapped = pagingResolve(stackProbe) != 0; + + pagingUnmapUserRange(slotBase, elfSlotSize); + + bool nowFreed = pagingResolve(stackProbe) == 0; + kprintf("[sched] reap pid=%u freed user slot %p (was-mapped=%s now-freed=%s) -> %s\n", + id, (void*)slotBase, wasMapped ? "yes" : "no", nowFreed ? "yes" : "no", + (wasMapped && nowFreed) ? "user-mem-reclaimed-ok" : "user-mem-reclaimed-FAIL"); +} + +/* Every open fd a thread still holds when it dies represents a live } void threadSleep(uint64 milliseconds)