| /* |
| * Copyright (C) 2021 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| #include <optional> |
| #include <string> |
| #include <vector> |
| |
| #include <android-base/file.h> |
| #include <android-base/properties.h> |
| #include <android-base/scopeguard.h> |
| #include <android-base/stringprintf.h> |
| #include <gmock/gmock.h> |
| #include <gtest/gtest.h> |
| #include <microdroid/signature.h> |
| |
| #include "apex_database.h" |
| #include "apex_file_repository.h" |
| #include "apexd.h" |
| #include "apexd_checkpoint.h" |
| #include "apexd_test_utils.h" |
| #include "apexd_utils.h" |
| |
| #include "apex_manifest.pb.h" |
| #include "com_android_apex.h" |
| #include "gmock/gmock-matchers.h" |
| |
| namespace android { |
| namespace apex { |
| |
| namespace fs = std::filesystem; |
| |
| using MountedApexData = MountedApexDatabase::MountedApexData; |
| using android::apex::testing::ApexFileEq; |
| using android::apex::testing::IsOk; |
| using android::base::GetExecutableDirectory; |
| using android::base::GetProperty; |
| using android::base::make_scope_guard; |
| using android::base::Result; |
| using android::base::StringPrintf; |
| using android::base::WriteStringToFile; |
| using com::android::apex::testing::ApexInfoXmlEq; |
| using ::testing::ByRef; |
| using ::testing::HasSubstr; |
| using ::testing::IsEmpty; |
| using ::testing::StartsWith; |
| using ::testing::UnorderedElementsAre; |
| using ::testing::UnorderedElementsAreArray; |
| |
| static std::string GetTestDataDir() { return GetExecutableDirectory(); } |
| static std::string GetTestFile(const std::string& name) { |
| return GetTestDataDir() + "/" + name; |
| } |
| |
| // A very basic mock of CheckpointInterface. |
| class MockCheckpointInterface : public CheckpointInterface { |
| public: |
| Result<bool> SupportsFsCheckpoints() override { return {}; } |
| |
| Result<bool> NeedsCheckpoint() override { return false; } |
| |
| Result<bool> NeedsRollback() override { return false; } |
| |
| Result<void> StartCheckpoint(int32_t num_retries) override { return {}; } |
| |
| Result<void> AbortChanges(const std::string& msg, bool retry) override { |
| return {}; |
| } |
| }; |
| |
| static constexpr const char* kTestApexdStatusSysprop = "apexd.status.test"; |
| |
| // A test fixture that provides frequently required temp directories for tests |
| class ApexdUnitTest : public ::testing::Test { |
| public: |
| ApexdUnitTest() { |
| built_in_dir_ = StringPrintf("%s/pre-installed-apex", td_.path); |
| data_dir_ = StringPrintf("%s/data-apex", td_.path); |
| decompression_dir_ = StringPrintf("%s/decompressed-apex", td_.path); |
| ota_reserved_dir_ = StringPrintf("%s/ota-reserved", td_.path); |
| hash_tree_dir_ = StringPrintf("%s/apex-hash-tree", td_.path); |
| vm_payload_signature_path_ = |
| StringPrintf("%s/vm-payload-1", td_.path); // should end with 1 |
| config_ = {kTestApexdStatusSysprop, |
| {built_in_dir_}, |
| data_dir_.c_str(), |
| decompression_dir_.c_str(), |
| ota_reserved_dir_.c_str(), |
| hash_tree_dir_.c_str(), |
| vm_payload_signature_path_.c_str()}; |
| } |
| |
| const std::string& GetBuiltInDir() { return built_in_dir_; } |
| const std::string& GetDataDir() { return data_dir_; } |
| const std::string& GetDecompressionDir() { return decompression_dir_; } |
| const std::string& GetOtaReservedDir() { return ota_reserved_dir_; } |
| const std::string& GetHashTreeDir() { return hash_tree_dir_; } |
| |
| std::string AddPreInstalledApex(const std::string& apex_name) { |
| fs::copy(GetTestFile(apex_name), built_in_dir_); |
| return StringPrintf("%s/%s", built_in_dir_.c_str(), apex_name.c_str()); |
| } |
| |
| std::string AddDataApex(const std::string& apex_name) { |
| fs::copy(GetTestFile(apex_name), data_dir_); |
| return StringPrintf("%s/%s", data_dir_.c_str(), apex_name.c_str()); |
| } |
| |
| std::string AddBlockApex(const std::string& apex_name, |
| std::optional<std::string> pubkey = std::nullopt) { |
| auto signature = android::microdroid::ReadMicrodroidSignature( |
| vm_payload_signature_path_); |
| |
| static constexpr const int kFirstApexPartition = 2; |
| auto partition_num = kFirstApexPartition + signature->apexes_size(); |
| auto apex_path = StringPrintf("%s/vm-payload-%d", td_.path, partition_num); |
| auto apex_size = *GetFileSize(GetTestFile(apex_name)); |
| { |
| // Create a temp file with padding at the end |
| std::ofstream out(apex_path); |
| std::ifstream in(GetTestFile(apex_name), std::ios::binary); |
| out << in.rdbuf(); |
| out << std::string(10, 0); // ten zeros. |
| } |
| |
| auto apex = signature->add_apexes(); |
| apex->set_name("apex" + std::to_string(partition_num)); |
| apex->set_size(apex_size); |
| if (pubkey.has_value()) { |
| apex->set_publickey(*pubkey); |
| } |
| |
| std::ofstream out(vm_payload_signature_path_); |
| WriteMicrodroidSignature(*signature, out); |
| return apex_path; |
| } |
| |
| // Copies the compressed apex to |built_in_dir| and decompresses it to |
| // |decompressed_dir| and then hard links to |target_dir| |
| std::string PrepareCompressedApex(const std::string& name, |
| const std::string& built_in_dir) { |
| fs::copy(GetTestFile(name), built_in_dir); |
| auto compressed_apex = ApexFile::Open( |
| StringPrintf("%s/%s", built_in_dir.c_str(), name.c_str())); |
| std::vector<ApexFileRef> compressed_apex_list; |
| compressed_apex_list.emplace_back(std::cref(*compressed_apex)); |
| auto return_value = |
| ProcessCompressedApex(compressed_apex_list, /*is_ota_chroot*/ false); |
| return StringPrintf("%s/%s", built_in_dir.c_str(), name.c_str()); |
| } |
| |
| std::string PrepareCompressedApex(const std::string& name) { |
| return PrepareCompressedApex(name, built_in_dir_); |
| } |
| |
| protected: |
| void SetUp() override { |
| SetConfig(config_); |
| ApexFileRepository::GetInstance().Reset(decompression_dir_); |
| ASSERT_EQ(mkdir(built_in_dir_.c_str(), 0755), 0); |
| ASSERT_EQ(mkdir(data_dir_.c_str(), 0755), 0); |
| ASSERT_EQ(mkdir(decompression_dir_.c_str(), 0755), 0); |
| ASSERT_EQ(mkdir(ota_reserved_dir_.c_str(), 0755), 0); |
| ASSERT_EQ(mkdir(hash_tree_dir_.c_str(), 0755), 0); |
| |
| android::microdroid::MicrodroidSignature signature; |
| std::ofstream out(vm_payload_signature_path_); |
| WriteMicrodroidSignature(signature, out); |
| } |
| |
| private: |
| TemporaryDir td_; |
| std::string built_in_dir_; |
| std::string data_dir_; |
| std::string decompression_dir_; |
| std::string ota_reserved_dir_; |
| std::string hash_tree_dir_; |
| std::string vm_payload_signature_path_; |
| ApexdConfig config_; |
| }; |
| |
| // Apex that does not have pre-installed version, does not get selected |
| TEST_F(ApexdUnitTest, ApexMustHavePreInstalledVersionForSelection) { |
| AddPreInstalledApex("apex.apexd_test.apex"); |
| AddPreInstalledApex("com.android.apex.cts.shim.apex"); |
| auto shared_lib_1 = ApexFile::Open(AddPreInstalledApex( |
| "com.android.apex.test.sharedlibs_generated.v1.libvX.apex")); |
| auto& instance = ApexFileRepository::GetInstance(); |
| // Pre-installed data needs to be present so that we can add data apex |
| ASSERT_TRUE(IsOk(instance.AddPreInstalledApex({GetBuiltInDir()}))); |
| |
| auto apexd_test_file = ApexFile::Open(AddDataApex("apex.apexd_test.apex")); |
| auto shim_v1 = ApexFile::Open(AddDataApex("com.android.apex.cts.shim.apex")); |
| auto shared_lib_2 = ApexFile::Open( |
| AddDataApex("com.android.apex.test.sharedlibs_generated.v1.libvX.apex")); |
| ASSERT_TRUE(IsOk(instance.AddDataApex(GetDataDir(), GetDecompressionDir()))); |
| |
| const auto all_apex = instance.AllApexFilesByName(); |
| // Pass a blank instance so that the data apex files are not considered |
| // pre-installed |
| const ApexFileRepository instance_blank; |
| auto result = SelectApexForActivation(all_apex, instance_blank); |
| ASSERT_EQ(result.size(), 0u); |
| // When passed proper instance they should get selected |
| result = SelectApexForActivation(all_apex, instance); |
| ASSERT_EQ(result.size(), 4u); |
| ASSERT_THAT(result, UnorderedElementsAre(ApexFileEq(ByRef(*apexd_test_file)), |
| ApexFileEq(ByRef(*shim_v1)), |
| ApexFileEq(ByRef(*shared_lib_1)), |
| ApexFileEq(ByRef(*shared_lib_2)))); |
| } |
| |
| // Higher version gets priority when selecting for activation |
| TEST_F(ApexdUnitTest, HigherVersionOfApexIsSelected) { |
| auto apexd_test_file_v2 = |
| ApexFile::Open(AddPreInstalledApex("apex.apexd_test_v2.apex")); |
| AddPreInstalledApex("com.android.apex.cts.shim.apex"); |
| auto& instance = ApexFileRepository::GetInstance(); |
| ASSERT_TRUE(IsOk(instance.AddPreInstalledApex({GetBuiltInDir()}))); |
| |
| TemporaryDir data_dir; |
| AddDataApex("apex.apexd_test.apex"); |
| auto shim_v2 = |
| ApexFile::Open(AddDataApex("com.android.apex.cts.shim.v2.apex")); |
| ASSERT_TRUE(IsOk(instance.AddDataApex(GetDataDir(), GetDecompressionDir()))); |
| |
| auto all_apex = instance.AllApexFilesByName(); |
| auto result = SelectApexForActivation(all_apex, instance); |
| ASSERT_EQ(result.size(), 2u); |
| |
| ASSERT_THAT(result, |
| UnorderedElementsAre(ApexFileEq(ByRef(*apexd_test_file_v2)), |
| ApexFileEq(ByRef(*shim_v2)))); |
| } |
| |
| // When versions are equal, non-pre-installed version gets priority |
| TEST_F(ApexdUnitTest, DataApexGetsPriorityForSameVersions) { |
| AddPreInstalledApex("apex.apexd_test.apex"); |
| AddPreInstalledApex("com.android.apex.cts.shim.apex"); |
| // Initialize pre-installed APEX information |
| auto& instance = ApexFileRepository::GetInstance(); |
| ASSERT_TRUE(IsOk(instance.AddPreInstalledApex({GetBuiltInDir()}))); |
| |
| auto apexd_test_file = ApexFile::Open(AddDataApex("apex.apexd_test.apex")); |
| auto shim_v1 = ApexFile::Open(AddDataApex("com.android.apex.cts.shim.apex")); |
| // Initialize ApexFile repo |
| ASSERT_TRUE(IsOk(instance.AddDataApex(GetDataDir(), GetDecompressionDir()))); |
| |
| auto all_apex = instance.AllApexFilesByName(); |
| auto result = SelectApexForActivation(all_apex, instance); |
| ASSERT_EQ(result.size(), 2u); |
| |
| ASSERT_THAT(result, UnorderedElementsAre(ApexFileEq(ByRef(*apexd_test_file)), |
| ApexFileEq(ByRef(*shim_v1)))); |
| } |
| |
| // Both versions of shared libs can be selected |
| TEST_F(ApexdUnitTest, SharedLibsCanHaveBothVersionSelected) { |
| auto shared_lib_v1 = ApexFile::Open(AddPreInstalledApex( |
| "com.android.apex.test.sharedlibs_generated.v1.libvX.apex")); |
| // Initialize pre-installed APEX information |
| auto& instance = ApexFileRepository::GetInstance(); |
| ASSERT_TRUE(IsOk(instance.AddPreInstalledApex({GetBuiltInDir()}))); |
| |
| auto shared_lib_v2 = ApexFile::Open( |
| AddDataApex("com.android.apex.test.sharedlibs_generated.v2.libvY.apex")); |
| // Initialize data APEX information |
| ASSERT_TRUE(IsOk(instance.AddDataApex(GetDataDir(), GetDecompressionDir()))); |
| |
| auto all_apex = instance.AllApexFilesByName(); |
| auto result = SelectApexForActivation(all_apex, instance); |
| ASSERT_EQ(result.size(), 2u); |
| |
| ASSERT_THAT(result, UnorderedElementsAre(ApexFileEq(ByRef(*shared_lib_v1)), |
| ApexFileEq(ByRef(*shared_lib_v2)))); |
| } |
| |
| TEST_F(ApexdUnitTest, ProcessCompressedApex) { |
| auto compressed_apex = ApexFile::Open( |
| AddPreInstalledApex("com.android.apex.compressed.v1.capex")); |
| |
| std::vector<ApexFileRef> compressed_apex_list; |
| compressed_apex_list.emplace_back(std::cref(*compressed_apex)); |
| auto return_value = |
| ProcessCompressedApex(compressed_apex_list, /* is_ota_chroot= */ false); |
| |
| std::string decompressed_file_path = StringPrintf( |
| "%s/com.android.apex.compressed@1%s", GetDecompressionDir().c_str(), |
| kDecompressedApexPackageSuffix); |
| // Assert output path is not empty |
| auto exists = PathExists(decompressed_file_path); |
| ASSERT_TRUE(IsOk(exists)); |
| ASSERT_TRUE(*exists) << decompressed_file_path << " does not exist"; |
| |
| // Assert that decompressed apex is same as original apex |
| const std::string original_apex_file_path = |
| GetTestFile("com.android.apex.compressed.v1_original.apex"); |
| auto comparison_result = |
| CompareFiles(original_apex_file_path, decompressed_file_path); |
| ASSERT_TRUE(IsOk(comparison_result)); |
| ASSERT_TRUE(*comparison_result); |
| |
| // Assert that return value contains decompressed APEX |
| auto decompressed_apex = ApexFile::Open(decompressed_file_path); |
| ASSERT_THAT(return_value, |
| UnorderedElementsAre(ApexFileEq(ByRef(*decompressed_apex)))); |
| } |
| |
| TEST_F(ApexdUnitTest, ProcessCompressedApexRunsVerification) { |
| auto compressed_apex_mismatch_key = ApexFile::Open(AddPreInstalledApex( |
| "com.android.apex.compressed_key_mismatch_with_original.capex")); |
| auto compressed_apex_version_mismatch = ApexFile::Open( |
| AddPreInstalledApex("com.android.apex.compressed.v1_with_v2_apex.capex")); |
| |
| std::vector<ApexFileRef> compressed_apex_list; |
| compressed_apex_list.emplace_back(std::cref(*compressed_apex_mismatch_key)); |
| compressed_apex_list.emplace_back( |
| std::cref(*compressed_apex_version_mismatch)); |
| auto return_value = |
| ProcessCompressedApex(compressed_apex_list, /* is_ota_chroot= */ false); |
| ASSERT_EQ(return_value.size(), 0u); |
| } |
| |
| TEST_F(ApexdUnitTest, ValidateDecompressedApex) { |
| auto capex = ApexFile::Open( |
| AddPreInstalledApex("com.android.apex.compressed.v1.capex")); |
| auto decompressed_v1 = ApexFile::Open( |
| AddDataApex("com.android.apex.compressed.v1_original.apex")); |
| |
| auto result = |
| ValidateDecompressedApex(std::cref(*capex), std::cref(*decompressed_v1)); |
| ASSERT_TRUE(IsOk(result)); |
| |
| // Validation checks version |
| auto decompressed_v2 = ApexFile::Open( |
| AddDataApex("com.android.apex.compressed.v2_original.apex")); |
| result = |
| ValidateDecompressedApex(std::cref(*capex), std::cref(*decompressed_v2)); |
| ASSERT_FALSE(IsOk(result)); |
| ASSERT_THAT( |
| result.error().message(), |
| HasSubstr( |
| "Compressed APEX has different version than decompressed APEX")); |
| |
| // Validation checks key |
| auto capex_different_key = ApexFile::Open( |
| AddDataApex("com.android.apex.compressed_different_key.capex")); |
| result = ValidateDecompressedApex(std::cref(*capex_different_key), |
| std::cref(*decompressed_v1)); |
| ASSERT_FALSE(IsOk(result)); |
| ASSERT_THAT( |
| result.error().message(), |
| HasSubstr("Public key of compressed APEX is different than original")); |
| } |
| |
| TEST_F(ApexdUnitTest, ProcessCompressedApexCanBeCalledMultipleTimes) { |
| auto compressed_apex = ApexFile::Open( |
| AddPreInstalledApex("com.android.apex.compressed.v1.capex")); |
| |
| std::vector<ApexFileRef> compressed_apex_list; |
| compressed_apex_list.emplace_back(std::cref(*compressed_apex)); |
| auto return_value = |
| ProcessCompressedApex(compressed_apex_list, /* is_ota_chroot= */ false); |
| ASSERT_EQ(return_value.size(), 1u); |
| |
| // Capture the creation time of the decompressed APEX |
| std::error_code ec; |
| auto decompressed_apex_path = StringPrintf( |
| "%s/com.android.apex.compressed@1%s", GetDecompressionDir().c_str(), |
| kDecompressedApexPackageSuffix); |
| auto last_write_time_1 = fs::last_write_time(decompressed_apex_path, ec); |
| ASSERT_FALSE(ec) << "Failed to capture last write time of " |
| << decompressed_apex_path; |
| |
| // Now try to decompress the same capex again. It should not fail. |
| return_value = |
| ProcessCompressedApex(compressed_apex_list, /* is_ota_chroot= */ false); |
| ASSERT_EQ(return_value.size(), 1u); |
| |
| // Ensure the decompressed APEX file did not change |
| auto last_write_time_2 = fs::last_write_time(decompressed_apex_path, ec); |
| ASSERT_FALSE(ec) << "Failed to capture last write time of " |
| << decompressed_apex_path; |
| ASSERT_EQ(last_write_time_1, last_write_time_2); |
| } |
| |
| // Test behavior of ProcessCompressedApex when is_ota_chroot is true |
| TEST_F(ApexdUnitTest, ProcessCompressedApexOnOtaChroot) { |
| auto compressed_apex = ApexFile::Open( |
| AddPreInstalledApex("com.android.apex.compressed.v1.capex")); |
| |
| std::vector<ApexFileRef> compressed_apex_list; |
| compressed_apex_list.emplace_back(std::cref(*compressed_apex)); |
| auto return_value = |
| ProcessCompressedApex(compressed_apex_list, /* is_ota_chroot= */ true); |
| ASSERT_EQ(return_value.size(), 1u); |
| |
| // Decompressed APEX should be located in decompression_dir |
| std::string decompressed_file_path = |
| StringPrintf("%s/com.android.apex.compressed@1%s", |
| GetDecompressionDir().c_str(), kOtaApexPackageSuffix); |
| // Assert output path is not empty |
| auto exists = PathExists(decompressed_file_path); |
| ASSERT_TRUE(IsOk(exists)); |
| ASSERT_TRUE(*exists) << decompressed_file_path << " does not exist"; |
| |
| // Assert that decompressed apex is same as original apex |
| const std::string original_apex_file_path = |
| GetTestFile("com.android.apex.compressed.v1_original.apex"); |
| auto comparison_result = |
| CompareFiles(original_apex_file_path, decompressed_file_path); |
| ASSERT_TRUE(IsOk(comparison_result)); |
| ASSERT_TRUE(*comparison_result); |
| |
| // Assert that return value contains the decompressed APEX |
| auto apex_file = ApexFile::Open(decompressed_file_path); |
| ASSERT_THAT(return_value, |
| UnorderedElementsAre(ApexFileEq(ByRef(*apex_file)))); |
| } |
| |
| // When decompressing APEX, reuse existing OTA APEX |
| TEST_F(ApexdUnitTest, ProcessCompressedApexReuseOtaApex) { |
| // Push a compressed APEX that will fail post-decompression verification |
| auto compressed_apex = ApexFile::Open( |
| AddPreInstalledApex("com.android.apex.compressed.v1_with_v2_apex.capex")); |
| |
| std::vector<ApexFileRef> compressed_apex_list; |
| compressed_apex_list.emplace_back(std::cref(*compressed_apex)); |
| |
| // If we try to decompress capex directly, it should fail since the capex |
| // pushed is faulty and cannot be decompressed |
| auto return_value = |
| ProcessCompressedApex(compressed_apex_list, /* is_ota_chroot= */ false); |
| ASSERT_EQ(return_value.size(), 0u); |
| |
| // But, if there is an ota_apex present for reuse, it should reuse that |
| // and avoid decompressing the faulty capex |
| |
| // Push an OTA apex that should be reused to skip decompression |
| auto ota_apex_path = |
| StringPrintf("%s/com.android.apex.compressed@1%s", |
| GetDecompressionDir().c_str(), kOtaApexPackageSuffix); |
| fs::copy(GetTestFile("com.android.apex.compressed.v1_original.apex"), |
| ota_apex_path); |
| return_value = |
| ProcessCompressedApex(compressed_apex_list, /* is_ota_chroot= */ false); |
| ASSERT_EQ(return_value.size(), 1u); |
| |
| // Ota Apex should be cleaned up |
| ASSERT_FALSE(*PathExists(ota_apex_path)); |
| ASSERT_EQ(return_value[0].GetPath(), |
| StringPrintf("%s/com.android.apex.compressed@1%s", |
| GetDecompressionDir().c_str(), |
| kDecompressedApexPackageSuffix)); |
| } |
| |
| TEST_F(ApexdUnitTest, ShouldAllocateSpaceForDecompressionNewApex) { |
| auto& instance = ApexFileRepository::GetInstance(); |
| ASSERT_TRUE(IsOk(instance.AddPreInstalledApex({GetBuiltInDir()}))); |
| |
| // A brand new compressed APEX is being introduced: selected |
| auto result = |
| ShouldAllocateSpaceForDecompression("com.android.brand.new", 1, instance); |
| ASSERT_TRUE(IsOk(result)); |
| ASSERT_TRUE(*result); |
| } |
| |
| TEST_F(ApexdUnitTest, |
| ShouldAllocateSpaceForDecompressionWasNotCompressedBefore) { |
| // Prepare fake pre-installed apex |
| AddPreInstalledApex("apex.apexd_test.apex"); |
| auto& instance = ApexFileRepository::GetInstance(); |
| ASSERT_TRUE(IsOk(instance.AddPreInstalledApex({GetBuiltInDir()}))); |
| |
| // An existing pre-installed APEX is now compressed in the OTA: selected |
| { |
| auto result = ShouldAllocateSpaceForDecompression( |
| "com.android.apex.test_package", 1, instance); |
| ASSERT_TRUE(IsOk(result)); |
| ASSERT_TRUE(*result); |
| } |
| |
| // Even if there is a data apex (lower version) |
| // Include data apex within calculation now |
| AddDataApex("apex.apexd_test_v2.apex"); |
| ASSERT_TRUE(IsOk(instance.AddDataApex(GetDataDir(), GetDecompressionDir()))); |
| { |
| auto result = ShouldAllocateSpaceForDecompression( |
| "com.android.apex.test_package", 3, instance); |
| ASSERT_TRUE(IsOk(result)); |
| ASSERT_TRUE(*result); |
| } |
| |
| // But not if data apex has equal or higher version |
| { |
| auto result = ShouldAllocateSpaceForDecompression( |
| "com.android.apex.test_package", 2, instance); |
| ASSERT_TRUE(IsOk(result)); |
| ASSERT_FALSE(*result); |
| } |
| } |
| |
| TEST_F(ApexdUnitTest, ShouldAllocateSpaceForDecompressionVersionCompare) { |
| // Prepare fake pre-installed apex |
| PrepareCompressedApex("com.android.apex.compressed.v1.capex"); |
| auto& instance = ApexFileRepository::GetInstance(); |
| ASSERT_TRUE(IsOk(instance.AddPreInstalledApex({GetBuiltInDir()}))); |
| ASSERT_TRUE(IsOk(instance.AddDataApex(GetDataDir(), GetDecompressionDir()))); |
| |
| { |
| // New Compressed apex has higher version than decompressed data apex: |
| // selected |
| auto result = ShouldAllocateSpaceForDecompression( |
| "com.android.apex.compressed", 2, instance); |
| ASSERT_TRUE(IsOk(result)); |
| ASSERT_TRUE(*result) |
| << "Higher version test with decompressed data returned false"; |
| } |
| |
| // Compare against decompressed data apex |
| { |
| // New Compressed apex has same version as decompressed data apex: not |
| // selected |
| auto result = ShouldAllocateSpaceForDecompression( |
| "com.android.apex.compressed", 1, instance); |
| ASSERT_TRUE(IsOk(result)); |
| ASSERT_FALSE(*result) |
| << "Same version test with decompressed data returned true"; |
| } |
| |
| { |
| // New Compressed apex has lower version than decompressed data apex: |
| // selected |
| auto result = ShouldAllocateSpaceForDecompression( |
| "com.android.apex.compressed", 0, instance); |
| ASSERT_TRUE(IsOk(result)); |
| ASSERT_TRUE(*result) |
| << "lower version test with decompressed data returned false"; |
| } |
| |
| // Replace decompressed data apex with a higher version |
| ApexFileRepository instance_new(GetDecompressionDir()); |
| ASSERT_TRUE(IsOk(instance_new.AddPreInstalledApex({GetBuiltInDir()}))); |
| TemporaryDir data_dir_new; |
| fs::copy(GetTestFile("com.android.apex.compressed.v2_original.apex"), |
| data_dir_new.path); |
| ASSERT_TRUE( |
| IsOk(instance_new.AddDataApex(data_dir_new.path, GetDecompressionDir()))); |
| |
| { |
| // New Compressed apex has higher version as data apex: selected |
| auto result = ShouldAllocateSpaceForDecompression( |
| "com.android.apex.compressed", 3, instance_new); |
| ASSERT_TRUE(IsOk(result)); |
| ASSERT_TRUE(*result) << "Higher version test with new data returned false"; |
| } |
| |
| { |
| // New Compressed apex has same version as data apex: not selected |
| auto result = ShouldAllocateSpaceForDecompression( |
| "com.android.apex.compressed", 2, instance_new); |
| ASSERT_TRUE(IsOk(result)); |
| ASSERT_FALSE(*result) << "Same version test with new data returned true"; |
| } |
| |
| { |
| // New Compressed apex has lower version than data apex: not selected |
| auto result = ShouldAllocateSpaceForDecompression( |
| "com.android.apex.compressed", 1, instance_new); |
| ASSERT_TRUE(IsOk(result)); |
| ASSERT_FALSE(*result) << "lower version test with new data returned true"; |
| } |
| } |
| |
| TEST_F(ApexdUnitTest, ReserveSpaceForCompressedApexCreatesSingleFile) { |
| TemporaryDir dest_dir; |
| // Reserving space should create a single file in dest_dir with exact size |
| |
| ASSERT_TRUE(IsOk(ReserveSpaceForCompressedApex(100, dest_dir.path))); |
| auto files = ReadDir(dest_dir.path, [](auto _) { return true; }); |
| ASSERT_TRUE(IsOk(files)); |
| ASSERT_EQ(files->size(), 1u); |
| EXPECT_EQ(fs::file_size((*files)[0]), 100u); |
| } |
| |
| TEST_F(ApexdUnitTest, ReserveSpaceForCompressedApexSafeToCallMultipleTimes) { |
| TemporaryDir dest_dir; |
| // Calling ReserveSpaceForCompressedApex multiple times should still create |
| // a single file |
| ASSERT_TRUE(IsOk(ReserveSpaceForCompressedApex(100, dest_dir.path))); |
| ASSERT_TRUE(IsOk(ReserveSpaceForCompressedApex(100, dest_dir.path))); |
| auto files = ReadDir(dest_dir.path, [](auto _) { return true; }); |
| ASSERT_TRUE(IsOk(files)); |
| ASSERT_EQ(files->size(), 1u); |
| EXPECT_EQ(fs::file_size((*files)[0]), 100u); |
| } |
| |
| TEST_F(ApexdUnitTest, ReserveSpaceForCompressedApexShrinkAndGrow) { |
| TemporaryDir dest_dir; |
| |
| // Create a 100 byte file |
| ASSERT_TRUE(IsOk(ReserveSpaceForCompressedApex(100, dest_dir.path))); |
| |
| // Should be able to shrink and grow the reserved space |
| ASSERT_TRUE(IsOk(ReserveSpaceForCompressedApex(1000, dest_dir.path))); |
| auto files = ReadDir(dest_dir.path, [](auto _) { return true; }); |
| ASSERT_TRUE(IsOk(files)); |
| ASSERT_EQ(files->size(), 1u); |
| EXPECT_EQ(fs::file_size((*files)[0]), 1000u); |
| |
| ASSERT_TRUE(IsOk(ReserveSpaceForCompressedApex(10, dest_dir.path))); |
| files = ReadDir(dest_dir.path, [](auto _) { return true; }); |
| ASSERT_TRUE(IsOk(files)); |
| ASSERT_EQ(files->size(), 1u); |
| EXPECT_EQ(fs::file_size((*files)[0]), 10u); |
| } |
| |
| TEST_F(ApexdUnitTest, ReserveSpaceForCompressedApexDeallocateIfPassedZero) { |
| TemporaryDir dest_dir; |
| |
| // Create a file first |
| ASSERT_TRUE(IsOk(ReserveSpaceForCompressedApex(100, dest_dir.path))); |
| auto files = ReadDir(dest_dir.path, [](auto _) { return true; }); |
| ASSERT_TRUE(IsOk(files)); |
| ASSERT_EQ(files->size(), 1u); |
| |
| // Should delete the reserved file if size passed is 0 |
| ASSERT_TRUE(IsOk(ReserveSpaceForCompressedApex(0, dest_dir.path))); |
| files = ReadDir(dest_dir.path, [](auto _) { return true; }); |
| ASSERT_TRUE(IsOk(files)); |
| ASSERT_EQ(files->size(), 0u); |
| } |
| |
| TEST_F(ApexdUnitTest, ReserveSpaceForCompressedApexErrorForNegativeValue) { |
| TemporaryDir dest_dir; |
| // Should return error if negative value is passed |
| ASSERT_FALSE(IsOk(ReserveSpaceForCompressedApex(-1, dest_dir.path))); |
| } |
| |
| // A test fixture to use for tests that mount/unmount apexes. |
| class ApexdMountTest : public ApexdUnitTest { |
| public: |
| |
| void UnmountOnTearDown(const std::string& apex_file) { |
| to_unmount_.push_back(apex_file); |
| } |
| |
| protected: |
| void SetUp() final { |
| ApexdUnitTest::SetUp(); |
| GetApexDatabaseForTesting().Reset(); |
| ASSERT_TRUE(IsOk(SetUpApexTestEnvironment())); |
| } |
| |
| void TearDown() final { |
| for (const auto& apex : to_unmount_) { |
| if (auto status = DeactivatePackage(apex); !status.ok()) { |
| LOG(ERROR) << "Failed to unmount " << apex << " : " << status.error(); |
| } |
| } |
| } |
| |
| private: |
| MountNamespaceRestorer restorer_; |
| std::vector<std::string> to_unmount_; |
| }; |
| |
| TEST_F(ApexdMountTest, ActivatePackage) { |
| std::string file_path = AddPreInstalledApex("apex.apexd_test.apex"); |
| ApexFileRepository::GetInstance().AddPreInstalledApex({GetBuiltInDir()}); |
| |
| ASSERT_TRUE(IsOk(ActivatePackage(file_path))); |
| UnmountOnTearDown(file_path); |
| |
| auto active_apex = GetActivePackage("com.android.apex.test_package"); |
| ASSERT_TRUE(IsOk(active_apex)); |
| ASSERT_EQ(active_apex->GetPath(), file_path); |
| |
| auto apex_mounts = GetApexMounts(); |
| ASSERT_THAT(apex_mounts, |
| UnorderedElementsAre("/apex/com.android.apex.test_package", |
| "/apex/com.android.apex.test_package@1")); |
| |
| ASSERT_TRUE(IsOk(DeactivatePackage(file_path))); |
| ASSERT_FALSE(IsOk(GetActivePackage("com.android.apex.test_package"))); |
| |
| auto new_apex_mounts = GetApexMounts(); |
| ASSERT_EQ(new_apex_mounts.size(), 0u); |
| } |
| |
| TEST_F(ApexdMountTest, ActivateDeactivateSharedLibsApex) { |
| ASSERT_EQ(mkdir("/apex/sharedlibs", 0755), 0); |
| ASSERT_EQ(mkdir("/apex/sharedlibs/lib", 0755), 0); |
| ASSERT_EQ(mkdir("/apex/sharedlibs/lib64", 0755), 0); |
| auto deleter = make_scope_guard([]() { |
| std::error_code ec; |
| fs::remove_all("/apex/sharedlibs", ec); |
| if (ec) { |
| LOG(ERROR) << "Failed to delete /apex/sharedlibs : " << ec; |
| } |
| }); |
| |
| std::string file_path = AddPreInstalledApex( |
| "com.android.apex.test.sharedlibs_generated.v1.libvX.apex"); |
| ApexFileRepository::GetInstance().AddPreInstalledApex({GetBuiltInDir()}); |
| |
| ASSERT_TRUE(IsOk(ActivatePackage(file_path))); |
| |
| auto active_apex = GetActivePackage("com.android.apex.test.sharedlibs"); |
| ASSERT_TRUE(IsOk(active_apex)); |
| ASSERT_EQ(active_apex->GetPath(), file_path); |
| |
| auto apex_mounts = GetApexMounts(); |
| ASSERT_THAT(apex_mounts, |
| UnorderedElementsAre("/apex/com.android.apex.test.sharedlibs@1")); |
| |
| ASSERT_TRUE(IsOk(DeactivatePackage(file_path))); |
| ASSERT_FALSE(IsOk(GetActivePackage("com.android.apex.test.sharedlibs"))); |
| |
| auto new_apex_mounts = GetApexMounts(); |
| ASSERT_EQ(new_apex_mounts.size(), 0u); |
| } |
| |
| TEST_F(ApexdMountTest, RemoveInactiveDataApex) { |
| AddPreInstalledApex("com.android.apex.compressed.v2.capex"); |
| // Add a decompressed apex that will not be mounted, so should be removed |
| auto decompressed_apex = StringPrintf("%s/com.android.apex.compressed@1%s", |
| GetDecompressionDir().c_str(), |
| kDecompressedApexPackageSuffix); |
| fs::copy(GetTestFile("com.android.apex.compressed.v1_original.apex"), |
| decompressed_apex); |
| // Add a decompressed apex that will be mounted, so should be not be removed |
| auto active_decompressed_apex = StringPrintf( |
| "%s/com.android.apex.compressed@2%s", GetDecompressionDir().c_str(), |
| kDecompressedApexPackageSuffix); |
| fs::copy(GetTestFile("com.android.apex.compressed.v2_original.apex"), |
| active_decompressed_apex); |
| // Apex that do not have kDecompressedApexPackageSuffix, should not be removed |
| // from decompression_dir |
| auto decompressed_different_suffix = |
| StringPrintf("%s/com.android.apex.compressed@2%s", |
| GetDecompressionDir().c_str(), kApexPackageSuffix); |
| fs::copy(GetTestFile("com.android.apex.compressed.v2_original.apex"), |
| decompressed_different_suffix); |
| |
| AddPreInstalledApex("apex.apexd_test.apex"); |
| auto data_apex = AddDataApex("apex.apexd_test.apex"); |
| auto active_data_apex = AddDataApex("apex.apexd_test_v2.apex"); |
| |
| // Activate some of the apex |
| ApexFileRepository::GetInstance().AddPreInstalledApex({GetBuiltInDir()}); |
| UnmountOnTearDown(active_decompressed_apex); |
| UnmountOnTearDown(active_data_apex); |
| ASSERT_TRUE(IsOk(ActivatePackage(active_decompressed_apex))); |
| ASSERT_TRUE(IsOk(ActivatePackage(active_data_apex))); |
| // Clean up inactive apex packages |
| RemoveInactiveDataApex(); |
| |
| // Verify inactive apex packages have been deleted |
| ASSERT_TRUE(*PathExists(active_decompressed_apex)); |
| ASSERT_TRUE(*PathExists(active_data_apex)); |
| ASSERT_TRUE(*PathExists(decompressed_different_suffix)); |
| ASSERT_FALSE(*PathExists(decompressed_apex)); |
| ASSERT_FALSE(*PathExists(data_apex)); |
| } |
| |
| TEST_F(ApexdMountTest, OnOtaChrootBootstrapOnlyPreInstalledApexes) { |
| std::string apex_path_1 = AddPreInstalledApex("apex.apexd_test.apex"); |
| std::string apex_path_2 = |
| AddPreInstalledApex("apex.apexd_test_different_app.apex"); |
| |
| ASSERT_EQ(OnOtaChrootBootstrap(), 0); |
| UnmountOnTearDown(apex_path_1); |
| UnmountOnTearDown(apex_path_2); |
| |
| 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")); |
| |
| ASSERT_EQ(access("/apex/apex-info-list.xml", F_OK), 0); |
| auto info_list = |
| com::android::apex::readApexInfoList("/apex/apex-info-list.xml"); |
| ASSERT_TRUE(info_list.has_value()); |
| auto apex_info_xml_1 = com::android::apex::ApexInfo( |
| /* moduleName= */ "com.android.apex.test_package", |
| /* modulePath= */ apex_path_1, |
| /* preinstalledModulePath= */ apex_path_1, |
| /* versionCode= */ 1, /* versionName= */ "1", |
| /* isFactory= */ true, /* isActive= */ true); |
| auto apex_info_xml_2 = com::android::apex::ApexInfo( |
| /* moduleName= */ "com.android.apex.test_package_2", |
| /* modulePath= */ apex_path_2, /* preinstalledModulePath= */ apex_path_2, |
| /* versionCode= */ 1, /* versionName= */ "1", /* isFactory= */ true, |
| /* isActive= */ true); |
| ASSERT_THAT(info_list->getApexInfo(), |
| UnorderedElementsAre(ApexInfoXmlEq(apex_info_xml_1), |
| ApexInfoXmlEq(apex_info_xml_2))); |
| } |
| |
| TEST_F(ApexdMountTest, OnOtaChrootBootstrapFailsToScanPreInstalledApexes) { |
| AddPreInstalledApex("apex.apexd_test.apex"); |
| AddPreInstalledApex("apex.apexd_test_corrupt_superblock_apex.apex"); |
| |
| ASSERT_EQ(OnOtaChrootBootstrap(), 1); |
| } |
| |
| TEST_F(ApexdMountTest, OnOtaChrootBootstrapDataHasHigherVersion) { |
| std::string apex_path_1 = AddPreInstalledApex("apex.apexd_test.apex"); |
| std::string apex_path_2 = |
| AddPreInstalledApex("apex.apexd_test_different_app.apex"); |
| std::string apex_path_3 = AddDataApex("apex.apexd_test_v2.apex"); |
| |
| ASSERT_EQ(OnOtaChrootBootstrap(), 0); |
| |
| UnmountOnTearDown(apex_path_2); |
| UnmountOnTearDown(apex_path_3); |
| |
| auto apex_mounts = GetApexMounts(); |
| ASSERT_THAT(apex_mounts, |
| UnorderedElementsAre("/apex/com.android.apex.test_package", |
| "/apex/com.android.apex.test_package@2", |
| "/apex/com.android.apex.test_package_2", |
| "/apex/com.android.apex.test_package_2@1")); |
| |
| ASSERT_EQ(access("/apex/apex-info-list.xml", F_OK), 0); |
| auto info_list = |
| com::android::apex::readApexInfoList("/apex/apex-info-list.xml"); |
| ASSERT_TRUE(info_list.has_value()); |
| auto apex_info_xml_1 = com::android::apex::ApexInfo( |
| /* moduleName= */ "com.android.apex.test_package", |
| /* modulePath= */ apex_path_1, |
| /* preinstalledModulePath= */ apex_path_1, |
| /* versionCode= */ 1, /* versionName= */ "1", |
| /* isFactory= */ true, /* isActive= */ false); |
| auto apex_info_xml_2 = com::android::apex::ApexInfo( |
| /* moduleName= */ "com.android.apex.test_package_2", |
| /* modulePath= */ apex_path_2, /* preinstalledModulePath= */ apex_path_2, |
| /* versionCode= */ 1, /* versionName= */ "1", /* isFactory= */ true, |
| /* isActive= */ true); |
| auto apex_info_xml_3 = com::android::apex::ApexInfo( |
| /* moduleName= */ "com.android.apex.test_package", |
| /* modulePath= */ apex_path_3, |
| /* preinstalledModulePath= */ apex_path_1, |
| /* versionCode= */ 2, /* versionName= */ "2", |
| /* isFactory= */ false, /* isActive= */ true); |
| ASSERT_THAT(info_list->getApexInfo(), |
| UnorderedElementsAre(ApexInfoXmlEq(apex_info_xml_1), |
| ApexInfoXmlEq(apex_info_xml_2), |
| ApexInfoXmlEq(apex_info_xml_3))); |
| } |
| |
| TEST_F(ApexdMountTest, OnOtaChrootBootstrapDataHasSameVersion) { |
| std::string apex_path_1 = AddPreInstalledApex("apex.apexd_test.apex"); |
| std::string apex_path_2 = |
| AddPreInstalledApex("apex.apexd_test_different_app.apex"); |
| std::string apex_path_3 = AddDataApex("apex.apexd_test.apex"); |
| |
| ASSERT_EQ(OnOtaChrootBootstrap(), 0); |
| |
| UnmountOnTearDown(apex_path_2); |
| UnmountOnTearDown(apex_path_3); |
| |
| 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")); |
| |
| ASSERT_EQ(access("/apex/apex-info-list.xml", F_OK), 0); |
| auto info_list = |
| com::android::apex::readApexInfoList("/apex/apex-info-list.xml"); |
| ASSERT_TRUE(info_list.has_value()); |
| auto apex_info_xml_1 = com::android::apex::ApexInfo( |
| /* moduleName= */ "com.android.apex.test_package", |
| /* modulePath= */ apex_path_1, |
| /* preinstalledModulePath= */ apex_path_1, |
| /* versionCode= */ 1, /* versionName= */ "1", |
| /* isFactory= */ true, /* isActive= */ false); |
| auto apex_info_xml_2 = com::android::apex::ApexInfo( |
| /* moduleName= */ "com.android.apex.test_package_2", |
| /* modulePath= */ apex_path_2, /* preinstalledModulePath= */ apex_path_2, |
| /* versionCode= */ 1, /* versionName= */ "1", /* isFactory= */ true, |
| /* isActive= */ true); |
| auto apex_info_xml_3 = com::android::apex::ApexInfo( |
| /* moduleName= */ "com.android.apex.test_package", |
| /* modulePath= */ apex_path_3, |
| /* preinstalledModulePath= */ apex_path_1, |
| /* versionCode= */ 1, /* versionName= */ "1", |
| /* isFactory= */ false, /* isActive= */ true); |
| ASSERT_THAT(info_list->getApexInfo(), |
| UnorderedElementsAre(ApexInfoXmlEq(apex_info_xml_1), |
| ApexInfoXmlEq(apex_info_xml_2), |
| ApexInfoXmlEq(apex_info_xml_3))); |
| } |
| |
| TEST_F(ApexdMountTest, OnOtaChrootBootstrapSystemHasHigherVersion) { |
| std::string apex_path_1 = AddPreInstalledApex("apex.apexd_test_v2.apex"); |
| std::string apex_path_2 = |
| AddPreInstalledApex("apex.apexd_test_different_app.apex"); |
| AddDataApex("apex.apexd_test.apex"); |
| |
| ASSERT_EQ(OnOtaChrootBootstrap(), 0); |
| |
| UnmountOnTearDown(apex_path_1); |
| UnmountOnTearDown(apex_path_2); |
| |
| auto apex_mounts = GetApexMounts(); |
| ASSERT_THAT(apex_mounts, |
| UnorderedElementsAre("/apex/com.android.apex.test_package", |
| "/apex/com.android.apex.test_package@2", |
| "/apex/com.android.apex.test_package_2", |
| "/apex/com.android.apex.test_package_2@1")); |
| |
| ASSERT_EQ(access("/apex/apex-info-list.xml", F_OK), 0); |
| auto info_list = |
| com::android::apex::readApexInfoList("/apex/apex-info-list.xml"); |
| ASSERT_TRUE(info_list.has_value()); |
| auto apex_info_xml_1 = com::android::apex::ApexInfo( |
| /* moduleName= */ "com.android.apex.test_package", |
| /* modulePath= */ apex_path_1, |
| /* preinstalledModulePath= */ apex_path_1, |
| /* versionCode= */ 2, /* versionName= */ "2", |
| /* isFactory= */ true, /* isActive= */ true); |
| auto apex_info_xml_2 = com::android::apex::ApexInfo( |
| /* moduleName= */ "com.android.apex.test_package_2", |
| /* modulePath= */ apex_path_2, /* preinstalledModulePath= */ apex_path_2, |
| /* versionCode= */ 1, /* versionName= */ "1", /* isFactory= */ true, |
| /* isActive= */ true); |
| |
| ASSERT_THAT(info_list->getApexInfo(), |
| UnorderedElementsAre(ApexInfoXmlEq(apex_info_xml_1), |
| ApexInfoXmlEq(apex_info_xml_2))); |
| } |
| |
| TEST_F(ApexdMountTest, OnOtaChrootBootstrapDataHasSameVersionButDifferentKey) { |
| std::string apex_path_1 = AddPreInstalledApex("apex.apexd_test.apex"); |
| std::string apex_path_2 = |
| AddPreInstalledApex("apex.apexd_test_different_app.apex"); |
| AddDataApex("apex.apexd_test_different_key.apex"); |
| |
| ASSERT_EQ(OnOtaChrootBootstrap(), 0); |
| |
| UnmountOnTearDown(apex_path_1); |
| UnmountOnTearDown(apex_path_2); |
| |
| 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")); |
| |
| ASSERT_EQ(access("/apex/apex-info-list.xml", F_OK), 0); |
| auto info_list = |
| com::android::apex::readApexInfoList("/apex/apex-info-list.xml"); |
| ASSERT_TRUE(info_list.has_value()); |
| auto apex_info_xml_1 = com::android::apex::ApexInfo( |
| /* moduleName= */ "com.android.apex.test_package", |
| /* modulePath= */ apex_path_1, |
| /* preinstalledModulePath= */ apex_path_1, |
| /* versionCode= */ 1, /* versionName= */ "1", |
| /* isFactory= */ true, /* isActive= */ true); |
| auto apex_info_xml_2 = com::android::apex::ApexInfo( |
| /* moduleName= */ "com.android.apex.test_package_2", |
| /* modulePath= */ apex_path_2, /* preinstalledModulePath= */ apex_path_2, |
| /* versionCode= */ 1, /* versionName= */ "1", /* isFactory= */ true, |
| /* isActive= */ true); |
| |
| ASSERT_THAT(info_list->getApexInfo(), |
| UnorderedElementsAre(ApexInfoXmlEq(apex_info_xml_1), |
| ApexInfoXmlEq(apex_info_xml_2))); |
| } |
| |
| TEST_F(ApexdMountTest, |
| OnOtaChrootBootstrapDataHasHigherVersionButDifferentKey) { |
| std::string apex_path_1 = AddPreInstalledApex("apex.apexd_test.apex"); |
| std::string apex_path_2 = |
| AddPreInstalledApex("apex.apexd_test_different_app.apex"); |
| std::string apex_path_3 = |
| AddDataApex("apex.apexd_test_different_key_v2.apex"); |
| |
| { |
| auto apex = ApexFile::Open(apex_path_3); |
| ASSERT_TRUE(IsOk(apex)); |
| ASSERT_EQ(static_cast<uint64_t>(apex->GetManifest().version()), 2ULL); |
| } |
| |
| ASSERT_EQ(OnOtaChrootBootstrap(), 0); |
| |
| UnmountOnTearDown(apex_path_1); |
| UnmountOnTearDown(apex_path_2); |
| |
| 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")); |
| |
| ASSERT_EQ(access("/apex/apex-info-list.xml", F_OK), 0); |
| auto info_list = |
| com::android::apex::readApexInfoList("/apex/apex-info-list.xml"); |
| ASSERT_TRUE(info_list.has_value()); |
| auto apex_info_xml_1 = com::android::apex::ApexInfo( |
| /* moduleName= */ "com.android.apex.test_package", |
| /* modulePath= */ apex_path_1, |
| /* preinstalledModulePath= */ apex_path_1, |
| /* versionCode= */ 1, /* versionName= */ "1", |
| /* isFactory= */ true, /* isActive= */ true); |
| auto apex_info_xml_2 = com::android::apex::ApexInfo( |
| /* moduleName= */ "com.android.apex.test_package_2", |
| /* modulePath= */ apex_path_2, /* preinstalledModulePath= */ apex_path_2, |
| /* versionCode= */ 1, /* versionName= */ "1", /* isFactory= */ true, |
| /* isActive= */ true); |
| |
| ASSERT_THAT(info_list->getApexInfo(), |
| UnorderedElementsAre(ApexInfoXmlEq(apex_info_xml_1), |
| ApexInfoXmlEq(apex_info_xml_2))); |
| } |
| |
| TEST_F(ApexdMountTest, OnOtaChrootBootstrapDataApexWithoutPreInstalledApex) { |
| std::string apex_path_1 = AddPreInstalledApex("apex.apexd_test.apex"); |
| AddDataApex("apex.apexd_test_different_app.apex"); |
| |
| ASSERT_EQ(OnOtaChrootBootstrap(), 0); |
| |
| UnmountOnTearDown(apex_path_1); |
| |
| auto apex_mounts = GetApexMounts(); |
| ASSERT_THAT(apex_mounts, |
| UnorderedElementsAre("/apex/com.android.apex.test_package", |
| "/apex/com.android.apex.test_package@1")); |
| |
| ASSERT_EQ(access("/apex/apex-info-list.xml", F_OK), 0); |
| auto info_list = |
| com::android::apex::readApexInfoList("/apex/apex-info-list.xml"); |
| ASSERT_TRUE(info_list.has_value()); |
| auto apex_info_xml_1 = com::android::apex::ApexInfo( |
| /* moduleName= */ "com.android.apex.test_package", |
| /* modulePath= */ apex_path_1, |
| /* preinstalledModulePath= */ apex_path_1, |
| /* versionCode= */ 1, /* versionName= */ "1", |
| /* isFactory= */ true, /* isActive= */ true); |
| |
| ASSERT_THAT(info_list->getApexInfo(), |
| UnorderedElementsAre(ApexInfoXmlEq(apex_info_xml_1))); |
| } |
| |
| TEST_F(ApexdMountTest, OnOtaChrootBootstrapPreInstalledSharedLibsApex) { |
| std::string apex_path_1 = AddPreInstalledApex("apex.apexd_test.apex"); |
| std::string apex_path_2 = AddPreInstalledApex( |
| "com.android.apex.test.sharedlibs_generated.v1.libvX.apex"); |
| std::string apex_path_3 = AddDataApex("apex.apexd_test_v2.apex"); |
| |
| ASSERT_EQ(OnOtaChrootBootstrap(), 0); |
| |
| UnmountOnTearDown(apex_path_2); |
| UnmountOnTearDown(apex_path_3); |
| |
| auto apex_mounts = GetApexMounts(); |
| ASSERT_THAT(apex_mounts, |
| UnorderedElementsAre("/apex/com.android.apex.test_package", |
| "/apex/com.android.apex.test_package@2", |
| "/apex/com.android.apex.test.sharedlibs@1")); |
| |
| ASSERT_EQ(access("/apex/apex-info-list.xml", F_OK), 0); |
| auto info_list = |
| com::android::apex::readApexInfoList("/apex/apex-info-list.xml"); |
| ASSERT_TRUE(info_list.has_value()); |
| auto apex_info_xml_1 = com::android::apex::ApexInfo( |
| /* moduleName= */ "com.android.apex.test_package", |
| /* modulePath= */ apex_path_1, |
| /* preinstalledModulePath= */ apex_path_1, |
| /* versionCode= */ 1, /* versionName= */ "1", |
| /* isFactory= */ true, /* isActive= */ false); |
| auto apex_info_xml_2 = com::android::apex::ApexInfo( |
| /* moduleName= */ "com.android.apex.test.sharedlibs", |
| /* modulePath= */ apex_path_2, |
| /* preinstalledModulePath= */ apex_path_2, |
| /* versionCode= */ 1, /* versionName= */ "1", |
| /* isFactory= */ true, /* isActive= */ true); |
| auto apex_info_xml_3 = com::android::apex::ApexInfo( |
| /* moduleName= */ "com.android.apex.test_package", |
| /* modulePath= */ apex_path_3, |
| /* preinstalledModulePath= */ apex_path_1, |
| /* versionCode= */ 2, /* versionName= */ "2", |
| /* isFactory= */ false, /* isActive= */ true); |
| |
| ASSERT_THAT(info_list->getApexInfo(), |
| UnorderedElementsAre(ApexInfoXmlEq(apex_info_xml_1), |
| ApexInfoXmlEq(apex_info_xml_2), |
| ApexInfoXmlEq(apex_info_xml_3))); |
| |
| ASSERT_EQ(access("/apex/sharedlibs", F_OK), 0); |
| |
| // Check /apex/sharedlibs is populated properly. |
| std::vector<std::string> sharedlibs; |
| for (const auto& p : fs::recursive_directory_iterator("/apex/sharedlibs")) { |
| if (fs::is_symlink(p)) { |
| auto src = fs::read_symlink(p.path()); |
| ASSERT_EQ(p.path().filename(), src.filename()); |
| sharedlibs.push_back(p.path().parent_path().string() + "->" + |
| src.parent_path().string()); |
| } |
| } |
| |
| std::vector<std::string> expected = { |
| "/apex/sharedlibs/lib/libsharedlibtest.so->" |
| "/apex/com.android.apex.test.sharedlibs@1/lib/libsharedlibtest.so", |
| "/apex/sharedlibs/lib/libc++.so->" |
| "/apex/com.android.apex.test.sharedlibs@1/lib/libc++.so", |
| }; |
| |
| // On 64bit devices we also have lib64. |
| if (!GetProperty("ro.product.cpu.abilist64", "").empty()) { |
| expected.push_back( |
| "/apex/sharedlibs/lib64/libsharedlibtest.so->" |
| "/apex/com.android.apex.test.sharedlibs@1/lib64/libsharedlibtest.so"); |
| expected.push_back( |
| "/apex/sharedlibs/lib64/libc++.so->" |
| "/apex/com.android.apex.test.sharedlibs@1/lib64/libc++.so"); |
| } |
| ASSERT_THAT(sharedlibs, UnorderedElementsAreArray(expected)); |
| } |
| |
| TEST_F(ApexdMountTest, OnOtaChrootBootstrapSharedLibsApexBothVersions) { |
| std::string apex_path_1 = AddPreInstalledApex("apex.apexd_test.apex"); |
| std::string apex_path_2 = AddPreInstalledApex( |
| "com.android.apex.test.sharedlibs_generated.v1.libvX.apex"); |
| std::string apex_path_3 = AddDataApex("apex.apexd_test_v2.apex"); |
| std::string apex_path_4 = |
| AddDataApex("com.android.apex.test.sharedlibs_generated.v2.libvY.apex"); |
| |
| ASSERT_EQ(OnOtaChrootBootstrap(), 0); |
| |
| UnmountOnTearDown(apex_path_2); |
| UnmountOnTearDown(apex_path_3); |
| UnmountOnTearDown(apex_path_4); |
| |
| auto apex_mounts = GetApexMounts(); |
| ASSERT_THAT(apex_mounts, |
| UnorderedElementsAre("/apex/com.android.apex.test_package", |
| "/apex/com.android.apex.test_package@2", |
| "/apex/com.android.apex.test.sharedlibs@1", |
| "/apex/com.android.apex.test.sharedlibs@2")); |
| |
| ASSERT_EQ(access("/apex/apex-info-list.xml", F_OK), 0); |
| auto info_list = |
| com::android::apex::readApexInfoList("/apex/apex-info-list.xml"); |
| ASSERT_TRUE(info_list.has_value()); |
| auto apex_info_xml_1 = com::android::apex::ApexInfo( |
| /* moduleName= */ "com.android.apex.test_package", |
| /* modulePath= */ apex_path_1, |
| /* preinstalledModulePath= */ apex_path_1, |
| /* versionCode= */ 1, /* versionName= */ "1", |
| /* isFactory= */ true, /* isActive= */ false); |
| auto apex_info_xml_2 = com::android::apex::ApexInfo( |
| /* moduleName= */ "com.android.apex.test.sharedlibs", |
| /* modulePath= */ apex_path_2, |
| /* preinstalledModulePath= */ apex_path_2, |
| /* versionCode= */ 1, /* versionName= */ "1", |
| /* isFactory= */ true, /* isActive= */ false); |
| auto apex_info_xml_3 = com::android::apex::ApexInfo( |
| /* moduleName= */ "com.android.apex.test_package", |
| /* modulePath= */ apex_path_3, |
| /* preinstalledModulePath= */ apex_path_1, |
| /* versionCode= */ 2, /* versionName= */ "2", |
| /* isFactory= */ false, /* isActive= */ true); |
| auto apex_info_xml_4 = com::android::apex::ApexInfo( |
| /* moduleName= */ "com.android.apex.test.sharedlibs", |
| /* modulePath= */ apex_path_4, |
| /* preinstalledModulePath= */ apex_path_2, |
| /* versionCode= */ 2, /* versionName= */ "2", |
| /* isFactory= */ false, /* isActive= */ true); |
| |
| ASSERT_THAT(info_list->getApexInfo(), |
| UnorderedElementsAre(ApexInfoXmlEq(apex_info_xml_1), |
| ApexInfoXmlEq(apex_info_xml_2), |
| ApexInfoXmlEq(apex_info_xml_3), |
| ApexInfoXmlEq(apex_info_xml_4))); |
| |
| ASSERT_EQ(access("/apex/sharedlibs", F_OK), 0); |
| |
| // Check /apex/sharedlibs is populated properly. |
| // Because we don't want to hardcode full paths (they are pretty long and have |
| // a hash in them which might change if new prebuilts are dropped in), the |
| // assertion logic is a little bit clunky. |
| std::vector<std::string> sharedlibs; |
| for (const auto& p : fs::recursive_directory_iterator("/apex/sharedlibs")) { |
| if (fs::is_symlink(p)) { |
| auto src = fs::read_symlink(p.path()); |
| ASSERT_EQ(p.path().filename(), src.filename()); |
| sharedlibs.push_back(p.path().parent_path().string() + "->" + |
| src.parent_path().string()); |
| } |
| } |
| |
| std::vector<std::string> expected = { |
| "/apex/sharedlibs/lib/libsharedlibtest.so->" |
| "/apex/com.android.apex.test.sharedlibs@2/lib/libsharedlibtest.so", |
| "/apex/sharedlibs/lib/libsharedlibtest.so->" |
| "/apex/com.android.apex.test.sharedlibs@1/lib/libsharedlibtest.so", |
| "/apex/sharedlibs/lib/libc++.so->" |
| "/apex/com.android.apex.test.sharedlibs@1/lib/libc++.so", |
| }; |
| // On 64bit devices we also have lib64. |
| if (!GetProperty("ro.product.cpu.abilist64", "").empty()) { |
| expected.push_back( |
| "/apex/sharedlibs/lib64/libsharedlibtest.so->" |
| "/apex/com.android.apex.test.sharedlibs@2/lib64/libsharedlibtest.so"); |
| expected.push_back( |
| "/apex/sharedlibs/lib64/libsharedlibtest.so->" |
| "/apex/com.android.apex.test.sharedlibs@1/lib64/libsharedlibtest.so"); |
| expected.push_back( |
| "/apex/sharedlibs/lib64/libc++.so->" |
| "/apex/com.android.apex.test.sharedlibs@1/lib64/libc++.so"); |
| } |
| |
| ASSERT_THAT(sharedlibs, UnorderedElementsAreArray(expected)); |
| } |
| |
| // Test when we move from uncompressed APEX to CAPEX via ota |
| TEST_F(ApexdMountTest, OnOtaChrootBootstrapOnlyCompressedApexes) { |
| std::string apex_path = |
| AddPreInstalledApex("com.android.apex.compressed.v1.capex"); |
| |
| ASSERT_EQ(OnOtaChrootBootstrap(), 0); |
| |
| // Decompressed APEX should be mounted from decompression_dir |
| std::string decompressed_apex = |
| StringPrintf("%s/com.android.apex.compressed@1%s", |
| GetDecompressionDir().c_str(), kOtaApexPackageSuffix); |
| UnmountOnTearDown(decompressed_apex); |
| |
| auto apex_mounts = GetApexMounts(); |
| ASSERT_THAT(apex_mounts, |
| UnorderedElementsAre("/apex/com.android.apex.compressed", |
| "/apex/com.android.apex.compressed@1")); |
| |
| ASSERT_EQ(access("/apex/apex-info-list.xml", F_OK), 0); |
| auto info_list = |
| com::android::apex::readApexInfoList("/apex/apex-info-list.xml"); |
| ASSERT_TRUE(info_list.has_value()); |
| auto apex_info_xml_decompressed = com::android::apex::ApexInfo( |
| /* moduleName= */ "com.android.apex.compressed", |
| /* modulePath= */ decompressed_apex, |
| /* preinstalledModulePath= */ apex_path, |
| /* versionCode= */ 1, /* versionName= */ "1", |
| /* isFactory= */ true, /* isActive= */ true); |
| ASSERT_THAT(info_list->getApexInfo(), |
| UnorderedElementsAre(ApexInfoXmlEq(apex_info_xml_decompressed))); |
| auto& db = GetApexDatabaseForTesting(); |
| // Check that it was mounted from decompressed apex. It should also be mounted |
| // on dm-verity device. |
| db.ForallMountedApexes("com.android.apex.compressed", |
| [&](const MountedApexData& data, bool latest) { |
| ASSERT_TRUE(latest); |
| ASSERT_EQ(data.full_path, decompressed_apex); |
| ASSERT_EQ(data.device_name, |
| "com.android.apex.compressed@1.chroot"); |
| }); |
| } |
| |
| // Test we decompress only once even if OnOtaChrootBootstrap is called multiple |
| // times |
| TEST_F(ApexdMountTest, OnOtaChrootBootstrapDecompressOnlyOnceMultipleCalls) { |
| std::string apex_path = |
| AddPreInstalledApex("com.android.apex.compressed.v1.capex"); |
| |
| ASSERT_EQ(OnOtaChrootBootstrap(), 0); |
| |
| // Decompressed APEX should be mounted from decompression_dir |
| std::string decompressed_apex = |
| StringPrintf("%s/com.android.apex.compressed@1%s", |
| GetDecompressionDir().c_str(), kOtaApexPackageSuffix); |
| UnmountOnTearDown(decompressed_apex); |
| |
| // Capture the creation time of the decompressed APEX |
| std::error_code ec; |
| auto last_write_time_1 = fs::last_write_time(decompressed_apex, ec); |
| ASSERT_FALSE(ec) << "Failed to capture last write time of " |
| << decompressed_apex; |
| |
| // Call OnOtaChrootBootstrap again. Since we do not hardlink decompressed APEX |
| // to /data/apex/active directory when in chroot, when selecting apex for |
| // activation, we will end up selecting compressed APEX again. |
| ASSERT_EQ(OnOtaChrootBootstrap(), 0); |
| |
| // Compare write time to ensure we did not decompress again |
| auto last_write_time_2 = fs::last_write_time(decompressed_apex, ec); |
| ASSERT_FALSE(ec) << "Failed to capture last write time of " |
| << decompressed_apex << ec.message(); |
| ASSERT_EQ(last_write_time_1, last_write_time_2); |
| } |
| |
| // Test when we upgrade existing CAPEX to higher version via OTA |
| TEST_F(ApexdMountTest, OnOtaChrootBootstrapUpgradeCapex) { |
| TemporaryDir previous_built_in_dir; |
| PrepareCompressedApex("com.android.apex.compressed.v1.capex", |
| previous_built_in_dir.path); |
| // Place a higher version capex in current built_in_dir |
| std::string apex_path = |
| AddPreInstalledApex("com.android.apex.compressed.v2.capex"); |
| |
| ASSERT_EQ(OnOtaChrootBootstrap(), 0); |
| |
| // Upgraded decompressed APEX should be mounted from decompression dir |
| std::string decompressed_active_apex = |
| StringPrintf("%s/com.android.apex.compressed@2%s", |
| GetDecompressionDir().c_str(), kOtaApexPackageSuffix); |
| UnmountOnTearDown(decompressed_active_apex); |
| |
| auto apex_mounts = GetApexMounts(); |
| ASSERT_THAT(apex_mounts, |
| UnorderedElementsAre("/apex/com.android.apex.compressed", |
| "/apex/com.android.apex.compressed@2")); |
| |
| ASSERT_EQ(access("/apex/apex-info-list.xml", F_OK), 0); |
| auto info_list = |
| com::android::apex::readApexInfoList("/apex/apex-info-list.xml"); |
| ASSERT_TRUE(info_list.has_value()); |
| auto apex_info_xml_decompressed = com::android::apex::ApexInfo( |
| /* moduleName= */ "com.android.apex.compressed", |
| /* modulePath= */ decompressed_active_apex, |
| /* preinstalledModulePath= */ apex_path, |
| /* versionCode= */ 2, /* versionName= */ "2", |
| /* isFactory= */ true, /* isActive= */ true); |
| ASSERT_THAT(info_list->getApexInfo(), |
| UnorderedElementsAre(ApexInfoXmlEq(apex_info_xml_decompressed))); |
| auto& db = GetApexDatabaseForTesting(); |
| // Check that it was mounted from decompressed apex. It should also be mounted |
| // on dm-verity device. |
| db.ForallMountedApexes("com.android.apex.compressed", |
| [&](const MountedApexData& data, bool latest) { |
| ASSERT_TRUE(latest); |
| ASSERT_EQ(data.full_path, decompressed_active_apex); |
| ASSERT_EQ(data.device_name, |
| "com.android.apex.compressed@2.chroot"); |
| }); |
| } |
| |
| // Test when we update existing CAPEX to same version via OTA |
| TEST_F(ApexdMountTest, OnOtaChrootBootstrapSamegradeCapex) { |
| TemporaryDir previous_built_in_dir; |
| PrepareCompressedApex("com.android.apex.compressed.v1.capex", |
| previous_built_in_dir.path); |
| // Place a same version capex in current built_in_dir, under a different name |
| auto apex_path = |
| StringPrintf("%s/different-name.capex", GetBuiltInDir().c_str()); |
| fs::copy(GetTestFile("com.android.apex.compressed.v1.capex"), apex_path); |
| |
| ASSERT_EQ(OnOtaChrootBootstrap(), 0); |
| |
| // Previously decompressed APEX should be mounted from decompression_dir |
| std::string decompressed_active_apex = StringPrintf( |
| "%s/com.android.apex.compressed@1%s", GetDecompressionDir().c_str(), |
| kDecompressedApexPackageSuffix); |
| UnmountOnTearDown(decompressed_active_apex); |
| |
| auto apex_mounts = GetApexMounts(); |
| ASSERT_THAT(apex_mounts, |
| UnorderedElementsAre("/apex/com.android.apex.compressed", |
| "/apex/com.android.apex.compressed@1")); |
| |
| ASSERT_EQ(access("/apex/apex-info-list.xml", F_OK), 0); |
| auto info_list = |
| com::android::apex::readApexInfoList("/apex/apex-info-list.xml"); |
| ASSERT_TRUE(info_list.has_value()); |
| auto apex_info_xml_decompressed = com::android::apex::ApexInfo( |
| /* moduleName= */ "com.android.apex.compressed", |
| /* modulePath= */ decompressed_active_apex, |
| /* preinstalledModulePath= */ apex_path, |
| /* versionCode= */ 1, /* versionName= */ "1", |
| /* isFactory= */ true, /* isActive= */ true); |
| ASSERT_THAT(info_list->getApexInfo(), |
| UnorderedElementsAre(ApexInfoXmlEq(apex_info_xml_decompressed))); |
| auto& db = GetApexDatabaseForTesting(); |
| // Check that it was mounted from decompressed apex. It should also be mounted |
| // on dm-verity device. |
| db.ForallMountedApexes("com.android.apex.compressed", |
| [&](const MountedApexData& data, bool latest) { |
| ASSERT_TRUE(latest); |
| ASSERT_EQ(data.full_path, decompressed_active_apex); |
| ASSERT_EQ(data.device_name, |
| "com.android.apex.compressed@1.chroot"); |
| }); |
| } |
| |
| // Test when we update existing CAPEX to same version, but different key via OTA |
| TEST_F(ApexdMountTest, OnOtaChrootBootstrapSamegradeCapexDifferentKey) { |
| TemporaryDir previous_built_in_dir; |
| PrepareCompressedApex("com.android.apex.compressed_different_key.capex", |
| previous_built_in_dir.path); |
| // Place a same version capex in current built_in_dir, which has different key |
| auto apex_path = AddPreInstalledApex("com.android.apex.compressed.v1.capex"); |
| |
| ASSERT_EQ(OnOtaChrootBootstrap(), 0); |
| |
| // New decompressed APEX should be mounted from ota_reserved directory |
| std::string decompressed_active_apex = |
| StringPrintf("%s/com.android.apex.compressed@1%s", |
| GetDecompressionDir().c_str(), kOtaApexPackageSuffix); |
| UnmountOnTearDown(decompressed_active_apex); |
| |
| auto apex_mounts = GetApexMounts(); |
| ASSERT_THAT(apex_mounts, |
| UnorderedElementsAre("/apex/com.android.apex.compressed", |
| "/apex/com.android.apex.compressed@1")); |
| |
| ASSERT_EQ(access("/apex/apex-info-list.xml", F_OK), 0); |
| auto info_list = |
| com::android::apex::readApexInfoList("/apex/apex-info-list.xml"); |
| ASSERT_TRUE(info_list.has_value()); |
| auto apex_info_xml_decompressed = com::android::apex::ApexInfo( |
| /* moduleName= */ "com.android.apex.compressed", |
| /* modulePath= */ decompressed_active_apex, |
| /* preinstalledModulePath= */ apex_path, |
| /* versionCode= */ 1, /* versionName= */ "1", |
| /* isFactory= */ true, /* isActive= */ true); |
| ASSERT_THAT(info_list->getApexInfo(), |
| UnorderedElementsAre(ApexInfoXmlEq(apex_info_xml_decompressed))); |
| auto& db = GetApexDatabaseForTesting(); |
| // Check that it was mounted from decompressed apex. It should also be mounted |
| // on dm-verity device. |
| db.ForallMountedApexes("com.android.apex.compressed", |
| [&](const MountedApexData& data, bool latest) { |
| ASSERT_TRUE(latest); |
| ASSERT_EQ(data.full_path, decompressed_active_apex); |
| ASSERT_EQ(data.device_name, |
| "com.android.apex.compressed@1.chroot"); |
| }); |
| } |
| |
| // Test when we remove CAPEX via OTA |
| TEST_F(ApexdMountTest, OnOtaChrootBootstrapCapexToApex) { |
| TemporaryDir previous_built_in_dir; |
| PrepareCompressedApex("com.android.apex.compressed.v1.capex", |
| previous_built_in_dir.path); |
| // Place a uncompressed version apex in current built_in_dir |
| std::string apex_path = |
| AddPreInstalledApex("com.android.apex.compressed.v1_original.apex"); |
| |
| ASSERT_EQ(OnOtaChrootBootstrap(), 0); |
| |
| // New uncompressed APEX should be mounted |
| UnmountOnTearDown(apex_path); |
| |
| auto apex_mounts = GetApexMounts(); |
| ASSERT_THAT(apex_mounts, |
| UnorderedElementsAre("/apex/com.android.apex.compressed", |
| "/apex/com.android.apex.compressed@1")); |
| |
| ASSERT_EQ(access("/apex/apex-info-list.xml", F_OK), 0); |
| auto info_list = |
| com::android::apex::readApexInfoList("/apex/apex-info-list.xml"); |
| ASSERT_TRUE(info_list.has_value()); |
| auto apex_info_xml_uncompressed = com::android::apex::ApexInfo( |
| /* moduleName= */ "com.android.apex.compressed", |
| /* modulePath= */ apex_path, |
| /* preinstalledModulePath= */ apex_path, |
| /* versionCode= */ 1, /* versionName= */ "1", |
| /* isFactory= */ true, /* isActive= */ true); |
| ASSERT_THAT(info_list->getApexInfo(), |
| UnorderedElementsAre(ApexInfoXmlEq(apex_info_xml_uncompressed))); |
| } |
| |
| TEST_F(ApexdMountTest, |
| OnOtaChrootBootstrapDecompressedApexVersionDifferentThanCapex) { |
| TemporaryDir previous_built_in_dir; |
| PrepareCompressedApex("com.android.apex.compressed.v2.capex", |
| previous_built_in_dir.path); |
| // Place a lower version capex in current built_in_dir, so that previously |
| // decompressed APEX has higher version but still doesn't get picked during |
| // selection. |
| std::string apex_path = |
| AddPreInstalledApex("com.android.apex.compressed.v1.capex"); |
| |
| ASSERT_EQ(OnOtaChrootBootstrap(), 0); |
| |
| // Pre-installed CAPEX should be decompressed again and mounted from |
| // decompression_dir |
| std::string decompressed_active_apex = |
| StringPrintf("%s/com.android.apex.compressed@1%s", |
| GetDecompressionDir().c_str(), kOtaApexPackageSuffix); |
| UnmountOnTearDown(decompressed_active_apex); |
| |
| auto apex_mounts = GetApexMounts(); |
| ASSERT_THAT(apex_mounts, |
| UnorderedElementsAre("/apex/com.android.apex.compressed", |
| "/apex/com.android.apex.compressed@1")); |
| |
| ASSERT_EQ(access("/apex/apex-info-list.xml", F_OK), 0); |
| auto info_list = |
| com::android::apex::readApexInfoList("/apex/apex-info-list.xml"); |
| ASSERT_TRUE(info_list.has_value()); |
| auto apex_info_xml_decompressed = com::android::apex::ApexInfo( |
| /* moduleName= */ "com.android.apex.compressed", |
| /* modulePath= */ decompressed_active_apex, |
| /* preinstalledModulePath= */ apex_path, |
| /* versionCode= */ 1, /* versionName= */ "1", |
| /* isFactory= */ true, /* isActive= */ true); |
| ASSERT_THAT(info_list->getApexInfo(), |
| UnorderedElementsAre(ApexInfoXmlEq(apex_info_xml_decompressed))); |
| } |
| |
| // Test when we update CAPEX and there is a higher version present in data |
| TEST_F(ApexdMountTest, OnOtaChrootBootstrapDataHigherThanCapex) { |
| auto system_apex_path = |
| PrepareCompressedApex("com.android.apex.compressed.v1.capex"); |
| auto data_apex_path = |
| AddDataApex("com.android.apex.compressed.v2_original.apex"); |
| |
| ASSERT_EQ(OnOtaChrootBootstrap(), 0); |
| |
| // Data APEX should be mounted |
| UnmountOnTearDown(data_apex_path); |
| |
| auto apex_mounts = GetApexMounts(); |
| ASSERT_THAT(apex_mounts, |
| UnorderedElementsAre("/apex/com.android.apex.compressed", |
| "/apex/com.android.apex.compressed@2")); |
| |
| ASSERT_EQ(access("/apex/apex-info-list.xml", F_OK), 0); |
| auto info_list = |
| com::android::apex::readApexInfoList("/apex/apex-info-list.xml"); |
| ASSERT_TRUE(info_list.has_value()); |
| auto apex_info_xml_data = com::android::apex::ApexInfo( |
| /* moduleName= */ "com.android.apex.compressed", |
| /* modulePath= */ data_apex_path, |
| /* preinstalledModulePath= */ system_apex_path, |
| /* versionCode= */ 2, /* versionName= */ "2", |
| /* isFactory= */ false, /* isActive= */ true); |
| auto apex_info_xml_system = com::android::apex::ApexInfo( |
| /* moduleName= */ "com.android.apex.compressed", |
| /* modulePath= */ system_apex_path, |
| /* preinstalledModulePath= */ system_apex_path, |
| /* versionCode= */ 1, /* versionName= */ "1", |
| /* isFactory= */ true, /* isActive= */ false); |
| ASSERT_THAT(info_list->getApexInfo(), |
| UnorderedElementsAre(ApexInfoXmlEq(apex_info_xml_data), |
| ApexInfoXmlEq(apex_info_xml_system))); |
| auto& db = GetApexDatabaseForTesting(); |
| // Check that it was mounted from decompressed apex. It should also be mounted |
| // on dm-verity device. |
| db.ForallMountedApexes("com.android.apex.compressed", |
| [&](const MountedApexData& data, bool latest) { |
| ASSERT_TRUE(latest); |
| ASSERT_EQ(data.full_path, data_apex_path); |
| ASSERT_EQ(data.device_name, |
| "com.android.apex.compressed@2.chroot"); |
| }); |
| } |
| |
| // Test when we update CAPEX and there is a lower version present in data |
| TEST_F(ApexdMountTest, OnOtaChrootBootstrapDataLowerThanCapex) { |
| auto apex_path = AddPreInstalledApex("com.android.apex.compressed.v2.capex"); |
| AddDataApex("com.android.apex.compressed.v1_original.apex"); |
| |
| ASSERT_EQ(OnOtaChrootBootstrap(), 0); |
| |
| // Decompressed APEX should be mounted from reserved dir |
| std::string decompressed_active_apex = |
| StringPrintf("%s/com.android.apex.compressed@2%s", |
| GetDecompressionDir().c_str(), kOtaApexPackageSuffix); |
| UnmountOnTearDown(decompressed_active_apex); |
| |
| auto apex_mounts = GetApexMounts(); |
| ASSERT_THAT(apex_mounts, |
| UnorderedElementsAre("/apex/com.android.apex.compressed", |
| "/apex/com.android.apex.compressed@2")); |
| |
| ASSERT_EQ(access("/apex/apex-info-list.xml", F_OK), 0); |
| auto info_list = |
| com::android::apex::readApexInfoList("/apex/apex-info-list.xml"); |
| ASSERT_TRUE(info_list.has_value()); |
| auto apex_info_xml = com::android::apex::ApexInfo( |
| /* moduleName= */ "com.android.apex.compressed", |
| /* modulePath= */ decompressed_active_apex, |
| /* preinstalledModulePath= */ apex_path, |
| /* versionCode= */ 2, /* versionName= */ "2", |
| /* isFactory= */ true, /* isActive= */ true); |
| ASSERT_THAT(info_list->getApexInfo(), |
| UnorderedElementsAre(ApexInfoXmlEq(apex_info_xml))); |
| auto& db = GetApexDatabaseForTesting(); |
| // Check that it was mounted from decompressed apex. It should also be mounted |
| // on dm-verity device. |
| db.ForallMountedApexes("com.android.apex.compressed", |
| [&](const MountedApexData& data, bool latest) { |
| ASSERT_TRUE(latest); |
| ASSERT_EQ(data.full_path, decompressed_active_apex); |
| ASSERT_EQ(data.device_name, |
| "com.android.apex.compressed@2.chroot"); |
| }); |
| } |
| |
| // Test when we update CAPEX and there is a higher version present in data |
| TEST_F(ApexdMountTest, OnOtaChrootBootstrapDataSameAsCapex) { |
| auto system_apex_path = |
| PrepareCompressedApex("com.android.apex.compressed.v1.capex"); |
| auto data_apex_path = |
| AddDataApex("com.android.apex.compressed.v1_original.apex"); |
| |
| ASSERT_EQ(OnOtaChrootBootstrap(), 0); |
| |
| // Data APEX should be mounted |
| UnmountOnTearDown(data_apex_path); |
| |
| auto apex_mounts = GetApexMounts(); |
| ASSERT_THAT(apex_mounts, |
| UnorderedElementsAre("/apex/com.android.apex.compressed", |
| "/apex/com.android.apex.compressed@1")); |
| |
| ASSERT_EQ(access("/apex/apex-info-list.xml", F_OK), 0); |
| auto info_list = |
| com::android::apex::readApexInfoList("/apex/apex-info-list.xml"); |
| ASSERT_TRUE(info_list.has_value()); |
| auto apex_info_xml_data = com::android::apex::ApexInfo( |
| /* moduleName= */ "com.android.apex.compressed", |
| /* modulePath= */ data_apex_path, |
| /* preinstalledModulePath= */ system_apex_path, |
| /* versionCode= */ 1, /* versionName= */ "1", |
| /* isFactory= */ false, /* isActive= */ true); |
| auto apex_info_xml_system = com::android::apex::ApexInfo( |
| /* moduleName= */ "com.android.apex.compressed", |
| /* modulePath= */ system_apex_path, |
| /* preinstalledModulePath= */ system_apex_path, |
| /* versionCode= */ 1, /* versionName= */ "1", |
| /* isFactory= */ true, /* isActive= */ false); |
| ASSERT_THAT(info_list->getApexInfo(), |
| UnorderedElementsAre(ApexInfoXmlEq(apex_info_xml_data), |
| ApexInfoXmlEq(apex_info_xml_system))); |
| auto& db = GetApexDatabaseForTesting(); |
| // Check that it was mounted from decompressed apex. It should also be mounted |
| // on dm-verity device. |
| db.ForallMountedApexes("com.android.apex.compressed", |
| [&](const MountedApexData& data, bool latest) { |
| ASSERT_TRUE(latest); |
| ASSERT_EQ(data.full_path, data_apex_path); |
| ASSERT_EQ(data.device_name, |
| "com.android.apex.compressed@1.chroot"); |
| }); |
| } |
| |
| TEST_F(ApexdMountTest, OnOtaChrootBootstrapDataHasDifferentKeyThanCapex) { |
| AddDataApex("com.android.apex.compressed_different_key.capex"); |
| // Place a same version capex in current built_in_dir, which has different key |
| auto apex_path = AddPreInstalledApex("com.android.apex.compressed.v1.capex"); |
| |
| ASSERT_EQ(OnOtaChrootBootstrap(), 0); |
| |
| // New decompressed APEX should be mounted from ota_reserved directory |
| std::string decompressed_active_apex = |
| StringPrintf("%s/com.android.apex.compressed@1%s", |
| GetDecompressionDir().c_str(), kOtaApexPackageSuffix); |
| UnmountOnTearDown(decompressed_active_apex); |
| |
| auto apex_mounts = GetApexMounts(); |
| ASSERT_THAT(apex_mounts, |
| UnorderedElementsAre("/apex/com.android.apex.compressed", |
| "/apex/com.android.apex.compressed@1")); |
| |
| ASSERT_EQ(access("/apex/apex-info-list.xml", F_OK), 0); |
| auto info_list = |
| com::android::apex::readApexInfoList("/apex/apex-info-list.xml"); |
| ASSERT_TRUE(info_list.has_value()); |
| auto apex_info_xml_decompressed = com::android::apex::ApexInfo( |
| /* moduleName= */ "com.android.apex.compressed", |
| /* modulePath= */ decompressed_active_apex, |
| /* preinstalledModulePath= */ apex_path, |
| /* versionCode= */ 1, /* versionName= */ "1", |
| /* isFactory= */ true, /* isActive= */ true); |
| ASSERT_THAT(info_list->getApexInfo(), |
| UnorderedElementsAre(ApexInfoXmlEq(apex_info_xml_decompressed))); |
| auto& db = GetApexDatabaseForTesting(); |
| // Check that it was mounted from decompressed apex. It should also be mounted |
| // on dm-verity device. |
| db.ForallMountedApexes("com.android.apex.compressed", |
| [&](const MountedApexData& data, bool latest) { |
| ASSERT_TRUE(latest); |
| ASSERT_EQ(data.full_path, decompressed_active_apex); |
| ASSERT_EQ(data.device_name, |
| "com.android.apex.compressed@1.chroot"); |
| }); |
| } |
| |
| static std::string GetSelinuxContext(const std::string& file) { |
| char* ctx; |
| if (getfilecon(file.c_str(), &ctx) < 0) { |
| PLOG(ERROR) << "Failed to getfilecon " << file; |
| return ""; |
| } |
| std::string result(ctx); |
| freecon(ctx); |
| return result; |
| } |
| |
| TEST_F(ApexdMountTest, OnOtaChrootBootstrapSelinuxLabelsAreCorrect) { |
| std::string apex_path_1 = AddPreInstalledApex("apex.apexd_test.apex"); |
| std::string apex_path_2 = AddPreInstalledApex( |
| "com.android.apex.test.sharedlibs_generated.v1.libvX.apex"); |
| std::string apex_path_3 = AddDataApex("apex.apexd_test_v2.apex"); |
| |
| ASSERT_EQ(OnOtaChrootBootstrap(), 0); |
| |
| EXPECT_EQ(GetSelinuxContext("/apex/apex-info-list.xml"), |
| "u:object_r:apex_info_file:s0"); |
| |
| EXPECT_EQ(GetSelinuxContext("/apex/sharedlibs"), |
| "u:object_r:apex_mnt_dir:s0"); |
| |
| EXPECT_EQ(GetSelinuxContext("/apex/com.android.apex.test_package"), |
| "u:object_r:system_file:s0"); |
| EXPECT_EQ(GetSelinuxContext("/apex/com.android.apex.test_package@2"), |
| "u:object_r:system_file:s0"); |
| } |
| |
| TEST_F(ApexdMountTest, OnOtaChrootBootstrapDmDevicesHaveCorrectName) { |
| std::string apex_path_1 = AddPreInstalledApex("apex.apexd_test.apex"); |
| std::string apex_path_2 = |
| AddPreInstalledApex("apex.apexd_test_different_app.apex"); |
| std::string apex_path_3 = AddDataApex("apex.apexd_test_v2.apex"); |
| |
| ASSERT_EQ(OnOtaChrootBootstrap(), 0); |
| UnmountOnTearDown(apex_path_2); |
| UnmountOnTearDown(apex_path_3); |
| |
| MountedApexDatabase& db = GetApexDatabaseForTesting(); |
| // com.android.apex.test_package_2 should be mounted directly on top of loop |
| // device. |
| db.ForallMountedApexes("com.android.apex.test_package_2", |
| [&](const MountedApexData& data, bool latest) { |
| ASSERT_TRUE(latest); |
| ASSERT_THAT(data.device_name, IsEmpty()); |
| ASSERT_THAT(data.loop_name, StartsWith("/dev")); |
| }); |
| // com.android.apex.test_package should be mounted on top of dm-verity device. |
| db.ForallMountedApexes("com.android.apex.test_package", |
| [&](const MountedApexData& data, bool latest) { |
| ASSERT_TRUE(latest); |
| ASSERT_EQ(data.device_name, |
| "com.android.apex.test_package@2.chroot"); |
| ASSERT_THAT(data.loop_name, StartsWith("/dev")); |
| }); |
| } |
| |
| TEST_F(ApexdMountTest, |
| OnOtaChrootBootstrapFailsToActivatePreInstalledApexKeepsGoing) { |
| std::string apex_path_1 = |
| AddPreInstalledApex("apex.apexd_test_manifest_mismatch.apex"); |
| std::string apex_path_2 = |
| AddPreInstalledApex("apex.apexd_test_different_app.apex"); |
| |
| ASSERT_EQ(OnOtaChrootBootstrap(), 0); |
| UnmountOnTearDown(apex_path_2); |
| |
| auto apex_mounts = GetApexMounts(); |
| ASSERT_THAT(apex_mounts, |
| UnorderedElementsAre("/apex/com.android.apex.test_package_2", |
| "/apex/com.android.apex.test_package_2@1")); |
| |
| ASSERT_EQ(access("/apex/apex-info-list.xml", F_OK), 0); |
| auto info_list = |
| com::android::apex::readApexInfoList("/apex/apex-info-list.xml"); |
| ASSERT_TRUE(info_list.has_value()); |
| auto apex_info_xml_1 = com::android::apex::ApexInfo( |
| /* moduleName= */ "com.android.apex.test_package", |
| /* modulePath= */ apex_path_1, |
| /* preinstalledModulePath= */ apex_path_1, |
| /* versionCode= */ 137, /* versionName= */ "1", |
| /* isFactory= */ true, /* isActive= */ false); |
| auto apex_info_xml_2 = com::android::apex::ApexInfo( |
| /* moduleName= */ "com.android.apex.test_package_2", |
| /* modulePath= */ apex_path_2, /* preinstalledModulePath= */ apex_path_2, |
| /* versionCode= */ 1, /* versionName= */ "1", /* isFactory= */ true, |
| /* isActive= */ true); |
| |
| ASSERT_THAT(info_list->getApexInfo(), |
| UnorderedElementsAre(ApexInfoXmlEq(apex_info_xml_1), |
| ApexInfoXmlEq(apex_info_xml_2))); |
| } |
| |
| TEST_F(ApexdMountTest, |
| OnOtaChrootBootstrapFailsToActivateDataApexFallsBackToPreInstalled) { |
| std::string apex_path_1 = AddPreInstalledApex("apex.apexd_test.apex"); |
| std::string apex_path_2 = |
| AddPreInstalledApex("apex.apexd_test_different_app.apex"); |
| std::string apex_path_3 = |
| AddDataApex("apex.apexd_test_manifest_mismatch.apex"); |
| |
| ASSERT_EQ(OnOtaChrootBootstrap(), 0); |
| UnmountOnTearDown(apex_path_1); |
| UnmountOnTearDown(apex_path_2); |
| |
| 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")); |
| |
| ASSERT_EQ(access("/apex/apex-info-list.xml", F_OK), 0); |
| auto info_list = |
| com::android::apex::readApexInfoList("/apex/apex-info-list.xml"); |
| ASSERT_TRUE(info_list.has_value()); |
| auto apex_info_xml_1 = com::android::apex::ApexInfo( |
| /* moduleName= */ "com.android.apex.test_package", |
| /* modulePath= */ apex_path_1, |
| /* preinstalledModulePath= */ apex_path_1, |
| /* versionCode= */ 1, /* versionName= */ "1", |
| /* isFactory= */ true, /* isActive= */ true); |
| auto apex_info_xml_2 = com::android::apex::ApexInfo( |
| /* moduleName= */ "com.android.apex.test_package_2", |
| /* modulePath= */ apex_path_2, /* preinstalledModulePath= */ apex_path_2, |
| /* versionCode= */ 1, /* versionName= */ "1", /* isFactory= */ true, |
| /* isActive= */ true); |
| |
| ASSERT_THAT(info_list->getApexInfo(), |
| UnorderedElementsAre(ApexInfoXmlEq(apex_info_xml_1), |
| ApexInfoXmlEq(apex_info_xml_2))); |
| } |
| |
| TEST_F(ApexdMountTest, OnOtaChrootBootstrapFlattenedApex) { |
| std::string apex_dir_1 = GetBuiltInDir() + "/com.android.apex.test_package"; |
| std::string apex_dir_2 = GetBuiltInDir() + "/com.android.apex.test_package_2"; |
| |
| ASSERT_EQ(mkdir(apex_dir_1.c_str(), 0755), 0); |
| ASSERT_EQ(mkdir(apex_dir_2.c_str(), 0755), 0); |
| |
| auto write_manifest_fn = [&](const std::string& apex_dir, |
| const std::string& module_name, int version) { |
| using ::apex::proto::ApexManifest; |
| |
| ApexManifest manifest; |
| manifest.set_name(module_name); |
| manifest.set_version(version); |
| manifest.set_versionname(std::to_string(version)); |
| |
| std::string out; |
| manifest.SerializeToString(&out); |
| ASSERT_TRUE(WriteStringToFile(out, apex_dir + "/apex_manifest.pb")); |
| }; |
| |
| write_manifest_fn(apex_dir_1, "com.android.apex.test_package", 2); |
| write_manifest_fn(apex_dir_2, "com.android.apex.test_package_2", 1); |
| |
| ASSERT_EQ(OnOtaChrootBootstrapFlattenedApex(), 0); |
| |
| auto apex_mounts = GetApexMounts(); |
| ASSERT_THAT(apex_mounts, |
| UnorderedElementsAre("/apex/com.android.apex.test_package", |
| "/apex/com.android.apex.test_package_2")); |
| |
| ASSERT_EQ(access("/apex/apex-info-list.xml", F_OK), 0); |
| ASSERT_EQ(GetSelinuxContext("/apex/apex-info-list.xml"), |
| "u:object_r:apex_info_file:s0"); |
| |
| auto info_list = |
| com::android::apex::readApexInfoList("/apex/apex-info-list.xml"); |
| ASSERT_TRUE(info_list.has_value()); |
| auto apex_info_xml_1 = com::android::apex::ApexInfo( |
| /* moduleName= */ "com.android.apex.test_package", |
| /* modulePath= */ apex_dir_1, |
| /* preinstalledModulePath= */ apex_dir_1, |
| /* versionCode= */ 2, /* versionName= */ "2", |
| /* isFactory= */ true, /* isActive= */ true); |
| auto apex_info_xml_2 = com::android::apex::ApexInfo( |
| /* moduleName= */ "com.android.apex.test_package_2", |
| /* modulePath= */ apex_dir_2, |
| /* preinstalledModulePath= */ apex_dir_2, |
| /* versionCode= */ 1, /* versionName= */ "1", |
| /* isFactory= */ true, /* isActive= */ true); |
| |
| ASSERT_THAT(info_list->getApexInfo(), |
| UnorderedElementsAre(ApexInfoXmlEq(apex_info_xml_1), |
| ApexInfoXmlEq(apex_info_xml_2))); |
| } |
| |
| TEST_F(ApexdMountTest, OnStartOnlyPreInstalledApexes) { |
| MockCheckpointInterface checkpoint_interface; |
| // Need to call InitializeVold before calling OnStart |
| InitializeVold(&checkpoint_interface); |
| |
| std::string apex_path_1 = AddPreInstalledApex("apex.apexd_test.apex"); |
| std::string apex_path_2 = |
| AddPreInstalledApex("apex.apexd_test_different_app.apex"); |
| |
| ASSERT_RESULT_OK( |
| ApexFileRepository::GetInstance().AddPreInstalledApex({GetBuiltInDir()})); |
| |
| OnStart(); |
| |
| UnmountOnTearDown(apex_path_1); |
| UnmountOnTearDown(apex_path_2); |
| |
| 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, OnStartDataHasHigherVersion) { |
| MockCheckpointInterface checkpoint_interface; |
| // Need to call InitializeVold before calling OnStart |
| InitializeVold(&checkpoint_interface); |
| |
| AddPreInstalledApex("apex.apexd_test.apex"); |
| std::string apex_path_2 = |
| AddPreInstalledApex("apex.apexd_test_different_app.apex"); |
| std::string apex_path_3 = AddDataApex("apex.apexd_test_v2.apex"); |
| |
| ASSERT_RESULT_OK( |
| ApexFileRepository::GetInstance().AddPreInstalledApex({GetBuiltInDir()})); |
| |
| OnStart(); |
| |
| UnmountOnTearDown(apex_path_2); |
| UnmountOnTearDown(apex_path_3); |
| |
| 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@2", |
| "/apex/com.android.apex.test_package_2", |
| "/apex/com.android.apex.test_package_2@1")); |
| } |
| |
| TEST_F(ApexdMountTest, OnStartDataHasWrongSHA) { |
| MockCheckpointInterface checkpoint_interface; |
| // Need to call InitializeVold before calling OnStart |
| InitializeVold(&checkpoint_interface); |
| |
| AddPreInstalledApex("com.android.apex.cts.shim.apex"); |
| AddDataApex("com.android.apex.cts.shim.v2_wrong_sha.apex"); |
| |
| ASSERT_RESULT_OK( |
| ApexFileRepository::GetInstance().AddPreInstalledApex({GetBuiltInDir()})); |
| |
| OnStart(); |
| |
| // Check system shim apex is activated instead of the data one. |
| auto apex_mounts = GetApexMounts(); |
| ASSERT_THAT(apex_mounts, |
| UnorderedElementsAre("/apex/com.android.apex.cts.shim", |
| "/apex/com.android.apex.cts.shim@1")); |
| } |
| |
| TEST_F(ApexdMountTest, OnStartDataHasSameVersion) { |
| MockCheckpointInterface checkpoint_interface; |
| // Need to call InitializeVold before calling OnStart |
| InitializeVold(&checkpoint_interface); |
| |
| AddPreInstalledApex("apex.apexd_test.apex"); |
| std::string apex_path_2 = |
| AddPreInstalledApex("apex.apexd_test_different_app.apex"); |
| std::string apex_path_3 = AddDataApex("apex.apexd_test.apex"); |
| |
| ASSERT_RESULT_OK( |
| ApexFileRepository::GetInstance().AddPreInstalledApex({GetBuiltInDir()})); |
| |
| OnStart(); |
| |
| UnmountOnTearDown(apex_path_2); |
| UnmountOnTearDown(apex_path_3); |
| |
| 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")); |
| |
| auto& db = GetApexDatabaseForTesting(); |
| // Check that it was mounted from data apex, not pre-installed one. |
| db.ForallMountedApexes("com.android.apex.test_package", |
| [&](const MountedApexData& data, bool latest) { |
| ASSERT_TRUE(latest); |
| ASSERT_EQ(data.full_path, apex_path_3); |
| }); |
| } |
| |
| TEST_F(ApexdMountTest, OnStartSystemHasHigherVersion) { |
| MockCheckpointInterface checkpoint_interface; |
| // Need to call InitializeVold before calling OnStart |
| InitializeVold(&checkpoint_interface); |
| |
| std::string apex_path_1 = AddPreInstalledApex("apex.apexd_test_v2.apex"); |
| std::string apex_path_2 = |
| AddPreInstalledApex("apex.apexd_test_different_app.apex"); |
| AddDataApex("apex.apexd_test.apex"); |
| |
| ASSERT_RESULT_OK( |
| ApexFileRepository::GetInstance().AddPreInstalledApex({GetBuiltInDir()})); |
| |
| OnStart(); |
| |
| UnmountOnTearDown(apex_path_1); |
| UnmountOnTearDown(apex_path_2); |
| |
| 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@2", |
| "/apex/com.android.apex.test_package_2", |
| "/apex/com.android.apex.test_package_2@1")); |
| |
| auto& db = GetApexDatabaseForTesting(); |
| // Check that it was mounted from pre-installed one. |
| db.ForallMountedApexes("com.android.apex.test_package", |
| [&](const MountedApexData& data, bool latest) { |
| ASSERT_TRUE(latest); |
| ASSERT_EQ(data.full_path, apex_path_1); |
| }); |
| } |
| |
| TEST_F(ApexdMountTest, OnStartFailsToActivateApexOnDataFallsBackToBuiltIn) { |
| MockCheckpointInterface checkpoint_interface; |
| // Need to call InitializeVold before calling OnStart |
| InitializeVold(&checkpoint_interface); |
| |
| std::string apex_path_1 = AddPreInstalledApex("apex.apexd_test.apex"); |
| std::string apex_path_2 = |
| AddPreInstalledApex("apex.apexd_test_different_app.apex"); |
| AddDataApex("apex.apexd_test_manifest_mismatch.apex"); |
| |
| ASSERT_RESULT_OK( |
| ApexFileRepository::GetInstance().AddPreInstalledApex({GetBuiltInDir()})); |
| |
| OnStart(); |
| |
| UnmountOnTearDown(apex_path_1); |
| UnmountOnTearDown(apex_path_2); |
| |
| 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")); |
| |
| auto& db = GetApexDatabaseForTesting(); |
| // Check that it was mounted from pre-installed apex. |
| db.ForallMountedApexes("com.android.apex.test_package", |
| [&](const MountedApexData& data, bool latest) { |
| ASSERT_TRUE(latest); |
| ASSERT_EQ(data.full_path, apex_path_1); |
| }); |
| } |
| |
| TEST_F(ApexdMountTest, OnStartApexOnDataHasWrongKeyFallsBackToBuiltIn) { |
| MockCheckpointInterface checkpoint_interface; |
| // Need to call InitializeVold before calling OnStart |
| InitializeVold(&checkpoint_interface); |
| |
| std::string apex_path_1 = AddPreInstalledApex("apex.apexd_test.apex"); |
| std::string apex_path_2 = |
| AddPreInstalledApex("apex.apexd_test_different_app.apex"); |
| std::string apex_path_3 = |
| AddDataApex("apex.apexd_test_different_key_v2.apex"); |
| |
| { |
| auto apex = ApexFile::Open(apex_path_3); |
| ASSERT_TRUE(IsOk(apex)); |
| ASSERT_EQ(static_cast<uint64_t>(apex->GetManifest().version()), 2ULL); |
| } |
| |
| ASSERT_RESULT_OK( |
| ApexFileRepository::GetInstance().AddPreInstalledApex({GetBuiltInDir()})); |
| |
| OnStart(); |
| |
| UnmountOnTearDown(apex_path_1); |
| UnmountOnTearDown(apex_path_2); |
| |
| 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")); |
| |
| auto& db = GetApexDatabaseForTesting(); |
| // Check that it was mounted from pre-installed apex. |
| db.ForallMountedApexes("com.android.apex.test_package", |
| [&](const MountedApexData& data, bool latest) { |
| ASSERT_TRUE(latest); |
| ASSERT_EQ(data.full_path, apex_path_1); |
| }); |
| } |
| |
| TEST_F(ApexdMountTest, OnStartOnlyPreInstalledCapexes) { |
| MockCheckpointInterface checkpoint_interface; |
| // Need to call InitializeVold before calling OnStart |
| InitializeVold(&checkpoint_interface); |
| |
| std::string apex_path_1 = |
| AddPreInstalledApex("com.android.apex.compressed.v1.capex"); |
| |
| ASSERT_RESULT_OK( |
| ApexFileRepository::GetInstance().AddPreInstalledApex({GetBuiltInDir()})); |
| |
| OnStart(); |
| |
| // 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")); |
| auto& db = GetApexDatabaseForTesting(); |
| // Check that it was mounted from decompressed apex. |
| db.ForallMountedApexes("com.android.apex.compressed", |
| [&](const MountedApexData& data, bool latest) { |
| ASSERT_TRUE(latest); |
| ASSERT_EQ(data.full_path, decompressed_active_apex); |
| ASSERT_EQ(data.device_name, |
| "com.android.apex.compressed@1"); |
| }); |
| } |
| |
| TEST_F(ApexdMountTest, OnStartDataHasHigherVersionThanCapex) { |
| MockCheckpointInterface checkpoint_interface; |
| // Need to call InitializeVold before calling OnStart |
| InitializeVold(&checkpoint_interface); |
| |
| AddPreInstalledApex("com.android.apex.compressed.v1.capex"); |
| std::string apex_path_2 = |
| AddDataApex("com.android.apex.compressed.v2_original.apex"); |
| |
| ASSERT_RESULT_OK( |
| ApexFileRepository::GetInstance().AddPreInstalledApex({GetBuiltInDir()})); |
| |
| OnStart(); |
| |
| UnmountOnTearDown(apex_path_2); |
| |
| ASSERT_EQ(GetProperty(kTestApexdStatusSysprop, ""), "starting"); |
| auto apex_mounts = GetApexMounts(); |
| ASSERT_THAT(apex_mounts, |
| UnorderedElementsAre("/apex/com.android.apex.compressed", |
| "/apex/com.android.apex.compressed@2")); |
| auto& db = GetApexDatabaseForTesting(); |
| // Check that it was mounted from data apex. |
| db.ForallMountedApexes("com.android.apex.compressed", |
| [&](const MountedApexData& data, bool latest) { |
| ASSERT_TRUE(latest); |
| ASSERT_EQ(data.full_path, apex_path_2); |
| ASSERT_EQ(data.device_name, |
| "com.android.apex.compressed@2"); |
| }); |
| } |
| |
| TEST_F(ApexdMountTest, OnStartDataHasSameVersionAsCapex) { |
| MockCheckpointInterface checkpoint_interface; |
| // Need to call InitializeVold before calling OnStart |
| InitializeVold(&checkpoint_interface); |
| |
| AddPreInstalledApex("com.android.apex.compressed.v1.capex"); |
| std::string apex_path_2 = |
| AddDataApex("com.android.apex.compressed.v1_original.apex"); |
| |
| ASSERT_RESULT_OK( |
| ApexFileRepository::GetInstance().AddPreInstalledApex({GetBuiltInDir()})); |
| |
| OnStart(); |
| |
| UnmountOnTearDown(apex_path_2); |
| |
| 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")); |
| |
| auto& db = GetApexDatabaseForTesting(); |
| // Check that it was mounted from data apex, not pre-installed one. |
| db.ForallMountedApexes("com.android.apex.compressed", |
| [&](const MountedApexData& data, bool latest) { |
| ASSERT_TRUE(latest); |
| ASSERT_EQ(data.full_path, apex_path_2); |
| ASSERT_EQ(data.device_name, |
| "com.android.apex.compressed@1"); |
| }); |
| } |
| |
| TEST_F(ApexdMountTest, OnStartSystemHasHigherVersionCapexThanData) { |
| MockCheckpointInterface checkpoint_interface; |
| // Need to call InitializeVold before calling OnStart |
| InitializeVold(&checkpoint_interface); |
| |
| std::string apex_path_1 = |
| AddPreInstalledApex("com.android.apex.compressed.v2.capex"); |
| AddDataApex("com.android.apex.compressed.v1_original.apex"); |
| |
| ASSERT_RESULT_OK( |
| ApexFileRepository::GetInstance().AddPreInstalledApex({GetBuiltInDir()})); |
| |
| OnStart(); |
| |
| // Decompressed APEX should be mounted |
| std::string decompressed_active_apex = StringPrintf( |
| "%s/com.android.apex.compressed@2%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@2")); |
| |
| auto& db = GetApexDatabaseForTesting(); |
| // Check that it was mounted from compressed apex |
| db.ForallMountedApexes("com.android.apex.compressed", |
| [&](const MountedApexData& data, bool latest) { |
| ASSERT_TRUE(latest); |
| ASSERT_EQ(data.full_path, decompressed_active_apex); |
| ASSERT_EQ(data.device_name, |
| "com.android.apex.compressed@2"); |
| }); |
| } |
| |
| TEST_F(ApexdMountTest, OnStartFailsToActivateApexOnDataFallsBackToCapex) { |
| MockCheckpointInterface checkpoint_interface; |
| // Need to call InitializeVold before calling OnStart |
| InitializeVold(&checkpoint_interface); |
| |
| AddPreInstalledApex("com.android.apex.compressed.v1.capex"); |
| AddDataApex("com.android.apex.compressed.v2_manifest_mismatch.apex"); |
| |
| ASSERT_RESULT_OK( |
| ApexFileRepository::GetInstance().AddPreInstalledApex({GetBuiltInDir()})); |
| |
| OnStart(); |
| |
| // 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")); |
| auto& db = GetApexDatabaseForTesting(); |
| // Check that it was mounted from decompressed apex. It should also be mounted |
| // on dm-verity device. |
| db.ForallMountedApexes("com.android.apex.compressed", |
| [&](const MountedApexData& data, bool latest) { |
| ASSERT_TRUE(latest); |
| ASSERT_EQ(data.full_path, decompressed_active_apex); |
| ASSERT_EQ(data.device_name, |
| "com.android.apex.compressed@1"); |
| }); |
| } |
| |
| // Test scenario when we fallback to capex but it already has a decompressed |
| // version on data |
| TEST_F(ApexdMountTest, OnStartFallbackToAlreadyDecompressedCapex) { |
| MockCheckpointInterface checkpoint_interface; |
| // Need to call InitializeVold before calling OnStart |
| InitializeVold(&checkpoint_interface); |
| |
| PrepareCompressedApex("com.android.apex.compressed.v1.capex"); |
| AddDataApex("com.android.apex.compressed.v2_manifest_mismatch.apex"); |
| |
| ASSERT_RESULT_OK( |
| ApexFileRepository::GetInstance().AddPreInstalledApex({GetBuiltInDir()})); |
| |
| OnStart(); |
| |
| // 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")); |
| auto& db = GetApexDatabaseForTesting(); |
| // Check that it was mounted from decompressed apex. |
| db.ForallMountedApexes("com.android.apex.compressed", |
| [&](const MountedApexData& data, bool latest) { |
| ASSERT_TRUE(latest); |
| ASSERT_EQ(data.full_path, decompressed_active_apex); |
| ASSERT_EQ(data.device_name, |
| "com.android.apex.compressed@1"); |
| }); |
| } |
| |
| // Test scenario when we fallback to capex but it has same version as corrupt |
| // data apex |
| TEST_F(ApexdMountTest, OnStartFallbackToCapexSameVersion) { |
| MockCheckpointInterface checkpoint_interface; |
| // Need to call InitializeVold before calling OnStart |
| InitializeVold(&checkpoint_interface); |
| |
| AddPreInstalledApex("com.android.apex.compressed.v2.capex"); |
| // Add data apex using the common naming convention for /data/apex/active |
| // directory |
| fs::copy(GetTestFile("com.android.apex.compressed.v2_manifest_mismatch.apex"), |
| GetDataDir() + "/com.android.apex.compressed@2.apex"); |
| |
| ASSERT_RESULT_OK( |
| ApexFileRepository::GetInstance().AddPreInstalledApex({GetBuiltInDir()})); |
| |
| OnStart(); |
| |
| // Decompressed APEX should be mounted |
| std::string decompressed_active_apex = StringPrintf( |
| "%s/com.android.apex.compressed@2%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@2")); |
| auto& db = GetApexDatabaseForTesting(); |
| // Check that it was mounted from decompressed apex. |
| db.ForallMountedApexes("com.android.apex.compressed", |
| [&](const MountedApexData& data, bool latest) { |
| ASSERT_TRUE(latest); |
| ASSERT_EQ(data.full_path, decompressed_active_apex); |
| ASSERT_EQ(data.device_name, |
| "com.android.apex.compressed@2"); |
| }); |
| } |
| |
| TEST_F(ApexdMountTest, OnStartCapexToApex) { |
| MockCheckpointInterface checkpoint_interface; |
| // Need to call InitializeVold before calling OnStart |
| InitializeVold(&checkpoint_interface); |
| |
| TemporaryDir previous_built_in_dir; |
| PrepareCompressedApex("com.android.apex.compressed.v1.capex", |
| previous_built_in_dir.path); |
| auto apex_path = |
| AddPreInstalledApex("com.android.apex.compressed.v1_original.apex"); |
| |
| ASSERT_RESULT_OK( |
| ApexFileRepository::GetInstance().AddPreInstalledApex({GetBuiltInDir()})); |
| |
| OnStart(); |
| |
| // Uncompressed APEX should be mounted |
| UnmountOnTearDown(apex_path); |
| |
| 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")); |
| auto& db = GetApexDatabaseForTesting(); |
| // Check that it was mounted from decompressed apex. |
| db.ForallMountedApexes("com.android.apex.compressed", |
| [&](const MountedApexData& data, bool latest) { |
| ASSERT_TRUE(latest); |
| ASSERT_EQ(data.full_path, apex_path); |
| ASSERT_THAT(data.device_name, IsEmpty()); |
| }); |
| } |
| |
| // Test to ensure we do not mount decompressed APEX from /data/apex/active |
| TEST_F(ApexdMountTest, OnStartOrphanedDecompressedApexInActiveDirectory) { |
| MockCheckpointInterface checkpoint_interface; |
| // Need to call InitializeVold before calling OnStart |
| InitializeVold(&checkpoint_interface); |
| |
| // Place a decompressed APEX in /data/apex/active. This apex should not |
| // be mounted since it's not in correct location. Instead, the |
| // pre-installed APEX should be mounted. |
| auto decompressed_apex_in_active_dir = |
| StringPrintf("%s/com.android.apex.compressed@1%s", GetDataDir().c_str(), |
| kDecompressedApexPackageSuffix); |
| fs::copy(GetTestFile("com.android.apex.compressed.v1_original.apex"), |
| decompressed_apex_in_active_dir); |
| auto apex_path = |
| AddPreInstalledApex("com.android.apex.compressed.v1_original.apex"); |
| |
| ASSERT_RESULT_OK( |
| ApexFileRepository::GetInstance().AddPreInstalledApex({GetBuiltInDir()})); |
| |
| OnStart(); |
| |
| // Pre-installed APEX should be mounted |
| UnmountOnTearDown(apex_path); |
| auto& db = GetApexDatabaseForTesting(); |
| // Check that pre-installed APEX has been activated |
| db.ForallMountedApexes("com.android.apex.compressed", |
| [&](const MountedApexData& data, bool latest) { |
| ASSERT_TRUE(latest); |
| ASSERT_EQ(data.full_path, apex_path); |
| ASSERT_THAT(data.device_name, IsEmpty()); |
| }); |
| } |
| |
| // Test scenario when decompressed version has different version than |
| // pre-installed CAPEX |
| TEST_F(ApexdMountTest, OnStartDecompressedApexVersionDifferentThanCapex) { |
| MockCheckpointInterface checkpoint_interface; |
| // Need to call InitializeVold before calling OnStart |
| InitializeVold(&checkpoint_interface); |
| |
| TemporaryDir previous_built_in_dir; |
| PrepareCompressedApex("com.android.apex.compressed.v2.capex", |
| previous_built_in_dir.path); |
| auto apex_path = AddPreInstalledApex("com.android.apex.compressed.v1.capex"); |
| |
| ASSERT_RESULT_OK( |
| ApexFileRepository::GetInstance().AddPreInstalledApex({GetBuiltInDir()})); |
| |
| OnStart(); |
| |
| // Existing higher version decompressed APEX should be ignored and new |
| // pre-installed CAPEX should be decompressed and 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")); |
| auto& db = GetApexDatabaseForTesting(); |
| // Check that it was mounted from newly decompressed apex. |
| db.ForallMountedApexes("com.android.apex.compressed", |
| [&](const MountedApexData& data, bool latest) { |
| ASSERT_TRUE(latest); |
| ASSERT_EQ(data.full_path, decompressed_active_apex); |
| ASSERT_EQ(data.device_name, |
| "com.android.apex.compressed@1"); |
| }); |
| } |
| |
| TEST_F(ApexdMountTest, PopulateFromMountsChecksPathPrefix) { |
| AddPreInstalledApex("apex.apexd_test.apex"); |
| std::string apex_path = AddDataApex("apex.apexd_test_v2.apex"); |
| |
| // Mount an apex from decomrpession_dir |
| PrepareCompressedApex("com.android.apex.compressed.v1.capex"); |
| std::string decompressed_apex = |
| StringPrintf("%s/com.android.apex.compressed@1.decompressed.apex", |
| GetDecompressionDir().c_str()); |
| |
| // Mount an apex from some other directory |
| TemporaryDir td; |
| AddPreInstalledApex("apex.apexd_test_different_app.apex"); |
| fs::copy(GetTestFile("apex.apexd_test_different_app.apex"), td.path); |
| std::string other_apex = |
| StringPrintf("%s/apex.apexd_test_different_app.apex", td.path); |
| |
| auto& instance = ApexFileRepository::GetInstance(); |
| ASSERT_RESULT_OK(instance.AddPreInstalledApex({GetBuiltInDir()})); |
| |
| ASSERT_TRUE(IsOk(ActivatePackage(apex_path))); |
| ASSERT_TRUE(IsOk(ActivatePackage(decompressed_apex))); |
| ASSERT_TRUE(IsOk(ActivatePackage(other_apex))); |
| UnmountOnTearDown(apex_path); |
| UnmountOnTearDown(decompressed_apex); |
| UnmountOnTearDown(other_apex); |
| |
| auto apex_mounts = GetApexMounts(); |
| ASSERT_THAT(apex_mounts, |
| UnorderedElementsAre("/apex/com.android.apex.test_package", |
| "/apex/com.android.apex.test_package@2", |
| "/apex/com.android.apex.compressed", |
| "/apex/com.android.apex.compressed@1", |
| "/apex/com.android.apex.test_package_2", |
| "/apex/com.android.apex.test_package_2@1")); |
| |
| auto& db = GetApexDatabaseForTesting(); |
| // Clear the database before calling PopulateFromMounts |
| db.Reset(); |
| |
| // Populate from mount |
| db.PopulateFromMounts(GetDataDir(), GetDecompressionDir(), GetHashTreeDir()); |
| |
| // Count number of package and collect package names |
| int package_count = 0; |
| std::vector<std::string> mounted_paths; |
| db.ForallMountedApexes([&](const std::string& package, |
| const MountedApexData& data, bool latest) { |
| package_count++; |
| mounted_paths.push_back(data.full_path); |
| }); |
| ASSERT_EQ(package_count, 2); |
| ASSERT_THAT(mounted_paths, |
| UnorderedElementsAre(apex_path, decompressed_apex)); |
| } |
| |
| TEST_F(ApexdMountTest, UnmountAll) { |
| AddPreInstalledApex("apex.apexd_test.apex"); |
| std::string apex_path_2 = |
| AddPreInstalledApex("apex.apexd_test_different_app.apex"); |
| std::string apex_path_3 = AddDataApex("apex.apexd_test_v2.apex"); |
| |
| // Mount an apex from decomrpession_dir |
| PrepareCompressedApex("com.android.apex.compressed.v1.capex"); |
| std::string decompressed_apex = |
| StringPrintf("%s/com.android.apex.compressed@1.decompressed.apex", |
| GetDecompressionDir().c_str()); |
| |
| auto& instance = ApexFileRepository::GetInstance(); |
| ASSERT_RESULT_OK(instance.AddPreInstalledApex({GetBuiltInDir()})); |
| |
| ASSERT_TRUE(IsOk(ActivatePackage(apex_path_2))); |
| ASSERT_TRUE(IsOk(ActivatePackage(apex_path_3))); |
| ASSERT_TRUE(IsOk(ActivatePackage(decompressed_apex))); |
| UnmountOnTearDown(apex_path_2); |
| UnmountOnTearDown(apex_path_3); |
| UnmountOnTearDown(decompressed_apex); |
| |
| auto apex_mounts = GetApexMounts(); |
| ASSERT_THAT(apex_mounts, |
| UnorderedElementsAre("/apex/com.android.apex.test_package", |
| "/apex/com.android.apex.test_package@2", |
| "/apex/com.android.apex.compressed", |
| "/apex/com.android.apex.compressed@1", |
| "/apex/com.android.apex.test_package_2", |
| "/apex/com.android.apex.test_package_2@1")); |
| |
| auto& db = GetApexDatabaseForTesting(); |
| // UnmountAll expects apex database to empty, hence this reset. |
| db.Reset(); |
| |
| ASSERT_EQ(0, UnmountAll()); |
| |
| auto new_apex_mounts = GetApexMounts(); |
| ASSERT_EQ(new_apex_mounts.size(), 0u); |
| } |
| |
| TEST_F(ApexdMountTest, UnmountAllSharedLibsApex) { |
| ASSERT_EQ(mkdir("/apex/sharedlibs", 0755), 0); |
| ASSERT_EQ(mkdir("/apex/sharedlibs/lib", 0755), 0); |
| ASSERT_EQ(mkdir("/apex/sharedlibs/lib64", 0755), 0); |
| auto deleter = make_scope_guard([]() { |
| std::error_code ec; |
| fs::remove_all("/apex/sharedlibs", ec); |
| if (ec) { |
| LOG(ERROR) << "Failed to delete /apex/sharedlibs : " << ec; |
| } |
| }); |
| |
| std::string apex_path_1 = AddPreInstalledApex( |
| "com.android.apex.test.sharedlibs_generated.v1.libvX.apex"); |
| std::string apex_path_2 = |
| AddDataApex("com.android.apex.test.sharedlibs_generated.v2.libvY.apex"); |
| |
| auto& instance = ApexFileRepository::GetInstance(); |
| ASSERT_RESULT_OK(instance.AddPreInstalledApex({GetBuiltInDir()})); |
| |
| ASSERT_TRUE(IsOk(ActivatePackage(apex_path_1))); |
| ASSERT_TRUE(IsOk(ActivatePackage(apex_path_2))); |
| UnmountOnTearDown(apex_path_1); |
| UnmountOnTearDown(apex_path_2); |
| |
| auto apex_mounts = GetApexMounts(); |
| ASSERT_THAT(apex_mounts, |
| UnorderedElementsAre("/apex/com.android.apex.test.sharedlibs@1", |
| "/apex/com.android.apex.test.sharedlibs@2")); |
| |
| auto& db = GetApexDatabaseForTesting(); |
| // UnmountAll expects apex database to empty, hence this reset. |
| db.Reset(); |
| |
| ASSERT_EQ(0, UnmountAll()); |
| |
| auto new_apex_mounts = GetApexMounts(); |
| ASSERT_EQ(new_apex_mounts.size(), 0u); |
| } |
| |
| TEST_F(ApexdMountTest, OnStartInVmModeActivatesPreInstalled) { |
| MockCheckpointInterface checkpoint_interface; |
| // Need to call InitializeVold before calling OnStart |
| InitializeVold(&checkpoint_interface); |
| |
| auto path1 = AddPreInstalledApex("apex.apexd_test.apex"); |
| auto path2 = AddPreInstalledApex("apex.apexd_test_different_app.apex"); |
| // In VM mode, we don't scan /data/apex |
| AddDataApex("apex.apexd_test_v2.apex"); |
| |
| ASSERT_EQ(0, OnStartInVmMode()); |
| UnmountOnTearDown(path1); |
| UnmountOnTearDown(path2); |
| |
| 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", |
| // Emits apex-info-list as well |
| "/apex/apex-info-list.xml")); |
| |
| ASSERT_EQ(GetProperty(kTestApexdStatusSysprop, ""), "ready"); |
| } |
| |
| TEST_F(ApexdMountTest, OnStartInVmModeFailsWithCapex) { |
| MockCheckpointInterface checkpoint_interface; |
| // Need to call InitializeVold before calling OnStart |
| InitializeVold(&checkpoint_interface); |
| |
| AddPreInstalledApex("com.android.apex.compressed.v2.capex"); |
| |
| ASSERT_EQ(1, OnStartInVmMode()); |
| } |
| |
| TEST_F(ApexdMountTest, OnStartInVmModeActivatesBlockDevicesAsWell) { |
| MockCheckpointInterface checkpoint_interface; |
| // Need to call InitializeVold before calling OnStart |
| InitializeVold(&checkpoint_interface); |
| |
| auto path1 = AddBlockApex("apex.apexd_test.apex"); |
| |
| ASSERT_EQ(0, OnStartInVmMode()); |
| UnmountOnTearDown(path1); |
| |
| auto apex_mounts = GetApexMounts(); |
| ASSERT_THAT(apex_mounts, |
| UnorderedElementsAre("/apex/com.android.apex.test_package", |
| "/apex/com.android.apex.test_package@1", |
| // Emits apex-info-list as well |
| "/apex/apex-info-list.xml")); |
| |
| ASSERT_EQ(access("/apex/apex-info-list.xml", F_OK), 0); |
| auto info_list = |
| com::android::apex::readApexInfoList("/apex/apex-info-list.xml"); |
| ASSERT_TRUE(info_list.has_value()); |
| auto apex_info_xml_1 = com::android::apex::ApexInfo( |
| /* moduleName= */ "com.android.apex.test_package", |
| /* modulePath= */ path1, |
| /* preinstalledModulePath= */ path1, |
| /* versionCode= */ 1, /* versionName= */ "1", |
| /* isFactory= */ true, /* isActive= */ true); |
| ASSERT_THAT(info_list->getApexInfo(), |
| UnorderedElementsAre(ApexInfoXmlEq(apex_info_xml_1))); |
| } |
| |
| TEST_F(ApexdMountTest, OnStartInVmModeFailsWithDuplicateNames) { |
| MockCheckpointInterface checkpoint_interface; |
| // Need to call InitializeVold before calling OnStart |
| InitializeVold(&checkpoint_interface); |
| |
| AddPreInstalledApex("apex.apexd_test.apex"); |
| AddBlockApex("apex.apexd_test_v2.apex"); |
| |
| ASSERT_EQ(1, OnStartInVmMode()); |
| } |
| |
| TEST_F(ApexdMountTest, OnStartInVmModeFailsWithWrongPubkey) { |
| MockCheckpointInterface checkpoint_interface; |
| // Need to call InitializeVold before calling OnStart |
| InitializeVold(&checkpoint_interface); |
| |
| AddBlockApex("apex.apexd_test.apex", "wrong pubkey"); |
| |
| ASSERT_EQ(1, OnStartInVmMode()); |
| } |
| |
| TEST_F(ApexdMountTest, GetActivePackagesReturningBlockApexesAsWell) { |
| MockCheckpointInterface checkpoint_interface; |
| // Need to call InitializeVold before calling OnStart |
| InitializeVold(&checkpoint_interface); |
| |
| auto path1 = AddBlockApex("apex.apexd_test.apex"); |
| |
| ASSERT_EQ(0, OnStartInVmMode()); |
| UnmountOnTearDown(path1); |
| |
| auto active_apexes = GetActivePackages(); |
| ASSERT_EQ(1u, active_apexes.size()); |
| ASSERT_EQ(path1, active_apexes[0].GetPath()); |
| } |
| |
| } // namespace apex |
| } // namespace android |