From f9973a7fe8c473b163df3008475a1fc8b8cccb2f Mon Sep 17 00:00:00 2001 From: DualFroz Date: Wed, 9 Sep 2026 13:38:44 +0200 Subject: [PATCH] feat: introduce streq and printErr --- user/pipesink.c | 65 +++++++++++++++++++++++++++++++++++++++++++++++ user/pipesrc.c | 43 +++++++++++++++++++++++++++++++ user/pollwriter.c | 51 +++++++++++++++++++++++++++++++++++++ 3 files changed, 159 insertions(+) create mode 100644 user/pipesink.c create mode 100644 user/pipesrc.c create mode 100644 user/pollwriter.c diff --git a/user/pipesink.c b/user/pipesink.c new file mode 100644 index 0000000..af04442 --- /dev/null +++ b/user/pipesink.c @@ -0,0 +1,65 @@ +#include "ulib.h" + +/* + * Right half of shell.c's new `cmd1 | cmd2` pipeline syntax's live demo -- + * see pipesrc.c's own header comment for the full picture. jobctlSpawnPiped + * bakes the SAME pipe's read end into this process's own fd 0 before it + * ever runs, so the blocking userRead below has to genuinely retry across + * timer ticks (fd 0's pipe end is flagged fdFlagBlocking, see + * vfsPipeAttachInto) until pipesrc.c's own write actually lands, exactly + * the same real cross-process wake this kernel's every other pipe/fifo + * demo already proves (schedWakeChannel via pipeWrite, pipe.c) -- never a + * coincidence of scheduling order (jobctlSpawnPiped installs pipesrc.c's + * write end into the pipe's writer count the moment IT is created, well + * before it ever runs, so this process can never see a false "no writer, + * EOF" even if it is scheduled to run first). + * + * Reads in a loop until a real EOF (0 return, once pipesrc.c has both + * written and exited -- see that file's own comment on why exit matters + * here) rather than trusting a single userRead call to receive the whole + * payload in one shot, the honest way a real pipe reader has to work. + * + * Unlike pipesrc.c, this process's own fd 1 (stdout) is NOT part of the + * pipeline (only fd 0 was redirected) -- print()/printDec() (ulib.h) reach + * the real console exactly like any unpiped demo, so the needle below is + * exactly as observable to ci-test.sh as every other one. + */ + +static int streq(const char* a, const char* b) +{ + while (*a && *a == *b) { + a++; + b++; + } + return *a == *b; +} + +int main(void) +{ + print("pipesink: blocking on read, waiting for pipesrc\n"); + + char buffer[64]; + unsigned long total = 0; + for (;;) { + long got = userRead(0, buffer + total, sizeof(buffer) - 1 - total); + if (got < 0) { + print("pipesink: FAIL read\n"); + return 1; + } + if (got == 0) + break; + total += (unsigned long)got; + if (total >= sizeof(buffer) - 1) + break; + } + buffer[total] = 0; + + print("pipesink: got: "); + print(buffer); + print("\n"); + + int ok = streq(buffer, "pipeline-payload-ok"); + print(ok ? "pipesink: payload-ok\n" : "pipesink: payload-FAIL\n"); + + return ok ? 0 : 1; +} diff --git a/user/pipesrc.c b/user/pipesrc.c new file mode 100644 index 0000000..c14023c --- /dev/null +++ b/user/pipesrc.c @@ -0,0 +1,43 @@ +#include "ulib.h" + +/* + * Left half of shell.c's new `cmd1 | cmd2` pipeline syntax's live demo. + * jobctlSpawnPiped (jobctl.h) bakes a real pipe end into this process's own + * fd 1 BEFORE it ever runs a single instruction (vfsPipeAttachInto, vfs.h) + * -- from this program's own point of view it writes its stdout exactly + * like any well-behaved Unix filter, with no idea a pipe is on the other + * end instead of the real console. fd 2 (stderr) is deliberately left + * alone by the pipeline setup (only fd 0/fd 1 are ever redirected, matching + * a real shell), so the diagnostic lines below go straight to the real + * console even though fd 1's own payload does not -- writing them through + * fd 1 instead (print()/printDec(), ulib.h, both hardcoded to fd 1) would + * corrupt the exact payload pipesink.c compares against. + * + * Deliberately writes once and exits immediately: pipesink.c's own + * blocking read has to notice the resulting EOF (this process's own exit + * reclaims its fd 1, dropping the pipe's writer count to 0 -- see + * sched.c's reclaimOpenFds) the same way every other pipe/fifo EOF demo in + * this kernel already does, not just receive the payload. + */ + +#define pipelinePayload "pipeline-payload-ok" + +static void printErr(const char* text) +{ + userWrite(2, text, stringLength(text)); +} + +int main(void) +{ + printErr("pipesrc: writing payload to stdout (pipe)\n"); + + unsigned long length = stringLength(pipelinePayload); + long written = userWrite(1, pipelinePayload, length); + if (written != (long)length) { + printErr("pipesrc: FAIL short/failed write\n"); + return 1; + } + + printErr("pipesrc: wrote payload\n"); + return 0; +} diff --git a/user/pollwriter.c b/user/pollwriter.c new file mode 100644 index 0000000..721e859 --- /dev/null +++ b/user/pollwriter.c @@ -0,0 +1,51 @@ +#include "ulib.h" + +/* + * Only ever spawned by polltest.c, with the shared pipe's handle number as + * argv[1] (decToString'd -- see polltest.c, mirrors fifomaster.c's own + * handle-passing convention). Sleeps well past when polltest.c will + * already be parked inside its own userPoll(..., -1) call, so the write + * below has to actually wake it back up (schedWakeChannel(schedPollChannel()), + * pipeWrite in pipe.c) instead of polltest simply finding the pipe already + * readable -- proof the block was real, not a lucky race, the same shape + * fifowriter.c already proves for a plain blocking read(). + */ + +/* Comfortably longer than polltest.c's own phase-2 bounded-timeout poll + (pollTimeoutMs, polltest.c) -- pollwriter is spawned immediately, at the + very start of polltest.c (mirroring fifomaster.c's own immediate double- + spawn), so by the time polltest reaches its phase-3 blocking multi-fd + poll, phase 2's own wait has already used up part of this delay; the + margin here guarantees phase 3 still genuinely has to block and be woken + by the write below, not race a write that already happened. */ +#define pollWriterDelayMs 500 + +static const char* payload = "poll payload"; + +int main(int argc, char** argv) +{ + if (argc != 2) { + print("pollwriter: FAIL expected one argv (pipe handle)\n"); + return 1; + } + + long handle = parseDec(argv[1]); + long fd = userPipeAttach(handle, 1); + if (fd < 0) { + print("pollwriter: FAIL attach\n"); + return 1; + } + + print("pollwriter: attached, sleeping before write\n"); + userSleep(pollWriterDelayMs); + + long written = userWrite((int)fd, payload, stringLength(payload)); + if (written != (long)stringLength(payload)) { + print("pollwriter: FAIL short/failed write\n"); + return 1; + } + print("pollwriter: wrote payload\n"); + + userClose((int)fd); + return 0; +}