// Kernel file I/O layer. This file owns file handles, filesystem dispatch, and asynchronous request plumbing. #include #include #include #include #include #include #include #include #include #include #include #include #include #define MAX_DRIVES 8 typedef struct { IO_FILESYSTEM *Fs; IO_VOLUME_PARAMS Vol; } VOLUME_ENTRY; static VOLUME_ENTRY gVolumes[MAX_DRIVES]; // 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; // Implements the io Async File Request Destroy operation. 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); } // Implements the io Async File Dequeue operation. 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; } // Implements the io Async File Worker operation. 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 } } // Implements the io Ensure Async File Io Started operation. 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); } // Implements the async File Request Object operation. __declspec(dllexport) OBJECT* IoAsyncFileRequestObject(IO_ASYNC_FILE_REQUEST_OBJECT* req) { return req ? &req->Header : (OBJECT*)0; } // Cancels async File Request. __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 PROCESS_CWD_MAX typedef struct KRESOLVED_PATH { uint8_t Drive; char Storage[CWD_MAX]; SYS_STRING_VIEW Path; } KRESOLVED_PATH; // Implements the format Cwd operation. static size_t KiFormatCwd(uint8_t drive, SYS_STRING_VIEW rel, char* buf, size_t size) { if (!buf || size == 0 || !SysSvIsValid(rel)) return 0; if (drive > 7) drive = 0; size_t needed = 3 + rel.Length + 1; if (needed > size) return needed; buf[0] = (char)('0' + drive); buf[1] = ':'; buf[2] = '/'; if (rel.Length) memcpy(buf + 3, rel.Data, rel.Length); buf[3 + rel.Length] = '\0'; return needed; } // Returns cwd. __declspec(dllexport) KSTATUS IoGetCwd(char* buf, size_t size) { if (!buf || size == 0) return STATUS_INVALID_PARAMETER; PROCESS* proc = KeGetCurrentProcess(); if (!proc) return STATUS_ACCESS_DENIED; uintptr_t flags = KeAcquireSpinlock(&proc->CwdLock); size_t needed = KiFormatCwd(proc->CurrentDrive, SysSvFromParts(proc->CurrentDirectory, proc->CurrentDirectoryLength), buf, size); KeReleaseSpinlock(&proc->CwdLock, flags); return needed && needed <= size ? STATUS_SUCCESS : STATUS_BUFFER_TOO_SMALL; } // Implements the sv Has Drive Root operation. static int KiSvHasDriveRoot(SYS_STRING_VIEW path) { return path.Length >= 3 && path.Data[0] >= '0' && path.Data[0] <= '7' && path.Data[1] == ':' && (path.Data[2] == '\\' || path.Data[2] == '/'); } static int KiAppendBytes(char* work, size_t* workLen, size_t cap, const char* data, size_t len) { if (!work || !workLen || (!data && len)) return 0; if (*workLen + len >= cap) return 0; if (len) memcpy(work + *workLen, data, len); *workLen += len; work[*workLen] = '\0'; return 1; } static int KiAppendComponent(char* work, size_t* workLen, size_t cap, const char* data, size_t len) { if (!work || !workLen || (!data && len)) return 0; if (*workLen && work[*workLen - 1] != '\\' && work[*workLen - 1] != '/') { if (!KiAppendBytes(work, workLen, cap, "\\", 1)) return 0; } return KiAppendBytes(work, workLen, cap, data, len); } // Implements the pop Component operation. static void KiPopComponent(char* work, size_t* workLen) { if (!work || !workLen || *workLen == 0) return; size_t i = *workLen; while (i > 0 && work[i - 1] != '\\' && work[i - 1] != '/') i--; if (i > 0) i--; *workLen = i; work[i] = '\0'; } // Resolve a possibly-relative string view against the CWD. // The returned view is backed by out->Storage and remains valid while 'out' lives. static KSTATUS KiResolvePathV(SYS_STRING_VIEW input, KRESOLVED_PATH* out) { if (!out) return STATUS_INVALID_PARAMETER; memset(out, 0, sizeof(*out)); if (!SysSvIsValid(input)) return STATUS_INVALID_PARAMETER; if (SysSvContainsByte(input, '\0')) return STATUS_INVALID_PARAMETER; if (input.Length >= CWD_MAX) return STATUS_BUFFER_TOO_SMALL; uint8_t drive = 0; PROCESS* proc = KeGetCurrentProcess(); if (proc) { uintptr_t flags = KeAcquireSpinlock(&proc->CwdLock); drive = proc->CurrentDrive; KeReleaseSpinlock(&proc->CwdLock, flags); } size_t pos = 0; bool fromDriveRoot = false; if (KiSvHasDriveRoot(input)) { drive = (uint8_t)(input.Data[0] - '0'); pos = 3; fromDriveRoot = true; } char work[CWD_MAX * 2]; size_t workLen = 0; work[0] = '\0'; bool absolute = fromDriveRoot || (pos < input.Length && (input.Data[pos] == '\\' || input.Data[pos] == '/')); if (!absolute && proc) { uintptr_t flags = KeAcquireSpinlock(&proc->CwdLock); size_t cwdLen = proc->CurrentDirectoryLength; if (cwdLen >= sizeof(work)) cwdLen = sizeof(work) - 1; memcpy(work, proc->CurrentDirectory, cwdLen); work[cwdLen] = '\0'; workLen = cwdLen; KeReleaseSpinlock(&proc->CwdLock, flags); } while (pos < input.Length) { while (pos < input.Length && (input.Data[pos] == '\\' || input.Data[pos] == '/')) pos++; if (pos >= input.Length) break; size_t compStart = pos; while (pos < input.Length && input.Data[pos] != '\\' && input.Data[pos] != '/') pos++; size_t compLen = pos - compStart; if (compLen == 0) continue; if (compLen == 1 && input.Data[compStart] == '.') { continue; } if (compLen == 2 && input.Data[compStart] == '.' && input.Data[compStart + 1] == '.') { KiPopComponent(work, &workLen); continue; } if (compLen >= 256) return STATUS_BUFFER_TOO_SMALL; if (!KiAppendComponent(work, &workLen, sizeof(work), input.Data + compStart, compLen)) return STATUS_BUFFER_TOO_SMALL; } if (workLen + 1 > sizeof(out->Storage)) return STATUS_BUFFER_TOO_SMALL; memcpy(out->Storage, work, workLen); out->Storage[workLen] = '\0'; out->Drive = drive; out->Path = SysSvFromParts(out->Storage, workLen); return STATUS_SUCCESS; } // Updates cwd. __declspec(dllexport) KSTATUS IoSetCwd(SYS_STRING_VIEW path) { if (path.Length && !path.Data) return STATUS_INVALID_PARAMETER; if (!KeCanWait()) return STATUS_INVALID_IRQL; PROCESS* proc = KeGetCurrentProcess(); if (!proc) return STATUS_ACCESS_DENIED; KRESOLVED_PATH resolved; KSTATUS s = KiResolvePathV(path, &resolved); if (!KSUCCESS(s)) return s; IO_FILESYSTEM* fs = gVolumes[resolved.Drive].Fs; if (!fs) return STATUS_NOT_FOUND; if (!fs->OpenDir) return STATUS_NOT_SUPPORTED; void* dirCtx = NULL; s = fs->OpenDir(fs, resolved.Path, &dirCtx); if (!KSUCCESS(s)) return s; fs->CloseDir(fs, dirCtx); if (resolved.Path.Length + 1 > sizeof(proc->CurrentDirectory)) return STATUS_BUFFER_TOO_SMALL; uintptr_t flags = KeAcquireSpinlock(&proc->CwdLock); proc->CurrentDrive = resolved.Drive; proc->CurrentDirectoryLength = resolved.Path.Length; memcpy(proc->CurrentDirectory, resolved.Path.Data, resolved.Path.Length); proc->CurrentDirectory[resolved.Path.Length] = '\0'; if (proc->UserCwdBase && proc->UserCwdSize) { size_t formatted = KiFormatCwd(resolved.Drive, resolved.Path, (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) { peb->CurrentDirectory = SysSvFromParts((const char*)proc->UserCwdBase, copyLen); } } KeReleaseSpinlock(&proc->CwdLock, flags); return STATUS_SUCCESS; } // Registration. __declspec(dllexport) KSTATUS IoRegisterFilesystem(uint8_t volumeIndex, IO_FILESYSTEM *fs, const IO_VOLUME_PARAMS *vol) { if (volumeIndex >= MAX_DRIVES || !fs || !vol) return STATUS_INVALID_PARAMETER; if (gVolumes[volumeIndex].Fs) return STATUS_ALREADY_EXISTS; gVolumes[volumeIndex].Fs = fs; gVolumes[volumeIndex].Vol = *vol; return STATUS_SUCCESS; } // Implements the unregister Filesystem operation. __declspec(dllexport) KSTATUS IoUnregisterFilesystem(uint8_t volumeIndex) { if (volumeIndex >= MAX_DRIVES) return STATUS_INVALID_PARAMETER; if (!gVolumes[volumeIndex].Fs) return STATUS_NOT_FOUND; memset(&gVolumes[volumeIndex], 0, sizeof(gVolumes[volumeIndex])); return STATUS_SUCCESS; } // Implements the unregister Filesystems For Disk operation. __declspec(dllexport) KSTATUS IoUnregisterFilesystemsForDisk(uint8_t diskIndex) { KSTATUS last = STATUS_NOT_FOUND; for (uint8_t i = 0; i < MAX_DRIVES; i++) { if (gVolumes[i].Fs && gVolumes[i].Vol.DiskIndex == diskIndex) { memset(&gVolumes[i], 0, sizeof(gVolumes[i])); last = STATUS_SUCCESS; } } return last; } // Implements the filesystem Present operation. __declspec(dllexport) bool IoFilesystemPresent(uint8_t volumeIndex) { if (volumeIndex >= MAX_DRIVES) return false; return gVolumes[volumeIndex].Fs != (IO_FILESYSTEM*)0; } // Destructor. 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); } // IoOpenFile. __declspec(dllexport) KSTATUS IoOpenFile(SYS_STRING_VIEW path, FILE_OBJECT **out) { if ((!path.Data && path.Length) || !out) return STATUS_INVALID_PARAMETER; if (!KeCanWait()) return STATUS_INVALID_IRQL; if (path.Length == 0) return STATUS_INVALID_PARAMETER; KRESOLVED_PATH resolved; KSTATUS s = KiResolvePathV(path, &resolved); if (!KSUCCESS(s)) return s; IO_FILESYSTEM* fs = gVolumes[resolved.Drive].Fs; if (!fs) return STATUS_NOT_FOUND; void* fileCtx; s = fs->Open(fs, resolved.Path, &fileCtx); if (!KSUCCESS(s)) return s; // File objects reuse the generic object slot because the handle table only needs the shared header contract here. FILE_OBJECT* f = (FILE_OBJECT *)ObAllocateObject(ObjectTypeFile, sizeof(FILE_OBJECT)); if (!f) { fs->Close(fs, fileCtx); return STATUS_NO_MEMORY; } f->Header.Destroy = KiFileDestroy; f->Fs = fs; f->FileCtx = fileCtx; f->Lock = (SPINLOCK)SPINLOCK_INIT; f->Size = fs->GetSize(fs, fileCtx); f->Offset = 0; *out = f; return STATUS_SUCCESS; } // 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 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 (!KeCanWait()) return STATUS_INVALID_IRQL; if (size == 0) return STATUS_SUCCESS; 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; 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; } // IoWriteFile - sequential write at current offset. __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; 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)) { flags = KeAcquireSpinlock(&file->Lock); file->Offset = offset + *bytesWritten; KeReleaseSpinlock(&file->Lock, flags); } return s; } // IoCreateFile - create a new file and return it open for writing. __declspec(dllexport) KSTATUS IoCreateFile(SYS_STRING_VIEW path, FILE_OBJECT** out) { if ((!path.Data && path.Length) || !out || path.Length == 0) return STATUS_INVALID_PARAMETER; if (!KeCanWait()) return STATUS_INVALID_IRQL; KRESOLVED_PATH resolved; KSTATUS s = KiResolvePathV(path, &resolved); if (!KSUCCESS(s)) return s; IO_FILESYSTEM* fs = gVolumes[resolved.Drive].Fs; if (!fs) return STATUS_NOT_FOUND; if (!fs->Create) return STATUS_NOT_SUPPORTED; void* fileCtx; s = fs->Create(fs, resolved.Path, &fileCtx); if (!KSUCCESS(s)) return s; FILE_OBJECT* f = (FILE_OBJECT*)ObAllocateObject(ObjectTypeFile, sizeof(FILE_OBJECT)); if (!f) { fs->Close(fs, fileCtx); return STATUS_NO_MEMORY; } f->Header.Destroy = KiFileDestroy; f->Fs = fs; f->FileCtx = fileCtx; f->Lock = (SPINLOCK)SPINLOCK_INIT; f->Size = 0; f->Offset = 0; *out = f; return STATUS_SUCCESS; } // IoDeleteFile - delete a file by path (does not require an open handle). __declspec(dllexport) KSTATUS IoDeleteFile(SYS_STRING_VIEW path) { if ((!path.Data && path.Length) || path.Length == 0) return STATUS_INVALID_PARAMETER; if (!KeCanWait()) return STATUS_INVALID_IRQL; KRESOLVED_PATH resolved; KSTATUS s = KiResolvePathV(path, &resolved); if (!KSUCCESS(s)) return s; IO_FILESYSTEM* fs = gVolumes[resolved.Drive].Fs; if (!fs) return STATUS_NOT_FOUND; if (!fs->Delete) return STATUS_NOT_SUPPORTED; return fs->Delete(fs, resolved.Path); } // Implements the rename File operation. __declspec(dllexport) KSTATUS IoRenameFile(SYS_STRING_VIEW oldPath, SYS_STRING_VIEW newPath) { if ((!oldPath.Data && oldPath.Length) || (!newPath.Data && newPath.Length) || oldPath.Length == 0 || newPath.Length == 0) return STATUS_INVALID_PARAMETER; if (!KeCanWait()) return STATUS_INVALID_IRQL; KRESOLVED_PATH oldResolved; KRESOLVED_PATH newResolved; KSTATUS s = KiResolvePathV(oldPath, &oldResolved); if (!KSUCCESS(s)) return s; s = KiResolvePathV(newPath, &newResolved); if (!KSUCCESS(s)) return s; if (oldResolved.Drive != newResolved.Drive) return STATUS_NOT_SUPPORTED; IO_FILESYSTEM* fs = gVolumes[oldResolved.Drive].Fs; if (!fs) return STATUS_NOT_FOUND; if (!fs->Rename) return STATUS_NOT_SUPPORTED; return fs->Rename(fs, oldResolved.Path, newResolved.Path); } // Creates dir. __declspec(dllexport) KSTATUS IoCreateDir(SYS_STRING_VIEW path) { if ((!path.Data && path.Length) || path.Length == 0) return STATUS_INVALID_PARAMETER; if (!KeCanWait()) return STATUS_INVALID_IRQL; KRESOLVED_PATH resolved; KSTATUS s = KiResolvePathV(path, &resolved); if (!KSUCCESS(s)) return s; IO_FILESYSTEM* fs = gVolumes[resolved.Drive].Fs; if (!fs) return STATUS_NOT_FOUND; if (!fs->CreateDir) return STATUS_NOT_SUPPORTED; return fs->CreateDir(fs, resolved.Path); } // Releases dir. __declspec(dllexport) KSTATUS IoRemoveDir(SYS_STRING_VIEW path) { if ((!path.Data && path.Length) || path.Length == 0) return STATUS_INVALID_PARAMETER; if (!KeCanWait()) return STATUS_INVALID_IRQL; KRESOLVED_PATH resolved; KSTATUS s = KiResolvePathV(path, &resolved); if (!KSUCCESS(s)) return s; IO_FILESYSTEM* fs = gVolumes[resolved.Drive].Fs; if (!fs) return STATUS_NOT_FOUND; if (!fs->RemoveDir) return STATUS_NOT_SUPPORTED; return fs->RemoveDir(fs, resolved.Path); } // IoTruncateFile - resize the file; updates the cached Size on success. __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); if (KSUCCESS(s)) { file->Size = newSize; // Pull the file pointer back if it now sits past EOF. if (file->Offset > file->Size) file->Offset = file->Size; } return s; } __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; } // Returns file Size. __declspec(dllexport) uint64_t IoGetFileSize(FILE_OBJECT* file) { if (!file) return 0; uintptr_t flags = KeAcquireSpinlock(&file->Lock); uint64_t size = file->Size; KeReleaseSpinlock(&file->Lock, flags); return size; } // Releases file. __declspec(dllexport) void IoCloseFile(FILE_OBJECT* file) { if (file) ObDereferenceObject(&file->Header); } // Directory enumeration. 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); } // Opens dir. __declspec(dllexport) KSTATUS IoOpenDir(SYS_STRING_VIEW dirPath, DIR_OBJECT** out) { if (!out) return STATUS_INVALID_PARAMETER; if (dirPath.Length && !dirPath.Data) return STATUS_INVALID_PARAMETER; if (!KeCanWait()) return STATUS_INVALID_IRQL; KRESOLVED_PATH resolved; KSTATUS s; if (dirPath.Length == 0) { PROCESS* proc = KeGetCurrentProcess(); if (!proc) return STATUS_ACCESS_DENIED; uintptr_t flags = KeAcquireSpinlock(&proc->CwdLock); size_t cwdLen = proc->CurrentDirectoryLength; if (cwdLen + 1 > sizeof(resolved.Storage)) { KeReleaseSpinlock(&proc->CwdLock, flags); return STATUS_BUFFER_TOO_SMALL; } memcpy(resolved.Storage, proc->CurrentDirectory, cwdLen + 1); resolved.Drive = proc->CurrentDrive; resolved.Path = SysSvFromParts(resolved.Storage, cwdLen); KeReleaseSpinlock(&proc->CwdLock, flags); } else { s = KiResolvePathV(dirPath, &resolved); if (!KSUCCESS(s)) return s; } IO_FILESYSTEM* fs = gVolumes[resolved.Drive].Fs; if (!fs) return STATUS_NOT_FOUND; if (!fs->OpenDir) return STATUS_NOT_SUPPORTED; void* dirCtx = NULL; s = fs->OpenDir(fs, resolved.Path, &dirCtx); if (!KSUCCESS(s)) return s; DIR_OBJECT* d = (DIR_OBJECT*)ObAllocateObject(ObjectTypeDir, sizeof(DIR_OBJECT)); if (!d) { fs->CloseDir(fs, dirCtx); return STATUS_NO_MEMORY; } d->Header.Destroy = KiDirDestroy; d->Fs = fs; d->DirCtx = dirCtx; *out = d; return STATUS_SUCCESS; } // Returns dir. __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); } // Releases dir. __declspec(dllexport) void IoCloseDir(DIR_OBJECT* dir) { if (dir) ObDereferenceObject(&dir->Header); }