1 package org.robolectric.res.android; 2 3 import static org.robolectric.res.android.Asset.toIntExact; 4 import static org.robolectric.res.android.CppAssetManager.FileType.kFileTypeDirectory; 5 import static org.robolectric.res.android.Util.ALOGD; 6 import static org.robolectric.res.android.Util.ALOGE; 7 import static org.robolectric.res.android.Util.ALOGI; 8 import static org.robolectric.res.android.Util.ALOGV; 9 import static org.robolectric.res.android.Util.ALOGW; 10 import static org.robolectric.res.android.Util.ATRACE_CALL; 11 import static org.robolectric.res.android.Util.LOG_FATAL_IF; 12 import static org.robolectric.res.android.Util.isTruthy; 13 14 import com.google.common.annotations.VisibleForTesting; 15 import com.google.common.base.Preconditions; 16 import java.io.File; 17 import java.io.IOException; 18 import java.lang.ref.WeakReference; 19 import java.nio.file.Files; 20 import java.nio.file.Paths; 21 import java.util.ArrayList; 22 import java.util.Enumeration; 23 import java.util.HashMap; 24 import java.util.List; 25 import java.util.Map; 26 import java.util.Objects; 27 import java.util.zip.ZipEntry; 28 import javax.annotation.Nullable; 29 import org.robolectric.res.Fs; 30 import org.robolectric.res.FsFile; 31 import org.robolectric.res.android.Asset.AccessMode; 32 import org.robolectric.res.android.AssetDir.FileInfo; 33 import org.robolectric.res.android.ZipFileRO.ZipEntryRO; 34 import org.robolectric.util.PerfStatsCollector; 35 36 // transliterated from https://android.googlesource.com/platform/frameworks/base/+/android-9.0.0_r12/libs/androidfw/AssetManager.cpp 37 @SuppressWarnings("NewApi") 38 public class CppAssetManager { 39 40 private static final boolean kIsDebug = false; 41 42 enum FileType { 43 kFileTypeUnknown, 44 kFileTypeNonexistent, // i.e. ENOENT 45 kFileTypeRegular, 46 kFileTypeDirectory, 47 kFileTypeCharDev, 48 kFileTypeBlockDev, 49 kFileTypeFifo, 50 kFileTypeSymlink, 51 kFileTypeSocket, 52 } 53 54 55 // transliterated from https://cs.corp.google.com/android/frameworks/base/libs/androidfw/include/androidfw/AssetManager.h 56 private static class asset_path { 57 // asset_path() : path(""), type(kFileTypeRegular), idmap(""), 58 // isSystemOverlay(false), isSystemAsset(false) {} 59 60 asset_path()61 public asset_path() { 62 this(new String8(), FileType.kFileTypeRegular, new String8(""), false, false); 63 } 64 asset_path(String8 path, FileType fileType, String8 idmap, boolean isSystemOverlay, boolean isSystemAsset)65 public asset_path(String8 path, FileType fileType, String8 idmap, 66 boolean isSystemOverlay, 67 boolean isSystemAsset) { 68 this.path = path; 69 this.type = fileType; 70 this.idmap = idmap; 71 this.isSystemOverlay = isSystemOverlay; 72 this.isSystemAsset = isSystemAsset; 73 } 74 75 String8 path; 76 FileType type; 77 String8 idmap; 78 boolean isSystemOverlay; 79 boolean isSystemAsset; 80 81 @Override toString()82 public String toString() { 83 return "asset_path{" + 84 "path=" + path + 85 ", type=" + type + 86 ", idmap='" + idmap + '\'' + 87 ", isSystemOverlay=" + isSystemOverlay + 88 ", isSystemAsset=" + isSystemAsset + 89 '}'; 90 } 91 } 92 93 private final Object mLock = new Object(); 94 95 // unlike AssetManager.cpp, this is shared between CppAssetManager instances, and is used 96 // to cache ResTables between tests. 97 private static final ZipSet mZipSet = new ZipSet(); 98 99 private final List<asset_path> mAssetPaths = new ArrayList<>(); 100 private String mLocale; 101 102 private ResTable mResources; 103 private ResTable_config mConfig = new ResTable_config(); 104 105 106 // static final boolean kIsDebug = false; 107 // 108 static final String kAssetsRoot = "assets"; 109 static final String kAppZipName = null; //"classes.jar"; 110 static final String kSystemAssets = "android.jar"; 111 // static final char* kResourceCache = "resource-cache"; 112 // 113 static final String kExcludeExtension = ".EXCLUDE"; 114 // 115 116 // static Asset final kExcludedAsset = (Asset*) 0xd000000d; 117 static final Asset kExcludedAsset = Asset.EXCLUDED_ASSET; 118 119 120 static volatile int gCount = 0; 121 122 // final char* RESOURCES_FILENAME = "resources.arsc"; 123 // final char* IDMAP_BIN = "/system/bin/idmap"; 124 // final char* OVERLAY_DIR = "/vendor/overlay"; 125 // final char* OVERLAY_THEME_DIR_PROPERTY = "ro.boot.vendor.overlay.theme"; 126 // final char* TARGET_PACKAGE_NAME = "android"; 127 // final char* TARGET_APK_PATH = "/system/framework/framework-res.apk"; 128 // final char* IDMAP_DIR = "/data/resource-cache"; 129 // 130 // namespace { 131 // idmapPathForPackagePath(final String8 pkgPath)132 String8 idmapPathForPackagePath(final String8 pkgPath) { 133 // TODO: implement this? 134 return pkgPath; 135 // const char* root = getenv("ANDROID_DATA"); 136 // LOG_ALWAYS_FATAL_IF(root == NULL, "ANDROID_DATA not set"); 137 // String8 path(root); 138 // path.appendPath(kResourceCache); 139 // char buf[256]; // 256 chars should be enough for anyone... 140 // strncpy(buf, pkgPath.string(), 255); 141 // buf[255] = '\0'; 142 // char* filename = buf; 143 // while (*filename && *filename == '/') { 144 // ++filename; 145 // } 146 // char* p = filename; 147 // while (*p) { 148 // if (*p == '/') { 149 // *p = '@'; 150 // } 151 // ++p; 152 // } 153 // path.appendPath(filename); 154 // path.append("@idmap"); 155 // return path; 156 } 157 // 158 // /* 159 // * Like strdup(), but uses C++ "new" operator instead of malloc. 160 // */ 161 // static char* strdupNew(final char* str) { 162 // char* newStr; 163 // int len; 164 // 165 // if (str == null) 166 // return null; 167 // 168 // len = strlen(str); 169 // newStr = new char[len+1]; 170 // memcpy(newStr, str, len+1); 171 // 172 // return newStr; 173 // } 174 // 175 // } // namespace 176 // 177 // /* 178 // * =========================================================================== 179 // * AssetManager 180 // * =========================================================================== 181 // */ 182 getGlobalCount()183 public static int getGlobalCount() { 184 return gCount; 185 } 186 187 // AssetManager() : 188 // mLocale(null), mResources(null), mConfig(new ResTable_config) { 189 // int count = android_atomic_inc(&gCount) + 1; 190 // if (kIsDebug) { 191 // ALOGI("Creating AssetManager %s #%d\n", this, count); 192 // } 193 // memset(mConfig, 0, sizeof(ResTable_config)); 194 // } 195 // 196 // ~AssetManager() { 197 // int count = android_atomic_dec(&gCount); 198 // if (kIsDebug) { 199 // ALOGI("Destroying AssetManager in %s #%d\n", this, count); 200 // } else { 201 // ALOGI("Destroying AssetManager in %s #%d\n", this, count); 202 // } 203 // // Manually close any fd paths for which we have not yet opened their zip (which 204 // // will take ownership of the fd and close it when done). 205 // for (size_t i=0; i<mAssetPaths.size(); i++) { 206 // ALOGV("Cleaning path #%d: fd=%d, zip=%p", (int)i, mAssetPaths[i].rawFd, 207 // mAssetPaths[i].zip.get()); 208 // if (mAssetPaths[i].rawFd >= 0 && mAssetPaths[i].zip == NULL) { 209 // close(mAssetPaths[i].rawFd); 210 // } 211 // } 212 // 213 // delete mConfig; 214 // delete mResources; 215 // 216 // // don't have a String class yet, so make sure we clean up 217 // delete[] mLocale; 218 // } 219 addAssetPath(String8 path, Ref<Integer> cookie, boolean appAsLib)220 public boolean addAssetPath(String8 path, Ref<Integer> cookie, boolean appAsLib) { 221 return addAssetPath(path, cookie, appAsLib, false); 222 } 223 addAssetPath( final String8 path, @Nullable Ref<Integer> cookie, boolean appAsLib, boolean isSystemAsset)224 public boolean addAssetPath( 225 final String8 path, @Nullable Ref<Integer> cookie, boolean appAsLib, boolean isSystemAsset) { 226 synchronized (mLock) { 227 228 asset_path ap = new asset_path(); 229 230 String8 realPath = path; 231 if (kAppZipName != null) { 232 realPath.appendPath(kAppZipName); 233 } 234 ap.type = getFileType(realPath.string()); 235 if (ap.type == FileType.kFileTypeRegular) { 236 ap.path = realPath; 237 } else { 238 ap.path = path; 239 ap.type = getFileType(path.string()); 240 if (ap.type != kFileTypeDirectory && ap.type != FileType.kFileTypeRegular) { 241 ALOGW("Asset path %s is neither a directory nor file (type=%s).", 242 path.toString(), ap.type.name()); 243 return false; 244 } 245 } 246 247 // Skip if we have it already. 248 for (int i = 0; i < mAssetPaths.size(); i++) { 249 if (mAssetPaths.get(i).path.equals(ap.path)) { 250 if (cookie != null) { 251 cookie.set(i + 1); 252 } 253 return true; 254 } 255 } 256 257 ALOGV("In %s Asset %s path: %s", this, 258 ap.type.name(), ap.path.toString()); 259 260 ap.isSystemAsset = isSystemAsset; 261 /*int apPos =*/ mAssetPaths.add(ap); 262 263 // new paths are always added at the end 264 if (cookie != null) { 265 cookie.set(mAssetPaths.size()); 266 } 267 268 // TODO: implement this? 269 //#ifdef __ANDROID__ 270 // Load overlays, if any 271 //asset_path oap; 272 //for (int idx = 0; mZipSet.getOverlay(ap.path, idx, & oap) 273 // ; idx++){ 274 // oap.isSystemAsset = isSystemAsset; 275 // mAssetPaths.add(oap); 276 // } 277 //#endif 278 279 if (mResources != null) { 280 // appendPathToResTable(mAssetPaths.editItemAt(apPos), appAsLib); 281 appendPathToResTable(ap, appAsLib); 282 } 283 284 return true; 285 } 286 } 287 288 // 289 // boolean addOverlayPath(final String8 packagePath, Ref<Integer> cookie) 290 // { 291 // final String8 idmapPath = idmapPathForPackagePath(packagePath); 292 // 293 // synchronized (mLock) { 294 // 295 // for (int i = 0; i < mAssetPaths.size(); ++i) { 296 // if (mAssetPaths.get(i).idmap.equals(idmapPath)) { 297 // cookie.set(i + 1); 298 // return true; 299 // } 300 // } 301 // 302 // Asset idmap = null; 303 // if ((idmap = openAssetFromFileLocked(idmapPath, Asset.AccessMode.ACCESS_BUFFER)) == null) { 304 // ALOGW("failed to open idmap file %s\n", idmapPath.string()); 305 // return false; 306 // } 307 // 308 // String8 targetPath; 309 // String8 overlayPath; 310 // if (!ResTable.getIdmapInfo(idmap.getBuffer(false), idmap.getLength(), 311 // null, null, null, & targetPath, &overlayPath)){ 312 // ALOGW("failed to read idmap file %s\n", idmapPath.string()); 313 // // delete idmap; 314 // return false; 315 // } 316 // // delete idmap; 317 // 318 // if (overlayPath != packagePath) { 319 // ALOGW("idmap file %s inconcistent: expected path %s does not match actual path %s\n", 320 // idmapPath.string(), packagePath.string(), overlayPath.string()); 321 // return false; 322 // } 323 // if (access(targetPath.string(), R_OK) != 0) { 324 // ALOGW("failed to access file %s: %s\n", targetPath.string(), strerror(errno)); 325 // return false; 326 // } 327 // if (access(idmapPath.string(), R_OK) != 0) { 328 // ALOGW("failed to access file %s: %s\n", idmapPath.string(), strerror(errno)); 329 // return false; 330 // } 331 // if (access(overlayPath.string(), R_OK) != 0) { 332 // ALOGW("failed to access file %s: %s\n", overlayPath.string(), strerror(errno)); 333 // return false; 334 // } 335 // 336 // asset_path oap; 337 // oap.path = overlayPath; 338 // oap.type = .getFileType(overlayPath.string()); 339 // oap.idmap = idmapPath; 340 // #if 0 341 // ALOGD("Overlay added: targetPath=%s overlayPath=%s idmapPath=%s\n", 342 // targetPath.string(), overlayPath.string(), idmapPath.string()); 343 // #endif 344 // mAssetPaths.add(oap); 345 // *cookie = static_cast <int>(mAssetPaths.size()); 346 // 347 // if (mResources != null) { 348 // appendPathToResTable(oap); 349 // } 350 // 351 // return true; 352 // } 353 // } 354 // 355 // boolean createIdmap(final char* targetApkPath, final char* overlayApkPath, 356 // uint32_t targetCrc, uint32_t overlayCrc, uint32_t** outData, int* outSize) 357 // { 358 // AutoMutex _l(mLock); 359 // final String8 paths[2] = { String8(targetApkPath), String8(overlayApkPath) }; 360 // Asset* assets[2] = {null, null}; 361 // boolean ret = false; 362 // { 363 // ResTable tables[2]; 364 // 365 // for (int i = 0; i < 2; ++i) { 366 // asset_path ap; 367 // ap.type = kFileTypeRegular; 368 // ap.path = paths[i]; 369 // assets[i] = openNonAssetInPathLocked("resources.arsc", 370 // Asset.ACCESS_BUFFER, ap); 371 // if (assets[i] == null) { 372 // ALOGW("failed to find resources.arsc in %s\n", ap.path.string()); 373 // goto exit; 374 // } 375 // if (tables[i].add(assets[i]) != NO_ERROR) { 376 // ALOGW("failed to add %s to resource table", paths[i].string()); 377 // goto exit; 378 // } 379 // } 380 // ret = tables[0].createIdmap(tables[1], targetCrc, overlayCrc, 381 // targetApkPath, overlayApkPath, (void**)outData, outSize) == NO_ERROR; 382 // } 383 // 384 // exit: 385 // delete assets[0]; 386 // delete assets[1]; 387 // return ret; 388 // } 389 // addDefaultAssets(String systemAssetsPath)390 public boolean addDefaultAssets(String systemAssetsPath) { 391 String8 path = new String8(systemAssetsPath); 392 return addAssetPath(path, null, false /* appAsLib */, true /* isSystemAsset */); 393 } 394 // 395 // int nextAssetPath(final int cookie) final 396 // { 397 // AutoMutex _l(mLock); 398 // final int next = static_cast<int>(cookie) + 1; 399 // return next > mAssetPaths.size() ? -1 : next; 400 // } 401 // 402 // String8 getAssetPath(final int cookie) final 403 // { 404 // AutoMutex _l(mLock); 405 // final int which = static_cast<int>(cookie) - 1; 406 // if (which < mAssetPaths.size()) { 407 // return mAssetPaths[which].path; 408 // } 409 // return String8(); 410 // } 411 setLocaleLocked(final String locale)412 void setLocaleLocked(final String locale) { 413 // if (mLocale != null) { 414 // delete[] mLocale; 415 // } 416 417 mLocale = /*strdupNew*/(locale); 418 updateResourceParamsLocked(); 419 } 420 setConfiguration(final ResTable_config config, final String locale)421 public void setConfiguration(final ResTable_config config, final String locale) { 422 synchronized (mLock) { 423 mConfig = config; 424 if (isTruthy(locale)) { 425 setLocaleLocked(locale); 426 } else { 427 if (config.language[0] != 0) { 428 // byte[] spec = new byte[RESTABLE_MAX_LOCALE_LEN]; 429 String spec = config.getBcp47Locale(false); 430 setLocaleLocked(spec); 431 } else { 432 updateResourceParamsLocked(); 433 } 434 } 435 } 436 } 437 438 @VisibleForTesting getConfiguration(Ref<ResTable_config> outConfig)439 public void getConfiguration(Ref<ResTable_config> outConfig) { 440 synchronized (mLock) { 441 outConfig.set(mConfig); 442 } 443 } 444 445 /* 446 * Open an asset. 447 * 448 * The data could be in any asset path. Each asset path could be: 449 * - A directory on disk. 450 * - A Zip archive, uncompressed or compressed. 451 * 452 * If the file is in a directory, it could have a .gz suffix, meaning it is compressed. 453 * 454 * We should probably reject requests for "illegal" filenames, e.g. those 455 * with illegal characters or "../" backward relative paths. 456 */ open(final String fileName, AccessMode mode)457 public Asset open(final String fileName, AccessMode mode) { 458 synchronized (mLock) { 459 LOG_FATAL_IF(mAssetPaths.isEmpty(), "No assets added to AssetManager"); 460 461 String8 assetName = new String8(kAssetsRoot); 462 assetName.appendPath(fileName); 463 /* 464 * For each top-level asset path, search for the asset. 465 */ 466 int i = mAssetPaths.size(); 467 while (i > 0) { 468 i--; 469 ALOGV("Looking for asset '%s' in '%s'\n", 470 assetName.string(), mAssetPaths.get(i).path.string()); 471 Asset pAsset = openNonAssetInPathLocked(assetName.string(), mode, 472 mAssetPaths.get(i)); 473 if (pAsset != null) { 474 return Objects.equals(pAsset, kExcludedAsset) ? null : pAsset; 475 } 476 } 477 478 return null; 479 } 480 } 481 482 /* 483 * Open a non-asset file as if it were an asset. 484 * 485 * The "fileName" is the partial path starting from the application name. 486 */ openNonAsset(final String fileName, AccessMode mode, Ref<Integer> outCookie)487 public Asset openNonAsset(final String fileName, AccessMode mode, Ref<Integer> outCookie) { 488 synchronized (mLock) { 489 // AutoMutex _l(mLock); 490 491 LOG_FATAL_IF(mAssetPaths.isEmpty(), "No assets added to AssetManager"); 492 493 /* 494 * For each top-level asset path, search for the asset. 495 */ 496 497 int i = mAssetPaths.size(); 498 while (i > 0) { 499 i--; 500 ALOGV("Looking for non-asset '%s' in '%s'\n", fileName, 501 mAssetPaths.get(i).path.string()); 502 Asset pAsset = openNonAssetInPathLocked( 503 fileName, mode, mAssetPaths.get(i)); 504 if (pAsset != null) { 505 if (outCookie != null) { 506 outCookie.set(i + 1); 507 } 508 return pAsset != kExcludedAsset ? pAsset : null; 509 } 510 } 511 512 return null; 513 } 514 } 515 openNonAsset(final int cookie, final String fileName, AccessMode mode)516 public Asset openNonAsset(final int cookie, final String fileName, AccessMode mode) { 517 final int which = cookie - 1; 518 519 synchronized (mLock) { 520 LOG_FATAL_IF(mAssetPaths.isEmpty(), "No assets added to AssetManager"); 521 522 if (which < mAssetPaths.size()) { 523 ALOGV("Looking for non-asset '%s' in '%s'\n", fileName, 524 mAssetPaths.get(which).path.string()); 525 Asset pAsset = openNonAssetInPathLocked( 526 fileName, mode, mAssetPaths.get(which)); 527 if (pAsset != null) { 528 return pAsset != kExcludedAsset ? pAsset : null; 529 } 530 } 531 532 return null; 533 } 534 } 535 536 /* 537 * Get the type of a file 538 */ getFileType(final String fileName)539 FileType getFileType(final String fileName) { 540 // deviate from Android CPP implementation here. Assume fileName is a complete path 541 // rather than limited to just asset namespace 542 File assetFile = new File(fileName); 543 if (!assetFile.exists()) { 544 return FileType.kFileTypeNonexistent; 545 } else if (assetFile.isFile()) { 546 return FileType.kFileTypeRegular; 547 } else if (assetFile.isDirectory()) { 548 return kFileTypeDirectory; 549 } 550 return FileType.kFileTypeNonexistent; 551 // Asset pAsset = null; 552 // 553 // /* 554 // * Open the asset. This is less efficient than simply finding the 555 // * file, but it's not too bad (we don't uncompress or mmap data until 556 // * the first read() call). 557 // */ 558 // pAsset = open(fileName, Asset.AccessMode.ACCESS_STREAMING); 559 // // delete pAsset; 560 // 561 // if (pAsset == null) { 562 // return FileType.kFileTypeNonexistent; 563 // } else { 564 // return FileType.kFileTypeRegular; 565 // } 566 } 567 appendPathToResTable(final asset_path ap, boolean appAsLib)568 boolean appendPathToResTable(final asset_path ap, boolean appAsLib) { 569 return PerfStatsCollector.getInstance() 570 .measure( 571 "load binary " + (ap.isSystemAsset ? "framework" : "app") + " resources", 572 () -> appendPathToResTable_measured(ap, appAsLib)); 573 } 574 appendPathToResTable_measured(final asset_path ap, boolean appAsLib)575 boolean appendPathToResTable_measured(final asset_path ap, boolean appAsLib) { 576 // TODO: properly handle reading system resources 577 // if (!ap.isSystemAsset) { 578 // URL resource = getClass().getResource("/resources.ap_"); // todo get this from asset_path 579 // // System.out.println("Reading ARSC file from " + resource); 580 // LOG_FATAL_IF(resource == null, "Could not find resources.ap_"); 581 // try { 582 // ZipFile zipFile = new ZipFile(resource.getFile()); 583 // ZipEntry arscEntry = zipFile.getEntry("resources.arsc"); 584 // InputStream inputStream = zipFile.getInputStream(arscEntry); 585 // mResources.add(inputStream, mResources.getTableCount() + 1); 586 // } catch (IOException e) { 587 // throw new RuntimeException(e); 588 // } 589 // } else { 590 // try { 591 // ZipFile zipFile = new ZipFile(ap.path.string()); 592 // ZipEntry arscEntry = zipFile.getEntry("resources.arsc"); 593 // InputStream inputStream = zipFile.getInputStream(arscEntry); 594 // mResources.add(inputStream, mResources.getTableCount() + 1); 595 // } catch (IOException e) { 596 // e.printStackTrace(); 597 // } 598 // } 599 // return false; 600 601 // skip those ap's that correspond to system overlays 602 if (ap.isSystemOverlay) { 603 return true; 604 } 605 606 Asset ass = null; 607 ResTable sharedRes = null; 608 boolean shared = true; 609 boolean onlyEmptyResources = true; 610 // ATRACE_NAME(ap.path.string()); 611 Asset idmap = openIdmapLocked(ap); 612 int nextEntryIdx = mResources.getTableCount(); 613 ALOGV("Looking for resource asset in '%s'\n", ap.path.string()); 614 if (ap.type != kFileTypeDirectory /*&& ap.rawFd < 0*/) { 615 if (nextEntryIdx == 0) { 616 // The first item is typically the framework resources, 617 // which we want to avoid parsing every time. 618 sharedRes = mZipSet.getZipResourceTable(ap.path); 619 if (sharedRes != null) { 620 // skip ahead the number of system overlay packages preloaded 621 nextEntryIdx = sharedRes.getTableCount(); 622 } 623 } 624 if (sharedRes == null) { 625 ass = mZipSet.getZipResourceTableAsset(ap.path); 626 if (ass == null) { 627 ALOGV("loading resource table %s\n", ap.path.string()); 628 ass = openNonAssetInPathLocked("resources.arsc", 629 AccessMode.ACCESS_BUFFER, 630 ap); 631 if (ass != null && ass != kExcludedAsset) { 632 ass = mZipSet.setZipResourceTableAsset(ap.path, ass); 633 } 634 } 635 636 if (nextEntryIdx == 0 && ass != null) { 637 // If this is the first resource table in the asset 638 // manager, then we are going to cache it so that we 639 // can quickly copy it out for others. 640 ALOGV("Creating shared resources for %s", ap.path.string()); 641 sharedRes = new ResTable(); 642 sharedRes.add(ass, idmap, nextEntryIdx + 1, false, false, false); 643 // #ifdef __ANDROID__ 644 // final char* data = getenv("ANDROID_DATA"); 645 // LOG_ALWAYS_FATAL_IF(data == null, "ANDROID_DATA not set"); 646 // String8 overlaysListPath(data); 647 // overlaysListPath.appendPath(kResourceCache); 648 // overlaysListPath.appendPath("overlays.list"); 649 // addSystemOverlays(overlaysListPath.string(), ap.path, sharedRes, nextEntryIdx); 650 // #endif 651 sharedRes = mZipSet.setZipResourceTable(ap.path, sharedRes); 652 } 653 } 654 } else { 655 ALOGV("loading resource table %s\n", ap.path.string()); 656 ass = openNonAssetInPathLocked("resources.arsc", 657 AccessMode.ACCESS_BUFFER, 658 ap); 659 shared = false; 660 } 661 662 if ((ass != null || sharedRes != null) && ass != kExcludedAsset) { 663 ALOGV("Installing resource asset %s in to table %s\n", ass, mResources); 664 if (sharedRes != null) { 665 ALOGV("Copying existing resources for %s", ap.path.string()); 666 mResources.add(sharedRes, ap.isSystemAsset); 667 } else { 668 ALOGV("Parsing resources for %s", ap.path.string()); 669 mResources.add(ass, idmap, nextEntryIdx + 1, !shared, appAsLib, ap.isSystemAsset); 670 } 671 onlyEmptyResources = false; 672 673 // if (!shared) { 674 // delete ass; 675 // } 676 } else { 677 ALOGV("Installing empty resources in to table %s\n", mResources); 678 mResources.addEmpty(nextEntryIdx + 1); 679 } 680 681 // if (idmap != null) { 682 // delete idmap; 683 // } 684 return onlyEmptyResources; 685 } 686 getResTable(boolean required)687 final ResTable getResTable(boolean required) { 688 ResTable rt = mResources; 689 if (isTruthy(rt)) { 690 return rt; 691 } 692 693 // Iterate through all asset packages, collecting resources from each. 694 695 synchronized (mLock) { 696 if (mResources != null) { 697 return mResources; 698 } 699 700 if (required) { 701 LOG_FATAL_IF(mAssetPaths.isEmpty(), "No assets added to AssetManager"); 702 } 703 704 PerfStatsCollector.getInstance().measure("load binary resources", () -> { 705 mResources = new ResTable(); 706 updateResourceParamsLocked(); 707 708 boolean onlyEmptyResources = true; 709 final int N = mAssetPaths.size(); 710 for (int i = 0; i < N; i++) { 711 boolean empty = appendPathToResTable(mAssetPaths.get(i), false); 712 onlyEmptyResources = onlyEmptyResources && empty; 713 } 714 715 if (required && onlyEmptyResources) { 716 ALOGW("Unable to find resources file resources.arsc"); 717 // delete mResources; 718 mResources = null; 719 } 720 }); 721 722 return mResources; 723 } 724 } 725 updateResourceParamsLocked()726 void updateResourceParamsLocked() { 727 ATRACE_CALL(); 728 ResTable res = mResources; 729 if (!isTruthy(res)) { 730 return; 731 } 732 733 if (isTruthy(mLocale)) { 734 mConfig.setBcp47Locale(mLocale); 735 } else { 736 mConfig.clearLocale(); 737 } 738 739 res.setParameters(mConfig); 740 } 741 openIdmapLocked(asset_path ap)742 Asset openIdmapLocked(asset_path ap) { 743 Asset ass = null; 744 if (ap.idmap.length() != 0) { 745 ass = openAssetFromFileLocked(ap.idmap, AccessMode.ACCESS_BUFFER); 746 if (isTruthy(ass)) { 747 ALOGV("loading idmap %s\n", ap.idmap.string()); 748 } else { 749 ALOGW("failed to load idmap %s\n", ap.idmap.string()); 750 } 751 } 752 return ass; 753 } 754 755 // void addSystemOverlays(final char* pathOverlaysList, 756 // final String8& targetPackagePath, ResTable* sharedRes, int offset) final 757 // { 758 // FILE* fin = fopen(pathOverlaysList, "r"); 759 // if (fin == null) { 760 // return; 761 // } 762 // 763 // #ifndef _WIN32 764 // if (TEMP_FAILURE_RETRY(flock(fileno(fin), LOCK_SH)) != 0) { 765 // fclose(fin); 766 // return; 767 // } 768 // #endif 769 // char buf[1024]; 770 // while (fgets(buf, sizeof(buf), fin)) { 771 // // format of each line: 772 // // <path to apk><space><path to idmap><newline> 773 // char* space = strchr(buf, ' '); 774 // char* newline = strchr(buf, '\n'); 775 // asset_path oap; 776 // 777 // if (space == null || newline == null || newline < space) { 778 // continue; 779 // } 780 // 781 // oap.path = String8(buf, space - buf); 782 // oap.type = kFileTypeRegular; 783 // oap.idmap = String8(space + 1, newline - space - 1); 784 // oap.isSystemOverlay = true; 785 // 786 // Asset* oass = final_cast<AssetManager*>(this). 787 // openNonAssetInPathLocked("resources.arsc", 788 // Asset.ACCESS_BUFFER, 789 // oap); 790 // 791 // if (oass != null) { 792 // Asset* oidmap = openIdmapLocked(oap); 793 // offset++; 794 // sharedRes.add(oass, oidmap, offset + 1, false); 795 // final_cast<AssetManager*>(this).mAssetPaths.add(oap); 796 // final_cast<AssetManager*>(this).mZipSet.addOverlay(targetPackagePath, oap); 797 // delete oidmap; 798 // } 799 // } 800 // 801 // #ifndef _WIN32 802 // TEMP_FAILURE_RETRY(flock(fileno(fin), LOCK_UN)); 803 // #endif 804 // fclose(fin); 805 // } 806 getResources()807 public final ResTable getResources() { 808 return getResources(true); 809 } 810 getResources(boolean required)811 final ResTable getResources(boolean required) { 812 final ResTable rt = getResTable(required); 813 return rt; 814 } 815 816 // boolean isUpToDate() 817 // { 818 // AutoMutex _l(mLock); 819 // return mZipSet.isUpToDate(); 820 // } 821 // 822 // void getLocales(Vector<String8>* locales, boolean includeSystemLocales) final 823 // { 824 // ResTable* res = mResources; 825 // if (res != null) { 826 // res.getLocales(locales, includeSystemLocales, true /* mergeEquivalentLangs */); 827 // } 828 // } 829 // 830 /* 831 * Open a non-asset file as if it were an asset, searching for it in the 832 * specified app. 833 * 834 * Pass in a null values for "appName" if the common app directory should 835 * be used. 836 */ openNonAssetInPathLocked(final String fileName, AccessMode mode, final asset_path ap)837 static Asset openNonAssetInPathLocked(final String fileName, AccessMode mode, 838 final asset_path ap) { 839 Asset pAsset = null; 840 841 /* look at the filesystem on disk */ 842 if (ap.type == kFileTypeDirectory) { 843 String8 path = new String8(ap.path); 844 path.appendPath(fileName); 845 846 pAsset = openAssetFromFileLocked(path, mode); 847 848 if (pAsset == null) { 849 /* try again, this time with ".gz" */ 850 path.append(".gz"); 851 pAsset = openAssetFromFileLocked(path, mode); 852 } 853 854 if (pAsset != null) { 855 //printf("FOUND NA '%s' on disk\n", fileName); 856 pAsset.setAssetSource(path); 857 } 858 859 /* look inside the zip file */ 860 } else { 861 String8 path = new String8(fileName); 862 863 /* check the appropriate Zip file */ 864 ZipFileRO pZip = getZipFileLocked(ap); 865 if (pZip != null) { 866 //printf("GOT zip, checking NA '%s'\n", (final char*) path); 867 ZipEntryRO entry = pZip.findEntryByName(path.string()); 868 if (entry != null) { 869 //printf("FOUND NA in Zip file for %s\n", appName ? appName : kAppCommon); 870 pAsset = openAssetFromZipLocked(pZip, entry, mode, path); 871 pZip.releaseEntry(entry); 872 } 873 } 874 875 if (pAsset != null) { 876 /* create a "source" name, for debug/display */ 877 pAsset.setAssetSource( 878 createZipSourceNameLocked(ap.path, new String8(), new String8(fileName))); 879 } 880 } 881 882 return pAsset; 883 } 884 885 /* 886 * Create a "source name" for a file from a Zip archive. 887 */ createZipSourceNameLocked(final String8 zipFileName, final String8 dirName, final String8 fileName)888 static String8 createZipSourceNameLocked(final String8 zipFileName, 889 final String8 dirName, final String8 fileName) { 890 String8 sourceName = new String8("zip:"); 891 sourceName.append(zipFileName.string()); 892 sourceName.append(":"); 893 if (dirName.length() > 0) { 894 sourceName.appendPath(dirName.string()); 895 } 896 sourceName.appendPath(fileName.string()); 897 return sourceName; 898 } 899 900 /* 901 * Create a path to a loose asset (asset-base/app/rootDir). 902 */ createPathNameLocked(final asset_path ap, final String rootDir)903 static String8 createPathNameLocked(final asset_path ap, final String rootDir) { 904 String8 path = new String8(ap.path); 905 if (rootDir != null) { 906 path.appendPath(rootDir); 907 } 908 return path; 909 } 910 911 /* 912 * Return a pointer to one of our open Zip archives. Returns null if no 913 * matching Zip file exists. 914 */ getZipFileLocked(final asset_path ap)915 static ZipFileRO getZipFileLocked(final asset_path ap) { 916 ALOGV("getZipFileLocked() in %s\n", CppAssetManager.class); 917 918 return mZipSet.getZip(ap.path.string()); 919 } 920 921 /* 922 * Try to open an asset from a file on disk. 923 * 924 * If the file is compressed with gzip, we seek to the start of the 925 * deflated data and pass that in (just like we would for a Zip archive). 926 * 927 * For uncompressed data, we may already have an mmap()ed version sitting 928 * around. If so, we want to hand that to the Asset instead. 929 * 930 * This returns null if the file doesn't exist, couldn't be opened, or 931 * claims to be a ".gz" but isn't. 932 */ openAssetFromFileLocked(final String8 pathName, AccessMode mode)933 static Asset openAssetFromFileLocked(final String8 pathName, 934 AccessMode mode) { 935 Asset pAsset = null; 936 937 if (pathName.getPathExtension().toLowerCase().equals(".gz")) { 938 //printf("TRYING '%s'\n", (final char*) pathName); 939 pAsset = Asset.createFromCompressedFile(pathName.string(), mode); 940 } else { 941 //printf("TRYING '%s'\n", (final char*) pathName); 942 pAsset = Asset.createFromFile(pathName.string(), mode); 943 } 944 945 return pAsset; 946 } 947 948 /* 949 * Given an entry in a Zip archive, create a new Asset object. 950 * 951 * If the entry is uncompressed, we may want to create or share a 952 * slice of shared memory. 953 */ openAssetFromZipLocked(final ZipFileRO pZipFile, final ZipEntryRO entry, AccessMode mode, final String8 entryName)954 static Asset openAssetFromZipLocked(final ZipFileRO pZipFile, 955 final ZipEntryRO entry, AccessMode mode, final String8 entryName) { 956 Asset pAsset = null; 957 958 // TODO: look for previously-created shared memory slice? 959 final Ref<Short> method = new Ref<>((short) 0); 960 final Ref<Long> uncompressedLen = new Ref<>(0L); 961 962 //printf("USING Zip '%s'\n", pEntry.getFileName()); 963 964 if (!pZipFile.getEntryInfo(entry, method, uncompressedLen, null, null, 965 null, null)) { 966 ALOGW("getEntryInfo failed\n"); 967 return null; 968 } 969 970 //return Asset.createFromZipEntry(pZipFile, entry, entryName); 971 FileMap dataMap = pZipFile.createEntryFileMap(entry); 972 // if (dataMap == null) { 973 // ALOGW("create map from entry failed\n"); 974 // return null; 975 // } 976 // 977 if (method.get() == ZipFileRO.kCompressStored) { 978 pAsset = Asset.createFromUncompressedMap(dataMap, mode); 979 ALOGV("Opened uncompressed entry %s in zip %s mode %s: %s", entryName.string(), 980 pZipFile.mFileName, mode, pAsset); 981 } else { 982 pAsset = Asset.createFromCompressedMap(dataMap, toIntExact(uncompressedLen.get()), mode); 983 ALOGV("Opened compressed entry %s in zip %s mode %s: %s", entryName.string(), 984 pZipFile.mFileName, mode, pAsset); 985 } 986 if (pAsset == null) { 987 /* unexpected */ 988 ALOGW("create from segment failed\n"); 989 } 990 991 return pAsset; 992 } 993 994 /* 995 * Open a directory in the asset namespace. 996 * 997 * An "asset directory" is simply the combination of all asset paths' "assets/" directories. 998 * 999 * Pass in "" for the root dir. 1000 */ openDir(final String dirName)1001 public AssetDir openDir(final String dirName) { 1002 synchronized (mLock) { 1003 1004 AssetDir pDir = null; 1005 final Ref<SortedVector<AssetDir.FileInfo>> pMergedInfo; 1006 1007 LOG_FATAL_IF(mAssetPaths.isEmpty(), "No assets added to AssetManager"); 1008 Preconditions.checkNotNull(dirName); 1009 1010 //printf("+++ openDir(%s) in '%s'\n", dirName, (final char*) mAssetBase); 1011 1012 pDir = new AssetDir(); 1013 1014 /* 1015 * Scan the various directories, merging what we find into a single 1016 * vector. We want to scan them in reverse priority order so that 1017 * the ".EXCLUDE" processing works correctly. Also, if we decide we 1018 * want to remember where the file is coming from, we'll get the right 1019 * version. 1020 * 1021 * We start with Zip archives, then do loose files. 1022 */ 1023 pMergedInfo = new Ref<>(new SortedVector<AssetDir.FileInfo>()); 1024 1025 int i = mAssetPaths.size(); 1026 while (i > 0) { 1027 i--; 1028 final asset_path ap = mAssetPaths.get(i); 1029 if (ap.type == FileType.kFileTypeRegular) { 1030 ALOGV("Adding directory %s from zip %s", dirName, ap.path.string()); 1031 scanAndMergeZipLocked(pMergedInfo, ap, kAssetsRoot, dirName); 1032 } else { 1033 ALOGV("Adding directory %s from dir %s", dirName, ap.path.string()); 1034 scanAndMergeDirLocked(pMergedInfo, ap, kAssetsRoot, dirName); 1035 } 1036 } 1037 1038 // #if 0 1039 // printf("FILE LIST:\n"); 1040 // for (i = 0; i < (int) pMergedInfo.size(); i++) { 1041 // printf(" %d: (%d) '%s'\n", i, 1042 // pMergedInfo.itemAt(i).getFileType(), 1043 // ( final char*)pMergedInfo.itemAt(i).getFileName()); 1044 // } 1045 // #endif 1046 1047 pDir.setFileList(pMergedInfo.get()); 1048 return pDir; 1049 } 1050 } 1051 1052 // 1053 // /* 1054 // * Open a directory in the non-asset namespace. 1055 // * 1056 // * An "asset directory" is simply the combination of all asset paths' "assets/" directories. 1057 // * 1058 // * Pass in "" for the root dir. 1059 // */ 1060 // AssetDir* openNonAssetDir(final int cookie, final char* dirName) 1061 // { 1062 // AutoMutex _l(mLock); 1063 // 1064 // AssetDir* pDir = null; 1065 // SortedVector<AssetDir.FileInfo>* pMergedInfo = null; 1066 // 1067 // LOG_FATAL_IF(mAssetPaths.isEmpty(), "No assets added to AssetManager"); 1068 // assert(dirName != null); 1069 // 1070 // //printf("+++ openDir(%s) in '%s'\n", dirName, (final char*) mAssetBase); 1071 // 1072 // pDir = new AssetDir; 1073 // 1074 // pMergedInfo = new SortedVector<AssetDir.FileInfo>; 1075 // 1076 // final int which = static_cast<int>(cookie) - 1; 1077 // 1078 // if (which < mAssetPaths.size()) { 1079 // final asset_path& ap = mAssetPaths.itemAt(which); 1080 // if (ap.type == kFileTypeRegular) { 1081 // ALOGV("Adding directory %s from zip %s", dirName, ap.path.string()); 1082 // scanAndMergeZipLocked(pMergedInfo, ap, null, dirName); 1083 // } else { 1084 // ALOGV("Adding directory %s from dir %s", dirName, ap.path.string()); 1085 // scanAndMergeDirLocked(pMergedInfo, ap, null, dirName); 1086 // } 1087 // } 1088 // 1089 // #if 0 1090 // printf("FILE LIST:\n"); 1091 // for (i = 0; i < (int) pMergedInfo.size(); i++) { 1092 // printf(" %d: (%d) '%s'\n", i, 1093 // pMergedInfo.itemAt(i).getFileType(), 1094 // (final char*) pMergedInfo.itemAt(i).getFileName()); 1095 // } 1096 // #endif 1097 // 1098 // pDir.setFileList(pMergedInfo); 1099 // return pDir; 1100 // } 1101 // 1102 /* 1103 * Scan the contents of the specified directory and merge them into the 1104 * "pMergedInfo" vector, removing previous entries if we find "exclude" 1105 * directives. 1106 * 1107 * Returns "false" if we found nothing to contribute. 1108 */ scanAndMergeDirLocked(Ref<SortedVector<AssetDir.FileInfo>> pMergedInfoRef, final asset_path ap, final String rootDir, final String dirName)1109 boolean scanAndMergeDirLocked(Ref<SortedVector<AssetDir.FileInfo>> pMergedInfoRef, 1110 final asset_path ap, final String rootDir, final String dirName) { 1111 SortedVector<AssetDir.FileInfo> pMergedInfo = pMergedInfoRef.get(); 1112 assert (pMergedInfo != null); 1113 1114 //printf("scanAndMergeDir: %s %s %s\n", ap.path.string(), rootDir, dirName); 1115 1116 String8 path = createPathNameLocked(ap, rootDir); 1117 if (dirName.charAt(0) != '\0') { 1118 path.appendPath(dirName); 1119 } 1120 1121 SortedVector<AssetDir.FileInfo> pContents = scanDirLocked(path); 1122 if (pContents == null) { 1123 return false; 1124 } 1125 1126 // if we wanted to do an incremental cache fill, we would do it here 1127 1128 /* 1129 * Process "exclude" directives. If we find a filename that ends with 1130 * ".EXCLUDE", we look for a matching entry in the "merged" set, and 1131 * remove it if we find it. We also delete the "exclude" entry. 1132 */ 1133 int i, count, exclExtLen; 1134 1135 count = pContents.size(); 1136 exclExtLen = kExcludeExtension.length(); 1137 for (i = 0; i < count; i++) { 1138 final String name; 1139 int nameLen; 1140 1141 name = pContents.itemAt(i).getFileName().string(); 1142 nameLen = name.length(); 1143 if (name.endsWith(kExcludeExtension)) { 1144 String8 match = new String8(name, nameLen - exclExtLen); 1145 int matchIdx; 1146 1147 matchIdx = AssetDir.FileInfo.findEntry(pMergedInfo, match); 1148 if (matchIdx > 0) { 1149 ALOGV("Excluding '%s' [%s]\n", 1150 pMergedInfo.itemAt(matchIdx).getFileName().string(), 1151 pMergedInfo.itemAt(matchIdx).getSourceName().string()); 1152 pMergedInfo.removeAt(matchIdx); 1153 } else { 1154 //printf("+++ no match on '%s'\n", (final char*) match); 1155 } 1156 1157 ALOGD("HEY: size=%d removing %d\n", (int) pContents.size(), i); 1158 pContents.removeAt(i); 1159 i--; // adjust "for" loop 1160 count--; // and loop limit 1161 } 1162 } 1163 1164 mergeInfoLocked(pMergedInfoRef, pContents); 1165 1166 return true; 1167 } 1168 1169 /* 1170 * Scan the contents of the specified directory, and stuff what we find 1171 * into a newly-allocated vector. 1172 * 1173 * Files ending in ".gz" will have their extensions removed. 1174 * 1175 * We should probably think about skipping files with "illegal" names, 1176 * e.g. illegal characters (/\:) or excessive length. 1177 * 1178 * Returns null if the specified directory doesn't exist. 1179 */ scanDirLocked(final String8 path)1180 SortedVector<AssetDir.FileInfo> scanDirLocked(final String8 path) { 1181 1182 String8 pathCopy = new String8(path); 1183 SortedVector<AssetDir.FileInfo> pContents = null; 1184 //DIR* dir; 1185 File dir; 1186 FileType fileType; 1187 1188 ALOGV("Scanning dir '%s'\n", path.string()); 1189 1190 dir = new File(path.string()); 1191 if (!dir.exists()) { 1192 return null; 1193 } 1194 1195 pContents = new SortedVector<>(); 1196 1197 for (File entry : dir.listFiles()) { 1198 if (entry == null) { 1199 break; 1200 } 1201 1202 // if (strcmp(entry.d_name, ".") == 0 || 1203 // strcmp(entry.d_name, "..") == 0) 1204 // continue; 1205 1206 // #ifdef _DIRENT_HAVE_D_TYPE 1207 // if (entry.d_type == DT_REG) 1208 // fileType = kFileTypeRegular; 1209 // else if (entry.d_type == DT_DIR) 1210 // fileType = kFileTypeDirectory; 1211 // else 1212 // fileType = kFileTypeUnknown; 1213 // #else 1214 // stat the file 1215 fileType = getFileType(pathCopy.appendPath(entry.getName()).string()); 1216 // #endif 1217 1218 if (fileType != FileType.kFileTypeRegular && fileType != kFileTypeDirectory) { 1219 continue; 1220 } 1221 1222 AssetDir.FileInfo info = new AssetDir.FileInfo(); 1223 info.set(new String8(entry.getName()), fileType); 1224 if (info.getFileName().getPathExtension().equalsIgnoreCase(".gz")) { 1225 info.setFileName(info.getFileName().getBasePath()); 1226 } 1227 info.setSourceName(pathCopy.appendPath(info.getFileName().string())); 1228 pContents.add(info); 1229 } 1230 1231 return pContents; 1232 } 1233 1234 /* 1235 * Scan the contents out of the specified Zip archive, and merge what we 1236 * find into "pMergedInfo". If the Zip archive in question doesn't exist, 1237 * we return immediately. 1238 * 1239 * Returns "false" if we found nothing to contribute. 1240 */ scanAndMergeZipLocked(Ref<SortedVector<AssetDir.FileInfo>> pMergedInfo, final asset_path ap, final String rootDir, final String baseDirName)1241 boolean scanAndMergeZipLocked(Ref<SortedVector<AssetDir.FileInfo>> pMergedInfo, 1242 final asset_path ap, final String rootDir, final String baseDirName) { 1243 ZipFileRO pZip; 1244 List<String8> dirs = new ArrayList<>(); 1245 //AssetDir.FileInfo info = new FileInfo(); 1246 SortedVector<AssetDir.FileInfo> contents = new SortedVector<>(); 1247 String8 sourceName; 1248 String8 zipName; 1249 String8 dirName = new String8(); 1250 1251 pZip = mZipSet.getZip(ap.path.string()); 1252 if (pZip == null) { 1253 ALOGW("Failure opening zip %s\n", ap.path.string()); 1254 return false; 1255 } 1256 1257 zipName = ZipSet.getPathName(ap.path.string()); 1258 1259 /* convert "sounds" to "rootDir/sounds" */ 1260 if (rootDir != null) { 1261 dirName = new String8(rootDir); 1262 } 1263 1264 dirName.appendPath(baseDirName); 1265 1266 /* 1267 * Scan through the list of files, looking for a match. The files in 1268 * the Zip table of contents are not in sorted order, so we have to 1269 * process the entire list. We're looking for a string that begins 1270 * with the characters in "dirName", is followed by a '/', and has no 1271 * subsequent '/' in the stuff that follows. 1272 * 1273 * What makes this especially fun is that directories are not stored 1274 * explicitly in Zip archives, so we have to infer them from context. 1275 * When we see "sounds/foo.wav" we have to leave a note to ourselves 1276 * to insert a directory called "sounds" into the list. We store 1277 * these in temporary vector so that we only return each one once. 1278 * 1279 * Name comparisons are case-sensitive to match UNIX filesystem 1280 * semantics. 1281 */ 1282 int dirNameLen = dirName.length(); 1283 final Ref<Enumeration<? extends ZipEntry>> iterationCookie = new Ref<>(null); 1284 if (!pZip.startIteration(iterationCookie, dirName.string(), null)) { 1285 ALOGW("ZipFileRO.startIteration returned false"); 1286 return false; 1287 } 1288 1289 ZipEntryRO entry; 1290 while ((entry = pZip.nextEntry(iterationCookie.get())) != null) { 1291 1292 final Ref<String> nameBuf = new Ref<>(null); 1293 1294 if (pZip.getEntryFileName(entry, nameBuf) != 0) { 1295 // TODO: fix this if we expect to have long names 1296 ALOGE("ARGH: name too long?\n"); 1297 continue; 1298 } 1299 1300 // System.out.printf("Comparing %s in %s?\n", nameBuf.get(), dirName.string()); 1301 if (!nameBuf.get().startsWith(dirName.string() + '/')) { 1302 // not matching 1303 continue; 1304 } 1305 if (dirNameLen == 0 || nameBuf.get().charAt(dirNameLen) == '/') { 1306 int cp = 0; 1307 int nextSlashIndex; 1308 1309 //cp = nameBuf + dirNameLen; 1310 cp += dirNameLen; 1311 if (dirNameLen != 0) { 1312 cp++; // advance past the '/' 1313 } 1314 1315 nextSlashIndex = nameBuf.get().indexOf('/', cp); 1316 //xxx this may break if there are bare directory entries 1317 if (nextSlashIndex == -1) { 1318 /* this is a file in the requested directory */ 1319 String8 fileName = new String8(nameBuf.get()).getPathLeaf(); 1320 if (fileName.string().isEmpty()) { 1321 // ignore 1322 continue; 1323 } 1324 AssetDir.FileInfo info = new FileInfo(); 1325 info.set(fileName, FileType.kFileTypeRegular); 1326 1327 info.setSourceName( 1328 createZipSourceNameLocked(zipName, dirName, info.getFileName())); 1329 1330 contents.add(info); 1331 //printf("FOUND: file '%s'\n", info.getFileName().string()); 1332 } else { 1333 /* this is a subdir; add it if we don't already have it*/ 1334 String8 subdirName = new String8(nameBuf.get().substring(cp, nextSlashIndex)); 1335 int j; 1336 int N = dirs.size(); 1337 1338 for (j = 0; j < N; j++) { 1339 if (subdirName.equals(dirs.get(j))) { 1340 break; 1341 } 1342 } 1343 if (j == N) { 1344 dirs.add(subdirName); 1345 } 1346 1347 //printf("FOUND: dir '%s'\n", subdirName.string()); 1348 } 1349 } 1350 } 1351 1352 pZip.endIteration(iterationCookie); 1353 1354 /* 1355 * Add the set of unique directories. 1356 */ 1357 for (int i = 0; i < dirs.size(); i++) { 1358 AssetDir.FileInfo info = new FileInfo(); 1359 info.set(dirs.get(i), kFileTypeDirectory); 1360 info.setSourceName( 1361 createZipSourceNameLocked(zipName, dirName, info.getFileName())); 1362 contents.add(info); 1363 } 1364 1365 mergeInfoLocked(pMergedInfo, contents); 1366 1367 return true; 1368 1369 } 1370 1371 1372 /* 1373 * Merge two vectors of FileInfo. 1374 * 1375 * The merged contents will be stuffed into *pMergedInfo. 1376 * 1377 * If an entry for a file exists in both "pMergedInfo" and "pContents", 1378 * we use the newer "pContents" entry. 1379 */ mergeInfoLocked(Ref<SortedVector<AssetDir.FileInfo>> pMergedInfoRef, final SortedVector<AssetDir.FileInfo> pContents)1380 void mergeInfoLocked(Ref<SortedVector<AssetDir.FileInfo>> pMergedInfoRef, 1381 final SortedVector<AssetDir.FileInfo> pContents) { 1382 /* 1383 * Merge what we found in this directory with what we found in 1384 * other places. 1385 * 1386 * Two basic approaches: 1387 * (1) Create a new array that holds the unique values of the two 1388 * arrays. 1389 * (2) Take the elements from pContents and shove them into pMergedInfo. 1390 * 1391 * Because these are vectors of complex objects, moving elements around 1392 * inside the vector requires finalructing new objects and allocating 1393 * storage for members. With approach #1, we're always adding to the 1394 * end, whereas with #2 we could be inserting multiple elements at the 1395 * front of the vector. Approach #1 requires a full copy of the 1396 * contents of pMergedInfo, but approach #2 requires the same copy for 1397 * every insertion at the front of pMergedInfo. 1398 * 1399 * (We should probably use a SortedVector interface that allows us to 1400 * just stuff items in, trusting us to maintain the sort order.) 1401 */ 1402 SortedVector<AssetDir.FileInfo> pNewSorted; 1403 int mergeMax, contMax; 1404 int mergeIdx, contIdx; 1405 1406 SortedVector<AssetDir.FileInfo> pMergedInfo = pMergedInfoRef.get(); 1407 pNewSorted = new SortedVector<>(); 1408 mergeMax = pMergedInfo.size(); 1409 contMax = pContents.size(); 1410 mergeIdx = contIdx = 0; 1411 1412 while (mergeIdx < mergeMax || contIdx < contMax) { 1413 if (mergeIdx == mergeMax) { 1414 /* hit end of "merge" list, copy rest of "contents" */ 1415 pNewSorted.add(pContents.itemAt(contIdx)); 1416 contIdx++; 1417 } else if (contIdx == contMax) { 1418 /* hit end of "cont" list, copy rest of "merge" */ 1419 pNewSorted.add(pMergedInfo.itemAt(mergeIdx)); 1420 mergeIdx++; 1421 } else if (pMergedInfo.itemAt(mergeIdx) == pContents.itemAt(contIdx)) { 1422 /* items are identical, add newer and advance both indices */ 1423 pNewSorted.add(pContents.itemAt(contIdx)); 1424 mergeIdx++; 1425 contIdx++; 1426 } else if (pMergedInfo.itemAt(mergeIdx).isLessThan(pContents.itemAt(contIdx))) { 1427 /* "merge" is lower, add that one */ 1428 pNewSorted.add(pMergedInfo.itemAt(mergeIdx)); 1429 mergeIdx++; 1430 } else { 1431 /* "cont" is lower, add that one */ 1432 assert (pContents.itemAt(contIdx).isLessThan(pMergedInfo.itemAt(mergeIdx))); 1433 pNewSorted.add(pContents.itemAt(contIdx)); 1434 contIdx++; 1435 } 1436 } 1437 1438 /* 1439 * Overwrite the "merged" list with the new stuff. 1440 */ 1441 pMergedInfoRef.set(pNewSorted); 1442 1443 // #if 0 // for Vector, rather than SortedVector 1444 // int i, j; 1445 // for (i = pContents.size() -1; i >= 0; i--) { 1446 // boolean add = true; 1447 // 1448 // for (j = pMergedInfo.size() -1; j >= 0; j--) { 1449 // /* case-sensitive comparisons, to behave like UNIX fs */ 1450 // if (strcmp(pContents.itemAt(i).mFileName, 1451 // pMergedInfo.itemAt(j).mFileName) == 0) 1452 // { 1453 // /* match, don't add this entry */ 1454 // add = false; 1455 // break; 1456 // } 1457 // } 1458 // 1459 // if (add) 1460 // pMergedInfo.add(pContents.itemAt(i)); 1461 // } 1462 // #endif 1463 } 1464 1465 /* 1466 * =========================================================================== 1467 * SharedZip 1468 * =========================================================================== 1469 */ 1470 1471 static class SharedZip /*: public RefBase */ { 1472 1473 final String mPath; 1474 final ZipFileRO mZipFile; 1475 final long mModWhen; 1476 1477 Asset mResourceTableAsset; 1478 ResTable mResourceTable; 1479 1480 List<asset_path> mOverlays; 1481 1482 final static Object gLock = new Object(); 1483 final static Map<String8, WeakReference<SharedZip>> gOpen = new HashMap<>(); 1484 SharedZip(String path, long modWhen)1485 public SharedZip(String path, long modWhen) { 1486 this.mPath = path; 1487 this.mModWhen = modWhen; 1488 this.mResourceTableAsset = null; 1489 this.mResourceTable = null; 1490 1491 if (kIsDebug) { 1492 ALOGI("Creating SharedZip %s %s\n", this, mPath); 1493 } 1494 ALOGV("+++ opening zip '%s'\n", mPath); 1495 this.mZipFile = ZipFileRO.open(mPath); 1496 if (mZipFile == null) { 1497 ALOGD("failed to open Zip archive '%s'\n", mPath); 1498 } 1499 } 1500 get(final String8 path)1501 static SharedZip get(final String8 path) { 1502 return get(path, true); 1503 } 1504 get(final String8 path, boolean createIfNotPresent)1505 static SharedZip get(final String8 path, boolean createIfNotPresent) { 1506 synchronized (gLock) { 1507 long modWhen = getFileModDate(path.string()); 1508 WeakReference<SharedZip> ref = gOpen.get(path); 1509 SharedZip zip = ref == null ? null : ref.get(); 1510 if (zip != null && zip.mModWhen == modWhen) { 1511 return zip; 1512 } 1513 if (zip == null && !createIfNotPresent) { 1514 return null; 1515 } 1516 zip = new SharedZip(path.string(), modWhen); 1517 gOpen.put(path, new WeakReference<>(zip)); 1518 return zip; 1519 1520 } 1521 1522 } 1523 getZip()1524 ZipFileRO getZip() { 1525 return mZipFile; 1526 } 1527 getResourceTableAsset()1528 Asset getResourceTableAsset() { 1529 synchronized (gLock) { 1530 ALOGV("Getting from SharedZip %s resource asset %s\n", this, mResourceTableAsset); 1531 return mResourceTableAsset; 1532 } 1533 } 1534 setResourceTableAsset(Asset asset)1535 Asset setResourceTableAsset(Asset asset) { 1536 synchronized (gLock) { 1537 if (mResourceTableAsset == null) { 1538 // This is not thread safe the first time it is called, so 1539 // do it here with the global lock held. 1540 asset.getBuffer(true); 1541 mResourceTableAsset = asset; 1542 return asset; 1543 } 1544 } 1545 return mResourceTableAsset; 1546 } 1547 getResourceTable()1548 ResTable getResourceTable() { 1549 ALOGV("Getting from SharedZip %s resource table %s\n", this, mResourceTable); 1550 return mResourceTable; 1551 } 1552 setResourceTable(ResTable res)1553 ResTable setResourceTable(ResTable res) { 1554 synchronized (gLock) { 1555 if (mResourceTable == null) { 1556 mResourceTable = res; 1557 return res; 1558 } 1559 } 1560 return mResourceTable; 1561 } 1562 1563 // boolean SharedZip.isUpToDate() 1564 // { 1565 // time_t modWhen = getFileModDate(mPath.string()); 1566 // return mModWhen == modWhen; 1567 // } 1568 // 1569 // void SharedZip.addOverlay(final asset_path& ap) 1570 // { 1571 // mOverlays.add(ap); 1572 // } 1573 // 1574 // boolean SharedZip.getOverlay(int idx, asset_path* out) final 1575 // { 1576 // if (idx >= mOverlays.size()) { 1577 // return false; 1578 // } 1579 // *out = mOverlays[idx]; 1580 // return true; 1581 // } 1582 // 1583 // SharedZip.~SharedZip() 1584 // { 1585 // if (kIsDebug) { 1586 // ALOGI("Destroying SharedZip %s %s\n", this, (final char*)mPath); 1587 // } 1588 // if (mResourceTable != null) { 1589 // delete mResourceTable; 1590 // } 1591 // if (mResourceTableAsset != null) { 1592 // delete mResourceTableAsset; 1593 // } 1594 // if (mZipFile != null) { 1595 // delete mZipFile; 1596 // ALOGV("Closed '%s'\n", mPath.string()); 1597 // } 1598 // } 1599 1600 @Override toString()1601 public String toString() { 1602 String id = Integer.toString(System.identityHashCode(this), 16); 1603 return "SharedZip{mPath='" + mPath + "\', id=0x" + id + "}"; 1604 } 1605 } 1606 1607 1608 /* 1609 * Manage a set of Zip files. For each file we need a pointer to the 1610 * ZipFile and a time_t with the file's modification date. 1611 * 1612 * We currently only have two zip files (current app, "common" app). 1613 * (This was originally written for 8, based on app/locale/vendor.) 1614 */ 1615 static class ZipSet { 1616 1617 final List<String> mZipPath = new ArrayList<>(); 1618 final List<SharedZip> mZipFile = new ArrayList<>(); 1619 1620 /* 1621 * =========================================================================== 1622 * ZipSet 1623 * =========================================================================== 1624 */ 1625 1626 /* 1627 * Destructor. Close any open archives. 1628 */ 1629 // ZipSet.~ZipSet(void) 1630 @Override finalize()1631 protected void finalize() { 1632 int N = mZipFile.size(); 1633 for (int i = 0; i < N; i++) { 1634 closeZip(i); 1635 } 1636 } 1637 1638 /* 1639 * Close a Zip file and reset the entry. 1640 */ closeZip(int idx)1641 void closeZip(int idx) { 1642 mZipFile.set(idx, null); 1643 } 1644 1645 1646 /* 1647 * Retrieve the appropriate Zip file from the set. 1648 */ getZip(final String path)1649 synchronized ZipFileRO getZip(final String path) { 1650 int idx = getIndex(path); 1651 SharedZip zip = mZipFile.get(idx); 1652 if (zip == null) { 1653 zip = SharedZip.get(new String8(path)); 1654 mZipFile.set(idx, zip); 1655 } 1656 return zip.getZip(); 1657 } 1658 getZipResourceTableAsset(final String8 path)1659 synchronized Asset getZipResourceTableAsset(final String8 path) { 1660 int idx = getIndex(path.string()); 1661 SharedZip zip = mZipFile.get(idx); 1662 if (zip == null) { 1663 zip = SharedZip.get(path); 1664 mZipFile.set(idx, zip); 1665 } 1666 return zip.getResourceTableAsset(); 1667 } 1668 setZipResourceTableAsset(final String8 path, Asset asset)1669 synchronized Asset setZipResourceTableAsset(final String8 path, Asset asset) { 1670 int idx = getIndex(path.string()); 1671 SharedZip zip = mZipFile.get(idx); 1672 // doesn't make sense to call before previously accessing. 1673 return zip.setResourceTableAsset(asset); 1674 } 1675 getZipResourceTable(final String8 path)1676 synchronized ResTable getZipResourceTable(final String8 path) { 1677 int idx = getIndex(path.string()); 1678 SharedZip zip = mZipFile.get(idx); 1679 if (zip == null) { 1680 zip = SharedZip.get(path); 1681 mZipFile.set(idx, zip); 1682 } 1683 return zip.getResourceTable(); 1684 } 1685 setZipResourceTable(final String8 path, ResTable res)1686 synchronized ResTable setZipResourceTable(final String8 path, ResTable res) { 1687 int idx = getIndex(path.string()); 1688 SharedZip zip = mZipFile.get(idx); 1689 // doesn't make sense to call before previously accessing. 1690 return zip.setResourceTable(res); 1691 } 1692 1693 /* 1694 * Generate the partial pathname for the specified archive. The caller 1695 * gets to prepend the asset root directory. 1696 * 1697 * Returns something like "common/en-US-noogle.jar". 1698 */ getPathName(final String zipPath)1699 static String8 getPathName(final String zipPath) { 1700 return new String8(zipPath); 1701 } 1702 1703 // 1704 // boolean ZipSet.isUpToDate() 1705 // { 1706 // final int N = mZipFile.size(); 1707 // for (int i=0; i<N; i++) { 1708 // if (mZipFile[i] != null && !mZipFile[i].isUpToDate()) { 1709 // return false; 1710 // } 1711 // } 1712 // return true; 1713 // } 1714 // 1715 // void ZipSet.addOverlay(final String8& path, final asset_path& overlay) 1716 // { 1717 // int idx = getIndex(path); 1718 // sp<SharedZip> zip = mZipFile[idx]; 1719 // zip.addOverlay(overlay); 1720 // } 1721 // 1722 // boolean ZipSet.getOverlay(final String8& path, int idx, asset_path* out) final 1723 // { 1724 // sp<SharedZip> zip = SharedZip.get(path, false); 1725 // if (zip == null) { 1726 // return false; 1727 // } 1728 // return zip.getOverlay(idx, out); 1729 // } 1730 // 1731 /* 1732 * Compute the zip file's index. 1733 * 1734 * "appName", "locale", and "vendor" should be set to null to indicate the 1735 * default directory. 1736 */ getIndex(final String zip)1737 int getIndex(final String zip) { 1738 final int N = mZipPath.size(); 1739 for (int i = 0; i < N; i++) { 1740 if (Objects.equals(mZipPath.get(i), zip)) { 1741 return i; 1742 } 1743 } 1744 1745 mZipPath.add(zip); 1746 mZipFile.add(null); 1747 1748 return mZipPath.size() - 1; 1749 } 1750 1751 } 1752 getFileModDate(String path)1753 private static long getFileModDate(String path) { 1754 try { 1755 return Files.getLastModifiedTime(Paths.get(path)).toMillis(); 1756 } catch (IOException e) { 1757 throw new RuntimeException(e); 1758 } 1759 } 1760 getAssetPaths()1761 public List<AssetPath> getAssetPaths() { 1762 synchronized (mLock) { 1763 ArrayList<AssetPath> assetPaths = new ArrayList<>(mAssetPaths.size()); 1764 for (asset_path asset_path : mAssetPaths) { 1765 FsFile fsFile; 1766 switch (asset_path.type) { 1767 case kFileTypeDirectory: 1768 fsFile = Fs.newFile(asset_path.path.string()); 1769 break; 1770 case kFileTypeRegular: 1771 fsFile = Fs.newFile(asset_path.path.string()); 1772 break; 1773 default: 1774 throw new IllegalStateException("Unsupported type " + asset_path.type + " for + " 1775 + asset_path.path.string()); 1776 } 1777 assetPaths.add(new AssetPath(fsFile, asset_path.isSystemAsset)); 1778 } 1779 return assetPaths; 1780 } 1781 } 1782 } 1783