fd/ops: Add readAllFixed()
This is similar to readAll in that it guarantees to read the file all
the way to EOF, but it allows you to provide a fixed buffer space to
store the file contents. This is useful if you know the bounds of the
file and don't mind preallocating space for them.
This is generally the preferred candidate for reading all of a file, as
you should know the bounds of the data you would like to read if you are
reading the entire thing.
Change-Id: I72a9e2fb7ee84d4e37963c8dcdff3e9e351e2339
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 5a1d2dc..53380d3 100644
--- a/include-fd/stdplus/fd/ops.hpp
+++ b/include-fd/stdplus/fd/ops.hpp
@@ -26,6 +26,8 @@
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> readAllFixed(Fd& fd, size_t align,
+ std::span<std::byte> buf);
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,
@@ -67,6 +69,13 @@
}
template <typename Container>
+inline auto readAllFixed(Fd& fd, Container&& c)
+{
+ return detail::alignedOp(detail::readAllFixed, fd,
+ std::forward<Container>(c));
+}
+
+template <typename Container>
inline auto recv(Fd& fd, Container&& c, RecvFlags flags = {})
{
return detail::alignedOp(detail::recvAligned, fd,
diff --git a/src/fd/ops.cpp b/src/fd/ops.cpp
index 8d79103..d05e9b2 100644
--- a/src/fd/ops.cpp
+++ b/src/fd/ops.cpp
@@ -181,6 +181,51 @@
}
}
+std::span<std::byte> readAllFixed(Fd& fd, size_t align,
+ std::span<std::byte> buf)
+{
+ std::size_t totalB = 0;
+ auto validateSize = [&]() {
+ if (totalB % align != 0)
+ {
+ throw exception::Incomplete(std::format(
+ "readAllFixed partial {}B/{}B", totalB % align, align));
+ }
+ };
+ try
+ {
+ while (totalB < buf.size())
+ {
+ auto r = fd.read(
+ buf.subspan(totalB, std::min(maxStrideB, buf.size() - totalB)));
+ if (r.size() == 0)
+ {
+ throw exception::WouldBlock("readAllFixed");
+ }
+ totalB += r.size();
+ }
+ std::byte b;
+ auto r = fd.read(std::span(&b, 1));
+ if (r.size() == 0)
+ {
+ throw exception::WouldBlock("readAllFixed");
+ }
+ throw std::system_error(
+ std::make_error_code(std::errc::value_too_large),
+ "readAllFixed overflow");
+ }
+ catch (const exception::Eof& e)
+ {
+ validateSize();
+ return buf.subspan(0, totalB);
+ }
+ catch (const std::system_error& e)
+ {
+ validateSize();
+ throw;
+ }
+}
+
} // namespace detail
} // namespace fd
} // namespace stdplus
diff --git a/test/fd/ops.cpp b/test/fd/ops.cpp
index 2855edd..708a0ff 100644
--- a/test/fd/ops.cpp
+++ b/test/fd/ops.cpp
@@ -248,4 +248,108 @@
stdplus::hton(int32_t{0x05060708})}));
}
+TEST(ReadAllFixed, SuccessEmpty)
+{
+ testing::StrictMock<FdMock> fd;
+ {
+ testing::InSequence seq;
+ EXPECT_CALL(fd, read(_))
+ .WillRepeatedly(testing::Throw(exception::Eof("test")));
+ }
+ std::array<char, 16> buf;
+ auto ret = readAllFixed(fd, buf);
+ EXPECT_EQ(std::string_view(ret.begin(), ret.end()), "");
+}
+
+TEST(ReadAllFixed, 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")));
+ }
+ std::array<char, 16> buf;
+ auto ret = readAllFixed(fd, buf);
+ EXPECT_EQ(std::string_view(ret.begin(), ret.end()), "alpha one");
+}
+
+TEST(ReadAllFixed, 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(""));
+ }
+ std::array<char, 16> buf;
+ EXPECT_THROW(readAllFixed(fd, buf), exception::WouldBlock);
+}
+
+TEST(ReadAllFixed, 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(""));
+ }
+ std::array<int32_t, 2> buf;
+ EXPECT_THROW(readAllFixed(fd, buf), exception::Incomplete);
+}
+
+TEST(ReadAllFixed, 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")));
+ }
+ std::array<int32_t, 2> buf;
+ EXPECT_THROW(readAllFixed(fd, buf), exception::Incomplete);
+}
+
+TEST(ReadAllFixed, 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")));
+ }
+ std::array<int32_t, 2> buf;
+ EXPECT_THAT(readAllFixed(fd, buf),
+ testing::ElementsAre(stdplus::hton(int32_t{0x01020304}),
+ stdplus::hton(int32_t{0x05060708})));
+}
+
+TEST(ReadAllFixed, TooMuch)
+{
+ 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(SizeIs(Ge(1)))).WillOnce(readSv("\x09"));
+ }
+ std::array<int32_t, 2> buf;
+ EXPECT_THROW(readAllFixed(fd, buf), std::system_error);
+}
+
} // namespace stdplus::fd