fd/ops: Add readall()
This creates a dynamic buffer of all of the data in read out of the file
starting at the current fd position. Generally this is used to reliably
read small files for convenience. Larger reads should still chunk if the
entire file does not need to reside in memory.
Change-Id: I722de2cbd66370a222763602248bc4912ada25cf
Signed-off-by: William A. Kennington III <wak@google.com>
diff --git a/include-fd/stdplus/fd/ops.hpp b/include-fd/stdplus/fd/ops.hpp
index 4296e2b..5a1d2dc 100644
--- a/include-fd/stdplus/fd/ops.hpp
+++ b/include-fd/stdplus/fd/ops.hpp
@@ -1,11 +1,13 @@
#pragma once
#include <stdplus/fd/dupable.hpp>
#include <stdplus/fd/intf.hpp>
+#include <stdplus/function_view.hpp>
#include <stdplus/net/addr/sock.hpp>
#include <stdplus/raw.hpp>
#include <span>
#include <utility>
+#include <vector>
namespace stdplus
{
@@ -23,6 +25,7 @@
std::span<std::byte> readAligned(Fd& fd, size_t align,
std::span<std::byte> buf);
+void readAll(Fd& fd, function_view<std::span<std::byte>(size_t req)> resize);
std::span<std::byte> recvAligned(Fd& fd, size_t align, std::span<std::byte> buf,
RecvFlags flags);
std::span<const std::byte> writeAligned(Fd& fd, size_t align,
@@ -49,6 +52,20 @@
std::forward<Container>(c));
}
+template <typename Container = std::vector<std::byte>>
+Container readAll(Fd& fd)
+{
+ using Data = raw::detail::dataType<Container>;
+ Container ret;
+ auto resize = [&](size_t req) {
+ ret.resize((req + sizeof(Data) - 1) / sizeof(Data));
+ return std::span(reinterpret_cast<std::byte*>(ret.data()),
+ ret.size() * sizeof(Data));
+ };
+ detail::readAll(fd, resize);
+ return ret;
+}
+
template <typename Container>
inline auto recv(Fd& fd, Container&& c, RecvFlags flags = {})
{
diff --git a/src/fd/ops.cpp b/src/fd/ops.cpp
index c299ed0..8d79103 100644
--- a/src/fd/ops.cpp
+++ b/src/fd/ops.cpp
@@ -131,6 +131,56 @@
return opAligned("sendAligned", &Fd::send, fd, align, data, flags);
}
+constexpr std::size_t maxStrideB = 65536;
+
+void readAll(Fd& fd, function_view<std::span<std::byte>(size_t req)> resize)
+{
+ std::size_t strideB = 256;
+ std::size_t totalB = 0;
+ auto doRead = [&]() {
+ auto buf = resize(totalB + strideB);
+ auto r = fd.read(buf.subspan(totalB));
+ if (r.size() == 0)
+ {
+ throw exception::WouldBlock("readAll");
+ }
+ totalB += r.size();
+ };
+ auto validateSize = [&]() {
+ auto buf = resize(totalB);
+ if (totalB != buf.size())
+ {
+ throw exception::Incomplete(
+ std::format("readAll extra {}B", buf.size() - totalB));
+ }
+ };
+ try
+ {
+ while (strideB < maxStrideB)
+ {
+ doRead();
+ if (totalB >= strideB)
+ {
+ strideB = strideB << 1;
+ }
+ }
+ while (true)
+ {
+ doRead();
+ }
+ }
+ catch (const exception::Eof&)
+ {
+ validateSize();
+ return;
+ }
+ catch (const std::system_error&)
+ {
+ validateSize();
+ throw;
+ }
+}
+
} // namespace detail
} // namespace fd
} // namespace stdplus
diff --git a/test/fd/ops.cpp b/test/fd/ops.cpp
index dde1788..2855edd 100644
--- a/test/fd/ops.cpp
+++ b/test/fd/ops.cpp
@@ -167,4 +167,85 @@
EXPECT_EQ(stdplus::ntoh(i[0]), 0x01020304);
}
+TEST(ReadAll, SuccessEmpty)
+{
+ testing::StrictMock<FdMock> fd;
+ {
+ testing::InSequence seq;
+ EXPECT_CALL(fd, read(_))
+ .WillRepeatedly(testing::Throw(exception::Eof("test")));
+ }
+ EXPECT_EQ(readAll<std::string>(fd), "");
+}
+
+TEST(ReadAll, Success)
+{
+ testing::StrictMock<FdMock> fd;
+ {
+ testing::InSequence seq;
+ EXPECT_CALL(fd, read(SizeIs(Ge(4)))).WillOnce(readSv("alph"));
+ EXPECT_CALL(fd, read(SizeIs(Ge(2)))).WillOnce(readSv("a "));
+ EXPECT_CALL(fd, read(SizeIs(Ge(3)))).WillOnce(readSv("one"));
+ EXPECT_CALL(fd, read(_))
+ .WillRepeatedly(testing::Throw(exception::Eof("test")));
+ }
+ EXPECT_EQ(readAll<std::string>(fd), "alpha one");
+}
+
+TEST(ReadAll, Block)
+{
+ testing::StrictMock<FdMock> fd;
+ {
+ testing::InSequence seq;
+ EXPECT_CALL(fd, read(SizeIs(Ge(4)))).WillOnce(readSv("alph"));
+ EXPECT_CALL(fd, read(SizeIs(Ge(2)))).WillOnce(readSv("a "));
+ EXPECT_CALL(fd, read(_)).WillRepeatedly(readSv(""));
+ }
+ EXPECT_THROW(readAll<std::string>(fd), exception::WouldBlock);
+}
+
+TEST(ReadAll, NotEnough)
+{
+ testing::StrictMock<FdMock> fd;
+ {
+ testing::InSequence seq;
+ EXPECT_CALL(fd, read(SizeIs(Ge(5))))
+ .WillOnce(readSv("\x01\x02\x03\x04\x05"));
+ EXPECT_CALL(fd, read(SizeIs(Ge(1)))).WillOnce(readSv("\x06"));
+ EXPECT_CALL(fd, read(_)).WillRepeatedly(readSv(""));
+ }
+ EXPECT_THROW(readAll<std::vector<int32_t>>(fd), exception::Incomplete);
+}
+
+TEST(ReadAll, NotEnoughEof)
+{
+ testing::StrictMock<FdMock> fd;
+ {
+ testing::InSequence seq;
+ EXPECT_CALL(fd, read(SizeIs(Ge(5))))
+ .WillOnce(readSv("\x01\x02\x03\x04\x05"));
+ EXPECT_CALL(fd, read(SizeIs(Ge(1)))).WillOnce(readSv("\x06"));
+ EXPECT_CALL(fd, read(_))
+ .WillRepeatedly(testing::Throw(exception::Eof("test")));
+ }
+ EXPECT_THROW(readAll<std::vector<int32_t>>(fd), exception::Incomplete);
+}
+
+TEST(ReadAll, EnoughInt)
+{
+ testing::StrictMock<FdMock> fd;
+ {
+ testing::InSequence seq;
+ EXPECT_CALL(fd, read(SizeIs(Ge(5))))
+ .WillOnce(readSv("\x01\x02\x03\x04\x05"));
+ EXPECT_CALL(fd, read(SizeIs(Ge(1)))).WillOnce(readSv("\x06"));
+ EXPECT_CALL(fd, read(SizeIs(Ge(2)))).WillOnce(readSv("\x07\x08"));
+ EXPECT_CALL(fd, read(_))
+ .WillRepeatedly(testing::Throw(exception::Eof("test")));
+ }
+ EXPECT_EQ(readAll<std::vector<int32_t>>(fd),
+ (std::vector{stdplus::hton(int32_t{0x01020304}),
+ stdplus::hton(int32_t{0x05060708})}));
+}
+
} // namespace stdplus::fd