Support read-write mmapped flatbuffers

Change-Id: I005788775d6fca5660b911d59078a0d75c0bb545
Signed-off-by: Austin Schuh <austin.schuh@bluerivertech.com>
diff --git a/aos/flatbuffers.h b/aos/flatbuffers.h
index 74d74ba..cdf5339 100644
--- a/aos/flatbuffers.h
+++ b/aos/flatbuffers.h
@@ -507,8 +507,9 @@
 class FlatbufferMMap : public NonSizePrefixedFlatbuffer<T> {
  public:
   // Builds a Flatbuffer by mmaping the data from a flatbuffer saved on disk.
-  FlatbufferMMap(const std::string &flatbuffer_path) {
-    span_ = util::MMapFile(flatbuffer_path);
+  FlatbufferMMap(const std::string &flatbuffer_path,
+                 util::FileOptions options = util::FileOptions::kReadable) {
+    span_ = util::MMapFile(flatbuffer_path, options);
   }
 
   // Copies the reference to the mapped memory.
@@ -519,10 +520,7 @@
   FlatbufferMMap(FlatbufferMMap &&) = default;
   FlatbufferMMap &operator=(FlatbufferMMap<T> &&other) = default;
 
-  absl::Span<uint8_t> span() override {
-    LOG(FATAL) << "Unimplemented. A flatbuffer is immutable.";
-    return *span_;
-  }
+  absl::Span<uint8_t> span() override { return *span_; }
   absl::Span<const uint8_t> span() const override { return *span_; }
 
  private:
diff --git a/aos/flatbuffers_test.cc b/aos/flatbuffers_test.cc
index f37af53..ae8d6e6 100644
--- a/aos/flatbuffers_test.cc
+++ b/aos/flatbuffers_test.cc
@@ -55,5 +55,26 @@
     ASSERT_EQ(fb_mmap3.message().foo_int(), 3);
   }
 }
+
+// Tests the ability to modify a flatbuffer mmaped from on disk in memory
+TEST(FlatbufferMMapTest, Writeable) {
+  FlatbufferDetachedBuffer<Configuration> fb =
+      JsonToFlatbuffer<Configuration>("{\"foo_int\": 3}");
+
+  const std::string fb_path = absl::StrCat(TestTmpDir(), "/fb.bfbs");
+  WriteFlatbufferToFile(fb_path, fb);
+
+  {
+    FlatbufferMMap<Configuration> fb_mmap(fb_path,
+                                          util::FileOptions::kWriteable);
+    fb_mmap.mutable_message()->mutate_foo_int(5);
+  }
+
+  {
+    FlatbufferMMap<Configuration> fb_mmap(fb_path);
+    EXPECT_EQ(fb_mmap.message().foo_int(), 5);
+  }
+}
+
 }  // namespace testing
 }  // namespace aos
diff --git a/aos/util/file.cc b/aos/util/file.cc
index cec5a6e..317206e 100644
--- a/aos/util/file.cc
+++ b/aos/util/file.cc
@@ -152,18 +152,24 @@
   }
 }
 
-std::shared_ptr<absl::Span<uint8_t>> MMapFile(const std::string &path) {
-  int fd = open(path.c_str(), O_RDONLY);
+std::shared_ptr<absl::Span<uint8_t>> MMapFile(const std::string &path,
+                                              FileOptions options) {
+  int fd =
+      open(path.c_str(), options == FileOptions::kReadable ? O_RDONLY : O_RDWR);
   PCHECK(fd != -1) << "Unable to open file " << path;
   struct stat sb;
   PCHECK(fstat(fd, &sb) != -1) << ": Unable to get file size of " << path;
-  uint8_t *start = reinterpret_cast<uint8_t *>(
-      mmap(NULL, sb.st_size, PROT_READ, MAP_PRIVATE, fd, 0));
+  uint8_t *start = reinterpret_cast<uint8_t *>(mmap(
+      NULL, sb.st_size,
+      options == FileOptions::kReadable ? PROT_READ : (PROT_READ | PROT_WRITE),
+      MAP_SHARED, fd, 0));
   CHECK(start != MAP_FAILED) << ": Unable to open mapping to file " << path;
   std::shared_ptr<absl::Span<uint8_t>> span =
       std::shared_ptr<absl::Span<uint8_t>>(
           new absl::Span<uint8_t>(start, sb.st_size),
           [](absl::Span<uint8_t> *span) {
+            PCHECK(msync(span->data(), span->size(), MS_SYNC) == 0)
+                << ": Failed to flush data before unmapping.";
             PCHECK(munmap(span->data(), span->size()) != -1);
             delete span;
           });
diff --git a/aos/util/file.h b/aos/util/file.h
index 7c06ece..2d37ec2 100644
--- a/aos/util/file.h
+++ b/aos/util/file.h
@@ -35,8 +35,11 @@
 // runs across.
 void UnlinkRecursive(std::string_view path);
 
+enum class FileOptions { kReadable, kWriteable };
+
 // Maps file from disk into memory
-std::shared_ptr<absl::Span<uint8_t>> MMapFile(const std::string &path);
+std::shared_ptr<absl::Span<uint8_t>> MMapFile(
+    const std::string &path, FileOptions options = FileOptions::kReadable);
 
 }  // namespace util
 }  // namespace aos