Add block apexes for OnBootstrap and OnStart am: 12bc67aaf3

Original change: https://android-review.googlesource.com/c/platform/system/apex/+/1891858

Change-Id: If5ee29bb2f814a7e4133da7d9fbfd11382ee46bc
diff --git a/apexd/apex_constants.h b/apexd/apex_constants.h
index 06fce23..5f14157 100644
--- a/apexd/apex_constants.h
+++ b/apexd/apex_constants.h
@@ -73,6 +73,7 @@
 
 static constexpr const char* kVmPayloadMetadataPartitionProp =
     "apexd.payload_metadata.path";
+static constexpr const std::chrono::seconds kBlockApexWaitTime(10);
 
 // Banned APEX names
 static const std::unordered_set<std::string> kBannedApexName = {
diff --git a/apexd/apex_file_repository.cpp b/apexd/apex_file_repository.cpp
index 0ca8c6c..bf818d0 100644
--- a/apexd/apex_file_repository.cpp
+++ b/apexd/apex_file_repository.cpp
@@ -176,11 +176,18 @@
   return {};
 }
 
-Result<void> ApexFileRepository::AddBlockApex(
+Result<int> ApexFileRepository::AddBlockApex(
     const std::string& metadata_partition) {
   CHECK(!block_disk_path_.has_value())
       << "AddBlockApex() can't be called twice.";
 
+  auto metadata_ready = WaitForFile(metadata_partition, kBlockApexWaitTime);
+  if (!metadata_ready.ok()) {
+    LOG(ERROR) << "Error waiting for metadata_partition : "
+               << metadata_ready.error();
+    return {};
+  }
+
   // TODO(b/185069443) consider moving the logic to find disk_path from
   // metadata_partition to its own library
   LOG(INFO) << "Scanning " << metadata_partition << " for host apexes";
@@ -223,6 +230,8 @@
     return {};
   }
 
+  int ret = 0;
+
   // subsequent partitions are APEX archives.
   static constexpr const int kFirstApexPartition = 2;
   for (int i = 0; i < metadata->apexes_size(); i++) {
@@ -230,6 +239,12 @@
 
     const std::string apex_path =
         *block_disk_path_ + std::to_string(i + kFirstApexPartition);
+
+    auto apex_ready = WaitForFile(apex_path, kBlockApexWaitTime);
+    if (!apex_ready.ok()) {
+      return Error() << "Error waiting for apex file : " << apex_ready.error();
+    }
+
     auto apex_file = ApexFile::Open(apex_path);
     if (!apex_file.ok()) {
       return Error() << "Failed to open " << apex_path << " : "
@@ -260,10 +275,10 @@
       return Error() << "duplicate of " << name << " found in "
                      << it->second.GetPath();
     }
-
+    ret++;
     pre_installed_store_.emplace(name, std::move(*apex_file));
   }
-  return {};
+  return {ret};
 }
 
 // TODO(b/179497746): AddDataApex should not concern with filtering out invalid
diff --git a/apexd/apex_file_repository.h b/apexd/apex_file_repository.h
index aedda54..e562ad0 100644
--- a/apexd/apex_file_repository.h
+++ b/apexd/apex_file_repository.h
@@ -72,8 +72,9 @@
   // |metadata_partition|. Host can provide its apexes to a VM instance via the
   // virtual disk image which has partitions: (see
   // /packages/modules/Virtualization/microdroid for the details)
-  //  - metadata partition(/dev/block/vd*1) should be accessed via
-  //  /dev/block/by-name/payload-metadata.
+  //  - metadata partition(/dev/block/vd*1) should be accessed by
+  //  setting the system property apexd.payload_metadata.prop. On microdroid,
+  //  this is /dev/block/by-name/payload-metadata.
   //  - each subsequence partition(/dev/block/vd*{2,3,..}) represents an APEX
   //  archive.
   // It will fail if there is more than one apex with the same name in
@@ -81,7 +82,8 @@
   // is expected to be performed in a single thread during initialization of
   // apexd. After initialization is finished, all queries to the instance are
   // thread safe.
-  android::base::Result<void> AddBlockApex(
+  // This will return the number of block apexes that were added.
+  android::base::Result<int> AddBlockApex(
       const std::string& metadata_partition);
 
   // Populate instance by collecting data apex files from the given |data_dir|.
diff --git a/apexd/apexd.cpp b/apexd/apexd.cpp
index c6c0eb5..5572f9e 100644
--- a/apexd/apexd.cpp
+++ b/apexd/apexd.cpp
@@ -2389,6 +2389,18 @@
     return 1;
   }
 
+  // TODO(b/209491448) Remove this.
+  auto block_count = AddBlockApex(instance);
+  if (!block_count.ok()) {
+    LOG(ERROR) << status.error();
+    return 1;
+  }
+  pre_allocate = loop::PreAllocateLoopDevices(*block_count);
+  if (!pre_allocate.ok()) {
+    LOG(ERROR) << "Failed to pre-allocate loop devices for block apexes : "
+               << pre_allocate.error();
+  }
+
   DeviceMapper& dm = DeviceMapper::Instance();
   // Create empty dm device for each found APEX.
   // This is a boot time optimization that makes use of the fact that user space
@@ -2474,6 +2486,13 @@
                << status.error();
     return;
   }
+
+  // TODO(b/209491448) Remove this.
+  if (auto block_status = AddBlockApex(instance); !block_status.ok()) {
+    LOG(ERROR) << status.error();
+    return;
+  }
+
   gMountedApexes.PopulateFromMounts(gConfig->active_apex_data_dir,
                                     gConfig->decompression_dir,
                                     gConfig->apex_hash_tree_dir);
@@ -3338,18 +3357,20 @@
 }
 
 // Adds block apexes if system property is set.
-inline Result<void> AddBlockApex(ApexFileRepository& instance) {
+Result<int> AddBlockApex(ApexFileRepository& instance) {
   auto prop = GetProperty(gConfig->vm_payload_metadata_partition_prop, "");
   if (prop != "") {
-    if (auto add_block = instance.AddBlockApex(prop); !add_block.ok()) {
+    auto block_count = instance.AddBlockApex(prop);
+    if (!block_count.ok()) {
       return Error() << "Failed to scan block APEX files: "
-                     << add_block.error();
+                     << block_count.error();
     }
+    return block_count;
   } else {
     LOG(INFO) << "No block apex metadata partition found, not adding block "
               << "apexes";
   }
-  return {};
+  return 0;
 }
 
 // When running in the VM mode, we follow the minimal start-up operations.
diff --git a/apexd/apexd.h b/apexd/apexd.h
index 368ed17..d08c9ac 100644
--- a/apexd/apexd.h
+++ b/apexd/apexd.h
@@ -222,6 +222,9 @@
 // TODO(ioffe): add more documentation.
 android::base::Result<ApexFile> InstallPackage(const std::string& package_path);
 
+// Exposed for testing.
+android::base::Result<int> AddBlockApex(ApexFileRepository& instance);
+
 }  // namespace apex
 }  // namespace android
 
diff --git a/apexd/apexd_test.cpp b/apexd/apexd_test.cpp
index 347ee22..6002dda 100644
--- a/apexd/apexd_test.cpp
+++ b/apexd/apexd_test.cpp
@@ -4029,6 +4029,201 @@
   ASSERT_EQ(1, OnStartInVmMode());
 }
 
+// Test that OnStart works with only block devices
+TEST_F(ApexdMountTest, OnStartOnlyBlockDevices) {
+  MockCheckpointInterface checkpoint_interface;
+  // Need to call InitializeVold before calling OnStart
+  InitializeVold(&checkpoint_interface);
+
+  // Set system property to enable block apexes
+  SetBlockApexEnabled(true);
+
+  auto path1 = AddBlockApex("apex.apexd_test.apex");
+
+  ASSERT_THAT(android::apex::AddBlockApex(ApexFileRepository::GetInstance()),
+              Ok());
+
+  OnStart();
+  UnmountOnTearDown(path1);
+
+  ASSERT_EQ(GetProperty(kTestApexdStatusSysprop, ""), "starting");
+  auto apex_mounts = GetApexMounts();
+
+  ASSERT_THAT(apex_mounts,
+              UnorderedElementsAre("/apex/com.android.apex.test_package",
+                                   "/apex/com.android.apex.test_package@1"));
+}
+
+// Test that we can have a mix of both block and system apexes
+TEST_F(ApexdMountTest, OnStartBlockAndSystemInstalled) {
+  MockCheckpointInterface checkpoint_interface;
+  // Need to call InitializeVold before calling OnStart
+  InitializeVold(&checkpoint_interface);
+
+  // Set system property to enable block apexes
+  SetBlockApexEnabled(true);
+
+  auto path1 = AddPreInstalledApex("apex.apexd_test.apex");
+  auto path2 = AddBlockApex("apex.apexd_test_different_app.apex");
+
+  auto& instance = ApexFileRepository::GetInstance();
+
+  ASSERT_THAT(instance.AddPreInstalledApex({GetBuiltInDir()}), Ok());
+  ASSERT_THAT(android::apex::AddBlockApex(instance), Ok());
+
+  OnStart();
+  UnmountOnTearDown(path1);
+  UnmountOnTearDown(path2);
+
+  ASSERT_EQ(GetProperty(kTestApexdStatusSysprop, ""), "starting");
+  auto apex_mounts = GetApexMounts();
+
+  ASSERT_THAT(apex_mounts,
+              UnorderedElementsAre("/apex/com.android.apex.test_package",
+                                   "/apex/com.android.apex.test_package@1",
+                                   "/apex/com.android.apex.test_package_2",
+                                   "/apex/com.android.apex.test_package_2@1"));
+}
+
+TEST_F(ApexdMountTest, OnStartBlockAndCompressedInstalled) {
+  MockCheckpointInterface checkpoint_interface;
+  // Need to call InitializeVold before calling OnStart
+  InitializeVold(&checkpoint_interface);
+
+  // Set system property to enable block apexes
+  SetBlockApexEnabled(true);
+
+  auto path1 = AddPreInstalledApex("com.android.apex.compressed.v1.capex");
+  auto path2 = AddBlockApex("apex.apexd_test.apex");
+
+  auto& instance = ApexFileRepository::GetInstance();
+
+  ASSERT_THAT(instance.AddPreInstalledApex({GetBuiltInDir()}), Ok());
+  ASSERT_THAT(android::apex::AddBlockApex(instance), Ok());
+
+  OnStart();
+  UnmountOnTearDown(path1);
+  UnmountOnTearDown(path2);
+
+  // Decompressed APEX should be mounted
+  std::string decompressed_active_apex = StringPrintf(
+      "%s/com.android.apex.compressed@1%s", GetDecompressionDir().c_str(),
+      kDecompressedApexPackageSuffix);
+  UnmountOnTearDown(decompressed_active_apex);
+
+  ASSERT_EQ(GetProperty(kTestApexdStatusSysprop, ""), "starting");
+  auto apex_mounts = GetApexMounts();
+  ASSERT_THAT(apex_mounts,
+              UnorderedElementsAre("/apex/com.android.apex.compressed",
+                                   "/apex/com.android.apex.compressed@1",
+                                   "/apex/com.android.apex.test_package",
+                                   "/apex/com.android.apex.test_package@1"));
+}
+
+// Test that data version of apex is used if newer
+TEST_F(ApexdMountTest, BlockAndNewerData) {
+  // MockCheckpointInterface checkpoint_interface;
+  //// Need to call InitializeVold before calling OnStart
+  // InitializeVold(&checkpoint_interface);
+
+  // Set system property to enable block apexes
+  SetBlockApexEnabled(true);
+
+  auto& instance = ApexFileRepository::GetInstance();
+  AddBlockApex("apex.apexd_test.apex");
+  ASSERT_THAT(android::apex::AddBlockApex(instance), Ok());
+
+  TemporaryDir data_dir;
+  auto apexd_test_file_v2 =
+      ApexFile::Open(AddDataApex("apex.apexd_test_v2.apex"));
+  ASSERT_THAT(instance.AddDataApex(GetDataDir()), Ok());
+
+  auto all_apex = instance.AllApexFilesByName();
+  auto result = SelectApexForActivation(all_apex, instance);
+  ASSERT_EQ(result.size(), 1u);
+
+  ASSERT_THAT(result,
+              UnorderedElementsAre(ApexFileEq(ByRef(*apexd_test_file_v2))));
+}
+
+// Test that data version of apex not is used if older
+TEST_F(ApexdMountTest, BlockApexAndOlderData) {
+  MockCheckpointInterface checkpoint_interface;
+  // Need to call InitializeVold before calling OnStart
+  InitializeVold(&checkpoint_interface);
+
+  // Set system property to enable block apexes
+  SetBlockApexEnabled(true);
+
+  auto& instance = ApexFileRepository::GetInstance();
+  auto apexd_test_file_v2 =
+      ApexFile::Open(AddBlockApex("apex.apexd_test_v2.apex"));
+  ASSERT_THAT(android::apex::AddBlockApex(instance), Ok());
+
+  TemporaryDir data_dir;
+  AddDataApex("apex.apexd_test.apex");
+  ASSERT_THAT(instance.AddDataApex(GetDataDir()), Ok());
+
+  auto all_apex = instance.AllApexFilesByName();
+  auto result = SelectApexForActivation(all_apex, instance);
+  ASSERT_EQ(result.size(), 1u);
+
+  ASSERT_THAT(result,
+              UnorderedElementsAre(ApexFileEq(ByRef(*apexd_test_file_v2))));
+}
+
+// Test that AddBlockApex does nothing if system property not set.
+TEST_F(ApexdMountTest, AddBlockApexWithoutSystemProp) {
+  MockCheckpointInterface checkpoint_interface;
+  // Need to call InitializeVold before calling OnStart
+  InitializeVold(&checkpoint_interface);
+
+  auto& instance = ApexFileRepository::GetInstance();
+  AddBlockApex("apex.apexd_test.apex");
+  ASSERT_THAT(android::apex::AddBlockApex(instance), Ok());
+  ASSERT_EQ(instance.AllApexFilesByName().size(), 0ul);
+}
+
+// Test that adding block apex fails if preinstalled version exists
+TEST_F(ApexdMountTest, AddBlockApexFailsWithDuplicate) {
+  MockCheckpointInterface checkpoint_interface;
+  // Need to call InitializeVold before calling OnStart
+  InitializeVold(&checkpoint_interface);
+
+  // Set system property to enable block apexes
+  SetBlockApexEnabled(true);
+
+  AddPreInstalledApex("apex.apexd_test.apex");
+  AddBlockApex("apex.apexd_test_v2.apex");
+
+  auto& instance = ApexFileRepository::GetInstance();
+
+  ASSERT_THAT(instance.AddPreInstalledApex({GetBuiltInDir()}), Ok());
+  ASSERT_THAT(android::apex::AddBlockApex(instance),
+              HasError(WithMessage(HasSubstr(
+                  "duplicate of com.android.apex.test_package found"))));
+}
+
+// Test that adding block apex fails if preinstalled compressed version exists
+TEST_F(ApexdMountTest, AddBlockApexFailsWithCompressedDuplicate) {
+  MockCheckpointInterface checkpoint_interface;
+  // Need to call InitializeVold before calling OnStart
+  InitializeVold(&checkpoint_interface);
+
+  // Set system property to enable block apexes
+  SetBlockApexEnabled(true);
+
+  auto path1 = AddPreInstalledApex("com.android.apex.compressed.v1.capex");
+  auto path2 = AddBlockApex("com.android.apex.compressed.v1_original.apex");
+
+  auto& instance = ApexFileRepository::GetInstance();
+
+  ASSERT_THAT(instance.AddPreInstalledApex({GetBuiltInDir()}), Ok());
+  ASSERT_THAT(android::apex::AddBlockApex(instance),
+              HasError(WithMessage(HasSubstr(
+                  "duplicate of com.android.apex.compressed found"))));
+}
+
 class ApexActivationFailureTests : public ApexdMountTest {};
 
 TEST_F(ApexActivationFailureTests, BuildFingerprintDifferent) {
diff --git a/apexd/apexservice.cpp b/apexd/apexservice.cpp
index ad149d1..417ae09 100644
--- a/apexd/apexservice.cpp
+++ b/apexd/apexservice.cpp
@@ -359,7 +359,17 @@
   Result<std::string> preinstalled_path =
       instance.GetPreinstalledPath(package.GetManifest().name());
   if (preinstalled_path.ok()) {
-    out.preinstalledModulePath = *preinstalled_path;
+    // We replace the preinstalled paths for block devices to /system/apex
+    // because PackageManager will not resolve them if they aren't in one of
+    // the SYSTEM_PARTITIONS defined in PackagePartitions.java.
+    // b/195363518 for more context.
+    const std::string block_path = "/dev/block/";
+    const std::string sys_apex_path =
+        std::string(kApexPackageSystemDir) + "/" +
+        preinstalled_path->substr(block_path.length());
+    out.preinstalledModulePath = preinstalled_path->starts_with(block_path)
+                                     ? sys_apex_path
+                                     : *preinstalled_path;
   }
   return out;
 }