Implement FuseBlockDataProvider

Adds a fuse data provider that parses the metadata from a block map,
reads the data from the given ranges of the block device; and provides
the data to the fuse.

Bug: 127071893
Test: unit tests pass, install a package from block map
Change-Id: Ie9925ee9144e98642505b3f5e1a4a186d2b21ed0
diff --git a/fuse_sideload/fuse_provider.cpp b/fuse_sideload/fuse_provider.cpp
index 58786f5..5ee6e24 100644
--- a/fuse_sideload/fuse_provider.cpp
+++ b/fuse_sideload/fuse_provider.cpp
@@ -27,8 +27,11 @@
 #include <functional>
 
 #include <android-base/file.h>
+#include <android-base/logging.h>
+#include <android-base/strings.h>
 
 #include "fuse_sideload.h"
+#include "otautil/sysutil.h"
 
 FuseFileDataProvider::FuseFileDataProvider(const std::string& path, uint32_t block_size) {
   struct stat sb;
@@ -69,3 +72,79 @@
 void FuseFileDataProvider::Close() {
   fd_.reset();
 }
+
+FuseBlockDataProvider::FuseBlockDataProvider(uint64_t file_size, uint32_t fuse_block_size,
+                                             android::base::unique_fd&& fd,
+                                             uint32_t source_block_size, RangeSet ranges)
+    : FuseDataProvider(file_size, fuse_block_size),
+      fd_(std::move(fd)),
+      source_block_size_(source_block_size),
+      ranges_(std::move(ranges)) {
+  // Make sure the offset is also aligned with the blocks on the block device when we call
+  // ReadBlockAlignedData().
+  CHECK_EQ(0, fuse_block_size_ % source_block_size_);
+}
+
+bool FuseBlockDataProvider::ReadBlockAlignedData(uint8_t* buffer, uint32_t fetch_size,
+                                                 uint32_t start_block) const {
+  uint64_t offset = static_cast<uint64_t>(start_block) * fuse_block_size_;
+  if (fetch_size > file_size_ || offset > file_size_ - fetch_size) {
+    LOG(ERROR) << "Out of bound read, offset: " << offset << ", fetch size: " << fetch_size
+               << ", file size " << file_size_;
+    return false;
+  }
+
+  auto read_ranges =
+      ranges_.GetSubRanges(offset / source_block_size_, fetch_size / source_block_size_);
+  if (!read_ranges) {
+    return false;
+  }
+
+  uint8_t* next_out = buffer;
+  for (const auto& [range_start, range_end] : read_ranges.value()) {
+    uint64_t bytes_start = static_cast<uint64_t>(range_start) * source_block_size_;
+    uint64_t bytes_to_read = static_cast<uint64_t>(range_end - range_start) * source_block_size_;
+    if (!android::base::ReadFullyAtOffset(fd_, next_out, bytes_to_read, bytes_start)) {
+      PLOG(ERROR) << "Failed to read " << bytes_to_read << " bytes at offset " << bytes_start;
+      return false;
+    }
+
+    next_out += bytes_to_read;
+  }
+
+  if (uint64_t tailing_bytes = fetch_size % source_block_size_; tailing_bytes != 0) {
+    // Calculate the offset to last partial block.
+    uint64_t tailing_offset =
+        read_ranges.value()
+            ? static_cast<uint64_t>((read_ranges->cend() - 1)->second) * source_block_size_
+            : static_cast<uint64_t>(start_block) * source_block_size_;
+    if (!android::base::ReadFullyAtOffset(fd_, next_out, tailing_bytes, tailing_offset)) {
+      PLOG(ERROR) << "Failed to read tailing " << tailing_bytes << " bytes at offset "
+                  << tailing_offset;
+      return false;
+    }
+  }
+  return true;
+}
+
+std::unique_ptr<FuseBlockDataProvider> FuseBlockDataProvider::CreateFromBlockMap(
+    const std::string& block_map_path, uint32_t fuse_block_size) {
+  auto block_map = BlockMapData::ParseBlockMapFile(block_map_path);
+  if (!block_map) {
+    return nullptr;
+  }
+
+  android::base::unique_fd fd(TEMP_FAILURE_RETRY(open(block_map.path().c_str(), O_RDONLY)));
+  if (fd == -1) {
+    PLOG(ERROR) << "Failed to open " << block_map.path();
+    return nullptr;
+  }
+
+  return std::unique_ptr<FuseBlockDataProvider>(
+      new FuseBlockDataProvider(block_map.file_size(), fuse_block_size, std::move(fd),
+                                block_map.block_size(), block_map.block_ranges()));
+}
+
+void FuseBlockDataProvider::Close() {
+  fd_.reset();
+}