3459 lines
120 KiB
C
3459 lines
120 KiB
C
// FAT32 filesystem driver. This file owns filesystem operations and FAT-backed file access.
|
|
|
|
#include "fat32.h"
|
|
|
|
// On-disk structures.
|
|
#pragma pack(push, 1)
|
|
|
|
typedef struct {
|
|
uint8_t Jump[3];
|
|
char OemId[8];
|
|
uint16_t BytesPerSector;
|
|
uint8_t SectorsPerCluster;
|
|
uint16_t ReservedSectors;
|
|
uint8_t NumFats;
|
|
uint16_t RootEntryCount;
|
|
uint16_t TotalSectors16;
|
|
uint8_t MediaType;
|
|
uint16_t FatSize16;
|
|
uint16_t SectorsPerTrack;
|
|
uint16_t NumHeads;
|
|
uint32_t HiddenSectors;
|
|
uint32_t TotalSectors32;
|
|
uint32_t FatSize32;
|
|
uint16_t ExtFlags;
|
|
uint16_t FsVersion;
|
|
uint32_t RootCluster;
|
|
uint16_t FsInfoSector;
|
|
uint16_t BackupBootSector;
|
|
uint8_t Reserved[12];
|
|
uint8_t DriveNumber;
|
|
uint8_t Reserved1;
|
|
uint8_t BootSignature;
|
|
uint32_t VolumeId;
|
|
char VolumeLabel[11];
|
|
char FsType[8];
|
|
} BPB;
|
|
|
|
typedef struct {
|
|
char Name[8];
|
|
char Ext[3];
|
|
uint8_t Attr;
|
|
uint8_t Reserved;
|
|
uint8_t CrtTimeTenth;
|
|
uint16_t CrtTime;
|
|
uint16_t CrtDate;
|
|
uint16_t LastAccDate;
|
|
uint16_t FirstClusterHi;
|
|
uint16_t WrtTime;
|
|
uint16_t WrtDate;
|
|
uint16_t FirstClusterLo;
|
|
uint32_t FileSize;
|
|
} DIRENT;
|
|
|
|
typedef struct {
|
|
uint8_t Order;
|
|
uint16_t Name1[5];
|
|
uint8_t Attr;
|
|
uint8_t Type;
|
|
uint8_t Checksum;
|
|
uint16_t Name2[6];
|
|
uint16_t FirstCluster;
|
|
uint16_t Name3[2];
|
|
} LFN_ENTRY;
|
|
|
|
typedef struct {
|
|
uint32_t Data1;
|
|
uint16_t Data2;
|
|
uint16_t Data3;
|
|
uint8_t Data4[8];
|
|
} EFI_GUID;
|
|
|
|
typedef struct {
|
|
char Signature[8];
|
|
uint32_t Revision;
|
|
uint32_t HeaderSize;
|
|
uint32_t HeaderCRC32;
|
|
uint32_t Reserved;
|
|
uint64_t MyLBA;
|
|
uint64_t AlternateLBA;
|
|
uint64_t FirstUsableLBA;
|
|
uint64_t LastUsableLBA;
|
|
EFI_GUID DiskGUID;
|
|
uint64_t PartitionEntryLBA;
|
|
uint32_t NumberOfPartitionEntries;
|
|
uint32_t SizeOfPartitionEntry;
|
|
uint32_t PartitionEntryArrayCRC32;
|
|
} GPT_HEADER;
|
|
|
|
typedef struct {
|
|
EFI_GUID PartitionTypeGUID;
|
|
EFI_GUID UniquePartitionGUID;
|
|
uint64_t StartingLBA;
|
|
uint64_t EndingLBA;
|
|
uint64_t Attributes;
|
|
uint16_t PartitionName[36];
|
|
} GPT_ENTRY;
|
|
|
|
#pragma pack(pop)
|
|
|
|
#define ATTR_VOLUME_ID 0x08
|
|
#define ATTR_DIRECTORY 0x10
|
|
#define ATTR_ARCHIVE 0x20
|
|
#define ATTR_LFN 0x0F
|
|
#define FAT_NTRES_BASE_LOWER 0x08u
|
|
#define FAT_NTRES_EXT_LOWER 0x10u
|
|
#define FAT32_EOC 0x0FFFFFF8u
|
|
#define FAT32_EOC_MARK 0x0FFFFFFFu
|
|
#define FAT32_FREE 0x00000000u
|
|
|
|
// FAT32_VOL forward declaration (needed by cache helpers).
|
|
typedef struct FAT32_VOL_S FAT32_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 {
|
|
FAT32_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 FAT32_PATH_CACHE_SLOTS 64u
|
|
#define FAT32_PATH_CACHE_MAX 512u
|
|
#define FAT32_CHAIN_CACHE_SLOTS 16u
|
|
#define FAT32_WRITE_RUN_MAX_SECTORS 128u
|
|
#define FAT32_READ_RUN_MAX_SECTORS 512u
|
|
|
|
typedef struct FAT32_PATH_CACHE_ENTRY {
|
|
bool Valid;
|
|
uint64_t Generation;
|
|
uint32_t Hash;
|
|
size_t PathLength;
|
|
char Path[FAT32_PATH_CACHE_MAX];
|
|
uint32_t Cluster;
|
|
uint32_t Size;
|
|
uint8_t Attr;
|
|
uint64_t DirentLba;
|
|
uint32_t DirentOffset;
|
|
} FAT32_PATH_CACHE_ENTRY;
|
|
|
|
typedef struct FAT32_CHAIN_CACHE_ENTRY {
|
|
bool Valid;
|
|
uint32_t LogicalStart;
|
|
uint32_t PhysicalStart;
|
|
uint32_t Length;
|
|
uint32_t NextCluster;
|
|
} FAT32_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));
|
|
gFatCacheMutex.Header.Type = ObjectTypeMutex;
|
|
gFatCacheMutex.Header.RefCount = 1;
|
|
gFatCacheMutex.Header.Destroy = (void*)0;
|
|
gFatCacheMutex.Lock = (SPINLOCK)SPINLOCK_INIT;
|
|
gFatCacheMutex.Owner = (struct THREAD*)0;
|
|
LIST_INIT(&gFatCacheMutex.WaitList);
|
|
gFatCacheMutexReady = 1;
|
|
}
|
|
KeReleaseSpinlock(&gFatCacheMutexInitLock, flags);
|
|
}
|
|
|
|
static int FatCacheAcquire(void) {
|
|
if (!KeCanWait())
|
|
return 0;
|
|
FatCacheEnsureMutex();
|
|
return KSUCCESS(KeAcquireMutex(&gFatCacheMutex));
|
|
}
|
|
|
|
static void FatCacheRelease(int held) {
|
|
if (held)
|
|
KeReleaseMutex(&gFatCacheMutex);
|
|
}
|
|
|
|
// Raw (non-cached) sector I/O — forward declarations.
|
|
static KSTATUS ReadSectorsRaw (FAT32_VOL *v, uint64_t lba, uint32_t count, void *buf);
|
|
static KSTATUS WriteSectorsRaw(FAT32_VOL *v, uint64_t lba, uint32_t count, const void *buf);
|
|
|
|
// Read one FAT sector through the cache.
|
|
// Caller must hold the FAT cache mutex while using the returned pointer.
|
|
static KSTATUS FatCacheRead(FAT32_VOL *v, uint64_t lba, uint8_t **out) {
|
|
uint32_t slot = (uint32_t)(lba % FAT_CACHE_SLOTS);
|
|
FAT_CACHE_ENTRY *e = &gFatCache[slot];
|
|
|
|
if (e->valid && e->vol == v && e->lba == lba) {
|
|
*out = e->data;
|
|
return STATUS_SUCCESS;
|
|
}
|
|
|
|
// Evict current occupant: single-copy writeback on collision.
|
|
// The full multi-copy flush happens in FlushFatCache().
|
|
if (e->valid && e->dirty && 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 = (FAT32_VOL*)0; return s; }
|
|
|
|
e->vol = v;
|
|
e->lba = lba;
|
|
e->valid = true;
|
|
e->dirty = false;
|
|
*out = e->data;
|
|
return STATUS_SUCCESS;
|
|
}
|
|
|
|
// Implements the fat Cache Mark Dirty operation.
|
|
static void FatCacheMarkDirty(FAT32_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;
|
|
}
|
|
|
|
// Implements the fat Cache Invalidate operation.
|
|
static void FatCacheInvalidate(void) {
|
|
int held = FatCacheAcquire();
|
|
for (int i = 0; i < FAT_CACHE_SLOTS; i++) {
|
|
gFatCache[i].vol = (FAT32_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 FAT32_VOL is fully declared (below).
|
|
static void FlushFatCache(FAT32_VOL *v);
|
|
|
|
// Driver state.
|
|
struct FAT32_VOL_S {
|
|
uint8_t DriveIndex; // physical disk DEVICE_OBJECT index
|
|
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;
|
|
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;
|
|
FAT32_PATH_CACHE_ENTRY PathCache[FAT32_PATH_CACHE_SLOTS];
|
|
};
|
|
typedef struct {
|
|
uint32_t StartCluster;
|
|
uint32_t FileSize;
|
|
FAT32_VOL *Vol;
|
|
uint64_t DirentLba;
|
|
uint32_t DirentOffset;
|
|
SPINLOCK ChainCacheLock;
|
|
uint32_t ChainCacheClock;
|
|
FAT32_CHAIN_CACHE_ENTRY ChainCache[FAT32_CHAIN_CACHE_SLOTS];
|
|
} FAT32_FILE;
|
|
|
|
// Raw sector I/O (bypasses FAT cache; used for file data and cache eviction).
|
|
static KSTATUS ReadSectorsRaw(FAT32_VOL *v, uint64_t lba,
|
|
uint32_t count, void *buf) {
|
|
if (!v) return STATUS_INVALID_PARAMETER;
|
|
return IoReadDiskSectors(v->DriveIndex, lba, count, buf);
|
|
}
|
|
|
|
static KSTATUS WriteSectorsRaw(FAT32_VOL *v, uint64_t lba,
|
|
uint32_t count, const void *buf) {
|
|
if (!v) return STATUS_INVALID_PARAMETER;
|
|
return IoWriteDiskSectors(v->DriveIndex, lba, count, buf);
|
|
}
|
|
|
|
// Convenience aliases for file-data I/O.
|
|
static KSTATUS ReadSectors (FAT32_VOL *v, uint64_t lba, uint32_t n, void *b) { return ReadSectorsRaw (v, lba, n, b); }
|
|
// Updates sectors.
|
|
static KSTATUS WriteSectors(FAT32_VOL *v, uint64_t lba, uint32_t n, const void *b) { return WriteSectorsRaw(v, lba, n, b); }
|
|
|
|
// Implements the cluster To Lba operation.
|
|
static uint64_t ClusterToLba(FAT32_VOL *v, uint32_t cluster) {
|
|
return v->DataStart + (uint64_t)(cluster - 2) * v->SectorsPerCluster;
|
|
}
|
|
|
|
// FlushFatCache — needs full FAT32_VOL definition.
|
|
static void FlushFatCache(FAT32_VOL *v) {
|
|
uint64_t fat0Start = (uint64_t)v->PartitionLba + v->ReservedSectors;
|
|
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(FAT32_VOL *v, uint32_t cluster) {
|
|
uint32_t fatOffset = cluster * 4;
|
|
uint64_t sector = (uint64_t)v->PartitionLba + v->ReservedSectors
|
|
+ fatOffset / v->BytesPerSector;
|
|
uint32_t offInSec = fatOffset % v->BytesPerSector;
|
|
|
|
int held = FatCacheAcquire();
|
|
uint8_t *sec;
|
|
KSTATUS s = FatCacheRead(v, sector, &sec);
|
|
uint32_t value = 0;
|
|
if (KSUCCESS(s))
|
|
value = *(uint32_t *)(sec + offInSec) & 0x0FFFFFFF;
|
|
FatCacheRelease(held);
|
|
return value;
|
|
}
|
|
|
|
// Updates fat Entry.
|
|
static KSTATUS WriteFatEntry(FAT32_VOL *v, uint32_t cluster, uint32_t value) {
|
|
uint32_t fatOffset = cluster * 4;
|
|
uint64_t sector = (uint64_t)v->PartitionLba + v->ReservedSectors
|
|
+ fatOffset / v->BytesPerSector;
|
|
uint32_t offInSec = fatOffset % v->BytesPerSector;
|
|
|
|
int held = FatCacheAcquire();
|
|
uint8_t *sec;
|
|
KSTATUS s = FatCacheRead(v, sector, &sec);
|
|
if (!KSUCCESS(s)) {
|
|
FatCacheRelease(held);
|
|
return s;
|
|
}
|
|
|
|
uint32_t existing = *(uint32_t *)(sec + offInSec);
|
|
*(uint32_t *)(sec + offInSec) = (existing & 0xF0000000u)
|
|
| (value & 0x0FFFFFFFu);
|
|
FatCacheMarkDirty(v, sector);
|
|
FatCacheRelease(held);
|
|
return STATUS_SUCCESS;
|
|
}
|
|
|
|
// Cluster allocation (fix #3: next-free hint + two-phase wrap-around scan).
|
|
static uint32_t AllocClusterEx(FAT32_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) == FAT32_FREE) {
|
|
if (!KSUCCESS(WriteFatEntry(v, c, FAT32_EOC_MARK))) 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(FAT32_VOL *v) {
|
|
return AllocClusterEx(v, 1);
|
|
}
|
|
|
|
static uint32_t AppendClusterEx(FAT32_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(FAT32_VOL *v, uint32_t lastCluster) {
|
|
return AppendClusterEx(v, lastCluster, 1);
|
|
}
|
|
|
|
// Returns chain End.
|
|
static uint32_t GetChainEnd(FAT32_VOL *v, uint32_t start, uint32_t *outCount) {
|
|
uint32_t cluster = start, prev = start, count = 0;
|
|
while (cluster >= 2 && cluster < FAT32_EOC) {
|
|
prev = cluster; count++;
|
|
cluster = FatEntry(v, cluster);
|
|
}
|
|
if (outCount) *outCount = count;
|
|
return prev;
|
|
}
|
|
|
|
// Releases cluster Chain.
|
|
static void FreeClusterChain(FAT32_VOL *v, uint32_t start) {
|
|
uint32_t cluster = start;
|
|
while (cluster >= 2 && cluster < FAT32_EOC) {
|
|
uint32_t next = FatEntry(v, cluster);
|
|
WriteFatEntry(v, cluster, FAT32_FREE);
|
|
if (cluster < v->NextFreeHint) v->NextFreeHint = cluster;
|
|
cluster = next;
|
|
}
|
|
}
|
|
|
|
// FAT timestamp and DIRENT metadata helpers.
|
|
|
|
typedef struct FAT32_ENCODED_TIME {
|
|
uint16_t Date;
|
|
uint16_t Time;
|
|
uint8_t CreateTimeTenth;
|
|
} FAT32_ENCODED_TIME;
|
|
|
|
typedef struct FAT32_DIRENT_UPDATE {
|
|
uint32_t Flags;
|
|
uint32_t Size;
|
|
uint32_t FirstCluster;
|
|
FAT32_ENCODED_TIME Timestamp;
|
|
} FAT32_DIRENT_UPDATE;
|
|
|
|
#define FAT32_DIRENT_UPDATE_SIZE 0x00000001u
|
|
#define FAT32_DIRENT_UPDATE_FIRST_CLUSTER 0x00000002u
|
|
#define FAT32_DIRENT_UPDATE_CREATE_TIME 0x00000004u
|
|
#define FAT32_DIRENT_UPDATE_WRITE_TIME 0x00000008u
|
|
#define FAT32_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(FAT32_ENCODED_TIME* out) {
|
|
SYS_SYSTEM_TIME now;
|
|
memset(&now, 0, sizeof(now));
|
|
if (KeQuerySystemTime(&now) != STATUS_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(FAT32_VOL *v, uint64_t lba,
|
|
uint32_t offset,
|
|
const FAT32_DIRENT_UPDATE* update) {
|
|
if (!v || !update || !update->Flags) return STATUS_INVALID_PARAMETER;
|
|
uint8_t *sec = (uint8_t *)MmAllocatePool(v->BytesPerSector);
|
|
if (!sec) return STATUS_NO_MEMORY;
|
|
KSTATUS s = ReadSectors(v, lba, 1, sec);
|
|
if (KSUCCESS(s)) {
|
|
DIRENT *d = (DIRENT *)(sec + offset);
|
|
if (update->Flags & FAT32_DIRENT_UPDATE_SIZE)
|
|
d->FileSize = update->Size;
|
|
if (update->Flags & FAT32_DIRENT_UPDATE_FIRST_CLUSTER) {
|
|
d->FirstClusterHi = (uint16_t)(update->FirstCluster >> 16);
|
|
d->FirstClusterLo = (uint16_t)(update->FirstCluster & 0xFFFF);
|
|
}
|
|
if (update->Flags & FAT32_DIRENT_UPDATE_CREATE_TIME) {
|
|
d->CrtTimeTenth = update->Timestamp.CreateTimeTenth;
|
|
d->CrtTime = update->Timestamp.Time;
|
|
d->CrtDate = update->Timestamp.Date;
|
|
}
|
|
if (update->Flags & FAT32_DIRENT_UPDATE_WRITE_TIME) {
|
|
d->WrtTime = update->Timestamp.Time;
|
|
d->WrtDate = update->Timestamp.Date;
|
|
}
|
|
if (update->Flags & FAT32_DIRENT_UPDATE_ACCESS_DATE)
|
|
d->LastAccDate = update->Timestamp.Date;
|
|
s = WriteSectors(v, lba, 1, sec);
|
|
}
|
|
MmFreePool(sec);
|
|
return s;
|
|
}
|
|
|
|
static KSTATUS DirentUpdateSize(FAT32_VOL *v, uint64_t lba,
|
|
uint32_t offset, uint32_t size) {
|
|
FAT32_DIRENT_UPDATE u;
|
|
memset(&u, 0, sizeof(u));
|
|
FatQueryEncodedTime(&u.Timestamp);
|
|
u.Flags = FAT32_DIRENT_UPDATE_SIZE | FAT32_DIRENT_UPDATE_WRITE_TIME;
|
|
u.Size = size;
|
|
return DirentUpdateMetadata(v, lba, offset, &u);
|
|
}
|
|
|
|
static KSTATUS DirentUpdateFirstCluster(FAT32_VOL *v, uint64_t lba,
|
|
uint32_t offset, uint32_t cluster) {
|
|
FAT32_DIRENT_UPDATE u;
|
|
memset(&u, 0, sizeof(u));
|
|
u.Flags = FAT32_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(FAT32_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 cluster = dirCluster;
|
|
while (cluster >= 2 && cluster < FAT32_EOC) {
|
|
uint64_t clusterLba = ClusterToLba(v, cluster);
|
|
if (!KSUCCESS(ReadSectors(v, clusterLba, v->SectorsPerCluster, clusterBuf))) break;
|
|
|
|
uint32_t entries = v->BytesPerCluster / sizeof(DIRENT);
|
|
DIRENT *dir = (DIRENT *)clusterBuf;
|
|
for (uint32_t i = 0; i < entries; i++) {
|
|
if (dir[i].Name[0] == 0x00) { 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;
|
|
}
|
|
}
|
|
cluster = FatEntry(v, cluster);
|
|
}
|
|
|
|
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(FAT32_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(FAT32_VOL *v, uint32_t dirCluster,
|
|
SYS_STRING_VIEW longName) {
|
|
if (!SysSvIsValid(longName) || longName.Length < 2 || longName.Data[0] != '.') return STATUS_SUCCESS;
|
|
|
|
char badName[8], badExt[3];
|
|
MakeLegacyBrokenDot83V(longName, badName, badExt);
|
|
|
|
uint8_t *clusterBuf = (uint8_t *)MmAllocatePool(v->BytesPerCluster);
|
|
if (!clusterBuf) return STATUS_NO_MEMORY;
|
|
|
|
uint32_t cluster = dirCluster;
|
|
while (cluster >= 2 && cluster < FAT32_EOC) {
|
|
uint64_t clusterLba = ClusterToLba(v, cluster);
|
|
KSTATUS s = ReadSectors(v, clusterLba, v->SectorsPerCluster, clusterBuf);
|
|
if (!KSUCCESS(s)) { MmFreePool(clusterBuf); return s; }
|
|
|
|
uint32_t entries = v->BytesPerCluster / sizeof(DIRENT);
|
|
DIRENT *dir = (DIRENT *)clusterBuf;
|
|
for (uint32_t i = 0; i < entries; i++) {
|
|
if (dir[i].Name[0] == 0x00) { MmFreePool(clusterBuf); return STATUS_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, clusterLba, v->SectorsPerCluster, clusterBuf);
|
|
MmFreePool(clusterBuf);
|
|
return s;
|
|
}
|
|
}
|
|
cluster = FatEntry(v, cluster);
|
|
}
|
|
|
|
MmFreePool(clusterBuf);
|
|
return STATUS_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 Fat32IsPathSep(char c) {
|
|
return c == '\\' || c == '/';
|
|
}
|
|
|
|
// Implements the name View Is Valid operation.
|
|
static int Fat32NameViewIsValid(SYS_STRING_VIEW name) {
|
|
if (!SysSvIsValid(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' || Fat32IsPathSep(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 Fat32PathCacheChar(char c) {
|
|
if (c == '/') return '\\';
|
|
if (c >= 'A' && c <= 'Z') return (char)(c + 32);
|
|
return c;
|
|
}
|
|
|
|
// Hashes a drive-relative path using the same case/separator rules as lookup.
|
|
static uint32_t Fat32PathCacheHash(SYS_STRING_VIEW path) {
|
|
uint32_t h = 2166136261u;
|
|
for (size_t i = 0; i < path.Length; i++) {
|
|
h ^= (uint8_t)Fat32PathCacheChar(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 Fat32PathCachePathEquals(const FAT32_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 (Fat32PathCacheChar(e->Path[i]) != Fat32PathCacheChar(path.Data[i]))
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
// Adds directory search counters without holding the lock during disk I/O.
|
|
static void Fat32PathCacheAddDirectoryStats(FAT32_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 drive-relative path.
|
|
static bool Fat32PathCacheLookup(FAT32_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 >= FAT32_PATH_CACHE_MAX)
|
|
return false;
|
|
|
|
uint32_t hash = Fat32PathCacheHash(path);
|
|
uint32_t slot = hash % FAT32_PATH_CACHE_SLOTS;
|
|
bool hit = false;
|
|
|
|
uintptr_t flags = KeAcquireSpinlock(&v->PathCacheLock);
|
|
v->Stats.PathLookups++;
|
|
FAT32_PATH_CACHE_ENTRY* e = &v->PathCache[slot];
|
|
if (e->Valid && e->Generation == v->PathCacheGeneration &&
|
|
e->Hash == hash && Fat32PathCachePathEquals(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 Fat32PathCacheStore(FAT32_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 >= FAT32_PATH_CACHE_MAX)
|
|
return;
|
|
|
|
uint32_t hash = Fat32PathCacheHash(path);
|
|
uint32_t slot = hash % FAT32_PATH_CACHE_SLOTS;
|
|
|
|
uintptr_t flags = KeAcquireSpinlock(&v->PathCacheLock);
|
|
FAT32_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 Fat32PathCacheInvalidate(FAT32_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 < FAT32_PATH_CACHE_SLOTS; i++)
|
|
v->PathCache[i].Valid = false;
|
|
KeReleaseSpinlock(&v->PathCacheLock, flags);
|
|
}
|
|
|
|
// Returns the current filesystem cache and traversal counters.
|
|
void Fat32QueryStats(IO_FILESYSTEM* fs, IO_FILESYSTEM_STATS* out) {
|
|
if (!fs || !out) return;
|
|
FAT32_VOL* v = (FAT32_VOL*)fs->DriverCtx;
|
|
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 Fat32StatsAddReadRun(FAT32_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 Fat32FileInitChainCache(FAT32_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 Fat32FileInvalidateChainCache(FAT32_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 Fat32ChainCacheLookupStart(FAT32_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 < FAT32_CHAIN_CACHE_SLOTS; i++) {
|
|
FAT32_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 && e->NextCluster >= 2 && e->NextCluster < FAT32_EOC) {
|
|
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 Fat32ChainCacheStore(FAT32_FILE* f, uint32_t logicalStart,
|
|
uint32_t physicalStart, uint32_t length,
|
|
uint32_t nextCluster) {
|
|
if (!f || physicalStart < 2 || physicalStart >= FAT32_EOC || length == 0)
|
|
return;
|
|
|
|
uintptr_t flags = KeAcquireSpinlock(&f->ChainCacheLock);
|
|
uint32_t slot = f->ChainCacheClock++ % FAT32_CHAIN_CACHE_SLOTS;
|
|
FAT32_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 Fat32ResolveFileCluster(FAT32_FILE* f, uint32_t logicalIndex) {
|
|
if (!f || f->StartCluster < 2 || f->StartCluster >= FAT32_EOC)
|
|
return 0;
|
|
if (logicalIndex == 0)
|
|
return f->StartCluster;
|
|
|
|
uint32_t currentLogical = 0;
|
|
uint32_t cluster = f->StartCluster;
|
|
if (!Fat32ChainCacheLookupStart(f, logicalIndex, ¤tLogical, &cluster)) {
|
|
currentLogical = 0;
|
|
cluster = f->StartCluster;
|
|
}
|
|
|
|
while (currentLogical < logicalIndex && cluster >= 2 && cluster < FAT32_EOC) {
|
|
cluster = FatEntry(f->Vol, cluster);
|
|
currentLogical++;
|
|
}
|
|
return cluster;
|
|
}
|
|
|
|
// Builds and caches one physically contiguous read run from a starting cluster.
|
|
static bool Fat32BuildReadRun(FAT32_FILE* f, uint32_t logicalIndex,
|
|
uint32_t startCluster, uint32_t maxSectors,
|
|
uint32_t* outRunSectors, uint32_t* outNextCluster) {
|
|
if (!f || !outRunSectors || !outNextCluster)
|
|
return false;
|
|
FAT32_VOL* v = f->Vol;
|
|
if (!v || startCluster < 2 || startCluster >= FAT32_EOC)
|
|
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 && next < FAT32_EOC &&
|
|
runSectors + v->SectorsPerCluster <= maxSectors) {
|
|
last = next;
|
|
runClusters++;
|
|
runSectors += v->SectorsPerCluster;
|
|
next = FatEntry(v, last);
|
|
}
|
|
|
|
Fat32ChainCacheStore(f, logicalIndex, startCluster, runClusters, next);
|
|
*outRunSectors = runSectors;
|
|
*outNextCluster = next;
|
|
return true;
|
|
}
|
|
|
|
static int SearchDirV(FAT32_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 (!Fat32NameViewIsValid(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);
|
|
uint32_t cluster = dirCluster;
|
|
uint64_t clustersRead = 0;
|
|
uint64_t entriesScanned = 0;
|
|
int found = 0;
|
|
|
|
while (cluster >= 2 && cluster < FAT32_EOC) {
|
|
uint64_t clusterLba = ClusterToLba(v, cluster);
|
|
if (!KSUCCESS(ReadSectors(v, clusterLba, v->SectorsPerCluster, clusterBuf))) break;
|
|
clustersRead++;
|
|
|
|
uint32_t entries = v->BytesPerCluster / 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 = ((uint32_t)dir[i].FirstClusterHi << 16) | dir[i].FirstClusterLo;
|
|
*outSize = dir[i].FileSize;
|
|
*outAttr = dir[i].Attr;
|
|
if (outDirentLba || outDirentOffset) {
|
|
uint64_t eb = (uint64_t)i * sizeof(DIRENT);
|
|
if (outDirentLba) *outDirentLba = clusterLba + eb / v->BytesPerSector;
|
|
if (outDirentOffset) *outDirentOffset = (uint32_t)(eb % v->BytesPerSector);
|
|
}
|
|
found = 1;
|
|
goto done;
|
|
}
|
|
}
|
|
cluster = FatEntry(v, cluster);
|
|
}
|
|
done:
|
|
Fat32PathCacheAddDirectoryStats(v, clustersRead, entriesScanned);
|
|
MmFreePool(clusterBuf);
|
|
return found;
|
|
}
|
|
|
|
static int Fat32PathNextComponent(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 && Fat32IsPathSep(path.Data[pos])) pos++;
|
|
if (pos >= path.Length) {
|
|
*cursor = pos;
|
|
*component = SYS_SV_NULL;
|
|
return 0;
|
|
}
|
|
|
|
size_t start = pos;
|
|
while (pos < path.Length && !Fat32IsPathSep(path.Data[pos])) pos++;
|
|
*cursor = pos;
|
|
*component = SysSvFromParts(path.Data + start, pos - start);
|
|
return 1;
|
|
}
|
|
|
|
static KSTATUS Fat32LookupPathV(FAT32_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 || !SysSvIsValid(path))
|
|
return STATUS_INVALID_PARAMETER;
|
|
if (SysSvContainsByte(path, '\0'))
|
|
return STATUS_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 (Fat32PathCacheLookup(v, path, outCluster, outSize, &attr,
|
|
outDirentLba, outDirentOffset)) {
|
|
if (outAttr) *outAttr = attr;
|
|
return STATUS_SUCCESS;
|
|
}
|
|
|
|
size_t cursor = 0;
|
|
SYS_STRING_VIEW component;
|
|
while (Fat32PathNextComponent(path, &cursor, &component)) {
|
|
if (component.Length >= MAX_LFN) return STATUS_BUFFER_TOO_SMALL;
|
|
if (!Fat32NameViewIsValid(component)) return STATUS_INVALID_PARAMETER;
|
|
if (!(attr & ATTR_DIRECTORY)) return STATUS_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 STATUS_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;
|
|
Fat32PathCacheStore(v, path, cluster, size, attr,
|
|
finalDirentLba, finalDirentOffset);
|
|
return STATUS_SUCCESS;
|
|
}
|
|
|
|
static KSTATUS TraverseV(FAT32_VOL *v, SYS_STRING_VIEW path,
|
|
uint32_t *outCluster, uint32_t *outSize) {
|
|
return Fat32LookupPathV(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 STATUS_INVALID_PARAMETER;
|
|
*parent = SYS_SV_NULL;
|
|
*name = SYS_SV_NULL;
|
|
|
|
while (path.Length && Fat32IsPathSep(path.Data[path.Length - 1]))
|
|
path.Length--;
|
|
if (path.Length == 0) return STATUS_INVALID_PARAMETER;
|
|
|
|
if (SysSvContainsByte(path, '\0')) return STATUS_INVALID_PARAMETER;
|
|
|
|
size_t lastSep = (size_t)-1;
|
|
for (size_t i = 0; i < path.Length; i++) {
|
|
if (Fat32IsPathSep(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 (!Fat32NameViewIsValid(*name)) return STATUS_INVALID_PARAMETER;
|
|
return STATUS_SUCCESS;
|
|
}
|
|
|
|
// Find a free DIRENT slot.
|
|
static int FindFreeDirent(FAT32_VOL *v, uint32_t dirCluster,
|
|
uint64_t *outLba, uint32_t *outOffset) {
|
|
uint8_t *clusterBuf = (uint8_t *)MmAllocatePool(v->BytesPerCluster);
|
|
if (!clusterBuf) return 0;
|
|
|
|
uint32_t cluster = dirCluster, prevCluster = dirCluster;
|
|
while (cluster >= 2 && cluster < FAT32_EOC) {
|
|
uint64_t clusterLba = ClusterToLba(v, cluster);
|
|
if (!KSUCCESS(ReadSectors(v, clusterLba, v->SectorsPerCluster, clusterBuf))) {
|
|
MmFreePool(clusterBuf); return 0;
|
|
}
|
|
uint32_t entries = v->BytesPerCluster / sizeof(DIRENT);
|
|
DIRENT *dir = (DIRENT *)clusterBuf;
|
|
for (uint32_t i = 0; i < entries; i++) {
|
|
uint8_t first = (uint8_t)dir[i].Name[0];
|
|
if (first == 0xE5 || first == 0x00) {
|
|
uint64_t eb = (uint64_t)i * sizeof(DIRENT);
|
|
*outLba = clusterLba + eb / v->BytesPerSector;
|
|
*outOffset = (uint32_t)(eb % v->BytesPerSector);
|
|
MmFreePool(clusterBuf); return 1;
|
|
}
|
|
}
|
|
prevCluster = cluster;
|
|
cluster = FatEntry(v, cluster);
|
|
}
|
|
|
|
uint32_t newCluster = AppendCluster(v, prevCluster);
|
|
if (newCluster) {
|
|
*outLba = ClusterToLba(v, newCluster); *outOffset = 0;
|
|
MmFreePool(clusterBuf); return 1;
|
|
}
|
|
MmFreePool(clusterBuf);
|
|
return 0;
|
|
}
|
|
|
|
// Find a contiguous run of free directory entries within one directory cluster.
|
|
// This is required for VFAT LFN creation because N long-name entries must
|
|
// immediately precede the final short entry.
|
|
static int FindFreeDirentRun(FAT32_VOL *v, uint32_t dirCluster, uint32_t needed,
|
|
uint32_t *outCluster, uint32_t *outFirstIndex) {
|
|
if (!needed) return 0;
|
|
|
|
uint8_t *clusterBuf = (uint8_t *)MmAllocatePool(v->BytesPerCluster);
|
|
if (!clusterBuf) return 0;
|
|
|
|
uint32_t cluster = dirCluster, prevCluster = dirCluster;
|
|
while (cluster >= 2 && cluster < FAT32_EOC) {
|
|
uint64_t clusterLba = ClusterToLba(v, cluster);
|
|
if (!KSUCCESS(ReadSectors(v, clusterLba, v->SectorsPerCluster, clusterBuf))) {
|
|
MmFreePool(clusterBuf);
|
|
return 0;
|
|
}
|
|
|
|
uint32_t entries = v->BytesPerCluster / sizeof(DIRENT);
|
|
DIRENT *dir = (DIRENT *)clusterBuf;
|
|
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) {
|
|
*outCluster = cluster;
|
|
*outFirstIndex = runStart;
|
|
MmFreePool(clusterBuf);
|
|
return 1;
|
|
}
|
|
} else {
|
|
runLen = 0;
|
|
}
|
|
}
|
|
|
|
prevCluster = cluster;
|
|
cluster = FatEntry(v, cluster);
|
|
}
|
|
|
|
uint32_t newCluster = AppendCluster(v, prevCluster);
|
|
if (newCluster) {
|
|
*outCluster = newCluster;
|
|
*outFirstIndex = 0;
|
|
MmFreePool(clusterBuf);
|
|
return 1;
|
|
}
|
|
|
|
MmFreePool(clusterBuf);
|
|
return 0;
|
|
}
|
|
|
|
// Finds the parent directory and final component for a create or delete path.
|
|
static KSTATUS Fat32ResolveParentForMutation(FAT32_VOL *v, SYS_STRING_VIEW path,
|
|
uint32_t *parentClusterOut,
|
|
SYS_STRING_VIEW *filenameOut) {
|
|
if (!v || !parentClusterOut || !filenameOut)
|
|
return STATUS_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 = Fat32LookupPathV(v, parentPath, &parentCluster, &parentSize,
|
|
&parentAttr, NULL, NULL);
|
|
if (!KSUCCESS(s)) return s;
|
|
if (!(parentAttr & ATTR_DIRECTORY)) return STATUS_NOT_FOUND;
|
|
|
|
*parentClusterOut = parentCluster;
|
|
*filenameOut = filenameView;
|
|
return STATUS_SUCCESS;
|
|
}
|
|
|
|
// Creates a FAT directory entry run for a new file or directory.
|
|
static KSTATUS Fat32CreateDirent(FAT32_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 STATUS_INVALID_PARAMETER;
|
|
|
|
uint32_t parentCluster = 0;
|
|
SYS_STRING_VIEW filenameView;
|
|
KSTATUS s = Fat32ResolveParentForMutation(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 STATUS_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 STATUS_OBJECT_NAME_COLLISION;
|
|
} else {
|
|
Make83NameV(filenameView, shortName, shortExt);
|
|
}
|
|
|
|
size_t nameLen = filenameView.Length;
|
|
if (nameLen == 0 || nameLen >= MAX_LFN) return STATUS_INVALID_PARAMETER;
|
|
uint32_t lfnCount = useLfn ? (uint32_t)((nameLen + 12u) / 13u) : 0;
|
|
uint32_t neededEntries = lfnCount + 1u;
|
|
|
|
uint32_t entryCluster, firstIndex;
|
|
if (!FindFreeDirentRun(v, parentCluster, neededEntries, &entryCluster, &firstIndex))
|
|
return STATUS_NO_MEMORY;
|
|
|
|
uint8_t *clusterBuf = (uint8_t *)MmAllocatePool(v->BytesPerCluster);
|
|
if (!clusterBuf) return STATUS_NO_MEMORY;
|
|
|
|
uint64_t clusterLba = ClusterToLba(v, entryCluster);
|
|
s = ReadSectors(v, clusterLba, v->SectorsPerCluster, 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);
|
|
d->FirstClusterHi = (uint16_t)(firstCluster >> 16);
|
|
d->FirstClusterLo = (uint16_t)(firstCluster & 0xFFFFu);
|
|
d->FileSize = fileSize;
|
|
{
|
|
FAT32_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, v->SectorsPerCluster, 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;
|
|
|
|
Fat32PathCacheInvalidate(v);
|
|
return STATUS_SUCCESS;
|
|
}
|
|
|
|
// Initializes one dot entry in a new directory cluster.
|
|
static void Fat32InitDotEntry(DIRENT *d, const char *name,
|
|
uint32_t firstCluster,
|
|
const FAT32_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;
|
|
d->FirstClusterHi = (uint16_t)(firstCluster >> 16);
|
|
d->FirstClusterLo = (uint16_t)(firstCluster & 0xFFFFu);
|
|
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 Fat32InitializeDirectoryCluster(FAT32_VOL *v, uint32_t dirCluster,
|
|
uint32_t parentCluster) {
|
|
if (!v || dirCluster < 2)
|
|
return STATUS_INVALID_PARAMETER;
|
|
|
|
uint8_t *clusterBuf = (uint8_t *)MmAllocatePool(v->BytesPerCluster);
|
|
if (!clusterBuf) return STATUS_NO_MEMORY;
|
|
memset(clusterBuf, 0, v->BytesPerCluster);
|
|
|
|
FAT32_ENCODED_TIME now;
|
|
FatQueryEncodedTime(&now);
|
|
DIRENT *dir = (DIRENT *)clusterBuf;
|
|
Fat32InitDotEntry(&dir[0], ".", dirCluster, &now);
|
|
Fat32InitDotEntry(&dir[1], "..", 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 Fat32IsDotEntry(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 Fat32DirectoryIsEmpty(FAT32_VOL *v, uint32_t dirCluster,
|
|
int *emptyOut) {
|
|
if (!v || !emptyOut || dirCluster < 2)
|
|
return STATUS_INVALID_PARAMETER;
|
|
|
|
uint8_t *clusterBuf = (uint8_t *)MmAllocatePool(v->BytesPerCluster);
|
|
if (!clusterBuf) return STATUS_NO_MEMORY;
|
|
|
|
uint32_t cluster = dirCluster;
|
|
while (cluster >= 2 && cluster < FAT32_EOC) {
|
|
KSTATUS s = ReadSectors(v, ClusterToLba(v, cluster),
|
|
v->SectorsPerCluster, clusterBuf);
|
|
if (!KSUCCESS(s)) { MmFreePool(clusterBuf); return s; }
|
|
|
|
uint32_t entries = v->BytesPerCluster / sizeof(DIRENT);
|
|
DIRENT *dir = (DIRENT *)clusterBuf;
|
|
for (uint32_t i = 0; i < entries; i++) {
|
|
uint8_t first = (uint8_t)dir[i].Name[0];
|
|
if (first == 0x00) { MmFreePool(clusterBuf); *emptyOut = 1; return STATUS_SUCCESS; }
|
|
if (first == 0xE5 || dir[i].Attr == ATTR_LFN) continue;
|
|
if ((dir[i].Attr & ATTR_VOLUME_ID) && !(dir[i].Attr & ATTR_DIRECTORY)) continue;
|
|
if (Fat32IsDotEntry(&dir[i])) continue;
|
|
|
|
MmFreePool(clusterBuf);
|
|
*emptyOut = 0;
|
|
return STATUS_SUCCESS;
|
|
}
|
|
|
|
cluster = FatEntry(v, cluster);
|
|
}
|
|
|
|
MmFreePool(clusterBuf);
|
|
*emptyOut = 1;
|
|
return STATUS_SUCCESS;
|
|
}
|
|
|
|
// Deletes the directory-entry run for one child name from a parent directory.
|
|
static KSTATUS Fat32DeleteDirentRunV(FAT32_VOL *v, uint32_t parentCluster,
|
|
SYS_STRING_VIEW name) {
|
|
if (!v || !Fat32NameViewIsValid(name))
|
|
return STATUS_INVALID_PARAMETER;
|
|
|
|
uint8_t *clusterBuf = (uint8_t *)MmAllocatePool(v->BytesPerCluster);
|
|
if (!clusterBuf) return STATUS_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 cluster = parentCluster;
|
|
while (cluster >= 2 && cluster < FAT32_EOC) {
|
|
uint64_t clusterLba = ClusterToLba(v, cluster);
|
|
KSTATUS s = ReadSectors(v, clusterLba, v->SectorsPerCluster, clusterBuf);
|
|
if (!KSUCCESS(s)) { MmFreePool(clusterBuf); return s; }
|
|
|
|
uint32_t entries = v->BytesPerCluster / 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 STATUS_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, clusterLba, v->SectorsPerCluster, clusterBuf);
|
|
MmFreePool(clusterBuf);
|
|
if (KSUCCESS(s)) Fat32PathCacheInvalidate(v);
|
|
return s;
|
|
}
|
|
|
|
hasLfn = 0;
|
|
memset(lfn, 0, sizeof(lfn));
|
|
}
|
|
|
|
cluster = FatEntry(v, cluster);
|
|
}
|
|
|
|
MmFreePool(clusterBuf);
|
|
return STATUS_NOT_FOUND;
|
|
}
|
|
|
|
// Open / Close.
|
|
KSTATUS Fat32Open(IO_FILESYSTEM *fs, SYS_STRING_VIEW path, void **fileCtx) {
|
|
if (!fs || !fileCtx || !SysSvIsValid(path) || path.Length == 0)
|
|
return STATUS_INVALID_PARAMETER;
|
|
|
|
FAT32_VOL *v = (FAT32_VOL *)fs->DriverCtx;
|
|
uint32_t cluster, size; uint64_t direntLba; uint32_t direntOffset;
|
|
KSTATUS s = Fat32LookupPathV(v, path, &cluster, &size, NULL, &direntLba, &direntOffset);
|
|
if (!KSUCCESS(s)) return s;
|
|
|
|
FAT32_FILE *f = (FAT32_FILE *)MmAllocatePool(sizeof(FAT32_FILE));
|
|
if (!f) return STATUS_NO_MEMORY;
|
|
f->StartCluster = cluster; f->FileSize = size; f->Vol = v;
|
|
f->DirentLba = direntLba; f->DirentOffset = direntOffset;
|
|
Fat32FileInitChainCache(f);
|
|
*fileCtx = f;
|
|
return STATUS_SUCCESS;
|
|
}
|
|
|
|
// Releases the requested object.
|
|
void Fat32Close(IO_FILESYSTEM *fs, void *fileCtx) {
|
|
(void)fs;
|
|
FAT32_FILE *f = (FAT32_FILE *)fileCtx;
|
|
FlushFatCache(f->Vol);
|
|
MmFreePool(fileCtx);
|
|
}
|
|
|
|
// Read (fix #4: contiguous-run coalescing)
|
|
// 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 Fat32Read(IO_FILESYSTEM *fs, void *fileCtx,
|
|
uint64_t offset, void *buf,
|
|
size_t size, size_t *bytesRead) {
|
|
(void)fs;
|
|
if (!bytesRead) return STATUS_INVALID_PARAMETER;
|
|
*bytesRead = 0;
|
|
if (!fileCtx || !buf) return STATUS_INVALID_PARAMETER;
|
|
|
|
FAT32_FILE *f = (FAT32_FILE *)fileCtx;
|
|
FAT32_VOL *v = f->Vol;
|
|
if (offset >= f->FileSize || size == 0) return STATUS_SUCCESS;
|
|
if (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 > FAT32_READ_RUN_MAX_SECTORS)
|
|
maxUsefulSectors = FAT32_READ_RUN_MAX_SECTORS;
|
|
|
|
uint8_t *runBuf = (uint8_t *)MmAllocatePool((size_t)maxUsefulSectors * 512u);
|
|
if (!runBuf) return STATUS_NO_MEMORY;
|
|
|
|
size_t totalRead = 0;
|
|
uint8_t *out = (uint8_t *)buf;
|
|
KSTATUS readStatus = STATUS_SUCCESS;
|
|
uint32_t logicalCluster = (uint32_t)(offset / v->BytesPerCluster);
|
|
uint32_t offsetInCluster = (uint32_t)(offset % v->BytesPerCluster);
|
|
uint32_t cluster = Fat32ResolveFileCluster(f, logicalCluster);
|
|
|
|
while (size > 0 && cluster >= 2 && cluster < FAT32_EOC) {
|
|
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 > FAT32_READ_RUN_MAX_SECTORS)
|
|
usefulSectors = FAT32_READ_RUN_MAX_SECTORS;
|
|
if (usefulSectors > maxUsefulSectors) usefulSectors = maxUsefulSectors;
|
|
|
|
uint32_t runSectors = 0;
|
|
uint32_t next = 0;
|
|
if (!Fat32BuildReadRun(f, logicalCluster, cluster, usefulSectors,
|
|
&runSectors, &next)) {
|
|
readStatus = STATUS_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;
|
|
Fat32StatsAddReadRun(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) ? STATUS_DEVICE_ERROR : readStatus;
|
|
return STATUS_SUCCESS;
|
|
}
|
|
|
|
// Write (fix #4: contiguous-run coalescing)
|
|
// Same strategy as read. Whole-cluster-aligned runs skip the read-modify-
|
|
// write; partial head/tail clusters still require it.
|
|
|
|
KSTATUS Fat32Write(IO_FILESYSTEM *fs, void *fileCtx,
|
|
uint64_t offset, const void *buf,
|
|
size_t size, size_t *bytesWritten) {
|
|
(void)fs;
|
|
if (!size) { *bytesWritten = 0; return STATUS_SUCCESS; }
|
|
|
|
FAT32_FILE *f = (FAT32_FILE *)fileCtx;
|
|
FAT32_VOL *v = f->Vol;
|
|
const uint8_t *src = (const uint8_t *)buf;
|
|
Fat32FileInvalidateChainCache(f);
|
|
|
|
// Ensure at least one cluster exists.
|
|
if (f->StartCluster < 2) {
|
|
uint32_t first = AllocCluster(v);
|
|
if (!first) { *bytesWritten = 0; return STATUS_NO_MEMORY; }
|
|
f->StartCluster = first;
|
|
DirentUpdateFirstCluster(v, f->DirentLba, f->DirentOffset, first);
|
|
}
|
|
|
|
// Walk to the starting cluster, allocating as needed.
|
|
uint32_t clusterIndex = (uint32_t)(offset / v->BytesPerCluster);
|
|
uint32_t offsetInCluster = (uint32_t)(offset % v->BytesPerCluster);
|
|
uint32_t cluster = f->StartCluster;
|
|
|
|
for (uint32_t i = 0; i < clusterIndex; i++) {
|
|
uint32_t next = FatEntry(v, cluster);
|
|
if (next < 2 || next >= FAT32_EOC) {
|
|
next = AppendCluster(v, cluster);
|
|
if (!next) { *bytesWritten = 0; return STATUS_NO_MEMORY; }
|
|
}
|
|
cluster = next;
|
|
}
|
|
|
|
uint8_t *runBuf = (uint8_t *)MmAllocatePool(FAT32_WRITE_RUN_MAX_SECTORS * 512u);
|
|
if (!runBuf) { *bytesWritten = 0; return STATUS_NO_MEMORY; }
|
|
|
|
size_t written = 0, remaining = size;
|
|
|
|
while (remaining > 0 && cluster >= 2) {
|
|
// Build a contiguous run (only when cluster-aligned to avoid a
|
|
// partial read-modify-write spanning multiple clusters).
|
|
uint32_t runStart = cluster;
|
|
uint32_t runSectors = v->SectorsPerCluster;
|
|
uint32_t next = FatEntry(v, cluster);
|
|
|
|
if (offsetInCluster == 0) {
|
|
while (next == cluster + 1 && next < FAT32_EOC
|
|
&& runSectors + v->SectorsPerCluster <= FAT32_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 (cluster < 2 || cluster >= FAT32_EOC) {
|
|
// Find the actual last cluster in the run we just wrote.
|
|
uint32_t last = runStart;
|
|
for (uint32_t tmp = FatEntry(v, last);
|
|
tmp >= 2 && tmp < FAT32_EOC;
|
|
tmp = FatEntry(v, last))
|
|
last = tmp;
|
|
cluster = AppendCluster(v, last);
|
|
if (!cluster) break;
|
|
}
|
|
}
|
|
}
|
|
|
|
MmFreePool(runBuf);
|
|
|
|
uint64_t newEnd = offset + written;
|
|
if (written > 0) {
|
|
FAT32_DIRENT_UPDATE u;
|
|
memset(&u, 0, sizeof(u));
|
|
FatQueryEncodedTime(&u.Timestamp);
|
|
u.Flags = FAT32_DIRENT_UPDATE_WRITE_TIME;
|
|
if (newEnd > f->FileSize) {
|
|
f->FileSize = (uint32_t)newEnd;
|
|
u.Flags |= FAT32_DIRENT_UPDATE_SIZE;
|
|
u.Size = f->FileSize;
|
|
}
|
|
KSTATUS ms = DirentUpdateMetadata(v, f->DirentLba, f->DirentOffset, &u);
|
|
if (KSUCCESS(ms))
|
|
Fat32PathCacheInvalidate(v);
|
|
}
|
|
|
|
*bytesWritten = written;
|
|
return (written == size) ? STATUS_SUCCESS : STATUS_DEVICE_ERROR;
|
|
}
|
|
|
|
// Create.
|
|
// Creates the requested file and fails if the name already exists.
|
|
KSTATUS Fat32Create(IO_FILESYSTEM *fs, SYS_STRING_VIEW path, void **fileCtx) {
|
|
if (!fs || !fileCtx || !SysSvIsValid(path) || path.Length == 0)
|
|
return STATUS_INVALID_PARAMETER;
|
|
|
|
FAT32_VOL *v = (FAT32_VOL *)fs->DriverCtx;
|
|
uint64_t direntLba = 0;
|
|
uint32_t direntOffset = 0;
|
|
KSTATUS s = Fat32CreateDirent(v, path, ATTR_ARCHIVE, 0, 0,
|
|
&direntLba, &direntOffset);
|
|
if (!KSUCCESS(s)) return s;
|
|
|
|
FAT32_FILE *f = (FAT32_FILE *)MmAllocatePool(sizeof(FAT32_FILE));
|
|
if (!f) return STATUS_NO_MEMORY;
|
|
f->StartCluster = 0;
|
|
f->FileSize = 0;
|
|
f->Vol = v;
|
|
f->DirentLba = direntLba;
|
|
f->DirentOffset = direntOffset;
|
|
Fat32FileInitChainCache(f);
|
|
*fileCtx = f;
|
|
return STATUS_SUCCESS;
|
|
}
|
|
|
|
// Creates a directory and initializes its dot entries.
|
|
KSTATUS Fat32CreateDir(IO_FILESYSTEM *fs, SYS_STRING_VIEW path) {
|
|
if (!fs || !SysSvIsValid(path) || path.Length == 0)
|
|
return STATUS_INVALID_PARAMETER;
|
|
|
|
FAT32_VOL *v = (FAT32_VOL *)fs->DriverCtx;
|
|
uint32_t parentCluster = 0;
|
|
SYS_STRING_VIEW filenameView;
|
|
KSTATUS s = Fat32ResolveParentForMutation(v, path, &parentCluster, &filenameView);
|
|
if (!KSUCCESS(s)) return s;
|
|
(void)filenameView;
|
|
|
|
uint32_t dirCluster = AllocCluster(v);
|
|
if (!dirCluster) return STATUS_NO_MEMORY;
|
|
|
|
s = Fat32InitializeDirectoryCluster(v, dirCluster, parentCluster);
|
|
if (!KSUCCESS(s)) {
|
|
FreeClusterChain(v, dirCluster);
|
|
FlushFatCache(v);
|
|
return s;
|
|
}
|
|
|
|
s = Fat32CreateDirent(v, path, ATTR_DIRECTORY, dirCluster, 0, NULL, NULL);
|
|
if (!KSUCCESS(s)) {
|
|
FreeClusterChain(v, dirCluster);
|
|
FlushFatCache(v);
|
|
return s;
|
|
}
|
|
|
|
FlushFatCache(v);
|
|
return STATUS_SUCCESS;
|
|
}
|
|
|
|
// Removes an empty directory.
|
|
KSTATUS Fat32RemoveDir(IO_FILESYSTEM *fs, SYS_STRING_VIEW path) {
|
|
if (!fs || !SysSvIsValid(path) || path.Length == 0)
|
|
return STATUS_INVALID_PARAMETER;
|
|
|
|
FAT32_VOL *v = (FAT32_VOL *)fs->DriverCtx;
|
|
uint32_t cluster = 0;
|
|
uint32_t size = 0;
|
|
uint8_t attr = 0;
|
|
uint64_t direntLba = 0;
|
|
uint32_t direntOffset = 0;
|
|
KSTATUS s = Fat32LookupPathV(v, path, &cluster, &size, &attr,
|
|
&direntLba, &direntOffset);
|
|
if (!KSUCCESS(s)) return s;
|
|
if (!direntLba) return STATUS_ACCESS_DENIED;
|
|
if (!(attr & ATTR_DIRECTORY)) return STATUS_ACCESS_DENIED;
|
|
|
|
int empty = 0;
|
|
s = Fat32DirectoryIsEmpty(v, cluster, &empty);
|
|
if (!KSUCCESS(s)) return s;
|
|
if (!empty) return STATUS_ACCESS_DENIED;
|
|
|
|
uint32_t parentCluster = 0;
|
|
SYS_STRING_VIEW filenameView;
|
|
s = Fat32ResolveParentForMutation(v, path, &parentCluster, &filenameView);
|
|
if (!KSUCCESS(s)) return s;
|
|
|
|
s = Fat32DeleteDirentRunV(v, parentCluster, filenameView);
|
|
if (!KSUCCESS(s)) return s;
|
|
|
|
if (cluster >= 2) FreeClusterChain(v, cluster);
|
|
FlushFatCache(v);
|
|
Fat32PathCacheInvalidate(v);
|
|
return STATUS_SUCCESS;
|
|
}
|
|
|
|
// Truncate.
|
|
KSTATUS Fat32Truncate(IO_FILESYSTEM *fs, void *fileCtx, uint64_t newSize, uint32_t fileFlags) {
|
|
(void)fs;
|
|
FAT32_FILE *f = (FAT32_FILE *)fileCtx;
|
|
FAT32_VOL *v = f->Vol;
|
|
Fat32FileInvalidateChainCache(f);
|
|
int zeroNewClusters = (fileFlags & IO_FILE_FLAG_SKIP_ZERO_EXTEND) ? 0 : 1;
|
|
if ((uint32_t)newSize == f->FileSize) {
|
|
FAT32_DIRENT_UPDATE u;
|
|
memset(&u, 0, sizeof(u));
|
|
FatQueryEncodedTime(&u.Timestamp);
|
|
u.Flags = FAT32_DIRENT_UPDATE_WRITE_TIME;
|
|
KSTATUS s = DirentUpdateMetadata(v, f->DirentLba, f->DirentOffset, &u);
|
|
if (KSUCCESS(s)) Fat32PathCacheInvalidate(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)) Fat32PathCacheInvalidate(v);
|
|
return s;
|
|
}
|
|
|
|
uint32_t newClusters = (uint32_t)
|
|
((newSize + v->BytesPerCluster - 1) / v->BytesPerCluster);
|
|
|
|
if (newSize < f->FileSize) {
|
|
if (f->StartCluster < 2) goto update_size;
|
|
uint32_t cluster = f->StartCluster, lastGood = cluster;
|
|
for (uint32_t i = 1; i < newClusters && cluster < FAT32_EOC; i++) {
|
|
lastGood = cluster; cluster = FatEntry(v, cluster);
|
|
}
|
|
uint32_t tail = FatEntry(v, lastGood);
|
|
WriteFatEntry(v, lastGood, FAT32_EOC_MARK);
|
|
FreeClusterChain(v, tail);
|
|
} else {
|
|
uint32_t curClusters = f->FileSize
|
|
? (f->FileSize + v->BytesPerCluster - 1) / v->BytesPerCluster : 0;
|
|
uint32_t lastCluster = 0;
|
|
if (f->StartCluster < 2) {
|
|
uint32_t first = AllocClusterEx(v, zeroNewClusters);
|
|
if (!first) return STATUS_NO_MEMORY;
|
|
f->StartCluster = first;
|
|
DirentUpdateFirstCluster(v, f->DirentLba, f->DirentOffset, first);
|
|
lastCluster = first; curClusters = 1;
|
|
} else {
|
|
lastCluster = GetChainEnd(v, f->StartCluster, NULL);
|
|
}
|
|
for (uint32_t i = curClusters; i < newClusters; i++) {
|
|
uint32_t next = AppendClusterEx(v, lastCluster, zeroNewClusters);
|
|
if (!next) return STATUS_NO_MEMORY;
|
|
lastCluster = next;
|
|
}
|
|
}
|
|
|
|
update_size:
|
|
f->FileSize = (uint32_t)newSize;
|
|
{ KSTATUS s = DirentUpdateSize(v, f->DirentLba, f->DirentOffset, (uint32_t)newSize);
|
|
FlushFatCache(v);
|
|
if (KSUCCESS(s)) Fat32PathCacheInvalidate(v);
|
|
return s; }
|
|
}
|
|
|
|
// Delete.
|
|
KSTATUS Fat32Delete(IO_FILESYSTEM *fs, SYS_STRING_VIEW path) {
|
|
if (!fs || !SysSvIsValid(path) || path.Length == 0)
|
|
return STATUS_INVALID_PARAMETER;
|
|
|
|
FAT32_VOL *v = (FAT32_VOL *)fs->DriverCtx;
|
|
uint32_t cluster, size; uint64_t direntLba; uint32_t direntOffset;
|
|
KSTATUS s = Fat32LookupPathV(v, path, &cluster, &size, NULL, &direntLba, &direntOffset);
|
|
if (!KSUCCESS(s)) return s;
|
|
if (!direntLba) return STATUS_ACCESS_DENIED;
|
|
|
|
uint8_t *sec = (uint8_t *)MmAllocatePool(v->BytesPerSector);
|
|
if (!sec) return STATUS_NO_MEMORY;
|
|
s = ReadSectors(v, direntLba, 1, sec);
|
|
if (KSUCCESS(s)) { sec[direntOffset] = 0xE5; s = WriteSectors(v, direntLba, 1, sec); }
|
|
MmFreePool(sec);
|
|
if (!KSUCCESS(s)) return s;
|
|
|
|
if (cluster >= 2) FreeClusterChain(v, cluster);
|
|
FlushFatCache(v);
|
|
Fat32PathCacheInvalidate(v);
|
|
return STATUS_SUCCESS;
|
|
}
|
|
|
|
// Returns size.
|
|
uint64_t Fat32GetSize(IO_FILESYSTEM *fs, void *fileCtx) {
|
|
(void)fs; return ((FAT32_FILE *)fileCtx)->FileSize;
|
|
}
|
|
|
|
// Returns a stable volume identity for metadata and cache keys.
|
|
static uint64_t Fat32VolumeIdentity(FAT32_VOL *v) {
|
|
if (!v) return 0;
|
|
return ((uint64_t)v->DriveIndex << 56) ^
|
|
((uint64_t)v->PartitionLba << 8) ^
|
|
((uint64_t)v->RootCluster << 1) ^
|
|
0x4641543332494445ULL;
|
|
}
|
|
|
|
// Converts FAT attributes to the public file type.
|
|
static uint32_t Fat32FileTypeFromAttr(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 Fat32FillStatFromDirent(FAT32_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 = Fat32FileTypeFromAttr(d->Attr);
|
|
out->Attributes = d->Attr;
|
|
out->LinkCount = 1;
|
|
out->FileSize = d->FileSize;
|
|
out->VolumeId = Fat32VolumeIdentity(v);
|
|
|
|
uint32_t firstCluster = ((uint32_t)d->FirstClusterHi << 16) | d->FirstClusterLo;
|
|
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 Fat32FillRootStat(FAT32_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->VolumeId = Fat32VolumeIdentity(v);
|
|
out->FileId = ((uint64_t)v->RootCluster << 1) ^ 0x524f4f54ULL;
|
|
}
|
|
|
|
// Queries metadata for an open FAT32 file.
|
|
KSTATUS Fat32QueryInfo(IO_FILESYSTEM *fs, void *fileCtx, SYS_FILE_STAT *out) {
|
|
(void)fs;
|
|
if (!fileCtx || !out) return STATUS_INVALID_PARAMETER;
|
|
|
|
FAT32_FILE *f = (FAT32_FILE *)fileCtx;
|
|
FAT32_VOL *v = f->Vol;
|
|
if (!v) return STATUS_INVALID_PARAMETER;
|
|
|
|
if (f->DirentLba == 0) {
|
|
if (f->StartCluster == v->RootCluster && f->FileSize == 0) {
|
|
Fat32FillRootStat(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->VolumeId = Fat32VolumeIdentity(v);
|
|
out->FileId = ((uint64_t)f->StartCluster << 1);
|
|
}
|
|
return STATUS_SUCCESS;
|
|
}
|
|
|
|
uint8_t *sec = (uint8_t *)MmAllocatePool(v->BytesPerSector);
|
|
if (!sec) return STATUS_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 STATUS_NOT_FOUND;
|
|
}
|
|
|
|
Fat32FillStatFromDirent(v, d, f->DirentLba, f->DirentOffset, out);
|
|
f->StartCluster = ((uint32_t)d->FirstClusterHi << 16) | d->FirstClusterLo;
|
|
f->FileSize = d->FileSize;
|
|
MmFreePool(sec);
|
|
return STATUS_SUCCESS;
|
|
}
|
|
|
|
// Queries metadata for a FAT32 path.
|
|
KSTATUS Fat32Stat(IO_FILESYSTEM *fs, SYS_STRING_VIEW path, SYS_FILE_STAT *out) {
|
|
if (!fs || !out || !SysSvIsValid(path)) return STATUS_INVALID_PARAMETER;
|
|
|
|
FAT32_VOL *v = (FAT32_VOL *)fs->DriverCtx;
|
|
if (!v) return STATUS_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 = Fat32LookupPathV(v, path, &cluster, &size, &attr, &direntLba, &direntOffset);
|
|
if (!KSUCCESS(s)) return s;
|
|
(void)cluster;
|
|
|
|
if (direntLba == 0) {
|
|
Fat32FillRootStat(v, out);
|
|
return STATUS_SUCCESS;
|
|
}
|
|
|
|
uint8_t *sec = (uint8_t *)MmAllocatePool(v->BytesPerSector);
|
|
if (!sec) return STATUS_NO_MEMORY;
|
|
|
|
s = ReadSectors(v, direntLba, 1, sec);
|
|
if (!KSUCCESS(s)) { MmFreePool(sec); return s; }
|
|
|
|
DIRENT *d = (DIRENT *)(sec + direntOffset);
|
|
if ((uint8_t)d->Name[0] == 0x00 || (uint8_t)d->Name[0] == 0xE5) {
|
|
MmFreePool(sec);
|
|
return STATUS_NOT_FOUND;
|
|
}
|
|
|
|
Fat32FillStatFromDirent(v, d, direntLba, direntOffset, out);
|
|
out->Type = Fat32FileTypeFromAttr(attr);
|
|
out->FileSize = size;
|
|
MmFreePool(sec);
|
|
return STATUS_SUCCESS;
|
|
}
|
|
|
|
// Mixes FAT directory metadata into a per-file content generation.
|
|
static uint64_t Fat32DirentGeneration(const DIRENT *d) {
|
|
if (!d)
|
|
return 0;
|
|
|
|
uint32_t firstCluster = ((uint32_t)d->FirstClusterHi << 16) | d->FirstClusterLo;
|
|
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 Fat32QueryIdentity(IO_FILESYSTEM *fs, void *fileCtx, IO_FILE_IDENTITY *out) {
|
|
(void)fs;
|
|
if (!fileCtx || !out)
|
|
return STATUS_INVALID_PARAMETER;
|
|
|
|
FAT32_FILE *f = (FAT32_FILE *)fileCtx;
|
|
FAT32_VOL *v = f->Vol;
|
|
if (!v)
|
|
return STATUS_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 STATUS_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 STATUS_NOT_FOUND;
|
|
}
|
|
|
|
startCluster = ((uint32_t)d->FirstClusterHi << 16) | d->FirstClusterLo;
|
|
fileSize = d->FileSize;
|
|
generation = Fat32DirentGeneration(d);
|
|
MmFreePool(sec);
|
|
}
|
|
|
|
out->VolumeId = Fat32VolumeIdentity(v);
|
|
out->FileId = f->DirentLba ^
|
|
((uint64_t)f->DirentOffset << 32) ^
|
|
((uint64_t)startCluster << 1);
|
|
out->Generation = generation ? generation : 1;
|
|
out->Size = fileSize;
|
|
return STATUS_SUCCESS;
|
|
}
|
|
|
|
// Directory enumeration (fix #2: cluster buffer cached in context)
|
|
// Previously: allocate + ReadSectors + return 1 entry + free, every call.
|
|
// Now: buffer lives in FAT32_DIR_CTX; ReadSectors fires only on cluster
|
|
// boundaries.
|
|
|
|
typedef struct {
|
|
FAT32_VOL *Vol;
|
|
uint32_t DirCluster;
|
|
uint32_t CurCluster;
|
|
uint32_t LoadedCluster; // which cluster is currently in ClusterBuf
|
|
uint32_t EntryIndex;
|
|
char LfnBuf[MAX_LFN];
|
|
int HasLfn;
|
|
uint8_t *ClusterBuf; // allocated once in OpenDir, freed in CloseDir
|
|
} FAT32_DIR_CTX;
|
|
|
|
// Opens dir.
|
|
KSTATUS Fat32OpenDir(IO_FILESYSTEM *fs, SYS_STRING_VIEW dirPath, void **dirCtx) {
|
|
if (!fs || !dirCtx || !SysSvIsValid(dirPath))
|
|
return STATUS_INVALID_PARAMETER;
|
|
|
|
FAT32_VOL *v = (FAT32_VOL *)fs->DriverCtx;
|
|
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;
|
|
}
|
|
|
|
FAT32_DIR_CTX *ctx = (FAT32_DIR_CTX *)MmAllocatePool(sizeof(FAT32_DIR_CTX));
|
|
if (!ctx) return STATUS_NO_MEMORY;
|
|
|
|
ctx->ClusterBuf = (uint8_t *)MmAllocatePool(v->BytesPerCluster);
|
|
if (!ctx->ClusterBuf) { MmFreePool(ctx); return STATUS_NO_MEMORY; }
|
|
|
|
ctx->Vol = v;
|
|
ctx->DirCluster = cluster;
|
|
ctx->CurCluster = cluster;
|
|
ctx->LoadedCluster = (uint32_t)-1;
|
|
ctx->EntryIndex = 0;
|
|
ctx->HasLfn = 0;
|
|
memset(ctx->LfnBuf, 0, sizeof(ctx->LfnBuf));
|
|
*dirCtx = ctx;
|
|
return STATUS_SUCCESS;
|
|
}
|
|
|
|
// Returns dir.
|
|
KSTATUS Fat32ReadDir(IO_FILESYSTEM *fs, void *dirCtx, KDIR_ENTRY *out) {
|
|
(void)fs;
|
|
FAT32_DIR_CTX *ctx = (FAT32_DIR_CTX *)dirCtx;
|
|
FAT32_VOL *v = ctx->Vol;
|
|
uint32_t epc = v->BytesPerCluster / sizeof(DIRENT);
|
|
|
|
while (ctx->CurCluster >= 2 && ctx->CurCluster < FAT32_EOC) {
|
|
// Load the cluster only when crossing a cluster boundary.
|
|
if (ctx->CurCluster != ctx->LoadedCluster) {
|
|
if (!KSUCCESS(ReadSectors(v, ClusterToLba(v, ctx->CurCluster),
|
|
v->SectorsPerCluster, ctx->ClusterBuf)))
|
|
break;
|
|
ctx->LoadedCluster = ctx->CurCluster;
|
|
}
|
|
|
|
DIRENT *dir = (DIRENT *)ctx->ClusterBuf;
|
|
while (ctx->EntryIndex < epc) {
|
|
DIRENT *e = &dir[ctx->EntryIndex++];
|
|
|
|
if (e->Name[0] == 0x00) { ctx->CurCluster = FAT32_EOC; return STATUS_NO_MORE_FILES; }
|
|
if ((uint8_t)e->Name[0] == 0xE5) { ctx->HasLfn = 0; memset(ctx->LfnBuf, 0, sizeof(ctx->LfnBuf)); continue; }
|
|
if (e->Attr == ATTR_LFN) { LfnAppend(ctx->LfnBuf, (LFN_ENTRY *)e); ctx->HasLfn = 1; continue; }
|
|
if (e->Attr & 0x08 && !(e->Attr & ATTR_DIRECTORY)) { ctx->HasLfn = 0; memset(ctx->LfnBuf, 0, sizeof(ctx->LfnBuf)); continue; }
|
|
|
|
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 STATUS_SUCCESS;
|
|
}
|
|
|
|
ctx->CurCluster = FatEntry(v, ctx->CurCluster);
|
|
ctx->EntryIndex = 0;
|
|
}
|
|
|
|
return STATUS_NO_MORE_FILES;
|
|
}
|
|
|
|
// Releases dir.
|
|
void Fat32CloseDir(IO_FILESYSTEM *fs, void *dirCtx) {
|
|
(void)fs;
|
|
FAT32_DIR_CTX *ctx = (FAT32_DIR_CTX *)dirCtx;
|
|
MmFreePool(ctx->ClusterBuf);
|
|
MmFreePool(ctx);
|
|
}
|
|
|
|
// Volume scan (MBR + GPT + superfloppy).
|
|
static const EFI_GUID GUID_EFI_SYSTEM = { 0xC12A7328, 0xF81F, 0x11D2, {0xBA,0x4B,0x00,0xA0,0xC9,0x3E,0xC9,0x3B} };
|
|
static const EFI_GUID GUID_MS_BASIC_DATA = { 0xEBD0A0A2, 0xB9E5, 0x4433, {0x87,0xC0,0x68,0xB6,0xB7,0x26,0x99,0xC7} };
|
|
static const EFI_GUID GUID_LINUX_BASIC_DATA = { 0x0FC63DAF, 0x8483, 0x4772, {0x8E,0x79,0x3D,0x69,0xD8,0x47,0x7D,0xE4} };
|
|
|
|
#define FAT32_MAX_PARTITIONS_PER_DISK 32
|
|
|
|
typedef struct FAT32_PARTITION_CANDIDATE {
|
|
uint64_t StartLba;
|
|
uint64_t SectorCount;
|
|
} FAT32_PARTITION_CANDIDATE;
|
|
|
|
// Returns le32.
|
|
static uint32_t ReadLe32(const void* p) {
|
|
const uint8_t* b = (const uint8_t*)p;
|
|
return (uint32_t)b[0]
|
|
| ((uint32_t)b[1] << 8)
|
|
| ((uint32_t)b[2] << 16)
|
|
| ((uint32_t)b[3] << 24);
|
|
}
|
|
|
|
// Checks whether fat32 Vbr.
|
|
static int IsFat32Vbr(FAT32_VOL *v, uint64_t lba) {
|
|
uint8_t sector[512];
|
|
if (!KSUCCESS(ReadSectors(v, lba, 1, sector))) return 0;
|
|
if (sector[510] != 0x55 || sector[511] != 0xAA) return 0;
|
|
|
|
BPB *bpb = (BPB *)sector;
|
|
if (bpb->BytesPerSector != 512) return 0; // disk I/O layer is 512-byte sector based
|
|
if (bpb->SectorsPerCluster == 0) return 0;
|
|
if (bpb->NumFats == 0) return 0;
|
|
if (bpb->FatSize16 != 0) return 0;
|
|
if (bpb->RootEntryCount != 0) return 0;
|
|
if (bpb->FatSize32 == 0) return 0;
|
|
if (bpb->RootCluster < 2) return 0;
|
|
|
|
uint32_t totalSectors = bpb->TotalSectors16 ? bpb->TotalSectors16 : bpb->TotalSectors32;
|
|
if (totalSectors == 0) return 0;
|
|
if (totalSectors <= (uint32_t)bpb->ReservedSectors + (uint32_t)bpb->NumFats * bpb->FatSize32)
|
|
return 0;
|
|
|
|
return 1;
|
|
}
|
|
|
|
static int Fat32CandidateExists(FAT32_PARTITION_CANDIDATE* out,
|
|
uint32_t count,
|
|
uint64_t startLba) {
|
|
for (uint32_t i = 0; i < count; i++) {
|
|
if (out[i].StartLba == startLba)
|
|
return 1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static void Fat32AddCandidate(FAT32_VOL* probe,
|
|
FAT32_PARTITION_CANDIDATE* out,
|
|
uint32_t* count,
|
|
uint32_t maxCount,
|
|
uint64_t startLba,
|
|
uint64_t sectorCount) {
|
|
if (!out || !count || *count >= maxCount) return;
|
|
if (Fat32CandidateExists(out, *count, startLba)) return;
|
|
|
|
uint64_t diskSectors = IoDiskSectorCount(probe->DriveIndex);
|
|
if (diskSectors) {
|
|
if (startLba >= diskSectors) return;
|
|
if (sectorCount == 0 || startLba + sectorCount > diskSectors)
|
|
sectorCount = diskSectors - startLba;
|
|
}
|
|
|
|
if (!IsFat32Vbr(probe, startLba)) return;
|
|
|
|
out[*count].StartLba = startLba;
|
|
out[*count].SectorCount = sectorCount;
|
|
(*count)++;
|
|
}
|
|
|
|
static uint32_t Fat32EnumerateDiskVolumes(uint8_t diskIndex,
|
|
FAT32_PARTITION_CANDIDATE* out,
|
|
uint32_t maxCount,
|
|
bool* outMediaReadable) {
|
|
if (outMediaReadable)
|
|
*outMediaReadable = false;
|
|
if (!out || maxCount == 0) return 0;
|
|
|
|
FAT32_VOL probe;
|
|
memset(&probe, 0, sizeof(probe));
|
|
probe.DriveIndex = diskIndex;
|
|
|
|
uint32_t count = 0;
|
|
uint8_t mbr[512];
|
|
if (!KSUCCESS(ReadSectors(&probe, 0, 1, mbr)))
|
|
return 0;
|
|
if (outMediaReadable)
|
|
*outMediaReadable = true;
|
|
|
|
// A disk image may be a raw FAT32 volume with no partition table.
|
|
Fat32AddCandidate(&probe, out, &count, maxCount, 0, IoDiskSectorCount(diskIndex));
|
|
|
|
if (mbr[510] != 0x55 || mbr[511] != 0xAA)
|
|
return count;
|
|
|
|
int isGpt = 0;
|
|
for (int i = 0; i < 4; i++) {
|
|
if (mbr[0x1BE + i * 16 + 4] == 0xEE) {
|
|
isGpt = 1;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (isGpt) {
|
|
uint8_t gptSector[512];
|
|
if (!KSUCCESS(ReadSectors(&probe, 1, 1, gptSector)))
|
|
return count;
|
|
|
|
GPT_HEADER* gpt = (GPT_HEADER*)gptSector;
|
|
if (memcmp(gpt->Signature, "EFI PART", 8) != 0)
|
|
return count;
|
|
|
|
uint64_t entryLBA = gpt->PartitionEntryLBA;
|
|
uint32_t numEntries = gpt->NumberOfPartitionEntries;
|
|
uint32_t entrySize = gpt->SizeOfPartitionEntry;
|
|
if (entrySize < sizeof(GPT_ENTRY) || entrySize > 512)
|
|
return count;
|
|
|
|
uint32_t entriesPerSector = 512 / entrySize;
|
|
if (!entriesPerSector)
|
|
return count;
|
|
|
|
uint8_t entrySector[512];
|
|
uint64_t loadedSector = UINT64_MAX;
|
|
static const EFI_GUID emptyGuid = {0};
|
|
|
|
for (uint32_t i = 0; i < numEntries && count < maxCount; i++) {
|
|
uint64_t sectorLba = entryLBA + i / entriesPerSector;
|
|
if (sectorLba != loadedSector) {
|
|
if (!KSUCCESS(ReadSectors(&probe, sectorLba, 1, entrySector)))
|
|
return count;
|
|
loadedSector = sectorLba;
|
|
}
|
|
|
|
GPT_ENTRY* entry = (GPT_ENTRY*)(entrySector + (i % entriesPerSector) * entrySize);
|
|
if (memcmp(&entry->PartitionTypeGUID, &emptyGuid, sizeof(EFI_GUID)) == 0)
|
|
continue;
|
|
|
|
if (memcmp(&entry->PartitionTypeGUID, &GUID_EFI_SYSTEM, sizeof(EFI_GUID)) != 0 &&
|
|
memcmp(&entry->PartitionTypeGUID, &GUID_MS_BASIC_DATA, sizeof(EFI_GUID)) != 0 &&
|
|
memcmp(&entry->PartitionTypeGUID, &GUID_LINUX_BASIC_DATA, sizeof(EFI_GUID)) != 0)
|
|
continue;
|
|
|
|
uint64_t start = entry->StartingLBA;
|
|
uint64_t end = entry->EndingLBA;
|
|
if (end < start) continue;
|
|
Fat32AddCandidate(&probe, out, &count, maxCount, start, end - start + 1);
|
|
}
|
|
|
|
return count;
|
|
}
|
|
|
|
for (int i = 0; i < 4 && count < maxCount; i++) {
|
|
uint8_t* entry = mbr + 0x1BE + i * 16;
|
|
uint8_t type = entry[4];
|
|
uint32_t start = ReadLe32(entry + 8);
|
|
uint32_t size = ReadLe32(entry + 12);
|
|
if (!start || !size) continue;
|
|
|
|
// FAT32 CHS/LBA plus EFI System Partition. The VBR probe is the
|
|
// authority, so accepting 0xEF here lets FAT32 ESPs mount too.
|
|
if (type == 0x0B || type == 0x0C || type == 0xEF)
|
|
Fat32AddCandidate(&probe, out, &count, maxCount, start, size);
|
|
}
|
|
|
|
return count;
|
|
}
|
|
|
|
// 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 VFS worker.
|
|
|
|
typedef struct FAT32_ASYNC_RW {
|
|
LIST_ENTRY Link;
|
|
FAT32_VOL* Vol;
|
|
FAT32_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;
|
|
} FAT32_ASYNC_RW;
|
|
|
|
static SPINLOCK gFat32AsyncLock = SPINLOCK_INIT;
|
|
static LIST_ENTRY gFat32AsyncQueue;
|
|
static SEMAPHORE* gFat32AsyncSemaphore;
|
|
static volatile int gFat32AsyncInitState;
|
|
|
|
static void Fat32AsyncReadSubmitNext(FAT32_ASYNC_RW* ctx);
|
|
static void Fat32AsyncWriteSubmitNext(FAT32_ASYNC_RW* ctx);
|
|
|
|
// Implements the async Worker operation.
|
|
static void Fat32AsyncWorker(void* arg) {
|
|
(void)arg;
|
|
|
|
for (;;) {
|
|
KSTATUS s = KeWaitSemaphore(gFat32AsyncSemaphore);
|
|
if (!KSUCCESS(s)) {
|
|
KeYield();
|
|
continue;
|
|
}
|
|
|
|
uintptr_t flags = KeAcquireSpinlock(&gFat32AsyncLock);
|
|
LIST_ENTRY* node = ListPopHead(&gFat32AsyncQueue);
|
|
KeReleaseSpinlock(&gFat32AsyncLock, flags);
|
|
if (!node) continue;
|
|
|
|
FAT32_ASYNC_RW* ctx = CONTAINING_RECORD(node, FAT32_ASYNC_RW, Link);
|
|
if (ctx->IsWrite)
|
|
Fat32AsyncWriteSubmitNext(ctx);
|
|
else
|
|
Fat32AsyncReadSubmitNext(ctx);
|
|
}
|
|
}
|
|
|
|
// Implements the ensure Async Worker Started operation.
|
|
static KSTATUS Fat32EnsureAsyncWorkerStarted(void) {
|
|
uintptr_t flags = KeAcquireSpinlock(&gFat32AsyncLock);
|
|
if (gFat32AsyncInitState == 2) {
|
|
KeReleaseSpinlock(&gFat32AsyncLock, flags);
|
|
return STATUS_SUCCESS;
|
|
}
|
|
if (gFat32AsyncInitState == 1) {
|
|
KeReleaseSpinlock(&gFat32AsyncLock, flags);
|
|
while (gFat32AsyncInitState == 1)
|
|
KeYield();
|
|
return gFat32AsyncInitState == 2 ? STATUS_SUCCESS : STATUS_FAILURE;
|
|
}
|
|
|
|
gFat32AsyncInitState = 1;
|
|
LIST_INIT(&gFat32AsyncQueue);
|
|
KeReleaseSpinlock(&gFat32AsyncLock, flags);
|
|
|
|
KSTATUS s = KeSemaphoreCreate(0, 0x7fffffff, &gFat32AsyncSemaphore);
|
|
if (KSUCCESS(s)) {
|
|
THREAD* worker = (THREAD*)0;
|
|
s = KeCreateKernelThread((uintptr_t)Fat32AsyncWorker, 0,
|
|
PriorityNormal, &worker);
|
|
(void)worker;
|
|
}
|
|
|
|
flags = KeAcquireSpinlock(&gFat32AsyncLock);
|
|
gFat32AsyncInitState = KSUCCESS(s) ? 2 : 0;
|
|
KeReleaseSpinlock(&gFat32AsyncLock, flags);
|
|
return s;
|
|
}
|
|
|
|
// Implements the async Queue Continuation operation.
|
|
static void Fat32AsyncQueueContinuation(FAT32_ASYNC_RW* ctx) {
|
|
if (!ctx) return;
|
|
|
|
uintptr_t flags = KeAcquireSpinlock(&gFat32AsyncLock);
|
|
ListInsertTail(&gFat32AsyncQueue, &ctx->Link);
|
|
KeReleaseSpinlock(&gFat32AsyncLock, flags);
|
|
KeSignalSemaphore(gFat32AsyncSemaphore);
|
|
}
|
|
|
|
// Implements the async Finish operation.
|
|
static void Fat32AsyncFinish(FAT32_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 Fat32AsyncReadIrpComplete(IRP* irp, void* completeCtx) {
|
|
FAT32_ASYNC_RW* ctx = (FAT32_ASYNC_RW*)completeCtx;
|
|
if (!ctx) return;
|
|
if (ctx->CurrentDevice) {
|
|
IoDereferenceDeviceObject(ctx->CurrentDevice);
|
|
ctx->CurrentDevice = (DEVICE_OBJECT*)0;
|
|
}
|
|
|
|
if (!KSUCCESS(irp->Status)) {
|
|
Fat32AsyncFinish(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;
|
|
Fat32StatsAddReadRun(ctx->Vol, sectors, merges);
|
|
}
|
|
|
|
Fat32AsyncQueueContinuation(ctx);
|
|
}
|
|
|
|
// Implements the async Read Submit Next operation.
|
|
static void Fat32AsyncReadSubmitNext(FAT32_ASYNC_RW* ctx) {
|
|
if (ctx->Remaining == 0) {
|
|
Fat32AsyncFinish(ctx, STATUS_SUCCESS, ctx->Done);
|
|
return;
|
|
}
|
|
|
|
if (ctx->Cluster < 2 || ctx->Cluster >= FAT32_EOC) {
|
|
if (ctx->Done < ctx->Requested)
|
|
memset(ctx->Dst, 0, ctx->Requested - ctx->Done);
|
|
Fat32AsyncFinish(ctx, STATUS_DEVICE_ERROR, ctx->Done);
|
|
return;
|
|
}
|
|
|
|
FAT32_VOL* v = ctx->Vol;
|
|
uint32_t runStart = ctx->Cluster;
|
|
uint32_t runSectors = 0;
|
|
uint32_t next = 0;
|
|
if (!Fat32BuildReadRun(ctx->File, ctx->LogicalCluster, ctx->Cluster,
|
|
FAT32_READ_RUN_MAX_SECTORS, &runSectors, &next)) {
|
|
Fat32AsyncFinish(ctx, STATUS_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) {
|
|
Fat32AsyncFinish(ctx, STATUS_NOT_FOUND, ctx->Done);
|
|
return;
|
|
}
|
|
|
|
IoBuildReadWriteIrp(&ctx->Irp, IRP_MJ_READ,
|
|
ClusterToLba(v, runStart) * 512ull,
|
|
runSectors * 512u,
|
|
ctx->RunBuf);
|
|
ctx->Irp.CompletionFn = Fat32AsyncReadIrpComplete;
|
|
ctx->Irp.CompletionCtx = ctx;
|
|
|
|
KSTATUS s = IoSubmitIrp(ctx->CurrentDevice, &ctx->Irp);
|
|
if (s != STATUS_PENDING) {
|
|
IoDereferenceDeviceObject(ctx->CurrentDevice);
|
|
ctx->CurrentDevice = (DEVICE_OBJECT*)0;
|
|
Fat32AsyncFinish(ctx, s, ctx->Done);
|
|
}
|
|
}
|
|
|
|
KSTATUS Fat32ReadAsync(IO_FILESYSTEM *fs, void *fileCtx,
|
|
uint64_t offset, void *buf, size_t size,
|
|
IO_FS_ASYNC_COMPLETE complete,
|
|
void *completeContext) {
|
|
(void)fs;
|
|
if (!complete) return STATUS_INVALID_PARAMETER;
|
|
if (!fileCtx || (!buf && size)) {
|
|
complete(completeContext, STATUS_INVALID_PARAMETER, 0);
|
|
return STATUS_PENDING;
|
|
}
|
|
|
|
FAT32_FILE* f = (FAT32_FILE*)fileCtx;
|
|
FAT32_VOL* v = f->Vol;
|
|
if (offset >= f->FileSize || size == 0) {
|
|
complete(completeContext, STATUS_SUCCESS, 0);
|
|
return STATUS_PENDING;
|
|
}
|
|
if (offset + size > f->FileSize)
|
|
size = (size_t)(f->FileSize - offset);
|
|
|
|
KSTATUS ws = Fat32EnsureAsyncWorkerStarted();
|
|
if (!KSUCCESS(ws)) {
|
|
complete(completeContext, ws, 0);
|
|
return STATUS_PENDING;
|
|
}
|
|
|
|
FAT32_ASYNC_RW* ctx = (FAT32_ASYNC_RW*)MmAllocatePool(sizeof(FAT32_ASYNC_RW));
|
|
if (!ctx) {
|
|
complete(completeContext, STATUS_NO_MEMORY, 0);
|
|
return STATUS_PENDING;
|
|
}
|
|
memset(ctx, 0, sizeof(*ctx));
|
|
LIST_INIT(&ctx->Link);
|
|
|
|
ctx->RunBuf = (uint8_t*)MmAllocatePool(FAT32_READ_RUN_MAX_SECTORS * 512u);
|
|
if (!ctx->RunBuf) {
|
|
MmFreePool(ctx);
|
|
complete(completeContext, STATUS_NO_MEMORY, 0);
|
|
return STATUS_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 = Fat32ResolveFileCluster(f, ctx->LogicalCluster);
|
|
ctx->Remaining = size;
|
|
ctx->Requested = size;
|
|
ctx->Dst = (uint8_t*)buf;
|
|
ctx->Complete = complete;
|
|
ctx->CompleteContext = completeContext;
|
|
|
|
Fat32AsyncQueueContinuation(ctx);
|
|
return STATUS_PENDING;
|
|
}
|
|
|
|
// Implements the async Write Irp Complete operation.
|
|
static void Fat32AsyncWriteIrpComplete(IRP* irp, void* completeCtx) {
|
|
FAT32_ASYNC_RW* ctx = (FAT32_ASYNC_RW*)completeCtx;
|
|
if (!ctx) return;
|
|
if (ctx->CurrentDevice) {
|
|
IoDereferenceDeviceObject(ctx->CurrentDevice);
|
|
ctx->CurrentDevice = (DEVICE_OBJECT*)0;
|
|
}
|
|
|
|
if (!KSUCCESS(irp->Status)) {
|
|
Fat32AsyncFinish(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;
|
|
|
|
Fat32AsyncQueueContinuation(ctx);
|
|
}
|
|
|
|
// Implements the async Write Submit Next operation.
|
|
static void Fat32AsyncWriteSubmitNext(FAT32_ASYNC_RW* ctx) {
|
|
if (ctx->Remaining == 0) {
|
|
uint64_t newEnd = ctx->Offset + ctx->Done;
|
|
if (ctx->Done > 0) {
|
|
FAT32_DIRENT_UPDATE u;
|
|
memset(&u, 0, sizeof(u));
|
|
FatQueryEncodedTime(&u.Timestamp);
|
|
u.Flags = FAT32_DIRENT_UPDATE_WRITE_TIME;
|
|
if (newEnd > ctx->File->FileSize) {
|
|
ctx->File->FileSize = (uint32_t)newEnd;
|
|
u.Flags |= FAT32_DIRENT_UPDATE_SIZE;
|
|
u.Size = ctx->File->FileSize;
|
|
}
|
|
DirentUpdateMetadata(ctx->Vol, ctx->File->DirentLba,
|
|
ctx->File->DirentOffset, &u);
|
|
}
|
|
Fat32AsyncFinish(ctx, STATUS_SUCCESS, ctx->Done);
|
|
return;
|
|
}
|
|
|
|
FAT32_VOL* v = ctx->Vol;
|
|
if (ctx->Cluster < 2 || ctx->Cluster >= FAT32_EOC) {
|
|
if (ctx->CurrentLast >= 2 && ctx->CurrentLast < FAT32_EOC) {
|
|
ctx->Cluster = AppendCluster(v, ctx->CurrentLast);
|
|
if (!ctx->Cluster) {
|
|
Fat32AsyncFinish(ctx, STATUS_NO_MEMORY, ctx->Done);
|
|
return;
|
|
}
|
|
} else {
|
|
Fat32AsyncFinish(ctx, STATUS_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 && next < FAT32_EOC &&
|
|
runSectors + v->SectorsPerCluster <= FAT32_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)) {
|
|
Fat32AsyncFinish(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) {
|
|
Fat32AsyncFinish(ctx, STATUS_NOT_FOUND, ctx->Done);
|
|
return;
|
|
}
|
|
|
|
IoBuildReadWriteIrp(&ctx->Irp, IRP_MJ_WRITE,
|
|
ClusterToLba(v, runStart) * 512ull,
|
|
runSectors * 512u,
|
|
ctx->RunBuf);
|
|
ctx->Irp.CompletionFn = Fat32AsyncWriteIrpComplete;
|
|
ctx->Irp.CompletionCtx = ctx;
|
|
|
|
KSTATUS s = IoSubmitIrp(ctx->CurrentDevice, &ctx->Irp);
|
|
if (s != STATUS_PENDING) {
|
|
IoDereferenceDeviceObject(ctx->CurrentDevice);
|
|
ctx->CurrentDevice = (DEVICE_OBJECT*)0;
|
|
Fat32AsyncFinish(ctx, s, ctx->Done);
|
|
}
|
|
}
|
|
|
|
KSTATUS Fat32WriteAsync(IO_FILESYSTEM *fs, void *fileCtx,
|
|
uint64_t offset, const void *buf, size_t size,
|
|
IO_FS_ASYNC_COMPLETE complete,
|
|
void *completeContext) {
|
|
(void)fs;
|
|
if (!complete) return STATUS_INVALID_PARAMETER;
|
|
if (!fileCtx || (!buf && size)) {
|
|
complete(completeContext, STATUS_INVALID_PARAMETER, 0);
|
|
return STATUS_PENDING;
|
|
}
|
|
if (size == 0) {
|
|
complete(completeContext, STATUS_SUCCESS, 0);
|
|
return STATUS_PENDING;
|
|
}
|
|
|
|
KSTATUS ws = Fat32EnsureAsyncWorkerStarted();
|
|
if (!KSUCCESS(ws)) {
|
|
complete(completeContext, ws, 0);
|
|
return STATUS_PENDING;
|
|
}
|
|
|
|
FAT32_FILE* f = (FAT32_FILE*)fileCtx;
|
|
FAT32_VOL* v = f->Vol;
|
|
Fat32FileInvalidateChainCache(f);
|
|
|
|
if (f->StartCluster < 2) {
|
|
uint32_t first = AllocCluster(v);
|
|
if (!first) {
|
|
complete(completeContext, STATUS_NO_MEMORY, 0);
|
|
return STATUS_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 (next < 2 || next >= FAT32_EOC) {
|
|
next = AppendCluster(v, cluster);
|
|
if (!next) {
|
|
complete(completeContext, STATUS_NO_MEMORY, 0);
|
|
return STATUS_PENDING;
|
|
}
|
|
}
|
|
cluster = next;
|
|
}
|
|
|
|
FAT32_ASYNC_RW* ctx = (FAT32_ASYNC_RW*)MmAllocatePool(sizeof(FAT32_ASYNC_RW));
|
|
if (!ctx) {
|
|
complete(completeContext, STATUS_NO_MEMORY, 0);
|
|
return STATUS_PENDING;
|
|
}
|
|
memset(ctx, 0, sizeof(*ctx));
|
|
LIST_INIT(&ctx->Link);
|
|
|
|
ctx->RunBuf = (uint8_t*)MmAllocatePool(FAT32_WRITE_RUN_MAX_SECTORS * 512u);
|
|
if (!ctx->RunBuf) {
|
|
MmFreePool(ctx);
|
|
complete(completeContext, STATUS_NO_MEMORY, 0);
|
|
return STATUS_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;
|
|
|
|
Fat32AsyncQueueContinuation(ctx);
|
|
return STATUS_PENDING;
|
|
}
|
|
|
|
// DriverEntry.
|
|
#define FAT32_MAX_VOLUMES 8
|
|
|
|
static FAT32_VOL gVols[FAT32_MAX_VOLUMES];
|
|
static IO_FILESYSTEM gFs[FAT32_MAX_VOLUMES];
|
|
static bool gVolumeMounted[FAT32_MAX_VOLUMES];
|
|
|
|
static uint32_t gMountedCount;
|
|
static bool gDiskScanned[FAT32_MAX_VOLUMES];
|
|
|
|
// Returns free Volume Index.
|
|
static int Fat32FindFreeVolumeIndex(void) {
|
|
for (uint8_t i = 0; i < FAT32_MAX_VOLUMES; i++) {
|
|
if (!gVolumeMounted[i] && !IoFilesystemPresent(i))
|
|
return i;
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
// Implements the try Mount Disk operation.
|
|
static uint32_t Fat32TryMountDisk(uint8_t disk) {
|
|
if (disk >= FAT32_MAX_VOLUMES)
|
|
return 0;
|
|
if (gDiskScanned[disk])
|
|
return 0;
|
|
|
|
if (!IoDiskDevicePresent(disk))
|
|
return 0;
|
|
|
|
uint32_t mountedHere = 0;
|
|
FAT32_PARTITION_CANDIDATE parts[FAT32_MAX_PARTITIONS_PER_DISK];
|
|
bool mediaReadable = false;
|
|
uint32_t partCount = Fat32EnumerateDiskVolumes(disk,
|
|
parts,
|
|
FAT32_MAX_PARTITIONS_PER_DISK,
|
|
&mediaReadable);
|
|
if (!mediaReadable) {
|
|
DbgPrint("FAT32: disk %u: media probe deferred; sector 0 unreadable\n", disk);
|
|
return 0;
|
|
}
|
|
|
|
// Only remember a completed probe. A transient hotplug/readiness failure
|
|
// must not permanently suppress a later mount attempt for the same disk
|
|
// index after a device is unplugged and replugged.
|
|
gDiskScanned[disk] = true;
|
|
|
|
if (!partCount) {
|
|
DbgPrint("FAT32: disk %u: no FAT32 volumes\n", disk);
|
|
return 0;
|
|
}
|
|
|
|
for (uint32_t part = 0; part < partCount; part++) {
|
|
int freeVolume = Fat32FindFreeVolumeIndex();
|
|
if (freeVolume < 0)
|
|
break;
|
|
uint8_t volumeIndex = (uint8_t)freeVolume;
|
|
FAT32_VOL* v = &gVols[volumeIndex];
|
|
memset(v, 0, sizeof(*v));
|
|
v->DriveIndex = disk;
|
|
v->PartitionLba = parts[part].StartLba;
|
|
v->PartitionSize = parts[part].SectorCount;
|
|
|
|
uint8_t sector[512];
|
|
if (!KSUCCESS(ReadSectors(v, v->PartitionLba, 1, sector))) {
|
|
DbgPrint("FAT32: disk %u part %u: failed to read VBR\n", disk, part);
|
|
continue;
|
|
}
|
|
|
|
BPB *bpb = (BPB *)sector;
|
|
if (!IsFat32Vbr(v, v->PartitionLba)) {
|
|
DbgPrint("FAT32: disk %u part %u: unsupported FAT variant\n", disk, part);
|
|
continue;
|
|
}
|
|
|
|
v->BytesPerSector = bpb->BytesPerSector;
|
|
v->SectorsPerCluster = bpb->SectorsPerCluster;
|
|
v->BytesPerCluster = bpb->BytesPerSector * bpb->SectorsPerCluster;
|
|
v->ReservedSectors = bpb->ReservedSectors;
|
|
v->NumFats = bpb->NumFats;
|
|
v->FatSize = bpb->FatSize32;
|
|
v->RootCluster = bpb->RootCluster;
|
|
v->DataStart = v->PartitionLba + bpb->ReservedSectors + (uint64_t)bpb->NumFats * bpb->FatSize32;
|
|
v->NextFreeHint = 2;
|
|
|
|
uint32_t totalSectors = bpb->TotalSectors16 ? bpb->TotalSectors16 : bpb->TotalSectors32;
|
|
uint32_t dataSectors = totalSectors - bpb->ReservedSectors - bpb->NumFats * bpb->FatSize32;
|
|
v->TotalClusters = dataSectors / bpb->SectorsPerCluster;
|
|
if (v->PartitionSize == 0 || v->PartitionSize > totalSectors)
|
|
v->PartitionSize = totalSectors;
|
|
|
|
Fat32InitVtable(&gFs[volumeIndex], v);
|
|
|
|
IO_VOLUME_PARAMS volParams;
|
|
volParams.DiskIndex = v->DriveIndex;
|
|
volParams.PartitionLba = v->PartitionLba;
|
|
volParams.PartitionSize = v->PartitionSize;
|
|
|
|
KSTATUS status = IoRegisterFilesystem(volumeIndex, &gFs[volumeIndex], &volParams);
|
|
if (KSUCCESS(status)) {
|
|
gVolumeMounted[volumeIndex] = true;
|
|
gMountedCount++;
|
|
mountedHere++;
|
|
DbgPrint("FAT32: disk %u part %u mounted as drive %u (lba=%llu clusters=%u).\n",
|
|
disk, part, volumeIndex, (unsigned long long)v->PartitionLba, v->TotalClusters);
|
|
} else {
|
|
DbgPrint("FAT32: disk %u part %u mount as drive %u failed status=%d.\n",
|
|
disk, part, volumeIndex, status);
|
|
}
|
|
}
|
|
|
|
if (partCount && !mountedHere && gMountedCount >= FAT32_MAX_VOLUMES) {
|
|
DbgPrint("FAT32: disk %u: volume table full\n", disk);
|
|
}
|
|
|
|
return mountedHere;
|
|
}
|
|
|
|
// Implements the disk Arrival operation.
|
|
static void Fat32DiskArrival(uint8_t driveIndex, DEVICE_OBJECT* device, void* context) {
|
|
(void)device;
|
|
(void)context;
|
|
uint32_t before = gMountedCount;
|
|
uint32_t mounted = Fat32TryMountDisk(driveIndex);
|
|
if (mounted) {
|
|
DbgPrint("FAT32: hotplug scan mounted %u new volume(s); total=%u\n",
|
|
mounted, gMountedCount);
|
|
} else if (gMountedCount != before) {
|
|
DbgPrint("FAT32: hotplug scan total=%u\n", gMountedCount);
|
|
}
|
|
}
|
|
|
|
// Implements the unmount Disk operation.
|
|
static void Fat32UnmountDisk(uint8_t diskIndex, IO_DEVICE_CHANGE change) {
|
|
uint32_t removed = 0;
|
|
|
|
for (uint8_t i = 0; i < FAT32_MAX_VOLUMES; i++) {
|
|
if (!gVolumeMounted[i])
|
|
continue;
|
|
if (gVols[i].DriveIndex != diskIndex)
|
|
continue;
|
|
|
|
// Surprise removal means the backing device may already be gone; do not
|
|
// attempt writeback here. Drop cached FAT sectors instead.
|
|
FatCacheInvalidate();
|
|
(void)IoUnregisterFilesystem(i);
|
|
memset(&gVols[i], 0, sizeof(gVols[i]));
|
|
memset(&gFs[i], 0, sizeof(gFs[i]));
|
|
gVolumeMounted[i] = false;
|
|
if (gMountedCount)
|
|
gMountedCount--;
|
|
removed++;
|
|
}
|
|
|
|
if (diskIndex < FAT32_MAX_VOLUMES)
|
|
gDiskScanned[diskIndex] = false;
|
|
|
|
if (removed) {
|
|
DbgPrint("FAT32: disk %u %s; unmounted %u volume(s); total=%u\n",
|
|
diskIndex,
|
|
change == IoDeviceSurpriseRemoved ? "surprise removed" : "removed",
|
|
removed,
|
|
gMountedCount);
|
|
}
|
|
}
|
|
|
|
static void Fat32DiskChange(uint8_t driveIndex,
|
|
DEVICE_OBJECT* device,
|
|
IO_DEVICE_CHANGE change,
|
|
void* context) {
|
|
(void)device;
|
|
(void)context;
|
|
|
|
switch (change) {
|
|
case IoDeviceArrived:
|
|
Fat32DiskArrival(driveIndex, device, context);
|
|
break;
|
|
case IoDeviceSurpriseRemoved:
|
|
case IoDeviceRemoved:
|
|
Fat32UnmountDisk(driveIndex, change);
|
|
break;
|
|
case IoDeviceQueryRemove:
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
// Implements the driver Entry operation.
|
|
KSTATUS DriverEntry(const HAL_API *hal) {
|
|
DbgPrint("FAT32: Init\n");
|
|
if (!hal) return STATUS_INVALID_PARAMETER;
|
|
|
|
FatCacheInvalidate();
|
|
(void)hal;
|
|
gMountedCount = 0;
|
|
memset(gDiskScanned, 0, sizeof(gDiskScanned));
|
|
|
|
KSTATUS cbStatus = IoRegisterDiskChangeCallback(Fat32DiskChange, (void*)0);
|
|
if (!KSUCCESS(cbStatus)) {
|
|
DbgPrint("FAT32: disk-change callback registration failed st=0x%x\n", cbStatus);
|
|
}
|
|
|
|
cbStatus = IoEnumerateDiskDevices(Fat32DiskArrival, (void*)0);
|
|
if (!KSUCCESS(cbStatus)) {
|
|
DbgPrint("FAT32: initial disk enumeration failed st=0x%x\n", cbStatus);
|
|
}
|
|
|
|
if (!gMountedCount) {
|
|
DbgPrint("FAT32: no mountable FAT32 volumes found.\n");
|
|
return STATUS_NOT_SUPPORTED;
|
|
}
|
|
|
|
DbgPrint("FAT32: Init complete; mounted %u volume(s).\n", gMountedCount);
|
|
return STATUS_SUCCESS;
|
|
}
|