add ntfs.sys standalone driver target, change default usb vhd layout formatting to ntfs, implement Fat32CreateDir for directory creation support in the fat32 file system driver, add a dedicated HAL serial output endpoint for COM1 writes in hal_serial.c and subscribe serial to HAL text streams, introduce waitable kernel timers with KeTimerCreate, KeTimerSet, and KeTimerCancel alongside spinlock-protected global timer list initialization and scheduler tick tracking, implement SyscallWriteFileAsync system call for asynchronous non-blocking file writes, refactor NetSocketConnect and NetSocketSendTo to use generic SYS_SOCKET_ADDRESS_K structures instead of being hardcoded to IPv4, update gethostbyname and DNS name resolution to support IPv6 parsing via ResolveNameFamily, update tcpserve.exe with -4 and -6 command-line flags to accept and display IPv6 addresses, append size and version headers to all kernel telemetry and statistics structures including SYS_PMM_STATS, SYS_CPU_STATS, and SYS_SCHEDULER_STATS, update winmm timeGetTime to look up high-resolution hardware frequencies via SysGetTickFrequency, integrate 14 native smoke test applications.

This commit is contained in:
maksw committed 2026-06-26 15:18:48 +02:00
1 parent 2c4a68a75f
commit ed3d065abd
156 files changed
+10509 -2643

No files matched your search

+529 -174
View File
@@ -97,6 +97,7 @@ typedef struct {
#pragma pack(pop)
#define ATTR_VOLUME_ID 0x08
#define ATTR_DIRECTORY 0x10
#define ATTR_ARCHIVE 0x20
#define ATTR_LFN 0x0F
@@ -109,10 +110,10 @@ typedef struct {
// FAT32_VOL forward declaration (needed by cache helpers).
typedef struct FAT32_VOL_S FAT32_VOL;
// FAT sector cache (fixes #1 and #5)
// FAT sector cache (fixes #1 and #5)
// 32 direct-mapped slots, indexed by (lba % FAT_CACHE_SLOTS).
// Write-back: WriteFatEntry modifies the in-cache bytes and marks the slot
// dirty. FlushFatCache() propagates dirty slots to every FAT copy at once,
// dirty. FlushFatCache() propagates dirty slots to every FAT copy at once,
// replacing the original per-entry 2x(read+write) pattern with a single
// write per dirty sector per FAT copy.
@@ -155,12 +156,12 @@ typedef struct FAT32_CHAIN_CACHE_ENTRY {
uint32_t NextCluster;
} FAT32_CHAIN_CACHE_ENTRY;
// The FAT sector cache is global and direct-mapped. mmap-backed video can fault
// The FAT sector cache is global and direct-mapped. mmap-backed video can fault
// file pages from multiple Bink worker threads at the same time; without
// serialization one thread can evict/reload a cache slot while another thread is
// still reading a FAT entry from that slot, corrupting cluster-chain traversal.
// Use a sleepable mutex rather than a spinlock because cache misses perform disk
// I/O. The static mutex avoids allocating an object from the paging path.
// I/O. The static mutex avoids allocating an object from the paging path.
static MUTEX gFatCacheMutex;
static SPINLOCK gFatCacheMutexInitLock = SPINLOCK_INIT;
static int gFatCacheMutexReady;
@@ -224,7 +225,7 @@ static KSTATUS FatCacheRead(FAT32_VOL *v, uint64_t lba, uint8_t **out) {
e->lba = lba;
e->valid = true;
e->dirty = false;
*out = e->data;
*out = e->data;
return STATUS_SUCCESS;
}
@@ -327,7 +328,7 @@ static void FlushFatCache(FAT32_VOL *v) {
FatCacheRelease(held);
}
// FAT read / write (through cache).
// FAT read / write (through cache).
static uint32_t FatEntry(FAT32_VOL *v, uint32_t cluster) {
uint32_t fatOffset = cluster * 4;
@@ -368,7 +369,7 @@ static KSTATUS WriteFatEntry(FAT32_VOL *v, uint32_t cluster, uint32_t value) {
return STATUS_SUCCESS;
}
// Cluster allocation (fix #3: next-free hint + two-phase wrap-around scan).
// Cluster allocation (fix #3: next-free hint + two-phase wrap-around scan).
static uint32_t AllocClusterEx(FAT32_VOL *v, int zeroCluster) {
if (v->TotalClusters == 0) return 0;
@@ -385,7 +386,7 @@ static uint32_t AllocClusterEx(FAT32_VOL *v, int zeroCluster) {
v->NextFreeHint = c + 1;
// Normal file growth zeroes newly allocated clusters so old
// disk contents are never exposed. Swap/pagefile creation is
// disk contents are never exposed. Swap/pagefile creation is
// the one exception: every allocated slot is overwritten before
// it can ever be read back, so skipping these writes avoids a
// slow boot-time full-pagefile zero pass.
@@ -404,7 +405,6 @@ static uint32_t AllocClusterEx(FAT32_VOL *v, int zeroCluster) {
return 0;
}
static uint32_t AllocCluster(FAT32_VOL *v) {
return AllocClusterEx(v, 1);
}
@@ -703,9 +703,9 @@ static int FatIsValidShortChar(char c) {
}
}
// True when the user spelling can refer to a normal FAT 8.3 entry. This is
// 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.
// 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;
@@ -757,7 +757,7 @@ static void FatSplitShortNameV(SYS_STRING_VIEW name, size_t* baseEnd,
}
// 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
// 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;
@@ -991,7 +991,7 @@ static KSTATUS DeleteLegacyBrokenDotAliasV(FAT32_VOL *v, uint32_t dirCluster,
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
// 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.
@@ -1024,7 +1024,6 @@ static void LfnAppend(char *lfn, const LFN_ENTRY *e) {
}
}
// Directory search and view-native path traversal.
static int Fat32IsPathSep(char c) {
return c == '\\' || c == '/';
@@ -1109,7 +1108,7 @@ static void Fat32PathCacheAddDirectoryStats(FAT32_VOL* v, uint64_t clusters, uin
if (!v) return;
uintptr_t flags = KeAcquireSpinlock(&v->PathCacheLock);
v->Stats.DirectorySearches++;
v->Stats.DirectoryClustersRead += clusters;
v->Stats.DirectoryBlocksRead += clusters;
v->Stats.DirectoryEntriesScanned += entries;
KeReleaseSpinlock(&v->PathCacheLock, flags);
}
@@ -1199,15 +1198,6 @@ void Fat32QueryStats(IO_FILESYSTEM* fs, IO_FILESYSTEM_STATS* out) {
KeReleaseSpinlock(&v->PathCacheLock, flags);
}
// Adds FAT-chain traversal counters shared by the read path helpers.
static void Fat32StatsAddChainWalk(FAT32_VOL* v, uint64_t steps) {
if (!v || !steps) return;
uintptr_t flags = KeAcquireSpinlock(&v->PathCacheLock);
v->Stats.FatChainWalkSteps += steps;
KeReleaseSpinlock(&v->PathCacheLock, flags);
}
// Records one data read span and its physically contiguous cluster merges.
static void Fat32StatsAddReadRun(FAT32_VOL* v, uint32_t sectors, uint32_t merges) {
if (!v || !sectors) return;
@@ -1235,12 +1225,6 @@ static void Fat32FileInvalidateChainCache(FAT32_FILE* f) {
memset(f->ChainCache, 0, sizeof(f->ChainCache));
f->ChainCacheClock = 0;
KeReleaseSpinlock(&f->ChainCacheLock, flags);
FAT32_VOL* v = f->Vol;
if (!v) return;
flags = KeAcquireSpinlock(&v->PathCacheLock);
v->Stats.FatChainCacheInvalidations++;
KeReleaseSpinlock(&v->PathCacheLock, flags);
}
// Locates the best cached starting point for a logical cluster index.
@@ -1281,15 +1265,6 @@ static bool Fat32ChainCacheLookupStart(FAT32_FILE* f, uint32_t logicalIndex,
*outCluster = bestCluster;
}
KeReleaseSpinlock(&f->ChainCacheLock, flags);
FAT32_VOL* v = f->Vol;
if (v) {
flags = KeAcquireSpinlock(&v->PathCacheLock);
v->Stats.FatChainCacheLookups++;
if (hit) v->Stats.FatChainCacheHits++;
else v->Stats.FatChainCacheMisses++;
KeReleaseSpinlock(&v->PathCacheLock, flags);
}
return hit;
}
@@ -1309,12 +1284,6 @@ static void Fat32ChainCacheStore(FAT32_FILE* f, uint32_t logicalStart,
e->Length = length;
e->NextCluster = nextCluster;
KeReleaseSpinlock(&f->ChainCacheLock, flags);
FAT32_VOL* v = f->Vol;
if (!v) return;
flags = KeAcquireSpinlock(&v->PathCacheLock);
v->Stats.FatChainCacheStores++;
KeReleaseSpinlock(&v->PathCacheLock, flags);
}
// Resolves a logical file cluster through the cached FAT-chain extent map.
@@ -1331,13 +1300,10 @@ static uint32_t Fat32ResolveFileCluster(FAT32_FILE* f, uint32_t logicalIndex) {
cluster = f->StartCluster;
}
uint64_t steps = 0;
while (currentLogical < logicalIndex && cluster >= 2 && cluster < FAT32_EOC) {
cluster = FatEntry(f->Vol, cluster);
currentLogical++;
steps++;
}
Fat32StatsAddChainWalk(f->Vol, steps);
return cluster;
}
@@ -1355,7 +1321,6 @@ static bool Fat32BuildReadRun(FAT32_FILE* f, uint32_t logicalIndex,
uint32_t runClusters = 1;
uint32_t last = startCluster;
uint32_t next = FatEntry(v, last);
uint64_t steps = 1;
while (next == last + 1 && next < FAT32_EOC &&
runSectors + v->SectorsPerCluster <= maxSectors) {
@@ -1363,10 +1328,8 @@ static bool Fat32BuildReadRun(FAT32_FILE* f, uint32_t logicalIndex,
runClusters++;
runSectors += v->SectorsPerCluster;
next = FatEntry(v, last);
steps++;
}
Fat32StatsAddChainWalk(v, steps);
Fat32ChainCacheStore(f, logicalIndex, startCluster, runClusters, next);
*outRunSectors = runSectors;
*outNextCluster = next;
@@ -1414,8 +1377,8 @@ static int SearchDirV(FAT32_VOL *v, uint32_t dirCluster, SYS_STRING_VIEW name,
if (match) {
*outCluster = ((uint32_t)dir[i].FirstClusterHi << 16) | dir[i].FirstClusterLo;
*outSize = dir[i].FileSize;
*outAttr = dir[i].Attr;
*outSize = dir[i].FileSize;
*outAttr = dir[i].Attr;
if (outDirentLba || outDirentOffset) {
uint64_t eb = (uint64_t)i * sizeof(DIRENT);
if (outDirentLba) *outDirentLba = clusterLba + eb / v->BytesPerSector;
@@ -1453,6 +1416,7 @@ static int Fat32PathNextComponent(SYS_STRING_VIEW path, size_t* cursor,
static KSTATUS Fat32LookupPathV(FAT32_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 || !SysSvIsValid(path))
return STATUS_INVALID_PARAMETER;
@@ -1461,12 +1425,16 @@ static KSTATUS Fat32LookupPathV(FAT32_VOL *v, SYS_STRING_VIEW path,
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 (Fat32PathCacheLookup(v, path, outCluster, outSize, &attr,
outDirentLba, outDirentOffset))
outDirentLba, outDirentOffset)) {
if (outAttr) *outAttr = attr;
return STATUS_SUCCESS;
}
size_t cursor = 0;
SYS_STRING_VIEW component;
@@ -1482,21 +1450,23 @@ static KSTATUS Fat32LookupPathV(FAT32_VOL *v, SYS_STRING_VIEW path,
return STATUS_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;
Fat32PathCacheStore(v, path, cluster, size, attr,
outDirentLba ? *outDirentLba : 0,
outDirentOffset ? *outDirentOffset : 0);
finalDirentLba, finalDirentOffset);
return STATUS_SUCCESS;
}
static KSTATUS TraverseV(FAT32_VOL *v, SYS_STRING_VIEW path,
uint32_t *outCluster, uint32_t *outSize) {
return Fat32LookupPathV(v, path, outCluster, outSize, NULL, NULL);
return Fat32LookupPathV(v, path, outCluster, outSize, NULL, NULL, NULL);
}
static KSTATUS SplitPathV(SYS_STRING_VIEW path, SYS_STRING_VIEW* parent,
@@ -1546,7 +1516,7 @@ static int FindFreeDirent(FAT32_VOL *v, uint32_t dirCluster,
uint8_t first = (uint8_t)dir[i].Name[0];
if (first == 0xE5 || first == 0x00) {
uint64_t eb = (uint64_t)i * sizeof(DIRENT);
*outLba = clusterLba + eb / v->BytesPerSector;
*outLba = clusterLba + eb / v->BytesPerSector;
*outOffset = (uint32_t)(eb % v->BytesPerSector);
MmFreePool(clusterBuf); return 1;
}
@@ -1564,7 +1534,6 @@ static int FindFreeDirent(FAT32_VOL *v, uint32_t dirCluster,
return 0;
}
// Find a contiguous run of free directory entries within one directory cluster.
// This is required for VFAT LFN creation because N long-name entries must
// immediately precede the final short entry.
@@ -1619,6 +1588,284 @@ static int FindFreeDirentRun(FAT32_VOL *v, uint32_t dirCluster, uint32_t needed,
return 0;
}
// Finds the parent directory and final component for a create or delete path.
static KSTATUS Fat32ResolveParentForMutation(FAT32_VOL *v, SYS_STRING_VIEW path,
uint32_t *parentClusterOut,
SYS_STRING_VIEW *filenameOut) {
if (!v || !parentClusterOut || !filenameOut)
return STATUS_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 = Fat32LookupPathV(v, parentPath, &parentCluster, &parentSize,
&parentAttr, NULL, NULL);
if (!KSUCCESS(s)) return s;
if (!(parentAttr & ATTR_DIRECTORY)) return STATUS_NOT_FOUND;
*parentClusterOut = parentCluster;
*filenameOut = filenameView;
return STATUS_SUCCESS;
}
// Creates a FAT directory entry run for a new file or directory.
static KSTATUS Fat32CreateDirent(FAT32_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 STATUS_INVALID_PARAMETER;
uint32_t parentCluster = 0;
SYS_STRING_VIEW filenameView;
KSTATUS s = Fat32ResolveParentForMutation(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 STATUS_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 STATUS_OBJECT_NAME_COLLISION;
} else {
Make83NameV(filenameView, shortName, shortExt);
}
size_t nameLen = filenameView.Length;
if (nameLen == 0 || nameLen >= MAX_LFN) return STATUS_INVALID_PARAMETER;
uint32_t lfnCount = useLfn ? (uint32_t)((nameLen + 12u) / 13u) : 0;
uint32_t neededEntries = lfnCount + 1u;
uint32_t entryCluster, firstIndex;
if (!FindFreeDirentRun(v, parentCluster, neededEntries, &entryCluster, &firstIndex))
return STATUS_NO_MEMORY;
uint8_t *clusterBuf = (uint8_t *)MmAllocatePool(v->BytesPerCluster);
if (!clusterBuf) return STATUS_NO_MEMORY;
uint64_t clusterLba = ClusterToLba(v, entryCluster);
s = ReadSectors(v, clusterLba, v->SectorsPerCluster, clusterBuf);
if (!KSUCCESS(s)) { MmFreePool(clusterBuf); return s; }
DIRENT *dir = (DIRENT *)clusterBuf;
uint32_t shortIndex = firstIndex + lfnCount;
if (useLfn) {
uint8_t checksum = LfnChecksum(shortName, shortExt);
for (uint32_t slot = 0; slot < lfnCount; slot++) {
uint32_t ordinal = lfnCount - slot;
BuildLfnEntry((LFN_ENTRY *)&dir[firstIndex + slot], filenameView.Data,
nameLen, ordinal, lfnCount, checksum);
}
}
DIRENT *d = &dir[shortIndex];
memset(d, 0, sizeof(DIRENT));
memcpy(d->Name, shortName, 8);
memcpy(d->Ext, shortExt, 3);
d->Attr = attr;
d->Reserved = FatShortNameCaseFlagsV(filenameView);
d->FirstClusterHi = (uint16_t)(firstCluster >> 16);
d->FirstClusterLo = (uint16_t)(firstCluster & 0xFFFFu);
d->FileSize = fileSize;
{
FAT32_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 = WriteSectors(v, clusterLba, v->SectorsPerCluster, clusterBuf);
MmFreePool(clusterBuf);
if (!KSUCCESS(s)) return s;
uint64_t shortEntryByte = (uint64_t)shortIndex * sizeof(DIRENT);
uint64_t direntLba = clusterLba + shortEntryByte / v->BytesPerSector;
uint32_t direntOffset = (uint32_t)(shortEntryByte % v->BytesPerSector);
if (direntLbaOut) *direntLbaOut = direntLba;
if (direntOffsetOut) *direntOffsetOut = direntOffset;
Fat32PathCacheInvalidate(v);
return STATUS_SUCCESS;
}
// Initializes one dot entry in a new directory cluster.
static void Fat32InitDotEntry(DIRENT *d, const char *name,
uint32_t firstCluster,
const FAT32_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;
d->FirstClusterHi = (uint16_t)(firstCluster >> 16);
d->FirstClusterLo = (uint16_t)(firstCluster & 0xFFFFu);
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.
static KSTATUS Fat32InitializeDirectoryCluster(FAT32_VOL *v, uint32_t dirCluster,
uint32_t parentCluster) {
if (!v || dirCluster < 2)
return STATUS_INVALID_PARAMETER;
uint8_t *clusterBuf = (uint8_t *)MmAllocatePool(v->BytesPerCluster);
if (!clusterBuf) return STATUS_NO_MEMORY;
memset(clusterBuf, 0, v->BytesPerCluster);
FAT32_ENCODED_TIME now;
FatQueryEncodedTime(&now);
DIRENT *dir = (DIRENT *)clusterBuf;
Fat32InitDotEntry(&dir[0], ".", dirCluster, &now);
Fat32InitDotEntry(&dir[1], "..", parentCluster, &now);
KSTATUS s = WriteSectors(v, ClusterToLba(v, dirCluster),
v->SectorsPerCluster, clusterBuf);
MmFreePool(clusterBuf);
return s;
}
// Tests whether a FAT directory entry is one of the synthetic dot entries.
static int Fat32IsDotEntry(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.
static KSTATUS Fat32DirectoryIsEmpty(FAT32_VOL *v, uint32_t dirCluster,
int *emptyOut) {
if (!v || !emptyOut || dirCluster < 2)
return STATUS_INVALID_PARAMETER;
uint8_t *clusterBuf = (uint8_t *)MmAllocatePool(v->BytesPerCluster);
if (!clusterBuf) return STATUS_NO_MEMORY;
uint32_t cluster = dirCluster;
while (cluster >= 2 && cluster < FAT32_EOC) {
KSTATUS s = ReadSectors(v, ClusterToLba(v, cluster),
v->SectorsPerCluster, clusterBuf);
if (!KSUCCESS(s)) { MmFreePool(clusterBuf); return s; }
uint32_t entries = v->BytesPerCluster / sizeof(DIRENT);
DIRENT *dir = (DIRENT *)clusterBuf;
for (uint32_t i = 0; i < entries; i++) {
uint8_t first = (uint8_t)dir[i].Name[0];
if (first == 0x00) { MmFreePool(clusterBuf); *emptyOut = 1; return STATUS_SUCCESS; }
if (first == 0xE5 || dir[i].Attr == ATTR_LFN) continue;
if ((dir[i].Attr & ATTR_VOLUME_ID) && !(dir[i].Attr & ATTR_DIRECTORY)) continue;
if (Fat32IsDotEntry(&dir[i])) continue;
MmFreePool(clusterBuf);
*emptyOut = 0;
return STATUS_SUCCESS;
}
cluster = FatEntry(v, cluster);
}
MmFreePool(clusterBuf);
*emptyOut = 1;
return STATUS_SUCCESS;
}
// Deletes the directory-entry run for one child name from a parent directory.
static KSTATUS Fat32DeleteDirentRunV(FAT32_VOL *v, uint32_t parentCluster,
SYS_STRING_VIEW name) {
if (!v || !Fat32NameViewIsValid(name))
return STATUS_INVALID_PARAMETER;
uint8_t *clusterBuf = (uint8_t *)MmAllocatePool(v->BytesPerCluster);
if (!clusterBuf) return STATUS_NO_MEMORY;
char lfn[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 cluster = parentCluster;
while (cluster >= 2 && cluster < FAT32_EOC) {
uint64_t clusterLba = ClusterToLba(v, cluster);
KSTATUS s = ReadSectors(v, clusterLba, v->SectorsPerCluster, clusterBuf);
if (!KSUCCESS(s)) { MmFreePool(clusterBuf); return s; }
uint32_t entries = v->BytesPerCluster / sizeof(DIRENT);
DIRENT *dir = (DIRENT *)clusterBuf;
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 STATUS_NOT_FOUND; }
if (first == 0xE5) { hasLfn = 0; memset(lfn, 0, sizeof(lfn)); continue; }
if (dir[i].Attr == ATTR_LFN) {
if (!hasLfn) lfnStart = i;
LfnAppend(lfn, (LFN_ENTRY *)&dir[i]);
hasLfn = 1;
continue;
}
int match = 0;
if (hasLfn && lfn[0]) match = LfnMatchV(lfn, name);
if (!match && allowShortMatch)
match = MatchName83V(dir[i].Name, dir[i].Ext, name);
if (!match && allowShortMatch)
match = MatchRaw83(dir[i].Name, dir[i].Ext, wantName83, wantExt83);
if (match) {
uint32_t start = hasLfn ? lfnStart : i;
for (uint32_t j = start; j <= i; j++)
dir[j].Name[0] = (char)0xE5;
s = WriteSectors(v, clusterLba, v->SectorsPerCluster, clusterBuf);
MmFreePool(clusterBuf);
if (KSUCCESS(s)) Fat32PathCacheInvalidate(v);
return s;
}
hasLfn = 0;
memset(lfn, 0, sizeof(lfn));
}
cluster = FatEntry(v, cluster);
}
MmFreePool(clusterBuf);
return STATUS_NOT_FOUND;
}
// Open / Close.
KSTATUS Fat32Open(IO_FILESYSTEM *fs, SYS_STRING_VIEW path, void **fileCtx) {
if (!fs || !fileCtx || !SysSvIsValid(path) || path.Length == 0)
@@ -1626,7 +1873,7 @@ KSTATUS Fat32Open(IO_FILESYSTEM *fs, SYS_STRING_VIEW path, void **fileCtx) {
FAT32_VOL *v = (FAT32_VOL *)fs->DriverCtx;
uint32_t cluster, size; uint64_t direntLba; uint32_t direntOffset;
KSTATUS s = Fat32LookupPathV(v, path, &cluster, &size, &direntLba, &direntOffset);
KSTATUS s = Fat32LookupPathV(v, path, &cluster, &size, NULL, &direntLba, &direntOffset);
if (!KSUCCESS(s)) return s;
FAT32_FILE *f = (FAT32_FILE *)MmAllocatePool(sizeof(FAT32_FILE));
@@ -1646,7 +1893,7 @@ void Fat32Close(IO_FILESYSTEM *fs, void *fileCtx) {
MmFreePool(fileCtx);
}
// Read (fix #4: contiguous-run coalescing)
// 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.
@@ -1722,7 +1969,7 @@ KSTATUS Fat32Read(IO_FILESYSTEM *fs, void *fileCtx,
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
// or disk read actually supplied. Clear the caller's remainder so stale
// memory cannot be interpreted as file contents.
memset(out, 0, wanted - totalRead);
}
@@ -1734,8 +1981,8 @@ KSTATUS Fat32Read(IO_FILESYSTEM *fs, void *fileCtx,
return STATUS_SUCCESS;
}
// Write (fix #4: contiguous-run coalescing)
// Same strategy as read. Whole-cluster-aligned runs skip the read-modify-
// Write (fix #4: contiguous-run coalescing)
// Same strategy as read. Whole-cluster-aligned runs skip the read-modify-
// write; partial head/tail clusters still require it.
KSTATUS Fat32Write(IO_FILESYSTEM *fs, void *fileCtx,
@@ -1852,121 +2099,99 @@ KSTATUS Fat32Write(IO_FILESYSTEM *fs, void *fileCtx,
}
// Create.
// Creates the requested object.
// Creates the requested file and fails if the name already exists.
KSTATUS Fat32Create(IO_FILESYSTEM *fs, SYS_STRING_VIEW path, void **fileCtx) {
if (!fs || !fileCtx || !SysSvIsValid(path) || path.Length == 0)
return STATUS_INVALID_PARAMETER;
FAT32_VOL *v = (FAT32_VOL *)fs->DriverCtx;
SYS_STRING_VIEW parentPath;
SYS_STRING_VIEW filenameView;
KSTATUS s = SplitPathV(path, &parentPath, &filenameView);
uint64_t direntLba = 0;
uint32_t direntOffset = 0;
KSTATUS s = Fat32CreateDirent(v, path, ATTR_ARCHIVE, 0, 0,
&direntLba, &direntOffset);
if (!KSUCCESS(s)) return s;
uint32_t parentCluster, parentSize;
s = TraverseV(v, parentPath, &parentCluster, &parentSize);
if (!KSUCCESS(s)) return s;
{
uint32_t ec, es; uint8_t ea;
uint64_t existingLba = 0; uint32_t existingOffset = 0;
if (SearchDirV(v, parentCluster, filenameView, &ec, &es, &ea,
&existingLba, &existingOffset)) {
if (ea & ATTR_DIRECTORY)
return STATUS_OBJECT_NAME_COLLISION;
FAT32_FILE *f = (FAT32_FILE *)MmAllocatePool(sizeof(FAT32_FILE));
if (!f) return STATUS_NO_MEMORY;
f->StartCluster = ec;
f->FileSize = es;
f->Vol = v;
f->DirentLba = existingLba;
f->DirentOffset = existingOffset;
// Create means overwrite by default for regular files. Reuse the
// existing directory entry, drop the old cluster chain, and return
// an open zero-length file instead of allocating a duplicate name.
KSTATUS ts = Fat32Truncate(fs, f, 0, 0);
if (!KSUCCESS(ts)) { MmFreePool(f); return ts; }
*fileCtx = f;
return STATUS_SUCCESS;
}
}
int useLfn = FatNameNeedsLfnForCreateV(filenameView);
char shortName[8], shortExt[3];
if (useLfn) {
s = DeleteLegacyBrokenDotAliasV(v, parentCluster, filenameView);
if (!KSUCCESS(s)) return s;
if (!MakeUniqueShortAliasV(v, parentCluster, filenameView, shortName, shortExt))
return STATUS_OBJECT_NAME_COLLISION;
} else {
Make83NameV(filenameView, shortName, shortExt);
}
size_t nameLen = filenameView.Length;
if (nameLen == 0 || nameLen >= MAX_LFN) return STATUS_INVALID_PARAMETER;
uint32_t lfnCount = useLfn ? (uint32_t)((nameLen + 12u) / 13u) : 0;
uint32_t neededEntries = lfnCount + 1u;
uint32_t entryCluster, firstIndex;
if (!FindFreeDirentRun(v, parentCluster, neededEntries, &entryCluster, &firstIndex))
return STATUS_NO_MEMORY;
uint8_t *clusterBuf = (uint8_t *)MmAllocatePool(v->BytesPerCluster);
if (!clusterBuf) return STATUS_NO_MEMORY;
uint64_t clusterLba = ClusterToLba(v, entryCluster);
s = ReadSectors(v, clusterLba, v->SectorsPerCluster, clusterBuf);
if (!KSUCCESS(s)) { MmFreePool(clusterBuf); return s; }
DIRENT *dir = (DIRENT *)clusterBuf;
uint32_t shortIndex = firstIndex + lfnCount;
if (useLfn) {
uint8_t checksum = LfnChecksum(shortName, shortExt);
for (uint32_t slot = 0; slot < lfnCount; slot++) {
uint32_t ordinal = lfnCount - slot;
BuildLfnEntry((LFN_ENTRY *)&dir[firstIndex + slot], filenameView.Data,
nameLen, ordinal, lfnCount, checksum);
}
}
DIRENT *d = &dir[shortIndex];
memset(d, 0, sizeof(DIRENT));
memcpy(d->Name, shortName, 8);
memcpy(d->Ext, shortExt, 3);
d->Attr = ATTR_ARCHIVE;
d->Reserved = FatShortNameCaseFlagsV(filenameView);
{
FAT32_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 = WriteSectors(v, clusterLba, v->SectorsPerCluster, clusterBuf);
MmFreePool(clusterBuf);
if (!KSUCCESS(s)) return s;
uint64_t shortEntryByte = (uint64_t)shortIndex * sizeof(DIRENT);
uint64_t direntLba = clusterLba + shortEntryByte / v->BytesPerSector;
uint32_t direntOffset = (uint32_t)(shortEntryByte % v->BytesPerSector);
FAT32_FILE *f = (FAT32_FILE *)MmAllocatePool(sizeof(FAT32_FILE));
if (!f) return STATUS_NO_MEMORY;
f->StartCluster = 0; f->FileSize = 0; f->Vol = v;
f->DirentLba = direntLba; f->DirentOffset = direntOffset;
f->StartCluster = 0;
f->FileSize = 0;
f->Vol = v;
f->DirentLba = direntLba;
f->DirentOffset = direntOffset;
Fat32FileInitChainCache(f);
Fat32PathCacheInvalidate(v);
*fileCtx = f;
return STATUS_SUCCESS;
}
// Creates a directory and initializes its dot entries.
KSTATUS Fat32CreateDir(IO_FILESYSTEM *fs, SYS_STRING_VIEW path) {
if (!fs || !SysSvIsValid(path) || path.Length == 0)
return STATUS_INVALID_PARAMETER;
FAT32_VOL *v = (FAT32_VOL *)fs->DriverCtx;
uint32_t parentCluster = 0;
SYS_STRING_VIEW filenameView;
KSTATUS s = Fat32ResolveParentForMutation(v, path, &parentCluster, &filenameView);
if (!KSUCCESS(s)) return s;
(void)filenameView;
uint32_t dirCluster = AllocCluster(v);
if (!dirCluster) return STATUS_NO_MEMORY;
s = Fat32InitializeDirectoryCluster(v, dirCluster, parentCluster);
if (!KSUCCESS(s)) {
FreeClusterChain(v, dirCluster);
FlushFatCache(v);
return s;
}
s = Fat32CreateDirent(v, path, ATTR_DIRECTORY, dirCluster, 0, NULL, NULL);
if (!KSUCCESS(s)) {
FreeClusterChain(v, dirCluster);
FlushFatCache(v);
return s;
}
FlushFatCache(v);
return STATUS_SUCCESS;
}
// Removes an empty directory.
KSTATUS Fat32RemoveDir(IO_FILESYSTEM *fs, SYS_STRING_VIEW path) {
if (!fs || !SysSvIsValid(path) || path.Length == 0)
return STATUS_INVALID_PARAMETER;
FAT32_VOL *v = (FAT32_VOL *)fs->DriverCtx;
uint32_t cluster = 0;
uint32_t size = 0;
uint8_t attr = 0;
uint64_t direntLba = 0;
uint32_t direntOffset = 0;
KSTATUS s = Fat32LookupPathV(v, path, &cluster, &size, &attr,
&direntLba, &direntOffset);
if (!KSUCCESS(s)) return s;
if (!direntLba) return STATUS_ACCESS_DENIED;
if (!(attr & ATTR_DIRECTORY)) return STATUS_ACCESS_DENIED;
int empty = 0;
s = Fat32DirectoryIsEmpty(v, cluster, &empty);
if (!KSUCCESS(s)) return s;
if (!empty) return STATUS_ACCESS_DENIED;
uint32_t parentCluster = 0;
SYS_STRING_VIEW filenameView;
s = Fat32ResolveParentForMutation(v, path, &parentCluster, &filenameView);
if (!KSUCCESS(s)) return s;
s = Fat32DeleteDirentRunV(v, parentCluster, filenameView);
if (!KSUCCESS(s)) return s;
if (cluster >= 2) FreeClusterChain(v, cluster);
FlushFatCache(v);
Fat32PathCacheInvalidate(v);
return STATUS_SUCCESS;
}
// Truncate.
KSTATUS Fat32Truncate(IO_FILESYSTEM *fs, void *fileCtx, uint64_t newSize, uint32_t fileFlags) {
(void)fs;
@@ -2044,7 +2269,7 @@ KSTATUS Fat32Delete(IO_FILESYSTEM *fs, SYS_STRING_VIEW path) {
FAT32_VOL *v = (FAT32_VOL *)fs->DriverCtx;
uint32_t cluster, size; uint64_t direntLba; uint32_t direntOffset;
KSTATUS s = Fat32LookupPathV(v, path, &cluster, &size, &direntLba, &direntOffset);
KSTATUS s = Fat32LookupPathV(v, path, &cluster, &size, NULL, &direntLba, &direntOffset);
if (!KSUCCESS(s)) return s;
if (!direntLba) return STATUS_ACCESS_DENIED;
@@ -2066,6 +2291,139 @@ uint64_t Fat32GetSize(IO_FILESYSTEM *fs, void *fileCtx) {
(void)fs; return ((FAT32_FILE *)fileCtx)->FileSize;
}
// Returns a stable volume identity for metadata and cache keys.
static uint64_t Fat32VolumeIdentity(FAT32_VOL *v) {
if (!v) return 0;
return ((uint64_t)v->DriveIndex << 56) ^
((uint64_t)v->PartitionLba << 8) ^
((uint64_t)v->RootCluster << 1) ^
0x4641543332494445ULL;
}
// Converts FAT attributes to the public file type.
static uint32_t Fat32FileTypeFromAttr(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 Fat32FillStatFromDirent(FAT32_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 = Fat32FileTypeFromAttr(d->Attr);
out->Attributes = d->Attr;
out->LinkCount = 1;
out->FileSize = d->FileSize;
out->VolumeId = Fat32VolumeIdentity(v);
uint32_t firstCluster = ((uint32_t)d->FirstClusterHi << 16) | d->FirstClusterLo;
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 Fat32FillRootStat(FAT32_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->VolumeId = Fat32VolumeIdentity(v);
out->FileId = ((uint64_t)v->RootCluster << 1) ^ 0x524f4f54ULL;
}
// Queries metadata for an open FAT32 file.
KSTATUS Fat32QueryInfo(IO_FILESYSTEM *fs, void *fileCtx, SYS_FILE_STAT *out) {
(void)fs;
if (!fileCtx || !out) return STATUS_INVALID_PARAMETER;
FAT32_FILE *f = (FAT32_FILE *)fileCtx;
FAT32_VOL *v = f->Vol;
if (!v) return STATUS_INVALID_PARAMETER;
if (f->DirentLba == 0) {
if (f->StartCluster == v->RootCluster && f->FileSize == 0) {
Fat32FillRootStat(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->VolumeId = Fat32VolumeIdentity(v);
out->FileId = ((uint64_t)f->StartCluster << 1);
}
return STATUS_SUCCESS;
}
uint8_t *sec = (uint8_t *)MmAllocatePool(v->BytesPerSector);
if (!sec) return STATUS_NO_MEMORY;
KSTATUS s = ReadSectors(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 STATUS_NOT_FOUND;
}
Fat32FillStatFromDirent(v, d, f->DirentLba, f->DirentOffset, out);
f->StartCluster = ((uint32_t)d->FirstClusterHi << 16) | d->FirstClusterLo;
f->FileSize = d->FileSize;
MmFreePool(sec);
return STATUS_SUCCESS;
}
// Queries metadata for a FAT32 path.
KSTATUS Fat32Stat(IO_FILESYSTEM *fs, SYS_STRING_VIEW path, SYS_FILE_STAT *out) {
if (!fs || !out || !SysSvIsValid(path)) return STATUS_INVALID_PARAMETER;
FAT32_VOL *v = (FAT32_VOL *)fs->DriverCtx;
if (!v) return STATUS_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 = Fat32LookupPathV(v, path, &cluster, &size, &attr, &direntLba, &direntOffset);
if (!KSUCCESS(s)) return s;
(void)cluster;
if (direntLba == 0) {
Fat32FillRootStat(v, out);
return STATUS_SUCCESS;
}
uint8_t *sec = (uint8_t *)MmAllocatePool(v->BytesPerSector);
if (!sec) return STATUS_NO_MEMORY;
s = ReadSectors(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 STATUS_NOT_FOUND;
}
Fat32FillStatFromDirent(v, d, direntLba, direntOffset, out);
out->Type = Fat32FileTypeFromAttr(attr);
out->FileSize = size;
MmFreePool(sec);
return STATUS_SUCCESS;
}
// Mixes FAT directory metadata into a per-file content generation.
static uint64_t Fat32DirentGeneration(const DIRENT *d) {
if (!d)
@@ -2123,10 +2481,7 @@ KSTATUS Fat32QueryIdentity(IO_FILESYSTEM *fs, void *fileCtx, IO_FILE_IDENTITY *o
MmFreePool(sec);
}
out->VolumeId = ((uint64_t)v->DriveIndex << 56) ^
((uint64_t)v->PartitionLba << 8) ^
((uint64_t)v->RootCluster << 1) ^
0x4641543332494445ULL;
out->VolumeId = Fat32VolumeIdentity(v);
out->FileId = f->DirentLba ^
((uint64_t)f->DirentOffset << 32) ^
((uint64_t)startCluster << 1);
@@ -2135,7 +2490,7 @@ KSTATUS Fat32QueryIdentity(IO_FILESYSTEM *fs, void *fileCtx, IO_FILE_IDENTITY *o
return STATUS_SUCCESS;
}
// Directory enumeration (fix #2: cluster buffer cached in context)
// Directory enumeration (fix #2: cluster buffer cached in context)
// Previously: allocate + ReadSectors + return 1 entry + free, every call.
// Now: buffer lives in FAT32_DIR_CTX; ReadSectors fires only on cluster
// boundaries.
@@ -2418,7 +2773,7 @@ static uint32_t Fat32EnumerateDiskVolumes(uint8_t diskIndex,
uint32_t size = ReadLe32(entry + 12);
if (!start || !size) continue;
// FAT32 CHS/LBA plus EFI System Partition. The VBR probe is the
// FAT32 CHS/LBA plus EFI System Partition. The VBR probe is the
// authority, so accepting 0xEF here lets FAT32 ESPs mount too.
if (type == 0x0B || type == 0x0C || type == 0xEF)
Fat32AddCandidate(&probe, out, &count, maxCount, start, size);
@@ -2928,7 +3283,7 @@ static uint32_t Fat32TryMountDisk(uint8_t disk) {
return 0;
}
// Only remember a completed probe. A transient hotplug/readiness failure
// Only remember a completed probe. A transient hotplug/readiness failure
// must not permanently suppress a later mount attempt for the same disk
// index after a device is unplugged and replugged.
gDiskScanned[disk] = true;
@@ -3029,7 +3384,7 @@ static void Fat32UnmountDisk(uint8_t diskIndex, IO_DEVICE_CHANGE change) {
continue;
// Surprise removal means the backing device may already be gone; do not
// attempt writeback here. Drop cached FAT sectors instead.
// attempt writeback here. Drop cached FAT sectors instead.
FatCacheInvalidate();
(void)IoUnregisterFilesystem(i);
memset(&gVols[i], 0, sizeof(gVols[i]));