466 lines
15 KiB
C
466 lines
15 KiB
C
// FAT asynchronous file I/O.
|
|
|
|
#include "fat_internal.h"
|
|
|
|
// Native async read/write
|
|
// Metadata work (FAT traversal/allocation and partial-cluster read-modify
|
|
// preparation) still runs synchronously in the caller/completion context, but
|
|
// file data transfers are submitted as disk IRPs and completion is chained from
|
|
// the driver's IRP completion callback instead of the generic I/O worker.
|
|
|
|
typedef struct FAT_ASYNC_RW {
|
|
LIST_ENTRY Link;
|
|
FAT_VOL* Vol;
|
|
FAT_FILE* File;
|
|
uint8_t IsWrite;
|
|
|
|
uint64_t Offset;
|
|
uint32_t OffsetInCluster;
|
|
uint32_t Cluster;
|
|
uint32_t LogicalCluster;
|
|
size_t Remaining;
|
|
size_t Done;
|
|
size_t Requested;
|
|
|
|
uint8_t* Dst;
|
|
const uint8_t* Src;
|
|
uint8_t* RunBuf;
|
|
|
|
uint32_t CurrentNext;
|
|
uint32_t CurrentLast;
|
|
uint32_t CurrentClusters;
|
|
uint32_t CurrentOffset;
|
|
size_t CurrentCopy;
|
|
|
|
IRP Irp;
|
|
DEVICE_OBJECT* CurrentDevice;
|
|
IO_FS_ASYNC_COMPLETE Complete;
|
|
void* CompleteContext;
|
|
} FAT_ASYNC_RW;
|
|
|
|
static SPINLOCK gFatAsyncLock = SPINLOCK_INIT;
|
|
static LIST_ENTRY gFatAsyncQueue;
|
|
static SEMAPHORE* gFatAsyncSemaphore;
|
|
static volatile int gFatAsyncInitState;
|
|
|
|
static void FatAsyncReadSubmitNext(FAT_ASYNC_RW* ctx);
|
|
static void FatAsyncWriteSubmitNext(FAT_ASYNC_RW* ctx);
|
|
|
|
// Implements the async Worker operation.
|
|
static void FatAsyncWorker(void* arg) {
|
|
(void)arg;
|
|
|
|
while (true) {
|
|
KSTATUS s = KeWaitSemaphore(gFatAsyncSemaphore, KE_WAIT_INFINITE);
|
|
if (!KSUCCESS(s)) {
|
|
KeYield();
|
|
continue;
|
|
}
|
|
|
|
uintptr_t flags = KeAcquireSpinlock(&gFatAsyncLock);
|
|
LIST_ENTRY* node = ListPopHead(&gFatAsyncQueue);
|
|
KeReleaseSpinlock(&gFatAsyncLock, flags);
|
|
if (!node) continue;
|
|
|
|
FAT_ASYNC_RW* ctx = CONTAINING_RECORD(node, FAT_ASYNC_RW, Link);
|
|
if (ctx->IsWrite)
|
|
FatAsyncWriteSubmitNext(ctx);
|
|
else
|
|
FatAsyncReadSubmitNext(ctx);
|
|
}
|
|
}
|
|
|
|
// Implements the ensure Async Worker Started operation.
|
|
static KSTATUS FatEnsureAsyncWorkerStarted(void) {
|
|
uintptr_t flags = KeAcquireSpinlock(&gFatAsyncLock);
|
|
if (gFatAsyncInitState == 2) {
|
|
KeReleaseSpinlock(&gFatAsyncLock, flags);
|
|
return NTLSTATUS_SUCCESS;
|
|
}
|
|
if (gFatAsyncInitState == 1) {
|
|
KeReleaseSpinlock(&gFatAsyncLock, flags);
|
|
while (gFatAsyncInitState == 1)
|
|
KeYield();
|
|
return gFatAsyncInitState == 2 ? NTLSTATUS_SUCCESS : NTLSTATUS_FAILURE;
|
|
}
|
|
|
|
gFatAsyncInitState = 1;
|
|
LIST_INIT(&gFatAsyncQueue);
|
|
KeReleaseSpinlock(&gFatAsyncLock, flags);
|
|
|
|
KSTATUS s = KeSemaphoreCreate(0, 0x7fffffff, &gFatAsyncSemaphore);
|
|
if (KSUCCESS(s)) {
|
|
THREAD* worker = (THREAD*)0;
|
|
const KE_THREAD_CREATE_PARAMETERS parameters =
|
|
KE_THREAD_CREATE_NAMED_NORMAL(PriorityNormal, "fat.async");
|
|
s = KeCreateKernelThread((uintptr_t)FatAsyncWorker, 0,
|
|
¶meters, &worker);
|
|
(void)worker;
|
|
}
|
|
|
|
flags = KeAcquireSpinlock(&gFatAsyncLock);
|
|
gFatAsyncInitState = KSUCCESS(s) ? 2 : 0;
|
|
KeReleaseSpinlock(&gFatAsyncLock, flags);
|
|
return s;
|
|
}
|
|
|
|
// Implements the async Queue Continuation operation.
|
|
static void FatAsyncQueueContinuation(FAT_ASYNC_RW* ctx) {
|
|
if (!ctx) return;
|
|
|
|
uintptr_t flags = KeAcquireSpinlock(&gFatAsyncLock);
|
|
ListInsertTail(&gFatAsyncQueue, &ctx->Link);
|
|
KeReleaseSpinlock(&gFatAsyncLock, flags);
|
|
KeSignalSemaphore(gFatAsyncSemaphore);
|
|
}
|
|
|
|
// Implements the async Finish operation.
|
|
static void FatAsyncFinish(FAT_ASYNC_RW* ctx, KSTATUS status, size_t information) {
|
|
if (!ctx) return;
|
|
if (ctx->CurrentDevice) {
|
|
IoDereferenceDeviceObject(ctx->CurrentDevice);
|
|
ctx->CurrentDevice = (DEVICE_OBJECT*)0;
|
|
}
|
|
if (ctx->RunBuf) MmFreePool(ctx->RunBuf);
|
|
IO_FS_ASYNC_COMPLETE complete = ctx->Complete;
|
|
void* completeContext = ctx->CompleteContext;
|
|
MmFreePool(ctx);
|
|
if (complete) complete(completeContext, status, information);
|
|
}
|
|
|
|
// Implements the async Read Irp Complete operation.
|
|
static void FatAsyncReadIrpComplete(IRP* irp, void* completeCtx) {
|
|
FAT_ASYNC_RW* ctx = (FAT_ASYNC_RW*)completeCtx;
|
|
if (!ctx) return;
|
|
if (ctx->CurrentDevice) {
|
|
IoDereferenceDeviceObject(ctx->CurrentDevice);
|
|
ctx->CurrentDevice = (DEVICE_OBJECT*)0;
|
|
}
|
|
|
|
if (!KSUCCESS(irp->Status)) {
|
|
FatAsyncFinish(ctx, irp->Status, ctx->Done);
|
|
return;
|
|
}
|
|
|
|
memcpy(ctx->Dst, ctx->RunBuf + ctx->CurrentOffset, ctx->CurrentCopy);
|
|
ctx->Dst += ctx->CurrentCopy;
|
|
ctx->Done += ctx->CurrentCopy;
|
|
ctx->Remaining -= ctx->CurrentCopy;
|
|
ctx->OffsetInCluster = 0;
|
|
ctx->LogicalCluster += ctx->CurrentClusters;
|
|
ctx->Cluster = ctx->CurrentNext;
|
|
if (ctx->CurrentClusters) {
|
|
uint32_t sectors = ctx->CurrentClusters * ctx->Vol->SectorsPerCluster;
|
|
uint32_t merges = ctx->CurrentClusters - 1u;
|
|
FatStatsAddReadRun(ctx->Vol, sectors, merges);
|
|
}
|
|
|
|
FatAsyncQueueContinuation(ctx);
|
|
}
|
|
|
|
// Implements the async Read Submit Next operation.
|
|
static void FatAsyncReadSubmitNext(FAT_ASYNC_RW* ctx) {
|
|
if (ctx->Remaining == 0) {
|
|
FatAsyncFinish(ctx, NTLSTATUS_SUCCESS, ctx->Done);
|
|
return;
|
|
}
|
|
|
|
if (!FatIsDataCluster(ctx->Vol, ctx->Cluster)) {
|
|
if (ctx->Done < ctx->Requested)
|
|
memset(ctx->Dst, 0, ctx->Requested - ctx->Done);
|
|
FatAsyncFinish(ctx, NTLSTATUS_DEVICE_ERROR, ctx->Done);
|
|
return;
|
|
}
|
|
|
|
FAT_VOL* v = ctx->Vol;
|
|
uint32_t runStart = ctx->Cluster;
|
|
uint32_t runSectors = 0;
|
|
uint32_t next = 0;
|
|
if (!FatBuildReadRun(ctx->File, ctx->LogicalCluster, ctx->Cluster,
|
|
FAT_READ_RUN_MAX_SECTORS, &runSectors, &next)) {
|
|
FatAsyncFinish(ctx, NTLSTATUS_DEVICE_ERROR, ctx->Done);
|
|
return;
|
|
}
|
|
|
|
uint32_t runBytes = runSectors * 512u;
|
|
size_t avail = runBytes - ctx->OffsetInCluster;
|
|
size_t copy = avail < ctx->Remaining ? avail : ctx->Remaining;
|
|
|
|
ctx->CurrentNext = next;
|
|
ctx->CurrentLast = runStart + (runSectors / v->SectorsPerCluster) - 1u;
|
|
ctx->CurrentClusters = runSectors / v->SectorsPerCluster;
|
|
ctx->CurrentOffset = ctx->OffsetInCluster;
|
|
ctx->CurrentCopy = copy;
|
|
|
|
ctx->CurrentDevice = IoReferenceDiskDevice(v->DriveIndex);
|
|
if (!ctx->CurrentDevice) {
|
|
FatAsyncFinish(ctx, NTLSTATUS_NOT_FOUND, ctx->Done);
|
|
return;
|
|
}
|
|
|
|
IoBuildReadWriteIrp(&ctx->Irp, IRP_MJ_READ,
|
|
FatClusterToLba(v, runStart) * 512ull,
|
|
runSectors * 512u,
|
|
ctx->RunBuf);
|
|
ctx->Irp.CompletionFn = FatAsyncReadIrpComplete;
|
|
ctx->Irp.CompletionCtx = ctx;
|
|
|
|
KSTATUS s = IoSubmitIrp(ctx->CurrentDevice, &ctx->Irp);
|
|
if (s != NTLSTATUS_PENDING) {
|
|
IoDereferenceDeviceObject(ctx->CurrentDevice);
|
|
ctx->CurrentDevice = (DEVICE_OBJECT*)0;
|
|
FatAsyncFinish(ctx, s, ctx->Done);
|
|
}
|
|
}
|
|
|
|
KSTATUS FatReadAsync(IO_VOLUME_PROVIDER* fs, void* fileCtx,
|
|
uint64_t offset, void* buf, size_t size,
|
|
IO_FS_ASYNC_COMPLETE complete,
|
|
void* completeContext) {
|
|
(void)fs;
|
|
if (!complete) return NTLSTATUS_INVALID_PARAMETER;
|
|
if (!fileCtx || (!buf && size)) {
|
|
complete(completeContext, NTLSTATUS_INVALID_PARAMETER, 0);
|
|
return NTLSTATUS_PENDING;
|
|
}
|
|
|
|
FAT_FILE* f = (FAT_FILE*)fileCtx;
|
|
FAT_VOL* v = f->Vol;
|
|
if (offset >= f->FileSize || size == 0) {
|
|
complete(completeContext, NTLSTATUS_SUCCESS, 0);
|
|
return NTLSTATUS_PENDING;
|
|
}
|
|
if (offset + size > f->FileSize)
|
|
size = (size_t)(f->FileSize - offset);
|
|
|
|
KSTATUS ws = FatEnsureAsyncWorkerStarted();
|
|
if (!KSUCCESS(ws)) {
|
|
complete(completeContext, ws, 0);
|
|
return NTLSTATUS_PENDING;
|
|
}
|
|
|
|
FAT_ASYNC_RW* ctx = (FAT_ASYNC_RW*)MmAllocatePool(sizeof(FAT_ASYNC_RW));
|
|
if (!ctx) {
|
|
complete(completeContext, NTLSTATUS_NO_MEMORY, 0);
|
|
return NTLSTATUS_PENDING;
|
|
}
|
|
memset(ctx, 0, sizeof(*ctx));
|
|
LIST_INIT(&ctx->Link);
|
|
|
|
ctx->RunBuf = (uint8_t*)MmAllocatePool(FAT_READ_RUN_MAX_SECTORS * 512u);
|
|
if (!ctx->RunBuf) {
|
|
MmFreePool(ctx);
|
|
complete(completeContext, NTLSTATUS_NO_MEMORY, 0);
|
|
return NTLSTATUS_PENDING;
|
|
}
|
|
|
|
ctx->Vol = v;
|
|
ctx->File = f;
|
|
ctx->IsWrite = 0;
|
|
ctx->Offset = offset;
|
|
ctx->OffsetInCluster = (uint32_t)(offset % v->BytesPerCluster);
|
|
ctx->LogicalCluster = (uint32_t)(offset / v->BytesPerCluster);
|
|
ctx->Cluster = FatResolveFileCluster(f, ctx->LogicalCluster);
|
|
ctx->Remaining = size;
|
|
ctx->Requested = size;
|
|
ctx->Dst = (uint8_t*)buf;
|
|
ctx->Complete = complete;
|
|
ctx->CompleteContext = completeContext;
|
|
|
|
FatAsyncQueueContinuation(ctx);
|
|
return NTLSTATUS_PENDING;
|
|
}
|
|
|
|
// Implements the async Write Irp Complete operation.
|
|
static void FatAsyncWriteIrpComplete(IRP* irp, void* completeCtx) {
|
|
FAT_ASYNC_RW* ctx = (FAT_ASYNC_RW*)completeCtx;
|
|
if (!ctx) return;
|
|
if (ctx->CurrentDevice) {
|
|
IoDereferenceDeviceObject(ctx->CurrentDevice);
|
|
ctx->CurrentDevice = (DEVICE_OBJECT*)0;
|
|
}
|
|
|
|
if (!KSUCCESS(irp->Status)) {
|
|
FatAsyncFinish(ctx, irp->Status, ctx->Done);
|
|
return;
|
|
}
|
|
|
|
ctx->Src += ctx->CurrentCopy;
|
|
ctx->Done += ctx->CurrentCopy;
|
|
ctx->Remaining -= ctx->CurrentCopy;
|
|
ctx->OffsetInCluster = 0;
|
|
ctx->Cluster = ctx->CurrentNext;
|
|
|
|
FatAsyncQueueContinuation(ctx);
|
|
}
|
|
|
|
// Implements the async Write Submit Next operation.
|
|
static void FatAsyncWriteSubmitNext(FAT_ASYNC_RW* ctx) {
|
|
if (ctx->Remaining == 0) {
|
|
uint64_t newEnd = ctx->Offset + ctx->Done;
|
|
if (ctx->Done > 0) {
|
|
FAT_DIRENT_UPDATE u;
|
|
memset(&u, 0, sizeof(u));
|
|
FatQueryEncodedTime(&u.Timestamp);
|
|
u.Flags = FAT_DIRENT_UPDATE_WRITE_TIME;
|
|
if (newEnd > ctx->File->FileSize) {
|
|
ctx->File->FileSize = (uint32_t)newEnd;
|
|
u.Flags |= FAT_DIRENT_UPDATE_SIZE;
|
|
u.Size = ctx->File->FileSize;
|
|
}
|
|
FatDirentUpdateMetadata(ctx->Vol, ctx->File->DirentLba,
|
|
ctx->File->DirentOffset, &u);
|
|
}
|
|
FatAsyncFinish(ctx, NTLSTATUS_SUCCESS, ctx->Done);
|
|
return;
|
|
}
|
|
|
|
FAT_VOL* v = ctx->Vol;
|
|
if (!FatIsDataCluster(ctx->Vol, ctx->Cluster)) {
|
|
if (FatIsDataCluster(ctx->Vol, ctx->CurrentLast)) {
|
|
ctx->Cluster = FatAppendCluster(v, ctx->CurrentLast);
|
|
if (!ctx->Cluster) {
|
|
FatAsyncFinish(ctx, NTLSTATUS_NO_MEMORY, ctx->Done);
|
|
return;
|
|
}
|
|
} else {
|
|
FatAsyncFinish(ctx, NTLSTATUS_DEVICE_ERROR, ctx->Done);
|
|
return;
|
|
}
|
|
}
|
|
|
|
uint32_t runStart = ctx->Cluster;
|
|
uint32_t runSectors = v->SectorsPerCluster;
|
|
uint32_t last = ctx->Cluster;
|
|
uint32_t next = FatEntry(v, last);
|
|
|
|
if (ctx->OffsetInCluster == 0) {
|
|
while (next == last + 1 && FatIsDataCluster(v, next) &&
|
|
runSectors + v->SectorsPerCluster <= FAT_WRITE_RUN_MAX_SECTORS) {
|
|
last = next;
|
|
next = FatEntry(v, last);
|
|
runSectors += v->SectorsPerCluster;
|
|
}
|
|
}
|
|
|
|
uint32_t runBytes = runSectors * 512u;
|
|
size_t avail = runBytes - ctx->OffsetInCluster;
|
|
size_t copy = avail < ctx->Remaining ? avail : ctx->Remaining;
|
|
|
|
bool partial = (ctx->OffsetInCluster > 0) || (copy < (size_t)runBytes);
|
|
if (partial) {
|
|
KSTATUS rs = FatReadSectors(v, FatClusterToLba(v, runStart), runSectors, ctx->RunBuf);
|
|
if (!KSUCCESS(rs)) {
|
|
FatAsyncFinish(ctx, rs, ctx->Done);
|
|
return;
|
|
}
|
|
}
|
|
|
|
memcpy(ctx->RunBuf + ctx->OffsetInCluster, ctx->Src, copy);
|
|
|
|
ctx->CurrentNext = next;
|
|
ctx->CurrentLast = last;
|
|
ctx->CurrentOffset = ctx->OffsetInCluster;
|
|
ctx->CurrentCopy = copy;
|
|
|
|
ctx->CurrentDevice = IoReferenceDiskDevice(v->DriveIndex);
|
|
if (!ctx->CurrentDevice) {
|
|
FatAsyncFinish(ctx, NTLSTATUS_NOT_FOUND, ctx->Done);
|
|
return;
|
|
}
|
|
|
|
IoBuildReadWriteIrp(&ctx->Irp, IRP_MJ_WRITE,
|
|
FatClusterToLba(v, runStart) * 512ull,
|
|
runSectors * 512u,
|
|
ctx->RunBuf);
|
|
ctx->Irp.CompletionFn = FatAsyncWriteIrpComplete;
|
|
ctx->Irp.CompletionCtx = ctx;
|
|
|
|
KSTATUS s = IoSubmitIrp(ctx->CurrentDevice, &ctx->Irp);
|
|
if (s != NTLSTATUS_PENDING) {
|
|
IoDereferenceDeviceObject(ctx->CurrentDevice);
|
|
ctx->CurrentDevice = (DEVICE_OBJECT*)0;
|
|
FatAsyncFinish(ctx, s, ctx->Done);
|
|
}
|
|
}
|
|
|
|
KSTATUS FatWriteAsync(IO_VOLUME_PROVIDER* fs, void* fileCtx,
|
|
uint64_t offset, const void* buf, size_t size,
|
|
IO_FS_ASYNC_COMPLETE complete,
|
|
void* completeContext) {
|
|
(void)fs;
|
|
if (!complete) return NTLSTATUS_INVALID_PARAMETER;
|
|
if (!fileCtx || (!buf && size)) {
|
|
complete(completeContext, NTLSTATUS_INVALID_PARAMETER, 0);
|
|
return NTLSTATUS_PENDING;
|
|
}
|
|
if (size == 0) {
|
|
complete(completeContext, NTLSTATUS_SUCCESS, 0);
|
|
return NTLSTATUS_PENDING;
|
|
}
|
|
|
|
KSTATUS ws = FatEnsureAsyncWorkerStarted();
|
|
if (!KSUCCESS(ws)) {
|
|
complete(completeContext, ws, 0);
|
|
return NTLSTATUS_PENDING;
|
|
}
|
|
|
|
FAT_FILE* f = (FAT_FILE*)fileCtx;
|
|
FAT_VOL* v = f->Vol;
|
|
FatFileInvalidateChainCache(f);
|
|
|
|
if (f->StartCluster < 2) {
|
|
uint32_t first = FatAllocCluster(v);
|
|
if (!first) {
|
|
complete(completeContext, NTLSTATUS_NO_MEMORY, 0);
|
|
return NTLSTATUS_PENDING;
|
|
}
|
|
f->StartCluster = first;
|
|
FatDirentUpdateFirstCluster(v, f->DirentLba, f->DirentOffset, first);
|
|
}
|
|
|
|
uint32_t clusterIndex = (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) {
|
|
complete(completeContext, NTLSTATUS_NO_MEMORY, 0);
|
|
return NTLSTATUS_PENDING;
|
|
}
|
|
}
|
|
cluster = next;
|
|
}
|
|
|
|
FAT_ASYNC_RW* ctx = (FAT_ASYNC_RW*)MmAllocatePool(sizeof(FAT_ASYNC_RW));
|
|
if (!ctx) {
|
|
complete(completeContext, NTLSTATUS_NO_MEMORY, 0);
|
|
return NTLSTATUS_PENDING;
|
|
}
|
|
memset(ctx, 0, sizeof(*ctx));
|
|
LIST_INIT(&ctx->Link);
|
|
|
|
ctx->RunBuf = (uint8_t*)MmAllocatePool(FAT_WRITE_RUN_MAX_SECTORS * 512u);
|
|
if (!ctx->RunBuf) {
|
|
MmFreePool(ctx);
|
|
complete(completeContext, NTLSTATUS_NO_MEMORY, 0);
|
|
return NTLSTATUS_PENDING;
|
|
}
|
|
|
|
ctx->Vol = v;
|
|
ctx->File = f;
|
|
ctx->IsWrite = 1;
|
|
ctx->Offset = offset;
|
|
ctx->OffsetInCluster = (uint32_t)(offset % v->BytesPerCluster);
|
|
ctx->Cluster = cluster;
|
|
ctx->Remaining = size;
|
|
ctx->Requested = size;
|
|
ctx->Src = (const uint8_t*)buf;
|
|
ctx->Complete = complete;
|
|
ctx->CompleteContext = completeContext;
|
|
|
|
FatAsyncQueueContinuation(ctx);
|
|
return NTLSTATUS_PENDING;
|
|
}
|