1 /*
2 * Copyright (C) 2021 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/AssetsProvider.h"
18
19 #include <sys/stat.h>
20
21 #include <android-base/errors.h>
22 #include <android-base/stringprintf.h>
23 #include <android-base/utf8.h>
24 #include <ziparchive/zip_archive.h>
25
26 namespace android {
27 namespace {
28 constexpr const char* kEmptyDebugString = "<empty>";
29 } // namespace
30
Open(const std::string & path,Asset::AccessMode mode,bool * file_exists) const31 std::unique_ptr<Asset> AssetsProvider::Open(const std::string& path, Asset::AccessMode mode,
32 bool* file_exists) const {
33 return OpenInternal(path, mode, file_exists);
34 }
35
CreateAssetFromFile(const std::string & path)36 std::unique_ptr<Asset> AssetsProvider::CreateAssetFromFile(const std::string& path) {
37 base::unique_fd fd(base::utf8::open(path.c_str(), O_RDONLY | O_CLOEXEC));
38 if (!fd.ok()) {
39 LOG(ERROR) << "Failed to open file '" << path << "': " << base::SystemErrorCodeToString(errno);
40 return {};
41 }
42
43 return CreateAssetFromFd(std::move(fd), path.c_str());
44 }
45
CreateAssetFromFd(base::unique_fd fd,const char * path,off64_t offset,off64_t length)46 std::unique_ptr<Asset> AssetsProvider::CreateAssetFromFd(base::unique_fd fd,
47 const char* path,
48 off64_t offset,
49 off64_t length) {
50 CHECK(length >= kUnknownLength) << "length must be greater than or equal to " << kUnknownLength;
51 CHECK(length != kUnknownLength || offset == 0) << "offset must be 0 if length is "
52 << kUnknownLength;
53 if (length == kUnknownLength) {
54 length = lseek64(fd, 0, SEEK_END);
55 if (length < 0) {
56 LOG(ERROR) << "Failed to get size of file '" << ((path) ? path : "anon") << "': "
57 << base::SystemErrorCodeToString(errno);
58 return {};
59 }
60 }
61
62 incfs::IncFsFileMap file_map;
63 if (!file_map.Create(fd, offset, static_cast<size_t>(length), path)) {
64 LOG(ERROR) << "Failed to mmap file '" << ((path != nullptr) ? path : "anon") << "': "
65 << base::SystemErrorCodeToString(errno);
66 return {};
67 }
68
69 // If `path` is set, do not pass ownership of the `fd` to the new Asset since
70 // Asset::openFileDescriptor can use `path` to create new file descriptors.
71 return Asset::createFromUncompressedMap(std::move(file_map),
72 Asset::AccessMode::ACCESS_RANDOM,
73 (path != nullptr) ? base::unique_fd(-1) : std::move(fd));
74 }
75
PathOrDebugName(std::string && value,bool is_path)76 ZipAssetsProvider::PathOrDebugName::PathOrDebugName(std::string&& value, bool is_path)
77 : value_(std::forward<std::string>(value)), is_path_(is_path) {}
78
GetPath() const79 const std::string* ZipAssetsProvider::PathOrDebugName::GetPath() const {
80 return is_path_ ? &value_ : nullptr;
81 }
82
GetDebugName() const83 const std::string& ZipAssetsProvider::PathOrDebugName::GetDebugName() const {
84 return value_;
85 }
86
ZipAssetsProvider(ZipArchiveHandle handle,PathOrDebugName && path,package_property_t flags,time_t last_mod_time)87 ZipAssetsProvider::ZipAssetsProvider(ZipArchiveHandle handle, PathOrDebugName&& path,
88 package_property_t flags, time_t last_mod_time)
89 : zip_handle_(handle, ::CloseArchive),
90 name_(std::forward<PathOrDebugName>(path)),
91 flags_(flags),
92 last_mod_time_(last_mod_time) {}
93
Create(std::string path,package_property_t flags)94 std::unique_ptr<ZipAssetsProvider> ZipAssetsProvider::Create(std::string path,
95 package_property_t flags) {
96 ZipArchiveHandle handle;
97 if (int32_t result = OpenArchive(path.c_str(), &handle); result != 0) {
98 LOG(ERROR) << "Failed to open APK '" << path << "': " << ::ErrorCodeString(result);
99 CloseArchive(handle);
100 return {};
101 }
102
103 struct stat sb{.st_mtime = -1};
104 if (stat(path.c_str(), &sb) < 0) {
105 // Stat requires execute permissions on all directories path to the file. If the process does
106 // not have execute permissions on this file, allow the zip to be opened but IsUpToDate() will
107 // always have to return true.
108 LOG(WARNING) << "Failed to stat file '" << path << "': "
109 << base::SystemErrorCodeToString(errno);
110 }
111
112 return std::unique_ptr<ZipAssetsProvider>(
113 new ZipAssetsProvider(handle, PathOrDebugName{std::move(path),
114 true /* is_path */}, flags, sb.st_mtime));
115 }
116
Create(base::unique_fd fd,std::string friendly_name,package_property_t flags,off64_t offset,off64_t len)117 std::unique_ptr<ZipAssetsProvider> ZipAssetsProvider::Create(base::unique_fd fd,
118 std::string friendly_name,
119 package_property_t flags,
120 off64_t offset,
121 off64_t len) {
122 ZipArchiveHandle handle;
123 const int released_fd = fd.release();
124 const int32_t result = (len == AssetsProvider::kUnknownLength)
125 ? ::OpenArchiveFd(released_fd, friendly_name.c_str(), &handle)
126 : ::OpenArchiveFdRange(released_fd, friendly_name.c_str(), &handle, len, offset);
127
128 if (result != 0) {
129 LOG(ERROR) << "Failed to open APK '" << friendly_name << "' through FD with offset " << offset
130 << " and length " << len << ": " << ::ErrorCodeString(result);
131 CloseArchive(handle);
132 return {};
133 }
134
135 struct stat sb{.st_mtime = -1};
136 if (fstat(released_fd, &sb) < 0) {
137 // Stat requires execute permissions on all directories path to the file. If the process does
138 // not have execute permissions on this file, allow the zip to be opened but IsUpToDate() will
139 // always have to return true.
140 LOG(WARNING) << "Failed to fstat file '" << friendly_name << "': "
141 << base::SystemErrorCodeToString(errno);
142 }
143
144 return std::unique_ptr<ZipAssetsProvider>(
145 new ZipAssetsProvider(handle, PathOrDebugName{std::move(friendly_name),
146 false /* is_path */}, flags, sb.st_mtime));
147 }
148
OpenInternal(const std::string & path,Asset::AccessMode mode,bool * file_exists) const149 std::unique_ptr<Asset> ZipAssetsProvider::OpenInternal(const std::string& path,
150 Asset::AccessMode mode,
151 bool* file_exists) const {
152 if (file_exists != nullptr) {
153 *file_exists = false;
154 }
155
156 ZipEntry entry;
157 if (FindEntry(zip_handle_.get(), path, &entry) != 0) {
158 return {};
159 }
160
161 if (file_exists != nullptr) {
162 *file_exists = true;
163 }
164
165 const int fd = GetFileDescriptor(zip_handle_.get());
166 const off64_t fd_offset = GetFileDescriptorOffset(zip_handle_.get());
167 const bool incremental_hardening = (flags_ & PROPERTY_DISABLE_INCREMENTAL_HARDENING) == 0U;
168 incfs::IncFsFileMap asset_map;
169 if (entry.method == kCompressDeflated) {
170 if (!asset_map.Create(fd, entry.offset + fd_offset, entry.compressed_length,
171 name_.GetDebugName().c_str(), incremental_hardening)) {
172 LOG(ERROR) << "Failed to mmap file '" << path << "' in APK '" << name_.GetDebugName()
173 << "'";
174 return {};
175 }
176
177 std::unique_ptr<Asset> asset =
178 Asset::createFromCompressedMap(std::move(asset_map), entry.uncompressed_length, mode);
179 if (asset == nullptr) {
180 LOG(ERROR) << "Failed to decompress '" << path << "' in APK '" << name_.GetDebugName()
181 << "'";
182 return {};
183 }
184 return asset;
185 }
186
187 if (!asset_map.Create(fd, entry.offset + fd_offset, entry.uncompressed_length,
188 name_.GetDebugName().c_str(), incremental_hardening)) {
189 LOG(ERROR) << "Failed to mmap file '" << path << "' in APK '" << name_.GetDebugName() << "'";
190 return {};
191 }
192
193 base::unique_fd ufd;
194 if (name_.GetPath() == nullptr) {
195 // If the zip name does not represent a path, create a new `fd` for the new Asset to own in
196 // order to create new file descriptors using Asset::openFileDescriptor. If the zip name is a
197 // path, it will be used to create new file descriptors.
198 ufd = base::unique_fd(dup(fd));
199 if (!ufd.ok()) {
200 LOG(ERROR) << "Unable to dup fd '" << path << "' in APK '" << name_.GetDebugName() << "'";
201 return {};
202 }
203 }
204
205 auto asset = Asset::createFromUncompressedMap(std::move(asset_map), mode, std::move(ufd));
206 if (asset == nullptr) {
207 LOG(ERROR) << "Failed to mmap file '" << path << "' in APK '" << name_.GetDebugName() << "'";
208 return {};
209 }
210 return asset;
211 }
212
ForEachFile(const std::string & root_path,const std::function<void (const StringPiece &,FileType)> & f) const213 bool ZipAssetsProvider::ForEachFile(const std::string& root_path,
214 const std::function<void(const StringPiece&, FileType)>& f)
215 const {
216 std::string root_path_full = root_path;
217 if (root_path_full.back() != '/') {
218 root_path_full += '/';
219 }
220
221 void* cookie;
222 if (StartIteration(zip_handle_.get(), &cookie, root_path_full, "") != 0) {
223 return false;
224 }
225
226 std::string name;
227 ::ZipEntry entry{};
228
229 // We need to hold back directories because many paths will contain them and we want to only
230 // surface one.
231 std::set<std::string> dirs{};
232
233 int32_t result;
234 while ((result = Next(cookie, &entry, &name)) == 0) {
235 StringPiece full_file_path(name);
236 StringPiece leaf_file_path = full_file_path.substr(root_path_full.size());
237
238 if (!leaf_file_path.empty()) {
239 auto iter = std::find(leaf_file_path.begin(), leaf_file_path.end(), '/');
240 if (iter != leaf_file_path.end()) {
241 std::string dir =
242 leaf_file_path.substr(0, std::distance(leaf_file_path.begin(), iter)).to_string();
243 dirs.insert(std::move(dir));
244 } else {
245 f(leaf_file_path, kFileTypeRegular);
246 }
247 }
248 }
249 EndIteration(cookie);
250
251 // Now present the unique directories.
252 for (const std::string& dir : dirs) {
253 f(dir, kFileTypeDirectory);
254 }
255
256 // -1 is end of iteration, anything else is an error.
257 return result == -1;
258 }
259
GetCrc(std::string_view path) const260 std::optional<uint32_t> ZipAssetsProvider::GetCrc(std::string_view path) const {
261 ::ZipEntry entry;
262 if (FindEntry(zip_handle_.get(), path, &entry) != 0) {
263 return {};
264 }
265 return entry.crc32;
266 }
267
GetPath() const268 std::optional<std::string_view> ZipAssetsProvider::GetPath() const {
269 if (name_.GetPath() != nullptr) {
270 return *name_.GetPath();
271 }
272 return {};
273 }
274
GetDebugName() const275 const std::string& ZipAssetsProvider::GetDebugName() const {
276 return name_.GetDebugName();
277 }
278
IsUpToDate() const279 bool ZipAssetsProvider::IsUpToDate() const {
280 struct stat sb{};
281 if (fstat(GetFileDescriptor(zip_handle_.get()), &sb) < 0) {
282 // If fstat fails on the zip archive, return true so the zip archive the resource system does
283 // attempt to refresh the ApkAsset.
284 return true;
285 }
286 return last_mod_time_ == sb.st_mtime;
287 }
288
DirectoryAssetsProvider(std::string && path,time_t last_mod_time)289 DirectoryAssetsProvider::DirectoryAssetsProvider(std::string&& path, time_t last_mod_time)
290 : dir_(std::forward<std::string>(path)), last_mod_time_(last_mod_time) {}
291
Create(std::string path)292 std::unique_ptr<DirectoryAssetsProvider> DirectoryAssetsProvider::Create(std::string path) {
293 struct stat sb{};
294 const int result = stat(path.c_str(), &sb);
295 if (result == -1) {
296 LOG(ERROR) << "Failed to find directory '" << path << "'.";
297 return nullptr;
298 }
299
300 if (!S_ISDIR(sb.st_mode)) {
301 LOG(ERROR) << "Path '" << path << "' is not a directory.";
302 return nullptr;
303 }
304
305 if (path[path.size() - 1] != OS_PATH_SEPARATOR) {
306 path += OS_PATH_SEPARATOR;
307 }
308
309 return std::unique_ptr<DirectoryAssetsProvider>(new DirectoryAssetsProvider(std::move(path),
310 sb.st_mtime));
311 }
312
OpenInternal(const std::string & path,Asset::AccessMode,bool * file_exists) const313 std::unique_ptr<Asset> DirectoryAssetsProvider::OpenInternal(const std::string& path,
314 Asset::AccessMode /* mode */,
315 bool* file_exists) const {
316 const std::string resolved_path = dir_ + path;
317 if (file_exists != nullptr) {
318 struct stat sb{};
319 *file_exists = (stat(resolved_path.c_str(), &sb) != -1) && S_ISREG(sb.st_mode);
320 }
321
322 return CreateAssetFromFile(resolved_path);
323 }
324
ForEachFile(const std::string &,const std::function<void (const StringPiece &,FileType)> &) const325 bool DirectoryAssetsProvider::ForEachFile(
326 const std::string& /* root_path */,
327 const std::function<void(const StringPiece&, FileType)>& /* f */)
328 const {
329 return true;
330 }
331
GetPath() const332 std::optional<std::string_view> DirectoryAssetsProvider::GetPath() const {
333 return dir_;
334 }
335
GetDebugName() const336 const std::string& DirectoryAssetsProvider::GetDebugName() const {
337 return dir_;
338 }
339
IsUpToDate() const340 bool DirectoryAssetsProvider::IsUpToDate() const {
341 struct stat sb{};
342 if (stat(dir_.c_str(), &sb) < 0) {
343 // If stat fails on the zip archive, return true so the zip archive the resource system does
344 // attempt to refresh the ApkAsset.
345 return true;
346 }
347 return last_mod_time_ == sb.st_mtime;
348 }
349
MultiAssetsProvider(std::unique_ptr<AssetsProvider> && primary,std::unique_ptr<AssetsProvider> && secondary)350 MultiAssetsProvider::MultiAssetsProvider(std::unique_ptr<AssetsProvider>&& primary,
351 std::unique_ptr<AssetsProvider>&& secondary)
352 : primary_(std::forward<std::unique_ptr<AssetsProvider>>(primary)),
353 secondary_(std::forward<std::unique_ptr<AssetsProvider>>(secondary)) {
354 debug_name_ = primary_->GetDebugName() + " and " + secondary_->GetDebugName();
355 path_ = (primary_->GetDebugName() != kEmptyDebugString) ? primary_->GetPath()
356 : secondary_->GetPath();
357 }
358
Create(std::unique_ptr<AssetsProvider> && primary,std::unique_ptr<AssetsProvider> && secondary)359 std::unique_ptr<AssetsProvider> MultiAssetsProvider::Create(
360 std::unique_ptr<AssetsProvider>&& primary, std::unique_ptr<AssetsProvider>&& secondary) {
361 if (primary == nullptr || secondary == nullptr) {
362 return nullptr;
363 }
364 return std::unique_ptr<MultiAssetsProvider>(new MultiAssetsProvider(std::move(primary),
365 std::move(secondary)));
366 }
367
OpenInternal(const std::string & path,Asset::AccessMode mode,bool * file_exists) const368 std::unique_ptr<Asset> MultiAssetsProvider::OpenInternal(const std::string& path,
369 Asset::AccessMode mode,
370 bool* file_exists) const {
371 auto asset = primary_->Open(path, mode, file_exists);
372 return (asset) ? std::move(asset) : secondary_->Open(path, mode, file_exists);
373 }
374
ForEachFile(const std::string & root_path,const std::function<void (const StringPiece &,FileType)> & f) const375 bool MultiAssetsProvider::ForEachFile(const std::string& root_path,
376 const std::function<void(const StringPiece&, FileType)>& f)
377 const {
378 return primary_->ForEachFile(root_path, f) && secondary_->ForEachFile(root_path, f);
379 }
380
GetPath() const381 std::optional<std::string_view> MultiAssetsProvider::GetPath() const {
382 return path_;
383 }
384
GetDebugName() const385 const std::string& MultiAssetsProvider::GetDebugName() const {
386 return debug_name_;
387 }
388
IsUpToDate() const389 bool MultiAssetsProvider::IsUpToDate() const {
390 return primary_->IsUpToDate() && secondary_->IsUpToDate();
391 }
392
EmptyAssetsProvider(std::optional<std::string> && path)393 EmptyAssetsProvider::EmptyAssetsProvider(std::optional<std::string>&& path) :
394 path_(std::move(path)) {}
395
Create()396 std::unique_ptr<AssetsProvider> EmptyAssetsProvider::Create() {
397 return std::unique_ptr<EmptyAssetsProvider>(new EmptyAssetsProvider({}));
398 }
399
Create(const std::string & path)400 std::unique_ptr<AssetsProvider> EmptyAssetsProvider::Create(const std::string& path) {
401 return std::unique_ptr<EmptyAssetsProvider>(new EmptyAssetsProvider(path));
402 }
403
OpenInternal(const std::string &,Asset::AccessMode,bool * file_exists) const404 std::unique_ptr<Asset> EmptyAssetsProvider::OpenInternal(const std::string& /* path */,
405 Asset::AccessMode /* mode */,
406 bool* file_exists) const {
407 if (file_exists) {
408 *file_exists = false;
409 }
410 return nullptr;
411 }
412
ForEachFile(const std::string &,const std::function<void (const StringPiece &,FileType)> &) const413 bool EmptyAssetsProvider::ForEachFile(
414 const std::string& /* root_path */,
415 const std::function<void(const StringPiece&, FileType)>& /* f */) const {
416 return true;
417 }
418
GetPath() const419 std::optional<std::string_view> EmptyAssetsProvider::GetPath() const {
420 if (path_.has_value()) {
421 return *path_;
422 }
423 return {};
424 }
425
GetDebugName() const426 const std::string& EmptyAssetsProvider::GetDebugName() const {
427 if (path_.has_value()) {
428 return *path_;
429 }
430 const static std::string kEmpty = kEmptyDebugString;
431 return kEmpty;
432 }
433
IsUpToDate() const434 bool EmptyAssetsProvider::IsUpToDate() const {
435 return true;
436 }
437
438 } // namespace android