am 340aa8cb: Merge "New command line tool \'idmap\'"

* commit '340aa8cbd4afbb68b16f369336cc1c202582f021':
  New command line tool 'idmap'
diff --git a/cmds/idmap/Android.mk b/cmds/idmap/Android.mk
new file mode 100644
index 0000000..ffa83f2
--- /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 0000000..ae35f7b
--- /dev/null
+++ b/cmds/idmap/create.cpp
@@ -0,0 +1,222 @@
+#include "idmap.h"
+
+#include <UniquePtr.h>
+#include <androidfw/AssetManager.h>
+#include <androidfw/ResourceTypes.h>
+#include <androidfw/ZipFileRO.h>
+#include <utils/String8.h>
+
+#include <fcntl.h>
+#include <sys/stat.h>
+
+using namespace android;
+
+namespace {
+    int get_zip_entry_crc(const char *zip_path, const char *entry_name, uint32_t *crc)
+    {
+        UniquePtr<ZipFileRO> 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 0000000..46c0edc
--- /dev/null
+++ b/cmds/idmap/idmap.cpp
@@ -0,0 +1,237 @@
+#include "idmap.h"
+
+#include <private/android_filesystem_config.h> // for AID_SYSTEM
+
+#include <stdlib.h>
+#include <string.h>
+
+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 <overlay target=\"...\"/> \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 0000000..f507dd8
--- /dev/null
+++ b/cmds/idmap/idmap.h
@@ -0,0 +1,34 @@
+#ifndef _IDMAP_H_
+#define _IDMAP_H_
+
+#define LOG_TAG "idmap"
+
+#include <utils/Log.h>
+
+#include <errno.h>
+#include <stdio.h>
+
+#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 0000000..a59f5d3
--- /dev/null
+++ b/cmds/idmap/inspect.cpp
@@ -0,0 +1,291 @@
+#include "idmap.h"
+
+#include <androidfw/AssetManager.h>
+#include <androidfw/ResourceTypes.h>
+#include <utils/String8.h>
+
+#include <fcntl.h>
+#include <sys/mman.h>
+#include <sys/stat.h>
+
+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<uint32_t>& 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<uint32_t> 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 0000000..c5fc941
--- /dev/null
+++ b/cmds/idmap/scan.cpp
@@ -0,0 +1,244 @@
+#include "idmap.h"
+
+#include <UniquePtr.h>
+#include <androidfw/ResourceTypes.h>
+#include <androidfw/StreamingZipInflater.h>
+#include <androidfw/ZipFileRO.h>
+#include <private/android_filesystem_config.h> // for AID_SYSTEM
+#include <utils/SortedVector.h>
+#include <utils/String16.h>
+#include <utils/String8.h>
+
+#include <dirent.h>
+
+#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<Overlay>& 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<ZipFileRO> 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<Overlay> 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;
+}