| /* |
| * Copyright (C) 2020 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 <filesystem> |
| #include <string> |
| |
| #include <errno.h> |
| #include <sys/stat.h> |
| |
| #include <android-base/file.h> |
| #include <android-base/logging.h> |
| #include <android-base/stringprintf.h> |
| #include <gmock/gmock.h> |
| #include <gtest/gtest.h> |
| #include <microdroid/signature.h> |
| |
| #include "apex_file.h" |
| #include "apex_file_repository.h" |
| #include "apexd_test_utils.h" |
| #include "apexd_verity.h" |
| |
| namespace android { |
| namespace apex { |
| |
| using namespace std::literals; |
| |
| namespace fs = std::filesystem; |
| |
| using android::apex::testing::ApexFileEq; |
| using android::apex::testing::IsOk; |
| using android::base::GetExecutableDirectory; |
| using android::base::StringPrintf; |
| using ::testing::ByRef; |
| using ::testing::Eq; |
| using ::testing::Optional; |
| using ::testing::UnorderedElementsAre; |
| |
| static std::string GetTestDataDir() { return GetExecutableDirectory(); } |
| static std::string GetTestFile(const std::string& name) { |
| return GetTestDataDir() + "/" + name; |
| } |
| |
| namespace { |
| // Copies the compressed apex to |built_in_dir| and decompresses it to |
| // |decompression_dir |
| void PrepareCompressedApex(const std::string& name, |
| const std::string& built_in_dir, |
| const std::string& decompression_dir) { |
| fs::copy(GetTestFile(name), built_in_dir); |
| auto compressed_apex = |
| ApexFile::Open(StringPrintf("%s/%s", built_in_dir.c_str(), name.c_str())); |
| |
| const auto& pkg_name = compressed_apex->GetManifest().name(); |
| const int version = compressed_apex->GetManifest().version(); |
| |
| auto decompression_path = |
| StringPrintf("%s/%s@%d%s", decompression_dir.c_str(), pkg_name.c_str(), |
| version, kDecompressedApexPackageSuffix); |
| compressed_apex->Decompress(decompression_path); |
| } |
| } // namespace |
| |
| TEST(ApexFileRepositoryTest, InitializeSuccess) { |
| // Prepare test data. |
| TemporaryDir built_in_dir, data_dir, decompression_dir; |
| fs::copy(GetTestFile("apex.apexd_test.apex"), built_in_dir.path); |
| fs::copy(GetTestFile("apex.apexd_test_different_app.apex"), |
| built_in_dir.path); |
| |
| fs::copy(GetTestFile("apex.apexd_test.apex"), data_dir.path); |
| fs::copy(GetTestFile("apex.apexd_test_different_app.apex"), data_dir.path); |
| |
| ApexFileRepository instance; |
| ASSERT_TRUE(IsOk(instance.AddPreInstalledApex({built_in_dir.path}))); |
| ASSERT_TRUE( |
| IsOk(instance.AddDataApex(data_dir.path, decompression_dir.path))); |
| |
| // Now test that apexes were scanned correctly; |
| auto test_fn = [&](const std::string& apex_name) { |
| auto apex = ApexFile::Open(GetTestFile(apex_name)); |
| ASSERT_TRUE(IsOk(apex)); |
| |
| { |
| auto ret = instance.GetPublicKey(apex->GetManifest().name()); |
| ASSERT_TRUE(IsOk(ret)); |
| ASSERT_EQ(apex->GetBundledPublicKey(), *ret); |
| } |
| |
| { |
| auto ret = instance.GetPreinstalledPath(apex->GetManifest().name()); |
| ASSERT_TRUE(IsOk(ret)); |
| ASSERT_EQ(StringPrintf("%s/%s", built_in_dir.path, apex_name.c_str()), |
| *ret); |
| } |
| |
| { |
| auto ret = instance.GetDataPath(apex->GetManifest().name()); |
| ASSERT_TRUE(IsOk(ret)); |
| ASSERT_EQ(StringPrintf("%s/%s", data_dir.path, apex_name.c_str()), *ret); |
| } |
| |
| ASSERT_TRUE(instance.HasPreInstalledVersion(apex->GetManifest().name())); |
| ASSERT_TRUE(instance.HasDataVersion(apex->GetManifest().name())); |
| }; |
| |
| test_fn("apex.apexd_test.apex"); |
| test_fn("apex.apexd_test_different_app.apex"); |
| |
| // Check that second call will succeed as well. |
| ASSERT_TRUE(IsOk(instance.AddPreInstalledApex({built_in_dir.path}))); |
| ASSERT_TRUE( |
| IsOk(instance.AddDataApex(data_dir.path, decompression_dir.path))); |
| |
| test_fn("apex.apexd_test.apex"); |
| test_fn("apex.apexd_test_different_app.apex"); |
| } |
| |
| TEST(ApexFileRepositoryTest, InitializeFailureCorruptApex) { |
| // Prepare test data. |
| TemporaryDir td; |
| fs::copy(GetTestFile("apex.apexd_test.apex"), td.path); |
| fs::copy(GetTestFile("apex.apexd_test_corrupt_superblock_apex.apex"), |
| td.path); |
| |
| ApexFileRepository instance; |
| ASSERT_FALSE(IsOk(instance.AddPreInstalledApex({td.path}))); |
| } |
| |
| TEST(ApexFileRepositoryTest, InitializeCompressedApexWithoutApex) { |
| // Prepare test data. |
| TemporaryDir td; |
| fs::copy(GetTestFile("com.android.apex.compressed.v1_without_apex.capex"), |
| td.path); |
| |
| ApexFileRepository instance; |
| // Compressed APEX without APEX cannot be opened |
| ASSERT_FALSE(IsOk(instance.AddPreInstalledApex({td.path}))); |
| } |
| |
| TEST(ApexFileRepositoryTest, InitializeSameNameDifferentPathAborts) { |
| // Prepare test data. |
| TemporaryDir td; |
| fs::copy(GetTestFile("apex.apexd_test.apex"), td.path); |
| fs::copy(GetTestFile("apex.apexd_test.apex"), |
| StringPrintf("%s/other.apex", td.path)); |
| |
| ASSERT_DEATH( |
| { |
| ApexFileRepository instance; |
| instance.AddPreInstalledApex({td.path}); |
| }, |
| ""); |
| } |
| |
| TEST(ApexFileRepositoryTest, |
| InitializeSameNameDifferentPathAbortsCompressedApex) { |
| // Prepare test data. |
| TemporaryDir td; |
| fs::copy(GetTestFile("com.android.apex.compressed.v1.capex"), td.path); |
| fs::copy(GetTestFile("com.android.apex.compressed.v1.capex"), |
| StringPrintf("%s/other.capex", td.path)); |
| |
| ASSERT_DEATH( |
| { |
| ApexFileRepository instance; |
| instance.AddPreInstalledApex({td.path}); |
| }, |
| ""); |
| } |
| |
| TEST(ApexFileRepositoryTest, InitializePublicKeyUnexpectdlyChangedAborts) { |
| // Prepare test data. |
| TemporaryDir td; |
| fs::copy(GetTestFile("apex.apexd_test.apex"), td.path); |
| |
| ApexFileRepository instance; |
| ASSERT_TRUE(IsOk(instance.AddPreInstalledApex({td.path}))); |
| |
| // Check that apex was loaded. |
| auto path = instance.GetPreinstalledPath("com.android.apex.test_package"); |
| ASSERT_TRUE(IsOk(path)); |
| ASSERT_EQ(StringPrintf("%s/apex.apexd_test.apex", td.path), *path); |
| |
| auto public_key = instance.GetPublicKey("com.android.apex.test_package"); |
| ASSERT_TRUE(IsOk(public_key)); |
| |
| // Substitute it with another apex with the same name, but different public |
| // key. |
| fs::copy(GetTestFile("apex.apexd_test_different_key.apex"), *path, |
| fs::copy_options::overwrite_existing); |
| |
| { |
| auto apex = ApexFile::Open(*path); |
| ASSERT_TRUE(IsOk(apex)); |
| // Check module name hasn't changed. |
| ASSERT_EQ("com.android.apex.test_package", apex->GetManifest().name()); |
| // Check public key has changed. |
| ASSERT_NE(*public_key, apex->GetBundledPublicKey()); |
| } |
| |
| ASSERT_DEATH({ instance.AddPreInstalledApex({td.path}); }, ""); |
| } |
| |
| TEST(ApexFileRepositoryTest, |
| InitializePublicKeyUnexpectdlyChangedAbortsCompressedApex) { |
| // Prepare test data. |
| TemporaryDir td; |
| fs::copy(GetTestFile("com.android.apex.compressed.v1.capex"), td.path); |
| |
| ApexFileRepository instance; |
| ASSERT_TRUE(IsOk(instance.AddPreInstalledApex({td.path}))); |
| |
| // Check that apex was loaded. |
| auto path = instance.GetPreinstalledPath("com.android.apex.compressed"); |
| ASSERT_TRUE(IsOk(path)); |
| ASSERT_EQ(StringPrintf("%s/com.android.apex.compressed.v1.capex", td.path), |
| *path); |
| |
| auto public_key = instance.GetPublicKey("com.android.apex.compressed"); |
| ASSERT_TRUE(IsOk(public_key)); |
| |
| // Substitute it with another apex with the same name, but different public |
| // key. |
| fs::copy(GetTestFile("com.android.apex.compressed_different_key.capex"), |
| *path, fs::copy_options::overwrite_existing); |
| |
| { |
| auto apex = ApexFile::Open(*path); |
| ASSERT_TRUE(IsOk(apex)); |
| // Check module name hasn't changed. |
| ASSERT_EQ("com.android.apex.compressed", apex->GetManifest().name()); |
| // Check public key has changed. |
| ASSERT_NE(*public_key, apex->GetBundledPublicKey()); |
| } |
| |
| ASSERT_DEATH({ instance.AddPreInstalledApex({td.path}); }, ""); |
| } |
| |
| TEST(ApexFileRepositoryTest, IsPreInstalledApex) { |
| // Prepare test data. |
| TemporaryDir td; |
| fs::copy(GetTestFile("apex.apexd_test.apex"), td.path); |
| fs::copy(GetTestFile("com.android.apex.compressed.v1.capex"), td.path); |
| |
| ApexFileRepository instance; |
| ASSERT_TRUE(IsOk(instance.AddPreInstalledApex({td.path}))); |
| |
| auto compressed_apex = ApexFile::Open( |
| StringPrintf("%s/com.android.apex.compressed.v1.capex", td.path)); |
| ASSERT_TRUE(IsOk(compressed_apex)); |
| ASSERT_TRUE(instance.IsPreInstalledApex(*compressed_apex)); |
| |
| auto apex1 = ApexFile::Open(StringPrintf("%s/apex.apexd_test.apex", td.path)); |
| ASSERT_TRUE(IsOk(apex1)); |
| ASSERT_TRUE(instance.IsPreInstalledApex(*apex1)); |
| |
| // It's same apex, but path is different. Shouldn't be treated as |
| // pre-installed. |
| auto apex2 = ApexFile::Open(GetTestFile("apex.apexd_test.apex")); |
| ASSERT_TRUE(IsOk(apex2)); |
| ASSERT_FALSE(instance.IsPreInstalledApex(*apex2)); |
| |
| auto apex3 = |
| ApexFile::Open(GetTestFile("apex.apexd_test_different_app.apex")); |
| ASSERT_TRUE(IsOk(apex3)); |
| ASSERT_FALSE(instance.IsPreInstalledApex(*apex3)); |
| } |
| |
| TEST(ApexFileRepositoryTest, IsDecompressedApex) { |
| // Prepare instance |
| TemporaryDir decompression_dir; |
| ApexFileRepository instance(decompression_dir.path); |
| |
| // Prepare decompressed apex |
| std::string filename = "com.android.apex.compressed.v1_original.apex"; |
| fs::copy(GetTestFile(filename), decompression_dir.path); |
| auto decompressed_path = |
| StringPrintf("%s/%s", decompression_dir.path, filename.c_str()); |
| auto decompressed_apex = ApexFile::Open(decompressed_path); |
| |
| // Any file which is already located in |decompression_dir| should be |
| // considered decompressed |
| ASSERT_TRUE(instance.IsDecompressedApex(*decompressed_apex)); |
| |
| // Hard links with same file name is not considered decompressed |
| TemporaryDir active_dir; |
| auto active_path = StringPrintf("%s/%s", active_dir.path, filename.c_str()); |
| std::error_code ec; |
| fs::create_hard_link(decompressed_path, active_path, ec); |
| ASSERT_FALSE(ec) << "Failed to create hardlink"; |
| auto active_apex = ApexFile::Open(active_path); |
| ASSERT_FALSE(instance.IsDecompressedApex(*active_apex)); |
| } |
| |
| TEST(ApexFileRepositoryTest, AddAndGetDataApex) { |
| // Prepare test data. |
| TemporaryDir built_in_dir, data_dir, decompression_dir; |
| fs::copy(GetTestFile("apex.apexd_test.apex"), built_in_dir.path); |
| fs::copy(GetTestFile("apex.apexd_test_v2.apex"), data_dir.path); |
| PrepareCompressedApex("com.android.apex.compressed.v1.capex", |
| built_in_dir.path, decompression_dir.path); |
| |
| ApexFileRepository instance(decompression_dir.path); |
| ASSERT_TRUE(IsOk(instance.AddPreInstalledApex({built_in_dir.path}))); |
| ASSERT_TRUE( |
| IsOk(instance.AddDataApex(data_dir.path, decompression_dir.path))); |
| |
| auto data_apexs = instance.GetDataApexFiles(); |
| auto normal_apex = |
| ApexFile::Open(StringPrintf("%s/apex.apexd_test_v2.apex", data_dir.path)); |
| auto decompressed_apex = ApexFile::Open( |
| StringPrintf("%s/com.android.apex.compressed@1%s", decompression_dir.path, |
| kDecompressedApexPackageSuffix)); |
| ASSERT_THAT(data_apexs, |
| UnorderedElementsAre(ApexFileEq(ByRef(*normal_apex)), |
| ApexFileEq(ByRef(*decompressed_apex)))); |
| } |
| |
| TEST(ApexFileRepositoryTest, AddDataApexMatchesSuffixForDecompressedApex) { |
| // Prepare test data. |
| TemporaryDir built_in_dir, data_dir, decompression_dir; |
| fs::copy(GetTestFile("com.android.apex.compressed.v1.capex"), |
| built_in_dir.path); |
| |
| ApexFileRepository instance(decompression_dir.path); |
| ASSERT_TRUE(IsOk(instance.AddPreInstalledApex({built_in_dir.path}))); |
| |
| // - Files with ".apex" suffix should be ignored when found in |
| // /data/apex/decompressed |
| // - Files with ".decompressed.apex" suffix should be ignored when found in |
| // /data/apex/active. |
| auto normal_apex_in_decompression_dir = StringPrintf( |
| "%s/com.android.apex.compressed@1.apex", decompression_dir.path); |
| fs::copy(GetTestFile("com.android.apex.compressed.v1_original.apex"), |
| normal_apex_in_decompression_dir); |
| auto decompressed_apex_in_active_dir = |
| StringPrintf("%s/com.android.apex.compressed@1%s", data_dir.path, |
| kDecompressedApexPackageSuffix); |
| fs::copy(GetTestFile("com.android.apex.compressed.v1_original.apex"), |
| decompressed_apex_in_active_dir); |
| ASSERT_TRUE( |
| IsOk(instance.AddDataApex(data_dir.path, decompression_dir.path))); |
| |
| auto data_apexs = instance.GetDataApexFiles(); |
| ASSERT_EQ(data_apexs.size(), 0u); |
| |
| // When decompressed apex has kDecompressedApexPackageSuffix, they get |
| // selected |
| auto decompressed_apex_path = |
| StringPrintf("%s/com.android.apex.compressed@1%s", decompression_dir.path, |
| kDecompressedApexPackageSuffix); |
| fs::copy(GetTestFile("com.android.apex.compressed.v1_original.apex"), |
| decompressed_apex_path); |
| ASSERT_TRUE( |
| IsOk(instance.AddDataApex(data_dir.path, decompression_dir.path))); |
| |
| data_apexs = instance.GetDataApexFiles(); |
| auto decompressed_apex = ApexFile::Open(decompressed_apex_path); |
| ASSERT_THAT(data_apexs, |
| UnorderedElementsAre(ApexFileEq(ByRef(*decompressed_apex)))); |
| } |
| |
| TEST(ApexFileRepositoryTest, AddDataApexIgnoreCompressedApex) { |
| // Prepare test data. |
| TemporaryDir data_dir, decompression_dir; |
| fs::copy(GetTestFile("com.android.apex.compressed.v1.capex"), data_dir.path); |
| |
| ApexFileRepository instance; |
| ASSERT_TRUE( |
| IsOk(instance.AddDataApex(data_dir.path, decompression_dir.path))); |
| |
| auto data_apexs = instance.GetDataApexFiles(); |
| ASSERT_EQ(data_apexs.size(), 0u); |
| } |
| |
| TEST(ApexFileRepositoryTest, AddDataApexIgnoreIfNotPreInstalled) { |
| // Prepare test data. |
| TemporaryDir data_dir, decompression_dir; |
| fs::copy(GetTestFile("apex.apexd_test.apex"), data_dir.path); |
| |
| ApexFileRepository instance; |
| ASSERT_TRUE( |
| IsOk(instance.AddDataApex(data_dir.path, decompression_dir.path))); |
| |
| auto data_apexs = instance.GetDataApexFiles(); |
| ASSERT_EQ(data_apexs.size(), 0u); |
| } |
| |
| TEST(ApexFileRepositoryTest, AddDataApexPrioritizeHigherVersionApex) { |
| // Prepare test data. |
| TemporaryDir built_in_dir, data_dir, decompression_dir; |
| fs::copy(GetTestFile("apex.apexd_test.apex"), built_in_dir.path); |
| fs::copy(GetTestFile("apex.apexd_test.apex"), data_dir.path); |
| fs::copy(GetTestFile("apex.apexd_test_v2.apex"), data_dir.path); |
| |
| ApexFileRepository instance; |
| ASSERT_TRUE(IsOk(instance.AddPreInstalledApex({built_in_dir.path}))); |
| ASSERT_TRUE( |
| IsOk(instance.AddDataApex(data_dir.path, decompression_dir.path))); |
| |
| auto data_apexs = instance.GetDataApexFiles(); |
| auto normal_apex = |
| ApexFile::Open(StringPrintf("%s/apex.apexd_test_v2.apex", data_dir.path)); |
| ASSERT_THAT(data_apexs, |
| UnorderedElementsAre(ApexFileEq(ByRef(*normal_apex)))); |
| } |
| |
| TEST(ApexFileRepositoryTest, AddDataApexPrioritizeNonDecompressedApex) { |
| // Prepare test data. |
| TemporaryDir built_in_dir, data_dir, decompression_dir; |
| PrepareCompressedApex("com.android.apex.compressed.v1.capex", |
| built_in_dir.path, decompression_dir.path); |
| fs::copy(GetTestFile("com.android.apex.compressed.v1_original.apex"), |
| data_dir.path); |
| |
| ApexFileRepository instance(decompression_dir.path); |
| ASSERT_TRUE(IsOk(instance.AddPreInstalledApex({built_in_dir.path}))); |
| ASSERT_TRUE( |
| IsOk(instance.AddDataApex(data_dir.path, decompression_dir.path))); |
| |
| auto data_apexs = instance.GetDataApexFiles(); |
| auto normal_apex = ApexFile::Open(StringPrintf( |
| "%s/com.android.apex.compressed.v1_original.apex", data_dir.path)); |
| ASSERT_THAT(data_apexs, |
| UnorderedElementsAre(ApexFileEq(ByRef(*normal_apex)))); |
| } |
| |
| TEST(ApexFileRepositoryTest, |
| AddDataApexIgnoreDecompressedApexIfVersionDifferent) { |
| // Prepare test data. Initially put v2 capex in system |
| TemporaryDir fake_built_in_dir, built_in_dir, data_dir, decompression_dir; |
| PrepareCompressedApex("com.android.apex.compressed.v2.capex", |
| fake_built_in_dir.path, decompression_dir.path); |
| // Then copy v1 in built_in_dir |
| fs::copy(GetTestFile("com.android.apex.compressed.v1.capex"), |
| built_in_dir.path); |
| |
| ApexFileRepository instance(decompression_dir.path); |
| ASSERT_TRUE(IsOk(instance.AddPreInstalledApex({built_in_dir.path}))); |
| ASSERT_TRUE( |
| IsOk(instance.AddDataApex(data_dir.path, decompression_dir.path))); |
| |
| auto data_apexs = instance.GetDataApexFiles(); |
| ASSERT_EQ(data_apexs.size(), 0u); |
| } |
| |
| TEST(ApexFileRepositoryTest, |
| AddDataApexIgnoreDecompressedApexIfSystemUncompressed) { |
| // Prepare test data. Initially put v1 capex in system |
| TemporaryDir fake_built_in_dir, built_in_dir, data_dir, decompression_dir; |
| PrepareCompressedApex("com.android.apex.compressed.v1.capex", |
| fake_built_in_dir.path, decompression_dir.path); |
| // Then copy uncompressed apex in built_in_dir |
| fs::copy(GetTestFile("com.android.apex.compressed.v1_original.apex"), |
| built_in_dir.path); |
| |
| ApexFileRepository instance(decompression_dir.path); |
| ASSERT_TRUE(IsOk(instance.AddPreInstalledApex({built_in_dir.path}))); |
| ASSERT_TRUE( |
| IsOk(instance.AddDataApex(data_dir.path, decompression_dir.path))); |
| |
| auto data_apexs = instance.GetDataApexFiles(); |
| ASSERT_EQ(data_apexs.size(), 0u); |
| } |
| |
| TEST(ApexFileRepositoryTest, AddDataApexIgnoreWrongPublicKey) { |
| // Prepare test data. |
| TemporaryDir built_in_dir, data_dir, decompression_dir; |
| fs::copy(GetTestFile("apex.apexd_test.apex"), built_in_dir.path); |
| fs::copy(GetTestFile("apex.apexd_test_different_key.apex"), data_dir.path); |
| |
| ApexFileRepository instance; |
| ASSERT_TRUE(IsOk(instance.AddPreInstalledApex({built_in_dir.path}))); |
| ASSERT_TRUE( |
| IsOk(instance.AddDataApex(data_dir.path, decompression_dir.path))); |
| |
| auto data_apexs = instance.GetDataApexFiles(); |
| ASSERT_EQ(data_apexs.size(), 0u); |
| } |
| |
| TEST(ApexFileRepositoryTest, GetPreInstalledApexFiles) { |
| // Prepare test data. |
| TemporaryDir built_in_dir; |
| fs::copy(GetTestFile("apex.apexd_test.apex"), built_in_dir.path); |
| fs::copy(GetTestFile("com.android.apex.compressed.v1.capex"), |
| built_in_dir.path); |
| |
| ApexFileRepository instance; |
| ASSERT_TRUE(IsOk(instance.AddPreInstalledApex({built_in_dir.path}))); |
| |
| auto pre_installed_apexs = instance.GetPreInstalledApexFiles(); |
| auto pre_apex_1 = ApexFile::Open( |
| StringPrintf("%s/apex.apexd_test.apex", built_in_dir.path)); |
| auto pre_apex_2 = ApexFile::Open(StringPrintf( |
| "%s/com.android.apex.compressed.v1.capex", built_in_dir.path)); |
| ASSERT_THAT(pre_installed_apexs, |
| UnorderedElementsAre(ApexFileEq(ByRef(*pre_apex_1)), |
| ApexFileEq(ByRef(*pre_apex_2)))); |
| } |
| |
| TEST(ApexFileRepositoryTest, AllApexFilesByName) { |
| TemporaryDir built_in_dir, decompression_dir; |
| fs::copy(GetTestFile("apex.apexd_test.apex"), built_in_dir.path); |
| fs::copy(GetTestFile("com.android.apex.cts.shim.apex"), built_in_dir.path); |
| fs::copy(GetTestFile("com.android.apex.compressed.v1.capex"), |
| built_in_dir.path); |
| ApexFileRepository instance; |
| ASSERT_TRUE(IsOk(instance.AddPreInstalledApex({built_in_dir.path}))); |
| |
| TemporaryDir data_dir; |
| fs::copy(GetTestFile("com.android.apex.cts.shim.v2.apex"), data_dir.path); |
| ASSERT_TRUE( |
| IsOk(instance.AddDataApex(data_dir.path, decompression_dir.path))); |
| |
| auto result = instance.AllApexFilesByName(); |
| |
| // Verify the contents of result |
| auto apexd_test_file = ApexFile::Open( |
| StringPrintf("%s/apex.apexd_test.apex", built_in_dir.path)); |
| auto shim_v1 = ApexFile::Open( |
| StringPrintf("%s/com.android.apex.cts.shim.apex", built_in_dir.path)); |
| auto compressed_apex = ApexFile::Open(StringPrintf( |
| "%s/com.android.apex.compressed.v1.capex", built_in_dir.path)); |
| auto shim_v2 = ApexFile::Open( |
| StringPrintf("%s/com.android.apex.cts.shim.v2.apex", data_dir.path)); |
| |
| ASSERT_EQ(result.size(), 3u); |
| ASSERT_THAT(result[apexd_test_file->GetManifest().name()], |
| UnorderedElementsAre(ApexFileEq(ByRef(*apexd_test_file)))); |
| ASSERT_THAT(result[shim_v1->GetManifest().name()], |
| UnorderedElementsAre(ApexFileEq(ByRef(*shim_v1)), |
| ApexFileEq(ByRef(*shim_v2)))); |
| ASSERT_THAT(result[compressed_apex->GetManifest().name()], |
| UnorderedElementsAre(ApexFileEq(ByRef(*compressed_apex)))); |
| } |
| |
| TEST(ApexFileRepositoryTest, GetPreInstalledApex) { |
| // Prepare test data. |
| TemporaryDir built_in_dir; |
| fs::copy(GetTestFile("apex.apexd_test.apex"), built_in_dir.path); |
| |
| ApexFileRepository instance; |
| ASSERT_TRUE(IsOk(instance.AddPreInstalledApex({built_in_dir.path}))); |
| |
| auto apex = ApexFile::Open( |
| StringPrintf("%s/apex.apexd_test.apex", built_in_dir.path)); |
| ASSERT_RESULT_OK(apex); |
| |
| auto ret = instance.GetPreInstalledApex("com.android.apex.test_package"); |
| ASSERT_THAT(ret, ApexFileEq(ByRef(*apex))); |
| } |
| |
| TEST(ApexFileRepositoryTest, GetApexFileWithPath) { |
| // Prepare test data. |
| TemporaryDir built_in_dir; |
| fs::copy(GetTestFile("apex.apexd_test.apex"), built_in_dir.path); |
| |
| ApexFileRepository instance; |
| ASSERT_TRUE(IsOk(instance.AddPreInstalledApex({built_in_dir.path}))); |
| |
| auto apex_path = StringPrintf("%s/apex.apexd_test.apex", built_in_dir.path); |
| auto apex = ApexFile::Open(apex_path); |
| ASSERT_RESULT_OK(apex); |
| |
| auto ret = instance.GetApexFile(apex_path); |
| ASSERT_THAT(ret, Optional(ApexFileEq(ByRef(*apex)))); |
| } |
| |
| TEST(ApexFileRepositoryTest, GetApexFileReturnsNulloptWithUnknownPath) { |
| // Prepare test data. |
| TemporaryDir built_in_dir; |
| fs::copy(GetTestFile("apex.apexd_test.apex"), built_in_dir.path); |
| |
| ApexFileRepository instance; |
| ASSERT_TRUE(IsOk(instance.AddPreInstalledApex({built_in_dir.path}))); |
| |
| auto apex_path = StringPrintf("%s/apex.apexd_test.apex", built_in_dir.path); |
| auto apex = ApexFile::Open(apex_path); |
| ASSERT_RESULT_OK(apex); |
| |
| auto ret = instance.GetApexFile(apex_path + ".wrong"); |
| ASSERT_THAT(ret, Eq(std::nullopt)); |
| } |
| |
| TEST(ApexFileRepositoryTest, GetPreInstalledApexNoSuchApexAborts) { |
| ASSERT_DEATH( |
| { |
| ApexFileRepository instance; |
| instance.GetPreInstalledApex("whatever"); |
| }, |
| ""); |
| } |
| |
| struct ApexFileRepositoryTestAddBlockApex : public ::testing::Test { |
| TemporaryDir test_dir; |
| |
| void WriteMicrodroidSignature(const std::string& signature_path, |
| const std::vector<std::string>& apex_paths) { |
| android::microdroid::MicrodroidSignature signature; |
| signature.set_version(1); |
| for (const auto& apex_path : apex_paths) { |
| auto apex = signature.add_apexes(); |
| apex->set_name(apex_path); // no strict rule for now; use the file_name |
| apex->set_size(GetFileSize(apex_path)); |
| } |
| std::ofstream out(signature_path); |
| android::microdroid::WriteMicrodroidSignature(signature, out); |
| } |
| size_t GetFileSize(const std::string& file_name) { |
| struct stat st; |
| CHECK(stat(file_name.c_str(), &st) == 0); |
| return st.st_size; |
| } |
| }; |
| |
| TEST_F(ApexFileRepositoryTestAddBlockApex, |
| ScansPayloadDisksAndAddApexFilesToPreInstalled) { |
| // prepare payload disk |
| // <test-dir>/vdc1 : signature |
| // /vdc2 : apex.apexd_test.apex |
| // /vdc3 : apex.apexd_test_different_app.apex |
| |
| const auto& test_apex_foo = GetTestFile("apex.apexd_test.apex"); |
| const auto& test_apex_bar = GetTestFile("apex.apexd_test_different_app.apex"); |
| |
| const std::string signature_partition_path = test_dir.path + "/vdc1"s; |
| const std::string apex_foo_path = test_dir.path + "/vdc2"s; |
| const std::string apex_bar_path = test_dir.path + "/vdc3"s; |
| |
| WriteMicrodroidSignature(signature_partition_path, |
| {test_apex_foo, test_apex_bar}); |
| fs::copy(test_apex_foo, apex_foo_path); |
| fs::copy(test_apex_bar, apex_bar_path); |
| |
| // call ApexFileRepository::AddBlockApex() |
| ApexFileRepository instance; |
| auto status = instance.AddBlockApex(signature_partition_path); |
| ASSERT_RESULT_OK(status); |
| |
| // "block" apexes are treated as "pre-installed" |
| auto apex_foo = ApexFile::Open(apex_foo_path); |
| ASSERT_RESULT_OK(apex_foo); |
| auto ret_foo = instance.GetPreInstalledApex("com.android.apex.test_package"); |
| ASSERT_THAT(ret_foo, ApexFileEq(ByRef(*apex_foo))); |
| |
| auto apex_bar = ApexFile::Open(apex_bar_path); |
| ASSERT_RESULT_OK(apex_bar); |
| auto ret_bar = |
| instance.GetPreInstalledApex("com.android.apex.test_package_2"); |
| ASSERT_THAT(ret_bar, ApexFileEq(ByRef(*apex_bar))); |
| } |
| |
| TEST_F(ApexFileRepositoryTestAddBlockApex, |
| ScansOnlySpecifiedInSignaturePartition) { |
| // prepare payload disk |
| // <test-dir>/vdc1 : signature with apex.apexd_test.apex only |
| // /vdc2 : apex.apexd_test.apex |
| // /vdc3 : apex.apexd_test_different_app.apex |
| |
| const auto& test_apex_foo = GetTestFile("apex.apexd_test.apex"); |
| const auto& test_apex_bar = GetTestFile("apex.apexd_test_different_app.apex"); |
| |
| const std::string signature_partition_path = test_dir.path + "/vdc1"s; |
| const std::string apex_foo_path = test_dir.path + "/vdc2"s; |
| const std::string apex_bar_path = test_dir.path + "/vdc3"s; |
| |
| // signature lists only "foo" |
| WriteMicrodroidSignature(signature_partition_path, {test_apex_foo}); |
| fs::copy(test_apex_foo, apex_foo_path); |
| fs::copy(test_apex_bar, apex_bar_path); |
| |
| // call ApexFileRepository::AddBlockApex() |
| ApexFileRepository instance; |
| auto status = instance.AddBlockApex(signature_partition_path); |
| ASSERT_RESULT_OK(status); |
| |
| // foo is added, but bar is not |
| auto ret_foo = instance.GetPreinstalledPath("com.android.apex.test_package"); |
| ASSERT_TRUE(IsOk(ret_foo)); |
| ASSERT_EQ(apex_foo_path, *ret_foo); |
| auto ret_bar = |
| instance.GetPreinstalledPath("com.android.apex.test_package_2"); |
| ASSERT_FALSE(IsOk(ret_bar)); |
| } |
| |
| TEST_F(ApexFileRepositoryTestAddBlockApex, FailsWhenTheresDuplicateNames) { |
| // prepare payload disk |
| // <test-dir>/vdc1 : signature with apex.apexd_test.apex only |
| // /vdc2 : apex.apexd_test.apex |
| // /vdc3 : apex.apexd_test_v2.apex |
| |
| const auto& test_apex_foo = GetTestFile("apex.apexd_test.apex"); |
| const auto& test_apex_bar = GetTestFile("apex.apexd_test_v2.apex"); |
| |
| const std::string signature_partition_path = test_dir.path + "/vdc1"s; |
| const std::string apex_foo_path = test_dir.path + "/vdc2"s; |
| const std::string apex_bar_path = test_dir.path + "/vdc3"s; |
| |
| // signature lists only "foo" |
| WriteMicrodroidSignature(signature_partition_path, |
| {test_apex_foo, test_apex_bar}); |
| fs::copy(test_apex_foo, apex_foo_path); |
| fs::copy(test_apex_bar, apex_bar_path); |
| |
| // call ApexFileRepository::AddBlockApex() |
| ApexFileRepository instance; |
| auto status = instance.AddBlockApex(signature_partition_path); |
| ASSERT_FALSE(IsOk(status)); |
| } |
| |
| } // namespace apex |
| } // namespace android |