/*** Copyright (C) 2021 J Reece Wilson (a/k/a "Reece"). All rights reserved. File: Async.NT.cpp Date: 2021-9-13 Author: Reece ***/ #include #include "FS.hpp" #include //#include "IPCHandle.hpp" #include "Async.NT.hpp" #include "FileAdvisory.NT.hpp" #include #include namespace Aurora::IO::FS { struct NtAsyncFileTransactionLoopSource : AuLoop::LSHandle { NtAsyncFileTransactionLoopSource(AuSPtr that) : caller_(that), Loop::LSHandle(AuUInt(that->event)) {} virtual bool IsSignaled() override; virtual bool OnTrigger(AuUInt handle) override; virtual AuLoop::ELoopSource GetType() override; private: AuWPtr caller_; }; bool NtAsyncFileTransactionLoopSource::OnTrigger(AuUInt handle) { auto lock = caller_.lock(); if (lock) { return lock->Complete(); } return true; } Loop::ELoopSource NtAsyncFileTransactionLoopSource::GetType() { return Loop::ELoopSource::eSourceAIO; } bool NtAsyncFileTransactionLoopSource::IsSignaled() { auto lock = caller_.lock(); if (lock) { return lock->Complete(); } return LSHandle::IsSignaled(); } NtAsyncFileTransaction::~NtAsyncFileTransaction() { AuWin32CloseHandle(this->event); } FileHandle::~FileHandle() { if (this->bNoOwns) { return; } if (this->writeHandle != this->handle) { AuWin32CloseHandle(this->writeHandle); } this->writeHandle = INVALID_HANDLE_VALUE; AuWin32CloseHandle(this->handle); } bool FileHandle::Init(const AuString &path, EFileOpenMode openMode, bool directIO, EFileAdvisoryLockLevel lock) { HANDLE fileHandle; auto pathex = NormalizePathRet(path); if (pathex.empty()) { return false; } auto win32Path = Locale::ConvertFromUTF8(pathex); if (win32Path.empty()) { return false; } auto flags = FILE_FLAG_OVERLAPPED; if (directIO) { flags |= FILE_FLAG_NO_BUFFERING; } fileHandle = INVALID_HANDLE_VALUE; switch (openMode) { case EFileOpenMode::eRead: { fileHandle = CreateFileW(win32Path.c_str(), GENERIC_READ, NtLockAdvisoryToShare(lock), NULL, OPEN_EXISTING, flags, NULL); break; } case EFileOpenMode::eReadWrite: { CreateDirectories(pathex, true); fileHandle = CreateFileW(win32Path.c_str(), GENERIC_WRITE | GENERIC_READ, NtLockAdvisoryToShare(lock), NULL, OPEN_ALWAYS, flags, NULL); if (fileHandle == INVALID_HANDLE_VALUE) { fileHandle = CreateFileW(win32Path.c_str(), GENERIC_WRITE | GENERIC_READ, NtLockAdvisoryToShare(lock), NULL, CREATE_ALWAYS, flags, NULL); } break; } case EFileOpenMode::eWrite: { CreateDirectories(pathex, true); fileHandle = CreateFileW(win32Path.c_str(), GENERIC_WRITE | FILE_READ_ATTRIBUTES, NtLockAdvisoryToShare(lock), NULL, OPEN_ALWAYS, flags, NULL); if (fileHandle == INVALID_HANDLE_VALUE) { fileHandle = CreateFileW(win32Path.c_str(), GENERIC_WRITE | GENERIC_READ, NtLockAdvisoryToShare(lock), NULL, CREATE_ALWAYS, flags, NULL); } break; } } if (fileHandle == INVALID_HANDLE_VALUE) { SysPushErrorIO("Missing file: {}", path); return {}; } this->directIO = directIO; this->handle = fileHandle; this->writeHandle = fileHandle; this->readOnly = openMode == EFileOpenMode::eRead; return true; } void FileHandle::Init(HANDLE read, HANDLE write) { this->directIO = true; this->handle = read; this->writeHandle = write; this->readOnly = false; } AuSPtr NtAsyncFileStream::GetHandle() { return handle_; } void NtAsyncFileStream::Init(const AuSPtr &handle) { this->handle_ = handle; } AuSPtr NtAsyncFileStream::NewTransaction() { auto shared = AuMakeShared(); if (!shared) { return {}; } if (!shared->Init(this->handle_)) { return {}; } return shared; } bool NtAsyncFileStream::BlockingTruncate(AuUInt64 length) { LARGE_INTEGER i {}; i.QuadPart = length; if (!SetFilePointerEx(this->handle_->handle, i, nullptr, FILE_BEGIN)) { SysPushErrorIO(); return false; } return SetEndOfFile(this->handle_->handle); } bool NtAsyncFileStream::BlockingRead(AuUInt64 offset, const Memory::MemoryViewStreamWrite ¶meters) { LARGE_INTEGER i {}; i.QuadPart = offset; if (this->handle_->bReadLock) { return false; } if (!SetFilePointerEx(this->handle_->handle, i, nullptr, FILE_BEGIN)) { SysPushErrorIO(); return false; } OVERLAPPED a {}; a.hEvent = CreateEventA(NULL, true, 0, NULL); DWORD read; if (!::ReadFile(this->handle_->handle, parameters.ptr, parameters.length, NULL, &a) && ::GetLastError() != ERROR_IO_PENDING) { SysPushErrorIO(); ::CloseHandle(a.hEvent); return false; } ::WaitForSingleObject(a.hEvent, 0); if (!::GetOverlappedResult(this->handle_->handle, &a, &read, true)) { ::CloseHandle(a.hEvent); return false; } ::CloseHandle(a.hEvent); parameters.outVariable = read; return true; } bool NtAsyncFileStream::BlockingWrite(AuUInt64 offset, const Memory::MemoryViewStreamRead ¶meters) { LARGE_INTEGER i {}; i.QuadPart = offset; if (this->handle_->bWriteLock) { return false; } if (!SetFilePointerEx(this->handle_->handle, i, nullptr, FILE_BEGIN)) { SysPushErrorIO(); return false; } OVERLAPPED a {}; a.hEvent = CreateEventA(NULL, true, 0, NULL); DWORD read; if (!::WriteFile(this->handle_->writeHandle, parameters.ptr, parameters.length, NULL, &a) && ::GetLastError() != ERROR_IO_PENDING) { SysPushErrorIO(); ::CloseHandle(a.hEvent); return false; } ::WaitForSingleObject(a.hEvent, 0); if (!::GetOverlappedResult(this->handle_->writeHandle, &a, &read, true)) { ::CloseHandle(a.hEvent); return false; } ::CloseHandle(a.hEvent); parameters.outVariable = read; return true; } bool NtAsyncFileTransaction::Init(const AuSPtr &handle) { this->pHandle_ = handle; this->overlap.hEvent = this->event = CreateEventW(nullptr, true, false, nullptr); return this->overlap.hEvent != INVALID_HANDLE_VALUE; } static bool TranslateNtStatus(NtAsyncFileTransaction *that, BOOL val) { auto er = GetLastError(); if (val) { return true; } else if (er == ERROR_IO_PENDING) { return true; } else if (er == ERROR_BROKEN_PIPE || er == ERROR_HANDLE_EOF) { SetEvent(that->event); // also required: that->bHasFailed = true; // to pass completion that->dwOsErrorCode = er; // to suppress actual error condition auto pipe = that->pNtIpcPipeImpl.lock(); that->DispatchCb(0); if (pipe) { pipe->OnEndOfReadStream(); } that->pMemoryHold.reset(); return true; } else { that->pPin.reset(); that->Reset(); that->dwOsErrorCode = er; that->bHasFailed = true; SysPushErrorFIO("QoA async FIO error: {} {}", that->GetFileHandle()->path, that->dwOsErrorCode); return false; } } static void WINAPI FileOperationCompletion( DWORD dwErrorCode, DWORD dwNumberOfBytesTransfered, LPOVERLAPPED lpOverlapped) { auto transaction = reinterpret_cast(reinterpret_cast(lpOverlapped) - offsetof(NtAsyncFileTransaction, overlap)); auto hold = AuExchange(transaction->pPin, {}); if (dwErrorCode) { hold->bHasFailed = true; hold->dwOsErrorCode = dwErrorCode; } else if (!hold->dwLastAbstractStat) { return; } SetEvent(lpOverlapped->hEvent); auto pStupid = AuExchange(hold->pMemoryHold, {}); hold->CompleteEx(dwNumberOfBytesTransfered); } bool NtAsyncFileTransaction::IDontWannaUsePorts() { // TODO: maybe we could use a semaphore counter of read attempts // to allow APC callbacks to drag behind if (AuExchange(this->pPin, AuSharedFromThis())) { while (SleepEx(100, true) == WAIT_IO_COMPLETION) { } if (AuExchange(this->pPin, AuSharedFromThis())) { SysPushErrorUnavailableError(); return {}; } } return true; } bool NtAsyncFileTransaction::StartRead(AuUInt64 offset, const AuSPtr &memoryView) { if (this->isIrredeemable_) { SysPushErrorIO("Transaction was signaled to be destroyed to reset mid synchronizable operation. You can no longer use this stream object"); return false; } if (!IDontWannaUsePorts()) { return false; } if (!memoryView) { SysPushErrorArg(); return {}; } if (this->pMemoryHold) { SysPushErrorIO("IO Operation in progress"); return {}; } if (this->pHandle_->bReadLock) { return false; } this->bLatch = false; ::ResetEvent(this->event); this->pMemoryHold = memoryView; this->bHasFailed = false; this->dwLastAbstractStat = memoryView->length; this->dwLastAbstractOffset = offset; this->dwLastBytes = 0; this->overlap.Offset = AuBitsToLower(offset); this->overlap.OffsetHigh = AuBitsToHigher(offset); auto ret = ::ReadFileEx(this->pHandle_->handle, memoryView->ptr, memoryView->length, &this->overlap, FileOperationCompletion); return TranslateNtStatus(this, ret); } bool NtAsyncFileTransaction::StartWrite(AuUInt64 offset, const AuSPtr &memoryView) { if (this->isIrredeemable_) { SysPushErrorIO("Transaction was signaled to be destroyed to reset mid synchronizable operation. You can no longer use this stream object"); return false; } if (!IDontWannaUsePorts()) { return false; } if (!memoryView) { SysPushErrorArg(); return {}; } if (this->pMemoryHold) { SysPushErrorIO("IO Operation in progress"); return {}; } if (this->pHandle_->bWriteLock) { return false; } this->bLatch = false; ::ResetEvent(this->event); this->bHasFailed = false; this->pMemoryHold = memoryView; this->dwLastBytes = 0; this->dwLastAbstractStat = memoryView->length; this->dwLastAbstractOffset = offset; this->overlap.Offset = AuBitsToLower(offset); this->overlap.OffsetHigh = AuBitsToHigher(offset); auto ret = ::WriteFileEx(this->pHandle_->writeHandle, memoryView->ptr, memoryView->length, &this->overlap, FileOperationCompletion); return TranslateNtStatus(this, ret); } void NtAsyncFileTransaction::DispatchCb(AuUInt32 read) { this->dwLastAbstractStat = 0; this->dwLastBytes = read; if (AuExchange(this->bLatch, true)) { return; } this->dwLastBytes = read; if (this->pSub_) { this->pSub_->OnAsyncFileOpFinished(this->dwLastAbstractOffset, read); } } void NtAsyncFileTransaction::Reset() { if (this->dwLastAbstractStat) { this->isIrredeemable_ = true; this->bHasFailed = true; ::CancelIoEx(this->pHandle_->handle, &this->overlap); ::SetEvent(this->event); this->dwOsErrorCode = ERROR_ABANDONED_WAIT_0; } else { ::ResetEvent(this->event); this->bHasFailed = false; } this->dwLastBytes = 0; this->dwLastAbstractStat = 0; } bool NtAsyncFileTransaction::Failed() { return this->bHasFailed && this->dwOsErrorCode != ERROR_BROKEN_PIPE && this->dwOsErrorCode != ERROR_HANDLE_EOF; } AuUInt NtAsyncFileTransaction::GetOSErrorCode() { return this->bHasFailed ? this->dwOsErrorCode : ERROR_SUCCESS; } bool NtAsyncFileTransaction::CompleteEx(AuUInt completeRoutine) { DWORD read {}; this->dwLastAbstractStat = 0; if (this->isIrredeemable_) { ::ResetEvent(this->event); return true; } if (!completeRoutine) { if ((this->bHasFailed) || (this->dwLastBytes)) { return true; } if (::GetOverlappedResult(this->pHandle_->handle, &this->overlap, &read, false)) { DispatchCb(read); return read; } } else { if (this->dwOsErrorCode == ERROR_BROKEN_PIPE || this->dwOsErrorCode == ERROR_HANDLE_EOF) { auto pipe = this->pNtIpcPipeImpl.lock(); DispatchCb(0); if (pipe) { pipe->OnEndOfReadStream(); } } else { DispatchCb(completeRoutine); } return true; } return false; } bool NtAsyncFileTransaction::Complete() { return CompleteEx(false); } AuUInt32 NtAsyncFileTransaction::GetLastPacketLength() { return this->dwLastBytes; } void NtAsyncFileTransaction::SetCallback(const AuSPtr &sub) { this->pSub_ = sub; } bool NtAsyncFileTransaction::Wait(AuUInt32 timeout) { if (this->bLatch) { return true; } DWORD ret; do { ret = ::WaitForSingleObjectEx(this->event, timeout ? timeout : INFINITE, true); } while (ret == WAIT_IO_COMPLETION); if (ret == WAIT_OBJECT_0) { return Complete(); } return false; } HANDLE NtAsyncFileTransaction::GetHandle() { return this->event; } AuSPtr NtAsyncFileTransaction::GetFileHandle() { return this->pHandle_; } AuSPtr NtAsyncFileTransaction::NewLoopSource() { return AuMakeShared(AuSharedFromThis()); } AUKN_SYM IAsyncFileStream *OpenAsyncNew(const AuString &path, EFileOpenMode openMode, bool directIO, EFileAdvisoryLockLevel lock) { AuSPtr fileHandle; NtAsyncFileStream *stream; if (path.empty()) { SysPushErrorParam("Empty path"); return {}; } if (!EFileOpenModeIsValid(openMode)) { SysPushErrorParam("Invalid open mode"); return {}; } fileHandle = AuMakeShared(); if (!fileHandle->Init(path, openMode, directIO, lock)) { return {}; } stream = _new NtAsyncFileStream(); if (!stream) { return {}; } stream->Init(fileHandle); return stream; } AUKN_SYM void OpenAsyncRelease(IAsyncFileStream *handle) { AuSafeDelete(handle); } }