/*** Copyright (C) 2021 J Reece Wilson (a/k/a "Reece"). All rights reserved. File: FileStream.Unix.cpp Date: 2021-6-12 Author: Reece ***/ #define _FILE_OFFSET_BITS 64 #define _LARGEFILE64_SOURCE #include #include "FS.hpp" #include "FileStream.Generic.hpp" #include "FileAdvisory.Unix.hpp" #include #include #include #if !defined(_AURUNTIME_GENERICFILESTREAM) namespace Aurora::IO::FS { static const AuUInt64 kFileCopyBlock = 0x4000; // 16KiB static bool PosixLseek63(int fd, AuUInt64 offset, int whence, AuUInt64 *pOffset) { if (offset > std::numeric_limits::max()) { SysPushErrorIO("int overflow exploit?"); return false; } #if defined(AURORA_IS_LINUX_DERIVED) auto ret = lseek64(fd, offset, whence); #elif defined(AURORA_IS_64BIT) static_assert(std::numeric_limits::max() >= std::numeric_limits::max(), "unsupported posix os"); auto ret = lseek(fd, offset, whence); #else #error accient 32-bit posix operating systems require 64-bit file awareness #endif if (ret < 0) { //SysPushErrorIO("PosixLseek63 IO Error: %i", ret); return false; } if (pOffset) { *pOffset = ret; } return true; } static AuUInt64 PosixGetOffset(int fd) { AuUInt64 ret; if (!PosixLseek63(fd, 0, SEEK_SET, &ret)) { return 0; } return ret; } bool PosixSetOffset(int fd, AuUInt64 offset) { return PosixLseek63(fd, offset, SEEK_SET, nullptr); } static AuUInt64 PosixGetLength(int fd) { AuUInt64 ret {}, old {}; bool status {}; if (!PosixLseek63(fd, 0, SEEK_SET, &old)) { return 0; } status = PosixLseek63(fd, 0, SEEK_END, &ret); status &= PosixLseek63(fd, old, SEEK_SET, nullptr); return status ? ret : 0; } bool PosixRead(int fd, void *buf, AuUInt32 count, AuUInt32 *pRead) { auto ret = read(fd, buf, count); if (ret < 0) { SysPushErrorIO("PosixRead IO Error: %i", ret); return false; } if (pRead) { *pRead = ret; } return true; } bool PosixWrite(int fd, const void *buf, AuUInt32 count, AuUInt32 *pWritten) { auto ret = write(fd, buf, count); if (ret < 0) { SysPushErrorIO("PosixWrite IO Error: %i", ret); return false; } if (pWritten) { *pWritten = ret; } return true; } PosixFileStream::~PosixFileStream() { Close(); } bool PosixFileStream::Init(AuSPtr pHandle) { AuCtorCode_t code; this->pHandle_ = pHandle; return true; } AuUInt64 PosixFileStream::GetOffset() { AU_LOCK_GUARD(this->spinlock_); int fd {}; if (auto pHandle = this->pHandle_) { if (!pHandle->IsFile()) { SysPushErrorIOResourceRejected(); return 0; } if (auto opt = pHandle->GetOSHandleSafe()) { fd = opt.Value(); } else { SysPushErrorInvalidFd(); return 0; } } else { SysPushErrorUninitialized(); return 0; } return PosixGetOffset(fd); } bool PosixFileStream::SetOffset(AuUInt64 offset) { AU_LOCK_GUARD(this->spinlock_); int fd {}; if (auto pHandle = this->pHandle_) { if (!pHandle->IsFile()) { SysPushErrorIOResourceRejected(); return 0; } if (auto opt = pHandle->GetOSHandleSafe()) { fd = opt.Value(); } else { SysPushErrorInvalidFd(); return false; } } else { SysPushErrorUninitialized(); return false; } return PosixSetOffset(fd, offset); } AuUInt64 PosixFileStream::GetLength() { AU_LOCK_GUARD(this->spinlock_); int fd {}; if (auto pHandle = this->pHandle_) { if (!pHandle->IsFile()) { // Intentionally suppressed (for the moment) // I don't want to fill error stacks up under paths that can handle char-devs by reading until EoS // If we do, we could hit an always-throw on AuErrorStack-catch // Some users are completely unaware they can access/test the underlying handle // Returning zero will do return 0; } if (auto opt = pHandle->GetOSHandleSafe()) { fd = opt.Value(); } else { SysPushErrorInvalidFd(); return 0; } } else { SysPushErrorUninitialized(); return 0; } return PosixGetLength(fd); } bool PosixFileStream::Read(const Memory::MemoryViewStreamWrite ¶meters) { AU_LOCK_GUARD(this->spinlock_); parameters.outVariable = 0; if (!this->pHandle_) { SysPushErrorUninitialized(); return 0; } int fd = (int)this->pHandle_->GetOSReadHandleSafe().ValueOr((AuUInt)-1); if (fd == -1) { SysPushErrorIOResourceRejected(); return 0; } auto length = parameters.length; AuUInt offset {0}; while (length) { AuUInt32 read; int blockSize = AuMin(kFileCopyBlock, length); if (!PosixRead(fd, &reinterpret_cast(parameters.ptr)[offset], blockSize, &read)) { SysPushErrorNested("File Error: {}", path_); break; } if (read == 0) { break; } offset += read; length -= read; } parameters.outVariable = offset; return true; } bool PosixFileStream::Write(const Memory::MemoryViewStreamRead ¶meters) { AU_LOCK_GUARD(this->spinlock_); if (!this->pHandle_) { SysPushErrorUninitialized(); return 0; } int fd = this->pHandle_->GetOSWriteHandleSafe().ValueOr((AuUInt)-1); if (fd == -1) { SysPushErrorUninitialized(); return 0; } parameters.outVariable = 0; AuUInt length = parameters.length; AuUInt offset {0}; while (length) { AuUInt32 written; int blockSize = AuMin(AuUInt(kFileCopyBlock), length); if (!PosixWrite(fd, &reinterpret_cast(parameters.ptr)[offset], blockSize, &written)) { SysPushErrorNested("File Error: {}", this->pHandle_->GetPath()); return false; } if (written != blockSize) { SysPushErrorNested("File Error: {}", path_); break; } offset += written; length -= written; } if (!offset) { return false; } parameters.outVariable = offset; return true; } void PosixFileStream::Close() { auto pHandle = this->pHandle_; AuResetMember(this->pHandle_); if (AuExchange(this->bMadeTemporary, false)) { if (pHandle) { ::unlink(pHandle->GetPath().c_str()); } } } void PosixFileStream::Flush() { if (!this->pHandle_) { SysPushErrorUninitialized(); return; } int fd = this->pHandle_->GetOSWriteHandleSafe().ValueOr((AuUInt)-1); if (fd == -1) { SysPushErrorIOResourceRejected(); return; } ::fsync(fd); } void PosixFileStream::WriteEoS() { if (!this->pHandle_) { SysPushErrorUninitialized(); return; } int fd = this->pHandle_->GetOSWriteHandleSafe().ValueOr((AuUInt)-1); if (fd == -1) { SysPushErrorIOResourceRejected(); return; } ::ftruncate(fd, GetOffset()); } void PosixFileStream::MakeTemporary() { this->bMadeTemporary = true; } int PosixFileStream::GetUnixHandle() { return this->pHandle_ ? (int)this->pHandle_->GetOSWriteHandleSafe().ValueOr(this->pHandle_->GetOSReadHandleSafe().ValueOr((AuUInt)-1)) : -1; } AuSPtr PosixFileStream::GetHandle() { return this->pHandle_; } AUKN_SYM AuSPtr OpenBlockingFileStreamFromHandle(AuSPtr pIOHandle) { auto pStream = AuMakeShared(); if (!pStream) { SysPushErrorMemory(); return nullptr; } pStream->Init(pIOHandle); return pStream; } static IFileStream *OpenNewEx(const AuString &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 PosixFileStream(); if (!pStream) { SysPushErrorMemory(); return nullptr; } pStream->Init(pHandle); return pStream; } catch (...) { return nullptr; } } AUKN_SYM IFileStream *CreateNew(const AuString &path) { return OpenNewEx(path, EFileOpenMode::eWrite, EFileAdvisoryLockLevel::eBlockReadWrite, true); } AUKN_SYM void CreateRelease(IFileStream *that) { AuSafeDelete(that); } AUKN_SYM IFileStream *OpenNew(const AuString &path, EFileOpenMode openMode, EFileAdvisoryLockLevel lock) { return OpenNewEx(path, openMode, lock, false); } AUKN_SYM void OpenRelease(IFileStream *that) { AuSafeDelete(that); } AUKN_SYM IFileStream *OpenReadNew(const AuString &path, EFileAdvisoryLockLevel level) { return OpenNewEx(path, EFileOpenMode::eRead, level, false); } AUKN_SYM void OpenReadRelease(IFileStream * that) { AuSafeDelete(that); } AUKN_SYM IFileStream *OpenWriteNew(const AuString &path, EFileAdvisoryLockLevel level) { return OpenNewEx(path, EFileOpenMode::eWrite, level, false); } AUKN_SYM void OpenWriteRelease(IFileStream *that) { AuSafeDelete(that); } } #endif