feat: vfs - add dynamic filesystem mounts and safe unmount lifecycle
This commit is contained in:
+150
-59
@@ -10,6 +10,7 @@
|
||||
#include <Terminal/Terminal.hpp>
|
||||
#include <Libraries/Memory.hpp>
|
||||
#include <Memory/PageFrameAllocator.hpp>
|
||||
#include <CppLib/Vector.hpp>
|
||||
|
||||
using namespace Kt;
|
||||
|
||||
@@ -19,7 +20,6 @@ namespace Fs::Fat32 {
|
||||
// Constants
|
||||
// =========================================================================
|
||||
|
||||
static constexpr int MaxInstances = 8;
|
||||
static constexpr int MaxFilesPerInstance = 16;
|
||||
static constexpr int MaxDirEntries = 128;
|
||||
static constexpr int MaxNameLen = 256;
|
||||
@@ -60,6 +60,10 @@ namespace Fs::Fat32 {
|
||||
int blockDevIndex;
|
||||
uint64_t partStartLba;
|
||||
|
||||
// The vtable handed to the VFS for this mount; owned by the instance
|
||||
// and freed alongside it in DrvUnmount.
|
||||
Vfs::FsDriver* driver;
|
||||
|
||||
// BPB fields
|
||||
uint16_t bytesPerSector;
|
||||
uint8_t sectorsPerCluster;
|
||||
@@ -87,8 +91,11 @@ namespace Fs::Fat32 {
|
||||
// Open file handles
|
||||
Fat32File files[MaxFilesPerInstance];
|
||||
|
||||
// ReadDir name cache
|
||||
char dirNames[MaxDirEntries][MaxNameLen];
|
||||
// ReadDir name cache. 32 KiB, allocated on the first listing rather
|
||||
// than at mount, so volumes that are never enumerated don't pay for
|
||||
// it. Released on unmount.
|
||||
char (*dirNames)[MaxNameLen];
|
||||
int dirNamesPages;
|
||||
int dirNameCount;
|
||||
};
|
||||
|
||||
@@ -106,8 +113,28 @@ namespace Fs::Fat32 {
|
||||
// Instance table
|
||||
// =========================================================================
|
||||
|
||||
static Fat32Instance g_instances[MaxInstances] = {};
|
||||
static int g_instanceCount = 0;
|
||||
// One heap allocation per mounted volume. The table holds pointers rather
|
||||
// than values so growing it never moves a live instance, and an index stays
|
||||
// valid for the lifetime of its mount. Unmounting nulls the slot; Mount
|
||||
// reuses the lowest free one, so hot-plug cycles don't grow the table.
|
||||
static kcp::vector<Fat32Instance*> g_instanceSlots;
|
||||
|
||||
// Resolve an instance index, or nullptr if the slot is out of range, freed,
|
||||
// or inactive. Every FsDriver entry point validates through this; internal
|
||||
// helpers may then assume the instance is live, because the VFS holds
|
||||
// vfsLock across the whole driver call and Unmount runs under that lock.
|
||||
static Fat32Instance* InstanceAt(int inst) {
|
||||
if (inst < 0 || (std::size_t)inst >= g_instanceSlots.size()) return nullptr;
|
||||
Fat32Instance* self = g_instanceSlots[(std::size_t)inst];
|
||||
return (self != nullptr && self->active) ? self : nullptr;
|
||||
}
|
||||
|
||||
// Adapter keeping the `g_instances[inst]` spelling at the ~40 call sites
|
||||
// that index the table by instance number.
|
||||
struct InstanceTable {
|
||||
Fat32Instance& operator[](int inst) { return *g_instanceSlots[(std::size_t)inst]; }
|
||||
};
|
||||
static InstanceTable g_instances;
|
||||
|
||||
// =========================================================================
|
||||
// Low-level helpers
|
||||
@@ -902,7 +929,7 @@ namespace Fs::Fat32 {
|
||||
// =========================================================================
|
||||
|
||||
static int OpenImpl(int inst, const char* path) {
|
||||
if (inst < 0 || inst >= g_instanceCount || !g_instances[inst].active) return -1;
|
||||
if (InstanceAt(inst) == nullptr) return -1;
|
||||
|
||||
ParsedEntry entry;
|
||||
if (!TraversePath(inst, path, &entry)) return -1;
|
||||
@@ -928,7 +955,7 @@ namespace Fs::Fat32 {
|
||||
|
||||
static int ReadImpl(int inst, int handle, uint8_t* buffer,
|
||||
uint64_t offset, uint64_t size) {
|
||||
if (inst < 0 || inst >= g_instanceCount) return -1;
|
||||
if (InstanceAt(inst) == nullptr) return -1;
|
||||
auto& self = g_instances[inst];
|
||||
if (handle < 0 || handle >= MaxFilesPerInstance || !self.files[handle].inUse) return -1;
|
||||
|
||||
@@ -988,28 +1015,44 @@ namespace Fs::Fat32 {
|
||||
}
|
||||
|
||||
static uint64_t GetSizeImpl(int inst, int handle) {
|
||||
if (inst < 0 || inst >= g_instanceCount) return 0;
|
||||
if (InstanceAt(inst) == nullptr) return 0;
|
||||
auto& self = g_instances[inst];
|
||||
if (handle < 0 || handle >= MaxFilesPerInstance || !self.files[handle].inUse) return 0;
|
||||
return self.files[handle].fileSize;
|
||||
}
|
||||
|
||||
static void CloseImpl(int inst, int handle) {
|
||||
if (inst < 0 || inst >= g_instanceCount) return;
|
||||
if (InstanceAt(inst) == nullptr) return;
|
||||
auto& self = g_instances[inst];
|
||||
if (handle < 0 || handle >= MaxFilesPerInstance) return;
|
||||
self.files[handle].inUse = false;
|
||||
}
|
||||
|
||||
static constexpr int DirNamesPages =
|
||||
(MaxDirEntries * MaxNameLen + 0xFFF) / 0x1000;
|
||||
|
||||
static bool EnsureDirNames(Fat32Instance& self) {
|
||||
if (self.dirNames != nullptr) return true;
|
||||
|
||||
self.dirNames = (char(*)[MaxNameLen])Memory::g_pfa->ReallocConsecutive(
|
||||
nullptr, DirNamesPages);
|
||||
if (self.dirNames == nullptr) return false;
|
||||
|
||||
self.dirNamesPages = DirNamesPages;
|
||||
return true;
|
||||
}
|
||||
|
||||
static int ReadDirImpl(int inst, const char* path,
|
||||
const char** outNames, int maxEntries, int startIndex = 0) {
|
||||
if (inst < 0 || inst >= g_instanceCount) return -1;
|
||||
if (InstanceAt(inst) == nullptr) return -1;
|
||||
auto& self = g_instances[inst];
|
||||
|
||||
ParsedEntry dirEntry;
|
||||
if (!TraversePath(inst, path, &dirEntry)) return -1;
|
||||
if (!(dirEntry.attributes & ATTR_DIRECTORY)) return -1;
|
||||
|
||||
if (!EnsureDirNames(self)) return -1;
|
||||
|
||||
int limit = maxEntries < MaxDirEntries ? maxEntries : MaxDirEntries;
|
||||
int count = ReadDirectoryNames(inst, dirEntry.firstCluster, self.dirNames, limit, startIndex);
|
||||
|
||||
@@ -1023,7 +1066,7 @@ namespace Fs::Fat32 {
|
||||
|
||||
static int WriteImpl(int inst, int handle, const uint8_t* buffer,
|
||||
uint64_t offset, uint64_t size) {
|
||||
if (inst < 0 || inst >= g_instanceCount) return -1;
|
||||
if (InstanceAt(inst) == nullptr) return -1;
|
||||
auto& self = g_instances[inst];
|
||||
if (handle < 0 || handle >= MaxFilesPerInstance || !self.files[handle].inUse) return -1;
|
||||
|
||||
@@ -1126,7 +1169,7 @@ namespace Fs::Fat32 {
|
||||
}
|
||||
|
||||
static int CreateImpl(int inst, const char* path) {
|
||||
if (inst < 0 || inst >= g_instanceCount || !g_instances[inst].active) return -1;
|
||||
if (InstanceAt(inst) == nullptr) return -1;
|
||||
auto& self = g_instances[inst];
|
||||
|
||||
// Split path into parent directory and filename
|
||||
@@ -1274,7 +1317,7 @@ namespace Fs::Fat32 {
|
||||
}
|
||||
|
||||
static int DeleteImpl(int inst, const char* path) {
|
||||
if (inst < 0 || inst >= g_instanceCount || !g_instances[inst].active) return -1;
|
||||
if (InstanceAt(inst) == nullptr) return -1;
|
||||
auto& self = g_instances[inst];
|
||||
|
||||
// Split path into parent directory and filename
|
||||
@@ -1410,7 +1453,7 @@ namespace Fs::Fat32 {
|
||||
// =========================================================================
|
||||
|
||||
static int MkdirImpl(int inst, const char* path) {
|
||||
if (inst < 0 || inst >= g_instanceCount || !g_instances[inst].active) return -1;
|
||||
if (InstanceAt(inst) == nullptr) return -1;
|
||||
auto& self = g_instances[inst];
|
||||
|
||||
// Split path into parent directory and new dir name
|
||||
@@ -1540,7 +1583,7 @@ namespace Fs::Fat32 {
|
||||
// =========================================================================
|
||||
|
||||
static int RenameImpl(int inst, const char* oldPath, const char* newPath) {
|
||||
if (inst < 0 || inst >= g_instanceCount || !g_instances[inst].active) return -1;
|
||||
if (InstanceAt(inst) == nullptr) return -1;
|
||||
auto& self = g_instances[inst];
|
||||
|
||||
// Split old path
|
||||
@@ -1754,58 +1797,72 @@ namespace Fs::Fat32 {
|
||||
// =========================================================================
|
||||
|
||||
static const char* GetLabelImpl(int inst) {
|
||||
if (inst < 0 || inst >= g_instanceCount || !g_instances[inst].active) return nullptr;
|
||||
if (InstanceAt(inst) == nullptr) return nullptr;
|
||||
|
||||
const char* label = g_instances[inst].volumeLabel;
|
||||
if (label[0] == '\0' || StrEqualNoCase(label, "NO NAME")) return nullptr;
|
||||
return label;
|
||||
}
|
||||
|
||||
template<int N> struct Thunks {
|
||||
static int Open(const char* p) { return OpenImpl(N, p); }
|
||||
static int Read(int h, uint8_t* b, uint64_t o, uint64_t s) { return ReadImpl(N, h, b, o, s); }
|
||||
static uint64_t GetSize(int h) { return GetSizeImpl(N, h); }
|
||||
static void Close(int h) { CloseImpl(N, h); }
|
||||
static int ReadDir(const char* p, const char** o, int m) { return ReadDirImpl(N, p, o, m); }
|
||||
static int ReadDirAt(const char* p, const char** o, int m, int s) { return ReadDirImpl(N, p, o, m, s); }
|
||||
static int Write(int h, const uint8_t* b, uint64_t o, uint64_t s) { return WriteImpl(N, h, b, o, s); }
|
||||
static int Create(const char* p) { return CreateImpl(N, p); }
|
||||
static int Delete(const char* p) { return DeleteImpl(N, p); }
|
||||
static int Mkdir(const char* p) { return MkdirImpl(N, p); }
|
||||
static int Rename(const char* o, const char* n) { return RenameImpl(N, o, n); }
|
||||
static const char* GetLabel() { return GetLabelImpl(N); }
|
||||
};
|
||||
// ctx carries the instance index. Slots are never compacted, so the index
|
||||
// stays valid until the mount is torn down.
|
||||
static int CtxToInst(void* ctx) { return (int)(uintptr_t)ctx; }
|
||||
static void* InstToCtx(int inst) { return (void*)(uintptr_t)inst; }
|
||||
|
||||
template<int N>
|
||||
static Vfs::FsDriver MakeDriver() {
|
||||
return {
|
||||
Thunks<N>::Open,
|
||||
Thunks<N>::Read,
|
||||
Thunks<N>::GetSize,
|
||||
Thunks<N>::Close,
|
||||
Thunks<N>::ReadDir,
|
||||
Thunks<N>::Write,
|
||||
Thunks<N>::Create,
|
||||
Thunks<N>::Delete,
|
||||
Thunks<N>::Mkdir,
|
||||
Thunks<N>::Rename,
|
||||
Thunks<N>::GetLabel,
|
||||
Thunks<N>::ReadDirAt,
|
||||
};
|
||||
static int DrvOpen(void* c, const char* p) { return OpenImpl(CtxToInst(c), p); }
|
||||
static int DrvRead(void* c, int h, uint8_t* b, uint64_t o, uint64_t s) { return ReadImpl(CtxToInst(c), h, b, o, s); }
|
||||
static uint64_t DrvGetSize(void* c, int h) { return GetSizeImpl(CtxToInst(c), h); }
|
||||
static void DrvClose(void* c, int h) { CloseImpl(CtxToInst(c), h); }
|
||||
static int DrvReadDir(void* c, const char* p, const char** o, int m) { return ReadDirImpl(CtxToInst(c), p, o, m); }
|
||||
static int DrvReadDirAt(void* c, const char* p, const char** o, int m, int s) { return ReadDirImpl(CtxToInst(c), p, o, m, s); }
|
||||
static int DrvWrite(void* c, int h, const uint8_t* b, uint64_t o, uint64_t s) { return WriteImpl(CtxToInst(c), h, b, o, s); }
|
||||
static int DrvCreate(void* c, const char* p) { return CreateImpl(CtxToInst(c), p); }
|
||||
static int DrvDelete(void* c, const char* p) { return DeleteImpl(CtxToInst(c), p); }
|
||||
static int DrvMkdir(void* c, const char* p) { return MkdirImpl(CtxToInst(c), p); }
|
||||
static int DrvRename(void* c, const char* o, const char* n) { return RenameImpl(CtxToInst(c), o, n); }
|
||||
static const char* DrvGetLabel(void* c) { return GetLabelImpl(CtxToInst(c)); }
|
||||
|
||||
// Release everything the mount owns. The driver is either not registered
|
||||
// yet, or the VFS has deactivated its drive and drained dispatches.
|
||||
static void DrvUnmount(void* c) {
|
||||
int inst = CtxToInst(c);
|
||||
Fat32Instance* self = InstanceAt(inst);
|
||||
if (self == nullptr) return;
|
||||
|
||||
if (self->clusterBuf != nullptr && self->clusterBufPages > 0) {
|
||||
Memory::g_pfa->Free(self->clusterBuf, self->clusterBufPages);
|
||||
}
|
||||
if (self->fatCache != nullptr && self->fatCachePages > 0) {
|
||||
Memory::g_pfa->Free(self->fatCache, self->fatCachePages);
|
||||
}
|
||||
|
||||
if (self->dirNames != nullptr && self->dirNamesPages > 0) {
|
||||
Memory::g_pfa->Free(self->dirNames, self->dirNamesPages);
|
||||
}
|
||||
|
||||
self->active = false;
|
||||
g_instanceSlots[(std::size_t)inst] = nullptr;
|
||||
|
||||
Vfs::FsDriver* driver = self->driver;
|
||||
Memory::g_heap->Free(self);
|
||||
if (driver != nullptr) Memory::g_heap->Free(driver);
|
||||
}
|
||||
|
||||
static Vfs::FsDriver g_drivers[] = {
|
||||
MakeDriver<0>(), MakeDriver<1>(), MakeDriver<2>(), MakeDriver<3>(),
|
||||
MakeDriver<4>(), MakeDriver<5>(), MakeDriver<6>(), MakeDriver<7>(),
|
||||
};
|
||||
|
||||
// =========================================================================
|
||||
// BPB validation and mount
|
||||
// =========================================================================
|
||||
|
||||
Vfs::FsDriver* Mount(int blockDevIndex, uint64_t startLba, uint64_t sectorCount) {
|
||||
if (g_instanceCount >= MaxInstances) return nullptr;
|
||||
// Claim the lowest free instance slot, extending the table if all are in
|
||||
// use. Returns -1 only if the heap is exhausted.
|
||||
static int AllocateInstanceSlot() {
|
||||
for (std::size_t i = 0; i < g_instanceSlots.size(); i++) {
|
||||
if (g_instanceSlots[i] == nullptr) return (int)i;
|
||||
}
|
||||
g_instanceSlots.push_back(nullptr);
|
||||
return (int)g_instanceSlots.size() - 1;
|
||||
}
|
||||
|
||||
Vfs::FsDriver* Mount(int blockDevIndex, uint64_t startLba, uint64_t sectorCount) {
|
||||
auto* dev = Drivers::Storage::GetBlockDevice(blockDevIndex);
|
||||
if (!dev) return nullptr;
|
||||
|
||||
@@ -1873,9 +1930,23 @@ namespace Fs::Fat32 {
|
||||
// At least one of: valid cluster count or FS type string
|
||||
if (!hasFat32Str && clusterCount < 65525) return nullptr;
|
||||
|
||||
// Success — initialize instance
|
||||
int idx = g_instanceCount;
|
||||
auto& inst = g_instances[idx];
|
||||
// Success — allocate and initialize the instance
|
||||
int idx = AllocateInstanceSlot();
|
||||
if (idx < 0) return nullptr;
|
||||
|
||||
auto* instPtr = (Fat32Instance*)Memory::g_heap->Request(sizeof(Fat32Instance));
|
||||
if (instPtr == nullptr) return nullptr;
|
||||
memset(instPtr, 0, sizeof(Fat32Instance));
|
||||
|
||||
auto* driver = (Vfs::FsDriver*)Memory::g_heap->Request(sizeof(Vfs::FsDriver));
|
||||
if (driver == nullptr) {
|
||||
Memory::g_heap->Free(instPtr);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
g_instanceSlots[(std::size_t)idx] = instPtr;
|
||||
auto& inst = *instPtr;
|
||||
inst.driver = driver;
|
||||
|
||||
inst.active = true;
|
||||
inst.blockDevIndex = blockDevIndex;
|
||||
@@ -1924,8 +1995,12 @@ namespace Fs::Fat32 {
|
||||
uint32_t chunk = (remaining > 4096) ? 4096 : (uint32_t)remaining;
|
||||
uint32_t secs = (chunk + bytesPerSector - 1) / bytesPerSector;
|
||||
if (!ReadPartSectors(inst, fatPartSector, secs, dst)) {
|
||||
// If read fails, disable cache and fall back to per-lookup reads
|
||||
// If read fails, disable cache and fall back to per-lookup
|
||||
// reads. Release the pages rather than orphaning them.
|
||||
Memory::g_pfa->Free(inst.fatCache, inst.fatCachePages);
|
||||
inst.fatCache = nullptr;
|
||||
inst.fatCachePages = 0;
|
||||
inst.fatCacheEntries = 0;
|
||||
break;
|
||||
}
|
||||
dst += secs * bytesPerSector;
|
||||
@@ -1939,13 +2014,29 @@ namespace Fs::Fat32 {
|
||||
inst.files[i].inUse = false;
|
||||
}
|
||||
|
||||
g_instanceCount++;
|
||||
*driver = Vfs::FsDriver{
|
||||
.ctx = InstToCtx(idx),
|
||||
.Open = DrvOpen,
|
||||
.Read = DrvRead,
|
||||
.GetSize = DrvGetSize,
|
||||
.Close = DrvClose,
|
||||
.ReadDir = DrvReadDir,
|
||||
.Write = DrvWrite,
|
||||
.Create = DrvCreate,
|
||||
.Delete = DrvDelete,
|
||||
.Mkdir = DrvMkdir,
|
||||
.Rename = DrvRename,
|
||||
.GetLabel = DrvGetLabel,
|
||||
.ReadDirAt = DrvReadDirAt,
|
||||
.Stat = nullptr,
|
||||
.Unmount = DrvUnmount,
|
||||
};
|
||||
|
||||
KernelLogStream(OK, "FAT32") << "Mounted volume \""
|
||||
<< inst.volumeLabel << "\" (" << clusterCount << " clusters, "
|
||||
<< (uint64_t)inst.clusterSize << " bytes/cluster)";
|
||||
|
||||
return &g_drivers[idx];
|
||||
return driver;
|
||||
}
|
||||
|
||||
void RegisterProbe() {
|
||||
|
||||
Reference in New Issue
Block a user