feat: add eintrtarget and envprint

This commit is contained in:
dualfroz committed 2026-09-08 22:15:55 +02:00
1 parent 607eaff366
commit 6184cf43be
4 files changed
+179

No files matched your search

+61
View File
@@ -0,0 +1,61 @@
#include "ulib.h"
/*
* Only ever spawned by eintrtest.c. Attaches as the READ end of its own,
* self-contained pipe (userPipeCreate/userPipeAttach) that no writer will
* ever attach to -- the exact same "blocked forever" setup killtest.c's
* killvictim.c already uses for threadKill -- but the way out here is an
* ORDINARY signal (sigTerm, default disposition Terminate) going through
* the FULL sigRaise -> pending-bit -> early-wake -> deliverPendingSignals
* pipeline (signal.h/sched.c's threadWakeForSignal), not threadKill's
* direct ThreadState bypass. Before that early-wake existed, sigRaise only
* ever set a pending bit and left a Blocked thread exactly where it was --
* delivery only happens at a return-to-ring3 this process would otherwise
* never naturally reach again, so sigTerm sent here would have done
* nothing observable, ever: the read below is never naturally satisfied,
* by construction.
*/
int main(int argc, char** argv)
{
print("eintrtarget: start, pid=");
printDec(userGetpid());
print("\n");
if (argc != 2) {
print("eintrtarget: FAIL expected one argv (ready handle)\n");
return 1;
}
long victimHandle = userPipeCreate();
if (victimHandle < 0) {
print("eintrtarget: FAIL create victim pipe\n");
return 1;
}
long victimFd = userPipeAttach(victimHandle, 0);
if (victimFd < 0) {
print("eintrtarget: FAIL attach victim pipe\n");
return 1;
}
long readyHandle = parseDec(argv[1]);
long readyFd = userPipeAttach(readyHandle, 1);
if (readyFd < 0) {
print("eintrtarget: FAIL attach ready pipe\n");
return 1;
}
if (userWrite((int)readyFd, "r", 1) != 1) {
print("eintrtarget: FAIL write ready byte\n");
return 1;
}
userClose((int)readyFd);
print("eintrtarget: blocking on read forever\n");
char buffer[4];
userRead((int)victimFd, buffer, sizeof(buffer));
/* Only reached if userRead somehow returned on its own -- eintrtest.c
never writes to the victim pipe, so that would itself be a bug. */
print("eintrtarget: FAIL read returned without being signaled\n");
return 1;
}
+84
View File
@@ -0,0 +1,84 @@
#include "ulib.h"
/*
* Live ring-3 proof for the real environment-variable subsystem (sched.h's
* Thread.env, syscall.c's sysGetEnv/sysSetEnv/sysUnsetEnv, shell.c's
* `export`/`env`/`unset` builtins and `$NAME` expansion) -- loaded on demand by
* the real interactive shell (kmain.c's envDriverThread types
* "export GREETING=frostbyte" then "envprint" at it, the same injected-
* keystroke mechanism every shell-driven demo above already uses), never
* boot-spawned, the same "no kmain.c spawnUser call, only /bin/NAME
* resolved by the shell's own fallback" shape jobctlfg.c/hcat.c/pipecat.c
* already established.
*
* Proves inheritance first (this process never called userSetEnv itself
* before the first check -- whatever it sees came only from
* threadCreateUser's own initialEnv copying the shell's table at spawn
* time), then proves this process's OWN later userSetEnv is visible to
* itself immediately, then proves userUnsetEnv actually removes the entry
* (a following userGetEnv on the same name fails) and that unsetting a
* name that was never set is still a no-op success, not an error. The
* other half of the isolation guarantee -- that
* this process's own mutation can never reach back into the PARENT
* shell's copy -- is proven by envDriverThread itself: it types a plain
* "env" at the shell AFTER this process has already exited and checks
* that the shell still reports the ORIGINAL "frostbyte" value, not
* "child-mutated" (mirrors cwdtest.c's own post-userWait
* parent-cwd-unaffected check, just driven through the shell instead of a
* direct userWait).
*/
int main(void)
{
print("envprint: start, pid=");
printDec(userGetpid());
print("\n");
char value[32];
long len = userGetEnv("GREETING", value, sizeof(value));
if (len < 0) {
print("envprint: FAIL missing inherited GREETING\n");
return 1;
}
print("envprint: inherited-ok GREETING=");
print(value);
print("\n");
if (userGetEnv("NO_SUCH_VAR", value, sizeof(value)) >= 0) {
print("envprint: FAIL unset variable returned a value\n");
return 1;
}
if (userSetEnv("GREETING", "child-mutated") != 0) {
print("envprint: FAIL setenv\n");
return 1;
}
len = userGetEnv("GREETING", value, sizeof(value));
if (len < 0) {
print("envprint: FAIL read back own setenv\n");
return 1;
}
print("envprint: self-set-ok GREETING=");
print(value);
print("\n");
if (userUnsetEnv("GREETING") != 0) {
print("envprint: FAIL unset\n");
return 1;
}
if (userGetEnv("GREETING", value, sizeof(value)) >= 0) {
print("envprint: FAIL unset did not remove variable\n");
return 1;
}
print("envprint: unset-ok\n");
/* Real unsetenv(3) is a no-op success on a name that was never set --
this must NOT be confused with a "not found" error. */
if (userUnsetEnv("NO_SUCH_VAR_EVER") != 0) {
print("envprint: FAIL unset-missing should be a no-op success\n");
return 1;
}
print("envprint: unset-missing-ok\n");
return 0;
}
+19
View File
@@ -0,0 +1,19 @@
#include "ulib.h"
/*
* Never spawned directly by kmain.c or by proctest.c's userSpawn -- the
* only way this binary's "exectarget: running" line can ever appear in the
* boot log is if proctest.c's userExec("/bin/exectarget") call actually
* replaced proctest's own image with this one (see proctest.c). Its pid is
* therefore the SAME pid proctest was given at spawnUser time in kmain.c,
* which is what kmain.c's execReaperThread checks via threadJoin to prove
* exec() preserves process identity, not just that some program ran.
*/
int main(void)
{
print("exectarget: running, pid=");
printDec(userGetpid());
print("\n");
return 7;
}
+15
View File
@@ -0,0 +1,15 @@
#include "ulib.h"
/*
* Real POSIX false(1): does nothing, always exits 1. Exists purely so
* shell.c's new `&&`/`||` conditional chaining (execute()/executeSingle,
* shellSplitAndOr in shell.h) has a genuinely SPAWNED process (real
* fork-less spawn + real userExit, not just a builtin's own int return
* like doExport/changeDir) whose nonzero exit status drives a real `&&`
* skip followed by a real `||` fallback -- kmain.c's andOrDriverThread
* types `false && echo x || echo ao2` at the live shell to prove it.
*/
int main(void)
{
return 1;
}