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