diff options
63 files changed, 5144 insertions, 0 deletions
diff --git a/cmds/idmap2/.clang-format b/cmds/idmap2/.clang-format new file mode 100644 index 000000000000..c91502a257f3 --- /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 000000000000..5a6c813fd202 --- /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 000000000000..5147f4e6cb4c --- /dev/null +++ b/cmds/idmap2/AndroidTest.xml @@ -0,0 +1,26 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- 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. +--> +<configuration description="Config for idmap2_tests"> + <target_preparer class="com.android.tradefed.targetprep.PushFilePreparer"> + <option name="cleanup" value="true" /> + <option name="push" value="idmap2_tests->/data/local/tmp/idmap2_tests" /> + </target_preparer> + <option name="test-suite-tag" value="idmap2_tests" /> + <test class="com.android.tradefed.testtype.GTest" > + <option name="native-test-device-path" value="/data/local/tmp" /> + <option name="module-name" value="idmap2_tests" /> + </test> +</configuration> diff --git a/cmds/idmap2/CPPLINT.cfg b/cmds/idmap2/CPPLINT.cfg new file mode 100644 index 000000000000..9dc6b4a77380 --- /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 000000000000..23ec5ab0d1f3 --- /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 000000000000..26ccf038cba2 --- /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 000000000000..dcc69b30743d --- /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 <string> +#include <vector> + +bool Create(const std::vector<std::string>& args, std::ostream& out_error); +bool Dump(const std::vector<std::string>& args, std::ostream& out_error); +bool Lookup(const std::vector<std::string>& args, std::ostream& out_error); +bool Scan(const std::vector<std::string>& args, std::ostream& out_error); +bool Verify(const std::vector<std::string>& 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 000000000000..291eaeb9c211 --- /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 <sys/stat.h> // umask +#include <sys/types.h> // umask +#include <fstream> +#include <memory> +#include <ostream> +#include <sstream> +#include <string> +#include <vector> + +#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<std::string>& 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<const ApkAssets> 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<const ApkAssets> 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<const Idmap> 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 000000000000..c8cdcfa6d3dc --- /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 <fstream> +#include <iostream> +#include <memory> +#include <string> +#include <vector> + +#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<std::string>& 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<const Idmap> 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 000000000000..1191e6a27b07 --- /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 <algorithm> +#include <fstream> +#include <iterator> +#include <memory> +#include <ostream> +#include <string> +#include <utility> +#include <vector> + +#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<bool, ResourceId> 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<bool, std::string> 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<bool, std::string> 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<std::string>& args, std::ostream& out_error) { + std::vector<std::string> 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<std::unique_ptr<const ApkAssets>> 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<const ApkAssets*> 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 000000000000..5d9ea778915a --- /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 <cstdlib> // EXIT_{FAILURE,SUCCESS} +#include <functional> +#include <iostream> +#include <map> +#include <memory> +#include <ostream> +#include <string> +#include <vector> + +#include "idmap2/CommandLineOptions.h" + +#include "Commands.h" + +using android::idmap2::CommandLineOptions; + +typedef std::map<std::string, std::function<int(const std::vector<std::string>&, 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<std::vector<std::string>> args = + CommandLineOptions::ConvertArgvToVector(argc - 1, const_cast<const char**>(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 000000000000..33c274e7fd35 --- /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 <dirent.h> +#include <fstream> +#include <memory> +#include <ostream> +#include <set> +#include <sstream> +#include <string> +#include <vector> + +#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<std::vector<std::string>> FindApkFiles(const std::vector<std::string>& 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<std::string> 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<std::vector<std::string>>( + new std::vector<std::string>(paths.cbegin(), paths.cend())); +} +} // namespace + +bool Scan(const std::vector<std::string>& args, std::ostream& out_error) { + std::vector<std::string> 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<std::string> interesting_apks; + for (const std::string& path : *apk_paths) { + std::unique_ptr<const ZipFile> zip = ZipFile::Open(path); + if (!zip) { + out_error << "error: failed to open " << path << " as a zip file" << std::endl; + return false; + } + + std::unique_ptr<const MemoryChunk> entry = zip->Uncompress("AndroidManifest.xml"); + if (!entry) { + out_error << "error: failed to uncompress AndroidManifest.xml from " << path << std::endl; + return false; + } + + std::unique_ptr<const Xml> 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<std::string>({"--idmap-path", idmap_path}), out_error) && + !Create(std::vector<std::string>({ + "--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 000000000000..b5fa438b5b9f --- /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 <fstream> +#include <memory> +#include <string> +#include <vector> + +#include "idmap2/CommandLineOptions.h" +#include "idmap2/Idmap.h" + +using android::idmap2::CommandLineOptions; +using android::idmap2::IdmapHeader; + +bool Verify(const std::vector<std::string>& 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<const IdmapHeader> 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 000000000000..cf72cb94da2c --- /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 <sys/stat.h> // umask +#include <sys/types.h> // umask +#include <unistd.h> + +#include <cerrno> +#include <cstring> +#include <fstream> +#include <memory> +#include <ostream> +#include <string> + +#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<std::string>* _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<const IdmapHeader> 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<const ApkAssets> target_apk = ApkAssets::Load(target_apk_path); + if (!target_apk) { + return error("failed to load apk " + target_apk_path); + } + + const std::unique_ptr<const ApkAssets> 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<const Idmap> 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 000000000000..2b32042d6aa3 --- /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 <android-base/unique_fd.h> +#include <binder/BinderService.h> + +#include <memory> +#include <string> + +#include "android/os/BnIdmap2.h" + +namespace android { +namespace os { +class Idmap2Service : public BinderService<Idmap2Service>, 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<std::string>* _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 000000000000..d64a87b8ee15 --- /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 <binder/BinderService.h> +#include <binder/IPCThreadState.h> +#include <binder/ProcessState.h> + +#include <cstdlib> // EXIT_{FAILURE,SUCCESS} + +#include <iostream> +#include <sstream> + +#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<Idmap2Service>::publish(); + if (ret != android::OK) { + return EXIT_FAILURE; + } + sp<ProcessState> 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 000000000000..5d196101a7a6 --- /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 000000000000..2368aeadbc9f --- /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 <cstdint> +#include <iostream> +#include <string> + +#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 000000000000..f3aa68b8d1a2 --- /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 <functional> +#include <memory> +#include <ostream> +#include <string> +#include <vector> + +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<std::vector<std::string>> 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<std::string>* value); + CommandLineOptions& OptionalOption(const std::string& name, const std::string& description, + std::string* value); + bool Parse(const std::vector<std::string>& argv, std::ostream& outError) const; + void Usage(std::ostream& out) const; + + private: + struct Option { + std::string name; + std::string description; + std::function<void(const std::string& value)> action; + enum { + COUNT_OPTIONAL, + COUNT_EXACTLY_ONCE, + COUNT_ONCE_OR_MORE, + } count; + bool argument; + }; + + mutable std::vector<Option> options_; + std::string name_; +}; + +} // namespace idmap2 +} // namespace android + +#endif // IDMAP2_INCLUDE_IDMAP2_COMMANDLINEOPTIONS_H_ diff --git a/cmds/idmap2/include/idmap2/FileUtils.h b/cmds/idmap2/include/idmap2/FileUtils.h new file mode 100644 index 000000000000..05c6d31d395d --- /dev/null +++ b/cmds/idmap2/include/idmap2/FileUtils.h @@ -0,0 +1,41 @@ +/* + * 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_FILEUTILS_H_ +#define IDMAP2_INCLUDE_IDMAP2_FILEUTILS_H_ + +#include <functional> +#include <memory> +#include <string> +#include <vector> + +namespace android { +namespace idmap2 { +namespace utils { +typedef std::function<bool(unsigned char type /* DT_* from dirent.h */, const std::string& path)> + FindFilesPredicate; +std::unique_ptr<std::vector<std::string>> FindFiles(const std::string& root, bool recurse, + const FindFilesPredicate& predicate); + +std::unique_ptr<std::string> ReadFile(int fd); + +std::unique_ptr<std::string> ReadFile(const std::string& path); + +} // namespace utils +} // namespace idmap2 +} // namespace android + +#endif // IDMAP2_INCLUDE_IDMAP2_FILEUTILS_H_ diff --git a/cmds/idmap2/include/idmap2/Idmap.h b/cmds/idmap2/include/idmap2/Idmap.h new file mode 100644 index 000000000000..837b7c5264d5 --- /dev/null +++ b/cmds/idmap2/include/idmap2/Idmap.h @@ -0,0 +1,277 @@ +/* + * 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. + */ + +/* + * # idmap file format (current version) + * + * idmap := header data* + * header := magic version target_crc overlay_crc target_path overlay_path + * data := data_header data_block* + * data_header := target_package_id types_count + * data_block := target_type overlay_type entry_count entry_offset entry* + * overlay_path := string + * target_path := string + * entry := <uint32_t> + * entry_count := <uint16_t> + * entry_offset := <uint16_t> + * magic := <uint32_t> + * overlay_crc := <uint32_t> + * overlay_type := <uint16_t> + * string := <uint8_t>[256] + * target_crc := <uint32_t> + * target_package_id := <uint16_t> + * target_type := <uint16_t> + * types_count := <uint16_t> + * version := <uint32_t> + * + * + * # idmap file format changelog + * ## v1 + * - Identical to idmap v1. + */ + +#ifndef IDMAP2_INCLUDE_IDMAP2_IDMAP_H_ +#define IDMAP2_INCLUDE_IDMAP2_IDMAP_H_ + +#include <iostream> +#include <memory> +#include <string> +#include <vector> + +#include "android-base/macros.h" + +#include "androidfw/ApkAssets.h" +#include "androidfw/ResourceTypes.h" +#include "androidfw/StringPiece.h" + +namespace android { +namespace idmap2 { + +class Idmap; +class Visitor; + +// use typedefs to let the compiler warn us about implicit casts +typedef uint32_t ResourceId; // 0xpptteeee +typedef uint8_t PackageId; // pp in 0xpptteeee +typedef uint8_t TypeId; // tt in 0xpptteeee +typedef uint16_t EntryId; // eeee in 0xpptteeee + +static constexpr const ResourceId kPadding = 0xffffffffu; + +static constexpr const EntryId kNoEntry = 0xffffu; + +// magic number: all idmap files start with this +static constexpr const uint32_t kIdmapMagic = android::kIdmapMagic; + +// current version of the idmap binary format; must be incremented when the format is changed +static constexpr const uint32_t kIdmapCurrentVersion = android::kIdmapCurrentVersion; + +// strings in the idmap are encoded char arrays of length 'kIdmapStringLength' (including mandatory +// terminating null) +static constexpr const size_t kIdmapStringLength = 256; + +class IdmapHeader { + public: + static std::unique_ptr<const IdmapHeader> FromBinaryStream(std::istream& stream); + + inline uint32_t GetMagic() const { + return magic_; + } + + inline uint32_t GetVersion() const { + return version_; + } + + inline uint32_t GetTargetCrc() const { + return target_crc_; + } + + inline uint32_t GetOverlayCrc() const { + return overlay_crc_; + } + + inline StringPiece GetTargetPath() const { + return StringPiece(target_path_); + } + + inline StringPiece GetOverlayPath() const { + return StringPiece(overlay_path_); + } + + // Invariant: anytime the idmap data encoding is changed, the idmap version + // field *must* be incremented. Because of this, we know that if the idmap + // header is up-to-date the entire file is up-to-date. + bool IsUpToDate(std::ostream& out_error) const; + + void accept(Visitor* v) const; + + private: + IdmapHeader() { + } + + uint32_t magic_; + uint32_t version_; + uint32_t target_crc_; + uint32_t overlay_crc_; + char target_path_[kIdmapStringLength]; + char overlay_path_[kIdmapStringLength]; + + friend Idmap; + DISALLOW_COPY_AND_ASSIGN(IdmapHeader); +}; + +class IdmapData { + public: + class Header { + public: + static std::unique_ptr<const Header> FromBinaryStream(std::istream& stream); + + inline PackageId GetTargetPackageId() const { + return target_package_id_; + } + + inline uint16_t GetTypeCount() const { + return type_count_; + } + + void accept(Visitor* v) const; + + private: + Header() { + } + + PackageId target_package_id_; + uint16_t type_count_; + + friend Idmap; + DISALLOW_COPY_AND_ASSIGN(Header); + }; + + class TypeEntry { + public: + static std::unique_ptr<const TypeEntry> FromBinaryStream(std::istream& stream); + + inline TypeId GetTargetTypeId() const { + return target_type_id_; + } + + inline TypeId GetOverlayTypeId() const { + return overlay_type_id_; + } + + inline uint16_t GetEntryCount() const { + return entries_.size(); + } + + inline uint16_t GetEntryOffset() const { + return entry_offset_; + } + + inline EntryId GetEntry(size_t i) const { + return i < entries_.size() ? entries_[i] : 0xffffu; + } + + void accept(Visitor* v) const; + + private: + TypeEntry() { + } + + TypeId target_type_id_; + TypeId overlay_type_id_; + uint16_t entry_offset_; + std::vector<EntryId> entries_; + + friend Idmap; + DISALLOW_COPY_AND_ASSIGN(TypeEntry); + }; + + static std::unique_ptr<const IdmapData> FromBinaryStream(std::istream& stream); + + inline const std::unique_ptr<const Header>& GetHeader() const { + return header_; + } + + inline const std::vector<std::unique_ptr<const TypeEntry>>& GetTypeEntries() const { + return type_entries_; + } + + void accept(Visitor* v) const; + + private: + IdmapData() { + } + + std::unique_ptr<const Header> header_; + std::vector<std::unique_ptr<const TypeEntry>> type_entries_; + + friend Idmap; + DISALLOW_COPY_AND_ASSIGN(IdmapData); +}; + +class Idmap { + public: + static std::string CanonicalIdmapPathFor(const std::string& absolute_dir, + const std::string& absolute_apk_path); + + static std::unique_ptr<const Idmap> FromBinaryStream(std::istream& stream, + std::ostream& out_error); + + // In the current version of idmap, the first package in each resources.arsc + // file is used; change this in the next version of idmap to use a named + // package instead; also update FromApkAssets to take additional parameters: + // the target and overlay package names + static std::unique_ptr<const Idmap> FromApkAssets(const std::string& target_apk_path, + const ApkAssets& target_apk_assets, + const std::string& overlay_apk_path, + const ApkAssets& overlay_apk_assets, + std::ostream& out_error); + + inline const std::unique_ptr<const IdmapHeader>& GetHeader() const { + return header_; + } + + inline const std::vector<std::unique_ptr<const IdmapData>>& GetData() const { + return data_; + } + + void accept(Visitor* v) const; + + private: + Idmap() { + } + + std::unique_ptr<const IdmapHeader> header_; + std::vector<std::unique_ptr<const IdmapData>> data_; + + DISALLOW_COPY_AND_ASSIGN(Idmap); +}; + +class Visitor { + public: + virtual ~Visitor() { + } + virtual void visit(const Idmap& idmap) = 0; + virtual void visit(const IdmapHeader& header) = 0; + virtual void visit(const IdmapData& data) = 0; + virtual void visit(const IdmapData::Header& header) = 0; + virtual void visit(const IdmapData::TypeEntry& type_entry) = 0; +}; + +} // namespace idmap2 +} // namespace android + +#endif // IDMAP2_INCLUDE_IDMAP2_IDMAP_H_ diff --git a/cmds/idmap2/include/idmap2/PrettyPrintVisitor.h b/cmds/idmap2/include/idmap2/PrettyPrintVisitor.h new file mode 100644 index 000000000000..c388f4b94251 --- /dev/null +++ b/cmds/idmap2/include/idmap2/PrettyPrintVisitor.h @@ -0,0 +1,53 @@ +/* + * 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_PRETTYPRINTVISITOR_H_ +#define IDMAP2_INCLUDE_IDMAP2_PRETTYPRINTVISITOR_H_ + +#include <iostream> +#include <memory> + +#include "androidfw/AssetManager2.h" + +#include "idmap2/Idmap.h" + +namespace android { + +class ApkAssets; + +namespace idmap2 { + +class PrettyPrintVisitor : public Visitor { + public: + explicit PrettyPrintVisitor(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: + std::ostream& stream_; + std::unique_ptr<const ApkAssets> target_apk_; + AssetManager2 target_am_; + PackageId last_seen_package_id_; +}; + +} // namespace idmap2 +} // namespace android + +#endif // IDMAP2_INCLUDE_IDMAP2_PRETTYPRINTVISITOR_H_ diff --git a/cmds/idmap2/include/idmap2/RawPrintVisitor.h b/cmds/idmap2/include/idmap2/RawPrintVisitor.h new file mode 100644 index 000000000000..7e33b3b06fc3 --- /dev/null +++ b/cmds/idmap2/include/idmap2/RawPrintVisitor.h @@ -0,0 +1,59 @@ +/* + * 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_RAWPRINTVISITOR_H_ +#define IDMAP2_INCLUDE_IDMAP2_RAWPRINTVISITOR_H_ + +#include <iostream> +#include <memory> +#include <string> + +#include "androidfw/AssetManager2.h" + +#include "idmap2/Idmap.h" + +namespace android { + +class ApkAssets; + +namespace idmap2 { + +class RawPrintVisitor : public Visitor { + public: + explicit RawPrintVisitor(std::ostream& stream) : stream_(stream), offset_(0) { + } + 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 print(uint16_t value, const char* fmt, ...); + void print(uint32_t value, const char* fmt, ...); + void print(const std::string& value, const char* fmt, ...); + + std::ostream& stream_; + std::unique_ptr<const ApkAssets> target_apk_; + AssetManager2 target_am_; + size_t offset_; + PackageId last_seen_package_id_; +}; + +} // namespace idmap2 +} // namespace android + +#endif // IDMAP2_INCLUDE_IDMAP2_RAWPRINTVISITOR_H_ diff --git a/cmds/idmap2/include/idmap2/ResourceUtils.h b/cmds/idmap2/include/idmap2/ResourceUtils.h new file mode 100644 index 000000000000..88a835b6439c --- /dev/null +++ b/cmds/idmap2/include/idmap2/ResourceUtils.h @@ -0,0 +1,39 @@ +/* + * 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_RESOURCEUTILS_H_ +#define IDMAP2_INCLUDE_IDMAP2_RESOURCEUTILS_H_ + +#include <string> +#include <utility> + +#include "android-base/macros.h" +#include "androidfw/AssetManager2.h" + +#include "idmap2/Idmap.h" + +namespace android { +namespace idmap2 { +namespace utils { + +std::pair<bool, std::string> WARN_UNUSED ResToTypeEntryName(const AssetManager2& am, + ResourceId resid); + +} // namespace utils +} // namespace idmap2 +} // namespace android + +#endif // IDMAP2_INCLUDE_IDMAP2_RESOURCEUTILS_H_ diff --git a/cmds/idmap2/include/idmap2/Xml.h b/cmds/idmap2/include/idmap2/Xml.h new file mode 100644 index 000000000000..9ab5ec454750 --- /dev/null +++ b/cmds/idmap2/include/idmap2/Xml.h @@ -0,0 +1,51 @@ +/* + * 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_XML_H_ +#define IDMAP2_INCLUDE_IDMAP2_XML_H_ + +#include <map> +#include <memory> +#include <string> + +#include "android-base/macros.h" +#include "androidfw/ResourceTypes.h" +#include "utils/String16.h" + +namespace android { +namespace idmap2 { + +class Xml { + public: + static std::unique_ptr<const Xml> Create(const uint8_t* data, size_t size, bool copyData = false); + + std::unique_ptr<std::map<std::string, std::string>> FindTag(const std::string& name) const; + + ~Xml(); + + private: + Xml() { + } + + mutable ResXMLTree xml_; + + DISALLOW_COPY_AND_ASSIGN(Xml); +}; + +} // namespace idmap2 +} // namespace android + +#endif // IDMAP2_INCLUDE_IDMAP2_XML_H_ diff --git a/cmds/idmap2/include/idmap2/ZipFile.h b/cmds/idmap2/include/idmap2/ZipFile.h new file mode 100644 index 000000000000..328bd367adfc --- /dev/null +++ b/cmds/idmap2/include/idmap2/ZipFile.h @@ -0,0 +1,62 @@ +/* + * 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_ZIPFILE_H_ +#define IDMAP2_INCLUDE_IDMAP2_ZIPFILE_H_ + +#include <memory> +#include <string> +#include <utility> + +#include "android-base/macros.h" +#include "ziparchive/zip_archive.h" + +namespace android { +namespace idmap2 { + +struct MemoryChunk { + size_t size; + uint8_t buf[0]; + + static std::unique_ptr<MemoryChunk> Allocate(size_t size); + + private: + MemoryChunk() { + } +}; + +class ZipFile { + public: + static std::unique_ptr<const ZipFile> Open(const std::string& path); + + std::unique_ptr<const MemoryChunk> Uncompress(const std::string& entryPath) const; + std::pair<bool, uint32_t> Crc(const std::string& entryPath) const; + + ~ZipFile(); + + private: + explicit ZipFile(const ::ZipArchiveHandle handle) : handle_(handle) { + } + + const ::ZipArchiveHandle handle_; + + DISALLOW_COPY_AND_ASSIGN(ZipFile); +}; + +} // namespace idmap2 +} // namespace android + +#endif // IDMAP2_INCLUDE_IDMAP2_ZIPFILE_H_ diff --git a/cmds/idmap2/libidmap2/BinaryStreamVisitor.cpp b/cmds/idmap2/libidmap2/BinaryStreamVisitor.cpp new file mode 100644 index 000000000000..29969a23250b --- /dev/null +++ b/cmds/idmap2/libidmap2/BinaryStreamVisitor.cpp @@ -0,0 +1,81 @@ +/* + * 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 <algorithm> +#include <cstring> +#include <string> + +#include "android-base/macros.h" + +#include "idmap2/BinaryStreamVisitor.h" + +namespace android { +namespace idmap2 { + +void BinaryStreamVisitor::Write16(uint16_t value) { + uint16_t x = htodl(value); + stream_.write(reinterpret_cast<char*>(&x), sizeof(uint16_t)); +} + +void BinaryStreamVisitor::Write32(uint32_t value) { + uint32_t x = htodl(value); + stream_.write(reinterpret_cast<char*>(&x), sizeof(uint32_t)); +} + +void BinaryStreamVisitor::WriteString(const StringPiece& value) { + char buf[kIdmapStringLength]; + memset(buf, 0, sizeof(buf)); + memcpy(buf, value.data(), std::min(value.size(), sizeof(buf))); + stream_.write(buf, sizeof(buf)); +} + +void BinaryStreamVisitor::visit(const Idmap& idmap ATTRIBUTE_UNUSED) { + // nothing to do +} + +void BinaryStreamVisitor::visit(const IdmapHeader& header) { + Write32(header.GetMagic()); + Write32(header.GetVersion()); + Write32(header.GetTargetCrc()); + Write32(header.GetOverlayCrc()); + WriteString(header.GetTargetPath()); + WriteString(header.GetOverlayPath()); +} + +void BinaryStreamVisitor::visit(const IdmapData& data ATTRIBUTE_UNUSED) { + // nothing to do +} + +void BinaryStreamVisitor::visit(const IdmapData::Header& header) { + Write16(header.GetTargetPackageId()); + Write16(header.GetTypeCount()); +} + +void BinaryStreamVisitor::visit(const IdmapData::TypeEntry& te) { + const uint16_t entryCount = te.GetEntryCount(); + + Write16(te.GetTargetTypeId()); + Write16(te.GetOverlayTypeId()); + Write16(entryCount); + Write16(te.GetEntryOffset()); + for (uint16_t i = 0; i < entryCount; i++) { + EntryId entry_id = te.GetEntry(i); + Write32(entry_id != kNoEntry ? static_cast<uint32_t>(entry_id) : kPadding); + } +} + +} // namespace idmap2 +} // namespace android diff --git a/cmds/idmap2/libidmap2/CommandLineOptions.cpp b/cmds/idmap2/libidmap2/CommandLineOptions.cpp new file mode 100644 index 000000000000..28c3797226e1 --- /dev/null +++ b/cmds/idmap2/libidmap2/CommandLineOptions.cpp @@ -0,0 +1,163 @@ +/* + * 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 <algorithm> +#include <iomanip> +#include <iostream> +#include <memory> +#include <set> +#include <string> +#include <vector> + +#include "android-base/macros.h" + +#include "idmap2/CommandLineOptions.h" + +namespace android { +namespace idmap2 { + +std::unique_ptr<std::vector<std::string>> CommandLineOptions::ConvertArgvToVector( + int argc, const char** argv) { + return std::unique_ptr<std::vector<std::string>>( + new std::vector<std::string>(argv + 1, argv + argc)); +} + +CommandLineOptions& CommandLineOptions::OptionalFlag(const std::string& name, + const std::string& description, bool* value) { + assert(value != nullptr); + auto func = [value](const std::string& arg ATTRIBUTE_UNUSED) -> void { *value = true; }; + options_.push_back(Option{name, description, func, Option::COUNT_OPTIONAL, false}); + return *this; +} + +CommandLineOptions& CommandLineOptions::MandatoryOption(const std::string& name, + const std::string& description, + std::string* value) { + assert(value != nullptr); + auto func = [value](const std::string& arg) -> void { *value = arg; }; + options_.push_back(Option{name, description, func, Option::COUNT_EXACTLY_ONCE, true}); + return *this; +} + +CommandLineOptions& CommandLineOptions::MandatoryOption(const std::string& name, + const std::string& description, + std::vector<std::string>* value) { + assert(value != nullptr); + auto func = [value](const std::string& arg) -> void { value->push_back(arg); }; + options_.push_back(Option{name, description, func, Option::COUNT_ONCE_OR_MORE, true}); + return *this; +} + +CommandLineOptions& CommandLineOptions::OptionalOption(const std::string& name, + const std::string& description, + std::string* value) { + assert(value != nullptr); + auto func = [value](const std::string& arg) -> void { *value = arg; }; + options_.push_back(Option{name, description, func, Option::COUNT_OPTIONAL, true}); + return *this; +} + +bool CommandLineOptions::Parse(const std::vector<std::string>& argv, std::ostream& outError) const { + const auto pivot = std::partition(options_.begin(), options_.end(), [](const Option& opt) { + return opt.count != Option::COUNT_OPTIONAL; + }); + std::set<std::string> mandatory_opts; + std::transform(options_.begin(), pivot, std::inserter(mandatory_opts, mandatory_opts.end()), + [](const Option& opt) -> std::string { return opt.name; }); + + const size_t argv_size = argv.size(); + for (size_t i = 0; i < argv_size; i++) { + const std::string arg = argv[i]; + if ("--help" == arg || "-h" == arg) { + Usage(outError); + return false; + } + bool match = false; + for (const Option& opt : options_) { + if (opt.name == arg) { + match = true; + + if (opt.argument) { + i++; + if (i >= argv_size) { + outError << "error: " << opt.name << ": missing argument" << std::endl; + Usage(outError); + return false; + } + } + opt.action(argv[i]); + mandatory_opts.erase(opt.name); + break; + } + } + if (!match) { + outError << "error: " << arg << ": unknown option" << std::endl; + Usage(outError); + return false; + } + } + + if (!mandatory_opts.empty()) { + for (auto iter = mandatory_opts.cbegin(); iter != mandatory_opts.cend(); ++iter) { + outError << "error: " << *iter << ": missing mandatory option" << std::endl; + } + Usage(outError); + return false; + } + return true; +} + +void CommandLineOptions::Usage(std::ostream& out) const { + size_t maxLength = 0; + out << "usage: " << name_; + for (const Option& opt : options_) { + const bool mandatory = opt.count != Option::COUNT_OPTIONAL; + out << " "; + if (!mandatory) { + out << "["; + } + if (opt.argument) { + out << opt.name << " arg"; + maxLength = std::max(maxLength, opt.name.size() + 4); + } else { + out << opt.name; + maxLength = std::max(maxLength, opt.name.size()); + } + if (!mandatory) { + out << "]"; + } + if (opt.count == Option::COUNT_ONCE_OR_MORE) { + out << " [" << opt.name << " arg [..]]"; + } + } + out << std::endl << std::endl; + for (const Option& opt : options_) { + out << std::left << std::setw(maxLength); + if (opt.argument) { + out << (opt.name + " arg"); + } else { + out << opt.name; + } + out << " " << opt.description; + if (opt.count == Option::COUNT_ONCE_OR_MORE) { + out << " (can be provided multiple times)"; + } + out << std::endl; + } +} + +} // namespace idmap2 +} // namespace android diff --git a/cmds/idmap2/libidmap2/FileUtils.cpp b/cmds/idmap2/libidmap2/FileUtils.cpp new file mode 100644 index 000000000000..4ac4c04d0bfc --- /dev/null +++ b/cmds/idmap2/libidmap2/FileUtils.cpp @@ -0,0 +1,82 @@ +/* + * 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 <dirent.h> +#include <errno.h> +#include <sys/types.h> +#include <unistd.h> + +#include <fstream> +#include <memory> +#include <string> +#include <utility> +#include <vector> + +#include "idmap2/FileUtils.h" + +namespace android { +namespace idmap2 { +namespace utils { + +std::unique_ptr<std::vector<std::string>> FindFiles(const std::string& root, bool recurse, + const FindFilesPredicate& predicate) { + DIR* dir = opendir(root.c_str()); + if (!dir) { + return nullptr; + } + std::unique_ptr<std::vector<std::string>> vector(new std::vector<std::string>()); + struct dirent* dirent; + while ((dirent = readdir(dir))) { + const std::string path = root + "/" + dirent->d_name; + if (predicate(dirent->d_type, path)) { + vector->push_back(path); + } + if (recurse && dirent->d_type == DT_DIR && strcmp(dirent->d_name, ".") != 0 && + strcmp(dirent->d_name, "..") != 0) { + auto sub_vector = FindFiles(path, recurse, predicate); + if (!sub_vector) { + closedir(dir); + return nullptr; + } + vector->insert(vector->end(), sub_vector->begin(), sub_vector->end()); + } + } + closedir(dir); + + return vector; +} + +std::unique_ptr<std::string> ReadFile(const std::string& path) { + std::unique_ptr<std::string> str(new std::string()); + std::ifstream fin(path); + str->append({std::istreambuf_iterator<char>(fin), std::istreambuf_iterator<char>()}); + fin.close(); + return str; +} + +std::unique_ptr<std::string> ReadFile(int fd) { + std::unique_ptr<std::string> str(new std::string()); + char buf[1024]; + ssize_t r; + while ((r = read(fd, buf, sizeof(buf))) > 0) { + str->append(buf, r); + } + return r == 0 ? std::move(str) : nullptr; +} + +} // namespace utils +} // namespace idmap2 +} // namespace android diff --git a/cmds/idmap2/libidmap2/Idmap.cpp b/cmds/idmap2/libidmap2/Idmap.cpp new file mode 100644 index 000000000000..5a47e301b66c --- /dev/null +++ b/cmds/idmap2/libidmap2/Idmap.cpp @@ -0,0 +1,443 @@ +/* + * 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 <algorithm> +#include <iostream> +#include <iterator> +#include <limits> +#include <map> +#include <memory> +#include <set> +#include <string> +#include <utility> +#include <vector> + +#include "android-base/macros.h" +#include "android-base/stringprintf.h" +#include "androidfw/AssetManager2.h" +#include "utils/String16.h" +#include "utils/String8.h" + +#include "idmap2/Idmap.h" +#include "idmap2/ResourceUtils.h" +#include "idmap2/ZipFile.h" + +namespace android { +namespace idmap2 { + +#define EXTRACT_TYPE(resid) ((0x00ff0000 & (resid)) >> 16) + +#define EXTRACT_ENTRY(resid) (0x0000ffff & (resid)) + +struct MatchingResources { + void Add(ResourceId target_resid, ResourceId overlay_resid) { + TypeId target_typeid = EXTRACT_TYPE(target_resid); + if (map.find(target_typeid) == map.end()) { + map.emplace(target_typeid, std::set<std::pair<ResourceId, ResourceId>>()); + } + map[target_typeid].insert(std::make_pair(target_resid, overlay_resid)); + } + + // target type id -> set { pair { overlay entry id, overlay entry id } } + std::map<TypeId, std::set<std::pair<ResourceId, ResourceId>>> map; +}; + +static bool WARN_UNUSED Read16(std::istream& stream, uint16_t* out) { + uint16_t value; + if (stream.read(reinterpret_cast<char*>(&value), sizeof(uint16_t))) { + *out = dtohl(value); + return true; + } + return false; +} + +static bool WARN_UNUSED Read32(std::istream& stream, uint32_t* out) { + uint32_t value; + if (stream.read(reinterpret_cast<char*>(&value), sizeof(uint32_t))) { + *out = dtohl(value); + return true; + } + return false; +} + +// a string is encoded as a kIdmapStringLength char array; the array is always null-terminated +static bool WARN_UNUSED ReadString(std::istream& stream, char out[kIdmapStringLength]) { + char buf[kIdmapStringLength]; + memset(buf, 0, sizeof(buf)); + if (!stream.read(buf, sizeof(buf))) { + return false; + } + if (buf[sizeof(buf) - 1] != '\0') { + return false; + } + memcpy(out, buf, sizeof(buf)); + return true; +} + +static ResourceId NameToResid(const AssetManager2& am, const std::string& name) { + return am.GetResourceId(name); +} + +// TODO(martenkongstad): scan for package name instead of assuming package at index 0 +// +// idmap version 0x01 naively assumes that the package to use is always the first ResTable_package +// in the resources.arsc blob. In most cases, there is only a single ResTable_package anyway, so +// this assumption tends to work out. That said, the correct thing to do is to scan +// resources.arsc for a package with a given name as read from the package manifest instead of +// relying on a hard-coded index. This however requires storing the package name in the idmap +// header, which in turn requires incrementing the idmap version. Because the initial version of +// idmap2 is compatible with idmap, this will have to wait for now. +static const LoadedPackage* GetPackageAtIndex0(const LoadedArsc& loaded_arsc) { + const std::vector<std::unique_ptr<const LoadedPackage>>& packages = loaded_arsc.GetPackages(); + if (packages.empty()) { + return nullptr; + } + int id = packages[0]->GetPackageId(); + return loaded_arsc.GetPackageById(id); +} + +std::unique_ptr<const IdmapHeader> IdmapHeader::FromBinaryStream(std::istream& stream) { + std::unique_ptr<IdmapHeader> idmap_header(new IdmapHeader()); + + if (!Read32(stream, &idmap_header->magic_) || !Read32(stream, &idmap_header->version_) || + !Read32(stream, &idmap_header->target_crc_) || !Read32(stream, &idmap_header->overlay_crc_) || + !ReadString(stream, idmap_header->target_path_) || + !ReadString(stream, idmap_header->overlay_path_)) { + return nullptr; + } + + return std::move(idmap_header); +} + +bool IdmapHeader::IsUpToDate(std::ostream& out_error) const { + if (magic_ != kIdmapMagic) { + out_error << base::StringPrintf("error: bad magic: actual 0x%08x, expected 0x%08x", magic_, + kIdmapMagic) + << std::endl; + return false; + } + + if (version_ != kIdmapCurrentVersion) { + out_error << base::StringPrintf("error: bad version: actual 0x%08x, expected 0x%08x", version_, + kIdmapCurrentVersion) + << std::endl; + return false; + } + + const std::unique_ptr<const ZipFile> target_zip = ZipFile::Open(target_path_); + if (!target_zip) { + out_error << "error: failed to open target " << target_path_ << std::endl; + return false; + } + + bool status; + uint32_t target_crc; + std::tie(status, target_crc) = target_zip->Crc("resources.arsc"); + if (!status) { + out_error << "error: failed to get target crc" << std::endl; + return false; + } + + if (target_crc_ != target_crc) { + out_error << base::StringPrintf( + "error: bad target crc: idmap version 0x%08x, file system version 0x%08x", + target_crc_, target_crc) + << std::endl; + return false; + } + + const std::unique_ptr<const ZipFile> overlay_zip = ZipFile::Open(overlay_path_); + if (!overlay_zip) { + out_error << "error: failed to open overlay " << overlay_path_ << std::endl; + return false; + } + + uint32_t overlay_crc; + std::tie(status, overlay_crc) = overlay_zip->Crc("resources.arsc"); + if (!status) { + out_error << "error: failed to get overlay crc" << std::endl; + return false; + } + + if (overlay_crc_ != overlay_crc) { + out_error << base::StringPrintf( + "error: bad overlay crc: idmap version 0x%08x, file system version 0x%08x", + overlay_crc_, overlay_crc) + << std::endl; + return false; + } + + return true; +} + +std::unique_ptr<const IdmapData::Header> IdmapData::Header::FromBinaryStream(std::istream& stream) { + std::unique_ptr<IdmapData::Header> idmap_data_header(new IdmapData::Header()); + + uint16_t target_package_id16; + if (!Read16(stream, &target_package_id16) || !Read16(stream, &idmap_data_header->type_count_)) { + return nullptr; + } + idmap_data_header->target_package_id_ = target_package_id16; + + return std::move(idmap_data_header); +} + +std::unique_ptr<const IdmapData::TypeEntry> IdmapData::TypeEntry::FromBinaryStream( + std::istream& stream) { + std::unique_ptr<IdmapData::TypeEntry> data(new IdmapData::TypeEntry()); + + uint16_t target_type16, overlay_type16, entry_count; + if (!Read16(stream, &target_type16) || !Read16(stream, &overlay_type16) || + !Read16(stream, &entry_count) || !Read16(stream, &data->entry_offset_)) { + return nullptr; + } + data->target_type_id_ = target_type16; + data->overlay_type_id_ = overlay_type16; + for (uint16_t i = 0; i < entry_count; i++) { + ResourceId resid; + if (!Read32(stream, &resid)) { + return nullptr; + } + data->entries_.push_back(resid); + } + + return std::move(data); +} + +std::unique_ptr<const IdmapData> IdmapData::FromBinaryStream(std::istream& stream) { + std::unique_ptr<IdmapData> data(new IdmapData()); + data->header_ = IdmapData::Header::FromBinaryStream(stream); + if (!data->header_) { + return nullptr; + } + for (size_t type_count = 0; type_count < data->header_->GetTypeCount(); type_count++) { + std::unique_ptr<const TypeEntry> type = IdmapData::TypeEntry::FromBinaryStream(stream); + if (!type) { + return nullptr; + } + data->type_entries_.push_back(std::move(type)); + } + return std::move(data); +} + +std::string Idmap::CanonicalIdmapPathFor(const std::string& absolute_dir, + const std::string& absolute_apk_path) { + assert(absolute_dir.size() > 0 && absolute_dir[0] == "/"); + assert(absolute_apk_path.size() > 0 && absolute_apk_path[0] == "/"); + std::string copy(++absolute_apk_path.cbegin(), absolute_apk_path.cend()); + replace(copy.begin(), copy.end(), '/', '@'); + return absolute_dir + "/" + copy + "@idmap"; +} + +std::unique_ptr<const Idmap> Idmap::FromBinaryStream(std::istream& stream, + std::ostream& out_error) { + std::unique_ptr<Idmap> idmap(new Idmap()); + + idmap->header_ = IdmapHeader::FromBinaryStream(stream); + if (!idmap->header_) { + out_error << "error: failed to parse idmap header" << std::endl; + return nullptr; + } + + // idmap version 0x01 does not specify the number of data blocks that follow + // the idmap header; assume exactly one data block + for (int i = 0; i < 1; i++) { + std::unique_ptr<const IdmapData> data = IdmapData::FromBinaryStream(stream); + if (!data) { + out_error << "error: failed to parse data block " << i << std::endl; + return nullptr; + } + idmap->data_.push_back(std::move(data)); + } + + return std::move(idmap); +} + +std::unique_ptr<const Idmap> Idmap::FromApkAssets(const std::string& target_apk_path, + const ApkAssets& target_apk_assets, + const std::string& overlay_apk_path, + const ApkAssets& overlay_apk_assets, + std::ostream& out_error) { + AssetManager2 target_asset_manager; + if (!target_asset_manager.SetApkAssets({&target_apk_assets}, true, false)) { + out_error << "error: failed to create target asset manager" << std::endl; + return nullptr; + } + + AssetManager2 overlay_asset_manager; + if (!overlay_asset_manager.SetApkAssets({&overlay_apk_assets}, true, false)) { + out_error << "error: failed to create overlay asset manager" << std::endl; + return nullptr; + } + + const LoadedArsc* target_arsc = target_apk_assets.GetLoadedArsc(); + if (!target_arsc) { + out_error << "error: failed to load target resources.arsc" << std::endl; + return nullptr; + } + + const LoadedArsc* overlay_arsc = overlay_apk_assets.GetLoadedArsc(); + if (!overlay_arsc) { + out_error << "error: failed to load overlay resources.arsc" << std::endl; + return nullptr; + } + + const LoadedPackage* target_pkg = GetPackageAtIndex0(*target_arsc); + if (!target_pkg) { + out_error << "error: failed to load target package from resources.arsc" << std::endl; + return nullptr; + } + + const LoadedPackage* overlay_pkg = GetPackageAtIndex0(*overlay_arsc); + if (!overlay_pkg) { + out_error << "error: failed to load overlay package from resources.arsc" << std::endl; + return nullptr; + } + + const std::unique_ptr<const ZipFile> target_zip = ZipFile::Open(target_apk_path); + if (!target_zip) { + out_error << "error: failed to open target as zip" << std::endl; + return nullptr; + } + + const std::unique_ptr<const ZipFile> overlay_zip = ZipFile::Open(overlay_apk_path); + if (!overlay_zip) { + out_error << "error: failed to open overlay as zip" << std::endl; + return nullptr; + } + + std::unique_ptr<IdmapHeader> header(new IdmapHeader()); + header->magic_ = kIdmapMagic; + header->version_ = kIdmapCurrentVersion; + bool crc_status; + std::tie(crc_status, header->target_crc_) = target_zip->Crc("resources.arsc"); + if (!crc_status) { + out_error << "error: failed to get zip crc for target" << std::endl; + return nullptr; + } + std::tie(crc_status, header->overlay_crc_) = overlay_zip->Crc("resources.arsc"); + if (!crc_status) { + out_error << "error: failed to get zip crc for overlay" << std::endl; + return nullptr; + } + + if (target_apk_path.size() > sizeof(header->target_path_)) { + out_error << "error: target apk path \"" << target_apk_path << "\" longer that maximum size " + << sizeof(header->target_path_) << std::endl; + return nullptr; + } + memset(header->target_path_, 0, sizeof(header->target_path_)); + memcpy(header->target_path_, target_apk_path.data(), target_apk_path.size()); + + if (overlay_apk_path.size() > sizeof(header->overlay_path_)) { + out_error << "error: overlay apk path \"" << overlay_apk_path << "\" longer that maximum size " + << sizeof(header->overlay_path_) << std::endl; + return nullptr; + } + memset(header->overlay_path_, 0, sizeof(header->overlay_path_)); + memcpy(header->overlay_path_, overlay_apk_path.data(), overlay_apk_path.size()); + + std::unique_ptr<Idmap> idmap(new Idmap()); + idmap->header_ = std::move(header); + + // find the resources that exist in both packages + MatchingResources matching_resources; + const auto end = overlay_pkg->end(); + for (auto iter = overlay_pkg->begin(); iter != end; ++iter) { + const ResourceId overlay_resid = *iter; + bool lookup_ok; + std::string name; + std::tie(lookup_ok, name) = utils::ResToTypeEntryName(overlay_asset_manager, overlay_resid); + if (!lookup_ok) { + continue; + } + // prepend "<package>:" to turn name into "<package>:<type>/<name>" + name = base::StringPrintf("%s:%s", target_pkg->GetPackageName().c_str(), name.c_str()); + const ResourceId target_resid = NameToResid(target_asset_manager, name); + if (target_resid == 0) { + continue; + } + matching_resources.Add(target_resid, overlay_resid); + } + + // encode idmap data + std::unique_ptr<IdmapData> data(new IdmapData()); + const auto types_end = matching_resources.map.cend(); + for (auto ti = matching_resources.map.cbegin(); ti != types_end; ++ti) { + auto ei = ti->second.cbegin(); + std::unique_ptr<IdmapData::TypeEntry> type(new IdmapData::TypeEntry()); + type->target_type_id_ = EXTRACT_TYPE(ei->first); + type->overlay_type_id_ = EXTRACT_TYPE(ei->second); + type->entry_offset_ = EXTRACT_ENTRY(ei->first); + EntryId last_target_entry = kNoEntry; + for (; ei != ti->second.cend(); ++ei) { + if (last_target_entry != kNoEntry) { + int count = EXTRACT_ENTRY(ei->first) - last_target_entry - 1; + type->entries_.insert(type->entries_.end(), count, kNoEntry); + } + type->entries_.push_back(EXTRACT_ENTRY(ei->second)); + last_target_entry = EXTRACT_ENTRY(ei->first); + } + data->type_entries_.push_back(std::move(type)); + } + + std::unique_ptr<IdmapData::Header> data_header(new IdmapData::Header()); + data_header->target_package_id_ = target_pkg->GetPackageId(); + data_header->type_count_ = data->type_entries_.size(); + data->header_ = std::move(data_header); + + idmap->data_.push_back(std::move(data)); + + return std::move(idmap); +} + +void IdmapHeader::accept(Visitor* v) const { + assert(v != nullptr); + v->visit(*this); +} + +void IdmapData::Header::accept(Visitor* v) const { + assert(v != nullptr); + v->visit(*this); +} + +void IdmapData::TypeEntry::accept(Visitor* v) const { + assert(v != nullptr); + v->visit(*this); +} + +void IdmapData::accept(Visitor* v) const { + assert(v != nullptr); + v->visit(*this); + header_->accept(v); + auto end = type_entries_.cend(); + for (auto iter = type_entries_.cbegin(); iter != end; ++iter) { + (*iter)->accept(v); + } +} + +void Idmap::accept(Visitor* v) const { + assert(v != nullptr); + v->visit(*this); + header_->accept(v); + auto end = data_.cend(); + for (auto iter = data_.cbegin(); iter != end; ++iter) { + (*iter)->accept(v); + } +} + +} // namespace idmap2 +} // namespace android diff --git a/cmds/idmap2/libidmap2/PrettyPrintVisitor.cpp b/cmds/idmap2/libidmap2/PrettyPrintVisitor.cpp new file mode 100644 index 000000000000..492e6f049d68 --- /dev/null +++ b/cmds/idmap2/libidmap2/PrettyPrintVisitor.cpp @@ -0,0 +1,78 @@ +/* + * 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 <string> +#include <utility> + +#include "android-base/macros.h" +#include "android-base/stringprintf.h" +#include "androidfw/ApkAssets.h" + +#include "idmap2/PrettyPrintVisitor.h" +#include "idmap2/ResourceUtils.h" + +namespace android { +namespace idmap2 { + +#define RESID(pkg, type, entry) (((pkg) << 24) | ((type) << 16) | (entry)) + +void PrettyPrintVisitor::visit(const Idmap& idmap ATTRIBUTE_UNUSED) { +} + +void PrettyPrintVisitor::visit(const IdmapHeader& header) { + stream_ << "target apk path : " << header.GetTargetPath() << std::endl + << "overlay apk path : " << header.GetOverlayPath() << std::endl; + + target_apk_ = ApkAssets::Load(header.GetTargetPath().to_string()); + if (target_apk_) { + target_am_.SetApkAssets({target_apk_.get()}); + } +} + +void PrettyPrintVisitor::visit(const IdmapData& data ATTRIBUTE_UNUSED) { +} + +void PrettyPrintVisitor::visit(const IdmapData::Header& header ATTRIBUTE_UNUSED) { + last_seen_package_id_ = header.GetTargetPackageId(); +} + +void PrettyPrintVisitor::visit(const IdmapData::TypeEntry& te) { + const bool target_package_loaded = !target_am_.GetApkAssets().empty(); + for (uint16_t i = 0; i < te.GetEntryCount(); i++) { + const EntryId entry = te.GetEntry(i); + if (entry == kNoEntry) { + continue; + } + + const ResourceId target_resid = + RESID(last_seen_package_id_, te.GetTargetTypeId(), te.GetEntryOffset() + i); + const ResourceId overlay_resid = RESID(last_seen_package_id_, te.GetOverlayTypeId(), entry); + + stream_ << base::StringPrintf("0x%08x -> 0x%08x", target_resid, overlay_resid); + if (target_package_loaded) { + bool lookup_ok; + std::string name; + std::tie(lookup_ok, name) = utils::ResToTypeEntryName(target_am_, target_resid); + if (lookup_ok) { + stream_ << " " << name; + } + } + stream_ << std::endl; + } +} + +} // namespace idmap2 +} // namespace android diff --git a/cmds/idmap2/libidmap2/RawPrintVisitor.cpp b/cmds/idmap2/libidmap2/RawPrintVisitor.cpp new file mode 100644 index 000000000000..57cfc8ef85b4 --- /dev/null +++ b/cmds/idmap2/libidmap2/RawPrintVisitor.cpp @@ -0,0 +1,131 @@ +/* + * 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 <cstdarg> +#include <string> +#include <utility> + +#include "android-base/macros.h" +#include "android-base/stringprintf.h" +#include "androidfw/ApkAssets.h" + +#include "idmap2/RawPrintVisitor.h" +#include "idmap2/ResourceUtils.h" + +using android::ApkAssets; + +namespace android { +namespace idmap2 { + +// verbatim copy fomr PrettyPrintVisitor.cpp, move to common utils +#define RESID(pkg, type, entry) (((pkg) << 24) | ((type) << 16) | (entry)) + +void RawPrintVisitor::visit(const Idmap& idmap ATTRIBUTE_UNUSED) { +} + +void RawPrintVisitor::visit(const IdmapHeader& header) { + print(header.GetMagic(), "magic"); + print(header.GetVersion(), "version"); + print(header.GetTargetCrc(), "target crc"); + print(header.GetOverlayCrc(), "overlay crc"); + print(header.GetTargetPath().to_string(), "target path"); + print(header.GetOverlayPath().to_string(), "overlay path"); + + target_apk_ = ApkAssets::Load(header.GetTargetPath().to_string()); + if (target_apk_) { + target_am_.SetApkAssets({target_apk_.get()}); + } +} + +void RawPrintVisitor::visit(const IdmapData& data ATTRIBUTE_UNUSED) { +} + +void RawPrintVisitor::visit(const IdmapData::Header& header) { + print(static_cast<uint16_t>(header.GetTargetPackageId()), "target package id"); + print(header.GetTypeCount(), "type count"); + last_seen_package_id_ = header.GetTargetPackageId(); +} + +void RawPrintVisitor::visit(const IdmapData::TypeEntry& te) { + const bool target_package_loaded = !target_am_.GetApkAssets().empty(); + + print(static_cast<uint16_t>(te.GetTargetTypeId()), "target type"); + print(static_cast<uint16_t>(te.GetOverlayTypeId()), "overlay type"); + print(static_cast<uint16_t>(te.GetEntryCount()), "entry count"); + print(static_cast<uint16_t>(te.GetEntryOffset()), "entry offset"); + + for (uint16_t i = 0; i < te.GetEntryCount(); i++) { + const EntryId entry = te.GetEntry(i); + if (entry == kNoEntry) { + print(kPadding, "no entry"); + } else { + const ResourceId target_resid = + RESID(last_seen_package_id_, te.GetTargetTypeId(), te.GetEntryOffset() + i); + const ResourceId overlay_resid = RESID(last_seen_package_id_, te.GetOverlayTypeId(), entry); + bool lookup_ok = false; + std::string name; + if (target_package_loaded) { + std::tie(lookup_ok, name) = utils::ResToTypeEntryName(target_am_, target_resid); + } + if (lookup_ok) { + print(static_cast<uint32_t>(entry), "0x%08x -> 0x%08x %s", target_resid, overlay_resid, + name.c_str()); + } else { + print(static_cast<uint32_t>(entry), "0x%08x -> 0x%08x", target_resid, overlay_resid); + } + } + } +} + +void RawPrintVisitor::print(uint16_t value, const char* fmt, ...) { + va_list ap; + va_start(ap, fmt); + std::string comment; + base::StringAppendV(&comment, fmt, ap); + va_end(ap); + + stream_ << base::StringPrintf("%08zx: %04x", offset_, value) << " " << comment << std::endl; + + offset_ += sizeof(uint16_t); +} + +void RawPrintVisitor::print(uint32_t value, const char* fmt, ...) { + va_list ap; + va_start(ap, fmt); + std::string comment; + base::StringAppendV(&comment, fmt, ap); + va_end(ap); + + stream_ << base::StringPrintf("%08zx: %08x", offset_, value) << " " << comment << std::endl; + + offset_ += sizeof(uint32_t); +} + +void RawPrintVisitor::print(const std::string& value, const char* fmt, ...) { + va_list ap; + va_start(ap, fmt); + std::string comment; + base::StringAppendV(&comment, fmt, ap); + va_end(ap); + + stream_ << base::StringPrintf("%08zx: ", offset_) << "........ " << comment << ": " << value + << std::endl; + + offset_ += kIdmapStringLength; +} + +} // namespace idmap2 +} // namespace android diff --git a/cmds/idmap2/libidmap2/ResourceUtils.cpp b/cmds/idmap2/libidmap2/ResourceUtils.cpp new file mode 100644 index 000000000000..e98f843931c8 --- /dev/null +++ b/cmds/idmap2/libidmap2/ResourceUtils.cpp @@ -0,0 +1,55 @@ +/* + * 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 <string> +#include <utility> + +#include "androidfw/StringPiece.h" +#include "androidfw/Util.h" + +#include "idmap2/ResourceUtils.h" + +using android::StringPiece16; +using android::util::Utf16ToUtf8; + +namespace android { +namespace idmap2 { +namespace utils { + +std::pair<bool, std::string> WARN_UNUSED ResToTypeEntryName(const AssetManager2& am, + ResourceId resid) { + AssetManager2::ResourceName name; + if (!am.GetResourceName(resid, &name)) { + return std::make_pair(false, ""); + } + std::string out; + if (name.type != nullptr) { + out.append(name.type, name.type_len); + } else { + out += Utf16ToUtf8(StringPiece16(name.type16, name.type_len)); + } + out.append("/"); + if (name.entry != nullptr) { + out.append(name.entry, name.entry_len); + } else { + out += Utf16ToUtf8(StringPiece16(name.entry16, name.entry_len)); + } + return std::make_pair(true, out); +} + +} // namespace utils +} // namespace idmap2 +} // namespace android diff --git a/cmds/idmap2/libidmap2/Xml.cpp b/cmds/idmap2/libidmap2/Xml.cpp new file mode 100644 index 000000000000..5543722ce4fe --- /dev/null +++ b/cmds/idmap2/libidmap2/Xml.cpp @@ -0,0 +1,82 @@ +/* + * 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 <map> +#include <memory> +#include <string> +#include <utility> + +#include "idmap2/Xml.h" + +namespace android { +namespace idmap2 { + +std::unique_ptr<const Xml> Xml::Create(const uint8_t* data, size_t size, bool copyData) { + std::unique_ptr<Xml> xml(new Xml()); + if (xml->xml_.setTo(data, size, copyData) != NO_ERROR) { + return nullptr; + } + return xml; +} + +std::unique_ptr<std::map<std::string, std::string>> Xml::FindTag(const std::string& name) const { + const String16 tag_to_find(name.c_str(), name.size()); + xml_.restart(); + ResXMLParser::event_code_t type; + do { + type = xml_.next(); + if (type == ResXMLParser::START_TAG) { + size_t len; + const String16 tag(xml_.getElementName(&len)); + if (tag == tag_to_find) { + std::unique_ptr<std::map<std::string, std::string>> map( + new std::map<std::string, std::string>()); + for (size_t i = 0; i < xml_.getAttributeCount(); i++) { + const String16 key16(xml_.getAttributeName(i, &len)); + std::string key = String8(key16).c_str(); + + std::string value; + switch (xml_.getAttributeDataType(i)) { + case Res_value::TYPE_STRING: { + const String16 value16(xml_.getAttributeStringValue(i, &len)); + value = String8(value16).c_str(); + } break; + case Res_value::TYPE_INT_DEC: + case Res_value::TYPE_INT_HEX: + case Res_value::TYPE_INT_BOOLEAN: { + Res_value resValue; + xml_.getAttributeValue(i, &resValue); + value = std::to_string(resValue.data); + } break; + default: + return nullptr; + } + + map->emplace(std::make_pair(key, value)); + } + return map; + } + } + } while (type != ResXMLParser::BAD_DOCUMENT && type != ResXMLParser::END_DOCUMENT); + return nullptr; +} + +Xml::~Xml() { + xml_.uninit(); +} + +} // namespace idmap2 +} // namespace android diff --git a/cmds/idmap2/libidmap2/ZipFile.cpp b/cmds/idmap2/libidmap2/ZipFile.cpp new file mode 100644 index 000000000000..3f2079a380d6 --- /dev/null +++ b/cmds/idmap2/libidmap2/ZipFile.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 <memory> +#include <string> +#include <utility> + +#include "idmap2/ZipFile.h" + +namespace android { +namespace idmap2 { + +std::unique_ptr<MemoryChunk> MemoryChunk::Allocate(size_t size) { + void* ptr = ::operator new(sizeof(MemoryChunk) + size); + std::unique_ptr<MemoryChunk> chunk(reinterpret_cast<MemoryChunk*>(ptr)); + chunk->size = size; + return chunk; +} + +std::unique_ptr<const ZipFile> ZipFile::Open(const std::string& path) { + ::ZipArchiveHandle handle; + int32_t status = ::OpenArchive(path.c_str(), &handle); + if (status != 0) { + return nullptr; + } + return std::unique_ptr<ZipFile>(new ZipFile(handle)); +} + +ZipFile::~ZipFile() { + ::CloseArchive(handle_); +} + +std::unique_ptr<const MemoryChunk> ZipFile::Uncompress(const std::string& entryPath) const { + ::ZipEntry entry; + int32_t status = ::FindEntry(handle_, ::ZipString(entryPath.c_str()), &entry); + if (status != 0) { + return nullptr; + } + std::unique_ptr<MemoryChunk> chunk = MemoryChunk::Allocate(entry.uncompressed_length); + status = ::ExtractToMemory(handle_, &entry, chunk->buf, chunk->size); + if (status != 0) { + return nullptr; + } + return chunk; +} + +std::pair<bool, uint32_t> ZipFile::Crc(const std::string& entryPath) const { + ::ZipEntry entry; + int32_t status = ::FindEntry(handle_, ::ZipString(entryPath.c_str()), &entry); + return std::make_pair(status == 0, entry.crc32); +} + +} // namespace idmap2 +} // namespace android diff --git a/cmds/idmap2/static-checks.sh b/cmds/idmap2/static-checks.sh new file mode 100755 index 000000000000..560ccb692bb1 --- /dev/null +++ b/cmds/idmap2/static-checks.sh @@ -0,0 +1,121 @@ +#!/bin/bash +# +# 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. +# + +function _log() +{ + echo -e "$*" >&2 +} + +function _eval() +{ + local label="$1" + local cmd="$2" + local red="\e[31m" + local green="\e[32m" + local reset="\e[0m" + + _log "${green}[ RUN ]${reset} ${label}" + local output="$(eval "$cmd")" + if [[ -z "${output}" ]]; then + _log "${green}[ OK ]${reset} ${label}" + return 0 + else + echo "${output}" + _log "${red}[ FAILED ]${reset} ${label}" + errors=$((errors + 1)) + return 1 + fi +} + +function _clang_format() +{ + local path + local errors=0 + + for path in $cpp_files; do + local output="$(clang-format -style=file "$path" | diff $path -)" + if [[ "$output" ]]; then + echo "$path" + echo "$output" + errors=1 + fi + done + return $errors +} + +function _bpfmt() +{ + local output="$(bpfmt -s -d $bp_files)" + if [[ "$output" ]]; then + echo "$output" + return 1 + fi + return 0 +} + +function _cpplint() +{ + local cpplint="${ANDROID_BUILD_TOP}/tools/repohooks/tools/cpplint.py" + $cpplint --quiet $cpp_files +} + +function _parse_args() +{ + local opts + + opts="$(getopt -o cfh --long check,fix,help -- "$@")" + if [[ $? -ne 0 ]]; then + exit 1 + fi + eval set -- "$opts" + while true; do + case "$1" in + -c|--check) opt_mode="check"; shift ;; + -f|--fix) opt_mode="fix"; shift ;; + -h|--help) opt_mode="help"; shift ;; + *) break ;; + esac + done +} + +errors=0 +script="$(readlink -f "$BASH_SOURCE")" +prefix="$(dirname "$script")" +cpp_files="$(find "$prefix" -name '*.cpp' -or -name '*.h')" +bp_files="$(find "$prefix" -name 'Android.bp')" +opt_mode="check" + +_parse_args "$@" +if [[ $opt_mode == "check" ]]; then + _eval "clang-format" "_clang_format" + _eval "bpfmt" "_bpfmt" + _eval "cpplint" "_cpplint" + exit $errors +elif [[ $opt_mode == "fix" ]]; then + clang-format -style=file -i $cpp_files + bpfmt -s -w $bp_files + exit 0 +elif [[ $opt_mode == "help" ]]; then + echo "Run static analysis tools such as clang-format and cpplint on the idmap2" + echo "module. Optionally fix some of the issues found (--fix). Intended to be run" + echo "before merging any changes." + echo + echo "usage: $(basename $script) [--check|--fix|--help]" + exit 0 +else + exit 1 +fi diff --git a/cmds/idmap2/tests/BinaryStreamVisitorTests.cpp b/cmds/idmap2/tests/BinaryStreamVisitorTests.cpp new file mode 100644 index 000000000000..8b552dcc1265 --- /dev/null +++ b/cmds/idmap2/tests/BinaryStreamVisitorTests.cpp @@ -0,0 +1,129 @@ +/* + * 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 <memory> +#include <sstream> +#include <string> + +#include "gmock/gmock.h" +#include "gtest/gtest.h" + +#include "androidfw/ApkAssets.h" +#include "androidfw/Idmap.h" + +#include "idmap2/BinaryStreamVisitor.h" +#include "idmap2/Idmap.h" + +#include "TestHelpers.h" + +using ::testing::IsNull; +using ::testing::NotNull; + +namespace android { +namespace idmap2 { + +TEST(BinaryStreamVisitorTests, CreateBinaryStreamViaBinaryStreamVisitor) { + std::string raw(reinterpret_cast<const char*>(idmap_raw_data), idmap_raw_data_len); + std::istringstream raw_stream(raw); + + std::stringstream error; + std::unique_ptr<const Idmap> idmap1 = Idmap::FromBinaryStream(raw_stream, error); + ASSERT_THAT(idmap1, NotNull()); + + std::stringstream stream; + BinaryStreamVisitor visitor(stream); + idmap1->accept(&visitor); + + std::unique_ptr<const Idmap> idmap2 = Idmap::FromBinaryStream(stream, error); + ASSERT_THAT(idmap2, NotNull()); + + ASSERT_EQ(idmap1->GetHeader()->GetTargetCrc(), idmap2->GetHeader()->GetTargetCrc()); + ASSERT_EQ(idmap1->GetHeader()->GetTargetPath(), idmap2->GetHeader()->GetTargetPath()); + ASSERT_EQ(idmap1->GetData().size(), 1u); + ASSERT_EQ(idmap1->GetData().size(), idmap2->GetData().size()); + + const auto& data1 = idmap1->GetData()[0]; + const auto& data2 = idmap2->GetData()[0]; + + ASSERT_EQ(data1->GetHeader()->GetTargetPackageId(), data2->GetHeader()->GetTargetPackageId()); + ASSERT_EQ(data1->GetTypeEntries().size(), 2u); + ASSERT_EQ(data1->GetTypeEntries().size(), data2->GetTypeEntries().size()); + ASSERT_EQ(data1->GetTypeEntries()[0]->GetEntry(0), data2->GetTypeEntries()[0]->GetEntry(0)); + ASSERT_EQ(data1->GetTypeEntries()[0]->GetEntry(1), data2->GetTypeEntries()[0]->GetEntry(1)); + ASSERT_EQ(data1->GetTypeEntries()[0]->GetEntry(2), data2->GetTypeEntries()[0]->GetEntry(2)); + ASSERT_EQ(data1->GetTypeEntries()[1]->GetEntry(0), data2->GetTypeEntries()[1]->GetEntry(0)); + ASSERT_EQ(data1->GetTypeEntries()[1]->GetEntry(1), data2->GetTypeEntries()[1]->GetEntry(1)); + ASSERT_EQ(data1->GetTypeEntries()[1]->GetEntry(2), data2->GetTypeEntries()[1]->GetEntry(2)); +} + +TEST(BinaryStreamVisitorTests, CreateIdmapFromApkAssetsInteropWithLoadedIdmap) { + const std::string target_apk_path(GetTestDataPath() + "/target/target.apk"); + std::unique_ptr<const ApkAssets> target_apk = ApkAssets::Load(target_apk_path); + ASSERT_THAT(target_apk, NotNull()); + + const std::string overlay_apk_path(GetTestDataPath() + "/overlay/overlay.apk"); + std::unique_ptr<const ApkAssets> overlay_apk = ApkAssets::Load(overlay_apk_path); + ASSERT_THAT(overlay_apk, NotNull()); + + std::stringstream error; + std::unique_ptr<const Idmap> idmap = + Idmap::FromApkAssets(target_apk_path, *target_apk, overlay_apk_path, *overlay_apk, error); + ASSERT_THAT(idmap, NotNull()); + + std::stringstream stream; + BinaryStreamVisitor visitor(stream); + idmap->accept(&visitor); + const std::string str = stream.str(); + const StringPiece data(str); + std::unique_ptr<const LoadedIdmap> loaded_idmap = LoadedIdmap::Load(data); + ASSERT_THAT(loaded_idmap, NotNull()); + ASSERT_EQ(loaded_idmap->TargetPackageId(), 0x7f); + + const IdmapEntry_header* header = loaded_idmap->GetEntryMapForType(0x01); + ASSERT_THAT(header, NotNull()); + + EntryId entry; + bool success = LoadedIdmap::Lookup(header, 0x0000, &entry); + ASSERT_TRUE(success); + ASSERT_EQ(entry, 0x0000); + + header = loaded_idmap->GetEntryMapForType(0x02); + ASSERT_THAT(header, NotNull()); + + success = LoadedIdmap::Lookup(header, 0x0002, &entry); + ASSERT_FALSE(success); + + success = LoadedIdmap::Lookup(header, 0x0003, &entry); + ASSERT_TRUE(success); + ASSERT_EQ(entry, 0x0000); + + success = LoadedIdmap::Lookup(header, 0x0004, &entry); + ASSERT_FALSE(success); + + success = LoadedIdmap::Lookup(header, 0x0005, &entry); + ASSERT_TRUE(success); + ASSERT_EQ(entry, 0x0001); + + success = LoadedIdmap::Lookup(header, 0x0006, &entry); + ASSERT_TRUE(success); + ASSERT_EQ(entry, 0x0002); + + success = LoadedIdmap::Lookup(header, 0x0007, &entry); + ASSERT_FALSE(success); +} + +} // namespace idmap2 +} // namespace android diff --git a/cmds/idmap2/tests/CommandLineOptionsTests.cpp b/cmds/idmap2/tests/CommandLineOptionsTests.cpp new file mode 100644 index 000000000000..b04b25660ee4 --- /dev/null +++ b/cmds/idmap2/tests/CommandLineOptionsTests.cpp @@ -0,0 +1,192 @@ +/* + * 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 <sys/mman.h> +#include <sys/stat.h> +#include <sys/types.h> +#include <unistd.h> + +#include <fstream> +#include <memory> +#include <sstream> +#include <string> +#include <vector> + +#include "gmock/gmock.h" +#include "gtest/gtest.h" + +#include "android-base/file.h" +#include "androidfw/ApkAssets.h" +#include "androidfw/Idmap.h" +#include "androidfw/LoadedArsc.h" + +#include "idmap2/CommandLineOptions.h" +#include "idmap2/Idmap.h" + +#include "TestHelpers.h" + +using ::testing::NotNull; + +namespace android { +namespace idmap2 { + +TEST(CommandLineOptionsTests, Flag) { + bool foo = true, bar = false; + + CommandLineOptions opts = + CommandLineOptions("test").OptionalFlag("--foo", "", &foo).OptionalFlag("--bar", "", &bar); + + std::ostream fakeStdErr(nullptr); + bool success = opts.Parse({"--foo", "--bar"}, fakeStdErr); + ASSERT_TRUE(success); + ASSERT_TRUE(foo); + ASSERT_TRUE(bar); + + foo = bar = false; + success = opts.Parse({"--foo"}, fakeStdErr); + ASSERT_TRUE(success); + ASSERT_TRUE(foo); + ASSERT_FALSE(bar); +} + +TEST(CommandLineOptionsTests, MandatoryOption) { + std::string foo, bar; + CommandLineOptions opts = CommandLineOptions("test") + .MandatoryOption("--foo", "", &foo) + .MandatoryOption("--bar", "", &bar); + std::ostream fakeStdErr(nullptr); + bool success = opts.Parse({"--foo", "FOO", "--bar", "BAR"}, fakeStdErr); + ASSERT_TRUE(success); + ASSERT_EQ(foo, "FOO"); + ASSERT_EQ(bar, "BAR"); + + success = opts.Parse({"--foo"}, fakeStdErr); + ASSERT_FALSE(success); +} + +TEST(CommandLineOptionsTests, MandatoryOptionMultipleArgsButExpectedOnce) { + std::string foo; + CommandLineOptions opts = CommandLineOptions("test").MandatoryOption("--foo", "", &foo); + std::ostream fakeStdErr(nullptr); + bool success = opts.Parse({"--foo", "FIRST", "--foo", "SECOND"}, fakeStdErr); + ASSERT_TRUE(success); + ASSERT_EQ(foo, "SECOND"); +} + +TEST(CommandLineOptionsTests, MandatoryOptionMultipleArgsAndExpectedOnceOrMore) { + std::vector<std::string> args; + CommandLineOptions opts = CommandLineOptions("test").MandatoryOption("--foo", "", &args); + std::ostream fakeStdErr(nullptr); + bool success = opts.Parse({"--foo", "FOO", "--foo", "BAR"}, fakeStdErr); + ASSERT_TRUE(success); + ASSERT_EQ(args.size(), 2u); + ASSERT_EQ(args[0], "FOO"); + ASSERT_EQ(args[1], "BAR"); +} + +TEST(CommandLineOptionsTests, OptionalOption) { + std::string foo, bar; + CommandLineOptions opts = CommandLineOptions("test") + .OptionalOption("--foo", "", &foo) + .OptionalOption("--bar", "", &bar); + std::ostream fakeStdErr(nullptr); + bool success = opts.Parse({"--foo", "FOO", "--bar", "BAR"}, fakeStdErr); + ASSERT_TRUE(success); + ASSERT_EQ(foo, "FOO"); + ASSERT_EQ(bar, "BAR"); + + success = opts.Parse({"--foo", "BAZ"}, fakeStdErr); + ASSERT_TRUE(success); + ASSERT_EQ(foo, "BAZ"); + + success = opts.Parse({"--foo"}, fakeStdErr); + ASSERT_FALSE(success); + + success = opts.Parse({"--foo", "--bar", "BAR"}, fakeStdErr); + ASSERT_FALSE(success); + + success = opts.Parse({"--foo", "FOO", "--bar"}, fakeStdErr); + ASSERT_FALSE(success); +} + +TEST(CommandLineOptionsTests, CornerCases) { + std::string foo, bar; + bool baz = false; + CommandLineOptions opts = CommandLineOptions("test") + .MandatoryOption("--foo", "", &foo) + .OptionalFlag("--baz", "", &baz) + .OptionalOption("--bar", "", &bar); + std::ostream fakeStdErr(nullptr); + bool success = opts.Parse({"--unexpected"}, fakeStdErr); + ASSERT_FALSE(success); + + success = opts.Parse({"--bar", "BAR"}, fakeStdErr); + ASSERT_FALSE(success); + + success = opts.Parse({"--baz", "--foo", "FOO"}, fakeStdErr); + ASSERT_TRUE(success); + ASSERT_TRUE(baz); + ASSERT_EQ(foo, "FOO"); +} + +TEST(CommandLineOptionsTests, ConvertArgvToVector) { + const char* argv[] = { + "program-name", + "--foo", + "FOO", + nullptr, + }; + std::unique_ptr<std::vector<std::string>> v = CommandLineOptions::ConvertArgvToVector(3, argv); + ASSERT_EQ(v->size(), 2ul); + ASSERT_EQ((*v)[0], "--foo"); + ASSERT_EQ((*v)[1], "FOO"); +} + +TEST(CommandLineOptionsTests, ConvertArgvToVectorNoArgs) { + const char* argv[] = { + "program-name", + nullptr, + }; + std::unique_ptr<std::vector<std::string>> v = CommandLineOptions::ConvertArgvToVector(1, argv); + ASSERT_EQ(v->size(), 0ul); +} + +TEST(CommandLineOptionsTests, Usage) { + std::string arg1, arg2, arg3, arg4; + bool arg5 = false, arg6 = false; + std::vector<std::string> arg7; + CommandLineOptions opts = CommandLineOptions("test") + .MandatoryOption("--aa", "description-aa", &arg1) + .OptionalFlag("--bb", "description-bb", &arg5) + .OptionalOption("--cc", "description-cc", &arg2) + .OptionalOption("--dd", "description-dd", &arg3) + .MandatoryOption("--ee", "description-ee", &arg4) + .OptionalFlag("--ff", "description-ff", &arg6) + .MandatoryOption("--gg", "description-gg", &arg7); + std::stringstream stream; + opts.Usage(stream); + const std::string s = stream.str(); + ASSERT_NE(s.find("usage: test --aa arg [--bb] [--cc arg] [--dd arg] --ee arg [--ff] --gg arg " + "[--gg arg [..]]"), + std::string::npos); + ASSERT_NE(s.find("--aa arg description-aa"), std::string::npos); + ASSERT_NE(s.find("--ff description-ff"), std::string::npos); + ASSERT_NE(s.find("--gg arg description-gg (can be provided multiple times)"), + std::string::npos); +} + +} // namespace idmap2 +} // namespace android diff --git a/cmds/idmap2/tests/FileUtilsTests.cpp b/cmds/idmap2/tests/FileUtilsTests.cpp new file mode 100644 index 000000000000..0c6439ab8c0c --- /dev/null +++ b/cmds/idmap2/tests/FileUtilsTests.cpp @@ -0,0 +1,76 @@ +/* + * 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 <dirent.h> +#include <set> +#include <string> + +#include "gmock/gmock.h" +#include "gtest/gtest.h" + +#include "android-base/macros.h" + +#include "idmap2/FileUtils.h" + +#include "TestHelpers.h" + +using ::testing::NotNull; + +namespace android { +namespace idmap2 { +namespace utils { + +TEST(FileUtilsTests, FindFilesFindEverythingNonRecursive) { + const auto& root = GetTestDataPath(); + auto v = utils::FindFiles(root, false, + [](unsigned char type ATTRIBUTE_UNUSED, + const std::string& path ATTRIBUTE_UNUSED) -> bool { return true; }); + ASSERT_THAT(v, NotNull()); + ASSERT_EQ(v->size(), 4u); + ASSERT_EQ( + std::set<std::string>(v->begin(), v->end()), + std::set<std::string>({root + "/.", root + "/..", root + "/overlay", root + "/target"})); +} + +TEST(FileUtilsTests, FindFilesFindApkFilesRecursive) { + const auto& root = GetTestDataPath(); + auto v = utils::FindFiles(root, true, [](unsigned char type, const std::string& path) -> bool { + return type == DT_REG && path.size() > 4 && !path.compare(path.size() - 4, 4, ".apk"); + }); + ASSERT_THAT(v, NotNull()); + ASSERT_EQ(v->size(), 4u); + ASSERT_EQ(std::set<std::string>(v->begin(), v->end()), + std::set<std::string>({root + "/target/target.apk", root + "/overlay/overlay.apk", + root + "/overlay/overlay-static-1.apk", + root + "/overlay/overlay-static-2.apk"})); +} + +TEST(FileUtilsTests, ReadFile) { + int pipefd[2]; + ASSERT_EQ(pipe(pipefd), 0); + + ASSERT_EQ(write(pipefd[1], "foobar", 6), 6); + close(pipefd[1]); + + auto data = ReadFile(pipefd[0]); + ASSERT_THAT(data, NotNull()); + ASSERT_EQ(*data, "foobar"); + close(pipefd[0]); +} + +} // namespace utils +} // namespace idmap2 +} // namespace android diff --git a/cmds/idmap2/tests/Idmap2BinaryTests.cpp b/cmds/idmap2/tests/Idmap2BinaryTests.cpp new file mode 100644 index 000000000000..5c4e8576985b --- /dev/null +++ b/cmds/idmap2/tests/Idmap2BinaryTests.cpp @@ -0,0 +1,313 @@ +/* + * 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. + */ + +/* + * The tests in this file operate on a higher level than the tests in the other + * files. Here, all tests execute the idmap2 binary and only depend on + * libidmap2 to verify the output of idmap2. + */ +#include <fcntl.h> +#include <sys/stat.h> +#include <sys/types.h> +#include <sys/wait.h> +#include <unistd.h> + +#include <cerrno> +#include <cstdlib> +#include <cstring> // strerror +#include <fstream> +#include <memory> +#include <sstream> +#include <string> +#include <vector> + +#include "gmock/gmock.h" +#include "gtest/gtest.h" + +#include "androidfw/PosixUtils.h" +#include "idmap2/FileUtils.h" +#include "idmap2/Idmap.h" + +#include "TestHelpers.h" + +using ::android::util::ExecuteBinary; +using ::testing::NotNull; + +namespace android { +namespace idmap2 { + +class Idmap2BinaryTests : public Idmap2Tests {}; + +static void AssertIdmap(const Idmap& idmap, const std::string& target_apk_path, + const std::string& overlay_apk_path) { + // check that the idmap file looks reasonable (IdmapTests is responsible for + // more in-depth verification) + ASSERT_EQ(idmap.GetHeader()->GetMagic(), kIdmapMagic); + ASSERT_EQ(idmap.GetHeader()->GetVersion(), kIdmapCurrentVersion); + ASSERT_EQ(idmap.GetHeader()->GetTargetPath(), target_apk_path); + ASSERT_EQ(idmap.GetHeader()->GetOverlayPath(), overlay_apk_path); + ASSERT_EQ(idmap.GetData().size(), 1u); +} + +#define ASSERT_IDMAP(idmap_ref, target_apk_path, overlay_apk_path) \ + do { \ + ASSERT_NO_FATAL_FAILURE(AssertIdmap(idmap_ref, target_apk_path, overlay_apk_path)); \ + } while (0) + +TEST_F(Idmap2BinaryTests, Create) { + // clang-format off + auto result = ExecuteBinary({"idmap2", + "create", + "--target-apk-path", GetTargetApkPath(), + "--overlay-apk-path", GetOverlayApkPath(), + "--idmap-path", GetIdmapPath()}); + // clang-format on + ASSERT_THAT(result, NotNull()); + ASSERT_EQ(result->status, EXIT_SUCCESS) << result->stderr; + + struct stat st; + ASSERT_EQ(stat(GetIdmapPath().c_str(), &st), 0); + + std::stringstream error; + std::ifstream fin(GetIdmapPath()); + std::unique_ptr<const Idmap> idmap = Idmap::FromBinaryStream(fin, error); + fin.close(); + + ASSERT_THAT(idmap, NotNull()); + ASSERT_IDMAP(*idmap, GetTargetApkPath(), GetOverlayApkPath()); + + unlink(GetIdmapPath().c_str()); +} + +TEST_F(Idmap2BinaryTests, Dump) { + // clang-format off + auto result = ExecuteBinary({"idmap2", + "create", + "--target-apk-path", GetTargetApkPath(), + "--overlay-apk-path", GetOverlayApkPath(), + "--idmap-path", GetIdmapPath()}); + // clang-format on + ASSERT_THAT(result, NotNull()); + ASSERT_EQ(result->status, EXIT_SUCCESS) << result->stderr; + + // clang-format off + result = ExecuteBinary({"idmap2", + "dump", + "--idmap-path", GetIdmapPath()}); + // clang-format on + ASSERT_THAT(result, NotNull()); + ASSERT_EQ(result->status, EXIT_SUCCESS) << result->stderr; + ASSERT_NE(result->stdout.find("0x7f010000 -> 0x7f010000 integer/int1"), std::string::npos); + ASSERT_NE(result->stdout.find("0x7f020003 -> 0x7f020000 string/str1"), std::string::npos); + ASSERT_NE(result->stdout.find("0x7f020005 -> 0x7f020001 string/str3"), std::string::npos); + ASSERT_EQ(result->stdout.find("00000210: 007f target package id"), std::string::npos); + + // clang-format off + result = ExecuteBinary({"idmap2", + "dump", + "--verbose", + "--idmap-path", GetIdmapPath()}); + // clang-format on + ASSERT_THAT(result, NotNull()); + ASSERT_EQ(result->status, EXIT_SUCCESS) << result->stderr; + ASSERT_NE(result->stdout.find("00000000: 504d4449 magic"), std::string::npos); + ASSERT_NE(result->stdout.find("00000210: 007f target package id"), std::string::npos); + + // clang-format off + result = ExecuteBinary({"idmap2", + "dump", + "--verbose", + "--idmap-path", GetTestDataPath() + "/DOES-NOT-EXIST"}); + // clang-format on + ASSERT_THAT(result, NotNull()); + ASSERT_NE(result->status, EXIT_SUCCESS); + + unlink(GetIdmapPath().c_str()); +} + +TEST_F(Idmap2BinaryTests, Scan) { + const std::string overlay_static_1_apk_path = GetTestDataPath() + "/overlay/overlay-static-1.apk"; + const std::string overlay_static_2_apk_path = GetTestDataPath() + "/overlay/overlay-static-2.apk"; + const std::string idmap_static_1_path = + Idmap::CanonicalIdmapPathFor(GetTempDirPath(), overlay_static_1_apk_path); + const std::string idmap_static_2_path = + Idmap::CanonicalIdmapPathFor(GetTempDirPath(), overlay_static_2_apk_path); + + // single input directory, recursive + // clang-format off + auto result = ExecuteBinary({"idmap2", + "scan", + "--input-directory", GetTestDataPath(), + "--recursive", + "--target-package-name", "test.target", + "--target-apk-path", GetTargetApkPath(), + "--output-directory", GetTempDirPath()}); + // clang-format on + ASSERT_THAT(result, NotNull()); + ASSERT_EQ(result->status, EXIT_SUCCESS) << result->stderr; + std::stringstream expected; + expected << idmap_static_1_path << std::endl; + expected << idmap_static_2_path << std::endl; + ASSERT_EQ(result->stdout, expected.str()); + + std::stringstream error; + auto idmap_static_1_raw_string = utils::ReadFile(idmap_static_1_path); + auto idmap_static_1_raw_stream = std::istringstream(*idmap_static_1_raw_string); + auto idmap_static_1 = Idmap::FromBinaryStream(idmap_static_1_raw_stream, error); + ASSERT_THAT(idmap_static_1, NotNull()); + ASSERT_IDMAP(*idmap_static_1, GetTargetApkPath(), overlay_static_1_apk_path); + + auto idmap_static_2_raw_string = utils::ReadFile(idmap_static_2_path); + auto idmap_static_2_raw_stream = std::istringstream(*idmap_static_2_raw_string); + auto idmap_static_2 = Idmap::FromBinaryStream(idmap_static_2_raw_stream, error); + ASSERT_THAT(idmap_static_2, NotNull()); + ASSERT_IDMAP(*idmap_static_2, GetTargetApkPath(), overlay_static_2_apk_path); + + unlink(idmap_static_2_path.c_str()); + unlink(idmap_static_1_path.c_str()); + + // multiple input directories, non-recursive + // clang-format off + result = ExecuteBinary({"idmap2", + "scan", + "--input-directory", GetTestDataPath() + "/target", + "--input-directory", GetTestDataPath() + "/overlay", + "--target-package-name", "test.target", + "--target-apk-path", GetTargetApkPath(), + "--output-directory", GetTempDirPath()}); + // clang-format on + ASSERT_THAT(result, NotNull()); + ASSERT_EQ(result->status, EXIT_SUCCESS) << result->stderr; + ASSERT_EQ(result->stdout, expected.str()); + unlink(idmap_static_2_path.c_str()); + unlink(idmap_static_1_path.c_str()); + + // the same input directory given twice, but no duplicate entries + // clang-format off + result = ExecuteBinary({"idmap2", + "scan", + "--input-directory", GetTestDataPath(), + "--input-directory", GetTestDataPath(), + "--recursive", + "--target-package-name", "test.target", + "--target-apk-path", GetTargetApkPath(), + "--output-directory", GetTempDirPath()}); + // clang-format on + ASSERT_THAT(result, NotNull()); + ASSERT_EQ(result->status, EXIT_SUCCESS) << result->stderr; + ASSERT_EQ(result->stdout, expected.str()); + unlink(idmap_static_2_path.c_str()); + unlink(idmap_static_1_path.c_str()); + + // no APKs in input-directory: ok, but no output + // clang-format off + result = ExecuteBinary({"idmap2", + "scan", + "--input-directory", GetTempDirPath(), + "--target-package-name", "test.target", + "--target-apk-path", GetTargetApkPath(), + "--output-directory", GetTempDirPath()}); + // clang-format on + ASSERT_THAT(result, NotNull()); + ASSERT_EQ(result->status, EXIT_SUCCESS) << result->stderr; + ASSERT_EQ(result->stdout, ""); +} + +TEST_F(Idmap2BinaryTests, Lookup) { + // clang-format off + auto result = ExecuteBinary({"idmap2", + "create", + "--target-apk-path", GetTargetApkPath(), + "--overlay-apk-path", GetOverlayApkPath(), + "--idmap-path", GetIdmapPath()}); + // clang-format on + ASSERT_THAT(result, NotNull()); + ASSERT_EQ(result->status, EXIT_SUCCESS) << result->stderr; + + // clang-format off + result = ExecuteBinary({"idmap2", + "lookup", + "--idmap-path", GetIdmapPath(), + "--config", "", + "--resid", "0x7f020003"}); // string/str1 + // clang-format on + ASSERT_THAT(result, NotNull()); + ASSERT_EQ(result->status, EXIT_SUCCESS) << result->stderr; + ASSERT_NE(result->stdout.find("overlay-1"), std::string::npos); + ASSERT_EQ(result->stdout.find("overlay-1-sv"), std::string::npos); + + // clang-format off + result = ExecuteBinary({"idmap2", + "lookup", + "--idmap-path", GetIdmapPath(), + "--config", "", + "--resid", "test.target:string/str1"}); + // clang-format on + ASSERT_THAT(result, NotNull()); + ASSERT_EQ(result->status, EXIT_SUCCESS) << result->stderr; + ASSERT_NE(result->stdout.find("overlay-1"), std::string::npos); + ASSERT_EQ(result->stdout.find("overlay-1-sv"), std::string::npos); + + // clang-format off + result = ExecuteBinary({"idmap2", + "lookup", + "--idmap-path", GetIdmapPath(), + "--config", "sv", + "--resid", "test.target:string/str1"}); + // clang-format on + ASSERT_THAT(result, NotNull()); + ASSERT_EQ(result->status, EXIT_SUCCESS) << result->stderr; + ASSERT_NE(result->stdout.find("overlay-1-sv"), std::string::npos); + + unlink(GetIdmapPath().c_str()); +} + +TEST_F(Idmap2BinaryTests, InvalidCommandLineOptions) { + const std::string invalid_target_apk_path = GetTestDataPath() + "/DOES-NOT-EXIST"; + + // missing mandatory options + // clang-format off + auto result = ExecuteBinary({"idmap2", + "create"}); + // clang-format on + ASSERT_THAT(result, NotNull()); + ASSERT_NE(result->status, EXIT_SUCCESS); + + // missing argument to option + // clang-format off + result = ExecuteBinary({"idmap2", + "create", + "--target-apk-path", GetTargetApkPath(), + "--overlay-apk-path", GetOverlayApkPath(), + "--idmap-path"}); + // clang-format on + ASSERT_THAT(result, NotNull()); + ASSERT_NE(result->status, EXIT_SUCCESS); + + // invalid target apk path + // clang-format off + result = ExecuteBinary({"idmap2", + "create", + "--target-apk-path", invalid_target_apk_path, + "--overlay-apk-path", GetOverlayApkPath(), + "--idmap-path", GetIdmapPath()}); + // clang-format on + ASSERT_THAT(result, NotNull()); + ASSERT_NE(result->status, EXIT_SUCCESS); +} + +} // namespace idmap2 +} // namespace android diff --git a/cmds/idmap2/tests/IdmapTests.cpp b/cmds/idmap2/tests/IdmapTests.cpp new file mode 100644 index 000000000000..0379aa491682 --- /dev/null +++ b/cmds/idmap2/tests/IdmapTests.cpp @@ -0,0 +1,395 @@ +/* + * 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 <cstdio> // fclose + +#include <fstream> +#include <memory> +#include <sstream> +#include <string> +#include <vector> + +#include "gmock/gmock.h" +#include "gtest/gtest.h" + +#include "android-base/macros.h" +#include "androidfw/ApkAssets.h" + +#include "idmap2/BinaryStreamVisitor.h" +#include "idmap2/CommandLineOptions.h" +#include "idmap2/Idmap.h" + +#include "TestHelpers.h" + +using ::testing::IsNull; +using ::testing::NotNull; + +namespace android { +namespace idmap2 { + +TEST(IdmapTests, TestCanonicalIdmapPathFor) { + ASSERT_EQ(Idmap::CanonicalIdmapPathFor("/foo", "/vendor/overlay/bar.apk"), + "/foo/vendor@overlay@bar.apk@idmap"); +} + +TEST(IdmapTests, CreateIdmapHeaderFromBinaryStream) { + std::string raw(reinterpret_cast<const char*>(idmap_raw_data), idmap_raw_data_len); + std::istringstream stream(raw); + std::unique_ptr<const IdmapHeader> header = IdmapHeader::FromBinaryStream(stream); + ASSERT_THAT(header, NotNull()); + ASSERT_EQ(header->GetMagic(), 0x504d4449u); + ASSERT_EQ(header->GetVersion(), 0x01u); + ASSERT_EQ(header->GetTargetCrc(), 0x1234u); + ASSERT_EQ(header->GetOverlayCrc(), 0x5678u); + ASSERT_EQ(header->GetTargetPath().to_string(), "target.apk"); + ASSERT_EQ(header->GetOverlayPath().to_string(), "overlay.apk"); +} + +TEST(IdmapTests, FailToCreateIdmapHeaderFromBinaryStreamIfPathTooLong) { + std::string raw(reinterpret_cast<const char*>(idmap_raw_data), idmap_raw_data_len); + // overwrite the target path string, including the terminating null, with '.' + for (size_t i = 0x10; i < 0x110; i++) { + raw[i] = '.'; + } + std::istringstream stream(raw); + std::unique_ptr<const IdmapHeader> header = IdmapHeader::FromBinaryStream(stream); + ASSERT_THAT(header, IsNull()); +} + +TEST(IdmapTests, CreateIdmapDataHeaderFromBinaryStream) { + const size_t offset = 0x210; + std::string raw(reinterpret_cast<const char*>(idmap_raw_data + offset), + idmap_raw_data_len - offset); + std::istringstream stream(raw); + + std::unique_ptr<const IdmapData::Header> header = IdmapData::Header::FromBinaryStream(stream); + ASSERT_THAT(header, NotNull()); + ASSERT_EQ(header->GetTargetPackageId(), 0x7fu); + ASSERT_EQ(header->GetTypeCount(), 2u); +} + +TEST(IdmapTests, CreateIdmapDataResourceTypeFromBinaryStream) { + const size_t offset = 0x214; + std::string raw(reinterpret_cast<const char*>(idmap_raw_data + offset), + idmap_raw_data_len - offset); + std::istringstream stream(raw); + + std::unique_ptr<const IdmapData::TypeEntry> data = IdmapData::TypeEntry::FromBinaryStream(stream); + ASSERT_THAT(data, NotNull()); + ASSERT_EQ(data->GetTargetTypeId(), 0x02u); + ASSERT_EQ(data->GetOverlayTypeId(), 0x02u); + ASSERT_EQ(data->GetEntryCount(), 1u); + ASSERT_EQ(data->GetEntryOffset(), 0u); + ASSERT_EQ(data->GetEntry(0), 0u); +} + +TEST(IdmapTests, CreateIdmapDataFromBinaryStream) { + const size_t offset = 0x210; + std::string raw(reinterpret_cast<const char*>(idmap_raw_data + offset), + idmap_raw_data_len - offset); + std::istringstream stream(raw); + + std::unique_ptr<const IdmapData> data = IdmapData::FromBinaryStream(stream); + ASSERT_THAT(data, NotNull()); + ASSERT_EQ(data->GetHeader()->GetTargetPackageId(), 0x7fu); + ASSERT_EQ(data->GetHeader()->GetTypeCount(), 2u); + const std::vector<std::unique_ptr<const IdmapData::TypeEntry>>& types = data->GetTypeEntries(); + ASSERT_EQ(types.size(), 2u); + + ASSERT_EQ(types[0]->GetTargetTypeId(), 0x02u); + ASSERT_EQ(types[0]->GetOverlayTypeId(), 0x02u); + ASSERT_EQ(types[0]->GetEntryCount(), 1u); + ASSERT_EQ(types[0]->GetEntryOffset(), 0u); + ASSERT_EQ(types[0]->GetEntry(0), 0x0000u); + + ASSERT_EQ(types[1]->GetTargetTypeId(), 0x03u); + ASSERT_EQ(types[1]->GetOverlayTypeId(), 0x03u); + ASSERT_EQ(types[1]->GetEntryCount(), 3u); + ASSERT_EQ(types[1]->GetEntryOffset(), 3u); + ASSERT_EQ(types[1]->GetEntry(0), 0x0000u); + ASSERT_EQ(types[1]->GetEntry(1), kNoEntry); + ASSERT_EQ(types[1]->GetEntry(2), 0x0001u); +} + +TEST(IdmapTests, CreateIdmapFromBinaryStream) { + std::string raw(reinterpret_cast<const char*>(idmap_raw_data), idmap_raw_data_len); + std::istringstream stream(raw); + + std::stringstream error; + std::unique_ptr<const Idmap> idmap = Idmap::FromBinaryStream(stream, error); + ASSERT_THAT(idmap, NotNull()); + + ASSERT_THAT(idmap->GetHeader(), NotNull()); + ASSERT_EQ(idmap->GetHeader()->GetMagic(), 0x504d4449u); + ASSERT_EQ(idmap->GetHeader()->GetVersion(), 0x01u); + ASSERT_EQ(idmap->GetHeader()->GetTargetCrc(), 0x1234u); + ASSERT_EQ(idmap->GetHeader()->GetOverlayCrc(), 0x5678u); + ASSERT_EQ(idmap->GetHeader()->GetTargetPath().to_string(), "target.apk"); + ASSERT_EQ(idmap->GetHeader()->GetOverlayPath().to_string(), "overlay.apk"); + + const std::vector<std::unique_ptr<const IdmapData>>& dataBlocks = idmap->GetData(); + ASSERT_EQ(dataBlocks.size(), 1u); + + const std::unique_ptr<const IdmapData>& data = dataBlocks[0]; + ASSERT_EQ(data->GetHeader()->GetTargetPackageId(), 0x7fu); + ASSERT_EQ(data->GetHeader()->GetTypeCount(), 2u); + const std::vector<std::unique_ptr<const IdmapData::TypeEntry>>& types = data->GetTypeEntries(); + ASSERT_EQ(types.size(), 2u); + + ASSERT_EQ(types[0]->GetTargetTypeId(), 0x02u); + ASSERT_EQ(types[0]->GetOverlayTypeId(), 0x02u); + ASSERT_EQ(types[0]->GetEntryCount(), 1u); + ASSERT_EQ(types[0]->GetEntryOffset(), 0u); + ASSERT_EQ(types[0]->GetEntry(0), 0x0000u); + + ASSERT_EQ(types[1]->GetTargetTypeId(), 0x03u); + ASSERT_EQ(types[1]->GetOverlayTypeId(), 0x03u); + ASSERT_EQ(types[1]->GetEntryCount(), 3u); + ASSERT_EQ(types[1]->GetEntryOffset(), 3u); + ASSERT_EQ(types[1]->GetEntry(0), 0x0000u); + ASSERT_EQ(types[1]->GetEntry(1), kNoEntry); + ASSERT_EQ(types[1]->GetEntry(2), 0x0001u); +} + +TEST(IdmapTests, GracefullyFailToCreateIdmapFromCorruptBinaryStream) { + std::string raw(reinterpret_cast<const char*>(idmap_raw_data), + 10); // data too small + std::istringstream stream(raw); + + std::stringstream error; + std::unique_ptr<const Idmap> idmap = Idmap::FromBinaryStream(stream, error); + ASSERT_THAT(idmap, IsNull()); +} + +TEST(IdmapTests, CreateIdmapFromApkAssets) { + const std::string target_apk_path(GetTestDataPath() + "/target/target.apk"); + std::unique_ptr<const ApkAssets> target_apk = ApkAssets::Load(target_apk_path); + ASSERT_THAT(target_apk, NotNull()); + + const std::string overlay_apk_path(GetTestDataPath() + "/overlay/overlay.apk"); + std::unique_ptr<const ApkAssets> overlay_apk = ApkAssets::Load(overlay_apk_path); + ASSERT_THAT(overlay_apk, NotNull()); + + std::stringstream error; + std::unique_ptr<const Idmap> idmap = + Idmap::FromApkAssets(target_apk_path, *target_apk, overlay_apk_path, *overlay_apk, error); + ASSERT_THAT(idmap, NotNull()); + + ASSERT_THAT(idmap->GetHeader(), NotNull()); + ASSERT_EQ(idmap->GetHeader()->GetMagic(), 0x504d4449u); + ASSERT_EQ(idmap->GetHeader()->GetVersion(), 0x01u); + ASSERT_EQ(idmap->GetHeader()->GetTargetCrc(), 0xf5ad1d1d); + ASSERT_EQ(idmap->GetHeader()->GetOverlayCrc(), 0xd470336b); + ASSERT_EQ(idmap->GetHeader()->GetTargetPath().to_string(), target_apk_path); + ASSERT_EQ(idmap->GetHeader()->GetOverlayPath(), overlay_apk_path); + ASSERT_EQ(idmap->GetHeader()->GetOverlayPath(), overlay_apk_path); + + const std::vector<std::unique_ptr<const IdmapData>>& dataBlocks = idmap->GetData(); + ASSERT_EQ(dataBlocks.size(), 1u); + + const std::unique_ptr<const IdmapData>& data = dataBlocks[0]; + + ASSERT_EQ(data->GetHeader()->GetTargetPackageId(), 0x7fu); + ASSERT_EQ(data->GetHeader()->GetTypeCount(), 2u); + + const std::vector<std::unique_ptr<const IdmapData::TypeEntry>>& types = data->GetTypeEntries(); + ASSERT_EQ(types.size(), 2u); + + ASSERT_EQ(types[0]->GetTargetTypeId(), 0x01u); + ASSERT_EQ(types[0]->GetOverlayTypeId(), 0x01u); + ASSERT_EQ(types[0]->GetEntryCount(), 1u); + ASSERT_EQ(types[0]->GetEntryOffset(), 0u); + ASSERT_EQ(types[0]->GetEntry(0), 0x0000u); + + ASSERT_EQ(types[1]->GetTargetTypeId(), 0x02u); + ASSERT_EQ(types[1]->GetOverlayTypeId(), 0x02u); + ASSERT_EQ(types[1]->GetEntryCount(), 4u); + ASSERT_EQ(types[1]->GetEntryOffset(), 3u); + ASSERT_EQ(types[1]->GetEntry(0), 0x0000u); + ASSERT_EQ(types[1]->GetEntry(1), kNoEntry); + ASSERT_EQ(types[1]->GetEntry(2), 0x0001u); + ASSERT_EQ(types[1]->GetEntry(3), 0x0002u); +} + +TEST(IdmapTests, FailToCreateIdmapFromApkAssetsIfPathTooLong) { + std::string target_apk_path(GetTestDataPath()); + for (int i = 0; i < 32; i++) { + target_apk_path += "/target/../"; + } + target_apk_path += "/target/target.apk"; + ASSERT_GT(target_apk_path.size(), kIdmapStringLength); + std::unique_ptr<const ApkAssets> target_apk = ApkAssets::Load(target_apk_path); + ASSERT_THAT(target_apk, NotNull()); + + const std::string overlay_apk_path(GetTestDataPath() + "/overlay/overlay.apk"); + std::unique_ptr<const ApkAssets> overlay_apk = ApkAssets::Load(overlay_apk_path); + ASSERT_THAT(overlay_apk, NotNull()); + + std::stringstream error; + std::unique_ptr<const Idmap> idmap = + Idmap::FromApkAssets(target_apk_path, *target_apk, overlay_apk_path, *overlay_apk, error); + ASSERT_THAT(idmap, IsNull()); +} + +TEST(IdmapTests, IdmapHeaderIsUpToDate) { + fclose(stderr); // silence expected warnings from libandroidfw + + const std::string target_apk_path(GetTestDataPath() + "/target/target.apk"); + std::unique_ptr<const ApkAssets> target_apk = ApkAssets::Load(target_apk_path); + ASSERT_THAT(target_apk, NotNull()); + + const std::string overlay_apk_path(GetTestDataPath() + "/overlay/overlay.apk"); + std::unique_ptr<const ApkAssets> overlay_apk = ApkAssets::Load(overlay_apk_path); + ASSERT_THAT(overlay_apk, NotNull()); + + std::stringstream error; + std::unique_ptr<const Idmap> idmap = + Idmap::FromApkAssets(target_apk_path, *target_apk, overlay_apk_path, *overlay_apk, error); + ASSERT_THAT(idmap, NotNull()); + + std::stringstream stream; + BinaryStreamVisitor visitor(stream); + idmap->accept(&visitor); + + std::unique_ptr<const IdmapHeader> header = IdmapHeader::FromBinaryStream(stream); + ASSERT_THAT(header, NotNull()); + ASSERT_TRUE(header->IsUpToDate(error)) << error.str(); + + // magic: bytes (0x0, 0x03) + std::string bad_magic_string(stream.str()); + bad_magic_string[0x0] = '.'; + bad_magic_string[0x1] = '.'; + bad_magic_string[0x2] = '.'; + bad_magic_string[0x3] = '.'; + std::stringstream bad_magic_stream(bad_magic_string); + std::unique_ptr<const IdmapHeader> bad_magic_header = + IdmapHeader::FromBinaryStream(bad_magic_stream); + ASSERT_THAT(bad_magic_header, NotNull()); + ASSERT_NE(header->GetMagic(), bad_magic_header->GetMagic()); + ASSERT_FALSE(bad_magic_header->IsUpToDate(error)); + + // version: bytes (0x4, 0x07) + std::string bad_version_string(stream.str()); + bad_version_string[0x4] = '.'; + bad_version_string[0x5] = '.'; + bad_version_string[0x6] = '.'; + bad_version_string[0x7] = '.'; + std::stringstream bad_version_stream(bad_version_string); + std::unique_ptr<const IdmapHeader> bad_version_header = + IdmapHeader::FromBinaryStream(bad_version_stream); + ASSERT_THAT(bad_version_header, NotNull()); + ASSERT_NE(header->GetVersion(), bad_version_header->GetVersion()); + ASSERT_FALSE(bad_version_header->IsUpToDate(error)); + + // target crc: bytes (0x8, 0xb) + std::string bad_target_crc_string(stream.str()); + bad_target_crc_string[0x8] = '.'; + bad_target_crc_string[0x9] = '.'; + bad_target_crc_string[0xa] = '.'; + bad_target_crc_string[0xb] = '.'; + std::stringstream bad_target_crc_stream(bad_target_crc_string); + std::unique_ptr<const IdmapHeader> bad_target_crc_header = + IdmapHeader::FromBinaryStream(bad_target_crc_stream); + ASSERT_THAT(bad_target_crc_header, NotNull()); + ASSERT_NE(header->GetTargetCrc(), bad_target_crc_header->GetTargetCrc()); + ASSERT_FALSE(bad_target_crc_header->IsUpToDate(error)); + + // overlay crc: bytes (0xc, 0xf) + std::string bad_overlay_crc_string(stream.str()); + bad_overlay_crc_string[0xc] = '.'; + bad_overlay_crc_string[0xd] = '.'; + bad_overlay_crc_string[0xe] = '.'; + bad_overlay_crc_string[0xf] = '.'; + std::stringstream bad_overlay_crc_stream(bad_overlay_crc_string); + std::unique_ptr<const IdmapHeader> bad_overlay_crc_header = + IdmapHeader::FromBinaryStream(bad_overlay_crc_stream); + ASSERT_THAT(bad_overlay_crc_header, NotNull()); + ASSERT_NE(header->GetOverlayCrc(), bad_overlay_crc_header->GetOverlayCrc()); + ASSERT_FALSE(bad_overlay_crc_header->IsUpToDate(error)); + + // target path: bytes (0x10, 0x10f) + std::string bad_target_path_string(stream.str()); + bad_target_path_string[0x10] = '\0'; + std::stringstream bad_target_path_stream(bad_target_path_string); + std::unique_ptr<const IdmapHeader> bad_target_path_header = + IdmapHeader::FromBinaryStream(bad_target_path_stream); + ASSERT_THAT(bad_target_path_header, NotNull()); + ASSERT_NE(header->GetTargetPath(), bad_target_path_header->GetTargetPath()); + ASSERT_FALSE(bad_target_path_header->IsUpToDate(error)); + + // overlay path: bytes (0x110, 0x20f) + std::string bad_overlay_path_string(stream.str()); + bad_overlay_path_string[0x110] = '\0'; + std::stringstream bad_overlay_path_stream(bad_overlay_path_string); + std::unique_ptr<const IdmapHeader> bad_overlay_path_header = + IdmapHeader::FromBinaryStream(bad_overlay_path_stream); + ASSERT_THAT(bad_overlay_path_header, NotNull()); + ASSERT_NE(header->GetOverlayPath(), bad_overlay_path_header->GetOverlayPath()); + ASSERT_FALSE(bad_overlay_path_header->IsUpToDate(error)); +} + +class TestVisitor : public Visitor { + public: + explicit TestVisitor(std::ostream& stream) : stream_(stream) { + } + + void visit(const Idmap& idmap ATTRIBUTE_UNUSED) { + stream_ << "TestVisitor::visit(Idmap)" << std::endl; + } + + void visit(const IdmapHeader& idmap ATTRIBUTE_UNUSED) { + stream_ << "TestVisitor::visit(IdmapHeader)" << std::endl; + } + + void visit(const IdmapData& idmap ATTRIBUTE_UNUSED) { + stream_ << "TestVisitor::visit(IdmapData)" << std::endl; + } + + void visit(const IdmapData::Header& idmap ATTRIBUTE_UNUSED) { + stream_ << "TestVisitor::visit(IdmapData::Header)" << std::endl; + } + + void visit(const IdmapData::TypeEntry& idmap ATTRIBUTE_UNUSED) { + stream_ << "TestVisitor::visit(IdmapData::TypeEntry)" << std::endl; + } + + private: + std::ostream& stream_; +}; + +TEST(IdmapTests, TestVisitor) { + std::string raw(reinterpret_cast<const char*>(idmap_raw_data), idmap_raw_data_len); + std::istringstream stream(raw); + + std::stringstream error; + std::unique_ptr<const Idmap> idmap = Idmap::FromBinaryStream(stream, error); + ASSERT_THAT(idmap, NotNull()); + + std::stringstream test_stream; + TestVisitor visitor(test_stream); + idmap->accept(&visitor); + + ASSERT_EQ(test_stream.str(), + "TestVisitor::visit(Idmap)\n" + "TestVisitor::visit(IdmapHeader)\n" + "TestVisitor::visit(IdmapData)\n" + "TestVisitor::visit(IdmapData::Header)\n" + "TestVisitor::visit(IdmapData::TypeEntry)\n" + "TestVisitor::visit(IdmapData::TypeEntry)\n"); +} + +} // namespace idmap2 +} // namespace android diff --git a/cmds/idmap2/tests/Main.cpp b/cmds/idmap2/tests/Main.cpp new file mode 100644 index 000000000000..f2469eaf57cc --- /dev/null +++ b/cmds/idmap2/tests/Main.cpp @@ -0,0 +1,38 @@ +/* + * 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 <string> + +#include "android-base/file.h" + +#include "gtest/gtest.h" + +#include "TestHelpers.h" + +namespace android { +namespace idmap2 { + +const std::string GetTestDataPath() { + return base::GetExecutableDirectory() + "/tests/data"; +} + +} // namespace idmap2 +} // namespace android + +int main(int argc, char** argv) { + ::testing::InitGoogleTest(&argc, argv); + return RUN_ALL_TESTS(); +} diff --git a/cmds/idmap2/tests/PrettyPrintVisitorTests.cpp b/cmds/idmap2/tests/PrettyPrintVisitorTests.cpp new file mode 100644 index 000000000000..da9779211f81 --- /dev/null +++ b/cmds/idmap2/tests/PrettyPrintVisitorTests.cpp @@ -0,0 +1,83 @@ +/* + * 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 <memory> +#include <sstream> +#include <string> + +#include "gmock/gmock.h" +#include "gtest/gtest.h" + +#include "androidfw/ApkAssets.h" +#include "androidfw/Idmap.h" + +#include "idmap2/Idmap.h" +#include "idmap2/PrettyPrintVisitor.h" + +#include "TestHelpers.h" + +using ::testing::IsNull; +using ::testing::NotNull; + +using android::ApkAssets; + +namespace android { +namespace idmap2 { + +TEST(PrettyPrintVisitorTests, CreatePrettyPrintVisitor) { + const std::string target_apk_path(GetTestDataPath() + "/target/target.apk"); + std::unique_ptr<const ApkAssets> target_apk = ApkAssets::Load(target_apk_path); + ASSERT_THAT(target_apk, NotNull()); + + const std::string overlay_apk_path(GetTestDataPath() + "/overlay/overlay.apk"); + std::unique_ptr<const ApkAssets> overlay_apk = ApkAssets::Load(overlay_apk_path); + ASSERT_THAT(overlay_apk, NotNull()); + + std::stringstream error; + std::unique_ptr<const Idmap> idmap = + Idmap::FromApkAssets(target_apk_path, *target_apk, overlay_apk_path, *overlay_apk, error); + ASSERT_THAT(idmap, NotNull()); + + std::stringstream stream; + PrettyPrintVisitor visitor(stream); + idmap->accept(&visitor); + + ASSERT_NE(stream.str().find("target apk path : "), std::string::npos); + ASSERT_NE(stream.str().find("overlay apk path : "), std::string::npos); + ASSERT_NE(stream.str().find("0x7f010000 -> 0x7f010000 integer/int1\n"), std::string::npos); +} + +TEST(PrettyPrintVisitorTests, CreatePrettyPrintVisitorWithoutAccessToApks) { + fclose(stderr); // silence expected warnings from libandroidfw + + std::string raw(reinterpret_cast<const char*>(idmap_raw_data), idmap_raw_data_len); + std::istringstream raw_stream(raw); + + std::stringstream error; + std::unique_ptr<const Idmap> idmap = Idmap::FromBinaryStream(raw_stream, error); + ASSERT_THAT(idmap, NotNull()); + + std::stringstream stream; + PrettyPrintVisitor visitor(stream); + idmap->accept(&visitor); + + ASSERT_NE(stream.str().find("target apk path : "), std::string::npos); + ASSERT_NE(stream.str().find("overlay apk path : "), std::string::npos); + ASSERT_NE(stream.str().find("0x7f020000 -> 0x7f020000\n"), std::string::npos); +} + +} // namespace idmap2 +} // namespace android diff --git a/cmds/idmap2/tests/RawPrintVisitorTests.cpp b/cmds/idmap2/tests/RawPrintVisitorTests.cpp new file mode 100644 index 000000000000..c28ce2e02ea9 --- /dev/null +++ b/cmds/idmap2/tests/RawPrintVisitorTests.cpp @@ -0,0 +1,84 @@ +/* + * 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 <cstdio> // fclose +#include <memory> +#include <sstream> +#include <string> + +#include "gmock/gmock.h" +#include "gtest/gtest.h" + +#include "idmap2/Idmap.h" +#include "idmap2/RawPrintVisitor.h" + +#include "TestHelpers.h" + +using ::testing::IsNull; +using ::testing::NotNull; + +namespace android { +namespace idmap2 { + +TEST(RawPrintVisitorTests, CreateRawPrintVisitor) { + const std::string target_apk_path(GetTestDataPath() + "/target/target.apk"); + std::unique_ptr<const ApkAssets> target_apk = ApkAssets::Load(target_apk_path); + ASSERT_THAT(target_apk, NotNull()); + + const std::string overlay_apk_path(GetTestDataPath() + "/overlay/overlay.apk"); + std::unique_ptr<const ApkAssets> overlay_apk = ApkAssets::Load(overlay_apk_path); + ASSERT_THAT(overlay_apk, NotNull()); + + std::stringstream error; + std::unique_ptr<const Idmap> idmap = + Idmap::FromApkAssets(target_apk_path, *target_apk, overlay_apk_path, *overlay_apk, error); + ASSERT_THAT(idmap, NotNull()); + + std::stringstream stream; + RawPrintVisitor visitor(stream); + idmap->accept(&visitor); + + ASSERT_NE(stream.str().find("00000000: 504d4449 magic\n"), std::string::npos); + ASSERT_NE(stream.str().find("00000004: 00000001 version\n"), std::string::npos); + ASSERT_NE(stream.str().find("00000008: f5ad1d1d target crc\n"), std::string::npos); + ASSERT_NE(stream.str().find("0000000c: d470336b overlay crc\n"), std::string::npos); + ASSERT_NE(stream.str().find("0000021c: 00000000 0x7f010000 -> 0x7f010000 integer/int1\n"), + std::string::npos); +} + +TEST(RawPrintVisitorTests, CreateRawPrintVisitorWithoutAccessToApks) { + fclose(stderr); // silence expected warnings from libandroidfw + + std::string raw(reinterpret_cast<const char*>(idmap_raw_data), idmap_raw_data_len); + std::istringstream raw_stream(raw); + + std::stringstream error; + std::unique_ptr<const Idmap> idmap = Idmap::FromBinaryStream(raw_stream, error); + ASSERT_THAT(idmap, NotNull()); + + std::stringstream stream; + RawPrintVisitor visitor(stream); + idmap->accept(&visitor); + + ASSERT_NE(stream.str().find("00000000: 504d4449 magic\n"), std::string::npos); + ASSERT_NE(stream.str().find("00000004: 00000001 version\n"), std::string::npos); + ASSERT_NE(stream.str().find("00000008: 00001234 target crc\n"), std::string::npos); + ASSERT_NE(stream.str().find("0000000c: 00005678 overlay crc\n"), std::string::npos); + ASSERT_NE(stream.str().find("0000021c: 00000000 0x7f020000 -> 0x7f020000\n"), std::string::npos); +} + +} // namespace idmap2 +} // namespace android diff --git a/cmds/idmap2/tests/ResourceUtilsTests.cpp b/cmds/idmap2/tests/ResourceUtilsTests.cpp new file mode 100644 index 000000000000..0547fa00de3d --- /dev/null +++ b/cmds/idmap2/tests/ResourceUtilsTests.cpp @@ -0,0 +1,69 @@ +/* + * 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 <memory> +#include <string> +#include <utility> + +#include "gmock/gmock.h" +#include "gtest/gtest.h" + +#include "androidfw/ApkAssets.h" +#include "idmap2/ResourceUtils.h" + +#include "TestHelpers.h" + +using ::testing::NotNull; + +namespace android { +namespace idmap2 { + +class ResourceUtilsTests : public Idmap2Tests { + protected: + void SetUp() override { + Idmap2Tests::SetUp(); + + apk_assets_ = ApkAssets::Load(GetTargetApkPath()); + ASSERT_THAT(apk_assets_, NotNull()); + + am_.SetApkAssets({apk_assets_.get()}); + } + + const AssetManager2& GetAssetManager() { + return am_; + } + + private: + AssetManager2 am_; + std::unique_ptr<const ApkAssets> apk_assets_; +}; + +TEST_F(ResourceUtilsTests, ResToTypeEntryName) { + bool lookup_ok; + std::string name; + std::tie(lookup_ok, name) = utils::ResToTypeEntryName(GetAssetManager(), 0x7f010000u); + ASSERT_TRUE(lookup_ok); + ASSERT_EQ(name, "integer/int1"); +} + +TEST_F(ResourceUtilsTests, ResToTypeEntryNameNoSuchResourceId) { + bool lookup_ok; + std::tie(lookup_ok, std::ignore) = utils::ResToTypeEntryName(GetAssetManager(), 0x7f123456u); + ASSERT_FALSE(lookup_ok); +} + +} // namespace idmap2 +} // namespace android diff --git a/cmds/idmap2/tests/TestHelpers.h b/cmds/idmap2/tests/TestHelpers.h new file mode 100644 index 000000000000..18dc541021c1 --- /dev/null +++ b/cmds/idmap2/tests/TestHelpers.h @@ -0,0 +1,168 @@ +/* + * 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_TESTS_TESTHELPERS_H_ +#define IDMAP2_TESTS_TESTHELPERS_H_ + +#include <string> + +namespace android { +namespace idmap2 { + +const unsigned char idmap_raw_data[] = { + // IDMAP HEADER + // 0x0: magic + 0x49, 0x44, 0x4d, 0x50, + + // 0x4: version + 0x01, 0x00, 0x00, 0x00, + + // 0x8: target crc + 0x34, 0x12, 0x00, 0x00, + + // 0xc: overlay crc + 0x78, 0x56, 0x00, 0x00, + + // 0x10: target path "target.apk" + 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x2e, 0x61, 0x70, 0x6b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + + // 0x110: overlay path "overlay.apk" + 0x6f, 0x76, 0x65, 0x72, 0x6c, 0x61, 0x79, 0x2e, 0x61, 0x70, 0x6b, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + + // DATA HEADER + // 0x210: target package id + 0x7f, 0x00, + + // 0x212: types count + 0x02, 0x00, + + // DATA BLOCK + // 0x214: target type + 0x02, 0x00, + + // 0x216: overlay type + 0x02, 0x00, + + // 0x218: entry count + 0x01, 0x00, + + // 0x21a: entry offset + 0x00, 0x00, + + // 0x21c: entries + 0x00, 0x00, 0x00, 0x00, + + // DATA BLOCK + // 0x220: target type + 0x03, 0x00, + + // 0x222: overlay type + 0x03, 0x00, + + // 0x224: entry count + 0x03, 0x00, + + // 0x226: entry offset + 0x03, 0x00, + + // 0x228, 0x22c, 0x230: entries + 0x00, 0x00, 0x00, 0x00, + + 0xff, 0xff, 0xff, 0xff, + + 0x01, 0x00, 0x00, 0x00}; + +const unsigned int idmap_raw_data_len = 565; + +const std::string GetTestDataPath(); + +class Idmap2Tests : public testing::Test { + protected: + virtual void SetUp() { +#ifdef __ANDROID__ + tmp_dir_path_ = "/data/local/tmp/idmap2-tests-XXXXXX"; +#else + tmp_dir_path_ = "/tmp/idmap2-tests-XXXXXX"; +#endif + EXPECT_NE(mkdtemp(const_cast<char*>(tmp_dir_path_.c_str())), nullptr) + << "Failed to create temporary directory: " << strerror(errno); + target_apk_path_ = GetTestDataPath() + "/target/target.apk"; + overlay_apk_path_ = GetTestDataPath() + "/overlay/overlay.apk"; + idmap_path_ = tmp_dir_path_ + "/a.idmap"; + } + + virtual void TearDown() { + EXPECT_EQ(rmdir(tmp_dir_path_.c_str()), 0) + << "Failed to remove temporary directory " << tmp_dir_path_ << ": " << strerror(errno); + } + + const std::string& GetTempDirPath() { + return tmp_dir_path_; + } + + const std::string& GetTargetApkPath() { + return target_apk_path_; + } + + const std::string& GetOverlayApkPath() { + return overlay_apk_path_; + } + + const std::string& GetIdmapPath() { + return idmap_path_; + } + + private: + std::string tmp_dir_path_; + std::string target_apk_path_; + std::string overlay_apk_path_; + std::string idmap_path_; +}; + +} // namespace idmap2 +} // namespace android + +#endif // IDMAP2_TESTS_TESTHELPERS_H_ diff --git a/cmds/idmap2/tests/XmlTests.cpp b/cmds/idmap2/tests/XmlTests.cpp new file mode 100644 index 000000000000..97ff03e0f9e3 --- /dev/null +++ b/cmds/idmap2/tests/XmlTests.cpp @@ -0,0 +1,72 @@ +/* + * 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 <cstdio> // fclose + +#include "idmap2/Xml.h" +#include "idmap2/ZipFile.h" + +#include "gmock/gmock.h" +#include "gtest/gtest.h" + +#include "TestHelpers.h" + +using ::testing::IsNull; +using ::testing::NotNull; + +namespace android { +namespace idmap2 { + +TEST(XmlTests, Create) { + auto zip = ZipFile::Open(GetTestDataPath() + "/target/target.apk"); + ASSERT_THAT(zip, NotNull()); + + auto data = zip->Uncompress("AndroidManifest.xml"); + ASSERT_THAT(data, NotNull()); + + auto xml = Xml::Create(data->buf, data->size); + ASSERT_THAT(xml, NotNull()); + + fclose(stderr); // silence expected warnings from libandroidfw + const char* not_xml = "foo"; + auto fail = Xml::Create(reinterpret_cast<const uint8_t*>(not_xml), strlen(not_xml)); + ASSERT_THAT(fail, IsNull()); +} + +TEST(XmlTests, FindTag) { + auto zip = ZipFile::Open(GetTestDataPath() + "/target/target.apk"); + ASSERT_THAT(zip, NotNull()); + + auto data = zip->Uncompress("res/xml/test.xml"); + ASSERT_THAT(data, NotNull()); + + auto xml = Xml::Create(data->buf, data->size); + ASSERT_THAT(xml, NotNull()); + + auto attrs = xml->FindTag("c"); + ASSERT_THAT(attrs, NotNull()); + ASSERT_EQ(attrs->size(), 4u); + ASSERT_EQ(attrs->at("type_string"), "fortytwo"); + ASSERT_EQ(std::stoi(attrs->at("type_int_dec")), 42); + ASSERT_EQ(std::stoi(attrs->at("type_int_hex")), 42); + ASSERT_NE(std::stoul(attrs->at("type_int_boolean")), 0u); + + auto fail = xml->FindTag("does-not-exist"); + ASSERT_THAT(fail, IsNull()); +} + +} // namespace idmap2 +} // namespace android diff --git a/cmds/idmap2/tests/ZipFileTests.cpp b/cmds/idmap2/tests/ZipFileTests.cpp new file mode 100644 index 000000000000..a504d3126c05 --- /dev/null +++ b/cmds/idmap2/tests/ZipFileTests.cpp @@ -0,0 +1,72 @@ +/* + * 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 <cstdio> // fclose +#include <string> +#include <utility> + +#include "idmap2/ZipFile.h" + +#include "gmock/gmock.h" +#include "gtest/gtest.h" + +#include "TestHelpers.h" + +using ::testing::IsNull; +using ::testing::NotNull; + +namespace android { +namespace idmap2 { + +TEST(ZipFileTests, BasicOpen) { + auto zip = ZipFile::Open(GetTestDataPath() + "/target/target.apk"); + ASSERT_THAT(zip, NotNull()); + + fclose(stderr); // silence expected warnings from libziparchive + auto fail = ZipFile::Open(GetTestDataPath() + "/does-not-exist"); + ASSERT_THAT(fail, IsNull()); +} + +TEST(ZipFileTests, Crc) { + auto zip = ZipFile::Open(GetTestDataPath() + "/target/target.apk"); + ASSERT_THAT(zip, NotNull()); + + bool status; + uint32_t crc; + std::tie(status, crc) = zip->Crc("AndroidManifest.xml"); + ASSERT_TRUE(status); + ASSERT_EQ(crc, 0x762f3d24); + + std::tie(status, std::ignore) = zip->Crc("does-not-exist"); + ASSERT_FALSE(status); +} + +TEST(ZipFileTests, Uncompress) { + auto zip = ZipFile::Open(GetTestDataPath() + "/target/target.apk"); + ASSERT_THAT(zip, NotNull()); + + auto data = zip->Uncompress("assets/lorem-ipsum.txt"); + ASSERT_THAT(data, NotNull()); + const std::string lorem_ipsum("Lorem ipsum dolor sit amet.\n"); + ASSERT_THAT(data->size, lorem_ipsum.size()); + ASSERT_THAT(std::string(reinterpret_cast<const char*>(data->buf), data->size), lorem_ipsum); + + auto fail = zip->Uncompress("does-not-exist"); + ASSERT_THAT(fail, IsNull()); +} + +} // namespace idmap2 +} // namespace android diff --git a/cmds/idmap2/tests/data/overlay/AndroidManifest.xml b/cmds/idmap2/tests/data/overlay/AndroidManifest.xml new file mode 100644 index 000000000000..9f89d3121a82 --- /dev/null +++ b/cmds/idmap2/tests/data/overlay/AndroidManifest.xml @@ -0,0 +1,21 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- 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. +--> +<manifest + xmlns:android="http://schemas.android.com/apk/res/android" + package="test.overlay"> + <overlay + android:targetPackage="test.target" /> +</manifest> diff --git a/cmds/idmap2/tests/data/overlay/AndroidManifestStatic1.xml b/cmds/idmap2/tests/data/overlay/AndroidManifestStatic1.xml new file mode 100644 index 000000000000..39336cc7e76b --- /dev/null +++ b/cmds/idmap2/tests/data/overlay/AndroidManifestStatic1.xml @@ -0,0 +1,23 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- 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. +--> +<manifest + xmlns:android="http://schemas.android.com/apk/res/android" + package="test.overlay.static1"> + <overlay + android:targetPackage="test.target" + android:isStatic="true" + android:priority="1" /> +</manifest> diff --git a/cmds/idmap2/tests/data/overlay/AndroidManifestStatic2.xml b/cmds/idmap2/tests/data/overlay/AndroidManifestStatic2.xml new file mode 100644 index 000000000000..e1cc1758d8cc --- /dev/null +++ b/cmds/idmap2/tests/data/overlay/AndroidManifestStatic2.xml @@ -0,0 +1,23 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- 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. +--> +<manifest + xmlns:android="http://schemas.android.com/apk/res/android" + package="test.overlay.static2"> + <overlay + android:targetPackage="test.target" + android:isStatic="true" + android:priority="2" /> +</manifest> diff --git a/cmds/idmap2/tests/data/overlay/build b/cmds/idmap2/tests/data/overlay/build new file mode 100644 index 000000000000..cba108674005 --- /dev/null +++ b/cmds/idmap2/tests/data/overlay/build @@ -0,0 +1,40 @@ +# 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. + +FRAMEWORK_RES_APK="$(gettop)/out/target/common/obj/APPS/framework-res_intermediates/package-export.apk" + +aapt2 compile --dir res -o compiled.flata + +aapt2 link \ + --no-resource-removal \ + -I "$FRAMEWORK_RES_APK" \ + --manifest AndroidManifest.xml \ + -o overlay.apk \ + compiled.flata + +aapt2 link \ + --no-resource-removal \ + -I "$FRAMEWORK_RES_APK" \ + --manifest AndroidManifestStatic1.xml \ + -o overlay-static-1.apk \ + compiled.flata + +aapt2 link \ + --no-resource-removal \ + -I "$FRAMEWORK_RES_APK" \ + --manifest AndroidManifestStatic2.xml \ + -o overlay-static-2.apk \ + compiled.flata + +rm compiled.flata diff --git a/cmds/idmap2/tests/data/overlay/overlay-static-1.apk b/cmds/idmap2/tests/data/overlay/overlay-static-1.apk Binary files differnew file mode 100644 index 000000000000..9a0f487522c8 --- /dev/null +++ b/cmds/idmap2/tests/data/overlay/overlay-static-1.apk diff --git a/cmds/idmap2/tests/data/overlay/overlay-static-2.apk b/cmds/idmap2/tests/data/overlay/overlay-static-2.apk Binary files differnew file mode 100644 index 000000000000..3fc31c7d11b0 --- /dev/null +++ b/cmds/idmap2/tests/data/overlay/overlay-static-2.apk diff --git a/cmds/idmap2/tests/data/overlay/overlay.apk b/cmds/idmap2/tests/data/overlay/overlay.apk Binary files differnew file mode 100644 index 000000000000..b4cd7cfc3248 --- /dev/null +++ b/cmds/idmap2/tests/data/overlay/overlay.apk diff --git a/cmds/idmap2/tests/data/overlay/res/values-sv/values.xml b/cmds/idmap2/tests/data/overlay/res/values-sv/values.xml new file mode 100644 index 000000000000..eed0b3dac1ab --- /dev/null +++ b/cmds/idmap2/tests/data/overlay/res/values-sv/values.xml @@ -0,0 +1,19 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- 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. +--> +<resources> + <string name="str1">overlay-1-sv</string> + <string name="str4">overlay-4-sv</string> +</resources> diff --git a/cmds/idmap2/tests/data/overlay/res/values/values.xml b/cmds/idmap2/tests/data/overlay/res/values/values.xml new file mode 100644 index 000000000000..815d1a88fa7b --- /dev/null +++ b/cmds/idmap2/tests/data/overlay/res/values/values.xml @@ -0,0 +1,21 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- 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. +--> +<resources> + <string name="str1">overlay-1</string> + <string name="str3">overlay-3</string> + <integer name="int1">-1</integer> + <integer name="not_in_target">-1</integer> +</resources> diff --git a/cmds/idmap2/tests/data/target/AndroidManifest.xml b/cmds/idmap2/tests/data/target/AndroidManifest.xml new file mode 100644 index 000000000000..3a861b4800fa --- /dev/null +++ b/cmds/idmap2/tests/data/target/AndroidManifest.xml @@ -0,0 +1,19 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- 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. +--> +<manifest + xmlns:android="http://schemas.android.com/apk/res/android" + package="test.target"> +</manifest> diff --git a/cmds/idmap2/tests/data/target/assets/lorem-ipsum.txt b/cmds/idmap2/tests/data/target/assets/lorem-ipsum.txt new file mode 100644 index 000000000000..d2cf010d36ff --- /dev/null +++ b/cmds/idmap2/tests/data/target/assets/lorem-ipsum.txt @@ -0,0 +1 @@ +Lorem ipsum dolor sit amet. diff --git a/cmds/idmap2/tests/data/target/build b/cmds/idmap2/tests/data/target/build new file mode 100644 index 000000000000..8569c4ff0a6b --- /dev/null +++ b/cmds/idmap2/tests/data/target/build @@ -0,0 +1,17 @@ +# 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. + +aapt2 compile --dir res -o compiled.flata +aapt2 link --manifest AndroidManifest.xml -A assets -o target.apk compiled.flata +rm compiled.flata diff --git a/cmds/idmap2/tests/data/target/res/values/values.xml b/cmds/idmap2/tests/data/target/res/values/values.xml new file mode 100644 index 000000000000..56bf0d60021a --- /dev/null +++ b/cmds/idmap2/tests/data/target/res/values/values.xml @@ -0,0 +1,28 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- 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. +--> +<resources> + <string name="a">a</string> + <string name="b">b</string> + <string name="c">c</string> + <string name="str1">target-1</string> + <string name="str2">target-2</string> + <string name="str3">target-3</string> + <string name="str4">target-4</string> + <string name="x">x</string> + <string name="y">y</string> + <string name="z">z</string> + <integer name="int1">1</integer> +</resources> diff --git a/cmds/idmap2/tests/data/target/res/xml/test.xml b/cmds/idmap2/tests/data/target/res/xml/test.xml new file mode 100644 index 000000000000..0fe21c6b6d0a --- /dev/null +++ b/cmds/idmap2/tests/data/target/res/xml/test.xml @@ -0,0 +1,25 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- 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. +--> +<a> + <b> + <c + type_string="fortytwo" + type_int_dec="42" + type_int_hex="0x2a" + type_int_boolean="true" + /> + </b> +</a> diff --git a/cmds/idmap2/tests/data/target/target.apk b/cmds/idmap2/tests/data/target/target.apk Binary files differnew file mode 100644 index 000000000000..18ecc276caae --- /dev/null +++ b/cmds/idmap2/tests/data/target/target.apk |