| /* | |
| * Copyright (C) 2016 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/ApkAssets.h" | |
| #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 "ziparchive/zip_archive.h" | |
| #include "androidfw/Asset.h" | |
| #include "androidfw/Idmap.h" | |
| #include "androidfw/misc.h" | |
| #include "androidfw/ResourceTypes.h" | |
| #include "androidfw/Util.h" | |
| namespace android { | |
| using base::SystemErrorCodeToString; | |
| using base::unique_fd; | |
| static const std::string kResourcesArsc("resources.arsc"); | |
| ApkAssets::ApkAssets(ZipArchiveHandle unmanaged_handle, | |
| const std::string& path, | |
| time_t last_mod_time, | |
| package_property_t property_flags) | |
| : zip_handle_(unmanaged_handle, ::CloseArchive), path_(path), last_mod_time_(last_mod_time), | |
| property_flags_(property_flags) { | |
| } | |
| std::unique_ptr<const ApkAssets> ApkAssets::Load(const std::string& path, bool system, | |
| bool for_loader) { | |
| package_property_t flags = (system ? PROPERTY_SYSTEM : 0U) | | |
| (for_loader ? PROPERTY_LOADER : 0U); | |
| return LoadImpl({} /*fd*/, path, nullptr, nullptr, flags); | |
| } | |
| std::unique_ptr<const ApkAssets> ApkAssets::LoadAsSharedLibrary(const std::string& path, | |
| bool system) { | |
| package_property_t flags = PROPERTY_DYNAMIC | (system ? PROPERTY_SYSTEM : 0U); | |
| return LoadImpl({} /*fd*/, path, nullptr, nullptr, flags); | |
| } | |
| 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({} /*fd*/, loaded_idmap->OverlayApkPath(), | |
| std::move(idmap_asset), | |
| std::move(loaded_idmap), | |
| PROPERTY_OVERLAY | (system ? PROPERTY_SYSTEM : 0U)); | |
| } | |
| std::unique_ptr<const ApkAssets> ApkAssets::LoadFromFd(unique_fd fd, | |
| const std::string& friendly_name, | |
| bool system, bool force_shared_lib, | |
| bool for_loader) { | |
| package_property_t flags = (system ? PROPERTY_SYSTEM : 0U) | | |
| (force_shared_lib ? PROPERTY_DYNAMIC : 0U) | | |
| (for_loader ? PROPERTY_LOADER : 0U); | |
| return LoadImpl(std::move(fd), friendly_name, nullptr /*idmap_asset*/, nullptr /*loaded_idmap*/, | |
| flags); | |
| } | |
| std::unique_ptr<const ApkAssets> ApkAssets::LoadArsc(const std::string& path, | |
| bool for_loader) { | |
| return LoadArscImpl({} /*fd*/, path, for_loader ? PROPERTY_LOADER : 0U); | |
| } | |
| std::unique_ptr<const ApkAssets> ApkAssets::LoadArsc(unique_fd fd, | |
| const std::string& friendly_name, | |
| bool for_loader) { | |
| return LoadArscImpl(std::move(fd), friendly_name, for_loader ? PROPERTY_LOADER : 0U); | |
| } | |
| 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( | |
| unique_fd fd, const std::string& path, std::unique_ptr<Asset> idmap_asset, | |
| std::unique_ptr<const LoadedIdmap> loaded_idmap, package_property_t property_flags) { | |
| ::ZipArchiveHandle unmanaged_handle; | |
| int32_t result; | |
| if (fd >= 0) { | |
| result = | |
| ::OpenArchiveFd(fd.release(), path.c_str(), &unmanaged_handle, true /*assume_ownership*/); | |
| } else { | |
| result = ::OpenArchive(path.c_str(), &unmanaged_handle); | |
| } | |
| if (result != 0) { | |
| LOG(ERROR) << "Failed to open APK '" << path << "' " << ::ErrorCodeString(result); | |
| ::CloseArchive(unmanaged_handle); | |
| return {}; | |
| } | |
| time_t last_mod_time = getFileModDate(path.c_str()); | |
| // Wrap the handle in a unique_ptr so it gets automatically closed. | |
| std::unique_ptr<ApkAssets> | |
| loaded_apk(new ApkAssets(unmanaged_handle, path, last_mod_time, property_flags)); | |
| // Find the resource table. | |
| ::ZipEntry entry; | |
| result = ::FindEntry(loaded_apk->zip_handle_.get(), kResourcesArsc, &entry); | |
| if (result != 0) { | |
| // 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) { | |
| ANDROID_LOG(WARNING) << kResourcesArsc << " in APK '" << path << "' is compressed."; | |
| } | |
| // 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); | |
| loaded_apk->loaded_idmap_ = std::move(loaded_idmap); | |
| 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(data, loaded_apk->loaded_idmap_.get(), | |
| property_flags); | |
| if (loaded_apk->loaded_arsc_ == nullptr) { | |
| LOG(ERROR) << "Failed to load '" << kResourcesArsc << "' in APK '" << path << "'."; | |
| return {}; | |
| } | |
| // Need to force a move for mingw32. | |
| return std::move(loaded_apk); | |
| } | |
| std::unique_ptr<const ApkAssets> ApkAssets::LoadArscImpl(unique_fd fd, | |
| const std::string& path, | |
| package_property_t property_flags) { | |
| std::unique_ptr<Asset> resources_asset; | |
| if (fd >= 0) { | |
| resources_asset = std::unique_ptr<Asset>(Asset::createFromFd(fd.release(), nullptr, | |
| Asset::AccessMode::ACCESS_BUFFER)); | |
| } else { | |
| resources_asset = CreateAssetFromFile(path); | |
| } | |
| if (resources_asset == nullptr) { | |
| LOG(ERROR) << "Failed to open ARSC '" << path; | |
| return {}; | |
| } | |
| time_t last_mod_time = getFileModDate(path.c_str()); | |
| std::unique_ptr<ApkAssets> loaded_apk( | |
| new ApkAssets(nullptr, path, last_mod_time, property_flags)); | |
| loaded_apk->resources_asset_ = std::move(resources_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(data, nullptr, property_flags); | |
| if (loaded_apk->loaded_arsc_ == nullptr) { | |
| LOG(ERROR) << "Failed to load '" << kResourcesArsc << path; | |
| return {}; | |
| } | |
| // Need to force a move for mingw32. | |
| return std::move(loaded_apk); | |
| } | |
| std::unique_ptr<const ApkAssets> ApkAssets::LoadEmpty(bool for_loader) { | |
| std::unique_ptr<ApkAssets> loaded_apk(new ApkAssets(nullptr, "", -1, for_loader)); | |
| loaded_apk->loaded_arsc_ = LoadedArsc::CreateEmpty(); | |
| // Need to force a move for mingw32. | |
| return std::move(loaded_apk); | |
| } | |
| std::unique_ptr<Asset> ApkAssets::Open(const std::string& path, Asset::AccessMode mode) const { | |
| // If this is a resource loader from an .arsc, there will be no zip handle | |
| if (zip_handle_ == nullptr) { | |
| return {}; | |
| } | |
| ::ZipEntry entry; | |
| int32_t result = ::FindEntry(zip_handle_.get(), path, &entry); | |
| if (result != 0) { | |
| return {}; | |
| } | |
| if (entry.method == kCompressDeflated) { | |
| std::unique_ptr<FileMap> map = util::make_unique<FileMap>(); | |
| if (!map->create(path_.c_str(), ::GetFileDescriptor(zip_handle_.get()), entry.offset, | |
| entry.compressed_length, true /*readOnly*/)) { | |
| LOG(ERROR) << "Failed to mmap file '" << path << "' in APK '" << path_ << "'"; | |
| return {}; | |
| } | |
| std::unique_ptr<Asset> asset = | |
| Asset::createFromCompressedMap(std::move(map), entry.uncompressed_length, mode); | |
| if (asset == nullptr) { | |
| LOG(ERROR) << "Failed to decompress '" << path << "'."; | |
| return {}; | |
| } | |
| return asset; | |
| } else { | |
| std::unique_ptr<FileMap> map = util::make_unique<FileMap>(); | |
| if (!map->create(path_.c_str(), ::GetFileDescriptor(zip_handle_.get()), entry.offset, | |
| entry.uncompressed_length, true /*readOnly*/)) { | |
| LOG(ERROR) << "Failed to mmap file '" << path << "' in APK '" << path_ << "'"; | |
| return {}; | |
| } | |
| std::unique_ptr<Asset> asset = Asset::createFromUncompressedMap(std::move(map), mode); | |
| if (asset == nullptr) { | |
| LOG(ERROR) << "Failed to mmap file '" << path << "' in APK '" << path_ << "'"; | |
| return {}; | |
| } | |
| return asset; | |
| } | |
| } | |
| bool ApkAssets::ForEachFile(const std::string& root_path, | |
| const std::function<void(const StringPiece&, FileType)>& f) const { | |
| // If this is a resource loader from an .arsc, there will be no zip handle | |
| if (zip_handle_ == nullptr) { | |
| return false; | |
| } | |
| 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; | |
| } | |
| bool ApkAssets::IsUpToDate() const { | |
| if (IsLoader()) { | |
| // Loaders are invalidated by the app, not the system, so assume up to date. | |
| return true; | |
| } | |
| return last_mod_time_ == getFileModDate(path_.c_str()); | |
| } | |
| } // namespace android |