// FAT synchronous file and directory operations. #include "fat_internal.h" // Open / Close. KSTATUS FatOpen(IO_VOLUME_PROVIDER* fs, SYS_STRING_VIEW path, void** fileCtx) { if (!fs || !fileCtx || !SysSvIsValid(path) || path.Length == 0) return NTLSTATUS_INVALID_PARAMETER; FAT_VOL* v = (FAT_VOL*)fs->Context; uint32_t cluster, size; uint64_t direntLba; uint32_t direntOffset; KSTATUS s = FatLookupPathV(v, path, &cluster, &size, NULL, &direntLba, &direntOffset); if (!KSUCCESS(s)) return s; FAT_FILE* f = (FAT_FILE*)MmAllocatePool(sizeof(FAT_FILE)); if (!f) return NTLSTATUS_NO_MEMORY; f->StartCluster = cluster; f->FileSize = size; f->Vol = v; f->DirentLba = direntLba; f->DirentOffset = direntOffset; FatFileInitChainCache(f); *fileCtx = f; return NTLSTATUS_SUCCESS; } // Flushes FAT metadata and the backing device write cache. KSTATUS FatFlush(IO_VOLUME_PROVIDER* fs, void* fileCtx) { (void)fs; FAT_FILE* f = (FAT_FILE*)fileCtx; if (!f || !f->Vol) return NTLSTATUS_INVALID_PARAMETER; FatFlushCache(f->Vol); return IoFlushDisk(f->Vol->DriveIndex); } // Releases the requested object. void FatClose(IO_VOLUME_PROVIDER* fs, void* fileCtx) { (void)fs; FAT_FILE* f = (FAT_FILE*)fileCtx; FatFlushCache(f->Vol); MmFreePool(fileCtx); } // Read (fix #4: contiguous-run coalescing) // Cluster-chain lookup is cached per open file, and data spans are allowed to // grow beyond the AHCI bounce size because the device layer now splits large // sector requests for the concrete controller. KSTATUS FatRead(IO_VOLUME_PROVIDER* fs, void* fileCtx, uint64_t offset, void* buf, size_t size, size_t* bytesRead) { (void)fs; if (!bytesRead) return NTLSTATUS_INVALID_PARAMETER; *bytesRead = 0; if (!fileCtx || !buf) return NTLSTATUS_INVALID_PARAMETER; FAT_FILE* f = (FAT_FILE*)fileCtx; FAT_VOL* v = f->Vol; if (offset >= f->FileSize || size == 0) return NTLSTATUS_SUCCESS; if (size > (size_t)(f->FileSize - offset)) size = (size_t)(f->FileSize - offset); size_t wanted = size; uint32_t initialOffsetInCluster = (uint32_t)(offset % v->BytesPerCluster); uint64_t initialBytesCovered = (uint64_t)initialOffsetInCluster + (uint64_t)size; uint32_t maxUsefulSectors = (uint32_t)((initialBytesCovered + 511u) / 512u); if (maxUsefulSectors < v->SectorsPerCluster) maxUsefulSectors = v->SectorsPerCluster; if (maxUsefulSectors > FAT_READ_RUN_MAX_SECTORS) maxUsefulSectors = FAT_READ_RUN_MAX_SECTORS; uint8_t* runBuf = (uint8_t*)MmAllocatePool((size_t)maxUsefulSectors * 512u); if (!runBuf) return NTLSTATUS_NO_MEMORY; size_t totalRead = 0; uint8_t* out = (uint8_t*)buf; KSTATUS readStatus = NTLSTATUS_SUCCESS; uint32_t logicalCluster = (uint32_t)(offset / v->BytesPerCluster); uint32_t offsetInCluster = (uint32_t)(offset % v->BytesPerCluster); uint32_t cluster = FatResolveFileCluster(f, logicalCluster); while (size > 0 && FatIsDataCluster(v, cluster)) { uint64_t bytesCovered = (uint64_t)offsetInCluster + (uint64_t)size; uint32_t usefulSectors = (uint32_t)((bytesCovered + 511u) / 512u); if (usefulSectors < v->SectorsPerCluster) usefulSectors = v->SectorsPerCluster; if (usefulSectors > FAT_READ_RUN_MAX_SECTORS) usefulSectors = FAT_READ_RUN_MAX_SECTORS; if (usefulSectors > maxUsefulSectors) usefulSectors = maxUsefulSectors; uint32_t runSectors = 0; uint32_t next = 0; if (!FatBuildReadRun(f, logicalCluster, cluster, usefulSectors, &runSectors, &next)) { readStatus = NTLSTATUS_DEVICE_ERROR; break; } KSTATUS rs = FatReadSectors(v, FatClusterToLba(v, cluster), runSectors, runBuf); if (!KSUCCESS(rs)) { readStatus = rs; break; } uint32_t runBytes = runSectors * 512u; size_t avail = runBytes - offsetInCluster; size_t copy = avail < size ? avail : size; uint32_t runClusters = runSectors / v->SectorsPerCluster; uint32_t merges = runClusters ? runClusters - 1u : 0; FatStatsAddReadRun(v, runSectors, merges); memcpy(out, runBuf + offsetInCluster, copy); out += copy; totalRead += copy; size -= copy; offsetInCluster = 0; logicalCluster += runClusters; cluster = next; } if (totalRead != wanted) { // The directory entry says the file has more data than the cluster chain // or disk read actually supplied. Clear the caller's remainder so stale // memory cannot be interpreted as file contents. memset(out, 0, wanted - totalRead); } MmFreePool(runBuf); *bytesRead = totalRead; if (totalRead != wanted) return KSUCCESS(readStatus) ? NTLSTATUS_DEVICE_ERROR : readStatus; return NTLSTATUS_SUCCESS; } // Write (fix #4: contiguous-run coalescing) // Same strategy as read. Whole-cluster-aligned runs skip the read-modify- // write; partial head/tail clusters still require it. KSTATUS FatWrite(IO_VOLUME_PROVIDER* fs, void* fileCtx, uint64_t offset, const void* buf, size_t size, size_t* bytesWritten) { (void)fs; if (!size) { *bytesWritten = 0; return NTLSTATUS_SUCCESS; } FAT_FILE* f = (FAT_FILE*)fileCtx; FAT_VOL* v = f->Vol; const uint8_t* src = (const uint8_t*)buf; FatFileInvalidateChainCache(f); // Ensure at least one cluster exists. if (f->StartCluster < 2) { uint32_t first = FatAllocCluster(v); if (!first) { *bytesWritten = 0; return NTLSTATUS_NO_MEMORY; } FatFlushCache(v); f->StartCluster = first; FatDirentUpdateFirstCluster(v, f->DirentLba, f->DirentOffset, first); } // Walk to the starting cluster, allocating as needed. uint32_t clusterIndex = (uint32_t)(offset / v->BytesPerCluster); uint32_t offsetInCluster = (uint32_t)(offset % v->BytesPerCluster); uint32_t cluster = f->StartCluster; for (uint32_t i = 0; i < clusterIndex; i++) { uint32_t next = FatEntry(v, cluster); if (!FatIsDataCluster(v, next)) { next = FatAppendCluster(v, cluster); if (!next) { *bytesWritten = 0; return NTLSTATUS_NO_MEMORY; } } cluster = next; } uint8_t* runBuf = (uint8_t*)MmAllocatePool(FAT_WRITE_RUN_MAX_SECTORS * 512u); if (!runBuf) { *bytesWritten = 0; return NTLSTATUS_NO_MEMORY; } size_t written = 0, remaining = size; while (remaining > 0 && cluster >= 2) { // Build a contiguous run (only when cluster-aligned to avoid a // partial read-modify-write spanning multiple clusters). uint32_t runStart = cluster; uint32_t runSectors = v->SectorsPerCluster; uint32_t next = FatEntry(v, cluster); if (offsetInCluster == 0) { while (next == cluster + 1 && FatIsDataCluster(v, next) && runSectors + v->SectorsPerCluster <= FAT_WRITE_RUN_MAX_SECTORS) { cluster = next; next = FatEntry(v, cluster); runSectors += v->SectorsPerCluster; } } uint32_t runBytes = runSectors * 512u; size_t avail = runBytes - offsetInCluster; size_t copy = avail < remaining ? avail : remaining; // Read existing data only for partial-cluster writes. bool partial = (offsetInCluster > 0) || (copy < (size_t)runBytes); if (partial) { if (!KSUCCESS(FatReadSectors(v, FatClusterToLba(v, runStart), runSectors, runBuf))) break; } memcpy(runBuf + offsetInCluster, src, copy); if (!KSUCCESS(FatWriteSectors(v, FatClusterToLba(v, runStart), runSectors, runBuf))) break; src += copy; written += copy; remaining -= copy; offsetInCluster = 0; if (remaining > 0) { // Advance: if end-of-chain, append a new cluster. cluster = next; if (!FatIsDataCluster(v, cluster)) { // Find the actual last cluster in the run we just wrote. uint32_t last = runStart; for (uint32_t tmp = FatEntry(v, last); FatIsDataCluster(v, tmp); tmp = FatEntry(v, last)) last = tmp; cluster = FatAppendCluster(v, last); if (!cluster) break; } } } MmFreePool(runBuf); uint64_t newEnd = offset + written; if (written > 0) { FAT_DIRENT_UPDATE u; memset(&u, 0, sizeof(u)); FatQueryEncodedTime(&u.Timestamp); u.Flags = FAT_DIRENT_UPDATE_WRITE_TIME; if (newEnd > f->FileSize) { f->FileSize = (uint32_t)newEnd; u.Flags |= FAT_DIRENT_UPDATE_SIZE; u.Size = f->FileSize; } KSTATUS ms = FatDirentUpdateMetadata(v, f->DirentLba, f->DirentOffset, &u); if (KSUCCESS(ms)) FatPathCacheInvalidate(v); } *bytesWritten = written; return (written == size) ? NTLSTATUS_SUCCESS : NTLSTATUS_DEVICE_ERROR; } // Create. // Creates the requested file and fails if the name already exists. KSTATUS FatCreate(IO_VOLUME_PROVIDER* fs, SYS_STRING_VIEW path, void** fileCtx) { if (!fs || !fileCtx || !SysSvIsValid(path) || path.Length == 0) return NTLSTATUS_INVALID_PARAMETER; FAT_VOL* v = (FAT_VOL*)fs->Context; uint64_t direntLba = 0; uint32_t direntOffset = 0; KSTATUS s = FatCreateDirent(v, path, ATTR_ARCHIVE, 0, 0, &direntLba, &direntOffset); if (!KSUCCESS(s)) return s; FAT_FILE* f = (FAT_FILE*)MmAllocatePool(sizeof(FAT_FILE)); if (!f) return NTLSTATUS_NO_MEMORY; f->StartCluster = 0; f->FileSize = 0; f->Vol = v; f->DirentLba = direntLba; f->DirentOffset = direntOffset; FatFileInitChainCache(f); *fileCtx = f; return NTLSTATUS_SUCCESS; } // Creates a directory and initializes its dot entries. KSTATUS FatCreateDir(IO_VOLUME_PROVIDER* fs, SYS_STRING_VIEW path) { if (!fs || !SysSvIsValid(path) || path.Length == 0) return NTLSTATUS_INVALID_PARAMETER; FAT_VOL* v = (FAT_VOL*)fs->Context; uint32_t parentCluster = 0; SYS_STRING_VIEW filenameView; KSTATUS s = FatResolveParentForMutation(v, path, &parentCluster, &filenameView); if (!KSUCCESS(s)) return s; (void)filenameView; uint32_t dirCluster = FatAllocCluster(v); if (!dirCluster) return NTLSTATUS_NO_MEMORY; s = FatInitializeDirectoryCluster(v, dirCluster, parentCluster); if (!KSUCCESS(s)) { FatFreeClusterChain(v, dirCluster); FatFlushCache(v); return s; } FatFlushCache(v); s = FatCreateDirent(v, path, ATTR_DIRECTORY, dirCluster, 0, NULL, NULL); if (!KSUCCESS(s)) { FatFreeClusterChain(v, dirCluster); FatFlushCache(v); return s; } FatFlushCache(v); return NTLSTATUS_SUCCESS; } // Removes an empty directory. KSTATUS FatRemoveDir(IO_VOLUME_PROVIDER* fs, SYS_STRING_VIEW path) { if (!fs || !SysSvIsValid(path) || path.Length == 0) return NTLSTATUS_INVALID_PARAMETER; FAT_VOL* v = (FAT_VOL*)fs->Context; uint32_t cluster = 0; uint32_t size = 0; uint8_t attr = 0; uint64_t direntLba = 0; uint32_t direntOffset = 0; KSTATUS s = FatLookupPathV(v, path, &cluster, &size, &attr, &direntLba, &direntOffset); if (!KSUCCESS(s)) return s; if (!direntLba) return NTLSTATUS_ACCESS_DENIED; if (!(attr & ATTR_DIRECTORY)) return NTLSTATUS_ACCESS_DENIED; int empty = 0; s = FatDirectoryIsEmpty(v, cluster, &empty); if (!KSUCCESS(s)) return s; if (!empty) return NTLSTATUS_ACCESS_DENIED; uint32_t parentCluster = 0; SYS_STRING_VIEW filenameView; s = FatResolveParentForMutation(v, path, &parentCluster, &filenameView); if (!KSUCCESS(s)) return s; s = FatDeleteDirentRunV(v, parentCluster, filenameView); if (!KSUCCESS(s)) return s; if (cluster >= 2) FatFreeClusterChain(v, cluster); FatFlushCache(v); FatPathCacheInvalidate(v); return NTLSTATUS_SUCCESS; } // Truncate. KSTATUS FatTruncate(IO_VOLUME_PROVIDER* fs, void* fileCtx, uint64_t newSize, uint32_t fileFlags) { (void)fs; FAT_FILE* f = (FAT_FILE*)fileCtx; FAT_VOL* v = f->Vol; FatFileInvalidateChainCache(f); int zeroNewClusters = (fileFlags & IO_FILE_FLAG_SKIP_ZERO_EXTEND) ? 0 : 1; if ((uint32_t)newSize == f->FileSize) { FAT_DIRENT_UPDATE u; memset(&u, 0, sizeof(u)); FatQueryEncodedTime(&u.Timestamp); u.Flags = FAT_DIRENT_UPDATE_WRITE_TIME; KSTATUS s = FatDirentUpdateMetadata(v, f->DirentLba, f->DirentOffset, &u); if (KSUCCESS(s)) FatPathCacheInvalidate(v); return s; } if (newSize == 0) { if (f->StartCluster >= 2) { FatFreeClusterChain(v, f->StartCluster); f->StartCluster = 0; FatDirentUpdateFirstCluster(v, f->DirentLba, f->DirentOffset, 0); } f->FileSize = 0; KSTATUS s = FatDirentUpdateSize(v, f->DirentLba, f->DirentOffset, 0); FatFlushCache(v); if (KSUCCESS(s)) FatPathCacheInvalidate(v); return s; } uint32_t newClusters = (uint32_t) ((newSize + v->BytesPerCluster - 1) / v->BytesPerCluster); if (newSize < f->FileSize) { if (f->StartCluster < 2) goto update_size; uint32_t cluster = f->StartCluster, lastGood = cluster; for (uint32_t i = 1; i < newClusters && FatIsDataCluster(v, cluster); i++) { lastGood = cluster; cluster = FatEntry(v, cluster); } uint32_t tail = FatEntry(v, lastGood); FatWriteEntry(v, lastGood, FatEocMark(v)); FatFreeClusterChain(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 = FatAllocClusterEx(v, zeroNewClusters); if (!first) return NTLSTATUS_NO_MEMORY; f->StartCluster = first; FatDirentUpdateFirstCluster(v, f->DirentLba, f->DirentOffset, first); lastCluster = first; curClusters = 1; } else { lastCluster = FatGetChainEnd(v, f->StartCluster, NULL); } for (uint32_t i = curClusters; i < newClusters; i++) { uint32_t next = FatAppendClusterEx(v, lastCluster, zeroNewClusters); if (!next) return NTLSTATUS_NO_MEMORY; lastCluster = next; } } update_size: f->FileSize = (uint32_t)newSize; { KSTATUS s = FatDirentUpdateSize(v, f->DirentLba, f->DirentOffset, (uint32_t)newSize); FatFlushCache(v); if (KSUCCESS(s)) FatPathCacheInvalidate(v); return s; } } // Delete. KSTATUS FatDelete(IO_VOLUME_PROVIDER* fs, SYS_STRING_VIEW path) { if (!fs || !SysSvIsValid(path) || path.Length == 0) return NTLSTATUS_INVALID_PARAMETER; FAT_VOL* v = (FAT_VOL*)fs->Context; uint32_t cluster, size; uint64_t direntLba; uint32_t direntOffset; KSTATUS s = FatLookupPathV(v, path, &cluster, &size, NULL, &direntLba, &direntOffset); if (!KSUCCESS(s)) return s; if (!direntLba) return NTLSTATUS_ACCESS_DENIED; uint32_t parentCluster = 0; SYS_STRING_VIEW filenameView; s = FatResolveParentForMutation(v, path, &parentCluster, &filenameView); if (!KSUCCESS(s)) return s; // Delete the complete entry run so long-name metadata cannot survive the // short entry and keep writable vvfat mirrors visibly present. s = FatDeleteDirentRunV(v, parentCluster, filenameView); if (!KSUCCESS(s)) return s; if (cluster >= 2) FatFreeClusterChain(v, cluster); FatFlushCache(v); FatPathCacheInvalidate(v); return NTLSTATUS_SUCCESS; } // Returns size. uint64_t FatGetSize(IO_VOLUME_PROVIDER* fs, void* fileCtx) { (void)fs; return ((FAT_FILE*)fileCtx)->FileSize; } // Converts FAT attributes to the public file type. static uint32_t FatFileTypeFromAttr(uint8_t attr) { return (attr & ATTR_DIRECTORY) ? SYS_FILE_TYPE_DIRECTORY : SYS_FILE_TYPE_REGULAR; } // Fills public metadata from one FAT directory entry. static void FatFillStatFromDirent(FAT_VOL* v, const DIRENT* d, uint64_t direntLba, uint32_t direntOffset, SYS_FILE_STAT* out) { memset(out, 0, sizeof(*out)); out->Size = sizeof(*out); out->Type = FatFileTypeFromAttr(d->Attr); out->Attributes = d->Attr; out->LinkCount = 1; out->FileSize = d->FileSize; uint32_t firstCluster = FatDirentFirstCluster(v, d); out->FileId = direntLba ^ ((uint64_t)direntOffset << 32) ^ ((uint64_t)firstCluster << 1); FatDecodeDateTime(d->CrtDate, d->CrtTime, d->CrtTimeTenth, &out->CreationTime); FatDecodeAccessDate(d->LastAccDate, &out->LastAccessTime); FatDecodeDateTime(d->WrtDate, d->WrtTime, 0, &out->LastWriteTime); out->ChangeTime = out->LastWriteTime; } // Fills public metadata for the filesystem root. static void FatFillRootStat(FAT_VOL* v, SYS_FILE_STAT* out) { memset(out, 0, sizeof(*out)); out->Size = sizeof(*out); out->Type = SYS_FILE_TYPE_DIRECTORY; out->Attributes = SYS_FILE_ATTR_DIRECTORY; out->LinkCount = 1; out->FileSize = 0; out->FileId = ((uint64_t)v->RootCluster << 1) ^ 0x524f4f54ULL; } // Queries metadata for an open FAT file. KSTATUS FatQueryInfo(IO_VOLUME_PROVIDER* fs, void* fileCtx, SYS_FILE_STAT* out) { (void)fs; if (!fileCtx || !out) return NTLSTATUS_INVALID_PARAMETER; FAT_FILE* f = (FAT_FILE*)fileCtx; FAT_VOL* v = f->Vol; if (!v) return NTLSTATUS_INVALID_PARAMETER; if (f->DirentLba == 0) { if (f->StartCluster == v->RootCluster && f->FileSize == 0) { FatFillRootStat(v, out); } else { memset(out, 0, sizeof(*out)); out->Size = sizeof(*out); out->Type = SYS_FILE_TYPE_REGULAR; out->Attributes = SYS_FILE_ATTR_ARCHIVE; out->LinkCount = 1; out->FileSize = f->FileSize; out->FileId = ((uint64_t)f->StartCluster << 1); } return NTLSTATUS_SUCCESS; } uint8_t* sec = (uint8_t*)MmAllocatePool(v->BytesPerSector); if (!sec) return NTLSTATUS_NO_MEMORY; KSTATUS s = FatReadSectors(v, f->DirentLba, 1, sec); if (!KSUCCESS(s)) { MmFreePool(sec); return s; } DIRENT* d = (DIRENT*)(sec + f->DirentOffset); if ((uint8_t)d->Name[0] == 0x00 || (uint8_t)d->Name[0] == 0xE5) { MmFreePool(sec); return NTLSTATUS_NOT_FOUND; } FatFillStatFromDirent(v, d, f->DirentLba, f->DirentOffset, out); f->StartCluster = FatDirentFirstCluster(v, d); f->FileSize = d->FileSize; MmFreePool(sec); return NTLSTATUS_SUCCESS; } // Queries metadata for a FAT path. KSTATUS FatStat(IO_VOLUME_PROVIDER* fs, SYS_STRING_VIEW path, SYS_FILE_STAT* out) { if (!fs || !out || !SysSvIsValid(path)) return NTLSTATUS_INVALID_PARAMETER; FAT_VOL* v = (FAT_VOL*)fs->Context; if (!v) return NTLSTATUS_INVALID_PARAMETER; uint32_t cluster = 0; uint32_t size = 0; uint8_t attr = ATTR_DIRECTORY; uint64_t direntLba = 0; uint32_t direntOffset = 0; KSTATUS s = FatLookupPathV(v, path, &cluster, &size, &attr, &direntLba, &direntOffset); if (!KSUCCESS(s)) return s; (void)cluster; if (direntLba == 0) { FatFillRootStat(v, out); return NTLSTATUS_SUCCESS; } uint8_t* sec = (uint8_t*)MmAllocatePool(v->BytesPerSector); if (!sec) return NTLSTATUS_NO_MEMORY; s = FatReadSectors(v, direntLba, 1, sec); if (!KSUCCESS(s)) { MmFreePool(sec); return s; } DIRENT* d = (DIRENT*)(sec + direntOffset); if ((uint8_t)d->Name[0] == 0x00 || (uint8_t)d->Name[0] == 0xE5) { MmFreePool(sec); return NTLSTATUS_NOT_FOUND; } FatFillStatFromDirent(v, d, direntLba, direntOffset, out); out->Type = FatFileTypeFromAttr(attr); out->FileSize = size; MmFreePool(sec); return NTLSTATUS_SUCCESS; } // Mixes FAT directory metadata into a per-file content generation. static uint64_t FatDirentGeneration(FAT_VOL* v, const DIRENT* d) { if (!d) return 0; uint32_t firstCluster = FatDirentFirstCluster(v, d); uint64_t generation = ((uint64_t)d->WrtDate << 48) | ((uint64_t)d->WrtTime << 32) | ((uint64_t)d->LastAccDate << 16) | (uint64_t)d->Attr; generation ^= ((uint64_t)d->CrtDate << 40); generation ^= ((uint64_t)d->CrtTime << 24); generation ^= ((uint64_t)d->CrtTimeTenth << 16); generation ^= ((uint64_t)firstCluster << 1); generation ^= ((uint64_t)d->FileSize << 17); return generation ? generation : 1; } // Returns a stable FAT file identity for cross-open image-section caches. KSTATUS FatQueryIdentity(IO_VOLUME_PROVIDER* fs, void* fileCtx, IO_FILE_IDENTITY* out) { (void)fs; if (!fileCtx || !out) return NTLSTATUS_INVALID_PARAMETER; FAT_FILE* f = (FAT_FILE*)fileCtx; FAT_VOL* v = f->Vol; if (!v) return NTLSTATUS_INVALID_PARAMETER; uint32_t startCluster = f->StartCluster; uint32_t fileSize = f->FileSize; uint64_t generation = ((uint64_t)startCluster << 1) ^ ((uint64_t)fileSize << 17); if (f->DirentLba != 0) { uint8_t* sec = (uint8_t*)MmAllocatePool(v->BytesPerSector); if (!sec) return NTLSTATUS_NO_MEMORY; KSTATUS s = FatReadSectors(v, f->DirentLba, 1, sec); if (!KSUCCESS(s)) { MmFreePool(sec); return s; } DIRENT* d = (DIRENT*)(sec + f->DirentOffset); if ((uint8_t)d->Name[0] == 0x00 || (uint8_t)d->Name[0] == 0xE5) { MmFreePool(sec); return NTLSTATUS_NOT_FOUND; } startCluster = FatDirentFirstCluster(v, d); fileSize = d->FileSize; generation = FatDirentGeneration(v, d); MmFreePool(sec); } out->FileId = f->DirentLba ^ ((uint64_t)f->DirentOffset << 32) ^ ((uint64_t)startCluster << 1); out->Generation = generation ? generation : 1; out->Size = fileSize; return NTLSTATUS_SUCCESS; } // Directory enumeration (fix #2: cluster buffer cached in context) // Previously: allocate + FatReadSectors + return 1 entry + free, every call. // Now: buffer lives in FAT_DIR_CTX; FatReadSectors fires only on cluster // boundaries. typedef struct { FAT_VOL* Vol; uint32_t DirCluster; uint32_t Cursor; uint32_t LoadedCursor; uint32_t LoadedSectors; uint64_t LoadedLba; uint32_t EntryIndex; char LfnBuf[FAT_MAX_LFN]; int HasLfn; uint8_t* ClusterBuf; // allocated once in OpenDir, freed in CloseDir } FAT_DIR_CTX; // Opens dir. KSTATUS FatOpenDir(IO_VOLUME_PROVIDER* fs, SYS_STRING_VIEW dirPath, void** dirCtx) { if (!fs || !dirCtx || !SysSvIsValid(dirPath)) return NTLSTATUS_INVALID_PARAMETER; FAT_VOL* v = (FAT_VOL*)fs->Context; uint32_t cluster; if (dirPath.Length == 0) { cluster = v->RootCluster; } else { uint32_t size; KSTATUS s = FatTraverseV(v, dirPath, &cluster, &size); if (!KSUCCESS(s)) return s; } uint32_t cursor; uint64_t lba; uint32_t sectors; if (!FatDirectoryFirstSegment(v, cluster, &cursor, &lba, §ors)) return NTLSTATUS_NOT_FOUND; FAT_DIR_CTX* ctx = (FAT_DIR_CTX*)MmAllocatePool(sizeof(FAT_DIR_CTX)); if (!ctx) return NTLSTATUS_NO_MEMORY; ctx->ClusterBuf = (uint8_t*)MmAllocatePool(v->BytesPerCluster); if (!ctx->ClusterBuf) { MmFreePool(ctx); return NTLSTATUS_NO_MEMORY; } ctx->Vol = v; ctx->DirCluster = cluster; ctx->Cursor = cursor; ctx->LoadedCursor = (uint32_t)-1; ctx->LoadedSectors = sectors; ctx->LoadedLba = lba; ctx->EntryIndex = 0; ctx->HasLfn = 0; memset(ctx->LfnBuf, 0, sizeof(ctx->LfnBuf)); *dirCtx = ctx; return NTLSTATUS_SUCCESS; } // Returns dir. KSTATUS FatReadDir(IO_VOLUME_PROVIDER* fs, void* dirCtx, KDIR_ENTRY* out) { (void)fs; FAT_DIR_CTX* ctx = (FAT_DIR_CTX*)dirCtx; FAT_VOL* v = ctx->Vol; while (true) { if (ctx->Cursor != ctx->LoadedCursor) { if (!KSUCCESS(FatReadSectors(v, ctx->LoadedLba, ctx->LoadedSectors, ctx->ClusterBuf))) return NTLSTATUS_NO_MORE_FILES; ctx->LoadedCursor = ctx->Cursor; } uint32_t epc = (ctx->LoadedSectors * v->BytesPerSector) / sizeof(DIRENT); DIRENT* dir = (DIRENT*)ctx->ClusterBuf; while (ctx->EntryIndex < epc) { DIRENT* e = &dir[ctx->EntryIndex++]; if (e->Name[0] == 0x00) return NTLSTATUS_NO_MORE_FILES; if ((uint8_t)e->Name[0] == 0xE5) { ctx->HasLfn = 0; memset(ctx->LfnBuf, 0, sizeof(ctx->LfnBuf)); continue; } if (e->Attr == ATTR_LFN) { FatLfnAppend(ctx->LfnBuf, (LFN_ENTRY*)e); ctx->HasLfn = 1; continue; } if (e->Attr & 0x08 && !(e->Attr & ATTR_DIRECTORY)) { ctx->HasLfn = 0; memset(ctx->LfnBuf, 0, sizeof(ctx->LfnBuf)); continue; } memset(out, 0, sizeof(*out)); if (ctx->HasLfn && ctx->LfnBuf[0]) { size_t len = strlen(ctx->LfnBuf); if (len >= KDIR_NAME_MAX) len = KDIR_NAME_MAX - 1; memcpy(out->Name, ctx->LfnBuf, len); out->Name[len] = '\0'; } else { char name83[13]; int n = 0; for (int i = 0; i < 8 && e->Name[i] != ' '; i++) name83[n++] = (char)(e->Name[i] >= 'A' && e->Name[i] <= 'Z' ? e->Name[i]+32 : e->Name[i]); if (e->Ext[0] != ' ') { name83[n++] = '.'; for (int i = 0; i < 3 && e->Ext[i] != ' '; i++) name83[n++] = (char)(e->Ext[i] >= 'A' && e->Ext[i] <= 'Z' ? e->Ext[i]+32 : e->Ext[i]); } name83[n] = '\0'; size_t len = (size_t)n; if (len >= KDIR_NAME_MAX) len = KDIR_NAME_MAX - 1; memcpy(out->Name, name83, len); out->Name[len] = '\0'; } out->Attr = e->Attr; out->FileSize = e->FileSize; FatDecodeDateTime(e->CrtDate, e->CrtTime, e->CrtTimeTenth, &out->CreateTime); FatDecodeAccessDate(e->LastAccDate, &out->LastAccessTime); FatDecodeDateTime(e->WrtDate, e->WrtTime, 0, &out->LastWriteTime); ctx->HasLfn = 0; memset(ctx->LfnBuf, 0, sizeof(ctx->LfnBuf)); return NTLSTATUS_SUCCESS; } if (!FatDirectoryNextSegment(v, ctx->DirCluster, &ctx->Cursor, &ctx->LoadedLba, &ctx->LoadedSectors)) return NTLSTATUS_NO_MORE_FILES; ctx->LoadedCursor = (uint32_t)-1; ctx->EntryIndex = 0; } } // Releases dir. void FatCloseDir(IO_VOLUME_PROVIDER* fs, void* dirCtx) { (void)fs; FAT_DIR_CTX* ctx = (FAT_DIR_CTX*)dirCtx; MmFreePool(ctx->ClusterBuf); MmFreePool(ctx); }