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

1271 lines
46 KiB
C

// FAT name handling, path traversal, and directory mutation.
#include <stdbool.h>
#include "fat_internal.h"
// 8.3 name matching / assembly.
static bool 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 bool FatIsLowerChar(char c);
static bool FatIsValidShortChar(char c);
// Implements the make83 Name V operation.
static void Make83NameV(SYS_STRING_VIEW src, char name8[8], char ext3[3]) {
memset(name8, ' ', 8);
memset(ext3, ' ', 3);
if (!SysSvIsValid(src) || src.Length == 0) return;
size_t baseStart = 0;
size_t dot = (size_t)-1;
if (src.Data[0] == '.' && src.Length > 1) {
baseStart = 1;
} else {
for (size_t i = 0; i < src.Length; i++)
if (src.Data[i] == '.') dot = i;
}
size_t end = (dot != (size_t)-1) ? dot : src.Length;
int ni = 0;
for (size_t i = baseStart; i < end && ni < 8; i++) {
char c = FatUpperChar(src.Data[i]);
name8[ni++] = c;
}
if (dot != (size_t)-1) {
int ei = 0;
for (size_t i = dot + 1; i < src.Length && ei < 3; i++) {
char c = FatUpperChar(src.Data[i]);
ext3[ei++] = c;
}
}
}
// LFN assembly / matching.
// Implements the fat Upper Char operation.
static char FatUpperChar(char c) {
return (c >= 'a' && c <= 'z') ? (char)(c - 32) : c;
}
// Implements the fat Is Lower Char operation.
static bool FatIsLowerChar(char c) {
return c >= 'a' && c <= 'z';
}
// Implements the fat Is Valid Short Char operation.
static bool FatIsValidShortChar(char c) {
c = FatUpperChar(c);
if (c >= 'A' && c <= 'Z') return true;
if (c >= '0' && c <= '9') return true;
switch (c) {
case '$': case '%': case '\'': case '-': case '_': case '@':
case '~': case '`': case '!': case '(': case ')': case '{':
case '}': case '^': case '#': case '&':
return true;
default:
return false;
}
}
// 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 bool FatNameIsStructurallyShort83V(SYS_STRING_VIEW name) {
if (!SysSvIsValid(name) || name.Length == 0) return false;
if (name.Data[0] == '.') return false;
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 false;
dot = i;
} else if (!FatIsValidShortChar(c)) {
return false;
}
}
size_t baseLen = (dot != (size_t)-1) ? dot : name.Length;
size_t extLen = (dot != (size_t)-1) ? (name.Length - dot - 1) : 0;
return baseLen >= 1 && baseLen <= 8 && extLen <= 3;
}
// Implements the fat Component Has Lower V operation.
static bool FatComponentHasLowerV(SYS_STRING_VIEW name, size_t start, size_t end) {
for (size_t i = start; i < end; i++)
if (FatIsLowerChar(name.Data[i])) return true;
return false;
}
// Implements the fat Component Has Upper V operation.
static bool FatComponentHasUpperV(SYS_STRING_VIEW name, size_t start, size_t end) {
for (size_t i = start; i < end; i++)
if (name.Data[i] >= 'A' && name.Data[i] <= 'Z') return true;
return false;
}
static void FatSplitShortNameV(SYS_STRING_VIEW name, size_t* baseEnd,
size_t* extStart, size_t* extEnd) {
size_t dot = (size_t)-1;
for (size_t i = 0; i < name.Length; i++)
if (name.Data[i] == '.') dot = i;
if (dot == (size_t)-1) {
*baseEnd = name.Length;
*extStart = name.Length;
*extEnd = name.Length;
} else {
*baseEnd = dot;
*extStart = dot + 1u;
*extEnd = name.Length;
}
}
// Creation uses LFN only when the spelling cannot be represented by a short
// entry, or when mixed-case 8.3 spelling would be lost. Plain lowercase
// 8.3 names are represented with FAT's NT case bits, not VFAT LFN entries.
static bool FatNameNeedsLfnForCreateV(SYS_STRING_VIEW name) {
if (!FatNameIsStructurallyShort83V(name)) return true;
size_t baseEnd, extStart, extEnd;
FatSplitShortNameV(name, &baseEnd, &extStart, &extEnd);
bool baseLower = FatComponentHasLowerV(name, 0, baseEnd);
bool baseUpper = FatComponentHasUpperV(name, 0, baseEnd);
bool extLower = FatComponentHasLowerV(name, extStart, extEnd);
bool extUpper = FatComponentHasUpperV(name, extStart, extEnd);
if ((baseLower && baseUpper) || (extLower && extUpper)) return true;
return false;
}
// Implements the fat Short Name Case Flags V operation.
static uint8_t FatShortNameCaseFlagsV(SYS_STRING_VIEW name) {
if (!FatNameIsStructurallyShort83V(name)) return 0;
size_t baseEnd, extStart, extEnd;
FatSplitShortNameV(name, &baseEnd, &extStart, &extEnd);
uint8_t flags = 0;
if (FatComponentHasLowerV(name, 0, baseEnd) &&
!FatComponentHasUpperV(name, 0, baseEnd))
flags |= FAT_NTRES_BASE_LOWER;
if (extStart < extEnd &&
FatComponentHasLowerV(name, extStart, extEnd) &&
!FatComponentHasUpperV(name, extStart, extEnd))
flags |= FAT_NTRES_EXT_LOWER;
return flags;
}
// Implements the lfn Checksum operation.
static uint8_t LfnChecksum(const char name8[8], const char ext3[3]) {
uint8_t sum = 0;
for (int i = 0; i < 11; i++) {
uint8_t c = (uint8_t)(i < 8 ? name8[i] : ext3[i - 8]);
sum = (uint8_t)(((sum & 1) ? 0x80 : 0) + (sum >> 1) + c);
}
return sum;
}
// Implements the lfn Set Char operation.
static void LfnSetChar(LFN_ENTRY* e, int idx, uint16_t value) {
if (idx < 5) e->Name1[idx] = value;
else if (idx < 11) e->Name2[idx - 5] = value;
else e->Name3[idx - 11] = value;
}
static void BuildLfnEntry(LFN_ENTRY* e, const char* name, size_t nameLen,
uint32_t ordinal, uint32_t lfnCount,
uint8_t checksum) {
memset(e, 0, sizeof(*e));
e->Order = (uint8_t)ordinal;
if (ordinal == lfnCount) e->Order |= 0x40;
e->Attr = ATTR_LFN;
e->Type = 0;
e->Checksum = checksum;
e->FirstCluster = 0;
size_t start = (size_t)(ordinal - 1) * 13u;
for (int i = 0; i < 13; i++) {
size_t pos = start + (size_t)i;
uint16_t ch;
if (pos < nameLen) ch = (uint8_t)name[pos];
else if (pos == nameLen) ch = 0x0000;
else ch = 0xFFFF;
LfnSetChar(e, i, ch);
}
}
// Creates legacy Broken Dot83.
static void MakeLegacyBrokenDot83(const char* src, char name8[8], char ext3[3]) {
// This exactly models the pre-fix bug for ".doomrc": empty name + "DOO".
memset(name8, ' ', 8);
memset(ext3, ' ', 3);
const char* dot = NULL;
for (const char* p = src; *p; p++) if (*p == '.') dot = p;
if (!dot) return;
int ei = 0;
for (const char* p = dot + 1; *p && ei < 3; p++)
ext3[ei++] = FatUpperChar(*p);
}
static bool ShortNameExistsRaw(FAT_VOL* v, uint32_t dirCluster,
const char name8[8], const char ext3[3]) {
uint8_t* clusterBuf = (uint8_t*)MmAllocatePool(v->BytesPerCluster);
if (!clusterBuf) return true; // fail closed; caller should pick another alias
uint32_t cursor; uint64_t lba; uint32_t sectors;
if (!FatDirectoryFirstSegment(v, dirCluster, &cursor, &lba, &sectors)) {
MmFreePool(clusterBuf);
return false;
}
do {
if (!KSUCCESS(FatReadSectors(v, lba, sectors, clusterBuf))) break;
uint32_t entries = (sectors * v->BytesPerSector) / sizeof(DIRENT);
DIRENT* dir = (DIRENT*)clusterBuf;
for (uint32_t i = 0; i < entries; i++) {
if (dir[i].Name[0] == 0x00) { MmFreePool(clusterBuf); return false; }
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 true;
}
}
} while (FatDirectoryNextSegment(v, dirCluster, &cursor, &lba, &sectors));
MmFreePool(clusterBuf);
return false;
}
// Implements the fat Alias Append Char operation.
static void FatAliasAppendChar(char* dst, int* len, int maxLen, char c) {
c = FatUpperChar(c);
if (!FatIsValidShortChar(c)) return;
if (*len < maxLen) dst[(*len)++] = c;
}
// Builds alias Stem Ext V.
static void BuildAliasStemExtV(SYS_STRING_VIEW src, char stem[9], char ext3[3]) {
memset(stem, 0, 9);
memset(ext3, ' ', 3);
if (!SysSvIsValid(src)) return;
size_t baseStart = 0;
size_t dot = (size_t)-1;
if (src.Length > 1 && src.Data[0] == '.') {
// Hidden/Unix-style names like .doomrc become alias stem DOOMRC.
baseStart = 1;
} else {
for (size_t i = 0; i < src.Length; i++)
if (src.Data[i] == '.') dot = i;
}
int slen = 0;
size_t end = (dot != (size_t)-1) ? dot : src.Length;
for (size_t i = baseStart; i < end; i++)
FatAliasAppendChar(stem, &slen, 8, src.Data[i]);
if (slen == 0) {
memcpy(stem, "FILE", 4);
slen = 4;
}
stem[slen] = '\0';
if (dot != (size_t)-1 && dot + 1 < src.Length) {
int ei = 0;
for (size_t i = dot + 1; i < src.Length && ei < 3; i++) {
char c = FatUpperChar(src.Data[i]);
if (!FatIsValidShortChar(c)) continue;
ext3[ei++] = c;
}
}
}
static void MakeAliasAttempt(const char stem[9], const char extIn[3],
uint32_t attempt, char name8[8], char ext3[3]) {
memset(name8, ' ', 8);
memcpy(ext3, extIn, 3);
char suffix[8];
int suffixLen = 0;
suffix[suffixLen++] = '~';
char digits[7];
int digitLen = 0;
uint32_t n = attempt;
do {
digits[digitLen++] = (char)('0' + (n % 10));
n /= 10;
} while (n && digitLen < (int)sizeof(digits));
for (int i = digitLen - 1; i >= 0; i--)
suffix[suffixLen++] = digits[i];
int stemLen = (int)strlen(stem);
int keep = 8 - suffixLen;
if (keep < 1) keep = 1;
if (stemLen < keep) keep = stemLen;
memcpy(name8, stem, keep);
memcpy(name8 + keep, suffix, suffixLen);
}
static bool MakeUniqueShortAliasV(FAT_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 true;
}
return false;
}
// Creates legacy Broken Dot83 V.
static void MakeLegacyBrokenDot83V(SYS_STRING_VIEW src, char name8[8], char ext3[3]) {
// This exactly models the pre-fix bug for ".doomrc": empty name + "DOO".
memset(name8, ' ', 8);
memset(ext3, ' ', 3);
if (!SysSvIsValid(src)) return;
size_t dot = (size_t)-1;
for (size_t i = 0; i < src.Length; i++) if (src.Data[i] == '.') dot = i;
if (dot == (size_t)-1) return;
int ei = 0;
for (size_t i = dot + 1; i < src.Length && ei < 3; i++)
ext3[ei++] = FatUpperChar(src.Data[i]);
}
static KSTATUS DeleteLegacyBrokenDotAliasV(FAT_VOL* v, uint32_t dirCluster,
SYS_STRING_VIEW longName) {
if (!SysSvIsValid(longName) || longName.Length < 2 || longName.Data[0] != '.') return NTLSTATUS_SUCCESS;
char badName[8], badExt[3];
MakeLegacyBrokenDot83V(longName, badName, badExt);
uint8_t* clusterBuf = (uint8_t*)MmAllocatePool(v->BytesPerCluster);
if (!clusterBuf) return NTLSTATUS_NO_MEMORY;
uint32_t cursor; uint64_t lba; uint32_t sectors;
if (!FatDirectoryFirstSegment(v, dirCluster, &cursor, &lba, &sectors)) {
MmFreePool(clusterBuf);
return NTLSTATUS_SUCCESS;
}
do {
KSTATUS s = FatReadSectors(v, lba, sectors, clusterBuf);
if (!KSUCCESS(s)) { MmFreePool(clusterBuf); return s; }
uint32_t entries = (sectors * v->BytesPerSector) / sizeof(DIRENT);
DIRENT* dir = (DIRENT*)clusterBuf;
for (uint32_t i = 0; i < entries; i++) {
if (dir[i].Name[0] == 0x00) { MmFreePool(clusterBuf); return NTLSTATUS_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 = FatWriteSectors(v, lba, sectors, clusterBuf);
MmFreePool(clusterBuf);
return s;
}
}
} while (FatDirectoryNextSegment(v, dirCluster, &cursor, &lba, &sectors));
MmFreePool(clusterBuf);
return NTLSTATUS_SUCCESS;
}
// Implements the lfn Append operation.
void FatLfnAppend(char* lfn, const LFN_ENTRY* e) {
int seq = (e->Order & 0x3F) - 1;
if (seq < 0 || seq >= (FAT_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 < FAT_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 bool FatIsPathSep(char c) {
return c == '\\' || c == '/';
}
// Tests whether a non-empty FAT path view points at plausible kernel-owned storage.
static bool FatStringViewDataIsPlausible(SYS_STRING_VIEW view) {
if (!SysSvIsValid(view)) return false;
if (view.Length == 0) return true;
uintptr_t data = (uintptr_t)view.Data;
return data >= 0x10000u;
}
// Implements the name View Is Valid operation.
static bool FatNameViewIsValid(SYS_STRING_VIEW name) {
if (!FatStringViewDataIsPlausible(name)) return false;
if (name.Length == 0 || name.Length >= FAT_MAX_LFN) return false;
for (size_t i = 0; i < name.Length; i++) {
char c = name.Data[i];
if (c == '\0' || FatIsPathSep(c)) return false;
}
return true;
}
// Implements the lfn Match V operation.
static bool LfnMatchV(const char* lfn, SYS_STRING_VIEW want) {
if (!lfn || !SysSvIsValid(want)) return false;
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 false;
i++;
}
return lfn[i] == '\0' && i == want.Length;
}
static bool 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 false;
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 false;
}
return true;
}
// Returns the path cache canonical byte used for hashes and comparisons.
static char FatPathCacheChar(char c) {
if (c == '/') return '\\';
if (c >= 'A' && c <= 'Z') return (char)(c + 32);
return c;
}
// Hashes a volume-relative path using the same case/separator rules as lookup.
static uint32_t FatPathCacheHash(SYS_STRING_VIEW path) {
uint32_t h = 2166136261u;
for (size_t i = 0; i < path.Length; i++) {
h ^= (uint8_t)FatPathCacheChar(path.Data[i]);
h *= 16777619u;
}
return h ? h : 1u;
}
// Tests whether the cached path names the same FAT path as the caller's view.
static bool FatPathCachePathEquals(const FAT_PATH_CACHE_ENTRY* e, SYS_STRING_VIEW path) {
if (!e || e->PathLength != path.Length) return false;
for (size_t i = 0; i < path.Length; i++) {
if (FatPathCacheChar(e->Path[i]) != FatPathCacheChar(path.Data[i]))
return false;
}
return true;
}
// Adds directory search counters without holding the lock during disk I/O.
static void FatPathCacheAddDirectoryStats(FAT_VOL* v, uint64_t clusters, uint64_t entries) {
if (!v) return;
uintptr_t flags = KeAcquireSpinlock(&v->PathCacheLock);
v->Stats.DirectorySearches++;
v->Stats.DirectoryBlocksRead += clusters;
v->Stats.DirectoryEntriesScanned += entries;
KeReleaseSpinlock(&v->PathCacheLock, flags);
}
// Looks up a positive path-cache entry for a full volume-relative path.
static bool FatPathCacheLookup(FAT_VOL* v, SYS_STRING_VIEW path,
uint32_t* outCluster, uint32_t* outSize,
uint8_t* outAttr, uint64_t* outDirentLba,
uint32_t* outDirentOffset) {
if (!v || path.Length == 0 || path.Length >= FAT_PATH_CACHE_MAX)
return false;
uint32_t hash = FatPathCacheHash(path);
uint32_t slot = hash % FAT_PATH_CACHE_SLOTS;
bool hit = false;
uintptr_t flags = KeAcquireSpinlock(&v->PathCacheLock);
v->Stats.PathLookups++;
FAT_PATH_CACHE_ENTRY* e = &v->PathCache[slot];
if (e->Valid && e->Generation == v->PathCacheGeneration &&
e->Hash == hash && FatPathCachePathEquals(e, path)) {
if (outCluster) *outCluster = e->Cluster;
if (outSize) *outSize = e->Size;
if (outAttr) *outAttr = e->Attr;
if (outDirentLba) *outDirentLba = e->DirentLba;
if (outDirentOffset) *outDirentOffset = e->DirentOffset;
v->Stats.PathCacheHits++;
hit = true;
} else {
v->Stats.PathCacheMisses++;
}
KeReleaseSpinlock(&v->PathCacheLock, flags);
return hit;
}
// Stores a positive path lookup in the volume-local path cache.
static void FatPathCacheStore(FAT_VOL* v, SYS_STRING_VIEW path,
uint32_t cluster, uint32_t size, uint8_t attr,
uint64_t direntLba, uint32_t direntOffset) {
if (!v || path.Length == 0 || path.Length >= FAT_PATH_CACHE_MAX)
return;
uint32_t hash = FatPathCacheHash(path);
uint32_t slot = hash % FAT_PATH_CACHE_SLOTS;
uintptr_t flags = KeAcquireSpinlock(&v->PathCacheLock);
FAT_PATH_CACHE_ENTRY* e = &v->PathCache[slot];
memset(e, 0, sizeof(*e));
e->Valid = true;
e->Generation = v->PathCacheGeneration;
e->Hash = hash;
e->PathLength = path.Length;
memcpy(e->Path, path.Data, path.Length);
e->Path[path.Length] = '\0';
e->Cluster = cluster;
e->Size = size;
e->Attr = attr;
e->DirentLba = direntLba;
e->DirentOffset = direntOffset;
v->Stats.PathCacheStores++;
KeReleaseSpinlock(&v->PathCacheLock, flags);
}
// Invalidates every positive path-cache entry after a directory or size mutation.
void FatPathCacheInvalidate(FAT_VOL* v) {
if (!v) return;
uintptr_t flags = KeAcquireSpinlock(&v->PathCacheLock);
v->PathCacheGeneration++;
if (v->PathCacheGeneration == 0)
v->PathCacheGeneration = 1;
v->Stats.PathCacheInvalidations++;
v->Stats.PathCacheGeneration = v->PathCacheGeneration;
for (uint32_t i = 0; i < FAT_PATH_CACHE_SLOTS; i++)
v->PathCache[i].Valid = false;
KeReleaseSpinlock(&v->PathCacheLock, flags);
}
// Returns the current filesystem cache and traversal counters.
void FatQueryStats(IO_VOLUME_PROVIDER* fs, IO_FILESYSTEM_STATS* out) {
if (!fs || !out) return;
FAT_VOL* v = (FAT_VOL*)fs->Context;
if (!v) { memset(out, 0, sizeof(*out)); return; }
uintptr_t flags = KeAcquireSpinlock(&v->PathCacheLock);
*out = v->Stats;
out->PathCacheGeneration = v->PathCacheGeneration;
KeReleaseSpinlock(&v->PathCacheLock, flags);
}
// Records one data read span and its physically contiguous cluster merges.
void FatStatsAddReadRun(FAT_VOL* v, uint32_t sectors, uint32_t merges) {
if (!v || !sectors) return;
uintptr_t flags = KeAcquireSpinlock(&v->PathCacheLock);
v->Stats.DataReadRuns++;
v->Stats.DataReadSectors += sectors;
v->Stats.DataReadRunMerges += merges;
if (v->Stats.MaxDataReadSpanSectors < sectors)
v->Stats.MaxDataReadSpanSectors = sectors;
KeReleaseSpinlock(&v->PathCacheLock, flags);
}
// Initializes the per-open cluster-chain cache for a FAT file object.
void FatFileInitChainCache(FAT_FILE* f) {
if (!f) return;
f->ChainCacheLock = (SPINLOCK)SPINLOCK_INIT;
f->ChainCacheClock = 0;
memset(f->ChainCache, 0, sizeof(f->ChainCache));
}
// Invalidates cached FAT extents after the file's cluster chain changes.
void FatFileInvalidateChainCache(FAT_FILE* f) {
if (!f) return;
uintptr_t flags = KeAcquireSpinlock(&f->ChainCacheLock);
memset(f->ChainCache, 0, sizeof(f->ChainCache));
f->ChainCacheClock = 0;
KeReleaseSpinlock(&f->ChainCacheLock, flags);
}
// Locates the best cached starting point for a logical cluster index.
static bool FatChainCacheLookupStart(FAT_FILE* f, uint32_t logicalIndex,
uint32_t* outLogical, uint32_t* outCluster) {
if (!f || !outLogical || !outCluster)
return false;
bool hit = false;
uint32_t bestLogical = 0;
uint32_t bestCluster = 0;
uintptr_t flags = KeAcquireSpinlock(&f->ChainCacheLock);
for (uint32_t i = 0; i < FAT_CHAIN_CACHE_SLOTS; i++) {
FAT_CHAIN_CACHE_ENTRY* e = &f->ChainCache[i];
if (!e->Valid || e->Length == 0)
continue;
uint32_t end = e->LogicalStart + e->Length;
if (logicalIndex >= e->LogicalStart && logicalIndex < end) {
*outLogical = logicalIndex;
*outCluster = e->PhysicalStart + (logicalIndex - e->LogicalStart);
hit = true;
break;
}
if (end <= logicalIndex && FatIsDataCluster(f->Vol, e->NextCluster)) {
if (!hit || end > bestLogical) {
bestLogical = end;
bestCluster = e->NextCluster;
hit = true;
}
}
}
if (hit && bestCluster) {
*outLogical = bestLogical;
*outCluster = bestCluster;
}
KeReleaseSpinlock(&f->ChainCacheLock, flags);
return hit;
}
// Stores a physically contiguous FAT extent in the per-open chain cache.
static void FatChainCacheStore(FAT_FILE* f, uint32_t logicalStart,
uint32_t physicalStart, uint32_t length,
uint32_t nextCluster) {
if (!f || !FatIsDataCluster(f->Vol, physicalStart) || length == 0)
return;
uintptr_t flags = KeAcquireSpinlock(&f->ChainCacheLock);
uint32_t slot = f->ChainCacheClock++ % FAT_CHAIN_CACHE_SLOTS;
FAT_CHAIN_CACHE_ENTRY* e = &f->ChainCache[slot];
e->Valid = true;
e->LogicalStart = logicalStart;
e->PhysicalStart = physicalStart;
e->Length = length;
e->NextCluster = nextCluster;
KeReleaseSpinlock(&f->ChainCacheLock, flags);
}
// Resolves a logical file cluster through the cached FAT-chain extent map.
uint32_t FatResolveFileCluster(FAT_FILE* f, uint32_t logicalIndex) {
if (!f || !FatIsDataCluster(f->Vol, f->StartCluster))
return 0;
if (logicalIndex == 0)
return f->StartCluster;
uint32_t currentLogical = 0;
uint32_t cluster = f->StartCluster;
if (!FatChainCacheLookupStart(f, logicalIndex, &currentLogical, &cluster)) {
currentLogical = 0;
cluster = f->StartCluster;
}
while (currentLogical < logicalIndex && FatIsDataCluster(f->Vol, cluster)) {
cluster = FatEntry(f->Vol, cluster);
currentLogical++;
}
return cluster;
}
// Builds and caches one physically contiguous read run from a starting cluster.
bool FatBuildReadRun(FAT_FILE* f, uint32_t logicalIndex,
uint32_t startCluster, uint32_t maxSectors,
uint32_t* outRunSectors, uint32_t* outNextCluster) {
if (!f || !outRunSectors || !outNextCluster)
return false;
FAT_VOL* v = f->Vol;
if (!v || !FatIsDataCluster(v, startCluster))
return false;
uint32_t runSectors = v->SectorsPerCluster;
uint32_t runClusters = 1;
uint32_t last = startCluster;
uint32_t next = FatEntry(v, last);
while (next == last + 1 && FatIsDataCluster(v, next) &&
runSectors + v->SectorsPerCluster <= maxSectors) {
last = next;
runClusters++;
runSectors += v->SectorsPerCluster;
next = FatEntry(v, last);
}
FatChainCacheStore(f, logicalIndex, startCluster, runClusters, next);
*outRunSectors = runSectors;
*outNextCluster = next;
return true;
}
static bool SearchDirV(FAT_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 (!FatNameViewIsValid(name)) return false;
uint8_t* clusterBuf = (uint8_t*)MmAllocatePool(v->BytesPerCluster);
if (!clusterBuf) return false;
char lfn[FAT_MAX_LFN]; bool hasLfn = false; memset(lfn, 0, sizeof(lfn));
char wantName83[8], wantExt83[3];
bool allowShortMatch = FatNameIsStructurallyShort83V(name);
Make83NameV(name, wantName83, wantExt83);
uint64_t clustersRead = 0;
uint64_t entriesScanned = 0;
bool found = false;
uint32_t cursor; uint64_t lba; uint32_t sectors;
if (!FatDirectoryFirstSegment(v, dirCluster, &cursor, &lba, &sectors))
goto done;
do {
if (!KSUCCESS(FatReadSectors(v, lba, sectors, clusterBuf))) break;
clustersRead++;
uint32_t entries = (sectors * v->BytesPerSector) / sizeof(DIRENT);
DIRENT* dir = (DIRENT*)clusterBuf;
for (uint32_t i = 0; i < entries; i++) {
entriesScanned++;
if (dir[i].Name[0] == 0x00) goto done;
if ((uint8_t)dir[i].Name[0] == 0xE5) { hasLfn = false; memset(lfn, 0, sizeof(lfn)); continue; }
if (dir[i].Attr == ATTR_LFN) { FatLfnAppend(lfn, (LFN_ENTRY*)&dir[i]); hasLfn = true; continue; }
bool match = false;
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 = false; memset(lfn, 0, sizeof(lfn));
if (match) {
*outCluster = FatDirentFirstCluster(v, &dir[i]);
*outSize = dir[i].FileSize;
*outAttr = dir[i].Attr;
if (outDirentLba || outDirentOffset) {
uint64_t eb = (uint64_t)i * sizeof(DIRENT);
if (outDirentLba) *outDirentLba = lba + eb / v->BytesPerSector;
if (outDirentOffset) *outDirentOffset = (uint32_t)(eb % v->BytesPerSector);
}
found = true;
goto done;
}
}
} while (FatDirectoryNextSegment(v, dirCluster, &cursor, &lba, &sectors));
done:
FatPathCacheAddDirectoryStats(v, clustersRead, entriesScanned);
MmFreePool(clusterBuf);
return found;
}
static bool FatPathNextComponent(SYS_STRING_VIEW path, size_t* cursor,
SYS_STRING_VIEW* component) {
if (!cursor || !component || !SysSvIsValid(path)) return false;
size_t pos = *cursor;
while (pos < path.Length && FatIsPathSep(path.Data[pos])) pos++;
if (pos >= path.Length) {
*cursor = pos;
*component = SYS_SV_NULL;
return false;
}
size_t start = pos;
while (pos < path.Length && !FatIsPathSep(path.Data[pos])) pos++;
*cursor = pos;
*component = SysSvFromParts(path.Data + start, pos - start);
return true;
}
KSTATUS FatLookupPathV(FAT_VOL* v, SYS_STRING_VIEW path,
uint32_t* outCluster, uint32_t* outSize,
uint8_t* outAttr,
uint64_t* outDirentLba, uint32_t* outDirentOffset) {
if (!v || !outCluster || !outSize || !FatStringViewDataIsPlausible(path))
return NTLSTATUS_INVALID_PARAMETER;
if (SysSvContainsByte(path, '\0'))
return NTLSTATUS_INVALID_PARAMETER;
uint32_t cluster = v->RootCluster, size = 0;
uint8_t attr = ATTR_DIRECTORY;
uint64_t finalDirentLba = 0;
uint32_t finalDirentOffset = 0;
if (outDirentLba) *outDirentLba = 0;
if (outDirentOffset) *outDirentOffset = 0;
if (FatPathCacheLookup(v, path, outCluster, outSize, &attr,
outDirentLba, outDirentOffset)) {
if (outAttr) *outAttr = attr;
return NTLSTATUS_SUCCESS;
}
size_t cursor = 0;
SYS_STRING_VIEW component;
while (FatPathNextComponent(path, &cursor, &component)) {
if (component.Length >= FAT_MAX_LFN) return NTLSTATUS_BUFFER_TOO_SMALL;
if (!FatNameViewIsValid(component)) return NTLSTATUS_INVALID_PARAMETER;
if (!(attr & ATTR_DIRECTORY)) return NTLSTATUS_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 NTLSTATUS_NOT_FOUND;
cluster = childCluster; size = childSize; attr = childAttr;
finalDirentLba = childLba;
finalDirentOffset = childOffset;
if (outDirentLba) *outDirentLba = childLba;
if (outDirentOffset) *outDirentOffset = childOffset;
}
*outCluster = cluster;
*outSize = size;
if (outAttr) *outAttr = attr;
FatPathCacheStore(v, path, cluster, size, attr,
finalDirentLba, finalDirentOffset);
return NTLSTATUS_SUCCESS;
}
KSTATUS FatTraverseV(FAT_VOL* v, SYS_STRING_VIEW path,
uint32_t* outCluster, uint32_t* outSize) {
return FatLookupPathV(v, path, outCluster, outSize, NULL, NULL, NULL);
}
static KSTATUS SplitPathV(SYS_STRING_VIEW path, SYS_STRING_VIEW* parent,
SYS_STRING_VIEW* name) {
if (!parent || !name || !SysSvIsValid(path)) return NTLSTATUS_INVALID_PARAMETER;
*parent = SYS_SV_NULL;
*name = SYS_SV_NULL;
while (path.Length && FatIsPathSep(path.Data[path.Length - 1]))
path.Length--;
if (path.Length == 0) return NTLSTATUS_INVALID_PARAMETER;
if (SysSvContainsByte(path, '\0')) return NTLSTATUS_INVALID_PARAMETER;
size_t lastSep = (size_t)-1;
for (size_t i = 0; i < path.Length; i++) {
if (FatIsPathSep(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 (!FatNameViewIsValid(*name)) return NTLSTATUS_INVALID_PARAMETER;
return NTLSTATUS_SUCCESS;
}
// Find a free DIRENT slot.
static bool FindFreeDirent(FAT_VOL* v, uint32_t dirCluster,
uint64_t* outLba, uint32_t* outOffset) {
uint8_t* clusterBuf = (uint8_t*)MmAllocatePool(v->BytesPerCluster);
if (!clusterBuf) return false;
uint32_t cursor; uint64_t lba; uint32_t sectors;
if (!FatDirectoryFirstSegment(v, dirCluster, &cursor, &lba, &sectors)) {
MmFreePool(clusterBuf);
return false;
}
do {
if (!KSUCCESS(FatReadSectors(v, lba, sectors, clusterBuf))) {
MmFreePool(clusterBuf); return false;
}
uint32_t entries = (sectors * v->BytesPerSector) / 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 = lba + eb / v->BytesPerSector;
*outOffset = (uint32_t)(eb % v->BytesPerSector);
MmFreePool(clusterBuf); return true;
}
}
} while (FatDirectoryNextSegment(v, dirCluster, &cursor, &lba, &sectors));
if (FatIsFixedRoot(v, dirCluster)) {
MmFreePool(clusterBuf);
return false;
}
uint32_t newCluster = FatAppendCluster(v, cursor);
if (newCluster) {
*outLba = FatClusterToLba(v, newCluster); *outOffset = 0;
MmFreePool(clusterBuf); return true;
}
MmFreePool(clusterBuf);
return false;
}
// Find a contiguous run of free directory entries.
// VFAT LFN creation requires N long-name entries to immediately precede the
// final short entry, so the run must live inside one readable/writeable segment.
static bool FindFreeDirentRun(FAT_VOL* v, uint32_t dirCluster, uint32_t needed,
uint64_t* outLba, uint32_t* outFirstIndex,
uint32_t* outSectors) {
if (!needed) return false;
uint8_t* clusterBuf = (uint8_t*)MmAllocatePool(v->BytesPerCluster);
if (!clusterBuf) return false;
uint32_t cursor; uint64_t lba; uint32_t sectors;
if (!FatDirectoryFirstSegment(v, dirCluster, &cursor, &lba, &sectors)) {
MmFreePool(clusterBuf);
return false;
}
do {
if (!KSUCCESS(FatReadSectors(v, lba, sectors, clusterBuf))) {
MmFreePool(clusterBuf);
return false;
}
uint32_t entries = (sectors * v->BytesPerSector) / 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) {
*outLba = lba;
*outFirstIndex = runStart;
*outSectors = sectors;
MmFreePool(clusterBuf);
return true;
}
} else {
runLen = 0;
}
}
} while (FatDirectoryNextSegment(v, dirCluster, &cursor, &lba, &sectors));
if (FatIsFixedRoot(v, dirCluster)) {
MmFreePool(clusterBuf);
return false;
}
uint32_t newCluster = FatAppendCluster(v, cursor);
if (newCluster) {
*outLba = FatClusterToLba(v, newCluster);
*outFirstIndex = 0;
*outSectors = v->SectorsPerCluster;
MmFreePool(clusterBuf);
return true;
}
MmFreePool(clusterBuf);
return false;
}
// Finds the parent directory and final component for a create or delete path.
KSTATUS FatResolveParentForMutation(FAT_VOL* v, SYS_STRING_VIEW path,
uint32_t* parentClusterOut,
SYS_STRING_VIEW* filenameOut) {
if (!v || !parentClusterOut || !filenameOut)
return NTLSTATUS_INVALID_PARAMETER;
SYS_STRING_VIEW parentPath;
SYS_STRING_VIEW filenameView;
KSTATUS s = SplitPathV(path, &parentPath, &filenameView);
if (!KSUCCESS(s)) return s;
uint32_t parentCluster = 0;
uint32_t parentSize = 0;
uint8_t parentAttr = 0;
s = FatLookupPathV(v, parentPath, &parentCluster, &parentSize,
&parentAttr, NULL, NULL);
if (!KSUCCESS(s)) return s;
if (!(parentAttr & ATTR_DIRECTORY)) return NTLSTATUS_NOT_FOUND;
*parentClusterOut = parentCluster;
*filenameOut = filenameView;
return NTLSTATUS_SUCCESS;
}
// Creates a FAT directory entry run for a new file or directory.
KSTATUS FatCreateDirent(FAT_VOL* v, SYS_STRING_VIEW path,
uint8_t attr, uint32_t firstCluster,
uint32_t fileSize,
uint64_t* direntLbaOut,
uint32_t* direntOffsetOut) {
if (!v || !SysSvIsValid(path) || path.Length == 0)
return NTLSTATUS_INVALID_PARAMETER;
uint32_t parentCluster = 0;
SYS_STRING_VIEW filenameView;
KSTATUS s = FatResolveParentForMutation(v, path, &parentCluster, &filenameView);
if (!KSUCCESS(s)) return s;
{
uint32_t ec, es;
uint8_t ea;
uint64_t existingLba = 0;
uint32_t existingOffset = 0;
if (SearchDirV(v, parentCluster, filenameView, &ec, &es, &ea,
&existingLba, &existingOffset)) {
return NTLSTATUS_OBJECT_NAME_COLLISION;
}
}
bool 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 NTLSTATUS_OBJECT_NAME_COLLISION;
} else {
Make83NameV(filenameView, shortName, shortExt);
}
size_t nameLen = filenameView.Length;
if (nameLen == 0 || nameLen >= FAT_MAX_LFN) return NTLSTATUS_INVALID_PARAMETER;
uint32_t lfnCount = useLfn ? (uint32_t)((nameLen + 12u) / 13u) : 0;
uint32_t neededEntries = lfnCount + 1u;
uint64_t clusterLba; uint32_t firstIndex; uint32_t entrySectors;
if (!FindFreeDirentRun(v, parentCluster, neededEntries, &clusterLba, &firstIndex, &entrySectors))
return NTLSTATUS_NO_MEMORY;
uint8_t* clusterBuf = (uint8_t*)MmAllocatePool(v->BytesPerCluster);
if (!clusterBuf) return NTLSTATUS_NO_MEMORY;
s = FatReadSectors(v, clusterLba, entrySectors, clusterBuf);
if (!KSUCCESS(s)) { MmFreePool(clusterBuf); return s; }
DIRENT* dir = (DIRENT*)clusterBuf;
uint32_t shortIndex = firstIndex + lfnCount;
if (useLfn) {
uint8_t checksum = LfnChecksum(shortName, shortExt);
for (uint32_t slot = 0; slot < lfnCount; slot++) {
uint32_t ordinal = lfnCount - slot;
BuildLfnEntry((LFN_ENTRY*)&dir[firstIndex + slot], filenameView.Data,
nameLen, ordinal, lfnCount, checksum);
}
}
DIRENT* d = &dir[shortIndex];
memset(d, 0, sizeof(DIRENT));
memcpy(d->Name, shortName, 8);
memcpy(d->Ext, shortExt, 3);
d->Attr = attr;
d->Reserved = FatShortNameCaseFlagsV(filenameView);
FatSetDirentFirstCluster(v, d, firstCluster);
d->FileSize = fileSize;
{
FAT_ENCODED_TIME now;
FatQueryEncodedTime(&now);
d->CrtTimeTenth = now.CreateTimeTenth;
d->CrtTime = now.Time;
d->CrtDate = now.Date;
d->LastAccDate = now.Date;
d->WrtTime = now.Time;
d->WrtDate = now.Date;
}
s = FatWriteSectors(v, clusterLba, entrySectors, clusterBuf);
MmFreePool(clusterBuf);
if (!KSUCCESS(s)) return s;
uint64_t shortEntryByte = (uint64_t)shortIndex * sizeof(DIRENT);
uint64_t direntLba = clusterLba + shortEntryByte / v->BytesPerSector;
uint32_t direntOffset = (uint32_t)(shortEntryByte % v->BytesPerSector);
if (direntLbaOut) *direntLbaOut = direntLba;
if (direntOffsetOut) *direntOffsetOut = direntOffset;
FatPathCacheInvalidate(v);
return NTLSTATUS_SUCCESS;
}
// Initializes one dot entry in a new directory cluster.
static void FatInitDotEntry(FAT_VOL* v, DIRENT* d, const char* name,
uint32_t firstCluster,
const FAT_ENCODED_TIME* now) {
memset(d, 0, sizeof(*d));
memset(d->Name, ' ', sizeof(d->Name));
memset(d->Ext, ' ', sizeof(d->Ext));
d->Name[0] = name[0];
if (name[1]) d->Name[1] = name[1];
d->Attr = ATTR_DIRECTORY;
FatSetDirentFirstCluster(v, d, firstCluster);
d->CrtTimeTenth = now->CreateTimeTenth;
d->CrtTime = now->Time;
d->CrtDate = now->Date;
d->LastAccDate = now->Date;
d->WrtTime = now->Time;
d->WrtDate = now->Date;
}
// Writes the initial contents of a newly allocated FAT directory cluster.
KSTATUS FatInitializeDirectoryCluster(FAT_VOL* v, uint32_t dirCluster,
uint32_t parentCluster) {
if (!v || dirCluster < 2)
return NTLSTATUS_INVALID_PARAMETER;
uint8_t* clusterBuf = (uint8_t*)MmAllocatePool(v->BytesPerCluster);
if (!clusterBuf) return NTLSTATUS_NO_MEMORY;
memset(clusterBuf, 0, v->BytesPerCluster);
FAT_ENCODED_TIME now;
FatQueryEncodedTime(&now);
DIRENT* dir = (DIRENT*)clusterBuf;
FatInitDotEntry(v, &dir[0], ".", dirCluster, &now);
FatInitDotEntry(v, &dir[1], "..", FatIsFixedRoot(v, parentCluster) ? 0u : parentCluster, &now);
KSTATUS s = FatWriteSectors(v, FatClusterToLba(v, dirCluster),
v->SectorsPerCluster, clusterBuf);
MmFreePool(clusterBuf);
return s;
}
// Tests whether a FAT directory entry is one of the synthetic dot entries.
static bool FatIsDotEntry(const DIRENT* d) {
if (!d || !(d->Attr & ATTR_DIRECTORY)) return false;
if (d->Name[0] != '.') return false;
if (d->Name[1] == ' ')
return true;
return d->Name[1] == '.' && d->Name[2] == ' ';
}
// Tests whether a directory contains any real child entries.
KSTATUS FatDirectoryIsEmpty(FAT_VOL* v, uint32_t dirCluster,
bool* emptyOut) {
if (!v || !emptyOut)
return NTLSTATUS_INVALID_PARAMETER;
uint8_t* clusterBuf = (uint8_t*)MmAllocatePool(v->BytesPerCluster);
if (!clusterBuf) return NTLSTATUS_NO_MEMORY;
uint32_t cursor; uint64_t lba; uint32_t sectors;
if (!FatDirectoryFirstSegment(v, dirCluster, &cursor, &lba, &sectors)) {
MmFreePool(clusterBuf);
return NTLSTATUS_INVALID_PARAMETER;
}
do {
KSTATUS s = FatReadSectors(v, lba, sectors, clusterBuf);
if (!KSUCCESS(s)) { MmFreePool(clusterBuf); return s; }
uint32_t entries = (sectors * v->BytesPerSector) / sizeof(DIRENT);
DIRENT* dir = (DIRENT*)clusterBuf;
for (uint32_t i = 0; i < entries; i++) {
uint8_t first = (uint8_t)dir[i].Name[0];
if (first == 0x00) { MmFreePool(clusterBuf); *emptyOut = 1; return NTLSTATUS_SUCCESS; }
if (first == 0xE5 || dir[i].Attr == ATTR_LFN) continue;
if ((dir[i].Attr & ATTR_VOLUME_ID) && !(dir[i].Attr & ATTR_DIRECTORY)) continue;
if (FatIsDotEntry(&dir[i])) continue;
MmFreePool(clusterBuf);
*emptyOut = 0;
return NTLSTATUS_SUCCESS;
}
} while (FatDirectoryNextSegment(v, dirCluster, &cursor, &lba, &sectors));
MmFreePool(clusterBuf);
*emptyOut = 1;
return NTLSTATUS_SUCCESS;
}
// Deletes the directory-entry run for one child name from a parent directory.
KSTATUS FatDeleteDirentRunV(FAT_VOL* v, uint32_t parentCluster,
SYS_STRING_VIEW name) {
if (!v || !FatNameViewIsValid(name))
return NTLSTATUS_INVALID_PARAMETER;
uint8_t* clusterBuf = (uint8_t*)MmAllocatePool(v->BytesPerCluster);
if (!clusterBuf) return NTLSTATUS_NO_MEMORY;
char lfn[FAT_MAX_LFN];
bool hasLfn = false;
uint32_t lfnStart = 0;
memset(lfn, 0, sizeof(lfn));
char wantName83[8], wantExt83[3];
bool allowShortMatch = FatNameIsStructurallyShort83V(name);
Make83NameV(name, wantName83, wantExt83);
uint32_t cursor; uint64_t lba; uint32_t sectors;
if (!FatDirectoryFirstSegment(v, parentCluster, &cursor, &lba, &sectors)) {
MmFreePool(clusterBuf);
return NTLSTATUS_NOT_FOUND;
}
do {
KSTATUS s = FatReadSectors(v, lba, sectors, clusterBuf);
if (!KSUCCESS(s)) { MmFreePool(clusterBuf); return s; }
uint32_t entries = (sectors * v->BytesPerSector) / sizeof(DIRENT);
DIRENT* dir = (DIRENT*)clusterBuf;
hasLfn = false;
memset(lfn, 0, sizeof(lfn));
for (uint32_t i = 0; i < entries; i++) {
uint8_t first = (uint8_t)dir[i].Name[0];
if (first == 0x00) { MmFreePool(clusterBuf); return NTLSTATUS_NOT_FOUND; }
if (first == 0xE5) { hasLfn = false; memset(lfn, 0, sizeof(lfn)); continue; }
if (dir[i].Attr == ATTR_LFN) {
if (!hasLfn) lfnStart = i;
FatLfnAppend(lfn, (LFN_ENTRY*)&dir[i]);
hasLfn = true;
continue;
}
bool match = false;
if (hasLfn && lfn[0]) match = LfnMatchV(lfn, name);
if (!match && allowShortMatch)
match = MatchName83V(dir[i].Name, dir[i].Ext, name);
if (!match && allowShortMatch)
match = MatchRaw83(dir[i].Name, dir[i].Ext, wantName83, wantExt83);
if (match) {
uint32_t start = hasLfn ? lfnStart : i;
for (uint32_t j = start; j <= i; j++)
dir[j].Name[0] = (char)0xE5;
s = FatWriteSectors(v, lba, sectors, clusterBuf);
MmFreePool(clusterBuf);
if (KSUCCESS(s)) FatPathCacheInvalidate(v);
return s;
}
hasLfn = false;
memset(lfn, 0, sizeof(lfn));
}
} while (FatDirectoryNextSegment(v, parentCluster, &cursor, &lba, &sectors));
MmFreePool(clusterBuf);
return NTLSTATUS_NOT_FOUND;
}