tor-browser

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

CacheIndex.cpp (113739B)


      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 "CacheIndex.h"
      6 
      7 #include "CacheLog.h"
      8 #include "CacheFileIOManager.h"
      9 #include "CacheFileMetadata.h"
     10 #include "CacheFileUtils.h"
     11 #include "CacheIndexIterator.h"
     12 #include "CacheIndexContextIterator.h"
     13 #include "nsThreadUtils.h"
     14 #include "nsPrintfCString.h"
     15 #include "mozilla/DebugOnly.h"
     16 #include "prinrval.h"
     17 #include "nsIFile.h"
     18 #include "nsITimer.h"
     19 #include "nsNetUtil.h"
     20 #include "mozilla/AutoRestore.h"
     21 #include <algorithm>
     22 #include "mozilla/StaticPrefs_network.h"
     23 #include "mozilla/glean/NetwerkCache2Metrics.h"
     24 
     25 #define kMinUnwrittenChanges 300
     26 #define kMinDumpInterval 20000  // in milliseconds
     27 #define kMaxBufSize 16384
     28 #define kIndexVersion 0x0000000A
     29 #define kUpdateIndexStartDelay 50000  // in milliseconds
     30 #define kTelemetryReportBytesLimit (2U * 1024U * 1024U * 1024U)  // 2GB
     31 
     32 #define INDEX_NAME "index"
     33 #define TEMP_INDEX_NAME "index.tmp"
     34 #define JOURNAL_NAME "index.log"
     35 
     36 namespace mozilla::net {
     37 
     38 namespace {
     39 
     40 class FrecencyComparator {
     41 public:
     42  bool Equals(const RefPtr<CacheIndexRecordWrapper>& a,
     43              const RefPtr<CacheIndexRecordWrapper>& b) const {
     44    if (!a || !b) {
     45      return false;
     46    }
     47 
     48    return a->Get()->mFrecency == b->Get()->mFrecency;
     49  }
     50  bool LessThan(const RefPtr<CacheIndexRecordWrapper>& a,
     51                const RefPtr<CacheIndexRecordWrapper>& b) const {
     52    // Removed (=null) entries must be at the end of the array.
     53    if (!a) {
     54      return false;
     55    }
     56    if (!b) {
     57      return true;
     58    }
     59 
     60    // Place entries with frecency 0 at the end of the non-removed entries.
     61    if (a->Get()->mFrecency == 0) {
     62      return false;
     63    }
     64    if (b->Get()->mFrecency == 0) {
     65      return true;
     66    }
     67 
     68    return a->Get()->mFrecency < b->Get()->mFrecency;
     69  }
     70 };
     71 
     72 }  // namespace
     73 
     74 // used to dispatch a wrapper deletion the caller's thread
     75 // cannot be used on IOThread after shutdown begins
     76 class DeleteCacheIndexRecordWrapper : public Runnable {
     77  CacheIndexRecordWrapper* mWrapper;
     78 
     79 public:
     80  explicit DeleteCacheIndexRecordWrapper(CacheIndexRecordWrapper* wrapper)
     81      : Runnable("net::CacheIndex::DeleteCacheIndexRecordWrapper"),
     82        mWrapper(wrapper) {}
     83  NS_IMETHOD Run() override {
     84    StaticMutexAutoLock lock(CacheIndex::sLock);
     85 
     86    // if somehow the item is still in the frecency storage, remove it
     87    RefPtr<CacheIndex> index = CacheIndex::gInstance;
     88    if (index) {
     89      bool found = index->mFrecencyStorage.RecordExistedUnlocked(mWrapper);
     90      if (found) {
     91        LOG(
     92            ("DeleteCacheIndexRecordWrapper::Run() - \
     93            record wrapper found in frecency storage during deletion"));
     94        index->mFrecencyStorage.RemoveRecord(mWrapper, lock);
     95      }
     96    }
     97 
     98    delete mWrapper;
     99    return NS_OK;
    100  }
    101 };
    102 
    103 void CacheIndexRecordWrapper::DispatchDeleteSelfToCurrentThread() {
    104  // Dispatch during shutdown will not trigger DeleteCacheIndexRecordWrapper
    105  nsCOMPtr<nsIRunnable> event = new DeleteCacheIndexRecordWrapper(this);
    106  MOZ_ALWAYS_SUCCEEDS(NS_DispatchToCurrentThread(event));
    107 }
    108 
    109 CacheIndexRecordWrapper::~CacheIndexRecordWrapper() {
    110 #ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED
    111  CacheIndex::sLock.AssertCurrentThreadOwns();
    112  RefPtr<CacheIndex> index = CacheIndex::gInstance;
    113  if (index) {
    114    bool found = index->mFrecencyStorage.RecordExistedUnlocked(this);
    115    MOZ_DIAGNOSTIC_ASSERT(!found);
    116  }
    117 #endif
    118 }
    119 
    120 /**
    121 * This helper class is responsible for keeping CacheIndex::mIndexStats and
    122 * CacheIndex::mFrecencyStorage up to date.
    123 */
    124 class MOZ_RAII CacheIndexEntryAutoManage {
    125 public:
    126  CacheIndexEntryAutoManage(const SHA1Sum::Hash* aHash, CacheIndex* aIndex,
    127                            const StaticMutexAutoLock& aProofOfLock)
    128      MOZ_REQUIRES(CacheIndex::sLock)
    129      : mIndex(aIndex), mProofOfLock(aProofOfLock) {
    130    mHash = aHash;
    131    const CacheIndexEntry* entry = FindEntry();
    132    mIndex->mIndexStats.BeforeChange(entry);
    133    if (entry && entry->IsInitialized() && !entry->IsRemoved()) {
    134      mOldRecord = entry->mRec;
    135    }
    136  }
    137 
    138  ~CacheIndexEntryAutoManage() MOZ_REQUIRES(CacheIndex::sLock) {
    139    const CacheIndexEntry* entry = FindEntry();
    140    mIndex->mIndexStats.AfterChange(entry);
    141    if (!entry || !entry->IsInitialized() || entry->IsRemoved()) {
    142      entry = nullptr;
    143    }
    144 
    145    if (entry && !mOldRecord) {
    146      mIndex->mFrecencyStorage.AppendRecord(entry->mRec, mProofOfLock);
    147      mIndex->AddRecordToIterators(entry->mRec, mProofOfLock);
    148    } else if (!entry && mOldRecord) {
    149      mIndex->mFrecencyStorage.RemoveRecord(mOldRecord, mProofOfLock);
    150      mIndex->RemoveRecordFromIterators(mOldRecord, mProofOfLock);
    151    } else if (entry && mOldRecord) {
    152      if (entry->mRec != mOldRecord) {
    153        // record has a different address, we have to replace it
    154        mIndex->ReplaceRecordInIterators(mOldRecord, entry->mRec, mProofOfLock);
    155 
    156        mIndex->mFrecencyStorage.ReplaceRecord(mOldRecord, entry->mRec,
    157                                               mProofOfLock);
    158      }
    159    } else {
    160      // both entries were removed or not initialized, do nothing
    161    }
    162  }
    163 
    164  // We cannot rely on nsTHashtable::GetEntry() in case we are removing entries
    165  // while iterating. Destructor is called before the entry is removed. Caller
    166  // must call one of following methods to skip lookup in the hashtable.
    167  void DoNotSearchInIndex() { mDoNotSearchInIndex = true; }
    168  void DoNotSearchInUpdates() { mDoNotSearchInUpdates = true; }
    169 
    170 private:
    171  const CacheIndexEntry* FindEntry() MOZ_REQUIRES(CacheIndex::sLock) {
    172    const CacheIndexEntry* entry = nullptr;
    173 
    174    switch (mIndex->mState) {
    175      case CacheIndex::READING:
    176      case CacheIndex::WRITING:
    177        if (!mDoNotSearchInUpdates) {
    178          entry = mIndex->mPendingUpdates.GetEntry(*mHash);
    179        }
    180        [[fallthrough]];
    181      case CacheIndex::BUILDING:
    182      case CacheIndex::UPDATING:
    183      case CacheIndex::READY:
    184        if (!entry && !mDoNotSearchInIndex) {
    185          entry = mIndex->mIndex.GetEntry(*mHash);
    186        }
    187        break;
    188      case CacheIndex::INITIAL:
    189      case CacheIndex::SHUTDOWN:
    190      default:
    191        MOZ_ASSERT(false, "Unexpected state!");
    192    }
    193 
    194    return entry;
    195  }
    196 
    197  const SHA1Sum::Hash* mHash;
    198  RefPtr<CacheIndex> mIndex;
    199  RefPtr<CacheIndexRecordWrapper> mOldRecord;
    200  bool mDoNotSearchInIndex{false};
    201  bool mDoNotSearchInUpdates{false};
    202  const StaticMutexAutoLock& mProofOfLock;
    203 };
    204 
    205 class FileOpenHelper final : public CacheFileIOListener {
    206 public:
    207  NS_DECL_THREADSAFE_ISUPPORTS
    208 
    209  explicit FileOpenHelper(CacheIndex* aIndex)
    210      : mIndex(aIndex), mCanceled(false) {}
    211 
    212  void Cancel() {
    213    CacheIndex::sLock.AssertCurrentThreadOwns();
    214    mCanceled = true;
    215  }
    216 
    217 private:
    218  virtual ~FileOpenHelper() = default;
    219 
    220  NS_IMETHOD OnFileOpened(CacheFileHandle* aHandle, nsresult aResult) override;
    221  NS_IMETHOD OnDataWritten(CacheFileHandle* aHandle, const char* aBuf,
    222                           nsresult aResult) override {
    223    MOZ_CRASH("FileOpenHelper::OnDataWritten should not be called!");
    224    return NS_ERROR_UNEXPECTED;
    225  }
    226  NS_IMETHOD OnDataRead(CacheFileHandle* aHandle, char* aBuf,
    227                        nsresult aResult) override {
    228    MOZ_CRASH("FileOpenHelper::OnDataRead should not be called!");
    229    return NS_ERROR_UNEXPECTED;
    230  }
    231  NS_IMETHOD OnFileDoomed(CacheFileHandle* aHandle, nsresult aResult) override {
    232    MOZ_CRASH("FileOpenHelper::OnFileDoomed should not be called!");
    233    return NS_ERROR_UNEXPECTED;
    234  }
    235  NS_IMETHOD OnEOFSet(CacheFileHandle* aHandle, nsresult aResult) override {
    236    MOZ_CRASH("FileOpenHelper::OnEOFSet should not be called!");
    237    return NS_ERROR_UNEXPECTED;
    238  }
    239  NS_IMETHOD OnFileRenamed(CacheFileHandle* aHandle,
    240                           nsresult aResult) override {
    241    MOZ_CRASH("FileOpenHelper::OnFileRenamed should not be called!");
    242    return NS_ERROR_UNEXPECTED;
    243  }
    244 
    245  RefPtr<CacheIndex> mIndex;
    246  bool mCanceled;
    247 };
    248 
    249 NS_IMETHODIMP FileOpenHelper::OnFileOpened(CacheFileHandle* aHandle,
    250                                           nsresult aResult) {
    251  StaticMutexAutoLock lock(CacheIndex::sLock);
    252 
    253  if (mCanceled) {
    254    if (aHandle) {
    255      CacheFileIOManager::DoomFile(aHandle, nullptr);
    256    }
    257 
    258    return NS_OK;
    259  }
    260 
    261  mIndex->OnFileOpenedInternal(this, aHandle, aResult, lock);
    262 
    263  return NS_OK;
    264 }
    265 
    266 NS_IMPL_ISUPPORTS(FileOpenHelper, CacheFileIOListener);
    267 
    268 StaticRefPtr<CacheIndex> CacheIndex::gInstance;
    269 StaticMutex CacheIndex::sLock;
    270 
    271 NS_IMPL_ADDREF(CacheIndex)
    272 NS_IMPL_RELEASE(CacheIndex)
    273 
    274 NS_INTERFACE_MAP_BEGIN(CacheIndex)
    275  NS_INTERFACE_MAP_ENTRY(mozilla::net::CacheFileIOListener)
    276  NS_INTERFACE_MAP_ENTRY(nsIRunnable)
    277 NS_INTERFACE_MAP_END
    278 
    279 CacheIndex::CacheIndex() {
    280  sLock.AssertCurrentThreadOwns();
    281  LOG(("CacheIndex::CacheIndex [this=%p]", this));
    282  MOZ_ASSERT(!gInstance, "multiple CacheIndex instances!");
    283 }
    284 
    285 CacheIndex::~CacheIndex() {
    286  sLock.AssertCurrentThreadOwns();
    287  LOG(("CacheIndex::~CacheIndex [this=%p]", this));
    288 
    289  ReleaseBuffer();
    290 }
    291 
    292 // static
    293 nsresult CacheIndex::Init(nsIFile* aCacheDirectory) {
    294  LOG(("CacheIndex::Init()"));
    295 
    296  MOZ_ASSERT(NS_IsMainThread());
    297 
    298  StaticMutexAutoLock lock(sLock);
    299 
    300  if (gInstance) {
    301    return NS_ERROR_ALREADY_INITIALIZED;
    302  }
    303 
    304  RefPtr<CacheIndex> idx = new CacheIndex();
    305 
    306  nsresult rv = idx->InitInternal(aCacheDirectory, lock);
    307  NS_ENSURE_SUCCESS(rv, rv);
    308 
    309  gInstance = std::move(idx);
    310  return NS_OK;
    311 }
    312 
    313 nsresult CacheIndex::InitInternal(nsIFile* aCacheDirectory,
    314                                  const StaticMutexAutoLock& aProofOfLock) {
    315  nsresult rv;
    316  sLock.AssertCurrentThreadOwns();
    317 
    318  rv = aCacheDirectory->Clone(getter_AddRefs(mCacheDirectory));
    319  NS_ENSURE_SUCCESS(rv, rv);
    320 
    321  mStartTime = TimeStamp::NowLoRes();
    322 
    323  ReadIndexFromDisk(aProofOfLock);
    324 
    325  return NS_OK;
    326 }
    327 
    328 // static
    329 nsresult CacheIndex::PreShutdown() {
    330  MOZ_ASSERT(NS_IsMainThread());
    331 
    332  StaticMutexAutoLock lock(sLock);
    333 
    334  LOG(("CacheIndex::PreShutdown() [gInstance=%p]", gInstance.get()));
    335 
    336  nsresult rv;
    337  RefPtr<CacheIndex> index = gInstance;
    338 
    339  if (!index) {
    340    return NS_ERROR_NOT_INITIALIZED;
    341  }
    342 
    343  LOG(
    344      ("CacheIndex::PreShutdown() - [state=%d, indexOnDiskIsValid=%d, "
    345       "dontMarkIndexClean=%d]",
    346       index->mState, index->mIndexOnDiskIsValid, index->mDontMarkIndexClean));
    347 
    348  LOG(("CacheIndex::PreShutdown() - Closing iterators."));
    349  for (uint32_t i = 0; i < index->mIterators.Length();) {
    350    rv = index->mIterators[i]->CloseInternal(NS_ERROR_FAILURE);
    351    if (NS_FAILED(rv)) {
    352      // CacheIndexIterator::CloseInternal() removes itself from mIteratos iff
    353      // it returns success.
    354      LOG(
    355          ("CacheIndex::PreShutdown() - Failed to remove iterator %p. "
    356           "[rv=0x%08" PRIx32 "]",
    357           index->mIterators[i], static_cast<uint32_t>(rv)));
    358      i++;
    359    }
    360  }
    361 
    362  index->mShuttingDown = true;
    363 
    364  if (index->mState == READY) {
    365    return NS_OK;  // nothing to do
    366  }
    367 
    368  nsCOMPtr<nsIRunnable> event;
    369  event = NewRunnableMethod("net::CacheIndex::PreShutdownInternal", index,
    370                            &CacheIndex::PreShutdownInternal);
    371 
    372  nsCOMPtr<nsIEventTarget> ioTarget = CacheFileIOManager::IOTarget();
    373  MOZ_ASSERT(ioTarget);
    374 
    375  // PreShutdownInternal() will be executed before any queued event on INDEX
    376  // level. That's OK since we don't want to wait for any operation in progess.
    377  rv = ioTarget->Dispatch(event, nsIEventTarget::DISPATCH_NORMAL);
    378  if (NS_FAILED(rv)) {
    379    NS_WARNING("CacheIndex::PreShutdown() - Can't dispatch event");
    380    LOG(("CacheIndex::PreShutdown() - Can't dispatch event"));
    381    return rv;
    382  }
    383 
    384  return NS_OK;
    385 }
    386 
    387 void CacheIndex::PreShutdownInternal() {
    388  StaticMutexAutoLock lock(sLock);
    389 
    390  LOG(
    391      ("CacheIndex::PreShutdownInternal() - [state=%d, indexOnDiskIsValid=%d, "
    392       "dontMarkIndexClean=%d]",
    393       mState, mIndexOnDiskIsValid, mDontMarkIndexClean));
    394 
    395  MOZ_ASSERT(mShuttingDown);
    396 
    397  if (mUpdateTimer) {
    398    mUpdateTimer->Cancel();
    399    mUpdateTimer = nullptr;
    400  }
    401 
    402  switch (mState) {
    403    case WRITING:
    404      FinishWrite(false, lock);
    405      break;
    406    case READY:
    407      // nothing to do, write the journal in Shutdown()
    408      break;
    409    case READING:
    410      FinishRead(false, lock);
    411      break;
    412    case BUILDING:
    413    case UPDATING:
    414      FinishUpdate(false, lock);
    415      break;
    416    default:
    417      MOZ_ASSERT(false, "Implement me!");
    418  }
    419 
    420  // We should end up in READY state
    421  MOZ_ASSERT(mState == READY);
    422 }
    423 
    424 // static
    425 nsresult CacheIndex::Shutdown() {
    426  MOZ_ASSERT(NS_IsMainThread());
    427 
    428  StaticMutexAutoLock lock(sLock);
    429 
    430  LOG(("CacheIndex::Shutdown() [gInstance=%p]", gInstance.get()));
    431 
    432  RefPtr<CacheIndex> index = gInstance.forget();
    433 
    434  if (!index) {
    435    return NS_ERROR_NOT_INITIALIZED;
    436  }
    437 
    438  bool sanitize = CacheObserver::ClearCacheOnShutdown();
    439 
    440  LOG(
    441      ("CacheIndex::Shutdown() - [state=%d, indexOnDiskIsValid=%d, "
    442       "dontMarkIndexClean=%d, sanitize=%d]",
    443       index->mState, index->mIndexOnDiskIsValid, index->mDontMarkIndexClean,
    444       sanitize));
    445 
    446  MOZ_ASSERT(index->mShuttingDown);
    447 
    448  EState oldState = index->mState;
    449  index->ChangeState(SHUTDOWN, lock);
    450 
    451  if (oldState != READY) {
    452    LOG(
    453        ("CacheIndex::Shutdown() - Unexpected state. Did posting of "
    454         "PreShutdownInternal() fail?"));
    455  }
    456 
    457  switch (oldState) {
    458    case WRITING:
    459      index->FinishWrite(false, lock);
    460      [[fallthrough]];
    461    case READY:
    462      if (index->mIndexOnDiskIsValid && !index->mDontMarkIndexClean) {
    463        if (!sanitize && NS_FAILED(index->WriteLogToDisk())) {
    464          index->RemoveJournalAndTempFile();
    465        }
    466      } else {
    467        index->RemoveJournalAndTempFile();
    468      }
    469      break;
    470    case READING:
    471      index->FinishRead(false, lock);
    472      break;
    473    case BUILDING:
    474    case UPDATING:
    475      index->FinishUpdate(false, lock);
    476      break;
    477    default:
    478      MOZ_ASSERT(false, "Unexpected state!");
    479  }
    480 
    481  if (sanitize) {
    482    index->RemoveAllIndexFiles();
    483  }
    484 
    485  return NS_OK;
    486 }
    487 
    488 // static
    489 nsresult CacheIndex::AddEntry(const SHA1Sum::Hash* aHash) {
    490  LOG(("CacheIndex::AddEntry() [hash=%08x%08x%08x%08x%08x]", LOGSHA1(aHash)));
    491 
    492  MOZ_ASSERT(CacheFileIOManager::IsOnIOThread());
    493 
    494  StaticMutexAutoLock lock(sLock);
    495 
    496  RefPtr<CacheIndex> index = gInstance;
    497 
    498  if (!index) {
    499    return NS_ERROR_NOT_INITIALIZED;
    500  }
    501 
    502  if (!index->IsIndexUsable()) {
    503    return NS_ERROR_NOT_AVAILABLE;
    504  }
    505 
    506  // Getters in CacheIndexStats assert when mStateLogged is true since the
    507  // information is incomplete between calls to BeforeChange() and AfterChange()
    508  // (i.e. while CacheIndexEntryAutoManage exists). We need to check whether
    509  // non-fresh entries exists outside the scope of CacheIndexEntryAutoManage.
    510  bool updateIfNonFreshEntriesExist = false;
    511 
    512  {
    513    CacheIndexEntryAutoManage entryMng(aHash, index, lock);
    514 
    515    CacheIndexEntry* entry = index->mIndex.GetEntry(*aHash);
    516    bool entryRemoved = entry && entry->IsRemoved();
    517    CacheIndexEntryUpdate* updated = nullptr;
    518 
    519    if (index->mState == READY || index->mState == UPDATING ||
    520        index->mState == BUILDING) {
    521      MOZ_ASSERT(index->mPendingUpdates.Count() == 0);
    522 
    523      if (entry && !entryRemoved) {
    524        // Found entry in index that shouldn't exist.
    525 
    526        if (entry->IsFresh()) {
    527          // Someone removed the file on disk while FF is running. Update
    528          // process can fix only non-fresh entries (i.e. entries that were not
    529          // added within this session). Start update only if we have such
    530          // entries.
    531          //
    532          // TODO: This should be very rare problem. If it turns out not to be
    533          // true, change the update process so that it also iterates all
    534          // initialized non-empty entries and checks whether the file exists.
    535 
    536          LOG(
    537              ("CacheIndex::AddEntry() - Cache file was removed outside FF "
    538               "process!"));
    539 
    540          updateIfNonFreshEntriesExist = true;
    541        } else if (index->mState == READY) {
    542          // Index is outdated, update it.
    543          LOG(
    544              ("CacheIndex::AddEntry() - Found entry that shouldn't exist, "
    545               "update is needed"));
    546          index->mIndexNeedsUpdate = true;
    547        } else {
    548          // We cannot be here when building index since all entries are fresh
    549          // during building.
    550          MOZ_ASSERT(index->mState == UPDATING);
    551        }
    552      }
    553 
    554      if (!entry) {
    555        entry = index->mIndex.PutEntry(*aHash);
    556      }
    557    } else {  // WRITING, READING
    558      updated = index->mPendingUpdates.GetEntry(*aHash);
    559      bool updatedRemoved = updated && updated->IsRemoved();
    560 
    561      if ((updated && !updatedRemoved) ||
    562          (!updated && entry && !entryRemoved && entry->IsFresh())) {
    563        // Fresh entry found, so the file was removed outside FF
    564        LOG(
    565            ("CacheIndex::AddEntry() - Cache file was removed outside FF "
    566             "process!"));
    567 
    568        updateIfNonFreshEntriesExist = true;
    569      } else if (!updated && entry && !entryRemoved) {
    570        if (index->mState == WRITING) {
    571          LOG(
    572              ("CacheIndex::AddEntry() - Found entry that shouldn't exist, "
    573               "update is needed"));
    574          index->mIndexNeedsUpdate = true;
    575        }
    576        // Ignore if state is READING since the index information is partial
    577      }
    578 
    579      updated = index->mPendingUpdates.PutEntry(*aHash);
    580    }
    581 
    582    if (updated) {
    583      updated->InitNew();
    584      updated->MarkDirty();
    585      updated->MarkFresh();
    586    } else {
    587      entry->InitNew();
    588      entry->MarkDirty();
    589      entry->MarkFresh();
    590    }
    591  }
    592 
    593  if (updateIfNonFreshEntriesExist &&
    594      index->mIndexStats.Count() != index->mIndexStats.Fresh()) {
    595    index->mIndexNeedsUpdate = true;
    596  }
    597 
    598  index->StartUpdatingIndexIfNeeded(lock);
    599  index->WriteIndexToDiskIfNeeded(lock);
    600 
    601  return NS_OK;
    602 }
    603 
    604 // static
    605 nsresult CacheIndex::EnsureEntryExists(const SHA1Sum::Hash* aHash) {
    606  LOG(("CacheIndex::EnsureEntryExists() [hash=%08x%08x%08x%08x%08x]",
    607       LOGSHA1(aHash)));
    608 
    609  MOZ_ASSERT(CacheFileIOManager::IsOnIOThread());
    610 
    611  StaticMutexAutoLock lock(sLock);
    612 
    613  RefPtr<CacheIndex> index = gInstance;
    614 
    615  if (!index) {
    616    return NS_ERROR_NOT_INITIALIZED;
    617  }
    618 
    619  if (!index->IsIndexUsable()) {
    620    return NS_ERROR_NOT_AVAILABLE;
    621  }
    622 
    623  {
    624    CacheIndexEntryAutoManage entryMng(aHash, index, lock);
    625 
    626    CacheIndexEntry* entry = index->mIndex.GetEntry(*aHash);
    627    bool entryRemoved = entry && entry->IsRemoved();
    628 
    629    if (index->mState == READY || index->mState == UPDATING ||
    630        index->mState == BUILDING) {
    631      MOZ_ASSERT(index->mPendingUpdates.Count() == 0);
    632 
    633      if (!entry || entryRemoved) {
    634        if (entryRemoved && entry->IsFresh()) {
    635          // This could happen only if somebody copies files to the entries
    636          // directory while FF is running.
    637          LOG(
    638              ("CacheIndex::EnsureEntryExists() - Cache file was added outside "
    639               "FF process! Update is needed."));
    640          index->mIndexNeedsUpdate = true;
    641        } else if (index->mState == READY ||
    642                   (entryRemoved && !entry->IsFresh())) {
    643          // Removed non-fresh entries can be present as a result of
    644          // MergeJournal()
    645          LOG(
    646              ("CacheIndex::EnsureEntryExists() - Didn't find entry that should"
    647               " exist, update is needed"));
    648          index->mIndexNeedsUpdate = true;
    649        }
    650 
    651        if (!entry) {
    652          entry = index->mIndex.PutEntry(*aHash);
    653        }
    654        entry->InitNew();
    655        entry->MarkDirty();
    656      }
    657      entry->MarkFresh();
    658    } else {  // WRITING, READING
    659      CacheIndexEntryUpdate* updated = index->mPendingUpdates.GetEntry(*aHash);
    660      bool updatedRemoved = updated && updated->IsRemoved();
    661 
    662      if (updatedRemoved || (!updated && entryRemoved && entry->IsFresh())) {
    663        // Fresh information about missing entry found. This could happen only
    664        // if somebody copies files to the entries directory while FF is
    665        // running.
    666        LOG(
    667            ("CacheIndex::EnsureEntryExists() - Cache file was added outside "
    668             "FF process! Update is needed."));
    669        index->mIndexNeedsUpdate = true;
    670      } else if (!updated && (!entry || entryRemoved)) {
    671        if (index->mState == WRITING) {
    672          LOG(
    673              ("CacheIndex::EnsureEntryExists() - Didn't find entry that should"
    674               " exist, update is needed"));
    675          index->mIndexNeedsUpdate = true;
    676        }
    677        // Ignore if state is READING since the index information is partial
    678      }
    679 
    680      // We don't need entryRemoved and updatedRemoved info anymore
    681      if (entryRemoved) entry = nullptr;
    682      if (updatedRemoved) updated = nullptr;
    683 
    684      if (updated) {
    685        updated->MarkFresh();
    686      } else {
    687        if (!entry) {
    688          // Create a new entry
    689          updated = index->mPendingUpdates.PutEntry(*aHash);
    690          updated->InitNew();
    691          updated->MarkFresh();
    692          updated->MarkDirty();
    693        } else {
    694          if (!entry->IsFresh()) {
    695            // To mark the entry fresh we must make a copy of index entry
    696            // since the index is read-only.
    697            updated = index->mPendingUpdates.PutEntry(*aHash);
    698            *updated = *entry;
    699            updated->MarkFresh();
    700          }
    701        }
    702      }
    703    }
    704  }
    705 
    706  index->StartUpdatingIndexIfNeeded(lock);
    707  index->WriteIndexToDiskIfNeeded(lock);
    708 
    709  return NS_OK;
    710 }
    711 
    712 // static
    713 nsresult CacheIndex::InitEntry(const SHA1Sum::Hash* aHash,
    714                               OriginAttrsHash aOriginAttrsHash,
    715                               bool aAnonymous, bool aPinned) {
    716  LOG(
    717      ("CacheIndex::InitEntry() [hash=%08x%08x%08x%08x%08x, "
    718       "originAttrsHash=%" PRIx64 ", anonymous=%d, pinned=%d]",
    719       LOGSHA1(aHash), aOriginAttrsHash, aAnonymous, aPinned));
    720 
    721  MOZ_ASSERT(CacheFileIOManager::IsOnIOThread());
    722 
    723  StaticMutexAutoLock lock(sLock);
    724 
    725  RefPtr<CacheIndex> index = gInstance;
    726 
    727  if (!index) {
    728    return NS_ERROR_NOT_INITIALIZED;
    729  }
    730 
    731  if (!index->IsIndexUsable()) {
    732    return NS_ERROR_NOT_AVAILABLE;
    733  }
    734 
    735  {
    736    CacheIndexEntryAutoManage entryMng(aHash, index, lock);
    737 
    738    CacheIndexEntry* entry = index->mIndex.GetEntry(*aHash);
    739    CacheIndexEntryUpdate* updated = nullptr;
    740    bool reinitEntry = false;
    741 
    742    if (entry && entry->IsRemoved()) {
    743      entry = nullptr;
    744    }
    745 
    746    if (index->mState == READY || index->mState == UPDATING ||
    747        index->mState == BUILDING) {
    748      MOZ_ASSERT(index->mPendingUpdates.Count() == 0);
    749      MOZ_ASSERT(entry);
    750      MOZ_ASSERT(entry->IsFresh());
    751 
    752      if (!entry) {
    753        LOG(("CacheIndex::InitEntry() - Entry was not found in mIndex!"));
    754        NS_WARNING(
    755            ("CacheIndex::InitEntry() - Entry was not found in mIndex!"));
    756        return NS_ERROR_UNEXPECTED;
    757      }
    758 
    759      if (IsCollision(entry, aOriginAttrsHash, aAnonymous)) {
    760        index->mIndexNeedsUpdate =
    761            true;  // TODO Does this really help in case of collision?
    762        reinitEntry = true;
    763      } else {
    764        if (entry->IsInitialized()) {
    765          return NS_OK;
    766        }
    767      }
    768    } else {
    769      updated = index->mPendingUpdates.GetEntry(*aHash);
    770      DebugOnly<bool> removed = updated && updated->IsRemoved();
    771 
    772      MOZ_ASSERT(updated || !removed);
    773      MOZ_ASSERT(updated || entry);
    774 
    775      if (!updated && !entry) {
    776        LOG(
    777            ("CacheIndex::InitEntry() - Entry was found neither in mIndex nor "
    778             "in mPendingUpdates!"));
    779        NS_WARNING(
    780            ("CacheIndex::InitEntry() - Entry was found neither in "
    781             "mIndex nor in mPendingUpdates!"));
    782        return NS_ERROR_UNEXPECTED;
    783      }
    784 
    785      if (updated) {
    786        MOZ_ASSERT(updated->IsFresh());
    787 
    788        if (IsCollision(updated, aOriginAttrsHash, aAnonymous)) {
    789          index->mIndexNeedsUpdate = true;
    790          reinitEntry = true;
    791        } else {
    792          if (updated->IsInitialized()) {
    793            return NS_OK;
    794          }
    795        }
    796      } else {
    797        MOZ_ASSERT(entry->IsFresh());
    798 
    799        if (IsCollision(entry, aOriginAttrsHash, aAnonymous)) {
    800          index->mIndexNeedsUpdate = true;
    801          reinitEntry = true;
    802        } else {
    803          if (entry->IsInitialized()) {
    804            return NS_OK;
    805          }
    806        }
    807 
    808        // make a copy of a read-only entry
    809        updated = index->mPendingUpdates.PutEntry(*aHash);
    810        *updated = *entry;
    811      }
    812    }
    813 
    814    if (reinitEntry) {
    815      // There is a collision and we are going to rewrite this entry. Initialize
    816      // it as a new entry.
    817      if (updated) {
    818        updated->InitNew();
    819        updated->MarkFresh();
    820      } else {
    821        entry->InitNew();
    822        entry->MarkFresh();
    823      }
    824    }
    825 
    826    if (updated) {
    827      updated->Init(aOriginAttrsHash, aAnonymous, aPinned);
    828      updated->MarkDirty();
    829    } else {
    830      entry->Init(aOriginAttrsHash, aAnonymous, aPinned);
    831      entry->MarkDirty();
    832    }
    833  }
    834 
    835  index->StartUpdatingIndexIfNeeded(lock);
    836  index->WriteIndexToDiskIfNeeded(lock);
    837 
    838  return NS_OK;
    839 }
    840 
    841 // static
    842 nsresult CacheIndex::RemoveEntry(const SHA1Sum::Hash* aHash,
    843                                 const nsACString& aKey,
    844                                 bool aClearDictionary) {
    845  LOG(
    846      ("CacheIndex::RemoveEntry() [hash=%08x%08x%08x%08x%08x] key=%s "
    847       "clear_dictionary=%d",
    848       LOGSHA1(aHash), PromiseFlatCString(aKey).get(), aClearDictionary));
    849 
    850  MOZ_ASSERT(CacheFileIOManager::IsOnIOThread());
    851 
    852  // Remove any dictionary associated with this entry even if we later
    853  // error out - async since removal happens on MainThread.
    854 
    855  // TODO XXX There may be a hole here where a dictionary entry can get
    856  // referenced for a request before RemoveDictionaryFor can run, but after
    857  // the entry is removed here.
    858 
    859  // Note: we don't want to (re)clear dictionaries when the
    860  // CacheFileContextEvictor purges entries; they've already been cleared
    861  // via CacheIndex::EvictByContext synchronously
    862  if (aClearDictionary) {
    863    DictionaryCache::RemoveDictionaryFor(aKey);
    864  }
    865 
    866  StaticMutexAutoLock lock(sLock);
    867 
    868  RefPtr<CacheIndex> index = gInstance;
    869 
    870  if (!index) {
    871    return NS_ERROR_NOT_INITIALIZED;
    872  }
    873 
    874  if (!index->IsIndexUsable()) {
    875    return NS_ERROR_NOT_AVAILABLE;
    876  }
    877 
    878  {
    879    CacheIndexEntryAutoManage entryMng(aHash, index, lock);
    880 
    881    CacheIndexEntry* entry = index->mIndex.GetEntry(*aHash);
    882    bool entryRemoved = entry && entry->IsRemoved();
    883 
    884    if (index->mState == READY || index->mState == UPDATING ||
    885        index->mState == BUILDING) {
    886      MOZ_ASSERT(index->mPendingUpdates.Count() == 0);
    887 
    888      if (!entry || entryRemoved) {
    889        if (entryRemoved && entry->IsFresh()) {
    890          // This could happen only if somebody copies files to the entries
    891          // directory while FF is running.
    892          LOG(
    893              ("CacheIndex::RemoveEntry() - Cache file was added outside FF "
    894               "process! Update is needed."));
    895          index->mIndexNeedsUpdate = true;
    896        } else if (index->mState == READY ||
    897                   (entryRemoved && !entry->IsFresh())) {
    898          // Removed non-fresh entries can be present as a result of
    899          // MergeJournal()
    900          LOG(
    901              ("CacheIndex::RemoveEntry() - Didn't find entry that should exist"
    902               ", update is needed"));
    903          index->mIndexNeedsUpdate = true;
    904        }
    905      } else {
    906        if (entry) {
    907          if (!entry->IsDirty() && entry->IsFileEmpty()) {
    908            index->mIndex.RemoveEntry(entry);
    909            entry = nullptr;
    910          } else {
    911            entry->MarkRemoved();
    912            entry->MarkDirty();
    913            entry->MarkFresh();
    914          }
    915        }
    916      }
    917    } else {  // WRITING, READING
    918      CacheIndexEntryUpdate* updated = index->mPendingUpdates.GetEntry(*aHash);
    919      bool updatedRemoved = updated && updated->IsRemoved();
    920 
    921      if (updatedRemoved || (!updated && entryRemoved && entry->IsFresh())) {
    922        // Fresh information about missing entry found. This could happen only
    923        // if somebody copies files to the entries directory while FF is
    924        // running.
    925        LOG(
    926            ("CacheIndex::RemoveEntry() - Cache file was added outside FF "
    927             "process! Update is needed."));
    928        index->mIndexNeedsUpdate = true;
    929      } else if (!updated && (!entry || entryRemoved)) {
    930        if (index->mState == WRITING) {
    931          LOG(
    932              ("CacheIndex::RemoveEntry() - Didn't find entry that should exist"
    933               ", update is needed"));
    934          index->mIndexNeedsUpdate = true;
    935        }
    936        // Ignore if state is READING since the index information is partial
    937      }
    938 
    939      if (!updated) {
    940        updated = index->mPendingUpdates.PutEntry(*aHash);
    941        updated->InitNew();
    942      }
    943 
    944      updated->MarkRemoved();
    945      updated->MarkDirty();
    946      updated->MarkFresh();
    947    }
    948  }
    949  index->StartUpdatingIndexIfNeeded(lock);
    950  index->WriteIndexToDiskIfNeeded(lock);
    951 
    952  return NS_OK;
    953 }
    954 
    955 // static
    956 nsresult CacheIndex::UpdateEntry(const SHA1Sum::Hash* aHash,
    957                                 const uint32_t* aFrecency,
    958                                 const bool* aHasAltData,
    959                                 const uint16_t* aOnStartTime,
    960                                 const uint16_t* aOnStopTime,
    961                                 const uint8_t* aContentType,
    962                                 const uint32_t* aSize) {
    963  LOG(
    964      ("CacheIndex::UpdateEntry() [hash=%08x%08x%08x%08x%08x, "
    965       "frecency=%s, hasAltData=%s, onStartTime=%s, onStopTime=%s, "
    966       "contentType=%s, size=%s]",
    967       LOGSHA1(aHash), aFrecency ? nsPrintfCString("%u", *aFrecency).get() : "",
    968       aHasAltData ? (*aHasAltData ? "true" : "false") : "",
    969       aOnStartTime ? nsPrintfCString("%u", *aOnStartTime).get() : "",
    970       aOnStopTime ? nsPrintfCString("%u", *aOnStopTime).get() : "",
    971       aContentType ? nsPrintfCString("%u", *aContentType).get() : "",
    972       aSize ? nsPrintfCString("%u", *aSize).get() : ""));
    973 
    974  MOZ_ASSERT(CacheFileIOManager::IsOnIOThread());
    975 
    976  StaticMutexAutoLock lock(sLock);
    977 
    978  RefPtr<CacheIndex> index = gInstance;
    979 
    980  if (!index) {
    981    return NS_ERROR_NOT_INITIALIZED;
    982  }
    983 
    984  if (!index->IsIndexUsable()) {
    985    return NS_ERROR_NOT_AVAILABLE;
    986  }
    987 
    988  {
    989    CacheIndexEntryAutoManage entryMng(aHash, index, lock);
    990 
    991    CacheIndexEntry* entry = index->mIndex.GetEntry(*aHash);
    992 
    993    if (entry && entry->IsRemoved()) {
    994      entry = nullptr;
    995    }
    996 
    997    if (index->mState == READY || index->mState == UPDATING ||
    998        index->mState == BUILDING) {
    999      MOZ_ASSERT(index->mPendingUpdates.Count() == 0);
   1000      MOZ_ASSERT(entry);
   1001 
   1002      if (!entry) {
   1003        LOG(("CacheIndex::UpdateEntry() - Entry was not found in mIndex!"));
   1004        NS_WARNING(
   1005            ("CacheIndex::UpdateEntry() - Entry was not found in mIndex!"));
   1006        return NS_ERROR_UNEXPECTED;
   1007      }
   1008 
   1009      if (!HasEntryChanged(entry, aFrecency, aHasAltData, aOnStartTime,
   1010                           aOnStopTime, aContentType, aSize)) {
   1011        return NS_OK;
   1012      }
   1013 
   1014      MOZ_ASSERT(entry->IsFresh());
   1015      MOZ_ASSERT(entry->IsInitialized());
   1016      entry->MarkDirty();
   1017 
   1018      if (aFrecency) {
   1019        entry->SetFrecency(*aFrecency);
   1020      }
   1021 
   1022      if (aHasAltData) {
   1023        entry->SetHasAltData(*aHasAltData);
   1024      }
   1025 
   1026      if (aOnStartTime) {
   1027        entry->SetOnStartTime(*aOnStartTime);
   1028      }
   1029 
   1030      if (aOnStopTime) {
   1031        entry->SetOnStopTime(*aOnStopTime);
   1032      }
   1033 
   1034      if (aContentType) {
   1035        entry->SetContentType(*aContentType);
   1036      }
   1037 
   1038      if (aSize) {
   1039        entry->SetFileSize(*aSize);
   1040      }
   1041    } else {
   1042      CacheIndexEntryUpdate* updated = index->mPendingUpdates.GetEntry(*aHash);
   1043      DebugOnly<bool> removed = updated && updated->IsRemoved();
   1044 
   1045      MOZ_ASSERT(updated || !removed);
   1046      MOZ_ASSERT(updated || entry);
   1047 
   1048      if (!updated) {
   1049        if (!entry) {
   1050          LOG(
   1051              ("CacheIndex::UpdateEntry() - Entry was found neither in mIndex "
   1052               "nor in mPendingUpdates!"));
   1053          NS_WARNING(
   1054              ("CacheIndex::UpdateEntry() - Entry was found neither in "
   1055               "mIndex nor in mPendingUpdates!"));
   1056          return NS_ERROR_UNEXPECTED;
   1057        }
   1058 
   1059        // make a copy of a read-only entry
   1060        updated = index->mPendingUpdates.PutEntry(*aHash);
   1061        *updated = *entry;
   1062      }
   1063 
   1064      MOZ_ASSERT(updated->IsFresh());
   1065      MOZ_ASSERT(updated->IsInitialized());
   1066      updated->MarkDirty();
   1067 
   1068      if (aFrecency) {
   1069        updated->SetFrecency(*aFrecency);
   1070      }
   1071 
   1072      if (aHasAltData) {
   1073        updated->SetHasAltData(*aHasAltData);
   1074      }
   1075 
   1076      if (aOnStartTime) {
   1077        updated->SetOnStartTime(*aOnStartTime);
   1078      }
   1079 
   1080      if (aOnStopTime) {
   1081        updated->SetOnStopTime(*aOnStopTime);
   1082      }
   1083 
   1084      if (aContentType) {
   1085        updated->SetContentType(*aContentType);
   1086      }
   1087 
   1088      if (aSize) {
   1089        updated->SetFileSize(*aSize);
   1090      }
   1091    }
   1092  }
   1093 
   1094  index->WriteIndexToDiskIfNeeded(lock);
   1095 
   1096  return NS_OK;
   1097 }
   1098 
   1099 // Clear the entries from the Index immediately, to comply with
   1100 // https://www.w3.org/TR/clear-site-data/#fetch-integration
   1101 // Note that we will effectively hide the entries until the actual evict
   1102 // happens.
   1103 
   1104 // aOrigin == "" means clear all unless aBaseDomain is set to something
   1105 // static
   1106 void CacheIndex::EvictByContext(const nsAString& aOrigin,
   1107                                const nsAString& aBaseDomain) {
   1108  StaticMutexAutoLock lock(sLock);
   1109 
   1110  RefPtr<CacheIndex> index = gInstance;
   1111 
   1112  // Store in hashset that this origin has been evicted; we'll remove it
   1113  // when CacheFileIOManager::EvictByContextInternal() finishes.
   1114  // Not valid to set both aOrigin and aBaseDomain
   1115  if (!aOrigin.IsEmpty() && aBaseDomain.IsEmpty()) {
   1116    // likely CacheStorageService::ClearByPrincipal
   1117    nsCOMPtr<nsIURI> uri;
   1118    if (NS_SUCCEEDED(NS_NewURI(getter_AddRefs(uri), aOrigin))) {
   1119      // Remove the dictionary entries for this origin immediately
   1120      DictionaryCache::RemoveDictionariesForOrigin(uri);
   1121    }
   1122  }
   1123 }
   1124 
   1125 // static
   1126 nsresult CacheIndex::RemoveAll() {
   1127  LOG(("CacheIndex::RemoveAll()"));
   1128 
   1129  MOZ_ASSERT(CacheFileIOManager::IsOnIOThread());
   1130 
   1131  nsCOMPtr<nsIFile> file;
   1132 
   1133  {
   1134    StaticMutexAutoLock lock(sLock);
   1135 
   1136    RefPtr<CacheIndex> index = gInstance;
   1137 
   1138    if (!index) {
   1139      return NS_ERROR_NOT_INITIALIZED;
   1140    }
   1141 
   1142    MOZ_ASSERT(!index->mRemovingAll);
   1143 
   1144    if (!index->IsIndexUsable()) {
   1145      return NS_ERROR_NOT_AVAILABLE;
   1146    }
   1147 
   1148    AutoRestore<bool> saveRemovingAll(index->mRemovingAll);
   1149    index->mRemovingAll = true;
   1150 
   1151    // Doom index and journal handles but don't null them out since this will be
   1152    // done in FinishWrite/FinishRead methods.
   1153    if (index->mIndexHandle) {
   1154      CacheFileIOManager::DoomFile(index->mIndexHandle, nullptr);
   1155    } else {
   1156      // We don't have a handle to index file, so get the file here, but delete
   1157      // it outside the lock. Ignore the result since this is not fatal.
   1158      index->GetFile(nsLiteralCString(INDEX_NAME), getter_AddRefs(file));
   1159    }
   1160 
   1161    if (index->mJournalHandle) {
   1162      CacheFileIOManager::DoomFile(index->mJournalHandle, nullptr);
   1163    }
   1164 
   1165    switch (index->mState) {
   1166      case WRITING:
   1167        index->FinishWrite(false, lock);
   1168        break;
   1169      case READY:
   1170        // nothing to do
   1171        break;
   1172      case READING:
   1173        index->FinishRead(false, lock);
   1174        break;
   1175      case BUILDING:
   1176      case UPDATING:
   1177        index->FinishUpdate(false, lock);
   1178        break;
   1179      default:
   1180        MOZ_ASSERT(false, "Unexpected state!");
   1181    }
   1182 
   1183    // We should end up in READY state
   1184    MOZ_ASSERT(index->mState == READY);
   1185 
   1186    // There should not be any handle
   1187    MOZ_ASSERT(!index->mIndexHandle);
   1188    MOZ_ASSERT(!index->mJournalHandle);
   1189 
   1190    index->mIndexOnDiskIsValid = false;
   1191    index->mIndexNeedsUpdate = false;
   1192 
   1193    index->mIndexStats.Clear();
   1194    index->mFrecencyStorage.Clear(lock);
   1195    index->mIndex.Clear();
   1196 
   1197    for (uint32_t i = 0; i < index->mIterators.Length();) {
   1198      nsresult rv = index->mIterators[i]->CloseInternal(NS_ERROR_NOT_AVAILABLE);
   1199      if (NS_FAILED(rv)) {
   1200        // CacheIndexIterator::CloseInternal() removes itself from mIterators
   1201        // iff it returns success.
   1202        LOG(
   1203            ("CacheIndex::RemoveAll() - Failed to remove iterator %p. "
   1204             "[rv=0x%08" PRIx32 "]",
   1205             index->mIterators[i], static_cast<uint32_t>(rv)));
   1206        i++;
   1207      }
   1208    }
   1209  }
   1210 
   1211  if (file) {
   1212    // Ignore the result. The file might not exist and the failure is not fatal.
   1213    file->Remove(false);
   1214  }
   1215 
   1216  return NS_OK;
   1217 }
   1218 
   1219 // static
   1220 nsresult CacheIndex::HasEntry(
   1221    const nsACString& aKey, EntryStatus* _retval,
   1222    const std::function<void(const CacheIndexEntry*)>& aCB) {
   1223  LOG(("CacheIndex::HasEntry() [key=%s]", PromiseFlatCString(aKey).get()));
   1224 
   1225  SHA1Sum sum;
   1226  SHA1Sum::Hash hash;
   1227  sum.update(aKey.BeginReading(), aKey.Length());
   1228  sum.finish(hash);
   1229 
   1230  return HasEntry(hash, _retval, aCB);
   1231 }
   1232 
   1233 // static
   1234 nsresult CacheIndex::HasEntry(
   1235    const SHA1Sum::Hash& hash, EntryStatus* _retval,
   1236    const std::function<void(const CacheIndexEntry*)>& aCB) {
   1237  StaticMutexAutoLock lock(sLock);
   1238 
   1239  RefPtr<CacheIndex> index = gInstance;
   1240 
   1241  if (!index) {
   1242    return NS_ERROR_NOT_INITIALIZED;
   1243  }
   1244 
   1245  if (!index->IsIndexUsable()) {
   1246    return NS_ERROR_NOT_AVAILABLE;
   1247  }
   1248 
   1249  const CacheIndexEntry* entry = nullptr;
   1250 
   1251  switch (index->mState) {
   1252    case READING:
   1253    case WRITING:
   1254      entry = index->mPendingUpdates.GetEntry(hash);
   1255      [[fallthrough]];
   1256    case BUILDING:
   1257    case UPDATING:
   1258    case READY:
   1259      if (!entry) {
   1260        entry = index->mIndex.GetEntry(hash);
   1261      }
   1262      break;
   1263    case INITIAL:
   1264    case SHUTDOWN:
   1265      MOZ_ASSERT(false, "Unexpected state!");
   1266  }
   1267 
   1268  if (!entry) {
   1269    if (index->mState == READY || index->mState == WRITING) {
   1270      *_retval = DOES_NOT_EXIST;
   1271    } else {
   1272      *_retval = DO_NOT_KNOW;
   1273    }
   1274  } else {
   1275    if (entry->IsRemoved()) {
   1276      if (entry->IsFresh()) {
   1277        *_retval = DOES_NOT_EXIST;
   1278      } else {
   1279        *_retval = DO_NOT_KNOW;
   1280      }
   1281    } else {
   1282      *_retval = EXISTS;
   1283      if (aCB) {
   1284        aCB(entry);
   1285      }
   1286    }
   1287  }
   1288 
   1289  LOG(("CacheIndex::HasEntry() - result is %u", *_retval));
   1290  return NS_OK;
   1291 }
   1292 
   1293 // static
   1294 // GetEntryForEviction is used by OverLimitEvictionInternal where we create and
   1295 // keep our EvictionSortedSnapshot while looping.
   1296 nsresult CacheIndex::GetEntryForEviction(EvictionSortedSnapshot& aSnapshot,
   1297                                         bool aIgnoreEmptyEntries,
   1298                                         SHA1Sum::Hash* aHash, uint32_t* aCnt) {
   1299  LOG(("CacheIndex::GetEntryForEviction()"));
   1300 
   1301  MOZ_ASSERT(CacheFileIOManager::IsOnIOThread());
   1302 
   1303  StaticMutexAutoLock lock(sLock);
   1304 
   1305  RefPtr<CacheIndex> index = gInstance;
   1306 
   1307  if (!index) return NS_ERROR_NOT_INITIALIZED;
   1308 
   1309  if (!index->IsIndexUsable()) {
   1310    return NS_ERROR_NOT_AVAILABLE;
   1311  }
   1312 
   1313  if (index->mIndexStats.Size() == 0) {
   1314    return NS_ERROR_NOT_AVAILABLE;
   1315  }
   1316 
   1317  int32_t mediaUsage =
   1318      round(static_cast<double>(index->mIndexStats.SizeByType(
   1319                nsICacheEntry::CONTENT_TYPE_MEDIA)) *
   1320            100.0 / static_cast<double>(index->mIndexStats.Size()));
   1321  int32_t mediaUsageLimit =
   1322      StaticPrefs::browser_cache_disk_content_type_media_limit();
   1323  bool evictMedia = false;
   1324  if (mediaUsage > mediaUsageLimit) {
   1325    LOG(
   1326        ("CacheIndex::GetEntryForEviction() - media content type is over the "
   1327         "limit [mediaUsage=%d, mediaUsageLimit=%d]",
   1328         mediaUsage, mediaUsageLimit));
   1329    evictMedia = true;
   1330  }
   1331 
   1332  SHA1Sum::Hash hash;
   1333  CacheIndexRecord* foundRecord = nullptr;
   1334  uint32_t skipped = 0;
   1335  size_t recordPosition = 0;
   1336 
   1337  // find first non-forced valid and unpinned entry with the lowest frecency
   1338  for (size_t i = 0; i < aSnapshot.Length(); ++i) {
   1339    if (!aSnapshot[i]) {
   1340      continue;  // Skip the null records
   1341    }
   1342    CacheIndexRecord* rec = aSnapshot[i]->Get();
   1343    if (!rec) {
   1344      continue;  // Skip the null records
   1345    }
   1346 
   1347    memcpy(&hash, rec->mHash, sizeof(SHA1Sum::Hash));
   1348 
   1349    ++skipped;
   1350 
   1351    uint32_t type = CacheIndexEntry::GetContentType(rec);
   1352 
   1353    if (evictMedia && type != nsICacheEntry::CONTENT_TYPE_MEDIA) {
   1354      continue;
   1355    }
   1356 
   1357    if (type == nsICacheEntry::CONTENT_TYPE_DICTIONARY) {
   1358      // Let them be removed by becoming empty and removing themselves
   1359      continue;
   1360    }
   1361 
   1362    if (IsForcedValidEntry(&hash)) {
   1363      continue;
   1364    }
   1365 
   1366    if (CacheIndexEntry::IsPinned(rec)) {
   1367      continue;
   1368    }
   1369 
   1370    if (aIgnoreEmptyEntries && !CacheIndexEntry::GetFileSize(*rec)) {
   1371      continue;
   1372    }
   1373 
   1374    --skipped;
   1375    foundRecord = rec;
   1376    recordPosition = i;
   1377    break;
   1378  }
   1379 
   1380  if (!foundRecord) return NS_ERROR_NOT_AVAILABLE;
   1381 
   1382  *aCnt = skipped;
   1383 
   1384  LOG(
   1385      ("CacheIndex::GetEntryForEviction() - returning entry "
   1386       "[hash=%08x%08x%08x%08x%08x, cnt=%u, frecency=%u, contentType=%u]",
   1387       LOGSHA1(&hash), *aCnt, foundRecord->mFrecency,
   1388       CacheIndexEntry::GetContentType(foundRecord)));
   1389 
   1390  memcpy(aHash, &hash, sizeof(SHA1Sum::Hash));
   1391  aSnapshot[recordPosition] = nullptr;  // Remove the record from the snapshot
   1392 
   1393  return NS_OK;
   1394 }
   1395 
   1396 // static
   1397 bool CacheIndex::IsForcedValidEntry(const SHA1Sum::Hash* aHash) {
   1398  RefPtr<CacheFileHandle> handle;
   1399 
   1400  CacheFileIOManager::gInstance->mHandles.GetHandle(aHash,
   1401                                                    getter_AddRefs(handle));
   1402 
   1403  if (!handle) return false;
   1404 
   1405  nsCString hashKey = handle->Key();
   1406  return CacheStorageService::Self()->IsForcedValidEntry(hashKey);
   1407 }
   1408 
   1409 // static
   1410 nsresult CacheIndex::GetCacheSize(uint32_t* _retval) {
   1411  LOG(("CacheIndex::GetCacheSize()"));
   1412 
   1413  StaticMutexAutoLock lock(sLock);
   1414 
   1415  RefPtr<CacheIndex> index = gInstance;
   1416 
   1417  if (!index) return NS_ERROR_NOT_INITIALIZED;
   1418 
   1419  if (!index->IsIndexUsable()) {
   1420    return NS_ERROR_NOT_AVAILABLE;
   1421  }
   1422 
   1423  *_retval = index->mIndexStats.Size();
   1424  LOG(("CacheIndex::GetCacheSize() - returning %u", *_retval));
   1425  return NS_OK;
   1426 }
   1427 
   1428 // static
   1429 nsresult CacheIndex::GetEntryFileCount(uint32_t* _retval) {
   1430  LOG(("CacheIndex::GetEntryFileCount()"));
   1431 
   1432  StaticMutexAutoLock lock(sLock);
   1433 
   1434  RefPtr<CacheIndex> index = gInstance;
   1435 
   1436  if (!index) {
   1437    return NS_ERROR_NOT_INITIALIZED;
   1438  }
   1439 
   1440  if (!index->IsIndexUsable()) {
   1441    return NS_ERROR_NOT_AVAILABLE;
   1442  }
   1443 
   1444  *_retval = index->mIndexStats.ActiveEntriesCount();
   1445  LOG(("CacheIndex::GetEntryFileCount() - returning %u", *_retval));
   1446  return NS_OK;
   1447 }
   1448 
   1449 // static
   1450 nsresult CacheIndex::GetCacheStats(nsILoadContextInfo* aInfo, uint32_t* aSize,
   1451                                   uint32_t* aCount) {
   1452  LOG(("CacheIndex::GetCacheStats() [info=%p]", aInfo));
   1453 
   1454  StaticMutexAutoLock lock(sLock);
   1455 
   1456  RefPtr<CacheIndex> index = gInstance;
   1457 
   1458  if (!index) {
   1459    return NS_ERROR_NOT_INITIALIZED;
   1460  }
   1461 
   1462  if (!index->IsIndexUsable()) {
   1463    return NS_ERROR_NOT_AVAILABLE;
   1464  }
   1465 
   1466  *aSize = 0;
   1467  *aCount = 0;
   1468 
   1469  for (const auto& item : index->mFrecencyStorage.mRecs) {
   1470    if (aInfo &&
   1471        !CacheIndexEntry::RecordMatchesLoadContextInfo(item.GetKey(), aInfo)) {
   1472      continue;
   1473    }
   1474 
   1475    *aSize += CacheIndexEntry::GetFileSize(*(item.GetKey()->Get()));
   1476    ++*aCount;
   1477  }
   1478 
   1479  return NS_OK;
   1480 }
   1481 
   1482 // static
   1483 nsresult CacheIndex::AsyncGetDiskConsumption(
   1484    nsICacheStorageConsumptionObserver* aObserver) {
   1485  LOG(("CacheIndex::AsyncGetDiskConsumption()"));
   1486 
   1487  StaticMutexAutoLock lock(sLock);
   1488 
   1489  RefPtr<CacheIndex> index = gInstance;
   1490 
   1491  if (!index) {
   1492    return NS_ERROR_NOT_INITIALIZED;
   1493  }
   1494 
   1495  if (!index->IsIndexUsable()) {
   1496    return NS_ERROR_NOT_AVAILABLE;
   1497  }
   1498 
   1499  RefPtr<DiskConsumptionObserver> observer =
   1500      DiskConsumptionObserver::Init(aObserver);
   1501 
   1502  NS_ENSURE_ARG(observer);
   1503 
   1504  if ((index->mState == READY || index->mState == WRITING) &&
   1505      !index->mAsyncGetDiskConsumptionBlocked) {
   1506    LOG(("CacheIndex::AsyncGetDiskConsumption - calling immediately"));
   1507    // Safe to call the callback under the lock,
   1508    // we always post to the main thread.
   1509    observer->OnDiskConsumption(index->mIndexStats.Size() << 10);
   1510    return NS_OK;
   1511  }
   1512 
   1513  LOG(("CacheIndex::AsyncGetDiskConsumption - remembering callback"));
   1514  // Will be called when the index get to the READY state.
   1515  index->mDiskConsumptionObservers.AppendElement(observer);
   1516 
   1517  // Move forward with index re/building if it is pending
   1518  RefPtr<CacheIOThread> ioThread = CacheFileIOManager::IOThread();
   1519  if (ioThread) {
   1520    ioThread->Dispatch(
   1521        NS_NewRunnableFunction("net::CacheIndex::AsyncGetDiskConsumption",
   1522                               []() -> void {
   1523                                 StaticMutexAutoLock lock(sLock);
   1524 
   1525                                 RefPtr<CacheIndex> index = gInstance;
   1526                                 if (index && index->mUpdateTimer) {
   1527                                   index->mUpdateTimer->Cancel();
   1528                                   index->DelayedUpdateLocked(lock);
   1529                                 }
   1530                               }),
   1531        CacheIOThread::INDEX);
   1532  }
   1533 
   1534  return NS_OK;
   1535 }
   1536 
   1537 // static
   1538 nsresult CacheIndex::GetIterator(nsILoadContextInfo* aInfo, bool aAddNew,
   1539                                 CacheIndexIterator** _retval) {
   1540  LOG(("CacheIndex::GetIterator() [info=%p, addNew=%d]", aInfo, aAddNew));
   1541 
   1542  StaticMutexAutoLock lock(sLock);
   1543 
   1544  RefPtr<CacheIndex> index = gInstance;
   1545 
   1546  if (!index) {
   1547    return NS_ERROR_NOT_INITIALIZED;
   1548  }
   1549 
   1550  if (!index->IsIndexUsable()) {
   1551    return NS_ERROR_NOT_AVAILABLE;
   1552  }
   1553 
   1554  RefPtr<CacheIndexIterator> idxIter;
   1555  if (aInfo) {
   1556    idxIter = new CacheIndexContextIterator(index, aAddNew, aInfo);
   1557  } else {
   1558    idxIter = new CacheIndexIterator(index, aAddNew);
   1559  }
   1560  for (const auto& item : index->mFrecencyStorage.mRecs) {
   1561    idxIter->AddRecord(item.GetKey(), lock);
   1562  }
   1563 
   1564  index->mIterators.AppendElement(idxIter);
   1565  idxIter.swap(*_retval);
   1566  return NS_OK;
   1567 }
   1568 
   1569 // static
   1570 nsresult CacheIndex::IsUpToDate(bool* _retval) {
   1571  LOG(("CacheIndex::IsUpToDate()"));
   1572 
   1573  StaticMutexAutoLock lock(sLock);
   1574 
   1575  RefPtr<CacheIndex> index = gInstance;
   1576 
   1577  if (!index) {
   1578    return NS_ERROR_NOT_INITIALIZED;
   1579  }
   1580 
   1581  if (!index->IsIndexUsable()) {
   1582    return NS_ERROR_NOT_AVAILABLE;
   1583  }
   1584 
   1585  *_retval = (index->mState == READY || index->mState == WRITING) &&
   1586             !index->mIndexNeedsUpdate && !index->mShuttingDown;
   1587 
   1588  LOG(("CacheIndex::IsUpToDate() - returning %d", *_retval));
   1589  return NS_OK;
   1590 }
   1591 
   1592 bool CacheIndex::IsIndexUsable() {
   1593  MOZ_ASSERT(mState != INITIAL);
   1594 
   1595  switch (mState) {
   1596    case INITIAL:
   1597    case SHUTDOWN:
   1598      return false;
   1599 
   1600    case READING:
   1601    case WRITING:
   1602    case BUILDING:
   1603    case UPDATING:
   1604    case READY:
   1605      break;
   1606  }
   1607 
   1608  return true;
   1609 }
   1610 
   1611 // static
   1612 bool CacheIndex::IsCollision(CacheIndexEntry* aEntry,
   1613                             OriginAttrsHash aOriginAttrsHash,
   1614                             bool aAnonymous) {
   1615  if (!aEntry->IsInitialized()) {
   1616    return false;
   1617  }
   1618 
   1619  if (aEntry->Anonymous() != aAnonymous ||
   1620      aEntry->OriginAttrsHash() != aOriginAttrsHash) {
   1621    LOG(
   1622        ("CacheIndex::IsCollision() - Collision detected for entry hash=%08x"
   1623         "%08x%08x%08x%08x, expected values: originAttrsHash=%" PRIu64 ", "
   1624         "anonymous=%d; actual values: originAttrsHash=%" PRIu64
   1625         ", anonymous=%d]",
   1626         LOGSHA1(aEntry->Hash()), aOriginAttrsHash, aAnonymous,
   1627         aEntry->OriginAttrsHash(), aEntry->Anonymous()));
   1628    return true;
   1629  }
   1630 
   1631  return false;
   1632 }
   1633 
   1634 // static
   1635 bool CacheIndex::HasEntryChanged(
   1636    CacheIndexEntry* aEntry, const uint32_t* aFrecency, const bool* aHasAltData,
   1637    const uint16_t* aOnStartTime, const uint16_t* aOnStopTime,
   1638    const uint8_t* aContentType, const uint32_t* aSize) {
   1639  if (aFrecency && *aFrecency != aEntry->GetFrecency()) {
   1640    return true;
   1641  }
   1642 
   1643  if (aHasAltData && *aHasAltData != aEntry->GetHasAltData()) {
   1644    return true;
   1645  }
   1646 
   1647  if (aOnStartTime && *aOnStartTime != aEntry->GetOnStartTime()) {
   1648    return true;
   1649  }
   1650 
   1651  if (aOnStopTime && *aOnStopTime != aEntry->GetOnStopTime()) {
   1652    return true;
   1653  }
   1654 
   1655  if (aContentType && *aContentType != aEntry->GetContentType()) {
   1656    return true;
   1657  }
   1658 
   1659  if (aSize &&
   1660      (*aSize & CacheIndexEntry::kFileSizeMask) != aEntry->GetFileSize()) {
   1661    return true;
   1662  }
   1663 
   1664  return false;
   1665 }
   1666 
   1667 void CacheIndex::ProcessPendingOperations(
   1668    const StaticMutexAutoLock& aProofOfLock) {
   1669  sLock.AssertCurrentThreadOwns();
   1670  LOG(("CacheIndex::ProcessPendingOperations()"));
   1671 
   1672  for (auto iter = mPendingUpdates.Iter(); !iter.Done(); iter.Next()) {
   1673    CacheIndexEntryUpdate* update = iter.Get();
   1674 
   1675    LOG(("CacheIndex::ProcessPendingOperations() [hash=%08x%08x%08x%08x%08x]",
   1676         LOGSHA1(update->Hash())));
   1677 
   1678    MOZ_ASSERT(update->IsFresh());
   1679 
   1680    CacheIndexEntry* entry = mIndex.GetEntry(*update->Hash());
   1681    {
   1682      CacheIndexEntryAutoManage emng(update->Hash(), this, aProofOfLock);
   1683      emng.DoNotSearchInUpdates();
   1684 
   1685      if (update->IsRemoved()) {
   1686        if (entry) {
   1687          if (entry->IsRemoved()) {
   1688            MOZ_ASSERT(entry->IsFresh());
   1689            MOZ_ASSERT(entry->IsDirty());
   1690          } else if (!entry->IsDirty() && entry->IsFileEmpty()) {
   1691            // Entries with empty file are not stored in index on disk. Just
   1692            // remove the entry, but only in case the entry is not dirty, i.e.
   1693            // the entry file was empty when we wrote the index.
   1694            mIndex.RemoveEntry(entry);
   1695            entry = nullptr;
   1696          } else {
   1697            entry->MarkRemoved();
   1698            entry->MarkDirty();
   1699            entry->MarkFresh();
   1700          }
   1701        }
   1702      } else if (entry) {
   1703        // Some information in mIndex can be newer than in mPendingUpdates (see
   1704        // bug 1074832). This will copy just those values that were really
   1705        // updated.
   1706        update->ApplyUpdate(entry);
   1707      } else {
   1708        // There is no entry in mIndex, copy all information from
   1709        // mPendingUpdates to mIndex.
   1710        entry = mIndex.PutEntry(*update->Hash());
   1711        *entry = *update;
   1712      }
   1713    }
   1714    iter.Remove();
   1715  }
   1716 
   1717  MOZ_ASSERT(mPendingUpdates.Count() == 0);
   1718 
   1719  EnsureCorrectStats();
   1720 }
   1721 
   1722 bool CacheIndex::WriteIndexToDiskIfNeeded(
   1723    const StaticMutexAutoLock& aProofOfLock) {
   1724  sLock.AssertCurrentThreadOwns();
   1725  if (mState != READY || mShuttingDown || mRWPending) {
   1726    return false;
   1727  }
   1728 
   1729  if (!mLastDumpTime.IsNull() &&
   1730      (TimeStamp::NowLoRes() - mLastDumpTime).ToMilliseconds() <
   1731          kMinDumpInterval) {
   1732    return false;
   1733  }
   1734 
   1735  if (mIndexStats.Dirty() < kMinUnwrittenChanges) {
   1736    return false;
   1737  }
   1738 
   1739  WriteIndexToDisk(aProofOfLock);
   1740  return true;
   1741 }
   1742 
   1743 void CacheIndex::WriteIndexToDisk(const StaticMutexAutoLock& aProofOfLock) {
   1744  sLock.AssertCurrentThreadOwns();
   1745  LOG(("CacheIndex::WriteIndexToDisk()"));
   1746  mIndexStats.Log();
   1747 
   1748  nsresult rv;
   1749 
   1750  MOZ_ASSERT(mState == READY);
   1751  MOZ_ASSERT(!mRWBuf);
   1752  MOZ_ASSERT(!mRWHash);
   1753  MOZ_ASSERT(!mRWPending);
   1754 
   1755  ChangeState(WRITING, aProofOfLock);
   1756 
   1757  mProcessEntries = mIndexStats.ActiveEntriesCount();
   1758 
   1759  mIndexFileOpener = new FileOpenHelper(this);
   1760  rv = CacheFileIOManager::OpenFile(
   1761      nsLiteralCString(TEMP_INDEX_NAME),
   1762      CacheFileIOManager::SPECIAL_FILE | CacheFileIOManager::CREATE,
   1763      mIndexFileOpener);
   1764  if (NS_FAILED(rv)) {
   1765    LOG(("CacheIndex::WriteIndexToDisk() - Can't open file [rv=0x%08" PRIx32
   1766         "]",
   1767         static_cast<uint32_t>(rv)));
   1768    FinishWrite(false, aProofOfLock);
   1769    return;
   1770  }
   1771 
   1772  // Write index header to a buffer, it will be written to disk together with
   1773  // records in WriteRecords() once we open the file successfully.
   1774  AllocBuffer();
   1775  mRWHash = new CacheHash();
   1776 
   1777  mRWBufPos = 0;
   1778  // index version
   1779  NetworkEndian::writeUint32(mRWBuf + mRWBufPos, kIndexVersion);
   1780  mRWBufPos += sizeof(uint32_t);
   1781  // timestamp
   1782  NetworkEndian::writeUint32(mRWBuf + mRWBufPos,
   1783                             static_cast<uint32_t>(PR_Now() / PR_USEC_PER_SEC));
   1784  mRWBufPos += sizeof(uint32_t);
   1785  // dirty flag
   1786  NetworkEndian::writeUint32(mRWBuf + mRWBufPos, 1);
   1787  mRWBufPos += sizeof(uint32_t);
   1788  // amount of data written to the cache
   1789  NetworkEndian::writeUint32(mRWBuf + mRWBufPos,
   1790                             static_cast<uint32_t>(mTotalBytesWritten >> 10));
   1791  mRWBufPos += sizeof(uint32_t);
   1792 
   1793  mSkipEntries = 0;
   1794 }
   1795 
   1796 void CacheIndex::WriteRecords(const StaticMutexAutoLock& aProofOfLock) {
   1797  sLock.AssertCurrentThreadOwns();
   1798  LOG(("CacheIndex::WriteRecords()"));
   1799 
   1800  nsresult rv;
   1801 
   1802  MOZ_ASSERT(mState == WRITING);
   1803  MOZ_ASSERT(!mRWPending);
   1804 
   1805  int64_t fileOffset;
   1806 
   1807  if (mSkipEntries) {
   1808    MOZ_ASSERT(mRWBufPos == 0);
   1809    fileOffset = sizeof(CacheIndexHeader);
   1810    fileOffset += sizeof(CacheIndexRecord) * mSkipEntries;
   1811  } else {
   1812    MOZ_ASSERT(mRWBufPos == sizeof(CacheIndexHeader));
   1813    fileOffset = 0;
   1814  }
   1815  uint32_t hashOffset = mRWBufPos;
   1816 
   1817  char* buf = mRWBuf + mRWBufPos;
   1818  uint32_t skip = mSkipEntries;
   1819  uint32_t processMax = (mRWBufSize - mRWBufPos) / sizeof(CacheIndexRecord);
   1820  MOZ_ASSERT(processMax != 0 ||
   1821             mProcessEntries ==
   1822                 0);  // TODO make sure we can write an empty index
   1823  uint32_t processed = 0;
   1824 #ifdef DEBUG
   1825  bool hasMore = false;
   1826 #endif
   1827  for (auto iter = mIndex.Iter(); !iter.Done(); iter.Next()) {
   1828    CacheIndexEntry* entry = iter.Get();
   1829    if (entry->IsRemoved() || !entry->IsInitialized() || entry->IsFileEmpty()) {
   1830      continue;
   1831    }
   1832 
   1833    if (skip) {
   1834      skip--;
   1835      continue;
   1836    }
   1837 
   1838    if (processed == processMax) {
   1839 #ifdef DEBUG
   1840      hasMore = true;
   1841 #endif
   1842      break;
   1843    }
   1844 
   1845    entry->WriteToBuf(buf);
   1846    buf += sizeof(CacheIndexRecord);
   1847    processed++;
   1848  }
   1849 
   1850  MOZ_ASSERT(mRWBufPos != static_cast<uint32_t>(buf - mRWBuf) ||
   1851             mProcessEntries == 0);
   1852  mRWBufPos = buf - mRWBuf;
   1853  mSkipEntries += processed;
   1854  MOZ_ASSERT(mSkipEntries <= mProcessEntries);
   1855 
   1856  mRWHash->Update(mRWBuf + hashOffset, mRWBufPos - hashOffset);
   1857 
   1858  if (mSkipEntries == mProcessEntries) {
   1859    MOZ_ASSERT(!hasMore);
   1860 
   1861    // We've processed all records
   1862    if (mRWBufPos + sizeof(CacheHash::Hash32_t) > mRWBufSize) {
   1863      // realloc buffer to spare another write cycle
   1864      mRWBufSize = mRWBufPos + sizeof(CacheHash::Hash32_t);
   1865      mRWBuf = static_cast<char*>(moz_xrealloc(mRWBuf, mRWBufSize));
   1866    }
   1867 
   1868    NetworkEndian::writeUint32(mRWBuf + mRWBufPos, mRWHash->GetHash());
   1869    mRWBufPos += sizeof(CacheHash::Hash32_t);
   1870  } else {
   1871    MOZ_ASSERT(hasMore);
   1872  }
   1873 
   1874  rv = CacheFileIOManager::Write(mIndexHandle, fileOffset, mRWBuf, mRWBufPos,
   1875                                 mSkipEntries == mProcessEntries, false, this);
   1876  if (NS_FAILED(rv)) {
   1877    LOG(
   1878        ("CacheIndex::WriteRecords() - CacheFileIOManager::Write() failed "
   1879         "synchronously [rv=0x%08" PRIx32 "]",
   1880         static_cast<uint32_t>(rv)));
   1881    FinishWrite(false, aProofOfLock);
   1882  } else {
   1883    mRWPending = true;
   1884  }
   1885 
   1886  mRWBufPos = 0;
   1887 }
   1888 
   1889 void CacheIndex::FinishWrite(bool aSucceeded,
   1890                             const StaticMutexAutoLock& aProofOfLock) {
   1891  sLock.AssertCurrentThreadOwns();
   1892  LOG(("CacheIndex::FinishWrite() [succeeded=%d]", aSucceeded));
   1893 
   1894  MOZ_ASSERT((!aSucceeded && mState == SHUTDOWN) || mState == WRITING);
   1895 
   1896  // If there is write operation pending we must be cancelling writing of the
   1897  // index when shutting down or removing the whole index.
   1898  MOZ_ASSERT(!mRWPending || (!aSucceeded && (mShuttingDown || mRemovingAll)));
   1899 
   1900  mIndexHandle = nullptr;
   1901  mRWHash = nullptr;
   1902  ReleaseBuffer();
   1903 
   1904  if (aSucceeded) {
   1905    // Opening of the file must not be in progress if writing succeeded.
   1906    MOZ_ASSERT(!mIndexFileOpener);
   1907 
   1908    for (auto iter = mIndex.Iter(); !iter.Done(); iter.Next()) {
   1909      CacheIndexEntry* entry = iter.Get();
   1910 
   1911      bool remove = false;
   1912      {
   1913        CacheIndexEntryAutoManage emng(entry->Hash(), this, aProofOfLock);
   1914 
   1915        if (entry->IsRemoved()) {
   1916          emng.DoNotSearchInIndex();
   1917          remove = true;
   1918        } else if (entry->IsDirty()) {
   1919          entry->ClearDirty();
   1920        }
   1921      }
   1922      if (remove) {
   1923        iter.Remove();
   1924      }
   1925    }
   1926 
   1927    mIndexOnDiskIsValid = true;
   1928  } else {
   1929    if (mIndexFileOpener) {
   1930      // If opening of the file is still in progress (e.g. WRITE process was
   1931      // canceled by RemoveAll()) then we need to cancel the opener to make sure
   1932      // that OnFileOpenedInternal() won't be called.
   1933      mIndexFileOpener->Cancel();
   1934      mIndexFileOpener = nullptr;
   1935    }
   1936  }
   1937 
   1938  ProcessPendingOperations(aProofOfLock);
   1939  mIndexStats.Log();
   1940 
   1941  if (mState == WRITING) {
   1942    ChangeState(READY, aProofOfLock);
   1943    mLastDumpTime = TimeStamp::NowLoRes();
   1944  }
   1945 }
   1946 
   1947 nsresult CacheIndex::GetFile(const nsACString& aName, nsIFile** _retval) {
   1948  nsresult rv;
   1949 
   1950  nsCOMPtr<nsIFile> file;
   1951  rv = mCacheDirectory->Clone(getter_AddRefs(file));
   1952  NS_ENSURE_SUCCESS(rv, rv);
   1953 
   1954  rv = file->AppendNative(aName);
   1955  NS_ENSURE_SUCCESS(rv, rv);
   1956 
   1957  file.swap(*_retval);
   1958  return NS_OK;
   1959 }
   1960 
   1961 void CacheIndex::RemoveFile(const nsACString& aName) {
   1962  MOZ_ASSERT(mState == SHUTDOWN);
   1963 
   1964  nsresult rv;
   1965 
   1966  nsCOMPtr<nsIFile> file;
   1967  rv = GetFile(aName, getter_AddRefs(file));
   1968  NS_ENSURE_SUCCESS_VOID(rv);
   1969 
   1970  rv = file->Remove(false);
   1971  if (NS_FAILED(rv) && rv != NS_ERROR_FILE_NOT_FOUND) {
   1972    LOG(
   1973        ("CacheIndex::RemoveFile() - Cannot remove old entry file from disk "
   1974         "[rv=0x%08" PRIx32 ", name=%s]",
   1975         static_cast<uint32_t>(rv), PromiseFlatCString(aName).get()));
   1976  }
   1977 }
   1978 
   1979 void CacheIndex::RemoveAllIndexFiles() {
   1980  LOG(("CacheIndex::RemoveAllIndexFiles()"));
   1981  RemoveFile(nsLiteralCString(INDEX_NAME));
   1982  RemoveJournalAndTempFile();
   1983 }
   1984 
   1985 void CacheIndex::RemoveJournalAndTempFile() {
   1986  LOG(("CacheIndex::RemoveJournalAndTempFile()"));
   1987  RemoveFile(nsLiteralCString(TEMP_INDEX_NAME));
   1988  RemoveFile(nsLiteralCString(JOURNAL_NAME));
   1989 }
   1990 
   1991 class WriteLogHelper {
   1992 public:
   1993  explicit WriteLogHelper(PRFileDesc* aFD)
   1994      : mFD(aFD), mBufSize(kMaxBufSize), mBufPos(0) {
   1995    mHash = new CacheHash();
   1996    mBuf = static_cast<char*>(moz_xmalloc(mBufSize));
   1997  }
   1998 
   1999  ~WriteLogHelper() { free(mBuf); }
   2000 
   2001  nsresult AddEntry(CacheIndexEntry* aEntry);
   2002  nsresult Finish();
   2003 
   2004 private:
   2005  nsresult FlushBuffer();
   2006 
   2007  PRFileDesc* mFD;
   2008  char* mBuf;
   2009  uint32_t mBufSize;
   2010  int32_t mBufPos;
   2011  RefPtr<CacheHash> mHash;
   2012 };
   2013 
   2014 nsresult WriteLogHelper::AddEntry(CacheIndexEntry* aEntry) {
   2015  nsresult rv;
   2016 
   2017  if (mBufPos + sizeof(CacheIndexRecord) > mBufSize) {
   2018    mHash->Update(mBuf, mBufPos);
   2019 
   2020    rv = FlushBuffer();
   2021    NS_ENSURE_SUCCESS(rv, rv);
   2022    MOZ_ASSERT(mBufPos + sizeof(CacheIndexRecord) <= mBufSize);
   2023  }
   2024 
   2025  aEntry->WriteToBuf(mBuf + mBufPos);
   2026  mBufPos += sizeof(CacheIndexRecord);
   2027 
   2028  return NS_OK;
   2029 }
   2030 
   2031 nsresult WriteLogHelper::Finish() {
   2032  nsresult rv;
   2033 
   2034  mHash->Update(mBuf, mBufPos);
   2035  if (mBufPos + sizeof(CacheHash::Hash32_t) > mBufSize) {
   2036    rv = FlushBuffer();
   2037    NS_ENSURE_SUCCESS(rv, rv);
   2038    MOZ_ASSERT(mBufPos + sizeof(CacheHash::Hash32_t) <= mBufSize);
   2039  }
   2040 
   2041  NetworkEndian::writeUint32(mBuf + mBufPos, mHash->GetHash());
   2042  mBufPos += sizeof(CacheHash::Hash32_t);
   2043 
   2044  rv = FlushBuffer();
   2045  NS_ENSURE_SUCCESS(rv, rv);
   2046 
   2047  return NS_OK;
   2048 }
   2049 
   2050 nsresult WriteLogHelper::FlushBuffer() {
   2051  if (CacheObserver::IsPastShutdownIOLag()) {
   2052    LOG(("WriteLogHelper::FlushBuffer() - Interrupting writing journal."));
   2053    return NS_ERROR_FAILURE;
   2054  }
   2055 
   2056  int32_t bytesWritten = PR_Write(mFD, mBuf, mBufPos);
   2057 
   2058  if (bytesWritten != mBufPos) {
   2059    return NS_ERROR_FAILURE;
   2060  }
   2061 
   2062  mBufPos = 0;
   2063  return NS_OK;
   2064 }
   2065 
   2066 nsresult CacheIndex::WriteLogToDisk() {
   2067  LOG(("CacheIndex::WriteLogToDisk()"));
   2068 
   2069  nsresult rv;
   2070 
   2071  MOZ_ASSERT(mPendingUpdates.Count() == 0);
   2072  MOZ_ASSERT(mState == SHUTDOWN);
   2073 
   2074  if (CacheObserver::IsPastShutdownIOLag()) {
   2075    LOG(("CacheIndex::WriteLogToDisk() - Skipping writing journal."));
   2076    return NS_ERROR_FAILURE;
   2077  }
   2078 
   2079  RemoveFile(nsLiteralCString(TEMP_INDEX_NAME));
   2080 
   2081  nsCOMPtr<nsIFile> indexFile;
   2082  rv = GetFile(nsLiteralCString(INDEX_NAME), getter_AddRefs(indexFile));
   2083  NS_ENSURE_SUCCESS(rv, rv);
   2084 
   2085  nsCOMPtr<nsIFile> logFile;
   2086  rv = GetFile(nsLiteralCString(JOURNAL_NAME), getter_AddRefs(logFile));
   2087  NS_ENSURE_SUCCESS(rv, rv);
   2088 
   2089  mIndexStats.Log();
   2090 
   2091  PRFileDesc* fd = nullptr;
   2092  rv = logFile->OpenNSPRFileDesc(PR_RDWR | PR_CREATE_FILE | PR_TRUNCATE, 0600,
   2093                                 &fd);
   2094  NS_ENSURE_SUCCESS(rv, rv);
   2095 
   2096  WriteLogHelper wlh(fd);
   2097  for (auto iter = mIndex.Iter(); !iter.Done(); iter.Next()) {
   2098    CacheIndexEntry* entry = iter.Get();
   2099    if (entry->IsRemoved() || entry->IsDirty()) {
   2100      rv = wlh.AddEntry(entry);
   2101      if (NS_WARN_IF(NS_FAILED(rv))) {
   2102        return rv;
   2103      }
   2104    }
   2105  }
   2106 
   2107  rv = wlh.Finish();
   2108  PR_Close(fd);
   2109  NS_ENSURE_SUCCESS(rv, rv);
   2110 
   2111  rv = indexFile->OpenNSPRFileDesc(PR_RDWR, 0600, &fd);
   2112  NS_ENSURE_SUCCESS(rv, rv);
   2113 
   2114  // Seek to dirty flag in the index header and clear it.
   2115  static_assert(2 * sizeof(uint32_t) == offsetof(CacheIndexHeader, mIsDirty),
   2116                "Unexpected offset of CacheIndexHeader::mIsDirty");
   2117  int64_t offset = PR_Seek64(fd, 2 * sizeof(uint32_t), PR_SEEK_SET);
   2118  if (offset == -1) {
   2119    PR_Close(fd);
   2120    return NS_ERROR_FAILURE;
   2121  }
   2122 
   2123  uint32_t isDirty = 0;
   2124  int32_t bytesWritten = PR_Write(fd, &isDirty, sizeof(isDirty));
   2125  PR_Close(fd);
   2126  if (bytesWritten != sizeof(isDirty)) {
   2127    return NS_ERROR_FAILURE;
   2128  }
   2129 
   2130  return NS_OK;
   2131 }
   2132 
   2133 void CacheIndex::ReadIndexFromDisk(const StaticMutexAutoLock& aProofOfLock) {
   2134  sLock.AssertCurrentThreadOwns();
   2135  LOG(("CacheIndex::ReadIndexFromDisk()"));
   2136 
   2137  nsresult rv;
   2138 
   2139  MOZ_ASSERT(mState == INITIAL);
   2140 
   2141  ChangeState(READING, aProofOfLock);
   2142 
   2143  mIndexFileOpener = new FileOpenHelper(this);
   2144  rv = CacheFileIOManager::OpenFile(
   2145      nsLiteralCString(INDEX_NAME),
   2146      CacheFileIOManager::SPECIAL_FILE | CacheFileIOManager::OPEN,
   2147      mIndexFileOpener);
   2148  if (NS_FAILED(rv)) {
   2149    LOG(
   2150        ("CacheIndex::ReadIndexFromDisk() - CacheFileIOManager::OpenFile() "
   2151         "failed [rv=0x%08" PRIx32 ", file=%s]",
   2152         static_cast<uint32_t>(rv), INDEX_NAME));
   2153    FinishRead(false, aProofOfLock);
   2154    return;
   2155  }
   2156 
   2157  mJournalFileOpener = new FileOpenHelper(this);
   2158  rv = CacheFileIOManager::OpenFile(
   2159      nsLiteralCString(JOURNAL_NAME),
   2160      CacheFileIOManager::SPECIAL_FILE | CacheFileIOManager::OPEN,
   2161      mJournalFileOpener);
   2162  if (NS_FAILED(rv)) {
   2163    LOG(
   2164        ("CacheIndex::ReadIndexFromDisk() - CacheFileIOManager::OpenFile() "
   2165         "failed [rv=0x%08" PRIx32 ", file=%s]",
   2166         static_cast<uint32_t>(rv), JOURNAL_NAME));
   2167    FinishRead(false, aProofOfLock);
   2168  }
   2169 
   2170  mTmpFileOpener = new FileOpenHelper(this);
   2171  rv = CacheFileIOManager::OpenFile(
   2172      nsLiteralCString(TEMP_INDEX_NAME),
   2173      CacheFileIOManager::SPECIAL_FILE | CacheFileIOManager::OPEN,
   2174      mTmpFileOpener);
   2175  if (NS_FAILED(rv)) {
   2176    LOG(
   2177        ("CacheIndex::ReadIndexFromDisk() - CacheFileIOManager::OpenFile() "
   2178         "failed [rv=0x%08" PRIx32 ", file=%s]",
   2179         static_cast<uint32_t>(rv), TEMP_INDEX_NAME));
   2180    FinishRead(false, aProofOfLock);
   2181  }
   2182 }
   2183 
   2184 void CacheIndex::StartReadingIndex(const StaticMutexAutoLock& aProofOfLock) {
   2185  sLock.AssertCurrentThreadOwns();
   2186  LOG(("CacheIndex::StartReadingIndex()"));
   2187 
   2188  nsresult rv;
   2189 
   2190  MOZ_ASSERT(mIndexHandle);
   2191  MOZ_ASSERT(mState == READING);
   2192  MOZ_ASSERT(!mIndexOnDiskIsValid);
   2193  MOZ_ASSERT(!mDontMarkIndexClean);
   2194  MOZ_ASSERT(!mJournalReadSuccessfully);
   2195  MOZ_ASSERT(mIndexHandle->FileSize() >= 0);
   2196  MOZ_ASSERT(!mRWPending);
   2197 
   2198  int64_t entriesSize = mIndexHandle->FileSize() - sizeof(CacheIndexHeader) -
   2199                        sizeof(CacheHash::Hash32_t);
   2200 
   2201  if (entriesSize < 0 || entriesSize % sizeof(CacheIndexRecord)) {
   2202    LOG(("CacheIndex::StartReadingIndex() - Index is corrupted"));
   2203    FinishRead(false, aProofOfLock);
   2204    return;
   2205  }
   2206 
   2207  AllocBuffer();
   2208  mSkipEntries = 0;
   2209  mRWHash = new CacheHash();
   2210 
   2211  mRWBufPos =
   2212      std::min(mRWBufSize, static_cast<uint32_t>(mIndexHandle->FileSize()));
   2213 
   2214  rv = CacheFileIOManager::Read(mIndexHandle, 0, mRWBuf, mRWBufPos, this);
   2215  if (NS_FAILED(rv)) {
   2216    LOG(
   2217        ("CacheIndex::StartReadingIndex() - CacheFileIOManager::Read() failed "
   2218         "synchronously [rv=0x%08" PRIx32 "]",
   2219         static_cast<uint32_t>(rv)));
   2220    FinishRead(false, aProofOfLock);
   2221  } else {
   2222    mRWPending = true;
   2223  }
   2224 }
   2225 
   2226 void CacheIndex::ParseRecords(const StaticMutexAutoLock& aProofOfLock) {
   2227  sLock.AssertCurrentThreadOwns();
   2228  LOG(("CacheIndex::ParseRecords()"));
   2229 
   2230  nsresult rv;
   2231 
   2232  MOZ_ASSERT(!mRWPending);
   2233 
   2234  uint32_t entryCnt = (mIndexHandle->FileSize() - sizeof(CacheIndexHeader) -
   2235                       sizeof(CacheHash::Hash32_t)) /
   2236                      sizeof(CacheIndexRecord);
   2237  uint32_t pos = 0;
   2238 
   2239  if (!mSkipEntries) {
   2240    if (NetworkEndian::readUint32(mRWBuf + pos) != kIndexVersion) {
   2241      FinishRead(false, aProofOfLock);
   2242      return;
   2243    }
   2244    pos += sizeof(uint32_t);
   2245 
   2246    mIndexTimeStamp = NetworkEndian::readUint32(mRWBuf + pos);
   2247    pos += sizeof(uint32_t);
   2248 
   2249    if (NetworkEndian::readUint32(mRWBuf + pos)) {
   2250      if (mJournalHandle) {
   2251        CacheFileIOManager::DoomFile(mJournalHandle, nullptr);
   2252        mJournalHandle = nullptr;
   2253      }
   2254    } else {
   2255      uint32_t* isDirty =
   2256          reinterpret_cast<uint32_t*>(moz_xmalloc(sizeof(uint32_t)));
   2257      NetworkEndian::writeUint32(isDirty, 1);
   2258 
   2259      // Mark index dirty. The buffer will be freed by CacheFileIOManager.
   2260      CacheFileIOManager::WriteWithoutCallback(
   2261          mIndexHandle, 2 * sizeof(uint32_t), reinterpret_cast<char*>(isDirty),
   2262          sizeof(uint32_t), true, false);
   2263    }
   2264    pos += sizeof(uint32_t);
   2265 
   2266    uint64_t dataWritten = NetworkEndian::readUint32(mRWBuf + pos);
   2267    pos += sizeof(uint32_t);
   2268    dataWritten <<= 10;
   2269    mTotalBytesWritten += dataWritten;
   2270  }
   2271 
   2272  uint32_t hashOffset = pos;
   2273 
   2274  while (pos + sizeof(CacheIndexRecord) <= mRWBufPos &&
   2275         mSkipEntries != entryCnt) {
   2276    CacheIndexRecord* rec = reinterpret_cast<CacheIndexRecord*>(mRWBuf + pos);
   2277    CacheIndexEntry tmpEntry(&rec->mHash);
   2278    tmpEntry.ReadFromBuf(mRWBuf + pos);
   2279 
   2280    if (tmpEntry.IsDirty() || !tmpEntry.IsInitialized() ||
   2281        tmpEntry.IsFileEmpty() || tmpEntry.IsFresh() || tmpEntry.IsRemoved()) {
   2282      LOG(
   2283          ("CacheIndex::ParseRecords() - Invalid entry found in index, removing"
   2284           " whole index [dirty=%d, initialized=%d, fileEmpty=%d, fresh=%d, "
   2285           "removed=%d]",
   2286           tmpEntry.IsDirty(), tmpEntry.IsInitialized(), tmpEntry.IsFileEmpty(),
   2287           tmpEntry.IsFresh(), tmpEntry.IsRemoved()));
   2288      FinishRead(false, aProofOfLock);
   2289      return;
   2290    }
   2291 
   2292    CacheIndexEntryAutoManage emng(tmpEntry.Hash(), this, aProofOfLock);
   2293 
   2294    CacheIndexEntry* entry = mIndex.PutEntry(*tmpEntry.Hash());
   2295    *entry = tmpEntry;
   2296 
   2297    pos += sizeof(CacheIndexRecord);
   2298    mSkipEntries++;
   2299  }
   2300 
   2301  mRWHash->Update(mRWBuf + hashOffset, pos - hashOffset);
   2302 
   2303  if (pos != mRWBufPos) {
   2304    memmove(mRWBuf, mRWBuf + pos, mRWBufPos - pos);
   2305  }
   2306 
   2307  mRWBufPos -= pos;
   2308  pos = 0;
   2309 
   2310  int64_t fileOffset = sizeof(CacheIndexHeader) +
   2311                       mSkipEntries * sizeof(CacheIndexRecord) + mRWBufPos;
   2312 
   2313  MOZ_ASSERT(fileOffset <= mIndexHandle->FileSize());
   2314  if (fileOffset == mIndexHandle->FileSize()) {
   2315    uint32_t expectedHash = NetworkEndian::readUint32(mRWBuf);
   2316    if (mRWHash->GetHash() != expectedHash) {
   2317      LOG(("CacheIndex::ParseRecords() - Hash mismatch, [is %x, should be %x]",
   2318           mRWHash->GetHash(), expectedHash));
   2319      FinishRead(false, aProofOfLock);
   2320      return;
   2321    }
   2322 
   2323    mIndexOnDiskIsValid = true;
   2324    mJournalReadSuccessfully = false;
   2325 
   2326    if (mJournalHandle) {
   2327      StartReadingJournal(aProofOfLock);
   2328    } else {
   2329      FinishRead(false, aProofOfLock);
   2330    }
   2331 
   2332    return;
   2333  }
   2334 
   2335  pos = mRWBufPos;
   2336  uint32_t toRead =
   2337      std::min(mRWBufSize - pos,
   2338               static_cast<uint32_t>(mIndexHandle->FileSize() - fileOffset));
   2339  mRWBufPos = pos + toRead;
   2340 
   2341  rv = CacheFileIOManager::Read(mIndexHandle, fileOffset, mRWBuf + pos, toRead,
   2342                                this);
   2343  if (NS_FAILED(rv)) {
   2344    LOG(
   2345        ("CacheIndex::ParseRecords() - CacheFileIOManager::Read() failed "
   2346         "synchronously [rv=0x%08" PRIx32 "]",
   2347         static_cast<uint32_t>(rv)));
   2348    FinishRead(false, aProofOfLock);
   2349    return;
   2350  }
   2351  mRWPending = true;
   2352 }
   2353 
   2354 void CacheIndex::StartReadingJournal(const StaticMutexAutoLock& aProofOfLock) {
   2355  sLock.AssertCurrentThreadOwns();
   2356  LOG(("CacheIndex::StartReadingJournal()"));
   2357 
   2358  nsresult rv;
   2359 
   2360  MOZ_ASSERT(mJournalHandle);
   2361  MOZ_ASSERT(mIndexOnDiskIsValid);
   2362  MOZ_ASSERT(mTmpJournal.Count() == 0);
   2363  MOZ_ASSERT(mJournalHandle->FileSize() >= 0);
   2364  MOZ_ASSERT(!mRWPending);
   2365 
   2366  int64_t entriesSize =
   2367      mJournalHandle->FileSize() - sizeof(CacheHash::Hash32_t);
   2368 
   2369  if (entriesSize < 0 || entriesSize % sizeof(CacheIndexRecord)) {
   2370    LOG(("CacheIndex::StartReadingJournal() - Journal is corrupted"));
   2371    FinishRead(false, aProofOfLock);
   2372    return;
   2373  }
   2374 
   2375  mSkipEntries = 0;
   2376  mRWHash = new CacheHash();
   2377 
   2378  mRWBufPos =
   2379      std::min(mRWBufSize, static_cast<uint32_t>(mJournalHandle->FileSize()));
   2380 
   2381  rv = CacheFileIOManager::Read(mJournalHandle, 0, mRWBuf, mRWBufPos, this);
   2382  if (NS_FAILED(rv)) {
   2383    LOG(
   2384        ("CacheIndex::StartReadingJournal() - CacheFileIOManager::Read() failed"
   2385         " synchronously [rv=0x%08" PRIx32 "]",
   2386         static_cast<uint32_t>(rv)));
   2387    FinishRead(false, aProofOfLock);
   2388  } else {
   2389    mRWPending = true;
   2390  }
   2391 }
   2392 
   2393 void CacheIndex::ParseJournal(const StaticMutexAutoLock& aProofOfLock) {
   2394  sLock.AssertCurrentThreadOwns();
   2395  LOG(("CacheIndex::ParseJournal()"));
   2396 
   2397  nsresult rv;
   2398 
   2399  MOZ_ASSERT(!mRWPending);
   2400 
   2401  uint32_t entryCnt =
   2402      (mJournalHandle->FileSize() - sizeof(CacheHash::Hash32_t)) /
   2403      sizeof(CacheIndexRecord);
   2404 
   2405  uint32_t pos = 0;
   2406 
   2407  while (pos + sizeof(CacheIndexRecord) <= mRWBufPos &&
   2408         mSkipEntries != entryCnt) {
   2409    CacheIndexEntry tmpEntry(reinterpret_cast<SHA1Sum::Hash*>(mRWBuf + pos));
   2410    tmpEntry.ReadFromBuf(mRWBuf + pos);
   2411 
   2412    CacheIndexEntry* entry = mTmpJournal.PutEntry(*tmpEntry.Hash());
   2413    *entry = tmpEntry;
   2414 
   2415    if (entry->IsDirty() || entry->IsFresh()) {
   2416      LOG(
   2417          ("CacheIndex::ParseJournal() - Invalid entry found in journal, "
   2418           "ignoring whole journal [dirty=%d, fresh=%d]",
   2419           entry->IsDirty(), entry->IsFresh()));
   2420      FinishRead(false, aProofOfLock);
   2421      return;
   2422    }
   2423 
   2424    pos += sizeof(CacheIndexRecord);
   2425    mSkipEntries++;
   2426  }
   2427 
   2428  mRWHash->Update(mRWBuf, pos);
   2429 
   2430  if (pos != mRWBufPos) {
   2431    memmove(mRWBuf, mRWBuf + pos, mRWBufPos - pos);
   2432  }
   2433 
   2434  mRWBufPos -= pos;
   2435  pos = 0;
   2436 
   2437  int64_t fileOffset = mSkipEntries * sizeof(CacheIndexRecord) + mRWBufPos;
   2438 
   2439  MOZ_ASSERT(fileOffset <= mJournalHandle->FileSize());
   2440  if (fileOffset == mJournalHandle->FileSize()) {
   2441    uint32_t expectedHash = NetworkEndian::readUint32(mRWBuf);
   2442    if (mRWHash->GetHash() != expectedHash) {
   2443      LOG(("CacheIndex::ParseJournal() - Hash mismatch, [is %x, should be %x]",
   2444           mRWHash->GetHash(), expectedHash));
   2445      FinishRead(false, aProofOfLock);
   2446      return;
   2447    }
   2448 
   2449    mJournalReadSuccessfully = true;
   2450    FinishRead(true, aProofOfLock);
   2451    return;
   2452  }
   2453 
   2454  pos = mRWBufPos;
   2455  uint32_t toRead =
   2456      std::min(mRWBufSize - pos,
   2457               static_cast<uint32_t>(mJournalHandle->FileSize() - fileOffset));
   2458  mRWBufPos = pos + toRead;
   2459 
   2460  rv = CacheFileIOManager::Read(mJournalHandle, fileOffset, mRWBuf + pos,
   2461                                toRead, this);
   2462  if (NS_FAILED(rv)) {
   2463    LOG(
   2464        ("CacheIndex::ParseJournal() - CacheFileIOManager::Read() failed "
   2465         "synchronously [rv=0x%08" PRIx32 "]",
   2466         static_cast<uint32_t>(rv)));
   2467    FinishRead(false, aProofOfLock);
   2468    return;
   2469  }
   2470  mRWPending = true;
   2471 }
   2472 
   2473 void CacheIndex::MergeJournal(const StaticMutexAutoLock& aProofOfLock) {
   2474  sLock.AssertCurrentThreadOwns();
   2475  LOG(("CacheIndex::MergeJournal()"));
   2476 
   2477  for (auto iter = mTmpJournal.Iter(); !iter.Done(); iter.Next()) {
   2478    CacheIndexEntry* entry = iter.Get();
   2479 
   2480    LOG(("CacheIndex::MergeJournal() [hash=%08x%08x%08x%08x%08x]",
   2481         LOGSHA1(entry->Hash())));
   2482 
   2483    CacheIndexEntry* entry2 = mIndex.GetEntry(*entry->Hash());
   2484    {
   2485      CacheIndexEntryAutoManage emng(entry->Hash(), this, aProofOfLock);
   2486      if (entry->IsRemoved()) {
   2487        if (entry2) {
   2488          entry2->MarkRemoved();
   2489          entry2->MarkDirty();
   2490        }
   2491      } else {
   2492        if (!entry2) {
   2493          entry2 = mIndex.PutEntry(*entry->Hash());
   2494        }
   2495 
   2496        *entry2 = *entry;
   2497        entry2->MarkDirty();
   2498      }
   2499    }
   2500    iter.Remove();
   2501  }
   2502 
   2503  MOZ_ASSERT(mTmpJournal.Count() == 0);
   2504 }
   2505 
   2506 void CacheIndex::EnsureNoFreshEntry() {
   2507 #ifdef DEBUG_STATS
   2508  CacheIndexStats debugStats;
   2509  debugStats.DisableLogging();
   2510  for (auto iter = mIndex.Iter(); !iter.Done(); iter.Next()) {
   2511    debugStats.BeforeChange(nullptr);
   2512    debugStats.AfterChange(iter.Get());
   2513  }
   2514  MOZ_ASSERT(debugStats.Fresh() == 0);
   2515 #endif
   2516 }
   2517 
   2518 void CacheIndex::EnsureCorrectStats() {
   2519 #ifdef DEBUG_STATS
   2520  MOZ_ASSERT(mPendingUpdates.Count() == 0);
   2521  CacheIndexStats debugStats;
   2522  debugStats.DisableLogging();
   2523  for (auto iter = mIndex.Iter(); !iter.Done(); iter.Next()) {
   2524    debugStats.BeforeChange(nullptr);
   2525    debugStats.AfterChange(iter.Get());
   2526  }
   2527  MOZ_ASSERT(debugStats == mIndexStats);
   2528 #endif
   2529 }
   2530 
   2531 void CacheIndex::FinishRead(bool aSucceeded,
   2532                            const StaticMutexAutoLock& aProofOfLock) {
   2533  sLock.AssertCurrentThreadOwns();
   2534  LOG(("CacheIndex::FinishRead() [succeeded=%d]", aSucceeded));
   2535 
   2536  MOZ_ASSERT((!aSucceeded && mState == SHUTDOWN) || mState == READING);
   2537 
   2538  MOZ_ASSERT(
   2539      // -> rebuild
   2540      (!aSucceeded && !mIndexOnDiskIsValid && !mJournalReadSuccessfully) ||
   2541      // -> update
   2542      (!aSucceeded && mIndexOnDiskIsValid && !mJournalReadSuccessfully) ||
   2543      // -> ready
   2544      (aSucceeded && mIndexOnDiskIsValid && mJournalReadSuccessfully));
   2545 
   2546  // If there is read operation pending we must be cancelling reading of the
   2547  // index when shutting down or removing the whole index.
   2548  MOZ_ASSERT(!mRWPending || (!aSucceeded && (mShuttingDown || mRemovingAll)));
   2549 
   2550  if (mState == SHUTDOWN) {
   2551    RemoveFile(nsLiteralCString(TEMP_INDEX_NAME));
   2552    RemoveFile(nsLiteralCString(JOURNAL_NAME));
   2553  } else {
   2554    if (mIndexHandle && !mIndexOnDiskIsValid) {
   2555      CacheFileIOManager::DoomFile(mIndexHandle, nullptr);
   2556    }
   2557 
   2558    if (mJournalHandle) {
   2559      CacheFileIOManager::DoomFile(mJournalHandle, nullptr);
   2560    }
   2561  }
   2562 
   2563  if (mIndexFileOpener) {
   2564    mIndexFileOpener->Cancel();
   2565    mIndexFileOpener = nullptr;
   2566  }
   2567  if (mJournalFileOpener) {
   2568    mJournalFileOpener->Cancel();
   2569    mJournalFileOpener = nullptr;
   2570  }
   2571  if (mTmpFileOpener) {
   2572    mTmpFileOpener->Cancel();
   2573    mTmpFileOpener = nullptr;
   2574  }
   2575 
   2576  mIndexHandle = nullptr;
   2577  mJournalHandle = nullptr;
   2578  mRWHash = nullptr;
   2579  ReleaseBuffer();
   2580 
   2581  if (mState == SHUTDOWN) {
   2582    return;
   2583  }
   2584 
   2585  if (!mIndexOnDiskIsValid) {
   2586    MOZ_ASSERT(mTmpJournal.Count() == 0);
   2587    EnsureNoFreshEntry();
   2588    ProcessPendingOperations(aProofOfLock);
   2589    // Remove all entries that we haven't seen during this session
   2590    RemoveNonFreshEntries(aProofOfLock);
   2591    StartUpdatingIndex(true, aProofOfLock);
   2592    return;
   2593  }
   2594 
   2595  if (!mJournalReadSuccessfully) {
   2596    mTmpJournal.Clear();
   2597    EnsureNoFreshEntry();
   2598    ProcessPendingOperations(aProofOfLock);
   2599    StartUpdatingIndex(false, aProofOfLock);
   2600    return;
   2601  }
   2602 
   2603  MergeJournal(aProofOfLock);
   2604  EnsureNoFreshEntry();
   2605  ProcessPendingOperations(aProofOfLock);
   2606  mIndexStats.Log();
   2607 
   2608  ChangeState(READY, aProofOfLock);
   2609  mLastDumpTime = TimeStamp::NowLoRes();  // Do not dump new index immediately
   2610 }
   2611 
   2612 // static
   2613 void CacheIndex::DelayedUpdate(nsITimer* aTimer, void* aClosure) {
   2614  LOG(("CacheIndex::DelayedUpdate()"));
   2615 
   2616  StaticMutexAutoLock lock(sLock);
   2617  RefPtr<CacheIndex> index = gInstance;
   2618 
   2619  if (!index) {
   2620    return;
   2621  }
   2622 
   2623  index->DelayedUpdateLocked(lock);
   2624 }
   2625 
   2626 // static
   2627 void CacheIndex::DelayedUpdateLocked(const StaticMutexAutoLock& aProofOfLock) {
   2628  sLock.AssertCurrentThreadOwns();
   2629  LOG(("CacheIndex::DelayedUpdateLocked()"));
   2630 
   2631  nsresult rv;
   2632 
   2633  mUpdateTimer = nullptr;
   2634 
   2635  if (!IsIndexUsable()) {
   2636    return;
   2637  }
   2638 
   2639  if (mState == READY && mShuttingDown) {
   2640    return;
   2641  }
   2642 
   2643  // mUpdateEventPending must be false here since StartUpdatingIndex() won't
   2644  // schedule timer if it is true.
   2645  MOZ_ASSERT(!mUpdateEventPending);
   2646  if (mState != BUILDING && mState != UPDATING) {
   2647    LOG(("CacheIndex::DelayedUpdateLocked() - Update was canceled"));
   2648    return;
   2649  }
   2650 
   2651  // We need to redispatch to run with lower priority
   2652  RefPtr<CacheIOThread> ioThread = CacheFileIOManager::IOThread();
   2653  MOZ_ASSERT(ioThread);
   2654 
   2655  mUpdateEventPending = true;
   2656  rv = ioThread->Dispatch(this, CacheIOThread::INDEX);
   2657  if (NS_FAILED(rv)) {
   2658    mUpdateEventPending = false;
   2659    NS_WARNING("CacheIndex::DelayedUpdateLocked() - Can't dispatch event");
   2660    LOG(("CacheIndex::DelayedUpdate() - Can't dispatch event"));
   2661    FinishUpdate(false, aProofOfLock);
   2662  }
   2663 }
   2664 
   2665 nsresult CacheIndex::ScheduleUpdateTimer(uint32_t aDelay) {
   2666  LOG(("CacheIndex::ScheduleUpdateTimer() [delay=%u]", aDelay));
   2667 
   2668  MOZ_ASSERT(!mUpdateTimer);
   2669 
   2670  nsCOMPtr<nsIEventTarget> ioTarget = CacheFileIOManager::IOTarget();
   2671  MOZ_ASSERT(ioTarget);
   2672 
   2673  return NS_NewTimerWithFuncCallback(
   2674      getter_AddRefs(mUpdateTimer), CacheIndex::DelayedUpdate, nullptr, aDelay,
   2675      nsITimer::TYPE_ONE_SHOT, "net::CacheIndex::ScheduleUpdateTimer"_ns,
   2676      ioTarget);
   2677 }
   2678 
   2679 nsresult CacheIndex::SetupDirectoryEnumerator() {
   2680  MOZ_ASSERT(!NS_IsMainThread());
   2681  MOZ_ASSERT(!mDirEnumerator);
   2682 
   2683  nsresult rv;
   2684  nsCOMPtr<nsIFile> file;
   2685 
   2686  rv = mCacheDirectory->Clone(getter_AddRefs(file));
   2687  NS_ENSURE_SUCCESS(rv, rv);
   2688 
   2689  rv = file->AppendNative(nsLiteralCString(ENTRIES_DIR));
   2690  NS_ENSURE_SUCCESS(rv, rv);
   2691 
   2692  bool exists;
   2693  rv = file->Exists(&exists);
   2694  NS_ENSURE_SUCCESS(rv, rv);
   2695 
   2696  if (!exists) {
   2697    NS_WARNING(
   2698        "CacheIndex::SetupDirectoryEnumerator() - Entries directory "
   2699        "doesn't exist!");
   2700    LOG(
   2701        ("CacheIndex::SetupDirectoryEnumerator() - Entries directory doesn't "
   2702         "exist!"));
   2703    return NS_ERROR_UNEXPECTED;
   2704  }
   2705 
   2706  // Do not do IO under the lock.
   2707  nsCOMPtr<nsIDirectoryEnumerator> dirEnumerator;
   2708  {
   2709    StaticMutexAutoUnlock unlock(sLock);
   2710    rv = file->GetDirectoryEntries(getter_AddRefs(dirEnumerator));
   2711  }
   2712  mDirEnumerator = dirEnumerator.forget();
   2713  NS_ENSURE_SUCCESS(rv, rv);
   2714 
   2715  return NS_OK;
   2716 }
   2717 
   2718 nsresult CacheIndex::InitEntryFromDiskData(CacheIndexEntry* aEntry,
   2719                                           CacheFileMetadata* aMetaData,
   2720                                           int64_t aFileSize) {
   2721  nsresult rv;
   2722 
   2723  aEntry->InitNew();
   2724  aEntry->MarkDirty();
   2725  aEntry->MarkFresh();
   2726 
   2727  aEntry->Init(GetOriginAttrsHash(aMetaData->OriginAttributes()),
   2728               aMetaData->IsAnonymous(), aMetaData->Pinned());
   2729 
   2730  aEntry->SetFrecency(aMetaData->GetFrecency());
   2731 
   2732  const char* altData = aMetaData->GetElement(CacheFileUtils::kAltDataKey);
   2733  bool hasAltData = altData != nullptr;
   2734  if (hasAltData && NS_FAILED(CacheFileUtils::ParseAlternativeDataInfo(
   2735                        altData, nullptr, nullptr))) {
   2736    return NS_ERROR_FAILURE;
   2737  }
   2738  aEntry->SetHasAltData(hasAltData);
   2739 
   2740  static auto toUint16 = [](const char* aUint16String) -> uint16_t {
   2741    if (!aUint16String) {
   2742      return kIndexTimeNotAvailable;
   2743    }
   2744    nsresult rv;
   2745    uint64_t n64 = nsDependentCString(aUint16String).ToInteger64(&rv);
   2746    MOZ_ASSERT(NS_SUCCEEDED(rv));
   2747    return n64 <= kIndexTimeOutOfBound ? n64 : kIndexTimeOutOfBound;
   2748  };
   2749 
   2750  aEntry->SetOnStartTime(
   2751      toUint16(aMetaData->GetElement("net-response-time-onstart")));
   2752  aEntry->SetOnStopTime(
   2753      toUint16(aMetaData->GetElement("net-response-time-onstop")));
   2754 
   2755  const char* contentTypeStr = aMetaData->GetElement("ctid");
   2756  uint8_t contentType = nsICacheEntry::CONTENT_TYPE_UNKNOWN;
   2757  if (contentTypeStr) {
   2758    int64_t n64 = nsDependentCString(contentTypeStr).ToInteger64(&rv);
   2759    if (NS_FAILED(rv) || n64 < nsICacheEntry::CONTENT_TYPE_UNKNOWN ||
   2760        n64 >= nsICacheEntry::CONTENT_TYPE_LAST) {
   2761      n64 = nsICacheEntry::CONTENT_TYPE_UNKNOWN;
   2762    }
   2763    contentType = n64;
   2764  }
   2765  aEntry->SetContentType(contentType);
   2766 
   2767  aEntry->SetFileSize(static_cast<uint32_t>(std::min(
   2768      static_cast<int64_t>(PR_UINT32_MAX), (aFileSize + 0x3FF) >> 10)));
   2769  return NS_OK;
   2770 }
   2771 
   2772 bool CacheIndex::IsUpdatePending() {
   2773  sLock.AssertCurrentThreadOwns();
   2774 
   2775  return mUpdateTimer || mUpdateEventPending;
   2776 }
   2777 
   2778 void CacheIndex::BuildIndex(const StaticMutexAutoLock& aProofOfLock) {
   2779  sLock.AssertCurrentThreadOwns();
   2780  LOG(("CacheIndex::BuildIndex()"));
   2781 
   2782  MOZ_ASSERT(mPendingUpdates.Count() == 0);
   2783 
   2784  nsresult rv;
   2785 
   2786  if (!mDirEnumerator) {
   2787    rv = SetupDirectoryEnumerator();
   2788    if (mState == SHUTDOWN) {
   2789      // The index was shut down while we released the lock. FinishUpdate() was
   2790      // already called from Shutdown(), so just simply return here.
   2791      return;
   2792    }
   2793 
   2794    if (NS_FAILED(rv)) {
   2795      FinishUpdate(false, aProofOfLock);
   2796      return;
   2797    }
   2798  }
   2799 
   2800  while (true) {
   2801    if (CacheIOThread::YieldAndRerun()) {
   2802      LOG((
   2803          "CacheIndex::BuildIndex() - Breaking loop for higher level events."));
   2804      mUpdateEventPending = true;
   2805      return;
   2806    }
   2807 
   2808    bool fileExists = false;
   2809    nsCOMPtr<nsIFile> file;
   2810    {
   2811      // Do not do IO under the lock.
   2812      nsCOMPtr<nsIDirectoryEnumerator> dirEnumerator(mDirEnumerator);
   2813      sLock.AssertCurrentThreadOwns();
   2814      StaticMutexAutoUnlock unlock(sLock);
   2815      rv = dirEnumerator->GetNextFile(getter_AddRefs(file));
   2816 
   2817      if (file) {
   2818        file->Exists(&fileExists);
   2819      }
   2820    }
   2821    if (mState == SHUTDOWN) {
   2822      return;
   2823    }
   2824    if (!file) {
   2825      FinishUpdate(NS_SUCCEEDED(rv), aProofOfLock);
   2826      return;
   2827    }
   2828 
   2829    nsAutoCString leaf;
   2830    rv = file->GetNativeLeafName(leaf);
   2831    if (NS_FAILED(rv)) {
   2832      LOG(
   2833          ("CacheIndex::BuildIndex() - GetNativeLeafName() failed! Skipping "
   2834           "file."));
   2835      mDontMarkIndexClean = true;
   2836      continue;
   2837    }
   2838 
   2839    if (!fileExists) {
   2840      LOG(
   2841          ("CacheIndex::BuildIndex() - File returned by the iterator was "
   2842           "removed in the meantime [name=%s]",
   2843           leaf.get()));
   2844      continue;
   2845    }
   2846 
   2847    SHA1Sum::Hash hash;
   2848    rv = CacheFileIOManager::StrToHash(leaf, &hash);
   2849    if (NS_FAILED(rv)) {
   2850      LOG(
   2851          ("CacheIndex::BuildIndex() - Filename is not a hash, removing file. "
   2852           "[name=%s]",
   2853           leaf.get()));
   2854      file->Remove(false);
   2855      continue;
   2856    }
   2857 
   2858    CacheIndexEntry* entry = mIndex.GetEntry(hash);
   2859    if (entry && entry->IsRemoved()) {
   2860      LOG(
   2861          ("CacheIndex::BuildIndex() - Found file that should not exist. "
   2862           "[name=%s]",
   2863           leaf.get()));
   2864      entry->Log();
   2865      MOZ_ASSERT(entry->IsFresh());
   2866      entry = nullptr;
   2867    }
   2868 
   2869 #ifdef DEBUG
   2870    RefPtr<CacheFileHandle> handle;
   2871    CacheFileIOManager::gInstance->mHandles.GetHandle(&hash,
   2872                                                      getter_AddRefs(handle));
   2873 #endif
   2874 
   2875    if (entry) {
   2876      // the entry is up to date
   2877      LOG(
   2878          ("CacheIndex::BuildIndex() - Skipping file because the entry is up to"
   2879           " date. [name=%s]",
   2880           leaf.get()));
   2881      entry->Log();
   2882      MOZ_ASSERT(entry->IsFresh());  // The entry must be from this session
   2883      // there must be an active CacheFile if the entry is not initialized
   2884      MOZ_ASSERT(entry->IsInitialized() || handle);
   2885      continue;
   2886    }
   2887 
   2888    MOZ_ASSERT(!handle);
   2889 
   2890    RefPtr<CacheFileMetadata> meta = new CacheFileMetadata();
   2891    int64_t size = 0;
   2892 
   2893    {
   2894      // Do not do IO under the lock.
   2895      StaticMutexAutoUnlock unlock(sLock);
   2896      rv = meta->SyncReadMetadata(file);
   2897 
   2898      if (NS_SUCCEEDED(rv)) {
   2899        rv = file->GetFileSize(&size);
   2900        if (NS_FAILED(rv)) {
   2901          LOG(
   2902              ("CacheIndex::BuildIndex() - Cannot get filesize of file that was"
   2903               " successfully parsed. [name=%s]",
   2904               leaf.get()));
   2905        }
   2906      }
   2907    }
   2908    if (mState == SHUTDOWN) {
   2909      return;
   2910    }
   2911 
   2912    // Nobody could add the entry while the lock was released since we modify
   2913    // the index only on IO thread and this loop is executed on IO thread too.
   2914    entry = mIndex.GetEntry(hash);
   2915    MOZ_ASSERT(!entry || entry->IsRemoved());
   2916 
   2917    if (NS_FAILED(rv)) {
   2918      LOG(
   2919          ("CacheIndex::BuildIndex() - CacheFileMetadata::SyncReadMetadata() "
   2920           "failed, removing file. [name=%s]",
   2921           leaf.get()));
   2922      file->Remove(false);
   2923    } else {
   2924      CacheIndexEntryAutoManage entryMng(&hash, this, aProofOfLock);
   2925      entry = mIndex.PutEntry(hash);
   2926      if (NS_FAILED(InitEntryFromDiskData(entry, meta, size))) {
   2927        LOG(
   2928            ("CacheIndex::BuildIndex() - CacheFile::InitEntryFromDiskData() "
   2929             "failed, removing file. [name=%s]",
   2930             leaf.get()));
   2931        file->Remove(false);
   2932        entry->MarkRemoved();
   2933      } else {
   2934        LOG(("CacheIndex::BuildIndex() - Added entry to index. [name=%s]",
   2935             leaf.get()));
   2936        entry->Log();
   2937      }
   2938    }
   2939  }
   2940 
   2941  MOZ_ASSERT_UNREACHABLE("We should never get here");
   2942 }
   2943 
   2944 bool CacheIndex::StartUpdatingIndexIfNeeded(
   2945    const StaticMutexAutoLock& aProofOfLock, bool aSwitchingToReadyState) {
   2946  sLock.AssertCurrentThreadOwns();
   2947  // Start updating process when we are in or we are switching to READY state
   2948  // and index needs update, but not during shutdown or when removing all
   2949  // entries.
   2950  if ((mState == READY || aSwitchingToReadyState) && mIndexNeedsUpdate &&
   2951      !mShuttingDown && !mRemovingAll) {
   2952    LOG(("CacheIndex::StartUpdatingIndexIfNeeded() - starting update process"));
   2953    mIndexNeedsUpdate = false;
   2954    StartUpdatingIndex(false, aProofOfLock);
   2955    return true;
   2956  }
   2957 
   2958  return false;
   2959 }
   2960 
   2961 void CacheIndex::StartUpdatingIndex(bool aRebuild,
   2962                                    const StaticMutexAutoLock& aProofOfLock) {
   2963  sLock.AssertCurrentThreadOwns();
   2964  LOG(("CacheIndex::StartUpdatingIndex() [rebuild=%d]", aRebuild));
   2965 
   2966  nsresult rv;
   2967 
   2968  mIndexStats.Log();
   2969 
   2970  ChangeState(aRebuild ? BUILDING : UPDATING, aProofOfLock);
   2971  mDontMarkIndexClean = false;
   2972 
   2973  if (mShuttingDown || mRemovingAll) {
   2974    FinishUpdate(false, aProofOfLock);
   2975    return;
   2976  }
   2977 
   2978  if (IsUpdatePending()) {
   2979    LOG(("CacheIndex::StartUpdatingIndex() - Update is already pending"));
   2980    return;
   2981  }
   2982 
   2983  uint32_t elapsed = (TimeStamp::NowLoRes() - mStartTime).ToMilliseconds();
   2984  if (elapsed < kUpdateIndexStartDelay) {
   2985    LOG(
   2986        ("CacheIndex::StartUpdatingIndex() - %u ms elapsed since startup, "
   2987         "scheduling timer to fire in %u ms.",
   2988         elapsed, kUpdateIndexStartDelay - elapsed));
   2989    rv = ScheduleUpdateTimer(kUpdateIndexStartDelay - elapsed);
   2990    if (NS_SUCCEEDED(rv)) {
   2991      return;
   2992    }
   2993 
   2994    LOG(
   2995        ("CacheIndex::StartUpdatingIndex() - ScheduleUpdateTimer() failed. "
   2996         "Starting update immediately."));
   2997  } else {
   2998    LOG(
   2999        ("CacheIndex::StartUpdatingIndex() - %u ms elapsed since startup, "
   3000         "starting update now.",
   3001         elapsed));
   3002  }
   3003 
   3004  RefPtr<CacheIOThread> ioThread = CacheFileIOManager::IOThread();
   3005  MOZ_ASSERT(ioThread);
   3006 
   3007  // We need to dispatch an event even if we are on IO thread since we need to
   3008  // update the index with the correct priority.
   3009  mUpdateEventPending = true;
   3010  rv = ioThread->Dispatch(this, CacheIOThread::INDEX);
   3011  if (NS_FAILED(rv)) {
   3012    mUpdateEventPending = false;
   3013    NS_WARNING("CacheIndex::StartUpdatingIndex() - Can't dispatch event");
   3014    LOG(("CacheIndex::StartUpdatingIndex() - Can't dispatch event"));
   3015    FinishUpdate(false, aProofOfLock);
   3016  }
   3017 }
   3018 
   3019 void CacheIndex::UpdateIndex(const StaticMutexAutoLock& aProofOfLock) {
   3020  sLock.AssertCurrentThreadOwns();
   3021  LOG(("CacheIndex::UpdateIndex()"));
   3022 
   3023  MOZ_ASSERT(mPendingUpdates.Count() == 0);
   3024  sLock.AssertCurrentThreadOwns();
   3025 
   3026  nsresult rv;
   3027 
   3028  if (!mDirEnumerator) {
   3029    rv = SetupDirectoryEnumerator();
   3030    if (mState == SHUTDOWN) {
   3031      // The index was shut down while we released the lock. FinishUpdate() was
   3032      // already called from Shutdown(), so just simply return here.
   3033      return;
   3034    }
   3035 
   3036    if (NS_FAILED(rv)) {
   3037      FinishUpdate(false, aProofOfLock);
   3038      return;
   3039    }
   3040  }
   3041 
   3042  while (true) {
   3043    if (CacheIOThread::YieldAndRerun()) {
   3044      LOG(
   3045          ("CacheIndex::UpdateIndex() - Breaking loop for higher level "
   3046           "events."));
   3047      mUpdateEventPending = true;
   3048      return;
   3049    }
   3050 
   3051    bool fileExists = false;
   3052    nsCOMPtr<nsIFile> file;
   3053    {
   3054      // Do not do IO under the lock.
   3055      nsCOMPtr<nsIDirectoryEnumerator> dirEnumerator(mDirEnumerator);
   3056      StaticMutexAutoUnlock unlock(sLock);
   3057      rv = dirEnumerator->GetNextFile(getter_AddRefs(file));
   3058 
   3059      if (file) {
   3060        file->Exists(&fileExists);
   3061      }
   3062    }
   3063    if (mState == SHUTDOWN) {
   3064      return;
   3065    }
   3066    if (!file) {
   3067      FinishUpdate(NS_SUCCEEDED(rv), aProofOfLock);
   3068      return;
   3069    }
   3070 
   3071    nsAutoCString leaf;
   3072    rv = file->GetNativeLeafName(leaf);
   3073    if (NS_FAILED(rv)) {
   3074      LOG(
   3075          ("CacheIndex::UpdateIndex() - GetNativeLeafName() failed! Skipping "
   3076           "file."));
   3077      mDontMarkIndexClean = true;
   3078      continue;
   3079    }
   3080 
   3081    if (!fileExists) {
   3082      LOG(
   3083          ("CacheIndex::UpdateIndex() - File returned by the iterator was "
   3084           "removed in the meantime [name=%s]",
   3085           leaf.get()));
   3086      continue;
   3087    }
   3088 
   3089    SHA1Sum::Hash hash;
   3090    rv = CacheFileIOManager::StrToHash(leaf, &hash);
   3091    if (NS_FAILED(rv)) {
   3092      LOG(
   3093          ("CacheIndex::UpdateIndex() - Filename is not a hash, removing file. "
   3094           "[name=%s]",
   3095           leaf.get()));
   3096      file->Remove(false);
   3097      continue;
   3098    }
   3099 
   3100    CacheIndexEntry* entry = mIndex.GetEntry(hash);
   3101    if (entry && entry->IsRemoved()) {
   3102      if (entry->IsFresh()) {
   3103        LOG(
   3104            ("CacheIndex::UpdateIndex() - Found file that should not exist. "
   3105             "[name=%s]",
   3106             leaf.get()));
   3107        entry->Log();
   3108      }
   3109      entry = nullptr;
   3110    }
   3111 
   3112 #ifdef DEBUG
   3113    RefPtr<CacheFileHandle> handle;
   3114    CacheFileIOManager::gInstance->mHandles.GetHandle(&hash,
   3115                                                      getter_AddRefs(handle));
   3116 #endif
   3117 
   3118    if (entry && entry->IsFresh()) {
   3119      // the entry is up to date
   3120      LOG(
   3121          ("CacheIndex::UpdateIndex() - Skipping file because the entry is up "
   3122           " to date. [name=%s]",
   3123           leaf.get()));
   3124      entry->Log();
   3125      // there must be an active CacheFile if the entry is not initialized
   3126      MOZ_ASSERT(entry->IsInitialized() || handle);
   3127      continue;
   3128    }
   3129 
   3130    MOZ_ASSERT(!handle);
   3131 
   3132    if (entry) {
   3133      PRTime lastModifiedTime;
   3134      {
   3135        // Do not do IO under the lock.
   3136        StaticMutexAutoUnlock unlock(sLock);
   3137        rv = file->GetLastModifiedTime(&lastModifiedTime);
   3138      }
   3139      if (mState == SHUTDOWN) {
   3140        return;
   3141      }
   3142      if (NS_FAILED(rv)) {
   3143        LOG(
   3144            ("CacheIndex::UpdateIndex() - Cannot get lastModifiedTime. "
   3145             "[name=%s]",
   3146             leaf.get()));
   3147        // Assume the file is newer than index
   3148      } else {
   3149        if (mIndexTimeStamp > (lastModifiedTime / PR_MSEC_PER_SEC)) {
   3150          LOG(
   3151              ("CacheIndex::UpdateIndex() - Skipping file because of last "
   3152               "modified time. [name=%s, indexTimeStamp=%" PRIu32 ", "
   3153               "lastModifiedTime=%" PRId64 "]",
   3154               leaf.get(), mIndexTimeStamp,
   3155               lastModifiedTime / PR_MSEC_PER_SEC));
   3156 
   3157          CacheIndexEntryAutoManage entryMng(&hash, this, aProofOfLock);
   3158          entry->MarkFresh();
   3159          continue;
   3160        }
   3161      }
   3162    }
   3163 
   3164    RefPtr<CacheFileMetadata> meta = new CacheFileMetadata();
   3165    int64_t size = 0;
   3166 
   3167    {
   3168      // Do not do IO under the lock.
   3169      StaticMutexAutoUnlock unlock(sLock);
   3170      rv = meta->SyncReadMetadata(file);
   3171 
   3172      if (NS_SUCCEEDED(rv)) {
   3173        rv = file->GetFileSize(&size);
   3174        if (NS_FAILED(rv)) {
   3175          LOG(
   3176              ("CacheIndex::UpdateIndex() - Cannot get filesize of file that "
   3177               "was successfully parsed. [name=%s]",
   3178               leaf.get()));
   3179        }
   3180      }
   3181    }
   3182    if (mState == SHUTDOWN) {
   3183      return;
   3184    }
   3185 
   3186    // Nobody could add the entry while the lock was released since we modify
   3187    // the index only on IO thread and this loop is executed on IO thread too.
   3188    entry = mIndex.GetEntry(hash);
   3189    MOZ_ASSERT(!entry || !entry->IsFresh());
   3190 
   3191    CacheIndexEntryAutoManage entryMng(&hash, this, aProofOfLock);
   3192 
   3193    if (NS_FAILED(rv)) {
   3194      LOG(
   3195          ("CacheIndex::UpdateIndex() - CacheFileMetadata::SyncReadMetadata() "
   3196           "failed, removing file. [name=%s]",
   3197           leaf.get()));
   3198    } else {
   3199      entry = mIndex.PutEntry(hash);
   3200      rv = InitEntryFromDiskData(entry, meta, size);
   3201      if (NS_FAILED(rv)) {
   3202        LOG(
   3203            ("CacheIndex::UpdateIndex() - CacheIndex::InitEntryFromDiskData "
   3204             "failed, removing file. [name=%s]",
   3205             leaf.get()));
   3206      }
   3207    }
   3208 
   3209    if (NS_FAILED(rv)) {
   3210      file->Remove(false);
   3211      if (entry) {
   3212        entry->MarkRemoved();
   3213        entry->MarkFresh();
   3214        entry->MarkDirty();
   3215      }
   3216    } else {
   3217      LOG(
   3218          ("CacheIndex::UpdateIndex() - Added/updated entry to/in index. "
   3219           "[name=%s]",
   3220           leaf.get()));
   3221      entry->Log();
   3222    }
   3223  }
   3224 
   3225  MOZ_ASSERT_UNREACHABLE("We should never get here");
   3226 }
   3227 
   3228 void CacheIndex::FinishUpdate(bool aSucceeded,
   3229                              const StaticMutexAutoLock& aProofOfLock) {
   3230  LOG(("CacheIndex::FinishUpdate() [succeeded=%d]", aSucceeded));
   3231 
   3232  MOZ_ASSERT(mState == UPDATING || mState == BUILDING ||
   3233             (!aSucceeded && mState == SHUTDOWN));
   3234 
   3235  if (mDirEnumerator) {
   3236    if (NS_IsMainThread()) {
   3237      LOG(
   3238          ("CacheIndex::FinishUpdate() - posting of PreShutdownInternal failed?"
   3239           " Cannot safely release mDirEnumerator, leaking it!"));
   3240      NS_WARNING(("CacheIndex::FinishUpdate() - Leaking mDirEnumerator!"));
   3241      // This can happen only in case dispatching event to IO thread failed in
   3242      // CacheIndex::PreShutdown().
   3243      mDirEnumerator.forget()
   3244          .leak();  // Leak it since dir enumerator is not threadsafe
   3245    } else {
   3246      mDirEnumerator->Close();
   3247      mDirEnumerator = nullptr;
   3248    }
   3249  }
   3250 
   3251  if (!aSucceeded) {
   3252    mDontMarkIndexClean = true;
   3253  }
   3254 
   3255  if (mState == SHUTDOWN) {
   3256    return;
   3257  }
   3258 
   3259  if (mState == UPDATING && aSucceeded) {
   3260    // If we've iterated over all entries successfully then all entries that
   3261    // really exist on the disk are now marked as fresh. All non-fresh entries
   3262    // don't exist anymore and must be removed from the index.
   3263    RemoveNonFreshEntries(aProofOfLock);
   3264  }
   3265 
   3266  // Make sure we won't start update. If the build or update failed, there is no
   3267  // reason to believe that it will succeed next time.
   3268  mIndexNeedsUpdate = false;
   3269 
   3270  ChangeState(READY, aProofOfLock);
   3271  mLastDumpTime = TimeStamp::NowLoRes();  // Do not dump new index immediately
   3272 }
   3273 
   3274 void CacheIndex::RemoveNonFreshEntries(
   3275    const StaticMutexAutoLock& aProofOfLock) {
   3276  sLock.AssertCurrentThreadOwns();
   3277  for (auto iter = mIndex.Iter(); !iter.Done(); iter.Next()) {
   3278    CacheIndexEntry* entry = iter.Get();
   3279    if (entry->IsFresh()) {
   3280      continue;
   3281    }
   3282 
   3283    LOG(
   3284        ("CacheIndex::RemoveNonFreshEntries() - Removing entry. "
   3285         "[hash=%08x%08x%08x%08x%08x]",
   3286         LOGSHA1(entry->Hash())));
   3287 
   3288    {
   3289      CacheIndexEntryAutoManage emng(entry->Hash(), this, aProofOfLock);
   3290      emng.DoNotSearchInIndex();
   3291    }
   3292 
   3293    iter.Remove();
   3294  }
   3295 }
   3296 
   3297 // static
   3298 char const* CacheIndex::StateString(EState aState) {
   3299  switch (aState) {
   3300    case INITIAL:
   3301      return "INITIAL";
   3302    case READING:
   3303      return "READING";
   3304    case WRITING:
   3305      return "WRITING";
   3306    case BUILDING:
   3307      return "BUILDING";
   3308    case UPDATING:
   3309      return "UPDATING";
   3310    case READY:
   3311      return "READY";
   3312    case SHUTDOWN:
   3313      return "SHUTDOWN";
   3314  }
   3315 
   3316  MOZ_ASSERT(false, "Unexpected state!");
   3317  return "?";
   3318 }
   3319 
   3320 void CacheIndex::ChangeState(EState aNewState,
   3321                             const StaticMutexAutoLock& aProofOfLock) {
   3322  sLock.AssertCurrentThreadOwns();
   3323  LOG(("CacheIndex::ChangeState() changing state %s -> %s", StateString(mState),
   3324       StateString(aNewState)));
   3325 
   3326  // All pending updates should be processed before changing state
   3327  MOZ_ASSERT(mPendingUpdates.Count() == 0);
   3328 
   3329  // PreShutdownInternal() should change the state to READY from every state. It
   3330  // may go through different states, but once we are in READY state the only
   3331  // possible transition is to SHUTDOWN state.
   3332  MOZ_ASSERT(!mShuttingDown || mState != READY || aNewState == SHUTDOWN);
   3333 
   3334  // Start updating process when switching to READY state if needed
   3335  if (aNewState == READY && StartUpdatingIndexIfNeeded(aProofOfLock, true)) {
   3336    return;
   3337  }
   3338 
   3339  // Try to evict entries over limit everytime we're leaving state READING,
   3340  // BUILDING or UPDATING, but not during shutdown or when removing all
   3341  // entries.
   3342  if (!mShuttingDown && !mRemovingAll && aNewState != SHUTDOWN &&
   3343      (mState == READING || mState == BUILDING || mState == UPDATING)) {
   3344    CacheFileIOManager::EvictIfOverLimit();
   3345  }
   3346 
   3347  mState = aNewState;
   3348 
   3349  if (mState != SHUTDOWN) {
   3350    CacheFileIOManager::CacheIndexStateChanged();
   3351  }
   3352 
   3353  NotifyAsyncGetDiskConsumptionCallbacks();
   3354 }
   3355 
   3356 void CacheIndex::NotifyAsyncGetDiskConsumptionCallbacks() {
   3357  if ((mState == READY || mState == WRITING) &&
   3358      !mAsyncGetDiskConsumptionBlocked && mDiskConsumptionObservers.Length()) {
   3359    for (uint32_t i = 0; i < mDiskConsumptionObservers.Length(); ++i) {
   3360      DiskConsumptionObserver* o = mDiskConsumptionObservers[i];
   3361      // Safe to call under the lock.  We always post to the main thread.
   3362      o->OnDiskConsumption(mIndexStats.Size() << 10);
   3363    }
   3364 
   3365    mDiskConsumptionObservers.Clear();
   3366  }
   3367 }
   3368 
   3369 void CacheIndex::AllocBuffer() {
   3370  switch (mState) {
   3371    case WRITING:
   3372      mRWBufSize = sizeof(CacheIndexHeader) + sizeof(CacheHash::Hash32_t) +
   3373                   mProcessEntries * sizeof(CacheIndexRecord);
   3374      if (mRWBufSize > kMaxBufSize) {
   3375        mRWBufSize = kMaxBufSize;
   3376      }
   3377      break;
   3378    case READING:
   3379      mRWBufSize = kMaxBufSize;
   3380      break;
   3381    default:
   3382      MOZ_ASSERT(false, "Unexpected state!");
   3383  }
   3384 
   3385  mRWBuf = static_cast<char*>(moz_xmalloc(mRWBufSize));
   3386 }
   3387 
   3388 void CacheIndex::ReleaseBuffer() {
   3389  sLock.AssertCurrentThreadOwns();
   3390 
   3391  if (!mRWBuf || mRWPending) {
   3392    return;
   3393  }
   3394 
   3395  LOG(("CacheIndex::ReleaseBuffer() releasing buffer"));
   3396 
   3397  free(mRWBuf);
   3398  mRWBuf = nullptr;
   3399  mRWBufSize = 0;
   3400  mRWBufPos = 0;
   3401 }
   3402 
   3403 void CacheIndex::FrecencyStorage::AppendRecord(
   3404    CacheIndexRecordWrapper* aRecord, const StaticMutexAutoLock& aProofOfLock) {
   3405  sLock.AssertCurrentThreadOwns();
   3406  LOG(
   3407      ("CacheIndex::FrecencyStorage::AppendRecord() [record=%p, "
   3408       "hash=%08x%08x%08x"
   3409       "%08x%08x]",
   3410       aRecord, LOGSHA1(aRecord->Get()->mHash)));
   3411  MOZ_DIAGNOSTIC_ASSERT(!mRecs.Contains(aRecord));
   3412  mRecs.PutEntry(aRecord);
   3413 }
   3414 
   3415 void CacheIndex::FrecencyStorage::RemoveRecord(
   3416    CacheIndexRecordWrapper* aRecord, const StaticMutexAutoLock& aProofOfLock) {
   3417  sLock.AssertCurrentThreadOwns();
   3418  LOG(("CacheIndex::FrecencyStorage::RemoveRecord() [record=%p]", aRecord));
   3419  MOZ_RELEASE_ASSERT(mRecs.Contains(aRecord));
   3420  mRecs.RemoveEntry(aRecord);
   3421 }
   3422 
   3423 void CacheIndex::FrecencyStorage::ReplaceRecord(
   3424    CacheIndexRecordWrapper* aOldRecord, CacheIndexRecordWrapper* aNewRecord,
   3425    const StaticMutexAutoLock& aProofOfLock) {
   3426  sLock.AssertCurrentThreadOwns();
   3427  LOG(
   3428      ("CacheIndex::FrecencyStorage::ReplaceRecord() [oldRecord=%p, "
   3429       "newRecord=%p]",
   3430       aOldRecord, aNewRecord));
   3431 
   3432  MOZ_RELEASE_ASSERT(mRecs.Contains(aOldRecord),
   3433                     "Tried to replace a record that doesn't exist");
   3434  mRecs.RemoveEntry(aOldRecord);
   3435  mRecs.PutEntry(aNewRecord);
   3436 }
   3437 
   3438 // static
   3439 CacheIndex::EvictionSortedSnapshot CacheIndex::GetSortedSnapshotForEviction() {
   3440  StaticMutexAutoLock lock(sLock);
   3441  RefPtr<CacheIndex> index = gInstance;
   3442  return index->mFrecencyStorage.GetSortedSnapshotForEviction();
   3443 }
   3444 
   3445 CacheIndex::EvictionSortedSnapshot
   3446 CacheIndex::FrecencyStorage::GetSortedSnapshotForEviction() {
   3447  CacheIndex::EvictionSortedSnapshot snapshot;
   3448  snapshot.SetCapacity(mRecs.Count());
   3449  for (const auto& item : mRecs) {
   3450    snapshot.AppendElement(item.GetKey());
   3451  }
   3452  snapshot.Sort(FrecencyComparator());
   3453  return snapshot;
   3454 }
   3455 
   3456 bool CacheIndex::FrecencyStorage::RecordExistedUnlocked(
   3457    CacheIndexRecordWrapper* aRecord) {
   3458  return mRecs.Contains(aRecord);
   3459 }
   3460 
   3461 void CacheIndex::AddRecordToIterators(CacheIndexRecordWrapper* aRecord,
   3462                                      const StaticMutexAutoLock& aProofOfLock) {
   3463  sLock.AssertCurrentThreadOwns();
   3464  for (uint32_t i = 0; i < mIterators.Length(); ++i) {
   3465    // Add a new record only when iterator is supposed to be updated.
   3466    if (mIterators[i]->ShouldBeNewAdded()) {
   3467      mIterators[i]->AddRecord(aRecord, aProofOfLock);
   3468    }
   3469  }
   3470 }
   3471 
   3472 void CacheIndex::RemoveRecordFromIterators(
   3473    CacheIndexRecordWrapper* aRecord, const StaticMutexAutoLock& aProofOfLock) {
   3474  sLock.AssertCurrentThreadOwns();
   3475  for (uint32_t i = 0; i < mIterators.Length(); ++i) {
   3476    // Remove the record from iterator always, it makes no sence to return
   3477    // non-existing entries. Also the pointer to the record is no longer valid
   3478    // once the entry is removed from index.
   3479    mIterators[i]->RemoveRecord(aRecord, aProofOfLock);
   3480  }
   3481 }
   3482 
   3483 void CacheIndex::ReplaceRecordInIterators(
   3484    CacheIndexRecordWrapper* aOldRecord, CacheIndexRecordWrapper* aNewRecord,
   3485    const StaticMutexAutoLock& aProofOfLock) {
   3486  sLock.AssertCurrentThreadOwns();
   3487  for (uint32_t i = 0; i < mIterators.Length(); ++i) {
   3488    // We have to replace the record always since the pointer is no longer
   3489    // valid after this point. NOTE: Replacing the record doesn't mean that
   3490    // a new entry was added, it just means that the data in the entry was
   3491    // changed (e.g. a file size) and we had to track this change in
   3492    // mPendingUpdates since mIndex was read-only.
   3493    mIterators[i]->ReplaceRecord(aOldRecord, aNewRecord, aProofOfLock);
   3494  }
   3495 }
   3496 
   3497 nsresult CacheIndex::Run() {
   3498  LOG(("CacheIndex::Run()"));
   3499 
   3500  StaticMutexAutoLock lock(sLock);
   3501 
   3502  if (!IsIndexUsable()) {
   3503    return NS_ERROR_NOT_AVAILABLE;
   3504  }
   3505 
   3506  if (mState == READY && mShuttingDown) {
   3507    return NS_OK;
   3508  }
   3509 
   3510  mUpdateEventPending = false;
   3511 
   3512  switch (mState) {
   3513    case BUILDING:
   3514      BuildIndex(lock);
   3515      break;
   3516    case UPDATING:
   3517      UpdateIndex(lock);
   3518      break;
   3519    default:
   3520      LOG(("CacheIndex::Run() - Update/Build was canceled"));
   3521  }
   3522 
   3523  return NS_OK;
   3524 }
   3525 
   3526 void CacheIndex::OnFileOpenedInternal(FileOpenHelper* aOpener,
   3527                                      CacheFileHandle* aHandle,
   3528                                      nsresult aResult,
   3529                                      const StaticMutexAutoLock& aProofOfLock) {
   3530  sLock.AssertCurrentThreadOwns();
   3531  LOG(
   3532      ("CacheIndex::OnFileOpenedInternal() [opener=%p, handle=%p, "
   3533       "result=0x%08" PRIx32 "]",
   3534       aOpener, aHandle, static_cast<uint32_t>(aResult)));
   3535  MOZ_ASSERT(CacheFileIOManager::IsOnIOThread());
   3536 
   3537  nsresult rv;
   3538 
   3539  MOZ_RELEASE_ASSERT(IsIndexUsable());
   3540 
   3541  if (mState == READY && mShuttingDown) {
   3542    return;
   3543  }
   3544 
   3545  switch (mState) {
   3546    case WRITING:
   3547      MOZ_ASSERT(aOpener == mIndexFileOpener);
   3548      mIndexFileOpener = nullptr;
   3549 
   3550      if (NS_FAILED(aResult)) {
   3551        LOG(
   3552            ("CacheIndex::OnFileOpenedInternal() - Can't open index file for "
   3553             "writing [rv=0x%08" PRIx32 "]",
   3554             static_cast<uint32_t>(aResult)));
   3555        FinishWrite(false, aProofOfLock);
   3556      } else {
   3557        mIndexHandle = aHandle;
   3558        WriteRecords(aProofOfLock);
   3559      }
   3560      break;
   3561    case READING:
   3562      if (aOpener == mIndexFileOpener) {
   3563        mIndexFileOpener = nullptr;
   3564 
   3565        if (NS_SUCCEEDED(aResult)) {
   3566          if (aHandle->FileSize() == 0) {
   3567            FinishRead(false, aProofOfLock);
   3568            CacheFileIOManager::DoomFile(aHandle, nullptr);
   3569            break;
   3570          }
   3571          mIndexHandle = aHandle;
   3572        } else {
   3573          FinishRead(false, aProofOfLock);
   3574          break;
   3575        }
   3576      } else if (aOpener == mJournalFileOpener) {
   3577        mJournalFileOpener = nullptr;
   3578        mJournalHandle = aHandle;
   3579      } else if (aOpener == mTmpFileOpener) {
   3580        mTmpFileOpener = nullptr;
   3581        mTmpHandle = aHandle;
   3582      } else {
   3583        MOZ_ASSERT(false, "Unexpected state!");
   3584      }
   3585 
   3586      if (mIndexFileOpener || mJournalFileOpener || mTmpFileOpener) {
   3587        // Some opener still didn't finish
   3588        break;
   3589      }
   3590 
   3591      // We fail and cancel all other openers when we opening index file fails.
   3592      MOZ_ASSERT(mIndexHandle);
   3593 
   3594      if (mTmpHandle) {
   3595        CacheFileIOManager::DoomFile(mTmpHandle, nullptr);
   3596        mTmpHandle = nullptr;
   3597 
   3598        if (mJournalHandle) {  // this shouldn't normally happen
   3599          LOG(
   3600              ("CacheIndex::OnFileOpenedInternal() - Unexpected state, all "
   3601               "files [%s, %s, %s] should never exist. Removing whole index.",
   3602               INDEX_NAME, JOURNAL_NAME, TEMP_INDEX_NAME));
   3603          FinishRead(false, aProofOfLock);
   3604          break;
   3605        }
   3606      }
   3607 
   3608      if (mJournalHandle) {
   3609        // Rename journal to make sure we update index on next start in case
   3610        // firefox crashes
   3611        rv = CacheFileIOManager::RenameFile(
   3612            mJournalHandle, nsLiteralCString(TEMP_INDEX_NAME), this);
   3613        if (NS_FAILED(rv)) {
   3614          LOG(
   3615              ("CacheIndex::OnFileOpenedInternal() - CacheFileIOManager::"
   3616               "RenameFile() failed synchronously [rv=0x%08" PRIx32 "]",
   3617               static_cast<uint32_t>(rv)));
   3618          FinishRead(false, aProofOfLock);
   3619          break;
   3620        }
   3621      } else {
   3622        StartReadingIndex(aProofOfLock);
   3623      }
   3624 
   3625      break;
   3626    default:
   3627      MOZ_ASSERT(false, "Unexpected state!");
   3628  }
   3629 }
   3630 
   3631 nsresult CacheIndex::OnFileOpened(CacheFileHandle* aHandle, nsresult aResult) {
   3632  MOZ_CRASH("CacheIndex::OnFileOpened should not be called!");
   3633  return NS_ERROR_UNEXPECTED;
   3634 }
   3635 
   3636 nsresult CacheIndex::OnDataWritten(CacheFileHandle* aHandle, const char* aBuf,
   3637                                   nsresult aResult) {
   3638  LOG(("CacheIndex::OnDataWritten() [handle=%p, result=0x%08" PRIx32 "]",
   3639       aHandle, static_cast<uint32_t>(aResult)));
   3640 
   3641  MOZ_ASSERT(CacheFileIOManager::IsOnIOThread());
   3642 
   3643  nsresult rv;
   3644 
   3645  StaticMutexAutoLock lock(sLock);
   3646 
   3647  MOZ_RELEASE_ASSERT(IsIndexUsable());
   3648  MOZ_RELEASE_ASSERT(mRWPending);
   3649  mRWPending = false;
   3650 
   3651  if (mState == READY && mShuttingDown) {
   3652    return NS_OK;
   3653  }
   3654 
   3655  switch (mState) {
   3656    case WRITING:
   3657      MOZ_ASSERT(mIndexHandle == aHandle);
   3658 
   3659      if (NS_FAILED(aResult)) {
   3660        FinishWrite(false, lock);
   3661      } else {
   3662        if (mSkipEntries == mProcessEntries) {
   3663          rv = CacheFileIOManager::RenameFile(
   3664              mIndexHandle, nsLiteralCString(INDEX_NAME), this);
   3665          if (NS_FAILED(rv)) {
   3666            LOG(
   3667                ("CacheIndex::OnDataWritten() - CacheFileIOManager::"
   3668                 "RenameFile() failed synchronously [rv=0x%08" PRIx32 "]",
   3669                 static_cast<uint32_t>(rv)));
   3670            FinishWrite(false, lock);
   3671          }
   3672        } else {
   3673          WriteRecords(lock);
   3674        }
   3675      }
   3676      break;
   3677    default:
   3678      // Writing was canceled.
   3679      LOG(
   3680          ("CacheIndex::OnDataWritten() - ignoring notification since the "
   3681           "operation was previously canceled [state=%d]",
   3682           mState));
   3683      ReleaseBuffer();
   3684  }
   3685 
   3686  return NS_OK;
   3687 }
   3688 
   3689 nsresult CacheIndex::OnDataRead(CacheFileHandle* aHandle, char* aBuf,
   3690                                nsresult aResult) {
   3691  LOG(("CacheIndex::OnDataRead() [handle=%p, result=0x%08" PRIx32 "]", aHandle,
   3692       static_cast<uint32_t>(aResult)));
   3693 
   3694  MOZ_ASSERT(CacheFileIOManager::IsOnIOThread());
   3695 
   3696  StaticMutexAutoLock lock(sLock);
   3697 
   3698  MOZ_RELEASE_ASSERT(IsIndexUsable());
   3699  MOZ_RELEASE_ASSERT(mRWPending);
   3700  mRWPending = false;
   3701 
   3702  switch (mState) {
   3703    case READING:
   3704      MOZ_ASSERT(mIndexHandle == aHandle || mJournalHandle == aHandle);
   3705 
   3706      if (NS_FAILED(aResult)) {
   3707        FinishRead(false, lock);
   3708      } else {
   3709        if (!mIndexOnDiskIsValid) {
   3710          ParseRecords(lock);
   3711        } else {
   3712          ParseJournal(lock);
   3713        }
   3714      }
   3715      break;
   3716    default:
   3717      // Reading was canceled.
   3718      LOG(
   3719          ("CacheIndex::OnDataRead() - ignoring notification since the "
   3720           "operation was previously canceled [state=%d]",
   3721           mState));
   3722      ReleaseBuffer();
   3723  }
   3724 
   3725  return NS_OK;
   3726 }
   3727 
   3728 nsresult CacheIndex::OnFileDoomed(CacheFileHandle* aHandle, nsresult aResult) {
   3729  MOZ_CRASH("CacheIndex::OnFileDoomed should not be called!");
   3730  return NS_ERROR_UNEXPECTED;
   3731 }
   3732 
   3733 nsresult CacheIndex::OnEOFSet(CacheFileHandle* aHandle, nsresult aResult) {
   3734  MOZ_CRASH("CacheIndex::OnEOFSet should not be called!");
   3735  return NS_ERROR_UNEXPECTED;
   3736 }
   3737 
   3738 nsresult CacheIndex::OnFileRenamed(CacheFileHandle* aHandle, nsresult aResult) {
   3739  LOG(("CacheIndex::OnFileRenamed() [handle=%p, result=0x%08" PRIx32 "]",
   3740       aHandle, static_cast<uint32_t>(aResult)));
   3741 
   3742  MOZ_ASSERT(CacheFileIOManager::IsOnIOThread());
   3743 
   3744  StaticMutexAutoLock lock(sLock);
   3745 
   3746  MOZ_RELEASE_ASSERT(IsIndexUsable());
   3747 
   3748  if (mState == READY && mShuttingDown) {
   3749    return NS_OK;
   3750  }
   3751 
   3752  switch (mState) {
   3753    case WRITING:
   3754      // This is a result of renaming the new index written to tmpfile to index
   3755      // file. This is the last step when writing the index and the whole
   3756      // writing process is successful iff renaming was successful.
   3757 
   3758      if (mIndexHandle != aHandle) {
   3759        LOG(
   3760            ("CacheIndex::OnFileRenamed() - ignoring notification since it "
   3761             "belongs to previously canceled operation [state=%d]",
   3762             mState));
   3763        break;
   3764      }
   3765 
   3766      FinishWrite(NS_SUCCEEDED(aResult), lock);
   3767      break;
   3768    case READING:
   3769      // This is a result of renaming journal file to tmpfile. It is renamed
   3770      // before we start reading index and journal file and it should normally
   3771      // succeed. If it fails give up reading of index.
   3772 
   3773      if (mJournalHandle != aHandle) {
   3774        LOG(
   3775            ("CacheIndex::OnFileRenamed() - ignoring notification since it "
   3776             "belongs to previously canceled operation [state=%d]",
   3777             mState));
   3778        break;
   3779      }
   3780 
   3781      if (NS_FAILED(aResult)) {
   3782        FinishRead(false, lock);
   3783      } else {
   3784        StartReadingIndex(lock);
   3785      }
   3786      break;
   3787    default:
   3788      // Reading/writing was canceled.
   3789      LOG(
   3790          ("CacheIndex::OnFileRenamed() - ignoring notification since the "
   3791           "operation was previously canceled [state=%d]",
   3792           mState));
   3793  }
   3794 
   3795  return NS_OK;
   3796 }
   3797 
   3798 // Memory reporting
   3799 
   3800 size_t CacheIndex::SizeOfExcludingThisInternal(
   3801    mozilla::MallocSizeOf mallocSizeOf) const {
   3802  sLock.AssertCurrentThreadOwns();
   3803 
   3804  size_t n = 0;
   3805 
   3806  // mIndexHandle and mJournalHandle are reported via SizeOfHandlesRunnable
   3807  // in CacheFileIOManager::SizeOfExcludingThisInternal as part of special
   3808  // handles array.
   3809 
   3810  // mCacheDirectory is an nsIFile which we don't have reporting for.
   3811 
   3812  // mUpdateTimer is an nsITimer which we don't have reporting for.
   3813 
   3814  n += mallocSizeOf(mRWBuf);
   3815  n += mallocSizeOf(mRWHash);
   3816 
   3817  n += mIndex.SizeOfExcludingThis(mallocSizeOf);
   3818  n += mPendingUpdates.SizeOfExcludingThis(mallocSizeOf);
   3819  n += mTmpJournal.SizeOfExcludingThis(mallocSizeOf);
   3820 
   3821  // mFrecencyStorage items are reported by mIndex/mPendingUpdates
   3822  n += mFrecencyStorage.mRecs.ShallowSizeOfExcludingThis(mallocSizeOf);
   3823  n += mDiskConsumptionObservers.ShallowSizeOfExcludingThis(mallocSizeOf);
   3824 
   3825  return n;
   3826 }
   3827 
   3828 // static
   3829 size_t CacheIndex::SizeOfExcludingThis(mozilla::MallocSizeOf mallocSizeOf) {
   3830  StaticMutexAutoLock lock(sLock);
   3831 
   3832  if (!gInstance) return 0;
   3833 
   3834  return gInstance->SizeOfExcludingThisInternal(mallocSizeOf);
   3835 }
   3836 
   3837 // static
   3838 size_t CacheIndex::SizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf) {
   3839  StaticMutexAutoLock lock(sLock);
   3840 
   3841  return mallocSizeOf(gInstance) +
   3842         (gInstance ? gInstance->SizeOfExcludingThisInternal(mallocSizeOf) : 0);
   3843 }
   3844 
   3845 // static
   3846 void CacheIndex::UpdateTotalBytesWritten(uint32_t aBytesWritten) {
   3847  StaticMutexAutoLock lock(sLock);
   3848 
   3849  RefPtr<CacheIndex> index = gInstance;
   3850  if (!index) {
   3851    return;
   3852  }
   3853 
   3854  index->mTotalBytesWritten += aBytesWritten;
   3855 
   3856  // Do telemetry report if enough data has been written and the index is
   3857  // in READY state. The data is available also in WRITING state, but we would
   3858  // need to deal with pending updates.
   3859  if (index->mTotalBytesWritten >= kTelemetryReportBytesLimit &&
   3860      index->mState == READY && !index->mIndexNeedsUpdate &&
   3861      !index->mShuttingDown) {
   3862    index->DoTelemetryReport();
   3863    index->mTotalBytesWritten = 0;
   3864    return;
   3865  }
   3866 }
   3867 
   3868 void CacheIndex::DoTelemetryReport() {
   3869  static const nsLiteralCString
   3870      contentTypeNames[nsICacheEntry::CONTENT_TYPE_LAST] = {
   3871          "UNKNOWN"_ns, "OTHER"_ns,      "JAVASCRIPT"_ns, "IMAGE"_ns,
   3872          "MEDIA"_ns,   "STYLESHEET"_ns, "WASM"_ns,       "DICTIONARY"_ns};
   3873 
   3874  for (uint32_t i = 0; i < nsICacheEntry::CONTENT_TYPE_LAST; ++i) {
   3875    if (mIndexStats.Size() > 0) {
   3876      glean::network::cache_size_share.Get(contentTypeNames[i])
   3877          .AccumulateSingleSample(
   3878              round(static_cast<double>(mIndexStats.SizeByType(i)) * 100.0 /
   3879                    static_cast<double>(mIndexStats.Size())));
   3880    }
   3881 
   3882    if (mIndexStats.Count() > 0) {
   3883      glean::network::cache_entry_count_share.Get(contentTypeNames[i])
   3884          .AccumulateSingleSample(
   3885              round(static_cast<double>(mIndexStats.CountByType(i)) * 100.0 /
   3886                    static_cast<double>(mIndexStats.Count())));
   3887    }
   3888  }
   3889 
   3890  nsCString probeKey;
   3891  if (CacheObserver::SmartCacheSizeEnabled()) {
   3892    probeKey = "SMARTSIZE"_ns;
   3893  } else {
   3894    probeKey = "USERDEFINEDSIZE"_ns;
   3895  }
   3896  glean::network::cache_entry_count.Get(probeKey).AccumulateSingleSample(
   3897      mIndexStats.Count());
   3898  glean::network::cache_size.Get(probeKey).Accumulate(mIndexStats.Size() >> 10);
   3899 }
   3900 
   3901 // static
   3902 void CacheIndex::OnAsyncEviction(bool aEvicting) {
   3903  StaticMutexAutoLock lock(sLock);
   3904 
   3905  RefPtr<CacheIndex> index = gInstance;
   3906  if (!index) {
   3907    return;
   3908  }
   3909 
   3910  index->mAsyncGetDiskConsumptionBlocked = aEvicting;
   3911  if (!aEvicting) {
   3912    index->NotifyAsyncGetDiskConsumptionCallbacks();
   3913  }
   3914 }
   3915 }  // namespace mozilla::net