From d7f42ecd65c58b1e7a404e303ed0d53b7d0e4c61 Mon Sep 17 00:00:00 2001 From: DualFroz Date: Mon, 14 Sep 2026 02:44:59 +0200 Subject: [PATCH] test: cover sigtest --- user/sigtest.c | 127 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 127 insertions(+) create mode 100644 user/sigtest.c diff --git a/user/sigtest.c b/user/sigtest.c new file mode 100644 index 0000000..43fa245 --- /dev/null +++ b/user/sigtest.c @@ -0,0 +1,127 @@ +#include "ulib.h" + +/* + * Boot-time driver for the whole signal-delivery demo (signal.h). Spawns + * two independent children -- sigtarget.c (installs a real handler) and + * sigdefault.c (installs none) -- and sends each a real signal through the + * full syscall path (userSigSend, not a kernel-internal shortcut), proving + * both outcomes sigConsumePending can produce: caught-and-resumed, and + * uncaught-and-terminated. + * + * sigtarget needs a real happens-before guarantee, not a sleep guess: + * sigUsr1's default disposition is Terminate, so a signal that arrives + * before sigtarget's userSigAction has actually run would kill it instead + * of being caught -- this was originally "give it 100ms and hope", which + * really did fail under host load (sigtarget killed by the uncaught + * default before it ever installed its handler). See sigtarget.c for the + * shared-pipe rendezvous (fifomaster.c's pattern) that fixes it: this + * process blocks on a real read for sigtarget's own "handler installed" + * byte before ever calling userSigSend. + * + * sigdefault has no such window -- its default disposition applies from + * the moment it exists, whether or not it has run any code yet -- so a + * plain sleep-then-send (mirroring killtest.c's own "give the victim time + * to reach its blocking read") is genuinely sufficient there. + */ + +int main(void) +{ + print("sigtest: start, pid="); + printDec(userGetpid()); + print("\n"); + + if (userSigSend(999999, sigUsr1) != -1) { + print("sigtest: FAIL signal to bogus pid should fail\n"); + return 1; + } + print("sigtest: signal to bad pid rejected\n"); + + if (userSigAction(sigKill, (SigHandler)0, 0, 0) != (long)sigSetFailed) { + print("sigtest: FAIL installing a sigKill handler should be rejected\n"); + return 1; + } + print("sigtest: sigkill-handler-rejected-ok\n"); + + long readyHandle = userPipeCreate(); + if (readyHandle < 0) { + print("sigtest: FAIL create ready pipe\n"); + return 1; + } + + /* Attach as reader BEFORE sigtarget even exists, let alone before it + can possibly attach+write+close its own end: pipeCloseWrite (pipe.c) + reclaims a detached pipe's slot the moment readers AND writers both + read 0, which they still would right after sigtarget's write+close + if this process had not attached yet -- observed for real (sigtarget + raced ahead, wrote its ready byte, closed, and freed the slot before + this process's own attach below ever ran, which then failed outright + on the now-invalid handle). Attaching first makes readers==1 from + the very start, so that race window never opens. */ + long readyFd = userPipeAttach(readyHandle, 0); + if (readyFd < 0) { + print("sigtest: FAIL attach ready pipe\n"); + return 1; + } + + char readyHandleStr[24]; + decToString(readyHandle, readyHandleStr); + + char* targetArgv[] = { "/bin/sigtarget", readyHandleStr, 0 }; + long target = userSpawn("/bin/sigtarget", targetArgv); + if (target < 0) { + print("sigtest: FAIL spawn sigtarget\n"); + return 1; + } + + char readyByte; + /* Real blocking read: does not return until sigtarget has actually + installed its handler and written this byte (see sigtarget.c) -- + the happens-before guarantee a fixed userSleep could not give. */ + if (userRead((int)readyFd, &readyByte, 1) != 1) { + print("sigtest: FAIL read ready byte\n"); + return 1; + } + userClose((int)readyFd); + + if (userSigSend(target, sigUsr1) != 0) { + print("sigtest: FAIL send sigUsr1 to sigtarget\n"); + return 1; + } + print("sigtest: sent sigUsr1 to sigtarget\n"); + + long status = userWait(target); + print("sigtest: sigtarget exit status="); + printDec(status); + print("\n"); + if (status != 42) { + print("sigtest: FAIL expected sigtarget exit status 42\n"); + return 1; + } + print("sigtest: handler-roundtrip-ok\n"); + + long defaultTarget = userSpawn("/bin/sigdefault", 0); + if (defaultTarget < 0) { + print("sigtest: FAIL spawn sigdefault\n"); + return 1; + } + + userSleep(100); + + if (userSigSend(defaultTarget, sigTerm) != 0) { + print("sigtest: FAIL send sigTerm to sigdefault\n"); + return 1; + } + print("sigtest: sent sigTerm to sigdefault\n"); + + long defaultStatus = userWait(defaultTarget); + print("sigtest: sigdefault exit status="); + printDec(defaultStatus); + print("\n"); + if (defaultStatus != 128 + sigTerm) { + print("sigtest: FAIL expected sigdefault exit status 128+sigTerm\n"); + return 1; + } + print("sigtest: default-terminate-ok\n"); + + return 0; +}