idmap2 is a reboot of the idmap project. The project aims to - use modern C++ - greatly improve test and debug support - interface towards AssetManager2 (instead of AssetManager) - provide a solid foundation to add support for new features To make it easier to verify correctness, this first version of idmap2 is feature equivalent to idmap. Later versions will add support for new features such as <overlayable>. Bug: 78815803 Test: make idmap2_tests Change-Id: I1d806dc875a493e730ab55d2fdb027618e586d16
444 lines
15 KiB
C++
444 lines
15 KiB
C++
/*
|
|
* Copyright (C) 2018 The Android Open Source Project
|
|
*
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
* you may not use this file except in compliance with the License.
|
|
* You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
* See the License for the specific language governing permissions and
|
|
* limitations under the License.
|
|
*/
|
|
|
|
#include <algorithm>
|
|
#include <iostream>
|
|
#include <iterator>
|
|
#include <limits>
|
|
#include <map>
|
|
#include <memory>
|
|
#include <set>
|
|
#include <string>
|
|
#include <utility>
|
|
#include <vector>
|
|
|
|
#include "android-base/macros.h"
|
|
#include "android-base/stringprintf.h"
|
|
#include "androidfw/AssetManager2.h"
|
|
#include "utils/String16.h"
|
|
#include "utils/String8.h"
|
|
|
|
#include "idmap2/Idmap.h"
|
|
#include "idmap2/ResourceUtils.h"
|
|
#include "idmap2/ZipFile.h"
|
|
|
|
namespace android {
|
|
namespace idmap2 {
|
|
|
|
#define EXTRACT_TYPE(resid) ((0x00ff0000 & (resid)) >> 16)
|
|
|
|
#define EXTRACT_ENTRY(resid) (0x0000ffff & (resid))
|
|
|
|
struct MatchingResources {
|
|
void Add(ResourceId target_resid, ResourceId overlay_resid) {
|
|
TypeId target_typeid = EXTRACT_TYPE(target_resid);
|
|
if (map.find(target_typeid) == map.end()) {
|
|
map.emplace(target_typeid, std::set<std::pair<ResourceId, ResourceId>>());
|
|
}
|
|
map[target_typeid].insert(std::make_pair(target_resid, overlay_resid));
|
|
}
|
|
|
|
// target type id -> set { pair { overlay entry id, overlay entry id } }
|
|
std::map<TypeId, std::set<std::pair<ResourceId, ResourceId>>> map;
|
|
};
|
|
|
|
static bool WARN_UNUSED Read16(std::istream& stream, uint16_t* out) {
|
|
uint16_t value;
|
|
if (stream.read(reinterpret_cast<char*>(&value), sizeof(uint16_t))) {
|
|
*out = dtohl(value);
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
static bool WARN_UNUSED Read32(std::istream& stream, uint32_t* out) {
|
|
uint32_t value;
|
|
if (stream.read(reinterpret_cast<char*>(&value), sizeof(uint32_t))) {
|
|
*out = dtohl(value);
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
// a string is encoded as a kIdmapStringLength char array; the array is always null-terminated
|
|
static bool WARN_UNUSED ReadString(std::istream& stream, char out[kIdmapStringLength]) {
|
|
char buf[kIdmapStringLength];
|
|
memset(buf, 0, sizeof(buf));
|
|
if (!stream.read(buf, sizeof(buf))) {
|
|
return false;
|
|
}
|
|
if (buf[sizeof(buf) - 1] != '\0') {
|
|
return false;
|
|
}
|
|
memcpy(out, buf, sizeof(buf));
|
|
return true;
|
|
}
|
|
|
|
static ResourceId NameToResid(const AssetManager2& am, const std::string& name) {
|
|
return am.GetResourceId(name);
|
|
}
|
|
|
|
// TODO(martenkongstad): scan for package name instead of assuming package at index 0
|
|
//
|
|
// idmap version 0x01 naively assumes that the package to use is always the first ResTable_package
|
|
// in the resources.arsc blob. In most cases, there is only a single ResTable_package anyway, so
|
|
// this assumption tends to work out. That said, the correct thing to do is to scan
|
|
// resources.arsc for a package with a given name as read from the package manifest instead of
|
|
// relying on a hard-coded index. This however requires storing the package name in the idmap
|
|
// header, which in turn requires incrementing the idmap version. Because the initial version of
|
|
// idmap2 is compatible with idmap, this will have to wait for now.
|
|
static const LoadedPackage* GetPackageAtIndex0(const LoadedArsc& loaded_arsc) {
|
|
const std::vector<std::unique_ptr<const LoadedPackage>>& packages = loaded_arsc.GetPackages();
|
|
if (packages.empty()) {
|
|
return nullptr;
|
|
}
|
|
int id = packages[0]->GetPackageId();
|
|
return loaded_arsc.GetPackageById(id);
|
|
}
|
|
|
|
std::unique_ptr<const IdmapHeader> IdmapHeader::FromBinaryStream(std::istream& stream) {
|
|
std::unique_ptr<IdmapHeader> idmap_header(new IdmapHeader());
|
|
|
|
if (!Read32(stream, &idmap_header->magic_) || !Read32(stream, &idmap_header->version_) ||
|
|
!Read32(stream, &idmap_header->target_crc_) || !Read32(stream, &idmap_header->overlay_crc_) ||
|
|
!ReadString(stream, idmap_header->target_path_) ||
|
|
!ReadString(stream, idmap_header->overlay_path_)) {
|
|
return nullptr;
|
|
}
|
|
|
|
return std::move(idmap_header);
|
|
}
|
|
|
|
bool IdmapHeader::IsUpToDate(std::ostream& out_error) const {
|
|
if (magic_ != kIdmapMagic) {
|
|
out_error << base::StringPrintf("error: bad magic: actual 0x%08x, expected 0x%08x", magic_,
|
|
kIdmapMagic)
|
|
<< std::endl;
|
|
return false;
|
|
}
|
|
|
|
if (version_ != kIdmapCurrentVersion) {
|
|
out_error << base::StringPrintf("error: bad version: actual 0x%08x, expected 0x%08x", version_,
|
|
kIdmapCurrentVersion)
|
|
<< std::endl;
|
|
return false;
|
|
}
|
|
|
|
const std::unique_ptr<const ZipFile> target_zip = ZipFile::Open(target_path_);
|
|
if (!target_zip) {
|
|
out_error << "error: failed to open target " << target_path_ << std::endl;
|
|
return false;
|
|
}
|
|
|
|
bool status;
|
|
uint32_t target_crc;
|
|
std::tie(status, target_crc) = target_zip->Crc("resources.arsc");
|
|
if (!status) {
|
|
out_error << "error: failed to get target crc" << std::endl;
|
|
return false;
|
|
}
|
|
|
|
if (target_crc_ != target_crc) {
|
|
out_error << base::StringPrintf(
|
|
"error: bad target crc: idmap version 0x%08x, file system version 0x%08x",
|
|
target_crc_, target_crc)
|
|
<< std::endl;
|
|
return false;
|
|
}
|
|
|
|
const std::unique_ptr<const ZipFile> overlay_zip = ZipFile::Open(overlay_path_);
|
|
if (!overlay_zip) {
|
|
out_error << "error: failed to open overlay " << overlay_path_ << std::endl;
|
|
return false;
|
|
}
|
|
|
|
uint32_t overlay_crc;
|
|
std::tie(status, overlay_crc) = overlay_zip->Crc("resources.arsc");
|
|
if (!status) {
|
|
out_error << "error: failed to get overlay crc" << std::endl;
|
|
return false;
|
|
}
|
|
|
|
if (overlay_crc_ != overlay_crc) {
|
|
out_error << base::StringPrintf(
|
|
"error: bad overlay crc: idmap version 0x%08x, file system version 0x%08x",
|
|
overlay_crc_, overlay_crc)
|
|
<< std::endl;
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
std::unique_ptr<const IdmapData::Header> IdmapData::Header::FromBinaryStream(std::istream& stream) {
|
|
std::unique_ptr<IdmapData::Header> idmap_data_header(new IdmapData::Header());
|
|
|
|
uint16_t target_package_id16;
|
|
if (!Read16(stream, &target_package_id16) || !Read16(stream, &idmap_data_header->type_count_)) {
|
|
return nullptr;
|
|
}
|
|
idmap_data_header->target_package_id_ = target_package_id16;
|
|
|
|
return std::move(idmap_data_header);
|
|
}
|
|
|
|
std::unique_ptr<const IdmapData::TypeEntry> IdmapData::TypeEntry::FromBinaryStream(
|
|
std::istream& stream) {
|
|
std::unique_ptr<IdmapData::TypeEntry> data(new IdmapData::TypeEntry());
|
|
|
|
uint16_t target_type16, overlay_type16, entry_count;
|
|
if (!Read16(stream, &target_type16) || !Read16(stream, &overlay_type16) ||
|
|
!Read16(stream, &entry_count) || !Read16(stream, &data->entry_offset_)) {
|
|
return nullptr;
|
|
}
|
|
data->target_type_id_ = target_type16;
|
|
data->overlay_type_id_ = overlay_type16;
|
|
for (uint16_t i = 0; i < entry_count; i++) {
|
|
ResourceId resid;
|
|
if (!Read32(stream, &resid)) {
|
|
return nullptr;
|
|
}
|
|
data->entries_.push_back(resid);
|
|
}
|
|
|
|
return std::move(data);
|
|
}
|
|
|
|
std::unique_ptr<const IdmapData> IdmapData::FromBinaryStream(std::istream& stream) {
|
|
std::unique_ptr<IdmapData> data(new IdmapData());
|
|
data->header_ = IdmapData::Header::FromBinaryStream(stream);
|
|
if (!data->header_) {
|
|
return nullptr;
|
|
}
|
|
for (size_t type_count = 0; type_count < data->header_->GetTypeCount(); type_count++) {
|
|
std::unique_ptr<const TypeEntry> type = IdmapData::TypeEntry::FromBinaryStream(stream);
|
|
if (!type) {
|
|
return nullptr;
|
|
}
|
|
data->type_entries_.push_back(std::move(type));
|
|
}
|
|
return std::move(data);
|
|
}
|
|
|
|
std::string Idmap::CanonicalIdmapPathFor(const std::string& absolute_dir,
|
|
const std::string& absolute_apk_path) {
|
|
assert(absolute_dir.size() > 0 && absolute_dir[0] == "/");
|
|
assert(absolute_apk_path.size() > 0 && absolute_apk_path[0] == "/");
|
|
std::string copy(++absolute_apk_path.cbegin(), absolute_apk_path.cend());
|
|
replace(copy.begin(), copy.end(), '/', '@');
|
|
return absolute_dir + "/" + copy + "@idmap";
|
|
}
|
|
|
|
std::unique_ptr<const Idmap> Idmap::FromBinaryStream(std::istream& stream,
|
|
std::ostream& out_error) {
|
|
std::unique_ptr<Idmap> idmap(new Idmap());
|
|
|
|
idmap->header_ = IdmapHeader::FromBinaryStream(stream);
|
|
if (!idmap->header_) {
|
|
out_error << "error: failed to parse idmap header" << std::endl;
|
|
return nullptr;
|
|
}
|
|
|
|
// idmap version 0x01 does not specify the number of data blocks that follow
|
|
// the idmap header; assume exactly one data block
|
|
for (int i = 0; i < 1; i++) {
|
|
std::unique_ptr<const IdmapData> data = IdmapData::FromBinaryStream(stream);
|
|
if (!data) {
|
|
out_error << "error: failed to parse data block " << i << std::endl;
|
|
return nullptr;
|
|
}
|
|
idmap->data_.push_back(std::move(data));
|
|
}
|
|
|
|
return std::move(idmap);
|
|
}
|
|
|
|
std::unique_ptr<const Idmap> Idmap::FromApkAssets(const std::string& target_apk_path,
|
|
const ApkAssets& target_apk_assets,
|
|
const std::string& overlay_apk_path,
|
|
const ApkAssets& overlay_apk_assets,
|
|
std::ostream& out_error) {
|
|
AssetManager2 target_asset_manager;
|
|
if (!target_asset_manager.SetApkAssets({&target_apk_assets}, true, false)) {
|
|
out_error << "error: failed to create target asset manager" << std::endl;
|
|
return nullptr;
|
|
}
|
|
|
|
AssetManager2 overlay_asset_manager;
|
|
if (!overlay_asset_manager.SetApkAssets({&overlay_apk_assets}, true, false)) {
|
|
out_error << "error: failed to create overlay asset manager" << std::endl;
|
|
return nullptr;
|
|
}
|
|
|
|
const LoadedArsc* target_arsc = target_apk_assets.GetLoadedArsc();
|
|
if (!target_arsc) {
|
|
out_error << "error: failed to load target resources.arsc" << std::endl;
|
|
return nullptr;
|
|
}
|
|
|
|
const LoadedArsc* overlay_arsc = overlay_apk_assets.GetLoadedArsc();
|
|
if (!overlay_arsc) {
|
|
out_error << "error: failed to load overlay resources.arsc" << std::endl;
|
|
return nullptr;
|
|
}
|
|
|
|
const LoadedPackage* target_pkg = GetPackageAtIndex0(*target_arsc);
|
|
if (!target_pkg) {
|
|
out_error << "error: failed to load target package from resources.arsc" << std::endl;
|
|
return nullptr;
|
|
}
|
|
|
|
const LoadedPackage* overlay_pkg = GetPackageAtIndex0(*overlay_arsc);
|
|
if (!overlay_pkg) {
|
|
out_error << "error: failed to load overlay package from resources.arsc" << std::endl;
|
|
return nullptr;
|
|
}
|
|
|
|
const std::unique_ptr<const ZipFile> target_zip = ZipFile::Open(target_apk_path);
|
|
if (!target_zip) {
|
|
out_error << "error: failed to open target as zip" << std::endl;
|
|
return nullptr;
|
|
}
|
|
|
|
const std::unique_ptr<const ZipFile> overlay_zip = ZipFile::Open(overlay_apk_path);
|
|
if (!overlay_zip) {
|
|
out_error << "error: failed to open overlay as zip" << std::endl;
|
|
return nullptr;
|
|
}
|
|
|
|
std::unique_ptr<IdmapHeader> header(new IdmapHeader());
|
|
header->magic_ = kIdmapMagic;
|
|
header->version_ = kIdmapCurrentVersion;
|
|
bool crc_status;
|
|
std::tie(crc_status, header->target_crc_) = target_zip->Crc("resources.arsc");
|
|
if (!crc_status) {
|
|
out_error << "error: failed to get zip crc for target" << std::endl;
|
|
return nullptr;
|
|
}
|
|
std::tie(crc_status, header->overlay_crc_) = overlay_zip->Crc("resources.arsc");
|
|
if (!crc_status) {
|
|
out_error << "error: failed to get zip crc for overlay" << std::endl;
|
|
return nullptr;
|
|
}
|
|
|
|
if (target_apk_path.size() > sizeof(header->target_path_)) {
|
|
out_error << "error: target apk path \"" << target_apk_path << "\" longer that maximum size "
|
|
<< sizeof(header->target_path_) << std::endl;
|
|
return nullptr;
|
|
}
|
|
memset(header->target_path_, 0, sizeof(header->target_path_));
|
|
memcpy(header->target_path_, target_apk_path.data(), target_apk_path.size());
|
|
|
|
if (overlay_apk_path.size() > sizeof(header->overlay_path_)) {
|
|
out_error << "error: overlay apk path \"" << overlay_apk_path << "\" longer that maximum size "
|
|
<< sizeof(header->overlay_path_) << std::endl;
|
|
return nullptr;
|
|
}
|
|
memset(header->overlay_path_, 0, sizeof(header->overlay_path_));
|
|
memcpy(header->overlay_path_, overlay_apk_path.data(), overlay_apk_path.size());
|
|
|
|
std::unique_ptr<Idmap> idmap(new Idmap());
|
|
idmap->header_ = std::move(header);
|
|
|
|
// find the resources that exist in both packages
|
|
MatchingResources matching_resources;
|
|
const auto end = overlay_pkg->end();
|
|
for (auto iter = overlay_pkg->begin(); iter != end; ++iter) {
|
|
const ResourceId overlay_resid = *iter;
|
|
bool lookup_ok;
|
|
std::string name;
|
|
std::tie(lookup_ok, name) = utils::ResToTypeEntryName(overlay_asset_manager, overlay_resid);
|
|
if (!lookup_ok) {
|
|
continue;
|
|
}
|
|
// prepend "<package>:" to turn name into "<package>:<type>/<name>"
|
|
name = base::StringPrintf("%s:%s", target_pkg->GetPackageName().c_str(), name.c_str());
|
|
const ResourceId target_resid = NameToResid(target_asset_manager, name);
|
|
if (target_resid == 0) {
|
|
continue;
|
|
}
|
|
matching_resources.Add(target_resid, overlay_resid);
|
|
}
|
|
|
|
// encode idmap data
|
|
std::unique_ptr<IdmapData> data(new IdmapData());
|
|
const auto types_end = matching_resources.map.cend();
|
|
for (auto ti = matching_resources.map.cbegin(); ti != types_end; ++ti) {
|
|
auto ei = ti->second.cbegin();
|
|
std::unique_ptr<IdmapData::TypeEntry> type(new IdmapData::TypeEntry());
|
|
type->target_type_id_ = EXTRACT_TYPE(ei->first);
|
|
type->overlay_type_id_ = EXTRACT_TYPE(ei->second);
|
|
type->entry_offset_ = EXTRACT_ENTRY(ei->first);
|
|
EntryId last_target_entry = kNoEntry;
|
|
for (; ei != ti->second.cend(); ++ei) {
|
|
if (last_target_entry != kNoEntry) {
|
|
int count = EXTRACT_ENTRY(ei->first) - last_target_entry - 1;
|
|
type->entries_.insert(type->entries_.end(), count, kNoEntry);
|
|
}
|
|
type->entries_.push_back(EXTRACT_ENTRY(ei->second));
|
|
last_target_entry = EXTRACT_ENTRY(ei->first);
|
|
}
|
|
data->type_entries_.push_back(std::move(type));
|
|
}
|
|
|
|
std::unique_ptr<IdmapData::Header> data_header(new IdmapData::Header());
|
|
data_header->target_package_id_ = target_pkg->GetPackageId();
|
|
data_header->type_count_ = data->type_entries_.size();
|
|
data->header_ = std::move(data_header);
|
|
|
|
idmap->data_.push_back(std::move(data));
|
|
|
|
return std::move(idmap);
|
|
}
|
|
|
|
void IdmapHeader::accept(Visitor* v) const {
|
|
assert(v != nullptr);
|
|
v->visit(*this);
|
|
}
|
|
|
|
void IdmapData::Header::accept(Visitor* v) const {
|
|
assert(v != nullptr);
|
|
v->visit(*this);
|
|
}
|
|
|
|
void IdmapData::TypeEntry::accept(Visitor* v) const {
|
|
assert(v != nullptr);
|
|
v->visit(*this);
|
|
}
|
|
|
|
void IdmapData::accept(Visitor* v) const {
|
|
assert(v != nullptr);
|
|
v->visit(*this);
|
|
header_->accept(v);
|
|
auto end = type_entries_.cend();
|
|
for (auto iter = type_entries_.cbegin(); iter != end; ++iter) {
|
|
(*iter)->accept(v);
|
|
}
|
|
}
|
|
|
|
void Idmap::accept(Visitor* v) const {
|
|
assert(v != nullptr);
|
|
v->visit(*this);
|
|
header_->accept(v);
|
|
auto end = data_.cend();
|
|
for (auto iter = data_.cbegin(); iter != end; ++iter) {
|
|
(*iter)->accept(v);
|
|
}
|
|
}
|
|
|
|
} // namespace idmap2
|
|
} // namespace android
|