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