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/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