diff --git a/src/vfsFifoTest.c b/src/vfsFifoTest.c index e4329c6..e0fa612 100644 --- a/src/vfsFifoTest.c +++ b/src/vfsFifoTest.c @@ -319,3 +319,87 @@ KTEST(sfsBackedFifoReopenAfterCompletedRoundTripGetsFreshWorkingSession) int rfd2 = vfsOpen("/mnt/fifoRoundTrip", oReadOnly); KT_GE(t, rfd2, 0); + /* A genuinely fresh pipe reports "try again" here, exactly like a + first-ever open -- a stale, no-longer-attached handle would instead + report -1 forever for an entirely different reason (pipeAt finding + nothing "used" at that slot), which the write assertion below is + what actually tells apart from this one. */ + char probe[4]; + KT_EQ(t, vfsRead(rfd2, probe, sizeof(probe)), -1); + + int wfd2 = vfsOpen("/mnt/fifoRoundTrip", oWriteOnly); + KT_GE(t, wfd2, 0); + KT_EQ(t, vfsWrite(wfd2, "two", 3), 3); + char second[4]; + memset(second, 0, sizeof(second)); + KT_EQ(t, vfsRead(rfd2, second, sizeof(second)), 3); + KT_TRUE(t, memcmp(second, "two", 3) == 0); + + vfsClose(rfd2); + vfsClose(wfd2); + KT_EQ(t, vfsUnlink("/mnt/fifoRoundTrip"), 0); +} + +KTEST(sfsBackedFifoBindingSurvivesReformatOfAnUnrelatedInode) +{ + /* sfsTest.c's tests each start with their own sfsFormat(), reusing + inode numbers dozens of times a boot. A fifo binding left open + ACROSS such a reformat (contrived here, but exactly the shape + reclaimOpenFds/sched.c would leave behind for a thread that died + without closing its fd -- see sched.c) must never let the fresh + filesystem's completely unrelated reuse of the same inode number + inherit the old, orphaned session. */ + KT_TRUE(t, sfsFormat()); + KT_EQ(t, vfsMkfifo("/mnt/fifoGenA"), 0); + int32 firstInode = sfsLookup("/fifoGenA"); + KT_GE(t, firstInode, 0); + + /* Left deliberately open across the reformat below -- an orphaned + session, on purpose, is exactly what must NOT leak into the new one. */ + int staleRfd = vfsOpen("/mnt/fifoGenA", oReadOnly); + KT_GE(t, staleRfd, 0); + + /* A full reformat: every old inode number, including firstInode, now + means whatever the fresh, empty filesystem below reassigns it to. */ + KT_TRUE(t, sfsFormat()); + KT_EQ(t, vfsMkfifo("/mnt/fifoGenA"), 0); + int32 secondInode = sfsLookup("/fifoGenA"); + KT_GE(t, secondInode, 0); + /* Deterministic on a freshly formatted, otherwise-empty filesystem: + both are the very first entry created after root, so the numbers + collide -- exactly the scenario that makes the generation check + necessary rather than merely defensive. */ + KT_EQ(t, secondInode, firstInode); + + int rfd = vfsOpen("/mnt/fifoGenA", oReadOnly); + int wfd = vfsOpen("/mnt/fifoGenA", oWriteOnly); + KT_GE(t, rfd, 0); + KT_GE(t, wfd, 0); + KT_EQ(t, vfsWrite(wfd, "new", 3), 3); + + /* The discriminating check: if the binding table wrongly inherited the + pre-reformat session (generation check not actually doing its job), + staleRfd would be a SECOND reader attached to the very same + underlying pipe wfd just wrote into, and -- read before rfd below -- + would steal the payload meant for the new session's own reader. + Correct isolation means staleRfd's pipe never had any writer at all + (it only ever attached as a reader), so it must still report "try + again", never the new session's bytes. */ + char stalePeek[4]; + KT_EQ(t, vfsRead(staleRfd, stalePeek, sizeof(stalePeek)), -1); + + char buffer[4]; + memset(buffer, 0, sizeof(buffer)); + KT_EQ(t, vfsRead(rfd, buffer, sizeof(buffer)), 3); + KT_TRUE(t, memcmp(buffer, "new", 3) == 0); + + vfsClose(rfd); + vfsClose(wfd); + /* The orphaned pre-reformat fd is cleaned up here rather than left to + leak a pipe.c slot for the rest of the boot -- vfsClose only ever + touches pipe.c state (pipeCloseRead), never re-derefs the long-gone + inode, so this is safe regardless of what the fresh filesystem did + with the number. */ + vfsClose(staleRfd); + vfsUnlink("/mnt/fifoGenA"); +}