Coverage Report

Created: 2018-09-25 14:53

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