From 4640fb5eb59d56538420d4aa190bc39840fb9871 Mon Sep 17 00:00:00 2001 From: DualFroz Date: Tue, 8 Sep 2026 20:47:02 +0200 Subject: [PATCH] feat: build out vfs --- src/vfs.c | 158 +++++++++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 140 insertions(+), 18 deletions(-) diff --git a/src/vfs.c b/src/vfs.c index c5716a3..a2cf682 100644 --- a/src/vfs.c +++ b/src/vfs.c @@ -1122,15 +1122,17 @@ int64 vfsRead(int fd, void* buffer, uint64 length) return sfsFdRead(d, buffer, length); if (d->kind == fdConsole) { - char* text = (char*)buffer; - uint64 count = 0; - while (count < length) { - char c = keyboardReadChar(); - text[count++] = c; - if (c == '\n') - break; - } - return (int64)count; + /* Non-blocking, like every other real fd kind above: -1 (queue + currently empty) is the "would block" signal syscall.c's sysRead + retries via vfsBlockingChannel's channel + the restart-syscall + trick, instead of this function looping/halting in place the way + the old single-global-buffer keyboardReadChar() used to. Returns + whatever is queued right now (possibly fewer than length bytes, + same partial-read contract pipeRead already has) rather than + insisting on a full line -- nothing in this kernel relied on the + old line-buffering behavior (grep shows no caller ever read fd 0 + before this wave). */ + return keyboardReaderRead(d->pipeHandle, buffer, length); } VfsNode* node = d->node; @@ -1149,7 +1151,7 @@ int64 vfsRead(int fd, void* buffer, uint64 length) int64 vfsLseek(int fd, int64 offset, int whence) { FileDescriptor* d = fdAt(fd); - if (!d || d->kind != fdFile) + if (!d || (d->kind != fdFile && d->kind != fdBlockDev && d->kind != fdSfs)) return -1; int64 base; @@ -1157,9 +1159,14 @@ int64 vfsLseek(int fd, int64 offset, int whence) base = 0; else if (whence == seekCur) base = (int64)d->offset; - else if (whence == seekEnd) - base = (int64)d->node->size; - else + else if (whence == seekEnd) { + if (d->kind == fdBlockDev) + base = (int64)blockDevTotalBytes(); + else if (d->kind == fdSfs) + base = (int64)sfsSizeOf(d->pipeHandle); + else + base = (int64)d->node->size; + } else return -1; int64 target = base + offset; @@ -1171,20 +1178,75 @@ int64 vfsLseek(int fd, int64 offset, int whence) int vfsStat(const char* path, FbStat* out) { + 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; + out->size = sfsSizeOf(inodeNum); + out->type = sfsTypeToFsType(sfsTypeOf(inodeNum)); + out->mode = sfsModeOf(inodeNum); + out->uid = sfsUidOf(inodeNum); + out->gid = sfsGidOf(inodeNum); + out->mtime = sfsMtimeOf(inodeNum); + out->ctime = sfsCtimeOf(inodeNum); + return 0; + } + + if (strcmp(path, blockDevPath) == 0) { + out->size = blockDevTotalBytes(); + out->type = fsTypeFile; + out->mode = 0666; + out->uid = 0; + out->gid = 0; + out->mtime = 0; + out->ctime = 0; + return 0; + } + VfsNode* node = lookupNode(path); if (!node) return -1; out->size = node->size; out->type = node->type; - out->mode = node->writable ? 0666 : 0444; + out->mode = node->mode; + out->uid = node->uid; + out->gid = node->gid; + out->mtime = 0; + out->ctime = 0; return 0; } int64 vfsGetdents(int fd, void* buffer, uint64 length) { FileDescriptor* d = fdAt(fd); - if (!d || d->kind != fdFile) + if (!d || (d->kind != fdFile && d->kind != fdSfs)) return -1; + + if (d->kind == fdSfs) { + if (sfsTypeOf(d->pipeHandle) != sfsTypeDir) + return -1; + FbDirent* out = (FbDirent*)buffer; + uint64 written = 0; + char name[fsNameMax]; + uint32 type; + while (written + sizeof(FbDirent) <= length && + sfsReaddirAt(d->pipeHandle, (uint32)d->offset, name, sizeof(name), &type)) { + uint64 nameLen = strlen(name); + out->type = sfsTypeToFsType(type); + out->nameLength = (unsigned int)nameLen; + memcpy(out->name, name, nameLen + 1); + out++; + written += sizeof(FbDirent); + d->offset++; + } + return (int64)written; + } + VfsNode* node = d->node; if (node->type != fsTypeDir) return -1; @@ -1214,28 +1276,88 @@ int64 vfsGetdents(int fd, void* buffer, uint64 length) int vfsMkdir(const char* path) { + char resolvedPath[fsPathMax]; + vfsResolvePath(path, resolvedPath, sizeof(resolvedPath)); + path = resolvedPath; + + const char* rel; + if (isSfsPath(path, &rel)) + return sfsCreateDir(rel) >= 0 ? 0 : -1; + if (lookupNode(path)) return -1; char leaf[fsNameMax]; VfsNode* parent = splitParent(path, leaf, sizeof(leaf)); - if (!parent || parent->type != fsTypeDir || !parent->writable) + if (!parent || parent->type != fsTypeDir || !parent->writable + || !vfsPermitted(parent->mode, parent->uid, parent->gid, currentThreadUid(), currentThreadGid(), fsModeOwnerWrite)) return -1; - return makeChildDir(parent, leaf, strlen(leaf), true) ? 0 : -1; + return makeChildDir(parent, leaf, strlen(leaf), true, fsModeDefaultDir) ? 0 : -1; } int vfsUnlink(const char* path) { + char resolvedPath[fsPathMax]; + vfsResolvePath(path, resolvedPath, sizeof(resolvedPath)); + path = resolvedPath; + + const char* rel; + if (isSfsPath(path, &rel)) { + int32 inodeNum = sfsLookup(rel); + /* Real unlink(2) semantics: EISDIR on a directory, even an empty + one -- rmdir(2) (vfsRmdir below) is the only sanctioned way to + remove one. sfsRemove itself has no opinion here (it is the raw + on-disk primitive, also used by vfsRmdir below), so the type + check has to happen at this dispatch layer, matching the tmpfs + branch below which has always refused a directory this way. */ + if (inodeNum < 0 || sfsTypeOf(inodeNum) == sfsTypeDir) + return -1; + /* Only the LAST directory entry pointing at a persistent fifo's + inode should ever tear down its live pipe.h binding (see + sfsFifoForget above) -- a hardlinked fifo (sfsLink does not + special-case sfsTypeFifo, unlike sfsTypeDir) losing one of + several names must leave any live session alone, exactly like + sfsRemove itself only actually frees the inode once linkCount + drops to 0. Checked and captured BEFORE sfsRemove, since a + successful removal may itself free the inode (readInode on it + afterwards would read whatever unrelated data now occupies that + slot, if anything). */ + bool lastLinkOfFifo = sfsTypeOf(inodeNum) == sfsTypeFifo && + sfsLinkCountOf(inodeNum) <= 1; + if (!sfsRemove(rel)) + return -1; + if (lastLinkOfFifo) + sfsFifoForget(inodeNum); + return 0; + } + VfsNode* node = lookupNode(path); - if (!node || node->type != fsTypeFile || !node->writable) + if (!node || !node->writable || (node->type != fsTypeFile && node->type != fsTypeFifo)) return -1; + /* Real unlink(2) semantics: removing a directory ENTRY needs write + permission on the PARENT directory, not on the entry itself -- a + real Unix caller can delete a read-only file it can write into the + containing directory of, matching real Unix's own "the file's own + mode is irrelevant to unlink" rule. */ 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; + /* Any fd already opened on this fifo (fdPipeRead/fdPipeWrite) keeps + working off its own copy of node->pipeHandle -- it never dereferences + this node again, so freeing it here does not disturb a live reader or + writer, matching real unlink-while-open semantics. Only reclaim the + underlying pipe slot if nobody ever actually opened it; otherwise the + still-open ends must keep it alive until their own vfsClose. */ + if (node->type == fsTypeFifo) + pipeDestroyIfUnattached(node->pipeHandle); + if (node->rwData) kfree(node->rwData); kfree(node);