From 5b5b0203ddfd6ff8596a27582b804d89c187a620 Mon Sep 17 00:00:00 2001 From: DualFroz Date: Tue, 8 Sep 2026 21:03:37 +0200 Subject: [PATCH] feat: extend vfs with vfsRmdir and vfsOwnerOrRoot --- src/vfs.c | 173 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 173 insertions(+) diff --git a/src/vfs.c b/src/vfs.c index a2cf682..8e1c320 100644 --- a/src/vfs.c +++ b/src/vfs.c @@ -1364,6 +1364,179 @@ int vfsUnlink(const char* path) return 0; } +/* Real rmdir(2): the counterpart vfsUnlink above deliberately refuses. Only + ever removes a directory, and only an EMPTY one (ENOTDIR/ENOTEMPTY in + real errno terms, both collapsed to -1 here like every other vfs.c entry + point). root itself can never be removed -- root is created non-writable + (nodeAlloc("/", ...) in vfsInit), so the same `!node->writable` check + every other mutating entry point already relies on covers it for free, + no separate identity check needed. */ +int vfsRmdir(const char* path) +{ + char resolvedPath[fsPathMax]; + vfsResolvePath(path, resolvedPath, sizeof(resolvedPath)); + path = resolvedPath; + + const char* rel; + if (isSfsPath(path, &rel)) { + int32 inodeNum = sfsLookup(rel); + if (inodeNum < 0 || sfsTypeOf(inodeNum) != sfsTypeDir) + return -1; + /* sfsRemove's own `inode.type == sfsTypeDir && inode.size > 0` + check (sfs.c) still refuses a non-empty directory -- kept as + defense in depth rather than duplicated here. */ + return sfsRemove(rel) ? 0 : -1; + } + + VfsNode* node = lookupNode(path); + if (!node || !node->writable || node->type != fsTypeDir || node->firstChild) + return -1; + + /* Same parent-directory write check as vfsUnlink above -- removing a + directory ENTRY needs write permission on the PARENT, not the + directory being removed itself. */ + VfsNode* parent = node->parent; + if (!vfsPermitted(parent->mode, parent->uid, parent->gid, currentThreadUid(), currentThreadGid(), fsModeOwnerWrite)) + return -1; + + VfsNode** link = &parent->firstChild; + while (*link && *link != node) + link = &(*link)->nextSibling; + if (*link) + *link = node->nextSibling; + + kfree(node); + return 0; +} + +bool vfsOwnerOrRoot(uint32 ownerUid, uint32 callerUid) +{ + return callerUid == 0 || callerUid == ownerUid; +} + +int vfsChmod(const char* path, uint32 mode) +{ + char resolvedPath[fsPathMax]; + vfsResolvePath(path, resolvedPath, sizeof(resolvedPath)); + path = resolvedPath; + + const char* rel; + if (isSfsPath(path, &rel)) { + int32 inodeNum = sfsLookup(rel); + if (inodeNum < 0) + return -1; + if (!vfsOwnerOrRoot(sfsUidOf(inodeNum), currentThreadUid())) + return -1; + return sfsChmod(inodeNum, mode) ? 0 : -1; + } + + VfsNode* node = lookupNode(path); + if (!node || !node->writable) + return -1; + if (!vfsOwnerOrRoot(node->uid, currentThreadUid())) + return -1; + node->mode = mode; + return 0; +} + +int vfsChown(const char* path, uint32 newUid) +{ + if (currentThreadUid() != 0) + return -1; + + char resolvedPath[fsPathMax]; + vfsResolvePath(path, resolvedPath, sizeof(resolvedPath)); + path = resolvedPath; + + const char* rel; + if (isSfsPath(path, &rel)) { + int32 inodeNum = sfsLookup(rel); + if (inodeNum < 0) + return -1; + return sfsChown(inodeNum, newUid) ? 0 : -1; + } + + VfsNode* node = lookupNode(path); + if (!node || !node->writable) + return -1; + node->uid = newUid; + return 0; +} + +int vfsChgrp(const char* path, uint32 newGid) +{ + uint32 callerUid = currentThreadUid(); + bool isRoot = callerUid == 0; + /* Non-root: only the CALLER's own current gid may ever be assigned + (this kernel's one-gid-per-thread "membership"), and only by the + file's own owner -- both conditions checked up front so neither + branch below needs to repeat them. */ + if (!isRoot && newGid != currentThreadGid()) + return -1; + + char resolvedPath[fsPathMax]; + vfsResolvePath(path, resolvedPath, sizeof(resolvedPath)); + path = resolvedPath; + + const char* rel; + if (isSfsPath(path, &rel)) { + int32 inodeNum = sfsLookup(rel); + if (inodeNum < 0) + return -1; + if (!isRoot && sfsUidOf(inodeNum) != callerUid) + return -1; + return sfsChgrp(inodeNum, newGid) ? 0 : -1; + } + + VfsNode* node = lookupNode(path); + if (!node || !node->writable) + return -1; + if (!isRoot && node->uid != callerUid) + return -1; + node->gid = newGid; + return 0; +} + +int vfsSymlink(const char* path, const char* target) +{ + /* Only `path` (where the symlink itself is created) is resolved against + the calling thread's cwd -- `target` (the string stored INSIDE it) is + kept exactly as given, never resolved, matching real symlink(2) + (a relative target is only ever interpreted later, at traversal + time, relative to the symlink's own directory -- not the creating + process's cwd at creation time). */ + char resolvedPath[fsPathMax]; + vfsResolvePath(path, resolvedPath, sizeof(resolvedPath)); + + const char* rel; + if (!isSfsPath(resolvedPath, &rel)) + return -1; + return sfsCreateSymlink(rel, target) >= 0 ? 0 : -1; +} + +int64 vfsReadlink(const char* path, char* buffer, uint64 cap) +{ + char resolvedPath[fsPathMax]; + vfsResolvePath(path, resolvedPath, sizeof(resolvedPath)); + path = resolvedPath; + + const char* rel; + if (!isSfsPath(path, &rel)) + return -1; + + int32 inodeNum = sfsLookupNoFollow(rel); + if (inodeNum < 0 || sfsTypeOf(inodeNum) != sfsTypeSymlink) + return -1; + if (!sfsReadlink(inodeNum, buffer, cap)) + return -1; + return (int64)strlen(buffer); +} + +int vfsLink(const char* existingPath, const char* newPath) +{ + char resolvedExisting[fsPathMax]; +} + VfsNode* vfsLookup(const char* path) { return lookupNode(path);