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