/* * Copyright (C) 2018 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "apex_constants.h" #include "apex_file.h" #include "apex_manifest.h" #include "apexd_private.h" #include "apexd_session.h" #include "apexd_test_utils.h" #include "apexd_utils.h" #include "status_or.h" #include "session_state.pb.h" using apex::proto::SessionState; namespace android { namespace apex { using android::sp; using android::String16; using android::apex::testing::ApexInfoEq; using android::apex::testing::CreateSessionInfo; using android::apex::testing::IsOk; using android::apex::testing::SessionInfoEq; using android::base::Join; using android::base::StringPrintf; using ::testing::Contains; using ::testing::EndsWith; using ::testing::HasSubstr; using ::testing::Not; using ::testing::UnorderedElementsAre; using ::testing::UnorderedElementsAreArray; namespace fs = std::filesystem; class ApexServiceTest : public ::testing::Test { public: ApexServiceTest() { using android::IBinder; using android::IServiceManager; sp sm = android::defaultServiceManager(); sp binder = sm->getService(String16("apexservice")); if (binder != nullptr) { service_ = android::interface_cast(binder); } binder = sm->getService(String16("vold")); if (binder != nullptr) { vold_service_ = android::interface_cast(binder); } } protected: void SetUp() override { ASSERT_NE(nullptr, service_.get()); ASSERT_NE(nullptr, vold_service_.get()); android::binder::Status status = vold_service_->supportsCheckpoint(&supports_fs_checkpointing_); ASSERT_TRUE(IsOk(status)); CleanUp(); } void TearDown() override { CleanUp(); } static std::string GetTestDataDir() { return android::base::GetExecutableDirectory(); } static std::string GetTestFile(const std::string& name) { return GetTestDataDir() + "/" + name; } static bool HaveSelinux() { return 1 == is_selinux_enabled(); } static bool IsSelinuxEnforced() { return 0 != security_getenforce(); } StatusOr IsActive(const std::string& name, int64_t version) { std::vector list; android::binder::Status status = service_->getActivePackages(&list); if (status.isOk()) { for (const ApexInfo& p : list) { if (p.packageName == name && p.versionCode == version) { return StatusOr(true); } } return StatusOr(false); } return StatusOr::MakeError(status.exceptionMessage().c_str()); } StatusOr> GetAllPackages() { std::vector list; android::binder::Status status = service_->getAllPackages(&list); if (status.isOk()) { return StatusOr>(list); } return StatusOr>::MakeError( status.toString8().c_str()); } StatusOr> GetActivePackages() { std::vector list; android::binder::Status status = service_->getActivePackages(&list); if (status.isOk()) { return StatusOr>(list); } return StatusOr>::MakeError( status.exceptionMessage().c_str()); } StatusOr> GetInactivePackages() { std::vector list; android::binder::Status status = service_->getAllPackages(&list); list.erase(std::remove_if( list.begin(), list.end(), [](const ApexInfo& apexInfo) { return apexInfo.isActive; }), list.end()); if (status.isOk()) { return StatusOr>(std::move(list)); } return StatusOr>::MakeError( status.toString8().c_str()); } StatusOr GetActivePackage(const std::string& name) { ApexInfo package; android::binder::Status status = service_->getActivePackage(name, &package); if (status.isOk()) { return StatusOr(package); } return StatusOr::MakeError(status.exceptionMessage().c_str()); } std::string GetPackageString(const ApexInfo& p) { return p.packageName + "@" + std::to_string(p.versionCode) + " [path=" + p.packagePath + "]"; } std::vector GetPackagesStrings( const std::vector& list) { std::vector ret; ret.reserve(list.size()); for (const ApexInfo& p : list) { ret.push_back(GetPackageString(p)); } return ret; } std::vector GetActivePackagesStrings() { std::vector list; android::binder::Status status = service_->getActivePackages(&list); if (status.isOk()) { std::vector ret(list.size()); for (const ApexInfo& p : list) { ret.push_back(GetPackageString(p)); } return ret; } std::vector error; error.push_back("ERROR"); return error; } StatusOr> GetFactoryPackages() { std::vector list; android::binder::Status status = service_->getAllPackages(&list); list.erase( std::remove_if(list.begin(), list.end(), [](ApexInfo& apexInfo) { return !apexInfo.isFactory; }), list.end()); if (status.isOk()) { return StatusOr>(std::move(list)); } return StatusOr>::MakeError( status.toString8().c_str()); } static std::vector ListDir(const std::string& path) { std::vector ret; std::error_code ec; if (!fs::is_directory(path, ec)) { return ret; } WalkDir(path, [&](const fs::directory_entry& entry) { std::string tmp; switch (entry.symlink_status(ec).type()) { case fs::file_type::directory: tmp = "[dir]"; break; case fs::file_type::symlink: tmp = "[lnk]"; break; case fs::file_type::regular: tmp = "[reg]"; break; default: tmp = "[other]"; } ret.push_back(tmp.append(entry.path().filename())); }); std::sort(ret.begin(), ret.end()); return ret; } static std::string GetLogcat() { // For simplicity, log to file and read it. std::string file = GetTestFile("logcat.tmp.txt"); std::vector args{ "/system/bin/logcat", "-d", "-f", file, }; std::string error_msg; int res = ForkAndRun(args, &error_msg); CHECK_EQ(0, res) << error_msg; std::string data; CHECK(android::base::ReadFileToString(file, &data)); unlink(file.c_str()); return data; } struct PrepareTestApexForInstall { static constexpr const char* kTestDir = "/data/app-staging/apexservice_tmp"; // This is given to the constructor. std::string test_input; // Original test file. std::string selinux_label_input; // SELinux label to apply. std::string test_dir_input; // This is derived from the input. std::string test_file; // Prepared path. Under test_dir_input. std::string test_installed_file; // Where apexd will store it. std::string package; // APEX package name. uint64_t version; // APEX version explicit PrepareTestApexForInstall( const std::string& test, const std::string& test_dir = std::string(kTestDir), const std::string& selinux_label = "staging_data_file") { test_input = test; selinux_label_input = selinux_label; test_dir_input = test_dir; test_file = test_dir_input + "/" + android::base::Basename(test); package = ""; // Explicitly mark as not initialized. StatusOr apex_file = ApexFile::Open(test); if (!apex_file.Ok()) { return; } const ApexManifest& manifest = apex_file->GetManifest(); package = manifest.name(); version = manifest.version(); test_installed_file = std::string(kActiveApexPackagesDataDir) + "/" + package + "@" + std::to_string(version) + ".apex"; } bool Prepare() { if (package.empty()) { // Failure in constructor. Redo work to get error message. auto fail_fn = [&]() { StatusOr apex_file = ApexFile::Open(test_input); ASSERT_FALSE(IsOk(apex_file)); ASSERT_TRUE(apex_file.Ok()) << test_input << " failed to load: " << apex_file.ErrorMessage(); }; fail_fn(); return false; } auto prepare = [](const std::string& src, const std::string& trg, const std::string& selinux_label) { ASSERT_EQ(0, access(src.c_str(), F_OK)) << src << ": " << strerror(errno); const std::string trg_dir = android::base::Dirname(trg); if (0 != mkdir(trg_dir.c_str(), 0777)) { int saved_errno = errno; ASSERT_EQ(saved_errno, EEXIST) << trg << ":" << strerror(saved_errno); } // Do not use a hardlink, even though it's the simplest solution. // b/119569101. { std::ifstream src_stream(src, std::ios::binary); ASSERT_TRUE(src_stream.good()); std::ofstream trg_stream(trg, std::ios::binary); ASSERT_TRUE(trg_stream.good()); trg_stream << src_stream.rdbuf(); } ASSERT_EQ(0, chmod(trg.c_str(), 0666)) << strerror(errno); struct group* g = getgrnam("system"); ASSERT_NE(nullptr, g); ASSERT_EQ(0, chown(trg.c_str(), /* root uid */ 0, g->gr_gid)) << strerror(errno); int rc = setfilecon( trg_dir.c_str(), std::string("u:object_r:" + selinux_label + ":s0").c_str()); ASSERT_TRUE(0 == rc || !HaveSelinux()) << strerror(errno); rc = setfilecon( trg.c_str(), std::string("u:object_r:" + selinux_label + ":s0").c_str()); ASSERT_TRUE(0 == rc || !HaveSelinux()) << strerror(errno); }; prepare(test_input, test_file, selinux_label_input); return !HasFatalFailure(); } ~PrepareTestApexForInstall() { if (unlink(test_file.c_str()) != 0) { PLOG(ERROR) << "Unable to unlink " << test_file; } if (rmdir(test_dir_input.c_str()) != 0) { PLOG(ERROR) << "Unable to rmdir " << test_dir_input; } if (!package.empty()) { // For cleanliness, also attempt to delete apexd's file. // TODO: to the unstaging using APIs if (unlink(test_installed_file.c_str()) != 0) { PLOG(ERROR) << "Unable to unlink " << test_installed_file; } } } }; std::string GetDebugStr(PrepareTestApexForInstall* installer) { StringLog log; if (installer != nullptr) { log << "test_input=" << installer->test_input << " "; log << "test_file=" << installer->test_file << " "; log << "test_installed_file=" << installer->test_installed_file << " "; log << "package=" << installer->package << " "; log << "version=" << installer->version << " "; } log << "active=[" << Join(GetActivePackagesStrings(), ',') << "] "; log << kActiveApexPackagesDataDir << "=[" << Join(ListDir(kActiveApexPackagesDataDir), ',') << "] "; log << kApexRoot << "=[" << Join(ListDir(kApexRoot), ',') << "]"; return log; } sp service_; sp vold_service_; bool supports_fs_checkpointing_; private: void CleanUp() { auto status = WalkDir(kApexDataDir, [](const fs::directory_entry& p) { std::error_code ec; fs::file_status status = p.status(ec); ASSERT_FALSE(ec) << "Failed to stat " << p.path() << " : " << ec.message(); if (fs::is_directory(status)) { fs::remove_all(p.path(), ec); } else { fs::remove(p.path(), ec); } ASSERT_FALSE(ec) << "Failed to delete " << p.path() << " : " << ec.message(); }); ASSERT_TRUE(IsOk(status)); } }; namespace { bool RegularFileExists(const std::string& path) { struct stat buf; if (0 != stat(path.c_str(), &buf)) { return false; } return S_ISREG(buf.st_mode); } } // namespace TEST_F(ApexServiceTest, HaveSelinux) { // We want to test under selinux. EXPECT_TRUE(HaveSelinux()); } // Skip for b/119032200. TEST_F(ApexServiceTest, DISABLED_EnforceSelinux) { // Crude cutout for virtual devices. #if !defined(__i386__) && !defined(__x86_64__) constexpr bool kIsX86 = false; #else constexpr bool kIsX86 = true; #endif EXPECT_TRUE(IsSelinuxEnforced() || kIsX86); } TEST_F(ApexServiceTest, StageFailAccess) { if (!IsSelinuxEnforced()) { LOG(WARNING) << "Skipping InstallFailAccess because of selinux"; return; } // Use an extra copy, so that even if this test fails (incorrectly installs), // we have the testdata file still around. std::string orig_test_file = GetTestFile("apex.apexd_test.apex"); std::string test_file = orig_test_file + ".2"; ASSERT_EQ(0, link(orig_test_file.c_str(), test_file.c_str())) << strerror(errno); struct Deleter { std::string to_delete; explicit Deleter(const std::string& t) : to_delete(t) {} ~Deleter() { if (unlink(to_delete.c_str()) != 0) { PLOG(ERROR) << "Could not unlink " << to_delete; } } }; Deleter del(test_file); bool success; android::binder::Status st = service_->stagePackage(test_file, &success); ASSERT_FALSE(IsOk(st)); std::string error = st.exceptionMessage().c_str(); EXPECT_NE(std::string::npos, error.find("Failed to open package")) << error; EXPECT_NE(std::string::npos, error.find("I/O error")) << error; } // TODO(jiyong): re-enable this test. This test is disabled because the build // system now always bundles the public key that was used to sign the APEX. // In debuggable build, the bundled public key is used as the last fallback. // As a result, the verification is always successful (and thus test fails). // In order to re-enable this test, we have to manually create an APEX // where public key is not bundled. #if 0 TEST_F(ApexServiceTest, StageFailKey) { PrepareTestApexForInstall installer( GetTestFile("apex.apexd_test_no_inst_key.apex")); if (!installer.Prepare()) { return; } ASSERT_EQ(std::string("com.android.apex.test_package.no_inst_key"), installer.package); bool success; android::binder::Status st = service_->stagePackage(installer.test_file, &success); ASSERT_FALSE(IsOk(st)); // May contain one of two errors. std::string error = st.exceptionMessage().c_str(); constexpr const char* kExpectedError1 = "Failed to get realpath of "; const size_t pos1 = error.find(kExpectedError1); constexpr const char* kExpectedError2 = "/etc/security/apex/com.android.apex.test_package.no_inst_key"; const size_t pos2 = error.find(kExpectedError2); constexpr const char* kExpectedError3 = "Error verifying " "/data/app-staging/apexservice_tmp/apex.apexd_test_no_inst_key.apex: " "couldn't verify public key: Failed to compare the bundled public key " "with key"; const size_t pos3 = error.find(kExpectedError3); const size_t npos = std::string::npos; EXPECT_TRUE((pos1 != npos && pos2 != npos) || pos3 != npos) << error; } #endif TEST_F(ApexServiceTest, StageSuccess) { PrepareTestApexForInstall installer(GetTestFile("apex.apexd_test.apex")); if (!installer.Prepare()) { return; } ASSERT_EQ(std::string("com.android.apex.test_package"), installer.package); bool success; ASSERT_TRUE(IsOk(service_->stagePackage(installer.test_file, &success))); ASSERT_TRUE(success); EXPECT_TRUE(RegularFileExists(installer.test_installed_file)); } TEST_F(ApexServiceTest, SubmitStagegSessionSuccessDoesNotLeakTempVerityDevices) { using android::dm::DeviceMapper; PrepareTestApexForInstall installer(GetTestFile("apex.apexd_test.apex"), "/data/app-staging/session_1543", "staging_data_file"); if (!installer.Prepare()) { return; } ApexInfoList list; bool success; ASSERT_TRUE(IsOk(service_->submitStagedSession(1543, {}, &list, &success))); ASSERT_TRUE(success); std::vector devices; DeviceMapper& dm = DeviceMapper::Instance(); ASSERT_TRUE(dm.GetAvailableDevices(&devices)); for (const auto& device : devices) { ASSERT_THAT(device.name(), Not(EndsWith(".tmp"))); } } TEST_F(ApexServiceTest, SubmitStagedSessionFailDoesNotLeakTempVerityDevices) { using android::dm::DeviceMapper; PrepareTestApexForInstall installer( GetTestFile("apex.apexd_test_manifest_mismatch.apex"), "/data/app-staging/session_239", "staging_data_file"); if (!installer.Prepare()) { return; } ApexInfoList list; bool success; ASSERT_TRUE(IsOk(service_->submitStagedSession(239, {}, &list, &success))); ASSERT_FALSE(success); std::vector devices; DeviceMapper& dm = DeviceMapper::Instance(); ASSERT_TRUE(dm.GetAvailableDevices(&devices)); for (const auto& device : devices) { ASSERT_THAT(device.name(), Not(EndsWith(".tmp"))); } } TEST_F(ApexServiceTest, StageSuccess_ClearsPreviouslyActivePackage) { PrepareTestApexForInstall installer1(GetTestFile("apex.apexd_test_v2.apex")); PrepareTestApexForInstall installer2( GetTestFile("apex.apexd_test_different_app.apex")); PrepareTestApexForInstall installer3(GetTestFile("apex.apexd_test.apex")); auto install_fn = [&](PrepareTestApexForInstall& installer) { if (!installer.Prepare()) { return; } bool success; ASSERT_TRUE(IsOk(service_->stagePackage(installer.test_file, &success))); ASSERT_TRUE(success); EXPECT_TRUE(RegularFileExists(installer.test_installed_file)); }; install_fn(installer1); install_fn(installer2); // Simulating a rollback. After this call test_v2_apex_path should be removed. install_fn(installer3); EXPECT_FALSE(RegularFileExists(installer1.test_installed_file)); EXPECT_TRUE(RegularFileExists(installer2.test_installed_file)); EXPECT_TRUE(RegularFileExists(installer3.test_installed_file)); } TEST_F(ApexServiceTest, StageAlreadyStagedPackageSuccess) { PrepareTestApexForInstall installer(GetTestFile("apex.apexd_test.apex")); if (!installer.Prepare()) { return; } ASSERT_EQ(std::string("com.android.apex.test_package"), installer.package); bool success = false; ASSERT_TRUE(IsOk(service_->stagePackage(installer.test_file, &success))); ASSERT_TRUE(success); ASSERT_TRUE(RegularFileExists(installer.test_installed_file)); success = false; ASSERT_TRUE(IsOk(service_->stagePackage(installer.test_file, &success))); ASSERT_TRUE(success); ASSERT_TRUE(RegularFileExists(installer.test_installed_file)); } TEST_F(ApexServiceTest, MultiStageSuccess) { PrepareTestApexForInstall installer(GetTestFile("apex.apexd_test.apex")); if (!installer.Prepare()) { return; } ASSERT_EQ(std::string("com.android.apex.test_package"), installer.package); // TODO: Add second test. Right now, just use a separate version. PrepareTestApexForInstall installer2(GetTestFile("apex.apexd_test_v2.apex")); if (!installer2.Prepare()) { return; } ASSERT_EQ(std::string("com.android.apex.test_package"), installer2.package); std::vector packages; packages.push_back(installer.test_file); packages.push_back(installer2.test_file); bool success; ASSERT_TRUE(IsOk(service_->stagePackages(packages, &success))); ASSERT_TRUE(success); EXPECT_TRUE(RegularFileExists(installer.test_installed_file)); EXPECT_TRUE(RegularFileExists(installer2.test_installed_file)); } template class ApexServiceActivationTest : public ApexServiceTest { public: ApexServiceActivationTest() : stage_package(true) {} explicit ApexServiceActivationTest(bool stage_package) : stage_package(stage_package) {} void SetUp() override { ApexServiceTest::SetUp(); ASSERT_NE(nullptr, service_.get()); installer_ = std::make_unique( GetTestFile(NameProvider::GetTestName())); if (!installer_->Prepare()) { return; } ASSERT_EQ(NameProvider::GetPackageName(), installer_->package); { // Check package is not active. StatusOr active = IsActive(installer_->package, installer_->version); ASSERT_TRUE(IsOk(active)); ASSERT_FALSE(*active); } if (stage_package) { bool success; ASSERT_TRUE( IsOk(service_->stagePackage(installer_->test_file, &success))); ASSERT_TRUE(success); } } void TearDown() override { // Attempt to deactivate. if (installer_ != nullptr) { if (stage_package) { service_->deactivatePackage(installer_->test_installed_file); } else { service_->deactivatePackage(installer_->test_file); } } installer_.reset(); // ApexServiceTest::TearDown will wipe out everything under /data/apex. // Since some of that information is required for deactivePackage binder // call, it's required to be called after deactivating package. ApexServiceTest::TearDown(); } std::unique_ptr installer_; private: bool stage_package; }; struct SuccessNameProvider { static std::string GetTestName() { return "apex.apexd_test.apex"; } static std::string GetPackageName() { return "com.android.apex.test_package"; } }; struct ManifestMismatchNameProvider { static std::string GetTestName() { return "apex.apexd_test_manifest_mismatch.apex"; } static std::string GetPackageName() { return "com.android.apex.test_package"; } }; class ApexServiceActivationManifestMismatchFailure : public ApexServiceActivationTest { public: ApexServiceActivationManifestMismatchFailure() : ApexServiceActivationTest(false) {} }; TEST_F(ApexServiceActivationManifestMismatchFailure, ActivateFailsWithManifestMismatch) { android::binder::Status st = service_->activatePackage(installer_->test_file); ASSERT_FALSE(IsOk(st)); std::string error = st.exceptionMessage().c_str(); ASSERT_THAT( error, HasSubstr( "Manifest inside filesystem does not match manifest outside it")); } class ApexServiceActivationSuccessTest : public ApexServiceActivationTest {}; TEST_F(ApexServiceActivationSuccessTest, Activate) { ASSERT_TRUE(IsOk(service_->activatePackage(installer_->test_installed_file))) << GetDebugStr(installer_.get()); { // Check package is active. StatusOr active = IsActive(installer_->package, installer_->version); ASSERT_TRUE(IsOk(active)); ASSERT_TRUE(*active) << Join(GetActivePackagesStrings(), ','); } { // Check that the "latest" view exists. std::string latest_path = std::string(kApexRoot) + "/" + installer_->package; struct stat buf; ASSERT_EQ(0, stat(latest_path.c_str(), &buf)) << strerror(errno); // Check that it is a folder. EXPECT_TRUE(S_ISDIR(buf.st_mode)); // Collect direct entries of a folder. auto collect_entries_fn = [](const std::string& path) { std::vector ret; WalkDir(path, [&](const fs::directory_entry& entry) { if (!entry.is_directory()) { return; } ret.emplace_back(entry.path().filename()); }); std::sort(ret.begin(), ret.end()); return ret; }; std::string versioned_path = std::string(kApexRoot) + "/" + installer_->package + "@" + std::to_string(installer_->version); std::vector versioned_folder_entries = collect_entries_fn(versioned_path); std::vector latest_folder_entries = collect_entries_fn(latest_path); EXPECT_TRUE(versioned_folder_entries == latest_folder_entries) << "Versioned: " << Join(versioned_folder_entries, ',') << " Latest: " << Join(latest_folder_entries, ','); } } TEST_F(ApexServiceActivationSuccessTest, GetActivePackages) { ASSERT_TRUE(IsOk(service_->activatePackage(installer_->test_installed_file))) << GetDebugStr(installer_.get()); StatusOr> active = GetActivePackages(); ASSERT_TRUE(IsOk(active)); ApexInfo match; for (const ApexInfo& info : *active) { if (info.packageName == installer_->package) { match = info; break; } } ASSERT_EQ(installer_->package, match.packageName); ASSERT_EQ(installer_->version, static_cast(match.versionCode)); ASSERT_EQ(installer_->test_installed_file, match.packagePath); } TEST_F(ApexServiceActivationSuccessTest, GetActivePackage) { ASSERT_TRUE(IsOk(service_->activatePackage(installer_->test_installed_file))) << GetDebugStr(installer_.get()); StatusOr active = GetActivePackage(installer_->package); ASSERT_TRUE(IsOk(active)); ASSERT_EQ(installer_->package, active->packageName); ASSERT_EQ(installer_->version, static_cast(active->versionCode)); ASSERT_EQ(installer_->test_installed_file, active->packagePath); } TEST_F(ApexServiceTest, GetFactoryPackages) { using ::android::base::StartsWith; StatusOr> factoryPackages = GetFactoryPackages(); ASSERT_TRUE(IsOk(factoryPackages)); ASSERT_TRUE(factoryPackages->size() > 0); for (const ApexInfo& package : *factoryPackages) { ASSERT_TRUE(isPathForBuiltinApexes(package.packagePath)); } } TEST_F(ApexServiceTest, NoPackagesAreBothActiveAndInactive) { StatusOr> activePackages = GetActivePackages(); ASSERT_TRUE(IsOk(activePackages)); ASSERT_TRUE(activePackages->size() > 0); StatusOr> inactivePackages = GetInactivePackages(); ASSERT_TRUE(IsOk(inactivePackages)); std::vector activePackagesStrings = GetPackagesStrings(*activePackages); std::vector inactivePackagesStrings = GetPackagesStrings(*inactivePackages); std::sort(activePackagesStrings.begin(), activePackagesStrings.end()); std::sort(inactivePackagesStrings.begin(), inactivePackagesStrings.end()); std::vector intersection; std::set_intersection( activePackagesStrings.begin(), activePackagesStrings.end(), inactivePackagesStrings.begin(), inactivePackagesStrings.end(), std::back_inserter(intersection)); ASSERT_EQ(intersection.size(), 0UL); } TEST_F(ApexServiceTest, GetAllPackages) { StatusOr> allPackages = GetAllPackages(); ASSERT_TRUE(IsOk(allPackages)); ASSERT_TRUE(allPackages->size() > 0); StatusOr> activePackages = GetActivePackages(); std::vector activeStrings = GetPackagesStrings(*activePackages); StatusOr> factoryPackages = GetFactoryPackages(); std::vector factoryStrings = GetPackagesStrings(*factoryPackages); for (ApexInfo& apexInfo : *allPackages) { std::string packageString = GetPackageString(apexInfo); bool shouldBeActive = std::find(activeStrings.begin(), activeStrings.end(), packageString) != activeStrings.end(); bool shouldBeFactory = std::find(factoryStrings.begin(), factoryStrings.end(), packageString) != factoryStrings.end(); ASSERT_EQ(shouldBeActive, apexInfo.isActive); ASSERT_EQ(shouldBeFactory, apexInfo.isFactory); } } TEST_F(ApexServiceActivationSuccessTest, StageAlreadyActivePackageSameVersion) { ASSERT_TRUE(IsOk(service_->activatePackage(installer_->test_installed_file))) << GetDebugStr(installer_.get()); bool success = false; ASSERT_TRUE(IsOk(service_->stagePackage(installer_->test_file, &success))); ASSERT_TRUE(success); } class ApexServiceDeactivationTest : public ApexServiceActivationSuccessTest { public: void SetUp() override { ApexServiceActivationSuccessTest::SetUp(); ASSERT_TRUE(installer_ != nullptr); } void TearDown() override { installer_.reset(); ApexServiceActivationSuccessTest::TearDown(); } std::unique_ptr installer_; }; TEST_F(ApexServiceActivationSuccessTest, DmDeviceTearDown) { std::string package_id = installer_->package + "@" + std::to_string(installer_->version); auto find_fn = [](const std::string& name) { auto& dm = dm::DeviceMapper::Instance(); std::vector devices; if (!dm.GetAvailableDevices(&devices)) { return StatusOr::Fail("GetAvailableDevices failed"); } for (const auto& device : devices) { if (device.name() == name) { return StatusOr(true); } } return StatusOr(false); }; #define ASSERT_FIND(type) \ { \ StatusOr res = find_fn(package_id); \ ASSERT_TRUE(res.Ok()); \ ASSERT_##type(*res); \ } ASSERT_FIND(FALSE); ASSERT_TRUE(IsOk(service_->activatePackage(installer_->test_installed_file))) << GetDebugStr(installer_.get()); ASSERT_FIND(TRUE); ASSERT_TRUE( IsOk(service_->deactivatePackage(installer_->test_installed_file))); ASSERT_FIND(FALSE); installer_.reset(); // Skip TearDown deactivatePackage. } class ApexServicePrePostInstallTest : public ApexServiceTest { public: template void RunPrePost(Fn fn, const std::vector& apex_names, const char* test_message, bool expect_success = true) { // Using unique_ptr is just the easiest here. using InstallerUPtr = std::unique_ptr; std::vector installers; std::vector pkgs; for (const std::string& apex_name : apex_names) { InstallerUPtr installer( new PrepareTestApexForInstall(GetTestFile(apex_name))); if (!installer->Prepare()) { return; } pkgs.push_back(installer->test_file); installers.emplace_back(std::move(installer)); } android::binder::Status st = (service_.get()->*fn)(pkgs); if (expect_success) { ASSERT_TRUE(IsOk(st)); } else { ASSERT_FALSE(IsOk(st)); } if (test_message != nullptr) { std::string logcat = GetLogcat(); EXPECT_NE(std::string::npos, logcat.find(test_message)) << logcat; } // Ensure that the package is neither active nor mounted. for (const InstallerUPtr& installer : installers) { StatusOr active = IsActive(installer->package, installer->version); ASSERT_TRUE(IsOk(active)); EXPECT_FALSE(*active); } for (const InstallerUPtr& installer : installers) { StatusOr apex = ApexFile::Open(installer->test_input); ASSERT_TRUE(IsOk(apex)); std::string path = apexd_private::GetPackageMountPoint(apex->GetManifest()); std::string entry = std::string("[dir]").append(path); std::vector slash_apex = ListDir(kApexRoot); auto it = std::find(slash_apex.begin(), slash_apex.end(), entry); EXPECT_TRUE(it == slash_apex.end()) << Join(slash_apex, ','); } } }; TEST_F(ApexServicePrePostInstallTest, Preinstall) { RunPrePost(&IApexService::preinstallPackages, {"apex.apexd_test_preinstall.apex"}, "sh : PreInstall Test"); } TEST_F(ApexServicePrePostInstallTest, MultiPreinstall) { constexpr const char* kLogcatText = "sh : /apex/com.android.apex.test_package/etc/sample_prebuilt_file"; RunPrePost(&IApexService::preinstallPackages, {"apex.apexd_test_preinstall.apex", "apex.apexd_test.apex"}, kLogcatText); } TEST_F(ApexServicePrePostInstallTest, PreinstallFail) { RunPrePost(&IApexService::preinstallPackages, {"apex.apexd_test_prepostinstall.fail.apex"}, /* test_message= */ nullptr, /* expect_success= */ false); } TEST_F(ApexServicePrePostInstallTest, Postinstall) { RunPrePost(&IApexService::postinstallPackages, {"apex.apexd_test_postinstall.apex"}, "sh : PostInstall Test"); } TEST_F(ApexServicePrePostInstallTest, MultiPostinstall) { constexpr const char* kLogcatText = "sh : /apex/com.android.apex.test_package/etc/sample_prebuilt_file"; RunPrePost(&IApexService::postinstallPackages, {"apex.apexd_test_postinstall.apex", "apex.apexd_test.apex"}, kLogcatText); } TEST_F(ApexServicePrePostInstallTest, PostinstallFail) { RunPrePost(&IApexService::postinstallPackages, {"apex.apexd_test_prepostinstall.fail.apex"}, /* test_message= */ nullptr, /* expect_success= */ false); } TEST_F(ApexServiceTest, SubmitSingleSessionTestSuccess) { PrepareTestApexForInstall installer(GetTestFile("apex.apexd_test.apex"), "/data/app-staging/session_123", "staging_data_file"); if (!installer.Prepare()) { FAIL() << GetDebugStr(&installer); } ApexInfoList list; bool ret_value; std::vector empty_child_session_ids; ASSERT_TRUE(IsOk(service_->submitStagedSession(123, empty_child_session_ids, &list, &ret_value))) << GetDebugStr(&installer); EXPECT_TRUE(ret_value); EXPECT_EQ(1u, list.apexInfos.size()); ApexInfo match; for (const ApexInfo& info : list.apexInfos) { if (info.packageName == installer.package) { match = info; break; } } ASSERT_EQ(installer.package, match.packageName); ASSERT_EQ(installer.version, static_cast(match.versionCode)); ASSERT_EQ(installer.test_file, match.packagePath); ApexSessionInfo session; ASSERT_TRUE(IsOk(service_->getStagedSessionInfo(123, &session))) << GetDebugStr(&installer); ApexSessionInfo expected = CreateSessionInfo(123); expected.isVerified = true; EXPECT_THAT(session, SessionInfoEq(expected)); ASSERT_TRUE(IsOk(service_->markStagedSessionReady(123, &ret_value))) << GetDebugStr(&installer); ASSERT_TRUE(ret_value); ASSERT_TRUE(IsOk(service_->getStagedSessionInfo(123, &session))) << GetDebugStr(&installer); expected.isVerified = false; expected.isStaged = true; EXPECT_THAT(session, SessionInfoEq(expected)); // Call markStagedSessionReady again. Should be a no-op. ASSERT_TRUE(IsOk(service_->markStagedSessionReady(123, &ret_value))) << GetDebugStr(&installer); ASSERT_TRUE(ret_value); ASSERT_TRUE(IsOk(service_->getStagedSessionInfo(123, &session))) << GetDebugStr(&installer); EXPECT_THAT(session, SessionInfoEq(expected)); // See if the session is reported with getSessions() as well std::vector sessions; ASSERT_TRUE(IsOk(service_->getSessions(&sessions))) << GetDebugStr(&installer); ASSERT_THAT(sessions, UnorderedElementsAre(SessionInfoEq(expected))); } TEST_F(ApexServiceTest, SubmitSingleStagedSessionDeletesPreviousSessions) { PrepareTestApexForInstall installer(GetTestFile("apex.apexd_test.apex"), "/data/app-staging/session_239", "staging_data_file"); if (!installer.Prepare()) { FAIL() << GetDebugStr(&installer); } // First simulate existence of a bunch of sessions. auto session1 = ApexSession::CreateSession(37); ASSERT_TRUE(IsOk(session1)); auto session2 = ApexSession::CreateSession(57); ASSERT_TRUE(IsOk(session2)); auto session3 = ApexSession::CreateSession(73); ASSERT_TRUE(IsOk(session3)); ASSERT_TRUE(IsOk(session1->UpdateStateAndCommit(SessionState::VERIFIED))); ASSERT_TRUE(IsOk(session2->UpdateStateAndCommit(SessionState::STAGED))); ASSERT_TRUE(IsOk(session3->UpdateStateAndCommit(SessionState::SUCCESS))); std::vector sessions; ASSERT_TRUE(IsOk(service_->getSessions(&sessions))); ApexSessionInfo expected_session1 = CreateSessionInfo(37); expected_session1.isVerified = true; ApexSessionInfo expected_session2 = CreateSessionInfo(57); expected_session2.isStaged = true; ApexSessionInfo expected_session3 = CreateSessionInfo(73); expected_session3.isSuccess = true; ASSERT_THAT(sessions, UnorderedElementsAre(SessionInfoEq(expected_session1), SessionInfoEq(expected_session2), SessionInfoEq(expected_session3))); ApexInfoList list; bool ret_value; std::vector empty_child_session_ids; ASSERT_TRUE(IsOk(service_->submitStagedSession(239, empty_child_session_ids, &list, &ret_value))); EXPECT_TRUE(ret_value); sessions.clear(); ASSERT_TRUE(IsOk(service_->getSessions(&sessions))); ApexSessionInfo new_session = CreateSessionInfo(239); new_session.isVerified = true; ASSERT_THAT(sessions, UnorderedElementsAre(SessionInfoEq(new_session))); } // TODO(jiyong): re-enable this test. This test is disabled because the build // system now always bundles the public key that was used to sign the APEX. // In debuggable build, the bundled public key is used as the last fallback. // As a result, the verification is always successful (and thus test fails). // In order to re-enable this test, we have to manually create an APEX // where public key is not bundled. #if 0 TEST_F(ApexServiceTest, SubmitSingleSessionTestFail) { PrepareTestApexForInstall installer( GetTestFile("apex.apexd_test_no_inst_key.apex"), "/data/app-staging/session_456", "staging_data_file"); if (!installer.Prepare()) { FAIL() << GetDebugStr(&installer); } ApexInfoList list; bool ret_value; std::vector empty_child_session_ids; ASSERT_TRUE(IsOk(service_->submitStagedSession(456, empty_child_session_ids, &list, &ret_value))) << GetDebugStr(&installer); EXPECT_FALSE(ret_value); ApexSessionInfo session; ASSERT_TRUE(IsOk(service_->getStagedSessionInfo(456, &session))) << GetDebugStr(&installer); ApexSessionInfo expected = CreateSessionInfo(-1); expected.isUnknown = true; EXPECT_THAT(session, SessionInfoEq(expected)); } #endif TEST_F(ApexServiceTest, SubmitMultiSessionTestSuccess) { // Parent session id: 10 // Children session ids: 20 30 PrepareTestApexForInstall installer(GetTestFile("apex.apexd_test.apex"), "/data/app-staging/session_20", "staging_data_file"); PrepareTestApexForInstall installer2( GetTestFile("apex.apexd_test_different_app.apex"), "/data/app-staging/session_30", "staging_data_file"); if (!installer.Prepare() || !installer2.Prepare()) { FAIL() << GetDebugStr(&installer) << GetDebugStr(&installer2); } ApexInfoList list; bool ret_value; std::vector child_session_ids = {20, 30}; ASSERT_TRUE(IsOk( service_->submitStagedSession(10, child_session_ids, &list, &ret_value))) << GetDebugStr(&installer); ASSERT_TRUE(ret_value); EXPECT_EQ(2u, list.apexInfos.size()); ApexInfo match; bool package1_found = false; bool package2_found = false; for (const ApexInfo& info : list.apexInfos) { if (info.packageName == installer.package) { ASSERT_EQ(installer.package, info.packageName); ASSERT_EQ(installer.version, static_cast(info.versionCode)); ASSERT_EQ(installer.test_file, info.packagePath); package1_found = true; } else if (info.packageName == installer2.package) { ASSERT_EQ(installer2.package, info.packageName); ASSERT_EQ(installer2.version, static_cast(info.versionCode)); ASSERT_EQ(installer2.test_file, info.packagePath); package2_found = true; } else { FAIL() << "Unexpected package found " << info.packageName << GetDebugStr(&installer) << GetDebugStr(&installer2); } } ASSERT_TRUE(package1_found); ASSERT_TRUE(package2_found); ApexSessionInfo session; ASSERT_TRUE(IsOk(service_->getStagedSessionInfo(10, &session))) << GetDebugStr(&installer); ApexSessionInfo expected = CreateSessionInfo(10); expected.isVerified = true; ASSERT_THAT(session, SessionInfoEq(expected)); ASSERT_TRUE(IsOk(service_->markStagedSessionReady(10, &ret_value))) << GetDebugStr(&installer); ASSERT_TRUE(ret_value); ASSERT_TRUE(IsOk(service_->getStagedSessionInfo(10, &session))) << GetDebugStr(&installer); expected.isVerified = false; expected.isStaged = true; ASSERT_THAT(session, SessionInfoEq(expected)); } // TODO(jiyong): re-enable this test. This test is disabled because the build // system now always bundles the public key that was used to sign the APEX. // In debuggable build, the bundled public key is used as the last fallback. // As a result, the verification is always successful (and thus test fails). // In order to re-enable this test, we have to manually create an APEX // where public key is not bundled. #if 0 TEST_F(ApexServiceTest, SubmitMultiSessionTestFail) { // Parent session id: 11 // Children session ids: 21 31 PrepareTestApexForInstall installer(GetTestFile("apex.apexd_test.apex"), "/data/app-staging/session_21", "staging_data_file"); PrepareTestApexForInstall installer2( GetTestFile("apex.apexd_test_no_inst_key.apex"), "/data/app-staging/session_31", "staging_data_file"); if (!installer.Prepare() || !installer2.Prepare()) { FAIL() << GetDebugStr(&installer) << GetDebugStr(&installer2); } ApexInfoList list; bool ret_value; std::vector child_session_ids = {21, 31}; ASSERT_TRUE(IsOk( service_->submitStagedSession(11, child_session_ids, &list, &ret_value))) << GetDebugStr(&installer); ASSERT_FALSE(ret_value); } #endif TEST_F(ApexServiceTest, MarkStagedSessionReadyFail) { // We should fail if we ask information about a session we don't know. bool ret_value; ASSERT_TRUE(IsOk(service_->markStagedSessionReady(666, &ret_value))); ASSERT_FALSE(ret_value); ApexSessionInfo session; ASSERT_TRUE(IsOk(service_->getStagedSessionInfo(666, &session))); ApexSessionInfo expected = CreateSessionInfo(-1); expected.isUnknown = true; ASSERT_THAT(session, SessionInfoEq(expected)); } TEST_F(ApexServiceTest, MarkStagedSessionSuccessfulFailsNoSession) { ASSERT_FALSE(IsOk(service_->markStagedSessionSuccessful(37))); ApexSessionInfo session_info; ASSERT_TRUE(IsOk(service_->getStagedSessionInfo(37, &session_info))); ApexSessionInfo expected = CreateSessionInfo(-1); expected.isUnknown = true; ASSERT_THAT(session_info, SessionInfoEq(expected)); } TEST_F(ApexServiceTest, MarkStagedSessionSuccessfulFailsSessionInWrongState) { auto session = ApexSession::CreateSession(73); ASSERT_TRUE(IsOk(session)); ASSERT_TRUE( IsOk(session->UpdateStateAndCommit(::apex::proto::SessionState::STAGED))); ASSERT_FALSE(IsOk(service_->markStagedSessionSuccessful(73))); ApexSessionInfo session_info; ASSERT_TRUE(IsOk(service_->getStagedSessionInfo(73, &session_info))); ApexSessionInfo expected = CreateSessionInfo(73); expected.isStaged = true; ASSERT_THAT(session_info, SessionInfoEq(expected)); } TEST_F(ApexServiceTest, MarkStagedSessionSuccessfulActivatedSession) { auto session = ApexSession::CreateSession(239); ASSERT_TRUE(IsOk(session)); ASSERT_TRUE(IsOk( session->UpdateStateAndCommit(::apex::proto::SessionState::ACTIVATED))); ASSERT_TRUE(IsOk(service_->markStagedSessionSuccessful(239))); ApexSessionInfo session_info; ASSERT_TRUE(IsOk(service_->getStagedSessionInfo(239, &session_info))); ApexSessionInfo expected = CreateSessionInfo(239); expected.isSuccess = true; ASSERT_THAT(session_info, SessionInfoEq(expected)); } TEST_F(ApexServiceTest, MarkStagedSessionSuccessfulNoOp) { auto session = ApexSession::CreateSession(1543); ASSERT_TRUE(IsOk(session)); ASSERT_TRUE(IsOk( session->UpdateStateAndCommit(::apex::proto::SessionState::SUCCESS))); ASSERT_TRUE(IsOk(service_->markStagedSessionSuccessful(1543))); ApexSessionInfo session_info; ASSERT_TRUE(IsOk(service_->getStagedSessionInfo(1543, &session_info))); ApexSessionInfo expected = CreateSessionInfo(1543); expected.isSuccess = true; ASSERT_THAT(session_info, SessionInfoEq(expected)); } TEST_F(ApexServiceTest, AbortActiveSessionNoSessions) { // First ensure there are no sessions. std::vector sessions; ASSERT_TRUE(IsOk(service_->getSessions(&sessions))); ASSERT_EQ(0u, sessions.size()); ASSERT_TRUE(IsOk(service_->abortActiveSession())); } TEST_F(ApexServiceTest, AbortActiveSession) { auto session = ApexSession::CreateSession(239); session->UpdateStateAndCommit(SessionState::VERIFIED); std::vector sessions; ASSERT_TRUE(IsOk(service_->getSessions(&sessions))); ASSERT_EQ(1u, sessions.size()); ASSERT_TRUE(IsOk(service_->abortActiveSession())); sessions.clear(); ASSERT_TRUE(IsOk(service_->getSessions(&sessions))); ASSERT_EQ(0u, sessions.size()); } TEST_F(ApexServiceTest, BackupActivePackages) { if (supports_fs_checkpointing_) { GTEST_SKIP() << "Can't run if filesystem checkpointing is enabled"; } PrepareTestApexForInstall installer1(GetTestFile("apex.apexd_test.apex")); PrepareTestApexForInstall installer2( GetTestFile("apex.apexd_test_different_app.apex")); PrepareTestApexForInstall installer3(GetTestFile("apex.apexd_test_v2.apex"), "/data/app-staging/session_23", "staging_data_file"); if (!installer1.Prepare() || !installer2.Prepare() || !installer3.Prepare()) { return; } // Activate some packages, in order to backup them later. bool ret = false; std::vector pkgs = {installer1.test_file, installer2.test_file}; ASSERT_TRUE(IsOk(service_->stagePackages(pkgs, &ret))); ASSERT_TRUE(ret); // Make sure that /data/apex/active has activated packages. auto active_pkgs = ReadDir(kActiveApexPackagesDataDir, [](auto _) { return true; }); ASSERT_TRUE(IsOk(active_pkgs)); ASSERT_THAT(*active_pkgs, UnorderedElementsAre(installer1.test_installed_file, installer2.test_installed_file)); ApexInfoList list; std::vector empty_child_session_ids; ASSERT_TRUE(IsOk( service_->submitStagedSession(23, empty_child_session_ids, &list, &ret))); ASSERT_TRUE(ret); auto backups = ReadDir(kApexBackupDir, [](auto _) { return true; }); ASSERT_TRUE(IsOk(backups)); auto backup1 = StringPrintf("%s/com.android.apex.test_package@1.apex", kApexBackupDir); auto backup2 = StringPrintf("%s/com.android.apex.test_package_2@1.apex", kApexBackupDir); ASSERT_THAT(*backups, UnorderedElementsAre(backup1, backup2)); } TEST_F(ApexServiceTest, BackupActivePackagesClearsPreviousBackup) { if (supports_fs_checkpointing_) { GTEST_SKIP() << "Can't run if filesystem checkpointing is enabled"; } PrepareTestApexForInstall installer1(GetTestFile("apex.apexd_test.apex")); PrepareTestApexForInstall installer2( GetTestFile("apex.apexd_test_different_app.apex")); PrepareTestApexForInstall installer3(GetTestFile("apex.apexd_test_v2.apex"), "/data/app-staging/session_43", "staging_data_file"); if (!installer1.Prepare() || !installer2.Prepare() || !installer3.Prepare()) { return; } // Make sure /data/apex/backups exists. ASSERT_TRUE(IsOk(createDirIfNeeded(std::string(kApexBackupDir), 0700))); // Create some bogus files in /data/apex/backups. std::ofstream old_backup(StringPrintf("%s/file1", kApexBackupDir)); ASSERT_TRUE(old_backup.good()); old_backup.close(); bool ret = false; std::vector pkgs = {installer1.test_file, installer2.test_file}; ASSERT_TRUE(IsOk(service_->stagePackages(pkgs, &ret))); ASSERT_TRUE(ret); // Make sure that /data/apex/active has activated packages. auto active_pkgs = ReadDir(kActiveApexPackagesDataDir, [](auto _) { return true; }); ASSERT_TRUE(IsOk(active_pkgs)); ASSERT_THAT(*active_pkgs, UnorderedElementsAre(installer1.test_installed_file, installer2.test_installed_file)); ApexInfoList list; std::vector empty_child_session_ids; ASSERT_TRUE(IsOk( service_->submitStagedSession(43, empty_child_session_ids, &list, &ret))); ASSERT_TRUE(ret); auto backups = ReadDir(kApexBackupDir, [](auto _) { return true; }); ASSERT_TRUE(IsOk(backups)); auto backup1 = StringPrintf("%s/com.android.apex.test_package@1.apex", kApexBackupDir); auto backup2 = StringPrintf("%s/com.android.apex.test_package_2@1.apex", kApexBackupDir); ASSERT_THAT(*backups, UnorderedElementsAre(backup1, backup2)); } TEST_F(ApexServiceTest, BackupActivePackagesZeroActivePackages) { if (supports_fs_checkpointing_) { GTEST_SKIP() << "Can't run if filesystem checkpointing is enabled"; } PrepareTestApexForInstall installer(GetTestFile("apex.apexd_test_v2.apex"), "/data/app-staging/session_41", "staging_data_file"); if (!installer.Prepare()) { return; } // Make sure that /data/apex/active exists and is empty ASSERT_TRUE( IsOk(createDirIfNeeded(std::string(kActiveApexPackagesDataDir), 0750))); auto active_pkgs = ReadDir(kActiveApexPackagesDataDir, [](auto _) { return true; }); ASSERT_TRUE(IsOk(active_pkgs)); ASSERT_EQ(0u, active_pkgs->size()); ApexInfoList list; std::vector empty_child_session_ids; bool ret = false; ASSERT_TRUE(IsOk( service_->submitStagedSession(41, empty_child_session_ids, &list, &ret))); ASSERT_TRUE(ret); auto backups = ReadDir(kApexBackupDir, [](auto _) { return true; }); ASSERT_TRUE(IsOk(backups)); ASSERT_EQ(0u, backups->size()); } TEST_F(ApexServiceTest, ActivePackagesFolderDoesNotExist) { PrepareTestApexForInstall installer(GetTestFile("apex.apexd_test_v2.apex"), "/data/app-staging/session_41", "staging_data_file"); if (!installer.Prepare()) { return; } // Make sure that /data/apex/active does not exist std::error_code ec; fs::remove_all(fs::path(kActiveApexPackagesDataDir), ec); ASSERT_FALSE(ec) << "Failed to delete " << kActiveApexPackagesDataDir; ApexInfoList list; std::vector empty_child_session_ids; bool ret = false; ASSERT_TRUE(IsOk( service_->submitStagedSession(41, empty_child_session_ids, &list, &ret))); ASSERT_TRUE(ret); if (!supports_fs_checkpointing_) { auto backups = ReadDir(kApexBackupDir, [](auto _) { return true; }); ASSERT_TRUE(IsOk(backups)); ASSERT_EQ(0u, backups->size()); } } TEST_F(ApexServiceTest, UnstagePackagesSuccess) { PrepareTestApexForInstall installer1(GetTestFile("apex.apexd_test.apex")); PrepareTestApexForInstall installer2( GetTestFile("apex.apexd_test_different_app.apex")); if (!installer1.Prepare() || !installer2.Prepare()) { return; } bool ret = false; std::vector pkgs = {installer1.test_file, installer2.test_file}; ASSERT_TRUE(IsOk(service_->stagePackages(pkgs, &ret))); ASSERT_TRUE(ret); pkgs = {installer2.test_installed_file}; ASSERT_TRUE(IsOk(service_->unstagePackages(pkgs))); auto active_packages = ReadDir(kActiveApexPackagesDataDir, [](auto _) { return true; }); ASSERT_TRUE(IsOk(active_packages)); ASSERT_THAT(*active_packages, UnorderedElementsAre(installer1.test_installed_file)); } TEST_F(ApexServiceTest, UnstagePackagesFail) { PrepareTestApexForInstall installer1(GetTestFile("apex.apexd_test.apex")); PrepareTestApexForInstall installer2( GetTestFile("apex.apexd_test_different_app.apex")); if (!installer1.Prepare() || !installer2.Prepare()) { return; } bool ret = false; std::vector pkgs = {installer1.test_file}; ASSERT_TRUE(IsOk(service_->stagePackages(pkgs, &ret))); ASSERT_TRUE(ret); pkgs = {installer1.test_installed_file, installer2.test_installed_file}; ASSERT_FALSE(IsOk(service_->unstagePackages(pkgs))); // Check that first package wasn't unstaged. auto active_packages = ReadDir(kActiveApexPackagesDataDir, [](auto _) { return true; }); ASSERT_TRUE(IsOk(active_packages)); ASSERT_THAT(*active_packages, UnorderedElementsAre(installer1.test_installed_file)); } class ApexServiceRollbackTest : public ApexServiceTest { protected: void SetUp() override { ApexServiceTest::SetUp(); } void PrepareBackup(const std::vector& pkgs) { ASSERT_TRUE(IsOk(createDirIfNeeded(std::string(kApexBackupDir), 0700))); for (const auto& pkg : pkgs) { PrepareTestApexForInstall installer(pkg); ASSERT_TRUE(installer.Prepare()) << " failed to prepare " << pkg; const std::string& from = installer.test_file; std::string to = std::string(kApexBackupDir) + "/" + installer.package + "@" + std::to_string(installer.version) + ".apex"; std::error_code ec; fs::copy(fs::path(from), fs::path(to), fs::copy_options::create_hard_links, ec); ASSERT_FALSE(ec) << "Failed to copy " << from << " to " << to << " : " << ec; } } void CheckRollbackWasPerformed( const std::vector& expected_pkgs) { // First check that /data/apex/active exists and has correct permissions. struct stat sd; ASSERT_EQ(0, stat(kActiveApexPackagesDataDir, &sd)); ASSERT_EQ(0750u, sd.st_mode & ALLPERMS); // Now read content and check it contains expected values. auto active_pkgs = ReadDir(kActiveApexPackagesDataDir, [](auto _) { return true; }); ASSERT_TRUE(IsOk(active_pkgs)); ASSERT_THAT(*active_pkgs, UnorderedElementsAreArray(expected_pkgs)); } }; TEST_F(ApexServiceRollbackTest, AbortActiveSessionSuccessfulRollback) { if (supports_fs_checkpointing_) { GTEST_SKIP() << "Can't run if filesystem checkpointing is enabled"; } PrepareTestApexForInstall installer(GetTestFile("apex.apexd_test_v2.apex")); if (!installer.Prepare()) { return; } auto session = ApexSession::CreateSession(239); ASSERT_TRUE(IsOk(session)); ASSERT_TRUE(IsOk(session->UpdateStateAndCommit(SessionState::ACTIVATED))); // Make sure /data/apex/active is non-empty. bool ret; ASSERT_TRUE(IsOk(service_->stagePackage(installer.test_file, &ret))); ASSERT_TRUE(ret); PrepareBackup({GetTestFile("apex.apexd_test.apex"), GetTestFile("apex.apexd_test_different_app.apex")}); ASSERT_TRUE(IsOk(service_->abortActiveSession())); auto pkg1 = StringPrintf("%s/com.android.apex.test_package@1.apex", kActiveApexPackagesDataDir); auto pkg2 = StringPrintf("%s/com.android.apex.test_package_2@1.apex", kActiveApexPackagesDataDir); SCOPED_TRACE(""); CheckRollbackWasPerformed({pkg1, pkg2}); std::vector sessions; ASSERT_TRUE(IsOk(service_->getSessions(&sessions))); ApexSessionInfo expected = CreateSessionInfo(239); expected.isRolledBack = true; ASSERT_THAT(sessions, UnorderedElementsAre(SessionInfoEq(expected))); } TEST_F(ApexServiceRollbackTest, RollbackLastSessionCalledSuccessfulRollback) { if (supports_fs_checkpointing_) { GTEST_SKIP() << "Can't run if filesystem checkpointing is enabled"; } PrepareTestApexForInstall installer(GetTestFile("apex.apexd_test_v2.apex")); if (!installer.Prepare()) { return; } auto session = ApexSession::CreateSession(1543); ASSERT_TRUE(IsOk(session)); ASSERT_TRUE(IsOk(session->UpdateStateAndCommit(SessionState::ACTIVATED))); // Make sure /data/apex/active is non-empty. bool ret; ASSERT_TRUE(IsOk(service_->stagePackage(installer.test_file, &ret))); ASSERT_TRUE(ret); PrepareBackup({GetTestFile("apex.apexd_test.apex")}); ASSERT_TRUE(IsOk(service_->rollbackActiveSession())); auto pkg = StringPrintf("%s/com.android.apex.test_package@1.apex", kActiveApexPackagesDataDir); SCOPED_TRACE(""); CheckRollbackWasPerformed({pkg}); } TEST_F(ApexServiceRollbackTest, RollbackLastSessionCalledNoActiveSession) { // This test simulates a situation that should never happen on user builds: // abortLastSession was called, but there are no active sessions. PrepareTestApexForInstall installer(GetTestFile("apex.apexd_test_v2.apex")); if (!installer.Prepare()) { return; } // Make sure /data/apex/active is non-empty. bool ret; ASSERT_TRUE(IsOk(service_->stagePackage(installer.test_file, &ret))); ASSERT_TRUE(ret); PrepareBackup({GetTestFile("apex.apexd_test.apex")}); // Even though backup is there, no sessions are active, hence rollback request // should fail. ASSERT_FALSE(IsOk(service_->rollbackActiveSession())); } TEST_F(ApexServiceRollbackTest, RollbackFailsNoBackupFolder) { ASSERT_FALSE(IsOk(service_->rollbackActiveSession())); } TEST_F(ApexServiceRollbackTest, RollbackFailsNoActivePackagesFolder) { PrepareTestApexForInstall installer(GetTestFile("apex.apexd_test.apex")); ASSERT_FALSE(IsOk(service_->rollbackActiveSession())); } TEST_F(ApexServiceRollbackTest, MarkStagedSessionSuccessfulCleanupBackup) { PrepareBackup({GetTestFile("apex.apexd_test.apex"), GetTestFile("apex.apexd_test_different_app.apex")}); auto session = ApexSession::CreateSession(101); ASSERT_TRUE(IsOk(session)); ASSERT_TRUE(IsOk(session->UpdateStateAndCommit(SessionState::ACTIVATED))); ASSERT_TRUE(IsOk(service_->markStagedSessionSuccessful(101))); ASSERT_TRUE(fs::is_empty(fs::path(kApexBackupDir))); } TEST_F(ApexServiceRollbackTest, ResumesRollback) { if (supports_fs_checkpointing_) { GTEST_SKIP() << "Can't run if filesystem checkpointing is enabled"; } PrepareBackup({GetTestFile("apex.apexd_test.apex"), GetTestFile("apex.apexd_test_different_app.apex")}); PrepareTestApexForInstall installer(GetTestFile("apex.apexd_test_v2.apex")); if (!installer.Prepare()) { return; } // Make sure /data/apex/active is non-empty. bool ret; ASSERT_TRUE(IsOk(service_->stagePackage(installer.test_file, &ret))); ASSERT_TRUE(ret); auto session = ApexSession::CreateSession(17239); ASSERT_TRUE(IsOk(session)); ASSERT_TRUE( IsOk(session->UpdateStateAndCommit(SessionState::ROLLBACK_IN_PROGRESS))); ASSERT_TRUE(IsOk(service_->resumeRollbackIfNeeded())); auto pkg1 = StringPrintf("%s/com.android.apex.test_package@1.apex", kActiveApexPackagesDataDir); auto pkg2 = StringPrintf("%s/com.android.apex.test_package_2@1.apex", kActiveApexPackagesDataDir); SCOPED_TRACE(""); CheckRollbackWasPerformed({pkg1, pkg2}); std::vector sessions; ASSERT_TRUE(IsOk(service_->getSessions(&sessions))); ApexSessionInfo expected = CreateSessionInfo(17239); expected.isRolledBack = true; ASSERT_THAT(sessions, UnorderedElementsAre(SessionInfoEq(expected))); } TEST_F(ApexServiceRollbackTest, DoesNotResumeRollback) { if (supports_fs_checkpointing_) { GTEST_SKIP() << "Can't run if filesystem checkpointing is enabled"; } PrepareTestApexForInstall installer(GetTestFile("apex.apexd_test_v2.apex")); if (!installer.Prepare()) { return; } // Make sure /data/apex/active is non-empty. bool ret; ASSERT_TRUE(IsOk(service_->stagePackage(installer.test_file, &ret))); ASSERT_TRUE(ret); auto session = ApexSession::CreateSession(53); ASSERT_TRUE(IsOk(session)); ASSERT_TRUE(IsOk(session->UpdateStateAndCommit(SessionState::SUCCESS))); ASSERT_TRUE(IsOk(service_->resumeRollbackIfNeeded())); // Check that rollback wasn't resumed. auto active_pkgs = ReadDir(kActiveApexPackagesDataDir, [](auto _) { return true; }); ASSERT_TRUE(IsOk(active_pkgs)); ASSERT_THAT(*active_pkgs, UnorderedElementsAre(installer.test_installed_file)); std::vector sessions; ASSERT_TRUE(IsOk(service_->getSessions(&sessions))); ApexSessionInfo expected = CreateSessionInfo(53); expected.isSuccess = true; ASSERT_THAT(sessions, UnorderedElementsAre(SessionInfoEq(expected))); } TEST_F(ApexServiceRollbackTest, FailsRollback) { if (supports_fs_checkpointing_) { GTEST_SKIP() << "Can't run if filesystem checkpointing is enabled"; } auto session = ApexSession::CreateSession(53); ASSERT_TRUE(IsOk(session)); ASSERT_TRUE(IsOk(session->UpdateStateAndCommit(SessionState::ACTIVATED))); ASSERT_FALSE(IsOk(service_->rollbackActiveSession())); ApexSessionInfo session_info; ASSERT_TRUE(IsOk(service_->getStagedSessionInfo(53, &session_info))); ApexSessionInfo expected = CreateSessionInfo(53); expected.isRollbackFailed = true; ASSERT_THAT(session_info, SessionInfoEq(expected)); } TEST_F(ApexServiceRollbackTest, RollbackFailedStateRollbackAttemptFails) { if (supports_fs_checkpointing_) { GTEST_SKIP() << "Can't run if filesystem checkpointing is enabled"; } auto session = ApexSession::CreateSession(17239); ASSERT_TRUE(IsOk(session)); ASSERT_TRUE( IsOk(session->UpdateStateAndCommit(SessionState::ROLLBACK_FAILED))); ASSERT_FALSE(IsOk(service_->rollbackActiveSession())); ApexSessionInfo session_info; ASSERT_TRUE(IsOk(service_->getStagedSessionInfo(17239, &session_info))); ApexSessionInfo expected = CreateSessionInfo(17239); expected.isRollbackFailed = true; ASSERT_THAT(session_info, SessionInfoEq(expected)); } static pid_t GetPidOf(const std::string& name) { char buf[1024]; const std::string cmd = std::string("pidof -s ") + name; FILE* cmd_pipe = popen(cmd.c_str(), "r"); if (cmd_pipe == nullptr) { PLOG(ERROR) << "Cannot open pipe for " << cmd; return 0; } if (fgets(buf, 1024, cmd_pipe) == nullptr) { PLOG(ERROR) << "Cannot read pipe for " << cmd; pclose(cmd_pipe); return 0; } pclose(cmd_pipe); return strtoul(buf, nullptr, 10); } static void ExecInMountNamespaceOf(pid_t pid, const std::function& func) { const std::string my_path = "/proc/self/ns/mnt"; android::base::unique_fd my_fd(open(my_path.c_str(), O_RDONLY | O_CLOEXEC)); ASSERT_TRUE(my_fd.get() >= 0); const std::string target_path = std::string("/proc/") + std::to_string(pid) + "/ns/mnt"; android::base::unique_fd target_fd( open(target_path.c_str(), O_RDONLY | O_CLOEXEC)); ASSERT_TRUE(target_fd.get() >= 0); int res = setns(target_fd.get(), CLONE_NEWNS); ASSERT_NE(-1, res); func(pid); res = setns(my_fd.get(), CLONE_NEWNS); ASSERT_NE(-1, res); } TEST(ApexdTest, ApexdIsInSameMountNamespaceAsInit) { std::string ns_apexd; std::string ns_init; ExecInMountNamespaceOf(GetPidOf("apexd"), [&](pid_t pid) { bool res = android::base::Readlink("/proc/self/ns/mnt", &ns_apexd); ASSERT_TRUE(res); }); ExecInMountNamespaceOf(1, [&](pid_t pid) { bool res = android::base::Readlink("/proc/self/ns/mnt", &ns_init); ASSERT_TRUE(res); }); ASSERT_EQ(ns_apexd, ns_init); } // These are NOT exhaustive list of early processes be should be enough static const std::vector kEarlyProcesses = { "servicemanager", "hwservicemanager", "vold", "logd", }; TEST(ApexdTest, EarlyProcessesAreInDifferentMountNamespace) { if (!android::base::GetBoolProperty("ro.apex.updatable", false)) { return; } std::string ns_apexd; ExecInMountNamespaceOf(GetPidOf("apexd"), [&](pid_t _) { bool res = android::base::Readlink("/proc/self/ns/mnt", &ns_apexd); ASSERT_TRUE(res); }); for (const auto& name : kEarlyProcesses) { std::string ns_early_process; ExecInMountNamespaceOf(GetPidOf(name), [&](pid_t _) { bool res = android::base::Readlink("/proc/self/ns/mnt", &ns_early_process); ASSERT_TRUE(res); }); ASSERT_NE(ns_apexd, ns_early_process); } } TEST(ApexdTest, ApexIsAPrivateMountPoint) { std::string mountinfo; ASSERT_TRUE( android::base::ReadFileToString("/proc/self/mountinfo", &mountinfo)); bool found_apex_mountpoint = false; for (const auto& line : android::base::Split(mountinfo, "\n")) { std::vector tokens = android::base::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") { found_apex_mountpoint = true; // Make sure that propagation type is set to - which means private ASSERT_EQ("-", tokens[6]); } } ASSERT_TRUE(found_apex_mountpoint); } static const std::vector kEarlyApexes = { "/apex/com.android.runtime", "/apex/com.android.tzdata", }; TEST(ApexdTest, ApexesAreActivatedForEarlyProcesses) { for (const auto& name : kEarlyProcesses) { pid_t pid = GetPidOf(name); const std::string path = std::string("/proc/") + std::to_string(pid) + "/mountinfo"; std::string mountinfo; ASSERT_TRUE(android::base::ReadFileToString(path.c_str(), &mountinfo)); std::unordered_set mountpoints; for (const auto& line : android::base::Split(mountinfo, "\n")) { std::vector tokens = android::base::Split(line, " "); // line format: // mnt_id parent_mnt_id major:minor source target option propagation_type // ex) 69 33 7:40 / /apex/com.android.conscrypt ro,nodev,noatime - if (tokens.size() >= 5) { // token[4] is the target mount point mountpoints.emplace(tokens[4]); } } for (const auto& apex_name : kEarlyApexes) { ASSERT_NE(mountpoints.end(), mountpoints.find(apex_name)); } } } class ApexShimUpdateTest : public ApexServiceTest { protected: void SetUp() override { ApexServiceTest::SetUp(); // Assert that shim apex is pre-installed. std::vector list; ASSERT_TRUE(IsOk(service_->getAllPackages(&list))); ApexInfo expected; expected.packageName = "com.android.apex.cts.shim"; expected.packagePath = "/system/apex/com.android.apex.cts.shim.apex"; expected.versionCode = 1; expected.isFactory = true; expected.isActive = true; ASSERT_THAT(list, Contains(ApexInfoEq(expected))); } }; TEST_F(ApexShimUpdateTest, UpdateToV2Success) { PrepareTestApexForInstall installer( GetTestFile("com.android.apex.cts.shim.v2.apex")); if (!installer.Prepare()) { FAIL() << GetDebugStr(&installer); } bool success; ASSERT_TRUE(IsOk(service_->stagePackage(installer.test_file, &success))); ASSERT_TRUE(success); } TEST_F(ApexShimUpdateTest, UpdateToV2FailureWrongSHA512) { PrepareTestApexForInstall installer( GetTestFile("com.android.apex.cts.shim.v2_wrong_sha.apex")); if (!installer.Prepare()) { FAIL() << GetDebugStr(&installer); } bool success; const auto& status = service_->stagePackage(installer.test_file, &success); ASSERT_FALSE(IsOk(status)); const std::string& error_message = std::string(status.exceptionMessage().c_str()); ASSERT_THAT(error_message, HasSubstr("has unexpected SHA512 hash")); } TEST_F(ApexShimUpdateTest, SubmitStagedSesssionFailureHasPreInstallHook) { PrepareTestApexForInstall installer( GetTestFile("com.android.apex.cts.shim.v2_with_pre_install_hook.apex"), "/data/app-staging/session_23", "staging_data_file"); if (!installer.Prepare()) { FAIL() << GetDebugStr(&installer); } ApexInfoList list; bool success; ASSERT_TRUE(IsOk(service_->submitStagedSession(23, {}, &list, &success))); ASSERT_FALSE(success); } TEST_F(ApexShimUpdateTest, SubmitStagedSessionFailureHasPostInstallHook) { PrepareTestApexForInstall installer( GetTestFile("com.android.apex.cts.shim.v2_with_post_install_hook.apex"), "/data/app-staging/session_43", "staging_data_file"); if (!installer.Prepare()) { FAIL() << GetDebugStr(&installer); } ApexInfoList list; bool success; ASSERT_TRUE(IsOk(service_->submitStagedSession(43, {}, &list, &success))); ASSERT_FALSE(success); } TEST_F(ApexShimUpdateTest, SubmitStagedSessionFailureAdditionalFile) { PrepareTestApexForInstall installer( GetTestFile("com.android.apex.cts.shim.v2_additional_file.apex"), "/data/app-staging/session_41", "staging_data_file"); if (!installer.Prepare()) { FAIL() << GetDebugStr(&installer); } ApexInfoList list; bool success; ASSERT_TRUE(IsOk(service_->submitStagedSession(41, {}, &list, &success))); ASSERT_FALSE(success); } TEST_F(ApexShimUpdateTest, SubmitStagedSessionFailureAdditionalFolder) { PrepareTestApexForInstall installer( GetTestFile("com.android.apex.cts.shim.v2_additional_folder.apex"), "/data/app-staging/session_42", "staging_data_file"); if (!installer.Prepare()) { FAIL() << GetDebugStr(&installer); } ApexInfoList list; bool success; ASSERT_TRUE(IsOk(service_->submitStagedSession(42, {}, &list, &success))); ASSERT_FALSE(success); } TEST_F(ApexServiceTest, SubmitStagedSessionCorruptApexFails) { PrepareTestApexForInstall installer( GetTestFile("apex.apexd_test_corrupt_apex.apex"), "/data/app-staging/session_57", "staging_data_file"); if (!installer.Prepare()) { FAIL() << GetDebugStr(&installer); } ApexInfoList list; bool success; ASSERT_TRUE(IsOk(service_->submitStagedSession(57, {}, &list, &success))); ASSERT_FALSE(success); } // Following test case piggybacks on logic in ApexServiceActivationSuccessTest // in order to use mounted apex as flattened one. TEST_F(ApexServiceActivationSuccessTest, StageFailsFlattenedApex) { ASSERT_TRUE(IsOk(service_->activatePackage(installer_->test_installed_file))) << GetDebugStr(installer_.get()); StatusOr flattened_apex = ApexFile::Open(StringPrintf("/apex/%s", installer_->package.c_str())); ASSERT_TRUE(IsOk(flattened_apex)); ASSERT_TRUE(flattened_apex->IsFlattened()); bool success; const auto& status = service_->stagePackage(flattened_apex->GetPath(), &success); ASSERT_FALSE(IsOk(status)); const std::string& error_message = std::string(status.exceptionMessage().c_str()); ASSERT_THAT(error_message, HasSubstr("Can't upgrade flattened apex")); } 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_case_name(), test_info.name(), test_info.file(), test_info.line()); l(LogId::MAIN, LogSeverity::INFO, "apexservice_test", __FILE__, __LINE__, msg.c_str()); #else UNUSED(test_info); #endif } }; } // namespace apex } // namespace android int main(int argc, char** argv) { android::base::InitLogging(argv, &android::base::StderrLogger); ::testing::InitGoogleTest(&argc, argv); ::testing::UnitTest::GetInstance()->listeners().Append( new android::apex::LogTestToLogcat()); return RUN_ALL_TESTS(); }