implement proper IRQL, DPCs, kernel and user APCs, alertable waits, async file I/O with cancellation, trap frames, selftests, a real driver/device model, native async FAT32, VFAT dotfile fixes, syscall ABI cleanup, async_apc and async_cancel tests, POSIX multi-volume paths, and pimp the readme.
This commit is contained in:
1 parent
f5fd7bdd75
commit
529f816166
75 files changed
+9969
-5675
No files matched your search
+500
-52
@@ -6,6 +6,12 @@
|
||||
#include <mm_virt.h>
|
||||
#include <ob_object.h>
|
||||
#include <ke_userenv.h>
|
||||
#include <ke_irql.h>
|
||||
#include <ke_event.h>
|
||||
#include <ke_apc.h>
|
||||
#include <ke_semaphore.h>
|
||||
#include <ke_thread.h>
|
||||
#include <ke_sched.h>
|
||||
|
||||
#define MAX_DRIVES 8
|
||||
|
||||
@@ -17,11 +23,389 @@ typedef struct {
|
||||
static VOLUME_ENTRY gVolumes[MAX_DRIVES];
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Current Working Directory (drive 0 relative, no trailing backslash)
|
||||
// e.g. "" = root, "apps" = root\apps, "apps\\data" = nested
|
||||
// Async file I/O worker
|
||||
// ---------------------------------------------------------------------------
|
||||
typedef enum IO_ASYNC_FILE_OP {
|
||||
IoAsyncFileRead = 0,
|
||||
IoAsyncFileWrite = 1,
|
||||
} IO_ASYNC_FILE_OP;
|
||||
|
||||
typedef enum IO_ASYNC_FILE_STATE {
|
||||
IoAsyncStateQueued = 0,
|
||||
IoAsyncStateRunning = 1,
|
||||
IoAsyncStateCompleted = 2,
|
||||
IoAsyncStateCancelled = 3,
|
||||
} IO_ASYNC_FILE_STATE;
|
||||
|
||||
struct IO_ASYNC_FILE_REQUEST_OBJECT {
|
||||
OBJECT Header; // ObjectTypeAsyncIo
|
||||
LIST_ENTRY Link; // protected by gAsyncFileQueueLock
|
||||
|
||||
IO_ASYNC_FILE_OP Op;
|
||||
IO_ASYNC_FILE_STATE State;
|
||||
uint8_t CancelRequested;
|
||||
|
||||
FILE_OBJECT* File; // referenced until object destruction
|
||||
EVENT* CompletionEvent; // optional, referenced until object destruction
|
||||
KAPC* CompletionApc; // caller-owned APC storage
|
||||
IO_STATUS_BLOCK* Iosb; // caller/user-owned completion block
|
||||
|
||||
void* Buffer; // caller/user-owned; must remain valid until completion/cancel
|
||||
size_t Size;
|
||||
uint64_t Offset;
|
||||
};
|
||||
|
||||
static SPINLOCK gAsyncFileInitLock = SPINLOCK_INIT;
|
||||
static int gAsyncFileInitState; // 0=off, 1=starting, 2=ready
|
||||
static SPINLOCK gAsyncFileQueueLock = SPINLOCK_INIT;
|
||||
static LIST_ENTRY gAsyncFileQueue;
|
||||
static SEMAPHORE* gAsyncFileSemaphore;
|
||||
|
||||
static void KiIoAsyncFileRequestDestroy(OBJECT* obj) {
|
||||
IO_ASYNC_FILE_REQUEST_OBJECT* req =
|
||||
CONTAINING_RECORD(obj, IO_ASYNC_FILE_REQUEST_OBJECT, Header);
|
||||
|
||||
if (req->CompletionEvent) {
|
||||
ObDereferenceObject(&req->CompletionEvent->Header);
|
||||
req->CompletionEvent = (EVENT*)0;
|
||||
}
|
||||
if (req->File) {
|
||||
ObDereferenceObject(&req->File->Header);
|
||||
req->File = (FILE_OBJECT*)0;
|
||||
}
|
||||
}
|
||||
|
||||
static void KiIoCompleteAsyncFileRequest(IO_ASYNC_FILE_REQUEST_OBJECT* req,
|
||||
KSTATUS status,
|
||||
size_t information) {
|
||||
if (!req) return;
|
||||
|
||||
uintptr_t flags = KeAcquireSpinlock(&gAsyncFileQueueLock);
|
||||
req->State = (status == STATUS_CANCELLED) ? IoAsyncStateCancelled : IoAsyncStateCompleted;
|
||||
KeReleaseSpinlock(&gAsyncFileQueueLock, flags);
|
||||
|
||||
if (req->Iosb) {
|
||||
req->Iosb->Status = status;
|
||||
req->Iosb->Information = information;
|
||||
}
|
||||
|
||||
// Match IRP completion ordering: queue APC first, then signal the event.
|
||||
if (req->CompletionApc) {
|
||||
if (!KeInsertQueueApc(req->CompletionApc, req->Iosb, (void*)(uintptr_t)status)) {
|
||||
if (req->CompletionApc->RundownRoutine)
|
||||
req->CompletionApc->RundownRoutine(req->CompletionApc);
|
||||
}
|
||||
}
|
||||
|
||||
if (req->CompletionEvent)
|
||||
KeSignalEvent(req->CompletionEvent);
|
||||
}
|
||||
|
||||
static IO_ASYNC_FILE_REQUEST_OBJECT* KiIoAsyncFileDequeue(void) {
|
||||
uintptr_t flags = KeAcquireSpinlock(&gAsyncFileQueueLock);
|
||||
LIST_ENTRY* node = ListPopHead(&gAsyncFileQueue);
|
||||
if (!node) {
|
||||
KeReleaseSpinlock(&gAsyncFileQueueLock, flags);
|
||||
return (IO_ASYNC_FILE_REQUEST_OBJECT*)0;
|
||||
}
|
||||
|
||||
IO_ASYNC_FILE_REQUEST_OBJECT* req =
|
||||
CONTAINING_RECORD(node, IO_ASYNC_FILE_REQUEST_OBJECT, Link);
|
||||
|
||||
if (req->CancelRequested) {
|
||||
req->State = IoAsyncStateCancelled;
|
||||
} else {
|
||||
req->State = IoAsyncStateRunning;
|
||||
}
|
||||
KeReleaseSpinlock(&gAsyncFileQueueLock, flags);
|
||||
return req;
|
||||
}
|
||||
|
||||
static void KiIoAsyncFileWorker(void* arg) {
|
||||
(void)arg;
|
||||
|
||||
for (;;) {
|
||||
KSTATUS s = KeWaitSemaphore(gAsyncFileSemaphore);
|
||||
if (!KSUCCESS(s)) {
|
||||
KeYield();
|
||||
continue;
|
||||
}
|
||||
|
||||
IO_ASYNC_FILE_REQUEST_OBJECT* req = KiIoAsyncFileDequeue();
|
||||
if (!req) continue;
|
||||
|
||||
if (req->CancelRequested || req->State == IoAsyncStateCancelled) {
|
||||
KiIoCompleteAsyncFileRequest(req, STATUS_CANCELLED, 0);
|
||||
ObDereferenceObject(&req->Header); // queue/worker reference
|
||||
continue;
|
||||
}
|
||||
|
||||
size_t done = 0;
|
||||
if (req->Op == IoAsyncFileRead) {
|
||||
s = IoReadFileAt(req->File, req->Offset, req->Buffer,
|
||||
req->Size, &done);
|
||||
} else {
|
||||
s = IoWriteFileAt(req->File, req->Offset, req->Buffer,
|
||||
req->Size, &done);
|
||||
}
|
||||
|
||||
if (req->CancelRequested) {
|
||||
// The operation may already have reached the filesystem. The
|
||||
// completion contract still reports cancellation so waiters/APCs do
|
||||
// not mistake a cancelled request for useful data.
|
||||
s = STATUS_CANCELLED;
|
||||
done = 0;
|
||||
}
|
||||
|
||||
KiIoCompleteAsyncFileRequest(req, s, done);
|
||||
ObDereferenceObject(&req->Header); // queue/worker reference
|
||||
}
|
||||
}
|
||||
|
||||
static KSTATUS KiIoEnsureAsyncFileIoStarted(void) {
|
||||
uintptr_t flags = KeAcquireSpinlock(&gAsyncFileInitLock);
|
||||
if (gAsyncFileInitState == 2) {
|
||||
KeReleaseSpinlock(&gAsyncFileInitLock, flags);
|
||||
return STATUS_SUCCESS;
|
||||
}
|
||||
if (gAsyncFileInitState == 1) {
|
||||
KeReleaseSpinlock(&gAsyncFileInitLock, flags);
|
||||
while (gAsyncFileInitState == 1)
|
||||
KeYield();
|
||||
return (gAsyncFileInitState == 2) ? STATUS_SUCCESS : STATUS_FAILURE;
|
||||
}
|
||||
|
||||
gAsyncFileInitState = 1;
|
||||
KeReleaseSpinlock(&gAsyncFileInitLock, flags);
|
||||
|
||||
LIST_INIT(&gAsyncFileQueue);
|
||||
|
||||
KSTATUS s = KeSemaphoreCreate(0, 0x7fffffff, &gAsyncFileSemaphore);
|
||||
if (KSUCCESS(s)) {
|
||||
THREAD* worker = (THREAD*)0;
|
||||
s = KeCreateKernelThread((uintptr_t)KiIoAsyncFileWorker,
|
||||
0,
|
||||
PriorityNormal,
|
||||
&worker);
|
||||
// Keep the creator reference: this worker is intentionally persistent
|
||||
// for the lifetime of the kernel.
|
||||
(void)worker;
|
||||
}
|
||||
|
||||
flags = KeAcquireSpinlock(&gAsyncFileInitLock);
|
||||
gAsyncFileInitState = KSUCCESS(s) ? 2 : 0;
|
||||
KeReleaseSpinlock(&gAsyncFileInitLock, flags);
|
||||
|
||||
return s;
|
||||
}
|
||||
|
||||
static void KiIoNativeFsAsyncComplete(void* completeContext,
|
||||
KSTATUS status,
|
||||
size_t information) {
|
||||
IO_ASYNC_FILE_REQUEST_OBJECT* req =
|
||||
(IO_ASYNC_FILE_REQUEST_OBJECT*)completeContext;
|
||||
if (!req) return;
|
||||
|
||||
if (req->CancelRequested) {
|
||||
status = STATUS_CANCELLED;
|
||||
information = 0;
|
||||
}
|
||||
|
||||
KiIoCompleteAsyncFileRequest(req, status, information);
|
||||
ObDereferenceObject(&req->Header); // native filesystem provider reference
|
||||
}
|
||||
|
||||
static KSTATUS KiIoSubmitAsyncFileRequest(IO_ASYNC_FILE_OP op,
|
||||
FILE_OBJECT* file,
|
||||
void* buffer,
|
||||
size_t size,
|
||||
uint64_t offset,
|
||||
IO_STATUS_BLOCK* iosb,
|
||||
EVENT* completionEvent,
|
||||
KAPC* completionApc,
|
||||
IO_ASYNC_FILE_REQUEST_OBJECT** requestOut) {
|
||||
if (!file || !iosb) return STATUS_INVALID_PARAMETER;
|
||||
if (!buffer && size) return STATUS_INVALID_PARAMETER;
|
||||
if (!KeCanWait()) return STATUS_INVALID_IRQL;
|
||||
|
||||
iosb->Status = STATUS_PENDING;
|
||||
iosb->Information = 0;
|
||||
|
||||
IO_ASYNC_FILE_REQUEST_OBJECT* req =
|
||||
(IO_ASYNC_FILE_REQUEST_OBJECT*)ObAllocateObject(ObjectTypeAsyncIo,
|
||||
sizeof(IO_ASYNC_FILE_REQUEST_OBJECT));
|
||||
if (!req) {
|
||||
iosb->Status = STATUS_NO_MEMORY;
|
||||
return STATUS_NO_MEMORY;
|
||||
}
|
||||
|
||||
req->Header.Destroy = KiIoAsyncFileRequestDestroy;
|
||||
LIST_INIT(&req->Link);
|
||||
req->Op = op;
|
||||
req->State = IoAsyncStateQueued;
|
||||
req->CancelRequested = 0;
|
||||
req->File = file;
|
||||
req->CompletionEvent = completionEvent;
|
||||
req->CompletionApc = completionApc;
|
||||
req->Iosb = iosb;
|
||||
req->Buffer = buffer;
|
||||
req->Size = size;
|
||||
req->Offset = offset;
|
||||
|
||||
ObReferenceObject(&file->Header);
|
||||
if (completionEvent)
|
||||
ObReferenceObject(&completionEvent->Header);
|
||||
|
||||
IO_FS_ASYNC_COMPLETE complete = KiIoNativeFsAsyncComplete;
|
||||
KSTATUS ns = STATUS_NOT_SUPPORTED;
|
||||
bool hasNative = (op == IoAsyncFileRead) ? (file->Fs->ReadAsync != 0)
|
||||
: (file->Fs->WriteAsync != 0);
|
||||
|
||||
if (hasNative) {
|
||||
uintptr_t flags = KeAcquireSpinlock(&gAsyncFileQueueLock);
|
||||
req->State = IoAsyncStateRunning;
|
||||
KeReleaseSpinlock(&gAsyncFileQueueLock, flags);
|
||||
|
||||
ObReferenceObject(&req->Header); // native filesystem provider reference
|
||||
if (op == IoAsyncFileRead) {
|
||||
ns = file->Fs->ReadAsync(file->Fs, file->FileCtx, offset, buffer,
|
||||
size, complete, req);
|
||||
} else {
|
||||
ns = file->Fs->WriteAsync(file->Fs, file->FileCtx, offset, buffer,
|
||||
size, complete, req);
|
||||
}
|
||||
if (ns == STATUS_PENDING) {
|
||||
if (requestOut) {
|
||||
*requestOut = req;
|
||||
} else {
|
||||
ObDereferenceObject(&req->Header);
|
||||
}
|
||||
return STATUS_PENDING;
|
||||
}
|
||||
|
||||
ObDereferenceObject(&req->Header); // provider rejected synchronously
|
||||
iosb->Status = ns;
|
||||
ObDereferenceObject(&req->Header); // creator reference
|
||||
return ns;
|
||||
}
|
||||
|
||||
KSTATUS s = KiIoEnsureAsyncFileIoStarted();
|
||||
if (!KSUCCESS(s)) {
|
||||
iosb->Status = s;
|
||||
ObDereferenceObject(&req->Header);
|
||||
return s;
|
||||
}
|
||||
|
||||
// Queue ownership keeps the request alive until the worker completes or a
|
||||
// successful queued cancellation completes it from IoCancelAsyncFileRequest.
|
||||
ObReferenceObject(&req->Header);
|
||||
|
||||
uintptr_t qflags = KeAcquireSpinlock(&gAsyncFileQueueLock);
|
||||
ListInsertTail(&gAsyncFileQueue, &req->Link);
|
||||
KeReleaseSpinlock(&gAsyncFileQueueLock, qflags);
|
||||
|
||||
KeSignalSemaphore(gAsyncFileSemaphore);
|
||||
|
||||
if (requestOut) {
|
||||
*requestOut = req; // creator reference returned to caller
|
||||
} else {
|
||||
ObDereferenceObject(&req->Header);
|
||||
}
|
||||
|
||||
return STATUS_PENDING;
|
||||
}
|
||||
|
||||
__declspec(dllexport) KSTATUS IoReadFileAsyncKernel(FILE_OBJECT* file, void* buf,
|
||||
size_t size, uint64_t offset,
|
||||
IO_STATUS_BLOCK* iosb,
|
||||
EVENT* completionEvent,
|
||||
KAPC* completionApc,
|
||||
IO_ASYNC_FILE_REQUEST_OBJECT** requestOut) {
|
||||
return KiIoSubmitAsyncFileRequest(IoAsyncFileRead, file, buf, size, offset,
|
||||
iosb, completionEvent, completionApc,
|
||||
requestOut);
|
||||
}
|
||||
|
||||
__declspec(dllexport) KSTATUS IoWriteFileAsyncKernel(FILE_OBJECT* file, const void* buf,
|
||||
size_t size, uint64_t offset,
|
||||
IO_STATUS_BLOCK* iosb,
|
||||
EVENT* completionEvent,
|
||||
KAPC* completionApc,
|
||||
IO_ASYNC_FILE_REQUEST_OBJECT** requestOut) {
|
||||
return KiIoSubmitAsyncFileRequest(IoAsyncFileWrite, file, (void*)buf, size, offset,
|
||||
iosb, completionEvent, completionApc,
|
||||
requestOut);
|
||||
}
|
||||
|
||||
__declspec(dllexport) OBJECT* IoAsyncFileRequestObject(IO_ASYNC_FILE_REQUEST_OBJECT* req) {
|
||||
return req ? &req->Header : (OBJECT*)0;
|
||||
}
|
||||
|
||||
__declspec(dllexport) KSTATUS IoCancelAsyncFileRequest(IO_ASYNC_FILE_REQUEST_OBJECT* req) {
|
||||
if (!req) return STATUS_INVALID_PARAMETER;
|
||||
|
||||
IO_ASYNC_FILE_STATE state;
|
||||
uintptr_t flags = KeAcquireSpinlock(&gAsyncFileQueueLock);
|
||||
state = req->State;
|
||||
|
||||
if (state == IoAsyncStateQueued) {
|
||||
ListRemove(&req->Link);
|
||||
req->CancelRequested = 1;
|
||||
req->State = IoAsyncStateCancelled;
|
||||
KeReleaseSpinlock(&gAsyncFileQueueLock, flags);
|
||||
|
||||
KiIoCompleteAsyncFileRequest(req, STATUS_CANCELLED, 0);
|
||||
ObDereferenceObject(&req->Header); // queue reference removed above
|
||||
return STATUS_SUCCESS;
|
||||
}
|
||||
|
||||
if (state == IoAsyncStateRunning) {
|
||||
req->CancelRequested = 1;
|
||||
KeReleaseSpinlock(&gAsyncFileQueueLock, flags);
|
||||
return STATUS_PENDING;
|
||||
}
|
||||
|
||||
KeReleaseSpinlock(&gAsyncFileQueueLock, flags);
|
||||
return STATUS_OBJECT_DEAD;
|
||||
}
|
||||
|
||||
__declspec(dllexport) KSTATUS IoQueryAsyncFileRequest(IO_ASYNC_FILE_REQUEST_OBJECT* req,
|
||||
IO_STATUS_BLOCK* iosbOut) {
|
||||
if (!req || !iosbOut) return STATUS_INVALID_PARAMETER;
|
||||
if (req->Iosb) {
|
||||
iosbOut->Status = req->Iosb->Status;
|
||||
iosbOut->Information = req->Iosb->Information;
|
||||
} else {
|
||||
iosbOut->Status = STATUS_FAILURE;
|
||||
iosbOut->Information = 0;
|
||||
}
|
||||
return STATUS_SUCCESS;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Current Working Directory
|
||||
// Stored as (CurrentDrive, drive-relative path with no trailing backslash).
|
||||
// Exported as native "N:/path" so personalities can round-trip volumes.
|
||||
// ---------------------------------------------------------------------------
|
||||
#define CWD_MAX 512
|
||||
|
||||
static size_t KiFormatCwd(uint8_t drive, const char* rel, char* buf, size_t size) {
|
||||
if (!buf || size == 0) return 0;
|
||||
if (drive > 7) drive = 0;
|
||||
if (!rel) rel = "";
|
||||
|
||||
size_t relLen = strlen(rel);
|
||||
size_t needed = 3 + relLen + 1;
|
||||
if (needed > size) return needed;
|
||||
|
||||
buf[0] = (char)('0' + drive);
|
||||
buf[1] = ':';
|
||||
buf[2] = '/';
|
||||
memcpy(buf + 3, rel, relLen);
|
||||
buf[3 + relLen] = '\0';
|
||||
return needed;
|
||||
}
|
||||
|
||||
__declspec(dllexport) KSTATUS IoGetCwd(char* buf, size_t size) {
|
||||
if (!buf || size == 0) return STATUS_INVALID_PARAMETER;
|
||||
|
||||
@@ -29,24 +413,26 @@ __declspec(dllexport) KSTATUS IoGetCwd(char* buf, size_t size) {
|
||||
if (!proc) return STATUS_ACCESS_DENIED;
|
||||
|
||||
uintptr_t flags = KeAcquireSpinlock(&proc->CwdLock);
|
||||
size_t cwdLen = strlen(proc->CurrentDirectory);
|
||||
if (cwdLen + 1 > size) {
|
||||
KeReleaseSpinlock(&proc->CwdLock, flags);
|
||||
return STATUS_BUFFER_TOO_SMALL;
|
||||
}
|
||||
memcpy(buf, proc->CurrentDirectory, cwdLen + 1);
|
||||
size_t needed = KiFormatCwd(proc->CurrentDrive, proc->CurrentDirectory, buf, size);
|
||||
KeReleaseSpinlock(&proc->CwdLock, flags);
|
||||
|
||||
return STATUS_SUCCESS;
|
||||
return needed && needed <= size ? STATUS_SUCCESS : STATUS_BUFFER_TOO_SMALL;
|
||||
}
|
||||
|
||||
// Resolve a possibly-relative path component against the CWD.
|
||||
// Writes the fully-resolved, drive-stripped relative path into 'resolved'
|
||||
// (caller supplies buf of at least CWD_MAX bytes).
|
||||
// Returns the drive index.
|
||||
static uint8_t ResolvePath(const char* path, char* resolved, size_t resolvedSize) {
|
||||
// Strip explicit drive prefix "N:\"
|
||||
static uint8_t KiResolvePath(const char* path, char* resolved, size_t resolvedSize) {
|
||||
uint8_t drive = 0;
|
||||
PROCESS* proc = KeGetCurrentProcess();
|
||||
if (proc) {
|
||||
uintptr_t flags = KeAcquireSpinlock(&proc->CwdLock);
|
||||
drive = proc->CurrentDrive;
|
||||
KeReleaseSpinlock(&proc->CwdLock, flags);
|
||||
}
|
||||
|
||||
// Strip explicit drive prefix "N:\" or "N:/".
|
||||
bool fromDriveRoot = false;
|
||||
if (path[0] >= '0' && path[0] <= '7' &&
|
||||
path[1] == ':' && (path[2] == '\\' || path[2] == '/')) {
|
||||
@@ -124,14 +510,15 @@ static uint8_t ResolvePath(const char* path, char* resolved, size_t resolvedSize
|
||||
|
||||
__declspec(dllexport) KSTATUS IoSetCwd(const char* path) {
|
||||
if (!path) return STATUS_INVALID_PARAMETER;
|
||||
if (!KeCanWait()) return STATUS_INVALID_IRQL;
|
||||
|
||||
PROCESS* proc = KeGetCurrentProcess();
|
||||
if (!proc) return STATUS_ACCESS_DENIED;
|
||||
|
||||
// Resolve the new path (this also validates ".." navigation).
|
||||
// Note: ResolvePath already uses proc->CurrentDirectory internally!
|
||||
// Note: KiResolvePath already uses proc->CurrentDirectory internally!
|
||||
char resolved[CWD_MAX];
|
||||
uint8_t drive = ResolvePath(path, resolved, sizeof(resolved));
|
||||
uint8_t drive = KiResolvePath(path, resolved, sizeof(resolved));
|
||||
|
||||
// Validate: try to open the directory to confirm it actually exists on disk
|
||||
IO_FILESYSTEM* fs = gVolumes[drive].Fs;
|
||||
@@ -148,16 +535,15 @@ __declspec(dllexport) KSTATUS IoSetCwd(const char* path) {
|
||||
if (len + 1 > sizeof(proc->CurrentDirectory)) return STATUS_BUFFER_TOO_SMALL;
|
||||
|
||||
uintptr_t flags = KeAcquireSpinlock(&proc->CwdLock);
|
||||
proc->CurrentDrive = drive;
|
||||
memcpy(proc->CurrentDirectory, resolved, len + 1);
|
||||
|
||||
// Keep the user-visible PEB mirror in sync. The PEB's CWD buffer is a
|
||||
// fixed SYS_CWD_MAX allocation made at process creation, so the pointer
|
||||
// remains stable for personalities that cache SYS_PEB*.
|
||||
if (proc->UserCwdBase && proc->UserCwdSize) {
|
||||
size_t copyLen = len;
|
||||
if (copyLen + 1 > proc->UserCwdSize) copyLen = proc->UserCwdSize - 1;
|
||||
memcpy((void*)proc->UserCwdBase, resolved, copyLen);
|
||||
((char*)proc->UserCwdBase)[copyLen] = '\0';
|
||||
size_t formatted = KiFormatCwd(drive, resolved, (char*)proc->UserCwdBase, proc->UserCwdSize);
|
||||
size_t copyLen = (formatted && formatted <= proc->UserCwdSize) ? formatted - 1 : proc->UserCwdSize - 1;
|
||||
|
||||
SYS_PEB* peb = (SYS_PEB*)proc->UserPebBase;
|
||||
if (peb && peb->Magic == SYS_PEB_MAGIC) {
|
||||
@@ -189,7 +575,7 @@ __declspec(dllexport) KSTATUS IoRegisterFilesystem(uint8_t volumeIndex,
|
||||
// ---------------------------------------------------------------------------
|
||||
// Destructor
|
||||
// ---------------------------------------------------------------------------
|
||||
static void FileDestroy(OBJECT* obj) {
|
||||
static void KiFileDestroy(OBJECT* obj) {
|
||||
FILE_OBJECT* f = CONTAINING_RECORD(obj, FILE_OBJECT, Header);
|
||||
if (f->Fs && f->FileCtx) f->Fs->Close(f->Fs, f->FileCtx);
|
||||
}
|
||||
@@ -199,9 +585,10 @@ static void FileDestroy(OBJECT* obj) {
|
||||
// ---------------------------------------------------------------------------
|
||||
__declspec(dllexport) KSTATUS IoOpenFile(const char* path, FILE_OBJECT **out) {
|
||||
if (!path || !out) return STATUS_INVALID_PARAMETER;
|
||||
if (!KeCanWait()) return STATUS_INVALID_IRQL;
|
||||
|
||||
char relPath[CWD_MAX];
|
||||
uint8_t drive = ResolvePath(path, relPath, sizeof(relPath));
|
||||
uint8_t drive = KiResolvePath(path, relPath, sizeof(relPath));
|
||||
|
||||
IO_FILESYSTEM* fs = gVolumes[drive].Fs;
|
||||
if (!fs) return STATUS_NOT_FOUND;
|
||||
@@ -213,7 +600,7 @@ __declspec(dllexport) KSTATUS IoOpenFile(const char* path, FILE_OBJECT **out) {
|
||||
FILE_OBJECT* f = (FILE_OBJECT *)ObAllocateObject(ObjectTypeFile /* reuse slot */,
|
||||
sizeof(FILE_OBJECT));
|
||||
if (!f) { fs->Close(fs, fileCtx); return STATUS_NO_MEMORY; }
|
||||
f->Header.Destroy = FileDestroy;
|
||||
f->Header.Destroy = KiFileDestroy;
|
||||
f->Fs = fs;
|
||||
f->FileCtx = fileCtx;
|
||||
f->Size = fs->GetSize(fs, fileCtx);
|
||||
@@ -224,20 +611,69 @@ __declspec(dllexport) KSTATUS IoOpenFile(const char* path, FILE_OBJECT **out) {
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// IoReadFile - sequential read at current offset
|
||||
// Explicit-offset file I/O. Async I/O is built on these routines so it does
|
||||
// not race the sequential FILE_OBJECT.Offset used by legacy read/write calls.
|
||||
// ---------------------------------------------------------------------------
|
||||
__declspec(dllexport) KSTATUS IoReadFile(FILE_OBJECT* file, void* buf, size_t size, size_t* bytesRead) {
|
||||
__declspec(dllexport) KSTATUS IoReadFileAt(FILE_OBJECT* file, uint64_t offset,
|
||||
void* buf, size_t size,
|
||||
size_t* bytesRead) {
|
||||
if (bytesRead) *bytesRead = 0;
|
||||
if (!file || !buf || !bytesRead) return STATUS_INVALID_PARAMETER;
|
||||
if (file->Offset >= file->Size || size == 0) return STATUS_SUCCESS;
|
||||
if (!KeCanWait()) return STATUS_INVALID_IRQL;
|
||||
if (size == 0) return STATUS_SUCCESS;
|
||||
|
||||
// Clamp to remaining bytes.
|
||||
size_t remaining = (size_t)(file->Size - file->Offset);
|
||||
uintptr_t flags = KeAcquireSpinlock(&file->Lock);
|
||||
uint64_t fileSize = file->Size;
|
||||
KeReleaseSpinlock(&file->Lock, flags);
|
||||
|
||||
if (offset >= fileSize) return STATUS_SUCCESS;
|
||||
|
||||
size_t remaining = (size_t)(fileSize - offset);
|
||||
if (size > remaining) size = remaining;
|
||||
|
||||
KSTATUS s = file->Fs->Read(file->Fs, file->FileCtx,
|
||||
file->Offset, buf, size, bytesRead);
|
||||
if (KSUCCESS(s)) file->Offset += *bytesRead;
|
||||
return file->Fs->Read(file->Fs, file->FileCtx, offset, buf, size, bytesRead);
|
||||
}
|
||||
|
||||
__declspec(dllexport) KSTATUS IoWriteFileAt(FILE_OBJECT* file, uint64_t offset,
|
||||
const void* buf, size_t size,
|
||||
size_t* bytesWritten) {
|
||||
if (bytesWritten) *bytesWritten = 0;
|
||||
if (!file || !buf || !bytesWritten) return STATUS_INVALID_PARAMETER;
|
||||
if (!KeCanWait()) return STATUS_INVALID_IRQL;
|
||||
if (!file->Fs->Write) return STATUS_NOT_SUPPORTED;
|
||||
if (size == 0) return STATUS_SUCCESS;
|
||||
|
||||
size_t written = 0;
|
||||
KSTATUS s = file->Fs->Write(file->Fs, file->FileCtx, offset, buf, size, &written);
|
||||
if (KSUCCESS(s)) {
|
||||
uintptr_t flags = KeAcquireSpinlock(&file->Lock);
|
||||
uint64_t end = offset + written;
|
||||
if (end > file->Size) file->Size = end;
|
||||
KeReleaseSpinlock(&file->Lock, flags);
|
||||
}
|
||||
|
||||
*bytesWritten = written;
|
||||
return s;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// IoReadFile - sequential read at current offset
|
||||
// ---------------------------------------------------------------------------
|
||||
__declspec(dllexport) KSTATUS IoReadFile(FILE_OBJECT* file, void* buf,
|
||||
size_t size, size_t* bytesRead) {
|
||||
if (bytesRead) *bytesRead = 0;
|
||||
if (!file || !buf || !bytesRead) return STATUS_INVALID_PARAMETER;
|
||||
|
||||
uintptr_t flags = KeAcquireSpinlock(&file->Lock);
|
||||
uint64_t offset = file->Offset;
|
||||
KeReleaseSpinlock(&file->Lock, flags);
|
||||
|
||||
KSTATUS s = IoReadFileAt(file, offset, buf, size, bytesRead);
|
||||
if (KSUCCESS(s)) {
|
||||
flags = KeAcquireSpinlock(&file->Lock);
|
||||
file->Offset = offset + *bytesRead;
|
||||
KeReleaseSpinlock(&file->Lock, flags);
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
||||
@@ -246,19 +682,19 @@ __declspec(dllexport) KSTATUS IoReadFile(FILE_OBJECT* file, void* buf, size_t si
|
||||
// ---------------------------------------------------------------------------
|
||||
__declspec(dllexport) KSTATUS IoWriteFile(FILE_OBJECT* file, const void* buf,
|
||||
size_t size, size_t* bytesWritten) {
|
||||
if (bytesWritten) *bytesWritten = 0;
|
||||
if (!file || !buf || !bytesWritten) return STATUS_INVALID_PARAMETER;
|
||||
if (!file->Fs->Write) return STATUS_NOT_SUPPORTED;
|
||||
|
||||
size_t written = 0;
|
||||
KSTATUS s = file->Fs->Write(file->Fs, file->FileCtx,
|
||||
file->Offset, buf, size, &written);
|
||||
uintptr_t flags = KeAcquireSpinlock(&file->Lock);
|
||||
uint64_t offset = file->Offset;
|
||||
KeReleaseSpinlock(&file->Lock, flags);
|
||||
|
||||
KSTATUS s = IoWriteFileAt(file, offset, buf, size, bytesWritten);
|
||||
if (KSUCCESS(s)) {
|
||||
file->Offset += written;
|
||||
// Keep the cached size in sync if the file grew.
|
||||
if (file->Offset > file->Size)
|
||||
file->Size = file->Offset;
|
||||
flags = KeAcquireSpinlock(&file->Lock);
|
||||
file->Offset = offset + *bytesWritten;
|
||||
KeReleaseSpinlock(&file->Lock, flags);
|
||||
}
|
||||
*bytesWritten = written;
|
||||
return s;
|
||||
}
|
||||
|
||||
@@ -267,9 +703,10 @@ __declspec(dllexport) KSTATUS IoWriteFile(FILE_OBJECT* file, const void* buf,
|
||||
// ---------------------------------------------------------------------------
|
||||
__declspec(dllexport) KSTATUS IoCreateFile(const char* path, FILE_OBJECT** out) {
|
||||
if (!path || !out) return STATUS_INVALID_PARAMETER;
|
||||
if (!KeCanWait()) return STATUS_INVALID_IRQL;
|
||||
|
||||
char relPath[CWD_MAX];
|
||||
uint8_t drive = ResolvePath(path, relPath, sizeof(relPath));
|
||||
uint8_t drive = KiResolvePath(path, relPath, sizeof(relPath));
|
||||
|
||||
IO_FILESYSTEM* fs = gVolumes[drive].Fs;
|
||||
if (!fs) return STATUS_NOT_FOUND;
|
||||
@@ -282,9 +719,10 @@ __declspec(dllexport) KSTATUS IoCreateFile(const char* path, FILE_OBJECT** out)
|
||||
FILE_OBJECT* f = (FILE_OBJECT*)ObAllocateObject(ObjectTypeFile,
|
||||
sizeof(FILE_OBJECT));
|
||||
if (!f) { fs->Close(fs, fileCtx); return STATUS_NO_MEMORY; }
|
||||
f->Header.Destroy = FileDestroy;
|
||||
f->Header.Destroy = KiFileDestroy;
|
||||
f->Fs = fs;
|
||||
f->FileCtx = fileCtx;
|
||||
f->Lock = (SPINLOCK)SPINLOCK_INIT;
|
||||
f->Size = 0;
|
||||
f->Offset = 0;
|
||||
|
||||
@@ -297,9 +735,10 @@ __declspec(dllexport) KSTATUS IoCreateFile(const char* path, FILE_OBJECT** out)
|
||||
// ---------------------------------------------------------------------------
|
||||
__declspec(dllexport) KSTATUS IoDeleteFile(const char* path) {
|
||||
if (!path) return STATUS_INVALID_PARAMETER;
|
||||
if (!KeCanWait()) return STATUS_INVALID_IRQL;
|
||||
|
||||
char relPath[CWD_MAX];
|
||||
uint8_t drive = ResolvePath(path, relPath, sizeof(relPath));
|
||||
uint8_t drive = KiResolvePath(path, relPath, sizeof(relPath));
|
||||
|
||||
IO_FILESYSTEM* fs = gVolumes[drive].Fs;
|
||||
if (!fs) return STATUS_NOT_FOUND;
|
||||
@@ -310,11 +749,12 @@ __declspec(dllexport) KSTATUS IoDeleteFile(const char* path) {
|
||||
|
||||
__declspec(dllexport) KSTATUS IoRenameFile(const char* oldPath, const char* newPath) {
|
||||
if (!oldPath || !newPath) return STATUS_INVALID_PARAMETER;
|
||||
if (!KeCanWait()) return STATUS_INVALID_IRQL;
|
||||
|
||||
char oldRel[CWD_MAX];
|
||||
char newRel[CWD_MAX];
|
||||
uint8_t oldDrive = ResolvePath(oldPath, oldRel, sizeof(oldRel));
|
||||
uint8_t newDrive = ResolvePath(newPath, newRel, sizeof(newRel));
|
||||
uint8_t oldDrive = KiResolvePath(oldPath, oldRel, sizeof(oldRel));
|
||||
uint8_t newDrive = KiResolvePath(newPath, newRel, sizeof(newRel));
|
||||
if (oldDrive != newDrive) return STATUS_NOT_SUPPORTED;
|
||||
|
||||
IO_FILESYSTEM* fs = gVolumes[oldDrive].Fs;
|
||||
@@ -326,9 +766,10 @@ __declspec(dllexport) KSTATUS IoRenameFile(const char* oldPath, const char* newP
|
||||
|
||||
__declspec(dllexport) KSTATUS IoCreateDir(const char* path) {
|
||||
if (!path) return STATUS_INVALID_PARAMETER;
|
||||
if (!KeCanWait()) return STATUS_INVALID_IRQL;
|
||||
|
||||
char relPath[CWD_MAX];
|
||||
uint8_t drive = ResolvePath(path, relPath, sizeof(relPath));
|
||||
uint8_t drive = KiResolvePath(path, relPath, sizeof(relPath));
|
||||
|
||||
IO_FILESYSTEM* fs = gVolumes[drive].Fs;
|
||||
if (!fs) return STATUS_NOT_FOUND;
|
||||
@@ -339,9 +780,10 @@ __declspec(dllexport) KSTATUS IoCreateDir(const char* path) {
|
||||
|
||||
__declspec(dllexport) KSTATUS IoRemoveDir(const char* path) {
|
||||
if (!path) return STATUS_INVALID_PARAMETER;
|
||||
if (!KeCanWait()) return STATUS_INVALID_IRQL;
|
||||
|
||||
char relPath[CWD_MAX];
|
||||
uint8_t drive = ResolvePath(path, relPath, sizeof(relPath));
|
||||
uint8_t drive = KiResolvePath(path, relPath, sizeof(relPath));
|
||||
|
||||
IO_FILESYSTEM* fs = gVolumes[drive].Fs;
|
||||
if (!fs) return STATUS_NOT_FOUND;
|
||||
@@ -355,6 +797,7 @@ __declspec(dllexport) KSTATUS IoRemoveDir(const char* path) {
|
||||
// ---------------------------------------------------------------------------
|
||||
__declspec(dllexport) KSTATUS IoTruncateFile(FILE_OBJECT* file, uint64_t newSize) {
|
||||
if (!file) return STATUS_INVALID_PARAMETER;
|
||||
if (!KeCanWait()) return STATUS_INVALID_IRQL;
|
||||
if (!file->Fs->Truncate) return STATUS_NOT_SUPPORTED;
|
||||
|
||||
KSTATUS s = file->Fs->Truncate(file->Fs, file->FileCtx, newSize);
|
||||
@@ -370,12 +813,18 @@ __declspec(dllexport) KSTATUS IoTruncateFile(FILE_OBJECT* file, uint64_t newSize
|
||||
// ---------------------------------------------------------------------------
|
||||
__declspec(dllexport) KSTATUS IoSeekFile(FILE_OBJECT* file, uint64_t offset) {
|
||||
if (!file) return STATUS_INVALID_PARAMETER;
|
||||
uintptr_t flags = KeAcquireSpinlock(&file->Lock);
|
||||
file->Offset = offset;
|
||||
KeReleaseSpinlock(&file->Lock, flags);
|
||||
return STATUS_SUCCESS;
|
||||
}
|
||||
|
||||
__declspec(dllexport) uint64_t IoGetFileSize(FILE_OBJECT* file) {
|
||||
return file ? file->Size : 0;
|
||||
if (!file) return 0;
|
||||
uintptr_t flags = KeAcquireSpinlock(&file->Lock);
|
||||
uint64_t size = file->Size;
|
||||
KeReleaseSpinlock(&file->Lock, flags);
|
||||
return size;
|
||||
}
|
||||
|
||||
__declspec(dllexport) void IoCloseFile(FILE_OBJECT* file) {
|
||||
@@ -386,13 +835,14 @@ __declspec(dllexport) void IoCloseFile(FILE_OBJECT* file) {
|
||||
// Directory enumeration
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
static void DirDestroy(OBJECT* obj) {
|
||||
static void KiDirDestroy(OBJECT* obj) {
|
||||
DIR_OBJECT* d = (DIR_OBJECT*)obj; // Header is first member
|
||||
if (d->Fs && d->DirCtx) d->Fs->CloseDir(d->Fs, d->DirCtx);
|
||||
}
|
||||
|
||||
__declspec(dllexport) KSTATUS IoOpenDir(const char* dirPath, DIR_OBJECT** out) {
|
||||
if (!out) return STATUS_INVALID_PARAMETER;
|
||||
if (!KeCanWait()) return STATUS_INVALID_IRQL;
|
||||
|
||||
// NULL or empty path means the CWD itself
|
||||
char relPath[CWD_MAX];
|
||||
@@ -404,13 +854,10 @@ __declspec(dllexport) KSTATUS IoOpenDir(const char* dirPath, DIR_OBJECT** out) {
|
||||
|
||||
uintptr_t flags = KeAcquireSpinlock(&proc->CwdLock);
|
||||
memcpy(relPath, proc->CurrentDirectory, strlen(proc->CurrentDirectory) + 1);
|
||||
drive = proc->CurrentDrive;
|
||||
KeReleaseSpinlock(&proc->CwdLock, flags);
|
||||
|
||||
// Note: For now, this assumes drive 0. If you add multi-drive support
|
||||
// later, you should add a CurrentDrive field to the PROCESS struct.
|
||||
drive = 0;
|
||||
} else {
|
||||
drive = ResolvePath(dirPath, relPath, sizeof(relPath));
|
||||
drive = KiResolvePath(dirPath, relPath, sizeof(relPath));
|
||||
}
|
||||
|
||||
IO_FILESYSTEM* fs = gVolumes[drive].Fs;
|
||||
@@ -424,7 +871,7 @@ __declspec(dllexport) KSTATUS IoOpenDir(const char* dirPath, DIR_OBJECT** out) {
|
||||
DIR_OBJECT* d = (DIR_OBJECT*)ObAllocateObject(ObjectTypeDir, sizeof(DIR_OBJECT));
|
||||
if (!d) { fs->CloseDir(fs, dirCtx); return STATUS_NO_MEMORY; }
|
||||
|
||||
d->Header.Destroy = DirDestroy;
|
||||
d->Header.Destroy = KiDirDestroy;
|
||||
d->Fs = fs;
|
||||
d->DirCtx = dirCtx;
|
||||
|
||||
@@ -434,6 +881,7 @@ __declspec(dllexport) KSTATUS IoOpenDir(const char* dirPath, DIR_OBJECT** out) {
|
||||
|
||||
__declspec(dllexport) KSTATUS IoReadDir(DIR_OBJECT* dir, KDIR_ENTRY* entryOut) {
|
||||
if (!dir || !entryOut) return STATUS_INVALID_PARAMETER;
|
||||
if (!KeCanWait()) return STATUS_INVALID_IRQL;
|
||||
if (!dir->Fs->ReadDir) return STATUS_NOT_SUPPORTED;
|
||||
return dir->Fs->ReadDir(dir->Fs, dir->DirCtx, entryOut);
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user