1 /* 2 * Copyright (C) 2006 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 // 18 // Provide access to read-only assets. 19 // 20 21 #define LOG_TAG "asset" 22 #define ATRACE_TAG ATRACE_TAG_RESOURCES 23 //#define LOG_NDEBUG 0 24 25 #include <androidfw/Asset.h> 26 #include <androidfw/AssetDir.h> 27 #include <androidfw/AssetManager.h> 28 #include <androidfw/ResourceTypes.h> 29 #include <utils/Atomic.h> 30 #include <utils/Log.h> 31 #include <utils/String8.h> 32 #include <utils/String8.h> 33 #include <utils/threads.h> 34 #include <utils/Timers.h> 35 #include <utils/ZipFileRO.h> 36 #ifdef HAVE_ANDROID_OS 37 #include <cutils/trace.h> 38 #endif 39 40 #include <assert.h> 41 #include <dirent.h> 42 #include <errno.h> 43 #include <fcntl.h> 44 #include <strings.h> 45 #include <sys/stat.h> 46 #include <unistd.h> 47 48 #ifndef TEMP_FAILURE_RETRY 49 /* Used to retry syscalls that can return EINTR. */ 50 #define TEMP_FAILURE_RETRY(exp) ({ \ 51 typeof (exp) _rc; \ 52 do { \ 53 _rc = (exp); \ 54 } while (_rc == -1 && errno == EINTR); \ 55 _rc; }) 56 #endif 57 58 #ifdef HAVE_ANDROID_OS 59 #define MY_TRACE_BEGIN(x) ATRACE_BEGIN(x) 60 #define MY_TRACE_END() ATRACE_END() 61 #else 62 #define MY_TRACE_BEGIN(x) 63 #define MY_TRACE_END() 64 #endif 65 66 using namespace android; 67 68 /* 69 * Names for default app, locale, and vendor. We might want to change 70 * these to be an actual locale, e.g. always use en-US as the default. 71 */ 72 static const char* kDefaultLocale = "default"; 73 static const char* kDefaultVendor = "default"; 74 static const char* kAssetsRoot = "assets"; 75 static const char* kAppZipName = NULL; //"classes.jar"; 76 static const char* kSystemAssets = "framework/framework-res.apk"; 77 static const char* kIdmapCacheDir = "resource-cache"; 78 79 static const char* kExcludeExtension = ".EXCLUDE"; 80 81 static Asset* const kExcludedAsset = (Asset*) 0xd000000d; 82 83 static volatile int32_t gCount = 0; 84 85 namespace { 86 // Transform string /a/b/c.apk to /data/resource-cache/a@b (at) c.apk@idmap 87 String8 idmapPathForPackagePath(const String8& pkgPath) 88 { 89 const char* root = getenv("ANDROID_DATA"); 90 LOG_ALWAYS_FATAL_IF(root == NULL, "ANDROID_DATA not set"); 91 String8 path(root); 92 path.appendPath(kIdmapCacheDir); 93 94 char buf[256]; // 256 chars should be enough for anyone... 95 strncpy(buf, pkgPath.string(), 255); 96 buf[255] = '\0'; 97 char* filename = buf; 98 while (*filename && *filename == '/') { 99 ++filename; 100 } 101 char* p = filename; 102 while (*p) { 103 if (*p == '/') { 104 *p = '@'; 105 } 106 ++p; 107 } 108 path.appendPath(filename); 109 path.append("@idmap"); 110 111 return path; 112 } 113 114 /* 115 * Like strdup(), but uses C++ "new" operator instead of malloc. 116 */ 117 static char* strdupNew(const char* str) 118 { 119 char* newStr; 120 int len; 121 122 if (str == NULL) 123 return NULL; 124 125 len = strlen(str); 126 newStr = new char[len+1]; 127 memcpy(newStr, str, len+1); 128 129 return newStr; 130 } 131 } 132 133 /* 134 * =========================================================================== 135 * AssetManager 136 * =========================================================================== 137 */ 138 139 int32_t AssetManager::getGlobalCount() 140 { 141 return gCount; 142 } 143 144 AssetManager::AssetManager(CacheMode cacheMode) 145 : mLocale(NULL), mVendor(NULL), 146 mResources(NULL), mConfig(new ResTable_config), 147 mCacheMode(cacheMode), mCacheValid(false) 148 { 149 int count = android_atomic_inc(&gCount)+1; 150 //ALOGI("Creating AssetManager %p #%d\n", this, count); 151 memset(mConfig, 0, sizeof(ResTable_config)); 152 } 153 154 AssetManager::~AssetManager(void) 155 { 156 int count = android_atomic_dec(&gCount); 157 //ALOGI("Destroying AssetManager in %p #%d\n", this, count); 158 159 delete mConfig; 160 delete mResources; 161 162 // don't have a String class yet, so make sure we clean up 163 delete[] mLocale; 164 delete[] mVendor; 165 } 166 167 bool AssetManager::addAssetPath(const String8& path, void** cookie) 168 { 169 AutoMutex _l(mLock); 170 171 asset_path ap; 172 173 String8 realPath(path); 174 if (kAppZipName) { 175 realPath.appendPath(kAppZipName); 176 } 177 ap.type = ::getFileType(realPath.string()); 178 if (ap.type == kFileTypeRegular) { 179 ap.path = realPath; 180 } else { 181 ap.path = path; 182 ap.type = ::getFileType(path.string()); 183 if (ap.type != kFileTypeDirectory && ap.type != kFileTypeRegular) { 184 ALOGW("Asset path %s is neither a directory nor file (type=%d).", 185 path.string(), (int)ap.type); 186 return false; 187 } 188 } 189 190 // Skip if we have it already. 191 for (size_t i=0; i<mAssetPaths.size(); i++) { 192 if (mAssetPaths[i].path == ap.path) { 193 if (cookie) { 194 *cookie = (void*)(i+1); 195 } 196 return true; 197 } 198 } 199 200 ALOGV("In %p Asset %s path: %s", this, 201 ap.type == kFileTypeDirectory ? "dir" : "zip", ap.path.string()); 202 203 mAssetPaths.add(ap); 204 205 // new paths are always added at the end 206 if (cookie) { 207 *cookie = (void*)mAssetPaths.size(); 208 } 209 210 // add overlay packages for /system/framework; apps are handled by the 211 // (Java) package manager 212 if (strncmp(path.string(), "/system/framework/", 18) == 0) { 213 // When there is an environment variable for /vendor, this 214 // should be changed to something similar to how ANDROID_ROOT 215 // and ANDROID_DATA are used in this file. 216 String8 overlayPath("/vendor/overlay/framework/"); 217 overlayPath.append(path.getPathLeaf()); 218 if (TEMP_FAILURE_RETRY(access(overlayPath.string(), R_OK)) == 0) { 219 asset_path oap; 220 oap.path = overlayPath; 221 oap.type = ::getFileType(overlayPath.string()); 222 bool addOverlay = (oap.type == kFileTypeRegular); // only .apks supported as overlay 223 if (addOverlay) { 224 oap.idmap = idmapPathForPackagePath(overlayPath); 225 226 if (isIdmapStaleLocked(ap.path, oap.path, oap.idmap)) { 227 addOverlay = createIdmapFileLocked(ap.path, oap.path, oap.idmap); 228 } 229 } 230 if (addOverlay) { 231 mAssetPaths.add(oap); 232 } else { 233 ALOGW("failed to add overlay package %s\n", overlayPath.string()); 234 } 235 } 236 } 237 238 return true; 239 } 240 241 bool AssetManager::isIdmapStaleLocked(const String8& originalPath, const String8& overlayPath, 242 const String8& idmapPath) 243 { 244 struct stat st; 245 if (TEMP_FAILURE_RETRY(stat(idmapPath.string(), &st)) == -1) { 246 if (errno == ENOENT) { 247 return true; // non-existing idmap is always stale 248 } else { 249 ALOGW("failed to stat file %s: %s\n", idmapPath.string(), strerror(errno)); 250 return false; 251 } 252 } 253 if (st.st_size < ResTable::IDMAP_HEADER_SIZE_BYTES) { 254 ALOGW("file %s has unexpectedly small size=%zd\n", idmapPath.string(), (size_t)st.st_size); 255 return false; 256 } 257 int fd = TEMP_FAILURE_RETRY(::open(idmapPath.string(), O_RDONLY)); 258 if (fd == -1) { 259 ALOGW("failed to open file %s: %s\n", idmapPath.string(), strerror(errno)); 260 return false; 261 } 262 char buf[ResTable::IDMAP_HEADER_SIZE_BYTES]; 263 ssize_t bytesLeft = ResTable::IDMAP_HEADER_SIZE_BYTES; 264 for (;;) { 265 ssize_t r = TEMP_FAILURE_RETRY(read(fd, buf + ResTable::IDMAP_HEADER_SIZE_BYTES - bytesLeft, 266 bytesLeft)); 267 if (r < 0) { 268 TEMP_FAILURE_RETRY(close(fd)); 269 return false; 270 } 271 bytesLeft -= r; 272 if (bytesLeft == 0) { 273 break; 274 } 275 } 276 TEMP_FAILURE_RETRY(close(fd)); 277 278 uint32_t cachedOriginalCrc, cachedOverlayCrc; 279 if (!ResTable::getIdmapInfo(buf, ResTable::IDMAP_HEADER_SIZE_BYTES, 280 &cachedOriginalCrc, &cachedOverlayCrc)) { 281 return false; 282 } 283 284 uint32_t actualOriginalCrc, actualOverlayCrc; 285 if (!getZipEntryCrcLocked(originalPath, "resources.arsc", &actualOriginalCrc)) { 286 return false; 287 } 288 if (!getZipEntryCrcLocked(overlayPath, "resources.arsc", &actualOverlayCrc)) { 289 return false; 290 } 291 return cachedOriginalCrc != actualOriginalCrc || cachedOverlayCrc != actualOverlayCrc; 292 } 293 294 bool AssetManager::getZipEntryCrcLocked(const String8& zipPath, const char* entryFilename, 295 uint32_t* pCrc) 296 { 297 asset_path ap; 298 ap.path = zipPath; 299 const ZipFileRO* zip = getZipFileLocked(ap); 300 if (zip == NULL) { 301 return false; 302 } 303 const ZipEntryRO entry = zip->findEntryByName(entryFilename); 304 if (entry == NULL) { 305 return false; 306 } 307 if (!zip->getEntryInfo(entry, NULL, NULL, NULL, NULL, NULL, (long*)pCrc)) { 308 return false; 309 } 310 return true; 311 } 312 313 bool AssetManager::createIdmapFileLocked(const String8& originalPath, const String8& overlayPath, 314 const String8& idmapPath) 315 { 316 ALOGD("%s: originalPath=%s overlayPath=%s idmapPath=%s\n", 317 __FUNCTION__, originalPath.string(), overlayPath.string(), idmapPath.string()); 318 ResTable tables[2]; 319 const String8* paths[2] = { &originalPath, &overlayPath }; 320 uint32_t originalCrc, overlayCrc; 321 bool retval = false; 322 ssize_t offset = 0; 323 int fd = 0; 324 uint32_t* data = NULL; 325 size_t size; 326 327 for (int i = 0; i < 2; ++i) { 328 asset_path ap; 329 ap.type = kFileTypeRegular; 330 ap.path = *paths[i]; 331 Asset* ass = openNonAssetInPathLocked("resources.arsc", Asset::ACCESS_BUFFER, ap); 332 if (ass == NULL) { 333 ALOGW("failed to find resources.arsc in %s\n", ap.path.string()); 334 goto error; 335 } 336 tables[i].add(ass, (void*)1, false); 337 } 338 339 if (!getZipEntryCrcLocked(originalPath, "resources.arsc", &originalCrc)) { 340 ALOGW("failed to retrieve crc for resources.arsc in %s\n", originalPath.string()); 341 goto error; 342 } 343 if (!getZipEntryCrcLocked(overlayPath, "resources.arsc", &overlayCrc)) { 344 ALOGW("failed to retrieve crc for resources.arsc in %s\n", overlayPath.string()); 345 goto error; 346 } 347 348 if (tables[0].createIdmap(tables[1], originalCrc, overlayCrc, 349 (void**)&data, &size) != NO_ERROR) { 350 ALOGW("failed to generate idmap data for file %s\n", idmapPath.string()); 351 goto error; 352 } 353 354 // This should be abstracted (eg replaced by a stand-alone 355 // application like dexopt, triggered by something equivalent to 356 // installd). 357 fd = TEMP_FAILURE_RETRY(::open(idmapPath.string(), O_WRONLY | O_CREAT | O_TRUNC, 0644)); 358 if (fd == -1) { 359 ALOGW("failed to write idmap file %s (open: %s)\n", idmapPath.string(), strerror(errno)); 360 goto error_free; 361 } 362 for (;;) { 363 ssize_t written = TEMP_FAILURE_RETRY(write(fd, data + offset, size)); 364 if (written < 0) { 365 ALOGW("failed to write idmap file %s (write: %s)\n", idmapPath.string(), 366 strerror(errno)); 367 goto error_close; 368 } 369 size -= (size_t)written; 370 offset += written; 371 if (size == 0) { 372 break; 373 } 374 } 375 376 retval = true; 377 error_close: 378 TEMP_FAILURE_RETRY(close(fd)); 379 error_free: 380 free(data); 381 error: 382 return retval; 383 } 384 385 bool AssetManager::addDefaultAssets() 386 { 387 const char* root = getenv("ANDROID_ROOT"); 388 LOG_ALWAYS_FATAL_IF(root == NULL, "ANDROID_ROOT not set"); 389 390 String8 path(root); 391 path.appendPath(kSystemAssets); 392 393 return addAssetPath(path, NULL); 394 } 395 396 void* AssetManager::nextAssetPath(void* cookie) const 397 { 398 AutoMutex _l(mLock); 399 size_t next = ((size_t)cookie)+1; 400 return next > mAssetPaths.size() ? NULL : (void*)next; 401 } 402 403 String8 AssetManager::getAssetPath(void* cookie) const 404 { 405 AutoMutex _l(mLock); 406 const size_t which = ((size_t)cookie)-1; 407 if (which < mAssetPaths.size()) { 408 return mAssetPaths[which].path; 409 } 410 return String8(); 411 } 412 413 /* 414 * Set the current locale. Use NULL to indicate no locale. 415 * 416 * Close and reopen Zip archives as appropriate, and reset cached 417 * information in the locale-specific sections of the tree. 418 */ 419 void AssetManager::setLocale(const char* locale) 420 { 421 AutoMutex _l(mLock); 422 setLocaleLocked(locale); 423 } 424 425 void AssetManager::setLocaleLocked(const char* locale) 426 { 427 if (mLocale != NULL) { 428 /* previously set, purge cached data */ 429 purgeFileNameCacheLocked(); 430 //mZipSet.purgeLocale(); 431 delete[] mLocale; 432 } 433 mLocale = strdupNew(locale); 434 435 updateResourceParamsLocked(); 436 } 437 438 /* 439 * Set the current vendor. Use NULL to indicate no vendor. 440 * 441 * Close and reopen Zip archives as appropriate, and reset cached 442 * information in the vendor-specific sections of the tree. 443 */ 444 void AssetManager::setVendor(const char* vendor) 445 { 446 AutoMutex _l(mLock); 447 448 if (mVendor != NULL) { 449 /* previously set, purge cached data */ 450 purgeFileNameCacheLocked(); 451 //mZipSet.purgeVendor(); 452 delete[] mVendor; 453 } 454 mVendor = strdupNew(vendor); 455 } 456 457 void AssetManager::setConfiguration(const ResTable_config& config, const char* locale) 458 { 459 AutoMutex _l(mLock); 460 *mConfig = config; 461 if (locale) { 462 setLocaleLocked(locale); 463 } else if (config.language[0] != 0) { 464 char spec[9]; 465 spec[0] = config.language[0]; 466 spec[1] = config.language[1]; 467 if (config.country[0] != 0) { 468 spec[2] = '_'; 469 spec[3] = config.country[0]; 470 spec[4] = config.country[1]; 471 spec[5] = 0; 472 } else { 473 spec[3] = 0; 474 } 475 setLocaleLocked(spec); 476 } else { 477 updateResourceParamsLocked(); 478 } 479 } 480 481 void AssetManager::getConfiguration(ResTable_config* outConfig) const 482 { 483 AutoMutex _l(mLock); 484 *outConfig = *mConfig; 485 } 486 487 /* 488 * Open an asset. 489 * 490 * The data could be; 491 * - In a file on disk (assetBase + fileName). 492 * - In a compressed file on disk (assetBase + fileName.gz). 493 * - In a Zip archive, uncompressed or compressed. 494 * 495 * It can be in a number of different directories and Zip archives. 496 * The search order is: 497 * - [appname] 498 * - locale + vendor 499 * - "default" + vendor 500 * - locale + "default" 501 * - "default + "default" 502 * - "common" 503 * - (same as above) 504 * 505 * To find a particular file, we have to try up to eight paths with 506 * all three forms of data. 507 * 508 * We should probably reject requests for "illegal" filenames, e.g. those 509 * with illegal characters or "../" backward relative paths. 510 */ 511 Asset* AssetManager::open(const char* fileName, AccessMode mode) 512 { 513 AutoMutex _l(mLock); 514 515 LOG_FATAL_IF(mAssetPaths.size() == 0, "No assets added to AssetManager"); 516 517 518 if (mCacheMode != CACHE_OFF && !mCacheValid) 519 loadFileNameCacheLocked(); 520 521 String8 assetName(kAssetsRoot); 522 assetName.appendPath(fileName); 523 524 /* 525 * For each top-level asset path, search for the asset. 526 */ 527 528 size_t i = mAssetPaths.size(); 529 while (i > 0) { 530 i--; 531 ALOGV("Looking for asset '%s' in '%s'\n", 532 assetName.string(), mAssetPaths.itemAt(i).path.string()); 533 Asset* pAsset = openNonAssetInPathLocked(assetName.string(), mode, mAssetPaths.itemAt(i)); 534 if (pAsset != NULL) { 535 return pAsset != kExcludedAsset ? pAsset : NULL; 536 } 537 } 538 539 return NULL; 540 } 541 542 /* 543 * Open a non-asset file as if it were an asset. 544 * 545 * The "fileName" is the partial path starting from the application 546 * name. 547 */ 548 Asset* AssetManager::openNonAsset(const char* fileName, AccessMode mode) 549 { 550 AutoMutex _l(mLock); 551 552 LOG_FATAL_IF(mAssetPaths.size() == 0, "No assets added to AssetManager"); 553 554 555 if (mCacheMode != CACHE_OFF && !mCacheValid) 556 loadFileNameCacheLocked(); 557 558 /* 559 * For each top-level asset path, search for the asset. 560 */ 561 562 size_t i = mAssetPaths.size(); 563 while (i > 0) { 564 i--; 565 ALOGV("Looking for non-asset '%s' in '%s'\n", fileName, mAssetPaths.itemAt(i).path.string()); 566 Asset* pAsset = openNonAssetInPathLocked( 567 fileName, mode, mAssetPaths.itemAt(i)); 568 if (pAsset != NULL) { 569 return pAsset != kExcludedAsset ? pAsset : NULL; 570 } 571 } 572 573 return NULL; 574 } 575 576 Asset* AssetManager::openNonAsset(void* cookie, const char* fileName, AccessMode mode) 577 { 578 const size_t which = ((size_t)cookie)-1; 579 580 AutoMutex _l(mLock); 581 582 LOG_FATAL_IF(mAssetPaths.size() == 0, "No assets added to AssetManager"); 583 584 585 if (mCacheMode != CACHE_OFF && !mCacheValid) 586 loadFileNameCacheLocked(); 587 588 if (which < mAssetPaths.size()) { 589 ALOGV("Looking for non-asset '%s' in '%s'\n", fileName, 590 mAssetPaths.itemAt(which).path.string()); 591 Asset* pAsset = openNonAssetInPathLocked( 592 fileName, mode, mAssetPaths.itemAt(which)); 593 if (pAsset != NULL) { 594 return pAsset != kExcludedAsset ? pAsset : NULL; 595 } 596 } 597 598 return NULL; 599 } 600 601 /* 602 * Get the type of a file in the asset namespace. 603 * 604 * This currently only works for regular files. All others (including 605 * directories) will return kFileTypeNonexistent. 606 */ 607 FileType AssetManager::getFileType(const char* fileName) 608 { 609 Asset* pAsset = NULL; 610 611 /* 612 * Open the asset. This is less efficient than simply finding the 613 * file, but it's not too bad (we don't uncompress or mmap data until 614 * the first read() call). 615 */ 616 pAsset = open(fileName, Asset::ACCESS_STREAMING); 617 delete pAsset; 618 619 if (pAsset == NULL) 620 return kFileTypeNonexistent; 621 else 622 return kFileTypeRegular; 623 } 624 625 const ResTable* AssetManager::getResTable(bool required) const 626 { 627 ResTable* rt = mResources; 628 if (rt) { 629 return rt; 630 } 631 632 // Iterate through all asset packages, collecting resources from each. 633 634 AutoMutex _l(mLock); 635 636 if (mResources != NULL) { 637 return mResources; 638 } 639 640 if (required) { 641 LOG_FATAL_IF(mAssetPaths.size() == 0, "No assets added to AssetManager"); 642 } 643 644 if (mCacheMode != CACHE_OFF && !mCacheValid) 645 const_cast<AssetManager*>(this)->loadFileNameCacheLocked(); 646 647 const size_t N = mAssetPaths.size(); 648 for (size_t i=0; i<N; i++) { 649 Asset* ass = NULL; 650 ResTable* sharedRes = NULL; 651 bool shared = true; 652 const asset_path& ap = mAssetPaths.itemAt(i); 653 MY_TRACE_BEGIN(ap.path.string()); 654 Asset* idmap = openIdmapLocked(ap); 655 ALOGV("Looking for resource asset in '%s'\n", ap.path.string()); 656 if (ap.type != kFileTypeDirectory) { 657 if (i == 0) { 658 // The first item is typically the framework resources, 659 // which we want to avoid parsing every time. 660 sharedRes = const_cast<AssetManager*>(this)-> 661 mZipSet.getZipResourceTable(ap.path); 662 } 663 if (sharedRes == NULL) { 664 ass = const_cast<AssetManager*>(this)-> 665 mZipSet.getZipResourceTableAsset(ap.path); 666 if (ass == NULL) { 667 ALOGV("loading resource table %s\n", ap.path.string()); 668 ass = const_cast<AssetManager*>(this)-> 669 openNonAssetInPathLocked("resources.arsc", 670 Asset::ACCESS_BUFFER, 671 ap); 672 if (ass != NULL && ass != kExcludedAsset) { 673 ass = const_cast<AssetManager*>(this)-> 674 mZipSet.setZipResourceTableAsset(ap.path, ass); 675 } 676 } 677 678 if (i == 0 && ass != NULL) { 679 // If this is the first resource table in the asset 680 // manager, then we are going to cache it so that we 681 // can quickly copy it out for others. 682 ALOGV("Creating shared resources for %s", ap.path.string()); 683 sharedRes = new ResTable(); 684 sharedRes->add(ass, (void*)(i+1), false, idmap); 685 sharedRes = const_cast<AssetManager*>(this)-> 686 mZipSet.setZipResourceTable(ap.path, sharedRes); 687 } 688 } 689 } else { 690 ALOGV("loading resource table %s\n", ap.path.string()); 691 Asset* ass = const_cast<AssetManager*>(this)-> 692 openNonAssetInPathLocked("resources.arsc", 693 Asset::ACCESS_BUFFER, 694 ap); 695 shared = false; 696 } 697 if ((ass != NULL || sharedRes != NULL) && ass != kExcludedAsset) { 698 if (rt == NULL) { 699 mResources = rt = new ResTable(); 700 updateResourceParamsLocked(); 701 } 702 ALOGV("Installing resource asset %p in to table %p\n", ass, mResources); 703 if (sharedRes != NULL) { 704 ALOGV("Copying existing resources for %s", ap.path.string()); 705 rt->add(sharedRes); 706 } else { 707 ALOGV("Parsing resources for %s", ap.path.string()); 708 rt->add(ass, (void*)(i+1), !shared, idmap); 709 } 710 711 if (!shared) { 712 delete ass; 713 } 714 } 715 if (idmap != NULL) { 716 delete idmap; 717 } 718 MY_TRACE_END(); 719 } 720 721 if (required && !rt) ALOGW("Unable to find resources file resources.arsc"); 722 if (!rt) { 723 mResources = rt = new ResTable(); 724 } 725 return rt; 726 } 727 728 void AssetManager::updateResourceParamsLocked() const 729 { 730 ResTable* res = mResources; 731 if (!res) { 732 return; 733 } 734 735 size_t llen = mLocale ? strlen(mLocale) : 0; 736 mConfig->language[0] = 0; 737 mConfig->language[1] = 0; 738 mConfig->country[0] = 0; 739 mConfig->country[1] = 0; 740 if (llen >= 2) { 741 mConfig->language[0] = mLocale[0]; 742 mConfig->language[1] = mLocale[1]; 743 } 744 if (llen >= 5) { 745 mConfig->country[0] = mLocale[3]; 746 mConfig->country[1] = mLocale[4]; 747 } 748 mConfig->size = sizeof(*mConfig); 749 750 res->setParameters(mConfig); 751 } 752 753 Asset* AssetManager::openIdmapLocked(const struct asset_path& ap) const 754 { 755 Asset* ass = NULL; 756 if (ap.idmap.size() != 0) { 757 ass = const_cast<AssetManager*>(this)-> 758 openAssetFromFileLocked(ap.idmap, Asset::ACCESS_BUFFER); 759 if (ass) { 760 ALOGV("loading idmap %s\n", ap.idmap.string()); 761 } else { 762 ALOGW("failed to load idmap %s\n", ap.idmap.string()); 763 } 764 } 765 return ass; 766 } 767 768 const ResTable& AssetManager::getResources(bool required) const 769 { 770 const ResTable* rt = getResTable(required); 771 return *rt; 772 } 773 774 bool AssetManager::isUpToDate() 775 { 776 AutoMutex _l(mLock); 777 return mZipSet.isUpToDate(); 778 } 779 780 void AssetManager::getLocales(Vector<String8>* locales) const 781 { 782 ResTable* res = mResources; 783 if (res != NULL) { 784 res->getLocales(locales); 785 } 786 } 787 788 /* 789 * Open a non-asset file as if it were an asset, searching for it in the 790 * specified app. 791 * 792 * Pass in a NULL values for "appName" if the common app directory should 793 * be used. 794 */ 795 Asset* AssetManager::openNonAssetInPathLocked(const char* fileName, AccessMode mode, 796 const asset_path& ap) 797 { 798 Asset* pAsset = NULL; 799 800 /* look at the filesystem on disk */ 801 if (ap.type == kFileTypeDirectory) { 802 String8 path(ap.path); 803 path.appendPath(fileName); 804 805 pAsset = openAssetFromFileLocked(path, mode); 806 807 if (pAsset == NULL) { 808 /* try again, this time with ".gz" */ 809 path.append(".gz"); 810 pAsset = openAssetFromFileLocked(path, mode); 811 } 812 813 if (pAsset != NULL) { 814 //printf("FOUND NA '%s' on disk\n", fileName); 815 pAsset->setAssetSource(path); 816 } 817 818 /* look inside the zip file */ 819 } else { 820 String8 path(fileName); 821 822 /* check the appropriate Zip file */ 823 ZipFileRO* pZip; 824 ZipEntryRO entry; 825 826 pZip = getZipFileLocked(ap); 827 if (pZip != NULL) { 828 //printf("GOT zip, checking NA '%s'\n", (const char*) path); 829 entry = pZip->findEntryByName(path.string()); 830 if (entry != NULL) { 831 //printf("FOUND NA in Zip file for %s\n", appName ? appName : kAppCommon); 832 pAsset = openAssetFromZipLocked(pZip, entry, mode, path); 833 } 834 } 835 836 if (pAsset != NULL) { 837 /* create a "source" name, for debug/display */ 838 pAsset->setAssetSource( 839 createZipSourceNameLocked(ZipSet::getPathName(ap.path.string()), String8(""), 840 String8(fileName))); 841 } 842 } 843 844 return pAsset; 845 } 846 847 /* 848 * Open an asset, searching for it in the directory hierarchy for the 849 * specified app. 850 * 851 * Pass in a NULL values for "appName" if the common app directory should 852 * be used. 853 */ 854 Asset* AssetManager::openInPathLocked(const char* fileName, AccessMode mode, 855 const asset_path& ap) 856 { 857 Asset* pAsset = NULL; 858 859 /* 860 * Try various combinations of locale and vendor. 861 */ 862 if (mLocale != NULL && mVendor != NULL) 863 pAsset = openInLocaleVendorLocked(fileName, mode, ap, mLocale, mVendor); 864 if (pAsset == NULL && mVendor != NULL) 865 pAsset = openInLocaleVendorLocked(fileName, mode, ap, NULL, mVendor); 866 if (pAsset == NULL && mLocale != NULL) 867 pAsset = openInLocaleVendorLocked(fileName, mode, ap, mLocale, NULL); 868 if (pAsset == NULL) 869 pAsset = openInLocaleVendorLocked(fileName, mode, ap, NULL, NULL); 870 871 return pAsset; 872 } 873 874 /* 875 * Open an asset, searching for it in the directory hierarchy for the 876 * specified locale and vendor. 877 * 878 * We also search in "app.jar". 879 * 880 * Pass in NULL values for "appName", "locale", and "vendor" if the 881 * defaults should be used. 882 */ 883 Asset* AssetManager::openInLocaleVendorLocked(const char* fileName, AccessMode mode, 884 const asset_path& ap, const char* locale, const char* vendor) 885 { 886 Asset* pAsset = NULL; 887 888 if (ap.type == kFileTypeDirectory) { 889 if (mCacheMode == CACHE_OFF) { 890 /* look at the filesystem on disk */ 891 String8 path(createPathNameLocked(ap, locale, vendor)); 892 path.appendPath(fileName); 893 894 String8 excludeName(path); 895 excludeName.append(kExcludeExtension); 896 if (::getFileType(excludeName.string()) != kFileTypeNonexistent) { 897 /* say no more */ 898 //printf("+++ excluding '%s'\n", (const char*) excludeName); 899 return kExcludedAsset; 900 } 901 902 pAsset = openAssetFromFileLocked(path, mode); 903 904 if (pAsset == NULL) { 905 /* try again, this time with ".gz" */ 906 path.append(".gz"); 907 pAsset = openAssetFromFileLocked(path, mode); 908 } 909 910 if (pAsset != NULL) 911 pAsset->setAssetSource(path); 912 } else { 913 /* find in cache */ 914 String8 path(createPathNameLocked(ap, locale, vendor)); 915 path.appendPath(fileName); 916 917 AssetDir::FileInfo tmpInfo; 918 bool found = false; 919 920 String8 excludeName(path); 921 excludeName.append(kExcludeExtension); 922 923 if (mCache.indexOf(excludeName) != NAME_NOT_FOUND) { 924 /* go no farther */ 925 //printf("+++ Excluding '%s'\n", (const char*) excludeName); 926 return kExcludedAsset; 927 } 928 929 /* 930 * File compression extensions (".gz") don't get stored in the 931 * name cache, so we have to try both here. 932 */ 933 if (mCache.indexOf(path) != NAME_NOT_FOUND) { 934 found = true; 935 pAsset = openAssetFromFileLocked(path, mode); 936 if (pAsset == NULL) { 937 /* try again, this time with ".gz" */ 938 path.append(".gz"); 939 pAsset = openAssetFromFileLocked(path, mode); 940 } 941 } 942 943 if (pAsset != NULL) 944 pAsset->setAssetSource(path); 945 946 /* 947 * Don't continue the search into the Zip files. Our cached info 948 * said it was a file on disk; to be consistent with openDir() 949 * we want to return the loose asset. If the cached file gets 950 * removed, we fail. 951 * 952 * The alternative is to update our cache when files get deleted, 953 * or make some sort of "best effort" promise, but for now I'm 954 * taking the hard line. 955 */ 956 if (found) { 957 if (pAsset == NULL) 958 ALOGD("Expected file not found: '%s'\n", path.string()); 959 return pAsset; 960 } 961 } 962 } 963 964 /* 965 * Either it wasn't found on disk or on the cached view of the disk. 966 * Dig through the currently-opened set of Zip files. If caching 967 * is disabled, the Zip file may get reopened. 968 */ 969 if (pAsset == NULL && ap.type == kFileTypeRegular) { 970 String8 path; 971 972 path.appendPath((locale != NULL) ? locale : kDefaultLocale); 973 path.appendPath((vendor != NULL) ? vendor : kDefaultVendor); 974 path.appendPath(fileName); 975 976 /* check the appropriate Zip file */ 977 ZipFileRO* pZip; 978 ZipEntryRO entry; 979 980 pZip = getZipFileLocked(ap); 981 if (pZip != NULL) { 982 //printf("GOT zip, checking '%s'\n", (const char*) path); 983 entry = pZip->findEntryByName(path.string()); 984 if (entry != NULL) { 985 //printf("FOUND in Zip file for %s/%s-%s\n", 986 // appName, locale, vendor); 987 pAsset = openAssetFromZipLocked(pZip, entry, mode, path); 988 } 989 } 990 991 if (pAsset != NULL) { 992 /* create a "source" name, for debug/display */ 993 pAsset->setAssetSource(createZipSourceNameLocked(ZipSet::getPathName(ap.path.string()), 994 String8(""), String8(fileName))); 995 } 996 } 997 998 return pAsset; 999 } 1000 1001 /* 1002 * Create a "source name" for a file from a Zip archive. 1003 */ 1004 String8 AssetManager::createZipSourceNameLocked(const String8& zipFileName, 1005 const String8& dirName, const String8& fileName) 1006 { 1007 String8 sourceName("zip:"); 1008 sourceName.append(zipFileName); 1009 sourceName.append(":"); 1010 if (dirName.length() > 0) { 1011 sourceName.appendPath(dirName); 1012 } 1013 sourceName.appendPath(fileName); 1014 return sourceName; 1015 } 1016 1017 /* 1018 * Create a path to a loose asset (asset-base/app/locale/vendor). 1019 */ 1020 String8 AssetManager::createPathNameLocked(const asset_path& ap, const char* locale, 1021 const char* vendor) 1022 { 1023 String8 path(ap.path); 1024 path.appendPath((locale != NULL) ? locale : kDefaultLocale); 1025 path.appendPath((vendor != NULL) ? vendor : kDefaultVendor); 1026 return path; 1027 } 1028 1029 /* 1030 * Create a path to a loose asset (asset-base/app/rootDir). 1031 */ 1032 String8 AssetManager::createPathNameLocked(const asset_path& ap, const char* rootDir) 1033 { 1034 String8 path(ap.path); 1035 if (rootDir != NULL) path.appendPath(rootDir); 1036 return path; 1037 } 1038 1039 /* 1040 * Return a pointer to one of our open Zip archives. Returns NULL if no 1041 * matching Zip file exists. 1042 * 1043 * Right now we have 2 possible Zip files (1 each in app/"common"). 1044 * 1045 * If caching is set to CACHE_OFF, to get the expected behavior we 1046 * need to reopen the Zip file on every request. That would be silly 1047 * and expensive, so instead we just check the file modification date. 1048 * 1049 * Pass in NULL values for "appName", "locale", and "vendor" if the 1050 * generics should be used. 1051 */ 1052 ZipFileRO* AssetManager::getZipFileLocked(const asset_path& ap) 1053 { 1054 ALOGV("getZipFileLocked() in %p\n", this); 1055 1056 return mZipSet.getZip(ap.path); 1057 } 1058 1059 /* 1060 * Try to open an asset from a file on disk. 1061 * 1062 * If the file is compressed with gzip, we seek to the start of the 1063 * deflated data and pass that in (just like we would for a Zip archive). 1064 * 1065 * For uncompressed data, we may already have an mmap()ed version sitting 1066 * around. If so, we want to hand that to the Asset instead. 1067 * 1068 * This returns NULL if the file doesn't exist, couldn't be opened, or 1069 * claims to be a ".gz" but isn't. 1070 */ 1071 Asset* AssetManager::openAssetFromFileLocked(const String8& pathName, 1072 AccessMode mode) 1073 { 1074 Asset* pAsset = NULL; 1075 1076 if (strcasecmp(pathName.getPathExtension().string(), ".gz") == 0) { 1077 //printf("TRYING '%s'\n", (const char*) pathName); 1078 pAsset = Asset::createFromCompressedFile(pathName.string(), mode); 1079 } else { 1080 //printf("TRYING '%s'\n", (const char*) pathName); 1081 pAsset = Asset::createFromFile(pathName.string(), mode); 1082 } 1083 1084 return pAsset; 1085 } 1086 1087 /* 1088 * Given an entry in a Zip archive, create a new Asset object. 1089 * 1090 * If the entry is uncompressed, we may want to create or share a 1091 * slice of shared memory. 1092 */ 1093 Asset* AssetManager::openAssetFromZipLocked(const ZipFileRO* pZipFile, 1094 const ZipEntryRO entry, AccessMode mode, const String8& entryName) 1095 { 1096 Asset* pAsset = NULL; 1097 1098 // TODO: look for previously-created shared memory slice? 1099 int method; 1100 size_t uncompressedLen; 1101 1102 //printf("USING Zip '%s'\n", pEntry->getFileName()); 1103 1104 //pZipFile->getEntryInfo(entry, &method, &uncompressedLen, &compressedLen, 1105 // &offset); 1106 if (!pZipFile->getEntryInfo(entry, &method, &uncompressedLen, NULL, NULL, 1107 NULL, NULL)) 1108 { 1109 ALOGW("getEntryInfo failed\n"); 1110 return NULL; 1111 } 1112 1113 FileMap* dataMap = pZipFile->createEntryFileMap(entry); 1114 if (dataMap == NULL) { 1115 ALOGW("create map from entry failed\n"); 1116 return NULL; 1117 } 1118 1119 if (method == ZipFileRO::kCompressStored) { 1120 pAsset = Asset::createFromUncompressedMap(dataMap, mode); 1121 ALOGV("Opened uncompressed entry %s in zip %s mode %d: %p", entryName.string(), 1122 dataMap->getFileName(), mode, pAsset); 1123 } else { 1124 pAsset = Asset::createFromCompressedMap(dataMap, method, 1125 uncompressedLen, mode); 1126 ALOGV("Opened compressed entry %s in zip %s mode %d: %p", entryName.string(), 1127 dataMap->getFileName(), mode, pAsset); 1128 } 1129 if (pAsset == NULL) { 1130 /* unexpected */ 1131 ALOGW("create from segment failed\n"); 1132 } 1133 1134 return pAsset; 1135 } 1136 1137 1138 1139 /* 1140 * Open a directory in the asset namespace. 1141 * 1142 * An "asset directory" is simply the combination of all files in all 1143 * locations, with ".gz" stripped for loose files. With app, locale, and 1144 * vendor defined, we have 8 directories and 2 Zip archives to scan. 1145 * 1146 * Pass in "" for the root dir. 1147 */ 1148 AssetDir* AssetManager::openDir(const char* dirName) 1149 { 1150 AutoMutex _l(mLock); 1151 1152 AssetDir* pDir = NULL; 1153 SortedVector<AssetDir::FileInfo>* pMergedInfo = NULL; 1154 1155 LOG_FATAL_IF(mAssetPaths.size() == 0, "No assets added to AssetManager"); 1156 assert(dirName != NULL); 1157 1158 //printf("+++ openDir(%s) in '%s'\n", dirName, (const char*) mAssetBase); 1159 1160 if (mCacheMode != CACHE_OFF && !mCacheValid) 1161 loadFileNameCacheLocked(); 1162 1163 pDir = new AssetDir; 1164 1165 /* 1166 * Scan the various directories, merging what we find into a single 1167 * vector. We want to scan them in reverse priority order so that 1168 * the ".EXCLUDE" processing works correctly. Also, if we decide we 1169 * want to remember where the file is coming from, we'll get the right 1170 * version. 1171 * 1172 * We start with Zip archives, then do loose files. 1173 */ 1174 pMergedInfo = new SortedVector<AssetDir::FileInfo>; 1175 1176 size_t i = mAssetPaths.size(); 1177 while (i > 0) { 1178 i--; 1179 const asset_path& ap = mAssetPaths.itemAt(i); 1180 if (ap.type == kFileTypeRegular) { 1181 ALOGV("Adding directory %s from zip %s", dirName, ap.path.string()); 1182 scanAndMergeZipLocked(pMergedInfo, ap, kAssetsRoot, dirName); 1183 } else { 1184 ALOGV("Adding directory %s from dir %s", dirName, ap.path.string()); 1185 scanAndMergeDirLocked(pMergedInfo, ap, kAssetsRoot, dirName); 1186 } 1187 } 1188 1189 #if 0 1190 printf("FILE LIST:\n"); 1191 for (i = 0; i < (size_t) pMergedInfo->size(); i++) { 1192 printf(" %d: (%d) '%s'\n", i, 1193 pMergedInfo->itemAt(i).getFileType(), 1194 (const char*) pMergedInfo->itemAt(i).getFileName()); 1195 } 1196 #endif 1197 1198 pDir->setFileList(pMergedInfo); 1199 return pDir; 1200 } 1201 1202 /* 1203 * Open a directory in the non-asset namespace. 1204 * 1205 * An "asset directory" is simply the combination of all files in all 1206 * locations, with ".gz" stripped for loose files. With app, locale, and 1207 * vendor defined, we have 8 directories and 2 Zip archives to scan. 1208 * 1209 * Pass in "" for the root dir. 1210 */ 1211 AssetDir* AssetManager::openNonAssetDir(void* cookie, const char* dirName) 1212 { 1213 AutoMutex _l(mLock); 1214 1215 AssetDir* pDir = NULL; 1216 SortedVector<AssetDir::FileInfo>* pMergedInfo = NULL; 1217 1218 LOG_FATAL_IF(mAssetPaths.size() == 0, "No assets added to AssetManager"); 1219 assert(dirName != NULL); 1220 1221 //printf("+++ openDir(%s) in '%s'\n", dirName, (const char*) mAssetBase); 1222 1223 if (mCacheMode != CACHE_OFF && !mCacheValid) 1224 loadFileNameCacheLocked(); 1225 1226 pDir = new AssetDir; 1227 1228 pMergedInfo = new SortedVector<AssetDir::FileInfo>; 1229 1230 const size_t which = ((size_t)cookie)-1; 1231 1232 if (which < mAssetPaths.size()) { 1233 const asset_path& ap = mAssetPaths.itemAt(which); 1234 if (ap.type == kFileTypeRegular) { 1235 ALOGV("Adding directory %s from zip %s", dirName, ap.path.string()); 1236 scanAndMergeZipLocked(pMergedInfo, ap, NULL, dirName); 1237 } else { 1238 ALOGV("Adding directory %s from dir %s", dirName, ap.path.string()); 1239 scanAndMergeDirLocked(pMergedInfo, ap, NULL, dirName); 1240 } 1241 } 1242 1243 #if 0 1244 printf("FILE LIST:\n"); 1245 for (i = 0; i < (size_t) pMergedInfo->size(); i++) { 1246 printf(" %d: (%d) '%s'\n", i, 1247 pMergedInfo->itemAt(i).getFileType(), 1248 (const char*) pMergedInfo->itemAt(i).getFileName()); 1249 } 1250 #endif 1251 1252 pDir->setFileList(pMergedInfo); 1253 return pDir; 1254 } 1255 1256 /* 1257 * Scan the contents of the specified directory and merge them into the 1258 * "pMergedInfo" vector, removing previous entries if we find "exclude" 1259 * directives. 1260 * 1261 * Returns "false" if we found nothing to contribute. 1262 */ 1263 bool AssetManager::scanAndMergeDirLocked(SortedVector<AssetDir::FileInfo>* pMergedInfo, 1264 const asset_path& ap, const char* rootDir, const char* dirName) 1265 { 1266 SortedVector<AssetDir::FileInfo>* pContents; 1267 String8 path; 1268 1269 assert(pMergedInfo != NULL); 1270 1271 //printf("scanAndMergeDir: %s %s %s %s\n", appName, locale, vendor,dirName); 1272 1273 if (mCacheValid) { 1274 int i, start, count; 1275 1276 pContents = new SortedVector<AssetDir::FileInfo>; 1277 1278 /* 1279 * Get the basic partial path and find it in the cache. That's 1280 * the start point for the search. 1281 */ 1282 path = createPathNameLocked(ap, rootDir); 1283 if (dirName[0] != '\0') 1284 path.appendPath(dirName); 1285 1286 start = mCache.indexOf(path); 1287 if (start == NAME_NOT_FOUND) { 1288 //printf("+++ not found in cache: dir '%s'\n", (const char*) path); 1289 delete pContents; 1290 return false; 1291 } 1292 1293 /* 1294 * The match string looks like "common/default/default/foo/bar/". 1295 * The '/' on the end ensures that we don't match on the directory 1296 * itself or on ".../foo/barfy/". 1297 */ 1298 path.append("/"); 1299 1300 count = mCache.size(); 1301 1302 /* 1303 * Pick out the stuff in the current dir by examining the pathname. 1304 * It needs to match the partial pathname prefix, and not have a '/' 1305 * (fssep) anywhere after the prefix. 1306 */ 1307 for (i = start+1; i < count; i++) { 1308 if (mCache[i].getFileName().length() > path.length() && 1309 strncmp(mCache[i].getFileName().string(), path.string(), path.length()) == 0) 1310 { 1311 const char* name = mCache[i].getFileName().string(); 1312 // XXX THIS IS BROKEN! Looks like we need to store the full 1313 // path prefix separately from the file path. 1314 if (strchr(name + path.length(), '/') == NULL) { 1315 /* grab it, reducing path to just the filename component */ 1316 AssetDir::FileInfo tmp = mCache[i]; 1317 tmp.setFileName(tmp.getFileName().getPathLeaf()); 1318 pContents->add(tmp); 1319 } 1320 } else { 1321 /* no longer in the dir or its subdirs */ 1322 break; 1323 } 1324 1325 } 1326 } else { 1327 path = createPathNameLocked(ap, rootDir); 1328 if (dirName[0] != '\0') 1329 path.appendPath(dirName); 1330 pContents = scanDirLocked(path); 1331 if (pContents == NULL) 1332 return false; 1333 } 1334 1335 // if we wanted to do an incremental cache fill, we would do it here 1336 1337 /* 1338 * Process "exclude" directives. If we find a filename that ends with 1339 * ".EXCLUDE", we look for a matching entry in the "merged" set, and 1340 * remove it if we find it. We also delete the "exclude" entry. 1341 */ 1342 int i, count, exclExtLen; 1343 1344 count = pContents->size(); 1345 exclExtLen = strlen(kExcludeExtension); 1346 for (i = 0; i < count; i++) { 1347 const char* name; 1348 int nameLen; 1349 1350 name = pContents->itemAt(i).getFileName().string(); 1351 nameLen = strlen(name); 1352 if (nameLen > exclExtLen && 1353 strcmp(name + (nameLen - exclExtLen), kExcludeExtension) == 0) 1354 { 1355 String8 match(name, nameLen - exclExtLen); 1356 int matchIdx; 1357 1358 matchIdx = AssetDir::FileInfo::findEntry(pMergedInfo, match); 1359 if (matchIdx > 0) { 1360 ALOGV("Excluding '%s' [%s]\n", 1361 pMergedInfo->itemAt(matchIdx).getFileName().string(), 1362 pMergedInfo->itemAt(matchIdx).getSourceName().string()); 1363 pMergedInfo->removeAt(matchIdx); 1364 } else { 1365 //printf("+++ no match on '%s'\n", (const char*) match); 1366 } 1367 1368 ALOGD("HEY: size=%d removing %d\n", (int)pContents->size(), i); 1369 pContents->removeAt(i); 1370 i--; // adjust "for" loop 1371 count--; // and loop limit 1372 } 1373 } 1374 1375 mergeInfoLocked(pMergedInfo, pContents); 1376 1377 delete pContents; 1378 1379 return true; 1380 } 1381 1382 /* 1383 * Scan the contents of the specified directory, and stuff what we find 1384 * into a newly-allocated vector. 1385 * 1386 * Files ending in ".gz" will have their extensions removed. 1387 * 1388 * We should probably think about skipping files with "illegal" names, 1389 * e.g. illegal characters (/\:) or excessive length. 1390 * 1391 * Returns NULL if the specified directory doesn't exist. 1392 */ 1393 SortedVector<AssetDir::FileInfo>* AssetManager::scanDirLocked(const String8& path) 1394 { 1395 SortedVector<AssetDir::FileInfo>* pContents = NULL; 1396 DIR* dir; 1397 struct dirent* entry; 1398 FileType fileType; 1399 1400 ALOGV("Scanning dir '%s'\n", path.string()); 1401 1402 dir = opendir(path.string()); 1403 if (dir == NULL) 1404 return NULL; 1405 1406 pContents = new SortedVector<AssetDir::FileInfo>; 1407 1408 while (1) { 1409 entry = readdir(dir); 1410 if (entry == NULL) 1411 break; 1412 1413 if (strcmp(entry->d_name, ".") == 0 || 1414 strcmp(entry->d_name, "..") == 0) 1415 continue; 1416 1417 #ifdef _DIRENT_HAVE_D_TYPE 1418 if (entry->d_type == DT_REG) 1419 fileType = kFileTypeRegular; 1420 else if (entry->d_type == DT_DIR) 1421 fileType = kFileTypeDirectory; 1422 else 1423 fileType = kFileTypeUnknown; 1424 #else 1425 // stat the file 1426 fileType = ::getFileType(path.appendPathCopy(entry->d_name).string()); 1427 #endif 1428 1429 if (fileType != kFileTypeRegular && fileType != kFileTypeDirectory) 1430 continue; 1431 1432 AssetDir::FileInfo info; 1433 info.set(String8(entry->d_name), fileType); 1434 if (strcasecmp(info.getFileName().getPathExtension().string(), ".gz") == 0) 1435 info.setFileName(info.getFileName().getBasePath()); 1436 info.setSourceName(path.appendPathCopy(info.getFileName())); 1437 pContents->add(info); 1438 } 1439 1440 closedir(dir); 1441 return pContents; 1442 } 1443 1444 /* 1445 * Scan the contents out of the specified Zip archive, and merge what we 1446 * find into "pMergedInfo". If the Zip archive in question doesn't exist, 1447 * we return immediately. 1448 * 1449 * Returns "false" if we found nothing to contribute. 1450 */ 1451 bool AssetManager::scanAndMergeZipLocked(SortedVector<AssetDir::FileInfo>* pMergedInfo, 1452 const asset_path& ap, const char* rootDir, const char* baseDirName) 1453 { 1454 ZipFileRO* pZip; 1455 Vector<String8> dirs; 1456 AssetDir::FileInfo info; 1457 SortedVector<AssetDir::FileInfo> contents; 1458 String8 sourceName, zipName, dirName; 1459 1460 pZip = mZipSet.getZip(ap.path); 1461 if (pZip == NULL) { 1462 ALOGW("Failure opening zip %s\n", ap.path.string()); 1463 return false; 1464 } 1465 1466 zipName = ZipSet::getPathName(ap.path.string()); 1467 1468 /* convert "sounds" to "rootDir/sounds" */ 1469 if (rootDir != NULL) dirName = rootDir; 1470 dirName.appendPath(baseDirName); 1471 1472 /* 1473 * Scan through the list of files, looking for a match. The files in 1474 * the Zip table of contents are not in sorted order, so we have to 1475 * process the entire list. We're looking for a string that begins 1476 * with the characters in "dirName", is followed by a '/', and has no 1477 * subsequent '/' in the stuff that follows. 1478 * 1479 * What makes this especially fun is that directories are not stored 1480 * explicitly in Zip archives, so we have to infer them from context. 1481 * When we see "sounds/foo.wav" we have to leave a note to ourselves 1482 * to insert a directory called "sounds" into the list. We store 1483 * these in temporary vector so that we only return each one once. 1484 * 1485 * Name comparisons are case-sensitive to match UNIX filesystem 1486 * semantics. 1487 */ 1488 int dirNameLen = dirName.length(); 1489 for (int i = 0; i < pZip->getNumEntries(); i++) { 1490 ZipEntryRO entry; 1491 char nameBuf[256]; 1492 1493 entry = pZip->findEntryByIndex(i); 1494 if (pZip->getEntryFileName(entry, nameBuf, sizeof(nameBuf)) != 0) { 1495 // TODO: fix this if we expect to have long names 1496 ALOGE("ARGH: name too long?\n"); 1497 continue; 1498 } 1499 //printf("Comparing %s in %s?\n", nameBuf, dirName.string()); 1500 if (dirNameLen == 0 || 1501 (strncmp(nameBuf, dirName.string(), dirNameLen) == 0 && 1502 nameBuf[dirNameLen] == '/')) 1503 { 1504 const char* cp; 1505 const char* nextSlash; 1506 1507 cp = nameBuf + dirNameLen; 1508 if (dirNameLen != 0) 1509 cp++; // advance past the '/' 1510 1511 nextSlash = strchr(cp, '/'); 1512 //xxx this may break if there are bare directory entries 1513 if (nextSlash == NULL) { 1514 /* this is a file in the requested directory */ 1515 1516 info.set(String8(nameBuf).getPathLeaf(), kFileTypeRegular); 1517 1518 info.setSourceName( 1519 createZipSourceNameLocked(zipName, dirName, info.getFileName())); 1520 1521 contents.add(info); 1522 //printf("FOUND: file '%s'\n", info.getFileName().string()); 1523 } else { 1524 /* this is a subdir; add it if we don't already have it*/ 1525 String8 subdirName(cp, nextSlash - cp); 1526 size_t j; 1527 size_t N = dirs.size(); 1528 1529 for (j = 0; j < N; j++) { 1530 if (subdirName == dirs[j]) { 1531 break; 1532 } 1533 } 1534 if (j == N) { 1535 dirs.add(subdirName); 1536 } 1537 1538 //printf("FOUND: dir '%s'\n", subdirName.string()); 1539 } 1540 } 1541 } 1542 1543 /* 1544 * Add the set of unique directories. 1545 */ 1546 for (int i = 0; i < (int) dirs.size(); i++) { 1547 info.set(dirs[i], kFileTypeDirectory); 1548 info.setSourceName( 1549 createZipSourceNameLocked(zipName, dirName, info.getFileName())); 1550 contents.add(info); 1551 } 1552 1553 mergeInfoLocked(pMergedInfo, &contents); 1554 1555 return true; 1556 } 1557 1558 1559 /* 1560 * Merge two vectors of FileInfo. 1561 * 1562 * The merged contents will be stuffed into *pMergedInfo. 1563 * 1564 * If an entry for a file exists in both "pMergedInfo" and "pContents", 1565 * we use the newer "pContents" entry. 1566 */ 1567 void AssetManager::mergeInfoLocked(SortedVector<AssetDir::FileInfo>* pMergedInfo, 1568 const SortedVector<AssetDir::FileInfo>* pContents) 1569 { 1570 /* 1571 * Merge what we found in this directory with what we found in 1572 * other places. 1573 * 1574 * Two basic approaches: 1575 * (1) Create a new array that holds the unique values of the two 1576 * arrays. 1577 * (2) Take the elements from pContents and shove them into pMergedInfo. 1578 * 1579 * Because these are vectors of complex objects, moving elements around 1580 * inside the vector requires constructing new objects and allocating 1581 * storage for members. With approach #1, we're always adding to the 1582 * end, whereas with #2 we could be inserting multiple elements at the 1583 * front of the vector. Approach #1 requires a full copy of the 1584 * contents of pMergedInfo, but approach #2 requires the same copy for 1585 * every insertion at the front of pMergedInfo. 1586 * 1587 * (We should probably use a SortedVector interface that allows us to 1588 * just stuff items in, trusting us to maintain the sort order.) 1589 */ 1590 SortedVector<AssetDir::FileInfo>* pNewSorted; 1591 int mergeMax, contMax; 1592 int mergeIdx, contIdx; 1593 1594 pNewSorted = new SortedVector<AssetDir::FileInfo>; 1595 mergeMax = pMergedInfo->size(); 1596 contMax = pContents->size(); 1597 mergeIdx = contIdx = 0; 1598 1599 while (mergeIdx < mergeMax || contIdx < contMax) { 1600 if (mergeIdx == mergeMax) { 1601 /* hit end of "merge" list, copy rest of "contents" */ 1602 pNewSorted->add(pContents->itemAt(contIdx)); 1603 contIdx++; 1604 } else if (contIdx == contMax) { 1605 /* hit end of "cont" list, copy rest of "merge" */ 1606 pNewSorted->add(pMergedInfo->itemAt(mergeIdx)); 1607 mergeIdx++; 1608 } else if (pMergedInfo->itemAt(mergeIdx) == pContents->itemAt(contIdx)) 1609 { 1610 /* items are identical, add newer and advance both indices */ 1611 pNewSorted->add(pContents->itemAt(contIdx)); 1612 mergeIdx++; 1613 contIdx++; 1614 } else if (pMergedInfo->itemAt(mergeIdx) < pContents->itemAt(contIdx)) 1615 { 1616 /* "merge" is lower, add that one */ 1617 pNewSorted->add(pMergedInfo->itemAt(mergeIdx)); 1618 mergeIdx++; 1619 } else { 1620 /* "cont" is lower, add that one */ 1621 assert(pContents->itemAt(contIdx) < pMergedInfo->itemAt(mergeIdx)); 1622 pNewSorted->add(pContents->itemAt(contIdx)); 1623 contIdx++; 1624 } 1625 } 1626 1627 /* 1628 * Overwrite the "merged" list with the new stuff. 1629 */ 1630 *pMergedInfo = *pNewSorted; 1631 delete pNewSorted; 1632 1633 #if 0 // for Vector, rather than SortedVector 1634 int i, j; 1635 for (i = pContents->size() -1; i >= 0; i--) { 1636 bool add = true; 1637 1638 for (j = pMergedInfo->size() -1; j >= 0; j--) { 1639 /* case-sensitive comparisons, to behave like UNIX fs */ 1640 if (strcmp(pContents->itemAt(i).mFileName, 1641 pMergedInfo->itemAt(j).mFileName) == 0) 1642 { 1643 /* match, don't add this entry */ 1644 add = false; 1645 break; 1646 } 1647 } 1648 1649 if (add) 1650 pMergedInfo->add(pContents->itemAt(i)); 1651 } 1652 #endif 1653 } 1654 1655 1656 /* 1657 * Load all files into the file name cache. We want to do this across 1658 * all combinations of { appname, locale, vendor }, performing a recursive 1659 * directory traversal. 1660 * 1661 * This is not the most efficient data structure. Also, gathering the 1662 * information as we needed it (file-by-file or directory-by-directory) 1663 * would be faster. However, on the actual device, 99% of the files will 1664 * live in Zip archives, so this list will be very small. The trouble 1665 * is that we have to check the "loose" files first, so it's important 1666 * that we don't beat the filesystem silly looking for files that aren't 1667 * there. 1668 * 1669 * Note on thread safety: this is the only function that causes updates 1670 * to mCache, and anybody who tries to use it will call here if !mCacheValid, 1671 * so we need to employ a mutex here. 1672 */ 1673 void AssetManager::loadFileNameCacheLocked(void) 1674 { 1675 assert(!mCacheValid); 1676 assert(mCache.size() == 0); 1677 1678 #ifdef DO_TIMINGS // need to link against -lrt for this now 1679 DurationTimer timer; 1680 timer.start(); 1681 #endif 1682 1683 fncScanLocked(&mCache, ""); 1684 1685 #ifdef DO_TIMINGS 1686 timer.stop(); 1687 ALOGD("Cache scan took %.3fms\n", 1688 timer.durationUsecs() / 1000.0); 1689 #endif 1690 1691 #if 0 1692 int i; 1693 printf("CACHED FILE LIST (%d entries):\n", mCache.size()); 1694 for (i = 0; i < (int) mCache.size(); i++) { 1695 printf(" %d: (%d) '%s'\n", i, 1696 mCache.itemAt(i).getFileType(), 1697 (const char*) mCache.itemAt(i).getFileName()); 1698 } 1699 #endif 1700 1701 mCacheValid = true; 1702 } 1703 1704 /* 1705 * Scan up to 8 versions of the specified directory. 1706 */ 1707 void AssetManager::fncScanLocked(SortedVector<AssetDir::FileInfo>* pMergedInfo, 1708 const char* dirName) 1709 { 1710 size_t i = mAssetPaths.size(); 1711 while (i > 0) { 1712 i--; 1713 const asset_path& ap = mAssetPaths.itemAt(i); 1714 fncScanAndMergeDirLocked(pMergedInfo, ap, NULL, NULL, dirName); 1715 if (mLocale != NULL) 1716 fncScanAndMergeDirLocked(pMergedInfo, ap, mLocale, NULL, dirName); 1717 if (mVendor != NULL) 1718 fncScanAndMergeDirLocked(pMergedInfo, ap, NULL, mVendor, dirName); 1719 if (mLocale != NULL && mVendor != NULL) 1720 fncScanAndMergeDirLocked(pMergedInfo, ap, mLocale, mVendor, dirName); 1721 } 1722 } 1723 1724 /* 1725 * Recursively scan this directory and all subdirs. 1726 * 1727 * This is similar to scanAndMergeDir, but we don't remove the .EXCLUDE 1728 * files, and we prepend the extended partial path to the filenames. 1729 */ 1730 bool AssetManager::fncScanAndMergeDirLocked( 1731 SortedVector<AssetDir::FileInfo>* pMergedInfo, 1732 const asset_path& ap, const char* locale, const char* vendor, 1733 const char* dirName) 1734 { 1735 SortedVector<AssetDir::FileInfo>* pContents; 1736 String8 partialPath; 1737 String8 fullPath; 1738 1739 // XXX This is broken -- the filename cache needs to hold the base 1740 // asset path separately from its filename. 1741 1742 partialPath = createPathNameLocked(ap, locale, vendor); 1743 if (dirName[0] != '\0') { 1744 partialPath.appendPath(dirName); 1745 } 1746 1747 fullPath = partialPath; 1748 pContents = scanDirLocked(fullPath); 1749 if (pContents == NULL) { 1750 return false; // directory did not exist 1751 } 1752 1753 /* 1754 * Scan all subdirectories of the current dir, merging what we find 1755 * into "pMergedInfo". 1756 */ 1757 for (int i = 0; i < (int) pContents->size(); i++) { 1758 if (pContents->itemAt(i).getFileType() == kFileTypeDirectory) { 1759 String8 subdir(dirName); 1760 subdir.appendPath(pContents->itemAt(i).getFileName()); 1761 1762 fncScanAndMergeDirLocked(pMergedInfo, ap, locale, vendor, subdir.string()); 1763 } 1764 } 1765 1766 /* 1767 * To be consistent, we want entries for the root directory. If 1768 * we're the root, add one now. 1769 */ 1770 if (dirName[0] == '\0') { 1771 AssetDir::FileInfo tmpInfo; 1772 1773 tmpInfo.set(String8(""), kFileTypeDirectory); 1774 tmpInfo.setSourceName(createPathNameLocked(ap, locale, vendor)); 1775 pContents->add(tmpInfo); 1776 } 1777 1778 /* 1779 * We want to prepend the extended partial path to every entry in 1780 * "pContents". It's the same value for each entry, so this will 1781 * not change the sorting order of the vector contents. 1782 */ 1783 for (int i = 0; i < (int) pContents->size(); i++) { 1784 const AssetDir::FileInfo& info = pContents->itemAt(i); 1785 pContents->editItemAt(i).setFileName(partialPath.appendPathCopy(info.getFileName())); 1786 } 1787 1788 mergeInfoLocked(pMergedInfo, pContents); 1789 return true; 1790 } 1791 1792 /* 1793 * Trash the cache. 1794 */ 1795 void AssetManager::purgeFileNameCacheLocked(void) 1796 { 1797 mCacheValid = false; 1798 mCache.clear(); 1799 } 1800 1801 /* 1802 * =========================================================================== 1803 * AssetManager::SharedZip 1804 * =========================================================================== 1805 */ 1806 1807 1808 Mutex AssetManager::SharedZip::gLock; 1809 DefaultKeyedVector<String8, wp<AssetManager::SharedZip> > AssetManager::SharedZip::gOpen; 1810 1811 AssetManager::SharedZip::SharedZip(const String8& path, time_t modWhen) 1812 : mPath(path), mZipFile(NULL), mModWhen(modWhen), 1813 mResourceTableAsset(NULL), mResourceTable(NULL) 1814 { 1815 //ALOGI("Creating SharedZip %p %s\n", this, (const char*)mPath); 1816 mZipFile = new ZipFileRO; 1817 ALOGV("+++ opening zip '%s'\n", mPath.string()); 1818 if (mZipFile->open(mPath.string()) != NO_ERROR) { 1819 ALOGD("failed to open Zip archive '%s'\n", mPath.string()); 1820 delete mZipFile; 1821 mZipFile = NULL; 1822 } 1823 } 1824 1825 sp<AssetManager::SharedZip> AssetManager::SharedZip::get(const String8& path) 1826 { 1827 AutoMutex _l(gLock); 1828 time_t modWhen = getFileModDate(path); 1829 sp<SharedZip> zip = gOpen.valueFor(path).promote(); 1830 if (zip != NULL && zip->mModWhen == modWhen) { 1831 return zip; 1832 } 1833 zip = new SharedZip(path, modWhen); 1834 gOpen.add(path, zip); 1835 return zip; 1836 1837 } 1838 1839 ZipFileRO* AssetManager::SharedZip::getZip() 1840 { 1841 return mZipFile; 1842 } 1843 1844 Asset* AssetManager::SharedZip::getResourceTableAsset() 1845 { 1846 ALOGV("Getting from SharedZip %p resource asset %p\n", this, mResourceTableAsset); 1847 return mResourceTableAsset; 1848 } 1849 1850 Asset* AssetManager::SharedZip::setResourceTableAsset(Asset* asset) 1851 { 1852 { 1853 AutoMutex _l(gLock); 1854 if (mResourceTableAsset == NULL) { 1855 mResourceTableAsset = asset; 1856 // This is not thread safe the first time it is called, so 1857 // do it here with the global lock held. 1858 asset->getBuffer(true); 1859 return asset; 1860 } 1861 } 1862 delete asset; 1863 return mResourceTableAsset; 1864 } 1865 1866 ResTable* AssetManager::SharedZip::getResourceTable() 1867 { 1868 ALOGV("Getting from SharedZip %p resource table %p\n", this, mResourceTable); 1869 return mResourceTable; 1870 } 1871 1872 ResTable* AssetManager::SharedZip::setResourceTable(ResTable* res) 1873 { 1874 { 1875 AutoMutex _l(gLock); 1876 if (mResourceTable == NULL) { 1877 mResourceTable = res; 1878 return res; 1879 } 1880 } 1881 delete res; 1882 return mResourceTable; 1883 } 1884 1885 bool AssetManager::SharedZip::isUpToDate() 1886 { 1887 time_t modWhen = getFileModDate(mPath.string()); 1888 return mModWhen == modWhen; 1889 } 1890 1891 AssetManager::SharedZip::~SharedZip() 1892 { 1893 //ALOGI("Destroying SharedZip %p %s\n", this, (const char*)mPath); 1894 if (mResourceTable != NULL) { 1895 delete mResourceTable; 1896 } 1897 if (mResourceTableAsset != NULL) { 1898 delete mResourceTableAsset; 1899 } 1900 if (mZipFile != NULL) { 1901 delete mZipFile; 1902 ALOGV("Closed '%s'\n", mPath.string()); 1903 } 1904 } 1905 1906 /* 1907 * =========================================================================== 1908 * AssetManager::ZipSet 1909 * =========================================================================== 1910 */ 1911 1912 /* 1913 * Constructor. 1914 */ 1915 AssetManager::ZipSet::ZipSet(void) 1916 { 1917 } 1918 1919 /* 1920 * Destructor. Close any open archives. 1921 */ 1922 AssetManager::ZipSet::~ZipSet(void) 1923 { 1924 size_t N = mZipFile.size(); 1925 for (size_t i = 0; i < N; i++) 1926 closeZip(i); 1927 } 1928 1929 /* 1930 * Close a Zip file and reset the entry. 1931 */ 1932 void AssetManager::ZipSet::closeZip(int idx) 1933 { 1934 mZipFile.editItemAt(idx) = NULL; 1935 } 1936 1937 1938 /* 1939 * Retrieve the appropriate Zip file from the set. 1940 */ 1941 ZipFileRO* AssetManager::ZipSet::getZip(const String8& path) 1942 { 1943 int idx = getIndex(path); 1944 sp<SharedZip> zip = mZipFile[idx]; 1945 if (zip == NULL) { 1946 zip = SharedZip::get(path); 1947 mZipFile.editItemAt(idx) = zip; 1948 } 1949 return zip->getZip(); 1950 } 1951 1952 Asset* AssetManager::ZipSet::getZipResourceTableAsset(const String8& path) 1953 { 1954 int idx = getIndex(path); 1955 sp<SharedZip> zip = mZipFile[idx]; 1956 if (zip == NULL) { 1957 zip = SharedZip::get(path); 1958 mZipFile.editItemAt(idx) = zip; 1959 } 1960 return zip->getResourceTableAsset(); 1961 } 1962 1963 Asset* AssetManager::ZipSet::setZipResourceTableAsset(const String8& path, 1964 Asset* asset) 1965 { 1966 int idx = getIndex(path); 1967 sp<SharedZip> zip = mZipFile[idx]; 1968 // doesn't make sense to call before previously accessing. 1969 return zip->setResourceTableAsset(asset); 1970 } 1971 1972 ResTable* AssetManager::ZipSet::getZipResourceTable(const String8& path) 1973 { 1974 int idx = getIndex(path); 1975 sp<SharedZip> zip = mZipFile[idx]; 1976 if (zip == NULL) { 1977 zip = SharedZip::get(path); 1978 mZipFile.editItemAt(idx) = zip; 1979 } 1980 return zip->getResourceTable(); 1981 } 1982 1983 ResTable* AssetManager::ZipSet::setZipResourceTable(const String8& path, 1984 ResTable* res) 1985 { 1986 int idx = getIndex(path); 1987 sp<SharedZip> zip = mZipFile[idx]; 1988 // doesn't make sense to call before previously accessing. 1989 return zip->setResourceTable(res); 1990 } 1991 1992 /* 1993 * Generate the partial pathname for the specified archive. The caller 1994 * gets to prepend the asset root directory. 1995 * 1996 * Returns something like "common/en-US-noogle.jar". 1997 */ 1998 /*static*/ String8 AssetManager::ZipSet::getPathName(const char* zipPath) 1999 { 2000 return String8(zipPath); 2001 } 2002 2003 bool AssetManager::ZipSet::isUpToDate() 2004 { 2005 const size_t N = mZipFile.size(); 2006 for (size_t i=0; i<N; i++) { 2007 if (mZipFile[i] != NULL && !mZipFile[i]->isUpToDate()) { 2008 return false; 2009 } 2010 } 2011 return true; 2012 } 2013 2014 /* 2015 * Compute the zip file's index. 2016 * 2017 * "appName", "locale", and "vendor" should be set to NULL to indicate the 2018 * default directory. 2019 */ 2020 int AssetManager::ZipSet::getIndex(const String8& zip) const 2021 { 2022 const size_t N = mZipPath.size(); 2023 for (size_t i=0; i<N; i++) { 2024 if (mZipPath[i] == zip) { 2025 return i; 2026 } 2027 } 2028 2029 mZipPath.add(zip); 2030 mZipFile.add(NULL); 2031 2032 return mZipPath.size()-1; 2033 } 2034