feat: extend vfs with vfsChdir and vfsGetcwd

This commit is contained in:
dualfroz committed 2026-09-08 21:15:50 +02:00
1 parent 5b5b0203dd
commit 4dee5e3009
1 file changed
+106
+106
View File
@@ -1535,6 +1535,62 @@ int64 vfsReadlink(const char* path, char* buffer, uint64 cap)
int vfsLink(const char* existingPath, const char* newPath) int vfsLink(const char* existingPath, const char* newPath)
{ {
char resolvedExisting[fsPathMax]; 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) VfsNode* vfsLookup(const char* path)
@@ -1567,6 +1623,56 @@ uint64 vfsSize(VfsNode* node)
return node->size; 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) const void* vfsFileData(VfsNode* node)
{ {
if (!node) if (!node)