diff options
author | android-build-team Robot <android-build-team-robot@google.com> | 2019-11-11 21:29:47 +0000 |
---|---|---|
committer | android-build-team Robot <android-build-team-robot@google.com> | 2019-11-11 21:29:47 +0000 |
commit | 25a56cd157e21d45191d6006ff392939d42e4410 (patch) | |
tree | fff8cd9985c5a885bcee94f65a081f2397003e5e | |
parent | b75503d9f5413c11327750f4b1d9d58615e8ffb1 (diff) | |
parent | a400d7e2e583b75da5f3db47e38138dbe13d58b7 (diff) | |
download | platform_system_apex-android10-mainline-resolv-release.tar.gz platform_system_apex-android10-mainline-resolv-release.tar.bz2 platform_system_apex-android10-mainline-resolv-release.zip |
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.bp | 7 | ||||
-rw-r--r-- | apexd/apex_constants.h | 3 | ||||
-rw-r--r-- | apexd/apex_file.cpp | 73 | ||||
-rw-r--r-- | apexd/apex_file_test.cpp | 6 | ||||
-rw-r--r-- | apexd/apex_manifest.cpp | 58 | ||||
-rw-r--r-- | apexd/apex_manifest.h | 3 | ||||
-rw-r--r-- | apexd/apex_manifest_test.cpp | 20 | ||||
-rw-r--r-- | apexd/apexd.cpp | 62 | ||||
-rw-r--r-- | apexd/apexd.h | 2 | ||||
-rw-r--r-- | apexd/apexd_main.cpp | 21 | ||||
-rw-r--r-- | apexd/apexd_prepostinstall.cpp | 17 | ||||
-rw-r--r-- | apexd/apexd_private.cpp | 2 | ||||
-rw-r--r-- | apexd/apexservice_test.cpp | 25 | ||||
-rw-r--r-- | apexer/Android.bp | 19 | ||||
-rw-r--r-- | apexer/apex_manifest.py | 15 | ||||
-rw-r--r-- | apexer/apexer.py | 42 | ||||
-rw-r--r-- | apexer/conv_apex_manifest.py | 75 | ||||
-rw-r--r-- | tests/Android.bp | 12 | ||||
-rw-r--r-- | tests/TEST_MAPPING | 13 | ||||
-rw-r--r-- | tests/neuralnetworks-e2e-tests.xml | 26 | ||||
-rw-r--r-- | tests/src/com/android/tests/apex/NeuralNetworksHostTest.java | 49 | ||||
-rw-r--r-- | tools/Android.bp | 5 | ||||
-rw-r--r-- | tools/deapexer.py | 73 |
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__': |