CacheFileIOManager.cpp (138463B)
1 /* This Source Code Form is subject to the terms of the Mozilla Public 2 * License, v. 2.0. If a copy of the MPL was not distributed with this 3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 5 #include <limits> 6 #include "CacheLog.h" 7 #include "CacheFileIOManager.h" 8 9 #include "CacheHashUtils.h" 10 #include "CacheStorageService.h" 11 #include "CacheIndex.h" 12 #include "CacheFileUtils.h" 13 #include "nsError.h" 14 #include "nsThreadUtils.h" 15 #include "CacheFile.h" 16 #include "CacheObserver.h" 17 #include "nsIFile.h" 18 #include "CacheFileContextEvictor.h" 19 #include "nsITimer.h" 20 #include "nsIDirectoryEnumerator.h" 21 #include "nsEffectiveTLDService.h" 22 #include "nsIObserverService.h" 23 #include "mozilla/net/MozURL.h" 24 #include "mozilla/glean/NetwerkCache2Metrics.h" 25 #include "mozilla/DebugOnly.h" 26 #include "mozilla/Services.h" 27 #include "mozilla/SpinEventLoopUntil.h" 28 #include "mozilla/StaticPrefs_network.h" 29 #include "mozilla/StoragePrincipalHelper.h" 30 #include "nsDirectoryServiceUtils.h" 31 #include "nsAppDirectoryServiceDefs.h" 32 #include "private/pprio.h" 33 #include "mozilla/IntegerPrintfMacros.h" 34 #include "mozilla/Preferences.h" 35 #include "nsNetUtil.h" 36 #include "mozilla/glean/NetwerkMetrics.h" 37 #include "mozilla/FileUtils.h" 38 39 #ifdef MOZ_BACKGROUNDTASKS 40 # include "mozilla/BackgroundTasksRunner.h" 41 # include "nsIBackgroundTasks.h" 42 #endif 43 44 // include files for ftruncate (or equivalent) 45 #if defined(XP_UNIX) 46 # include <unistd.h> 47 #elif defined(XP_WIN) 48 # include <windows.h> 49 # undef CreateFile 50 # undef CREATE_NEW 51 #else 52 // XXX add necessary include file for ftruncate (or equivalent) 53 #endif 54 55 namespace mozilla::net { 56 57 #define kOpenHandlesLimit 128 58 #define kMetadataWriteDelay 5000 59 #define kRemoveTrashStartDelay 60000 // in milliseconds 60 #define kSmartSizeUpdateInterval 60000 // in milliseconds 61 62 #ifdef ANDROID 63 const uint32_t kMaxCacheSizeKB = 512 * 1024; // 512 MB 64 #else 65 const uint32_t kMaxCacheSizeKB = 1024 * 1024; // 1 GB 66 #endif 67 const uint32_t kMaxClearOnShutdownCacheSizeKB = 150 * 1024; // 150 MB 68 const auto kPurgeExtension = ".purge.bg_rm"_ns; 69 70 bool CacheFileHandle::DispatchRelease() { 71 if (CacheFileIOManager::IsOnIOThreadOrCeased()) { 72 return false; 73 } 74 75 nsCOMPtr<nsIEventTarget> ioTarget = CacheFileIOManager::IOTarget(); 76 if (!ioTarget) { 77 return false; 78 } 79 80 nsresult rv = ioTarget->Dispatch( 81 NewNonOwningRunnableMethod("net::CacheFileHandle::Release", this, 82 &CacheFileHandle::Release), 83 nsIEventTarget::DISPATCH_NORMAL); 84 return NS_SUCCEEDED(rv); 85 } 86 87 #if defined(MOZ_CACHE_ASYNC_IO) 88 void CacheFileHandle::StartAsyncOperation() { 89 mAsyncRunning++; 90 CacheFileIOManager::gInstance->mAsyncRunning++; 91 } 92 93 bool CacheFileHandle::WaitForAsyncCompletion(nsIRunnable* aEvent, 94 uint32_t aLevel) { 95 if (mAsyncRunning) { 96 mPendingEvents.AppendElement(PendingItem(aEvent, aLevel)); 97 return true; 98 } 99 return false; 100 } 101 102 # define GUARD_ASYNC_WRITE() \ 103 if (mHandle->WaitForAsyncCompletion( \ 104 this, mHandle->IsPriority() ? CacheIOThread::WRITE_PRIORITY \ 105 : CacheIOThread::WRITE)) { \ 106 return NS_OK; \ 107 } 108 109 class PendingItemComparator { 110 public: 111 using PendingItem = CacheFileHandle::PendingItem; 112 bool LessThan(const PendingItem& a, const PendingItem& b) const { 113 return a.second < b.second; 114 } 115 }; 116 117 void CacheFileHandle::EndAsyncOperation() { 118 MOZ_ASSERT(mAsyncRunning, "EndAsyncOperation called without async operation"); 119 MOZ_ASSERT(CacheFileIOManager::gInstance->mIOThread->IsCurrentThread()); 120 121 if (--mAsyncRunning == 0) { 122 // Notify all pending events 123 auto pendingEvents = std::move(mPendingEvents); 124 pendingEvents.StableSort(PendingItemComparator()); 125 for (auto& event : pendingEvents) { 126 event.first->Run(); 127 } 128 } 129 if (--CacheFileIOManager::gInstance->mAsyncRunning == 0) { 130 CacheFileIOManager::gInstance->DispatchPendingEvents(); 131 } 132 } 133 #else 134 # define GUARD_ASYNC_WRITE() 135 #endif 136 137 NS_IMPL_ADDREF(CacheFileHandle) 138 NS_IMETHODIMP_(MozExternalRefCountType) 139 CacheFileHandle::Release() { 140 nsrefcnt count = mRefCnt - 1; 141 if (DispatchRelease()) { 142 // Redispatched to the IO thread. 143 return count; 144 } 145 146 MOZ_ASSERT(CacheFileIOManager::IsOnIOThreadOrCeased()); 147 148 LOG(("CacheFileHandle::Release() [this=%p, refcnt=%" PRIuPTR "]", this, 149 mRefCnt.get())); 150 MOZ_ASSERT(0 != mRefCnt, "dup release"); 151 count = --mRefCnt; 152 NS_LOG_RELEASE(this, count, "CacheFileHandle"); 153 154 if (0 == count) { 155 mRefCnt = 1; 156 delete (this); 157 return 0; 158 } 159 160 return count; 161 } 162 163 NS_INTERFACE_MAP_BEGIN(CacheFileHandle) 164 NS_INTERFACE_MAP_ENTRY(nsISupports) 165 NS_INTERFACE_MAP_END 166 167 CacheFileHandle::CacheFileHandle(const SHA1Sum::Hash* aHash, bool aPriority, 168 PinningStatus aPinning) 169 : mHash(aHash), 170 mIsDoomed(false), 171 mClosed(false), 172 mPriority(aPriority), 173 mSpecialFile(false), 174 mInvalid(false), 175 mFileExists(false), 176 mDoomWhenFoundPinned(false), 177 mDoomWhenFoundNonPinned(false), 178 mKilled(false), 179 mPinning(aPinning), 180 mFileSize(-1), 181 mFD(nullptr) { 182 // If we initialize mDoomed in the initialization list, that initialization is 183 // not guaranteeded to be atomic. Whereas this assignment here is guaranteed 184 // to be atomic. TSan will see this (atomic) assignment and be satisfied 185 // that cross-thread accesses to mIsDoomed are properly synchronized. 186 mIsDoomed = false; 187 LOG(( 188 "CacheFileHandle::CacheFileHandle() [this=%p, hash=%08x%08x%08x%08x%08x]", 189 this, LOGSHA1(aHash))); 190 } 191 192 CacheFileHandle::CacheFileHandle(const nsACString& aKey, bool aPriority, 193 PinningStatus aPinning) 194 : mHash(nullptr), 195 mIsDoomed(false), 196 mClosed(false), 197 mPriority(aPriority), 198 mSpecialFile(true), 199 mInvalid(false), 200 mFileExists(false), 201 mDoomWhenFoundPinned(false), 202 mDoomWhenFoundNonPinned(false), 203 mKilled(false), 204 mPinning(aPinning), 205 mFileSize(-1), 206 mFD(nullptr), 207 mKey(aKey) { 208 // See comment above about the initialization of mIsDoomed. 209 mIsDoomed = false; 210 LOG(("CacheFileHandle::CacheFileHandle() [this=%p, key=%s]", this, 211 PromiseFlatCString(aKey).get())); 212 } 213 214 CacheFileHandle::~CacheFileHandle() { 215 LOG(("CacheFileHandle::~CacheFileHandle() [this=%p]", this)); 216 217 MOZ_ASSERT(CacheFileIOManager::IsOnIOThreadOrCeased()); 218 219 RefPtr<CacheFileIOManager> ioMan = CacheFileIOManager::gInstance; 220 if (!IsClosed() && ioMan) { 221 ioMan->CloseHandleInternal(this); 222 } 223 } 224 225 void CacheFileHandle::Log() { 226 nsAutoCString leafName; 227 if (mFile) { 228 mFile->GetNativeLeafName(leafName); 229 } 230 231 if (mSpecialFile) { 232 LOG( 233 ("CacheFileHandle::Log() - special file [this=%p, " 234 "isDoomed=%d, priority=%d, closed=%d, invalid=%d, " 235 "pinning=%" PRIu32 ", fileExists=%d, fileSize=%" PRId64 236 ", leafName=%s, key=%s]", 237 this, bool(mIsDoomed), bool(mPriority), bool(mClosed), bool(mInvalid), 238 static_cast<uint32_t>(mPinning), bool(mFileExists), int64_t(mFileSize), 239 leafName.get(), mKey.get())); 240 } else { 241 LOG( 242 ("CacheFileHandle::Log() - entry file [this=%p, " 243 "hash=%08x%08x%08x%08x%08x, " 244 "isDoomed=%d, priority=%d, closed=%d, invalid=%d, " 245 "pinning=%" PRIu32 ", fileExists=%d, fileSize=%" PRId64 246 ", leafName=%s, key=%s]", 247 this, LOGSHA1(mHash), bool(mIsDoomed), bool(mPriority), bool(mClosed), 248 bool(mInvalid), static_cast<uint32_t>(mPinning), bool(mFileExists), 249 int64_t(mFileSize), leafName.get(), mKey.get())); 250 } 251 } 252 253 uint32_t CacheFileHandle::FileSizeInK() const { 254 MOZ_ASSERT(mFileSize != -1); 255 uint64_t size64 = mFileSize; 256 257 size64 += 0x3FF; 258 size64 >>= 10; 259 260 uint32_t size; 261 if (size64 >> 32) { 262 NS_WARNING( 263 "CacheFileHandle::FileSizeInK() - FileSize is too large, " 264 "truncating to PR_UINT32_MAX"); 265 size = PR_UINT32_MAX; 266 } else { 267 size = static_cast<uint32_t>(size64); 268 } 269 270 return size; 271 } 272 273 bool CacheFileHandle::SetPinned(bool aPinned) { 274 LOG(("CacheFileHandle::SetPinned [this=%p, pinned=%d]", this, aPinned)); 275 276 MOZ_ASSERT(CacheFileIOManager::IsOnIOThreadOrCeased()); 277 278 mPinning = aPinned ? PinningStatus::PINNED : PinningStatus::NON_PINNED; 279 280 if ((MOZ_UNLIKELY(mDoomWhenFoundPinned) && aPinned) || 281 (MOZ_UNLIKELY(mDoomWhenFoundNonPinned) && !aPinned)) { 282 LOG((" dooming, when: pinned=%d, non-pinned=%d, found: pinned=%d", 283 bool(mDoomWhenFoundPinned), bool(mDoomWhenFoundNonPinned), aPinned)); 284 285 mDoomWhenFoundPinned = false; 286 mDoomWhenFoundNonPinned = false; 287 288 return false; 289 } 290 291 return true; 292 } 293 294 // Memory reporting 295 296 size_t CacheFileHandle::SizeOfExcludingThis( 297 mozilla::MallocSizeOf mallocSizeOf) const { 298 size_t n = 0; 299 n += mallocSizeOf(mFD); 300 n += mKey.SizeOfExcludingThisIfUnshared(mallocSizeOf); 301 return n; 302 } 303 304 size_t CacheFileHandle::SizeOfIncludingThis( 305 mozilla::MallocSizeOf mallocSizeOf) const { 306 return mallocSizeOf(this) + SizeOfExcludingThis(mallocSizeOf); 307 } 308 309 /****************************************************************************** 310 * CacheFileHandles::HandleHashKey 311 *****************************************************************************/ 312 313 void CacheFileHandles::HandleHashKey::AddHandle(CacheFileHandle* aHandle) { 314 MOZ_ASSERT(CacheFileIOManager::IsOnIOThreadOrCeased()); 315 316 mHandles.InsertElementAt(0, aHandle); 317 } 318 319 void CacheFileHandles::HandleHashKey::RemoveHandle(CacheFileHandle* aHandle) { 320 MOZ_ASSERT(CacheFileIOManager::IsOnIOThreadOrCeased()); 321 322 DebugOnly<bool> found{}; 323 found = mHandles.RemoveElement(aHandle); 324 MOZ_ASSERT(found); 325 } 326 327 already_AddRefed<CacheFileHandle> 328 CacheFileHandles::HandleHashKey::GetNewestHandle() { 329 MOZ_ASSERT(CacheFileIOManager::IsOnIOThreadOrCeased()); 330 331 RefPtr<CacheFileHandle> handle; 332 if (mHandles.Length()) { 333 handle = mHandles[0]; 334 } 335 336 return handle.forget(); 337 } 338 339 void CacheFileHandles::HandleHashKey::GetHandles( 340 nsTArray<RefPtr<CacheFileHandle>>& aResult) { 341 MOZ_ASSERT(CacheFileIOManager::IsOnIOThreadOrCeased()); 342 343 for (uint32_t i = 0; i < mHandles.Length(); ++i) { 344 CacheFileHandle* handle = mHandles[i]; 345 aResult.AppendElement(handle); 346 } 347 } 348 349 #ifdef DEBUG 350 351 void CacheFileHandles::HandleHashKey::AssertHandlesState() { 352 for (uint32_t i = 0; i < mHandles.Length(); ++i) { 353 CacheFileHandle* handle = mHandles[i]; 354 MOZ_ASSERT(handle->IsDoomed()); 355 } 356 } 357 358 #endif 359 360 size_t CacheFileHandles::HandleHashKey::SizeOfExcludingThis( 361 mozilla::MallocSizeOf mallocSizeOf) const { 362 MOZ_ASSERT(CacheFileIOManager::IsOnIOThread()); 363 364 size_t n = 0; 365 n += mallocSizeOf(mHash.get()); 366 for (uint32_t i = 0; i < mHandles.Length(); ++i) { 367 n += mHandles[i]->SizeOfIncludingThis(mallocSizeOf); 368 } 369 370 return n; 371 } 372 373 /****************************************************************************** 374 * CacheFileHandles 375 *****************************************************************************/ 376 377 CacheFileHandles::CacheFileHandles() { 378 LOG(("CacheFileHandles::CacheFileHandles() [this=%p]", this)); 379 MOZ_COUNT_CTOR(CacheFileHandles); 380 } 381 382 CacheFileHandles::~CacheFileHandles() { 383 LOG(("CacheFileHandles::~CacheFileHandles() [this=%p]", this)); 384 MOZ_COUNT_DTOR(CacheFileHandles); 385 } 386 387 nsresult CacheFileHandles::GetHandle(const SHA1Sum::Hash* aHash, 388 CacheFileHandle** _retval) { 389 MOZ_ASSERT(CacheFileIOManager::IsOnIOThreadOrCeased()); 390 MOZ_ASSERT(aHash); 391 392 #ifdef DEBUG_HANDLES 393 LOG(("CacheFileHandles::GetHandle() [hash=%08x%08x%08x%08x%08x]", 394 LOGSHA1(aHash))); 395 #endif 396 397 // find hash entry for key 398 HandleHashKey* entry = mTable.GetEntry(*aHash); 399 if (!entry) { 400 LOG( 401 ("CacheFileHandles::GetHandle() hash=%08x%08x%08x%08x%08x " 402 "no handle entries found", 403 LOGSHA1(aHash))); 404 return NS_ERROR_NOT_AVAILABLE; 405 } 406 407 #ifdef DEBUG_HANDLES 408 Log(entry); 409 #endif 410 411 // Check if the entry is doomed 412 RefPtr<CacheFileHandle> handle = entry->GetNewestHandle(); 413 if (!handle) { 414 LOG( 415 ("CacheFileHandles::GetHandle() hash=%08x%08x%08x%08x%08x " 416 "no handle found %p, entry %p", 417 LOGSHA1(aHash), handle.get(), entry)); 418 return NS_ERROR_NOT_AVAILABLE; 419 } 420 421 if (handle->IsDoomed()) { 422 LOG( 423 ("CacheFileHandles::GetHandle() hash=%08x%08x%08x%08x%08x " 424 "found doomed handle %p, entry %p", 425 LOGSHA1(aHash), handle.get(), entry)); 426 return NS_ERROR_NOT_AVAILABLE; 427 } 428 429 LOG( 430 ("CacheFileHandles::GetHandle() hash=%08x%08x%08x%08x%08x " 431 "found handle %p, entry %p", 432 LOGSHA1(aHash), handle.get(), entry)); 433 434 handle.forget(_retval); 435 return NS_OK; 436 } 437 438 already_AddRefed<CacheFileHandle> CacheFileHandles::NewHandle( 439 const SHA1Sum::Hash* aHash, bool aPriority, 440 CacheFileHandle::PinningStatus aPinning) { 441 MOZ_ASSERT(CacheFileIOManager::IsOnIOThreadOrCeased()); 442 MOZ_ASSERT(aHash); 443 444 #ifdef DEBUG_HANDLES 445 LOG(("CacheFileHandles::NewHandle() [hash=%08x%08x%08x%08x%08x]", 446 LOGSHA1(aHash))); 447 #endif 448 449 // find hash entry for key 450 HandleHashKey* entry = mTable.PutEntry(*aHash); 451 452 #ifdef DEBUG_HANDLES 453 Log(entry); 454 #endif 455 456 #ifdef DEBUG 457 entry->AssertHandlesState(); 458 #endif 459 460 RefPtr<CacheFileHandle> handle = 461 new CacheFileHandle(entry->Hash(), aPriority, aPinning); 462 entry->AddHandle(handle); 463 464 LOG( 465 ("CacheFileHandles::NewHandle() hash=%08x%08x%08x%08x%08x " 466 "created new handle %p, entry=%p", 467 LOGSHA1(aHash), handle.get(), entry)); 468 return handle.forget(); 469 } 470 471 void CacheFileHandles::RemoveHandle(CacheFileHandle* aHandle) { 472 MOZ_ASSERT(CacheFileIOManager::IsOnIOThreadOrCeased()); 473 MOZ_ASSERT(aHandle); 474 475 if (!aHandle) { 476 return; 477 } 478 479 #ifdef DEBUG_HANDLES 480 LOG(( 481 "CacheFileHandles::RemoveHandle() [handle=%p, hash=%08x%08x%08x%08x%08x]", 482 aHandle, LOGSHA1(aHandle->Hash()))); 483 #endif 484 485 // find hash entry for key 486 HandleHashKey* entry = mTable.GetEntry(*aHandle->Hash()); 487 if (!entry) { 488 MOZ_ASSERT(CacheFileIOManager::IsShutdown(), 489 "Should find entry when removing a handle before shutdown"); 490 491 LOG( 492 ("CacheFileHandles::RemoveHandle() hash=%08x%08x%08x%08x%08x " 493 "no entries found", 494 LOGSHA1(aHandle->Hash()))); 495 return; 496 } 497 498 #ifdef DEBUG_HANDLES 499 Log(entry); 500 #endif 501 502 LOG( 503 ("CacheFileHandles::RemoveHandle() hash=%08x%08x%08x%08x%08x " 504 "removing handle %p", 505 LOGSHA1(entry->Hash()), aHandle)); 506 entry->RemoveHandle(aHandle); 507 508 if (entry->IsEmpty()) { 509 LOG( 510 ("CacheFileHandles::RemoveHandle() hash=%08x%08x%08x%08x%08x " 511 "list is empty, removing entry %p", 512 LOGSHA1(entry->Hash()), entry)); 513 mTable.RemoveEntry(entry); 514 } 515 } 516 517 void CacheFileHandles::GetAllHandles( 518 nsTArray<RefPtr<CacheFileHandle>>* _retval) { 519 MOZ_ASSERT(CacheFileIOManager::IsOnIOThreadOrCeased()); 520 for (auto iter = mTable.Iter(); !iter.Done(); iter.Next()) { 521 iter.Get()->GetHandles(*_retval); 522 } 523 } 524 525 void CacheFileHandles::GetActiveHandles( 526 nsTArray<RefPtr<CacheFileHandle>>* _retval) { 527 MOZ_ASSERT(CacheFileIOManager::IsOnIOThreadOrCeased()); 528 for (auto iter = mTable.Iter(); !iter.Done(); iter.Next()) { 529 RefPtr<CacheFileHandle> handle = iter.Get()->GetNewestHandle(); 530 MOZ_ASSERT(handle); 531 532 if (!handle->IsDoomed()) { 533 _retval->AppendElement(handle); 534 } 535 } 536 } 537 538 void CacheFileHandles::ClearAll() { 539 MOZ_ASSERT(CacheFileIOManager::IsOnIOThreadOrCeased()); 540 mTable.Clear(); 541 } 542 543 uint32_t CacheFileHandles::HandleCount() { return mTable.Count(); } 544 545 #ifdef DEBUG_HANDLES 546 void CacheFileHandles::Log(CacheFileHandlesEntry* entry) { 547 LOG(("CacheFileHandles::Log() BEGIN [entry=%p]", entry)); 548 549 nsTArray<RefPtr<CacheFileHandle>> array; 550 aEntry->GetHandles(array); 551 552 for (uint32_t i = 0; i < array.Length(); ++i) { 553 CacheFileHandle* handle = array[i]; 554 handle->Log(); 555 } 556 557 LOG(("CacheFileHandles::Log() END [entry=%p]", entry)); 558 } 559 #endif 560 561 // Memory reporting 562 563 size_t CacheFileHandles::SizeOfExcludingThis( 564 mozilla::MallocSizeOf mallocSizeOf) const { 565 MOZ_ASSERT(CacheFileIOManager::IsOnIOThread()); 566 567 return mTable.SizeOfExcludingThis(mallocSizeOf); 568 } 569 570 // Events 571 572 class ShutdownEvent : public Runnable, nsITimerCallback { 573 NS_DECL_ISUPPORTS_INHERITED 574 public: 575 ShutdownEvent() : Runnable("net::ShutdownEvent") {} 576 577 protected: 578 ~ShutdownEvent() = default; 579 580 public: 581 NS_IMETHOD Run() override { 582 #if defined(MOZ_CACHE_ASYNC_IO) 583 if (CacheFileIOManager::gInstance->mAsyncRunning > 0) { 584 // If there are any async operations running, we will wait for them to 585 // finish before shutting down the IO thread. 586 LOG(("CacheFileIOManager::ShutdownEvent - waiting for async operations")); 587 CacheFileIOManager::gInstance->mPendingEvents.AppendElement( 588 CacheFileIOManager::PendingItem(this, CacheIOThread::WRITE)); 589 return NS_OK; 590 } 591 #endif 592 CacheFileIOManager::gInstance->ShutdownInternal(); 593 594 mNotified = true; 595 596 NS_DispatchToMainThread( 597 NS_NewRunnableFunction("CacheFileIOManager::ShutdownEvent::Run", []() { 598 // This empty runnable is dispatched just in case the MT event loop 599 // becomes empty - we need to process a task to break out of 600 // SpinEventLoopUntil. 601 })); 602 603 return NS_OK; 604 } 605 606 NS_IMETHOD Notify(nsITimer* timer) override { 607 if (mNotified) { 608 return NS_OK; 609 } 610 611 // If there is any IO blocking on the IO thread, this will 612 // try to cancel it. 613 CacheFileIOManager::gInstance->mIOThread->CancelBlockingIO(); 614 615 // After this runs the first time, the browser_cache_max_shutdown_io_lag 616 // time has elapsed. The CacheIO thread may pick up more blocking IO tasks 617 // so we want to block those too if necessary. 618 mTimer->SetDelay( 619 StaticPrefs::browser_cache_shutdown_io_time_between_cancellations_ms()); 620 return NS_OK; 621 } 622 623 void PostAndWait() { 624 nsresult rv = CacheFileIOManager::gInstance->mIOThread->Dispatch( 625 this, 626 CacheIOThread::WRITE); // When writes and closing of handles is done 627 MOZ_ASSERT(NS_SUCCEEDED(rv)); 628 629 // If we failed to post the even there's no reason to go into the loop 630 // because mNotified will never be set to true. 631 if (NS_FAILED(rv)) { 632 NS_WARNING("Posting ShutdownEvent task failed"); 633 return; 634 } 635 636 rv = NS_NewTimerWithCallback( 637 getter_AddRefs(mTimer), this, 638 StaticPrefs::browser_cache_max_shutdown_io_lag() * 1000, 639 nsITimer::TYPE_REPEATING_SLACK); 640 MOZ_ASSERT(NS_SUCCEEDED(rv)); 641 642 mozilla::SpinEventLoopUntil("CacheFileIOManager::ShutdownEvent"_ns, 643 [&]() { return bool(mNotified); }); 644 645 if (mTimer) { 646 mTimer->Cancel(); 647 mTimer = nullptr; 648 } 649 } 650 651 protected: 652 Atomic<bool> mNotified{false}; 653 nsCOMPtr<nsITimer> mTimer; 654 }; 655 656 NS_IMPL_ISUPPORTS_INHERITED(ShutdownEvent, Runnable, nsITimerCallback) 657 658 // Class responsible for reporting IO performance stats 659 class IOPerfReportEvent { 660 public: 661 explicit IOPerfReportEvent(CacheFileUtils::CachePerfStats::EDataType aType) 662 : mType(aType), mEventCounter(0) {} 663 664 void Start(CacheIOThread* aIOThread) { 665 mStartTime = TimeStamp::Now(); 666 mEventCounter = aIOThread->EventCounter(); 667 } 668 669 void Report(CacheIOThread* aIOThread) { 670 if (mStartTime.IsNull()) { 671 return; 672 } 673 674 // Single IO operations can take less than 1ms. So we use microseconds to 675 // keep a good resolution of data. 676 uint32_t duration = (TimeStamp::Now() - mStartTime).ToMicroseconds(); 677 678 // This is a simple prefiltering of values that might differ a lot from the 679 // average value. Do not add the value to the filtered stats when the event 680 // had to wait in a long queue. 681 uint32_t eventCounter = aIOThread->EventCounter(); 682 bool shortOnly = eventCounter - mEventCounter >= 5; 683 684 CacheFileUtils::CachePerfStats::AddValue(mType, duration, shortOnly); 685 } 686 687 protected: 688 CacheFileUtils::CachePerfStats::EDataType mType; 689 TimeStamp mStartTime; 690 uint32_t mEventCounter; 691 }; 692 693 class OpenFileEvent : public Runnable, public IOPerfReportEvent { 694 public: 695 OpenFileEvent(const nsACString& aKey, uint32_t aFlags, 696 CacheFileIOListener* aCallback) 697 : Runnable("net::OpenFileEvent"), 698 IOPerfReportEvent(CacheFileUtils::CachePerfStats::IO_OPEN), 699 mFlags(aFlags), 700 mCallback(aCallback), 701 mKey(aKey) { 702 mIOMan = CacheFileIOManager::gInstance; 703 if (!(mFlags & CacheFileIOManager::SPECIAL_FILE)) { 704 Start(mIOMan->mIOThread); 705 } 706 } 707 708 protected: 709 ~OpenFileEvent() = default; 710 711 public: 712 NS_IMETHOD Run() override { 713 nsresult rv = NS_OK; 714 715 if (!(mFlags & CacheFileIOManager::SPECIAL_FILE)) { 716 SHA1Sum sum; 717 sum.update(mKey.BeginReading(), mKey.Length()); 718 sum.finish(mHash); 719 } 720 721 if (!mIOMan) { 722 rv = NS_ERROR_NOT_INITIALIZED; 723 } else { 724 if (mFlags & CacheFileIOManager::SPECIAL_FILE) { 725 rv = mIOMan->OpenSpecialFileInternal(mKey, mFlags, 726 getter_AddRefs(mHandle)); 727 } else { 728 rv = mIOMan->OpenFileInternal(&mHash, mKey, mFlags, 729 getter_AddRefs(mHandle)); 730 if (NS_SUCCEEDED(rv)) { 731 Report(mIOMan->mIOThread); 732 } 733 } 734 mIOMan = nullptr; 735 if (mHandle) { 736 if (mHandle->Key().IsEmpty()) { 737 mHandle->Key() = mKey; 738 } 739 } 740 } 741 742 mCallback->OnFileOpened(mHandle, rv); 743 return NS_OK; 744 } 745 746 protected: 747 SHA1Sum::Hash mHash{}; 748 uint32_t mFlags; 749 nsCOMPtr<CacheFileIOListener> mCallback; 750 RefPtr<CacheFileIOManager> mIOMan; 751 RefPtr<CacheFileHandle> mHandle; 752 nsCString mKey; 753 }; 754 755 class ReadEvent : public Runnable, public IOPerfReportEvent { 756 public: 757 ReadEvent(CacheFileHandle* aHandle, int64_t aOffset, char* aBuf, 758 int32_t aCount, CacheFileIOListener* aCallback) 759 : Runnable("net::ReadEvent"), 760 IOPerfReportEvent(CacheFileUtils::CachePerfStats::IO_READ), 761 mHandle(aHandle), 762 mOffset(aOffset), 763 mBuf(aBuf), 764 mCount(aCount), 765 mCallback(aCallback) { 766 if (!mHandle->IsSpecialFile()) { 767 Start(CacheFileIOManager::gInstance->mIOThread); 768 } 769 } 770 771 protected: 772 ~ReadEvent() = default; 773 774 public: 775 nsresult InlineRun(bool aAsyncDataRead) { 776 nsresult rv; 777 778 MOZ_ASSERT(CacheFileIOManager::IsOnIOThread()); 779 780 if (mHandle->IsClosed() || (mCallback && mCallback->IsKilled())) { 781 rv = NS_ERROR_NOT_INITIALIZED; 782 } else { 783 rv = CacheFileIOManager::gInstance->ReadInternal(mHandle, mOffset, mBuf, 784 mCount, this); 785 if (NS_SUCCEEDED(rv)) { 786 #if !defined(MOZ_CACHE_ASYNC_IO) 787 Report(CacheFileIOManager::gInstance->mIOThread); 788 #else 789 /* The request has been performed asynchronously. It should 790 * complete later. 791 */ 792 return NS_OK; 793 #endif 794 } 795 } 796 797 #if defined(MOZ_CACHE_ASYNC_IO) 798 if (aAsyncDataRead) { 799 nsCOMPtr<nsIEventTarget> ioTarget = CacheFileIOManager::IOTarget(); 800 ioTarget->Dispatch(NS_NewRunnableFunction( 801 "net::ReadEvent::Callback", [self = RefPtr(this), rv]() { 802 // Prevent calling back twice 803 nsCOMPtr<CacheFileIOListener> cb = std::move(self->mCallback); 804 cb->OnDataRead(self->mHandle, self->mBuf, rv); 805 })); 806 return NS_OK; 807 } 808 #endif 809 810 // Prevent calling back twice 811 nsCOMPtr<CacheFileIOListener> cb = std::move(mCallback); 812 cb->OnDataRead(mHandle, mBuf, rv); 813 return NS_OK; 814 } 815 816 NS_IMETHOD Run() override { return InlineRun(false); } 817 818 #if defined(MOZ_CACHE_ASYNC_IO) 819 nsresult OnComplete(nsresult aStatus) { 820 nsresult result = aStatus; 821 822 if (NS_SUCCEEDED(result)) { 823 Report(CacheFileIOManager::gInstance->mIOThread); 824 } 825 826 // Prevent calling back twice 827 nsCOMPtr<CacheFileIOListener> cb = std::move(mCallback); 828 cb->OnDataRead(mHandle, mBuf, result); 829 mHandle->EndAsyncOperation(); 830 return NS_OK; 831 } 832 #endif 833 834 protected: 835 RefPtr<CacheFileHandle> mHandle; 836 int64_t mOffset; 837 char* mBuf; 838 int32_t mCount; 839 nsCOMPtr<CacheFileIOListener> mCallback; 840 }; 841 842 class WriteEvent : public Runnable, public IOPerfReportEvent { 843 public: 844 WriteEvent(CacheFileHandle* aHandle, int64_t aOffset, const char* aBuf, 845 int32_t aCount, bool aValidate, bool aTruncate, 846 CacheFileIOListener* aCallback) 847 : Runnable("net::WriteEvent"), 848 IOPerfReportEvent(CacheFileUtils::CachePerfStats::IO_WRITE), 849 mHandle(aHandle), 850 mOffset(aOffset), 851 mBuf(aBuf), 852 mCount(aCount), 853 mValidate(aValidate), 854 mTruncate(aTruncate), 855 mCallback(aCallback) { 856 if (!mHandle->IsSpecialFile()) { 857 Start(CacheFileIOManager::gInstance->mIOThread); 858 } 859 } 860 861 protected: 862 ~WriteEvent() { 863 if (!mCallback && mBuf) { 864 free(const_cast<char*>(mBuf)); 865 } 866 } 867 868 public: 869 NS_IMETHOD Run() override { 870 GUARD_ASYNC_WRITE(); 871 nsresult rv; 872 873 if (mHandle->IsClosed() || (mCallback && mCallback->IsKilled())) { 874 // We usually get here only after the internal shutdown 875 // (i.e. mShuttingDown == true). Pretend write has succeeded 876 // to avoid any past-shutdown file dooming. 877 rv = (CacheObserver::IsPastShutdownIOLag() || 878 CacheFileIOManager::gInstance->mShuttingDown) 879 ? NS_OK 880 : NS_ERROR_NOT_INITIALIZED; 881 } else { 882 rv = CacheFileIOManager::gInstance->WriteInternal( 883 mHandle, mOffset, mBuf, mCount, mValidate, mTruncate); 884 if (NS_SUCCEEDED(rv)) { 885 Report(CacheFileIOManager::gInstance->mIOThread); 886 } 887 if (NS_FAILED(rv) && !mCallback) { 888 // No listener is going to handle the error, doom the file 889 CacheFileIOManager::gInstance->DoomFileInternal(mHandle); 890 } 891 } 892 if (mCallback) { 893 mCallback->OnDataWritten(mHandle, mBuf, rv); 894 } else { 895 free(const_cast<char*>(mBuf)); 896 mBuf = nullptr; 897 } 898 899 return NS_OK; 900 } 901 902 protected: 903 RefPtr<CacheFileHandle> mHandle; 904 int64_t mOffset; 905 const char* mBuf; 906 int32_t mCount; 907 bool mValidate : 1; 908 bool mTruncate : 1; 909 nsCOMPtr<CacheFileIOListener> mCallback; 910 }; 911 912 class DoomFileEvent : public Runnable { 913 public: 914 DoomFileEvent(CacheFileHandle* aHandle, CacheFileIOListener* aCallback) 915 : Runnable("net::DoomFileEvent"), 916 mCallback(aCallback), 917 mHandle(aHandle) {} 918 919 protected: 920 ~DoomFileEvent() = default; 921 922 public: 923 NS_IMETHOD Run() override { 924 nsresult rv; 925 926 if (mHandle->IsClosed()) { 927 rv = NS_ERROR_NOT_INITIALIZED; 928 } else { 929 rv = CacheFileIOManager::gInstance->DoomFileInternal(mHandle); 930 } 931 932 if (mCallback) { 933 mCallback->OnFileDoomed(mHandle, rv); 934 } 935 936 return NS_OK; 937 } 938 939 protected: 940 nsCOMPtr<CacheFileIOListener> mCallback; 941 nsCOMPtr<nsIEventTarget> mTarget; 942 RefPtr<CacheFileHandle> mHandle; 943 }; 944 945 class DoomFileByKeyEvent : public Runnable { 946 public: 947 DoomFileByKeyEvent(const nsACString& aKey, CacheFileIOListener* aCallback) 948 : Runnable("net::DoomFileByKeyEvent"), mCallback(aCallback) { 949 SHA1Sum sum; 950 sum.update(aKey.BeginReading(), aKey.Length()); 951 sum.finish(mHash); 952 953 mIOMan = CacheFileIOManager::gInstance; 954 } 955 956 protected: 957 ~DoomFileByKeyEvent() = default; 958 959 public: 960 NS_IMETHOD Run() override { 961 nsresult rv; 962 963 if (!mIOMan) { 964 rv = NS_ERROR_NOT_INITIALIZED; 965 } else { 966 rv = mIOMan->DoomFileByKeyInternal(&mHash); 967 mIOMan = nullptr; 968 } 969 970 if (mCallback) { 971 mCallback->OnFileDoomed(nullptr, rv); 972 } 973 974 return NS_OK; 975 } 976 977 protected: 978 SHA1Sum::Hash mHash{}; 979 nsCOMPtr<CacheFileIOListener> mCallback; 980 RefPtr<CacheFileIOManager> mIOMan; 981 }; 982 983 class ReleaseNSPRHandleEvent : public Runnable { 984 public: 985 explicit ReleaseNSPRHandleEvent(CacheFileHandle* aHandle) 986 : Runnable("net::ReleaseNSPRHandleEvent"), mHandle(aHandle) {} 987 988 protected: 989 ~ReleaseNSPRHandleEvent() = default; 990 991 public: 992 NS_IMETHOD Run() override { 993 if (!mHandle->IsClosed()) { 994 CacheFileIOManager::gInstance->MaybeReleaseNSPRHandleInternal(mHandle); 995 } 996 997 return NS_OK; 998 } 999 1000 protected: 1001 RefPtr<CacheFileHandle> mHandle; 1002 }; 1003 1004 class TruncateSeekSetEOFEvent : public Runnable { 1005 public: 1006 TruncateSeekSetEOFEvent(CacheFileHandle* aHandle, int64_t aTruncatePos, 1007 int64_t aEOFPos, CacheFileIOListener* aCallback) 1008 : Runnable("net::TruncateSeekSetEOFEvent"), 1009 mHandle(aHandle), 1010 mTruncatePos(aTruncatePos), 1011 mEOFPos(aEOFPos), 1012 mCallback(aCallback) {} 1013 1014 protected: 1015 ~TruncateSeekSetEOFEvent() = default; 1016 1017 public: 1018 NS_IMETHOD Run() override { 1019 GUARD_ASYNC_WRITE(); 1020 nsresult rv; 1021 1022 if (mHandle->IsClosed() || (mCallback && mCallback->IsKilled())) { 1023 rv = NS_ERROR_NOT_INITIALIZED; 1024 } else { 1025 rv = CacheFileIOManager::gInstance->TruncateSeekSetEOFInternal( 1026 mHandle, mTruncatePos, mEOFPos); 1027 } 1028 1029 if (mCallback) { 1030 mCallback->OnEOFSet(mHandle, rv); 1031 } 1032 1033 return NS_OK; 1034 } 1035 1036 protected: 1037 RefPtr<CacheFileHandle> mHandle; 1038 int64_t mTruncatePos; 1039 int64_t mEOFPos; 1040 nsCOMPtr<CacheFileIOListener> mCallback; 1041 }; 1042 1043 class RenameFileEvent : public Runnable { 1044 public: 1045 RenameFileEvent(CacheFileHandle* aHandle, const nsACString& aNewName, 1046 CacheFileIOListener* aCallback) 1047 : Runnable("net::RenameFileEvent"), 1048 mHandle(aHandle), 1049 mNewName(aNewName), 1050 mCallback(aCallback) {} 1051 1052 protected: 1053 ~RenameFileEvent() = default; 1054 1055 public: 1056 NS_IMETHOD Run() override { 1057 GUARD_ASYNC_WRITE(); 1058 nsresult rv; 1059 1060 if (mHandle->IsClosed()) { 1061 rv = NS_ERROR_NOT_INITIALIZED; 1062 } else { 1063 rv = CacheFileIOManager::gInstance->RenameFileInternal(mHandle, mNewName); 1064 } 1065 1066 if (mCallback) { 1067 mCallback->OnFileRenamed(mHandle, rv); 1068 } 1069 1070 return NS_OK; 1071 } 1072 1073 protected: 1074 RefPtr<CacheFileHandle> mHandle; 1075 nsCString mNewName; 1076 nsCOMPtr<CacheFileIOListener> mCallback; 1077 }; 1078 1079 class InitIndexEntryEvent : public Runnable { 1080 public: 1081 InitIndexEntryEvent(CacheFileHandle* aHandle, 1082 OriginAttrsHash aOriginAttrsHash, bool aAnonymous, 1083 bool aPinning) 1084 : Runnable("net::InitIndexEntryEvent"), 1085 mHandle(aHandle), 1086 mOriginAttrsHash(aOriginAttrsHash), 1087 mAnonymous(aAnonymous), 1088 mPinning(aPinning) {} 1089 1090 protected: 1091 ~InitIndexEntryEvent() = default; 1092 1093 public: 1094 NS_IMETHOD Run() override { 1095 if (mHandle->IsClosed() || mHandle->IsDoomed()) { 1096 return NS_OK; 1097 } 1098 1099 CacheIndex::InitEntry(mHandle->Hash(), mOriginAttrsHash, mAnonymous, 1100 mPinning); 1101 1102 // We cannot set the filesize before we init the entry. If we're opening 1103 // an existing entry file, frecency will be set after parsing the entry 1104 // file, but we must set the filesize here since nobody is going to set it 1105 // if there is no write to the file. 1106 uint32_t sizeInK = mHandle->FileSizeInK(); 1107 CacheIndex::UpdateEntry(mHandle->Hash(), nullptr, nullptr, nullptr, nullptr, 1108 nullptr, &sizeInK); 1109 1110 return NS_OK; 1111 } 1112 1113 protected: 1114 RefPtr<CacheFileHandle> mHandle; 1115 OriginAttrsHash mOriginAttrsHash; 1116 bool mAnonymous; 1117 bool mPinning; 1118 }; 1119 1120 class UpdateIndexEntryEvent : public Runnable { 1121 public: 1122 UpdateIndexEntryEvent(CacheFileHandle* aHandle, const uint32_t* aFrecency, 1123 const bool* aHasAltData, const uint16_t* aOnStartTime, 1124 const uint16_t* aOnStopTime, 1125 const uint8_t* aContentType) 1126 : Runnable("net::UpdateIndexEntryEvent"), 1127 mHandle(aHandle), 1128 mHasFrecency(false), 1129 mHasHasAltData(false), 1130 mHasOnStartTime(false), 1131 mHasOnStopTime(false), 1132 mHasContentType(false), 1133 mFrecency(0), 1134 mHasAltData(false), 1135 mOnStartTime(0), 1136 mOnStopTime(0), 1137 mContentType(nsICacheEntry::CONTENT_TYPE_UNKNOWN) { 1138 if (aFrecency) { 1139 mHasFrecency = true; 1140 mFrecency = *aFrecency; 1141 } 1142 if (aHasAltData) { 1143 mHasHasAltData = true; 1144 mHasAltData = *aHasAltData; 1145 } 1146 if (aOnStartTime) { 1147 mHasOnStartTime = true; 1148 mOnStartTime = *aOnStartTime; 1149 } 1150 if (aOnStopTime) { 1151 mHasOnStopTime = true; 1152 mOnStopTime = *aOnStopTime; 1153 } 1154 if (aContentType) { 1155 mHasContentType = true; 1156 mContentType = *aContentType; 1157 } 1158 } 1159 1160 protected: 1161 ~UpdateIndexEntryEvent() = default; 1162 1163 public: 1164 NS_IMETHOD Run() override { 1165 if (mHandle->IsClosed() || mHandle->IsDoomed()) { 1166 return NS_OK; 1167 } 1168 1169 CacheIndex::UpdateEntry(mHandle->Hash(), 1170 mHasFrecency ? &mFrecency : nullptr, 1171 mHasHasAltData ? &mHasAltData : nullptr, 1172 mHasOnStartTime ? &mOnStartTime : nullptr, 1173 mHasOnStopTime ? &mOnStopTime : nullptr, 1174 mHasContentType ? &mContentType : nullptr, nullptr); 1175 return NS_OK; 1176 } 1177 1178 protected: 1179 RefPtr<CacheFileHandle> mHandle; 1180 1181 bool mHasFrecency; 1182 bool mHasHasAltData; 1183 bool mHasOnStartTime; 1184 bool mHasOnStopTime; 1185 bool mHasContentType; 1186 1187 uint32_t mFrecency; 1188 bool mHasAltData; 1189 uint16_t mOnStartTime; 1190 uint16_t mOnStopTime; 1191 uint8_t mContentType; 1192 }; 1193 1194 class MetadataWriteScheduleEvent : public Runnable { 1195 public: 1196 enum EMode { SCHEDULE, UNSCHEDULE, SHUTDOWN } mMode; 1197 1198 RefPtr<CacheFile> mFile; 1199 RefPtr<CacheFileIOManager> mIOMan; 1200 1201 MetadataWriteScheduleEvent(CacheFileIOManager* aManager, CacheFile* aFile, 1202 EMode aMode) 1203 : Runnable("net::MetadataWriteScheduleEvent"), 1204 mMode(aMode), 1205 mFile(aFile), 1206 mIOMan(aManager) {} 1207 1208 virtual ~MetadataWriteScheduleEvent() = default; 1209 1210 NS_IMETHOD Run() override { 1211 RefPtr<CacheFileIOManager> ioMan = CacheFileIOManager::gInstance; 1212 if (!ioMan) { 1213 NS_WARNING( 1214 "CacheFileIOManager already gone in " 1215 "MetadataWriteScheduleEvent::Run()"); 1216 return NS_OK; 1217 } 1218 1219 switch (mMode) { 1220 case SCHEDULE: 1221 ioMan->ScheduleMetadataWriteInternal(mFile); 1222 break; 1223 case UNSCHEDULE: 1224 ioMan->UnscheduleMetadataWriteInternal(mFile); 1225 break; 1226 case SHUTDOWN: 1227 ioMan->ShutdownMetadataWriteSchedulingInternal(); 1228 break; 1229 } 1230 return NS_OK; 1231 } 1232 }; 1233 1234 StaticRefPtr<CacheFileIOManager> CacheFileIOManager::gInstance; 1235 1236 NS_IMPL_ISUPPORTS(CacheFileIOManager, nsITimerCallback, nsINamed) 1237 1238 CacheFileIOManager::CacheFileIOManager() 1239 1240 { 1241 LOG(("CacheFileIOManager::CacheFileIOManager [this=%p]", this)); 1242 MOZ_ASSERT(!gInstance, "multiple CacheFileIOManager instances!"); 1243 } 1244 1245 CacheFileIOManager::~CacheFileIOManager() { 1246 LOG(("CacheFileIOManager::~CacheFileIOManager [this=%p]", this)); 1247 } 1248 1249 // static 1250 nsresult CacheFileIOManager::Init() { 1251 LOG(("CacheFileIOManager::Init()")); 1252 1253 MOZ_ASSERT(NS_IsMainThread()); 1254 1255 if (gInstance) { 1256 return NS_ERROR_ALREADY_INITIALIZED; 1257 } 1258 1259 RefPtr<CacheFileIOManager> ioMan = new CacheFileIOManager(); 1260 1261 nsresult rv = ioMan->InitInternal(); 1262 NS_ENSURE_SUCCESS(rv, rv); 1263 1264 gInstance = std::move(ioMan); 1265 1266 // Ensure that transport service is initialized on the main thread. 1267 nsCOMPtr<nsIEventTarget> target = 1268 do_GetService(NS_STREAMTRANSPORTSERVICE_CONTRACTID, &rv); 1269 (void)target; 1270 1271 return NS_OK; 1272 } 1273 1274 nsresult CacheFileIOManager::InitInternal() { 1275 nsresult rv; 1276 1277 mIOThread = new CacheIOThread(); 1278 1279 rv = mIOThread->Init(); 1280 MOZ_ASSERT(NS_SUCCEEDED(rv), "Can't create background thread"); 1281 NS_ENSURE_SUCCESS(rv, rv); 1282 1283 mStartTime = TimeStamp::NowLoRes(); 1284 1285 return NS_OK; 1286 } 1287 1288 // static 1289 nsresult CacheFileIOManager::Shutdown() { 1290 LOG(("CacheFileIOManager::Shutdown() [gInstance=%p]", gInstance.get())); 1291 1292 MOZ_ASSERT(NS_IsMainThread()); 1293 1294 if (!gInstance) { 1295 return NS_ERROR_NOT_INITIALIZED; 1296 } 1297 1298 auto shutdownTimer = glean::network::disk_cache_shutdown_v2.Measure(); 1299 1300 CacheIndex::PreShutdown(); 1301 1302 ShutdownMetadataWriteScheduling(); 1303 1304 RefPtr<ShutdownEvent> ev = new ShutdownEvent(); 1305 ev->PostAndWait(); 1306 1307 MOZ_ASSERT(gInstance->mHandles.HandleCount() == 0); 1308 MOZ_ASSERT(gInstance->mHandlesByLastUsed.Length() == 0); 1309 1310 if (gInstance->mIOThread) { 1311 gInstance->mIOThread->Shutdown(); 1312 } 1313 1314 CacheIndex::Shutdown(); 1315 DictionaryCache::Shutdown(); 1316 1317 if (CacheObserver::ClearCacheOnShutdown()) { 1318 auto totalTimer = 1319 glean::network::disk_cache2_shutdown_clear_private.Measure(); 1320 gInstance->SyncRemoveAllCacheFiles(); 1321 } 1322 1323 gInstance = nullptr; 1324 1325 return NS_OK; 1326 } 1327 1328 void CacheFileIOManager::ShutdownInternal() { 1329 LOG(("CacheFileIOManager::ShutdownInternal() [this=%p]", this)); 1330 1331 MOZ_ASSERT(mIOThread->IsCurrentThread()); 1332 1333 // No new handles can be created after this flag is set 1334 mShuttingDown = true; 1335 1336 if (mTrashTimer) { 1337 mTrashTimer->Cancel(); 1338 mTrashTimer = nullptr; 1339 } 1340 1341 // close all handles and delete all associated files 1342 nsTArray<RefPtr<CacheFileHandle>> handles; 1343 mHandles.GetAllHandles(&handles); 1344 handles.AppendElements(mSpecialHandles); 1345 1346 for (uint32_t i = 0; i < handles.Length(); i++) { 1347 CacheFileHandle* h = handles[i]; 1348 h->mClosed = true; 1349 1350 h->Log(); 1351 1352 // Close completely written files. 1353 MaybeReleaseNSPRHandleInternal(h); 1354 // Don't bother removing invalid and/or doomed files to improve 1355 // shutdown perfomrance. 1356 // Doomed files are already in the doomed directory from which 1357 // we never reuse files and delete the dir on next session startup. 1358 // Invalid files don't have metadata and thus won't load anyway 1359 // (hashes won't match). 1360 1361 if (!h->IsSpecialFile() && !h->mIsDoomed && !h->mFileExists) { 1362 CacheIndex::RemoveEntry(h->Hash(), h->Key()); 1363 } 1364 1365 // Remove the handle from mHandles/mSpecialHandles 1366 if (h->IsSpecialFile()) { 1367 mSpecialHandles.RemoveElement(h); 1368 } else { 1369 mHandles.RemoveHandle(h); 1370 } 1371 1372 // Pointer to the hash is no longer valid once the last handle with the 1373 // given hash is released. Null out the pointer so that we crash if there 1374 // is a bug in this code and we dereference the pointer after this point. 1375 if (!h->IsSpecialFile()) { 1376 h->mHash = nullptr; 1377 } 1378 } 1379 1380 // Assert the table is empty. When we are here, no new handles can be added 1381 // and handles will no longer remove them self from this table and we don't 1382 // want to keep invalid handles here. Also, there is no lookup after this 1383 // point to happen. 1384 MOZ_ASSERT(mHandles.HandleCount() == 0); 1385 1386 // Release trash directory enumerator 1387 if (mTrashDirEnumerator) { 1388 mTrashDirEnumerator->Close(); 1389 mTrashDirEnumerator = nullptr; 1390 } 1391 1392 if (mContextEvictor) { 1393 mContextEvictor->Shutdown(); 1394 mContextEvictor = nullptr; 1395 } 1396 } 1397 1398 // static 1399 nsresult CacheFileIOManager::OnProfile() { 1400 LOG(("CacheFileIOManager::OnProfile() [gInstance=%p]", gInstance.get())); 1401 1402 RefPtr<CacheFileIOManager> ioMan = gInstance; 1403 if (!ioMan) { 1404 // CacheFileIOManager::Init() failed, probably could not create the IO 1405 // thread, just go with it... 1406 return NS_ERROR_NOT_INITIALIZED; 1407 } 1408 1409 nsresult rv; 1410 1411 nsCOMPtr<nsIFile> directory; 1412 1413 CacheObserver::ParentDirOverride(getter_AddRefs(directory)); 1414 1415 #if defined(MOZ_WIDGET_ANDROID) 1416 nsCOMPtr<nsIFile> profilelessDirectory; 1417 char* cachePath = getenv("CACHE_DIRECTORY"); 1418 if (!directory && cachePath && *cachePath) { 1419 rv = NS_NewNativeLocalFile(nsDependentCString(cachePath), 1420 getter_AddRefs(directory)); 1421 if (NS_SUCCEEDED(rv)) { 1422 // Save this directory as the profileless path. 1423 rv = directory->Clone(getter_AddRefs(profilelessDirectory)); 1424 NS_ENSURE_SUCCESS(rv, rv); 1425 1426 // Add profile leaf name to the directory name to distinguish 1427 // multiple profiles Fennec supports. 1428 nsCOMPtr<nsIFile> profD; 1429 rv = NS_GetSpecialDirectory(NS_APP_USER_PROFILE_50_DIR, 1430 getter_AddRefs(profD)); 1431 1432 nsAutoCString leafName; 1433 if (NS_SUCCEEDED(rv)) { 1434 rv = profD->GetNativeLeafName(leafName); 1435 } 1436 if (NS_SUCCEEDED(rv)) { 1437 rv = directory->AppendNative(leafName); 1438 } 1439 if (NS_FAILED(rv)) { 1440 directory = nullptr; 1441 } 1442 } 1443 } 1444 #endif 1445 1446 if (!directory) { 1447 rv = NS_GetSpecialDirectory(NS_APP_CACHE_PARENT_DIR, 1448 getter_AddRefs(directory)); 1449 } 1450 1451 if (!directory) { 1452 rv = NS_GetSpecialDirectory(NS_APP_USER_PROFILE_LOCAL_50_DIR, 1453 getter_AddRefs(directory)); 1454 } 1455 1456 if (directory) { 1457 rv = directory->Append(u"cache2"_ns); 1458 NS_ENSURE_SUCCESS(rv, rv); 1459 } 1460 1461 // All functions return a clone. 1462 ioMan->mCacheDirectory.swap(directory); 1463 1464 #if defined(MOZ_WIDGET_ANDROID) 1465 if (profilelessDirectory) { 1466 rv = profilelessDirectory->Append(u"cache2"_ns); 1467 NS_ENSURE_SUCCESS(rv, rv); 1468 } 1469 1470 ioMan->mCacheProfilelessDirectory.swap(profilelessDirectory); 1471 #endif 1472 1473 if (ioMan->mCacheDirectory) { 1474 CacheIndex::Init(ioMan->mCacheDirectory); 1475 } 1476 1477 return NS_OK; 1478 } 1479 1480 static bool inBackgroundTask() { 1481 MOZ_ASSERT(NS_IsMainThread(), "backgroundtasks are main thread only"); 1482 #if defined(MOZ_BACKGROUNDTASKS) 1483 nsCOMPtr<nsIBackgroundTasks> backgroundTaskService = 1484 do_GetService("@mozilla.org/backgroundtasks;1"); 1485 if (!backgroundTaskService) { 1486 return false; 1487 } 1488 bool isBackgroundTask = false; 1489 backgroundTaskService->GetIsBackgroundTaskMode(&isBackgroundTask); 1490 return isBackgroundTask; 1491 #else 1492 return false; 1493 #endif 1494 } 1495 1496 // static 1497 nsresult CacheFileIOManager::OnDelayedStartupFinished() { 1498 // If we don't clear the cache at shutdown, or we don't use a 1499 // background task then there's no need to dispatch a cleanup task 1500 // at startup 1501 if (!CacheObserver::ClearCacheOnShutdown()) { 1502 return NS_OK; 1503 } 1504 if (!StaticPrefs::network_cache_shutdown_purge_in_background_task()) { 1505 return NS_OK; 1506 } 1507 1508 // If this is a background task already, there's no need to 1509 // dispatch another one. 1510 if (inBackgroundTask()) { 1511 return NS_OK; 1512 } 1513 1514 RefPtr<CacheFileIOManager> ioMan = gInstance; 1515 nsCOMPtr<nsIEventTarget> target = IOTarget(); 1516 if (NS_WARN_IF(!ioMan || !target)) { 1517 return NS_ERROR_NOT_AVAILABLE; 1518 } 1519 1520 return target->Dispatch( 1521 NS_NewRunnableFunction("CacheFileIOManager::OnDelayedStartupFinished", 1522 [ioMan = std::move(ioMan)] { 1523 ioMan->DispatchPurgeTask(""_ns, "0"_ns, 1524 kPurgeExtension); 1525 }), 1526 nsIEventTarget::DISPATCH_NORMAL); 1527 } 1528 1529 // static 1530 nsresult CacheFileIOManager::OnIdleDaily() { 1531 // In case the background task process fails for some reason (bug 1848542) 1532 // We will remove the directories in the main process on a daily idle. 1533 if (!CacheObserver::ClearCacheOnShutdown()) { 1534 return NS_OK; 1535 } 1536 if (!StaticPrefs::network_cache_shutdown_purge_in_background_task()) { 1537 return NS_OK; 1538 } 1539 1540 RefPtr<CacheFileIOManager> ioMan = gInstance; 1541 nsCOMPtr<nsIFile> parentDir; 1542 nsresult rv = ioMan->mCacheDirectory->GetParent(getter_AddRefs(parentDir)); 1543 if (NS_FAILED(rv) || !parentDir) { 1544 return NS_OK; 1545 } 1546 1547 NS_DispatchBackgroundTask( 1548 NS_NewRunnableFunction( 1549 "CacheFileIOManager::CheckLeftoverFolders", 1550 [dir = std::move(parentDir)] { 1551 nsCOMPtr<nsIDirectoryEnumerator> directories; 1552 nsresult rv = dir->GetDirectoryEntries(getter_AddRefs(directories)); 1553 if (NS_FAILED(rv)) { 1554 return; 1555 } 1556 bool hasMoreElements = false; 1557 while ( 1558 NS_SUCCEEDED(directories->HasMoreElements(&hasMoreElements)) && 1559 hasMoreElements) { 1560 nsCOMPtr<nsIFile> subdir; 1561 rv = directories->GetNextFile(getter_AddRefs(subdir)); 1562 if (NS_FAILED(rv) || !subdir) { 1563 break; 1564 } 1565 nsAutoCString leafName; 1566 rv = subdir->GetNativeLeafName(leafName); 1567 if (NS_FAILED(rv)) { 1568 continue; 1569 } 1570 if (leafName.Find(kPurgeExtension) != kNotFound) { 1571 mozilla::glean::networking::residual_cache_folder_count.Add(1); 1572 rv = subdir->Remove(true); 1573 if (NS_SUCCEEDED(rv)) { 1574 mozilla::glean::networking::residual_cache_folder_removal 1575 .Get("success"_ns) 1576 .Add(1); 1577 } else { 1578 mozilla::glean::networking::residual_cache_folder_removal 1579 .Get("failure"_ns) 1580 .Add(1); 1581 } 1582 } 1583 } 1584 1585 return; 1586 }), 1587 NS_DISPATCH_EVENT_MAY_BLOCK); 1588 1589 return NS_OK; 1590 } 1591 1592 // static 1593 already_AddRefed<nsIEventTarget> CacheFileIOManager::IOTarget() { 1594 nsCOMPtr<nsIEventTarget> target; 1595 if (gInstance && gInstance->mIOThread) { 1596 target = gInstance->mIOThread->Target(); 1597 } 1598 1599 return target.forget(); 1600 } 1601 1602 // static 1603 already_AddRefed<CacheIOThread> CacheFileIOManager::IOThread() { 1604 RefPtr<CacheIOThread> thread; 1605 if (gInstance) { 1606 thread = gInstance->mIOThread; 1607 } 1608 1609 return thread.forget(); 1610 } 1611 1612 // static 1613 bool CacheFileIOManager::IsOnIOThread() { 1614 RefPtr<CacheFileIOManager> ioMan = gInstance; 1615 if (ioMan && ioMan->mIOThread) { 1616 return ioMan->mIOThread->IsCurrentThread(); 1617 } 1618 1619 return false; 1620 } 1621 1622 // static 1623 bool CacheFileIOManager::IsOnIOThreadOrCeased() { 1624 RefPtr<CacheFileIOManager> ioMan = gInstance; 1625 if (ioMan && ioMan->mIOThread) { 1626 return ioMan->mIOThread->IsCurrentThread(); 1627 } 1628 1629 // Ceased... 1630 return true; 1631 } 1632 1633 // static 1634 bool CacheFileIOManager::IsShutdown() { 1635 if (!gInstance) { 1636 return true; 1637 } 1638 return gInstance->mShuttingDown; 1639 } 1640 1641 // static 1642 nsresult CacheFileIOManager::ScheduleMetadataWrite(CacheFile* aFile) { 1643 RefPtr<CacheFileIOManager> ioMan = gInstance; 1644 NS_ENSURE_TRUE(ioMan, NS_ERROR_NOT_INITIALIZED); 1645 1646 NS_ENSURE_TRUE(!ioMan->mShuttingDown, NS_ERROR_NOT_INITIALIZED); 1647 1648 RefPtr<MetadataWriteScheduleEvent> event = new MetadataWriteScheduleEvent( 1649 ioMan, aFile, MetadataWriteScheduleEvent::SCHEDULE); 1650 nsCOMPtr<nsIEventTarget> target = ioMan->IOTarget(); 1651 NS_ENSURE_TRUE(target, NS_ERROR_UNEXPECTED); 1652 return target->Dispatch(event, nsIEventTarget::DISPATCH_NORMAL); 1653 } 1654 1655 nsresult CacheFileIOManager::ScheduleMetadataWriteInternal(CacheFile* aFile) { 1656 MOZ_ASSERT(IsOnIOThreadOrCeased()); 1657 1658 nsresult rv; 1659 1660 if (!mMetadataWritesTimer) { 1661 rv = NS_NewTimerWithCallback(getter_AddRefs(mMetadataWritesTimer), this, 1662 kMetadataWriteDelay, nsITimer::TYPE_ONE_SHOT); 1663 NS_ENSURE_SUCCESS(rv, rv); 1664 } 1665 1666 if (mScheduledMetadataWrites.IndexOf(aFile) != 1667 nsTArray<RefPtr<mozilla::net::CacheFile>>::NoIndex) { 1668 return NS_OK; 1669 } 1670 1671 mScheduledMetadataWrites.AppendElement(aFile); 1672 1673 return NS_OK; 1674 } 1675 1676 // static 1677 nsresult CacheFileIOManager::UnscheduleMetadataWrite(CacheFile* aFile) { 1678 RefPtr<CacheFileIOManager> ioMan = gInstance; 1679 NS_ENSURE_TRUE(ioMan, NS_ERROR_NOT_INITIALIZED); 1680 1681 NS_ENSURE_TRUE(!ioMan->mShuttingDown, NS_ERROR_NOT_INITIALIZED); 1682 1683 RefPtr<MetadataWriteScheduleEvent> event = new MetadataWriteScheduleEvent( 1684 ioMan, aFile, MetadataWriteScheduleEvent::UNSCHEDULE); 1685 nsCOMPtr<nsIEventTarget> target = ioMan->IOTarget(); 1686 NS_ENSURE_TRUE(target, NS_ERROR_UNEXPECTED); 1687 return target->Dispatch(event, nsIEventTarget::DISPATCH_NORMAL); 1688 } 1689 1690 void CacheFileIOManager::UnscheduleMetadataWriteInternal(CacheFile* aFile) { 1691 MOZ_ASSERT(IsOnIOThreadOrCeased()); 1692 1693 mScheduledMetadataWrites.RemoveElement(aFile); 1694 1695 if (mScheduledMetadataWrites.Length() == 0 && mMetadataWritesTimer) { 1696 mMetadataWritesTimer->Cancel(); 1697 mMetadataWritesTimer = nullptr; 1698 } 1699 } 1700 1701 // static 1702 nsresult CacheFileIOManager::ShutdownMetadataWriteScheduling() { 1703 RefPtr<CacheFileIOManager> ioMan = gInstance; 1704 NS_ENSURE_TRUE(ioMan, NS_ERROR_NOT_INITIALIZED); 1705 1706 RefPtr<MetadataWriteScheduleEvent> event = new MetadataWriteScheduleEvent( 1707 ioMan, nullptr, MetadataWriteScheduleEvent::SHUTDOWN); 1708 nsCOMPtr<nsIEventTarget> target = ioMan->IOTarget(); 1709 NS_ENSURE_TRUE(target, NS_ERROR_UNEXPECTED); 1710 return target->Dispatch(event, nsIEventTarget::DISPATCH_NORMAL); 1711 } 1712 1713 void CacheFileIOManager::ShutdownMetadataWriteSchedulingInternal() { 1714 MOZ_ASSERT(IsOnIOThreadOrCeased()); 1715 1716 nsTArray<RefPtr<CacheFile>> files = std::move(mScheduledMetadataWrites); 1717 for (uint32_t i = 0; i < files.Length(); ++i) { 1718 CacheFile* file = files[i]; 1719 file->WriteMetadataIfNeeded(); 1720 } 1721 1722 if (mMetadataWritesTimer) { 1723 mMetadataWritesTimer->Cancel(); 1724 mMetadataWritesTimer = nullptr; 1725 } 1726 } 1727 1728 NS_IMETHODIMP 1729 CacheFileIOManager::Notify(nsITimer* aTimer) { 1730 MOZ_ASSERT(IsOnIOThreadOrCeased()); 1731 MOZ_ASSERT(mMetadataWritesTimer == aTimer); 1732 1733 mMetadataWritesTimer = nullptr; 1734 1735 nsTArray<RefPtr<CacheFile>> files = std::move(mScheduledMetadataWrites); 1736 for (uint32_t i = 0; i < files.Length(); ++i) { 1737 CacheFile* file = files[i]; 1738 file->WriteMetadataIfNeeded(); 1739 } 1740 1741 return NS_OK; 1742 } 1743 1744 NS_IMETHODIMP 1745 CacheFileIOManager::GetName(nsACString& aName) { 1746 aName.AssignLiteral("CacheFileIOManager"); 1747 return NS_OK; 1748 } 1749 1750 // static 1751 nsresult CacheFileIOManager::OpenFile(const nsACString& aKey, uint32_t aFlags, 1752 CacheFileIOListener* aCallback) { 1753 LOG(("CacheFileIOManager::OpenFile() [key=%s, flags=%d, listener=%p]", 1754 PromiseFlatCString(aKey).get(), aFlags, aCallback)); 1755 1756 nsresult rv; 1757 RefPtr<CacheFileIOManager> ioMan = gInstance; 1758 1759 if (!ioMan) { 1760 return NS_ERROR_NOT_INITIALIZED; 1761 } 1762 1763 bool priority = aFlags & CacheFileIOManager::PRIORITY; 1764 RefPtr<OpenFileEvent> ev = new OpenFileEvent(aKey, aFlags, aCallback); 1765 rv = ioMan->mIOThread->Dispatch( 1766 ev, priority ? CacheIOThread::OPEN_PRIORITY : CacheIOThread::OPEN); 1767 NS_ENSURE_SUCCESS(rv, rv); 1768 1769 return NS_OK; 1770 } 1771 1772 nsresult CacheFileIOManager::OpenFileInternal(const SHA1Sum::Hash* aHash, 1773 const nsACString& aKey, 1774 uint32_t aFlags, 1775 CacheFileHandle** _retval) { 1776 LOG( 1777 ("CacheFileIOManager::OpenFileInternal() [hash=%08x%08x%08x%08x%08x, " 1778 "key=%s, flags=%d]", 1779 LOGSHA1(aHash), PromiseFlatCString(aKey).get(), aFlags)); 1780 1781 MOZ_ASSERT(CacheFileIOManager::IsOnIOThread()); 1782 1783 nsresult rv; 1784 1785 if (mShuttingDown) { 1786 return NS_ERROR_NOT_INITIALIZED; 1787 } 1788 1789 CacheIOThread::Cancelable cancelable( 1790 true /* never called for special handles */); 1791 1792 if (!mTreeCreated) { 1793 rv = CreateCacheTree(); 1794 if (NS_FAILED(rv)) return rv; 1795 } 1796 1797 CacheFileHandle::PinningStatus pinning = 1798 aFlags & PINNED ? CacheFileHandle::PinningStatus::PINNED 1799 : CacheFileHandle::PinningStatus::NON_PINNED; 1800 1801 nsCOMPtr<nsIFile> file; 1802 rv = GetFile(aHash, getter_AddRefs(file)); 1803 NS_ENSURE_SUCCESS(rv, rv); 1804 1805 RefPtr<CacheFileHandle> handle; 1806 mHandles.GetHandle(aHash, getter_AddRefs(handle)); 1807 1808 if ((aFlags & (OPEN | CREATE | CREATE_NEW)) == CREATE_NEW) { 1809 if (handle) { 1810 rv = DoomFileInternal(handle); 1811 NS_ENSURE_SUCCESS(rv, rv); 1812 handle = nullptr; 1813 } 1814 1815 handle = mHandles.NewHandle(aHash, aFlags & PRIORITY, pinning); 1816 1817 bool exists; 1818 rv = file->Exists(&exists); 1819 NS_ENSURE_SUCCESS(rv, rv); 1820 1821 if (exists) { 1822 CacheIndex::RemoveEntry(aHash, handle->Key()); 1823 1824 LOG( 1825 ("CacheFileIOManager::OpenFileInternal() - Removing old file from " 1826 "disk")); 1827 rv = file->Remove(false); 1828 if (NS_FAILED(rv)) { 1829 NS_WARNING("Cannot remove old entry from the disk"); 1830 LOG( 1831 ("CacheFileIOManager::OpenFileInternal() - Removing old file failed" 1832 ". [rv=0x%08" PRIx32 "]", 1833 static_cast<uint32_t>(rv))); 1834 } 1835 } 1836 1837 CacheIndex::AddEntry(aHash); 1838 handle->mFile.swap(file); 1839 handle->mFileSize = 0; 1840 } 1841 1842 if (handle) { 1843 handle.swap(*_retval); 1844 return NS_OK; 1845 } 1846 1847 bool exists, evictedAsPinned = false, evictedAsNonPinned = false; 1848 rv = file->Exists(&exists); 1849 NS_ENSURE_SUCCESS(rv, rv); 1850 1851 if (exists && mContextEvictor) { 1852 if (mContextEvictor->ContextsCount() == 0) { 1853 mContextEvictor = nullptr; 1854 } else { 1855 mContextEvictor->WasEvicted(aKey, file, &evictedAsPinned, 1856 &evictedAsNonPinned); 1857 } 1858 } 1859 1860 if (!exists && (aFlags & (OPEN | CREATE | CREATE_NEW)) == OPEN) { 1861 return NS_ERROR_NOT_AVAILABLE; 1862 } 1863 1864 if (exists) { 1865 // For existing files we determine the pinning status later, after the 1866 // metadata gets parsed. 1867 pinning = CacheFileHandle::PinningStatus::UNKNOWN; 1868 } 1869 1870 handle = mHandles.NewHandle(aHash, aFlags & PRIORITY, pinning); 1871 if (exists) { 1872 // If this file has been found evicted through the context file evictor 1873 // above for any of pinned or non-pinned state, these calls ensure we doom 1874 // the handle ASAP we know the real pinning state after metadata has been 1875 // parsed. DoomFileInternal on the |handle| doesn't doom right now, since 1876 // the pinning state is unknown and we pass down a pinning restriction. 1877 if (evictedAsPinned) { 1878 rv = DoomFileInternal(handle, DOOM_WHEN_PINNED); 1879 MOZ_ASSERT(!handle->IsDoomed() && NS_SUCCEEDED(rv)); 1880 } 1881 if (evictedAsNonPinned) { 1882 rv = DoomFileInternal(handle, DOOM_WHEN_NON_PINNED); 1883 MOZ_ASSERT(!handle->IsDoomed() && NS_SUCCEEDED(rv)); 1884 } 1885 1886 int64_t fileSize = -1; 1887 rv = file->GetFileSize(&fileSize); 1888 NS_ENSURE_SUCCESS(rv, rv); 1889 1890 handle->mFileSize = fileSize; 1891 handle->mFileExists = true; 1892 1893 CacheIndex::EnsureEntryExists(aHash); 1894 } else { 1895 handle->mFileSize = 0; 1896 1897 CacheIndex::AddEntry(aHash); 1898 } 1899 1900 handle->mFile.swap(file); 1901 handle.swap(*_retval); 1902 return NS_OK; 1903 } 1904 1905 nsresult CacheFileIOManager::OpenSpecialFileInternal( 1906 const nsACString& aKey, uint32_t aFlags, CacheFileHandle** _retval) { 1907 LOG(("CacheFileIOManager::OpenSpecialFileInternal() [key=%s, flags=%d]", 1908 PromiseFlatCString(aKey).get(), aFlags)); 1909 1910 MOZ_ASSERT(CacheFileIOManager::IsOnIOThread()); 1911 1912 nsresult rv; 1913 1914 if (mShuttingDown) { 1915 return NS_ERROR_NOT_INITIALIZED; 1916 } 1917 1918 if (!mTreeCreated) { 1919 rv = CreateCacheTree(); 1920 if (NS_FAILED(rv)) return rv; 1921 } 1922 1923 nsCOMPtr<nsIFile> file; 1924 rv = GetSpecialFile(aKey, getter_AddRefs(file)); 1925 NS_ENSURE_SUCCESS(rv, rv); 1926 1927 RefPtr<CacheFileHandle> handle; 1928 for (uint32_t i = 0; i < mSpecialHandles.Length(); i++) { 1929 if (!mSpecialHandles[i]->IsDoomed() && mSpecialHandles[i]->Key() == aKey) { 1930 handle = mSpecialHandles[i]; 1931 break; 1932 } 1933 } 1934 1935 if ((aFlags & (OPEN | CREATE | CREATE_NEW)) == CREATE_NEW) { 1936 if (handle) { 1937 rv = DoomFileInternal(handle); 1938 NS_ENSURE_SUCCESS(rv, rv); 1939 handle = nullptr; 1940 } 1941 1942 handle = new CacheFileHandle(aKey, aFlags & PRIORITY, 1943 CacheFileHandle::PinningStatus::NON_PINNED); 1944 mSpecialHandles.AppendElement(handle); 1945 1946 bool exists; 1947 rv = file->Exists(&exists); 1948 NS_ENSURE_SUCCESS(rv, rv); 1949 1950 if (exists) { 1951 LOG( 1952 ("CacheFileIOManager::OpenSpecialFileInternal() - Removing file from " 1953 "disk")); 1954 rv = file->Remove(false); 1955 if (NS_FAILED(rv)) { 1956 NS_WARNING("Cannot remove old entry from the disk"); 1957 LOG( 1958 ("CacheFileIOManager::OpenSpecialFileInternal() - Removing file " 1959 "failed. [rv=0x%08" PRIx32 "]", 1960 static_cast<uint32_t>(rv))); 1961 } 1962 } 1963 1964 handle->mFile.swap(file); 1965 handle->mFileSize = 0; 1966 } 1967 1968 if (handle) { 1969 handle.swap(*_retval); 1970 return NS_OK; 1971 } 1972 1973 bool exists; 1974 rv = file->Exists(&exists); 1975 NS_ENSURE_SUCCESS(rv, rv); 1976 1977 if (!exists && (aFlags & (OPEN | CREATE | CREATE_NEW)) == OPEN) { 1978 return NS_ERROR_NOT_AVAILABLE; 1979 } 1980 1981 handle = new CacheFileHandle(aKey, aFlags & PRIORITY, 1982 CacheFileHandle::PinningStatus::NON_PINNED); 1983 mSpecialHandles.AppendElement(handle); 1984 1985 if (exists) { 1986 int64_t fileSize = -1; 1987 rv = file->GetFileSize(&fileSize); 1988 NS_ENSURE_SUCCESS(rv, rv); 1989 1990 handle->mFileSize = fileSize; 1991 handle->mFileExists = true; 1992 } else { 1993 handle->mFileSize = 0; 1994 } 1995 1996 handle->mFile.swap(file); 1997 handle.swap(*_retval); 1998 return NS_OK; 1999 } 2000 2001 void CacheFileIOManager::CloseHandleInternal(CacheFileHandle* aHandle) { 2002 nsresult rv; 2003 LOG(("CacheFileIOManager::CloseHandleInternal() [handle=%p]", aHandle)); 2004 2005 MOZ_ASSERT(!aHandle->IsClosed()); 2006 2007 aHandle->Log(); 2008 2009 MOZ_ASSERT(CacheFileIOManager::IsOnIOThreadOrCeased()); 2010 2011 CacheIOThread::Cancelable cancelable(!aHandle->IsSpecialFile()); 2012 2013 // Maybe close file handle (can be legally bypassed after shutdown) 2014 rv = MaybeReleaseNSPRHandleInternal(aHandle); 2015 2016 // Delete the file if the entry was doomed or invalid and 2017 // filedesc properly closed 2018 if ((aHandle->mIsDoomed || aHandle->mInvalid) && aHandle->mFileExists && 2019 NS_SUCCEEDED(rv)) { 2020 LOG( 2021 ("CacheFileIOManager::CloseHandleInternal() - Removing file from " 2022 "disk")); 2023 2024 rv = aHandle->mFile->Remove(false); 2025 if (NS_SUCCEEDED(rv)) { 2026 aHandle->mFileExists = false; 2027 } else { 2028 LOG((" failed to remove the file [rv=0x%08" PRIx32 "]", 2029 static_cast<uint32_t>(rv))); 2030 } 2031 } 2032 2033 if (!aHandle->IsSpecialFile() && !aHandle->mIsDoomed && 2034 (aHandle->mInvalid || !aHandle->mFileExists)) { 2035 CacheIndex::RemoveEntry(aHandle->Hash(), aHandle->Key()); 2036 } 2037 2038 // Don't remove handles after shutdown 2039 if (!mShuttingDown) { 2040 if (aHandle->IsSpecialFile()) { 2041 mSpecialHandles.RemoveElement(aHandle); 2042 } else { 2043 mHandles.RemoveHandle(aHandle); 2044 } 2045 } 2046 } 2047 2048 // static 2049 nsresult CacheFileIOManager::Read(CacheFileHandle* aHandle, int64_t aOffset, 2050 char* aBuf, int32_t aCount, 2051 CacheFileIOListener* aCallback) { 2052 LOG(("CacheFileIOManager::Read() [handle=%p, offset=%" PRId64 ", count=%d, " 2053 "listener=%p]", 2054 aHandle, aOffset, aCount, aCallback)); 2055 2056 if (CacheObserver::ShuttingDown()) { 2057 LOG((" no reads after shutdown")); 2058 return NS_ERROR_NOT_INITIALIZED; 2059 } 2060 2061 if (!aHandle) { 2062 return NS_ERROR_NULL_POINTER; 2063 } 2064 2065 nsresult rv; 2066 RefPtr<CacheFileIOManager> ioMan = gInstance; 2067 2068 if (aHandle->IsClosed() || !ioMan) { 2069 return NS_ERROR_NOT_INITIALIZED; 2070 } 2071 2072 RefPtr<ReadEvent> ev = 2073 new ReadEvent(aHandle, aOffset, aBuf, aCount, aCallback); 2074 #if defined(MOZ_CACHE_ASYNC_IO) 2075 if (IsOnIOThread()) { 2076 // If we are already on the IO thread, we can run the event 2077 // inline. However, preserve the behavior that OnDataRead will be 2078 // called async when read operation is unsucessful in case the 2079 // caller relies this assumption. 2080 ev->InlineRun(true); 2081 return NS_OK; 2082 } 2083 #endif 2084 2085 rv = ioMan->mIOThread->Dispatch(ev, aHandle->IsPriority() 2086 ? CacheIOThread::READ_PRIORITY 2087 : CacheIOThread::READ); 2088 NS_ENSURE_SUCCESS(rv, rv); 2089 2090 return NS_OK; 2091 } 2092 2093 nsresult CacheFileIOManager::ReadInternal(CacheFileHandle* aHandle, 2094 int64_t aOffset, char* aBuf, 2095 int32_t aCount, 2096 ReadEvent* aReadEvent) { 2097 LOG(("CacheFileIOManager::ReadInternal() [handle=%p, offset=%" PRId64 2098 ", count=%d]", 2099 aHandle, aOffset, aCount)); 2100 2101 nsresult rv; 2102 2103 if (CacheObserver::ShuttingDown()) { 2104 LOG((" no reads after shutdown")); 2105 return NS_ERROR_NOT_INITIALIZED; 2106 } 2107 2108 if (!aHandle->mFileExists) { 2109 NS_WARNING("Trying to read from non-existent file"); 2110 return NS_ERROR_NOT_AVAILABLE; 2111 } 2112 2113 CacheIOThread::Cancelable cancelable(!aHandle->IsSpecialFile()); 2114 2115 if (!aHandle->mFD) { 2116 rv = OpenNSPRHandle(aHandle); 2117 NS_ENSURE_SUCCESS(rv, rv); 2118 } else { 2119 NSPRHandleUsed(aHandle); 2120 } 2121 2122 // Check again, OpenNSPRHandle could figure out the file was gone. 2123 if (!aHandle->mFileExists) { 2124 NS_WARNING("Trying to read from non-existent file"); 2125 return NS_ERROR_NOT_AVAILABLE; 2126 } 2127 2128 MOZ_ASSERT(aOffset + aCount <= aHandle->mFileSize); 2129 2130 #if !defined(MOZ_CACHE_ASYNC_IO) 2131 int64_t offset = PR_Seek64(aHandle->mFD, aOffset, PR_SEEK_SET); 2132 if (offset == -1) { 2133 return NS_ERROR_FAILURE; 2134 } 2135 2136 int32_t bytesRead = PR_Read(aHandle->mFD, aBuf, aCount); 2137 if (bytesRead != aCount) { 2138 return NS_ERROR_FAILURE; 2139 } 2140 #else 2141 PRFileDesc* fd; 2142 AutoFDClose autoFd; 2143 2144 if (!aHandle->IsAsyncOperationRunning()) { 2145 // If the handle is not running an async operation, we can use the 2146 // file descriptor directly without extra file open operation. 2147 fd = aHandle->mFD; 2148 } else { 2149 PRFileDesc* rawFd = nullptr; 2150 rv = aHandle->mFile->OpenNSPRFileDesc(PR_RDONLY, 0600, &rawFd); 2151 if (NS_FAILED(rv)) { 2152 return NS_ERROR_FAILURE; 2153 } 2154 2155 // Assign the raw pointer to AutoFDClose 2156 autoFd.reset(rawFd); 2157 fd = autoFd.get(); 2158 } 2159 2160 nsCOMPtr<nsIEventTarget> target = 2161 do_GetService(NS_STREAMTRANSPORTSERVICE_CONTRACTID, &rv); 2162 MOZ_ASSERT(target); 2163 const bool isPriority = aHandle->IsPriority(); 2164 rv = target->Dispatch( 2165 NS_NewRunnableFunction( 2166 "CacheFileIOManager::ReadInternal", 2167 [fd, autoFd = std::move(autoFd), aBuf, aCount, aOffset, 2168 readevent = RefPtr(aReadEvent), isPriority]() mutable { 2169 nsresult rv = NS_OK; 2170 int64_t offset = PR_Seek64(fd, aOffset, PR_SEEK_SET); 2171 if (offset == -1) { 2172 LOG( 2173 ("CacheFileIOManager::ReadInternal() - PR_Seek64 failed " 2174 "[offset=%d]\n", 2175 static_cast<int32_t>(aOffset))); 2176 rv = NS_ERROR_FAILURE; 2177 } 2178 2179 if (NS_SUCCEEDED(rv)) { 2180 int32_t bytesRead = PR_Read(fd, aBuf, aCount); 2181 if (bytesRead != aCount) { 2182 LOG( 2183 ("CacheFileIOManager::ReadInternal() - PR_Read failed " 2184 "[bytesRead=%d]\n", 2185 bytesRead)); 2186 rv = NS_ERROR_FAILURE; 2187 } 2188 } 2189 2190 // Post completion back to the cache IO thread at the READ level, 2191 // so that test suspensions at READ properly block completion. 2192 DebugOnly<nsresult> rv_dispatch = gInstance->mIOThread->Dispatch( 2193 NewRunnableMethod<nsresult>( 2194 "CacheFileIOManager::ReadEvent::OnComplete", readevent, 2195 &ReadEvent::OnComplete, rv), 2196 isPriority ? CacheIOThread::READ_PRIORITY 2197 : CacheIOThread::READ); 2198 2199 MOZ_ASSERT(NS_SUCCEEDED(rv_dispatch)); 2200 }), 2201 NS_DISPATCH_NORMAL); 2202 NS_ENSURE_SUCCESS(rv, rv); 2203 2204 aHandle->StartAsyncOperation(); 2205 #endif 2206 2207 return NS_OK; 2208 } 2209 2210 // static 2211 nsresult CacheFileIOManager::Write(CacheFileHandle* aHandle, int64_t aOffset, 2212 const char* aBuf, int32_t aCount, 2213 bool aValidate, bool aTruncate, 2214 CacheFileIOListener* aCallback) { 2215 LOG(("CacheFileIOManager::Write() [handle=%p, offset=%" PRId64 ", count=%d, " 2216 "validate=%d, truncate=%d, listener=%p]", 2217 aHandle, aOffset, aCount, aValidate, aTruncate, aCallback)); 2218 2219 MOZ_ASSERT(aCallback); 2220 2221 RefPtr<CacheFileIOManager> ioMan = gInstance; 2222 2223 if (aHandle->IsClosed() || aCallback->IsKilled() || !ioMan) { 2224 return NS_ERROR_NOT_INITIALIZED; 2225 } 2226 2227 RefPtr<WriteEvent> ev = new WriteEvent(aHandle, aOffset, aBuf, aCount, 2228 aValidate, aTruncate, aCallback); 2229 return ioMan->mIOThread->Dispatch(ev, aHandle->mPriority 2230 ? CacheIOThread::WRITE_PRIORITY 2231 : CacheIOThread::WRITE); 2232 } 2233 2234 // static 2235 nsresult CacheFileIOManager::WriteWithoutCallback(CacheFileHandle* aHandle, 2236 int64_t aOffset, char* aBuf, 2237 int32_t aCount, 2238 bool aValidate, 2239 bool aTruncate) { 2240 LOG(("CacheFileIOManager::WriteWithoutCallback() [handle=%p, offset=%" PRId64 2241 ", count=%d, " 2242 "validate=%d, truncate=%d]", 2243 aHandle, aOffset, aCount, aValidate, aTruncate)); 2244 2245 RefPtr<CacheFileIOManager> ioMan = gInstance; 2246 2247 if (aHandle->IsClosed() || !ioMan) { 2248 // When no callback is provided, CacheFileIOManager is responsible for 2249 // releasing the buffer. We must release it even in case of failure. 2250 free(aBuf); 2251 return NS_ERROR_NOT_INITIALIZED; 2252 } 2253 2254 RefPtr<WriteEvent> ev = new WriteEvent(aHandle, aOffset, aBuf, aCount, 2255 aValidate, aTruncate, nullptr); 2256 return ioMan->mIOThread->Dispatch(ev, aHandle->mPriority 2257 ? CacheIOThread::WRITE_PRIORITY 2258 : CacheIOThread::WRITE); 2259 } 2260 2261 static nsresult TruncFile(PRFileDesc* aFD, int64_t aEOF) { 2262 #if defined(XP_UNIX) 2263 if (ftruncate(PR_FileDesc2NativeHandle(aFD), aEOF) != 0) { 2264 NS_ERROR("ftruncate failed"); 2265 return NS_ERROR_FAILURE; 2266 } 2267 #elif defined(XP_WIN) 2268 int64_t cnt = PR_Seek64(aFD, aEOF, PR_SEEK_SET); 2269 if (cnt == -1) { 2270 return NS_ERROR_FAILURE; 2271 } 2272 if (!SetEndOfFile((HANDLE)PR_FileDesc2NativeHandle(aFD))) { 2273 NS_ERROR("SetEndOfFile failed"); 2274 return NS_ERROR_FAILURE; 2275 } 2276 #else 2277 MOZ_ASSERT(false, "Not implemented!"); 2278 return NS_ERROR_NOT_IMPLEMENTED; 2279 #endif 2280 2281 return NS_OK; 2282 } 2283 2284 nsresult CacheFileIOManager::WriteInternal(CacheFileHandle* aHandle, 2285 int64_t aOffset, const char* aBuf, 2286 int32_t aCount, bool aValidate, 2287 bool aTruncate) { 2288 LOG(("CacheFileIOManager::WriteInternal() [handle=%p, offset=%" PRId64 2289 ", count=%d, " 2290 "validate=%d, truncate=%d]", 2291 aHandle, aOffset, aCount, aValidate, aTruncate)); 2292 2293 nsresult rv; 2294 2295 if (aHandle->mKilled) { 2296 LOG((" handle already killed, nothing written")); 2297 return NS_OK; 2298 } 2299 2300 if (CacheObserver::ShuttingDown() && (!aValidate || !aHandle->mFD)) { 2301 aHandle->mKilled = true; 2302 LOG((" killing the handle, nothing written")); 2303 return NS_OK; 2304 } 2305 2306 if (CacheObserver::IsPastShutdownIOLag()) { 2307 LOG((" past the shutdown I/O lag, nothing written")); 2308 // Pretend the write has succeeded, otherwise upper layers will doom 2309 // the file and we end up with I/O anyway. 2310 return NS_OK; 2311 } 2312 2313 CacheIOThread::Cancelable cancelable(!aHandle->IsSpecialFile()); 2314 2315 if (!aHandle->mFileExists) { 2316 rv = CreateFile(aHandle); 2317 NS_ENSURE_SUCCESS(rv, rv); 2318 } 2319 2320 if (!aHandle->mFD) { 2321 rv = OpenNSPRHandle(aHandle); 2322 NS_ENSURE_SUCCESS(rv, rv); 2323 } else { 2324 NSPRHandleUsed(aHandle); 2325 } 2326 2327 // Check again, OpenNSPRHandle could figure out the file was gone. 2328 if (!aHandle->mFileExists) { 2329 return NS_ERROR_NOT_AVAILABLE; 2330 } 2331 2332 // When this operation would increase cache size, check whether the cache size 2333 // reached the hard limit and whether it would cause critical low disk space. 2334 if (aHandle->mFileSize < aOffset + aCount) { 2335 if (mOverLimitEvicting && mCacheSizeOnHardLimit) { 2336 LOG( 2337 ("CacheFileIOManager::WriteInternal() - failing because cache size " 2338 "reached hard limit!")); 2339 return NS_ERROR_FILE_NO_DEVICE_SPACE; 2340 } 2341 2342 int64_t freeSpace; 2343 rv = mCacheDirectory->GetDiskSpaceAvailable(&freeSpace); 2344 if (NS_WARN_IF(NS_FAILED(rv))) { 2345 freeSpace = -1; 2346 LOG( 2347 ("CacheFileIOManager::WriteInternal() - GetDiskSpaceAvailable() " 2348 "failed! [rv=0x%08" PRIx32 "]", 2349 static_cast<uint32_t>(rv))); 2350 } else { 2351 freeSpace >>= 10; // bytes to kilobytes 2352 uint32_t limit = CacheObserver::DiskFreeSpaceHardLimit(); 2353 if (freeSpace - aOffset - aCount + aHandle->mFileSize < limit) { 2354 LOG( 2355 ("CacheFileIOManager::WriteInternal() - Low free space, refusing " 2356 "to write! [freeSpace=%" PRId64 "kB, limit=%ukB]", 2357 freeSpace, limit)); 2358 return NS_ERROR_FILE_NO_DEVICE_SPACE; 2359 } 2360 } 2361 } 2362 2363 // Write invalidates the entry by default 2364 aHandle->mInvalid = true; 2365 2366 int64_t offset = PR_Seek64(aHandle->mFD, aOffset, PR_SEEK_SET); 2367 if (offset == -1) { 2368 return NS_ERROR_FAILURE; 2369 } 2370 2371 int32_t bytesWritten = PR_Write(aHandle->mFD, aBuf, aCount); 2372 2373 if (bytesWritten != -1) { 2374 uint32_t oldSizeInK = aHandle->FileSizeInK(); 2375 int64_t writeEnd = aOffset + bytesWritten; 2376 2377 if (aTruncate) { 2378 rv = TruncFile(aHandle->mFD, writeEnd); 2379 NS_ENSURE_SUCCESS(rv, rv); 2380 2381 aHandle->mFileSize = writeEnd; 2382 } else { 2383 if (aHandle->mFileSize < writeEnd) { 2384 aHandle->mFileSize = writeEnd; 2385 } 2386 } 2387 2388 uint32_t newSizeInK = aHandle->FileSizeInK(); 2389 2390 if (oldSizeInK != newSizeInK && !aHandle->IsDoomed() && 2391 !aHandle->IsSpecialFile()) { 2392 CacheIndex::UpdateEntry(aHandle->Hash(), nullptr, nullptr, nullptr, 2393 nullptr, nullptr, &newSizeInK); 2394 2395 if (oldSizeInK < newSizeInK) { 2396 EvictIfOverLimitInternal(); 2397 } 2398 } 2399 2400 CacheIndex::UpdateTotalBytesWritten(bytesWritten); 2401 } 2402 2403 if (bytesWritten != aCount) { 2404 return NS_ERROR_FAILURE; 2405 } 2406 2407 // Write was successful and this write validates the entry (i.e. metadata) 2408 if (aValidate) { 2409 aHandle->mInvalid = false; 2410 } 2411 2412 return NS_OK; 2413 } 2414 2415 // static 2416 nsresult CacheFileIOManager::DoomFile(CacheFileHandle* aHandle, 2417 CacheFileIOListener* aCallback) { 2418 LOG(("CacheFileIOManager::DoomFile() [handle=%p, listener=%p]", aHandle, 2419 aCallback)); 2420 2421 nsresult rv; 2422 RefPtr<CacheFileIOManager> ioMan = gInstance; 2423 2424 if (aHandle->IsClosed() || !ioMan) { 2425 return NS_ERROR_NOT_INITIALIZED; 2426 } 2427 2428 RefPtr<DoomFileEvent> ev = new DoomFileEvent(aHandle, aCallback); 2429 rv = ioMan->mIOThread->Dispatch(ev, aHandle->IsPriority() 2430 ? CacheIOThread::OPEN_PRIORITY 2431 : CacheIOThread::OPEN); 2432 NS_ENSURE_SUCCESS(rv, rv); 2433 2434 return NS_OK; 2435 } 2436 2437 nsresult CacheFileIOManager::DoomFileInternal( 2438 CacheFileHandle* aHandle, PinningDoomRestriction aPinningDoomRestriction, 2439 bool aClearDictionary) { 2440 LOG(("CacheFileIOManager::DoomFileInternal() [handle=%p]", aHandle)); 2441 aHandle->Log(); 2442 2443 MOZ_ASSERT(CacheFileIOManager::IsOnIOThreadOrCeased()); 2444 2445 nsresult rv; 2446 2447 if (aHandle->IsDoomed()) { 2448 return NS_OK; 2449 } 2450 2451 CacheIOThread::Cancelable cancelable(!aHandle->IsSpecialFile()); 2452 2453 if (aPinningDoomRestriction > NO_RESTRICTION) { 2454 switch (aHandle->mPinning) { 2455 case CacheFileHandle::PinningStatus::NON_PINNED: 2456 if (MOZ_LIKELY(aPinningDoomRestriction != DOOM_WHEN_NON_PINNED)) { 2457 LOG((" not dooming, it's a non-pinned handle")); 2458 return NS_OK; 2459 } 2460 // Doom now 2461 break; 2462 2463 case CacheFileHandle::PinningStatus::PINNED: 2464 if (MOZ_UNLIKELY(aPinningDoomRestriction != DOOM_WHEN_PINNED)) { 2465 LOG((" not dooming, it's a pinned handle")); 2466 return NS_OK; 2467 } 2468 // Doom now 2469 break; 2470 2471 case CacheFileHandle::PinningStatus::UNKNOWN: 2472 if (MOZ_LIKELY(aPinningDoomRestriction == DOOM_WHEN_NON_PINNED)) { 2473 LOG((" doom when non-pinned set")); 2474 aHandle->mDoomWhenFoundNonPinned = true; 2475 } else if (MOZ_UNLIKELY(aPinningDoomRestriction == DOOM_WHEN_PINNED)) { 2476 LOG((" doom when pinned set")); 2477 aHandle->mDoomWhenFoundPinned = true; 2478 } 2479 2480 LOG((" pinning status not known, deferring doom decision")); 2481 return NS_OK; 2482 } 2483 } 2484 2485 if (aHandle->mFileExists) { 2486 // we need to move the current file to the doomed directory 2487 rv = MaybeReleaseNSPRHandleInternal(aHandle, true); 2488 NS_ENSURE_SUCCESS(rv, rv); 2489 2490 // find unused filename 2491 nsCOMPtr<nsIFile> file; 2492 rv = GetDoomedFile(getter_AddRefs(file)); 2493 NS_ENSURE_SUCCESS(rv, rv); 2494 2495 nsCOMPtr<nsIFile> parentDir; 2496 rv = file->GetParent(getter_AddRefs(parentDir)); 2497 NS_ENSURE_SUCCESS(rv, rv); 2498 2499 nsAutoCString leafName; 2500 rv = file->GetNativeLeafName(leafName); 2501 NS_ENSURE_SUCCESS(rv, rv); 2502 2503 rv = aHandle->mFile->MoveToNative(parentDir, leafName); 2504 if (NS_ERROR_FILE_NOT_FOUND == rv) { 2505 LOG((" file already removed under our hands")); 2506 aHandle->mFileExists = false; 2507 rv = NS_OK; 2508 } else { 2509 NS_ENSURE_SUCCESS(rv, rv); 2510 aHandle->mFile.swap(file); 2511 } 2512 } 2513 2514 if (!aHandle->IsSpecialFile()) { 2515 // Ensure the string doesn't disappear with the handle 2516 RefPtr<CacheFileHandle> handle(aHandle); 2517 CacheIndex::RemoveEntry(aHandle->Hash(), aHandle->Key(), aClearDictionary); 2518 } 2519 2520 aHandle->mIsDoomed = true; 2521 2522 if (!aHandle->IsSpecialFile()) { 2523 RefPtr<CacheStorageService> storageService = CacheStorageService::Self(); 2524 if (storageService) { 2525 nsAutoCString idExtension, url; 2526 nsCOMPtr<nsILoadContextInfo> info = 2527 CacheFileUtils::ParseKey(aHandle->Key(), &idExtension, &url); 2528 MOZ_ASSERT(info); 2529 if (info) { 2530 storageService->CacheFileDoomed(aHandle->mKey, info, idExtension, url); 2531 } 2532 } 2533 } 2534 2535 return NS_OK; 2536 } 2537 2538 // static 2539 nsresult CacheFileIOManager::DoomFileByKey(const nsACString& aKey, 2540 CacheFileIOListener* aCallback) { 2541 LOG(("CacheFileIOManager::DoomFileByKey() [key=%s, listener=%p]", 2542 PromiseFlatCString(aKey).get(), aCallback)); 2543 2544 nsresult rv; 2545 RefPtr<CacheFileIOManager> ioMan = gInstance; 2546 2547 if (!ioMan) { 2548 return NS_ERROR_NOT_INITIALIZED; 2549 } 2550 2551 RefPtr<DoomFileByKeyEvent> ev = new DoomFileByKeyEvent(aKey, aCallback); 2552 rv = ioMan->mIOThread->DispatchAfterPendingOpens(ev); 2553 NS_ENSURE_SUCCESS(rv, rv); 2554 2555 return NS_OK; 2556 } 2557 2558 nsresult CacheFileIOManager::DoomFileByKeyInternal(const SHA1Sum::Hash* aHash) { 2559 LOG(( 2560 "CacheFileIOManager::DoomFileByKeyInternal() [hash=%08x%08x%08x%08x%08x]", 2561 LOGSHA1(aHash))); 2562 2563 MOZ_ASSERT(CacheFileIOManager::IsOnIOThreadOrCeased()); 2564 2565 nsresult rv; 2566 2567 if (mShuttingDown) { 2568 return NS_ERROR_NOT_INITIALIZED; 2569 } 2570 2571 if (!mCacheDirectory) { 2572 return NS_ERROR_FILE_INVALID_PATH; 2573 } 2574 2575 // Find active handle 2576 RefPtr<CacheFileHandle> handle; 2577 mHandles.GetHandle(aHash, getter_AddRefs(handle)); 2578 2579 if (handle) { 2580 handle->Log(); 2581 2582 return DoomFileInternal(handle); 2583 } 2584 2585 CacheIOThread::Cancelable cancelable(true); 2586 2587 // There is no handle for this file, delete the file if exists 2588 nsCOMPtr<nsIFile> file; 2589 rv = GetFile(aHash, getter_AddRefs(file)); 2590 NS_ENSURE_SUCCESS(rv, rv); 2591 2592 bool exists; 2593 rv = file->Exists(&exists); 2594 NS_ENSURE_SUCCESS(rv, rv); 2595 2596 if (!exists) { 2597 return NS_ERROR_NOT_AVAILABLE; 2598 } 2599 2600 LOG( 2601 ("CacheFileIOManager::DoomFileByKeyInternal() - Removing file from " 2602 "disk")); 2603 rv = file->Remove(false); 2604 if (NS_FAILED(rv)) { 2605 NS_WARNING("Cannot remove old entry from the disk"); 2606 LOG( 2607 ("CacheFileIOManager::DoomFileByKeyInternal() - Removing file failed. " 2608 "[rv=0x%08" PRIx32 "]", 2609 static_cast<uint32_t>(rv))); 2610 } 2611 2612 // Find the key for the hash 2613 // Read metadata from the file synchronously 2614 RefPtr<CacheFileMetadata> metadata = new CacheFileMetadata(); 2615 rv = metadata->SyncReadMetadata(file); 2616 if (NS_WARN_IF(NS_FAILED(rv))) { 2617 CacheIndex::RemoveEntry(aHash, ""_ns); 2618 } else { 2619 CacheIndex::RemoveEntry(aHash, metadata->GetKey()); 2620 } 2621 2622 return NS_OK; 2623 } 2624 2625 // static 2626 nsresult CacheFileIOManager::ReleaseNSPRHandle(CacheFileHandle* aHandle) { 2627 LOG(("CacheFileIOManager::ReleaseNSPRHandle() [handle=%p]", aHandle)); 2628 2629 nsresult rv; 2630 RefPtr<CacheFileIOManager> ioMan = gInstance; 2631 2632 if (aHandle->IsClosed() || !ioMan) { 2633 return NS_ERROR_NOT_INITIALIZED; 2634 } 2635 2636 RefPtr<ReleaseNSPRHandleEvent> ev = new ReleaseNSPRHandleEvent(aHandle); 2637 rv = ioMan->mIOThread->Dispatch(ev, aHandle->mPriority 2638 ? CacheIOThread::WRITE_PRIORITY 2639 : CacheIOThread::WRITE); 2640 NS_ENSURE_SUCCESS(rv, rv); 2641 2642 return NS_OK; 2643 } 2644 2645 nsresult CacheFileIOManager::MaybeReleaseNSPRHandleInternal( 2646 CacheFileHandle* aHandle, bool aIgnoreShutdownLag) { 2647 LOG( 2648 ("CacheFileIOManager::MaybeReleaseNSPRHandleInternal() [handle=%p, " 2649 "ignore shutdown=%d]", 2650 aHandle, aIgnoreShutdownLag)); 2651 2652 MOZ_ASSERT(CacheFileIOManager::IsOnIOThreadOrCeased()); 2653 2654 if (aHandle->mFD) { 2655 DebugOnly<bool> found{}; 2656 found = mHandlesByLastUsed.RemoveElement(aHandle); 2657 MOZ_ASSERT(found); 2658 } 2659 2660 PRFileDesc* fd = aHandle->mFD; 2661 aHandle->mFD = nullptr; 2662 2663 // Leak invalid (w/o metadata) and doomed handles immediately after shutdown. 2664 // Leak other handles when past the shutdown time maximum lag. 2665 if ( 2666 #ifndef DEBUG 2667 ((aHandle->mInvalid || aHandle->mIsDoomed) && 2668 MOZ_UNLIKELY(CacheObserver::ShuttingDown())) || 2669 #endif 2670 MOZ_UNLIKELY(!aIgnoreShutdownLag && 2671 CacheObserver::IsPastShutdownIOLag())) { 2672 // Don't bother closing this file. Return a failure code from here will 2673 // cause any following IO operation on the file (mainly removal) to be 2674 // bypassed, which is what we want. 2675 // For mInvalid == true the entry will never be used, since it doesn't 2676 // have correct metadata, thus we don't need to worry about removing it. 2677 // For mIsDoomed == true the file is already in the doomed sub-dir and 2678 // will be removed on next session start. 2679 LOG((" past the shutdown I/O lag, leaking file handle")); 2680 return NS_ERROR_ILLEGAL_DURING_SHUTDOWN; 2681 } 2682 2683 if (!fd) { 2684 // The filedesc has already been closed before, just let go. 2685 return NS_OK; 2686 } 2687 2688 bool cancelable = !aHandle->IsSpecialFile(); 2689 #if defined(MOZ_CACHE_ASYNC_IO) 2690 if (aHandle->mAsyncRunning) { 2691 // Need to wait for async operation to finish before closing the 2692 // file descriptor. 2693 RefPtr<nsIRunnable> closeFunc = NS_NewRunnableFunction( 2694 "CacheFileIOManager::MaybeReleaseNSPRHandleInternal", 2695 [fd, cancelable]() { 2696 CacheIOThread::Cancelable _cancelable(cancelable); 2697 2698 PR_Close(fd); 2699 }); 2700 2701 if (aHandle->WaitForAsyncCompletion(closeFunc, CacheIOThread::OPEN)) { 2702 return NS_OK; 2703 } 2704 } 2705 #endif 2706 2707 CacheIOThread::Cancelable _cancelable(cancelable); 2708 2709 PRStatus status = PR_Close(fd); 2710 if (status != PR_SUCCESS) { 2711 LOG( 2712 ("CacheFileIOManager::MaybeReleaseNSPRHandleInternal() " 2713 "failed to close [handle=%p, status=%u]", 2714 aHandle, status)); 2715 return NS_ERROR_FAILURE; 2716 } 2717 2718 LOG(("CacheFileIOManager::MaybeReleaseNSPRHandleInternal() DONE")); 2719 2720 return NS_OK; 2721 } 2722 2723 // static 2724 nsresult CacheFileIOManager::TruncateSeekSetEOF( 2725 CacheFileHandle* aHandle, int64_t aTruncatePos, int64_t aEOFPos, 2726 CacheFileIOListener* aCallback) { 2727 LOG( 2728 ("CacheFileIOManager::TruncateSeekSetEOF() [handle=%p, " 2729 "truncatePos=%" PRId64 ", " 2730 "EOFPos=%" PRId64 ", listener=%p]", 2731 aHandle, aTruncatePos, aEOFPos, aCallback)); 2732 2733 nsresult rv; 2734 RefPtr<CacheFileIOManager> ioMan = gInstance; 2735 2736 if (aHandle->IsClosed() || (aCallback && aCallback->IsKilled()) || !ioMan) { 2737 return NS_ERROR_NOT_INITIALIZED; 2738 } 2739 2740 RefPtr<TruncateSeekSetEOFEvent> ev = 2741 new TruncateSeekSetEOFEvent(aHandle, aTruncatePos, aEOFPos, aCallback); 2742 rv = ioMan->mIOThread->Dispatch(ev, aHandle->mPriority 2743 ? CacheIOThread::WRITE_PRIORITY 2744 : CacheIOThread::WRITE); 2745 NS_ENSURE_SUCCESS(rv, rv); 2746 2747 return NS_OK; 2748 } 2749 2750 // static 2751 void CacheFileIOManager::GetCacheDirectory(nsIFile** result) { 2752 *result = nullptr; 2753 2754 RefPtr<CacheFileIOManager> ioMan = gInstance; 2755 if (!ioMan || !ioMan->mCacheDirectory) { 2756 return; 2757 } 2758 2759 ioMan->mCacheDirectory->Clone(result); 2760 } 2761 2762 #if defined(MOZ_WIDGET_ANDROID) 2763 2764 // static 2765 void CacheFileIOManager::GetProfilelessCacheDirectory(nsIFile** result) { 2766 *result = nullptr; 2767 2768 RefPtr<CacheFileIOManager> ioMan = gInstance; 2769 if (!ioMan || !ioMan->mCacheProfilelessDirectory) { 2770 return; 2771 } 2772 2773 ioMan->mCacheProfilelessDirectory->Clone(result); 2774 } 2775 2776 #endif 2777 2778 // static 2779 nsresult CacheFileIOManager::GetEntryInfo( 2780 const SHA1Sum::Hash* aHash, 2781 CacheStorageService::EntryInfoCallback* aCallback) { 2782 MOZ_ASSERT(CacheFileIOManager::IsOnIOThread()); 2783 2784 nsresult rv; 2785 2786 RefPtr<CacheFileIOManager> ioMan = gInstance; 2787 if (!ioMan) { 2788 return NS_ERROR_NOT_INITIALIZED; 2789 } 2790 2791 nsAutoCString enhanceId; 2792 nsAutoCString uriSpec; 2793 2794 RefPtr<CacheFileHandle> handle; 2795 ioMan->mHandles.GetHandle(aHash, getter_AddRefs(handle)); 2796 if (handle) { 2797 RefPtr<nsILoadContextInfo> info = 2798 CacheFileUtils::ParseKey(handle->Key(), &enhanceId, &uriSpec); 2799 2800 MOZ_ASSERT(info); 2801 if (!info) { 2802 return NS_OK; // ignore 2803 } 2804 2805 RefPtr<CacheStorageService> service = CacheStorageService::Self(); 2806 if (!service) { 2807 return NS_ERROR_NOT_INITIALIZED; 2808 } 2809 2810 // Invokes OnCacheEntryInfo when an existing entry is found 2811 if (service->GetCacheEntryInfo(info, enhanceId, uriSpec, aCallback)) { 2812 return NS_OK; 2813 } 2814 2815 // When we are here, there is no existing entry and we need 2816 // to synchrnously load metadata from a disk file. 2817 } 2818 2819 // Locate the actual file 2820 nsCOMPtr<nsIFile> file; 2821 ioMan->GetFile(aHash, getter_AddRefs(file)); 2822 2823 // Read metadata from the file synchronously 2824 RefPtr<CacheFileMetadata> metadata = new CacheFileMetadata(); 2825 rv = metadata->SyncReadMetadata(file); 2826 if (NS_FAILED(rv)) { 2827 return NS_OK; 2828 } 2829 2830 // Now get the context + enhance id + URL from the key. 2831 RefPtr<nsILoadContextInfo> info = 2832 CacheFileUtils::ParseKey(metadata->GetKey(), &enhanceId, &uriSpec); 2833 MOZ_ASSERT(info); 2834 if (!info) { 2835 return NS_OK; 2836 } 2837 2838 // Pick all data to pass to the callback. 2839 int64_t dataSize = metadata->Offset(); 2840 int64_t altDataSize = 0; 2841 uint32_t fetchCount = metadata->GetFetchCount(); 2842 uint32_t expirationTime = metadata->GetExpirationTime(); 2843 uint32_t lastModified = metadata->GetLastModified(); 2844 2845 const char* altDataElement = 2846 metadata->GetElement(CacheFileUtils::kAltDataKey); 2847 if (altDataElement) { 2848 int64_t altDataOffset = std::numeric_limits<int64_t>::max(); 2849 if (NS_SUCCEEDED(CacheFileUtils::ParseAlternativeDataInfo( 2850 altDataElement, &altDataOffset, nullptr)) && 2851 altDataOffset < dataSize) { 2852 dataSize = altDataOffset; 2853 altDataSize = metadata->Offset() - altDataOffset; 2854 } else { 2855 LOG(("CacheFileIOManager::GetEntryInfo() invalid alternative data info")); 2856 return NS_OK; 2857 } 2858 } 2859 2860 // Call directly on the callback. 2861 aCallback->OnEntryInfo(uriSpec, enhanceId, dataSize, altDataSize, fetchCount, 2862 lastModified, expirationTime, metadata->Pinned(), 2863 info); 2864 2865 return NS_OK; 2866 } 2867 2868 nsresult CacheFileIOManager::TruncateSeekSetEOFInternal( 2869 CacheFileHandle* aHandle, int64_t aTruncatePos, int64_t aEOFPos) { 2870 LOG( 2871 ("CacheFileIOManager::TruncateSeekSetEOFInternal() [handle=%p, " 2872 "truncatePos=%" PRId64 ", EOFPos=%" PRId64 "]", 2873 aHandle, aTruncatePos, aEOFPos)); 2874 2875 nsresult rv; 2876 2877 if (aHandle->mKilled) { 2878 LOG((" handle already killed, file not truncated")); 2879 return NS_OK; 2880 } 2881 2882 if (CacheObserver::ShuttingDown() && !aHandle->mFD) { 2883 aHandle->mKilled = true; 2884 LOG((" killing the handle, file not truncated")); 2885 return NS_OK; 2886 } 2887 2888 CacheIOThread::Cancelable cancelable(!aHandle->IsSpecialFile()); 2889 2890 if (!aHandle->mFileExists) { 2891 rv = CreateFile(aHandle); 2892 NS_ENSURE_SUCCESS(rv, rv); 2893 } 2894 2895 if (!aHandle->mFD) { 2896 rv = OpenNSPRHandle(aHandle); 2897 NS_ENSURE_SUCCESS(rv, rv); 2898 } else { 2899 NSPRHandleUsed(aHandle); 2900 } 2901 2902 // Check again, OpenNSPRHandle could figure out the file was gone. 2903 if (!aHandle->mFileExists) { 2904 return NS_ERROR_NOT_AVAILABLE; 2905 } 2906 2907 // When this operation would increase cache size, check whether the cache size 2908 // reached the hard limit and whether it would cause critical low disk space. 2909 if (aHandle->mFileSize < aEOFPos) { 2910 if (mOverLimitEvicting && mCacheSizeOnHardLimit) { 2911 LOG( 2912 ("CacheFileIOManager::TruncateSeekSetEOFInternal() - failing because " 2913 "cache size reached hard limit!")); 2914 return NS_ERROR_FILE_NO_DEVICE_SPACE; 2915 } 2916 2917 int64_t freeSpace; 2918 rv = mCacheDirectory->GetDiskSpaceAvailable(&freeSpace); 2919 if (NS_WARN_IF(NS_FAILED(rv))) { 2920 freeSpace = -1; 2921 LOG( 2922 ("CacheFileIOManager::TruncateSeekSetEOFInternal() - " 2923 "GetDiskSpaceAvailable() failed! [rv=0x%08" PRIx32 "]", 2924 static_cast<uint32_t>(rv))); 2925 } else { 2926 freeSpace >>= 10; // bytes to kilobytes 2927 uint32_t limit = CacheObserver::DiskFreeSpaceHardLimit(); 2928 if (freeSpace - aEOFPos + aHandle->mFileSize < limit) { 2929 LOG( 2930 ("CacheFileIOManager::TruncateSeekSetEOFInternal() - Low free space" 2931 ", refusing to write! [freeSpace=%" PRId64 "kB, limit=%ukB]", 2932 freeSpace, limit)); 2933 return NS_ERROR_FILE_NO_DEVICE_SPACE; 2934 } 2935 } 2936 } 2937 2938 // This operation always invalidates the entry 2939 aHandle->mInvalid = true; 2940 2941 rv = TruncFile(aHandle->mFD, aTruncatePos); 2942 NS_ENSURE_SUCCESS(rv, rv); 2943 2944 if (aTruncatePos != aEOFPos) { 2945 rv = TruncFile(aHandle->mFD, aEOFPos); 2946 NS_ENSURE_SUCCESS(rv, rv); 2947 } 2948 2949 uint32_t oldSizeInK = aHandle->FileSizeInK(); 2950 aHandle->mFileSize = aEOFPos; 2951 uint32_t newSizeInK = aHandle->FileSizeInK(); 2952 2953 if (oldSizeInK != newSizeInK && !aHandle->IsDoomed() && 2954 !aHandle->IsSpecialFile()) { 2955 CacheIndex::UpdateEntry(aHandle->Hash(), nullptr, nullptr, nullptr, nullptr, 2956 nullptr, &newSizeInK); 2957 2958 if (oldSizeInK < newSizeInK) { 2959 EvictIfOverLimitInternal(); 2960 } 2961 } 2962 2963 return NS_OK; 2964 } 2965 2966 // static 2967 nsresult CacheFileIOManager::RenameFile(CacheFileHandle* aHandle, 2968 const nsACString& aNewName, 2969 CacheFileIOListener* aCallback) { 2970 LOG(("CacheFileIOManager::RenameFile() [handle=%p, newName=%s, listener=%p]", 2971 aHandle, PromiseFlatCString(aNewName).get(), aCallback)); 2972 2973 nsresult rv; 2974 RefPtr<CacheFileIOManager> ioMan = gInstance; 2975 2976 if (aHandle->IsClosed() || !ioMan) { 2977 return NS_ERROR_NOT_INITIALIZED; 2978 } 2979 2980 if (!aHandle->IsSpecialFile()) { 2981 return NS_ERROR_UNEXPECTED; 2982 } 2983 2984 RefPtr<RenameFileEvent> ev = 2985 new RenameFileEvent(aHandle, aNewName, aCallback); 2986 rv = ioMan->mIOThread->Dispatch(ev, aHandle->mPriority 2987 ? CacheIOThread::WRITE_PRIORITY 2988 : CacheIOThread::WRITE); 2989 NS_ENSURE_SUCCESS(rv, rv); 2990 2991 return NS_OK; 2992 } 2993 2994 nsresult CacheFileIOManager::RenameFileInternal(CacheFileHandle* aHandle, 2995 const nsACString& aNewName) { 2996 LOG(("CacheFileIOManager::RenameFileInternal() [handle=%p, newName=%s]", 2997 aHandle, PromiseFlatCString(aNewName).get())); 2998 2999 nsresult rv; 3000 3001 MOZ_ASSERT(aHandle->IsSpecialFile()); 3002 3003 if (aHandle->IsDoomed()) { 3004 return NS_ERROR_NOT_AVAILABLE; 3005 } 3006 3007 // Doom old handle if it exists and is not doomed 3008 for (uint32_t i = 0; i < mSpecialHandles.Length(); i++) { 3009 if (!mSpecialHandles[i]->IsDoomed() && 3010 mSpecialHandles[i]->Key() == aNewName) { 3011 MOZ_ASSERT(aHandle != mSpecialHandles[i]); 3012 rv = DoomFileInternal(mSpecialHandles[i]); 3013 NS_ENSURE_SUCCESS(rv, rv); 3014 break; 3015 } 3016 } 3017 3018 nsCOMPtr<nsIFile> file; 3019 rv = GetSpecialFile(aNewName, getter_AddRefs(file)); 3020 NS_ENSURE_SUCCESS(rv, rv); 3021 3022 bool exists; 3023 rv = file->Exists(&exists); 3024 NS_ENSURE_SUCCESS(rv, rv); 3025 3026 if (exists) { 3027 LOG( 3028 ("CacheFileIOManager::RenameFileInternal() - Removing old file from " 3029 "disk")); 3030 rv = file->Remove(false); 3031 if (NS_FAILED(rv)) { 3032 NS_WARNING("Cannot remove file from the disk"); 3033 LOG( 3034 ("CacheFileIOManager::RenameFileInternal() - Removing old file failed" 3035 ". [rv=0x%08" PRIx32 "]", 3036 static_cast<uint32_t>(rv))); 3037 } 3038 } 3039 3040 if (!aHandle->FileExists()) { 3041 aHandle->mKey = aNewName; 3042 return NS_OK; 3043 } 3044 3045 rv = MaybeReleaseNSPRHandleInternal(aHandle, true); 3046 NS_ENSURE_SUCCESS(rv, rv); 3047 3048 rv = aHandle->mFile->MoveToNative(nullptr, aNewName); 3049 NS_ENSURE_SUCCESS(rv, rv); 3050 3051 aHandle->mKey = aNewName; 3052 return NS_OK; 3053 } 3054 3055 // static 3056 nsresult CacheFileIOManager::EvictIfOverLimit() { 3057 LOG(("CacheFileIOManager::EvictIfOverLimit()")); 3058 3059 nsresult rv; 3060 RefPtr<CacheFileIOManager> ioMan = gInstance; 3061 3062 if (!ioMan) { 3063 return NS_ERROR_NOT_INITIALIZED; 3064 } 3065 3066 nsCOMPtr<nsIRunnable> ev; 3067 ev = NewRunnableMethod("net::CacheFileIOManager::EvictIfOverLimitInternal", 3068 ioMan, &CacheFileIOManager::EvictIfOverLimitInternal); 3069 3070 rv = ioMan->mIOThread->Dispatch(ev, CacheIOThread::EVICT); 3071 NS_ENSURE_SUCCESS(rv, rv); 3072 3073 return NS_OK; 3074 } 3075 3076 nsresult CacheFileIOManager::EvictIfOverLimitInternal() { 3077 LOG(("CacheFileIOManager::EvictIfOverLimitInternal()")); 3078 3079 nsresult rv; 3080 3081 MOZ_ASSERT(mIOThread->IsCurrentThread()); 3082 3083 if (mShuttingDown) { 3084 return NS_ERROR_NOT_INITIALIZED; 3085 } 3086 3087 if (mOverLimitEvicting) { 3088 LOG( 3089 ("CacheFileIOManager::EvictIfOverLimitInternal() - Eviction already " 3090 "running.")); 3091 return NS_OK; 3092 } 3093 3094 CacheIOThread::Cancelable cancelable(true); 3095 3096 int64_t freeSpace; 3097 rv = mCacheDirectory->GetDiskSpaceAvailable(&freeSpace); 3098 if (NS_WARN_IF(NS_FAILED(rv))) { 3099 freeSpace = -1; 3100 3101 // Do not change smart size. 3102 LOG( 3103 ("CacheFileIOManager::EvictIfOverLimitInternal() - " 3104 "GetDiskSpaceAvailable() failed! [rv=0x%08" PRIx32 "]", 3105 static_cast<uint32_t>(rv))); 3106 } else { 3107 freeSpace >>= 10; // bytes to kilobytes 3108 UpdateSmartCacheSize(freeSpace); 3109 } 3110 3111 uint32_t cacheUsage; 3112 rv = CacheIndex::GetCacheSize(&cacheUsage); 3113 NS_ENSURE_SUCCESS(rv, rv); 3114 3115 uint32_t cacheLimit = CacheObserver::DiskCacheCapacity(); 3116 uint32_t freeSpaceLimit = CacheObserver::DiskFreeSpaceSoftLimit(); 3117 3118 if (cacheUsage <= cacheLimit && 3119 (freeSpace == -1 || freeSpace >= freeSpaceLimit)) { 3120 LOG( 3121 ("CacheFileIOManager::EvictIfOverLimitInternal() - Cache size and free " 3122 "space in limits. [cacheSize=%ukB, cacheSizeLimit=%ukB, " 3123 "freeSpace=%" PRId64 "kB, freeSpaceLimit=%ukB]", 3124 cacheUsage, cacheLimit, freeSpace, freeSpaceLimit)); 3125 return NS_OK; 3126 } 3127 3128 LOG( 3129 ("CacheFileIOManager::EvictIfOverLimitInternal() - Cache size exceeded " 3130 "limit. Starting overlimit eviction. [cacheSize=%ukB, limit=%ukB]", 3131 cacheUsage, cacheLimit)); 3132 3133 nsCOMPtr<nsIRunnable> ev; 3134 ev = NewRunnableMethod("net::CacheFileIOManager::OverLimitEvictionInternal", 3135 this, &CacheFileIOManager::OverLimitEvictionInternal); 3136 3137 rv = mIOThread->Dispatch(ev, CacheIOThread::EVICT); 3138 NS_ENSURE_SUCCESS(rv, rv); 3139 3140 mOverLimitEvicting = true; 3141 return NS_OK; 3142 } 3143 3144 nsresult CacheFileIOManager::OverLimitEvictionInternal() { 3145 LOG(("CacheFileIOManager::OverLimitEvictionInternal()")); 3146 3147 nsresult rv; 3148 3149 MOZ_ASSERT(mIOThread->IsCurrentThread()); 3150 3151 // mOverLimitEvicting is accessed only on IO thread, so we can set it to false 3152 // here and set it to true again once we dispatch another event that will 3153 // continue with the eviction. The reason why we do so is that we can fail 3154 // early anywhere in this method and the variable will contain a correct 3155 // value. Otherwise we would need to set it to false on every failing place. 3156 mOverLimitEvicting = false; 3157 3158 if (mShuttingDown) { 3159 return NS_ERROR_NOT_INITIALIZED; 3160 } 3161 3162 auto frecencySnapshot = CacheIndex::GetSortedSnapshotForEviction(); 3163 while (true) { 3164 int64_t freeSpace; 3165 rv = mCacheDirectory->GetDiskSpaceAvailable(&freeSpace); 3166 if (NS_WARN_IF(NS_FAILED(rv))) { 3167 freeSpace = -1; 3168 3169 // Do not change smart size. 3170 LOG( 3171 ("CacheFileIOManager::EvictIfOverLimitInternal() - " 3172 "GetDiskSpaceAvailable() failed! [rv=0x%08" PRIx32 "]", 3173 static_cast<uint32_t>(rv))); 3174 } else { 3175 freeSpace >>= 10; // bytes to kilobytes 3176 UpdateSmartCacheSize(freeSpace); 3177 } 3178 3179 uint32_t cacheUsage; 3180 rv = CacheIndex::GetCacheSize(&cacheUsage); 3181 NS_ENSURE_SUCCESS(rv, rv); 3182 3183 uint32_t cacheLimit = CacheObserver::DiskCacheCapacity(); 3184 uint32_t freeSpaceLimit = CacheObserver::DiskFreeSpaceSoftLimit(); 3185 3186 if (cacheUsage > cacheLimit) { 3187 LOG( 3188 ("CacheFileIOManager::OverLimitEvictionInternal() - Cache size over " 3189 "limit. [cacheSize=%ukB, limit=%ukB]", 3190 cacheUsage, cacheLimit)); 3191 3192 // We allow cache size to go over the specified limit. Eviction should 3193 // keep the size within the limit in a long run, but in case the eviction 3194 // is too slow, the cache could go way over the limit. To prevent this we 3195 // set flag mCacheSizeOnHardLimit when the size reaches 105% of the limit 3196 // and WriteInternal() and TruncateSeekSetEOFInternal() fail to cache 3197 // additional data. 3198 if ((cacheUsage - cacheLimit) > (cacheLimit / 20)) { 3199 LOG( 3200 ("CacheFileIOManager::OverLimitEvictionInternal() - Cache size " 3201 "reached hard limit.")); 3202 mCacheSizeOnHardLimit = true; 3203 } else { 3204 mCacheSizeOnHardLimit = false; 3205 } 3206 } else if (freeSpace != -1 && freeSpace < freeSpaceLimit) { 3207 LOG( 3208 ("CacheFileIOManager::OverLimitEvictionInternal() - Free space under " 3209 "limit. [freeSpace=%" PRId64 "kB, freeSpaceLimit=%ukB]", 3210 freeSpace, freeSpaceLimit)); 3211 } else { 3212 LOG( 3213 ("CacheFileIOManager::OverLimitEvictionInternal() - Cache size and " 3214 "free space in limits. [cacheSize=%ukB, cacheSizeLimit=%ukB, " 3215 "freeSpace=%" PRId64 "kB, freeSpaceLimit=%ukB]", 3216 cacheUsage, cacheLimit, freeSpace, freeSpaceLimit)); 3217 3218 mCacheSizeOnHardLimit = false; 3219 return NS_OK; 3220 } 3221 3222 if (CacheIOThread::YieldAndRerun()) { 3223 LOG( 3224 ("CacheFileIOManager::OverLimitEvictionInternal() - Breaking loop " 3225 "for higher level events.")); 3226 mOverLimitEvicting = true; 3227 return NS_OK; 3228 } 3229 3230 SHA1Sum::Hash hash; 3231 uint32_t cnt = 0; 3232 static uint32_t consecutiveFailures = 0; 3233 rv = CacheIndex::GetEntryForEviction(frecencySnapshot, false, &hash, &cnt); 3234 NS_ENSURE_SUCCESS(rv, rv); 3235 3236 rv = DoomFileByKeyInternal(&hash); 3237 if (NS_SUCCEEDED(rv)) { 3238 consecutiveFailures = 0; 3239 } else if (rv == NS_ERROR_NOT_AVAILABLE) { 3240 LOG( 3241 ("CacheFileIOManager::OverLimitEvictionInternal() - " 3242 "DoomFileByKeyInternal() failed. [rv=0x%08" PRIx32 "]", 3243 static_cast<uint32_t>(rv))); 3244 // TODO index is outdated, start update 3245 3246 // Make sure index won't return the same entry again 3247 // XXX find the key for the hash 3248 CacheIndex::RemoveEntry(&hash, ""_ns); 3249 consecutiveFailures = 0; 3250 } else { 3251 // This shouldn't normally happen, but the eviction must not fail 3252 // completely if we ever encounter this problem. 3253 NS_WARNING( 3254 "CacheFileIOManager::OverLimitEvictionInternal() - Unexpected " 3255 "failure of DoomFileByKeyInternal()"); 3256 3257 LOG( 3258 ("CacheFileIOManager::OverLimitEvictionInternal() - " 3259 "DoomFileByKeyInternal() failed. [rv=0x%08" PRIx32 "]", 3260 static_cast<uint32_t>(rv))); 3261 3262 if (++consecutiveFailures >= cnt) { 3263 // This doesn't necessarily mean that we've tried to doom every entry 3264 // but we've reached a sane number of tries. It is likely that another 3265 // eviction will start soon. And as said earlier, this normally doesn't 3266 // happen at all. 3267 return NS_OK; 3268 } 3269 } 3270 } 3271 3272 MOZ_ASSERT_UNREACHABLE("We should never get here"); 3273 return NS_OK; 3274 } 3275 3276 // static 3277 nsresult CacheFileIOManager::EvictAll() { 3278 LOG(("CacheFileIOManager::EvictAll()")); 3279 3280 nsresult rv; 3281 RefPtr<CacheFileIOManager> ioMan = gInstance; 3282 3283 if (!ioMan) { 3284 return NS_ERROR_NOT_INITIALIZED; 3285 } 3286 3287 nsCOMPtr<nsIRunnable> ev; 3288 ev = NewRunnableMethod("net::CacheFileIOManager::EvictAllInternal", ioMan, 3289 &CacheFileIOManager::EvictAllInternal); 3290 3291 rv = ioMan->mIOThread->DispatchAfterPendingOpens(ev); 3292 if (NS_WARN_IF(NS_FAILED(rv))) { 3293 return rv; 3294 } 3295 3296 return NS_OK; 3297 } 3298 3299 namespace { 3300 3301 class EvictionNotifierRunnable : public Runnable { 3302 public: 3303 EvictionNotifierRunnable() : Runnable("EvictionNotifierRunnable") {} 3304 NS_DECL_NSIRUNNABLE 3305 }; 3306 3307 NS_IMETHODIMP 3308 EvictionNotifierRunnable::Run() { 3309 nsCOMPtr<nsIObserverService> obsSvc = mozilla::services::GetObserverService(); 3310 if (obsSvc) { 3311 obsSvc->NotifyObservers(nullptr, "cacheservice:empty-cache", nullptr); 3312 } 3313 return NS_OK; 3314 } 3315 3316 } // namespace 3317 3318 nsresult CacheFileIOManager::EvictAllInternal() { 3319 LOG(("CacheFileIOManager::EvictAllInternal()")); 3320 3321 nsresult rv; 3322 3323 MOZ_ASSERT(mIOThread->IsCurrentThread()); 3324 3325 RefPtr<EvictionNotifierRunnable> r = new EvictionNotifierRunnable(); 3326 3327 if (!mCacheDirectory) { 3328 // This is a kind of hack. Somebody called EvictAll() without a profile. 3329 // This happens in xpcshell tests that use cache without profile. We need 3330 // to notify observers in this case since the tests are waiting for it. 3331 NS_DispatchToMainThread(r); 3332 return NS_ERROR_FILE_INVALID_PATH; 3333 } 3334 3335 if (mShuttingDown) { 3336 return NS_ERROR_NOT_INITIALIZED; 3337 } 3338 3339 if (!mTreeCreated) { 3340 rv = CreateCacheTree(); 3341 if (NS_FAILED(rv)) { 3342 return rv; 3343 } 3344 } 3345 3346 // Doom all active handles 3347 nsTArray<RefPtr<CacheFileHandle>> handles; 3348 mHandles.GetActiveHandles(&handles); 3349 3350 for (uint32_t i = 0; i < handles.Length(); ++i) { 3351 rv = DoomFileInternal(handles[i]); 3352 if (NS_WARN_IF(NS_FAILED(rv))) { 3353 LOG( 3354 ("CacheFileIOManager::EvictAllInternal() - Cannot doom handle " 3355 "[handle=%p]", 3356 handles[i].get())); 3357 } 3358 } 3359 3360 nsCOMPtr<nsIFile> file; 3361 rv = mCacheDirectory->Clone(getter_AddRefs(file)); 3362 if (NS_WARN_IF(NS_FAILED(rv))) { 3363 return rv; 3364 } 3365 3366 rv = file->AppendNative(nsLiteralCString(ENTRIES_DIR)); 3367 if (NS_WARN_IF(NS_FAILED(rv))) { 3368 return rv; 3369 } 3370 3371 // Trash current entries directory 3372 rv = TrashDirectory(file); 3373 if (NS_WARN_IF(NS_FAILED(rv))) { 3374 return rv; 3375 } 3376 3377 // Files are now inaccessible in entries directory, notify observers. 3378 NS_DispatchToMainThread(r); 3379 3380 // Create a new empty entries directory 3381 rv = CheckAndCreateDir(mCacheDirectory, ENTRIES_DIR, false); 3382 if (NS_WARN_IF(NS_FAILED(rv))) { 3383 return rv; 3384 } 3385 3386 CacheIndex::RemoveAll(); 3387 3388 return NS_OK; 3389 } 3390 3391 // static 3392 nsresult CacheFileIOManager::EvictByContext( 3393 nsILoadContextInfo* aLoadContextInfo, bool aPinned, 3394 const nsAString& aOrigin, const nsAString& aBaseDomain) { 3395 LOG(("CacheFileIOManager::EvictByContext() [loadContextInfo=%p]", 3396 aLoadContextInfo)); 3397 3398 // XXX evict dictionary data from memory cache 3399 nsresult rv; 3400 RefPtr<CacheFileIOManager> ioMan = gInstance; 3401 3402 if (!ioMan) { 3403 return NS_ERROR_NOT_INITIALIZED; 3404 } 3405 3406 nsCOMPtr<nsIRunnable> ev; 3407 ev = 3408 NewRunnableMethod<nsCOMPtr<nsILoadContextInfo>, bool, nsString, nsString>( 3409 "net::CacheFileIOManager::EvictByContextInternal", ioMan, 3410 &CacheFileIOManager::EvictByContextInternal, aLoadContextInfo, 3411 aPinned, aOrigin, aBaseDomain); 3412 3413 rv = ioMan->mIOThread->DispatchAfterPendingOpens(ev); 3414 if (NS_WARN_IF(NS_FAILED(rv))) { 3415 return rv; 3416 } 3417 // Clear the entries from the Index immediately, to comply with 3418 // https://www.w3.org/TR/clear-site-data/#fetch-integration 3419 // aBaseDomain isn't needed for Clear-Site-Data, but is for 3420 // ClearBaseDomain. This can also make CacheStorageService::Clear() and 3421 // ClearBaseDomain() be synchronous. 3422 // Note that we will effectively hide the entries until the actual evict 3423 // happens. 3424 CacheIndex::EvictByContext(aOrigin, aBaseDomain); 3425 3426 return NS_OK; 3427 } 3428 3429 nsresult CacheFileIOManager::EvictByContextInternal( 3430 nsILoadContextInfo* aLoadContextInfo, bool aPinned, 3431 const nsAString& aOrigin, const nsAString& aBaseDomain) { 3432 LOG( 3433 ("CacheFileIOManager::EvictByContextInternal() [loadContextInfo=%p, " 3434 "pinned=%d]", 3435 aLoadContextInfo, aPinned)); 3436 3437 nsresult rv; 3438 3439 if (aLoadContextInfo) { 3440 nsAutoCString suffix; 3441 aLoadContextInfo->OriginAttributesPtr()->CreateSuffix(suffix); 3442 LOG((" anonymous=%u, suffix=%s]", aLoadContextInfo->IsAnonymous(), 3443 suffix.get())); 3444 3445 MOZ_ASSERT(mIOThread->IsCurrentThread()); 3446 3447 MOZ_ASSERT(!aLoadContextInfo->IsPrivate()); 3448 if (aLoadContextInfo->IsPrivate()) { 3449 return NS_ERROR_INVALID_ARG; 3450 } 3451 } 3452 3453 if (!mCacheDirectory) { 3454 // This is a kind of hack. Somebody called EvictAll() without a profile. 3455 // This happens in xpcshell tests that use cache without profile. We need 3456 // to notify observers in this case since the tests are waiting for it. 3457 // Also notify for aPinned == true, those are interested as well. 3458 3459 // XXX This doesn't actually clear anything in this case (is there anything 3460 // to clear?) 3461 if (!aLoadContextInfo) { 3462 RefPtr<EvictionNotifierRunnable> r = new EvictionNotifierRunnable(); 3463 NS_DispatchToMainThread(r); 3464 } 3465 return NS_ERROR_FILE_INVALID_PATH; 3466 } 3467 3468 if (mShuttingDown) { 3469 return NS_ERROR_NOT_INITIALIZED; 3470 } 3471 3472 if (!mTreeCreated) { 3473 rv = CreateCacheTree(); 3474 if (NS_FAILED(rv)) { 3475 return rv; 3476 } 3477 } 3478 3479 NS_ConvertUTF16toUTF8 origin(aOrigin); 3480 NS_ConvertUTF16toUTF8 baseDomain(aBaseDomain); 3481 3482 // Doom all active handles that matches the load context 3483 // NOTE: Dictionaries have already been cleared synchronously, 3484 // so there's no need to re-clear them (which might cause 3485 // problems if they were re-created in to interim). 3486 nsTArray<RefPtr<CacheFileHandle>> handles; 3487 mHandles.GetActiveHandles(&handles); 3488 3489 for (uint32_t i = 0; i < handles.Length(); ++i) { 3490 CacheFileHandle* handle = handles[i]; 3491 3492 const bool shouldRemove = [&] { 3493 nsAutoCString uriSpec; 3494 RefPtr<nsILoadContextInfo> info = 3495 CacheFileUtils::ParseKey(handle->Key(), nullptr, &uriSpec); 3496 if (!info) { 3497 LOG( 3498 ("CacheFileIOManager::EvictByContextInternal() - Cannot parse key " 3499 "in " 3500 "handle! [handle=%p, key=%s]", 3501 handle, handle->Key().get())); 3502 MOZ_CRASH("Unexpected error!"); 3503 } 3504 3505 // Filter by base domain. 3506 if (!aBaseDomain.IsEmpty()) { 3507 if (StoragePrincipalHelper::PartitionKeyHasBaseDomain( 3508 info->OriginAttributesPtr()->mPartitionKey, aBaseDomain)) { 3509 return true; 3510 } 3511 3512 // If the partitionKey does not match, check the entry URI next. 3513 3514 // Get host portion of uriSpec. 3515 nsCOMPtr<nsIURI> uri; 3516 rv = NS_NewURI(getter_AddRefs(uri), uriSpec); 3517 if (NS_WARN_IF(NS_FAILED(rv))) { 3518 return false; 3519 } 3520 nsAutoCString host; 3521 rv = uri->GetHost(host); 3522 // Some entries may not have valid hosts. We can skip them. 3523 if (NS_FAILED(rv) || host.IsEmpty()) { 3524 return false; 3525 } 3526 3527 // Clear entry if the host belongs to the given base domain. 3528 bool hasRootDomain = false; 3529 rv = HasRootDomain(host, baseDomain, &hasRootDomain); 3530 if (NS_WARN_IF(NS_FAILED(rv))) { 3531 return false; 3532 } 3533 3534 return hasRootDomain; 3535 } 3536 3537 // Filter by LoadContextInfo. 3538 if (aLoadContextInfo && !info->Equals(aLoadContextInfo)) { 3539 return false; 3540 } 3541 3542 // Filter by origin. 3543 if (!origin.IsEmpty()) { // XXX also look for dict:<origin>, or let that 3544 // be handled by Doom? Probably Doom 3545 RefPtr<MozURL> url; 3546 rv = MozURL::Init(getter_AddRefs(url), uriSpec); 3547 if (NS_FAILED(rv)) { 3548 return false; 3549 } 3550 3551 nsAutoCString urlOrigin; 3552 url->Origin(urlOrigin); 3553 3554 if (!urlOrigin.Equals(origin)) { 3555 return false; 3556 } 3557 } 3558 return true; 3559 }(); 3560 3561 if (!shouldRemove) { 3562 continue; 3563 } 3564 3565 // handle will be doomed only when pinning status is known and equal or 3566 // doom decision will be deferred until pinning status is determined. 3567 rv = DoomFileInternal(handle, 3568 aPinned ? CacheFileIOManager::DOOM_WHEN_PINNED 3569 : CacheFileIOManager::DOOM_WHEN_NON_PINNED, 3570 false); 3571 if (NS_WARN_IF(NS_FAILED(rv))) { 3572 LOG( 3573 ("CacheFileIOManager::EvictByContextInternal() - Cannot doom handle" 3574 " [handle=%p]", 3575 handle)); 3576 } 3577 } 3578 3579 if (!aLoadContextInfo) { 3580 RefPtr<EvictionNotifierRunnable> r = new EvictionNotifierRunnable(); 3581 NS_DispatchToMainThread(r); 3582 } 3583 3584 if (!mContextEvictor) { 3585 mContextEvictor = new CacheFileContextEvictor(); 3586 mContextEvictor->Init(mCacheDirectory); 3587 } 3588 3589 mContextEvictor->AddContext(aLoadContextInfo, aPinned, aOrigin, aBaseDomain); 3590 3591 return NS_OK; 3592 } 3593 3594 // static 3595 nsresult CacheFileIOManager::CacheIndexStateChanged() { 3596 LOG(("CacheFileIOManager::CacheIndexStateChanged()")); 3597 3598 nsresult rv; 3599 3600 // CacheFileIOManager lives longer than CacheIndex so gInstance must be 3601 // non-null here. 3602 MOZ_ASSERT(gInstance); 3603 3604 // We have to re-dispatch even if we are on IO thread to prevent reentering 3605 // the lock in CacheIndex 3606 nsCOMPtr<nsIRunnable> ev = NewRunnableMethod( 3607 "net::CacheFileIOManager::CacheIndexStateChangedInternal", 3608 gInstance.get(), &CacheFileIOManager::CacheIndexStateChangedInternal); 3609 3610 nsCOMPtr<nsIEventTarget> ioTarget = IOTarget(); 3611 MOZ_ASSERT(ioTarget); 3612 3613 rv = ioTarget->Dispatch(ev, nsIEventTarget::DISPATCH_NORMAL); 3614 if (NS_WARN_IF(NS_FAILED(rv))) { 3615 return rv; 3616 } 3617 3618 return NS_OK; 3619 } 3620 3621 void CacheFileIOManager::CacheIndexStateChangedInternal() { 3622 if (mShuttingDown) { 3623 // ignore notification during shutdown 3624 return; 3625 } 3626 3627 if (!mContextEvictor) { 3628 return; 3629 } 3630 3631 mContextEvictor->CacheIndexStateChanged(); 3632 } 3633 3634 nsresult CacheFileIOManager::TrashDirectory(nsIFile* aFile) { 3635 LOG(("CacheFileIOManager::TrashDirectory() [file=%s]", 3636 aFile->HumanReadablePath().get())); 3637 3638 nsresult rv; 3639 3640 MOZ_ASSERT(mIOThread->IsCurrentThread()); 3641 MOZ_ASSERT(mCacheDirectory); 3642 3643 // When the directory is empty, it is cheaper to remove it directly instead of 3644 // using the trash mechanism. 3645 bool isEmpty; 3646 rv = IsEmptyDirectory(aFile, &isEmpty); 3647 NS_ENSURE_SUCCESS(rv, rv); 3648 3649 if (isEmpty) { 3650 rv = aFile->Remove(false); 3651 LOG( 3652 ("CacheFileIOManager::TrashDirectory() - Directory removed " 3653 "[rv=0x%08" PRIx32 "]", 3654 static_cast<uint32_t>(rv))); 3655 return rv; 3656 } 3657 3658 #ifdef DEBUG 3659 nsCOMPtr<nsIFile> dirCheck; 3660 rv = aFile->GetParent(getter_AddRefs(dirCheck)); 3661 NS_ENSURE_SUCCESS(rv, rv); 3662 3663 bool equals = false; 3664 rv = dirCheck->Equals(mCacheDirectory, &equals); 3665 NS_ENSURE_SUCCESS(rv, rv); 3666 3667 MOZ_ASSERT(equals); 3668 #endif 3669 3670 nsCOMPtr<nsIFile> dir, trash; 3671 nsAutoCString leaf; 3672 3673 rv = aFile->Clone(getter_AddRefs(dir)); 3674 NS_ENSURE_SUCCESS(rv, rv); 3675 3676 rv = aFile->Clone(getter_AddRefs(trash)); 3677 NS_ENSURE_SUCCESS(rv, rv); 3678 3679 const int32_t kMaxTries = 16; 3680 srand(static_cast<unsigned>(PR_Now())); 3681 for (int32_t triesCount = 0;; ++triesCount) { 3682 leaf = TRASH_DIR; 3683 leaf.AppendInt(rand()); 3684 rv = trash->SetNativeLeafName(leaf); 3685 NS_ENSURE_SUCCESS(rv, rv); 3686 3687 bool exists; 3688 if (NS_SUCCEEDED(trash->Exists(&exists)) && !exists) { 3689 break; 3690 } 3691 3692 LOG( 3693 ("CacheFileIOManager::TrashDirectory() - Trash directory already " 3694 "exists [leaf=%s]", 3695 leaf.get())); 3696 3697 if (triesCount == kMaxTries) { 3698 LOG( 3699 ("CacheFileIOManager::TrashDirectory() - Could not find unused trash " 3700 "directory in %d tries.", 3701 kMaxTries)); 3702 return NS_ERROR_FAILURE; 3703 } 3704 } 3705 3706 LOG(("CacheFileIOManager::TrashDirectory() - Renaming directory [leaf=%s]", 3707 leaf.get())); 3708 3709 rv = dir->MoveToNative(nullptr, leaf); 3710 NS_ENSURE_SUCCESS(rv, rv); 3711 3712 StartRemovingTrash(); 3713 return NS_OK; 3714 } 3715 3716 // static 3717 void CacheFileIOManager::OnTrashTimer(nsITimer* aTimer, void* aClosure) { 3718 LOG(("CacheFileIOManager::OnTrashTimer() [timer=%p, closure=%p]", aTimer, 3719 aClosure)); 3720 3721 RefPtr<CacheFileIOManager> ioMan = gInstance; 3722 3723 if (!ioMan) { 3724 return; 3725 } 3726 3727 ioMan->mTrashTimer = nullptr; 3728 ioMan->StartRemovingTrash(); 3729 } 3730 3731 nsresult CacheFileIOManager::StartRemovingTrash() { 3732 LOG(("CacheFileIOManager::StartRemovingTrash()")); 3733 3734 nsresult rv; 3735 3736 MOZ_ASSERT(mIOThread->IsCurrentThread()); 3737 3738 if (mShuttingDown) { 3739 return NS_ERROR_NOT_INITIALIZED; 3740 } 3741 3742 if (!mCacheDirectory) { 3743 return NS_ERROR_FILE_INVALID_PATH; 3744 } 3745 3746 if (mTrashTimer) { 3747 LOG(("CacheFileIOManager::StartRemovingTrash() - Trash timer exists.")); 3748 return NS_OK; 3749 } 3750 3751 if (mRemovingTrashDirs) { 3752 LOG( 3753 ("CacheFileIOManager::StartRemovingTrash() - Trash removing in " 3754 "progress.")); 3755 return NS_OK; 3756 } 3757 3758 uint32_t elapsed = (TimeStamp::NowLoRes() - mStartTime).ToMilliseconds(); 3759 if (elapsed < kRemoveTrashStartDelay) { 3760 nsCOMPtr<nsIEventTarget> ioTarget = IOTarget(); 3761 MOZ_ASSERT(ioTarget); 3762 3763 return NS_NewTimerWithFuncCallback( 3764 getter_AddRefs(mTrashTimer), CacheFileIOManager::OnTrashTimer, nullptr, 3765 kRemoveTrashStartDelay - elapsed, nsITimer::TYPE_ONE_SHOT, 3766 "net::CacheFileIOManager::StartRemovingTrash"_ns, ioTarget); 3767 } 3768 3769 nsCOMPtr<nsIRunnable> ev; 3770 ev = NewRunnableMethod("net::CacheFileIOManager::RemoveTrashInternal", this, 3771 &CacheFileIOManager::RemoveTrashInternal); 3772 3773 rv = mIOThread->Dispatch(ev, CacheIOThread::EVICT); 3774 NS_ENSURE_SUCCESS(rv, rv); 3775 3776 mRemovingTrashDirs = true; 3777 return NS_OK; 3778 } 3779 3780 nsresult CacheFileIOManager::RemoveTrashInternal() { 3781 LOG(("CacheFileIOManager::RemoveTrashInternal()")); 3782 3783 nsresult rv; 3784 3785 MOZ_ASSERT(mIOThread->IsCurrentThread()); 3786 3787 if (mShuttingDown) { 3788 return NS_ERROR_NOT_INITIALIZED; 3789 } 3790 3791 CacheIOThread::Cancelable cancelable(true); 3792 3793 MOZ_ASSERT(!mTrashTimer); 3794 MOZ_ASSERT(mRemovingTrashDirs); 3795 3796 if (!mTreeCreated) { 3797 rv = CreateCacheTree(); 3798 if (NS_FAILED(rv)) { 3799 return rv; 3800 } 3801 } 3802 3803 // mRemovingTrashDirs is accessed only on IO thread, so we can drop the flag 3804 // here and set it again once we dispatch a continuation event. By doing so, 3805 // we don't have to drop the flag on any possible early return. 3806 mRemovingTrashDirs = false; 3807 3808 while (true) { 3809 if (CacheIOThread::YieldAndRerun()) { 3810 LOG( 3811 ("CacheFileIOManager::RemoveTrashInternal() - Breaking loop for " 3812 "higher level events.")); 3813 mRemovingTrashDirs = true; 3814 return NS_OK; 3815 } 3816 3817 // Find some trash directory 3818 if (!mTrashDir) { 3819 MOZ_ASSERT(!mTrashDirEnumerator); 3820 3821 rv = FindTrashDirToRemove(); 3822 if (rv == NS_ERROR_NOT_AVAILABLE) { 3823 LOG( 3824 ("CacheFileIOManager::RemoveTrashInternal() - No trash directory " 3825 "found.")); 3826 return NS_OK; 3827 } 3828 NS_ENSURE_SUCCESS(rv, rv); 3829 3830 rv = mTrashDir->GetDirectoryEntries(getter_AddRefs(mTrashDirEnumerator)); 3831 NS_ENSURE_SUCCESS(rv, rv); 3832 3833 continue; // check elapsed time 3834 } 3835 3836 // We null out mTrashDirEnumerator once we remove all files in the 3837 // directory, so remove the trash directory if we don't have enumerator. 3838 if (!mTrashDirEnumerator) { 3839 rv = mTrashDir->Remove(false); 3840 if (NS_FAILED(rv)) { 3841 // There is no reason why removing an empty directory should fail, but 3842 // if it does, we should continue and try to remove all other trash 3843 // directories. 3844 nsAutoCString leafName; 3845 mTrashDir->GetNativeLeafName(leafName); 3846 mFailedTrashDirs.AppendElement(leafName); 3847 LOG( 3848 ("CacheFileIOManager::RemoveTrashInternal() - Cannot remove " 3849 "trashdir. [name=%s]", 3850 leafName.get())); 3851 } 3852 3853 mTrashDir = nullptr; 3854 continue; // check elapsed time 3855 } 3856 3857 nsCOMPtr<nsIFile> file; 3858 rv = mTrashDirEnumerator->GetNextFile(getter_AddRefs(file)); 3859 if (!file) { 3860 mTrashDirEnumerator->Close(); 3861 mTrashDirEnumerator = nullptr; 3862 continue; // check elapsed time 3863 } 3864 bool isDir = false; 3865 file->IsDirectory(&isDir); 3866 if (isDir) { 3867 NS_WARNING( 3868 "Found a directory in a trash directory! It will be removed " 3869 "recursively, but this can block IO thread for a while!"); 3870 if (LOG_ENABLED()) { 3871 LOG( 3872 ("CacheFileIOManager::RemoveTrashInternal() - Found a directory in " 3873 "a trash " 3874 "directory! It will be removed recursively, but this can block IO " 3875 "thread for a while! [file=%s]", 3876 file->HumanReadablePath().get())); 3877 } 3878 } 3879 file->Remove(isDir); 3880 } 3881 3882 MOZ_ASSERT_UNREACHABLE("We should never get here"); 3883 return NS_OK; 3884 } 3885 3886 nsresult CacheFileIOManager::FindTrashDirToRemove() { 3887 LOG(("CacheFileIOManager::FindTrashDirToRemove()")); 3888 3889 nsresult rv; 3890 3891 if (!mCacheDirectory) { 3892 return NS_ERROR_UNEXPECTED; 3893 } 3894 3895 // We call this method on the main thread during shutdown when user wants to 3896 // remove all cache files. 3897 MOZ_ASSERT(mIOThread->IsCurrentThread() || mShuttingDown); 3898 3899 nsCOMPtr<nsIDirectoryEnumerator> iter; 3900 rv = mCacheDirectory->GetDirectoryEntries(getter_AddRefs(iter)); 3901 NS_ENSURE_SUCCESS(rv, rv); 3902 3903 nsCOMPtr<nsIFile> file; 3904 while (NS_SUCCEEDED(iter->GetNextFile(getter_AddRefs(file))) && file) { 3905 bool isDir = false; 3906 file->IsDirectory(&isDir); 3907 if (!isDir) { 3908 continue; 3909 } 3910 3911 nsAutoCString leafName; 3912 rv = file->GetNativeLeafName(leafName); 3913 if (NS_FAILED(rv)) { 3914 continue; 3915 } 3916 3917 if (leafName.Length() < strlen(TRASH_DIR)) { 3918 continue; 3919 } 3920 3921 if (!StringBeginsWith(leafName, nsLiteralCString(TRASH_DIR))) { 3922 continue; 3923 } 3924 3925 if (mFailedTrashDirs.Contains(leafName)) { 3926 continue; 3927 } 3928 3929 LOG(("CacheFileIOManager::FindTrashDirToRemove() - Returning directory %s", 3930 leafName.get())); 3931 3932 mTrashDir = file; 3933 return NS_OK; 3934 } 3935 3936 // When we're here we've tried to delete all trash directories. Clear 3937 // mFailedTrashDirs so we will try to delete them again when we start removing 3938 // trash directories next time. 3939 mFailedTrashDirs.Clear(); 3940 return NS_ERROR_NOT_AVAILABLE; 3941 } 3942 3943 // static 3944 nsresult CacheFileIOManager::InitIndexEntry(CacheFileHandle* aHandle, 3945 OriginAttrsHash aOriginAttrsHash, 3946 bool aAnonymous, bool aPinning) { 3947 LOG( 3948 ("CacheFileIOManager::InitIndexEntry() [handle=%p, " 3949 "originAttrsHash=%" PRIx64 ", " 3950 "anonymous=%d, pinning=%d]", 3951 aHandle, aOriginAttrsHash, aAnonymous, aPinning)); 3952 3953 nsresult rv; 3954 RefPtr<CacheFileIOManager> ioMan = gInstance; 3955 3956 if (aHandle->IsClosed() || !ioMan) { 3957 return NS_ERROR_NOT_INITIALIZED; 3958 } 3959 3960 if (aHandle->IsSpecialFile()) { 3961 return NS_ERROR_UNEXPECTED; 3962 } 3963 3964 RefPtr<InitIndexEntryEvent> ev = 3965 new InitIndexEntryEvent(aHandle, aOriginAttrsHash, aAnonymous, aPinning); 3966 rv = ioMan->mIOThread->Dispatch(ev, aHandle->mPriority 3967 ? CacheIOThread::WRITE_PRIORITY 3968 : CacheIOThread::WRITE); 3969 NS_ENSURE_SUCCESS(rv, rv); 3970 3971 return NS_OK; 3972 } 3973 3974 // static 3975 nsresult CacheFileIOManager::UpdateIndexEntry(CacheFileHandle* aHandle, 3976 const uint32_t* aFrecency, 3977 const bool* aHasAltData, 3978 const uint16_t* aOnStartTime, 3979 const uint16_t* aOnStopTime, 3980 const uint8_t* aContentType) { 3981 LOG( 3982 ("CacheFileIOManager::UpdateIndexEntry() [handle=%p, frecency=%s, " 3983 "hasAltData=%s, onStartTime=%s, onStopTime=%s, contentType=%s]", 3984 aHandle, aFrecency ? nsPrintfCString("%u", *aFrecency).get() : "", 3985 aHasAltData ? (*aHasAltData ? "true" : "false") : "", 3986 aOnStartTime ? nsPrintfCString("%u", *aOnStartTime).get() : "", 3987 aOnStopTime ? nsPrintfCString("%u", *aOnStopTime).get() : "", 3988 aContentType ? nsPrintfCString("%u", *aContentType).get() : "")); 3989 3990 nsresult rv; 3991 RefPtr<CacheFileIOManager> ioMan = gInstance; 3992 3993 if (aHandle->IsClosed() || !ioMan) { 3994 return NS_ERROR_NOT_INITIALIZED; 3995 } 3996 3997 if (aHandle->IsSpecialFile()) { 3998 return NS_ERROR_UNEXPECTED; 3999 } 4000 4001 RefPtr<UpdateIndexEntryEvent> ev = new UpdateIndexEntryEvent( 4002 aHandle, aFrecency, aHasAltData, aOnStartTime, aOnStopTime, aContentType); 4003 rv = ioMan->mIOThread->Dispatch(ev, aHandle->mPriority 4004 ? CacheIOThread::WRITE_PRIORITY 4005 : CacheIOThread::WRITE); 4006 NS_ENSURE_SUCCESS(rv, rv); 4007 4008 return NS_OK; 4009 } 4010 4011 nsresult CacheFileIOManager::CreateFile(CacheFileHandle* aHandle) { 4012 MOZ_ASSERT(!aHandle->mFD); 4013 MOZ_ASSERT(aHandle->mFile); 4014 4015 nsresult rv; 4016 4017 if (aHandle->IsDoomed()) { 4018 nsCOMPtr<nsIFile> file; 4019 4020 rv = GetDoomedFile(getter_AddRefs(file)); 4021 NS_ENSURE_SUCCESS(rv, rv); 4022 4023 aHandle->mFile.swap(file); 4024 } else { 4025 bool exists; 4026 if (NS_SUCCEEDED(aHandle->mFile->Exists(&exists)) && exists) { 4027 NS_WARNING("Found a file that should not exist!"); 4028 } 4029 } 4030 4031 rv = OpenNSPRHandle(aHandle, true); 4032 NS_ENSURE_SUCCESS(rv, rv); 4033 4034 aHandle->mFileSize = 0; 4035 return NS_OK; 4036 } 4037 4038 // static 4039 void CacheFileIOManager::HashToStr(const SHA1Sum::Hash* aHash, 4040 nsACString& _retval) { 4041 _retval.Truncate(); 4042 const char hexChars[] = {'0', '1', '2', '3', '4', '5', '6', '7', 4043 '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'}; 4044 for (uint32_t i = 0; i < sizeof(SHA1Sum::Hash); i++) { 4045 _retval.Append(hexChars[(*aHash)[i] >> 4]); 4046 _retval.Append(hexChars[(*aHash)[i] & 0xF]); 4047 } 4048 } 4049 4050 // static 4051 nsresult CacheFileIOManager::StrToHash(const nsACString& aHash, 4052 SHA1Sum::Hash* _retval) { 4053 if (aHash.Length() != 2 * sizeof(SHA1Sum::Hash)) { 4054 return NS_ERROR_INVALID_ARG; 4055 } 4056 4057 for (uint32_t i = 0; i < aHash.Length(); i++) { 4058 uint8_t value; 4059 4060 if (aHash[i] >= '0' && aHash[i] <= '9') { 4061 value = aHash[i] - '0'; 4062 } else if (aHash[i] >= 'A' && aHash[i] <= 'F') { 4063 value = aHash[i] - 'A' + 10; 4064 } else if (aHash[i] >= 'a' && aHash[i] <= 'f') { 4065 value = aHash[i] - 'a' + 10; 4066 } else { 4067 return NS_ERROR_INVALID_ARG; 4068 } 4069 4070 if (i % 2 == 0) { 4071 (reinterpret_cast<uint8_t*>(_retval))[i / 2] = value << 4; 4072 } else { 4073 (reinterpret_cast<uint8_t*>(_retval))[i / 2] += value; 4074 } 4075 } 4076 4077 return NS_OK; 4078 } 4079 4080 nsresult CacheFileIOManager::GetFile(const SHA1Sum::Hash* aHash, 4081 nsIFile** _retval) { 4082 nsresult rv; 4083 nsCOMPtr<nsIFile> file; 4084 rv = mCacheDirectory->Clone(getter_AddRefs(file)); 4085 NS_ENSURE_SUCCESS(rv, rv); 4086 4087 rv = file->AppendNative(nsLiteralCString(ENTRIES_DIR)); 4088 NS_ENSURE_SUCCESS(rv, rv); 4089 4090 nsAutoCString leafName; 4091 HashToStr(aHash, leafName); 4092 4093 rv = file->AppendNative(leafName); 4094 NS_ENSURE_SUCCESS(rv, rv); 4095 4096 file.swap(*_retval); 4097 return NS_OK; 4098 } 4099 4100 nsresult CacheFileIOManager::GetSpecialFile(const nsACString& aKey, 4101 nsIFile** _retval) { 4102 nsresult rv; 4103 nsCOMPtr<nsIFile> file; 4104 rv = mCacheDirectory->Clone(getter_AddRefs(file)); 4105 NS_ENSURE_SUCCESS(rv, rv); 4106 4107 rv = file->AppendNative(aKey); 4108 NS_ENSURE_SUCCESS(rv, rv); 4109 4110 file.swap(*_retval); 4111 return NS_OK; 4112 } 4113 4114 nsresult CacheFileIOManager::GetDoomedFile(nsIFile** _retval) { 4115 nsresult rv; 4116 nsCOMPtr<nsIFile> file; 4117 rv = mCacheDirectory->Clone(getter_AddRefs(file)); 4118 NS_ENSURE_SUCCESS(rv, rv); 4119 4120 rv = file->AppendNative(nsLiteralCString(DOOMED_DIR)); 4121 NS_ENSURE_SUCCESS(rv, rv); 4122 4123 rv = file->AppendNative("dummyleaf"_ns); 4124 NS_ENSURE_SUCCESS(rv, rv); 4125 4126 const int32_t kMaxTries = 64; 4127 srand(static_cast<unsigned>(PR_Now())); 4128 nsAutoCString leafName; 4129 for (int32_t triesCount = 0;; ++triesCount) { 4130 leafName.AppendInt(rand()); 4131 rv = file->SetNativeLeafName(leafName); 4132 NS_ENSURE_SUCCESS(rv, rv); 4133 4134 bool exists; 4135 if (NS_SUCCEEDED(file->Exists(&exists)) && !exists) { 4136 break; 4137 } 4138 4139 if (triesCount == kMaxTries) { 4140 LOG( 4141 ("CacheFileIOManager::GetDoomedFile() - Could not find unused file " 4142 "name in %d tries.", 4143 kMaxTries)); 4144 return NS_ERROR_FAILURE; 4145 } 4146 4147 leafName.Truncate(); 4148 } 4149 4150 file.swap(*_retval); 4151 return NS_OK; 4152 } 4153 4154 nsresult CacheFileIOManager::IsEmptyDirectory(nsIFile* aFile, bool* _retval) { 4155 MOZ_ASSERT(mIOThread->IsCurrentThread()); 4156 4157 nsresult rv; 4158 4159 nsCOMPtr<nsIDirectoryEnumerator> enumerator; 4160 rv = aFile->GetDirectoryEntries(getter_AddRefs(enumerator)); 4161 NS_ENSURE_SUCCESS(rv, rv); 4162 4163 bool hasMoreElements = false; 4164 rv = enumerator->HasMoreElements(&hasMoreElements); 4165 NS_ENSURE_SUCCESS(rv, rv); 4166 4167 *_retval = !hasMoreElements; 4168 return NS_OK; 4169 } 4170 4171 nsresult CacheFileIOManager::CheckAndCreateDir(nsIFile* aFile, const char* aDir, 4172 bool aEnsureEmptyDir) { 4173 nsresult rv; 4174 4175 nsCOMPtr<nsIFile> file; 4176 if (!aDir) { 4177 file = aFile; 4178 } else { 4179 nsAutoCString dir(aDir); 4180 rv = aFile->Clone(getter_AddRefs(file)); 4181 NS_ENSURE_SUCCESS(rv, rv); 4182 rv = file->AppendNative(dir); 4183 NS_ENSURE_SUCCESS(rv, rv); 4184 } 4185 4186 bool exists = false; 4187 rv = file->Exists(&exists); 4188 if (NS_SUCCEEDED(rv) && exists) { 4189 bool isDirectory = false; 4190 rv = file->IsDirectory(&isDirectory); 4191 if (NS_FAILED(rv) || !isDirectory) { 4192 // Try to remove the file 4193 rv = file->Remove(false); 4194 if (NS_SUCCEEDED(rv)) { 4195 exists = false; 4196 } 4197 } 4198 NS_ENSURE_SUCCESS(rv, rv); 4199 } 4200 4201 if (aEnsureEmptyDir && NS_SUCCEEDED(rv) && exists) { 4202 bool isEmpty; 4203 rv = IsEmptyDirectory(file, &isEmpty); 4204 NS_ENSURE_SUCCESS(rv, rv); 4205 4206 if (!isEmpty) { 4207 // Don't check the result, if this fails, it's OK. We do this 4208 // only for the doomed directory that doesn't need to be deleted 4209 // for the cost of completely disabling the whole browser. 4210 TrashDirectory(file); 4211 } 4212 } 4213 4214 if (NS_SUCCEEDED(rv) && !exists) { 4215 rv = file->Create(nsIFile::DIRECTORY_TYPE, 0700); 4216 } 4217 if (NS_FAILED(rv)) { 4218 NS_WARNING("Cannot create directory"); 4219 return NS_ERROR_FAILURE; 4220 } 4221 4222 return NS_OK; 4223 } 4224 4225 nsresult CacheFileIOManager::CreateCacheTree() { 4226 MOZ_ASSERT(mIOThread->IsCurrentThread()); 4227 MOZ_ASSERT(!mTreeCreated); 4228 4229 if (!mCacheDirectory || mTreeCreationFailed) { 4230 return NS_ERROR_FILE_INVALID_PATH; 4231 } 4232 4233 nsresult rv; 4234 4235 // Set the flag here and clear it again below when the tree is created 4236 // successfully. 4237 mTreeCreationFailed = true; 4238 4239 // ensure parent directory exists 4240 nsCOMPtr<nsIFile> parentDir; 4241 rv = mCacheDirectory->GetParent(getter_AddRefs(parentDir)); 4242 NS_ENSURE_SUCCESS(rv, rv); 4243 rv = CheckAndCreateDir(parentDir, nullptr, false); 4244 NS_ENSURE_SUCCESS(rv, rv); 4245 4246 // ensure cache directory exists 4247 rv = CheckAndCreateDir(mCacheDirectory, nullptr, false); 4248 NS_ENSURE_SUCCESS(rv, rv); 4249 4250 // ensure entries directory exists 4251 rv = CheckAndCreateDir(mCacheDirectory, ENTRIES_DIR, false); 4252 NS_ENSURE_SUCCESS(rv, rv); 4253 4254 // ensure doomed directory exists 4255 rv = CheckAndCreateDir(mCacheDirectory, DOOMED_DIR, true); 4256 NS_ENSURE_SUCCESS(rv, rv); 4257 4258 mTreeCreated = true; 4259 mTreeCreationFailed = false; 4260 4261 if (!mContextEvictor) { 4262 RefPtr<CacheFileContextEvictor> contextEvictor; 4263 contextEvictor = new CacheFileContextEvictor(); 4264 4265 // Init() method will try to load unfinished contexts from the disk. Store 4266 // the evictor as a member only when there is some unfinished job. 4267 contextEvictor->Init(mCacheDirectory); 4268 if (contextEvictor->ContextsCount()) { 4269 contextEvictor.swap(mContextEvictor); 4270 } 4271 } 4272 4273 StartRemovingTrash(); 4274 4275 return NS_OK; 4276 } 4277 4278 nsresult CacheFileIOManager::OpenNSPRHandle(CacheFileHandle* aHandle, 4279 bool aCreate) { 4280 LOG(("CacheFileIOManager::OpenNSPRHandle BEGIN, handle=%p", aHandle)); 4281 4282 MOZ_ASSERT(CacheFileIOManager::IsOnIOThreadOrCeased()); 4283 MOZ_ASSERT(!aHandle->mFD); 4284 MOZ_ASSERT(mHandlesByLastUsed.IndexOf(aHandle) == mHandlesByLastUsed.NoIndex); 4285 MOZ_ASSERT(mHandlesByLastUsed.Length() <= kOpenHandlesLimit); 4286 MOZ_ASSERT((aCreate && !aHandle->mFileExists) || 4287 (!aCreate && aHandle->mFileExists)); 4288 4289 nsresult rv; 4290 4291 if (mHandlesByLastUsed.Length() == kOpenHandlesLimit) { 4292 // close handle that hasn't been used for the longest time 4293 rv = MaybeReleaseNSPRHandleInternal(mHandlesByLastUsed[0], true); 4294 NS_ENSURE_SUCCESS(rv, rv); 4295 } 4296 4297 if (aCreate) { 4298 rv = aHandle->mFile->OpenNSPRFileDesc( 4299 PR_RDWR | PR_CREATE_FILE | PR_TRUNCATE, 0600, &aHandle->mFD); 4300 if (rv == NS_ERROR_FILE_ALREADY_EXISTS || // error from nsLocalFileWin 4301 rv == NS_ERROR_FILE_NO_DEVICE_SPACE) { // error from nsLocalFileUnix 4302 LOG( 4303 ("CacheFileIOManager::OpenNSPRHandle() - Cannot create a new file, we" 4304 " might reached a limit on FAT32. Will evict a single entry and try " 4305 "again. [hash=%08x%08x%08x%08x%08x]", 4306 LOGSHA1(aHandle->Hash()))); 4307 4308 SHA1Sum::Hash hash; 4309 uint32_t cnt; 4310 auto snapshot = CacheIndex::GetSortedSnapshotForEviction(); 4311 rv = CacheIndex::GetEntryForEviction(snapshot, true, &hash, &cnt); 4312 if (NS_SUCCEEDED(rv)) { 4313 rv = DoomFileByKeyInternal(&hash); 4314 } 4315 if (NS_SUCCEEDED(rv)) { 4316 rv = aHandle->mFile->OpenNSPRFileDesc( 4317 PR_RDWR | PR_CREATE_FILE | PR_TRUNCATE, 0600, &aHandle->mFD); 4318 LOG( 4319 ("CacheFileIOManager::OpenNSPRHandle() - Successfully evicted entry" 4320 " with hash %08x%08x%08x%08x%08x. %s to create the new file.", 4321 LOGSHA1(&hash), NS_SUCCEEDED(rv) ? "Succeeded" : "Failed")); 4322 } else { 4323 LOG( 4324 ("CacheFileIOManager::OpenNSPRHandle() - Couldn't evict an existing" 4325 " entry.")); 4326 rv = NS_ERROR_FILE_NO_DEVICE_SPACE; 4327 } 4328 } 4329 if (NS_FAILED(rv)) { 4330 LOG( 4331 ("CacheFileIOManager::OpenNSPRHandle() Create failed with " 4332 "0x%08" PRIx32, 4333 static_cast<uint32_t>(rv))); 4334 } 4335 NS_ENSURE_SUCCESS(rv, rv); 4336 4337 aHandle->mFileExists = true; 4338 } else { 4339 rv = aHandle->mFile->OpenNSPRFileDesc(PR_RDWR, 0600, &aHandle->mFD); 4340 if (NS_ERROR_FILE_NOT_FOUND == rv) { 4341 LOG((" file doesn't exists")); 4342 aHandle->mFileExists = false; 4343 return DoomFileInternal(aHandle); 4344 } 4345 if (NS_FAILED(rv)) { 4346 LOG(("CacheFileIOManager::OpenNSPRHandle() Open failed with 0x%08" PRIx32, 4347 static_cast<uint32_t>(rv))); 4348 } 4349 NS_ENSURE_SUCCESS(rv, rv); 4350 } 4351 4352 mHandlesByLastUsed.AppendElement(aHandle); 4353 4354 LOG(("CacheFileIOManager::OpenNSPRHandle END, handle=%p", aHandle)); 4355 4356 return NS_OK; 4357 } 4358 4359 void CacheFileIOManager::NSPRHandleUsed(CacheFileHandle* aHandle) { 4360 MOZ_ASSERT(CacheFileIOManager::IsOnIOThreadOrCeased()); 4361 MOZ_ASSERT(aHandle->mFD); 4362 4363 DebugOnly<bool> found{}; 4364 found = mHandlesByLastUsed.RemoveElement(aHandle); 4365 MOZ_ASSERT(found); 4366 4367 mHandlesByLastUsed.AppendElement(aHandle); 4368 } 4369 4370 nsresult CacheFileIOManager::SyncRemoveDir(nsIFile* aFile, const char* aDir) { 4371 nsresult rv; 4372 nsCOMPtr<nsIFile> file; 4373 4374 if (!aFile) { 4375 return NS_ERROR_INVALID_ARG; 4376 } 4377 4378 if (!aDir) { 4379 file = aFile; 4380 } else { 4381 rv = aFile->Clone(getter_AddRefs(file)); 4382 if (NS_WARN_IF(NS_FAILED(rv))) { 4383 return rv; 4384 } 4385 4386 rv = file->AppendNative(nsDependentCString(aDir)); 4387 if (NS_WARN_IF(NS_FAILED(rv))) { 4388 return rv; 4389 } 4390 } 4391 4392 if (LOG_ENABLED()) { 4393 LOG(("CacheFileIOManager::SyncRemoveDir() - Removing directory %s", 4394 file->HumanReadablePath().get())); 4395 } 4396 4397 rv = file->Remove(true); 4398 if (NS_WARN_IF(NS_FAILED(rv))) { 4399 LOG( 4400 ("CacheFileIOManager::SyncRemoveDir() - Removing failed! " 4401 "[rv=0x%08" PRIx32 "]", 4402 static_cast<uint32_t>(rv))); 4403 } 4404 4405 return rv; 4406 } 4407 4408 nsresult CacheFileIOManager::DispatchPurgeTask( 4409 const nsCString& aCacheDirName, const nsCString& aSecondsToWait, 4410 const nsCString& aPurgeExtension) { 4411 #if !defined(MOZ_BACKGROUNDTASKS) 4412 // If background tasks are disabled, then we should just bail out early. 4413 return NS_ERROR_NOT_IMPLEMENTED; 4414 #else 4415 nsCOMPtr<nsIFile> cacheDir; 4416 nsresult rv = mCacheDirectory->Clone(getter_AddRefs(cacheDir)); 4417 NS_ENSURE_SUCCESS(rv, rv); 4418 4419 nsCOMPtr<nsIFile> profileDir; 4420 rv = cacheDir->GetParent(getter_AddRefs(profileDir)); 4421 NS_ENSURE_SUCCESS(rv, rv); 4422 4423 nsCOMPtr<nsIFile> lf; 4424 rv = XRE_GetBinaryPath(getter_AddRefs(lf)); 4425 NS_ENSURE_SUCCESS(rv, rv); 4426 4427 nsAutoCString path; 4428 # if !defined(XP_WIN) 4429 rv = profileDir->GetNativePath(path); 4430 # else 4431 rv = profileDir->GetNativeTarget(path); 4432 # endif 4433 NS_ENSURE_SUCCESS(rv, rv); 4434 4435 nsCOMPtr<nsIBackgroundTasksRunner> runner = 4436 do_GetService("@mozilla.org/backgroundtasksrunner;1"); 4437 4438 return runner->RemoveDirectoryInDetachedProcess( 4439 path, aCacheDirName, aSecondsToWait, aPurgeExtension, "HttpCache"_ns); 4440 #endif 4441 } 4442 4443 void CacheFileIOManager::SyncRemoveAllCacheFiles() { 4444 LOG(("CacheFileIOManager::SyncRemoveAllCacheFiles()")); 4445 nsresult rv; 4446 4447 // If we are already running in a background task, we 4448 // don't want to spawn yet another one at shutdown. 4449 if (inBackgroundTask()) { 4450 return; 4451 } 4452 4453 if (StaticPrefs::network_cache_shutdown_purge_in_background_task()) { 4454 rv = [&]() -> nsresult { 4455 nsresult rv; 4456 4457 // If there is no cache directory, there's nothing to remove. 4458 if (!mCacheDirectory) { 4459 return NS_OK; 4460 } 4461 4462 nsAutoCString leafName; 4463 rv = mCacheDirectory->GetNativeLeafName(leafName); 4464 NS_ENSURE_SUCCESS(rv, rv); 4465 4466 leafName.Append('.'); 4467 4468 PRExplodedTime now; 4469 PR_ExplodeTime(PR_Now(), PR_GMTParameters, &now); 4470 leafName.Append(nsPrintfCString( 4471 "%04d-%02d-%02d-%02d-%02d-%02d", now.tm_year, now.tm_month + 1, 4472 now.tm_mday, now.tm_hour, now.tm_min, now.tm_sec)); 4473 leafName.Append(kPurgeExtension); 4474 4475 nsAutoCString secondsToWait; 4476 secondsToWait.AppendInt( 4477 StaticPrefs::network_cache_shutdown_purge_folder_wait_seconds()); 4478 4479 rv = DispatchPurgeTask(leafName, secondsToWait, kPurgeExtension); 4480 NS_ENSURE_SUCCESS(rv, rv); 4481 4482 rv = mCacheDirectory->RenameToNative(nullptr, leafName); 4483 NS_ENSURE_SUCCESS(rv, rv); 4484 4485 return NS_OK; 4486 }(); 4487 4488 // Dispatching to the background task has succeeded. This is finished. 4489 if (NS_SUCCEEDED(rv)) { 4490 return; 4491 } 4492 } 4493 4494 SyncRemoveDir(mCacheDirectory, ENTRIES_DIR); 4495 SyncRemoveDir(mCacheDirectory, DOOMED_DIR); 4496 4497 // Clear any intermediate state of trash dir enumeration. 4498 mFailedTrashDirs.Clear(); 4499 mTrashDir = nullptr; 4500 4501 while (true) { 4502 // FindTrashDirToRemove() fills mTrashDir if there is any trash directory. 4503 rv = FindTrashDirToRemove(); 4504 if (rv == NS_ERROR_NOT_AVAILABLE) { 4505 LOG( 4506 ("CacheFileIOManager::SyncRemoveAllCacheFiles() - No trash directory " 4507 "found.")); 4508 break; 4509 } 4510 if (NS_WARN_IF(NS_FAILED(rv))) { 4511 LOG( 4512 ("CacheFileIOManager::SyncRemoveAllCacheFiles() - " 4513 "FindTrashDirToRemove() returned an unexpected error. " 4514 "[rv=0x%08" PRIx32 "]", 4515 static_cast<uint32_t>(rv))); 4516 break; 4517 } 4518 4519 rv = SyncRemoveDir(mTrashDir, nullptr); 4520 if (NS_FAILED(rv)) { 4521 nsAutoCString leafName; 4522 mTrashDir->GetNativeLeafName(leafName); 4523 mFailedTrashDirs.AppendElement(leafName); 4524 } 4525 } 4526 } 4527 4528 // Returns default ("smart") size (in KB) of cache, given available disk space 4529 // (also in KB) 4530 static uint32_t SmartCacheSize(const int64_t availKB) { 4531 uint32_t maxSize; 4532 4533 if (CacheObserver::ClearCacheOnShutdown()) { 4534 maxSize = kMaxClearOnShutdownCacheSizeKB; 4535 } else { 4536 maxSize = kMaxCacheSizeKB; 4537 } 4538 4539 if (availKB > 25 * 1024 * 1024) { 4540 return maxSize; // skip computing if we're over 25 GB 4541 } 4542 4543 // Grow/shrink in 10 MB units, deliberately, so that in the common case we 4544 // don't shrink cache and evict items every time we startup (it's important 4545 // that we don't slow down startup benchmarks). 4546 uint32_t sz10MBs = 0; 4547 uint32_t avail10MBs = availKB / (1024 * 10); 4548 4549 // 2.5% of space above 7GB 4550 if (avail10MBs > 700) { 4551 sz10MBs += static_cast<uint32_t>((avail10MBs - 700) * .025); 4552 avail10MBs = 700; 4553 } 4554 // 7.5% of space between 500 MB -> 7 GB 4555 if (avail10MBs > 50) { 4556 sz10MBs += static_cast<uint32_t>((avail10MBs - 50) * .075); 4557 avail10MBs = 50; 4558 } 4559 4560 #ifdef ANDROID 4561 // On Android, smaller/older devices may have very little storage and 4562 // device owners may be sensitive to storage footprint: Use a smaller 4563 // percentage of available space and a smaller minimum. 4564 4565 // 16% of space up to 500 MB (10 MB min) 4566 sz10MBs += std::max<uint32_t>(1, static_cast<uint32_t>(avail10MBs * .16)); 4567 #else 4568 // 30% of space up to 500 MB (50 MB min) 4569 sz10MBs += std::max<uint32_t>(5, static_cast<uint32_t>(avail10MBs * .3)); 4570 #endif 4571 4572 return std::min<uint32_t>(maxSize, sz10MBs * 10 * 1024); 4573 } 4574 4575 nsresult CacheFileIOManager::UpdateSmartCacheSize(int64_t aFreeSpace) { 4576 MOZ_ASSERT(mIOThread->IsCurrentThread()); 4577 4578 nsresult rv; 4579 4580 if (!CacheObserver::SmartCacheSizeEnabled()) { 4581 return NS_ERROR_NOT_AVAILABLE; 4582 } 4583 4584 // Wait at least kSmartSizeUpdateInterval before recomputing smart size. 4585 static const TimeDuration kUpdateLimit = 4586 TimeDuration::FromMilliseconds(kSmartSizeUpdateInterval); 4587 if (!mLastSmartSizeTime.IsNull() && 4588 (TimeStamp::NowLoRes() - mLastSmartSizeTime) < kUpdateLimit) { 4589 return NS_OK; 4590 } 4591 4592 // Do not compute smart size when cache size is not reliable. 4593 bool isUpToDate = false; 4594 CacheIndex::IsUpToDate(&isUpToDate); 4595 if (!isUpToDate) { 4596 return NS_ERROR_NOT_AVAILABLE; 4597 } 4598 4599 uint32_t cacheUsage; 4600 rv = CacheIndex::GetCacheSize(&cacheUsage); 4601 if (NS_WARN_IF(NS_FAILED(rv))) { 4602 LOG( 4603 ("CacheFileIOManager::UpdateSmartCacheSize() - Cannot get cacheUsage! " 4604 "[rv=0x%08" PRIx32 "]", 4605 static_cast<uint32_t>(rv))); 4606 return rv; 4607 } 4608 4609 mLastSmartSizeTime = TimeStamp::NowLoRes(); 4610 4611 uint32_t smartSize = SmartCacheSize(aFreeSpace + cacheUsage); 4612 4613 if (smartSize == CacheObserver::DiskCacheCapacity()) { 4614 // Smart size has not changed. 4615 return NS_OK; 4616 } 4617 4618 CacheObserver::SetSmartDiskCacheCapacity(smartSize); 4619 4620 return NS_OK; 4621 } 4622 4623 // Memory reporting 4624 4625 namespace { 4626 4627 // A helper class that dispatches and waits for an event that gets result of 4628 // CacheFileIOManager->mHandles.SizeOfExcludingThis() on the I/O thread 4629 // to safely get handles memory report. 4630 // We must do this, since the handle list is only accessed and managed w/o 4631 // locking on the I/O thread. That is by design. 4632 class SizeOfHandlesRunnable : public Runnable { 4633 public: 4634 SizeOfHandlesRunnable(mozilla::MallocSizeOf mallocSizeOf, 4635 CacheFileHandles const& handles, 4636 nsTArray<CacheFileHandle*> const& specialHandles) 4637 : Runnable("net::SizeOfHandlesRunnable"), 4638 mMonitor("SizeOfHandlesRunnable.mMonitor"), 4639 mMonitorNotified(false), 4640 mMallocSizeOf(mallocSizeOf), 4641 mHandles(handles), 4642 mSpecialHandles(specialHandles), 4643 mSize(0) {} 4644 4645 size_t Get(CacheIOThread* thread) { 4646 nsCOMPtr<nsIEventTarget> target = thread->Target(); 4647 if (!target) { 4648 NS_ERROR("If we have the I/O thread we also must have the I/O target"); 4649 return 0; 4650 } 4651 4652 mozilla::MonitorAutoLock mon(mMonitor); 4653 mMonitorNotified = false; 4654 nsresult rv = target->Dispatch(this, nsIEventTarget::DISPATCH_NORMAL); 4655 if (NS_FAILED(rv)) { 4656 NS_ERROR("Dispatch failed, cannot do memory report of CacheFileHandles"); 4657 return 0; 4658 } 4659 4660 while (!mMonitorNotified) { 4661 mon.Wait(); 4662 } 4663 return mSize; 4664 } 4665 4666 NS_IMETHOD Run() override { 4667 mozilla::MonitorAutoLock mon(mMonitor); 4668 // Excluding this since the object itself is a member of CacheFileIOManager 4669 // reported in CacheFileIOManager::SizeOfIncludingThis as part of |this|. 4670 mSize = mHandles.SizeOfExcludingThis(mMallocSizeOf); 4671 for (uint32_t i = 0; i < mSpecialHandles.Length(); ++i) { 4672 mSize += mSpecialHandles[i]->SizeOfIncludingThis(mMallocSizeOf); 4673 } 4674 4675 mMonitorNotified = true; 4676 mon.Notify(); 4677 return NS_OK; 4678 } 4679 4680 private: 4681 mozilla::Monitor mMonitor; 4682 bool mMonitorNotified; 4683 mozilla::MallocSizeOf mMallocSizeOf; 4684 CacheFileHandles const& mHandles; 4685 nsTArray<CacheFileHandle*> const& mSpecialHandles; 4686 size_t mSize; 4687 }; 4688 4689 } // namespace 4690 4691 size_t CacheFileIOManager::SizeOfExcludingThisInternal( 4692 mozilla::MallocSizeOf mallocSizeOf) const { 4693 size_t n = 0; 4694 4695 if (mIOThread) { 4696 n += mIOThread->SizeOfIncludingThis(mallocSizeOf); 4697 4698 // mHandles and mSpecialHandles must be accessed only on the I/O thread, 4699 // must sync dispatch. 4700 RefPtr<SizeOfHandlesRunnable> sizeOfHandlesRunnable = 4701 new SizeOfHandlesRunnable(mallocSizeOf, mHandles, mSpecialHandles); 4702 n += sizeOfHandlesRunnable->Get(mIOThread); 4703 } 4704 4705 // mHandlesByLastUsed just refers handles reported by mHandles. 4706 4707 // mCacheDirectory is an nsIFile which we don't have reporting for. 4708 4709 // mMetadataWritesTimer is an nsITimer which we don't have reporting for. 4710 // Note that it would need to be accessed on the I/O thread. 4711 4712 // mTrashTimer is an nsITimer which we don't have reporting for. 4713 4714 // mTrashDir is an nsIFile which we don't have reporting for. 4715 4716 for (uint32_t i = 0; i < mFailedTrashDirs.Length(); ++i) { 4717 n += mFailedTrashDirs[i].SizeOfExcludingThisIfUnshared(mallocSizeOf); 4718 } 4719 4720 return n; 4721 } 4722 4723 // static 4724 size_t CacheFileIOManager::SizeOfExcludingThis( 4725 mozilla::MallocSizeOf mallocSizeOf) { 4726 if (!gInstance) return 0; 4727 4728 return gInstance->SizeOfExcludingThisInternal(mallocSizeOf); 4729 } 4730 4731 // static 4732 size_t CacheFileIOManager::SizeOfIncludingThis( 4733 mozilla::MallocSizeOf mallocSizeOf) { 4734 return mallocSizeOf(gInstance) + SizeOfExcludingThis(mallocSizeOf); 4735 } 4736 4737 #if defined(MOZ_CACHE_ASYNC_IO) 4738 void CacheFileIOManager::DispatchPendingEvents() { 4739 auto pendingEvents = std::move(mPendingEvents); 4740 for (auto& event : pendingEvents) { 4741 mIOThread->Dispatch(event.first, event.second); 4742 } 4743 } 4744 #endif 4745 4746 } // namespace mozilla::net