From 5ee3dafa98443cef8c0139aa6b2b9a4766412615 Mon Sep 17 00:00:00 2001 From: =?utf8?q?M=C3=A5rten=20Kongstad?= Date: Fri, 31 Jan 2014 14:00:54 +0100 Subject: [PATCH] New command line tool 'idmap' Introduce a new tool 'idmap' to handle generation and verification of idmap files. The tool is modelled on 'dexopt', and is intended to be used similarly, notably by 'installd'. See cmds/idmap/idmap.cpp for further documentation on 'idmap'. Note: this commit is interdependent on a commit in project build/ to add 'idmap' to PRODUCT_PACKAGES. Note: the changes to androidfw are only stubs. The actual implementation will be provided in Runtime resource overlay, iteration 2. Change-Id: I7131b74ece1e46c8a9c0a31d103e686aa07da2bb --- cmds/idmap/Android.mk | 28 +++++ cmds/idmap/create.cpp | 222 +++++++++++++++++++++++++++++++++++++ cmds/idmap/idmap.cpp | 237 ++++++++++++++++++++++++++++++++++++++++ cmds/idmap/idmap.h | 34 ++++++ cmds/idmap/inspect.cpp | 291 +++++++++++++++++++++++++++++++++++++++++++++++++ cmds/idmap/scan.cpp | 244 +++++++++++++++++++++++++++++++++++++++++ 6 files changed, 1056 insertions(+) create mode 100644 cmds/idmap/Android.mk create mode 100644 cmds/idmap/create.cpp create mode 100644 cmds/idmap/idmap.cpp create mode 100644 cmds/idmap/idmap.h create mode 100644 cmds/idmap/inspect.cpp create mode 100644 cmds/idmap/scan.cpp diff --git a/cmds/idmap/Android.mk b/cmds/idmap/Android.mk new file mode 100644 index 0000000000..ffa83f2ba4 --- /dev/null +++ b/cmds/idmap/Android.mk @@ -0,0 +1,28 @@ +# Copyright (C) 2012 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. + +LOCAL_PATH:= $(call my-dir) +include $(CLEAR_VARS) + +LOCAL_SRC_FILES := idmap.cpp create.cpp scan.cpp inspect.cpp + +LOCAL_SHARED_LIBRARIES := liblog libutils libandroidfw + +LOCAL_MODULE := idmap + +LOCAL_C_INCLUDES := external/zlib + +LOCAL_MODULE_TAGS := optional + +include $(BUILD_EXECUTABLE) diff --git a/cmds/idmap/create.cpp b/cmds/idmap/create.cpp new file mode 100644 index 0000000000..ae35f7b0a8 --- /dev/null +++ b/cmds/idmap/create.cpp @@ -0,0 +1,222 @@ +#include "idmap.h" + +#include +#include +#include +#include +#include + +#include +#include + +using namespace android; + +namespace { + int get_zip_entry_crc(const char *zip_path, const char *entry_name, uint32_t *crc) + { + UniquePtr zip(ZipFileRO::open(zip_path)); + if (zip.get() == NULL) { + return -1; + } + ZipEntryRO entry = zip->findEntryByName(entry_name); + if (entry == NULL) { + return -1; + } + if (!zip->getEntryInfo(entry, NULL, NULL, NULL, NULL, NULL, (long*)crc)) { + return -1; + } + zip->releaseEntry(entry); + return 0; + } + + int open_idmap(const char *path) + { + int fd = TEMP_FAILURE_RETRY(open(path, O_WRONLY | O_CREAT | O_TRUNC, 0644)); + if (fd == -1) { + ALOGD("error: open %s: %s\n", path, strerror(errno)); + goto fail; + } + if (fchmod(fd, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH) < 0) { + ALOGD("error: fchmod %s: %s\n", path, strerror(errno)); + goto fail; + } + if (TEMP_FAILURE_RETRY(flock(fd, LOCK_EX | LOCK_NB)) != 0) { + ALOGD("error: flock %s: %s\n", path, strerror(errno)); + goto fail; + } + + return fd; +fail: + if (fd != -1) { + close(fd); + unlink(path); + } + return -1; + } + + int write_idmap(int fd, const uint32_t *data, size_t size) + { + if (lseek(fd, SEEK_SET, 0) < 0) { + return -1; + } + size_t bytesLeft = size; + while (bytesLeft > 0) { + ssize_t w = TEMP_FAILURE_RETRY(write(fd, data + size - bytesLeft, bytesLeft)); + if (w < 0) { + fprintf(stderr, "error: write: %s\n", strerror(errno)); + return -1; + } + bytesLeft -= w; + } + return 0; + } + + bool is_idmap_stale_fd(const char *target_apk_path, const char *overlay_apk_path, int idmap_fd) + { + static const size_t N = ResTable::IDMAP_HEADER_SIZE_BYTES; + struct stat st; + if (fstat(idmap_fd, &st) == -1) { + return true; + } + if (st.st_size < N) { + // file is empty or corrupt + return true; + } + + char buf[N]; + ssize_t bytesLeft = N; + if (lseek(idmap_fd, SEEK_SET, 0) < 0) { + return true; + } + for (;;) { + ssize_t r = TEMP_FAILURE_RETRY(read(idmap_fd, buf + N - bytesLeft, bytesLeft)); + if (r < 0) { + return true; + } + bytesLeft -= r; + if (bytesLeft == 0) { + break; + } + if (r == 0) { + // "shouldn't happen" + return true; + } + } + + uint32_t cached_target_crc, cached_overlay_crc; + String8 cached_target_path, cached_overlay_path; + if (!ResTable::getIdmapInfo(buf, N, &cached_target_crc, &cached_overlay_crc, + &cached_target_path, &cached_overlay_path)) { + return true; + } + + if (cached_target_path != target_apk_path) { + return true; + } + if (cached_overlay_path != overlay_apk_path) { + return true; + } + + uint32_t actual_target_crc, actual_overlay_crc; + if (get_zip_entry_crc(target_apk_path, AssetManager::RESOURCES_FILENAME, + &actual_target_crc) == -1) { + return true; + } + if (get_zip_entry_crc(overlay_apk_path, AssetManager::RESOURCES_FILENAME, + &actual_overlay_crc) == -1) { + return true; + } + + return cached_target_crc != actual_target_crc || cached_overlay_crc != actual_overlay_crc; + } + + bool is_idmap_stale_path(const char *target_apk_path, const char *overlay_apk_path, + const char *idmap_path) + { + struct stat st; + if (stat(idmap_path, &st) == -1) { + // non-existing idmap is always stale; on other errors, abort idmap generation + return errno == ENOENT; + } + + int idmap_fd = TEMP_FAILURE_RETRY(open(idmap_path, O_RDONLY)); + if (idmap_fd == -1) { + return false; + } + bool is_stale = is_idmap_stale_fd(target_apk_path, overlay_apk_path, idmap_fd); + close(idmap_fd); + return is_stale; + } + + int create_idmap(const char *target_apk_path, const char *overlay_apk_path, + uint32_t **data, size_t *size) + { + uint32_t target_crc, overlay_crc; + if (get_zip_entry_crc(target_apk_path, AssetManager::RESOURCES_FILENAME, + &target_crc) == -1) { + return -1; + } + if (get_zip_entry_crc(overlay_apk_path, AssetManager::RESOURCES_FILENAME, + &overlay_crc) == -1) { + return -1; + } + + AssetManager am; + bool b = am.createIdmap(target_apk_path, overlay_apk_path, target_crc, overlay_crc, + data, size); + return b ? 0 : -1; + } + + int create_and_write_idmap(const char *target_apk_path, const char *overlay_apk_path, + int fd, bool check_if_stale) + { + if (check_if_stale) { + if (!is_idmap_stale_fd(target_apk_path, overlay_apk_path, fd)) { + // already up to date -- nothing to do + return 0; + } + } + + uint32_t *data = NULL; + size_t size; + + if (create_idmap(target_apk_path, overlay_apk_path, &data, &size) == -1) { + return -1; + } + + if (write_idmap(fd, data, size) == -1) { + free(data); + return -1; + } + + free(data); + return 0; + } +} + +int idmap_create_path(const char *target_apk_path, const char *overlay_apk_path, + const char *idmap_path) +{ + if (!is_idmap_stale_path(target_apk_path, overlay_apk_path, idmap_path)) { + // already up to date -- nothing to do + return EXIT_SUCCESS; + } + + int fd = open_idmap(idmap_path); + if (fd == -1) { + return EXIT_FAILURE; + } + + int r = create_and_write_idmap(target_apk_path, overlay_apk_path, fd, false); + close(fd); + if (r != 0) { + unlink(idmap_path); + } + return r == 0 ? EXIT_SUCCESS : EXIT_FAILURE; +} + +int idmap_create_fd(const char *target_apk_path, const char *overlay_apk_path, int fd) +{ + return create_and_write_idmap(target_apk_path, overlay_apk_path, fd, true) == 0 ? + EXIT_SUCCESS : EXIT_FAILURE; +} diff --git a/cmds/idmap/idmap.cpp b/cmds/idmap/idmap.cpp new file mode 100644 index 0000000000..46c0edc035 --- /dev/null +++ b/cmds/idmap/idmap.cpp @@ -0,0 +1,237 @@ +#include "idmap.h" + +#include // for AID_SYSTEM + +#include +#include + +namespace { + const char *usage = "NAME\n\ + idmap - create or display idmap files\n\ +\n\ +SYNOPSIS \n\ + idmap --help \n\ + idmap --fd target overlay fd \n\ + idmap --path target overlay idmap \n\ + idmap --scan dir-to-scan target-to-look-for target dir-to-hold-idmaps \n\ + idmap --inspect idmap \n\ +\n\ +DESCRIPTION \n\ + Idmap files play an integral part in the runtime resource overlay framework. An idmap \n\ + file contains a mapping of resource identifiers between overlay package and its target \n\ + package; this mapping is used during resource lookup. Idmap files also act as control \n\ + files by their existence: if not present, the corresponding overlay package is ignored \n\ + when the resource context is created. \n\ +\n\ + Idmap files are stored in /data/resource-cache. For each pair (target package, overlay \n\ + package), there exists exactly one idmap file, or none if the overlay should not be used. \n\ +\n\ +NOMENCLATURE \n\ + target: the original, non-overlay, package. Each target package may be associated with \n\ + any number of overlay packages. \n\ +\n\ + overlay: an overlay package. Each overlay package is associated with exactly one target \n\ + package, specified in the overlay's manifest using the \n\ + tag. \n\ +\n\ +OPTIONS \n\ + --help: display this help \n\ +\n\ + --fd: create idmap for target package 'target' (path to apk) and overlay package 'overlay' \n\ + (path to apk); write results to file descriptor 'fd' (integer). This invocation \n\ + version is intended to be used by a parent process with higher privileges to call \n\ + idmap in a controlled way: the parent will open a suitable file descriptor, fork, \n\ + drop its privileges and exec. This tool will continue execution without the extra \n\ + privileges, but still have write access to a file it could not have opened on its \n\ + own. \n\ +\n\ + --path: create idmap for target package 'target' (path to apk) and overlay package \n\ + 'overlay' (path to apk); write results to 'idmap' (path). \n\ +\n\ + --scan: non-recursively search directory 'dir-to-scan' (path) for overlay packages with \n\ + target package 'target-to-look-for' (package name) present at 'target' (path to \n\ + apk). For each overlay package found, create an idmap file in 'dir-to-hold-idmaps' \n\ + (path). \n\ +\n\ + --inspect: decode the binary format of 'idmap' (path) and display the contents in a \n\ + debug-friendly format. \n\ +\n\ +EXAMPLES \n\ + Create an idmap file: \n\ +\n\ + $ adb shell idmap --path /system/app/target.apk \\ \n\ + /vendor/overlay/overlay.apk \\ \n\ + /data/resource-cache/vendor@overlay@overlay.apk@idmap \n\ +\n\ + Display an idmap file: \n\ +\n\ + $ adb shell idmap --inspect /data/resource-cache/vendor@overlay@overlay.apk@idmap \n\ + SECTION ENTRY VALUE OFFSET COMMENT \n\ + IDMAP HEADER magic 0x706d6469 0x0 \n\ + base crc 0x484aa77f 0x1 \n\ + overlay crc 0x03c66fa5 0x2 \n\ + base path .......... 0x03-0x42 /system/app/target.apk \n\ + overlay path .......... 0x43-0x82 /vendor/overlay/overlay.apk \n\ + DATA HEADER types count 0x00000003 0x83 \n\ + padding 0x00000000 0x84 \n\ + type offset 0x00000004 0x85 absolute offset 0x87, xml \n\ + type offset 0x00000007 0x86 absolute offset 0x8a, string \n\ + DATA BLOCK entry count 0x00000001 0x87 \n\ + entry offset 0x00000000 0x88 \n\ + entry 0x7f020000 0x89 xml/integer \n\ + DATA BLOCK entry count 0x00000002 0x8a \n\ + entry offset 0x00000000 0x8b \n\ + entry 0x7f030000 0x8c string/str \n\ + entry 0x7f030001 0x8d string/str2 \n\ +\n\ + In this example, the overlay package provides three alternative resource values:\n\ + xml/integer, string/str and string/str2.\n\ +\n\ +NOTES \n\ + This tool and its expected invocation from installd is modelled on dexopt."; + + bool verify_directory_readable(const char *path) + { + return access(path, R_OK | X_OK) == 0; + } + + bool verify_directory_writable(const char *path) + { + return access(path, W_OK) == 0; + } + + bool verify_file_readable(const char *path) + { + return access(path, R_OK) == 0; + } + + bool verify_root_or_system() + { + uid_t uid = getuid(); + gid_t gid = getgid(); + + return (uid == 0 && gid == 0) || (uid == AID_SYSTEM && gid == AID_SYSTEM); + } + + int maybe_create_fd(const char *target_apk_path, const char *overlay_apk_path, + const char *idmap_str) + { + // anyone (not just root or system) may do --fd -- the file has + // already been opened by someone else on our behalf + + char *endptr; + int idmap_fd = strtol(idmap_str, &endptr, 10); + if (*endptr != '\0') { + fprintf(stderr, "error: failed to parse file descriptor argument %s\n", idmap_str); + return -1; + } + + if (!verify_file_readable(target_apk_path)) { + ALOGD("error: failed to read apk %s: %s\n", target_apk_path, strerror(errno)); + return -1; + } + + if (!verify_file_readable(overlay_apk_path)) { + ALOGD("error: failed to read apk %s: %s\n", overlay_apk_path, strerror(errno)); + return -1; + } + + return idmap_create_fd(target_apk_path, overlay_apk_path, idmap_fd); + } + + int maybe_create_path(const char *target_apk_path, const char *overlay_apk_path, + const char *idmap_path) + { + if (!verify_root_or_system()) { + fprintf(stderr, "error: permission denied: not user root or user system\n"); + return -1; + } + + if (!verify_file_readable(target_apk_path)) { + ALOGD("error: failed to read apk %s: %s\n", target_apk_path, strerror(errno)); + return -1; + } + + if (!verify_file_readable(overlay_apk_path)) { + ALOGD("error: failed to read apk %s: %s\n", overlay_apk_path, strerror(errno)); + return -1; + } + + return idmap_create_path(target_apk_path, overlay_apk_path, idmap_path); + } + + int maybe_scan(const char *overlay_dir, const char *target_package_name, + const char *target_apk_path, const char *idmap_dir) + { + if (!verify_root_or_system()) { + fprintf(stderr, "error: permission denied: not user root or user system\n"); + return -1; + } + + if (!verify_directory_readable(overlay_dir)) { + ALOGD("error: no read access to %s: %s\n", overlay_dir, strerror(errno)); + return -1; + } + + if (!verify_file_readable(target_apk_path)) { + ALOGD("error: failed to read apk %s: %s\n", target_apk_path, strerror(errno)); + return -1; + } + + if (!verify_directory_writable(idmap_dir)) { + ALOGD("error: no write access to %s: %s\n", idmap_dir, strerror(errno)); + return -1; + } + + return idmap_scan(overlay_dir, target_package_name, target_apk_path, idmap_dir); + } + + int maybe_inspect(const char *idmap_path) + { + // anyone (not just root or system) may do --inspect + if (!verify_file_readable(idmap_path)) { + ALOGD("error: failed to read idmap %s: %s\n", idmap_path, strerror(errno)); + return -1; + } + return idmap_inspect(idmap_path); + } +} + +int main(int argc, char **argv) +{ +#if 0 + { + char buf[1024]; + buf[0] = '\0'; + for (int i = 0; i < argc; ++i) { + strncat(buf, argv[i], sizeof(buf) - 1); + strncat(buf, " ", sizeof(buf) - 1); + } + ALOGD("%s:%d: uid=%d gid=%d argv=%s\n", __FILE__, __LINE__, getuid(), getgid(), buf); + } +#endif + + if (argc == 2 && !strcmp(argv[1], "--help")) { + printf("%s\n", usage); + return 0; + } + + if (argc == 5 && !strcmp(argv[1], "--fd")) { + return maybe_create_fd(argv[2], argv[3], argv[4]); + } + + if (argc == 5 && !strcmp(argv[1], "--path")) { + return maybe_create_path(argv[2], argv[3], argv[4]); + } + + if (argc == 6 && !strcmp(argv[1], "--scan")) { + return maybe_scan(argv[2], argv[3], argv[4], argv[5]); + } + + if (argc == 3 && !strcmp(argv[1], "--inspect")) { + return maybe_inspect(argv[2]); + } + + fprintf(stderr, "Usage: don't use this (cf dexopt usage).\n"); + return EXIT_FAILURE; +} diff --git a/cmds/idmap/idmap.h b/cmds/idmap/idmap.h new file mode 100644 index 0000000000..f507dd8530 --- /dev/null +++ b/cmds/idmap/idmap.h @@ -0,0 +1,34 @@ +#ifndef _IDMAP_H_ +#define _IDMAP_H_ + +#define LOG_TAG "idmap" + +#include + +#include +#include + +#ifndef TEMP_FAILURE_RETRY +// Used to retry syscalls that can return EINTR. +#define TEMP_FAILURE_RETRY(exp) ({ \ + typeof (exp) _rc; \ + do { \ + _rc = (exp); \ + } while (_rc == -1 && errno == EINTR); \ + _rc; }) +#endif + +int idmap_create_path(const char *target_apk_path, const char *overlay_apk_path, + const char *idmap_path); + +int idmap_create_fd(const char *target_apk_path, const char *overlay_apk_path, int fd); + +// Regarding target_package_name: the idmap_scan implementation should +// be able to extract this from the manifest in target_apk_path, +// simplifying the external API. +int idmap_scan(const char *overlay_dir, const char *target_package_name, + const char *target_apk_path, const char *idmap_dir); + +int idmap_inspect(const char *idmap_path); + +#endif // _IDMAP_H_ diff --git a/cmds/idmap/inspect.cpp b/cmds/idmap/inspect.cpp new file mode 100644 index 0000000000..a59f5d31ca --- /dev/null +++ b/cmds/idmap/inspect.cpp @@ -0,0 +1,291 @@ +#include "idmap.h" + +#include +#include +#include + +#include +#include +#include + +using namespace android; + +#define NEXT(b, i, o) do { if (buf.next(&i, &o) < 0) { return -1; } } while (0) + +namespace { + static const uint32_t IDMAP_MAGIC = 0x706d6469; + static const size_t PATH_LENGTH = 256; + static const uint32_t IDMAP_HEADER_SIZE = (3 + 2 * (PATH_LENGTH / sizeof(uint32_t))); + + void printe(const char *fmt, ...); + + class IdmapBuffer { + private: + char *buf_; + size_t len_; + mutable size_t pos_; + public: + IdmapBuffer() : buf_((char *)MAP_FAILED), len_(0), pos_(0) {} + + ~IdmapBuffer() { + if (buf_ != MAP_FAILED) { + munmap(buf_, len_); + } + } + + int init(const char *idmap_path) + { + struct stat st; + int fd; + + if (stat(idmap_path, &st) < 0) { + printe("failed to stat idmap '%s': %s\n", idmap_path, strerror(errno)); + return -1; + } + len_ = st.st_size; + if ((fd = TEMP_FAILURE_RETRY(open(idmap_path, O_RDONLY))) < 0) { + printe("failed to open idmap '%s': %s\n", idmap_path, strerror(errno)); + return -1; + } + if ((buf_ = (char*)mmap(NULL, len_, PROT_READ, MAP_PRIVATE, fd, 0)) == MAP_FAILED) { + close(fd); + printe("failed to mmap idmap: %s\n", strerror(errno)); + return -1; + } + close(fd); + return 0; + } + + int next(uint32_t *i, uint32_t *offset) const + { + if (!buf_) { + printe("failed to read next uint32_t: buffer not initialized\n"); + return -1; + } + if (pos_ + 4 > len_) { + printe("failed to read next uint32_t: end of buffer reached at pos=0x%08x\n", + pos_); + return -1; + } + *offset = pos_ / sizeof(uint32_t); + char a = buf_[pos_++]; + char b = buf_[pos_++]; + char c = buf_[pos_++]; + char d = buf_[pos_++]; + *i = (d << 24) | (c << 16) | (b << 8) | a; + return 0; + } + + int nextPath(char *b, uint32_t *offset_start, uint32_t *offset_end) const + { + if (!buf_) { + printe("failed to read next path: buffer not initialized\n"); + return -1; + } + if (pos_ + PATH_LENGTH > len_) { + printe("failed to read next path: end of buffer reached at pos=0x%08x\n", pos_); + return -1; + } + memcpy(b, buf_ + pos_, PATH_LENGTH); + *offset_start = pos_ / sizeof(uint32_t); + pos_ += PATH_LENGTH; + *offset_end = pos_ / sizeof(uint32_t) - 1; + return 0; + } + }; + + void printe(const char *fmt, ...) + { + va_list ap; + + va_start(ap, fmt); + fprintf(stderr, "error: "); + vfprintf(stderr, fmt, ap); + va_end(ap); + } + + void print_header() + { + printf("SECTION ENTRY VALUE OFFSET COMMENT\n"); + } + + void print(const char *section, const char *subsection, uint32_t value, uint32_t offset, + const char *fmt, ...) + { + va_list ap; + + va_start(ap, fmt); + printf("%-12s %-12s 0x%08x 0x%-4x ", section, subsection, value, offset); + vprintf(fmt, ap); + printf("\n"); + va_end(ap); + } + + void print_path(const char *section, const char *subsection, uint32_t offset_start, + uint32_t offset_end, const char *fmt, ...) + { + va_list ap; + + va_start(ap, fmt); + printf("%-12s %-12s .......... 0x%02x-0x%02x ", section, subsection, offset_start, + offset_end); + vprintf(fmt, ap); + printf("\n"); + va_end(ap); + } + + int resource_metadata(const AssetManager& am, uint32_t res_id, + String8 *package, String8 *type, String8 *name) + { + const ResTable& rt = am.getResources(); + struct ResTable::resource_name data; + if (!rt.getResourceName(res_id, false, &data)) { + printe("failed to get resource name id=0x%08x\n", res_id); + return -1; + } + if (package) { + *package = String8(String16(data.package, data.packageLen)); + } + if (type) { + *type = String8(String16(data.type, data.typeLen)); + } + if (name) { + *name = String8(String16(data.name, data.nameLen)); + } + return 0; + } + + int package_id(const AssetManager& am) + { + return (am.getResources().getBasePackageId(0)) << 24; + } + + int parse_idmap_header(const IdmapBuffer& buf, AssetManager& am) + { + uint32_t i, o, e; + char path[PATH_LENGTH]; + + NEXT(buf, i, o); + if (i != IDMAP_MAGIC) { + printe("not an idmap file: actual magic constant 0x%08x does not match expected magic " + "constant 0x%08x\n", i, IDMAP_MAGIC); + return -1; + } + print_header(); + print("IDMAP HEADER", "magic", i, o, ""); + + NEXT(buf, i, o); + print("", "base crc", i, o, ""); + + NEXT(buf, i, o); + print("", "overlay crc", i, o, ""); + + if (buf.nextPath(path, &o, &e) < 0) { + // printe done from IdmapBuffer::nextPath + return -1; + } + print_path("", "base path", o, e, "%s", path); + if (!am.addAssetPath(String8(path), NULL)) { + printe("failed to add '%s' as asset path\n", path); + return -1; + } + + if (buf.nextPath(path, &o, &e) < 0) { + // printe done from IdmapBuffer::nextPath + return -1; + } + print_path("", "overlay path", o, e, "%s", path); + + return 0; + } + + int parse_data_header(const IdmapBuffer& buf, const AssetManager& am, Vector& types) + { + uint32_t i, o; + const uint32_t numeric_package = package_id(am); + + NEXT(buf, i, o); + print("DATA HEADER", "types count", i, o, ""); + const uint32_t N = i; + + for (uint32_t j = 0; j < N; ++j) { + NEXT(buf, i, o); + if (i == 0) { + print("", "padding", i, o, ""); + } else { + String8 type; + const uint32_t numeric_type = (j + 1) << 16; + const uint32_t res_id = numeric_package | numeric_type; + if (resource_metadata(am, res_id, NULL, &type, NULL) < 0) { + // printe done from resource_metadata + return -1; + } + print("", "type offset", i, o, "absolute offset 0x%02x, %s", + i + IDMAP_HEADER_SIZE, type.string()); + types.add(numeric_type); + } + } + + return 0; + } + + int parse_data_block(const IdmapBuffer& buf, const AssetManager& am, size_t numeric_type) + { + uint32_t i, o, n, id_offset; + const uint32_t numeric_package = package_id(am); + + NEXT(buf, i, o); + print("DATA BLOCK", "entry count", i, o, ""); + n = i; + + NEXT(buf, i, o); + print("", "entry offset", i, o, ""); + id_offset = i; + + for ( ; n > 0; --n) { + String8 type, name; + + NEXT(buf, i, o); + if (i == 0) { + print("", "padding", i, o, ""); + } else { + uint32_t res_id = numeric_package | numeric_type | id_offset; + if (resource_metadata(am, res_id, NULL, &type, &name) < 0) { + // printe done from resource_metadata + return -1; + } + print("", "entry", i, o, "%s/%s", type.string(), name.string()); + } + ++id_offset; + } + + return 0; + } +} + +int idmap_inspect(const char *idmap_path) +{ + IdmapBuffer buf; + if (buf.init(idmap_path) < 0) { + // printe done from IdmapBuffer::init + return EXIT_FAILURE; + } + AssetManager am; + if (parse_idmap_header(buf, am) < 0) { + // printe done from parse_idmap_header + return EXIT_FAILURE; + } + Vector types; + if (parse_data_header(buf, am, types) < 0) { + // printe done from parse_data_header + return EXIT_FAILURE; + } + const size_t N = types.size(); + for (size_t i = 0; i < N; ++i) { + if (parse_data_block(buf, am, types.itemAt(i)) < 0) { + // printe done from parse_data_block + return EXIT_FAILURE; + } + } + return EXIT_SUCCESS; +} diff --git a/cmds/idmap/scan.cpp b/cmds/idmap/scan.cpp new file mode 100644 index 0000000000..c5fc941fa8 --- /dev/null +++ b/cmds/idmap/scan.cpp @@ -0,0 +1,244 @@ +#include "idmap.h" + +#include +#include +#include +#include +#include // for AID_SYSTEM +#include +#include +#include + +#include + +#define NO_OVERLAY_TAG (-1000) + +using namespace android; + +namespace { + struct Overlay { + Overlay() {} + Overlay(const String8& a, const String8& i, int p) : + apk_path(a), idmap_path(i), priority(p) {} + + bool operator<(Overlay const& rhs) const + { + // Note: order is reversed by design + return rhs.priority < priority; + } + + String8 apk_path; + String8 idmap_path; + int priority; + }; + + bool writePackagesList(const char *filename, const SortedVector& overlayVector) + { + FILE* fout = fopen(filename, "w"); + if (fout == NULL) { + return false; + } + + for (size_t i = 0; i < overlayVector.size(); ++i) { + const Overlay& overlay = overlayVector[i]; + fprintf(fout, "%s %s\n", overlay.apk_path.string(), overlay.idmap_path.string()); + } + + fclose(fout); + + // Make file world readable since Zygote (running as root) will read + // it when creating the initial AssetManger object + const mode_t mode = S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH; // 0644 + if (chmod(filename, mode) == -1) { + unlink(filename); + return false; + } + + return true; + } + + String8 flatten_path(const char *path) + { + String16 tmp(path); + tmp.replaceAll('/', '@'); + return String8(tmp); + } + + int mkdir_p(const String8& path, uid_t uid, gid_t gid) + { + static const mode_t mode = + S_IRUSR | S_IWUSR | S_IXUSR | S_IRGRP | S_IWGRP | S_IXGRP | S_IROTH | S_IXOTH; + struct stat st; + + if (stat(path.string(), &st) == 0) { + return 0; + } + if (mkdir_p(path.getPathDir(), uid, gid) < 0) { + return -1; + } + if (mkdir(path.string(), 0755) != 0) { + return -1; + } + if (chown(path.string(), uid, gid) == -1) { + return -1; + } + if (chmod(path.string(), mode) == -1) { + return -1; + } + return 0; + } + + int parse_overlay_tag(const ResXMLTree& parser, const char *target_package_name) + { + const size_t N = parser.getAttributeCount(); + String16 target; + int priority = -1; + for (size_t i = 0; i < N; ++i) { + size_t len; + String16 key(parser.getAttributeName(i, &len)); + if (key == String16("targetPackage")) { + const uint16_t *p = parser.getAttributeStringValue(i, &len); + if (p) { + target = String16(p, len); + } + } else if (key == String16("priority")) { + Res_value v; + if (parser.getAttributeValue(i, &v) == sizeof(Res_value)) { + priority = v.data; + if (priority < 0 || priority > 9999) { + return -1; + } + } + } + } + if (target == String16(target_package_name)) { + return priority; + } + return NO_OVERLAY_TAG; + } + + int parse_manifest(const void *data, size_t size, const char *target_package_name) + { + ResXMLTree parser(data, size); + if (parser.getError() != NO_ERROR) { + ALOGD("%s failed to init xml parser, error=0x%08x\n", __FUNCTION__, parser.getError()); + return -1; + } + + ResXMLParser::event_code_t type; + do { + type = parser.next(); + if (type == ResXMLParser::START_TAG) { + size_t len; + String16 tag(parser.getElementName(&len)); + if (tag == String16("overlay")) { + return parse_overlay_tag(parser, target_package_name); + } + } + } while (type != ResXMLParser::BAD_DOCUMENT && type != ResXMLParser::END_DOCUMENT); + + return NO_OVERLAY_TAG; + } + + int parse_apk(const char *path, const char *target_package_name) + { + UniquePtr zip(ZipFileRO::open(path)); + if (zip.get() == NULL) { + ALOGW("%s: failed to open zip %s\n", __FUNCTION__, path); + return -1; + } + ZipEntryRO entry; + if ((entry = zip->findEntryByName("AndroidManifest.xml")) == NULL) { + ALOGW("%s: failed to find entry AndroidManifest.xml\n", __FUNCTION__); + return -1; + } + size_t uncompLen = 0; + int method; + if (!zip->getEntryInfo(entry, &method, &uncompLen, NULL, NULL, NULL, NULL)) { + ALOGW("%s: failed to read entry info\n", __FUNCTION__); + return -1; + } + if (method != ZipFileRO::kCompressDeflated) { + ALOGW("%s: cannot handle zip compression method %d\n", __FUNCTION__, method); + return -1; + } + FileMap *dataMap = zip->createEntryFileMap(entry); + if (!dataMap) { + ALOGW("%s: failed to create FileMap\n", __FUNCTION__); + return -1; + } + char *buf = new char[uncompLen]; + if (NULL == buf) { + ALOGW("%s: failed to allocate %d byte\n", __FUNCTION__, uncompLen); + dataMap->release(); + return -1; + } + StreamingZipInflater inflater(dataMap, uncompLen); + if (inflater.read(buf, uncompLen) < 0) { + ALOGW("%s: failed to inflate %d byte\n", __FUNCTION__, uncompLen); + delete[] buf; + dataMap->release(); + return -1; + } + + int priority = parse_manifest(buf, uncompLen, target_package_name); + delete[] buf; + dataMap->release(); + return priority; + } +} + +int idmap_scan(const char *overlay_dir, const char *target_package_name, + const char *target_apk_path, const char *idmap_dir) +{ + String8 filename = String8(idmap_dir); + filename.appendPath("overlays.list"); + if (unlink(filename.string()) != 0 && errno != ENOENT) { + return EXIT_FAILURE; + } + + DIR *dir = opendir(overlay_dir); + if (dir == NULL) { + return EXIT_FAILURE; + } + + SortedVector overlayVector; + struct dirent *dirent; + while ((dirent = readdir(dir)) != NULL) { + struct stat st; + char overlay_apk_path[PATH_MAX + 1]; + snprintf(overlay_apk_path, PATH_MAX, "%s/%s", overlay_dir, dirent->d_name); + if (stat(overlay_apk_path, &st) < 0) { + continue; + } + if (!S_ISREG(st.st_mode)) { + continue; + } + + int priority = parse_apk(overlay_apk_path, target_package_name); + if (priority < 0) { + continue; + } + + String8 idmap_path(idmap_dir); + idmap_path.appendPath(flatten_path(overlay_apk_path + 1)); + idmap_path.append("@idmap"); + + if (idmap_create_path(target_apk_path, overlay_apk_path, idmap_path.string()) != 0) { + ALOGE("error: failed to create idmap for target=%s overlay=%s idmap=%s\n", + target_apk_path, overlay_apk_path, idmap_path.string()); + continue; + } + + Overlay overlay(String8(overlay_apk_path), idmap_path, priority); + overlayVector.add(overlay); + } + + closedir(dir); + + if (!writePackagesList(filename.string(), overlayVector)) { + return EXIT_FAILURE; + } + + return EXIT_SUCCESS; +} -- 2.11.0