You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
3156 lines
110 KiB
3156 lines
110 KiB
/*
|
|
* 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 <algorithm>
|
|
#include <filesystem>
|
|
#include <fstream>
|
|
#include <functional>
|
|
#include <memory>
|
|
#include <optional>
|
|
#include <string>
|
|
#include <unordered_set>
|
|
#include <vector>
|
|
|
|
#include <grp.h>
|
|
#include <linux/loop.h>
|
|
#include <stdio.h>
|
|
#include <sys/ioctl.h>
|
|
#include <sys/stat.h>
|
|
#include <sys/types.h>
|
|
|
|
#include <android-base/file.h>
|
|
#include <android-base/logging.h>
|
|
#include <android-base/macros.h>
|
|
#include <android-base/properties.h>
|
|
#include <android-base/scopeguard.h>
|
|
#include <android-base/stringprintf.h>
|
|
#include <android-base/strings.h>
|
|
#include <android/os/IVold.h>
|
|
#include <binder/IServiceManager.h>
|
|
#include <fs_mgr_overlayfs.h>
|
|
#include <fstab/fstab.h>
|
|
#include <gmock/gmock.h>
|
|
#include <gtest/gtest.h>
|
|
#include <libdm/dm.h>
|
|
#include <selinux/selinux.h>
|
|
|
|
#include <android/apex/ApexInfo.h>
|
|
#include <android/apex/IApexService.h>
|
|
|
|
#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<IServiceManager> sm = android::defaultServiceManager();
|
|
sp<IBinder> binder = sm->waitForService(String16("apexservice"));
|
|
if (binder != nullptr) {
|
|
service_ = android::interface_cast<IApexService>(binder);
|
|
}
|
|
binder = sm->getService(String16("vold"));
|
|
if (binder != nullptr) {
|
|
vold_service_ = android::interface_cast<android::os::IVold>(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<bool> IsActive(const std::string& name) {
|
|
std::vector<ApexInfo> 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<bool> IsActive(const std::string& name, int64_t version,
|
|
const std::string& path) {
|
|
std::vector<ApexInfo> 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<std::vector<ApexInfo>> GetAllPackages() {
|
|
std::vector<ApexInfo> list;
|
|
android::binder::Status status = service_->getAllPackages(&list);
|
|
if (status.isOk()) {
|
|
return list;
|
|
}
|
|
|
|
return Error() << status.toString8().c_str();
|
|
}
|
|
|
|
Result<std::vector<ApexInfo>> GetActivePackages() {
|
|
std::vector<ApexInfo> list;
|
|
android::binder::Status status = service_->getActivePackages(&list);
|
|
if (status.isOk()) {
|
|
return list;
|
|
}
|
|
|
|
return Error() << status.exceptionMessage().c_str();
|
|
}
|
|
|
|
Result<std::vector<ApexInfo>> GetInactivePackages() {
|
|
std::vector<ApexInfo> 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<ApexInfo> 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<std::string> GetPackagesStrings(
|
|
const std::vector<ApexInfo>& list) {
|
|
std::vector<std::string> ret;
|
|
ret.reserve(list.size());
|
|
for (const ApexInfo& p : list) {
|
|
ret.push_back(GetPackageString(p));
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
std::vector<std::string> GetActivePackagesStrings() {
|
|
std::vector<ApexInfo> list;
|
|
android::binder::Status status = service_->getActivePackages(&list);
|
|
if (status.isOk()) {
|
|
std::vector<std::string> ret(list.size());
|
|
for (const ApexInfo& p : list) {
|
|
ret.push_back(GetPackageString(p));
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
std::vector<std::string> error;
|
|
error.push_back("ERROR");
|
|
return error;
|
|
}
|
|
|
|
Result<std::vector<ApexInfo>> GetFactoryPackages() {
|
|
std::vector<ApexInfo> 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<std::string> ListDir(const std::string& path) {
|
|
std::vector<std::string> 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<std::string> 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<ApexFile> 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<ApexFile> 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<IApexService> service_;
|
|
sp<android::os::IVold> 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<std::vector<std::string>> ReadEntireDir(const std::string& path) {
|
|
static const auto kAcceptAll = [](auto /*entry*/) { return true; };
|
|
return ReadDir(path, kAcceptAll);
|
|
}
|
|
|
|
Result<std::string> 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<void> ReadDevice(const std::string& block_device) {
|
|
static constexpr int kBlockSize = 4096;
|
|
static constexpr size_t kBufSize = 1024 * kBlockSize;
|
|
std::vector<uint8_t> 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<std::string> 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<std::string> 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<void> 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<DeviceMapper::DmBlockDevice> 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<DeviceMapper::DmBlockDevice> 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<std::string> 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<int> 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 <typename NameProvider>
|
|
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<PrepareTestApexForInstall>(
|
|
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<bool> 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<PrepareTestApexForInstall> 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<ManifestMismatchNameProvider> {
|
|
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<SuccessNameProvider> {};
|
|
|
|
TEST_F(ApexServiceActivationSuccessTest, Activate) {
|
|
ASSERT_TRUE(IsOk(service_->activatePackage(installer_->test_installed_file)))
|
|
<< GetDebugStr(installer_.get());
|
|
|
|
{
|
|
// Check package is active.
|
|
Result<bool> 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<std::string> 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<std::string> versioned_folder_entries =
|
|
collect_entries_fn(versioned_path);
|
|
std::vector<std::string> 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<std::vector<ApexInfo>> 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<uint64_t>(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<ApexInfo> active = GetActivePackage(installer_->package);
|
|
ASSERT_TRUE(IsOk(active));
|
|
|
|
ASSERT_EQ(installer_->package, active->moduleName);
|
|
ASSERT_EQ(installer_->version, static_cast<uint64_t>(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<MountedApexData> 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<NoHashtreeApexNameProvider> {};
|
|
|
|
TEST_F(ApexServiceNoHashtreeApexActivationTest, Activate) {
|
|
ASSERT_TRUE(IsOk(service_->activatePackage(installer_->test_installed_file)))
|
|
<< GetDebugStr(installer_.get());
|
|
{
|
|
// Check package is active.
|
|
Result<bool> 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<bool> 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<MountedApexData> 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<std::string> 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<std::string> 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<uint8_t> {
|
|
static constexpr size_t kBufSize = 4096;
|
|
std::vector<uint8_t> 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<uint8_t> 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<uint8_t> moved_hashtree_data = read_fn(hashtree_path);
|
|
ASSERT_EQ(moved_hashtree_data, original_hashtree_data);
|
|
}
|
|
}
|
|
|
|
TEST_F(ApexServiceTest, GetFactoryPackages) {
|
|
Result<std::vector<ApexInfo>> factory_packages = GetFactoryPackages();
|
|
ASSERT_TRUE(IsOk(factory_packages));
|
|
ASSERT_TRUE(factory_packages->size() > 0);
|
|
|
|
std::vector<std::string> 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<std::vector<ApexInfo>> active_packages = GetActivePackages();
|
|
ASSERT_TRUE(IsOk(active_packages));
|
|
ASSERT_TRUE(active_packages->size() > 0);
|
|
Result<std::vector<ApexInfo>> inactive_packages = GetInactivePackages();
|
|
ASSERT_TRUE(IsOk(inactive_packages));
|
|
std::vector<std::string> active_packages_strings =
|
|
GetPackagesStrings(*active_packages);
|
|
std::vector<std::string> 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<std::string> 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<std::vector<ApexInfo>> all_packages = GetAllPackages();
|
|
ASSERT_TRUE(IsOk(all_packages));
|
|
ASSERT_TRUE(all_packages->size() > 0);
|
|
Result<std::vector<ApexInfo>> active_packages = GetActivePackages();
|
|
std::vector<std::string> active_strings =
|
|
GetPackagesStrings(*active_packages);
|
|
Result<std::vector<ApexInfo>> factory_packages = GetFactoryPackages();
|
|
std::vector<std::string> 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<PrepareTestApexForInstall>(
|
|
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<PrepareTestApexForInstall> installer_;
|
|
};
|
|
|
|
TEST_F(ApexSameGradeOfPreInstalledVersionTest, VersionOnDataWins) {
|
|
std::vector<ApexInfo> 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<PrepareTestApexForInstall> 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<DeviceMapper::DmBlockDevice> devices;
|
|
if (!dm.GetAvailableDevices(&devices)) {
|
|
return Result<bool>(Errorf("GetAvailableDevices failed"));
|
|
}
|
|
for (const auto& device : devices) {
|
|
if (device.name() == name) {
|
|
return Result<bool>(true);
|
|
}
|
|
}
|
|
return Result<bool>(false);
|
|
};
|
|
|
|
#define ASSERT_FIND(type) \
|
|
{ \
|
|
Result<bool> 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<std::string> 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 <typename Fn>
|
|
void RunPrePost(Fn fn, const std::vector<std::string>& apex_names,
|
|
const char* test_message, bool expect_success = true) {
|
|
// Using unique_ptr is just the easiest here.
|
|
using InstallerUPtr = std::unique_ptr<PrepareTestApexForInstall>;
|
|
std::vector<InstallerUPtr> installers;
|
|
std::vector<std::string> 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<bool> active = IsActive(installer->package, installer->version,
|
|
installer->test_file);
|
|
ASSERT_TRUE(IsOk(active));
|
|
EXPECT_FALSE(*active);
|
|
}
|
|
for (const InstallerUPtr& installer : installers) {
|
|
Result<ApexFile> 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<std::string> 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<uint64_t>(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<ApexSessionInfo> 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<ApexSessionInfo> 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<uint64_t>(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<uint64_t>(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<ApexSessionInfo> 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<ApexSessionInfo> 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<SessionState::State> 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<ApexSession> 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<std::string> 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<std::string> 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<std::string> 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<std::string> 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<std::string>& 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<std::string>& 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<ApexSessionInfo> 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<ApexSessionInfo> 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<void> 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<void(pid_t)>& 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<const std::string> 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<std::string> 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<const std::string> 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<std::string> mountpoints;
|
|
for (const auto& line : android::base::Split(mountinfo, "\n")) {
|
|
std::vector<std::string> 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<ApexInfo> 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<NoCodeApexNameProvider> {};
|
|
|
|
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<std::string> 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<std::string> 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<BannedNameProvider> {
|
|
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<ApexFile>& 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();
|
|
}
|