feat: extend vfs with makeChildFifo

This commit is contained in:
dualfroz committed 2026-09-08 19:26:24 +02:00
1 parent 8b629c1858
commit db5a6274c9
1 file changed
+105 -12
+105 -12
View File
@@ -5,17 +5,51 @@
#include "console.h" #include "console.h"
#include "keyboard.h" #include "keyboard.h"
#include "sched.h" #include "sched.h"
#include "pipe.h"
#include "pidfd.h"
#include "ata.h"
#include "sfs.h"
#include "poll.h"
#define tmpfsBlock 512 #define tmpfsBlock 512
#define blockDevPath "/dev/disk0"
#define sfsMountPrefix "/mnt"
#define sfsMountPrefixLen 4
struct VfsNode { struct VfsNode {
char name[fsNameMax]; char name[fsNameMax];
uint32 type; uint32 type;
bool writable; 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; const uint8* roData;
uint8* rwData; uint8* rwData;
uint64 size; uint64 size;
uint64 capacity; 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* parent;
struct VfsNode* firstChild; struct VfsNode* firstChild;
struct VfsNode* nextSibling; struct VfsNode* nextSibling;
@@ -23,7 +57,7 @@ struct VfsNode {
static VfsNode* root; 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)); VfsNode* node = (VfsNode*)kmalloc(sizeof(VfsNode));
if (!node) if (!node)
@@ -35,6 +69,10 @@ static VfsNode* nodeAlloc(const char* name, uint64 nameLen, uint32 type, bool wr
node->name[nameLen] = 0; node->name[nameLen] = 0;
node->type = type; node->type = type;
node->writable = writable; node->writable = writable;
node->mode = mode;
node->uid = currentThreadUid();
node->gid = currentThreadGid();
node->pipeHandle = -1;
return node; return node;
} }
@@ -53,18 +91,18 @@ static VfsNode* findChild(VfsNode* parent, const char* name, uint64 nameLen)
return nullptr; 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) if (node)
addChild(parent, node); addChild(parent, node);
return node; return node;
} }
static VfsNode* makeChildFile(VfsNode* parent, const char* name, uint64 nameLen, 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) if (!node)
return nullptr; return nullptr;
if (writable) { if (writable) {
@@ -79,6 +117,29 @@ static VfsNode* makeChildFile(VfsNode* parent, const char* name, uint64 nameLen,
return node; 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) static VfsNode* lookupNode(const char* path)
{ {
if (!path) if (!path)
@@ -103,6 +164,8 @@ static VfsNode* lookupNode(const char* path)
} else { } else {
if (cur->type != fsTypeDir) if (cur->type != fsTypeDir)
return nullptr; return nullptr;
if (!vfsPermitted(cur->mode, cur->uid, cur->gid, currentThreadUid(), currentThreadGid(), fsModeOwnerExec))
return nullptr;
VfsNode* child = findChild(cur, start, len); VfsNode* child = findChild(cur, start, len);
if (!child) if (!child)
return nullptr; return nullptr;
@@ -191,12 +254,27 @@ static void ustarAdd(const char* rawPath, bool isDir, const uint8* data, uint64
if (last) { if (last) {
if (!child) { if (!child) {
if (isDir) if (isDir)
child = makeChildDir(cur, path + start, len, false); child = makeChildDir(cur, path + start, len, false, fsModeDefaultDir);
else else {
child = makeChildFile(cur, path + start, len, data, size, false); /* 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) { } else if (!child) {
child = makeChildDir(cur, path + start, len, false); child = makeChildDir(cur, path + start, len, false, fsModeDefaultDir);
} }
if (!child) if (!child)
return; return;
@@ -206,7 +284,7 @@ static void ustarAdd(const char* rawPath, bool isDir, const uint8* data, uint64
void vfsInit(void) void vfsInit(void)
{ {
root = nodeAlloc("/", 1, fsTypeDir, false); root = nodeAlloc("/", 1, fsTypeDir, false, fsModeDefaultDir);
} }
void vfsMountUstar(uint64 start, uint64 end) void vfsMountUstar(uint64 start, uint64 end)
@@ -233,7 +311,7 @@ void vfsMountTmpfs(const char* path)
return; return;
if (findChild(parent, leaf, strlen(leaf))) if (findChild(parent, leaf, strlen(leaf)))
return; return;
makeChildDir(parent, leaf, strlen(leaf), true); makeChildDir(parent, leaf, strlen(leaf), true, fsModeDefaultDir);
kprintf("[vfs] mounted writable tmpfs at %s\n", path); kprintf("[vfs] mounted writable tmpfs at %s\n", path);
} }
@@ -244,10 +322,25 @@ void vfsFdInit(FileDescriptor* fds)
fds[i].offset = 0; fds[i].offset = 0;
fds[i].kind = fdFree; fds[i].kind = fdFree;
fds[i].flags = 0; fds[i].flags = 0;
fds[i].pipeHandle = -1;
} }
fds[0].kind = fdConsole; fds[0].kind = fdConsole;
fds[1].kind = fdConsole; fds[1].kind = fdConsole;
fds[2].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) static FileDescriptor* fdAt(int fd)
@@ -286,7 +379,7 @@ static bool ensureCapacity(VfsNode* node, uint64 needed)
return true; return true;
} }
int vfsOpen(const char* path, uint32 flags) static uint64 blockDevTotalBytes(void)
{ {
VfsNode* node = lookupNode(path); VfsNode* node = lookupNode(path);
if (!node) { if (!node) {