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

558 lines
20 KiB
C

// FAT allocation, cache, and metadata core shared by the filesystem operation translation units.
#include <stdbool.h>
#include "fat_internal.h"
// FAT sector cache (fixes #1 and #5)
// 32 direct-mapped slots, indexed by (lba % FAT_CACHE_SLOTS).
// Write-back: FatWriteEntry modifies the in-cache bytes and marks the slot
// dirty. FatFlushCache() 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];
// 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 FatFlushCache().
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.
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.
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);
}
// FatFlushCache — write every dirty slot to all FAT copies, clear dirty flag.
// Defined after FAT_VOL is fully declared (below).
void FatFlushCache(FAT_VOL* v);
// 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.
KSTATUS FatReadSectors (FAT_VOL* v, uint64_t lba, uint32_t n, void* b) { return ReadSectorsRaw (v, lba, n, b); }
// Updates sectors.
KSTATUS FatWriteSectors(FAT_VOL* v, uint64_t lba, uint32_t n, const void* b) { return WriteSectorsRaw(v, lba, n, b); }
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;
}
uint32_t FatEocMark(FAT_VOL* v) {
return v && v->Variant == FatVariant16 ? FAT16_EOC_MARK : FAT32_EOC_MARK;
}
bool FatIsDataCluster(FAT_VOL* v, uint32_t cluster) {
return v && cluster >= 2u && cluster < FatEocThreshold(v);
}
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;
}
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;
}
bool FatIsFixedRoot(FAT_VOL* v, uint32_t dirCluster) {
return v && v->Variant == FatVariant16 && dirCluster == FAT_FIXED_ROOT_CLUSTER;
}
uint64_t FatClusterToLba(FAT_VOL* v, uint32_t cluster);
uint32_t FatEntry(FAT_VOL* v, uint32_t cluster);
bool FatDirectoryFirstSegment(FAT_VOL* v, uint32_t dirCluster,
uint32_t* cursor, uint64_t* lba,
uint32_t* sectors) {
if (!v || !cursor || !lba || !sectors) return false;
if (FatIsFixedRoot(v, dirCluster)) {
if (!v->RootDirSectors) return false;
*cursor = 0;
*lba = v->RootDirStart;
*sectors = 1;
return true;
}
if (!FatIsDataCluster(v, dirCluster)) return false;
*cursor = dirCluster;
*lba = FatClusterToLba(v, dirCluster);
*sectors = v->SectorsPerCluster;
return true;
}
bool FatDirectoryNextSegment(FAT_VOL* v, uint32_t dirCluster,
uint32_t* cursor, uint64_t* lba,
uint32_t* sectors) {
if (!v || !cursor || !lba || !sectors) return false;
if (FatIsFixedRoot(v, dirCluster)) {
uint32_t next = *cursor + 1u;
if (next >= v->RootDirSectors) return false;
*cursor = next;
*lba = v->RootDirStart + next;
*sectors = 1;
return true;
}
uint32_t next = FatEntry(v, *cursor);
if (!FatIsDataCluster(v, next)) return false;
*cursor = next;
*lba = FatClusterToLba(v, next);
*sectors = v->SectorsPerCluster;
return true;
}
// Implements the cluster To Lba operation.
uint64_t FatClusterToLba(FAT_VOL* v, uint32_t cluster) {
return v->DataStart + (uint64_t)(cluster - 2) * v->SectorsPerCluster;
}
// FatFlushCache — needs full FAT_VOL definition.
void FatFlushCache(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).
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.
KSTATUS FatWriteEntry(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).
uint32_t FatAllocClusterEx(FAT_VOL* v, bool 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(FatWriteEntry(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);
FatWriteSectors(v, FatClusterToLba(v, c), v->SectorsPerCluster, zero);
MmFreePool(zero);
}
}
return c;
}
}
}
return 0;
}
uint32_t FatAllocCluster(FAT_VOL* v) {
return FatAllocClusterEx(v, 1);
}
uint32_t FatAppendClusterEx(FAT_VOL* v, uint32_t lastCluster, bool zeroCluster) {
uint32_t newCluster = FatAllocClusterEx(v, zeroCluster);
if (!newCluster) return 0;
if (!KSUCCESS(FatWriteEntry(v, lastCluster, newCluster))) return 0;
return newCluster;
}
// Appends cluster.
uint32_t FatAppendCluster(FAT_VOL* v, uint32_t lastCluster) {
return FatAppendClusterEx(v, lastCluster, 1);
}
// Returns chain End.
uint32_t FatGetChainEnd(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.
void FatFreeClusterChain(FAT_VOL* v, uint32_t start) {
uint32_t cluster = start;
while (FatIsDataCluster(v, cluster)) {
uint32_t next = FatEntry(v, cluster);
FatWriteEntry(v, cluster, FAT_FREE);
if (cluster < v->NextFreeHint) v->NextFreeHint = cluster;
cluster = next;
}
}
// FAT timestamp and DIRENT metadata helpers.
// Implements the fat Is Leap Year operation.
static bool 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.
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);
}
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.
void FatDecodeAccessDate(uint16_t date, SYS_SYSTEM_TIME* out) {
FatDecodeDateTime(date, 0, 0, out);
}
KSTATUS FatDirentUpdateMetadata(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 = FatReadSectors(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 = FatWriteSectors(v, lba, 1, sec);
}
MmFreePool(sec);
return s;
}
KSTATUS FatDirentUpdateSize(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 FatDirentUpdateMetadata(v, lba, offset, &u);
}
KSTATUS FatDirentUpdateFirstCluster(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 FatDirentUpdateMetadata(v, lba, offset, &u);
}