/* * 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 #include #include #include #include #include "apex_constants.h" #include "apex_database.h" #include "apex_file.h" #include "apex_manifest.h" #include "apexd.h" #include "apexd_private.h" #include "apexd_session.h" #include "apexd_test_utils.h" #include "apexd_utils.h" #include "session_state.pb.h" #include "string_log.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::EndsWith; using android::base::ErrnoError; using android::base::Join; using android::base::ReadFully; using android::base::StartsWith; using android::base::StringPrintf; using android::base::unique_fd; using android::dm::DeviceMapper; using android::fs_mgr::Fstab; using android::fs_mgr::GetEntryForMountPoint; using android::fs_mgr::ReadFstabFromFile; using ::apex::proto::ApexManifest; using ::testing::Contains; using ::testing::EndsWith; using ::testing::HasSubstr; using ::testing::Not; using ::testing::SizeIs; using ::testing::UnorderedElementsAre; using ::testing::UnorderedElementsAreArray; using MountedApexData = MountedApexDatabase::MountedApexData; namespace fs = std::filesystem; class ApexServiceTest : public ::testing::Test { public: ApexServiceTest() { using android::IBinder; using android::IServiceManager; sp sm = android::defaultServiceManager(); sp binder = sm->waitForService(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 { // TODO(b/136647373): Move this check to environment setup if (!android::base::GetBoolProperty("ro.apex.updatable", false)) { GTEST_SKIP() << "Skipping test because device doesn't support APEX"; } 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(); service_->recollectPreinstalledData(kApexPackageBuiltinDirs); } 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(); } Result IsActive(const std::string& name) { std::vector list; android::binder::Status status = service_->getActivePackages(&list); if (!status.isOk()) { return Error() << "Failed to check if " << name << " is active : " << status.exceptionMessage().c_str(); } for (const ApexInfo& apex : list) { if (apex.moduleName == name) { return true; } } return false; } Result IsActive(const std::string& name, int64_t version, const std::string& path) { std::vector list; android::binder::Status status = service_->getActivePackages(&list); if (status.isOk()) { for (const ApexInfo& p : list) { if (p.moduleName == name && p.versionCode == version && p.modulePath == path) { return true; } } return false; } return Error() << status.exceptionMessage().c_str(); } Result> GetAllPackages() { std::vector list; android::binder::Status status = service_->getAllPackages(&list); if (status.isOk()) { return list; } return Error() << status.toString8().c_str(); } Result> GetActivePackages() { std::vector list; android::binder::Status status = service_->getActivePackages(&list); if (status.isOk()) { return list; } return Error() << status.exceptionMessage().c_str(); } Result> 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 list; } return Error() << status.toString8().c_str(); } Result GetActivePackage(const std::string& name) { ApexInfo package; android::binder::Status status = service_->getActivePackage(name, &package); if (status.isOk()) { return package; } return Error() << status.exceptionMessage().c_str(); } std::string GetPackageString(const ApexInfo& p) { return p.moduleName + "@" + std::to_string(p.versionCode) + " [path=" + p.moduleName + "]"; } 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; } Result> 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 list; } return Error() << 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; } auto status = 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())); }); CHECK(status.has_value()) << "Failed to list " << path << " : " << status.error(); 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, }; auto res = ForkAndRun(args); CHECK(res.ok()) << res.error(); std::string data; CHECK(android::base::ReadFileToString(file, &data)); unlink(file.c_str()); return data; } static void DeleteIfExists(const std::string& path) { if (fs::exists(path)) { std::error_code ec; fs::remove_all(path, ec); ASSERT_FALSE(ec) << "Failed to delete dir " << path << " : " << ec.message(); } } 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. Result 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 = [&]() { Result apex_file = ApexFile::Open(test_input); ASSERT_FALSE(IsOk(apex_file)); ASSERT_TRUE(apex_file.ok()) << test_input << " failed to load: " << apex_file.error(); }; 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() { LOG(INFO) << "Deleting file " << test_file; if (unlink(test_file.c_str()) != 0) { PLOG(ERROR) << "Unable to unlink " << test_file; } LOG(INFO) << "Deleting directory " << test_dir_input; if (rmdir(test_dir_input.c_str()) != 0) { PLOG(ERROR) << "Unable to rmdir " << test_dir_input; } } }; 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() { DeleteDirContent(kActiveApexPackagesDataDir); DeleteDirContent(kApexBackupDir); DeleteDirContent(kApexHashTreeDir); DeleteDirContent(ApexSession::GetSessionsDir()); DeleteIfExists("/data/misc_ce/0/apexdata/apex.apexd_test"); DeleteIfExists("/data/misc_ce/0/apexrollback/123456"); DeleteIfExists("/data/misc_ce/0/apexrollback/77777"); DeleteIfExists("/data/misc_ce/0/apexrollback/98765"); DeleteIfExists("/data/misc_de/0/apexrollback/123456"); DeleteIfExists("/data/misc/apexrollback/123456"); } }; 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); } bool DirExists(const std::string& path) { struct stat buf; if (0 != stat(path.c_str(), &buf)) { return false; } return S_ISDIR(buf.st_mode); } void CreateDir(const std::string& path) { std::error_code ec; fs::create_directory(path, ec); ASSERT_FALSE(ec) << "Failed to create rollback dir " << " : " << ec.message(); } void CreateFile(const std::string& path) { std::ofstream ofs(path); ASSERT_TRUE(ofs.good()); ofs.close(); } Result> ReadEntireDir(const std::string& path) { static const auto kAcceptAll = [](auto /*entry*/) { return true; }; return ReadDir(path, kAcceptAll); } Result GetBlockDeviceForApex(const std::string& package_id) { std::string mount_point = std::string(kApexRoot) + "/" + package_id; Fstab fstab; if (!ReadFstabFromFile("/proc/mounts", &fstab)) { return Error() << "Failed to read /proc/mounts"; } auto entry = GetEntryForMountPoint(&fstab, mount_point); if (entry == nullptr) { return Error() << "Can't find " << mount_point << " in /proc/mounts"; } return entry->blk_device; } Result ReadDevice(const std::string& block_device) { static constexpr int kBlockSize = 4096; static constexpr size_t kBufSize = 1024 * kBlockSize; std::vector buffer(kBufSize); unique_fd fd( TEMP_FAILURE_RETRY(open(block_device.c_str(), O_RDONLY | O_CLOEXEC))); if (fd.get() == -1) { return ErrnoError() << "Can't open " << block_device; } while (true) { int n = read(fd.get(), buffer.data(), kBufSize); if (n < 0) { return ErrnoError() << "Failed to read " << block_device; } if (n == 0) { break; } } return {}; } std::vector ListSlavesOfDmDevice(const std::string& name) { DeviceMapper& dm = DeviceMapper::Instance(); std::string dm_path; EXPECT_TRUE(dm.GetDmDevicePathByName(name, &dm_path)) << "Failed to get path of dm device " << name; // It's a little bit sad we can't use ConsumePrefix here :( constexpr std::string_view kDevPrefix = "/dev/"; EXPECT_TRUE(StartsWith(dm_path, kDevPrefix)) << "Illegal path " << dm_path; dm_path = dm_path.substr(kDevPrefix.length()); std::vector slaves; { std::string slaves_dir = "/sys/" + dm_path + "/slaves"; auto st = WalkDir(slaves_dir, [&](const auto& entry) { std::error_code ec; if (entry.is_symlink(ec)) { slaves.push_back("/dev/block/" + entry.path().filename().string()); } if (ec) { ADD_FAILURE() << "Failed to scan " << slaves_dir << " : " << ec; } }); EXPECT_TRUE(IsOk(st)); } return slaves; } Result CopyFile(const std::string& from, const std::string& to, const fs::copy_options& options) { std::error_code ec; if (!fs::copy_file(from, to, options)) { return Error() << "Failed to copy file " << from << " to " << to << " : " << ec.message(); } return {}; } } // 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(std::string t) : to_delete(std::move(t)) {} ~Deleter() { if (unlink(to_delete.c_str()) != 0) { PLOG(ERROR) << "Could not unlink " << to_delete; } } }; Deleter del(test_file); android::binder::Status st = service_->stagePackages({test_file}); 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; } 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); android::binder::Status st = service_->stagePackages({installer.test_file}); ASSERT_FALSE(IsOk(st)); // May contain one of two errors. std::string error = st.exceptionMessage().c_str(); ASSERT_THAT(error, HasSubstr("No preinstalled apex found for package " "com.android.apex.test_package.no_inst_key")); } 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); ASSERT_TRUE(IsOk(service_->stagePackages({installer.test_file}))); EXPECT_TRUE(RegularFileExists(installer.test_installed_file)); } TEST_F(ApexServiceTest, SubmitStagegSessionSuccessDoesNotLeakTempVerityDevices) { PrepareTestApexForInstall installer(GetTestFile("apex.apexd_test.apex"), "/data/app-staging/session_1543", "staging_data_file"); if (!installer.Prepare()) { return; } ApexInfoList list; ApexSessionParams params; params.sessionId = 1543; ASSERT_TRUE(IsOk(service_->submitStagedSession(params, &list))); 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, SubmitStagedSessionStoresBuildFingerprint) { PrepareTestApexForInstall installer(GetTestFile("apex.apexd_test.apex"), "/data/app-staging/session_1547", "staging_data_file"); if (!installer.Prepare()) { return; } ApexInfoList list; ApexSessionParams params; params.sessionId = 1547; ASSERT_TRUE(IsOk(service_->submitStagedSession(params, &list))); auto session = ApexSession::GetSession(1547); ASSERT_FALSE(session->GetBuildFingerprint().empty()); } TEST_F(ApexServiceTest, SubmitStagedSessionFailDoesNotLeakTempVerityDevices) { PrepareTestApexForInstall installer( GetTestFile("apex.apexd_test_manifest_mismatch.apex"), "/data/app-staging/session_239", "staging_data_file"); if (!installer.Prepare()) { return; } ApexInfoList list; ApexSessionParams params; params.sessionId = 239; ASSERT_FALSE(IsOk(service_->submitStagedSession(params, &list))); 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, StageSuccessClearsPreviouslyActivePackage) { 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; } ASSERT_TRUE(IsOk(service_->stagePackages({installer.test_file}))); EXPECT_TRUE(RegularFileExists(installer.test_installed_file)); }; install_fn(installer1); install_fn(installer2); // Simulating a revert. 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); ASSERT_TRUE(IsOk(service_->stagePackages({installer.test_file}))); ASSERT_TRUE(RegularFileExists(installer.test_installed_file)); ASSERT_TRUE(IsOk(service_->stagePackages({installer.test_file}))); ASSERT_TRUE(RegularFileExists(installer.test_installed_file)); } TEST_F(ApexServiceTest, StageAlreadyStagedPackageSuccessNewWins) { PrepareTestApexForInstall installer(GetTestFile("apex.apexd_test.apex")); PrepareTestApexForInstall installer2( GetTestFile("apex.apexd_test_nocode.apex")); if (!installer.Prepare() || !installer2.Prepare()) { return; } ASSERT_EQ(std::string("com.android.apex.test_package"), installer.package); ASSERT_EQ(installer.test_installed_file, installer2.test_installed_file); ASSERT_TRUE(IsOk(service_->stagePackages({installer.test_file}))); const auto& apex = ApexFile::Open(installer.test_installed_file); ASSERT_TRUE(IsOk(apex)); ASSERT_FALSE(apex->GetManifest().nocode()); ASSERT_TRUE(IsOk(service_->stagePackages({installer2.test_file}))); const auto& new_apex = ApexFile::Open(installer.test_installed_file); ASSERT_TRUE(IsOk(new_apex)); ASSERT_TRUE(new_apex->GetManifest().nocode()); } 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); 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); ASSERT_TRUE(IsOk(service_->stagePackages(packages))); EXPECT_TRUE(RegularFileExists(installer.test_installed_file)); EXPECT_TRUE(RegularFileExists(installer2.test_installed_file)); } TEST_F(ApexServiceTest, CannotBeRollbackAndHaveRollbackEnabled) { PrepareTestApexForInstall installer(GetTestFile("apex.apexd_test.apex"), "/data/app-staging/session_1543", "staging_data_file"); if (!installer.Prepare()) { return; } ApexInfoList list; ApexSessionParams params; params.sessionId = 1543; params.isRollback = true; params.hasRollbackEnabled = true; ASSERT_FALSE(IsOk(service_->submitStagedSession(params, &list))); } TEST_F(ApexServiceTest, SessionParamDefaults) { PrepareTestApexForInstall installer(GetTestFile("apex.apexd_test.apex"), "/data/app-staging/session_1547", "staging_data_file"); if (!installer.Prepare()) { return; } ApexInfoList list; ApexSessionParams params; params.sessionId = 1547; ASSERT_TRUE(IsOk(service_->submitStagedSession(params, &list))); auto session = ApexSession::GetSession(1547); ASSERT_TRUE(session->GetChildSessionIds().empty()); ASSERT_FALSE(session->IsRollback()); ASSERT_FALSE(session->HasRollbackEnabled()); ASSERT_EQ(0, session->GetRollbackId()); } TEST_F(ApexServiceTest, SnapshotCeData) { CreateDir("/data/misc_ce/0/apexdata/apex.apexd_test"); CreateFile("/data/misc_ce/0/apexdata/apex.apexd_test/hello.txt"); ASSERT_TRUE( RegularFileExists("/data/misc_ce/0/apexdata/apex.apexd_test/hello.txt")); service_->snapshotCeData(0, 123456, "apex.apexd_test"); ASSERT_TRUE(RegularFileExists( "/data/misc_ce/0/apexrollback/123456/apex.apexd_test/hello.txt")); } TEST_F(ApexServiceTest, RestoreCeData) { CreateDir("/data/misc_ce/0/apexdata/apex.apexd_test"); CreateDir("/data/misc_ce/0/apexrollback/123456"); CreateDir("/data/misc_ce/0/apexrollback/123456/apex.apexd_test"); CreateFile("/data/misc_ce/0/apexdata/apex.apexd_test/newfile.txt"); CreateFile("/data/misc_ce/0/apexrollback/123456/apex.apexd_test/oldfile.txt"); ASSERT_TRUE(RegularFileExists( "/data/misc_ce/0/apexdata/apex.apexd_test/newfile.txt")); ASSERT_TRUE(RegularFileExists( "/data/misc_ce/0/apexrollback/123456/apex.apexd_test/oldfile.txt")); service_->restoreCeData(0, 123456, "apex.apexd_test"); ASSERT_TRUE(RegularFileExists( "/data/misc_ce/0/apexdata/apex.apexd_test/oldfile.txt")); ASSERT_FALSE(RegularFileExists( "/data/misc_ce/0/apexdata/apex.apexd_test/newfile.txt")); // The snapshot should be deleted after restoration. ASSERT_FALSE( DirExists("/data/misc_ce/0/apexrollback/123456/apex.apexd_test")); } TEST_F(ApexServiceTest, DestroyDeSnapshotsDeSys) { CreateDir("/data/misc/apexrollback/123456"); CreateDir("/data/misc/apexrollback/123456/my.apex"); CreateFile("/data/misc/apexrollback/123456/my.apex/hello.txt"); ASSERT_TRUE( RegularFileExists("/data/misc/apexrollback/123456/my.apex/hello.txt")); service_->destroyDeSnapshots(8975); ASSERT_TRUE( RegularFileExists("/data/misc/apexrollback/123456/my.apex/hello.txt")); service_->destroyDeSnapshots(123456); ASSERT_FALSE( RegularFileExists("/data/misc/apexrollback/123456/my.apex/hello.txt")); ASSERT_FALSE(DirExists("/data/misc/apexrollback/123456")); } TEST_F(ApexServiceTest, DestroyDeSnapshotsDeUser) { CreateDir("/data/misc_de/0/apexrollback/123456"); CreateDir("/data/misc_de/0/apexrollback/123456/my.apex"); CreateFile("/data/misc_de/0/apexrollback/123456/my.apex/hello.txt"); ASSERT_TRUE(RegularFileExists( "/data/misc_de/0/apexrollback/123456/my.apex/hello.txt")); service_->destroyDeSnapshots(8975); ASSERT_TRUE(RegularFileExists( "/data/misc_de/0/apexrollback/123456/my.apex/hello.txt")); service_->destroyDeSnapshots(123456); ASSERT_FALSE(RegularFileExists( "/data/misc_de/0/apexrollback/123456/my.apex/hello.txt")); ASSERT_FALSE(DirExists("/data/misc_de/0/apexrollback/123456")); } TEST_F(ApexServiceTest, DestroyCeSnapshots) { CreateDir("/data/misc_ce/0/apexrollback/123456"); CreateDir("/data/misc_ce/0/apexrollback/123456/apex.apexd_test"); CreateFile("/data/misc_ce/0/apexrollback/123456/apex.apexd_test/file.txt"); CreateDir("/data/misc_ce/0/apexrollback/77777"); CreateDir("/data/misc_ce/0/apexrollback/77777/apex.apexd_test"); CreateFile("/data/misc_ce/0/apexrollback/77777/apex.apexd_test/thing.txt"); ASSERT_TRUE(RegularFileExists( "/data/misc_ce/0/apexrollback/123456/apex.apexd_test/file.txt")); ASSERT_TRUE(RegularFileExists( "/data/misc_ce/0/apexrollback/77777/apex.apexd_test/thing.txt")); android::binder::Status st = service_->destroyCeSnapshots(0, 123456); ASSERT_TRUE(IsOk(st)); // Should be OK if the directory doesn't exist. st = service_->destroyCeSnapshots(1, 123456); ASSERT_TRUE(IsOk(st)); ASSERT_TRUE(RegularFileExists( "/data/misc_ce/0/apexrollback/77777/apex.apexd_test/thing.txt")); ASSERT_FALSE(DirExists("/data/misc_ce/0/apexrollback/123456")); } TEST_F(ApexServiceTest, DestroyCeSnapshotsNotSpecified) { CreateDir("/data/misc_ce/0/apexrollback/123456"); CreateDir("/data/misc_ce/0/apexrollback/123456/apex.apexd_test"); CreateFile("/data/misc_ce/0/apexrollback/123456/apex.apexd_test/file.txt"); CreateDir("/data/misc_ce/0/apexrollback/77777"); CreateDir("/data/misc_ce/0/apexrollback/77777/apex.apexd_test"); CreateFile("/data/misc_ce/0/apexrollback/77777/apex.apexd_test/thing.txt"); CreateDir("/data/misc_ce/0/apexrollback/98765"); CreateDir("/data/misc_ce/0/apexrollback/98765/apex.apexd_test"); CreateFile("/data/misc_ce/0/apexrollback/98765/apex.apexd_test/test.txt"); ASSERT_TRUE(RegularFileExists( "/data/misc_ce/0/apexrollback/123456/apex.apexd_test/file.txt")); ASSERT_TRUE(RegularFileExists( "/data/misc_ce/0/apexrollback/77777/apex.apexd_test/thing.txt")); ASSERT_TRUE(RegularFileExists( "/data/misc_ce/0/apexrollback/98765/apex.apexd_test/test.txt")); std::vector retain{123, 77777, 987654}; android::binder::Status st = service_->destroyCeSnapshotsNotSpecified(0, retain); ASSERT_TRUE(IsOk(st)); ASSERT_TRUE(RegularFileExists( "/data/misc_ce/0/apexrollback/77777/apex.apexd_test/thing.txt")); ASSERT_FALSE(DirExists("/data/misc_ce/0/apexrollback/123456")); ASSERT_FALSE(DirExists("/data/misc_ce/0/apexrollback/98765")); } TEST_F(ApexServiceTest, SubmitStagedSessionCleanupsTempMountOnFailure) { // Parent session id: 23 // Children session ids: 37 73 PrepareTestApexForInstall installer( GetTestFile("apex.apexd_test_different_app.apex"), "/data/app-staging/session_37", "staging_data_file"); PrepareTestApexForInstall installer2( GetTestFile("apex.apexd_test_manifest_mismatch.apex"), "/data/app-staging/session_73", "staging_data_file"); if (!installer.Prepare() || !installer2.Prepare()) { FAIL() << GetDebugStr(&installer) << GetDebugStr(&installer2); } ApexInfoList list; ApexSessionParams params; params.sessionId = 23; params.childSessionIds = {37, 73}; ASSERT_FALSE(IsOk(service_->submitStagedSession(params, &list))) << GetDebugStr(&installer); // Check that temp mounts were cleanded up. for (const auto& mount : GetApexMounts()) { EXPECT_FALSE(EndsWith(mount, ".tmp")) << "Found temp mount " << mount; } } template class ApexServiceActivationTest : public ApexServiceTest { public: ApexServiceActivationTest() : stage_package(true) {} explicit ApexServiceActivationTest(bool stage_package) : stage_package(stage_package) {} void SetUp() override { // TODO(b/136647373): Move this check to environment setup if (!android::base::GetBoolProperty("ro.apex.updatable", false)) { GTEST_SKIP() << "Skipping test because device doesn't support APEX"; } 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. std::string path = stage_package ? installer_->test_installed_file : installer_->test_file; Result active = IsActive(installer_->package, installer_->version, path); ASSERT_TRUE(IsOk(active)); ASSERT_FALSE(*active); } if (stage_package) { ASSERT_TRUE(IsOk(service_->stagePackages({installer_->test_file}))); } } 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 deactivatePackage 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. Result active = IsActive(installer_->package, installer_->version, installer_->test_installed_file); 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; auto status = WalkDir(path, [&](const fs::directory_entry& entry) { if (!entry.is_directory()) { return; } ret.emplace_back(entry.path().filename()); }); CHECK(status.has_value()) << "Failed to list " << path << " : " << status.error(); 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()); Result> active = GetActivePackages(); ASSERT_TRUE(IsOk(active)); ApexInfo match; for (const ApexInfo& info : *active) { if (info.moduleName == installer_->package) { match = info; break; } } ASSERT_EQ(installer_->package, match.moduleName); ASSERT_EQ(installer_->version, static_cast(match.versionCode)); ASSERT_EQ(installer_->test_installed_file, match.modulePath); } TEST_F(ApexServiceActivationSuccessTest, GetActivePackage) { ASSERT_TRUE(IsOk(service_->activatePackage(installer_->test_installed_file))) << GetDebugStr(installer_.get()); Result active = GetActivePackage(installer_->package); ASSERT_TRUE(IsOk(active)); ASSERT_EQ(installer_->package, active->moduleName); ASSERT_EQ(installer_->version, static_cast(active->versionCode)); ASSERT_EQ(installer_->test_installed_file, active->modulePath); } TEST_F(ApexServiceActivationSuccessTest, ShowsUpInMountedApexDatabase) { ASSERT_TRUE(IsOk(service_->activatePackage(installer_->test_installed_file))) << GetDebugStr(installer_.get()); MountedApexDatabase db; db.PopulateFromMounts(kActiveApexPackagesDataDir, kApexDecompressedDir, kApexHashTreeDir); std::optional mounted_apex; db.ForallMountedApexes(installer_->package, [&](const MountedApexData& d, bool active) { if (active) { mounted_apex.emplace(d); } }); ASSERT_TRUE(mounted_apex) << "Haven't found " << installer_->test_installed_file << " in the database of mounted apexes"; // Get all necessary data for assertions on mounted_apex. std::string package_id = installer_->package + "@" + std::to_string(installer_->version); DeviceMapper& dm = DeviceMapper::Instance(); std::string dm_path; ASSERT_TRUE(dm.GetDmDevicePathByName(package_id, &dm_path)) << "Failed to get path of dm device " << package_id; auto loop_device = dm.GetParentBlockDeviceByPath(dm_path); ASSERT_TRUE(loop_device) << "Failed to find parent block device of " << dm_path; // Now we are ready to assert on mounted_apex. ASSERT_EQ(*loop_device, mounted_apex->loop_name); ASSERT_EQ(installer_->test_installed_file, mounted_apex->full_path); std::string expected_mount = std::string(kApexRoot) + "/" + package_id; ASSERT_EQ(expected_mount, mounted_apex->mount_point); ASSERT_EQ(package_id, mounted_apex->device_name); ASSERT_EQ("", mounted_apex->hashtree_loop_name); } struct NoHashtreeApexNameProvider { static std::string GetTestName() { return "apex.apexd_test_no_hashtree.apex"; } static std::string GetPackageName() { return "com.android.apex.test_package"; } }; class ApexServiceNoHashtreeApexActivationTest : public ApexServiceActivationTest {}; TEST_F(ApexServiceNoHashtreeApexActivationTest, Activate) { ASSERT_TRUE(IsOk(service_->activatePackage(installer_->test_installed_file))) << GetDebugStr(installer_.get()); { // Check package is active. Result active = IsActive(installer_->package, installer_->version, installer_->test_installed_file); ASSERT_TRUE(IsOk(active)); ASSERT_TRUE(*active) << Join(GetActivePackagesStrings(), ','); } std::string package_id = installer_->package + "@" + std::to_string(installer_->version); // Check that hashtree file was created. { std::string hashtree_path = std::string(kApexHashTreeDir) + "/" + package_id; auto exists = PathExists(hashtree_path); ASSERT_TRUE(IsOk(exists)); ASSERT_TRUE(*exists); } // Check that block device can be read. auto block_device = GetBlockDeviceForApex(package_id); ASSERT_TRUE(IsOk(block_device)); ASSERT_TRUE(IsOk(ReadDevice(*block_device))); } TEST_F(ApexServiceNoHashtreeApexActivationTest, NewSessionDoesNotImpactActivePackage) { ASSERT_TRUE(IsOk(service_->activatePackage(installer_->test_installed_file))) << GetDebugStr(installer_.get()); { // Check package is active. Result active = IsActive(installer_->package, installer_->version, installer_->test_installed_file); ASSERT_TRUE(IsOk(active)); ASSERT_TRUE(*active) << Join(GetActivePackagesStrings(), ','); } PrepareTestApexForInstall installer2( GetTestFile("apex.apexd_test_no_hashtree_2.apex"), "/data/app-staging/session_123", "staging_data_file"); if (!installer2.Prepare()) { FAIL(); } ApexInfoList list; ApexSessionParams params; params.sessionId = 123; ASSERT_TRUE(IsOk(service_->submitStagedSession(params, &list))); std::string package_id = installer_->package + "@" + std::to_string(installer_->version); // Check that new hashtree file was created. { std::string hashtree_path = std::string(kApexHashTreeDir) + "/" + package_id + ".new"; auto exists = PathExists(hashtree_path); ASSERT_TRUE(IsOk(exists)); ASSERT_TRUE(*exists) << hashtree_path << " does not exist"; } // Check that active hashtree is still there. { std::string hashtree_path = std::string(kApexHashTreeDir) + "/" + package_id; auto exists = PathExists(hashtree_path); ASSERT_TRUE(IsOk(exists)); ASSERT_TRUE(*exists) << hashtree_path << " does not exist"; } // Check that block device of active APEX can still be read. auto block_device = GetBlockDeviceForApex(package_id); ASSERT_TRUE(IsOk(block_device)); } TEST_F(ApexServiceNoHashtreeApexActivationTest, ShowsUpInMountedApexDatabase) { ASSERT_TRUE(IsOk(service_->activatePackage(installer_->test_installed_file))) << GetDebugStr(installer_.get()); MountedApexDatabase db; db.PopulateFromMounts(kActiveApexPackagesDataDir, kApexDecompressedDir, kApexHashTreeDir); std::optional mounted_apex; db.ForallMountedApexes(installer_->package, [&](const MountedApexData& d, bool active) { if (active) { mounted_apex.emplace(d); } }); ASSERT_TRUE(mounted_apex) << "Haven't found " << installer_->test_installed_file << " in the database of mounted apexes"; // Get all necessary data for assertions on mounted_apex. std::string package_id = installer_->package + "@" + std::to_string(installer_->version); std::vector slaves = ListSlavesOfDmDevice(package_id); ASSERT_EQ(2u, slaves.size()) << "Unexpected number of slaves: " << Join(slaves, ","); // Now we are ready to assert on mounted_apex. ASSERT_EQ(installer_->test_installed_file, mounted_apex->full_path); std::string expected_mount = std::string(kApexRoot) + "/" + package_id; ASSERT_EQ(expected_mount, mounted_apex->mount_point); ASSERT_EQ(package_id, mounted_apex->device_name); // For loops we only check that both loop_name and hashtree_loop_name are // slaves of the top device mapper device. ASSERT_THAT(slaves, Contains(mounted_apex->loop_name)); ASSERT_THAT(slaves, Contains(mounted_apex->hashtree_loop_name)); ASSERT_NE(mounted_apex->loop_name, mounted_apex->hashtree_loop_name); } TEST_F(ApexServiceNoHashtreeApexActivationTest, DeactivateFreesLoopDevices) { ASSERT_TRUE(IsOk(service_->activatePackage(installer_->test_installed_file))) << GetDebugStr(installer_.get()); std::string package_id = installer_->package + "@" + std::to_string(installer_->version); std::vector slaves = ListSlavesOfDmDevice(package_id); ASSERT_EQ(2u, slaves.size()) << "Unexpected number of slaves: " << Join(slaves, ","); ASSERT_TRUE( IsOk(service_->deactivatePackage(installer_->test_installed_file))); for (const auto& loop : slaves) { struct loop_info li; unique_fd fd(TEMP_FAILURE_RETRY(open(loop.c_str(), O_RDWR | O_CLOEXEC))); ASSERT_NE(-1, fd.get()) << "Failed to open " << loop << " : " << strerror(errno); ASSERT_EQ(-1, ioctl(fd.get(), LOOP_GET_STATUS, &li)) << loop << " is still alive"; ASSERT_EQ(ENXIO, errno) << "Unexpected errno : " << strerror(errno); } // Skip deactivatePackage on TearDown. installer_.reset(); } TEST_F(ApexServiceTest, NoHashtreeApexStagePackagesMovesHashtree) { PrepareTestApexForInstall installer( GetTestFile("apex.apexd_test_no_hashtree.apex"), "/data/app-staging/session_239", "staging_data_file"); if (!installer.Prepare()) { FAIL(); } auto read_fn = [](const std::string& path) -> std::vector { static constexpr size_t kBufSize = 4096; std::vector buffer(kBufSize); unique_fd fd(TEMP_FAILURE_RETRY(open(path.c_str(), O_RDONLY | O_CLOEXEC))); if (fd.get() == -1) { PLOG(ERROR) << "Failed to open " << path; ADD_FAILURE(); return buffer; } if (!ReadFully(fd.get(), buffer.data(), kBufSize)) { PLOG(ERROR) << "Failed to read " << path; ADD_FAILURE(); } return buffer; }; ApexInfoList list; ApexSessionParams params; params.sessionId = 239; ASSERT_TRUE(IsOk(service_->submitStagedSession(params, &list))); std::string package_id = installer.package + "@" + std::to_string(installer.version); // Check that new hashtree file was created. std::vector original_hashtree_data; { std::string hashtree_path = std::string(kApexHashTreeDir) + "/" + package_id + ".new"; auto exists = PathExists(hashtree_path); ASSERT_TRUE(IsOk(exists)); ASSERT_TRUE(*exists); original_hashtree_data = read_fn(hashtree_path); } ASSERT_TRUE(IsOk(service_->stagePackages({installer.test_file}))); // Check that hashtree file was moved. { std::string hashtree_path = std::string(kApexHashTreeDir) + "/" + package_id + ".new"; auto exists = PathExists(hashtree_path); ASSERT_TRUE(IsOk(exists)); ASSERT_FALSE(*exists); } { std::string hashtree_path = std::string(kApexHashTreeDir) + "/" + package_id; auto exists = PathExists(hashtree_path); ASSERT_TRUE(IsOk(exists)); ASSERT_TRUE(*exists); std::vector moved_hashtree_data = read_fn(hashtree_path); ASSERT_EQ(moved_hashtree_data, original_hashtree_data); } } TEST_F(ApexServiceTest, GetFactoryPackages) { Result> factory_packages = GetFactoryPackages(); ASSERT_TRUE(IsOk(factory_packages)); ASSERT_TRUE(factory_packages->size() > 0); std::vector builtin_dirs; for (const auto& d : kApexPackageBuiltinDirs) { std::string realpath; if (android::base::Realpath(d, &realpath)) { builtin_dirs.push_back(realpath); } // realpath might fail in case when dir is a non-existing path. We can // ignore non-existing paths. } // Decompressed APEX is also considred factory package builtin_dirs.push_back(kApexDecompressedDir); for (const ApexInfo& package : *factory_packages) { bool is_builtin = false; for (const auto& dir : builtin_dirs) { if (StartsWith(package.modulePath, dir)) { is_builtin = true; } } ASSERT_TRUE(is_builtin); } } TEST_F(ApexServiceTest, NoPackagesAreBothActiveAndInactive) { Result> active_packages = GetActivePackages(); ASSERT_TRUE(IsOk(active_packages)); ASSERT_TRUE(active_packages->size() > 0); Result> inactive_packages = GetInactivePackages(); ASSERT_TRUE(IsOk(inactive_packages)); std::vector active_packages_strings = GetPackagesStrings(*active_packages); std::vector inactive_packages_strings = GetPackagesStrings(*inactive_packages); std::sort(active_packages_strings.begin(), active_packages_strings.end()); std::sort(inactive_packages_strings.begin(), inactive_packages_strings.end()); std::vector intersection; std::set_intersection( active_packages_strings.begin(), active_packages_strings.end(), inactive_packages_strings.begin(), inactive_packages_strings.end(), std::back_inserter(intersection)); ASSERT_THAT(intersection, SizeIs(0)); } TEST_F(ApexServiceTest, GetAllPackages) { Result> all_packages = GetAllPackages(); ASSERT_TRUE(IsOk(all_packages)); ASSERT_TRUE(all_packages->size() > 0); Result> active_packages = GetActivePackages(); std::vector active_strings = GetPackagesStrings(*active_packages); Result> factory_packages = GetFactoryPackages(); std::vector factory_strings = GetPackagesStrings(*factory_packages); for (ApexInfo& apexInfo : *all_packages) { std::string package_string = GetPackageString(apexInfo); bool should_be_active = std::find(active_strings.begin(), active_strings.end(), package_string) != active_strings.end(); bool should_be_factory = std::find(factory_strings.begin(), factory_strings.end(), package_string) != factory_strings.end(); ASSERT_EQ(should_be_active, apexInfo.isActive) << package_string << " should " << (should_be_active ? "" : "not ") << "be active"; ASSERT_EQ(should_be_factory, apexInfo.isFactory) << package_string << " should " << (should_be_factory ? "" : "not ") << "be factory"; } } class ApexSameGradeOfPreInstalledVersionTest : public ApexServiceTest { public: void SetUp() override { // TODO(b/136647373): Move this check to environment setup if (!android::base::GetBoolProperty("ro.apex.updatable", false)) { GTEST_SKIP() << "Skipping test because device doesn't support APEX"; } ApexServiceTest::SetUp(); ASSERT_NE(nullptr, service_.get()); installer_ = std::make_unique( GetTestFile("com.android.apex.cts.shim.apex")); if (!installer_->Prepare()) { return; } ASSERT_EQ("com.android.apex.cts.shim", installer_->package); // First deactivate currently active shim, otherwise activatePackage will be // no-op. { ApexInfo system_shim; ASSERT_TRUE(IsOk(service_->getActivePackage("com.android.apex.cts.shim", &system_shim))); ASSERT_TRUE(IsOk(service_->deactivatePackage(system_shim.modulePath))); } ASSERT_TRUE(IsOk(service_->stagePackages({installer_->test_file}))); ASSERT_TRUE( IsOk(service_->activatePackage(installer_->test_installed_file))); } void TearDown() override { // Attempt to deactivate. service_->deactivatePackage(installer_->test_installed_file); installer_.reset(); // ApexServiceTest::TearDown will wipe out everything under /data/apex. // Since some of that information is required for deactivatePackage binder // call, it's required to be called after deactivating package. ApexServiceTest::TearDown(); ASSERT_TRUE(IsOk(service_->activatePackage( "/system/apex/com.android.apex.cts.shim.apex"))); } std::unique_ptr installer_; }; TEST_F(ApexSameGradeOfPreInstalledVersionTest, VersionOnDataWins) { std::vector all; ASSERT_TRUE(IsOk(service_->getAllPackages(&all))); ApexInfo on_data; on_data.moduleName = "com.android.apex.cts.shim"; on_data.modulePath = "/data/apex/active/com.android.apex.cts.shim@1.apex"; on_data.preinstalledModulePath = "/system/apex/com.android.apex.cts.shim.apex"; on_data.versionCode = 1; on_data.isFactory = false; on_data.isActive = true; ApexInfo preinstalled; preinstalled.moduleName = "com.android.apex.cts.shim"; preinstalled.modulePath = "/system/apex/com.android.apex.cts.shim.apex"; preinstalled.preinstalledModulePath = "/system/apex/com.android.apex.cts.shim.apex"; preinstalled.versionCode = 1; preinstalled.isFactory = true; preinstalled.isActive = false; ASSERT_THAT(all, Contains(ApexInfoEq(on_data))); ASSERT_THAT(all, Contains(ApexInfoEq(preinstalled))); } 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 = DeviceMapper::Instance(); std::vector devices; if (!dm.GetAvailableDevices(&devices)) { return Result(Errorf("GetAvailableDevices failed")); } for (const auto& device : devices) { if (device.name() == name) { return Result(true); } } return Result(false); }; #define ASSERT_FIND(type) \ { \ Result res = find_fn(package_id); \ ASSERT_RESULT_OK(res); \ 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. } TEST_F(ApexServiceActivationSuccessTest, DeactivateFreesLoopDevices) { ASSERT_TRUE(IsOk(service_->activatePackage(installer_->test_installed_file))) << GetDebugStr(installer_.get()); std::string package_id = installer_->package + "@" + std::to_string(installer_->version); std::vector slaves = ListSlavesOfDmDevice(package_id); ASSERT_EQ(1u, slaves.size()) << "Unexpected number of slaves: " << Join(slaves, ","); const std::string& loop = slaves[0]; ASSERT_TRUE( IsOk(service_->deactivatePackage(installer_->test_installed_file))); struct loop_info li; unique_fd fd(TEMP_FAILURE_RETRY(open(loop.c_str(), O_RDWR | O_CLOEXEC))); ASSERT_NE(-1, fd.get()) << "Failed to open " << loop << " : " << strerror(errno); ASSERT_EQ(-1, ioctl(fd.get(), LOOP_GET_STATUS, &li)) << loop << " is still alive"; ASSERT_EQ(ENXIO, errno) << "Unexpected errno : " << strerror(errno); 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_THAT(logcat, HasSubstr(test_message)); } // Ensure that the package is neither active nor mounted. for (const InstallerUPtr& installer : installers) { Result active = IsActive(installer->package, installer->version, installer->test_file); ASSERT_TRUE(IsOk(active)); EXPECT_FALSE(*active); } for (const InstallerUPtr& installer : installers) { Result 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; ApexSessionParams params; params.sessionId = 123; ASSERT_TRUE(IsOk(service_->submitStagedSession(params, &list))) << GetDebugStr(&installer); EXPECT_EQ(1u, list.apexInfos.size()); ApexInfo match; for (const ApexInfo& info : list.apexInfos) { if (info.moduleName == installer.package) { match = info; break; } } ASSERT_EQ(installer.package, match.moduleName); ASSERT_EQ(installer.version, static_cast(match.versionCode)); ASSERT_EQ(installer.test_file, match.modulePath); 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))); 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))) << GetDebugStr(&installer); 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, SubmitSingleStagedSessionKeepsPreviousSessions) { 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; ApexSessionParams params; params.sessionId = 239; ASSERT_TRUE(IsOk(service_->submitStagedSession(params, &list))); sessions.clear(); ASSERT_TRUE(IsOk(service_->getSessions(&sessions))); ApexSessionInfo new_session = CreateSessionInfo(239); new_session.isVerified = true; ASSERT_THAT(sessions, UnorderedElementsAre(SessionInfoEq(new_session), SessionInfoEq(expected_session1), SessionInfoEq(expected_session2), SessionInfoEq(expected_session3))); } TEST_F(ApexServiceTest, SubmitSingleSessionTestFail) { PrepareTestApexForInstall installer( GetTestFile("apex.apexd_test_corrupt_apex.apex"), "/data/app-staging/session_456", "staging_data_file"); if (!installer.Prepare()) { FAIL() << GetDebugStr(&installer); } ApexInfoList list; ApexSessionParams params; params.sessionId = 456; ASSERT_FALSE(IsOk(service_->submitStagedSession(params, &list))) << GetDebugStr(&installer); ApexSessionInfo session; ASSERT_TRUE(IsOk(service_->getStagedSessionInfo(456, &session))) << GetDebugStr(&installer); ApexSessionInfo expected = CreateSessionInfo(-1); expected.isUnknown = true; EXPECT_THAT(session, SessionInfoEq(expected)); } 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; ApexSessionParams params; params.sessionId = 10; params.childSessionIds = {20, 30}; ASSERT_TRUE(IsOk(service_->submitStagedSession(params, &list))) << GetDebugStr(&installer); EXPECT_EQ(2u, list.apexInfos.size()); ApexInfo match; bool package1_found = false; bool package2_found = false; for (const ApexInfo& info : list.apexInfos) { if (info.moduleName == installer.package) { ASSERT_EQ(installer.package, info.moduleName); ASSERT_EQ(installer.version, static_cast(info.versionCode)); ASSERT_EQ(installer.test_file, info.modulePath); package1_found = true; } else if (info.moduleName == installer2.package) { ASSERT_EQ(installer2.package, info.moduleName); ASSERT_EQ(installer2.version, static_cast(info.versionCode)); ASSERT_EQ(installer2.test_file, info.modulePath); package2_found = true; } else { FAIL() << "Unexpected package found " << info.moduleName << 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))) << GetDebugStr(&installer); ASSERT_TRUE(IsOk(service_->getStagedSessionInfo(10, &session))) << GetDebugStr(&installer); expected.isVerified = false; expected.isStaged = true; ASSERT_THAT(session, SessionInfoEq(expected)); // Check that temp mounts were cleanded up. for (const auto& mount : GetApexMounts()) { EXPECT_FALSE(EndsWith(mount, ".tmp")) << "Found temp mount " << mount; } } 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_corrupt_apex.apex"), "/data/app-staging/session_31", "staging_data_file"); if (!installer.Prepare() || !installer2.Prepare()) { FAIL() << GetDebugStr(&installer) << GetDebugStr(&installer2); } ApexInfoList list; ApexSessionParams params; params.sessionId = 11; params.childSessionIds = {21, 31}; ASSERT_FALSE(IsOk(service_->submitStagedSession(params, &list))) << GetDebugStr(&installer); } TEST_F(ApexServiceTest, MarkStagedSessionReadyFail) { // We should fail if we ask information about a session we don't know. ASSERT_FALSE(IsOk(service_->markStagedSessionReady(666))); 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)); } // Should be able to abort individual staged session TEST_F(ApexServiceTest, AbortStagedSession) { auto session1 = ApexSession::CreateSession(239); ASSERT_TRUE(IsOk(session1->UpdateStateAndCommit(SessionState::VERIFIED))); auto session2 = ApexSession::CreateSession(240); ASSERT_TRUE(IsOk(session2->UpdateStateAndCommit(SessionState::STAGED))); std::vector sessions; ASSERT_TRUE(IsOk(service_->getSessions(&sessions))); ASSERT_EQ(2u, sessions.size()); ASSERT_TRUE(IsOk(service_->abortStagedSession(239))); sessions.clear(); ASSERT_TRUE(IsOk(service_->getSessions(&sessions))); ApexSessionInfo expected = CreateSessionInfo(240); expected.isStaged = true; ASSERT_THAT(sessions, UnorderedElementsAre(SessionInfoEq(expected))); } // abortStagedSession should not abort activated session TEST_F(ApexServiceTest, AbortStagedSessionActivatedFail) { auto session1 = ApexSession::CreateSession(239); ASSERT_TRUE(IsOk(session1->UpdateStateAndCommit(SessionState::ACTIVATED))); auto session2 = ApexSession::CreateSession(240); ASSERT_TRUE(IsOk(session2->UpdateStateAndCommit(SessionState::STAGED))); std::vector sessions; ASSERT_TRUE(IsOk(service_->getSessions(&sessions))); ASSERT_EQ(2u, sessions.size()); ASSERT_FALSE(IsOk(service_->abortStagedSession(239))); sessions.clear(); ASSERT_TRUE(IsOk(service_->getSessions(&sessions))); ApexSessionInfo expected1 = CreateSessionInfo(239); expected1.isActivated = true; ApexSessionInfo expected2 = CreateSessionInfo(240); expected2.isStaged = true; ASSERT_THAT(sessions, UnorderedElementsAre(SessionInfoEq(expected1), SessionInfoEq(expected2))); } // Only finalized sessions should be deleted on DeleteFinalizedSessions() TEST_F(ApexServiceTest, DeleteFinalizedSessions) { // Fetch list of all session state std::vector states; for (int i = SessionState::State_MIN; i < SessionState::State_MAX; i++) { if (!SessionState::State_IsValid(i)) { continue; } states.push_back(SessionState::State(i)); } // For every session state, create a new session. This is to verify we only // delete sessions in final state. auto nonFinalSessions = 0u; for (auto i = 0u; i < states.size(); i++) { auto session = ApexSession::CreateSession(230 + i); SessionState::State state = states[i]; ASSERT_TRUE(IsOk(session->UpdateStateAndCommit(state))); if (!session->IsFinalized()) { nonFinalSessions++; } } std::vector sessions = ApexSession::GetSessions(); ASSERT_EQ(states.size(), sessions.size()); // Now try cleaning up all finalized sessions ApexSession::DeleteFinalizedSessions(); sessions = ApexSession::GetSessions(); ASSERT_EQ(nonFinalSessions, sessions.size()); // Verify only finalized sessions have been deleted for (auto& session : sessions) { ASSERT_FALSE(session.IsFinalized()); } } 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. std::vector pkgs = {installer1.test_file, installer2.test_file}; ASSERT_TRUE(IsOk(service_->stagePackages(pkgs))); // Make sure that /data/apex/active has activated packages. auto active_pkgs = ReadEntireDir(kActiveApexPackagesDataDir); ASSERT_TRUE(IsOk(active_pkgs)); ASSERT_THAT(*active_pkgs, UnorderedElementsAre(installer1.test_installed_file, installer2.test_installed_file)); ApexInfoList list; ApexSessionParams params; params.sessionId = 23; ASSERT_TRUE(IsOk(service_->submitStagedSession(params, &list))); auto backups = ReadEntireDir(kApexBackupDir); 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(); std::vector pkgs = {installer1.test_file, installer2.test_file}; ASSERT_TRUE(IsOk(service_->stagePackages(pkgs))); // Make sure that /data/apex/active has activated packages. auto active_pkgs = ReadEntireDir(kActiveApexPackagesDataDir); ASSERT_TRUE(IsOk(active_pkgs)); ASSERT_THAT(*active_pkgs, UnorderedElementsAre(installer1.test_installed_file, installer2.test_installed_file)); ApexInfoList list; ApexSessionParams params; params.sessionId = 43; ASSERT_TRUE(IsOk(service_->submitStagedSession(params, &list))); auto backups = ReadEntireDir(kApexBackupDir); 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), 0755))); auto active_pkgs = ReadEntireDir(kActiveApexPackagesDataDir); ASSERT_TRUE(IsOk(active_pkgs)); ASSERT_EQ(0u, active_pkgs->size()); ApexInfoList list; ApexSessionParams params; params.sessionId = 41; ASSERT_TRUE(IsOk(service_->submitStagedSession(params, &list))); auto backups = ReadEntireDir(kApexBackupDir); ASSERT_TRUE(IsOk(backups)); ASSERT_EQ(0u, backups->size()); } TEST_F(ApexServiceTest, ActivePackagesDirEmpty) { 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 is empty DeleteDirContent(kActiveApexPackagesDataDir); ApexInfoList list; ApexSessionParams params; params.sessionId = 41; ASSERT_TRUE(IsOk(service_->submitStagedSession(params, &list))); if (!supports_fs_checkpointing_) { auto backups = ReadEntireDir(kApexBackupDir); 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; } std::vector pkgs = {installer1.test_file, installer2.test_file}; ASSERT_TRUE(IsOk(service_->stagePackages(pkgs))); pkgs = {installer2.test_installed_file}; ASSERT_TRUE(IsOk(service_->unstagePackages(pkgs))); auto active_packages = ReadEntireDir(kActiveApexPackagesDataDir); 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; } std::vector pkgs = {installer1.test_file}; ASSERT_TRUE(IsOk(service_->stagePackages(pkgs))); 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 = ReadEntireDir(kActiveApexPackagesDataDir); ASSERT_TRUE(IsOk(active_packages)); ASSERT_THAT(*active_packages, UnorderedElementsAre(installer1.test_installed_file)); } TEST_F(ApexServiceTest, UnstagePackagesFailPreInstalledApex) { auto status = service_->unstagePackages( {"/system/apex/com.android.apex.cts.shim.apex"}); ASSERT_FALSE(IsOk(status)); const std::string& error_message = std::string(status.exceptionMessage().c_str()); ASSERT_THAT(error_message, HasSubstr("Can't uninstall pre-installed apex " "/system/apex/com.android.apex.cts.shim.apex")); ASSERT_TRUE(RegularFileExists("/system/apex/com.android.apex.cts.shim.apex")); } class ApexServiceRevertTest : 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 CheckActiveApexContents(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(0755u, sd.st_mode & ALLPERMS); // Now read content and check it contains expected values. auto active_pkgs = ReadEntireDir(kActiveApexPackagesDataDir); ASSERT_TRUE(IsOk(active_pkgs)); ASSERT_THAT(*active_pkgs, UnorderedElementsAreArray(expected_pkgs)); } }; // Should be able to revert activated sessions TEST_F(ApexServiceRevertTest, RevertActiveSessionsSuccessful) { 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. ASSERT_TRUE(IsOk(service_->stagePackages({installer.test_file}))); PrepareBackup({GetTestFile("apex.apexd_test.apex")}); ASSERT_TRUE(IsOk(service_->revertActiveSessions())); auto pkg = StringPrintf("%s/com.android.apex.test_package@1.apex", kActiveApexPackagesDataDir); SCOPED_TRACE(""); CheckActiveApexContents({pkg}); } // Calling revertActiveSessions should not restore backup on checkpointing // devices TEST_F(ApexServiceRevertTest, RevertActiveSessionsDoesNotRestoreBackupIfCheckpointingSupported) { if (!supports_fs_checkpointing_) { GTEST_SKIP() << "Can't run if filesystem checkpointing is not supported"; } 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. ASSERT_TRUE(IsOk(service_->stagePackages({installer.test_file}))); PrepareBackup({GetTestFile("apex.apexd_test.apex")}); ASSERT_TRUE(IsOk(service_->revertActiveSessions())); // Check that active apexes were not reverted. auto pkg = StringPrintf("%s/com.android.apex.test_package@2.apex", kActiveApexPackagesDataDir); SCOPED_TRACE(""); CheckActiveApexContents({pkg}); } // Should fail to revert active sessions when there are none TEST_F(ApexServiceRevertTest, RevertActiveSessionsWithoutActiveSessions) { // This test simulates a situation that should never happen on user builds: // revertActiveSessions was called, but there were no active sessions. PrepareTestApexForInstall installer(GetTestFile("apex.apexd_test_v2.apex")); if (!installer.Prepare()) { return; } // Make sure /data/apex/active is non-empty. ASSERT_TRUE(IsOk(service_->stagePackages({installer.test_file}))); PrepareBackup({GetTestFile("apex.apexd_test.apex")}); // Even though backup is there, no sessions are active, hence revert request // should fail. ASSERT_FALSE(IsOk(service_->revertActiveSessions())); } TEST_F(ApexServiceRevertTest, RevertFailsNoBackupFolder) { ASSERT_FALSE(IsOk(service_->revertActiveSessions())); } TEST_F(ApexServiceRevertTest, RevertFailsNoActivePackagesFolder) { PrepareTestApexForInstall installer(GetTestFile("apex.apexd_test.apex")); ASSERT_FALSE(IsOk(service_->revertActiveSessions())); } TEST_F(ApexServiceRevertTest, 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(ApexServiceRevertTest, ResumesRevert) { 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. ASSERT_TRUE(IsOk(service_->stagePackages({installer.test_file}))); auto session = ApexSession::CreateSession(17239); ASSERT_TRUE(IsOk(session)); ASSERT_TRUE( IsOk(session->UpdateStateAndCommit(SessionState::REVERT_IN_PROGRESS))); ASSERT_TRUE(IsOk(service_->resumeRevertIfNeeded())); 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(""); CheckActiveApexContents({pkg1, pkg2}); std::vector sessions; ASSERT_TRUE(IsOk(service_->getSessions(&sessions))); ApexSessionInfo expected = CreateSessionInfo(17239); expected.isReverted = true; ASSERT_THAT(sessions, UnorderedElementsAre(SessionInfoEq(expected))); } TEST_F(ApexServiceRevertTest, DoesNotResumeRevert) { 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. ASSERT_TRUE(IsOk(service_->stagePackages({installer.test_file}))); auto session = ApexSession::CreateSession(53); ASSERT_TRUE(IsOk(session)); ASSERT_TRUE(IsOk(session->UpdateStateAndCommit(SessionState::SUCCESS))); ASSERT_TRUE(IsOk(service_->resumeRevertIfNeeded())); // Check that revert wasn't resumed. auto active_pkgs = ReadEntireDir(kActiveApexPackagesDataDir); 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))); } // Should mark sessions as REVERT_FAILED on failed revert TEST_F(ApexServiceRevertTest, SessionsMarkedAsRevertFailed) { 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_->revertActiveSessions())); ApexSessionInfo session_info; ASSERT_TRUE(IsOk(service_->getStagedSessionInfo(53, &session_info))); ApexSessionInfo expected = CreateSessionInfo(53); expected.isRevertFailed = true; ASSERT_THAT(session_info, SessionInfoEq(expected)); } TEST_F(ApexServiceRevertTest, RevertFailedStateRevertAttemptFails) { 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::REVERT_FAILED))); ASSERT_FALSE(IsOk(service_->revertActiveSessions())); ApexSessionInfo session_info; ASSERT_TRUE(IsOk(service_->getStagedSessionInfo(17239, &session_info))); ApexSessionInfo expected = CreateSessionInfo(17239); expected.isRevertFailed = true; ASSERT_THAT(session_info, SessionInfoEq(expected)); } TEST_F(ApexServiceRevertTest, RevertStoresCrashingNativeProcess) { 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. ASSERT_TRUE(IsOk(service_->stagePackages({installer.test_file}))); std::string native_process = "test_process"; // TODO(ioffe): this is calling into internals of apexd which makes test quite // britle. With some refactoring we should be able to call binder api, or // make this a unit test of apexd.cpp. Result res = ::android::apex::RevertActiveSessions(native_process, ""); session = ApexSession::GetSession(1543); ASSERT_EQ(session->GetCrashingNativeProcess(), native_process); } 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) { // TODO(b/136647373): Move this check to environment setup if (!android::base::GetBoolProperty("ro.apex.updatable", false)) { GTEST_SKIP() << "Skipping test because device doesn't support APEX"; } 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) { // TODO(b/136647373): Move this check to environment setup if (!android::base::GetBoolProperty("ro.apex.updatable", false)) { GTEST_SKIP() << "Skipping test because device doesn't support APEX"; } std::string ns_apexd; ExecInMountNamespaceOf(GetPidOf("apexd"), [&](pid_t /*pid*/) { 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 /*pid*/) { 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) { // TODO(b/136647373): Move this check to environment setup if (!android::base::GetBoolProperty("ro.apex.updatable", false)) { GTEST_SKIP() << "Skipping test because device doesn't support APEX"; } 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) { // TODO(b/136647373): Move this check to environment setup if (!android::base::GetBoolProperty("ro.apex.updatable", false)) { GTEST_SKIP() << "Skipping test because device doesn't support APEX"; } 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 { // TODO(b/136647373): Move this check to environment setup if (!android::base::GetBoolProperty("ro.apex.updatable", false)) { GTEST_SKIP() << "Skipping test because device doesn't support APEX"; } ApexServiceTest::SetUp(); // Assert that shim apex is pre-installed. std::vector list; ASSERT_TRUE(IsOk(service_->getAllPackages(&list))); ApexInfo expected; expected.moduleName = "com.android.apex.cts.shim"; expected.modulePath = "/system/apex/com.android.apex.cts.shim.apex"; expected.preinstalledModulePath = "/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); } ASSERT_TRUE(IsOk(service_->stagePackages({installer.test_file}))); } TEST_F(ApexShimUpdateTest, SubmitStagedSessionFailureHasPreInstallHook) { 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; ApexSessionParams params; params.sessionId = 23; ASSERT_FALSE(IsOk(service_->submitStagedSession(params, &list))); } 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; ApexSessionParams params; params.sessionId = 43; ASSERT_FALSE(IsOk(service_->submitStagedSession(params, &list))); } 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; ApexSessionParams params; params.sessionId = 41; ASSERT_FALSE(IsOk(service_->submitStagedSession(params, &list))); } 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; ApexSessionParams params; params.sessionId = 42; ASSERT_FALSE(IsOk(service_->submitStagedSession(params, &list))); } TEST_F(ApexShimUpdateTest, UpdateToV1Success) { PrepareTestApexForInstall installer( GetTestFile("com.android.apex.cts.shim.apex")); if (!installer.Prepare()) { FAIL() << GetDebugStr(&installer); } ASSERT_TRUE(IsOk(service_->stagePackages({installer.test_file}))); } TEST_F(ApexShimUpdateTest, SubmitStagedSessionV1ShimApexSuccess) { PrepareTestApexForInstall installer( GetTestFile("com.android.apex.cts.shim.apex"), "/data/app-staging/session_97", "staging_data_file"); if (!installer.Prepare()) { FAIL() << GetDebugStr(&installer); } ApexInfoList list; ApexSessionParams params; params.sessionId = 97; ASSERT_TRUE(IsOk(service_->submitStagedSession(params, &list))); } 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; ApexSessionParams params; params.sessionId = 57; ASSERT_FALSE(IsOk(service_->submitStagedSession(params, &list))); } TEST_F(ApexServiceTest, SubmitStagedSessionCorruptApexFailsB146895998) { PrepareTestApexForInstall installer(GetTestFile("corrupted_b146895998.apex"), "/data/app-staging/session_71", "staging_data_file"); if (!installer.Prepare()) { FAIL() << GetDebugStr(&installer); } ApexInfoList list; ApexSessionParams params; params.sessionId = 71; ASSERT_FALSE(IsOk(service_->submitStagedSession(params, &list))); } TEST_F(ApexServiceTest, StageCorruptApexFailsB146895998) { PrepareTestApexForInstall installer(GetTestFile("corrupted_b146895998.apex")); if (!installer.Prepare()) { FAIL() << GetDebugStr(&installer); } ASSERT_FALSE(IsOk(service_->stagePackages({installer.test_file}))); } TEST_F(ApexServiceTest, RemountPackagesPackageOnSystemChanged) { static constexpr const char* kSystemPath = "/system_ext/apex/apex.apexd_test.apex"; static constexpr const char* kPackageName = "com.android.apex.test_package"; if (!fs_mgr_overlayfs_is_setup()) { GTEST_SKIP() << "/system_ext is not overlayed into read-write"; } if (auto res = IsActive(kPackageName); !res.ok()) { FAIL() << res.error(); } else { ASSERT_FALSE(*res) << kPackageName << " is active"; } ASSERT_EQ(0, access(kSystemPath, F_OK)) << "Failed to stat " << kSystemPath << " : " << strerror(errno); ASSERT_TRUE(IsOk(service_->activatePackage(kSystemPath))); std::string backup_path = GetTestFile("apex.apexd_test.apexd.bak"); // Copy original /system_ext apex file. We will need to restore it after test // runs. ASSERT_RESULT_OK(CopyFile(kSystemPath, backup_path, fs::copy_options::none)); // Make sure we cleanup after ourselves. auto deleter = android::base::make_scope_guard([&]() { if (auto ret = service_->deactivatePackage(kSystemPath); !ret.isOk()) { LOG(ERROR) << ret.exceptionMessage(); } auto ret = CopyFile(backup_path, kSystemPath, fs::copy_options::overwrite_existing); if (!ret.ok()) { LOG(ERROR) << ret.error(); } }); // Copy v2 version to /system_ext/apex/ and then call remountPackages. PrepareTestApexForInstall installer(GetTestFile("apex.apexd_test_v2.apex")); if (!installer.Prepare()) { FAIL() << GetDebugStr(&installer); } ASSERT_RESULT_OK(CopyFile(installer.test_file, kSystemPath, fs::copy_options::overwrite_existing)); // Don't check that remountPackages succeeded. Most likely it will fail, but // it should still remount our test apex. service_->remountPackages(); // Check that v2 is now active. auto active_apex = GetActivePackage("com.android.apex.test_package"); ASSERT_RESULT_OK(active_apex); ASSERT_EQ(2u, active_apex->versionCode); // Check that module path didn't change, modulo symlink. std::string realSystemPath; ASSERT_TRUE(android::base::Realpath(kSystemPath, &realSystemPath)); ASSERT_EQ(realSystemPath, active_apex->modulePath); } TEST_F(ApexServiceActivationSuccessTest, RemountPackagesPackageOnDataChanged) { ASSERT_TRUE(IsOk(service_->activatePackage(installer_->test_installed_file))) << GetDebugStr(installer_.get()); // Copy v2 version to /data/apex/active and then call remountPackages. PrepareTestApexForInstall installer2(GetTestFile("apex.apexd_test_v2.apex")); if (!installer2.Prepare()) { FAIL() << GetDebugStr(&installer2); } ASSERT_RESULT_OK(CopyFile(installer2.test_file, installer_->test_installed_file, fs::copy_options::overwrite_existing)); // Don't check that remountPackages succeeded. Most likely it will fail, but // it should still remount our test apex. service_->remountPackages(); // Check that v2 is now active. auto active_apex = GetActivePackage("com.android.apex.test_package"); ASSERT_RESULT_OK(active_apex); ASSERT_EQ(2u, active_apex->versionCode); // Sanity check that module path didn't change. ASSERT_EQ(installer_->test_installed_file, active_apex->modulePath); } TEST_F(ApexServiceTest, SubmitStagedSessionFailsManifestMismatchCleansUpHashtree) { PrepareTestApexForInstall installer( GetTestFile("apex.apexd_test_no_hashtree_manifest_mismatch.apex"), "/data/app-staging/session_83", "staging_data_file"); if (!installer.Prepare()) { return; } ApexInfoList list; ApexSessionParams params; params.sessionId = 83; ASSERT_FALSE(IsOk(service_->submitStagedSession(params, &list))); std::string hashtree_file = std::string(kApexHashTreeDir) + "/" + installer.package + "@" + std::to_string(installer.version) + ".new"; ASSERT_FALSE(RegularFileExists(hashtree_file)); } 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 } }; struct NoCodeApexNameProvider { static std::string GetTestName() { return "apex.apexd_test_nocode.apex"; } static std::string GetPackageName() { return "com.android.apex.test_package"; } }; class ApexServiceActivationNoCode : public ApexServiceActivationTest {}; TEST_F(ApexServiceActivationNoCode, NoCodeApexIsNotExecutable) { ASSERT_TRUE(IsOk(service_->activatePackage(installer_->test_installed_file))) << GetDebugStr(installer_.get()); 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/" + NoCodeApexNameProvider::GetPackageName() + "@1") { found_apex_mountpoint = true; // Make sure that option contains noexec std::vector options = android::base::Split(tokens[5], ","); EXPECT_NE(options.end(), std::find(options.begin(), options.end(), "noexec")); break; } } EXPECT_TRUE(found_apex_mountpoint); } struct BannedNameProvider { static std::string GetTestName() { return "sharedlibs.apex"; } static std::string GetPackageName() { return "sharedlibs"; } }; class ApexServiceActivationBannedName : public ApexServiceActivationTest { public: ApexServiceActivationBannedName() : ApexServiceActivationTest(false) {} }; TEST_F(ApexServiceActivationBannedName, ApexWithBannedNameCannotBeActivated) { ASSERT_FALSE( IsOk(service_->activatePackage(installer_->test_installed_file))); } namespace { void PrepareCompressedTestApex(const std::string& input_apex, const std::string& builtin_dir, const std::string& decompressed_dir, const std::string& active_apex_dir) { const Result& apex_file = ApexFile::Open(input_apex); ASSERT_TRUE(apex_file.ok()); ASSERT_TRUE(apex_file->IsCompressed()) << "Not a compressed APEX"; auto prebuilt_file_path = builtin_dir + "/" + android::base::Basename(input_apex); fs::copy(input_apex, prebuilt_file_path); const ApexManifest& manifest = apex_file->GetManifest(); const std::string& package = manifest.name(); const int64_t& version = manifest.version(); auto decompressed_file_path = decompressed_dir + "/" + package + "@" + std::to_string(version) + ".apex"; auto result = apex_file->Decompress(decompressed_file_path); ASSERT_TRUE(result.ok()) << "Failed to decompress " << result.error(); auto active_apex_file_path = active_apex_dir + "/" + package + "@" + std::to_string(version) + ".apex"; auto error = link(decompressed_file_path.c_str(), active_apex_file_path.c_str()); ASSERT_EQ(error, 0) << "Failed to hardlink decompressed APEX"; } CompressedApexInfo CreateCompressedApex(const std::string& name, const int version, const int size) { CompressedApexInfo result; result.moduleName = name; result.versionCode = version; result.decompressedSize = size; return result; } } // namespace class ApexServiceTestForCompressedApex : public ApexServiceTest { public: static constexpr const char* kTempPrebuiltDir = "/data/apex/temp_prebuilt"; void SetUp() override { ApexServiceTest::SetUp(); ASSERT_NE(nullptr, service_.get()); TemporaryDir decompression_dir, active_apex_dir; if (0 != mkdir(kTempPrebuiltDir, 0777)) { int saved_errno = errno; ASSERT_EQ(saved_errno, EEXIST) << kTempPrebuiltDir << ":" << strerror(saved_errno); } PrepareCompressedTestApex( GetTestFile("com.android.apex.compressed.v1.capex"), kTempPrebuiltDir, kApexDecompressedDir, kActiveApexPackagesDataDir); service_->recollectPreinstalledData({kTempPrebuiltDir}); service_->recollectDataApex(kActiveApexPackagesDataDir, kApexDecompressedDir); } void TearDown() override { ApexServiceTest::TearDown(); DeleteDirContent(kTempPrebuiltDir); rmdir(kTempPrebuiltDir); DeleteDirContent(kApexDecompressedDir); DeleteDirContent(kActiveApexPackagesDataDir); } }; TEST_F(ApexServiceTestForCompressedApex, CalculateSizeForCompressedApex) { int64_t result; // Empty list of compressed apex info { CompressedApexInfoList empty_list; ASSERT_TRUE( IsOk(service_->calculateSizeForCompressedApex(empty_list, &result))); ASSERT_EQ(result, 0ll); } // Multiple compressed APEX should get summed { CompressedApexInfoList non_empty_list; CompressedApexInfo new_apex = CreateCompressedApex("new_apex", 1, 1); CompressedApexInfo new_apex_2 = CreateCompressedApex("new_apex_2", 1, 2); CompressedApexInfo compressed_apex_same_version = CreateCompressedApex("com.android.apex.compressed", 1, 4); CompressedApexInfo compressed_apex_higher_version = CreateCompressedApex("com.android.apex.compressed", 2, 8); non_empty_list.apexInfos.push_back(new_apex); non_empty_list.apexInfos.push_back(new_apex_2); non_empty_list.apexInfos.push_back(compressed_apex_same_version); non_empty_list.apexInfos.push_back(compressed_apex_higher_version); ASSERT_TRUE(IsOk( service_->calculateSizeForCompressedApex(non_empty_list, &result))); ASSERT_EQ(result, 11ll); // 1+2+8. compressed_apex_same_version is ignored } } TEST_F(ApexServiceTestForCompressedApex, ReserveSpaceForCompressedApex) { // Multiple compressed APEX should reserve equal to // CalculateSizeForCompressedApex { CompressedApexInfoList non_empty_list; CompressedApexInfo new_apex = CreateCompressedApex("new_apex", 1, 1); CompressedApexInfo new_apex_2 = CreateCompressedApex("new_apex_2", 1, 2); CompressedApexInfo compressed_apex_same_version = CreateCompressedApex("com.android.apex.compressed", 1, 4); CompressedApexInfo compressed_apex_higher_version = CreateCompressedApex("com.android.apex.compressed", 2, 8); non_empty_list.apexInfos.push_back(new_apex); non_empty_list.apexInfos.push_back(new_apex_2); non_empty_list.apexInfos.push_back(compressed_apex_same_version); non_empty_list.apexInfos.push_back(compressed_apex_higher_version); int64_t required_size; ASSERT_TRUE(IsOk(service_->calculateSizeForCompressedApex(non_empty_list, &required_size))); ASSERT_EQ(required_size, 11ll); // 1+2+8. compressed_apex_same_version is ignored ASSERT_TRUE(IsOk(service_->reserveSpaceForCompressedApex(non_empty_list))); auto files = ReadDir(kOtaReservedDir, [](auto _) { return true; }); ASSERT_TRUE(IsOk(files)); ASSERT_EQ(files->size(), 1u); EXPECT_EQ((int64_t)fs::file_size((*files)[0]), required_size); } // Sending empty list should delete reserved file { CompressedApexInfoList empty_list; ASSERT_TRUE(IsOk(service_->reserveSpaceForCompressedApex(empty_list))); auto files = ReadDir(kOtaReservedDir, [](auto _) { return true; }); ASSERT_TRUE(IsOk(files)); ASSERT_EQ(files->size(), 0u); } } } // namespace apex } // namespace android int main(int argc, char** argv) { android::base::InitLogging(argv, &android::base::StderrLogger); android::base::SetMinimumLogSeverity(android::base::VERBOSE); ::testing::InitGoogleTest(&argc, argv); ::testing::UnitTest::GetInstance()->listeners().Append( new android::apex::LogTestToLogcat()); return RUN_ALL_TESTS(); }