Ryan Mitchell 2ed8bfa7fd Add fabricated RRO generation to libidmap2
Fabricated Runtime Resource Overlays are overlays that are generated
at runtime and are stored in the data/ partition.

The system can fabricate RROs at runtime to dynamically theme the
device. Idmaps can now be created from APK RROs and fabricated RROs.

Rather than operating on ApkAssets, libidmap2 now operates on abstract
resource "containers" that supply resource values. Target resource
containers implement methods needed to query overlayable and target
overlay information. Currently only APKs can be loaded as target
resource containers. Overlay resource containers implement methods to
supply the mapping of target resource to overlay value and other
overlay information.

The format of a fabricated RRO is as follows:
0x00 - 0x04 : fabricated overlay magic (always FRRO)
0x04 - 0x08 : file format version
0x08 - 0x0c : crc of version + proto data
0x0c - EOF  : proto fabricated overlay data

The magic is used to quickly detect if the file is a fabricated overlay.
The version is incremented whenever backwards incompatible changes are
made to the proto file format. Idmap must always be able to upgrade
fabricated overlays from previous versions to new versions, so all
previous versions must be checked into the tree.

Bug: 172471315
Test: libidmap2_tests && libandroidfw_tests
Change-Id: I4c9f29da278672e5695fb57d131a44c11a835180
2021-02-09 20:13:50 -08:00

406 lines
15 KiB
C++

/*
* Copyright (C) 2021 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 "androidfw/AssetsProvider.h"
#include <sys/stat.h>
#include <android-base/errors.h>
#include <android-base/stringprintf.h>
#include <android-base/utf8.h>
#include <ziparchive/zip_archive.h>
namespace android {
namespace {
constexpr const char* kEmptyDebugString = "<empty>";
} // namespace
std::unique_ptr<Asset> AssetsProvider::Open(const std::string& path, Asset::AccessMode mode,
bool* file_exists) const {
return OpenInternal(path, mode, file_exists);
}
std::unique_ptr<Asset> AssetsProvider::CreateAssetFromFile(const std::string& path) {
base::unique_fd fd(base::utf8::open(path.c_str(), O_RDONLY | O_CLOEXEC));
if (!fd.ok()) {
LOG(ERROR) << "Failed to open file '" << path << "': " << base::SystemErrorCodeToString(errno);
return {};
}
return CreateAssetFromFd(std::move(fd), path.c_str());
}
std::unique_ptr<Asset> AssetsProvider::CreateAssetFromFd(base::unique_fd fd,
const char* path,
off64_t offset,
off64_t length) {
CHECK(length >= kUnknownLength) << "length must be greater than or equal to " << kUnknownLength;
CHECK(length != kUnknownLength || offset == 0) << "offset must be 0 if length is "
<< kUnknownLength;
if (length == kUnknownLength) {
length = lseek64(fd, 0, SEEK_END);
if (length < 0) {
LOG(ERROR) << "Failed to get size of file '" << ((path) ? path : "anon") << "': "
<< base::SystemErrorCodeToString(errno);
return {};
}
}
incfs::IncFsFileMap file_map;
if (!file_map.Create(fd, offset, static_cast<size_t>(length), path)) {
LOG(ERROR) << "Failed to mmap file '" << ((path != nullptr) ? path : "anon") << "': "
<< base::SystemErrorCodeToString(errno);
return {};
}
// If `path` is set, do not pass ownership of the `fd` to the new Asset since
// Asset::openFileDescriptor can use `path` to create new file descriptors.
return Asset::createFromUncompressedMap(std::move(file_map),
Asset::AccessMode::ACCESS_RANDOM,
(path != nullptr) ? base::unique_fd(-1) : std::move(fd));
}
ZipAssetsProvider::PathOrDebugName::PathOrDebugName(std::string&& value, bool is_path)
: value_(std::forward<std::string>(value)), is_path_(is_path) {}
const std::string* ZipAssetsProvider::PathOrDebugName::GetPath() const {
return is_path_ ? &value_ : nullptr;
}
const std::string& ZipAssetsProvider::PathOrDebugName::GetDebugName() const {
return value_;
}
ZipAssetsProvider::ZipAssetsProvider(ZipArchiveHandle handle, PathOrDebugName&& path,
time_t last_mod_time)
: zip_handle_(handle, ::CloseArchive),
name_(std::forward<PathOrDebugName>(path)),
last_mod_time_(last_mod_time) {}
std::unique_ptr<ZipAssetsProvider> ZipAssetsProvider::Create(std::string path) {
ZipArchiveHandle handle;
if (int32_t result = OpenArchive(path.c_str(), &handle); result != 0) {
LOG(ERROR) << "Failed to open APK '" << path << "': " << ::ErrorCodeString(result);
CloseArchive(handle);
return {};
}
struct stat sb{.st_mtime = -1};
if (stat(path.c_str(), &sb) < 0) {
// Stat requires execute permissions on all directories path to the file. If the process does
// not have execute permissions on this file, allow the zip to be opened but IsUpToDate() will
// always have to return true.
LOG(WARNING) << "Failed to stat file '" << path << "': "
<< base::SystemErrorCodeToString(errno);
}
return std::unique_ptr<ZipAssetsProvider>(
new ZipAssetsProvider(handle, PathOrDebugName{std::move(path),
true /* is_path */}, sb.st_mtime));
}
std::unique_ptr<ZipAssetsProvider> ZipAssetsProvider::Create(base::unique_fd fd,
std::string friendly_name,
off64_t offset,
off64_t len) {
ZipArchiveHandle handle;
const int released_fd = fd.release();
const int32_t result = (len == AssetsProvider::kUnknownLength)
? ::OpenArchiveFd(released_fd, friendly_name.c_str(), &handle)
: ::OpenArchiveFdRange(released_fd, friendly_name.c_str(), &handle, len, offset);
if (result != 0) {
LOG(ERROR) << "Failed to open APK '" << friendly_name << "' through FD with offset " << offset
<< " and length " << len << ": " << ::ErrorCodeString(result);
CloseArchive(handle);
return {};
}
struct stat sb{.st_mtime = -1};
if (fstat(released_fd, &sb) < 0) {
// Stat requires execute permissions on all directories path to the file. If the process does
// not have execute permissions on this file, allow the zip to be opened but IsUpToDate() will
// always have to return true.
LOG(WARNING) << "Failed to fstat file '" << friendly_name << "': "
<< base::SystemErrorCodeToString(errno);
}
return std::unique_ptr<ZipAssetsProvider>(
new ZipAssetsProvider(handle, PathOrDebugName{std::move(friendly_name),
false /* is_path */}, sb.st_mtime));
}
std::unique_ptr<Asset> ZipAssetsProvider::OpenInternal(const std::string& path,
Asset::AccessMode mode,
bool* file_exists) const {
if (file_exists != nullptr) {
*file_exists = false;
}
ZipEntry entry;
if (FindEntry(zip_handle_.get(), path, &entry) != 0) {
return {};
}
if (file_exists != nullptr) {
*file_exists = true;
}
const int fd = GetFileDescriptor(zip_handle_.get());
const off64_t fd_offset = GetFileDescriptorOffset(zip_handle_.get());
incfs::IncFsFileMap asset_map;
if (entry.method == kCompressDeflated) {
if (!asset_map.Create(fd, entry.offset + fd_offset, entry.compressed_length,
name_.GetDebugName().c_str())) {
LOG(ERROR) << "Failed to mmap file '" << path << "' in APK '" << name_.GetDebugName()
<< "'";
return {};
}
std::unique_ptr<Asset> asset =
Asset::createFromCompressedMap(std::move(asset_map), entry.uncompressed_length, mode);
if (asset == nullptr) {
LOG(ERROR) << "Failed to decompress '" << path << "' in APK '" << name_.GetDebugName()
<< "'";
return {};
}
return asset;
}
if (!asset_map.Create(fd, entry.offset + fd_offset, entry.uncompressed_length,
name_.GetDebugName().c_str())) {
LOG(ERROR) << "Failed to mmap file '" << path << "' in APK '" << name_.GetDebugName() << "'";
return {};
}
base::unique_fd ufd;
if (name_.GetPath() == nullptr) {
// If the zip name does not represent a path, create a new `fd` for the new Asset to own in
// order to create new file descriptors using Asset::openFileDescriptor. If the zip name is a
// path, it will be used to create new file descriptors.
ufd = base::unique_fd(dup(fd));
if (!ufd.ok()) {
LOG(ERROR) << "Unable to dup fd '" << path << "' in APK '" << name_.GetDebugName() << "'";
return {};
}
}
auto asset = Asset::createFromUncompressedMap(std::move(asset_map), mode, std::move(ufd));
if (asset == nullptr) {
LOG(ERROR) << "Failed to mmap file '" << path << "' in APK '" << name_.GetDebugName() << "'";
return {};
}
return asset;
}
bool ZipAssetsProvider::ForEachFile(const std::string& root_path,
const std::function<void(const StringPiece&, FileType)>& f)
const {
std::string root_path_full = root_path;
if (root_path_full.back() != '/') {
root_path_full += '/';
}
void* cookie;
if (StartIteration(zip_handle_.get(), &cookie, root_path_full, "") != 0) {
return false;
}
std::string name;
::ZipEntry entry{};
// We need to hold back directories because many paths will contain them and we want to only
// surface one.
std::set<std::string> dirs{};
int32_t result;
while ((result = Next(cookie, &entry, &name)) == 0) {
StringPiece full_file_path(name);
StringPiece leaf_file_path = full_file_path.substr(root_path_full.size());
if (!leaf_file_path.empty()) {
auto iter = std::find(leaf_file_path.begin(), leaf_file_path.end(), '/');
if (iter != leaf_file_path.end()) {
std::string dir =
leaf_file_path.substr(0, std::distance(leaf_file_path.begin(), iter)).to_string();
dirs.insert(std::move(dir));
} else {
f(leaf_file_path, kFileTypeRegular);
}
}
}
EndIteration(cookie);
// Now present the unique directories.
for (const std::string& dir : dirs) {
f(dir, kFileTypeDirectory);
}
// -1 is end of iteration, anything else is an error.
return result == -1;
}
std::optional<uint32_t> ZipAssetsProvider::GetCrc(std::string_view path) const {
::ZipEntry entry;
if (FindEntry(zip_handle_.get(), path, &entry) != 0) {
return {};
}
return entry.crc32;
}
const std::string& ZipAssetsProvider::GetDebugName() const {
return name_.GetDebugName();
}
bool ZipAssetsProvider::IsUpToDate() const {
struct stat sb{};
if (fstat(GetFileDescriptor(zip_handle_.get()), &sb) < 0) {
// If fstat fails on the zip archive, return true so the zip archive the resource system does
// attempt to refresh the ApkAsset.
return true;
}
return last_mod_time_ == sb.st_mtime;
}
DirectoryAssetsProvider::DirectoryAssetsProvider(std::string&& path, time_t last_mod_time)
: dir_(std::forward<std::string>(path)), last_mod_time_(last_mod_time) {}
std::unique_ptr<DirectoryAssetsProvider> DirectoryAssetsProvider::Create(std::string path) {
struct stat sb{};
const int result = stat(path.c_str(), &sb);
if (result == -1) {
LOG(ERROR) << "Failed to find directory '" << path << "'.";
return nullptr;
}
if (!S_ISDIR(sb.st_mode)) {
LOG(ERROR) << "Path '" << path << "' is not a directory.";
return nullptr;
}
if (path[path.size() - 1] != OS_PATH_SEPARATOR) {
path += OS_PATH_SEPARATOR;
}
return std::unique_ptr<DirectoryAssetsProvider>(new DirectoryAssetsProvider(std::move(path),
sb.st_mtime));
}
std::unique_ptr<Asset> DirectoryAssetsProvider::OpenInternal(const std::string& path,
Asset::AccessMode /* mode */,
bool* file_exists) const {
const std::string resolved_path = dir_ + path;
if (file_exists != nullptr) {
struct stat sb{};
*file_exists = (stat(resolved_path.c_str(), &sb) != -1) && S_ISREG(sb.st_mode);
}
return CreateAssetFromFile(resolved_path);
}
bool DirectoryAssetsProvider::ForEachFile(
const std::string& /* root_path */,
const std::function<void(const StringPiece&, FileType)>& /* f */)
const {
return true;
}
const std::string& DirectoryAssetsProvider::GetDebugName() const {
return dir_;
}
bool DirectoryAssetsProvider::IsUpToDate() const {
struct stat sb{};
if (stat(dir_.c_str(), &sb) < 0) {
// If stat fails on the zip archive, return true so the zip archive the resource system does
// attempt to refresh the ApkAsset.
return true;
}
return last_mod_time_ == sb.st_mtime;
}
MultiAssetsProvider::MultiAssetsProvider(std::unique_ptr<AssetsProvider>&& primary,
std::unique_ptr<AssetsProvider>&& secondary)
: primary_(std::forward<std::unique_ptr<AssetsProvider>>(primary)),
secondary_(std::forward<std::unique_ptr<AssetsProvider>>(secondary)) {
if (primary_->GetDebugName() == kEmptyDebugString) {
debug_name_ = secondary_->GetDebugName();
} else if (secondary_->GetDebugName() == kEmptyDebugString) {
debug_name_ = primary_->GetDebugName();
} else {
debug_name_ = primary_->GetDebugName() + " and " + secondary_->GetDebugName();
}
}
std::unique_ptr<AssetsProvider> MultiAssetsProvider::Create(
std::unique_ptr<AssetsProvider>&& primary, std::unique_ptr<AssetsProvider>&& secondary) {
if (primary == nullptr || secondary == nullptr) {
return nullptr;
}
return std::unique_ptr<MultiAssetsProvider>(new MultiAssetsProvider(std::move(primary),
std::move(secondary)));
}
std::unique_ptr<Asset> MultiAssetsProvider::OpenInternal(const std::string& path,
Asset::AccessMode mode,
bool* file_exists) const {
auto asset = primary_->Open(path, mode, file_exists);
return (asset) ? std::move(asset) : secondary_->Open(path, mode, file_exists);
}
bool MultiAssetsProvider::ForEachFile(const std::string& root_path,
const std::function<void(const StringPiece&, FileType)>& f)
const {
return primary_->ForEachFile(root_path, f) && secondary_->ForEachFile(root_path, f);
}
const std::string& MultiAssetsProvider::GetDebugName() const {
return debug_name_;
}
bool MultiAssetsProvider::IsUpToDate() const {
return primary_->IsUpToDate() && secondary_->IsUpToDate();
}
std::unique_ptr<AssetsProvider> EmptyAssetsProvider::Create() {
return std::make_unique<EmptyAssetsProvider>();
}
std::unique_ptr<Asset> EmptyAssetsProvider::OpenInternal(const std::string& /* path */,
Asset::AccessMode /* mode */,
bool* file_exists) const {
if (file_exists) {
*file_exists = false;
}
return nullptr;
}
bool EmptyAssetsProvider::ForEachFile(
const std::string& /* root_path */,
const std::function<void(const StringPiece&, FileType)>& /* f */) const {
return true;
}
const std::string& EmptyAssetsProvider::GetDebugName() const {
const static std::string kEmpty = kEmptyDebugString;
return kEmpty;
}
bool EmptyAssetsProvider::IsUpToDate() const {
return true;
}
} // namespace android