diff --git a/src/vfs.c b/src/vfs.c index 8e1c320..783e4bd 100644 --- a/src/vfs.c +++ b/src/vfs.c @@ -1535,6 +1535,62 @@ int64 vfsReadlink(const char* path, char* buffer, uint64 cap) int vfsLink(const char* existingPath, const char* newPath) { char resolvedExisting[fsPathMax]; + char resolvedNew[fsPathMax]; + vfsResolvePath(existingPath, resolvedExisting, sizeof(resolvedExisting)); + vfsResolvePath(newPath, resolvedNew, sizeof(resolvedNew)); + existingPath = resolvedExisting; + newPath = resolvedNew; + + const char* existingRel; + const char* newRel; + /* Cross-filesystem hardlinks (one path under /mnt, the other not) are + not supported -- same scope limit as a real link(2) across different + mounted filesystems (EXDEV), just expressed as a flat rejection since + this kernel has exactly one linkable filesystem. */ + if (!isSfsPath(existingPath, &existingRel) || !isSfsPath(newPath, &newRel)) + return -1; + return sfsLink(existingRel, newRel) >= 0 ? 0 : -1; +} + +int vfsChdir(const char* path) +{ + char resolvedPath[fsPathMax]; + vfsResolvePath(path, resolvedPath, sizeof(resolvedPath)); + + const char* rel; + if (isSfsPath(resolvedPath, &rel)) { + int32 inodeNum = sfsLookup(rel); + if (inodeNum < 0 || sfsTypeOf(inodeNum) != sfsTypeDir) + return -1; + /* No parent-walk primitive exists for sfs.h inodes (unlike vfsPath + below for the VfsNode tree), so the /mnt branch stores exactly + what vfsResolvePath produced -- the one honestly-scoped gap + vfsChdir's own doc comment (vfs.h) calls out. */ + return currentThreadSetCwd(resolvedPath) ? 0 : -1; + } + + VfsNode* node = lookupNode(resolvedPath); + if (!node || node->type != fsTypeDir) + return -1; + + /* Canonicalizes away any "."/".." components the caller typed by + walking the resolved node's REAL parent chain (vfsPath), rather than + storing vfsResolvePath's own plain string join verbatim -- shell.c's + old changeDir already did exactly this before this subsystem + existed, so this keeps that behavior for the VfsNode-tree case. */ + char canonical[fsPathMax]; + vfsPath(node, canonical, sizeof(canonical)); + return currentThreadSetCwd(canonical) ? 0 : -1; +} + +int64 vfsGetcwd(char* out, uint64 cap) +{ + const char* cwd = currentThreadCwd(); + uint64 len = strlen(cwd); + if (len >= cap) + return -1; + memcpy(out, cwd, len + 1); + return (int64)len; } VfsNode* vfsLookup(const char* path) @@ -1567,6 +1623,56 @@ uint64 vfsSize(VfsNode* node) return node->size; } +uint32 vfsMode(VfsNode* node) +{ + return node->mode; +} + +uint32 vfsUid(VfsNode* node) +{ + return node->uid; +} + +uint32 vfsGid(VfsNode* node) +{ + return node->gid; +} + +/* Real Unix owner/other permission algorithm, the one real choke-point + piece of logic resolveOpen's two branches (tmpfs, SFS) and syscall.c's + loadImageByPath/jobctl.c's jobctlSpawn all now share instead of each + re-deriving it -- previously all four sites just tested a single bit + unconditionally (`mode & fsModeOwnerWrite`), honest only because there + was no uid model yet to make owner-vs-other meaningful. Deliberately a + pure function of its four arguments, not of "whoever is currently + running" -- every real call site passes currentThreadUid() in explicitly + for `callerUid`, so this stays testable against arbitrary uid/mode/owner + combinations without ever touching a live thread's own credential (see + coreTest.c's own KTEST coverage). `wantOwnerBit` is always one of + fs.h's fsModeOwner* constants; the matching "other" bit is derived by + shifting it right 6 (fsModeOther*'s own doc comment explains why that is + exact, not approximate) rather than requiring a second argument. + + Deliberately NO uid-0-bypasses-everything superuser rule, unlike real + Unix's root: every boot-time process starts at uid 0 (sched.h's + Thread.uid doc comment), so a blanket bypass would have silently undone + the entire pre-existing "chmod denies write even to the file's own + creator" contract every current permission test (this file's own + resolveOpen tests, permtest.c's live demo) already relies on -- caught + by exactly those tests going red the first time this function existed + with a bypass in it. uid 0 is simply this kernel's default starting + identity, gated by the same owner/other bits as any other uid; its only + real asymmetry is currentThreadSetUid's own one-way privilege-drop rule + (sched.h), not a free pass here. */ +bool vfsPermitted(uint32 mode, uint32 ownerUid, uint32 ownerGid, uint32 callerUid, uint32 callerGid, uint32 wantOwnerBit) +{ + if (callerUid == ownerUid) + return (mode & wantOwnerBit) != 0; + if (callerGid == ownerGid) + return (mode & (wantOwnerBit >> 3)) != 0; + return (mode & (wantOwnerBit >> 6)) != 0; +} + const void* vfsFileData(VfsNode* node) { if (!node)