// fat32.c - FAT32 filesystem core, directory, cache, and I/O implementation. #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_DIRECTORY 0x10 #define ATTR_ARCHIVE 0x20 #define ATTR_LFN 0x0F #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]; // Raw (non-cached) sector I/O — forward declarations. static KSTATUS ReadSectorsRaw (FAT32_VOL *v, uint64_t lba, uint32_t count, void *buf); static KSTATUS WriteSectorsRaw(FAT32_VOL *v, uint64_t lba, uint32_t count, const void *buf); // Read one FAT sector through the cache. // Returns a pointer into the cache buffer (valid until the next eviction). static KSTATUS FatCacheRead(FAT32_VOL *v, uint64_t lba, uint8_t **out) { uint32_t slot = (uint32_t)(lba % FAT_CACHE_SLOTS); FAT_CACHE_ENTRY *e = &gFatCache[slot]; if (e->valid && e->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; } 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; } static void FatCacheInvalidate(void) { for (int i = 0; i < FAT_CACHE_SLOTS; i++) { gFatCache[i].vol = (FAT32_VOL*)0; gFatCache[i].valid = false; gFatCache[i].dirty = false; } } // FlushFatCache — write every dirty slot to all FAT copies, clear dirty flag. // Defined after FAT32_VOL is fully declared (below). static void FlushFatCache(FAT32_VOL *v); // --------------------------------------------------------------------------- // Driver state // --------------------------------------------------------------------------- struct FAT32_VOL_S { uint8_t DriveIndex; // 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 }; typedef struct { uint32_t StartCluster; uint32_t FileSize; FAT32_VOL *Vol; uint64_t DirentLba; uint32_t DirentOffset; } FAT32_FILE; // --------------------------------------------------------------------------- // Raw sector I/O (bypasses FAT cache; used for file data and cache eviction) // --------------------------------------------------------------------------- static KSTATUS ReadSectorsRaw(FAT32_VOL *v, uint64_t lba, uint32_t count, void *buf) { if (!v) 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); } static KSTATUS WriteSectors(FAT32_VOL *v, uint64_t lba, uint32_t n, const void *b) { return WriteSectorsRaw(v, lba, n, b); } static uint64_t ClusterToLba(FAT32_VOL *v, uint32_t cluster) { return v->DataStart + (uint64_t)(cluster - 2) * v->SectorsPerCluster; } // --------------------------------------------------------------------------- // FlushFatCache — needs full FAT32_VOL definition // --------------------------------------------------------------------------- static void FlushFatCache(FAT32_VOL *v) { uint64_t fat0Start = (uint64_t)v->PartitionLba + v->ReservedSectors; for (int i = 0; i < FAT_CACHE_SLOTS; i++) { FAT_CACHE_ENTRY *e = &gFatCache[i]; if (!e->valid || !e->dirty || 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; } } // --------------------------------------------------------------------------- // FAT read / write (through cache) // --------------------------------------------------------------------------- static uint32_t FatEntry(FAT32_VOL *v, uint32_t cluster) { uint32_t fatOffset = cluster * 4; uint64_t sector = (uint64_t)v->PartitionLba + v->ReservedSectors + fatOffset / v->BytesPerSector; uint32_t offInSec = fatOffset % v->BytesPerSector; uint8_t *sec; if (!KSUCCESS(FatCacheRead(v, sector, &sec))) return 0; return *(uint32_t *)(sec + offInSec) & 0x0FFFFFFF; } static KSTATUS WriteFatEntry(FAT32_VOL *v, uint32_t cluster, uint32_t value) { uint32_t fatOffset = cluster * 4; uint64_t sector = (uint64_t)v->PartitionLba + v->ReservedSectors + fatOffset / v->BytesPerSector; uint32_t offInSec = fatOffset % v->BytesPerSector; uint8_t *sec; KSTATUS s = FatCacheRead(v, sector, &sec); if (!KSUCCESS(s)) return s; uint32_t existing = *(uint32_t *)(sec + offInSec); *(uint32_t *)(sec + offInSec) = (existing & 0xF0000000u) | (value & 0x0FFFFFFFu); FatCacheMarkDirty(v, sector); return STATUS_SUCCESS; } // --------------------------------------------------------------------------- // Cluster allocation (fix #3: next-free hint + two-phase wrap-around scan) // --------------------------------------------------------------------------- static uint32_t AllocCluster(FAT32_VOL *v) { if (v->TotalClusters == 0) return 0; uint32_t total = v->TotalClusters; uint32_t start = v->NextFreeHint; if (start < 2 || start >= total + 2) start = 2; for (uint32_t pass = 0; pass < 2; pass++) { uint32_t lo = (pass == 0) ? start : 2; uint32_t hi = (pass == 0) ? total+2 : start; for (uint32_t c = lo; c < hi; c++) { if (FatEntry(v, c) == FAT32_FREE) { if (!KSUCCESS(WriteFatEntry(v, c, FAT32_EOC_MARK))) return 0; v->NextFreeHint = c + 1; // Zero the cluster (required for directory correctness). uint8_t *zero = (uint8_t *)MmAllocatePool(v->BytesPerCluster); if (zero) { memset(zero, 0, v->BytesPerCluster); WriteSectors(v, ClusterToLba(v, c), v->SectorsPerCluster, zero); MmFreePool(zero); } return c; } } } return 0; } static uint32_t AppendCluster(FAT32_VOL *v, uint32_t lastCluster) { uint32_t newCluster = AllocCluster(v); if (!newCluster) return 0; if (!KSUCCESS(WriteFatEntry(v, lastCluster, newCluster))) return 0; return newCluster; } static uint32_t GetChainEnd(FAT32_VOL *v, uint32_t start, uint32_t *outCount) { uint32_t cluster = start, prev = start, count = 0; while (cluster >= 2 && cluster < FAT32_EOC) { prev = cluster; count++; cluster = FatEntry(v, cluster); } if (outCount) *outCount = count; return prev; } static void FreeClusterChain(FAT32_VOL *v, uint32_t start) { uint32_t cluster = start; while (cluster >= 2 && cluster < FAT32_EOC) { uint32_t next = FatEntry(v, cluster); WriteFatEntry(v, cluster, FAT32_FREE); if (cluster < v->NextFreeHint) v->NextFreeHint = cluster; cluster = next; } } // --------------------------------------------------------------------------- // DIRENT update helpers // --------------------------------------------------------------------------- static KSTATUS DirentUpdateSize(FAT32_VOL *v, uint64_t lba, uint32_t offset, uint32_t size) { uint8_t *sec = (uint8_t *)MmAllocatePool(v->BytesPerSector); if (!sec) return STATUS_NO_MEMORY; KSTATUS s = ReadSectors(v, lba, 1, sec); if (KSUCCESS(s)) { ((DIRENT *)(sec + offset))->FileSize = size; s = WriteSectors(v, lba, 1, sec); } MmFreePool(sec); return s; } static KSTATUS DirentUpdateFirstCluster(FAT32_VOL *v, uint64_t lba, uint32_t offset, uint32_t cluster) { uint8_t *sec = (uint8_t *)MmAllocatePool(v->BytesPerSector); if (!sec) return STATUS_NO_MEMORY; KSTATUS s = ReadSectors(v, lba, 1, sec); if (KSUCCESS(s)) { DIRENT *d = (DIRENT *)(sec + offset); d->FirstClusterHi = (uint16_t)(cluster >> 16); d->FirstClusterLo = (uint16_t)(cluster & 0xFFFF); s = WriteSectors(v, lba, 1, sec); } MmFreePool(sec); return s; } // --------------------------------------------------------------------------- // 8.3 name matching / assembly // --------------------------------------------------------------------------- static int 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); 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 static char FatUpperChar(char c) { return (c >= 'a' && c <= 'z') ? (char)(c - 32) : c; } static int FatIsLowerChar(char c) { return c >= 'a' && c <= 'z'; } 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; } // Creation uses LFN when needed for structure, or when lowercase would be lost. static int FatNameNeedsLfnForCreateV(SYS_STRING_VIEW name) { if (!FatNameIsStructurallyShort83V(name)) return 1; for (size_t i = 0; i < name.Length; i++) if (FatIsLowerChar(name.Data[i])) return 1; return 0; } 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; } 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); } } 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; } static void FatAliasAppendChar(char *dst, int *len, int maxLen, char c) { c = FatUpperChar(c); if (!FatIsValidShortChar(c)) return; if (*len < maxLen) dst[(*len)++] = c; } 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; } 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; } 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 == '/'; } 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; } 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; } 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; while (cluster >= 2 && cluster < FAT32_EOC) { uint64_t clusterLba = ClusterToLba(v, cluster); if (!KSUCCESS(ReadSectors(v, clusterLba, v->SectorsPerCluster, clusterBuf))) break; uint32_t entries = v->BytesPerCluster / sizeof(DIRENT); DIRENT *dir = (DIRENT *)clusterBuf; for (uint32_t i = 0; i < entries; i++) { if (dir[i].Name[0] == 0x00) goto done; if ((uint8_t)dir[i].Name[0] == 0xE5) { hasLfn = 0; memset(lfn, 0, sizeof(lfn)); continue; } if (dir[i].Attr == ATTR_LFN) { LfnAppend(lfn, (LFN_ENTRY *)&dir[i]); hasLfn = 1; continue; } int match = 0; if (hasLfn && lfn[0]) match = 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); } MmFreePool(clusterBuf); return 1; } } cluster = FatEntry(v, cluster); } done: MmFreePool(clusterBuf); return 0; } 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, 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; if (outDirentLba) *outDirentLba = 0; if (outDirentOffset) *outDirentOffset = 0; 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; if (outDirentLba) *outDirentLba = childLba; if (outDirentOffset) *outDirentOffset = childOffset; } *outCluster = cluster; *outSize = size; 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); } 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; } // --------------------------------------------------------------------------- // 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, &direntLba, &direntOffset); if (!KSUCCESS(s)) return s; FAT32_FILE *f = (FAT32_FILE *)MmAllocatePool(sizeof(FAT32_FILE)); if (!f) return STATUS_NO_MEMORY; f->StartCluster = cluster; f->FileSize = size; f->Vol = v; f->DirentLba = direntLba; f->DirentOffset = direntOffset; *fileCtx = f; return STATUS_SUCCESS; } void Fat32Close(IO_FILESYSTEM *fs, void *fileCtx) { (void)fs; FAT32_FILE *f = (FAT32_FILE *)fileCtx; FlushFatCache(f->Vol); MmFreePool(fileCtx); } // --------------------------------------------------------------------------- // Read (fix #4: contiguous-run coalescing) // --------------------------------------------------------------------------- // We peek ahead in the FAT chain: if cluster N+1 == cluster N physically, // extend the current run instead of issuing a new ReadSectors call. // Run length is capped at MAX_RUN_SECTORS to stay within the AHCI bounce // buffer (128 sectors = 64 KB). // // FatEntry() calls in the peek loop are nearly free because they hit the // FAT sector cache (fix #1). #define MAX_RUN_SECTORS 128u 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; // Allocate only as much bounce space as this read can actually use. // The old code allocated 64 KiB and could read a 64 KiB contiguous run even // for tiny random reads; Doom does lots of seek/read pairs for small lumps. 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 > MAX_RUN_SECTORS) maxUsefulSectors = MAX_RUN_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 skipClusters = (uint32_t)(offset / v->BytesPerCluster); uint32_t offsetInCluster = (uint32_t)(offset % v->BytesPerCluster); uint32_t cluster = f->StartCluster; for (uint32_t i = 0; i < skipClusters && cluster < FAT32_EOC; i++) cluster = FatEntry(v, cluster); while (size > 0 && cluster >= 2 && cluster < FAT32_EOC) { // Build the longest physically-contiguous run that can contribute to // this caller's remaining read. Do not prefetch an unrelated 64 KiB run // for a tiny random read. 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 > MAX_RUN_SECTORS) usefulSectors = MAX_RUN_SECTORS; if (usefulSectors > maxUsefulSectors) usefulSectors = maxUsefulSectors; uint32_t runStart = cluster; uint32_t runSectors = v->SectorsPerCluster; uint32_t next = FatEntry(v, cluster); while (next == cluster + 1 && next < FAT32_EOC && runSectors + v->SectorsPerCluster <= usefulSectors) { cluster = next; next = FatEntry(v, cluster); runSectors += v->SectorsPerCluster; } KSTATUS rs = ReadSectors(v, ClusterToLba(v, runStart), runSectors, runBuf); if (!KSUCCESS(rs)) { readStatus = rs; break; } uint32_t runBytes = runSectors * 512u; size_t avail = runBytes - offsetInCluster; size_t copy = avail < size ? avail : size; memcpy(out, runBuf + offsetInCluster, copy); out += copy; totalRead += copy; size -= copy; offsetInCluster = 0; cluster = next; } if (totalRead != wanted) { // The directory entry says the file has more data than the cluster chain // or disk read actually supplied. Clear the caller's remainder so stale // memory cannot be interpreted as file contents. memset(out, 0, wanted - totalRead); } MmFreePool(runBuf); *bytesRead = totalRead; if (totalRead != wanted) return KSUCCESS(readStatus) ? STATUS_DEVICE_ERROR : readStatus; return STATUS_SUCCESS; } // --------------------------------------------------------------------------- // Write (fix #4: contiguous-run coalescing) // --------------------------------------------------------------------------- // Same strategy as read. Whole-cluster-aligned runs skip the read-modify- // write; partial head/tail clusters still require it. KSTATUS Fat32Write(IO_FILESYSTEM *fs, void *fileCtx, uint64_t offset, const void *buf, size_t size, size_t *bytesWritten) { (void)fs; if (!size) { *bytesWritten = 0; return STATUS_SUCCESS; } FAT32_FILE *f = (FAT32_FILE *)fileCtx; FAT32_VOL *v = f->Vol; const uint8_t *src = (const uint8_t *)buf; // Ensure at least one cluster exists. if (f->StartCluster < 2) { uint32_t first = AllocCluster(v); if (!first) { *bytesWritten = 0; return STATUS_NO_MEMORY; } f->StartCluster = first; DirentUpdateFirstCluster(v, f->DirentLba, f->DirentOffset, first); } // Walk to the starting cluster, allocating as needed. uint32_t clusterIndex = (uint32_t)(offset / v->BytesPerCluster); uint32_t offsetInCluster = (uint32_t)(offset % v->BytesPerCluster); uint32_t cluster = f->StartCluster; for (uint32_t i = 0; i < clusterIndex; i++) { uint32_t next = FatEntry(v, cluster); if (next < 2 || next >= FAT32_EOC) { next = AppendCluster(v, cluster); if (!next) { *bytesWritten = 0; return STATUS_NO_MEMORY; } } cluster = next; } uint8_t *runBuf = (uint8_t *)MmAllocatePool(MAX_RUN_SECTORS * 512u); if (!runBuf) { *bytesWritten = 0; return STATUS_NO_MEMORY; } size_t written = 0, remaining = size; while (remaining > 0 && cluster >= 2) { // Build a contiguous run (only when cluster-aligned to avoid a // partial read-modify-write spanning multiple clusters). uint32_t runStart = cluster; uint32_t runSectors = v->SectorsPerCluster; uint32_t next = FatEntry(v, cluster); if (offsetInCluster == 0) { while (next == cluster + 1 && next < FAT32_EOC && runSectors + v->SectorsPerCluster <= MAX_RUN_SECTORS) { cluster = next; next = FatEntry(v, cluster); runSectors += v->SectorsPerCluster; } } uint32_t runBytes = runSectors * 512u; size_t avail = runBytes - offsetInCluster; size_t copy = avail < remaining ? avail : remaining; // Read existing data only for partial-cluster writes. bool partial = (offsetInCluster > 0) || (copy < (size_t)runBytes); if (partial) { if (!KSUCCESS(ReadSectors(v, ClusterToLba(v, runStart), runSectors, runBuf))) break; } memcpy(runBuf + offsetInCluster, src, copy); if (!KSUCCESS(WriteSectors(v, ClusterToLba(v, runStart), runSectors, runBuf))) break; src += copy; written += copy; remaining -= copy; offsetInCluster = 0; if (remaining > 0) { // Advance: if end-of-chain, append a new cluster. cluster = next; if (cluster < 2 || cluster >= FAT32_EOC) { // Find the actual last cluster in the run we just wrote. uint32_t last = runStart; for (uint32_t tmp = FatEntry(v, last); tmp >= 2 && tmp < FAT32_EOC; tmp = FatEntry(v, last)) last = tmp; cluster = AppendCluster(v, last); if (!cluster) break; } } } MmFreePool(runBuf); uint64_t newEnd = offset + written; if (newEnd > f->FileSize) { f->FileSize = (uint32_t)newEnd; DirentUpdateSize(v, f->DirentLba, f->DirentOffset, f->FileSize); } *bytesWritten = written; return (written == size) ? STATUS_SUCCESS : STATUS_DEVICE_ERROR; } // --------------------------------------------------------------------------- // Create // --------------------------------------------------------------------------- KSTATUS Fat32Truncate(IO_FILESYSTEM *fs, void *fileCtx, uint64_t newSize); 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; SYS_STRING_VIEW parentPath; SYS_STRING_VIEW filenameView; KSTATUS s = SplitPathV(path, &parentPath, &filenameView); if (!KSUCCESS(s)) return s; uint32_t parentCluster, parentSize; s = TraverseV(v, parentPath, &parentCluster, &parentSize); 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)) { if (ea & ATTR_DIRECTORY) return STATUS_OBJECT_NAME_COLLISION; FAT32_FILE *f = (FAT32_FILE *)MmAllocatePool(sizeof(FAT32_FILE)); if (!f) return STATUS_NO_MEMORY; f->StartCluster = ec; f->FileSize = es; f->Vol = v; f->DirentLba = existingLba; f->DirentOffset = existingOffset; // Create means overwrite by default for regular files. Reuse the // existing directory entry, drop the old cluster chain, and return // an open zero-length file instead of allocating a duplicate name. KSTATUS ts = Fat32Truncate(fs, f, 0); if (!KSUCCESS(ts)) { MmFreePool(f); return ts; } *fileCtx = f; return STATUS_SUCCESS; } } 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_ARCHIVE; 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); FAT32_FILE *f = (FAT32_FILE *)MmAllocatePool(sizeof(FAT32_FILE)); if (!f) return STATUS_NO_MEMORY; f->StartCluster = 0; f->FileSize = 0; f->Vol = v; f->DirentLba = direntLba; f->DirentOffset = direntOffset; *fileCtx = f; return STATUS_SUCCESS; } // --------------------------------------------------------------------------- // Truncate // --------------------------------------------------------------------------- KSTATUS Fat32Truncate(IO_FILESYSTEM *fs, void *fileCtx, uint64_t newSize) { (void)fs; FAT32_FILE *f = (FAT32_FILE *)fileCtx; FAT32_VOL *v = f->Vol; if ((uint32_t)newSize == f->FileSize) return STATUS_SUCCESS; if (newSize == 0) { if (f->StartCluster >= 2) { FreeClusterChain(v, f->StartCluster); f->StartCluster = 0; DirentUpdateFirstCluster(v, f->DirentLba, f->DirentOffset, 0); } f->FileSize = 0; KSTATUS s = DirentUpdateSize(v, f->DirentLba, f->DirentOffset, 0); FlushFatCache(v); return s; } uint32_t newClusters = (uint32_t) ((newSize + v->BytesPerCluster - 1) / v->BytesPerCluster); if (newSize < f->FileSize) { if (f->StartCluster < 2) goto update_size; uint32_t cluster = f->StartCluster, lastGood = cluster; for (uint32_t i = 1; i < newClusters && cluster < FAT32_EOC; i++) { lastGood = cluster; cluster = FatEntry(v, cluster); } uint32_t tail = FatEntry(v, lastGood); WriteFatEntry(v, lastGood, FAT32_EOC_MARK); FreeClusterChain(v, tail); } else { uint32_t curClusters = f->FileSize ? (f->FileSize + v->BytesPerCluster - 1) / v->BytesPerCluster : 0; uint32_t lastCluster = 0; if (f->StartCluster < 2) { uint32_t first = AllocCluster(v); if (!first) return STATUS_NO_MEMORY; f->StartCluster = first; DirentUpdateFirstCluster(v, f->DirentLba, f->DirentOffset, first); lastCluster = first; curClusters = 1; } else { lastCluster = GetChainEnd(v, f->StartCluster, NULL); } for (uint32_t i = curClusters; i < newClusters; i++) { uint32_t next = AppendCluster(v, lastCluster); if (!next) return STATUS_NO_MEMORY; lastCluster = next; } } update_size: f->FileSize = (uint32_t)newSize; { KSTATUS s = DirentUpdateSize(v, f->DirentLba, f->DirentOffset, (uint32_t)newSize); FlushFatCache(v); return s; } } // --------------------------------------------------------------------------- // Delete // --------------------------------------------------------------------------- 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, &direntLba, &direntOffset); if (!KSUCCESS(s)) return s; if (!direntLba) return STATUS_ACCESS_DENIED; uint8_t *sec = (uint8_t *)MmAllocatePool(v->BytesPerSector); if (!sec) return STATUS_NO_MEMORY; s = ReadSectors(v, direntLba, 1, sec); if (KSUCCESS(s)) { sec[direntOffset] = 0xE5; s = WriteSectors(v, direntLba, 1, sec); } MmFreePool(sec); if (!KSUCCESS(s)) return s; if (cluster >= 2) FreeClusterChain(v, cluster); FlushFatCache(v); return STATUS_SUCCESS; } uint64_t Fat32GetSize(IO_FILESYSTEM *fs, void *fileCtx) { (void)fs; return ((FAT32_FILE *)fileCtx)->FileSize; } // --------------------------------------------------------------------------- // Directory enumeration (fix #2: cluster buffer cached in context) // --------------------------------------------------------------------------- // Previously: allocate + ReadSectors + return 1 entry + free, every call. // Now: buffer lives in FAT32_DIR_CTX; ReadSectors fires only on cluster // boundaries. typedef struct { FAT32_VOL *Vol; uint32_t DirCluster; uint32_t CurCluster; uint32_t LoadedCluster; // which cluster is currently in ClusterBuf uint32_t EntryIndex; char LfnBuf[MAX_LFN]; int HasLfn; uint8_t *ClusterBuf; // allocated once in OpenDir, freed in CloseDir } FAT32_DIR_CTX; 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; } KSTATUS Fat32ReadDir(IO_FILESYSTEM *fs, void *dirCtx, KDIR_ENTRY *out) { (void)fs; FAT32_DIR_CTX *ctx = (FAT32_DIR_CTX *)dirCtx; FAT32_VOL *v = ctx->Vol; uint32_t epc = v->BytesPerCluster / sizeof(DIRENT); while (ctx->CurCluster >= 2 && ctx->CurCluster < FAT32_EOC) { // Load the cluster only when crossing a cluster boundary. if (ctx->CurCluster != ctx->LoadedCluster) { if (!KSUCCESS(ReadSectors(v, ClusterToLba(v, ctx->CurCluster), v->SectorsPerCluster, ctx->ClusterBuf))) break; ctx->LoadedCluster = ctx->CurCluster; } DIRENT *dir = (DIRENT *)ctx->ClusterBuf; while (ctx->EntryIndex < epc) { DIRENT *e = &dir[ctx->EntryIndex++]; if (e->Name[0] == 0x00) { ctx->CurCluster = FAT32_EOC; return STATUS_NO_MORE_FILES; } if ((uint8_t)e->Name[0] == 0xE5) { ctx->HasLfn = 0; memset(ctx->LfnBuf, 0, sizeof(ctx->LfnBuf)); continue; } if (e->Attr == ATTR_LFN) { LfnAppend(ctx->LfnBuf, (LFN_ENTRY *)e); ctx->HasLfn = 1; continue; } if (e->Attr & 0x08 && !(e->Attr & ATTR_DIRECTORY)) { ctx->HasLfn = 0; memset(ctx->LfnBuf, 0, sizeof(ctx->LfnBuf)); continue; } if (ctx->HasLfn && ctx->LfnBuf[0]) { size_t len = strlen(ctx->LfnBuf); if (len >= KDIR_NAME_MAX) len = KDIR_NAME_MAX - 1; memcpy(out->Name, ctx->LfnBuf, len); out->Name[len] = '\0'; } else { char name83[13]; int n = 0; for (int i = 0; i < 8 && e->Name[i] != ' '; i++) name83[n++] = (char)(e->Name[i] >= 'A' && e->Name[i] <= 'Z' ? e->Name[i]+32 : e->Name[i]); if (e->Ext[0] != ' ') { name83[n++] = '.'; for (int i = 0; i < 3 && e->Ext[i] != ' '; i++) name83[n++] = (char)(e->Ext[i] >= 'A' && e->Ext[i] <= 'Z' ? e->Ext[i]+32 : e->Ext[i]); } name83[n] = '\0'; size_t len = (size_t)n; if (len >= KDIR_NAME_MAX) len = KDIR_NAME_MAX - 1; memcpy(out->Name, name83, len); out->Name[len] = '\0'; } out->Attr = e->Attr; out->FileSize = e->FileSize; ctx->HasLfn = 0; memset(ctx->LfnBuf, 0, sizeof(ctx->LfnBuf)); return STATUS_SUCCESS; } ctx->CurCluster = FatEntry(v, ctx->CurCluster); ctx->EntryIndex = 0; ctx->HasLfn = 0; memset(ctx->LfnBuf, 0, sizeof(ctx->LfnBuf)); } return STATUS_NO_MORE_FILES; } 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; 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); } 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; 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 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); 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); } } 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; } static void Fat32AsyncQueueContinuation(FAT32_ASYNC_RW* ctx) { if (!ctx) return; uintptr_t flags = KeAcquireSpinlock(&gFat32AsyncLock); ListInsertTail(&gFat32AsyncQueue, &ctx->Link); KeReleaseSpinlock(&gFat32AsyncLock, flags); KeSignalSemaphore(gFat32AsyncSemaphore); } 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); } 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->Cluster = ctx->CurrentNext; Fat32AsyncQueueContinuation(ctx); } 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 = v->SectorsPerCluster; uint32_t last = ctx->Cluster; uint32_t next = FatEntry(v, last); while (next == last + 1 && next < FAT32_EOC && runSectors + v->SectorsPerCluster <= MAX_RUN_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; 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_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(MAX_RUN_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->Cluster = f->StartCluster; ctx->Remaining = size; ctx->Requested = size; ctx->Dst = (uint8_t*)buf; ctx->Complete = complete; ctx->CompleteContext = completeContext; uint32_t skipClusters = (uint32_t)(offset / v->BytesPerCluster); for (uint32_t i = 0; i < skipClusters && ctx->Cluster < FAT32_EOC; i++) ctx->Cluster = FatEntry(v, ctx->Cluster); Fat32AsyncQueueContinuation(ctx); return STATUS_PENDING; } 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); } static void Fat32AsyncWriteSubmitNext(FAT32_ASYNC_RW* ctx) { if (ctx->Remaining == 0) { uint64_t newEnd = ctx->Offset + ctx->Done; if (newEnd > ctx->File->FileSize) { ctx->File->FileSize = (uint32_t)newEnd; DirentUpdateSize(ctx->Vol, ctx->File->DirentLba, ctx->File->DirentOffset, ctx->File->FileSize); } 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 <= MAX_RUN_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; 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(MAX_RUN_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]; static int Fat32FindFreeVolumeIndex(void) { for (uint8_t i = 0; i < FAT32_MAX_VOLUMES; i++) { if (!gVolumeMounted[i] && !IoFilesystemPresent(i)) return i; } return -1; } 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; } 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); } } 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; } } 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; }