netwerk/cache2/CacheIndex.cpp

Thu, 22 Jan 2015 13:21:57 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Thu, 22 Jan 2015 13:21:57 +0100
branch
TOR_BUG_9701
changeset 15
b8a032363ba2
permissions
-rw-r--r--

Incorporate requested changes from Mozilla in review:
https://bugzilla.mozilla.org/show_bug.cgi?id=1123480#c6

     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/. */
     5 #include "CacheIndex.h"
     7 #include "CacheLog.h"
     8 #include "CacheFileIOManager.h"
     9 #include "CacheFileMetadata.h"
    10 #include "CacheIndexIterator.h"
    11 #include "CacheIndexContextIterator.h"
    12 #include "nsThreadUtils.h"
    13 #include "nsISimpleEnumerator.h"
    14 #include "nsIDirectoryEnumerator.h"
    15 #include "nsISizeOf.h"
    16 #include "nsPrintfCString.h"
    17 #include "mozilla/DebugOnly.h"
    18 #include "prinrval.h"
    19 #include "nsIFile.h"
    20 #include "nsITimer.h"
    21 #include "mozilla/AutoRestore.h"
    22 #include <algorithm>
    25 #define kMinUnwrittenChanges   300
    26 #define kMinDumpInterval       20000 // in milliseconds
    27 #define kMaxBufSize            16384
    28 #define kIndexVersion          0x00000001
    29 #define kUpdateIndexStartDelay 50000 // in milliseconds
    31 const char kIndexName[]     = "index";
    32 const char kTempIndexName[] = "index.tmp";
    33 const char kJournalName[]   = "index.log";
    35 namespace mozilla {
    36 namespace net {
    38 /**
    39  * This helper class is responsible for keeping CacheIndex::mIndexStats,
    40  * CacheIndex::mFrecencyArray and CacheIndex::mExpirationArray up to date.
    41  */
    42 class CacheIndexEntryAutoManage
    43 {
    44 public:
    45   CacheIndexEntryAutoManage(const SHA1Sum::Hash *aHash, CacheIndex *aIndex)
    46     : mIndex(aIndex)
    47     , mOldRecord(nullptr)
    48     , mOldFrecency(0)
    49     , mOldExpirationTime(nsICacheEntry::NO_EXPIRATION_TIME)
    50     , mDoNotSearchInIndex(false)
    51     , mDoNotSearchInUpdates(false)
    52   {
    53     mIndex->AssertOwnsLock();
    55     mHash = aHash;
    56     CacheIndexEntry *entry = FindEntry();
    57     mIndex->mIndexStats.BeforeChange(entry);
    58     if (entry && entry->IsInitialized() && !entry->IsRemoved()) {
    59       mOldRecord = entry->mRec;
    60       mOldFrecency = entry->mRec->mFrecency;
    61       mOldExpirationTime = entry->mRec->mExpirationTime;
    62     }
    63   }
    65   ~CacheIndexEntryAutoManage()
    66   {
    67     mIndex->AssertOwnsLock();
    69     CacheIndexEntry *entry = FindEntry();
    70     mIndex->mIndexStats.AfterChange(entry);
    71     if (!entry || !entry->IsInitialized() || entry->IsRemoved()) {
    72       entry = nullptr;
    73     }
    75     if (entry && !mOldRecord) {
    76       mIndex->InsertRecordToFrecencyArray(entry->mRec);
    77       mIndex->InsertRecordToExpirationArray(entry->mRec);
    78       mIndex->AddRecordToIterators(entry->mRec);
    79     } else if (!entry && mOldRecord) {
    80       mIndex->RemoveRecordFromFrecencyArray(mOldRecord);
    81       mIndex->RemoveRecordFromExpirationArray(mOldRecord);
    82       mIndex->RemoveRecordFromIterators(mOldRecord);
    83     } else if (entry && mOldRecord) {
    84       bool replaceFrecency = false;
    85       bool replaceExpiration = false;
    87       if (entry->mRec != mOldRecord) {
    88         // record has a different address, we have to replace it
    89         replaceFrecency = replaceExpiration = true;
    90         mIndex->ReplaceRecordInIterators(mOldRecord, entry->mRec);
    91       } else {
    92         if (entry->mRec->mFrecency == 0 &&
    93             entry->mRec->mExpirationTime == nsICacheEntry::NO_EXPIRATION_TIME) {
    94           // This is a special case when we want to make sure that the entry is
    95           // placed at the end of the lists even when the values didn't change.
    96           replaceFrecency = replaceExpiration = true;
    97         } else {
    98           if (entry->mRec->mFrecency != mOldFrecency) {
    99             replaceFrecency = true;
   100           }
   101           if (entry->mRec->mExpirationTime != mOldExpirationTime) {
   102             replaceExpiration = true;
   103           }
   104         }
   105       }
   107       if (replaceFrecency) {
   108         mIndex->RemoveRecordFromFrecencyArray(mOldRecord);
   109         mIndex->InsertRecordToFrecencyArray(entry->mRec);
   110       }
   111       if (replaceExpiration) {
   112         mIndex->RemoveRecordFromExpirationArray(mOldRecord);
   113         mIndex->InsertRecordToExpirationArray(entry->mRec);
   114       }
   115     } else {
   116       // both entries were removed or not initialized, do nothing
   117     }
   118   }
   120   // We cannot rely on nsTHashtable::GetEntry() in case we are enumerating the
   121   // entries and returning PL_DHASH_REMOVE. Destructor is called before the
   122   // entry is removed. Caller must call one of following methods to skip
   123   // lookup in the hashtable.
   124   void DoNotSearchInIndex()   { mDoNotSearchInIndex = true; }
   125   void DoNotSearchInUpdates() { mDoNotSearchInUpdates = true; }
   127 private:
   128   CacheIndexEntry * FindEntry()
   129   {
   130     CacheIndexEntry *entry = nullptr;
   132     switch (mIndex->mState) {
   133       case CacheIndex::READING:
   134       case CacheIndex::WRITING:
   135         if (!mDoNotSearchInUpdates) {
   136           entry = mIndex->mPendingUpdates.GetEntry(*mHash);
   137         }
   138         // no break
   139       case CacheIndex::BUILDING:
   140       case CacheIndex::UPDATING:
   141       case CacheIndex::READY:
   142         if (!entry && !mDoNotSearchInIndex) {
   143           entry = mIndex->mIndex.GetEntry(*mHash);
   144         }
   145         break;
   146       case CacheIndex::INITIAL:
   147       case CacheIndex::SHUTDOWN:
   148       default:
   149         MOZ_ASSERT(false, "Unexpected state!");
   150     }
   152     return entry;
   153   }
   155   const SHA1Sum::Hash *mHash;
   156   nsRefPtr<CacheIndex> mIndex;
   157   CacheIndexRecord    *mOldRecord;
   158   uint32_t             mOldFrecency;
   159   uint32_t             mOldExpirationTime;
   160   bool                 mDoNotSearchInIndex;
   161   bool                 mDoNotSearchInUpdates;
   162 };
   164 class FileOpenHelper : public CacheFileIOListener
   165 {
   166 public:
   167   NS_DECL_THREADSAFE_ISUPPORTS
   169   FileOpenHelper(CacheIndex* aIndex)
   170     : mIndex(aIndex)
   171     , mCanceled(false)
   172   {}
   174   virtual ~FileOpenHelper() {}
   176   void Cancel() {
   177     mIndex->AssertOwnsLock();
   178     mCanceled = true;
   179   }
   181 private:
   182   NS_IMETHOD OnFileOpened(CacheFileHandle *aHandle, nsresult aResult);
   183   NS_IMETHOD OnDataWritten(CacheFileHandle *aHandle, const char *aBuf,
   184                            nsresult aResult) {
   185     MOZ_CRASH("FileOpenHelper::OnDataWritten should not be called!");
   186     return NS_ERROR_UNEXPECTED;
   187   }
   188   NS_IMETHOD OnDataRead(CacheFileHandle *aHandle, char *aBuf,
   189                         nsresult aResult) {
   190     MOZ_CRASH("FileOpenHelper::OnDataRead should not be called!");
   191     return NS_ERROR_UNEXPECTED;
   192   }
   193   NS_IMETHOD OnFileDoomed(CacheFileHandle *aHandle, nsresult aResult) {
   194     MOZ_CRASH("FileOpenHelper::OnFileDoomed should not be called!");
   195     return NS_ERROR_UNEXPECTED;
   196   }
   197   NS_IMETHOD OnEOFSet(CacheFileHandle *aHandle, nsresult aResult) {
   198     MOZ_CRASH("FileOpenHelper::OnEOFSet should not be called!");
   199     return NS_ERROR_UNEXPECTED;
   200   }
   201   NS_IMETHOD OnFileRenamed(CacheFileHandle *aHandle, nsresult aResult) {
   202     MOZ_CRASH("FileOpenHelper::OnFileRenamed should not be called!");
   203     return NS_ERROR_UNEXPECTED;
   204   }
   206   nsRefPtr<CacheIndex> mIndex;
   207   bool                 mCanceled;
   208 };
   210 NS_IMETHODIMP FileOpenHelper::OnFileOpened(CacheFileHandle *aHandle,
   211                                            nsresult aResult)
   212 {
   213   CacheIndexAutoLock lock(mIndex);
   215   if (mCanceled) {
   216     if (aHandle) {
   217       CacheFileIOManager::DoomFile(aHandle, nullptr);
   218     }
   220     return NS_OK;
   221   }
   223   mIndex->OnFileOpenedInternal(this, aHandle, aResult);
   225   return NS_OK;
   226 }
   228 NS_IMPL_ISUPPORTS(FileOpenHelper, CacheFileIOListener);
   231 CacheIndex * CacheIndex::gInstance = nullptr;
   234 NS_IMPL_ADDREF(CacheIndex)
   235 NS_IMPL_RELEASE(CacheIndex)
   237 NS_INTERFACE_MAP_BEGIN(CacheIndex)
   238   NS_INTERFACE_MAP_ENTRY(mozilla::net::CacheFileIOListener)
   239   NS_INTERFACE_MAP_ENTRY(nsIRunnable)
   240 NS_INTERFACE_MAP_END_THREADSAFE
   243 CacheIndex::CacheIndex()
   244   : mLock("CacheFile.mLock")
   245   , mState(INITIAL)
   246   , mShuttingDown(false)
   247   , mIndexNeedsUpdate(false)
   248   , mRemovingAll(false)
   249   , mIndexOnDiskIsValid(false)
   250   , mDontMarkIndexClean(false)
   251   , mIndexTimeStamp(0)
   252   , mUpdateEventPending(false)
   253   , mSkipEntries(0)
   254   , mProcessEntries(0)
   255   , mRWBuf(nullptr)
   256   , mRWBufSize(0)
   257   , mRWBufPos(0)
   258   , mJournalReadSuccessfully(false)
   259 {
   260   LOG(("CacheIndex::CacheIndex [this=%p]", this));
   261   MOZ_COUNT_CTOR(CacheIndex);
   262   MOZ_ASSERT(!gInstance, "multiple CacheIndex instances!");
   263 }
   265 CacheIndex::~CacheIndex()
   266 {
   267   LOG(("CacheIndex::~CacheIndex [this=%p]", this));
   268   MOZ_COUNT_DTOR(CacheIndex);
   270   ReleaseBuffer();
   271 }
   273 void
   274 CacheIndex::Lock()
   275 {
   276   mLock.Lock();
   278   MOZ_ASSERT(!mIndexStats.StateLogged());
   279 }
   281 void
   282 CacheIndex::Unlock()
   283 {
   284   MOZ_ASSERT(!mIndexStats.StateLogged());
   286   mLock.Unlock();
   287 }
   289 inline void
   290 CacheIndex::AssertOwnsLock()
   291 {
   292   mLock.AssertCurrentThreadOwns();
   293 }
   295 // static
   296 nsresult
   297 CacheIndex::Init(nsIFile *aCacheDirectory)
   298 {
   299   LOG(("CacheIndex::Init()"));
   301   MOZ_ASSERT(NS_IsMainThread());
   303   if (gInstance) {
   304     return NS_ERROR_ALREADY_INITIALIZED;
   305   }
   307   nsRefPtr<CacheIndex> idx = new CacheIndex();
   309   CacheIndexAutoLock lock(idx);
   311   nsresult rv = idx->InitInternal(aCacheDirectory);
   312   NS_ENSURE_SUCCESS(rv, rv);
   314   idx.swap(gInstance);
   315   return NS_OK;
   316 }
   318 nsresult
   319 CacheIndex::InitInternal(nsIFile *aCacheDirectory)
   320 {
   321   nsresult rv;
   323   rv = aCacheDirectory->Clone(getter_AddRefs(mCacheDirectory));
   324   NS_ENSURE_SUCCESS(rv, rv);
   326   mStartTime = TimeStamp::NowLoRes();
   328   ReadIndexFromDisk();
   330   return NS_OK;
   331 }
   333 // static
   334 nsresult
   335 CacheIndex::PreShutdown()
   336 {
   337   LOG(("CacheIndex::PreShutdown() [gInstance=%p]", gInstance));
   339   MOZ_ASSERT(NS_IsMainThread());
   341   nsresult rv;
   342   nsRefPtr<CacheIndex> index = gInstance;
   344   if (!index) {
   345     return NS_ERROR_NOT_INITIALIZED;
   346   }
   348   CacheIndexAutoLock lock(index);
   350   LOG(("CacheIndex::PreShutdown() - [state=%d, indexOnDiskIsValid=%d, "
   351        "dontMarkIndexClean=%d]", index->mState, index->mIndexOnDiskIsValid,
   352        index->mDontMarkIndexClean));
   354   LOG(("CacheIndex::PreShutdown() - Closing iterators."));
   355   for (uint32_t i = 0; i < index->mIterators.Length(); ) {
   356     rv = index->mIterators[i]->CloseInternal(NS_ERROR_FAILURE);
   357     if (NS_FAILED(rv)) {
   358       // CacheIndexIterator::CloseInternal() removes itself from mIteratos iff
   359       // it returns success.
   360       LOG(("CacheIndex::PreShutdown() - Failed to remove iterator %p. "
   361            "[rv=0x%08x]", rv));
   362       i++;
   363     }
   364   }
   366   index->mShuttingDown = true;
   368   if (index->mState == READY) {
   369     return NS_OK; // nothing to do
   370   }
   372   nsCOMPtr<nsIRunnable> event;
   373   event = NS_NewRunnableMethod(index, &CacheIndex::PreShutdownInternal);
   375   nsCOMPtr<nsIEventTarget> ioTarget = CacheFileIOManager::IOTarget();
   376   MOZ_ASSERT(ioTarget);
   378   // PreShutdownInternal() will be executed before any queued event on INDEX
   379   // level. That's OK since we don't want to wait for any operation in progess.
   380   // We need to interrupt it and save journal as quickly as possible.
   381   rv = ioTarget->Dispatch(event, nsIEventTarget::DISPATCH_NORMAL);
   382   if (NS_FAILED(rv)) {
   383     NS_WARNING("CacheIndex::PreShutdown() - Can't dispatch event");
   384     LOG(("CacheIndex::PreShutdown() - Can't dispatch event" ));
   385     return rv;
   386   }
   388   return NS_OK;
   389 }
   391 void
   392 CacheIndex::PreShutdownInternal()
   393 {
   394   CacheIndexAutoLock lock(this);
   396   LOG(("CacheIndex::PreShutdownInternal() - [state=%d, indexOnDiskIsValid=%d, "
   397        "dontMarkIndexClean=%d]", mState, mIndexOnDiskIsValid,
   398        mDontMarkIndexClean));
   400   MOZ_ASSERT(mShuttingDown);
   402   if (mUpdateTimer) {
   403     mUpdateTimer = nullptr;
   404   }
   406   switch (mState) {
   407     case WRITING:
   408       FinishWrite(false);
   409       break;
   410     case READY:
   411       // nothing to do, write the journal in Shutdown()
   412       break;
   413     case READING:
   414       FinishRead(false);
   415       break;
   416     case BUILDING:
   417     case UPDATING:
   418       FinishUpdate(false);
   419       break;
   420     default:
   421       MOZ_ASSERT(false, "Implement me!");
   422   }
   424   // We should end up in READY state
   425   MOZ_ASSERT(mState == READY);
   426 }
   428 // static
   429 nsresult
   430 CacheIndex::Shutdown()
   431 {
   432   LOG(("CacheIndex::Shutdown() [gInstance=%p]", gInstance));
   434   MOZ_ASSERT(NS_IsMainThread());
   436   nsRefPtr<CacheIndex> index;
   437   index.swap(gInstance);
   439   if (!index) {
   440     return NS_ERROR_NOT_INITIALIZED;
   441   }
   443   CacheIndexAutoLock lock(index);
   445   bool sanitize = CacheObserver::ClearCacheOnShutdown();
   447   LOG(("CacheIndex::Shutdown() - [state=%d, indexOnDiskIsValid=%d, "
   448        "dontMarkIndexClean=%d, sanitize=%d]", index->mState,
   449        index->mIndexOnDiskIsValid, index->mDontMarkIndexClean, sanitize));
   451   MOZ_ASSERT(index->mShuttingDown);
   453   EState oldState = index->mState;
   454   index->ChangeState(SHUTDOWN);
   456   if (oldState != READY) {
   457     LOG(("CacheIndex::Shutdown() - Unexpected state. Did posting of "
   458          "PreShutdownInternal() fail?"));
   459   }
   461   switch (oldState) {
   462     case WRITING:
   463       index->FinishWrite(false);
   464       // no break
   465     case READY:
   466       if (index->mIndexOnDiskIsValid && !index->mDontMarkIndexClean) {
   467         if (!sanitize && NS_FAILED(index->WriteLogToDisk())) {
   468           index->RemoveIndexFromDisk();
   469         }
   470       } else {
   471         index->RemoveIndexFromDisk();
   472       }
   473       break;
   474     case READING:
   475       index->FinishRead(false);
   476       break;
   477     case BUILDING:
   478     case UPDATING:
   479       index->FinishUpdate(false);
   480       break;
   481     default:
   482       MOZ_ASSERT(false, "Unexpected state!");
   483   }
   485   if (sanitize) {
   486     index->RemoveIndexFromDisk();
   487   }
   489   return NS_OK;
   490 }
   492 // static
   493 nsresult
   494 CacheIndex::AddEntry(const SHA1Sum::Hash *aHash)
   495 {
   496   LOG(("CacheIndex::AddEntry() [hash=%08x%08x%08x%08x%08x]", LOGSHA1(aHash)));
   498   nsRefPtr<CacheIndex> index = gInstance;
   500   if (!index) {
   501     return NS_ERROR_NOT_INITIALIZED;
   502   }
   504   MOZ_ASSERT(CacheFileIOManager::IsOnIOThread());
   506   CacheIndexAutoLock lock(index);
   508   if (!index->IsIndexUsable()) {
   509     return NS_ERROR_NOT_AVAILABLE;
   510   }
   512   // Getters in CacheIndexStats assert when mStateLogged is true since the
   513   // information is incomplete between calls to BeforeChange() and AfterChange()
   514   // (i.e. while CacheIndexEntryAutoManage exists). We need to check whether
   515   // non-fresh entries exists outside the scope of CacheIndexEntryAutoManage.
   516   bool updateIfNonFreshEntriesExist = false;
   518   {
   519     CacheIndexEntryAutoManage entryMng(aHash, index);
   521     CacheIndexEntry *entry = index->mIndex.GetEntry(*aHash);
   522     bool entryRemoved = entry && entry->IsRemoved();
   524     if (index->mState == READY || index->mState == UPDATING ||
   525         index->mState == BUILDING) {
   526       MOZ_ASSERT(index->mPendingUpdates.Count() == 0);
   528       if (entry && !entryRemoved) {
   529         // Found entry in index that shouldn't exist.
   531         if (entry->IsFresh()) {
   532           // Someone removed the file on disk while FF is running. Update
   533           // process can fix only non-fresh entries (i.e. entries that were not
   534           // added within this session). Start update only if we have such
   535           // entries.
   536           //
   537           // TODO: This should be very rare problem. If it turns out not to be
   538           // true, change the update process so that it also iterates all
   539           // initialized non-empty entries and checks whether the file exists.
   541           LOG(("CacheIndex::AddEntry() - Cache file was removed outside FF "
   542                "process!"));
   544           updateIfNonFreshEntriesExist = true;
   545         } else if (index->mState == READY) {
   546           // Index is outdated, update it.
   547           LOG(("CacheIndex::AddEntry() - Found entry that shouldn't exist, "
   548                "update is needed"));
   549           index->mIndexNeedsUpdate = true;
   550         } else {
   551           // We cannot be here when building index since all entries are fresh
   552           // during building.
   553           MOZ_ASSERT(index->mState == UPDATING);
   554         }
   555       }
   557       if (!entry) {
   558         entry = index->mIndex.PutEntry(*aHash);
   559       }
   560     } else { // WRITING, READING
   561       CacheIndexEntry *updated = index->mPendingUpdates.GetEntry(*aHash);
   562       bool updatedRemoved = updated && updated->IsRemoved();
   564       if ((updated && !updatedRemoved) ||
   565           (!updated && entry && !entryRemoved && entry->IsFresh())) {
   566         // Fresh entry found, so the file was removed outside FF
   567         LOG(("CacheIndex::AddEntry() - Cache file was removed outside FF "
   568              "process!"));
   570         updateIfNonFreshEntriesExist = true;
   571       } else if (!updated && entry && !entryRemoved) {
   572         if (index->mState == WRITING) {
   573           LOG(("CacheIndex::AddEntry() - Found entry that shouldn't exist, "
   574                "update is needed"));
   575           index->mIndexNeedsUpdate = true;
   576         }
   577         // Ignore if state is READING since the index information is partial
   578       }
   580       updated = index->mPendingUpdates.PutEntry(*aHash);
   581       entry = updated;
   582     }
   584     entry->InitNew();
   585     entry->MarkDirty();
   586     entry->MarkFresh();
   587   }
   589   if (updateIfNonFreshEntriesExist &&
   590       index->mIndexStats.Count() != index->mIndexStats.Fresh()) {
   591     index->mIndexNeedsUpdate = true;
   592   }
   594   index->StartUpdatingIndexIfNeeded();
   595   index->WriteIndexToDiskIfNeeded();
   597   return NS_OK;
   598 }
   600 // static
   601 nsresult
   602 CacheIndex::EnsureEntryExists(const SHA1Sum::Hash *aHash)
   603 {
   604   LOG(("CacheIndex::EnsureEntryExists() [hash=%08x%08x%08x%08x%08x]",
   605        LOGSHA1(aHash)));
   607   nsRefPtr<CacheIndex> index = gInstance;
   609   if (!index) {
   610     return NS_ERROR_NOT_INITIALIZED;
   611   }
   613   MOZ_ASSERT(CacheFileIOManager::IsOnIOThread());
   615   CacheIndexAutoLock lock(index);
   617   if (!index->IsIndexUsable()) {
   618     return NS_ERROR_NOT_AVAILABLE;
   619   }
   621   {
   622     CacheIndexEntryAutoManage entryMng(aHash, index);
   624     CacheIndexEntry *entry = index->mIndex.GetEntry(*aHash);
   625     bool entryRemoved = entry && entry->IsRemoved();
   627     if (index->mState == READY || index->mState == UPDATING ||
   628         index->mState == BUILDING) {
   629       MOZ_ASSERT(index->mPendingUpdates.Count() == 0);
   631       if (!entry || entryRemoved) {
   632         if (entryRemoved && entry->IsFresh()) {
   633           // This could happen only if somebody copies files to the entries
   634           // directory while FF is running.
   635           LOG(("CacheIndex::EnsureEntryExists() - Cache file was added outside "
   636                "FF process! Update is needed."));
   637           index->mIndexNeedsUpdate = true;
   638         } else if (index->mState == READY ||
   639                    (entryRemoved && !entry->IsFresh())) {
   640           // Removed non-fresh entries can be present as a result of
   641           // ProcessJournalEntry()
   642           LOG(("CacheIndex::EnsureEntryExists() - Didn't find entry that should"
   643                " exist, update is needed"));
   644           index->mIndexNeedsUpdate = true;
   645         }
   647         if (!entry) {
   648           entry = index->mIndex.PutEntry(*aHash);
   649         }
   650         entry->InitNew();
   651         entry->MarkDirty();
   652       }
   653       entry->MarkFresh();
   654     } else { // WRITING, READING
   655       CacheIndexEntry *updated = index->mPendingUpdates.GetEntry(*aHash);
   656       bool updatedRemoved = updated && updated->IsRemoved();
   658       if (updatedRemoved ||
   659           (!updated && entryRemoved && entry->IsFresh())) {
   660         // Fresh information about missing entry found. This could happen only
   661         // if somebody copies files to the entries directory while FF is running.
   662         LOG(("CacheIndex::EnsureEntryExists() - Cache file was added outside "
   663              "FF process! Update is needed."));
   664         index->mIndexNeedsUpdate = true;
   665       } else if (!updated && (!entry || entryRemoved)) {
   666         if (index->mState == WRITING) {
   667           LOG(("CacheIndex::EnsureEntryExists() - Didn't find entry that should"
   668                " exist, update is needed"));
   669           index->mIndexNeedsUpdate = true;
   670         }
   671         // Ignore if state is READING since the index information is partial
   672       }
   674       // We don't need entryRemoved and updatedRemoved info anymore
   675       if (entryRemoved)   entry = nullptr;
   676       if (updatedRemoved) updated = nullptr;
   678       if (updated) {
   679         updated->MarkFresh();
   680       } else {
   681         if (!entry) {
   682           // Create a new entry
   683           updated = index->mPendingUpdates.PutEntry(*aHash);
   684           updated->InitNew();
   685           updated->MarkFresh();
   686           updated->MarkDirty();
   687         } else {
   688           if (!entry->IsFresh()) {
   689             // To mark the entry fresh we must make a copy of index entry
   690             // since the index is read-only.
   691             updated = index->mPendingUpdates.PutEntry(*aHash);
   692             *updated = *entry;
   693             updated->MarkFresh();
   694           }
   695         }
   696       }
   697     }
   698   }
   700   index->StartUpdatingIndexIfNeeded();
   701   index->WriteIndexToDiskIfNeeded();
   703   return NS_OK;
   704 }
   706 // static
   707 nsresult
   708 CacheIndex::InitEntry(const SHA1Sum::Hash *aHash,
   709                       uint32_t             aAppId,
   710                       bool                 aAnonymous,
   711                       bool                 aInBrowser)
   712 {
   713   LOG(("CacheIndex::InitEntry() [hash=%08x%08x%08x%08x%08x, appId=%u, "
   714        "anonymous=%d, inBrowser=%d]", LOGSHA1(aHash), aAppId, aAnonymous,
   715        aInBrowser));
   717   nsRefPtr<CacheIndex> index = gInstance;
   719   if (!index) {
   720     return NS_ERROR_NOT_INITIALIZED;
   721   }
   723   MOZ_ASSERT(CacheFileIOManager::IsOnIOThread());
   725   CacheIndexAutoLock lock(index);
   727   if (!index->IsIndexUsable()) {
   728     return NS_ERROR_NOT_AVAILABLE;
   729   }
   731   {
   732     CacheIndexEntryAutoManage entryMng(aHash, index);
   734     CacheIndexEntry *entry = index->mIndex.GetEntry(*aHash);
   735     bool reinitEntry = false;
   737     if (entry && entry->IsRemoved()) {
   738       entry = nullptr;
   739     }
   741     if (index->mState == READY || index->mState == UPDATING ||
   742         index->mState == BUILDING) {
   743       MOZ_ASSERT(index->mPendingUpdates.Count() == 0);
   744       MOZ_ASSERT(entry);
   745       MOZ_ASSERT(entry->IsFresh());
   747       if (IsCollision(entry, aAppId, aAnonymous, aInBrowser)) {
   748         index->mIndexNeedsUpdate = true; // TODO Does this really help in case of collision?
   749         reinitEntry = true;
   750       } else {
   751         if (entry->IsInitialized()) {
   752           return NS_OK;
   753         }
   754       }
   755     } else {
   756       CacheIndexEntry *updated = index->mPendingUpdates.GetEntry(*aHash);
   757       DebugOnly<bool> removed = updated && updated->IsRemoved();
   759       MOZ_ASSERT(updated || !removed);
   760       MOZ_ASSERT(updated || entry);
   762       if (updated) {
   763         MOZ_ASSERT(updated->IsFresh());
   765         if (IsCollision(updated, aAppId, aAnonymous, aInBrowser)) {
   766           index->mIndexNeedsUpdate = true;
   767           reinitEntry = true;
   768         } else {
   769           if (updated->IsInitialized()) {
   770             return NS_OK;
   771           }
   772         }
   773         entry = updated;
   774       } else {
   775         MOZ_ASSERT(entry->IsFresh());
   777         if (IsCollision(entry, aAppId, aAnonymous, aInBrowser)) {
   778           index->mIndexNeedsUpdate = true;
   779           reinitEntry = true;
   780         } else {
   781           if (entry->IsInitialized()) {
   782             return NS_OK;
   783           }
   784         }
   786         // make a copy of a read-only entry
   787         updated = index->mPendingUpdates.PutEntry(*aHash);
   788         *updated = *entry;
   789         entry = updated;
   790       }
   791     }
   793     if (reinitEntry) {
   794       // There is a collision and we are going to rewrite this entry. Initialize
   795       // it as a new entry.
   796       entry->InitNew();
   797       entry->MarkFresh();
   798     }
   799     entry->Init(aAppId, aAnonymous, aInBrowser);
   800     entry->MarkDirty();
   801   }
   803   index->StartUpdatingIndexIfNeeded();
   804   index->WriteIndexToDiskIfNeeded();
   806   return NS_OK;
   807 }
   809 // static
   810 nsresult
   811 CacheIndex::RemoveEntry(const SHA1Sum::Hash *aHash)
   812 {
   813   LOG(("CacheIndex::RemoveEntry() [hash=%08x%08x%08x%08x%08x]",
   814        LOGSHA1(aHash)));
   816   nsRefPtr<CacheIndex> index = gInstance;
   818   if (!index) {
   819     return NS_ERROR_NOT_INITIALIZED;
   820   }
   822   MOZ_ASSERT(CacheFileIOManager::IsOnIOThread());
   824   CacheIndexAutoLock lock(index);
   826   if (!index->IsIndexUsable()) {
   827     return NS_ERROR_NOT_AVAILABLE;
   828   }
   830   {
   831     CacheIndexEntryAutoManage entryMng(aHash, index);
   833     CacheIndexEntry *entry = index->mIndex.GetEntry(*aHash);
   834     bool entryRemoved = entry && entry->IsRemoved();
   836     if (index->mState == READY || index->mState == UPDATING ||
   837         index->mState == BUILDING) {
   838       MOZ_ASSERT(index->mPendingUpdates.Count() == 0);
   840       if (!entry || entryRemoved) {
   841         if (entryRemoved && entry->IsFresh()) {
   842           // This could happen only if somebody copies files to the entries
   843           // directory while FF is running.
   844           LOG(("CacheIndex::RemoveEntry() - Cache file was added outside FF "
   845                "process! Update is needed."));
   846           index->mIndexNeedsUpdate = true;
   847         } else if (index->mState == READY ||
   848                    (entryRemoved && !entry->IsFresh())) {
   849           // Removed non-fresh entries can be present as a result of
   850           // ProcessJournalEntry()
   851           LOG(("CacheIndex::RemoveEntry() - Didn't find entry that should exist"
   852                ", update is needed"));
   853           index->mIndexNeedsUpdate = true;
   854         }
   855       } else {
   856         if (entry) {
   857           if (!entry->IsDirty() && entry->IsFileEmpty()) {
   858             index->mIndex.RemoveEntry(*aHash);
   859             entry = nullptr;
   860           } else {
   861             entry->MarkRemoved();
   862             entry->MarkDirty();
   863             entry->MarkFresh();
   864           }
   865         }
   866       }
   867     } else { // WRITING, READING
   868       CacheIndexEntry *updated = index->mPendingUpdates.GetEntry(*aHash);
   869       bool updatedRemoved = updated && updated->IsRemoved();
   871       if (updatedRemoved ||
   872           (!updated && entryRemoved && entry->IsFresh())) {
   873         // Fresh information about missing entry found. This could happen only
   874         // if somebody copies files to the entries directory while FF is running.
   875         LOG(("CacheIndex::RemoveEntry() - Cache file was added outside FF "
   876              "process! Update is needed."));
   877         index->mIndexNeedsUpdate = true;
   878       } else if (!updated && (!entry || entryRemoved)) {
   879         if (index->mState == WRITING) {
   880           LOG(("CacheIndex::RemoveEntry() - Didn't find entry that should exist"
   881                ", update is needed"));
   882           index->mIndexNeedsUpdate = true;
   883         }
   884         // Ignore if state is READING since the index information is partial
   885       }
   887       if (!updated) {
   888         updated = index->mPendingUpdates.PutEntry(*aHash);
   889         updated->InitNew();
   890       }
   892       updated->MarkRemoved();
   893       updated->MarkDirty();
   894       updated->MarkFresh();
   895     }
   896   }
   898   index->StartUpdatingIndexIfNeeded();
   899   index->WriteIndexToDiskIfNeeded();
   901   return NS_OK;
   902 }
   904 // static
   905 nsresult
   906 CacheIndex::UpdateEntry(const SHA1Sum::Hash *aHash,
   907                         const uint32_t      *aFrecency,
   908                         const uint32_t      *aExpirationTime,
   909                         const uint32_t      *aSize)
   910 {
   911   LOG(("CacheIndex::UpdateEntry() [hash=%08x%08x%08x%08x%08x, "
   912        "frecency=%s, expirationTime=%s, size=%s]", LOGSHA1(aHash),
   913        aFrecency ? nsPrintfCString("%u", *aFrecency).get() : "",
   914        aExpirationTime ? nsPrintfCString("%u", *aExpirationTime).get() : "",
   915        aSize ? nsPrintfCString("%u", *aSize).get() : ""));
   917   nsRefPtr<CacheIndex> index = gInstance;
   919   if (!index) {
   920     return NS_ERROR_NOT_INITIALIZED;
   921   }
   923   MOZ_ASSERT(CacheFileIOManager::IsOnIOThread());
   925   CacheIndexAutoLock lock(index);
   927   if (!index->IsIndexUsable()) {
   928     return NS_ERROR_NOT_AVAILABLE;
   929   }
   931   {
   932     CacheIndexEntryAutoManage entryMng(aHash, index);
   934     CacheIndexEntry *entry = index->mIndex.GetEntry(*aHash);
   936     if (entry && entry->IsRemoved()) {
   937       entry = nullptr;
   938     }
   940     if (index->mState == READY || index->mState == UPDATING ||
   941         index->mState == BUILDING) {
   942       MOZ_ASSERT(index->mPendingUpdates.Count() == 0);
   943       MOZ_ASSERT(entry);
   945       if (!HasEntryChanged(entry, aFrecency, aExpirationTime, aSize)) {
   946         return NS_OK;
   947       }
   948     } else {
   949       CacheIndexEntry *updated = index->mPendingUpdates.GetEntry(*aHash);
   950       DebugOnly<bool> removed = updated && updated->IsRemoved();
   952       MOZ_ASSERT(updated || !removed);
   953       MOZ_ASSERT(updated || entry);
   955       if (!updated) {
   956         if (entry &&
   957             HasEntryChanged(entry, aFrecency, aExpirationTime, aSize)) {
   958           // make a copy of a read-only entry
   959           updated = index->mPendingUpdates.PutEntry(*aHash);
   960           *updated = *entry;
   961           entry = updated;
   962         } else {
   963           return NS_ERROR_NOT_AVAILABLE;
   964         }
   965       } else {
   966         entry = updated;
   967       }
   968     }
   970     MOZ_ASSERT(entry->IsFresh());
   971     MOZ_ASSERT(entry->IsInitialized());
   972     entry->MarkDirty();
   974     if (aFrecency) {
   975       entry->SetFrecency(*aFrecency);
   976     }
   978     if (aExpirationTime) {
   979       entry->SetExpirationTime(*aExpirationTime);
   980     }
   982     if (aSize) {
   983       entry->SetFileSize(*aSize);
   984     }
   985   }
   987   index->WriteIndexToDiskIfNeeded();
   989   return NS_OK;
   990 }
   992 // static
   993 nsresult
   994 CacheIndex::RemoveAll()
   995 {
   996   LOG(("CacheIndex::RemoveAll()"));
   998   nsRefPtr<CacheIndex> index = gInstance;
  1000   if (!index) {
  1001     return NS_ERROR_NOT_INITIALIZED;
  1004   MOZ_ASSERT(CacheFileIOManager::IsOnIOThread());
  1006   nsCOMPtr<nsIFile> file;
  1009     CacheIndexAutoLock lock(index);
  1011     MOZ_ASSERT(!index->mRemovingAll);
  1013     if (!index->IsIndexUsable()) {
  1014       return NS_ERROR_NOT_AVAILABLE;
  1017     AutoRestore<bool> saveRemovingAll(index->mRemovingAll);
  1018     index->mRemovingAll = true;
  1020     // Doom index and journal handles but don't null them out since this will be
  1021     // done in FinishWrite/FinishRead methods.
  1022     if (index->mIndexHandle) {
  1023       CacheFileIOManager::DoomFile(index->mIndexHandle, nullptr);
  1024     } else {
  1025       // We don't have a handle to index file, so get the file here, but delete
  1026       // it outside the lock. Ignore the result since this is not fatal.
  1027       index->GetFile(NS_LITERAL_CSTRING(kIndexName), getter_AddRefs(file));
  1030     if (index->mJournalHandle) {
  1031       CacheFileIOManager::DoomFile(index->mJournalHandle, nullptr);
  1034     switch (index->mState) {
  1035       case WRITING:
  1036         index->FinishWrite(false);
  1037         break;
  1038       case READY:
  1039         // nothing to do
  1040         break;
  1041       case READING:
  1042         index->FinishRead(false);
  1043         break;
  1044       case BUILDING:
  1045       case UPDATING:
  1046         index->FinishUpdate(false);
  1047         break;
  1048       default:
  1049         MOZ_ASSERT(false, "Unexpected state!");
  1052     // We should end up in READY state
  1053     MOZ_ASSERT(index->mState == READY);
  1055     // There should not be any handle
  1056     MOZ_ASSERT(!index->mIndexHandle);
  1057     MOZ_ASSERT(!index->mJournalHandle);
  1059     index->mIndexOnDiskIsValid = false;
  1060     index->mIndexNeedsUpdate = false;
  1062     index->mIndexStats.Clear();
  1063     index->mFrecencyArray.Clear();
  1064     index->mExpirationArray.Clear();
  1065     index->mIndex.Clear();
  1068   if (file) {
  1069     // Ignore the result. The file might not exist and the failure is not fatal.
  1070     file->Remove(false);
  1073   return NS_OK;
  1076 // static
  1077 nsresult
  1078 CacheIndex::HasEntry(const nsACString &aKey, EntryStatus *_retval)
  1080   LOG(("CacheIndex::HasEntry() [key=%s]", PromiseFlatCString(aKey).get()));
  1082   nsRefPtr<CacheIndex> index = gInstance;
  1084   if (!index) {
  1085     return NS_ERROR_NOT_INITIALIZED;
  1088   CacheIndexAutoLock lock(index);
  1090   if (!index->IsIndexUsable()) {
  1091     return NS_ERROR_NOT_AVAILABLE;
  1094   SHA1Sum sum;
  1095   SHA1Sum::Hash hash;
  1096   sum.update(aKey.BeginReading(), aKey.Length());
  1097   sum.finish(hash);
  1099   CacheIndexEntry *entry = nullptr;
  1101   switch (index->mState) {
  1102     case READING:
  1103     case WRITING:
  1104       entry = index->mPendingUpdates.GetEntry(hash);
  1105       // no break
  1106     case BUILDING:
  1107     case UPDATING:
  1108     case READY:
  1109       if (!entry) {
  1110         entry = index->mIndex.GetEntry(hash);
  1112       break;
  1113     case INITIAL:
  1114     case SHUTDOWN:
  1115       MOZ_ASSERT(false, "Unexpected state!");
  1118   if (!entry) {
  1119     if (index->mState == READY || index->mState == WRITING) {
  1120       *_retval = DOES_NOT_EXIST;
  1121     } else {
  1122       *_retval = DO_NOT_KNOW;
  1124   } else {
  1125     if (entry->IsRemoved()) {
  1126       if (entry->IsFresh()) {
  1127         *_retval = DOES_NOT_EXIST;
  1128       } else {
  1129         *_retval = DO_NOT_KNOW;
  1131     } else {
  1132       *_retval = EXISTS;
  1136   LOG(("CacheIndex::HasEntry() - result is %u", *_retval));
  1137   return NS_OK;
  1140 // static
  1141 nsresult
  1142 CacheIndex::GetEntryForEviction(SHA1Sum::Hash *aHash, uint32_t *aCnt)
  1144   LOG(("CacheIndex::GetEntryForEviction()"));
  1146   nsRefPtr<CacheIndex> index = gInstance;
  1148   if (!index)
  1149     return NS_ERROR_NOT_INITIALIZED;
  1151   CacheIndexAutoLock lock(index);
  1153   if (!index->IsIndexUsable()) {
  1154     return NS_ERROR_NOT_AVAILABLE;
  1157   MOZ_ASSERT(index->mFrecencyArray.Length() ==
  1158              index->mExpirationArray.Length());
  1160   if (index->mExpirationArray.Length() == 0)
  1161     return NS_ERROR_NOT_AVAILABLE;
  1163   uint32_t now = PR_Now() / PR_USEC_PER_SEC;
  1164   if (index->mExpirationArray[0]->mExpirationTime < now) {
  1165     memcpy(aHash, &index->mExpirationArray[0]->mHash, sizeof(SHA1Sum::Hash));
  1166     *aCnt = index->mExpirationArray.Length();
  1167     LOG(("CacheIndex::GetEntryForEviction() - returning entry from expiration "
  1168          "array [hash=%08x%08x%08x%08x%08x, cnt=%u, expTime=%u, now=%u, "
  1169          "frecency=%u]", LOGSHA1(aHash), *aCnt,
  1170          index->mExpirationArray[0]->mExpirationTime, now,
  1171          index->mExpirationArray[0]->mFrecency));
  1173   else {
  1174     memcpy(aHash, &index->mFrecencyArray[0]->mHash, sizeof(SHA1Sum::Hash));
  1175     *aCnt = index->mFrecencyArray.Length();
  1176     LOG(("CacheIndex::GetEntryForEviction() - returning entry from frecency "
  1177          "array [hash=%08x%08x%08x%08x%08x, cnt=%u, expTime=%u, now=%u, "
  1178          "frecency=%u]", LOGSHA1(aHash), *aCnt,
  1179          index->mExpirationArray[0]->mExpirationTime, now,
  1180          index->mExpirationArray[0]->mFrecency));
  1183   return NS_OK;
  1186 // static
  1187 nsresult
  1188 CacheIndex::GetCacheSize(uint32_t *_retval)
  1190   LOG(("CacheIndex::GetCacheSize()"));
  1192   nsRefPtr<CacheIndex> index = gInstance;
  1194   if (!index)
  1195     return NS_ERROR_NOT_INITIALIZED;
  1197   CacheIndexAutoLock lock(index);
  1199   if (!index->IsIndexUsable()) {
  1200     return NS_ERROR_NOT_AVAILABLE;
  1203   *_retval = index->mIndexStats.Size();
  1204   LOG(("CacheIndex::GetCacheSize() - returning %u", *_retval));
  1205   return NS_OK;
  1208 // static
  1209 nsresult
  1210 CacheIndex::AsyncGetDiskConsumption(nsICacheStorageConsumptionObserver* aObserver)
  1212   LOG(("CacheIndex::AsyncGetDiskConsumption()"));
  1214   nsRefPtr<CacheIndex> index = gInstance;
  1216   if (!index) {
  1217     return NS_ERROR_NOT_INITIALIZED;
  1220   CacheIndexAutoLock lock(index);
  1222   if (!index->IsIndexUsable()) {
  1223     return NS_ERROR_NOT_AVAILABLE;
  1226   nsRefPtr<DiskConsumptionObserver> observer =
  1227     DiskConsumptionObserver::Init(aObserver);
  1229   NS_ENSURE_ARG(observer);
  1231   if (index->mState == READY || index->mState == WRITING) {
  1232     LOG(("CacheIndex::AsyncGetDiskConsumption - calling immediately"));
  1233     // Safe to call the callback under the lock,
  1234     // we always post to the main thread.
  1235     observer->OnDiskConsumption(index->mIndexStats.Size() << 10);
  1236     return NS_OK;
  1239   LOG(("CacheIndex::AsyncGetDiskConsumption - remembering callback"));
  1240   // Will be called when the index get to the READY state.
  1241   index->mDiskConsumptionObservers.AppendElement(observer);
  1243   return NS_OK;
  1246 // static
  1247 nsresult
  1248 CacheIndex::GetIterator(nsILoadContextInfo *aInfo, bool aAddNew,
  1249                         CacheIndexIterator **_retval)
  1251   LOG(("CacheIndex::GetIterator() [info=%p, addNew=%d]", aInfo, aAddNew));
  1253   nsRefPtr<CacheIndex> index = gInstance;
  1255   if (!index) {
  1256     return NS_ERROR_NOT_INITIALIZED;
  1259   CacheIndexAutoLock lock(index);
  1261   if (!index->IsIndexUsable()) {
  1262     return NS_ERROR_NOT_AVAILABLE;
  1265   nsRefPtr<CacheIndexIterator> iter;
  1266   if (aInfo) {
  1267     iter = new CacheIndexContextIterator(index, aAddNew, aInfo);
  1268   } else {
  1269     iter = new CacheIndexIterator(index, aAddNew);
  1272   iter->AddRecords(index->mFrecencyArray);
  1274   index->mIterators.AppendElement(iter);
  1275   iter.swap(*_retval);
  1276   return NS_OK;
  1279 // static
  1280 nsresult
  1281 CacheIndex::IsUpToDate(bool *_retval)
  1283   LOG(("CacheIndex::IsUpToDate()"));
  1285   nsRefPtr<CacheIndex> index = gInstance;
  1287   if (!index) {
  1288     return NS_ERROR_NOT_INITIALIZED;
  1291   CacheIndexAutoLock lock(index);
  1293   if (!index->IsIndexUsable()) {
  1294     return NS_ERROR_NOT_AVAILABLE;
  1297   *_retval = (index->mState == READY || index->mState == WRITING) &&
  1298              !index->mIndexNeedsUpdate && !index->mShuttingDown;
  1300   LOG(("CacheIndex::IsUpToDate() - returning %p", *_retval));
  1301   return NS_OK;
  1304 bool
  1305 CacheIndex::IsIndexUsable()
  1307   MOZ_ASSERT(mState != INITIAL);
  1309   switch (mState) {
  1310     case INITIAL:
  1311     case SHUTDOWN:
  1312       return false;
  1314     case READING:
  1315     case WRITING:
  1316     case BUILDING:
  1317     case UPDATING:
  1318     case READY:
  1319       break;
  1322   return true;
  1325 // static
  1326 bool
  1327 CacheIndex::IsCollision(CacheIndexEntry *aEntry,
  1328                         uint32_t         aAppId,
  1329                         bool             aAnonymous,
  1330                         bool             aInBrowser)
  1332   if (!aEntry->IsInitialized()) {
  1333     return false;
  1336   if (aEntry->AppId() != aAppId || aEntry->Anonymous() != aAnonymous ||
  1337       aEntry->InBrowser() != aInBrowser) {
  1338     LOG(("CacheIndex::IsCollision() - Collision detected for entry hash=%08x"
  1339          "%08x%08x%08x%08x, expected values: appId=%u, anonymous=%d, "
  1340          "inBrowser=%d; actual values: appId=%u, anonymous=%d, inBrowser=%d]",
  1341          LOGSHA1(aEntry->Hash()), aAppId, aAnonymous, aInBrowser,
  1342          aEntry->AppId(), aEntry->Anonymous(), aEntry->InBrowser()));
  1343     return true;
  1346   return false;
  1349 // static
  1350 bool
  1351 CacheIndex::HasEntryChanged(CacheIndexEntry *aEntry,
  1352                             const uint32_t  *aFrecency,
  1353                             const uint32_t  *aExpirationTime,
  1354                             const uint32_t  *aSize)
  1356   if (aFrecency && *aFrecency != aEntry->GetFrecency()) {
  1357     return true;
  1360   if (aExpirationTime && *aExpirationTime != aEntry->GetExpirationTime()) {
  1361     return true;
  1364   if (aSize &&
  1365       (*aSize & CacheIndexEntry::kFileSizeMask) != aEntry->GetFileSize()) {
  1366     return true;
  1369   return false;
  1372 void
  1373 CacheIndex::ProcessPendingOperations()
  1375   LOG(("CacheIndex::ProcessPendingOperations()"));
  1377   AssertOwnsLock();
  1379   mPendingUpdates.EnumerateEntries(&CacheIndex::UpdateEntryInIndex, this);
  1381   MOZ_ASSERT(mPendingUpdates.Count() == 0);
  1383   EnsureCorrectStats();
  1386 // static
  1387 PLDHashOperator
  1388 CacheIndex::UpdateEntryInIndex(CacheIndexEntry *aEntry, void* aClosure)
  1390   CacheIndex *index = static_cast<CacheIndex *>(aClosure);
  1392   LOG(("CacheFile::UpdateEntryInIndex() [hash=%08x%08x%08x%08x%08x]",
  1393        LOGSHA1(aEntry->Hash())));
  1395   MOZ_ASSERT(aEntry->IsFresh());
  1396   MOZ_ASSERT(aEntry->IsDirty());
  1398   CacheIndexEntry *entry = index->mIndex.GetEntry(*aEntry->Hash());
  1400   CacheIndexEntryAutoManage emng(aEntry->Hash(), index);
  1401   emng.DoNotSearchInUpdates();
  1403   if (aEntry->IsRemoved()) {
  1404     if (entry) {
  1405       if (entry->IsRemoved()) {
  1406         MOZ_ASSERT(entry->IsFresh());
  1407         MOZ_ASSERT(entry->IsDirty());
  1408       } else if (!entry->IsDirty() && entry->IsFileEmpty()) {
  1409         // Entries with empty file are not stored in index on disk. Just remove
  1410         // the entry, but only in case the entry is not dirty, i.e. the entry
  1411         // file was empty when we wrote the index.
  1412         index->mIndex.RemoveEntry(*aEntry->Hash());
  1413         entry = nullptr;
  1414       } else {
  1415         entry->MarkRemoved();
  1416         entry->MarkDirty();
  1417         entry->MarkFresh();
  1421     return PL_DHASH_REMOVE;
  1424   entry = index->mIndex.PutEntry(*aEntry->Hash());
  1425   *entry = *aEntry;
  1427   return PL_DHASH_REMOVE;
  1430 bool
  1431 CacheIndex::WriteIndexToDiskIfNeeded()
  1433   if (mState != READY || mShuttingDown) {
  1434     return false;
  1437   if (!mLastDumpTime.IsNull() &&
  1438       (TimeStamp::NowLoRes() - mLastDumpTime).ToMilliseconds() <
  1439       kMinDumpInterval) {
  1440     return false;
  1443   if (mIndexStats.Dirty() < kMinUnwrittenChanges) {
  1444     return false;
  1447   WriteIndexToDisk();
  1448   return true;
  1451 void
  1452 CacheIndex::WriteIndexToDisk()
  1454   LOG(("CacheIndex::WriteIndexToDisk()"));
  1455   mIndexStats.Log();
  1457   nsresult rv;
  1459   AssertOwnsLock();
  1460   MOZ_ASSERT(mState == READY);
  1461   MOZ_ASSERT(!mRWBuf);
  1462   MOZ_ASSERT(!mRWHash);
  1464   ChangeState(WRITING);
  1466   mProcessEntries = mIndexStats.ActiveEntriesCount();
  1468   mIndexFileOpener = new FileOpenHelper(this);
  1469   rv = CacheFileIOManager::OpenFile(NS_LITERAL_CSTRING(kTempIndexName),
  1470                                     CacheFileIOManager::SPECIAL_FILE |
  1471                                     CacheFileIOManager::CREATE,
  1472                                     true,
  1473                                     mIndexFileOpener);
  1474   if (NS_FAILED(rv)) {
  1475     LOG(("CacheIndex::WriteIndexToDisk() - Can't open file [rv=0x%08x]", rv));
  1476     FinishWrite(false);
  1477     return;
  1480   // Write index header to a buffer, it will be written to disk together with
  1481   // records in WriteRecords() once we open the file successfully.
  1482   AllocBuffer();
  1483   mRWHash = new CacheHash();
  1485   CacheIndexHeader *hdr = reinterpret_cast<CacheIndexHeader *>(mRWBuf);
  1486   NetworkEndian::writeUint32(&hdr->mVersion, kIndexVersion);
  1487   NetworkEndian::writeUint32(&hdr->mTimeStamp,
  1488                              static_cast<uint32_t>(PR_Now() / PR_USEC_PER_SEC));
  1489   NetworkEndian::writeUint32(&hdr->mIsDirty, 1);
  1491   mRWBufPos = sizeof(CacheIndexHeader);
  1492   mSkipEntries = 0;
  1495 namespace { // anon
  1497 struct WriteRecordsHelper
  1499   char    *mBuf;
  1500   uint32_t mSkip;
  1501   uint32_t mProcessMax;
  1502   uint32_t mProcessed;
  1503 #ifdef DEBUG
  1504   bool     mHasMore;
  1505 #endif
  1506 };
  1508 } // anon
  1510 void
  1511 CacheIndex::WriteRecords()
  1513   LOG(("CacheIndex::WriteRecords()"));
  1515   nsresult rv;
  1517   AssertOwnsLock();
  1518   MOZ_ASSERT(mState == WRITING);
  1520   int64_t fileOffset;
  1522   if (mSkipEntries) {
  1523     MOZ_ASSERT(mRWBufPos == 0);
  1524     fileOffset = sizeof(CacheIndexHeader);
  1525     fileOffset += sizeof(CacheIndexRecord) * mSkipEntries;
  1526   } else {
  1527     MOZ_ASSERT(mRWBufPos == sizeof(CacheIndexHeader));
  1528     fileOffset = 0;
  1530   uint32_t hashOffset = mRWBufPos;
  1532   WriteRecordsHelper data;
  1533   data.mBuf = mRWBuf + mRWBufPos;
  1534   data.mSkip = mSkipEntries;
  1535   data.mProcessMax = (mRWBufSize - mRWBufPos) / sizeof(CacheIndexRecord);
  1536   MOZ_ASSERT(data.mProcessMax != 0 || mProcessEntries == 0); // TODO make sure we can write an empty index
  1537   data.mProcessed = 0;
  1538 #ifdef DEBUG
  1539   data.mHasMore = false;
  1540 #endif
  1542   mIndex.EnumerateEntries(&CacheIndex::CopyRecordsToRWBuf, &data);
  1543   MOZ_ASSERT(mRWBufPos != static_cast<uint32_t>(data.mBuf - mRWBuf) ||
  1544              mProcessEntries == 0);
  1545   mRWBufPos = data.mBuf - mRWBuf;
  1546   mSkipEntries += data.mProcessed;
  1547   MOZ_ASSERT(mSkipEntries <= mProcessEntries);
  1549   mRWHash->Update(mRWBuf + hashOffset, mRWBufPos - hashOffset);
  1551   if (mSkipEntries == mProcessEntries) {
  1552     MOZ_ASSERT(!data.mHasMore);
  1554     // We've processed all records
  1555     if (mRWBufPos + sizeof(CacheHash::Hash32_t) > mRWBufSize) {
  1556       // realloc buffer to spare another write cycle
  1557       mRWBufSize = mRWBufPos + sizeof(CacheHash::Hash32_t);
  1558       mRWBuf = static_cast<char *>(moz_xrealloc(mRWBuf, mRWBufSize));
  1561     NetworkEndian::writeUint32(mRWBuf + mRWBufPos, mRWHash->GetHash());
  1562     mRWBufPos += sizeof(CacheHash::Hash32_t);
  1563   } else {
  1564     MOZ_ASSERT(data.mHasMore);
  1567   rv = CacheFileIOManager::Write(mIndexHandle, fileOffset, mRWBuf, mRWBufPos,
  1568                                  mSkipEntries == mProcessEntries, this);
  1569   if (NS_FAILED(rv)) {
  1570     LOG(("CacheIndex::WriteRecords() - CacheFileIOManager::Write() failed "
  1571          "synchronously [rv=0x%08x]", rv));
  1572     FinishWrite(false);
  1575   mRWBufPos = 0;
  1578 void
  1579 CacheIndex::FinishWrite(bool aSucceeded)
  1581   LOG(("CacheIndex::FinishWrite() [succeeded=%d]", aSucceeded));
  1583   MOZ_ASSERT((!aSucceeded && mState == SHUTDOWN) || mState == WRITING);
  1585   AssertOwnsLock();
  1587   mIndexHandle = nullptr;
  1588   mRWHash = nullptr;
  1589   ReleaseBuffer();
  1591   if (aSucceeded) {
  1592     // Opening of the file must not be in progress if writing succeeded.
  1593     MOZ_ASSERT(!mIndexFileOpener);
  1595     mIndex.EnumerateEntries(&CacheIndex::ApplyIndexChanges, this);
  1596     mIndexOnDiskIsValid = true;
  1597   } else {
  1598     if (mIndexFileOpener) {
  1599       // If opening of the file is still in progress (e.g. WRITE process was
  1600       // canceled by RemoveAll()) then we need to cancel the opener to make sure
  1601       // that OnFileOpenedInternal() won't be called.
  1602       mIndexFileOpener->Cancel();
  1603       mIndexFileOpener = nullptr;
  1607   ProcessPendingOperations();
  1608   mIndexStats.Log();
  1610   if (mState == WRITING) {
  1611     ChangeState(READY);
  1612     mLastDumpTime = TimeStamp::NowLoRes();
  1616 // static
  1617 PLDHashOperator
  1618 CacheIndex::CopyRecordsToRWBuf(CacheIndexEntry *aEntry, void* aClosure)
  1620   if (aEntry->IsRemoved()) {
  1621     return PL_DHASH_NEXT;
  1624   if (!aEntry->IsInitialized()) {
  1625     return PL_DHASH_NEXT;
  1628   if (aEntry->IsFileEmpty()) {
  1629     return PL_DHASH_NEXT;
  1632   WriteRecordsHelper *data = static_cast<WriteRecordsHelper *>(aClosure);
  1633   if (data->mSkip) {
  1634     data->mSkip--;
  1635     return PL_DHASH_NEXT;
  1638   if (data->mProcessed == data->mProcessMax) {
  1639 #ifdef DEBUG
  1640     data->mHasMore = true;
  1641 #endif
  1642     return PL_DHASH_STOP;
  1645   aEntry->WriteToBuf(data->mBuf);
  1646   data->mBuf += sizeof(CacheIndexRecord);
  1647   data->mProcessed++;
  1649   return PL_DHASH_NEXT;
  1652 // static
  1653 PLDHashOperator
  1654 CacheIndex::ApplyIndexChanges(CacheIndexEntry *aEntry, void* aClosure)
  1656   CacheIndex *index = static_cast<CacheIndex *>(aClosure);
  1658   CacheIndexEntryAutoManage emng(aEntry->Hash(), index);
  1660   if (aEntry->IsRemoved()) {
  1661     emng.DoNotSearchInIndex();
  1662     return PL_DHASH_REMOVE;
  1665   if (aEntry->IsDirty()) {
  1666     aEntry->ClearDirty();
  1669   return PL_DHASH_NEXT;
  1672 nsresult
  1673 CacheIndex::GetFile(const nsACString &aName, nsIFile **_retval)
  1675   nsresult rv;
  1677   nsCOMPtr<nsIFile> file;
  1678   rv = mCacheDirectory->Clone(getter_AddRefs(file));
  1679   NS_ENSURE_SUCCESS(rv, rv);
  1681   rv = file->AppendNative(aName);
  1682   NS_ENSURE_SUCCESS(rv, rv);
  1684   file.swap(*_retval);
  1685   return NS_OK;
  1688 nsresult
  1689 CacheIndex::RemoveFile(const nsACString &aName)
  1691   MOZ_ASSERT(mState == SHUTDOWN);
  1693   nsresult rv;
  1695   nsCOMPtr<nsIFile> file;
  1696   rv = GetFile(aName, getter_AddRefs(file));
  1697   NS_ENSURE_SUCCESS(rv, rv);
  1699   bool exists;
  1700   rv = file->Exists(&exists);
  1701   NS_ENSURE_SUCCESS(rv, rv);
  1703   if (exists) {
  1704     rv = file->Remove(false);
  1705     if (NS_FAILED(rv)) {
  1706       LOG(("CacheIndex::RemoveFile() - Cannot remove old entry file from disk."
  1707            "[name=%s]", PromiseFlatCString(aName).get()));
  1708       NS_WARNING("Cannot remove old entry file from the disk");
  1709       return rv;
  1713   return NS_OK;
  1716 void
  1717 CacheIndex::RemoveIndexFromDisk()
  1719   LOG(("CacheIndex::RemoveIndexFromDisk()"));
  1721   RemoveFile(NS_LITERAL_CSTRING(kIndexName));
  1722   RemoveFile(NS_LITERAL_CSTRING(kTempIndexName));
  1723   RemoveFile(NS_LITERAL_CSTRING(kJournalName));
  1726 class WriteLogHelper
  1728 public:
  1729   WriteLogHelper(PRFileDesc *aFD)
  1730     : mStatus(NS_OK)
  1731     , mFD(aFD)
  1732     , mBufSize(kMaxBufSize)
  1733     , mBufPos(0)
  1735     mHash = new CacheHash();
  1736     mBuf = static_cast<char *>(moz_xmalloc(mBufSize));
  1739   ~WriteLogHelper() {
  1740     free(mBuf);
  1743   nsresult AddEntry(CacheIndexEntry *aEntry);
  1744   nsresult Finish();
  1746 private:
  1748   nsresult FlushBuffer();
  1750   nsresult            mStatus;
  1751   PRFileDesc         *mFD;
  1752   char               *mBuf;
  1753   uint32_t            mBufSize;
  1754   int32_t             mBufPos;
  1755   nsRefPtr<CacheHash> mHash;
  1756 };
  1758 nsresult
  1759 WriteLogHelper::AddEntry(CacheIndexEntry *aEntry)
  1761   nsresult rv;
  1763   if (NS_FAILED(mStatus)) {
  1764     return mStatus;
  1767   if (mBufPos + sizeof(CacheIndexRecord) > mBufSize) {
  1768     mHash->Update(mBuf, mBufPos);
  1770     rv = FlushBuffer();
  1771     if (NS_FAILED(rv)) {
  1772       mStatus = rv;
  1773       return rv;
  1775     MOZ_ASSERT(mBufPos + sizeof(CacheIndexRecord) <= mBufSize);
  1778   aEntry->WriteToBuf(mBuf + mBufPos);
  1779   mBufPos += sizeof(CacheIndexRecord);
  1781   return NS_OK;
  1784 nsresult
  1785 WriteLogHelper::Finish()
  1787   nsresult rv;
  1789   if (NS_FAILED(mStatus)) {
  1790     return mStatus;
  1793   mHash->Update(mBuf, mBufPos);
  1794   if (mBufPos + sizeof(CacheHash::Hash32_t) > mBufSize) {
  1795     rv = FlushBuffer();
  1796     if (NS_FAILED(rv)) {
  1797       mStatus = rv;
  1798       return rv;
  1800     MOZ_ASSERT(mBufPos + sizeof(CacheHash::Hash32_t) <= mBufSize);
  1803   NetworkEndian::writeUint32(mBuf + mBufPos, mHash->GetHash());
  1804   mBufPos += sizeof(CacheHash::Hash32_t);
  1806   rv = FlushBuffer();
  1807   NS_ENSURE_SUCCESS(rv, rv);
  1809   mStatus = NS_ERROR_UNEXPECTED; // Don't allow any other operation
  1810   return NS_OK;
  1813 nsresult
  1814 WriteLogHelper::FlushBuffer()
  1816   MOZ_ASSERT(NS_SUCCEEDED(mStatus));
  1818   int32_t bytesWritten = PR_Write(mFD, mBuf, mBufPos);
  1820   if (bytesWritten != mBufPos) {
  1821     return NS_ERROR_FAILURE;
  1824   mBufPos = 0;
  1825   return NS_OK;
  1828 nsresult
  1829 CacheIndex::WriteLogToDisk()
  1831   LOG(("CacheIndex::WriteLogToDisk()"));
  1833   nsresult rv;
  1835   MOZ_ASSERT(mPendingUpdates.Count() == 0);
  1836   MOZ_ASSERT(mState == SHUTDOWN);
  1838   RemoveFile(NS_LITERAL_CSTRING(kTempIndexName));
  1840   nsCOMPtr<nsIFile> indexFile;
  1841   rv = GetFile(NS_LITERAL_CSTRING(kIndexName), getter_AddRefs(indexFile));
  1842   NS_ENSURE_SUCCESS(rv, rv);
  1844   nsCOMPtr<nsIFile> logFile;
  1845   rv = GetFile(NS_LITERAL_CSTRING(kJournalName), getter_AddRefs(logFile));
  1846   NS_ENSURE_SUCCESS(rv, rv);
  1848   mIndexStats.Log();
  1850   PRFileDesc *fd = nullptr;
  1851   rv = logFile->OpenNSPRFileDesc(PR_RDWR | PR_CREATE_FILE | PR_TRUNCATE,
  1852                                  0600, &fd);
  1853   NS_ENSURE_SUCCESS(rv, rv);
  1855   WriteLogHelper wlh(fd);
  1856   mIndex.EnumerateEntries(&CacheIndex::WriteEntryToLog, &wlh);
  1858   rv = wlh.Finish();
  1859   PR_Close(fd);
  1860   NS_ENSURE_SUCCESS(rv, rv);
  1862   rv = indexFile->OpenNSPRFileDesc(PR_RDWR, 0600, &fd);
  1863   NS_ENSURE_SUCCESS(rv, rv);
  1865   CacheIndexHeader header;
  1866   int32_t bytesRead = PR_Read(fd, &header, sizeof(CacheIndexHeader));
  1867   if (bytesRead != sizeof(CacheIndexHeader)) {
  1868     PR_Close(fd);
  1869     return NS_ERROR_FAILURE;
  1872   NetworkEndian::writeUint32(&header.mIsDirty, 0);
  1874   int64_t offset = PR_Seek64(fd, 0, PR_SEEK_SET);
  1875   if (offset == -1) {
  1876     PR_Close(fd);
  1877     return NS_ERROR_FAILURE;
  1880   int32_t bytesWritten = PR_Write(fd, &header, sizeof(CacheIndexHeader));
  1881   PR_Close(fd);
  1882   if (bytesWritten != sizeof(CacheIndexHeader)) {
  1883     return NS_ERROR_FAILURE;
  1886   return NS_OK;
  1889 // static
  1890 PLDHashOperator
  1891 CacheIndex::WriteEntryToLog(CacheIndexEntry *aEntry, void* aClosure)
  1893   WriteLogHelper *wlh = static_cast<WriteLogHelper *>(aClosure);
  1895   if (aEntry->IsRemoved() || aEntry->IsDirty()) {
  1896     wlh->AddEntry(aEntry);
  1899   return PL_DHASH_REMOVE;
  1902 void
  1903 CacheIndex::ReadIndexFromDisk()
  1905   LOG(("CacheIndex::ReadIndexFromDisk()"));
  1907   nsresult rv;
  1909   AssertOwnsLock();
  1910   MOZ_ASSERT(mState == INITIAL);
  1912   ChangeState(READING);
  1914   mIndexFileOpener = new FileOpenHelper(this);
  1915   rv = CacheFileIOManager::OpenFile(NS_LITERAL_CSTRING(kIndexName),
  1916                                     CacheFileIOManager::SPECIAL_FILE |
  1917                                     CacheFileIOManager::OPEN,
  1918                                     true,
  1919                                     mIndexFileOpener);
  1920   if (NS_FAILED(rv)) {
  1921     LOG(("CacheIndex::ReadIndexFromDisk() - CacheFileIOManager::OpenFile() "
  1922          "failed [rv=0x%08x, file=%s]", rv, kIndexName));
  1923     FinishRead(false);
  1924     return;
  1927   mJournalFileOpener = new FileOpenHelper(this);
  1928   rv = CacheFileIOManager::OpenFile(NS_LITERAL_CSTRING(kJournalName),
  1929                                     CacheFileIOManager::SPECIAL_FILE |
  1930                                     CacheFileIOManager::OPEN,
  1931                                     true,
  1932                                     mJournalFileOpener);
  1933   if (NS_FAILED(rv)) {
  1934     LOG(("CacheIndex::ReadIndexFromDisk() - CacheFileIOManager::OpenFile() "
  1935          "failed [rv=0x%08x, file=%s]", rv, kJournalName));
  1936     FinishRead(false);
  1939   mTmpFileOpener = new FileOpenHelper(this);
  1940   rv = CacheFileIOManager::OpenFile(NS_LITERAL_CSTRING(kTempIndexName),
  1941                                     CacheFileIOManager::SPECIAL_FILE |
  1942                                     CacheFileIOManager::OPEN,
  1943                                     true,
  1944                                     mTmpFileOpener);
  1945   if (NS_FAILED(rv)) {
  1946     LOG(("CacheIndex::ReadIndexFromDisk() - CacheFileIOManager::OpenFile() "
  1947          "failed [rv=0x%08x, file=%s]", rv, kTempIndexName));
  1948     FinishRead(false);
  1952 void
  1953 CacheIndex::StartReadingIndex()
  1955   LOG(("CacheIndex::StartReadingIndex()"));
  1957   nsresult rv;
  1959   AssertOwnsLock();
  1961   MOZ_ASSERT(mIndexHandle);
  1962   MOZ_ASSERT(mState == READING);
  1963   MOZ_ASSERT(!mIndexOnDiskIsValid);
  1964   MOZ_ASSERT(!mDontMarkIndexClean);
  1965   MOZ_ASSERT(!mJournalReadSuccessfully);
  1966   MOZ_ASSERT(mIndexHandle->FileSize() >= 0);
  1968   int64_t entriesSize = mIndexHandle->FileSize() - sizeof(CacheIndexHeader) -
  1969                         sizeof(CacheHash::Hash32_t);
  1971   if (entriesSize < 0 || entriesSize % sizeof(CacheIndexRecord)) {
  1972     LOG(("CacheIndex::StartReadingIndex() - Index is corrupted"));
  1973     FinishRead(false);
  1974     return;
  1977   AllocBuffer();
  1978   mSkipEntries = 0;
  1979   mRWHash = new CacheHash();
  1981   mRWBufPos = std::min(mRWBufSize,
  1982                        static_cast<uint32_t>(mIndexHandle->FileSize()));
  1984   rv = CacheFileIOManager::Read(mIndexHandle, 0, mRWBuf, mRWBufPos, true, this);
  1985   if (NS_FAILED(rv)) {
  1986     LOG(("CacheIndex::StartReadingIndex() - CacheFileIOManager::Read() failed "
  1987          "synchronously [rv=0x%08x]", rv));
  1988     FinishRead(false);
  1992 void
  1993 CacheIndex::ParseRecords()
  1995   LOG(("CacheIndex::ParseRecords()"));
  1997   nsresult rv;
  1999   AssertOwnsLock();
  2001   uint32_t entryCnt = (mIndexHandle->FileSize() - sizeof(CacheIndexHeader) -
  2002                      sizeof(CacheHash::Hash32_t)) / sizeof(CacheIndexRecord);
  2003   uint32_t pos = 0;
  2005   if (!mSkipEntries) {
  2006     CacheIndexHeader *hdr = reinterpret_cast<CacheIndexHeader *>(
  2007                               moz_xmalloc(sizeof(CacheIndexHeader)));
  2008     memcpy(hdr, mRWBuf, sizeof(CacheIndexHeader));
  2010     if (NetworkEndian::readUint32(&hdr->mVersion) != kIndexVersion) {
  2011       free(hdr);
  2012       FinishRead(false);
  2013       return;
  2016     mIndexTimeStamp = NetworkEndian::readUint32(&hdr->mTimeStamp);
  2018     if (NetworkEndian::readUint32(&hdr->mIsDirty)) {
  2019       if (mJournalHandle) {
  2020         CacheFileIOManager::DoomFile(mJournalHandle, nullptr);
  2021         mJournalHandle = nullptr;
  2023       free(hdr);
  2024     } else {
  2025       NetworkEndian::writeUint32(&hdr->mIsDirty, 1);
  2027       // Mark index dirty. The buffer is freed by CacheFileIOManager when
  2028       // nullptr is passed as the listener and the call doesn't fail
  2029       // synchronously.
  2030       rv = CacheFileIOManager::Write(mIndexHandle, 0,
  2031                                      reinterpret_cast<char *>(hdr),
  2032                                      sizeof(CacheIndexHeader), true, nullptr);
  2033       if (NS_FAILED(rv)) {
  2034         // This is not fatal, just free the memory
  2035         free(hdr);
  2039     pos += sizeof(CacheIndexHeader);
  2042   uint32_t hashOffset = pos;
  2044   while (pos + sizeof(CacheIndexRecord) <= mRWBufPos &&
  2045          mSkipEntries != entryCnt) {
  2046     CacheIndexRecord *rec = reinterpret_cast<CacheIndexRecord *>(mRWBuf + pos);
  2047     CacheIndexEntry tmpEntry(&rec->mHash);
  2048     tmpEntry.ReadFromBuf(mRWBuf + pos);
  2050     if (tmpEntry.IsDirty() || !tmpEntry.IsInitialized() ||
  2051         tmpEntry.IsFileEmpty() || tmpEntry.IsFresh() || tmpEntry.IsRemoved()) {
  2052       LOG(("CacheIndex::ParseRecords() - Invalid entry found in index, removing"
  2053            " whole index [dirty=%d, initialized=%d, fileEmpty=%d, fresh=%d, "
  2054            "removed=%d]", tmpEntry.IsDirty(), tmpEntry.IsInitialized(),
  2055            tmpEntry.IsFileEmpty(), tmpEntry.IsFresh(), tmpEntry.IsRemoved()));
  2056       FinishRead(false);
  2057       return;
  2060     CacheIndexEntryAutoManage emng(tmpEntry.Hash(), this);
  2062     CacheIndexEntry *entry = mIndex.PutEntry(*tmpEntry.Hash());
  2063     *entry = tmpEntry;
  2065     pos += sizeof(CacheIndexRecord);
  2066     mSkipEntries++;
  2069   mRWHash->Update(mRWBuf + hashOffset, pos - hashOffset);
  2071   if (pos != mRWBufPos) {
  2072     memmove(mRWBuf, mRWBuf + pos, mRWBufPos - pos);
  2073     mRWBufPos -= pos;
  2074     pos = 0;
  2077   int64_t fileOffset = sizeof(CacheIndexHeader) +
  2078                        mSkipEntries * sizeof(CacheIndexRecord) + mRWBufPos;
  2080   MOZ_ASSERT(fileOffset <= mIndexHandle->FileSize());
  2081   if (fileOffset == mIndexHandle->FileSize()) {
  2082     if (mRWHash->GetHash() != NetworkEndian::readUint32(mRWBuf)) {
  2083       LOG(("CacheIndex::ParseRecords() - Hash mismatch, [is %x, should be %x]",
  2084            mRWHash->GetHash(),
  2085            NetworkEndian::readUint32(mRWBuf)));
  2086       FinishRead(false);
  2087       return;
  2090     mIndexOnDiskIsValid = true;
  2091     mJournalReadSuccessfully = false;
  2093     if (mJournalHandle) {
  2094       StartReadingJournal();
  2095     } else {
  2096       FinishRead(false);
  2099     return;
  2102   pos = mRWBufPos;
  2103   uint32_t toRead = std::min(mRWBufSize - pos,
  2104                              static_cast<uint32_t>(mIndexHandle->FileSize() -
  2105                                                    fileOffset));
  2106   mRWBufPos = pos + toRead;
  2108   rv = CacheFileIOManager::Read(mIndexHandle, fileOffset, mRWBuf + pos, toRead,
  2109                                 true, this);
  2110   if (NS_FAILED(rv)) {
  2111     LOG(("CacheIndex::ParseRecords() - CacheFileIOManager::Read() failed "
  2112          "synchronously [rv=0x%08x]", rv));
  2113     FinishRead(false);
  2114     return;
  2118 void
  2119 CacheIndex::StartReadingJournal()
  2121   LOG(("CacheIndex::StartReadingJournal()"));
  2123   nsresult rv;
  2125   AssertOwnsLock();
  2127   MOZ_ASSERT(mJournalHandle);
  2128   MOZ_ASSERT(mIndexOnDiskIsValid);
  2129   MOZ_ASSERT(mTmpJournal.Count() == 0);
  2130   MOZ_ASSERT(mJournalHandle->FileSize() >= 0);
  2132   int64_t entriesSize = mJournalHandle->FileSize() -
  2133                         sizeof(CacheHash::Hash32_t);
  2135   if (entriesSize < 0 || entriesSize % sizeof(CacheIndexRecord)) {
  2136     LOG(("CacheIndex::StartReadingJournal() - Journal is corrupted"));
  2137     FinishRead(false);
  2138     return;
  2141   mSkipEntries = 0;
  2142   mRWHash = new CacheHash();
  2144   mRWBufPos = std::min(mRWBufSize,
  2145                        static_cast<uint32_t>(mJournalHandle->FileSize()));
  2147   rv = CacheFileIOManager::Read(mJournalHandle, 0, mRWBuf, mRWBufPos, true, this);
  2148   if (NS_FAILED(rv)) {
  2149     LOG(("CacheIndex::StartReadingJournal() - CacheFileIOManager::Read() failed"
  2150          " synchronously [rv=0x%08x]", rv));
  2151     FinishRead(false);
  2155 void
  2156 CacheIndex::ParseJournal()
  2158   LOG(("CacheIndex::ParseRecords()"));
  2160   nsresult rv;
  2162   AssertOwnsLock();
  2164   uint32_t entryCnt = (mJournalHandle->FileSize() -
  2165                        sizeof(CacheHash::Hash32_t)) / sizeof(CacheIndexRecord);
  2167   uint32_t pos = 0;
  2169   while (pos + sizeof(CacheIndexRecord) <= mRWBufPos &&
  2170          mSkipEntries != entryCnt) {
  2171     CacheIndexRecord *rec = reinterpret_cast<CacheIndexRecord *>(mRWBuf + pos);
  2172     CacheIndexEntry tmpEntry(&rec->mHash);
  2173     tmpEntry.ReadFromBuf(mRWBuf + pos);
  2175     CacheIndexEntry *entry = mTmpJournal.PutEntry(*tmpEntry.Hash());
  2176     *entry = tmpEntry;
  2178     if (entry->IsDirty() || entry->IsFresh()) {
  2179       LOG(("CacheIndex::ParseJournal() - Invalid entry found in journal, "
  2180            "ignoring whole journal [dirty=%d, fresh=%d]", entry->IsDirty(),
  2181            entry->IsFresh()));
  2182       FinishRead(false);
  2183       return;
  2186     pos += sizeof(CacheIndexRecord);
  2187     mSkipEntries++;
  2190   mRWHash->Update(mRWBuf, pos);
  2192   if (pos != mRWBufPos) {
  2193     memmove(mRWBuf, mRWBuf + pos, mRWBufPos - pos);
  2194     mRWBufPos -= pos;
  2195     pos = 0;
  2198   int64_t fileOffset = mSkipEntries * sizeof(CacheIndexRecord) + mRWBufPos;
  2200   MOZ_ASSERT(fileOffset <= mJournalHandle->FileSize());
  2201   if (fileOffset == mJournalHandle->FileSize()) {
  2202     if (mRWHash->GetHash() != NetworkEndian::readUint32(mRWBuf)) {
  2203       LOG(("CacheIndex::ParseJournal() - Hash mismatch, [is %x, should be %x]",
  2204            mRWHash->GetHash(),
  2205            NetworkEndian::readUint32(mRWBuf)));
  2206       FinishRead(false);
  2207       return;
  2210     mJournalReadSuccessfully = true;
  2211     FinishRead(true);
  2212     return;
  2215   pos = mRWBufPos;
  2216   uint32_t toRead = std::min(mRWBufSize - pos,
  2217                              static_cast<uint32_t>(mJournalHandle->FileSize() -
  2218                                                    fileOffset));
  2219   mRWBufPos = pos + toRead;
  2221   rv = CacheFileIOManager::Read(mJournalHandle, fileOffset, mRWBuf + pos,
  2222                                 toRead, true, this);
  2223   if (NS_FAILED(rv)) {
  2224     LOG(("CacheIndex::ParseJournal() - CacheFileIOManager::Read() failed "
  2225          "synchronously [rv=0x%08x]", rv));
  2226     FinishRead(false);
  2227     return;
  2231 void
  2232 CacheIndex::MergeJournal()
  2234   LOG(("CacheIndex::MergeJournal()"));
  2236   AssertOwnsLock();
  2238   mTmpJournal.EnumerateEntries(&CacheIndex::ProcessJournalEntry, this);
  2240   MOZ_ASSERT(mTmpJournal.Count() == 0);
  2243 // static
  2244 PLDHashOperator
  2245 CacheIndex::ProcessJournalEntry(CacheIndexEntry *aEntry, void* aClosure)
  2247   CacheIndex *index = static_cast<CacheIndex *>(aClosure);
  2249   LOG(("CacheFile::ProcessJournalEntry() [hash=%08x%08x%08x%08x%08x]",
  2250        LOGSHA1(aEntry->Hash())));
  2252   CacheIndexEntry *entry = index->mIndex.GetEntry(*aEntry->Hash());
  2254   CacheIndexEntryAutoManage emng(aEntry->Hash(), index);
  2256   if (aEntry->IsRemoved()) {
  2257     if (entry) {
  2258       entry->MarkRemoved();
  2259       entry->MarkDirty();
  2261   } else {
  2262     if (!entry) {
  2263       entry = index->mIndex.PutEntry(*aEntry->Hash());
  2266     *entry = *aEntry;
  2267     entry->MarkDirty();
  2270   return PL_DHASH_REMOVE;
  2273 void
  2274 CacheIndex::EnsureNoFreshEntry()
  2276 #ifdef DEBUG_STATS
  2277   CacheIndexStats debugStats;
  2278   debugStats.DisableLogging();
  2279   mIndex.EnumerateEntries(&CacheIndex::SumIndexStats, &debugStats);
  2280   MOZ_ASSERT(debugStats.Fresh() == 0);
  2281 #endif
  2284 void
  2285 CacheIndex::EnsureCorrectStats()
  2287 #ifdef DEBUG_STATS
  2288   MOZ_ASSERT(mPendingUpdates.Count() == 0);
  2289   CacheIndexStats debugStats;
  2290   debugStats.DisableLogging();
  2291   mIndex.EnumerateEntries(&CacheIndex::SumIndexStats, &debugStats);
  2292   MOZ_ASSERT(debugStats == mIndexStats);
  2293 #endif
  2296 // static
  2297 PLDHashOperator
  2298 CacheIndex::SumIndexStats(CacheIndexEntry *aEntry, void* aClosure)
  2300   CacheIndexStats *stats = static_cast<CacheIndexStats *>(aClosure);
  2301   stats->BeforeChange(nullptr);
  2302   stats->AfterChange(aEntry);
  2303   return PL_DHASH_NEXT;
  2306 void
  2307 CacheIndex::FinishRead(bool aSucceeded)
  2309   LOG(("CacheIndex::FinishRead() [succeeded=%d]", aSucceeded));
  2310   AssertOwnsLock();
  2312   MOZ_ASSERT((!aSucceeded && mState == SHUTDOWN) || mState == READING);
  2314   MOZ_ASSERT(
  2315     // -> rebuild
  2316     (!aSucceeded && !mIndexOnDiskIsValid && !mJournalReadSuccessfully) ||
  2317     // -> update
  2318     (!aSucceeded && mIndexOnDiskIsValid && !mJournalReadSuccessfully) ||
  2319     // -> ready
  2320     (aSucceeded && mIndexOnDiskIsValid && mJournalReadSuccessfully));
  2322   if (mState == SHUTDOWN) {
  2323     RemoveFile(NS_LITERAL_CSTRING(kTempIndexName));
  2324     RemoveFile(NS_LITERAL_CSTRING(kJournalName));
  2325   } else {
  2326     if (mIndexHandle && !mIndexOnDiskIsValid) {
  2327       CacheFileIOManager::DoomFile(mIndexHandle, nullptr);
  2330     if (mJournalHandle) {
  2331       CacheFileIOManager::DoomFile(mJournalHandle, nullptr);
  2335   if (mIndexFileOpener) {
  2336     mIndexFileOpener->Cancel();
  2337     mIndexFileOpener = nullptr;
  2339   if (mJournalFileOpener) {
  2340     mJournalFileOpener->Cancel();
  2341     mJournalFileOpener = nullptr;
  2343   if (mTmpFileOpener) {
  2344     mTmpFileOpener->Cancel();
  2345     mTmpFileOpener = nullptr;
  2348   mIndexHandle = nullptr;
  2349   mJournalHandle = nullptr;
  2350   mRWHash = nullptr;
  2351   ReleaseBuffer();
  2353   if (mState == SHUTDOWN) {
  2354     return;
  2357   if (!mIndexOnDiskIsValid) {
  2358     MOZ_ASSERT(mTmpJournal.Count() == 0);
  2359     EnsureNoFreshEntry();
  2360     ProcessPendingOperations();
  2361     // Remove all entries that we haven't seen during this session
  2362     mIndex.EnumerateEntries(&CacheIndex::RemoveNonFreshEntries, this);
  2363     StartUpdatingIndex(true);
  2364     return;
  2367   if (!mJournalReadSuccessfully) {
  2368     mTmpJournal.Clear();
  2369     EnsureNoFreshEntry();
  2370     ProcessPendingOperations();
  2371     StartUpdatingIndex(false);
  2372     return;
  2375   MergeJournal();
  2376   EnsureNoFreshEntry();
  2377   ProcessPendingOperations();
  2378   mIndexStats.Log();
  2380   ChangeState(READY);
  2381   mLastDumpTime = TimeStamp::NowLoRes(); // Do not dump new index immediately
  2384 // static
  2385 void
  2386 CacheIndex::DelayedUpdate(nsITimer *aTimer, void *aClosure)
  2388   LOG(("CacheIndex::DelayedUpdate()"));
  2390   nsresult rv;
  2391   nsRefPtr<CacheIndex> index = gInstance;
  2393   if (!index) {
  2394     return;
  2397   CacheIndexAutoLock lock(index);
  2399   index->mUpdateTimer = nullptr;
  2401   if (!index->IsIndexUsable()) {
  2402     return;
  2405   if (index->mState == READY && index->mShuttingDown) {
  2406     return;
  2409   // mUpdateEventPending must be false here since StartUpdatingIndex() won't
  2410   // schedule timer if it is true.
  2411   MOZ_ASSERT(!index->mUpdateEventPending);
  2412   if (index->mState != BUILDING && index->mState != UPDATING) {
  2413     LOG(("CacheIndex::DelayedUpdate() - Update was canceled"));
  2414     return;
  2417   // We need to redispatch to run with lower priority
  2418   nsRefPtr<CacheIOThread> ioThread = CacheFileIOManager::IOThread();
  2419   MOZ_ASSERT(ioThread);
  2421   index->mUpdateEventPending = true;
  2422   rv = ioThread->Dispatch(index, CacheIOThread::INDEX);
  2423   if (NS_FAILED(rv)) {
  2424     index->mUpdateEventPending = false;
  2425     NS_WARNING("CacheIndex::DelayedUpdate() - Can't dispatch event");
  2426     LOG(("CacheIndex::DelayedUpdate() - Can't dispatch event" ));
  2427     index->FinishUpdate(false);
  2431 nsresult
  2432 CacheIndex::ScheduleUpdateTimer(uint32_t aDelay)
  2434   LOG(("CacheIndex::ScheduleUpdateTimer() [delay=%u]", aDelay));
  2436   MOZ_ASSERT(!mUpdateTimer);
  2438   nsresult rv;
  2440   nsCOMPtr<nsITimer> timer = do_CreateInstance("@mozilla.org/timer;1", &rv);
  2441   NS_ENSURE_SUCCESS(rv, rv);
  2443   nsCOMPtr<nsIEventTarget> ioTarget = CacheFileIOManager::IOTarget();
  2444   MOZ_ASSERT(ioTarget);
  2446   rv = timer->SetTarget(ioTarget);
  2447   NS_ENSURE_SUCCESS(rv, rv);
  2449   rv = timer->InitWithFuncCallback(CacheIndex::DelayedUpdate, nullptr,
  2450                                    aDelay, nsITimer::TYPE_ONE_SHOT);
  2451   NS_ENSURE_SUCCESS(rv, rv);
  2453   mUpdateTimer.swap(timer);
  2454   return NS_OK;
  2457 nsresult
  2458 CacheIndex::SetupDirectoryEnumerator()
  2460   MOZ_ASSERT(!NS_IsMainThread());
  2461   MOZ_ASSERT(!mDirEnumerator);
  2463   nsresult rv;
  2464   nsCOMPtr<nsIFile> file;
  2466   rv = mCacheDirectory->Clone(getter_AddRefs(file));
  2467   NS_ENSURE_SUCCESS(rv, rv);
  2469   rv = file->AppendNative(NS_LITERAL_CSTRING(kEntriesDir));
  2470   NS_ENSURE_SUCCESS(rv, rv);
  2472   bool exists;
  2473   rv = file->Exists(&exists);
  2474   NS_ENSURE_SUCCESS(rv, rv);
  2476   if (!exists) {
  2477     NS_WARNING("CacheIndex::SetupDirectoryEnumerator() - Entries directory "
  2478                "doesn't exist!");
  2479     LOG(("CacheIndex::SetupDirectoryEnumerator() - Entries directory doesn't "
  2480           "exist!" ));
  2481     return NS_ERROR_UNEXPECTED;
  2484   nsCOMPtr<nsISimpleEnumerator> enumerator;
  2485   rv = file->GetDirectoryEntries(getter_AddRefs(enumerator));
  2486   NS_ENSURE_SUCCESS(rv, rv);
  2488   mDirEnumerator = do_QueryInterface(enumerator, &rv);
  2489   NS_ENSURE_SUCCESS(rv, rv);
  2491   return NS_OK;
  2494 void
  2495 CacheIndex::InitEntryFromDiskData(CacheIndexEntry *aEntry,
  2496                                   CacheFileMetadata *aMetaData,
  2497                                   int64_t aFileSize)
  2499   aEntry->InitNew();
  2500   aEntry->MarkDirty();
  2501   aEntry->MarkFresh();
  2502   aEntry->Init(aMetaData->AppId(), aMetaData->IsAnonymous(),
  2503                aMetaData->IsInBrowser());
  2505   uint32_t expirationTime;
  2506   aMetaData->GetExpirationTime(&expirationTime);
  2507   aEntry->SetExpirationTime(expirationTime);
  2509   uint32_t frecency;
  2510   aMetaData->GetFrecency(&frecency);
  2511   aEntry->SetFrecency(frecency);
  2513   aEntry->SetFileSize(static_cast<uint32_t>(
  2514                         std::min(static_cast<int64_t>(PR_UINT32_MAX),
  2515                                  (aFileSize + 0x3FF) >> 10)));
  2518 bool
  2519 CacheIndex::IsUpdatePending()
  2521   AssertOwnsLock();
  2523   if (mUpdateTimer || mUpdateEventPending) {
  2524     return true;
  2527   return false;
  2530 void
  2531 CacheIndex::BuildIndex()
  2533   LOG(("CacheIndex::BuildIndex()"));
  2535   AssertOwnsLock();
  2537   MOZ_ASSERT(mPendingUpdates.Count() == 0);
  2539   nsresult rv;
  2541   if (!mDirEnumerator) {
  2543       // Do not do IO under the lock.
  2544       CacheIndexAutoUnlock unlock(this);
  2545       rv = SetupDirectoryEnumerator();
  2547     if (mState == SHUTDOWN) {
  2548       // The index was shut down while we released the lock. FinishUpdate() was
  2549       // already called from Shutdown(), so just simply return here.
  2550       return;
  2553     if (NS_FAILED(rv)) {
  2554       FinishUpdate(false);
  2555       return;
  2559   while (true) {
  2560     if (CacheIOThread::YieldAndRerun()) {
  2561       LOG(("CacheIndex::BuildIndex() - Breaking loop for higher level events."));
  2562       mUpdateEventPending = true;
  2563       return;
  2566     nsCOMPtr<nsIFile> file;
  2568       // Do not do IO under the lock.
  2569       CacheIndexAutoUnlock unlock(this);
  2570       rv = mDirEnumerator->GetNextFile(getter_AddRefs(file));
  2572     if (mState == SHUTDOWN) {
  2573       return;
  2575     if (!file) {
  2576       FinishUpdate(NS_SUCCEEDED(rv));
  2577       return;
  2580     nsAutoCString leaf;
  2581     rv = file->GetNativeLeafName(leaf);
  2582     if (NS_FAILED(rv)) {
  2583       LOG(("CacheIndex::BuildIndex() - GetNativeLeafName() failed! Skipping "
  2584            "file."));
  2585       mDontMarkIndexClean = true;
  2586       continue;
  2589     SHA1Sum::Hash hash;
  2590     rv = CacheFileIOManager::StrToHash(leaf, &hash);
  2591     if (NS_FAILED(rv)) {
  2592       LOG(("CacheIndex::BuildIndex() - Filename is not a hash, removing file. "
  2593            "[name=%s]", leaf.get()));
  2594       file->Remove(false);
  2595       continue;
  2598     CacheIndexEntry *entry = mIndex.GetEntry(hash);
  2599     if (entry && entry->IsRemoved()) {
  2600       LOG(("CacheIndex::BuildIndex() - Found file that should not exist. "
  2601            "[name=%s]", leaf.get()));
  2602       entry->Log();
  2603       MOZ_ASSERT(entry->IsFresh());
  2604       entry = nullptr;
  2607 #ifdef DEBUG
  2608     nsRefPtr<CacheFileHandle> handle;
  2609     CacheFileIOManager::gInstance->mHandles.GetHandle(&hash, false,
  2610                                                       getter_AddRefs(handle));
  2611 #endif
  2613     if (entry) {
  2614       // the entry is up to date
  2615       LOG(("CacheIndex::BuildIndex() - Skipping file because the entry is up to"
  2616            " date. [name=%s]", leaf.get()));
  2617       entry->Log();
  2618       MOZ_ASSERT(entry->IsFresh()); // The entry must be from this session
  2619       // there must be an active CacheFile if the entry is not initialized
  2620       MOZ_ASSERT(entry->IsInitialized() || handle);
  2621       continue;
  2624     MOZ_ASSERT(!handle);
  2626     nsRefPtr<CacheFileMetadata> meta = new CacheFileMetadata();
  2627     int64_t size = 0;
  2630       // Do not do IO under the lock.
  2631       CacheIndexAutoUnlock unlock(this);
  2632       rv = meta->SyncReadMetadata(file);
  2634       if (NS_SUCCEEDED(rv)) {
  2635         rv = file->GetFileSize(&size);
  2636         if (NS_FAILED(rv)) {
  2637           LOG(("CacheIndex::BuildIndex() - Cannot get filesize of file that was"
  2638                " successfully parsed. [name=%s]", leaf.get()));
  2642     if (mState == SHUTDOWN) {
  2643       return;
  2646     // Nobody could add the entry while the lock was released since we modify
  2647     // the index only on IO thread and this loop is executed on IO thread too.
  2648     entry = mIndex.GetEntry(hash);
  2649     MOZ_ASSERT(!entry || entry->IsRemoved());
  2651     if (NS_FAILED(rv)) {
  2652       LOG(("CacheIndex::BuildIndex() - CacheFileMetadata::SyncReadMetadata() "
  2653            "failed, removing file. [name=%s]", leaf.get()));
  2654       file->Remove(false);
  2655     } else {
  2656       CacheIndexEntryAutoManage entryMng(&hash, this);
  2657       entry = mIndex.PutEntry(hash);
  2658       InitEntryFromDiskData(entry, meta, size);
  2659       LOG(("CacheIndex::BuildIndex() - Added entry to index. [hash=%s]",
  2660            leaf.get()));
  2661       entry->Log();
  2665   NS_NOTREACHED("We should never get here");
  2668 bool
  2669 CacheIndex::StartUpdatingIndexIfNeeded(bool aSwitchingToReadyState)
  2671   // Start updating process when we are in or we are switching to READY state
  2672   // and index needs update, but not during shutdown or when removing all
  2673   // entries.
  2674   if ((mState == READY || aSwitchingToReadyState) && mIndexNeedsUpdate &&
  2675       !mShuttingDown && !mRemovingAll) {
  2676     LOG(("CacheIndex::StartUpdatingIndexIfNeeded() - starting update process"));
  2677     mIndexNeedsUpdate = false;
  2678     StartUpdatingIndex(false);
  2679     return true;
  2682   return false;
  2685 void
  2686 CacheIndex::StartUpdatingIndex(bool aRebuild)
  2688   LOG(("CacheIndex::StartUpdatingIndex() [rebuild=%d]", aRebuild));
  2690   AssertOwnsLock();
  2692   nsresult rv;
  2694   mIndexStats.Log();
  2696   ChangeState(aRebuild ? BUILDING : UPDATING);
  2697   mDontMarkIndexClean = false;
  2699   if (mShuttingDown || mRemovingAll) {
  2700     FinishUpdate(false);
  2701     return;
  2704   if (IsUpdatePending()) {
  2705     LOG(("CacheIndex::StartUpdatingIndex() - Update is already pending"));
  2706     return;
  2709   uint32_t elapsed = (TimeStamp::NowLoRes() - mStartTime).ToMilliseconds();
  2710   if (elapsed < kUpdateIndexStartDelay) {
  2711     LOG(("CacheIndex::StartUpdatingIndex() - %u ms elapsed since startup, "
  2712          "scheduling timer to fire in %u ms.", elapsed,
  2713          kUpdateIndexStartDelay - elapsed));
  2714     rv = ScheduleUpdateTimer(kUpdateIndexStartDelay - elapsed);
  2715     if (NS_SUCCEEDED(rv)) {
  2716       return;
  2719     LOG(("CacheIndex::StartUpdatingIndex() - ScheduleUpdateTimer() failed. "
  2720          "Starting update immediately."));
  2721   } else {
  2722     LOG(("CacheIndex::StartUpdatingIndex() - %u ms elapsed since startup, "
  2723          "starting update now.", elapsed));
  2726   nsRefPtr<CacheIOThread> ioThread = CacheFileIOManager::IOThread();
  2727   MOZ_ASSERT(ioThread);
  2729   // We need to dispatch an event even if we are on IO thread since we need to
  2730   // update the index with the correct priority.
  2731   mUpdateEventPending = true;
  2732   rv = ioThread->Dispatch(this, CacheIOThread::INDEX);
  2733   if (NS_FAILED(rv)) {
  2734     mUpdateEventPending = false;
  2735     NS_WARNING("CacheIndex::StartUpdatingIndex() - Can't dispatch event");
  2736     LOG(("CacheIndex::StartUpdatingIndex() - Can't dispatch event" ));
  2737     FinishUpdate(false);
  2741 void
  2742 CacheIndex::UpdateIndex()
  2744   LOG(("CacheIndex::UpdateIndex()"));
  2746   AssertOwnsLock();
  2748   MOZ_ASSERT(mPendingUpdates.Count() == 0);
  2750   nsresult rv;
  2752   if (!mDirEnumerator) {
  2754       // Do not do IO under the lock.
  2755       CacheIndexAutoUnlock unlock(this);
  2756       rv = SetupDirectoryEnumerator();
  2758     if (mState == SHUTDOWN) {
  2759       // The index was shut down while we released the lock. FinishUpdate() was
  2760       // already called from Shutdown(), so just simply return here.
  2761       return;
  2764     if (NS_FAILED(rv)) {
  2765       FinishUpdate(false);
  2766       return;
  2770   while (true) {
  2771     if (CacheIOThread::YieldAndRerun()) {
  2772       LOG(("CacheIndex::UpdateIndex() - Breaking loop for higher level "
  2773            "events."));
  2774       mUpdateEventPending = true;
  2775       return;
  2778     nsCOMPtr<nsIFile> file;
  2780       // Do not do IO under the lock.
  2781       CacheIndexAutoUnlock unlock(this);
  2782       rv = mDirEnumerator->GetNextFile(getter_AddRefs(file));
  2784     if (mState == SHUTDOWN) {
  2785       return;
  2787     if (!file) {
  2788       FinishUpdate(NS_SUCCEEDED(rv));
  2789       return;
  2792     nsAutoCString leaf;
  2793     rv = file->GetNativeLeafName(leaf);
  2794     if (NS_FAILED(rv)) {
  2795       LOG(("CacheIndex::UpdateIndex() - GetNativeLeafName() failed! Skipping "
  2796            "file."));
  2797       mDontMarkIndexClean = true;
  2798       continue;
  2801     SHA1Sum::Hash hash;
  2802     rv = CacheFileIOManager::StrToHash(leaf, &hash);
  2803     if (NS_FAILED(rv)) {
  2804       LOG(("CacheIndex::UpdateIndex() - Filename is not a hash, removing file. "
  2805            "[name=%s]", leaf.get()));
  2806       file->Remove(false);
  2807       continue;
  2810     CacheIndexEntry *entry = mIndex.GetEntry(hash);
  2811     if (entry && entry->IsRemoved()) {
  2812       if (entry->IsFresh()) {
  2813         LOG(("CacheIndex::UpdateIndex() - Found file that should not exist. "
  2814              "[name=%s]", leaf.get()));
  2815         entry->Log();
  2817       entry = nullptr;
  2820 #ifdef DEBUG
  2821     nsRefPtr<CacheFileHandle> handle;
  2822     CacheFileIOManager::gInstance->mHandles.GetHandle(&hash, false,
  2823                                                       getter_AddRefs(handle));
  2824 #endif
  2826     if (entry && entry->IsFresh()) {
  2827       // the entry is up to date
  2828       LOG(("CacheIndex::UpdateIndex() - Skipping file because the entry is up "
  2829            " to date. [name=%s]", leaf.get()));
  2830       entry->Log();
  2831       // there must be an active CacheFile if the entry is not initialized
  2832       MOZ_ASSERT(entry->IsInitialized() || handle);
  2833       continue;
  2836     MOZ_ASSERT(!handle);
  2838     if (entry) {
  2839       PRTime lastModifiedTime;
  2841         // Do not do IO under the lock.
  2842         CacheIndexAutoUnlock unlock(this);
  2843         rv = file->GetLastModifiedTime(&lastModifiedTime);
  2845       if (mState == SHUTDOWN) {
  2846         return;
  2848       if (NS_FAILED(rv)) {
  2849         LOG(("CacheIndex::UpdateIndex() - Cannot get lastModifiedTime. "
  2850              "[name=%s]", leaf.get()));
  2851         // Assume the file is newer than index
  2852       } else {
  2853         if (mIndexTimeStamp > (lastModifiedTime / PR_MSEC_PER_SEC)) {
  2854           LOG(("CacheIndex::UpdateIndex() - Skipping file because of last "
  2855                "modified time. [name=%s, indexTimeStamp=%u, "
  2856                "lastModifiedTime=%u]", leaf.get(), mIndexTimeStamp,
  2857                lastModifiedTime / PR_MSEC_PER_SEC));
  2859           CacheIndexEntryAutoManage entryMng(&hash, this);
  2860           entry->MarkFresh();
  2861           continue;
  2866     nsRefPtr<CacheFileMetadata> meta = new CacheFileMetadata();
  2867     int64_t size = 0;
  2870       // Do not do IO under the lock.
  2871       CacheIndexAutoUnlock unlock(this);
  2872       rv = meta->SyncReadMetadata(file);
  2874       if (NS_SUCCEEDED(rv)) {
  2875         rv = file->GetFileSize(&size);
  2876         if (NS_FAILED(rv)) {
  2877           LOG(("CacheIndex::UpdateIndex() - Cannot get filesize of file that "
  2878                "was successfully parsed. [name=%s]", leaf.get()));
  2882     if (mState == SHUTDOWN) {
  2883       return;
  2886     // Nobody could add the entry while the lock was released since we modify
  2887     // the index only on IO thread and this loop is executed on IO thread too.
  2888     entry = mIndex.GetEntry(hash);
  2889     MOZ_ASSERT(!entry || !entry->IsFresh());
  2891     CacheIndexEntryAutoManage entryMng(&hash, this);
  2893     if (NS_FAILED(rv)) {
  2894       LOG(("CacheIndex::UpdateIndex() - CacheFileMetadata::SyncReadMetadata() "
  2895            "failed, removing file. [name=%s]", leaf.get()));
  2896       file->Remove(false);
  2897       if (entry) {
  2898         entry->MarkRemoved();
  2899         entry->MarkFresh();
  2900         entry->MarkDirty();
  2902     } else {
  2903       entry = mIndex.PutEntry(hash);
  2904       InitEntryFromDiskData(entry, meta, size);
  2905       LOG(("CacheIndex::UpdateIndex() - Added/updated entry to/in index. "
  2906            "[hash=%s]", leaf.get()));
  2907       entry->Log();
  2911   NS_NOTREACHED("We should never get here");
  2914 void
  2915 CacheIndex::FinishUpdate(bool aSucceeded)
  2917   LOG(("CacheIndex::FinishUpdate() [succeeded=%d]", aSucceeded));
  2919   MOZ_ASSERT(mState == UPDATING || mState == BUILDING ||
  2920              (!aSucceeded && mState == SHUTDOWN));
  2922   AssertOwnsLock();
  2924   if (mDirEnumerator) {
  2925     if (NS_IsMainThread()) {
  2926       LOG(("CacheIndex::FinishUpdate() - posting of PreShutdownInternal failed?"
  2927            " Cannot safely release mDirEnumerator, leaking it!"));
  2928       NS_WARNING(("CacheIndex::FinishUpdate() - Leaking mDirEnumerator!"));
  2929       // This can happen only in case dispatching event to IO thread failed in
  2930       // CacheIndex::PreShutdown().
  2931       mDirEnumerator.forget(); // Leak it since dir enumerator is not threadsafe
  2932     } else {
  2933       mDirEnumerator->Close();
  2934       mDirEnumerator = nullptr;
  2938   if (!aSucceeded) {
  2939     mDontMarkIndexClean = true;
  2942   if (mState == SHUTDOWN) {
  2943     return;
  2946   if (mState == UPDATING && aSucceeded) {
  2947     // If we've iterated over all entries successfully then all entries that
  2948     // really exist on the disk are now marked as fresh. All non-fresh entries
  2949     // don't exist anymore and must be removed from the index.
  2950     mIndex.EnumerateEntries(&CacheIndex::RemoveNonFreshEntries, this);
  2953   // Make sure we won't start update. If the build or update failed, there is no
  2954   // reason to believe that it will succeed next time.
  2955   mIndexNeedsUpdate = false;
  2957   ChangeState(READY);
  2958   mLastDumpTime = TimeStamp::NowLoRes(); // Do not dump new index immediately
  2961 // static
  2962 PLDHashOperator
  2963 CacheIndex::RemoveNonFreshEntries(CacheIndexEntry *aEntry, void* aClosure)
  2965   if (aEntry->IsFresh()) {
  2966     return PL_DHASH_NEXT;
  2969   LOG(("CacheFile::RemoveNonFreshEntries() - Removing entry. "
  2970        "[hash=%08x%08x%08x%08x%08x]", LOGSHA1(aEntry->Hash())));
  2972   CacheIndex *index = static_cast<CacheIndex *>(aClosure);
  2974   CacheIndexEntryAutoManage emng(aEntry->Hash(), index);
  2975   emng.DoNotSearchInIndex();
  2977   return PL_DHASH_REMOVE;
  2980 #ifdef PR_LOGGING
  2981 // static
  2982 char const *
  2983 CacheIndex::StateString(EState aState)
  2985   switch (aState) {
  2986     case INITIAL:  return "INITIAL";
  2987     case READING:  return "READING";
  2988     case WRITING:  return "WRITING";
  2989     case BUILDING: return "BUILDING";
  2990     case UPDATING: return "UPDATING";
  2991     case READY:    return "READY";
  2992     case SHUTDOWN: return "SHUTDOWN";
  2995   MOZ_ASSERT(false, "Unexpected state!");
  2996   return "?";
  2998 #endif
  3000 void
  3001 CacheIndex::ChangeState(EState aNewState)
  3003   LOG(("CacheIndex::ChangeState() changing state %s -> %s", StateString(mState),
  3004        StateString(aNewState)));
  3006   // All pending updates should be processed before changing state
  3007   MOZ_ASSERT(mPendingUpdates.Count() == 0);
  3009   // PreShutdownInternal() should change the state to READY from every state. It
  3010   // may go through different states, but once we are in READY state the only
  3011   // possible transition is to SHUTDOWN state.
  3012   MOZ_ASSERT(!mShuttingDown || mState != READY || aNewState == SHUTDOWN);
  3014   // Start updating process when switching to READY state if needed
  3015   if (aNewState == READY && StartUpdatingIndexIfNeeded(true)) {
  3016     return;
  3019   // Try to evict entries over limit everytime we're leaving state READING,
  3020   // BUILDING or UPDATING, but not during shutdown or when removing all
  3021   // entries.
  3022   if (!mShuttingDown && !mRemovingAll && aNewState != SHUTDOWN &&
  3023       (mState == READING || mState == BUILDING || mState == UPDATING))  {
  3024     CacheFileIOManager::EvictIfOverLimit();
  3027   mState = aNewState;
  3029   if (mState != SHUTDOWN) {
  3030     CacheFileIOManager::CacheIndexStateChanged();
  3033   if (mState == READY && mDiskConsumptionObservers.Length()) {
  3034     for (uint32_t i = 0; i < mDiskConsumptionObservers.Length(); ++i) {
  3035       DiskConsumptionObserver* o = mDiskConsumptionObservers[i];
  3036       // Safe to call under the lock.  We always post to the main thread.
  3037       o->OnDiskConsumption(mIndexStats.Size() << 10);
  3040     mDiskConsumptionObservers.Clear();
  3044 void
  3045 CacheIndex::AllocBuffer()
  3047   switch (mState) {
  3048     case WRITING:
  3049       mRWBufSize = sizeof(CacheIndexHeader) + sizeof(CacheHash::Hash32_t) +
  3050                    mProcessEntries * sizeof(CacheIndexRecord);
  3051       if (mRWBufSize > kMaxBufSize) {
  3052         mRWBufSize = kMaxBufSize;
  3054       break;
  3055     case READING:
  3056       mRWBufSize = kMaxBufSize;
  3057       break;
  3058     default:
  3059       MOZ_ASSERT(false, "Unexpected state!");
  3062   mRWBuf = static_cast<char *>(moz_xmalloc(mRWBufSize));
  3065 void
  3066 CacheIndex::ReleaseBuffer()
  3068   if (!mRWBuf) {
  3069     return;
  3072   free(mRWBuf);
  3073   mRWBuf = nullptr;
  3074   mRWBufSize = 0;
  3075   mRWBufPos = 0;
  3078 namespace { // anon
  3080 class FrecencyComparator
  3082 public:
  3083   bool Equals(CacheIndexRecord* a, CacheIndexRecord* b) const {
  3084     return a->mFrecency == b->mFrecency;
  3086   bool LessThan(CacheIndexRecord* a, CacheIndexRecord* b) const {
  3087     // Place entries with frecency 0 at the end of the array.
  3088     if (a->mFrecency == 0) {
  3089       return false;
  3091     if (b->mFrecency == 0) {
  3092       return true;
  3094     return a->mFrecency < b->mFrecency;
  3096 };
  3098 class ExpirationComparator
  3100 public:
  3101   bool Equals(CacheIndexRecord* a, CacheIndexRecord* b) const {
  3102     return a->mExpirationTime == b->mExpirationTime;
  3104   bool LessThan(CacheIndexRecord* a, CacheIndexRecord* b) const {
  3105     return a->mExpirationTime < b->mExpirationTime;
  3107 };
  3109 } // anon
  3111 void
  3112 CacheIndex::InsertRecordToFrecencyArray(CacheIndexRecord *aRecord)
  3114   LOG(("CacheIndex::InsertRecordToFrecencyArray() [record=%p, hash=%08x%08x%08x"
  3115        "%08x%08x]", aRecord, LOGSHA1(aRecord->mHash)));
  3117   MOZ_ASSERT(!mFrecencyArray.Contains(aRecord));
  3118   mFrecencyArray.InsertElementSorted(aRecord, FrecencyComparator());
  3121 void
  3122 CacheIndex::InsertRecordToExpirationArray(CacheIndexRecord *aRecord)
  3124   LOG(("CacheIndex::InsertRecordToExpirationArray() [record=%p, hash=%08x%08x"
  3125        "%08x%08x%08x]", aRecord, LOGSHA1(aRecord->mHash)));
  3127   MOZ_ASSERT(!mExpirationArray.Contains(aRecord));
  3128   mExpirationArray.InsertElementSorted(aRecord, ExpirationComparator());
  3131 void
  3132 CacheIndex::RemoveRecordFromFrecencyArray(CacheIndexRecord *aRecord)
  3134   LOG(("CacheIndex::RemoveRecordFromFrecencyArray() [record=%p]", aRecord));
  3136   DebugOnly<bool> removed;
  3137   removed = mFrecencyArray.RemoveElement(aRecord);
  3138   MOZ_ASSERT(removed);
  3141 void
  3142 CacheIndex::RemoveRecordFromExpirationArray(CacheIndexRecord *aRecord)
  3144   LOG(("CacheIndex::RemoveRecordFromExpirationArray() [record=%p]", aRecord));
  3146   DebugOnly<bool> removed;
  3147   removed = mExpirationArray.RemoveElement(aRecord);
  3148   MOZ_ASSERT(removed);
  3151 void
  3152 CacheIndex::AddRecordToIterators(CacheIndexRecord *aRecord)
  3154   AssertOwnsLock();
  3156   for (uint32_t i = 0; i < mIterators.Length(); ++i) {
  3157     // Add a new record only when iterator is supposed to be updated.
  3158     if (mIterators[i]->ShouldBeNewAdded()) {
  3159       mIterators[i]->AddRecord(aRecord);
  3164 void
  3165 CacheIndex::RemoveRecordFromIterators(CacheIndexRecord *aRecord)
  3167   AssertOwnsLock();
  3169   for (uint32_t i = 0; i < mIterators.Length(); ++i) {
  3170     // Remove the record from iterator always, it makes no sence to return
  3171     // non-existing entries. Also the pointer to the record is no longer valid
  3172     // once the entry is removed from index.
  3173     mIterators[i]->RemoveRecord(aRecord);
  3177 void
  3178 CacheIndex::ReplaceRecordInIterators(CacheIndexRecord *aOldRecord,
  3179                                      CacheIndexRecord *aNewRecord)
  3181   AssertOwnsLock();
  3183   for (uint32_t i = 0; i < mIterators.Length(); ++i) {
  3184     // We have to replace the record always since the pointer is no longer
  3185     // valid after this point. NOTE: Replacing the record doesn't mean that
  3186     // a new entry was added, it just means that the data in the entry was
  3187     // changed (e.g. a file size) and we had to track this change in
  3188     // mPendingUpdates since mIndex was read-only.
  3189     mIterators[i]->ReplaceRecord(aOldRecord, aNewRecord);
  3193 nsresult
  3194 CacheIndex::Run()
  3196   LOG(("CacheIndex::Run()"));
  3198   CacheIndexAutoLock lock(this);
  3200   if (!IsIndexUsable()) {
  3201     return NS_ERROR_NOT_AVAILABLE;
  3204   if (mState == READY && mShuttingDown) {
  3205     return NS_OK;
  3208   mUpdateEventPending = false;
  3210   switch (mState) {
  3211     case BUILDING:
  3212       BuildIndex();
  3213       break;
  3214     case UPDATING:
  3215       UpdateIndex();
  3216       break;
  3217     default:
  3218       LOG(("CacheIndex::Run() - Update/Build was canceled"));
  3221   return NS_OK;
  3224 nsresult
  3225 CacheIndex::OnFileOpenedInternal(FileOpenHelper *aOpener,
  3226                                  CacheFileHandle *aHandle, nsresult aResult)
  3228   LOG(("CacheIndex::OnFileOpenedInternal() [opener=%p, handle=%p, "
  3229        "result=0x%08x]", aOpener, aHandle, aResult));
  3231   nsresult rv;
  3233   AssertOwnsLock();
  3235   if (!IsIndexUsable()) {
  3236     return NS_ERROR_NOT_AVAILABLE;
  3239   if (mState == READY && mShuttingDown) {
  3240     return NS_OK;
  3243   switch (mState) {
  3244     case WRITING:
  3245       MOZ_ASSERT(aOpener == mIndexFileOpener);
  3246       mIndexFileOpener = nullptr;
  3248       if (NS_FAILED(aResult)) {
  3249         LOG(("CacheIndex::OnFileOpenedInternal() - Can't open index file for "
  3250              "writing [rv=0x%08x]", aResult));
  3251         FinishWrite(false);
  3252       } else {
  3253         mIndexHandle = aHandle;
  3254         WriteRecords();
  3256       break;
  3257     case READING:
  3258       if (aOpener == mIndexFileOpener) {
  3259         mIndexFileOpener = nullptr;
  3261         if (NS_SUCCEEDED(aResult)) {
  3262           if (aHandle->FileSize() == 0) {
  3263             FinishRead(false);
  3264             CacheFileIOManager::DoomFile(aHandle, nullptr);
  3265             break;
  3266           } else {
  3267             mIndexHandle = aHandle;
  3269         } else {
  3270           FinishRead(false);
  3271           break;
  3273       } else if (aOpener == mJournalFileOpener) {
  3274         mJournalFileOpener = nullptr;
  3275         mJournalHandle = aHandle;
  3276       } else if (aOpener == mTmpFileOpener) {
  3277         mTmpFileOpener = nullptr;
  3278         mTmpHandle = aHandle;
  3279       } else {
  3280         MOZ_ASSERT(false, "Unexpected state!");
  3283       if (mIndexFileOpener || mJournalFileOpener || mTmpFileOpener) {
  3284         // Some opener still didn't finish
  3285         break;
  3288       // We fail and cancel all other openers when we opening index file fails.
  3289       MOZ_ASSERT(mIndexHandle);
  3291       if (mTmpHandle) {
  3292         CacheFileIOManager::DoomFile(mTmpHandle, nullptr);
  3293         mTmpHandle = nullptr;
  3295         if (mJournalHandle) { // this shouldn't normally happen
  3296           LOG(("CacheIndex::OnFileOpenedInternal() - Unexpected state, all "
  3297                "files [%s, %s, %s] should never exist. Removing whole index.",
  3298                kIndexName, kJournalName, kTempIndexName));
  3299           FinishRead(false);
  3300           break;
  3304       if (mJournalHandle) {
  3305         // Rename journal to make sure we update index on next start in case
  3306         // firefox crashes
  3307         rv = CacheFileIOManager::RenameFile(
  3308           mJournalHandle, NS_LITERAL_CSTRING(kTempIndexName), this);
  3309         if (NS_FAILED(rv)) {
  3310           LOG(("CacheIndex::OnFileOpenedInternal() - CacheFileIOManager::"
  3311                "RenameFile() failed synchronously [rv=0x%08x]", rv));
  3312           FinishRead(false);
  3313           break;
  3315       } else {
  3316         StartReadingIndex();
  3319       break;
  3320     default:
  3321       MOZ_ASSERT(false, "Unexpected state!");
  3324   return NS_OK;
  3327 nsresult
  3328 CacheIndex::OnFileOpened(CacheFileHandle *aHandle, nsresult aResult)
  3330   MOZ_CRASH("CacheIndex::OnFileOpened should not be called!");
  3331   return NS_ERROR_UNEXPECTED;
  3334 nsresult
  3335 CacheIndex::OnDataWritten(CacheFileHandle *aHandle, const char *aBuf,
  3336                           nsresult aResult)
  3338   LOG(("CacheIndex::OnDataWritten() [handle=%p, result=0x%08x]", aHandle,
  3339        aResult));
  3341   nsresult rv;
  3343   CacheIndexAutoLock lock(this);
  3345   if (!IsIndexUsable()) {
  3346     return NS_ERROR_NOT_AVAILABLE;
  3349   if (mState == READY && mShuttingDown) {
  3350     return NS_OK;
  3353   switch (mState) {
  3354     case WRITING:
  3355       if (mIndexHandle != aHandle) {
  3356         LOG(("CacheIndex::OnDataWritten() - ignoring notification since it "
  3357              "belongs to previously canceled operation [state=%d]", mState));
  3358         break;
  3361       if (NS_FAILED(aResult)) {
  3362         FinishWrite(false);
  3363       } else {
  3364         if (mSkipEntries == mProcessEntries) {
  3365           rv = CacheFileIOManager::RenameFile(mIndexHandle,
  3366                                               NS_LITERAL_CSTRING(kIndexName),
  3367                                               this);
  3368           if (NS_FAILED(rv)) {
  3369             LOG(("CacheIndex::OnDataWritten() - CacheFileIOManager::"
  3370                  "RenameFile() failed synchronously [rv=0x%08x]", rv));
  3371             FinishWrite(false);
  3373         } else {
  3374           WriteRecords();
  3377       break;
  3378     default:
  3379       // Writing was canceled.
  3380       LOG(("CacheIndex::OnDataWritten() - ignoring notification since the "
  3381            "operation was previously canceled [state=%d]", mState));
  3384   return NS_OK;
  3387 nsresult
  3388 CacheIndex::OnDataRead(CacheFileHandle *aHandle, char *aBuf, nsresult aResult)
  3390   LOG(("CacheIndex::OnDataRead() [handle=%p, result=0x%08x]", aHandle,
  3391        aResult));
  3393   CacheIndexAutoLock lock(this);
  3395   if (!IsIndexUsable()) {
  3396     return NS_ERROR_NOT_AVAILABLE;
  3399   switch (mState) {
  3400     case READING:
  3401       MOZ_ASSERT(mIndexHandle == aHandle || mJournalHandle == aHandle);
  3403       if (NS_FAILED(aResult)) {
  3404         FinishRead(false);
  3405       } else {
  3406         if (!mIndexOnDiskIsValid) {
  3407           ParseRecords();
  3408         } else {
  3409           ParseJournal();
  3412       break;
  3413     default:
  3414       // Reading was canceled.
  3415       LOG(("CacheIndex::OnDataRead() - ignoring notification since the "
  3416            "operation was previously canceled [state=%d]", mState));
  3419   return NS_OK;
  3422 nsresult
  3423 CacheIndex::OnFileDoomed(CacheFileHandle *aHandle, nsresult aResult)
  3425   MOZ_CRASH("CacheIndex::OnFileDoomed should not be called!");
  3426   return NS_ERROR_UNEXPECTED;
  3429 nsresult
  3430 CacheIndex::OnEOFSet(CacheFileHandle *aHandle, nsresult aResult)
  3432   MOZ_CRASH("CacheIndex::OnEOFSet should not be called!");
  3433   return NS_ERROR_UNEXPECTED;
  3436 nsresult
  3437 CacheIndex::OnFileRenamed(CacheFileHandle *aHandle, nsresult aResult)
  3439   LOG(("CacheIndex::OnFileRenamed() [handle=%p, result=0x%08x]", aHandle,
  3440        aResult));
  3442   CacheIndexAutoLock lock(this);
  3444   if (!IsIndexUsable()) {
  3445     return NS_ERROR_NOT_AVAILABLE;
  3448   if (mState == READY && mShuttingDown) {
  3449     return NS_OK;
  3452   switch (mState) {
  3453     case WRITING:
  3454       // This is a result of renaming the new index written to tmpfile to index
  3455       // file. This is the last step when writing the index and the whole
  3456       // writing process is successful iff renaming was successful.
  3458       if (mIndexHandle != aHandle) {
  3459         LOG(("CacheIndex::OnFileRenamed() - ignoring notification since it "
  3460              "belongs to previously canceled operation [state=%d]", mState));
  3461         break;
  3464       FinishWrite(NS_SUCCEEDED(aResult));
  3465       break;
  3466     case READING:
  3467       // This is a result of renaming journal file to tmpfile. It is renamed
  3468       // before we start reading index and journal file and it should normally
  3469       // succeed. If it fails give up reading of index.
  3471       if (mJournalHandle != aHandle) {
  3472         LOG(("CacheIndex::OnFileRenamed() - ignoring notification since it "
  3473              "belongs to previously canceled operation [state=%d]", mState));
  3474         break;
  3477       if (NS_FAILED(aResult)) {
  3478         FinishRead(false);
  3479       } else {
  3480         StartReadingIndex();
  3482       break;
  3483     default:
  3484       // Reading/writing was canceled.
  3485       LOG(("CacheIndex::OnFileRenamed() - ignoring notification since the "
  3486            "operation was previously canceled [state=%d]", mState));
  3489   return NS_OK;
  3492 // Memory reporting
  3494 namespace { // anon
  3496 size_t
  3497 CollectIndexEntryMemory(CacheIndexEntry* aEntry,
  3498                         mozilla::MallocSizeOf mallocSizeOf,
  3499                         void *arg)
  3501   return aEntry->SizeOfExcludingThis(mallocSizeOf);
  3504 } // anon
  3506 size_t
  3507 CacheIndex::SizeOfExcludingThisInternal(mozilla::MallocSizeOf mallocSizeOf) const
  3509   CacheIndexAutoLock lock(const_cast<CacheIndex*>(this));
  3511   size_t n = 0;
  3512   nsCOMPtr<nsISizeOf> sizeOf;
  3514   // mIndexHandle and mJournalHandle are reported via SizeOfHandlesRunnable
  3515   // in CacheFileIOManager::SizeOfExcludingThisInternal as part of special
  3516   // handles array.
  3518   sizeOf = do_QueryInterface(mCacheDirectory);
  3519   if (sizeOf) {
  3520     n += sizeOf->SizeOfIncludingThis(mallocSizeOf);
  3523   sizeOf = do_QueryInterface(mUpdateTimer);
  3524   if (sizeOf) {
  3525     n += sizeOf->SizeOfIncludingThis(mallocSizeOf);
  3528   n += mallocSizeOf(mRWBuf);
  3529   n += mallocSizeOf(mRWHash);
  3531   n += mIndex.SizeOfExcludingThis(&CollectIndexEntryMemory, mallocSizeOf);
  3532   n += mPendingUpdates.SizeOfExcludingThis(&CollectIndexEntryMemory, mallocSizeOf);
  3533   n += mTmpJournal.SizeOfExcludingThis(&CollectIndexEntryMemory, mallocSizeOf);
  3535   // mFrecencyArray and mExpirationArray items are reported by
  3536   // mIndex/mPendingUpdates
  3537   n += mFrecencyArray.SizeOfExcludingThis(mallocSizeOf);
  3538   n += mExpirationArray.SizeOfExcludingThis(mallocSizeOf);
  3539   n += mDiskConsumptionObservers.SizeOfExcludingThis(mallocSizeOf);
  3541   return n;
  3544 // static
  3545 size_t
  3546 CacheIndex::SizeOfExcludingThis(mozilla::MallocSizeOf mallocSizeOf)
  3548   if (!gInstance)
  3549     return 0;
  3551   return gInstance->SizeOfExcludingThisInternal(mallocSizeOf);
  3554 // static
  3555 size_t
  3556 CacheIndex::SizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf)
  3558   return mallocSizeOf(gInstance) + SizeOfExcludingThis(mallocSizeOf);
  3561 } // net
  3562 } // mozilla

mercurial