Add malloc-free file contents reader

If you have a file that can be read safely in realtime code, this
provides a convenient wrapper for grabbing the entire contents of
said file. This should also just be a bit faster than
ReadFileToStringOrDie just by virtue of avoiding all the malloc's in
std::string and not having to re-open the file.

Change-Id: I90a3fa9433ac3a8773027327bde245ebf0c13b10
Signed-off-by: James Kuszmaul <james.kuszmaul@bluerivertech.com>
diff --git a/aos/util/BUILD b/aos/util/BUILD
index 5a87251..19a9172 100644
--- a/aos/util/BUILD
+++ b/aos/util/BUILD
@@ -333,6 +333,7 @@
     target_compatible_with = ["@platforms//os:linux"],
     deps = [
         ":file",
+        "//aos:realtime",
         "//aos/testing:googletest",
     ],
 )
diff --git a/aos/util/file.h b/aos/util/file.h
index 2d37ec2..d2fb9fa 100644
--- a/aos/util/file.h
+++ b/aos/util/file.h
@@ -1,13 +1,17 @@
 #ifndef AOS_UTIL_FILE_H_
 #define AOS_UTIL_FILE_H_
 
+#include <fcntl.h>
 #include <sys/stat.h>
+#include <sys/types.h>
 
 #include <memory>
 #include <string>
 #include <string_view>
 
+#include "absl/strings/numbers.h"
 #include "absl/types/span.h"
+#include "aos/scoped/scoped_fd.h"
 #include "glog/logging.h"
 
 namespace aos {
@@ -41,6 +45,41 @@
 std::shared_ptr<absl::Span<uint8_t>> MMapFile(
     const std::string &path, FileOptions options = FileOptions::kReadable);
 
+// Wrapper to handle reading the contents of a file into a buffer. Meant for
+// situations where the malloc'ing of ReadFileToStringOrDie is inappropriate,
+// but where you still want to read a file.
+template <int kBufferSize = 1024>
+class FileReader {
+ public:
+  FileReader(std::string_view filename)
+      : file_(open(::std::string(filename).c_str(), O_RDONLY)) {
+    PCHECK(file_.get() != -1) << ": opening " << filename;
+    memset(buffer_, 0, kBufferSize);
+  }
+  // Reads the entire contents of the file into the internal buffer and returns
+  // a string_view of it.
+  // Note: The result may not be null-terminated.
+  std::string_view ReadContents() {
+    PCHECK(0 == lseek(file_.get(), 0, SEEK_SET));
+    const ssize_t result = read(file_.get(), buffer_, sizeof(buffer_));
+    PCHECK(result >= 0);
+    return {buffer_, static_cast<size_t>(result)};
+  }
+  // Calls ReadContents() and attempts to convert the result into an integer, or
+  // dies trying.
+  int ReadInt() {
+    int result;
+    std::string_view contents = ReadContents();
+    CHECK(absl::SimpleAtoi(contents, &result))
+        << "Failed to parse \"" << contents << "\" as int.";
+    return result;
+  }
+
+ private:
+  aos::ScopedFD file_;
+  char buffer_[kBufferSize];
+};
+
 }  // namespace util
 }  // namespace aos
 
diff --git a/aos/util/file_test.cc b/aos/util/file_test.cc
index 7c93fb3..8e76154 100644
--- a/aos/util/file_test.cc
+++ b/aos/util/file_test.cc
@@ -4,6 +4,9 @@
 #include <string>
 
 #include "gtest/gtest.h"
+#include "aos/realtime.h"
+
+DECLARE_bool(die_on_malloc);
 
 namespace aos {
 namespace util {
@@ -38,6 +41,21 @@
   EXPECT_TRUE(PathExists(test_file));
 }
 
+// Basic test of reading a normal file.
+TEST(FileTest, ReadNormalFileNoMalloc) {
+  const ::std::string tmpdir(getenv("TEST_TMPDIR"));
+  const ::std::string test_file = tmpdir + "/test_file";
+  ASSERT_EQ(0, system(("echo 971 > " + test_file).c_str()));
+
+  FileReader reader(test_file);
+
+  FLAGS_die_on_malloc = true;
+  RegisterMallocHook();
+  aos::ScopedRealtime realtime;
+  EXPECT_EQ("971\n", reader.ReadContents());
+  EXPECT_EQ(971, reader.ReadInt());
+}
+
 }  // namespace testing
 }  // namespace util
 }  // namespace aos