[automerger skipped] DO NOT MERGE: Disable apexer_test on sc-dev am: f151f363b8 am: 9d17fac88b -s ours
am skip reason: subject contains skip directive
Original change: https://googleplex-android-review.googlesource.com/c/platform/system/apex/+/16763548
Change-Id: Ic9b390cd73fa8155b4f2dd120fb2dcf2eb6badfc
diff --git a/Android.bp b/Android.bp
index b198f25..e17f64c 100644
--- a/Android.bp
+++ b/Android.bp
@@ -28,7 +28,6 @@
],
}
-// TODO(b/178585590): delete this after testing linking strategy
soong_config_module_type {
name: "library_linking_strategy_apex_defaults",
module_type: "apex_defaults",
diff --git a/apexd/Android.bp b/apexd/Android.bp
index ba2de03..8d6cd8f 100644
--- a/apexd/Android.bp
+++ b/apexd/Android.bp
@@ -148,11 +148,11 @@
"libapexd-deps",
],
srcs: [
+ "apex_classpath.cpp",
"apex_database.cpp",
"apexd.cpp",
"apexd_lifecycle.cpp",
"apexd_loop.cpp",
- "apexd_prepostinstall.cpp",
"apexd_private.cpp",
"apexd_session.cpp",
"apexd_verity.cpp",
@@ -212,6 +212,7 @@
static_libs: [
"lib_apex_session_state_proto",
"lib_apex_manifest_proto",
+ "lib_microdroid_metadata_proto",
"libavb",
],
static: {
@@ -336,6 +337,17 @@
}
genrule {
+ // Extract the root digest with avbtool
+ name: "apex.apexd_test_erofs_digest",
+ out: ["apex.apexd_test_erofs_digest.txt"],
+ srcs: [":apex.apexd_test_erofs"],
+ tools: ["avbtool"],
+ cmd: "unzip -q $(in) -d $(genDir) apex_payload.img && " +
+ "$(location avbtool) print_partition_digests --image $(genDir)/apex_payload.img " +
+ "| cut -c 3-| tee $(out)"
+}
+
+genrule {
// Generates an apex which has same module name as apex.apexd_test.apex, but
// is actually signed with a different key.
name: "gen_key_mismatch_apex",
@@ -414,18 +426,18 @@
],
data: [
":apex.apexd_test",
+ ":apex.apexd_test_erofs",
":apex.apexd_test_f2fs",
":apex.apexd_test_digest",
+ ":apex.apexd_test_erofs_digest",
":apex.apexd_test_f2fs_digest",
+ ":apex.apexd_test_classpath",
":apex.apexd_test_different_app",
":apex.apexd_test_no_hashtree",
":apex.apexd_test_no_hashtree_2",
":apex.apexd_test_no_inst_key",
":apex.apexd_test_f2fs_no_inst_key",
":apex.apexd_test_nocode",
- ":apex.apexd_test_postinstall",
- ":apex.apexd_test_preinstall",
- ":apex.apexd_test_prepostinstall.fail",
":apex.apexd_test_v2",
":apex.corrupted_b146895998",
":apex.banned_name",
@@ -474,6 +486,7 @@
":test.rebootless_apex_priv_app_in_apex",
],
srcs: [
+ "apex_classpath_test.cpp",
"apex_database_test.cpp",
"apex_file_test.cpp",
"apex_file_repository_test.cpp",
@@ -528,3 +541,12 @@
api_dir: "apex-info-list-api",
gen_writer: true,
}
+
+xsd_config {
+ name: "apex-info-list-tinyxml",
+ srcs: ["ApexInfoList.xsd"],
+ package_name: "com.android.apex",
+ api_dir: "apex-info-list-api",
+ gen_writer: true,
+ tinyxml: true,
+}
diff --git a/apexd/AndroidTest.xml b/apexd/AndroidTest.xml
index 6f65f4e..bc2eae6 100644
--- a/apexd/AndroidTest.xml
+++ b/apexd/AndroidTest.xml
@@ -33,8 +33,6 @@
<option name="remount-system" value="true" />
<option name="push" value="apex.apexd_test.apex->/system_ext/apex/apex.apexd_test.apex" />
<option name="push" value="apex.apexd_test_different_app.apex->/system_ext/apex/apex.apexd_test_different_app.apex" />
- <option name="push" value="apex.apexd_test_postinstall.apex->/system_ext/apex/apex.apexd_test_postinstall.apex" />
- <option name="push" value="apex.apexd_test_preinstall.apex->/system_ext/apex/apex.apexd_test_preinstall.apex" />
</target_preparer>
<!-- system_server might still hold a reference to apexservice. This means that apexd is still
diff --git a/apexd/TEST_MAPPING b/apexd/TEST_MAPPING
index 77ff9b8..bcf6133 100644
--- a/apexd/TEST_MAPPING
+++ b/apexd/TEST_MAPPING
@@ -2,6 +2,9 @@
"presubmit": [
{
"name": "ApexTestCases"
+ },
+ {
+ "name": "MicrodroidHostTestCases"
}
],
"imports": [
diff --git a/apexd/aidl/android/apex/ApexInfo.aidl b/apexd/aidl/android/apex/ApexInfo.aidl
index 9cc4d75..47a78d0 100644
--- a/apexd/aidl/android/apex/ApexInfo.aidl
+++ b/apexd/aidl/android/apex/ApexInfo.aidl
@@ -24,4 +24,7 @@
@utf8InCpp String versionName;
boolean isFactory;
boolean isActive;
+
+ // Populated only for getStagedApex() API
+ boolean hasClassPathJars;
}
diff --git a/apexd/aidl/android/apex/IApexService.aidl b/apexd/aidl/android/apex/IApexService.aidl
index 2d77a82..d33970c 100644
--- a/apexd/aidl/android/apex/IApexService.aidl
+++ b/apexd/aidl/android/apex/IApexService.aidl
@@ -29,6 +29,7 @@
ApexSessionInfo[] getSessions();
ApexSessionInfo getStagedSessionInfo(int session_id);
+ ApexInfo[] getStagedApexInfos(in ApexSessionParams params);
ApexInfo[] getActivePackages();
ApexInfo[] getAllPackages();
@@ -75,26 +76,6 @@
* Not meant for use outside of testing. The call will not be
* functional on user builds.
*/
- void activatePackage(in @utf8InCpp String package_path);
- /**
- * Not meant for use outside of testing. The call will not be
- * functional on user builds.
- */
- void deactivatePackage(in @utf8InCpp String package_path);
- /**
- * Not meant for use outside of testing. The call will not be
- * functional on user builds.
- */
- void preinstallPackages(in @utf8InCpp List<String> package_tmp_paths);
- /**
- * Not meant for use outside of testing. The call will not be
- * functional on user builds.
- */
- void postinstallPackages(in @utf8InCpp List<String> package_tmp_paths);
- /**
- * Not meant for use outside of testing. The call will not be
- * functional on user builds.
- */
void stagePackages(in @utf8InCpp List<String> package_tmp_paths);
/**
* Not meant for use outside of testing. The call will not be
diff --git a/apexd/apex_classpath.cpp b/apexd/apex_classpath.cpp
new file mode 100644
index 0000000..68b3fdb
--- /dev/null
+++ b/apexd/apex_classpath.cpp
@@ -0,0 +1,131 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "apex_classpath.h"
+
+#include <android-base/file.h>
+#include <android-base/scopeguard.h>
+#include <android-base/stringprintf.h>
+#include <android-base/strings.h>
+#include <logwrap/logwrap.h>
+
+#include <fstream>
+#include <regex>
+
+namespace android {
+namespace apex {
+
+using ::android::base::Error;
+using ::android::base::StringPrintf;
+
+android::base::Result<ClassPath> ClassPath::DeriveClassPath(
+ const std::vector<std::string>& temp_mounted_apex_paths,
+ const std::string& sdkext_module_name) {
+ if (temp_mounted_apex_paths.empty()) {
+ return Error()
+ << "Invalid argument: There are no APEX to derive claspath from";
+ }
+ // Call derive_classpath binary to generate required information
+
+ // Prefer using the binary from staged session if possible
+ std::string apex_of_binary =
+ StringPrintf("/apex/%s", sdkext_module_name.c_str());
+ for (const auto& temp_mounted_apex_path : temp_mounted_apex_paths) {
+ if (temp_mounted_apex_path.starts_with(apex_of_binary + "@")) {
+ apex_of_binary = temp_mounted_apex_path;
+ break;
+ }
+ }
+ std::string binary_path =
+ StringPrintf("%s/bin/derive_classpath", apex_of_binary.c_str());
+ std::string scan_dirs_flag =
+ StringPrintf("--scan-dirs=%s",
+ android::base::Join(temp_mounted_apex_paths, ",").c_str());
+
+ // Create a temp file to write output
+ auto temp_output_path = "/apex/derive_classpath_temp";
+ auto cleanup = [temp_output_path]() {
+ android::base::RemoveFileIfExists(temp_output_path);
+ };
+ auto scope_guard = android::base::make_scope_guard(cleanup);
+ // Cleanup to ensure we are creating an empty file
+ cleanup();
+ // Create the empty file where derive_classpath will write into
+ std::ofstream _(temp_output_path);
+
+ const char* const argv[] = {binary_path.c_str(), scan_dirs_flag.c_str(),
+ temp_output_path};
+ auto rc = logwrap_fork_execvp(arraysize(argv), argv, nullptr, false, LOG_ALOG,
+ false, nullptr);
+ if (rc != 0) {
+ return Error() << "Running derive_classpath failed; binary path: " +
+ binary_path;
+ }
+
+ return ClassPath::ParseFromFile(temp_output_path);
+}
+
+// Parse the string output into structured information
+// The raw output from derive_classpath has the following format:
+// ```
+// export BOOTCLASSPATH path/to/jar1:/path/to/jar2
+// export DEX2OATBOOTCLASSPATH
+// export SYSTEMSERVERCLASSPATH path/to/some/jar
+android::base::Result<ClassPath> ClassPath::ParseFromFile(
+ const std::string& file_path) {
+ ClassPath result;
+
+ std::string contents;
+ auto read_status = android::base::ReadFileToString(file_path, &contents,
+ /*follow_symlinks=*/false);
+ if (!read_status) {
+ return Error() << "Failed to read classpath info from file";
+ }
+
+ // Jars in apex have the following format: /apex/<package-name>/*
+ const std::regex capture_apex_package_name("^/apex/([^/]+)/");
+
+ for (const auto& line : android::base::Split(contents, "\n")) {
+ // Split the line by space. The second element determines which type of
+ // classpath we are dealing with and the third element are the jars
+ // separated by :
+ auto tokens = android::base::Split(line, " ");
+ if (tokens.size() < 3) {
+ continue;
+ }
+ auto jars_list = tokens[2];
+ for (const auto& jar_path : android::base::Split(jars_list, ":")) {
+ std::smatch match;
+ if (std::regex_search(jar_path, match, capture_apex_package_name)) {
+ auto package_name = match[1];
+ result.AddPackageWithClasspathJars(package_name);
+ }
+ }
+ }
+ return result;
+}
+
+void ClassPath::AddPackageWithClasspathJars(const std::string& package) {
+ packages_with_classpath_jars.insert(package);
+}
+
+bool ClassPath::HasClassPathJars(const std::string& package) {
+ return packages_with_classpath_jars.find(package) !=
+ packages_with_classpath_jars.end();
+}
+
+} // namespace apex
+} // namespace android
diff --git a/apexd/apex_classpath.h b/apexd/apex_classpath.h
new file mode 100644
index 0000000..de3d07f
--- /dev/null
+++ b/apexd/apex_classpath.h
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef ANDROID_APEXD_APEX_CLASSPATH_H_
+#define ANDROID_APEXD_APEX_CLASSPATH_H_
+
+#include <android-base/result.h>
+
+#include <set>
+#include <string>
+
+namespace android {
+namespace apex {
+
+/**
+ * An utility class that contains logic to extract classpath fragments
+ * information from mounted APEX.
+ *
+ * The bulk of the work is done by derive_classpath binary, which is found
+ * inside sdkext module. This class is a wrapper for calling that binary and
+ * parsing its string output into a structured object.
+ */
+class ClassPath {
+ static constexpr const char* kSdkExtModuleName = "com.android.sdkext";
+
+ public:
+ static android::base::Result<ClassPath> DeriveClassPath(
+ const std::vector<std::string>& temp_mounted_apex_paths,
+ const std::string& sdkext_module_name = kSdkExtModuleName);
+
+ bool HasClassPathJars(const std::string& package);
+
+ // Exposed for testing only
+ static android::base::Result<ClassPath> ParseFromFile(
+ const std::string& file_path);
+
+ private:
+ void AddPackageWithClasspathJars(const std::string& package);
+
+ std::set<std::string> packages_with_classpath_jars;
+};
+
+} // namespace apex
+} // namespace android
+
+#endif // ANDROID_APEXD_APEXD_CHECKPOINT_H_
diff --git a/apexd/apex_classpath_test.cpp b/apexd/apex_classpath_test.cpp
new file mode 100644
index 0000000..efa4f3f
--- /dev/null
+++ b/apexd/apex_classpath_test.cpp
@@ -0,0 +1,118 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "apex_classpath.h"
+
+#include <android-base/file.h>
+#include <android-base/result-gmock.h>
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+
+#include <fstream>
+#include <string>
+
+namespace android {
+namespace apex {
+
+using android::base::WriteStringToFile;
+using android::base::testing::Ok;
+using android::base::testing::WithMessage;
+using ::testing::HasSubstr;
+
+TEST(ApexClassPathUnitTest, ParseFromFile) {
+ TemporaryFile output;
+ WriteStringToFile(
+ "export BOOTCLASSPATH /apex/a/jar1:/apex/b/jar2\n"
+ "export SYSTEMSERVERCLASSPATH\n"
+ "export UNEXPECTED /apex/c/\n",
+ output.path);
+ auto result = ClassPath::ParseFromFile(output.path);
+ ASSERT_THAT(result, Ok());
+
+ ASSERT_THAT(result->HasClassPathJars("a"), true);
+ ASSERT_THAT(result->HasClassPathJars("b"), true);
+ ASSERT_THAT(result->HasClassPathJars("c"), true);
+ ASSERT_THAT(result->HasClassPathJars("d"), false);
+}
+
+TEST(ApexClassPathUnitTest, ParseFromFileJarsNotInApex) {
+ TemporaryFile output;
+ // We accept jars with regex: /apex/<package-name>/*
+ WriteStringToFile("export BOOTCLASSPATH a:b\n", output.path);
+ auto result = ClassPath::ParseFromFile(output.path);
+ ASSERT_THAT(result, Ok());
+
+ ASSERT_THAT(result->HasClassPathJars("a"), false);
+ ASSERT_THAT(result->HasClassPathJars("b"), false);
+}
+
+TEST(ApexClassPathUnitTest, ParseFromFilePackagesWithSamePrefix) {
+ TemporaryFile output;
+ WriteStringToFile(
+ "export BOOTCLASSPATH /apex/media/:/apex/mediaprovider\n"
+ "export SYSTEMSERVERCLASSPATH /apex/mediafoo/\n",
+ output.path);
+ auto result = ClassPath::ParseFromFile(output.path);
+ ASSERT_THAT(result, Ok());
+
+ ASSERT_THAT(result->HasClassPathJars("media"), true);
+ // "/apex/mediaprovider" did not end with /
+ ASSERT_THAT(result->HasClassPathJars("mediaprovider"), false);
+ // A prefix of an apex name present should not be accepted
+ ASSERT_THAT(result->HasClassPathJars("m"), false);
+}
+
+TEST(ApexClassPathUnitTest, ParseFromFileDoesNotExist) {
+ auto result = ClassPath::ParseFromFile("/file/does/not/exist");
+ ASSERT_THAT(result, HasError(WithMessage(HasSubstr(
+ "Failed to read classpath info from file"))));
+}
+
+TEST(ApexClassPathUnitTest, ParseFromFileEmptyJars) {
+ TemporaryFile output;
+ WriteStringToFile(
+ "export BOOTCLASSPATH\n"
+ "export SYSTEMSERVERCLASSPATH \n"
+ "export DEX2OATBOOTCLASSPATH \n",
+ output.path);
+ auto result = ClassPath::ParseFromFile(output.path);
+ ASSERT_THAT(result, Ok());
+}
+
+TEST(ApexClassPathUnitTest, DeriveClassPathNoStagedApex) {
+ auto result = ClassPath::DeriveClassPath({});
+ ASSERT_THAT(
+ result,
+ HasError(WithMessage(HasSubstr(
+ "Invalid argument: There are no APEX to derive claspath from"))));
+}
+
+TEST(ApexClassPathUnitTest, DeriveClassPathPreferBinaryInStagedApex) {
+ // Default location uses provided package name to compose binary path
+ auto result = ClassPath::DeriveClassPath({"/apex/temp@123"}, "different");
+ ASSERT_THAT(result,
+ HasError(WithMessage(HasSubstr(
+ "binary path: /apex/different/bin/derive_classpath"))));
+
+ // When staged apex has same package name, we use that location instead
+ result = ClassPath::DeriveClassPath({"/apex/temp@123"}, "temp");
+ ASSERT_THAT(result,
+ HasError(WithMessage(HasSubstr(
+ "binary path: /apex/temp@123/bin/derive_classpath"))));
+}
+
+} // namespace apex
+} // namespace android
diff --git a/apexd/apex_constants.h b/apexd/apex_constants.h
index 798afd5..5f14157 100644
--- a/apexd/apex_constants.h
+++ b/apexd/apex_constants.h
@@ -66,6 +66,14 @@
static constexpr const char* kApexStatusStarting = "starting";
static constexpr const char* kApexStatusActivated = "activated";
static constexpr const char* kApexStatusReady = "ready";
+static constexpr const char* kMultiApexSelectPersistPrefix =
+ "persist.vendor.apex.";
+static constexpr const char* kMultiApexSelectBootconfigPrefix =
+ "ro.boot.vendor.apex.";
+
+static constexpr const char* kVmPayloadMetadataPartitionProp =
+ "apexd.payload_metadata.path";
+static constexpr const std::chrono::seconds kBlockApexWaitTime(10);
// Banned APEX names
static const std::unordered_set<std::string> kBannedApexName = {
diff --git a/apexd/apex_file.cpp b/apexd/apex_file.cpp
index c043def..9ed48aa 100644
--- a/apexd/apex_file.cpp
+++ b/apexd/apex_file.cpp
@@ -16,25 +16,25 @@
#include "apex_file.h"
-#include <fcntl.h>
-#include <sys/stat.h>
-#include <sys/types.h>
-#include <unistd.h>
-
-#include <filesystem>
-#include <fstream>
-#include <span>
-
#include <android-base/file.h>
#include <android-base/logging.h>
#include <android-base/scopeguard.h>
#include <android-base/strings.h>
#include <android-base/unique_fd.h>
+#include <fcntl.h>
#include <libavb/libavb.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <unistd.h>
#include <ziparchive/zip_archive.h>
+#include <filesystem>
+#include <fstream>
+#include <span>
+
#include "apex_constants.h"
#include "apexd_utils.h"
+#include "apexd_verity.h"
using android::base::borrowed_fd;
using android::base::ErrnoError;
@@ -60,9 +60,10 @@
const char* magic;
};
constexpr const FsMagic kFsType[] = {{"f2fs", 1024, 4, "\x10\x20\xf5\xf2"},
- {"ext4", 1024 + 0x38, 2, "\123\357"}};
+ {"ext4", 1024 + 0x38, 2, "\123\357"},
+ {"erofs", 1024, 4, "\xe2\xe1\xf5\xe0"}};
-Result<std::string> RetrieveFsType(borrowed_fd fd, int32_t image_offset) {
+Result<std::string> RetrieveFsType(borrowed_fd fd, uint32_t image_offset) {
for (const auto& fs : kFsType) {
char buf[fs.len];
if (!ReadFullyAtOffset(fd, buf, fs.len, image_offset + fs.offset)) {
@@ -78,7 +79,7 @@
} // namespace
Result<ApexFile> ApexFile::Open(const std::string& path) {
- std::optional<int32_t> image_offset;
+ std::optional<uint32_t> image_offset;
std::optional<size_t> image_size;
std::string manifest_content;
std::string pubkey;
@@ -87,14 +88,15 @@
unique_fd fd(open(path.c_str(), O_RDONLY | O_BINARY | O_CLOEXEC));
if (fd < 0) {
- return Error() << "Failed to open package " << path << ": "
- << "I/O error";
+ return ErrnoError() << "Failed to open package " << path << ": "
+ << "I/O error";
}
ZipArchiveHandle handle;
auto handle_guard =
android::base::make_scope_guard([&handle] { CloseArchive(handle); });
- int ret = OpenArchiveFd(fd.get(), path.c_str(), &handle, false);
+ int ret = OpenArchiveFd(fd.get(), path.c_str(), &handle,
+ /*assume_ownership=*/false);
if (ret < 0) {
return Error() << "Failed to open package " << path << ": "
<< ErrorCodeString(ret);
@@ -181,16 +183,6 @@
static constexpr int kVbMetaMaxSize = 64 * 1024;
-std::string BytesToHex(const uint8_t* bytes, size_t bytes_len) {
- std::ostringstream s;
-
- s << std::hex << std::setfill('0');
- for (size_t i = 0; i < bytes_len; i++) {
- s << std::setw(2) << static_cast<int>(bytes[i]);
- }
- return s.str();
-}
-
std::string GetSalt(const AvbHashtreeDescriptor& desc,
const uint8_t* trailing_data) {
const uint8_t* desc_salt = trailing_data + desc.partition_name_len;
diff --git a/apexd/apex_file.h b/apexd/apex_file.h
index 60d5feb..56077bb 100644
--- a/apexd/apex_file.h
+++ b/apexd/apex_file.h
@@ -42,12 +42,15 @@
class ApexFile {
public:
static android::base::Result<ApexFile> Open(const std::string& path);
+
ApexFile() = delete;
ApexFile(ApexFile&&) = default;
ApexFile& operator=(ApexFile&&) = default;
const std::string& GetPath() const { return apex_path_; }
- const std::optional<int32_t>& GetImageOffset() const { return image_offset_; }
+ const std::optional<uint32_t>& GetImageOffset() const {
+ return image_offset_;
+ }
const std::optional<size_t>& GetImageSize() const { return image_size_; }
const ::apex::proto::ApexManifest& GetManifest() const { return manifest_; }
const std::string& GetBundledPublicKey() const { return apex_pubkey_; }
@@ -59,7 +62,7 @@
private:
ApexFile(const std::string& apex_path,
- const std::optional<int32_t>& image_offset,
+ const std::optional<uint32_t>& image_offset,
const std::optional<size_t>& image_size,
::apex::proto::ApexManifest manifest, const std::string& apex_pubkey,
const std::optional<std::string>& fs_type, bool is_compressed)
@@ -72,7 +75,7 @@
is_compressed_(is_compressed) {}
std::string apex_path_;
- std::optional<int32_t> image_offset_;
+ std::optional<uint32_t> image_offset_;
std::optional<size_t> image_size_;
::apex::proto::ApexManifest manifest_;
std::string apex_pubkey_;
diff --git a/apexd/apex_file_repository.cpp b/apexd/apex_file_repository.cpp
index c50d136..e82feb4 100644
--- a/apexd/apex_file_repository.cpp
+++ b/apexd/apex_file_repository.cpp
@@ -18,17 +18,19 @@
#include "apex_file_repository.h"
-#include <unordered_map>
-
#include <android-base/file.h>
#include <android-base/properties.h>
#include <android-base/result.h>
#include <android-base/stringprintf.h>
#include <android-base/strings.h>
+#include <microdroid/metadata.h>
+
+#include <unordered_map>
#include "apex_constants.h"
#include "apex_file.h"
#include "apexd_utils.h"
+#include "apexd_verity.h"
using android::base::EndsWith;
using android::base::Error;
@@ -38,6 +40,24 @@
namespace android {
namespace apex {
+std::string ConsumeApexPackageSuffix(const std::string& path) {
+ std::string_view path_view(path);
+ android::base::ConsumeSuffix(&path_view, kApexPackageSuffix);
+ android::base::ConsumeSuffix(&path_view, kCompressedApexPackageSuffix);
+ return std::string(path_view);
+}
+
+std::string GetApexSelectFilenameFromProp(
+ const std::vector<std::string>& prefixes, const std::string& apex_name) {
+ for (const std::string& prefix : prefixes) {
+ const std::string& filename = GetProperty(prefix + apex_name, "");
+ if (filename != "") {
+ return ConsumeApexPackageSuffix(filename);
+ }
+ }
+ return "";
+}
+
Result<void> ApexFileRepository::ScanBuiltInDir(const std::string& dir) {
LOG(INFO) << "Scanning " << dir << " for pre-installed ApexFiles";
if (access(dir.c_str(), F_OK) != 0 && errno == ENOENT) {
@@ -60,6 +80,59 @@
}
const std::string& name = apex_file->GetManifest().name();
+
+ // Check if this APEX name is treated as a multi-install APEX.
+ //
+ // Note: apexd is a oneshot service which runs at boot, but can be restarted
+ // when needed (such as staging an APEX update). If a multi-install select
+ // property changes between boot and when apexd restarts, the LOG messages
+ // below will report the version that will be activated on next reboot,
+ // which may differ from the currently-active version.
+ std::string select_filename = GetApexSelectFilenameFromProp(
+ multi_install_select_prop_prefixes_, name);
+ if (!select_filename.empty()) {
+ std::string path;
+ if (!android::base::Realpath(apex_file->GetPath(), &path)) {
+ LOG(ERROR) << "Unable to resolve realpath of APEX with path "
+ << apex_file->GetPath();
+ continue;
+ }
+ if (enforce_multi_install_partition_ &&
+ !android::base::StartsWith(path, "/vendor/apex/")) {
+ LOG(ERROR) << "Multi-install APEX " << path
+ << " can only be preinstalled on /vendor/apex/.";
+ continue;
+ }
+
+ auto& keys = multi_install_public_keys_[name];
+ keys.insert(apex_file->GetBundledPublicKey());
+ if (keys.size() > 1) {
+ LOG(ERROR) << "Multi-install APEXes for " << name
+ << " have different public keys.";
+ // If any versions of a multi-installed APEX differ in public key,
+ // then no version should be installed.
+ if (auto it = pre_installed_store_.find(name);
+ it != pre_installed_store_.end()) {
+ pre_installed_store_.erase(it);
+ }
+ continue;
+ }
+
+ if (ConsumeApexPackageSuffix(android::base::Basename(path)) ==
+ select_filename) {
+ LOG(INFO) << "Found APEX at path " << path << " for multi-install APEX "
+ << name;
+ // Add the APEX file to the store if its filename matches the property.
+ pre_installed_store_.emplace(name, std::move(*apex_file));
+ } else {
+ LOG(INFO) << "Skipping APEX at path " << path
+ << " because it does not match expected multi-install"
+ << " APEX property for " << name;
+ }
+
+ continue;
+ }
+
auto it = pre_installed_store_.find(name);
if (it == pre_installed_store_.end()) {
pre_installed_store_.emplace(name, std::move(*apex_file));
@@ -84,6 +157,7 @@
<< " (" << name << ") has unexpectedly changed";
}
}
+ multi_install_public_keys_.clear();
return {};
}
@@ -102,6 +176,130 @@
return {};
}
+Result<int> ApexFileRepository::AddBlockApex(
+ const std::string& metadata_partition) {
+ CHECK(!block_disk_path_.has_value())
+ << "AddBlockApex() can't be called twice.";
+
+ auto metadata_ready = WaitForFile(metadata_partition, kBlockApexWaitTime);
+ if (!metadata_ready.ok()) {
+ LOG(ERROR) << "Error waiting for metadata_partition : "
+ << metadata_ready.error();
+ return {};
+ }
+
+ // TODO(b/185069443) consider moving the logic to find disk_path from
+ // metadata_partition to its own library
+ LOG(INFO) << "Scanning " << metadata_partition << " for host apexes";
+ if (access(metadata_partition.c_str(), F_OK) != 0 && errno == ENOENT) {
+ LOG(WARNING) << metadata_partition << " does not exist. Skipping";
+ return {};
+ }
+
+ std::string metadata_realpath;
+ if (!android::base::Realpath(metadata_partition, &metadata_realpath)) {
+ LOG(WARNING) << "Can't get realpath of " << metadata_partition
+ << ". Skipping";
+ return {};
+ }
+
+ std::string_view metadata_path_view(metadata_realpath);
+ if (!android::base::ConsumeSuffix(&metadata_path_view, "1")) {
+ LOG(WARNING) << metadata_realpath << " is not a first partition. Skipping";
+ return {};
+ }
+
+ block_disk_path_ = std::string(metadata_path_view);
+
+ // Read the payload metadata.
+ // "metadata" can be overridden by microdroid_manager. To ensure that
+ // "microdroid" is started with the same/unmodified set of host APEXes,
+ // microdroid stores APEXes' pubkeys in its encrypted instance disk. Next
+ // time, microdroid checks if there's pubkeys in the instance disk and use
+ // them to activate APEXes. Microdroid_manager passes pubkeys in instance.img
+ // via the following file.
+ if (auto exists = PathExists("/apex/vm-payload-metadata");
+ exists.ok() && *exists) {
+ metadata_realpath = "/apex/vm-payload-metadata";
+ LOG(INFO) << "Overriding metadata to " << metadata_realpath;
+ }
+ auto metadata = android::microdroid::ReadMetadata(metadata_realpath);
+ if (!metadata.ok()) {
+ LOG(WARNING) << "Failed to load metadata from " << metadata_realpath
+ << ". Skipping: " << metadata.error();
+ return {};
+ }
+
+ int ret = 0;
+
+ // subsequent partitions are APEX archives.
+ static constexpr const int kFirstApexPartition = 2;
+ for (int i = 0; i < metadata->apexes_size(); i++) {
+ const auto& apex_config = metadata->apexes(i);
+
+ const std::string apex_path =
+ *block_disk_path_ + std::to_string(i + kFirstApexPartition);
+
+ auto apex_ready = WaitForFile(apex_path, kBlockApexWaitTime);
+ if (!apex_ready.ok()) {
+ return Error() << "Error waiting for apex file : " << apex_ready.error();
+ }
+
+ auto apex_file = ApexFile::Open(apex_path);
+ if (!apex_file.ok()) {
+ return Error() << "Failed to open " << apex_path << " : "
+ << apex_file.error();
+ }
+
+ // When metadata specifies the public key of the apex, it should match the
+ // bundled key. Otherwise we accept it.
+ if (apex_config.public_key() != "" &&
+ apex_config.public_key() != apex_file->GetBundledPublicKey()) {
+ return Error() << "public key doesn't match: " << apex_path;
+ }
+
+ const std::string& name = apex_file->GetManifest().name();
+
+ BlockApexOverride overrides;
+
+ // A block device doesn't have an inherent timestamp, so it is carried in
+ // the metadata.
+ if (int64_t last_update_seconds = apex_config.last_update_seconds();
+ last_update_seconds != 0) {
+ overrides.last_update_seconds = last_update_seconds;
+ }
+
+ // When metadata specifies the root digest of the apex, it should be used
+ // when activating the apex. So we need to keep it.
+ if (auto root_digest = apex_config.root_digest(); root_digest != "") {
+ overrides.block_apex_root_digest =
+ BytesToHex(reinterpret_cast<const uint8_t*>(root_digest.data()),
+ root_digest.size());
+ }
+
+ if (overrides.last_update_seconds.has_value() ||
+ overrides.block_apex_root_digest.has_value()) {
+ block_apex_overrides_.emplace(name, std::move(overrides));
+ }
+
+ // APEX should be unique.
+ for (const auto* store : {&pre_installed_store_, &data_store_}) {
+ auto it = store->find(name);
+ if (it != store->end()) {
+ return Error() << "duplicate of " << name << " found in "
+ << it->second.GetPath();
+ }
+ }
+ // Depending on whether the APEX was a factory version in the host or not,
+ // put it to different stores.
+ auto& store = apex_config.is_factory() ? pre_installed_store_ : data_store_;
+ store.emplace(name, std::move(*apex_file));
+
+ ret++;
+ }
+ return {ret};
+}
+
// TODO(b/179497746): AddDataApex should not concern with filtering out invalid
// apex.
Result<void> ApexFileRepository::AddDataApex(const std::string& data_dir) {
@@ -128,10 +326,19 @@
const std::string& name = apex_file->GetManifest().name();
if (!HasPreInstalledVersion(name)) {
- LOG(ERROR) << "Skipping " << file << " : no preisntalled apex";
+ LOG(ERROR) << "Skipping " << file << " : no preinstalled apex";
// Ignore data apex without corresponding pre-installed apex
continue;
}
+
+ std::string select_filename = GetApexSelectFilenameFromProp(
+ multi_install_select_prop_prefixes_, name);
+ if (!select_filename.empty()) {
+ LOG(WARNING) << "APEX " << name << " is a multi-installed APEX."
+ << " Any updated version in /data will always overwrite"
+ << " the multi-installed preinstalled version, if possible.";
+ }
+
auto pre_installed_public_key = GetPublicKey(name);
if (!pre_installed_public_key.ok() ||
apex_file->GetBundledPublicKey() != *pre_installed_public_key) {
@@ -172,6 +379,14 @@
const std::string& name) const {
auto it = pre_installed_store_.find(name);
if (it == pre_installed_store_.end()) {
+ // Special casing for APEXes backed by block devices, i.e. APEXes in VM.
+ // Inside a VM, we fall back to find the key from data_store_. This is
+ // because an APEX is put to either pre_installed_store_ or data_store,
+ // depending on whether it was a factory APEX or not in the host.
+ it = data_store_.find(name);
+ if (it != data_store_.end() && IsBlockApex(it->second)) {
+ return it->second.GetBundledPublicKey();
+ }
return Error() << "No preinstalled apex found for package " << name;
}
return it->second.GetBundledPublicKey();
@@ -199,6 +414,24 @@
return it->second.GetPath();
}
+std::optional<std::string> ApexFileRepository::GetBlockApexRootDigest(
+ const std::string& name) const {
+ auto it = block_apex_overrides_.find(name);
+ if (it == block_apex_overrides_.end()) {
+ return std::nullopt;
+ }
+ return it->second.block_apex_root_digest;
+}
+
+std::optional<int64_t> ApexFileRepository::GetBlockApexLastUpdateSeconds(
+ const std::string& name) const {
+ auto it = block_apex_overrides_.find(name);
+ if (it == block_apex_overrides_.end()) {
+ return std::nullopt;
+ }
+ return it->second.last_update_seconds;
+}
+
bool ApexFileRepository::HasPreInstalledVersion(const std::string& name) const {
return pre_installed_store_.find(name) != pre_installed_store_.end();
}
@@ -221,6 +454,11 @@
return it->second.GetPath() == apex.GetPath() || IsDecompressedApex(apex);
}
+bool ApexFileRepository::IsBlockApex(const ApexFile& apex) const {
+ return block_disk_path_.has_value() &&
+ apex.GetPath().starts_with(*block_disk_path_);
+}
+
std::vector<ApexFileRef> ApexFileRepository::GetPreInstalledApexFiles() const {
std::vector<ApexFileRef> result;
for (const auto& it : pre_installed_store_) {
diff --git a/apexd/apex_file_repository.h b/apexd/apex_file_repository.h
index e9ccf7e..1a1cf94 100644
--- a/apexd/apex_file_repository.h
+++ b/apexd/apex_file_repository.h
@@ -16,15 +16,18 @@
#pragma once
+#include <android-base/result.h>
+
#include <functional>
+#include <optional>
#include <string>
#include <unordered_map>
+#include <unordered_set>
#include <vector>
+
#include "apex_constants.h"
#include "apex_file.h"
-#include <android-base/result.h>
-
namespace android {
namespace apex {
@@ -39,10 +42,15 @@
// mounts apexes (e.g. apexd, otapreopt_chroot).
class ApexFileRepository final {
public:
- // c-tor and d-tor are exposed for testing.
+ // c-tors and d-tor are exposed for testing.
explicit ApexFileRepository(
const std::string& decompression_dir = kApexDecompressedDir)
: decompression_dir_(decompression_dir){};
+ explicit ApexFileRepository(
+ bool enforce_multi_install_partition,
+ const std::vector<std::string>& multi_install_select_prop_prefixes)
+ : multi_install_select_prop_prefixes_(multi_install_select_prop_prefixes),
+ enforce_multi_install_partition_(enforce_multi_install_partition){};
~ApexFileRepository() {
pre_installed_store_.clear();
@@ -60,6 +68,24 @@
android::base::Result<void> AddPreInstalledApex(
const std::vector<std::string>& prebuilt_dirs);
+ // Populate instance by collecting host-provided apex files via
+ // |metadata_partition|. Host can provide its apexes to a VM instance via the
+ // virtual disk image which has partitions: (see
+ // /packages/modules/Virtualization/microdroid for the details)
+ // - metadata partition(/dev/block/vd*1) should be accessed by
+ // setting the system property apexd.payload_metadata.prop. On microdroid,
+ // this is /dev/block/by-name/payload-metadata.
+ // - each subsequence partition(/dev/block/vd*{2,3,..}) represents an APEX
+ // archive.
+ // It will fail if there is more than one apex with the same name in
+ // pre-installed and block apexes. Note: this call is **not thread safe** and
+ // is expected to be performed in a single thread during initialization of
+ // apexd. After initialization is finished, all queries to the instance are
+ // thread safe.
+ // This will return the number of block apexes that were added.
+ android::base::Result<int> AddBlockApex(
+ const std::string& metadata_partition);
+
// Populate instance by collecting data apex files from the given |data_dir|.
// Note: this call is **not thread safe** and is expected to be performed in a
// single thread during initialization of apexd. After initialization is
@@ -78,6 +104,14 @@
android::base::Result<const std::string> GetDataPath(
const std::string& name) const;
+ // Returns root digest of an apex with the given |name| for block apexes.
+ std::optional<std::string> GetBlockApexRootDigest(
+ const std::string& name) const;
+
+ // Returns timestamp to be used for the block apex of the given |name|.
+ std::optional<int64_t> GetBlockApexLastUpdateSeconds(
+ const std::string& name) const;
+
// Checks whether there is a pre-installed version of an apex with the given
// |name|.
bool HasPreInstalledVersion(const std::string& name) const;
@@ -91,6 +125,9 @@
// Checks if given |apex| is decompressed from a pre-installed APEX
bool IsDecompressedApex(const ApexFile& apex) const;
+ // Checks if given |apex| is loaded from block device.
+ bool IsBlockApex(const ApexFile& apex) const;
+
// Returns reference to all pre-installed APEX on device
std::vector<ApexFileRef> GetPreInstalledApexFiles() const;
@@ -115,7 +152,9 @@
void Reset(const std::string& decompression_dir = kApexDecompressedDir) {
pre_installed_store_.clear();
data_store_.clear();
+ block_apex_overrides_.clear();
decompression_dir_ = decompression_dir;
+ block_disk_path_.reset();
}
private:
@@ -130,9 +169,41 @@
android::base::Result<void> ScanBuiltInDir(const std::string& dir);
std::unordered_map<std::string, ApexFile> pre_installed_store_, data_store_;
+
+ // Multi-installed APEX name -> all encountered public keys for this APEX.
+ std::unordered_map<std::string, std::unordered_set<std::string>>
+ multi_install_public_keys_;
+
+ // Prefixes used when looking for multi-installed APEX sysprops.
+ // Order matters: the first non-empty prop value is returned.
+ std::vector<std::string> multi_install_select_prop_prefixes_ = {
+ // Check persist props first, to allow users to override bootconfig.
+ kMultiApexSelectPersistPrefix,
+ kMultiApexSelectBootconfigPrefix,
+ };
+
+ // Allows multi-install APEXes outside of expected partitions.
+ // Only set false in tests.
+ bool enforce_multi_install_partition_ = true;
+
// Decompression directory which will be used to determine if apex is
// decompressed or not
std::string decompression_dir_;
+
+ // Disk path where block apexes are read from. AddBlockApex() sets this.
+ std::optional<std::string> block_disk_path_;
+
+ // Information from the metadata for block apexes, overriding the file data.
+ struct BlockApexOverride {
+ // Root digest for the APEX. When specified in block apex config, it
+ // should be used/checked when activating the apex to avoid
+ // TOCTOU(time-of-check to time-of-use).
+ std::optional<std::string> block_apex_root_digest;
+ // The last update time of the APEX.
+ std::optional<int64_t> last_update_seconds;
+ };
+
+ std::unordered_map<std::string, BlockApexOverride> block_apex_overrides_;
};
} // namespace apex
diff --git a/apexd/apex_file_repository_test.cpp b/apexd/apex_file_repository_test.cpp
index c1f5d53..bf73092 100644
--- a/apexd/apex_file_repository_test.cpp
+++ b/apexd/apex_file_repository_test.cpp
@@ -14,20 +14,22 @@
* limitations under the License.
*/
-#include <filesystem>
-#include <string>
-
-#include <errno.h>
-#include <sys/stat.h>
+#include "apex_file_repository.h"
#include <android-base/file.h>
#include <android-base/logging.h>
+#include <android-base/properties.h>
#include <android-base/stringprintf.h>
+#include <errno.h>
#include <gmock/gmock.h>
#include <gtest/gtest.h>
+#include <microdroid/metadata.h>
+#include <sys/stat.h>
+
+#include <filesystem>
+#include <string>
#include "apex_file.h"
-#include "apex_file_repository.h"
#include "apexd_test_utils.h"
#include "apexd_verity.h"
@@ -160,6 +162,90 @@
"");
}
+TEST(ApexFileRepositoryTest, InitializeMultiInstalledSuccess) {
+ // Prepare test data.
+ TemporaryDir td;
+ std::string apex_file = GetTestFile("apex.apexd_test.apex");
+ fs::copy(apex_file, StringPrintf("%s/version_a.apex", td.path));
+ fs::copy(apex_file, StringPrintf("%s/version_b.apex", td.path));
+ std::string apex_name = ApexFile::Open(apex_file)->GetManifest().name();
+
+ std::string persist_prefix = "debug.apexd.test.persistprefix.";
+ std::string bootconfig_prefix = "debug.apexd.test.bootconfigprefix.";
+ ApexFileRepository instance(/*enforce_multi_install_partition=*/false,
+ /*multi_install_select_prop_prefixes=*/{
+ persist_prefix, bootconfig_prefix});
+
+ auto test_fn = [&](const std::string& selected_filename) {
+ ASSERT_TRUE(IsOk(instance.AddPreInstalledApex({td.path})));
+ auto ret = instance.GetPreinstalledPath(apex_name);
+ ASSERT_TRUE(IsOk(ret));
+ ASSERT_EQ(StringPrintf("%s/%s", td.path, selected_filename.c_str()), *ret);
+ instance.Reset();
+ };
+
+ // Start with version_a in bootconfig.
+ android::base::SetProperty(bootconfig_prefix + apex_name, "version_a.apex");
+ test_fn("version_a.apex");
+ // Developer chooses version_b with persist prop.
+ android::base::SetProperty(persist_prefix + apex_name, "version_b.apex");
+ test_fn("version_b.apex");
+ // Developer goes back to version_a with persist prop.
+ android::base::SetProperty(persist_prefix + apex_name, "version_a.apex");
+ test_fn("version_a.apex");
+
+ android::base::SetProperty(persist_prefix + apex_name, "");
+ android::base::SetProperty(bootconfig_prefix + apex_name, "");
+}
+
+TEST(ApexFileRepositoryTest, InitializeMultiInstalledSkipsForDifferingKeys) {
+ // Prepare test data.
+ TemporaryDir td;
+ fs::copy(GetTestFile("apex.apexd_test.apex"),
+ StringPrintf("%s/version_a.apex", td.path));
+ fs::copy(GetTestFile("apex.apexd_test_different_key.apex"),
+ StringPrintf("%s/version_b.apex", td.path));
+ std::string apex_name =
+ ApexFile::Open(GetTestFile("apex.apexd_test.apex"))->GetManifest().name();
+ std::string prop_prefix = "debug.apexd.test.bootconfigprefix.";
+ std::string prop = prop_prefix + apex_name;
+ android::base::SetProperty(prop, "version_a.apex");
+
+ ApexFileRepository instance(
+ /*enforce_multi_install_partition=*/false,
+ /*multi_install_select_prop_prefixes=*/{prop_prefix});
+ ASSERT_TRUE(IsOk(instance.AddPreInstalledApex({td.path})));
+ // Neither version should be have been installed.
+ ASSERT_FALSE(IsOk(instance.GetPreinstalledPath(apex_name)));
+
+ android::base::SetProperty(prop, "");
+}
+
+TEST(ApexFileRepositoryTest, InitializeMultiInstalledSkipsForInvalidPartition) {
+ // Prepare test data.
+ TemporaryDir td;
+ // Note: These test files are on /data, which is not a valid partition for
+ // multi-installed APEXes.
+ fs::copy(GetTestFile("apex.apexd_test.apex"),
+ StringPrintf("%s/version_a.apex", td.path));
+ fs::copy(GetTestFile("apex.apexd_test.apex"),
+ StringPrintf("%s/version_b.apex", td.path));
+ std::string apex_name =
+ ApexFile::Open(GetTestFile("apex.apexd_test.apex"))->GetManifest().name();
+ std::string prop_prefix = "debug.apexd.test.bootconfigprefix.";
+ std::string prop = prop_prefix + apex_name;
+ android::base::SetProperty(prop, "version_a.apex");
+
+ ApexFileRepository instance(
+ /*enforce_multi_install_partition=*/true,
+ /*multi_install_select_prop_prefixes=*/{prop_prefix});
+ ASSERT_TRUE(IsOk(instance.AddPreInstalledApex({td.path})));
+ // Neither version should be have been installed.
+ ASSERT_FALSE(IsOk(instance.GetPreinstalledPath(apex_name)));
+
+ android::base::SetProperty(prop, "");
+}
+
TEST(ApexFileRepositoryTest,
InitializeSameNameDifferentPathAbortsCompressedApex) {
// Prepare test data.
@@ -502,5 +588,236 @@
"");
}
+struct ApexFileRepositoryTestAddBlockApex : public ::testing::Test {
+ TemporaryDir test_dir;
+
+ struct PayloadMetadata {
+ android::microdroid::Metadata metadata;
+ std::string path;
+ PayloadMetadata(const std::string& path) : path(path) {}
+ PayloadMetadata& apex(const std::string& name,
+ const std::string& public_key = "",
+ const std::string& root_digest = "",
+ int64_t last_update_seconds = 0,
+ bool is_factory = true) {
+ auto apex = metadata.add_apexes();
+ apex->set_name(name);
+ apex->set_public_key(public_key);
+ apex->set_root_digest(root_digest);
+ apex->set_last_update_seconds(last_update_seconds);
+ apex->set_is_factory(is_factory);
+ return *this;
+ }
+ ~PayloadMetadata() {
+ metadata.set_version(1);
+ std::ofstream out(path);
+ android::microdroid::WriteMetadata(metadata, out);
+ }
+ };
+};
+
+TEST_F(ApexFileRepositoryTestAddBlockApex,
+ ScansPayloadDisksAndAddApexFilesToPreInstalled) {
+ // prepare payload disk
+ // <test-dir>/vdc1 : metadata
+ // /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 metadata_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;
+
+ PayloadMetadata(metadata_partition_path)
+ .apex(test_apex_foo)
+ .apex(test_apex_bar);
+ auto loop_device1 = WriteBlockApex(test_apex_foo, apex_foo_path);
+ auto loop_device2 = WriteBlockApex(test_apex_bar, apex_bar_path);
+
+ // call ApexFileRepository::AddBlockApex()
+ ApexFileRepository instance;
+ auto status = instance.AddBlockApex(metadata_partition_path);
+ ASSERT_RESULT_OK(status);
+
+ auto apex_foo = ApexFile::Open(apex_foo_path);
+ ASSERT_RESULT_OK(apex_foo);
+ // block apexes can be identified with IsBlockApex
+ ASSERT_TRUE(instance.IsBlockApex(*apex_foo));
+
+ // "block" apexes are treated as "pre-installed"
+ 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,
+ ScansOnlySpecifiedInMetadataPartition) {
+ // prepare payload disk
+ // <test-dir>/vdc1 : metadata 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 metadata_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;
+
+ // metadata lists only "foo"
+ PayloadMetadata(metadata_partition_path).apex(test_apex_foo);
+ auto loop_device1 = WriteBlockApex(test_apex_foo, apex_foo_path);
+ auto loop_device2 = WriteBlockApex(test_apex_bar, apex_bar_path);
+
+ // call ApexFileRepository::AddBlockApex()
+ ApexFileRepository instance;
+ auto status = instance.AddBlockApex(metadata_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 : metadata with v1 and v2 of apex.apexd_test
+ // /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 metadata_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;
+
+ PayloadMetadata(metadata_partition_path)
+ .apex(test_apex_foo)
+ .apex(test_apex_bar);
+ auto loop_device1 = WriteBlockApex(test_apex_foo, apex_foo_path);
+ auto loop_device2 = WriteBlockApex(test_apex_bar, apex_bar_path);
+
+ ApexFileRepository instance;
+ auto status = instance.AddBlockApex(metadata_partition_path);
+ ASSERT_FALSE(IsOk(status));
+}
+
+TEST_F(ApexFileRepositoryTestAddBlockApex, GetBlockApexRootDigest) {
+ // prepare payload disk with root digest
+ // <test-dir>/vdc1 : metadata with apex.apexd_test.apex only
+ // /vdc2 : apex.apexd_test.apex
+
+ const auto& test_apex_foo = GetTestFile("apex.apexd_test.apex");
+
+ const std::string metadata_partition_path = test_dir.path + "/vdc1"s;
+ const std::string apex_foo_path = test_dir.path + "/vdc2"s;
+
+ // root digest is stored as bytes in metadata and as hexadecimal in
+ // ApexFileRepository
+ const std::string root_digest = "root_digest";
+ const std::string hex_root_digest = BytesToHex(
+ reinterpret_cast<const uint8_t*>(root_digest.data()), root_digest.size());
+
+ // metadata lists "foo"
+ PayloadMetadata(metadata_partition_path)
+ .apex(test_apex_foo, /*public_key=*/"", root_digest);
+ auto loop_device1 = WriteBlockApex(test_apex_foo, apex_foo_path);
+
+ // call ApexFileRepository::AddBlockApex()
+ ApexFileRepository instance;
+ auto status = instance.AddBlockApex(metadata_partition_path);
+ ASSERT_TRUE(IsOk(status));
+
+ ASSERT_EQ(hex_root_digest,
+ instance.GetBlockApexRootDigest("com.android.apex.test_package"));
+}
+
+TEST_F(ApexFileRepositoryTestAddBlockApex, GetBlockApexLastUpdateSeconds) {
+ // prepare payload disk with last update time
+ // <test-dir>/vdc1 : metadata with apex.apexd_test.apex only
+ // /vdc2 : apex.apexd_test.apex
+
+ const auto& test_apex_foo = GetTestFile("apex.apexd_test.apex");
+
+ const std::string metadata_partition_path = test_dir.path + "/vdc1"s;
+ const std::string apex_foo_path = test_dir.path + "/vdc2"s;
+
+ const int64_t last_update_seconds = 123456789;
+
+ // metadata lists "foo"
+ PayloadMetadata(metadata_partition_path)
+ .apex(test_apex_foo, /*public_key=*/"", /*root_digest=*/"",
+ last_update_seconds);
+ auto loop_device1 = WriteBlockApex(test_apex_foo, apex_foo_path);
+
+ // call ApexFileRepository::AddBlockApex()
+ ApexFileRepository instance;
+ auto status = instance.AddBlockApex(metadata_partition_path);
+ ASSERT_TRUE(IsOk(status));
+
+ ASSERT_EQ(last_update_seconds, instance.GetBlockApexLastUpdateSeconds(
+ "com.android.apex.test_package"));
+}
+
+TEST_F(ApexFileRepositoryTestAddBlockApex, VerifyPublicKeyWhenAddingBlockApex) {
+ // prepare payload disk
+ // <test-dir>/vdc1 : metadata with apex.apexd_test.apex only
+ // /vdc2 : apex.apexd_test.apex
+
+ const auto& test_apex_foo = GetTestFile("apex.apexd_test.apex");
+
+ const std::string metadata_partition_path = test_dir.path + "/vdc1"s;
+ const std::string apex_foo_path = test_dir.path + "/vdc2"s;
+
+ // metadata lists "foo"
+ PayloadMetadata(metadata_partition_path)
+ .apex(test_apex_foo, /*public_key=*/"wrong public key");
+ auto loop_device1 = WriteBlockApex(test_apex_foo, apex_foo_path);
+
+ // call ApexFileRepository::AddBlockApex()
+ ApexFileRepository instance;
+ auto status = instance.AddBlockApex(metadata_partition_path);
+ ASSERT_FALSE(IsOk(status));
+}
+
+TEST_F(ApexFileRepositoryTestAddBlockApex, RespectIsFactoryBitFromMetadata) {
+ // prepare payload disk
+ // <test-dir>/vdc1 : metadata with apex.apexd_test.apex only
+ // /vdc2 : apex.apexd_test.apex
+
+ const auto& test_apex_foo = GetTestFile("apex.apexd_test.apex");
+
+ const std::string metadata_partition_path = test_dir.path + "/vdc1"s;
+ const std::string apex_foo_path = test_dir.path + "/vdc2"s;
+ auto loop_device1 = WriteBlockApex(test_apex_foo, apex_foo_path);
+
+ for (const bool is_factory : {true, false}) {
+ // metadata lists "foo"
+ PayloadMetadata(metadata_partition_path)
+ .apex(test_apex_foo, /*public_key=*/"", /*root_digest=*/"",
+ /*last_update_seconds=*/0, is_factory);
+
+ // call ApexFileRepository::AddBlockApex()
+ ApexFileRepository instance;
+ auto status = instance.AddBlockApex(metadata_partition_path);
+ ASSERT_TRUE(IsOk(status))
+ << "failed to add block apex with is_factory=" << is_factory;
+ ASSERT_EQ(is_factory,
+ instance.HasPreInstalledVersion("com.android.apex.test_package"));
+ }
+}
+
} // namespace apex
} // namespace android
diff --git a/apexd/apex_file_test.cpp b/apexd/apex_file_test.cpp
index 3da7448..d6d9cf2 100644
--- a/apexd/apex_file_test.cpp
+++ b/apexd/apex_file_test.cpp
@@ -14,6 +14,7 @@
* limitations under the License.
*/
+#include <limits>
#include <string>
#include <android-base/file.h>
@@ -44,7 +45,9 @@
};
constexpr const ApexFileTestParam kParameters[] = {
- {"ext4", "apex.apexd_test"}, {"f2fs", "apex.apexd_test_f2fs"}};
+ {"ext4", "apex.apexd_test"},
+ {"f2fs", "apex.apexd_test_f2fs"},
+ {"erofs", "apex.apexd_test_erofs"}};
class ApexFileTest : public ::testing::TestWithParam<ApexFileTestParam> {};
@@ -55,7 +58,7 @@
Result<ApexFile> apex_file = ApexFile::Open(file_path);
ASSERT_TRUE(apex_file.ok());
- int32_t zip_image_offset;
+ uint32_t zip_image_offset;
size_t zip_image_size;
{
ZipArchiveHandle handle;
@@ -69,7 +72,7 @@
ASSERT_EQ(0, rc);
zip_image_offset = entry.offset;
- EXPECT_EQ(zip_image_offset % 4096, 0);
+ EXPECT_EQ(zip_image_offset % 4096, 0U);
zip_image_size = entry.uncompressed_length;
EXPECT_EQ(zip_image_size, entry.compressed_length);
}
@@ -78,6 +81,21 @@
EXPECT_EQ(zip_image_size, apex_file->GetImageSize().value());
}
+TEST_P(ApexFileTest, OpenBlockApex) {
+ const std::string file_path = kTestDataDir + GetParam().prefix + ".apex";
+ Result<ApexFile> apex_file = ApexFile::Open(file_path);
+ ASSERT_RESULT_OK(apex_file);
+
+ TemporaryFile temp_file;
+ auto loop_device = WriteBlockApex(file_path, temp_file.path);
+
+ Result<ApexFile> apex_file_sized = ApexFile::Open(temp_file.path);
+ ASSERT_RESULT_OK(apex_file_sized);
+
+ EXPECT_EQ(apex_file->GetImageOffset(), apex_file_sized->GetImageOffset());
+ EXPECT_EQ(apex_file->GetImageSize(), apex_file_sized->GetImageSize());
+}
+
TEST(ApexFileTest, GetOffsetMissingFile) {
const std::string file_path = kTestDataDir + "missing.apex";
Result<ApexFile> apex_file = ApexFile::Open(file_path);
diff --git a/apexd/apexd.cpp b/apexd/apexd.cpp
index e0562f0..b7dd8d3 100644
--- a/apexd/apexd.cpp
+++ b/apexd/apexd.cpp
@@ -14,6 +14,8 @@
* limitations under the License.
*/
+#define ATRACE_TAG ATRACE_TAG_PACKAGE_MANAGER
+
#include "apexd.h"
#include "apex_file_repository.h"
#include "apexd_private.h"
@@ -26,7 +28,6 @@
#include "apexd_checkpoint.h"
#include "apexd_lifecycle.h"
#include "apexd_loop.h"
-#include "apexd_prepostinstall.h"
#include "apexd_rollback_utils.h"
#include "apexd_session.h"
#include "apexd_utils.h"
@@ -50,6 +51,7 @@
#include <libdm/dm_table.h>
#include <libdm/dm_target.h>
#include <selinux/android.h>
+#include <utils/Trace.h>
#include <dirent.h>
#include <fcntl.h>
@@ -96,6 +98,7 @@
using android::base::RemoveFileIfExists;
using android::base::Result;
using android::base::SetProperty;
+using android::base::StartsWith;
using android::base::StringPrintf;
using android::base::unique_fd;
using android::dm::DeviceMapper;
@@ -304,22 +307,9 @@
bool cleared_;
};
-Result<DmVerityDevice> CreateVerityDevice(const std::string& name,
- const DmTable& table) {
- DeviceMapper& dm = DeviceMapper::Instance();
-
- if (dm.GetState(name) != DmDeviceState::INVALID) {
- // Delete dangling dm-device. This can happen if apexd fails to delete it
- // while unmounting an apex.
- LOG(WARNING) << "Deleting existing dm device " << name;
- auto result = DeleteVerityDevice(name, /* deferred= */ false);
- if (!result.ok()) {
- return result.error();
- }
- }
-
- auto timeout = std::chrono::milliseconds(
- android::sysprop::ApexProperties::dm_create_timeout().value_or(1000));
+Result<DmVerityDevice> CreateVerityDevice(
+ DeviceMapper& dm, const std::string& name, const DmTable& table,
+ const std::chrono::milliseconds& timeout) {
std::string dev_path;
if (!dm.CreateDevice(name, table, &dev_path, timeout)) {
return Errorf("Couldn't create verity device.");
@@ -327,6 +317,52 @@
return DmVerityDevice(name, dev_path);
}
+Result<DmVerityDevice> CreateVerityDevice(const std::string& name,
+ const DmTable& table,
+ bool reuse_device) {
+ ATRACE_NAME("CreateVerityDevice");
+ LOG(VERBOSE) << "Creating verity device " << name;
+ auto timeout = std::chrono::milliseconds(
+ android::sysprop::ApexProperties::dm_create_timeout().value_or(1000));
+
+ DeviceMapper& dm = DeviceMapper::Instance();
+
+ auto state = dm.GetState(name);
+ if (state == DmDeviceState::INVALID) {
+ return CreateVerityDevice(dm, name, table, timeout);
+ }
+
+ if (reuse_device) {
+ if (state == DmDeviceState::ACTIVE) {
+ LOG(WARNING) << "Deleting existing active dm device " << name;
+ if (auto r = DeleteVerityDevice(name, /* deferred= */ false); !r.ok()) {
+ return r.error();
+ }
+ return CreateVerityDevice(dm, name, table, timeout);
+ }
+ if (!dm.LoadTableAndActivate(name, table)) {
+ dm.DeleteDevice(name);
+ return Error() << "Failed to activate dm device " << name;
+ }
+ std::string path;
+ if (!dm.WaitForDevice(name, timeout, &path)) {
+ dm.DeleteDevice(name);
+ return Error() << "Failed waiting for dm device " << name;
+ }
+ return DmVerityDevice(name, path);
+ } else {
+ if (state != DmDeviceState::INVALID) {
+ // Delete dangling dm-device. This can happen if apexd fails to delete it
+ // while unmounting an apex.
+ LOG(WARNING) << "Deleting existing dm device " << name;
+ if (auto r = DeleteVerityDevice(name, /* deferred= */ false); !r.ok()) {
+ return r.error();
+ }
+ }
+ return CreateVerityDevice(dm, name, table, timeout);
+ }
+}
+
/**
* When we create hardlink for a new apex package in kActiveApexPackagesDataDir,
* there might be an older version of the same package already present in there.
@@ -424,8 +460,10 @@
const std::string& mount_point,
const std::string& device_name,
const std::string& hashtree_file,
- bool verify_image,
+ bool verify_image, bool reuse_device,
bool temp_mount = false) {
+ auto tag = "MountPackageImpl: " + apex.GetManifest().name();
+ ATRACE_NAME(tag.c_str());
if (apex.IsCompressed()) {
return Error() << "Cannot directly mount compressed APEX "
<< apex.GetPath();
@@ -465,8 +503,10 @@
}
loop::LoopbackDeviceUniqueFd loopback_device;
for (size_t attempts = 1;; ++attempts) {
- Result<loop::LoopbackDeviceUniqueFd> ret = loop::CreateLoopDevice(
- full_path, apex.GetImageOffset().value(), apex.GetImageSize().value());
+ Result<loop::LoopbackDeviceUniqueFd> ret =
+ loop::CreateAndConfigureLoopDevice(full_path,
+ apex.GetImageOffset().value(),
+ apex.GetImageSize().value());
if (ret.ok()) {
loopback_device = std::move(*ret);
break;
@@ -490,6 +530,18 @@
return Error() << "Failed to verify Apex Verity data for " << full_path
<< ": " << verity_data.error();
}
+ if (instance.IsBlockApex(apex)) {
+ auto root_digest =
+ instance.GetBlockApexRootDigest(apex.GetManifest().name());
+ if (root_digest.has_value() &&
+ root_digest.value() != verity_data->root_digest) {
+ return Error() << "Failed to verify Apex Verity data for " << full_path
+ << ": root digest (" << verity_data->root_digest
+ << ") mismatches with the one (" << root_digest.value()
+ << ") specified in config";
+ }
+ }
+
std::string block_device = loopback_device.name;
MountedApexData apex_data(loopback_device.name, apex.GetPath(), mount_point,
/* device_name = */ "",
@@ -500,8 +552,11 @@
// dm-verity because they are already in the dm-verity protected partition;
// system. However, note that we don't skip verification to ensure that APEXes
// are correctly signed.
- const bool mount_on_verity =
- !instance.IsPreInstalledApex(apex) || instance.IsDecompressedApex(apex);
+ const bool mount_on_verity = !instance.IsPreInstalledApex(apex) ||
+ // decompressed apexes are on /data
+ instance.IsDecompressedApex(apex) ||
+ // block apexes are from host
+ instance.IsBlockApex(apex);
DmVerityDevice verity_dev;
loop::LoopbackDeviceUniqueFd loop_for_hash;
@@ -512,7 +567,10 @@
!st.ok()) {
return st.error();
}
- auto create_loop_status = loop::CreateLoopDevice(hashtree_file, 0, 0);
+ auto create_loop_status =
+ loop::CreateAndConfigureLoopDevice(hashtree_file,
+ /* image_offset= */ 0,
+ /* image_size= */ 0);
if (!create_loop_status.ok()) {
return create_loop_status.error();
}
@@ -524,7 +582,7 @@
CreateVerityTable(*verity_data, loopback_device.name, hash_device,
/* restart_on_corruption = */ !verify_image);
Result<DmVerityDevice> verity_dev_res =
- CreateVerityDevice(device_name, *verity_table);
+ CreateVerityDevice(device_name, *verity_table, reuse_device);
if (!verity_dev_res.ok()) {
return Error() << "Failed to create Apex Verity device " << full_path
<< ": " << verity_dev_res.error();
@@ -603,7 +661,8 @@
}
auto ret =
MountPackageImpl(apex, mount_point, temp_device_name, hashtree_file,
- /* verify_image = */ true, /* temp_mount = */ true);
+ /* verify_image = */ true, /* reuse_device= */ false,
+ /* temp_mount = */ true);
if (!ret.ok()) {
LOG(DEBUG) << "Cleaning up " << hashtree_file;
if (TEMP_FAILURE_RETRY(unlink(hashtree_file.c_str())) != 0) {
@@ -700,65 +759,6 @@
return {};
}
-template <typename HookFn, typename HookCall>
-Result<void> PrePostinstallPackages(const std::vector<ApexFile>& apexes,
- HookFn fn, HookCall call) {
- auto scope_guard = android::base::make_scope_guard([&]() {
- for (const ApexFile& apex_file : apexes) {
- apexd_private::UnmountTempMount(apex_file);
- }
- });
- if (apexes.empty()) {
- return Errorf("Empty set of inputs");
- }
-
- // 1) Check whether the APEXes have hooks.
- bool has_hooks = false;
- for (const ApexFile& apex_file : apexes) {
- if (!(apex_file.GetManifest().*fn)().empty()) {
- has_hooks = true;
- break;
- }
- }
-
- // 2) If we found hooks, temp mount if required, and run the pre/post-install.
- if (has_hooks) {
- std::vector<std::string> mount_points;
- for (const ApexFile& apex : apexes) {
- // Retrieve the mount data if the apex is already temp mounted, temp
- // mount it otherwise.
- std::string mount_point =
- apexd_private::GetPackageTempMountPoint(apex.GetManifest());
- Result<MountedApexData> mount_data =
- apexd_private::GetTempMountedApexData(apex.GetManifest().name());
- if (!mount_data.ok()) {
- mount_data = VerifyAndTempMountPackage(apex, mount_point);
- if (!mount_data.ok()) {
- return mount_data.error();
- }
- }
- mount_points.push_back(mount_point);
- }
-
- Result<void> install_status = (*call)(apexes, mount_points);
- if (!install_status.ok()) {
- return install_status;
- }
- }
-
- return {};
-}
-
-Result<void> PreinstallPackages(const std::vector<ApexFile>& apexes) {
- return PrePostinstallPackages(apexes, &ApexManifest::preinstallhook,
- &StagePreInstall);
-}
-
-Result<void> PostinstallPackages(const std::vector<ApexFile>& apexes) {
- return PrePostinstallPackages(apexes, &ApexManifest::postinstallhook,
- &StagePostInstall);
-}
-
// Converts a list of apex file paths into a list of ApexFile objects
//
// Returns error when trying to open empty set of inputs.
@@ -853,9 +853,9 @@
return std::move(*apex_files);
}
-Result<ApexFile> VerifySessionDir(const int session_id) {
- std::string session_dir_path = std::string(kStagedSessionsDir) + "/session_" +
- std::to_string(session_id);
+Result<ApexFile> VerifySessionDir(int session_id) {
+ std::string session_dir_path =
+ StringPrintf("%s/session_%d", gConfig->staged_session_dir, session_id);
LOG(INFO) << "Scanning " << session_dir_path
<< " looking for packages to be validated";
Result<std::vector<std::string>> scan =
@@ -1042,10 +1042,12 @@
void SetConfig(const ApexdConfig& config) { gConfig = config; }
Result<void> MountPackage(const ApexFile& apex, const std::string& mount_point,
- const std::string& device_name) {
- auto ret = MountPackageImpl(apex, mount_point, device_name,
- GetHashTreeFileName(apex, /* is_new= */ false),
- /* verify_image = */ false);
+ const std::string& device_name, bool reuse_device,
+ bool temp_mount) {
+ auto ret =
+ MountPackageImpl(apex, mount_point, device_name,
+ GetHashTreeFileName(apex, /* is_new= */ false),
+ /* verify_image = */ false, reuse_device, temp_mount);
if (!ret.ok()) {
return ret.error();
}
@@ -1234,7 +1236,9 @@
}
Result<void> ActivatePackageImpl(const ApexFile& apex_file,
- const std::string& device_name) {
+ const std::string& device_name,
+ bool reuse_device) {
+ ATRACE_NAME("ActivatePackageImpl");
const ApexManifest& manifest = apex_file.GetManifest();
if (!IsValidPackageName(manifest.name())) {
@@ -1293,7 +1297,8 @@
apexd_private::GetPackageMountPoint(manifest);
if (!version_found_mounted) {
- auto mount_status = MountPackage(apex_file, mount_point, device_name);
+ auto mount_status = MountPackage(apex_file, mount_point, device_name,
+ reuse_device, /*temp_mount=*/false);
if (!mount_status.ok()) {
return mount_status;
}
@@ -1347,8 +1352,8 @@
if (!apex_file.ok()) {
return apex_file.error();
}
- return ActivatePackageImpl(*apex_file,
- GetPackageId(apex_file->GetManifest()));
+ return ActivatePackageImpl(*apex_file, GetPackageId(apex_file->GetManifest()),
+ /* reuse_device= */ false);
}
Result<void> DeactivatePackage(const std::string& full_path) {
@@ -1363,6 +1368,74 @@
/* deferred= */ false);
}
+Result<std::vector<ApexFile>> GetStagedApexFiles(
+ int session_id, const std::vector<int>& child_session_ids) {
+ auto session = ApexSession::GetSession(session_id);
+ if (!session.ok()) {
+ return session.error();
+ }
+ // We should only accept sessions in SessionState::STAGED state
+ auto session_state = (*session).GetState();
+ if (session_state != SessionState::STAGED) {
+ return Error() << "Session " << session_id << " is not in state STAGED";
+ }
+
+ std::vector<int> ids_to_scan;
+ if (!child_session_ids.empty()) {
+ ids_to_scan = child_session_ids;
+ } else {
+ ids_to_scan = {session_id};
+ }
+
+ // Find apex files in the staging directory
+ std::vector<std::string> apex_file_paths;
+ for (int id_to_scan : ids_to_scan) {
+ std::string session_dir_path = std::string(gConfig->staged_session_dir) +
+ "/session_" + std::to_string(id_to_scan);
+ Result<std::vector<std::string>> scan =
+ FindFilesBySuffix(session_dir_path, {kApexPackageSuffix});
+ if (!scan.ok()) {
+ return scan.error();
+ }
+ if (scan->size() != 1) {
+ return Error() << "Expected exactly one APEX file in directory "
+ << session_dir_path << ". Found: " << scan->size();
+ }
+ std::string& apex_file_path = (*scan)[0];
+ apex_file_paths.push_back(std::move(apex_file_path));
+ }
+
+ return OpenApexFiles(apex_file_paths);
+}
+
+Result<ClassPath> MountAndDeriveClassPath(
+ const std::vector<ApexFile>& apex_files) {
+ auto guard = android::base::make_scope_guard([&]() {
+ for (const auto& apex : apex_files) {
+ apexd_private::UnmountTempMount(apex);
+ }
+ });
+
+ // Mount the staged apex files
+ std::vector<std::string> temp_mounted_apex_paths;
+ for (const auto& apex : apex_files) {
+ const std::string& temp_mount_point =
+ apexd_private::GetPackageTempMountPoint(apex.GetManifest());
+ const std::string& package_id = GetPackageId(apex.GetManifest());
+ const std::string& temp_device_name = package_id + ".tmp";
+ auto mount_status =
+ MountPackage(apex, temp_mount_point, temp_device_name,
+ /*reuse_device=*/false, /*temp_mount=*/true);
+ if (!mount_status.ok()) {
+ return mount_status.error();
+ }
+ temp_mounted_apex_paths.push_back(temp_mount_point);
+ }
+
+ // Calculate classpaths of temp mounted staged apexs
+ return ClassPath::DeriveClassPath(temp_mounted_apex_paths);
+}
+
std::vector<ApexFile> GetActivePackages() {
std::vector<ApexFile> ret;
gMountedApexes.ForallMountedApexes(
@@ -1534,40 +1607,12 @@
namespace {
-// TODO(b/179497746): Avoid scanning apex directly here
-// Only used in OnBootstrap. Should we remove this function?
-Result<std::vector<ApexFile>> ScanApexFiles(const char* apex_package_dir,
- bool include_compressed = false) {
- LOG(INFO) << "Scanning " << apex_package_dir << " looking for APEX packages.";
- if (access(apex_package_dir, F_OK) != 0 && errno == ENOENT) {
- LOG(INFO) << "... does not exist. Skipping";
- return {};
- }
- std::vector<std::string> suffix_list = {kApexPackageSuffix};
- if (include_compressed) {
- suffix_list.push_back(kCompressedApexPackageSuffix);
- }
- Result<std::vector<std::string>> scan =
- FindFilesBySuffix(apex_package_dir, suffix_list);
- if (!scan.ok()) {
- return Error() << "Failed to scan " << apex_package_dir << " : "
- << scan.error();
- }
- std::vector<ApexFile> ret;
- for (const auto& name : *scan) {
- Result<ApexFile> apex_file = ApexFile::Open(name);
- if (!apex_file.ok()) {
- LOG(ERROR) << "Failed to scan " << name << " : " << apex_file.error();
- } else {
- ret.emplace_back(std::move(*apex_file));
- }
- }
- return ret;
-}
+enum ActivationMode { kBootstrapMode = 0, kBootMode, kOtaChrootMode, kVmMode };
std::vector<Result<void>> ActivateApexWorker(
- bool is_ota_chroot, std::queue<const ApexFile*>& apex_queue,
+ ActivationMode mode, std::queue<const ApexFile*>& apex_queue,
std::mutex& mutex) {
+ ATRACE_NAME("ActivateApexWorker");
std::vector<Result<void>> ret;
while (true) {
@@ -1579,13 +1624,20 @@
apex_queue.pop();
}
- std::string device_name = GetPackageId(apex->GetManifest());
- if (is_ota_chroot) {
+ std::string device_name;
+ if (mode == ActivationMode::kBootMode) {
+ device_name = apex->GetManifest().name();
+ } else {
+ device_name = GetPackageId(apex->GetManifest());
+ }
+ if (mode == ActivationMode::kOtaChrootMode) {
device_name += ".chroot";
}
- if (auto res = ActivatePackageImpl(*apex, device_name); !res.ok()) {
- ret.push_back(Error() << "Failed to activate " << apex->GetPath() << " : "
- << res.error());
+ bool reuse_device = mode == ActivationMode::kBootMode;
+ auto res = ActivatePackageImpl(*apex, device_name, reuse_device);
+ if (!res.ok()) {
+ ret.push_back(Error() << "Failed to activate " << apex->GetPath() << "("
+ << device_name << "): " << res.error());
} else {
ret.push_back({});
}
@@ -1595,7 +1647,8 @@
}
Result<void> ActivateApexPackages(const std::vector<ApexFileRef>& apexes,
- bool is_ota_chroot) {
+ ActivationMode mode) {
+ ATRACE_NAME("ActivateApexPackages");
std::queue<const ApexFile*> apex_queue;
std::mutex apex_queue_mutex;
@@ -1620,7 +1673,7 @@
futures.reserve(worker_num);
for (size_t i = 0; i < worker_num; i++) {
futures.push_back(std::async(std::launch::async, ActivateApexWorker,
- std::ref(is_ota_chroot), std::ref(apex_queue),
+ std::ref(mode), std::ref(apex_queue),
std::ref(apex_queue_mutex)));
}
@@ -1653,7 +1706,7 @@
// such apexes that were coming from /data partition we will attempt to activate
// their corresponding pre-installed copies.
Result<void> ActivateMissingApexes(const std::vector<ApexFileRef>& apexes,
- bool is_ota_chroot) {
+ ActivationMode mode) {
LOG(INFO) << "Trying to activate pre-installed versions of missing apexes";
const auto& file_repository = ApexFileRepository::GetInstance();
const auto& activated_apexes = GetActivePackagesMap();
@@ -1688,13 +1741,14 @@
}
std::vector<ApexFile> decompressed_apex;
if (!compressed_apex.empty()) {
- decompressed_apex =
- ProcessCompressedApex(compressed_apex, /* is_ota_chroot= */ false);
+ decompressed_apex = ProcessCompressedApex(
+ compressed_apex,
+ /* is_ota_chroot= */ mode == ActivationMode::kOtaChrootMode);
for (const ApexFile& apex_file : decompressed_apex) {
fallback_apexes.emplace_back(std::cref(apex_file));
}
}
- return ActivateApexPackages(fallback_apexes, is_ota_chroot);
+ return ActivateApexPackages(fallback_apexes, mode);
}
} // namespace
@@ -1941,7 +1995,6 @@
void OnBootCompleted() {
ApexdLifecycle::GetInstance().MarkBootCompleted();
- BootCompletedCleanup();
}
// Returns true if any session gets staged
@@ -2045,17 +2098,6 @@
continue;
}
- // Run postinstall, if necessary.
- Result<void> postinstall_status = PostinstallPackages(apexes);
- if (!postinstall_status.ok()) {
- std::string error_message =
- StringPrintf("Postinstall failed for session %d %s", session_id,
- postinstall_status.error().message().c_str());
- LOG(ERROR) << error_message;
- session.SetErrorMessage(error_message);
- continue;
- }
-
for (const auto& apex : apexes) {
// TODO(b/158470836): Avoid opening ApexFile repeatedly.
Result<ApexFile> apex_file = ApexFile::Open(apex);
@@ -2087,24 +2129,6 @@
}
}
-Result<void> PreinstallPackages(const std::vector<std::string>& paths) {
- Result<std::vector<ApexFile>> apex_files = OpenApexFiles(paths);
- if (!apex_files.ok()) {
- return apex_files.error();
- }
- LOG(DEBUG) << "PreinstallPackages() for " << Join(paths, ',');
- return PreinstallPackages(*apex_files);
-}
-
-Result<void> PostinstallPackages(const std::vector<std::string>& paths) {
- Result<std::vector<ApexFile>> apex_files = OpenApexFiles(paths);
- if (!apex_files.ok()) {
- return apex_files.error();
- }
- LOG(DEBUG) << "PostinstallPackages() for " << Join(paths, ',');
- return PostinstallPackages(*apex_files);
-}
-
namespace {
std::string StageDestPath(const ApexFile& apex_file) {
return StringPrintf("%s/%s%s", gConfig->active_apex_data_dir,
@@ -2349,6 +2373,7 @@
}
int OnBootstrap() {
+ ATRACE_NAME("OnBootstrap");
auto time_started = boot_clock::now();
Result<void> pre_allocate = PreAllocateLoopDevices();
if (!pre_allocate.ok()) {
@@ -2357,14 +2382,41 @@
}
ApexFileRepository& instance = ApexFileRepository::GetInstance();
- static const std::vector<std::string> kBootstrapApexDirs{
- kApexPackageSystemDir, kApexPackageSystemExtDir, kApexPackageVendorDir};
- Result<void> status = instance.AddPreInstalledApex(kBootstrapApexDirs);
+ Result<void> status =
+ instance.AddPreInstalledApex(gConfig->apex_built_in_dirs);
if (!status.ok()) {
LOG(ERROR) << "Failed to collect APEX keys : " << status.error();
return 1;
}
+ // TODO(b/209491448) Remove this.
+ auto block_count = AddBlockApex(instance);
+ if (!block_count.ok()) {
+ LOG(ERROR) << status.error();
+ return 1;
+ }
+ pre_allocate = loop::PreAllocateLoopDevices(*block_count);
+ if (!pre_allocate.ok()) {
+ LOG(ERROR) << "Failed to pre-allocate loop devices for block apexes : "
+ << pre_allocate.error();
+ }
+
+ DeviceMapper& dm = DeviceMapper::Instance();
+ // Create empty dm device for each found APEX.
+ // This is a boot time optimization that makes use of the fact that user space
+ // paths will be created by ueventd before apexd is started, and hence
+ // reducing the time to activate APEXEs on /data.
+ // Note: since at this point we don't know which APEXes are updated, we are
+ // optimistically creating a verity device for all of them. Once boot
+ // finishes, apexd will clean up unused devices.
+ // TODO(b/192241176): move to apexd_verity.{h,cpp}
+ for (const auto& apex : instance.GetPreInstalledApexFiles()) {
+ const std::string& name = apex.get().GetManifest().name();
+ if (!dm.CreateEmptyDevice(name)) {
+ LOG(ERROR) << "Failed to create empty device " << name;
+ }
+ }
+
// Create directories for APEX shared libraries.
auto sharedlibs_apex_dir = CreateSharedLibsApexDir();
if (!sharedlibs_apex_dir.ok()) {
@@ -2373,26 +2425,16 @@
}
// Find all bootstrap apexes
- std::vector<ApexFile> bootstrap_apexes;
- for (const auto& dir : kBootstrapApexDirs) {
- auto scan = ScanApexFiles(dir.c_str());
- if (!scan.ok()) {
- LOG(ERROR) << "Failed to scan APEX files in " << dir << " : "
- << scan.error();
- return 1;
+ std::vector<ApexFileRef> bootstrap_apexes;
+ for (const auto& apex : instance.GetPreInstalledApexFiles()) {
+ if (IsBootstrapApex(apex.get())) {
+ bootstrap_apexes.push_back(apex);
}
- std::copy_if(std::make_move_iterator(scan->begin()),
- std::make_move_iterator(scan->end()),
- std::back_inserter(bootstrap_apexes), IsBootstrapApex);
}
// Now activate bootstrap apexes.
- std::vector<ApexFileRef> bootstrap_apexes_ref;
- std::transform(bootstrap_apexes.begin(), bootstrap_apexes.end(),
- std::back_inserter(bootstrap_apexes_ref),
- [](const auto& x) { return std::cref(x); });
- auto ret = ActivateApexPackages(bootstrap_apexes_ref,
- /* is_ota_chroot= */ false);
+ auto ret =
+ ActivateApexPackages(bootstrap_apexes, ActivationMode::kBootstrapMode);
if (!ret.ok()) {
LOG(ERROR) << "Failed to activate bootstrap apex files : " << ret.error();
return 1;
@@ -2444,6 +2486,13 @@
<< status.error();
return;
}
+
+ // TODO(b/209491448) Remove this.
+ if (auto block_status = AddBlockApex(instance); !block_status.ok()) {
+ LOG(ERROR) << status.error();
+ return;
+ }
+
gMountedApexes.PopulateFromMounts(gConfig->active_apex_data_dir,
gConfig->decompression_dir,
gConfig->apex_hash_tree_dir);
@@ -2579,6 +2628,13 @@
if (!result.ok()) {
return result.error();
}
+ auto ctx = GetfileconPath(apex_path);
+ if (!ctx.ok()) {
+ return ctx.error();
+ }
+ if (!StartsWith(*ctx, gConfig->active_apex_selinux_ctx)) {
+ return Error() << apex_path << " has wrong SELinux context " << *ctx;
+ }
return std::move(*apex);
}
@@ -2752,6 +2808,7 @@
}
void OnStart() {
+ ATRACE_NAME("OnStart");
LOG(INFO) << "Marking APEXd as starting";
auto time_started = boot_clock::now();
if (!SetProperty(gConfig->apex_status_sysprop, kApexStatusStarting)) {
@@ -2835,7 +2892,7 @@
// TODO(b/179248390): activate parallelly if possible
auto activate_status =
- ActivateApexPackages(activation_list, /* is_ota_chroot= */ false);
+ ActivateApexPackages(activation_list, ActivationMode::kBootMode);
if (!activate_status.ok()) {
std::string error_message =
StringPrintf("Failed to activate packages: %s",
@@ -2846,8 +2903,8 @@
if (!revert_status.ok()) {
LOG(ERROR) << "Failed to revert : " << revert_status.error();
}
- auto retry_status = ActivateMissingApexes(activation_list,
- /* is_ota_chroot= */ false);
+ auto retry_status =
+ ActivateMissingApexes(activation_list, ActivationMode::kBootMode);
if (!retry_status.ok()) {
LOG(ERROR) << retry_status.error();
}
@@ -2922,7 +2979,7 @@
}
std::vector<ApexFile> ret;
- auto guard = android::base::make_scope_guard([&ret]() {
+ auto guard = android::base::make_scope_guard([&]() {
for (const auto& apex : ret) {
apexd_private::UnmountTempMount(apex);
}
@@ -2932,15 +2989,10 @@
if (!verified.ok()) {
return verified.error();
}
+ LOG(DEBUG) << verified->GetPath() << " is verified";
ret.push_back(std::move(*verified));
}
- // Run preinstall, if necessary.
- Result<void> preinstall_status = PreinstallPackages(ret);
- if (!preinstall_status.ok()) {
- return preinstall_status.error();
- }
-
if (has_rollback_enabled && is_rollback) {
return Error() << "Cannot set session " << session_id << " as both a"
<< " rollback and enabled for rollback.";
@@ -2967,6 +3019,14 @@
ReleaseF2fsCompressedBlocks(apex.GetPath());
}
+ // The scope guard above uses lambda that captures ret by reference.
+ // Unfortunately, for the capture by-reference, lifetime of the captured
+ // reference ends together with the lifetime of the closure object. This means
+ // that we need to manually call UnmountTempMount here.
+ for (const auto& apex : ret) {
+ apexd_private::UnmountTempMount(apex);
+ }
+
return ret;
}
@@ -3047,9 +3107,40 @@
}
}
+bool IsApexDevice(const std::string& dev_name) {
+ auto& repo = ApexFileRepository::GetInstance();
+ for (const auto& apex : repo.GetPreInstalledApexFiles()) {
+ if (StartsWith(dev_name, apex.get().GetManifest().name())) {
+ return true;
+ }
+ }
+ return false;
+}
+
+// TODO(b/192241176): move to apexd_verity.{h,cpp}.
+void DeleteUnusedVerityDevices() {
+ DeviceMapper& dm = DeviceMapper::Instance();
+ std::vector<DeviceMapper::DmBlockDevice> all_devices;
+ if (!dm.GetAvailableDevices(&all_devices)) {
+ LOG(WARNING) << "Failed to fetch dm devices";
+ return;
+ }
+ for (const auto& dev : all_devices) {
+ auto state = dm.GetState(dev.name());
+ if (state == DmDeviceState::SUSPENDED && IsApexDevice(dev.name())) {
+ LOG(INFO) << "Deleting unused dm device " << dev.name();
+ auto res = DeleteVerityDevice(dev.name(), /* deferred= */ false);
+ if (!res.ok()) {
+ LOG(WARNING) << res.error();
+ }
+ }
+ }
+}
+
void BootCompletedCleanup() {
RemoveInactiveDataApex();
ApexSession::DeleteFinalizedSessions();
+ DeleteUnusedVerityDevices();
}
int UnmountAll() {
@@ -3119,9 +3210,9 @@
}
// Given a single new APEX incoming via OTA, should we allocate space for it?
-Result<bool> ShouldAllocateSpaceForDecompression(
- const std::string& new_apex_name, const int64_t new_apex_version,
- const ApexFileRepository& instance) {
+bool ShouldAllocateSpaceForDecompression(const std::string& new_apex_name,
+ const int64_t new_apex_version,
+ const ApexFileRepository& instance) {
// An apex at most will have two versions on device: pre-installed and data.
// Check if there is a pre-installed version for the new apex.
@@ -3160,6 +3251,24 @@
return new_apex_version > data_version;
}
+int64_t CalculateSizeForCompressedApex(
+ const std::vector<std::tuple<std::string, int64_t, int64_t>>&
+ compressed_apexes,
+ const ApexFileRepository& instance) {
+ int64_t result = 0;
+ for (const auto& compressed_apex : compressed_apexes) {
+ std::string module_name;
+ int64_t version_code;
+ int64_t decompressed_size;
+ std::tie(module_name, version_code, decompressed_size) = compressed_apex;
+ if (ShouldAllocateSpaceForDecompression(module_name, version_code,
+ instance)) {
+ result += decompressed_size;
+ }
+ }
+ return result;
+}
+
void CollectApexInfoList(std::ostream& os,
const std::vector<ApexFile>& active_apexs,
const std::vector<ApexFile>& inactive_apexs) {
@@ -3176,12 +3285,15 @@
preinstalled_module_path = *preinstalled_path;
}
- std::optional<int64_t> mtime;
- struct stat stat_buf;
- if (stat(apex.GetPath().c_str(), &stat_buf) == 0) {
- mtime.emplace(stat_buf.st_mtime);
- } else {
- PLOG(WARNING) << "Failed to stat " << apex.GetPath();
+ std::optional<int64_t> mtime =
+ instance.GetBlockApexLastUpdateSeconds(apex.GetManifest().name());
+ if (!mtime.has_value()) {
+ struct stat stat_buf;
+ if (stat(apex.GetPath().c_str(), &stat_buf) == 0) {
+ mtime.emplace(stat_buf.st_mtime);
+ } else {
+ PLOG(WARNING) << "Failed to stat " << apex.GetPath();
+ }
}
com::android::apex::ApexInfo apex_info(
apex.GetManifest().name(), apex.GetPath(), preinstalled_module_path,
@@ -3247,6 +3359,73 @@
return {};
}
+// Adds block apexes if system property is set.
+Result<int> AddBlockApex(ApexFileRepository& instance) {
+ auto prop = GetProperty(gConfig->vm_payload_metadata_partition_prop, "");
+ if (prop != "") {
+ auto block_count = instance.AddBlockApex(prop);
+ if (!block_count.ok()) {
+ return Error() << "Failed to scan block APEX files: "
+ << block_count.error();
+ }
+ return block_count;
+ } else {
+ LOG(INFO) << "No block apex metadata partition found, not adding block "
+ << "apexes";
+ }
+ return 0;
+}
+
+// When running in the VM mode, we follow the minimal start-up operations.
+// - CreateSharedLibsApexDir
+// - AddPreInstalledApex: note that CAPEXes are not supported in the VM mode
+// - AddBlockApex
+// - ActivateApexPackages
+// - setprop apexd.status: activated/ready
+int OnStartInVmMode() {
+ // waits for /dev/loop-control
+ loop::PreAllocateLoopDevices(0);
+
+ // Create directories for APEX shared libraries.
+ if (auto status = CreateSharedLibsApexDir(); !status.ok()) {
+ LOG(ERROR) << "Failed to create /apex/sharedlibs : " << status.ok();
+ return 1;
+ }
+
+ auto& instance = ApexFileRepository::GetInstance();
+
+ // Scan pre-installed apexes
+ if (auto status = instance.AddPreInstalledApex(gConfig->apex_built_in_dirs);
+ !status.ok()) {
+ LOG(ERROR) << "Failed to scan pre-installed APEX files: " << status.error();
+ return 1;
+ }
+
+ if (auto status = AddBlockApex(instance); !status.ok()) {
+ LOG(ERROR) << status.error();
+ return 1;
+ }
+
+ if (auto status = ActivateApexPackages(instance.GetPreInstalledApexFiles(),
+ ActivationMode::kVmMode);
+ !status.ok()) {
+ LOG(ERROR) << "Failed to activate apex packages : " << status.error();
+ return 1;
+ }
+ if (auto status = ActivateApexPackages(instance.GetDataApexFiles(),
+ ActivationMode::kVmMode);
+ !status.ok()) {
+ LOG(ERROR) << "Failed to activate apex packages : " << status.error();
+ return 1;
+ }
+
+ OnAllPackagesActivated(false);
+ // In VM mode, we don't run a separate --snapshotde mode.
+ // Instead, we mark apexd.status "ready" right now.
+ OnAllPackagesReady();
+ return 0;
+}
+
int OnOtaChrootBootstrap() {
auto& instance = ApexFileRepository::GetInstance();
if (auto status = instance.AddPreInstalledApex(gConfig->apex_built_in_dirs);
@@ -3298,13 +3477,13 @@
}
}
- auto activate_status = ActivateApexPackages(activation_list,
- /* is_ota_chroot= */ true);
+ auto activate_status =
+ ActivateApexPackages(activation_list, ActivationMode::kOtaChrootMode);
if (!activate_status.ok()) {
LOG(ERROR) << "Failed to activate apex packages : "
<< activate_status.error();
- auto retry_status = ActivateMissingApexes(activation_list,
- /* is_ota_chroot= */ true);
+ auto retry_status =
+ ActivateMissingApexes(activation_list, ActivationMode::kOtaChrootMode);
if (!retry_status.ok()) {
LOG(ERROR) << retry_status.error();
}
@@ -3350,8 +3529,8 @@
return 0;
}
-int OnOtaChrootBootstrapFlattenedApex() {
- LOG(INFO) << "OnOtaChrootBootstrapFlattenedApex";
+int ActivateFlattenedApex() {
+ LOG(INFO) << "ActivateFlattenedApex";
std::vector<com::android::apex::ApexInfo> apex_infos;
@@ -3664,7 +3843,9 @@
// previously active APEX is still around. We need to create a new one.
std::string old_new_id = GetPackageId(temp_apex->GetManifest()) + "_" +
std::to_string(*new_id_minor + 1);
- if (auto res = ActivatePackageImpl(*cur_apex, old_new_id); !res.ok()) {
+ auto res = ActivatePackageImpl(*cur_apex, old_new_id,
+ /* reuse_device= */ false);
+ if (!res.ok()) {
// At this point not much we can do... :(
LOG(ERROR) << res.error();
}
@@ -3684,8 +3865,10 @@
}
// 4. And activate new one.
- if (auto res = ActivatePackageImpl(*new_apex, new_id); !res.ok()) {
- return res.error();
+ auto activate_status = ActivatePackageImpl(*new_apex, new_id,
+ /* reuse_device= */ false);
+ if (!activate_status.ok()) {
+ return activate_status.error();
}
// Accept the install.
diff --git a/apexd/apexd.h b/apexd/apexd.h
index 2465bde..d08c9ac 100644
--- a/apexd/apexd.h
+++ b/apexd/apexd.h
@@ -17,13 +17,14 @@
#ifndef ANDROID_APEXD_APEXD_H_
#define ANDROID_APEXD_APEXD_H_
+#include <android-base/macros.h>
+#include <android-base/result.h>
+
#include <ostream>
#include <string>
#include <vector>
-#include <android-base/macros.h>
-#include <android-base/result.h>
-
+#include "apex_classpath.h"
#include "apex_constants.h"
#include "apex_database.h"
#include "apex_file.h"
@@ -47,12 +48,25 @@
const char* ota_reserved_dir;
const char* apex_hash_tree_dir;
const char* staged_session_dir;
+ // Overrides the path to the "metadata" partition which is by default
+ // /dev/block/by-name/payload-metadata It should be a path pointing the first
+ // partition of the VM payload disk. So, realpath() of this path is checked if
+ // it has the suffix "1". For example, /test-dir/test-metadata-1 can be valid
+ // and the subsequent numbers should point APEX files.
+ const char* vm_payload_metadata_partition_prop;
+ const char* active_apex_selinux_ctx;
};
static const ApexdConfig kDefaultConfig = {
- kApexStatusSysprop, kApexPackageBuiltinDirs, kActiveApexPackagesDataDir,
- kApexDecompressedDir, kOtaReservedDir, kApexHashTreeDir,
+ kApexStatusSysprop,
+ kApexPackageBuiltinDirs,
+ kActiveApexPackagesDataDir,
+ kApexDecompressedDir,
+ kOtaReservedDir,
+ kApexHashTreeDir,
kStagedSessionsDir,
+ kVmPayloadMetadataPartitionProp,
+ "u:object_r:staging_data_file",
};
class CheckpointInterface;
@@ -67,8 +81,6 @@
android::base::Result<void> PreinstallPackages(
const std::vector<std::string>& paths) WARN_UNUSED;
-android::base::Result<void> PostinstallPackages(
- const std::vector<std::string>& paths) WARN_UNUSED;
android::base::Result<void> StagePackages(
const std::vector<std::string>& tmpPaths) WARN_UNUSED;
@@ -79,6 +91,11 @@
const int session_id, const std::vector<int>& child_session_ids,
const bool has_rollback_enabled, const bool is_rollback,
const int rollback_id) WARN_UNUSED;
+android::base::Result<std::vector<ApexFile>> GetStagedApexFiles(
+ const int session_id,
+ const std::vector<int>& child_session_ids) WARN_UNUSED;
+android::base::Result<ClassPath> MountAndDeriveClassPath(
+ const std::vector<ApexFile>&) WARN_UNUSED;
android::base::Result<void> MarkStagedSessionReady(const int session_id)
WARN_UNUSED;
android::base::Result<void> MarkStagedSessionSuccessful(const int session_id)
@@ -172,8 +189,13 @@
android::base::Result<void> RemountPackages();
// Exposed for unit tests
-android::base::Result<bool> ShouldAllocateSpaceForDecompression(
- const std::string& new_apex_name, int64_t new_apex_version,
+bool ShouldAllocateSpaceForDecompression(const std::string& new_apex_name,
+ int64_t new_apex_version,
+ const ApexFileRepository& instance);
+
+int64_t CalculateSizeForCompressedApex(
+ const std::vector<std::tuple<std::string, int64_t, int64_t>>&
+ compressed_apexes,
const ApexFileRepository& instance);
void CollectApexInfoList(std::ostream& os,
@@ -184,12 +206,15 @@
android::base::Result<void> ReserveSpaceForCompressedApex(
int64_t size, const std::string& dest_dir);
+// Entry point when running in the VM mode (with --vm arg)
+int OnStartInVmMode();
+
// Activates apexes in otapreot_chroot environment.
// TODO(b/172911822): support compressed apexes.
int OnOtaChrootBootstrap();
-// Activates flattened apexes in otapreopt_chroot environment.
-int OnOtaChrootBootstrapFlattenedApex();
+// Activates flattened apexes
+int ActivateFlattenedApex();
android::apex::MountedApexDatabase& GetApexDatabaseForTesting();
@@ -197,6 +222,9 @@
// TODO(ioffe): add more documentation.
android::base::Result<ApexFile> InstallPackage(const std::string& package_path);
+// Exposed for testing.
+android::base::Result<int> AddBlockApex(ApexFileRepository& instance);
+
} // namespace apex
} // namespace android
diff --git a/apexd/apexd_checkpoint_vold.h b/apexd/apexd_checkpoint_vold.h
index f547532..3e8ce7d 100644
--- a/apexd/apexd_checkpoint_vold.h
+++ b/apexd/apexd_checkpoint_vold.h
@@ -51,7 +51,7 @@
VoldCheckpointInterface(VoldCheckpointInterface&& other) noexcept;
private:
- VoldCheckpointInterface(sp<os::IVold>&& vold_service);
+ explicit VoldCheckpointInterface(sp<os::IVold>&& vold_service);
sp<os::IVold> vold_service_;
bool supports_fs_checkpoints_;
diff --git a/apexd/apexd_lifecycle.cpp b/apexd/apexd_lifecycle.cpp
index 41c1abe..b3fa1d0 100644
--- a/apexd/apexd_lifecycle.cpp
+++ b/apexd/apexd_lifecycle.cpp
@@ -14,7 +14,8 @@
* limitations under the License.
*/
-#define LOG_TAG "apexd"
+#include <chrono>
+#include <thread>
#include "apexd_lifecycle.h"
@@ -23,6 +24,8 @@
#include "apexd_utils.h"
+#define LOG_TAG "apexd"
+
using android::base::GetProperty;
using android::base::Result;
using android::base::WaitForProperty;
@@ -40,32 +43,35 @@
while (!boot_completed_) {
// Check for change in either crashing property or sys.boot_completed
// Wait for updatable_crashing property change for most of the time
- // (arbitrary 30s), briefly check if boot has completed successfully,
+ // (arbitrary 10s), briefly check if boot has completed successfully,
// if not continue waiting for updatable_crashing.
// We use this strategy so that we can quickly detect if an updatable
// process is crashing.
if (WaitForProperty("sys.init.updatable_crashing", "1",
- std::chrono::seconds(30))) {
+ std::chrono::seconds(10))) {
auto name = GetProperty("sys.init.updatable_crashing_process_name", "");
LOG(ERROR) << "Native process '" << (name.empty() ? "[unknown]" : name)
<< "' is crashing. Attempting a revert";
auto result = revert_fn(name, "");
if (!result.ok()) {
LOG(ERROR) << "Revert failed : " << result.error();
- break;
+ return WaitForBootStatus();
} else {
- // This should never be reached, since revert_fn should've rebooted a
- // device. But if for some reason we end up here, let's reboot it
- // manually.
- LOG(ERROR) << "Active sessions were reverted, but reboot wasn't "
- "triggered. Rebooting manually";
- Reboot();
- return;
+ // This should never be reached, since revert_fn should've rebooted
+ // the device.
+ LOG(FATAL) << "Active sessions were reverted, but reboot wasn't "
+ "triggered.";
}
}
}
}
+void ApexdLifecycle::WaitForBootStatus() {
+ while (!boot_completed_) {
+ std::this_thread::sleep_for(std::chrono::seconds(1));
+ }
+}
+
void ApexdLifecycle::MarkBootCompleted() { boot_completed_ = true; }
} // namespace apex
diff --git a/apexd/apexd_lifecycle.h b/apexd/apexd_lifecycle.h
index ecdef96..be99834 100644
--- a/apexd/apexd_lifecycle.h
+++ b/apexd/apexd_lifecycle.h
@@ -32,6 +32,8 @@
ApexdLifecycle& operator=(const ApexdLifecycle&) = delete;
ApexdLifecycle& operator=(ApexdLifecycle&&) = delete;
+ void WaitForBootStatus();
+
public:
static ApexdLifecycle& GetInstance() {
static ApexdLifecycle instance;
diff --git a/apexd/apexd_loop.cpp b/apexd/apexd_loop.cpp
index 08805ea..36521b0 100644
--- a/apexd/apexd_loop.cpp
+++ b/apexd/apexd_loop.cpp
@@ -15,18 +15,24 @@
*/
#define LOG_TAG "apexd"
+#define ATRACE_TAG ATRACE_TAG_PACKAGE_MANAGER
#include "apexd_loop.h"
+#include <array>
+#include <filesystem>
#include <mutex>
+#include <string_view>
#include <dirent.h>
#include <fcntl.h>
+#include <libdm/dm.h>
#include <linux/fs.h>
#include <linux/loop.h>
#include <sys/ioctl.h>
#include <sys/stat.h>
#include <sys/statfs.h>
+#include <sys/sysmacros.h>
#include <sys/types.h>
#include <unistd.h>
@@ -36,6 +42,7 @@
#include <android-base/properties.h>
#include <android-base/stringprintf.h>
#include <android-base/strings.h>
+#include <utils/Trace.h>
#include "apexd_utils.h"
#include "string_log.h"
@@ -45,21 +52,12 @@
using android::base::Error;
using android::base::GetBoolProperty;
using android::base::ParseUint;
+using android::base::ReadFileToString;
using android::base::Result;
using android::base::StartsWith;
using android::base::StringPrintf;
using android::base::unique_fd;
-
-#ifndef LOOP_CONFIGURE
-// These can be removed whenever we pull in the Linux v5.8 UAPI headers
-struct loop_config {
- __u32 fd;
- __u32 block_size;
- struct loop_info64 info;
- __u64 __reserved[8];
-};
-#define LOOP_CONFIGURE 0x4C0A
-#endif
+using android::dm::DeviceMapper;
namespace android {
namespace apex {
@@ -86,6 +84,168 @@
}
}
+Result<void> ConfigureScheduler(const std::string& device_path) {
+ if (!StartsWith(device_path, "/dev/")) {
+ return Error() << "Invalid argument " << device_path;
+ }
+
+ const std::string device_name = Basename(device_path);
+
+ const std::string sysfs_path =
+ StringPrintf("/sys/block/%s/queue/scheduler", device_name.c_str());
+ unique_fd sysfs_fd(open(sysfs_path.c_str(), O_RDWR | O_CLOEXEC));
+ if (sysfs_fd.get() == -1) {
+ return ErrnoError() << "Failed to open " << sysfs_path;
+ }
+
+ // Kernels before v4.1 only support 'noop'. Kernels [v4.1, v5.0) support
+ // 'noop' and 'none'. Kernels v5.0 and later only support 'none'.
+ static constexpr const std::array<std::string_view, 2> kNoScheduler = {
+ "none", "noop"};
+
+ int ret = 0;
+
+ for (const std::string_view& scheduler : kNoScheduler) {
+ ret = write(sysfs_fd.get(), scheduler.data(), scheduler.size());
+ if (ret > 0) {
+ break;
+ }
+ }
+
+ if (ret <= 0) {
+ return ErrnoError() << "Failed to write to " << sysfs_path;
+ }
+
+ return {};
+}
+
+// Return the parent device of a partition. Converts e.g. "sda26" into "sda".
+static Result<std::string> PartitionParent(const std::string& blockdev) {
+ if (blockdev.find('/') != std::string::npos) {
+ return Error() << "Invalid argument " << blockdev;
+ }
+ std::error_code ec;
+ for (const auto& entry :
+ std::filesystem::directory_iterator("/sys/class/block", ec)) {
+ const std::string path = entry.path().string();
+ if (std::filesystem::exists(
+ StringPrintf("%s/%s", path.c_str(), blockdev.c_str()))) {
+ return Basename(path);
+ }
+ }
+ return blockdev;
+}
+
+// Convert a major:minor pair into a block device name.
+static std::string BlockdevName(dev_t dev) {
+ std::error_code ec;
+ for (const auto& entry :
+ std::filesystem::directory_iterator("/dev/block", ec)) {
+ struct stat statbuf;
+ if (stat(entry.path().string().c_str(), &statbuf) < 0) {
+ continue;
+ }
+ if (dev == statbuf.st_rdev) {
+ return Basename(entry.path().string());
+ }
+ }
+ return {};
+}
+
+// For file `file_path`, retrieve the block device backing the filesystem on
+// which the file exists and return the queue depth of the block device. The
+// loop in this function may e.g. traverse the following hierarchy:
+// /dev/block/dm-9 (system-verity; dm-verity)
+// -> /dev/block/dm-1 (system_b; dm-linear)
+// -> /dev/sda26
+static Result<uint32_t> BlockDeviceQueueDepth(const std::string& file_path) {
+ struct stat statbuf;
+ int res = stat(file_path.c_str(), &statbuf);
+ if (res < 0) {
+ return ErrnoErrorf("stat({})", file_path.c_str());
+ }
+ std::string blockdev = "/dev/block/" + BlockdevName(statbuf.st_dev);
+ LOG(VERBOSE) << file_path << " -> " << blockdev;
+ if (blockdev.empty()) {
+ return Errorf("Failed to convert {}:{} (path {})", major(statbuf.st_dev),
+ minor(statbuf.st_dev), file_path.c_str());
+ }
+ auto& dm = DeviceMapper::Instance();
+ for (;;) {
+ std::optional<std::string> child = dm.GetParentBlockDeviceByPath(blockdev);
+ if (!child) {
+ break;
+ }
+ LOG(VERBOSE) << blockdev << " -> " << *child;
+ blockdev = *child;
+ }
+ std::optional<std::string> maybe_blockdev =
+ android::dm::ExtractBlockDeviceName(blockdev);
+ if (!maybe_blockdev) {
+ return Error() << "Failed to remove /dev/block/ prefix from " << blockdev;
+ }
+ Result<std::string> maybe_parent = PartitionParent(*maybe_blockdev);
+ if (!maybe_parent.ok()) {
+ return Error() << "Failed to determine parent of " << *maybe_blockdev;
+ }
+ blockdev = *maybe_parent;
+ LOG(VERBOSE) << "Partition parent: " << blockdev;
+ const std::string nr_tags_path =
+ StringPrintf("/sys/class/block/%s/mq/0/nr_tags", blockdev.c_str());
+ std::string nr_tags;
+ if (!ReadFileToString(nr_tags_path, &nr_tags)) {
+ return Error() << "Failed to read " << nr_tags_path;
+ }
+ nr_tags = android::base::Trim(nr_tags);
+ LOG(VERBOSE) << file_path << " is backed by /dev/" << blockdev
+ << " and that block device supports queue depth " << nr_tags;
+ return strtol(nr_tags.c_str(), NULL, 0);
+}
+
+// Set 'nr_requests' of `loop_device_path` equal to the queue depth of
+// the block device backing `file_path`.
+Result<void> ConfigureQueueDepth(const std::string& loop_device_path,
+ const std::string& file_path) {
+ if (!StartsWith(loop_device_path, "/dev/")) {
+ return Error() << "Invalid argument " << loop_device_path;
+ }
+
+ const std::string loop_device_name = Basename(loop_device_path);
+
+ const std::string sysfs_path =
+ StringPrintf("/sys/block/%s/queue/nr_requests", loop_device_name.c_str());
+ std::string cur_nr_requests_str;
+ if (!ReadFileToString(sysfs_path, &cur_nr_requests_str)) {
+ return Error() << "Failed to read " << sysfs_path;
+ }
+ cur_nr_requests_str = android::base::Trim(cur_nr_requests_str);
+ uint32_t cur_nr_requests = 0;
+ if (!ParseUint(cur_nr_requests_str.c_str(), &cur_nr_requests)) {
+ return Error() << "Failed to parse " << cur_nr_requests_str;
+ }
+
+ unique_fd sysfs_fd(open(sysfs_path.c_str(), O_RDWR | O_CLOEXEC));
+ if (sysfs_fd.get() == -1) {
+ return ErrnoErrorf("Failed to open {}", sysfs_path);
+ }
+
+ const auto qd = BlockDeviceQueueDepth(file_path);
+ if (!qd.ok()) {
+ return qd.error();
+ }
+ if (*qd == cur_nr_requests) {
+ return {};
+ }
+ // Only report write failures if reducing the queue depth. Attempts to
+ // increase the queue depth are rejected by the kernel if no I/O scheduler
+ // is associated with the request queue.
+ if (!WriteStringToFd(StringPrintf("%u", *qd), sysfs_fd) &&
+ *qd < cur_nr_requests) {
+ return ErrnoErrorf("Failed to write {} to {}", *qd, sysfs_path);
+ }
+ return {};
+}
+
Result<void> ConfigureReadAhead(const std::string& device_path) {
CHECK(StartsWith(device_path, "/dev/"));
std::string device_name = Basename(device_path);
@@ -159,7 +319,7 @@
}
Result<void> ConfigureLoopDevice(const int device_fd, const std::string& target,
- const int32_t image_offset,
+ const uint32_t image_offset,
const size_t image_size) {
static bool use_loop_configure;
static std::once_flag once_flag;
@@ -187,6 +347,7 @@
* kernel driver will automatically enable Direct I/O when it sees that
* condition is now met.
*/
+ bool use_buffered_io = false;
unique_fd target_fd(open(target.c_str(), O_RDONLY | O_CLOEXEC | O_DIRECT));
if (target_fd.get() == -1) {
struct statfs stbuf;
@@ -199,6 +360,7 @@
return Error(saved_errno) << "Failed to open " << target;
}
LOG(WARNING) << "Fallback to buffered I/O for " << target;
+ use_buffered_io = true;
target_fd.reset(open(target.c_str(), O_RDONLY | O_CLOEXEC));
if (target_fd.get() == -1) {
return ErrnoError() << "Failed to open " << target;
@@ -216,10 +378,12 @@
if (use_loop_configure) {
struct loop_config config;
memset(&config, 0, sizeof(config));
- li.lo_flags |= LO_FLAGS_DIRECT_IO;
config.fd = target_fd.get();
config.info = li;
config.block_size = 4096;
+ if (!use_buffered_io) {
+ li.lo_flags |= LO_FLAGS_DIRECT_IO;
+ }
if (ioctl(device_fd, LOOP_CONFIGURE, &config) == -1) {
return ErrnoError() << "Failed to LOOP_CONFIGURE";
@@ -278,14 +442,13 @@
// a loop device for it. To work around this we keep polling for loop device
// to be created until ueventd's cold boot sequence is done.
// See comment on kLoopDeviceRetryAttempts.
- unique_fd sysfs_fd;
bool cold_boot_done = GetBoolProperty("ro.cold_boot_done", false);
for (size_t i = 0; i != kLoopDeviceRetryAttempts; ++i) {
if (!cold_boot_done) {
cold_boot_done = GetBoolProperty("ro.cold_boot_done", false);
}
for (const auto& device : candidate_devices) {
- sysfs_fd.reset(open(device.c_str(), O_RDWR | O_CLOEXEC));
+ unique_fd sysfs_fd(open(device.c_str(), O_RDWR | O_CLOEXEC));
if (sysfs_fd.get() != -1) {
return LoopbackDeviceUniqueFd(std::move(sysfs_fd), device);
}
@@ -302,15 +465,17 @@
}
Result<LoopbackDeviceUniqueFd> CreateLoopDevice(const std::string& target,
- const int32_t image_offset,
- const size_t image_size) {
+ uint32_t image_offset,
+ size_t image_size) {
+ ATRACE_NAME("CreateLoopDevice");
+
unique_fd ctl_fd(open("/dev/loop-control", O_RDWR | O_CLOEXEC));
if (ctl_fd.get() == -1) {
return ErrnoError() << "Failed to open loop-control";
}
- static std::mutex mlock;
- std::lock_guard lock(mlock);
+ static std::mutex mtx;
+ std::lock_guard lock(mtx);
int num = ioctl(ctl_fd.get(), LOOP_CTL_GET_FREE);
if (num == -1) {
return ErrnoError() << "Failed LOOP_CTL_GET_FREE";
@@ -322,10 +487,39 @@
}
CHECK_NE(loop_device->device_fd.get(), -1);
- Result<void> configureStatus = ConfigureLoopDevice(
+ Result<void> configure_status = ConfigureLoopDevice(
loop_device->device_fd.get(), target, image_offset, image_size);
- if (!configureStatus.ok()) {
- return configureStatus.error();
+ if (!configure_status.ok()) {
+ return configure_status.error();
+ }
+
+ return loop_device;
+}
+
+Result<LoopbackDeviceUniqueFd> CreateAndConfigureLoopDevice(
+ const std::string& target, uint32_t image_offset, size_t image_size) {
+ ATRACE_NAME("CreateAndConfigureLoopDevice");
+ // Do minimal amount of work while holding a mutex. We need it because
+ // acquiring + configuring a loop device is not atomic. Ideally we should
+ // pre-acquire all the loop devices in advance, so that when we run APEX
+ // activation in-parallel, we can do it without holding any lock.
+ // Unfortunately, this will require some refactoring of how we manage loop
+ // devices, and probably some new loop-control ioctls, so for the time being
+ // we just limit the scope that requires locking.
+ auto loop_device = CreateLoopDevice(target, image_offset, image_size);
+ if (!loop_device.ok()) {
+ return loop_device.error();
+ }
+
+ Result<void> sched_status = ConfigureScheduler(loop_device->name);
+ if (!sched_status.ok()) {
+ LOG(WARNING) << "Configuring I/O scheduler failed: "
+ << sched_status.error();
+ }
+
+ Result<void> qd_status = ConfigureQueueDepth(loop_device->name, target);
+ if (!qd_status.ok()) {
+ LOG(WARNING) << qd_status.error();
}
Result<void> read_ahead_status = ConfigureReadAhead(loop_device->name);
diff --git a/apexd/apexd_loop.h b/apexd/apexd_loop.h
index c727944..3c356d9 100644
--- a/apexd/apexd_loop.h
+++ b/apexd/apexd_loop.h
@@ -55,13 +55,17 @@
int Get() { return device_fd.get(); }
};
+android::base::Result<LoopbackDeviceUniqueFd> WaitForDevice(int num);
+
+android::base::Result<void> ConfigureQueueDepth(
+ const std::string& loop_device_path, const std::string& file_path);
+
android::base::Result<void> ConfigureReadAhead(const std::string& device_path);
android::base::Result<void> PreAllocateLoopDevices(size_t num);
-android::base::Result<LoopbackDeviceUniqueFd> CreateLoopDevice(
- const std::string& target, const int32_t image_offset,
- const size_t image_size);
+android::base::Result<LoopbackDeviceUniqueFd> CreateAndConfigureLoopDevice(
+ const std::string& target, uint32_t image_offset, size_t image_size);
using DestroyLoopFn =
std::function<void(const std::string&, const std::string&)>;
diff --git a/apexd/apexd_main.cpp b/apexd/apexd_main.cpp
index 26d5732..fb6b488 100644
--- a/apexd/apexd_main.cpp
+++ b/apexd/apexd_main.cpp
@@ -25,7 +25,6 @@
#include "apexd.h"
#include "apexd_checkpoint_vold.h"
#include "apexd_lifecycle.h"
-#include "apexd_prepostinstall.h"
#include "apexservice.h"
#include <android-base/properties.h>
@@ -33,16 +32,6 @@
namespace {
int HandleSubcommand(char** argv) {
- if (strcmp("--pre-install", argv[1]) == 0) {
- LOG(INFO) << "Preinstall subcommand detected";
- return android::apex::RunPreInstall(argv);
- }
-
- if (strcmp("--post-install", argv[1]) == 0) {
- LOG(INFO) << "Postinstall subcommand detected";
- return android::apex::RunPostInstall(argv);
- }
-
if (strcmp("--bootstrap", argv[1]) == 0) {
LOG(INFO) << "Bootstrap subcommand detected";
return android::apex::OnBootstrap();
@@ -82,6 +71,11 @@
return result;
}
+ if (strcmp("--vm", argv[1]) == 0) {
+ LOG(INFO) << "VM subcommand detected";
+ return android::apex::OnStartInVmMode();
+ }
+
LOG(ERROR) << "Unknown subcommand: " << argv[1];
return 1;
}
@@ -137,7 +131,11 @@
// mark apexd as ready
android::apex::OnAllPackagesReady();
} else if (strcmp("--otachroot-bootstrap", argv[1]) == 0) {
- return android::apex::OnOtaChrootBootstrapFlattenedApex();
+ LOG(INFO) << "OTA chroot bootstrap subcommand detected";
+ return android::apex::ActivateFlattenedApex();
+ } else if (strcmp("--bootstrap", argv[1]) == 0) {
+ LOG(INFO) << "Bootstrap subcommand detected";
+ return android::apex::ActivateFlattenedApex();
}
return 0;
}
@@ -185,6 +183,9 @@
lifecycle.WaitForBootStatus(android::apex::RevertActiveSessionsAndReboot);
}
+ // Run cleanup routine before AllowServiceShutdown(), to prevent
+ // service_manager killing apexd in the middle of the cleanup.
+ android::apex::BootCompletedCleanup();
android::apex::binder::AllowServiceShutdown();
android::apex::binder::JoinThreadPool();
diff --git a/apexd/apexd_prepostinstall.cpp b/apexd/apexd_prepostinstall.cpp
deleted file mode 100644
index 51b60b4..0000000
--- a/apexd/apexd_prepostinstall.cpp
+++ /dev/null
@@ -1,225 +0,0 @@
-/*
- * 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.
- */
-
-#define LOG_TAG "apexd"
-
-#include "apexd_prepostinstall.h"
-
-#include <algorithm>
-#include <vector>
-
-#include <fcntl.h>
-#include <sys/mount.h>
-#include <sys/stat.h>
-#include <sys/types.h>
-#include <sys/wait.h>
-#include <unistd.h>
-
-#include <android-base/logging.h>
-#include <android-base/macros.h>
-#include <android-base/scopeguard.h>
-#include <android-base/strings.h>
-
-#include "apex_database.h"
-#include "apex_file.h"
-#include "apex_manifest.h"
-#include "apexd.h"
-#include "apexd_private.h"
-#include "apexd_utils.h"
-#include "string_log.h"
-
-using android::base::Error;
-using android::base::Result;
-using ::apex::proto::ApexManifest;
-
-namespace android {
-namespace apex {
-
-namespace {
-
-using MountedApexData = MountedApexDatabase::MountedApexData;
-
-void CloseSTDDescriptors() {
- // exec()d process will reopen STD* file descriptors as
- // /dev/null
- close(STDIN_FILENO);
- close(STDOUT_FILENO);
- close(STDERR_FILENO);
-}
-
-template <typename Fn>
-Result<void> StageFnInstall(const std::vector<ApexFile>& apexes,
- const std::vector<std::string>& mount_points, Fn fn,
- const char* arg, const char* name) {
- // TODO(b/158470023): consider supporting a session with more than one
- // pre-install hook.
- int hook_idx = -1;
- for (size_t i = 0; i < apexes.size(); i++) {
- if (!(apexes[i].GetManifest().*fn)().empty()) {
- if (hook_idx != -1) {
- return Error() << "Missing support for multiple " << name << " hooks";
- }
- hook_idx = i;
- }
- }
- CHECK(hook_idx != -1);
- LOG(VERBOSE) << name << " for " << apexes[hook_idx].GetPath();
-
- // Create invocation args.
- std::vector<std::string> args{
- "/system/bin/apexd", arg,
- mount_points[hook_idx] // Make the APEX with hook first.
- };
- for (size_t i = 0; i < mount_points.size(); i++) {
- if ((int)i != hook_idx) {
- args.push_back(mount_points[i]);
- }
- }
-
- return ForkAndRun(args);
-}
-
-template <typename Fn>
-int RunFnInstall(char** in_argv, Fn fn, const char* name) {
- std::vector<std::string> activation_dirs;
- auto preinstall_guard = android::base::make_scope_guard([&]() {
- for (const std::string& active_point : activation_dirs) {
- if (0 != rmdir(active_point.c_str())) {
- PLOG(ERROR) << "Could not delete temporary active point "
- << active_point;
- }
- }
- });
-
- // 1) Unshare.
- if (unshare(CLONE_NEWNS) != 0) {
- PLOG(ERROR) << "Failed to unshare() for apex " << name;
- _exit(200);
- }
-
- // 2) Make everything private, so that our (and hook's) changes do not
- // propagate.
- if (mount(nullptr, "/", nullptr, MS_PRIVATE | MS_REC, nullptr) == -1) {
- PLOG(ERROR) << "Failed to mount private.";
- _exit(201);
- }
-
- std::string hook_path;
- {
- auto bind_fn = [&fn, name,
- activation_dirs](const std::string& mount_point) mutable {
- std::string hook;
- std::string active_point;
- {
- Result<ApexManifest> manifest_or =
- ReadManifest(mount_point + "/" + kManifestFilenamePb);
- if (!manifest_or.ok()) {
- LOG(ERROR) << "Could not read manifest from " << mount_point << "/"
- << kManifestFilenamePb << " for " << name << ": "
- << manifest_or.error();
- // Fallback to Json manifest if present.
- LOG(ERROR) << "Trying to find a JSON manifest";
- manifest_or = ReadManifest(mount_point + "/" + kManifestFilenameJson);
- if (!manifest_or.ok()) {
- LOG(ERROR) << "Could not read manifest from " << mount_point << "/"
- << kManifestFilenameJson << " for " << name << ": "
- << manifest_or.error();
- _exit(202);
- }
- }
- const auto& manifest = *manifest_or;
- hook = (manifest.*fn)();
- active_point = apexd_private::GetActiveMountPoint(manifest);
- // Ensure there is an activation point. If not, create one and delete
- // later.
- if (0 == mkdir(active_point.c_str(), kMkdirMode)) {
- activation_dirs.push_back(active_point);
- } else if (errno != EEXIST) {
- PLOG(ERROR) << "Unable to create mount point " << active_point;
- _exit(205);
- }
- }
-
- // 3) Activate the new apex.
- Result<void> bind_status =
- apexd_private::BindMount(active_point, mount_point);
- if (!bind_status.ok()) {
- LOG(ERROR) << "Failed to bind-mount " << mount_point << " to "
- << active_point << ": " << bind_status.error();
- _exit(203);
- }
-
- return std::make_pair(active_point, hook);
- };
-
- // First/main APEX.
- auto [active_point, hook] = bind_fn(in_argv[2]);
- hook_path = active_point + "/" + hook;
-
- for (size_t i = 3;; ++i) {
- if (in_argv[i] == nullptr) {
- break;
- }
- bind_fn(in_argv[i]); // Ignore result, hook will be empty.
- }
- }
-
- // 4) Run the hook.
-
- // For now, just run sh. But this probably needs to run the new linker.
- std::vector<std::string> args{
- hook_path,
- };
- std::vector<const char*> argv;
- argv.resize(args.size() + 1, nullptr);
- std::transform(args.begin(), args.end(), argv.begin(),
- [](const std::string& in) { return in.c_str(); });
-
- LOG(ERROR) << "execv of " << android::base::Join(args, " ");
-
- // Close all file descriptors. They are coming from the caller, we do not
- // want to pass them on across our fork/exec into a different domain.
- CloseSTDDescriptors();
-
- execv(argv[0], const_cast<char**>(argv.data()));
- PLOG(ERROR) << "execv of " << android::base::Join(args, " ") << " failed";
- _exit(204);
-}
-
-} // namespace
-
-Result<void> StagePreInstall(const std::vector<ApexFile>& apexes,
- const std::vector<std::string>& mount_points) {
- return StageFnInstall(apexes, mount_points, &ApexManifest::preinstallhook,
- "--pre-install", "pre-install");
-}
-
-int RunPreInstall(char** in_argv) {
- return RunFnInstall(in_argv, &ApexManifest::preinstallhook, "pre-install");
-}
-
-Result<void> StagePostInstall(const std::vector<ApexFile>& apexes,
- const std::vector<std::string>& mount_points) {
- return StageFnInstall(apexes, mount_points, &ApexManifest::postinstallhook,
- "--post-install", "post-install");
-}
-
-int RunPostInstall(char** in_argv) {
- return RunFnInstall(in_argv, &ApexManifest::postinstallhook, "post-install");
-}
-
-} // namespace apex
-} // namespace android
diff --git a/apexd/apexd_prepostinstall.h b/apexd/apexd_prepostinstall.h
deleted file mode 100644
index 65125ba..0000000
--- a/apexd/apexd_prepostinstall.h
+++ /dev/null
@@ -1,49 +0,0 @@
-/*
- * 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.
- */
-
-#ifndef ANDROID_APEXD_APEXD_PREPOSTINSTALL_H_
-#define ANDROID_APEXD_APEXD_PREPOSTINSTALL_H_
-
-#include <string>
-#include <vector>
-
-#include <android-base/result.h>
-
-namespace android {
-namespace apex {
-
-class ApexFile;
-
-// Forks into: apexd --pre-install <mount-point-of-apex-with-hook>
-// [<other-mount-points>] The caller must pass the temp mount point for each
-// apex file.
-android::base::Result<void> StagePreInstall(
- const std::vector<ApexFile>& apexes,
- const std::vector<std::string>& mount_points);
-int RunPreInstall(char** argv);
-
-// Forks into: apexd --post-install <mount-point-of-apex-with-hook>
-// [<other-mount-points>] The caller must pass the temp mount point for each
-// apex file.
-android::base::Result<void> StagePostInstall(
- const std::vector<ApexFile>& apexes,
- const std::vector<std::string>& mount_points);
-int RunPostInstall(char** argv);
-
-} // namespace apex
-} // namespace android
-
-#endif // ANDROID_APEXD_APEXD_PREPOSTINSTALL_H_
diff --git a/apexd/apexd_rollback_utils.h b/apexd/apexd_rollback_utils.h
index c7fa05e..b4dffd2 100644
--- a/apexd/apexd_rollback_utils.h
+++ b/apexd/apexd_rollback_utils.h
@@ -41,7 +41,7 @@
kCpPath,
"-F", /* delete any existing destination file first
(--remove-destination) */
- "-p", /* preserve timestamps, ownership, and permissions */
+ "--preserve=mode,ownership,timestamps,xattr", /* preserve properties */
"-R", /* recurse into subdirectories (DEST must be a directory) */
"-P", /* Do not follow symlinks [default] */
"-d", /* don't dereference symlinks */
diff --git a/apexd/apexd_session_test.cpp b/apexd/apexd_session_test.cpp
index 50d0f85..7ee1eb1 100644
--- a/apexd/apexd_session_test.cpp
+++ b/apexd/apexd_session_test.cpp
@@ -39,6 +39,7 @@
using android::apex::testing::IsOk;
using android::base::Join;
using android::base::make_scope_guard;
+using ::apex::proto::SessionState;
// TODO(b/170329726): add unit tests for apexd_sessions.h
diff --git a/apexd/apexd_test.cpp b/apexd/apexd_test.cpp
index a3ebd03..ea5aab3 100644
--- a/apexd/apexd_test.cpp
+++ b/apexd/apexd_test.cpp
@@ -14,25 +14,35 @@
* limitations under the License.
*/
-#include <string>
-#include <vector>
+#include "apexd.h"
#include <android-base/file.h>
#include <android-base/properties.h>
+#include <android-base/result-gmock.h>
#include <android-base/scopeguard.h>
#include <android-base/stringprintf.h>
#include <gmock/gmock.h>
#include <gtest/gtest.h>
+#include <libdm/dm.h>
+#include <microdroid/metadata.h>
+#include <selinux/selinux.h>
+#include <sys/stat.h>
+
+#include <functional>
+#include <optional>
+#include <string>
+#include <tuple>
+#include <unordered_set>
+#include <vector>
#include "apex_database.h"
#include "apex_file_repository.h"
-#include "apexd.h"
+#include "apex_manifest.pb.h"
#include "apexd_checkpoint.h"
+#include "apexd_loop.h"
#include "apexd_session.h"
#include "apexd_test_utils.h"
#include "apexd_utils.h"
-
-#include "apex_manifest.pb.h"
#include "com_android_apex.h"
#include "gmock/gmock-matchers.h"
@@ -43,19 +53,30 @@
using MountedApexData = MountedApexDatabase::MountedApexData;
using android::apex::testing::ApexFileEq;
-using android::apex::testing::IsOk;
using android::base::GetExecutableDirectory;
using android::base::GetProperty;
+using android::base::Join;
using android::base::make_scope_guard;
+using android::base::ReadFileToString;
+using android::base::ReadFully;
using android::base::RemoveFileIfExists;
using android::base::Result;
+using android::base::Split;
using android::base::StringPrintf;
using android::base::unique_fd;
using android::base::WriteStringToFile;
+using android::base::testing::HasError;
+using android::base::testing::HasValue;
+using android::base::testing::Ok;
+using android::base::testing::WithMessage;
+using android::dm::DeviceMapper;
+using ::apex::proto::SessionState;
using com::android::apex::testing::ApexInfoXmlEq;
using ::testing::ByRef;
+using ::testing::Contains;
using ::testing::HasSubstr;
using ::testing::IsEmpty;
+using ::testing::Not;
using ::testing::StartsWith;
using ::testing::UnorderedElementsAre;
using ::testing::UnorderedElementsAreArray;
@@ -102,6 +123,11 @@
};
static constexpr const char* kTestApexdStatusSysprop = "apexd.status.test";
+static constexpr const char* kTestVmPayloadMetadataPartitionProp =
+ "apexd.vm.payload_metadata_partition.test";
+
+static constexpr const char* kTestActiveApexSelinuxCtx =
+ "u:object_r:shell_data_file:s0";
// A test fixture that provides frequently required temp directories for tests
class ApexdUnitTest : public ::testing::Test {
@@ -113,10 +139,14 @@
ota_reserved_dir_ = StringPrintf("%s/ota-reserved", td_.path);
hash_tree_dir_ = StringPrintf("%s/apex-hash-tree", td_.path);
staged_session_dir_ = StringPrintf("%s/staged-session-dir", td_.path);
- config_ = {kTestApexdStatusSysprop, {built_in_dir_},
- data_dir_.c_str(), decompression_dir_.c_str(),
- ota_reserved_dir_.c_str(), hash_tree_dir_.c_str(),
- staged_session_dir_.c_str()};
+
+ vm_payload_disk_ = StringPrintf("%s/vm-payload", td_.path);
+
+ config_ = {kTestApexdStatusSysprop, {built_in_dir_},
+ data_dir_.c_str(), decompression_dir_.c_str(),
+ ota_reserved_dir_.c_str(), hash_tree_dir_.c_str(),
+ staged_session_dir_.c_str(), kTestVmPayloadMetadataPartitionProp,
+ kTestActiveApexSelinuxCtx};
}
const std::string& GetBuiltInDir() { return built_in_dir_; }
@@ -156,6 +186,17 @@
return StringPrintf("%s/%s", data_dir_.c_str(), target_name.c_str());
}
+ std::string AddBlockApex(const std::string& apex_name,
+ const std::string& public_key = "",
+ const std::string& root_digest = "") {
+ auto apex_path = vm_payload_disk_ + "2"; // second partition
+ auto apex_file = GetTestFile(apex_name);
+ WriteMetadata(apex_file, public_key, root_digest);
+ // loop_devices_ will be disposed after each test
+ loop_devices_.push_back(*WriteBlockApex(apex_file, apex_path));
+ return apex_path;
+ }
+
// Copies the compressed apex to |built_in_dir| and decompresses it to
// |decompressed_dir| and then hard links to |target_dir|
std::string PrepareCompressedApex(const std::string& name,
@@ -183,6 +224,12 @@
return result;
}
+ void SetBlockApexEnabled(bool enabled) {
+ // The first partition(1) is "metadata" partition
+ base::SetProperty(kTestVmPayloadMetadataPartitionProp,
+ enabled ? (vm_payload_disk_ + "1") : "");
+ }
+
protected:
void SetUp() override {
SetConfig(config_);
@@ -196,8 +243,28 @@
DeleteDirContent(ApexSession::GetSessionsDir());
}
+ void WriteMetadata(const std::string& apex_file,
+ const std::string& public_key,
+ const std::string& root_digest) {
+ android::microdroid::Metadata metadata;
- void TearDown() override { DeleteDirContent(ApexSession::GetSessionsDir()); }
+ auto apex = metadata.add_apexes();
+ apex->set_name("apex");
+ apex->set_public_key(public_key);
+ apex->set_root_digest(root_digest);
+
+ // The first partition is metadata partition
+ auto metadata_partition = vm_payload_disk_ + "1";
+ LOG(INFO) << "Writing metadata to " << metadata_partition;
+ std::ofstream out(metadata_partition);
+
+ android::microdroid::WriteMetadata(metadata, out);
+ }
+
+ void TearDown() override {
+ DeleteDirContent(ApexSession::GetSessionsDir());
+ SetBlockApexEnabled(false);
+ }
private:
TemporaryDir td_;
@@ -206,8 +273,11 @@
std::string decompression_dir_;
std::string ota_reserved_dir_;
std::string hash_tree_dir_;
+ std::string vm_payload_disk_;
+ std::string vm_payload_metadata_path_;
std::string staged_session_dir_;
ApexdConfig config_;
+ std::vector<loop::LoopbackDeviceUniqueFd> loop_devices_; // to be cleaned up
};
// Apex that does not have pre-installed version, does not get selected
@@ -218,7 +288,7 @@
"com.android.apex.test.sharedlibs_generated.v1.libvX.apex"));
auto& instance = ApexFileRepository::GetInstance();
// Pre-installed data needs to be present so that we can add data apex
- ASSERT_TRUE(IsOk(instance.AddPreInstalledApex({GetBuiltInDir()})));
+ ASSERT_THAT(instance.AddPreInstalledApex({GetBuiltInDir()}), Ok());
auto apexd_test_file = ApexFile::Open(AddDataApex("apex.apexd_test.apex"));
auto shim_v1 = ApexFile::Open(AddDataApex("com.android.apex.cts.shim.apex"));
@@ -226,7 +296,7 @@
// libs apex, but if they are the same version only the data apex will be.
auto shared_lib_2 = ApexFile::Open(
AddDataApex("com.android.apex.test.sharedlibs_generated.v1.libvX.apex"));
- ASSERT_TRUE(IsOk(instance.AddDataApex(GetDataDir())));
+ ASSERT_THAT(instance.AddDataApex(GetDataDir()), Ok());
const auto all_apex = instance.AllApexFilesByName();
// Pass a blank instance so that the data apex files are not considered
@@ -248,13 +318,13 @@
ApexFile::Open(AddPreInstalledApex("apex.apexd_test_v2.apex"));
AddPreInstalledApex("com.android.apex.cts.shim.apex");
auto& instance = ApexFileRepository::GetInstance();
- ASSERT_TRUE(IsOk(instance.AddPreInstalledApex({GetBuiltInDir()})));
+ ASSERT_THAT(instance.AddPreInstalledApex({GetBuiltInDir()}), Ok());
TemporaryDir data_dir;
AddDataApex("apex.apexd_test.apex");
auto shim_v2 =
ApexFile::Open(AddDataApex("com.android.apex.cts.shim.v2.apex"));
- ASSERT_TRUE(IsOk(instance.AddDataApex(GetDataDir())));
+ ASSERT_THAT(instance.AddDataApex(GetDataDir()), Ok());
auto all_apex = instance.AllApexFilesByName();
auto result = SelectApexForActivation(all_apex, instance);
@@ -271,12 +341,12 @@
AddPreInstalledApex("com.android.apex.cts.shim.apex");
// Initialize pre-installed APEX information
auto& instance = ApexFileRepository::GetInstance();
- ASSERT_TRUE(IsOk(instance.AddPreInstalledApex({GetBuiltInDir()})));
+ ASSERT_THAT(instance.AddPreInstalledApex({GetBuiltInDir()}), Ok());
auto apexd_test_file = ApexFile::Open(AddDataApex("apex.apexd_test.apex"));
auto shim_v1 = ApexFile::Open(AddDataApex("com.android.apex.cts.shim.apex"));
// Initialize ApexFile repo
- ASSERT_TRUE(IsOk(instance.AddDataApex(GetDataDir())));
+ ASSERT_THAT(instance.AddDataApex(GetDataDir()), Ok());
auto all_apex = instance.AllApexFilesByName();
auto result = SelectApexForActivation(all_apex, instance);
@@ -293,12 +363,12 @@
"com.android.apex.test.sharedlibs_generated.v1.libvX.apex"));
// Initialize pre-installed APEX information
auto& instance = ApexFileRepository::GetInstance();
- ASSERT_TRUE(IsOk(instance.AddPreInstalledApex({GetBuiltInDir()})));
+ ASSERT_THAT(instance.AddPreInstalledApex({GetBuiltInDir()}), Ok());
auto shared_lib_v2 = ApexFile::Open(
AddDataApex("com.android.apex.test.sharedlibs_generated.v2.libvY.apex"));
// Initialize data APEX information
- ASSERT_TRUE(IsOk(instance.AddDataApex(GetDataDir())));
+ ASSERT_THAT(instance.AddDataApex(GetDataDir()), Ok());
auto all_apex = instance.AllApexFilesByName();
auto result = SelectApexForActivation(all_apex, instance);
@@ -315,12 +385,12 @@
"com.android.apex.test.sharedlibs_generated.v2.libvY.apex"));
// Initialize pre-installed APEX information
auto& instance = ApexFileRepository::GetInstance();
- ASSERT_TRUE(IsOk(instance.AddPreInstalledApex({GetBuiltInDir()})));
+ ASSERT_THAT(instance.AddPreInstalledApex({GetBuiltInDir()}), Ok());
auto shared_lib_v1 = ApexFile::Open(
AddDataApex("com.android.apex.test.sharedlibs_generated.v1.libvX.apex"));
// Initialize data APEX information
- ASSERT_TRUE(IsOk(instance.AddDataApex(GetDataDir())));
+ ASSERT_THAT(instance.AddDataApex(GetDataDir()), Ok());
auto all_apex = instance.AllApexFilesByName();
auto result = SelectApexForActivation(all_apex, instance);
@@ -343,16 +413,14 @@
kDecompressedApexPackageSuffix);
// Assert output path is not empty
auto exists = PathExists(decompressed_file_path);
- ASSERT_TRUE(IsOk(exists));
- ASSERT_TRUE(*exists) << decompressed_file_path << " does not exist";
+ ASSERT_THAT(exists, HasValue(true));
// Assert that decompressed apex is same as original apex
const std::string original_apex_file_path =
GetTestFile("com.android.apex.compressed.v1_original.apex");
auto comparison_result =
CompareFiles(original_apex_file_path, decompressed_file_path);
- ASSERT_TRUE(IsOk(comparison_result));
- ASSERT_TRUE(*comparison_result);
+ ASSERT_THAT(comparison_result, HasValue(true));
// Assert that return value contains decompressed APEX
auto decompressed_apex = ApexFile::Open(decompressed_file_path);
@@ -383,37 +451,35 @@
auto result =
ValidateDecompressedApex(std::cref(*capex), std::cref(*decompressed_v1));
- ASSERT_TRUE(IsOk(result));
+ ASSERT_THAT(result, Ok());
// Validation checks version
auto decompressed_v2 = ApexFile::Open(
AddDataApex("com.android.apex.compressed.v2_original.apex"));
result =
ValidateDecompressedApex(std::cref(*capex), std::cref(*decompressed_v2));
- ASSERT_FALSE(IsOk(result));
ASSERT_THAT(
- result.error().message(),
- HasSubstr(
- "Compressed APEX has different version than decompressed APEX"));
+ result,
+ HasError(WithMessage(HasSubstr(
+ "Compressed APEX has different version than decompressed APEX"))));
// Validation check root digest
auto decompressed_v1_different_digest = ApexFile::Open(AddDataApex(
"com.android.apex.compressed.v1_different_digest_original.apex"));
result = ValidateDecompressedApex(
std::cref(*capex), std::cref(*decompressed_v1_different_digest));
- ASSERT_FALSE(IsOk(result));
- ASSERT_THAT(result.error().message(),
- HasSubstr("does not match with expected root digest"));
+ ASSERT_THAT(result, HasError(WithMessage(HasSubstr(
+ "does not match with expected root digest"))));
// Validation checks key
auto capex_different_key = ApexFile::Open(
AddDataApex("com.android.apex.compressed_different_key.capex"));
result = ValidateDecompressedApex(std::cref(*capex_different_key),
std::cref(*decompressed_v1));
- ASSERT_FALSE(IsOk(result));
ASSERT_THAT(
- result.error().message(),
- HasSubstr("Public key of compressed APEX is different than original"));
+ result,
+ HasError(WithMessage(HasSubstr(
+ "Public key of compressed APEX is different than original"))));
}
TEST_F(ApexdUnitTest, ProcessCompressedApexCanBeCalledMultipleTimes) {
@@ -464,16 +530,15 @@
GetDecompressionDir().c_str(), kOtaApexPackageSuffix);
// Assert output path is not empty
auto exists = PathExists(decompressed_file_path);
- ASSERT_TRUE(IsOk(exists));
- ASSERT_TRUE(*exists) << decompressed_file_path << " does not exist";
+ ASSERT_THAT(exists, HasValue(true))
+ << decompressed_file_path << " does not exist";
// Assert that decompressed apex is same as original apex
const std::string original_apex_file_path =
GetTestFile("com.android.apex.compressed.v1_original.apex");
auto comparison_result =
CompareFiles(original_apex_file_path, decompressed_file_path);
- ASSERT_TRUE(IsOk(comparison_result));
- ASSERT_TRUE(*comparison_result);
+ ASSERT_THAT(comparison_result, HasValue(true));
// Assert that return value contains the decompressed APEX
auto apex_file = ApexFile::Open(decompressed_file_path);
@@ -510,7 +575,7 @@
ASSERT_EQ(return_value.size(), 1u);
// Ota Apex should be cleaned up
- ASSERT_FALSE(*PathExists(ota_apex_path));
+ ASSERT_THAT(PathExists(ota_apex_path), HasValue(false));
ASSERT_EQ(return_value[0].GetPath(),
StringPrintf("%s/com.android.apex.compressed@1%s",
GetDecompressionDir().c_str(),
@@ -519,13 +584,12 @@
TEST_F(ApexdUnitTest, ShouldAllocateSpaceForDecompressionNewApex) {
auto& instance = ApexFileRepository::GetInstance();
- ASSERT_TRUE(IsOk(instance.AddPreInstalledApex({GetBuiltInDir()})));
+ ASSERT_THAT(instance.AddPreInstalledApex({GetBuiltInDir()}), Ok());
// A brand new compressed APEX is being introduced: selected
- auto result =
+ bool result =
ShouldAllocateSpaceForDecompression("com.android.brand.new", 1, instance);
- ASSERT_TRUE(IsOk(result));
- ASSERT_TRUE(*result);
+ ASSERT_TRUE(result);
}
TEST_F(ApexdUnitTest,
@@ -533,33 +597,30 @@
// Prepare fake pre-installed apex
AddPreInstalledApex("apex.apexd_test.apex");
auto& instance = ApexFileRepository::GetInstance();
- ASSERT_TRUE(IsOk(instance.AddPreInstalledApex({GetBuiltInDir()})));
+ ASSERT_THAT(instance.AddPreInstalledApex({GetBuiltInDir()}), Ok());
// An existing pre-installed APEX is now compressed in the OTA: selected
{
- auto result = ShouldAllocateSpaceForDecompression(
+ bool result = ShouldAllocateSpaceForDecompression(
"com.android.apex.test_package", 1, instance);
- ASSERT_TRUE(IsOk(result));
- ASSERT_TRUE(*result);
+ ASSERT_TRUE(result);
}
// Even if there is a data apex (lower version)
// Include data apex within calculation now
AddDataApex("apex.apexd_test_v2.apex");
- ASSERT_TRUE(IsOk(instance.AddDataApex(GetDataDir())));
+ ASSERT_THAT(instance.AddDataApex(GetDataDir()), Ok());
{
- auto result = ShouldAllocateSpaceForDecompression(
+ bool result = ShouldAllocateSpaceForDecompression(
"com.android.apex.test_package", 3, instance);
- ASSERT_TRUE(IsOk(result));
- ASSERT_TRUE(*result);
+ ASSERT_TRUE(result);
}
// But not if data apex has equal or higher version
{
- auto result = ShouldAllocateSpaceForDecompression(
+ bool result = ShouldAllocateSpaceForDecompression(
"com.android.apex.test_package", 2, instance);
- ASSERT_TRUE(IsOk(result));
- ASSERT_FALSE(*result);
+ ASSERT_FALSE(result);
}
}
@@ -567,16 +628,15 @@
// Prepare fake pre-installed apex
PrepareCompressedApex("com.android.apex.compressed.v1.capex");
auto& instance = ApexFileRepository::GetInstance();
- ASSERT_TRUE(IsOk(instance.AddPreInstalledApex({GetBuiltInDir()})));
- ASSERT_TRUE(IsOk(instance.AddDataApex(GetDataDir())));
+ ASSERT_THAT(instance.AddPreInstalledApex({GetBuiltInDir()}), Ok());
+ ASSERT_THAT(instance.AddDataApex(GetDataDir()), Ok());
{
// New Compressed apex has higher version than decompressed data apex:
// selected
- auto result = ShouldAllocateSpaceForDecompression(
+ bool result = ShouldAllocateSpaceForDecompression(
"com.android.apex.compressed", 2, instance);
- ASSERT_TRUE(IsOk(result));
- ASSERT_TRUE(*result)
+ ASSERT_TRUE(result)
<< "Higher version test with decompressed data returned false";
}
@@ -584,63 +644,79 @@
{
// New Compressed apex has same version as decompressed data apex: not
// selected
- auto result = ShouldAllocateSpaceForDecompression(
+ bool result = ShouldAllocateSpaceForDecompression(
"com.android.apex.compressed", 1, instance);
- ASSERT_TRUE(IsOk(result));
- ASSERT_FALSE(*result)
+ ASSERT_FALSE(result)
<< "Same version test with decompressed data returned true";
}
{
// New Compressed apex has lower version than decompressed data apex:
// selected
- auto result = ShouldAllocateSpaceForDecompression(
+ bool result = ShouldAllocateSpaceForDecompression(
"com.android.apex.compressed", 0, instance);
- ASSERT_TRUE(IsOk(result));
- ASSERT_TRUE(*result)
+ ASSERT_TRUE(result)
<< "lower version test with decompressed data returned false";
}
// Replace decompressed data apex with a higher version
ApexFileRepository instance_new(GetDecompressionDir());
- ASSERT_TRUE(IsOk(instance_new.AddPreInstalledApex({GetBuiltInDir()})));
+ ASSERT_THAT(instance_new.AddPreInstalledApex({GetBuiltInDir()}), Ok());
TemporaryDir data_dir_new;
fs::copy(GetTestFile("com.android.apex.compressed.v2_original.apex"),
data_dir_new.path);
- ASSERT_TRUE(IsOk(instance_new.AddDataApex(data_dir_new.path)));
+ ASSERT_THAT(instance_new.AddDataApex(data_dir_new.path), Ok());
{
// New Compressed apex has higher version as data apex: selected
- auto result = ShouldAllocateSpaceForDecompression(
+ bool result = ShouldAllocateSpaceForDecompression(
"com.android.apex.compressed", 3, instance_new);
- ASSERT_TRUE(IsOk(result));
- ASSERT_TRUE(*result) << "Higher version test with new data returned false";
+ ASSERT_TRUE(result) << "Higher version test with new data returned false";
}
{
// New Compressed apex has same version as data apex: not selected
- auto result = ShouldAllocateSpaceForDecompression(
+ bool result = ShouldAllocateSpaceForDecompression(
"com.android.apex.compressed", 2, instance_new);
- ASSERT_TRUE(IsOk(result));
- ASSERT_FALSE(*result) << "Same version test with new data returned true";
+ ASSERT_FALSE(result) << "Same version test with new data returned true";
}
{
// New Compressed apex has lower version than data apex: not selected
- auto result = ShouldAllocateSpaceForDecompression(
+ bool result = ShouldAllocateSpaceForDecompression(
"com.android.apex.compressed", 1, instance_new);
- ASSERT_TRUE(IsOk(result));
- ASSERT_FALSE(*result) << "lower version test with new data returned true";
+ ASSERT_FALSE(result) << "lower version test with new data returned true";
}
}
+TEST_F(ApexdUnitTest, CalculateSizeForCompressedApexEmptyList) {
+ ApexFileRepository instance;
+ int64_t result = CalculateSizeForCompressedApex({}, instance);
+ ASSERT_EQ(0LL, result);
+}
+
+TEST_F(ApexdUnitTest, CalculateSizeForCompressedApex) {
+ ApexFileRepository instance;
+ AddPreInstalledApex("com.android.apex.compressed.v1.capex");
+ ASSERT_THAT(instance.AddPreInstalledApex({GetBuiltInDir()}), Ok());
+
+ std::vector<std::tuple<std::string, int64_t, int64_t>> input = {
+ std::make_tuple("new_apex", 1, 1),
+ std::make_tuple("new_apex_2", 1, 2),
+ std::make_tuple("com.android.apex.compressed", 1, 4), // will be ignored
+ std::make_tuple("com.android.apex.compressed", 2, 8),
+ };
+ int64_t result = CalculateSizeForCompressedApex(input, instance);
+ ASSERT_EQ(1 + 2 + 8LL, result);
+}
+
TEST_F(ApexdUnitTest, ReserveSpaceForCompressedApexCreatesSingleFile) {
TemporaryDir dest_dir;
// Reserving space should create a single file in dest_dir with exact size
- ASSERT_TRUE(IsOk(ReserveSpaceForCompressedApex(100, dest_dir.path)));
+ ASSERT_THAT(ReserveSpaceForCompressedApex(100, dest_dir.path), Ok());
auto files = ReadDir(dest_dir.path, [](auto _) { return true; });
- ASSERT_TRUE(IsOk(files));
+ ASSERT_THAT(files, Ok());
ASSERT_EQ(files->size(), 1u);
EXPECT_EQ(fs::file_size((*files)[0]), 100u);
}
@@ -649,10 +725,10 @@
TemporaryDir dest_dir;
// Calling ReserveSpaceForCompressedApex multiple times should still create
// a single file
- ASSERT_TRUE(IsOk(ReserveSpaceForCompressedApex(100, dest_dir.path)));
- ASSERT_TRUE(IsOk(ReserveSpaceForCompressedApex(100, dest_dir.path)));
+ ASSERT_THAT(ReserveSpaceForCompressedApex(100, dest_dir.path), Ok());
+ ASSERT_THAT(ReserveSpaceForCompressedApex(100, dest_dir.path), Ok());
auto files = ReadDir(dest_dir.path, [](auto _) { return true; });
- ASSERT_TRUE(IsOk(files));
+ ASSERT_THAT(files, Ok());
ASSERT_EQ(files->size(), 1u);
EXPECT_EQ(fs::file_size((*files)[0]), 100u);
}
@@ -661,18 +737,19 @@
TemporaryDir dest_dir;
// Create a 100 byte file
- ASSERT_TRUE(IsOk(ReserveSpaceForCompressedApex(100, dest_dir.path)));
+ ASSERT_THAT(ReserveSpaceForCompressedApex(100, dest_dir.path), Ok());
// Should be able to shrink and grow the reserved space
- ASSERT_TRUE(IsOk(ReserveSpaceForCompressedApex(1000, dest_dir.path)));
+ ASSERT_THAT(ReserveSpaceForCompressedApex(1000, dest_dir.path), Ok());
+
auto files = ReadDir(dest_dir.path, [](auto _) { return true; });
- ASSERT_TRUE(IsOk(files));
+ ASSERT_THAT(files, Ok());
ASSERT_EQ(files->size(), 1u);
EXPECT_EQ(fs::file_size((*files)[0]), 1000u);
- ASSERT_TRUE(IsOk(ReserveSpaceForCompressedApex(10, dest_dir.path)));
+ ASSERT_THAT(ReserveSpaceForCompressedApex(10, dest_dir.path), Ok());
files = ReadDir(dest_dir.path, [](auto _) { return true; });
- ASSERT_TRUE(IsOk(files));
+ ASSERT_THAT(files, Ok());
ASSERT_EQ(files->size(), 1u);
EXPECT_EQ(fs::file_size((*files)[0]), 10u);
}
@@ -681,15 +758,15 @@
TemporaryDir dest_dir;
// Create a file first
- ASSERT_TRUE(IsOk(ReserveSpaceForCompressedApex(100, dest_dir.path)));
+ ASSERT_THAT(ReserveSpaceForCompressedApex(100, dest_dir.path), Ok());
auto files = ReadDir(dest_dir.path, [](auto _) { return true; });
- ASSERT_TRUE(IsOk(files));
+ ASSERT_THAT(files, Ok());
ASSERT_EQ(files->size(), 1u);
// Should delete the reserved file if size passed is 0
- ASSERT_TRUE(IsOk(ReserveSpaceForCompressedApex(0, dest_dir.path)));
+ ASSERT_THAT(ReserveSpaceForCompressedApex(0, dest_dir.path), Ok());
files = ReadDir(dest_dir.path, [](auto _) { return true; });
- ASSERT_TRUE(IsOk(files));
+ ASSERT_THAT(files, Ok());
ASSERT_EQ(files->size(), 0u);
}
@@ -702,38 +779,109 @@
// Create an ota_apex first
fs::copy(GetTestFile("com.android.apex.compressed.v1_original.apex"),
ota_apex_path);
- auto path_exists = PathExists(ota_apex_path);
- ASSERT_TRUE(*path_exists);
+ ASSERT_THAT(PathExists(ota_apex_path), HasValue(true));
};
create_ota_apex();
// Should not delete the reserved file if size passed is negative
- ASSERT_FALSE(IsOk(ReserveSpaceForCompressedApex(-1, dest_dir.path)));
- auto path_exists = PathExists(ota_apex_path);
- ASSERT_TRUE(*path_exists);
+ ASSERT_THAT(ReserveSpaceForCompressedApex(-1, dest_dir.path), Not(Ok()));
+ ASSERT_THAT(PathExists(ota_apex_path), HasValue(true));
// Should delete the reserved file if size passed is 0
- ASSERT_TRUE(IsOk(ReserveSpaceForCompressedApex(0, dest_dir.path)));
- path_exists = PathExists(ota_apex_path);
- ASSERT_FALSE(*path_exists);
+ ASSERT_THAT(ReserveSpaceForCompressedApex(0, dest_dir.path), Ok());
+ ASSERT_THAT(PathExists(ota_apex_path), HasValue(false));
create_ota_apex();
// Should delete the reserved file if size passed is positive
- ASSERT_TRUE(IsOk(ReserveSpaceForCompressedApex(10, dest_dir.path)));
- path_exists = PathExists(ota_apex_path);
- ASSERT_FALSE(*path_exists);
+ ASSERT_THAT(ReserveSpaceForCompressedApex(10, dest_dir.path), Ok());
+ ASSERT_THAT(PathExists(ota_apex_path), HasValue(false));
}
TEST_F(ApexdUnitTest, ReserveSpaceForCompressedApexErrorForNegativeValue) {
TemporaryDir dest_dir;
// Should return error if negative value is passed
- ASSERT_FALSE(IsOk(ReserveSpaceForCompressedApex(-1, dest_dir.path)));
+ ASSERT_THAT(ReserveSpaceForCompressedApex(-1, dest_dir.path), Not(Ok()));
+}
+
+TEST_F(ApexdUnitTest, GetStagedApexFilesNoChild) {
+ // Create staged session
+ auto apex_session = CreateStagedSession("apex.apexd_test.apex", 123);
+ apex_session->UpdateStateAndCommit(SessionState::STAGED);
+
+ // Query for its file
+ auto result = GetStagedApexFiles(123, {});
+
+ auto apex_file = ApexFile::Open(
+ StringPrintf("%s/apex.apexd_test.apex", GetStagedDir(123).c_str()));
+ ASSERT_THAT(result,
+ HasValue(UnorderedElementsAre(ApexFileEq(ByRef(*apex_file)))));
+}
+
+TEST_F(ApexdUnitTest, GetStagedApexFilesOnlyStaged) {
+ // Create staged session
+ auto apex_session = CreateStagedSession("apex.apexd_test.apex", 123);
+ apex_session->UpdateStateAndCommit(SessionState::VERIFIED);
+
+ // Query for its file
+ auto result = GetStagedApexFiles(123, {});
+
+ ASSERT_THAT(
+ result,
+ HasError(WithMessage(HasSubstr("Session 123 is not in state STAGED"))));
+}
+
+TEST_F(ApexdUnitTest, GetStagedApexFilesChecksNumberOfApexFiles) {
+ // Create staged session
+ auto apex_session = CreateStagedSession("apex.apexd_test.apex", 123);
+ apex_session->UpdateStateAndCommit(SessionState::STAGED);
+ auto staged_dir = GetStagedDir(123);
+
+ {
+ // Delete the staged apex file
+ DeleteDirContent(staged_dir);
+
+ // Query for its file
+ auto result = GetStagedApexFiles(123, {});
+ ASSERT_THAT(result, HasError(WithMessage(HasSubstr(
+ "Expected exactly one APEX file in directory"))));
+ ASSERT_THAT(result, HasError(WithMessage(HasSubstr("Found: 0"))));
+ }
+ {
+ // Copy multiple files to staged dir
+ fs::copy(GetTestFile("apex.apexd_test.apex"), staged_dir);
+ fs::copy(GetTestFile("apex.apexd_test_v2.apex"), staged_dir);
+
+ // Query for its file
+ auto result = GetStagedApexFiles(123, {});
+ ASSERT_THAT(result, HasError(WithMessage(HasSubstr(
+ "Expected exactly one APEX file in directory"))));
+ ASSERT_THAT(result, HasError(WithMessage(HasSubstr("Found: 2"))));
+ }
+}
+
+TEST_F(ApexdUnitTest, GetStagedApexFilesWithChildren) {
+ // Create staged session
+ auto parent_apex_session = CreateStagedSession("apex.apexd_test.apex", 123);
+ parent_apex_session->UpdateStateAndCommit(SessionState::STAGED);
+ auto child_session_1 = CreateStagedSession("apex.apexd_test.apex", 124);
+ auto child_session_2 = CreateStagedSession("apex.apexd_test.apex", 125);
+
+ // Query for its file
+ auto result = GetStagedApexFiles(123, {124, 125});
+
+ ASSERT_THAT(result, Ok());
+ auto child_apex_file_1 = ApexFile::Open(
+ StringPrintf("%s/apex.apexd_test.apex", GetStagedDir(124).c_str()));
+ auto child_apex_file_2 = ApexFile::Open(
+ StringPrintf("%s/apex.apexd_test.apex", GetStagedDir(125).c_str()));
+ ASSERT_THAT(*result,
+ UnorderedElementsAre(ApexFileEq(ByRef(*child_apex_file_1)),
+ ApexFileEq(ByRef(*child_apex_file_2))));
}
// A test fixture to use for tests that mount/unmount apexes.
class ApexdMountTest : public ApexdUnitTest {
public:
-
void UnmountOnTearDown(const std::string& apex_file) {
to_unmount_.push_back(apex_file);
}
@@ -742,7 +890,7 @@
void SetUp() final {
ApexdUnitTest::SetUp();
GetApexDatabaseForTesting().Reset();
- ASSERT_TRUE(IsOk(SetUpApexTestEnvironment()));
+ ASSERT_THAT(SetUpApexTestEnvironment(), Ok());
}
void TearDown() final {
@@ -764,35 +912,34 @@
std::string file_path = AddPreInstalledApex("apex.apexd_test.apex");
ApexFileRepository::GetInstance().AddPreInstalledApex({GetBuiltInDir()});
- ASSERT_TRUE(IsOk(ActivatePackage(file_path)));
+ ASSERT_THAT(ActivatePackage(file_path), Ok());
UnmountOnTearDown(file_path);
auto ret = InstallPackage(GetTestFile("apex.apexd_test.apex"));
- ASSERT_FALSE(IsOk(ret));
- ASSERT_THAT(ret.error().message(),
- HasSubstr("does not support non-staged update"));
+ ASSERT_THAT(
+ ret,
+ HasError(WithMessage(HasSubstr("does not support non-staged update"))));
}
TEST_F(ApexdMountTest, InstallPackageRejectsNoPreInstalledApex) {
auto ret = InstallPackage(GetTestFile("test.rebootless_apex_v1.apex"));
- ASSERT_FALSE(IsOk(ret));
ASSERT_THAT(
- ret.error().message(),
- HasSubstr("No active version found for package test.apex.rebootless"));
+ ret, HasError(WithMessage(HasSubstr(
+ "No active version found for package test.apex.rebootless"))));
}
TEST_F(ApexdMountTest, InstallPackageRejectsNoHashtree) {
std::string file_path = AddPreInstalledApex("test.rebootless_apex_v1.apex");
ApexFileRepository::GetInstance().AddPreInstalledApex({GetBuiltInDir()});
- ASSERT_TRUE(IsOk(ActivatePackage(file_path)));
+ ASSERT_THAT(ActivatePackage(file_path), Ok());
UnmountOnTearDown(file_path);
auto ret =
InstallPackage(GetTestFile("test.rebootless_apex_v2_no_hashtree.apex"));
- ASSERT_FALSE(IsOk(ret));
- ASSERT_THAT(ret.error().message(),
- HasSubstr(" does not have an embedded hash tree"));
+ ASSERT_THAT(
+ ret,
+ HasError(WithMessage(HasSubstr(" does not have an embedded hash tree"))));
}
TEST_F(ApexdMountTest, InstallPackageRejectsNoActiveApex) {
@@ -800,166 +947,158 @@
ApexFileRepository::GetInstance().AddPreInstalledApex({GetBuiltInDir()});
auto ret = InstallPackage(GetTestFile("test.rebootless_apex_v2.apex"));
- ASSERT_FALSE(IsOk(ret));
ASSERT_THAT(
- ret.error().message(),
- HasSubstr("No active version found for package test.apex.rebootless"));
+ ret, HasError(WithMessage(HasSubstr(
+ "No active version found for package test.apex.rebootless"))));
}
TEST_F(ApexdMountTest, InstallPackageRejectsManifestMismatch) {
std::string file_path = AddPreInstalledApex("test.rebootless_apex_v1.apex");
ApexFileRepository::GetInstance().AddPreInstalledApex({GetBuiltInDir()});
- ASSERT_TRUE(IsOk(ActivatePackage(file_path)));
+ ASSERT_THAT(ActivatePackage(file_path), Ok());
UnmountOnTearDown(file_path);
auto ret = InstallPackage(
GetTestFile("test.rebootless_apex_manifest_mismatch.apex"));
- ASSERT_FALSE(IsOk(ret));
ASSERT_THAT(
- ret.error().message(),
- HasSubstr(
- "Manifest inside filesystem does not match manifest outside it"));
+ ret,
+ HasError(WithMessage(HasSubstr(
+ "Manifest inside filesystem does not match manifest outside it"))));
}
TEST_F(ApexdMountTest, InstallPackageRejectsCorrupted) {
std::string file_path = AddPreInstalledApex("test.rebootless_apex_v1.apex");
ApexFileRepository::GetInstance().AddPreInstalledApex({GetBuiltInDir()});
- ASSERT_TRUE(IsOk(ActivatePackage(file_path)));
+ ASSERT_THAT(ActivatePackage(file_path), Ok());
UnmountOnTearDown(file_path);
auto ret = InstallPackage(GetTestFile("test.rebootless_apex_corrupted.apex"));
- ASSERT_FALSE(IsOk(ret));
- ASSERT_THAT(ret.error().message(), HasSubstr("Can't verify /dev/block/dm-"));
+ ASSERT_THAT(ret,
+ HasError(WithMessage(HasSubstr("Can't verify /dev/block/dm-"))));
}
TEST_F(ApexdMountTest, InstallPackageRejectsProvidesSharedLibs) {
std::string file_path = AddPreInstalledApex("test.rebootless_apex_v1.apex");
ApexFileRepository::GetInstance().AddPreInstalledApex({GetBuiltInDir()});
- ASSERT_TRUE(IsOk(ActivatePackage(file_path)));
+ ASSERT_THAT(ActivatePackage(file_path), Ok());
UnmountOnTearDown(file_path);
auto ret = InstallPackage(
GetTestFile("test.rebootless_apex_provides_sharedlibs.apex"));
- ASSERT_FALSE(IsOk(ret));
- ASSERT_THAT(ret.error().message(), HasSubstr(" is a shared libs APEX"));
+ ASSERT_THAT(ret, HasError(WithMessage(HasSubstr(" is a shared libs APEX"))));
}
TEST_F(ApexdMountTest, InstallPackageRejectsProvidesNativeLibs) {
std::string file_path = AddPreInstalledApex("test.rebootless_apex_v1.apex");
ApexFileRepository::GetInstance().AddPreInstalledApex({GetBuiltInDir()});
- ASSERT_TRUE(IsOk(ActivatePackage(file_path)));
+ ASSERT_THAT(ActivatePackage(file_path), Ok());
UnmountOnTearDown(file_path);
auto ret = InstallPackage(
GetTestFile("test.rebootless_apex_provides_native_libs.apex"));
- ASSERT_FALSE(IsOk(ret));
- ASSERT_THAT(ret.error().message(), HasSubstr(" provides native libs"));
+ ASSERT_THAT(ret, HasError(WithMessage(HasSubstr(" provides native libs"))));
}
TEST_F(ApexdMountTest, InstallPackageRejectsRequiresSharedApexLibs) {
std::string file_path = AddPreInstalledApex("test.rebootless_apex_v1.apex");
ApexFileRepository::GetInstance().AddPreInstalledApex({GetBuiltInDir()});
- ASSERT_TRUE(IsOk(ActivatePackage(file_path)));
+ ASSERT_THAT(ActivatePackage(file_path), Ok());
UnmountOnTearDown(file_path);
auto ret = InstallPackage(
GetTestFile("test.rebootless_apex_requires_shared_apex_libs.apex"));
- ASSERT_FALSE(IsOk(ret));
- ASSERT_THAT(ret.error().message(), HasSubstr(" requires shared apex libs"));
+ ASSERT_THAT(ret,
+ HasError(WithMessage(HasSubstr(" requires shared apex libs"))));
}
TEST_F(ApexdMountTest, InstallPackageRejectsJniLibs) {
std::string file_path = AddPreInstalledApex("test.rebootless_apex_v1.apex");
ApexFileRepository::GetInstance().AddPreInstalledApex({GetBuiltInDir()});
- ASSERT_TRUE(IsOk(ActivatePackage(file_path)));
+ ASSERT_THAT(ActivatePackage(file_path), Ok());
UnmountOnTearDown(file_path);
auto ret = InstallPackage(GetTestFile("test.rebootless_apex_jni_libs.apex"));
- ASSERT_FALSE(IsOk(ret));
- ASSERT_THAT(ret.error().message(), HasSubstr(" requires JNI libs"));
+ ASSERT_THAT(ret, HasError(WithMessage(HasSubstr(" requires JNI libs"))));
}
TEST_F(ApexdMountTest, InstallPackageRejectsAddRequiredNativeLib) {
std::string file_path = AddPreInstalledApex("test.rebootless_apex_v1.apex");
ApexFileRepository::GetInstance().AddPreInstalledApex({GetBuiltInDir()});
- ASSERT_TRUE(IsOk(ActivatePackage(file_path)));
+ ASSERT_THAT(ActivatePackage(file_path), Ok());
UnmountOnTearDown(file_path);
auto ret =
InstallPackage(GetTestFile("test.rebootless_apex_add_native_lib.apex"));
- ASSERT_FALSE(IsOk(ret));
- ASSERT_THAT(ret.error().message(),
- HasSubstr("Set of native libs required by"));
ASSERT_THAT(
- ret.error().message(),
- HasSubstr("differs from the one required by the currently active"));
+ ret, HasError(WithMessage(HasSubstr("Set of native libs required by"))));
+ ASSERT_THAT(ret,
+ HasError(WithMessage(HasSubstr(
+ "differs from the one required by the currently active"))));
}
TEST_F(ApexdMountTest, InstallPackageRejectsRemovesRequiredNativeLib) {
std::string file_path = AddPreInstalledApex("test.rebootless_apex_v1.apex");
ApexFileRepository::GetInstance().AddPreInstalledApex({GetBuiltInDir()});
- ASSERT_TRUE(IsOk(ActivatePackage(file_path)));
+ ASSERT_THAT(ActivatePackage(file_path), Ok());
UnmountOnTearDown(file_path);
auto ret = InstallPackage(
GetTestFile("test.rebootless_apex_remove_native_lib.apex"));
- ASSERT_FALSE(IsOk(ret));
- ASSERT_THAT(ret.error().message(),
- HasSubstr("Set of native libs required by"));
ASSERT_THAT(
- ret.error().message(),
- HasSubstr("differs from the one required by the currently active"));
+ ret, HasError(WithMessage(HasSubstr("Set of native libs required by"))));
+ ASSERT_THAT(ret,
+ HasError(WithMessage(HasSubstr(
+ "differs from the one required by the currently active"))));
}
TEST_F(ApexdMountTest, InstallPackageRejectsAppInApex) {
std::string file_path = AddPreInstalledApex("test.rebootless_apex_v1.apex");
ApexFileRepository::GetInstance().AddPreInstalledApex({GetBuiltInDir()});
- ASSERT_TRUE(IsOk(ActivatePackage(file_path)));
+ ASSERT_THAT(ActivatePackage(file_path), Ok());
UnmountOnTearDown(file_path);
auto ret =
InstallPackage(GetTestFile("test.rebootless_apex_app_in_apex.apex"));
- ASSERT_FALSE(IsOk(ret));
- ASSERT_THAT(ret.error().message(), HasSubstr("contains app inside"));
+ ASSERT_THAT(ret, HasError(WithMessage(HasSubstr("contains app inside"))));
}
TEST_F(ApexdMountTest, InstallPackageRejectsPrivAppInApex) {
std::string file_path = AddPreInstalledApex("test.rebootless_apex_v1.apex");
ApexFileRepository::GetInstance().AddPreInstalledApex({GetBuiltInDir()});
- ASSERT_TRUE(IsOk(ActivatePackage(file_path)));
+ ASSERT_THAT(ActivatePackage(file_path), Ok());
UnmountOnTearDown(file_path);
auto ret =
InstallPackage(GetTestFile("test.rebootless_apex_priv_app_in_apex.apex"));
- ASSERT_FALSE(IsOk(ret));
- ASSERT_THAT(ret.error().message(), HasSubstr("contains priv-app inside"));
+ ASSERT_THAT(ret,
+ HasError(WithMessage(HasSubstr("contains priv-app inside"))));
}
TEST_F(ApexdMountTest, InstallPackagePreInstallVersionActive) {
std::string file_path = AddPreInstalledApex("test.rebootless_apex_v1.apex");
ApexFileRepository::GetInstance().AddPreInstalledApex({GetBuiltInDir()});
- ASSERT_TRUE(IsOk(ActivatePackage(file_path)));
+ ASSERT_THAT(ActivatePackage(file_path), Ok());
UnmountOnTearDown(file_path);
{
auto active_apex = GetActivePackage("test.apex.rebootless");
- ASSERT_TRUE(IsOk(active_apex));
+ ASSERT_THAT(active_apex, Ok());
ASSERT_EQ(active_apex->GetPath(), file_path);
}
auto ret = InstallPackage(GetTestFile("test.rebootless_apex_v2.apex"));
- ASSERT_TRUE(IsOk(ret));
+ ASSERT_THAT(ret, Ok());
UnmountOnTearDown(ret->GetPath());
auto apex_mounts = GetApexMounts();
@@ -970,12 +1109,12 @@
// Check that /apex/test.apex.rebootless is a bind mount of
// /apex/test.apex.rebootless@2.
auto manifest = ReadManifest("/apex/test.apex.rebootless/apex_manifest.pb");
- ASSERT_TRUE(IsOk(manifest));
+ ASSERT_THAT(manifest, Ok());
ASSERT_EQ(2u, manifest->version());
// Check that GetActivePackage correctly reports upgraded version.
auto active_apex = GetActivePackage("test.apex.rebootless");
- ASSERT_TRUE(IsOk(active_apex));
+ ASSERT_THAT(active_apex, Ok());
ASSERT_EQ(active_apex->GetPath(), ret->GetPath());
// Check that pre-installed APEX is still around
@@ -996,17 +1135,17 @@
std::string file_path = AddPreInstalledApex("test.rebootless_apex_v1.apex");
ApexFileRepository::GetInstance().AddPreInstalledApex({GetBuiltInDir()});
- ASSERT_TRUE(IsOk(ActivatePackage(file_path)));
+ ASSERT_THAT(ActivatePackage(file_path), Ok());
UnmountOnTearDown(file_path);
{
auto active_apex = GetActivePackage("test.apex.rebootless");
- ASSERT_TRUE(IsOk(active_apex));
+ ASSERT_THAT(active_apex, Ok());
ASSERT_EQ(active_apex->GetPath(), file_path);
}
auto ret = InstallPackage(GetTestFile("test.rebootless_apex_v1.apex"));
- ASSERT_TRUE(IsOk(ret));
+ ASSERT_THAT(ret, Ok());
UnmountOnTearDown(ret->GetPath());
auto apex_mounts = GetApexMounts();
@@ -1016,7 +1155,7 @@
// Check that GetActivePackage correctly reports upgraded version.
auto active_apex = GetActivePackage("test.apex.rebootless");
- ASSERT_TRUE(IsOk(active_apex));
+ ASSERT_THAT(active_apex, Ok());
ASSERT_EQ(active_apex->GetPath(), ret->GetPath());
// Check that pre-installed APEX is still around
@@ -1038,17 +1177,17 @@
ApexFileRepository::GetInstance().AddPreInstalledApex({GetBuiltInDir()});
std::string file_path = AddDataApex("test.rebootless_apex_v1.apex");
- ASSERT_TRUE(IsOk(ActivatePackage(file_path)));
+ ASSERT_THAT(ActivatePackage(file_path), Ok());
UnmountOnTearDown(file_path);
{
auto active_apex = GetActivePackage("test.apex.rebootless");
- ASSERT_TRUE(IsOk(active_apex));
+ ASSERT_THAT(active_apex, Ok());
ASSERT_EQ(active_apex->GetPath(), file_path);
}
auto ret = InstallPackage(GetTestFile("test.rebootless_apex_v2.apex"));
- ASSERT_TRUE(IsOk(ret));
+ ASSERT_THAT(ret, Ok());
UnmountOnTearDown(ret->GetPath());
auto apex_mounts = GetApexMounts();
@@ -1059,12 +1198,12 @@
// Check that /apex/test.apex.rebootless is a bind mount of
// /apex/test.apex.rebootless@2.
auto manifest = ReadManifest("/apex/test.apex.rebootless/apex_manifest.pb");
- ASSERT_TRUE(IsOk(manifest));
+ ASSERT_THAT(manifest, Ok());
ASSERT_EQ(2u, manifest->version());
// Check that GetActivePackage correctly reports upgraded version.
auto active_apex = GetActivePackage("test.apex.rebootless");
- ASSERT_TRUE(IsOk(active_apex));
+ ASSERT_THAT(active_apex, Ok());
ASSERT_EQ(active_apex->GetPath(), ret->GetPath());
// Check that previously active APEX was deleted.
@@ -1087,17 +1226,17 @@
std::string file_path = AddDataApex("test.rebootless_apex_v1.apex",
"test.apex.rebootless@1_1.apex");
- ASSERT_TRUE(IsOk(ActivatePackage(file_path)));
+ ASSERT_THAT(ActivatePackage(file_path), Ok());
UnmountOnTearDown(file_path);
{
auto active_apex = GetActivePackage("test.apex.rebootless");
- ASSERT_TRUE(IsOk(active_apex));
+ ASSERT_THAT(active_apex, Ok());
ASSERT_EQ(active_apex->GetPath(), file_path);
}
auto ret = InstallPackage(GetTestFile("test.rebootless_apex_v1.apex"));
- ASSERT_TRUE(IsOk(ret));
+ ASSERT_THAT(ret, Ok());
UnmountOnTearDown(ret->GetPath());
auto apex_mounts = GetApexMounts();
@@ -1108,12 +1247,12 @@
// Check that /apex/test.apex.rebootless is a bind mount of
// /apex/test.apex.rebootless@2.
auto manifest = ReadManifest("/apex/test.apex.rebootless/apex_manifest.pb");
- ASSERT_TRUE(IsOk(manifest));
+ ASSERT_THAT(manifest, Ok());
ASSERT_EQ(1u, manifest->version());
// Check that GetActivePackage correctly reports upgraded version.
auto active_apex = GetActivePackage("test.apex.rebootless");
- ASSERT_TRUE(IsOk(active_apex));
+ ASSERT_THAT(active_apex, Ok());
ASSERT_EQ(active_apex->GetPath(), ret->GetPath());
// Check that we correctly resolved active apex path collision.
@@ -1139,17 +1278,17 @@
ApexFileRepository::GetInstance().AddPreInstalledApex({GetBuiltInDir()});
std::string file_path = AddDataApex("test.rebootless_apex_v2.apex");
- ASSERT_TRUE(IsOk(ActivatePackage(file_path)));
+ ASSERT_THAT(ActivatePackage(file_path), Ok());
UnmountOnTearDown(file_path);
{
auto active_apex = GetActivePackage("test.apex.rebootless");
- ASSERT_TRUE(IsOk(active_apex));
+ ASSERT_THAT(active_apex, Ok());
ASSERT_EQ(active_apex->GetPath(), file_path);
}
auto ret = InstallPackage(GetTestFile("test.rebootless_apex_v2.apex"));
- ASSERT_TRUE(IsOk(ret));
+ ASSERT_THAT(ret, Ok());
UnmountOnTearDown(ret->GetPath());
auto apex_mounts = GetApexMounts();
@@ -1160,12 +1299,12 @@
// Check that /apex/test.apex.rebootless is a bind mount of
// /apex/test.apex.rebootless@2.
auto manifest = ReadManifest("/apex/test.apex.rebootless/apex_manifest.pb");
- ASSERT_TRUE(IsOk(manifest));
+ ASSERT_THAT(manifest, Ok());
ASSERT_EQ(2u, manifest->version());
// Check that GetActivePackage correctly reports upgraded version.
auto active_apex = GetActivePackage("test.apex.rebootless");
- ASSERT_TRUE(IsOk(active_apex));
+ ASSERT_THAT(active_apex, Ok());
ASSERT_EQ(active_apex->GetPath(), ret->GetPath());
// Check that previously active APEX was deleted.
@@ -1186,12 +1325,12 @@
std::string file_path = AddPreInstalledApex("test.rebootless_apex_v1.apex");
ApexFileRepository::GetInstance().AddPreInstalledApex({GetBuiltInDir()});
- ASSERT_TRUE(IsOk(ActivatePackage(file_path)));
+ ASSERT_THAT(ActivatePackage(file_path), Ok());
UnmountOnTearDown(file_path);
{
auto active_apex = GetActivePackage("test.apex.rebootless");
- ASSERT_TRUE(IsOk(active_apex));
+ ASSERT_THAT(active_apex, Ok());
ASSERT_EQ(active_apex->GetPath(), file_path);
}
@@ -1200,7 +1339,7 @@
ASSERT_NE(-1, fd.get());
auto ret = InstallPackage(GetTestFile("test.rebootless_apex_v2.apex"));
- ASSERT_FALSE(IsOk(ret));
+ ASSERT_THAT(ret, Not(Ok()));
auto apex_mounts = GetApexMounts();
ASSERT_THAT(apex_mounts,
@@ -1209,7 +1348,7 @@
// Check that GetActivePackage correctly reports upgraded version.
auto active_apex = GetActivePackage("test.apex.rebootless");
- ASSERT_TRUE(IsOk(active_apex));
+ ASSERT_THAT(active_apex, Ok());
ASSERT_EQ(active_apex->GetPath(), file_path);
// Check that old APEX is still around
@@ -1231,12 +1370,12 @@
std::string file_path = AddDataApex("test.rebootless_apex_v1.apex");
- ASSERT_TRUE(IsOk(ActivatePackage(file_path)));
+ ASSERT_THAT(ActivatePackage(file_path), Ok());
UnmountOnTearDown(file_path);
{
auto active_apex = GetActivePackage("test.apex.rebootless");
- ASSERT_TRUE(IsOk(active_apex));
+ ASSERT_THAT(active_apex, Ok());
ASSERT_EQ(active_apex->GetPath(), file_path);
}
@@ -1245,7 +1384,7 @@
ASSERT_NE(-1, fd.get());
auto ret = InstallPackage(GetTestFile("test.rebootless_apex_v2.apex"));
- ASSERT_FALSE(IsOk(ret));
+ ASSERT_THAT(ret, Not(Ok()));
auto apex_mounts = GetApexMounts();
ASSERT_THAT(apex_mounts,
@@ -1254,7 +1393,7 @@
// Check that GetActivePackage correctly reports old apex.
auto active_apex = GetActivePackage("test.apex.rebootless");
- ASSERT_TRUE(IsOk(active_apex));
+ ASSERT_THAT(active_apex, Ok());
ASSERT_EQ(active_apex->GetPath(), file_path);
// Check that old APEX is still around
@@ -1277,8 +1416,8 @@
UnmountOnTearDown(apex_1);
UnmountOnTearDown(apex_2);
- ASSERT_TRUE(IsOk(ActivatePackage(apex_1)));
- ASSERT_TRUE(IsOk(ActivatePackage(apex_2)));
+ ASSERT_THAT(ActivatePackage(apex_1), Ok());
+ ASSERT_THAT(ActivatePackage(apex_2), Ok());
// Call OnAllPackagesActivated to create /apex/apex-info-list.xml.
OnAllPackagesActivated(/* is_bootstrap= */ false);
@@ -1286,7 +1425,7 @@
ASSERT_EQ(0, access("/apex/apex-info-list.xml", F_OK));
auto ret = InstallPackage(GetTestFile("test.rebootless_apex_v2.apex"));
- ASSERT_TRUE(IsOk(ret));
+ ASSERT_THAT(ret, Ok());
UnmountOnTearDown(ret->GetPath());
ASSERT_EQ(access("/apex/apex-info-list.xml", F_OK), 0);
@@ -1316,15 +1455,60 @@
ApexInfoXmlEq(apex_info_xml_3)));
}
+TEST_F(ApexdMountTest, ActivatePackageBannedName) {
+ auto status = ActivatePackage(GetTestFile("sharedlibs.apex"));
+ ASSERT_THAT(status,
+ HasError(WithMessage("Package name sharedlibs is not allowed.")));
+}
+
+TEST_F(ApexdMountTest, ActivatePackageNoCode) {
+ std::string file_path = AddPreInstalledApex("apex.apexd_test_nocode.apex");
+ ApexFileRepository::GetInstance().AddPreInstalledApex({GetBuiltInDir()});
+
+ ASSERT_THAT(ActivatePackage(file_path), Ok());
+ UnmountOnTearDown(file_path);
+
+ std::string mountinfo;
+ ASSERT_TRUE(ReadFileToString("/proc/self/mountinfo", &mountinfo));
+ bool found_apex_mountpoint = false;
+ for (const auto& line : Split(mountinfo, "\n")) {
+ std::vector<std::string> tokens = Split(line, " ");
+ // line format:
+ // mnt_id parent_mnt_id major:minor source target option propagation_type
+ // ex) 33 260:19 / /apex rw,nosuid,nodev -
+ if (tokens.size() >= 7 &&
+ tokens[4] == "/apex/com.android.apex.test_package@1") {
+ found_apex_mountpoint = true;
+ // Make sure that option contains noexec
+ std::vector<std::string> options = Split(tokens[5], ",");
+ EXPECT_THAT(options, Contains("noexec"));
+ break;
+ }
+ }
+ EXPECT_TRUE(found_apex_mountpoint);
+}
+
+TEST_F(ApexdMountTest, ActivatePackageManifestMissmatch) {
+ std::string file_path =
+ AddPreInstalledApex("apex.apexd_test_manifest_mismatch.apex");
+ ApexFileRepository::GetInstance().AddPreInstalledApex({GetBuiltInDir()});
+
+ auto status = ActivatePackage(file_path);
+ ASSERT_THAT(
+ status,
+ HasError(WithMessage(HasSubstr(
+ "Manifest inside filesystem does not match manifest outside it"))));
+}
+
TEST_F(ApexdMountTest, ActivatePackage) {
std::string file_path = AddPreInstalledApex("apex.apexd_test.apex");
ApexFileRepository::GetInstance().AddPreInstalledApex({GetBuiltInDir()});
- ASSERT_TRUE(IsOk(ActivatePackage(file_path)));
+ ASSERT_THAT(ActivatePackage(file_path), Ok());
UnmountOnTearDown(file_path);
auto active_apex = GetActivePackage("com.android.apex.test_package");
- ASSERT_TRUE(IsOk(active_apex));
+ ASSERT_THAT(active_apex, Ok());
ASSERT_EQ(active_apex->GetPath(), file_path);
auto apex_mounts = GetApexMounts();
@@ -1332,13 +1516,235 @@
UnorderedElementsAre("/apex/com.android.apex.test_package",
"/apex/com.android.apex.test_package@1"));
- ASSERT_TRUE(IsOk(DeactivatePackage(file_path)));
- ASSERT_FALSE(IsOk(GetActivePackage("com.android.apex.test_package")));
+ ASSERT_THAT(DeactivatePackage(file_path), Ok());
+ ASSERT_THAT(GetActivePackage("com.android.apex.test_package"), Not(Ok()));
auto new_apex_mounts = GetApexMounts();
ASSERT_EQ(new_apex_mounts.size(), 0u);
}
+TEST_F(ApexdMountTest, ActivatePackageShowsUpInMountedApexDatabase) {
+ std::string file_path = AddPreInstalledApex("apex.apexd_test.apex");
+ ApexFileRepository::GetInstance().AddPreInstalledApex({GetBuiltInDir()});
+
+ ASSERT_THAT(ActivatePackage(file_path), Ok());
+ UnmountOnTearDown(file_path);
+
+ auto active_apex = GetActivePackage("com.android.apex.test_package");
+ ASSERT_THAT(active_apex, Ok());
+ ASSERT_EQ(active_apex->GetPath(), file_path);
+
+ auto apex_mounts = GetApexMounts();
+ ASSERT_THAT(apex_mounts,
+ UnorderedElementsAre("/apex/com.android.apex.test_package",
+ "/apex/com.android.apex.test_package@1"));
+
+ // Check that mounted apex database contains information about our APEX.
+ auto& db = GetApexDatabaseForTesting();
+ std::optional<MountedApexData> mounted_apex;
+ db.ForallMountedApexes("com.android.apex.test_package",
+ [&](const MountedApexData& d, bool active) {
+ if (active) {
+ mounted_apex.emplace(d);
+ }
+ });
+ ASSERT_TRUE(mounted_apex)
+ << "Haven't found com.android.apex.test_package in the database of "
+ << "mounted apexes";
+}
+
+TEST_F(ApexdMountTest, ActivatePackageNoHashtree) {
+ AddPreInstalledApex("apex.apexd_test.apex");
+ ApexFileRepository::GetInstance().AddPreInstalledApex({GetBuiltInDir()});
+
+ std::string file_path = AddDataApex("apex.apexd_test_no_hashtree.apex");
+ ASSERT_THAT(ActivatePackage(file_path), Ok());
+ UnmountOnTearDown(file_path);
+
+ // Check that hashtree was generated
+ std::string hashtree_path =
+ GetHashTreeDir() + "/com.android.apex.test_package@1";
+ ASSERT_EQ(0, access(hashtree_path.c_str(), F_OK));
+
+ // Check that block device can be read.
+ auto block_device = GetBlockDeviceForApex("com.android.apex.test_package@1");
+ ASSERT_THAT(block_device, Ok());
+ ASSERT_THAT(ReadDevice(*block_device), Ok());
+}
+
+TEST_F(ApexdMountTest, ActivatePackageNoHashtreeShowsUpInMountedDatabase) {
+ AddPreInstalledApex("apex.apexd_test.apex");
+ ApexFileRepository::GetInstance().AddPreInstalledApex({GetBuiltInDir()});
+
+ std::string file_path = AddDataApex("apex.apexd_test_no_hashtree.apex");
+ ASSERT_THAT(ActivatePackage(file_path), Ok());
+ UnmountOnTearDown(file_path);
+
+ // Get loop devices that were used to mount APEX.
+ auto children = ListChildLoopDevices("com.android.apex.test_package@1");
+ ASSERT_THAT(children, Ok());
+ ASSERT_EQ(2u, children->size())
+ << "Unexpected number of children: " << Join(*children, ",");
+
+ auto& db = GetApexDatabaseForTesting();
+ std::optional<MountedApexData> mounted_apex;
+ db.ForallMountedApexes("com.android.apex.test_package",
+ [&](const MountedApexData& d, bool active) {
+ if (active) {
+ mounted_apex.emplace(d);
+ }
+ });
+ ASSERT_TRUE(mounted_apex)
+ << "Haven't found com.android.apex.test_package@1 in the database of "
+ << "mounted apexes";
+
+ ASSERT_EQ(file_path, mounted_apex->full_path);
+ ASSERT_EQ("/apex/com.android.apex.test_package@1", mounted_apex->mount_point);
+ ASSERT_EQ("com.android.apex.test_package@1", mounted_apex->device_name);
+ // For loops we only check that both loop_name and hashtree_loop_name are
+ // children of the top device mapper device.
+ ASSERT_THAT(*children, Contains(mounted_apex->loop_name));
+ ASSERT_THAT(*children, Contains(mounted_apex->hashtree_loop_name));
+ ASSERT_NE(mounted_apex->loop_name, mounted_apex->hashtree_loop_name);
+}
+
+TEST_F(ApexdMountTest, DeactivePackageFreesLoopDevices) {
+ AddPreInstalledApex("apex.apexd_test.apex");
+ ApexFileRepository::GetInstance().AddPreInstalledApex({GetBuiltInDir()});
+
+ std::string file_path = AddDataApex("apex.apexd_test_no_hashtree.apex");
+ ASSERT_THAT(ActivatePackage(file_path), Ok());
+ UnmountOnTearDown(file_path);
+
+ // Get loop devices that were used to mount APEX.
+ auto children = ListChildLoopDevices("com.android.apex.test_package@1");
+ ASSERT_THAT(children, Ok());
+ ASSERT_EQ(2u, children->size())
+ << "Unexpected number of children: " << Join(*children, ",");
+
+ ASSERT_THAT(DeactivatePackage(file_path), Ok());
+ for (const auto& loop : *children) {
+ struct loop_info li;
+ unique_fd fd(TEMP_FAILURE_RETRY(open(loop.c_str(), O_RDWR | O_CLOEXEC)));
+ EXPECT_NE(-1, fd.get())
+ << "Failed to open " << loop << " : " << strerror(errno);
+ EXPECT_EQ(-1, ioctl(fd.get(), LOOP_GET_STATUS, &li))
+ << loop << " is still alive";
+ EXPECT_EQ(ENXIO, errno) << "Unexpected errno : " << strerror(errno);
+ }
+}
+
+TEST_F(ApexdMountTest, NoHashtreeApexNewSessionDoesNotImpactActivePackage) {
+ MockCheckpointInterface checkpoint_interface;
+ checkpoint_interface.SetSupportsCheckpoint(true);
+ InitializeVold(&checkpoint_interface);
+
+ AddPreInstalledApex("apex.apexd_test_no_hashtree.apex");
+ ApexFileRepository::GetInstance().AddPreInstalledApex({GetBuiltInDir()});
+
+ std::string file_path = AddDataApex("apex.apexd_test_no_hashtree.apex");
+ ASSERT_THAT(ActivatePackage(file_path), Ok());
+ UnmountOnTearDown(file_path);
+
+ ASSERT_THAT(CreateStagedSession("apex.apexd_test_no_hashtree_2.apex", 239),
+ Ok());
+ auto status =
+ SubmitStagedSession(239, {}, /* has_rollback_enabled= */ false,
+ /* is_rollback= */ false, /* rollback_id= */ -1);
+ ASSERT_THAT(status, Ok());
+
+ // Check that new hashtree file was created.
+ {
+ std::string hashtree_path =
+ GetHashTreeDir() + "/com.android.apex.test_package@1.new";
+ ASSERT_THAT(PathExists(hashtree_path), HasValue(true))
+ << hashtree_path << " does not exist";
+ }
+ // Check that active hashtree is still there.
+ {
+ std::string hashtree_path =
+ GetHashTreeDir() + "/com.android.apex.test_package@1";
+ ASSERT_THAT(PathExists(hashtree_path), HasValue(true))
+ << hashtree_path << " does not exist";
+ }
+
+ // Check that block device of active APEX can still be read.
+ auto block_device = GetBlockDeviceForApex("com.android.apex.test_package@1");
+ ASSERT_THAT(block_device, Ok());
+ ASSERT_THAT(ReadDevice(*block_device), Ok());
+}
+
+TEST_F(ApexdMountTest, NoHashtreeApexStagePackagesMovesHashtree) {
+ MockCheckpointInterface checkpoint_interface;
+ checkpoint_interface.SetSupportsCheckpoint(true);
+ InitializeVold(&checkpoint_interface);
+
+ AddPreInstalledApex("apex.apexd_test_no_hashtree.apex");
+ ApexFileRepository::GetInstance().AddPreInstalledApex({GetBuiltInDir()});
+
+ 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;
+ };
+
+ ASSERT_THAT(CreateStagedSession("apex.apexd_test_no_hashtree_2.apex", 37),
+ Ok());
+ auto status =
+ SubmitStagedSession(37, {}, /* has_rollback_enabled= */ false,
+ /* is_rollback= */ false, /* rollback_id= */ -1);
+ ASSERT_THAT(status, Ok());
+ auto staged_apex = std::move((*status)[0]);
+
+ // Check that new hashtree file was created.
+ std::vector<uint8_t> original_hashtree_data;
+ {
+ std::string hashtree_path =
+ GetHashTreeDir() + "/com.android.apex.test_package@1.new";
+ ASSERT_THAT(PathExists(hashtree_path), HasValue(true));
+ original_hashtree_data = read_fn(hashtree_path);
+ }
+
+ ASSERT_THAT(StagePackages({staged_apex.GetPath()}), Ok());
+ // Check that hashtree file was moved.
+ {
+ std::string hashtree_path =
+ GetHashTreeDir() + "/com.android.apex.test_package@1.new";
+ ASSERT_THAT(PathExists(hashtree_path), HasValue(false));
+ }
+ {
+ std::string hashtree_path =
+ GetHashTreeDir() + "/com.android.apex.test_package@1";
+ ASSERT_THAT(PathExists(hashtree_path), HasValue(true));
+ std::vector<uint8_t> moved_hashtree_data = read_fn(hashtree_path);
+ ASSERT_EQ(moved_hashtree_data, original_hashtree_data);
+ }
+}
+
+TEST_F(ApexdMountTest, DeactivePackageTearsDownVerityDevice) {
+ AddPreInstalledApex("apex.apexd_test.apex");
+ ApexFileRepository::GetInstance().AddPreInstalledApex({GetBuiltInDir()});
+
+ std::string file_path = AddDataApex("apex.apexd_test_v2.apex");
+ ASSERT_THAT(ActivatePackage(file_path), Ok());
+ UnmountOnTearDown(file_path);
+
+ ASSERT_THAT(DeactivatePackage(file_path), Ok());
+ auto& dm = DeviceMapper::Instance();
+ ASSERT_EQ(dm::DmDeviceState::INVALID,
+ dm.GetState("com.android.apex.test_package@2"));
+}
+
TEST_F(ApexdMountTest, ActivateDeactivateSharedLibsApex) {
ASSERT_EQ(mkdir("/apex/sharedlibs", 0755), 0);
ASSERT_EQ(mkdir("/apex/sharedlibs/lib", 0755), 0);
@@ -1356,18 +1762,18 @@
ApexFileRepository::GetInstance().AddPreInstalledApex({GetBuiltInDir()});
UnmountOnTearDown(file_path);
- ASSERT_TRUE(IsOk(ActivatePackage(file_path)));
+ ASSERT_THAT(ActivatePackage(file_path), Ok());
auto active_apex = GetActivePackage("com.android.apex.test.sharedlibs");
- ASSERT_TRUE(IsOk(active_apex));
+ ASSERT_THAT(active_apex, Ok());
ASSERT_EQ(active_apex->GetPath(), file_path);
auto apex_mounts = GetApexMounts();
ASSERT_THAT(apex_mounts,
UnorderedElementsAre("/apex/com.android.apex.test.sharedlibs@1"));
- ASSERT_TRUE(IsOk(DeactivatePackage(file_path)));
- ASSERT_FALSE(IsOk(GetActivePackage("com.android.apex.test.sharedlibs")));
+ ASSERT_THAT(DeactivatePackage(file_path), Ok());
+ ASSERT_THAT(GetActivePackage("com.android.apex.test.sharedlibs"), Not(Ok()));
auto new_apex_mounts = GetApexMounts();
ASSERT_EQ(new_apex_mounts.size(), 0u);
@@ -1403,8 +1809,8 @@
ApexFileRepository::GetInstance().AddPreInstalledApex({GetBuiltInDir()});
UnmountOnTearDown(active_decompressed_apex);
UnmountOnTearDown(active_data_apex);
- ASSERT_TRUE(IsOk(ActivatePackage(active_decompressed_apex)));
- ASSERT_TRUE(IsOk(ActivatePackage(active_data_apex)));
+ ASSERT_THAT(ActivatePackage(active_decompressed_apex), Ok());
+ ASSERT_THAT(ActivatePackage(active_data_apex), Ok());
// Clean up inactive apex packages
RemoveInactiveDataApex();
@@ -1637,7 +2043,7 @@
{
auto apex = ApexFile::Open(apex_path_3);
- ASSERT_TRUE(IsOk(apex));
+ ASSERT_THAT(apex, Ok());
ASSERT_EQ(static_cast<uint64_t>(apex->GetManifest().version()), 2ULL);
}
@@ -2549,7 +2955,7 @@
ApexInfoXmlEq(apex_info_xml_2)));
}
-TEST_F(ApexdMountTest, OnOtaChrootBootstrapFlattenedApex) {
+TEST_F(ApexdMountTest, ActivateFlattenedApex) {
std::string apex_dir_1 = GetBuiltInDir() + "/com.android.apex.test_package";
std::string apex_dir_2 = GetBuiltInDir() + "/com.android.apex.test_package_2";
@@ -2573,7 +2979,7 @@
write_manifest_fn(apex_dir_1, "com.android.apex.test_package", 2);
write_manifest_fn(apex_dir_2, "com.android.apex.test_package_2", 1);
- ASSERT_EQ(OnOtaChrootBootstrapFlattenedApex(), 0);
+ ASSERT_EQ(ActivateFlattenedApex(), 0);
auto apex_mounts = GetApexMounts();
ASSERT_THAT(apex_mounts,
@@ -2616,8 +3022,9 @@
std::string apex_path_2 =
AddPreInstalledApex("apex.apexd_test_different_app.apex");
- ASSERT_RESULT_OK(
- ApexFileRepository::GetInstance().AddPreInstalledApex({GetBuiltInDir()}));
+ ASSERT_THAT(
+ ApexFileRepository::GetInstance().AddPreInstalledApex({GetBuiltInDir()}),
+ Ok());
OnStart();
@@ -2643,8 +3050,9 @@
AddPreInstalledApex("apex.apexd_test_different_app.apex");
std::string apex_path_3 = AddDataApex("apex.apexd_test_v2.apex");
- ASSERT_RESULT_OK(
- ApexFileRepository::GetInstance().AddPreInstalledApex({GetBuiltInDir()}));
+ ASSERT_THAT(
+ ApexFileRepository::GetInstance().AddPreInstalledApex({GetBuiltInDir()}),
+ Ok());
OnStart();
@@ -2668,8 +3076,9 @@
std::string apex_path = AddPreInstalledApex("com.android.apex.cts.shim.apex");
AddDataApex("com.android.apex.cts.shim.v2_wrong_sha.apex");
- ASSERT_RESULT_OK(
- ApexFileRepository::GetInstance().AddPreInstalledApex({GetBuiltInDir()}));
+ ASSERT_THAT(
+ ApexFileRepository::GetInstance().AddPreInstalledApex({GetBuiltInDir()}),
+ Ok());
UnmountOnTearDown(apex_path);
OnStart();
@@ -2691,8 +3100,9 @@
AddPreInstalledApex("apex.apexd_test_different_app.apex");
std::string apex_path_3 = AddDataApex("apex.apexd_test.apex");
- ASSERT_RESULT_OK(
- ApexFileRepository::GetInstance().AddPreInstalledApex({GetBuiltInDir()}));
+ ASSERT_THAT(
+ ApexFileRepository::GetInstance().AddPreInstalledApex({GetBuiltInDir()}),
+ Ok());
OnStart();
@@ -2726,8 +3136,9 @@
AddPreInstalledApex("apex.apexd_test_different_app.apex");
AddDataApex("apex.apexd_test.apex");
- ASSERT_RESULT_OK(
- ApexFileRepository::GetInstance().AddPreInstalledApex({GetBuiltInDir()}));
+ ASSERT_THAT(
+ ApexFileRepository::GetInstance().AddPreInstalledApex({GetBuiltInDir()}),
+ Ok());
OnStart();
@@ -2761,8 +3172,9 @@
AddPreInstalledApex("apex.apexd_test_different_app.apex");
AddDataApex("apex.apexd_test_manifest_mismatch.apex");
- ASSERT_RESULT_OK(
- ApexFileRepository::GetInstance().AddPreInstalledApex({GetBuiltInDir()}));
+ ASSERT_THAT(
+ ApexFileRepository::GetInstance().AddPreInstalledApex({GetBuiltInDir()}),
+ Ok());
OnStart();
@@ -2799,12 +3211,13 @@
{
auto apex = ApexFile::Open(apex_path_3);
- ASSERT_TRUE(IsOk(apex));
+ ASSERT_THAT(apex, Ok());
ASSERT_EQ(static_cast<uint64_t>(apex->GetManifest().version()), 2ULL);
}
- ASSERT_RESULT_OK(
- ApexFileRepository::GetInstance().AddPreInstalledApex({GetBuiltInDir()}));
+ ASSERT_THAT(
+ ApexFileRepository::GetInstance().AddPreInstalledApex({GetBuiltInDir()}),
+ Ok());
OnStart();
@@ -2836,8 +3249,9 @@
std::string apex_path_1 =
AddPreInstalledApex("com.android.apex.compressed.v1.capex");
- ASSERT_RESULT_OK(
- ApexFileRepository::GetInstance().AddPreInstalledApex({GetBuiltInDir()}));
+ ASSERT_THAT(
+ ApexFileRepository::GetInstance().AddPreInstalledApex({GetBuiltInDir()}),
+ Ok());
OnStart();
@@ -2859,7 +3273,7 @@
ASSERT_TRUE(latest);
ASSERT_EQ(data.full_path, decompressed_active_apex);
ASSERT_EQ(data.device_name,
- "com.android.apex.compressed@1");
+ "com.android.apex.compressed");
});
}
@@ -2872,8 +3286,9 @@
std::string apex_path_2 =
AddDataApex("com.android.apex.compressed.v2_original.apex");
- ASSERT_RESULT_OK(
- ApexFileRepository::GetInstance().AddPreInstalledApex({GetBuiltInDir()}));
+ ASSERT_THAT(
+ ApexFileRepository::GetInstance().AddPreInstalledApex({GetBuiltInDir()}),
+ Ok());
OnStart();
@@ -2891,7 +3306,7 @@
ASSERT_TRUE(latest);
ASSERT_EQ(data.full_path, apex_path_2);
ASSERT_EQ(data.device_name,
- "com.android.apex.compressed@2");
+ "com.android.apex.compressed");
});
}
@@ -2904,8 +3319,9 @@
std::string apex_path_2 =
AddDataApex("com.android.apex.compressed.v1_original.apex");
- ASSERT_RESULT_OK(
- ApexFileRepository::GetInstance().AddPreInstalledApex({GetBuiltInDir()}));
+ ASSERT_THAT(
+ ApexFileRepository::GetInstance().AddPreInstalledApex({GetBuiltInDir()}),
+ Ok());
OnStart();
@@ -2925,7 +3341,7 @@
ASSERT_TRUE(latest);
ASSERT_EQ(data.full_path, apex_path_2);
ASSERT_EQ(data.device_name,
- "com.android.apex.compressed@1");
+ "com.android.apex.compressed");
});
}
@@ -2938,8 +3354,9 @@
AddPreInstalledApex("com.android.apex.compressed.v2.capex");
AddDataApex("com.android.apex.compressed.v1_original.apex");
- ASSERT_RESULT_OK(
- ApexFileRepository::GetInstance().AddPreInstalledApex({GetBuiltInDir()}));
+ ASSERT_THAT(
+ ApexFileRepository::GetInstance().AddPreInstalledApex({GetBuiltInDir()}),
+ Ok());
OnStart();
@@ -2962,7 +3379,7 @@
ASSERT_TRUE(latest);
ASSERT_EQ(data.full_path, decompressed_active_apex);
ASSERT_EQ(data.device_name,
- "com.android.apex.compressed@2");
+ "com.android.apex.compressed");
});
}
@@ -2974,8 +3391,9 @@
AddPreInstalledApex("com.android.apex.compressed.v1.capex");
AddDataApex("com.android.apex.compressed.v2_manifest_mismatch.apex");
- ASSERT_RESULT_OK(
- ApexFileRepository::GetInstance().AddPreInstalledApex({GetBuiltInDir()}));
+ ASSERT_THAT(
+ ApexFileRepository::GetInstance().AddPreInstalledApex({GetBuiltInDir()}),
+ Ok());
OnStart();
@@ -2998,7 +3416,7 @@
ASSERT_TRUE(latest);
ASSERT_EQ(data.full_path, decompressed_active_apex);
ASSERT_EQ(data.device_name,
- "com.android.apex.compressed@1");
+ "com.android.apex.compressed");
});
}
@@ -3012,8 +3430,9 @@
PrepareCompressedApex("com.android.apex.compressed.v1.capex");
AddDataApex("com.android.apex.compressed.v2_manifest_mismatch.apex");
- ASSERT_RESULT_OK(
- ApexFileRepository::GetInstance().AddPreInstalledApex({GetBuiltInDir()}));
+ ASSERT_THAT(
+ ApexFileRepository::GetInstance().AddPreInstalledApex({GetBuiltInDir()}),
+ Ok());
OnStart();
@@ -3035,7 +3454,7 @@
ASSERT_TRUE(latest);
ASSERT_EQ(data.full_path, decompressed_active_apex);
ASSERT_EQ(data.device_name,
- "com.android.apex.compressed@1");
+ "com.android.apex.compressed");
});
}
@@ -3052,8 +3471,9 @@
fs::copy(GetTestFile("com.android.apex.compressed.v2_manifest_mismatch.apex"),
GetDataDir() + "/com.android.apex.compressed@2.apex");
- ASSERT_RESULT_OK(
- ApexFileRepository::GetInstance().AddPreInstalledApex({GetBuiltInDir()}));
+ ASSERT_THAT(
+ ApexFileRepository::GetInstance().AddPreInstalledApex({GetBuiltInDir()}),
+ Ok());
OnStart();
@@ -3075,7 +3495,7 @@
ASSERT_TRUE(latest);
ASSERT_EQ(data.full_path, decompressed_active_apex);
ASSERT_EQ(data.device_name,
- "com.android.apex.compressed@2");
+ "com.android.apex.compressed");
});
}
@@ -3090,8 +3510,9 @@
auto apex_path =
AddPreInstalledApex("com.android.apex.compressed.v1_original.apex");
- ASSERT_RESULT_OK(
- ApexFileRepository::GetInstance().AddPreInstalledApex({GetBuiltInDir()}));
+ ASSERT_THAT(
+ ApexFileRepository::GetInstance().AddPreInstalledApex({GetBuiltInDir()}),
+ Ok());
OnStart();
@@ -3130,8 +3551,9 @@
auto apex_path =
AddPreInstalledApex("com.android.apex.compressed.v1_original.apex");
- ASSERT_RESULT_OK(
- ApexFileRepository::GetInstance().AddPreInstalledApex({GetBuiltInDir()}));
+ ASSERT_THAT(
+ ApexFileRepository::GetInstance().AddPreInstalledApex({GetBuiltInDir()}),
+ Ok());
OnStart();
@@ -3159,8 +3581,9 @@
previous_built_in_dir.path);
auto apex_path = AddPreInstalledApex("com.android.apex.compressed.v1.capex");
- ASSERT_RESULT_OK(
- ApexFileRepository::GetInstance().AddPreInstalledApex({GetBuiltInDir()}));
+ ASSERT_THAT(
+ ApexFileRepository::GetInstance().AddPreInstalledApex({GetBuiltInDir()}),
+ Ok());
OnStart();
@@ -3183,7 +3606,7 @@
ASSERT_TRUE(latest);
ASSERT_EQ(data.full_path, decompressed_active_apex);
ASSERT_EQ(data.device_name,
- "com.android.apex.compressed@1");
+ "com.android.apex.compressed");
});
}
@@ -3201,8 +3624,9 @@
fs::copy(GetTestFile("com.android.apex.compressed.v2_original.apex"),
ota_apex_path.c_str());
- ASSERT_RESULT_OK(
- ApexFileRepository::GetInstance().AddPreInstalledApex({GetBuiltInDir()}));
+ ASSERT_THAT(
+ ApexFileRepository::GetInstance().AddPreInstalledApex({GetBuiltInDir()}),
+ Ok());
// When we call OnStart for the first time, it will decompress v1 capex and
// activate it, while after second call it will decompress v2 capex and
@@ -3228,8 +3652,9 @@
RemoveFileIfExists(old_capex);
AddPreInstalledApex("com.android.apex.compressed.v2.capex");
ApexFileRepository::GetInstance().Reset(GetDecompressionDir());
- ASSERT_RESULT_OK(
- ApexFileRepository::GetInstance().AddPreInstalledApex({GetBuiltInDir()}));
+ ASSERT_THAT(
+ ApexFileRepository::GetInstance().AddPreInstalledApex({GetBuiltInDir()}),
+ Ok());
OnStart();
path_exists = PathExists(ota_apex_path);
ASSERT_FALSE(*path_exists);
@@ -3259,8 +3684,9 @@
pre_installed_apex->GetManifest().capexmetadata().originalapexdigest(),
different_digest);
- ASSERT_RESULT_OK(
- ApexFileRepository::GetInstance().AddPreInstalledApex({GetBuiltInDir()}));
+ ASSERT_THAT(
+ ApexFileRepository::GetInstance().AddPreInstalledApex({GetBuiltInDir()}),
+ Ok());
OnStart();
@@ -3289,8 +3715,9 @@
// Place a same version capex in current built_in_dir, which has different key
auto apex_path = AddPreInstalledApex("com.android.apex.compressed.v1.capex");
- ASSERT_RESULT_OK(
- ApexFileRepository::GetInstance().AddPreInstalledApex({GetBuiltInDir()}));
+ ASSERT_THAT(
+ ApexFileRepository::GetInstance().AddPreInstalledApex({GetBuiltInDir()}),
+ Ok());
OnStart();
@@ -3331,11 +3758,11 @@
StringPrintf("%s/apex.apexd_test_different_app.apex", td.path);
auto& instance = ApexFileRepository::GetInstance();
- ASSERT_RESULT_OK(instance.AddPreInstalledApex({GetBuiltInDir()}));
+ ASSERT_THAT(instance.AddPreInstalledApex({GetBuiltInDir()}), Ok());
- ASSERT_TRUE(IsOk(ActivatePackage(apex_path)));
- ASSERT_TRUE(IsOk(ActivatePackage(decompressed_apex)));
- ASSERT_TRUE(IsOk(ActivatePackage(other_apex)));
+ ASSERT_THAT(ActivatePackage(apex_path), Ok());
+ ASSERT_THAT(ActivatePackage(decompressed_apex), Ok());
+ ASSERT_THAT(ActivatePackage(other_apex), Ok());
auto& db = GetApexDatabaseForTesting();
// Remember mount information for |other_apex|, since it won't be available in
@@ -3405,11 +3832,11 @@
GetDecompressionDir().c_str());
auto& instance = ApexFileRepository::GetInstance();
- ASSERT_RESULT_OK(instance.AddPreInstalledApex({GetBuiltInDir()}));
+ ASSERT_THAT(instance.AddPreInstalledApex({GetBuiltInDir()}), Ok());
- ASSERT_TRUE(IsOk(ActivatePackage(apex_path_2)));
- ASSERT_TRUE(IsOk(ActivatePackage(apex_path_3)));
- ASSERT_TRUE(IsOk(ActivatePackage(decompressed_apex)));
+ ASSERT_THAT(ActivatePackage(apex_path_2), Ok());
+ ASSERT_THAT(ActivatePackage(apex_path_3), Ok());
+ ASSERT_THAT(ActivatePackage(decompressed_apex), Ok());
UnmountOnTearDown(apex_path_2);
UnmountOnTearDown(apex_path_3);
UnmountOnTearDown(decompressed_apex);
@@ -3451,10 +3878,10 @@
AddDataApex("com.android.apex.test.sharedlibs_generated.v2.libvY.apex");
auto& instance = ApexFileRepository::GetInstance();
- ASSERT_RESULT_OK(instance.AddPreInstalledApex({GetBuiltInDir()}));
+ ASSERT_THAT(instance.AddPreInstalledApex({GetBuiltInDir()}), Ok());
- ASSERT_TRUE(IsOk(ActivatePackage(apex_path_1)));
- ASSERT_TRUE(IsOk(ActivatePackage(apex_path_2)));
+ ASSERT_THAT(ActivatePackage(apex_path_1), Ok());
+ ASSERT_THAT(ActivatePackage(apex_path_2), Ok());
UnmountOnTearDown(apex_path_1);
UnmountOnTearDown(apex_path_2);
@@ -3473,6 +3900,330 @@
ASSERT_EQ(new_apex_mounts.size(), 0u);
}
+TEST_F(ApexdMountTest, OnStartInVmModeActivatesPreInstalled) {
+ MockCheckpointInterface checkpoint_interface;
+ // Need to call InitializeVold before calling OnStart
+ InitializeVold(&checkpoint_interface);
+
+ auto path1 = AddPreInstalledApex("apex.apexd_test.apex");
+ auto path2 = AddPreInstalledApex("apex.apexd_test_different_app.apex");
+ // In VM mode, we don't scan /data/apex
+ AddDataApex("apex.apexd_test_v2.apex");
+
+ ASSERT_EQ(0, OnStartInVmMode());
+ UnmountOnTearDown(path1);
+ UnmountOnTearDown(path2);
+
+ auto apex_mounts = GetApexMounts();
+ ASSERT_THAT(apex_mounts,
+ UnorderedElementsAre("/apex/com.android.apex.test_package",
+ "/apex/com.android.apex.test_package@1",
+ "/apex/com.android.apex.test_package_2",
+ "/apex/com.android.apex.test_package_2@1",
+ // Emits apex-info-list as well
+ "/apex/apex-info-list.xml"));
+
+ ASSERT_EQ(GetProperty(kTestApexdStatusSysprop, ""), "ready");
+}
+
+TEST_F(ApexdMountTest, OnStartInVmModeFailsWithCapex) {
+ MockCheckpointInterface checkpoint_interface;
+ // Need to call InitializeVold before calling OnStart
+ InitializeVold(&checkpoint_interface);
+
+ AddPreInstalledApex("com.android.apex.compressed.v2.capex");
+
+ ASSERT_EQ(1, OnStartInVmMode());
+}
+
+TEST_F(ApexdMountTest, OnStartInVmModeActivatesBlockDevicesAsWell) {
+ MockCheckpointInterface checkpoint_interface;
+ // Need to call InitializeVold before calling OnStart
+ InitializeVold(&checkpoint_interface);
+
+ // Set system property to enable block apexes
+ SetBlockApexEnabled(true);
+
+ auto path1 = AddBlockApex("apex.apexd_test.apex");
+
+ ASSERT_EQ(0, OnStartInVmMode());
+ UnmountOnTearDown(path1);
+
+ auto apex_mounts = GetApexMounts();
+ ASSERT_THAT(apex_mounts,
+ UnorderedElementsAre("/apex/com.android.apex.test_package",
+ "/apex/com.android.apex.test_package@1",
+ // Emits apex-info-list as well
+ "/apex/apex-info-list.xml"));
+
+ ASSERT_EQ(access("/apex/apex-info-list.xml", F_OK), 0);
+ auto info_list =
+ com::android::apex::readApexInfoList("/apex/apex-info-list.xml");
+ ASSERT_TRUE(info_list.has_value());
+ auto apex_info_xml_1 = com::android::apex::ApexInfo(
+ /* moduleName= */ "com.android.apex.test_package",
+ /* modulePath= */ path1,
+ /* preinstalledModulePath= */ path1,
+ /* versionCode= */ 1, /* versionName= */ "1",
+ /* isFactory= */ true, /* isActive= */ true, GetMTime(path1));
+ ASSERT_THAT(info_list->getApexInfo(),
+ UnorderedElementsAre(ApexInfoXmlEq(apex_info_xml_1)));
+}
+
+TEST_F(ApexdMountTest, OnStartInVmModeFailsWithDuplicateNames) {
+ MockCheckpointInterface checkpoint_interface;
+ // Need to call InitializeVold before calling OnStart
+ InitializeVold(&checkpoint_interface);
+
+ // Set system property to enable block apexes
+ SetBlockApexEnabled(true);
+
+ AddPreInstalledApex("apex.apexd_test.apex");
+ AddBlockApex("apex.apexd_test_v2.apex");
+
+ ASSERT_EQ(1, OnStartInVmMode());
+}
+
+TEST_F(ApexdMountTest, OnStartInVmModeFailsWithWrongPubkey) {
+ MockCheckpointInterface checkpoint_interface;
+ // Need to call InitializeVold before calling OnStart
+ InitializeVold(&checkpoint_interface);
+
+ // Set system property to enable block apexes
+ SetBlockApexEnabled(true);
+
+ AddBlockApex("apex.apexd_test.apex", /*public_key=*/"wrong pubkey");
+
+ ASSERT_EQ(1, OnStartInVmMode());
+}
+
+TEST_F(ApexdMountTest, GetActivePackagesReturningBlockApexesAsWell) {
+ MockCheckpointInterface checkpoint_interface;
+ // Need to call InitializeVold before calling OnStart
+ InitializeVold(&checkpoint_interface);
+
+ // Set system property to enable block apexes
+ SetBlockApexEnabled(true);
+
+ auto path1 = AddBlockApex("apex.apexd_test.apex");
+
+ ASSERT_EQ(0, OnStartInVmMode());
+ UnmountOnTearDown(path1);
+
+ auto active_apexes = GetActivePackages();
+ ASSERT_EQ(1u, active_apexes.size());
+ ASSERT_EQ(path1, active_apexes[0].GetPath());
+}
+
+TEST_F(ApexdMountTest, OnStartInVmModeFailsWithWrongRootDigest) {
+ MockCheckpointInterface checkpoint_interface;
+ // Need to call InitializeVold before calling OnStart
+ InitializeVold(&checkpoint_interface);
+
+ // Set system property to enable block apexes
+ SetBlockApexEnabled(true);
+
+ AddBlockApex("apex.apexd_test.apex", /*public_key=*/"",
+ /*root_digest=*/"wrong root digest");
+
+ ASSERT_EQ(1, OnStartInVmMode());
+}
+
+// Test that OnStart works with only block devices
+TEST_F(ApexdMountTest, OnStartOnlyBlockDevices) {
+ MockCheckpointInterface checkpoint_interface;
+ // Need to call InitializeVold before calling OnStart
+ InitializeVold(&checkpoint_interface);
+
+ // Set system property to enable block apexes
+ SetBlockApexEnabled(true);
+
+ auto path1 = AddBlockApex("apex.apexd_test.apex");
+
+ ASSERT_THAT(android::apex::AddBlockApex(ApexFileRepository::GetInstance()),
+ Ok());
+
+ OnStart();
+ UnmountOnTearDown(path1);
+
+ ASSERT_EQ(GetProperty(kTestApexdStatusSysprop, ""), "starting");
+ auto apex_mounts = GetApexMounts();
+
+ ASSERT_THAT(apex_mounts,
+ UnorderedElementsAre("/apex/com.android.apex.test_package",
+ "/apex/com.android.apex.test_package@1"));
+}
+
+// Test that we can have a mix of both block and system apexes
+TEST_F(ApexdMountTest, OnStartBlockAndSystemInstalled) {
+ MockCheckpointInterface checkpoint_interface;
+ // Need to call InitializeVold before calling OnStart
+ InitializeVold(&checkpoint_interface);
+
+ // Set system property to enable block apexes
+ SetBlockApexEnabled(true);
+
+ auto path1 = AddPreInstalledApex("apex.apexd_test.apex");
+ auto path2 = AddBlockApex("apex.apexd_test_different_app.apex");
+
+ auto& instance = ApexFileRepository::GetInstance();
+
+ ASSERT_THAT(instance.AddPreInstalledApex({GetBuiltInDir()}), Ok());
+ ASSERT_THAT(android::apex::AddBlockApex(instance), Ok());
+
+ OnStart();
+ UnmountOnTearDown(path1);
+ UnmountOnTearDown(path2);
+
+ ASSERT_EQ(GetProperty(kTestApexdStatusSysprop, ""), "starting");
+ auto apex_mounts = GetApexMounts();
+
+ ASSERT_THAT(apex_mounts,
+ UnorderedElementsAre("/apex/com.android.apex.test_package",
+ "/apex/com.android.apex.test_package@1",
+ "/apex/com.android.apex.test_package_2",
+ "/apex/com.android.apex.test_package_2@1"));
+}
+
+TEST_F(ApexdMountTest, OnStartBlockAndCompressedInstalled) {
+ MockCheckpointInterface checkpoint_interface;
+ // Need to call InitializeVold before calling OnStart
+ InitializeVold(&checkpoint_interface);
+
+ // Set system property to enable block apexes
+ SetBlockApexEnabled(true);
+
+ auto path1 = AddPreInstalledApex("com.android.apex.compressed.v1.capex");
+ auto path2 = AddBlockApex("apex.apexd_test.apex");
+
+ auto& instance = ApexFileRepository::GetInstance();
+
+ ASSERT_THAT(instance.AddPreInstalledApex({GetBuiltInDir()}), Ok());
+ ASSERT_THAT(android::apex::AddBlockApex(instance), Ok());
+
+ OnStart();
+ UnmountOnTearDown(path1);
+ UnmountOnTearDown(path2);
+
+ // Decompressed APEX should be mounted
+ std::string decompressed_active_apex = StringPrintf(
+ "%s/com.android.apex.compressed@1%s", GetDecompressionDir().c_str(),
+ kDecompressedApexPackageSuffix);
+ UnmountOnTearDown(decompressed_active_apex);
+
+ ASSERT_EQ(GetProperty(kTestApexdStatusSysprop, ""), "starting");
+ auto apex_mounts = GetApexMounts();
+ ASSERT_THAT(apex_mounts,
+ UnorderedElementsAre("/apex/com.android.apex.compressed",
+ "/apex/com.android.apex.compressed@1",
+ "/apex/com.android.apex.test_package",
+ "/apex/com.android.apex.test_package@1"));
+}
+
+// Test that data version of apex is used if newer
+TEST_F(ApexdMountTest, BlockAndNewerData) {
+ // MockCheckpointInterface checkpoint_interface;
+ //// Need to call InitializeVold before calling OnStart
+ // InitializeVold(&checkpoint_interface);
+
+ // Set system property to enable block apexes
+ SetBlockApexEnabled(true);
+
+ auto& instance = ApexFileRepository::GetInstance();
+ AddBlockApex("apex.apexd_test.apex");
+ ASSERT_THAT(android::apex::AddBlockApex(instance), Ok());
+
+ TemporaryDir data_dir;
+ auto apexd_test_file_v2 =
+ ApexFile::Open(AddDataApex("apex.apexd_test_v2.apex"));
+ ASSERT_THAT(instance.AddDataApex(GetDataDir()), Ok());
+
+ auto all_apex = instance.AllApexFilesByName();
+ auto result = SelectApexForActivation(all_apex, instance);
+ ASSERT_EQ(result.size(), 1u);
+
+ ASSERT_THAT(result,
+ UnorderedElementsAre(ApexFileEq(ByRef(*apexd_test_file_v2))));
+}
+
+// Test that data version of apex not is used if older
+TEST_F(ApexdMountTest, BlockApexAndOlderData) {
+ MockCheckpointInterface checkpoint_interface;
+ // Need to call InitializeVold before calling OnStart
+ InitializeVold(&checkpoint_interface);
+
+ // Set system property to enable block apexes
+ SetBlockApexEnabled(true);
+
+ auto& instance = ApexFileRepository::GetInstance();
+ auto apexd_test_file_v2 =
+ ApexFile::Open(AddBlockApex("apex.apexd_test_v2.apex"));
+ ASSERT_THAT(android::apex::AddBlockApex(instance), Ok());
+
+ TemporaryDir data_dir;
+ AddDataApex("apex.apexd_test.apex");
+ ASSERT_THAT(instance.AddDataApex(GetDataDir()), Ok());
+
+ auto all_apex = instance.AllApexFilesByName();
+ auto result = SelectApexForActivation(all_apex, instance);
+ ASSERT_EQ(result.size(), 1u);
+
+ ASSERT_THAT(result,
+ UnorderedElementsAre(ApexFileEq(ByRef(*apexd_test_file_v2))));
+}
+
+// Test that AddBlockApex does nothing if system property not set.
+TEST_F(ApexdMountTest, AddBlockApexWithoutSystemProp) {
+ MockCheckpointInterface checkpoint_interface;
+ // Need to call InitializeVold before calling OnStart
+ InitializeVold(&checkpoint_interface);
+
+ auto& instance = ApexFileRepository::GetInstance();
+ AddBlockApex("apex.apexd_test.apex");
+ ASSERT_THAT(android::apex::AddBlockApex(instance), Ok());
+ ASSERT_EQ(instance.AllApexFilesByName().size(), 0ul);
+}
+
+// Test that adding block apex fails if preinstalled version exists
+TEST_F(ApexdMountTest, AddBlockApexFailsWithDuplicate) {
+ MockCheckpointInterface checkpoint_interface;
+ // Need to call InitializeVold before calling OnStart
+ InitializeVold(&checkpoint_interface);
+
+ // Set system property to enable block apexes
+ SetBlockApexEnabled(true);
+
+ AddPreInstalledApex("apex.apexd_test.apex");
+ AddBlockApex("apex.apexd_test_v2.apex");
+
+ auto& instance = ApexFileRepository::GetInstance();
+
+ ASSERT_THAT(instance.AddPreInstalledApex({GetBuiltInDir()}), Ok());
+ ASSERT_THAT(android::apex::AddBlockApex(instance),
+ HasError(WithMessage(HasSubstr(
+ "duplicate of com.android.apex.test_package found"))));
+}
+
+// Test that adding block apex fails if preinstalled compressed version exists
+TEST_F(ApexdMountTest, AddBlockApexFailsWithCompressedDuplicate) {
+ MockCheckpointInterface checkpoint_interface;
+ // Need to call InitializeVold before calling OnStart
+ InitializeVold(&checkpoint_interface);
+
+ // Set system property to enable block apexes
+ SetBlockApexEnabled(true);
+
+ auto path1 = AddPreInstalledApex("com.android.apex.compressed.v1.capex");
+ auto path2 = AddBlockApex("com.android.apex.compressed.v1_original.apex");
+
+ auto& instance = ApexFileRepository::GetInstance();
+
+ ASSERT_THAT(instance.AddPreInstalledApex({GetBuiltInDir()}), Ok());
+ ASSERT_THAT(android::apex::AddBlockApex(instance),
+ HasError(WithMessage(HasSubstr(
+ "duplicate of com.android.apex.compressed found"))));
+}
+
class ApexActivationFailureTests : public ApexdMountTest {};
TEST_F(ApexActivationFailureTests, BuildFingerprintDifferent) {
@@ -3512,7 +4263,7 @@
HasSubstr("More than one APEX package found"));
}
-TEST_F(ApexActivationFailureTests, PostInstallFailsForApex) {
+TEST_F(ApexActivationFailureTests, CorruptedSuperblockApexCannotBeStaged) {
auto apex_session =
CreateStagedSession("apex.apexd_test_corrupt_superblock_apex.apex", 123);
apex_session->UpdateStateAndCommit(SessionState::STAGED);
@@ -3521,7 +4272,7 @@
apex_session = ApexSession::GetSession(123);
ASSERT_THAT(apex_session->GetErrorMessage(),
- HasSubstr("Postinstall failed for session"));
+ HasSubstr("Couldn't find filesystem magic"));
}
TEST_F(ApexActivationFailureTests, CorruptedApexCannotBeStaged) {
@@ -3538,7 +4289,7 @@
TEST_F(ApexActivationFailureTests, ActivatePackageImplFails) {
auto shim_path = AddPreInstalledApex("com.android.apex.cts.shim.apex");
auto& instance = ApexFileRepository::GetInstance();
- ASSERT_RESULT_OK(instance.AddPreInstalledApex({GetBuiltInDir()}));
+ ASSERT_THAT(instance.AddPreInstalledApex({GetBuiltInDir()}), Ok());
auto apex_session =
CreateStagedSession("com.android.apex.cts.shim.v2_wrong_sha.apex", 123);
@@ -3563,7 +4314,7 @@
auto pre_installed_apex = AddPreInstalledApex("apex.apexd_test.apex");
auto& instance = ApexFileRepository::GetInstance();
- ASSERT_RESULT_OK(instance.AddPreInstalledApex({GetBuiltInDir()}));
+ ASSERT_THAT(instance.AddPreInstalledApex({GetBuiltInDir()}), Ok());
auto apex_session = CreateStagedSession("apex.apexd_test.apex", 123);
apex_session->UpdateStateAndCommit(SessionState::STAGED);
@@ -3587,7 +4338,7 @@
auto pre_installed_apex = AddPreInstalledApex("apex.apexd_test.apex");
auto& instance = ApexFileRepository::GetInstance();
- ASSERT_RESULT_OK(instance.AddPreInstalledApex({GetBuiltInDir()}));
+ ASSERT_THAT(instance.AddPreInstalledApex({GetBuiltInDir()}), Ok());
auto apex_session = CreateStagedSession("apex.apexd_test.apex", 123);
apex_session->UpdateStateAndCommit(SessionState::STAGED);
@@ -3599,5 +4350,256 @@
ASSERT_EQ(apex_session->GetState(), SessionState::REVERTED);
}
+TEST_F(ApexdMountTest, OnBootstrapCreatesEmptyDmDevices) {
+ AddPreInstalledApex("apex.apexd_test.apex");
+ AddPreInstalledApex("com.android.apex.compressed.v1.capex");
+
+ DeviceMapper& dm = DeviceMapper::Instance();
+
+ auto cleaner = make_scope_guard([&]() {
+ dm.DeleteDeviceIfExists("com.android.apex.test_package", 1s);
+ dm.DeleteDeviceIfExists("com.android.apex.compressed", 1s);
+ });
+
+ ASSERT_EQ(0, OnBootstrap());
+
+ ASSERT_EQ(dm::DmDeviceState::SUSPENDED,
+ dm.GetState("com.android.apex.test_package"));
+ ASSERT_EQ(dm::DmDeviceState::SUSPENDED,
+ dm.GetState("com.android.apex.compressed"));
+}
+
+TEST_F(ApexdUnitTest, StagePackagesFailKey) {
+ auto status =
+ StagePackages({GetTestFile("apex.apexd_test_no_inst_key.apex")});
+
+ ASSERT_THAT(
+ status,
+ HasError(WithMessage(("No preinstalled apex found for package "
+ "com.android.apex.test_package.no_inst_key"))));
+}
+
+TEST_F(ApexdUnitTest, StagePackagesSuccess) {
+ AddPreInstalledApex("apex.apexd_test.apex");
+ auto& instance = ApexFileRepository::GetInstance();
+ ASSERT_THAT(instance.AddPreInstalledApex({GetBuiltInDir()}), Ok());
+
+ auto status = StagePackages({GetTestFile("apex.apexd_test.apex")});
+ ASSERT_THAT(status, Ok());
+
+ auto staged_path = StringPrintf("%s/com.android.apex.test_package@1.apex",
+ GetDataDir().c_str());
+ ASSERT_EQ(0, access(staged_path.c_str(), F_OK));
+}
+
+TEST_F(ApexdUnitTest, StagePackagesClearsPreviouslyActivePackage) {
+ AddPreInstalledApex("apex.apexd_test.apex");
+ auto& instance = ApexFileRepository::GetInstance();
+ ASSERT_THAT(instance.AddPreInstalledApex({GetBuiltInDir()}), Ok());
+
+ auto current_apex = AddDataApex("apex.apexd_test.apex");
+ ASSERT_EQ(0, access(current_apex.c_str(), F_OK));
+
+ auto status = StagePackages({GetTestFile("apex.apexd_test_v2.apex")});
+ ASSERT_THAT(status, Ok());
+
+ auto staged_path = StringPrintf("%s/com.android.apex.test_package@2.apex",
+ GetDataDir().c_str());
+ ASSERT_EQ(0, access(staged_path.c_str(), F_OK));
+ ASSERT_EQ(-1, access(current_apex.c_str(), F_OK));
+ ASSERT_EQ(ENOENT, errno);
+}
+
+TEST_F(ApexdUnitTest, StagePackagesClearsPreviouslyActivePackageDowngrade) {
+ AddPreInstalledApex("apex.apexd_test.apex");
+ auto& instance = ApexFileRepository::GetInstance();
+ ASSERT_THAT(instance.AddPreInstalledApex({GetBuiltInDir()}), Ok());
+
+ auto current_apex = AddDataApex("apex.apexd_test_v2.apex");
+ ASSERT_EQ(0, access(current_apex.c_str(), F_OK));
+
+ auto status = StagePackages({GetTestFile("apex.apexd_test.apex")});
+ ASSERT_THAT(status, Ok());
+
+ auto staged_path = StringPrintf("%s/com.android.apex.test_package@1.apex",
+ GetDataDir().c_str());
+ ASSERT_EQ(0, access(staged_path.c_str(), F_OK));
+ ASSERT_EQ(-1, access(current_apex.c_str(), F_OK));
+ ASSERT_EQ(ENOENT, errno);
+}
+
+TEST_F(ApexdUnitTest, StagePackagesAlreadyStagedPackage) {
+ AddPreInstalledApex("apex.apexd_test.apex");
+ auto& instance = ApexFileRepository::GetInstance();
+ ASSERT_THAT(instance.AddPreInstalledApex({GetBuiltInDir()}), Ok());
+
+ auto status = StagePackages({GetTestFile("apex.apexd_test.apex")});
+ ASSERT_THAT(status, Ok());
+
+ auto staged_path = StringPrintf("%s/com.android.apex.test_package@1.apex",
+ GetDataDir().c_str());
+ struct stat stat1;
+ ASSERT_EQ(0, stat(staged_path.c_str(), &stat1));
+ ASSERT_TRUE(S_ISREG(stat1.st_mode));
+
+ {
+ auto apex = ApexFile::Open(staged_path);
+ ASSERT_THAT(apex, Ok());
+ ASSERT_FALSE(apex->GetManifest().nocode());
+ }
+
+ auto status2 = StagePackages({GetTestFile("apex.apexd_test_nocode.apex")});
+ ASSERT_THAT(status2, Ok());
+
+ struct stat stat2;
+ ASSERT_EQ(0, stat(staged_path.c_str(), &stat2));
+ ASSERT_TRUE(S_ISREG(stat2.st_mode));
+
+ ASSERT_NE(stat1.st_ino, stat2.st_ino);
+
+ {
+ auto apex = ApexFile::Open(staged_path);
+ ASSERT_THAT(apex, Ok());
+ ASSERT_TRUE(apex->GetManifest().nocode());
+ }
+}
+
+TEST_F(ApexdUnitTest, StagePackagesMultiplePackages) {
+ AddPreInstalledApex("apex.apexd_test.apex");
+ AddPreInstalledApex("apex.apexd_test_different_app.apex");
+ auto& instance = ApexFileRepository::GetInstance();
+ ASSERT_THAT(instance.AddPreInstalledApex({GetBuiltInDir()}), Ok());
+
+ auto status =
+ StagePackages({GetTestFile("apex.apexd_test_v2.apex"),
+ GetTestFile("apex.apexd_test_different_app.apex")});
+ ASSERT_THAT(status, Ok());
+
+ auto staged_path1 = StringPrintf("%s/com.android.apex.test_package@2.apex",
+ GetDataDir().c_str());
+ auto staged_path2 = StringPrintf("%s/com.android.apex.test_package_2@1.apex",
+ GetDataDir().c_str());
+ ASSERT_EQ(0, access(staged_path1.c_str(), F_OK));
+ ASSERT_EQ(0, access(staged_path2.c_str(), F_OK));
+}
+
+TEST_F(ApexdUnitTest, UnstagePackages) {
+ auto file_path1 = AddDataApex("apex.apexd_test.apex");
+ auto file_path2 = AddDataApex("apex.apexd_test_different_app.apex");
+
+ ASSERT_THAT(UnstagePackages({file_path1}), Ok());
+ ASSERT_EQ(-1, access(file_path1.c_str(), F_OK));
+ ASSERT_EQ(errno, ENOENT);
+ ASSERT_EQ(0, access(file_path2.c_str(), F_OK));
+}
+
+TEST_F(ApexdUnitTest, UnstagePackagesEmptyInput) {
+ auto file_path1 = AddDataApex("apex.apexd_test.apex");
+ auto file_path2 = AddDataApex("apex.apexd_test_different_app.apex");
+
+ ASSERT_THAT(UnstagePackages({}),
+ HasError(WithMessage("Empty set of inputs")));
+ ASSERT_EQ(0, access(file_path1.c_str(), F_OK));
+ ASSERT_EQ(0, access(file_path2.c_str(), F_OK));
+}
+
+TEST_F(ApexdUnitTest, UnstagePackagesFail) {
+ auto file_path1 = AddDataApex("apex.apexd_test.apex");
+ auto bad_path = GetDataDir() + "/missing.apex";
+
+ ASSERT_THAT(UnstagePackages({file_path1, bad_path}), Not(Ok()));
+ ASSERT_EQ(0, access(file_path1.c_str(), F_OK));
+}
+
+TEST_F(ApexdUnitTest, UnstagePackagesFailPreInstalledApex) {
+ auto file_path1 = AddPreInstalledApex("apex.apexd_test.apex");
+ auto file_path2 = AddDataApex("apex.apexd_test_different_app.apex");
+
+ auto& instance = ApexFileRepository::GetInstance();
+ ASSERT_THAT(instance.AddPreInstalledApex({GetBuiltInDir()}), Ok());
+
+ ASSERT_THAT(UnstagePackages({file_path1, file_path2}),
+ HasError(WithMessage("Can't uninstall pre-installed apex " +
+ file_path1)));
+ ASSERT_EQ(0, access(file_path1.c_str(), F_OK));
+ ASSERT_EQ(0, access(file_path2.c_str(), F_OK));
+}
+
+TEST_F(ApexdUnitTest, RevertStoresCrashingNativeProcess) {
+ MockCheckpointInterface checkpoint_interface;
+ checkpoint_interface.SetSupportsCheckpoint(true);
+ InitializeVold(&checkpoint_interface);
+
+ auto apex_session = CreateStagedSession("apex.apexd_test.apex", 1543);
+ ASSERT_THAT(apex_session, Ok());
+ ASSERT_THAT(apex_session->UpdateStateAndCommit(SessionState::ACTIVATED),
+ Ok());
+
+ ASSERT_THAT(RevertActiveSessions("test_process", ""), Ok());
+ apex_session = ApexSession::GetSession(1543);
+ ASSERT_THAT(apex_session, Ok());
+ ASSERT_EQ(apex_session->GetCrashingNativeProcess(), "test_process");
+}
+
+TEST_F(ApexdUnitTest, MountAndDeriveClasspathNoJar) {
+ AddPreInstalledApex("apex.apexd_test_classpath.apex");
+ ApexFileRepository::GetInstance().AddPreInstalledApex({GetBuiltInDir()});
+
+ // Call MountAndDeriveClassPath
+ auto apex_file = ApexFile::Open(GetTestFile("apex.apexd_test.apex"));
+ auto package_name = apex_file->GetManifest().name();
+ std::vector<ApexFile> apex_files;
+ apex_files.emplace_back(std::move(*apex_file));
+ auto class_path = MountAndDeriveClassPath(apex_files);
+ ASSERT_THAT(class_path, Ok());
+ ASSERT_THAT(class_path->HasClassPathJars(package_name), false);
+}
+
+TEST_F(ApexdUnitTest, MountAndDeriveClassPathJarsPresent) {
+ AddPreInstalledApex("apex.apexd_test_classpath.apex");
+ ApexFileRepository::GetInstance().AddPreInstalledApex({GetBuiltInDir()});
+
+ // Call MountAndDeriveClassPath
+ auto apex_file =
+ ApexFile::Open(GetTestFile("apex.apexd_test_classpath.apex"));
+ auto package_name = apex_file->GetManifest().name();
+ std::vector<ApexFile> apex_files;
+ apex_files.emplace_back(std::move(*apex_file));
+ auto class_path = MountAndDeriveClassPath(apex_files);
+ ASSERT_THAT(class_path, Ok());
+ ASSERT_THAT(class_path->HasClassPathJars(package_name), true);
+}
+
+TEST_F(ApexdUnitTest, ProcessCompressedApexWrongSELinuxContext) {
+ auto compressed_apex = ApexFile::Open(
+ AddPreInstalledApex("com.android.apex.compressed.v1.capex"));
+
+ std::vector<ApexFileRef> compressed_apex_list;
+ compressed_apex_list.emplace_back(std::cref(*compressed_apex));
+ auto return_value =
+ ProcessCompressedApex(compressed_apex_list, /* is_ota_chroot= */ false);
+ ASSERT_EQ(return_value.size(), 1u);
+
+ auto decompressed_apex_path = StringPrintf(
+ "%s/com.android.apex.compressed@1%s", GetDecompressionDir().c_str(),
+ kDecompressedApexPackageSuffix);
+ // Verify that so far it has correct context.
+ ASSERT_EQ(kTestActiveApexSelinuxCtx,
+ GetSelinuxContext(decompressed_apex_path));
+
+ // Manually mess up the context
+ ASSERT_EQ(0, setfilecon(decompressed_apex_path.c_str(),
+ "u:object_r:apex_data_file:s0"));
+ ASSERT_EQ("u:object_r:apex_data_file:s0",
+ GetSelinuxContext(decompressed_apex_path));
+
+ auto attempt_2 =
+ ProcessCompressedApex(compressed_apex_list, /* is_ota_chroot= */ false);
+ ASSERT_EQ(attempt_2.size(), 1u);
+ // Verify that it again has correct context.
+ ASSERT_EQ(kTestActiveApexSelinuxCtx,
+ GetSelinuxContext(decompressed_apex_path));
+}
+
} // namespace apex
} // namespace android
diff --git a/apexd/apexd_test_utils.h b/apexd/apexd_test_utils.h
index 5e3689d..79e1191 100644
--- a/apexd/apexd_test_utils.h
+++ b/apexd/apexd_test_utils.h
@@ -17,9 +17,9 @@
#include <filesystem>
#include <fstream>
-#include <asm-generic/errno-base.h>
#include <gmock/gmock.h>
#include <gtest/gtest.h>
+#include <linux/loop.h>
#include <sched.h>
#include <sys/mount.h>
@@ -33,27 +33,21 @@
#include <android/apex/ApexInfo.h>
#include <android/apex/ApexSessionInfo.h>
#include <binder/IServiceManager.h>
+#include <fstab/fstab.h>
+#include <libdm/dm.h>
#include <selinux/android.h>
#include "apex_file.h"
+#include "apexd_loop.h"
+#include "apexd_utils.h"
#include "session_state.pb.h"
#include "com_android_apex.h"
-using android::base::Error;
-using android::base::Result;
-using apex::proto::SessionState;
-
namespace android {
namespace apex {
namespace testing {
-using ::testing::AllOf;
-using ::testing::Eq;
-using ::testing::ExplainMatchResult;
-using ::testing::Field;
-using ::testing::Property;
-
template <typename T>
inline ::testing::AssertionResult IsOk(const android::base::Result<T>& result) {
if (result.ok()) {
@@ -73,6 +67,10 @@
}
MATCHER_P(SessionInfoEq, other, "") {
+ using ::testing::AllOf;
+ using ::testing::Eq;
+ using ::testing::Field;
+
return ExplainMatchResult(
AllOf(
Field("sessionId", &ApexSessionInfo::sessionId, Eq(other.sessionId)),
@@ -95,6 +93,10 @@
}
MATCHER_P(ApexInfoEq, other, "") {
+ using ::testing::AllOf;
+ using ::testing::Eq;
+ using ::testing::Field;
+
return ExplainMatchResult(
AllOf(Field("moduleName", &ApexInfo::moduleName, Eq(other.moduleName)),
Field("modulePath", &ApexInfo::modulePath, Eq(other.modulePath)),
@@ -107,6 +109,10 @@
}
MATCHER_P(ApexFileEq, other, "") {
+ using ::testing::AllOf;
+ using ::testing::Eq;
+ using ::testing::Property;
+
return ExplainMatchResult(
AllOf(Property("path", &ApexFile::GetPath, Eq(other.get().GetPath())),
Property("image_offset", &ApexFile::GetImageOffset,
@@ -165,13 +171,13 @@
*os << "}";
}
-inline Result<bool> CompareFiles(const std::string& filename1,
- const std::string& filename2) {
+inline android::base::Result<bool> CompareFiles(const std::string& filename1,
+ const std::string& filename2) {
std::ifstream file1(filename1, std::ios::binary);
std::ifstream file2(filename2, std::ios::binary);
if (file1.bad() || file2.bad()) {
- return Error() << "Could not open one of the file";
+ return android::base::Error() << "Could not open one of the file";
}
std::istreambuf_iterator<char> begin1(file1);
@@ -298,12 +304,138 @@
// Just in case, run restorecon -R on /apex.
if (selinux_android_restorecon("/apex", SELINUX_ANDROID_RESTORECON_RECURSE) <
0) {
- return android::base::ErrnoError() << "Failed to restorecon /apex";
+ return ErrnoError() << "Failed to restorecon /apex";
}
return {};
}
+// Simpler version of loop::CreateLoopDevice. Uses LOOP_SET_FD/LOOP_SET_STATUS64
+// instead of LOOP_CONFIGURE.
+// TODO(b/191244059) use loop::CreateLoopDevice
+inline base::Result<loop::LoopbackDeviceUniqueFd> CreateLoopDeviceForTest(
+ const std::string& filepath) {
+ base::unique_fd ctl_fd(open("/dev/loop-control", O_RDWR | O_CLOEXEC));
+ if (ctl_fd.get() == -1) {
+ return base::ErrnoError() << "Failed to open loop-control";
+ }
+ int num = ioctl(ctl_fd.get(), LOOP_CTL_GET_FREE);
+ if (num == -1) {
+ return base::ErrnoError() << "Failed LOOP_CTL_GET_FREE";
+ }
+ auto loop_device = loop::WaitForDevice(num);
+ if (!loop_device.ok()) {
+ return loop_device.error();
+ }
+ base::unique_fd target_fd(open(filepath.c_str(), O_RDONLY | O_CLOEXEC));
+ if (target_fd.get() == -1) {
+ return base::ErrnoError() << "Failed to open " << filepath;
+ }
+ struct loop_info64 li = {};
+ strlcpy((char*)li.lo_crypt_name, filepath.c_str(), LO_NAME_SIZE);
+ li.lo_flags |= LO_FLAGS_AUTOCLEAR;
+ if (ioctl(loop_device->device_fd.get(), LOOP_SET_FD, target_fd.get()) == -1) {
+ return base::ErrnoError() << "Failed to LOOP_SET_FD";
+ }
+ if (ioctl(loop_device->device_fd.get(), LOOP_SET_STATUS64, &li) == -1) {
+ return base::ErrnoError() << "Failed to LOOP_SET_STATUS64";
+ }
+ return loop_device;
+}
+
+inline base::Result<loop::LoopbackDeviceUniqueFd> MountViaLoopDevice(
+ const std::string& filepath, const std::string& mount_point) {
+ auto loop_device = CreateLoopDeviceForTest(filepath);
+ if (loop_device.ok()) {
+ close(open(mount_point.c_str(), O_WRONLY | O_CREAT | O_TRUNC | O_CLOEXEC,
+ 0644));
+ if (0 != mount(loop_device->name.c_str(), mount_point.c_str(), nullptr,
+ MS_BIND, nullptr)) {
+ return base::ErrnoError() << "can't mount.";
+ }
+ }
+ return loop_device;
+}
+
+inline base::Result<loop::LoopbackDeviceUniqueFd> WriteBlockApex(
+ const std::string& apex_file, const std::string& apex_path) {
+ std::string intermediate_path = apex_path + ".intermediate";
+ std::filesystem::copy(apex_file, intermediate_path);
+ return MountViaLoopDevice(intermediate_path, apex_path);
+}
+
+inline android::base::Result<std::string> GetBlockDeviceForApex(
+ const std::string& package_id) {
+ using android::fs_mgr::Fstab;
+ using android::fs_mgr::GetEntryForMountPoint;
+ using android::fs_mgr::ReadFstabFromFile;
+
+ std::string mount_point = std::string(kApexRoot) + "/" + package_id;
+ Fstab fstab;
+ if (!ReadFstabFromFile("/proc/mounts", &fstab)) {
+ return android::base::Error() << "Failed to read /proc/mounts";
+ }
+ auto entry = GetEntryForMountPoint(&fstab, mount_point);
+ if (entry == nullptr) {
+ return android::base::Error()
+ << "Can't find " << mount_point << " in /proc/mounts";
+ }
+ return entry->blk_device;
+}
+
+inline android::base::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);
+
+ android::base::unique_fd fd(
+ TEMP_FAILURE_RETRY(open(block_device.c_str(), O_RDONLY | O_CLOEXEC)));
+ if (fd.get() == -1) {
+ return android::base::ErrnoError() << "Can't open " << block_device;
+ }
+
+ while (true) {
+ int n = read(fd.get(), buffer.data(), kBufSize);
+ if (n < 0) {
+ return android::base::ErrnoError() << "Failed to read " << block_device;
+ }
+ if (n == 0) {
+ break;
+ }
+ }
+ return {};
+}
+
+inline android::base::Result<std::vector<std::string>> ListChildLoopDevices(
+ const std::string& name) {
+ using android::base::Error;
+ using android::dm::DeviceMapper;
+
+ DeviceMapper& dm = DeviceMapper::Instance();
+ std::string dm_path;
+ if (!dm.GetDmDevicePathByName(name, &dm_path)) {
+ return Error() << "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/";
+ if (!android::base::StartsWith(dm_path, kDevPrefix)) {
+ return Error() << "Illegal path " << dm_path;
+ }
+ dm_path = dm_path.substr(kDevPrefix.length());
+ std::vector<std::string> children;
+ std::string dir = "/sys/" + dm_path + "/slaves";
+ auto status = WalkDir(dir, [&](const auto& entry) {
+ std::error_code ec;
+ if (entry.is_symlink(ec)) {
+ children.push_back("/dev/block/" + entry.path().filename().string());
+ }
+ });
+ if (!status.ok()) {
+ return status.error();
+ }
+ return children;
+}
+
} // namespace apex
} // namespace android
diff --git a/apexd/apexd_testdata/Android.bp b/apexd/apexd_testdata/Android.bp
index 0e7039a..0307690 100644
--- a/apexd/apexd_testdata/Android.bp
+++ b/apexd/apexd_testdata/Android.bp
@@ -105,7 +105,7 @@
name: "gen_key_mismatch_capex",
out: ["com.android.apex.compressed_different_key.capex"],
srcs: [":apex.apexd_test_no_inst_key"],
- tools: ["soong_zip", "zipalign", "conv_apex_manifest", "apex_compression_tool"],
+ tools: ["soong_zip", "zipalign", "conv_apex_manifest", "apex_compression_tool", "avbtool"],
cmd: "unzip -q $(in) -d $(genDir) && " +
"$(location conv_apex_manifest) setprop name com.android.apex.compressed $(genDir)/apex_manifest.pb && " +
"$(location soong_zip) -d -C $(genDir) -D $(genDir) " +
@@ -113,8 +113,9 @@
"-o $(genDir)/unaligned.apex && " +
"$(location zipalign) -f 4096 $(genDir)/unaligned.apex " +
"$(genDir)/com.android.apex.compressed_different_key.apex && " +
+ "HOST_OUT_BIN=$$(dirname $(location apex_compression_tool)) && " +
"$(location apex_compression_tool) compress " +
- "--apex_compression_tool_path='out/soong/host/linux-x86/bin:prebuilts/sdk/tools/linux/bin' " +
+ "--apex_compression_tool_path=\"$$HOST_OUT_BIN\" " +
"--input=$(genDir)/com.android.apex.compressed_different_key.apex " +
"--output=$(genDir)/com.android.apex.compressed_different_key.capex"
}
@@ -203,6 +204,17 @@
}
apex {
+ name: "apex.apexd_test_erofs",
+ manifest: "manifest.json",
+ file_contexts: ":apex.test-file_contexts",
+ prebuilts: ["sample_prebuilt_file"],
+ key: "com.android.apex.test_package.key",
+ installable: false,
+ min_sdk_version: "current",
+ payload_fs_type: "erofs",
+}
+
+apex {
name: "apex.apexd_test_no_hashtree",
manifest: "manifest.json",
file_contexts: ":apex.test-file_contexts",
@@ -269,60 +281,6 @@
}
apex_key {
- name: "com.android.apex.test_package.preinstall.key",
- public_key: "com.android.apex.test_package.preinstall.avbpubkey",
- private_key: "com.android.apex.test_package.preinstall.pem",
- installable: false,
-}
-
-apex {
- name: "apex.apexd_test_preinstall",
- manifest: "manifest_preinstall.json",
- file_contexts: ":apex.test-file_contexts",
- prebuilts: ["sample_prebuilt_file"],
- key: "com.android.apex.test_package.preinstall.key",
- binaries: ["apex_test_preInstallHook"],
- installable: false,
- updatable: false,
-}
-
-apex_key {
- name: "com.android.apex.test_package.postinstall.key",
- public_key: "com.android.apex.test_package.postinstall.avbpubkey",
- private_key: "com.android.apex.test_package.postinstall.pem",
- installable: false,
-}
-
-apex {
- name: "apex.apexd_test_postinstall",
- manifest: "manifest_postinstall.json",
- file_contexts: ":apex.test-file_contexts",
- prebuilts: ["sample_prebuilt_file"],
- key: "com.android.apex.test_package.postinstall.key",
- binaries: ["apex_test_postInstallHook"],
- installable: false,
- updatable: false,
-}
-
-apex_key {
- name: "com.android.apex.test_package.prepostinstall.fail.key",
- public_key: "com.android.apex.test_package.prepostinstall.fail.avbpubkey",
- private_key: "com.android.apex.test_package.prepostinstall.fail.pem",
- installable: false,
-}
-
-apex {
- name: "apex.apexd_test_prepostinstall.fail",
- manifest: "manifest_prepostinstall.fail.json",
- file_contexts: ":apex.test-file_contexts",
- prebuilts: ["sample_prebuilt_file"],
- key: "com.android.apex.test_package.prepostinstall.fail.key",
- binaries: ["apex_test_prePostInstallHookFail"],
- installable: false,
- updatable: false,
-}
-
-apex_key {
name: "com.android.apex.test_package.no_inst_key.key",
public_key: "com.android.apex.test_package.no_inst_key.avbpubkey",
private_key: "com.android.apex.test_package.no_inst_key.pem",
@@ -350,6 +308,17 @@
updatable: false,
}
+apex {
+ name: "apex.apexd_test_erofs_no_inst_key",
+ manifest: "manifest_no_inst_key.json",
+ file_contexts: ":apex.test-file_contexts",
+ prebuilts: ["sample_prebuilt_file"],
+ key: "com.android.apex.test_package.no_inst_key.key",
+ installable: false,
+ payload_fs_type: "erofs",
+ updatable: false,
+}
+
apex_key {
name: "com.android.apex.test_package_2.key",
public_key: "com.android.apex.test_package_2.avbpubkey",
@@ -367,21 +336,6 @@
updatable: false,
}
-sh_binary {
- name: "apex_test_preInstallHook",
- src: "preInstallHook.sh",
-}
-
-sh_binary {
- name: "apex_test_postInstallHook",
- src: "postInstallHook.sh",
-}
-
-sh_binary {
- name: "apex_test_prePostInstallHookFail",
- src: "fail.sh",
-}
-
apex {
name: "apex.apexd_test_nocode",
manifest: "manifest_nocode.json",
@@ -556,3 +510,57 @@
"test.rebootless_apex_priv_app_in_apex",
],
}
+
+apex_test {
+ name: "apex.apexd_test_classpath",
+ bootclasspath_fragments: ["apex.apexd_test_bootclasspath-fragment"],
+ systemserverclasspath_fragments: ["apex.apexd_test_systemserverclasspath-fragment"],
+ manifest: "manifest.json",
+ prebuilts: ["sample_prebuilt_file"],
+ key: "com.android.apex.test_package.key",
+ file_contexts: ":apex.test-file_contexts",
+ installable: false, // Should never be installed on the systemimage
+ updatable: false,
+}
+
+bootclasspath_fragment {
+ name: "apex.apexd_test_bootclasspath-fragment",
+ contents: ["test_framework-apexd"],
+}
+
+systemserverclasspath_fragment {
+ name: "apex.apexd_test_systemserverclasspath-fragment",
+ contents: ["test_service-apexd"],
+}
+
+java_sdk_library {
+ name: "test_framework-apexd",
+ defaults: ["framework-module-defaults"],
+ srcs: ["src/com/android/apex/test/Test.java"],
+ permitted_packages: ["com.android.apex.test"],
+ min_sdk_version: "30",
+
+ // Test only SDK, don't check against released APIs.
+ unsafe_ignore_missing_latest_api: true,
+ // Output the api files to a special directory that won't trigger an API
+ // review as it is a test only API.
+ api_dir: "apis_for_tests",
+ // Testing only.
+ no_dist: true,
+}
+
+java_sdk_library {
+ name: "test_service-apexd",
+ defaults: ["framework-system-server-module-defaults"],
+ srcs: ["src/com/android/apex/test/Test.java"],
+ permitted_packages: ["com.android.apex.test"],
+ min_sdk_version: "30",
+
+ // Test only SDK, don't check against released APIs.
+ unsafe_ignore_missing_latest_api: true,
+ // Output the api files to a special directory that won't trigger an API
+ // review as it is a test only API.
+ api_dir: "apis_for_tests",
+ // Testing only.
+ no_dist: true,
+}
diff --git a/apexd/apexd_testdata/apis_for_tests/current.txt b/apexd/apexd_testdata/apis_for_tests/current.txt
new file mode 100644
index 0000000..7ceb448
--- /dev/null
+++ b/apexd/apexd_testdata/apis_for_tests/current.txt
@@ -0,0 +1,9 @@
+// Signature format: 2.0
+package com.android.apex.test {
+
+ public class Test {
+ ctor public Test();
+ }
+
+}
+
diff --git a/apexd/apexd_testdata/apis_for_tests/module-lib-current.txt b/apexd/apexd_testdata/apis_for_tests/module-lib-current.txt
new file mode 100644
index 0000000..d802177
--- /dev/null
+++ b/apexd/apexd_testdata/apis_for_tests/module-lib-current.txt
@@ -0,0 +1 @@
+// Signature format: 2.0
diff --git a/apexd/apexd_testdata/apis_for_tests/module-lib-removed.txt b/apexd/apexd_testdata/apis_for_tests/module-lib-removed.txt
new file mode 100644
index 0000000..d802177
--- /dev/null
+++ b/apexd/apexd_testdata/apis_for_tests/module-lib-removed.txt
@@ -0,0 +1 @@
+// Signature format: 2.0
diff --git a/apexd/apexd_testdata/apis_for_tests/removed.txt b/apexd/apexd_testdata/apis_for_tests/removed.txt
new file mode 100644
index 0000000..d802177
--- /dev/null
+++ b/apexd/apexd_testdata/apis_for_tests/removed.txt
@@ -0,0 +1 @@
+// Signature format: 2.0
diff --git a/apexd/apexd_testdata/apis_for_tests/system-current.txt b/apexd/apexd_testdata/apis_for_tests/system-current.txt
new file mode 100644
index 0000000..d802177
--- /dev/null
+++ b/apexd/apexd_testdata/apis_for_tests/system-current.txt
@@ -0,0 +1 @@
+// Signature format: 2.0
diff --git a/apexd/apexd_testdata/apis_for_tests/system-removed.txt b/apexd/apexd_testdata/apis_for_tests/system-removed.txt
new file mode 100644
index 0000000..d802177
--- /dev/null
+++ b/apexd/apexd_testdata/apis_for_tests/system-removed.txt
@@ -0,0 +1 @@
+// Signature format: 2.0
diff --git a/apexd/apexd_testdata/apis_for_tests/system-server-current.txt b/apexd/apexd_testdata/apis_for_tests/system-server-current.txt
new file mode 100644
index 0000000..d802177
--- /dev/null
+++ b/apexd/apexd_testdata/apis_for_tests/system-server-current.txt
@@ -0,0 +1 @@
+// Signature format: 2.0
diff --git a/apexd/apexd_testdata/apis_for_tests/system-server-removed.txt b/apexd/apexd_testdata/apis_for_tests/system-server-removed.txt
new file mode 100644
index 0000000..d802177
--- /dev/null
+++ b/apexd/apexd_testdata/apis_for_tests/system-server-removed.txt
@@ -0,0 +1 @@
+// Signature format: 2.0
diff --git a/apexd/apexd_testdata/com.android.apex.test_package.postinstall.avbpubkey b/apexd/apexd_testdata/com.android.apex.test_package.postinstall.avbpubkey
deleted file mode 100644
index 97f26a2..0000000
--- a/apexd/apexd_testdata/com.android.apex.test_package.postinstall.avbpubkey
+++ /dev/null
Binary files differ
diff --git a/apexd/apexd_testdata/com.android.apex.test_package.postinstall.pem b/apexd/apexd_testdata/com.android.apex.test_package.postinstall.pem
deleted file mode 100644
index af1efd9..0000000
--- a/apexd/apexd_testdata/com.android.apex.test_package.postinstall.pem
+++ /dev/null
@@ -1,51 +0,0 @@
------BEGIN RSA PRIVATE KEY-----
-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
------END RSA PRIVATE KEY-----
diff --git a/apexd/apexd_testdata/com.android.apex.test_package.preinstall.avbpubkey b/apexd/apexd_testdata/com.android.apex.test_package.preinstall.avbpubkey
deleted file mode 100644
index f3593c4..0000000
--- a/apexd/apexd_testdata/com.android.apex.test_package.preinstall.avbpubkey
+++ /dev/null
Binary files differ
diff --git a/apexd/apexd_testdata/com.android.apex.test_package.preinstall.pem b/apexd/apexd_testdata/com.android.apex.test_package.preinstall.pem
deleted file mode 100644
index 09f861b..0000000
--- a/apexd/apexd_testdata/com.android.apex.test_package.preinstall.pem
+++ /dev/null
@@ -1,51 +0,0 @@
------BEGIN RSA PRIVATE KEY-----
-MIIJKgIBAAKCAgEA43h4SU45Am/C2YlZllyBFQiOVCn2Z+ngoTewe9o/4++Z7/uS
-3sWyHahm9PJ3KDihmxI1gQxUMuoj1QH0oPzM19obw+Ubx0S5JeZLP3C86AuLwrxn
-ftkH1m/E8qbkIZ+bT7rWC121ZglXhYH+LPHGtocYAChsPIczHYltyyQXiSFBDKse
-b32NFJ1TOTcDkrIowajZzEFb587wCkDGOvwQlj51NmSAM32rL9vIxevzx4OKQHJx
-4IMy70c1dAi/zTiUfXIbEQRbr8KYAORnUPhtJ28olvVDcHgeQlVYu2CTWzftCvs/
-9RnrpKZOvohBV0Lx6pNG7At56oyNVhdmL4PyoLfhpATXGSAplxAsn8yHdYyImOcP
-ZioZmXfggQUVXabDH+8tDnTOu6no0p5kcyxqL+965RCnpLpo1XSi3fDVbq037Qq2
-F+XubJ30CGNYqR4HjpfqoniJIyrVz43JYTiGhvCaMEgcjHMi+CzHFa67Amu/kogK
-aOVnFe3t1YVe7sW5H+xz+tqH0foYdpGXKwF1y0BLWOlcvypNQqtiPO0uCJmgfu0q
-blJ+KAa/AGg3Iob5y9HqR87Ee7S+/c3J+jtQBzis0VA1m9ajSpy2WwaNe7yHuRmu
-v4iQsJFW8phOHLoKnUeuGqWj77pePIzO101sqPtTv/T3NOx7sjLpX4X9YgECAwEA
-AQKCAgAbHGmhuwv6aV2c1czyqdXMrvdfupyep/ZY4K1NJacFwLHlM2O+32sqM78J
-pLhk99LzC4mK9vA2HUfmBFSmt1qmw10ZzP2xk1RJ2xfO2N36/h1LOW9QZAeWHD4p
-+ZApHb/CEe+e7S1ImwZuaB7mNm7LpHBM3ISB9k82TwHh+0w+0NLS8rYu7ZTaByI7
-KH5phohrBHfo60VL7JMZdbzqnM5RSeJoeDP7bxfvHU/hnwywE9JvefRuoNR6Rk9t
-o/lQOVUhWm81aQKQgS3itVZHBUTlNU9uJo9CIV7h4xAvTQRjE9hRvb5StSMrvZKX
-DOBUySrSHXmPepKuTNAvmZxZP220Voalt0WdHFI6xzuv1e3wO/3/jvVYLfKKhHw8
-WMRyZKhBAYzYCbeRJIKP0z+BrHbF+V+oX/cWjtEU9BDZ6KcQ401FgDLyPyGVfBbb
-GgqC0heOvIkaoK40XS/OEDb9BCIjgWlTZjGbeJzNmZFap9ZzRWw18W5QZdTC6X4x
-wfZiTf7k1DngcxN+kGF6ox42UycPnVKARVrBa9p7eS1NYugxXXJZgd0Oqq9DuEgt
-A1iXwkgMqcdCUMMOb/Fng7p3hUY0ANTVqnKnvtaxnq7h3SBXLfSHi1ySHzRZPb2Z
-nDM0+8UeF+W33q3DVV/YGh5MA4lMHoWmrxAoCs3YbVxz6Ano/QKCAQEA+kB38OFl
-IP0N1OWOEGJoOobLGi/XCz8qYjC5RC2CJ7V5yPR9gcVS1F8fQjsNnaYAigF0HcKS
-bkeeuXqr6KKQlhc2sKEFjVyaejT9cMMtObVlgVYkSv60cFQ/lKqnH9hGFspG2tTu
-6IjS5O7+AiRrpVvkMOGv9vvUE0TnU8cVM9f9euCP8HCGs8hE4+45tQmMK5iX0Tqm
-qKgXfhvKpAOdgbP3RKrboql+EtjF1ks7lYuyI6dfchLbaoQtU5oe09tqtjUG9iF5
-toUq/Pu+WTbWGyovcFHDS8QEKrdUe5IofIonodMZ76DJSCGrO8zp5nPDjrrhTiMN
-vx/3OZ9xM9gOSwKCAQEA6LILAugGENQCamyxIP+mtNBcuvtQerIr/q/quT5HKAyU
-GNqdpbwyp9w3Hm/Uy2q60Dt5J50MmQewrewvwQvp+bD1XIaWU1TOxkurxkDEVgkL
-8uTiA/SnDgJsNznCFt99/VhTG2BnW7nU1yKD/KcBXHJzQpnly2krCO8GxPEV5RY8
-Ebh++UzThYAszuXjY+jL3dzyuCLsJJHBbOW+aeNjNOfgoieGHCbqAjE9TlIv74Cv
-Zjnb39gphBThJ7HU7F1EWH1HvK+bs6+X9XM2MQ04+U7v0pbqFoDzQYc42KKdD7Z5
-4BGcxOdj0/a1wz7iGb7uBFmCoX3jPMQTfYl4teqxYwKCAQEA7GQHqJgRYlcIQMpT
-JzD+CgMYSfVU6n/Rdo1WBIwEfaQUlXo2MGaINqpgKb0EwJ11tudmm1IX5mprCgRR
-7V/aupzVAYYpa35FQhlaKrGDwBaU+ta3U8xEADPlF3cYhaFTm+WZhs9LmobcyHrl
-oHps4Pfrly0pfmIl08nrxpyxb5ahD/ien158L8mHIdP/2P5a1TfAeVkw4vJdtrdj
-9QihOUsZ0VigPohi2kTApQvODha2wK0zINulPnn7IxTb9/41UEbI+6llgr9Ke00G
-YtnE6EYyJ6RkmeZglUU2XcAT1IjgpSF3R3+XgzaivMt1S0ahPOtVNu0v6BG3BEyT
-r4hw/wKCAQEAu6Nua66LzaAYPpddEf31ANMQI5sqH3ZzMZHLlgrZODtEZPJMs2uZ
-8XALZljJYCq1b9q1bLMgiHoCB41wSJwB1V3TzQOeTp4RiydT4a8yYyoyX3AfrQ7o
-csyTVMkXcHhR0Swa//VyAy5LqhtkESTeXRzRQsB049zvXenZLJA3lp6iW6vJf+Bq
-pLlh6IjKHzOGyWY0tzU59JcwQU7Vh+9bfTsKEdgLNcJPausnw3NUwi2jwqwfWFan
-y6FGmkq1JtBin3SOl2GIDZIn2D5KUa2W5rq3erpFE1ZyUcHnnhEq05PSnVO2dbUc
-87x6f3HCwv4KUqmXRAAOcF6wVMe49gDFfwKCAQEAo00/8sbxTMU9lUz2muJ6Xzy2
-MlGRnYK01u4zXerojWxNowwjNXUhKU8tsEcV2OdQz07CxkKLps+5fSZ7MJfqXU/m
-HTRryb9rPMxuwK77Mnx4dWqeMnHtC+6bA0ScZHC7DbAXbkUVc36DVFssIjRkOQDw
-MMUU0qyIrvoVq/J5wZxWquEh2jt0MtKotLPXDIzJM2G0cc7e7yw09fmkSpc7xQmC
-ISgkR6C4+W2eoo3+Iq/lPoV1igg+07UZkdZS5b96asETDVHEL9Ba33J8WoOpOoEE
-gSgpS/5XubFhK4KMFR8HkIpS860+Scl3VbsFUwvwGAzkEmucseq9XaavUcfXbw==
------END RSA PRIVATE KEY-----
diff --git a/apexd/apexd_testdata/manifest_postinstall.json b/apexd/apexd_testdata/manifest_postinstall.json
deleted file mode 100644
index 80e2eeb..0000000
--- a/apexd/apexd_testdata/manifest_postinstall.json
+++ /dev/null
@@ -1,5 +0,0 @@
-{
- "name": "com.android.apex.test_package.postinstall",
- "version": 1,
- "postInstallHook": "bin/apex_test_postInstallHook"
-}
diff --git a/apexd/apexd_testdata/manifest_preinstall.json b/apexd/apexd_testdata/manifest_preinstall.json
deleted file mode 100644
index 91a3f23..0000000
--- a/apexd/apexd_testdata/manifest_preinstall.json
+++ /dev/null
@@ -1,5 +0,0 @@
-{
- "name": "com.android.apex.test_package.preinstall",
- "version": 1,
- "preInstallHook": "bin/apex_test_preInstallHook"
-}
diff --git a/apexd/apexd_testdata/manifest_prepostinstall.fail.json b/apexd/apexd_testdata/manifest_prepostinstall.fail.json
deleted file mode 100644
index 218c3f8..0000000
--- a/apexd/apexd_testdata/manifest_prepostinstall.fail.json
+++ /dev/null
@@ -1,6 +0,0 @@
-{
- "name": "com.android.apex.test_package.prepostinstall.fail",
- "version": 1,
- "preInstallHook": "bin/apex_test_prePostInstallHookFail",
- "postInstallHook": "bin/apex_test_prePostInstallHookFail"
-}
diff --git a/apexd/apexd_testdata/postInstallHook.sh b/apexd/apexd_testdata/postInstallHook.sh
deleted file mode 100644
index 3bee5a8..0000000
--- a/apexd/apexd_testdata/postInstallHook.sh
+++ /dev/null
@@ -1,15 +0,0 @@
-#!/system/bin/sh
-
-# Side test for b/124769206.
-if [ -z "$BOOTCLASSPATH" ] ; then
- log -t apex_tests -p f "BOOTCLASSPATH is perceived as empty"
- exit 1
-fi
-
-# Look for the session data, but do not fail and instead print a literal
-# otherwise. Sleep is an attempt to ensure that the message always reaches
-# logcat.
-/system/bin/logwrapper \
- /system/bin/sh -c \
- 'ls /apex/com.android.apex.test_package/etc/sample_prebuilt_file \
- || echo "PostInstall Test" ; sleep 5s'
diff --git a/apexd/apexd_testdata/preInstallHook.sh b/apexd/apexd_testdata/preInstallHook.sh
deleted file mode 100644
index 2d341a8..0000000
--- a/apexd/apexd_testdata/preInstallHook.sh
+++ /dev/null
@@ -1,15 +0,0 @@
-#!/system/bin/sh
-
-# Side test for b/124769206.
-if [ -z "$BOOTCLASSPATH" ] ; then
- log -t apex_tests -p f "BOOTCLASSPATH is perceived as empty"
- exit 1
-fi
-
-# Look for the session data, but do not fail and instead print a literal
-# otherwise. Sleep is an attempt to ensure that the message always reaches
-# logcat.
-/system/bin/logwrapper \
- /system/bin/sh -c \
- 'ls /apex/com.android.apex.test_package/etc/sample_prebuilt_file \
- || echo "PreInstall Test" ; sleep 5s'
diff --git a/apexd/apexd_testdata/src/com/android/apex/test/Test.java b/apexd/apexd_testdata/src/com/android/apex/test/Test.java
new file mode 100644
index 0000000..b24fabf
--- /dev/null
+++ b/apexd/apexd_testdata/src/com/android/apex/test/Test.java
@@ -0,0 +1,22 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.apex.test;
+
+public class Test {
+
+ public Test() {}
+}
diff --git a/apexd/apexd_utils.h b/apexd/apexd_utils.h
index 23194eb..782bd2f 100644
--- a/apexd/apexd_utils.h
+++ b/apexd/apexd_utils.h
@@ -44,49 +44,6 @@
namespace android {
namespace apex {
-inline int WaitChild(pid_t pid) {
- int status;
- pid_t got_pid = TEMP_FAILURE_RETRY(waitpid(pid, &status, 0));
-
- if (got_pid != pid) {
- PLOG(WARNING) << "waitpid failed: wanted " << pid << ", got " << got_pid;
- return 1;
- }
-
- if (WIFEXITED(status) && WEXITSTATUS(status) == 0) {
- return 0;
- } else {
- return status;
- }
-}
-
-inline android::base::Result<void> ForkAndRun(
- const std::vector<std::string>& args) {
- LOG(DEBUG) << "Forking : " << android::base::Join(args, " ");
- std::vector<const char*> argv;
- argv.resize(args.size() + 1, nullptr);
- std::transform(args.begin(), args.end(), argv.begin(),
- [](const std::string& in) { return in.c_str(); });
-
- pid_t pid = fork();
- if (pid == -1) {
- // Fork failed.
- return android::base::ErrnoError() << "Unable to fork";
- }
-
- if (pid == 0) {
- execv(argv[0], const_cast<char**>(argv.data()));
- PLOG(ERROR) << "execv failed";
- _exit(1);
- }
-
- int rc = WaitChild(pid);
- if (rc != 0) {
- return android::base::Error() << "Failed run: status=" << rc;
- }
- return {};
-}
-
template <typename Fn>
android::base::Result<void> WalkDir(const std::string& path, Fn fn) {
namespace fs = std::filesystem;
@@ -202,6 +159,11 @@
if (android_reboot(ANDROID_RB_RESTART2, 0, nullptr) != 0) {
LOG(ERROR) << "Failed to reboot device";
}
+ // Wait for reboot to complete as we expect this to be a terminal
+ // command. Crash apexd if reboot does not complete even after
+ // waiting an arbitrary significant amount of time.
+ std::this_thread::sleep_for(std::chrono::seconds(120));
+ LOG(FATAL) << "Device did not reboot within 120 seconds";
}
inline android::base::Result<void> WaitForFile(
@@ -372,6 +334,17 @@
return {};
}
+inline android::base::Result<std::string> GetfileconPath(
+ const std::string& path) {
+ char* ctx;
+ if (getfilecon(path.c_str(), &ctx) < 0) {
+ return android::base::ErrnoError() << "Failed to getfilecon " << path;
+ }
+ std::string ret(ctx);
+ freecon(ctx);
+ return ret;
+}
+
} // namespace apex
} // namespace android
diff --git a/apexd/apexd_verity.cpp b/apexd/apexd_verity.cpp
index 0008f6d..5db78bf 100644
--- a/apexd/apexd_verity.cpp
+++ b/apexd/apexd_verity.cpp
@@ -17,14 +17,17 @@
#include "apexd_verity.h"
-#include <filesystem>
-#include <vector>
-
#include <android-base/file.h>
#include <android-base/result.h>
#include <android-base/unique_fd.h>
#include <verity/hash_tree_builder.h>
+#include <filesystem>
+#include <iomanip>
+#include <sstream>
+#include <string>
+#include <vector>
+
#include "apex_constants.h"
#include "apex_file.h"
#include "apexd_utils.h"
@@ -199,5 +202,15 @@
// TODO(b/120058143): on boot complete, remove unused hashtree files
}
+std::string BytesToHex(const uint8_t* bytes, size_t bytes_len) {
+ std::ostringstream s;
+
+ s << std::hex << std::setfill('0');
+ for (size_t i = 0; i < bytes_len; i++) {
+ s << std::setw(2) << static_cast<int>(bytes[i]);
+ }
+ return s.str();
+}
+
} // namespace apex
} // namespace android
diff --git a/apexd/apexd_verity.h b/apexd/apexd_verity.h
index e3c8427..deb3ac0 100644
--- a/apexd/apexd_verity.h
+++ b/apexd/apexd_verity.h
@@ -23,6 +23,8 @@
namespace android {
namespace apex {
+std::string BytesToHex(const uint8_t* bytes, size_t len);
+
enum PrepareHashTreeResult {
kReuse = 0,
KRegenerate = 1,
diff --git a/apexd/apexservice.cpp b/apexd/apexservice.cpp
index 4f3d2fc..dfe946c 100644
--- a/apexd/apexservice.cpp
+++ b/apexd/apexservice.cpp
@@ -63,6 +63,16 @@
return BinderStatus::ok();
}
+BinderStatus CheckCallerSystemOrRoot(const std::string& name) {
+ uid_t uid = IPCThreadState::self()->getCallingUid();
+ if (uid != AID_ROOT && uid != AID_SYSTEM) {
+ std::string msg = "Only root and system_server are allowed to call " + name;
+ return BinderStatus::fromExceptionCode(BinderStatus::EX_SECURITY,
+ String8(name.c_str()));
+ }
+ return BinderStatus::ok();
+}
+
class ApexService : public BnApexService {
public:
using BinderStatus = ::android::binder::Status;
@@ -79,16 +89,12 @@
BinderStatus getSessions(std::vector<ApexSessionInfo>* aidl_return) override;
BinderStatus getStagedSessionInfo(
int session_id, ApexSessionInfo* apex_session_info) override;
- BinderStatus activatePackage(const std::string& package_path) override;
- BinderStatus deactivatePackage(const std::string& package_path) override;
+ BinderStatus getStagedApexInfos(const ApexSessionParams& params,
+ std::vector<ApexInfo>* aidl_return) override;
BinderStatus getActivePackages(std::vector<ApexInfo>* aidl_return) override;
BinderStatus getActivePackage(const std::string& package_name,
ApexInfo* aidl_return) override;
BinderStatus getAllPackages(std::vector<ApexInfo>* aidl_return) override;
- BinderStatus preinstallPackages(
- const std::vector<std::string>& paths) override;
- BinderStatus postinstallPackages(
- const std::vector<std::string>& paths) override;
BinderStatus abortStagedSession(int session_id) override;
BinderStatus revertActiveSessions() override;
BinderStatus resumeRevertIfNeeded() override;
@@ -137,6 +143,9 @@
if (!debug_check.isOk()) {
return debug_check;
}
+ if (auto is_root = CheckCallerIsRoot("stagePackages"); !is_root.isOk()) {
+ return is_root;
+ }
LOG(DEBUG) << "stagePackages() received by ApexService, paths "
<< android::base::Join(paths, ',');
@@ -155,6 +164,10 @@
BinderStatus ApexService::unstagePackages(
const std::vector<std::string>& paths) {
+ if (auto check = CheckCallerSystemOrRoot("unstagePackages"); !check.isOk()) {
+ return check;
+ }
+
Result<void> res = ::android::apex::UnstagePackages(paths);
if (res.ok()) {
return BinderStatus::ok();
@@ -169,6 +182,11 @@
BinderStatus ApexService::submitStagedSession(const ApexSessionParams& params,
ApexInfoList* apex_info_list) {
+ auto check = CheckCallerSystemOrRoot("submitStagedSession");
+ if (!check.isOk()) {
+ return check;
+ }
+
LOG(DEBUG) << "submitStagedSession() received by ApexService, session id "
<< params.sessionId << " child sessions: ["
<< android::base::Join(params.childSessionIds, ',') << "]";
@@ -195,6 +213,11 @@
}
BinderStatus ApexService::markStagedSessionReady(int session_id) {
+ auto check = CheckCallerSystemOrRoot("markStagedSessionReady");
+ if (!check.isOk()) {
+ return check;
+ }
+
LOG(DEBUG) << "markStagedSessionReady() received by ApexService, session id "
<< session_id;
Result<void> success = ::android::apex::MarkStagedSessionReady(session_id);
@@ -209,6 +232,11 @@
}
BinderStatus ApexService::markStagedSessionSuccessful(int session_id) {
+ auto check = CheckCallerSystemOrRoot("markStagedSessionSuccessful");
+ if (!check.isOk()) {
+ return check;
+ }
+
LOG(DEBUG)
<< "markStagedSessionSuccessful() received by ApexService, session id "
<< session_id;
@@ -224,6 +252,11 @@
}
BinderStatus ApexService::markBootCompleted() {
+ auto check = CheckCallerSystemOrRoot("markBootCompleted");
+ if (!check.isOk()) {
+ return check;
+ }
+
::android::apex::OnBootCompleted();
return BinderStatus::ok();
}
@@ -231,15 +264,15 @@
BinderStatus ApexService::calculateSizeForCompressedApex(
const CompressedApexInfoList& compressed_apex_info_list,
int64_t* required_size) {
- *required_size = 0;
- const auto& instance = ApexFileRepository::GetInstance();
+ std::vector<std::tuple<std::string, int64_t, int64_t>> compressed_apexes;
+ compressed_apexes.reserve(compressed_apex_info_list.apexInfos.size());
for (const auto& apex_info : compressed_apex_info_list.apexInfos) {
- auto should_allocate_space = ShouldAllocateSpaceForDecompression(
- apex_info.moduleName, apex_info.versionCode, instance);
- if (!should_allocate_space.ok() || *should_allocate_space) {
- *required_size += apex_info.decompressedSize;
- }
+ compressed_apexes.emplace_back(apex_info.moduleName, apex_info.versionCode,
+ apex_info.decompressedSize);
}
+ const auto& instance = ApexFileRepository::GetInstance();
+ *required_size = ::android::apex::CalculateSizeForCompressedApex(
+ compressed_apexes, instance);
return BinderStatus::ok();
}
@@ -326,7 +359,17 @@
Result<std::string> preinstalled_path =
instance.GetPreinstalledPath(package.GetManifest().name());
if (preinstalled_path.ok()) {
- out.preinstalledModulePath = *preinstalled_path;
+ // We replace the preinstalled paths for block devices to /system/apex
+ // because PackageManager will not resolve them if they aren't in one of
+ // the SYSTEM_PARTITIONS defined in PackagePartitions.java.
+ // b/195363518 for more context.
+ const std::string block_path = "/dev/block/";
+ const std::string sys_apex_path =
+ std::string(kApexPackageSystemDir) + "/" +
+ preinstalled_path->substr(block_path.length());
+ out.preinstalledModulePath = preinstalled_path->starts_with(block_path)
+ ? sys_apex_path
+ : *preinstalled_path;
}
return out;
}
@@ -345,6 +388,11 @@
BinderStatus ApexService::getSessions(
std::vector<ApexSessionInfo>* aidl_return) {
+ auto check = CheckCallerSystemOrRoot("getSessions");
+ if (!check.isOk()) {
+ return check;
+ }
+
auto sessions = ApexSession::GetSessions();
for (const auto& session : sessions) {
ApexSessionInfo session_info;
@@ -357,6 +405,11 @@
BinderStatus ApexService::getStagedSessionInfo(
int session_id, ApexSessionInfo* apex_session_info) {
+ auto check = CheckCallerSystemOrRoot("getStagedSessionInfo");
+ if (!check.isOk()) {
+ return check;
+ }
+
LOG(DEBUG) << "getStagedSessionInfo() received by ApexService, session id "
<< session_id;
auto session = ApexSession::GetSession(session_id);
@@ -372,50 +425,45 @@
return BinderStatus::ok();
}
-BinderStatus ApexService::activatePackage(const std::string& package_path) {
- BinderStatus debug_check = CheckDebuggable("activatePackage");
- if (!debug_check.isOk()) {
- return debug_check;
+BinderStatus ApexService::getStagedApexInfos(
+ const ApexSessionParams& params, std::vector<ApexInfo>* aidl_return) {
+ auto check = CheckCallerSystemOrRoot("getStagedApexInfos");
+ if (!check.isOk()) {
+ return check;
}
- LOG(DEBUG) << "activatePackage() received by ApexService, path "
- << package_path;
-
- Result<void> res = ::android::apex::ActivatePackage(package_path);
-
- if (res.ok()) {
- return BinderStatus::ok();
+ LOG(DEBUG) << "getStagedApexInfos() received by ApexService, session id "
+ << params.sessionId << " child sessions: ["
+ << android::base::Join(params.childSessionIds, ',') << "]";
+ Result<std::vector<ApexFile>> files = ::android::apex::GetStagedApexFiles(
+ params.sessionId, params.childSessionIds);
+ if (!files.ok()) {
+ LOG(ERROR) << "Failed to getStagedApexInfo session id " << params.sessionId
+ << ": " << files.error();
+ return BinderStatus::fromExceptionCode(
+ BinderStatus::EX_SERVICE_SPECIFIC,
+ String8(files.error().message().c_str()));
}
- LOG(ERROR) << "Failed to activate " << package_path << ": " << res.error();
- return BinderStatus::fromExceptionCode(
- BinderStatus::EX_SERVICE_SPECIFIC,
- String8(res.error().message().c_str()));
-}
-
-BinderStatus ApexService::deactivatePackage(const std::string& package_path) {
- BinderStatus debug_check = CheckDebuggable("deactivatePackage");
- if (!debug_check.isOk()) {
- return debug_check;
+ // Retrieve classpath information
+ auto class_path = ::android::apex::MountAndDeriveClassPath(*files);
+ for (const auto& apex_file : *files) {
+ ApexInfo apex_info = GetApexInfo(apex_file);
+ auto package_name = apex_info.moduleName;
+ apex_info.hasClassPathJars = class_path->HasClassPathJars(package_name);
+ aidl_return->push_back(std::move(apex_info));
}
- LOG(DEBUG) << "deactivatePackage() received by ApexService, path "
- << package_path;
-
- Result<void> res = ::android::apex::DeactivatePackage(package_path);
-
- if (res.ok()) {
- return BinderStatus::ok();
- }
-
- LOG(ERROR) << "Failed to deactivate " << package_path << ": " << res.error();
- return BinderStatus::fromExceptionCode(
- BinderStatus::EX_SERVICE_SPECIFIC,
- String8(res.error().message().c_str()));
+ return BinderStatus::ok();
}
BinderStatus ApexService::getActivePackages(
std::vector<ApexInfo>* aidl_return) {
+ auto check = CheckCallerSystemOrRoot("getActivePackages");
+ if (!check.isOk()) {
+ return check;
+ }
+
auto packages = ::android::apex::GetActivePackages();
for (const auto& package : packages) {
ApexInfo apex_info = GetApexInfo(package);
@@ -428,6 +476,11 @@
BinderStatus ApexService::getActivePackage(const std::string& package_name,
ApexInfo* aidl_return) {
+ auto check = CheckCallerSystemOrRoot("getActivePackage");
+ if (!check.isOk()) {
+ return check;
+ }
+
Result<ApexFile> apex = ::android::apex::GetActivePackage(package_name);
if (apex.ok()) {
*aidl_return = GetApexInfo(*apex);
@@ -437,6 +490,11 @@
}
BinderStatus ApexService::getAllPackages(std::vector<ApexInfo>* aidl_return) {
+ auto check = CheckCallerSystemOrRoot("getAllPackages");
+ if (!check.isOk()) {
+ return check;
+ }
+
const auto& active = ::android::apex::GetActivePackages();
const auto& factory = ::android::apex::GetFactoryPackages();
for (const ApexFile& pkg : active) {
@@ -457,6 +515,11 @@
BinderStatus ApexService::installAndActivatePackage(
const std::string& package_path, ApexInfo* aidl_return) {
+ auto check = CheckCallerSystemOrRoot("installAndActivatePackage");
+ if (!check.isOk()) {
+ return check;
+ }
+
LOG(DEBUG) << "installAndActivatePackage() received by ApexService, path: "
<< package_path;
auto res = InstallPackage(package_path);
@@ -472,45 +535,12 @@
return BinderStatus::ok();
}
-BinderStatus ApexService::preinstallPackages(
- const std::vector<std::string>& paths) {
- BinderStatus debug_check = CheckDebuggable("preinstallPackages");
- if (!debug_check.isOk()) {
- return debug_check;
- }
-
- Result<void> res = ::android::apex::PreinstallPackages(paths);
- if (res.ok()) {
- return BinderStatus::ok();
- }
-
- LOG(ERROR) << "Failed to preinstall packages "
- << android::base::Join(paths, ',') << ": " << res.error();
- return BinderStatus::fromExceptionCode(
- BinderStatus::EX_SERVICE_SPECIFIC,
- String8(res.error().message().c_str()));
-}
-
-BinderStatus ApexService::postinstallPackages(
- const std::vector<std::string>& paths) {
- BinderStatus debug_check = CheckDebuggable("postinstallPackages");
- if (!debug_check.isOk()) {
- return debug_check;
- }
-
- Result<void> res = ::android::apex::PostinstallPackages(paths);
- if (res.ok()) {
- return BinderStatus::ok();
- }
-
- LOG(ERROR) << "Failed to postinstall packages "
- << android::base::Join(paths, ',') << ": " << res.error();
- return BinderStatus::fromExceptionCode(
- BinderStatus::EX_SERVICE_SPECIFIC,
- String8(res.error().message().c_str()));
-}
-
BinderStatus ApexService::abortStagedSession(int session_id) {
+ auto check = CheckCallerSystemOrRoot("abortStagedSession");
+ if (!check.isOk()) {
+ return check;
+ }
+
LOG(DEBUG) << "abortStagedSession() received by ApexService.";
Result<void> res = ::android::apex::AbortStagedSession(session_id);
if (!res.ok()) {
@@ -522,6 +552,11 @@
}
BinderStatus ApexService::revertActiveSessions() {
+ auto check = CheckCallerSystemOrRoot("revertActiveSessions");
+ if (!check.isOk()) {
+ return check;
+ }
+
LOG(DEBUG) << "revertActiveSessions() received by ApexService.";
Result<void> res = ::android::apex::RevertActiveSessions("", "");
if (!res.ok()) {
@@ -538,6 +573,11 @@
return debug_check;
}
+ auto root_check = CheckCallerIsRoot("resumeRevertIfNeeded");
+ if (!root_check.isOk()) {
+ return root_check;
+ }
+
LOG(DEBUG) << "resumeRevertIfNeeded() received by ApexService.";
Result<void> res = ::android::apex::ResumeRevertIfNeeded();
if (!res.ok()) {
@@ -550,6 +590,11 @@
BinderStatus ApexService::snapshotCeData(int user_id, int rollback_id,
const std::string& apex_name) {
+ auto check = CheckCallerSystemOrRoot("snapshotCeData");
+ if (!check.isOk()) {
+ return check;
+ }
+
LOG(DEBUG) << "snapshotCeData() received by ApexService.";
Result<void> res =
::android::apex::SnapshotCeData(user_id, rollback_id, apex_name);
@@ -563,6 +608,11 @@
BinderStatus ApexService::restoreCeData(int user_id, int rollback_id,
const std::string& apex_name) {
+ auto check = CheckCallerSystemOrRoot("restoreCeData");
+ if (!check.isOk()) {
+ return check;
+ }
+
LOG(DEBUG) << "restoreCeData() received by ApexService.";
Result<void> res =
::android::apex::RestoreCeData(user_id, rollback_id, apex_name);
@@ -575,6 +625,11 @@
}
BinderStatus ApexService::destroyDeSnapshots(int rollback_id) {
+ auto check = CheckCallerSystemOrRoot("destroyDeSnapshots");
+ if (!check.isOk()) {
+ return check;
+ }
+
LOG(DEBUG) << "destroyDeSnapshots() received by ApexService.";
Result<void> res = ::android::apex::DestroyDeSnapshots(rollback_id);
if (!res.ok()) {
@@ -586,6 +641,11 @@
}
BinderStatus ApexService::destroyCeSnapshots(int user_id, int rollback_id) {
+ auto check = CheckCallerSystemOrRoot("destroyCeSnapshots");
+ if (!check.isOk()) {
+ return check;
+ }
+
LOG(DEBUG) << "destroyCeSnapshots() received by ApexService.";
Result<void> res = ::android::apex::DestroyCeSnapshots(user_id, rollback_id);
if (!res.ok()) {
@@ -598,6 +658,11 @@
BinderStatus ApexService::destroyCeSnapshotsNotSpecified(
int user_id, const std::vector<int>& retain_rollback_ids) {
+ auto check = CheckCallerSystemOrRoot("destroyCeSnapshotsNotSpecified");
+ if (!check.isOk()) {
+ return check;
+ }
+
LOG(DEBUG) << "destroyCeSnapshotsNotSpecified() received by ApexService.";
Result<void> res = ::android::apex::DestroyCeSnapshotsNotSpecified(
user_id, retain_rollback_ids);
@@ -771,12 +836,6 @@
<< " deactivatePackage [package_path] - deactivate package from the "
"given path"
<< std::endl
- << " preinstallPackages [package_path1] ([package_path2]...) - run "
- "pre-install hooks of the given packages"
- << std::endl
- << " postinstallPackages [package_path1] ([package_path2]...) - run "
- "post-install hooks of the given packages"
- << std::endl
<< " getStagedSessionInfo [sessionId] - displays information about a "
"given session previously submitted"
<< std::endl
@@ -890,12 +949,13 @@
print_help(err, "activatePackage requires one package_path");
return BAD_VALUE;
}
- BinderStatus status = activatePackage(String8(args[1]).string());
- if (status.isOk()) {
+ std::string path = String8(args[1]).string();
+ auto status = ::android::apex::ActivatePackage(path);
+ if (status.ok()) {
return OK;
}
std::string msg = StringLog() << "Failed to activate package: "
- << status.toString8().string() << std::endl;
+ << status.error().message() << std::endl;
dprintf(err, "%s", msg.c_str());
return BAD_VALUE;
}
@@ -905,12 +965,13 @@
print_help(err, "deactivatePackage requires one package_path");
return BAD_VALUE;
}
- BinderStatus status = deactivatePackage(String8(args[1]).string());
- if (status.isOk()) {
+ std::string path = String8(args[1]).string();
+ auto status = ::android::apex::DeactivatePackage(path);
+ if (status.ok()) {
return OK;
}
std::string msg = StringLog() << "Failed to deactivate package: "
- << status.toString8().string() << std::endl;
+ << status.error().message() << std::endl;
dprintf(err, "%s", msg.c_str());
return BAD_VALUE;
}
@@ -986,31 +1047,6 @@
return BAD_VALUE;
}
- if (cmd == String16("preinstallPackages") ||
- cmd == String16("postinstallPackages")) {
- if (args.size() < 2) {
- print_help(err,
- "preinstallPackages/postinstallPackages requires at least"
- " one package_path");
- return BAD_VALUE;
- }
- std::vector<std::string> pkgs;
- pkgs.reserve(args.size() - 1);
- for (size_t i = 1; i != args.size(); ++i) {
- pkgs.emplace_back(String8(args[i]).string());
- }
- BinderStatus status = cmd == String16("preinstallPackages")
- ? preinstallPackages(pkgs)
- : postinstallPackages(pkgs);
- if (status.isOk()) {
- return OK;
- }
- std::string msg = StringLog() << "Failed to pre/postinstall package(s): "
- << status.toString8().string() << std::endl;
- dprintf(err, "%s", msg.c_str());
- return BAD_VALUE;
- }
-
if (cmd == String16("remountPackages")) {
BinderStatus status = remountPackages();
if (status.isOk()) {
diff --git a/apexd/apexservice_test.cpp b/apexd/apexservice_test.cpp
index 545bb38..18918fb 100644
--- a/apexd/apexservice_test.cpp
+++ b/apexd/apexservice_test.cpp
@@ -14,6 +14,31 @@
* limitations under the License.
*/
+#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/apex/ApexInfo.h>
+#include <android/apex/IApexService.h>
+#include <android/os/IVold.h>
+#include <binder/IServiceManager.h>
+#include <fs_mgr_overlayfs.h>
+#include <fstab/fstab.h>
+#include <gmock/gmock.h>
+#include <grp.h>
+#include <gtest/gtest.h>
+#include <libdm/dm.h>
+#include <linux/loop.h>
+#include <selinux/selinux.h>
+#include <stdio.h>
+#include <sys/ioctl.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <sys/xattr.h>
+
#include <algorithm>
#include <filesystem>
#include <fstream>
@@ -24,32 +49,6 @@
#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"
@@ -69,25 +68,21 @@
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::Error;
using android::base::Join;
-using android::base::ReadFully;
+using android::base::Result;
+using android::base::SetProperty;
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 ::apex::proto::SessionState;
using ::testing::EndsWith;
-using ::testing::HasSubstr;
using ::testing::Not;
using ::testing::SizeIs;
using ::testing::UnorderedElementsAre;
@@ -99,7 +94,18 @@
class ApexServiceTest : public ::testing::Test {
public:
- ApexServiceTest() {
+ 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";
+ }
+
+ // Enable VERBOSE logging to simplifying debugging
+ SetProperty("log.tag.apexd", "VERBOSE");
+
using android::IBinder;
using android::IServiceManager;
@@ -112,14 +118,7 @@
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 =
@@ -142,37 +141,6 @@
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);
@@ -207,16 +175,6 @@
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 + "]";
@@ -291,26 +249,6 @@
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;
@@ -495,83 +433,46 @@
ofs.close();
}
+void CreateFileWithExpectedProperties(const std::string& path) {
+ CreateFile(path);
+ std::error_code ec;
+ fs::permissions(
+ path,
+ fs::perms::owner_read | fs::perms::group_write | fs::perms::others_exec,
+ fs::perm_options::replace, ec);
+ ASSERT_FALSE(ec) << "Failed to set permissions: " << ec.message();
+ ASSERT_EQ(0, chown(path.c_str(), 1007 /* log */, 3001 /* net_bt_admin */))
+ << "chown failed: " << strerror(errno);
+ ASSERT_TRUE(RegularFileExists(path));
+ char buf[65536]; // 64kB is max possible xattr list size. See "man 7 xattr".
+ ASSERT_EQ(0, setxattr(path.c_str(), "user.foo", "bar", 4, 0));
+ ASSERT_GE(listxattr(path.c_str(), buf, sizeof(buf)), 9);
+ ASSERT_TRUE(memmem(buf, sizeof(buf), "user.foo", 9) != nullptr);
+ ASSERT_EQ(4, getxattr(path.c_str(), "user.foo", buf, sizeof(buf)));
+ ASSERT_STREQ("bar", buf);
+}
+
+void ExpectFileWithExpectedProperties(const std::string& path) {
+ EXPECT_TRUE(RegularFileExists(path));
+ EXPECT_EQ(fs::status(path).permissions(), fs::perms::owner_read |
+ fs::perms::group_write |
+ fs::perms::others_exec);
+ struct stat sd;
+ ASSERT_EQ(0, stat(path.c_str(), &sd));
+ EXPECT_EQ(1007u, sd.st_uid);
+ EXPECT_EQ(3001u, sd.st_gid);
+ char buf[65536]; // 64kB is max possible xattr list size. See "man 7 xattr".
+ EXPECT_GE(listxattr(path.c_str(), buf, sizeof(buf)), 9);
+ EXPECT_TRUE(memmem(buf, sizeof(buf), "user.foo", 9) != nullptr);
+ EXPECT_EQ(4, getxattr(path.c_str(), "user.foo", buf, sizeof(buf)));
+ EXPECT_STREQ("bar", buf);
+}
+
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) {
@@ -590,66 +491,6 @@
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"),
@@ -711,85 +552,6 @@
}
}
-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",
@@ -827,15 +589,13 @@
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"));
+ CreateFileWithExpectedProperties(
+ "/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"));
+ ExpectFileWithExpectedProperties(
+ "/data/misc_ce/0/apexrollback/123456/apex.apexd_test/hello.txt");
}
TEST_F(ApexServiceTest, RestoreCeData) {
@@ -844,21 +604,22 @@
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");
+ CreateFileWithExpectedProperties(
+ "/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"));
+ ExpectFileWithExpectedProperties(
+ "/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(
+ ExpectFileWithExpectedProperties(
+ "/data/misc_ce/0/apexdata/apex.apexd_test/oldfile.txt");
+ EXPECT_FALSE(RegularFileExists(
"/data/misc_ce/0/apexdata/apex.apexd_test/newfile.txt"));
// The snapshot should be deleted after restoration.
- ASSERT_FALSE(
+ EXPECT_FALSE(
DirExists("/data/misc_ce/0/apexrollback/123456/apex.apexd_test"));
}
@@ -979,441 +740,6 @@
}
}
-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));
@@ -1490,247 +816,6 @@
}
}
-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",
@@ -2224,61 +1309,6 @@
}
}
-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(); }
@@ -2514,30 +1544,10 @@
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");
+ FILE* cmd_pipe = popen(cmd.c_str(), "r"); // NOLINT(cert-env33-c): test code
if (cmd_pipe == nullptr) {
PLOG(ERROR) << "Cannot open pipe for " << cmd;
return 0;
@@ -2691,18 +1701,15 @@
}
ApexServiceTest::SetUp();
- // Assert that shim apex is pre-installed.
+ // Skip test if for some reason shim APEX is missing.
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)));
+ bool found = std::any_of(list.begin(), list.end(), [](const auto& apex) {
+ return apex.moduleName == "com.android.apex.cts.shim";
+ });
+ if (!found) {
+ GTEST_SKIP() << "Can't find com.android.apex.cts.shim";
+ }
}
};
@@ -2840,82 +1847,6 @@
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(
@@ -2953,195 +1884,6 @@
}
};
-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
diff --git a/apexd/sysprop/Android.bp b/apexd/sysprop/Android.bp
index 0b81efd..050434b 100644
--- a/apexd/sysprop/Android.bp
+++ b/apexd/sysprop/Android.bp
@@ -7,5 +7,6 @@
srcs: ["ApexProperties.sysprop"],
property_owner: "Platform",
api_packages: ["android.sysprop"],
+ ramdisk_available: true,
recovery_available: true,
}
diff --git a/apexer/Android.bp b/apexer/Android.bp
index 081bae1..959836a 100644
--- a/apexer/Android.bp
+++ b/apexer/Android.bp
@@ -27,6 +27,7 @@
"zipalign",
"make_f2fs",
"sload_f2fs",
+ "make_erofs",
// TODO(b/124476339) apex doesn't follow 'required' dependencies so we need to include this
// manually for 'avbtool'.
"fec",
@@ -43,14 +44,6 @@
srcs: [
"apex_manifest.py",
],
- version: {
- py2: {
- enabled: true,
- },
- py3: {
- enabled: true,
- },
- },
libs: [
"apex_manifest_proto",
],
@@ -66,12 +59,8 @@
":mke2fs_conf",
],
version: {
- py2: {
- enabled: true,
- embedded_launcher: true,
- },
py3: {
- enabled: false,
+ embedded_launcher: true,
},
},
libs: [
@@ -88,12 +77,8 @@
"conv_apex_manifest.py",
],
version: {
- py2: {
- enabled: true,
- embedded_launcher: true,
- },
py3: {
- enabled: false,
+ embedded_launcher: true,
},
},
libs: [
@@ -138,22 +123,13 @@
"testdata/com.android.example.apex.pem",
"testdata/com.android.example.apex.pk8",
"testdata/com.android.example.apex.x509.pem",
+ "testdata/manifest_v1.xml",
+ "testdata/manifest_v2.xml",
],
- version: {
- py2: {
- enabled: false,
- },
- py3: {
- enabled: true,
- },
- },
test_suites: ["general-tests"],
libs: [
"apex_manifest",
],
- test_options: {
- unit_test: false,
- },
}
apexer_deps_minus_go_tools = apexer_tools + [
@@ -177,7 +153,7 @@
],
out: ["apexer_test_host_tools.zip"],
tools: apexer_deps_tools + [
- // To force signapk.jar generated in out/soong/host
+ // To force signapk.jar generated in out/host
"signapk",
],
cmd: "HOST_OUT_BIN=$$(dirname $(location apexer)) && " +
diff --git a/apexer/TEST_MAPPING b/apexer/TEST_MAPPING
index 0b3e797..1ad3078 100644
--- a/apexer/TEST_MAPPING
+++ b/apexer/TEST_MAPPING
@@ -1,11 +1,9 @@
{
- // b/214537606: disabled since test lab JDK version has been upgraded to follow newer branches,
- // and will not be able to match sc-v2-dev
- //"presubmit": [
- // {
- // "name": "apexer_test"
- // }
- //],
+ "presubmit": [
+ {
+ "name": "apexer_test"
+ }
+ ],
"imports": [
{
"path": "system/apex/tests"
diff --git a/apexer/apexer.py b/apexer/apexer.py
index f0c17f4..88c6029 100644
--- a/apexer/apexer.py
+++ b/apexer/apexer.py
@@ -1,4 +1,4 @@
-#!/usr/bin/env python
+#!/usr/bin/env python3
#
# Copyright (C) 2018 The Android Open Source Project
#
@@ -32,6 +32,8 @@
import tempfile
import uuid
import xml.etree.ElementTree as ET
+import zipfile
+import glob
from apex_manifest import ValidateApexManifest
from apex_manifest import ApexManifestError
from manifest import android_ns
@@ -106,8 +108,8 @@
metavar='FS_TYPE',
required=False,
default='ext4',
- choices=['ext4', 'f2fs'],
- help='type of filesystem being used for payload image "ext4" or "f2fs"')
+ choices=['ext4', 'f2fs', 'erofs'],
+ help='type of filesystem being used for payload image "ext4", "f2fs" or "erofs"')
parser.add_argument(
'--override_apk_package_name',
required=False,
@@ -201,6 +203,7 @@
p = subprocess.Popen(
cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, env=env)
output, _ = p.communicate()
+ output = output.decode()
if verbose or p.returncode not in expected_return_values:
print(output.rstrip())
@@ -263,7 +266,7 @@
if not os.path.exists(args.build_info):
print("Build info file '" + args.build_info + "' does not exist")
return False
- with open(args.build_info) as buildInfoFile:
+ with open(args.build_info, 'rb') as buildInfoFile:
build_info = apex_build_info_pb2.ApexBuildInfo()
build_info.ParseFromString(buildInfoFile.read())
@@ -362,13 +365,13 @@
if (args.include_cmd_line_in_build_info):
build_info.apexer_command_line = str(sys.argv)
- with open(args.file_contexts) as f:
+ with open(args.file_contexts, 'rb') as f:
build_info.file_contexts = f.read()
- with open(args.canned_fs_config) as f:
+ with open(args.canned_fs_config, 'rb') as f:
build_info.canned_fs_config = f.read()
- with open(args.android_manifest) as f:
+ with open(args.android_manifest, 'rb') as f:
build_info.android_manifest = f.read()
if args.target_sdk_version:
@@ -435,202 +438,250 @@
indent = get_indent(application.previousSibling, 1)
application.appendChild(doc.createTextNode(indent))
- with tempfile.NamedTemporaryFile(delete=False) as temp:
+ with tempfile.NamedTemporaryFile(delete=False, mode='w') as temp:
write_xml(temp, doc)
return temp.name
-def CreateApex(args, work_dir):
- if not ValidateArgs(args):
- return False
- if args.verbose:
- print 'Using tools from ' + str(tool_path_list)
+def ShaHashFiles(file_paths):
+ """get hash for a number of files."""
+ h = hashlib.sha256()
+ for file_path in file_paths:
+ with open(file_path, 'rb') as file:
+ while True:
+ chunk = file.read(h.block_size)
+ if not chunk:
+ break
+ h.update(chunk)
+ return h.hexdigest()
- def copyfile(src, dst):
- if args.verbose:
- print('Copying ' + src + ' to ' + dst)
- shutil.copyfile(src, dst)
- try:
- manifest_apex = ValidateApexManifest(args.manifest)
- except ApexManifestError as err:
- print("'" + args.manifest + "' is not a valid manifest file")
- print err.errmessage
- return False
- except IOError:
- print("Cannot read manifest file: '" + args.manifest + "'")
- return False
+def CreateImageExt4(args, work_dir, manifests_dir, img_file):
+ """Create image for ext4 file system."""
+ # sufficiently big = size + 16MB margin
+ size_in_mb = (GetDirSize(args.input_dir) // (1024 * 1024))
+ size_in_mb += 16
- # create an empty image that is sufficiently big
- size_in_mb = (GetDirSize(args.input_dir) / (1024 * 1024))
+ # Margin is for files that are not under args.input_dir. this consists of
+ # n inodes for apex_manifest files and 11 reserved inodes for ext4.
+ # TOBO(b/122991714) eliminate these details. Use build_image.py which
+ # determines the optimal inode count by first building an image and then
+ # count the inodes actually used.
+ inode_num_margin = GetFilesAndDirsCount(manifests_dir) + 11
+ inode_num = GetFilesAndDirsCount(args.input_dir) + inode_num_margin
- content_dir = os.path.join(work_dir, 'content')
- os.mkdir(content_dir)
+ cmd = ['mke2fs']
+ cmd.extend(['-O', '^has_journal']) # because image is read-only
+ cmd.extend(['-b', str(BLOCK_SIZE)])
+ cmd.extend(['-m', '0']) # reserved block percentage
+ cmd.extend(['-t', 'ext4'])
+ cmd.extend(['-I', '256']) # inode size
+ cmd.extend(['-N', str(inode_num)])
+ uu = str(uuid.uuid5(uuid.NAMESPACE_URL, 'www.android.com'))
+ cmd.extend(['-U', uu])
+ cmd.extend(['-E', 'hash_seed=' + uu])
+ cmd.append(img_file)
+ cmd.append(str(size_in_mb) + 'M')
+ with tempfile.NamedTemporaryFile(dir=work_dir, suffix='mke2fs.conf') as conf_file:
+ conf_data = pkgutil.get_data('apexer', 'mke2fs.conf')
+ conf_file.write(conf_data)
+ conf_file.flush()
+ RunCommand(cmd, args.verbose,
+ {'MKE2FS_CONFIG': conf_file.name, 'E2FSPROGS_FAKE_TIME': '1'})
- # APEX manifest is also included in the image. The manifest is included
- # twice: once inside the image and once outside the image (but still
- # within the zip container).
- manifests_dir = os.path.join(work_dir, 'manifests')
- os.mkdir(manifests_dir)
- copyfile(args.manifest, os.path.join(manifests_dir, 'apex_manifest.pb'))
- if args.manifest_json:
- # manifest_json is for compatibility
- copyfile(args.manifest_json, os.path.join(manifests_dir, 'apex_manifest.json'))
+ # Compile the file context into the binary form
+ compiled_file_contexts = os.path.join(work_dir, 'file_contexts.bin')
+ cmd = ['sefcontext_compile']
+ cmd.extend(['-o', compiled_file_contexts])
+ cmd.append(args.file_contexts)
+ RunCommand(cmd, args.verbose)
+ # Add files to the image file
+ cmd = ['e2fsdroid']
+ cmd.append('-e') # input is not android_sparse_file
+ cmd.extend(['-f', args.input_dir])
+ cmd.extend(['-T', '0']) # time is set to epoch
+ cmd.extend(['-S', compiled_file_contexts])
+ cmd.extend(['-C', args.canned_fs_config])
+ cmd.extend(['-a', '/'])
+ cmd.append('-s') # share dup blocks
+ cmd.append(img_file)
+ RunCommand(cmd, args.verbose, {'E2FSPROGS_FAKE_TIME': '1'})
+
+ cmd = ['e2fsdroid']
+ cmd.append('-e') # input is not android_sparse_file
+ cmd.extend(['-f', manifests_dir])
+ cmd.extend(['-T', '0']) # time is set to epoch
+ cmd.extend(['-S', compiled_file_contexts])
+ cmd.extend(['-C', args.canned_fs_config])
+ cmd.extend(['-a', '/'])
+ cmd.append('-s') # share dup blocks
+ cmd.append(img_file)
+ RunCommand(cmd, args.verbose, {'E2FSPROGS_FAKE_TIME': '1'})
+
+ # Resize the image file to save space
+ cmd = ['resize2fs']
+ cmd.append('-M') # shrink as small as possible
+ cmd.append(img_file)
+ RunCommand(cmd, args.verbose, {'E2FSPROGS_FAKE_TIME': '1'})
+
+
+def CreateImageF2fs(args, manifests_dir, img_file):
+ """Create image for f2fs file system."""
+ # F2FS requires a ~100M minimum size (necessary for ART, could be reduced
+ # a bit for other)
+ # TODO(b/158453869): relax these requirements for readonly devices
+ size_in_mb = (GetDirSize(args.input_dir) // (1024 * 1024))
+ size_in_mb += 100
+
+ # Create an empty image
+ cmd = ['/usr/bin/fallocate']
+ cmd.extend(['-l', str(size_in_mb)+'M'])
+ cmd.append(img_file)
+ RunCommand(cmd, args.verbose)
+
+ # Format the image to F2FS
+ cmd = ['make_f2fs']
+ cmd.extend(['-g', 'android'])
+ uu = str(uuid.uuid5(uuid.NAMESPACE_URL, 'www.android.com'))
+ cmd.extend(['-U', uu])
+ cmd.extend(['-T', '0'])
+ cmd.append('-r') # sets checkpointing seed to 0 to remove random bits
+ cmd.append(img_file)
+ RunCommand(cmd, args.verbose)
+
+ # Add files to the image
+ cmd = ['sload_f2fs']
+ cmd.extend(['-C', args.canned_fs_config])
+ cmd.extend(['-f', manifests_dir])
+ cmd.extend(['-s', args.file_contexts])
+ cmd.extend(['-T', '0'])
+ cmd.append(img_file)
+ RunCommand(cmd, args.verbose, expected_return_values={0,1})
+
+ cmd = ['sload_f2fs']
+ cmd.extend(['-C', args.canned_fs_config])
+ cmd.extend(['-f', args.input_dir])
+ cmd.extend(['-s', args.file_contexts])
+ cmd.extend(['-T', '0'])
+ cmd.append(img_file)
+ RunCommand(cmd, args.verbose, expected_return_values={0,1})
+
+ # TODO(b/158453869): resize the image file to save space
+
+
+def CreateImageErofs(args, work_dir, manifests_dir, img_file):
+ """Create image for erofs file system."""
+ # mkfs.erofs doesn't support multiple input
+
+ tmp_input_dir = os.path.join(work_dir, 'tmp_input_dir')
+ os.mkdir(tmp_input_dir)
+ cmd = ['/bin/cp', '-ra']
+ cmd.extend(glob.glob(manifests_dir + '/*'))
+ cmd.extend(glob.glob(args.input_dir + '/*'))
+ cmd.append(tmp_input_dir)
+ RunCommand(cmd, args.verbose)
+
+ cmd = ['make_erofs']
+ cmd.extend(['-z', 'lz4hc'])
+ cmd.extend(['--fs-config-file', args.canned_fs_config])
+ cmd.extend(['--file-contexts', args.file_contexts])
+ uu = str(uuid.uuid5(uuid.NAMESPACE_URL, 'www.android.com'))
+ cmd.extend(['-U', uu])
+ cmd.extend(['-T', '0'])
+ cmd.extend([img_file, tmp_input_dir])
+ RunCommand(cmd, args.verbose)
+ shutil.rmtree(tmp_input_dir)
+
+ # The minimum image size of erofs is 4k, which will cause an error
+ # when execute generate_hash_tree in avbtool
+ cmd = ['/bin/ls', '-lgG', img_file]
+ output, _ = RunCommand(cmd, verbose=False)
+ image_size = int(output.split()[2])
+ if image_size == 4096:
+ cmd = ['/usr/bin/fallocate', '-l', '8k', img_file]
+ RunCommand(cmd, verbose=False)
+
+
+def CreateImage(args, work_dir, manifests_dir, img_file):
+ """create payload image."""
+ if args.payload_fs_type == 'ext4':
+ CreateImageExt4(args, work_dir, manifests_dir, img_file)
+ elif args.payload_fs_type == 'f2fs':
+ CreateImageF2fs(args, manifests_dir, img_file)
+ elif args.payload_fs_type == 'erofs':
+ CreateImageErofs(args, work_dir, manifests_dir, img_file)
+
+
+def SignImage(args, manifest_apex, img_file):
+ """sign payload image.
+
+ Args:
+ args: apexer options
+ manifest_apex: apex manifest proto
+ img_file: unsigned payload image file
+ """
+
+ if args.do_not_check_keyname or args.unsigned_payload:
+ key_name = manifest_apex.name
+ else:
+ key_name = os.path.basename(os.path.splitext(args.key)[0])
+
+ cmd = ['avbtool']
+ cmd.append('add_hashtree_footer')
+ cmd.append('--do_not_generate_fec')
+ cmd.extend(['--algorithm', 'SHA256_RSA4096'])
+ cmd.extend(['--hash_algorithm', 'sha256'])
+ cmd.extend(['--key', args.key])
+ cmd.extend(['--prop', 'apex.key:' + key_name])
+ # Set up the salt based on manifest content which includes name
+ # and version
+ salt = hashlib.sha256(manifest_apex.SerializeToString()).hexdigest()
+ cmd.extend(['--salt', salt])
+ cmd.extend(['--image', img_file])
+ if args.no_hashtree:
+ cmd.append('--no_hashtree')
+ if args.signing_args:
+ cmd.extend(shlex.split(args.signing_args))
+ RunCommand(cmd, args.verbose)
+
+ # Get the minimum size of the partition required.
+ # TODO(b/113320014) eliminate this step
+ info, _ = RunCommand(['avbtool', 'info_image', '--image', img_file],
+ args.verbose)
+ vbmeta_offset = int(re.search('VBMeta\ offset:\ *([0-9]+)', info).group(1))
+ vbmeta_size = int(re.search('VBMeta\ size:\ *([0-9]+)', info).group(1))
+ partition_size = RoundUp(vbmeta_offset + vbmeta_size,
+ BLOCK_SIZE) + BLOCK_SIZE
+
+ # Resize to the minimum size
+ # TODO(b/113320014) eliminate this step
+ cmd = ['avbtool']
+ cmd.append('resize_image')
+ cmd.extend(['--image', img_file])
+ cmd.extend(['--partition_size', str(partition_size)])
+ RunCommand(cmd, args.verbose)
+
+
+def CreateApexPayload(args, work_dir, content_dir, manifests_dir,
+ manifest_apex):
+ """Create payload.
+
+ Args:
+ args: apexer options
+ work_dir: apex container working directory
+ content_dir: the working directory for payload contents
+ manifests_dir: manifests directory
+ manifest_apex: apex manifest proto
+
+ Returns:
+ payload file
+ """
if args.payload_type == 'image':
- if args.do_not_check_keyname or args.unsigned_payload:
- key_name = manifest_apex.name
- else:
- key_name = os.path.basename(os.path.splitext(args.key)[0])
-
img_file = os.path.join(content_dir, 'apex_payload.img')
-
- if args.payload_fs_type == 'ext4':
- # sufficiently big = size + 16MB margin
- size_in_mb += 16
-
- # margin is for files that are not under args.input_dir. this consists of
- # n inodes for apex_manifest files and 11 reserved inodes for ext4.
- # TOBO(b/122991714) eliminate these details. use build_image.py which
- # determines the optimal inode count by first building an image and then
- # count the inodes actually used.
- inode_num_margin = GetFilesAndDirsCount(manifests_dir) + 11
- inode_num = GetFilesAndDirsCount(args.input_dir) + inode_num_margin
-
- cmd = ['mke2fs']
- cmd.extend(['-O', '^has_journal']) # because image is read-only
- cmd.extend(['-b', str(BLOCK_SIZE)])
- cmd.extend(['-m', '0']) # reserved block percentage
- cmd.extend(['-t', 'ext4'])
- cmd.extend(['-I', '256']) # inode size
- cmd.extend(['-N', str(inode_num)])
- uu = str(uuid.uuid5(uuid.NAMESPACE_URL, 'www.android.com'))
- cmd.extend(['-U', uu])
- cmd.extend(['-E', 'hash_seed=' + uu])
- cmd.append(img_file)
- cmd.append(str(size_in_mb) + 'M')
- with tempfile.NamedTemporaryFile(dir=work_dir, suffix="mke2fs.conf") as conf_file:
- conf_data = pkgutil.get_data('apexer', 'mke2fs.conf')
- conf_file.write(conf_data)
- conf_file.flush()
- RunCommand(cmd, args.verbose,
- {"MKE2FS_CONFIG": conf_file.name, 'E2FSPROGS_FAKE_TIME': '1'})
-
- # Compile the file context into the binary form
- compiled_file_contexts = os.path.join(work_dir, 'file_contexts.bin')
- cmd = ['sefcontext_compile']
- cmd.extend(['-o', compiled_file_contexts])
- cmd.append(args.file_contexts)
- RunCommand(cmd, args.verbose)
-
- # Add files to the image file
- cmd = ['e2fsdroid']
- cmd.append('-e') # input is not android_sparse_file
- cmd.extend(['-f', args.input_dir])
- cmd.extend(['-T', '0']) # time is set to epoch
- cmd.extend(['-S', compiled_file_contexts])
- cmd.extend(['-C', args.canned_fs_config])
- cmd.append('-s') # share dup blocks
- cmd.append(img_file)
- RunCommand(cmd, args.verbose, {'E2FSPROGS_FAKE_TIME': '1'})
-
- cmd = ['e2fsdroid']
- cmd.append('-e') # input is not android_sparse_file
- cmd.extend(['-f', manifests_dir])
- cmd.extend(['-T', '0']) # time is set to epoch
- cmd.extend(['-S', compiled_file_contexts])
- cmd.extend(['-C', args.canned_fs_config])
- cmd.append('-s') # share dup blocks
- cmd.append(img_file)
- RunCommand(cmd, args.verbose, {'E2FSPROGS_FAKE_TIME': '1'})
-
- # Resize the image file to save space
- cmd = ['resize2fs']
- cmd.append('-M') # shrink as small as possible
- cmd.append(img_file)
- RunCommand(cmd, args.verbose, {'E2FSPROGS_FAKE_TIME': '1'})
-
- elif args.payload_fs_type == 'f2fs':
- # F2FS requires a ~100M minimum size (necessary for ART, could be reduced a bit for other)
- # TODO(b/158453869): relax these requirements for readonly devices
- size_in_mb += 100
-
- # Create an empty image
- cmd = ['/usr/bin/fallocate']
- cmd.extend(['-l', str(size_in_mb)+'M'])
- cmd.append(img_file)
- RunCommand(cmd, args.verbose)
-
- # Format the image to F2FS
- cmd = ['make_f2fs']
- cmd.extend(['-g', 'android'])
- uu = str(uuid.uuid5(uuid.NAMESPACE_URL, 'www.android.com'))
- cmd.extend(['-U', uu])
- cmd.extend(['-T', '0'])
- cmd.append('-r') # sets checkpointing seed to 0 to remove random bits
- cmd.append(img_file)
- RunCommand(cmd, args.verbose)
-
- # Add files to the image
- cmd = ['sload_f2fs']
- cmd.extend(['-C', args.canned_fs_config])
- cmd.extend(['-f', manifests_dir])
- cmd.extend(['-s', args.file_contexts])
- cmd.extend(['-T', '0'])
- cmd.append(img_file)
- RunCommand(cmd, args.verbose, expected_return_values={0,1})
-
- cmd = ['sload_f2fs']
- cmd.extend(['-C', args.canned_fs_config])
- cmd.extend(['-f', args.input_dir])
- cmd.extend(['-s', args.file_contexts])
- cmd.extend(['-T', '0'])
- cmd.append(img_file)
- RunCommand(cmd, args.verbose, expected_return_values={0,1})
-
- # TODO(b/158453869): resize the image file to save space
-
- if args.unsigned_payload_only:
- shutil.copyfile(img_file, args.output)
- if (args.verbose):
- print('Created (unsigned payload only) ' + args.output)
- return True
-
+ CreateImage(args, work_dir, manifests_dir, img_file)
if not args.unsigned_payload:
- cmd = ['avbtool']
- cmd.append('add_hashtree_footer')
- cmd.append('--do_not_generate_fec')
- cmd.extend(['--algorithm', 'SHA256_RSA4096'])
- cmd.extend(['--hash_algorithm', 'sha256'])
- cmd.extend(['--key', args.key])
- cmd.extend(['--prop', 'apex.key:' + key_name])
- # Set up the salt based on manifest content which includes name
- # and version
- salt = hashlib.sha256(manifest_apex.SerializeToString()).hexdigest()
- cmd.extend(['--salt', salt])
- cmd.extend(['--image', img_file])
- if args.no_hashtree:
- cmd.append('--no_hashtree')
- if args.signing_args:
- cmd.extend(shlex.split(args.signing_args))
- RunCommand(cmd, args.verbose)
-
- # Get the minimum size of the partition required.
- # TODO(b/113320014) eliminate this step
- info, _ = RunCommand(['avbtool', 'info_image', '--image', img_file],
- args.verbose)
- vbmeta_offset = int(re.search('VBMeta\ offset:\ *([0-9]+)', info).group(1))
- vbmeta_size = int(re.search('VBMeta\ size:\ *([0-9]+)', info).group(1))
- partition_size = RoundUp(vbmeta_offset + vbmeta_size,
- BLOCK_SIZE) + BLOCK_SIZE
-
- # Resize to the minimum size
- # TODO(b/113320014) eliminate this step
- cmd = ['avbtool']
- cmd.append('resize_image')
- cmd.extend(['--image', img_file])
- cmd.extend(['--partition_size', str(partition_size)])
- RunCommand(cmd, args.verbose)
+ SignImage(args, manifest_apex, img_file)
else:
img_file = os.path.join(content_dir, 'apex_payload.zip')
cmd = ['soong_zip']
@@ -640,20 +691,25 @@
cmd.extend(['-C', manifests_dir])
cmd.extend(['-D', manifests_dir])
RunCommand(cmd, args.verbose)
+ return img_file
- if args.payload_only:
- shutil.copyfile(img_file, args.output)
- if (args.verbose):
- print('Created (payload only) ' + args.output)
- return True
- # package the image file and APEX manifest as an APK.
- # The AndroidManifest file is automatically generated if not given.
+def CreateAndroidManifestXml(args, work_dir, manifest_apex):
+ """Create AndroidManifest.xml file.
+
+ Args:
+ args: apexer options
+ work_dir: apex container working directory
+ manifest_apex: apex manifest proto
+
+ Returns:
+ AndroidManifest.xml file inside the work dir
+ """
android_manifest_file = os.path.join(work_dir, 'AndroidManifest.xml')
if not args.android_manifest:
if args.verbose:
print('Creating AndroidManifest ' + android_manifest_file)
- with open(android_manifest_file, 'w+') as f:
+ with open(android_manifest_file, 'w') as f:
app_package_name = manifest_apex.name
f.write(PrepareAndroidManifest(app_package_name, manifest_apex.version))
args.android_manifest = android_manifest_file
@@ -662,15 +718,75 @@
shutil.copyfile(args.android_manifest, android_manifest_file)
# If logging parent is specified, add it to the AndroidManifest.
- if args.logging_parent != "":
+ if args.logging_parent:
android_manifest_file = AddLoggingParent(android_manifest_file,
args.logging_parent)
+ return android_manifest_file
- # copy manifest to the content dir so that it is also accessible
- # without mounting the image
- copyfile(args.manifest, os.path.join(content_dir, 'apex_manifest.pb'))
+
+def CreateApex(args, work_dir):
+ if not ValidateArgs(args):
+ return False
+
+ if args.verbose:
+ print('Using tools from ' + str(tool_path_list))
+
+ def CopyFile(src, dst):
+ if args.verbose:
+ print('Copying ' + src + ' to ' + dst)
+ shutil.copyfile(src, dst)
+
+ try:
+ manifest_apex = ValidateApexManifest(args.manifest)
+ except ApexManifestError as err:
+ print("'" + args.manifest + "' is not a valid manifest file")
+ print(err.errmessage)
+ return False
+ except IOError:
+ print("Cannot read manifest file: '" + args.manifest + "'")
+ return False
+
+ # Create content dir and manifests dir, the manifests dir is used to
+ # create the payload image
+ content_dir = os.path.join(work_dir, 'content')
+ os.mkdir(content_dir)
+ manifests_dir = os.path.join(work_dir, 'manifests')
+ os.mkdir(manifests_dir)
+
+ # Create AndroidManifest.xml file first so that we can hash the file
+ # and store the hashed value in the manifest proto buf that goes into
+ # the payload image. So any change in this file will ensure changes
+ # in payload image file
+ android_manifest_file = CreateAndroidManifestXml(
+ args, work_dir, manifest_apex)
+ files_to_hash = [android_manifest_file]
+ manifest_apex.apexContainerFilesHash = ShaHashFiles(files_to_hash)
+
+ # APEX manifest is also included in the image. The manifest is included
+ # twice: once inside the image and once outside the image (but still
+ # within the zip container).
+ with open(os.path.join(manifests_dir, 'apex_manifest.pb'), 'wb') as f:
+ f.write(manifest_apex.SerializeToString())
+ with open(os.path.join(content_dir, 'apex_manifest.pb'), 'wb') as f:
+ f.write(manifest_apex.SerializeToString())
if args.manifest_json:
- copyfile(args.manifest_json, os.path.join(content_dir, 'apex_manifest.json'))
+ CopyFile(args.manifest_json,
+ os.path.join(manifests_dir, 'apex_manifest.json'))
+ CopyFile(args.manifest_json,
+ os.path.join(content_dir, 'apex_manifest.json'))
+
+ # Create payload
+ img_file = CreateApexPayload(args, work_dir, content_dir, manifests_dir,
+ manifest_apex)
+
+ if args.unsigned_payload_only or args.payload_only:
+ shutil.copyfile(img_file, args.output)
+ if args.verbose:
+ if args.unsigned_payload_only:
+ print('Created (unsigned payload only) ' + args.output)
+ else:
+ print('Created (payload only) ' + args.output)
+ return True
# copy the public key, if specified
if args.pubkey:
@@ -678,7 +794,7 @@
if args.include_build_info:
build_info = GenerateBuildInfo(args)
- with open(os.path.join(content_dir, 'apex_build_info.pb'), "wb") as f:
+ with open(os.path.join(content_dir, 'apex_build_info.pb'), 'wb') as f:
f.write(build_info.SerializeToString())
apk_file = os.path.join(work_dir, 'apex.apk')
@@ -706,33 +822,10 @@
RunCommand(cmd, args.verbose)
zip_file = os.path.join(work_dir, 'apex.zip')
- cmd = ['soong_zip']
- cmd.append('-d') # include directories
- cmd.extend(['-C', content_dir]) # relative root
- cmd.extend(['-D', content_dir]) # input dir
- for file_ in os.listdir(content_dir):
- if os.path.isfile(os.path.join(content_dir, file_)):
- cmd.extend(['-s', file_]) # don't compress any files
- cmd.extend(['-o', zip_file])
- RunCommand(cmd, args.verbose)
+ CreateZip(content_dir, zip_file)
+ MergeZips([apk_file, zip_file], args.output)
- unaligned_apex_file = os.path.join(work_dir, 'unaligned.apex')
- cmd = ['merge_zips']
- cmd.append('-j') # sort
- cmd.append(unaligned_apex_file) # output
- cmd.append(apk_file) # input
- cmd.append(zip_file) # input
- RunCommand(cmd, args.verbose)
-
- # Align the files at page boundary for efficient access
- cmd = ['zipalign']
- cmd.append('-f')
- cmd.append(str(BLOCK_SIZE))
- cmd.append(unaligned_apex_file)
- cmd.append(args.output)
- RunCommand(cmd, args.verbose)
-
- if (args.verbose):
+ if args.verbose:
print('Created ' + args.output)
return True
@@ -747,6 +840,32 @@
def __exit__(self, *unused):
shutil.rmtree(self.name)
+def CreateZip(content_dir, apex_zip):
+ with zipfile.ZipFile(apex_zip, 'w', compression=zipfile.ZIP_DEFLATED) as out:
+ for root, _, files in os.walk(content_dir):
+ for file in files:
+ path = os.path.join(root, file)
+ rel_path = os.path.relpath(path, content_dir)
+ # "apex_payload.img" shouldn't be compressed
+ if rel_path == 'apex_payload.img':
+ out.write(path, rel_path, compress_type=zipfile.ZIP_STORED)
+ else:
+ out.write(path, rel_path)
+
+def MergeZips(zip_files, output_zip):
+ with zipfile.ZipFile(output_zip, 'w') as out:
+ for file in zip_files:
+ # copy to output_zip
+ with zipfile.ZipFile(file, 'r') as inzip:
+ for info in inzip.infolist():
+ # reset timestamp for deterministic output
+ info.date_time = (1980, 1, 1, 0, 0, 0)
+ # "apex_payload.img" should be 4K aligned
+ if info.filename == 'apex_payload.img':
+ data_offset = out.fp.tell() + len(info.FileHeader())
+ info.extra = b'\0' * (BLOCK_SIZE - data_offset % BLOCK_SIZE)
+ data = inzip.read(info)
+ out.writestr(info, data)
def main(argv):
global tool_path_list
diff --git a/apexer/apexer_test.py b/apexer/apexer_test.py
index 7fec4ec..911ef9f 100644
--- a/apexer/apexer_test.py
+++ b/apexer/apexer_test.py
@@ -40,7 +40,8 @@
TEST_X509_KEY = os.path.join("testdata", "com.android.example.apex.x509.pem")
TEST_PK8_KEY = os.path.join("testdata", "com.android.example.apex.pk8")
TEST_AVB_PUBLIC_KEY = os.path.join("testdata", "com.android.example.apex.avbpubkey")
-
+TEST_MANIFEST_XML_V1 = os.path.join("testdata", "manifest_v1.xml")
+TEST_MANIFEST_XML_V2 = os.path.join("testdata", "manifest_v2.xml")
def run(args, verbose=None, **kwargs):
"""Creates and returns a subprocess.Popen object.
@@ -74,7 +75,7 @@
def run_host_command(args, verbose=None, **kwargs):
host_build_top = os.environ.get("ANDROID_BUILD_TOP")
if host_build_top:
- host_command_dir = os.path.join(host_build_top, "out/soong/host/linux-x86/bin")
+ host_command_dir = os.path.join(host_build_top, "out/host/linux-x86/bin")
args[0] = os.path.join(host_command_dir, args[0])
return run_and_check_output(args, verbose, **kwargs)
@@ -270,7 +271,7 @@
payload_only = True
os.environ["APEXER_TOOL_PATH"] = (self.host_tools_path +
- ":out/soong/host/linux-x86/bin:prebuilts/sdk/tools/linux/bin")
+ ":out/host/linux-x86/bin:prebuilts/sdk/tools/linux/bin")
cmd = ["apexer", "--force", "--include_build_info", "--do_not_check_keyname"]
if DEBUG_TEST:
cmd.append('-v')
@@ -315,7 +316,7 @@
java_dep_lib += ":" + os.path.join(os.environ["ANDROID_HOST_OUT"], "lib64")
if "ANDROID_BUILD_TOP" in os.environ:
java_dep_lib += ":" + os.path.join(os.environ["ANDROID_BUILD_TOP"],
- "out/soong/host/linux-x86/lib64")
+ "out/host/linux-x86/lib64")
return [java_toolchain, java_dep_lib]
@@ -328,7 +329,7 @@
java_toolchain,
"-Djava.library.path=" + java_dep_lib,
"-jar", self.host_tools['signapk.jar'],
- "-a", "4096",
+ "-a", "4096", "--align-file-size",
os.path.join(get_current_dir(), TEST_X509_KEY),
os.path.join(get_current_dir(), TEST_PK8_KEY),
unsigned_apex, fn]
@@ -426,6 +427,37 @@
def test_apex_with_overridden_package_name(self):
self._run_build_test(TEST_APEX_WITH_OVERRIDDEN_PACKAGE_NAME)
+ def test_android_manifest_xml_change(self):
+ """Test payload image changes when AndroidManifest.xml changes.
+
+ This is a regression test for b194787885, the issue was that the
+ AndroidManifest.xml file change does not change payload image and
+ thus causing boot loops. This test verfies the apex_manifest.pb
+ files in payload image as well as apex are both impacted by changes
+ to AndroidManifest.xml.
+ """
+ apex_file_path = os.path.join(get_current_dir(), TEST_APEX + ".apex")
+ container_files = self._get_container_files(apex_file_path)
+ payload_dir = self._extract_payload(apex_file_path)
+ manifest_xml_v1_file = os.path.join(get_current_dir(), TEST_MANIFEST_XML_V1)
+ manifest_xml_v2_file = os.path.join(get_current_dir(), TEST_MANIFEST_XML_V2)
+ repacked_apex_v1 = self._run_apexer(
+ container_files, payload_dir,
+ ["--android_manifest", manifest_xml_v1_file])
+ repacked_apex_v2 = self._run_apexer(
+ container_files, payload_dir,
+ ["--android_manifest", manifest_xml_v2_file])
+
+ container_files_v1 = self._get_container_files(repacked_apex_v1)
+ self.assertIn("apex_payload.img", container_files_v1)
+ payload_hash_v1 = get_sha1sum(container_files_v1["apex_payload.img"])
+
+ container_files_v2 = self._get_container_files(repacked_apex_v2)
+ self.assertIn("apex_payload.img", container_files_v2)
+ payload_hash_v2 = get_sha1sum(container_files_v2["apex_payload.img"])
+
+ self.assertNotEqual(payload_hash_v1, payload_hash_v2)
+
if __name__ == '__main__':
unittest.main(verbosity=2)
diff --git a/apexer/conv_apex_manifest.py b/apexer/conv_apex_manifest.py
index dd598db..9ebf3f9 100644
--- a/apexer/conv_apex_manifest.py
+++ b/apexer/conv_apex_manifest.py
@@ -1,4 +1,4 @@
-#!/usr/bin/env python
+#!/usr/bin/env python3
#
# Copyright (C) 2019 The Android Open Source Project
#
diff --git a/apexer/runtests.sh b/apexer/runtests.sh
index e2eacc9..4d9d602 100755
--- a/apexer/runtests.sh
+++ b/apexer/runtests.sh
@@ -25,10 +25,10 @@
source ${ANDROID_BUILD_TOP}/build/envsetup.sh
m -j apexer
-export APEXER_TOOL_PATH="${ANDROID_BUILD_TOP}/out/soong/host/linux-x86/bin:${ANDROID_BUILD_TOP}/prebuilts/sdk/tools/linux/bin"
+export APEXER_TOOL_PATH="${ANDROID_BUILD_TOP}/out/host/linux-x86/bin:${ANDROID_BUILD_TOP}/prebuilts/sdk/tools/linux/bin"
PATH+=":${ANDROID_BUILD_TOP}/prebuilts/sdk/tools/linux/bin"
-for fs_type in ext4 f2fs
+for fs_type in ext4 f2fs erofs
do
input_dir=$(mktemp -d)
output_dir=$(mktemp -d)
@@ -57,7 +57,7 @@
manifest_dir=$(mktemp -d)
manifest_file=${manifest_dir}/apex_manifest.pb
echo '{"name": "com.android.example.apex", "version": 1}' > ${manifest_dir}/apex_manifest.json
-${ANDROID_BUILD_TOP}/out/soong/host/linux-x86/bin/conv_apex_manifest proto ${manifest_dir}/apex_manifest.json -o ${manifest_file}
+${ANDROID_BUILD_TOP}/out/host/linux-x86/bin/conv_apex_manifest proto ${manifest_dir}/apex_manifest.json -o ${manifest_file}
# Create the file_contexts file
file_contexts_file=$(mktemp)
diff --git a/apexer/testdata/manifest_v1.xml b/apexer/testdata/manifest_v1.xml
new file mode 100644
index 0000000..be095d4
--- /dev/null
+++ b/apexer/testdata/manifest_v1.xml
@@ -0,0 +1,10 @@
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.example.apex">
+
+ <uses-sdk
+ android:minSdkVersion="23"
+ android:targetSdkVersion="24" />
+
+ <application />
+
+</manifest>
diff --git a/apexer/testdata/manifest_v2.xml b/apexer/testdata/manifest_v2.xml
new file mode 100644
index 0000000..defae93
--- /dev/null
+++ b/apexer/testdata/manifest_v2.xml
@@ -0,0 +1,10 @@
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.example.apex">
+
+ <uses-sdk
+ android:minSdkVersion="24"
+ android:targetSdkVersion="24" />
+
+ <application />
+
+</manifest>
diff --git a/docs/howto.md b/docs/howto.md
index a704249..270d12f 100644
--- a/docs/howto.md
+++ b/docs/howto.md
@@ -520,8 +520,8 @@
```
$ java \
- -Djava.library.path=$(dirname out/soong/host/linux-x86/lib64/libconscrypt_openjdk_jni.so)\
- -jar out/soong/host/linux-x86/framework/signapk.jar \
+ -Djava.library.path=$(dirname out/host/linux-x86/lib64/libconscrypt_openjdk_jni.so)\
+ -jar out/host/linux-x86/framework/signapk.jar \
-a 4096 \
<apk_certificate_file> \
<apk_private_key_file> \
diff --git a/proto/apex_manifest.proto b/proto/apex_manifest.proto
index 22fb7d7..334b5a0 100644
--- a/proto/apex_manifest.proto
+++ b/proto/apex_manifest.proto
@@ -33,7 +33,8 @@
string preInstallHook = 3;
// Post Install Hook
- string postInstallHook = 4;
+ // This feature is not supported.
+ string postInstallHook = 4 [ deprecated = true ];
// Version Name
string versionName = 5;
@@ -56,9 +57,12 @@
// marked as "is_jni: true" from the list of "native_shared_libs".
repeated string jniLibs = 9;
- // List of libs required that are located in a shared libraries APEX.
- // Format of the content is 'library:hash'.
- // Example) libc++.so:83d8f50...
+ // List of libs required that are located in a shared libraries APEX. The
+ // Android platform only checks whether this list is non-empty, and by default
+ // the Android build system never sets this. This field can be used when
+ // producing or processing an APEX using libraries in /apex/sharedlibs (see
+ // `provideSharedApexLibs` field) to store some information about the
+ // libraries.
repeated string requireSharedApexLibs = 10;
// Whether this APEX provides libraries to be shared with other APEXs. This
@@ -78,5 +82,7 @@
// Indicates that this APEX can be updated without rebooting device.
bool supportsRebootlessUpdate = 13;
-}
+ // File hash value for apex container files except payload.
+ string apexContainerFilesHash = 14;
+}
diff --git a/tests/Android.bp b/tests/Android.bp
index 425f2bc..8a7ed1d 100644
--- a/tests/Android.bp
+++ b/tests/Android.bp
@@ -87,6 +87,7 @@
apex {
name: "apex.test",
manifest: "testdata/apex_manifest.json",
+ androidManifest: "testdata/AndroidManifest.xml",
prebuilts: ["sample_prebuilt_file"],
key: "apex.test.key",
certificate: ":apex.test.certificate",
@@ -220,8 +221,8 @@
android_test_helper_app {
name: "apex_compression_tests_app",
- manifest: "app/AndroidManifest.xml",
- srcs: ["app/src/**/*.java"],
+ manifest: "app/ApexCompressionTests_AndroidManifest.xml",
+ srcs: ["app/src/**/ApexCompressionTests.java"],
static_libs: ["androidx.test.rules", "cts-install-lib", "cts-rollback-lib", "testng"],
test_suites: ["general-tests"],
java_resources: [
@@ -229,3 +230,33 @@
":com.android.apex.compressed.v2_original",
]
}
+
+java_test_host {
+ name: "apex_apkinapex_tests",
+ srcs: ["src/**/ApkInApexTests.java"],
+ libs: ["tradefed", "truth-prebuilt"],
+ static_libs: ["cts-install-lib-host", "frameworks-base-hostutils", "testng"],
+ test_config: "apk-in-apex-tests.xml",
+ test_suites: ["general-tests"],
+ data: [
+ ":apex_apkinapex_tests_app",
+ ],
+ java_resources: [
+ ":com.android.apex.product.test",
+ ":com.android.apex.product.app.test.xml",
+ ":com.android.apex.system.test",
+ ":com.android.apex.system.app.test.xml",
+ ":com.android.apex.system_ext.test",
+ ":com.android.apex.system_ext.app.test.xml",
+ ":com.android.apex.vendor.test",
+ ":com.android.apex.vendor.app.test.xml",
+ ],
+}
+
+android_test_helper_app {
+ name: "apex_apkinapex_tests_app",
+ manifest: "app/ApkInApexTests_AndroidManifest.xml",
+ srcs: ["app/src/**/ApkInApexTests.java"],
+ static_libs: ["androidx.test.rules", "cts-install-lib", "cts-rollback-lib", "testng"],
+ test_suites: ["general-tests"],
+}
diff --git a/tests/README.md b/tests/README.md
index 1d71d30..ae42641 100644
--- a/tests/README.md
+++ b/tests/README.md
@@ -1,4 +1 @@
Integration tests for Apex OS Infrastructure.
-
-Presently, these tests require an unreleased version of the framework, and
-therefore will not likely succeed on a pure AOSP build.
diff --git a/tests/TEST_MAPPING b/tests/TEST_MAPPING
index d3177a8..dd4daa2 100644
--- a/tests/TEST_MAPPING
+++ b/tests/TEST_MAPPING
@@ -7,6 +7,9 @@
"name": "timezone_data_e2e_tests"
},
{
+ "name": "apex_apkinapex_tests"
+ },
+ {
"name": "CtsApexSharedLibrariesTestCases"
}
],
diff --git a/tests/apk-in-apex-tests.xml b/tests/apk-in-apex-tests.xml
new file mode 100644
index 0000000..baa00b6
--- /dev/null
+++ b/tests/apk-in-apex-tests.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2021 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<configuration description="Runs the apk-in-apex test cases">
+ <option name="test-suite-tag" value="apk-in-apex-tests" />
+ <option name="test-suite-tag" value="apct" />
+ <target_preparer class="com.android.tradefed.targetprep.RootTargetPreparer"/>
+ <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
+ <option name="cleanup-apks" value="true" />
+ <option name="test-file-name" value="apex_apkinapex_tests_app.apk" />
+ </target_preparer>
+ <test class="com.android.tradefed.testtype.HostTest" >
+ <option name="class"
+ value="com.android.tests.apex.host.ApkInApexTests" />
+ </test>
+</configuration>
diff --git a/tests/app/AndroidManifest.xml b/tests/app/ApexCompressionTests_AndroidManifest.xml
similarity index 93%
rename from tests/app/AndroidManifest.xml
rename to tests/app/ApexCompressionTests_AndroidManifest.xml
index 0644ba7..428ab69 100644
--- a/tests/app/AndroidManifest.xml
+++ b/tests/app/ApexCompressionTests_AndroidManifest.xml
@@ -16,7 +16,7 @@
-->
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
- package="com.android.tests.apex.app" >
+ package="com.android.tests.apex.compression.app" >
<uses-permission android:name="android.permission.QUERY_ALL_PACKAGES" />
<application>
@@ -27,7 +27,7 @@
<instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
- android:targetPackage="com.android.tests.apex.app"
+ android:targetPackage="com.android.tests.apex.compression.app"
android:label="ApexCompression Test"/>
</manifest>
diff --git a/tests/app/AndroidManifest.xml b/tests/app/ApkInApexTests_AndroidManifest.xml
similarity index 89%
copy from tests/app/AndroidManifest.xml
copy to tests/app/ApkInApexTests_AndroidManifest.xml
index 0644ba7..59e7fa9 100644
--- a/tests/app/AndroidManifest.xml
+++ b/tests/app/ApkInApexTests_AndroidManifest.xml
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
- ~ Copyright (C) 2020 The Android Open Source Project
+ ~ Copyright (C) 2021 The Android Open Source Project
~
~ Licensed under the Apache License, Version 2.0 (the "License");
~ you may not use this file except in compliance with the License.
@@ -16,7 +16,7 @@
-->
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
- package="com.android.tests.apex.app" >
+ package="com.android.tests.apex.apkinapex.app" >
<uses-permission android:name="android.permission.QUERY_ALL_PACKAGES" />
<application>
@@ -27,7 +27,7 @@
<instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
- android:targetPackage="com.android.tests.apex.app"
+ android:targetPackage="com.android.tests.apex.apkinapex.app"
android:label="ApexCompression Test"/>
</manifest>
diff --git a/tests/app/src/com/android/tests/apex/app/ApkInApexTests.java b/tests/app/src/com/android/tests/apex/app/ApkInApexTests.java
new file mode 100644
index 0000000..e9d1920
--- /dev/null
+++ b/tests/app/src/com/android/tests/apex/app/ApkInApexTests.java
@@ -0,0 +1,82 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.tests.apex.app;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.Manifest;
+import android.content.Context;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+
+import androidx.test.InstrumentationRegistry;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+@RunWith(JUnit4.class)
+public class ApkInApexTests {
+ private final Context mContext = InstrumentationRegistry.getContext();
+ private final PackageManager mPm = mContext.getPackageManager();
+
+
+ @Before
+ public void adoptShellPermissions() {
+ InstrumentationRegistry
+ .getInstrumentation()
+ .getUiAutomation()
+ .adoptShellPermissionIdentity(
+ Manifest.permission.INSTALL_PACKAGES,
+ Manifest.permission.DELETE_PACKAGES,
+ Manifest.permission.TEST_MANAGE_ROLLBACKS);
+ }
+
+ @After
+ public void dropShellPermissions() {
+ InstrumentationRegistry
+ .getInstrumentation()
+ .getUiAutomation()
+ .dropShellPermissionIdentity();
+ }
+
+ @Test
+ public void testPrivPermissionIsGranted() throws Exception {
+ PackageInfo pi = mPm.getPackageInfo("com.android.apex.product.app.test",
+ PackageManager.GET_PERMISSIONS);
+ assertThat(pi.requestedPermissions).asList()
+ .contains("android.permission.PACKAGE_USAGE_STATS");
+
+ pi = mPm.getPackageInfo("com.android.apex.system.app.test",
+ PackageManager.GET_PERMISSIONS);
+ assertThat(pi.requestedPermissions).asList()
+ .contains("android.permission.PACKAGE_USAGE_STATS");
+
+ pi = mPm.getPackageInfo("com.android.apex.system_ext.app.test",
+ PackageManager.GET_PERMISSIONS);
+ assertThat(pi.requestedPermissions).asList()
+ .contains("android.permission.PACKAGE_USAGE_STATS");
+
+ pi = mPm.getPackageInfo("com.android.apex.vendor.app.test",
+ PackageManager.GET_PERMISSIONS);
+
+ assertThat(pi.requestedPermissions).asList()
+ .contains("android.permission.START_ACTIVITIES_FROM_BACKGROUND");
+ }
+}
diff --git a/tests/native/AndroidTest.xml b/tests/native/AndroidTest.xml
index fa189d5..be00cda 100644
--- a/tests/native/AndroidTest.xml
+++ b/tests/native/AndroidTest.xml
@@ -26,4 +26,7 @@
<option name="module-name" value="CtsApexSharedLibrariesTestCases" />
<option name="runtime-hint" value="65s" />
</test>
+ <!-- Controller that will skip the module if a native bridge situation is detected -->
+ <!-- For example: module wants to run arm32 and device is x86 -->
+ <object type="module_controller" class="com.android.tradefed.testtype.suite.module.NativeBridgeModuleController" />
</configuration>
diff --git a/tests/src/com/android/tests/apex/ApexE2EBaseHostTest.java b/tests/src/com/android/tests/apex/ApexE2EBaseHostTest.java
index e0871df..6203b9b 100644
--- a/tests/src/com/android/tests/apex/ApexE2EBaseHostTest.java
+++ b/tests/src/com/android/tests/apex/ApexE2EBaseHostTest.java
@@ -16,13 +16,11 @@
package com.android.tests.apex;
-import static com.google.common.truth.Truth.assertThat;
import static com.google.common.truth.Truth.assertWithMessage;
import static org.junit.Assume.assumeTrue;
import android.cts.install.lib.host.InstallUtilsHost;
-import android.platform.test.annotations.RequiresDevice;
import com.android.tests.rollback.host.AbandonSessionsRule;
import com.android.tradefed.config.Option;
@@ -51,9 +49,6 @@
private static final Duration BOOT_COMPLETE_TIMEOUT = Duration.ofMinutes(2);
- private static final String USERSPACE_REBOOT_SUPPORTED_PROP =
- "init.userspace_reboot.is_supported";
-
// Protected so that derived tests can have access to test utils automatically
protected final InstallUtilsHost mHostUtils = new InstallUtilsHost(this);
@@ -85,21 +80,13 @@
@Test
public final void testStageActivateUninstallApexPackage() throws Exception {
- stageActivateUninstallApexPackage(false/*userspaceReboot*/);
+ stageActivateUninstallApexPackage();
}
- @Test
- @RequiresDevice // TODO(b/147726967): Remove when Userspace reboot works on cuttlefish
- public final void testStageActivateUninstallApexPackageWithUserspaceReboot() throws Exception {
- assumeTrue("Userspace reboot not supported on the device",
- getDevice().getBooleanProperty(USERSPACE_REBOOT_SUPPORTED_PROP, false));
- stageActivateUninstallApexPackage(true/*userspaceReboot*/);
- }
-
- private void stageActivateUninstallApexPackage(boolean userspaceReboot) throws Exception {
+ private void stageActivateUninstallApexPackage() throws Exception {
ApexInfo apex = installApex(mApexFileName);
- reboot(userspaceReboot); // for install to take affect
+ getDevice().reboot(); // for install to take affect
Set<ApexInfo> activatedApexes = getDevice().getActiveApexes();
assertWithMessage("Failed to activate %s", apex).that(activatedApexes).contains(apex);
@@ -125,25 +112,6 @@
return testApexInfo;
}
- protected final void reboot(boolean userspaceReboot) throws Exception {
- if (userspaceReboot) {
- assertThat(getDevice().setProperty("test.userspace_reboot.requested", "1")).isTrue();
- getDevice().rebootUserspace();
- } else {
- getDevice().reboot();
- }
- boolean success = getDevice().waitForBootComplete(BOOT_COMPLETE_TIMEOUT.toMillis());
- assertWithMessage("Device didn't boot in %s", BOOT_COMPLETE_TIMEOUT).that(success).isTrue();
- if (userspaceReboot) {
- // If userspace reboot fails and fallback to hard reboot is triggered then
- // test.userspace_reboot.requested won't be set.
- boolean res = getDevice().getBooleanProperty("test.userspace_reboot.requested", false);
- String message = "Userspace reboot failed, fallback to full reboot was triggered. ";
- message += "Boot reason: " + getDevice().getProperty("sys.boot.reason.last");
- assertWithMessage(message).that(res).isTrue();
- }
- }
-
/**
* Do some additional check, invoked by {@link #testStageActivateUninstallApexPackage()}.
*/
diff --git a/tests/src/com/android/tests/apex/ApexRollbackTests.java b/tests/src/com/android/tests/apex/ApexRollbackTests.java
index 68f82ca..b7cb78b 100644
--- a/tests/src/com/android/tests/apex/ApexRollbackTests.java
+++ b/tests/src/com/android/tests/apex/ApexRollbackTests.java
@@ -52,6 +52,10 @@
private boolean mWasAdbRoot = false;
+ private static boolean sCheckedIfCrashingProcessExists = false;
+ private static boolean sAlreadyCrashingProcessExists = false;
+ private static String sCrashingProcess;
+
@Before
public void setUp() throws Exception {
mHostUtils.uninstallShimApexIfNecessary();
@@ -60,6 +64,12 @@
if (!mWasAdbRoot) {
assumeTrue("Requires root", getDevice().enableAdbRoot());
}
+ if (!sCheckedIfCrashingProcessExists) {
+ sAlreadyCrashingProcessExists =
+ getDevice().getBooleanProperty("sys.init.updatable_crashing", false);
+ sCrashingProcess = getDevice().getProperty("sys.init.updatable_crashing_process_name");
+ sCheckedIfCrashingProcessExists = true;
+ }
}
/**
@@ -95,11 +105,9 @@
assumeTrue("Device does not support updating APEX", mHostUtils.isApexUpdateSupported());
ITestDevice device = getDevice();
// Skip this test if there is already crashing process on device
- boolean hasCrashingProcess =
- device.getBooleanProperty("sys.init.updatable_crashing", false);
- String crashingProcess = device.getProperty("sys.init.updatable_crashing_process_name");
assumeFalse(
- "Device already has a crashing process: " + crashingProcess, hasCrashingProcess);
+ "Device already has a crashing process: " + sCrashingProcess,
+ sAlreadyCrashingProcessExists);
File apexFile = mHostUtils.getTestFile("com.android.apex.cts.shim.v2.apex");
// To simulate an apex update that causes a boot loop, we install a
@@ -253,8 +261,6 @@
assertThat(activatedApexes).doesNotContain(ctsShimV1);
}
- // TODO(ioffe): check that we recover from the boot loop in case of userspace reboot.
-
/**
* Test to verify that apexd won't boot loop a device in case {@code sys.init
* .updatable_crashing} is {@code true} and there is no apex session to revert.
@@ -275,97 +281,6 @@
}
/**
- * Test to verify that if a hard reboot is triggered during userspace reboot boot
- * sequence, an apex update will not be reverted.
- */
- @Test
- public void testFailingUserspaceReboot_doesNotRevertUpdate() throws Exception {
- assumeTrue("Device does not support updating APEX", mHostUtils.isApexUpdateSupported());
- assumeTrue("Device doesn't support userspace reboot",
- getDevice().getBooleanProperty("init.userspace_reboot.is_supported", false));
- assumeTrue("Device doesn't support fs checkpointing", mHostUtils.isCheckpointSupported());
-
- File apexFile = mHostUtils.getTestFile("com.android.apex.cts.shim.v2.apex");
- // Simulate failure in userspace reboot by triggering a full reboot in the middle of the
- // boot sequence.
- assertThat(getDevice().setProperty("test.apex_revert_test_force_reboot", "1")).isTrue();
- String error = mHostUtils.installStagedPackage(apexFile);
- assertWithMessage("Failed to stage com.android.apex.cts.shim.v2.apex : %s", error).that(
- error).isNull();
- // After we reboot the device, apexd will apply the update
- getDevice().rebootUserspace();
- // Verify that hard reboot happened.
- assertThat(getDevice().getIntProperty("sys.init.userspace_reboot.last_finished",
- -1)).isEqualTo(-1);
- Set<ApexInfo> activatedApexes = getDevice().getActiveApexes();
- assertThat(activatedApexes).doesNotContain(new ApexInfo("com.android.apex.cts.shim", 1L));
- assertThat(activatedApexes).contains(new ApexInfo("com.android.apex.cts.shim", 2L));
- }
-
- /**
- * Test to verify that if a hard reboot is triggered before executing init executes {@code
- * /system/bin/vdc checkpoint markBootAttempt} of userspace reboot boot sequence, apex update
- * still will be installed.
- */
- @Test
- public void testUserspaceRebootFailedShutdownSequence_doesNotRevertUpdate() throws Exception {
- assumeTrue("Device does not support updating APEX", mHostUtils.isApexUpdateSupported());
- assumeTrue("Device doesn't support userspace reboot",
- getDevice().getBooleanProperty("init.userspace_reboot.is_supported", false));
- assumeTrue("Device doesn't support fs checkpointing", mHostUtils.isCheckpointSupported());
-
- File apexFile = mHostUtils.getTestFile("com.android.apex.cts.shim.v2.apex");
- // Simulate failure in userspace reboot by triggering a full reboot in the middle of the
- // boot sequence.
- assertThat(getDevice().setProperty("test.apex_userspace_reboot_simulate_shutdown_failed",
- "1")).isTrue();
- String error = mHostUtils.installStagedPackage(apexFile);
- assertWithMessage("Failed to stage com.android.apex.cts.shim.v2.apex : %s", error).that(
- error).isNull();
- // After the userspace reboot started, we simulate it's failure by rebooting device during
- // on userspace-reboot-requested action. Since boot attempt hasn't been marked yet, next
- // boot will apply the update.
- assertThat(getDevice().getIntProperty("test.apex_userspace_reboot_simulate_shutdown_failed",
- 0)).isEqualTo(1);
- getDevice().rebootUserspace();
- // Verify that hard reboot happened.
- assertThat(getDevice().getIntProperty("sys.init.userspace_reboot.last_finished",
- -1)).isEqualTo(-1);
- Set<ApexInfo> activatedApexes = getDevice().getActiveApexes();
- assertThat(activatedApexes).contains(new ApexInfo("com.android.apex.cts.shim", 2L));
- }
-
- /**
- * Test to verify that if a hard reboot is triggered around the time of
- * executing {@code /system/bin/vdc checkpoint markBootAttempt} of userspace reboot boot
- * sequence, apex update will still be installed.
- */
- @Test
- public void testUserspaceRebootFailedRemount_revertsUpdate() throws Exception {
- assumeTrue("Device does not support updating APEX", mHostUtils.isApexUpdateSupported());
- assumeTrue("Device doesn't support userspace reboot",
- getDevice().getBooleanProperty("init.userspace_reboot.is_supported", false));
- assumeTrue("Device doesn't support fs checkpointing", mHostUtils.isCheckpointSupported());
-
- File apexFile = mHostUtils.getTestFile("com.android.apex.cts.shim.v2.apex");
- // Simulate failure in userspace reboot by triggering a full reboot in the middle of the
- // boot sequence.
- assertThat(getDevice().setProperty("test.apex_userspace_reboot_simulate_remount_failed",
- "1")).isTrue();
- String error = mHostUtils.installStagedPackage(apexFile);
- assertWithMessage("Failed to stage com.android.apex.cts.shim.v2.apex : %s", error).that(
- error).isNull();
- // After we reboot the device, apexd will apply the update
- getDevice().rebootUserspace();
- // Verify that hard reboot happened.
- assertThat(getDevice().getIntProperty("sys.init.userspace_reboot.last_finished",
- -1)).isEqualTo(-1);
- Set<ApexInfo> activatedApexes = getDevice().getActiveApexes();
- assertThat(activatedApexes).doesNotContain(new ApexInfo("com.android.apex.cts.shim", 1L));
- assertThat(activatedApexes).contains(new ApexInfo("com.android.apex.cts.shim", 2L));
- }
-
- /**
* Test to verify that boot cleanup logic in apexd is triggered when there is a crash looping
* process, but there is nothing to revert.
*/
@@ -406,13 +321,9 @@
assumeTrue("Fs checkpointing is enabled", mHostUtils.isCheckpointSupported());
ITestDevice device = getDevice();
- // Skip this test if there is already crashing process on device
- final boolean hasCrashingProcess =
- device.getBooleanProperty("sys.init.updatable_crashing", false);
- final String crashingProcess =
- device.getProperty("sys.init.updatable_crashing_process_name");
assumeFalse(
- "Device already has a crashing process: " + crashingProcess, hasCrashingProcess);
+ "Device already has a crashing process: " + sCrashingProcess,
+ sAlreadyCrashingProcessExists);
final File apexFile = mHostUtils.getTestFile("com.android.apex.cts.shim.v2.apex");
// To simulate an apex update that causes a boot loop, we install a
diff --git a/tests/src/com/android/tests/apex/SharedLibsApexTest.java b/tests/src/com/android/tests/apex/SharedLibsApexTest.java
index fdeac2c..df49f62 100644
--- a/tests/src/com/android/tests/apex/SharedLibsApexTest.java
+++ b/tests/src/com/android/tests/apex/SharedLibsApexTest.java
@@ -33,6 +33,7 @@
import org.junit.rules.TemporaryFolder;
import org.junit.runner.RunWith;
+import java.time.Duration;
@RunWith(DeviceJUnit4ClassRunner.class)
public class SharedLibsApexTest extends BaseHostJUnit4Test {
@@ -511,12 +512,13 @@
assertThat(getDevice().doesFileExist("/data/apex/active/"
+ getInstalledApexFileName(ApexName.SHAREDLIBS, ApexVersion.ONE))).isTrue();
mPreparer.reboot();
- assertThat(getDevice().doesFileExist("/data/apex/active/"
- + getInstalledApexFileName(ApexName.BAR, ApexVersion.ONE))).isFalse();
- assertThat(getDevice().doesFileExist("/data/apex/active/"
- + getInstalledApexFileName(ApexName.FOO, ApexVersion.ONE))).isFalse();
- assertThat(getDevice().doesFileExist("/data/apex/active/"
- + getInstalledApexFileName(ApexName.SHAREDLIBS, ApexVersion.ONE))).isFalse();
+ mHostUtils.waitForFileDeleted("/data/apex/active/"
+ + getInstalledApexFileName(ApexName.BAR, ApexVersion.ONE), Duration.ofMinutes(3));
+ mHostUtils.waitForFileDeleted("/data/apex/active/"
+ + getInstalledApexFileName(ApexName.FOO, ApexVersion.ONE), Duration.ofMinutes(3));
+ mHostUtils.waitForFileDeleted("/data/apex/active/"
+ + getInstalledApexFileName(ApexName.SHAREDLIBS, ApexVersion.ONE),
+ Duration.ofMinutes(3));
getDevice().disableAdbRoot();
runAsResult = getDevice().executeShellCommand(
diff --git a/tests/src/com/android/tests/apex/host/ApexCompressionTests.java b/tests/src/com/android/tests/apex/host/ApexCompressionTests.java
index a09ca60..e37bc0d 100644
--- a/tests/src/com/android/tests/apex/host/ApexCompressionTests.java
+++ b/tests/src/com/android/tests/apex/host/ApexCompressionTests.java
@@ -91,7 +91,7 @@
* For example, <code>runPhase("testApkOnlyEnableRollback");</code>
*/
private void runPhase(String phase) throws Exception {
- assertTrue(runDeviceTests("com.android.tests.apex.app",
+ assertTrue(runDeviceTests("com.android.tests.apex.compression.app",
"com.android.tests.apex.app.ApexCompressionTests",
phase));
}
@@ -283,10 +283,10 @@
runPhase("testUnusedDecompressedApexIsCleanedUp_HigherVersion");
getDevice().reboot();
- // Verify that DECOMPRESSED_DIR_PATH does not contain the decompressed APEX
- files = getFilesInDir(DECOMPRESSED_DIR_PATH);
- assertThat(files).doesNotContain(
- COMPRESSED_APEX_PACKAGE_NAME + "@1" + DECOMPRESSED_APEX_SUFFIX);
+ // Verify that the decompressed APEX has been cleaned up
+ String filePath = Paths.get(DECOMPRESSED_DIR_PATH,
+ COMPRESSED_APEX_PACKAGE_NAME + "@1" + DECOMPRESSED_APEX_SUFFIX).toString();
+ mHostUtils.waitForFileDeleted(filePath, Duration.ofSeconds(15));
}
@Test
@@ -302,10 +302,10 @@
runPhase("testUnusedDecompressedApexIsCleanedUp_SameVersion");
getDevice().reboot();
- // Verify that DECOMPRESSED_DIR_PATH does not contain the decompressed APEX
- files = getFilesInDir(DECOMPRESSED_DIR_PATH);
- assertThat(files).doesNotContain(
- COMPRESSED_APEX_PACKAGE_NAME + "@1" + DECOMPRESSED_APEX_SUFFIX);
+ // Verify that the decompressed APEX has been cleaned up
+ String filePath = Paths.get(DECOMPRESSED_DIR_PATH,
+ COMPRESSED_APEX_PACKAGE_NAME + "@1" + DECOMPRESSED_APEX_SUFFIX).toString();
+ mHostUtils.waitForFileDeleted(filePath, Duration.ofSeconds(15));
}
@Test
@@ -378,8 +378,9 @@
"Can't find " + COMPRESSED_APEX_PACKAGE_NAME));
assertThat(activeApex.sourceDir).startsWith("/system");
// Ensure previous decompressed APEX has been cleaned up
- assertThat(getFilesInDir(DECOMPRESSED_DIR_PATH))
- .doesNotContain(COMPRESSED_APEX_PACKAGE_NAME + "@1" + DECOMPRESSED_APEX_SUFFIX);
+ String filePath = Paths.get(DECOMPRESSED_DIR_PATH,
+ COMPRESSED_APEX_PACKAGE_NAME + "@1" + DECOMPRESSED_APEX_SUFFIX).toString();
+ mHostUtils.waitForFileDeleted(filePath, Duration.ofSeconds(15));
}
@Test
@@ -431,8 +432,9 @@
"Can't find " + COMPRESSED_APEX_PACKAGE_NAME));
assertThat(activeApex.sourceDir).startsWith("/system");
// Ensure orphaned decompressed APEX has been cleaned up
- assertThat(getFilesInDir(APEX_ACTIVE_DIR))
- .doesNotContain(COMPRESSED_APEX_PACKAGE_NAME + "@1" + DECOMPRESSED_APEX_SUFFIX);
+ String filePath = Paths.get(DECOMPRESSED_DIR_PATH,
+ COMPRESSED_APEX_PACKAGE_NAME + "@1" + DECOMPRESSED_APEX_SUFFIX).toString();
+ mHostUtils.waitForFileDeleted(filePath, Duration.ofSeconds(15));
}
}
diff --git a/tests/src/com/android/tests/apex/host/ApkInApexTests.java b/tests/src/com/android/tests/apex/host/ApkInApexTests.java
new file mode 100644
index 0000000..386a9da
--- /dev/null
+++ b/tests/src/com/android/tests/apex/host/ApkInApexTests.java
@@ -0,0 +1,99 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.tests.apex.host;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.junit.Assume.assumeTrue;
+
+import android.cts.install.lib.host.InstallUtilsHost;
+
+import com.android.internal.util.test.SystemPreparer;
+import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
+import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.RuleChain;
+import org.junit.rules.TemporaryFolder;
+import org.junit.runner.RunWith;
+
+@RunWith(DeviceJUnit4ClassRunner.class)
+public class ApkInApexTests extends BaseHostJUnit4Test {
+ private static final String PRODUCT_APEX = "com.android.apex.product.test.apex";
+ private static final String SYSTEM_APEX = "com.android.apex.system.test.apex";
+ private static final String SYSTEM_EXT_APEX = "com.android.apex.system_ext.test.apex";
+ private static final String VENDOR_APEX = "com.android.apex.vendor.test.apex";
+
+ private static final String PRODUCT_PRIVAPP_XML = "com.android.apex.product.app.test.xml";
+ private static final String SYSTEM_PRIVAPP_XML = "com.android.apex.system.app.test.xml";
+ private static final String SYSTEM_EXT_PRIVAPP_XML = "com.android.apex.system_ext.app.test.xml";
+ private static final String VENDOR_PRIVAPP_XML = "com.android.apex.vendor.app.test.xml";
+
+ private final InstallUtilsHost mHostUtils = new InstallUtilsHost(this);
+ private final TemporaryFolder mTemporaryFolder = new TemporaryFolder();
+ private final SystemPreparer mPreparer = new SystemPreparer(mTemporaryFolder,
+ this::getDevice);
+
+ @Rule
+ public final RuleChain ruleChain = RuleChain.outerRule(mTemporaryFolder).around(mPreparer);
+
+ @Before
+ public void setUp() throws Exception {
+ assumeTrue("Updating APEX is not supported", mHostUtils.isApexUpdateSupported());
+ mPreparer.pushResourceFile(PRODUCT_APEX,
+ "/product/apex/" + PRODUCT_APEX);
+ mPreparer.pushResourceFile(PRODUCT_PRIVAPP_XML,
+ "/product/etc/permissions/" + PRODUCT_PRIVAPP_XML);
+ mPreparer.pushResourceFile(SYSTEM_APEX,
+ "/system/apex/" + SYSTEM_APEX);
+ mPreparer.pushResourceFile(SYSTEM_PRIVAPP_XML,
+ "/system/etc/permissions/" + SYSTEM_PRIVAPP_XML);
+ mPreparer.pushResourceFile(SYSTEM_EXT_APEX,
+ "/system_ext/apex/" + SYSTEM_EXT_APEX);
+ mPreparer.pushResourceFile(SYSTEM_EXT_PRIVAPP_XML,
+ "/system_ext/etc/permissions/" + SYSTEM_EXT_PRIVAPP_XML);
+ mPreparer.pushResourceFile(VENDOR_APEX,
+ "/vendor/apex/" + VENDOR_APEX);
+ mPreparer.pushResourceFile(VENDOR_PRIVAPP_XML,
+ "/vendor/etc/permissions/" + VENDOR_PRIVAPP_XML);
+ mPreparer.reboot();
+ }
+
+ @After
+ public void tearDown() throws Exception {
+ getDevice().disableAdbRoot();
+ }
+
+ /**
+ * Runs the given phase of a test by calling into the device.
+ * Throws an exception if the test phase fails.
+ * <p>
+ * For example, <code>runPhase("testApkOnlyEnableRollback");</code>
+ */
+ private void runPhase(String phase) throws Exception {
+ assertThat(runDeviceTests("com.android.tests.apex.apkinapex.app",
+ "com.android.tests.apex.app.ApkInApexTests",
+ phase)).isTrue();
+ }
+ @Test
+ public void testPrivPermissionIsGranted() throws Exception {
+ runPhase("testPrivPermissionIsGranted");
+ }
+}
diff --git a/tests/testdata/AndroidManifest.xml b/tests/testdata/AndroidManifest.xml
index 04bc559..e7c5c02 100644
--- a/tests/testdata/AndroidManifest.xml
+++ b/tests/testdata/AndroidManifest.xml
@@ -1,7 +1,26 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
- package="com.android.apex.test">
- <!-- APEX does not have classes.dex -->
- <application android:hasCode="false" />
-</manifest>
+ package="com.android.apex.test">
+ <!-- APEX does not have classes.dex -->
+ <application android:hasCode="false">
+ <apex-system-service
+ android:name="com.android.apex.test.ApexSystemService"
+ android:path="/apex/com.android.apex.test/javalib/fake.jar"
+ android:minSdkVersion="30"/>
+ <!-- Always inactive system service, since maxSdkVersion is low -->
+ <apex-system-service
+ android:name="com.android.apex.test.OldApexSystemService"
+ android:path="/apex/com.android.apex.test/javalib/fake.jar"
+ android:minSdkVersion="1"
+ android:maxSdkVersion="1"
+ />
+
+ <!-- Always inactive system service, since minSdkVersion is high -->
+ <apex-system-service
+ android:name="com.android.apex.test.NewApexSystemService"
+ android:path="/apex/com.android.apex.test/javalib/fake.jar"
+ android:minSdkVersion="999999"
+ />
+ </application>
+</manifest>
diff --git a/tests/testdata/apkinapex/com.android.apex.product.test/Android.bp b/tests/testdata/apkinapex/com.android.apex.product.test/Android.bp
new file mode 100644
index 0000000..df7fc12
--- /dev/null
+++ b/tests/testdata/apkinapex/com.android.apex.product.test/Android.bp
@@ -0,0 +1,52 @@
+// Copyright (C) 2021 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package {
+ default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+apex_key {
+ name: "com.android.apex.product.test.key",
+ public_key: "com.android.apex.product.test.avbpubkey",
+ private_key: "com.android.apex.product.test.pem",
+}
+
+android_app_certificate {
+ name: "com.android.apex.product.test.certificate",
+ certificate: "com.android.apex.product.test",
+}
+
+apex {
+ name: "com.android.apex.product.test",
+ manifest: "manifest.json",
+ file_contexts: ":apex.test-file_contexts", // Default, please edit, see go/android-apex-howto
+ key: "com.android.apex.product.test.key",
+ updatable: false,
+ apps: ["com.android.apex.product.app.test"],
+}
+
+android_app {
+ name: "com.android.apex.product.app.test",
+ manifest: "App_AndroidManifest.xml",
+ sdk_version: "31",
+ privileged: true,
+ apex_available: [
+ "com.android.apex.product.test",
+ ]
+}
+
+filegroup {
+ name: "com.android.apex.product.app.test.xml",
+ srcs: ["com.android.apex.product.app.test.xml"],
+}
diff --git a/tests/testdata/apkinapex/com.android.apex.product.test/App_AndroidManifest.xml b/tests/testdata/apkinapex/com.android.apex.product.test/App_AndroidManifest.xml
new file mode 100644
index 0000000..d9aae58
--- /dev/null
+++ b/tests/testdata/apkinapex/com.android.apex.product.test/App_AndroidManifest.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ * Copyright (C) 2021 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.
+ -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.apex.product.app.test">
+ <uses-permission android:name="android.permission.PACKAGE_USAGE_STATS" />
+</manifest>
diff --git a/tests/testdata/apkinapex/com.android.apex.product.test/com.android.apex.product.app.test.xml b/tests/testdata/apkinapex/com.android.apex.product.test/com.android.apex.product.app.test.xml
new file mode 100644
index 0000000..839d058
--- /dev/null
+++ b/tests/testdata/apkinapex/com.android.apex.product.test/com.android.apex.product.app.test.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+ <!--
+ ~ Copyright (C) 2021 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+<permissions>
+ <privapp-permissions package="com.android.apex.product.app.test">
+ <permission name="android.permission.PACKAGE_USAGE_STATS" />
+ </privapp-permissions>
+</permissions>
\ No newline at end of file
diff --git a/tests/testdata/apkinapex/com.android.apex.product.test/com.android.apex.product.test.avbpubkey b/tests/testdata/apkinapex/com.android.apex.product.test/com.android.apex.product.test.avbpubkey
new file mode 100644
index 0000000..bef1df6
--- /dev/null
+++ b/tests/testdata/apkinapex/com.android.apex.product.test/com.android.apex.product.test.avbpubkey
Binary files differ
diff --git a/tests/testdata/apkinapex/com.android.apex.product.test/com.android.apex.product.test.pem b/tests/testdata/apkinapex/com.android.apex.product.test/com.android.apex.product.test.pem
new file mode 100644
index 0000000..e3d0e23
--- /dev/null
+++ b/tests/testdata/apkinapex/com.android.apex.product.test/com.android.apex.product.test.pem
@@ -0,0 +1,51 @@
+-----BEGIN RSA PRIVATE KEY-----
+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
+-----END RSA PRIVATE KEY-----
diff --git a/tests/testdata/apkinapex/com.android.apex.product.test/com.android.apex.product.test.pk8 b/tests/testdata/apkinapex/com.android.apex.product.test/com.android.apex.product.test.pk8
new file mode 100644
index 0000000..a16e635
--- /dev/null
+++ b/tests/testdata/apkinapex/com.android.apex.product.test/com.android.apex.product.test.pk8
Binary files differ
diff --git a/tests/testdata/apkinapex/com.android.apex.product.test/com.android.apex.product.test.x509.pem b/tests/testdata/apkinapex/com.android.apex.product.test/com.android.apex.product.test.x509.pem
new file mode 100644
index 0000000..44a6506
--- /dev/null
+++ b/tests/testdata/apkinapex/com.android.apex.product.test/com.android.apex.product.test.x509.pem
@@ -0,0 +1,34 @@
+-----BEGIN CERTIFICATE-----
+MIIF3zCCA8cCFFS0AutXdKo2HIVPZD/RDZDd2LqMMA0GCSqGSIb3DQEBCwUAMIGq
+MQswCQYDVQQGEwJVUzETMBEGA1UECAwKQ2FsaWZvcm5pYTEWMBQGA1UEBwwNTW91
+bnRhaW4gVmlldzEQMA4GA1UECgwHQW5kcm9pZDEQMA4GA1UECwwHQW5kcm9pZDEi
+MCAGCSqGSIb3DQEJARYTYW5kcm9pZEBhbmRyb2lkLmNvbTEmMCQGA1UEAwwdY29t
+LmFuZHJvaWQuYXBleC5wcm9kdWN0LnRlc3QwIBcNMjExMTA0MTc0NjI3WhgPNDc1
+OTEwMDExNzQ2MjdaMIGqMQswCQYDVQQGEwJVUzETMBEGA1UECAwKQ2FsaWZvcm5p
+YTEWMBQGA1UEBwwNTW91bnRhaW4gVmlldzEQMA4GA1UECgwHQW5kcm9pZDEQMA4G
+A1UECwwHQW5kcm9pZDEiMCAGCSqGSIb3DQEJARYTYW5kcm9pZEBhbmRyb2lkLmNv
+bTEmMCQGA1UEAwwdY29tLmFuZHJvaWQuYXBleC5wcm9kdWN0LnRlc3QwggIiMA0G
+CSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQC6Qyaym34nAtGBoH5JwJAxxieNK83Q
+jG4wf56qMX1Kq+NFc+rYqsMebq3Tijc/3CSdE5f/4FE3cXwBJzhUKKDuBjKOGgyY
+YMYrgbdB23LyWlgmuRfUX68MAOAQ9GJrzBhVWKYRn66j6HJ9mEkHxNq0QO1XcSkB
+3K0bNS7rp4rfzOZnGtnGSoDdAjwyaN4NhBxkWTvZ7+LiHQixkL6yxPTtQEEbGh06
+Ni6X9upoByhXVYlaGDhfYhMksSaEVgCQQrXuCbnu9SRwCYfGL6HHeOO2F2XPyIi4
+K4ND490/mlNpk1p225IARQA/PAQcZFgb+++X75/fd/KzI9lcRKR3Gj1DS4giHp5Z
+3PUgB7q/nFJl4UcqkbwkasDC9vgVp7vgjIYJB9jrw4hAfsubsjKkOYbl4WrPGBTa
+Me9K6Bqk5mWUFHKyqt8ztLiJhq62CzE3dK5zo+koixYMHRJoup+7fDNi8O8j5G7D
+JDmAnPMhru/Ey9UQOxSQT+lvl4PRB11qZtmY1BT695IyKs+LxukPPGqOh3qBYt2G
+B2QRwXJVbqrfyMG32t2KY42j053hI4sY5kv+Mmt4tK2xrrWJ6VUJuQKWXoF8+1px
+O2xVXkIjsRpx9+rx5N5Gh29yvFCLsYFikDYFolmVSA+rfQxgQ0I4qUcZWWhYXu9b
+gKoY2pOpwdjDXQIDAQABMA0GCSqGSIb3DQEBCwUAA4ICAQBTdD9Aa179TBSBHJQK
+STQ1m8mX7RTdI854AkcdLrFR+NNukmaV5aep2dDRB6zj2uU8bjRvNLe/KgQROj24
+S4WSHcX2AyjeBHzH28vfbqZJee/a01VVv9id4h7sbN+P4k2kd8K0pMLuDLryu64q
+7qEej2E5eiROUVrgeMUrzmMV3ef4CJfi5Myw1+HEekR4LfaHAbUZJ7A9rmX0N+LX
+ysNj2wSL5Yj/3sn8XvOJU2K4v/1M6JphzJLDT6N+uOmmuzSTk9uh6Aq4WWUjShp9
+6k2NfNffbE+Ds/AgRmkxFMFARCLgdvu1PfjCua6sGnbH5xJvvkXpvipb8vnbmWjj
+EyJi7oPXZFtDyB5wG92q7YA/PCyXRuPG4QXkKSHqCN4An0t+/0VnusUGZyXE6SQc
+6EkLdn3Sm++sZS/Vix7Xn7okHwtN1UR2MOIRsYPNqofeQ4Qnt96vzm5pdHCHa9eu
+5lys3wplCmyyATiWcQqRKJ4jLDaYbErfIb5mK8TQEP5E/Hw/hZvtqk4+fKTt8me3
+SUnLN1LXGhio0m3Os6vv22KL5BIa2kdVUdZpOJh85jUPnJcxaGCt/owNN6gkbfbp
+VWal+dzAw40U3SWqsMETgNUtCj+rHRdoaidnV3dI9dGXWWTK9q2HE6W5aYZxtHrz
+Xrfar3a04h4eewoDgbxeCcz5Bg==
+-----END CERTIFICATE-----
diff --git a/tests/testdata/apkinapex/com.android.apex.product.test/manifest.json b/tests/testdata/apkinapex/com.android.apex.product.test/manifest.json
new file mode 100644
index 0000000..59a9184
--- /dev/null
+++ b/tests/testdata/apkinapex/com.android.apex.product.test/manifest.json
@@ -0,0 +1,4 @@
+{
+ "name": "com.android.apex.product.test",
+ "version": 1
+}
diff --git a/tests/testdata/apkinapex/com.android.apex.system.test/Android.bp b/tests/testdata/apkinapex/com.android.apex.system.test/Android.bp
new file mode 100644
index 0000000..7282270
--- /dev/null
+++ b/tests/testdata/apkinapex/com.android.apex.system.test/Android.bp
@@ -0,0 +1,52 @@
+// Copyright (C) 2021 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package {
+ default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+apex_key {
+ name: "com.android.apex.system.test.key",
+ public_key: "com.android.apex.system.test.avbpubkey",
+ private_key: "com.android.apex.system.test.pem",
+}
+
+android_app_certificate {
+ name: "com.android.apex.system.test.certificate",
+ certificate: "com.android.apex.system.test",
+}
+
+apex {
+ name: "com.android.apex.system.test",
+ manifest: "manifest.json",
+ file_contexts: ":apex.test-file_contexts", // Default, please edit, see go/android-apex-howto
+ key: "com.android.apex.system.test.key",
+ updatable: false,
+ apps: ["com.android.apex.system.app.test"],
+}
+
+android_app {
+ name: "com.android.apex.system.app.test",
+ manifest: "App_AndroidManifest.xml",
+ sdk_version: "31",
+ privileged: true,
+ apex_available: [
+ "com.android.apex.system.test",
+ ]
+}
+
+filegroup {
+ name: "com.android.apex.system.app.test.xml",
+ srcs: ["com.android.apex.system.app.test.xml"],
+}
diff --git a/tests/testdata/apkinapex/com.android.apex.system.test/App_AndroidManifest.xml b/tests/testdata/apkinapex/com.android.apex.system.test/App_AndroidManifest.xml
new file mode 100644
index 0000000..b990458
--- /dev/null
+++ b/tests/testdata/apkinapex/com.android.apex.system.test/App_AndroidManifest.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ * Copyright (C) 2021 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.
+ -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.apex.system.app.test">
+ <uses-permission android:name="android.permission.PACKAGE_USAGE_STATS" />
+</manifest>
diff --git a/tests/testdata/apkinapex/com.android.apex.system.test/com.android.apex.system.app.test.xml b/tests/testdata/apkinapex/com.android.apex.system.test/com.android.apex.system.app.test.xml
new file mode 100644
index 0000000..08f4843
--- /dev/null
+++ b/tests/testdata/apkinapex/com.android.apex.system.test/com.android.apex.system.app.test.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+ <!--
+ ~ Copyright (C) 2021 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+<permissions>
+ <privapp-permissions package="com.android.apex.system.app.test">
+ <permission name="android.permission.PACKAGE_USAGE_STATS" />
+ </privapp-permissions>
+</permissions>
\ No newline at end of file
diff --git a/tests/testdata/apkinapex/com.android.apex.system.test/com.android.apex.system.test.avbpubkey b/tests/testdata/apkinapex/com.android.apex.system.test/com.android.apex.system.test.avbpubkey
new file mode 100644
index 0000000..ef0438e
--- /dev/null
+++ b/tests/testdata/apkinapex/com.android.apex.system.test/com.android.apex.system.test.avbpubkey
Binary files differ
diff --git a/tests/testdata/apkinapex/com.android.apex.system.test/com.android.apex.system.test.pem b/tests/testdata/apkinapex/com.android.apex.system.test/com.android.apex.system.test.pem
new file mode 100644
index 0000000..2a70c45
--- /dev/null
+++ b/tests/testdata/apkinapex/com.android.apex.system.test/com.android.apex.system.test.pem
@@ -0,0 +1,51 @@
+-----BEGIN RSA PRIVATE KEY-----
+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
+-----END RSA PRIVATE KEY-----
diff --git a/tests/testdata/apkinapex/com.android.apex.system.test/com.android.apex.system.test.pk8 b/tests/testdata/apkinapex/com.android.apex.system.test/com.android.apex.system.test.pk8
new file mode 100644
index 0000000..025f2cd
--- /dev/null
+++ b/tests/testdata/apkinapex/com.android.apex.system.test/com.android.apex.system.test.pk8
Binary files differ
diff --git a/tests/testdata/apkinapex/com.android.apex.system.test/com.android.apex.system.test.x509.pem b/tests/testdata/apkinapex/com.android.apex.system.test/com.android.apex.system.test.x509.pem
new file mode 100644
index 0000000..3c7bb6f
--- /dev/null
+++ b/tests/testdata/apkinapex/com.android.apex.system.test/com.android.apex.system.test.x509.pem
@@ -0,0 +1,34 @@
+-----BEGIN CERTIFICATE-----
+MIIF3TCCA8UCFDZWDufIf2NnqQxJ6twoAcosGjeiMA0GCSqGSIb3DQEBCwUAMIGp
+MQswCQYDVQQGEwJVUzETMBEGA1UECAwKQ2FsaWZvcm5pYTEWMBQGA1UEBwwNTW91
+bnRhaW4gVmlldzEQMA4GA1UECgwHQW5kcm9pZDEQMA4GA1UECwwHQW5kcm9pZDEi
+MCAGCSqGSIb3DQEJARYTYW5kcm9pZEBhbmRyb2lkLmNvbTElMCMGA1UEAwwcY29t
+LmFuZHJvaWQuYXBleC5zeXN0ZW0udGVzdDAgFw0yMTExMDQxNzQ2MDZaGA80NzU5
+MTAwMTE3NDYwNlowgakxCzAJBgNVBAYTAlVTMRMwEQYDVQQIDApDYWxpZm9ybmlh
+MRYwFAYDVQQHDA1Nb3VudGFpbiBWaWV3MRAwDgYDVQQKDAdBbmRyb2lkMRAwDgYD
+VQQLDAdBbmRyb2lkMSIwIAYJKoZIhvcNAQkBFhNhbmRyb2lkQGFuZHJvaWQuY29t
+MSUwIwYDVQQDDBxjb20uYW5kcm9pZC5hcGV4LnN5c3RlbS50ZXN0MIICIjANBgkq
+hkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAugO3YGqR0dJFmL2H7QDOiSHYPGOvMZD0
+udb0YM0wEcvq8MKxakhJuprrQPITIrA/19EbsPnVW2dVgg5Ss0O1pXMpsGL7CppS
+pLJOkNWHnPtAe8BkUGLJmUrqyYLygyM2iJ8J32Q6oyFV/Wxfk5tNTLL9bl3SrM/V
+0nrLkEmt0yvTh8kxdc/41oYPbUSaDbfk/zkiK1hkJaraqPaCir+Pt/dogwjyFyGy
+8+DkTDZFaG749ONASGJUfYptkHyVPGvbCY0MkVvUB+b7Z2n55LHJoQL+Xypr4364
+ShOHGvSsHcfM3UkaH8lRh59zd0xRcGARA71vclNBBrzNTINsd8jvh/UGdt1Ncedr
+gFC+mDnkdphMF3LeAtim1hnZ96h1pDNvTzLbygoSoquBrHRbQLnhjrzCRII3xLxQ
+GSdPLXUOA/8S7hf154H9RIwdiRyZ9hTYcjVPPXeKkqhR0Pb0cnlAncgd74dHKyIj
+QGlGTiih3jP9aYqQBCZQoNhiuQJaG3n2XgddPr5FnmFsssmSNGCbIRlhkbh9dCWx
+gQmA8fbuzzDTp2Wlcpe4dcEhIpHK96bBn439n/1LK8Ffn8K77+MDqz0Yic/Nuolg
+EyUu8kephLXoCJI85IhclOr9hrCQnlA3jMQmF0XXxh3pKXejbI3Y1sMHjmOBiyYt
+rIdmfWDiO5ECAwEAATANBgkqhkiG9w0BAQsFAAOCAgEAnYLicSFfR+sa+mXNzgn3
+jdUtilK5L97f1OcvSzC0e0Zc87iZGP1A/6CxjVousa9s+z4Emi80bTUmSDH2K7pr
+BRyUU4U9kUCEtnGxi9OUDdX9hYloWkJx4Ub83cjzCRZYKNo4mKSBs4Q/MuiJ+/Op
+LbHCDYu6H49lHzfDe+ELhiBXrWSbmDVK8NFizN/eJhAyX/DCqkmdZ+AjUqSfZfUs
+WcozlWBF8hIQ1dKUQOQSCUzqbHXZwC8y03DuLtWIhZ6SXOdD1uSvvdaWP6IjzuzF
+WRiFlO3Rrxf80RKnQWe2JE1Nautb5RCNdfNUJTowFBvdLFc+LQeHOTZ3SILn64wu
+zPWVLUx4n/oNPNJAfLxTtXH1nzhVkgYIdrvm/2lOWfLCvVDLncGULm8+w3MWY9oX
+Fq8CMhKx9Uj+vleQX5yV4TenW92/51ATXCSN3w8Wk1OBH+pzWzFFjRIyYOPa1oqv
+Xs1/HJSK3FZcjKY+W5qSSXU7RptAZh8/1HA9oP12nYsHm3Aa9G1VDrFw34rMMz13
+QN17B72y0hoPs1Fm9vI0qDy+lAgnq6HNIrgrEUDtlXYaDZcCaPgBTFXDoBj4/L7b
+lg/O8MyJ9Tx4b94qfhesgYOwgO/95hY2L3XBI77MbLvJA5VQ6lHfuTsKoPDf4z2/
+XFK/sUlIc3Nig/PVVEo4mxE=
+-----END CERTIFICATE-----
diff --git a/tests/testdata/apkinapex/com.android.apex.system.test/manifest.json b/tests/testdata/apkinapex/com.android.apex.system.test/manifest.json
new file mode 100644
index 0000000..35c429f
--- /dev/null
+++ b/tests/testdata/apkinapex/com.android.apex.system.test/manifest.json
@@ -0,0 +1,4 @@
+{
+ "name": "com.android.apex.system.test",
+ "version": 1
+}
diff --git a/tests/testdata/apkinapex/com.android.apex.system_ext.test/Android.bp b/tests/testdata/apkinapex/com.android.apex.system_ext.test/Android.bp
new file mode 100644
index 0000000..ebab455
--- /dev/null
+++ b/tests/testdata/apkinapex/com.android.apex.system_ext.test/Android.bp
@@ -0,0 +1,52 @@
+// Copyright (C) 2021 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package {
+ default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+apex_key {
+ name: "com.android.apex.system_ext.test.key",
+ public_key: "com.android.apex.system_ext.test.avbpubkey",
+ private_key: "com.android.apex.system_ext.test.pem",
+}
+
+android_app_certificate {
+ name: "com.android.apex.system_ext.test.certificate",
+ certificate: "com.android.apex.system_ext.test",
+}
+
+apex {
+ name: "com.android.apex.system_ext.test",
+ manifest: "manifest.json",
+ file_contexts: ":apex.test-file_contexts", // Default, please edit, see go/android-apex-howto
+ key: "com.android.apex.system_ext.test.key",
+ updatable: false,
+ apps: ["com.android.apex.system_ext.app.test"],
+}
+
+android_app {
+ name: "com.android.apex.system_ext.app.test",
+ manifest: "App_AndroidManifest.xml",
+ sdk_version: "31",
+ privileged: true,
+ apex_available: [
+ "com.android.apex.system_ext.test",
+ ]
+}
+
+filegroup {
+ name: "com.android.apex.system_ext.app.test.xml",
+ srcs: ["com.android.apex.system_ext.app.test.xml"],
+}
diff --git a/tests/testdata/apkinapex/com.android.apex.system_ext.test/App_AndroidManifest.xml b/tests/testdata/apkinapex/com.android.apex.system_ext.test/App_AndroidManifest.xml
new file mode 100644
index 0000000..08e6e8d
--- /dev/null
+++ b/tests/testdata/apkinapex/com.android.apex.system_ext.test/App_AndroidManifest.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ * Copyright (C) 2021 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.
+ -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.apex.system_ext.app.test">
+ <uses-permission android:name="android.permission.PACKAGE_USAGE_STATS" />
+</manifest>
diff --git a/tests/testdata/apkinapex/com.android.apex.system_ext.test/com.android.apex.system_ext.app.test.xml b/tests/testdata/apkinapex/com.android.apex.system_ext.test/com.android.apex.system_ext.app.test.xml
new file mode 100644
index 0000000..c7da76f
--- /dev/null
+++ b/tests/testdata/apkinapex/com.android.apex.system_ext.test/com.android.apex.system_ext.app.test.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+ <!--
+ ~ Copyright (C) 2021 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+<permissions>
+ <privapp-permissions package="com.android.apex.system_ext.app.test">
+ <permission name="android.permission.PACKAGE_USAGE_STATS" />
+ </privapp-permissions>
+</permissions>
\ No newline at end of file
diff --git a/tests/testdata/apkinapex/com.android.apex.system_ext.test/com.android.apex.system_ext.test.avbpubkey b/tests/testdata/apkinapex/com.android.apex.system_ext.test/com.android.apex.system_ext.test.avbpubkey
new file mode 100644
index 0000000..0d978b8
--- /dev/null
+++ b/tests/testdata/apkinapex/com.android.apex.system_ext.test/com.android.apex.system_ext.test.avbpubkey
Binary files differ
diff --git a/tests/testdata/apkinapex/com.android.apex.system_ext.test/com.android.apex.system_ext.test.pem b/tests/testdata/apkinapex/com.android.apex.system_ext.test/com.android.apex.system_ext.test.pem
new file mode 100644
index 0000000..fe56f0b
--- /dev/null
+++ b/tests/testdata/apkinapex/com.android.apex.system_ext.test/com.android.apex.system_ext.test.pem
@@ -0,0 +1,51 @@
+-----BEGIN RSA PRIVATE KEY-----
+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
+-----END RSA PRIVATE KEY-----
diff --git a/tests/testdata/apkinapex/com.android.apex.system_ext.test/com.android.apex.system_ext.test.pk8 b/tests/testdata/apkinapex/com.android.apex.system_ext.test/com.android.apex.system_ext.test.pk8
new file mode 100644
index 0000000..e9b17b5
--- /dev/null
+++ b/tests/testdata/apkinapex/com.android.apex.system_ext.test/com.android.apex.system_ext.test.pk8
Binary files differ
diff --git a/tests/testdata/apkinapex/com.android.apex.system_ext.test/com.android.apex.system_ext.test.x509.pem b/tests/testdata/apkinapex/com.android.apex.system_ext.test/com.android.apex.system_ext.test.x509.pem
new file mode 100644
index 0000000..516effd
--- /dev/null
+++ b/tests/testdata/apkinapex/com.android.apex.system_ext.test/com.android.apex.system_ext.test.x509.pem
@@ -0,0 +1,34 @@
+-----BEGIN CERTIFICATE-----
+MIIF5TCCA80CFFkJTEXUYaClwWvv+oDTGGGDUJE9MA0GCSqGSIb3DQEBCwUAMIGt
+MQswCQYDVQQGEwJVUzETMBEGA1UECAwKQ2FsaWZvcm5pYTEWMBQGA1UEBwwNTW91
+bnRhaW4gVmlldzEQMA4GA1UECgwHQW5kcm9pZDEQMA4GA1UECwwHQW5kcm9pZDEi
+MCAGCSqGSIb3DQEJARYTYW5kcm9pZEBhbmRyb2lkLmNvbTEpMCcGA1UEAwwgY29t
+LmFuZHJvaWQuYXBleC5zeXN0ZW1fZXh0LnRlc3QwIBcNMjExMTA0MTc0NjE0WhgP
+NDc1OTEwMDExNzQ2MTRaMIGtMQswCQYDVQQGEwJVUzETMBEGA1UECAwKQ2FsaWZv
+cm5pYTEWMBQGA1UEBwwNTW91bnRhaW4gVmlldzEQMA4GA1UECgwHQW5kcm9pZDEQ
+MA4GA1UECwwHQW5kcm9pZDEiMCAGCSqGSIb3DQEJARYTYW5kcm9pZEBhbmRyb2lk
+LmNvbTEpMCcGA1UEAwwgY29tLmFuZHJvaWQuYXBleC5zeXN0ZW1fZXh0LnRlc3Qw
+ggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDU2EVWNLhekkYf1mT8RnED
+9pf7v/1RN65CAFDmNydLsd8S4ef0xiIJEgaOH70T3AinLIq5o9EYJenGkvhLWdB1
+YEMcaNlmIz/HjxFOHwIyzNoUyHOP049ZggW8sCWzrB2srO0Dv9EjIzuA3c8s2cFx
+mKKzarW45CDACoFQ5KXn73LzKc3CI/qBnnMGlzWpCY2VW4rJjUCiSmAg2F6+bC7M
+xu0YPIabo6OL4dFHFI37Zhh8RkbrMyT+C6sA7wJym2X1dIt6NQ7/kDKRwjWAMz9D
+Myt1mg7CiK+rNgWsYJZcPopxvuis4RXppgl2JZhi+31NWlaxgPEOo+u1jUWCJRLG
+z1qjL1icGWB6f3oG2hMpV+QuNCAJyQj7Ixn6zLXjQaV2YngK0PJwKYW+jKc3/w8T
+mF4MsMmPGYg7734VsK/K+IAl5KkcYRMvAbBMJ+cm/V89yKLuWngf73F7AOQn6jyZ
+Puo3y9k7vdIVO1TGKltCx66R+omMwjt8OmqMDPQcaXo1fb1CZbvOEPrGIFn1pdN9
+H7IjszxldJzBSBD4Pee1XyykI4tkEIG4ZvwF7NNSPUPkTIgcu47YASe2Z+aWhAwp
+fyz+0D2NOtEBvjg+jDq1jgr1716DP7h91iwOSDt+/VCmuUzaN2IEeIM7m24/X+iq
+79oUCfEu2sEuNz9LHJONKQIDAQABMA0GCSqGSIb3DQEBCwUAA4ICAQB0rOMaVXta
+sa8o1ijJkZYu3xR17aZcq5FjiziqF22pkVOKNMLm8/IlSFEYgXFzHdtplslHD6Sm
+0ohITbEOJVarAchphl0WiW9YU9tvEbV06feOBE8UgOkAm6eLW4ahfxKUGtikL3UK
+Cudjjz56uzXoxsJuylwP3e+rhKH4otL7bzEHI7Unmz2eNd5ro67avzIuxCUVKcQL
+ne43YWLvlO0DjUBUXQcYIuhNTaGkgzRwezKZHQCG61LDdTVVrxzh+H3H1A6yYD9h
+BsuTJn3ybuXhS+NGo5PEbSPMN9m6nWPMOhzov0fMp3q78riBERw8K1Ut1v2UWmto
+w6xDV7WBhUWT3U8fvYKni+L0dAEDImCvIMgi7+2ucQUhhIyMkShABlYOrWmgYa9W
+/gljh9yrtMc/r/LEsgf/CUSiMDW/fQZrP9LaApwH5LF0ObXiklrhhC3UT+utlRZi
+9RMvlW7OZQCefEwRAVk/KM69tLZaafKuGFgEtvqRvBqK/NQwsgJ0F6zQpUQ2x0qP
+YIv8qbSJYLybAcm3cYwJ7KZQesdydVgY/AVRzFOoT9T7h4Lx3B/UG054LYW4UoTT
+XQjRK9jufPsLvDIbkf02TX1N5RSp2U5EvViaujUmFLlqQfJZcIo9dGKltVThuBuh
+gFuk3Y7JQVZ0GDt6gpeSLReRjGD1OBAbZA==
+-----END CERTIFICATE-----
diff --git a/tests/testdata/apkinapex/com.android.apex.system_ext.test/manifest.json b/tests/testdata/apkinapex/com.android.apex.system_ext.test/manifest.json
new file mode 100644
index 0000000..2249493
--- /dev/null
+++ b/tests/testdata/apkinapex/com.android.apex.system_ext.test/manifest.json
@@ -0,0 +1,4 @@
+{
+ "name": "com.android.apex.system_ext.test",
+ "version": 1
+}
diff --git a/tests/testdata/apkinapex/com.android.apex.vendor.test/Android.bp b/tests/testdata/apkinapex/com.android.apex.vendor.test/Android.bp
new file mode 100644
index 0000000..8300d6f
--- /dev/null
+++ b/tests/testdata/apkinapex/com.android.apex.vendor.test/Android.bp
@@ -0,0 +1,52 @@
+// Copyright (C) 2021 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package {
+ default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+apex_key {
+ name: "com.android.apex.vendor.test.key",
+ public_key: "com.android.apex.vendor.test.avbpubkey",
+ private_key: "com.android.apex.vendor.test.pem",
+}
+
+android_app_certificate {
+ name: "com.android.apex.vendor.test.certificate",
+ certificate: "com.android.apex.vendor.test",
+}
+
+apex {
+ name: "com.android.apex.vendor.test",
+ manifest: "manifest.json",
+ file_contexts: ":apex.test-file_contexts", // Default, please edit, see go/android-apex-howto
+ key: "com.android.apex.vendor.test.key",
+ updatable: false,
+ apps: ["com.android.apex.vendor.app.test"],
+}
+
+android_app {
+ name: "com.android.apex.vendor.app.test",
+ manifest: "App_AndroidManifest.xml",
+ sdk_version: "31",
+ privileged: true,
+ apex_available: [
+ "com.android.apex.vendor.test",
+ ]
+}
+
+filegroup {
+ name: "com.android.apex.vendor.app.test.xml",
+ srcs: ["com.android.apex.vendor.app.test.xml"],
+}
diff --git a/tests/testdata/apkinapex/com.android.apex.vendor.test/App_AndroidManifest.xml b/tests/testdata/apkinapex/com.android.apex.vendor.test/App_AndroidManifest.xml
new file mode 100644
index 0000000..1b3df04
--- /dev/null
+++ b/tests/testdata/apkinapex/com.android.apex.vendor.test/App_AndroidManifest.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ * Copyright (C) 2021 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.
+ -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.apex.vendor.app.test">
+ <uses-permission android:name="android.permission.START_ACTIVITIES_FROM_BACKGROUND" />
+</manifest>
diff --git a/tests/testdata/apkinapex/com.android.apex.vendor.test/com.android.apex.vendor.app.test.xml b/tests/testdata/apkinapex/com.android.apex.vendor.test/com.android.apex.vendor.app.test.xml
new file mode 100644
index 0000000..6c68a2b
--- /dev/null
+++ b/tests/testdata/apkinapex/com.android.apex.vendor.test/com.android.apex.vendor.app.test.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+ <!--
+ ~ Copyright (C) 2021 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+<permissions>
+ <privapp-permissions package="com.android.apex.vendor.app.test">
+ <permission name="android.permission.START_ACTIVITIES_FROM_BACKGROUND" />
+ </privapp-permissions>
+</permissions>
\ No newline at end of file
diff --git a/tests/testdata/apkinapex/com.android.apex.vendor.test/com.android.apex.vendor.test.avbpubkey b/tests/testdata/apkinapex/com.android.apex.vendor.test/com.android.apex.vendor.test.avbpubkey
new file mode 100644
index 0000000..3ad68fc
--- /dev/null
+++ b/tests/testdata/apkinapex/com.android.apex.vendor.test/com.android.apex.vendor.test.avbpubkey
Binary files differ
diff --git a/tests/testdata/apkinapex/com.android.apex.vendor.test/com.android.apex.vendor.test.pem b/tests/testdata/apkinapex/com.android.apex.vendor.test/com.android.apex.vendor.test.pem
new file mode 100644
index 0000000..686044f
--- /dev/null
+++ b/tests/testdata/apkinapex/com.android.apex.vendor.test/com.android.apex.vendor.test.pem
@@ -0,0 +1,51 @@
+-----BEGIN RSA PRIVATE KEY-----
+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
+-----END RSA PRIVATE KEY-----
diff --git a/tests/testdata/apkinapex/com.android.apex.vendor.test/com.android.apex.vendor.test.pk8 b/tests/testdata/apkinapex/com.android.apex.vendor.test/com.android.apex.vendor.test.pk8
new file mode 100644
index 0000000..318020b
--- /dev/null
+++ b/tests/testdata/apkinapex/com.android.apex.vendor.test/com.android.apex.vendor.test.pk8
Binary files differ
diff --git a/tests/testdata/apkinapex/com.android.apex.vendor.test/com.android.apex.vendor.test.x509.pem b/tests/testdata/apkinapex/com.android.apex.vendor.test/com.android.apex.vendor.test.x509.pem
new file mode 100644
index 0000000..048a4f8
--- /dev/null
+++ b/tests/testdata/apkinapex/com.android.apex.vendor.test/com.android.apex.vendor.test.x509.pem
@@ -0,0 +1,34 @@
+-----BEGIN CERTIFICATE-----
+MIIF3TCCA8UCFF9h0n91axJ62gD/xssXeSrUmAvdMA0GCSqGSIb3DQEBCwUAMIGp
+MQswCQYDVQQGEwJVUzETMBEGA1UECAwKQ2FsaWZvcm5pYTEWMBQGA1UEBwwNTW91
+bnRhaW4gVmlldzEQMA4GA1UECgwHQW5kcm9pZDEQMA4GA1UECwwHQW5kcm9pZDEi
+MCAGCSqGSIb3DQEJARYTYW5kcm9pZEBhbmRyb2lkLmNvbTElMCMGA1UEAwwcY29t
+LmFuZHJvaWQuYXBleC52ZW5kb3IudGVzdDAgFw0yMTExMDQxNzQ2MjFaGA80NzU5
+MTAwMTE3NDYyMVowgakxCzAJBgNVBAYTAlVTMRMwEQYDVQQIDApDYWxpZm9ybmlh
+MRYwFAYDVQQHDA1Nb3VudGFpbiBWaWV3MRAwDgYDVQQKDAdBbmRyb2lkMRAwDgYD
+VQQLDAdBbmRyb2lkMSIwIAYJKoZIhvcNAQkBFhNhbmRyb2lkQGFuZHJvaWQuY29t
+MSUwIwYDVQQDDBxjb20uYW5kcm9pZC5hcGV4LnZlbmRvci50ZXN0MIICIjANBgkq
+hkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAt7zWvwuFCqqc8C7aMlkJRHENiAm9E52I
+1xAtlmUWAgQkkubPp91oY2yWTE0CH5WELis7iaBeZVIhuoDv1IvYHmIU/Jjd2g55
+vFmAzgJQV6b8h6sifJWqj0dkxgEMUalqYzbpQTcqSalypTEzxBPRWk97qPcZUoUJ
+MLLeCJtXdCdfCFXdUV+k3caMfID3xxXWGwms4DYJFAAbHVdDtfJkQ7nnad4PFK0+
+96p8piYqxG9b76/r4XNmpBQn4Fg/2/BTG1LRBTinJkk4OeCdnaAzlBIgcH3FXT77
+Zg+vqTsbNCaC7CrOAFU7KT50+kCSm65+CUPv+se+yiBBf6b8h93nV4UubyCFNQtr
+9+6I3IQxZJ2ArjJzriBYmuDV6hAEDY6c95ItCveEd2tQlWhnYTpOGw7OAC9+A2m+
+CzSGBFdVbloIEWt2MZPChdquxMd1v6t6mJdcFCWa1Ls6khiHlMYpmywyPSVyC5RA
+q5eG24vXGP0M5PhECgHaWVevrKVYcUyzWj/CrVxpkMFmejE081OvI3jlDyZSDBHH
+5xwlySWbiR2xNyCri5W+A/4qF+MT4UsqREnajHteHXoD7E3BoLWAlkFHNiclVQmT
+onNjtWEf6+9G49TJJ7xYWFvXC83xGhsaM0C3dZ0z/O1r0deXScSH20CP2A+/J2YW
+ROgQ7NVbxP0CAwEAATANBgkqhkiG9w0BAQsFAAOCAgEAngWUpDnD9n2Az9V7kl21
+PhKvsQaqad8NDQTAXYFU9JgNmVWk+TBHu1cKD7y06s676Hj+8yxZmi4twNYzVSf+
+pBBNazhGDsCPLUfSMgj8pCoXYxnFok+DrixY7p1fce5s7YZeu4IPUmsidMLO3YhJ
+lit1PmH5sEsFeC5E8OKP++uvOnOjbw5RWZs1t+FFW1RbDXMGTn3fpah+mN/N5Ie0
+t8FA+nghgGwiAx5/mf/B32RHjK+7BeHZZJWj87j3uBXOAkgH7vmcuy1zvlH8huTD
+Xh+rYUZBj0Ik+8iqF9Zj5tQLTuSVtaEiSH4kIvnLeFUYmQZwnnLPhzHB6dcL+KRY
+A4Zztxq6m3/5lA+6i4nHF/woY0cBnCaIirDRqjSzx9zlgk7ko4ZJAbTbEV735AbU
+253C73isnx465/sdiq9bk9/vIi6mj0ZAlPXtGict6XAnmjIqQKMAtJQJ0xB187BT
+PHqbUthMA10N4jey2QkyKVERZP7KyZFiPF99bM6gYemnnpedEawMF3dPSIK0egki
+78/VQVTBC5Uelx8QrIuYzT6EZHuGHIuNlm42WuH8pEOl0oh/jsXWTSiRFE/kQ9Gk
+/7bFLsiRPGSxccKy7QpMYvs08hKgQz3S/n22Wr3zmopVdqKnZ5kLoqpfsE4Ve0ab
+H379betgKB8EGRPjQLdNeI8=
+-----END CERTIFICATE-----
diff --git a/tests/testdata/apkinapex/com.android.apex.vendor.test/manifest.json b/tests/testdata/apkinapex/com.android.apex.vendor.test/manifest.json
new file mode 100644
index 0000000..7cbe6f9
--- /dev/null
+++ b/tests/testdata/apkinapex/com.android.apex.vendor.test/manifest.json
@@ -0,0 +1,4 @@
+{
+ "name": "com.android.apex.vendor.test",
+ "version": 1
+}
diff --git a/tests/testdata/sharedlibs/build/shared_libs_repack.py b/tests/testdata/sharedlibs/build/shared_libs_repack.py
index 31b9a6b..022d932 100644
--- a/tests/testdata/sharedlibs/build/shared_libs_repack.py
+++ b/tests/testdata/sharedlibs/build/shared_libs_repack.py
@@ -212,7 +212,7 @@
def _get_host_tools_path(tool_name=None):
# This script is located at e.g.
- # out/soong/host/linux-x86/bin/shared_libs_repack/shared_libs_repack.py.
+ # out/host/linux-x86/bin/shared_libs_repack/shared_libs_repack.py.
# Find the host tools dir by going up two directories.
dirname = os.path.dirname(os.path.dirname(os.path.realpath(__file__)))
if tool_name:
diff --git a/tools/apex_compression_test.py b/tools/apex_compression_test.py
index 018fc56..2a30007 100644
--- a/tools/apex_compression_test.py
+++ b/tools/apex_compression_test.py
@@ -74,7 +74,7 @@
host_build_top = os.environ.get('ANDROID_BUILD_TOP')
if host_build_top:
host_command_dir = os.path.join(host_build_top,
- 'out/soong/host/linux-x86/bin')
+ 'out/host/linux-x86/bin')
args[0] = os.path.join(host_command_dir, args[0])
return run_and_check_output(args, verbose, **kwargs)
@@ -152,7 +152,7 @@
host_build_top = os.environ.get('ANDROID_BUILD_TOP')
if host_build_top:
os.environ['APEX_COMPRESSION_TOOL_PATH'] = (
- os.path.join(host_build_top, 'out/soong/host/linux-x86/bin')
+ os.path.join(host_build_top, 'out/host/linux-x86/bin')
+ ':' + os.path.join(host_build_top, 'prebuilts/sdk/tools/linux/bin'))
else:
os.environ['APEX_COMPRESSION_TOOL_PATH'] = os.path.dirname(
diff --git a/tools/apex_compression_tool.py b/tools/apex_compression_tool.py
index 3c0f16d..a5545f5 100644
--- a/tools/apex_compression_tool.py
+++ b/tools/apex_compression_tool.py
@@ -89,10 +89,13 @@
cmd.extend(['-o', args.output])
# We want to put the input apex inside the compressed APEX with name
- # "original_apex". So we create a hard link and put the renamed file inside
- # the zip
+ # "original_apex". Originally this was done by creating a hard link
+ # in order to put the renamed file inside the zip, but it causes some issue
+ # when running this tool with Bazel in a sandbox which restricts the function
+ # of creating cross-device links. So instead of creating hard links, we make a
+ # copy of the original_apex here.
original_apex = os.path.join(work_dir, 'original_apex')
- os.link(args.input, original_apex)
+ shutil.copy2(args.input, original_apex)
cmd.extend(['-C', work_dir])
cmd.extend(['-f', original_apex])
@@ -115,24 +118,24 @@
# Set digest of original_apex to apex_manifest.pb
apex_manifest_path = os.path.join(extract_dir, 'apex_manifest.pb')
- assert AddOriginalApexDigestToManifest(apex_manifest_path, image_path)
+ assert AddOriginalApexDigestToManifest(apex_manifest_path, image_path, args.verbose)
# Don't forget to compress
cmd.extend(['-L', '9'])
- RunCommand(cmd, verbose=True)
+ RunCommand(cmd, verbose=args.verbose)
return True
-def AddOriginalApexDigestToManifest(capex_manifest_path, apex_image_path):
+def AddOriginalApexDigestToManifest(capex_manifest_path, apex_image_path, verbose=False):
# Retrieve the root digest of the image
avbtool_cmd = [
'avbtool',
'print_partition_digests', '--image',
apex_image_path]
# avbtool_cmd output has format "<name>: <value>"
- root_digest = RunCommand(avbtool_cmd, True)[0].decode().split(': ')[1].strip()
+ root_digest = RunCommand(avbtool_cmd, verbose=verbose)[0].decode().split(': ')[1].strip()
# Update the manifest proto file
with open(capex_manifest_path, 'rb') as f:
pb = apex_manifest_pb2.ApexManifest()
@@ -154,6 +157,8 @@
# Handle sub-command "compress"
parser_compress = subparsers.add_parser('compress',
help='compresses an APEX')
+ parser_compress.add_argument('-v', '--verbose', action='store_true',
+ help='verbose execution')
parser_compress.add_argument('--input', type=str, required=True,
help='path to input APEX file that will be '
'compressed')
diff --git a/tools/create_apex_skeleton.sh b/tools/create_apex_skeleton.sh
old mode 100644
new mode 100755
index 818e119..7b2a93d
--- a/tools/create_apex_skeleton.sh
+++ b/tools/create_apex_skeleton.sh
@@ -1,15 +1,28 @@
#!/bin/sh
-# Creates an apex stub in a subdirectory named after the package name. Edit the APEX_NAME variable
-# before running.
+# Creates an apex stub in a subdirectory named after the input package name.
-APEX_NAME=com.android.yourpackagenamehere
+# Exit early if any subcommands fail.
+set -e
-mkdir ${APEX_NAME}
+APEX_NAME=$1
+if [ -z ${APEX_NAME} ]
+then
+ echo "Missing apex package name"
+ echo "Usage $0 apex_package_name [existing_apex_key_name]"
+ exit -1
+fi
+
+# Optional. If provided, uses existing key files and module name.
+# Otherwise, generates new key files using the APEX_NAME.
+APEX_KEY=$2
+
+YEAR=$(date +%Y)
+mkdir -p ${APEX_NAME}
cd ${APEX_NAME}
cat > Android.bp <<EOF
-// Copyright (C) 2020 The Android Open Source Project
+// Copyright (C) ${YEAR} 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.
@@ -22,27 +35,19 @@
// 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.
-apex_key {
- name: "${APEX_NAME}.key",
- public_key: "${APEX_NAME}.avbpubkey",
- private_key: "${APEX_NAME}.pem",
+
+package {
+ default_applicable_licenses: ["Android-Apache-2.0"],
}
-android_app_certificate {
- name: "${APEX_NAME}.certificate",
- certificate: "${APEX_NAME}",
-}
-
-apex {
- name: "${APEX_NAME}",
- manifest: "manifest.json",
- file_contexts: ":apex.test-file_contexts", // Default, please edit, see go/android-apex-howto
- key: "${APEX_NAME}.key",
-}
EOF
-openssl genrsa -out ${APEX_NAME}.pem 4096
-avbtool extract_public_key --key ${APEX_NAME}.pem --output ${APEX_NAME}.avbpubkey
+if [ -z ${APEX_KEY} ]
+then
+APEX_KEY=${APEX_NAME}
+
+openssl genrsa -out ${APEX_KEY}.pem 4096
+avbtool extract_public_key --key ${APEX_KEY}.pem --output ${APEX_KEY}.avbpubkey
cat > csr.conf <<EOF
[req]
@@ -57,14 +62,41 @@
O="Android"
OU="Android"
emailAddress="android@android.com"
-CN="${APEX_NAME}"
+CN="${APEX_KEY}"
EOF
-openssl req -x509 -config csr.conf -newkey rsa:4096 -nodes -days 999999 -keyout key.pem -out ${APEX_NAME}.x509.pem
+openssl req -x509 -config csr.conf -newkey rsa:4096 -nodes -days 999999 -keyout key.pem -out ${APEX_KEY}.x509.pem
rm csr.conf
-openssl pkcs8 -topk8 -inform PEM -outform DER -in key.pem -out ${APEX_NAME}.pk8 -nocrypt
+openssl pkcs8 -topk8 -inform PEM -outform DER -in key.pem -out ${APEX_KEY}.pk8 -nocrypt
rm key.pem
+cat >> Android.bp <<EOF
+apex_key {
+ name: "${APEX_KEY}.key",
+ public_key: "${APEX_KEY}.avbpubkey",
+ private_key: "${APEX_KEY}.pem",
+}
+
+android_app_certificate {
+ name: "${APEX_KEY}.certificate",
+ certificate: "${APEX_KEY}",
+}
+
+EOF
+
+fi
+
+cat >> Android.bp <<EOF
+apex {
+ name: "${APEX_NAME}",
+ manifest: "manifest.json",
+ file_contexts: ":apex.test-file_contexts", // Default, please edit, see go/android-apex-howto
+ key: "${APEX_KEY}.key",
+ certificate: "${APEX_KEY}.certificate",
+ updatable: false,
+}
+EOF
+
cat > manifest.json << EOF
{
"name": "${APEX_NAME}",