diff options
author | Ryan Mitchell <rtmitchell@google.com> | 2020-03-10 13:49:12 -0700 |
---|---|---|
committer | Ryan Mitchell <rtmitchell@google.com> | 2020-03-19 18:33:55 -0700 |
commit | c07aa702703388747bd6e9b1091127e2736ffcd8 (patch) | |
tree | 9d2f3a07e88e7d5a2ef367c4b8d95c81e07cb576 /libs/androidfw/ApkAssets.cpp | |
parent | 349695f3701e90746cb2197552299dcb99960ce8 (diff) |
Add ResourcesProvider.loadFromDirectory
This API allows a directory to be loaded as if it was a zipped APK.
This is a substitute for the DirectoryAssetProvider API that
currently does not work in the native layer.
Bug: 142716192
Test: atest FrameworksResourceLoaderTests
Change-Id: Ia13e15653e75b421423dd56f9fe89e183ab4cb9a
Diffstat (limited to 'libs/androidfw/ApkAssets.cpp')
-rw-r--r-- | libs/androidfw/ApkAssets.cpp | 457 |
1 files changed, 284 insertions, 173 deletions
diff --git a/libs/androidfw/ApkAssets.cpp b/libs/androidfw/ApkAssets.cpp index 946fcc03f57e..f5bf84f18a89 100644 --- a/libs/androidfw/ApkAssets.cpp +++ b/libs/androidfw/ApkAssets.cpp @@ -21,6 +21,7 @@ #include "android-base/errors.h" #include "android-base/file.h" #include "android-base/logging.h" +#include "android-base/stringprintf.h" #include "android-base/unique_fd.h" #include "android-base/utf8.h" #include "utils/Compat.h" @@ -40,28 +41,263 @@ using base::unique_fd; static const std::string kResourcesArsc("resources.arsc"); -ApkAssets::ApkAssets(ZipArchiveHandle unmanaged_handle, +ApkAssets::ApkAssets(std::unique_ptr<const AssetsProvider> assets_provider, std::string path, time_t last_mod_time, package_property_t property_flags) - : zip_handle_(unmanaged_handle, ::CloseArchive), + : assets_provider_(std::move(assets_provider)), path_(std::move(path)), last_mod_time_(last_mod_time), property_flags_(property_flags) { } -std::unique_ptr<const ApkAssets> ApkAssets::Load(const std::string& path, - const package_property_t flags) { - ::ZipArchiveHandle unmanaged_handle; - const int32_t result = ::OpenArchive(path.c_str(), &unmanaged_handle); - if (result != 0) { - LOG(ERROR) << "Failed to open APK '" << path << "' " << ::ErrorCodeString(result); - ::CloseArchive(unmanaged_handle); - return {}; +// Provides asset files from a zip file. +class ZipAssetsProvider : public AssetsProvider { + public: + ~ZipAssetsProvider() override = default; + + static std::unique_ptr<const AssetsProvider> Create(const std::string& path) { + ::ZipArchiveHandle unmanaged_handle; + const int32_t result = ::OpenArchive(path.c_str(), &unmanaged_handle); + if (result != 0) { + LOG(ERROR) << "Failed to open APK '" << path << "' " << ::ErrorCodeString(result); + ::CloseArchive(unmanaged_handle); + return {}; + } + + return std::unique_ptr<AssetsProvider>(new ZipAssetsProvider(path, path, unmanaged_handle)); + } + + static std::unique_ptr<const AssetsProvider> Create( + unique_fd fd, const std::string& friendly_name, const off64_t offset = 0, + const off64_t length = ApkAssets::kUnknownLength) { + + ::ZipArchiveHandle unmanaged_handle; + const int32_t result = (length == ApkAssets::kUnknownLength) + ? ::OpenArchiveFd(fd.release(), friendly_name.c_str(), &unmanaged_handle) + : ::OpenArchiveFdRange(fd.release(), friendly_name.c_str(), &unmanaged_handle, length, + offset); + + if (result != 0) { + LOG(ERROR) << "Failed to open APK '" << friendly_name << "' through FD with offset " << offset + << " and length " << length << ": " << ::ErrorCodeString(result); + ::CloseArchive(unmanaged_handle); + return {}; + } + + return std::unique_ptr<AssetsProvider>(new ZipAssetsProvider({}, friendly_name, + unmanaged_handle)); + } + + // Iterate over all files and directories within the zip. The order of iteration is not + // guaranteed to be the same as the order of elements in the central directory but is stable for a + // given zip file. + bool ForEachFile(const std::string& root_path, + const std::function<void(const StringPiece&, FileType)>& f) const override { + // 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; + } + + protected: + std::unique_ptr<Asset> OpenInternal( + const std::string& path, Asset::AccessMode mode, bool* file_exists) const override { + if (file_exists) { + *file_exists = false; + } + + ::ZipEntry entry; + int32_t result = ::FindEntry(zip_handle_.get(), path, &entry); + if (result != 0) { + return {}; + } + + if (file_exists) { + *file_exists = true; + } + + const int fd = ::GetFileDescriptor(zip_handle_.get()); + const off64_t fd_offset = ::GetFileDescriptorOffset(zip_handle_.get()); + if (entry.method == kCompressDeflated) { + std::unique_ptr<FileMap> map = util::make_unique<FileMap>(); + if (!map->create(GetPath(), fd, entry.offset + fd_offset, entry.compressed_length, + true /*readOnly*/)) { + LOG(ERROR) << "Failed to mmap file '" << path << "' in APK '" << friendly_name_ << "'"; + return {}; + } + + std::unique_ptr<Asset> asset = + Asset::createFromCompressedMap(std::move(map), entry.uncompressed_length, mode); + if (asset == nullptr) { + LOG(ERROR) << "Failed to decompress '" << path << "' in APK '" << friendly_name_ << "'"; + return {}; + } + return asset; + } else { + std::unique_ptr<FileMap> map = util::make_unique<FileMap>(); + if (!map->create(GetPath(), fd, entry.offset + fd_offset, entry.uncompressed_length, + true /*readOnly*/)) { + LOG(ERROR) << "Failed to mmap file '" << path << "' in APK '" << friendly_name_ << "'"; + return {}; + } + + unique_fd ufd; + if (!GetPath()) { + // If the `path` is not set, create a new `fd` for the new Asset to own in order to create + // new file descriptors using Asset::openFileDescriptor. If the path is set, it will be used + // to create new file descriptors. + ufd = unique_fd(dup(fd)); + if (!ufd.ok()) { + LOG(ERROR) << "Unable to dup fd '" << path << "' in APK '" << friendly_name_ << "'"; + return {}; + } + } + + std::unique_ptr<Asset> asset = Asset::createFromUncompressedMap(std::move(map), + std::move(ufd), mode); + if (asset == nullptr) { + LOG(ERROR) << "Failed to mmap file '" << path << "' in APK '" << friendly_name_ << "'"; + return {}; + } + return asset; + } + } + + private: + DISALLOW_COPY_AND_ASSIGN(ZipAssetsProvider); + + explicit ZipAssetsProvider(std::string path, + std::string friendly_name, + ZipArchiveHandle unmanaged_handle) + : zip_handle_(unmanaged_handle, ::CloseArchive), + path_(std::move(path)), + friendly_name_(std::move(friendly_name)) { } + + const char* GetPath() const { + return path_.empty() ? nullptr : path_.c_str(); + } + + using ZipArchivePtr = std::unique_ptr<ZipArchive, void (*)(ZipArchiveHandle)>; + ZipArchivePtr zip_handle_; + std::string path_; + std::string friendly_name_; +}; + +class DirectoryAssetsProvider : AssetsProvider { + public: + ~DirectoryAssetsProvider() override = default; + + static std::unique_ptr<const AssetsProvider> Create(const 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; + } + + return std::unique_ptr<AssetsProvider>(new DirectoryAssetsProvider(path)); + } + + protected: + std::unique_ptr<Asset> OpenInternal( + const std::string& path, Asset::AccessMode /* mode */, bool* file_exists) const override { + const std::string resolved_path = ResolvePath(path); + if (file_exists) { + struct stat sb{}; + const int result = stat(resolved_path.c_str(), &sb); + *file_exists = result != -1 && S_ISREG(sb.st_mode); + } + + return ApkAssets::CreateAssetFromFile(resolved_path); + } + + private: + DISALLOW_COPY_AND_ASSIGN(DirectoryAssetsProvider); + + explicit DirectoryAssetsProvider(std::string path) : path_(std::move(path)) { } + + inline std::string ResolvePath(const std::string& path) const { + return base::StringPrintf("%s%c%s", path_.c_str(), OS_PATH_SEPARATOR, path.c_str()); } - return LoadImpl(unmanaged_handle, path, nullptr /*idmap_asset*/, nullptr /*loaded_idmap*/, - flags); + const std::string path_; +}; + +// AssetProvider implementation that does not provide any assets. Used for ApkAssets::LoadEmpty. +class EmptyAssetsProvider : public AssetsProvider { + public: + EmptyAssetsProvider() = default; + ~EmptyAssetsProvider() override = default; + + protected: + std::unique_ptr<Asset> OpenInternal(const std::string& /*path */, + Asset::AccessMode /* mode */, + bool* file_exists) const override { + if (file_exists) { + *file_exists = false; + } + return nullptr; + } + + private: + DISALLOW_COPY_AND_ASSIGN(EmptyAssetsProvider); +}; + +std::unique_ptr<const ApkAssets> ApkAssets::Load(const std::string& path, + const package_property_t flags) { + auto assets = ZipAssetsProvider::Create(path); + return (assets) ? LoadImpl(std::move(assets), path, nullptr /*idmap_asset*/, + nullptr /*loaded_idmap*/, flags) + : nullptr; } std::unique_ptr<const ApkAssets> ApkAssets::LoadFromFd(unique_fd fd, @@ -72,33 +308,17 @@ std::unique_ptr<const ApkAssets> ApkAssets::LoadFromFd(unique_fd fd, 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; - - ::ZipArchiveHandle unmanaged_handle; - const int32_t result = (length == kUnknownLength) - ? ::OpenArchiveFd(fd.release(), friendly_name.c_str(), &unmanaged_handle) - : ::OpenArchiveFdRange(fd.release(), friendly_name.c_str(), &unmanaged_handle, length, - offset); - - if (result != 0) { - LOG(ERROR) << "Failed to open APK '" << friendly_name << "' through FD with offset " << offset - << " and length " << length << ": " << ::ErrorCodeString(result); - ::CloseArchive(unmanaged_handle); - return {}; - } - - return LoadImpl(unmanaged_handle, friendly_name, nullptr /*idmap_asset*/, - nullptr /*loaded_idmap*/, flags); + auto assets = ZipAssetsProvider::Create(std::move(fd), friendly_name, offset, length); + return (assets) ? LoadImpl(std::move(assets), friendly_name, nullptr /*idmap_asset*/, + nullptr /*loaded_idmap*/, flags) + : nullptr; } std::unique_ptr<const ApkAssets> ApkAssets::LoadTable(const std::string& path, const package_property_t flags) { auto resources_asset = CreateAssetFromFile(path); - if (!resources_asset) { - LOG(ERROR) << "Failed to open ARSC '" << path; - return {}; - } - - return LoadTableImpl(std::move(resources_asset), path, flags); + return (resources_asset) ? LoadTableImpl(std::move(resources_asset), path, flags) + : nullptr; } std::unique_ptr<const ApkAssets> ApkAssets::LoadTableFromFd(unique_fd fd, @@ -107,13 +327,8 @@ std::unique_ptr<const ApkAssets> ApkAssets::LoadTableFromFd(unique_fd fd, const off64_t offset, const off64_t length) { auto resources_asset = CreateAssetFromFd(std::move(fd), nullptr /* path */, offset, length); - if (!resources_asset) { - LOG(ERROR) << "Failed to open ARSC '" << friendly_name << "' through FD with offset " << offset - << " and length " << length; - return {}; - } - - return LoadTableImpl(std::move(resources_asset), friendly_name, flags); + return (resources_asset) ? LoadTableImpl(std::move(resources_asset), friendly_name, flags) + : nullptr; } std::unique_ptr<const ApkAssets> ApkAssets::LoadOverlay(const std::string& idmap_path, @@ -133,24 +348,26 @@ std::unique_ptr<const ApkAssets> ApkAssets::LoadOverlay(const std::string& idmap LOG(ERROR) << "failed to load IDMAP " << idmap_path; return {}; } - - - ::ZipArchiveHandle unmanaged_handle; + auto overlay_path = loaded_idmap->OverlayApkPath(); - const int32_t result = ::OpenArchive(overlay_path.c_str(), &unmanaged_handle); - if (result != 0) { - LOG(ERROR) << "Failed to open overlay APK '" << overlay_path << "' " - << ::ErrorCodeString(result); - ::CloseArchive(unmanaged_handle); - return {}; - } + auto assets = ZipAssetsProvider::Create(overlay_path); + return (assets) ? LoadImpl(std::move(assets), overlay_path, std::move(idmap_asset), + std::move(loaded_idmap), flags | PROPERTY_OVERLAY) + : nullptr; +} - return LoadImpl(unmanaged_handle, overlay_path, std::move(idmap_asset), std::move(loaded_idmap), - flags | PROPERTY_OVERLAY); +std::unique_ptr<const ApkAssets> ApkAssets::LoadFromDir(const std::string& path, + const package_property_t flags) { + auto assets = DirectoryAssetsProvider::Create(path); + return (assets) ? LoadImpl(std::move(assets), path, nullptr /*idmap_asset*/, + nullptr /*loaded_idmap*/, flags) + : nullptr; } std::unique_ptr<const ApkAssets> ApkAssets::LoadEmpty(const package_property_t flags) { - std::unique_ptr<ApkAssets> loaded_apk(new ApkAssets(nullptr, "empty", -1, flags)); + std::unique_ptr<ApkAssets> loaded_apk(new ApkAssets( + std::unique_ptr<AssetsProvider>(new EmptyAssetsProvider()), "empty" /* path */, + -1 /* last_mod-time */, flags)); loaded_apk->loaded_arsc_ = LoadedArsc::CreateEmpty(); // Need to force a move for mingw32. return std::move(loaded_apk); @@ -196,7 +413,7 @@ std::unique_ptr<Asset> ApkAssets::CreateAssetFromFd(base::unique_fd fd, Asset::AccessMode::ACCESS_RANDOM); } -std::unique_ptr<const ApkAssets> ApkAssets::LoadImpl(ZipArchiveHandle unmanaged_handle, +std::unique_ptr<const ApkAssets> ApkAssets::LoadImpl(std::unique_ptr<const AssetsProvider> assets, const std::string& path, std::unique_ptr<Asset> idmap_asset, std::unique_ptr<const LoadedIdmap> idmap, @@ -205,24 +422,19 @@ std::unique_ptr<const ApkAssets> ApkAssets::LoadImpl(ZipArchiveHandle unmanaged_ // 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)); + loaded_apk(new ApkAssets(std::move(assets), path, last_mod_time, property_flags)); - // Find the resource table. - ::ZipEntry entry; - int32_t result = ::FindEntry(loaded_apk->zip_handle_.get(), kResourcesArsc, &entry); - if (result != 0) { - // There is no resources.arsc, so create an empty LoadedArsc and return. + // Open the resource table via mmap unless it is compressed. This logic is taken care of by Open. + bool resources_asset_exists = false; + loaded_apk->resources_asset_ = loaded_apk->assets_provider_->Open( + kResourcesArsc, Asset::AccessMode::ACCESS_BUFFER, &resources_asset_exists); + + if (!resources_asset_exists) { 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) { + if (!loaded_apk->resources_asset_) { LOG(ERROR) << "Failed to open '" << kResourcesArsc << "' in APK '" << path << "'."; return {}; } @@ -236,7 +448,7 @@ std::unique_ptr<const ApkAssets> ApkAssets::LoadImpl(ZipArchiveHandle unmanaged_ 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) { + if (!loaded_apk->loaded_arsc_) { LOG(ERROR) << "Failed to load '" << kResourcesArsc << "' in APK '" << path << "'."; return {}; } @@ -251,7 +463,8 @@ std::unique_ptr<const ApkAssets> ApkAssets::LoadTableImpl(std::unique_ptr<Asset> const time_t last_mod_time = getFileModDate(path.c_str()); std::unique_ptr<ApkAssets> loaded_apk( - new ApkAssets(nullptr, path, last_mod_time, property_flags)); + new ApkAssets(std::unique_ptr<AssetsProvider>(new EmptyAssetsProvider()), path, last_mod_time, + property_flags)); loaded_apk->resources_asset_ = std::move(resources_asset); const StringPiece data( @@ -267,111 +480,9 @@ std::unique_ptr<const ApkAssets> ApkAssets::LoadTableImpl(std::unique_ptr<Asset> 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 {}; - } - - const int fd = ::GetFileDescriptor(zip_handle_.get()); - const off64_t fd_offset = ::GetFileDescriptorOffset(zip_handle_.get()); - if (entry.method == kCompressDeflated) { - std::unique_ptr<FileMap> map = util::make_unique<FileMap>(); - if (!map->create(path_.c_str(), fd, fd_offset + 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(), fd, fd_offset + entry.offset, entry.uncompressed_length, - true /*readOnly*/)) { - LOG(ERROR) << "Failed to mmap file '" << path << "' in APK '" << path_ << "'"; - return {}; - } - - // TODO: apks created from file descriptors residing in RAM currently cannot open file - // descriptors to the assets they contain. This is because the Asset::openFileDeescriptor uses - // the zip path on disk to create a new file descriptor. This is fixed in a future change - // in the change topic. - std::unique_ptr<Asset> asset = Asset::createFromUncompressedMap(std::move(map), - unique_fd(-1) /* fd*/, 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. + // Loaders are invalidated by the app, not the system, so assume they are up to date. return true; } |