feat: extend vfs with sfsFifoFind and sfsFifoHandleFor

This commit is contained in:
dualfroz committed 2026-09-08 19:37:29 +02:00
1 parent 86c742f0c8
commit 7115dab220
1 file changed
+173
+173
View File
@@ -554,6 +554,179 @@ typedef struct SfsFifoBinding {
int32 inodeNum;
int32 pipeHandle;
uint32 incarnation;
uint64 generation;
} SfsFifoBinding;
static SfsFifoBinding sfsFifoBindings[maxSfsFifoBindings];
/* Finds inodeNum's live binding, discarding it first if it is stale by
either check above. Returns nullptr if there is no valid binding (never
bound, or just evicted as stale). */
static SfsFifoBinding* sfsFifoFind(int32 inodeNum)
{
uint64 gen = sfsGeneration();
for (uint32 i = 0; i < maxSfsFifoBindings; i++) {
SfsFifoBinding* b = &sfsFifoBindings[i];
if (b->inodeNum != inodeNum)
continue;
if (b->generation != gen || pipeIncarnation(b->pipeHandle) != b->incarnation) {
b->inodeNum = 0;
return nullptr;
}
return b;
}
return nullptr;
}
/* Returns inodeNum's live pipe handle, creating a fresh pipeCreateDetached
one (and a new table slot) the first time this inode is opened since the
last time nobody had it open -- exactly the "nobody attached yet, keep
retrying rather than report broken/EOF" contract pipeCreateDetached
itself already documents. -1 if pipe.c's own table is exhausted, or this
inode has no existing binding and the (much smaller) binding table above
is itself full. */
static int32 sfsFifoHandleFor(int32 inodeNum)
{
SfsFifoBinding* existing = sfsFifoFind(inodeNum);
if (existing)
return existing->pipeHandle;
int handle = pipeCreateDetached();
if (handle < 0)
return -1;
for (uint32 i = 0; i < maxSfsFifoBindings; i++) {
if (sfsFifoBindings[i].inodeNum == 0) {
sfsFifoBindings[i].inodeNum = inodeNum;
sfsFifoBindings[i].pipeHandle = handle;
sfsFifoBindings[i].incarnation = pipeIncarnation(handle);
sfsFifoBindings[i].generation = sfsGeneration();
return handle;
}
}
pipeDestroyIfUnattached(handle);
return -1;
}
/* Called when inodeNum's own directory entry is actually gone for good
(sfsRemove reached the last link, see vfsUnlink) -- evicts the table slot
FIRST, then attempts pipeDestroyIfUnattached on the handle it held:
unlinking a fifo that is currently open must leave the still-open ends
alone (they keep working off their own copy of the handle number,
matching the tmpfs fifo's own vfsUnlink comment below), so the destroy is
a no-op unless nobody ever attached to it. Evicting before destroying
(rather than after) closes the narrow window where a concurrent opener
of the SAME inode number could otherwise see the stale slot as still
valid and attach to a handle number pipe.c is about to hand out to
something completely unrelated. */
static void sfsFifoForget(int32 inodeNum)
{
for (uint32 i = 0; i < maxSfsFifoBindings; i++) {
if (sfsFifoBindings[i].inodeNum == inodeNum) {
int handle = sfsFifoBindings[i].pipeHandle;
sfsFifoBindings[i].inodeNum = 0;
pipeDestroyIfUnattached(handle);
return;
}
}
}
static int64 sfsFdRead(FileDescriptor* d, void* buffer, uint64 length)
{
int64 got = sfsReadAt(d->pipeHandle, d->offset, buffer, length);
if (got > 0)
d->offset += (uint64)got;
return got;
}
static int64 sfsFdWrite(FileDescriptor* d, const void* buffer, uint64 length)
{
int64 written = sfsWriteAt(d->pipeHandle, d->offset, buffer, length);
if (written > 0)
d->offset += (uint64)written;
return written;
}
int vfsMkfifo(const char* path)
{
char resolvedPath[fsPathMax];
vfsResolvePath(path, resolvedPath, sizeof(resolvedPath));
path = resolvedPath;
const char* rel;
/* Persistent named pipe (sfs.h's sfsTypeFifo): only the directory entry
and its type are created here -- the live pipe.h handle two openers
actually rendezvous over is created lazily by vfsOpen the first time
anyone opens it (sfsFifoHandleFor above), the same "create on first
real use" split every other SFS-backed thing keeps between "what is
on disk" (sfs.c, no pipe.h dependency at all) and "what is live in
this boot's memory" (here). */
if (isSfsPath(path, &rel))
return sfsCreateFifo(rel) >= 0 ? 0 : -1;
if (strcmp(path, blockDevPath) == 0)
return -1;
if (lookupNode(path))
return -1;
char leaf[fsNameMax];
VfsNode* parent = splitParent(path, leaf, sizeof(leaf));
if (!parent || parent->type != fsTypeDir || !parent->writable
|| !vfsPermitted(parent->mode, parent->uid, parent->gid, currentThreadUid(), currentThreadGid(), fsModeOwnerWrite))
return -1;
int handle = pipeCreateDetached();
if (handle < 0)
return -1;
if (!makeChildFifo(parent, leaf, strlen(leaf), handle)) {
pipeDestroyIfUnattached(handle);
return -1;
}
return 0;
}
/* Shared core of vfsOpen/vfsOpenInto below: resolves `path`+`flags` and, on
success, fills EVERY field of `d` (whichever FileDescriptor the caller
decided on -- a freshly allocated slot in the calling thread's own table
for vfsOpen, or an explicit slot in some other, not-yet-running thread's
table for vfsOpenInto) and returns 0. On failure, returns -1 having
written NOTHING to `d` at all -- every early return below is before the
first store into `d`, so a caller never has to clean up a half-written
descriptor. */
static int resolveOpen(FileDescriptor* d, const char* path, uint32 flags)
{
const char* rel;
if (isSfsPath(path, &rel)) {
int32 inodeNum = sfsLookup(rel);
if (inodeNum < 0) {
if (!(flags & oCreate))
return -1;
inodeNum = sfsCreateFile(rel);
if (inodeNum < 0)
return -1;
} else if (sfsTypeOf(inodeNum) == sfsTypeFifo) {
/* One-directional only, exactly like the tmpfs fifo branch
below and vfsPipeAttach's wantWrite bool -- a real O_RDWR
fifo fd has no equivalent here, since a single fd can only be
fdPipeRead or fdPipeWrite, never both. */
uint32 mode = flags & (oWriteOnly | oReadWrite);
bool wantWrite;
if (mode == oWriteOnly)
wantWrite = true;
else if (mode == oReadOnly)
wantWrite = false;
else
return -1;
int handle = sfsFifoHandleFor(inodeNum);
if (handle < 0)
return -1;
d->node = nullptr;
d->offset = 0;
d->kind = wantWrite ? fdPipeWrite : fdPipeRead;
d->flags = fdFlagBlocking;
d->pipeHandle = handle;
VfsNode* node = lookupNode(path);
if (!node) {
if (!(flags & oCreate))