summaryrefslogtreecommitdiff
path: root/source/fud_file.cpp
diff options
context:
space:
mode:
authorDominick Allen <djallen@librehumanitas.org>2024-11-10 15:14:54 -0600
committerDominick Allen <djallen@librehumanitas.org>2024-11-10 15:14:54 -0600
commit012df4bc38777c9053353ec2c4213bba67d63ab4 (patch)
tree4267c0cd4a7ed61119a44b31f0fc8b8adff2b4cf /source/fud_file.cpp
parent1e89700693e92bb9c78ace739c71431b74d91e22 (diff)
Buffered file IO.
Diffstat (limited to 'source/fud_file.cpp')
-rw-r--r--source/fud_file.cpp465
1 files changed, 448 insertions, 17 deletions
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<int> dirFdOption)
+FileResult RegularFile::open(StringView filename, FileAccessMode mode, OpenFlags flags, Option<int> 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<int> 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<uint8_t>(exclusive));
+ openFlags |= flags.flags() | O_CREAT | (O_EXCL * static_cast<uint8_t>(createOnly));
open_how openHow{};
zeroObject(openHow);
@@ -210,11 +206,6 @@ RegularFile::~RegularFile()
static_cast<void>(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<void>(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<size_t, FudStatus> RegularFile::size() const
{
- using RetType = Result<size_t, FudStatus>;
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<size_t>{static_cast<size_t>(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<size_t>(fileSize);
+ return FudStatus::Success;
+}
+
+FudStatus RegularFile::seek(size_t position)
+{
+ if (position > std::numeric_limits<off_t>::max()) {
+ return FudStatus::RangeError;
+ }
+
+ auto seekStatus = lseek(m_fd, static_cast<off_t>(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<size_t>(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<size_t>(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<size_t>(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<size_t>(fileSize));
+ if (bytesRemaining > 0 && result.status == FudStatus::Success) {
+ result.status = FudStatus::Partial;
+ }
+
+ return result;
+}
+
+BufferedRegularFile BufferedRegularFile::make(RegularFile&& file, Vector<std::byte>&& buffer)
+{
+ return BufferedRegularFile{std::move(file), std::move(buffer)};
+}
+
+FudStatus BufferedRegularFile::close(bool discardBuffer)
+{
+ if (not discardBuffer) {
+ static_cast<void>(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<size_t> 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<size_t> 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<std::byte>&& buffer, bool discardOldBuffer)
+{
+ static_cast<void>(buffer);
+ static_cast<void>(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