Merge "Support starting fuse from a block map"
diff --git a/fuse_sideload/fuse_provider.cpp b/fuse_sideload/fuse_provider.cpp
index 5ee6e24..8fa1b5c 100644
--- a/fuse_sideload/fuse_provider.cpp
+++ b/fuse_sideload/fuse_provider.cpp
@@ -49,6 +49,11 @@
   fuse_block_size_ = block_size;
 }
 
+std::unique_ptr<FuseDataProvider> FuseFileDataProvider::CreateFromFile(const std::string& path,
+                                                                       uint32_t block_size) {
+  return std::make_unique<FuseFileDataProvider>(path, block_size);
+}
+
 bool FuseFileDataProvider::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_;
@@ -127,7 +132,7 @@
   return true;
 }
 
-std::unique_ptr<FuseBlockDataProvider> FuseBlockDataProvider::CreateFromBlockMap(
+std::unique_ptr<FuseDataProvider> FuseBlockDataProvider::CreateFromBlockMap(
     const std::string& block_map_path, uint32_t fuse_block_size) {
   auto block_map = BlockMapData::ParseBlockMapFile(block_map_path);
   if (!block_map) {
diff --git a/fuse_sideload/include/fuse_provider.h b/fuse_sideload/include/fuse_provider.h
index 8d4ea40..3cdaef3 100644
--- a/fuse_sideload/include/fuse_provider.h
+++ b/fuse_sideload/include/fuse_provider.h
@@ -44,6 +44,8 @@
   virtual bool ReadBlockAlignedData(uint8_t* buffer, uint32_t fetch_size,
                                     uint32_t start_block) const = 0;
 
+  virtual bool Valid() const = 0;
+
   virtual void Close() {}
 
  protected:
@@ -60,10 +62,13 @@
  public:
   FuseFileDataProvider(const std::string& path, uint32_t block_size);
 
+  static std::unique_ptr<FuseDataProvider> CreateFromFile(const std::string& path,
+                                                          uint32_t block_size);
+
   bool ReadBlockAlignedData(uint8_t* buffer, uint32_t fetch_size,
                             uint32_t start_block) const override;
 
-  bool Valid() const {
+  bool Valid() const override {
     return fd_ != -1;
   }
 
@@ -78,14 +83,20 @@
 class FuseBlockDataProvider : public FuseDataProvider {
  public:
   // Constructs the fuse provider from the block map.
-  static std::unique_ptr<FuseBlockDataProvider> CreateFromBlockMap(
-      const std::string& block_map_path, uint32_t fuse_block_size);
+  static std::unique_ptr<FuseDataProvider> CreateFromBlockMap(const std::string& block_map_path,
+                                                              uint32_t fuse_block_size);
 
   RangeSet ranges() const {
     return ranges_;
   }
+
   bool ReadBlockAlignedData(uint8_t* buffer, uint32_t fetch_size,
                             uint32_t start_block) const override;
+
+  bool Valid() const override {
+    return fd_ != -1;
+  }
+
   void Close() override;
 
  private:
diff --git a/install/Android.bp b/install/Android.bp
index 4696e50..89cc3f2 100644
--- a/install/Android.bp
+++ b/install/Android.bp
@@ -61,7 +61,7 @@
     srcs: [
         "adb_install.cpp",
         "asn1_decoder.cpp",
-        "fuse_sdcard_install.cpp",
+        "fuse_install.cpp",
         "install.cpp",
         "package.cpp",
         "verifier.cpp",
diff --git a/install/fuse_sdcard_install.cpp b/install/fuse_install.cpp
similarity index 80%
rename from install/fuse_sdcard_install.cpp
rename to install/fuse_install.cpp
index 9fdb2f3..ffde4a3 100644
--- a/install/fuse_sdcard_install.cpp
+++ b/install/fuse_install.cpp
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-#include "install/fuse_sdcard_install.h"
+#include "install/fuse_install.h"
 
 #include <dirent.h>
 #include <signal.h>
@@ -27,6 +27,7 @@
 #include <algorithm>
 #include <functional>
 #include <memory>
+#include <string>
 #include <vector>
 
 #include <android-base/logging.h>
@@ -74,7 +75,8 @@
       // Skip "." and ".." entries.
       if (name == "." || name == "..") continue;
       dirs.push_back(name + "/");
-    } else if (de->d_type == DT_REG && android::base::EndsWithIgnoreCase(name, ".zip")) {
+    } else if (de->d_type == DT_REG && (android::base::EndsWithIgnoreCase(name, ".zip") ||
+                                        android::base::EndsWithIgnoreCase(name, ".map"))) {
       entries.push_back(name);
     }
   }
@@ -119,42 +121,37 @@
   // Unreachable.
 }
 
-static bool StartSdcardFuse(const std::string& path) {
-  auto file_data_reader = std::make_unique<FuseFileDataProvider>(path, 65536);
+static bool StartInstallPackageFuse(std::string_view path) {
+  if (path.empty()) {
+    return false;
+  }
+
+  constexpr auto FUSE_BLOCK_SIZE = 65536;
+  bool is_block_map = android::base::ConsumePrefix(&path, "@");
+  auto file_data_reader =
+      is_block_map ? FuseBlockDataProvider::CreateFromBlockMap(std::string(path), FUSE_BLOCK_SIZE)
+                   : FuseFileDataProvider::CreateFromFile(std::string(path), FUSE_BLOCK_SIZE);
 
   if (!file_data_reader->Valid()) {
     return false;
   }
 
-  // The installation process expects to find the sdcard unmounted. Unmount it with MNT_DETACH so
-  // that our open file continues to work but new references see it as unmounted.
-  umount2("/sdcard", MNT_DETACH);
+  if (android::base::StartsWith(path, SDCARD_ROOT)) {
+    // The installation process expects to find the sdcard unmounted. Unmount it with MNT_DETACH so
+    // that our open file continues to work but new references see it as unmounted.
+    umount2(SDCARD_ROOT, MNT_DETACH);
+  }
 
   return run_fuse_sideload(std::move(file_data_reader)) == 0;
 }
 
-InstallResult ApplyFromSdcard(Device* device, RecoveryUI* ui) {
-  if (ensure_path_mounted(SDCARD_ROOT) != 0) {
-    LOG(ERROR) << "\n-- Couldn't mount " << SDCARD_ROOT << ".\n";
-    return INSTALL_ERROR;
-  }
-
-  std::string path = BrowseDirectory(SDCARD_ROOT, device, ui);
-  if (path.empty()) {
-    LOG(ERROR) << "\n-- No package file selected.\n";
-    ensure_path_unmounted(SDCARD_ROOT);
-    return INSTALL_ERROR;
-  }
-
-  ui->Print("\n-- Install %s ...\n", path.c_str());
-  SetSdcardUpdateBootloaderMessage();
-
+InstallResult InstallWithFuseFromPath(std::string_view path, RecoveryUI* ui) {
   // We used to use fuse in a thread as opposed to a process. Since accessing
   // through fuse involves going from kernel to userspace to kernel, it leads
   // to deadlock when a page fault occurs. (Bug: 26313124)
   pid_t child;
   if ((child = fork()) == 0) {
-    bool status = StartSdcardFuse(path);
+    bool status = StartInstallPackageFuse(path);
 
     _exit(status ? EXIT_SUCCESS : EXIT_FAILURE);
   }
@@ -203,6 +200,32 @@
     LOG(ERROR) << "Error exit from the fuse process: " << WEXITSTATUS(status);
   }
 
+  return result;
+}
+
+InstallResult ApplyFromSdcard(Device* device) {
+  auto ui = device->GetUI();
+  if (ensure_path_mounted(SDCARD_ROOT) != 0) {
+    LOG(ERROR) << "\n-- Couldn't mount " << SDCARD_ROOT << ".\n";
+    return INSTALL_ERROR;
+  }
+
+  std::string path = BrowseDirectory(SDCARD_ROOT, device, ui);
+  if (path.empty()) {
+    LOG(ERROR) << "\n-- No package file selected.\n";
+    ensure_path_unmounted(SDCARD_ROOT);
+    return INSTALL_ERROR;
+  }
+
+  // Hint the install function to read from a block map file.
+  if (android::base::EndsWithIgnoreCase(path, ".map")) {
+    path = "@" + path;
+  }
+
+  ui->Print("\n-- Install %s ...\n", path.c_str());
+  SetSdcardUpdateBootloaderMessage();
+
+  auto result = InstallWithFuseFromPath(path, ui);
   ensure_path_unmounted(SDCARD_ROOT);
   return result;
 }
diff --git a/install/include/install/fuse_sdcard_install.h b/install/include/install/fuse_install.h
similarity index 62%
rename from install/include/install/fuse_sdcard_install.h
rename to install/include/install/fuse_install.h
index e5bb01f..63b116a 100644
--- a/install/include/install/fuse_sdcard_install.h
+++ b/install/include/install/fuse_install.h
@@ -16,8 +16,15 @@
 
 #pragma once
 
+#include <string_view>
+
 #include "install/install.h"
 #include "recovery_ui/device.h"
 #include "recovery_ui/ui.h"
 
-InstallResult ApplyFromSdcard(Device* device, RecoveryUI* ui);
+// Starts FUSE with the package from |path| as the data source. And installs the package from
+// |FUSE_SIDELOAD_HOST_PATHNAME|. The |path| can point to the location of a package zip file or a
+// block map file with the prefix '@'; e.g. /sdcard/package.zip, @/cache/recovery/block.map.
+InstallResult InstallWithFuseFromPath(std::string_view path, RecoveryUI* ui);
+
+InstallResult ApplyFromSdcard(Device* device);
diff --git a/minadbd/fuse_adb_provider.h b/minadbd/fuse_adb_provider.h
index c5561e5..43c07d2 100644
--- a/minadbd/fuse_adb_provider.h
+++ b/minadbd/fuse_adb_provider.h
@@ -29,6 +29,10 @@
   bool ReadBlockAlignedData(uint8_t* buffer, uint32_t fetch_size,
                             uint32_t start_block) const override;
 
+  bool Valid() const override {
+    return fd_ != -1;
+  }
+
  private:
   // The underlying source to read data from (i.e. the one that talks to the host).
   int fd_;
diff --git a/recovery.cpp b/recovery.cpp
index db66ea7..b18a8e7 100644
--- a/recovery.cpp
+++ b/recovery.cpp
@@ -49,7 +49,7 @@
 #include "common.h"
 #include "fsck_unshare_blocks.h"
 #include "install/adb_install.h"
-#include "install/fuse_sdcard_install.h"
+#include "install/fuse_install.h"
 #include "install/install.h"
 #include "install/package.h"
 #include "install/wipe_data.h"
@@ -408,7 +408,7 @@
           status = ApplyFromAdb(device, false /* rescue_mode */, &reboot_action);
         } else {
           adb = false;
-          status = ApplyFromSdcard(device, ui);
+          status = ApplyFromSdcard(device);
         }
 
         ui->Print("\nInstall from %s completed with status %d.\n", adb ? "ADB" : "SD card", status);
diff --git a/tests/unit/fuse_provider_test.cpp b/tests/unit/fuse_provider_test.cpp
index c5995dd..37f99f9 100644
--- a/tests/unit/fuse_provider_test.cpp
+++ b/tests/unit/fuse_provider_test.cpp
@@ -44,7 +44,8 @@
   ASSERT_TRUE(block_map_data);
   ASSERT_EQ(10000, block_map_data->file_size());
   ASSERT_EQ(4096, block_map_data->fuse_block_size());
-  ASSERT_EQ(RangeSet({ { 10, 11 }, { 20, 21 }, { 22, 23 } }), block_map_data->ranges());
+  ASSERT_EQ(RangeSet({ { 10, 11 }, { 20, 21 }, { 22, 23 } }),
+            static_cast<FuseBlockDataProvider*>(block_map_data.get())->ranges());
 }
 
 TEST(FuseBlockMapTest, ReadBlockAlignedData_smoke) {
diff --git a/tests/unit/fuse_sideload_test.cpp b/tests/unit/fuse_sideload_test.cpp
index 6add99f..ea89503 100644
--- a/tests/unit/fuse_sideload_test.cpp
+++ b/tests/unit/fuse_sideload_test.cpp
@@ -40,6 +40,10 @@
   bool ReadBlockAlignedData(uint8_t*, uint32_t, uint32_t) const override {
     return true;
   }
+
+  bool Valid() const override {
+    return true;
+  }
 };
 
 TEST(SideloadTest, run_fuse_sideload_wrong_parameters) {