From 012df4bc38777c9053353ec2c4213bba67d63ab4 Mon Sep 17 00:00:00 2001 From: Dominick Allen Date: Sun, 10 Nov 2024 15:14:54 -0600 Subject: Buffered file IO. --- source/fud_file.cpp | 465 ++++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 448 insertions(+), 17 deletions(-) (limited to 'source/fud_file.cpp') diff --git a/source/fud_file.cpp b/source/fud_file.cpp index 94f1f27..55f8dbc 100644 --- a/source/fud_file.cpp +++ b/source/fud_file.cpp @@ -26,11 +26,7 @@ namespace fud { -FileResult RegularFile::open( - StringView filename, - FileAccessMode mode, - OpenFlags flags, - Option dirFdOption) +FileResult RegularFile::open(StringView filename, FileAccessMode mode, OpenFlags flags, Option dirFdOption) { if (!filename.nullTerminated()) { return FileResult::error(FudStatus::ArgumentInvalid); @@ -120,7 +116,7 @@ FileResult RegularFile::create( FileAccessMode mode, OpenFlags flags, Permissions permissions, - bool exclusive, + bool createOnly, Option dirFdOption) { if (!filename.nullTerminated()) { @@ -150,7 +146,7 @@ FileResult RegularFile::create( return FileResult::error(FudStatus::OperationInvalid); } - openFlags |= flags.flags() | O_CREAT | (O_EXCL * static_cast(exclusive)); + openFlags |= flags.flags() | O_CREAT | (O_EXCL * static_cast(createOnly)); open_how openHow{}; zeroObject(openHow); @@ -210,11 +206,6 @@ RegularFile::~RegularFile() static_cast(this->close()); } -RegularFile::RegularFile(RegularFile&& rhs) noexcept : m_fd{rhs.m_fd}, m_openFlags{rhs.m_openFlags}, m_modeFlags{rhs.m_modeFlags} -{ - rhs.m_fd = -1; -} - RegularFile& RegularFile::operator=(RegularFile&& rhs) noexcept { if (&rhs == this) { @@ -223,6 +214,7 @@ RegularFile& RegularFile::operator=(RegularFile&& rhs) noexcept static_cast(this->close()); + m_position = rhs.m_position; m_fd = rhs.m_fd; m_openFlags = rhs.m_openFlags; m_modeFlags = rhs.m_modeFlags; @@ -278,24 +270,463 @@ FudStatus RegularFile::close() Result RegularFile::size() const { - using RetType = Result; auto fileSize = lseek(m_fd, 0, SEEK_END); if (fileSize == -1) { switch (errno) { case EBADF: + return FudError{FudStatus::HandleInvalid}; case ESPIPE: - return RetType::error(FudStatus::ObjectInvalid); + return FudError{FudStatus::ObjectInvalid}; default: - return RetType::error(FudStatus::Failure); + return FudError{FudStatus::Failure}; } } auto seekBegin = lseek(m_fd, 0, SEEK_SET); if (seekBegin == -1) { - return RetType::error(FudStatus::Failure); + return FudError{FudStatus::Failure}; + } + + return Okay{static_cast(fileSize)}; +} + +FudStatus RegularFile::seekStart() +{ + return seek(0); +} + +FudStatus RegularFile::seekEnd() +{ + auto fileSize = lseek(m_fd, 0, SEEK_END); + if (fileSize == -1) { + switch (errno) { + case EBADF: + return FudStatus::HandleInvalid; + case ESPIPE: + return FudStatus::ObjectInvalid; + case EOVERFLOW: + return FudStatus::RangeError; + case EINVAL: + default: + return FudStatus::Failure; + } + } + fudAssert(fileSize >= 0); + m_position = static_cast(fileSize); + return FudStatus::Success; +} + +FudStatus RegularFile::seek(size_t position) +{ + if (position > std::numeric_limits::max()) { + return FudStatus::RangeError; + } + + auto seekStatus = lseek(m_fd, static_cast(position), SEEK_SET); + if (seekStatus == -1) { + switch (errno) { + case EBADF: + return FudStatus::HandleInvalid; + case ESPIPE: + return FudStatus::ObjectInvalid; + case EOVERFLOW: + return FudStatus::RangeError; + case EINVAL: + default: + return FudStatus::Failure; + } + } + fudAssert(seekStatus >= 0); + fudAssert(static_cast(seekStatus) == position); + m_position = position; + return FudStatus::Success; +} + +FudStatus RegularFile::validateIOParameters(const std::byte* source) const +{ + if (source == nullptr) { + return FudStatus::NullPointer; + } + + if (not isOpen()) { + return FudStatus::HandleInvalid; + } + + return FudStatus::Success; +} + +DrainResult RegularFile::write(const std::byte* source, size_t length, size_t maxExtraAttempts) +{ + DrainResult result{0, FudStatus::Success}; + + result.status = validateIOParameters(source); + if (result.status != FudStatus::Success || length == 0) { + return result; + } + + size_t bytesRemaining = length; + size_t numAttempts = 0; + while (bytesRemaining > 0 && numAttempts <= maxExtraAttempts) { + auto writeStatus = ::write(m_fd, source + result.bytesDrained, bytesRemaining); + + bool okay = writeStatus != -1; + size_t bytesWritten = 0; + if (writeStatus > 0) { + bytesWritten = static_cast(writeStatus); + } + fudAssert(bytesWritten <= bytesRemaining); + if constexpr (EAGAIN != EWOULDBLOCK) { + if (writeStatus == EWOULDBLOCK) { + okay = true; + numAttempts++; + } + } + if (not okay) { + switch (errno) { + case EAGAIN: + case EINTR: + okay = true; + numAttempts++; + break; + case EINVAL: + result.status = FudStatus::ObjectInvalid; + break; + case EBADF: + result.status = FudStatus::HandleInvalid; + break; + case EPERM: + result.status = FudStatus::PermissionDenied; + break; + default: + result.status = FudStatus::Failure; + break; + } + } + + if (not okay) { + break; + } + + if (writeStatus > 0) { + numAttempts = 0; + } else if (writeStatus == 0) { + numAttempts++; + } + + m_position += bytesWritten; + bytesRemaining -= bytesWritten; + result.bytesDrained += bytesWritten; + } + + if (bytesRemaining > 0 && result.status == FudStatus::Success) { + result.status = FudStatus::Partial; + } + + return result; +} + +DrainResult RegularFile::read(std::byte* sink, size_t length, size_t maxExtraAttempts) +{ + DrainResult result{0, FudStatus::Success}; + + result.status = validateIOParameters(sink); + if (result.status != FudStatus::Success || length == 0) { + return result; + } + + size_t bytesRemaining = length; + size_t numAttempts = 0; + while (bytesRemaining > 0 && numAttempts <= maxExtraAttempts) { + auto readStatus = ::read(m_fd, sink + result.bytesDrained, bytesRemaining); + size_t bytesRead = 0; + if (readStatus > 0) { + bytesRead = static_cast(readStatus); + } + bool okay = readStatus != -1; + if constexpr (EAGAIN != EWOULDBLOCK) { + if (readStatus == EWOULDBLOCK) { + okay = true; + numAttempts++; + } + } + if (not okay) { + switch (errno) { + case EAGAIN: + case EINTR: + okay = true; + numAttempts++; + break; + case EINVAL: + case EISDIR: + result.status = FudStatus::ObjectInvalid; + break; + case EBADF: + result.status = FudStatus::HandleInvalid; + break; + case EPERM: + result.status = FudStatus::PermissionDenied; + break; + default: + result.status = FudStatus::Failure; + break; + } + } + + if (not okay) { + break; + } + + if (readStatus > 0) { + numAttempts = 0; + } else if (readStatus == 0) { + numAttempts++; + } + + m_position += bytesRead; + bytesRemaining -= bytesRead; + result.bytesDrained += bytesRead; } - return RetType::okay(static_cast(fileSize)); + if (bytesRemaining > 0 && result.status == FudStatus::Success) { + result.status = FudStatus::Partial; + } + + return result; +} + +BufferedRegularFile BufferedRegularFile::make(RegularFile&& file, Vector&& buffer) +{ + return BufferedRegularFile{std::move(file), std::move(buffer)}; +} + +FudStatus BufferedRegularFile::close(bool discardBuffer) +{ + if (not discardBuffer) { + static_cast(flush()); + } else { + discard(); + } + return m_file.close(); +} + +void BufferedRegularFile::discard() +{ + m_bufferLength = 0; +} + +FudStatus BufferedRegularFile::seekStart() +{ + if (not bufferEmpty()) { + return FudStatus::OperationInvalid; + } + + return m_file.seekStart(); +} + +FudStatus BufferedRegularFile::seekEnd() +{ + if (not bufferEmpty()) { + return FudStatus::OperationInvalid; + } + + return m_file.seekEnd(); +} + +FudStatus BufferedRegularFile::seek(size_t position) +{ + if (not bufferEmpty()) { + return FudStatus::OperationInvalid; + } + + return m_file.seek(position); +} + +FudStatus BufferedRegularFile::resizeBuffer(size_t size) +{ + if (m_bufferLength > 0) { + return FudStatus::OperationInvalid; + } + + return m_buffer.resize(size); +} + +DrainResult BufferedRegularFile::write(const std::byte* source, size_t length, Option maxExtraAttempts) +{ + DrainResult result{0, FudStatus::Success}; + + if (source == nullptr) { + result.status = FudStatus::NullPointer; + return result; + } + + if (not m_file.isOpen()) { + result.status = FudStatus::HandleInvalid; + return result; + } + + if (m_lastOperation != Operation::Write) { + m_bufferLength = 0; + m_lastOperation = Operation::Write; + } + + if (length == 0) { + return result; + } + + if (m_bufferLength < m_buffer.size()) { + auto cap = m_buffer.size() - m_bufferLength; + size_t count = length; + if (count > cap) { + count = cap; + } + auto copyStatus = copyMem(m_buffer.data() + m_bufferLength, cap, source, count); + fudAssert(copyStatus == FudStatus::Success); + + source += count; + length -= count; + + m_bufferLength += count; + result.bytesDrained += count; + } + + fudAssert(m_bufferLength <= m_buffer.size()); + if (m_bufferLength == m_buffer.size()) { + auto writeBufferResult = flush(maxExtraAttempts.valueOr(0)); + if (writeBufferResult.status != FudStatus::Success) { + result.status = writeBufferResult.status; + return result; + } + } + + if (length > m_buffer.size()) { + auto drainResult = m_file.write(source, length, maxExtraAttempts.valueOr(0)); + fudAssert(drainResult.bytesDrained <= length); + + source += drainResult.bytesDrained; + length -= drainResult.bytesDrained; + + result.bytesDrained += drainResult.bytesDrained; + result.status = drainResult.status; + } + + if (result.status != FudStatus::Success) { + return result; + } + + if (length > 0) { + fudAssert(m_bufferLength == 0); + fudAssert(length < m_buffer.size()); + auto copyStatus = copyMem(m_buffer.data(), m_buffer.size(), source, length); + fudAssert(copyStatus == FudStatus::Success); + } + + return result; +} + +DrainResult BufferedRegularFile::read(std::byte* sink, size_t length, Option maxExtraAttempts) +{ + DrainResult result{0, FudStatus::Success}; + + if (sink == nullptr) { + result.status = FudStatus::NullPointer; + return result; + } + + if (not m_file.isOpen()) { + result.status = FudStatus::HandleInvalid; + return result; + } + + if (m_lastOperation != Operation::Write) { + m_bufferLength = 0; + m_lastOperation = Operation::Write; + } + + if (length == 0) { + return result; + } + + if (m_lastOperation == Operation::Write && m_bufferLength > 0) { + result.status = FudStatus::OperationInvalid; + return result; + } + + if (m_lastOperation != Operation::Read) { + m_lastOperation = Operation::Read; + } + + if (m_bufferLength > 0 && m_bufferPosition < m_bufferLength) { + auto count = m_bufferLength - m_bufferPosition; + if (count > length) { + count = length; + } + auto copyStatus = copyMem(sink, length, m_buffer.data() + m_bufferPosition, count); + fudAssert(copyStatus == FudStatus::Success); + + sink += count; + length -= count; + + m_bufferPosition += count; + result.bytesDrained += count; + } + + fudAssert(length == 0 || m_bufferPosition == m_bufferLength); + + if (m_bufferPosition == m_bufferLength) { + m_bufferPosition = 0; + m_bufferLength = 0; + } + + if (length > m_buffer.size()) { + auto drainResult = m_file.read(sink, length, maxExtraAttempts.valueOr(0)); + result.status = drainResult.status; + result.bytesDrained += drainResult.bytesDrained; + } + + if (length == 0 || result.status != FudStatus::Success) { + return result; + } + + fudAssert(m_bufferLength == 0 && m_bufferPosition == 0); + auto drainResult = m_file.read(m_buffer.data(), m_buffer.size(), maxExtraAttempts.valueOr(0)); + if (drainResult.status != FudStatus::Success && drainResult.status != FudStatus::Partial) { + result.status = drainResult.status; + } else { + result.status = FudStatus::Success; + m_bufferLength = drainResult.bytesDrained; + } + + return result; +} + +FudStatus BufferedRegularFile::setBuffer(Vector&& buffer, bool discardOldBuffer) +{ + static_cast(buffer); + static_cast(discardOldBuffer); + return FudStatus::NotImplemented; +} + +DrainResult BufferedRegularFile::flush(size_t maxExtraAttempts) +{ + fudAssert(m_bufferLength <= m_buffer.size()); + if (m_bufferLength == 0) { + return {0, FudStatus::Success}; + } + + auto drainResult = m_file.write(m_buffer.data(), m_bufferLength, maxExtraAttempts); + if (drainResult.status == FudStatus::Success) { + m_bufferLength = 0; + } else if (drainResult.status == FudStatus::Partial) { + fudAssert(drainResult.bytesDrained <= m_bufferLength); + if (drainResult.bytesDrained < m_bufferLength) { + auto diff = m_bufferLength - drainResult.bytesDrained; + const auto* src = m_buffer.data() + drainResult.bytesDrained; + auto moveStatus = copyMem(m_buffer.data(), m_buffer.size(), src, diff); + fudAssert(moveStatus == FudStatus::Success); + m_bufferLength = diff; + } + } + return drainResult; } } // namespace fud -- cgit v1.2.3