/*** Copyright (C) 2022 J Reece Wilson (a/k/a "Reece"). All rights reserved. File: Async.Linux.cpp Date: 2022-4-12 Author: Reece ***/ #include #include "FS.hpp" #include "FileAdvisory.Unix.hpp" #include #include #include #include #include #include #include "FileStream.Unix.hpp" #include "Async.Linux.hpp" namespace Aurora::IO::FS { struct LinuxAsyncFileTransactionLoopSource : Aurora::IO::Loop::LSEvent { LinuxAsyncFileTransactionLoopSource(AuSPtr that); virtual bool IsSignaled() override; virtual bool OnTrigger(AuUInt handle) override; virtual AuLoop::ELoopSource GetType() override; private: AuWPtr caller_; }; LinuxAsyncFileTransactionLoopSource::LinuxAsyncFileTransactionLoopSource(AuSPtr that) : caller_(that), Loop::LSEvent(false, false, true) { } bool LinuxAsyncFileTransactionLoopSource::OnTrigger(AuUInt handle) { auto lock = caller_.lock(); if (lock) { return lock->Complete(); } return true; } bool LinuxAsyncFileTransactionLoopSource::IsSignaled() { if (LSEvent::IsSignaled()) { auto lock = caller_.lock(); return lock->Complete(); } return false; } Loop::ELoopSource LinuxAsyncFileTransactionLoopSource::GetType() { return Loop::ELoopSource::eSourceAIO; } LinuxAsyncFileTransaction::~LinuxAsyncFileTransaction() { } FileHandle::~FileHandle() { if ((this->readHandle != 0) && (this->readHandle != -1)) { ::close(this->readHandle); } if ((this->writeHandle != 0) && (this->writeHandle != -1) && (this->writeHandle != this->readHandle)) { ::close(this->writeHandle); } this->readHandle = this->writeHandle = -1; } bool FileHandle::Init(const AuString &path, EFileOpenMode openMode, bool directIO, EFileAdvisoryLockLevel lock) { int fileHandle; auto pathex = NormalizePathRet(path); if (pathex.empty()) { return false; } fileHandle = ::open(pathex.c_str(), openMode == EFileOpenMode::eRead ? O_RDONLY : (O_RDWR | O_CREAT), 0664); if (fileHandle == -1) { SysPushErrorIO("Couldn't open file: {} ({}) {}", path, pathex, errno); return false; } if (!ApplyDumbAdvisoryLock(fileHandle, lock)) { SysPushErrorIO("Couldn't open file: {}. File node (not section) is locked.", path); return false; } this->directIO = directIO; this->readHandle = this->writeHandle = fileHandle; this->readOnly = openMode == EFileOpenMode::eRead; return true; } void FileHandle::Init(int read, int write) { this->readHandle = read; this->writeHandle = write; this->directIO = true; this->readOnly = false; } AuSPtr LinuxAsyncFileStream::GetHandle() { return handle_; } void LinuxAsyncFileStream::Init(const AuSPtr &handle) { this->handle_ = handle; } AuSPtr LinuxAsyncFileStream::NewTransaction() { auto shared = AuMakeShared(); if (!shared) { return {}; } if (!shared->Init(this->handle_)) { return {}; } return shared; } bool LinuxAsyncFileStream::BlockingTruncate(AuUInt64 length) { return ::ftruncate(this->handle_->writeHandle, length) != -1; } bool LinuxAsyncFileStream::BlockingRead(AuUInt64 offset, const Memory::MemoryViewStreamWrite ¶meters) { if (!PosixSetOffset(this->handle_->readHandle, offset)) { return false; } AuUInt32 read; if (!PosixRead(this->handle_->readHandle, parameters.ptr, parameters.length, &read)) { return false; } return true; } bool LinuxAsyncFileStream::BlockingWrite(AuUInt64 offset, const Memory::MemoryViewStreamRead ¶meters) { if (!PosixSetOffset(this->handle_->writeHandle, offset)) { return false; } AuUInt32 read; if (!PosixWrite(this->handle_->writeHandle, parameters.ptr, parameters.length, &read)) { return false; } return true; } bool LinuxAsyncFileTransaction::Init(const AuSPtr &handle) { this->handle_ = handle; this->loopSource_ = AuMakeShared(AuSharedFromThis()); return bool(this->loopSource_); } bool LinuxAsyncFileTransaction::StartRead(AuUInt64 offset, const AuSPtr &memoryView) { if (HasState()) { SysPushErrorIO("IO Operation can not be reused yet."); return false; } auto fd = this->handle_->readHandle; if (fd == -1) { SysPushErrorUninitialized(); return false; } this->latch_ = false; this->hasError_ = false; this->bTxFinished_ = false; this->lastFinishedStat_ = 0; if (!this->loopSource_) { SysPushErrorUninitialized(); return false; } this->loopSource_->Reset(); this->lastAbstractOffset_ = offset; LIOS_Init(AuSharedFromThis()); SetMemory(memoryView); if (!UNIX::LinuxOverlappedSubmitRead(fd, offset, this, this->loopSource_.get())) { LIOS_SendProcess(0, true, errno); return false; } else { if (gRuntimeConfig.bFIODisableBatching) { UNIX::SendIOBuffers(); } return true; } } bool LinuxAsyncFileTransaction::StartWrite(AuUInt64 offset, const AuSPtr &memoryView) { if (HasState()) { SysPushErrorIO("IO Operation can not be reused yet."); return false; } auto fd = this->handle_->writeHandle; if (fd == -1) { SysPushErrorUninitialized(); return false; } this->latch_ = false; this->bTxFinished_ = false; this->hasError_ = false; this->lastFinishedStat_ = 0; if (!this->loopSource_) { SysPushErrorUninitialized(); return false; } this->loopSource_->Reset(); this->lastAbstractOffset_ = offset; LIOS_Init(AuSharedFromThis()); SetMemory(memoryView); if (!UNIX::LinuxOverlappedSubmitWrite(fd, offset, this, this->loopSource_.get())) { LIOS_SendProcess(0, true, errno); return false; } else { if (gRuntimeConfig.bFIODisableBatching) { UNIX::SendIOBuffers(); } return true; } } void LinuxAsyncFileTransaction::Reset() { if (this->loopSource_) { this->loopSource_->Reset(); } } void LinuxAsyncFileTransaction::LIOS_Process(AuUInt32 read, bool failure, int err, bool mark) { this->lastFinishedStat_ = failure ? 0 : read; this->hasError_ = failure; this->error_ = err; this->bTxFinished_ = true; if (mark) { return; } this->DispatchCb(); } void LinuxAsyncFileTransaction::DispatchCb() { if (AuExchange(this->latch_, true)) { SysPushErrorGeneric(); return; } if (this->sub_) { this->sub_->OnAsyncFileOpFinished(this->lastAbstractOffset_, this->lastFinishedStat_); } } bool LinuxAsyncFileTransaction::Complete() { if (this->bTxFinished_) { if (!this->latch_) { LIOS_SendProcess(this->lastFinishedStat_, this->lastFinishedStat_ == 0, 0, false); //DispatchCb(); } return true; } return false; } bool LinuxAsyncFileTransaction::Failed() { return this->hasError_; } AuUInt LinuxAsyncFileTransaction::GetOSErrorCode() { return AuUInt(this->error_); } AuUInt32 LinuxAsyncFileTransaction::GetLastPacketLength() { return this->lastFinishedStat_; } void LinuxAsyncFileTransaction::SetCallback(const AuSPtr &sub) { this->sub_ = sub; } bool LinuxAsyncFileTransaction::Wait(AuUInt32 timeout) { // TODO: AuList> files {AuUnsafeRaiiToShared(this)}; return WaitMultiple(files, timeout); } AuSPtr LinuxAsyncFileTransaction::GetFileHandle() { return this->handle_; } AuSPtr LinuxAsyncFileTransaction::NewLoopSource() { return AuStaticCast(AuStaticCast(this->loopSource_)); } AUKN_SYM IAsyncFileStream *OpenAsyncNew(const AuString &path, EFileOpenMode openMode, bool directIO, EFileAdvisoryLockLevel lock) { AuSPtr fileHandle; LinuxAsyncFileStream *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)) { SysPushErrorNested(); return {}; } stream = _new LinuxAsyncFileStream(); if (!stream) { SysPushErrorMem(); return {}; } stream->Init(fileHandle); return stream; } AUKN_SYM void OpenAsyncRelease(IAsyncFileStream *handle) { AuSafeDelete(handle); } }