fd/ops: Fix aligned ops to return incomplete

This improves error handling in certain transfer situations.

Change-Id: I13541c1e852c2992c2074c328c3ed9ed2084949f
Signed-off-by: William A. Kennington III <wak@google.com>
diff --git a/src/fd/ops.cpp b/src/fd/ops.cpp
index d5991c6..d18e855 100644
--- a/src/fd/ops.cpp
+++ b/src/fd/ops.cpp
@@ -66,19 +66,31 @@
                                  size_t align, std::span<Byte> data,
                                  Args&&... args)
 {
-    std::span<Byte> ret;
-    do
+    std::size_t total = 0;
+    try
     {
-        auto r = (fd.*fun)(data.subspan(ret.size()),
-                           std::forward<Args>(args)...);
-        if (ret.size() != 0 && r.size() == 0)
+        do
         {
-            throw exception::WouldBlock(
-                std::format("{} is {}B/{}B", name, ret.size() % align, align));
+            auto r = (fd.*fun)(data.subspan(total),
+                               std::forward<Args>(args)...);
+            if (total != 0 && r.size() == 0)
+            {
+                throw exception::Incomplete(
+                    std::format("{} is {}B/{}B", name, total % align, align));
+            }
+            total += r.size();
+        } while (total % align != 0);
+    }
+    catch (const std::system_error&)
+    {
+        if (total % align)
+        {
+            throw exception::Incomplete(
+                std::format("{} is {}B/{}B", name, total % align, align));
         }
-        ret = data.subspan(0, ret.size() + r.size());
-    } while (ret.size() % align != 0);
-    return ret;
+        throw;
+    }
+    return std::span<Byte>(data.data(), total);
 }
 
 std::span<std::byte> readAligned(Fd& fd, size_t align, std::span<std::byte> buf)
diff --git a/test/fd/ops.cpp b/test/fd/ops.cpp
index ef1d489..0a7638f 100644
--- a/test/fd/ops.cpp
+++ b/test/fd/ops.cpp
@@ -94,4 +94,64 @@
     EXPECT_EQ(ntoh(i), 0x01020304);
 }
 
+TEST(Read, Success)
+{
+    testing::StrictMock<FdMock> fd;
+    {
+        testing::InSequence seq;
+        EXPECT_CALL(fd, read(_)).WillOnce(readSv("alph"));
+        EXPECT_CALL(fd, read(_)).WillOnce(readSv("one"));
+        EXPECT_CALL(fd, read(_)).WillOnce(readSv(""));
+    }
+    char buf[15];
+    auto res = read(fd, buf);
+    EXPECT_EQ(std::string_view(res.begin(), res.end()), "alph");
+    res = read(fd, buf);
+    EXPECT_EQ(std::string_view(res.begin(), res.end()), "one");
+    EXPECT_TRUE(read(fd, buf).empty());
+}
+
+TEST(Read, NotEnoughInt)
+{
+    testing::StrictMock<FdMock> fd;
+    {
+        testing::InSequence seq;
+        EXPECT_CALL(fd, read(_)).WillOnce(readSv("\0\0\0"sv));
+        EXPECT_CALL(fd, read(_)).WillRepeatedly(readSv(""));
+    }
+    std::array<int32_t, 1> i;
+    EXPECT_THROW(read(fd, i), exception::Incomplete);
+    EXPECT_TRUE(read(fd, i).empty());
+}
+
+TEST(Read, NotEnoughEofInt)
+{
+    testing::StrictMock<FdMock> fd;
+    {
+        testing::InSequence seq;
+        EXPECT_CALL(fd, read(_)).WillOnce(readSv("\0\0\0"sv));
+        EXPECT_CALL(fd, read(_))
+            .WillRepeatedly(testing::Throw(exception::Eof("test")));
+    }
+    std::array<int32_t, 1> i;
+    EXPECT_THROW(read(fd, i), exception::Incomplete);
+    EXPECT_THROW(read(fd, i), exception::Eof);
+}
+
+TEST(Read, 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));
+    }
+    std::array<int32_t, 1> i;
+    read(fd, i);
+    EXPECT_EQ(i[0], 0);
+    read(fd, i);
+    EXPECT_EQ(stdplus::ntoh(i[0]), 0x01020304);
+}
+
 } // namespace stdplus::fd