Files
nt-lite/code/drivers/fs/fat/fat.c
T

3425 lines
120 KiB
C

// FAT filesystem driver. This file owns filesystem operations and FAT-backed file access.
#include "fat.h"
#include "fat_disk.h"
#ifndef FAT_DEBUG
#define FAT_DEBUG 0
#endif
#define FatDebugPrint(...) do { if (FAT_DEBUG) DbgPrint(__VA_ARGS__); } while (0)
// FAT_VOL forward declaration (needed by cache helpers).
typedef struct FAT_VOL_S FAT_VOL;
// 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 {
FAT_VOL* vol;
uint64_t lba;
uint8_t data[512];
bool valid;
bool dirty;
} FAT_CACHE_ENTRY;
static FAT_CACHE_ENTRY gFatCache[FAT_CACHE_SLOTS];
#define FAT_PATH_CACHE_SLOTS 64u
#define FAT_PATH_CACHE_MAX 512u
#define FAT_CHAIN_CACHE_SLOTS 16u
#define FAT_WRITE_RUN_MAX_SECTORS 128u
#define FAT_READ_RUN_MAX_SECTORS 512u
typedef struct FAT_PATH_CACHE_ENTRY {
bool Valid;
uint64_t Generation;
uint32_t Hash;
size_t PathLength;
char Path[FAT_PATH_CACHE_MAX];
uint32_t Cluster;
uint32_t Size;
uint8_t Attr;
uint64_t DirentLba;
uint32_t DirentOffset;
} FAT_PATH_CACHE_ENTRY;
typedef struct FAT_CHAIN_CACHE_ENTRY {
bool Valid;
uint32_t LogicalStart;
uint32_t PhysicalStart;
uint32_t Length;
uint32_t NextCluster;
} FAT_CHAIN_CACHE_ENTRY;
// The FAT sector cache is global and direct-mapped. mmap-backed video can fault
// file pages from multiple Bink worker threads at the same time; without
// serialization one thread can evict/reload a cache slot while another thread is
// still reading a FAT entry from that slot, corrupting cluster-chain traversal.
// Use a sleepable mutex rather than a spinlock because cache misses perform disk
// I/O. The static mutex avoids allocating an object from the paging path.
static MUTEX gFatCacheMutex;
static SPINLOCK gFatCacheMutexInitLock = SPINLOCK_INIT;
static int gFatCacheMutexReady;
static void FatCacheEnsureMutex(void) {
if (gFatCacheMutexReady)
return;
uintptr_t flags = KeAcquireSpinlock(&gFatCacheMutexInitLock);
if (!gFatCacheMutexReady) {
memset(&gFatCacheMutex, 0, sizeof(gFatCacheMutex));
ObInitializeObjectHeader(&gFatCacheMutex.Header, ObjectTypeMutex);
gFatCacheMutex.Lock = (SPINLOCK)SPINLOCK_INIT;
gFatCacheMutex.Owner = (struct THREAD*)0;
LIST_INIT(&gFatCacheMutex.WaitList);
LIST_INIT(&gFatCacheMutex.OwnerLink);
gFatCacheMutex.DonationClass = KeSchedulingNormal;
gFatCacheMutex.DonationPriority = PriorityLow;
gFatCacheMutex.HasDonation = 0;
gFatCacheMutex.Abandoned = 0;
gFatCacheMutexReady = 1;
}
KeReleaseSpinlock(&gFatCacheMutexInitLock, flags);
}
static int FatCacheAcquire(void) {
if (!KeCanWait())
return 0;
FatCacheEnsureMutex();
return KE_MUTEX_ACQUIRE_SUCCEEDED(KeAcquireMutex(&gFatCacheMutex, KE_WAIT_INFINITE));
}
static void FatCacheRelease(int held) {
if (held)
KeReleaseMutex(&gFatCacheMutex);
}
// Raw (non-cached) sector I/O — forward declarations.
static KSTATUS ReadSectorsRaw (FAT_VOL* v, uint64_t lba, uint32_t count, void* buf);
static KSTATUS WriteSectorsRaw(FAT_VOL* v, uint64_t lba, uint32_t count, const void* buf);
// Read one FAT sector through the cache.
// Caller must hold the FAT cache mutex while using the returned pointer.
static KSTATUS FatCacheRead(FAT_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->vol == v && e->lba == lba) {
*out = e->data;
return NTLSTATUS_SUCCESS;
}
// Evict current occupant: single-copy writeback on collision.
// The full multi-copy flush happens in FlushFatCache().
if (e->valid && e->dirty && e->vol) {
WriteSectorsRaw(e->vol, e->lba, 1, e->data);
e->dirty = false;
}
KSTATUS s = ReadSectorsRaw(v, lba, 1, e->data);
if (!KSUCCESS(s)) { e->valid = false; e->vol = (FAT_VOL*)0; return s; }
e->vol = v;
e->lba = lba;
e->valid = true;
e->dirty = false;
*out = e->data;
return NTLSTATUS_SUCCESS;
}
// Implements the fat Cache Mark Dirty operation.
static void FatCacheMarkDirty(FAT_VOL* v, uint64_t lba) {
uint32_t slot = (uint32_t)(lba % FAT_CACHE_SLOTS);
if (gFatCache[slot].valid && gFatCache[slot].vol == v && gFatCache[slot].lba == lba)
gFatCache[slot].dirty = true;
}
// Invalidates every FAT sector-cache entry.
static void FatCacheInvalidate(void) {
int held = FatCacheAcquire();
for (int i = 0; i < FAT_CACHE_SLOTS; i++) {
gFatCache[i].vol = (FAT_VOL*)0;
gFatCache[i].valid = false;
gFatCache[i].dirty = false;
}
FatCacheRelease(held);
}
// Discards cache entries owned by one dead or destroyed mounted FAT instance.
static void FatCacheInvalidateVolume(FAT_VOL* volume) {
if (!volume)
return;
int held = FatCacheAcquire();
for (int i = 0; i < FAT_CACHE_SLOTS; i++) {
if (gFatCache[i].vol != volume)
continue;
gFatCache[i].vol = (FAT_VOL*)0;
gFatCache[i].valid = false;
gFatCache[i].dirty = false;
}
FatCacheRelease(held);
}
// FlushFatCache — write every dirty slot to all FAT copies, clear dirty flag.
// Defined after FAT_VOL is fully declared (below).
static void FlushFatCache(FAT_VOL* v);
// Driver state.
struct FAT_VOL_S {
FAT_VARIANT Variant;
uint8_t DriveIndex;
uint64_t PartitionLba; // absolute LBA of the mounted volume
uint64_t PartitionSize; // sectors, best-effort bounds/documentation
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 RootEntryCount;
uint32_t RootDirSectors;
uint64_t RootDirStart;
uint64_t DataStart; // absolute LBA of cluster 2
uint32_t TotalClusters; // usable data clusters
uint32_t NextFreeHint; // fix #3: start cluster for next free-scan
SPINLOCK PathCacheLock;
uint64_t PathCacheGeneration;
IO_FILESYSTEM_STATS Stats;
FAT_PATH_CACHE_ENTRY PathCache[FAT_PATH_CACHE_SLOTS];
IO_VOLUME* Volume; // Active mount-list reference.
LIST_ENTRY MountLink;
};
typedef struct {
uint32_t StartCluster;
uint32_t FileSize;
FAT_VOL* Vol;
uint64_t DirentLba;
uint32_t DirentOffset;
SPINLOCK ChainCacheLock;
uint32_t ChainCacheClock;
FAT_CHAIN_CACHE_ENTRY ChainCache[FAT_CHAIN_CACHE_SLOTS];
} FAT_FILE;
// Raw sector I/O (bypasses FAT cache; used for file data and cache eviction).
static KSTATUS ReadSectorsRaw(FAT_VOL* v, uint64_t lba,
uint32_t count, void* buf) {
if (!v) return NTLSTATUS_INVALID_PARAMETER;
return IoReadDiskSectors(v->DriveIndex, lba, count, buf);
}
static KSTATUS WriteSectorsRaw(FAT_VOL* v, uint64_t lba,
uint32_t count, const void* buf) {
if (!v) return NTLSTATUS_INVALID_PARAMETER;
return IoWriteDiskSectors(v->DriveIndex, lba, count, buf);
}
// Convenience aliases for file-data I/O.
static KSTATUS ReadSectors (FAT_VOL* v, uint64_t lba, uint32_t n, void* b) { return ReadSectorsRaw (v, lba, n, b); }
// Updates sectors.
static KSTATUS WriteSectors(FAT_VOL* v, uint64_t lba, uint32_t n, const void* b) { return WriteSectorsRaw(v, lba, n, b); }
static const char* FatVariantName(FAT_VARIANT variant) {
switch (variant) {
case FatVariant16: return "FAT16";
case FatVariant32: return "FAT32";
case FatVariant12: return "FAT12";
default: return "FAT";
}
}
static uint32_t FatEocThreshold(FAT_VOL* v) {
return v && v->Variant == FatVariant16 ? FAT16_EOC : FAT32_EOC;
}
static uint32_t FatEocMark(FAT_VOL* v) {
return v && v->Variant == FatVariant16 ? FAT16_EOC_MARK : FAT32_EOC_MARK;
}
static int FatIsDataCluster(FAT_VOL* v, uint32_t cluster) {
return v && cluster >= 2u && cluster < FatEocThreshold(v);
}
static uint32_t FatDirentFirstCluster(FAT_VOL* v, const DIRENT* d) {
uint32_t lo = d ? d->FirstClusterLo : 0u;
uint32_t hi = (v && v->Variant == FatVariant32 && d) ? d->FirstClusterHi : 0u;
return (hi << 16) | lo;
}
static void FatSetDirentFirstCluster(FAT_VOL* v, DIRENT* d, uint32_t cluster) {
if (!d) return;
d->FirstClusterLo = (uint16_t)(cluster & 0xFFFFu);
d->FirstClusterHi = (v && v->Variant == FatVariant32) ? (uint16_t)(cluster >> 16) : 0u;
}
static int FatIsFixedRoot(FAT_VOL* v, uint32_t dirCluster) {
return v && v->Variant == FatVariant16 && dirCluster == FAT_FIXED_ROOT_CLUSTER;
}
static uint64_t ClusterToLba(FAT_VOL* v, uint32_t cluster);
static uint32_t FatEntry(FAT_VOL* v, uint32_t cluster);
static int FatDirectoryFirstSegment(FAT_VOL* v, uint32_t dirCluster,
uint32_t* cursor, uint64_t* lba,
uint32_t* sectors) {
if (!v || !cursor || !lba || !sectors) return 0;
if (FatIsFixedRoot(v, dirCluster)) {
if (!v->RootDirSectors) return 0;
*cursor = 0;
*lba = v->RootDirStart;
*sectors = 1;
return 1;
}
if (!FatIsDataCluster(v, dirCluster)) return 0;
*cursor = dirCluster;
*lba = ClusterToLba(v, dirCluster);
*sectors = v->SectorsPerCluster;
return 1;
}
static int FatDirectoryNextSegment(FAT_VOL* v, uint32_t dirCluster,
uint32_t* cursor, uint64_t* lba,
uint32_t* sectors) {
if (!v || !cursor || !lba || !sectors) return 0;
if (FatIsFixedRoot(v, dirCluster)) {
uint32_t next = *cursor + 1u;
if (next >= v->RootDirSectors) return 0;
*cursor = next;
*lba = v->RootDirStart + next;
*sectors = 1;
return 1;
}
uint32_t next = FatEntry(v, *cursor);
if (!FatIsDataCluster(v, next)) return 0;
*cursor = next;
*lba = ClusterToLba(v, next);
*sectors = v->SectorsPerCluster;
return 1;
}
// Implements the cluster To Lba operation.
static uint64_t ClusterToLba(FAT_VOL* v, uint32_t cluster) {
return v->DataStart + (uint64_t)(cluster - 2) * v->SectorsPerCluster;
}
// FlushFatCache — needs full FAT_VOL definition.
static void FlushFatCache(FAT_VOL* v) {
uint64_t fat0Start = (uint64_t)v->PartitionLba + v->ReservedSectors;
int held = FatCacheAcquire();
for (int i = 0; i < FAT_CACHE_SLOTS; i++) {
FAT_CACHE_ENTRY* e = &gFatCache[i];
if (!e->valid || !e->dirty || e->vol != v) 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;
}
FatCacheRelease(held);
}
// FAT read / write (through cache).
static uint32_t FatEntry(FAT_VOL* v, uint32_t cluster) {
if (!v || !FatIsDataCluster(v, cluster)) return 0;
uint32_t bytesPerEntry = v->Variant == FatVariant16 ? 2u : 4u;
uint32_t fatOffset = cluster * bytesPerEntry;
uint64_t sector = (uint64_t)v->PartitionLba + v->ReservedSectors
+ fatOffset / v->BytesPerSector;
uint32_t offInSec = fatOffset % v->BytesPerSector;
int held = FatCacheAcquire();
uint8_t* sec;
KSTATUS s = FatCacheRead(v, sector, &sec);
uint32_t value = 0;
if (KSUCCESS(s)) {
if (v->Variant == FatVariant16)
value = *(uint16_t*)(sec + offInSec);
else
value = *(uint32_t*)(sec + offInSec) & 0x0FFFFFFFu;
}
FatCacheRelease(held);
return value;
}
// Updates fat Entry.
static KSTATUS WriteFatEntry(FAT_VOL* v, uint32_t cluster, uint32_t value) {
if (!v || !FatIsDataCluster(v, cluster)) return NTLSTATUS_INVALID_PARAMETER;
uint32_t bytesPerEntry = v->Variant == FatVariant16 ? 2u : 4u;
uint32_t fatOffset = cluster * bytesPerEntry;
uint64_t sector = (uint64_t)v->PartitionLba + v->ReservedSectors
+ fatOffset / v->BytesPerSector;
uint32_t offInSec = fatOffset % v->BytesPerSector;
int held = FatCacheAcquire();
uint8_t* sec;
KSTATUS s = FatCacheRead(v, sector, &sec);
if (!KSUCCESS(s)) {
FatCacheRelease(held);
return s;
}
if (v->Variant == FatVariant16) {
*(uint16_t*)(sec + offInSec) = (uint16_t)(value & 0xFFFFu);
} else {
uint32_t existing = *(uint32_t*)(sec + offInSec);
*(uint32_t*)(sec + offInSec) = (existing & 0xF0000000u)
| (value & 0x0FFFFFFFu);
}
FatCacheMarkDirty(v, sector);
FatCacheRelease(held);
return NTLSTATUS_SUCCESS;
}
// Cluster allocation (fix #3: next-free hint + two-phase wrap-around scan).
static uint32_t AllocClusterEx(FAT_VOL* v, int zeroCluster) {
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) == FAT_FREE) {
if (!KSUCCESS(WriteFatEntry(v, c, FatEocMark(v)))) return 0;
v->NextFreeHint = c + 1;
// Normal file growth zeroes newly allocated clusters so old
// disk contents are never exposed. Swap/pagefile creation is
// the one exception: every allocated slot is overwritten before
// it can ever be read back, so skipping these writes avoids a
// slow boot-time full-pagefile zero pass.
if (zeroCluster) {
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 AllocCluster(FAT_VOL* v) {
return AllocClusterEx(v, 1);
}
static uint32_t AppendClusterEx(FAT_VOL* v, uint32_t lastCluster, int zeroCluster) {
uint32_t newCluster = AllocClusterEx(v, zeroCluster);
if (!newCluster) return 0;
if (!KSUCCESS(WriteFatEntry(v, lastCluster, newCluster))) return 0;
return newCluster;
}
// Appends cluster.
static uint32_t AppendCluster(FAT_VOL* v, uint32_t lastCluster) {
return AppendClusterEx(v, lastCluster, 1);
}
// Returns chain End.
static uint32_t GetChainEnd(FAT_VOL* v, uint32_t start, uint32_t* outCount) {
uint32_t cluster = start, prev = start, count = 0;
while (FatIsDataCluster(v, cluster)) {
prev = cluster; count++;
cluster = FatEntry(v, cluster);
}
if (outCount) *outCount = count;
return prev;
}
// Releases cluster Chain.
static void FreeClusterChain(FAT_VOL* v, uint32_t start) {
uint32_t cluster = start;
while (FatIsDataCluster(v, cluster)) {
uint32_t next = FatEntry(v, cluster);
WriteFatEntry(v, cluster, FAT_FREE);
if (cluster < v->NextFreeHint) v->NextFreeHint = cluster;
cluster = next;
}
}
// FAT timestamp and DIRENT metadata helpers.
typedef struct FAT_ENCODED_TIME {
uint16_t Date;
uint16_t Time;
uint8_t CreateTimeTenth;
} FAT_ENCODED_TIME;
typedef struct FAT_DIRENT_UPDATE {
uint32_t Flags;
uint32_t Size;
uint32_t FirstCluster;
FAT_ENCODED_TIME Timestamp;
} FAT_DIRENT_UPDATE;
#define FAT_DIRENT_UPDATE_SIZE 0x00000001u
#define FAT_DIRENT_UPDATE_FIRST_CLUSTER 0x00000002u
#define FAT_DIRENT_UPDATE_CREATE_TIME 0x00000004u
#define FAT_DIRENT_UPDATE_WRITE_TIME 0x00000008u
#define FAT_DIRENT_UPDATE_ACCESS_DATE 0x00000010u
// Implements the fat Is Leap Year operation.
static int FatIsLeapYear(uint32_t year) {
return ((year % 4u) == 0u && (year % 100u) != 0u) || ((year % 400u) == 0u);
}
// Implements the fat Days In Month operation.
static uint32_t FatDaysInMonth(uint32_t year, uint32_t month) {
static const uint8_t days[12] = { 31,28,31,30,31,30,31,31,30,31,30,31 };
if (month < 1u || month > 12u) return 31u;
if (month == 2u && FatIsLeapYear(year)) return 29u;
return days[month - 1u];
}
static uint64_t FatCalendarToEpochSeconds(uint32_t year, uint32_t month, uint32_t day,
uint32_t hour, uint32_t minute, uint32_t second) {
uint64_t days = 0;
for (uint32_t y = 1970u; y < year; y++)
days += FatIsLeapYear(y) ? 366u : 365u;
for (uint32_t m = 1u; m < month; m++)
days += FatDaysInMonth(year, m);
days += day - 1u;
return (((days * 24u) + hour) * 60u + minute) * 60u + second;
}
// Implements the fat Day Of Week operation.
static uint8_t FatDayOfWeek(uint32_t year, uint32_t month, uint32_t day) {
uint64_t epoch = FatCalendarToEpochSeconds(year, month, day, 0, 0, 0);
return (uint8_t)(((epoch / 86400u) + 4u) % 7u);
}
// Implements the fat Encode Date operation.
static uint16_t FatEncodeDate(const SYS_SYSTEM_TIME* t) {
uint32_t year = t && t->Year ? t->Year : 1980u;
uint32_t month = t && t->Month ? t->Month : 1u;
uint32_t day = t && t->Day ? t->Day : 1u;
if (year < 1980u) year = 1980u;
if (year > 2107u) year = 2107u;
if (month < 1u) month = 1u;
if (month > 12u) month = 12u;
uint32_t dim = FatDaysInMonth(year, month);
if (day < 1u) day = 1u;
if (day > dim) day = dim;
return (uint16_t)(((year - 1980u) << 9) | (month << 5) | day);
}
// Implements the fat Encode Time operation.
static uint16_t FatEncodeTime(const SYS_SYSTEM_TIME* t) {
uint32_t hour = t ? t->Hour : 0u;
uint32_t minute = t ? t->Minute : 0u;
uint32_t second = t ? t->Second : 0u;
if (hour > 23u) hour = 23u;
if (minute > 59u) minute = 59u;
if (second > 59u) second = 59u;
return (uint16_t)((hour << 11) | (minute << 5) | (second / 2u));
}
// Implements the fat Encode Create Time Tenth operation.
static uint8_t FatEncodeCreateTimeTenth(const SYS_SYSTEM_TIME* t) {
uint32_t second = t ? t->Second : 0u;
uint32_t ms = t ? t->Millisecond : 0u;
if (second > 59u) second = 59u;
if (ms > 999u) ms = 999u;
uint32_t v = ((second & 1u) * 100u) + (ms / 10u);
if (v > 199u) v = 199u;
return (uint8_t)v;
}
// Implements the fat Query Encoded Time operation.
static void FatQueryEncodedTime(FAT_ENCODED_TIME* out) {
SYS_SYSTEM_TIME now;
memset(&now, 0, sizeof(now));
if (KeQuerySystemTime(&now) != NTLSTATUS_SUCCESS) {
now.Year = 1980;
now.Month = 1;
now.Day = 1;
}
out->Date = FatEncodeDate(&now);
out->Time = FatEncodeTime(&now);
out->CreateTimeTenth = FatEncodeCreateTimeTenth(&now);
}
static void FatDecodeDateTime(uint16_t date, uint16_t time, uint8_t createTenth,
SYS_SYSTEM_TIME* out) {
memset(out, 0, sizeof(*out));
uint32_t year = 1980u + ((date >> 9) & 0x7Fu);
uint32_t month = (date >> 5) & 0x0Fu;
uint32_t day = date & 0x1Fu;
uint32_t hour = (time >> 11) & 0x1Fu;
uint32_t minute = (time >> 5) & 0x3Fu;
uint32_t second = (time & 0x1Fu) * 2u;
if (date == 0 || month < 1u || month > 12u) { year = 1980u; month = 1u; day = 1u; }
uint32_t dim = FatDaysInMonth(year, month);
if (day < 1u) day = 1u;
if (day > dim) day = dim;
if (hour > 23u) hour = 23u;
if (minute > 59u) minute = 59u;
if (second > 58u) second = 58u;
if (createTenth >= 100u && second < 59u) second++;
out->Year = (uint16_t)year;
out->Month = (uint8_t)month;
out->Day = (uint8_t)day;
out->Hour = (uint8_t)hour;
out->Minute = (uint8_t)minute;
out->Second = (uint8_t)second;
out->Millisecond = (uint16_t)((createTenth % 100u) * 10u);
out->DayOfWeek = FatDayOfWeek(year, month, day);
out->Flags = SYS_TIME_FLAG_VALID;
out->EpochSeconds = FatCalendarToEpochSeconds(year, month, day, hour, minute, second);
}
// Implements the fat Decode Access Date operation.
static void FatDecodeAccessDate(uint16_t date, SYS_SYSTEM_TIME* out) {
FatDecodeDateTime(date, 0, 0, out);
}
static KSTATUS DirentUpdateMetadata(FAT_VOL* v, uint64_t lba,
uint32_t offset,
const FAT_DIRENT_UPDATE* update) {
if (!v || !update || !update->Flags) return NTLSTATUS_INVALID_PARAMETER;
uint8_t* sec = (uint8_t*)MmAllocatePool(v->BytesPerSector);
if (!sec) return NTLSTATUS_NO_MEMORY;
KSTATUS s = ReadSectors(v, lba, 1, sec);
if (KSUCCESS(s)) {
DIRENT* d = (DIRENT*)(sec + offset);
if (update->Flags & FAT_DIRENT_UPDATE_SIZE)
d->FileSize = update->Size;
if (update->Flags & FAT_DIRENT_UPDATE_FIRST_CLUSTER)
FatSetDirentFirstCluster(v, d, update->FirstCluster);
if (update->Flags & FAT_DIRENT_UPDATE_CREATE_TIME) {
d->CrtTimeTenth = update->Timestamp.CreateTimeTenth;
d->CrtTime = update->Timestamp.Time;
d->CrtDate = update->Timestamp.Date;
}
if (update->Flags & FAT_DIRENT_UPDATE_WRITE_TIME) {
d->WrtTime = update->Timestamp.Time;
d->WrtDate = update->Timestamp.Date;
}
if (update->Flags & FAT_DIRENT_UPDATE_ACCESS_DATE)
d->LastAccDate = update->Timestamp.Date;
s = WriteSectors(v, lba, 1, sec);
}
MmFreePool(sec);
return s;
}
static KSTATUS DirentUpdateSize(FAT_VOL* v, uint64_t lba,
uint32_t offset, uint32_t size) {
FAT_DIRENT_UPDATE u;
memset(&u, 0, sizeof(u));
FatQueryEncodedTime(&u.Timestamp);
u.Flags = FAT_DIRENT_UPDATE_SIZE | FAT_DIRENT_UPDATE_WRITE_TIME;
u.Size = size;
return DirentUpdateMetadata(v, lba, offset, &u);
}
static KSTATUS DirentUpdateFirstCluster(FAT_VOL* v, uint64_t lba,
uint32_t offset, uint32_t cluster) {
FAT_DIRENT_UPDATE u;
memset(&u, 0, sizeof(u));
u.Flags = FAT_DIRENT_UPDATE_FIRST_CLUSTER;
u.FirstCluster = cluster;
return DirentUpdateMetadata(v, lba, offset, &u);
}
// 8.3 name matching / assembly.
static int MatchRaw83(const char dirName[8], const char dirExt[3],
const char wantName[8], const char wantExt[3]) {
return memcmp(dirName, wantName, 8) == 0 &&
memcmp(dirExt, wantExt, 3) == 0;
}
static char FatUpperChar(char c);
static int FatIsLowerChar(char c);
static int FatIsValidShortChar(char c);
// Implements the make83 Name V operation.
static void Make83NameV(SYS_STRING_VIEW src, char name8[8], char ext3[3]) {
memset(name8, ' ', 8);
memset(ext3, ' ', 3);
if (!SysSvIsValid(src) || src.Length == 0) return;
size_t baseStart = 0;
size_t dot = (size_t)-1;
if (src.Data[0] == '.' && src.Length > 1) {
baseStart = 1;
} else {
for (size_t i = 0; i < src.Length; i++)
if (src.Data[i] == '.') dot = i;
}
size_t end = (dot != (size_t)-1) ? dot : src.Length;
int ni = 0;
for (size_t i = baseStart; i < end && ni < 8; i++) {
char c = FatUpperChar(src.Data[i]);
name8[ni++] = c;
}
if (dot != (size_t)-1) {
int ei = 0;
for (size_t i = dot + 1; i < src.Length && ei < 3; i++) {
char c = FatUpperChar(src.Data[i]);
ext3[ei++] = c;
}
}
}
// LFN assembly / matching.
#define MAX_LFN 256
// Implements the fat Upper Char operation.
static char FatUpperChar(char c) {
return (c >= 'a' && c <= 'z') ? (char)(c - 32) : c;
}
// Implements the fat Is Lower Char operation.
static int FatIsLowerChar(char c) {
return c >= 'a' && c <= 'z';
}
// Implements the fat Is Valid Short Char operation.
static int FatIsValidShortChar(char c) {
c = FatUpperChar(c);
if (c >= 'A' && c <= 'Z') return 1;
if (c >= '0' && c <= '9') return 1;
switch (c) {
case '$': case '%': case '\'': case '-': case '_': case '@':
case '~': case '`': case '!': case '(': case ')': case '{':
case '}': case '^': case '#': case '&':
return 1;
default:
return 0;
}
}
// True when the user spelling can refer to a normal FAT 8.3 entry. This is
// intentionally structural and case-insensitive: "doom1.wad" must still open
// an existing short entry named DOOM1.WAD. Leading-dot names are not 8.3.
static int FatNameIsStructurallyShort83V(SYS_STRING_VIEW name) {
if (!SysSvIsValid(name) || name.Length == 0) return 0;
if (name.Data[0] == '.') return 0;
size_t dot = (size_t)-1;
for (size_t i = 0; i < name.Length; i++) {
char c = name.Data[i];
if (c == '.') {
if (dot != (size_t)-1) return 0;
dot = i;
} else if (!FatIsValidShortChar(c)) {
return 0;
}
}
size_t baseLen = (dot != (size_t)-1) ? dot : name.Length;
size_t extLen = (dot != (size_t)-1) ? (name.Length - dot - 1) : 0;
return baseLen >= 1 && baseLen <= 8 && extLen <= 3;
}
// Implements the fat Component Has Lower V operation.
static int FatComponentHasLowerV(SYS_STRING_VIEW name, size_t start, size_t end) {
for (size_t i = start; i < end; i++)
if (FatIsLowerChar(name.Data[i])) return 1;
return 0;
}
// Implements the fat Component Has Upper V operation.
static int FatComponentHasUpperV(SYS_STRING_VIEW name, size_t start, size_t end) {
for (size_t i = start; i < end; i++)
if (name.Data[i] >= 'A' && name.Data[i] <= 'Z') return 1;
return 0;
}
static void FatSplitShortNameV(SYS_STRING_VIEW name, size_t* baseEnd,
size_t* extStart, size_t* extEnd) {
size_t dot = (size_t)-1;
for (size_t i = 0; i < name.Length; i++)
if (name.Data[i] == '.') dot = i;
if (dot == (size_t)-1) {
*baseEnd = name.Length;
*extStart = name.Length;
*extEnd = name.Length;
} else {
*baseEnd = dot;
*extStart = dot + 1u;
*extEnd = name.Length;
}
}
// Creation uses LFN only when the spelling cannot be represented by a short
// entry, or when mixed-case 8.3 spelling would be lost. Plain lowercase
// 8.3 names are represented with FAT's NT case bits, not VFAT LFN entries.
static int FatNameNeedsLfnForCreateV(SYS_STRING_VIEW name) {
if (!FatNameIsStructurallyShort83V(name)) return 1;
size_t baseEnd, extStart, extEnd;
FatSplitShortNameV(name, &baseEnd, &extStart, &extEnd);
int baseLower = FatComponentHasLowerV(name, 0, baseEnd);
int baseUpper = FatComponentHasUpperV(name, 0, baseEnd);
int extLower = FatComponentHasLowerV(name, extStart, extEnd);
int extUpper = FatComponentHasUpperV(name, extStart, extEnd);
if ((baseLower && baseUpper) || (extLower && extUpper)) return 1;
return 0;
}
// Implements the fat Short Name Case Flags V operation.
static uint8_t FatShortNameCaseFlagsV(SYS_STRING_VIEW name) {
if (!FatNameIsStructurallyShort83V(name)) return 0;
size_t baseEnd, extStart, extEnd;
FatSplitShortNameV(name, &baseEnd, &extStart, &extEnd);
uint8_t flags = 0;
if (FatComponentHasLowerV(name, 0, baseEnd) &&
!FatComponentHasUpperV(name, 0, baseEnd))
flags |= FAT_NTRES_BASE_LOWER;
if (extStart < extEnd &&
FatComponentHasLowerV(name, extStart, extEnd) &&
!FatComponentHasUpperV(name, extStart, extEnd))
flags |= FAT_NTRES_EXT_LOWER;
return flags;
}
// Implements the lfn Checksum operation.
static uint8_t LfnChecksum(const char name8[8], const char ext3[3]) {
uint8_t sum = 0;
for (int i = 0; i < 11; i++) {
uint8_t c = (uint8_t)(i < 8 ? name8[i] : ext3[i - 8]);
sum = (uint8_t)(((sum & 1) ? 0x80 : 0) + (sum >> 1) + c);
}
return sum;
}
// Implements the lfn Set Char operation.
static void LfnSetChar(LFN_ENTRY* e, int idx, uint16_t value) {
if (idx < 5) e->Name1[idx] = value;
else if (idx < 11) e->Name2[idx - 5] = value;
else e->Name3[idx - 11] = value;
}
static void BuildLfnEntry(LFN_ENTRY* e, const char* name, size_t nameLen,
uint32_t ordinal, uint32_t lfnCount,
uint8_t checksum) {
memset(e, 0, sizeof(*e));
e->Order = (uint8_t)ordinal;
if (ordinal == lfnCount) e->Order |= 0x40;
e->Attr = ATTR_LFN;
e->Type = 0;
e->Checksum = checksum;
e->FirstCluster = 0;
size_t start = (size_t)(ordinal - 1) * 13u;
for (int i = 0; i < 13; i++) {
size_t pos = start + (size_t)i;
uint16_t ch;
if (pos < nameLen) ch = (uint8_t)name[pos];
else if (pos == nameLen) ch = 0x0000;
else ch = 0xFFFF;
LfnSetChar(e, i, ch);
}
}
// Creates legacy Broken Dot83.
static void MakeLegacyBrokenDot83(const char* src, char name8[8], char ext3[3]) {
// This exactly models the pre-fix bug for ".doomrc": empty name + "DOO".
memset(name8, ' ', 8);
memset(ext3, ' ', 3);
const char* dot = NULL;
for (const char* p = src; *p; p++) if (*p == '.') dot = p;
if (!dot) return;
int ei = 0;
for (const char* p = dot + 1; *p && ei < 3; p++)
ext3[ei++] = FatUpperChar(*p);
}
static int ShortNameExistsRaw(FAT_VOL* v, uint32_t dirCluster,
const char name8[8], const char ext3[3]) {
uint8_t* clusterBuf = (uint8_t*)MmAllocatePool(v->BytesPerCluster);
if (!clusterBuf) return 1; // fail closed; caller should pick another alias
uint32_t cursor; uint64_t lba; uint32_t sectors;
if (!FatDirectoryFirstSegment(v, dirCluster, &cursor, &lba, &sectors)) {
MmFreePool(clusterBuf);
return 0;
}
do {
if (!KSUCCESS(ReadSectors(v, lba, sectors, clusterBuf))) break;
uint32_t entries = (sectors * v->BytesPerSector) / sizeof(DIRENT);
DIRENT* dir = (DIRENT*)clusterBuf;
for (uint32_t i = 0; i < entries; i++) {
if (dir[i].Name[0] == 0x00) { MmFreePool(clusterBuf); return 0; }
if ((uint8_t)dir[i].Name[0] == 0xE5 || dir[i].Attr == ATTR_LFN) continue;
if (MatchRaw83(dir[i].Name, dir[i].Ext, name8, ext3)) {
MmFreePool(clusterBuf);
return 1;
}
}
} while (FatDirectoryNextSegment(v, dirCluster, &cursor, &lba, &sectors));
MmFreePool(clusterBuf);
return 0;
}
// Implements the fat Alias Append Char operation.
static void FatAliasAppendChar(char* dst, int* len, int maxLen, char c) {
c = FatUpperChar(c);
if (!FatIsValidShortChar(c)) return;
if (*len < maxLen) dst[(*len)++] = c;
}
// Builds alias Stem Ext V.
static void BuildAliasStemExtV(SYS_STRING_VIEW src, char stem[9], char ext3[3]) {
memset(stem, 0, 9);
memset(ext3, ' ', 3);
if (!SysSvIsValid(src)) return;
size_t baseStart = 0;
size_t dot = (size_t)-1;
if (src.Length > 1 && src.Data[0] == '.') {
// Hidden/Unix-style names like .doomrc become alias stem DOOMRC.
baseStart = 1;
} else {
for (size_t i = 0; i < src.Length; i++)
if (src.Data[i] == '.') dot = i;
}
int slen = 0;
size_t end = (dot != (size_t)-1) ? dot : src.Length;
for (size_t i = baseStart; i < end; i++)
FatAliasAppendChar(stem, &slen, 8, src.Data[i]);
if (slen == 0) {
memcpy(stem, "FILE", 4);
slen = 4;
}
stem[slen] = '\0';
if (dot != (size_t)-1 && dot + 1 < src.Length) {
int ei = 0;
for (size_t i = dot + 1; i < src.Length && ei < 3; i++) {
char c = FatUpperChar(src.Data[i]);
if (!FatIsValidShortChar(c)) continue;
ext3[ei++] = c;
}
}
}
static void MakeAliasAttempt(const char stem[9], const char extIn[3],
uint32_t attempt, char name8[8], char ext3[3]) {
memset(name8, ' ', 8);
memcpy(ext3, extIn, 3);
char suffix[8];
int suffixLen = 0;
suffix[suffixLen++] = '~';
char digits[7];
int digitLen = 0;
uint32_t n = attempt;
do {
digits[digitLen++] = (char)('0' + (n % 10));
n /= 10;
} while (n && digitLen < (int)sizeof(digits));
for (int i = digitLen - 1; i >= 0; i--)
suffix[suffixLen++] = digits[i];
int stemLen = (int)strlen(stem);
int keep = 8 - suffixLen;
if (keep < 1) keep = 1;
if (stemLen < keep) keep = stemLen;
memcpy(name8, stem, keep);
memcpy(name8 + keep, suffix, suffixLen);
}
static int MakeUniqueShortAliasV(FAT_VOL* v, uint32_t dirCluster,
SYS_STRING_VIEW longName,
char name8[8], char ext3[3]) {
char stem[9], extCandidate[3];
BuildAliasStemExtV(longName, stem, extCandidate);
for (uint32_t attempt = 1; attempt < 1000000; attempt++) {
MakeAliasAttempt(stem, extCandidate, attempt, name8, ext3);
if (!ShortNameExistsRaw(v, dirCluster, name8, ext3)) return 1;
}
return 0;
}
// Creates legacy Broken Dot83 V.
static void MakeLegacyBrokenDot83V(SYS_STRING_VIEW src, char name8[8], char ext3[3]) {
// This exactly models the pre-fix bug for ".doomrc": empty name + "DOO".
memset(name8, ' ', 8);
memset(ext3, ' ', 3);
if (!SysSvIsValid(src)) return;
size_t dot = (size_t)-1;
for (size_t i = 0; i < src.Length; i++) if (src.Data[i] == '.') dot = i;
if (dot == (size_t)-1) return;
int ei = 0;
for (size_t i = dot + 1; i < src.Length && ei < 3; i++)
ext3[ei++] = FatUpperChar(src.Data[i]);
}
static KSTATUS DeleteLegacyBrokenDotAliasV(FAT_VOL* v, uint32_t dirCluster,
SYS_STRING_VIEW longName) {
if (!SysSvIsValid(longName) || longName.Length < 2 || longName.Data[0] != '.') return NTLSTATUS_SUCCESS;
char badName[8], badExt[3];
MakeLegacyBrokenDot83V(longName, badName, badExt);
uint8_t* clusterBuf = (uint8_t*)MmAllocatePool(v->BytesPerCluster);
if (!clusterBuf) return NTLSTATUS_NO_MEMORY;
uint32_t cursor; uint64_t lba; uint32_t sectors;
if (!FatDirectoryFirstSegment(v, dirCluster, &cursor, &lba, &sectors)) {
MmFreePool(clusterBuf);
return NTLSTATUS_SUCCESS;
}
do {
KSTATUS s = ReadSectors(v, lba, sectors, clusterBuf);
if (!KSUCCESS(s)) { MmFreePool(clusterBuf); return s; }
uint32_t entries = (sectors * v->BytesPerSector) / sizeof(DIRENT);
DIRENT* dir = (DIRENT*)clusterBuf;
for (uint32_t i = 0; i < entries; i++) {
if (dir[i].Name[0] == 0x00) { MmFreePool(clusterBuf); return NTLSTATUS_SUCCESS; }
if ((uint8_t)dir[i].Name[0] == 0xE5 || dir[i].Attr == ATTR_LFN) continue;
if (MatchRaw83(dir[i].Name, dir[i].Ext, badName, badExt)) {
int emptyBase = 1;
for (int j = 0; j < 8; j++) if (dir[i].Name[j] != ' ') emptyBase = 0;
if (!emptyBase) continue;
// Do not free the cluster chain here. The broken entry may have
// aliased existing data; deleting the directory entry is enough to
// hide it from hosts and avoids freeing clusters still owned by a
// valid file.
dir[i].Name[0] = (char)0xE5;
s = WriteSectors(v, lba, sectors, clusterBuf);
MmFreePool(clusterBuf);
return s;
}
}
} while (FatDirectoryNextSegment(v, dirCluster, &cursor, &lba, &sectors));
MmFreePool(clusterBuf);
return NTLSTATUS_SUCCESS;
}
// Implements the lfn Append operation.
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);
}
}
// Directory search and view-native path traversal.
static int FatIsPathSep(char c) {
return c == '\\' || c == '/';
}
// Tests whether a non-empty FAT path view points at plausible kernel-owned storage.
static int FatStringViewDataIsPlausible(SYS_STRING_VIEW view) {
if (!SysSvIsValid(view)) return 0;
if (view.Length == 0) return 1;
uintptr_t data = (uintptr_t)view.Data;
return data >= 0x10000u;
}
// Implements the name View Is Valid operation.
static int FatNameViewIsValid(SYS_STRING_VIEW name) {
if (!FatStringViewDataIsPlausible(name)) return 0;
if (name.Length == 0 || name.Length >= MAX_LFN) return 0;
for (size_t i = 0; i < name.Length; i++) {
char c = name.Data[i];
if (c == '\0' || FatIsPathSep(c)) return 0;
}
return 1;
}
// Implements the lfn Match V operation.
static int LfnMatchV(const char* lfn, SYS_STRING_VIEW want) {
if (!lfn || !SysSvIsValid(want)) return 0;
size_t i = 0;
while (lfn[i] && i < want.Length) {
char a = lfn[i], b = want.Data[i];
if (a >= 'A' && a <= 'Z') a += 32;
if (b >= 'A' && b <= 'Z') b += 32;
if (a != b) return 0;
i++;
}
return lfn[i] == '\0' && i == want.Length;
}
static int MatchName83V(const char* dirName, const char* dirExt,
SYS_STRING_VIEW 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]);
}
if ((size_t)n != want.Length) return 0;
for (int i = 0; i < n; i++) {
char a = buf[i], b = want.Data[i];
if (a >= 'A' && a <= 'Z') a += 32;
if (b >= 'A' && b <= 'Z') b += 32;
if (a != b) return 0;
}
return 1;
}
// Returns the path cache canonical byte used for hashes and comparisons.
static char FatPathCacheChar(char c) {
if (c == '/') return '\\';
if (c >= 'A' && c <= 'Z') return (char)(c + 32);
return c;
}
// Hashes a volume-relative path using the same case/separator rules as lookup.
static uint32_t FatPathCacheHash(SYS_STRING_VIEW path) {
uint32_t h = 2166136261u;
for (size_t i = 0; i < path.Length; i++) {
h ^= (uint8_t)FatPathCacheChar(path.Data[i]);
h *= 16777619u;
}
return h ? h : 1u;
}
// Tests whether the cached path names the same FAT path as the caller's view.
static bool FatPathCachePathEquals(const FAT_PATH_CACHE_ENTRY* e, SYS_STRING_VIEW path) {
if (!e || e->PathLength != path.Length) return false;
for (size_t i = 0; i < path.Length; i++) {
if (FatPathCacheChar(e->Path[i]) != FatPathCacheChar(path.Data[i]))
return false;
}
return true;
}
// Adds directory search counters without holding the lock during disk I/O.
static void FatPathCacheAddDirectoryStats(FAT_VOL* v, uint64_t clusters, uint64_t entries) {
if (!v) return;
uintptr_t flags = KeAcquireSpinlock(&v->PathCacheLock);
v->Stats.DirectorySearches++;
v->Stats.DirectoryBlocksRead += clusters;
v->Stats.DirectoryEntriesScanned += entries;
KeReleaseSpinlock(&v->PathCacheLock, flags);
}
// Looks up a positive path-cache entry for a full volume-relative path.
static bool FatPathCacheLookup(FAT_VOL* v, SYS_STRING_VIEW path,
uint32_t* outCluster, uint32_t* outSize,
uint8_t* outAttr, uint64_t* outDirentLba,
uint32_t* outDirentOffset) {
if (!v || path.Length == 0 || path.Length >= FAT_PATH_CACHE_MAX)
return false;
uint32_t hash = FatPathCacheHash(path);
uint32_t slot = hash % FAT_PATH_CACHE_SLOTS;
bool hit = false;
uintptr_t flags = KeAcquireSpinlock(&v->PathCacheLock);
v->Stats.PathLookups++;
FAT_PATH_CACHE_ENTRY* e = &v->PathCache[slot];
if (e->Valid && e->Generation == v->PathCacheGeneration &&
e->Hash == hash && FatPathCachePathEquals(e, path)) {
if (outCluster) *outCluster = e->Cluster;
if (outSize) *outSize = e->Size;
if (outAttr) *outAttr = e->Attr;
if (outDirentLba) *outDirentLba = e->DirentLba;
if (outDirentOffset) *outDirentOffset = e->DirentOffset;
v->Stats.PathCacheHits++;
hit = true;
} else {
v->Stats.PathCacheMisses++;
}
KeReleaseSpinlock(&v->PathCacheLock, flags);
return hit;
}
// Stores a positive path lookup in the volume-local path cache.
static void FatPathCacheStore(FAT_VOL* v, SYS_STRING_VIEW path,
uint32_t cluster, uint32_t size, uint8_t attr,
uint64_t direntLba, uint32_t direntOffset) {
if (!v || path.Length == 0 || path.Length >= FAT_PATH_CACHE_MAX)
return;
uint32_t hash = FatPathCacheHash(path);
uint32_t slot = hash % FAT_PATH_CACHE_SLOTS;
uintptr_t flags = KeAcquireSpinlock(&v->PathCacheLock);
FAT_PATH_CACHE_ENTRY* e = &v->PathCache[slot];
memset(e, 0, sizeof(*e));
e->Valid = true;
e->Generation = v->PathCacheGeneration;
e->Hash = hash;
e->PathLength = path.Length;
memcpy(e->Path, path.Data, path.Length);
e->Path[path.Length] = '\0';
e->Cluster = cluster;
e->Size = size;
e->Attr = attr;
e->DirentLba = direntLba;
e->DirentOffset = direntOffset;
v->Stats.PathCacheStores++;
KeReleaseSpinlock(&v->PathCacheLock, flags);
}
// Invalidates every positive path-cache entry after a directory or size mutation.
static void FatPathCacheInvalidate(FAT_VOL* v) {
if (!v) return;
uintptr_t flags = KeAcquireSpinlock(&v->PathCacheLock);
v->PathCacheGeneration++;
if (v->PathCacheGeneration == 0)
v->PathCacheGeneration = 1;
v->Stats.PathCacheInvalidations++;
v->Stats.PathCacheGeneration = v->PathCacheGeneration;
for (uint32_t i = 0; i < FAT_PATH_CACHE_SLOTS; i++)
v->PathCache[i].Valid = false;
KeReleaseSpinlock(&v->PathCacheLock, flags);
}
// Returns the current filesystem cache and traversal counters.
void FatQueryStats(IO_VOLUME_PROVIDER* fs, IO_FILESYSTEM_STATS* out) {
if (!fs || !out) return;
FAT_VOL* v = (FAT_VOL*)fs->Context;
if (!v) { memset(out, 0, sizeof(*out)); return; }
uintptr_t flags = KeAcquireSpinlock(&v->PathCacheLock);
*out = v->Stats;
out->PathCacheGeneration = v->PathCacheGeneration;
KeReleaseSpinlock(&v->PathCacheLock, flags);
}
// Records one data read span and its physically contiguous cluster merges.
static void FatStatsAddReadRun(FAT_VOL* v, uint32_t sectors, uint32_t merges) {
if (!v || !sectors) return;
uintptr_t flags = KeAcquireSpinlock(&v->PathCacheLock);
v->Stats.DataReadRuns++;
v->Stats.DataReadSectors += sectors;
v->Stats.DataReadRunMerges += merges;
if (v->Stats.MaxDataReadSpanSectors < sectors)
v->Stats.MaxDataReadSpanSectors = sectors;
KeReleaseSpinlock(&v->PathCacheLock, flags);
}
// Initializes the per-open cluster-chain cache for a FAT file object.
static void FatFileInitChainCache(FAT_FILE* f) {
if (!f) return;
f->ChainCacheLock = (SPINLOCK)SPINLOCK_INIT;
f->ChainCacheClock = 0;
memset(f->ChainCache, 0, sizeof(f->ChainCache));
}
// Invalidates cached FAT extents after the file's cluster chain changes.
static void FatFileInvalidateChainCache(FAT_FILE* f) {
if (!f) return;
uintptr_t flags = KeAcquireSpinlock(&f->ChainCacheLock);
memset(f->ChainCache, 0, sizeof(f->ChainCache));
f->ChainCacheClock = 0;
KeReleaseSpinlock(&f->ChainCacheLock, flags);
}
// Locates the best cached starting point for a logical cluster index.
static bool FatChainCacheLookupStart(FAT_FILE* f, uint32_t logicalIndex,
uint32_t* outLogical, uint32_t* outCluster) {
if (!f || !outLogical || !outCluster)
return false;
bool hit = false;
uint32_t bestLogical = 0;
uint32_t bestCluster = 0;
uintptr_t flags = KeAcquireSpinlock(&f->ChainCacheLock);
for (uint32_t i = 0; i < FAT_CHAIN_CACHE_SLOTS; i++) {
FAT_CHAIN_CACHE_ENTRY* e = &f->ChainCache[i];
if (!e->Valid || e->Length == 0)
continue;
uint32_t end = e->LogicalStart + e->Length;
if (logicalIndex >= e->LogicalStart && logicalIndex < end) {
*outLogical = logicalIndex;
*outCluster = e->PhysicalStart + (logicalIndex - e->LogicalStart);
hit = true;
break;
}
if (end <= logicalIndex && FatIsDataCluster(f->Vol, e->NextCluster)) {
if (!hit || end > bestLogical) {
bestLogical = end;
bestCluster = e->NextCluster;
hit = true;
}
}
}
if (hit && bestCluster) {
*outLogical = bestLogical;
*outCluster = bestCluster;
}
KeReleaseSpinlock(&f->ChainCacheLock, flags);
return hit;
}
// Stores a physically contiguous FAT extent in the per-open chain cache.
static void FatChainCacheStore(FAT_FILE* f, uint32_t logicalStart,
uint32_t physicalStart, uint32_t length,
uint32_t nextCluster) {
if (!f || !FatIsDataCluster(f->Vol, physicalStart) || length == 0)
return;
uintptr_t flags = KeAcquireSpinlock(&f->ChainCacheLock);
uint32_t slot = f->ChainCacheClock++ % FAT_CHAIN_CACHE_SLOTS;
FAT_CHAIN_CACHE_ENTRY* e = &f->ChainCache[slot];
e->Valid = true;
e->LogicalStart = logicalStart;
e->PhysicalStart = physicalStart;
e->Length = length;
e->NextCluster = nextCluster;
KeReleaseSpinlock(&f->ChainCacheLock, flags);
}
// Resolves a logical file cluster through the cached FAT-chain extent map.
static uint32_t FatResolveFileCluster(FAT_FILE* f, uint32_t logicalIndex) {
if (!f || !FatIsDataCluster(f->Vol, f->StartCluster))
return 0;
if (logicalIndex == 0)
return f->StartCluster;
uint32_t currentLogical = 0;
uint32_t cluster = f->StartCluster;
if (!FatChainCacheLookupStart(f, logicalIndex, &currentLogical, &cluster)) {
currentLogical = 0;
cluster = f->StartCluster;
}
while (currentLogical < logicalIndex && FatIsDataCluster(f->Vol, cluster)) {
cluster = FatEntry(f->Vol, cluster);
currentLogical++;
}
return cluster;
}
// Builds and caches one physically contiguous read run from a starting cluster.
static bool FatBuildReadRun(FAT_FILE* f, uint32_t logicalIndex,
uint32_t startCluster, uint32_t maxSectors,
uint32_t* outRunSectors, uint32_t* outNextCluster) {
if (!f || !outRunSectors || !outNextCluster)
return false;
FAT_VOL* v = f->Vol;
if (!v || !FatIsDataCluster(v, startCluster))
return false;
uint32_t runSectors = v->SectorsPerCluster;
uint32_t runClusters = 1;
uint32_t last = startCluster;
uint32_t next = FatEntry(v, last);
while (next == last + 1 && FatIsDataCluster(v, next) &&
runSectors + v->SectorsPerCluster <= maxSectors) {
last = next;
runClusters++;
runSectors += v->SectorsPerCluster;
next = FatEntry(v, last);
}
FatChainCacheStore(f, logicalIndex, startCluster, runClusters, next);
*outRunSectors = runSectors;
*outNextCluster = next;
return true;
}
static int SearchDirV(FAT_VOL* v, uint32_t dirCluster, SYS_STRING_VIEW name,
uint32_t* outCluster, uint32_t* outSize, uint8_t* outAttr,
uint64_t* outDirentLba, uint32_t* outDirentOffset) {
if (!FatNameViewIsValid(name)) return 0;
uint8_t* clusterBuf = (uint8_t*)MmAllocatePool(v->BytesPerCluster);
if (!clusterBuf) return 0;
char lfn[MAX_LFN]; int hasLfn = 0; memset(lfn, 0, sizeof(lfn));
char wantName83[8], wantExt83[3];
int allowShortMatch = FatNameIsStructurallyShort83V(name);
Make83NameV(name, wantName83, wantExt83);
uint64_t clustersRead = 0;
uint64_t entriesScanned = 0;
int found = 0;
uint32_t cursor; uint64_t lba; uint32_t sectors;
if (!FatDirectoryFirstSegment(v, dirCluster, &cursor, &lba, &sectors))
goto done;
do {
if (!KSUCCESS(ReadSectors(v, lba, sectors, clusterBuf))) break;
clustersRead++;
uint32_t entries = (sectors * v->BytesPerSector) / sizeof(DIRENT);
DIRENT* dir = (DIRENT*)clusterBuf;
for (uint32_t i = 0; i < entries; i++) {
entriesScanned++;
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 = LfnMatchV(lfn, name);
if (!match && allowShortMatch)
match = MatchName83V(dir[i].Name, dir[i].Ext, name);
if (!match && allowShortMatch)
match = MatchRaw83(dir[i].Name, dir[i].Ext, wantName83, wantExt83);
hasLfn = 0; memset(lfn, 0, sizeof(lfn));
if (match) {
*outCluster = FatDirentFirstCluster(v, &dir[i]);
*outSize = dir[i].FileSize;
*outAttr = dir[i].Attr;
if (outDirentLba || outDirentOffset) {
uint64_t eb = (uint64_t)i * sizeof(DIRENT);
if (outDirentLba) *outDirentLba = lba + eb / v->BytesPerSector;
if (outDirentOffset) *outDirentOffset = (uint32_t)(eb % v->BytesPerSector);
}
found = 1;
goto done;
}
}
} while (FatDirectoryNextSegment(v, dirCluster, &cursor, &lba, &sectors));
done:
FatPathCacheAddDirectoryStats(v, clustersRead, entriesScanned);
MmFreePool(clusterBuf);
return found;
}
static int FatPathNextComponent(SYS_STRING_VIEW path, size_t* cursor,
SYS_STRING_VIEW* component) {
if (!cursor || !component || !SysSvIsValid(path)) return 0;
size_t pos = *cursor;
while (pos < path.Length && FatIsPathSep(path.Data[pos])) pos++;
if (pos >= path.Length) {
*cursor = pos;
*component = SYS_SV_NULL;
return 0;
}
size_t start = pos;
while (pos < path.Length && !FatIsPathSep(path.Data[pos])) pos++;
*cursor = pos;
*component = SysSvFromParts(path.Data + start, pos - start);
return 1;
}
static KSTATUS FatLookupPathV(FAT_VOL* v, SYS_STRING_VIEW path,
uint32_t* outCluster, uint32_t* outSize,
uint8_t* outAttr,
uint64_t* outDirentLba, uint32_t* outDirentOffset) {
if (!v || !outCluster || !outSize || !FatStringViewDataIsPlausible(path))
return NTLSTATUS_INVALID_PARAMETER;
if (SysSvContainsByte(path, '\0'))
return NTLSTATUS_INVALID_PARAMETER;
uint32_t cluster = v->RootCluster, size = 0;
uint8_t attr = ATTR_DIRECTORY;
uint64_t finalDirentLba = 0;
uint32_t finalDirentOffset = 0;
if (outDirentLba) *outDirentLba = 0;
if (outDirentOffset) *outDirentOffset = 0;
if (FatPathCacheLookup(v, path, outCluster, outSize, &attr,
outDirentLba, outDirentOffset)) {
if (outAttr) *outAttr = attr;
return NTLSTATUS_SUCCESS;
}
size_t cursor = 0;
SYS_STRING_VIEW component;
while (FatPathNextComponent(path, &cursor, &component)) {
if (component.Length >= MAX_LFN) return NTLSTATUS_BUFFER_TOO_SMALL;
if (!FatNameViewIsValid(component)) return NTLSTATUS_INVALID_PARAMETER;
if (!(attr & ATTR_DIRECTORY)) return NTLSTATUS_NOT_FOUND;
uint32_t childCluster, childSize; uint8_t childAttr;
uint64_t childLba = 0; uint32_t childOffset = 0;
if (!SearchDirV(v, cluster, component, &childCluster, &childSize,
&childAttr, &childLba, &childOffset))
return NTLSTATUS_NOT_FOUND;
cluster = childCluster; size = childSize; attr = childAttr;
finalDirentLba = childLba;
finalDirentOffset = childOffset;
if (outDirentLba) *outDirentLba = childLba;
if (outDirentOffset) *outDirentOffset = childOffset;
}
*outCluster = cluster;
*outSize = size;
if (outAttr) *outAttr = attr;
FatPathCacheStore(v, path, cluster, size, attr,
finalDirentLba, finalDirentOffset);
return NTLSTATUS_SUCCESS;
}
static KSTATUS TraverseV(FAT_VOL* v, SYS_STRING_VIEW path,
uint32_t* outCluster, uint32_t* outSize) {
return FatLookupPathV(v, path, outCluster, outSize, NULL, NULL, NULL);
}
static KSTATUS SplitPathV(SYS_STRING_VIEW path, SYS_STRING_VIEW* parent,
SYS_STRING_VIEW* name) {
if (!parent || !name || !SysSvIsValid(path)) return NTLSTATUS_INVALID_PARAMETER;
*parent = SYS_SV_NULL;
*name = SYS_SV_NULL;
while (path.Length && FatIsPathSep(path.Data[path.Length - 1]))
path.Length--;
if (path.Length == 0) return NTLSTATUS_INVALID_PARAMETER;
if (SysSvContainsByte(path, '\0')) return NTLSTATUS_INVALID_PARAMETER;
size_t lastSep = (size_t)-1;
for (size_t i = 0; i < path.Length; i++) {
if (FatIsPathSep(path.Data[i])) lastSep = i;
}
if (lastSep == (size_t)-1) {
*parent = SYS_SV_NULL;
*name = path;
} else {
*parent = SysSvFromParts(path.Data, lastSep);
*name = SysSvFromParts(path.Data + lastSep + 1, path.Length - lastSep - 1);
}
if (!FatNameViewIsValid(*name)) return NTLSTATUS_INVALID_PARAMETER;
return NTLSTATUS_SUCCESS;
}
// Find a free DIRENT slot.
static int FindFreeDirent(FAT_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 cursor; uint64_t lba; uint32_t sectors;
if (!FatDirectoryFirstSegment(v, dirCluster, &cursor, &lba, &sectors)) {
MmFreePool(clusterBuf);
return 0;
}
do {
if (!KSUCCESS(ReadSectors(v, lba, sectors, clusterBuf))) {
MmFreePool(clusterBuf); return 0;
}
uint32_t entries = (sectors * v->BytesPerSector) / 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 = lba + eb / v->BytesPerSector;
*outOffset = (uint32_t)(eb % v->BytesPerSector);
MmFreePool(clusterBuf); return 1;
}
}
} while (FatDirectoryNextSegment(v, dirCluster, &cursor, &lba, &sectors));
if (FatIsFixedRoot(v, dirCluster)) {
MmFreePool(clusterBuf);
return 0;
}
uint32_t newCluster = AppendCluster(v, cursor);
if (newCluster) {
*outLba = ClusterToLba(v, newCluster); *outOffset = 0;
MmFreePool(clusterBuf); return 1;
}
MmFreePool(clusterBuf);
return 0;
}
// Find a contiguous run of free directory entries.
// VFAT LFN creation requires N long-name entries to immediately precede the
// final short entry, so the run must live inside one readable/writeable segment.
static int FindFreeDirentRun(FAT_VOL* v, uint32_t dirCluster, uint32_t needed,
uint64_t* outLba, uint32_t* outFirstIndex,
uint32_t* outSectors) {
if (!needed) return 0;
uint8_t* clusterBuf = (uint8_t*)MmAllocatePool(v->BytesPerCluster);
if (!clusterBuf) return 0;
uint32_t cursor; uint64_t lba; uint32_t sectors;
if (!FatDirectoryFirstSegment(v, dirCluster, &cursor, &lba, &sectors)) {
MmFreePool(clusterBuf);
return 0;
}
do {
if (!KSUCCESS(ReadSectors(v, lba, sectors, clusterBuf))) {
MmFreePool(clusterBuf);
return 0;
}
uint32_t entries = (sectors * v->BytesPerSector) / sizeof(DIRENT);
DIRENT* dir = (DIRENT*)clusterBuf;
uint32_t runStart = 0, runLen = 0;
for (uint32_t i = 0; i < entries; i++) {
uint8_t first = (uint8_t)dir[i].Name[0];
int freeEntry = (first == 0xE5 || first == 0x00);
if (freeEntry) {
if (runLen == 0) runStart = i;
runLen++;
if (runLen >= needed) {
*outLba = lba;
*outFirstIndex = runStart;
*outSectors = sectors;
MmFreePool(clusterBuf);
return 1;
}
} else {
runLen = 0;
}
}
} while (FatDirectoryNextSegment(v, dirCluster, &cursor, &lba, &sectors));
if (FatIsFixedRoot(v, dirCluster)) {
MmFreePool(clusterBuf);
return 0;
}
uint32_t newCluster = AppendCluster(v, cursor);
if (newCluster) {
*outLba = ClusterToLba(v, newCluster);
*outFirstIndex = 0;
*outSectors = v->SectorsPerCluster;
MmFreePool(clusterBuf);
return 1;
}
MmFreePool(clusterBuf);
return 0;
}
// Finds the parent directory and final component for a create or delete path.
static KSTATUS FatResolveParentForMutation(FAT_VOL* v, SYS_STRING_VIEW path,
uint32_t* parentClusterOut,
SYS_STRING_VIEW* filenameOut) {
if (!v || !parentClusterOut || !filenameOut)
return NTLSTATUS_INVALID_PARAMETER;
SYS_STRING_VIEW parentPath;
SYS_STRING_VIEW filenameView;
KSTATUS s = SplitPathV(path, &parentPath, &filenameView);
if (!KSUCCESS(s)) return s;
uint32_t parentCluster = 0;
uint32_t parentSize = 0;
uint8_t parentAttr = 0;
s = FatLookupPathV(v, parentPath, &parentCluster, &parentSize,
&parentAttr, NULL, NULL);
if (!KSUCCESS(s)) return s;
if (!(parentAttr & ATTR_DIRECTORY)) return NTLSTATUS_NOT_FOUND;
*parentClusterOut = parentCluster;
*filenameOut = filenameView;
return NTLSTATUS_SUCCESS;
}
// Creates a FAT directory entry run for a new file or directory.
static KSTATUS FatCreateDirent(FAT_VOL* v, SYS_STRING_VIEW path,
uint8_t attr, uint32_t firstCluster,
uint32_t fileSize,
uint64_t* direntLbaOut,
uint32_t* direntOffsetOut) {
if (!v || !SysSvIsValid(path) || path.Length == 0)
return NTLSTATUS_INVALID_PARAMETER;
uint32_t parentCluster = 0;
SYS_STRING_VIEW filenameView;
KSTATUS s = FatResolveParentForMutation(v, path, &parentCluster, &filenameView);
if (!KSUCCESS(s)) return s;
{
uint32_t ec, es;
uint8_t ea;
uint64_t existingLba = 0;
uint32_t existingOffset = 0;
if (SearchDirV(v, parentCluster, filenameView, &ec, &es, &ea,
&existingLba, &existingOffset)) {
return NTLSTATUS_OBJECT_NAME_COLLISION;
}
}
int useLfn = FatNameNeedsLfnForCreateV(filenameView);
char shortName[8], shortExt[3];
if (useLfn) {
s = DeleteLegacyBrokenDotAliasV(v, parentCluster, filenameView);
if (!KSUCCESS(s)) return s;
if (!MakeUniqueShortAliasV(v, parentCluster, filenameView, shortName, shortExt))
return NTLSTATUS_OBJECT_NAME_COLLISION;
} else {
Make83NameV(filenameView, shortName, shortExt);
}
size_t nameLen = filenameView.Length;
if (nameLen == 0 || nameLen >= MAX_LFN) return NTLSTATUS_INVALID_PARAMETER;
uint32_t lfnCount = useLfn ? (uint32_t)((nameLen + 12u) / 13u) : 0;
uint32_t neededEntries = lfnCount + 1u;
uint64_t clusterLba; uint32_t firstIndex; uint32_t entrySectors;
if (!FindFreeDirentRun(v, parentCluster, neededEntries, &clusterLba, &firstIndex, &entrySectors))
return NTLSTATUS_NO_MEMORY;
uint8_t* clusterBuf = (uint8_t*)MmAllocatePool(v->BytesPerCluster);
if (!clusterBuf) return NTLSTATUS_NO_MEMORY;
s = ReadSectors(v, clusterLba, entrySectors, clusterBuf);
if (!KSUCCESS(s)) { MmFreePool(clusterBuf); return s; }
DIRENT* dir = (DIRENT*)clusterBuf;
uint32_t shortIndex = firstIndex + lfnCount;
if (useLfn) {
uint8_t checksum = LfnChecksum(shortName, shortExt);
for (uint32_t slot = 0; slot < lfnCount; slot++) {
uint32_t ordinal = lfnCount - slot;
BuildLfnEntry((LFN_ENTRY*)&dir[firstIndex + slot], filenameView.Data,
nameLen, ordinal, lfnCount, checksum);
}
}
DIRENT* d = &dir[shortIndex];
memset(d, 0, sizeof(DIRENT));
memcpy(d->Name, shortName, 8);
memcpy(d->Ext, shortExt, 3);
d->Attr = attr;
d->Reserved = FatShortNameCaseFlagsV(filenameView);
FatSetDirentFirstCluster(v, d, firstCluster);
d->FileSize = fileSize;
{
FAT_ENCODED_TIME now;
FatQueryEncodedTime(&now);
d->CrtTimeTenth = now.CreateTimeTenth;
d->CrtTime = now.Time;
d->CrtDate = now.Date;
d->LastAccDate = now.Date;
d->WrtTime = now.Time;
d->WrtDate = now.Date;
}
s = WriteSectors(v, clusterLba, entrySectors, clusterBuf);
MmFreePool(clusterBuf);
if (!KSUCCESS(s)) return s;
uint64_t shortEntryByte = (uint64_t)shortIndex * sizeof(DIRENT);
uint64_t direntLba = clusterLba + shortEntryByte / v->BytesPerSector;
uint32_t direntOffset = (uint32_t)(shortEntryByte % v->BytesPerSector);
if (direntLbaOut) *direntLbaOut = direntLba;
if (direntOffsetOut) *direntOffsetOut = direntOffset;
FatPathCacheInvalidate(v);
return NTLSTATUS_SUCCESS;
}
// Initializes one dot entry in a new directory cluster.
static void FatInitDotEntry(FAT_VOL* v, DIRENT* d, const char* name,
uint32_t firstCluster,
const FAT_ENCODED_TIME* now) {
memset(d, 0, sizeof(*d));
memset(d->Name, ' ', sizeof(d->Name));
memset(d->Ext, ' ', sizeof(d->Ext));
d->Name[0] = name[0];
if (name[1]) d->Name[1] = name[1];
d->Attr = ATTR_DIRECTORY;
FatSetDirentFirstCluster(v, d, firstCluster);
d->CrtTimeTenth = now->CreateTimeTenth;
d->CrtTime = now->Time;
d->CrtDate = now->Date;
d->LastAccDate = now->Date;
d->WrtTime = now->Time;
d->WrtDate = now->Date;
}
// Writes the initial contents of a newly allocated FAT directory cluster.
static KSTATUS FatInitializeDirectoryCluster(FAT_VOL* v, uint32_t dirCluster,
uint32_t parentCluster) {
if (!v || dirCluster < 2)
return NTLSTATUS_INVALID_PARAMETER;
uint8_t* clusterBuf = (uint8_t*)MmAllocatePool(v->BytesPerCluster);
if (!clusterBuf) return NTLSTATUS_NO_MEMORY;
memset(clusterBuf, 0, v->BytesPerCluster);
FAT_ENCODED_TIME now;
FatQueryEncodedTime(&now);
DIRENT* dir = (DIRENT*)clusterBuf;
FatInitDotEntry(v, &dir[0], ".", dirCluster, &now);
FatInitDotEntry(v, &dir[1], "..", FatIsFixedRoot(v, parentCluster) ? 0u : parentCluster, &now);
KSTATUS s = WriteSectors(v, ClusterToLba(v, dirCluster),
v->SectorsPerCluster, clusterBuf);
MmFreePool(clusterBuf);
return s;
}
// Tests whether a FAT directory entry is one of the synthetic dot entries.
static int FatIsDotEntry(const DIRENT* d) {
if (!d || !(d->Attr & ATTR_DIRECTORY)) return 0;
if (d->Name[0] != '.') return 0;
if (d->Name[1] == ' ')
return 1;
return d->Name[1] == '.' && d->Name[2] == ' ';
}
// Tests whether a directory contains any real child entries.
static KSTATUS FatDirectoryIsEmpty(FAT_VOL* v, uint32_t dirCluster,
int* emptyOut) {
if (!v || !emptyOut)
return NTLSTATUS_INVALID_PARAMETER;
uint8_t* clusterBuf = (uint8_t*)MmAllocatePool(v->BytesPerCluster);
if (!clusterBuf) return NTLSTATUS_NO_MEMORY;
uint32_t cursor; uint64_t lba; uint32_t sectors;
if (!FatDirectoryFirstSegment(v, dirCluster, &cursor, &lba, &sectors)) {
MmFreePool(clusterBuf);
return NTLSTATUS_INVALID_PARAMETER;
}
do {
KSTATUS s = ReadSectors(v, lba, sectors, clusterBuf);
if (!KSUCCESS(s)) { MmFreePool(clusterBuf); return s; }
uint32_t entries = (sectors * v->BytesPerSector) / 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 == 0x00) { MmFreePool(clusterBuf); *emptyOut = 1; return NTLSTATUS_SUCCESS; }
if (first == 0xE5 || dir[i].Attr == ATTR_LFN) continue;
if ((dir[i].Attr & ATTR_VOLUME_ID) && !(dir[i].Attr & ATTR_DIRECTORY)) continue;
if (FatIsDotEntry(&dir[i])) continue;
MmFreePool(clusterBuf);
*emptyOut = 0;
return NTLSTATUS_SUCCESS;
}
} while (FatDirectoryNextSegment(v, dirCluster, &cursor, &lba, &sectors));
MmFreePool(clusterBuf);
*emptyOut = 1;
return NTLSTATUS_SUCCESS;
}
// Deletes the directory-entry run for one child name from a parent directory.
static KSTATUS FatDeleteDirentRunV(FAT_VOL* v, uint32_t parentCluster,
SYS_STRING_VIEW name) {
if (!v || !FatNameViewIsValid(name))
return NTLSTATUS_INVALID_PARAMETER;
uint8_t* clusterBuf = (uint8_t*)MmAllocatePool(v->BytesPerCluster);
if (!clusterBuf) return NTLSTATUS_NO_MEMORY;
char lfn[MAX_LFN];
int hasLfn = 0;
uint32_t lfnStart = 0;
memset(lfn, 0, sizeof(lfn));
char wantName83[8], wantExt83[3];
int allowShortMatch = FatNameIsStructurallyShort83V(name);
Make83NameV(name, wantName83, wantExt83);
uint32_t cursor; uint64_t lba; uint32_t sectors;
if (!FatDirectoryFirstSegment(v, parentCluster, &cursor, &lba, &sectors)) {
MmFreePool(clusterBuf);
return NTLSTATUS_NOT_FOUND;
}
do {
KSTATUS s = ReadSectors(v, lba, sectors, clusterBuf);
if (!KSUCCESS(s)) { MmFreePool(clusterBuf); return s; }
uint32_t entries = (sectors * v->BytesPerSector) / sizeof(DIRENT);
DIRENT* dir = (DIRENT*)clusterBuf;
hasLfn = 0;
memset(lfn, 0, sizeof(lfn));
for (uint32_t i = 0; i < entries; i++) {
uint8_t first = (uint8_t)dir[i].Name[0];
if (first == 0x00) { MmFreePool(clusterBuf); return NTLSTATUS_NOT_FOUND; }
if (first == 0xE5) { hasLfn = 0; memset(lfn, 0, sizeof(lfn)); continue; }
if (dir[i].Attr == ATTR_LFN) {
if (!hasLfn) lfnStart = i;
LfnAppend(lfn, (LFN_ENTRY*)&dir[i]);
hasLfn = 1;
continue;
}
int match = 0;
if (hasLfn && lfn[0]) match = LfnMatchV(lfn, name);
if (!match && allowShortMatch)
match = MatchName83V(dir[i].Name, dir[i].Ext, name);
if (!match && allowShortMatch)
match = MatchRaw83(dir[i].Name, dir[i].Ext, wantName83, wantExt83);
if (match) {
uint32_t start = hasLfn ? lfnStart : i;
for (uint32_t j = start; j <= i; j++)
dir[j].Name[0] = (char)0xE5;
s = WriteSectors(v, lba, sectors, clusterBuf);
MmFreePool(clusterBuf);
if (KSUCCESS(s)) FatPathCacheInvalidate(v);
return s;
}
hasLfn = 0;
memset(lfn, 0, sizeof(lfn));
}
} while (FatDirectoryNextSegment(v, parentCluster, &cursor, &lba, &sectors));
MmFreePool(clusterBuf);
return NTLSTATUS_NOT_FOUND;
}
// Open / Close.
KSTATUS FatOpen(IO_VOLUME_PROVIDER* fs, SYS_STRING_VIEW path, void** fileCtx) {
if (!fs || !fileCtx || !SysSvIsValid(path) || path.Length == 0)
return NTLSTATUS_INVALID_PARAMETER;
FAT_VOL* v = (FAT_VOL*)fs->Context;
uint32_t cluster, size; uint64_t direntLba; uint32_t direntOffset;
KSTATUS s = FatLookupPathV(v, path, &cluster, &size, NULL, &direntLba, &direntOffset);
if (!KSUCCESS(s)) return s;
FAT_FILE* f = (FAT_FILE*)MmAllocatePool(sizeof(FAT_FILE));
if (!f) return NTLSTATUS_NO_MEMORY;
f->StartCluster = cluster; f->FileSize = size; f->Vol = v;
f->DirentLba = direntLba; f->DirentOffset = direntOffset;
FatFileInitChainCache(f);
*fileCtx = f;
return NTLSTATUS_SUCCESS;
}
// Flushes FAT metadata and the backing device write cache.
KSTATUS FatFlush(IO_VOLUME_PROVIDER* fs, void* fileCtx) {
(void)fs;
FAT_FILE* f = (FAT_FILE*)fileCtx;
if (!f || !f->Vol)
return NTLSTATUS_INVALID_PARAMETER;
FlushFatCache(f->Vol);
return IoFlushDisk(f->Vol->DriveIndex);
}
// Releases the requested object.
void FatClose(IO_VOLUME_PROVIDER* fs, void* fileCtx) {
(void)fs;
FAT_FILE* f = (FAT_FILE*)fileCtx;
FlushFatCache(f->Vol);
MmFreePool(fileCtx);
}
// Read (fix #4: contiguous-run coalescing)
// Cluster-chain lookup is cached per open file, and data spans are allowed to
// grow beyond the AHCI bounce size because the device layer now splits large
// sector requests for the concrete controller.
KSTATUS FatRead(IO_VOLUME_PROVIDER* fs, void* fileCtx,
uint64_t offset, void* buf,
size_t size, size_t* bytesRead) {
(void)fs;
if (!bytesRead) return NTLSTATUS_INVALID_PARAMETER;
*bytesRead = 0;
if (!fileCtx || !buf) return NTLSTATUS_INVALID_PARAMETER;
FAT_FILE* f = (FAT_FILE*)fileCtx;
FAT_VOL* v = f->Vol;
if (offset >= f->FileSize || size == 0) return NTLSTATUS_SUCCESS;
if (size > (size_t)(f->FileSize - offset)) size = (size_t)(f->FileSize - offset);
size_t wanted = size;
uint32_t initialOffsetInCluster = (uint32_t)(offset % v->BytesPerCluster);
uint64_t initialBytesCovered = (uint64_t)initialOffsetInCluster + (uint64_t)size;
uint32_t maxUsefulSectors = (uint32_t)((initialBytesCovered + 511u) / 512u);
if (maxUsefulSectors < v->SectorsPerCluster) maxUsefulSectors = v->SectorsPerCluster;
if (maxUsefulSectors > FAT_READ_RUN_MAX_SECTORS)
maxUsefulSectors = FAT_READ_RUN_MAX_SECTORS;
uint8_t* runBuf = (uint8_t*)MmAllocatePool((size_t)maxUsefulSectors * 512u);
if (!runBuf) return NTLSTATUS_NO_MEMORY;
size_t totalRead = 0;
uint8_t* out = (uint8_t*)buf;
KSTATUS readStatus = NTLSTATUS_SUCCESS;
uint32_t logicalCluster = (uint32_t)(offset / v->BytesPerCluster);
uint32_t offsetInCluster = (uint32_t)(offset % v->BytesPerCluster);
uint32_t cluster = FatResolveFileCluster(f, logicalCluster);
while (size > 0 && FatIsDataCluster(v, cluster)) {
uint64_t bytesCovered = (uint64_t)offsetInCluster + (uint64_t)size;
uint32_t usefulSectors = (uint32_t)((bytesCovered + 511u) / 512u);
if (usefulSectors < v->SectorsPerCluster) usefulSectors = v->SectorsPerCluster;
if (usefulSectors > FAT_READ_RUN_MAX_SECTORS)
usefulSectors = FAT_READ_RUN_MAX_SECTORS;
if (usefulSectors > maxUsefulSectors) usefulSectors = maxUsefulSectors;
uint32_t runSectors = 0;
uint32_t next = 0;
if (!FatBuildReadRun(f, logicalCluster, cluster, usefulSectors,
&runSectors, &next)) {
readStatus = NTLSTATUS_DEVICE_ERROR;
break;
}
KSTATUS rs = ReadSectors(v, ClusterToLba(v, cluster), 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;
uint32_t runClusters = runSectors / v->SectorsPerCluster;
uint32_t merges = runClusters ? runClusters - 1u : 0;
FatStatsAddReadRun(v, runSectors, merges);
memcpy(out, runBuf + offsetInCluster, copy);
out += copy;
totalRead += copy;
size -= copy;
offsetInCluster = 0;
logicalCluster += runClusters;
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) ? NTLSTATUS_DEVICE_ERROR : readStatus;
return NTLSTATUS_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.
KSTATUS FatWrite(IO_VOLUME_PROVIDER* fs, void* fileCtx,
uint64_t offset, const void* buf,
size_t size, size_t* bytesWritten) {
(void)fs;
if (!size) { *bytesWritten = 0; return NTLSTATUS_SUCCESS; }
FAT_FILE* f = (FAT_FILE*)fileCtx;
FAT_VOL* v = f->Vol;
const uint8_t* src = (const uint8_t*)buf;
FatFileInvalidateChainCache(f);
// Ensure at least one cluster exists.
if (f->StartCluster < 2) {
uint32_t first = AllocCluster(v);
if (!first) { *bytesWritten = 0; return NTLSTATUS_NO_MEMORY; }
FlushFatCache(v);
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 (!FatIsDataCluster(v, next)) {
next = AppendCluster(v, cluster);
if (!next) { *bytesWritten = 0; return NTLSTATUS_NO_MEMORY; }
}
cluster = next;
}
uint8_t* runBuf = (uint8_t*)MmAllocatePool(FAT_WRITE_RUN_MAX_SECTORS * 512u);
if (!runBuf) { *bytesWritten = 0; return NTLSTATUS_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 && FatIsDataCluster(v, next)
&& runSectors + v->SectorsPerCluster <= FAT_WRITE_RUN_MAX_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 (!FatIsDataCluster(v, cluster)) {
// Find the actual last cluster in the run we just wrote.
uint32_t last = runStart;
for (uint32_t tmp = FatEntry(v, last);
FatIsDataCluster(v, tmp);
tmp = FatEntry(v, last))
last = tmp;
cluster = AppendCluster(v, last);
if (!cluster) break;
}
}
}
MmFreePool(runBuf);
uint64_t newEnd = offset + written;
if (written > 0) {
FAT_DIRENT_UPDATE u;
memset(&u, 0, sizeof(u));
FatQueryEncodedTime(&u.Timestamp);
u.Flags = FAT_DIRENT_UPDATE_WRITE_TIME;
if (newEnd > f->FileSize) {
f->FileSize = (uint32_t)newEnd;
u.Flags |= FAT_DIRENT_UPDATE_SIZE;
u.Size = f->FileSize;
}
KSTATUS ms = DirentUpdateMetadata(v, f->DirentLba, f->DirentOffset, &u);
if (KSUCCESS(ms))
FatPathCacheInvalidate(v);
}
*bytesWritten = written;
return (written == size) ? NTLSTATUS_SUCCESS : NTLSTATUS_DEVICE_ERROR;
}
// Create.
// Creates the requested file and fails if the name already exists.
KSTATUS FatCreate(IO_VOLUME_PROVIDER* fs, SYS_STRING_VIEW path, void** fileCtx) {
if (!fs || !fileCtx || !SysSvIsValid(path) || path.Length == 0)
return NTLSTATUS_INVALID_PARAMETER;
FAT_VOL* v = (FAT_VOL*)fs->Context;
uint64_t direntLba = 0;
uint32_t direntOffset = 0;
KSTATUS s = FatCreateDirent(v, path, ATTR_ARCHIVE, 0, 0,
&direntLba, &direntOffset);
if (!KSUCCESS(s)) return s;
FAT_FILE* f = (FAT_FILE*)MmAllocatePool(sizeof(FAT_FILE));
if (!f) return NTLSTATUS_NO_MEMORY;
f->StartCluster = 0;
f->FileSize = 0;
f->Vol = v;
f->DirentLba = direntLba;
f->DirentOffset = direntOffset;
FatFileInitChainCache(f);
*fileCtx = f;
return NTLSTATUS_SUCCESS;
}
// Creates a directory and initializes its dot entries.
KSTATUS FatCreateDir(IO_VOLUME_PROVIDER* fs, SYS_STRING_VIEW path) {
if (!fs || !SysSvIsValid(path) || path.Length == 0)
return NTLSTATUS_INVALID_PARAMETER;
FAT_VOL* v = (FAT_VOL*)fs->Context;
uint32_t parentCluster = 0;
SYS_STRING_VIEW filenameView;
KSTATUS s = FatResolveParentForMutation(v, path, &parentCluster, &filenameView);
if (!KSUCCESS(s)) return s;
(void)filenameView;
uint32_t dirCluster = AllocCluster(v);
if (!dirCluster) return NTLSTATUS_NO_MEMORY;
s = FatInitializeDirectoryCluster(v, dirCluster, parentCluster);
if (!KSUCCESS(s)) {
FreeClusterChain(v, dirCluster);
FlushFatCache(v);
return s;
}
FlushFatCache(v);
s = FatCreateDirent(v, path, ATTR_DIRECTORY, dirCluster, 0, NULL, NULL);
if (!KSUCCESS(s)) {
FreeClusterChain(v, dirCluster);
FlushFatCache(v);
return s;
}
FlushFatCache(v);
return NTLSTATUS_SUCCESS;
}
// Removes an empty directory.
KSTATUS FatRemoveDir(IO_VOLUME_PROVIDER* fs, SYS_STRING_VIEW path) {
if (!fs || !SysSvIsValid(path) || path.Length == 0)
return NTLSTATUS_INVALID_PARAMETER;
FAT_VOL* v = (FAT_VOL*)fs->Context;
uint32_t cluster = 0;
uint32_t size = 0;
uint8_t attr = 0;
uint64_t direntLba = 0;
uint32_t direntOffset = 0;
KSTATUS s = FatLookupPathV(v, path, &cluster, &size, &attr,
&direntLba, &direntOffset);
if (!KSUCCESS(s)) return s;
if (!direntLba) return NTLSTATUS_ACCESS_DENIED;
if (!(attr & ATTR_DIRECTORY)) return NTLSTATUS_ACCESS_DENIED;
int empty = 0;
s = FatDirectoryIsEmpty(v, cluster, &empty);
if (!KSUCCESS(s)) return s;
if (!empty) return NTLSTATUS_ACCESS_DENIED;
uint32_t parentCluster = 0;
SYS_STRING_VIEW filenameView;
s = FatResolveParentForMutation(v, path, &parentCluster, &filenameView);
if (!KSUCCESS(s)) return s;
s = FatDeleteDirentRunV(v, parentCluster, filenameView);
if (!KSUCCESS(s)) return s;
if (cluster >= 2) FreeClusterChain(v, cluster);
FlushFatCache(v);
FatPathCacheInvalidate(v);
return NTLSTATUS_SUCCESS;
}
// Truncate.
KSTATUS FatTruncate(IO_VOLUME_PROVIDER* fs, void* fileCtx, uint64_t newSize, uint32_t fileFlags) {
(void)fs;
FAT_FILE* f = (FAT_FILE*)fileCtx;
FAT_VOL* v = f->Vol;
FatFileInvalidateChainCache(f);
int zeroNewClusters = (fileFlags & IO_FILE_FLAG_SKIP_ZERO_EXTEND) ? 0 : 1;
if ((uint32_t)newSize == f->FileSize) {
FAT_DIRENT_UPDATE u;
memset(&u, 0, sizeof(u));
FatQueryEncodedTime(&u.Timestamp);
u.Flags = FAT_DIRENT_UPDATE_WRITE_TIME;
KSTATUS s = DirentUpdateMetadata(v, f->DirentLba, f->DirentOffset, &u);
if (KSUCCESS(s)) FatPathCacheInvalidate(v);
return s;
}
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);
if (KSUCCESS(s)) FatPathCacheInvalidate(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 && FatIsDataCluster(v, cluster); i++) {
lastGood = cluster; cluster = FatEntry(v, cluster);
}
uint32_t tail = FatEntry(v, lastGood);
WriteFatEntry(v, lastGood, FatEocMark(v));
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 = AllocClusterEx(v, zeroNewClusters);
if (!first) return NTLSTATUS_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 = AppendClusterEx(v, lastCluster, zeroNewClusters);
if (!next) return NTLSTATUS_NO_MEMORY;
lastCluster = next;
}
}
update_size:
f->FileSize = (uint32_t)newSize;
{ KSTATUS s = DirentUpdateSize(v, f->DirentLba, f->DirentOffset, (uint32_t)newSize);
FlushFatCache(v);
if (KSUCCESS(s)) FatPathCacheInvalidate(v);
return s; }
}
// Delete.
KSTATUS FatDelete(IO_VOLUME_PROVIDER* fs, SYS_STRING_VIEW path) {
if (!fs || !SysSvIsValid(path) || path.Length == 0)
return NTLSTATUS_INVALID_PARAMETER;
FAT_VOL* v = (FAT_VOL*)fs->Context;
uint32_t cluster, size; uint64_t direntLba; uint32_t direntOffset;
KSTATUS s = FatLookupPathV(v, path, &cluster, &size, NULL, &direntLba, &direntOffset);
if (!KSUCCESS(s)) return s;
if (!direntLba) return NTLSTATUS_ACCESS_DENIED;
uint32_t parentCluster = 0;
SYS_STRING_VIEW filenameView;
s = FatResolveParentForMutation(v, path, &parentCluster, &filenameView);
if (!KSUCCESS(s)) return s;
// Delete the complete entry run so long-name metadata cannot survive the
// short entry and keep writable vvfat mirrors visibly present.
s = FatDeleteDirentRunV(v, parentCluster, filenameView);
if (!KSUCCESS(s)) return s;
if (cluster >= 2) FreeClusterChain(v, cluster);
FlushFatCache(v);
FatPathCacheInvalidate(v);
return NTLSTATUS_SUCCESS;
}
// Returns size.
uint64_t FatGetSize(IO_VOLUME_PROVIDER* fs, void* fileCtx) {
(void)fs; return ((FAT_FILE*)fileCtx)->FileSize;
}
// Converts FAT attributes to the public file type.
static uint32_t FatFileTypeFromAttr(uint8_t attr) {
return (attr & ATTR_DIRECTORY) ? SYS_FILE_TYPE_DIRECTORY : SYS_FILE_TYPE_REGULAR;
}
// Fills public metadata from one FAT directory entry.
static void FatFillStatFromDirent(FAT_VOL* v, const DIRENT* d,
uint64_t direntLba, uint32_t direntOffset,
SYS_FILE_STAT* out) {
memset(out, 0, sizeof(*out));
out->Size = sizeof(*out);
out->Type = FatFileTypeFromAttr(d->Attr);
out->Attributes = d->Attr;
out->LinkCount = 1;
out->FileSize = d->FileSize;
uint32_t firstCluster = FatDirentFirstCluster(v, d);
out->FileId = direntLba ^
((uint64_t)direntOffset << 32) ^
((uint64_t)firstCluster << 1);
FatDecodeDateTime(d->CrtDate, d->CrtTime, d->CrtTimeTenth, &out->CreationTime);
FatDecodeAccessDate(d->LastAccDate, &out->LastAccessTime);
FatDecodeDateTime(d->WrtDate, d->WrtTime, 0, &out->LastWriteTime);
out->ChangeTime = out->LastWriteTime;
}
// Fills public metadata for the filesystem root.
static void FatFillRootStat(FAT_VOL* v, SYS_FILE_STAT* out) {
memset(out, 0, sizeof(*out));
out->Size = sizeof(*out);
out->Type = SYS_FILE_TYPE_DIRECTORY;
out->Attributes = SYS_FILE_ATTR_DIRECTORY;
out->LinkCount = 1;
out->FileSize = 0;
out->FileId = ((uint64_t)v->RootCluster << 1) ^ 0x524f4f54ULL;
}
// Queries metadata for an open FAT file.
KSTATUS FatQueryInfo(IO_VOLUME_PROVIDER* fs, void* fileCtx, SYS_FILE_STAT* out) {
(void)fs;
if (!fileCtx || !out) return NTLSTATUS_INVALID_PARAMETER;
FAT_FILE* f = (FAT_FILE*)fileCtx;
FAT_VOL* v = f->Vol;
if (!v) return NTLSTATUS_INVALID_PARAMETER;
if (f->DirentLba == 0) {
if (f->StartCluster == v->RootCluster && f->FileSize == 0) {
FatFillRootStat(v, out);
} else {
memset(out, 0, sizeof(*out));
out->Size = sizeof(*out);
out->Type = SYS_FILE_TYPE_REGULAR;
out->Attributes = SYS_FILE_ATTR_ARCHIVE;
out->LinkCount = 1;
out->FileSize = f->FileSize;
out->FileId = ((uint64_t)f->StartCluster << 1);
}
return NTLSTATUS_SUCCESS;
}
uint8_t* sec = (uint8_t*)MmAllocatePool(v->BytesPerSector);
if (!sec) return NTLSTATUS_NO_MEMORY;
KSTATUS s = ReadSectors(v, f->DirentLba, 1, sec);
if (!KSUCCESS(s)) { MmFreePool(sec); return s; }
DIRENT* d = (DIRENT*)(sec + f->DirentOffset);
if ((uint8_t)d->Name[0] == 0x00 || (uint8_t)d->Name[0] == 0xE5) {
MmFreePool(sec);
return NTLSTATUS_NOT_FOUND;
}
FatFillStatFromDirent(v, d, f->DirentLba, f->DirentOffset, out);
f->StartCluster = FatDirentFirstCluster(v, d);
f->FileSize = d->FileSize;
MmFreePool(sec);
return NTLSTATUS_SUCCESS;
}
// Queries metadata for a FAT path.
KSTATUS FatStat(IO_VOLUME_PROVIDER* fs, SYS_STRING_VIEW path, SYS_FILE_STAT* out) {
if (!fs || !out || !SysSvIsValid(path)) return NTLSTATUS_INVALID_PARAMETER;
FAT_VOL* v = (FAT_VOL*)fs->Context;
if (!v) return NTLSTATUS_INVALID_PARAMETER;
uint32_t cluster = 0;
uint32_t size = 0;
uint8_t attr = ATTR_DIRECTORY;
uint64_t direntLba = 0;
uint32_t direntOffset = 0;
KSTATUS s = FatLookupPathV(v, path, &cluster, &size, &attr, &direntLba, &direntOffset);
if (!KSUCCESS(s)) return s;
(void)cluster;
if (direntLba == 0) {
FatFillRootStat(v, out);
return NTLSTATUS_SUCCESS;
}
uint8_t* sec = (uint8_t*)MmAllocatePool(v->BytesPerSector);
if (!sec) return NTLSTATUS_NO_MEMORY;
s = ReadSectors(v, direntLba, 1, sec);
if (!KSUCCESS(s)) { MmFreePool(sec); return s; }
DIRENT* d = (DIRENT*)(sec + direntOffset);
if ((uint8_t)d->Name[0] == 0x00 || (uint8_t)d->Name[0] == 0xE5) {
MmFreePool(sec);
return NTLSTATUS_NOT_FOUND;
}
FatFillStatFromDirent(v, d, direntLba, direntOffset, out);
out->Type = FatFileTypeFromAttr(attr);
out->FileSize = size;
MmFreePool(sec);
return NTLSTATUS_SUCCESS;
}
// Mixes FAT directory metadata into a per-file content generation.
static uint64_t FatDirentGeneration(FAT_VOL* v, const DIRENT* d) {
if (!d)
return 0;
uint32_t firstCluster = FatDirentFirstCluster(v, d);
uint64_t generation = ((uint64_t)d->WrtDate << 48) |
((uint64_t)d->WrtTime << 32) |
((uint64_t)d->LastAccDate << 16) |
(uint64_t)d->Attr;
generation ^= ((uint64_t)d->CrtDate << 40);
generation ^= ((uint64_t)d->CrtTime << 24);
generation ^= ((uint64_t)d->CrtTimeTenth << 16);
generation ^= ((uint64_t)firstCluster << 1);
generation ^= ((uint64_t)d->FileSize << 17);
return generation ? generation : 1;
}
// Returns a stable FAT file identity for cross-open image-section caches.
KSTATUS FatQueryIdentity(IO_VOLUME_PROVIDER* fs, void* fileCtx, IO_FILE_IDENTITY* out) {
(void)fs;
if (!fileCtx || !out)
return NTLSTATUS_INVALID_PARAMETER;
FAT_FILE* f = (FAT_FILE*)fileCtx;
FAT_VOL* v = f->Vol;
if (!v)
return NTLSTATUS_INVALID_PARAMETER;
uint32_t startCluster = f->StartCluster;
uint32_t fileSize = f->FileSize;
uint64_t generation = ((uint64_t)startCluster << 1) ^
((uint64_t)fileSize << 17);
if (f->DirentLba != 0) {
uint8_t* sec = (uint8_t*)MmAllocatePool(v->BytesPerSector);
if (!sec)
return NTLSTATUS_NO_MEMORY;
KSTATUS s = ReadSectors(v, f->DirentLba, 1, sec);
if (!KSUCCESS(s)) {
MmFreePool(sec);
return s;
}
DIRENT* d = (DIRENT*)(sec + f->DirentOffset);
if ((uint8_t)d->Name[0] == 0x00 || (uint8_t)d->Name[0] == 0xE5) {
MmFreePool(sec);
return NTLSTATUS_NOT_FOUND;
}
startCluster = FatDirentFirstCluster(v, d);
fileSize = d->FileSize;
generation = FatDirentGeneration(v, d);
MmFreePool(sec);
}
out->FileId = f->DirentLba ^
((uint64_t)f->DirentOffset << 32) ^
((uint64_t)startCluster << 1);
out->Generation = generation ? generation : 1;
out->Size = fileSize;
return NTLSTATUS_SUCCESS;
}
// Directory enumeration (fix #2: cluster buffer cached in context)
// Previously: allocate + ReadSectors + return 1 entry + free, every call.
// Now: buffer lives in FAT_DIR_CTX; ReadSectors fires only on cluster
// boundaries.
typedef struct {
FAT_VOL* Vol;
uint32_t DirCluster;
uint32_t Cursor;
uint32_t LoadedCursor;
uint32_t LoadedSectors;
uint64_t LoadedLba;
uint32_t EntryIndex;
char LfnBuf[MAX_LFN];
int HasLfn;
uint8_t* ClusterBuf; // allocated once in OpenDir, freed in CloseDir
} FAT_DIR_CTX;
// Opens dir.
KSTATUS FatOpenDir(IO_VOLUME_PROVIDER* fs, SYS_STRING_VIEW dirPath, void** dirCtx) {
if (!fs || !dirCtx || !SysSvIsValid(dirPath))
return NTLSTATUS_INVALID_PARAMETER;
FAT_VOL* v = (FAT_VOL*)fs->Context;
uint32_t cluster;
if (dirPath.Length == 0) {
cluster = v->RootCluster;
} else {
uint32_t size;
KSTATUS s = TraverseV(v, dirPath, &cluster, &size);
if (!KSUCCESS(s)) return s;
}
uint32_t cursor; uint64_t lba; uint32_t sectors;
if (!FatDirectoryFirstSegment(v, cluster, &cursor, &lba, &sectors))
return NTLSTATUS_NOT_FOUND;
FAT_DIR_CTX* ctx = (FAT_DIR_CTX*)MmAllocatePool(sizeof(FAT_DIR_CTX));
if (!ctx) return NTLSTATUS_NO_MEMORY;
ctx->ClusterBuf = (uint8_t*)MmAllocatePool(v->BytesPerCluster);
if (!ctx->ClusterBuf) { MmFreePool(ctx); return NTLSTATUS_NO_MEMORY; }
ctx->Vol = v;
ctx->DirCluster = cluster;
ctx->Cursor = cursor;
ctx->LoadedCursor = (uint32_t)-1;
ctx->LoadedSectors = sectors;
ctx->LoadedLba = lba;
ctx->EntryIndex = 0;
ctx->HasLfn = 0;
memset(ctx->LfnBuf, 0, sizeof(ctx->LfnBuf));
*dirCtx = ctx;
return NTLSTATUS_SUCCESS;
}
// Returns dir.
KSTATUS FatReadDir(IO_VOLUME_PROVIDER* fs, void* dirCtx, KDIR_ENTRY* out) {
(void)fs;
FAT_DIR_CTX* ctx = (FAT_DIR_CTX*)dirCtx;
FAT_VOL* v = ctx->Vol;
while (true) {
if (ctx->Cursor != ctx->LoadedCursor) {
if (!KSUCCESS(ReadSectors(v, ctx->LoadedLba, ctx->LoadedSectors, ctx->ClusterBuf)))
return NTLSTATUS_NO_MORE_FILES;
ctx->LoadedCursor = ctx->Cursor;
}
uint32_t epc = (ctx->LoadedSectors * v->BytesPerSector) / sizeof(DIRENT);
DIRENT* dir = (DIRENT*)ctx->ClusterBuf;
while (ctx->EntryIndex < epc) {
DIRENT* e = &dir[ctx->EntryIndex++];
if (e->Name[0] == 0x00) return NTLSTATUS_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; }
memset(out, 0, sizeof(*out));
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;
FatDecodeDateTime(e->CrtDate, e->CrtTime, e->CrtTimeTenth, &out->CreateTime);
FatDecodeAccessDate(e->LastAccDate, &out->LastAccessTime);
FatDecodeDateTime(e->WrtDate, e->WrtTime, 0, &out->LastWriteTime);
ctx->HasLfn = 0; memset(ctx->LfnBuf, 0, sizeof(ctx->LfnBuf));
return NTLSTATUS_SUCCESS;
}
if (!FatDirectoryNextSegment(v, ctx->DirCluster, &ctx->Cursor,
&ctx->LoadedLba, &ctx->LoadedSectors))
return NTLSTATUS_NO_MORE_FILES;
ctx->LoadedCursor = (uint32_t)-1;
ctx->EntryIndex = 0;
}
}
// Releases dir.
void FatCloseDir(IO_VOLUME_PROVIDER* fs, void* dirCtx) {
(void)fs;
FAT_DIR_CTX* ctx = (FAT_DIR_CTX*)dirCtx;
MmFreePool(ctx->ClusterBuf);
MmFreePool(ctx);
}
// FAT BPB validation.
typedef struct FAT_BPB_LAYOUT {
FAT_VARIANT Variant;
uint32_t TotalSectors;
uint32_t FatSize;
uint32_t RootDirSectors;
uint32_t DataSectors;
uint32_t TotalClusters;
} FAT_BPB_LAYOUT;
static int FatComputeBpbLayout(const BPB* bpb, FAT_BPB_LAYOUT* out) {
if (!bpb || !out) return 0;
memset(out, 0, sizeof(*out));
if (bpb->BytesPerSector != 512) return 0;
if (bpb->SectorsPerCluster == 0) return 0;
if (bpb->NumFats == 0) return 0;
if (bpb->ReservedSectors == 0) return 0;
uint32_t totalSectors = bpb->TotalSectors16 ? bpb->TotalSectors16 : bpb->TotalSectors32;
uint32_t fatSize = bpb->FatSize16 ? bpb->FatSize16 : bpb->FatSize32;
if (!totalSectors || !fatSize) return 0;
uint32_t rootDirSectors = ((uint32_t)bpb->RootEntryCount * sizeof(DIRENT) +
(uint32_t)bpb->BytesPerSector - 1u) /
(uint32_t)bpb->BytesPerSector;
uint32_t metadataSectors = (uint32_t)bpb->ReservedSectors +
(uint32_t)bpb->NumFats * fatSize +
rootDirSectors;
if (totalSectors <= metadataSectors) return 0;
uint32_t dataSectors = totalSectors - metadataSectors;
uint32_t totalClusters = dataSectors / bpb->SectorsPerCluster;
FAT_VARIANT variant;
if (totalClusters < 4085u)
variant = FatVariant12;
else if (totalClusters < 65525u)
variant = FatVariant16;
else
variant = FatVariant32;
if (variant == FatVariant16) {
if (bpb->FatSize16 == 0 || bpb->RootEntryCount == 0) return 0;
} else if (variant == FatVariant32) {
if (bpb->FatSize16 != 0 || bpb->RootEntryCount != 0) return 0;
if (bpb->FatSize32 == 0 || bpb->RootCluster < 2) return 0;
}
out->Variant = variant;
out->TotalSectors = totalSectors;
out->FatSize = fatSize;
out->RootDirSectors = rootDirSectors;
out->DataSectors = dataSectors;
out->TotalClusters = totalClusters;
return 1;
}
// Native async read/write
// Metadata work (FAT traversal/allocation and partial-cluster read-modify
// preparation) still runs synchronously in the caller/completion context, but
// file data transfers are submitted as disk IRPs and completion is chained from
// the driver's IRP completion callback instead of the generic I/O worker.
typedef struct FAT_ASYNC_RW {
LIST_ENTRY Link;
FAT_VOL* Vol;
FAT_FILE* File;
uint8_t IsWrite;
uint64_t Offset;
uint32_t OffsetInCluster;
uint32_t Cluster;
uint32_t LogicalCluster;
size_t Remaining;
size_t Done;
size_t Requested;
uint8_t* Dst;
const uint8_t* Src;
uint8_t* RunBuf;
uint32_t CurrentNext;
uint32_t CurrentLast;
uint32_t CurrentClusters;
uint32_t CurrentOffset;
size_t CurrentCopy;
IRP Irp;
DEVICE_OBJECT* CurrentDevice;
IO_FS_ASYNC_COMPLETE Complete;
void* CompleteContext;
} FAT_ASYNC_RW;
static SPINLOCK gFatAsyncLock = SPINLOCK_INIT;
static LIST_ENTRY gFatAsyncQueue;
static SEMAPHORE* gFatAsyncSemaphore;
static volatile int gFatAsyncInitState;
static void FatAsyncReadSubmitNext(FAT_ASYNC_RW* ctx);
static void FatAsyncWriteSubmitNext(FAT_ASYNC_RW* ctx);
// Implements the async Worker operation.
static void FatAsyncWorker(void* arg) {
(void)arg;
while (true) {
KSTATUS s = KeWaitSemaphore(gFatAsyncSemaphore, KE_WAIT_INFINITE);
if (!KSUCCESS(s)) {
KeYield();
continue;
}
uintptr_t flags = KeAcquireSpinlock(&gFatAsyncLock);
LIST_ENTRY* node = ListPopHead(&gFatAsyncQueue);
KeReleaseSpinlock(&gFatAsyncLock, flags);
if (!node) continue;
FAT_ASYNC_RW* ctx = CONTAINING_RECORD(node, FAT_ASYNC_RW, Link);
if (ctx->IsWrite)
FatAsyncWriteSubmitNext(ctx);
else
FatAsyncReadSubmitNext(ctx);
}
}
// Implements the ensure Async Worker Started operation.
static KSTATUS FatEnsureAsyncWorkerStarted(void) {
uintptr_t flags = KeAcquireSpinlock(&gFatAsyncLock);
if (gFatAsyncInitState == 2) {
KeReleaseSpinlock(&gFatAsyncLock, flags);
return NTLSTATUS_SUCCESS;
}
if (gFatAsyncInitState == 1) {
KeReleaseSpinlock(&gFatAsyncLock, flags);
while (gFatAsyncInitState == 1)
KeYield();
return gFatAsyncInitState == 2 ? NTLSTATUS_SUCCESS : NTLSTATUS_FAILURE;
}
gFatAsyncInitState = 1;
LIST_INIT(&gFatAsyncQueue);
KeReleaseSpinlock(&gFatAsyncLock, flags);
KSTATUS s = KeSemaphoreCreate(0, 0x7fffffff, &gFatAsyncSemaphore);
if (KSUCCESS(s)) {
THREAD* worker = (THREAD*)0;
const KE_THREAD_CREATE_PARAMETERS parameters = KE_THREAD_CREATE_NORMAL(PriorityNormal);
s = KeCreateKernelThread((uintptr_t)FatAsyncWorker, 0,
&parameters, &worker);
(void)worker;
}
flags = KeAcquireSpinlock(&gFatAsyncLock);
gFatAsyncInitState = KSUCCESS(s) ? 2 : 0;
KeReleaseSpinlock(&gFatAsyncLock, flags);
return s;
}
// Implements the async Queue Continuation operation.
static void FatAsyncQueueContinuation(FAT_ASYNC_RW* ctx) {
if (!ctx) return;
uintptr_t flags = KeAcquireSpinlock(&gFatAsyncLock);
ListInsertTail(&gFatAsyncQueue, &ctx->Link);
KeReleaseSpinlock(&gFatAsyncLock, flags);
KeSignalSemaphore(gFatAsyncSemaphore);
}
// Implements the async Finish operation.
static void FatAsyncFinish(FAT_ASYNC_RW* ctx, KSTATUS status, size_t information) {
if (!ctx) return;
if (ctx->CurrentDevice) {
IoDereferenceDeviceObject(ctx->CurrentDevice);
ctx->CurrentDevice = (DEVICE_OBJECT*)0;
}
if (ctx->RunBuf) MmFreePool(ctx->RunBuf);
IO_FS_ASYNC_COMPLETE complete = ctx->Complete;
void* completeContext = ctx->CompleteContext;
MmFreePool(ctx);
if (complete) complete(completeContext, status, information);
}
// Implements the async Read Irp Complete operation.
static void FatAsyncReadIrpComplete(IRP* irp, void* completeCtx) {
FAT_ASYNC_RW* ctx = (FAT_ASYNC_RW*)completeCtx;
if (!ctx) return;
if (ctx->CurrentDevice) {
IoDereferenceDeviceObject(ctx->CurrentDevice);
ctx->CurrentDevice = (DEVICE_OBJECT*)0;
}
if (!KSUCCESS(irp->Status)) {
FatAsyncFinish(ctx, irp->Status, ctx->Done);
return;
}
memcpy(ctx->Dst, ctx->RunBuf + ctx->CurrentOffset, ctx->CurrentCopy);
ctx->Dst += ctx->CurrentCopy;
ctx->Done += ctx->CurrentCopy;
ctx->Remaining -= ctx->CurrentCopy;
ctx->OffsetInCluster = 0;
ctx->LogicalCluster += ctx->CurrentClusters;
ctx->Cluster = ctx->CurrentNext;
if (ctx->CurrentClusters) {
uint32_t sectors = ctx->CurrentClusters * ctx->Vol->SectorsPerCluster;
uint32_t merges = ctx->CurrentClusters - 1u;
FatStatsAddReadRun(ctx->Vol, sectors, merges);
}
FatAsyncQueueContinuation(ctx);
}
// Implements the async Read Submit Next operation.
static void FatAsyncReadSubmitNext(FAT_ASYNC_RW* ctx) {
if (ctx->Remaining == 0) {
FatAsyncFinish(ctx, NTLSTATUS_SUCCESS, ctx->Done);
return;
}
if (!FatIsDataCluster(ctx->Vol, ctx->Cluster)) {
if (ctx->Done < ctx->Requested)
memset(ctx->Dst, 0, ctx->Requested - ctx->Done);
FatAsyncFinish(ctx, NTLSTATUS_DEVICE_ERROR, ctx->Done);
return;
}
FAT_VOL* v = ctx->Vol;
uint32_t runStart = ctx->Cluster;
uint32_t runSectors = 0;
uint32_t next = 0;
if (!FatBuildReadRun(ctx->File, ctx->LogicalCluster, ctx->Cluster,
FAT_READ_RUN_MAX_SECTORS, &runSectors, &next)) {
FatAsyncFinish(ctx, NTLSTATUS_DEVICE_ERROR, ctx->Done);
return;
}
uint32_t runBytes = runSectors * 512u;
size_t avail = runBytes - ctx->OffsetInCluster;
size_t copy = avail < ctx->Remaining ? avail : ctx->Remaining;
ctx->CurrentNext = next;
ctx->CurrentLast = runStart + (runSectors / v->SectorsPerCluster) - 1u;
ctx->CurrentClusters = runSectors / v->SectorsPerCluster;
ctx->CurrentOffset = ctx->OffsetInCluster;
ctx->CurrentCopy = copy;
ctx->CurrentDevice = IoReferenceDiskDevice(v->DriveIndex);
if (!ctx->CurrentDevice) {
FatAsyncFinish(ctx, NTLSTATUS_NOT_FOUND, ctx->Done);
return;
}
IoBuildReadWriteIrp(&ctx->Irp, IRP_MJ_READ,
ClusterToLba(v, runStart) * 512ull,
runSectors * 512u,
ctx->RunBuf);
ctx->Irp.CompletionFn = FatAsyncReadIrpComplete;
ctx->Irp.CompletionCtx = ctx;
KSTATUS s = IoSubmitIrp(ctx->CurrentDevice, &ctx->Irp);
if (s != NTLSTATUS_PENDING) {
IoDereferenceDeviceObject(ctx->CurrentDevice);
ctx->CurrentDevice = (DEVICE_OBJECT*)0;
FatAsyncFinish(ctx, s, ctx->Done);
}
}
KSTATUS FatReadAsync(IO_VOLUME_PROVIDER* fs, void* fileCtx,
uint64_t offset, void* buf, size_t size,
IO_FS_ASYNC_COMPLETE complete,
void* completeContext) {
(void)fs;
if (!complete) return NTLSTATUS_INVALID_PARAMETER;
if (!fileCtx || (!buf && size)) {
complete(completeContext, NTLSTATUS_INVALID_PARAMETER, 0);
return NTLSTATUS_PENDING;
}
FAT_FILE* f = (FAT_FILE*)fileCtx;
FAT_VOL* v = f->Vol;
if (offset >= f->FileSize || size == 0) {
complete(completeContext, NTLSTATUS_SUCCESS, 0);
return NTLSTATUS_PENDING;
}
if (offset + size > f->FileSize)
size = (size_t)(f->FileSize - offset);
KSTATUS ws = FatEnsureAsyncWorkerStarted();
if (!KSUCCESS(ws)) {
complete(completeContext, ws, 0);
return NTLSTATUS_PENDING;
}
FAT_ASYNC_RW* ctx = (FAT_ASYNC_RW*)MmAllocatePool(sizeof(FAT_ASYNC_RW));
if (!ctx) {
complete(completeContext, NTLSTATUS_NO_MEMORY, 0);
return NTLSTATUS_PENDING;
}
memset(ctx, 0, sizeof(*ctx));
LIST_INIT(&ctx->Link);
ctx->RunBuf = (uint8_t*)MmAllocatePool(FAT_READ_RUN_MAX_SECTORS * 512u);
if (!ctx->RunBuf) {
MmFreePool(ctx);
complete(completeContext, NTLSTATUS_NO_MEMORY, 0);
return NTLSTATUS_PENDING;
}
ctx->Vol = v;
ctx->File = f;
ctx->IsWrite = 0;
ctx->Offset = offset;
ctx->OffsetInCluster = (uint32_t)(offset % v->BytesPerCluster);
ctx->LogicalCluster = (uint32_t)(offset / v->BytesPerCluster);
ctx->Cluster = FatResolveFileCluster(f, ctx->LogicalCluster);
ctx->Remaining = size;
ctx->Requested = size;
ctx->Dst = (uint8_t*)buf;
ctx->Complete = complete;
ctx->CompleteContext = completeContext;
FatAsyncQueueContinuation(ctx);
return NTLSTATUS_PENDING;
}
// Implements the async Write Irp Complete operation.
static void FatAsyncWriteIrpComplete(IRP* irp, void* completeCtx) {
FAT_ASYNC_RW* ctx = (FAT_ASYNC_RW*)completeCtx;
if (!ctx) return;
if (ctx->CurrentDevice) {
IoDereferenceDeviceObject(ctx->CurrentDevice);
ctx->CurrentDevice = (DEVICE_OBJECT*)0;
}
if (!KSUCCESS(irp->Status)) {
FatAsyncFinish(ctx, irp->Status, ctx->Done);
return;
}
ctx->Src += ctx->CurrentCopy;
ctx->Done += ctx->CurrentCopy;
ctx->Remaining -= ctx->CurrentCopy;
ctx->OffsetInCluster = 0;
ctx->Cluster = ctx->CurrentNext;
FatAsyncQueueContinuation(ctx);
}
// Implements the async Write Submit Next operation.
static void FatAsyncWriteSubmitNext(FAT_ASYNC_RW* ctx) {
if (ctx->Remaining == 0) {
uint64_t newEnd = ctx->Offset + ctx->Done;
if (ctx->Done > 0) {
FAT_DIRENT_UPDATE u;
memset(&u, 0, sizeof(u));
FatQueryEncodedTime(&u.Timestamp);
u.Flags = FAT_DIRENT_UPDATE_WRITE_TIME;
if (newEnd > ctx->File->FileSize) {
ctx->File->FileSize = (uint32_t)newEnd;
u.Flags |= FAT_DIRENT_UPDATE_SIZE;
u.Size = ctx->File->FileSize;
}
DirentUpdateMetadata(ctx->Vol, ctx->File->DirentLba,
ctx->File->DirentOffset, &u);
}
FatAsyncFinish(ctx, NTLSTATUS_SUCCESS, ctx->Done);
return;
}
FAT_VOL* v = ctx->Vol;
if (!FatIsDataCluster(ctx->Vol, ctx->Cluster)) {
if (FatIsDataCluster(ctx->Vol, ctx->CurrentLast)) {
ctx->Cluster = AppendCluster(v, ctx->CurrentLast);
if (!ctx->Cluster) {
FatAsyncFinish(ctx, NTLSTATUS_NO_MEMORY, ctx->Done);
return;
}
} else {
FatAsyncFinish(ctx, NTLSTATUS_DEVICE_ERROR, ctx->Done);
return;
}
}
uint32_t runStart = ctx->Cluster;
uint32_t runSectors = v->SectorsPerCluster;
uint32_t last = ctx->Cluster;
uint32_t next = FatEntry(v, last);
if (ctx->OffsetInCluster == 0) {
while (next == last + 1 && FatIsDataCluster(v, next) &&
runSectors + v->SectorsPerCluster <= FAT_WRITE_RUN_MAX_SECTORS) {
last = next;
next = FatEntry(v, last);
runSectors += v->SectorsPerCluster;
}
}
uint32_t runBytes = runSectors * 512u;
size_t avail = runBytes - ctx->OffsetInCluster;
size_t copy = avail < ctx->Remaining ? avail : ctx->Remaining;
bool partial = (ctx->OffsetInCluster > 0) || (copy < (size_t)runBytes);
if (partial) {
KSTATUS rs = ReadSectors(v, ClusterToLba(v, runStart), runSectors, ctx->RunBuf);
if (!KSUCCESS(rs)) {
FatAsyncFinish(ctx, rs, ctx->Done);
return;
}
}
memcpy(ctx->RunBuf + ctx->OffsetInCluster, ctx->Src, copy);
ctx->CurrentNext = next;
ctx->CurrentLast = last;
ctx->CurrentOffset = ctx->OffsetInCluster;
ctx->CurrentCopy = copy;
ctx->CurrentDevice = IoReferenceDiskDevice(v->DriveIndex);
if (!ctx->CurrentDevice) {
FatAsyncFinish(ctx, NTLSTATUS_NOT_FOUND, ctx->Done);
return;
}
IoBuildReadWriteIrp(&ctx->Irp, IRP_MJ_WRITE,
ClusterToLba(v, runStart) * 512ull,
runSectors * 512u,
ctx->RunBuf);
ctx->Irp.CompletionFn = FatAsyncWriteIrpComplete;
ctx->Irp.CompletionCtx = ctx;
KSTATUS s = IoSubmitIrp(ctx->CurrentDevice, &ctx->Irp);
if (s != NTLSTATUS_PENDING) {
IoDereferenceDeviceObject(ctx->CurrentDevice);
ctx->CurrentDevice = (DEVICE_OBJECT*)0;
FatAsyncFinish(ctx, s, ctx->Done);
}
}
KSTATUS FatWriteAsync(IO_VOLUME_PROVIDER* fs, void* fileCtx,
uint64_t offset, const void* buf, size_t size,
IO_FS_ASYNC_COMPLETE complete,
void* completeContext) {
(void)fs;
if (!complete) return NTLSTATUS_INVALID_PARAMETER;
if (!fileCtx || (!buf && size)) {
complete(completeContext, NTLSTATUS_INVALID_PARAMETER, 0);
return NTLSTATUS_PENDING;
}
if (size == 0) {
complete(completeContext, NTLSTATUS_SUCCESS, 0);
return NTLSTATUS_PENDING;
}
KSTATUS ws = FatEnsureAsyncWorkerStarted();
if (!KSUCCESS(ws)) {
complete(completeContext, ws, 0);
return NTLSTATUS_PENDING;
}
FAT_FILE* f = (FAT_FILE*)fileCtx;
FAT_VOL* v = f->Vol;
FatFileInvalidateChainCache(f);
if (f->StartCluster < 2) {
uint32_t first = AllocCluster(v);
if (!first) {
complete(completeContext, NTLSTATUS_NO_MEMORY, 0);
return NTLSTATUS_PENDING;
}
f->StartCluster = first;
DirentUpdateFirstCluster(v, f->DirentLba, f->DirentOffset, first);
}
uint32_t clusterIndex = (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 (!FatIsDataCluster(v, next)) {
next = AppendCluster(v, cluster);
if (!next) {
complete(completeContext, NTLSTATUS_NO_MEMORY, 0);
return NTLSTATUS_PENDING;
}
}
cluster = next;
}
FAT_ASYNC_RW* ctx = (FAT_ASYNC_RW*)MmAllocatePool(sizeof(FAT_ASYNC_RW));
if (!ctx) {
complete(completeContext, NTLSTATUS_NO_MEMORY, 0);
return NTLSTATUS_PENDING;
}
memset(ctx, 0, sizeof(*ctx));
LIST_INIT(&ctx->Link);
ctx->RunBuf = (uint8_t*)MmAllocatePool(FAT_WRITE_RUN_MAX_SECTORS * 512u);
if (!ctx->RunBuf) {
MmFreePool(ctx);
complete(completeContext, NTLSTATUS_NO_MEMORY, 0);
return NTLSTATUS_PENDING;
}
ctx->Vol = v;
ctx->File = f;
ctx->IsWrite = 1;
ctx->Offset = offset;
ctx->OffsetInCluster = (uint32_t)(offset % v->BytesPerCluster);
ctx->Cluster = cluster;
ctx->Remaining = size;
ctx->Requested = size;
ctx->Src = (const uint8_t*)buf;
ctx->Complete = complete;
ctx->CompleteContext = completeContext;
FatAsyncQueueContinuation(ctx);
return NTLSTATUS_PENDING;
}
// Driver entry and dynamic partition-backed mount instances.
static LIST_ENTRY gFatMounts;
static uint32_t gMountedCount;
static MUTEX gFatMountMutex;
static SPINLOCK gFatMountMutexInitLock = SPINLOCK_INIT;
static int gFatMountMutexReady;
// Marks only this FAT mount's cache state unusable when its backing volume dies.
void FatVolumeDead(IO_VOLUME_PROVIDER* provider, IO_VOLUME_REMOVAL_REASON reason) {
(void)reason;
if (!provider || !provider->Context)
return;
FatCacheInvalidateVolume((FAT_VOL*)provider->Context);
}
// Releases one dynamically allocated FAT mount after every volume reference has drained.
void FatDestroyVolumeProvider(IO_VOLUME_PROVIDER* provider) {
if (!provider || !provider->Context)
return;
FAT_VOL* volume = (FAT_VOL*)provider->Context;
FatCacheInvalidateVolume(volume);
memset(volume, 0, sizeof(*volume));
MmFreePool(volume);
}
// Initializes the mount-list mutex without allocating kernel objects.
static void FatMountEnsureMutex(void) {
if (gFatMountMutexReady)
return;
uintptr_t flags = KeAcquireSpinlock(&gFatMountMutexInitLock);
if (!gFatMountMutexReady) {
memset(&gFatMountMutex, 0, sizeof(gFatMountMutex));
ObInitializeObjectHeader(&gFatMountMutex.Header, ObjectTypeMutex);
gFatMountMutex.Lock = (SPINLOCK)SPINLOCK_INIT;
gFatMountMutex.Owner = (struct THREAD*)0;
LIST_INIT(&gFatMountMutex.WaitList);
LIST_INIT(&gFatMountMutex.OwnerLink);
gFatMountMutex.DonationClass = KeSchedulingNormal;
gFatMountMutex.DonationPriority = PriorityLow;
gFatMountMutex.HasDonation = 0;
gFatMountMutex.Abandoned = 0;
gFatMountMutexReady = 1;
}
KeReleaseSpinlock(&gFatMountMutexInitLock, flags);
}
// Returns one active FAT mount for an exact partition extent while the mount mutex is held.
static FAT_VOL* FatFindMountLocked(const IO_PARTITION_INFO* partition) {
if (!partition)
return (FAT_VOL*)0;
for (LIST_ENTRY* entry = gFatMounts.Fwd; entry != &gFatMounts; entry = entry->Fwd) {
FAT_VOL* volume = CONTAINING_RECORD(entry, FAT_VOL, MountLink);
if (volume->DriveIndex == partition->DiskIndex &&
volume->PartitionLba == partition->StartLba) {
return volume;
}
}
return (FAT_VOL*)0;
}
// Probes one I/O-core partition extent and mounts it when its VBR describes supported FAT16/FAT32.
static bool FatTryMountPartition(const IO_PARTITION_INFO* partition) {
if (!partition || partition->SectorCount == 0 || !IoDiskDevicePresent(partition->DiskIndex))
return false;
FatMountEnsureMutex();
if (!KE_MUTEX_ACQUIRE_SUCCEEDED(KeAcquireMutex(&gFatMountMutex, KE_WAIT_INFINITE)))
return false;
if (FatFindMountLocked(partition)) {
KeReleaseMutex(&gFatMountMutex);
return false;
}
FAT_VOL* v = (FAT_VOL*)MmAllocatePool(sizeof(FAT_VOL));
if (!v) {
KeReleaseMutex(&gFatMountMutex);
return false;
}
memset(v, 0, sizeof(*v));
LIST_INIT(&v->MountLink);
v->DriveIndex = partition->DiskIndex;
v->PartitionLba = partition->StartLba;
v->PartitionSize = partition->SectorCount;
uint8_t sector[512];
if (!KSUCCESS(ReadSectors(v, v->PartitionLba, 1, sector))) {
MmFreePool(v);
KeReleaseMutex(&gFatMountMutex);
return false;
}
BPB* bpb = (BPB*)sector;
FAT_BPB_LAYOUT layout;
if (!FatComputeBpbLayout(bpb, &layout) ||
(layout.Variant != FatVariant16 && layout.Variant != FatVariant32)) {
MmFreePool(v);
KeReleaseMutex(&gFatMountMutex);
return false;
}
v->Variant = layout.Variant;
v->BytesPerSector = bpb->BytesPerSector;
v->SectorsPerCluster = bpb->SectorsPerCluster;
v->BytesPerCluster = bpb->BytesPerSector * bpb->SectorsPerCluster;
v->ReservedSectors = bpb->ReservedSectors;
v->NumFats = bpb->NumFats;
v->FatSize = layout.FatSize;
v->RootEntryCount = bpb->RootEntryCount;
v->RootDirSectors = layout.RootDirSectors;
v->RootDirStart = v->PartitionLba + bpb->ReservedSectors +
(uint64_t)bpb->NumFats * layout.FatSize;
v->RootCluster = v->Variant == FatVariant16 ? FAT_FIXED_ROOT_CLUSTER : bpb->RootCluster;
v->DataStart = v->RootDirStart + layout.RootDirSectors;
v->NextFreeHint = 2;
v->TotalClusters = layout.TotalClusters;
if (v->PartitionSize > layout.TotalSectors)
v->PartitionSize = layout.TotalSectors;
FatDebugPrint("FAT: disk=%u lba=%llu %s layout fat=%u root-sectors=%u data=%u clusters=%u\n",
v->DriveIndex, (unsigned long long)v->PartitionLba,
FatVariantName(v->Variant), v->FatSize,
v->RootDirSectors, layout.DataSectors, v->TotalClusters);
IO_VOLUME_PROVIDER provider;
FatInitProvider(&provider, v);
IO_VOLUME_SOURCE volParams;
volParams.Type = IoVolumeSourcePartition;
volParams.DiskIndex = v->DriveIndex;
memset(volParams.Reserved, 0, sizeof(volParams.Reserved));
volParams.StartLba = v->PartitionLba;
volParams.SectorCount = v->PartitionSize;
IO_VOLUME* volume = (IO_VOLUME*)0;
KSTATUS status = IoCreateVolume(&provider, &volParams, &volume);
if (!KSUCCESS(status)) {
if (volume) {
(void)IoRemoveVolume(volume, IoVolumeUnmounted);
IoReleaseVolume(volume);
} else {
MmFreePool(v);
}
DbgPrint("FAT: disk=%u lba=%llu mount failed status=0x%x\n",
partition->DiskIndex, (unsigned long long)partition->StartLba, status);
KeReleaseMutex(&gFatMountMutex);
return false;
}
// Retain the IoCreateVolume caller reference while this provider remains on the active mount list.
v->Volume = volume;
ListInsertTail(&gFatMounts, &v->MountLink);
gMountedCount++;
uint32_t instanceId = IoGetVolumeInstanceId(volume);
uint32_t mountedCount = gMountedCount;
DbgPrint("FAT: disk=%u lba=%llu %s mounted volume=%u; total=%u\n",
v->DriveIndex, (unsigned long long)v->PartitionLba,
FatVariantName(v->Variant), instanceId, mountedCount);
KeReleaseMutex(&gFatMountMutex);
return true;
}
// Unmounts one exact FAT provider instance when its backing partition disappears.
static void FatUnmountPartition(const IO_PARTITION_INFO* partition, IO_PARTITION_CHANGE change) {
if (!partition)
return;
FatMountEnsureMutex();
if (!KE_MUTEX_ACQUIRE_SUCCEEDED(KeAcquireMutex(&gFatMountMutex, KE_WAIT_INFINITE)))
return;
FAT_VOL* v = FatFindMountLocked(partition);
if (!v) {
KeReleaseMutex(&gFatMountMutex);
return;
}
IO_VOLUME* volume = v->Volume;
uint32_t instanceId = IoGetVolumeInstanceId(volume);
ListRemove(&v->MountLink);
v->Volume = (IO_VOLUME*)0;
if (gMountedCount)
gMountedCount--;
uint32_t mountedCount = gMountedCount;
KeReleaseMutex(&gFatMountMutex);
(void)IoRemoveVolume(volume, IoVolumeBackingRemoved);
IoReleaseVolume(volume); // Drop the active mount-list reference.
DbgPrint("FAT: disk=%u lba=%llu %s; unmounted volume=%u; total=%u\n",
partition->DiskIndex, (unsigned long long)partition->StartLba,
change == IoPartitionSurpriseRemoved ? "surprise removed" : "removed",
instanceId, mountedCount);
}
// Receives immutable partition extents from the I/O core; FAT never parses partition tables.
static void FatPartitionChange(const IO_PARTITION_INFO* partition,
IO_PARTITION_CHANGE change,
void* context) {
(void)context;
switch (change) {
case IoPartitionArrived:
(void)FatTryMountPartition(partition);
break;
case IoPartitionSurpriseRemoved:
case IoPartitionRemoved:
FatUnmountPartition(partition, change);
break;
default:
break;
}
}
// Initializes FAT and subscribes to the authoritative kernel partition stream.
KSTATUS DriverEntry(const NTL_DDK* ddk, NTL_DRIVER_INIT* init) {
DbgPrint("FAT: Init\n");
if (!ddk)
return NTLSTATUS_INVALID_PARAMETER;
(void)init;
FatCacheInvalidate();
LIST_INIT(&gFatMounts);
gMountedCount = 0;
FatMountEnsureMutex();
KSTATUS status = IoRegisterPartitionChangeCallback(FatPartitionChange, NULL);
if (!KSUCCESS(status)) {
DbgPrint("FAT: partition callback registration failed status=0x%x\n", status);
return status;
}
if (!gMountedCount)
DbgPrint("FAT: no mountable FAT partitions found; driver resident for partition changes.\n");
DbgPrint("FAT: Init complete; mounted %u volume(s).\n", gMountedCount);
return NTLSTATUS_SUCCESS;
}