summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorandroid-build-team Robot <android-build-team-robot@google.com>2019-11-11 21:29:47 +0000
committerandroid-build-team Robot <android-build-team-robot@google.com>2019-11-11 21:29:47 +0000
commit25a56cd157e21d45191d6006ff392939d42e4410 (patch)
treefff8cd9985c5a885bcee94f65a081f2397003e5e
parentb75503d9f5413c11327750f4b1d9d58615e8ffb1 (diff)
parenta400d7e2e583b75da5f3db47e38138dbe13d58b7 (diff)
downloadapex-android10-mainline-resolv-release.tar.gz
Snap for 6001391 from a400d7e2e583b75da5f3db47e38138dbe13d58b7 to qt-aml-resolv-releaseandroid-mainline-10.0.0_r8android10-mainline-resolv-release
Change-Id: Ic7fa1dedcda674f2f61690b3135930cafd7b103a
-rw-r--r--apexd/Android.bp7
-rw-r--r--apexd/apex_constants.h3
-rw-r--r--apexd/apex_file.cpp73
-rw-r--r--apexd/apex_file_test.cpp6
-rw-r--r--apexd/apex_manifest.cpp58
-rw-r--r--apexd/apex_manifest.h3
-rw-r--r--apexd/apex_manifest_test.cpp20
-rw-r--r--apexd/apexd.cpp62
-rw-r--r--apexd/apexd.h2
-rw-r--r--apexd/apexd_main.cpp21
-rw-r--r--apexd/apexd_prepostinstall.cpp17
-rw-r--r--apexd/apexd_private.cpp2
-rw-r--r--apexd/apexservice_test.cpp25
-rw-r--r--apexer/Android.bp19
-rw-r--r--apexer/apex_manifest.py15
-rw-r--r--apexer/apexer.py42
-rw-r--r--apexer/conv_apex_manifest.py75
-rw-r--r--tests/Android.bp12
-rw-r--r--tests/TEST_MAPPING13
-rw-r--r--tests/neuralnetworks-e2e-tests.xml26
-rw-r--r--tests/src/com/android/tests/apex/NeuralNetworksHostTest.java49
-rw-r--r--tools/Android.bp5
-rw-r--r--tools/deapexer.py73
23 files changed, 468 insertions, 160 deletions
diff --git a/apexd/Android.bp b/apexd/Android.bp
index c597cb18..600bb8aa 100644
--- a/apexd/Android.bp
+++ b/apexd/Android.bp
@@ -288,11 +288,12 @@ genrule {
name: "gen_bad_apexes",
out: ["apex.apexd_test_manifest_mismatch.apex"],
srcs: [":apex.apexd_test"],
- tools: ["soong_zip", "zipalign"],
+ tools: ["soong_zip", "zipalign", "conv_apex_manifest"],
cmd: "unzip -q $(in) -d $(genDir) && " +
"sed -i -e 's/\"version\": 1/\"version\": 137/' $(genDir)/apex_manifest.json && " +
+ "$(location conv_apex_manifest) proto $(genDir)/apex_manifest.json -o $(genDir)/apex_manifest.pb && " +
"$(location soong_zip) -d -C $(genDir) -D $(genDir) " +
- "-s apex_manifest.json -s apex_payload.img -s apex_pubkey " +
+ "-s apex_manifest.pb -s apex_manifest.json -s apex_payload.img -s apex_pubkey " +
"-o $(genDir)/unaligned.apex && " +
"$(location zipalign) -f 4096 $(genDir)/unaligned.apex " +
"$(genDir)/apex.apexd_test_manifest_mismatch.apex"
@@ -308,7 +309,7 @@ genrule {
cmd: "unzip -q $(in) -d $(genDir) && " +
"dd if=/dev/zero of=$(genDir)/apex_payload.img conv=notrunc bs=1024 seek=16 count=1 && " +
"$(location soong_zip) -d -C $(genDir) -D $(genDir) " +
- "-s apex_manifest.json -s apex_payload.img -s apex_pubkey " +
+ "-s apex_manifest.pb -s apex_manifest.json -s apex_payload.img -s apex_pubkey " +
"-o $(genDir)/unaligned.apex && " +
"$(location zipalign) -f 4096 $(genDir)/unaligned.apex " +
"$(genDir)/apex.apexd_test_corrupt_apex.apex"
diff --git a/apexd/apex_constants.h b/apexd/apex_constants.h
index eb2ec7dc..e36ea8c9 100644
--- a/apexd/apex_constants.h
+++ b/apexd/apex_constants.h
@@ -33,6 +33,7 @@ static constexpr const char* kStagedSessionsDir = "/data/app-staging";
static constexpr const char* kApexPackageSuffix = ".apex";
-static constexpr const char* kManifestFilename = "apex_manifest.json";
+static constexpr const char* kManifestFilenameJson = "apex_manifest.json";
+static constexpr const char* kManifestFilenamePb = "apex_manifest.pb";
} // namespace apex
} // namespace android
diff --git a/apexd/apex_file.cpp b/apexd/apex_file.cpp
index 10a987b1..6219db21 100644
--- a/apexd/apex_file.cpp
+++ b/apexd/apex_file.cpp
@@ -85,10 +85,19 @@ Result<ApexFile> ApexFile::Open(const std::string& path) {
image_offset = entry.offset;
image_size = entry.uncompressed_length;
- ret = FindEntry(handle, kManifestFilename, &entry);
+ ret = FindEntry(handle, kManifestFilenamePb, &entry);
+ bool isJsonManifest = false;
if (ret < 0) {
- return Error() << "Could not find entry \"" << kManifestFilename
- << "\" in package " << path << ": " << ErrorCodeString(ret);
+ LOG(ERROR) << "Could not find entry \"" << kManifestFilenamePb
+ << "\" in package " << path << ": " << ErrorCodeString(ret);
+ LOG(ERROR) << "Falling back to JSON if present.";
+ isJsonManifest = true;
+ ret = FindEntry(handle, kManifestFilenameJson, &entry);
+ if (ret < 0) {
+ return Error() << "Could not find entry \"" << kManifestFilenameJson
+ << "\" in package " << path << ": "
+ << ErrorCodeString(ret);
+ }
}
uint32_t length = entry.uncompressed_length;
@@ -114,7 +123,12 @@ Result<ApexFile> ApexFile::Open(const std::string& path) {
}
}
- Result<ApexManifest> manifest = ParseManifest(manifest_content);
+ Result<ApexManifest> manifest;
+ if (isJsonManifest) {
+ manifest = ParseManifestJson(manifest_content);
+ } else {
+ manifest = ParseManifest(manifest_content);
+ }
if (!manifest) {
return manifest.error();
}
@@ -182,13 +196,10 @@ Result<std::unique_ptr<AvbFooter>> getAvbFooter(const ApexFile& apex,
return footer;
}
-Result<void> verifyPublicKey(const uint8_t* key, size_t length,
- std::string public_key_content) {
- if (public_key_content.length() != length ||
- memcmp(&public_key_content[0], key, length) != 0) {
- return Errorf("Failed to compare the bundled public key with key");
- }
- return {};
+bool CompareKeys(const uint8_t* key, size_t length,
+ const std::string& public_key_content) {
+ return public_key_content.length() == length &&
+ memcmp(&public_key_content[0], key, length) == 0;
}
Result<std::string> getPublicKeyName(const ApexFile& apex, const uint8_t* data,
@@ -240,29 +251,28 @@ Result<void> verifyVbMetaSignature(const ApexFile& apex, const uint8_t* data,
}
Result<const std::string> public_key = getApexKey(*key_name);
- Result<void> st;
if (public_key) {
// TODO(b/115718846)
// We need to decide whether we need rollback protection, and whether
// we can use the rollback protection provided by libavb.
- st = verifyPublicKey(pk, pk_len, *public_key);
+ if (!CompareKeys(pk, pk_len, *public_key)) {
+ return Error() << "Error verifying " << apex.GetPath() << ": "
+ << "public key doesn't match the pre-installed one";
+ }
} else if (kDebugAllowBundledKey) {
// Failing to find the matching public key in the built-in partitions
// is a hard error for non-debuggable build. For debuggable builds,
// the public key bundled in the APEX itself is used as a fallback.
LOG(WARNING) << "Verifying " << apex.GetPath() << " with the bundled key";
- st = verifyPublicKey(pk, pk_len, apex.GetBundledPublicKey());
+ if (!CompareKeys(pk, pk_len, apex.GetBundledPublicKey())) {
+ return Error() << "Error verifying " << apex.GetPath() << ": "
+ << "public key doesn't match the one bundled in the APEX";
+ }
} else {
return public_key.error();
}
-
- if (st) {
- LOG(VERBOSE) << apex.GetPath() << ": public key matches.";
- return st;
- }
-
- return Error() << "Error verifying " << apex.GetPath() << ": "
- << "couldn't verify public key: " << st.error();
+ LOG(VERBOSE) << apex.GetPath() << ": public key matches.";
+ return {};
}
Result<std::unique_ptr<uint8_t[]>> verifyVbMeta(const ApexFile& apex,
@@ -376,16 +386,17 @@ Result<ApexVerityData> ApexFile::VerifyApexVerity() const {
Result<void> ApexFile::VerifyManifestMatches(
const std::string& mount_path) const {
- std::string manifest_content;
- const std::string manifest_path = mount_path + "/" + kManifestFilename;
-
- if (!android::base::ReadFileToString(manifest_path, &manifest_content)) {
- return Error() << "Failed to read manifest file: " << manifest_path;
- }
-
- Result<ApexManifest> verifiedManifest = ParseManifest(manifest_content);
+ Result<ApexManifest> verifiedManifest =
+ ReadManifest(mount_path + "/" + kManifestFilenamePb);
if (!verifiedManifest) {
- return verifiedManifest.error();
+ LOG(ERROR) << "Could not read manifest from " << mount_path << "/"
+ << kManifestFilenamePb << " : " << verifiedManifest.error();
+ // Fallback to Json manifest if present.
+ LOG(ERROR) << "Trying to find a JSON manifest";
+ verifiedManifest = ReadManifest(mount_path + "/" + kManifestFilenameJson);
+ if (!verifiedManifest) {
+ return verifiedManifest.error();
+ }
}
if (!MessageDifferencer::Equals(manifest_, *verifiedManifest)) {
diff --git a/apexd/apex_file_test.cpp b/apexd/apex_file_test.cpp
index 409fc0cb..bba45a00 100644
--- a/apexd/apex_file_test.cpp
+++ b/apexd/apex_file_test.cpp
@@ -89,10 +89,10 @@ TEST(ApexFileTest, VerifyApexVerity) {
const ApexVerityData& data = *verity_or;
EXPECT_NE(nullptr, data.desc.get());
- EXPECT_EQ(std::string("e2dfc983b21982053ee049c03310ea89be03a889"
- "0f997280fe1b93d9102837fd"),
+ EXPECT_EQ(std::string("368a22e64858647bc45498e92f749f85482ac468"
+ "50ca7ec8071f49dfa47a243c"),
data.salt);
- EXPECT_EQ(std::string("d1c0b25724e35c5af83145d000b556fe45c4c8e5"),
+ EXPECT_EQ(std::string("705d8ec15be38fe416ed75045056434132758008"),
data.root_digest);
}
diff --git a/apexd/apex_manifest.cpp b/apexd/apex_manifest.cpp
index 1d1de4d9..76b484bd 100644
--- a/apexd/apex_manifest.cpp
+++ b/apexd/apex_manifest.cpp
@@ -17,57 +17,39 @@
#include "apex_manifest.h"
#include <android-base/file.h>
#include <android-base/logging.h>
+#include <android-base/strings.h>
+#include "apex_constants.h"
#include "string_log.h"
#include <google/protobuf/util/json_util.h>
-#include <google/protobuf/util/type_resolver_util.h>
#include <memory>
#include <string>
+using android::base::EndsWith;
using android::base::Error;
using android::base::Result;
-using google::protobuf::DescriptorPool;
-using google::protobuf::util::NewTypeResolverForDescriptorPool;
-using google::protobuf::util::TypeResolver;
+using google::protobuf::util::JsonParseOptions;
namespace android {
namespace apex {
namespace {
-const char kTypeUrlPrefix[] = "type.googleapis.com";
-std::string GetTypeUrl(const ApexManifest& apex_manifest) {
- const google::protobuf::Descriptor* message = apex_manifest.GetDescriptor();
- return std::string(kTypeUrlPrefix) + "/" + message->full_name();
-}
-
-// TODO: JsonStringToMessage is a newly added function in protobuf
-// and is not yet available in the android tree. Replace this function with
-// https://developers.google.com/protocol-buffers/docs/reference/cpp/
-// google.protobuf.util.json_util#JsonStringToMessage.details
-// as and when the android tree gets updated
Result<void> JsonToApexManifestMessage(const std::string& content,
ApexManifest* apex_manifest) {
- std::unique_ptr<TypeResolver> resolver(NewTypeResolverForDescriptorPool(
- kTypeUrlPrefix, DescriptorPool::generated_pool()));
- std::string binary;
- auto parse_status = JsonToBinaryString(
- resolver.get(), GetTypeUrl(*apex_manifest), content, &binary);
+ JsonParseOptions options;
+ options.ignore_unknown_fields = true;
+ auto parse_status = JsonStringToMessage(content, apex_manifest, options);
if (!parse_status.ok()) {
return Error() << "Failed to parse APEX Manifest JSON config: "
<< parse_status.error_message().as_string();
}
-
- if (!apex_manifest->ParseFromString(binary)) {
- return Error() << "Unexpected fields in APEX Manifest JSON config";
- }
return {};
}
} // namespace
-Result<ApexManifest> ParseManifest(const std::string& content) {
+Result<ApexManifest> ParseManifestJson(const std::string& content) {
ApexManifest apex_manifest;
- std::string err;
Result<void> parse_manifest_status =
JsonToApexManifestMessage(content, &apex_manifest);
if (!parse_manifest_status) {
@@ -87,6 +69,27 @@ Result<ApexManifest> ParseManifest(const std::string& content) {
return apex_manifest;
}
+Result<ApexManifest> ParseManifest(const std::string& content) {
+ ApexManifest apex_manifest;
+ std::string err;
+
+ if (!apex_manifest.ParseFromString(content)) {
+ return Error() << "Can't parse APEX manifest.";
+ }
+
+ // Verifying required fields.
+ // name
+ if (apex_manifest.name().empty()) {
+ return Error() << "Missing required field \"name\" from APEX manifest.";
+ }
+
+ // version
+ if (apex_manifest.version() == 0) {
+ return Error() << "Missing required field \"version\" from APEX manifest.";
+ }
+ return apex_manifest;
+}
+
std::string GetPackageId(const ApexManifest& apexManifest) {
return apexManifest.name() + "@" + std::to_string(apexManifest.version());
}
@@ -96,6 +99,9 @@ Result<ApexManifest> ReadManifest(const std::string& path) {
if (!android::base::ReadFileToString(path, &content)) {
return Error() << "Failed to read manifest file: " << path;
}
+ if (EndsWith(path, kManifestFilenameJson)) {
+ return ParseManifestJson(content);
+ }
return ParseManifest(content);
}
diff --git a/apexd/apex_manifest.h b/apexd/apex_manifest.h
index f2996483..58f24bc9 100644
--- a/apexd/apex_manifest.h
+++ b/apexd/apex_manifest.h
@@ -29,6 +29,9 @@ namespace android {
namespace apex {
// Parses and validates APEX manifest.
android::base::Result<ApexManifest> ParseManifest(const std::string& content);
+// Parses and validates APEX manifest (in JSON format);
+android::base::Result<ApexManifest> ParseManifestJson(
+ const std::string& content);
// Returns package id of an ApexManifest
std::string GetPackageId(const ApexManifest& apex_manifest);
// Reads and parses APEX manifest from the file on disk.
diff --git a/apexd/apex_manifest_test.cpp b/apexd/apex_manifest_test.cpp
index 44528fa5..dec6d76e 100644
--- a/apexd/apex_manifest_test.cpp
+++ b/apexd/apex_manifest_test.cpp
@@ -26,7 +26,7 @@ namespace android {
namespace apex {
TEST(ApexManifestTest, SimpleTest) {
- auto apex_manifest = ParseManifest(
+ auto apex_manifest = ParseManifestJson(
"{\"name\": \"com.android.example.apex\", \"version\": 1}\n");
ASSERT_TRUE(apex_manifest) << apex_manifest.error();
EXPECT_EQ("com.android.example.apex", std::string(apex_manifest->name()));
@@ -35,7 +35,7 @@ TEST(ApexManifestTest, SimpleTest) {
}
TEST(ApexManifestTest, NameMissing) {
- auto apex_manifest = ParseManifest("{\"version\": 1}\n");
+ auto apex_manifest = ParseManifestJson("{\"version\": 1}\n");
ASSERT_FALSE(apex_manifest);
EXPECT_EQ(apex_manifest.error().message(),
std::string("Missing required field \"name\" from APEX manifest."))
@@ -44,7 +44,7 @@ TEST(ApexManifestTest, NameMissing) {
TEST(ApexManifestTest, VersionMissing) {
auto apex_manifest =
- ParseManifest("{\"name\": \"com.android.example.apex\"}\n");
+ ParseManifestJson("{\"name\": \"com.android.example.apex\"}\n");
ASSERT_FALSE(apex_manifest);
EXPECT_EQ(
apex_manifest.error().message(),
@@ -53,7 +53,7 @@ TEST(ApexManifestTest, VersionMissing) {
}
TEST(ApexManifestTest, VersionNotNumber) {
- auto apex_manifest = ParseManifest(
+ auto apex_manifest = ParseManifestJson(
"{\"name\": \"com.android.example.apex\", \"version\": \"a\"}\n");
ASSERT_FALSE(apex_manifest);
@@ -64,14 +64,14 @@ TEST(ApexManifestTest, VersionNotNumber) {
}
TEST(ApexManifestTest, NoPreInstallHook) {
- auto apex_manifest = ParseManifest(
+ auto apex_manifest = ParseManifestJson(
"{\"name\": \"com.android.example.apex\", \"version\": 1}\n");
ASSERT_TRUE(apex_manifest) << apex_manifest.error();
EXPECT_EQ("", std::string(apex_manifest->preinstallhook()));
}
TEST(ApexManifestTest, PreInstallHook) {
- auto apex_manifest = ParseManifest(
+ auto apex_manifest = ParseManifestJson(
"{\"name\": \"com.android.example.apex\", \"version\": 1, "
"\"preInstallHook\": \"bin/preInstallHook\"}\n");
ASSERT_TRUE(apex_manifest) << apex_manifest.error();
@@ -79,14 +79,14 @@ TEST(ApexManifestTest, PreInstallHook) {
}
TEST(ApexManifestTest, NoPostInstallHook) {
- auto apex_manifest = ParseManifest(
+ auto apex_manifest = ParseManifestJson(
"{\"name\": \"com.android.example.apex\", \"version\": 1}\n");
ASSERT_TRUE(apex_manifest) << apex_manifest.error();
EXPECT_EQ("", std::string(apex_manifest->postinstallhook()));
}
TEST(ApexManifestTest, PostInstallHook) {
- auto apex_manifest = ParseManifest(
+ auto apex_manifest = ParseManifestJson(
"{\"name\": \"com.android.example.apex\", \"version\": 1, "
"\"postInstallHook\": \"bin/postInstallHook\"}\n");
ASSERT_TRUE(apex_manifest) << apex_manifest.error();
@@ -95,7 +95,7 @@ TEST(ApexManifestTest, PostInstallHook) {
}
TEST(ApexManifestTest, UnparsableManifest) {
- auto apex_manifest = ParseManifest("This is an invalid pony");
+ auto apex_manifest = ParseManifestJson("This is an invalid pony");
ASSERT_FALSE(apex_manifest);
EXPECT_EQ(apex_manifest.error().message(),
std::string("Failed to parse APEX Manifest JSON config: Unexpected "
@@ -104,7 +104,7 @@ TEST(ApexManifestTest, UnparsableManifest) {
}
TEST(ApexManifestTest, NoCode) {
- auto apex_manifest = ParseManifest(
+ auto apex_manifest = ParseManifestJson(
"{\"name\": \"com.android.example.apex\", \"version\": 1, "
"\"noCode\": true}\n");
ASSERT_TRUE(apex_manifest) << apex_manifest.error();
diff --git a/apexd/apexd.cpp b/apexd/apexd.cpp
index 190a71c8..8c2d762a 100644
--- a/apexd/apexd.cpp
+++ b/apexd/apexd.cpp
@@ -118,12 +118,19 @@ bool gInFsCheckpointMode = false;
static constexpr size_t kLoopDeviceSetupAttempts = 3u;
bool gBootstrap = false;
-static const std::vector<const std::string> kBootstrapApexes = {
- "com.android.art",
- "com.android.i18n",
- "com.android.runtime",
- "com.android.tzdata",
-};
+static const std::vector<std::string> kBootstrapApexes = ([]() {
+ std::vector<std::string> ret = {
+ "com.android.art",
+ "com.android.i18n",
+ "com.android.runtime",
+ "com.android.tzdata",
+ };
+
+ if (auto ver = android::base::GetProperty("ro.vndk.version", ""); ver != "") {
+ ret.push_back("com.android.vndk.v" + ver);
+ }
+ return ret;
+})();
static constexpr const int kNumRetriesWhenCheckpointingEnabled = 1;
@@ -521,7 +528,9 @@ Result<MountedApexData> MountPackageImpl(const ApexFile& apex,
<< mountPoint;
auto status = VerifyMountedImage(apex, mountPoint);
if (!status) {
- umount2(mountPoint.c_str(), UMOUNT_NOFOLLOW | MNT_DETACH);
+ if (umount2(mountPoint.c_str(), UMOUNT_NOFOLLOW) != 0) {
+ PLOG(ERROR) << "Failed to umount " << mountPoint;
+ }
return Error() << "Failed to verify " << full_path << ": "
<< status.error();
}
@@ -551,7 +560,7 @@ Result<void> Unmount(const MountedApexData& data) {
LOG(DEBUG) << "Unmounting " << data.full_path << " from mount point "
<< data.mount_point;
// Lazily try to umount whatever is mounted.
- if (umount2(data.mount_point.c_str(), UMOUNT_NOFOLLOW | MNT_DETACH) != 0 &&
+ if (umount2(data.mount_point.c_str(), UMOUNT_NOFOLLOW) != 0 &&
errno != EINVAL && errno != ENOENT) {
return ErrnoError() << "Failed to unmount directory " << data.mount_point;
}
@@ -565,8 +574,7 @@ Result<void> Unmount(const MountedApexData& data) {
if (!data.device_name.empty()) {
const auto& status = DeleteVerityDevice(data.device_name);
if (!status) {
- LOG(DEBUG) << "Failed to free device " << data.device_name << " : "
- << status.error();
+ return status;
}
}
@@ -1000,7 +1008,7 @@ Result<void> UnmountPackage(const ApexFile& apex, bool allow_latest) {
}
std::string mount_point = apexd_private::GetActiveMountPoint(manifest);
LOG(VERBOSE) << "Unmounting and deleting " << mount_point;
- if (umount2(mount_point.c_str(), UMOUNT_NOFOLLOW | MNT_DETACH) != 0) {
+ if (umount2(mount_point.c_str(), UMOUNT_NOFOLLOW) != 0) {
return ErrnoError() << "Failed to unmount " << mount_point;
}
if (rmdir(mount_point.c_str()) != 0) {
@@ -1475,8 +1483,10 @@ Result<void> stagePackages(const std::vector<std::string>& tmpPaths) {
}
std::string dest_path = StageDestPath(*apex_file);
if (access(dest_path.c_str(), F_OK) == 0) {
- LOG(DEBUG) << dest_path << " already exists. Skipping";
- continue;
+ LOG(DEBUG) << dest_path << " already exists. Deleting";
+ if (TEMP_FAILURE_RETRY(unlink(dest_path.c_str())) != 0) {
+ return ErrnoError() << "Failed to unlink " << dest_path;
+ }
}
if (link(apex_file->GetPath().c_str(), dest_path.c_str()) != 0) {
@@ -1897,5 +1907,31 @@ void unmountDanglingMounts() {
RemoveObsoleteHashTrees();
}
+int unmountAll() {
+ gMountedApexes.PopulateFromMounts();
+ int ret = 0;
+ gMountedApexes.ForallMountedApexes([&](const std::string& /*package*/,
+ const MountedApexData& data,
+ bool latest) {
+ LOG(INFO) << "Unmounting " << data.full_path << " mounted on "
+ << data.mount_point;
+ if (latest) {
+ auto pos = data.mount_point.find('@');
+ CHECK(pos != std::string::npos);
+ std::string bind_mount = data.mount_point.substr(0, pos);
+ if (umount2(bind_mount.c_str(), UMOUNT_NOFOLLOW) != 0) {
+ PLOG(ERROR) << "Failed to unmount bind-mount " << bind_mount;
+ ret = 1;
+ }
+ }
+ if (auto status = Unmount(data); !status) {
+ LOG(ERROR) << "Failed to unmount " << data.mount_point << " : "
+ << status.error();
+ ret = 1;
+ }
+ });
+ return ret;
+}
+
} // namespace apex
} // namespace android
diff --git a/apexd/apexd.h b/apexd/apexd.h
index c9e86f1e..841b8c56 100644
--- a/apexd/apexd.h
+++ b/apexd/apexd.h
@@ -75,6 +75,8 @@ void onStart(CheckpointInterface* checkpoint_service);
void onAllPackagesReady();
void unmountDanglingMounts();
+int unmountAll();
+
} // namespace apex
} // namespace android
diff --git a/apexd/apexd_main.cpp b/apexd/apexd_main.cpp
index 1e228c29..7b429252 100644
--- a/apexd/apexd_main.cpp
+++ b/apexd/apexd_main.cpp
@@ -47,28 +47,19 @@ int HandleSubcommand(char** argv) {
return android::apex::onBootstrap();
}
+ if (strcmp("--unmount-all", argv[1]) == 0) {
+ LOG(INFO) << "Unmount all subcommand detected";
+ return android::apex::unmountAll();
+ }
+
LOG(ERROR) << "Unknown subcommand: " << argv[1];
return 1;
}
-struct CombinedLogger {
- android::base::LogdLogger logd;
-
- CombinedLogger() {}
-
- void operator()(android::base::LogId id, android::base::LogSeverity severity,
- const char* tag, const char* file, unsigned int line,
- const char* message) {
- logd(id, severity, tag, file, line, message);
- KernelLogger(id, severity, tag, file, line, message);
- }
-};
-
} // namespace
int main(int /*argc*/, char** argv) {
- // Use CombinedLogger to also log to the kernel log.
- android::base::InitLogging(argv, CombinedLogger());
+ android::base::InitLogging(argv, &android::base::KernelLogger);
// TODO: add a -v flag or an external setting to change LogSeverity.
android::base::SetMinimumLogSeverity(android::base::VERBOSE);
diff --git a/apexd/apexd_prepostinstall.cpp b/apexd/apexd_prepostinstall.cpp
index d9ece738..0f162ff2 100644
--- a/apexd/apexd_prepostinstall.cpp
+++ b/apexd/apexd_prepostinstall.cpp
@@ -165,11 +165,20 @@ int RunFnInstall(char** in_argv, Fn fn, const char* name) {
std::string active_point;
{
Result<ApexManifest> manifest_or =
- ReadManifest(mount_point + "/" + kManifestFilename);
+ ReadManifest(mount_point + "/" + kManifestFilenamePb);
if (!manifest_or) {
- LOG(ERROR) << "Could not read manifest from " << mount_point
- << " for " << name << ": " << manifest_or.error();
- _exit(202);
+ 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) {
+ 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)();
diff --git a/apexd/apexd_private.cpp b/apexd/apexd_private.cpp
index 8efe4463..10c5009f 100644
--- a/apexd/apexd_private.cpp
+++ b/apexd/apexd_private.cpp
@@ -75,7 +75,7 @@ Result<void> BindMount(const std::string& target, const std::string& source) {
};
// Unmount any active bind-mount.
if (exists) {
- int rc = umount2(target.c_str(), UMOUNT_NOFOLLOW | MNT_DETACH);
+ int rc = umount2(target.c_str(), UMOUNT_NOFOLLOW);
if (rc != 0 && errno != EINVAL) {
// Log error but ignore.
PLOG(ERROR) << "Could not unmount " << target;
diff --git a/apexd/apexservice_test.cpp b/apexd/apexservice_test.cpp
index 1a2c7c35..319f02d1 100644
--- a/apexd/apexservice_test.cpp
+++ b/apexd/apexservice_test.cpp
@@ -635,6 +635,27 @@ TEST_F(ApexServiceTest, StageAlreadyStagedPackageSuccess) {
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()) {
@@ -2122,7 +2143,9 @@ struct NoCodeApexNameProvider {
class ApexServiceActivationNoCode
: public ApexServiceActivationTest<NoCodeApexNameProvider> {};
-TEST_F(ApexServiceActivationNoCode, NoCodeApexIsNotExecutable) {
+// TODO(b/143974564): Enable no_code test after fixing apexd to understand
+// apex_manifest.pb
+TEST_F(ApexServiceActivationNoCode, DISABLED_NoCodeApexIsNotExecutable) {
ASSERT_TRUE(IsOk(service_->activatePackage(installer_->test_installed_file)))
<< GetDebugStr(installer_.get());
diff --git a/apexer/Android.bp b/apexer/Android.bp
index 68dc384c..97d64480 100644
--- a/apexer/Android.bp
+++ b/apexer/Android.bp
@@ -49,6 +49,25 @@ python_binary_host {
required: apexer_tools,
}
+python_binary_host {
+ name: "conv_apex_manifest",
+ srcs: [
+ "conv_apex_manifest.py",
+ ],
+ version: {
+ py2: {
+ enabled: true,
+ embedded_launcher: true,
+ },
+ py3: {
+ enabled: false,
+ },
+ },
+ libs: [
+ "apex_manifest_proto",
+ ],
+}
+
apex_key {
name: "com.android.support.apexer.key",
public_key: "etc/com.android.support.apexer.avbpubkey",
diff --git a/apexer/apex_manifest.py b/apexer/apex_manifest.py
index 290924e2..b2f08e52 100644
--- a/apexer/apex_manifest.py
+++ b/apexer/apex_manifest.py
@@ -14,11 +14,8 @@
# See the License for the specific language governing permissions and
# limitations under the License.
-import json
import apex_manifest_pb2
-from google.protobuf.json_format import Parse
-from google.protobuf.json_format import ParseError
-
+from google.protobuf import message
class ApexManifestError(Exception):
@@ -27,12 +24,12 @@ class ApexManifestError(Exception):
self.errmessage = errmessage
-def ValidateApexManifest(manifest_raw):
+def ValidateApexManifest(file):
try:
- manifest_json = json.loads(manifest_raw)
- manifest_pb = Parse(
- json.dumps(manifest_json), apex_manifest_pb2.ApexManifest())
- except (ParseError, ValueError) as err:
+ with open(file, "rb") as f:
+ manifest_pb = apex_manifest_pb2.ApexManifest()
+ manifest_pb.ParseFromString(f.read())
+ except message.DecodeError as err:
raise ApexManifestError(err)
# Checking required fields
if manifest_pb.name == "":
diff --git a/apexer/apexer.py b/apexer/apexer.py
index 678280c1..0e526f3c 100644
--- a/apexer/apexer.py
+++ b/apexer/apexer.py
@@ -44,8 +44,14 @@ def ParseArgs(argv):
'-v', '--verbose', action='store_true', help='verbose execution')
parser.add_argument(
'--manifest',
- default='apex_manifest.json',
- help='path to the APEX manifest file')
+ default='apex_manifest.pb',
+ help='path to the APEX manifest file (.pb)')
+ parser.add_argument(
+ '--manifest_json',
+ help='path to the APEX manifest file (Q compatible .json)')
+ parser.add_argument(
+ '--manifest_json_full',
+ help='path to the APEX manifest file (.json)')
parser.add_argument(
'--android_manifest',
help='path to the AndroidManifest file. If omitted, a default one is created and used'
@@ -244,10 +250,13 @@ def CreateApex(args, work_dir):
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:
- with open(args.manifest, 'r') as f:
- manifest_raw = f.read()
- manifest_apex = ValidateApexManifest(manifest_raw)
+ manifest_apex = ValidateApexManifest(args.manifest)
except ApexManifestError as err:
print("'" + args.manifest + "' is not a valid manifest file")
print err.errmessage
@@ -268,10 +277,10 @@ def CreateApex(args, work_dir):
# within the zip container).
manifests_dir = os.path.join(work_dir, 'manifests')
os.mkdir(manifests_dir)
- manifest_file = os.path.join(manifests_dir, 'apex_manifest.json')
- if args.verbose:
- print('Copying ' + args.manifest + ' to ' + manifest_file)
- shutil.copyfile(args.manifest, manifest_file)
+ 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'))
if args.payload_type == 'image':
key_name = os.path.basename(os.path.splitext(args.key)[0])
@@ -285,11 +294,11 @@ def CreateApex(args, work_dir):
img_file = os.path.join(content_dir, 'apex_payload.img')
# margin is for files that are not under args.input_dir. this consists of
- # one inode for apex_manifest.json and 11 reserved inodes for ext4.
+ # 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 = 12
+ inode_num_margin = GetFilesAndDirsCount(manifests_dir) + 11
inode_num = GetFilesAndDirsCount(args.input_dir) + inode_num_margin
cmd = ['mke2fs']
@@ -348,7 +357,7 @@ def CreateApex(args, work_dir):
cmd.extend(['--prop', 'apex.key:' + key_name])
# Set up the salt based on manifest content which includes name
# and version
- salt = hashlib.sha256(manifest_raw).hexdigest()
+ salt = hashlib.sha256(manifest_apex.SerializeToString()).hexdigest()
cmd.extend(['--salt', salt])
cmd.extend(['--image', img_file])
if args.no_hashtree:
@@ -396,8 +405,13 @@ def CreateApex(args, work_dir):
# copy manifest to the content dir so that it is also accessible
# without mounting the image
- shutil.copyfile(args.manifest, os.path.join(content_dir,
- 'apex_manifest.json'))
+ copyfile(args.manifest, os.path.join(content_dir, 'apex_manifest.pb'))
+ if args.manifest_json:
+ copyfile(args.manifest_json, os.path.join(content_dir, 'apex_manifest.json'))
+ if args.manifest_json_full:
+ copyfile(args.manifest_json_full, os.path.join(content_dir, 'apex_manifest_full.json'))
+ elif args.manifest_json_full:
+ copyfile(args.manifest_json_full, os.path.join(content_dir, 'apex_manifest.json'))
# copy the public key, if specified
if args.pubkey:
diff --git a/apexer/conv_apex_manifest.py b/apexer/conv_apex_manifest.py
new file mode 100644
index 00000000..04ad865e
--- /dev/null
+++ b/apexer/conv_apex_manifest.py
@@ -0,0 +1,75 @@
+#!/usr/bin/env python
+#
+# Copyright (C) 2019 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.
+"""conv_apex_manifest converts apex_manifest.json in two ways
+
+To remove keys which are unknown to Q
+ conv_apex_manifest strip apex_manifest.json (-o apex_manifest_stripped.json)
+
+To convert into .pb
+ conv_apex_manifest proto apex_manifest.json -o apex_manifest.pb
+"""
+
+import argparse
+import collections
+import json
+
+import apex_manifest_pb2
+from google.protobuf.json_format import ParseDict
+from google.protobuf.json_format import ParseError
+
+Q_compat_keys = ["name", "version", "preInstallHook", "postInstallHook", "versionName"]
+
+def Strip(args):
+ with open(args.input) as f:
+ obj = json.load(f, object_pairs_hook=collections.OrderedDict)
+
+ # remove unknown keys
+ for key in list(obj):
+ if key not in Q_compat_keys:
+ del obj[key]
+
+ if args.out:
+ with open(args.out, "w") as f:
+ json.dump(obj, f, indent=2)
+ else:
+ print(json.dumps(obj, indent=2))
+
+def Proto(args):
+ with open(args.input) as f:
+ obj = json.load(f, object_pairs_hook=collections.OrderedDict)
+ pb = ParseDict(obj, apex_manifest_pb2.ApexManifest())
+ with open(args.out, "wb") as f:
+ f.write(pb.SerializeToString())
+
+def main():
+ parser = argparse.ArgumentParser()
+ subparsers = parser.add_subparsers()
+
+ parser_strip = subparsers.add_parser('strip', help='remove unknown keys from APEX manifest (JSON)')
+ parser_strip.add_argument('input', type=str, help='APEX manifest file (JSON)')
+ parser_strip.add_argument('-o', '--out', type=str, help='Output filename. If omitted, prints to stdout')
+ parser_strip.set_defaults(func=Strip)
+
+ parser_proto = subparsers.add_parser('proto', help='write protobuf binary format')
+ parser_proto.add_argument('input', type=str, help='APEX manifest file (JSON)')
+ parser_proto.add_argument('-o', '--out', required=True, type=str, help='Directory to extract content of APEX to')
+ parser_proto.set_defaults(func=Proto)
+
+ args = parser.parse_args()
+ args.func(args)
+
+if __name__ == '__main__':
+ main()
diff --git a/tests/Android.bp b/tests/Android.bp
index 9567f3f5..7f6095b0 100644
--- a/tests/Android.bp
+++ b/tests/Android.bp
@@ -88,6 +88,18 @@ java_test_host {
}
java_test_host {
+ name: "neuralnetworks_e2e_tests",
+ srcs: ["src/**/NeuralNetworksHostTest.java"],
+ libs: ["tradefed"],
+ static_libs: ["apex_e2e_base_test"],
+ data: [
+ ":test_com.android.neuralnetworks",
+ ],
+ test_config: "neuralnetworks-e2e-tests.xml",
+ test_suites: ["general-tests"],
+}
+
+java_test_host {
name: "apex_targetprep_tests",
libs: ["tradefed"],
diff --git a/tests/TEST_MAPPING b/tests/TEST_MAPPING
index 7e89fcd3..e95d6021 100644
--- a/tests/TEST_MAPPING
+++ b/tests/TEST_MAPPING
@@ -1,10 +1,21 @@
{
"presubmit": [
{
+ "name": "apex_rollback_tests"
+ },
+ {
"name": "apex_targetprep_tests"
},
{
- "name": "CtsStagedInstallHostTestCases"
+ "name": "conscrypt_e2e_tests"
+ },
+ {
+ "name": "timezone_data_e2e_tests"
+ }
+ ],
+ "imports": [
+ {
+ "path": "cts/hostsidetests/stagedinstall"
}
]
}
diff --git a/tests/neuralnetworks-e2e-tests.xml b/tests/neuralnetworks-e2e-tests.xml
new file mode 100644
index 00000000..63c38a68
--- /dev/null
+++ b/tests/neuralnetworks-e2e-tests.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2019 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="Config for neuralnetworks module e2e test cases">
+ <option name="test-suite-tag" value="neuralnetworks_e2e_tests" />
+ <option name="test-suite-tag" value="apct" />
+
+ <target_preparer class="com.android.tradefed.targetprep.RootTargetPreparer"/>
+ <test class="com.android.tradefed.testtype.HostTest" >
+ <option name="jar" value="neuralnetworks_e2e_tests.jar" />
+ <option name="set-option" value="apex_file_name:test_com.android.neuralnetworks.apex" />
+ </test>
+</configuration>
+
diff --git a/tests/src/com/android/tests/apex/NeuralNetworksHostTest.java b/tests/src/com/android/tests/apex/NeuralNetworksHostTest.java
new file mode 100644
index 00000000..ea4cdeb9
--- /dev/null
+++ b/tests/src/com/android/tests/apex/NeuralNetworksHostTest.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2019 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;
+
+import static org.junit.Assert.assertTrue;
+
+import com.android.tradefed.device.DeviceNotAvailableException;
+import com.android.tradefed.device.IManagedTestDevice;
+import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.io.IOException;
+
+/**
+ * Test to check if Apex can be staged, activated and uninstalled successfully.
+ */
+@RunWith(DeviceJUnit4ClassRunner.class)
+public class NeuralNetworksHostTest extends ApexE2EBaseHostTest {
+
+ /**
+ * Tests that if Apex package can be staged, activated and uninstalled successfully.
+ */
+ @Test
+ public void testStageActivateUninstallApexPackage()
+ throws DeviceNotAvailableException, IOException {
+ doTestStageActivateUninstallApexPackage();
+ }
+
+ @Override
+ public void additionalCheck() {
+ assertTrue(((IManagedTestDevice) getDevice()).getMonitor().waitForBootComplete(60000));
+ }
+}
diff --git a/tools/Android.bp b/tools/Android.bp
index 9446e929..16858616 100644
--- a/tools/Android.bp
+++ b/tools/Android.bp
@@ -19,11 +19,10 @@ python_binary_host {
],
version: {
py2: {
- enabled: true,
- embedded_launcher: true,
+ enabled: false,
},
py3: {
- enabled: false,
+ enabled: true,
},
},
required: [
diff --git a/tools/deapexer.py b/tools/deapexer.py
index 9a2686d5..5b0035e4 100644
--- a/tools/deapexer.py
+++ b/tools/deapexer.py
@@ -16,16 +16,18 @@
"""deapexer is a tool that prints out content of an APEX.
To print content of an APEX to stdout:
- deapexer foo.apex
+ deapexer list foo.apex
-To diff content of an APEX with expected whitelist:
- deapexer foo.apex foo_whitelist.txt
+To extract content of an APEX to the given directory:
+ deapexer extract foo.apex dest
"""
+import argparse
+import os
import shutil
+import sys
import subprocess
import tempfile
-import os
import zipfile
@@ -99,7 +101,11 @@ class ApexImageDirectory(object):
yield ce
def enter_subdir(self, entry):
- return self._apex._list(self._path + '/' + entry.name)
+ return self._apex._list(self._path + entry.name + '/')
+
+ def extract(self, dest):
+ path = self._path
+ self._apex._extract(self._path, dest)
class Apex(object):
@@ -117,7 +123,7 @@ class Apex(object):
shutil.rmtree(self._tempdir)
def __enter__(self):
- return self._list('.')
+ return self._list('./')
def __exit__(self, type, value, traceback):
pass
@@ -145,27 +151,44 @@ class Apex(object):
is_directory=bits[1]=='4', is_symlink=bits[1]=='2'))
return ApexImageDirectory(path, entries, self)
+ def _extract(self, path, dest):
+ process = subprocess.Popen([self._debugfs, '-R', 'rdump %s %s' % (path, dest), self._payload],
+ stdout=subprocess.PIPE, stderr=subprocess.PIPE,
+ universal_newlines=True)
+ _, stderr = process.communicate()
+ print(stderr, file=sys.stderr)
+
-def main(argv):
- apex_content = []
- with Apex(argv[0]) as apex_dir:
- for e in apex_dir.list(is_recursive=True):
+def RunList(args):
+ with Apex(args.apex) as apex:
+ for e in apex.list(is_recursive=True):
if e.is_regular_file:
- apex_content.append(e.full_path)
- if len(argv) > 1:
- # diffing
- with open(argv[1], 'r') as f:
- whitelist = set([line.rstrip() for line in f.readlines()])
- diff = []
- for line in apex_content:
- if line not in whitelist:
- diff.append(line)
- if diff:
- print('%s contains following unexpected entries:\n%s' % (argv[0], '\n'.join(diff)))
- sys.exit(1)
- else:
- for line in apex_content:
- print(line)
+ print(e.full_path)
+
+
+def RunExtract(args):
+ with Apex(args.apex) as apex:
+ os.makedirs(args.dest, mode=0o755, exist_ok=True)
+ apex.extract(args.dest)
+
+
+def main(argv):
+ parser = argparse.ArgumentParser()
+ subparsers = parser.add_subparsers()
+
+ parser_list = subparsers.add_parser('list', help='prints content of an APEX to stdout')
+ parser_list.add_argument('apex', type=str, help='APEX file')
+ parser_list.set_defaults(func=RunList)
+
+ parser_extract = subparsers.add_parser('extract', help='extracts content of an APEX to the given '
+ 'directory')
+ parser_extract.add_argument('apex', type=str, help='APEX file')
+ parser_extract.add_argument('dest', type=str, help='Directory to extract content of APEX to')
+ parser_extract.set_defaults(func=RunExtract)
+
+ args = parser.parse_args(argv)
+
+ args.func(args)
if __name__ == '__main__':