From 67ea1f461ed144f76090b311241e65a76cbb52fb Mon Sep 17 00:00:00 2001 From: DualFroz Date: Wed, 9 Sep 2026 15:12:31 +0200 Subject: [PATCH] feat: add onUsr1 and onUsr2 --- user/restarttarget.c | 153 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 153 insertions(+) create mode 100644 user/restarttarget.c diff --git a/user/restarttarget.c b/user/restarttarget.c new file mode 100644 index 0000000..0042400 --- /dev/null +++ b/user/restarttarget.c @@ -0,0 +1,153 @@ +#include "ulib.h" + +/* + * Only ever spawned by restarttest.c, with four shared pipe handles as argv + * (ready/announce1/announce2/data, decToString'd -- the same convention + * nesttarget.c/jobtarget.c already establish for a multi-pipe rendezvous). + * + * Proves signal.h's SA_RESTART/EINTR distinction (signalnum.h's + * sigFlagRestart, sched.h's Thread.sigInRetryableSyscall) end to end, in + * two phases against two independently registered handlers: + * + * phase 1 (no sigFlagRestart): blocks in a real userRead on a pipe NO ONE + * will EVER write (the exact "blocked forever" setup user/eintrtarget.c + * already uses for threadKill/early-wake), gets sigUsr1 -- registered + * WITHOUT sigFlagRestart -- and must see that read return errInterrupted + * the instant onUsr1 returns, not silently retry and hang forever. + * + * phase 2 (WITH sigFlagRestart): blocks in a real userRead on a SECOND + * pipe restarttest.c genuinely does write, gets sigUsr2 -- registered + * WITH sigFlagRestart -- while blocked, and must see that read + * transparently resume and return the real byte once onUsr2 returns, + * exactly like every other retryable syscall already did before this + * flag existed (user/nesttarget.c's onUsr1/goFd relies on precisely this + * for an unrelated reason -- see that file's own comment). + * + * Each phase's blocking read is announced ("about to call it now") over + * its own pipe immediately beforehand, the same "announce, then the OTHER + * process learns it via its own blocking read before acting" discipline + * nesttest.c/nesttarget.c already established for the analogous nested- + * delivery race -- not airtight against an adversarial scheduler in + * principle, but the SAME assumption those already-verified demos (and + * eintrtest.c's) rely on in practice. The rigorous, race-free proof of the + * actual rip/rax rewrite lives in sigTest.c, driving sigConsumePending + * directly. + */ + +static volatile int handlerUsr1Fired = 0; +static volatile int handlerUsr2Fired = 0; + +static void onUsr1(int signum) +{ + (void)signum; + handlerUsr1Fired = 1; + print("restarttarget: usr1-handler-fired\n"); +} + +static void onUsr2(int signum) +{ + (void)signum; + handlerUsr2Fired = 1; + print("restarttarget: usr2-handler-fired\n"); +} + +int main(int argc, char** argv) +{ + print("restarttarget: start, pid="); + printDec(userGetpid()); + print("\n"); + + if (argc != 5) { + print("restarttarget: FAIL expected four argv (ready/announce1/announce2/data handles)\n"); + return 1; + } + + /* sigUsr1 deliberately has NO sigFlagRestart (0): phase 1 below expects + its own interrupted read to return errInterrupted, not resume. + sigUsr2 deliberately DOES have sigFlagRestart: phase 2 expects the + opposite, a transparent resume with the real data. */ + if (userSigAction(sigUsr1, onUsr1, 0, 0) == (long)sigSetFailed || + userSigAction(sigUsr2, onUsr2, 0, sigFlagRestart) == (long)sigSetFailed) { + print("restarttarget: FAIL install handlers\n"); + return 1; + } + + long victimHandle = userPipeCreate(); + if (victimHandle < 0) { + print("restarttarget: FAIL create victim pipe\n"); + return 1; + } + long victimFd = userPipeAttach(victimHandle, 0); + if (victimFd < 0) { + print("restarttarget: FAIL attach victim pipe\n"); + return 1; + } + + long readyHandle = parseDec(argv[1]); + long announce1Handle = parseDec(argv[2]); + long announce2Handle = parseDec(argv[3]); + long dataHandle = parseDec(argv[4]); + + long readyFd = userPipeAttach(readyHandle, 1); + long announce1Fd = userPipeAttach(announce1Handle, 1); + long announce2Fd = userPipeAttach(announce2Handle, 1); + long dataFd = userPipeAttach(dataHandle, 0); + if (readyFd < 0 || announce1Fd < 0 || announce2Fd < 0 || dataFd < 0) { + print("restarttarget: FAIL attach ready/announce/data pipes\n"); + return 1; + } + + if (userWrite((int)readyFd, "r", 1) != 1) { + print("restarttarget: FAIL write ready byte\n"); + return 1; + } + userClose((int)readyFd); + print("restarttarget: ready, handlers installed\n"); + + /* Phase 1: no-restart. */ + print("restarttarget: phase1 blocking on victim read (expect errInterrupted)\n"); + if (userWrite((int)announce1Fd, "a", 1) != 1) { + print("restarttarget: FAIL write announce1 byte\n"); + return 1; + } + userClose((int)announce1Fd); + + char victimBuffer[4]; + long n1 = userRead((int)victimFd, victimBuffer, sizeof(victimBuffer)); + userClose((int)victimFd); + + if (!handlerUsr1Fired) { + print("restarttarget: FAIL usr1 handler never fired\n"); + return 1; + } + if (n1 != errInterrupted) { + print("restarttarget: FAIL victim read did not return errInterrupted\n"); + return 1; + } + print("restarttarget: phase1-eintr-no-restart-ok\n"); + + /* Phase 2: sigFlagRestart. */ + print("restarttarget: phase2 blocking on data read (expect transparent restart)\n"); + if (userWrite((int)announce2Fd, "a", 1) != 1) { + print("restarttarget: FAIL write announce2 byte\n"); + return 1; + } + userClose((int)announce2Fd); + + char dataByte = 0; + long n2 = userRead((int)dataFd, &dataByte, 1); + userClose((int)dataFd); + + if (!handlerUsr2Fired) { + print("restarttarget: FAIL usr2 handler never fired\n"); + return 1; + } + if (n2 != 1 || dataByte != 'd') { + print("restarttarget: FAIL data read did not transparently resume with real data\n"); + return 1; + } + print("restarttarget: phase2-restart-transparent-ok\n"); + + print("restarttarget: all-phases-ok\n"); + return 55; +}