/* * 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; // 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; bool hasDigit = false; while (path[i] >= '0' && path[i] <= '9') { drive = drive * 10 + (path[i] - '0'); hasDigit = true; i++; } if (!hasDigit) return false; if (path[i] != ':') return false; // Everything after "N:" is the local path outDrive = drive; outPath = &path[i + 1]; return true; } 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; BumpDriveGeneration(driveNumber); 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; if (drive < 0 || drive >= MaxDrives || !driveActive[drive] || driveTable[drive] == nullptr) return -1; vfsLock.Acquire(); FsDriver* driver = driveTable[drive]; uint32_t generation = driveGeneration[drive]; int localHandle = (driveActive[drive] && driver) ? driver->Open(localPath) : -1; 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 = (file.driveNumber >= 0 && file.driveNumber < MaxDrives) ? driveTable[file.driveNumber] : nullptr; if (file.driveNumber < 0 || file.driveNumber >= MaxDrives || !driveActive[file.driveNumber] || driver == nullptr || file.generation != driveGeneration[file.driveNumber] || file.localHandle < 0) { vfsLock.Release(); return -1; } int result = driver->Read(file.localHandle, buffer, offset, size); vfsLock.Release(); return result; } uint64_t GetBackendFileSize(const BackendFile& file) { vfsLock.Acquire(); FsDriver* driver = (file.driveNumber >= 0 && file.driveNumber < MaxDrives) ? driveTable[file.driveNumber] : nullptr; if (file.driveNumber < 0 || file.driveNumber >= MaxDrives || !driveActive[file.driveNumber] || driver == nullptr || file.generation != driveGeneration[file.driveNumber] || file.localHandle < 0) { vfsLock.Release(); return 0; } uint64_t result = driver->GetSize(file.localHandle); vfsLock.Release(); return result; } bool BackendFileCanWrite(const BackendFile& file) { vfsLock.Acquire(); FsDriver* driver = (file.driveNumber >= 0 && file.driveNumber < MaxDrives) ? driveTable[file.driveNumber] : nullptr; bool canWrite = file.driveNumber >= 0 && file.driveNumber < MaxDrives && driveActive[file.driveNumber] && driver != nullptr && file.generation == driveGeneration[file.driveNumber] && file.localHandle >= 0 && driver->Write != nullptr; vfsLock.Release(); return canWrite; } void CloseBackendFile(BackendFile& file) { vfsLock.Acquire(); FsDriver* driver = (file.driveNumber >= 0 && file.driveNumber < MaxDrives) ? driveTable[file.driveNumber] : nullptr; if (file.driveNumber < 0 || file.driveNumber >= MaxDrives || !driveActive[file.driveNumber] || driver == nullptr || file.generation != driveGeneration[file.driveNumber] || file.localHandle < 0) { vfsLock.Release(); file.driveNumber = -1; file.localHandle = -1; file.generation = 0; return; } driver->Close(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 = (file.driveNumber >= 0 && file.driveNumber < MaxDrives) ? driveTable[file.driveNumber] : nullptr; if (file.driveNumber < 0 || file.driveNumber >= MaxDrives || !driveActive[file.driveNumber] || driver == nullptr || file.generation != driveGeneration[file.driveNumber] || file.localHandle < 0) { vfsLock.Release(); return -1; } if (driver->Write == nullptr) { vfsLock.Release(); return -1; } int result = driver->Write(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; if (drive < 0 || drive >= MaxDrives || !driveActive[drive] || driveTable[drive] == nullptr) return -1; if (driveTable[drive]->Create == nullptr) return -1; vfsLock.Acquire(); FsDriver* driver = driveTable[drive]; uint32_t generation = driveGeneration[drive]; int localHandle = (driveActive[drive] && driver && driver->Create) ? driver->Create(localPath) : -1; 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; if (drive < 0 || drive >= MaxDrives || !driveActive[drive] || driveTable[drive] == nullptr) return -1; if (driveTable[drive]->Delete == nullptr) return -1; vfsLock.Acquire(); FsDriver* driver = driveTable[drive]; int result = (driveActive[drive] && driver && driver->Delete) ? driver->Delete(localPath) : -1; vfsLock.Release(); return result; } int VfsMkdir(const char* path) { int drive; const char* localPath; if (!ParsePath(path, drive, localPath)) return -1; if (drive < 0 || drive >= MaxDrives || !driveActive[drive] || driveTable[drive] == nullptr) return -1; if (driveTable[drive]->Mkdir == nullptr) return -1; vfsLock.Acquire(); FsDriver* driver = driveTable[drive]; int result = (driveActive[drive] && driver && driver->Mkdir) ? driver->Mkdir(localPath) : -1; vfsLock.Release(); return result; } int VfsDriveList(int* outDrives, int maxEntries) { vfsLock.Acquire(); int count = 0; for (int i = 0; i < MaxDrives && count < maxEntries; i++) { if (driveActive[i]) { outDrives[count++] = i; } } vfsLock.Release(); return count; } int VfsDriveLabel(int driveNumber, char* outLabel, int maxLen) { if (outLabel == nullptr || maxLen <= 0) return -1; outLabel[0] = '\0'; vfsLock.Acquire(); if (driveNumber < 0 || driveNumber >= MaxDrives || !driveActive[driveNumber] || driveTable[driveNumber] == nullptr) { vfsLock.Release(); return -1; } if (driveTable[driveNumber]->GetLabel == nullptr) { vfsLock.Release(); return 0; } const char* label = driveTable[driveNumber]->GetLabel(); if (label == nullptr || label[0] == '\0') { vfsLock.Release(); return 0; } int copied = 0; while (label[copied] != '\0' && copied < maxLen - 1) { outLabel[copied] = label[copied]; copied++; } outLabel[copied] = '\0'; vfsLock.Release(); return copied; } 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; if (oldDrive < 0 || oldDrive >= MaxDrives || !driveActive[oldDrive] || driveTable[oldDrive] == nullptr) return -1; if (driveTable[oldDrive]->Rename == nullptr) return -1; vfsLock.Acquire(); FsDriver* driver = driveTable[oldDrive]; int result = (driveActive[oldDrive] && driver && driver->Rename) ? driver->Rename(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; if (drive < 0 || drive >= MaxDrives || !driveActive[drive] || driveTable[drive] == nullptr) return -1; vfsLock.Acquire(); FsDriver* driver = driveTable[drive]; int result; if (!driveActive[drive] || !driver) { result = -1; } else if (driver->ReadDirAt) { result = driver->ReadDirAt(localPath, outNames, maxEntries, startIndex); } else if (driver->ReadDir) { // Driver without pagination support: only the first page is reachable. result = (startIndex == 0) ? driver->ReadDir(localPath, outNames, maxEntries) : 0; } else { result = -1; } vfsLock.Release(); return result; } }