tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

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