test: cover vfsFifoTest

This commit is contained in:
dualfroz committed 2026-09-12 18:07:43 +02:00
1 parent 8fe67d9b3e
commit 4b41308da9
1 file changed
+162
+162
View File
@@ -0,0 +1,162 @@
#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]);
}