diff --git a/user/redirreader.c b/user/redirreader.c new file mode 100644 index 0000000..bf1aa6e --- /dev/null +++ b/user/redirreader.c @@ -0,0 +1,57 @@ +#include "ulib.h" + +/* + * Companion to redirwriter.c -- reads fd 0 (redirected by the shell to a + * REAL file via `<`, jobctlApplyRedirects) in a loop until a real EOF (a + * regular file's own vfsRead already returns 0 the instant the offset + * reaches its size -- no writer-count bookkeeping needed the way + * pipesink.c's pipe-backed EOF does, since a file's "end" is just its + * length, not "every writer has gone away"), then compares byte-for-byte + * against two back-to-back copies of redirwriter.c's own payload -- proof + * that `>` (create/truncate) followed by `>>` (append) on the SAME path + * produced exactly that file, not a truncated/duplicated/reordered one. + * + * Unlike pipesink.c, this process's own fd 1 (stdout) is NOT redirected -- + * print()/printDec() (ulib.h) reach the real console exactly like any + * unredirected 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("redirreader: reading redirected stdin\n"); + + char buffer[80]; + unsigned long total = 0; + for (;;) { + long got = userRead(0, buffer + total, sizeof(buffer) - 1 - total); + if (got < 0) { + print("redirreader: FAIL read\n"); + return 1; + } + if (got == 0) + break; + total += (unsigned long)got; + if (total >= sizeof(buffer) - 1) + break; + } + buffer[total] = 0; + + print("redirreader: got: "); + print(buffer); + print("\n"); + + int ok = streq(buffer, "redirect-payload-okredirect-payload-ok"); + print(ok ? "redirreader: payload-ok\n" : "redirreader: payload-FAIL\n"); + + return ok ? 0 : 1; +} diff --git a/user/redirwriter.c b/user/redirwriter.c new file mode 100644 index 0000000..33b06d4 --- /dev/null +++ b/user/redirwriter.c @@ -0,0 +1,43 @@ +#include "ulib.h" + +/* + * Live ring-3 proof for shell.c's new `cmd > file` / `cmd >> file` file + * redirection (jobctlApplyRedirects/jobctlSpawnForegroundRedirected, + * jobctl.h): from this program's own point of view it just writes its + * stdout exactly like pipesrc.c already does for the pipe case -- it has + * no idea whether fd 1 is the real console, a pipe, or (here) a real file + * on tmpfs, the same "redirection is invisible to the child" property a + * real shell's dup2()-before-exec gives every Unix program. fd 2 is + * deliberately left alone (only fd 0/fd 1 are ever redirected, matching + * jobctlSpawnPiped's own pipeline convention), so the diagnostic line + * below reaches the real console even while fd 1's own payload goes to + * the file instead -- writing it through fd 1 would corrupt the exact + * bytes redirreader.c later compares. + * + * kmain.c's redirDriverThread runs this program TWICE against the same + * path -- once through `>` (truncate/create) and once through `>>` + * (append) -- so the resulting file holds two back-to-back copies of this + * same payload with no separator, proving `>>` genuinely continues from + * the end of what `>` already wrote rather than overwriting it. + */ +#define redirectPayload "redirect-payload-ok" + +static void printErr(const char* text) +{ + userWrite(2, text, stringLength(text)); +} + +int main(void) +{ + printErr("redirwriter: writing payload to stdout (redirected)\n"); + + unsigned long length = stringLength(redirectPayload); + long written = userWrite(1, redirectPayload, length); + if (written != (long)length) { + printErr("redirwriter: FAIL short/failed write\n"); + return 1; + } + + printErr("redirwriter: wrote payload\n"); + return 0; +}