From 0f4437ed804f3348639076c39a2444da73b78fb3 Mon Sep 17 00:00:00 2001 From: DualFroz Date: Wed, 9 Sep 2026 13:20:57 +0200 Subject: [PATCH] feat: add onUsr1 and printErr --- user/pgroupbg.c | 90 +++++++++++++++++++++++++++++++++++++++++++++++++ user/pgroupfg.c | 57 +++++++++++++++++++++++++++++++ user/pipecat.c | 56 ++++++++++++++++++++++++++++++ 3 files changed, 203 insertions(+) create mode 100644 user/pgroupbg.c create mode 100644 user/pgroupfg.c create mode 100644 user/pipecat.c diff --git a/user/pgroupbg.c b/user/pgroupbg.c new file mode 100644 index 0000000..2374bc3 --- /dev/null +++ b/user/pgroupbg.c @@ -0,0 +1,90 @@ +#include "ulib.h" + +/* + * Spawned directly by kmain.c at boot, alongside pgroupfg.c. Deliberately + * NEVER told to keyboardSetForegroundPgid -- every boot-time spawnUser call + * becomes its own new process group leader by default (sched.c's + * initialPgid), so this process's pgid already differs from pgroupfg's + * without any extra call, the same way two independently-launched + * background jobs in a real shell would each get their own group. This is + * the CONTROL side of the pgroupfg/pgroupbg pair: pgroupfg proves the + * injected Ctrl-C reaches the foreground group and kills it; this file + * proves that exact same event does NOT reach a DIFFERENT group at all -- + * if keyboard.c's scoping were ever broken to deliver sigInt/sigTstp to + * every live thread (the bug this whole subsystem exists to prevent), this + * process would already be Dead by the time kmain.c's injector later tries + * to prove it alive, and every needle below would simply never appear. + * + * Along the way, exercises the actual syscall boundary (sysSetpgid/ + * sysGetpgid, syscall.c) that sched.c's own KTEST coverage cannot reach on + * its own (ktestRunAll runs before schedStart(), with no int 0x80 path at + * all -- see schedTest.c's file comment): userGetpgid(0) must already + * equal this process's own pid (new-group-leader default), and an + * explicit, real setpgid(0, 0) self-affirmation (the same defensive call a + * real shell places a background job's own PID with, even when it is + * already its own leader) must be a harmless no-op. + */ + +static volatile int handlerFired = 0; + +static void onUsr1(int signum) +{ + (void)signum; + handlerFired = 1; + print("pgroupbg: still-alive-after-ctrlc-ok\n"); +} + +int main(void) +{ + long myPid = userGetpid(); + print("pgroupbg: start, pid="); + printDec(myPid); + print("\n"); + + long pgid = userGetpgid(0); + if (pgid != myPid) { + print("pgroupbg: FAIL expected to be its own group leader\n"); + return 1; + } + if (userSetpgid(0, 0) != 0) { + print("pgroupbg: FAIL self setpgid(0,0) rejected\n"); + return 1; + } + if (userGetpgid(0) != myPid) { + print("pgroupbg: FAIL pgid changed after self-affirming setpgid\n"); + return 1; + } + print("pgroupbg: own-process-group-ok\n"); + + if (userSigAction(sigUsr1, onUsr1, 0, 0) == (long)sigSetFailed) { + print("pgroupbg: FAIL install handler\n"); + return 1; + } + + long victimHandle = userPipeCreate(); + if (victimHandle < 0) { + print("pgroupbg: FAIL create victim pipe\n"); + return 1; + } + long victimFd = userPipeAttach(victimHandle, 0); + if (victimFd < 0) { + print("pgroupbg: FAIL attach victim pipe\n"); + return 1; + } + + print("pgroupbg: ready, blocking on go pipe\n"); + char buffer[4]; + userRead((int)victimFd, buffer, sizeof(buffer)); + + if (!handlerFired) { + print("pgroupbg: FAIL read returned without being signaled\n"); + return 1; + } + + /* Reached only if the read was genuinely interrupted (errInterrupted, + no sigFlagRestart) by onUsr1 above, itself only ever sent by + kmain.c's own injector thread well AFTER the Ctrl-C event -- proving + this process was never touched by it at all. */ + print("pgroupbg: resumed-after-signal-ok\n"); + return 0; +} diff --git a/user/pgroupfg.c b/user/pgroupfg.c new file mode 100644 index 0000000..2d07769 --- /dev/null +++ b/user/pgroupfg.c @@ -0,0 +1,57 @@ +#include "ulib.h" + +/* + * Spawned directly by kmain.c at boot, alongside pgroupbg.c -- the live + * ring-3 proof that sched.h's new Thread.pgid and keyboard.c's new real + * Ctrl-C actually scope a terminal-generated signal to the FOREGROUND + * process group, not to "every process reading a keyboard" or "one single + * hardcoded target". kmain.c makes this process's own pgid (its default, + * unchanged: every boot-time spawnUser call becomes its own new group + * leader, sched.c's initialPgid) the foreground via + * keyboardSetForegroundPgid right after spawning it. + * + * Installs NO handler for sigInt at all -- the real-world default + * disposition (signal.c's sigDefaultIsIgnore does not list sigInt) is + * Terminate, exactly like a real un-caught Ctrl-C killing a foreground + * program with no signal handling of its own. The read below blocks on a + * pipe no writer will ever attach to (the same "blocked forever" setup + * eintrtarget.c/killvictim.c/alarmdefault.c already use), so the only way + * this process ever leaves it is the kernel-injected Ctrl-C's early-wake + * (signal.c's sigWouldDeliver/sched.c's threadWakeForSignal, the same + * mechanism eintrtarget.c's own sigTerm already proves) actually reaching + * a genuinely Blocked thread and terminating it. If keyboard.c's group + * scoping were ever broken such that this process were somehow excluded + * from its own foreground group, this process would simply never return + * from the read below, hanging the whole boot gate to its 25s QEMU timeout + * instead of merely failing a check -- the same fault-injection-visible + * shape eintrtest.c's own file comment already establishes. + */ + +int main(void) +{ + print("pgroupfg: start, pid="); + printDec(userGetpid()); + print("\n"); + + long victimHandle = userPipeCreate(); + if (victimHandle < 0) { + print("pgroupfg: FAIL create victim pipe\n"); + return 1; + } + long victimFd = userPipeAttach(victimHandle, 0); + if (victimFd < 0) { + print("pgroupfg: FAIL attach victim pipe\n"); + return 1; + } + + print("pgroupfg: ready, blocking on go pipe\n"); + char buffer[4]; + userRead((int)victimFd, buffer, sizeof(buffer)); + + /* Only reached if userRead somehow returned on its own -- nothing ever + writes to the victim pipe, so the foreground Ctrl-C never arriving + (or arriving but not actually terminating this process) is the only + way to get here. */ + print("pgroupfg: FAIL read returned without being interrupted\n"); + return 1; +} diff --git a/user/pipecat.c b/user/pipecat.c new file mode 100644 index 0000000..b8339e6 --- /dev/null +++ b/user/pipecat.c @@ -0,0 +1,56 @@ +#include "ulib.h" + +/* + * Middle stage of shell.c's new N-stage pipeline live demo + * (`pipesrc | pipecat | pipesink`) -- the first process in this kernel with + * BOTH ends of two DIFFERENT real inter-process pipes baked into its fds at + * once (fd 0 from jobctlSpawnPiped's stdinHandle, fd 1 from its + * stdoutHandle). user/hcat.c already proved a piped stdout alongside a + * piped stdin, but that stdin was always a shell-fed here-string + * (jobctlSpawnPipedHereString), never a second real process-to-process + * pipe the way this one's is -- this program is the actual proof that + * shell.c's runPipelineStages can wire a genuine middle pipeline stage + * where NEITHER end is the console. From this program's own point of view + * there is nothing special about either fd: it is simply "cat" -- read + * stdin to real EOF, then write exactly those bytes to stdout -- with no + * idea either end is a pipe rather than a real file or console, the same + * shape as hcat.c's/pipesink.c's own read loops. + * + * Diagnostics go to fd 2 (never fd 1), same reason as every other filter in + * this pipeline family: fd 1 here is the real payload pipesink.c compares + * byte-for-byte, and print()/printDec() (ulib.h) are both hardcoded to fd 1. + */ + +static void printErr(const char* text) +{ + userWrite(2, text, stringLength(text)); +} + +int main(void) +{ + printErr("pipecat: reading piped stdin\n"); + + char buffer[64]; + unsigned long total = 0; + for (;;) { + long got = userRead(0, buffer + total, sizeof(buffer) - 1 - total); + if (got < 0) { + printErr("pipecat: FAIL read\n"); + return 1; + } + if (got == 0) + break; + total += (unsigned long)got; + if (total >= sizeof(buffer) - 1) + break; + } + + long written = userWrite(1, buffer, total); + if (written != (long)total) { + printErr("pipecat: FAIL short/failed write\n"); + return 1; + } + + printErr("pipecat: forwarded payload\n"); + return 0; +}