Files
nt-lite/code/drivers/fat32/fat32.c
T

1207 lines
45 KiB
C

// fat32.c - FAT32 filesystem driver
// Read + write support: file data, cluster allocation, directory entries.
//
// Performance improvements over the original:
//
// 1. FAT sector cache (FAT_CACHE_SLOTS write-back entries).
// FatEntry() and WriteFatEntry() go through the cache; the hot FAT
// region stays in RAM instead of being read from disk on every chain
// traversal step. A 32-slot cache covers 32 x (512/4) = 4096
// consecutive cluster entries — enough to keep the entire FAT of a
// small partition in memory, and to avoid repeated reads even on large
// ones.
//
// 2. Fat32ReadDir() cluster buffer cached in FAT32_DIR_CTX.
// The original re-read the same cluster from disk on every call
// (allocate -> read -> return one entry -> free). Now the buffer is
// allocated once in Fat32OpenDir, freed in Fat32CloseDir, and only
// re-read when the iteration moves to a new cluster.
//
// 3. AllocCluster() next-free hint.
// The search now starts from the cluster after the last allocation
// (gVol.NextFreeHint) and wraps around once, so repeated allocations
// on a fragmented volume don't rescan from sector 0 every time.
//
// 4. Contiguous-cluster run coalescing in Fat32Read() and Fat32Write().
// After fetching a cluster's FAT entry the code peeks ahead: if the
// next cluster is physically adjacent (cluster+1) it extends the run
// rather than issuing a separate ReadSectors/WriteSectors call. A
// freshly-written unfragmented file now transfers in one or a handful
// of large DMA bursts instead of SectorsPerCluster-sized slices.
//
// 5. FAT write batching via a dirty-sector cache.
// WriteFatEntry() marks modified FAT cache slots dirty instead of
// flushing immediately. FlushFatCache() (called from Fat32Close,
// Fat32Truncate, and Fat32Delete) writes each dirty slot once for
// every FAT copy, collapsing the 2x(read+write) per-entry cost of the
// original into a single write per dirty sector per FAT copy.
#include <stdint.h>
#include <stddef.h>
#include <string.h>
#include <io_driver.h>
#include <hal_api.h>
#include <io_file.h>
#include <mm_virt.h>
#include <ke_debug.h>
// ---------------------------------------------------------------------------
// On-disk structures
// ---------------------------------------------------------------------------
#pragma pack(push, 1)
typedef struct {
uint8_t Jump[3];
char OemId[8];
uint16_t BytesPerSector;
uint8_t SectorsPerCluster;
uint16_t ReservedSectors;
uint8_t NumFats;
uint16_t RootEntryCount;
uint16_t TotalSectors16;
uint8_t MediaType;
uint16_t FatSize16;
uint16_t SectorsPerTrack;
uint16_t NumHeads;
uint32_t HiddenSectors;
uint32_t TotalSectors32;
uint32_t FatSize32;
uint16_t ExtFlags;
uint16_t FsVersion;
uint32_t RootCluster;
uint16_t FsInfoSector;
uint16_t BackupBootSector;
uint8_t Reserved[12];
uint8_t DriveNumber;
uint8_t Reserved1;
uint8_t BootSignature;
uint32_t VolumeId;
char VolumeLabel[11];
char FsType[8];
} BPB;
typedef struct {
char Name[8];
char Ext[3];
uint8_t Attr;
uint8_t Reserved;
uint8_t CrtTimeTenth;
uint16_t CrtTime;
uint16_t CrtDate;
uint16_t LastAccDate;
uint16_t FirstClusterHi;
uint16_t WrtTime;
uint16_t WrtDate;
uint16_t FirstClusterLo;
uint32_t FileSize;
} DIRENT;
typedef struct {
uint8_t Order;
uint16_t Name1[5];
uint8_t Attr;
uint8_t Type;
uint8_t Checksum;
uint16_t Name2[6];
uint16_t FirstCluster;
uint16_t Name3[2];
} LFN_ENTRY;
typedef struct {
uint32_t Data1;
uint16_t Data2;
uint16_t Data3;
uint8_t Data4[8];
} EFI_GUID;
typedef struct {
char Signature[8];
uint32_t Revision;
uint32_t HeaderSize;
uint32_t HeaderCRC32;
uint32_t Reserved;
uint64_t MyLBA;
uint64_t AlternateLBA;
uint64_t FirstUsableLBA;
uint64_t LastUsableLBA;
EFI_GUID DiskGUID;
uint64_t PartitionEntryLBA;
uint32_t NumberOfPartitionEntries;
uint32_t SizeOfPartitionEntry;
uint32_t PartitionEntryArrayCRC32;
} GPT_HEADER;
typedef struct {
EFI_GUID PartitionTypeGUID;
EFI_GUID UniquePartitionGUID;
uint64_t StartingLBA;
uint64_t EndingLBA;
uint64_t Attributes;
uint16_t PartitionName[36];
} GPT_ENTRY;
#pragma pack(pop)
#define ATTR_DIRECTORY 0x10
#define ATTR_ARCHIVE 0x20
#define ATTR_LFN 0x0F
#define FAT32_EOC 0x0FFFFFF8u
#define FAT32_EOC_MARK 0x0FFFFFFFu
#define FAT32_FREE 0x00000000u
// ---------------------------------------------------------------------------
// FAT sector cache (fixes #1 and #5)
// ---------------------------------------------------------------------------
// 32 direct-mapped slots, indexed by (lba % FAT_CACHE_SLOTS).
// Write-back: WriteFatEntry modifies the in-cache bytes and marks the slot
// dirty. FlushFatCache() propagates dirty slots to every FAT copy at once,
// replacing the original per-entry 2x(read+write) pattern with a single
// write per dirty sector per FAT copy.
#define FAT_CACHE_SLOTS 32
typedef struct {
uint64_t lba;
uint8_t data[512];
bool valid;
bool dirty;
} FAT_CACHE_ENTRY;
static FAT_CACHE_ENTRY gFatCache[FAT_CACHE_SLOTS];
// FAT32_VOL forward declaration (needed by cache helpers).
typedef struct FAT32_VOL_S FAT32_VOL;
// Raw (non-cached) sector I/O — forward declarations.
static KSTATUS ReadSectorsRaw (FAT32_VOL *v, uint64_t lba, uint32_t count, void *buf);
static KSTATUS WriteSectorsRaw(FAT32_VOL *v, uint64_t lba, uint32_t count, const void *buf);
// Read one FAT sector through the cache.
// Returns a pointer into the cache buffer (valid until the next eviction).
static KSTATUS FatCacheRead(FAT32_VOL *v, uint64_t lba, uint8_t **out) {
uint32_t slot = (uint32_t)(lba % FAT_CACHE_SLOTS);
FAT_CACHE_ENTRY *e = &gFatCache[slot];
if (e->valid && e->lba == lba) {
*out = e->data;
return STATUS_SUCCESS;
}
// Evict current occupant: single-copy writeback on collision.
// The full multi-copy flush happens in FlushFatCache().
if (e->valid && e->dirty) {
WriteSectorsRaw(v, e->lba, 1, e->data);
e->dirty = false;
}
KSTATUS s = ReadSectorsRaw(v, lba, 1, e->data);
if (!KSUCCESS(s)) { e->valid = false; return s; }
e->lba = lba;
e->valid = true;
e->dirty = false;
*out = e->data;
return STATUS_SUCCESS;
}
static void FatCacheMarkDirty(uint64_t lba) {
uint32_t slot = (uint32_t)(lba % FAT_CACHE_SLOTS);
if (gFatCache[slot].valid && gFatCache[slot].lba == lba)
gFatCache[slot].dirty = true;
}
static void FatCacheInvalidate(void) {
for (int i = 0; i < FAT_CACHE_SLOTS; i++) {
gFatCache[i].valid = false;
gFatCache[i].dirty = false;
}
}
// FlushFatCache — write every dirty slot to all FAT copies, clear dirty flag.
// Defined after FAT32_VOL is fully declared (below).
static void FlushFatCache(FAT32_VOL *v);
// ---------------------------------------------------------------------------
// Driver state
// ---------------------------------------------------------------------------
struct FAT32_VOL_S {
uint8_t DriveIndex;
uint32_t PartitionLba;
uint32_t BytesPerSector;
uint32_t SectorsPerCluster;
uint32_t BytesPerCluster;
uint32_t ReservedSectors;
uint32_t NumFats;
uint32_t FatSize; // sectors per FAT copy
uint32_t RootCluster;
uint32_t DataStart; // absolute LBA of cluster 2
uint32_t TotalClusters; // usable data clusters
uint32_t NextFreeHint; // fix #3: start cluster for next free-scan
const HAL_API *Hal;
};
typedef struct {
uint32_t StartCluster;
uint32_t FileSize;
FAT32_VOL *Vol;
uint64_t DirentLba;
uint32_t DirentOffset;
} FAT32_FILE;
// ---------------------------------------------------------------------------
// Raw sector I/O (bypasses FAT cache; used for file data and cache eviction)
// ---------------------------------------------------------------------------
static KSTATUS ReadSectorsRaw(FAT32_VOL *v, uint64_t lba,
uint32_t count, void *buf) {
if (!v || !v->Hal || !v->Hal->Disk.ReadSectors)
return STATUS_INVALID_PARAMETER;
return v->Hal->Disk.ReadSectors(v->DriveIndex, lba, count, buf);
}
static KSTATUS WriteSectorsRaw(FAT32_VOL *v, uint64_t lba,
uint32_t count, const void *buf) {
if (!v || !v->Hal || !v->Hal->Disk.WriteSectors)
return STATUS_INVALID_PARAMETER;
return v->Hal->Disk.WriteSectors(v->DriveIndex, lba, count, buf);
}
// Convenience aliases for file-data I/O.
static KSTATUS ReadSectors (FAT32_VOL *v, uint64_t lba, uint32_t n, void *b) { return ReadSectorsRaw (v, lba, n, b); }
static KSTATUS WriteSectors(FAT32_VOL *v, uint64_t lba, uint32_t n, const void *b) { return WriteSectorsRaw(v, lba, n, b); }
static uint64_t ClusterToLba(FAT32_VOL *v, uint32_t cluster) {
return v->DataStart + (uint64_t)(cluster - 2) * v->SectorsPerCluster;
}
// ---------------------------------------------------------------------------
// FlushFatCache — needs full FAT32_VOL definition
// ---------------------------------------------------------------------------
static void FlushFatCache(FAT32_VOL *v) {
uint64_t fat0Start = (uint64_t)v->PartitionLba + v->ReservedSectors;
for (int i = 0; i < FAT_CACHE_SLOTS; i++) {
FAT_CACHE_ENTRY *e = &gFatCache[i];
if (!e->valid || !e->dirty) continue;
if (e->lba < fat0Start) continue;
uint64_t sectorIdx = e->lba - fat0Start;
if (sectorIdx >= v->FatSize) continue;
for (uint32_t f = 0; f < v->NumFats; f++) {
uint64_t lba = fat0Start + (uint64_t)f * v->FatSize + sectorIdx;
WriteSectorsRaw(v, lba, 1, e->data);
}
e->dirty = false;
}
}
// ---------------------------------------------------------------------------
// FAT read / write (through cache)
// ---------------------------------------------------------------------------
static uint32_t FatEntry(FAT32_VOL *v, uint32_t cluster) {
uint32_t fatOffset = cluster * 4;
uint64_t sector = (uint64_t)v->PartitionLba + v->ReservedSectors
+ fatOffset / v->BytesPerSector;
uint32_t offInSec = fatOffset % v->BytesPerSector;
uint8_t *sec;
if (!KSUCCESS(FatCacheRead(v, sector, &sec))) return 0;
return *(uint32_t *)(sec + offInSec) & 0x0FFFFFFF;
}
static KSTATUS WriteFatEntry(FAT32_VOL *v, uint32_t cluster, uint32_t value) {
uint32_t fatOffset = cluster * 4;
uint64_t sector = (uint64_t)v->PartitionLba + v->ReservedSectors
+ fatOffset / v->BytesPerSector;
uint32_t offInSec = fatOffset % v->BytesPerSector;
uint8_t *sec;
KSTATUS s = FatCacheRead(v, sector, &sec);
if (!KSUCCESS(s)) return s;
uint32_t existing = *(uint32_t *)(sec + offInSec);
*(uint32_t *)(sec + offInSec) = (existing & 0xF0000000u)
| (value & 0x0FFFFFFFu);
FatCacheMarkDirty(sector);
return STATUS_SUCCESS;
}
// ---------------------------------------------------------------------------
// Cluster allocation (fix #3: next-free hint + two-phase wrap-around scan)
// ---------------------------------------------------------------------------
static uint32_t AllocCluster(FAT32_VOL *v) {
if (v->TotalClusters == 0) return 0;
uint32_t total = v->TotalClusters;
uint32_t start = v->NextFreeHint;
if (start < 2 || start >= total + 2) start = 2;
for (uint32_t pass = 0; pass < 2; pass++) {
uint32_t lo = (pass == 0) ? start : 2;
uint32_t hi = (pass == 0) ? total+2 : start;
for (uint32_t c = lo; c < hi; c++) {
if (FatEntry(v, c) == FAT32_FREE) {
if (!KSUCCESS(WriteFatEntry(v, c, FAT32_EOC_MARK))) return 0;
v->NextFreeHint = c + 1;
// Zero the cluster (required for directory correctness).
uint8_t *zero = (uint8_t *)MmAllocatePool(v->BytesPerCluster);
if (zero) {
memset(zero, 0, v->BytesPerCluster);
WriteSectors(v, ClusterToLba(v, c), v->SectorsPerCluster, zero);
MmFreePool(zero);
}
return c;
}
}
}
return 0;
}
static uint32_t AppendCluster(FAT32_VOL *v, uint32_t lastCluster) {
uint32_t newCluster = AllocCluster(v);
if (!newCluster) return 0;
if (!KSUCCESS(WriteFatEntry(v, lastCluster, newCluster))) return 0;
return newCluster;
}
static uint32_t GetChainEnd(FAT32_VOL *v, uint32_t start, uint32_t *outCount) {
uint32_t cluster = start, prev = start, count = 0;
while (cluster >= 2 && cluster < FAT32_EOC) {
prev = cluster; count++;
cluster = FatEntry(v, cluster);
}
if (outCount) *outCount = count;
return prev;
}
static void FreeClusterChain(FAT32_VOL *v, uint32_t start) {
uint32_t cluster = start;
while (cluster >= 2 && cluster < FAT32_EOC) {
uint32_t next = FatEntry(v, cluster);
WriteFatEntry(v, cluster, FAT32_FREE);
if (cluster < v->NextFreeHint) v->NextFreeHint = cluster;
cluster = next;
}
}
// ---------------------------------------------------------------------------
// DIRENT update helpers
// ---------------------------------------------------------------------------
static KSTATUS DirentUpdateSize(FAT32_VOL *v, uint64_t lba,
uint32_t offset, uint32_t size) {
uint8_t *sec = (uint8_t *)MmAllocatePool(v->BytesPerSector);
if (!sec) return STATUS_NO_MEMORY;
KSTATUS s = ReadSectors(v, lba, 1, sec);
if (KSUCCESS(s)) {
((DIRENT *)(sec + offset))->FileSize = size;
s = WriteSectors(v, lba, 1, sec);
}
MmFreePool(sec);
return s;
}
static KSTATUS DirentUpdateFirstCluster(FAT32_VOL *v, uint64_t lba,
uint32_t offset, uint32_t cluster) {
uint8_t *sec = (uint8_t *)MmAllocatePool(v->BytesPerSector);
if (!sec) return STATUS_NO_MEMORY;
KSTATUS s = ReadSectors(v, lba, 1, sec);
if (KSUCCESS(s)) {
DIRENT *d = (DIRENT *)(sec + offset);
d->FirstClusterHi = (uint16_t)(cluster >> 16);
d->FirstClusterLo = (uint16_t)(cluster & 0xFFFF);
s = WriteSectors(v, lba, 1, sec);
}
MmFreePool(sec);
return s;
}
// ---------------------------------------------------------------------------
// 8.3 name matching / assembly
// ---------------------------------------------------------------------------
static int MatchName83(const char *dirName, const char *dirExt,
const char *want) {
char buf[13]; int n = 0;
for (int i = 0; i < 8 && dirName[i] != ' '; i++)
buf[n++] = (char)(dirName[i] >= 'A' && dirName[i] <= 'Z' ? dirName[i]+32 : dirName[i]);
if (dirExt[0] != ' ') {
buf[n++] = '.';
for (int i = 0; i < 3 && dirExt[i] != ' '; i++)
buf[n++] = (char)(dirExt[i] >= 'A' && dirExt[i] <= 'Z' ? dirExt[i]+32 : dirExt[i]);
}
buf[n] = '\0';
const char *p = buf;
while (*want && *p) {
char a = *p++, b = *want++;
if (a >= 'A' && a <= 'Z') a += 32;
if (b >= 'A' && b <= 'Z') b += 32;
if (a != b) return 0;
}
return *want == '\0' && *p == '\0';
}
static void Make83Name(const char *src, char name8[8], char ext3[3]) {
memset(name8, ' ', 8); memset(ext3, ' ', 3);
const char *dot = NULL;
for (const char *p = src; *p; p++) if (*p == '.') dot = p;
int ni = 0;
const char *end = dot ? dot : src + strlen(src);
for (const char *p = src; p < end && ni < 8; p++) {
char c = *p; if (c >= 'a' && c <= 'z') c -= 32; name8[ni++] = c;
}
if (dot) {
int ei = 0;
for (const char *p = dot+1; *p && ei < 3; p++) {
char c = *p; if (c >= 'a' && c <= 'z') c -= 32; ext3[ei++] = c;
}
}
}
// ---------------------------------------------------------------------------
// LFN assembly / matching
// ---------------------------------------------------------------------------
#define MAX_LFN 256
static void LfnAppend(char *lfn, const LFN_ENTRY *e) {
int seq = (e->Order & 0x3F) - 1;
if (seq < 0 || seq >= (MAX_LFN / 13)) return;
int pos = seq * 13;
uint16_t chars[13]; int idx = 0;
for (int i = 0; i < 5; i++) chars[idx++] = e->Name1[i];
for (int i = 0; i < 6; i++) chars[idx++] = e->Name2[i];
for (int i = 0; i < 2; i++) chars[idx++] = e->Name3[i];
for (int i = 0; i < 13 && pos < MAX_LFN-1; i++, pos++) {
uint16_t c = chars[i];
if (c == 0x0000 || c == 0xFFFF) { lfn[pos] = '\0'; break; }
lfn[pos] = (char)(c & 0x7F);
}
}
static int LfnMatch(const char *lfn, const char *want) {
while (*lfn && *want) {
char a = *lfn++, b = *want++;
if (a >= 'A' && a <= 'Z') a += 32;
if (b >= 'A' && b <= 'Z') b += 32;
if (a != b) return 0;
}
return *lfn == '\0' && *want == '\0';
}
// ---------------------------------------------------------------------------
// Directory search
// ---------------------------------------------------------------------------
static int SearchDir(FAT32_VOL *v, uint32_t dirCluster, const char *name,
uint32_t *outCluster, uint32_t *outSize, uint8_t *outAttr,
uint64_t *outDirentLba, uint32_t *outDirentOffset) {
uint8_t *clusterBuf = (uint8_t *)MmAllocatePool(v->BytesPerCluster);
if (!clusterBuf) return 0;
char lfn[MAX_LFN]; int hasLfn = 0; memset(lfn, 0, sizeof(lfn));
uint32_t cluster = dirCluster;
while (cluster >= 2 && cluster < FAT32_EOC) {
uint64_t clusterLba = ClusterToLba(v, cluster);
if (!KSUCCESS(ReadSectors(v, clusterLba, v->SectorsPerCluster, clusterBuf))) break;
uint32_t entries = v->BytesPerCluster / sizeof(DIRENT);
DIRENT *dir = (DIRENT *)clusterBuf;
for (uint32_t i = 0; i < entries; i++) {
if (dir[i].Name[0] == 0x00) goto done;
if ((uint8_t)dir[i].Name[0] == 0xE5) { hasLfn = 0; memset(lfn, 0, sizeof(lfn)); continue; }
if (dir[i].Attr == ATTR_LFN) { LfnAppend(lfn, (LFN_ENTRY *)&dir[i]); hasLfn = 1; continue; }
int match = 0;
if (hasLfn && lfn[0]) match = LfnMatch(lfn, name);
if (!match) match = MatchName83(dir[i].Name, dir[i].Ext, name);
hasLfn = 0; memset(lfn, 0, sizeof(lfn));
if (match) {
*outCluster = ((uint32_t)dir[i].FirstClusterHi << 16) | dir[i].FirstClusterLo;
*outSize = dir[i].FileSize;
*outAttr = dir[i].Attr;
if (outDirentLba || outDirentOffset) {
uint64_t eb = (uint64_t)i * sizeof(DIRENT);
if (outDirentLba) *outDirentLba = clusterLba + eb / v->BytesPerSector;
if (outDirentOffset) *outDirentOffset = (uint32_t)(eb % v->BytesPerSector);
}
MmFreePool(clusterBuf);
return 1;
}
}
cluster = FatEntry(v, cluster);
}
done:
MmFreePool(clusterBuf);
return 0;
}
// ---------------------------------------------------------------------------
// Path traversal
// ---------------------------------------------------------------------------
static KSTATUS TraverseEx(FAT32_VOL *v, const char *path,
uint32_t *outCluster, uint32_t *outSize,
uint64_t *outDirentLba, uint32_t *outDirentOffset) {
uint32_t cluster = v->RootCluster, size = 0;
uint8_t attr = ATTR_DIRECTORY;
if (outDirentLba) *outDirentLba = 0;
if (outDirentOffset) *outDirentOffset = 0;
char component[256]; const char *p = path;
while (*p) {
while (*p == '\\' || *p == '/') p++;
if (!*p) break;
int len = 0;
while (*p && *p != '\\' && *p != '/' && len < 255) component[len++] = *p++;
component[len] = '\0';
if (!len) break;
if (!(attr & ATTR_DIRECTORY)) return STATUS_NOT_FOUND;
uint32_t childCluster, childSize; uint8_t childAttr;
uint64_t childLba = 0; uint32_t childOffset = 0;
if (!SearchDir(v, cluster, component, &childCluster, &childSize,
&childAttr, &childLba, &childOffset))
return STATUS_NOT_FOUND;
cluster = childCluster; size = childSize; attr = childAttr;
if (outDirentLba) *outDirentLba = childLba;
if (outDirentOffset) *outDirentOffset = childOffset;
}
*outCluster = cluster; *outSize = size;
return STATUS_SUCCESS;
}
static KSTATUS Traverse(FAT32_VOL *v, const char *path,
uint32_t *outCluster, uint32_t *outSize) {
return TraverseEx(v, path, outCluster, outSize, NULL, NULL);
}
// ---------------------------------------------------------------------------
// Path helpers for Fat32Create
// ---------------------------------------------------------------------------
static void SplitPath(const char *path, char parent[256], char name[256]) {
const char *last = NULL;
for (const char *p = path; *p; p++) if (*p == '\\' || *p == '/') last = p;
if (!last) { parent[0] = '\0'; strncpy(name, path, 255); name[255] = '\0'; }
else {
size_t len = (size_t)(last - path); if (len > 255) len = 255;
memcpy(parent, path, len); parent[len] = '\0';
strncpy(name, last+1, 255); name[255] = '\0';
}
}
// ---------------------------------------------------------------------------
// Find a free DIRENT slot
// ---------------------------------------------------------------------------
static int FindFreeDirent(FAT32_VOL *v, uint32_t dirCluster,
uint64_t *outLba, uint32_t *outOffset) {
uint8_t *clusterBuf = (uint8_t *)MmAllocatePool(v->BytesPerCluster);
if (!clusterBuf) return 0;
uint32_t cluster = dirCluster, prevCluster = dirCluster;
while (cluster >= 2 && cluster < FAT32_EOC) {
uint64_t clusterLba = ClusterToLba(v, cluster);
if (!KSUCCESS(ReadSectors(v, clusterLba, v->SectorsPerCluster, clusterBuf))) {
MmFreePool(clusterBuf); return 0;
}
uint32_t entries = v->BytesPerCluster / sizeof(DIRENT);
DIRENT *dir = (DIRENT *)clusterBuf;
for (uint32_t i = 0; i < entries; i++) {
uint8_t first = (uint8_t)dir[i].Name[0];
if (first == 0xE5 || first == 0x00) {
uint64_t eb = (uint64_t)i * sizeof(DIRENT);
*outLba = clusterLba + eb / v->BytesPerSector;
*outOffset = (uint32_t)(eb % v->BytesPerSector);
MmFreePool(clusterBuf); return 1;
}
}
prevCluster = cluster;
cluster = FatEntry(v, cluster);
}
uint32_t newCluster = AppendCluster(v, prevCluster);
if (newCluster) {
*outLba = ClusterToLba(v, newCluster); *outOffset = 0;
MmFreePool(clusterBuf); return 1;
}
MmFreePool(clusterBuf);
return 0;
}
// ---------------------------------------------------------------------------
// Open / Close
// ---------------------------------------------------------------------------
static KSTATUS Fat32Open(IO_FILESYSTEM *fs, const char *path, void **fileCtx) {
FAT32_VOL *v = (FAT32_VOL *)fs->DriverCtx;
uint32_t cluster, size; uint64_t direntLba; uint32_t direntOffset;
KSTATUS s = TraverseEx(v, path, &cluster, &size, &direntLba, &direntOffset);
if (!KSUCCESS(s)) return s;
FAT32_FILE *f = (FAT32_FILE *)MmAllocatePool(sizeof(FAT32_FILE));
if (!f) return STATUS_NO_MEMORY;
f->StartCluster = cluster; f->FileSize = size; f->Vol = v;
f->DirentLba = direntLba; f->DirentOffset = direntOffset;
*fileCtx = f;
return STATUS_SUCCESS;
}
static void Fat32Close(IO_FILESYSTEM *fs, void *fileCtx) {
(void)fs;
FAT32_FILE *f = (FAT32_FILE *)fileCtx;
FlushFatCache(f->Vol);
MmFreePool(fileCtx);
}
// ---------------------------------------------------------------------------
// Read (fix #4: contiguous-run coalescing)
// ---------------------------------------------------------------------------
// We peek ahead in the FAT chain: if cluster N+1 == cluster N physically,
// extend the current run instead of issuing a new ReadSectors call.
// Run length is capped at MAX_RUN_SECTORS to stay within the AHCI bounce
// buffer (128 sectors = 64 KB).
//
// FatEntry() calls in the peek loop are nearly free because they hit the
// FAT sector cache (fix #1).
#define MAX_RUN_SECTORS 128u
static KSTATUS Fat32Read(IO_FILESYSTEM *fs, void *fileCtx,
uint64_t offset, void *buf,
size_t size, size_t *bytesRead) {
(void)fs;
if (!bytesRead) return STATUS_INVALID_PARAMETER;
*bytesRead = 0;
if (!fileCtx || !buf) return STATUS_INVALID_PARAMETER;
FAT32_FILE *f = (FAT32_FILE *)fileCtx;
FAT32_VOL *v = f->Vol;
if (offset >= f->FileSize || size == 0) return STATUS_SUCCESS;
if (offset + size > f->FileSize) size = (size_t)(f->FileSize - offset);
size_t wanted = size;
// Single allocation for the largest possible coalesced run.
uint8_t *runBuf = (uint8_t *)MmAllocatePool(MAX_RUN_SECTORS * 512u);
if (!runBuf) return STATUS_NO_MEMORY;
size_t totalRead = 0;
uint8_t *out = (uint8_t *)buf;
KSTATUS readStatus = STATUS_SUCCESS;
uint32_t skipClusters = (uint32_t)(offset / v->BytesPerCluster);
uint32_t offsetInCluster = (uint32_t)(offset % v->BytesPerCluster);
uint32_t cluster = f->StartCluster;
for (uint32_t i = 0; i < skipClusters && cluster < FAT32_EOC; i++)
cluster = FatEntry(v, cluster);
while (size > 0 && cluster >= 2 && cluster < FAT32_EOC) {
// Build the longest physically-contiguous run we can.
uint32_t runStart = cluster;
uint32_t runSectors = v->SectorsPerCluster;
uint32_t next = FatEntry(v, cluster);
while (next == cluster + 1 && next < FAT32_EOC
&& runSectors + v->SectorsPerCluster <= MAX_RUN_SECTORS) {
cluster = next;
next = FatEntry(v, cluster);
runSectors += v->SectorsPerCluster;
}
KSTATUS rs = ReadSectors(v, ClusterToLba(v, runStart), runSectors, runBuf);
if (!KSUCCESS(rs)) {
readStatus = rs;
break;
}
uint32_t runBytes = runSectors * 512u;
size_t avail = runBytes - offsetInCluster;
size_t copy = avail < size ? avail : size;
memcpy(out, runBuf + offsetInCluster, copy);
out += copy;
totalRead += copy;
size -= copy;
offsetInCluster = 0;
cluster = next;
}
if (totalRead != wanted) {
// The directory entry says the file has more data than the cluster chain
// or disk read actually supplied. Clear the caller's remainder so stale
// memory cannot be interpreted as file contents.
memset(out, 0, wanted - totalRead);
}
MmFreePool(runBuf);
*bytesRead = totalRead;
if (totalRead != wanted)
return KSUCCESS(readStatus) ? STATUS_DEVICE_ERROR : readStatus;
return STATUS_SUCCESS;
}
// ---------------------------------------------------------------------------
// Write (fix #4: contiguous-run coalescing)
// ---------------------------------------------------------------------------
// Same strategy as read. Whole-cluster-aligned runs skip the read-modify-
// write; partial head/tail clusters still require it.
static KSTATUS Fat32Write(IO_FILESYSTEM *fs, void *fileCtx,
uint64_t offset, const void *buf,
size_t size, size_t *bytesWritten) {
(void)fs;
if (!size) { *bytesWritten = 0; return STATUS_SUCCESS; }
FAT32_FILE *f = (FAT32_FILE *)fileCtx;
FAT32_VOL *v = f->Vol;
const uint8_t *src = (const uint8_t *)buf;
// Ensure at least one cluster exists.
if (f->StartCluster < 2) {
uint32_t first = AllocCluster(v);
if (!first) { *bytesWritten = 0; return STATUS_NO_MEMORY; }
f->StartCluster = first;
DirentUpdateFirstCluster(v, f->DirentLba, f->DirentOffset, first);
}
// Walk to the starting cluster, allocating as needed.
uint32_t clusterIndex = (uint32_t)(offset / v->BytesPerCluster);
uint32_t offsetInCluster = (uint32_t)(offset % v->BytesPerCluster);
uint32_t cluster = f->StartCluster;
for (uint32_t i = 0; i < clusterIndex; i++) {
uint32_t next = FatEntry(v, cluster);
if (next < 2 || next >= FAT32_EOC) {
next = AppendCluster(v, cluster);
if (!next) { *bytesWritten = 0; return STATUS_NO_MEMORY; }
}
cluster = next;
}
uint8_t *runBuf = (uint8_t *)MmAllocatePool(MAX_RUN_SECTORS * 512u);
if (!runBuf) { *bytesWritten = 0; return STATUS_NO_MEMORY; }
size_t written = 0, remaining = size;
while (remaining > 0 && cluster >= 2) {
// Build a contiguous run (only when cluster-aligned to avoid a
// partial read-modify-write spanning multiple clusters).
uint32_t runStart = cluster;
uint32_t runSectors = v->SectorsPerCluster;
uint32_t next = FatEntry(v, cluster);
if (offsetInCluster == 0) {
while (next == cluster + 1 && next < FAT32_EOC
&& runSectors + v->SectorsPerCluster <= MAX_RUN_SECTORS) {
cluster = next;
next = FatEntry(v, cluster);
runSectors += v->SectorsPerCluster;
}
}
uint32_t runBytes = runSectors * 512u;
size_t avail = runBytes - offsetInCluster;
size_t copy = avail < remaining ? avail : remaining;
// Read existing data only for partial-cluster writes.
bool partial = (offsetInCluster > 0) || (copy < (size_t)runBytes);
if (partial) {
if (!KSUCCESS(ReadSectors(v, ClusterToLba(v, runStart), runSectors, runBuf)))
break;
}
memcpy(runBuf + offsetInCluster, src, copy);
if (!KSUCCESS(WriteSectors(v, ClusterToLba(v, runStart), runSectors, runBuf)))
break;
src += copy;
written += copy;
remaining -= copy;
offsetInCluster = 0;
if (remaining > 0) {
// Advance: if end-of-chain, append a new cluster.
cluster = next;
if (cluster < 2 || cluster >= FAT32_EOC) {
// Find the actual last cluster in the run we just wrote.
uint32_t last = runStart;
for (uint32_t tmp = FatEntry(v, last);
tmp >= 2 && tmp < FAT32_EOC;
tmp = FatEntry(v, last))
last = tmp;
cluster = AppendCluster(v, last);
if (!cluster) break;
}
}
}
MmFreePool(runBuf);
uint64_t newEnd = offset + written;
if (newEnd > f->FileSize) {
f->FileSize = (uint32_t)newEnd;
DirentUpdateSize(v, f->DirentLba, f->DirentOffset, f->FileSize);
}
*bytesWritten = written;
return (written == size) ? STATUS_SUCCESS : STATUS_DEVICE_ERROR;
}
// ---------------------------------------------------------------------------
// Create
// ---------------------------------------------------------------------------
static KSTATUS Fat32Create(IO_FILESYSTEM *fs, const char *path, void **fileCtx) {
FAT32_VOL *v = (FAT32_VOL *)fs->DriverCtx;
char parentPath[256], filename[256];
SplitPath(path, parentPath, filename);
if (!filename[0]) return STATUS_INVALID_PARAMETER;
uint32_t parentCluster, parentSize;
KSTATUS s = Traverse(v, parentPath, &parentCluster, &parentSize);
if (!KSUCCESS(s)) return s;
{ uint32_t ec, es; uint8_t ea;
if (SearchDir(v, parentCluster, filename, &ec, &es, &ea, NULL, NULL))
return STATUS_OBJECT_NAME_COLLISION; }
uint64_t direntLba; uint32_t direntOffset;
if (!FindFreeDirent(v, parentCluster, &direntLba, &direntOffset))
return STATUS_NO_MEMORY;
uint8_t *sec = (uint8_t *)MmAllocatePool(v->BytesPerSector);
if (!sec) return STATUS_NO_MEMORY;
s = ReadSectors(v, direntLba, 1, sec);
if (!KSUCCESS(s)) { MmFreePool(sec); return s; }
DIRENT *d = (DIRENT *)(sec + direntOffset);
memset(d, 0, sizeof(DIRENT));
Make83Name(filename, d->Name, d->Ext);
d->Attr = ATTR_ARCHIVE;
s = WriteSectors(v, direntLba, 1, sec);
MmFreePool(sec);
if (!KSUCCESS(s)) return s;
FAT32_FILE *f = (FAT32_FILE *)MmAllocatePool(sizeof(FAT32_FILE));
if (!f) return STATUS_NO_MEMORY;
f->StartCluster = 0; f->FileSize = 0; f->Vol = v;
f->DirentLba = direntLba; f->DirentOffset = direntOffset;
*fileCtx = f;
return STATUS_SUCCESS;
}
// ---------------------------------------------------------------------------
// Truncate
// ---------------------------------------------------------------------------
static KSTATUS Fat32Truncate(IO_FILESYSTEM *fs, void *fileCtx, uint64_t newSize) {
(void)fs;
FAT32_FILE *f = (FAT32_FILE *)fileCtx;
FAT32_VOL *v = f->Vol;
if ((uint32_t)newSize == f->FileSize) return STATUS_SUCCESS;
if (newSize == 0) {
if (f->StartCluster >= 2) {
FreeClusterChain(v, f->StartCluster);
f->StartCluster = 0;
DirentUpdateFirstCluster(v, f->DirentLba, f->DirentOffset, 0);
}
f->FileSize = 0;
KSTATUS s = DirentUpdateSize(v, f->DirentLba, f->DirentOffset, 0);
FlushFatCache(v);
return s;
}
uint32_t newClusters = (uint32_t)
((newSize + v->BytesPerCluster - 1) / v->BytesPerCluster);
if (newSize < f->FileSize) {
if (f->StartCluster < 2) goto update_size;
uint32_t cluster = f->StartCluster, lastGood = cluster;
for (uint32_t i = 1; i < newClusters && cluster < FAT32_EOC; i++) {
lastGood = cluster; cluster = FatEntry(v, cluster);
}
uint32_t tail = FatEntry(v, lastGood);
WriteFatEntry(v, lastGood, FAT32_EOC_MARK);
FreeClusterChain(v, tail);
} else {
uint32_t curClusters = f->FileSize
? (f->FileSize + v->BytesPerCluster - 1) / v->BytesPerCluster : 0;
uint32_t lastCluster = 0;
if (f->StartCluster < 2) {
uint32_t first = AllocCluster(v);
if (!first) return STATUS_NO_MEMORY;
f->StartCluster = first;
DirentUpdateFirstCluster(v, f->DirentLba, f->DirentOffset, first);
lastCluster = first; curClusters = 1;
} else {
lastCluster = GetChainEnd(v, f->StartCluster, NULL);
}
for (uint32_t i = curClusters; i < newClusters; i++) {
uint32_t next = AppendCluster(v, lastCluster);
if (!next) return STATUS_NO_MEMORY;
lastCluster = next;
}
}
update_size:
f->FileSize = (uint32_t)newSize;
{ KSTATUS s = DirentUpdateSize(v, f->DirentLba, f->DirentOffset, (uint32_t)newSize);
FlushFatCache(v);
return s; }
}
// ---------------------------------------------------------------------------
// Delete
// ---------------------------------------------------------------------------
static KSTATUS Fat32Delete(IO_FILESYSTEM *fs, const char *path) {
FAT32_VOL *v = (FAT32_VOL *)fs->DriverCtx;
uint32_t cluster, size; uint64_t direntLba; uint32_t direntOffset;
KSTATUS s = TraverseEx(v, path, &cluster, &size, &direntLba, &direntOffset);
if (!KSUCCESS(s)) return s;
if (!direntLba) return STATUS_ACCESS_DENIED;
uint8_t *sec = (uint8_t *)MmAllocatePool(v->BytesPerSector);
if (!sec) return STATUS_NO_MEMORY;
s = ReadSectors(v, direntLba, 1, sec);
if (KSUCCESS(s)) { sec[direntOffset] = 0xE5; s = WriteSectors(v, direntLba, 1, sec); }
MmFreePool(sec);
if (!KSUCCESS(s)) return s;
if (cluster >= 2) FreeClusterChain(v, cluster);
FlushFatCache(v);
return STATUS_SUCCESS;
}
static uint64_t Fat32GetSize(IO_FILESYSTEM *fs, void *fileCtx) {
(void)fs; return ((FAT32_FILE *)fileCtx)->FileSize;
}
// ---------------------------------------------------------------------------
// Directory enumeration (fix #2: cluster buffer cached in context)
// ---------------------------------------------------------------------------
// Previously: allocate + ReadSectors + return 1 entry + free, every call.
// Now: buffer lives in FAT32_DIR_CTX; ReadSectors fires only on cluster
// boundaries.
typedef struct {
FAT32_VOL *Vol;
uint32_t DirCluster;
uint32_t CurCluster;
uint32_t LoadedCluster; // which cluster is currently in ClusterBuf
uint32_t EntryIndex;
char LfnBuf[MAX_LFN];
int HasLfn;
uint8_t *ClusterBuf; // allocated once in OpenDir, freed in CloseDir
} FAT32_DIR_CTX;
static KSTATUS Fat32OpenDir(IO_FILESYSTEM *fs, const char *dirPath, void **dirCtx) {
FAT32_VOL *v = (FAT32_VOL *)fs->DriverCtx;
uint32_t cluster;
if (!dirPath || dirPath[0] == '\0') {
cluster = v->RootCluster;
} else {
uint32_t size; KSTATUS s = Traverse(v, dirPath, &cluster, &size);
if (!KSUCCESS(s)) return s;
}
FAT32_DIR_CTX *ctx = (FAT32_DIR_CTX *)MmAllocatePool(sizeof(FAT32_DIR_CTX));
if (!ctx) return STATUS_NO_MEMORY;
ctx->ClusterBuf = (uint8_t *)MmAllocatePool(v->BytesPerCluster);
if (!ctx->ClusterBuf) { MmFreePool(ctx); return STATUS_NO_MEMORY; }
ctx->Vol = v;
ctx->DirCluster = cluster;
ctx->CurCluster = cluster;
ctx->LoadedCluster = (uint32_t)-1;
ctx->EntryIndex = 0;
ctx->HasLfn = 0;
memset(ctx->LfnBuf, 0, sizeof(ctx->LfnBuf));
*dirCtx = ctx;
return STATUS_SUCCESS;
}
static KSTATUS Fat32ReadDir(IO_FILESYSTEM *fs, void *dirCtx, KDIR_ENTRY *out) {
(void)fs;
FAT32_DIR_CTX *ctx = (FAT32_DIR_CTX *)dirCtx;
FAT32_VOL *v = ctx->Vol;
uint32_t epc = v->BytesPerCluster / sizeof(DIRENT);
while (ctx->CurCluster >= 2 && ctx->CurCluster < FAT32_EOC) {
// Load the cluster only when crossing a cluster boundary.
if (ctx->CurCluster != ctx->LoadedCluster) {
if (!KSUCCESS(ReadSectors(v, ClusterToLba(v, ctx->CurCluster),
v->SectorsPerCluster, ctx->ClusterBuf)))
break;
ctx->LoadedCluster = ctx->CurCluster;
}
DIRENT *dir = (DIRENT *)ctx->ClusterBuf;
while (ctx->EntryIndex < epc) {
DIRENT *e = &dir[ctx->EntryIndex++];
if (e->Name[0] == 0x00) { ctx->CurCluster = FAT32_EOC; return STATUS_NO_MORE_FILES; }
if ((uint8_t)e->Name[0] == 0xE5) { ctx->HasLfn = 0; memset(ctx->LfnBuf, 0, sizeof(ctx->LfnBuf)); continue; }
if (e->Attr == ATTR_LFN) { LfnAppend(ctx->LfnBuf, (LFN_ENTRY *)e); ctx->HasLfn = 1; continue; }
if (e->Attr & 0x08 && !(e->Attr & ATTR_DIRECTORY)) { ctx->HasLfn = 0; memset(ctx->LfnBuf, 0, sizeof(ctx->LfnBuf)); continue; }
if (ctx->HasLfn && ctx->LfnBuf[0]) {
size_t len = strlen(ctx->LfnBuf);
if (len >= KDIR_NAME_MAX) len = KDIR_NAME_MAX - 1;
memcpy(out->Name, ctx->LfnBuf, len); out->Name[len] = '\0';
} else {
char name83[13]; int n = 0;
for (int i = 0; i < 8 && e->Name[i] != ' '; i++)
name83[n++] = (char)(e->Name[i] >= 'A' && e->Name[i] <= 'Z' ? e->Name[i]+32 : e->Name[i]);
if (e->Ext[0] != ' ') {
name83[n++] = '.';
for (int i = 0; i < 3 && e->Ext[i] != ' '; i++)
name83[n++] = (char)(e->Ext[i] >= 'A' && e->Ext[i] <= 'Z' ? e->Ext[i]+32 : e->Ext[i]);
}
name83[n] = '\0';
size_t len = (size_t)n; if (len >= KDIR_NAME_MAX) len = KDIR_NAME_MAX - 1;
memcpy(out->Name, name83, len); out->Name[len] = '\0';
}
out->Attr = e->Attr; out->FileSize = e->FileSize;
ctx->HasLfn = 0; memset(ctx->LfnBuf, 0, sizeof(ctx->LfnBuf));
return STATUS_SUCCESS;
}
ctx->CurCluster = FatEntry(v, ctx->CurCluster);
ctx->EntryIndex = 0; ctx->HasLfn = 0;
memset(ctx->LfnBuf, 0, sizeof(ctx->LfnBuf));
}
return STATUS_NO_MORE_FILES;
}
static void Fat32CloseDir(IO_FILESYSTEM *fs, void *dirCtx) {
(void)fs;
FAT32_DIR_CTX *ctx = (FAT32_DIR_CTX *)dirCtx;
MmFreePool(ctx->ClusterBuf);
MmFreePool(ctx);
}
// ---------------------------------------------------------------------------
// Partition scan (MBR + GPT) — unchanged
// ---------------------------------------------------------------------------
static const EFI_GUID GUID_EFI_SYSTEM = { 0xC12A7328, 0xF81F, 0x11D2, {0xBA,0x4B,0x00,0xA0,0xC9,0x3E,0xC9,0x3B} };
static const EFI_GUID GUID_MS_BASIC_DATA = { 0xEBD0A0A2, 0xB9E5, 0x4433, {0x87,0xC0,0x68,0xB6,0xB7,0x26,0x99,0xC7} };
static const EFI_GUID GUID_LINUX_BASIC_DATA = { 0x0FC63DAF, 0x8483, 0x4772, {0x8E,0x79,0x3D,0x69,0xD8,0x47,0x7D,0xE4} };
static int IsFat32Vbr(FAT32_VOL *v, uint32_t lba) {
uint8_t sector[512];
if (!KSUCCESS(ReadSectors(v, lba, 1, sector))) return 0;
if (sector[510] != 0x55 || sector[511] != 0xAA) return 0;
BPB *bpb = (BPB *)sector;
return bpb->FatSize16 == 0 && bpb->RootEntryCount == 0 && bpb->FatSize32 != 0;
}
static uint32_t FindFat32Partition(FAT32_VOL *v) {
uint8_t mbr[512];
if (!KSUCCESS(ReadSectors(v, 0, 1, mbr))) return 0;
if (mbr[510] != 0x55 || mbr[511] != 0xAA) return 0;
int isGpt = 0;
for (int i = 0; i < 4; i++) if (mbr[0x1BE + i*16 + 4] == 0xEE) { isGpt = 1; break; }
if (isGpt) {
uint8_t gptSector[512];
if (!KSUCCESS(ReadSectors(v, 1, 1, gptSector))) return 0;
GPT_HEADER *gpt = (GPT_HEADER *)gptSector;
if (memcmp(gpt->Signature, "EFI PART", 8) != 0) return 0;
uint64_t entryLBA = gpt->PartitionEntryLBA;
uint32_t numEntries = gpt->NumberOfPartitionEntries;
uint32_t entrySize = gpt->SizeOfPartitionEntry;
if (entrySize < sizeof(GPT_ENTRY)) return 0;
uint32_t eps = 512 / entrySize; uint8_t entrySector[512];
for (uint32_t i = 0; i < numEntries; i++) {
if (i % eps == 0)
if (!KSUCCESS(ReadSectors(v, entryLBA + i/eps, 1, entrySector))) return 0;
GPT_ENTRY *entry = (GPT_ENTRY *)(entrySector + (i % eps) * entrySize);
static const EFI_GUID emptyGuid = {0};
if (memcmp(&entry->PartitionTypeGUID, &emptyGuid, sizeof(EFI_GUID)) == 0) continue;
if (memcmp(&entry->PartitionTypeGUID, &GUID_EFI_SYSTEM, sizeof(EFI_GUID)) == 0 ||
memcmp(&entry->PartitionTypeGUID, &GUID_MS_BASIC_DATA, sizeof(EFI_GUID)) == 0 ||
memcmp(&entry->PartitionTypeGUID, &GUID_LINUX_BASIC_DATA, sizeof(EFI_GUID)) == 0) {
uint32_t lba = (uint32_t)entry->StartingLBA;
if (IsFat32Vbr(v, lba)) return lba;
}
}
return 0;
}
for (int i = 0; i < 4; i++) {
uint8_t *entry = mbr + 0x1BE + i*16;
if (entry[4] == 0x0B || entry[4] == 0x0C) {
uint32_t lba = *(uint32_t *)(entry + 8);
if (IsFat32Vbr(v, lba)) return lba;
}
}
return 0;
}
// ---------------------------------------------------------------------------
// DriverEntry
// ---------------------------------------------------------------------------
static FAT32_VOL gVol;
static IO_FILESYSTEM gFs;
KSTATUS DriverEntry(const HAL_API *hal) {
DbgPrint("FAT32: Init\n");
if (!hal) return STATUS_INVALID_PARAMETER;
FatCacheInvalidate();
gVol.Hal = hal;
gVol.DriveIndex = 0;
uint32_t partLba = FindFat32Partition(&gVol);
if (!partLba) { DbgPrint("FAT32: No FAT32 volume found.\n"); return STATUS_NOT_SUPPORTED; }
gVol.PartitionLba = partLba;
uint8_t sector[512];
if (!KSUCCESS(ReadSectors(&gVol, partLba, 1, sector))) return STATUS_DEVICE_ERROR;
BPB *bpb = (BPB *)sector;
if (bpb->FatSize16 != 0 || bpb->RootEntryCount != 0) return STATUS_NOT_SUPPORTED;
gVol.BytesPerSector = bpb->BytesPerSector;
gVol.SectorsPerCluster = bpb->SectorsPerCluster;
gVol.BytesPerCluster = bpb->BytesPerSector * bpb->SectorsPerCluster;
gVol.ReservedSectors = bpb->ReservedSectors;
gVol.NumFats = bpb->NumFats;
gVol.FatSize = bpb->FatSize32;
gVol.RootCluster = bpb->RootCluster;
gVol.DataStart = partLba + bpb->ReservedSectors + bpb->NumFats * bpb->FatSize32;
gVol.NextFreeHint = 2;
uint32_t totalSectors = bpb->TotalSectors16 ? bpb->TotalSectors16 : bpb->TotalSectors32;
uint32_t dataSectors = totalSectors - bpb->ReservedSectors - bpb->NumFats * bpb->FatSize32;
gVol.TotalClusters = dataSectors / bpb->SectorsPerCluster;
gFs.Open = Fat32Open;
gFs.Read = Fat32Read;
gFs.Write = Fat32Write;
gFs.Create = Fat32Create;
gFs.Delete = Fat32Delete;
gFs.Truncate = Fat32Truncate;
gFs.GetSize = Fat32GetSize;
gFs.Close = Fat32Close;
gFs.DriverCtx = &gVol;
gFs.OpenDir = Fat32OpenDir;
gFs.ReadDir = Fat32ReadDir;
gFs.CloseDir = Fat32CloseDir;
IO_VOLUME_PARAMS volParams;
volParams.DiskIndex = gVol.DriveIndex;
volParams.PartitionLba = partLba;
volParams.PartitionSize = totalSectors;
KSTATUS status = IoRegisterFilesystem(gVol.DriveIndex, &gFs, &volParams);
DbgPrint("FAT32: Init complete (status=%d, clusters=%u).\n", status, gVol.TotalClusters);
return STATUS_SUCCESS;
}