diff --git a/user/hcat.c b/user/hcat.c new file mode 100644 index 0000000..bd3bbe0 --- /dev/null +++ b/user/hcat.c @@ -0,0 +1,57 @@ +#include "ulib.h" + +/* + * Left half of shell.c's new `cmd1 <<< TEXT | cmd2` here-string-into- + * pipeline live demo -- jobctl.c's new jobctlSpawnPipedHereString wires + * THIS process's fd 0 to a real pipe the shell itself writes the typed + * `<<<` text into, AND its fd 1 to the pipeline's shared pipe (right + * half's fd 0), both BEFORE this process ever runs a single instruction. + * 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. Named "hcat" (not "cat") purely so + * the driver thread's typed line (binary name + `<<<` + text + `|` + + * binary name) stays clearly under keyboard.c's 32-byte + * consoleReaderBufferSize per line -- see kmain.c's FALA 31 lesson, + * documented right where stderrDriverThread's own path length once + * crossed that exact boundary. + * + * Diagnostics go to fd 2 (never fd 1) for the same reason pipesrc.c's own + * printErr does: fd 1 here is the real pipeline payload hsink.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("hcat: reading here-string stdin\n"); + + char buffer[64]; + unsigned long total = 0; + for (;;) { + long got = userRead(0, buffer + total, sizeof(buffer) - 1 - total); + if (got < 0) { + printErr("hcat: 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("hcat: FAIL short/failed write\n"); + return 1; + } + + printErr("hcat: forwarded payload\n"); + return 0; +} diff --git a/user/hsink.c b/user/hsink.c new file mode 100644 index 0000000..e3db481 --- /dev/null +++ b/user/hsink.c @@ -0,0 +1,55 @@ +#include "ulib.h" + +/* + * Right half of shell.c's new `cmd1 <<< TEXT | cmd2` here-string-into- + * pipeline live demo -- see hcat.c's own header comment for the full + * picture. jobctlSpawnPiped bakes the pipeline's shared pipe's read end + * into this process's own fd 0 exactly like pipesink.c's, so the blocking + * userRead below has to genuinely retry across timer ticks until hcat.c's + * own forwarded write actually lands. Compares the received bytes + * byte-for-byte against the exact text kmain.c's driver thread types after + * `<<<` plus the single trailing newline jobctlSpawnPipedHereString always + * appends (matching a real shell's own `<<<` behavior) -- unlike hcat.c, + * this process's own fd 1 (stdout) is NOT part of the pipeline, so + * print()/printDec() (ulib.h) reach the real console exactly like + * pipesink.c's own needle. + */ +#define hsinkPayload "hpipe-ok\n" + +static int streq(const char* a, const char* b) +{ + while (*a && *a == *b) { + a++; + b++; + } + return *a == *b; +} + +int main(void) +{ + print("hsink: blocking on read, waiting for hcat\n"); + + char buffer[32]; + unsigned long total = 0; + for (;;) { + long got = userRead(0, buffer + total, sizeof(buffer) - 1 - total); + if (got < 0) { + print("hsink: FAIL read\n"); + return 1; + } + if (got == 0) + break; + total += (unsigned long)got; + if (total >= sizeof(buffer) - 1) + break; + } + buffer[total] = 0; + + print("hsink: got: "); + print(buffer); + + int ok = streq(buffer, hsinkPayload); + print(ok ? "hsink: payload-ok\n" : "hsink: payload-FAIL\n"); + + return ok ? 0 : 1; +} diff --git a/user/jobctlbg.c b/user/jobctlbg.c new file mode 100644 index 0000000..8233c0d --- /dev/null +++ b/user/jobctlbg.c @@ -0,0 +1,80 @@ +#include "ulib.h" + +/* + * Companion to jobctlfg.c: loaded the same way, on demand, when kmain.c's + * jobctlDriverThread types "jobctlbg &" at the real interactive shell. + * The trailing "&" (shell.c's runProgram) routes this through + * jobctlRunBackground (jobctl.c) instead of jobctlSpawnForeground -- own + * new process group leader exactly like jobctlfg.c, but the shell never + * calls keyboardSetForegroundPgid for it and never blocks waiting for it + * either, a real shell's `&` job semantics. This is the CONTROL side of + * the jobctlfg/jobctlbg pair: jobctlfg proves the shell's foreground + * grant actually reaches an injected Ctrl-C; this file proves that exact + * same event does NOT reach a job the shell deliberately left in the + * background -- if shell.c's `&` handling or jobctl.c's group-leader + * assignment were ever broken such that this process shared jobctlfg's + * group (or somehow ended up foreground itself), this process would + * already be Dead by the time jobctlDriverThread later tries to prove it + * alive, and its own needles below would simply never appear. + * + * Also exercises the actual sysGetpgid syscall boundary (unreachable from + * KTEST, which runs before schedStart() with no int 0x80 path at all) to + * confirm jobctl.c's threadSetPgid(pid, pid) call genuinely took effect + * from ring3's own point of view, not just internally. + */ + +static volatile int handlerFired = 0; + +static void onUsr1(int signum) +{ + (void)signum; + handlerFired = 1; + print("jobctlbg: still-alive-after-ctrlc-ok\n"); +} + +int main(void) +{ + long myPid = userGetpid(); + print("jobctlbg: start, pid="); + printDec(myPid); + print("\n"); + + long pgid = userGetpgid(0); + if (pgid != myPid) { + print("jobctlbg: FAIL expected to be its own group leader\n"); + return 1; + } + print("jobctlbg: own-process-group-ok\n"); + + if (userSigAction(sigUsr1, onUsr1, 0, 0) == (long)sigSetFailed) { + print("jobctlbg: FAIL install handler\n"); + return 1; + } + + long victimHandle = userPipeCreate(); + if (victimHandle < 0) { + print("jobctlbg: FAIL create victim pipe\n"); + return 1; + } + long victimFd = userPipeAttach(victimHandle, 0); + if (victimFd < 0) { + print("jobctlbg: FAIL attach victim pipe\n"); + return 1; + } + + print("jobctlbg: ready, blocking on go pipe\n"); + char buffer[4]; + userRead((int)victimFd, buffer, sizeof(buffer)); + + if (!handlerFired) { + print("jobctlbg: FAIL read returned without being signaled\n"); + return 1; + } + + /* Reached only if the read was genuinely interrupted (errInterrupted, + no sigFlagRestart) by onUsr1 above, sent by kmain.c's own driver + thread well AFTER the Ctrl-C injected for jobctlfg -- proving this + backgrounded process was never touched by it. */ + print("jobctlbg: resumed-after-signal-ok\n"); + return 0; +}