summaryrefslogtreecommitdiff
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
parent1e89700693e92bb9c78ace739c71431b74d91e22 (diff)
Buffered file IO.
-rw-r--r--include/fud_drain.hpp2
-rw-r--r--include/fud_file.hpp119
-rw-r--r--include/fud_string.hpp17
-rw-r--r--include/fud_vector.hpp7
-rw-r--r--source/fud_file.cpp465
-rw-r--r--test/test_file.cpp80
6 files changed, 635 insertions, 55 deletions
diff --git a/include/fud_drain.hpp b/include/fud_drain.hpp
index d630bc7..13c878a 100644
--- a/include/fud_drain.hpp
+++ b/include/fud_drain.hpp
@@ -32,6 +32,8 @@ struct DrainResult {
{
return status == FudStatus::Success;
}
+
+ constexpr bool operator==(const DrainResult&) const noexcept = default;
};
template <typename Sink, typename Source>
diff --git a/include/fud_file.hpp b/include/fud_file.hpp
index 7a53468..82f8291 100644
--- a/include/fud_file.hpp
+++ b/include/fud_file.hpp
@@ -18,13 +18,13 @@
#ifndef FUD_FILE_HPP
#define FUD_FILE_HPP
+#include "fud_drain.hpp"
#include "fud_option.hpp"
#include "fud_permissions.hpp"
#include "fud_result.hpp"
#include "fud_status.hpp"
#include "fud_string_view.hpp"
#include "fud_vector.hpp"
-#include "fud_drain.hpp"
#include <fcntl.h>
@@ -92,7 +92,8 @@ class OpenFlags {
return mode;
}
- constexpr uint32_t flags() const noexcept {
+ constexpr uint32_t flags() const noexcept
+ {
uint32_t openFlags = 0;
openFlags |= static_cast<uint32_t>(hasFlag(OpenFlagEnum::Append)) * O_APPEND;
openFlags |= static_cast<uint32_t>(hasFlag(OpenFlagEnum::Truncate)) * O_TRUNC;
@@ -125,18 +126,15 @@ using FileResult = Result<RegularFile, FudStatus>;
class RegularFile {
public:
- static FileResult open(
- StringView filename,
- FileAccessMode mode,
- OpenFlags flags,
- Option<int> dirFdoption);
+ friend class BufferedRegularFile;
+ static FileResult open(StringView filename, FileAccessMode mode, OpenFlags flags, Option<int> dirFdoption);
static FileResult create(
StringView filename,
FileAccessMode mode,
OpenFlags flags,
Permissions permissions,
- bool exclusive,
+ bool createOnly,
Option<int> dirFdOption);
FudStatus close();
@@ -145,13 +143,40 @@ class RegularFile {
Result<size_t, FudStatus> size() const;
+ constexpr int fileDescriptor() const
+ {
+ return m_fd;
+ }
+
+ [[nodiscard]] constexpr bool isOpen() const
+ {
+ return m_fd >= 0;
+ }
+
+ FudStatus seekStart();
+
+ FudStatus seekEnd();
+
+ FudStatus seek(size_t position);
+
+ /** \brief Write from source to file as sink. */
+ DrainResult write(const std::byte* source, size_t length, size_t maxExtraAttempts = 0);
+
+ DrainResult read(std::byte* sink, size_t length, size_t maxExtraAttempts = 0);
+
private:
- RegularFile() = default;
+ constexpr RegularFile() = default;
+
+ FudStatus validateIOParameters(const std::byte* source) const;
public:
RegularFile(const RegularFile& rhs) = delete;
- RegularFile(RegularFile&& rhs) noexcept;
+ constexpr RegularFile(RegularFile&& rhs) noexcept :
+ m_position{rhs.m_position}, m_fd{rhs.m_fd}, m_openFlags{rhs.m_openFlags}, m_modeFlags{rhs.m_modeFlags}
+ {
+ rhs.m_fd = -1;
+ }
~RegularFile();
@@ -160,28 +185,76 @@ class RegularFile {
RegularFile& operator=(RegularFile&& rhs) noexcept;
private:
+ size_t m_position{0};
int m_fd{-1};
OpenFlags m_openFlags{};
FileAccessMode m_modeFlags{};
};
-enum class ReadPolicy {
- Unbuffered,
- ReadAhead,
-};
-
class BufferedRegularFile {
-public:
+ public:
+ static BufferedRegularFile make(RegularFile&& file, Vector<std::byte>&& buffer = Vector<std::byte>::NullVector());
+
+ FudStatus close(bool discardBuffer);
+
/** \brief Write from source to file as sink. */
- DrainResult write(const std::byte* source, size_t sourceSize, size_t length, size_t offset);
+ DrainResult write(const std::byte* source, size_t length, Option<size_t> maxExtraAttempts);
+
/** \brief Read from file as source to sink. */
- DrainResult read(std::byte* sink, size_t sinkSize, size_t length, size_t offset);
-private:
- Vector<std::byte> m_readBuffer{Vector<std::byte>::NullVector()};
- Vector<std::byte> m_writeBuffer{Vector<std::byte>::NullVector()};
+ DrainResult read(std::byte* sink, size_t length, Option<size_t> maxExtraAttempts);
+
+ FudStatus setBuffer(Vector<std::byte>&& buffer, bool discardOldBuffer);
+
+ DrainResult flush(size_t maxExtraAttempts = 0);
+
+ void discard();
+
+ FudStatus resizeBuffer(size_t size);
+
+ FudStatus seekStart();
+
+ FudStatus seekEnd();
+
+ FudStatus seek(size_t position);
+
+ constexpr const RegularFile& file() const
+ {
+ return m_file;
+ }
+
+ constexpr RegularFile& file()
+ {
+ return m_file;
+ }
+
+ [[nodiscard]] constexpr bool bufferEmpty() const
+ {
+ return m_bufferLength == 0;
+ }
+
+ private:
+ constexpr BufferedRegularFile() noexcept = default;
+
+ constexpr BufferedRegularFile(RegularFile&& regularFile, Vector<std::byte>&& buffer) noexcept :
+ m_buffer{std::move(buffer)}, m_file{std::move(regularFile)}
+ {
+ }
+
+ Vector<std::byte> m_buffer{Vector<std::byte>::NullVector()};
+
RegularFile m_file;
- bool m_readBuffered{false};
- bool m_writeBuffered{false};
+
+ size_t m_bufferLength{0};
+ size_t m_bufferPosition{0};
+
+ enum class Operation : uint8_t
+ {
+ None,
+ Write,
+ Read
+ };
+
+ Operation m_lastOperation{Operation::None};
};
} // namespace fud
diff --git a/include/fud_string.hpp b/include/fud_string.hpp
index 935e1c1..a20b067 100644
--- a/include/fud_string.hpp
+++ b/include/fud_string.hpp
@@ -307,22 +307,31 @@ class String {
* length is greater than zero. */
Option<utf8> pop();
+ /** \brief Append a C string to the back of the string, growing it as necessary. */
FudStatus append(const char* source);
+ /** \brief Append a String to the back of the string, growing it as necessary. */
FudStatus append(const String& source);
+ /** \brief Append a StringView to the back of the string, growing it as necessary. */
FudStatus append(StringView source);
+ /** \brief Create a new string with the contents of this string and rhs. */
+ [[nodiscard]] StringResult catenate(const String& rhs) const;
+
+ /** \@copydoc String::catenate(const String& rhs) const */
+ [[nodiscard]] StringResult catenate(const char* rhs) const;
+
+ /** \brief Insert as much of source into the string as possible, returning
+ * how many bytes and the status of the insertion. */
DrainResult drain(const char* source);
+ /** @copydoc String::drain(const char* source) */
DrainResult drain(const String& source);
+ /** @copydoc String::drain(const char* source) */
DrainResult drain(StringView source);
- [[nodiscard]] StringResult catenate(const String& rhs) const;
-
- [[nodiscard]] StringResult catenate(const char* rhs) const;
-
[[nodiscard]] bool compare(const String& rhs) const;
FudStatus clear();
diff --git a/include/fud_vector.hpp b/include/fud_vector.hpp
index a2a0984..9159770 100644
--- a/include/fud_vector.hpp
+++ b/include/fud_vector.hpp
@@ -120,7 +120,7 @@ class Vector {
static Result<Vector<T>, FudStatus> withSize(size_t count, Allocator* allocator = &globalFudAllocator)
{
Vector<T> output{};
- auto status = initializeWithCapacity(output, count, allocator);
+ auto status = initializeWithSize(output, count, allocator);
if (status != FudStatus::Success) {
return FudError{status};
}
@@ -402,7 +402,10 @@ class Vector {
FudStatus clear()
{
if (m_allocator == nullptr || m_data == nullptr) {
- return FudStatus::ObjectInvalid;
+ if (m_length > 0) {
+ return FudStatus::ObjectInvalid;
+ }
+ return FudStatus::Success;
}
for (size_t index = 0; index < m_length; ++index) {
m_data[index].~T();
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
diff --git a/test/test_file.cpp b/test/test_file.cpp
index 27844d8..62df1ae 100644
--- a/test/test_file.cpp
+++ b/test/test_file.cpp
@@ -17,6 +17,7 @@
#include "fud_file.hpp"
#include "fud_string.hpp"
+#include "fud_vector.hpp"
#include "test_common.hpp"
#include "gtest/gtest.h"
@@ -28,6 +29,17 @@
namespace fud {
+auto rmFile(const auto& filename) -> int
+{
+ auto result = unlink(filename.c_str());
+ if (result == -1) {
+ if (errno == ENOENT) {
+ return 0;
+ }
+ }
+ return result;
+}
+
TEST(FudFile, Basic)
{
constexpr const char* testDirCName = "/tmp/fud_directory_test";
@@ -47,15 +59,6 @@ TEST(FudFile, Basic)
String testName1{String::makeFromCStrings(testDirCName, "/", "test1").takeOkay()};
String testName2{String::makeFromCStrings(testDirCName, "/", "test2").takeOkay()};
- auto rmFile = [](const auto& filename) -> int {
- auto result = unlink(filename.c_str());
- if (result == -1) {
- if (errno == ENOENT) {
- return 0;
- }
- }
- return result;
- };
ASSERT_EQ(rmFile(testName1), 0);
ASSERT_EQ(rmFile(testName2), 0);
@@ -94,4 +97,63 @@ TEST(FudFile, Basic)
EXPECT_EQ(fileResult.getError(), FudStatus::Failure);
}
+TEST(FudBufferedFile, OpenReadWrite)
+{
+ constexpr const char* testDirCName = "/tmp/fud_directory_test";
+ const auto testDirName{String::makeFromCString(testDirCName).takeOkay()};
+ ASSERT_TRUE(testDirName.utf8Valid());
+ constexpr mode_t pathMode = 0777;
+
+ ASSERT_EQ(removeRecursive(testDirName), FudStatus::Success);
+
+ auto mkdirResult = mkdir(testDirName.c_str(), pathMode);
+ EXPECT_EQ(mkdirResult, 0);
+ if (mkdirResult != 0) {
+ ASSERT_EQ(removeRecursive(testDirName), FudStatus::Success);
+ return;
+ }
+
+ String testName{String::makeFromCStrings(testDirCName, "/", "test1").takeOkay()};
+ auto fileResult{RegularFile::create(
+ testName.asView(),
+ FileAccessMode::ReadWrite,
+ OpenFlags{OpenFlagEnum::Truncate},
+ Permissions{PermReadWrite, PermReadWrite, PermReadWrite},
+ true,
+ NullOpt)};
+ ASSERT_TRUE(fileResult.isOkay());
+
+ ASSERT_GT(testName.length(), 8);
+
+ auto vectorResult{Vector<std::byte>::withSize(8)};
+ ASSERT_TRUE(vectorResult.isOkay());
+ auto bufferedFile{BufferedRegularFile::make(fileResult.takeOkay(), vectorResult.takeOkay())};
+
+ ASSERT_EQ(bufferedFile.file().size().getOkayOr(std::numeric_limits<size_t>::max()), 0);
+
+ auto writeResult = bufferedFile.write(
+ reinterpret_cast<const std::byte*>(testName.data()),
+ testName.size(),
+ NullOpt);
+ DrainResult expected{testName.size(), FudStatus::Success};
+ ASSERT_EQ(writeResult, expected);
+
+ DrainResult nullExpected{0, FudStatus::Success};
+ ASSERT_EQ(bufferedFile.flush(), nullExpected);
+ ASSERT_EQ(bufferedFile.seekStart(), FudStatus::Success);
+
+ Vector<utf8> output{Vector<utf8>::withSize(testName.size()).takeOkay()};
+ auto readResult = bufferedFile.read(
+ reinterpret_cast<std::byte*>(output.data()),
+ testName.size(),
+ testName.size());
+ ASSERT_EQ(readResult, expected);
+
+ EXPECT_EQ(output.size(), testName.size());
+ EXPECT_EQ(0, compareMem(output.data(), output.size(), testName.data(), testName.size()).takeOkayOr(-1));
+
+ EXPECT_EQ(bufferedFile.close(true), FudStatus::Success);
+ ASSERT_EQ(rmFile(testName), 0);
+}
+
} // namespace fud