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