#ifndef FROSTBYTE_VFS_H #define FROSTBYTE_VFS_H #include "types.h" #include "fs.h" struct VfsNode; typedef enum { fdFree = 0, fdConsole = 1, fdFile = 2, fdPipeRead = 3, fdPipeWrite = 4, fdBlockDev = 5, fdSfs = 6, /* pidfd.h handle wrapped as an ordinary fd (FileDescriptor.pipeHandle reused to store the pidfd.c table index, same field-reuse convention fdPipeRead/fdPipeWrite already use for a pipe.c handle) -- unlike anonymous pipes, a pidfd never needs the cross-process pipeCreateDetached/vfsPipeAttach dance: it is opened by, and lives entirely within, the SAME process that calls vfsPidfdOpen, exactly like real Linux's pidfd_open(2). */ fdPidfd = 7 } FdKind; /* FileDescriptor.flags bit for a pipe end installed by vfsPipeAttach: reads/ writes on it should really block (via syscall.c's sysRead/sysWrite retry) rather than report "would block" (-1) the way vfsPipe's anonymous, single-process pipe ends always have (see pipetest.c, which depends on that -1 -- unflagged, unchanged). File-kind descriptors already store their vfsOpen flags (oReadOnly etc, fs.h) in this same field, but never a pipe fd, so the two uses never collide. */ #define fdFlagBlocking 0x1 typedef struct FileDescriptor { struct VfsNode* node; uint64 offset; uint32 kind; uint32 flags; int32 pipeHandle; } FileDescriptor; #define maxOpenFiles 16 void vfsInit(void); void vfsMountUstar(uint64 start, uint64 end); void vfsMountTmpfs(const char* path); void vfsFdInit(FileDescriptor* fds); int vfsOpen(const char* path, uint32 flags); int vfsClose(int fd); int64 vfsRead(int fd, void* buffer, uint64 length); int64 vfsWrite(int fd, const void* buffer, uint64 length); int64 vfsLseek(int fd, int64 offset, int whence); int vfsStat(const char* path, FbStat* out); int64 vfsGetdents(int fd, void* buffer, uint64 length); int vfsMkdir(const char* path); int vfsUnlink(const char* path); int vfsRmdir(const char* path); /* Real chmod(2)/chown(2) privilege rule, factored out as a pure predicate exactly like vfsPermitted itself (see that function's own doc comment for why): true if `callerUid` may change `ownerUid`-owned metadata, i.e. is either the owner itself or uid 0 ("root", administrative bypass -- unlike vfsPermitted's read/write/exec check, which deliberately has none). Only chmod uses this; chown is stricter still (root-only, see vfsChown) since real Unix's default _POSIX_CHOWN_RESTRICTED denies even a file's own owner the right to give it away. Pure and explicit-argument like vfsPermitted so it stays testable without needing a live thread's own uid -- KTEST always runs on the idle thread (permanently uid 0), which could never otherwise exercise the "denied" side of this check. */ bool vfsOwnerOrRoot(uint32 ownerUid, uint32 callerUid); /* Real chmod(2): overwrites `path`'s whole owner-rwx mode (fs.h's fsModeOwner*) with `mode` -- no symbolic +x-style merge, matching real chmod's own numeric-mode form. Refuses unless the caller owns the file or is uid 0 (vfsOwnerOrRoot above) -- a real, previously-missing check: every uid could chmod any file's bits until this was added. Dispatches on isSfsPath like every other entry point above (sfsChmod for "/mnt", the tmpfs VfsNode's own `mode` field otherwise). Returns -1 for a missing path, or (tmpfs only) a node under a non-writable mount -- chmod is a metadata mutation, gated by the exact same `writable` flag every other tmpfs mutation (vfsUnlink, vfsWrite's write path) already respects, so a ustar-loaded /bin/ *.elf file can never have its mode changed at runtime, matching its read-only backing store. 0 on success. */ int vfsChmod(const char* path, uint32 mode); /* Real chown(2): overwrites `path`'s owner uid outright. Root-only (currentThreadUid() == 0), full stop -- unlike vfsChmod, ownership is never delegated to the file's own owner (real Unix's _POSIX_CHOWN_RESTRICTED default: an owner who could give a file away could use that to defeat disk-quota accounting, so only root may). Same isSfsPath dispatch and missing-path/non-writable-mount refusal as vfsChmod. 0 on success. */ int vfsChown(const char* path, uint32 newUid); /* Real chgrp(2): overwrites `path`'s group gid. Unlike vfsChown (root-only, full stop), chgrp is delegated to the file's own owner too -- but only to set the group to the CALLER's own current gid (currentThreadGid()), never an arbitrary one: this kernel tracks exactly one gid per thread (no supplementary-groups list), so "a group the caller belongs to" collapses to "the caller's own current gid", matching real chgrp(2)'s own membership requirement for a non-privileged caller. Root (uid 0) may set any gid, same administrative bypass vfsOwnerOrRoot already grants chmod. Same isSfsPath dispatch and missing-path/non-writable-mount refusal as vfsChown. 0 on success. */ int vfsChgrp(const char* path, uint32 newGid); /* Symlinks and hardlinks are only supported on the persistent "/mnt" filesystem (sfs.h already has the on-disk machinery for both); any other path (the ustar/tmpfs VfsNode tree, "/dev/disk0") rejects these with -1, the same way a real filesystem that lacks the feature would refuse the syscall rather than fake it. */ int vfsSymlink(const char* path, const char* target); int64 vfsReadlink(const char* path, char* buffer, uint64 cap); int vfsLink(const char* existingPath, const char* newPath); /* Creates an anonymous pipe and installs its two ends into the current thread's fd table. On success readFd/writeFd receive the descriptors and 0 is returned; on failure (fd table or pipe table exhausted) -1 is returned and neither output is written. */ int vfsPipe(int* readFd, int* writeFd); /* Creates a pipeCreateDetached handle (pipe.h) with neither end attached to anyone's fd table and returns its raw number, or -1 on exhaustion. Meant to be handed to two independently spawned processes (typically as a decimal argv string, see user/fifomaster.c) so they can rendezvous over a real blocking pipe without needing fork() to share an fd -- see vfsPipeAttach. */ int vfsPipeCreateHandle(void); /* Installs `handle` (from vfsPipeCreateHandle, or in principle any live pipe handle) into a fresh slot in the CALLING thread's own fd table, as a read end (wantWrite false) or write end (wantWrite true), flagged fdFlagBlocking so syscall.c's sysRead/sysWrite give it real blocking semantics. Returns the new fd, or -1 if the handle is not a live pipe or the fd table is full. */ int vfsPipeAttach(int handle, bool wantWrite); /* Same as vfsPipeAttach, but installs into fd slot `fd` of an EXPLICIT fd table instead of allocating a free slot in the CALLING thread's own table -- jobctl.c's own way to bake a real pipe end into a brand new child's stdin/stdout (fd 0/1) before that child has run a single instruction, so there is never a window where it could be scheduled with its default console fds instead (see jobctlSpawnPiped, jobctl.h). `fd` is trusted to already name a valid slot (0/1/2) in `targetFds` -- unlike vfsPipeAttach, this never allocates a fresh one, so the caller decides exactly which fd is overwritten. Silently does nothing if `handle` is not a live pipe. */ void vfsPipeAttachInto(FileDescriptor* targetFds, int fd, int handle, bool wantWrite); /* Opens a pidfd (pidfd.h) on `targetId` and installs it as a brand new fdPidfd descriptor in the CALLING thread's own fd table -- the fd-table-wrapping half of pidfd.h's raw pidfdOpen, exactly the same split vfsPipe/vfsPipeAttach already have over pipe.c's raw pipeCreate/pipeCreateDetached. Returns the new fd, or -1 if `targetId` is not a valid, currently-live thread (pidfdOpen's own contract) or the calling thread's own fd table is full. */ int vfsPidfdOpen(uint32 targetId); /* Resolves fd `fd` (which must be an fdPidfd this thread's own vfsPidfdOpen returned) to the live target thread id it still safely refers to, or threadInvalidId if `fd` is not an open pidfd at all, or the original target has since been reaped/recycled (pidfdTarget's own contract, pidfd.h) -- the single check syscall.c's sysPidfdSend/sysPidfdWait both need before ever touching a target by number. */ uint32 vfsPidfdTarget(int fd); /* Same as vfsOpen, but installs into fd slot `fd` of an EXPLICIT fd table instead of allocating a free slot in the CALLING thread's own table -- the file-redirection counterpart of vfsPipeAttachInto above, used by jobctl.c's own jobctlApplyRedirects to bake a real file open (not a pipe) into a freshly spawned child's stdin/stdout for shell.c's new `cmd < file` / `cmd > file` / `cmd >> file` syntax, before that child has run a single instruction. `fd` is trusted to already name a valid slot (0/1/2) in `targetFds`, exactly like vfsPipeAttachInto. Returns 0 on success, -1 on the same failures vfsOpen itself can have (missing path without oCreate, unwritable parent directory, a fifo opened with a bad mode, exhausted pipe/inode resources) -- leaves `targetFds[fd]` completely untouched on failure, the same "never marks a fd used when it fails" contract vfsOpen already has for its own newly allocated fd. */ int vfsOpenInto(FileDescriptor* targetFds, int fd, const char* path, uint32 flags); /* nullptr unless `fd` is a fdFlagBlocking pipe/fifo end or a console (fd 0) read whose next read/write would have to report "try again" for a resolvable reason (a pipe buffer empty but a writer could still show up, full but a reader could still drain it, or a console queue simply not having a keystroke queued yet) -- in which case it is the channel (sched.h) that end's condition changing will wake. syscall.c's sysRead/sysWrite call this after a -1 from doRead/doWrite to decide whether to park-and-retry the syscall (schedBlockCurrent + rewind rip, same trick sysWait uses for threadJoin) or simply hand -1 back to userspace as-is (a non-blocking pipe end, a console write, or a permanently broken/at-EOF pipe that retrying could never fix). */ const void* vfsBlockingChannel(int fd, bool forWrite); /* poll.h's sysPoll (syscall.c): fills *outRevents with the subset of `events` (poll.h's pollIn/pollOut) `fd` could satisfy right now without blocking -- pollNval instead if `fd` is not open at all, pollErr for a pipe write end whose only reader has gone away (a real write there would fail immediately, not block, so it counts as "ready" the same way a readable EOF pipe does). A regular file, "/dev/disk0", or "/mnt" fd always reports every requested bit ready, matching real poll(2): none of this kernel's local-storage reads/writes ever return "would block". Returns true iff *outRevents ended up nonzero, i.e. this fd alone would already satisfy a poll call. */ bool vfsPollReady(int fd, int16 events, int16* outRevents); /* Named pipe: creates a real filesystem entry of type fsTypeFifo at `path` (must live under a writable directory, e.g. the "/tmp" tmpfs mount -- same requirement makeChildFile already has for oCreate) backed by a fresh pipeCreateDetached handle. Unlike vfsPipeCreateHandle/vfsPipeAttach, which need SOME cooperating parent to pass the raw handle number to both sides (typically as an argv string, see user/fifomaster.c), a fifo node lets any two processes that simply agree on a path -- no common ancestor, no handle to pass -- rendezvous by both calling vfsOpen on it: whichever opens with oWriteOnly gets a blocking write end, whichever opens with oReadOnly gets a blocking read end, exactly like vfsPipeAttach's fds. oReadWrite (or any other flag combination) is rejected -- this kernel's pipe end is inherently one-directional, so a dual-direction fifo fd is out of scope, same as vfsPipeAttach's wantWrite being a single bool. Returns 0 on success, -1 if the path already exists, its parent is not a writable directory, or the pipe table is exhausted. Not supported under "/dev/disk0". Under "/mnt" this creates a REAL, persistent on-disk entry (sfs.h's sfsTypeFifo) that survives sfsUnmount()+sfsMount() -- only the special file itself persists, never any buffered bytes (exactly like a real mkfifo(2) special file survives a reboot while its pipe buffer does not): the live pipe.h handle two openers actually exchange bytes through is created lazily, in memory, the first time anyone opens the typedef struct VfsNode VfsNode; VfsNode* vfsLookup(const char* path); VfsNode* vfsChild(VfsNode* dir); VfsNode* vfsNext(VfsNode* node); const char* vfsName(VfsNode* node); bool vfsIsDir(VfsNode* node); uint64 vfsSize(VfsNode* node); const void* vfsFileData(VfsNode* node); void vfsPath(VfsNode* node, char* out, uint64 cap); #endif