1 /*
2  * Copyright (C) 2016 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 #include "androidfw/ApkAssets.h"
18 
19 #include <algorithm>
20 
21 #include "android-base/errors.h"
22 #include "android-base/file.h"
23 #include "android-base/logging.h"
24 #include "android-base/unique_fd.h"
25 #include "android-base/utf8.h"
26 #include "utils/Compat.h"
27 #include "utils/FileMap.h"
28 #include "ziparchive/zip_archive.h"
29 
30 #include "androidfw/Asset.h"
31 #include "androidfw/Idmap.h"
32 #include "androidfw/misc.h"
33 #include "androidfw/ResourceTypes.h"
34 #include "androidfw/Util.h"
35 
36 namespace android {
37 
38 using base::SystemErrorCodeToString;
39 using base::unique_fd;
40 
41 static const std::string kResourcesArsc("resources.arsc");
42 
ApkAssets(ZipArchiveHandle unmanaged_handle,const std::string & path,time_t last_mod_time)43 ApkAssets::ApkAssets(ZipArchiveHandle unmanaged_handle,
44                      const std::string& path,
45                      time_t last_mod_time)
46     : zip_handle_(unmanaged_handle, ::CloseArchive), path_(path), last_mod_time_(last_mod_time) {
47 }
48 
Load(const std::string & path,bool system)49 std::unique_ptr<const ApkAssets> ApkAssets::Load(const std::string& path, bool system) {
50   return LoadImpl({} /*fd*/, path, nullptr, nullptr, system, false /*load_as_shared_library*/);
51 }
52 
LoadAsSharedLibrary(const std::string & path,bool system)53 std::unique_ptr<const ApkAssets> ApkAssets::LoadAsSharedLibrary(const std::string& path,
54                                                                 bool system) {
55   return LoadImpl({} /*fd*/, path, nullptr, nullptr, system, true /*load_as_shared_library*/);
56 }
57 
LoadOverlay(const std::string & idmap_path,bool system)58 std::unique_ptr<const ApkAssets> ApkAssets::LoadOverlay(const std::string& idmap_path,
59                                                         bool system) {
60   std::unique_ptr<Asset> idmap_asset = CreateAssetFromFile(idmap_path);
61   if (idmap_asset == nullptr) {
62     return {};
63   }
64 
65   const StringPiece idmap_data(
66       reinterpret_cast<const char*>(idmap_asset->getBuffer(true /*wordAligned*/)),
67       static_cast<size_t>(idmap_asset->getLength()));
68   std::unique_ptr<const LoadedIdmap> loaded_idmap = LoadedIdmap::Load(idmap_data);
69   if (loaded_idmap == nullptr) {
70     LOG(ERROR) << "failed to load IDMAP " << idmap_path;
71     return {};
72   }
73   auto apkPath = loaded_idmap->OverlayApkPath();
74   return LoadImpl({} /*fd*/, apkPath,
75                   std::move(idmap_asset),
76                   std::move(loaded_idmap),
77                   system, false /*load_as_shared_library*/);
78 }
79 
LoadFromFd(unique_fd fd,const std::string & friendly_name,bool system,bool force_shared_lib)80 std::unique_ptr<const ApkAssets> ApkAssets::LoadFromFd(unique_fd fd,
81                                                        const std::string& friendly_name,
82                                                        bool system, bool force_shared_lib) {
83   return LoadImpl(std::move(fd), friendly_name, nullptr /*idmap_asset*/, nullptr /*loaded_idmap*/,
84                   system, force_shared_lib);
85 }
86 
CreateAssetFromFile(const std::string & path)87 std::unique_ptr<Asset> ApkAssets::CreateAssetFromFile(const std::string& path) {
88   unique_fd fd(base::utf8::open(path.c_str(), O_RDONLY | O_BINARY | O_CLOEXEC));
89   if (fd == -1) {
90     LOG(ERROR) << "Failed to open file '" << path << "': " << SystemErrorCodeToString(errno);
91     return {};
92   }
93 
94   const off64_t file_len = lseek64(fd, 0, SEEK_END);
95   if (file_len < 0) {
96     LOG(ERROR) << "Failed to get size of file '" << path << "': " << SystemErrorCodeToString(errno);
97     return {};
98   }
99 
100   std::unique_ptr<FileMap> file_map = util::make_unique<FileMap>();
101   if (!file_map->create(path.c_str(), fd, 0, static_cast<size_t>(file_len), true /*readOnly*/)) {
102     LOG(ERROR) << "Failed to mmap file '" << path << "': " << SystemErrorCodeToString(errno);
103     return {};
104   }
105   return Asset::createFromUncompressedMap(std::move(file_map), Asset::AccessMode::ACCESS_RANDOM);
106 }
107 
LoadImpl(unique_fd fd,const std::string & path,std::unique_ptr<Asset> idmap_asset,std::unique_ptr<const LoadedIdmap> loaded_idmap,bool system,bool load_as_shared_library)108 std::unique_ptr<const ApkAssets> ApkAssets::LoadImpl(
109     unique_fd fd, const std::string& path, std::unique_ptr<Asset> idmap_asset,
110     std::unique_ptr<const LoadedIdmap> loaded_idmap, bool system, bool load_as_shared_library) {
111   ::ZipArchiveHandle unmanaged_handle;
112   int32_t result;
113   if (fd >= 0) {
114     result =
115         ::OpenArchiveFd(fd.release(), path.c_str(), &unmanaged_handle, true /*assume_ownership*/);
116   } else {
117     result = ::OpenArchive(path.c_str(), &unmanaged_handle);
118   }
119 
120   if (result != 0) {
121     LOG(ERROR) << "Failed to open APK '" << path << "' " << ::ErrorCodeString(result);
122     return {};
123   }
124 
125   time_t last_mod_time = getFileModDate(path.c_str());
126 
127   // Wrap the handle in a unique_ptr so it gets automatically closed.
128   std::unique_ptr<ApkAssets> loaded_apk(new ApkAssets(unmanaged_handle, path, last_mod_time));
129 
130   // Find the resource table.
131   ::ZipEntry entry;
132   result = ::FindEntry(loaded_apk->zip_handle_.get(), kResourcesArsc, &entry);
133   if (result != 0) {
134     // There is no resources.arsc, so create an empty LoadedArsc and return.
135     loaded_apk->loaded_arsc_ = LoadedArsc::CreateEmpty();
136     return std::move(loaded_apk);
137   }
138 
139   if (entry.method == kCompressDeflated) {
140     LOG(WARNING) << kResourcesArsc << " in APK '" << path << "' is compressed.";
141   }
142 
143   // Open the resource table via mmap unless it is compressed. This logic is taken care of by Open.
144   loaded_apk->resources_asset_ = loaded_apk->Open(kResourcesArsc, Asset::AccessMode::ACCESS_BUFFER);
145   if (loaded_apk->resources_asset_ == nullptr) {
146     LOG(ERROR) << "Failed to open '" << kResourcesArsc << "' in APK '" << path << "'.";
147     return {};
148   }
149 
150   // Must retain ownership of the IDMAP Asset so that all pointers to its mmapped data remain valid.
151   loaded_apk->idmap_asset_ = std::move(idmap_asset);
152 
153   const StringPiece data(
154       reinterpret_cast<const char*>(loaded_apk->resources_asset_->getBuffer(true /*wordAligned*/)),
155       loaded_apk->resources_asset_->getLength());
156   loaded_apk->loaded_arsc_ =
157       LoadedArsc::Load(data, loaded_idmap.get(), system, load_as_shared_library);
158   if (loaded_apk->loaded_arsc_ == nullptr) {
159     LOG(ERROR) << "Failed to load '" << kResourcesArsc << "' in APK '" << path << "'.";
160     return {};
161   }
162 
163   // Need to force a move for mingw32.
164   return std::move(loaded_apk);
165 }
166 
Open(const std::string & path,Asset::AccessMode mode) const167 std::unique_ptr<Asset> ApkAssets::Open(const std::string& path, Asset::AccessMode mode) const {
168   CHECK(zip_handle_ != nullptr);
169 
170   ::ZipEntry entry;
171   int32_t result = ::FindEntry(zip_handle_.get(), path, &entry);
172   if (result != 0) {
173     return {};
174   }
175 
176   if (entry.method == kCompressDeflated) {
177     std::unique_ptr<FileMap> map = util::make_unique<FileMap>();
178     if (!map->create(path_.c_str(), ::GetFileDescriptor(zip_handle_.get()), entry.offset,
179                      entry.compressed_length, true /*readOnly*/)) {
180       LOG(ERROR) << "Failed to mmap file '" << path << "' in APK '" << path_ << "'";
181       return {};
182     }
183 
184     std::unique_ptr<Asset> asset =
185         Asset::createFromCompressedMap(std::move(map), entry.uncompressed_length, mode);
186     if (asset == nullptr) {
187       LOG(ERROR) << "Failed to decompress '" << path << "'.";
188       return {};
189     }
190     return asset;
191   } else {
192     std::unique_ptr<FileMap> map = util::make_unique<FileMap>();
193     if (!map->create(path_.c_str(), ::GetFileDescriptor(zip_handle_.get()), entry.offset,
194                      entry.uncompressed_length, true /*readOnly*/)) {
195       LOG(ERROR) << "Failed to mmap file '" << path << "' in APK '" << path_ << "'";
196       return {};
197     }
198 
199     std::unique_ptr<Asset> asset = Asset::createFromUncompressedMap(std::move(map), mode);
200     if (asset == nullptr) {
201       LOG(ERROR) << "Failed to mmap file '" << path << "' in APK '" << path_ << "'";
202       return {};
203     }
204     return asset;
205   }
206 }
207 
ForEachFile(const std::string & root_path,const std::function<void (const StringPiece &,FileType)> & f) const208 bool ApkAssets::ForEachFile(const std::string& root_path,
209                             const std::function<void(const StringPiece&, FileType)>& f) const {
210   CHECK(zip_handle_ != nullptr);
211 
212   std::string root_path_full = root_path;
213   if (root_path_full.back() != '/') {
214     root_path_full += '/';
215   }
216 
217   void* cookie;
218   if (::StartIteration(zip_handle_.get(), &cookie, root_path_full, "") != 0) {
219     return false;
220   }
221 
222   std::string name;
223   ::ZipEntry entry;
224 
225   // We need to hold back directories because many paths will contain them and we want to only
226   // surface one.
227   std::set<std::string> dirs;
228 
229   int32_t result;
230   while ((result = ::Next(cookie, &entry, &name)) == 0) {
231     StringPiece full_file_path(name);
232     StringPiece leaf_file_path = full_file_path.substr(root_path_full.size());
233 
234     if (!leaf_file_path.empty()) {
235       auto iter = std::find(leaf_file_path.begin(), leaf_file_path.end(), '/');
236       if (iter != leaf_file_path.end()) {
237         std::string dir =
238             leaf_file_path.substr(0, std::distance(leaf_file_path.begin(), iter)).to_string();
239         dirs.insert(std::move(dir));
240       } else {
241         f(leaf_file_path, kFileTypeRegular);
242       }
243     }
244   }
245   ::EndIteration(cookie);
246 
247   // Now present the unique directories.
248   for (const std::string& dir : dirs) {
249     f(dir, kFileTypeDirectory);
250   }
251 
252   // -1 is end of iteration, anything else is an error.
253   return result == -1;
254 }
255 
IsUpToDate() const256 bool ApkAssets::IsUpToDate() const {
257   return last_mod_time_ == getFileModDate(path_.c_str());
258 }
259 
260 }  // namespace android
261