feat: add onUsr1 and printErr

This commit is contained in:
dualfroz committed 2026-09-09 13:20:57 +02:00
1 parent 9480af603f
commit 0f4437ed80
3 files changed
+203

No files matched your search

+90
View File
@@ -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;
}
+57
View File
@@ -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;
}
+56
View File
@@ -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;
}