From 7115dab220702168f5905c61e92525bd7ebbaf98 Mon Sep 17 00:00:00 2001 From: DualFroz Date: Tue, 8 Sep 2026 19:37:29 +0200 Subject: [PATCH] feat: extend vfs with sfsFifoFind and sfsFifoHandleFor --- src/vfs.c | 173 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 173 insertions(+) diff --git a/src/vfs.c b/src/vfs.c index 24a1ad8..8f9c730 100644 --- a/src/vfs.c +++ b/src/vfs.c @@ -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))