/*** Copyright (C) 2021 J Reece Wilson (a/k/a "Reece"). All rights reserved. File: FileStream.NT.cpp Date: 2021-6-12 Author: Reece ***/ #include #include "FS.hpp" #include "FileStream.NT.hpp" #include "FileAdvisory.NT.hpp" #include #if !defined(_AURUNTIME_GENERICFILESTREAM) namespace Aurora::IO::FS { static const AuUInt64 kFileCopyBlock = 0xFFFF * 128; WinFileStream::WinFileStream() : reader_(AuUnsafeRaiiToShared(this)), writer_(AuUnsafeRaiiToShared(this)), sreader_(AuUnsafeRaiiToShared(this)), swriter_(AuUnsafeRaiiToShared(this)) { } WinFileStream::~WinFileStream() { Close(); } void WinFileStream::Init(AuSPtr pHandle) { this->pHandle_ = pHandle; } AuUInt64 WinFileStream::GetOffset() { AU_LOCK_GUARD(this->mutex_); LARGE_INTEGER distance {}; LARGE_INTEGER pos {}; HANDLE hHandle {}; if (auto pHandle = this->pHandle_) { if (!pHandle->IsFile()) { SysPushErrorIOResourceRejected(); return 0; } hHandle = this->GetWin32Handle(); if (hHandle == INVALID_HANDLE_VALUE) { SysPushErrorInvalidFd(); return 0; } } else { SysPushErrorUninitialized(); return 0; } if (::SetFilePointerEx(hHandle, distance, &pos, FILE_CURRENT) == INVALID_SET_FILE_POINTER) { SysPushErrorIO("SetFilePointerEx IO Error: 0x{:x}, {}", GetLastError(), this->pHandle_ ? this->pHandle_->GetPath() : ""); return 0; } return pos.QuadPart; } bool WinFileStream::SetOffset(AuUInt64 offset) { AU_LOCK_GUARD(this->mutex_); LARGE_INTEGER distance {}; LARGE_INTEGER pos {}; HANDLE hHandle {}; if (auto pHandle = this->pHandle_) { if (!pHandle->IsFile()) { SysPushErrorIOResourceRejected(); return 0; } hHandle = this->GetWin32Handle(); if (hHandle == INVALID_HANDLE_VALUE) { SysPushErrorInvalidFd(); return 0; } } else { SysPushErrorUninitialized(); return 0; } distance.QuadPart = offset; if (::SetFilePointerEx(hHandle, distance, &pos, FILE_BEGIN) == INVALID_SET_FILE_POINTER) { SysPushErrorIO("SetFilePointerEx IO Error: 0x{:x}, {}", GetLastError(), this->pHandle_ ? this->pHandle_->GetPath() : ""); return false; } return true; } AuUInt64 WinFileStream::GetLength() { AU_LOCK_GUARD(this->mutex_); LARGE_INTEGER length; HANDLE hHandle {}; if (auto pHandle = this->pHandle_) { if (!pHandle->IsFile()) { SysPushErrorIOResourceRejected(); return 0; } hHandle = this->GetWin32Handle(); if (hHandle == INVALID_HANDLE_VALUE) { SysPushErrorInvalidFd(); return 0; } } else { SysPushErrorUninitialized(); return 0; } if (!::GetFileSizeEx(hHandle, &length)) { SysPushErrorIO(); return 0; } return length.QuadPart; } bool WinFileStream::Read(const Memory::MemoryViewStreamWrite ¶meters) { AU_LOCK_GUARD(this->mutex_); OVERLAPPED overlapped {}; AuUInt64 uOffset {}; parameters.outVariable = 0; if (!this->pHandle_) { SysPushErrorUninitialized(); return 0; } auto hHandle = this->GetWin32Handle(true); HANDLE hEventHandle { INVALID_HANDLE_VALUE }; if (hHandle == INVALID_HANDLE_VALUE) { SysPushErrorIOResourceRejected(); return {}; } AuUInt64 uLength = parameters.length; if (this->pHandle_->IsPipe()) { DWORD uAvail {}; if (!PeekNamedPipe(hHandle, NULL, NULL, NULL, &uAvail, NULL)) { return false; } uLength = AuMin(uLength, uAvail); if (!uLength) { return true; } } bool bIsAsync = this->pHandle_->IsAsync(); if (bIsAsync) { hEventHandle = ::CreateEventA(nullptr, false, false, nullptr); if (!hEventHandle) { SysPushErrorGeneric(); return false; } } while (uLength) { DWORD read {}; auto blockSize = AuMin(AuUInt(kFileCopyBlock), uLength); if (this->pHandle_->IsAsync()) { overlapped.hEvent = hEventHandle; if (!::ReadFile(hHandle, reinterpret_cast(parameters.ptr) + uOffset, AuUInt32(blockSize), NULL, &overlapped)) { if (::GetLastError() == ERROR_IO_PENDING) { ::WaitForSingleObject(hEventHandle, 0); } else { SysPushErrorIO("ReadFile IO Error: 0x{:x}, {}", GetLastError(), this->pHandle_ ? this->pHandle_->GetPath() : ""); AuWin32CloseHandle(hEventHandle); parameters.outVariable = uOffset; return false; } } if (!::GetOverlappedResult(hHandle, &overlapped, &read, true)) { if (::GetLastError() != ERROR_HANDLE_EOF) { AuWin32CloseHandle(hEventHandle); parameters.outVariable = uOffset; return false; } } } else { if (!::ReadFile(hHandle, reinterpret_cast(parameters.ptr) + uOffset, blockSize, &read, NULL)) { SysPushErrorIO("ReadFile IO Error: 0x{:x}, {}", GetLastError(), this->pHandle_ ? this->pHandle_->GetPath() : ""); AuWin32CloseHandle(hEventHandle); parameters.outVariable = uOffset; return false; } } if (read == 0) { break; } uOffset += read; uLength -= read; } AuWin32CloseHandle(hEventHandle); parameters.outVariable = uOffset; return true; } bool WinFileStream::Write(const Memory::MemoryViewStreamRead ¶meters) { AU_LOCK_GUARD(this->mutex_); OVERLAPPED overlapped {}; AuUInt64 uOffset {}; parameters.outVariable = 0; auto optHandle = this->GetHandle()->GetOSWriteHandleSafe(); if (!optHandle) { SysPushErrorIOResourceRejected(); return false; } auto hHandle = (HANDLE)optHandle.value(); HANDLE hEventHandle { INVALID_HANDLE_VALUE }; if (hHandle == INVALID_HANDLE_VALUE) { SysPushErrorUninitialized(); return 0; } bool bIsAsync = this->pHandle_->IsAsync(); if (bIsAsync) { hEventHandle = ::CreateEventA(nullptr, false, false, nullptr); if (!hEventHandle) { SysPushErrorGeneric(); return false; } } AuUInt64 uLength = parameters.length; while (uLength) { DWORD written {}; auto uBlockSize = AuMin(AuUInt(kFileCopyBlock), uLength); if (bIsAsync) { overlapped.hEvent = hEventHandle; if (!::WriteFile(hHandle, reinterpret_cast(parameters.ptr) + uOffset, AuUInt32(uBlockSize), NULL, &overlapped)) { if (GetLastError() == ERROR_IO_PENDING) { ::WaitForSingleObject(hEventHandle, 0); } else { SysPushErrorIO("WriteFile IO Error: 0x{:x}, {}", GetLastError(), this->pHandle_ ? this->pHandle_->GetPath() : ""); AuWin32CloseHandle(hEventHandle); parameters.outVariable = uOffset; return false; } } if (!::GetOverlappedResult(hHandle, &overlapped, &written, true)) { AuWin32CloseHandle(hEventHandle); parameters.outVariable = uOffset; return false; } } else { if (!::WriteFile(hHandle, reinterpret_cast(parameters.ptr) + uOffset, uBlockSize, &written, NULL)) { SysPushErrorIO("WriteFile IO Error: 0x{:x}, {}", GetLastError(), this->pHandle_ ? this->pHandle_->GetPath() : ""); parameters.outVariable = uOffset; return false; } } if (!written) { break; } uOffset += written; uLength -= written; } AuWin32CloseHandle(hEventHandle); parameters.outVariable = uOffset; return true; } void WinFileStream::WriteEoS() { AU_LOCK_GUARD(this->mutex_); auto hHandle = this->GetWin32Handle(); if (hHandle == INVALID_HANDLE_VALUE) { SysPushErrorUninitialized(); return; } NTWriteEoS(hHandle); } void WinFileStream::Close() { AU_LOCK_GUARD(this->mutex_); auto hHandle = this->GetWin32Handle(); bool bDeleteFailed {}; AuString path; if ((hHandle != INVALID_HANDLE_VALUE) && (this->bShouldDelete)) { FILE_DISPOSITION_INFO rm {}; rm.DeleteFile = true; if (!(pSetFileInformationByHandle && pSetFileInformationByHandle(hHandle, _FILE_INFO_BY_HANDLE_CLASS::FileDispositionInfo, &rm, sizeof(rm)))) { path = this->pHandle_ ? this->pHandle_->GetPath() : ""; SysPushErrorIO("Couldn't delete temporary file {}", path); if (path.length()) { bDeleteFailed = true; } } else { bDeleteFailed = true; } } AuResetMember(this->pHandle_); // a bit of a hack for problematic NT-like platforms and Windows XP if (bDeleteFailed) { Aurora::RuntimeWaitForSecondaryTick(); AuFS::Remove(path); } } void WinFileStream::Flush() { auto hHandle = this->GetWin32Handle(); ::FlushFileBuffers(hHandle); } void WinFileStream::MakeTemporary() { this->bShouldDelete = true; } bool WinFileStream::IsFlushOnClose() const { AU_LOCK_GUARD(this->mutex_); if (auto &pHandle = this->pHandle_) { return pHandle->IsFlushOnClose(); } else { return false; } } void WinFileStream::SetFlushOnClose(bool bFlushOnClose) { AU_LOCK_GUARD(this->mutex_); if (auto &pHandle = this->pHandle_) { pHandle->SetFlushOnClose(bFlushOnClose); } } bool WinFileStream::IsWriteEoSOnClose() const { AU_LOCK_GUARD(this->mutex_); if (auto &pHandle = this->pHandle_) { return pHandle->IsWriteEoSOnClose(); } else { return false; } } void WinFileStream::SetWriteEoSOnClose(bool bShouldWriteEoS) { AU_LOCK_GUARD(this->mutex_); if (auto &pHandle = this->pHandle_) { pHandle->SetWriteEoSOnClose(bShouldWriteEoS); } } AuSPtr WinFileStream::GetHandle() { return this->pHandle_; } IO::IStreamReader *WinFileStream::ToStreamReader() { return &this->reader_; } IO::IStreamWriter *WinFileStream::ToStreamWriter() { return &this->writer_; } IO::ISeekingReader *WinFileStream::ToStreamSeekingReader() { return &this->sreader_; } IO::ISeekingWriter *WinFileStream::ToStreamSeekingWriter() { return &this->swriter_; } HANDLE WinFileStream::GetWin32Handle(bool bReadOnly) { if (auto pHandle = this->pHandle_) { if (bReadOnly) { return (HANDLE)pHandle->GetOSReadHandleSafe().ValueOr((AuUInt)INVALID_HANDLE_VALUE); } else { return (HANDLE)pHandle->GetOSWriteHandleSafe().ValueOr(pHandle->GetOSReadHandleSafe().ValueOr((AuUInt)INVALID_HANDLE_VALUE)); } } return INVALID_HANDLE_VALUE; } AUKN_SYM IFileStream *OpenBlockingFileStreamFromHandleNew(const AuSPtr &pIOHandle) { SysCheckArgNotNull(pIOHandle, {}); auto pStream = _new WinFileStream(); SysCheckNotNullMemory(pStream, {}); if (!AuStaticCast(pIOHandle)->pIPCPipe && AuStaticCast(pIOHandle)->ShouldClone()) { auto pIOHandle2 = AuMakeShared(); SysCheckNotNullMemory(pIOHandle2, {}); if (!pIOHandle2->InitFromPair(pIOHandle->GetOSReadHandleSafe(), pIOHandle->GetOSWriteHandle())) { SysPushErrorIOResourceFailure(); return {}; } pStream->Init(pIOHandle2); } else { pStream->Init(pIOHandle); } return pStream; } AUKN_SYM void OpenBlockingFileStreamFromHandleRelease(IFileStream *that) { AuSafeDelete(that); } static IFileStream *OpenNewEx(const AuROString &path, EFileOpenMode openMode, EFileAdvisoryLockLevel lock, bool bCheck) { try { auto pHandle = AuIO::IOHandleShared(); if (!pHandle) { SysPushErrorMemory(); return nullptr; } AuIO::IIOHandle::HandleCreate createhandle(path); createhandle.eAdvisoryLevel = lock; createhandle.eMode = openMode; createhandle.bFailIfNonEmptyFile = bCheck; createhandle.bDirectIOMode = false; createhandle.bAsyncHandle = false; if (!pHandle->InitFromPath(createhandle)) { SysPushErrorNested(); return nullptr; } auto pStream = _new WinFileStream(); if (!pStream) { SysPushErrorMemory(); return nullptr; } pStream->Init(pHandle); return pStream; } catch (...) { return nullptr; } } AUKN_SYM IFileStream *CreateNew(const AuROString &path) { return OpenNewEx(path, EFileOpenMode::eWrite, EFileAdvisoryLockLevel::eBlockReadWrite, true); } AUKN_SYM void CreateRelease(IFileStream *that) { AuSafeDelete(that); } AUKN_SYM IFileStream *OpenNew(const AuROString &path, EFileOpenMode openMode, EFileAdvisoryLockLevel lock) { return OpenNewEx(path, openMode, lock, false); } AUKN_SYM void OpenRelease(IFileStream *that) { AuSafeDelete(that); } AUKN_SYM IFileStream *OpenReadNew(const AuROString &path, EFileAdvisoryLockLevel level) { return OpenNew(path, EFileOpenMode::eRead, level); } AUKN_SYM void OpenReadRelease(IFileStream * that) { AuSafeDelete(that); } AUKN_SYM IFileStream *OpenWriteNew(const AuROString &path, EFileAdvisoryLockLevel level) { return OpenNew(path, EFileOpenMode::eWrite, level); } AUKN_SYM void OpenWriteRelease(IFileStream *that) { AuSafeDelete(that); } } #endif