vpnor: Avoid mmap() due to lack of support on some filesystems

JFFS2 does not support writable mappings. Switch to read()/write() and
add all the failure handling to ensure we get the required semantics.

Change-Id: I5ec547bf7a01a389cf8f1403f748026fe6bb21fa
Signed-off-by: Andrew Jeffery <andrew@aj.id.au>
diff --git a/vpnor/partition.cpp b/vpnor/partition.cpp
index 66c51d1..4547d64 100644
--- a/vpnor/partition.cpp
+++ b/vpnor/partition.cpp
@@ -103,34 +103,37 @@
     return std::min(maxAccess, partSize) - offset;
 }
 
-void Request::resize(const fs::path& path, size_t len)
+/* Perform reads and writes for an entire buffer's worth of data
+ *
+ * Post-condition: All bytes read or written, or an error has occurred
+ *
+ * Yields 0 if the entire buffer was processed, otherwise -1
+ */
+#define fd_process_all(fn, fd, src, len)                                       \
+    ({                                                                         \
+        size_t __len = len;                                                    \
+        ssize_t __accessed = 0;                                                \
+        do                                                                     \
+        {                                                                      \
+            __len -= __accessed;                                               \
+            __accessed = TEMP_FAILURE_RETRY(fn(fd, src, __len));               \
+        } while (__len > 0 && __accessed > 0);                                 \
+        __len ? -1 : 0;                                                        \
+    })
+
+ssize_t Request::read(void* dst, size_t len)
 {
-    size_t maxAccess = offset + len;
+    len = clamp(len);
+
+    fs::path path = getPartitionFilePath(O_RDONLY);
+
+    MSG_INFO("Fulfilling read request against %s at offset 0x%zx into %p "
+             "for %zu\n",
+             path.c_str(), offset, dst, len);
+
     size_t fileSize = fs::file_size(path);
-    if (maxAccess < fileSize)
-    {
-        return;
-    }
-    MSG_DBG("Resizing %s to %zu bytes\n", path.c_str(), maxAccess);
-    int rc = truncate(path.c_str(), maxAccess);
-    if (rc == -1)
-    {
-        MSG_ERR("Failed to resize %s: %d\n", path.c_str(), errno);
-        throw std::system_error(errno, std::system_category());
-    }
-}
 
-size_t Request::fulfil(const fs::path& path, int flags, void* buf, size_t len)
-{
-    if (!(flags == O_RDONLY || flags == O_RDWR))
-    {
-        std::stringstream err;
-        err << "Require O_RDONLY (0x" << std::hex << O_RDONLY << " or O_RDWR "
-            << std::hex << O_RDWR << " for flags, got: 0x" << std::hex << flags;
-        throw std::invalid_argument(err.str());
-    }
-
-    int fd = ::open(path.c_str(), flags);
+    int fd = ::open(path.c_str(), O_RDONLY);
     if (fd == -1)
     {
         MSG_ERR("Failed to open backing file at '%s': %d\n", path.c_str(),
@@ -138,42 +141,75 @@
         throw std::system_error(errno, std::system_category());
     }
 
-    if (flags == O_RDONLY)
+    int rc = lseek(fd, offset, SEEK_SET);
+    if (rc < 0)
     {
-        MSG_INFO("Fulfilling read request against %s at offset 0x%zx into %p "
-                 "for %zu\n",
-                 path.c_str(), offset, buf, len);
-    }
-    else
-    {
-        MSG_INFO("Fulfilling write request against %s at offset 0x%zx from %p "
-                 "for %zu\n",
-                 path.c_str(), offset, buf, len);
-    }
-
-    size_t fileSize = fs::file_size(path);
-    int mprot = PROT_READ | ((flags == O_RDWR) ? PROT_WRITE : 0);
-    auto map = mmap(NULL, fileSize, mprot, MAP_SHARED, fd, 0);
-    if (map == MAP_FAILED)
-    {
-        close(fd);
-        MSG_ERR("Failed to map backing file '%s' for %zd bytes: %d\n",
+        MSG_ERR("Failed to seek to %zu in %s (%zu bytes): %d\n", offset,
                 path.c_str(), fileSize, errno);
         throw std::system_error(errno, std::system_category());
     }
 
-    // copy to the reserved memory area
-    if (flags == O_RDONLY)
+    auto access_len = std::min(len, fileSize - offset);
+    rc = fd_process_all(::read, fd, dst, access_len);
+    if (rc < 0)
     {
-        memset(buf, 0xff, len);
-        memcpy(buf, (char*)map + offset, std::min(len, fileSize));
+        MSG_ERR("Requested %zu bytes but failed to read %zu from %s (%zu) at "
+                "%zu: %d\n",
+                len, access_len, path.c_str(), fileSize, offset, errno);
+        throw std::system_error(errno, std::system_category());
     }
-    else
+
+    /* Set any remaining buffer space to the erased state */
+    memset((char*)dst + access_len, 0xff, len - access_len);
+
+    close(fd);
+
+    return len;
+}
+
+ssize_t Request::write(void* dst, size_t len)
+{
+    if (len != clamp(len))
     {
-        memcpy((char*)map + offset, buf, len);
-        backend_set_bytemap(backend, base + offset, len, FLASH_DIRTY);
+        std::stringstream err;
+        err << "Request size 0x" << std::hex << len << " from offset 0x"
+            << std::hex << offset << " exceeds the partition size 0x"
+            << std::hex << (partition.data.size << backend->block_size_shift);
+        throw OutOfBoundsOffset(err.str());
     }
-    munmap(map, fileSize);
+
+    /* Ensure file is at least the size of the maximum access */
+    fs::path path = getPartitionFilePath(O_RDWR);
+
+    MSG_INFO("Fulfilling write request against %s at offset 0x%zx from %p "
+             "for %zu\n",
+             path.c_str(), offset, dst, len);
+
+    int fd = ::open(path.c_str(), O_RDWR);
+    if (fd == -1)
+    {
+        MSG_ERR("Failed to open backing file at '%s': %d\n", path.c_str(),
+                errno);
+        throw std::system_error(errno, std::system_category());
+    }
+
+    int rc = lseek(fd, offset, SEEK_SET);
+    if (rc < 0)
+    {
+        MSG_ERR("Failed to seek to %zu in %s: %d\n", offset, path.c_str(),
+                errno);
+        throw std::system_error(errno, std::system_category());
+    }
+
+    rc = fd_process_all(::write, fd, dst, len);
+    if (rc < 0)
+    {
+        MSG_ERR("Failed to write %zu bytes to %s at %zu: %d\n", len,
+                path.c_str(), offset, errno);
+        throw std::system_error(errno, std::system_category());
+    }
+    backend_set_bytemap(backend, base + offset, len, FLASH_DIRTY);
+
     close(fd);
 
     return len;
diff --git a/vpnor/partition.hpp b/vpnor/partition.hpp
index d6346b1..6b6d46b 100644
--- a/vpnor/partition.hpp
+++ b/vpnor/partition.hpp
@@ -49,47 +49,13 @@
     Request& operator=(Request&&) = default;
     ~Request() = default;
 
-    ssize_t read(void* dst, size_t len)
-    {
-        len = clamp(len);
-        constexpr auto flags = O_RDONLY;
-        fs::path path = getPartitionFilePath(flags);
-        return fulfil(path, flags, dst, len);
-    }
-
-    ssize_t write(void* dst, size_t len)
-    {
-        if (len != clamp(len))
-        {
-            std::stringstream err;
-            err << "Request size 0x" << std::hex << len << " from offset 0x"
-                << std::hex << offset << " exceeds the partition size 0x"
-                << std::hex
-                << (partition.data.size << backend->block_size_shift);
-            throw OutOfBoundsOffset(err.str());
-        }
-        constexpr auto flags = O_RDWR;
-        /* Ensure file is at least the size of the maximum access */
-        fs::path path = getPartitionFilePath(flags);
-        resize(path, len);
-        return fulfil(path, flags, dst, len);
-    }
+    ssize_t read(void* dst, size_t len);
+    ssize_t write(void* dst, size_t len);
 
   private:
     /** @brief Clamp the access length to the maximum supported by the ToC */
     size_t clamp(size_t len);
 
-    /** @brief Ensure the backing file is sized appropriately for the access
-     *
-     *  We need to ensure the file is big enough to satisfy the request so that
-     *  mmap() will succeed for the required size.
-     *
-     *  @return The valid access length
-     *
-     *  Throws: std::system_error
-     */
-    void resize(const std::experimental::filesystem::path& path, size_t len);
-
     /** @brief Returns the partition file path associated with the offset.
      *
      *  The search strategy for the partition file depends on the value of the
@@ -124,15 +90,6 @@
      */
     std::experimental::filesystem::path getPartitionFilePath(int flags);
 
-    /** @brief Fill dst with the content of the partition relative to offset.
-     *
-     *  @param[in] offset - The pnor offset(bytes).
-     *  @param[out] dst - The buffer to fill with partition data
-     *  @param[in] len - The length of the destination buffer
-     */
-    size_t fulfil(const std::experimental::filesystem::path& path, int flags,
-                  void* dst, size_t len);
-
     struct backend* backend;
     const pnor_partition& partition;
     size_t base;