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