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
This commit is contained in:
parent
af3efe8ce7
commit
5ee3dafa98
28
cmds/idmap/Android.mk
Normal file
28
cmds/idmap/Android.mk
Normal file
@ -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)
|
222
cmds/idmap/create.cpp
Normal file
222
cmds/idmap/create.cpp
Normal file
@ -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;
|
||||
}
|
237
cmds/idmap/idmap.cpp
Normal file
237
cmds/idmap/idmap.cpp
Normal file
@ -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;
|
||||
}
|
34
cmds/idmap/idmap.h
Normal file
34
cmds/idmap/idmap.h
Normal file
@ -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_
|
291
cmds/idmap/inspect.cpp
Normal file
291
cmds/idmap/inspect.cpp
Normal file
@ -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;
|
||||
}
|
244
cmds/idmap/scan.cpp
Normal file
244
cmds/idmap/scan.cpp
Normal file
@ -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;
|
||||
}
|
Loading…
Reference in New Issue
Block a user