// fat32.c - FAT32 filesystem driver // Read + write support: file data, cluster allocation, directory entries. // // Performance improvements over the original: // // 1. FAT sector cache (FAT_CACHE_SLOTS write-back entries). // FatEntry() and WriteFatEntry() go through the cache; the hot FAT // region stays in RAM instead of being read from disk on every chain // traversal step. A 32-slot cache covers 32 x (512/4) = 4096 // consecutive cluster entries — enough to keep the entire FAT of a // small partition in memory, and to avoid repeated reads even on large // ones. // // 2. Fat32ReadDir() cluster buffer cached in FAT32_DIR_CTX. // The original re-read the same cluster from disk on every call // (allocate -> read -> return one entry -> free). Now the buffer is // allocated once in Fat32OpenDir, freed in Fat32CloseDir, and only // re-read when the iteration moves to a new cluster. // // 3. AllocCluster() next-free hint. // The search now starts from the cluster after the last allocation // (volume NextFreeHint) and wraps around once, so repeated allocations // on a fragmented volume don't rescan from sector 0 every time. // // 4. Contiguous-cluster run coalescing in Fat32Read() and Fat32Write(). // After fetching a cluster's FAT entry the code peeks ahead: if the // next cluster is physically adjacent (cluster+1) it extends the run // rather than issuing a separate ReadSectors/WriteSectors call. A // freshly-written unfragmented file now transfers in one or a handful // of large DMA bursts instead of SectorsPerCluster-sized slices. // // 5. FAT write batching via a dirty-sector cache. // WriteFatEntry() marks modified FAT cache slots dirty instead of // flushing immediately. FlushFatCache() (called from Fat32Close, // Fat32Truncate, and Fat32Delete) writes each dirty slot once for // every FAT copy, collapsing the 2x(read+write) per-entry cost of the // original into a single write per dirty sector per FAT copy. #include #include #include #include #include #include #include #include #include #include #include #include #include // --------------------------------------------------------------------------- // 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 MatchName83(const char *dirName, const char *dirExt, const char *want) { char buf[13]; int n = 0; for (int i = 0; i < 8 && dirName[i] != ' '; i++) buf[n++] = (char)(dirName[i] >= 'A' && dirName[i] <= 'Z' ? dirName[i]+32 : dirName[i]); if (dirExt[0] != ' ') { buf[n++] = '.'; for (int i = 0; i < 3 && dirExt[i] != ' '; i++) buf[n++] = (char)(dirExt[i] >= 'A' && dirExt[i] <= 'Z' ? dirExt[i]+32 : dirExt[i]); } buf[n] = '\0'; const char *p = buf; while (*want && *p) { char a = *p++, b = *want++; if (a >= 'A' && a <= 'Z') a += 32; if (b >= 'A' && b <= 'Z') b += 32; if (a != b) return 0; } return *want == '\0' && *p == '\0'; } static 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 void Make83Name(const char *src, char name8[8], char ext3[3]) { memset(name8, ' ', 8); memset(ext3, ' ', 3); // A leading dot is not a FAT 8.3 extension separator. The old version // turned ".doomrc" into an invalid empty-base short name with extension // "DOO", making .doomrc, .doom and .doo all alias the same entry. Treat // leading-dot names as a basename here; the create path will usually emit // a VFAT long-name entry for them anyway. const char *base = src; const char *dot = NULL; if (src && src[0] == '.' && src[1]) { base = src + 1; } else { for (const char *p = src; p && *p; p++) if (*p == '.') dot = p; } int ni = 0; const char *end = dot ? dot : base + strlen(base); for (const char *p = base; p < end && ni < 8; p++) { char c = *p; if (c >= 'a' && c <= 'z') c -= 32; name8[ni++] = c; } if (dot) { int ei = 0; for (const char *p = dot + 1; *p && ei < 3; p++) { char c = *p; if (c >= 'a' && c <= 'z') c -= 32; ext3[ei++] = c; } } } // --------------------------------------------------------------------------- // LFN assembly / matching // --------------------------------------------------------------------------- #define MAX_LFN 256 static 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 FatNameIsStructurallyShort83(const char *name) { if (!name || !name[0]) return 0; if (name[0] == '.') return 0; const char *dot = NULL; for (const char *p = name; *p; p++) { if (*p == '.') { if (dot) return 0; // multiple dots need LFN dot = p; } else if (!FatIsValidShortChar(*p)) { return 0; } } size_t baseLen = dot ? (size_t)(dot - name) : strlen(name); size_t extLen = dot ? strlen(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 FatNameNeedsLfnForCreate(const char *name) { if (!FatNameIsStructurallyShort83(name)) return 1; for (const char *p = name; *p; p++) if (FatIsLowerChar(*p)) 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 BuildAliasStemExt(const char *src, char stem[9], char ext3[3]) { memset(stem, 0, 9); memset(ext3, ' ', 3); const char *base = src; const char *dot = NULL; if (src[0] == '.' && src[1]) { // Hidden/Unix-style names like .doomrc become alias stem DOOMRC. base = src + 1; } else { for (const char *p = src; *p; p++) if (*p == '.') dot = p; } int slen = 0; const char *end = dot ? dot : src + strlen(src); for (const char *p = base; p < end && *p; p++) FatAliasAppendChar(stem, &slen, 8, *p); if (slen == 0) { memcpy(stem, "FILE", 4); slen = 4; } stem[slen] = '\0'; if (dot && dot[1]) { int ei = 0; for (const char *p = dot + 1; *p && ei < 3; p++) { char c = FatUpperChar(*p); 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 MakeUniqueShortAlias(FAT32_VOL *v, uint32_t dirCluster, const char *longName, char name8[8], char ext3[3]) { char stem[9], extCandidate[3]; BuildAliasStemExt(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 KSTATUS DeleteLegacyBrokenDotAlias(FAT32_VOL *v, uint32_t dirCluster, const char *longName) { if (!longName || longName[0] != '.' || !longName[1]) return STATUS_SUCCESS; char badName[8], badExt[3]; MakeLegacyBrokenDot83(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); } } static int LfnMatch(const char *lfn, const char *want) { while (*lfn && *want) { char a = *lfn++, b = *want++; if (a >= 'A' && a <= 'Z') a += 32; if (b >= 'A' && b <= 'Z') b += 32; if (a != b) return 0; } return *lfn == '\0' && *want == '\0'; } // --------------------------------------------------------------------------- // Directory search // --------------------------------------------------------------------------- static int SearchDir(FAT32_VOL *v, uint32_t dirCluster, const char *name, uint32_t *outCluster, uint32_t *outSize, uint8_t *outAttr, uint64_t *outDirentLba, uint32_t *outDirentOffset) { uint8_t *clusterBuf = (uint8_t *)MmAllocatePool(v->BytesPerCluster); if (!clusterBuf) return 0; char lfn[MAX_LFN]; int hasLfn = 0; memset(lfn, 0, sizeof(lfn)); char wantName83[8], wantExt83[3]; int allowShortMatch = FatNameIsStructurallyShort83(name); Make83Name(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 = LfnMatch(lfn, name); if (!match && allowShortMatch) match = MatchName83(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; } // --------------------------------------------------------------------------- // Path traversal // --------------------------------------------------------------------------- static KSTATUS Fat32LookupPath(FAT32_VOL *v, const char *path, uint32_t *outCluster, uint32_t *outSize, uint64_t *outDirentLba, uint32_t *outDirentOffset) { uint32_t cluster = v->RootCluster, size = 0; uint8_t attr = ATTR_DIRECTORY; if (outDirentLba) *outDirentLba = 0; if (outDirentOffset) *outDirentOffset = 0; char component[256]; const char *p = path; while (*p) { while (*p == '\\' || *p == '/') p++; if (!*p) break; int len = 0; while (*p && *p != '\\' && *p != '/' && len < 255) component[len++] = *p++; component[len] = '\0'; if (!len) break; if (!(attr & ATTR_DIRECTORY)) return STATUS_NOT_FOUND; uint32_t childCluster, childSize; uint8_t childAttr; uint64_t childLba = 0; uint32_t childOffset = 0; if (!SearchDir(v, cluster, component, &childCluster, &childSize, &childAttr, &childLba, &childOffset)) return STATUS_NOT_FOUND; cluster = childCluster; size = childSize; attr = childAttr; if (outDirentLba) *outDirentLba = childLba; if (outDirentOffset) *outDirentOffset = childOffset; } *outCluster = cluster; *outSize = size; return STATUS_SUCCESS; } static KSTATUS Traverse(FAT32_VOL *v, const char *path, uint32_t *outCluster, uint32_t *outSize) { return Fat32LookupPath(v, path, outCluster, outSize, NULL, NULL); } // --------------------------------------------------------------------------- // Path helpers for Fat32Create // --------------------------------------------------------------------------- static void SplitPath(const char *path, char parent[256], char name[256]) { const char *last = NULL; for (const char *p = path; *p; p++) if (*p == '\\' || *p == '/') last = p; if (!last) { parent[0] = '\0'; strncpy(name, path, 255); name[255] = '\0'; } else { size_t len = (size_t)(last - path); if (len > 255) len = 255; memcpy(parent, path, len); parent[len] = '\0'; strncpy(name, last+1, 255); name[255] = '\0'; } } // --------------------------------------------------------------------------- // Find a free DIRENT slot // --------------------------------------------------------------------------- static int FindFreeDirent(FAT32_VOL *v, uint32_t dirCluster, uint64_t *outLba, uint32_t *outOffset) { uint8_t *clusterBuf = (uint8_t *)MmAllocatePool(v->BytesPerCluster); if (!clusterBuf) return 0; uint32_t cluster = dirCluster, prevCluster = dirCluster; while (cluster >= 2 && cluster < FAT32_EOC) { uint64_t clusterLba = ClusterToLba(v, cluster); if (!KSUCCESS(ReadSectors(v, clusterLba, v->SectorsPerCluster, clusterBuf))) { MmFreePool(clusterBuf); return 0; } uint32_t entries = v->BytesPerCluster / sizeof(DIRENT); DIRENT *dir = (DIRENT *)clusterBuf; for (uint32_t i = 0; i < entries; i++) { uint8_t first = (uint8_t)dir[i].Name[0]; if (first == 0xE5 || first == 0x00) { uint64_t eb = (uint64_t)i * sizeof(DIRENT); *outLba = clusterLba + eb / v->BytesPerSector; *outOffset = (uint32_t)(eb % v->BytesPerSector); MmFreePool(clusterBuf); return 1; } } prevCluster = cluster; cluster = FatEntry(v, cluster); } uint32_t newCluster = AppendCluster(v, prevCluster); if (newCluster) { *outLba = ClusterToLba(v, newCluster); *outOffset = 0; MmFreePool(clusterBuf); return 1; } MmFreePool(clusterBuf); return 0; } // 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 // --------------------------------------------------------------------------- static KSTATUS Fat32Open(IO_FILESYSTEM *fs, const char *path, void **fileCtx) { FAT32_VOL *v = (FAT32_VOL *)fs->DriverCtx; uint32_t cluster, size; uint64_t direntLba; uint32_t direntOffset; KSTATUS s = Fat32LookupPath(v, path, &cluster, &size, &direntLba, &direntOffset); if (!KSUCCESS(s)) return s; FAT32_FILE *f = (FAT32_FILE *)MmAllocatePool(sizeof(FAT32_FILE)); if (!f) return STATUS_NO_MEMORY; f->StartCluster = cluster; f->FileSize = size; f->Vol = v; f->DirentLba = direntLba; f->DirentOffset = direntOffset; *fileCtx = f; return STATUS_SUCCESS; } static void Fat32Close(IO_FILESYSTEM *fs, void *fileCtx) { (void)fs; FAT32_FILE *f = (FAT32_FILE *)fileCtx; FlushFatCache(f->Vol); MmFreePool(fileCtx); } // --------------------------------------------------------------------------- // Read (fix #4: contiguous-run coalescing) // --------------------------------------------------------------------------- // We peek ahead in the FAT chain: if cluster N+1 == cluster N physically, // extend the current run instead of issuing a new ReadSectors call. // Run length is capped at MAX_RUN_SECTORS to stay within the AHCI bounce // buffer (128 sectors = 64 KB). // // FatEntry() calls in the peek loop are nearly free because they hit the // FAT sector cache (fix #1). #define MAX_RUN_SECTORS 128u static KSTATUS Fat32Read(IO_FILESYSTEM *fs, void *fileCtx, uint64_t offset, void *buf, size_t size, size_t *bytesRead) { (void)fs; if (!bytesRead) return STATUS_INVALID_PARAMETER; *bytesRead = 0; if (!fileCtx || !buf) return STATUS_INVALID_PARAMETER; FAT32_FILE *f = (FAT32_FILE *)fileCtx; FAT32_VOL *v = f->Vol; if (offset >= f->FileSize || size == 0) return STATUS_SUCCESS; if (offset + size > f->FileSize) size = (size_t)(f->FileSize - offset); size_t wanted = size; // Single allocation for the largest possible coalesced run. uint8_t *runBuf = (uint8_t *)MmAllocatePool(MAX_RUN_SECTORS * 512u); if (!runBuf) return STATUS_NO_MEMORY; size_t totalRead = 0; uint8_t *out = (uint8_t *)buf; KSTATUS readStatus = STATUS_SUCCESS; uint32_t skipClusters = (uint32_t)(offset / v->BytesPerCluster); uint32_t offsetInCluster = (uint32_t)(offset % v->BytesPerCluster); uint32_t cluster = f->StartCluster; for (uint32_t i = 0; i < skipClusters && cluster < FAT32_EOC; i++) cluster = FatEntry(v, cluster); while (size > 0 && cluster >= 2 && cluster < FAT32_EOC) { // Build the longest physically-contiguous run we can. uint32_t runStart = cluster; uint32_t runSectors = v->SectorsPerCluster; uint32_t next = FatEntry(v, cluster); while (next == cluster + 1 && next < FAT32_EOC && runSectors + v->SectorsPerCluster <= MAX_RUN_SECTORS) { cluster = next; next = FatEntry(v, cluster); runSectors += v->SectorsPerCluster; } KSTATUS rs = ReadSectors(v, ClusterToLba(v, runStart), runSectors, runBuf); if (!KSUCCESS(rs)) { readStatus = rs; break; } uint32_t runBytes = runSectors * 512u; size_t avail = runBytes - offsetInCluster; size_t copy = avail < size ? avail : size; memcpy(out, runBuf + offsetInCluster, copy); out += copy; totalRead += copy; size -= copy; offsetInCluster = 0; cluster = next; } if (totalRead != wanted) { // The directory entry says the file has more data than the cluster chain // or disk read actually supplied. Clear the caller's remainder so stale // memory cannot be interpreted as file contents. memset(out, 0, wanted - totalRead); } MmFreePool(runBuf); *bytesRead = totalRead; if (totalRead != wanted) return KSUCCESS(readStatus) ? STATUS_DEVICE_ERROR : readStatus; return STATUS_SUCCESS; } // --------------------------------------------------------------------------- // Write (fix #4: contiguous-run coalescing) // --------------------------------------------------------------------------- // Same strategy as read. Whole-cluster-aligned runs skip the read-modify- // write; partial head/tail clusters still require it. static KSTATUS Fat32Write(IO_FILESYSTEM *fs, void *fileCtx, uint64_t offset, const void *buf, size_t size, size_t *bytesWritten) { (void)fs; if (!size) { *bytesWritten = 0; return STATUS_SUCCESS; } FAT32_FILE *f = (FAT32_FILE *)fileCtx; FAT32_VOL *v = f->Vol; const uint8_t *src = (const uint8_t *)buf; // Ensure at least one cluster exists. if (f->StartCluster < 2) { uint32_t first = AllocCluster(v); if (!first) { *bytesWritten = 0; return STATUS_NO_MEMORY; } f->StartCluster = first; DirentUpdateFirstCluster(v, f->DirentLba, f->DirentOffset, first); } // Walk to the starting cluster, allocating as needed. uint32_t clusterIndex = (uint32_t)(offset / v->BytesPerCluster); uint32_t offsetInCluster = (uint32_t)(offset % v->BytesPerCluster); uint32_t cluster = f->StartCluster; for (uint32_t i = 0; i < clusterIndex; i++) { uint32_t next = FatEntry(v, cluster); if (next < 2 || next >= FAT32_EOC) { next = AppendCluster(v, cluster); if (!next) { *bytesWritten = 0; return STATUS_NO_MEMORY; } } cluster = next; } uint8_t *runBuf = (uint8_t *)MmAllocatePool(MAX_RUN_SECTORS * 512u); if (!runBuf) { *bytesWritten = 0; return STATUS_NO_MEMORY; } size_t written = 0, remaining = size; while (remaining > 0 && cluster >= 2) { // Build a contiguous run (only when cluster-aligned to avoid a // partial read-modify-write spanning multiple clusters). uint32_t runStart = cluster; uint32_t runSectors = v->SectorsPerCluster; uint32_t next = FatEntry(v, cluster); if (offsetInCluster == 0) { while (next == cluster + 1 && next < FAT32_EOC && runSectors + v->SectorsPerCluster <= MAX_RUN_SECTORS) { cluster = next; next = FatEntry(v, cluster); runSectors += v->SectorsPerCluster; } } uint32_t runBytes = runSectors * 512u; size_t avail = runBytes - offsetInCluster; size_t copy = avail < remaining ? avail : remaining; // Read existing data only for partial-cluster writes. bool partial = (offsetInCluster > 0) || (copy < (size_t)runBytes); if (partial) { if (!KSUCCESS(ReadSectors(v, ClusterToLba(v, runStart), runSectors, runBuf))) break; } memcpy(runBuf + offsetInCluster, src, copy); if (!KSUCCESS(WriteSectors(v, ClusterToLba(v, runStart), runSectors, runBuf))) break; src += copy; written += copy; remaining -= copy; offsetInCluster = 0; if (remaining > 0) { // Advance: if end-of-chain, append a new cluster. cluster = next; if (cluster < 2 || cluster >= FAT32_EOC) { // Find the actual last cluster in the run we just wrote. uint32_t last = runStart; for (uint32_t tmp = FatEntry(v, last); tmp >= 2 && tmp < FAT32_EOC; tmp = FatEntry(v, last)) last = tmp; cluster = AppendCluster(v, last); if (!cluster) break; } } } MmFreePool(runBuf); uint64_t newEnd = offset + written; if (newEnd > f->FileSize) { f->FileSize = (uint32_t)newEnd; DirentUpdateSize(v, f->DirentLba, f->DirentOffset, f->FileSize); } *bytesWritten = written; return (written == size) ? STATUS_SUCCESS : STATUS_DEVICE_ERROR; } // --------------------------------------------------------------------------- // Create // --------------------------------------------------------------------------- static KSTATUS Fat32Truncate(IO_FILESYSTEM *fs, void *fileCtx, uint64_t newSize); static KSTATUS Fat32Create(IO_FILESYSTEM *fs, const char *path, void **fileCtx) { FAT32_VOL *v = (FAT32_VOL *)fs->DriverCtx; char parentPath[256], filename[256]; SplitPath(path, parentPath, filename); if (!filename[0]) return STATUS_INVALID_PARAMETER; uint32_t parentCluster, parentSize; KSTATUS s = Traverse(v, parentPath, &parentCluster, &parentSize); if (!KSUCCESS(s)) return s; { uint32_t ec, es; uint8_t ea; uint64_t existingLba = 0; uint32_t existingOffset = 0; if (SearchDir(v, parentCluster, filename, &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 = FatNameNeedsLfnForCreate(filename); char shortName[8], shortExt[3]; if (useLfn) { s = DeleteLegacyBrokenDotAlias(v, parentCluster, filename); if (!KSUCCESS(s)) return s; if (!MakeUniqueShortAlias(v, parentCluster, filename, shortName, shortExt)) return STATUS_OBJECT_NAME_COLLISION; } else { Make83Name(filename, shortName, shortExt); } size_t nameLen = strlen(filename); 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], filename, 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 // --------------------------------------------------------------------------- static KSTATUS Fat32Truncate(IO_FILESYSTEM *fs, void *fileCtx, uint64_t newSize) { (void)fs; FAT32_FILE *f = (FAT32_FILE *)fileCtx; FAT32_VOL *v = f->Vol; if ((uint32_t)newSize == f->FileSize) return STATUS_SUCCESS; if (newSize == 0) { if (f->StartCluster >= 2) { FreeClusterChain(v, f->StartCluster); f->StartCluster = 0; DirentUpdateFirstCluster(v, f->DirentLba, f->DirentOffset, 0); } f->FileSize = 0; KSTATUS s = DirentUpdateSize(v, f->DirentLba, f->DirentOffset, 0); FlushFatCache(v); return s; } uint32_t newClusters = (uint32_t) ((newSize + v->BytesPerCluster - 1) / v->BytesPerCluster); if (newSize < f->FileSize) { if (f->StartCluster < 2) goto update_size; uint32_t cluster = f->StartCluster, lastGood = cluster; for (uint32_t i = 1; i < newClusters && cluster < FAT32_EOC; i++) { lastGood = cluster; cluster = FatEntry(v, cluster); } uint32_t tail = FatEntry(v, lastGood); WriteFatEntry(v, lastGood, FAT32_EOC_MARK); FreeClusterChain(v, tail); } else { uint32_t curClusters = f->FileSize ? (f->FileSize + v->BytesPerCluster - 1) / v->BytesPerCluster : 0; uint32_t lastCluster = 0; if (f->StartCluster < 2) { uint32_t first = AllocCluster(v); if (!first) return STATUS_NO_MEMORY; f->StartCluster = first; DirentUpdateFirstCluster(v, f->DirentLba, f->DirentOffset, first); lastCluster = first; curClusters = 1; } else { lastCluster = GetChainEnd(v, f->StartCluster, NULL); } for (uint32_t i = curClusters; i < newClusters; i++) { uint32_t next = AppendCluster(v, lastCluster); if (!next) return STATUS_NO_MEMORY; lastCluster = next; } } update_size: f->FileSize = (uint32_t)newSize; { KSTATUS s = DirentUpdateSize(v, f->DirentLba, f->DirentOffset, (uint32_t)newSize); FlushFatCache(v); return s; } } // --------------------------------------------------------------------------- // Delete // --------------------------------------------------------------------------- static KSTATUS Fat32Delete(IO_FILESYSTEM *fs, const char *path) { FAT32_VOL *v = (FAT32_VOL *)fs->DriverCtx; uint32_t cluster, size; uint64_t direntLba; uint32_t direntOffset; KSTATUS s = Fat32LookupPath(v, path, &cluster, &size, &direntLba, &direntOffset); if (!KSUCCESS(s)) return s; if (!direntLba) return STATUS_ACCESS_DENIED; uint8_t *sec = (uint8_t *)MmAllocatePool(v->BytesPerSector); if (!sec) return STATUS_NO_MEMORY; s = ReadSectors(v, direntLba, 1, sec); if (KSUCCESS(s)) { sec[direntOffset] = 0xE5; s = WriteSectors(v, direntLba, 1, sec); } MmFreePool(sec); if (!KSUCCESS(s)) return s; if (cluster >= 2) FreeClusterChain(v, cluster); FlushFatCache(v); return STATUS_SUCCESS; } static uint64_t Fat32GetSize(IO_FILESYSTEM *fs, void *fileCtx) { (void)fs; return ((FAT32_FILE *)fileCtx)->FileSize; } // --------------------------------------------------------------------------- // Directory enumeration (fix #2: cluster buffer cached in context) // --------------------------------------------------------------------------- // Previously: allocate + ReadSectors + return 1 entry + free, every call. // Now: buffer lives in FAT32_DIR_CTX; ReadSectors fires only on cluster // boundaries. typedef struct { FAT32_VOL *Vol; uint32_t DirCluster; uint32_t CurCluster; uint32_t LoadedCluster; // which cluster is currently in ClusterBuf uint32_t EntryIndex; char LfnBuf[MAX_LFN]; int HasLfn; uint8_t *ClusterBuf; // allocated once in OpenDir, freed in CloseDir } FAT32_DIR_CTX; static KSTATUS Fat32OpenDir(IO_FILESYSTEM *fs, const char *dirPath, void **dirCtx) { FAT32_VOL *v = (FAT32_VOL *)fs->DriverCtx; uint32_t cluster; if (!dirPath || dirPath[0] == '\0') { cluster = v->RootCluster; } else { uint32_t size; KSTATUS s = Traverse(v, dirPath, &cluster, &size); if (!KSUCCESS(s)) return s; } FAT32_DIR_CTX *ctx = (FAT32_DIR_CTX *)MmAllocatePool(sizeof(FAT32_DIR_CTX)); if (!ctx) return STATUS_NO_MEMORY; ctx->ClusterBuf = (uint8_t *)MmAllocatePool(v->BytesPerCluster); if (!ctx->ClusterBuf) { MmFreePool(ctx); return STATUS_NO_MEMORY; } ctx->Vol = v; ctx->DirCluster = cluster; ctx->CurCluster = cluster; ctx->LoadedCluster = (uint32_t)-1; ctx->EntryIndex = 0; ctx->HasLfn = 0; memset(ctx->LfnBuf, 0, sizeof(ctx->LfnBuf)); *dirCtx = ctx; return STATUS_SUCCESS; } static KSTATUS Fat32ReadDir(IO_FILESYSTEM *fs, void *dirCtx, KDIR_ENTRY *out) { (void)fs; FAT32_DIR_CTX *ctx = (FAT32_DIR_CTX *)dirCtx; FAT32_VOL *v = ctx->Vol; uint32_t epc = v->BytesPerCluster / sizeof(DIRENT); while (ctx->CurCluster >= 2 && ctx->CurCluster < FAT32_EOC) { // Load the cluster only when crossing a cluster boundary. if (ctx->CurCluster != ctx->LoadedCluster) { if (!KSUCCESS(ReadSectors(v, ClusterToLba(v, ctx->CurCluster), v->SectorsPerCluster, ctx->ClusterBuf))) break; ctx->LoadedCluster = ctx->CurCluster; } DIRENT *dir = (DIRENT *)ctx->ClusterBuf; while (ctx->EntryIndex < epc) { DIRENT *e = &dir[ctx->EntryIndex++]; if (e->Name[0] == 0x00) { ctx->CurCluster = FAT32_EOC; return STATUS_NO_MORE_FILES; } if ((uint8_t)e->Name[0] == 0xE5) { ctx->HasLfn = 0; memset(ctx->LfnBuf, 0, sizeof(ctx->LfnBuf)); continue; } if (e->Attr == ATTR_LFN) { LfnAppend(ctx->LfnBuf, (LFN_ENTRY *)e); ctx->HasLfn = 1; continue; } if (e->Attr & 0x08 && !(e->Attr & ATTR_DIRECTORY)) { ctx->HasLfn = 0; memset(ctx->LfnBuf, 0, sizeof(ctx->LfnBuf)); continue; } if (ctx->HasLfn && ctx->LfnBuf[0]) { size_t len = strlen(ctx->LfnBuf); if (len >= KDIR_NAME_MAX) len = KDIR_NAME_MAX - 1; memcpy(out->Name, ctx->LfnBuf, len); out->Name[len] = '\0'; } else { char name83[13]; int n = 0; for (int i = 0; i < 8 && e->Name[i] != ' '; i++) name83[n++] = (char)(e->Name[i] >= 'A' && e->Name[i] <= 'Z' ? e->Name[i]+32 : e->Name[i]); if (e->Ext[0] != ' ') { name83[n++] = '.'; for (int i = 0; i < 3 && e->Ext[i] != ' '; i++) name83[n++] = (char)(e->Ext[i] >= 'A' && e->Ext[i] <= 'Z' ? e->Ext[i]+32 : e->Ext[i]); } name83[n] = '\0'; size_t len = (size_t)n; if (len >= KDIR_NAME_MAX) len = KDIR_NAME_MAX - 1; memcpy(out->Name, name83, len); out->Name[len] = '\0'; } out->Attr = e->Attr; out->FileSize = e->FileSize; ctx->HasLfn = 0; memset(ctx->LfnBuf, 0, sizeof(ctx->LfnBuf)); return STATUS_SUCCESS; } ctx->CurCluster = FatEntry(v, ctx->CurCluster); ctx->EntryIndex = 0; ctx->HasLfn = 0; memset(ctx->LfnBuf, 0, sizeof(ctx->LfnBuf)); } return STATUS_NO_MORE_FILES; } static void Fat32CloseDir(IO_FILESYSTEM *fs, void *dirCtx) { (void)fs; FAT32_DIR_CTX *ctx = (FAT32_DIR_CTX *)dirCtx; MmFreePool(ctx->ClusterBuf); MmFreePool(ctx); } // --------------------------------------------------------------------------- // 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) { 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; // 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; 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->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 (!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; memset(&ctx->Irp, 0, sizeof(ctx->Irp)); ctx->Irp.DriveIndex = v->DriveIndex; ctx->Irp.Lba = ClusterToLba(v, runStart); ctx->Irp.SectorCount = runSectors; ctx->Irp.Buffer = ctx->RunBuf; ctx->Irp.CompletionFn = Fat32AsyncReadIrpComplete; ctx->Irp.CompletionCtx = ctx; KSTATUS s = IoSubmitIrp(&ctx->Irp); if (s != STATUS_PENDING) Fat32AsyncFinish(ctx, s, ctx->Done); } static 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 (!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; memset(&ctx->Irp, 0, sizeof(ctx->Irp)); ctx->Irp.DriveIndex = v->DriveIndex; ctx->Irp.Flags = IRP_FLAG_WRITE; ctx->Irp.Lba = ClusterToLba(v, runStart); ctx->Irp.SectorCount = runSectors; ctx->Irp.Buffer = ctx->RunBuf; ctx->Irp.CompletionFn = Fat32AsyncWriteIrpComplete; ctx->Irp.CompletionCtx = ctx; KSTATUS s = IoSubmitIrp(&ctx->Irp); if (s != STATUS_PENDING) Fat32AsyncFinish(ctx, s, ctx->Done); } static 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 void Fat32InitVtable(IO_FILESYSTEM* fs, FAT32_VOL* vol) { memset(fs, 0, sizeof(*fs)); fs->Open = Fat32Open; fs->Read = Fat32Read; fs->Write = Fat32Write; fs->ReadAsync = Fat32ReadAsync; fs->WriteAsync = Fat32WriteAsync; fs->Create = Fat32Create; fs->Delete = Fat32Delete; fs->Truncate = Fat32Truncate; fs->GetSize = Fat32GetSize; fs->Close = Fat32Close; fs->DriverCtx = vol; fs->OpenDir = Fat32OpenDir; fs->ReadDir = Fat32ReadDir; fs->CloseDir = Fat32CloseDir; } KSTATUS DriverEntry(const HAL_API *hal) { DbgPrint("FAT32: Init\n"); if (!hal) return STATUS_INVALID_PARAMETER; FatCacheInvalidate(); (void)hal; uint32_t mounted = 0; KSTATUS lastStatus = STATUS_NOT_SUPPORTED; for (uint8_t disk = 0; disk < FAT32_MAX_VOLUMES && mounted < FAT32_MAX_VOLUMES; disk++) { if (!IoDiskDevicePresent(disk)) continue; FAT32_PARTITION_CANDIDATE parts[FAT32_MAX_PARTITIONS_PER_DISK]; uint32_t partCount = Fat32EnumerateDiskVolumes(disk, parts, FAT32_MAX_PARTITIONS_PER_DISK); if (!partCount) { DbgPrint("FAT32: disk %u: no FAT32 volumes\n", disk); continue; } for (uint32_t part = 0; part < partCount && mounted < FAT32_MAX_VOLUMES; part++) { uint8_t volumeIndex = (uint8_t)mounted; 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))) { lastStatus = STATUS_DEVICE_ERROR; DbgPrint("FAT32: disk %u part %u: failed to read VBR\n", disk, part); continue; } BPB *bpb = (BPB *)sector; if (!IsFat32Vbr(v, v->PartitionLba)) { lastStatus = STATUS_NOT_SUPPORTED; 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)) { mounted++; 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 { lastStatus = status; DbgPrint("FAT32: disk %u part %u mount as drive %u failed status=%d.\n", disk, part, volumeIndex, status); } } } if (!mounted) { DbgPrint("FAT32: no mountable FAT32 volumes found.\n"); return lastStatus; } DbgPrint("FAT32: Init complete; mounted %u volume(s).\n", mounted); return STATUS_SUCCESS; }