Files
nt-lite/code/drivers/fs/fat/fat_directory.c
T
maksw 73abd6f90d isolate POSIX builds from the host UCRT, wire lwext4 and ntfs-3g as ext4.sys and ntfs.sys, split the FAT driver into directory/file/async/driver/mount units, add posixsrv and dwm plus input/console/desktop syscall clients and a pile of new smoke targets, rename serial.sys to cereal.sys, name HDA/USB/lwIP/AHCI workers, harden ATA probing/reset/removal around actual hardware deadlines, absent-channel detection, diagnostics and explicit worker lifetime, redo ACPI S5 shutdown around GAS/MMIO and HW-reduced firmware, scan SSDTs for _S5 and deal with Intel SMI sleep trapping, make serial and SMP timeouts use real monotonic deadlines, add thread names and fix terminal thread teardown, scheduler and APC prepare/commit races, make advisory file locks queryable and preserve split ranges and add mutable file append state, add shared-memory pipes with kernel-assisted wait/wake, introduce capability-scoped input and display managers, split surface map rights into read/write, add explicit unmap and replace present with commit/generation/damage tracking while keeping physical presentation under the display manager, decouple native exception classes from Win32 exception numbers and translate them in kernel32, add opaque process startup data, bump the resource protocol to v3 with namespace directories, symlinks and enumeration and service control to v2 with generic READY handle exports, add the new POSIX errno/status surface and rebuild fds around posixsrv-owned shared open descriptions, descriptor-local flags, exact inheritance snapshots, dup/fcntl/record locks and shared-ring pipes, add waitpid, POSIX credentials/groups/sessions/passwd/group lookups and move sockets onto stable open descriptions, port the shells and apps to the new console/input services and surface commit API, teach init to load services.ini and start/supervise RSSRV, DWM and generic services under restricted capabilities while publishing their endpoints through RSSRV, split RSSRV into namespace, volume, display, session, input, console and resource components and make /SystemRoot a real symlink to the independently published system volume, shift the syscall table again, expand lock/resource/surface/POSIX/service/thread smoke coverage, make docs scream ASK QUESTIONS and DISCUSS at LLMs, replace MIT No Attribution with WTFPL+.
2026-09-16 10:33:20 +02:00

1270 lines
45 KiB
C

// FAT name handling, path traversal, and directory mutation.
#include "fat_internal.h"
// 8.3 name matching / assembly.
static int MatchRaw83(const char dirName[8], const char dirExt[3],
const char wantName[8], const char wantExt[3]) {
return memcmp(dirName, wantName, 8) == 0 &&
memcmp(dirExt, wantExt, 3) == 0;
}
static char FatUpperChar(char c);
static int FatIsLowerChar(char c);
static int FatIsValidShortChar(char c);
// Implements the make83 Name V operation.
static void Make83NameV(SYS_STRING_VIEW src, char name8[8], char ext3[3]) {
memset(name8, ' ', 8);
memset(ext3, ' ', 3);
if (!SysSvIsValid(src) || src.Length == 0) return;
size_t baseStart = 0;
size_t dot = (size_t)-1;
if (src.Data[0] == '.' && src.Length > 1) {
baseStart = 1;
} else {
for (size_t i = 0; i < src.Length; i++)
if (src.Data[i] == '.') dot = i;
}
size_t end = (dot != (size_t)-1) ? dot : src.Length;
int ni = 0;
for (size_t i = baseStart; i < end && ni < 8; i++) {
char c = FatUpperChar(src.Data[i]);
name8[ni++] = c;
}
if (dot != (size_t)-1) {
int ei = 0;
for (size_t i = dot + 1; i < src.Length && ei < 3; i++) {
char c = FatUpperChar(src.Data[i]);
ext3[ei++] = c;
}
}
}
// LFN assembly / matching.
// Implements the fat Upper Char operation.
static char FatUpperChar(char c) {
return (c >= 'a' && c <= 'z') ? (char)(c - 32) : c;
}
// Implements the fat Is Lower Char operation.
static int FatIsLowerChar(char c) {
return c >= 'a' && c <= 'z';
}
// Implements the fat Is Valid Short Char operation.
static int FatIsValidShortChar(char c) {
c = FatUpperChar(c);
if (c >= 'A' && c <= 'Z') return 1;
if (c >= '0' && c <= '9') return 1;
switch (c) {
case '$': case '%': case '\'': case '-': case '_': case '@':
case '~': case '`': case '!': case '(': case ')': case '{':
case '}': case '^': case '#': case '&':
return 1;
default:
return 0;
}
}
// True when the user spelling can refer to a normal FAT 8.3 entry. This is
// intentionally structural and case-insensitive: "doom1.wad" must still open
// an existing short entry named DOOM1.WAD. Leading-dot names are not 8.3.
static int FatNameIsStructurallyShort83V(SYS_STRING_VIEW name) {
if (!SysSvIsValid(name) || name.Length == 0) return 0;
if (name.Data[0] == '.') return 0;
size_t dot = (size_t)-1;
for (size_t i = 0; i < name.Length; i++) {
char c = name.Data[i];
if (c == '.') {
if (dot != (size_t)-1) return 0;
dot = i;
} else if (!FatIsValidShortChar(c)) {
return 0;
}
}
size_t baseLen = (dot != (size_t)-1) ? dot : name.Length;
size_t extLen = (dot != (size_t)-1) ? (name.Length - dot - 1) : 0;
return baseLen >= 1 && baseLen <= 8 && extLen <= 3;
}
// Implements the fat Component Has Lower V operation.
static int FatComponentHasLowerV(SYS_STRING_VIEW name, size_t start, size_t end) {
for (size_t i = start; i < end; i++)
if (FatIsLowerChar(name.Data[i])) return 1;
return 0;
}
// Implements the fat Component Has Upper V operation.
static int FatComponentHasUpperV(SYS_STRING_VIEW name, size_t start, size_t end) {
for (size_t i = start; i < end; i++)
if (name.Data[i] >= 'A' && name.Data[i] <= 'Z') return 1;
return 0;
}
static void FatSplitShortNameV(SYS_STRING_VIEW name, size_t* baseEnd,
size_t* extStart, size_t* extEnd) {
size_t dot = (size_t)-1;
for (size_t i = 0; i < name.Length; i++)
if (name.Data[i] == '.') dot = i;
if (dot == (size_t)-1) {
*baseEnd = name.Length;
*extStart = name.Length;
*extEnd = name.Length;
} else {
*baseEnd = dot;
*extStart = dot + 1u;
*extEnd = name.Length;
}
}
// Creation uses LFN only when the spelling cannot be represented by a short
// entry, or when mixed-case 8.3 spelling would be lost. Plain lowercase
// 8.3 names are represented with FAT's NT case bits, not VFAT LFN entries.
static int FatNameNeedsLfnForCreateV(SYS_STRING_VIEW name) {
if (!FatNameIsStructurallyShort83V(name)) return 1;
size_t baseEnd, extStart, extEnd;
FatSplitShortNameV(name, &baseEnd, &extStart, &extEnd);
int baseLower = FatComponentHasLowerV(name, 0, baseEnd);
int baseUpper = FatComponentHasUpperV(name, 0, baseEnd);
int extLower = FatComponentHasLowerV(name, extStart, extEnd);
int extUpper = FatComponentHasUpperV(name, extStart, extEnd);
if ((baseLower && baseUpper) || (extLower && extUpper)) return 1;
return 0;
}
// Implements the fat Short Name Case Flags V operation.
static uint8_t FatShortNameCaseFlagsV(SYS_STRING_VIEW name) {
if (!FatNameIsStructurallyShort83V(name)) return 0;
size_t baseEnd, extStart, extEnd;
FatSplitShortNameV(name, &baseEnd, &extStart, &extEnd);
uint8_t flags = 0;
if (FatComponentHasLowerV(name, 0, baseEnd) &&
!FatComponentHasUpperV(name, 0, baseEnd))
flags |= FAT_NTRES_BASE_LOWER;
if (extStart < extEnd &&
FatComponentHasLowerV(name, extStart, extEnd) &&
!FatComponentHasUpperV(name, extStart, extEnd))
flags |= FAT_NTRES_EXT_LOWER;
return flags;
}
// Implements the lfn Checksum operation.
static uint8_t LfnChecksum(const char name8[8], const char ext3[3]) {
uint8_t sum = 0;
for (int i = 0; i < 11; i++) {
uint8_t c = (uint8_t)(i < 8 ? name8[i] : ext3[i - 8]);
sum = (uint8_t)(((sum & 1) ? 0x80 : 0) + (sum >> 1) + c);
}
return sum;
}
// Implements the lfn Set Char operation.
static void LfnSetChar(LFN_ENTRY* e, int idx, uint16_t value) {
if (idx < 5) e->Name1[idx] = value;
else if (idx < 11) e->Name2[idx - 5] = value;
else e->Name3[idx - 11] = value;
}
static void BuildLfnEntry(LFN_ENTRY* e, const char* name, size_t nameLen,
uint32_t ordinal, uint32_t lfnCount,
uint8_t checksum) {
memset(e, 0, sizeof(*e));
e->Order = (uint8_t)ordinal;
if (ordinal == lfnCount) e->Order |= 0x40;
e->Attr = ATTR_LFN;
e->Type = 0;
e->Checksum = checksum;
e->FirstCluster = 0;
size_t start = (size_t)(ordinal - 1) * 13u;
for (int i = 0; i < 13; i++) {
size_t pos = start + (size_t)i;
uint16_t ch;
if (pos < nameLen) ch = (uint8_t)name[pos];
else if (pos == nameLen) ch = 0x0000;
else ch = 0xFFFF;
LfnSetChar(e, i, ch);
}
}
// Creates legacy Broken Dot83.
static void MakeLegacyBrokenDot83(const char* src, char name8[8], char ext3[3]) {
// This exactly models the pre-fix bug for ".doomrc": empty name + "DOO".
memset(name8, ' ', 8);
memset(ext3, ' ', 3);
const char* dot = NULL;
for (const char* p = src; *p; p++) if (*p == '.') dot = p;
if (!dot) return;
int ei = 0;
for (const char* p = dot + 1; *p && ei < 3; p++)
ext3[ei++] = FatUpperChar(*p);
}
static int ShortNameExistsRaw(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 1; // 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 0;
}
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 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;
}
}
} while (FatDirectoryNextSegment(v, dirCluster, &cursor, &lba, &sectors));
MmFreePool(clusterBuf);
return 0;
}
// Implements the fat Alias Append Char operation.
static void FatAliasAppendChar(char* dst, int* len, int maxLen, char c) {
c = FatUpperChar(c);
if (!FatIsValidShortChar(c)) return;
if (*len < maxLen) dst[(*len)++] = c;
}
// Builds alias Stem Ext V.
static void BuildAliasStemExtV(SYS_STRING_VIEW src, char stem[9], char ext3[3]) {
memset(stem, 0, 9);
memset(ext3, ' ', 3);
if (!SysSvIsValid(src)) return;
size_t baseStart = 0;
size_t dot = (size_t)-1;
if (src.Length > 1 && src.Data[0] == '.') {
// Hidden/Unix-style names like .doomrc become alias stem DOOMRC.
baseStart = 1;
} else {
for (size_t i = 0; i < src.Length; i++)
if (src.Data[i] == '.') dot = i;
}
int slen = 0;
size_t end = (dot != (size_t)-1) ? dot : src.Length;
for (size_t i = baseStart; i < end; i++)
FatAliasAppendChar(stem, &slen, 8, src.Data[i]);
if (slen == 0) {
memcpy(stem, "FILE", 4);
slen = 4;
}
stem[slen] = '\0';
if (dot != (size_t)-1 && dot + 1 < src.Length) {
int ei = 0;
for (size_t i = dot + 1; i < src.Length && ei < 3; i++) {
char c = FatUpperChar(src.Data[i]);
if (!FatIsValidShortChar(c)) continue;
ext3[ei++] = c;
}
}
}
static void MakeAliasAttempt(const char stem[9], const char extIn[3],
uint32_t attempt, char name8[8], char ext3[3]) {
memset(name8, ' ', 8);
memcpy(ext3, extIn, 3);
char suffix[8];
int suffixLen = 0;
suffix[suffixLen++] = '~';
char digits[7];
int digitLen = 0;
uint32_t n = attempt;
do {
digits[digitLen++] = (char)('0' + (n % 10));
n /= 10;
} while (n && digitLen < (int)sizeof(digits));
for (int i = digitLen - 1; i >= 0; i--)
suffix[suffixLen++] = digits[i];
int stemLen = (int)strlen(stem);
int keep = 8 - suffixLen;
if (keep < 1) keep = 1;
if (stemLen < keep) keep = stemLen;
memcpy(name8, stem, keep);
memcpy(name8 + keep, suffix, suffixLen);
}
static int MakeUniqueShortAliasV(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 1;
}
return 0;
}
// Creates legacy Broken Dot83 V.
static void MakeLegacyBrokenDot83V(SYS_STRING_VIEW src, char name8[8], char ext3[3]) {
// This exactly models the pre-fix bug for ".doomrc": empty name + "DOO".
memset(name8, ' ', 8);
memset(ext3, ' ', 3);
if (!SysSvIsValid(src)) return;
size_t dot = (size_t)-1;
for (size_t i = 0; i < src.Length; i++) if (src.Data[i] == '.') dot = i;
if (dot == (size_t)-1) return;
int ei = 0;
for (size_t i = dot + 1; i < src.Length && ei < 3; i++)
ext3[ei++] = FatUpperChar(src.Data[i]);
}
static KSTATUS DeleteLegacyBrokenDotAliasV(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 int FatIsPathSep(char c) {
return c == '\\' || c == '/';
}
// Tests whether a non-empty FAT path view points at plausible kernel-owned storage.
static int FatStringViewDataIsPlausible(SYS_STRING_VIEW view) {
if (!SysSvIsValid(view)) return 0;
if (view.Length == 0) return 1;
uintptr_t data = (uintptr_t)view.Data;
return data >= 0x10000u;
}
// Implements the name View Is Valid operation.
static int FatNameViewIsValid(SYS_STRING_VIEW name) {
if (!FatStringViewDataIsPlausible(name)) return 0;
if (name.Length == 0 || name.Length >= FAT_MAX_LFN) return 0;
for (size_t i = 0; i < name.Length; i++) {
char c = name.Data[i];
if (c == '\0' || FatIsPathSep(c)) return 0;
}
return 1;
}
// Implements the lfn Match V operation.
static int LfnMatchV(const char* lfn, SYS_STRING_VIEW want) {
if (!lfn || !SysSvIsValid(want)) return 0;
size_t i = 0;
while (lfn[i] && i < want.Length) {
char a = lfn[i], b = want.Data[i];
if (a >= 'A' && a <= 'Z') a += 32;
if (b >= 'A' && b <= 'Z') b += 32;
if (a != b) return 0;
i++;
}
return lfn[i] == '\0' && i == want.Length;
}
static int MatchName83V(const char* dirName, const char* dirExt,
SYS_STRING_VIEW want) {
char buf[13];
int n = 0;
for (int i = 0; i < 8 && dirName[i] != ' '; i++)
buf[n++] = (char)(dirName[i] >= 'A' && dirName[i] <= 'Z' ? dirName[i]+32 : dirName[i]);
if (dirExt[0] != ' ') {
buf[n++] = '.';
for (int i = 0; i < 3 && dirExt[i] != ' '; i++)
buf[n++] = (char)(dirExt[i] >= 'A' && dirExt[i] <= 'Z' ? dirExt[i]+32 : dirExt[i]);
}
if ((size_t)n != want.Length) return 0;
for (int i = 0; i < n; i++) {
char a = buf[i], b = want.Data[i];
if (a >= 'A' && a <= 'Z') a += 32;
if (b >= 'A' && b <= 'Z') b += 32;
if (a != b) return 0;
}
return 1;
}
// Returns the path cache canonical byte used for hashes and comparisons.
static char 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 int 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 0;
uint8_t* clusterBuf = (uint8_t*)MmAllocatePool(v->BytesPerCluster);
if (!clusterBuf) return 0;
char lfn[FAT_MAX_LFN]; int hasLfn = 0; memset(lfn, 0, sizeof(lfn));
char wantName83[8], wantExt83[3];
int allowShortMatch = FatNameIsStructurallyShort83V(name);
Make83NameV(name, wantName83, wantExt83);
uint64_t clustersRead = 0;
uint64_t entriesScanned = 0;
int found = 0;
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 = 0; memset(lfn, 0, sizeof(lfn)); continue; }
if (dir[i].Attr == ATTR_LFN) { FatLfnAppend(lfn, (LFN_ENTRY*)&dir[i]); hasLfn = 1; continue; }
int match = 0;
if (hasLfn && lfn[0]) match = LfnMatchV(lfn, name);
if (!match && allowShortMatch)
match = MatchName83V(dir[i].Name, dir[i].Ext, name);
if (!match && allowShortMatch)
match = MatchRaw83(dir[i].Name, dir[i].Ext, wantName83, wantExt83);
hasLfn = 0; memset(lfn, 0, sizeof(lfn));
if (match) {
*outCluster = 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 = 1;
goto done;
}
}
} while (FatDirectoryNextSegment(v, dirCluster, &cursor, &lba, &sectors));
done:
FatPathCacheAddDirectoryStats(v, clustersRead, entriesScanned);
MmFreePool(clusterBuf);
return found;
}
static int FatPathNextComponent(SYS_STRING_VIEW path, size_t* cursor,
SYS_STRING_VIEW* component) {
if (!cursor || !component || !SysSvIsValid(path)) return 0;
size_t pos = *cursor;
while (pos < path.Length && FatIsPathSep(path.Data[pos])) pos++;
if (pos >= path.Length) {
*cursor = pos;
*component = SYS_SV_NULL;
return 0;
}
size_t start = pos;
while (pos < path.Length && !FatIsPathSep(path.Data[pos])) pos++;
*cursor = pos;
*component = SysSvFromParts(path.Data + start, pos - start);
return 1;
}
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 int FindFreeDirent(FAT_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 cursor; uint64_t lba; uint32_t sectors;
if (!FatDirectoryFirstSegment(v, dirCluster, &cursor, &lba, &sectors)) {
MmFreePool(clusterBuf);
return 0;
}
do {
if (!KSUCCESS(FatReadSectors(v, lba, sectors, clusterBuf))) {
MmFreePool(clusterBuf); return 0;
}
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 1;
}
}
} while (FatDirectoryNextSegment(v, dirCluster, &cursor, &lba, &sectors));
if (FatIsFixedRoot(v, dirCluster)) {
MmFreePool(clusterBuf);
return 0;
}
uint32_t newCluster = FatAppendCluster(v, cursor);
if (newCluster) {
*outLba = FatClusterToLba(v, newCluster); *outOffset = 0;
MmFreePool(clusterBuf); return 1;
}
MmFreePool(clusterBuf);
return 0;
}
// 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 int FindFreeDirentRun(FAT_VOL* v, uint32_t dirCluster, uint32_t needed,
uint64_t* outLba, uint32_t* outFirstIndex,
uint32_t* outSectors) {
if (!needed) return 0;
uint8_t* clusterBuf = (uint8_t*)MmAllocatePool(v->BytesPerCluster);
if (!clusterBuf) return 0;
uint32_t cursor; uint64_t lba; uint32_t sectors;
if (!FatDirectoryFirstSegment(v, dirCluster, &cursor, &lba, &sectors)) {
MmFreePool(clusterBuf);
return 0;
}
do {
if (!KSUCCESS(FatReadSectors(v, lba, sectors, clusterBuf))) {
MmFreePool(clusterBuf);
return 0;
}
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 1;
}
} else {
runLen = 0;
}
}
} while (FatDirectoryNextSegment(v, dirCluster, &cursor, &lba, &sectors));
if (FatIsFixedRoot(v, dirCluster)) {
MmFreePool(clusterBuf);
return 0;
}
uint32_t newCluster = FatAppendCluster(v, cursor);
if (newCluster) {
*outLba = FatClusterToLba(v, newCluster);
*outFirstIndex = 0;
*outSectors = v->SectorsPerCluster;
MmFreePool(clusterBuf);
return 1;
}
MmFreePool(clusterBuf);
return 0;
}
// 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;
}
}
int useLfn = FatNameNeedsLfnForCreateV(filenameView);
char shortName[8], shortExt[3];
if (useLfn) {
s = DeleteLegacyBrokenDotAliasV(v, parentCluster, filenameView);
if (!KSUCCESS(s)) return s;
if (!MakeUniqueShortAliasV(v, parentCluster, filenameView, shortName, shortExt))
return 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 int FatIsDotEntry(const DIRENT* d) {
if (!d || !(d->Attr & ATTR_DIRECTORY)) return 0;
if (d->Name[0] != '.') return 0;
if (d->Name[1] == ' ')
return 1;
return d->Name[1] == '.' && d->Name[2] == ' ';
}
// Tests whether a directory contains any real child entries.
KSTATUS FatDirectoryIsEmpty(FAT_VOL* v, uint32_t dirCluster,
int* 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];
int hasLfn = 0;
uint32_t lfnStart = 0;
memset(lfn, 0, sizeof(lfn));
char wantName83[8], wantExt83[3];
int allowShortMatch = FatNameIsStructurallyShort83V(name);
Make83NameV(name, wantName83, wantExt83);
uint32_t 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 = 0;
memset(lfn, 0, sizeof(lfn));
for (uint32_t i = 0; i < entries; i++) {
uint8_t first = (uint8_t)dir[i].Name[0];
if (first == 0x00) { MmFreePool(clusterBuf); return NTLSTATUS_NOT_FOUND; }
if (first == 0xE5) { hasLfn = 0; memset(lfn, 0, sizeof(lfn)); continue; }
if (dir[i].Attr == ATTR_LFN) {
if (!hasLfn) lfnStart = i;
FatLfnAppend(lfn, (LFN_ENTRY*)&dir[i]);
hasLfn = 1;
continue;
}
int match = 0;
if (hasLfn && lfn[0]) match = LfnMatchV(lfn, name);
if (!match && allowShortMatch)
match = MatchName83V(dir[i].Name, dir[i].Ext, name);
if (!match && allowShortMatch)
match = MatchRaw83(dir[i].Name, dir[i].Ext, wantName83, wantExt83);
if (match) {
uint32_t start = hasLfn ? lfnStart : i;
for (uint32_t j = start; j <= i; j++)
dir[j].Name[0] = (char)0xE5;
s = FatWriteSectors(v, lba, sectors, clusterBuf);
MmFreePool(clusterBuf);
if (KSUCCESS(s)) FatPathCacheInvalidate(v);
return s;
}
hasLfn = 0;
memset(lfn, 0, sizeof(lfn));
}
} while (FatDirectoryNextSegment(v, parentCluster, &cursor, &lba, &sectors));
MmFreePool(clusterBuf);
return NTLSTATUS_NOT_FOUND;
}