/* * Vfs.cpp * Virtual File System with numerical logical drive identifiers * Copyright (c) 2025 Daniel Hammer */ #include "Vfs.hpp" #include #include namespace Fs::Vfs { static FsDriver* driveTable[MaxDrives]; static bool driveActive[MaxDrives]; static uint32_t driveGeneration[MaxDrives]; static bool initialized = false; // Protects driver dispatch from concurrent CPU access. Hot-unplug only // flips driveActive so an interrupted dispatch never sees a null driver. static kcp::Mutex vfsLock; // Upper bound on digits in a drive number. Without it the accumulator in // ParsePath overflows on a long digit run and wraps into a valid drive -- // "4294967296:/x" would otherwise resolve to drive 0. static constexpr int MaxDriveDigits = 4; // Parse "N:/path" into drive number and local path. // Returns true on success, sets outDrive and outPath. static bool ParsePath(const char* path, int& outDrive, const char*& outPath) { if (path == nullptr) return false; // Parse decimal drive number before ':' int drive = 0; int i = 0; int digits = 0; while (path[i] >= '0' && path[i] <= '9') { if (++digits > MaxDriveDigits) return false; drive = drive * 10 + (path[i] - '0'); i++; } if (digits == 0) return false; if (path[i] != ':') return false; // Everything after "N:" is the local path outDrive = drive; outPath = &path[i + 1]; return true; } // Resolve a drive number to its driver, or nullptr if the slot is out of // range, unregistered, or empty. Caller must hold vfsLock: the returned // pointer is only valid for as long as the lock is held. static FsDriver* DriverForLocked(int driveNumber) { if (driveNumber < 0 || driveNumber >= MaxDrives) return nullptr; if (!driveActive[driveNumber]) return nullptr; return driveTable[driveNumber]; } // As DriverForLocked, but for an already-open handle: also rejects handles // whose generation stamp is stale (the drive was unmounted and reused). static FsDriver* DriverForFileLocked(const BackendFile& file) { if (file.localHandle < 0) return nullptr; FsDriver* driver = DriverForLocked(file.driveNumber); if (driver == nullptr) return nullptr; if (file.generation != driveGeneration[file.driveNumber]) return nullptr; return driver; } static void BumpDriveGeneration(int driveNumber) { driveGeneration[driveNumber]++; if (driveGeneration[driveNumber] == 0) { driveGeneration[driveNumber] = 1; } } void Initialize() { for (int i = 0; i < MaxDrives; i++) { driveTable[i] = nullptr; driveActive[i] = false; driveGeneration[i] = 1; } initialized = true; Kt::KernelLogStream(Kt::OK, "VFS") << "Initialized (" << MaxDrives << " drives)"; } bool IsInitialized() { return initialized; } int RegisterDrive(int driveNumber, FsDriver* driver) { if (driveNumber < 0 || driveNumber >= MaxDrives) return -1; if (driver == nullptr) return -1; if (!initialized) return -1; vfsLock.Acquire(); if (driveActive[driveNumber]) { vfsLock.Release(); return -1; } driveTable[driveNumber] = driver; BumpDriveGeneration(driveNumber); driveActive[driveNumber] = true; vfsLock.Release(); Kt::KernelLogStream(Kt::OK, "VFS") << "Registered drive " << driveNumber; return 0; } int UnregisterDrive(int driveNumber) { if (!initialized) return -1; if (driveNumber < 0 || driveNumber >= MaxDrives) return -1; vfsLock.Acquire(); if (!driveActive[driveNumber]) { vfsLock.Release(); return -1; } driveActive[driveNumber] = false; FsDriver* driver = driveTable[driveNumber]; driveTable[driveNumber] = nullptr; BumpDriveGeneration(driveNumber); // Safe to tear down here: every dispatch path runs under vfsLock, so // holding it means no call into this driver can still be in flight. // Unmount frees the FsDriver itself, so nothing may touch it after. if (driver != nullptr && driver->Unmount != nullptr) { driver->Unmount(driver->ctx); } vfsLock.Release(); Kt::KernelLogStream(Kt::OK, "VFS") << "Unregistered drive " << driveNumber; return 0; } bool IsDriveRegistered(int driveNumber) { if (driveNumber < 0 || driveNumber >= MaxDrives) return false; vfsLock.Acquire(); bool registered = driveActive[driveNumber]; vfsLock.Release(); return registered; } int FindLowestAvailableDrive(int firstDrive) { if (!initialized) return -1; if (firstDrive < 0) firstDrive = 0; vfsLock.Acquire(); for (int i = firstDrive; i < MaxDrives; i++) { if (!driveActive[i]) { vfsLock.Release(); return i; } } vfsLock.Release(); return -1; } int OpenBackendFile(const char* path, BackendFile& outFile) { outFile.driveNumber = -1; outFile.localHandle = -1; outFile.generation = 0; int drive; const char* localPath; if (!ParsePath(path, drive, localPath)) return -1; vfsLock.Acquire(); FsDriver* driver = DriverForLocked(drive); if (driver == nullptr || driver->Open == nullptr) { vfsLock.Release(); return -1; } uint32_t generation = driveGeneration[drive]; int localHandle = driver->Open(driver->ctx, localPath); vfsLock.Release(); if (localHandle < 0) return -1; outFile.driveNumber = drive; outFile.localHandle = localHandle; outFile.generation = generation; return 0; } int ReadBackendFile(const BackendFile& file, uint8_t* buffer, uint64_t offset, uint64_t size) { vfsLock.Acquire(); FsDriver* driver = DriverForFileLocked(file); if (driver == nullptr || driver->Read == nullptr) { vfsLock.Release(); return -1; } int result = driver->Read(driver->ctx, file.localHandle, buffer, offset, size); vfsLock.Release(); return result; } uint64_t GetBackendFileSize(const BackendFile& file) { vfsLock.Acquire(); FsDriver* driver = DriverForFileLocked(file); if (driver == nullptr || driver->GetSize == nullptr) { vfsLock.Release(); return 0; } uint64_t result = driver->GetSize(driver->ctx, file.localHandle); vfsLock.Release(); return result; } bool BackendFileCanWrite(const BackendFile& file) { vfsLock.Acquire(); FsDriver* driver = DriverForFileLocked(file); bool canWrite = driver != nullptr && driver->Write != nullptr; vfsLock.Release(); return canWrite; } void CloseBackendFile(BackendFile& file) { vfsLock.Acquire(); FsDriver* driver = DriverForFileLocked(file); if (driver != nullptr && driver->Close != nullptr) { driver->Close(driver->ctx, file.localHandle); } vfsLock.Release(); file.driveNumber = -1; file.localHandle = -1; file.generation = 0; } int WriteBackendFile(const BackendFile& file, const uint8_t* buffer, uint64_t offset, uint64_t size) { vfsLock.Acquire(); FsDriver* driver = DriverForFileLocked(file); if (driver == nullptr || driver->Write == nullptr) { vfsLock.Release(); return -1; } int result = driver->Write(driver->ctx, file.localHandle, buffer, offset, size); vfsLock.Release(); return result; } int CreateBackendFile(const char* path, BackendFile& outFile) { outFile.driveNumber = -1; outFile.localHandle = -1; outFile.generation = 0; int drive; const char* localPath; if (!ParsePath(path, drive, localPath)) return -1; vfsLock.Acquire(); FsDriver* driver = DriverForLocked(drive); if (driver == nullptr || driver->Create == nullptr) { vfsLock.Release(); return -1; } uint32_t generation = driveGeneration[drive]; int localHandle = driver->Create(driver->ctx, localPath); vfsLock.Release(); if (localHandle < 0) return -1; outFile.driveNumber = drive; outFile.localHandle = localHandle; outFile.generation = generation; return 0; } int VfsDelete(const char* path) { int drive; const char* localPath; if (!ParsePath(path, drive, localPath)) return -1; vfsLock.Acquire(); FsDriver* driver = DriverForLocked(drive); int result = (driver != nullptr && driver->Delete != nullptr) ? driver->Delete(driver->ctx, localPath) : -1; vfsLock.Release(); return result; } int VfsStat(const char* path, StatInfo& out) { int drive; const char* localPath; out = StatInfo{}; if (!ParsePath(path, drive, localPath)) return -1; vfsLock.Acquire(); FsDriver* driver = DriverForLocked(drive); int result = (driver != nullptr && driver->Stat != nullptr) ? driver->Stat(driver->ctx, localPath, &out) : -1; vfsLock.Release(); return result; } int VfsSetTimes(const char* path, int64_t atime, int64_t mtime) { int drive; const char* localPath; if (!ParsePath(path, drive, localPath)) return -1; vfsLock.Acquire(); FsDriver* driver = DriverForLocked(drive); int result = (driver != nullptr && driver->SetTimes != nullptr) ? driver->SetTimes(driver->ctx, localPath, atime, mtime) : -2; vfsLock.Release(); return result; } int VfsMkdir(const char* path) { int drive; const char* localPath; if (!ParsePath(path, drive, localPath)) return -1; vfsLock.Acquire(); FsDriver* driver = DriverForLocked(drive); int result = (driver != nullptr && driver->Mkdir != nullptr) ? driver->Mkdir(driver->ctx, localPath) : -1; vfsLock.Release(); return result; } int VfsDriveList(int* outDrives, int maxEntries) { if (outDrives == nullptr || maxEntries <= 0) return 0; vfsLock.Acquire(); int count = 0; for (int i = 0; i < MaxDrives && count < maxEntries; i++) { if (driveActive[i]) { outDrives[count++] = i; } } vfsLock.Release(); return count; } static int CopyDriveString(int driveNumber, const char* (*FsDriver::*hook)(void*), char* out, int maxLen) { if (out == nullptr || maxLen <= 0) return -1; out[0] = '\0'; vfsLock.Acquire(); FsDriver* driver = DriverForLocked(driveNumber); if (driver == nullptr) { vfsLock.Release(); return -1; } if (driver->*hook == nullptr) { vfsLock.Release(); return 0; } const char* value = (driver->*hook)(driver->ctx); if (value == nullptr || value[0] == '\0') { vfsLock.Release(); return 0; } int copied = 0; while (value[copied] != '\0' && copied < maxLen - 1) { out[copied] = value[copied]; copied++; } out[copied] = '\0'; vfsLock.Release(); return copied; } int VfsDriveLabel(int driveNumber, char* outLabel, int maxLen) { return CopyDriveString(driveNumber, &FsDriver::GetLabel, outLabel, maxLen); } int VfsDriveFsType(int driveNumber, char* outType, int maxLen) { return CopyDriveString(driveNumber, &FsDriver::GetFsType, outType, maxLen); } int VfsGrow(int driveNumber, uint64_t partSectors, bool apply, GrowStatus* out) { vfsLock.Acquire(); FsDriver* driver = DriverForLocked(driveNumber); int result = (driver != nullptr && driver->Grow != nullptr) ? driver->Grow(driver->ctx, partSectors, apply, out) : -2; vfsLock.Release(); return result; } int VfsRename(const char* oldPath, const char* newPath) { int oldDrive, newDrive; const char* oldLocal; const char* newLocal; if (!ParsePath(oldPath, oldDrive, oldLocal)) return -1; if (!ParsePath(newPath, newDrive, newLocal)) return -1; // Cross-drive rename not supported if (oldDrive != newDrive) return -1; vfsLock.Acquire(); FsDriver* driver = DriverForLocked(oldDrive); int result = (driver != nullptr && driver->Rename != nullptr) ? driver->Rename(driver->ctx, oldLocal, newLocal) : -1; vfsLock.Release(); return result; } int VfsReadDir(const char* path, const char** outNames, int maxEntries) { return VfsReadDirAt(path, outNames, maxEntries, 0); } int VfsReadDirAt(const char* path, const char** outNames, int maxEntries, int startIndex) { int drive; const char* localPath; if (startIndex < 0) return -1; if (!ParsePath(path, drive, localPath)) return -1; vfsLock.Acquire(); FsDriver* driver = DriverForLocked(drive); int result; if (driver == nullptr) { result = -1; } else if (driver->ReadDirAt) { result = driver->ReadDirAt(driver->ctx, localPath, outNames, maxEntries, startIndex); } else if (driver->ReadDir) { // Driver without pagination support: only the first page is reachable. result = (startIndex == 0) ? driver->ReadDir(driver->ctx, localPath, outNames, maxEntries) : 0; } else { result = -1; } vfsLock.Release(); return result; } }