diff --git a/user/timetest.c b/user/timetest.c new file mode 100644 index 0000000..5e2a964 --- /dev/null +++ b/user/timetest.c @@ -0,0 +1,89 @@ +#include "ulib.h" +#include "fs.h" + +/* + * Exercises SFS's new real wall-clock inode timestamps (sfs.h's + * sfsMtimeOf/sfsCtimeOf) through the exact same syscall path a real + * userspace program would use (userOpen/userWrite/userStat/userLink on + * "/mnt", like fstest.c/linktest.c already do for size/content), proving the + * full stack -- syscall -> vfs -> sfs -> the on-disk inode's mtime/ctime + * fields, stamped from the real CMOS RTC (rtc.c) -- works end to end, not + * just the in-kernel sfs.c unit tests. Fully synchronous like nicetest.c/ + * date.c: no injected keystrokes, no shared shell channel, so there is no + * scheduling-timing race to flake on regardless of host load. + */ + +int main(void) +{ + print("timetest: start, pid="); + printDec(userGetpid()); + print("\n"); + + if (userMkdir("/mnt/timetest") != 0) { + print("timetest: FAIL mkdir /mnt/timetest\n"); + return 1; + } + + long before = userTime(); + int fd = (int)userOpen("/mnt/timetest/data.txt", oReadWrite | oCreate); + long after = userTime(); + if (fd < 0) { + print("timetest: FAIL open for create\n"); + return 1; + } + + FbStat st; + if (userStat("/mnt/timetest/data.txt", &st) != 0) { + print("timetest: FAIL stat after create\n"); + return 1; + } + long createdMtime = (long)st.mtime; + long createdCtime = (long)st.ctime; + if (createdMtime != createdCtime || createdMtime < before || createdMtime > after) { + print("timetest: FAIL creation timestamp out of range\n"); + return 1; + } + print("timetest: create-stamped-ok\n"); + + const char* payload = "timetest payload\n"; + unsigned long payloadLen = stringLength(payload); + if (userWrite(fd, payload, payloadLen) != (long)payloadLen) { + print("timetest: FAIL write\n"); + return 1; + } + userClose(fd); + + if (userStat("/mnt/timetest/data.txt", &st) != 0) { + print("timetest: FAIL stat after write\n"); + return 1; + } + if ((long)st.mtime != (long)st.ctime || (long)st.mtime < createdMtime) { + print("timetest: FAIL write did not bump mtime\n"); + return 1; + } + print("timetest: write-stamped-ok\n"); + long mtimeAfterWrite = (long)st.mtime; + + if (userLink("/mnt/timetest/data.txt", "/mnt/timetest/alias.txt") != 0) { + print("timetest: FAIL hardlink\n"); + return 1; + } + + FbStat aliasSt; + if (userStat("/mnt/timetest/alias.txt", &aliasSt) != 0) { + print("timetest: FAIL stat alias\n"); + return 1; + } + if ((long)aliasSt.mtime != mtimeAfterWrite) { + print("timetest: FAIL hardlink does not share mtime\n"); + return 1; + } + if ((long)aliasSt.ctime < mtimeAfterWrite) { + print("timetest: FAIL hardlink ctime went backwards\n"); + return 1; + } + print("timetest: hardlink-shares-mtime-ok\n"); + + print("timetest: PASS\n"); + return 0; +} diff --git a/user/waitanytest.c b/user/waitanytest.c new file mode 100644 index 0000000..f38d21b --- /dev/null +++ b/user/waitanytest.c @@ -0,0 +1,102 @@ +#include "ulib.h" + +/* + * Boot-time driver for waitpid(-1)-style userWaitAny (ulib.c/sched.c's new + * threadJoinAnyChild) -- proves a parent can reap TWO independently exiting + * children without ever naming either pid, in whichever order they + * actually die, and correctly reports ECHILD (-1, no blocking) once none + * are left. + * + * Spawns waitanyfast.c (exits immediately) then waitanyslow.c (sleeps + * 150ms first), then calls userWaitAny() twice: + * - the first call happens essentially immediately after both spawns, so + * waitanyslow.c cannot possibly be Dead yet (its own 150ms sleep alone + * dwarfs the handful of syscalls in between) -- it must be + * waitanyfast.c that gets reaped here, exercising + * threadJoinAnyChild's waitScanFoundDead branch without ever blocking. + * - the second call therefore has only waitanyslow.c left, which is + * genuinely still alive at that point -- it must take the + * waitScanNoneDead branch and actually block through sysWait's real + * retry-syscall path (schedBlockCurrent(currentThread()), sched.c's + * new any-child wake in threadExit) until waitanyslow.c's sleep + * elapses and it exits. + * Verified without leaning on that ordering being guaranteed, though: both + * reaped pids together must equal exactly {fastPid, slowPid} as a set, and + * each one's own exit code must match whichever pid it actually was, in + * whatever order they came back. + * A third userWaitAny() call, once both children are gone, must return -1 + * immediately (ECHILD) rather than hang forever with no children left to + * wait for -- proves threadJoinAnyChild's waitScanNoChildren branch live. + */ + +int main(void) +{ + print("waitanytest: start, pid="); + printDec(userGetpid()); + print("\n"); + + long fastPid = userSpawn("/bin/waitanyfast", 0); + if (fastPid < 0) { + print("waitanytest: FAIL spawn waitanyfast\n"); + return 1; + } + long slowPid = userSpawn("/bin/waitanyslow", 0); + if (slowPid < 0) { + print("waitanytest: FAIL spawn waitanyslow\n"); + return 1; + } + + long reapedPid[2]; + long reapedCode[2]; + for (int i = 0; i < 2; i++) { + long pid = -1; + long code = userWaitAny(&pid); + print("waitanytest: reaped pid="); + printDec(pid); + print(" code="); + printDec(code); + print("\n"); + if (code < 0) { + print("waitanytest: FAIL userWaitAny reported no children too early\n"); + return 1; + } + reapedPid[i] = pid; + reapedCode[i] = code; + } + + int sawFast = 0; + int sawSlow = 0; + for (int i = 0; i < 2; i++) { + if (reapedPid[i] == fastPid) { + sawFast = 1; + if (reapedCode[i] != 44) { + print("waitanytest: FAIL waitanyfast exit code mismatch\n"); + return 1; + } + } else if (reapedPid[i] == slowPid) { + sawSlow = 1; + if (reapedCode[i] != 33) { + print("waitanytest: FAIL waitanyslow exit code mismatch\n"); + return 1; + } + } else { + print("waitanytest: FAIL reaped an unrecognized pid\n"); + return 1; + } + } + if (!sawFast || !sawSlow) { + print("waitanytest: FAIL did not reap both known children exactly once\n"); + return 1; + } + print("waitanytest: any-child-reap-set-ok\n"); + + long pid = 0; + long status = userWaitAny(&pid); + if (status != -1) { + print("waitanytest: FAIL expected -1 (ECHILD) once no children remain\n"); + return 1; + } + print("waitanytest: echild-when-no-children-ok\n"); + + return 0; +}