Merge changes Ia6c335db,Id8079104

* changes:
  AssetManager2: Provide a method for iterating over packages
  AssetManager2: Implement IDMAP support
This commit is contained in:
TreeHugger Robot
2017-10-13 17:27:39 +00:00
committed by Android (Google) Code Review
13 changed files with 641 additions and 79 deletions

View File

@@ -43,6 +43,7 @@ cc_library {
"AssetManager2.cpp",
"AttributeResolution.cpp",
"ChunkIterator.cpp",
"Idmap.cpp",
"LoadedArsc.cpp",
"LocaleData.cpp",
"misc.cpp",

View File

@@ -20,64 +20,126 @@
#include <algorithm>
#include "android-base/errors.h"
#include "android-base/file.h"
#include "android-base/logging.h"
#include "android-base/unique_fd.h"
#include "android-base/utf8.h"
#include "utils/Compat.h"
#include "utils/FileMap.h"
#include "utils/Trace.h"
#include "ziparchive/zip_archive.h"
#include "androidfw/Asset.h"
#include "androidfw/Idmap.h"
#include "androidfw/ResourceTypes.h"
#include "androidfw/Util.h"
namespace android {
ApkAssets::ApkAssets() : zip_handle_(nullptr, ::CloseArchive) {}
using base::SystemErrorCodeToString;
using base::unique_fd;
static const std::string kResourcesArsc("resources.arsc");
ApkAssets::ApkAssets(void* unmanaged_handle, const std::string& path)
: zip_handle_(unmanaged_handle, ::CloseArchive), path_(path) {
}
std::unique_ptr<const ApkAssets> ApkAssets::Load(const std::string& path, bool system) {
return ApkAssets::LoadImpl(path, system, false /*load_as_shared_library*/);
return ApkAssets::LoadImpl(path, nullptr, nullptr, system, false /*load_as_shared_library*/);
}
std::unique_ptr<const ApkAssets> ApkAssets::LoadAsSharedLibrary(const std::string& path,
bool system) {
return ApkAssets::LoadImpl(path, system, true /*load_as_shared_library*/);
return ApkAssets::LoadImpl(path, nullptr, nullptr, system, true /*load_as_shared_library*/);
}
std::unique_ptr<const ApkAssets> ApkAssets::LoadImpl(const std::string& path, bool system,
bool load_as_shared_library) {
std::unique_ptr<const ApkAssets> ApkAssets::LoadOverlay(const std::string& idmap_path,
bool system) {
std::unique_ptr<Asset> idmap_asset = CreateAssetFromFile(idmap_path);
if (idmap_asset == nullptr) {
return {};
}
const StringPiece idmap_data(
reinterpret_cast<const char*>(idmap_asset->getBuffer(true /*wordAligned*/)),
static_cast<size_t>(idmap_asset->getLength()));
std::unique_ptr<const LoadedIdmap> loaded_idmap = LoadedIdmap::Load(idmap_data);
if (loaded_idmap == nullptr) {
LOG(ERROR) << "failed to load IDMAP " << idmap_path;
return {};
}
return LoadImpl(loaded_idmap->OverlayApkPath(), std::move(idmap_asset), std::move(loaded_idmap),
system, false /*load_as_shared_library*/);
}
std::unique_ptr<Asset> ApkAssets::CreateAssetFromFile(const std::string& path) {
unique_fd fd(base::utf8::open(path.c_str(), O_RDONLY | O_BINARY | O_CLOEXEC));
if (fd == -1) {
LOG(ERROR) << "Failed to open file '" << path << "': " << SystemErrorCodeToString(errno);
return {};
}
const off64_t file_len = lseek64(fd, 0, SEEK_END);
if (file_len < 0) {
LOG(ERROR) << "Failed to get size of file '" << path << "': " << SystemErrorCodeToString(errno);
return {};
}
std::unique_ptr<FileMap> file_map = util::make_unique<FileMap>();
if (!file_map->create(path.c_str(), fd, 0, static_cast<size_t>(file_len), true /*readOnly*/)) {
LOG(ERROR) << "Failed to mmap file '" << path << "': " << SystemErrorCodeToString(errno);
return {};
}
return Asset::createFromUncompressedMap(std::move(file_map), Asset::AccessMode::ACCESS_RANDOM);
}
std::unique_ptr<const ApkAssets> ApkAssets::LoadImpl(
const std::string& path, std::unique_ptr<Asset> idmap_asset,
std::unique_ptr<const LoadedIdmap> loaded_idmap, bool system, bool load_as_shared_library) {
ATRACE_CALL();
::ZipArchiveHandle unmanaged_handle;
int32_t result = ::OpenArchive(path.c_str(), &unmanaged_handle);
if (result != 0) {
LOG(ERROR) << ::ErrorCodeString(result);
LOG(ERROR) << "Failed to open APK '" << path << "' " << ::ErrorCodeString(result);
return {};
}
// Wrap the handle in a unique_ptr so it gets automatically closed.
std::unique_ptr<ApkAssets> loaded_apk(new ApkAssets());
loaded_apk->zip_handle_.reset(unmanaged_handle);
std::unique_ptr<ApkAssets> loaded_apk(new ApkAssets(unmanaged_handle, path));
::ZipString entry_name("resources.arsc");
// Find the resource table.
::ZipString entry_name(kResourcesArsc.c_str());
::ZipEntry entry;
result = ::FindEntry(loaded_apk->zip_handle_.get(), entry_name, &entry);
if (result != 0) {
LOG(ERROR) << ::ErrorCodeString(result);
return {};
// There is no resources.arsc, so create an empty LoadedArsc and return.
loaded_apk->loaded_arsc_ = LoadedArsc::CreateEmpty();
return std::move(loaded_apk);
}
if (entry.method == kCompressDeflated) {
LOG(WARNING) << "resources.arsc is compressed.";
LOG(WARNING) << kResourcesArsc << " in APK '" << path << "' is compressed.";
}
loaded_apk->path_ = path;
loaded_apk->resources_asset_ =
loaded_apk->Open("resources.arsc", Asset::AccessMode::ACCESS_BUFFER);
// Open the resource table via mmap unless it is compressed. This logic is taken care of by Open.
loaded_apk->resources_asset_ = loaded_apk->Open(kResourcesArsc, Asset::AccessMode::ACCESS_BUFFER);
if (loaded_apk->resources_asset_ == nullptr) {
LOG(ERROR) << "Failed to open '" << kResourcesArsc << "' in APK '" << path << "'.";
return {};
}
// Must retain ownership of the IDMAP Asset so that all pointers to its mmapped data remain valid.
loaded_apk->idmap_asset_ = std::move(idmap_asset);
const StringPiece data(
reinterpret_cast<const char*>(loaded_apk->resources_asset_->getBuffer(true /*wordAligned*/)),
loaded_apk->resources_asset_->getLength());
loaded_apk->loaded_arsc_ =
LoadedArsc::Load(loaded_apk->resources_asset_->getBuffer(true /*wordAligned*/),
loaded_apk->resources_asset_->getLength(), system, load_as_shared_library);
LoadedArsc::Load(data, loaded_idmap.get(), system, load_as_shared_library);
if (loaded_apk->loaded_arsc_ == nullptr) {
LOG(ERROR) << "Failed to load '" << kResourcesArsc << "' in APK '" << path << "'.";
return {};
}
@@ -93,7 +155,6 @@ std::unique_ptr<Asset> ApkAssets::Open(const std::string& path, Asset::AccessMod
::ZipEntry entry;
int32_t result = ::FindEntry(zip_handle_.get(), name, &entry);
if (result != 0) {
LOG(ERROR) << "No entry '" << path << "' found in APK '" << path_ << "'";
return {};
}

View File

@@ -35,7 +35,9 @@
namespace android {
AssetManager2::AssetManager2() { memset(&configuration_, 0, sizeof(configuration_)); }
AssetManager2::AssetManager2() {
memset(&configuration_, 0, sizeof(configuration_));
}
bool AssetManager2::SetApkAssets(const std::vector<const ApkAssets*>& apk_assets,
bool invalidate_caches) {
@@ -55,9 +57,9 @@ void AssetManager2::BuildDynamicRefTable() {
int next_package_id = 0x02;
const size_t apk_assets_count = apk_assets_.size();
for (size_t i = 0; i < apk_assets_count; i++) {
const ApkAssets* apk_asset = apk_assets_[i];
for (const std::unique_ptr<const LoadedPackage>& package :
apk_asset->GetLoadedArsc()->GetPackages()) {
const LoadedArsc* loaded_arsc = apk_assets_[i]->GetLoadedArsc();
for (const std::unique_ptr<const LoadedPackage>& package : loaded_arsc->GetPackages()) {
// Get the package ID or assign one if a shared library.
int package_id;
if (package->IsDynamic()) {
@@ -312,7 +314,8 @@ ApkAssetsCookie AssetManager2::FindEntry(uint32_t resid, uint16_t density_overri
cumulated_flags |= current_flags;
if (best_cookie == kInvalidCookie || current_config.isBetterThan(best_config, desired_config)) {
if (best_cookie == kInvalidCookie || current_config.isBetterThan(best_config, desired_config) ||
(loaded_package->IsOverlay() && current_config.compare(best_config) == 0)) {
best_entry = current_entry;
best_config = current_config;
best_cookie = package_group.cookies_[i];

190
libs/androidfw/Idmap.cpp Normal file
View File

@@ -0,0 +1,190 @@
/*
* Copyright (C) 2017 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#define ATRACE_TAG ATRACE_TAG_RESOURCES
#include "androidfw/Idmap.h"
#include "android-base/logging.h"
#include "android-base/stringprintf.h"
#include "utils/ByteOrder.h"
#include "utils/Trace.h"
#ifdef _WIN32
#ifdef ERROR
#undef ERROR
#endif
#endif
#include "androidfw/ResourceTypes.h"
using ::android::base::StringPrintf;
namespace android {
constexpr static inline bool is_valid_package_id(uint16_t id) {
return id != 0 && id <= 255;
}
constexpr static inline bool is_valid_type_id(uint16_t id) {
// Type IDs and package IDs have the same constraints in the IDMAP.
return is_valid_package_id(id);
}
bool LoadedIdmap::Lookup(const IdmapEntry_header* header, uint16_t input_entry_id,
uint16_t* output_entry_id) {
if (input_entry_id < dtohs(header->entry_id_offset)) {
// After applying the offset, the entry is not present.
return false;
}
input_entry_id -= dtohs(header->entry_id_offset);
if (input_entry_id >= dtohs(header->entry_count)) {
// The entry is not present.
return false;
}
uint32_t result = dtohl(header->entries[input_entry_id]);
if (result == 0xffffffffu) {
return false;
}
*output_entry_id = static_cast<uint16_t>(result);
return true;
}
static bool is_word_aligned(const void* data) {
return (reinterpret_cast<uintptr_t>(data) & 0x03) == 0;
}
static bool IsValidIdmapHeader(const StringPiece& data) {
if (!is_word_aligned(data.data())) {
LOG(ERROR) << "Idmap header is not word aligned.";
return false;
}
if (data.size() < sizeof(Idmap_header)) {
LOG(ERROR) << "Idmap header is too small.";
return false;
}
const Idmap_header* header = reinterpret_cast<const Idmap_header*>(data.data());
if (dtohl(header->magic) != kIdmapMagic) {
LOG(ERROR) << StringPrintf("Invalid Idmap file: bad magic value (was 0x%08x, expected 0x%08x)",
dtohl(header->magic), kIdmapMagic);
return false;
}
if (dtohl(header->version) != kIdmapCurrentVersion) {
// We are strict about versions because files with this format are auto-generated and don't need
// backwards compatibility.
LOG(ERROR) << StringPrintf("Version mismatch in Idmap (was 0x%08x, expected 0x%08x)",
dtohl(header->version), kIdmapCurrentVersion);
return false;
}
if (!is_valid_package_id(dtohs(header->target_package_id))) {
LOG(ERROR) << StringPrintf("Target package ID in Idmap is invalid: 0x%02x",
dtohs(header->target_package_id));
return false;
}
if (dtohs(header->type_count) > 255) {
LOG(ERROR) << StringPrintf("Idmap has too many type mappings (was %d, max 255)",
(int)dtohs(header->type_count));
return false;
}
return true;
}
LoadedIdmap::LoadedIdmap(const Idmap_header* header) : header_(header) {
size_t length = strnlen(reinterpret_cast<const char*>(header_->overlay_path),
arraysize(header_->overlay_path));
overlay_apk_path_.assign(reinterpret_cast<const char*>(header_->overlay_path), length);
}
std::unique_ptr<const LoadedIdmap> LoadedIdmap::Load(const StringPiece& idmap_data) {
ATRACE_CALL();
if (!IsValidIdmapHeader(idmap_data)) {
return {};
}
const Idmap_header* header = reinterpret_cast<const Idmap_header*>(idmap_data.data());
// Can't use make_unique because LoadedImpl constructor is private.
std::unique_ptr<LoadedIdmap> loaded_idmap = std::unique_ptr<LoadedIdmap>(new LoadedIdmap(header));
const uint8_t* data_ptr = reinterpret_cast<const uint8_t*>(idmap_data.data()) + sizeof(*header);
size_t data_size = idmap_data.size() - sizeof(*header);
size_t type_maps_encountered = 0u;
while (data_size >= sizeof(IdmapEntry_header)) {
if (!is_word_aligned(data_ptr)) {
LOG(ERROR) << "Type mapping in Idmap is not word aligned";
return {};
}
// Validate the type IDs.
const IdmapEntry_header* entry_header = reinterpret_cast<const IdmapEntry_header*>(data_ptr);
if (!is_valid_type_id(dtohs(entry_header->target_type_id)) || !is_valid_type_id(dtohs(entry_header->overlay_type_id))) {
LOG(ERROR) << StringPrintf("Invalid type map (0x%02x -> 0x%02x)",
dtohs(entry_header->target_type_id),
dtohs(entry_header->overlay_type_id));
return {};
}
// Make sure there is enough space for the entries declared in the header.
if ((data_size - sizeof(*entry_header)) / sizeof(uint32_t) <
static_cast<size_t>(dtohs(entry_header->entry_count))) {
LOG(ERROR) << StringPrintf("Idmap too small for the number of entries (%d)",
(int)dtohs(entry_header->entry_count));
return {};
}
// Only add a non-empty overlay.
if (dtohs(entry_header->entry_count != 0)) {
loaded_idmap->type_map_[static_cast<uint8_t>(dtohs(entry_header->overlay_type_id))] =
entry_header;
}
const size_t entry_size_bytes =
sizeof(*entry_header) + (dtohs(entry_header->entry_count) * sizeof(uint32_t));
data_ptr += entry_size_bytes;
data_size -= entry_size_bytes;
type_maps_encountered++;
}
// Verify that we parsed all the type maps.
if (type_maps_encountered != static_cast<size_t>(dtohs(header->type_count))) {
LOG(ERROR) << "Parsed " << type_maps_encountered << " type maps but expected "
<< (int)dtohs(header->type_count);
return {};
}
return std::move(loaded_idmap);
}
uint8_t LoadedIdmap::TargetPackageId() const {
return static_cast<uint8_t>(dtohs(header_->target_package_id));
}
const IdmapEntry_header* LoadedIdmap::GetEntryMapForType(uint8_t type_id) const {
auto iter = type_map_.find(type_id);
if (iter != type_map_.end()) {
return iter->second;
}
return nullptr;
}
} // namespace android

View File

@@ -37,7 +37,7 @@
#include "androidfw/ResourceUtils.h"
#include "androidfw/Util.h"
using android::base::StringPrintf;
using ::android::base::StringPrintf;
namespace android {
@@ -61,6 +61,10 @@ struct TypeSpec {
// and under which configurations it varies.
const ResTable_typeSpec* type_spec;
// Pointer to the mmapped data where the IDMAP mappings for this type
// exist. May be nullptr if no IDMAP exists.
const IdmapEntry_header* idmap_entries;
// The number of types that follow this struct.
// There is a type for each configuration
// that entries are defined for.
@@ -84,7 +88,10 @@ namespace {
// the Type structs.
class TypeSpecPtrBuilder {
public:
TypeSpecPtrBuilder(const ResTable_typeSpec* header) : header_(header) {}
explicit TypeSpecPtrBuilder(const ResTable_typeSpec* header,
const IdmapEntry_header* idmap_header)
: header_(header), idmap_header_(idmap_header) {
}
void AddType(const ResTable_type* type) {
ResTable_config config;
@@ -99,6 +106,7 @@ class TypeSpecPtrBuilder {
}
TypeSpec* type_spec = (TypeSpec*)::malloc(sizeof(TypeSpec) + (types_.size() * sizeof(Type)));
type_spec->type_spec = header_;
type_spec->idmap_entries = idmap_header_;
type_spec->type_count = types_.size();
memcpy(type_spec + 1, types_.data(), types_.size() * sizeof(Type));
return TypeSpecPtr(type_spec);
@@ -108,6 +116,7 @@ class TypeSpecPtrBuilder {
DISALLOW_COPY_AND_ASSIGN(TypeSpecPtrBuilder);
const ResTable_typeSpec* header_;
const IdmapEntry_header* idmap_header_;
std::vector<Type> types_;
};
@@ -125,6 +134,14 @@ bool LoadedPackage::FindEntry(uint8_t type_idx, uint16_t entry_idx, const ResTab
return false;
}
// If there is an IDMAP supplied with this package, translate the entry ID.
if (ptr->idmap_entries != nullptr) {
if (!LoadedIdmap::Lookup(ptr->idmap_entries, entry_idx, &entry_idx)) {
// There is no mapping, so the resource is not meant to be in this overlay package.
return false;
}
}
// Don't bother checking if the entry ID is larger than
// the number of entries.
if (entry_idx >= dtohl(ptr->type_spec->entryCount)) {
@@ -225,7 +242,7 @@ static bool VerifyType(const Chunk& chunk) {
const size_t entries_offset = dtohl(header->entriesStart);
const size_t offsets_length = sizeof(uint32_t) * entry_count;
if (offsets_offset + offsets_length > entries_offset) {
if (offsets_offset > entries_offset || entries_offset - offsets_offset < offsets_length) {
LOG(ERROR) << "Entry offsets overlap actual entry data.";
return false;
}
@@ -269,13 +286,13 @@ static bool VerifyType(const Chunk& chunk) {
reinterpret_cast<const uint8_t*>(header) + offset);
const size_t entry_size = dtohs(entry->size);
if (entry_size < sizeof(*entry)) {
LOG(ERROR) << "ResTable_entry size " << entry_size << " is too small.";
LOG(ERROR) << "ResTable_entry size " << entry_size << " at index " << i << " is too small.";
return false;
}
// Check the declared entrySize.
if (entry_size > chunk.size() || offset > chunk.size() - entry_size) {
LOG(ERROR) << "ResTable_entry size " << entry_size << " is too large.";
LOG(ERROR) << "ResTable_entry size " << entry_size << " at index " << i << " is too large.";
return false;
}
@@ -286,13 +303,13 @@ static bool VerifyType(const Chunk& chunk) {
size_t map_entries_start = offset + entry_size;
if (map_entries_start & 0x03) {
LOG(ERROR) << "Map entries start at unaligned offset.";
LOG(ERROR) << "Map entries at index " << i << " start at unaligned offset.";
return false;
}
// Each entry is sizeof(ResTable_map) big.
if (map_entry_count > ((chunk.size() - map_entries_start) / sizeof(ResTable_map))) {
LOG(ERROR) << "Too many map entries in ResTable_map_entry.";
LOG(ERROR) << "Too many map entries in ResTable_map_entry at index " << i << ".";
return false;
}
@@ -300,7 +317,9 @@ static bool VerifyType(const Chunk& chunk) {
} else {
// There needs to be room for one Res_value struct.
if (offset + entry_size > chunk.size() - sizeof(Res_value)) {
LOG(ERROR) << "No room for Res_value after ResTable_entry.";
LOG(ERROR) << "No room for Res_value after ResTable_entry at index " << i << " for type "
<< (int)header->id << " with config " << header->config.toString().string()
<< ".";
return false;
}
@@ -308,12 +327,12 @@ static bool VerifyType(const Chunk& chunk) {
reinterpret_cast<const uint8_t*>(entry) + entry_size);
const size_t value_size = dtohs(value->size);
if (value_size < sizeof(Res_value)) {
LOG(ERROR) << "Res_value is too small.";
LOG(ERROR) << "Res_value at index " << i << " is too small.";
return false;
}
if (value_size > chunk.size() || offset + entry_size > chunk.size() - value_size) {
LOG(ERROR) << "Res_value size is too large.";
LOG(ERROR) << "Res_value size " << value_size << " at index " << i << " is too large.";
return false;
}
}
@@ -412,10 +431,13 @@ uint32_t LoadedPackage::FindEntryByName(const std::u16string& type_name,
return 0u;
}
std::unique_ptr<LoadedPackage> LoadedPackage::Load(const Chunk& chunk) {
std::unique_ptr<LoadedPackage> LoadedPackage::Load(const Chunk& chunk,
const LoadedIdmap* loaded_idmap) {
ATRACE_CALL();
std::unique_ptr<LoadedPackage> loaded_package{new LoadedPackage()};
// typeIdOffset was added at some point, but we still must recognize apps built before this
// was added.
constexpr size_t kMinPackageSize =
sizeof(ResTable_package) - sizeof(ResTable_package::typeIdOffset);
const ResTable_package* header = chunk.header<ResTable_package, kMinPackageSize>();
@@ -430,6 +452,12 @@ std::unique_ptr<LoadedPackage> LoadedPackage::Load(const Chunk& chunk) {
loaded_package->dynamic_ = true;
}
if (loaded_idmap != nullptr) {
// This is an overlay and so it needs to pretend to be the target package.
loaded_package->package_id_ = loaded_idmap->TargetPackageId();
loaded_package->overlay_ = true;
}
if (header->header.headerSize >= sizeof(ResTable_package)) {
uint32_t type_id_offset = dtohl(header->typeIdOffset);
if (type_id_offset > std::numeric_limits<uint8_t>::max()) {
@@ -490,7 +518,16 @@ std::unique_ptr<LoadedPackage> LoadedPackage::Load(const Chunk& chunk) {
LOG(ERROR) << "Too many type configurations, overflow detected.";
return {};
}
loaded_package->type_specs_.editItemAt(last_type_idx) = std::move(type_spec_ptr);
// We only add the type to the package if there is no IDMAP, or if the type is
// overlaying something.
if (loaded_idmap == nullptr || type_spec_ptr->idmap_entries != nullptr) {
// If this is an overlay, insert it at the target type ID.
if (type_spec_ptr->idmap_entries != nullptr) {
last_type_idx = dtohs(type_spec_ptr->idmap_entries->target_type_id) - 1;
}
loaded_package->type_specs_.editItemAt(last_type_idx) = std::move(type_spec_ptr);
}
types_builder = {};
last_type_idx = 0;
@@ -531,7 +568,15 @@ std::unique_ptr<LoadedPackage> LoadedPackage::Load(const Chunk& chunk) {
}
last_type_idx = type_spec->id - 1;
types_builder = util::make_unique<TypeSpecPtrBuilder>(type_spec);
// If this is an overlay, associate the mapping of this type to the target type
// from the IDMAP.
const IdmapEntry_header* idmap_entry_header = nullptr;
if (loaded_idmap != nullptr) {
idmap_entry_header = loaded_idmap->GetEntryMapForType(type_spec->id);
}
types_builder = util::make_unique<TypeSpecPtrBuilder>(type_spec, idmap_entry_header);
} break;
case RES_TABLE_TYPE_TYPE: {
@@ -608,7 +653,16 @@ std::unique_ptr<LoadedPackage> LoadedPackage::Load(const Chunk& chunk) {
LOG(ERROR) << "Too many type configurations, overflow detected.";
return {};
}
loaded_package->type_specs_.editItemAt(last_type_idx) = std::move(type_spec_ptr);
// We only add the type to the package if there is no IDMAP, or if the type is
// overlaying something.
if (loaded_idmap == nullptr || type_spec_ptr->idmap_entries != nullptr) {
// If this is an overlay, insert it at the target type ID.
if (type_spec_ptr->idmap_entries != nullptr) {
last_type_idx = dtohs(type_spec_ptr->idmap_entries->target_type_id) - 1;
}
loaded_package->type_specs_.editItemAt(last_type_idx) = std::move(type_spec_ptr);
}
}
if (iter.HadError()) {
@@ -618,7 +672,8 @@ std::unique_ptr<LoadedPackage> LoadedPackage::Load(const Chunk& chunk) {
return loaded_package;
}
bool LoadedArsc::LoadTable(const Chunk& chunk, bool load_as_shared_library) {
bool LoadedArsc::LoadTable(const Chunk& chunk, const LoadedIdmap* loaded_idmap,
bool load_as_shared_library) {
ATRACE_CALL();
const ResTable_header* header = chunk.header<ResTable_header>();
if (header == nullptr) {
@@ -652,13 +707,13 @@ bool LoadedArsc::LoadTable(const Chunk& chunk, bool load_as_shared_library) {
case RES_TABLE_PACKAGE_TYPE: {
if (packages_seen + 1 > package_count) {
LOG(ERROR) << "More package chunks were found than the " << package_count
<< " declared in the "
"header.";
<< " declared in the header.";
return false;
}
packages_seen++;
std::unique_ptr<LoadedPackage> loaded_package = LoadedPackage::Load(child_chunk);
std::unique_ptr<LoadedPackage> loaded_package =
LoadedPackage::Load(child_chunk, loaded_idmap);
if (!loaded_package) {
return false;
}
@@ -684,7 +739,8 @@ bool LoadedArsc::LoadTable(const Chunk& chunk, bool load_as_shared_library) {
return true;
}
std::unique_ptr<const LoadedArsc> LoadedArsc::Load(const void* data, size_t len, bool system,
std::unique_ptr<const LoadedArsc> LoadedArsc::Load(const StringPiece& data,
const LoadedIdmap* loaded_idmap, bool system,
bool load_as_shared_library) {
ATRACE_CALL();
@@ -692,12 +748,12 @@ std::unique_ptr<const LoadedArsc> LoadedArsc::Load(const void* data, size_t len,
std::unique_ptr<LoadedArsc> loaded_arsc(new LoadedArsc());
loaded_arsc->system_ = system;
ChunkIterator iter(data, len);
ChunkIterator iter(data.data(), data.size());
while (iter.HasNext()) {
const Chunk chunk = iter.Next();
switch (chunk.type()) {
case RES_TABLE_TYPE:
if (!loaded_arsc->LoadTable(chunk, load_as_shared_library)) {
if (!loaded_arsc->LoadTable(chunk, loaded_idmap, load_as_shared_library)) {
return {};
}
break;
@@ -717,4 +773,8 @@ std::unique_ptr<const LoadedArsc> LoadedArsc::Load(const void* data, size_t len,
return std::move(loaded_arsc);
}
std::unique_ptr<const LoadedArsc> LoadedArsc::CreateEmpty() {
return std::unique_ptr<LoadedArsc>(new LoadedArsc());
}
} // namespace android

View File

@@ -28,36 +28,63 @@
namespace android {
class LoadedIdmap;
// Holds an APK.
class ApkAssets {
public:
// Creates an ApkAssets.
// If `system` is true, the package is marked as a system package, and allows some functions to
// filter out this package when computing what configurations/resources are available.
static std::unique_ptr<const ApkAssets> Load(const std::string& path, bool system = false);
// Creates an ApkAssets, but forces any package with ID 0x7f to be loaded as a shared library.
// If `system` is true, the package is marked as a system package, and allows some functions to
// filter out this package when computing what configurations/resources are available.
static std::unique_ptr<const ApkAssets> LoadAsSharedLibrary(const std::string& path,
bool system = false);
// Creates an ApkAssets from an IDMAP, which contains the original APK path, and the overlay
// data.
// If `system` is true, the package is marked as a system package, and allows some functions to
// filter out this package when computing what configurations/resources are available.
static std::unique_ptr<const ApkAssets> LoadOverlay(const std::string& idmap_path,
bool system = false);
std::unique_ptr<Asset> Open(const std::string& path,
Asset::AccessMode mode = Asset::AccessMode::ACCESS_RANDOM) const;
bool ForEachFile(const std::string& path,
const std::function<void(const StringPiece&, FileType)>& f) const;
inline const std::string& GetPath() const { return path_; }
inline const std::string& GetPath() const {
return path_;
}
inline const LoadedArsc* GetLoadedArsc() const { return loaded_arsc_.get(); }
// This is never nullptr.
inline const LoadedArsc* GetLoadedArsc() const {
return loaded_arsc_.get();
}
private:
DISALLOW_COPY_AND_ASSIGN(ApkAssets);
static std::unique_ptr<const ApkAssets> LoadImpl(const std::string& path, bool system,
bool load_as_shared_library);
static std::unique_ptr<const ApkAssets> LoadImpl(const std::string& path,
std::unique_ptr<Asset> idmap_asset,
std::unique_ptr<const LoadedIdmap> loaded_idmap,
bool system, bool load_as_shared_library);
ApkAssets();
// Creates an Asset from any file on the file system.
static std::unique_ptr<Asset> CreateAssetFromFile(const std::string& path);
ApkAssets(void* unmanaged_handle, const std::string& path);
using ZipArchivePtr = std::unique_ptr<void, void(*)(void*)>;
ZipArchivePtr zip_handle_;
std::string path_;
const std::string path_;
std::unique_ptr<Asset> resources_asset_;
std::unique_ptr<Asset> idmap_asset_;
std::unique_ptr<const LoadedArsc> loaded_arsc_;
};

View File

@@ -96,24 +96,29 @@ class AssetManager2 {
// new resource IDs.
bool SetApkAssets(const std::vector<const ApkAssets*>& apk_assets, bool invalidate_caches = true);
inline const std::vector<const ApkAssets*> GetApkAssets() const { return apk_assets_; }
inline const std::vector<const ApkAssets*> GetApkAssets() const {
return apk_assets_;
}
// Returns the string pool for the given asset cookie.
// Use the string pool returned here with a valid Res_value object of
// type Res_value::TYPE_STRING.
// Use the string pool returned here with a valid Res_value object of type Res_value::TYPE_STRING.
const ResStringPool* GetStringPoolForCookie(ApkAssetsCookie cookie) const;
// Returns the DynamicRefTable for the given package ID.
// This may be nullptr if the APK represented by `cookie` has no resource table.
const DynamicRefTable* GetDynamicRefTableForPackage(uint32_t package_id) const;
// Returns the DynamicRefTable for the ApkAssets represented by the cookie.
// This may be nullptr if the APK represented by `cookie` has no resource table.
const DynamicRefTable* GetDynamicRefTableForCookie(ApkAssetsCookie cookie) const;
// Sets/resets the configuration for this AssetManager. This will cause all
// caches that are related to the configuration change to be invalidated.
void SetConfiguration(const ResTable_config& configuration);
inline const ResTable_config& GetConfiguration() const { return configuration_; }
inline const ResTable_config& GetConfiguration() const {
return configuration_;
}
// Returns all configurations for which there are resources defined. This includes resource
// configurations in all the ApkAssets set for this AssetManager.
@@ -227,6 +232,14 @@ class AssetManager2 {
// Creates a new Theme from this AssetManager.
std::unique_ptr<Theme> NewTheme();
template <typename Func>
void ForEachPackage(Func func) {
for (const PackageGroup& package_group : package_groups_) {
func(package_group.packages_.front()->GetPackageName(),
package_group.dynamic_ref_table.mAssignedPackageId);
}
}
void DumpToLog() const;
private:

View File

@@ -0,0 +1,76 @@
/*
* Copyright (C) 2017 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef IDMAP_H_
#define IDMAP_H_
#include <memory>
#include <string>
#include <unordered_map>
#include "android-base/macros.h"
#include "androidfw/StringPiece.h"
namespace android {
struct Idmap_header;
struct IdmapEntry_header;
// Represents a loaded/parsed IDMAP for a Runtime Resource Overlay (RRO).
// An RRO and its target APK have different resource IDs assigned to their resources. Overlaying
// a resource is done by resource name. An IDMAP is a generated mapping between the resource IDs
// of the RRO and the target APK for each resource with the same name.
// A LoadedIdmap can be set alongside the overlay's LoadedArsc to allow the overlay ApkAssets to
// masquerade as the target ApkAssets resources.
class LoadedIdmap {
public:
// Loads an IDMAP from a chunk of memory. Returns nullptr if the IDMAP data was malformed.
static std::unique_ptr<const LoadedIdmap> Load(const StringPiece& idmap_data);
// Performs a lookup of the expected entry ID for the given IDMAP entry header.
// Returns true if the mapping exists and fills `output_entry_id` with the result.
static bool Lookup(const IdmapEntry_header* header, uint16_t input_entry_id,
uint16_t* output_entry_id);
// Returns the package ID for which this overlay should apply.
uint8_t TargetPackageId() const;
// Returns the path to the RRO (Runtime Resource Overlay) APK for which this IDMAP was generated.
inline const std::string& OverlayApkPath() const {
return overlay_apk_path_;
}
// Returns the mapping of target entry ID to overlay entry ID for the given target type.
const IdmapEntry_header* GetEntryMapForType(uint8_t type_id) const;
protected:
// Exposed as protected so that tests can subclass and mock this class out.
LoadedIdmap() = default;
const Idmap_header* header_ = nullptr;
std::string overlay_apk_path_;
std::unordered_map<uint8_t, const IdmapEntry_header*> type_map_;
private:
DISALLOW_COPY_AND_ASSIGN(LoadedIdmap);
explicit LoadedIdmap(const Idmap_header* header);
};
} // namespace android
#endif // IDMAP_H_

View File

@@ -25,6 +25,7 @@
#include "androidfw/ByteBucketArray.h"
#include "androidfw/Chunk.h"
#include "androidfw/Idmap.h"
#include "androidfw/ResourceTypes.h"
#include "androidfw/Util.h"
@@ -70,20 +71,36 @@ class LoadedPackage {
uint32_t* out_flags) const;
// Returns the string pool where type names are stored.
inline const ResStringPool* GetTypeStringPool() const { return &type_string_pool_; }
inline const ResStringPool* GetTypeStringPool() const {
return &type_string_pool_;
}
// Returns the string pool where the names of resource entries are stored.
inline const ResStringPool* GetKeyStringPool() const { return &key_string_pool_; }
inline const ResStringPool* GetKeyStringPool() const {
return &key_string_pool_;
}
inline const std::string& GetPackageName() const { return package_name_; }
inline const std::string& GetPackageName() const {
return package_name_;
}
inline int GetPackageId() const { return package_id_; }
inline int GetPackageId() const {
return package_id_;
}
// Returns true if this package is dynamic (shared library) and needs to have an ID assigned.
inline bool IsDynamic() const { return dynamic_; }
inline bool IsDynamic() const {
return dynamic_;
}
// Returns true if this package originates from a system provided resource.
inline bool IsSystem() const { return system_; }
inline bool IsSystem() const {
return system_;
}
inline bool IsOverlay() const {
return overlay_;
}
// Returns the map of package name to package ID used in this LoadedPackage. At runtime, a
// package could have been assigned a different package ID than what this LoadedPackage was
@@ -111,7 +128,7 @@ class LoadedPackage {
private:
DISALLOW_COPY_AND_ASSIGN(LoadedPackage);
static std::unique_ptr<LoadedPackage> Load(const Chunk& chunk);
static std::unique_ptr<LoadedPackage> Load(const Chunk& chunk, const LoadedIdmap* loaded_idmap);
LoadedPackage() = default;
@@ -122,6 +139,7 @@ class LoadedPackage {
int type_id_offset_ = 0;
bool dynamic_ = false;
bool system_ = false;
bool overlay_ = false;
ByteBucketArray<util::unique_cptr<TypeSpec>> type_specs_;
std::vector<DynamicPackageEntry> dynamic_package_map_;
@@ -137,14 +155,21 @@ class LoadedArsc {
// If `load_as_shared_library` is set to true, the application package (0x7f) is treated
// as a shared library (0x00). When loaded into an AssetManager, the package will be assigned an
// ID.
static std::unique_ptr<const LoadedArsc> Load(const void* data, size_t len, bool system = false,
static std::unique_ptr<const LoadedArsc> Load(const StringPiece& data,
const LoadedIdmap* loaded_idmap = nullptr,
bool system = false,
bool load_as_shared_library = false);
// Create an empty LoadedArsc. This is used when an APK has no resources.arsc.
static std::unique_ptr<const LoadedArsc> CreateEmpty();
~LoadedArsc();
// Returns the string pool where all string resource values
// (Res_value::dataType == Res_value::TYPE_STRING) are indexed.
inline const ResStringPool* GetStringPool() const { return &global_string_pool_; }
inline const ResStringPool* GetStringPool() const {
return &global_string_pool_;
}
// Finds the resource with ID `resid` with the best value for configuration `config`.
// The parameter `out_entry` will be filled with the resulting resource entry.
@@ -157,7 +182,9 @@ class LoadedArsc {
const LoadedPackage* GetPackageForId(uint32_t resid) const;
// Returns true if this is a system provided resource.
inline bool IsSystem() const { return system_; }
inline bool IsSystem() const {
return system_;
}
// Returns a vector of LoadedPackage pointers, representing the packages in this LoadedArsc.
inline const std::vector<std::unique_ptr<const LoadedPackage>>& GetPackages() const {
@@ -168,7 +195,7 @@ class LoadedArsc {
DISALLOW_COPY_AND_ASSIGN(LoadedArsc);
LoadedArsc() = default;
bool LoadTable(const Chunk& chunk, bool load_as_shared_library);
bool LoadTable(const Chunk& chunk, const LoadedIdmap* loaded_idmap, bool load_as_shared_library);
ResStringPool global_string_pool_;
std::vector<std::unique_ptr<const LoadedPackage>> packages_;

View File

@@ -38,6 +38,9 @@
namespace android {
constexpr const static uint32_t kIdmapMagic = 0x504D4449u;
constexpr const static uint32_t kIdmapCurrentVersion = 0x00000001u;
/**
* In C++11, char16_t is defined as *at least* 16 bits. We do a lot of
* casting on raw data and expect char16_t to be exactly 16 bits.
@@ -1583,6 +1586,30 @@ struct ResTable_lib_entry
uint16_t packageName[128];
};
struct alignas(uint32_t) Idmap_header {
// Always 0x504D4449 ('IDMP')
uint32_t magic;
uint32_t version;
uint32_t target_crc32;
uint32_t overlay_crc32;
uint8_t target_path[256];
uint8_t overlay_path[256];
uint16_t target_package_id;
uint16_t type_count;
} __attribute__((packed));
struct alignas(uint32_t) IdmapEntry_header {
uint16_t target_type_id;
uint16_t overlay_type_id;
uint16_t entry_count;
uint16_t entry_id_offset;
uint32_t entries[0];
} __attribute__((packed));
class AssetManager2;
/**

View File

@@ -17,12 +17,14 @@
#include "androidfw/ApkAssets.h"
#include "android-base/file.h"
#include "android-base/test_utils.h"
#include "android-base/unique_fd.h"
#include "androidfw/Util.h"
#include "TestHelpers.h"
#include "data/basic/R.h"
using com::android::basic::R;
using ::com::android::basic::R;
namespace android {
@@ -54,6 +56,37 @@ TEST(ApkAssetsTest, LoadApkAsSharedLibrary) {
EXPECT_TRUE(loaded_arsc->GetPackages()[0]->IsDynamic());
}
TEST(ApkAssetsTest, LoadApkWithIdmap) {
std::string contents;
ResTable target_table;
const std::string target_path = GetTestDataPath() + "/basic/basic.apk";
ASSERT_TRUE(ReadFileFromZipToString(target_path, "resources.arsc", &contents));
ASSERT_EQ(NO_ERROR, target_table.add(contents.data(), contents.size(), 0, true /*copyData*/));
ResTable overlay_table;
const std::string overlay_path = GetTestDataPath() + "/overlay/overlay.apk";
ASSERT_TRUE(ReadFileFromZipToString(overlay_path, "resources.arsc", &contents));
ASSERT_EQ(NO_ERROR, overlay_table.add(contents.data(), contents.size(), 0, true /*copyData*/));
util::unique_cptr<void> idmap_data;
void* temp_data;
size_t idmap_len;
ASSERT_EQ(NO_ERROR, target_table.createIdmap(overlay_table, 0u, 0u, target_path.c_str(),
overlay_path.c_str(), &temp_data, &idmap_len));
idmap_data.reset(temp_data);
TemporaryFile tf;
ASSERT_TRUE(base::WriteFully(tf.fd, idmap_data.get(), idmap_len));
close(tf.fd);
// Open something so that the destructor of TemporaryFile closes a valid fd.
tf.fd = open("/dev/null", O_WRONLY);
std::unique_ptr<const ApkAssets> loaded_overlay_apk = ApkAssets::LoadOverlay(tf.path);
ASSERT_NE(nullptr, loaded_overlay_apk);
}
TEST(ApkAssetsTest, CreateAndDestroyAssetKeepsApkAssetsOpen) {
std::unique_ptr<const ApkAssets> loaded_apk =
ApkAssets::Load(GetTestDataPath() + "/basic/basic.apk");

View File

@@ -22,7 +22,7 @@
#include "TestHelpers.h"
#include "data/basic/R.h"
using com::android::basic::R;
using ::com::android::basic::R;
namespace android {

View File

@@ -32,8 +32,7 @@ TEST(LoadedArscTest, LoadSinglePackageArsc) {
ASSERT_TRUE(ReadFileFromZipToString(GetTestDataPath() + "/styles/styles.apk", "resources.arsc",
&contents));
std::unique_ptr<const LoadedArsc> loaded_arsc =
LoadedArsc::Load(contents.data(), contents.size());
std::unique_ptr<const LoadedArsc> loaded_arsc = LoadedArsc::Load(StringPiece(contents));
ASSERT_NE(nullptr, loaded_arsc);
const std::vector<std::unique_ptr<const LoadedPackage>>& packages = loaded_arsc->GetPackages();
@@ -59,8 +58,7 @@ TEST(LoadedArscTest, FindDefaultEntry) {
ASSERT_TRUE(
ReadFileFromZipToString(GetTestDataPath() + "/basic/basic.apk", "resources.arsc", &contents));
std::unique_ptr<const LoadedArsc> loaded_arsc =
LoadedArsc::Load(contents.data(), contents.size());
std::unique_ptr<const LoadedArsc> loaded_arsc = LoadedArsc::Load(StringPiece(contents));
ASSERT_NE(nullptr, loaded_arsc);
ResTable_config desired_config;
@@ -82,8 +80,7 @@ TEST(LoadedArscTest, LoadSharedLibrary) {
ASSERT_TRUE(ReadFileFromZipToString(GetTestDataPath() + "/lib_one/lib_one.apk", "resources.arsc",
&contents));
std::unique_ptr<const LoadedArsc> loaded_arsc =
LoadedArsc::Load(contents.data(), contents.size());
std::unique_ptr<const LoadedArsc> loaded_arsc = LoadedArsc::Load(StringPiece(contents));
ASSERT_NE(nullptr, loaded_arsc);
const auto& packages = loaded_arsc->GetPackages();
@@ -104,8 +101,7 @@ TEST(LoadedArscTest, LoadAppLinkedAgainstSharedLibrary) {
ASSERT_TRUE(ReadFileFromZipToString(GetTestDataPath() + "/libclient/libclient.apk",
"resources.arsc", &contents));
std::unique_ptr<const LoadedArsc> loaded_arsc =
LoadedArsc::Load(contents.data(), contents.size());
std::unique_ptr<const LoadedArsc> loaded_arsc = LoadedArsc::Load(StringPiece(contents));
ASSERT_NE(nullptr, loaded_arsc);
const auto& packages = loaded_arsc->GetPackages();
@@ -132,8 +128,9 @@ TEST(LoadedArscTest, LoadAppAsSharedLibrary) {
ASSERT_TRUE(ReadFileFromZipToString(GetTestDataPath() + "/appaslib/appaslib.apk",
"resources.arsc", &contents));
std::unique_ptr<const LoadedArsc> loaded_arsc = LoadedArsc::Load(
contents.data(), contents.size(), false /*system*/, true /*load_as_shared_library*/);
std::unique_ptr<const LoadedArsc> loaded_arsc =
LoadedArsc::Load(StringPiece(contents), nullptr /*loaded_idmap*/, false /*system*/,
true /*load_as_shared_library*/);
ASSERT_NE(nullptr, loaded_arsc);
const auto& packages = loaded_arsc->GetPackages();
@@ -147,8 +144,7 @@ TEST(LoadedArscTest, LoadFeatureSplit) {
std::string contents;
ASSERT_TRUE(ReadFileFromZipToString(GetTestDataPath() + "/feature/feature.apk", "resources.arsc",
&contents));
std::unique_ptr<const LoadedArsc> loaded_arsc =
LoadedArsc::Load(contents.data(), contents.size());
std::unique_ptr<const LoadedArsc> loaded_arsc = LoadedArsc::Load(StringPiece(contents));
ASSERT_NE(nullptr, loaded_arsc);
ResTable_config desired_config;
@@ -174,6 +170,54 @@ TEST(LoadedArscTest, LoadFeatureSplit) {
EXPECT_EQ(std::string("string"), type_name);
}
class MockLoadedIdmap : public LoadedIdmap {
public:
MockLoadedIdmap() : LoadedIdmap() {
local_header_.magic = kIdmapMagic;
local_header_.version = kIdmapCurrentVersion;
local_header_.target_package_id = 0x08;
local_header_.type_count = 1;
header_ = &local_header_;
entry_header = util::unique_cptr<IdmapEntry_header>(
(IdmapEntry_header*)::malloc(sizeof(IdmapEntry_header) + sizeof(uint32_t)));
entry_header->target_type_id = 0x03;
entry_header->overlay_type_id = 0x02;
entry_header->entry_id_offset = 1;
entry_header->entry_count = 1;
entry_header->entries[0] = 0x00000000u;
type_map_[entry_header->overlay_type_id] = entry_header.get();
}
private:
Idmap_header local_header_;
util::unique_cptr<IdmapEntry_header> entry_header;
};
TEST(LoadedArscTest, LoadOverlay) {
std::string contents, overlay_contents;
ASSERT_TRUE(
ReadFileFromZipToString(GetTestDataPath() + "/basic/basic.apk", "resources.arsc", &contents));
ASSERT_TRUE(ReadFileFromZipToString(GetTestDataPath() + "/overlay/overlay.apk", "resources.arsc",
&overlay_contents));
MockLoadedIdmap loaded_idmap;
std::unique_ptr<const LoadedArsc> loaded_arsc =
LoadedArsc::Load(StringPiece(overlay_contents), &loaded_idmap);
ASSERT_NE(nullptr, loaded_arsc);
ResTable_config desired_config;
memset(&desired_config, 0, sizeof(desired_config));
LoadedArscEntry entry;
ResTable_config selected_config;
uint32_t flags;
ASSERT_TRUE(
loaded_arsc->FindEntry(0x08030001u, desired_config, &entry, &selected_config, &flags));
}
// structs with size fields (like Res_value, ResTable_entry) should be
// backwards and forwards compatible (aka checking the size field against
// sizeof(Res_value) might not be backwards compatible.