fd/ops: Exact operations to differentiate incomplete and blocking
We want to be able to tell the difference between the two and allow for
programs to terminate entire transfers if incompletes happen.
Change-Id: Ie424879b7b045ad5478720f26ed3e58d0696e142
Signed-off-by: William A. Kennington III <wak@google.com>
diff --git a/src/fd/ops.cpp b/src/fd/ops.cpp
index b0f2bef..d5991c6 100644
--- a/src/fd/ops.cpp
+++ b/src/fd/ops.cpp
@@ -15,15 +15,29 @@
static void opExact(const char* name, Fun&& fun, Fd& fd, std::span<Byte> data,
Args&&... args)
{
- while (data.size() > 0)
+ std::size_t total = 0;
+ try
{
- auto ret = (fd.*fun)(data, std::forward<Args>(args)...);
- if (ret.size() == 0)
+ while (total < data.size())
{
- throw exception::WouldBlock(
- std::format("{} missing {}B", name, data.size()));
+ auto r = (fd.*fun)(data.subspan(total),
+ std::forward<Args>(args)...);
+ if (r.size() == 0)
+ {
+ throw exception::WouldBlock(
+ std::format("{} missing", name, data.size()));
+ }
+ total += r.size();
}
- data = data.subspan(ret.size());
+ }
+ catch (const std::system_error&)
+ {
+ if (total != 0)
+ {
+ throw exception::Incomplete(
+ std::format("{} is {}B/{}B", name, total, data.size()));
+ }
+ throw;
}
}
diff --git a/test/fd/ops.cpp b/test/fd/ops.cpp
index c0328fe..ef1d489 100644
--- a/test/fd/ops.cpp
+++ b/test/fd/ops.cpp
@@ -1,11 +1,19 @@
+#include "util.hpp"
+
+#include <stdplus/exception.hpp>
+#include <stdplus/fd/gmock.hpp>
#include <stdplus/fd/ops.hpp>
+#include <stdplus/numeric/endian.hpp>
#include <gtest/gtest.h>
-namespace stdplus
+namespace stdplus::fd
{
-namespace fd
-{
+
+using testing::_;
+using testing::Ge;
+using testing::SizeIs;
+using std::literals::string_view_literals::operator""sv;
TEST(Flags, Flags)
{
@@ -14,5 +22,76 @@
EXPECT_EQ(0, static_cast<int>(f));
}
-} // namespace fd
-} // namespace stdplus
+TEST(ReadExact, 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"));
+ }
+ char buf[9];
+ readExact(fd, buf);
+ EXPECT_EQ(std::string_view(buf, sizeof(buf)), "alpha one");
+}
+
+TEST(ReadExact, NotEnoughEof)
+{
+ testing::StrictMock<FdMock> fd;
+ {
+ testing::InSequence seq;
+ EXPECT_CALL(fd, read(_)).WillOnce(readSv("alph"));
+ EXPECT_CALL(fd, read(_)).WillOnce(readSv("a "));
+ EXPECT_CALL(fd, read(_))
+ .WillRepeatedly(testing::Throw(exception::Eof("test")));
+ }
+ char buf[9];
+ EXPECT_THROW(readExact(fd, buf), exception::Incomplete);
+ EXPECT_THROW(readExact(fd, buf), exception::Eof);
+}
+
+TEST(ReadExact, NotEnoughBlock)
+{
+ testing::StrictMock<FdMock> fd;
+ {
+ testing::InSequence seq;
+ EXPECT_CALL(fd, read(_)).WillOnce(readSv("alph"));
+ EXPECT_CALL(fd, read(_)).WillOnce(readSv("a "));
+ EXPECT_CALL(fd, read(_)).WillRepeatedly(readSv(""));
+ }
+ char buf[9];
+ EXPECT_THROW(readExact(fd, buf), exception::Incomplete);
+ EXPECT_THROW(readExact(fd, buf), exception::WouldBlock);
+}
+
+TEST(ReadExact, NotEnoughBlockInt)
+{
+ testing::StrictMock<FdMock> fd;
+ {
+ testing::InSequence seq;
+ EXPECT_CALL(fd, read(_)).WillOnce(readSv("\0\0\0"sv));
+ EXPECT_CALL(fd, read(_)).WillRepeatedly(readSv(""));
+ }
+ int32_t i;
+ EXPECT_THROW(readExact(fd, i), exception::Incomplete);
+ EXPECT_THROW(readExact(fd, i), exception::WouldBlock);
+}
+
+TEST(ReadExact, EnoughInt)
+{
+ testing::StrictMock<FdMock> fd;
+ {
+ testing::InSequence seq;
+ EXPECT_CALL(fd, read(_)).WillOnce(readSv("\0\0\0\0"sv));
+ EXPECT_CALL(fd, read(_)).WillOnce(readSv("\x01\x02"sv));
+ EXPECT_CALL(fd, read(_)).WillOnce(readSv("\x03\x04"sv));
+ }
+ int32_t i;
+ readExact(fd, i);
+ EXPECT_EQ(i, 0);
+ readExact(fd, i);
+ EXPECT_EQ(ntoh(i), 0x01020304);
+}
+
+} // namespace stdplus::fd
diff --git a/test/meson.build b/test/meson.build
index a0c37a1..b43cf9b 100644
--- a/test/meson.build
+++ b/test/meson.build
@@ -47,7 +47,7 @@
'fd/line': [stdplus_fd_dep, stdplus_dep, gmock_dep, gtest_main_dep],
'fd/mmap': [stdplus_fd_dep, gtest_main_dep],
'fd/mock': [stdplus_fd_dep, gmock_dep, gtest_main_dep],
- 'fd/ops': [stdplus_fd_dep, gtest_main_dep],
+ 'fd/ops': [stdplus_fd_dep, stdplus_dep, gmock_dep, gtest_main_dep],
}
if has_gtest
gtests += {