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;
}