#include "FileStream.hpp" #include #include namespace fs = std::filesystem; #if defined(_WIN32) # define WIN32_LEAN_AND_MEAN # define NOMINMAX # include InputFileStream::InputFileStream(const fs::path& path) : mOsFileHandle{ 0 } { auto handle = reinterpret_cast(mOsFileHandle); *handle = CreateFileW( path.c_str(), // fs::path::c_str() returns a wide string on Windows GENERIC_READ, /* No sharing */ 0, /* Use default security*/ nullptr, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, /* No attribute template */ nullptr); // TODO handle error } InputFileStream::~InputFileStream() { auto handle = reinterpret_cast(mOsFileHandle); CloseHandle(*handle); } OutputFileStream::OutputFileStream(const fs::path& path, WriteMode mode) : mOsFileHandle{ 0 } { auto handle = reinterpret_cast(mOsFileHandle); DWORD creationDisposition; switch (mode) { case AppendFile: creationDisposition = OPEN_ALWAYS; break; case TruncateFile: creationDisposition = CREATE_ALWAYS; break; } *handle = CreateFileW( path.c_str(), GENERIC_WRITE, /* No sharing */ 0, /* Use default security*/ nullptr, creationDisposition, FILE_ATTRIBUTE_NORMAL, /* No attribute template */ nullptr); // TODO handle error } OutputFileStream::~OutputFileStream() { auto handle = reinterpret_cast(mOsFileHandle); CloseHandle(*handle); } static IoResult::ErrorKind MapErrorCodeToIoResult(DWORD error) { switch (error) { // TODO default: std::cerr << "Unimplemented win32 error code " << error << ", report bug immediately.\n"; std::abort(); } } static IoResult ReadBytesDirect(HANDLE hFile, size_t byteCount, std::byte* bytes) { DWORD bytesRead; BOOL result = ReadFile(hFile, bytes, byteCount, &bytesRead, nullptr); if (result) { return IoResult{ .Error = IoResult::ERR_None, .SystemError = 0, .BytesMoved = bytesRead, }; } else { DWORD errorCode = GetLastError(); return IoResult{ .Error = ::MapErrorCodeToIoResult(errorCode), .SystemError = errorCode, .BytesMoved = bytesRead, }; } } static IoResult WriteBytesDirect(HANDLE hFile, size_t byteCount, const std::byte* bytes) { DWORD bytesWritten; BOOL result = WriteFile(hFile, bytes, byteCount, &bytesWritten, nullptr); if (result) { return IoResult{ .Error = IoResult::ERR_None, .SystemError = 0, .BytesMoved = bytesWritten, }; } else { DWORD errorCode = GetLastError(); return IoResult{ .Error = ::MapErrorCodeToIoResult(errorCode), .SystemError = errorCode, .BytesMoved = bytesWritten, }; } } #elif defined(__APPLE__) || defined(__linux__) # include # include # include # include InputFileStream::InputFileStream(const fs::path& path) : mOsFileHandle{ 0 } { auto fd = reinterpret_cast(mOsFileHandle); *fd = open(path.c_str(), O_RDONLY); } InputFileStream::~InputFileStream() { auto fd = reinterpret_cast(mOsFileHandle); close(*fd); } OutputFileStream::OutputFileStream(const fs::path& path, WriteMode mode) : mOsFileHandle{ 0 } { auto fd = reinterpret_cast(mOsFileHandle); int flags = O_WRONLY | O_CREAT; switch (mode) { case AppendFile: flags |= O_APPEND; break; case TruncateFile: flags |= O_TRUNC; break; } *fd = open(path.c_str(), flags, 0644); } OutputFileStream::~OutputFileStream() { auto fd = reinterpret_cast(mOsFileHandle); close(*fd); } static IoResult::ErrorKind MapErrnoToIoResult(int err) { switch (err) { // TODO case EFAULT: return IoResult::ERR_UnexpectedEof; case EPERM: return IoResult::ERR_PermissionDenied; case ENOSPC: return IoResult::ERR_OutOfSpace; case EIO: return IoResult::ERR_Other; default: std::cerr << "Unimplemented POSIX errno " << err << ", report bug immediately.\n"; std::abort(); } } static IoResult ReadBytesDirect(const char* osFileHandle, size_t byteCount, std::byte* bytes) { int fd = *reinterpret_cast(osFileHandle); int status = read(fd, bytes, byteCount); if (status == -1) { int err = errno; return IoResult{ .Error = ::MapErrnoToIoResult(err), .SystemError = (uint32_t)err, .BytesMoved = 0, }; } else { return IoResult{ .Error = IoResult::ERR_None, .SystemError = 0, .BytesMoved = (size_t)status, // Equal to number of bytes read }; } } static IoResult WriteBytesDirect(const char* osFileHandle, size_t byteCount, const std::byte* bytes) { int fd = *reinterpret_cast(osFileHandle); int status = write(fd, bytes, byteCount); if (status == -1) { int err = errno; return IoResult{ .Error = ::MapErrnoToIoResult(err), .SystemError = (uint32_t)err, .BytesMoved = 0, }; } else { return IoResult{ .Error = IoResult::ERR_None, .SystemError = 0, .BytesMoved = (size_t)status, // Equal to number of bytes read }; } } #else # error "Unsupported target platform." #endif int InputFileStream::GetReadInSize() const { return mReadInSize; } void InputFileStream::SetReadInSize(int size) { if (size > mReadInSize) { mReadInSize = size; mBuffer = std::make_unique(size); } } bool InputFileStream::IsEof() const { return mEof; } IoResult InputFileStream::ReadBytes(size_t bufferLength, std::byte* buffer) { // TODO reduce duplicated code auto bytesMoved = std::min(mAvailableBytes, bufferLength); // On first call after construction, mFirstByteIdx will equal to mReadInSize, i.e. bytesAvailable == 0 // and this call to std::memcpy will be no-op std::memcpy(buffer, &mBuffer[mFirstByteIdx], bytesMoved); mFirstByteIdx += (int)bytesMoved; mAvailableBytes -= (int)bytesMoved; buffer += bytesMoved; size_t bytesLeft = bufferLength - bytesMoved; if (bytesLeft > mReadInSize) { // Our buffer can't handle rest of the request, just skip the buffering step // Read rest of the data into buffer { auto result = ::ReadBytesDirect(mOsFileHandle, bytesLeft, buffer); bytesMoved += result.BytesMoved; if (result.Error == IoResult::ERR_None) { if (result.BytesMoved < mReadInSize) { mEof = true; } } else { goto end; } } // Refill our buffer { auto result = ::ReadBytesDirect(mOsFileHandle, mReadInSize, mBuffer.get()); mFirstByteIdx = 0; mAvailableBytes = (int)result.BytesMoved; if (result.Error == IoResult::ERR_None) { if (result.BytesMoved < mReadInSize) { mEof = true; } } else { goto end; } } } else if (bytesLeft > 0) { // Our buffer can handle rest of the request, first buffer than supply the requested data // Refill our buffer { auto result = ::ReadBytesDirect(mOsFileHandle, mReadInSize, mBuffer.get()); mFirstByteIdx = 0; mAvailableBytes = (int)result.BytesMoved; if (result.Error == IoResult::ERR_None) { if (result.BytesMoved < mReadInSize) { mEof = true; } } else { goto end; } } // Copy data into buffer { std::memcpy(buffer, &mBuffer[mFirstByteIdx], bytesLeft); mFirstByteIdx += (int)bytesLeft; bytesMoved += bytesLeft; buffer += bytesLeft; } } else { // Request completed already } end: return IoResult{ .Error = IoResult::ERR_None, .SystemError = 0, .BytesMoved = bytesMoved, }; } int OutputFileStream::GetMaxBufferSize() const { return mMaxBufferSize; } void OutputFileStream::SetMaxBufferSize(int maxSize) { FlushBuffer(); if (maxSize > mMaxBufferSize) { mMaxBufferSize = maxSize; mBuffer = std::make_unique(maxSize); } } IoResult OutputFileStream::WriteBytes(size_t bufferLength, const std::byte* buffer) { if (bufferLength + mCurrentBufferSize > mMaxBufferSize) { FlushBuffer(); if (bufferLength > mMaxBufferSize) { return ::WriteBytesDirect(mOsFileHandle, bufferLength, buffer); } } std::memcpy(mBuffer.get() + mCurrentBufferSize, buffer, bufferLength); mCurrentBufferSize += (int)bufferLength; return IoResult{ .Error = IoResult::ERR_None, .SystemError = 0, .BytesMoved = bufferLength, }; } void OutputFileStream::FlushBuffer() { ::WriteBytesDirect(mOsFileHandle, mCurrentBufferSize, mBuffer.get()); mCurrentBufferSize = 0; }