diff --git a/cmds/idmap2/.clang-format b/cmds/idmap2/.clang-format new file mode 100644 index 0000000000000..c91502a257f3f --- /dev/null +++ b/cmds/idmap2/.clang-format @@ -0,0 +1,7 @@ +BasedOnStyle: Google +ColumnLimit: 100 +AllowShortBlocksOnASingleLine: false +AllowShortFunctionsOnASingleLine: false +CommentPragmas: NOLINT:.* +DerivePointerAlignment: false +PointerAlignment: Left diff --git a/cmds/idmap2/Android.bp b/cmds/idmap2/Android.bp new file mode 100644 index 0000000000000..5a6c813fd2025 --- /dev/null +++ b/cmds/idmap2/Android.bp @@ -0,0 +1,191 @@ +// 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. + +cc_library { + name: "libidmap2", + host_supported: true, + tidy: true, + tidy_flags: [ + "-system-headers", + "-warnings-as-errors=*", + ], + srcs: [ + "libidmap2/BinaryStreamVisitor.cpp", + "libidmap2/CommandLineOptions.cpp", + "libidmap2/FileUtils.cpp", + "libidmap2/Idmap.cpp", + "libidmap2/PrettyPrintVisitor.cpp", + "libidmap2/RawPrintVisitor.cpp", + "libidmap2/ResourceUtils.cpp", + "libidmap2/Xml.cpp", + "libidmap2/ZipFile.cpp", + ], + export_include_dirs: ["include"], + target: { + android: { + static: { + enabled: false, + }, + shared_libs: [ + "libandroidfw", + "libbase", + "libutils", + "libziparchive", + ], + }, + host: { + shared: { + enabled: false, + }, + static_libs: [ + "libandroidfw", + "libbase", + "libutils", + "libziparchive", + ], + }, + }, +} + +cc_test { + name: "idmap2_tests", + host_supported: true, + tidy: true, + tidy_flags: [ + "-system-headers", + "-warnings-as-errors=*", + ], + srcs: [ + "tests/BinaryStreamVisitorTests.cpp", + "tests/CommandLineOptionsTests.cpp", + "tests/FileUtilsTests.cpp", + "tests/Idmap2BinaryTests.cpp", + "tests/IdmapTests.cpp", + "tests/Main.cpp", + "tests/PrettyPrintVisitorTests.cpp", + "tests/RawPrintVisitorTests.cpp", + "tests/ResourceUtilsTests.cpp", + "tests/XmlTests.cpp", + "tests/ZipFileTests.cpp", + ], + required: [ + "idmap2", + ], + static_libs: ["libgmock"], + target: { + android: { + shared_libs: [ + "libandroidfw", + "libbase", + "libidmap2", + "liblog", + "libutils", + "libz", + "libziparchive", + ], + }, + host: { + static_libs: [ + "libandroidfw", + "libbase", + "libidmap2", + "liblog", + "libutils", + "libziparchive", + ], + shared_libs: [ + "libz", + ], + }, + }, + data: ["tests/data/**/*.apk"], +} + +cc_binary { + name: "idmap2", + host_supported: true, + tidy: true, + tidy_flags: [ + "-system-headers", + "-warnings-as-errors=*", + ], + srcs: [ + "idmap2/Create.cpp", + "idmap2/Dump.cpp", + "idmap2/Lookup.cpp", + "idmap2/Main.cpp", + "idmap2/Scan.cpp", + "idmap2/Verify.cpp", + ], + target: { + android: { + shared_libs: [ + "libandroidfw", + "libbase", + "libidmap2", + "libutils", + "libziparchive", + ], + }, + host: { + static_libs: [ + "libandroidfw", + "libbase", + "libidmap2", + "liblog", + "libutils", + "libziparchive", + ], + shared_libs: [ + "libz", + ], + }, + }, +} + +cc_binary { + name: "idmap2d", + host_supported: false, + tidy: true, + tidy_checks: [ + // remove google-default-arguments or clang-tidy will complain about + // the auto-generated file IIdmap2.cpp + "-google-default-arguments", + ], + tidy_flags: [ + "-system-headers", + "-warnings-as-errors=*", + ], + srcs: [ + ":idmap2_aidl", + "idmap2d/Idmap2Service.cpp", + "idmap2d/Main.cpp", + ], + shared_libs: [ + "libandroidfw", + "libbase", + "libbinder", + "libcutils", + "libidmap2", + "libutils", + "libziparchive", + ], +} + +filegroup { + name: "idmap2_aidl", + srcs: [ + "idmap2d/aidl/android/os/IIdmap2.aidl", + ], +} diff --git a/cmds/idmap2/AndroidTest.xml b/cmds/idmap2/AndroidTest.xml new file mode 100644 index 0000000000000..5147f4e6cb4c8 --- /dev/null +++ b/cmds/idmap2/AndroidTest.xml @@ -0,0 +1,26 @@ + + + + + + diff --git a/cmds/idmap2/CPPLINT.cfg b/cmds/idmap2/CPPLINT.cfg new file mode 100644 index 0000000000000..9dc6b4a773805 --- /dev/null +++ b/cmds/idmap2/CPPLINT.cfg @@ -0,0 +1,18 @@ +# 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. + +set noparent +linelength=100 +root=.. +filter=+build/include_alpha diff --git a/cmds/idmap2/OWNERS b/cmds/idmap2/OWNERS new file mode 100644 index 0000000000000..23ec5ab0d1f37 --- /dev/null +++ b/cmds/idmap2/OWNERS @@ -0,0 +1,2 @@ +set noparent +toddke@google.com diff --git a/cmds/idmap2/TEST_MAPPING b/cmds/idmap2/TEST_MAPPING new file mode 100644 index 0000000000000..26ccf038cba21 --- /dev/null +++ b/cmds/idmap2/TEST_MAPPING @@ -0,0 +1,7 @@ +{ + "presubmit" : [ + { + "name" : "idmap2_tests" + } + ] +} diff --git a/cmds/idmap2/idmap2/Commands.h b/cmds/idmap2/idmap2/Commands.h new file mode 100644 index 0000000000000..dcc69b30743da --- /dev/null +++ b/cmds/idmap2/idmap2/Commands.h @@ -0,0 +1,29 @@ +/* + * 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 IDMAP2_IDMAP2_COMMANDS_H_ +#define IDMAP2_IDMAP2_COMMANDS_H_ + +#include +#include + +bool Create(const std::vector& args, std::ostream& out_error); +bool Dump(const std::vector& args, std::ostream& out_error); +bool Lookup(const std::vector& args, std::ostream& out_error); +bool Scan(const std::vector& args, std::ostream& out_error); +bool Verify(const std::vector& args, std::ostream& out_error); + +#endif // IDMAP2_IDMAP2_COMMANDS_H_ diff --git a/cmds/idmap2/idmap2/Create.cpp b/cmds/idmap2/idmap2/Create.cpp new file mode 100644 index 0000000000000..291eaeb9c2116 --- /dev/null +++ b/cmds/idmap2/idmap2/Create.cpp @@ -0,0 +1,86 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include // umask +#include // umask +#include +#include +#include +#include +#include +#include + +#include "idmap2/BinaryStreamVisitor.h" +#include "idmap2/CommandLineOptions.h" +#include "idmap2/FileUtils.h" +#include "idmap2/Idmap.h" + +using android::ApkAssets; +using android::idmap2::BinaryStreamVisitor; +using android::idmap2::CommandLineOptions; +using android::idmap2::Idmap; + +bool Create(const std::vector& args, std::ostream& out_error) { + std::string target_apk_path, overlay_apk_path, idmap_path; + + const CommandLineOptions opts = + CommandLineOptions("idmap2 create") + .MandatoryOption("--target-apk-path", + "input: path to apk which will have its resources overlaid", + &target_apk_path) + .MandatoryOption("--overlay-apk-path", + "input: path to apk which contains the new resource values", + &overlay_apk_path) + .MandatoryOption("--idmap-path", "output: path to where to write idmap file", + &idmap_path); + if (!opts.Parse(args, out_error)) { + return false; + } + + const std::unique_ptr target_apk = ApkAssets::Load(target_apk_path); + if (!target_apk) { + out_error << "error: failed to load apk " << target_apk_path << std::endl; + return false; + } + + const std::unique_ptr overlay_apk = ApkAssets::Load(overlay_apk_path); + if (!overlay_apk) { + out_error << "error: failed to load apk " << overlay_apk_path << std::endl; + return false; + } + + const std::unique_ptr idmap = + Idmap::FromApkAssets(target_apk_path, *target_apk, overlay_apk_path, *overlay_apk, out_error); + if (!idmap) { + return false; + } + + umask(0133); // u=rw,g=r,o=r + std::ofstream fout(idmap_path); + if (fout.fail()) { + out_error << "failed to open idmap path " << idmap_path << std::endl; + return false; + } + BinaryStreamVisitor visitor(fout); + idmap->accept(&visitor); + fout.close(); + if (fout.fail()) { + out_error << "failed to write to idmap path " << idmap_path << std::endl; + return false; + } + + return true; +} diff --git a/cmds/idmap2/idmap2/Dump.cpp b/cmds/idmap2/idmap2/Dump.cpp new file mode 100644 index 0000000000000..c8cdcfa6d3dc7 --- /dev/null +++ b/cmds/idmap2/idmap2/Dump.cpp @@ -0,0 +1,60 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include +#include +#include +#include + +#include "idmap2/CommandLineOptions.h" +#include "idmap2/Idmap.h" +#include "idmap2/PrettyPrintVisitor.h" +#include "idmap2/RawPrintVisitor.h" + +using android::idmap2::CommandLineOptions; +using android::idmap2::Idmap; +using android::idmap2::PrettyPrintVisitor; +using android::idmap2::RawPrintVisitor; + +bool Dump(const std::vector& args, std::ostream& out_error) { + std::string idmap_path; + bool verbose; + + const CommandLineOptions opts = + CommandLineOptions("idmap2 dump") + .MandatoryOption("--idmap-path", "input: path to idmap file to pretty-print", &idmap_path) + .OptionalFlag("--verbose", "annotate every byte of the idmap", &verbose); + if (!opts.Parse(args, out_error)) { + return false; + } + std::ifstream fin(idmap_path); + const std::unique_ptr idmap = Idmap::FromBinaryStream(fin, out_error); + fin.close(); + if (!idmap) { + return false; + } + + if (verbose) { + RawPrintVisitor visitor(std::cout); + idmap->accept(&visitor); + } else { + PrettyPrintVisitor visitor(std::cout); + idmap->accept(&visitor); + } + + return true; +} diff --git a/cmds/idmap2/idmap2/Lookup.cpp b/cmds/idmap2/idmap2/Lookup.cpp new file mode 100644 index 0000000000000..1191e6a27b079 --- /dev/null +++ b/cmds/idmap2/idmap2/Lookup.cpp @@ -0,0 +1,249 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "android-base/macros.h" +#include "android-base/stringprintf.h" +#include "androidfw/ApkAssets.h" +#include "androidfw/AssetManager2.h" +#include "androidfw/ConfigDescription.h" +#include "androidfw/ResourceUtils.h" +#include "androidfw/StringPiece.h" +#include "androidfw/Util.h" +#include "utils/String16.h" +#include "utils/String8.h" + +#include "idmap2/CommandLineOptions.h" +#include "idmap2/Idmap.h" +#include "idmap2/Xml.h" +#include "idmap2/ZipFile.h" + +using android::ApkAssets; +using android::ApkAssetsCookie; +using android::AssetManager2; +using android::ConfigDescription; +using android::is_valid_resid; +using android::kInvalidCookie; +using android::Res_value; +using android::ResStringPool; +using android::ResTable_config; +using android::String16; +using android::String8; +using android::StringPiece16; +using android::base::StringPrintf; +using android::idmap2::CommandLineOptions; +using android::idmap2::IdmapHeader; +using android::idmap2::ResourceId; +using android::idmap2::Xml; +using android::idmap2::ZipFile; +using android::util::Utf16ToUtf8; + +namespace { +std::pair WARN_UNUSED ParseResReference(const AssetManager2& am, + const std::string& res, + const std::string& fallback_package) { + // first, try to parse as a hex number + char* endptr = nullptr; + ResourceId resid; + resid = strtol(res.c_str(), &endptr, 16); + if (*endptr == '\0') { + return std::make_pair(true, resid); + } + + // next, try to parse as a package:type/name string + resid = am.GetResourceId(res, "", fallback_package); + if (is_valid_resid(resid)) { + return std::make_pair(true, resid); + } + + // end of the road: res could not be parsed + return std::make_pair(false, 0); +} + +std::pair WARN_UNUSED GetValue(const AssetManager2& am, ResourceId resid) { + Res_value value; + ResTable_config config; + uint32_t flags; + ApkAssetsCookie cookie = am.GetResource(resid, false, 0, &value, &config, &flags); + if (cookie == kInvalidCookie) { + return std::make_pair(false, ""); + } + + std::string out; + + // TODO(martenkongstad): use optional parameter GetResource(..., std::string* + // stacktrace = NULL) instead + out.append(StringPrintf("cookie=%d ", cookie)); + + out.append("config='"); + out.append(config.toString().c_str()); + out.append("' value="); + + switch (value.dataType) { + case Res_value::TYPE_INT_DEC: + out.append(StringPrintf("%d", value.data)); + break; + case Res_value::TYPE_INT_HEX: + out.append(StringPrintf("0x%08x", value.data)); + break; + case Res_value::TYPE_INT_BOOLEAN: + out.append(value.data != 0 ? "true" : "false"); + break; + case Res_value::TYPE_STRING: { + const ResStringPool* pool = am.GetStringPoolForCookie(cookie); + size_t len; + if (pool->isUTF8()) { + const char* str = pool->string8At(value.data, &len); + out.append(str, len); + } else { + const char16_t* str16 = pool->stringAt(value.data, &len); + out += Utf16ToUtf8(StringPiece16(str16, len)); + } + } break; + default: + out.append(StringPrintf("dataType=0x%02x data=0x%08x", value.dataType, value.data)); + break; + } + return std::make_pair(true, out); +} + +std::pair GetTargetPackageNameFromManifest(const std::string& apk_path) { + const auto zip = ZipFile::Open(apk_path); + if (!zip) { + return std::make_pair(false, ""); + } + const auto entry = zip->Uncompress("AndroidManifest.xml"); + if (!entry) { + return std::make_pair(false, ""); + } + const auto xml = Xml::Create(entry->buf, entry->size); + if (!xml) { + return std::make_pair(false, ""); + } + const auto tag = xml->FindTag("overlay"); + if (!tag) { + return std::make_pair(false, ""); + } + const auto iter = tag->find("targetPackage"); + if (iter == tag->end()) { + return std::make_pair(false, ""); + } + return std::make_pair(true, iter->second); +} +} // namespace + +bool Lookup(const std::vector& args, std::ostream& out_error) { + std::vector idmap_paths; + std::string config_str, resid_str; + const CommandLineOptions opts = + CommandLineOptions("idmap2 lookup") + .MandatoryOption("--idmap-path", "input: path to idmap file to load", &idmap_paths) + .MandatoryOption("--config", "configuration to use", &config_str) + .MandatoryOption("--resid", + "Resource ID (in the target package; '0xpptteeee' or " + "'[package:]type/name') to look up", + &resid_str); + + if (!opts.Parse(args, out_error)) { + return false; + } + + ConfigDescription config; + if (!ConfigDescription::Parse(config_str, &config)) { + out_error << "error: failed to parse config" << std::endl; + return false; + } + + std::vector> apk_assets; + std::string target_path; + std::string target_package_name; + for (size_t i = 0; i < idmap_paths.size(); i++) { + const auto& idmap_path = idmap_paths[i]; + std::fstream fin(idmap_path); + auto idmap_header = IdmapHeader::FromBinaryStream(fin); + fin.close(); + if (!idmap_header) { + out_error << "error: failed to read idmap from " << idmap_path << std::endl; + return false; + } + + if (i == 0) { + target_path = idmap_header->GetTargetPath().to_string(); + auto target_apk = ApkAssets::Load(target_path); + if (!target_apk) { + out_error << "error: failed to read target apk from " << target_path << std::endl; + return false; + } + apk_assets.push_back(std::move(target_apk)); + + bool lookup_ok; + std::tie(lookup_ok, target_package_name) = + GetTargetPackageNameFromManifest(idmap_header->GetOverlayPath().to_string()); + if (!lookup_ok) { + out_error << "error: failed to parse android:targetPackage from overlay manifest" + << std::endl; + return false; + } + } else if (target_path != idmap_header->GetTargetPath()) { + out_error << "error: different target APKs (expected target APK " << target_path << " but " + << idmap_path << " has target APK " << idmap_header->GetTargetPath() << ")" + << std::endl; + return false; + } + + auto overlay_apk = ApkAssets::LoadOverlay(idmap_path); + if (!overlay_apk) { + out_error << "error: failed to read overlay apk from " << idmap_header->GetOverlayPath() + << std::endl; + return false; + } + apk_assets.push_back(std::move(overlay_apk)); + } + + // AssetManager2::SetApkAssets requires raw ApkAssets pointers, not unique_ptrs + std::vector raw_pointer_apk_assets; + std::transform(apk_assets.cbegin(), apk_assets.cend(), std::back_inserter(raw_pointer_apk_assets), + [](const auto& p) -> const ApkAssets* { return p.get(); }); + AssetManager2 am; + am.SetApkAssets(raw_pointer_apk_assets); + am.SetConfiguration(config); + + ResourceId resid; + bool lookup_ok; + std::tie(lookup_ok, resid) = ParseResReference(am, resid_str, target_package_name); + if (!lookup_ok) { + out_error << "error: failed to parse resource ID" << std::endl; + return false; + } + + std::string value; + std::tie(lookup_ok, value) = GetValue(am, resid); + if (!lookup_ok) { + out_error << StringPrintf("error: resource 0x%08x not found", resid) << std::endl; + return false; + } + std::cout << value << std::endl; + + return true; +} diff --git a/cmds/idmap2/idmap2/Main.cpp b/cmds/idmap2/idmap2/Main.cpp new file mode 100644 index 0000000000000..5d9ea778915a8 --- /dev/null +++ b/cmds/idmap2/idmap2/Main.cpp @@ -0,0 +1,67 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include // EXIT_{FAILURE,SUCCESS} +#include +#include +#include +#include +#include +#include +#include + +#include "idmap2/CommandLineOptions.h" + +#include "Commands.h" + +using android::idmap2::CommandLineOptions; + +typedef std::map&, std::ostream&)>> + NameToFunctionMap; + +static void PrintUsage(const NameToFunctionMap& commands, std::ostream& out) { + out << "usage: idmap2 ["; + for (auto iter = commands.cbegin(); iter != commands.cend(); iter++) { + if (iter != commands.cbegin()) { + out << "|"; + } + out << iter->first; + } + out << "]" << std::endl; +} + +int main(int argc, char** argv) { + const NameToFunctionMap commands = { + {"create", Create}, {"dump", Dump}, {"lookup", Lookup}, {"scan", Scan}, {"verify", Verify}, + }; + if (argc <= 1) { + PrintUsage(commands, std::cerr); + return EXIT_FAILURE; + } + const std::unique_ptr> args = + CommandLineOptions::ConvertArgvToVector(argc - 1, const_cast(argv + 1)); + if (!args) { + std::cerr << "error: failed to parse command line options" << std::endl; + return EXIT_FAILURE; + } + const auto iter = commands.find(argv[1]); + if (iter == commands.end()) { + std::cerr << argv[1] << ": command not found" << std::endl; + PrintUsage(commands, std::cerr); + return EXIT_FAILURE; + } + return iter->second(*args, std::cerr) ? EXIT_SUCCESS : EXIT_FAILURE; +} diff --git a/cmds/idmap2/idmap2/Scan.cpp b/cmds/idmap2/idmap2/Scan.cpp new file mode 100644 index 0000000000000..33c274e7fd355 --- /dev/null +++ b/cmds/idmap2/idmap2/Scan.cpp @@ -0,0 +1,159 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "idmap2/CommandLineOptions.h" +#include "idmap2/FileUtils.h" +#include "idmap2/Idmap.h" +#include "idmap2/Xml.h" +#include "idmap2/ZipFile.h" + +#include "Commands.h" + +using android::idmap2::CommandLineOptions; +using android::idmap2::Idmap; +using android::idmap2::MemoryChunk; +using android::idmap2::Xml; +using android::idmap2::ZipFile; +using android::idmap2::utils::FindFiles; + +namespace { +std::unique_ptr> FindApkFiles(const std::vector& dirs, + bool recursive, std::ostream& out_error) { + const auto predicate = [](unsigned char type, const std::string& path) -> bool { + static constexpr size_t kExtLen = 4; // strlen(".apk") + return type == DT_REG && path.size() > kExtLen && + !path.compare(path.size() - kExtLen, kExtLen, ".apk"); + }; + // pass apk paths through a set to filter out duplicates + std::set paths; + for (const auto& dir : dirs) { + const auto apk_paths = FindFiles(dir, recursive, predicate); + if (!apk_paths) { + out_error << "error: failed to open directory " << dir << std::endl; + return nullptr; + } + paths.insert(apk_paths->cbegin(), apk_paths->cend()); + } + return std::unique_ptr>( + new std::vector(paths.cbegin(), paths.cend())); +} +} // namespace + +bool Scan(const std::vector& args, std::ostream& out_error) { + std::vector input_directories; + std::string target_package_name, target_apk_path, output_directory; + bool recursive = false; + + const CommandLineOptions opts = + CommandLineOptions("idmap2 scan") + .MandatoryOption("--input-directory", "directory containing overlay apks to scan", + &input_directories) + .OptionalFlag("--recursive", "also scan subfolders of overlay-directory", &recursive) + .MandatoryOption("--target-package-name", "package name of target package", + &target_package_name) + .MandatoryOption("--target-apk-path", "path to target apk", &target_apk_path) + .MandatoryOption("--output-directory", + "directory in which to write artifacts (idmap files and overlays.list)", + &output_directory); + if (!opts.Parse(args, out_error)) { + return false; + } + + const auto apk_paths = FindApkFiles(input_directories, recursive, out_error); + if (!apk_paths) { + return false; + } + + std::vector interesting_apks; + for (const std::string& path : *apk_paths) { + std::unique_ptr zip = ZipFile::Open(path); + if (!zip) { + out_error << "error: failed to open " << path << " as a zip file" << std::endl; + return false; + } + + std::unique_ptr entry = zip->Uncompress("AndroidManifest.xml"); + if (!entry) { + out_error << "error: failed to uncompress AndroidManifest.xml from " << path << std::endl; + return false; + } + + std::unique_ptr xml = Xml::Create(entry->buf, entry->size); + if (!xml) { + out_error << "error: failed to parse AndroidManifest.xml from " << path << std::endl; + continue; + } + + const auto tag = xml->FindTag("overlay"); + if (!tag) { + continue; + } + + auto iter = tag->find("isStatic"); + if (iter == tag->end() || std::stoul(iter->second) == 0u) { + continue; + } + + iter = tag->find("targetPackage"); + if (iter == tag->end() || iter->second != target_package_name) { + continue; + } + + iter = tag->find("priority"); + if (iter == tag->end()) { + continue; + } + + const int priority = std::stoi(iter->second); + if (priority < 0) { + continue; + } + + interesting_apks.insert( + std::lower_bound(interesting_apks.begin(), interesting_apks.end(), path), path); + } + + std::stringstream stream; + for (auto iter = interesting_apks.cbegin(); iter != interesting_apks.cend(); ++iter) { + const std::string idmap_path = Idmap::CanonicalIdmapPathFor(output_directory, *iter); + if (!Verify(std::vector({"--idmap-path", idmap_path}), out_error) && + !Create(std::vector({ + "--target-apk-path", + target_apk_path, + "--overlay-apk-path", + *iter, + "--idmap-path", + idmap_path, + }), + out_error)) { + return false; + } + stream << idmap_path << std::endl; + } + + std::cout << stream.str(); + + return true; +} diff --git a/cmds/idmap2/idmap2/Verify.cpp b/cmds/idmap2/idmap2/Verify.cpp new file mode 100644 index 0000000000000..b5fa438b5b9f8 --- /dev/null +++ b/cmds/idmap2/idmap2/Verify.cpp @@ -0,0 +1,46 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include +#include +#include + +#include "idmap2/CommandLineOptions.h" +#include "idmap2/Idmap.h" + +using android::idmap2::CommandLineOptions; +using android::idmap2::IdmapHeader; + +bool Verify(const std::vector& args, std::ostream& out_error) { + std::string idmap_path; + const CommandLineOptions opts = + CommandLineOptions("idmap2 verify") + .MandatoryOption("--idmap-path", "input: path to idmap file to verify", &idmap_path); + if (!opts.Parse(args, out_error)) { + return false; + } + + std::ifstream fin(idmap_path); + const std::unique_ptr header = IdmapHeader::FromBinaryStream(fin); + fin.close(); + if (!header) { + out_error << "error: failed to parse idmap header" << std::endl; + return false; + } + + return header->IsUpToDate(out_error); +} diff --git a/cmds/idmap2/idmap2d/Idmap2Service.cpp b/cmds/idmap2/idmap2d/Idmap2Service.cpp new file mode 100644 index 0000000000000..cf72cb94da2c4 --- /dev/null +++ b/cmds/idmap2/idmap2d/Idmap2Service.cpp @@ -0,0 +1,138 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include // umask +#include // umask +#include + +#include +#include +#include +#include +#include +#include + +#include "android-base/macros.h" +#include "utils/String8.h" +#include "utils/Trace.h" + +#include "idmap2/BinaryStreamVisitor.h" +#include "idmap2/FileUtils.h" +#include "idmap2/Idmap.h" + +#include "idmap2d/Idmap2Service.h" + +using android::binder::Status; +using android::idmap2::BinaryStreamVisitor; +using android::idmap2::Idmap; +using android::idmap2::IdmapHeader; + +namespace { + +static constexpr const char* kIdmapCacheDir = "/data/resource-cache"; + +Status ok() { + return Status::ok(); +} + +Status error(const std::string& msg) { + LOG(ERROR) << msg; + return Status::fromExceptionCode(Status::EX_NONE, msg.c_str()); +} + +} // namespace + +namespace android { +namespace os { + +Status Idmap2Service::getIdmapPath(const std::string& overlay_apk_path, + int32_t user_id ATTRIBUTE_UNUSED, std::string* _aidl_return) { + assert(_aidl_return); + *_aidl_return = Idmap::CanonicalIdmapPathFor(kIdmapCacheDir, overlay_apk_path); + return ok(); +} + +Status Idmap2Service::removeIdmap(const std::string& overlay_apk_path, + int32_t user_id ATTRIBUTE_UNUSED, bool* _aidl_return) { + assert(_aidl_return); + const std::string idmap_path = Idmap::CanonicalIdmapPathFor(kIdmapCacheDir, overlay_apk_path); + if (unlink(idmap_path.c_str()) == 0) { + *_aidl_return = true; + return ok(); + } else { + *_aidl_return = false; + return error("failed to unlink " + idmap_path + ": " + strerror(errno)); + } +} + +Status Idmap2Service::createIdmap(const std::string& target_apk_path, + const std::string& overlay_apk_path, int32_t user_id, + std::unique_ptr* _aidl_return) { + assert(_aidl_return); + std::stringstream trace; + trace << __FUNCTION__ << " " << target_apk_path << " " << overlay_apk_path << " " + << std::to_string(user_id); + ATRACE_NAME(trace.str().c_str()); + std::cout << trace.str() << std::endl; + + _aidl_return->reset(nullptr); + + const std::string idmap_path = Idmap::CanonicalIdmapPathFor(kIdmapCacheDir, overlay_apk_path); + std::ifstream fin(idmap_path); + const std::unique_ptr header = IdmapHeader::FromBinaryStream(fin); + fin.close(); + // do not reuse error stream from IsUpToDate below, or error messages will be + // polluted with irrelevant data + std::stringstream dev_null; + if (header && header->IsUpToDate(dev_null)) { + return ok(); + } + + const std::unique_ptr target_apk = ApkAssets::Load(target_apk_path); + if (!target_apk) { + return error("failed to load apk " + target_apk_path); + } + + const std::unique_ptr overlay_apk = ApkAssets::Load(overlay_apk_path); + if (!overlay_apk) { + return error("failed to load apk " + overlay_apk_path); + } + + std::stringstream err; + const std::unique_ptr idmap = + Idmap::FromApkAssets(target_apk_path, *target_apk, overlay_apk_path, *overlay_apk, err); + if (!idmap) { + return error(err.str()); + } + + umask(0133); // u=rw,g=r,o=r + std::ofstream fout(idmap_path); + if (fout.fail()) { + return error("failed to open idmap path " + idmap_path); + } + BinaryStreamVisitor visitor(fout); + idmap->accept(&visitor); + fout.close(); + if (fout.fail()) { + return error("failed to write to idmap path " + idmap_path); + } + + _aidl_return->reset(new std::string(idmap_path)); + return ok(); +} + +} // namespace os +} // namespace android diff --git a/cmds/idmap2/idmap2d/Idmap2Service.h b/cmds/idmap2/idmap2d/Idmap2Service.h new file mode 100644 index 0000000000000..2b32042d6aa3b --- /dev/null +++ b/cmds/idmap2/idmap2d/Idmap2Service.h @@ -0,0 +1,49 @@ +/* + * 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 IDMAP2_IDMAP2D_IDMAP2SERVICE_H_ +#define IDMAP2_IDMAP2D_IDMAP2SERVICE_H_ + +#include +#include + +#include +#include + +#include "android/os/BnIdmap2.h" + +namespace android { +namespace os { +class Idmap2Service : public BinderService, public BnIdmap2 { + public: + static char const* getServiceName() { + return "idmap"; + } + + binder::Status getIdmapPath(const std::string& overlay_apk_path, int32_t user_id, + std::string* _aidl_return); + + binder::Status removeIdmap(const std::string& overlay_apk_path, int32_t user_id, + bool* _aidl_return); + + binder::Status createIdmap(const std::string& target_apk_path, + const std::string& overlay_apk_path, int32_t user_id, + std::unique_ptr* _aidl_return); +}; +} // namespace os +} // namespace android + +#endif // IDMAP2_IDMAP2D_IDMAP2SERVICE_H_ diff --git a/cmds/idmap2/idmap2d/Main.cpp b/cmds/idmap2/idmap2d/Main.cpp new file mode 100644 index 0000000000000..d64a87b8ee157 --- /dev/null +++ b/cmds/idmap2/idmap2d/Main.cpp @@ -0,0 +1,50 @@ +/* + * 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 ATRACE_TAG ATRACE_TAG_RESOURCES + +#include +#include +#include + +#include // EXIT_{FAILURE,SUCCESS} + +#include +#include + +#include "android-base/macros.h" + +#include "Idmap2Service.h" + +using android::BinderService; +using android::IPCThreadState; +using android::ProcessState; +using android::sp; +using android::status_t; +using android::os::Idmap2Service; + +int main(int argc ATTRIBUTE_UNUSED, char** argv ATTRIBUTE_UNUSED) { + IPCThreadState::self()->disableBackgroundScheduling(true); + status_t ret = BinderService::publish(); + if (ret != android::OK) { + return EXIT_FAILURE; + } + sp ps(ProcessState::self()); + ps->startThreadPool(); + ps->giveThreadPoolName(); + IPCThreadState::self()->joinThreadPool(); + return EXIT_SUCCESS; +} diff --git a/cmds/idmap2/idmap2d/aidl/android/os/IIdmap2.aidl b/cmds/idmap2/idmap2d/aidl/android/os/IIdmap2.aidl new file mode 100644 index 0000000000000..5d196101a7a6a --- /dev/null +++ b/cmds/idmap2/idmap2d/aidl/android/os/IIdmap2.aidl @@ -0,0 +1,27 @@ +/* + * 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. + */ + +package android.os; + +/** + * @hide + */ +interface IIdmap2 { + @utf8InCpp String getIdmapPath(@utf8InCpp String overlayApkPath, int userId); + boolean removeIdmap(@utf8InCpp String overlayApkPath, int userId); + @nullable @utf8InCpp String createIdmap(@utf8InCpp String targetApkPath, + @utf8InCpp String overlayApkPath, int userId); +} diff --git a/cmds/idmap2/include/idmap2/BinaryStreamVisitor.h b/cmds/idmap2/include/idmap2/BinaryStreamVisitor.h new file mode 100644 index 0000000000000..2368aeadbc9f3 --- /dev/null +++ b/cmds/idmap2/include/idmap2/BinaryStreamVisitor.h @@ -0,0 +1,49 @@ +/* + * 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 IDMAP2_INCLUDE_IDMAP2_BINARYSTREAMVISITOR_H_ +#define IDMAP2_INCLUDE_IDMAP2_BINARYSTREAMVISITOR_H_ + +#include +#include +#include + +#include "idmap2/Idmap.h" + +namespace android { +namespace idmap2 { + +class BinaryStreamVisitor : public Visitor { + public: + explicit BinaryStreamVisitor(std::ostream& stream) : stream_(stream) { + } + virtual void visit(const Idmap& idmap); + virtual void visit(const IdmapHeader& header); + virtual void visit(const IdmapData& data); + virtual void visit(const IdmapData::Header& header); + virtual void visit(const IdmapData::TypeEntry& type_entry); + + private: + void Write16(uint16_t value); + void Write32(uint32_t value); + void WriteString(const StringPiece& value); + std::ostream& stream_; +}; + +} // namespace idmap2 +} // namespace android + +#endif // IDMAP2_INCLUDE_IDMAP2_BINARYSTREAMVISITOR_H_ diff --git a/cmds/idmap2/include/idmap2/CommandLineOptions.h b/cmds/idmap2/include/idmap2/CommandLineOptions.h new file mode 100644 index 0000000000000..f3aa68b8d1a23 --- /dev/null +++ b/cmds/idmap2/include/idmap2/CommandLineOptions.h @@ -0,0 +1,71 @@ +/* + * 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 IDMAP2_INCLUDE_IDMAP2_COMMANDLINEOPTIONS_H_ +#define IDMAP2_INCLUDE_IDMAP2_COMMANDLINEOPTIONS_H_ + +#include +#include +#include +#include +#include + +namespace android { +namespace idmap2 { + +/* + * Utility class to convert a command line, including options (--path foo.txt), + * into data structures (options.path = "foo.txt"). + */ +class CommandLineOptions { + public: + static std::unique_ptr> ConvertArgvToVector(int argc, const char** argv); + + explicit CommandLineOptions(const std::string& name) : name_(name) { + } + + CommandLineOptions& OptionalFlag(const std::string& name, const std::string& description, + bool* value); + CommandLineOptions& MandatoryOption(const std::string& name, const std::string& description, + std::string* value); + CommandLineOptions& MandatoryOption(const std::string& name, const std::string& description, + std::vector* value); + CommandLineOptions& OptionalOption(const std::string& name, const std::string& description, + std::string* value); + bool Parse(const std::vector& argv, std::ostream& outError) const; + void Usage(std::ostream& out) const; + + private: + struct Option { + std::string name; + std::string description; + std::function action; + enum { + COUNT_OPTIONAL, + COUNT_EXACTLY_ONCE, + COUNT_ONCE_OR_MORE, + } count; + bool argument; + }; + + mutable std::vector