diff --git a/user/eintrtarget.c b/user/eintrtarget.c new file mode 100644 index 0000000..d1badcd --- /dev/null +++ b/user/eintrtarget.c @@ -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; +} diff --git a/user/envprint.c b/user/envprint.c new file mode 100644 index 0000000..8b31389 --- /dev/null +++ b/user/envprint.c @@ -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; +} diff --git a/user/exectarget.c b/user/exectarget.c new file mode 100644 index 0000000..90c7b65 --- /dev/null +++ b/user/exectarget.c @@ -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; +} diff --git a/user/false.c b/user/false.c new file mode 100644 index 0000000..f50de6c --- /dev/null +++ b/user/false.c @@ -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; +}