netwerk/cache2/CacheIndex.cpp

changeset 0
6474c204b198
     1.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     1.2 +++ b/netwerk/cache2/CacheIndex.cpp	Wed Dec 31 06:09:35 2014 +0100
     1.3 @@ -0,0 +1,3562 @@
     1.4 +/* This Source Code Form is subject to the terms of the Mozilla Public
     1.5 + * License, v. 2.0. If a copy of the MPL was not distributed with this
     1.6 + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
     1.7 +
     1.8 +#include "CacheIndex.h"
     1.9 +
    1.10 +#include "CacheLog.h"
    1.11 +#include "CacheFileIOManager.h"
    1.12 +#include "CacheFileMetadata.h"
    1.13 +#include "CacheIndexIterator.h"
    1.14 +#include "CacheIndexContextIterator.h"
    1.15 +#include "nsThreadUtils.h"
    1.16 +#include "nsISimpleEnumerator.h"
    1.17 +#include "nsIDirectoryEnumerator.h"
    1.18 +#include "nsISizeOf.h"
    1.19 +#include "nsPrintfCString.h"
    1.20 +#include "mozilla/DebugOnly.h"
    1.21 +#include "prinrval.h"
    1.22 +#include "nsIFile.h"
    1.23 +#include "nsITimer.h"
    1.24 +#include "mozilla/AutoRestore.h"
    1.25 +#include <algorithm>
    1.26 +
    1.27 +
    1.28 +#define kMinUnwrittenChanges   300
    1.29 +#define kMinDumpInterval       20000 // in milliseconds
    1.30 +#define kMaxBufSize            16384
    1.31 +#define kIndexVersion          0x00000001
    1.32 +#define kUpdateIndexStartDelay 50000 // in milliseconds
    1.33 +
    1.34 +const char kIndexName[]     = "index";
    1.35 +const char kTempIndexName[] = "index.tmp";
    1.36 +const char kJournalName[]   = "index.log";
    1.37 +
    1.38 +namespace mozilla {
    1.39 +namespace net {
    1.40 +
    1.41 +/**
    1.42 + * This helper class is responsible for keeping CacheIndex::mIndexStats,
    1.43 + * CacheIndex::mFrecencyArray and CacheIndex::mExpirationArray up to date.
    1.44 + */
    1.45 +class CacheIndexEntryAutoManage
    1.46 +{
    1.47 +public:
    1.48 +  CacheIndexEntryAutoManage(const SHA1Sum::Hash *aHash, CacheIndex *aIndex)
    1.49 +    : mIndex(aIndex)
    1.50 +    , mOldRecord(nullptr)
    1.51 +    , mOldFrecency(0)
    1.52 +    , mOldExpirationTime(nsICacheEntry::NO_EXPIRATION_TIME)
    1.53 +    , mDoNotSearchInIndex(false)
    1.54 +    , mDoNotSearchInUpdates(false)
    1.55 +  {
    1.56 +    mIndex->AssertOwnsLock();
    1.57 +
    1.58 +    mHash = aHash;
    1.59 +    CacheIndexEntry *entry = FindEntry();
    1.60 +    mIndex->mIndexStats.BeforeChange(entry);
    1.61 +    if (entry && entry->IsInitialized() && !entry->IsRemoved()) {
    1.62 +      mOldRecord = entry->mRec;
    1.63 +      mOldFrecency = entry->mRec->mFrecency;
    1.64 +      mOldExpirationTime = entry->mRec->mExpirationTime;
    1.65 +    }
    1.66 +  }
    1.67 +
    1.68 +  ~CacheIndexEntryAutoManage()
    1.69 +  {
    1.70 +    mIndex->AssertOwnsLock();
    1.71 +
    1.72 +    CacheIndexEntry *entry = FindEntry();
    1.73 +    mIndex->mIndexStats.AfterChange(entry);
    1.74 +    if (!entry || !entry->IsInitialized() || entry->IsRemoved()) {
    1.75 +      entry = nullptr;
    1.76 +    }
    1.77 +
    1.78 +    if (entry && !mOldRecord) {
    1.79 +      mIndex->InsertRecordToFrecencyArray(entry->mRec);
    1.80 +      mIndex->InsertRecordToExpirationArray(entry->mRec);
    1.81 +      mIndex->AddRecordToIterators(entry->mRec);
    1.82 +    } else if (!entry && mOldRecord) {
    1.83 +      mIndex->RemoveRecordFromFrecencyArray(mOldRecord);
    1.84 +      mIndex->RemoveRecordFromExpirationArray(mOldRecord);
    1.85 +      mIndex->RemoveRecordFromIterators(mOldRecord);
    1.86 +    } else if (entry && mOldRecord) {
    1.87 +      bool replaceFrecency = false;
    1.88 +      bool replaceExpiration = false;
    1.89 +
    1.90 +      if (entry->mRec != mOldRecord) {
    1.91 +        // record has a different address, we have to replace it
    1.92 +        replaceFrecency = replaceExpiration = true;
    1.93 +        mIndex->ReplaceRecordInIterators(mOldRecord, entry->mRec);
    1.94 +      } else {
    1.95 +        if (entry->mRec->mFrecency == 0 &&
    1.96 +            entry->mRec->mExpirationTime == nsICacheEntry::NO_EXPIRATION_TIME) {
    1.97 +          // This is a special case when we want to make sure that the entry is
    1.98 +          // placed at the end of the lists even when the values didn't change.
    1.99 +          replaceFrecency = replaceExpiration = true;
   1.100 +        } else {
   1.101 +          if (entry->mRec->mFrecency != mOldFrecency) {
   1.102 +            replaceFrecency = true;
   1.103 +          }
   1.104 +          if (entry->mRec->mExpirationTime != mOldExpirationTime) {
   1.105 +            replaceExpiration = true;
   1.106 +          }
   1.107 +        }
   1.108 +      }
   1.109 +
   1.110 +      if (replaceFrecency) {
   1.111 +        mIndex->RemoveRecordFromFrecencyArray(mOldRecord);
   1.112 +        mIndex->InsertRecordToFrecencyArray(entry->mRec);
   1.113 +      }
   1.114 +      if (replaceExpiration) {
   1.115 +        mIndex->RemoveRecordFromExpirationArray(mOldRecord);
   1.116 +        mIndex->InsertRecordToExpirationArray(entry->mRec);
   1.117 +      }
   1.118 +    } else {
   1.119 +      // both entries were removed or not initialized, do nothing
   1.120 +    }
   1.121 +  }
   1.122 +
   1.123 +  // We cannot rely on nsTHashtable::GetEntry() in case we are enumerating the
   1.124 +  // entries and returning PL_DHASH_REMOVE. Destructor is called before the
   1.125 +  // entry is removed. Caller must call one of following methods to skip
   1.126 +  // lookup in the hashtable.
   1.127 +  void DoNotSearchInIndex()   { mDoNotSearchInIndex = true; }
   1.128 +  void DoNotSearchInUpdates() { mDoNotSearchInUpdates = true; }
   1.129 +
   1.130 +private:
   1.131 +  CacheIndexEntry * FindEntry()
   1.132 +  {
   1.133 +    CacheIndexEntry *entry = nullptr;
   1.134 +
   1.135 +    switch (mIndex->mState) {
   1.136 +      case CacheIndex::READING:
   1.137 +      case CacheIndex::WRITING:
   1.138 +        if (!mDoNotSearchInUpdates) {
   1.139 +          entry = mIndex->mPendingUpdates.GetEntry(*mHash);
   1.140 +        }
   1.141 +        // no break
   1.142 +      case CacheIndex::BUILDING:
   1.143 +      case CacheIndex::UPDATING:
   1.144 +      case CacheIndex::READY:
   1.145 +        if (!entry && !mDoNotSearchInIndex) {
   1.146 +          entry = mIndex->mIndex.GetEntry(*mHash);
   1.147 +        }
   1.148 +        break;
   1.149 +      case CacheIndex::INITIAL:
   1.150 +      case CacheIndex::SHUTDOWN:
   1.151 +      default:
   1.152 +        MOZ_ASSERT(false, "Unexpected state!");
   1.153 +    }
   1.154 +
   1.155 +    return entry;
   1.156 +  }
   1.157 +
   1.158 +  const SHA1Sum::Hash *mHash;
   1.159 +  nsRefPtr<CacheIndex> mIndex;
   1.160 +  CacheIndexRecord    *mOldRecord;
   1.161 +  uint32_t             mOldFrecency;
   1.162 +  uint32_t             mOldExpirationTime;
   1.163 +  bool                 mDoNotSearchInIndex;
   1.164 +  bool                 mDoNotSearchInUpdates;
   1.165 +};
   1.166 +
   1.167 +class FileOpenHelper : public CacheFileIOListener
   1.168 +{
   1.169 +public:
   1.170 +  NS_DECL_THREADSAFE_ISUPPORTS
   1.171 +
   1.172 +  FileOpenHelper(CacheIndex* aIndex)
   1.173 +    : mIndex(aIndex)
   1.174 +    , mCanceled(false)
   1.175 +  {}
   1.176 +
   1.177 +  virtual ~FileOpenHelper() {}
   1.178 +
   1.179 +  void Cancel() {
   1.180 +    mIndex->AssertOwnsLock();
   1.181 +    mCanceled = true;
   1.182 +  }
   1.183 +
   1.184 +private:
   1.185 +  NS_IMETHOD OnFileOpened(CacheFileHandle *aHandle, nsresult aResult);
   1.186 +  NS_IMETHOD OnDataWritten(CacheFileHandle *aHandle, const char *aBuf,
   1.187 +                           nsresult aResult) {
   1.188 +    MOZ_CRASH("FileOpenHelper::OnDataWritten should not be called!");
   1.189 +    return NS_ERROR_UNEXPECTED;
   1.190 +  }
   1.191 +  NS_IMETHOD OnDataRead(CacheFileHandle *aHandle, char *aBuf,
   1.192 +                        nsresult aResult) {
   1.193 +    MOZ_CRASH("FileOpenHelper::OnDataRead should not be called!");
   1.194 +    return NS_ERROR_UNEXPECTED;
   1.195 +  }
   1.196 +  NS_IMETHOD OnFileDoomed(CacheFileHandle *aHandle, nsresult aResult) {
   1.197 +    MOZ_CRASH("FileOpenHelper::OnFileDoomed should not be called!");
   1.198 +    return NS_ERROR_UNEXPECTED;
   1.199 +  }
   1.200 +  NS_IMETHOD OnEOFSet(CacheFileHandle *aHandle, nsresult aResult) {
   1.201 +    MOZ_CRASH("FileOpenHelper::OnEOFSet should not be called!");
   1.202 +    return NS_ERROR_UNEXPECTED;
   1.203 +  }
   1.204 +  NS_IMETHOD OnFileRenamed(CacheFileHandle *aHandle, nsresult aResult) {
   1.205 +    MOZ_CRASH("FileOpenHelper::OnFileRenamed should not be called!");
   1.206 +    return NS_ERROR_UNEXPECTED;
   1.207 +  }
   1.208 +
   1.209 +  nsRefPtr<CacheIndex> mIndex;
   1.210 +  bool                 mCanceled;
   1.211 +};
   1.212 +
   1.213 +NS_IMETHODIMP FileOpenHelper::OnFileOpened(CacheFileHandle *aHandle,
   1.214 +                                           nsresult aResult)
   1.215 +{
   1.216 +  CacheIndexAutoLock lock(mIndex);
   1.217 +
   1.218 +  if (mCanceled) {
   1.219 +    if (aHandle) {
   1.220 +      CacheFileIOManager::DoomFile(aHandle, nullptr);
   1.221 +    }
   1.222 +
   1.223 +    return NS_OK;
   1.224 +  }
   1.225 +
   1.226 +  mIndex->OnFileOpenedInternal(this, aHandle, aResult);
   1.227 +
   1.228 +  return NS_OK;
   1.229 +}
   1.230 +
   1.231 +NS_IMPL_ISUPPORTS(FileOpenHelper, CacheFileIOListener);
   1.232 +
   1.233 +
   1.234 +CacheIndex * CacheIndex::gInstance = nullptr;
   1.235 +
   1.236 +
   1.237 +NS_IMPL_ADDREF(CacheIndex)
   1.238 +NS_IMPL_RELEASE(CacheIndex)
   1.239 +
   1.240 +NS_INTERFACE_MAP_BEGIN(CacheIndex)
   1.241 +  NS_INTERFACE_MAP_ENTRY(mozilla::net::CacheFileIOListener)
   1.242 +  NS_INTERFACE_MAP_ENTRY(nsIRunnable)
   1.243 +NS_INTERFACE_MAP_END_THREADSAFE
   1.244 +
   1.245 +
   1.246 +CacheIndex::CacheIndex()
   1.247 +  : mLock("CacheFile.mLock")
   1.248 +  , mState(INITIAL)
   1.249 +  , mShuttingDown(false)
   1.250 +  , mIndexNeedsUpdate(false)
   1.251 +  , mRemovingAll(false)
   1.252 +  , mIndexOnDiskIsValid(false)
   1.253 +  , mDontMarkIndexClean(false)
   1.254 +  , mIndexTimeStamp(0)
   1.255 +  , mUpdateEventPending(false)
   1.256 +  , mSkipEntries(0)
   1.257 +  , mProcessEntries(0)
   1.258 +  , mRWBuf(nullptr)
   1.259 +  , mRWBufSize(0)
   1.260 +  , mRWBufPos(0)
   1.261 +  , mJournalReadSuccessfully(false)
   1.262 +{
   1.263 +  LOG(("CacheIndex::CacheIndex [this=%p]", this));
   1.264 +  MOZ_COUNT_CTOR(CacheIndex);
   1.265 +  MOZ_ASSERT(!gInstance, "multiple CacheIndex instances!");
   1.266 +}
   1.267 +
   1.268 +CacheIndex::~CacheIndex()
   1.269 +{
   1.270 +  LOG(("CacheIndex::~CacheIndex [this=%p]", this));
   1.271 +  MOZ_COUNT_DTOR(CacheIndex);
   1.272 +
   1.273 +  ReleaseBuffer();
   1.274 +}
   1.275 +
   1.276 +void
   1.277 +CacheIndex::Lock()
   1.278 +{
   1.279 +  mLock.Lock();
   1.280 +
   1.281 +  MOZ_ASSERT(!mIndexStats.StateLogged());
   1.282 +}
   1.283 +
   1.284 +void
   1.285 +CacheIndex::Unlock()
   1.286 +{
   1.287 +  MOZ_ASSERT(!mIndexStats.StateLogged());
   1.288 +
   1.289 +  mLock.Unlock();
   1.290 +}
   1.291 +
   1.292 +inline void
   1.293 +CacheIndex::AssertOwnsLock()
   1.294 +{
   1.295 +  mLock.AssertCurrentThreadOwns();
   1.296 +}
   1.297 +
   1.298 +// static
   1.299 +nsresult
   1.300 +CacheIndex::Init(nsIFile *aCacheDirectory)
   1.301 +{
   1.302 +  LOG(("CacheIndex::Init()"));
   1.303 +
   1.304 +  MOZ_ASSERT(NS_IsMainThread());
   1.305 +
   1.306 +  if (gInstance) {
   1.307 +    return NS_ERROR_ALREADY_INITIALIZED;
   1.308 +  }
   1.309 +
   1.310 +  nsRefPtr<CacheIndex> idx = new CacheIndex();
   1.311 +
   1.312 +  CacheIndexAutoLock lock(idx);
   1.313 +
   1.314 +  nsresult rv = idx->InitInternal(aCacheDirectory);
   1.315 +  NS_ENSURE_SUCCESS(rv, rv);
   1.316 +
   1.317 +  idx.swap(gInstance);
   1.318 +  return NS_OK;
   1.319 +}
   1.320 +
   1.321 +nsresult
   1.322 +CacheIndex::InitInternal(nsIFile *aCacheDirectory)
   1.323 +{
   1.324 +  nsresult rv;
   1.325 +
   1.326 +  rv = aCacheDirectory->Clone(getter_AddRefs(mCacheDirectory));
   1.327 +  NS_ENSURE_SUCCESS(rv, rv);
   1.328 +
   1.329 +  mStartTime = TimeStamp::NowLoRes();
   1.330 +
   1.331 +  ReadIndexFromDisk();
   1.332 +
   1.333 +  return NS_OK;
   1.334 +}
   1.335 +
   1.336 +// static
   1.337 +nsresult
   1.338 +CacheIndex::PreShutdown()
   1.339 +{
   1.340 +  LOG(("CacheIndex::PreShutdown() [gInstance=%p]", gInstance));
   1.341 +
   1.342 +  MOZ_ASSERT(NS_IsMainThread());
   1.343 +
   1.344 +  nsresult rv;
   1.345 +  nsRefPtr<CacheIndex> index = gInstance;
   1.346 +
   1.347 +  if (!index) {
   1.348 +    return NS_ERROR_NOT_INITIALIZED;
   1.349 +  }
   1.350 +
   1.351 +  CacheIndexAutoLock lock(index);
   1.352 +
   1.353 +  LOG(("CacheIndex::PreShutdown() - [state=%d, indexOnDiskIsValid=%d, "
   1.354 +       "dontMarkIndexClean=%d]", index->mState, index->mIndexOnDiskIsValid,
   1.355 +       index->mDontMarkIndexClean));
   1.356 +
   1.357 +  LOG(("CacheIndex::PreShutdown() - Closing iterators."));
   1.358 +  for (uint32_t i = 0; i < index->mIterators.Length(); ) {
   1.359 +    rv = index->mIterators[i]->CloseInternal(NS_ERROR_FAILURE);
   1.360 +    if (NS_FAILED(rv)) {
   1.361 +      // CacheIndexIterator::CloseInternal() removes itself from mIteratos iff
   1.362 +      // it returns success.
   1.363 +      LOG(("CacheIndex::PreShutdown() - Failed to remove iterator %p. "
   1.364 +           "[rv=0x%08x]", rv));
   1.365 +      i++;
   1.366 +    }
   1.367 +  }
   1.368 +
   1.369 +  index->mShuttingDown = true;
   1.370 +
   1.371 +  if (index->mState == READY) {
   1.372 +    return NS_OK; // nothing to do
   1.373 +  }
   1.374 +
   1.375 +  nsCOMPtr<nsIRunnable> event;
   1.376 +  event = NS_NewRunnableMethod(index, &CacheIndex::PreShutdownInternal);
   1.377 +
   1.378 +  nsCOMPtr<nsIEventTarget> ioTarget = CacheFileIOManager::IOTarget();
   1.379 +  MOZ_ASSERT(ioTarget);
   1.380 +
   1.381 +  // PreShutdownInternal() will be executed before any queued event on INDEX
   1.382 +  // level. That's OK since we don't want to wait for any operation in progess.
   1.383 +  // We need to interrupt it and save journal as quickly as possible.
   1.384 +  rv = ioTarget->Dispatch(event, nsIEventTarget::DISPATCH_NORMAL);
   1.385 +  if (NS_FAILED(rv)) {
   1.386 +    NS_WARNING("CacheIndex::PreShutdown() - Can't dispatch event");
   1.387 +    LOG(("CacheIndex::PreShutdown() - Can't dispatch event" ));
   1.388 +    return rv;
   1.389 +  }
   1.390 +
   1.391 +  return NS_OK;
   1.392 +}
   1.393 +
   1.394 +void
   1.395 +CacheIndex::PreShutdownInternal()
   1.396 +{
   1.397 +  CacheIndexAutoLock lock(this);
   1.398 +
   1.399 +  LOG(("CacheIndex::PreShutdownInternal() - [state=%d, indexOnDiskIsValid=%d, "
   1.400 +       "dontMarkIndexClean=%d]", mState, mIndexOnDiskIsValid,
   1.401 +       mDontMarkIndexClean));
   1.402 +
   1.403 +  MOZ_ASSERT(mShuttingDown);
   1.404 +
   1.405 +  if (mUpdateTimer) {
   1.406 +    mUpdateTimer = nullptr;
   1.407 +  }
   1.408 +
   1.409 +  switch (mState) {
   1.410 +    case WRITING:
   1.411 +      FinishWrite(false);
   1.412 +      break;
   1.413 +    case READY:
   1.414 +      // nothing to do, write the journal in Shutdown()
   1.415 +      break;
   1.416 +    case READING:
   1.417 +      FinishRead(false);
   1.418 +      break;
   1.419 +    case BUILDING:
   1.420 +    case UPDATING:
   1.421 +      FinishUpdate(false);
   1.422 +      break;
   1.423 +    default:
   1.424 +      MOZ_ASSERT(false, "Implement me!");
   1.425 +  }
   1.426 +
   1.427 +  // We should end up in READY state
   1.428 +  MOZ_ASSERT(mState == READY);
   1.429 +}
   1.430 +
   1.431 +// static
   1.432 +nsresult
   1.433 +CacheIndex::Shutdown()
   1.434 +{
   1.435 +  LOG(("CacheIndex::Shutdown() [gInstance=%p]", gInstance));
   1.436 +
   1.437 +  MOZ_ASSERT(NS_IsMainThread());
   1.438 +
   1.439 +  nsRefPtr<CacheIndex> index;
   1.440 +  index.swap(gInstance);
   1.441 +
   1.442 +  if (!index) {
   1.443 +    return NS_ERROR_NOT_INITIALIZED;
   1.444 +  }
   1.445 +
   1.446 +  CacheIndexAutoLock lock(index);
   1.447 +
   1.448 +  bool sanitize = CacheObserver::ClearCacheOnShutdown();
   1.449 +
   1.450 +  LOG(("CacheIndex::Shutdown() - [state=%d, indexOnDiskIsValid=%d, "
   1.451 +       "dontMarkIndexClean=%d, sanitize=%d]", index->mState,
   1.452 +       index->mIndexOnDiskIsValid, index->mDontMarkIndexClean, sanitize));
   1.453 +
   1.454 +  MOZ_ASSERT(index->mShuttingDown);
   1.455 +
   1.456 +  EState oldState = index->mState;
   1.457 +  index->ChangeState(SHUTDOWN);
   1.458 +
   1.459 +  if (oldState != READY) {
   1.460 +    LOG(("CacheIndex::Shutdown() - Unexpected state. Did posting of "
   1.461 +         "PreShutdownInternal() fail?"));
   1.462 +  }
   1.463 +
   1.464 +  switch (oldState) {
   1.465 +    case WRITING:
   1.466 +      index->FinishWrite(false);
   1.467 +      // no break
   1.468 +    case READY:
   1.469 +      if (index->mIndexOnDiskIsValid && !index->mDontMarkIndexClean) {
   1.470 +        if (!sanitize && NS_FAILED(index->WriteLogToDisk())) {
   1.471 +          index->RemoveIndexFromDisk();
   1.472 +        }
   1.473 +      } else {
   1.474 +        index->RemoveIndexFromDisk();
   1.475 +      }
   1.476 +      break;
   1.477 +    case READING:
   1.478 +      index->FinishRead(false);
   1.479 +      break;
   1.480 +    case BUILDING:
   1.481 +    case UPDATING:
   1.482 +      index->FinishUpdate(false);
   1.483 +      break;
   1.484 +    default:
   1.485 +      MOZ_ASSERT(false, "Unexpected state!");
   1.486 +  }
   1.487 +
   1.488 +  if (sanitize) {
   1.489 +    index->RemoveIndexFromDisk();
   1.490 +  }
   1.491 +
   1.492 +  return NS_OK;
   1.493 +}
   1.494 +
   1.495 +// static
   1.496 +nsresult
   1.497 +CacheIndex::AddEntry(const SHA1Sum::Hash *aHash)
   1.498 +{
   1.499 +  LOG(("CacheIndex::AddEntry() [hash=%08x%08x%08x%08x%08x]", LOGSHA1(aHash)));
   1.500 +
   1.501 +  nsRefPtr<CacheIndex> index = gInstance;
   1.502 +
   1.503 +  if (!index) {
   1.504 +    return NS_ERROR_NOT_INITIALIZED;
   1.505 +  }
   1.506 +
   1.507 +  MOZ_ASSERT(CacheFileIOManager::IsOnIOThread());
   1.508 +
   1.509 +  CacheIndexAutoLock lock(index);
   1.510 +
   1.511 +  if (!index->IsIndexUsable()) {
   1.512 +    return NS_ERROR_NOT_AVAILABLE;
   1.513 +  }
   1.514 +
   1.515 +  // Getters in CacheIndexStats assert when mStateLogged is true since the
   1.516 +  // information is incomplete between calls to BeforeChange() and AfterChange()
   1.517 +  // (i.e. while CacheIndexEntryAutoManage exists). We need to check whether
   1.518 +  // non-fresh entries exists outside the scope of CacheIndexEntryAutoManage.
   1.519 +  bool updateIfNonFreshEntriesExist = false;
   1.520 +
   1.521 +  {
   1.522 +    CacheIndexEntryAutoManage entryMng(aHash, index);
   1.523 +
   1.524 +    CacheIndexEntry *entry = index->mIndex.GetEntry(*aHash);
   1.525 +    bool entryRemoved = entry && entry->IsRemoved();
   1.526 +
   1.527 +    if (index->mState == READY || index->mState == UPDATING ||
   1.528 +        index->mState == BUILDING) {
   1.529 +      MOZ_ASSERT(index->mPendingUpdates.Count() == 0);
   1.530 +
   1.531 +      if (entry && !entryRemoved) {
   1.532 +        // Found entry in index that shouldn't exist.
   1.533 +
   1.534 +        if (entry->IsFresh()) {
   1.535 +          // Someone removed the file on disk while FF is running. Update
   1.536 +          // process can fix only non-fresh entries (i.e. entries that were not
   1.537 +          // added within this session). Start update only if we have such
   1.538 +          // entries.
   1.539 +          //
   1.540 +          // TODO: This should be very rare problem. If it turns out not to be
   1.541 +          // true, change the update process so that it also iterates all
   1.542 +          // initialized non-empty entries and checks whether the file exists.
   1.543 +
   1.544 +          LOG(("CacheIndex::AddEntry() - Cache file was removed outside FF "
   1.545 +               "process!"));
   1.546 +
   1.547 +          updateIfNonFreshEntriesExist = true;
   1.548 +        } else if (index->mState == READY) {
   1.549 +          // Index is outdated, update it.
   1.550 +          LOG(("CacheIndex::AddEntry() - Found entry that shouldn't exist, "
   1.551 +               "update is needed"));
   1.552 +          index->mIndexNeedsUpdate = true;
   1.553 +        } else {
   1.554 +          // We cannot be here when building index since all entries are fresh
   1.555 +          // during building.
   1.556 +          MOZ_ASSERT(index->mState == UPDATING);
   1.557 +        }
   1.558 +      }
   1.559 +
   1.560 +      if (!entry) {
   1.561 +        entry = index->mIndex.PutEntry(*aHash);
   1.562 +      }
   1.563 +    } else { // WRITING, READING
   1.564 +      CacheIndexEntry *updated = index->mPendingUpdates.GetEntry(*aHash);
   1.565 +      bool updatedRemoved = updated && updated->IsRemoved();
   1.566 +
   1.567 +      if ((updated && !updatedRemoved) ||
   1.568 +          (!updated && entry && !entryRemoved && entry->IsFresh())) {
   1.569 +        // Fresh entry found, so the file was removed outside FF
   1.570 +        LOG(("CacheIndex::AddEntry() - Cache file was removed outside FF "
   1.571 +             "process!"));
   1.572 +
   1.573 +        updateIfNonFreshEntriesExist = true;
   1.574 +      } else if (!updated && entry && !entryRemoved) {
   1.575 +        if (index->mState == WRITING) {
   1.576 +          LOG(("CacheIndex::AddEntry() - Found entry that shouldn't exist, "
   1.577 +               "update is needed"));
   1.578 +          index->mIndexNeedsUpdate = true;
   1.579 +        }
   1.580 +        // Ignore if state is READING since the index information is partial
   1.581 +      }
   1.582 +
   1.583 +      updated = index->mPendingUpdates.PutEntry(*aHash);
   1.584 +      entry = updated;
   1.585 +    }
   1.586 +
   1.587 +    entry->InitNew();
   1.588 +    entry->MarkDirty();
   1.589 +    entry->MarkFresh();
   1.590 +  }
   1.591 +
   1.592 +  if (updateIfNonFreshEntriesExist &&
   1.593 +      index->mIndexStats.Count() != index->mIndexStats.Fresh()) {
   1.594 +    index->mIndexNeedsUpdate = true;
   1.595 +  }
   1.596 +
   1.597 +  index->StartUpdatingIndexIfNeeded();
   1.598 +  index->WriteIndexToDiskIfNeeded();
   1.599 +
   1.600 +  return NS_OK;
   1.601 +}
   1.602 +
   1.603 +// static
   1.604 +nsresult
   1.605 +CacheIndex::EnsureEntryExists(const SHA1Sum::Hash *aHash)
   1.606 +{
   1.607 +  LOG(("CacheIndex::EnsureEntryExists() [hash=%08x%08x%08x%08x%08x]",
   1.608 +       LOGSHA1(aHash)));
   1.609 +
   1.610 +  nsRefPtr<CacheIndex> index = gInstance;
   1.611 +
   1.612 +  if (!index) {
   1.613 +    return NS_ERROR_NOT_INITIALIZED;
   1.614 +  }
   1.615 +
   1.616 +  MOZ_ASSERT(CacheFileIOManager::IsOnIOThread());
   1.617 +
   1.618 +  CacheIndexAutoLock lock(index);
   1.619 +
   1.620 +  if (!index->IsIndexUsable()) {
   1.621 +    return NS_ERROR_NOT_AVAILABLE;
   1.622 +  }
   1.623 +
   1.624 +  {
   1.625 +    CacheIndexEntryAutoManage entryMng(aHash, index);
   1.626 +
   1.627 +    CacheIndexEntry *entry = index->mIndex.GetEntry(*aHash);
   1.628 +    bool entryRemoved = entry && entry->IsRemoved();
   1.629 +
   1.630 +    if (index->mState == READY || index->mState == UPDATING ||
   1.631 +        index->mState == BUILDING) {
   1.632 +      MOZ_ASSERT(index->mPendingUpdates.Count() == 0);
   1.633 +
   1.634 +      if (!entry || entryRemoved) {
   1.635 +        if (entryRemoved && entry->IsFresh()) {
   1.636 +          // This could happen only if somebody copies files to the entries
   1.637 +          // directory while FF is running.
   1.638 +          LOG(("CacheIndex::EnsureEntryExists() - Cache file was added outside "
   1.639 +               "FF process! Update is needed."));
   1.640 +          index->mIndexNeedsUpdate = true;
   1.641 +        } else if (index->mState == READY ||
   1.642 +                   (entryRemoved && !entry->IsFresh())) {
   1.643 +          // Removed non-fresh entries can be present as a result of
   1.644 +          // ProcessJournalEntry()
   1.645 +          LOG(("CacheIndex::EnsureEntryExists() - Didn't find entry that should"
   1.646 +               " exist, update is needed"));
   1.647 +          index->mIndexNeedsUpdate = true;
   1.648 +        }
   1.649 +
   1.650 +        if (!entry) {
   1.651 +          entry = index->mIndex.PutEntry(*aHash);
   1.652 +        }
   1.653 +        entry->InitNew();
   1.654 +        entry->MarkDirty();
   1.655 +      }
   1.656 +      entry->MarkFresh();
   1.657 +    } else { // WRITING, READING
   1.658 +      CacheIndexEntry *updated = index->mPendingUpdates.GetEntry(*aHash);
   1.659 +      bool updatedRemoved = updated && updated->IsRemoved();
   1.660 +
   1.661 +      if (updatedRemoved ||
   1.662 +          (!updated && entryRemoved && entry->IsFresh())) {
   1.663 +        // Fresh information about missing entry found. This could happen only
   1.664 +        // if somebody copies files to the entries directory while FF is running.
   1.665 +        LOG(("CacheIndex::EnsureEntryExists() - Cache file was added outside "
   1.666 +             "FF process! Update is needed."));
   1.667 +        index->mIndexNeedsUpdate = true;
   1.668 +      } else if (!updated && (!entry || entryRemoved)) {
   1.669 +        if (index->mState == WRITING) {
   1.670 +          LOG(("CacheIndex::EnsureEntryExists() - Didn't find entry that should"
   1.671 +               " exist, update is needed"));
   1.672 +          index->mIndexNeedsUpdate = true;
   1.673 +        }
   1.674 +        // Ignore if state is READING since the index information is partial
   1.675 +      }
   1.676 +
   1.677 +      // We don't need entryRemoved and updatedRemoved info anymore
   1.678 +      if (entryRemoved)   entry = nullptr;
   1.679 +      if (updatedRemoved) updated = nullptr;
   1.680 +
   1.681 +      if (updated) {
   1.682 +        updated->MarkFresh();
   1.683 +      } else {
   1.684 +        if (!entry) {
   1.685 +          // Create a new entry
   1.686 +          updated = index->mPendingUpdates.PutEntry(*aHash);
   1.687 +          updated->InitNew();
   1.688 +          updated->MarkFresh();
   1.689 +          updated->MarkDirty();
   1.690 +        } else {
   1.691 +          if (!entry->IsFresh()) {
   1.692 +            // To mark the entry fresh we must make a copy of index entry
   1.693 +            // since the index is read-only.
   1.694 +            updated = index->mPendingUpdates.PutEntry(*aHash);
   1.695 +            *updated = *entry;
   1.696 +            updated->MarkFresh();
   1.697 +          }
   1.698 +        }
   1.699 +      }
   1.700 +    }
   1.701 +  }
   1.702 +
   1.703 +  index->StartUpdatingIndexIfNeeded();
   1.704 +  index->WriteIndexToDiskIfNeeded();
   1.705 +
   1.706 +  return NS_OK;
   1.707 +}
   1.708 +
   1.709 +// static
   1.710 +nsresult
   1.711 +CacheIndex::InitEntry(const SHA1Sum::Hash *aHash,
   1.712 +                      uint32_t             aAppId,
   1.713 +                      bool                 aAnonymous,
   1.714 +                      bool                 aInBrowser)
   1.715 +{
   1.716 +  LOG(("CacheIndex::InitEntry() [hash=%08x%08x%08x%08x%08x, appId=%u, "
   1.717 +       "anonymous=%d, inBrowser=%d]", LOGSHA1(aHash), aAppId, aAnonymous,
   1.718 +       aInBrowser));
   1.719 +
   1.720 +  nsRefPtr<CacheIndex> index = gInstance;
   1.721 +
   1.722 +  if (!index) {
   1.723 +    return NS_ERROR_NOT_INITIALIZED;
   1.724 +  }
   1.725 +
   1.726 +  MOZ_ASSERT(CacheFileIOManager::IsOnIOThread());
   1.727 +
   1.728 +  CacheIndexAutoLock lock(index);
   1.729 +
   1.730 +  if (!index->IsIndexUsable()) {
   1.731 +    return NS_ERROR_NOT_AVAILABLE;
   1.732 +  }
   1.733 +
   1.734 +  {
   1.735 +    CacheIndexEntryAutoManage entryMng(aHash, index);
   1.736 +
   1.737 +    CacheIndexEntry *entry = index->mIndex.GetEntry(*aHash);
   1.738 +    bool reinitEntry = false;
   1.739 +
   1.740 +    if (entry && entry->IsRemoved()) {
   1.741 +      entry = nullptr;
   1.742 +    }
   1.743 +
   1.744 +    if (index->mState == READY || index->mState == UPDATING ||
   1.745 +        index->mState == BUILDING) {
   1.746 +      MOZ_ASSERT(index->mPendingUpdates.Count() == 0);
   1.747 +      MOZ_ASSERT(entry);
   1.748 +      MOZ_ASSERT(entry->IsFresh());
   1.749 +
   1.750 +      if (IsCollision(entry, aAppId, aAnonymous, aInBrowser)) {
   1.751 +        index->mIndexNeedsUpdate = true; // TODO Does this really help in case of collision?
   1.752 +        reinitEntry = true;
   1.753 +      } else {
   1.754 +        if (entry->IsInitialized()) {
   1.755 +          return NS_OK;
   1.756 +        }
   1.757 +      }
   1.758 +    } else {
   1.759 +      CacheIndexEntry *updated = index->mPendingUpdates.GetEntry(*aHash);
   1.760 +      DebugOnly<bool> removed = updated && updated->IsRemoved();
   1.761 +
   1.762 +      MOZ_ASSERT(updated || !removed);
   1.763 +      MOZ_ASSERT(updated || entry);
   1.764 +
   1.765 +      if (updated) {
   1.766 +        MOZ_ASSERT(updated->IsFresh());
   1.767 +
   1.768 +        if (IsCollision(updated, aAppId, aAnonymous, aInBrowser)) {
   1.769 +          index->mIndexNeedsUpdate = true;
   1.770 +          reinitEntry = true;
   1.771 +        } else {
   1.772 +          if (updated->IsInitialized()) {
   1.773 +            return NS_OK;
   1.774 +          }
   1.775 +        }
   1.776 +        entry = updated;
   1.777 +      } else {
   1.778 +        MOZ_ASSERT(entry->IsFresh());
   1.779 +
   1.780 +        if (IsCollision(entry, aAppId, aAnonymous, aInBrowser)) {
   1.781 +          index->mIndexNeedsUpdate = true;
   1.782 +          reinitEntry = true;
   1.783 +        } else {
   1.784 +          if (entry->IsInitialized()) {
   1.785 +            return NS_OK;
   1.786 +          }
   1.787 +        }
   1.788 +
   1.789 +        // make a copy of a read-only entry
   1.790 +        updated = index->mPendingUpdates.PutEntry(*aHash);
   1.791 +        *updated = *entry;
   1.792 +        entry = updated;
   1.793 +      }
   1.794 +    }
   1.795 +
   1.796 +    if (reinitEntry) {
   1.797 +      // There is a collision and we are going to rewrite this entry. Initialize
   1.798 +      // it as a new entry.
   1.799 +      entry->InitNew();
   1.800 +      entry->MarkFresh();
   1.801 +    }
   1.802 +    entry->Init(aAppId, aAnonymous, aInBrowser);
   1.803 +    entry->MarkDirty();
   1.804 +  }
   1.805 +
   1.806 +  index->StartUpdatingIndexIfNeeded();
   1.807 +  index->WriteIndexToDiskIfNeeded();
   1.808 +
   1.809 +  return NS_OK;
   1.810 +}
   1.811 +
   1.812 +// static
   1.813 +nsresult
   1.814 +CacheIndex::RemoveEntry(const SHA1Sum::Hash *aHash)
   1.815 +{
   1.816 +  LOG(("CacheIndex::RemoveEntry() [hash=%08x%08x%08x%08x%08x]",
   1.817 +       LOGSHA1(aHash)));
   1.818 +
   1.819 +  nsRefPtr<CacheIndex> index = gInstance;
   1.820 +
   1.821 +  if (!index) {
   1.822 +    return NS_ERROR_NOT_INITIALIZED;
   1.823 +  }
   1.824 +
   1.825 +  MOZ_ASSERT(CacheFileIOManager::IsOnIOThread());
   1.826 +
   1.827 +  CacheIndexAutoLock lock(index);
   1.828 +
   1.829 +  if (!index->IsIndexUsable()) {
   1.830 +    return NS_ERROR_NOT_AVAILABLE;
   1.831 +  }
   1.832 +
   1.833 +  {
   1.834 +    CacheIndexEntryAutoManage entryMng(aHash, index);
   1.835 +
   1.836 +    CacheIndexEntry *entry = index->mIndex.GetEntry(*aHash);
   1.837 +    bool entryRemoved = entry && entry->IsRemoved();
   1.838 +
   1.839 +    if (index->mState == READY || index->mState == UPDATING ||
   1.840 +        index->mState == BUILDING) {
   1.841 +      MOZ_ASSERT(index->mPendingUpdates.Count() == 0);
   1.842 +
   1.843 +      if (!entry || entryRemoved) {
   1.844 +        if (entryRemoved && entry->IsFresh()) {
   1.845 +          // This could happen only if somebody copies files to the entries
   1.846 +          // directory while FF is running.
   1.847 +          LOG(("CacheIndex::RemoveEntry() - Cache file was added outside FF "
   1.848 +               "process! Update is needed."));
   1.849 +          index->mIndexNeedsUpdate = true;
   1.850 +        } else if (index->mState == READY ||
   1.851 +                   (entryRemoved && !entry->IsFresh())) {
   1.852 +          // Removed non-fresh entries can be present as a result of
   1.853 +          // ProcessJournalEntry()
   1.854 +          LOG(("CacheIndex::RemoveEntry() - Didn't find entry that should exist"
   1.855 +               ", update is needed"));
   1.856 +          index->mIndexNeedsUpdate = true;
   1.857 +        }
   1.858 +      } else {
   1.859 +        if (entry) {
   1.860 +          if (!entry->IsDirty() && entry->IsFileEmpty()) {
   1.861 +            index->mIndex.RemoveEntry(*aHash);
   1.862 +            entry = nullptr;
   1.863 +          } else {
   1.864 +            entry->MarkRemoved();
   1.865 +            entry->MarkDirty();
   1.866 +            entry->MarkFresh();
   1.867 +          }
   1.868 +        }
   1.869 +      }
   1.870 +    } else { // WRITING, READING
   1.871 +      CacheIndexEntry *updated = index->mPendingUpdates.GetEntry(*aHash);
   1.872 +      bool updatedRemoved = updated && updated->IsRemoved();
   1.873 +
   1.874 +      if (updatedRemoved ||
   1.875 +          (!updated && entryRemoved && entry->IsFresh())) {
   1.876 +        // Fresh information about missing entry found. This could happen only
   1.877 +        // if somebody copies files to the entries directory while FF is running.
   1.878 +        LOG(("CacheIndex::RemoveEntry() - Cache file was added outside FF "
   1.879 +             "process! Update is needed."));
   1.880 +        index->mIndexNeedsUpdate = true;
   1.881 +      } else if (!updated && (!entry || entryRemoved)) {
   1.882 +        if (index->mState == WRITING) {
   1.883 +          LOG(("CacheIndex::RemoveEntry() - Didn't find entry that should exist"
   1.884 +               ", update is needed"));
   1.885 +          index->mIndexNeedsUpdate = true;
   1.886 +        }
   1.887 +        // Ignore if state is READING since the index information is partial
   1.888 +      }
   1.889 +
   1.890 +      if (!updated) {
   1.891 +        updated = index->mPendingUpdates.PutEntry(*aHash);
   1.892 +        updated->InitNew();
   1.893 +      }
   1.894 +
   1.895 +      updated->MarkRemoved();
   1.896 +      updated->MarkDirty();
   1.897 +      updated->MarkFresh();
   1.898 +    }
   1.899 +  }
   1.900 +
   1.901 +  index->StartUpdatingIndexIfNeeded();
   1.902 +  index->WriteIndexToDiskIfNeeded();
   1.903 +
   1.904 +  return NS_OK;
   1.905 +}
   1.906 +
   1.907 +// static
   1.908 +nsresult
   1.909 +CacheIndex::UpdateEntry(const SHA1Sum::Hash *aHash,
   1.910 +                        const uint32_t      *aFrecency,
   1.911 +                        const uint32_t      *aExpirationTime,
   1.912 +                        const uint32_t      *aSize)
   1.913 +{
   1.914 +  LOG(("CacheIndex::UpdateEntry() [hash=%08x%08x%08x%08x%08x, "
   1.915 +       "frecency=%s, expirationTime=%s, size=%s]", LOGSHA1(aHash),
   1.916 +       aFrecency ? nsPrintfCString("%u", *aFrecency).get() : "",
   1.917 +       aExpirationTime ? nsPrintfCString("%u", *aExpirationTime).get() : "",
   1.918 +       aSize ? nsPrintfCString("%u", *aSize).get() : ""));
   1.919 +
   1.920 +  nsRefPtr<CacheIndex> index = gInstance;
   1.921 +
   1.922 +  if (!index) {
   1.923 +    return NS_ERROR_NOT_INITIALIZED;
   1.924 +  }
   1.925 +
   1.926 +  MOZ_ASSERT(CacheFileIOManager::IsOnIOThread());
   1.927 +
   1.928 +  CacheIndexAutoLock lock(index);
   1.929 +
   1.930 +  if (!index->IsIndexUsable()) {
   1.931 +    return NS_ERROR_NOT_AVAILABLE;
   1.932 +  }
   1.933 +
   1.934 +  {
   1.935 +    CacheIndexEntryAutoManage entryMng(aHash, index);
   1.936 +
   1.937 +    CacheIndexEntry *entry = index->mIndex.GetEntry(*aHash);
   1.938 +
   1.939 +    if (entry && entry->IsRemoved()) {
   1.940 +      entry = nullptr;
   1.941 +    }
   1.942 +
   1.943 +    if (index->mState == READY || index->mState == UPDATING ||
   1.944 +        index->mState == BUILDING) {
   1.945 +      MOZ_ASSERT(index->mPendingUpdates.Count() == 0);
   1.946 +      MOZ_ASSERT(entry);
   1.947 +
   1.948 +      if (!HasEntryChanged(entry, aFrecency, aExpirationTime, aSize)) {
   1.949 +        return NS_OK;
   1.950 +      }
   1.951 +    } else {
   1.952 +      CacheIndexEntry *updated = index->mPendingUpdates.GetEntry(*aHash);
   1.953 +      DebugOnly<bool> removed = updated && updated->IsRemoved();
   1.954 +
   1.955 +      MOZ_ASSERT(updated || !removed);
   1.956 +      MOZ_ASSERT(updated || entry);
   1.957 +
   1.958 +      if (!updated) {
   1.959 +        if (entry &&
   1.960 +            HasEntryChanged(entry, aFrecency, aExpirationTime, aSize)) {
   1.961 +          // make a copy of a read-only entry
   1.962 +          updated = index->mPendingUpdates.PutEntry(*aHash);
   1.963 +          *updated = *entry;
   1.964 +          entry = updated;
   1.965 +        } else {
   1.966 +          return NS_ERROR_NOT_AVAILABLE;
   1.967 +        }
   1.968 +      } else {
   1.969 +        entry = updated;
   1.970 +      }
   1.971 +    }
   1.972 +
   1.973 +    MOZ_ASSERT(entry->IsFresh());
   1.974 +    MOZ_ASSERT(entry->IsInitialized());
   1.975 +    entry->MarkDirty();
   1.976 +
   1.977 +    if (aFrecency) {
   1.978 +      entry->SetFrecency(*aFrecency);
   1.979 +    }
   1.980 +
   1.981 +    if (aExpirationTime) {
   1.982 +      entry->SetExpirationTime(*aExpirationTime);
   1.983 +    }
   1.984 +
   1.985 +    if (aSize) {
   1.986 +      entry->SetFileSize(*aSize);
   1.987 +    }
   1.988 +  }
   1.989 +
   1.990 +  index->WriteIndexToDiskIfNeeded();
   1.991 +
   1.992 +  return NS_OK;
   1.993 +}
   1.994 +
   1.995 +// static
   1.996 +nsresult
   1.997 +CacheIndex::RemoveAll()
   1.998 +{
   1.999 +  LOG(("CacheIndex::RemoveAll()"));
  1.1000 +
  1.1001 +  nsRefPtr<CacheIndex> index = gInstance;
  1.1002 +
  1.1003 +  if (!index) {
  1.1004 +    return NS_ERROR_NOT_INITIALIZED;
  1.1005 +  }
  1.1006 +
  1.1007 +  MOZ_ASSERT(CacheFileIOManager::IsOnIOThread());
  1.1008 +
  1.1009 +  nsCOMPtr<nsIFile> file;
  1.1010 +
  1.1011 +  {
  1.1012 +    CacheIndexAutoLock lock(index);
  1.1013 +
  1.1014 +    MOZ_ASSERT(!index->mRemovingAll);
  1.1015 +
  1.1016 +    if (!index->IsIndexUsable()) {
  1.1017 +      return NS_ERROR_NOT_AVAILABLE;
  1.1018 +    }
  1.1019 +
  1.1020 +    AutoRestore<bool> saveRemovingAll(index->mRemovingAll);
  1.1021 +    index->mRemovingAll = true;
  1.1022 +
  1.1023 +    // Doom index and journal handles but don't null them out since this will be
  1.1024 +    // done in FinishWrite/FinishRead methods.
  1.1025 +    if (index->mIndexHandle) {
  1.1026 +      CacheFileIOManager::DoomFile(index->mIndexHandle, nullptr);
  1.1027 +    } else {
  1.1028 +      // We don't have a handle to index file, so get the file here, but delete
  1.1029 +      // it outside the lock. Ignore the result since this is not fatal.
  1.1030 +      index->GetFile(NS_LITERAL_CSTRING(kIndexName), getter_AddRefs(file));
  1.1031 +    }
  1.1032 +
  1.1033 +    if (index->mJournalHandle) {
  1.1034 +      CacheFileIOManager::DoomFile(index->mJournalHandle, nullptr);
  1.1035 +    }
  1.1036 +
  1.1037 +    switch (index->mState) {
  1.1038 +      case WRITING:
  1.1039 +        index->FinishWrite(false);
  1.1040 +        break;
  1.1041 +      case READY:
  1.1042 +        // nothing to do
  1.1043 +        break;
  1.1044 +      case READING:
  1.1045 +        index->FinishRead(false);
  1.1046 +        break;
  1.1047 +      case BUILDING:
  1.1048 +      case UPDATING:
  1.1049 +        index->FinishUpdate(false);
  1.1050 +        break;
  1.1051 +      default:
  1.1052 +        MOZ_ASSERT(false, "Unexpected state!");
  1.1053 +    }
  1.1054 +
  1.1055 +    // We should end up in READY state
  1.1056 +    MOZ_ASSERT(index->mState == READY);
  1.1057 +
  1.1058 +    // There should not be any handle
  1.1059 +    MOZ_ASSERT(!index->mIndexHandle);
  1.1060 +    MOZ_ASSERT(!index->mJournalHandle);
  1.1061 +
  1.1062 +    index->mIndexOnDiskIsValid = false;
  1.1063 +    index->mIndexNeedsUpdate = false;
  1.1064 +
  1.1065 +    index->mIndexStats.Clear();
  1.1066 +    index->mFrecencyArray.Clear();
  1.1067 +    index->mExpirationArray.Clear();
  1.1068 +    index->mIndex.Clear();
  1.1069 +  }
  1.1070 +
  1.1071 +  if (file) {
  1.1072 +    // Ignore the result. The file might not exist and the failure is not fatal.
  1.1073 +    file->Remove(false);
  1.1074 +  }
  1.1075 +
  1.1076 +  return NS_OK;
  1.1077 +}
  1.1078 +
  1.1079 +// static
  1.1080 +nsresult
  1.1081 +CacheIndex::HasEntry(const nsACString &aKey, EntryStatus *_retval)
  1.1082 +{
  1.1083 +  LOG(("CacheIndex::HasEntry() [key=%s]", PromiseFlatCString(aKey).get()));
  1.1084 +
  1.1085 +  nsRefPtr<CacheIndex> index = gInstance;
  1.1086 +
  1.1087 +  if (!index) {
  1.1088 +    return NS_ERROR_NOT_INITIALIZED;
  1.1089 +  }
  1.1090 +
  1.1091 +  CacheIndexAutoLock lock(index);
  1.1092 +
  1.1093 +  if (!index->IsIndexUsable()) {
  1.1094 +    return NS_ERROR_NOT_AVAILABLE;
  1.1095 +  }
  1.1096 +
  1.1097 +  SHA1Sum sum;
  1.1098 +  SHA1Sum::Hash hash;
  1.1099 +  sum.update(aKey.BeginReading(), aKey.Length());
  1.1100 +  sum.finish(hash);
  1.1101 +
  1.1102 +  CacheIndexEntry *entry = nullptr;
  1.1103 +
  1.1104 +  switch (index->mState) {
  1.1105 +    case READING:
  1.1106 +    case WRITING:
  1.1107 +      entry = index->mPendingUpdates.GetEntry(hash);
  1.1108 +      // no break
  1.1109 +    case BUILDING:
  1.1110 +    case UPDATING:
  1.1111 +    case READY:
  1.1112 +      if (!entry) {
  1.1113 +        entry = index->mIndex.GetEntry(hash);
  1.1114 +      }
  1.1115 +      break;
  1.1116 +    case INITIAL:
  1.1117 +    case SHUTDOWN:
  1.1118 +      MOZ_ASSERT(false, "Unexpected state!");
  1.1119 +  }
  1.1120 +
  1.1121 +  if (!entry) {
  1.1122 +    if (index->mState == READY || index->mState == WRITING) {
  1.1123 +      *_retval = DOES_NOT_EXIST;
  1.1124 +    } else {
  1.1125 +      *_retval = DO_NOT_KNOW;
  1.1126 +    }
  1.1127 +  } else {
  1.1128 +    if (entry->IsRemoved()) {
  1.1129 +      if (entry->IsFresh()) {
  1.1130 +        *_retval = DOES_NOT_EXIST;
  1.1131 +      } else {
  1.1132 +        *_retval = DO_NOT_KNOW;
  1.1133 +      }
  1.1134 +    } else {
  1.1135 +      *_retval = EXISTS;
  1.1136 +    }
  1.1137 +  }
  1.1138 +
  1.1139 +  LOG(("CacheIndex::HasEntry() - result is %u", *_retval));
  1.1140 +  return NS_OK;
  1.1141 +}
  1.1142 +
  1.1143 +// static
  1.1144 +nsresult
  1.1145 +CacheIndex::GetEntryForEviction(SHA1Sum::Hash *aHash, uint32_t *aCnt)
  1.1146 +{
  1.1147 +  LOG(("CacheIndex::GetEntryForEviction()"));
  1.1148 +
  1.1149 +  nsRefPtr<CacheIndex> index = gInstance;
  1.1150 +
  1.1151 +  if (!index)
  1.1152 +    return NS_ERROR_NOT_INITIALIZED;
  1.1153 +
  1.1154 +  CacheIndexAutoLock lock(index);
  1.1155 +
  1.1156 +  if (!index->IsIndexUsable()) {
  1.1157 +    return NS_ERROR_NOT_AVAILABLE;
  1.1158 +  }
  1.1159 +
  1.1160 +  MOZ_ASSERT(index->mFrecencyArray.Length() ==
  1.1161 +             index->mExpirationArray.Length());
  1.1162 +
  1.1163 +  if (index->mExpirationArray.Length() == 0)
  1.1164 +    return NS_ERROR_NOT_AVAILABLE;
  1.1165 +
  1.1166 +  uint32_t now = PR_Now() / PR_USEC_PER_SEC;
  1.1167 +  if (index->mExpirationArray[0]->mExpirationTime < now) {
  1.1168 +    memcpy(aHash, &index->mExpirationArray[0]->mHash, sizeof(SHA1Sum::Hash));
  1.1169 +    *aCnt = index->mExpirationArray.Length();
  1.1170 +    LOG(("CacheIndex::GetEntryForEviction() - returning entry from expiration "
  1.1171 +         "array [hash=%08x%08x%08x%08x%08x, cnt=%u, expTime=%u, now=%u, "
  1.1172 +         "frecency=%u]", LOGSHA1(aHash), *aCnt,
  1.1173 +         index->mExpirationArray[0]->mExpirationTime, now,
  1.1174 +         index->mExpirationArray[0]->mFrecency));
  1.1175 +  }
  1.1176 +  else {
  1.1177 +    memcpy(aHash, &index->mFrecencyArray[0]->mHash, sizeof(SHA1Sum::Hash));
  1.1178 +    *aCnt = index->mFrecencyArray.Length();
  1.1179 +    LOG(("CacheIndex::GetEntryForEviction() - returning entry from frecency "
  1.1180 +         "array [hash=%08x%08x%08x%08x%08x, cnt=%u, expTime=%u, now=%u, "
  1.1181 +         "frecency=%u]", LOGSHA1(aHash), *aCnt,
  1.1182 +         index->mExpirationArray[0]->mExpirationTime, now,
  1.1183 +         index->mExpirationArray[0]->mFrecency));
  1.1184 +  }
  1.1185 +
  1.1186 +  return NS_OK;
  1.1187 +}
  1.1188 +
  1.1189 +// static
  1.1190 +nsresult
  1.1191 +CacheIndex::GetCacheSize(uint32_t *_retval)
  1.1192 +{
  1.1193 +  LOG(("CacheIndex::GetCacheSize()"));
  1.1194 +
  1.1195 +  nsRefPtr<CacheIndex> index = gInstance;
  1.1196 +
  1.1197 +  if (!index)
  1.1198 +    return NS_ERROR_NOT_INITIALIZED;
  1.1199 +
  1.1200 +  CacheIndexAutoLock lock(index);
  1.1201 +
  1.1202 +  if (!index->IsIndexUsable()) {
  1.1203 +    return NS_ERROR_NOT_AVAILABLE;
  1.1204 +  }
  1.1205 +
  1.1206 +  *_retval = index->mIndexStats.Size();
  1.1207 +  LOG(("CacheIndex::GetCacheSize() - returning %u", *_retval));
  1.1208 +  return NS_OK;
  1.1209 +}
  1.1210 +
  1.1211 +// static
  1.1212 +nsresult
  1.1213 +CacheIndex::AsyncGetDiskConsumption(nsICacheStorageConsumptionObserver* aObserver)
  1.1214 +{
  1.1215 +  LOG(("CacheIndex::AsyncGetDiskConsumption()"));
  1.1216 +
  1.1217 +  nsRefPtr<CacheIndex> index = gInstance;
  1.1218 +
  1.1219 +  if (!index) {
  1.1220 +    return NS_ERROR_NOT_INITIALIZED;
  1.1221 +  }
  1.1222 +
  1.1223 +  CacheIndexAutoLock lock(index);
  1.1224 +
  1.1225 +  if (!index->IsIndexUsable()) {
  1.1226 +    return NS_ERROR_NOT_AVAILABLE;
  1.1227 +  }
  1.1228 +
  1.1229 +  nsRefPtr<DiskConsumptionObserver> observer =
  1.1230 +    DiskConsumptionObserver::Init(aObserver);
  1.1231 +
  1.1232 +  NS_ENSURE_ARG(observer);
  1.1233 +
  1.1234 +  if (index->mState == READY || index->mState == WRITING) {
  1.1235 +    LOG(("CacheIndex::AsyncGetDiskConsumption - calling immediately"));
  1.1236 +    // Safe to call the callback under the lock,
  1.1237 +    // we always post to the main thread.
  1.1238 +    observer->OnDiskConsumption(index->mIndexStats.Size() << 10);
  1.1239 +    return NS_OK;
  1.1240 +  }
  1.1241 +
  1.1242 +  LOG(("CacheIndex::AsyncGetDiskConsumption - remembering callback"));
  1.1243 +  // Will be called when the index get to the READY state.
  1.1244 +  index->mDiskConsumptionObservers.AppendElement(observer);
  1.1245 +
  1.1246 +  return NS_OK;
  1.1247 +}
  1.1248 +
  1.1249 +// static
  1.1250 +nsresult
  1.1251 +CacheIndex::GetIterator(nsILoadContextInfo *aInfo, bool aAddNew,
  1.1252 +                        CacheIndexIterator **_retval)
  1.1253 +{
  1.1254 +  LOG(("CacheIndex::GetIterator() [info=%p, addNew=%d]", aInfo, aAddNew));
  1.1255 +
  1.1256 +  nsRefPtr<CacheIndex> index = gInstance;
  1.1257 +
  1.1258 +  if (!index) {
  1.1259 +    return NS_ERROR_NOT_INITIALIZED;
  1.1260 +  }
  1.1261 +
  1.1262 +  CacheIndexAutoLock lock(index);
  1.1263 +
  1.1264 +  if (!index->IsIndexUsable()) {
  1.1265 +    return NS_ERROR_NOT_AVAILABLE;
  1.1266 +  }
  1.1267 +
  1.1268 +  nsRefPtr<CacheIndexIterator> iter;
  1.1269 +  if (aInfo) {
  1.1270 +    iter = new CacheIndexContextIterator(index, aAddNew, aInfo);
  1.1271 +  } else {
  1.1272 +    iter = new CacheIndexIterator(index, aAddNew);
  1.1273 +  }
  1.1274 +
  1.1275 +  iter->AddRecords(index->mFrecencyArray);
  1.1276 +
  1.1277 +  index->mIterators.AppendElement(iter);
  1.1278 +  iter.swap(*_retval);
  1.1279 +  return NS_OK;
  1.1280 +}
  1.1281 +
  1.1282 +// static
  1.1283 +nsresult
  1.1284 +CacheIndex::IsUpToDate(bool *_retval)
  1.1285 +{
  1.1286 +  LOG(("CacheIndex::IsUpToDate()"));
  1.1287 +
  1.1288 +  nsRefPtr<CacheIndex> index = gInstance;
  1.1289 +
  1.1290 +  if (!index) {
  1.1291 +    return NS_ERROR_NOT_INITIALIZED;
  1.1292 +  }
  1.1293 +
  1.1294 +  CacheIndexAutoLock lock(index);
  1.1295 +
  1.1296 +  if (!index->IsIndexUsable()) {
  1.1297 +    return NS_ERROR_NOT_AVAILABLE;
  1.1298 +  }
  1.1299 +
  1.1300 +  *_retval = (index->mState == READY || index->mState == WRITING) &&
  1.1301 +             !index->mIndexNeedsUpdate && !index->mShuttingDown;
  1.1302 +
  1.1303 +  LOG(("CacheIndex::IsUpToDate() - returning %p", *_retval));
  1.1304 +  return NS_OK;
  1.1305 +}
  1.1306 +
  1.1307 +bool
  1.1308 +CacheIndex::IsIndexUsable()
  1.1309 +{
  1.1310 +  MOZ_ASSERT(mState != INITIAL);
  1.1311 +
  1.1312 +  switch (mState) {
  1.1313 +    case INITIAL:
  1.1314 +    case SHUTDOWN:
  1.1315 +      return false;
  1.1316 +
  1.1317 +    case READING:
  1.1318 +    case WRITING:
  1.1319 +    case BUILDING:
  1.1320 +    case UPDATING:
  1.1321 +    case READY:
  1.1322 +      break;
  1.1323 +  }
  1.1324 +
  1.1325 +  return true;
  1.1326 +}
  1.1327 +
  1.1328 +// static
  1.1329 +bool
  1.1330 +CacheIndex::IsCollision(CacheIndexEntry *aEntry,
  1.1331 +                        uint32_t         aAppId,
  1.1332 +                        bool             aAnonymous,
  1.1333 +                        bool             aInBrowser)
  1.1334 +{
  1.1335 +  if (!aEntry->IsInitialized()) {
  1.1336 +    return false;
  1.1337 +  }
  1.1338 +
  1.1339 +  if (aEntry->AppId() != aAppId || aEntry->Anonymous() != aAnonymous ||
  1.1340 +      aEntry->InBrowser() != aInBrowser) {
  1.1341 +    LOG(("CacheIndex::IsCollision() - Collision detected for entry hash=%08x"
  1.1342 +         "%08x%08x%08x%08x, expected values: appId=%u, anonymous=%d, "
  1.1343 +         "inBrowser=%d; actual values: appId=%u, anonymous=%d, inBrowser=%d]",
  1.1344 +         LOGSHA1(aEntry->Hash()), aAppId, aAnonymous, aInBrowser,
  1.1345 +         aEntry->AppId(), aEntry->Anonymous(), aEntry->InBrowser()));
  1.1346 +    return true;
  1.1347 +  }
  1.1348 +
  1.1349 +  return false;
  1.1350 +}
  1.1351 +
  1.1352 +// static
  1.1353 +bool
  1.1354 +CacheIndex::HasEntryChanged(CacheIndexEntry *aEntry,
  1.1355 +                            const uint32_t  *aFrecency,
  1.1356 +                            const uint32_t  *aExpirationTime,
  1.1357 +                            const uint32_t  *aSize)
  1.1358 +{
  1.1359 +  if (aFrecency && *aFrecency != aEntry->GetFrecency()) {
  1.1360 +    return true;
  1.1361 +  }
  1.1362 +
  1.1363 +  if (aExpirationTime && *aExpirationTime != aEntry->GetExpirationTime()) {
  1.1364 +    return true;
  1.1365 +  }
  1.1366 +
  1.1367 +  if (aSize &&
  1.1368 +      (*aSize & CacheIndexEntry::kFileSizeMask) != aEntry->GetFileSize()) {
  1.1369 +    return true;
  1.1370 +  }
  1.1371 +
  1.1372 +  return false;
  1.1373 +}
  1.1374 +
  1.1375 +void
  1.1376 +CacheIndex::ProcessPendingOperations()
  1.1377 +{
  1.1378 +  LOG(("CacheIndex::ProcessPendingOperations()"));
  1.1379 +
  1.1380 +  AssertOwnsLock();
  1.1381 +
  1.1382 +  mPendingUpdates.EnumerateEntries(&CacheIndex::UpdateEntryInIndex, this);
  1.1383 +
  1.1384 +  MOZ_ASSERT(mPendingUpdates.Count() == 0);
  1.1385 +
  1.1386 +  EnsureCorrectStats();
  1.1387 +}
  1.1388 +
  1.1389 +// static
  1.1390 +PLDHashOperator
  1.1391 +CacheIndex::UpdateEntryInIndex(CacheIndexEntry *aEntry, void* aClosure)
  1.1392 +{
  1.1393 +  CacheIndex *index = static_cast<CacheIndex *>(aClosure);
  1.1394 +
  1.1395 +  LOG(("CacheFile::UpdateEntryInIndex() [hash=%08x%08x%08x%08x%08x]",
  1.1396 +       LOGSHA1(aEntry->Hash())));
  1.1397 +
  1.1398 +  MOZ_ASSERT(aEntry->IsFresh());
  1.1399 +  MOZ_ASSERT(aEntry->IsDirty());
  1.1400 +
  1.1401 +  CacheIndexEntry *entry = index->mIndex.GetEntry(*aEntry->Hash());
  1.1402 +
  1.1403 +  CacheIndexEntryAutoManage emng(aEntry->Hash(), index);
  1.1404 +  emng.DoNotSearchInUpdates();
  1.1405 +
  1.1406 +  if (aEntry->IsRemoved()) {
  1.1407 +    if (entry) {
  1.1408 +      if (entry->IsRemoved()) {
  1.1409 +        MOZ_ASSERT(entry->IsFresh());
  1.1410 +        MOZ_ASSERT(entry->IsDirty());
  1.1411 +      } else if (!entry->IsDirty() && entry->IsFileEmpty()) {
  1.1412 +        // Entries with empty file are not stored in index on disk. Just remove
  1.1413 +        // the entry, but only in case the entry is not dirty, i.e. the entry
  1.1414 +        // file was empty when we wrote the index.
  1.1415 +        index->mIndex.RemoveEntry(*aEntry->Hash());
  1.1416 +        entry = nullptr;
  1.1417 +      } else {
  1.1418 +        entry->MarkRemoved();
  1.1419 +        entry->MarkDirty();
  1.1420 +        entry->MarkFresh();
  1.1421 +      }
  1.1422 +    }
  1.1423 +
  1.1424 +    return PL_DHASH_REMOVE;
  1.1425 +  }
  1.1426 +
  1.1427 +  entry = index->mIndex.PutEntry(*aEntry->Hash());
  1.1428 +  *entry = *aEntry;
  1.1429 +
  1.1430 +  return PL_DHASH_REMOVE;
  1.1431 +}
  1.1432 +
  1.1433 +bool
  1.1434 +CacheIndex::WriteIndexToDiskIfNeeded()
  1.1435 +{
  1.1436 +  if (mState != READY || mShuttingDown) {
  1.1437 +    return false;
  1.1438 +  }
  1.1439 +
  1.1440 +  if (!mLastDumpTime.IsNull() &&
  1.1441 +      (TimeStamp::NowLoRes() - mLastDumpTime).ToMilliseconds() <
  1.1442 +      kMinDumpInterval) {
  1.1443 +    return false;
  1.1444 +  }
  1.1445 +
  1.1446 +  if (mIndexStats.Dirty() < kMinUnwrittenChanges) {
  1.1447 +    return false;
  1.1448 +  }
  1.1449 +
  1.1450 +  WriteIndexToDisk();
  1.1451 +  return true;
  1.1452 +}
  1.1453 +
  1.1454 +void
  1.1455 +CacheIndex::WriteIndexToDisk()
  1.1456 +{
  1.1457 +  LOG(("CacheIndex::WriteIndexToDisk()"));
  1.1458 +  mIndexStats.Log();
  1.1459 +
  1.1460 +  nsresult rv;
  1.1461 +
  1.1462 +  AssertOwnsLock();
  1.1463 +  MOZ_ASSERT(mState == READY);
  1.1464 +  MOZ_ASSERT(!mRWBuf);
  1.1465 +  MOZ_ASSERT(!mRWHash);
  1.1466 +
  1.1467 +  ChangeState(WRITING);
  1.1468 +
  1.1469 +  mProcessEntries = mIndexStats.ActiveEntriesCount();
  1.1470 +
  1.1471 +  mIndexFileOpener = new FileOpenHelper(this);
  1.1472 +  rv = CacheFileIOManager::OpenFile(NS_LITERAL_CSTRING(kTempIndexName),
  1.1473 +                                    CacheFileIOManager::SPECIAL_FILE |
  1.1474 +                                    CacheFileIOManager::CREATE,
  1.1475 +                                    true,
  1.1476 +                                    mIndexFileOpener);
  1.1477 +  if (NS_FAILED(rv)) {
  1.1478 +    LOG(("CacheIndex::WriteIndexToDisk() - Can't open file [rv=0x%08x]", rv));
  1.1479 +    FinishWrite(false);
  1.1480 +    return;
  1.1481 +  }
  1.1482 +
  1.1483 +  // Write index header to a buffer, it will be written to disk together with
  1.1484 +  // records in WriteRecords() once we open the file successfully.
  1.1485 +  AllocBuffer();
  1.1486 +  mRWHash = new CacheHash();
  1.1487 +
  1.1488 +  CacheIndexHeader *hdr = reinterpret_cast<CacheIndexHeader *>(mRWBuf);
  1.1489 +  NetworkEndian::writeUint32(&hdr->mVersion, kIndexVersion);
  1.1490 +  NetworkEndian::writeUint32(&hdr->mTimeStamp,
  1.1491 +                             static_cast<uint32_t>(PR_Now() / PR_USEC_PER_SEC));
  1.1492 +  NetworkEndian::writeUint32(&hdr->mIsDirty, 1);
  1.1493 +
  1.1494 +  mRWBufPos = sizeof(CacheIndexHeader);
  1.1495 +  mSkipEntries = 0;
  1.1496 +}
  1.1497 +
  1.1498 +namespace { // anon
  1.1499 +
  1.1500 +struct WriteRecordsHelper
  1.1501 +{
  1.1502 +  char    *mBuf;
  1.1503 +  uint32_t mSkip;
  1.1504 +  uint32_t mProcessMax;
  1.1505 +  uint32_t mProcessed;
  1.1506 +#ifdef DEBUG
  1.1507 +  bool     mHasMore;
  1.1508 +#endif
  1.1509 +};
  1.1510 +
  1.1511 +} // anon
  1.1512 +
  1.1513 +void
  1.1514 +CacheIndex::WriteRecords()
  1.1515 +{
  1.1516 +  LOG(("CacheIndex::WriteRecords()"));
  1.1517 +
  1.1518 +  nsresult rv;
  1.1519 +
  1.1520 +  AssertOwnsLock();
  1.1521 +  MOZ_ASSERT(mState == WRITING);
  1.1522 +
  1.1523 +  int64_t fileOffset;
  1.1524 +
  1.1525 +  if (mSkipEntries) {
  1.1526 +    MOZ_ASSERT(mRWBufPos == 0);
  1.1527 +    fileOffset = sizeof(CacheIndexHeader);
  1.1528 +    fileOffset += sizeof(CacheIndexRecord) * mSkipEntries;
  1.1529 +  } else {
  1.1530 +    MOZ_ASSERT(mRWBufPos == sizeof(CacheIndexHeader));
  1.1531 +    fileOffset = 0;
  1.1532 +  }
  1.1533 +  uint32_t hashOffset = mRWBufPos;
  1.1534 +
  1.1535 +  WriteRecordsHelper data;
  1.1536 +  data.mBuf = mRWBuf + mRWBufPos;
  1.1537 +  data.mSkip = mSkipEntries;
  1.1538 +  data.mProcessMax = (mRWBufSize - mRWBufPos) / sizeof(CacheIndexRecord);
  1.1539 +  MOZ_ASSERT(data.mProcessMax != 0 || mProcessEntries == 0); // TODO make sure we can write an empty index
  1.1540 +  data.mProcessed = 0;
  1.1541 +#ifdef DEBUG
  1.1542 +  data.mHasMore = false;
  1.1543 +#endif
  1.1544 +
  1.1545 +  mIndex.EnumerateEntries(&CacheIndex::CopyRecordsToRWBuf, &data);
  1.1546 +  MOZ_ASSERT(mRWBufPos != static_cast<uint32_t>(data.mBuf - mRWBuf) ||
  1.1547 +             mProcessEntries == 0);
  1.1548 +  mRWBufPos = data.mBuf - mRWBuf;
  1.1549 +  mSkipEntries += data.mProcessed;
  1.1550 +  MOZ_ASSERT(mSkipEntries <= mProcessEntries);
  1.1551 +
  1.1552 +  mRWHash->Update(mRWBuf + hashOffset, mRWBufPos - hashOffset);
  1.1553 +
  1.1554 +  if (mSkipEntries == mProcessEntries) {
  1.1555 +    MOZ_ASSERT(!data.mHasMore);
  1.1556 +
  1.1557 +    // We've processed all records
  1.1558 +    if (mRWBufPos + sizeof(CacheHash::Hash32_t) > mRWBufSize) {
  1.1559 +      // realloc buffer to spare another write cycle
  1.1560 +      mRWBufSize = mRWBufPos + sizeof(CacheHash::Hash32_t);
  1.1561 +      mRWBuf = static_cast<char *>(moz_xrealloc(mRWBuf, mRWBufSize));
  1.1562 +    }
  1.1563 +
  1.1564 +    NetworkEndian::writeUint32(mRWBuf + mRWBufPos, mRWHash->GetHash());
  1.1565 +    mRWBufPos += sizeof(CacheHash::Hash32_t);
  1.1566 +  } else {
  1.1567 +    MOZ_ASSERT(data.mHasMore);
  1.1568 +  }
  1.1569 +
  1.1570 +  rv = CacheFileIOManager::Write(mIndexHandle, fileOffset, mRWBuf, mRWBufPos,
  1.1571 +                                 mSkipEntries == mProcessEntries, this);
  1.1572 +  if (NS_FAILED(rv)) {
  1.1573 +    LOG(("CacheIndex::WriteRecords() - CacheFileIOManager::Write() failed "
  1.1574 +         "synchronously [rv=0x%08x]", rv));
  1.1575 +    FinishWrite(false);
  1.1576 +  }
  1.1577 +
  1.1578 +  mRWBufPos = 0;
  1.1579 +}
  1.1580 +
  1.1581 +void
  1.1582 +CacheIndex::FinishWrite(bool aSucceeded)
  1.1583 +{
  1.1584 +  LOG(("CacheIndex::FinishWrite() [succeeded=%d]", aSucceeded));
  1.1585 +
  1.1586 +  MOZ_ASSERT((!aSucceeded && mState == SHUTDOWN) || mState == WRITING);
  1.1587 +
  1.1588 +  AssertOwnsLock();
  1.1589 +
  1.1590 +  mIndexHandle = nullptr;
  1.1591 +  mRWHash = nullptr;
  1.1592 +  ReleaseBuffer();
  1.1593 +
  1.1594 +  if (aSucceeded) {
  1.1595 +    // Opening of the file must not be in progress if writing succeeded.
  1.1596 +    MOZ_ASSERT(!mIndexFileOpener);
  1.1597 +
  1.1598 +    mIndex.EnumerateEntries(&CacheIndex::ApplyIndexChanges, this);
  1.1599 +    mIndexOnDiskIsValid = true;
  1.1600 +  } else {
  1.1601 +    if (mIndexFileOpener) {
  1.1602 +      // If opening of the file is still in progress (e.g. WRITE process was
  1.1603 +      // canceled by RemoveAll()) then we need to cancel the opener to make sure
  1.1604 +      // that OnFileOpenedInternal() won't be called.
  1.1605 +      mIndexFileOpener->Cancel();
  1.1606 +      mIndexFileOpener = nullptr;
  1.1607 +    }
  1.1608 +  }
  1.1609 +
  1.1610 +  ProcessPendingOperations();
  1.1611 +  mIndexStats.Log();
  1.1612 +
  1.1613 +  if (mState == WRITING) {
  1.1614 +    ChangeState(READY);
  1.1615 +    mLastDumpTime = TimeStamp::NowLoRes();
  1.1616 +  }
  1.1617 +}
  1.1618 +
  1.1619 +// static
  1.1620 +PLDHashOperator
  1.1621 +CacheIndex::CopyRecordsToRWBuf(CacheIndexEntry *aEntry, void* aClosure)
  1.1622 +{
  1.1623 +  if (aEntry->IsRemoved()) {
  1.1624 +    return PL_DHASH_NEXT;
  1.1625 +  }
  1.1626 +
  1.1627 +  if (!aEntry->IsInitialized()) {
  1.1628 +    return PL_DHASH_NEXT;
  1.1629 +  }
  1.1630 +
  1.1631 +  if (aEntry->IsFileEmpty()) {
  1.1632 +    return PL_DHASH_NEXT;
  1.1633 +  }
  1.1634 +
  1.1635 +  WriteRecordsHelper *data = static_cast<WriteRecordsHelper *>(aClosure);
  1.1636 +  if (data->mSkip) {
  1.1637 +    data->mSkip--;
  1.1638 +    return PL_DHASH_NEXT;
  1.1639 +  }
  1.1640 +
  1.1641 +  if (data->mProcessed == data->mProcessMax) {
  1.1642 +#ifdef DEBUG
  1.1643 +    data->mHasMore = true;
  1.1644 +#endif
  1.1645 +    return PL_DHASH_STOP;
  1.1646 +  }
  1.1647 +
  1.1648 +  aEntry->WriteToBuf(data->mBuf);
  1.1649 +  data->mBuf += sizeof(CacheIndexRecord);
  1.1650 +  data->mProcessed++;
  1.1651 +
  1.1652 +  return PL_DHASH_NEXT;
  1.1653 +}
  1.1654 +
  1.1655 +// static
  1.1656 +PLDHashOperator
  1.1657 +CacheIndex::ApplyIndexChanges(CacheIndexEntry *aEntry, void* aClosure)
  1.1658 +{
  1.1659 +  CacheIndex *index = static_cast<CacheIndex *>(aClosure);
  1.1660 +
  1.1661 +  CacheIndexEntryAutoManage emng(aEntry->Hash(), index);
  1.1662 +
  1.1663 +  if (aEntry->IsRemoved()) {
  1.1664 +    emng.DoNotSearchInIndex();
  1.1665 +    return PL_DHASH_REMOVE;
  1.1666 +  }
  1.1667 +
  1.1668 +  if (aEntry->IsDirty()) {
  1.1669 +    aEntry->ClearDirty();
  1.1670 +  }
  1.1671 +
  1.1672 +  return PL_DHASH_NEXT;
  1.1673 +}
  1.1674 +
  1.1675 +nsresult
  1.1676 +CacheIndex::GetFile(const nsACString &aName, nsIFile **_retval)
  1.1677 +{
  1.1678 +  nsresult rv;
  1.1679 +
  1.1680 +  nsCOMPtr<nsIFile> file;
  1.1681 +  rv = mCacheDirectory->Clone(getter_AddRefs(file));
  1.1682 +  NS_ENSURE_SUCCESS(rv, rv);
  1.1683 +
  1.1684 +  rv = file->AppendNative(aName);
  1.1685 +  NS_ENSURE_SUCCESS(rv, rv);
  1.1686 +
  1.1687 +  file.swap(*_retval);
  1.1688 +  return NS_OK;
  1.1689 +}
  1.1690 +
  1.1691 +nsresult
  1.1692 +CacheIndex::RemoveFile(const nsACString &aName)
  1.1693 +{
  1.1694 +  MOZ_ASSERT(mState == SHUTDOWN);
  1.1695 +
  1.1696 +  nsresult rv;
  1.1697 +
  1.1698 +  nsCOMPtr<nsIFile> file;
  1.1699 +  rv = GetFile(aName, getter_AddRefs(file));
  1.1700 +  NS_ENSURE_SUCCESS(rv, rv);
  1.1701 +
  1.1702 +  bool exists;
  1.1703 +  rv = file->Exists(&exists);
  1.1704 +  NS_ENSURE_SUCCESS(rv, rv);
  1.1705 +
  1.1706 +  if (exists) {
  1.1707 +    rv = file->Remove(false);
  1.1708 +    if (NS_FAILED(rv)) {
  1.1709 +      LOG(("CacheIndex::RemoveFile() - Cannot remove old entry file from disk."
  1.1710 +           "[name=%s]", PromiseFlatCString(aName).get()));
  1.1711 +      NS_WARNING("Cannot remove old entry file from the disk");
  1.1712 +      return rv;
  1.1713 +    }
  1.1714 +  }
  1.1715 +
  1.1716 +  return NS_OK;
  1.1717 +}
  1.1718 +
  1.1719 +void
  1.1720 +CacheIndex::RemoveIndexFromDisk()
  1.1721 +{
  1.1722 +  LOG(("CacheIndex::RemoveIndexFromDisk()"));
  1.1723 +
  1.1724 +  RemoveFile(NS_LITERAL_CSTRING(kIndexName));
  1.1725 +  RemoveFile(NS_LITERAL_CSTRING(kTempIndexName));
  1.1726 +  RemoveFile(NS_LITERAL_CSTRING(kJournalName));
  1.1727 +}
  1.1728 +
  1.1729 +class WriteLogHelper
  1.1730 +{
  1.1731 +public:
  1.1732 +  WriteLogHelper(PRFileDesc *aFD)
  1.1733 +    : mStatus(NS_OK)
  1.1734 +    , mFD(aFD)
  1.1735 +    , mBufSize(kMaxBufSize)
  1.1736 +    , mBufPos(0)
  1.1737 +  {
  1.1738 +    mHash = new CacheHash();
  1.1739 +    mBuf = static_cast<char *>(moz_xmalloc(mBufSize));
  1.1740 +  }
  1.1741 +
  1.1742 +  ~WriteLogHelper() {
  1.1743 +    free(mBuf);
  1.1744 +  }
  1.1745 +
  1.1746 +  nsresult AddEntry(CacheIndexEntry *aEntry);
  1.1747 +  nsresult Finish();
  1.1748 +
  1.1749 +private:
  1.1750 +
  1.1751 +  nsresult FlushBuffer();
  1.1752 +
  1.1753 +  nsresult            mStatus;
  1.1754 +  PRFileDesc         *mFD;
  1.1755 +  char               *mBuf;
  1.1756 +  uint32_t            mBufSize;
  1.1757 +  int32_t             mBufPos;
  1.1758 +  nsRefPtr<CacheHash> mHash;
  1.1759 +};
  1.1760 +
  1.1761 +nsresult
  1.1762 +WriteLogHelper::AddEntry(CacheIndexEntry *aEntry)
  1.1763 +{
  1.1764 +  nsresult rv;
  1.1765 +
  1.1766 +  if (NS_FAILED(mStatus)) {
  1.1767 +    return mStatus;
  1.1768 +  }
  1.1769 +
  1.1770 +  if (mBufPos + sizeof(CacheIndexRecord) > mBufSize) {
  1.1771 +    mHash->Update(mBuf, mBufPos);
  1.1772 +
  1.1773 +    rv = FlushBuffer();
  1.1774 +    if (NS_FAILED(rv)) {
  1.1775 +      mStatus = rv;
  1.1776 +      return rv;
  1.1777 +    }
  1.1778 +    MOZ_ASSERT(mBufPos + sizeof(CacheIndexRecord) <= mBufSize);
  1.1779 +  }
  1.1780 +
  1.1781 +  aEntry->WriteToBuf(mBuf + mBufPos);
  1.1782 +  mBufPos += sizeof(CacheIndexRecord);
  1.1783 +
  1.1784 +  return NS_OK;
  1.1785 +}
  1.1786 +
  1.1787 +nsresult
  1.1788 +WriteLogHelper::Finish()
  1.1789 +{
  1.1790 +  nsresult rv;
  1.1791 +
  1.1792 +  if (NS_FAILED(mStatus)) {
  1.1793 +    return mStatus;
  1.1794 +  }
  1.1795 +
  1.1796 +  mHash->Update(mBuf, mBufPos);
  1.1797 +  if (mBufPos + sizeof(CacheHash::Hash32_t) > mBufSize) {
  1.1798 +    rv = FlushBuffer();
  1.1799 +    if (NS_FAILED(rv)) {
  1.1800 +      mStatus = rv;
  1.1801 +      return rv;
  1.1802 +    }
  1.1803 +    MOZ_ASSERT(mBufPos + sizeof(CacheHash::Hash32_t) <= mBufSize);
  1.1804 +  }
  1.1805 +
  1.1806 +  NetworkEndian::writeUint32(mBuf + mBufPos, mHash->GetHash());
  1.1807 +  mBufPos += sizeof(CacheHash::Hash32_t);
  1.1808 +
  1.1809 +  rv = FlushBuffer();
  1.1810 +  NS_ENSURE_SUCCESS(rv, rv);
  1.1811 +
  1.1812 +  mStatus = NS_ERROR_UNEXPECTED; // Don't allow any other operation
  1.1813 +  return NS_OK;
  1.1814 +}
  1.1815 +
  1.1816 +nsresult
  1.1817 +WriteLogHelper::FlushBuffer()
  1.1818 +{
  1.1819 +  MOZ_ASSERT(NS_SUCCEEDED(mStatus));
  1.1820 +
  1.1821 +  int32_t bytesWritten = PR_Write(mFD, mBuf, mBufPos);
  1.1822 +
  1.1823 +  if (bytesWritten != mBufPos) {
  1.1824 +    return NS_ERROR_FAILURE;
  1.1825 +  }
  1.1826 +
  1.1827 +  mBufPos = 0;
  1.1828 +  return NS_OK;
  1.1829 +}
  1.1830 +
  1.1831 +nsresult
  1.1832 +CacheIndex::WriteLogToDisk()
  1.1833 +{
  1.1834 +  LOG(("CacheIndex::WriteLogToDisk()"));
  1.1835 +
  1.1836 +  nsresult rv;
  1.1837 +
  1.1838 +  MOZ_ASSERT(mPendingUpdates.Count() == 0);
  1.1839 +  MOZ_ASSERT(mState == SHUTDOWN);
  1.1840 +
  1.1841 +  RemoveFile(NS_LITERAL_CSTRING(kTempIndexName));
  1.1842 +
  1.1843 +  nsCOMPtr<nsIFile> indexFile;
  1.1844 +  rv = GetFile(NS_LITERAL_CSTRING(kIndexName), getter_AddRefs(indexFile));
  1.1845 +  NS_ENSURE_SUCCESS(rv, rv);
  1.1846 +
  1.1847 +  nsCOMPtr<nsIFile> logFile;
  1.1848 +  rv = GetFile(NS_LITERAL_CSTRING(kJournalName), getter_AddRefs(logFile));
  1.1849 +  NS_ENSURE_SUCCESS(rv, rv);
  1.1850 +
  1.1851 +  mIndexStats.Log();
  1.1852 +
  1.1853 +  PRFileDesc *fd = nullptr;
  1.1854 +  rv = logFile->OpenNSPRFileDesc(PR_RDWR | PR_CREATE_FILE | PR_TRUNCATE,
  1.1855 +                                 0600, &fd);
  1.1856 +  NS_ENSURE_SUCCESS(rv, rv);
  1.1857 +
  1.1858 +  WriteLogHelper wlh(fd);
  1.1859 +  mIndex.EnumerateEntries(&CacheIndex::WriteEntryToLog, &wlh);
  1.1860 +
  1.1861 +  rv = wlh.Finish();
  1.1862 +  PR_Close(fd);
  1.1863 +  NS_ENSURE_SUCCESS(rv, rv);
  1.1864 +
  1.1865 +  rv = indexFile->OpenNSPRFileDesc(PR_RDWR, 0600, &fd);
  1.1866 +  NS_ENSURE_SUCCESS(rv, rv);
  1.1867 +
  1.1868 +  CacheIndexHeader header;
  1.1869 +  int32_t bytesRead = PR_Read(fd, &header, sizeof(CacheIndexHeader));
  1.1870 +  if (bytesRead != sizeof(CacheIndexHeader)) {
  1.1871 +    PR_Close(fd);
  1.1872 +    return NS_ERROR_FAILURE;
  1.1873 +  }
  1.1874 +
  1.1875 +  NetworkEndian::writeUint32(&header.mIsDirty, 0);
  1.1876 +
  1.1877 +  int64_t offset = PR_Seek64(fd, 0, PR_SEEK_SET);
  1.1878 +  if (offset == -1) {
  1.1879 +    PR_Close(fd);
  1.1880 +    return NS_ERROR_FAILURE;
  1.1881 +  }
  1.1882 +
  1.1883 +  int32_t bytesWritten = PR_Write(fd, &header, sizeof(CacheIndexHeader));
  1.1884 +  PR_Close(fd);
  1.1885 +  if (bytesWritten != sizeof(CacheIndexHeader)) {
  1.1886 +    return NS_ERROR_FAILURE;
  1.1887 +  }
  1.1888 +
  1.1889 +  return NS_OK;
  1.1890 +}
  1.1891 +
  1.1892 +// static
  1.1893 +PLDHashOperator
  1.1894 +CacheIndex::WriteEntryToLog(CacheIndexEntry *aEntry, void* aClosure)
  1.1895 +{
  1.1896 +  WriteLogHelper *wlh = static_cast<WriteLogHelper *>(aClosure);
  1.1897 +
  1.1898 +  if (aEntry->IsRemoved() || aEntry->IsDirty()) {
  1.1899 +    wlh->AddEntry(aEntry);
  1.1900 +  }
  1.1901 +
  1.1902 +  return PL_DHASH_REMOVE;
  1.1903 +}
  1.1904 +
  1.1905 +void
  1.1906 +CacheIndex::ReadIndexFromDisk()
  1.1907 +{
  1.1908 +  LOG(("CacheIndex::ReadIndexFromDisk()"));
  1.1909 +
  1.1910 +  nsresult rv;
  1.1911 +
  1.1912 +  AssertOwnsLock();
  1.1913 +  MOZ_ASSERT(mState == INITIAL);
  1.1914 +
  1.1915 +  ChangeState(READING);
  1.1916 +
  1.1917 +  mIndexFileOpener = new FileOpenHelper(this);
  1.1918 +  rv = CacheFileIOManager::OpenFile(NS_LITERAL_CSTRING(kIndexName),
  1.1919 +                                    CacheFileIOManager::SPECIAL_FILE |
  1.1920 +                                    CacheFileIOManager::OPEN,
  1.1921 +                                    true,
  1.1922 +                                    mIndexFileOpener);
  1.1923 +  if (NS_FAILED(rv)) {
  1.1924 +    LOG(("CacheIndex::ReadIndexFromDisk() - CacheFileIOManager::OpenFile() "
  1.1925 +         "failed [rv=0x%08x, file=%s]", rv, kIndexName));
  1.1926 +    FinishRead(false);
  1.1927 +    return;
  1.1928 +  }
  1.1929 +
  1.1930 +  mJournalFileOpener = new FileOpenHelper(this);
  1.1931 +  rv = CacheFileIOManager::OpenFile(NS_LITERAL_CSTRING(kJournalName),
  1.1932 +                                    CacheFileIOManager::SPECIAL_FILE |
  1.1933 +                                    CacheFileIOManager::OPEN,
  1.1934 +                                    true,
  1.1935 +                                    mJournalFileOpener);
  1.1936 +  if (NS_FAILED(rv)) {
  1.1937 +    LOG(("CacheIndex::ReadIndexFromDisk() - CacheFileIOManager::OpenFile() "
  1.1938 +         "failed [rv=0x%08x, file=%s]", rv, kJournalName));
  1.1939 +    FinishRead(false);
  1.1940 +  }
  1.1941 +
  1.1942 +  mTmpFileOpener = new FileOpenHelper(this);
  1.1943 +  rv = CacheFileIOManager::OpenFile(NS_LITERAL_CSTRING(kTempIndexName),
  1.1944 +                                    CacheFileIOManager::SPECIAL_FILE |
  1.1945 +                                    CacheFileIOManager::OPEN,
  1.1946 +                                    true,
  1.1947 +                                    mTmpFileOpener);
  1.1948 +  if (NS_FAILED(rv)) {
  1.1949 +    LOG(("CacheIndex::ReadIndexFromDisk() - CacheFileIOManager::OpenFile() "
  1.1950 +         "failed [rv=0x%08x, file=%s]", rv, kTempIndexName));
  1.1951 +    FinishRead(false);
  1.1952 +  }
  1.1953 +}
  1.1954 +
  1.1955 +void
  1.1956 +CacheIndex::StartReadingIndex()
  1.1957 +{
  1.1958 +  LOG(("CacheIndex::StartReadingIndex()"));
  1.1959 +
  1.1960 +  nsresult rv;
  1.1961 +
  1.1962 +  AssertOwnsLock();
  1.1963 +
  1.1964 +  MOZ_ASSERT(mIndexHandle);
  1.1965 +  MOZ_ASSERT(mState == READING);
  1.1966 +  MOZ_ASSERT(!mIndexOnDiskIsValid);
  1.1967 +  MOZ_ASSERT(!mDontMarkIndexClean);
  1.1968 +  MOZ_ASSERT(!mJournalReadSuccessfully);
  1.1969 +  MOZ_ASSERT(mIndexHandle->FileSize() >= 0);
  1.1970 +
  1.1971 +  int64_t entriesSize = mIndexHandle->FileSize() - sizeof(CacheIndexHeader) -
  1.1972 +                        sizeof(CacheHash::Hash32_t);
  1.1973 +
  1.1974 +  if (entriesSize < 0 || entriesSize % sizeof(CacheIndexRecord)) {
  1.1975 +    LOG(("CacheIndex::StartReadingIndex() - Index is corrupted"));
  1.1976 +    FinishRead(false);
  1.1977 +    return;
  1.1978 +  }
  1.1979 +
  1.1980 +  AllocBuffer();
  1.1981 +  mSkipEntries = 0;
  1.1982 +  mRWHash = new CacheHash();
  1.1983 +
  1.1984 +  mRWBufPos = std::min(mRWBufSize,
  1.1985 +                       static_cast<uint32_t>(mIndexHandle->FileSize()));
  1.1986 +
  1.1987 +  rv = CacheFileIOManager::Read(mIndexHandle, 0, mRWBuf, mRWBufPos, true, this);
  1.1988 +  if (NS_FAILED(rv)) {
  1.1989 +    LOG(("CacheIndex::StartReadingIndex() - CacheFileIOManager::Read() failed "
  1.1990 +         "synchronously [rv=0x%08x]", rv));
  1.1991 +    FinishRead(false);
  1.1992 +  }
  1.1993 +}
  1.1994 +
  1.1995 +void
  1.1996 +CacheIndex::ParseRecords()
  1.1997 +{
  1.1998 +  LOG(("CacheIndex::ParseRecords()"));
  1.1999 +
  1.2000 +  nsresult rv;
  1.2001 +
  1.2002 +  AssertOwnsLock();
  1.2003 +
  1.2004 +  uint32_t entryCnt = (mIndexHandle->FileSize() - sizeof(CacheIndexHeader) -
  1.2005 +                     sizeof(CacheHash::Hash32_t)) / sizeof(CacheIndexRecord);
  1.2006 +  uint32_t pos = 0;
  1.2007 +
  1.2008 +  if (!mSkipEntries) {
  1.2009 +    CacheIndexHeader *hdr = reinterpret_cast<CacheIndexHeader *>(
  1.2010 +                              moz_xmalloc(sizeof(CacheIndexHeader)));
  1.2011 +    memcpy(hdr, mRWBuf, sizeof(CacheIndexHeader));
  1.2012 +
  1.2013 +    if (NetworkEndian::readUint32(&hdr->mVersion) != kIndexVersion) {
  1.2014 +      free(hdr);
  1.2015 +      FinishRead(false);
  1.2016 +      return;
  1.2017 +    }
  1.2018 +
  1.2019 +    mIndexTimeStamp = NetworkEndian::readUint32(&hdr->mTimeStamp);
  1.2020 +
  1.2021 +    if (NetworkEndian::readUint32(&hdr->mIsDirty)) {
  1.2022 +      if (mJournalHandle) {
  1.2023 +        CacheFileIOManager::DoomFile(mJournalHandle, nullptr);
  1.2024 +        mJournalHandle = nullptr;
  1.2025 +      }
  1.2026 +      free(hdr);
  1.2027 +    } else {
  1.2028 +      NetworkEndian::writeUint32(&hdr->mIsDirty, 1);
  1.2029 +
  1.2030 +      // Mark index dirty. The buffer is freed by CacheFileIOManager when
  1.2031 +      // nullptr is passed as the listener and the call doesn't fail
  1.2032 +      // synchronously.
  1.2033 +      rv = CacheFileIOManager::Write(mIndexHandle, 0,
  1.2034 +                                     reinterpret_cast<char *>(hdr),
  1.2035 +                                     sizeof(CacheIndexHeader), true, nullptr);
  1.2036 +      if (NS_FAILED(rv)) {
  1.2037 +        // This is not fatal, just free the memory
  1.2038 +        free(hdr);
  1.2039 +      }
  1.2040 +    }
  1.2041 +
  1.2042 +    pos += sizeof(CacheIndexHeader);
  1.2043 +  }
  1.2044 +
  1.2045 +  uint32_t hashOffset = pos;
  1.2046 +
  1.2047 +  while (pos + sizeof(CacheIndexRecord) <= mRWBufPos &&
  1.2048 +         mSkipEntries != entryCnt) {
  1.2049 +    CacheIndexRecord *rec = reinterpret_cast<CacheIndexRecord *>(mRWBuf + pos);
  1.2050 +    CacheIndexEntry tmpEntry(&rec->mHash);
  1.2051 +    tmpEntry.ReadFromBuf(mRWBuf + pos);
  1.2052 +
  1.2053 +    if (tmpEntry.IsDirty() || !tmpEntry.IsInitialized() ||
  1.2054 +        tmpEntry.IsFileEmpty() || tmpEntry.IsFresh() || tmpEntry.IsRemoved()) {
  1.2055 +      LOG(("CacheIndex::ParseRecords() - Invalid entry found in index, removing"
  1.2056 +           " whole index [dirty=%d, initialized=%d, fileEmpty=%d, fresh=%d, "
  1.2057 +           "removed=%d]", tmpEntry.IsDirty(), tmpEntry.IsInitialized(),
  1.2058 +           tmpEntry.IsFileEmpty(), tmpEntry.IsFresh(), tmpEntry.IsRemoved()));
  1.2059 +      FinishRead(false);
  1.2060 +      return;
  1.2061 +    }
  1.2062 +
  1.2063 +    CacheIndexEntryAutoManage emng(tmpEntry.Hash(), this);
  1.2064 +
  1.2065 +    CacheIndexEntry *entry = mIndex.PutEntry(*tmpEntry.Hash());
  1.2066 +    *entry = tmpEntry;
  1.2067 +
  1.2068 +    pos += sizeof(CacheIndexRecord);
  1.2069 +    mSkipEntries++;
  1.2070 +  }
  1.2071 +
  1.2072 +  mRWHash->Update(mRWBuf + hashOffset, pos - hashOffset);
  1.2073 +
  1.2074 +  if (pos != mRWBufPos) {
  1.2075 +    memmove(mRWBuf, mRWBuf + pos, mRWBufPos - pos);
  1.2076 +    mRWBufPos -= pos;
  1.2077 +    pos = 0;
  1.2078 +  }
  1.2079 +
  1.2080 +  int64_t fileOffset = sizeof(CacheIndexHeader) +
  1.2081 +                       mSkipEntries * sizeof(CacheIndexRecord) + mRWBufPos;
  1.2082 +
  1.2083 +  MOZ_ASSERT(fileOffset <= mIndexHandle->FileSize());
  1.2084 +  if (fileOffset == mIndexHandle->FileSize()) {
  1.2085 +    if (mRWHash->GetHash() != NetworkEndian::readUint32(mRWBuf)) {
  1.2086 +      LOG(("CacheIndex::ParseRecords() - Hash mismatch, [is %x, should be %x]",
  1.2087 +           mRWHash->GetHash(),
  1.2088 +           NetworkEndian::readUint32(mRWBuf)));
  1.2089 +      FinishRead(false);
  1.2090 +      return;
  1.2091 +    }
  1.2092 +
  1.2093 +    mIndexOnDiskIsValid = true;
  1.2094 +    mJournalReadSuccessfully = false;
  1.2095 +
  1.2096 +    if (mJournalHandle) {
  1.2097 +      StartReadingJournal();
  1.2098 +    } else {
  1.2099 +      FinishRead(false);
  1.2100 +    }
  1.2101 +
  1.2102 +    return;
  1.2103 +  }
  1.2104 +
  1.2105 +  pos = mRWBufPos;
  1.2106 +  uint32_t toRead = std::min(mRWBufSize - pos,
  1.2107 +                             static_cast<uint32_t>(mIndexHandle->FileSize() -
  1.2108 +                                                   fileOffset));
  1.2109 +  mRWBufPos = pos + toRead;
  1.2110 +
  1.2111 +  rv = CacheFileIOManager::Read(mIndexHandle, fileOffset, mRWBuf + pos, toRead,
  1.2112 +                                true, this);
  1.2113 +  if (NS_FAILED(rv)) {
  1.2114 +    LOG(("CacheIndex::ParseRecords() - CacheFileIOManager::Read() failed "
  1.2115 +         "synchronously [rv=0x%08x]", rv));
  1.2116 +    FinishRead(false);
  1.2117 +    return;
  1.2118 +  }
  1.2119 +}
  1.2120 +
  1.2121 +void
  1.2122 +CacheIndex::StartReadingJournal()
  1.2123 +{
  1.2124 +  LOG(("CacheIndex::StartReadingJournal()"));
  1.2125 +
  1.2126 +  nsresult rv;
  1.2127 +
  1.2128 +  AssertOwnsLock();
  1.2129 +
  1.2130 +  MOZ_ASSERT(mJournalHandle);
  1.2131 +  MOZ_ASSERT(mIndexOnDiskIsValid);
  1.2132 +  MOZ_ASSERT(mTmpJournal.Count() == 0);
  1.2133 +  MOZ_ASSERT(mJournalHandle->FileSize() >= 0);
  1.2134 +
  1.2135 +  int64_t entriesSize = mJournalHandle->FileSize() -
  1.2136 +                        sizeof(CacheHash::Hash32_t);
  1.2137 +
  1.2138 +  if (entriesSize < 0 || entriesSize % sizeof(CacheIndexRecord)) {
  1.2139 +    LOG(("CacheIndex::StartReadingJournal() - Journal is corrupted"));
  1.2140 +    FinishRead(false);
  1.2141 +    return;
  1.2142 +  }
  1.2143 +
  1.2144 +  mSkipEntries = 0;
  1.2145 +  mRWHash = new CacheHash();
  1.2146 +
  1.2147 +  mRWBufPos = std::min(mRWBufSize,
  1.2148 +                       static_cast<uint32_t>(mJournalHandle->FileSize()));
  1.2149 +
  1.2150 +  rv = CacheFileIOManager::Read(mJournalHandle, 0, mRWBuf, mRWBufPos, true, this);
  1.2151 +  if (NS_FAILED(rv)) {
  1.2152 +    LOG(("CacheIndex::StartReadingJournal() - CacheFileIOManager::Read() failed"
  1.2153 +         " synchronously [rv=0x%08x]", rv));
  1.2154 +    FinishRead(false);
  1.2155 +  }
  1.2156 +}
  1.2157 +
  1.2158 +void
  1.2159 +CacheIndex::ParseJournal()
  1.2160 +{
  1.2161 +  LOG(("CacheIndex::ParseRecords()"));
  1.2162 +
  1.2163 +  nsresult rv;
  1.2164 +
  1.2165 +  AssertOwnsLock();
  1.2166 +
  1.2167 +  uint32_t entryCnt = (mJournalHandle->FileSize() -
  1.2168 +                       sizeof(CacheHash::Hash32_t)) / sizeof(CacheIndexRecord);
  1.2169 +
  1.2170 +  uint32_t pos = 0;
  1.2171 +
  1.2172 +  while (pos + sizeof(CacheIndexRecord) <= mRWBufPos &&
  1.2173 +         mSkipEntries != entryCnt) {
  1.2174 +    CacheIndexRecord *rec = reinterpret_cast<CacheIndexRecord *>(mRWBuf + pos);
  1.2175 +    CacheIndexEntry tmpEntry(&rec->mHash);
  1.2176 +    tmpEntry.ReadFromBuf(mRWBuf + pos);
  1.2177 +
  1.2178 +    CacheIndexEntry *entry = mTmpJournal.PutEntry(*tmpEntry.Hash());
  1.2179 +    *entry = tmpEntry;
  1.2180 +
  1.2181 +    if (entry->IsDirty() || entry->IsFresh()) {
  1.2182 +      LOG(("CacheIndex::ParseJournal() - Invalid entry found in journal, "
  1.2183 +           "ignoring whole journal [dirty=%d, fresh=%d]", entry->IsDirty(),
  1.2184 +           entry->IsFresh()));
  1.2185 +      FinishRead(false);
  1.2186 +      return;
  1.2187 +    }
  1.2188 +
  1.2189 +    pos += sizeof(CacheIndexRecord);
  1.2190 +    mSkipEntries++;
  1.2191 +  }
  1.2192 +
  1.2193 +  mRWHash->Update(mRWBuf, pos);
  1.2194 +
  1.2195 +  if (pos != mRWBufPos) {
  1.2196 +    memmove(mRWBuf, mRWBuf + pos, mRWBufPos - pos);
  1.2197 +    mRWBufPos -= pos;
  1.2198 +    pos = 0;
  1.2199 +  }
  1.2200 +
  1.2201 +  int64_t fileOffset = mSkipEntries * sizeof(CacheIndexRecord) + mRWBufPos;
  1.2202 +
  1.2203 +  MOZ_ASSERT(fileOffset <= mJournalHandle->FileSize());
  1.2204 +  if (fileOffset == mJournalHandle->FileSize()) {
  1.2205 +    if (mRWHash->GetHash() != NetworkEndian::readUint32(mRWBuf)) {
  1.2206 +      LOG(("CacheIndex::ParseJournal() - Hash mismatch, [is %x, should be %x]",
  1.2207 +           mRWHash->GetHash(),
  1.2208 +           NetworkEndian::readUint32(mRWBuf)));
  1.2209 +      FinishRead(false);
  1.2210 +      return;
  1.2211 +    }
  1.2212 +
  1.2213 +    mJournalReadSuccessfully = true;
  1.2214 +    FinishRead(true);
  1.2215 +    return;
  1.2216 +  }
  1.2217 +
  1.2218 +  pos = mRWBufPos;
  1.2219 +  uint32_t toRead = std::min(mRWBufSize - pos,
  1.2220 +                             static_cast<uint32_t>(mJournalHandle->FileSize() -
  1.2221 +                                                   fileOffset));
  1.2222 +  mRWBufPos = pos + toRead;
  1.2223 +
  1.2224 +  rv = CacheFileIOManager::Read(mJournalHandle, fileOffset, mRWBuf + pos,
  1.2225 +                                toRead, true, this);
  1.2226 +  if (NS_FAILED(rv)) {
  1.2227 +    LOG(("CacheIndex::ParseJournal() - CacheFileIOManager::Read() failed "
  1.2228 +         "synchronously [rv=0x%08x]", rv));
  1.2229 +    FinishRead(false);
  1.2230 +    return;
  1.2231 +  }
  1.2232 +}
  1.2233 +
  1.2234 +void
  1.2235 +CacheIndex::MergeJournal()
  1.2236 +{
  1.2237 +  LOG(("CacheIndex::MergeJournal()"));
  1.2238 +
  1.2239 +  AssertOwnsLock();
  1.2240 +
  1.2241 +  mTmpJournal.EnumerateEntries(&CacheIndex::ProcessJournalEntry, this);
  1.2242 +
  1.2243 +  MOZ_ASSERT(mTmpJournal.Count() == 0);
  1.2244 +}
  1.2245 +
  1.2246 +// static
  1.2247 +PLDHashOperator
  1.2248 +CacheIndex::ProcessJournalEntry(CacheIndexEntry *aEntry, void* aClosure)
  1.2249 +{
  1.2250 +  CacheIndex *index = static_cast<CacheIndex *>(aClosure);
  1.2251 +
  1.2252 +  LOG(("CacheFile::ProcessJournalEntry() [hash=%08x%08x%08x%08x%08x]",
  1.2253 +       LOGSHA1(aEntry->Hash())));
  1.2254 +
  1.2255 +  CacheIndexEntry *entry = index->mIndex.GetEntry(*aEntry->Hash());
  1.2256 +
  1.2257 +  CacheIndexEntryAutoManage emng(aEntry->Hash(), index);
  1.2258 +
  1.2259 +  if (aEntry->IsRemoved()) {
  1.2260 +    if (entry) {
  1.2261 +      entry->MarkRemoved();
  1.2262 +      entry->MarkDirty();
  1.2263 +    }
  1.2264 +  } else {
  1.2265 +    if (!entry) {
  1.2266 +      entry = index->mIndex.PutEntry(*aEntry->Hash());
  1.2267 +    }
  1.2268 +
  1.2269 +    *entry = *aEntry;
  1.2270 +    entry->MarkDirty();
  1.2271 +  }
  1.2272 +
  1.2273 +  return PL_DHASH_REMOVE;
  1.2274 +}
  1.2275 +
  1.2276 +void
  1.2277 +CacheIndex::EnsureNoFreshEntry()
  1.2278 +{
  1.2279 +#ifdef DEBUG_STATS
  1.2280 +  CacheIndexStats debugStats;
  1.2281 +  debugStats.DisableLogging();
  1.2282 +  mIndex.EnumerateEntries(&CacheIndex::SumIndexStats, &debugStats);
  1.2283 +  MOZ_ASSERT(debugStats.Fresh() == 0);
  1.2284 +#endif
  1.2285 +}
  1.2286 +
  1.2287 +void
  1.2288 +CacheIndex::EnsureCorrectStats()
  1.2289 +{
  1.2290 +#ifdef DEBUG_STATS
  1.2291 +  MOZ_ASSERT(mPendingUpdates.Count() == 0);
  1.2292 +  CacheIndexStats debugStats;
  1.2293 +  debugStats.DisableLogging();
  1.2294 +  mIndex.EnumerateEntries(&CacheIndex::SumIndexStats, &debugStats);
  1.2295 +  MOZ_ASSERT(debugStats == mIndexStats);
  1.2296 +#endif
  1.2297 +}
  1.2298 +
  1.2299 +// static
  1.2300 +PLDHashOperator
  1.2301 +CacheIndex::SumIndexStats(CacheIndexEntry *aEntry, void* aClosure)
  1.2302 +{
  1.2303 +  CacheIndexStats *stats = static_cast<CacheIndexStats *>(aClosure);
  1.2304 +  stats->BeforeChange(nullptr);
  1.2305 +  stats->AfterChange(aEntry);
  1.2306 +  return PL_DHASH_NEXT;
  1.2307 +}
  1.2308 +
  1.2309 +void
  1.2310 +CacheIndex::FinishRead(bool aSucceeded)
  1.2311 +{
  1.2312 +  LOG(("CacheIndex::FinishRead() [succeeded=%d]", aSucceeded));
  1.2313 +  AssertOwnsLock();
  1.2314 +
  1.2315 +  MOZ_ASSERT((!aSucceeded && mState == SHUTDOWN) || mState == READING);
  1.2316 +
  1.2317 +  MOZ_ASSERT(
  1.2318 +    // -> rebuild
  1.2319 +    (!aSucceeded && !mIndexOnDiskIsValid && !mJournalReadSuccessfully) ||
  1.2320 +    // -> update
  1.2321 +    (!aSucceeded && mIndexOnDiskIsValid && !mJournalReadSuccessfully) ||
  1.2322 +    // -> ready
  1.2323 +    (aSucceeded && mIndexOnDiskIsValid && mJournalReadSuccessfully));
  1.2324 +
  1.2325 +  if (mState == SHUTDOWN) {
  1.2326 +    RemoveFile(NS_LITERAL_CSTRING(kTempIndexName));
  1.2327 +    RemoveFile(NS_LITERAL_CSTRING(kJournalName));
  1.2328 +  } else {
  1.2329 +    if (mIndexHandle && !mIndexOnDiskIsValid) {
  1.2330 +      CacheFileIOManager::DoomFile(mIndexHandle, nullptr);
  1.2331 +    }
  1.2332 +
  1.2333 +    if (mJournalHandle) {
  1.2334 +      CacheFileIOManager::DoomFile(mJournalHandle, nullptr);
  1.2335 +    }
  1.2336 +  }
  1.2337 +
  1.2338 +  if (mIndexFileOpener) {
  1.2339 +    mIndexFileOpener->Cancel();
  1.2340 +    mIndexFileOpener = nullptr;
  1.2341 +  }
  1.2342 +  if (mJournalFileOpener) {
  1.2343 +    mJournalFileOpener->Cancel();
  1.2344 +    mJournalFileOpener = nullptr;
  1.2345 +  }
  1.2346 +  if (mTmpFileOpener) {
  1.2347 +    mTmpFileOpener->Cancel();
  1.2348 +    mTmpFileOpener = nullptr;
  1.2349 +  }
  1.2350 +
  1.2351 +  mIndexHandle = nullptr;
  1.2352 +  mJournalHandle = nullptr;
  1.2353 +  mRWHash = nullptr;
  1.2354 +  ReleaseBuffer();
  1.2355 +
  1.2356 +  if (mState == SHUTDOWN) {
  1.2357 +    return;
  1.2358 +  }
  1.2359 +
  1.2360 +  if (!mIndexOnDiskIsValid) {
  1.2361 +    MOZ_ASSERT(mTmpJournal.Count() == 0);
  1.2362 +    EnsureNoFreshEntry();
  1.2363 +    ProcessPendingOperations();
  1.2364 +    // Remove all entries that we haven't seen during this session
  1.2365 +    mIndex.EnumerateEntries(&CacheIndex::RemoveNonFreshEntries, this);
  1.2366 +    StartUpdatingIndex(true);
  1.2367 +    return;
  1.2368 +  }
  1.2369 +
  1.2370 +  if (!mJournalReadSuccessfully) {
  1.2371 +    mTmpJournal.Clear();
  1.2372 +    EnsureNoFreshEntry();
  1.2373 +    ProcessPendingOperations();
  1.2374 +    StartUpdatingIndex(false);
  1.2375 +    return;
  1.2376 +  }
  1.2377 +
  1.2378 +  MergeJournal();
  1.2379 +  EnsureNoFreshEntry();
  1.2380 +  ProcessPendingOperations();
  1.2381 +  mIndexStats.Log();
  1.2382 +
  1.2383 +  ChangeState(READY);
  1.2384 +  mLastDumpTime = TimeStamp::NowLoRes(); // Do not dump new index immediately
  1.2385 +}
  1.2386 +
  1.2387 +// static
  1.2388 +void
  1.2389 +CacheIndex::DelayedUpdate(nsITimer *aTimer, void *aClosure)
  1.2390 +{
  1.2391 +  LOG(("CacheIndex::DelayedUpdate()"));
  1.2392 +
  1.2393 +  nsresult rv;
  1.2394 +  nsRefPtr<CacheIndex> index = gInstance;
  1.2395 +
  1.2396 +  if (!index) {
  1.2397 +    return;
  1.2398 +  }
  1.2399 +
  1.2400 +  CacheIndexAutoLock lock(index);
  1.2401 +
  1.2402 +  index->mUpdateTimer = nullptr;
  1.2403 +
  1.2404 +  if (!index->IsIndexUsable()) {
  1.2405 +    return;
  1.2406 +  }
  1.2407 +
  1.2408 +  if (index->mState == READY && index->mShuttingDown) {
  1.2409 +    return;
  1.2410 +  }
  1.2411 +
  1.2412 +  // mUpdateEventPending must be false here since StartUpdatingIndex() won't
  1.2413 +  // schedule timer if it is true.
  1.2414 +  MOZ_ASSERT(!index->mUpdateEventPending);
  1.2415 +  if (index->mState != BUILDING && index->mState != UPDATING) {
  1.2416 +    LOG(("CacheIndex::DelayedUpdate() - Update was canceled"));
  1.2417 +    return;
  1.2418 +  }
  1.2419 +
  1.2420 +  // We need to redispatch to run with lower priority
  1.2421 +  nsRefPtr<CacheIOThread> ioThread = CacheFileIOManager::IOThread();
  1.2422 +  MOZ_ASSERT(ioThread);
  1.2423 +
  1.2424 +  index->mUpdateEventPending = true;
  1.2425 +  rv = ioThread->Dispatch(index, CacheIOThread::INDEX);
  1.2426 +  if (NS_FAILED(rv)) {
  1.2427 +    index->mUpdateEventPending = false;
  1.2428 +    NS_WARNING("CacheIndex::DelayedUpdate() - Can't dispatch event");
  1.2429 +    LOG(("CacheIndex::DelayedUpdate() - Can't dispatch event" ));
  1.2430 +    index->FinishUpdate(false);
  1.2431 +  }
  1.2432 +}
  1.2433 +
  1.2434 +nsresult
  1.2435 +CacheIndex::ScheduleUpdateTimer(uint32_t aDelay)
  1.2436 +{
  1.2437 +  LOG(("CacheIndex::ScheduleUpdateTimer() [delay=%u]", aDelay));
  1.2438 +
  1.2439 +  MOZ_ASSERT(!mUpdateTimer);
  1.2440 +
  1.2441 +  nsresult rv;
  1.2442 +
  1.2443 +  nsCOMPtr<nsITimer> timer = do_CreateInstance("@mozilla.org/timer;1", &rv);
  1.2444 +  NS_ENSURE_SUCCESS(rv, rv);
  1.2445 +
  1.2446 +  nsCOMPtr<nsIEventTarget> ioTarget = CacheFileIOManager::IOTarget();
  1.2447 +  MOZ_ASSERT(ioTarget);
  1.2448 +
  1.2449 +  rv = timer->SetTarget(ioTarget);
  1.2450 +  NS_ENSURE_SUCCESS(rv, rv);
  1.2451 +
  1.2452 +  rv = timer->InitWithFuncCallback(CacheIndex::DelayedUpdate, nullptr,
  1.2453 +                                   aDelay, nsITimer::TYPE_ONE_SHOT);
  1.2454 +  NS_ENSURE_SUCCESS(rv, rv);
  1.2455 +
  1.2456 +  mUpdateTimer.swap(timer);
  1.2457 +  return NS_OK;
  1.2458 +}
  1.2459 +
  1.2460 +nsresult
  1.2461 +CacheIndex::SetupDirectoryEnumerator()
  1.2462 +{
  1.2463 +  MOZ_ASSERT(!NS_IsMainThread());
  1.2464 +  MOZ_ASSERT(!mDirEnumerator);
  1.2465 +
  1.2466 +  nsresult rv;
  1.2467 +  nsCOMPtr<nsIFile> file;
  1.2468 +
  1.2469 +  rv = mCacheDirectory->Clone(getter_AddRefs(file));
  1.2470 +  NS_ENSURE_SUCCESS(rv, rv);
  1.2471 +
  1.2472 +  rv = file->AppendNative(NS_LITERAL_CSTRING(kEntriesDir));
  1.2473 +  NS_ENSURE_SUCCESS(rv, rv);
  1.2474 +
  1.2475 +  bool exists;
  1.2476 +  rv = file->Exists(&exists);
  1.2477 +  NS_ENSURE_SUCCESS(rv, rv);
  1.2478 +
  1.2479 +  if (!exists) {
  1.2480 +    NS_WARNING("CacheIndex::SetupDirectoryEnumerator() - Entries directory "
  1.2481 +               "doesn't exist!");
  1.2482 +    LOG(("CacheIndex::SetupDirectoryEnumerator() - Entries directory doesn't "
  1.2483 +          "exist!" ));
  1.2484 +    return NS_ERROR_UNEXPECTED;
  1.2485 +  }
  1.2486 +
  1.2487 +  nsCOMPtr<nsISimpleEnumerator> enumerator;
  1.2488 +  rv = file->GetDirectoryEntries(getter_AddRefs(enumerator));
  1.2489 +  NS_ENSURE_SUCCESS(rv, rv);
  1.2490 +
  1.2491 +  mDirEnumerator = do_QueryInterface(enumerator, &rv);
  1.2492 +  NS_ENSURE_SUCCESS(rv, rv);
  1.2493 +
  1.2494 +  return NS_OK;
  1.2495 +}
  1.2496 +
  1.2497 +void
  1.2498 +CacheIndex::InitEntryFromDiskData(CacheIndexEntry *aEntry,
  1.2499 +                                  CacheFileMetadata *aMetaData,
  1.2500 +                                  int64_t aFileSize)
  1.2501 +{
  1.2502 +  aEntry->InitNew();
  1.2503 +  aEntry->MarkDirty();
  1.2504 +  aEntry->MarkFresh();
  1.2505 +  aEntry->Init(aMetaData->AppId(), aMetaData->IsAnonymous(),
  1.2506 +               aMetaData->IsInBrowser());
  1.2507 +
  1.2508 +  uint32_t expirationTime;
  1.2509 +  aMetaData->GetExpirationTime(&expirationTime);
  1.2510 +  aEntry->SetExpirationTime(expirationTime);
  1.2511 +
  1.2512 +  uint32_t frecency;
  1.2513 +  aMetaData->GetFrecency(&frecency);
  1.2514 +  aEntry->SetFrecency(frecency);
  1.2515 +
  1.2516 +  aEntry->SetFileSize(static_cast<uint32_t>(
  1.2517 +                        std::min(static_cast<int64_t>(PR_UINT32_MAX),
  1.2518 +                                 (aFileSize + 0x3FF) >> 10)));
  1.2519 +}
  1.2520 +
  1.2521 +bool
  1.2522 +CacheIndex::IsUpdatePending()
  1.2523 +{
  1.2524 +  AssertOwnsLock();
  1.2525 +
  1.2526 +  if (mUpdateTimer || mUpdateEventPending) {
  1.2527 +    return true;
  1.2528 +  }
  1.2529 +
  1.2530 +  return false;
  1.2531 +}
  1.2532 +
  1.2533 +void
  1.2534 +CacheIndex::BuildIndex()
  1.2535 +{
  1.2536 +  LOG(("CacheIndex::BuildIndex()"));
  1.2537 +
  1.2538 +  AssertOwnsLock();
  1.2539 +
  1.2540 +  MOZ_ASSERT(mPendingUpdates.Count() == 0);
  1.2541 +
  1.2542 +  nsresult rv;
  1.2543 +
  1.2544 +  if (!mDirEnumerator) {
  1.2545 +    {
  1.2546 +      // Do not do IO under the lock.
  1.2547 +      CacheIndexAutoUnlock unlock(this);
  1.2548 +      rv = SetupDirectoryEnumerator();
  1.2549 +    }
  1.2550 +    if (mState == SHUTDOWN) {
  1.2551 +      // The index was shut down while we released the lock. FinishUpdate() was
  1.2552 +      // already called from Shutdown(), so just simply return here.
  1.2553 +      return;
  1.2554 +    }
  1.2555 +
  1.2556 +    if (NS_FAILED(rv)) {
  1.2557 +      FinishUpdate(false);
  1.2558 +      return;
  1.2559 +    }
  1.2560 +  }
  1.2561 +
  1.2562 +  while (true) {
  1.2563 +    if (CacheIOThread::YieldAndRerun()) {
  1.2564 +      LOG(("CacheIndex::BuildIndex() - Breaking loop for higher level events."));
  1.2565 +      mUpdateEventPending = true;
  1.2566 +      return;
  1.2567 +    }
  1.2568 +
  1.2569 +    nsCOMPtr<nsIFile> file;
  1.2570 +    {
  1.2571 +      // Do not do IO under the lock.
  1.2572 +      CacheIndexAutoUnlock unlock(this);
  1.2573 +      rv = mDirEnumerator->GetNextFile(getter_AddRefs(file));
  1.2574 +    }
  1.2575 +    if (mState == SHUTDOWN) {
  1.2576 +      return;
  1.2577 +    }
  1.2578 +    if (!file) {
  1.2579 +      FinishUpdate(NS_SUCCEEDED(rv));
  1.2580 +      return;
  1.2581 +    }
  1.2582 +
  1.2583 +    nsAutoCString leaf;
  1.2584 +    rv = file->GetNativeLeafName(leaf);
  1.2585 +    if (NS_FAILED(rv)) {
  1.2586 +      LOG(("CacheIndex::BuildIndex() - GetNativeLeafName() failed! Skipping "
  1.2587 +           "file."));
  1.2588 +      mDontMarkIndexClean = true;
  1.2589 +      continue;
  1.2590 +    }
  1.2591 +
  1.2592 +    SHA1Sum::Hash hash;
  1.2593 +    rv = CacheFileIOManager::StrToHash(leaf, &hash);
  1.2594 +    if (NS_FAILED(rv)) {
  1.2595 +      LOG(("CacheIndex::BuildIndex() - Filename is not a hash, removing file. "
  1.2596 +           "[name=%s]", leaf.get()));
  1.2597 +      file->Remove(false);
  1.2598 +      continue;
  1.2599 +    }
  1.2600 +
  1.2601 +    CacheIndexEntry *entry = mIndex.GetEntry(hash);
  1.2602 +    if (entry && entry->IsRemoved()) {
  1.2603 +      LOG(("CacheIndex::BuildIndex() - Found file that should not exist. "
  1.2604 +           "[name=%s]", leaf.get()));
  1.2605 +      entry->Log();
  1.2606 +      MOZ_ASSERT(entry->IsFresh());
  1.2607 +      entry = nullptr;
  1.2608 +    }
  1.2609 +
  1.2610 +#ifdef DEBUG
  1.2611 +    nsRefPtr<CacheFileHandle> handle;
  1.2612 +    CacheFileIOManager::gInstance->mHandles.GetHandle(&hash, false,
  1.2613 +                                                      getter_AddRefs(handle));
  1.2614 +#endif
  1.2615 +
  1.2616 +    if (entry) {
  1.2617 +      // the entry is up to date
  1.2618 +      LOG(("CacheIndex::BuildIndex() - Skipping file because the entry is up to"
  1.2619 +           " date. [name=%s]", leaf.get()));
  1.2620 +      entry->Log();
  1.2621 +      MOZ_ASSERT(entry->IsFresh()); // The entry must be from this session
  1.2622 +      // there must be an active CacheFile if the entry is not initialized
  1.2623 +      MOZ_ASSERT(entry->IsInitialized() || handle);
  1.2624 +      continue;
  1.2625 +    }
  1.2626 +
  1.2627 +    MOZ_ASSERT(!handle);
  1.2628 +
  1.2629 +    nsRefPtr<CacheFileMetadata> meta = new CacheFileMetadata();
  1.2630 +    int64_t size = 0;
  1.2631 +
  1.2632 +    {
  1.2633 +      // Do not do IO under the lock.
  1.2634 +      CacheIndexAutoUnlock unlock(this);
  1.2635 +      rv = meta->SyncReadMetadata(file);
  1.2636 +
  1.2637 +      if (NS_SUCCEEDED(rv)) {
  1.2638 +        rv = file->GetFileSize(&size);
  1.2639 +        if (NS_FAILED(rv)) {
  1.2640 +          LOG(("CacheIndex::BuildIndex() - Cannot get filesize of file that was"
  1.2641 +               " successfully parsed. [name=%s]", leaf.get()));
  1.2642 +        }
  1.2643 +      }
  1.2644 +    }
  1.2645 +    if (mState == SHUTDOWN) {
  1.2646 +      return;
  1.2647 +    }
  1.2648 +
  1.2649 +    // Nobody could add the entry while the lock was released since we modify
  1.2650 +    // the index only on IO thread and this loop is executed on IO thread too.
  1.2651 +    entry = mIndex.GetEntry(hash);
  1.2652 +    MOZ_ASSERT(!entry || entry->IsRemoved());
  1.2653 +
  1.2654 +    if (NS_FAILED(rv)) {
  1.2655 +      LOG(("CacheIndex::BuildIndex() - CacheFileMetadata::SyncReadMetadata() "
  1.2656 +           "failed, removing file. [name=%s]", leaf.get()));
  1.2657 +      file->Remove(false);
  1.2658 +    } else {
  1.2659 +      CacheIndexEntryAutoManage entryMng(&hash, this);
  1.2660 +      entry = mIndex.PutEntry(hash);
  1.2661 +      InitEntryFromDiskData(entry, meta, size);
  1.2662 +      LOG(("CacheIndex::BuildIndex() - Added entry to index. [hash=%s]",
  1.2663 +           leaf.get()));
  1.2664 +      entry->Log();
  1.2665 +    }
  1.2666 +  }
  1.2667 +
  1.2668 +  NS_NOTREACHED("We should never get here");
  1.2669 +}
  1.2670 +
  1.2671 +bool
  1.2672 +CacheIndex::StartUpdatingIndexIfNeeded(bool aSwitchingToReadyState)
  1.2673 +{
  1.2674 +  // Start updating process when we are in or we are switching to READY state
  1.2675 +  // and index needs update, but not during shutdown or when removing all
  1.2676 +  // entries.
  1.2677 +  if ((mState == READY || aSwitchingToReadyState) && mIndexNeedsUpdate &&
  1.2678 +      !mShuttingDown && !mRemovingAll) {
  1.2679 +    LOG(("CacheIndex::StartUpdatingIndexIfNeeded() - starting update process"));
  1.2680 +    mIndexNeedsUpdate = false;
  1.2681 +    StartUpdatingIndex(false);
  1.2682 +    return true;
  1.2683 +  }
  1.2684 +
  1.2685 +  return false;
  1.2686 +}
  1.2687 +
  1.2688 +void
  1.2689 +CacheIndex::StartUpdatingIndex(bool aRebuild)
  1.2690 +{
  1.2691 +  LOG(("CacheIndex::StartUpdatingIndex() [rebuild=%d]", aRebuild));
  1.2692 +
  1.2693 +  AssertOwnsLock();
  1.2694 +
  1.2695 +  nsresult rv;
  1.2696 +
  1.2697 +  mIndexStats.Log();
  1.2698 +
  1.2699 +  ChangeState(aRebuild ? BUILDING : UPDATING);
  1.2700 +  mDontMarkIndexClean = false;
  1.2701 +
  1.2702 +  if (mShuttingDown || mRemovingAll) {
  1.2703 +    FinishUpdate(false);
  1.2704 +    return;
  1.2705 +  }
  1.2706 +
  1.2707 +  if (IsUpdatePending()) {
  1.2708 +    LOG(("CacheIndex::StartUpdatingIndex() - Update is already pending"));
  1.2709 +    return;
  1.2710 +  }
  1.2711 +
  1.2712 +  uint32_t elapsed = (TimeStamp::NowLoRes() - mStartTime).ToMilliseconds();
  1.2713 +  if (elapsed < kUpdateIndexStartDelay) {
  1.2714 +    LOG(("CacheIndex::StartUpdatingIndex() - %u ms elapsed since startup, "
  1.2715 +         "scheduling timer to fire in %u ms.", elapsed,
  1.2716 +         kUpdateIndexStartDelay - elapsed));
  1.2717 +    rv = ScheduleUpdateTimer(kUpdateIndexStartDelay - elapsed);
  1.2718 +    if (NS_SUCCEEDED(rv)) {
  1.2719 +      return;
  1.2720 +    }
  1.2721 +
  1.2722 +    LOG(("CacheIndex::StartUpdatingIndex() - ScheduleUpdateTimer() failed. "
  1.2723 +         "Starting update immediately."));
  1.2724 +  } else {
  1.2725 +    LOG(("CacheIndex::StartUpdatingIndex() - %u ms elapsed since startup, "
  1.2726 +         "starting update now.", elapsed));
  1.2727 +  }
  1.2728 +
  1.2729 +  nsRefPtr<CacheIOThread> ioThread = CacheFileIOManager::IOThread();
  1.2730 +  MOZ_ASSERT(ioThread);
  1.2731 +
  1.2732 +  // We need to dispatch an event even if we are on IO thread since we need to
  1.2733 +  // update the index with the correct priority.
  1.2734 +  mUpdateEventPending = true;
  1.2735 +  rv = ioThread->Dispatch(this, CacheIOThread::INDEX);
  1.2736 +  if (NS_FAILED(rv)) {
  1.2737 +    mUpdateEventPending = false;
  1.2738 +    NS_WARNING("CacheIndex::StartUpdatingIndex() - Can't dispatch event");
  1.2739 +    LOG(("CacheIndex::StartUpdatingIndex() - Can't dispatch event" ));
  1.2740 +    FinishUpdate(false);
  1.2741 +  }
  1.2742 +}
  1.2743 +
  1.2744 +void
  1.2745 +CacheIndex::UpdateIndex()
  1.2746 +{
  1.2747 +  LOG(("CacheIndex::UpdateIndex()"));
  1.2748 +
  1.2749 +  AssertOwnsLock();
  1.2750 +
  1.2751 +  MOZ_ASSERT(mPendingUpdates.Count() == 0);
  1.2752 +
  1.2753 +  nsresult rv;
  1.2754 +
  1.2755 +  if (!mDirEnumerator) {
  1.2756 +    {
  1.2757 +      // Do not do IO under the lock.
  1.2758 +      CacheIndexAutoUnlock unlock(this);
  1.2759 +      rv = SetupDirectoryEnumerator();
  1.2760 +    }
  1.2761 +    if (mState == SHUTDOWN) {
  1.2762 +      // The index was shut down while we released the lock. FinishUpdate() was
  1.2763 +      // already called from Shutdown(), so just simply return here.
  1.2764 +      return;
  1.2765 +    }
  1.2766 +
  1.2767 +    if (NS_FAILED(rv)) {
  1.2768 +      FinishUpdate(false);
  1.2769 +      return;
  1.2770 +    }
  1.2771 +  }
  1.2772 +
  1.2773 +  while (true) {
  1.2774 +    if (CacheIOThread::YieldAndRerun()) {
  1.2775 +      LOG(("CacheIndex::UpdateIndex() - Breaking loop for higher level "
  1.2776 +           "events."));
  1.2777 +      mUpdateEventPending = true;
  1.2778 +      return;
  1.2779 +    }
  1.2780 +
  1.2781 +    nsCOMPtr<nsIFile> file;
  1.2782 +    {
  1.2783 +      // Do not do IO under the lock.
  1.2784 +      CacheIndexAutoUnlock unlock(this);
  1.2785 +      rv = mDirEnumerator->GetNextFile(getter_AddRefs(file));
  1.2786 +    }
  1.2787 +    if (mState == SHUTDOWN) {
  1.2788 +      return;
  1.2789 +    }
  1.2790 +    if (!file) {
  1.2791 +      FinishUpdate(NS_SUCCEEDED(rv));
  1.2792 +      return;
  1.2793 +    }
  1.2794 +
  1.2795 +    nsAutoCString leaf;
  1.2796 +    rv = file->GetNativeLeafName(leaf);
  1.2797 +    if (NS_FAILED(rv)) {
  1.2798 +      LOG(("CacheIndex::UpdateIndex() - GetNativeLeafName() failed! Skipping "
  1.2799 +           "file."));
  1.2800 +      mDontMarkIndexClean = true;
  1.2801 +      continue;
  1.2802 +    }
  1.2803 +
  1.2804 +    SHA1Sum::Hash hash;
  1.2805 +    rv = CacheFileIOManager::StrToHash(leaf, &hash);
  1.2806 +    if (NS_FAILED(rv)) {
  1.2807 +      LOG(("CacheIndex::UpdateIndex() - Filename is not a hash, removing file. "
  1.2808 +           "[name=%s]", leaf.get()));
  1.2809 +      file->Remove(false);
  1.2810 +      continue;
  1.2811 +    }
  1.2812 +
  1.2813 +    CacheIndexEntry *entry = mIndex.GetEntry(hash);
  1.2814 +    if (entry && entry->IsRemoved()) {
  1.2815 +      if (entry->IsFresh()) {
  1.2816 +        LOG(("CacheIndex::UpdateIndex() - Found file that should not exist. "
  1.2817 +             "[name=%s]", leaf.get()));
  1.2818 +        entry->Log();
  1.2819 +      }
  1.2820 +      entry = nullptr;
  1.2821 +    }
  1.2822 +
  1.2823 +#ifdef DEBUG
  1.2824 +    nsRefPtr<CacheFileHandle> handle;
  1.2825 +    CacheFileIOManager::gInstance->mHandles.GetHandle(&hash, false,
  1.2826 +                                                      getter_AddRefs(handle));
  1.2827 +#endif
  1.2828 +
  1.2829 +    if (entry && entry->IsFresh()) {
  1.2830 +      // the entry is up to date
  1.2831 +      LOG(("CacheIndex::UpdateIndex() - Skipping file because the entry is up "
  1.2832 +           " to date. [name=%s]", leaf.get()));
  1.2833 +      entry->Log();
  1.2834 +      // there must be an active CacheFile if the entry is not initialized
  1.2835 +      MOZ_ASSERT(entry->IsInitialized() || handle);
  1.2836 +      continue;
  1.2837 +    }
  1.2838 +
  1.2839 +    MOZ_ASSERT(!handle);
  1.2840 +
  1.2841 +    if (entry) {
  1.2842 +      PRTime lastModifiedTime;
  1.2843 +      {
  1.2844 +        // Do not do IO under the lock.
  1.2845 +        CacheIndexAutoUnlock unlock(this);
  1.2846 +        rv = file->GetLastModifiedTime(&lastModifiedTime);
  1.2847 +      }
  1.2848 +      if (mState == SHUTDOWN) {
  1.2849 +        return;
  1.2850 +      }
  1.2851 +      if (NS_FAILED(rv)) {
  1.2852 +        LOG(("CacheIndex::UpdateIndex() - Cannot get lastModifiedTime. "
  1.2853 +             "[name=%s]", leaf.get()));
  1.2854 +        // Assume the file is newer than index
  1.2855 +      } else {
  1.2856 +        if (mIndexTimeStamp > (lastModifiedTime / PR_MSEC_PER_SEC)) {
  1.2857 +          LOG(("CacheIndex::UpdateIndex() - Skipping file because of last "
  1.2858 +               "modified time. [name=%s, indexTimeStamp=%u, "
  1.2859 +               "lastModifiedTime=%u]", leaf.get(), mIndexTimeStamp,
  1.2860 +               lastModifiedTime / PR_MSEC_PER_SEC));
  1.2861 +
  1.2862 +          CacheIndexEntryAutoManage entryMng(&hash, this);
  1.2863 +          entry->MarkFresh();
  1.2864 +          continue;
  1.2865 +        }
  1.2866 +      }
  1.2867 +    }
  1.2868 +
  1.2869 +    nsRefPtr<CacheFileMetadata> meta = new CacheFileMetadata();
  1.2870 +    int64_t size = 0;
  1.2871 +
  1.2872 +    {
  1.2873 +      // Do not do IO under the lock.
  1.2874 +      CacheIndexAutoUnlock unlock(this);
  1.2875 +      rv = meta->SyncReadMetadata(file);
  1.2876 +
  1.2877 +      if (NS_SUCCEEDED(rv)) {
  1.2878 +        rv = file->GetFileSize(&size);
  1.2879 +        if (NS_FAILED(rv)) {
  1.2880 +          LOG(("CacheIndex::UpdateIndex() - Cannot get filesize of file that "
  1.2881 +               "was successfully parsed. [name=%s]", leaf.get()));
  1.2882 +        }
  1.2883 +      }
  1.2884 +    }
  1.2885 +    if (mState == SHUTDOWN) {
  1.2886 +      return;
  1.2887 +    }
  1.2888 +
  1.2889 +    // Nobody could add the entry while the lock was released since we modify
  1.2890 +    // the index only on IO thread and this loop is executed on IO thread too.
  1.2891 +    entry = mIndex.GetEntry(hash);
  1.2892 +    MOZ_ASSERT(!entry || !entry->IsFresh());
  1.2893 +
  1.2894 +    CacheIndexEntryAutoManage entryMng(&hash, this);
  1.2895 +
  1.2896 +    if (NS_FAILED(rv)) {
  1.2897 +      LOG(("CacheIndex::UpdateIndex() - CacheFileMetadata::SyncReadMetadata() "
  1.2898 +           "failed, removing file. [name=%s]", leaf.get()));
  1.2899 +      file->Remove(false);
  1.2900 +      if (entry) {
  1.2901 +        entry->MarkRemoved();
  1.2902 +        entry->MarkFresh();
  1.2903 +        entry->MarkDirty();
  1.2904 +      }
  1.2905 +    } else {
  1.2906 +      entry = mIndex.PutEntry(hash);
  1.2907 +      InitEntryFromDiskData(entry, meta, size);
  1.2908 +      LOG(("CacheIndex::UpdateIndex() - Added/updated entry to/in index. "
  1.2909 +           "[hash=%s]", leaf.get()));
  1.2910 +      entry->Log();
  1.2911 +    }
  1.2912 +  }
  1.2913 +
  1.2914 +  NS_NOTREACHED("We should never get here");
  1.2915 +}
  1.2916 +
  1.2917 +void
  1.2918 +CacheIndex::FinishUpdate(bool aSucceeded)
  1.2919 +{
  1.2920 +  LOG(("CacheIndex::FinishUpdate() [succeeded=%d]", aSucceeded));
  1.2921 +
  1.2922 +  MOZ_ASSERT(mState == UPDATING || mState == BUILDING ||
  1.2923 +             (!aSucceeded && mState == SHUTDOWN));
  1.2924 +
  1.2925 +  AssertOwnsLock();
  1.2926 +
  1.2927 +  if (mDirEnumerator) {
  1.2928 +    if (NS_IsMainThread()) {
  1.2929 +      LOG(("CacheIndex::FinishUpdate() - posting of PreShutdownInternal failed?"
  1.2930 +           " Cannot safely release mDirEnumerator, leaking it!"));
  1.2931 +      NS_WARNING(("CacheIndex::FinishUpdate() - Leaking mDirEnumerator!"));
  1.2932 +      // This can happen only in case dispatching event to IO thread failed in
  1.2933 +      // CacheIndex::PreShutdown().
  1.2934 +      mDirEnumerator.forget(); // Leak it since dir enumerator is not threadsafe
  1.2935 +    } else {
  1.2936 +      mDirEnumerator->Close();
  1.2937 +      mDirEnumerator = nullptr;
  1.2938 +    }
  1.2939 +  }
  1.2940 +
  1.2941 +  if (!aSucceeded) {
  1.2942 +    mDontMarkIndexClean = true;
  1.2943 +  }
  1.2944 +
  1.2945 +  if (mState == SHUTDOWN) {
  1.2946 +    return;
  1.2947 +  }
  1.2948 +
  1.2949 +  if (mState == UPDATING && aSucceeded) {
  1.2950 +    // If we've iterated over all entries successfully then all entries that
  1.2951 +    // really exist on the disk are now marked as fresh. All non-fresh entries
  1.2952 +    // don't exist anymore and must be removed from the index.
  1.2953 +    mIndex.EnumerateEntries(&CacheIndex::RemoveNonFreshEntries, this);
  1.2954 +  }
  1.2955 +
  1.2956 +  // Make sure we won't start update. If the build or update failed, there is no
  1.2957 +  // reason to believe that it will succeed next time.
  1.2958 +  mIndexNeedsUpdate = false;
  1.2959 +
  1.2960 +  ChangeState(READY);
  1.2961 +  mLastDumpTime = TimeStamp::NowLoRes(); // Do not dump new index immediately
  1.2962 +}
  1.2963 +
  1.2964 +// static
  1.2965 +PLDHashOperator
  1.2966 +CacheIndex::RemoveNonFreshEntries(CacheIndexEntry *aEntry, void* aClosure)
  1.2967 +{
  1.2968 +  if (aEntry->IsFresh()) {
  1.2969 +    return PL_DHASH_NEXT;
  1.2970 +  }
  1.2971 +
  1.2972 +  LOG(("CacheFile::RemoveNonFreshEntries() - Removing entry. "
  1.2973 +       "[hash=%08x%08x%08x%08x%08x]", LOGSHA1(aEntry->Hash())));
  1.2974 +
  1.2975 +  CacheIndex *index = static_cast<CacheIndex *>(aClosure);
  1.2976 +
  1.2977 +  CacheIndexEntryAutoManage emng(aEntry->Hash(), index);
  1.2978 +  emng.DoNotSearchInIndex();
  1.2979 +
  1.2980 +  return PL_DHASH_REMOVE;
  1.2981 +}
  1.2982 +
  1.2983 +#ifdef PR_LOGGING
  1.2984 +// static
  1.2985 +char const *
  1.2986 +CacheIndex::StateString(EState aState)
  1.2987 +{
  1.2988 +  switch (aState) {
  1.2989 +    case INITIAL:  return "INITIAL";
  1.2990 +    case READING:  return "READING";
  1.2991 +    case WRITING:  return "WRITING";
  1.2992 +    case BUILDING: return "BUILDING";
  1.2993 +    case UPDATING: return "UPDATING";
  1.2994 +    case READY:    return "READY";
  1.2995 +    case SHUTDOWN: return "SHUTDOWN";
  1.2996 +  }
  1.2997 +
  1.2998 +  MOZ_ASSERT(false, "Unexpected state!");
  1.2999 +  return "?";
  1.3000 +}
  1.3001 +#endif
  1.3002 +
  1.3003 +void
  1.3004 +CacheIndex::ChangeState(EState aNewState)
  1.3005 +{
  1.3006 +  LOG(("CacheIndex::ChangeState() changing state %s -> %s", StateString(mState),
  1.3007 +       StateString(aNewState)));
  1.3008 +
  1.3009 +  // All pending updates should be processed before changing state
  1.3010 +  MOZ_ASSERT(mPendingUpdates.Count() == 0);
  1.3011 +
  1.3012 +  // PreShutdownInternal() should change the state to READY from every state. It
  1.3013 +  // may go through different states, but once we are in READY state the only
  1.3014 +  // possible transition is to SHUTDOWN state.
  1.3015 +  MOZ_ASSERT(!mShuttingDown || mState != READY || aNewState == SHUTDOWN);
  1.3016 +
  1.3017 +  // Start updating process when switching to READY state if needed
  1.3018 +  if (aNewState == READY && StartUpdatingIndexIfNeeded(true)) {
  1.3019 +    return;
  1.3020 +  }
  1.3021 +
  1.3022 +  // Try to evict entries over limit everytime we're leaving state READING,
  1.3023 +  // BUILDING or UPDATING, but not during shutdown or when removing all
  1.3024 +  // entries.
  1.3025 +  if (!mShuttingDown && !mRemovingAll && aNewState != SHUTDOWN &&
  1.3026 +      (mState == READING || mState == BUILDING || mState == UPDATING))  {
  1.3027 +    CacheFileIOManager::EvictIfOverLimit();
  1.3028 +  }
  1.3029 +
  1.3030 +  mState = aNewState;
  1.3031 +
  1.3032 +  if (mState != SHUTDOWN) {
  1.3033 +    CacheFileIOManager::CacheIndexStateChanged();
  1.3034 +  }
  1.3035 +
  1.3036 +  if (mState == READY && mDiskConsumptionObservers.Length()) {
  1.3037 +    for (uint32_t i = 0; i < mDiskConsumptionObservers.Length(); ++i) {
  1.3038 +      DiskConsumptionObserver* o = mDiskConsumptionObservers[i];
  1.3039 +      // Safe to call under the lock.  We always post to the main thread.
  1.3040 +      o->OnDiskConsumption(mIndexStats.Size() << 10);
  1.3041 +    }
  1.3042 +
  1.3043 +    mDiskConsumptionObservers.Clear();
  1.3044 +  }
  1.3045 +}
  1.3046 +
  1.3047 +void
  1.3048 +CacheIndex::AllocBuffer()
  1.3049 +{
  1.3050 +  switch (mState) {
  1.3051 +    case WRITING:
  1.3052 +      mRWBufSize = sizeof(CacheIndexHeader) + sizeof(CacheHash::Hash32_t) +
  1.3053 +                   mProcessEntries * sizeof(CacheIndexRecord);
  1.3054 +      if (mRWBufSize > kMaxBufSize) {
  1.3055 +        mRWBufSize = kMaxBufSize;
  1.3056 +      }
  1.3057 +      break;
  1.3058 +    case READING:
  1.3059 +      mRWBufSize = kMaxBufSize;
  1.3060 +      break;
  1.3061 +    default:
  1.3062 +      MOZ_ASSERT(false, "Unexpected state!");
  1.3063 +  }
  1.3064 +
  1.3065 +  mRWBuf = static_cast<char *>(moz_xmalloc(mRWBufSize));
  1.3066 +}
  1.3067 +
  1.3068 +void
  1.3069 +CacheIndex::ReleaseBuffer()
  1.3070 +{
  1.3071 +  if (!mRWBuf) {
  1.3072 +    return;
  1.3073 +  }
  1.3074 +
  1.3075 +  free(mRWBuf);
  1.3076 +  mRWBuf = nullptr;
  1.3077 +  mRWBufSize = 0;
  1.3078 +  mRWBufPos = 0;
  1.3079 +}
  1.3080 +
  1.3081 +namespace { // anon
  1.3082 +
  1.3083 +class FrecencyComparator
  1.3084 +{
  1.3085 +public:
  1.3086 +  bool Equals(CacheIndexRecord* a, CacheIndexRecord* b) const {
  1.3087 +    return a->mFrecency == b->mFrecency;
  1.3088 +  }
  1.3089 +  bool LessThan(CacheIndexRecord* a, CacheIndexRecord* b) const {
  1.3090 +    // Place entries with frecency 0 at the end of the array.
  1.3091 +    if (a->mFrecency == 0) {
  1.3092 +      return false;
  1.3093 +    }
  1.3094 +    if (b->mFrecency == 0) {
  1.3095 +      return true;
  1.3096 +    }
  1.3097 +    return a->mFrecency < b->mFrecency;
  1.3098 +  }
  1.3099 +};
  1.3100 +
  1.3101 +class ExpirationComparator
  1.3102 +{
  1.3103 +public:
  1.3104 +  bool Equals(CacheIndexRecord* a, CacheIndexRecord* b) const {
  1.3105 +    return a->mExpirationTime == b->mExpirationTime;
  1.3106 +  }
  1.3107 +  bool LessThan(CacheIndexRecord* a, CacheIndexRecord* b) const {
  1.3108 +    return a->mExpirationTime < b->mExpirationTime;
  1.3109 +  }
  1.3110 +};
  1.3111 +
  1.3112 +} // anon
  1.3113 +
  1.3114 +void
  1.3115 +CacheIndex::InsertRecordToFrecencyArray(CacheIndexRecord *aRecord)
  1.3116 +{
  1.3117 +  LOG(("CacheIndex::InsertRecordToFrecencyArray() [record=%p, hash=%08x%08x%08x"
  1.3118 +       "%08x%08x]", aRecord, LOGSHA1(aRecord->mHash)));
  1.3119 +
  1.3120 +  MOZ_ASSERT(!mFrecencyArray.Contains(aRecord));
  1.3121 +  mFrecencyArray.InsertElementSorted(aRecord, FrecencyComparator());
  1.3122 +}
  1.3123 +
  1.3124 +void
  1.3125 +CacheIndex::InsertRecordToExpirationArray(CacheIndexRecord *aRecord)
  1.3126 +{
  1.3127 +  LOG(("CacheIndex::InsertRecordToExpirationArray() [record=%p, hash=%08x%08x"
  1.3128 +       "%08x%08x%08x]", aRecord, LOGSHA1(aRecord->mHash)));
  1.3129 +
  1.3130 +  MOZ_ASSERT(!mExpirationArray.Contains(aRecord));
  1.3131 +  mExpirationArray.InsertElementSorted(aRecord, ExpirationComparator());
  1.3132 +}
  1.3133 +
  1.3134 +void
  1.3135 +CacheIndex::RemoveRecordFromFrecencyArray(CacheIndexRecord *aRecord)
  1.3136 +{
  1.3137 +  LOG(("CacheIndex::RemoveRecordFromFrecencyArray() [record=%p]", aRecord));
  1.3138 +
  1.3139 +  DebugOnly<bool> removed;
  1.3140 +  removed = mFrecencyArray.RemoveElement(aRecord);
  1.3141 +  MOZ_ASSERT(removed);
  1.3142 +}
  1.3143 +
  1.3144 +void
  1.3145 +CacheIndex::RemoveRecordFromExpirationArray(CacheIndexRecord *aRecord)
  1.3146 +{
  1.3147 +  LOG(("CacheIndex::RemoveRecordFromExpirationArray() [record=%p]", aRecord));
  1.3148 +
  1.3149 +  DebugOnly<bool> removed;
  1.3150 +  removed = mExpirationArray.RemoveElement(aRecord);
  1.3151 +  MOZ_ASSERT(removed);
  1.3152 +}
  1.3153 +
  1.3154 +void
  1.3155 +CacheIndex::AddRecordToIterators(CacheIndexRecord *aRecord)
  1.3156 +{
  1.3157 +  AssertOwnsLock();
  1.3158 +
  1.3159 +  for (uint32_t i = 0; i < mIterators.Length(); ++i) {
  1.3160 +    // Add a new record only when iterator is supposed to be updated.
  1.3161 +    if (mIterators[i]->ShouldBeNewAdded()) {
  1.3162 +      mIterators[i]->AddRecord(aRecord);
  1.3163 +    }
  1.3164 +  }
  1.3165 +}
  1.3166 +
  1.3167 +void
  1.3168 +CacheIndex::RemoveRecordFromIterators(CacheIndexRecord *aRecord)
  1.3169 +{
  1.3170 +  AssertOwnsLock();
  1.3171 +
  1.3172 +  for (uint32_t i = 0; i < mIterators.Length(); ++i) {
  1.3173 +    // Remove the record from iterator always, it makes no sence to return
  1.3174 +    // non-existing entries. Also the pointer to the record is no longer valid
  1.3175 +    // once the entry is removed from index.
  1.3176 +    mIterators[i]->RemoveRecord(aRecord);
  1.3177 +  }
  1.3178 +}
  1.3179 +
  1.3180 +void
  1.3181 +CacheIndex::ReplaceRecordInIterators(CacheIndexRecord *aOldRecord,
  1.3182 +                                     CacheIndexRecord *aNewRecord)
  1.3183 +{
  1.3184 +  AssertOwnsLock();
  1.3185 +
  1.3186 +  for (uint32_t i = 0; i < mIterators.Length(); ++i) {
  1.3187 +    // We have to replace the record always since the pointer is no longer
  1.3188 +    // valid after this point. NOTE: Replacing the record doesn't mean that
  1.3189 +    // a new entry was added, it just means that the data in the entry was
  1.3190 +    // changed (e.g. a file size) and we had to track this change in
  1.3191 +    // mPendingUpdates since mIndex was read-only.
  1.3192 +    mIterators[i]->ReplaceRecord(aOldRecord, aNewRecord);
  1.3193 +  }
  1.3194 +}
  1.3195 +
  1.3196 +nsresult
  1.3197 +CacheIndex::Run()
  1.3198 +{
  1.3199 +  LOG(("CacheIndex::Run()"));
  1.3200 +
  1.3201 +  CacheIndexAutoLock lock(this);
  1.3202 +
  1.3203 +  if (!IsIndexUsable()) {
  1.3204 +    return NS_ERROR_NOT_AVAILABLE;
  1.3205 +  }
  1.3206 +
  1.3207 +  if (mState == READY && mShuttingDown) {
  1.3208 +    return NS_OK;
  1.3209 +  }
  1.3210 +
  1.3211 +  mUpdateEventPending = false;
  1.3212 +
  1.3213 +  switch (mState) {
  1.3214 +    case BUILDING:
  1.3215 +      BuildIndex();
  1.3216 +      break;
  1.3217 +    case UPDATING:
  1.3218 +      UpdateIndex();
  1.3219 +      break;
  1.3220 +    default:
  1.3221 +      LOG(("CacheIndex::Run() - Update/Build was canceled"));
  1.3222 +  }
  1.3223 +
  1.3224 +  return NS_OK;
  1.3225 +}
  1.3226 +
  1.3227 +nsresult
  1.3228 +CacheIndex::OnFileOpenedInternal(FileOpenHelper *aOpener,
  1.3229 +                                 CacheFileHandle *aHandle, nsresult aResult)
  1.3230 +{
  1.3231 +  LOG(("CacheIndex::OnFileOpenedInternal() [opener=%p, handle=%p, "
  1.3232 +       "result=0x%08x]", aOpener, aHandle, aResult));
  1.3233 +
  1.3234 +  nsresult rv;
  1.3235 +
  1.3236 +  AssertOwnsLock();
  1.3237 +
  1.3238 +  if (!IsIndexUsable()) {
  1.3239 +    return NS_ERROR_NOT_AVAILABLE;
  1.3240 +  }
  1.3241 +
  1.3242 +  if (mState == READY && mShuttingDown) {
  1.3243 +    return NS_OK;
  1.3244 +  }
  1.3245 +
  1.3246 +  switch (mState) {
  1.3247 +    case WRITING:
  1.3248 +      MOZ_ASSERT(aOpener == mIndexFileOpener);
  1.3249 +      mIndexFileOpener = nullptr;
  1.3250 +
  1.3251 +      if (NS_FAILED(aResult)) {
  1.3252 +        LOG(("CacheIndex::OnFileOpenedInternal() - Can't open index file for "
  1.3253 +             "writing [rv=0x%08x]", aResult));
  1.3254 +        FinishWrite(false);
  1.3255 +      } else {
  1.3256 +        mIndexHandle = aHandle;
  1.3257 +        WriteRecords();
  1.3258 +      }
  1.3259 +      break;
  1.3260 +    case READING:
  1.3261 +      if (aOpener == mIndexFileOpener) {
  1.3262 +        mIndexFileOpener = nullptr;
  1.3263 +
  1.3264 +        if (NS_SUCCEEDED(aResult)) {
  1.3265 +          if (aHandle->FileSize() == 0) {
  1.3266 +            FinishRead(false);
  1.3267 +            CacheFileIOManager::DoomFile(aHandle, nullptr);
  1.3268 +            break;
  1.3269 +          } else {
  1.3270 +            mIndexHandle = aHandle;
  1.3271 +          }
  1.3272 +        } else {
  1.3273 +          FinishRead(false);
  1.3274 +          break;
  1.3275 +        }
  1.3276 +      } else if (aOpener == mJournalFileOpener) {
  1.3277 +        mJournalFileOpener = nullptr;
  1.3278 +        mJournalHandle = aHandle;
  1.3279 +      } else if (aOpener == mTmpFileOpener) {
  1.3280 +        mTmpFileOpener = nullptr;
  1.3281 +        mTmpHandle = aHandle;
  1.3282 +      } else {
  1.3283 +        MOZ_ASSERT(false, "Unexpected state!");
  1.3284 +      }
  1.3285 +
  1.3286 +      if (mIndexFileOpener || mJournalFileOpener || mTmpFileOpener) {
  1.3287 +        // Some opener still didn't finish
  1.3288 +        break;
  1.3289 +      }
  1.3290 +
  1.3291 +      // We fail and cancel all other openers when we opening index file fails.
  1.3292 +      MOZ_ASSERT(mIndexHandle);
  1.3293 +
  1.3294 +      if (mTmpHandle) {
  1.3295 +        CacheFileIOManager::DoomFile(mTmpHandle, nullptr);
  1.3296 +        mTmpHandle = nullptr;
  1.3297 +
  1.3298 +        if (mJournalHandle) { // this shouldn't normally happen
  1.3299 +          LOG(("CacheIndex::OnFileOpenedInternal() - Unexpected state, all "
  1.3300 +               "files [%s, %s, %s] should never exist. Removing whole index.",
  1.3301 +               kIndexName, kJournalName, kTempIndexName));
  1.3302 +          FinishRead(false);
  1.3303 +          break;
  1.3304 +        }
  1.3305 +      }
  1.3306 +
  1.3307 +      if (mJournalHandle) {
  1.3308 +        // Rename journal to make sure we update index on next start in case
  1.3309 +        // firefox crashes
  1.3310 +        rv = CacheFileIOManager::RenameFile(
  1.3311 +          mJournalHandle, NS_LITERAL_CSTRING(kTempIndexName), this);
  1.3312 +        if (NS_FAILED(rv)) {
  1.3313 +          LOG(("CacheIndex::OnFileOpenedInternal() - CacheFileIOManager::"
  1.3314 +               "RenameFile() failed synchronously [rv=0x%08x]", rv));
  1.3315 +          FinishRead(false);
  1.3316 +          break;
  1.3317 +        }
  1.3318 +      } else {
  1.3319 +        StartReadingIndex();
  1.3320 +      }
  1.3321 +
  1.3322 +      break;
  1.3323 +    default:
  1.3324 +      MOZ_ASSERT(false, "Unexpected state!");
  1.3325 +  }
  1.3326 +
  1.3327 +  return NS_OK;
  1.3328 +}
  1.3329 +
  1.3330 +nsresult
  1.3331 +CacheIndex::OnFileOpened(CacheFileHandle *aHandle, nsresult aResult)
  1.3332 +{
  1.3333 +  MOZ_CRASH("CacheIndex::OnFileOpened should not be called!");
  1.3334 +  return NS_ERROR_UNEXPECTED;
  1.3335 +}
  1.3336 +
  1.3337 +nsresult
  1.3338 +CacheIndex::OnDataWritten(CacheFileHandle *aHandle, const char *aBuf,
  1.3339 +                          nsresult aResult)
  1.3340 +{
  1.3341 +  LOG(("CacheIndex::OnDataWritten() [handle=%p, result=0x%08x]", aHandle,
  1.3342 +       aResult));
  1.3343 +
  1.3344 +  nsresult rv;
  1.3345 +
  1.3346 +  CacheIndexAutoLock lock(this);
  1.3347 +
  1.3348 +  if (!IsIndexUsable()) {
  1.3349 +    return NS_ERROR_NOT_AVAILABLE;
  1.3350 +  }
  1.3351 +
  1.3352 +  if (mState == READY && mShuttingDown) {
  1.3353 +    return NS_OK;
  1.3354 +  }
  1.3355 +
  1.3356 +  switch (mState) {
  1.3357 +    case WRITING:
  1.3358 +      if (mIndexHandle != aHandle) {
  1.3359 +        LOG(("CacheIndex::OnDataWritten() - ignoring notification since it "
  1.3360 +             "belongs to previously canceled operation [state=%d]", mState));
  1.3361 +        break;
  1.3362 +      }
  1.3363 +
  1.3364 +      if (NS_FAILED(aResult)) {
  1.3365 +        FinishWrite(false);
  1.3366 +      } else {
  1.3367 +        if (mSkipEntries == mProcessEntries) {
  1.3368 +          rv = CacheFileIOManager::RenameFile(mIndexHandle,
  1.3369 +                                              NS_LITERAL_CSTRING(kIndexName),
  1.3370 +                                              this);
  1.3371 +          if (NS_FAILED(rv)) {
  1.3372 +            LOG(("CacheIndex::OnDataWritten() - CacheFileIOManager::"
  1.3373 +                 "RenameFile() failed synchronously [rv=0x%08x]", rv));
  1.3374 +            FinishWrite(false);
  1.3375 +          }
  1.3376 +        } else {
  1.3377 +          WriteRecords();
  1.3378 +        }
  1.3379 +      }
  1.3380 +      break;
  1.3381 +    default:
  1.3382 +      // Writing was canceled.
  1.3383 +      LOG(("CacheIndex::OnDataWritten() - ignoring notification since the "
  1.3384 +           "operation was previously canceled [state=%d]", mState));
  1.3385 +  }
  1.3386 +
  1.3387 +  return NS_OK;
  1.3388 +}
  1.3389 +
  1.3390 +nsresult
  1.3391 +CacheIndex::OnDataRead(CacheFileHandle *aHandle, char *aBuf, nsresult aResult)
  1.3392 +{
  1.3393 +  LOG(("CacheIndex::OnDataRead() [handle=%p, result=0x%08x]", aHandle,
  1.3394 +       aResult));
  1.3395 +
  1.3396 +  CacheIndexAutoLock lock(this);
  1.3397 +
  1.3398 +  if (!IsIndexUsable()) {
  1.3399 +    return NS_ERROR_NOT_AVAILABLE;
  1.3400 +  }
  1.3401 +
  1.3402 +  switch (mState) {
  1.3403 +    case READING:
  1.3404 +      MOZ_ASSERT(mIndexHandle == aHandle || mJournalHandle == aHandle);
  1.3405 +
  1.3406 +      if (NS_FAILED(aResult)) {
  1.3407 +        FinishRead(false);
  1.3408 +      } else {
  1.3409 +        if (!mIndexOnDiskIsValid) {
  1.3410 +          ParseRecords();
  1.3411 +        } else {
  1.3412 +          ParseJournal();
  1.3413 +        }
  1.3414 +      }
  1.3415 +      break;
  1.3416 +    default:
  1.3417 +      // Reading was canceled.
  1.3418 +      LOG(("CacheIndex::OnDataRead() - ignoring notification since the "
  1.3419 +           "operation was previously canceled [state=%d]", mState));
  1.3420 +  }
  1.3421 +
  1.3422 +  return NS_OK;
  1.3423 +}
  1.3424 +
  1.3425 +nsresult
  1.3426 +CacheIndex::OnFileDoomed(CacheFileHandle *aHandle, nsresult aResult)
  1.3427 +{
  1.3428 +  MOZ_CRASH("CacheIndex::OnFileDoomed should not be called!");
  1.3429 +  return NS_ERROR_UNEXPECTED;
  1.3430 +}
  1.3431 +
  1.3432 +nsresult
  1.3433 +CacheIndex::OnEOFSet(CacheFileHandle *aHandle, nsresult aResult)
  1.3434 +{
  1.3435 +  MOZ_CRASH("CacheIndex::OnEOFSet should not be called!");
  1.3436 +  return NS_ERROR_UNEXPECTED;
  1.3437 +}
  1.3438 +
  1.3439 +nsresult
  1.3440 +CacheIndex::OnFileRenamed(CacheFileHandle *aHandle, nsresult aResult)
  1.3441 +{
  1.3442 +  LOG(("CacheIndex::OnFileRenamed() [handle=%p, result=0x%08x]", aHandle,
  1.3443 +       aResult));
  1.3444 +
  1.3445 +  CacheIndexAutoLock lock(this);
  1.3446 +
  1.3447 +  if (!IsIndexUsable()) {
  1.3448 +    return NS_ERROR_NOT_AVAILABLE;
  1.3449 +  }
  1.3450 +
  1.3451 +  if (mState == READY && mShuttingDown) {
  1.3452 +    return NS_OK;
  1.3453 +  }
  1.3454 +
  1.3455 +  switch (mState) {
  1.3456 +    case WRITING:
  1.3457 +      // This is a result of renaming the new index written to tmpfile to index
  1.3458 +      // file. This is the last step when writing the index and the whole
  1.3459 +      // writing process is successful iff renaming was successful.
  1.3460 +
  1.3461 +      if (mIndexHandle != aHandle) {
  1.3462 +        LOG(("CacheIndex::OnFileRenamed() - ignoring notification since it "
  1.3463 +             "belongs to previously canceled operation [state=%d]", mState));
  1.3464 +        break;
  1.3465 +      }
  1.3466 +
  1.3467 +      FinishWrite(NS_SUCCEEDED(aResult));
  1.3468 +      break;
  1.3469 +    case READING:
  1.3470 +      // This is a result of renaming journal file to tmpfile. It is renamed
  1.3471 +      // before we start reading index and journal file and it should normally
  1.3472 +      // succeed. If it fails give up reading of index.
  1.3473 +
  1.3474 +      if (mJournalHandle != aHandle) {
  1.3475 +        LOG(("CacheIndex::OnFileRenamed() - ignoring notification since it "
  1.3476 +             "belongs to previously canceled operation [state=%d]", mState));
  1.3477 +        break;
  1.3478 +      }
  1.3479 +
  1.3480 +      if (NS_FAILED(aResult)) {
  1.3481 +        FinishRead(false);
  1.3482 +      } else {
  1.3483 +        StartReadingIndex();
  1.3484 +      }
  1.3485 +      break;
  1.3486 +    default:
  1.3487 +      // Reading/writing was canceled.
  1.3488 +      LOG(("CacheIndex::OnFileRenamed() - ignoring notification since the "
  1.3489 +           "operation was previously canceled [state=%d]", mState));
  1.3490 +  }
  1.3491 +
  1.3492 +  return NS_OK;
  1.3493 +}
  1.3494 +
  1.3495 +// Memory reporting
  1.3496 +
  1.3497 +namespace { // anon
  1.3498 +
  1.3499 +size_t
  1.3500 +CollectIndexEntryMemory(CacheIndexEntry* aEntry,
  1.3501 +                        mozilla::MallocSizeOf mallocSizeOf,
  1.3502 +                        void *arg)
  1.3503 +{
  1.3504 +  return aEntry->SizeOfExcludingThis(mallocSizeOf);
  1.3505 +}
  1.3506 +
  1.3507 +} // anon
  1.3508 +
  1.3509 +size_t
  1.3510 +CacheIndex::SizeOfExcludingThisInternal(mozilla::MallocSizeOf mallocSizeOf) const
  1.3511 +{
  1.3512 +  CacheIndexAutoLock lock(const_cast<CacheIndex*>(this));
  1.3513 +
  1.3514 +  size_t n = 0;
  1.3515 +  nsCOMPtr<nsISizeOf> sizeOf;
  1.3516 +
  1.3517 +  // mIndexHandle and mJournalHandle are reported via SizeOfHandlesRunnable
  1.3518 +  // in CacheFileIOManager::SizeOfExcludingThisInternal as part of special
  1.3519 +  // handles array.
  1.3520 +
  1.3521 +  sizeOf = do_QueryInterface(mCacheDirectory);
  1.3522 +  if (sizeOf) {
  1.3523 +    n += sizeOf->SizeOfIncludingThis(mallocSizeOf);
  1.3524 +  }
  1.3525 +
  1.3526 +  sizeOf = do_QueryInterface(mUpdateTimer);
  1.3527 +  if (sizeOf) {
  1.3528 +    n += sizeOf->SizeOfIncludingThis(mallocSizeOf);
  1.3529 +  }
  1.3530 +
  1.3531 +  n += mallocSizeOf(mRWBuf);
  1.3532 +  n += mallocSizeOf(mRWHash);
  1.3533 +
  1.3534 +  n += mIndex.SizeOfExcludingThis(&CollectIndexEntryMemory, mallocSizeOf);
  1.3535 +  n += mPendingUpdates.SizeOfExcludingThis(&CollectIndexEntryMemory, mallocSizeOf);
  1.3536 +  n += mTmpJournal.SizeOfExcludingThis(&CollectIndexEntryMemory, mallocSizeOf);
  1.3537 +
  1.3538 +  // mFrecencyArray and mExpirationArray items are reported by
  1.3539 +  // mIndex/mPendingUpdates
  1.3540 +  n += mFrecencyArray.SizeOfExcludingThis(mallocSizeOf);
  1.3541 +  n += mExpirationArray.SizeOfExcludingThis(mallocSizeOf);
  1.3542 +  n += mDiskConsumptionObservers.SizeOfExcludingThis(mallocSizeOf);
  1.3543 +
  1.3544 +  return n;
  1.3545 +}
  1.3546 +
  1.3547 +// static
  1.3548 +size_t
  1.3549 +CacheIndex::SizeOfExcludingThis(mozilla::MallocSizeOf mallocSizeOf)
  1.3550 +{
  1.3551 +  if (!gInstance)
  1.3552 +    return 0;
  1.3553 +
  1.3554 +  return gInstance->SizeOfExcludingThisInternal(mallocSizeOf);
  1.3555 +}
  1.3556 +
  1.3557 +// static
  1.3558 +size_t
  1.3559 +CacheIndex::SizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf)
  1.3560 +{
  1.3561 +  return mallocSizeOf(gInstance) + SizeOfExcludingThis(mallocSizeOf);
  1.3562 +}
  1.3563 +
  1.3564 +} // net
  1.3565 +} // mozilla

mercurial