nsJAR.cpp (24047B)
1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */ 3 /* This Source Code Form is subject to the terms of the Mozilla Public 4 * License, v. 2.0. If a copy of the MPL was not distributed with this 5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 6 #include <string.h> 7 #include "nsJARInputStream.h" 8 #include "nsJAR.h" 9 #include "nsIFile.h" 10 #include "nsIObserverService.h" 11 #include "mozilla/DebugOnly.h" 12 #include "mozilla/Logging.h" 13 #include "mozilla/Omnijar.h" 14 15 #ifdef XP_UNIX 16 # include <sys/stat.h> 17 #elif defined(XP_WIN) 18 # include <io.h> 19 #endif 20 21 using namespace mozilla; 22 23 static LazyLogModule gJarLog("nsJAR"); 24 25 #ifdef LOG 26 # undef LOG 27 #endif 28 #ifdef LOG_ENABLED 29 # undef LOG_ENABLED 30 #endif 31 32 #define LOG(args) MOZ_LOG(gJarLog, mozilla::LogLevel::Debug, args) 33 #define LOG_ENABLED() MOZ_LOG_TEST(gJarLog, mozilla::LogLevel::Debug) 34 35 //---------------------------------------------- 36 // nsJAR constructor/destructor 37 //---------------------------------------------- 38 39 // The following initialization makes a guess of 10 entries per jarfile. 40 nsJAR::nsJAR() 41 : mReleaseTime(PR_INTERVAL_NO_TIMEOUT), 42 mLock("nsJAR::mLock"), 43 mCache(nullptr) {} 44 45 nsJAR::~nsJAR() { Close(); } 46 47 NS_IMPL_QUERY_INTERFACE(nsJAR, nsIZipReader) 48 NS_IMPL_ADDREF(nsJAR) 49 50 // Custom Release method works with nsZipReaderCache... 51 // Release might be called from multi-thread, we have to 52 // take this function carefully to avoid delete-after-use. 53 MozExternalRefCountType nsJAR::Release(void) { 54 nsrefcnt count; 55 MOZ_ASSERT(0 != mRefCnt, "dup release"); 56 57 RefPtr<nsZipReaderCache> cache; 58 if (mRefCnt == 2) { // don't use a lock too frequently 59 // Use a mutex here to guarantee mCache is not racing and the target 60 // instance is still valid to increase ref-count. 61 RecursiveMutexAutoLock lock(mLock); 62 cache = mCache; 63 mCache = nullptr; 64 } 65 if (cache) { 66 DebugOnly<nsresult> rv = cache->ReleaseZip(this); 67 MOZ_ASSERT(NS_SUCCEEDED(rv), "failed to release zip file"); 68 } 69 70 count = --mRefCnt; // don't access any member variable after this line 71 NS_LOG_RELEASE(this, count, "nsJAR"); 72 if (0 == count) { 73 mRefCnt = 1; /* stabilize */ 74 /* enable this to find non-threadsafe destructors: */ 75 /* NS_ASSERT_OWNINGTHREAD(nsJAR); */ 76 delete this; 77 return 0; 78 } 79 80 return count; 81 } 82 83 //---------------------------------------------- 84 // nsIZipReader implementation 85 //---------------------------------------------- 86 87 NS_IMETHODIMP 88 nsJAR::Open(nsIFile* zipFile) { 89 NS_ENSURE_ARG_POINTER(zipFile); 90 RecursiveMutexAutoLock lock(mLock); 91 LOG(("Open[%p] %s", this, zipFile->HumanReadablePath().get())); 92 if (mZip) return NS_ERROR_FAILURE; // Already open! 93 94 mZipFile = zipFile; 95 mOuterZipEntry.Truncate(); 96 97 // The omnijar is special, it is opened early on and closed late 98 RefPtr<nsZipArchive> zip = mozilla::Omnijar::GetReader(zipFile); 99 if (!zip) { 100 zip = nsZipArchive::OpenArchive(zipFile); 101 } 102 mZip = zip; 103 return mZip ? NS_OK : NS_ERROR_FAILURE; 104 } 105 106 NS_IMETHODIMP 107 nsJAR::OpenInner(nsIZipReader* aZipReader, const nsACString& aZipEntry) { 108 nsresult rv; 109 110 LOG(("OpenInner[%p] %s", this, PromiseFlatCString(aZipEntry).get())); 111 NS_ENSURE_ARG_POINTER(aZipReader); 112 113 nsCOMPtr<nsIFile> zipFile; 114 rv = aZipReader->GetFile(getter_AddRefs(zipFile)); 115 NS_ENSURE_SUCCESS(rv, rv); 116 117 RefPtr<nsZipArchive> innerZip = 118 mozilla::Omnijar::GetInnerReader(zipFile, aZipEntry); 119 if (innerZip) { 120 RecursiveMutexAutoLock lock(mLock); 121 if (mZip) { 122 return NS_ERROR_FAILURE; 123 } 124 mZip = innerZip; 125 return NS_OK; 126 } 127 128 bool exist; 129 rv = aZipReader->HasEntry(aZipEntry, &exist); 130 NS_ENSURE_SUCCESS(rv, rv); 131 NS_ENSURE_TRUE(exist, NS_ERROR_FILE_NOT_FOUND); 132 133 RefPtr<nsZipHandle> handle; 134 { 135 nsJAR* outerJAR = static_cast<nsJAR*>(aZipReader); 136 RecursiveMutexAutoLock outerLock(outerJAR->mLock); 137 rv = nsZipHandle::Init(outerJAR->mZip.get(), aZipEntry, 138 getter_AddRefs(handle)); 139 NS_ENSURE_SUCCESS(rv, rv); 140 } 141 142 RecursiveMutexAutoLock lock(mLock); 143 MOZ_ASSERT(!mZip, "Another thread tried to open this nsJAR racily!"); 144 mZipFile = zipFile.forget(); 145 mOuterZipEntry.Assign(aZipEntry); 146 mZip = nsZipArchive::OpenArchive(handle); 147 return mZip ? NS_OK : NS_ERROR_FAILURE; 148 } 149 150 NS_IMETHODIMP 151 nsJAR::OpenMemory(void* aData, uint32_t aLength) { 152 NS_ENSURE_ARG_POINTER(aData); 153 RecursiveMutexAutoLock lock(mLock); 154 if (mZip) return NS_ERROR_FAILURE; // Already open! 155 156 RefPtr<nsZipHandle> handle; 157 nsresult rv = nsZipHandle::Init(static_cast<uint8_t*>(aData), aLength, 158 getter_AddRefs(handle)); 159 if (NS_FAILED(rv)) return rv; 160 161 mZip = nsZipArchive::OpenArchive(handle); 162 return mZip ? NS_OK : NS_ERROR_FAILURE; 163 } 164 165 NS_IMETHODIMP 166 nsJAR::GetFile(nsIFile** result) { 167 RecursiveMutexAutoLock lock(mLock); 168 LOG(("GetFile[%p]", this)); 169 *result = mZipFile; 170 NS_IF_ADDREF(*result); 171 return NS_OK; 172 } 173 174 NS_IMETHODIMP 175 nsJAR::Close() { 176 RecursiveMutexAutoLock lock(mLock); 177 LOG(("Close[%p]", this)); 178 if (!mZip) { 179 return NS_ERROR_FAILURE; // Never opened or already closed. 180 } 181 182 mZip = nullptr; 183 return NS_OK; 184 } 185 186 NS_IMETHODIMP 187 nsJAR::Test(const nsACString& aEntryName) { 188 RecursiveMutexAutoLock lock(mLock); 189 if (!mZip) { 190 return NS_ERROR_FAILURE; 191 } 192 return mZip->Test(aEntryName); 193 } 194 195 NS_IMETHODIMP 196 nsJAR::Extract(const nsACString& aEntryName, nsIFile* outFile) { 197 // nsZipArchive and zlib are not thread safe 198 // we need to use a lock to prevent bug #51267 199 RecursiveMutexAutoLock lock(mLock); 200 if (!mZip) { 201 return NS_ERROR_FAILURE; 202 } 203 204 LOG(("Extract[%p] %s", this, PromiseFlatCString(aEntryName).get())); 205 nsZipItem* item = mZip->GetItem(aEntryName); 206 NS_ENSURE_TRUE(item, NS_ERROR_FILE_NOT_FOUND); 207 208 // Remove existing file or directory so we set permissions correctly. 209 // If it's a directory that already exists and contains files, throw 210 // an exception and return. 211 212 nsresult rv = outFile->Remove(false); 213 if (rv == NS_ERROR_FILE_DIR_NOT_EMPTY || rv == NS_ERROR_FAILURE) return rv; 214 215 if (item->IsDirectory()) { 216 rv = outFile->Create(nsIFile::DIRECTORY_TYPE, item->Mode()); 217 // XXX Do this in nsZipArchive? It would be nice to keep extraction 218 // XXX code completely there, but that would require a way to get a 219 // XXX PRDir from outFile. 220 } else { 221 PRFileDesc* fd; 222 rv = outFile->OpenNSPRFileDesc(PR_WRONLY | PR_CREATE_FILE, item->Mode(), 223 &fd); 224 if (NS_FAILED(rv)) return rv; 225 226 // ExtractFile also closes the fd handle and resolves the symlink if needed 227 rv = mZip->ExtractFile(item, outFile, fd); 228 } 229 if (NS_FAILED(rv)) return rv; 230 231 // nsIFile needs milliseconds, while prtime is in microseconds. 232 // non-fatal if this fails, ignore errors 233 outFile->SetLastModifiedTime(item->LastModTime() / PR_USEC_PER_MSEC); 234 235 return NS_OK; 236 } 237 238 NS_IMETHODIMP 239 nsJAR::GetEntry(const nsACString& aEntryName, nsIZipEntry** result) { 240 RecursiveMutexAutoLock lock(mLock); 241 LOG(("GetEntry[%p] %s", this, PromiseFlatCString(aEntryName).get())); 242 if (!mZip) { 243 return NS_ERROR_FAILURE; 244 } 245 nsZipItem* zipItem = mZip->GetItem(aEntryName); 246 NS_ENSURE_TRUE(zipItem, NS_ERROR_FILE_NOT_FOUND); 247 248 RefPtr<nsJARItem> jarItem = new nsJARItem(zipItem); 249 250 *result = jarItem.forget().take(); 251 return NS_OK; 252 } 253 254 NS_IMETHODIMP 255 nsJAR::HasEntry(const nsACString& aEntryName, bool* result) { 256 RecursiveMutexAutoLock lock(mLock); 257 LOG(("HasEntry[%p] %s", this, PromiseFlatCString(aEntryName).get())); 258 if (!mZip) { 259 return NS_ERROR_FAILURE; 260 } 261 *result = mZip->GetItem(aEntryName) != nullptr; 262 return NS_OK; 263 } 264 265 NS_IMETHODIMP 266 nsJAR::FindEntries(const nsACString& aPattern, 267 nsIUTF8StringEnumerator** result) { 268 NS_ENSURE_ARG_POINTER(result); 269 RecursiveMutexAutoLock lock(mLock); 270 LOG(("FindEntries[%p] %s", this, PromiseFlatCString(aPattern).get())); 271 if (!mZip) { 272 return NS_ERROR_FAILURE; 273 } 274 275 nsZipFind* find; 276 nsresult rv = mZip->FindInit( 277 aPattern.IsEmpty() ? nullptr : PromiseFlatCString(aPattern).get(), &find); 278 NS_ENSURE_SUCCESS(rv, rv); 279 280 RefPtr<nsIUTF8StringEnumerator> zipEnum = new nsJAREnumerator(find); 281 282 // Callers use getter_addrefs 283 *result = zipEnum.forget().take(); 284 return NS_OK; 285 } 286 287 NS_IMETHODIMP 288 nsJAR::GetInputStream(const nsACString& aEntryName, nsIInputStream** result) { 289 NS_ENSURE_ARG_POINTER(result); 290 RecursiveMutexAutoLock lock(mLock); 291 if (!mZip) { 292 return NS_ERROR_FAILURE; 293 } 294 295 LOG(("GetInputStream[%p] %s", this, PromiseFlatCString(aEntryName).get())); 296 // Watch out for the jar:foo.zip!/ (aDir is empty) top-level special case! 297 nsZipItem* item = nullptr; 298 const nsCString& entry = PromiseFlatCString(aEntryName); 299 if (*entry.get()) { 300 // First check if item exists in jar 301 item = mZip->GetItem(entry); 302 if (!item) return NS_ERROR_FILE_NOT_FOUND; 303 } 304 RefPtr<nsJARInputStream> jis = new nsJARInputStream(); 305 306 nsresult rv = NS_OK; 307 if (!item || item->IsDirectory()) { 308 rv = jis->InitDirectory(this, entry.get()); 309 } else { 310 RefPtr<nsZipHandle> fd = mZip->GetFD(); 311 rv = jis->InitFile(fd, mZip->GetData(item), item); 312 } 313 if (NS_SUCCEEDED(rv)) { 314 // Callers use getter_addrefs 315 *result = jis.forget().take(); 316 } 317 return rv; 318 } 319 320 nsresult nsJAR::GetFullJarPath(nsACString& aResult) { 321 RecursiveMutexAutoLock lock(mLock); 322 NS_ENSURE_ARG_POINTER(mZipFile); 323 324 nsresult rv = mZipFile->GetPersistentDescriptor(aResult); 325 if (NS_FAILED(rv)) { 326 return rv; 327 } 328 329 if (mOuterZipEntry.IsEmpty()) { 330 aResult.InsertLiteral("file:", 0); 331 } else { 332 aResult.InsertLiteral("jar:", 0); 333 aResult.AppendLiteral("!/"); 334 aResult.Append(mOuterZipEntry); 335 } 336 return NS_OK; 337 } 338 339 nsresult nsJAR::GetNSPRFileDesc(PRFileDesc** aNSPRFileDesc) { 340 RecursiveMutexAutoLock lock(mLock); 341 if (!aNSPRFileDesc) { 342 return NS_ERROR_ILLEGAL_VALUE; 343 } 344 *aNSPRFileDesc = nullptr; 345 346 if (!mZip) { 347 return NS_ERROR_FAILURE; 348 } 349 350 RefPtr<nsZipHandle> handle = mZip->GetFD(); 351 if (!handle) { 352 return NS_ERROR_FAILURE; 353 } 354 355 return handle->GetNSPRFileDesc(aNSPRFileDesc); 356 } 357 358 //---------------------------------------------- 359 // nsJAR private implementation 360 //---------------------------------------------- 361 nsresult nsJAR::LoadEntry(const nsACString& aFilename, nsCString& aBuf) { 362 //-- Get a stream for reading the file 363 nsresult rv; 364 nsCOMPtr<nsIInputStream> manifestStream; 365 rv = GetInputStream(aFilename, getter_AddRefs(manifestStream)); 366 if (NS_FAILED(rv)) return NS_ERROR_FILE_NOT_FOUND; 367 368 //-- Read the manifest file into memory 369 char* buf; 370 uint64_t len64; 371 rv = manifestStream->Available(&len64); 372 if (NS_FAILED(rv)) return rv; 373 NS_ENSURE_TRUE(len64 < UINT32_MAX, NS_ERROR_FILE_CORRUPTED); // bug 164695 374 uint32_t len = (uint32_t)len64; 375 buf = (char*)malloc(len + 1); 376 if (!buf) return NS_ERROR_OUT_OF_MEMORY; 377 uint32_t bytesRead; 378 rv = manifestStream->Read(buf, len, &bytesRead); 379 if (bytesRead != len) { 380 rv = NS_ERROR_FILE_CORRUPTED; 381 } 382 if (NS_FAILED(rv)) { 383 free(buf); 384 return rv; 385 } 386 buf[len] = '\0'; // Null-terminate the buffer 387 aBuf.Adopt(buf, len); 388 return NS_OK; 389 } 390 391 int32_t nsJAR::ReadLine(const char** src) { 392 if (!*src) { 393 return 0; 394 } 395 396 //--Moves pointer to beginning of next line and returns line length 397 // not including CR/LF. 398 int32_t length; 399 const char* eol = strpbrk(*src, "\r\n"); 400 401 if (eol == nullptr) // Probably reached end of file before newline 402 { 403 length = strlen(*src); 404 if (length == 0) // immediate end-of-file 405 *src = nullptr; 406 else // some data left on this line 407 *src += length; 408 } else { 409 length = eol - *src; 410 if (eol[0] == '\r' && eol[1] == '\n') // CR LF, so skip 2 411 *src = eol + 2; 412 else // Either CR or LF, so skip 1 413 *src = eol + 1; 414 } 415 return length; 416 } 417 418 NS_IMPL_ISUPPORTS(nsJAREnumerator, nsIUTF8StringEnumerator, nsIStringEnumerator) 419 420 //---------------------------------------------- 421 // nsJAREnumerator::HasMore 422 //---------------------------------------------- 423 NS_IMETHODIMP 424 nsJAREnumerator::HasMore(bool* aResult) { 425 // try to get the next element 426 if (!mName) { 427 NS_ASSERTION(mFind, "nsJAREnumerator: Missing zipFind."); 428 nsresult rv = mFind->FindNext(&mName, &mNameLen); 429 if (rv == NS_ERROR_FILE_NOT_FOUND) { 430 *aResult = false; // No more matches available 431 return NS_OK; 432 } 433 NS_ENSURE_SUCCESS(rv, NS_ERROR_FAILURE); // no error translation 434 } 435 436 *aResult = true; 437 return NS_OK; 438 } 439 440 //---------------------------------------------- 441 // nsJAREnumerator::GetNext 442 //---------------------------------------------- 443 NS_IMETHODIMP 444 nsJAREnumerator::GetNext(nsACString& aResult) { 445 // check if the current item is "stale" 446 if (!mName) { 447 bool bMore; 448 nsresult rv = HasMore(&bMore); 449 if (NS_FAILED(rv) || !bMore) 450 return NS_ERROR_FAILURE; // no error translation 451 } 452 aResult.Assign(mName, mNameLen); 453 mName = 0; // we just gave this one away 454 return NS_OK; 455 } 456 457 NS_IMPL_ISUPPORTS(nsJARItem, nsIZipEntry) 458 459 nsJARItem::nsJARItem(nsZipItem* aZipItem) 460 : mSize(aZipItem->Size()), 461 mRealsize(aZipItem->RealSize()), 462 mCrc32(aZipItem->CRC32()), 463 mLastModTime(aZipItem->LastModTime()), 464 mCompression(aZipItem->Compression()), 465 mPermissions(aZipItem->Mode()), 466 mIsDirectory(aZipItem->IsDirectory()), 467 mIsSynthetic(aZipItem->isSynthetic) {} 468 469 //------------------------------------------ 470 // nsJARItem::GetCompression 471 //------------------------------------------ 472 NS_IMETHODIMP 473 nsJARItem::GetCompression(uint16_t* aCompression) { 474 NS_ENSURE_ARG_POINTER(aCompression); 475 476 *aCompression = mCompression; 477 return NS_OK; 478 } 479 480 //------------------------------------------ 481 // nsJARItem::GetSize 482 //------------------------------------------ 483 NS_IMETHODIMP 484 nsJARItem::GetSize(uint32_t* aSize) { 485 NS_ENSURE_ARG_POINTER(aSize); 486 487 *aSize = mSize; 488 return NS_OK; 489 } 490 491 //------------------------------------------ 492 // nsJARItem::GetRealSize 493 //------------------------------------------ 494 NS_IMETHODIMP 495 nsJARItem::GetRealSize(uint32_t* aRealsize) { 496 NS_ENSURE_ARG_POINTER(aRealsize); 497 498 *aRealsize = mRealsize; 499 return NS_OK; 500 } 501 502 //------------------------------------------ 503 // nsJARItem::GetCrc32 504 //------------------------------------------ 505 NS_IMETHODIMP 506 nsJARItem::GetCRC32(uint32_t* aCrc32) { 507 NS_ENSURE_ARG_POINTER(aCrc32); 508 509 *aCrc32 = mCrc32; 510 return NS_OK; 511 } 512 513 //------------------------------------------ 514 // nsJARItem::GetIsDirectory 515 //------------------------------------------ 516 NS_IMETHODIMP 517 nsJARItem::GetIsDirectory(bool* aIsDirectory) { 518 NS_ENSURE_ARG_POINTER(aIsDirectory); 519 520 *aIsDirectory = mIsDirectory; 521 return NS_OK; 522 } 523 524 //------------------------------------------ 525 // nsJARItem::GetIsSynthetic 526 //------------------------------------------ 527 NS_IMETHODIMP 528 nsJARItem::GetIsSynthetic(bool* aIsSynthetic) { 529 NS_ENSURE_ARG_POINTER(aIsSynthetic); 530 531 *aIsSynthetic = mIsSynthetic; 532 return NS_OK; 533 } 534 535 //------------------------------------------ 536 // nsJARItem::GetLastModifiedTime 537 //------------------------------------------ 538 NS_IMETHODIMP 539 nsJARItem::GetLastModifiedTime(PRTime* aLastModTime) { 540 NS_ENSURE_ARG_POINTER(aLastModTime); 541 542 *aLastModTime = mLastModTime; 543 return NS_OK; 544 } 545 546 //------------------------------------------ 547 // nsJARItem::GetPermissions 548 //------------------------------------------ 549 NS_IMETHODIMP 550 nsJARItem::GetPermissions(uint32_t* aPermissions) { 551 NS_ENSURE_ARG_POINTER(aPermissions); 552 553 *aPermissions = mPermissions; 554 return NS_OK; 555 } 556 557 //////////////////////////////////////////////////////////////////////////////// 558 // nsIZipReaderCache 559 560 NS_IMPL_ISUPPORTS(nsZipReaderCache, nsIZipReaderCache, nsIObserver, 561 nsISupportsWeakReference) 562 563 nsZipReaderCache::nsZipReaderCache() 564 : mLock("nsZipReaderCache.mLock"), 565 mCacheSize(0), 566 mZips() 567 #ifdef ZIP_CACHE_HIT_RATE 568 , 569 mZipCacheLookups(0), 570 mZipCacheHits(0), 571 mZipCacheFlushes(0), 572 mZipSyncMisses(0) 573 #endif 574 { 575 } 576 577 NS_IMETHODIMP 578 nsZipReaderCache::Init(uint32_t cacheSize) { 579 MutexAutoLock lock(mLock); 580 mCacheSize = cacheSize; 581 582 // Register as a memory pressure observer 583 nsCOMPtr<nsIObserverService> os = 584 do_GetService("@mozilla.org/observer-service;1"); 585 if (os) { 586 os->AddObserver(this, "memory-pressure", true); 587 os->AddObserver(this, "chrome-flush-caches", true); 588 os->AddObserver(this, "flush-cache-entry", true); 589 } 590 // ignore failure of the observer registration. 591 592 return NS_OK; 593 } 594 595 nsZipReaderCache::~nsZipReaderCache() { 596 for (const auto& zip : mZips.Values()) { 597 zip->SetZipReaderCache(nullptr); 598 } 599 600 #ifdef ZIP_CACHE_HIT_RATE 601 printf( 602 "nsZipReaderCache size=%d hits=%d lookups=%d rate=%f%% flushes=%d missed " 603 "%d\n", 604 mCacheSize, mZipCacheHits, mZipCacheLookups, 605 (float)mZipCacheHits / mZipCacheLookups, mZipCacheFlushes, 606 mZipSyncMisses); 607 #endif 608 } 609 610 NS_IMETHODIMP 611 nsZipReaderCache::IsCached(nsIFile* zipFile, bool* aResult) { 612 NS_ENSURE_ARG_POINTER(zipFile); 613 nsresult rv; 614 MutexAutoLock lock(mLock); 615 616 nsAutoCString uri; 617 rv = zipFile->GetPersistentDescriptor(uri); 618 if (NS_FAILED(rv)) return rv; 619 620 uri.InsertLiteral("file:", 0); 621 622 *aResult = mZips.Contains(uri); 623 return NS_OK; 624 } 625 626 nsresult nsZipReaderCache::GetZip(nsIFile* zipFile, nsIZipReader** result, 627 bool failOnMiss) { 628 NS_ENSURE_ARG_POINTER(zipFile); 629 nsresult rv; 630 MutexAutoLock lock(mLock); 631 632 #ifdef ZIP_CACHE_HIT_RATE 633 mZipCacheLookups++; 634 #endif 635 636 nsAutoCString uri; 637 rv = zipFile->GetPersistentDescriptor(uri); 638 if (NS_FAILED(rv)) return rv; 639 640 uri.InsertLiteral("file:", 0); 641 642 RefPtr<nsJAR> zip; 643 mZips.Get(uri, getter_AddRefs(zip)); 644 if (zip) { 645 #ifdef ZIP_CACHE_HIT_RATE 646 mZipCacheHits++; 647 #endif 648 zip->ClearReleaseTime(); 649 } else { 650 if (failOnMiss) { 651 return NS_ERROR_CACHE_KEY_NOT_FOUND; 652 } 653 654 zip = new nsJAR(); 655 zip->SetZipReaderCache(this); 656 rv = zip->Open(zipFile); 657 if (NS_FAILED(rv)) { 658 return rv; 659 } 660 661 MOZ_ASSERT(!mZips.Contains(uri)); 662 mZips.InsertOrUpdate(uri, RefPtr{zip}); 663 } 664 zip.forget(result); 665 return rv; 666 } 667 668 NS_IMETHODIMP 669 nsZipReaderCache::GetZipIfCached(nsIFile* zipFile, nsIZipReader** result) { 670 return GetZip(zipFile, result, true); 671 } 672 673 NS_IMETHODIMP 674 nsZipReaderCache::GetZip(nsIFile* zipFile, nsIZipReader** result) { 675 return GetZip(zipFile, result, false); 676 } 677 678 NS_IMETHODIMP 679 nsZipReaderCache::GetInnerZip(nsIFile* zipFile, const nsACString& entry, 680 nsIZipReader** result) { 681 NS_ENSURE_ARG_POINTER(zipFile); 682 683 nsCOMPtr<nsIZipReader> outerZipReader; 684 nsresult rv = GetZip(zipFile, getter_AddRefs(outerZipReader)); 685 NS_ENSURE_SUCCESS(rv, rv); 686 687 MutexAutoLock lock(mLock); 688 689 #ifdef ZIP_CACHE_HIT_RATE 690 mZipCacheLookups++; 691 #endif 692 693 nsAutoCString uri; 694 rv = zipFile->GetPersistentDescriptor(uri); 695 if (NS_FAILED(rv)) return rv; 696 697 uri.InsertLiteral("jar:", 0); 698 uri.AppendLiteral("!/"); 699 uri.Append(entry); 700 701 RefPtr<nsJAR> zip; 702 mZips.Get(uri, getter_AddRefs(zip)); 703 if (zip) { 704 #ifdef ZIP_CACHE_HIT_RATE 705 mZipCacheHits++; 706 #endif 707 zip->ClearReleaseTime(); 708 } else { 709 zip = new nsJAR(); 710 zip->SetZipReaderCache(this); 711 712 rv = zip->OpenInner(outerZipReader, entry); 713 if (NS_FAILED(rv)) { 714 return rv; 715 } 716 717 MOZ_ASSERT(!mZips.Contains(uri)); 718 mZips.InsertOrUpdate(uri, RefPtr{zip}); 719 } 720 zip.forget(result); 721 return rv; 722 } 723 724 NS_IMETHODIMP 725 nsZipReaderCache::GetFd(nsIFile* zipFile, PRFileDesc** aRetVal) { 726 #if defined(XP_WIN) 727 MOZ_CRASH("Not implemented"); 728 return NS_ERROR_NOT_IMPLEMENTED; 729 #else 730 if (!zipFile) { 731 return NS_ERROR_FAILURE; 732 } 733 734 nsresult rv; 735 nsAutoCString uri; 736 rv = zipFile->GetPersistentDescriptor(uri); 737 if (NS_FAILED(rv)) { 738 return rv; 739 } 740 uri.InsertLiteral("file:", 0); 741 742 MutexAutoLock lock(mLock); 743 RefPtr<nsJAR> zip; 744 mZips.Get(uri, getter_AddRefs(zip)); 745 if (!zip) { 746 return NS_ERROR_FAILURE; 747 } 748 749 zip->ClearReleaseTime(); 750 rv = zip->GetNSPRFileDesc(aRetVal); 751 // Do this to avoid possible deadlock on mLock with ReleaseZip(). 752 { 753 MutexAutoUnlock unlock(mLock); 754 zip = nullptr; 755 } 756 return rv; 757 #endif /* XP_WIN */ 758 } 759 760 nsresult nsZipReaderCache::ReleaseZip(nsJAR* zip) { 761 nsresult rv; 762 MutexAutoLock lock(mLock); 763 764 // It is possible that two thread compete for this zip. The dangerous 765 // case is where one thread Releases the zip and discovers that the ref 766 // count has gone to one. Before it can call this ReleaseZip method 767 // another thread calls our GetZip method. The ref count goes to two. That 768 // second thread then Releases the zip and the ref count goes to one. It 769 // then tries to enter this ReleaseZip method and blocks while the first 770 // thread is still here. The first thread continues and remove the zip from 771 // the cache and calls its Release method sending the ref count to 0 and 772 // deleting the zip. However, the second thread is still blocked at the 773 // start of ReleaseZip, but the 'zip' param now hold a reference to a 774 // deleted zip! 775 // 776 // So, we are going to try safeguarding here by searching our hashtable while 777 // locked here for the zip. We return fast if it is not found. 778 779 bool found = false; 780 for (const auto& current : mZips.Values()) { 781 if (zip == current) { 782 found = true; 783 break; 784 } 785 } 786 787 if (!found) { 788 #ifdef ZIP_CACHE_HIT_RATE 789 mZipSyncMisses++; 790 #endif 791 return NS_OK; 792 } 793 794 zip->SetReleaseTime(); 795 796 if (mZips.Count() <= mCacheSize) return NS_OK; 797 798 // Find the oldest zip. 799 nsJAR* oldest = nullptr; 800 for (const auto& current : mZips.Values()) { 801 PRIntervalTime currentReleaseTime = current->GetReleaseTime(); 802 if (currentReleaseTime != PR_INTERVAL_NO_TIMEOUT) { 803 if (oldest == nullptr || currentReleaseTime < oldest->GetReleaseTime()) { 804 oldest = current; 805 } 806 } 807 } 808 809 // Because of the craziness above it is possible that there is no zip that 810 // needs removing. 811 if (!oldest) return NS_OK; 812 813 #ifdef ZIP_CACHE_HIT_RATE 814 mZipCacheFlushes++; 815 #endif 816 817 // remove from hashtable 818 nsAutoCString uri; 819 rv = oldest->GetFullJarPath(uri); 820 if (NS_FAILED(rv)) { 821 return rv; 822 } 823 824 // Retrieving and removing the JAR should be done without an extra AddRef 825 // and Release, or we'll trigger nsJAR::Release's magic refcount 1 case 826 // an extra time. 827 RefPtr<nsJAR> removed; 828 mZips.Remove(uri, getter_AddRefs(removed)); 829 NS_ASSERTION(removed, "botched"); 830 NS_ASSERTION(oldest == removed, "removed wrong entry"); 831 832 if (removed) removed->SetZipReaderCache(nullptr); 833 834 return NS_OK; 835 } 836 837 NS_IMETHODIMP 838 nsZipReaderCache::Observe(nsISupports* aSubject, const char* aTopic, 839 const char16_t* aSomeData) { 840 if (strcmp(aTopic, "memory-pressure") == 0) { 841 MutexAutoLock lock(mLock); 842 for (auto iter = mZips.Iter(); !iter.Done(); iter.Next()) { 843 RefPtr<nsJAR>& current = iter.Data(); 844 if (current->GetReleaseTime() != PR_INTERVAL_NO_TIMEOUT) { 845 current->SetZipReaderCache(nullptr); 846 iter.Remove(); 847 } 848 } 849 } else if (strcmp(aTopic, "chrome-flush-caches") == 0) { 850 MutexAutoLock lock(mLock); 851 for (const auto& current : mZips.Values()) { 852 current->SetZipReaderCache(nullptr); 853 } 854 mZips.Clear(); 855 } else if (strcmp(aTopic, "flush-cache-entry") == 0) { 856 nsCOMPtr<nsIFile> file; 857 if (aSubject) { 858 file = do_QueryInterface(aSubject); 859 } else if (aSomeData) { 860 nsDependentString fileName(aSomeData); 861 (void)NS_NewLocalFile(fileName, getter_AddRefs(file)); 862 } 863 864 if (!file) return NS_OK; 865 866 nsAutoCString uri; 867 if (NS_FAILED(file->GetPersistentDescriptor(uri))) return NS_OK; 868 869 uri.InsertLiteral("file:", 0); 870 871 MutexAutoLock lock(mLock); 872 873 RefPtr<nsJAR> zip; 874 mZips.Get(uri, getter_AddRefs(zip)); 875 if (!zip) return NS_OK; 876 877 #ifdef ZIP_CACHE_HIT_RATE 878 mZipCacheFlushes++; 879 #endif 880 881 zip->SetZipReaderCache(nullptr); 882 883 mZips.Remove(uri); 884 } 885 return NS_OK; 886 } 887 888 ////////////////////////////////////////////////////////////////////////////////