feat: vfs - add dynamic filesystem mounts and safe unmount lifecycle
This commit is contained in:
@@ -12,4 +12,4 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#define MONTAUK_BUILD_NUMBER 37
|
||||
#define MONTAUK_BUILD_NUMBER 42
|
||||
|
||||
+31
-12
@@ -54,19 +54,38 @@ namespace Fs {
|
||||
return hasRamdisk;
|
||||
}
|
||||
|
||||
// The ramdisk is a singleton, so it has no per-mount state and ignores
|
||||
// ctx. These shims exist only to match the FsDriver signature.
|
||||
int RdOpen(void*, const char* p) { return Ramdisk::Open(p); }
|
||||
int RdRead(void*, int h, uint8_t* b, uint64_t o, uint64_t s) { return Ramdisk::Read(h, b, o, s); }
|
||||
uint64_t RdGetSize(void*, int h) { return Ramdisk::GetSize(h); }
|
||||
void RdClose(void*, int h) { Ramdisk::Close(h); }
|
||||
int RdReadDir(void*, const char* p, const char** o, int m) { return Ramdisk::ReadDir(p, o, m); }
|
||||
int RdWrite(void*, int h, const uint8_t* b, uint64_t o, uint64_t s) { return Ramdisk::Write(h, b, o, s); }
|
||||
int RdCreate(void*, const char* p) { return Ramdisk::Create(p); }
|
||||
int RdDelete(void*, const char* p) { return Ramdisk::Delete(p); }
|
||||
int RdMkdir(void*, const char* p) { return Ramdisk::Mkdir(p); }
|
||||
int RdRename(void*, const char* o, const char* n) { return Ramdisk::Rename(o, n); }
|
||||
const char* RdGetLabel(void*) { return Ramdisk::GetLabel(); }
|
||||
int RdReadDirAt(void*, const char* p, const char** o, int m, int s) { return Ramdisk::ReadDirAt(p, o, m, s); }
|
||||
|
||||
Vfs::FsDriver g_ramdiskDriver = {
|
||||
Ramdisk::Open,
|
||||
Ramdisk::Read,
|
||||
Ramdisk::GetSize,
|
||||
Ramdisk::Close,
|
||||
Ramdisk::ReadDir,
|
||||
Ramdisk::Write,
|
||||
Ramdisk::Create,
|
||||
Ramdisk::Delete,
|
||||
Ramdisk::Mkdir,
|
||||
Ramdisk::Rename,
|
||||
Ramdisk::GetLabel,
|
||||
Ramdisk::ReadDirAt,
|
||||
.ctx = nullptr,
|
||||
.Open = RdOpen,
|
||||
.Read = RdRead,
|
||||
.GetSize = RdGetSize,
|
||||
.Close = RdClose,
|
||||
.ReadDir = RdReadDir,
|
||||
.Write = RdWrite,
|
||||
.Create = RdCreate,
|
||||
.Delete = RdDelete,
|
||||
.Mkdir = RdMkdir,
|
||||
.Rename = RdRename,
|
||||
.GetLabel = RdGetLabel,
|
||||
.ReadDirAt = RdReadDirAt,
|
||||
.Stat = nullptr,
|
||||
// Statically allocated: nothing to release.
|
||||
.Unmount = nullptr,
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
+168
-67
@@ -10,6 +10,7 @@
|
||||
#include <Terminal/Terminal.hpp>
|
||||
#include <Libraries/Memory.hpp>
|
||||
#include <Memory/PageFrameAllocator.hpp>
|
||||
#include <CppLib/Vector.hpp>
|
||||
#include <Timekeeping/Time.hpp>
|
||||
|
||||
using namespace Kt;
|
||||
@@ -20,7 +21,6 @@ namespace Fs::Ext2 {
|
||||
// Constants
|
||||
// =========================================================================
|
||||
|
||||
static constexpr int MaxInstances = 8;
|
||||
static constexpr int MaxFilesPerInstance = 16;
|
||||
static constexpr int MaxDirEntries = 128;
|
||||
static constexpr int MaxNameLen = 256;
|
||||
@@ -148,6 +148,10 @@ namespace Fs::Ext2 {
|
||||
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;
|
||||
|
||||
// Superblock fields
|
||||
uint32_t blockSize; // 1024 << s_log_block_size
|
||||
uint32_t inodeSize;
|
||||
@@ -170,8 +174,11 @@ namespace Fs::Ext2 {
|
||||
// Open file handles
|
||||
Ext2File 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;
|
||||
};
|
||||
|
||||
@@ -179,8 +186,28 @@ namespace Fs::Ext2 {
|
||||
// Instance table
|
||||
// =========================================================================
|
||||
|
||||
static Ext2Instance 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<Ext2Instance*> 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 Ext2Instance* InstanceAt(int inst) {
|
||||
if (inst < 0 || (std::size_t)inst >= g_instanceSlots.size()) return nullptr;
|
||||
Ext2Instance* self = g_instanceSlots[(std::size_t)inst];
|
||||
return (self != nullptr && self->active) ? self : nullptr;
|
||||
}
|
||||
|
||||
// Adapter keeping the `g_instances[inst]` spelling at the call sites that
|
||||
// index the table by instance number.
|
||||
struct InstanceTable {
|
||||
Ext2Instance& operator[](int inst) { return *g_instanceSlots[(std::size_t)inst]; }
|
||||
};
|
||||
static InstanceTable g_instances;
|
||||
|
||||
// =========================================================================
|
||||
// Low-level helpers
|
||||
@@ -1168,7 +1195,7 @@ namespace Fs::Ext2 {
|
||||
// =========================================================================
|
||||
|
||||
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;
|
||||
auto& self = g_instances[inst];
|
||||
|
||||
uint32_t inodeNum;
|
||||
@@ -1191,7 +1218,7 @@ namespace Fs::Ext2 {
|
||||
|
||||
static int StatImpl(int inst, const char* path, Vfs::StatInfo* out) {
|
||||
if (!out) return -1;
|
||||
if (inst < 0 || inst >= g_instanceCount || !g_instances[inst].active) return -1;
|
||||
if (InstanceAt(inst) == nullptr) return -1;
|
||||
auto& self = g_instances[inst];
|
||||
|
||||
uint32_t inodeNum;
|
||||
@@ -1209,7 +1236,7 @@ namespace Fs::Ext2 {
|
||||
|
||||
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;
|
||||
|
||||
@@ -1250,22 +1277,36 @@ namespace Fs::Ext2 {
|
||||
}
|
||||
|
||||
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].inode.i_size;
|
||||
}
|
||||
|
||||
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(Ext2Instance& 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];
|
||||
|
||||
uint32_t inodeNum;
|
||||
@@ -1273,6 +1314,8 @@ namespace Fs::Ext2 {
|
||||
if (!TraversePath(self, path, &inodeNum, &inode)) return -1;
|
||||
if ((inode.i_mode & IMODE_TYPE_MASK) != IMODE_DIR) return -1;
|
||||
|
||||
if (!EnsureDirNames(self)) return -1;
|
||||
|
||||
int limit = maxEntries < MaxDirEntries ? maxEntries : MaxDirEntries;
|
||||
int count = ReadDirectoryNames(self, inode, self.dirNames, limit, startIndex);
|
||||
|
||||
@@ -1286,7 +1329,7 @@ namespace Fs::Ext2 {
|
||||
|
||||
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;
|
||||
|
||||
@@ -1358,7 +1401,7 @@ namespace Fs::Ext2 {
|
||||
}
|
||||
|
||||
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];
|
||||
|
||||
char parentPath[MaxNameLen];
|
||||
@@ -1448,7 +1491,7 @@ namespace Fs::Ext2 {
|
||||
}
|
||||
|
||||
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];
|
||||
|
||||
char parentPath[MaxNameLen];
|
||||
@@ -1508,7 +1551,7 @@ namespace Fs::Ext2 {
|
||||
}
|
||||
|
||||
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];
|
||||
|
||||
char parentPath[MaxNameLen];
|
||||
@@ -1640,7 +1683,7 @@ namespace Fs::Ext2 {
|
||||
// =========================================================================
|
||||
|
||||
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
|
||||
@@ -1765,57 +1808,88 @@ namespace Fs::Ext2 {
|
||||
// =========================================================================
|
||||
|
||||
static const char* GetLabelImpl(int inst) {
|
||||
if (inst < 0 || inst >= g_instanceCount || !g_instances[inst].active) return nullptr;
|
||||
if (InstanceAt(inst) == nullptr) return nullptr;
|
||||
return g_instances[inst].volumeLabel[0] ? g_instances[inst].volumeLabel : nullptr;
|
||||
}
|
||||
|
||||
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); }
|
||||
static int Stat(const char* p, Vfs::StatInfo* o) { return StatImpl(N, p, o); }
|
||||
};
|
||||
// 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,
|
||||
Thunks<N>::Stat,
|
||||
};
|
||||
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)); }
|
||||
static int DrvStat(void* c, const char* p, Vfs::StatInfo* o) { return StatImpl(CtxToInst(c), p, o); }
|
||||
|
||||
// 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);
|
||||
Ext2Instance* self = InstanceAt(inst);
|
||||
if (self == nullptr) return;
|
||||
|
||||
if (self->blockBuf != nullptr && self->blockBufPages > 0) {
|
||||
Memory::g_pfa->Free(self->blockBuf, self->blockBufPages);
|
||||
}
|
||||
if (self->bgdt != nullptr && self->bgdtPages > 0) {
|
||||
Memory::g_pfa->Free(self->bgdt, self->bgdtPages);
|
||||
}
|
||||
|
||||
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>(),
|
||||
};
|
||||
|
||||
// =========================================================================
|
||||
// Superblock 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;
|
||||
}
|
||||
|
||||
// Undo a partially built mount: release whatever was allocated so far and
|
||||
// free the slot. Only used on the Mount error paths.
|
||||
static Vfs::FsDriver* AbortMount(int idx) {
|
||||
Ext2Instance* self = g_instanceSlots[(std::size_t)idx];
|
||||
if (self != nullptr) {
|
||||
if (self->blockBuf != nullptr && self->blockBufPages > 0) {
|
||||
Memory::g_pfa->Free(self->blockBuf, self->blockBufPages);
|
||||
}
|
||||
if (self->bgdt != nullptr && self->bgdtPages > 0) {
|
||||
Memory::g_pfa->Free(self->bgdt, self->bgdtPages);
|
||||
}
|
||||
if (self->driver != nullptr) Memory::g_heap->Free(self->driver);
|
||||
Memory::g_heap->Free(self);
|
||||
}
|
||||
g_instanceSlots[(std::size_t)idx] = nullptr;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
Vfs::FsDriver* Mount(int blockDevIndex, uint64_t startLba, uint64_t sectorCount) {
|
||||
auto* dev = Drivers::Storage::GetBlockDevice(blockDevIndex);
|
||||
if (!dev) return nullptr;
|
||||
|
||||
@@ -1865,9 +1939,23 @@ namespace Fs::Ext2 {
|
||||
/ sb->s_blocks_per_group;
|
||||
if (groupCount == 0) 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 = (Ext2Instance*)Memory::g_heap->Request(sizeof(Ext2Instance));
|
||||
if (instPtr == nullptr) return nullptr;
|
||||
memset(instPtr, 0, sizeof(Ext2Instance));
|
||||
|
||||
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;
|
||||
@@ -1900,8 +1988,7 @@ namespace Fs::Ext2 {
|
||||
nullptr, inst.blockBufPages);
|
||||
}
|
||||
if (!inst.blockBuf) {
|
||||
inst.active = false;
|
||||
return nullptr;
|
||||
return AbortMount(idx);
|
||||
}
|
||||
|
||||
// Load block group descriptor table
|
||||
@@ -1918,8 +2005,7 @@ namespace Fs::Ext2 {
|
||||
}
|
||||
|
||||
if (!inst.bgdt) {
|
||||
inst.active = false;
|
||||
return nullptr;
|
||||
return AbortMount(idx);
|
||||
}
|
||||
|
||||
// Read BGDT blocks
|
||||
@@ -1927,8 +2013,7 @@ namespace Fs::Ext2 {
|
||||
uint8_t* dst = (uint8_t*)inst.bgdt;
|
||||
for (uint32_t b = 0; b < bgdtBlocks; b++) {
|
||||
if (!ReadBlock(inst, bgdtStartBlock + b, inst.blockBuf)) {
|
||||
inst.active = false;
|
||||
return nullptr;
|
||||
return AbortMount(idx);
|
||||
}
|
||||
uint32_t copyLen = bgdtBytes - b * blockSize;
|
||||
if (copyLen > blockSize) copyLen = blockSize;
|
||||
@@ -1940,13 +2025,29 @@ namespace Fs::Ext2 {
|
||||
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 = DrvStat,
|
||||
.Unmount = DrvUnmount,
|
||||
};
|
||||
|
||||
KernelLogStream(OK, "Ext2") << "Mounted volume \""
|
||||
<< inst.volumeLabel << "\" (" << inst.totalBlocks << " blocks, "
|
||||
<< blockSize << " bytes/block, " << groupCount << " groups)";
|
||||
|
||||
return &g_drivers[idx];
|
||||
return driver;
|
||||
}
|
||||
|
||||
void RegisterProbe() {
|
||||
|
||||
+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() {
|
||||
|
||||
@@ -17,6 +17,15 @@ namespace Fs::FsProbe {
|
||||
static bool g_mounted[Drivers::Storage::Gpt::MaxPartitions] = {};
|
||||
static int g_driveForPart[Drivers::Storage::Gpt::MaxPartitions] = {};
|
||||
|
||||
// A successful probe returns an owned mount. Registration transfers that
|
||||
// ownership to the VFS; if registration loses a drive-slot race, release
|
||||
// the mount here so its driver, instance, and page allocations do not leak.
|
||||
static void DiscardUnregisteredDriver(Vfs::FsDriver* driver) {
|
||||
if (driver != nullptr && driver->Unmount != nullptr) {
|
||||
driver->Unmount(driver->ctx);
|
||||
}
|
||||
}
|
||||
|
||||
void Register(ProbeFn fn) {
|
||||
if (g_probeCount < MaxProbes && fn) {
|
||||
g_probes[g_probeCount++] = fn;
|
||||
@@ -60,6 +69,7 @@ namespace Fs::FsProbe {
|
||||
return 1;
|
||||
}
|
||||
|
||||
DiscardUnregisteredDriver(driver);
|
||||
return -1;
|
||||
}
|
||||
|
||||
@@ -135,6 +145,7 @@ namespace Fs::FsProbe {
|
||||
<< partIndex << " as drive " << driveNum;
|
||||
return 0;
|
||||
}
|
||||
DiscardUnregisteredDriver(driver);
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
+86
-90
@@ -19,6 +19,11 @@ namespace Fs::Vfs {
|
||||
// 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) {
|
||||
@@ -27,15 +32,15 @@ namespace Fs::Vfs {
|
||||
// Parse decimal drive number before ':'
|
||||
int drive = 0;
|
||||
int i = 0;
|
||||
bool hasDigit = false;
|
||||
int digits = 0;
|
||||
|
||||
while (path[i] >= '0' && path[i] <= '9') {
|
||||
if (++digits > MaxDriveDigits) return false;
|
||||
drive = drive * 10 + (path[i] - '0');
|
||||
hasDigit = true;
|
||||
i++;
|
||||
}
|
||||
|
||||
if (!hasDigit) return false;
|
||||
if (digits == 0) return false;
|
||||
if (path[i] != ':') return false;
|
||||
|
||||
// Everything after "N:" is the local path
|
||||
@@ -44,6 +49,27 @@ namespace Fs::Vfs {
|
||||
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) {
|
||||
@@ -95,7 +121,16 @@ namespace Fs::Vfs {
|
||||
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;
|
||||
@@ -135,12 +170,15 @@ namespace Fs::Vfs {
|
||||
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];
|
||||
FsDriver* driver = DriverForLocked(drive);
|
||||
if (driver == nullptr || driver->Open == nullptr) {
|
||||
vfsLock.Release();
|
||||
return -1;
|
||||
}
|
||||
uint32_t generation = driveGeneration[drive];
|
||||
int localHandle = (driveActive[drive] && driver) ? driver->Open(localPath) : -1;
|
||||
int localHandle = driver->Open(driver->ctx, localPath);
|
||||
vfsLock.Release();
|
||||
if (localHandle < 0) return -1;
|
||||
|
||||
@@ -152,72 +190,44 @@ namespace Fs::Vfs {
|
||||
|
||||
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) {
|
||||
FsDriver* driver = DriverForFileLocked(file);
|
||||
if (driver == nullptr || driver->Read == nullptr) {
|
||||
vfsLock.Release();
|
||||
return -1;
|
||||
}
|
||||
|
||||
int result = driver->Read(file.localHandle, buffer, offset, size);
|
||||
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 = (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) {
|
||||
FsDriver* driver = DriverForFileLocked(file);
|
||||
if (driver == nullptr || driver->GetSize == nullptr) {
|
||||
vfsLock.Release();
|
||||
return 0;
|
||||
}
|
||||
|
||||
uint64_t result = driver->GetSize(file.localHandle);
|
||||
uint64_t result = driver->GetSize(driver->ctx, 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;
|
||||
FsDriver* driver = DriverForFileLocked(file);
|
||||
bool canWrite = driver != nullptr && 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;
|
||||
FsDriver* driver = DriverForFileLocked(file);
|
||||
if (driver != nullptr && driver->Close != nullptr) {
|
||||
driver->Close(driver->ctx, file.localHandle);
|
||||
}
|
||||
|
||||
driver->Close(file.localHandle);
|
||||
vfsLock.Release();
|
||||
|
||||
file.driveNumber = -1;
|
||||
@@ -227,19 +237,13 @@ namespace Fs::Vfs {
|
||||
|
||||
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) {
|
||||
FsDriver* driver = DriverForFileLocked(file);
|
||||
if (driver == nullptr || driver->Write == nullptr) {
|
||||
vfsLock.Release();
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (driver->Write == nullptr) { vfsLock.Release(); return -1; }
|
||||
int result = driver->Write(file.localHandle, buffer, offset, size);
|
||||
int result = driver->Write(driver->ctx, file.localHandle, buffer, offset, size);
|
||||
vfsLock.Release();
|
||||
return result;
|
||||
}
|
||||
@@ -253,15 +257,15 @@ namespace Fs::Vfs {
|
||||
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];
|
||||
FsDriver* driver = DriverForLocked(drive);
|
||||
if (driver == nullptr || driver->Create == nullptr) {
|
||||
vfsLock.Release();
|
||||
return -1;
|
||||
}
|
||||
uint32_t generation = driveGeneration[drive];
|
||||
int localHandle = (driveActive[drive] && driver && driver->Create)
|
||||
? driver->Create(localPath)
|
||||
: -1;
|
||||
int localHandle = driver->Create(driver->ctx, localPath);
|
||||
vfsLock.Release();
|
||||
if (localHandle < 0) return -1;
|
||||
|
||||
@@ -276,13 +280,11 @@ namespace Fs::Vfs {
|
||||
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)
|
||||
FsDriver* driver = DriverForLocked(drive);
|
||||
int result = (driver != nullptr && driver->Delete != nullptr)
|
||||
? driver->Delete(driver->ctx, localPath)
|
||||
: -1;
|
||||
vfsLock.Release();
|
||||
return result;
|
||||
@@ -295,13 +297,11 @@ namespace Fs::Vfs {
|
||||
out = StatInfo{};
|
||||
|
||||
if (!ParsePath(path, drive, localPath)) return -1;
|
||||
if (drive < 0 || drive >= MaxDrives || !driveActive[drive] || driveTable[drive] == nullptr) return -1;
|
||||
if (driveTable[drive]->Stat == nullptr) return -1;
|
||||
|
||||
vfsLock.Acquire();
|
||||
FsDriver* driver = driveTable[drive];
|
||||
int result = (driveActive[drive] && driver && driver->Stat)
|
||||
? driver->Stat(localPath, &out)
|
||||
FsDriver* driver = DriverForLocked(drive);
|
||||
int result = (driver != nullptr && driver->Stat != nullptr)
|
||||
? driver->Stat(driver->ctx, localPath, &out)
|
||||
: -1;
|
||||
vfsLock.Release();
|
||||
return result;
|
||||
@@ -312,19 +312,19 @@ namespace Fs::Vfs {
|
||||
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)
|
||||
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++) {
|
||||
@@ -341,18 +341,18 @@ namespace Fs::Vfs {
|
||||
outLabel[0] = '\0';
|
||||
|
||||
vfsLock.Acquire();
|
||||
if (driveNumber < 0 || driveNumber >= MaxDrives || !driveActive[driveNumber] ||
|
||||
driveTable[driveNumber] == nullptr) {
|
||||
FsDriver* driver = DriverForLocked(driveNumber);
|
||||
if (driver == nullptr) {
|
||||
vfsLock.Release();
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (driveTable[driveNumber]->GetLabel == nullptr) {
|
||||
if (driver->GetLabel == nullptr) {
|
||||
vfsLock.Release();
|
||||
return 0;
|
||||
}
|
||||
|
||||
const char* label = driveTable[driveNumber]->GetLabel();
|
||||
const char* label = driver->GetLabel(driver->ctx);
|
||||
if (label == nullptr || label[0] == '\0') {
|
||||
vfsLock.Release();
|
||||
return 0;
|
||||
@@ -379,14 +379,11 @@ namespace Fs::Vfs {
|
||||
|
||||
// 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)
|
||||
FsDriver* driver = DriverForLocked(oldDrive);
|
||||
int result = (driver != nullptr && driver->Rename != nullptr)
|
||||
? driver->Rename(driver->ctx, oldLocal, newLocal)
|
||||
: -1;
|
||||
vfsLock.Release();
|
||||
return result;
|
||||
@@ -402,18 +399,17 @@ namespace Fs::Vfs {
|
||||
|
||||
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];
|
||||
FsDriver* driver = DriverForLocked(drive);
|
||||
int result;
|
||||
if (!driveActive[drive] || !driver) {
|
||||
if (driver == nullptr) {
|
||||
result = -1;
|
||||
} else if (driver->ReadDirAt) {
|
||||
result = driver->ReadDirAt(localPath, outNames, maxEntries, startIndex);
|
||||
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(localPath, outNames, maxEntries) : 0;
|
||||
result = (startIndex == 0) ? driver->ReadDir(driver->ctx, localPath, outNames, maxEntries) : 0;
|
||||
} else {
|
||||
result = -1;
|
||||
}
|
||||
|
||||
+32
-14
@@ -10,7 +10,15 @@
|
||||
|
||||
namespace Fs::Vfs {
|
||||
|
||||
static constexpr int MaxDrives = 16;
|
||||
// Size of the drive-number namespace. This is a sanity bound, not a
|
||||
// resource limit: drive numbers arrive as text in paths ("N:/..."), so the
|
||||
// range has to be bounded somewhere, and the table costs only 13 bytes per
|
||||
// slot. The number of mountable volumes is limited by memory alone -- the
|
||||
// FS drivers allocate one instance per mount.
|
||||
//
|
||||
// Keep at or below 99: the shell's drive-number formatter
|
||||
// (programs/src/shell/shell.h) emits at most two digits.
|
||||
static constexpr int MaxDrives = 64;
|
||||
|
||||
struct BackendFile {
|
||||
int driveNumber;
|
||||
@@ -29,24 +37,34 @@ namespace Fs::Vfs {
|
||||
bool isDir; // true if the entry is a directory
|
||||
};
|
||||
|
||||
// A mounted filesystem. Every entry point takes the driver's own `ctx` so a
|
||||
// driver can serve any number of concurrent mounts from one set of function
|
||||
// pointers; drivers with a single global mount (e.g. the ramdisk) ignore it.
|
||||
struct FsDriver {
|
||||
int (*Open)(const char* path);
|
||||
int (*Read)(int handle, uint8_t* buffer, uint64_t offset, uint64_t size);
|
||||
uint64_t (*GetSize)(int handle);
|
||||
void (*Close)(int handle);
|
||||
int (*ReadDir)(const char* path, const char** outNames, int maxEntries);
|
||||
int (*Write)(int handle, const uint8_t* buffer, uint64_t offset, uint64_t size);
|
||||
int (*Create)(const char* path);
|
||||
int (*Delete)(const char* path);
|
||||
int (*Mkdir)(const char* path);
|
||||
int (*Rename)(const char* oldPath, const char* newPath);
|
||||
const char* (*GetLabel)();
|
||||
void* ctx;
|
||||
int (*Open)(void* ctx, const char* path);
|
||||
int (*Read)(void* ctx, int handle, uint8_t* buffer, uint64_t offset, uint64_t size);
|
||||
uint64_t (*GetSize)(void* ctx, int handle);
|
||||
void (*Close)(void* ctx, int handle);
|
||||
int (*ReadDir)(void* ctx, const char* path, const char** outNames, int maxEntries);
|
||||
int (*Write)(void* ctx, int handle, const uint8_t* buffer, uint64_t offset, uint64_t size);
|
||||
int (*Create)(void* ctx, const char* path);
|
||||
int (*Delete)(void* ctx, const char* path);
|
||||
int (*Mkdir)(void* ctx, const char* path);
|
||||
int (*Rename)(void* ctx, const char* oldPath, const char* newPath);
|
||||
const char* (*GetLabel)(void* ctx);
|
||||
// Optional: paginated directory read returning entries [startIndex, startIndex+maxEntries).
|
||||
// Drivers that leave this null are read via ReadDir at startIndex 0 only.
|
||||
int (*ReadDirAt)(const char* path, const char** outNames, int maxEntries, int startIndex);
|
||||
int (*ReadDirAt)(void* ctx, const char* path, const char** outNames, int maxEntries, int startIndex);
|
||||
// Optional: fill metadata for a path. Drivers that leave this null do
|
||||
// not support stat and VfsStat returns -1 for their paths.
|
||||
int (*Stat)(const char* path, StatInfo* out);
|
||||
int (*Stat)(void* ctx, const char* path, StatInfo* out);
|
||||
// Optional: release everything this mount owns, including the FsDriver
|
||||
// itself. Called either when registration fails, before the driver has
|
||||
// become reachable, or by UnregisterDrive once the slot is deactivated
|
||||
// and no dispatch can still be in flight. Drivers with statically
|
||||
// allocated state (e.g. the ramdisk) leave this null.
|
||||
void (*Unmount)(void* ctx);
|
||||
};
|
||||
|
||||
void Initialize();
|
||||
|
||||
Reference in New Issue
Block a user