Files
frostbyte/src/vfsFifoTest.c
T

322 lines
12 KiB
C

#include "ktest.h"
#include "vfs.h"
#include "pipe.h"
#include "fs.h"
#include "string.h"
#include "klib.h"
#include "sfs.h"
/*
* Coverage for named pipes (vfsMkfifo, vfs.h/vfs.c): a real filesystem entry
* of type fsTypeFifo that lets two processes rendezvous over a blocking
* pipe just by agreeing on a path, with no common ancestor and no handle
* number to pass through argv the way vfsPipeCreateHandle/vfsPipeAttach
* (pipeShareTest.c) require. The genuine cross-process park/wake handshake
* over such a fifo only runs for real at boot (namedreader.c/namedwriter.c,
* two independently spawned kmain.c programs, needles "namedreader:
* opened, blocking on read"/"namedreader: payload-ok" in tools/ci-test.sh)
* -- what is deterministic and scheduler-free (open/read/write never
* actually parking a thread) lives here, matching the division of labor
* pipeShareTest.c already established for the handle-based flavor.
*/
KTEST(vfsMkfifoCreatesFifoNodeVisibleViaStat)
{
KT_EQ(t, vfsMkfifo("/tmp/fifoBasic"), 0);
FbStat st;
KT_EQ(t, vfsStat("/tmp/fifoBasic", &st), 0);
KT_EQ(t, st.type, fsTypeFifo);
/* Same path again must fail, like mkdir/open(O_CREAT|O_EXCL) would --
mkfifo never silently succeeds on an existing name. */
KT_EQ(t, vfsMkfifo("/tmp/fifoBasic"), -1);
KT_EQ(t, vfsUnlink("/tmp/fifoBasic"), 0);
KT_EQ(t, vfsStat("/tmp/fifoBasic", &st), -1);
}
KTEST(vfsMkfifoRejectsReservedAndInvalidPaths)
{
/* The one reserved, non-tree path mkfifo never supports -- a raw byte
device has no notion of a named directory entry at all, unlike "/mnt"
(see the sfsCreateFifo-backed tests below), which does. */
KT_EQ(t, vfsMkfifo("/dev/disk0"), -1);
/* Parent directory does not exist at all. */
KT_EQ(t, vfsMkfifo("/doesNotExist/fifo"), -1);
/* Root itself is never writable -- only vfsMountTmpfs-created
directories (e.g. "/tmp") are, same rule makeChildFile enforces for
oCreate. */
KT_EQ(t, vfsMkfifo("/rootFifo"), -1);
}
KTEST(vfsFifoOpenRejectsDualDirectionModes)
{
KT_EQ(t, vfsMkfifo("/tmp/fifoModes"), 0);
/* This kernel's pipe end is inherently one-directional (fdPipeRead XOR
fdPipeWrite) -- a fifo fd can never be both, unlike Linux's permissive
(POSIX-undefined) O_RDWR on a FIFO. */
KT_EQ(t, vfsOpen("/tmp/fifoModes", oReadWrite), -1);
KT_EQ(t, vfsOpen("/tmp/fifoModes", oReadWrite | oWriteOnly), -1);
KT_EQ(t, vfsUnlink("/tmp/fifoModes"), 0);
}
KTEST(vfsFifoRendezvousByPathAloneNoSharedHandle)
{
KT_EQ(t, vfsMkfifo("/tmp/fifoRendezvous"), 0);
/* Two independent vfsOpen calls on the SAME PATH -- no handle number, no
argv, no common caller -- is exactly what namedreader.c/namedwriter.c
(two kmain.c boot programs with no parent-child relationship) rely
on. */
int rfd = vfsOpen("/tmp/fifoRendezvous", oReadOnly);
KT_GE(t, rfd, 0);
/* Nobody has opened the write end yet: a read must report "try again"
(-1), never spurious end-of-file, exactly like a freshly
pipeCreateDetached handle (pipeShareTest.c). */
char probe[4];
KT_EQ(t, vfsRead(rfd, probe, sizeof(probe)), -1);
KT_NOTNULL(t, vfsBlockingChannel(rfd, false));
int wfd = vfsOpen("/tmp/fifoRendezvous", oWriteOnly);
KT_GE(t, wfd, 0);
KT_NE(t, rfd, wfd);
KT_EQ(t, vfsWrite(wfd, "hi", 2), 2);
char buffer[4];
memset(buffer, 0, sizeof(buffer));
KT_EQ(t, vfsRead(rfd, buffer, sizeof(buffer)), 2);
KT_TRUE(t, memcmp(buffer, "hi", 2) == 0);
vfsClose(rfd);
vfsClose(wfd);
KT_EQ(t, vfsUnlink("/tmp/fifoRendezvous"), 0);
}
KTEST(vfsFifoUnlinkWhileOpenLeavesLiveFdsWorking)
{
KT_EQ(t, vfsMkfifo("/tmp/fifoUnlinkLive"), 0);
int rfd = vfsOpen("/tmp/fifoUnlinkLive", oReadOnly);
int wfd = vfsOpen("/tmp/fifoUnlinkLive", oWriteOnly);
KT_GE(t, rfd, 0);
KT_GE(t, wfd, 0);
/* Real unlink-while-open semantics: removing the name must not disturb
fds opened before the unlink -- they keep working off their own copy
of the raw pipe handle, never the freed vfs node. */
KT_EQ(t, vfsUnlink("/tmp/fifoUnlinkLive"), 0);
FbStat st;
KT_EQ(t, vfsStat("/tmp/fifoUnlinkLive", &st), -1);
KT_EQ(t, vfsWrite(wfd, "ok", 2), 2);
char buffer[4];
memset(buffer, 0, sizeof(buffer));
KT_EQ(t, vfsRead(rfd, buffer, sizeof(buffer)), 2);
KT_TRUE(t, memcmp(buffer, "ok", 2) == 0);
vfsClose(rfd);
vfsClose(wfd);
}
KTEST(vfsUnlinkOnNeverOpenedFifoReclaimsPipeSlot)
{
/* Drives the pipe table (pipe.h's fixed-size `pipes[maxPipes]`, shared
with every other pipe user in the kernel) to exhaustion using fifos
that are never opened by anyone -- the one case pipeCloseRead/
pipeCloseWrite's own cleanup can never reach, since neither is ever
called for an end nobody attached. Does not assume the table starts
fully empty (kmain.c's own vfsMkfifo("/tmp/rendezvous") already holds
one slot for the rest of boot by the time ktests run) -- just proves
whatever headroom exists gets used up, and reclaimed, correctly. */
char paths[maxPipes][24];
int madeCount = 0;
for (int i = 0; i < (int)maxPipes; i++) {
snprintf(paths[i], sizeof(paths[i]), "/tmp/fifoLeak%d", i);
if (vfsMkfifo(paths[i]) != 0)
break;
madeCount++;
}
KT_GT(t, madeCount, 0);
/* Table is now genuinely full: one more must fail. */
KT_EQ(t, vfsMkfifo("/tmp/fifoOverflow"), -1);
/* Unlinking one never-opened fifo must reclaim its slot immediately
(pipeDestroyIfUnattached), not leave it dangling until some later,
unrelated event. */
KT_EQ(t, vfsUnlink(paths[0]), 0);
KT_EQ(t, vfsMkfifo("/tmp/fifoOverflow"), 0);
/* Leave the shared pipe table exactly as free as it was found, so later
boot-time pipe users (the pipeProducer/pipeConsumer demo,
fifomaster's handle, killtest's shared pipe, namedreader/
namedwriter's own fifo) are never starved by this test having run. */
vfsUnlink("/tmp/fifoOverflow");
for (int i = 1; i < madeCount; i++)
vfsUnlink(paths[i]);
}
/*
* Coverage for PERSISTENT named pipes: vfsMkfifo under "/mnt" now creates a
* real, on-disk sfs.h entry (sfsTypeFifo) instead of being rejected, so the
* special file itself -- unlike everything above, which lives only in this
* process's kmalloc heap -- survives sfsUnmount()+sfsMount(). The live
* pipe.h byte stream two openers exchange data through is still purely
* in-memory (vfs.c's sfsFifoHandleFor), created lazily on first open, since
* buffered bytes never persist across a real mkfifo(2)'s reboot either --
* only the rendezvous point's name and type do. Every test below starts
* with its own sfsFormat() (same discipline sfsTest.c uses) so results
* never depend on whatever an earlier test (here or in sfsTest.c, same
* boot, same disk) left behind.
*/
KTEST(sfsBackedMkfifoCreatesPersistentFifoVisibleViaStat)
{
KT_TRUE(t, sfsFormat());
KT_EQ(t, vfsMkfifo("/mnt/fifoBasic"), 0);
FbStat st;
KT_EQ(t, vfsStat("/mnt/fifoBasic", &st), 0);
KT_EQ(t, st.type, fsTypeFifo);
KT_EQU(t, st.size, 0u);
/* Same path again must fail, exactly like the tmpfs case above. */
KT_EQ(t, vfsMkfifo("/mnt/fifoBasic"), -1);
KT_EQ(t, vfsUnlink("/mnt/fifoBasic"), 0);
KT_EQ(t, vfsStat("/mnt/fifoBasic", &st), -1);
}
KTEST(sfsBackedFifoRendezvousByPathAloneNoSharedHandle)
{
KT_TRUE(t, sfsFormat());
KT_EQ(t, vfsMkfifo("/mnt/fifoRendezvous"), 0);
int rfd = vfsOpen("/mnt/fifoRendezvous", oReadOnly);
KT_GE(t, rfd, 0);
/* Nobody has opened the write end yet: "try again" (-1), never spurious
end-of-file, exactly like the tmpfs fifo and a freshly
pipeCreateDetached handle both already guarantee. */
char probe[4];
KT_EQ(t, vfsRead(rfd, probe, sizeof(probe)), -1);
KT_NOTNULL(t, vfsBlockingChannel(rfd, false));
int wfd = vfsOpen("/mnt/fifoRendezvous", oWriteOnly);
KT_GE(t, wfd, 0);
KT_NE(t, rfd, wfd);
KT_EQ(t, vfsWrite(wfd, "hi", 2), 2);
char buffer[4];
memset(buffer, 0, sizeof(buffer));
KT_EQ(t, vfsRead(rfd, buffer, sizeof(buffer)), 2);
KT_TRUE(t, memcmp(buffer, "hi", 2) == 0);
vfsClose(rfd);
vfsClose(wfd);
KT_EQ(t, vfsUnlink("/mnt/fifoRendezvous"), 0);
}
KTEST(sfsBackedFifoUnlinkWhileOpenLeavesLiveFdsWorking)
{
KT_TRUE(t, sfsFormat());
KT_EQ(t, vfsMkfifo("/mnt/fifoUnlinkLive"), 0);
int rfd = vfsOpen("/mnt/fifoUnlinkLive", oReadOnly);
int wfd = vfsOpen("/mnt/fifoUnlinkLive", oWriteOnly);
KT_GE(t, rfd, 0);
KT_GE(t, wfd, 0);
/* Real unlink-while-open semantics, same guarantee as the tmpfs fifo:
removing the directory entry must not disturb fds opened before the
unlink -- they keep working off their own copy of the raw pipe
handle number. */
KT_EQ(t, vfsUnlink("/mnt/fifoUnlinkLive"), 0);
FbStat st;
KT_EQ(t, vfsStat("/mnt/fifoUnlinkLive", &st), -1);
KT_EQ(t, vfsWrite(wfd, "ok", 2), 2);
char buffer[4];
memset(buffer, 0, sizeof(buffer));
KT_EQ(t, vfsRead(rfd, buffer, sizeof(buffer)), 2);
KT_TRUE(t, memcmp(buffer, "ok", 2) == 0);
vfsClose(rfd);
vfsClose(wfd);
}
KTEST(sfsBackedFifoEntryPersistsAcrossUnmountRemount)
{
KT_TRUE(t, sfsFormat());
KT_EQ(t, vfsMkfifo("/mnt/fifoPersist"), 0);
/* The real point of this test (mirrors sfsTest.c's own
sfsMountReloadsExistingFilesystem-style coverage): drop EVERY
in-memory sfs.c structure (bitmaps, superblock) and reconstruct them
purely from what sfsFormat/sfsCreateFifo actually wrote to disk --
proving the fifo is a genuine on-disk entry, not a kmalloc node that
would evaporate the instant this process's heap did (like the tmpfs
fifo above, which vfsMkfifo("/mnt/...") deliberately never was). */
sfsUnmount();
KT_TRUE(t, sfsMount());
FbStat st;
KT_EQ(t, vfsStat("/mnt/fifoPersist", &st), 0);
KT_EQ(t, st.type, fsTypeFifo);
/* Still fully functional after the remount, not just present in a
directory listing: a fresh open/write/read round trip works exactly
as it would have before the remount. */
int rfd = vfsOpen("/mnt/fifoPersist", oReadOnly);
int wfd = vfsOpen("/mnt/fifoPersist", oWriteOnly);
KT_GE(t, rfd, 0);
KT_GE(t, wfd, 0);
KT_EQ(t, vfsWrite(wfd, "yo", 2), 2);
char buffer[4];
memset(buffer, 0, sizeof(buffer));
KT_EQ(t, vfsRead(rfd, buffer, sizeof(buffer)), 2);
KT_TRUE(t, memcmp(buffer, "yo", 2) == 0);
vfsClose(rfd);
vfsClose(wfd);
KT_EQ(t, vfsUnlink("/mnt/fifoPersist"), 0);
}
KTEST(sfsBackedFifoReopenAfterCompletedRoundTripGetsFreshWorkingSession)
{
/* Ordinary fifo usage, not a corner case: open both ends, exchange
data, close both, then reopen the SAME persistent path later for a
second, unrelated conversation -- exactly what a real named pipe is
for. The first round trip fully releases pipe.h's underlying slot
back to its general pool (pipeReleaseIfUnused, once both readers and
writers hit 0), so the second round's vfs.c-level binding
(sfsFifoHandleFor) MUST detect that its remembered handle number no
longer refers to the same pipe (pipeIncarnation, pipe.h) and mint a
genuinely fresh one -- otherwise this second write would silently
target a slot nobody has attached to (or, worse, one some unrelated
pipe elsewhere in the kernel grabbed in between), and would report
-1 forever instead of actually delivering the byte. */
KT_TRUE(t, sfsFormat());
KT_EQ(t, vfsMkfifo("/mnt/fifoRoundTrip"), 0);
int rfd1 = vfsOpen("/mnt/fifoRoundTrip", oReadOnly);
int wfd1 = vfsOpen("/mnt/fifoRoundTrip", oWriteOnly);
KT_GE(t, rfd1, 0);
KT_GE(t, wfd1, 0);
KT_EQ(t, vfsWrite(wfd1, "one", 3), 3);
char first[4];
memset(first, 0, sizeof(first));
KT_EQ(t, vfsRead(rfd1, first, sizeof(first)), 3);
KT_TRUE(t, memcmp(first, "one", 3) == 0);
vfsClose(rfd1);
vfsClose(wfd1);
/* Second, independent session on the same path. */
int rfd2 = vfsOpen("/mnt/fifoRoundTrip", oReadOnly);
KT_GE(t, rfd2, 0);