feat: extend vfs
This commit is contained in:
1 parent
98e09f13f8
commit
33578598ca
1 file changed
+196
-1
+196
-1
@@ -9,14 +9,36 @@ struct VfsNode;
|
||||
typedef enum {
|
||||
fdFree = 0,
|
||||
fdConsole = 1,
|
||||
fdFile = 2
|
||||
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
|
||||
@@ -35,6 +57,179 @@ 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);
|
||||
|
||||
Reference in new issue
Block a user