diff options
author | Dominick Allen <djallen@librehumanitas.org> | 2024-11-10 15:14:54 -0600 |
---|---|---|
committer | Dominick Allen <djallen@librehumanitas.org> | 2024-11-10 15:14:54 -0600 |
commit | 012df4bc38777c9053353ec2c4213bba67d63ab4 (patch) | |
tree | 4267c0cd4a7ed61119a44b31f0fc8b8adff2b4cf | |
parent | 1e89700693e92bb9c78ace739c71431b74d91e22 (diff) |
Buffered file IO.
-rw-r--r-- | include/fud_drain.hpp | 2 | ||||
-rw-r--r-- | include/fud_file.hpp | 119 | ||||
-rw-r--r-- | include/fud_string.hpp | 17 | ||||
-rw-r--r-- | include/fud_vector.hpp | 7 | ||||
-rw-r--r-- | source/fud_file.cpp | 465 | ||||
-rw-r--r-- | test/test_file.cpp | 80 |
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 |