From db5a6274c9bbe636e8ade96e54bed00b466f4283 Mon Sep 17 00:00:00 2001 From: DualFroz Date: Tue, 8 Sep 2026 19:26:24 +0200 Subject: [PATCH] feat: extend vfs with makeChildFifo --- src/vfs.c | 117 ++++++++++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 105 insertions(+), 12 deletions(-) diff --git a/src/vfs.c b/src/vfs.c index 0da7118..0c795c1 100644 --- a/src/vfs.c +++ b/src/vfs.c @@ -5,17 +5,51 @@ #include "console.h" #include "keyboard.h" #include "sched.h" +#include "pipe.h" +#include "pidfd.h" +#include "ata.h" +#include "sfs.h" +#include "poll.h" #define tmpfsBlock 512 +#define blockDevPath "/dev/disk0" +#define sfsMountPrefix "/mnt" +#define sfsMountPrefixLen 4 struct VfsNode { char name[fsNameMax]; uint32 type; bool writable; + /* Real owner rwx permission bits (fs.h's fsModeOwner*), an axis + independent of `writable` above: `writable` says whether this node's + CONTENT/CHILDREN may be mutated at all (a mount-level property, false + for every ustar-loaded node regardless of mode); `mode` is the real, + chmod(2)-settable per-node permission a caller must ALSO satisfy to + open the node for writing or (jobctl.c/syscall.c) exec it. Both gates + must pass, exactly like a real read-only mount PLUS real per-file + permission bits would. */ + uint32 mode; + /* Real owner identity (POSIX uid, fs.h's FbStat.uid): whichever + currentThreadUid() was current at nodeAlloc time -- always 0 ("root") + for every ustar-loaded and boot-time tmpfs-mount node, since + vfsMountUstar/vfsMountTmpfs both run after schedInit but before any + real process exists (schedInit's idle thread, uid 0, is always + "current" then). Never retroactive, like mode's own default; there is + no chown-shaped call anywhere in this kernel to ever change it after + creation (see fs.h's FbStat.uid doc comment). */ + uint32 uid; + /* Real group identity (fs.h's FbStat.gid): whichever currentThreadGid() + was current at nodeAlloc time, same creation-time-only, never- + retroactive shape as uid immediately above. */ + uint32 gid; const uint8* roData; uint8* rwData; uint64 size; uint64 capacity; + /* Only meaningful for type == fsTypeFifo: the pipe.h handle every + vfsOpen on this path attaches a fresh end to (see makeChildFifo). -1 + for every other node type. */ + int32 pipeHandle; struct VfsNode* parent; struct VfsNode* firstChild; struct VfsNode* nextSibling; @@ -23,7 +57,7 @@ struct VfsNode { static VfsNode* root; -static VfsNode* nodeAlloc(const char* name, uint64 nameLen, uint32 type, bool writable) +static VfsNode* nodeAlloc(const char* name, uint64 nameLen, uint32 type, bool writable, uint32 mode) { VfsNode* node = (VfsNode*)kmalloc(sizeof(VfsNode)); if (!node) @@ -35,6 +69,10 @@ static VfsNode* nodeAlloc(const char* name, uint64 nameLen, uint32 type, bool wr node->name[nameLen] = 0; node->type = type; node->writable = writable; + node->mode = mode; + node->uid = currentThreadUid(); + node->gid = currentThreadGid(); + node->pipeHandle = -1; return node; } @@ -53,18 +91,18 @@ static VfsNode* findChild(VfsNode* parent, const char* name, uint64 nameLen) return nullptr; } -static VfsNode* makeChildDir(VfsNode* parent, const char* name, uint64 nameLen, bool writable) +static VfsNode* makeChildDir(VfsNode* parent, const char* name, uint64 nameLen, bool writable, uint32 mode) { - VfsNode* node = nodeAlloc(name, nameLen, fsTypeDir, writable); + VfsNode* node = nodeAlloc(name, nameLen, fsTypeDir, writable, mode); if (node) addChild(parent, node); return node; } static VfsNode* makeChildFile(VfsNode* parent, const char* name, uint64 nameLen, - const uint8* data, uint64 size, bool writable) + const uint8* data, uint64 size, bool writable, uint32 mode) { - VfsNode* node = nodeAlloc(name, nameLen, fsTypeFile, writable); + VfsNode* node = nodeAlloc(name, nameLen, fsTypeFile, writable, mode); if (!node) return nullptr; if (writable) { @@ -79,6 +117,29 @@ static VfsNode* makeChildFile(VfsNode* parent, const char* name, uint64 nameLen, return node; } +static VfsNode* makeChildFifo(VfsNode* parent, const char* name, uint64 nameLen, int pipeHandle) +{ + VfsNode* node = nodeAlloc(name, nameLen, fsTypeFifo, true, fsModeDefaultFile); + if (!node) + return nullptr; + node->pipeHandle = pipeHandle; + addChild(parent, node); + return node; +} + +/* Real POSIX "search" permission: resolving a NAME inside a directory (as + opposed to "." / ".." above, which never look anything up) requires the + owner-or-other exec bit on that directory for the calling thread's own + uid, exactly like a real kernel's namei() checks X_OK on every directory + it walks through. Deliberately only gates the findChild() branch below, + not "."/".." -- an honest, minor scope boundary matching this codebase's + existing style of not fixing unrelated pre-existing gaps while adding a + new feature (see this file's many other "deliberately out of scope" + comments). Denial collapses to the same nullptr every other lookup + failure already returns, matching every other EACCES-shaped refusal in + this file (resolveOpen's write check, etc.) -- callers cannot tell + "does not exist" from "exists but not searchable", exactly like the rest + of this VFS layer's error handling. */ static VfsNode* lookupNode(const char* path) { if (!path) @@ -103,6 +164,8 @@ static VfsNode* lookupNode(const char* path) } else { if (cur->type != fsTypeDir) return nullptr; + if (!vfsPermitted(cur->mode, cur->uid, cur->gid, currentThreadUid(), currentThreadGid(), fsModeOwnerExec)) + return nullptr; VfsNode* child = findChild(cur, start, len); if (!child) return nullptr; @@ -191,12 +254,27 @@ static void ustarAdd(const char* rawPath, bool isDir, const uint8* data, uint64 if (last) { if (!child) { if (isDir) - child = makeChildDir(cur, path + start, len, false); - else - child = makeChildFile(cur, path + start, len, data, size, false); + child = makeChildDir(cur, path + start, len, false, fsModeDefaultDir); + else { + /* Anything shipped under "bin/" (mktar's own stored-name + convention, no leading slash -- see tools/mktar.c) is a + real binary this kernel spawns by path (jobctlSpawn, + syscall.c's loadImageByPath): give it the owner-exec + bit by default, exactly as if someone had already run + `chmod 755` on it, since nothing in this kernel could + otherwise ever chmod a read-only ustar-loaded file + (vfsChmod refuses a non-writable node, same as every + other tmpfs mutation). Everything else defaults to + fsModeDefaultFile (no exec bit), matching a real + ordinary shipped data file. */ + uint32 mode = (n > 4 && memcmp(path, "bin/", 4) == 0) + ? fsModeDefaultExec + : fsModeDefaultFile; + child = makeChildFile(cur, path + start, len, data, size, false, mode); + } } } else if (!child) { - child = makeChildDir(cur, path + start, len, false); + child = makeChildDir(cur, path + start, len, false, fsModeDefaultDir); } if (!child) return; @@ -206,7 +284,7 @@ static void ustarAdd(const char* rawPath, bool isDir, const uint8* data, uint64 void vfsInit(void) { - root = nodeAlloc("/", 1, fsTypeDir, false); + root = nodeAlloc("/", 1, fsTypeDir, false, fsModeDefaultDir); } void vfsMountUstar(uint64 start, uint64 end) @@ -233,7 +311,7 @@ void vfsMountTmpfs(const char* path) return; if (findChild(parent, leaf, strlen(leaf))) return; - makeChildDir(parent, leaf, strlen(leaf), true); + makeChildDir(parent, leaf, strlen(leaf), true, fsModeDefaultDir); kprintf("[vfs] mounted writable tmpfs at %s\n", path); } @@ -244,10 +322,25 @@ void vfsFdInit(FileDescriptor* fds) fds[i].offset = 0; fds[i].kind = fdFree; fds[i].flags = 0; + fds[i].pipeHandle = -1; } fds[0].kind = fdConsole; fds[1].kind = fdConsole; fds[2].kind = fdConsole; + + /* fd 0's own console reader queue (keyboard.h) -- reused field, same as + a pipe/fifo fd's handle into pipe.c's table. Every thread (kernel and + user alike) gets one, whether it ever reads stdin or not, matching + fd 0 already always being fdConsole for every thread regardless of + use. fdFlagBlocking makes syscall.c's sysRead retry-syscall path + treat it exactly like a blocking pipe end (vfsBlockingChannel below); + fd 1/2 (stdout/stderr) never read, so they get no reader and no + blocking flag. If the reader table is exhausted, fd 0 is left + non-blocking with an invalid handle -- reads on it then simply fail + (-1) rather than crash, the same fail-open behavior a fully exhausted + pipe/fifo table already has elsewhere in this file. */ + fds[0].pipeHandle = keyboardOpenReader(); + fds[0].flags = fdFlagBlocking; } static FileDescriptor* fdAt(int fd) @@ -286,7 +379,7 @@ static bool ensureCapacity(VfsNode* node, uint64 needed) return true; } -int vfsOpen(const char* path, uint32 flags) +static uint64 blockDevTotalBytes(void) { VfsNode* node = lookupNode(path); if (!node) {