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:
1 parent
2c4a68a75f
commit
ed3d065abd
156 files changed
+10509
-2643
No files matched your search
+529
-174
@@ -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]));
|
||||
|
||||
@@ -28,9 +28,13 @@ KSTATUS Fat32Write(IO_FILESYSTEM *fs, void *fileCtx, uint64_t offset, const void
|
||||
KSTATUS Fat32ReadAsync(IO_FILESYSTEM *fs, void *fileCtx, uint64_t offset, void *buf, size_t size, IO_FS_ASYNC_COMPLETE complete, void *completeContext);
|
||||
KSTATUS Fat32WriteAsync(IO_FILESYSTEM *fs, void *fileCtx, uint64_t offset, const void *buf, size_t size, IO_FS_ASYNC_COMPLETE complete, void *completeContext);
|
||||
KSTATUS Fat32Create(IO_FILESYSTEM *fs, SYS_STRING_VIEW path, void **fileCtx);
|
||||
KSTATUS Fat32CreateDir(IO_FILESYSTEM *fs, SYS_STRING_VIEW path);
|
||||
KSTATUS Fat32RemoveDir(IO_FILESYSTEM *fs, SYS_STRING_VIEW path);
|
||||
KSTATUS Fat32Delete(IO_FILESYSTEM *fs, SYS_STRING_VIEW path);
|
||||
KSTATUS Fat32Truncate(IO_FILESYSTEM *fs, void *fileCtx, uint64_t newSize, uint32_t fileFlags);
|
||||
uint64_t Fat32GetSize(IO_FILESYSTEM *fs, void *fileCtx);
|
||||
KSTATUS Fat32QueryInfo(IO_FILESYSTEM *fs, void *fileCtx, SYS_FILE_STAT *out);
|
||||
KSTATUS Fat32Stat(IO_FILESYSTEM *fs, SYS_STRING_VIEW path, SYS_FILE_STAT *out);
|
||||
KSTATUS Fat32QueryIdentity(IO_FILESYSTEM *fs, void *fileCtx, IO_FILE_IDENTITY *out);
|
||||
KSTATUS Fat32OpenDir(IO_FILESYSTEM *fs, SYS_STRING_VIEW dirPath, void **dirCtx);
|
||||
KSTATUS Fat32ReadDir(IO_FILESYSTEM *fs, void *dirCtx, KDIR_ENTRY *out);
|
||||
|
||||
@@ -12,8 +12,12 @@ void Fat32InitVtable(IO_FILESYSTEM* fs, FAT32_VOL* vol) {
|
||||
fs->WriteAsync = Fat32WriteAsync;
|
||||
fs->Create = Fat32Create;
|
||||
fs->Delete = Fat32Delete;
|
||||
fs->CreateDir = Fat32CreateDir;
|
||||
fs->RemoveDir = Fat32RemoveDir;
|
||||
fs->Truncate = Fat32Truncate;
|
||||
fs->GetSize = Fat32GetSize;
|
||||
fs->QueryInfo = Fat32QueryInfo;
|
||||
fs->Stat = Fat32Stat;
|
||||
fs->QueryIdentity = Fat32QueryIdentity;
|
||||
fs->Close = Fat32Close;
|
||||
fs->DriverCtx = vol;
|
||||
|
||||
Reference in new issue
Block a user