feat: vfs - add dynamic filesystem mounts and safe unmount lifecycle

This commit is contained in:
2026-08-04 08:58:50 +02:00
parent b1c55073c7
commit ca5331c9db
12 changed files with 520 additions and 259 deletions
+1 -1
View File
@@ -12,4 +12,4 @@
#pragma once #pragma once
#define MONTAUK_BUILD_NUMBER 37 #define MONTAUK_BUILD_NUMBER 42
+31 -12
View File
@@ -54,19 +54,38 @@ namespace Fs {
return hasRamdisk; 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 = { Vfs::FsDriver g_ramdiskDriver = {
Ramdisk::Open, .ctx = nullptr,
Ramdisk::Read, .Open = RdOpen,
Ramdisk::GetSize, .Read = RdRead,
Ramdisk::Close, .GetSize = RdGetSize,
Ramdisk::ReadDir, .Close = RdClose,
Ramdisk::Write, .ReadDir = RdReadDir,
Ramdisk::Create, .Write = RdWrite,
Ramdisk::Delete, .Create = RdCreate,
Ramdisk::Mkdir, .Delete = RdDelete,
Ramdisk::Rename, .Mkdir = RdMkdir,
Ramdisk::GetLabel, .Rename = RdRename,
Ramdisk::ReadDirAt, .GetLabel = RdGetLabel,
.ReadDirAt = RdReadDirAt,
.Stat = nullptr,
// Statically allocated: nothing to release.
.Unmount = nullptr,
}; };
} }
+167 -66
View File
@@ -10,6 +10,7 @@
#include <Terminal/Terminal.hpp> #include <Terminal/Terminal.hpp>
#include <Libraries/Memory.hpp> #include <Libraries/Memory.hpp>
#include <Memory/PageFrameAllocator.hpp> #include <Memory/PageFrameAllocator.hpp>
#include <CppLib/Vector.hpp>
#include <Timekeeping/Time.hpp> #include <Timekeeping/Time.hpp>
using namespace Kt; using namespace Kt;
@@ -20,7 +21,6 @@ namespace Fs::Ext2 {
// Constants // Constants
// ========================================================================= // =========================================================================
static constexpr int MaxInstances = 8;
static constexpr int MaxFilesPerInstance = 16; static constexpr int MaxFilesPerInstance = 16;
static constexpr int MaxDirEntries = 128; static constexpr int MaxDirEntries = 128;
static constexpr int MaxNameLen = 256; static constexpr int MaxNameLen = 256;
@@ -148,6 +148,10 @@ namespace Fs::Ext2 {
int blockDevIndex; int blockDevIndex;
uint64_t partStartLba; 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 // Superblock fields
uint32_t blockSize; // 1024 << s_log_block_size uint32_t blockSize; // 1024 << s_log_block_size
uint32_t inodeSize; uint32_t inodeSize;
@@ -170,8 +174,11 @@ namespace Fs::Ext2 {
// Open file handles // Open file handles
Ext2File files[MaxFilesPerInstance]; Ext2File files[MaxFilesPerInstance];
// ReadDir name cache // ReadDir name cache. 32 KiB, allocated on the first listing rather
char dirNames[MaxDirEntries][MaxNameLen]; // than at mount, so volumes that are never enumerated don't pay for
// it. Released on unmount.
char (*dirNames)[MaxNameLen];
int dirNamesPages;
int dirNameCount; int dirNameCount;
}; };
@@ -179,8 +186,28 @@ namespace Fs::Ext2 {
// Instance table // Instance table
// ========================================================================= // =========================================================================
static Ext2Instance g_instances[MaxInstances] = {}; // One heap allocation per mounted volume. The table holds pointers rather
static int g_instanceCount = 0; // 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 // Low-level helpers
@@ -1168,7 +1195,7 @@ namespace Fs::Ext2 {
// ========================================================================= // =========================================================================
static int OpenImpl(int inst, const char* path) { 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]; auto& self = g_instances[inst];
uint32_t inodeNum; uint32_t inodeNum;
@@ -1191,7 +1218,7 @@ namespace Fs::Ext2 {
static int StatImpl(int inst, const char* path, Vfs::StatInfo* out) { static int StatImpl(int inst, const char* path, Vfs::StatInfo* out) {
if (!out) return -1; 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]; auto& self = g_instances[inst];
uint32_t inodeNum; uint32_t inodeNum;
@@ -1209,7 +1236,7 @@ namespace Fs::Ext2 {
static int ReadImpl(int inst, int handle, uint8_t* buffer, static int ReadImpl(int inst, int handle, uint8_t* buffer,
uint64_t offset, uint64_t size) { 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]; auto& self = g_instances[inst];
if (handle < 0 || handle >= MaxFilesPerInstance || !self.files[handle].inUse) return -1; 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) { 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]; auto& self = g_instances[inst];
if (handle < 0 || handle >= MaxFilesPerInstance || !self.files[handle].inUse) return 0; if (handle < 0 || handle >= MaxFilesPerInstance || !self.files[handle].inUse) return 0;
return self.files[handle].inode.i_size; return self.files[handle].inode.i_size;
} }
static void CloseImpl(int inst, int handle) { static void CloseImpl(int inst, int handle) {
if (inst < 0 || inst >= g_instanceCount) return; if (InstanceAt(inst) == nullptr) return;
auto& self = g_instances[inst]; auto& self = g_instances[inst];
if (handle < 0 || handle >= MaxFilesPerInstance) return; if (handle < 0 || handle >= MaxFilesPerInstance) return;
self.files[handle].inUse = false; 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, static int ReadDirImpl(int inst, const char* path,
const char** outNames, int maxEntries, int startIndex = 0) { 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]; auto& self = g_instances[inst];
uint32_t inodeNum; uint32_t inodeNum;
@@ -1273,6 +1314,8 @@ namespace Fs::Ext2 {
if (!TraversePath(self, path, &inodeNum, &inode)) return -1; if (!TraversePath(self, path, &inodeNum, &inode)) return -1;
if ((inode.i_mode & IMODE_TYPE_MASK) != IMODE_DIR) 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 limit = maxEntries < MaxDirEntries ? maxEntries : MaxDirEntries;
int count = ReadDirectoryNames(self, inode, self.dirNames, limit, startIndex); 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, static int WriteImpl(int inst, int handle, const uint8_t* buffer,
uint64_t offset, uint64_t size) { 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]; auto& self = g_instances[inst];
if (handle < 0 || handle >= MaxFilesPerInstance || !self.files[handle].inUse) return -1; 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) { 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]; auto& self = g_instances[inst];
char parentPath[MaxNameLen]; char parentPath[MaxNameLen];
@@ -1448,7 +1491,7 @@ namespace Fs::Ext2 {
} }
static int DeleteImpl(int inst, const char* path) { 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]; auto& self = g_instances[inst];
char parentPath[MaxNameLen]; char parentPath[MaxNameLen];
@@ -1508,7 +1551,7 @@ namespace Fs::Ext2 {
} }
static int MkdirImpl(int inst, const char* path) { 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]; auto& self = g_instances[inst];
char parentPath[MaxNameLen]; char parentPath[MaxNameLen];
@@ -1640,7 +1683,7 @@ namespace Fs::Ext2 {
// ========================================================================= // =========================================================================
static int RenameImpl(int inst, const char* oldPath, const char* newPath) { 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]; auto& self = g_instances[inst];
// Split old path // Split old path
@@ -1765,57 +1808,88 @@ namespace Fs::Ext2 {
// ========================================================================= // =========================================================================
static const char* GetLabelImpl(int inst) { 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; return g_instances[inst].volumeLabel[0] ? g_instances[inst].volumeLabel : nullptr;
} }
template<int N> struct Thunks { // ctx carries the instance index. Slots are never compacted, so the index
static int Open(const char* p) { return OpenImpl(N, p); } // stays valid until the mount is torn down.
static int Read(int h, uint8_t* b, uint64_t o, uint64_t s) { return ReadImpl(N, h, b, o, s); } static int CtxToInst(void* ctx) { return (int)(uintptr_t)ctx; }
static uint64_t GetSize(int h) { return GetSizeImpl(N, h); } static void* InstToCtx(int inst) { return (void*)(uintptr_t)inst; }
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); }
};
template<int N> static int DrvOpen(void* c, const char* p) { return OpenImpl(CtxToInst(c), p); }
static Vfs::FsDriver MakeDriver() { static int DrvRead(void* c, int h, uint8_t* b, uint64_t o, uint64_t s) { return ReadImpl(CtxToInst(c), h, b, o, s); }
return { static uint64_t DrvGetSize(void* c, int h) { return GetSizeImpl(CtxToInst(c), h); }
Thunks<N>::Open, static void DrvClose(void* c, int h) { CloseImpl(CtxToInst(c), h); }
Thunks<N>::Read, static int DrvReadDir(void* c, const char* p, const char** o, int m) { return ReadDirImpl(CtxToInst(c), p, o, m); }
Thunks<N>::GetSize, static int DrvReadDirAt(void* c, const char* p, const char** o, int m, int s) { return ReadDirImpl(CtxToInst(c), p, o, m, s); }
Thunks<N>::Close, 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); }
Thunks<N>::ReadDir, static int DrvCreate(void* c, const char* p) { return CreateImpl(CtxToInst(c), p); }
Thunks<N>::Write, static int DrvDelete(void* c, const char* p) { return DeleteImpl(CtxToInst(c), p); }
Thunks<N>::Create, static int DrvMkdir(void* c, const char* p) { return MkdirImpl(CtxToInst(c), p); }
Thunks<N>::Delete, static int DrvRename(void* c, const char* o, const char* n) { return RenameImpl(CtxToInst(c), o, n); }
Thunks<N>::Mkdir, static const char* DrvGetLabel(void* c) { return GetLabelImpl(CtxToInst(c)); }
Thunks<N>::Rename, static int DrvStat(void* c, const char* p, Vfs::StatInfo* o) { return StatImpl(CtxToInst(c), p, o); }
Thunks<N>::GetLabel,
Thunks<N>::ReadDirAt, // Release everything the mount owns. The driver is either not registered
Thunks<N>::Stat, // 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);
} }
static Vfs::FsDriver g_drivers[] = { if (self->dirNames != nullptr && self->dirNamesPages > 0) {
MakeDriver<0>(), MakeDriver<1>(), MakeDriver<2>(), MakeDriver<3>(), Memory::g_pfa->Free(self->dirNames, self->dirNamesPages);
MakeDriver<4>(), MakeDriver<5>(), MakeDriver<6>(), MakeDriver<7>(), }
};
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);
}
// ========================================================================= // =========================================================================
// Superblock validation and mount // Superblock validation and mount
// ========================================================================= // =========================================================================
Vfs::FsDriver* Mount(int blockDevIndex, uint64_t startLba, uint64_t sectorCount) { // Claim the lowest free instance slot, extending the table if all are in
if (g_instanceCount >= MaxInstances) return nullptr; // 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); auto* dev = Drivers::Storage::GetBlockDevice(blockDevIndex);
if (!dev) return nullptr; if (!dev) return nullptr;
@@ -1865,9 +1939,23 @@ namespace Fs::Ext2 {
/ sb->s_blocks_per_group; / sb->s_blocks_per_group;
if (groupCount == 0) return nullptr; if (groupCount == 0) return nullptr;
// Success — initialize instance // Success — allocate and initialize the instance
int idx = g_instanceCount; int idx = AllocateInstanceSlot();
auto& inst = g_instances[idx]; 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.active = true;
inst.blockDevIndex = blockDevIndex; inst.blockDevIndex = blockDevIndex;
@@ -1900,8 +1988,7 @@ namespace Fs::Ext2 {
nullptr, inst.blockBufPages); nullptr, inst.blockBufPages);
} }
if (!inst.blockBuf) { if (!inst.blockBuf) {
inst.active = false; return AbortMount(idx);
return nullptr;
} }
// Load block group descriptor table // Load block group descriptor table
@@ -1918,8 +2005,7 @@ namespace Fs::Ext2 {
} }
if (!inst.bgdt) { if (!inst.bgdt) {
inst.active = false; return AbortMount(idx);
return nullptr;
} }
// Read BGDT blocks // Read BGDT blocks
@@ -1927,8 +2013,7 @@ namespace Fs::Ext2 {
uint8_t* dst = (uint8_t*)inst.bgdt; uint8_t* dst = (uint8_t*)inst.bgdt;
for (uint32_t b = 0; b < bgdtBlocks; b++) { for (uint32_t b = 0; b < bgdtBlocks; b++) {
if (!ReadBlock(inst, bgdtStartBlock + b, inst.blockBuf)) { if (!ReadBlock(inst, bgdtStartBlock + b, inst.blockBuf)) {
inst.active = false; return AbortMount(idx);
return nullptr;
} }
uint32_t copyLen = bgdtBytes - b * blockSize; uint32_t copyLen = bgdtBytes - b * blockSize;
if (copyLen > blockSize) copyLen = blockSize; if (copyLen > blockSize) copyLen = blockSize;
@@ -1940,13 +2025,29 @@ namespace Fs::Ext2 {
inst.files[i].inUse = false; 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 \"" KernelLogStream(OK, "Ext2") << "Mounted volume \""
<< inst.volumeLabel << "\" (" << inst.totalBlocks << " blocks, " << inst.volumeLabel << "\" (" << inst.totalBlocks << " blocks, "
<< blockSize << " bytes/block, " << groupCount << " groups)"; << blockSize << " bytes/block, " << groupCount << " groups)";
return &g_drivers[idx]; return driver;
} }
void RegisterProbe() { void RegisterProbe() {
+149 -58
View File
@@ -10,6 +10,7 @@
#include <Terminal/Terminal.hpp> #include <Terminal/Terminal.hpp>
#include <Libraries/Memory.hpp> #include <Libraries/Memory.hpp>
#include <Memory/PageFrameAllocator.hpp> #include <Memory/PageFrameAllocator.hpp>
#include <CppLib/Vector.hpp>
using namespace Kt; using namespace Kt;
@@ -19,7 +20,6 @@ namespace Fs::Fat32 {
// Constants // Constants
// ========================================================================= // =========================================================================
static constexpr int MaxInstances = 8;
static constexpr int MaxFilesPerInstance = 16; static constexpr int MaxFilesPerInstance = 16;
static constexpr int MaxDirEntries = 128; static constexpr int MaxDirEntries = 128;
static constexpr int MaxNameLen = 256; static constexpr int MaxNameLen = 256;
@@ -60,6 +60,10 @@ namespace Fs::Fat32 {
int blockDevIndex; int blockDevIndex;
uint64_t partStartLba; 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 // BPB fields
uint16_t bytesPerSector; uint16_t bytesPerSector;
uint8_t sectorsPerCluster; uint8_t sectorsPerCluster;
@@ -87,8 +91,11 @@ namespace Fs::Fat32 {
// Open file handles // Open file handles
Fat32File files[MaxFilesPerInstance]; Fat32File files[MaxFilesPerInstance];
// ReadDir name cache // ReadDir name cache. 32 KiB, allocated on the first listing rather
char dirNames[MaxDirEntries][MaxNameLen]; // than at mount, so volumes that are never enumerated don't pay for
// it. Released on unmount.
char (*dirNames)[MaxNameLen];
int dirNamesPages;
int dirNameCount; int dirNameCount;
}; };
@@ -106,8 +113,28 @@ namespace Fs::Fat32 {
// Instance table // Instance table
// ========================================================================= // =========================================================================
static Fat32Instance g_instances[MaxInstances] = {}; // One heap allocation per mounted volume. The table holds pointers rather
static int g_instanceCount = 0; // 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 // Low-level helpers
@@ -902,7 +929,7 @@ namespace Fs::Fat32 {
// ========================================================================= // =========================================================================
static int OpenImpl(int inst, const char* path) { 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; ParsedEntry entry;
if (!TraversePath(inst, path, &entry)) return -1; if (!TraversePath(inst, path, &entry)) return -1;
@@ -928,7 +955,7 @@ namespace Fs::Fat32 {
static int ReadImpl(int inst, int handle, uint8_t* buffer, static int ReadImpl(int inst, int handle, uint8_t* buffer,
uint64_t offset, uint64_t size) { 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]; auto& self = g_instances[inst];
if (handle < 0 || handle >= MaxFilesPerInstance || !self.files[handle].inUse) return -1; 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) { 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]; auto& self = g_instances[inst];
if (handle < 0 || handle >= MaxFilesPerInstance || !self.files[handle].inUse) return 0; if (handle < 0 || handle >= MaxFilesPerInstance || !self.files[handle].inUse) return 0;
return self.files[handle].fileSize; return self.files[handle].fileSize;
} }
static void CloseImpl(int inst, int handle) { static void CloseImpl(int inst, int handle) {
if (inst < 0 || inst >= g_instanceCount) return; if (InstanceAt(inst) == nullptr) return;
auto& self = g_instances[inst]; auto& self = g_instances[inst];
if (handle < 0 || handle >= MaxFilesPerInstance) return; if (handle < 0 || handle >= MaxFilesPerInstance) return;
self.files[handle].inUse = false; 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, static int ReadDirImpl(int inst, const char* path,
const char** outNames, int maxEntries, int startIndex = 0) { 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]; auto& self = g_instances[inst];
ParsedEntry dirEntry; ParsedEntry dirEntry;
if (!TraversePath(inst, path, &dirEntry)) return -1; if (!TraversePath(inst, path, &dirEntry)) return -1;
if (!(dirEntry.attributes & ATTR_DIRECTORY)) return -1; if (!(dirEntry.attributes & ATTR_DIRECTORY)) return -1;
if (!EnsureDirNames(self)) return -1;
int limit = maxEntries < MaxDirEntries ? maxEntries : MaxDirEntries; int limit = maxEntries < MaxDirEntries ? maxEntries : MaxDirEntries;
int count = ReadDirectoryNames(inst, dirEntry.firstCluster, self.dirNames, limit, startIndex); 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, static int WriteImpl(int inst, int handle, const uint8_t* buffer,
uint64_t offset, uint64_t size) { 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]; auto& self = g_instances[inst];
if (handle < 0 || handle >= MaxFilesPerInstance || !self.files[handle].inUse) return -1; 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) { 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]; auto& self = g_instances[inst];
// Split path into parent directory and filename // Split path into parent directory and filename
@@ -1274,7 +1317,7 @@ namespace Fs::Fat32 {
} }
static int DeleteImpl(int inst, const char* path) { 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]; auto& self = g_instances[inst];
// Split path into parent directory and filename // Split path into parent directory and filename
@@ -1410,7 +1453,7 @@ namespace Fs::Fat32 {
// ========================================================================= // =========================================================================
static int MkdirImpl(int inst, const char* path) { 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]; auto& self = g_instances[inst];
// Split path into parent directory and new dir name // 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) { 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]; auto& self = g_instances[inst];
// Split old path // Split old path
@@ -1754,58 +1797,72 @@ namespace Fs::Fat32 {
// ========================================================================= // =========================================================================
static const char* GetLabelImpl(int inst) { 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; const char* label = g_instances[inst].volumeLabel;
if (label[0] == '\0' || StrEqualNoCase(label, "NO NAME")) return nullptr; if (label[0] == '\0' || StrEqualNoCase(label, "NO NAME")) return nullptr;
return label; return label;
} }
template<int N> struct Thunks { // ctx carries the instance index. Slots are never compacted, so the index
static int Open(const char* p) { return OpenImpl(N, p); } // stays valid until the mount is torn down.
static int Read(int h, uint8_t* b, uint64_t o, uint64_t s) { return ReadImpl(N, h, b, o, s); } static int CtxToInst(void* ctx) { return (int)(uintptr_t)ctx; }
static uint64_t GetSize(int h) { return GetSizeImpl(N, h); } static void* InstToCtx(int inst) { return (void*)(uintptr_t)inst; }
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); }
};
template<int N> static int DrvOpen(void* c, const char* p) { return OpenImpl(CtxToInst(c), p); }
static Vfs::FsDriver MakeDriver() { static int DrvRead(void* c, int h, uint8_t* b, uint64_t o, uint64_t s) { return ReadImpl(CtxToInst(c), h, b, o, s); }
return { static uint64_t DrvGetSize(void* c, int h) { return GetSizeImpl(CtxToInst(c), h); }
Thunks<N>::Open, static void DrvClose(void* c, int h) { CloseImpl(CtxToInst(c), h); }
Thunks<N>::Read, static int DrvReadDir(void* c, const char* p, const char** o, int m) { return ReadDirImpl(CtxToInst(c), p, o, m); }
Thunks<N>::GetSize, static int DrvReadDirAt(void* c, const char* p, const char** o, int m, int s) { return ReadDirImpl(CtxToInst(c), p, o, m, s); }
Thunks<N>::Close, 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); }
Thunks<N>::ReadDir, static int DrvCreate(void* c, const char* p) { return CreateImpl(CtxToInst(c), p); }
Thunks<N>::Write, static int DrvDelete(void* c, const char* p) { return DeleteImpl(CtxToInst(c), p); }
Thunks<N>::Create, static int DrvMkdir(void* c, const char* p) { return MkdirImpl(CtxToInst(c), p); }
Thunks<N>::Delete, static int DrvRename(void* c, const char* o, const char* n) { return RenameImpl(CtxToInst(c), o, n); }
Thunks<N>::Mkdir, static const char* DrvGetLabel(void* c) { return GetLabelImpl(CtxToInst(c)); }
Thunks<N>::Rename,
Thunks<N>::GetLabel, // Release everything the mount owns. The driver is either not registered
Thunks<N>::ReadDirAt, // 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);
} }
static Vfs::FsDriver g_drivers[] = { if (self->dirNames != nullptr && self->dirNamesPages > 0) {
MakeDriver<0>(), MakeDriver<1>(), MakeDriver<2>(), MakeDriver<3>(), Memory::g_pfa->Free(self->dirNames, self->dirNamesPages);
MakeDriver<4>(), MakeDriver<5>(), MakeDriver<6>(), MakeDriver<7>(), }
};
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);
}
// ========================================================================= // =========================================================================
// BPB validation and mount // BPB validation and mount
// ========================================================================= // =========================================================================
Vfs::FsDriver* Mount(int blockDevIndex, uint64_t startLba, uint64_t sectorCount) { // Claim the lowest free instance slot, extending the table if all are in
if (g_instanceCount >= MaxInstances) return nullptr; // 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); auto* dev = Drivers::Storage::GetBlockDevice(blockDevIndex);
if (!dev) return nullptr; if (!dev) return nullptr;
@@ -1873,9 +1930,23 @@ namespace Fs::Fat32 {
// At least one of: valid cluster count or FS type string // At least one of: valid cluster count or FS type string
if (!hasFat32Str && clusterCount < 65525) return nullptr; if (!hasFat32Str && clusterCount < 65525) return nullptr;
// Success — initialize instance // Success — allocate and initialize the instance
int idx = g_instanceCount; int idx = AllocateInstanceSlot();
auto& inst = g_instances[idx]; 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.active = true;
inst.blockDevIndex = blockDevIndex; inst.blockDevIndex = blockDevIndex;
@@ -1924,8 +1995,12 @@ namespace Fs::Fat32 {
uint32_t chunk = (remaining > 4096) ? 4096 : (uint32_t)remaining; uint32_t chunk = (remaining > 4096) ? 4096 : (uint32_t)remaining;
uint32_t secs = (chunk + bytesPerSector - 1) / bytesPerSector; uint32_t secs = (chunk + bytesPerSector - 1) / bytesPerSector;
if (!ReadPartSectors(inst, fatPartSector, secs, dst)) { 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.fatCache = nullptr;
inst.fatCachePages = 0;
inst.fatCacheEntries = 0;
break; break;
} }
dst += secs * bytesPerSector; dst += secs * bytesPerSector;
@@ -1939,13 +2014,29 @@ namespace Fs::Fat32 {
inst.files[i].inUse = false; 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 \"" KernelLogStream(OK, "FAT32") << "Mounted volume \""
<< inst.volumeLabel << "\" (" << clusterCount << " clusters, " << inst.volumeLabel << "\" (" << clusterCount << " clusters, "
<< (uint64_t)inst.clusterSize << " bytes/cluster)"; << (uint64_t)inst.clusterSize << " bytes/cluster)";
return &g_drivers[idx]; return driver;
} }
void RegisterProbe() { void RegisterProbe() {
+11
View File
@@ -17,6 +17,15 @@ namespace Fs::FsProbe {
static bool g_mounted[Drivers::Storage::Gpt::MaxPartitions] = {}; static bool g_mounted[Drivers::Storage::Gpt::MaxPartitions] = {};
static int g_driveForPart[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) { void Register(ProbeFn fn) {
if (g_probeCount < MaxProbes && fn) { if (g_probeCount < MaxProbes && fn) {
g_probes[g_probeCount++] = fn; g_probes[g_probeCount++] = fn;
@@ -60,6 +69,7 @@ namespace Fs::FsProbe {
return 1; return 1;
} }
DiscardUnregisteredDriver(driver);
return -1; return -1;
} }
@@ -135,6 +145,7 @@ namespace Fs::FsProbe {
<< partIndex << " as drive " << driveNum; << partIndex << " as drive " << driveNum;
return 0; return 0;
} }
DiscardUnregisteredDriver(driver);
return -1; return -1;
} }
} }
+86 -90
View File
@@ -19,6 +19,11 @@ namespace Fs::Vfs {
// flips driveActive so an interrupted dispatch never sees a null driver. // flips driveActive so an interrupted dispatch never sees a null driver.
static kcp::Mutex vfsLock; 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. // Parse "N:/path" into drive number and local path.
// Returns true on success, sets outDrive and outPath. // Returns true on success, sets outDrive and outPath.
static bool ParsePath(const char* path, int& outDrive, const char*& outPath) { static bool ParsePath(const char* path, int& outDrive, const char*& outPath) {
@@ -27,15 +32,15 @@ namespace Fs::Vfs {
// Parse decimal drive number before ':' // Parse decimal drive number before ':'
int drive = 0; int drive = 0;
int i = 0; int i = 0;
bool hasDigit = false; int digits = 0;
while (path[i] >= '0' && path[i] <= '9') { while (path[i] >= '0' && path[i] <= '9') {
if (++digits > MaxDriveDigits) return false;
drive = drive * 10 + (path[i] - '0'); drive = drive * 10 + (path[i] - '0');
hasDigit = true;
i++; i++;
} }
if (!hasDigit) return false; if (digits == 0) return false;
if (path[i] != ':') return false; if (path[i] != ':') return false;
// Everything after "N:" is the local path // Everything after "N:" is the local path
@@ -44,6 +49,27 @@ namespace Fs::Vfs {
return true; 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) { static void BumpDriveGeneration(int driveNumber) {
driveGeneration[driveNumber]++; driveGeneration[driveNumber]++;
if (driveGeneration[driveNumber] == 0) { if (driveGeneration[driveNumber] == 0) {
@@ -95,7 +121,16 @@ namespace Fs::Vfs {
return -1; return -1;
} }
driveActive[driveNumber] = false; driveActive[driveNumber] = false;
FsDriver* driver = driveTable[driveNumber];
driveTable[driveNumber] = nullptr;
BumpDriveGeneration(driveNumber); 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(); vfsLock.Release();
Kt::KernelLogStream(Kt::OK, "VFS") << "Unregistered drive " << driveNumber; Kt::KernelLogStream(Kt::OK, "VFS") << "Unregistered drive " << driveNumber;
@@ -135,12 +170,15 @@ namespace Fs::Vfs {
const char* localPath; const char* localPath;
if (!ParsePath(path, drive, localPath)) return -1; if (!ParsePath(path, drive, localPath)) return -1;
if (drive < 0 || drive >= MaxDrives || !driveActive[drive] || driveTable[drive] == nullptr) return -1;
vfsLock.Acquire(); 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]; uint32_t generation = driveGeneration[drive];
int localHandle = (driveActive[drive] && driver) ? driver->Open(localPath) : -1; int localHandle = driver->Open(driver->ctx, localPath);
vfsLock.Release(); vfsLock.Release();
if (localHandle < 0) return -1; 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) { int ReadBackendFile(const BackendFile& file, uint8_t* buffer, uint64_t offset, uint64_t size) {
vfsLock.Acquire(); vfsLock.Acquire();
FsDriver* driver = (file.driveNumber >= 0 && file.driveNumber < MaxDrives) FsDriver* driver = DriverForFileLocked(file);
? driveTable[file.driveNumber] if (driver == nullptr || driver->Read == nullptr) {
: nullptr;
if (file.driveNumber < 0 || file.driveNumber >= MaxDrives || !driveActive[file.driveNumber] ||
driver == nullptr ||
file.generation != driveGeneration[file.driveNumber] ||
file.localHandle < 0) {
vfsLock.Release(); vfsLock.Release();
return -1; return -1;
} }
int result = driver->Read(file.localHandle, buffer, offset, size); int result = driver->Read(driver->ctx, file.localHandle, buffer, offset, size);
vfsLock.Release(); vfsLock.Release();
return result; return result;
} }
uint64_t GetBackendFileSize(const BackendFile& file) { uint64_t GetBackendFileSize(const BackendFile& file) {
vfsLock.Acquire(); vfsLock.Acquire();
FsDriver* driver = (file.driveNumber >= 0 && file.driveNumber < MaxDrives) FsDriver* driver = DriverForFileLocked(file);
? driveTable[file.driveNumber] if (driver == nullptr || driver->GetSize == nullptr) {
: nullptr;
if (file.driveNumber < 0 || file.driveNumber >= MaxDrives || !driveActive[file.driveNumber] ||
driver == nullptr ||
file.generation != driveGeneration[file.driveNumber] ||
file.localHandle < 0) {
vfsLock.Release(); vfsLock.Release();
return 0; return 0;
} }
uint64_t result = driver->GetSize(file.localHandle); uint64_t result = driver->GetSize(driver->ctx, file.localHandle);
vfsLock.Release(); vfsLock.Release();
return result; return result;
} }
bool BackendFileCanWrite(const BackendFile& file) { bool BackendFileCanWrite(const BackendFile& file) {
vfsLock.Acquire(); vfsLock.Acquire();
FsDriver* driver = (file.driveNumber >= 0 && file.driveNumber < MaxDrives) FsDriver* driver = DriverForFileLocked(file);
? driveTable[file.driveNumber] bool canWrite = driver != nullptr && driver->Write != nullptr;
: 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(); vfsLock.Release();
return canWrite; return canWrite;
} }
void CloseBackendFile(BackendFile& file) { void CloseBackendFile(BackendFile& file) {
vfsLock.Acquire(); vfsLock.Acquire();
FsDriver* driver = (file.driveNumber >= 0 && file.driveNumber < MaxDrives) FsDriver* driver = DriverForFileLocked(file);
? driveTable[file.driveNumber] if (driver != nullptr && driver->Close != nullptr) {
: nullptr; driver->Close(driver->ctx, file.localHandle);
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(); vfsLock.Release();
file.driveNumber = -1; 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) { int WriteBackendFile(const BackendFile& file, const uint8_t* buffer, uint64_t offset, uint64_t size) {
vfsLock.Acquire(); vfsLock.Acquire();
FsDriver* driver = (file.driveNumber >= 0 && file.driveNumber < MaxDrives) FsDriver* driver = DriverForFileLocked(file);
? driveTable[file.driveNumber] if (driver == nullptr || driver->Write == nullptr) {
: nullptr;
if (file.driveNumber < 0 || file.driveNumber >= MaxDrives || !driveActive[file.driveNumber] ||
driver == nullptr ||
file.generation != driveGeneration[file.driveNumber] ||
file.localHandle < 0) {
vfsLock.Release(); vfsLock.Release();
return -1; return -1;
} }
if (driver->Write == nullptr) { vfsLock.Release(); return -1; } int result = driver->Write(driver->ctx, file.localHandle, buffer, offset, size);
int result = driver->Write(file.localHandle, buffer, offset, size);
vfsLock.Release(); vfsLock.Release();
return result; return result;
} }
@@ -253,15 +257,15 @@ namespace Fs::Vfs {
const char* localPath; const char* localPath;
if (!ParsePath(path, drive, localPath)) return -1; 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(); 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]; uint32_t generation = driveGeneration[drive];
int localHandle = (driveActive[drive] && driver && driver->Create) int localHandle = driver->Create(driver->ctx, localPath);
? driver->Create(localPath)
: -1;
vfsLock.Release(); vfsLock.Release();
if (localHandle < 0) return -1; if (localHandle < 0) return -1;
@@ -276,13 +280,11 @@ namespace Fs::Vfs {
const char* localPath; const char* localPath;
if (!ParsePath(path, drive, localPath)) return -1; 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(); vfsLock.Acquire();
FsDriver* driver = driveTable[drive]; FsDriver* driver = DriverForLocked(drive);
int result = (driveActive[drive] && driver && driver->Delete) int result = (driver != nullptr && driver->Delete != nullptr)
? driver->Delete(localPath) ? driver->Delete(driver->ctx, localPath)
: -1; : -1;
vfsLock.Release(); vfsLock.Release();
return result; return result;
@@ -295,13 +297,11 @@ namespace Fs::Vfs {
out = StatInfo{}; out = StatInfo{};
if (!ParsePath(path, drive, localPath)) return -1; 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(); vfsLock.Acquire();
FsDriver* driver = driveTable[drive]; FsDriver* driver = DriverForLocked(drive);
int result = (driveActive[drive] && driver && driver->Stat) int result = (driver != nullptr && driver->Stat != nullptr)
? driver->Stat(localPath, &out) ? driver->Stat(driver->ctx, localPath, &out)
: -1; : -1;
vfsLock.Release(); vfsLock.Release();
return result; return result;
@@ -312,19 +312,19 @@ namespace Fs::Vfs {
const char* localPath; const char* localPath;
if (!ParsePath(path, drive, localPath)) return -1; 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(); vfsLock.Acquire();
FsDriver* driver = driveTable[drive]; FsDriver* driver = DriverForLocked(drive);
int result = (driveActive[drive] && driver && driver->Mkdir) int result = (driver != nullptr && driver->Mkdir != nullptr)
? driver->Mkdir(localPath) ? driver->Mkdir(driver->ctx, localPath)
: -1; : -1;
vfsLock.Release(); vfsLock.Release();
return result; return result;
} }
int VfsDriveList(int* outDrives, int maxEntries) { int VfsDriveList(int* outDrives, int maxEntries) {
if (outDrives == nullptr || maxEntries <= 0) return 0;
vfsLock.Acquire(); vfsLock.Acquire();
int count = 0; int count = 0;
for (int i = 0; i < MaxDrives && count < maxEntries; i++) { for (int i = 0; i < MaxDrives && count < maxEntries; i++) {
@@ -341,18 +341,18 @@ namespace Fs::Vfs {
outLabel[0] = '\0'; outLabel[0] = '\0';
vfsLock.Acquire(); vfsLock.Acquire();
if (driveNumber < 0 || driveNumber >= MaxDrives || !driveActive[driveNumber] || FsDriver* driver = DriverForLocked(driveNumber);
driveTable[driveNumber] == nullptr) { if (driver == nullptr) {
vfsLock.Release(); vfsLock.Release();
return -1; return -1;
} }
if (driveTable[driveNumber]->GetLabel == nullptr) { if (driver->GetLabel == nullptr) {
vfsLock.Release(); vfsLock.Release();
return 0; return 0;
} }
const char* label = driveTable[driveNumber]->GetLabel(); const char* label = driver->GetLabel(driver->ctx);
if (label == nullptr || label[0] == '\0') { if (label == nullptr || label[0] == '\0') {
vfsLock.Release(); vfsLock.Release();
return 0; return 0;
@@ -379,14 +379,11 @@ namespace Fs::Vfs {
// Cross-drive rename not supported // Cross-drive rename not supported
if (oldDrive != newDrive) return -1; 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(); vfsLock.Acquire();
FsDriver* driver = driveTable[oldDrive]; FsDriver* driver = DriverForLocked(oldDrive);
int result = (driveActive[oldDrive] && driver && driver->Rename) int result = (driver != nullptr && driver->Rename != nullptr)
? driver->Rename(oldLocal, newLocal) ? driver->Rename(driver->ctx, oldLocal, newLocal)
: -1; : -1;
vfsLock.Release(); vfsLock.Release();
return result; return result;
@@ -402,18 +399,17 @@ namespace Fs::Vfs {
if (startIndex < 0) return -1; if (startIndex < 0) return -1;
if (!ParsePath(path, drive, localPath)) return -1; if (!ParsePath(path, drive, localPath)) return -1;
if (drive < 0 || drive >= MaxDrives || !driveActive[drive] || driveTable[drive] == nullptr) return -1;
vfsLock.Acquire(); vfsLock.Acquire();
FsDriver* driver = driveTable[drive]; FsDriver* driver = DriverForLocked(drive);
int result; int result;
if (!driveActive[drive] || !driver) { if (driver == nullptr) {
result = -1; result = -1;
} else if (driver->ReadDirAt) { } else if (driver->ReadDirAt) {
result = driver->ReadDirAt(localPath, outNames, maxEntries, startIndex); result = driver->ReadDirAt(driver->ctx, localPath, outNames, maxEntries, startIndex);
} else if (driver->ReadDir) { } else if (driver->ReadDir) {
// Driver without pagination support: only the first page is reachable. // 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 { } else {
result = -1; result = -1;
} }
+32 -14
View File
@@ -10,7 +10,15 @@
namespace Fs::Vfs { 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 { struct BackendFile {
int driveNumber; int driveNumber;
@@ -29,24 +37,34 @@ namespace Fs::Vfs {
bool isDir; // true if the entry is a directory 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 { struct FsDriver {
int (*Open)(const char* path); void* ctx;
int (*Read)(int handle, uint8_t* buffer, uint64_t offset, uint64_t size); int (*Open)(void* ctx, const char* path);
uint64_t (*GetSize)(int handle); int (*Read)(void* ctx, int handle, uint8_t* buffer, uint64_t offset, uint64_t size);
void (*Close)(int handle); uint64_t (*GetSize)(void* ctx, int handle);
int (*ReadDir)(const char* path, const char** outNames, int maxEntries); void (*Close)(void* ctx, int handle);
int (*Write)(int handle, const uint8_t* buffer, uint64_t offset, uint64_t size); int (*ReadDir)(void* ctx, const char* path, const char** outNames, int maxEntries);
int (*Create)(const char* path); int (*Write)(void* ctx, int handle, const uint8_t* buffer, uint64_t offset, uint64_t size);
int (*Delete)(const char* path); int (*Create)(void* ctx, const char* path);
int (*Mkdir)(const char* path); int (*Delete)(void* ctx, const char* path);
int (*Rename)(const char* oldPath, const char* newPath); int (*Mkdir)(void* ctx, const char* path);
const char* (*GetLabel)(); int (*Rename)(void* ctx, const char* oldPath, const char* newPath);
const char* (*GetLabel)(void* ctx);
// Optional: paginated directory read returning entries [startIndex, startIndex+maxEntries). // Optional: paginated directory read returning entries [startIndex, startIndex+maxEntries).
// Drivers that leave this null are read via ReadDir at startIndex 0 only. // 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 // Optional: fill metadata for a path. Drivers that leave this null do
// not support stat and VfsStat returns -1 for their paths. // 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(); void Initialize();
@@ -11,7 +11,8 @@
namespace filemanager { namespace filemanager {
inline constexpr int FM_MAX_DRIVES = 16; // Matches Fs::Vfs::MaxDrives; sizes the buffer passed to montauk::drivelist().
inline constexpr int FM_MAX_DRIVES = 64;
enum FileManagerEntryType : int { enum FileManagerEntryType : int {
FM_ENTRY_FILE = 0, FM_ENTRY_FILE = 0,
@@ -238,18 +238,12 @@ void filemanager_read_drives(FileManagerState* fm) {
for (int di = 0; di < driveCount; di++) { for (int di = 0; di < driveCount; di++) {
int d = drives[di]; int d = drives[di];
char probe[8]; char probe[8];
if (d < 10) { int pi = 0;
probe[0] = '0' + d; if (d >= 10) probe[pi++] = (char)('0' + (d / 10));
probe[1] = ':'; probe[pi++] = (char)('0' + (d % 10));
probe[2] = '/'; probe[pi++] = ':';
probe[3] = '\0'; probe[pi++] = '/';
} else { probe[pi] = '\0';
probe[0] = '1';
probe[1] = '0' + (d - 10);
probe[2] = ':';
probe[3] = '/';
probe[4] = '\0';
}
char label[64]; char label[64];
montauk::strcpy(label, "Drive "); montauk::strcpy(label, "Drive ");
str_append(label, probe, 64); str_append(label, probe, 64);
+4 -2
View File
@@ -42,9 +42,11 @@ constexpr int GRID_CELL_H = 80;
constexpr int GRID_ICON = 48; constexpr int GRID_ICON = 48;
constexpr int GRID_PAD = 4; constexpr int GRID_PAD = 4;
constexpr int MAX_ENTRIES = 64;
constexpr int MAX_HISTORY = 16; constexpr int MAX_HISTORY = 16;
constexpr int MAX_DRIVES = 16; // Matches Fs::Vfs::MaxDrives; sizes the buffer passed to montauk::drivelist().
constexpr int MAX_DRIVES = 64;
// The drives root can contain all drives plus the six special home folders.
constexpr int MAX_ENTRIES = MAX_DRIVES + 6;
constexpr int BUTTON_H = 30; constexpr int BUTTON_H = 30;
constexpr int BUTTON_W = 88; constexpr int BUTTON_W = 88;
+27 -1
View File
@@ -150,7 +150,33 @@ void do_mount_partition() {
int global_idx = part_indices[dt.selected_part]; int global_idx = part_indices[dt.selected_part];
int driveNum = 1 + global_idx; int driveNum = 1 + global_idx;
if (driveNum >= 16) driveNum = 15;
int mountedDrives[MAX_DRIVES];
int mountedCount = montauk::drivelist(mountedDrives, MAX_DRIVES);
bool occupied[MAX_DRIVES] = {};
for (int i = 0; i < mountedCount; i++) {
int mountedDrive = mountedDrives[i];
if (mountedDrive >= 0 && mountedDrive < MAX_DRIVES) {
occupied[mountedDrive] = true;
}
}
// Preserve the stable partition-to-drive mapping when it is available.
// Otherwise use the lowest free non-ramdisk drive in the full namespace.
if (driveNum >= MAX_DRIVES || occupied[driveNum]) {
driveNum = -1;
for (int candidate = 1; candidate < MAX_DRIVES; candidate++) {
if (!occupied[candidate]) {
driveNum = candidate;
break;
}
}
}
if (driveNum < 0) {
set_status("Mount failed (no free drive slot)");
return;
}
int r = montauk::fs_mount(global_idx, driveNum); int r = montauk::fs_mount(global_idx, driveNum);
if (r < 0) { if (r < 0) {
+2
View File
@@ -32,6 +32,8 @@ static constexpr int MAP_H = 48;
static constexpr int MAP_PAD = 16; static constexpr int MAP_PAD = 16;
static constexpr int MAX_PARTS = 32; static constexpr int MAX_PARTS = 32;
static constexpr int MAX_DISKS = 32; static constexpr int MAX_DISKS = 32;
// Matches Fs::Vfs::MaxDrives; used for selecting a free mount target.
static constexpr int MAX_DRIVES = 64;
static constexpr int STATUS_H = 44; static constexpr int STATUS_H = 44;
static constexpr int TB_BTN_Y = 7; static constexpr int TB_BTN_Y = 7;