| /* |
| * 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 "apexd.h" |
| |
| #include <android-base/file.h> |
| #include <android-base/properties.h> |
| #include <android-base/result-gmock.h> |
| #include <android-base/scopeguard.h> |
| #include <android-base/stringprintf.h> |
| #include <android-base/unique_fd.h> |
| #include <gmock/gmock-matchers.h> |
| #include <gmock/gmock.h> |
| #include <gtest/gtest.h> |
| #include <libdm/dm.h> |
| #include <microdroid/metadata.h> |
| #include <selinux/selinux.h> |
| #include <sys/stat.h> |
| #include <unistd.h> |
| |
| #include <chrono> |
| #include <functional> |
| #include <optional> |
| #include <string> |
| #include <thread> |
| #include <tuple> |
| #include <unordered_set> |
| #include <vector> |
| |
| #include "apex_constants.h" |
| #include "apex_database.h" |
| #include "apex_file.h" |
| #include "apex_file_repository.h" |
| #include "apex_manifest.pb.h" |
| #include "apexd_checkpoint.h" |
| #include "apexd_dm.h" |
| #include "apexd_image_manager.h" |
| #include "apexd_loop.h" |
| #include "apexd_metrics.h" |
| #include "apexd_session.h" |
| #include "apexd_test_utils.h" |
| #include "apexd_utils.h" |
| #include "apexd_verity.h" |
| #include "com_android_apex.h" |
| #include "com_android_apex_flags.h" |
| |
| namespace android { |
| namespace apex { |
| |
| using namespace std::literals; |
| namespace fs = std::filesystem; |
| namespace flags = com::android::apex::flags; |
| |
| using MountedApexData = MountedApexDatabase::MountedApexData; |
| using android::apex::testing::ApexFileEq; |
| using android::base::Error; |
| using android::base::GetExecutableDirectory; |
| using android::base::GetProperty; |
| using android::base::Join; |
| using android::base::make_scope_guard; |
| using android::base::ReadFileToString; |
| using android::base::ReadFully; |
| using android::base::RemoveFileIfExists; |
| using android::base::Result; |
| using android::base::SetProperty; |
| using android::base::Split; |
| using android::base::StringPrintf; |
| using android::base::unique_fd; |
| using android::base::WriteStringToFile; |
| using android::base::testing::HasError; |
| using android::base::testing::HasValue; |
| using android::base::testing::Ok; |
| using android::base::testing::WithCode; |
| using android::base::testing::WithMessage; |
| using android::dm::DeviceMapper; |
| using ::apex::proto::SessionState; |
| using com::android::apex::testing::ApexInfoXmlEq; |
| using ::testing::Contains; |
| using ::testing::ElementsAre; |
| using ::testing::EndsWith; |
| using ::testing::Eq; |
| using ::testing::Field; |
| using ::testing::HasSubstr; |
| using ::testing::IsEmpty; |
| using ::testing::Not; |
| using ::testing::Optional; |
| using ::testing::Pointwise; |
| using ::testing::Property; |
| using ::testing::SizeIs; |
| using ::testing::StartsWith; |
| using ::testing::UnorderedElementsAre; |
| using ::testing::UnorderedElementsAreArray; |
| using ::testing::internal::CaptureStderr; |
| using ::testing::internal::GetCapturedStderr; |
| |
| #define OR_FAIL(expr) \ |
| UNWRAP_OR_DO(__or_fail, expr, { ASSERT_THAT(__or_fail, Ok()); }) |
| |
| static int64_t GetMTime(const std::string& path) { |
| struct stat st_buf; |
| if (stat(path.c_str(), &st_buf) != 0) { |
| PLOG(ERROR) << "Failed to stat " << path; |
| return 0; |
| } |
| return st_buf.st_mtime; |
| } |
| |
| static int64_t GetSizeByBlocks(const std::string& path) { |
| struct stat st_buf; |
| if (stat(path.c_str(), &st_buf) != 0) { |
| PLOG(ERROR) << "Failed to stat " << path; |
| return 0; |
| } |
| return st_buf.st_blocks * st_buf.st_blksize; |
| } |
| |
| static Result<ApexFile> GetActivePackage(const std::string& packageName) { |
| std::vector<ApexFile> packages = GetActivePackages(); |
| for (ApexFile& apex : packages) { |
| if (apex.GetManifest().name() == packageName) { |
| return std::move(apex); |
| } |
| } |
| |
| return base::ErrnoError() |
| << "Cannot find matching package for: " << packageName; |
| } |
| |
| // A very basic mock of CheckpointInterface. |
| class MockCheckpointInterface : public CheckpointInterface { |
| public: |
| Result<bool> SupportsFsCheckpoints() override { |
| return supports_fs_checkpoint_; |
| } |
| |
| Result<bool> NeedsCheckpoint() override { return needs_checkpoint_; } |
| |
| Result<bool> NeedsRollback() override { return needs_rollback_; } |
| |
| Result<void> AbortChanges(const std::string& msg, bool retry) override { |
| return {}; |
| } |
| |
| void SetSupportsCheckpoint(bool value) { supports_fs_checkpoint_ = value; } |
| |
| void SetNeedsCheckpoint(bool value) { needs_checkpoint_ = value; } |
| |
| void SetNeedsRollback(bool value) { needs_rollback_ = value; } |
| |
| private: |
| bool supports_fs_checkpoint_, needs_checkpoint_, needs_rollback_; |
| }; |
| |
| static constexpr const char* kTestApexdStatusSysprop = "apexd.status.test"; |
| static constexpr const char* kTestApexdChangedActiveApexesSysprop = |
| "apexd.test.changed_active_apexes"; |
| static constexpr const char* kTestVmPayloadMetadataPartitionProp = |
| "apexd.vm.payload_metadata_partition.test"; |
| |
| static constexpr const char* kTestActiveApexSelinuxCtx = |
| "u:object_r:shell_data_file:s0"; |
| |
| // 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); |
| partition_ = ApexPartition::System; |
| partition_string_ = "SYSTEM"; |
| block_partition_string_ = "SYSTEM"; |
| 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); |
| staged_session_dir_ = StringPrintf("%s/staged-session-dir", td_.path); |
| |
| sessions_metadata_dir_ = |
| StringPrintf("%s/metadata-staged-session-dir", td_.path); |
| session_manager_ = ApexSessionManager::Create(sessions_metadata_dir_); |
| |
| metadata_images_dir_ = StringPrintf("%s/metadata-images", td_.path); |
| data_images_dir_ = StringPrintf("%s/data-images", td_.path); |
| image_manager_ = |
| ApexImageManager::Create(metadata_images_dir_, data_images_dir_); |
| metadata_config_dir_ = StringPrintf("%s/metadata-config", td_.path); |
| brand_new_config_dir_ = StringPrintf("%s/brand-new-config", td_.path); |
| |
| config_ = ApexdConfig{ |
| kTestApexdStatusSysprop, |
| kTestApexdChangedActiveApexesSysprop, |
| {{partition_, built_in_dir_}}, |
| data_dir_.c_str(), |
| decompression_dir_.c_str(), |
| ota_reserved_dir_.c_str(), |
| staged_session_dir_.c_str(), |
| kTestVmPayloadMetadataPartitionProp, |
| kTestActiveApexSelinuxCtx, |
| {{partition_, brand_new_config_dir_}}, /* brand_new_apex_config_dirs */ |
| flags::mount_before_data(), |
| metadata_config_dir_.c_str(), |
| }; |
| } |
| |
| const std::string& GetBuiltInDir() { return built_in_dir_; } |
| ApexPartition GetPartition() { return partition_; } |
| const std::string& GetPartitionString() { return partition_string_; } |
| const std::string& GetBlockPartitionString() { |
| return block_partition_string_; |
| } |
| 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 GetStagedDir(int session_id) { |
| return StringPrintf("%s/session_%d", staged_session_dir_.c_str(), |
| session_id); |
| } |
| ApexSessionManager* GetSessionManager() { return session_manager_.get(); } |
| |
| std::string GetRootDigest(const ApexFile& apex) { |
| if (apex.IsCompressed()) { |
| return ""; |
| } |
| auto digest = apex.VerifyApexVerity(apex.GetBundledPublicKey()); |
| if (!digest.ok()) { |
| return ""; |
| } |
| return digest->root_digest; |
| } |
| |
| 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 AddDataApex(const std::string& apex_name, |
| const std::string& target_name) { |
| fs::copy(GetTestFile(apex_name), data_dir_ + "/" + target_name); |
| return StringPrintf("%s/%s", data_dir_.c_str(), target_name.c_str()); |
| } |
| |
| // Returns image name |
| std::string AddPinnedDataApex(const std::string& apex_name) { |
| auto apex_file = ApexFile::Open(GetTestFile(apex_name)); |
| CHECK(apex_file.ok()); |
| auto images = image_manager_->PinApexFiles(Single(*apex_file)); |
| CHECK(images.ok()); |
| |
| // Add it to the ACTIVE |
| auto list = image_manager_->GetApexList(ApexListType::ACTIVE); |
| CHECK(list.ok()); |
| ApexListEntry new_entry{images->at(0), apex_file->GetManifest().name()}; |
| auto update = image_manager_->UpdateApexList( |
| ApexListType::ACTIVE, |
| UpdateApexListWithNewEntries(std::move(*list), {new_entry})); |
| CHECK(update.ok()); |
| |
| return images->at(0); |
| } |
| |
| std::string AddDecompressedApex(const std::string& apex_name) { |
| auto apex_file = ApexFile::Open(GetTestFile(apex_name)); |
| CHECK(apex_file.ok()); |
| std::string target_name = |
| apex_file->GetManifest().name() + "@" + |
| std::to_string(apex_file->GetManifest().version()) + |
| std::string(kDecompressedApexPackageSuffix); |
| fs::copy(GetTestFile(apex_name), decompression_dir_ + "/" + target_name); |
| return StringPrintf("%s/%s", decompression_dir_.c_str(), |
| target_name.c_str()); |
| } |
| |
| // Copies the compressed apex to |built_in_dir| and decompresses it to |
| // |decompressed_dir| and returns both paths as tuple. |
| std::tuple<std::string, std::string> PrepareCompressedApex( |
| const std::string& name, const std::string& built_in_dir) { |
| fs::copy(GetTestFile(name), built_in_dir); |
| auto compressed_file_path = |
| StringPrintf("%s/%s", built_in_dir.c_str(), name.c_str()); |
| auto compressed_apex = ApexFile::Open(compressed_file_path); |
| auto decompressed = |
| ProcessCompressedApex(*compressed_apex, /*is_ota_chroot*/ false); |
| CHECK(decompressed.ok()); |
| return std::make_tuple(compressed_file_path, decompressed->GetPath()); |
| } |
| |
| std::tuple<std::string, std::string> PrepareCompressedApex( |
| const std::string& name) { |
| return PrepareCompressedApex(name, built_in_dir_); |
| } |
| |
| std::string PrepareStagedSession(const std::string& apex_name, |
| int session_id) { |
| auto session_dir = GetStagedDir(session_id); |
| CreateDirIfNeeded(session_dir, 0755); |
| fs::copy(GetTestFile(apex_name), session_dir); |
| return session_dir + "/" + apex_name; |
| } |
| |
| Result<ApexSession> CreateStagedSession(const std::string& apex_name, |
| int session_id) { |
| PrepareStagedSession(apex_name, session_id); |
| auto result = session_manager_->CreateSession(session_id); |
| if (!result.ok()) { |
| return result.error(); |
| } |
| result->SetBuildFingerprint(GetProperty("ro.build.fingerprint", "")); |
| return result; |
| } |
| |
| bool IsMountBeforeDataEnabled() const { return config_.mount_before_data; } |
| |
| 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(staged_session_dir_.c_str(), 0755), 0); |
| ASSERT_EQ(mkdir(sessions_metadata_dir_.c_str(), 0755), 0); |
| ASSERT_EQ(mkdir(metadata_images_dir_.c_str(), 0755), 0); |
| ASSERT_EQ(mkdir(metadata_config_dir_.c_str(), 0755), 0); |
| ASSERT_EQ(mkdir(data_images_dir_.c_str(), 0755), 0); |
| ASSERT_EQ(mkdir(brand_new_config_dir_.c_str(), 0755), 0); |
| |
| // We don't really need for all the test cases, but until we refactor apexd |
| // to use dependency injection instead of this SetConfig approach, it is not |
| // trivial to figure out which test cases need the session manager, so we |
| // initialize it for all of them. |
| InitializeSessionManager(GetSessionManager()); |
| DeleteDirContent(GetSessionsDir()); |
| |
| InitializeImageManager(image_manager_.get()); |
| |
| SetProperty(kTestApexdChangedActiveApexesSysprop, ""); |
| } |
| |
| void TearDown() override { |
| DeleteDirContent(GetSessionsDir()); |
| // Reset vold; some tests changing this might affect other tests. |
| InitializeVold(nullptr); |
| } |
| |
| protected: |
| TemporaryDir td_; |
| std::string built_in_dir_; |
| ApexPartition partition_; |
| std::string partition_string_; |
| std::string block_partition_string_; |
| std::string data_dir_; |
| std::string decompression_dir_; |
| std::string ota_reserved_dir_; |
| |
| std::string staged_session_dir_; |
| std::string sessions_metadata_dir_; |
| std::unique_ptr<ApexSessionManager> session_manager_; |
| |
| std::string metadata_images_dir_; |
| std::string data_images_dir_; |
| std::unique_ptr<ApexImageManager> image_manager_; |
| |
| std::string metadata_config_dir_; |
| std::string brand_new_config_dir_; |
| |
| ApexdConfig config_; |
| }; |
| |
| TEST_F(ApexdUnitTest, VerifyVerityRootDigest) { |
| auto apex_ok = ApexFile::Open(GetTestFile("apex.apexd_test.apex")); |
| ASSERT_THAT(apex_ok, Ok()); |
| ASSERT_THAT(VerifyVerityRootDigest(*apex_ok), Ok()); |
| |
| auto apex_bad = |
| ApexFile::Open(GetTestFile("apex.apexd_test_corrupt_apex.apex")); |
| ASSERT_THAT(apex_bad, Ok()); |
| ASSERT_THAT(VerifyVerityRootDigest(*apex_bad), |
| HasError(WithMessage(HasSubstr("root digest mismatch")))); |
| } |
| |
| TEST_F(ApexdUnitTest, ProcessCompressedApex) { |
| auto compressed_apex = ApexFile::Open( |
| AddPreInstalledApex("com.android.apex.compressed.v1.capex")); |
| |
| auto return_value = ProcessCompressedApex(*compressed_apex, 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_THAT(exists, HasValue(true)); |
| |
| // Assert that return value contains decompressed APEX |
| auto decompressed_apex = ApexFile::Open(decompressed_file_path); |
| ASSERT_THAT(return_value, HasValue(ApexFileEq(*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")); |
| ASSERT_THAT(ProcessCompressedApex(*compressed_apex_mismatch_key, false), |
| Not(Ok())); |
| ASSERT_THAT(ProcessCompressedApex(*compressed_apex_version_mismatch, false), |
| Not(Ok())); |
| } |
| |
| 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.apex")); |
| |
| auto result = |
| ValidateDecompressedApex(std::cref(*capex), std::cref(*decompressed_v1)); |
| ASSERT_THAT(result, Ok()); |
| |
| // 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_THAT( |
| result, |
| HasError(WithMessage(HasSubstr( |
| "Compressed APEX has different version than decompressed APEX")))); |
| |
| // Validation check root digest |
| auto decompressed_v1_different_digest = ApexFile::Open(AddDataApex( |
| "com.android.apex.compressed.v1_different_digest_original.apex")); |
| result = ValidateDecompressedApex( |
| std::cref(*capex), std::cref(*decompressed_v1_different_digest)); |
| ASSERT_THAT(result, HasError(WithMessage(HasSubstr( |
| "does not match with expected root digest")))); |
| |
| // 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_THAT( |
| result, |
| HasError(WithMessage(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")); |
| |
| auto return_value = |
| ProcessCompressedApex(*compressed_apex, /* is_ota_chroot= */ false); |
| ASSERT_THAT(return_value, Ok()); |
| |
| // 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, /* is_ota_chroot= */ false); |
| ASSERT_THAT(return_value, Ok()); |
| |
| // 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")); |
| |
| auto return_value = |
| ProcessCompressedApex(*compressed_apex, /* is_ota_chroot= */ true); |
| ASSERT_THAT(return_value, Ok()); |
| |
| // 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_THAT(exists, HasValue(true)) |
| << decompressed_file_path << " does not exist"; |
| |
| // Assert that return value contains the decompressed APEX |
| auto apex_file = ApexFile::Open(decompressed_file_path); |
| ASSERT_THAT(return_value, HasValue(ApexFileEq(*apex_file))); |
| } |
| |
| // When decompressing APEX, reuse existing OTA APEX |
| TEST_F(ApexdUnitTest, ProcessCompressedApexReuseOtaApex) { |
| // Push a compressed APEX that will fail to decompress |
| auto compressed_apex = ApexFile::Open(AddPreInstalledApex( |
| "com.android.apex.compressed.v1_not_decompressible.capex")); |
| |
| // 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, /* is_ota_chroot= */ false); |
| ASSERT_THAT(return_value, Not(Ok())); |
| |
| // 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.apex"), ota_apex_path); |
| return_value = |
| ProcessCompressedApex(*compressed_apex, /* is_ota_chroot= */ false); |
| ASSERT_THAT(return_value, Ok()); |
| |
| // Ota Apex should be cleaned up |
| ASSERT_THAT(PathExists(ota_apex_path), HasValue(false)); |
| ASSERT_EQ(return_value->GetPath(), |
| StringPrintf("%s/com.android.apex.compressed@1%s", |
| GetDecompressionDir().c_str(), |
| kDecompressedApexPackageSuffix)); |
| } |
| |
| TEST_F(ApexdUnitTest, ShouldAllocateSpaceForDecompression_NewApex) { |
| ApexFileRepository instance; |
| MountedApexDatabase db; |
| |
| // A brand new compressed APEX is being introduced: selected |
| bool result = ShouldAllocateSpaceForDecompression("com.android.brand.new", 1, |
| instance, db); |
| ASSERT_TRUE(result); |
| } |
| |
| TEST_F(ApexdUnitTest, |
| ShouldAllocateSpaceForDecompression_WasNotCompressedBefore) { |
| ApexFileRepository instance; |
| auto preinstalled_path = AddPreInstalledApex("apex.apexd_test.apex"); |
| ASSERT_THAT(instance.AddPreInstalledApex({{GetPartition(), GetBuiltInDir()}}), |
| Ok()); |
| |
| // An existing pre-installed APEX is now compressed in the OTA: selected |
| { |
| MountedApexDatabase db; |
| db.AddMountedApex("com.android.apex.test_package", 1, "", preinstalled_path, |
| "mount_point", "device_name", ""); |
| bool result = ShouldAllocateSpaceForDecompression( |
| "com.android.apex.test_package", 1, instance, db); |
| ASSERT_TRUE(result); |
| } |
| |
| // Even if there is a data apex (lower version) |
| // Include data apex within calculation now |
| auto data_path = AddDataApex("apex.apexd_test_v2.apex"); |
| ASSERT_THAT(instance.AddDataApex(GetDataDir()), Ok()); |
| { |
| MountedApexDatabase db; |
| db.AddMountedApex("com.android.apex.test_package", 2, "", data_path, |
| "mount_point", "device_name", ""); |
| bool result = ShouldAllocateSpaceForDecompression( |
| "com.android.apex.test_package", 3, instance, db); |
| ASSERT_TRUE(result); |
| } |
| |
| // But not if data apex has equal or higher version |
| { |
| MountedApexDatabase db; |
| db.AddMountedApex("com.android.apex.test_package", 2, "", data_path, |
| "mount_point", "device_name", ""); |
| bool result = ShouldAllocateSpaceForDecompression( |
| "com.android.apex.test_package", 2, instance, db); |
| ASSERT_FALSE(result); |
| } |
| } |
| |
| TEST_F(ApexdUnitTest, ShouldAllocateSpaceForDecompression_VersionCompare) { |
| // Prepare fake pre-installed apex |
| ApexFileRepository instance(decompression_dir_); |
| auto [_, decompressed_path] = |
| PrepareCompressedApex("com.android.apex.compressed.v1.capex"); |
| ASSERT_THAT(instance.AddPreInstalledApex({{GetPartition(), GetBuiltInDir()}}), |
| Ok()); |
| // Fake mount |
| MountedApexDatabase db; |
| db.AddMountedApex("com.android.apex.compressed", 1, "", decompressed_path, |
| "mount_point", "device_name", ""); |
| |
| { |
| // New Compressed apex has higher version than decompressed data apex: |
| // selected |
| |
| bool result = ShouldAllocateSpaceForDecompression( |
| "com.android.apex.compressed", 2, instance, db); |
| 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: selected |
| bool result = ShouldAllocateSpaceForDecompression( |
| "com.android.apex.compressed", 1, instance, db); |
| ASSERT_TRUE(result) << "Even with same version, the incoming apex may have " |
| "a different size. Need to decompress"; |
| } |
| |
| { |
| // New Compressed apex has lower version than decompressed data apex: |
| // selected |
| bool result = ShouldAllocateSpaceForDecompression( |
| "com.android.apex.compressed", 0, instance, db); |
| ASSERT_TRUE(result) |
| << "lower version test with decompressed data returned false"; |
| } |
| |
| // Replace decompressed data apex with a higher version |
| auto data_path = AddDataApex("com.android.apex.compressed.v2_original.apex"); |
| ASSERT_THAT(instance.AddDataApex(GetDataDir()), Ok()); |
| db.Reset(); |
| db.AddMountedApex("com.android.apex.compressed", 2, "", data_path, |
| "mount_point", "device_name", ""); |
| { |
| // New Compressed apex has higher version as data apex: selected |
| bool result = ShouldAllocateSpaceForDecompression( |
| "com.android.apex.compressed", 3, instance, db); |
| ASSERT_TRUE(result) << "Higher version test with new data returned false"; |
| } |
| |
| { |
| // New Compressed apex has same version as data apex: not selected |
| bool result = ShouldAllocateSpaceForDecompression( |
| "com.android.apex.compressed", 2, instance, db); |
| ASSERT_FALSE(result) << "Same version test with new data returned true"; |
| } |
| |
| { |
| // New Compressed apex has lower version than data apex: not selected |
| bool result = ShouldAllocateSpaceForDecompression( |
| "com.android.apex.compressed", 1, instance, db); |
| 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_THAT(ReserveSpaceForCompressedApex(100, dest_dir.path), Ok()); |
| auto files = ReadDir(dest_dir.path, [](auto _) { return true; }); |
| ASSERT_THAT(files, Ok()); |
| ASSERT_EQ(files->size(), 1u); |
| EXPECT_EQ(fs::file_size((*files)[0]), 100u); |
| EXPECT_GE(GetSizeByBlocks((*files)[0]), 100u); |
| } |
| |
| TEST_F(ApexdUnitTest, ReserveSpaceForCompressedApexSafeToCallMultipleTimes) { |
| TemporaryDir dest_dir; |
| // Calling ReserveSpaceForCompressedApex multiple times should still create |
| // a single file |
| ASSERT_THAT(ReserveSpaceForCompressedApex(100, dest_dir.path), Ok()); |
| ASSERT_THAT(ReserveSpaceForCompressedApex(100, dest_dir.path), Ok()); |
| auto files = ReadDir(dest_dir.path, [](auto _) { return true; }); |
| ASSERT_THAT(files, Ok()); |
| ASSERT_EQ(files->size(), 1u); |
| EXPECT_EQ(fs::file_size((*files)[0]), 100u); |
| EXPECT_GE(GetSizeByBlocks((*files)[0]), 100u); |
| } |
| |
| TEST_F(ApexdUnitTest, ReserveSpaceForCompressedApexShrinkAndGrow) { |
| TemporaryDir dest_dir; |
| |
| // Create a 100 byte file |
| ASSERT_THAT(ReserveSpaceForCompressedApex(100, dest_dir.path), Ok()); |
| |
| // Should be able to shrink and grow the reserved space |
| ASSERT_THAT(ReserveSpaceForCompressedApex(1000, dest_dir.path), Ok()); |
| |
| auto files = ReadDir(dest_dir.path, [](auto _) { return true; }); |
| ASSERT_THAT(files, Ok()); |
| ASSERT_EQ(files->size(), 1u); |
| EXPECT_EQ(fs::file_size((*files)[0]), 1000u); |
| EXPECT_GE(GetSizeByBlocks((*files)[0]), 1000u); |
| |
| ASSERT_THAT(ReserveSpaceForCompressedApex(10, dest_dir.path), Ok()); |
| files = ReadDir(dest_dir.path, [](auto _) { return true; }); |
| ASSERT_THAT(files, Ok()); |
| ASSERT_EQ(files->size(), 1u); |
| EXPECT_EQ(fs::file_size((*files)[0]), 10u); |
| EXPECT_GE(GetSizeByBlocks((*files)[0]), 10u); |
| } |
| |
| TEST_F(ApexdUnitTest, ReserveSpaceForCompressedApexDeallocateIfPassedZero) { |
| TemporaryDir dest_dir; |
| |
| // Create a file first |
| ASSERT_THAT(ReserveSpaceForCompressedApex(100, dest_dir.path), Ok()); |
| auto files = ReadDir(dest_dir.path, [](auto _) { return true; }); |
| ASSERT_THAT(files, Ok()); |
| ASSERT_EQ(files->size(), 1u); |
| |
| // Should delete the reserved file if size passed is 0 |
| ASSERT_THAT(ReserveSpaceForCompressedApex(0, dest_dir.path), Ok()); |
| files = ReadDir(dest_dir.path, [](auto _) { return true; }); |
| ASSERT_THAT(files, Ok()); |
| ASSERT_EQ(files->size(), 0u); |
| } |
| |
| TEST_F(ApexdUnitTest, ReserveSpaceForCapexCleansOtaApex) { |
| TemporaryDir dest_dir; |
| |
| auto ota_apex_path = StringPrintf( |
| "%s/ota_apex%s", GetDecompressionDir().c_str(), kOtaApexPackageSuffix); |
| auto create_ota_apex = [&]() { |
| // Create an ota_apex first |
| fs::copy(GetTestFile("com.android.apex.compressed.v1.apex"), ota_apex_path); |
| ASSERT_THAT(PathExists(ota_apex_path), HasValue(true)); |
| }; |
| create_ota_apex(); |
| |
| // Should not delete the reserved file if size passed is negative |
| ASSERT_THAT(ReserveSpaceForCompressedApex(-1, dest_dir.path), Not(Ok())); |
| ASSERT_THAT(PathExists(ota_apex_path), HasValue(true)); |
| |
| // Should delete the reserved file if size passed is 0 |
| ASSERT_THAT(ReserveSpaceForCompressedApex(0, dest_dir.path), Ok()); |
| ASSERT_THAT(PathExists(ota_apex_path), HasValue(false)); |
| |
| create_ota_apex(); |
| // Should delete the reserved file if size passed is positive |
| ASSERT_THAT(ReserveSpaceForCompressedApex(10, dest_dir.path), Ok()); |
| ASSERT_THAT(PathExists(ota_apex_path), HasValue(false)); |
| } |
| |
| TEST_F(ApexdUnitTest, ReserveSpaceForCompressedApexErrorForNegativeValue) { |
| TemporaryDir dest_dir; |
| // Should return error if negative value is passed |
| ASSERT_THAT(ReserveSpaceForCompressedApex(-1, dest_dir.path), Not(Ok())); |
| } |
| |
| TEST_F(ApexdUnitTest, GetStagedApexFilesNoChild) { |
| // Create staged session |
| auto apex_session = CreateStagedSession("apex.apexd_test.apex", 123); |
| apex_session->UpdateStateAndCommit(SessionState::STAGED); |
| |
| // Query for its file |
| auto result = GetStagedApexFiles(123, {}); |
| |
| auto apex_file = ApexFile::Open( |
| StringPrintf("%s/apex.apexd_test.apex", GetStagedDir(123).c_str())); |
| ASSERT_THAT(result, HasValue(UnorderedElementsAre(ApexFileEq(*apex_file)))); |
| } |
| |
| TEST_F(ApexdUnitTest, GetStagedApexFilesOnlyStaged) { |
| // Create staged session |
| auto apex_session = CreateStagedSession("apex.apexd_test.apex", 123); |
| apex_session->UpdateStateAndCommit(SessionState::VERIFIED); |
| |
| // Query for its file |
| auto result = GetStagedApexFiles(123, {}); |
| |
| ASSERT_THAT( |
| result, |
| HasError(WithMessage(HasSubstr("Session 123 is not in state STAGED")))); |
| } |
| |
| TEST_F(ApexdUnitTest, GetStagedApexFilesChecksNumberOfApexFiles) { |
| // Create staged session |
| auto apex_session = CreateStagedSession("apex.apexd_test.apex", 123); |
| apex_session->UpdateStateAndCommit(SessionState::STAGED); |
| auto staged_dir = GetStagedDir(123); |
| |
| { |
| // Delete the staged apex file |
| DeleteDirContent(staged_dir); |
| |
| // Query for its file |
| auto result = GetStagedApexFiles(123, {}); |
| ASSERT_THAT(result, HasError(WithMessage(HasSubstr( |
| "Expected exactly one APEX file in directory")))); |
| ASSERT_THAT(result, HasError(WithMessage(HasSubstr("Found: 0")))); |
| } |
| { |
| // Copy multiple files to staged dir |
| fs::copy(GetTestFile("apex.apexd_test.apex"), staged_dir); |
| fs::copy(GetTestFile("apex.apexd_test_v2.apex"), staged_dir); |
| |
| // Query for its file |
| auto result = GetStagedApexFiles(123, {}); |
| ASSERT_THAT(result, HasError(WithMessage(HasSubstr( |
| "Expected exactly one APEX file in directory")))); |
| ASSERT_THAT(result, HasError(WithMessage(HasSubstr("Found: 2")))); |
| } |
| } |
| |
| TEST_F(ApexdUnitTest, GetStagedApexFilesWithChildren) { |
| // Create staged session |
| auto parent_apex_session = CreateStagedSession("apex.apexd_test.apex", 123); |
| parent_apex_session->UpdateStateAndCommit(SessionState::STAGED); |
| auto child_session_1 = CreateStagedSession("apex.apexd_test.apex", 124); |
| auto child_session_2 = CreateStagedSession("apex.apexd_test.apex", 125); |
| |
| // Query for its file |
| auto result = GetStagedApexFiles(123, {124, 125}); |
| |
| ASSERT_THAT(result, Ok()); |
| auto child_apex_file_1 = ApexFile::Open( |
| StringPrintf("%s/apex.apexd_test.apex", GetStagedDir(124).c_str())); |
| auto child_apex_file_2 = ApexFile::Open( |
| StringPrintf("%s/apex.apexd_test.apex", GetStagedDir(125).c_str())); |
| ASSERT_THAT(*result, UnorderedElementsAre(ApexFileEq(*child_apex_file_1), |
| ApexFileEq(*child_apex_file_2))); |
| } |
| |
| // A test fixture to use for tests that mount/unmount apexes. |
| // This also supports test-purpose BlockApex via mount. |
| class ApexdMountTest : public ApexdUnitTest { |
| public: |
| ApexdMountTest() { |
| vm_payload_disk_ = StringPrintf("%s/vm-payload", td_.path); |
| } |
| |
| protected: |
| void SetUp() override { |
| ApexdUnitTest::SetUp(); |
| GetApexDatabaseForTesting().Reset(); |
| ASSERT_THAT(SetUpApexTestEnvironment(), Ok()); |
| } |
| |
| void TearDown() override { |
| SetBlockApexEnabled(false); |
| DeactivateAllPackages(); |
| InitMetrics({}); // reset |
| ApexdUnitTest::TearDown(); |
| } |
| |
| void DeactivateAllPackages() { |
| auto activated = std::vector<std::string>{}; |
| GetApexDatabaseForTesting().ForallMountedApexes( |
| [&](auto pkg, auto data, auto latest) { |
| activated.push_back(data.full_path); |
| }); |
| for (const auto& apex : activated) { |
| if (auto status = DeactivatePackage(apex); !status.ok()) { |
| LOG(ERROR) << "Failed to unmount " << apex << " : " << status.error(); |
| } |
| } |
| } |
| |
| void SetBlockApexEnabled(bool enabled) { |
| // The first partition(1) is "metadata" partition |
| base::SetProperty(kTestVmPayloadMetadataPartitionProp, |
| enabled ? (vm_payload_disk_ + "1") : ""); |
| } |
| |
| std::string AddBlockApex(const std::string& apex_name, |
| const std::string& public_key = "", |
| const std::string& root_digest = "", |
| bool is_factory = true) { |
| auto apex_path = vm_payload_disk_ + std::to_string(block_device_index_++); |
| auto apex_file = GetTestFile(apex_name); |
| AddToMetadata(apex_name, public_key, root_digest, is_factory); |
| // block_apexes_ will be disposed after each test |
| auto block_apex = WriteBlockApex(apex_file, apex_path); |
| if (!block_apex.ok()) { |
| PLOG(ERROR) << block_apex.error(); |
| } |
| block_apexes_.push_back(std::move(*block_apex)); |
| return apex_path; |
| } |
| |
| void AddToMetadata(const std::string& apex_name, |
| const std::string& public_key, |
| const std::string& root_digest, bool is_factory) { |
| android::microdroid::Metadata metadata; |
| // The first partition is metadata partition |
| auto metadata_partition = vm_payload_disk_ + "1"; |
| if (access(metadata_partition.c_str(), F_OK) == 0) { |
| auto result = android::microdroid::ReadMetadata(metadata_partition); |
| ASSERT_THAT(result, Ok()); |
| metadata = *result; |
| } |
| |
| auto apex = metadata.add_apexes(); |
| apex->set_name(apex_name); |
| apex->set_public_key(public_key); |
| apex->set_root_digest(root_digest); |
| apex->set_is_factory(is_factory); |
| |
| std::ofstream out(metadata_partition); |
| ASSERT_THAT(android::microdroid::WriteMetadata(metadata, out), Ok()); |
| } |
| |
| private: |
| MountNamespaceRestorer restorer_; |
| |
| // Block APEX specific stuff. |
| std::string vm_payload_disk_; |
| int block_device_index_ = 2; // "1" is reserved for metadata; |
| // This should be freed before ~MountNamespaceRestorer() because it |
| // switches to the original mount namespace while block apexes are mounted |
| // in test-purpose mount namespace. |
| std::vector<BlockApex> block_apexes_; |
| }; |
| |
| TEST_F(ApexdMountTest, CalculateSizeForCompressedApexEmptyList) { |
| int64_t result = CalculateSizeForCompressedApex({}); |
| ASSERT_EQ(0LL, result); |
| } |
| |
| TEST_F(ApexdMountTest, CalculateSizeForCompressedApex) { |
| auto& instance = ApexFileRepository::GetInstance(); |
| AddPreInstalledApex("com.android.apex.compressed.v1.capex"); |
| ASSERT_THAT(instance.AddPreInstalledApex({{GetPartition(), GetBuiltInDir()}}), |
| Ok()); |
| |
| OnStart(); |
| |
| std::vector<std::tuple<std::string, int64_t, int64_t>> input = { |
| std::make_tuple("new_apex", 1, 1), |
| std::make_tuple("new_apex_2", 1, 2), |
| std::make_tuple("com.android.apex.compressed", 1, 8), |
| }; |
| int64_t result = CalculateSizeForCompressedApex(input); |
| ASSERT_EQ(1 + 2 + 8LL, result); |
| } |
| |
| TEST_F( |
| ApexdMountTest, |
| CalculateSizeForCompressedApex_SkipIfDataApexIsNewerThanOrEqualToPreInstalledApex) { |
| if (IsMountBeforeDataEnabled()) GTEST_SKIP() << "mount_before_data enabled"; |
| |
| auto& instance = ApexFileRepository::GetInstance(); |
| AddPreInstalledApex("com.android.apex.compressed.v1.capex"); |
| AddDataApex("com.android.apex.compressed.v2_original.apex"); |
| ASSERT_THAT(instance.AddPreInstalledApex({{GetPartition(), GetBuiltInDir()}}), |
| Ok()); |
| ASSERT_THAT(instance.AddDataApex(GetDataDir()), Ok()); |
| |
| OnStart(); |
| |
| std::vector<std::tuple<std::string, int64_t, int64_t>> input = { |
| std::make_tuple("new_apex", 1, 1), |
| std::make_tuple("com.android.apex.compressed", 2, 8), // ignored |
| }; |
| int64_t result = CalculateSizeForCompressedApex(input); |
| ASSERT_EQ(1LL, result); |
| } |
| |
| // TODO(b/187864524): cover other negative scenarios. |
| TEST_F(ApexdMountTest, InstallPackageRejectsApexWithoutRebootlessSupport) { |
| std::string file_path = AddPreInstalledApex("apex.apexd_test.apex"); |
| ApexFileRepository::GetInstance().AddPreInstalledApex( |
| {{GetPartition(), GetBuiltInDir()}}); |
| |
| ASSERT_THAT(ActivatePackage(file_path), Ok()); |
| |
| auto ret = |
| InstallPackage(GetTestFile("apex.apexd_test.apex"), /* force= */ false); |
| ASSERT_THAT( |
| ret, |
| HasError(WithMessage(HasSubstr("does not support non-staged update")))); |
| } |
| |
| TEST_F(ApexdMountTest, InstallPackageRejectsNoPreInstalledApex) { |
| auto ret = InstallPackage(GetTestFile("test.rebootless_apex_v1.apex"), |
| /* force= */ false); |
| ASSERT_THAT( |
| ret, HasError(WithMessage(HasSubstr( |
| "No active version found for package test.apex.rebootless")))); |
| } |
| |
| TEST_F(ApexdMountTest, InstallPackageRejectsNoActiveApex) { |
| std::string file_path = AddPreInstalledApex("test.rebootless_apex_v1.apex"); |
| ApexFileRepository::GetInstance().AddPreInstalledApex( |
| {{GetPartition(), GetBuiltInDir()}}); |
| |
| auto ret = InstallPackage(GetTestFile("test.rebootless_apex_v2.apex"), |
| /* force= */ false); |
| ASSERT_THAT( |
| ret, HasError(WithMessage(HasSubstr( |
| "No active version found for package test.apex.rebootless")))); |
| } |
| |
| TEST_F(ApexdMountTest, InstallPackageRejectsManifestMismatch) { |
| std::string file_path = AddPreInstalledApex("test.rebootless_apex_v1.apex"); |
| ApexFileRepository::GetInstance().AddPreInstalledApex( |
| {{GetPartition(), GetBuiltInDir()}}); |
| |
| ASSERT_THAT(ActivatePackage(file_path), Ok()); |
| |
| auto ret = |
| InstallPackage(GetTestFile("test.rebootless_apex_manifest_mismatch.apex"), |
| /* force= */ false); |
| ASSERT_THAT( |
| ret, |
| HasError(WithMessage(HasSubstr( |
| "Manifest inside filesystem does not match manifest outside it")))); |
| } |
| |
| TEST_F(ApexdMountTest, InstallPackageRejectsCorrupted) { |
| std::string file_path = AddPreInstalledApex("test.rebootless_apex_v1.apex"); |
| ApexFileRepository::GetInstance().AddPreInstalledApex( |
| {{GetPartition(), GetBuiltInDir()}}); |
| |
| ASSERT_THAT(ActivatePackage(file_path), Ok()); |
| |
| auto ret = InstallPackage(GetTestFile("test.rebootless_apex_corrupted.apex"), |
| /* force= */ false); |
| ASSERT_THAT(ret, |
| HasError(WithMessage(HasSubstr("Can't verify /dev/block/dm-")))); |
| } |
| |
| TEST_F(ApexdMountTest, InstallPackageRejectsProvidesNativeLibs) { |
| std::string file_path = AddPreInstalledApex("test.rebootless_apex_v1.apex"); |
| ApexFileRepository::GetInstance().AddPreInstalledApex( |
| {{GetPartition(), GetBuiltInDir()}}); |
| |
| ASSERT_THAT(ActivatePackage(file_path), Ok()); |
| |
| auto ret = InstallPackage( |
| GetTestFile("test.rebootless_apex_provides_native_libs.apex"), |
| /* force= */ false); |
| ASSERT_THAT(ret, HasError(WithMessage(HasSubstr(" provides native libs")))); |
| } |
| |
| TEST_F(ApexdMountTest, InstallPackageRejectsJniLibs) { |
| std::string file_path = AddPreInstalledApex("test.rebootless_apex_v1.apex"); |
| ApexFileRepository::GetInstance().AddPreInstalledApex( |
| {{GetPartition(), GetBuiltInDir()}}); |
| |
| ASSERT_THAT(ActivatePackage(file_path), Ok()); |
| |
| auto ret = InstallPackage(GetTestFile("test.rebootless_apex_jni_libs.apex"), |
| /* force= */ false); |
| ASSERT_THAT(ret, HasError(WithMessage(HasSubstr(" requires JNI libs")))); |
| } |
| |
| TEST_F(ApexdMountTest, InstallPackageAcceptsAddRequiredNativeLib) { |
| std::string file_path = AddPreInstalledApex("test.rebootless_apex_v1.apex"); |
| ApexFileRepository::GetInstance().AddPreInstalledApex( |
| {{GetPartition(), GetBuiltInDir()}}); |
| |
| ASSERT_THAT(ActivatePackage(file_path), Ok()); |
| |
| auto ret = |
| InstallPackage(GetTestFile("test.rebootless_apex_add_native_lib.apex"), |
| /* force= */ false); |
| ASSERT_THAT(ret, Ok()); |
| } |
| |
| TEST_F(ApexdMountTest, InstallPackageAcceptsRemoveRequiredNativeLib) { |
| std::string file_path = AddPreInstalledApex("test.rebootless_apex_v1.apex"); |
| ApexFileRepository::GetInstance().AddPreInstalledApex( |
| {{GetPartition(), GetBuiltInDir()}}); |
| |
| ASSERT_THAT(ActivatePackage(file_path), Ok()); |
| |
| auto ret = |
| InstallPackage(GetTestFile("test.rebootless_apex_remove_native_lib.apex"), |
| /* force= */ false); |
| ASSERT_THAT(ret, Ok()); |
| } |
| |
| TEST_F(ApexdMountTest, InstallPackageRejectsAppInApex) { |
| std::string file_path = AddPreInstalledApex("test.rebootless_apex_v1.apex"); |
| ApexFileRepository::GetInstance().AddPreInstalledApex( |
| {{GetPartition(), GetBuiltInDir()}}); |
| |
| ASSERT_THAT(ActivatePackage(file_path), Ok()); |
| |
| auto ret = InstallPackage( |
| GetTestFile("test.rebootless_apex_app_in_apex.apex"), /* force= */ false); |
| ASSERT_THAT(ret, HasError(WithMessage(HasSubstr("contains app inside")))); |
| } |
| |
| TEST_F(ApexdMountTest, InstallPackageRejectsPrivAppInApex) { |
| std::string file_path = AddPreInstalledApex("test.rebootless_apex_v1.apex"); |
| ApexFileRepository::GetInstance().AddPreInstalledApex( |
| {{GetPartition(), GetBuiltInDir()}}); |
| |
| ASSERT_THAT(ActivatePackage(file_path), Ok()); |
| |
| auto ret = |
| InstallPackage(GetTestFile("test.rebootless_apex_priv_app_in_apex.apex"), |
| /* force= */ false); |
| ASSERT_THAT(ret, |
| HasError(WithMessage(HasSubstr("contains priv-app inside")))); |
| } |
| |
| TEST_F(ApexdMountTest, InstallPackagePreInstallVersionActive) { |
| std::string file_path = AddPreInstalledApex("test.rebootless_apex_v1.apex"); |
| ApexFileRepository::GetInstance().AddPreInstalledApex( |
| {{GetPartition(), GetBuiltInDir()}}); |
| |
| ASSERT_THAT(ActivatePackage(file_path), Ok()); |
| |
| { |
| auto active_apex = GetActivePackage("test.apex.rebootless"); |
| ASSERT_THAT(active_apex, Ok()); |
| ASSERT_EQ(active_apex->GetPath(), file_path); |
| } |
| |
| auto ret = InstallPackage(GetTestFile("test.rebootless_apex_v2.apex"), |
| /* force= */ false); |
| ASSERT_THAT(ret, Ok()); |
| |
| auto apex_mounts = GetApexMounts(); |
| ASSERT_THAT(apex_mounts, |
| UnorderedElementsAre("/apex/test.apex.rebootless", |
| "/apex/test.apex.rebootless@2")); |
| |
| // Check that /apex/test.apex.rebootless is a bind mount of |
| // /apex/test.apex.rebootless@2. |
| auto manifest = ReadManifest("/apex/test.apex.rebootless/apex_manifest.pb"); |
| ASSERT_THAT(manifest, Ok()); |
| ASSERT_EQ(2u, manifest->version()); |
| |
| // Check that GetActivePackage correctly reports upgraded version. |
| auto active_apex = GetActivePackage("test.apex.rebootless"); |
| ASSERT_THAT(active_apex, Ok()); |
| ASSERT_EQ(active_apex->GetPath(), ret->GetPath()); |
| |
| // Check that pre-installed APEX is still around |
| ASSERT_EQ(0, access(file_path.c_str(), F_OK)) |
| << "Can't access " << file_path << " : " << strerror(errno); |
| |
| auto& db = GetApexDatabaseForTesting(); |
| // Check that upgraded APEX is mounted on top of dm-verity device. |
| db.ForallMountedApexes( |
| "test.apex.rebootless", [&](const MountedApexData& data, bool latest) { |
| ASSERT_TRUE(latest); |
| ASSERT_EQ(data.full_path, ret->GetPath()); |
| ASSERT_EQ(data.verity_name, "test.apex.rebootless@2_1"); |
| }); |
| } |
| |
| TEST_F(ApexdMountTest, InstallPackagePreInstallVersionActiveSamegrade) { |
| std::string file_path = AddPreInstalledApex("test.rebootless_apex_v1.apex"); |
| ApexFileRepository::GetInstance().AddPreInstalledApex( |
| {{GetPartition(), GetBuiltInDir()}}); |
| |
| ASSERT_THAT(ActivatePackage(file_path), Ok()); |
| |
| { |
| auto active_apex = GetActivePackage("test.apex.rebootless"); |
| ASSERT_THAT(active_apex, Ok()); |
| ASSERT_EQ(active_apex->GetPath(), file_path); |
| } |
| |
| auto ret = InstallPackage(GetTestFile("test.rebootless_apex_v1.apex"), |
| /* force= */ false); |
| ASSERT_THAT(ret, Ok()); |
| |
| auto apex_mounts = GetApexMounts(); |
| ASSERT_THAT(apex_mounts, |
| UnorderedElementsAre("/apex/test.apex.rebootless", |
| "/apex/test.apex.rebootless@1")); |
| |
| // Check that GetActivePackage correctly reports upgraded version. |
| auto active_apex = GetActivePackage("test.apex.rebootless"); |
| ASSERT_THAT(active_apex, Ok()); |
| ASSERT_EQ(active_apex->GetPath(), ret->GetPath()); |
| |
| // Check that pre-installed APEX is still around |
| ASSERT_EQ(0, access(file_path.c_str(), F_OK)) |
| << "Can't access " << file_path << " : " << strerror(errno); |
| |
| auto& db = GetApexDatabaseForTesting(); |
| // Check that upgraded APEX is mounted on top of dm-verity device. |
| db.ForallMountedApexes( |
| "test.apex.rebootless", [&](const MountedApexData& data, bool latest) { |
| ASSERT_TRUE(latest); |
| ASSERT_EQ(data.full_path, ret->GetPath()); |
| ASSERT_EQ(data.verity_name, "test.apex.rebootless@1_1"); |
| }); |
| } |
| |
| TEST_F(ApexdMountTest, InstallPackageUnloadOldApex) { |
| std::string file_path = AddPreInstalledApex("test.rebootless_apex_v1.apex"); |
| ApexFileRepository::GetInstance().AddPreInstalledApex( |
| {{GetPartition(), GetBuiltInDir()}}); |
| |
| bool unloaded = false; |
| bool loaded = false; |
| const std::string prop = "apex.test.apex.rebootless.ready"; |
| std::thread monitor_apex_ready_prop([&]() { |
| unloaded = base::WaitForProperty(prop, "false", 10s); |
| loaded = base::WaitForProperty(prop, "true", 10s); |
| }); |
| |
| ASSERT_THAT(ActivatePackage(file_path), Ok()); |
| |
| auto ret = InstallPackage(GetTestFile("test.rebootless_apex_v2.apex"), |
| /* force= */ false); |
| ASSERT_THAT(ret, Ok()); |
| |
| monitor_apex_ready_prop.join(); |
| ASSERT_TRUE(unloaded); |
| ASSERT_TRUE(loaded); |
| } |
| |
| TEST_F(ApexdMountTest, InstallPackageWithService) { |
| std::string file_path = AddPreInstalledApex("test.rebootless_apex_service_v1.apex"); |
| ApexFileRepository::GetInstance().AddPreInstalledApex( |
| {{GetPartition(), GetBuiltInDir()}}); |
| |
| ASSERT_THAT(ActivatePackage(file_path), Ok()); |
| |
| auto ret = InstallPackage(GetTestFile("test.rebootless_apex_service_v2.apex"), |
| /* force= */ false); |
| ASSERT_THAT(ret, Ok()); |
| auto manifest = ReadManifest("/apex/test.apex.rebootless/apex_manifest.pb"); |
| ASSERT_THAT(manifest, Ok()); |
| ASSERT_EQ(2u, manifest->version()); |
| } |
| |
| TEST_F(ApexdMountTest, InstallPackageDataVersionActive) { |
| AddPreInstalledApex("test.rebootless_apex_v1.apex"); |
| ApexFileRepository::GetInstance().AddPreInstalledApex( |
| {{GetPartition(), GetBuiltInDir()}}); |
| |
| std::string file_path = AddDataApex("test.rebootless_apex_v1.apex"); |
| ASSERT_THAT(ActivatePackage(file_path), Ok()); |
| |
| { |
| auto active_apex = GetActivePackage("test.apex.rebootless"); |
| ASSERT_THAT(active_apex, Ok()); |
| ASSERT_EQ(active_apex->GetPath(), file_path); |
| } |
| |
| auto ret = InstallPackage(GetTestFile("test.rebootless_apex_v2.apex"), |
| /* force= */ false); |
| ASSERT_THAT(ret, Ok()); |
| |
| auto apex_mounts = GetApexMounts(); |
| ASSERT_THAT(apex_mounts, |
| UnorderedElementsAre("/apex/test.apex.rebootless", |
| "/apex/test.apex.rebootless@2")); |
| |
| // Check that /apex/test.apex.rebootless is a bind mount of |
| // /apex/test.apex.rebootless@2. |
| auto manifest = ReadManifest("/apex/test.apex.rebootless/apex_manifest.pb"); |
| ASSERT_THAT(manifest, Ok()); |
| ASSERT_EQ(2u, manifest->version()); |
| |
| // Check that GetActivePackage correctly reports upgraded version. |
| auto active_apex = GetActivePackage("test.apex.rebootless"); |
| ASSERT_THAT(active_apex, Ok()); |
| ASSERT_EQ(active_apex->GetPath(), ret->GetPath()); |
| |
| // Check that previously active APEX was deleted. |
| ASSERT_EQ(-1, access(file_path.c_str(), F_OK)); |
| ASSERT_EQ(ENOENT, errno); |
| |
| auto& db = GetApexDatabaseForTesting(); |
| // Check that upgraded APEX is mounted on top of dm-verity device. |
| db.ForallMountedApexes( |
| "test.apex.rebootless", [&](const MountedApexData& data, bool latest) { |
| ASSERT_TRUE(latest); |
| ASSERT_EQ(data.full_path, ret->GetPath()); |
| ASSERT_EQ(data.verity_name, "test.apex.rebootless@2_1"); |
| }); |
| } |
| |
| TEST_F(ApexdMountTest, InstallPackageResolvesPathCollision) { |
| if (IsMountBeforeDataEnabled()) GTEST_SKIP() << "mount_before_data enabled"; |
| |
| AddPreInstalledApex("test.rebootless_apex_v1.apex"); |
| ApexFileRepository::GetInstance().AddPreInstalledApex( |
| {{GetPartition(), GetBuiltInDir()}}); |
| |
| std::string file_path = AddDataApex("test.rebootless_apex_v1.apex", |
| "test.apex.rebootless@1_1.apex"); |
| ASSERT_THAT(ActivatePackage(file_path), Ok()); |
| |
| { |
| auto active_apex = GetActivePackage("test.apex.rebootless"); |
| ASSERT_THAT(active_apex, Ok()); |
| ASSERT_EQ(active_apex->GetPath(), file_path); |
| } |
| |
| auto ret = InstallPackage(GetTestFile("test.rebootless_apex_v1.apex"), |
| /* force= */ false); |
| ASSERT_THAT(ret, Ok()); |
| |
| auto apex_mounts = GetApexMounts(); |
| ASSERT_THAT(apex_mounts, |
| UnorderedElementsAre("/apex/test.apex.rebootless", |
| "/apex/test.apex.rebootless@1")); |
| |
| // Check that /apex/test.apex.rebootless is a bind mount of |
| // /apex/test.apex.rebootless@2. |
| auto manifest = ReadManifest("/apex/test.apex.rebootless/apex_manifest.pb"); |
| ASSERT_THAT(manifest, Ok()); |
| ASSERT_EQ(1u, manifest->version()); |
| |
| // Check that GetActivePackage correctly reports upgraded version. |
| auto active_apex = GetActivePackage("test.apex.rebootless"); |
| ASSERT_THAT(active_apex, Ok()); |
| ASSERT_EQ(active_apex->GetPath(), ret->GetPath()); |
| |
| // Check that we correctly resolved active apex path collision. |
| ASSERT_EQ(active_apex->GetPath(), |
| GetDataDir() + "/test.apex.rebootless@1_2.apex"); |
| |
| // Check that previously active APEX was deleted. |
| ASSERT_EQ(-1, access(file_path.c_str(), F_OK)); |
| ASSERT_EQ(ENOENT, errno); |
| |
| auto& db = GetApexDatabaseForTesting(); |
| // Check that upgraded APEX is mounted on top of dm-verity device. |
| db.ForallMountedApexes( |
| "test.apex.rebootless", [&](const MountedApexData& data, bool latest) { |
| ASSERT_TRUE(latest); |
| ASSERT_EQ(data.full_path, ret->GetPath()); |
| ASSERT_EQ(data.verity_name, "test.apex.rebootless@1_2"); |
| }); |
| } |
| |
| TEST_F(ApexdMountTest, InstallPackageDataVersionActiveSamegrade) { |
| AddPreInstalledApex("test.rebootless_apex_v1.apex"); |
| ApexFileRepository::GetInstance().AddPreInstalledApex( |
| {{GetPartition(), GetBuiltInDir()}}); |
| |
| std::string file_path = AddDataApex("test.rebootless_apex_v2.apex"); |
| ASSERT_THAT(ActivatePackage(file_path), Ok()); |
| |
| { |
| auto active_apex = GetActivePackage("test.apex.rebootless"); |
| ASSERT_THAT(active_apex, Ok()); |
| ASSERT_EQ(active_apex->GetPath(), file_path); |
| } |
| |
| auto ret = InstallPackage(GetTestFile("test.rebootless_apex_v2.apex"), |
| /* force= */ false); |
| ASSERT_THAT(ret, Ok()); |
| |
| auto apex_mounts = GetApexMounts(); |
| ASSERT_THAT(apex_mounts, |
| UnorderedElementsAre("/apex/test.apex.rebootless", |
| "/apex/test.apex.rebootless@2")); |
| |
| // Check that /apex/test.apex.rebootless is a bind mount of |
| // /apex/test.apex.rebootless@2. |
| auto manifest = ReadManifest("/apex/test.apex.rebootless/apex_manifest.pb"); |
| ASSERT_THAT(manifest, Ok()); |
| ASSERT_EQ(2u, manifest->version()); |
| |
| // Check that GetActivePackage correctly reports upgraded version. |
| auto active_apex = GetActivePackage("test.apex.rebootless"); |
| ASSERT_THAT(active_apex, Ok()); |
| ASSERT_EQ(active_apex->GetPath(), ret->GetPath()); |
| |
| // Check that previously active APEX was deleted. |
| ASSERT_EQ(-1, access(file_path.c_str(), F_OK)); |
| ASSERT_EQ(ENOENT, errno); |
| |
| auto& db = GetApexDatabaseForTesting(); |
| // Check that upgraded APEX is mounted on top of dm-verity device. |
| db.ForallMountedApexes( |
| "test.apex.rebootless", [&](const MountedApexData& data, bool latest) { |
| ASSERT_TRUE(latest); |
| ASSERT_EQ(data.full_path, ret->GetPath()); |
| ASSERT_EQ(data.verity_name, "test.apex.rebootless@2_1"); |
| }); |
| } |
| |
| TEST_F(ApexdMountTest, InstallPackageUnmountFailsPreInstalledApexActive) { |
| std::string file_path = AddPreInstalledApex("test.rebootless_apex_v1.apex"); |
| ApexFileRepository::GetInstance().AddPreInstalledApex( |
| {{GetPartition(), GetBuiltInDir()}}); |
| |
| ASSERT_THAT(ActivatePackage(file_path), Ok()); |
| |
| { |
| auto active_apex = GetActivePackage("test.apex.rebootless"); |
| ASSERT_THAT(active_apex, Ok()); |
| ASSERT_EQ(active_apex->GetPath(), file_path); |
| } |
| |
| unique_fd fd(open("/apex/test.apex.rebootless/apex_manifest.pb", |
| O_RDONLY | O_CLOEXEC)); |
| ASSERT_NE(-1, fd.get()); |
| |
| auto ret = InstallPackage(GetTestFile("test.rebootless_apex_v2.apex"), |
| /* force= */ false); |
| ASSERT_THAT(ret, Not(Ok())); |
| |
| auto apex_mounts = GetApexMounts(); |
| ASSERT_THAT(apex_mounts, |
| UnorderedElementsAre("/apex/test.apex.rebootless", |
| "/apex/test.apex.rebootless@1")); |
| |
| // Check that GetActivePackage correctly reports upgraded version. |
| auto active_apex = GetActivePackage("test.apex.rebootless"); |
| ASSERT_THAT(active_apex, Ok()); |
| ASSERT_EQ(active_apex->GetPath(), file_path); |
| |
| // Check that old APEX is still around |
| ASSERT_EQ(0, access(file_path.c_str(), F_OK)) |
| << "Can't access " << file_path << " : " << strerror(errno); |
| |
| auto& db = GetApexDatabaseForTesting(); |
| // Check that upgraded APEX is mounted on top of dm-verity device. |
| db.ForallMountedApexes("test.apex.rebootless", |
| [&](const MountedApexData& data, bool latest) { |
| ASSERT_TRUE(latest); |
| ASSERT_EQ(data.full_path, file_path); |
| }); |
| } |
| |
| TEST_F(ApexdMountTest, InstallPackageUnmountFailedUpdatedApexActive) { |
| AddPreInstalledApex("test.rebootless_apex_v1.apex"); |
| ApexFileRepository::GetInstance().AddPreInstalledApex( |
| {{GetPartition(), GetBuiltInDir()}}); |
| |
| std::string file_path = AddDataApex("test.rebootless_apex_v1.apex"); |
| |
| ASSERT_THAT(ActivatePackage(file_path), Ok()); |
| |
| { |
| auto active_apex = GetActivePackage("test.apex.rebootless"); |
| ASSERT_THAT(active_apex, Ok()); |
| ASSERT_EQ(active_apex->GetPath(), file_path); |
| } |
| |
| unique_fd fd(open("/apex/test.apex.rebootless/apex_manifest.pb", |
| O_RDONLY | O_CLOEXEC)); |
| ASSERT_NE(-1, fd.get()); |
| |
| auto ret = InstallPackage(GetTestFile("test.rebootless_apex_v2.apex"), |
| /* force= */ false); |
| ASSERT_THAT(ret, Not(Ok())); |
| |
| auto apex_mounts = GetApexMounts(); |
| ASSERT_THAT(apex_mounts, |
| UnorderedElementsAre("/apex/test.apex.rebootless", |
| "/apex/test.apex.rebootless@1")); |
| |
| // Check that GetActivePackage correctly reports old apex. |
| auto active_apex = GetActivePackage("test.apex.rebootless"); |
| ASSERT_THAT(active_apex, Ok()); |
| ASSERT_EQ(active_apex->GetPath(), file_path); |
| |
| // Check that old APEX is still around |
| ASSERT_EQ(0, access(file_path.c_str(), F_OK)) |
| << "Can't access " << file_path << " : " << strerror(errno); |
| |
| auto& db = GetApexDatabaseForTesting(); |
| db.ForallMountedApexes( |
| "test.apex.rebootless", [&](const MountedApexData& data, bool latest) { |
| ASSERT_TRUE(latest); |
| ASSERT_EQ(data.full_path, file_path); |
| ASSERT_EQ(data.verity_name, "test.apex.rebootless@1"); |
| }); |
| } |
| |
| TEST_F(ApexdMountTest, InstallPackageUpdatesApexInfoList) { |
| if (IsMountBeforeDataEnabled()) GTEST_SKIP() << "mount_before_data enabled"; |
| |
| auto apex_1 = AddPreInstalledApex("test.rebootless_apex_v1.apex"); |
| auto apex_2 = AddPreInstalledApex("apex.apexd_test.apex"); |
| ApexFileRepository::GetInstance().AddPreInstalledApex( |
| {{GetPartition(), GetBuiltInDir()}}); |
| |
| OnStart(); |
| ASSERT_EQ(0, access("/apex/apex-info-list.xml", F_OK)); |
| |
| auto ret = InstallPackage(GetTestFile("test.rebootless_apex_v2.apex"), |
| /* force= */ false); |
| ASSERT_THAT(ret, Ok()); |
| |
| 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= */ "test.apex.rebootless", |
| /* modulePath= */ apex_1, |
| /* preinstalledModulePath= */ apex_1, |
| /* versionCode= */ 1, /* versionName= */ "1", |
| /* isFactory= */ true, /* isActive= */ false, GetMTime(apex_1), |
| /* provideSharedApexLibs= */ false, |
| /* partition= */ GetPartitionString()); |
| auto apex_info_xml_2 = com::android::apex::ApexInfo( |
| /* moduleName= */ "com.android.apex.test_package", |
| /* modulePath= */ apex_2, /* preinstalledModulePath= */ apex_2, |
| /* versionCode= */ 1, /* versionName= */ "1", /* isFactory= */ true, |
| /* isActive= */ true, GetMTime(apex_2), |
| /* provideSharedApexLibs= */ false, |
| /* partition= */ GetPartitionString()); |
| auto apex_info_xml_3 = com::android::apex::ApexInfo( |
| /* moduleName= */ "test.apex.rebootless", |
| /* modulePath= */ ret->GetPath(), |
| /* preinstalledModulePath= */ apex_1, |
| /* versionCode= */ 2, /* versionName= */ "2", |
| /* isFactory= */ false, /* isActive= */ true, GetMTime(ret->GetPath()), |
| /* provideSharedApexLibs= */ false, |
| /* partition= */ GetPartitionString()); |
| ASSERT_THAT(info_list->getApexInfo(), |
| UnorderedElementsAre(ApexInfoXmlEq(apex_info_xml_1), |
| ApexInfoXmlEq(apex_info_xml_2), |
| ApexInfoXmlEq(apex_info_xml_3))); |
| } |
| |
| TEST_F(ApexdMountTest, ActivatePackageNoCode) { |
| std::string file_path = AddPreInstalledApex("apex.apexd_test_nocode.apex"); |
| ApexFileRepository::GetInstance().AddPreInstalledApex( |
| {{GetPartition(), GetBuiltInDir()}}); |
| |
| ASSERT_THAT(ActivatePackage(file_path), Ok()); |
| |
| std::string mountinfo; |
| ASSERT_TRUE(ReadFileToString("/proc/self/mountinfo", &mountinfo)); |
| bool found_apex_mountpoint = false; |
| for (const auto& line : Split(mountinfo, "\n")) { |
| std::vector<std::string> tokens = Split(line, " "); |
| // line format: |
| // mnt_id parent_mnt_id major:minor source target option propagation_type |
| // ex) 33 260:19 / /apex rw,nosuid,nodev - |
| if (tokens.size() >= 7 && |
| tokens[4] == "/apex/com.android.apex.test_package@1") { |
| found_apex_mountpoint = true; |
| // Make sure that option contains noexec |
| std::vector<std::string> options = Split(tokens[5], ","); |
| EXPECT_THAT(options, Contains("noexec")); |
| break; |
| } |
| } |
| EXPECT_TRUE(found_apex_mountpoint); |
| } |
| |
| TEST_F(ApexdMountTest, ActivatePackageManifestMissmatch) { |
| std::string file_path = |
| AddPreInstalledApex("apex.apexd_test_manifest_mismatch.apex"); |
| ApexFileRepository::GetInstance().AddPreInstalledApex( |
| {{GetPartition(), GetBuiltInDir()}}); |
| |
| auto status = ActivatePackage(file_path); |
| ASSERT_THAT( |
| status, |
| HasError(WithMessage(HasSubstr( |
| "Manifest inside filesystem does not match manifest outside it")))); |
| } |
| |
| TEST_F(ApexdMountTest, ActivatePackage) { |
| std::string file_path = AddPreInstalledApex("apex.apexd_test.apex"); |
| ApexFileRepository::GetInstance().AddPreInstalledApex( |
| {{GetPartition(), GetBuiltInDir()}}); |
| |
| ASSERT_THAT(ActivatePackage(file_path), Ok()); |
| |
| auto active_apex = GetActivePackage("com.android.apex.test_package"); |
| ASSERT_THAT(active_apex, Ok()); |
| 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_THAT(DeactivatePackage(file_path), Ok()); |
| ASSERT_THAT(GetActivePackage("com.android.apex.test_package"), Not(Ok())); |
| |
| auto new_apex_mounts = GetApexMounts(); |
| ASSERT_EQ(new_apex_mounts.size(), 0u); |
| } |
| |
| TEST_F(ApexdMountTest, ActivatePackageShowsUpInMountedApexDatabase) { |
| std::string file_path = AddPreInstalledApex("apex.apexd_test.apex"); |
| ApexFileRepository::GetInstance().AddPreInstalledApex( |
| {{GetPartition(), GetBuiltInDir()}}); |
| |
| ASSERT_THAT(ActivatePackage(file_path), Ok()); |
| |
| auto active_apex = GetActivePackage("com.android.apex.test_package"); |
| ASSERT_THAT(active_apex, Ok()); |
| 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")); |
| |
| // Check that mounted apex database contains information about our APEX. |
| auto& db = GetApexDatabaseForTesting(); |
| std::optional<MountedApexData> mounted_apex; |
| db.ForallMountedApexes("com.android.apex.test_package", |
| [&](const MountedApexData& d, bool active) { |
| if (active) { |
| mounted_apex.emplace(d); |
| } |
| }); |
| ASSERT_TRUE(mounted_apex) |
| << "Haven't found com.android.apex.test_package in the database of " |
| << "mounted apexes"; |
| } |
| |
| TEST_F(ApexdMountTest, DeactivePackageFreesLoopDevices) { |
| AddPreInstalledApex("apex.apexd_test.apex"); |
| ApexFileRepository::GetInstance().AddPreInstalledApex( |
| {{GetPartition(), GetBuiltInDir()}}); |
| |
| std::string file_path = AddDataApex("apex.apexd_test_v2.apex"); |
| ASSERT_THAT(ActivatePackage(file_path), Ok()); |
| |
| // Get loop devices that were used to mount APEX. |
| auto children = ListChildLoopDevices("com.android.apex.test_package@2"); |
| ASSERT_THAT(children, Ok()); |
| ASSERT_EQ(1u, children->size()) |
| << "Unexpected number of children: " << Join(*children, ","); |
| |
| ASSERT_THAT(DeactivatePackage(file_path), Ok()); |
| for (const auto& loop : *children) { |
| struct loop_info li; |
| unique_fd fd(TEMP_FAILURE_RETRY(open(loop.c_str(), O_RDWR | O_CLOEXEC))); |
| EXPECT_NE(-1, fd.get()) |
| << "Failed to open " << loop << " : " << strerror(errno); |
| EXPECT_EQ(-1, ioctl(fd.get(), LOOP_GET_STATUS, &li)) |
| << loop << " is still alive"; |
| EXPECT_EQ(ENXIO, errno) << "Unexpected errno : " << strerror(errno); |
| } |
| } |
| |
| TEST_F(ApexdMountTest, DeactivePackageTearsDownVerityDevice) { |
| AddPreInstalledApex("apex.apexd_test.apex"); |
| ApexFileRepository::GetInstance().AddPreInstalledApex( |
| {{GetPartition(), GetBuiltInDir()}}); |
| |
| std::string file_path = AddDataApex("apex.apexd_test_v2.apex"); |
| ASSERT_THAT(ActivatePackage(file_path), Ok()); |
| |
| ASSERT_THAT(DeactivatePackage(file_path), Ok()); |
| auto& dm = DeviceMapper::Instance(); |
| ASSERT_EQ(dm::DmDeviceState::INVALID, |
| dm.GetState("com.android.apex.test_package@2")); |
| } |
| |
| 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.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( |
| {{GetPartition(), GetBuiltInDir()}}); |
| ASSERT_THAT(ActivatePackage(active_decompressed_apex), Ok()); |
| ASSERT_THAT(ActivatePackage(active_data_apex), Ok()); |
| // 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(/*also_include_staged_apexes=*/false), 0); |
| |
| 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, GetMTime(apex_path_1), |
| /* provideSharedApexLibs= */ false, |
| /* partition= */ GetPartitionString()); |
| 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, GetMTime(apex_path_2), |
| /* provideSharedApexLibs= */ false, |
| /* partition= */ GetPartitionString()); |
| ASSERT_THAT(info_list->getApexInfo(), |
| UnorderedElementsAre(ApexInfoXmlEq(apex_info_xml_1), |
| ApexInfoXmlEq(apex_info_xml_2))); |
| } |
| |
| TEST_F(ApexdMountTest, OnOtaChrootBootstrap_ActivatesPinnedApex_UnmountAll) { |
| if constexpr (!flags::mount_before_data()) { |
| GTEST_SKIP() << "mount_before_data flag not set"; |
| } |
| AddPreInstalledApex("apex.apexd_test.apex"); |
| auto image = AddPinnedDataApex("apex.apexd_test_v2.apex"); |
| AddPreInstalledApex("apex.apexd_test_different_app.apex"); |
| |
| ASSERT_EQ(OnOtaChrootBootstrap(/*also_include_staged_apexes=*/false), 0); |
| |
| 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")); |
| |
| // UnmountAll unmounts pinned APEX as well. |
| ASSERT_EQ(UnmountAll(), 0); |
| ASSERT_THAT(GetApexMounts(), IsEmpty()); |
| |
| // The dm-linear device created for the APEX is not destroyed. |
| ASSERT_THAT(DeleteDmDevice(image, /*deferred=*/false), Ok()); |
| } |
| |
| TEST_F(ApexdMountTest, OnOtaChrootBootstrap_WithStaged_ActivatesPinnedApex) { |
| if constexpr (!flags::mount_before_data()) { |
| GTEST_SKIP() << "mount_before_data flag not set"; |
| } |
| // preinstalled: V1 |
| AddPreInstalledApex("apex.apexd_test.apex"); |
| // pinned data: V1 |
| auto image = AddPinnedDataApex("apex.apexd_test.apex"); |
| // staged: V2 |
| auto apex_session = CreateStagedSession("apex.apexd_test_v2.apex", 123); |
| apex_session->UpdateStateAndCommit(SessionState::STAGED); |
| |
| ASSERT_EQ(OnOtaChrootBootstrap(/*also_include_staged_apexes=*/true), 0); |
| |
| // Verify that the staged one (V2) is chosen. |
| auto apex_mounts = GetApexMounts(); |
| ASSERT_THAT(apex_mounts, |
| UnorderedElementsAre("/apex/com.android.apex.test_package", |
| "/apex/com.android.apex.test_package@2")); |
| |
| // UnmountAll unmounts pinned APEX as well. |
| ASSERT_EQ(UnmountAll(), 0); |
| ASSERT_THAT(GetApexMounts(), IsEmpty()); |
| |
| // The dm-linear device created for the APEX is not destroyed. |
| ASSERT_THAT(DeleteDmDevice(image, /*deferred=*/false), Ok()); |
| } |
| |
| TEST_F(ApexdMountTest, OnOtaChrootBootstrapFailsToScanPreInstalledApexes) { |
| AddPreInstalledApex("apex.apexd_test.apex"); |
| AddPreInstalledApex("apex.apexd_test_corrupt_superblock_apex.apex"); |
| |
| ASSERT_EQ(OnOtaChrootBootstrap(/*also_include_staged_apexes=*/false), 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(/*also_include_staged_apexes=*/false), 0); |
| |
| 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, GetMTime(apex_path_1), |
| /* provideSharedApexLibs= */ false, |
| /* partition= */ GetPartitionString()); |
| 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, GetMTime(apex_path_2), |
| /* provideSharedApexLibs= */ false, |
| /* partition= */ GetPartitionString()); |
| 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, GetMTime(apex_path_3), |
| /* provideSharedApexLibs= */ false, |
| /* partition= */ GetPartitionString()); |
| 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(/*also_include_staged_apexes=*/false), 0); |
| |
| 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, GetMTime(apex_path_1), |
| /* provideSharedApexLibs= */ false, |
| /* partition= */ GetPartitionString()); |
| 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, GetMTime(apex_path_2), |
| /* provideSharedApexLibs= */ false, |
| /* partition= */ GetPartitionString()); |
| 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, GetMTime(apex_path_3), |
| /* provideSharedApexLibs= */ false, |
| /* partition= */ GetPartitionString()); |
| 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(/*also_include_staged_apexes=*/false), 0); |
| |
| 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, GetMTime(apex_path_1), |
| /* provideSharedApexLibs= */ false, |
| /* partition= */ GetPartitionString()); |
| 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, GetMTime(apex_path_2), |
| /* provideSharedApexLibs= */ false, |
| /* partition= */ GetPartitionString()); |
| |
| 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(/*also_include_staged_apexes=*/false), 0); |
| |
| 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, GetMTime(apex_path_1), |
| /* provideSharedApexLibs= */ false, |
| /* partition= */ GetPartitionString()); |
| 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, GetMTime(apex_path_2), |
| /* provideSharedApexLibs= */ false, |
| /* partition= */ GetPartitionString()); |
| |
| 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_THAT(apex, Ok()); |
| ASSERT_EQ(static_cast<uint64_t>(apex->GetManifest().version()), 2ULL); |
| } |
| |
| ASSERT_EQ(OnOtaChrootBootstrap(/*also_include_staged_apexes=*/false), 0); |
| |
| 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, GetMTime(apex_path_1), |
| /* provideSharedApexLibs= */ false, |
| /* partition= */ GetPartitionString()); |
| 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, GetMTime(apex_path_2), |
| /* provideSharedApexLibs= */ false, |
| /* partition= */ GetPartitionString()); |
| |
| 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(/*also_include_staged_apexes=*/false), 0); |
| |
| 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, GetMTime(apex_path_1), |
| /* provideSharedApexLibs= */ false, |
| /* partition= */ GetPartitionString()); |
| |
| ASSERT_THAT(info_list->getApexInfo(), |
| UnorderedElementsAre(ApexInfoXmlEq(apex_info_xml_1))); |
| } |
| |
| |
| // 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(/*also_include_staged_apexes=*/false), 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); |
| |
| 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, GetMTime(decompressed_apex), |
| /* provideSharedApexLibs= */ false, |
| /* partition= */ GetPartitionString()); |
| 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.verity_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(/*also_include_staged_apexes=*/false), 0); |
| |
| // Decompressed OTA APEX should be mounted |
| std::string decompressed_ota_apex = |
| StringPrintf("%s/com.android.apex.compressed@1%s", |
| GetDecompressionDir().c_str(), kOtaApexPackageSuffix); |
| |
| // Capture the creation time of the OTA APEX |
| std::error_code ec; |
| auto last_write_time_1 = fs::last_write_time(decompressed_ota_apex, ec); |
| ASSERT_FALSE(ec) << "Failed to capture last write time of " |
| << decompressed_ota_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(/*also_include_staged_apexes=*/false), 0); |
| |
| // Compare write time to ensure we did not decompress again |
| auto last_write_time_2 = fs::last_write_time(decompressed_ota_apex, ec); |
| ASSERT_FALSE(ec) << "Failed to capture last write time of " |
| << decompressed_ota_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(/*also_include_staged_apexes=*/false), 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); |
| |
| 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, |
| GetMTime(decompressed_active_apex), |
| /* provideSharedApexLibs= */ false, |
| /* partition= */ GetPartitionString()); |
| 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.verity_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(/*also_include_staged_apexes=*/false), 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); |
| |
| 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, |
| GetMTime(decompressed_active_apex), |
| /* provideSharedApexLibs= */ false, |
| /* partition= */ GetPartitionString()); |
| 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.verity_name, |
| "com.android.apex.compressed@1.chroot"); |
| }); |
| } |
| |
| // Test when we update existing CAPEX to same version, but different digest |
| TEST_F(ApexdMountTest, OnOtaChrootBootstrapSamegradeCapexDifferentDigest) { |
| TemporaryDir previous_built_in_dir; |
| auto [different_digest_apex_path, _] = PrepareCompressedApex( |
| "com.android.apex.compressed.v1_different_digest.capex", |
| previous_built_in_dir.path); |
| // Place a same version capex in current built_in_dir, which has different |
| // digest |
| auto apex_path = AddPreInstalledApex("com.android.apex.compressed.v1.capex"); |
| |
| ASSERT_EQ(OnOtaChrootBootstrap(/*also_include_staged_apexes=*/false), 0); |
| |
| // New decompressed ota APEX should be mounted with kOtaApexPackageSuffix |
| std::string decompressed_ota_apex = |
| StringPrintf("%s/com.android.apex.compressed@1%s", |
| GetDecompressionDir().c_str(), kOtaApexPackageSuffix); |
| |
| 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_ota_apex, |
| /* preinstalledModulePath= */ apex_path, |
| /* versionCode= */ 1, /* versionName= */ "1", |
| /* isFactory= */ true, /* isActive= */ true, |
| GetMTime(decompressed_ota_apex), |
| /* provideSharedApexLibs= */ false, |
| /* partition= */ GetPartitionString()); |
| 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_ota_apex); |
| ASSERT_EQ(data.verity_name, |
| "com.android.apex.compressed@1.chroot"); |
| }); |
| |
| // Ensure decompressed apex has same digest as pre-installed |
| auto pre_installed_apex = ApexFile::Open(apex_path); |
| auto decompressed_apex = ApexFile::Open(decompressed_ota_apex); |
| auto different_digest_apex = ApexFile::Open(different_digest_apex_path); |
| ASSERT_EQ( |
| pre_installed_apex->GetManifest().capexmetadata().originalapexdigest(), |
| GetRootDigest(*decompressed_apex)); |
| ASSERT_NE( |
| pre_installed_apex->GetManifest().capexmetadata().originalapexdigest(), |
| GetRootDigest(*different_digest_apex)); |
| |
| // Ensure we didn't remove previous decompressed APEX |
| std::string previous_decompressed_apex = StringPrintf( |
| "%s/com.android.apex.compressed@1%s", GetDecompressionDir().c_str(), |
| kDecompressedApexPackageSuffix); |
| auto path_exists = PathExists(previous_decompressed_apex); |
| ASSERT_TRUE(*path_exists); |
| } |
| |
| // 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(/*also_include_staged_apexes=*/false), 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); |
| |
| 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, |
| GetMTime(decompressed_active_apex), |
| /* provideSharedApexLibs= */ false, |
| /* partition= */ GetPartitionString()); |
| 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.verity_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.apex"); |
| |
| ASSERT_EQ(OnOtaChrootBootstrap(/*also_include_staged_apexes=*/false), 0); |
| |
| // New uncompressed APEX should be mounted |
| |
| 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, GetMTime(apex_path), |
| /* provideSharedApexLibs= */ false, |
| /* partition= */ GetPartitionString()); |
| 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(/*also_include_staged_apexes=*/false), 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); |
| |
| 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, |
| GetMTime(decompressed_active_apex), |
| /* provideSharedApexLibs= */ false, |
| /* partition= */ GetPartitionString()); |
| 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(/*also_include_staged_apexes=*/false), 0); |
| |
| // Data APEX should be mounted |
| |
| 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, GetMTime(data_apex_path), |
| /* provideSharedApexLibs= */ false, |
| /* partition= */ GetPartitionString()); |
| 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, GetMTime(system_apex_path), |
| /* provideSharedApexLibs= */ false, |
| /* partition= */ GetPartitionString()); |
| 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.verity_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.apex"); |
| |
| ASSERT_EQ(OnOtaChrootBootstrap(/*also_include_staged_apexes=*/false), 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); |
| |
| 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, |
| GetMTime(decompressed_active_apex), |
| /* provideSharedApexLibs= */ false, |
| /* partition= */ GetPartitionString()); |
| 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.verity_name, |
| "com.android.apex.compressed@2.chroot"); |
| }); |
| } |
| |
| // Test when we update CAPEX and there is a same 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.apex"); |
| |
| ASSERT_EQ(OnOtaChrootBootstrap(/*also_include_staged_apexes=*/false), 0); |
| |
| // Data APEX should be mounted |
| |
| 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, GetMTime(data_apex_path), |
| /* provideSharedApexLibs= */ false, |
| /* partition= */ GetPartitionString()); |
| 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, GetMTime(system_apex_path), |
| /* provideSharedApexLibs= */ false, |
| /* partition= */ GetPartitionString()); |
| 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.verity_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(/*also_include_staged_apexes=*/false), 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); |
| |
| 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, |
| GetMTime(decompressed_active_apex), |
| /* provideSharedApexLibs= */ false, |
| /* partition= */ GetPartitionString()); |
| 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.verity_name, |
| "com.android.apex.compressed@1.chroot"); |
| }); |
| } |
| |
| TEST_F(ApexdMountTest, OnOtaChrootBootstrapSystemDataStagedInSameVersion) { |
| // The APEXes on system, data, and staged are all in the same version. The |
| // staged one should be picked. |
| std::string apex_path_1 = AddPreInstalledApex("apex.apexd_test.apex"); |
| AddDataApex("apex.apexd_test.apex"); |
| auto apex_session = CreateStagedSession("apex.apexd_test.apex", 123); |
| apex_session->UpdateStateAndCommit(SessionState::STAGED); |
| std::string apex_path_3 = |
| GetStagedDir(apex_session->GetId()) + "/" + "apex.apexd_test.apex"; |
| |
| ASSERT_EQ(OnOtaChrootBootstrap(/*also_include_staged_apexes=*/true), 0); |
| |
| 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= */ false, GetMTime(apex_path_1), |
| /* provideSharedApexLibs= */ false, |
| /* partition= */ GetPartitionString()); |
| auto apex_info_xml_2 = 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, GetMTime(apex_path_3), |
| /* provideSharedApexLibs= */ false, |
| /* partition= */ GetPartitionString()); |
| |
| ASSERT_THAT(info_list->getApexInfo(), |
| UnorderedElementsAre(ApexInfoXmlEq(apex_info_xml_1), |
| ApexInfoXmlEq(apex_info_xml_2))); |
| } |
| |
| TEST_F(ApexdMountTest, OnOtaChrootBootstrapSystemNewerThanDataStaged) { |
| // The system one is newer than the data one and the staged one. The system |
| // one should be picked. |
| std::string apex_path_1 = AddPreInstalledApex("apex.apexd_test_v2.apex"); |
| AddDataApex("apex.apexd_test.apex"); |
| auto apex_session = CreateStagedSession("apex.apexd_test.apex", 123); |
| apex_session->UpdateStateAndCommit(SessionState::STAGED); |
| |
| ASSERT_EQ(OnOtaChrootBootstrap(/*also_include_staged_apexes=*/true), 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); |
| 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.test_package", |
| /* modulePath= */ apex_path_1, |
| /* preinstalledModulePath= */ apex_path_1, |
| /* versionCode= */ 2, /* versionName= */ "2", |
| /* isFactory= */ true, /* isActive= */ true, GetMTime(apex_path_1), |
| /* provideSharedApexLibs= */ false, |
| /* partition= */ GetPartitionString()); |
| |
| ASSERT_THAT(info_list->getApexInfo(), |
| UnorderedElementsAre(ApexInfoXmlEq(apex_info_xml))); |
| } |
| |
| 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 = AddDataApex("apex.apexd_test_v2.apex"); |
| |
| ASSERT_EQ(OnOtaChrootBootstrap(/*also_include_staged_apexes=*/false), 0); |
| |
| EXPECT_EQ(GetSelinuxContext("/apex/apex-info-list.xml"), |
| "u:object_r:apex_info_file: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(/*also_include_staged_apexes=*/false), 0); |
| |
| 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.verity_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.verity_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(/*also_include_staged_apexes=*/false), 0); |
| |
| 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, GetMTime(apex_path_1), |
| /* provideSharedApexLibs= */ false, |
| /* partition= */ GetPartitionString()); |
| 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, GetMTime(apex_path_2), |
| /* provideSharedApexLibs= */ false, |
| /* partition= */ GetPartitionString()); |
| |
| 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(/*also_include_staged_apexes=*/false), 0); |
| |
| 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, GetMTime(apex_path_1), |
| /* provideSharedApexLibs= */ false, |
| /* partition= */ GetPartitionString()); |
| 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, GetMTime(apex_path_2), |
| /* provideSharedApexLibs= */ false, |
| /* partition= */ GetPartitionString()); |
| |
| ASSERT_THAT(info_list->getApexInfo(), |
| UnorderedElementsAre(ApexInfoXmlEq(apex_info_xml_1), |
| ApexInfoXmlEq(apex_info_xml_2))); |
| } |
| |
| TEST_F(ApexdMountTest, OnStartSetsStatusAsStarting) { |
| std::string apex_path_1 = AddPreInstalledApex("apex.apexd_test.apex"); |
| ASSERT_THAT(ApexFileRepository::GetInstance().AddPreInstalledApex( |
| {{GetPartition(), GetBuiltInDir()}}), |
| Ok()); |
| OnStart(); |
| ASSERT_EQ(GetProperty(kTestApexdStatusSysprop, ""), "starting"); |
| } |
| |
| TEST_F(ApexdMountTest, OnStartOnlyPreInstalledApexes) { |
| if (IsMountBeforeDataEnabled()) GTEST_SKIP() << "mount_before_data enabled"; |
| |
| std::string apex_path_1 = AddPreInstalledApex("apex.apexd_test.apex"); |
| std::string apex_path_2 = |
| AddPreInstalledApex("apex.apexd_test_different_app.apex"); |
| |
| ASSERT_THAT(ApexFileRepository::GetInstance().AddPreInstalledApex( |
| {{GetPartition(), GetBuiltInDir()}}), |
| Ok()); |
| |
| OnStart(); |
| |
| 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) { |
| if (IsMountBeforeDataEnabled()) GTEST_SKIP() << "mount_before_data enabled"; |
| |
| 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_THAT(ApexFileRepository::GetInstance().AddPreInstalledApex( |
| {{GetPartition(), GetBuiltInDir()}}), |
| Ok()); |
| |
| OnStart(); |
| |
| 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) { |
| if (IsMountBeforeDataEnabled()) GTEST_SKIP() << "mount_before_data enabled"; |
| |
| std::string apex_path = AddPreInstalledApex("com.android.apex.cts.shim.apex"); |
| AddDataApex("com.android.apex.cts.shim.v2_wrong_sha.apex"); |
| |
| ASSERT_THAT(ApexFileRepository::GetInstance().AddPreInstalledApex( |
| {{GetPartition(), GetBuiltInDir()}}), |
| Ok()); |
| |
| 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) { |
| if (IsMountBeforeDataEnabled()) GTEST_SKIP() << "mount_before_data enabled"; |
| |
| 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_THAT(ApexFileRepository::GetInstance().AddPreInstalledApex( |
| {{GetPartition(), GetBuiltInDir()}}), |
| Ok()); |
| |
| OnStart(); |
| |
| 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) { |
| if (IsMountBeforeDataEnabled()) GTEST_SKIP() << "mount_before_data enabled"; |
| |
| 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_THAT(ApexFileRepository::GetInstance().AddPreInstalledApex( |
| {{GetPartition(), GetBuiltInDir()}}), |
| Ok()); |
| |
| OnStart(); |
| |
| 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) { |
| if (IsMountBeforeDataEnabled()) GTEST_SKIP() << "mount_before_data enabled"; |
| |
| 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_THAT(ApexFileRepository::GetInstance().AddPreInstalledApex( |
| {{GetPartition(), GetBuiltInDir()}}), |
| Ok()); |
| |
| OnStart(); |
| |
| 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) { |
| if (IsMountBeforeDataEnabled()) GTEST_SKIP() << "mount_before_data enabled"; |
| |
| 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_THAT(apex, Ok()); |
| ASSERT_EQ(static_cast<uint64_t>(apex->GetManifest().version()), 2ULL); |
| } |
| |
| ASSERT_THAT(ApexFileRepository::GetInstance().AddPreInstalledApex( |
| {{GetPartition(), GetBuiltInDir()}}), |
| Ok()); |
| |
| OnStart(); |
| |
| 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) { |
| if (IsMountBeforeDataEnabled()) GTEST_SKIP() << "mount_before_data enabled"; |
| |
| std::string apex_path_1 = |
| AddPreInstalledApex("com.android.apex.compressed.v1.capex"); |
| |
| ASSERT_THAT(ApexFileRepository::GetInstance().AddPreInstalledApex( |
| {{GetPartition(), GetBuiltInDir()}}), |
| Ok()); |
| |
| OnStart(); |
| |
| // Decompressed APEX should be mounted |
| std::string decompressed_active_apex = StringPrintf( |
| "%s/com.android.apex.compressed@1%s", GetDecompressionDir().c_str(), |
| kDecompressedApexPackageSuffix); |
| |
| 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.verity_name, |
| "com.android.apex.compressed"); |
| }); |
| } |
| |
| TEST_F(ApexdMountTest, OnStartDataHasHigherVersionThanCapex) { |
| if (IsMountBeforeDataEnabled()) GTEST_SKIP() << "mount_before_data enabled"; |
| |
| AddPreInstalledApex("com.android.apex.compressed.v1.capex"); |
| std::string apex_path_2 = |
| AddDataApex("com.android.apex.compressed.v2_original.apex"); |
| |
| ASSERT_THAT(ApexFileRepository::GetInstance().AddPreInstalledApex( |
| {{GetPartition(), GetBuiltInDir()}}), |
| Ok()); |
| |
| OnStart(); |
| |
| 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.verity_name, |
| "com.android.apex.compressed"); |
| }); |
| } |
| |
| TEST_F(ApexdMountTest, OnStartDataHasSameVersionAsCapex) { |
| if (IsMountBeforeDataEnabled()) GTEST_SKIP() << "mount_before_data enabled"; |
| |
| AddPreInstalledApex("com.android.apex.compressed.v1.capex"); |
| std::string apex_path_2 = AddDataApex("com.android.apex.compressed.v1.apex"); |
| |
| ASSERT_THAT(ApexFileRepository::GetInstance().AddPreInstalledApex( |
| {{GetPartition(), GetBuiltInDir()}}), |
| Ok()); |
| |
| OnStart(); |
| |
| // Data APEX should be mounted |
| |
| 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.verity_name, |
| "com.android.apex.compressed"); |
| }); |
| } |
| |
| TEST_F(ApexdMountTest, OnStartSystemHasHigherVersionCapexThanData) { |
| if (IsMountBeforeDataEnabled()) GTEST_SKIP() << "mount_before_data enabled"; |
| |
| std::string apex_path_1 = |
| AddPreInstalledApex("com.android.apex.compressed.v2.capex"); |
| AddDataApex("com.android.apex.compressed.v1.apex"); |
| |
| ASSERT_THAT(ApexFileRepository::GetInstance().AddPreInstalledApex( |
| {{GetPartition(), GetBuiltInDir()}}), |
| Ok()); |
| |
| OnStart(); |
| |
| // Decompressed APEX should be mounted |
| std::string decompressed_active_apex = StringPrintf( |
| "%s/com.android.apex.compressed@2%s", GetDecompressionDir().c_str(), |
| kDecompressedApexPackageSuffix); |
| |
| 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.verity_name, |
| "com.android.apex.compressed"); |
| }); |
| } |
| |
| TEST_F(ApexdMountTest, OnStartFailsToActivateApexOnDataFallsBackToCapex) { |
| if (IsMountBeforeDataEnabled()) GTEST_SKIP() << "mount_before_data enabled"; |
| |
| AddPreInstalledApex("com.android.apex.compressed.v1.capex"); |
| AddDataApex("com.android.apex.compressed.v2_manifest_mismatch.apex"); |
| |
| ASSERT_THAT(ApexFileRepository::GetInstance().AddPreInstalledApex( |
| {{GetPartition(), GetBuiltInDir()}}), |
| Ok()); |
| |
| OnStart(); |
| |
| // Decompressed APEX should be mounted |
| std::string decompressed_active_apex = StringPrintf( |
| "%s/com.android.apex.compressed@1%s", GetDecompressionDir().c_str(), |
| kDecompressedApexPackageSuffix); |
| |
| 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.verity_name, |
| "com.android.apex.compressed"); |
| }); |
| } |
| |
| // Test scenario when we fallback to capex but it already has a decompressed |
| // version on data |
| TEST_F(ApexdMountTest, OnStartFallbackToAlreadyDecompressedCapex) { |
| if (IsMountBeforeDataEnabled()) GTEST_SKIP() << "mount_before_data enabled"; |
| |
| PrepareCompressedApex("com.android.apex.compressed.v1.capex"); |
| AddDataApex("com.android.apex.compressed.v2_manifest_mismatch.apex"); |
| |
| ASSERT_THAT(ApexFileRepository::GetInstance().AddPreInstalledApex( |
| {{GetPartition(), GetBuiltInDir()}}), |
| Ok()); |
| |
| OnStart(); |
| |
| // Decompressed APEX should be mounted |
| std::string decompressed_active_apex = StringPrintf( |
| "%s/com.android.apex.compressed@1%s", GetDecompressionDir().c_str(), |
| kDecompressedApexPackageSuffix); |
| |
| 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.verity_name, |
| "com.android.apex.compressed"); |
| }); |
| } |
| |
| // Test scenario when we fallback to capex but it has same version as corrupt |
| // data apex |
| TEST_F(ApexdMountTest, OnStartFallbackToCapexSameVersion) { |
| if (IsMountBeforeDataEnabled()) GTEST_SKIP() << "mount_before_data enabled"; |
| |
| 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_THAT(ApexFileRepository::GetInstance().AddPreInstalledApex( |
| {{GetPartition(), GetBuiltInDir()}}), |
| Ok()); |
| |
| OnStart(); |
| |
| // Decompressed APEX should be mounted |
| std::string decompressed_active_apex = StringPrintf( |
| "%s/com.android.apex.compressed@2%s", GetDecompressionDir().c_str(), |
| kDecompressedApexPackageSuffix); |
| |
| 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.verity_name, |
| "com.android.apex.compressed"); |
| }); |
| } |
| |
| TEST_F(ApexdMountTest, OnStartCapexToApex) { |
| if (IsMountBeforeDataEnabled()) GTEST_SKIP() << "mount_before_data enabled"; |
| |
| 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.apex"); |
| |
| ASSERT_THAT(ApexFileRepository::GetInstance().AddPreInstalledApex( |
| {{GetPartition(), GetBuiltInDir()}}), |
| Ok()); |
| |
| OnStart(); |
| |
| // Uncompressed APEX should be mounted |
| |
| 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.verity_name, IsEmpty()); |
| }); |
| } |
| |
| // Test to ensure we do not mount decompressed APEX from /data/apex/active |
| TEST_F(ApexdMountTest, OnStartOrphanedDecompressedApexInActiveDirectory) { |
| // 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.apex"), |
| decompressed_apex_in_active_dir); |
| auto apex_path = AddPreInstalledApex("com.android.apex.compressed.v1.apex"); |
| |
| ASSERT_THAT(ApexFileRepository::GetInstance().AddPreInstalledApex( |
| {{GetPartition(), GetBuiltInDir()}}), |
| Ok()); |
| |
| OnStart(); |
| |
| // Pre-installed APEX should be mounted |
| 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.verity_name, IsEmpty()); |
| }); |
| } |
| |
| // Test scenario when decompressed version has different version than |
| // pre-installed CAPEX |
| TEST_F(ApexdMountTest, OnStartDecompressedApexVersionDifferentThanCapex) { |
| if (IsMountBeforeDataEnabled()) GTEST_SKIP() << "mount_before_data enabled"; |
| |
| 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_THAT(ApexFileRepository::GetInstance().AddPreInstalledApex( |
| {{GetPartition(), GetBuiltInDir()}}), |
| Ok()); |
| |
| 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); |
| |
| 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.verity_name, |
| "com.android.apex.compressed"); |
| }); |
| } |
| |
| // Test that ota_apex is persisted until slot switch |
| TEST_F(ApexdMountTest, OnStartOtaApexKeptUntilSlotSwitch) { |
| if (IsMountBeforeDataEnabled()) GTEST_SKIP() << "mount_before_data enabled"; |
| |
| // Imagine current system has v1 capex and we have v2 incoming via ota |
| auto old_capex = AddPreInstalledApex("com.android.apex.compressed.v1.capex"); |
| auto ota_apex_path = |
| StringPrintf("%s/com.android.apex.compressed@2%s", |
| GetDecompressionDir().c_str(), kOtaApexPackageSuffix); |
| fs::copy(GetTestFile("com.android.apex.compressed.v2_original.apex"), |
| ota_apex_path.c_str()); |
| |
| ASSERT_THAT(ApexFileRepository::GetInstance().AddPreInstalledApex( |
| {{GetPartition(), GetBuiltInDir()}}), |
| Ok()); |
| |
| // When we call OnStart for the first time, it will decompress v1 capex and |
| // activate it, while after second call it will decompress v2 capex and |
| // activate it. We need to make sure that activated APEXes are cleaned up |
| // after test finishes. |
| auto old_decompressed_apex = StringPrintf( |
| "%s/com.android.apex.compressed@1%s", GetDecompressionDir().c_str(), |
| kDecompressedApexPackageSuffix); |
| auto new_decompressed_apex = StringPrintf( |
| "%s/com.android.apex.compressed@2%s", GetDecompressionDir().c_str(), |
| kDecompressedApexPackageSuffix); |
| |
| // First try starting without slot switch. Since we are booting with |
| // old pre-installed capex, ota_apex should not be deleted |
| OnStart(); |
| auto path_exists = PathExists(ota_apex_path); |
| ASSERT_TRUE(*path_exists); |
| |
| // When we switch slot, the pre-installed APEX will match ota_apex |
| // and the ota_apex will end up getting renamed. |
| RemoveFileIfExists(old_capex); |
| AddPreInstalledApex("com.android.apex.compressed.v2.capex"); |
| ApexFileRepository::GetInstance().Reset(GetDecompressionDir()); |
| ASSERT_THAT(ApexFileRepository::GetInstance().AddPreInstalledApex( |
| {{GetPartition(), GetBuiltInDir()}}), |
| Ok()); |
| OnStart(); |
| path_exists = PathExists(ota_apex_path); |
| ASSERT_FALSE(*path_exists); |
| } |
| |
| // Test scenario when decompressed version has same version but different |
| // digest |
| TEST_F(ApexdMountTest, |
| OnStartDecompressedApexVersionSameAsCapexDifferentDigest) { |
| if (IsMountBeforeDataEnabled()) GTEST_SKIP() << "mount_before_data enabled"; |
| |
| // Push a CAPEX to system without decompressing it |
| auto apex_path = AddPreInstalledApex("com.android.apex.compressed.v1.capex"); |
| auto pre_installed_apex = ApexFile::Open(apex_path); |
| // Now push an APEX with different root digest as decompressed APEX |
| auto decompressed_apex_path = StringPrintf( |
| "%s/com.android.apex.compressed@1%s", GetDecompressionDir().c_str(), |
| kDecompressedApexPackageSuffix); |
| fs::copy(GetTestFile( |
| "com.android.apex.compressed.v1_different_digest_original.apex"), |
| decompressed_apex_path); |
| auto different_digest_apex = ApexFile::Open(decompressed_apex_path); |
| auto different_digest = GetRootDigest(*different_digest_apex); |
| ASSERT_NE( |
| pre_installed_apex->GetManifest().capexmetadata().originalapexdigest(), |
| different_digest); |
| |
| ASSERT_THAT(ApexFileRepository::GetInstance().AddPreInstalledApex( |
| {{GetPartition(), GetBuiltInDir()}}), |
| Ok()); |
| |
| OnStart(); |
| |
| // Existing same version decompressed APEX with different root digest should |
| // be ignored and the pre-installed CAPEX should be decompressed again. |
| |
| // Ensure decompressed apex has same digest as pre-installed |
| auto decompressed_apex = ApexFile::Open(decompressed_apex_path); |
| ASSERT_EQ( |
| pre_installed_apex->GetManifest().capexmetadata().originalapexdigest(), |
| GetRootDigest(*decompressed_apex)); |
| ASSERT_NE(GetRootDigest(*decompressed_apex), different_digest); |
| } |
| |
| // Test when decompressed APEX has different key than CAPEX |
| TEST_F(ApexdMountTest, OnStartDecompressedApexVersionSameAsCapexDifferentKey) { |
| if (IsMountBeforeDataEnabled()) GTEST_SKIP() << "mount_before_data enabled"; |
| |
| TemporaryDir previous_built_in_dir; |
| auto [different_key_apex_path, _] = |
| 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_THAT(ApexFileRepository::GetInstance().AddPreInstalledApex( |
| {{GetPartition(), GetBuiltInDir()}}), |
| Ok()); |
| |
| OnStart(); |
| |
| // Existing same 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); |
| |
| // Ensure decompressed apex has same digest as pre-installed |
| auto pre_installed_apex = ApexFile::Open(apex_path); |
| auto decompressed_apex = ApexFile::Open(decompressed_active_apex); |
| auto different_key_apex = ApexFile::Open(different_key_apex_path); |
| ASSERT_EQ( |
| pre_installed_apex->GetManifest().capexmetadata().originalapexdigest(), |
| GetRootDigest(*decompressed_apex)); |
| ASSERT_NE( |
| pre_installed_apex->GetManifest().capexmetadata().originalapexdigest(), |
| GetRootDigest(*different_key_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_THAT(instance.AddPreInstalledApex({{GetPartition(), GetBuiltInDir()}}), |
| Ok()); |
| |
| ASSERT_THAT(ActivatePackage(apex_path_2), Ok()); |
| ASSERT_THAT(ActivatePackage(apex_path_3), Ok()); |
| ASSERT_THAT(ActivatePackage(decompressed_apex), Ok()); |
| |
| 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")); |
| |
| ASSERT_EQ(0, UnmountAll()); |
| ASSERT_THAT(GetApexMounts(), IsEmpty()); |
| } |
| |
| TEST_F(ApexdMountTest, UnmountAllDeferred) { |
| 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"); |
| |
| auto& instance = ApexFileRepository::GetInstance(); |
| ASSERT_THAT(instance.AddPreInstalledApex({{GetPartition(), GetBuiltInDir()}}), |
| Ok()); |
| |
| ASSERT_THAT(ActivatePackage(apex_path_2), Ok()); |
| ASSERT_THAT(ActivatePackage(apex_path_3), Ok()); |
| |
| ASSERT_THAT(GetApexMounts(), |
| 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")); |
| |
| const std::string kDeviceName = "com.android.apex.test_package@2"; |
| Result<std::vector<std::string>> loop_devices = |
| ListChildLoopDevices(kDeviceName); |
| ASSERT_THAT(loop_devices, HasValue(Not(IsEmpty()))); |
| |
| // Open a file. This should make unmounting in `UnmountAll` deferred. |
| unique_fd fd( |
| open("/apex/com.android.apex.test_package/etc/sample_prebuilt_file", |
| O_RDONLY)); |
| ASSERT_GE(fd, 0) << strerror(errno); |
| |
| // UnmountAll should succeed despite the open file. |
| ASSERT_EQ(UnmountAll(), 0); |
| |
| // The mount should still be there, but it should be detached from the |
| // filesystem, so the mount point should be gone. |
| EXPECT_THAT(GetApexMounts(), IsEmpty()); |
| // The DM device and the loop device should still be there. |
| auto& dm = DeviceMapper::Instance(); |
| EXPECT_EQ(dm.GetState(kDeviceName), dm::DmDeviceState::ACTIVE); |
| for (const std::string& loop_device : *loop_devices) { |
| EXPECT_THAT(GetLoopDeviceStatus(loop_device), Ok()); |
| } |
| |
| // Close the file. Unmounting should be automatically performed after then. |
| fd.reset(); |
| // Wait for the kernel to clean things up. |
| std::this_thread::sleep_for(std::chrono::milliseconds(300)); |
| |
| // The DM device and the loop device should be gone. |
| EXPECT_EQ(dm.GetState(kDeviceName), dm::DmDeviceState::INVALID); |
| for (const std::string& loop_device : *loop_devices) { |
| EXPECT_THAT(GetLoopDeviceStatus(loop_device), HasError(WithCode(ENXIO))); |
| } |
| } |
| |
| TEST_F(ApexdMountTest, UnmountAllStaged) { |
| // Both a pre-installed apex and a staged apex are mounted. UnmountAll should |
| // unmount both. |
| AddPreInstalledApex("apex.apexd_test.apex"); |
| AddPreInstalledApex("apex.apexd_test_different_app.apex"); |
| auto session = OR_FAIL(CreateStagedSession("apex.apexd_test_v2.apex", 123)); |
| ASSERT_THAT(session.UpdateStateAndCommit(SessionState::STAGED), Ok()); |
| |
| ASSERT_THAT(OnOtaChrootBootstrap(/*also_include_staged_apexes=*/true), 0); |
| ASSERT_THAT(GetApexMounts(), |
| 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(0, UnmountAll()); |
| ASSERT_THAT(GetApexMounts(), IsEmpty()); |
| } |
| |
| TEST_F(ApexdMountTest, OnStartInVmModeActivatesPreInstalled) { |
| 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()); |
| |
| 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, OnStartInVmModeSetsStatusAsReady) { |
| AddPreInstalledApex("apex.apexd_test.apex"); |
| ASSERT_EQ(0, OnStartInVmMode()); |
| ASSERT_EQ(GetProperty(kTestApexdStatusSysprop, ""), "ready"); |
| } |
| |
| TEST_F(ApexdMountTest, OnStartInVmModeFailsWithCapex) { |
| AddPreInstalledApex("com.android.apex.compressed.v2.capex"); |
| |
| ASSERT_EQ(1, OnStartInVmMode()); |
| } |
| |
| TEST_F(ApexdMountTest, OnStartInVmModeActivatesBlockDevicesAsWell) { |
| // Set system property to enable block apexes |
| SetBlockApexEnabled(true); |
| |
| auto path1 = AddBlockApex("apex.apexd_test.apex"); |
| |
| ASSERT_EQ(0, OnStartInVmMode()); |
| |
| 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= */ path1, |
| /* preinstalledModulePath= */ path1, |
| /* versionCode= */ 1, /* versionName= */ "1", |
| /* isFactory= */ true, /* isActive= */ true, GetMTime(path1), |
| /* provideSharedApexLibs= */ false, |
| /* partition= */ GetBlockPartitionString()); |
| ASSERT_THAT(info_list->getApexInfo(), |
| UnorderedElementsAre(ApexInfoXmlEq(apex_info_xml_1))); |
| } |
| |
| TEST_F(ApexdMountTest, OnStartInVmModeFailsWithDuplicateNames) { |
| // Set system property to enable block apexes |
| SetBlockApexEnabled(true); |
| |
| AddPreInstalledApex("apex.apexd_test.apex"); |
| AddBlockApex("apex.apexd_test_v2.apex"); |
| |
| ASSERT_EQ(1, OnStartInVmMode()); |
| } |
| |
| TEST_F(ApexdMountTest, OnStartInVmModeFailsWithWrongPubkey) { |
| // Set system property to enable block apexes |
| SetBlockApexEnabled(true); |
| |
| AddBlockApex("apex.apexd_test.apex", /*public_key=*/"wrong pubkey"); |
| |
| ASSERT_EQ(1, OnStartInVmMode()); |
| } |
| |
| TEST_F(ApexdMountTest, GetActivePackagesReturningBlockApexesAsWell) { |
| // Set system property to enable block apexes |
| SetBlockApexEnabled(true); |
| |
| auto path1 = AddBlockApex("apex.apexd_test.apex"); |
| |
| ASSERT_EQ(0, OnStartInVmMode()); |
| |
| auto active_apexes = GetActivePackages(); |
| ASSERT_EQ(1u, active_apexes.size()); |
| ASSERT_EQ(path1, active_apexes[0].GetPath()); |
| } |
| |
| TEST_F(ApexdMountTest, OnStartInVmModeFailsWithWrongRootDigest) { |
| // Set system property to enable block apexes |
| SetBlockApexEnabled(true); |
| |
| AddBlockApex("apex.apexd_test.apex", /*public_key=*/"", |
| /*root_digest=*/"wrong root digest"); |
| |
| ASSERT_EQ(1, OnStartInVmMode()); |
| } |
| |
| class ApexActivationFailureTests : public ApexdMountTest {}; |
| |
| TEST_F(ApexActivationFailureTests, BuildFingerprintDifferent) { |
| if (IsMountBeforeDataEnabled()) GTEST_SKIP() << "mount_before_data enabled"; |
| |
| auto apex_session = CreateStagedSession("apex.apexd_test.apex", 123); |
| ASSERT_RESULT_OK(apex_session); |
| apex_session->SetBuildFingerprint("wrong fingerprint"); |
| ASSERT_RESULT_OK(apex_session->UpdateStateAndCommit(SessionState::STAGED)); |
| |
| OnStart(); |
| |
| apex_session = GetSessionManager()->GetSession(123); |
| ASSERT_RESULT_OK(apex_session); |
| ASSERT_THAT(apex_session->GetErrorMessage(), |
| HasSubstr("APEX build fingerprint has changed")); |
| } |
| |
| TEST_F(ApexActivationFailureTests, BuildFingerprintDifferent_Verified) { |
| if (IsMountBeforeDataEnabled()) GTEST_SKIP() << "mount_before_data enabled"; |
| |
| auto apex_session = CreateStagedSession("apex.apexd_test.apex", 123); |
| ASSERT_RESULT_OK(apex_session); |
| apex_session->SetBuildFingerprint("wrong fingerprint"); |
| ASSERT_RESULT_OK(apex_session->UpdateStateAndCommit(SessionState::VERIFIED)); |
| |
| OnStart(); |
| |
| apex_session = GetSessionManager()->GetSession(123); |
| ASSERT_RESULT_OK(apex_session); |
| ASSERT_THAT(apex_session->GetErrorMessage(), |
| HasSubstr("APEX build fingerprint has changed")); |
| } |
| |
| TEST_F(ApexActivationFailureTests, ApexFileMissingInStagingDirectory) { |
| if (IsMountBeforeDataEnabled()) GTEST_SKIP() << "mount_before_data enabled"; |
| |
| auto apex_session = CreateStagedSession("apex.apexd_test.apex", 123); |
| ASSERT_RESULT_OK(apex_session); |
| apex_session->UpdateStateAndCommit(SessionState::STAGED); |
| // Delete the apex file in staging directory |
| DeleteDirContent(GetStagedDir(123)); |
| |
| OnStart(); |
| |
| apex_session = GetSessionManager()->GetSession(123); |
| ASSERT_RESULT_OK(apex_session); |
| ASSERT_THAT(apex_session->GetErrorMessage(), HasSubstr("Found: 0")); |
| } |
| |
| TEST_F(ApexActivationFailureTests, MultipleApexFileInStagingDirectory) { |
| if (IsMountBeforeDataEnabled()) GTEST_SKIP() << "mount_before_data enabled"; |
| |
| auto apex_session = CreateStagedSession("apex.apexd_test.apex", 123); |
| ASSERT_RESULT_OK(apex_session); |
| CreateStagedSession("com.android.apex.compressed.v1.apex", 123); |
| apex_session->UpdateStateAndCommit(SessionState::STAGED); |
| |
| OnStart(); |
| |
| apex_session = GetSessionManager()->GetSession(123); |
| ASSERT_RESULT_OK(apex_session); |
| ASSERT_THAT(apex_session->GetErrorMessage(), HasSubstr("Found: 2")); |
| } |
| |
| TEST_F(ApexActivationFailureTests, CorruptedSuperblockApexCannotBeStaged) { |
| if (IsMountBeforeDataEnabled()) GTEST_SKIP() << "mount_before_data enabled"; |
| |
| auto apex_session = |
| CreateStagedSession("apex.apexd_test_corrupt_superblock_apex.apex", 123); |
| apex_session->UpdateStateAndCommit(SessionState::STAGED); |
| ASSERT_RESULT_OK(apex_session); |
| |
| OnStart(); |
| |
| apex_session = GetSessionManager()->GetSession(123); |
| ASSERT_RESULT_OK(apex_session); |
| ASSERT_THAT(apex_session->GetErrorMessage(), |
| HasSubstr("Couldn't find filesystem magic")); |
| } |
| |
| TEST_F(ApexActivationFailureTests, CorruptedApexCannotBeStaged) { |
| if (IsMountBeforeDataEnabled()) GTEST_SKIP() << "mount_before_data enabled"; |
| |
| auto apex_session = CreateStagedSession("corrupted_b146895998.apex", 123); |
| ASSERT_RESULT_OK(apex_session); |
| apex_session->UpdateStateAndCommit(SessionState::STAGED); |
| |
| OnStart(); |
| |
| apex_session = GetSessionManager()->GetSession(123); |
| ASSERT_RESULT_OK(apex_session); |
| ASSERT_THAT(apex_session->GetErrorMessage(), |
| HasSubstr("Activation failed for packages")); |
| } |
| |
| TEST_F(ApexActivationFailureTests, ActivatePackageImplFails) { |
| if (IsMountBeforeDataEnabled()) GTEST_SKIP() << "mount_before_data enabled"; |
| |
| auto shim_path = AddPreInstalledApex("com.android.apex.cts.shim.apex"); |
| auto& instance = ApexFileRepository::GetInstance(); |
| ASSERT_RESULT_OK( |
| instance.AddPreInstalledApex({{GetPartition(), GetBuiltInDir()}})); |
| |
| auto apex_session = |
| CreateStagedSession("com.android.apex.cts.shim.v2_wrong_sha.apex", 123); |
| ASSERT_RESULT_OK(apex_session); |
| apex_session->UpdateStateAndCommit(SessionState::STAGED); |
| |
| OnStart(); |
| |
| apex_session = GetSessionManager()->GetSession(123); |
| ASSERT_RESULT_OK(apex_session); |
| ASSERT_THAT(apex_session->GetErrorMessage(), |
| HasSubstr("Failed to activate packages")); |
| ASSERT_THAT(apex_session->GetErrorMessage(), |
| HasSubstr("has unexpected SHA512 hash")); |
| } |
| |
| TEST_F(ApexActivationFailureTests, |
| StagedSessionFailsWhenNotInFsCheckpointMode) { |
| if (IsMountBeforeDataEnabled()) GTEST_SKIP() << "mount_before_data enabled"; |
| |
| MockCheckpointInterface checkpoint_interface; |
| checkpoint_interface.SetSupportsCheckpoint(true); |
| // Need to call InitializeVold before calling OnStart |
| InitializeVold(&checkpoint_interface); |
| |
| auto pre_installed_apex = AddPreInstalledApex("apex.apexd_test.apex"); |
| auto& instance = ApexFileRepository::GetInstance(); |
| ASSERT_RESULT_OK( |
| instance.AddPreInstalledApex({{GetPartition(), GetBuiltInDir()}})); |
| |
| auto apex_session = CreateStagedSession("apex.apexd_test.apex", 123); |
| ASSERT_RESULT_OK(apex_session); |
| apex_session->UpdateStateAndCommit(SessionState::STAGED); |
| |
| OnStart(); |
| |
| apex_session = GetSessionManager()->GetSession(123); |
| ASSERT_RESULT_OK(apex_session); |
| ASSERT_EQ(apex_session->GetState(), SessionState::ACTIVATION_FAILED); |
| ASSERT_THAT( |
| apex_session->GetErrorMessage(), |
| HasSubstr("Cannot install apex session if not in fs-checkpoint mode")); |
| } |
| |
| TEST_F(ApexActivationFailureTests, StagedSessionRevertsWhenInFsRollbackMode) { |
| MockCheckpointInterface checkpoint_interface; |
| checkpoint_interface.SetSupportsCheckpoint(true); |
| checkpoint_interface.SetNeedsRollback(true); |
| // Need to call InitializeVold before calling OnStart |
| InitializeVold(&checkpoint_interface); |
| |
| auto pre_installed_apex = AddPreInstalledApex("apex.apexd_test.apex"); |
| auto& instance = ApexFileRepository::GetInstance(); |
| ASSERT_RESULT_OK( |
| instance.AddPreInstalledApex({{GetPartition(), GetBuiltInDir()}})); |
| |
| auto apex_session = CreateStagedSession("apex.apexd_test.apex", 123); |
| ASSERT_RESULT_OK(apex_session); |
| apex_session->UpdateStateAndCommit(SessionState::STAGED); |
| |
| OnStart(); |
| |
| apex_session = GetSessionManager()->GetSession(123); |
| ASSERT_RESULT_OK(apex_session); |
| ASSERT_EQ(apex_session->GetState(), SessionState::REVERTED); |
| } |
| |
| TEST_F(ApexdMountTest, OnBootstrapCreatesEmptyDmDevices) { |
| if (IsMountBeforeDataEnabled()) GTEST_SKIP() << "mount_before_data enabled"; |
| |
| AddPreInstalledApex("apex.apexd_test.apex"); |
| AddPreInstalledApex("com.android.apex.compressed.v1.capex"); |
| |
| DeviceMapper& dm = DeviceMapper::Instance(); |
| |
| auto cleaner = make_scope_guard([&]() { |
| dm.DeleteDeviceIfExists("com.android.apex.test_package", 1s); |
| dm.DeleteDeviceIfExists("com.android.apex.compressed", 1s); |
| }); |
| |
| ASSERT_EQ(0, OnBootstrap()); |
| |
| ASSERT_EQ(dm::DmDeviceState::SUSPENDED, |
| dm.GetState("com.android.apex.test_package")); |
| ASSERT_EQ(dm::DmDeviceState::SUSPENDED, |
| dm.GetState("com.android.apex.compressed")); |
| } |
| |
| TEST_F(ApexdMountTest, OnBootstrapLoadBootstrapApexOnly) { |
| if (IsMountBeforeDataEnabled()) GTEST_SKIP() << "mount_before_data enabled"; |
| |
| AddPreInstalledApex("apex.apexd_test.apex"); |
| AddPreInstalledApex("apex.apexd_bootstrap_test.apex"); |
| |
| DeviceMapper& dm = DeviceMapper::Instance(); |
| auto cleaner = make_scope_guard([&]() { |
| dm.DeleteDeviceIfExists("com.android.apex.test_package", 1s); |
| dm.DeleteDeviceIfExists("com.android.apex.bootstrap_test_package", 1s); |
| }); |
| |
| ASSERT_EQ(0, OnBootstrap()); |
| |
| // Check bootstrap apex was loaded |
| auto active_bootstrap_apex = |
| GetActivePackage("com.android.apex.bootstrap_test_package"); |
| ASSERT_THAT(active_bootstrap_apex, Ok()); |
| // Check that non-bootstrap apex was not loaded |
| ASSERT_THAT(GetActivePackage("com.android.apex.test_package"), Not(Ok())); |
| } |
| |
| TEST_F(ApexdUnitTest, StagePackagesFailKey) { |
| auto status = |
| StagePackages({GetTestFile("apex.apexd_test_no_inst_key.apex")}); |
| |
| ASSERT_THAT( |
| status, |
| HasError(WithMessage(("No preinstalled apex found for unverified package " |
| "com.android.apex.test_package.no_inst_key")))); |
| } |
| |
| TEST_F(ApexdUnitTest, StagePackagesSuccess) { |
| AddPreInstalledApex("apex.apexd_test.apex"); |
| auto& instance = ApexFileRepository::GetInstance(); |
| ASSERT_THAT(instance.AddPreInstalledApex({{GetPartition(), GetBuiltInDir()}}), |
| Ok()); |
| |
| auto status = StagePackages({GetTestFile("apex.apexd_test.apex")}); |
| ASSERT_THAT(status, Ok()); |
| |
| auto staged_path = StringPrintf("%s/com.android.apex.test_package@1.apex", |
| GetDataDir().c_str()); |
| ASSERT_EQ(0, access(staged_path.c_str(), F_OK)); |
| } |
| |
| TEST_F(ApexdUnitTest, StagePackagesClearsPreviouslyActivePackage) { |
| AddPreInstalledApex("apex.apexd_test.apex"); |
| auto& instance = ApexFileRepository::GetInstance(); |
| ASSERT_THAT(instance.AddPreInstalledApex({{GetPartition(), GetBuiltInDir()}}), |
| Ok()); |
| |
| auto current_apex = AddDataApex("apex.apexd_test.apex"); |
| ASSERT_EQ(0, access(current_apex.c_str(), F_OK)); |
| |
| auto status = StagePackages({GetTestFile("apex.apexd_test_v2.apex")}); |
| ASSERT_THAT(status, Ok()); |
| |
| auto staged_path = StringPrintf("%s/com.android.apex.test_package@2.apex", |
| GetDataDir().c_str()); |
| ASSERT_EQ(0, access(staged_path.c_str(), F_OK)); |
| ASSERT_EQ(-1, access(current_apex.c_str(), F_OK)); |
| ASSERT_EQ(ENOENT, errno); |
| } |
| |
| TEST_F(ApexdUnitTest, StagePackagesClearsPreviouslyActivePackageDowngrade) { |
| AddPreInstalledApex("apex.apexd_test.apex"); |
| auto& instance = ApexFileRepository::GetInstance(); |
| ASSERT_THAT(instance.AddPreInstalledApex({{GetPartition(), GetBuiltInDir()}}), |
| Ok()); |
| |
| auto current_apex = AddDataApex("apex.apexd_test_v2.apex"); |
| ASSERT_EQ(0, access(current_apex.c_str(), F_OK)); |
| |
| auto status = StagePackages({GetTestFile("apex.apexd_test.apex")}); |
| ASSERT_THAT(status, Ok()); |
| |
| auto staged_path = StringPrintf("%s/com.android.apex.test_package@1.apex", |
| GetDataDir().c_str()); |
| ASSERT_EQ(0, access(staged_path.c_str(), F_OK)); |
| ASSERT_EQ(-1, access(current_apex.c_str(), F_OK)); |
| ASSERT_EQ(ENOENT, errno); |
| } |
| |
| TEST_F(ApexdUnitTest, StagePackagesAlreadyStagedPackage) { |
| AddPreInstalledApex("apex.apexd_test.apex"); |
| auto& instance = ApexFileRepository::GetInstance(); |
| ASSERT_THAT(instance.AddPreInstalledApex({{GetPartition(), GetBuiltInDir()}}), |
| Ok()); |
| |
| auto status = StagePackages({GetTestFile("apex.apexd_test.apex")}); |
| ASSERT_THAT(status, Ok()); |
| |
| auto staged_path = StringPrintf("%s/com.android.apex.test_package@1.apex", |
| GetDataDir().c_str()); |
| struct stat stat1; |
| ASSERT_EQ(0, stat(staged_path.c_str(), &stat1)); |
| ASSERT_TRUE(S_ISREG(stat1.st_mode)); |
| |
| { |
| auto apex = ApexFile::Open(staged_path); |
| ASSERT_THAT(apex, Ok()); |
| ASSERT_FALSE(apex->GetManifest().nocode()); |
| } |
| |
| auto status2 = StagePackages({GetTestFile("apex.apexd_test_nocode.apex")}); |
| ASSERT_THAT(status2, Ok()); |
| |
| struct stat stat2; |
| ASSERT_EQ(0, stat(staged_path.c_str(), &stat2)); |
| ASSERT_TRUE(S_ISREG(stat2.st_mode)); |
| |
| ASSERT_NE(stat1.st_ino, stat2.st_ino); |
| |
| { |
| auto apex = ApexFile::Open(staged_path); |
| ASSERT_THAT(apex, Ok()); |
| ASSERT_TRUE(apex->GetManifest().nocode()); |
| } |
| } |
| |
| TEST_F(ApexdUnitTest, StagePackagesMultiplePackages) { |
| AddPreInstalledApex("apex.apexd_test.apex"); |
| AddPreInstalledApex("apex.apexd_test_different_app.apex"); |
| auto& instance = ApexFileRepository::GetInstance(); |
| ASSERT_THAT(instance.AddPreInstalledApex({{GetPartition(), GetBuiltInDir()}}), |
| Ok()); |
| |
| auto status = |
| StagePackages({GetTestFile("apex.apexd_test_v2.apex"), |
| GetTestFile("apex.apexd_test_different_app.apex")}); |
| ASSERT_THAT(status, Ok()); |
| |
| auto staged_path1 = StringPrintf("%s/com.android.apex.test_package@2.apex", |
| GetDataDir().c_str()); |
| auto staged_path2 = StringPrintf("%s/com.android.apex.test_package_2@1.apex", |
| GetDataDir().c_str()); |
| ASSERT_EQ(0, access(staged_path1.c_str(), F_OK)); |
| ASSERT_EQ(0, access(staged_path2.c_str(), F_OK)); |
| } |
| |
| TEST_F(ApexdUnitTest, UnstagePackages) { |
| if (IsMountBeforeDataEnabled()) GTEST_SKIP() << "mount_before_data enabled"; |
| |
| auto file_path1 = AddDataApex("apex.apexd_test.apex"); |
| auto file_path2 = AddDataApex("apex.apexd_test_different_app.apex"); |
| |
| ASSERT_THAT(UnstagePackages({file_path1}), Ok()); |
| ASSERT_EQ(-1, access(file_path1.c_str(), F_OK)); |
| ASSERT_EQ(errno, ENOENT); |
| ASSERT_EQ(0, access(file_path2.c_str(), F_OK)); |
| } |
| |
| TEST_F(ApexdUnitTest, UnstagePackagesEmptyInput) { |
| auto file_path1 = AddDataApex("apex.apexd_test.apex"); |
| auto file_path2 = AddDataApex("apex.apexd_test_different_app.apex"); |
| |
| ASSERT_THAT(UnstagePackages({}), |
| HasError(WithMessage("Empty set of inputs"))); |
| ASSERT_EQ(0, access(file_path1.c_str(), F_OK)); |
| ASSERT_EQ(0, access(file_path2.c_str(), F_OK)); |
| } |
| |
| TEST_F(ApexdUnitTest, UnstagePackagesFail) { |
| auto file_path1 = AddDataApex("apex.apexd_test.apex"); |
| auto bad_path = GetDataDir() + "/missing.apex"; |
| |
| ASSERT_THAT(UnstagePackages({file_path1, bad_path}), Not(Ok())); |
| ASSERT_EQ(0, access(file_path1.c_str(), F_OK)); |
| } |
| |
| TEST_F(ApexdUnitTest, UnstagePackagesFailPreInstalledApex) { |
| auto file_path1 = AddPreInstalledApex("apex.apexd_test.apex"); |
| auto file_path2 = AddDataApex("apex.apexd_test_different_app.apex"); |
| |
| auto& instance = ApexFileRepository::GetInstance(); |
| ASSERT_THAT(instance.AddPreInstalledApex({{GetPartition(), GetBuiltInDir()}}), |
| Ok()); |
| |
| ASSERT_THAT(UnstagePackages({file_path1, file_path2}), |
| HasError(WithMessage("Can't uninstall pre-installed apex " + |
| file_path1))); |
| ASSERT_EQ(0, access(file_path1.c_str(), F_OK)); |
| ASSERT_EQ(0, access(file_path2.c_str(), F_OK)); |
| } |
| |
| TEST_F(ApexdUnitTest, RevertStoresCrashingNativeProcess) { |
| MockCheckpointInterface checkpoint_interface; |
| checkpoint_interface.SetSupportsCheckpoint(true); |
| InitializeVold(&checkpoint_interface); |
| |
| auto apex_session = CreateStagedSession("apex.apexd_test.apex", 1543); |
| ASSERT_THAT(apex_session, Ok()); |
| ASSERT_THAT(apex_session->UpdateStateAndCommit(SessionState::ACTIVATED), |
| Ok()); |
| |
| ASSERT_THAT(RevertActiveSessions("test_process", ""), Ok()); |
| apex_session = GetSessionManager()->GetSession(1543); |
| ASSERT_THAT(apex_session, Ok()); |
| ASSERT_EQ(apex_session->GetCrashingNativeProcess(), "test_process"); |
| } |
| |
| TEST_F(ApexdUnitTest, MountAndDeriveClasspathNoJar) { |
| AddPreInstalledApex("apex.apexd_test_classpath.apex"); |
| ApexFileRepository::GetInstance().AddPreInstalledApex( |
| {{GetPartition(), GetBuiltInDir()}}); |
| |
| // Call MountAndDeriveClassPath |
| auto apex_file = ApexFile::Open(GetTestFile("apex.apexd_test.apex")); |
| auto package_name = apex_file->GetManifest().name(); |
| std::vector<ApexFile> apex_files; |
| apex_files.emplace_back(std::move(*apex_file)); |
| auto class_path = MountAndDeriveClassPath(apex_files); |
| ASSERT_THAT(class_path, Ok()); |
| ASSERT_THAT(class_path->HasClassPathJars(package_name), false); |
| } |
| |
| TEST_F(ApexdUnitTest, MountAndDeriveClassPathJarsPresent) { |
| AddPreInstalledApex("apex.apexd_test_classpath.apex"); |
| ApexFileRepository::GetInstance().AddPreInstalledApex( |
| {{GetPartition(), GetBuiltInDir()}}); |
| |
| // Call MountAndDeriveClassPath |
| auto apex_file = |
| ApexFile::Open(GetTestFile("apex.apexd_test_classpath.apex")); |
| auto package_name = apex_file->GetManifest().name(); |
| std::vector<ApexFile> apex_files; |
| apex_files.emplace_back(std::move(*apex_file)); |
| auto class_path = MountAndDeriveClassPath(apex_files); |
| ASSERT_THAT(class_path, Ok()); |
| ASSERT_THAT(class_path->HasClassPathJars(package_name), true); |
| } |
| |
| TEST_F(ApexdUnitTest, ProcessCompressedApexWrongSELinuxContext) { |
| auto compressed_apex = ApexFile::Open( |
| AddPreInstalledApex("com.android.apex.compressed.v1.capex")); |
| |
| auto return_value = |
| ProcessCompressedApex(*compressed_apex, /* is_ota_chroot= */ false); |
| ASSERT_THAT(return_value, Ok()); |
| |
| auto decompressed_apex_path = StringPrintf( |
| "%s/com.android.apex.compressed@1%s", GetDecompressionDir().c_str(), |
| kDecompressedApexPackageSuffix); |
| // Verify that so far it has correct context. |
| ASSERT_EQ(kTestActiveApexSelinuxCtx, |
| GetSelinuxContext(decompressed_apex_path)); |
| |
| // Manually mess up the context |
| ASSERT_EQ(0, setfilecon(decompressed_apex_path.c_str(), |
| "u:object_r:apex_data_file:s0")); |
| ASSERT_EQ("u:object_r:apex_data_file:s0", |
| GetSelinuxContext(decompressed_apex_path)); |
| |
| auto attempt_2 = |
| ProcessCompressedApex(*compressed_apex, /* is_ota_chroot= */ false); |
| ASSERT_THAT(attempt_2, Ok()); |
| // Verify that it again has correct context. |
| ASSERT_EQ(kTestActiveApexSelinuxCtx, |
| GetSelinuxContext(decompressed_apex_path)); |
| } |
| |
| TEST_F(ApexdMountTest, OnStartNoApexUpdated) { |
| if (IsMountBeforeDataEnabled()) GTEST_SKIP() << "mount_before_data enabled"; |
| |
| AddPreInstalledApex("com.android.apex.compressed.v1.capex"); |
| AddPreInstalledApex("apex.apexd_test.apex"); |
| AddPreInstalledApex("apex.apexd_test_different_app.apex"); |
| |
| AddDataApex("apex.apexd_test_v2.apex"); |
| AddDecompressedApex("com.android.apex.compressed.v1.apex"); |
| |
| ASSERT_THAT(ApexFileRepository::GetInstance().AddPreInstalledApex( |
| {{GetPartition(), GetBuiltInDir()}}), |
| Ok()); |
| |
| OnStart(); |
| |
| ASSERT_THAT(GetChangedActiveApexes(), IsEmpty()); |
| ASSERT_THAT(GetApexMounts(), SizeIs(6)); |
| } |
| |
| TEST_F(ApexdMountTest, ActivatesStagedSession) { |
| if (IsMountBeforeDataEnabled()) GTEST_SKIP() << "mount_before_data enabled"; |
| |
| std::string preinstalled_apex = AddPreInstalledApex("apex.apexd_test.apex"); |
| auto apex_session = CreateStagedSession("apex.apexd_test_v2.apex", 37); |
| apex_session->UpdateStateAndCommit(SessionState::STAGED); |
| |
| ASSERT_THAT(ApexFileRepository::GetInstance().AddPreInstalledApex( |
| {{GetPartition(), GetBuiltInDir()}}), |
| Ok()); |
| |
| OnStart(); |
| |
| // Quick check that session was activated |
| { |
| auto session = GetSessionManager()->GetSession(37); |
| ASSERT_THAT(session, Ok()); |
| ASSERT_EQ(session->GetState(), SessionState::ACTIVATED); |
| } |
| |
| ASSERT_THAT(GetChangedActiveApexes(), |
| UnorderedElementsAre("com.android.apex.test_package")); |
| } |
| |
| TEST_F(ApexdMountTest, FailsToActivateStagedSession) { |
| if (IsMountBeforeDataEnabled()) GTEST_SKIP() << "mount_before_data enabled"; |
| |
| AddPreInstalledApex("apex.apexd_test.apex"); |
| auto apex_session = |
| CreateStagedSession("apex.apexd_test_manifest_mismatch.apex", 73); |
| apex_session->UpdateStateAndCommit(SessionState::STAGED); |
| |
| ASSERT_THAT(ApexFileRepository::GetInstance().AddPreInstalledApex( |
| {{GetPartition(), GetBuiltInDir()}}), |
| Ok()); |
| |
| OnStart(); |
| |
| // Quick check that session failed. |
| { |
| auto session = GetSessionManager()->GetSession(73); |
| ASSERT_THAT(session, Ok()); |
| ASSERT_NE(session->GetState(), SessionState::ACTIVATED); |
| } |
| |
| // Failed session should trigger "revert & reboot". However, "revert" fails |
| // while running tests (We'd better not reboot the device under test). |
| // Hence, failed session causes activating fallback(preinstalled) apexes. |
| // Note that activating pre-installed apexes doesn't necessarily mean |
| // "changed" active apexes because they could be the same previous active |
| // apexes, but we don't know if there was data APEXes that's removed by the |
| // failed session. |
| ASSERT_THAT(GetChangedActiveApexes(), |
| UnorderedElementsAre("com.android.apex.test_package")); |
| } |
| |
| TEST_F(ApexdMountTest, FailsToActivateApexFallbacksToSystemOne) { |
| if (IsMountBeforeDataEnabled()) GTEST_SKIP() << "mount_before_data enabled"; |
| |
| AddPreInstalledApex("apex.apexd_test.apex"); |
| AddDataApex("apex.apexd_test_manifest_mismatch.apex"); |
| |
| ASSERT_THAT(ApexFileRepository::GetInstance().AddPreInstalledApex( |
| {{GetPartition(), GetBuiltInDir()}}), |
| Ok()); |
| |
| OnStart(); |
| |
| ASSERT_THAT(GetChangedActiveApexes(), |
| UnorderedElementsAre("com.android.apex.test_package")); |
| } |
| |
| TEST_F(ApexdMountTest, SubmitSingleStagedSessionKeepsPreviousSessions) { |
| MockCheckpointInterface checkpoint_interface; |
| checkpoint_interface.SetSupportsCheckpoint(true); |
| InitializeVold(&checkpoint_interface); |
| |
| std::string preinstalled_apex = AddPreInstalledApex("apex.apexd_test.apex"); |
| |
| ASSERT_RESULT_OK(ApexFileRepository::GetInstance().AddPreInstalledApex( |
| {{GetPartition(), GetBuiltInDir()}})); |
| |
| // First simulate existence of a bunch of sessions. |
| auto session1 = GetSessionManager()->CreateSession(37); |
| ASSERT_RESULT_OK(session1); |
| ASSERT_RESULT_OK(session1->UpdateStateAndCommit(SessionState::STAGED)); |
| |
| auto session2 = GetSessionManager()->CreateSession(57); |
| ASSERT_RESULT_OK(session2); |
| ASSERT_RESULT_OK(session2->UpdateStateAndCommit(SessionState::STAGED)); |
| |
| auto session3 = GetSessionManager()->CreateSession(73); |
| ASSERT_RESULT_OK(session3); |
| ASSERT_RESULT_OK(session3->UpdateStateAndCommit(SessionState::SUCCESS)); |
| |
| PrepareStagedSession("apex.apexd_test.apex", 239); |
| ASSERT_RESULT_OK(SubmitStagedSession(239, {}, false, false, -1)); |
| |
| auto sessions = GetSessionManager()->GetSessions(); |
| std::sort( |
| sessions.begin(), sessions.end(), |
| [](const auto& s1, const auto& s2) { return s1.GetId() < s2.GetId(); }); |
| |
| ASSERT_EQ(4u, sessions.size()); |
| |
| ASSERT_EQ(37, sessions[0].GetId()); |
| ASSERT_EQ(SessionState::STAGED, sessions[0].GetState()); |
| |
| ASSERT_EQ(57, sessions[1].GetId()); |
| ASSERT_EQ(SessionState::STAGED, sessions[1].GetState()); |
| |
| ASSERT_EQ(73, sessions[2].GetId()); |
| ASSERT_EQ(SessionState::SUCCESS, sessions[2].GetState()); |
| |
| ASSERT_EQ(239, sessions[3].GetId()); |
| ASSERT_EQ(SessionState::VERIFIED, sessions[3].GetState()); |
| } |
| |
| struct SpyMetrics : Metrics { |
| std::vector<std::tuple<InstallType, bool, ApexFileInfo>> requested; |
| std::vector<std::tuple<std::string, InstallResult>> ended; |
| |
| void SendInstallationRequested(InstallType install_type, bool is_rollback, |
| const ApexFileInfo& info) override { |
| requested.emplace_back(install_type, is_rollback, info); |
| } |
| void SendInstallationEnded(const std::string& file_hash, |
| InstallResult result) override { |
| ended.emplace_back(file_hash, result); |
| } |
| }; |
| |
| TEST_F(ApexdMountTest, SendEventOnSubmitStagedSession) { |
| if (IsMountBeforeDataEnabled()) GTEST_SKIP() << "mount_before_data enabled"; |
| |
| MockCheckpointInterface checkpoint_interface; |
| checkpoint_interface.SetSupportsCheckpoint(true); |
| InitializeVold(&checkpoint_interface); |
| |
| InitMetrics(std::make_unique<SpyMetrics>()); |
| |
| std::string preinstalled_apex = |
| AddPreInstalledApex("com.android.apex.vendor.foo.apex"); |
| |
| // Test APEX is a "vendor" APEX. Preinstalled partition should be vendor. |
| ASSERT_RESULT_OK(ApexFileRepository::GetInstance().AddPreInstalledApex( |
| {{ApexPartition::Vendor, GetBuiltInDir()}})); |
| |
| OnStart(); |
| |
| PrepareStagedSession("com.android.apex.vendor.foo.with_vintf.apex", 239); |
| ASSERT_RESULT_OK(SubmitStagedSession(239, {}, false, false, -1)); |
| |
| auto spy = std::unique_ptr<SpyMetrics>( |
| static_cast<SpyMetrics*>(InitMetrics(nullptr).release())); |
| ASSERT_NE(nullptr, spy.get()); |
| |
| ASSERT_EQ(1u, spy->requested.size()); |
| const auto& requested = spy->requested[0]; |
| ASSERT_EQ(InstallType::Staged, std::get<0>(requested)); |
| ASSERT_EQ("com.android.apex.vendor.foo"s, std::get<2>(requested).name); |
| ASSERT_THAT(std::get<2>(requested).hals, ElementsAre("android.apex.foo@1"s)); |
| |
| ASSERT_EQ(0u, spy->ended.size()); |
| } |
| |
| TEST_F(ApexdMountTest, SubmitStagedSessionSucceedVerifiedBrandNewApex) { |
| ApexFileRepository::EnableBrandNewApex(); |
| auto& file_repository = ApexFileRepository::GetInstance(); |
| const auto partition = ApexPartition::System; |
| TemporaryDir trusted_key_dir; |
| fs::copy(GetTestFile("apexd_testdata/com.android.apex.brand.new.avbpubkey"), |
| trusted_key_dir.path); |
| file_repository.AddBrandNewApexCredentialAndBlocklist( |
| {{partition, trusted_key_dir.path}}); |
| |
| PrepareStagedSession("com.android.apex.brand.new.apex", 239); |
| ASSERT_RESULT_OK(SubmitStagedSession(239, {}, false, false, -1)); |
| |
| auto sessions = GetSessionManager()->GetSessions(); |
| ASSERT_EQ(1u, sessions.size()); |
| ASSERT_EQ(239, sessions[0].GetId()); |
| ASSERT_EQ(SessionState::VERIFIED, sessions[0].GetState()); |
| file_repository.Reset(); |
| } |
| |
| TEST_F(ApexdMountTest, |
| SubmitStagedSessionSucceedVerifiedBrandNewApexWithActiveVersion) { |
| ApexFileRepository::EnableBrandNewApex(); |
| auto& file_repository = ApexFileRepository::GetInstance(); |
| const auto partition = ApexPartition::System; |
| TemporaryDir trusted_key_dir; |
| fs::copy(GetTestFile("apexd_testdata/com.android.apex.brand.new.avbpubkey"), |
| trusted_key_dir.path); |
| file_repository.AddBrandNewApexCredentialAndBlocklist( |
| {{partition, trusted_key_dir.path}}); |
| auto data_apex = AddDataApex("com.android.apex.brand.new.apex"); |
| ASSERT_THAT(ActivatePackage(data_apex), Ok()); |
| |
| PrepareStagedSession("com.android.apex.brand.new.v2.apex", 239); |
| ASSERT_RESULT_OK(SubmitStagedSession(239, {}, false, false, -1)); |
| |
| auto sessions = GetSessionManager()->GetSessions(); |
| ASSERT_EQ(1u, sessions.size()); |
| ASSERT_EQ(239, sessions[0].GetId()); |
| ASSERT_EQ(SessionState::VERIFIED, sessions[0].GetState()); |
| file_repository.Reset(); |
| } |
| |
| TEST_F(ApexdMountTest, |
| SubmitStagedSessionFailBrandNewApexMismatchActiveVersion) { |
| ApexFileRepository::EnableBrandNewApex(); |
| auto& file_repository = ApexFileRepository::GetInstance(); |
| const auto partition = ApexPartition::System; |
| TemporaryDir trusted_key_dir; |
| fs::copy(GetTestFile("apexd_testdata/com.android.apex.brand.new.avbpubkey"), |
| trusted_key_dir.path); |
| fs::copy(GetTestFile( |
| "apexd_testdata/com.android.apex.brand.new.another.avbpubkey"), |
| trusted_key_dir.path); |
| file_repository.AddBrandNewApexCredentialAndBlocklist( |
| {{partition, trusted_key_dir.path}}); |
| auto data_apex = AddDataApex("com.android.apex.brand.new.apex"); |
| ASSERT_THAT(ActivatePackage(data_apex), Ok()); |
| |
| PrepareStagedSession("com.android.apex.brand.new.v2.diffkey.apex", 239); |
| auto ret = SubmitStagedSession(239, {}, false, false, -1); |
| |
| ASSERT_THAT( |
| ret, |
| HasError(WithMessage(("Brand-new APEX public key doesn't match existing " |
| "active APEX: com.android.apex.brand.new")))); |
| file_repository.Reset(); |
| } |
| |
| TEST_F(ApexdMountTest, SubmitStagedSessionFailBrandNewApexDisabled) { |
| auto& file_repository = ApexFileRepository::GetInstance(); |
| const auto partition = ApexPartition::System; |
| TemporaryDir trusted_key_dir; |
| fs::copy(GetTestFile("apexd_testdata/com.android.apex.brand.new.avbpubkey"), |
| trusted_key_dir.path); |
| file_repository.AddBrandNewApexCredentialAndBlocklist( |
| {{partition, trusted_key_dir.path}}); |
| |
| PrepareStagedSession("com.android.apex.brand.new.apex", 239); |
| auto ret = SubmitStagedSession(239, {}, false, false, -1); |
| |
| ASSERT_THAT(ret, |
| HasError(WithMessage(("No preinstalled apex found for unverified " |
| "package com.android.apex.brand.new")))); |
| file_repository.Reset(); |
| } |
| |
| TEST_F(ApexdUnitTest, StagePackagesSucceedVerifiedBrandNewApex) { |
| ApexFileRepository::EnableBrandNewApex(); |
| auto& file_repository = ApexFileRepository::GetInstance(); |
| const auto partition = ApexPartition::System; |
| TemporaryDir trusted_key_dir; |
| fs::copy(GetTestFile("apexd_testdata/com.android.apex.brand.new.avbpubkey"), |
| trusted_key_dir.path); |
| file_repository.AddBrandNewApexCredentialAndBlocklist( |
| {{partition, trusted_key_dir.path}}); |
| |
| auto status = StagePackages({GetTestFile("com.android.apex.brand.new.apex")}); |
| |
| ASSERT_RESULT_OK(status); |
| auto staged_path = StringPrintf("%s/com.android.apex.brand.new@1.apex", |
| GetDataDir().c_str()); |
| ASSERT_EQ(0, access(staged_path.c_str(), F_OK)); |
| file_repository.Reset(); |
| } |
| |
| TEST_F(ApexdUnitTest, StagePackagesFailUnverifiedBrandNewApex) { |
| ApexFileRepository::EnableBrandNewApex(); |
| auto& file_repository = ApexFileRepository::GetInstance(); |
| const auto partition = ApexPartition::System; |
| TemporaryDir trusted_key_dir; |
| fs::copy(GetTestFile( |
| "apexd_testdata/com.android.apex.brand.new.another.avbpubkey"), |
| trusted_key_dir.path); |
| file_repository.AddBrandNewApexCredentialAndBlocklist( |
| {{partition, trusted_key_dir.path}}); |
| |
| auto status = StagePackages({GetTestFile("com.android.apex.brand.new.apex")}); |
| |
| ASSERT_THAT(status, |
| HasError(WithMessage(("No preinstalled apex found for unverified " |
| "package com.android.apex.brand.new")))); |
| |
| file_repository.Reset(); |
| } |
| |
| TEST_F(ApexdMountTest, ActivatesStagedSessionSucceedVerifiedBrandNewApex) { |
| if (IsMountBeforeDataEnabled()) GTEST_SKIP() << "mount_before_data enabled"; |
| |
| ApexFileRepository::EnableBrandNewApex(); |
| auto& file_repository = ApexFileRepository::GetInstance(); |
| const auto partition = ApexPartition::System; |
| TemporaryDir trusted_key_dir; |
| fs::copy(GetTestFile("apexd_testdata/com.android.apex.brand.new.avbpubkey"), |
| trusted_key_dir.path); |
| file_repository.AddBrandNewApexCredentialAndBlocklist( |
| {{partition, trusted_key_dir.path}}); |
| |
| auto apex_session = |
| CreateStagedSession("com.android.apex.brand.new.apex", 37); |
| apex_session->UpdateStateAndCommit(SessionState::STAGED); |
| |
| std::string active_apex = |
| GetDataDir() + "/" + "com.android.apex.brand.new@1.apex"; |
| |
| OnStart(); |
| |
| // Quick check that session was activated |
| { |
| auto session = GetSessionManager()->GetSession(37); |
| ASSERT_THAT(session, Ok()); |
| ASSERT_EQ(session->GetState(), SessionState::ACTIVATED); |
| } |
| ASSERT_THAT(GetChangedActiveApexes(), |
| UnorderedElementsAre("com.android.apex.brand.new")); |
| |
| file_repository.Reset(); |
| } |
| |
| TEST_F(ApexdMountTest, ActivatesStagedSessionFailUnverifiedBrandNewApex) { |
| if (IsMountBeforeDataEnabled()) GTEST_SKIP() << "mount_before_data enabled"; |
| |
| ApexFileRepository::EnableBrandNewApex(); |
| auto& file_repository = ApexFileRepository::GetInstance(); |
| const auto partition = ApexPartition::System; |
| TemporaryDir trusted_key_dir; |
| fs::copy(GetTestFile( |
| "apexd_testdata/com.android.apex.brand.new.another.avbpubkey"), |
| trusted_key_dir.path); |
| file_repository.AddBrandNewApexCredentialAndBlocklist( |
| {{partition, trusted_key_dir.path}}); |
| |
| auto apex_session = |
| CreateStagedSession("com.android.apex.brand.new.apex", 37); |
| apex_session->UpdateStateAndCommit(SessionState::STAGED); |
| |
| std::string active_apex = |
| GetDataDir() + "/" + "com.android.apex.brand.new@1.apex"; |
| |
| OnStart(); |
| |
| // Quick check that session was activated |
| { |
| auto session = GetSessionManager()->GetSession(37); |
| ASSERT_THAT(session, Ok()); |
| ASSERT_EQ(session->GetState(), SessionState::ACTIVATION_FAILED); |
| } |
| |
| // Failed session would trigger revert or fallback to preinstalled. In tests, |
| // fallback happens, but since brand-new apexes don't have pre-installed |
| // counterparts, fallback doesn't cause any "change". |
| ASSERT_THAT(GetChangedActiveApexes(), IsEmpty()); |
| |
| file_repository.Reset(); |
| } |
| |
| TEST_F(ApexdMountTest, NonStagedUpdateFailVerifiedBrandNewApex) { |
| ApexFileRepository::EnableBrandNewApex(); |
| auto& file_repository = ApexFileRepository::GetInstance(); |
| const auto partition = ApexPartition::System; |
| TemporaryDir trusted_key_dir, data_dir; |
| fs::copy(GetTestFile("apexd_testdata/com.android.apex.brand.new.avbpubkey"), |
| trusted_key_dir.path); |
| file_repository.AddBrandNewApexCredentialAndBlocklist( |
| {{partition, trusted_key_dir.path}}); |
| auto file_path = AddDataApex("com.android.apex.brand.new.apex"); |
| ASSERT_THAT(ActivatePackage(file_path), Ok()); |
| |
| auto ret = InstallPackage(GetTestFile("com.android.apex.brand.new.apex"), |
| /* force= */ false); |
| ASSERT_THAT( |
| ret, |
| HasError(WithMessage(HasSubstr("No preinstalled apex found for package " |
| "com.android.apex.brand.new")))); |
| |
| file_repository.Reset(); |
| } |
| |
| TEST_F(ApexdMountTest, BootCompletedCleanup_CleanupInactiveApexes) { |
| if (IsMountBeforeDataEnabled()) GTEST_SKIP() << "mount_before_data enabled"; |
| |
| AddPreInstalledApex("apex.apexd_test.apex"); |
| auto selected = AddDataApex("apex.apexd_test_v2.apex"); |
| auto ignored1 = AddDataApex("apex.apexd_test.apex"); |
| auto ignored2 = AddDataApex("apex.apexd_test_different_app.apex"); |
| |
| auto& instance = ApexFileRepository::GetInstance(); |
| ASSERT_THAT(instance.AddPreInstalledApex({{GetPartition(), GetBuiltInDir()}}), |
| Ok()); |
| OnStart(); |
| ASSERT_THAT(FindFilesBySuffix(data_dir_, {kApexPackageSuffix}), |
| HasValue(UnorderedElementsAre(selected, ignored1, ignored2))); |
| |
| // Inactive data apexes are removed on boot completion. |
| BootCompletedCleanup(); |
| ASSERT_THAT(FindFilesBySuffix(data_dir_, {kApexPackageSuffix}), |
| HasValue(UnorderedElementsAre(selected))); |
| } |
| |
| class SubmitStagedSessionTest : public ApexdMountTest { |
| protected: |
| void SetUp() override { |
| ApexdMountTest::SetUp(); |
| |
| MockCheckpointInterface checkpoint_interface; |
| checkpoint_interface.SetSupportsCheckpoint(true); |
| InitializeVold(&checkpoint_interface); |
| |
| // Has two preinstalled APEXes (for testing multi-APEX session) |
| AddPreInstalledApex("apex.apexd_test.apex"); |
| AddPreInstalledApex("apex.apexd_test_different_app.apex"); |
| ApexFileRepository::GetInstance().AddPreInstalledApex( |
| {{GetPartition(), GetBuiltInDir()}}); |
| |
| OnStart(); |
| } |
| |
| void TearDown() override { |
| // Should not leak temporary verity devices regardless of success. |
| // Why EXPECT? Needs to call TearDown() for unmounting even when something |
| // goes wrong with the test. |
| std::vector<DeviceMapper::DmBlockDevice> devices; |
| EXPECT_TRUE(DeviceMapper::Instance().GetAvailableDevices(&devices)); |
| for (const auto& device : devices) { |
| EXPECT_THAT(device.name(), Not(EndsWith(".tmp"))); |
| } |
| |
| ApexdMountTest::TearDown(); |
| } |
| }; |
| |
| TEST_F(SubmitStagedSessionTest, SimpleSuccess) { |
| auto session_id = 42; |
| PrepareStagedSession("apex.apexd_test.apex", session_id); |
| |
| ASSERT_THAT(SubmitStagedSession(session_id, {}, false, false, -1), Ok()); |
| } |
| |
| TEST_F(SubmitStagedSessionTest, SuccessStoresBuildFingerprint) { |
| auto session_id = 42; |
| PrepareStagedSession("apex.apexd_test.apex", session_id); |
| |
| ASSERT_THAT(SubmitStagedSession(session_id, {}, false, false, -1), Ok()); |
| |
| auto session = GetSessionManager()->GetSession(session_id); |
| ASSERT_NE(session->GetBuildFingerprint(), ""s); |
| } |
| |
| TEST_F(SubmitStagedSessionTest, |
| RejectIfSamePackageIsAlreadyStaged_SameVersion) { |
| auto session_id = 42; |
| PrepareStagedSession("apex.apexd_test.apex", session_id); |
| ASSERT_THAT(SubmitStagedSession(session_id, {}, false, false, -1), Ok()); |
| ASSERT_THAT(MarkStagedSessionReady(session_id), Ok()); |
| |
| auto session_id2 = 43; |
| PrepareStagedSession("apex.apexd_test.apex", session_id2); |
| ASSERT_THAT(SubmitStagedSession(session_id2, {}, false, false, -1), |
| HasError(WithMessage(HasSubstr("already staged")))); |
| } |
| |
| TEST_F(SubmitStagedSessionTest, |
| RejectIfSamePackageIsAlreadyStaged_DifferentVersion) { |
| auto session_id = 42; |
| PrepareStagedSession("apex.apexd_test.apex", session_id); |
| ASSERT_THAT(SubmitStagedSession(session_id, {}, false, false, -1), Ok()); |
| ASSERT_THAT(MarkStagedSessionReady(session_id), Ok()); |
| |
| auto session_id2 = 43; |
| PrepareStagedSession("apex.apexd_test_v2.apex", session_id2); |
| ASSERT_THAT(SubmitStagedSession(session_id2, {}, false, false, -1), |
| HasError(WithMessage(HasSubstr("already staged")))); |
| } |
| |
| TEST_F(SubmitStagedSessionTest, RejectInstallPackageForStagedPackage) { |
| if (IsMountBeforeDataEnabled()) GTEST_SKIP() << "mount_before_data enabled"; |
| |
| auto session_id = 42; |
| PrepareStagedSession("apex.apexd_test.apex", session_id); |
| ASSERT_THAT(SubmitStagedSession(session_id, {}, false, false, -1), Ok()); |
| ASSERT_THAT(MarkStagedSessionReady(session_id), Ok()); |
| |
| ASSERT_THAT( |
| InstallPackage(GetTestFile("apex.apexd_test.apex"), /* force= */ true), |
| HasError(WithMessage(HasSubstr("already staged")))); |
| } |
| |
| TEST_F(SubmitStagedSessionTest, AbortedSessionDoesNotBlockNewStagingOrInstall) { |
| if (IsMountBeforeDataEnabled()) GTEST_SKIP() << "mount_before_data enabled"; |
| |
| auto session_id = 42; |
| PrepareStagedSession("apex.apexd_test.apex", session_id); |
| ASSERT_THAT(SubmitStagedSession(session_id, {}, false, false, -1), Ok()); |
| ASSERT_THAT(AbortStagedSession(session_id), Ok()); |
| |
| auto session_id2 = 43; |
| PrepareStagedSession("apex.apexd_test.apex", session_id2); |
| ASSERT_THAT(SubmitStagedSession(session_id2, {}, false, false, -1), Ok()); |
| ASSERT_THAT(AbortStagedSession(session_id2), Ok()); |
| |
| ASSERT_THAT(InstallPackage(GetTestFile("apex.apexd_test.apex"), |
| /* force= */ true), |
| Ok()); |
| } |
| |
| TEST_F(SubmitStagedSessionTest, FailWithManifestMismatch) { |
| auto session_id = 42; |
| PrepareStagedSession("apex.apexd_test_manifest_mismatch.apex", session_id); |
| |
| ASSERT_THAT(SubmitStagedSession(session_id, {}, false, false, -1), |
| HasError(WithMessage(HasSubstr("does not match manifest")))); |
| } |
| |
| TEST_F(SubmitStagedSessionTest, FailedSessionNotPersisted) { |
| auto session_id = 42; |
| PrepareStagedSession("apex.apexd_test_manifest_mismatch.apex", session_id); |
| |
| ASSERT_THAT(SubmitStagedSession(session_id, {}, false, false, -1), Not(Ok())); |
| |
| auto session = GetSessionManager()->GetSession(session_id); |
| ASSERT_THAT(session, Not(Ok())); |
| } |
| |
| TEST_F(SubmitStagedSessionTest, CannotBeRollbackAndHaveRollbackEnabled) { |
| auto session_id = 42; |
| PrepareStagedSession("apex.apexd_test.apex", session_id); |
| |
| ASSERT_THAT(SubmitStagedSession(session_id, {}, /*has_rollback=*/true, |
| /*is_rollback*/ true, -1), |
| HasError(WithMessage( |
| HasSubstr("both a rollback and enabled for rollback")))); |
| } |
| |
| TEST_F(SubmitStagedSessionTest, FailWithCorruptApex) { |
| auto session_id = 42; |
| PrepareStagedSession("apex.apexd_test_corrupt_apex.apex", session_id); |
| |
| ASSERT_THAT(SubmitStagedSession(session_id, {}, false, false, -1), |
| HasError(WithMessage(HasSubstr("corrupted?")))); |
| } |
| |
| TEST_F(SubmitStagedSessionTest, SuccessWithMultiSession) { |
| auto parent_session_id = 42; |
| auto child_session1_id = 43; |
| auto child_session2_id = 44; |
| auto file1 = PrepareStagedSession("apex.apexd_test.apex", child_session1_id); |
| auto file2 = PrepareStagedSession("apex.apexd_test_different_app.apex", |
| child_session2_id); |
| |
| auto ret = SubmitStagedSession(parent_session_id, |
| {child_session1_id, child_session2_id}, false, |
| false, -1); |
| ASSERT_THAT(ret, HasValue(ElementsAre(Property(&ApexFile::GetPath, file1), |
| Property(&ApexFile::GetPath, file2)))); |
| |
| auto session = GetSessionManager()->GetSession(parent_session_id); |
| ASSERT_THAT(session->GetChildSessionIds(), |
| ElementsAre(child_session1_id, child_session2_id)); |
| } |
| |
| // Test cases specific to mount_before_data |
| class MountBeforeDataTest : public ApexdMountTest { |
| protected: |
| void SetUp() override { |
| config_.mount_before_data = true; |
| ApexdMountTest::SetUp(); |
| |
| // preinstalled APEXes |
| AddPreInstalledApex("apex.apexd_test.apex"); |
| AddPreInstalledApex("apex.apexd_test_different_app.apex"); |
| } |
| |
| void TearDown() override { |
| ApexdMountTest::TearDown(); |
| // Unmap dm-linear devices mapped by ApexImageManager |
| for (const auto& image : image_manager_->GetAllImages()) { |
| image_manager_->UnmapImageIfExists(image); |
| } |
| } |
| |
| void SimulateReboot() { |
| DeactivateAllPackages(); |
| ApexFileRepository::GetInstance().Reset(); |
| // Staged apexes in /data/app-staging are not accessible |
| DeleteDirContent(staged_session_dir_); |
| InitializeVold(nullptr); |
| } |
| }; |
| |
| TEST_F(MountBeforeDataTest, ActivatePinnedApex) { |
| ASSERT_EQ(0, OnBootstrap()); |
| |
| auto orig = ApexFile::Open(GetTestFile("apex.apexd_test_v2.apex")); |
| ASSERT_THAT(orig, Ok()); |
| auto name = orig->GetManifest().name(); |
| |
| auto pinned = image_manager_->PinApexFiles(Single(*orig)); |
| ASSERT_THAT(pinned, Ok()); |
| |
| auto image = pinned->at(0); |
| auto block_dev_path = image_manager_->MapImage(image); |
| ASSERT_THAT(block_dev_path, Ok()); |
| auto unmap = |
| base::make_scope_guard([&]() { image_manager_->UnmapImage(image); }); |
| |
| ASSERT_THAT(ActivatePackage(*block_dev_path), Ok()); |
| auto deactivate = base::make_scope_guard( |
| [&]() { ASSERT_THAT(DeactivatePackage(*block_dev_path), Ok()); }); |
| |
| // Checks if PopulateFromMounts() works okay with dm-linear device |
| MountedApexDatabase db; |
| db.PopulateFromMounts(); |
| auto linear_name = image + kDmLinearPayloadSuffix; |
| ASSERT_THAT(db.GetLatestMountedApex(name), |
| Optional(Field(&MountedApexData::linear_name, linear_name))); |
| } |
| |
| TEST_F(MountBeforeDataTest, NonStagedInstall_SucceedAgainstPreinstalled) { |
| ASSERT_EQ(0, OnBootstrap()); |
| |
| // Install succeeds. |
| const auto apex_name = "com.android.apex.test_package"s; |
| ASSERT_THAT(InstallPackage(GetTestFile("apex.apexd_test_v2.apex"), |
| /* force= */ true), |
| Ok()); |
| |
| // Active list is updated with new install. |
| auto active_list = image_manager_->GetApexList(ApexListType::ACTIVE); |
| ASSERT_THAT(active_list, HasValue(SizeIs(1))); |
| auto entry = active_list->at(0); |
| ASSERT_EQ(entry.apex_name, apex_name); |
| |
| // Active mount is backed by the mapped device. |
| auto mount_data = GetApexDatabaseForTesting().GetLatestMountedApex(apex_name); |
| ASSERT_TRUE(mount_data.has_value()); |
| ASSERT_THAT(image_manager_->MapImage(entry.image_name), |
| HasValue(mount_data->full_path)); |
| } |
| |
| TEST_F(MountBeforeDataTest, NonStagedInstall_SucceedAgainstData) { |
| ASSERT_EQ(0, OnBootstrap()); |
| |
| const auto apex_name = "com.android.apex.test_package"s; |
| ASSERT_THAT(InstallPackage(GetTestFile("apex.apexd_test.apex"), |
| /* force= */ true), |
| Ok()); |
| // Keep the path of the newly installed apex |
| auto mount_data = GetApexDatabaseForTesting().GetLatestMountedApex(apex_name); |
| ASSERT_TRUE(mount_data.has_value()); |
| auto data_apex = ApexFile::Open(mount_data->full_path); |
| |
| // Second installation replaces the previous one. |
| ASSERT_THAT(InstallPackage(GetTestFile("apex.apexd_test_v2.apex"), |
| /* force= */ true), |
| Ok()); |
| |
| // and the previous apex is removed. |
| ASSERT_THAT(PathExists(data_apex->GetPath()), HasValue(false)); |
| ASSERT_THAT(GetImageManager()->FindPinnedApex(*data_apex), Eq(std::nullopt)); |
| } |
| |
| TEST_F(MountBeforeDataTest, NonStagedInstall_FailToCreateBackingImage) { |
| // Setup failing image manager |
| struct MockApexImageManager : public ApexImageManager { |
| MockApexImageManager(std::string metadata_dir, std::string data_dir) |
| : ApexImageManager(metadata_dir, data_dir) {} |
| Result<std::vector<std::string>> PinApexFiles( |
| std::span<const ApexFile>) override { |
| return Error() << "Can't pin apex"; |
| } |
| } test_im{metadata_images_dir_, data_images_dir_}; |
| InitializeImageManager(&test_im); |
| auto guard = |
| make_scope_guard([&] { InitializeImageManager(image_manager_.get()); }); |
| |
| ASSERT_EQ(0, OnBootstrap()); |
| auto mounts = GetApexMounts(); |
| auto active_list = GetImageManager()->GetApexList(ApexListType::ACTIVE); |
| |
| ASSERT_THAT(InstallPackage(GetTestFile("apex.apexd_test_v2.apex"), |
| /* force= */ true), |
| HasError(WithMessage("Can't pin apex"))); |
| |
| // Others remain the same |
| ASSERT_THAT(GetApexMounts(), UnorderedElementsAreArray(mounts)); |
| ASSERT_EQ(GetImageManager()->GetApexList(ApexListType::ACTIVE), active_list); |
| } |
| |
| TEST_F(MountBeforeDataTest, NonStagedInstall_FailToMapImage) { |
| // Setup failing image manager |
| struct MockApexImageManager : public ApexImageManager { |
| MockApexImageManager(std::string metadata_dir, std::string data_dir) |
| : ApexImageManager(metadata_dir, data_dir) {} |
| Result<std::string> MapImage(const std::string&) override { |
| return Error() << "Can't map image"; |
| } |
| } test_im{metadata_images_dir_, data_images_dir_}; |
| InitializeImageManager(&test_im); |
| auto guard = |
| make_scope_guard([&] { InitializeImageManager(image_manager_.get()); }); |
| |
| ASSERT_EQ(0, OnBootstrap()); |
| auto mounts = GetApexMounts(); |
| auto active_list = GetImageManager()->GetApexList(ApexListType::ACTIVE); |
| |
| ASSERT_THAT(InstallPackage(GetTestFile("apex.apexd_test_v2.apex"), |
| /* force= */ true), |
| HasError(WithMessage("Can't map image"))); |
| |
| // Others remain the same |
| ASSERT_THAT(GetApexMounts(), UnorderedElementsAreArray(mounts)); |
| ASSERT_EQ(GetImageManager()->GetApexList(ApexListType::ACTIVE), active_list); |
| // Pinned apex is deleted on error. |
| ASSERT_THAT(GetImageManager()->GetAllImages(), IsEmpty()); |
| } |
| |
| TEST_F(MountBeforeDataTest, StagingCreatesBackingImages) { |
| ASSERT_EQ(0, OnBootstrap()); |
| |
| auto session_id = 42; |
| PrepareStagedSession("apex.apexd_test.apex", session_id); |
| ASSERT_THAT(SubmitStagedSession(session_id, {}, false, false, -1), Ok()); |
| |
| auto session = GetSessionManager()->GetSession(session_id); |
| ASSERT_THAT(session->GetApexImages(), |
| Pointwise(Eq(), image_manager_->GetAllImages())); |
| } |
| |
| TEST_F(MountBeforeDataTest, AbortSessionRemovesBackingImages) { |
| ASSERT_EQ(0, OnBootstrap()); |
| |
| auto session_id = 42; |
| PrepareStagedSession("apex.apexd_test.apex", session_id); |
| ASSERT_THAT(SubmitStagedSession(session_id, {}, false, false, -1), Ok()); |
| ASSERT_THAT(AbortStagedSession(session_id), Ok()); |
| |
| ASSERT_THAT(image_manager_->GetAllImages(), IsEmpty()); |
| } |
| |
| TEST_F(MountBeforeDataTest, OnBootstrapActivatesAllApexes) { |
| ASSERT_EQ(0, OnBootstrap()); |
| |
| ASSERT_THAT(GetApexMounts(), |
| UnorderedElementsAre("/apex/com.android.apex.test_package_2"s, |
| "/apex/com.android.apex.test_package_2@1"s, |
| "/apex/com.android.apex.test_package"s, |
| "/apex/com.android.apex.test_package@1"s)); |
| } |
| |
| TEST_F(MountBeforeDataTest, OnBootstrapActivatesAllApexes_ActivateData) { |
| // Prepare pinned data apex before onBootstrap() |
| auto data_apex = ApexFile::Open(GetTestFile("apex.apexd_test_v2.apex")); |
| auto pinned = image_manager_->PinApexFiles(Single(*data_apex)); |
| ASSERT_THAT(pinned, Ok()); |
| // Prepare the active list |
| std::vector<ApexListEntry> list; |
| list.emplace_back(pinned->at(0), data_apex->GetManifest().name()); |
| ASSERT_THAT(image_manager_->UpdateApexList(ApexListType::ACTIVE, list), Ok()); |
| |
| ASSERT_EQ(0, OnBootstrap()); |
| |
| // Pinned apex (com.android.apex.test_package@2) is activated. |
| ASSERT_THAT(GetApexMounts(), |
| Contains("/apex/com.android.apex.test_package@2")); |
| } |
| |
| TEST_F(MountBeforeDataTest, OnBootstrapActivatesAllApexes_IgnoreInvalidImage) { |
| // Prepare pinned data apex before onBootstrap() |
| auto data_apex = ApexFile::Open(GetTestFile("apex.apexd_test_v2.apex")); |
| auto pinned = image_manager_->PinApexFiles(Single(*data_apex)); |
| ASSERT_THAT(pinned, Ok()); |
| // Prepare the active list |
| std::vector<ApexListEntry> list; |
| list.emplace_back("invalid", "invalid"); // invalid |
| list.emplace_back(pinned->at(0), data_apex->GetManifest().name()); // valid |
| ASSERT_THAT(image_manager_->UpdateApexList(ApexListType::ACTIVE, list), Ok()); |
| |
| // OnBootstrap() should succeed with valid ones. |
| ASSERT_EQ(0, OnBootstrap()); |
| ASSERT_THAT(GetApexMounts(), |
| Contains("/apex/com.android.apex.test_package@2")); |
| } |
| |
| TEST_F(MountBeforeDataTest, |
| OnBootstrapActivatesAllApexes_FallbackToPreinstalled) { |
| // Prepare pinned data apex before onBootstrap() |
| auto data_apex = |
| ApexFile::Open(GetTestFile("apex.apexd_test_manifest_mismatch.apex")); |
| ASSERT_THAT(data_apex, Ok()); |
| auto pinned = image_manager_->PinApexFiles(Single(*data_apex)); |
| ASSERT_THAT(pinned, Ok()); |
| // Prepare the active list |
| std::vector<ApexListEntry> list; |
| list.emplace_back(pinned->at(0), data_apex->GetManifest().name()); |
| ASSERT_THAT(image_manager_->UpdateApexList(ApexListType::ACTIVE, list), Ok()); |
| |
| // OnBootstrap() should succeed with preinstalled apexes (@1). |
| ASSERT_EQ(0, OnBootstrap()); |
| ASSERT_THAT(GetApexMounts(), |
| Contains("/apex/com.android.apex.test_package@1")); |
| |
| // On boot-completed, the problematic apex should be removed. |
| BootCompletedCleanup(); |
| ASSERT_THAT(image_manager_->GetAllImages(), IsEmpty()); |
| ASSERT_THAT(image_manager_->GetApexList(ApexListType::ACTIVE), |
| HasValue(IsEmpty())); |
| } |
| |
| TEST_F(MountBeforeDataTest, OnBootstrapActivatesStagedSessions) { |
| // Given that com.android.apex.test_package@1 is preinstalled |
| ASSERT_EQ(0, OnBootstrap()); |
| auto mounts = GetApexMounts(); |
| |
| // Stage com.android.apex.test_package@2 |
| auto session_id = 42; |
| PrepareStagedSession("apex.apexd_test_v2.apex", session_id); |
| ASSERT_THAT(SubmitStagedSession(session_id, {}, false, false, -1), Ok()); |
| ASSERT_THAT(MarkStagedSessionReady(session_id), Ok()); |
| |
| SimulateReboot(); |
| |
| ASSERT_THAT(OnBootstrap(), Eq(0)); |
| |
| // Staged session should be activated. |
| auto session = GetSessionManager()->GetSession(session_id); |
| ASSERT_THAT(session, Ok()); |
| ASSERT_EQ(session->GetState(), SessionState::ACTIVATED); |
| |
| // The apex in the session should be activated. |
| std::ranges::replace(mounts, "/apex/com.android.apex.test_package@1"s, |
| "/apex/com.android.apex.test_package@2"s); |
| ASSERT_THAT(GetApexMounts(), UnorderedElementsAreArray(mounts)); |
| } |
| |
| TEST_F(MountBeforeDataTest, OnStartSkipsActivation) { |
| ASSERT_EQ(0, OnBootstrap()); |
| auto mounts = GetApexMounts(); |
| |
| // Apexes in /data/apex/active should be ignored. |
| AddDataApex("apex.apexd_test_v2.apex"); |
| OnStart(); |
| |
| // Mounts remain unchanged. |
| ASSERT_THAT(GetApexMounts(), Eq(mounts)); |
| } |
| |
| TEST_F(MountBeforeDataTest, BootCompletedCleanup_RemovesInactiveDataApexes) { |
| // apex0 is valid and apex1 is unknown. |
| auto apex0 = ApexFile::Open(GetTestFile("apex.apexd_test_v2.apex")); |
| auto apex1 = ApexFile::Open(GetTestFile("test.rebootless_apex_v1.apex")); |
| auto pinned = image_manager_->PinApexFiles(std::vector{*apex0, *apex1}); |
| ASSERT_THAT(pinned, HasValue(SizeIs(2))); |
| std::vector<ApexListEntry> active_list{ |
| {pinned->at(0), apex0->GetManifest().name()}, |
| {pinned->at(1), apex1->GetManifest().name()}, |
| }; |
| ASSERT_THAT(image_manager_->UpdateApexList(ApexListType::ACTIVE, active_list), |
| Ok()); |
| |
| // APEX files in /data/apex/active should be skipped and removed. |
| auto data_apex = AddDataApex("apex.apexd_test_v2.apex"); |
| |
| ASSERT_EQ(0, OnBootstrap()); |
| BootCompletedCleanup(); |
| |
| ASSERT_THAT(PathExists(data_apex), HasValue(false)); |
| ASSERT_THAT(image_manager_->GetAllImages(), |
| UnorderedElementsAre(pinned->at(0))); |
| } |
| |
| TEST_F(MountBeforeDataTest, BootCompletedCleanup_CreatesConfigFile) { |
| if (!flags::mount_before_data()) { |
| GTEST_SKIP() << "mount_before_data is off"; |
| } |
| ASSERT_EQ(0, OnBootstrap()); |
| BootCompletedCleanup(); |
| auto config_file = metadata_config_dir_ + "/mount_before_data"; |
| ASSERT_EQ(0, access(config_file.c_str(), F_OK)); |
| } |
| |
| TEST_F(MountBeforeDataTest, BrandNewApex) { |
| fs::copy(GetTestFile("apexd_testdata/com.android.apex.brand.new.avbpubkey"), |
| brand_new_config_dir_); |
| ApexFileRepository::EnableBrandNewApex(); |
| ASSERT_EQ(0, OnBootstrap()); |
| |
| // Prepare brand-new apex installation |
| auto session_id = 42; |
| PrepareStagedSession("com.android.apex.brand.new.apex", session_id); |
| ASSERT_THAT(SubmitStagedSession(session_id, {}, false, false, -1), Ok()); |
| ASSERT_THAT(MarkStagedSessionReady(session_id), Ok()); |
| |
| SimulateReboot(); |
| ApexFileRepository::EnableBrandNewApex(); |
| ASSERT_EQ(0, OnBootstrap()); |
| |
| // Staged session should be activated. |
| auto session = GetSessionManager()->GetSession(session_id); |
| ASSERT_THAT(session, Ok()); |
| ASSERT_EQ(session->GetState(), SessionState::ACTIVATED); |
| ApexFileRepository::GetInstance().Reset(); |
| } |
| |
| TEST_F(MountBeforeDataTest, UnstagePackages) { |
| ASSERT_EQ(0, OnBootstrap()); |
| |
| // Install v2. |
| const auto apex_name = "com.android.apex.test_package"s; |
| ASSERT_THAT(InstallPackage(GetTestFile("apex.apexd_test_v2.apex"), |
| /* force= */ true), |
| Ok()); |
| ASSERT_THAT(GetApexMounts(), |
| Contains("/apex/com.android.apex.test_package@2")); |
| |
| auto mount_data = GetApexDatabaseForTesting().GetLatestMountedApex(apex_name); |
| ASSERT_TRUE(mount_data.has_value()); |
| |
| // Remove v2. |
| ASSERT_THAT(UnstagePackages({mount_data->full_path}), Ok()); |
| |
| SimulateReboot(); |
| ASSERT_THAT(OnBootstrap(), Eq(0)); |
| |
| // Now v1 is activated. |
| ASSERT_THAT(GetApexMounts(), |
| Contains("/apex/com.android.apex.test_package@1")); |
| } |
| |
| class LogTestToLogcat : public ::testing::EmptyTestEventListener { |
| void OnTestStart(const ::testing::TestInfo& test_info) override { |
| #ifdef __ANDROID__ |
| using base::LogId; |
| using base::LogSeverity; |
| using base::StringPrintf; |
| base::LogdLogger l; |
| std::string msg = |
| StringPrintf("=== %s::%s (%s:%d)", test_info.test_suite_name(), |
| test_info.name(), test_info.file(), test_info.line()); |
| l(LogId::MAIN, LogSeverity::INFO, "ApexTestCases", __FILE__, __LINE__, |
| msg.c_str()); |
| #else |
| UNUSED(test_info); |
| #endif |
| } |
| }; |
| |
| } // namespace apex |
| } // namespace android |
| |
| int main(int argc, char** argv) { |
| ::testing::InitGoogleTest(&argc, argv); |
| android::base::InitLogging(argv, &android::base::StderrLogger); |
| android::base::SetMinimumLogSeverity(android::base::VERBOSE); |
| ::testing::UnitTest::GetInstance()->listeners().Append( |
| new android::apex::LogTestToLogcat()); |
| return RUN_ALL_TESTS(); |
| } |