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;
     }
 }