1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /* vim:set ts=4 sw=4 sts=4 cin et: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6
7 #include "nsCache.h"
8 #include "nsDiskCacheMap.h"
9 #include "nsDiskCacheBinding.h"
10 #include "nsDiskCacheEntry.h"
11 #include "nsDiskCacheDevice.h"
12 #include "nsCacheService.h"
13
14 #include <string.h>
15 #include "nsPrintfCString.h"
16
17 #include "nsISerializable.h"
18 #include "nsSerializationHelper.h"
19
20 #include "mozilla/MemoryReporting.h"
21 #include "mozilla/Sprintf.h"
22 #include "mozilla/Telemetry.h"
23 #include <algorithm>
24
25 using namespace mozilla;
26
27 /******************************************************************************
28 * nsDiskCacheMap
29 *****************************************************************************/
30
31 /**
32 * File operations
33 */
34
Open(nsIFile * cacheDirectory,nsDiskCache::CorruptCacheInfo * corruptInfo)35 nsresult nsDiskCacheMap::Open(nsIFile *cacheDirectory,
36 nsDiskCache::CorruptCacheInfo *corruptInfo) {
37 NS_ENSURE_ARG_POINTER(corruptInfo);
38
39 // Assume we have an unexpected error until we find otherwise.
40 *corruptInfo = nsDiskCache::kUnexpectedError;
41 NS_ENSURE_ARG_POINTER(cacheDirectory);
42 if (mMapFD) return NS_ERROR_ALREADY_INITIALIZED;
43
44 mCacheDirectory = cacheDirectory; // save a reference for ourselves
45
46 // create nsIFile for _CACHE_MAP_
47 nsresult rv;
48 nsCOMPtr<nsIFile> file;
49 rv = cacheDirectory->Clone(getter_AddRefs(file));
50 rv = file->AppendNative(NS_LITERAL_CSTRING("_CACHE_MAP_"));
51 NS_ENSURE_SUCCESS(rv, rv);
52
53 // open the file - restricted to user, the data could be confidential
54 rv = file->OpenNSPRFileDesc(PR_RDWR | PR_CREATE_FILE, 00600, &mMapFD);
55 if (NS_FAILED(rv)) {
56 *corruptInfo = nsDiskCache::kOpenCacheMapError;
57 NS_WARNING("Could not open cache map file");
58 return NS_ERROR_FILE_CORRUPTED;
59 }
60
61 bool cacheFilesExist = CacheFilesExist();
62 rv = NS_ERROR_FILE_CORRUPTED; // presume the worst
63 uint32_t mapSize = PR_Available(mMapFD);
64
65 if (NS_FAILED(InitCacheClean(cacheDirectory, corruptInfo))) {
66 // corruptInfo is set in the call to InitCacheClean
67 goto error_exit;
68 }
69
70 // check size of map file
71 if (mapSize == 0) { // creating a new _CACHE_MAP_
72
73 // block files shouldn't exist if we're creating the _CACHE_MAP_
74 if (cacheFilesExist) {
75 *corruptInfo = nsDiskCache::kBlockFilesShouldNotExist;
76 goto error_exit;
77 }
78
79 if (NS_FAILED(CreateCacheSubDirectories())) {
80 *corruptInfo = nsDiskCache::kCreateCacheSubdirectories;
81 goto error_exit;
82 }
83
84 // create the file - initialize in memory
85 memset(&mHeader, 0, sizeof(nsDiskCacheHeader));
86 mHeader.mVersion = nsDiskCache::kCurrentVersion;
87 mHeader.mRecordCount = kMinRecordCount;
88 mRecordArray = (nsDiskCacheRecord *)calloc(mHeader.mRecordCount,
89 sizeof(nsDiskCacheRecord));
90 if (!mRecordArray) {
91 *corruptInfo = nsDiskCache::kOutOfMemory;
92 rv = NS_ERROR_OUT_OF_MEMORY;
93 goto error_exit;
94 }
95 } else if (mapSize >=
96 sizeof(nsDiskCacheHeader)) { // read existing _CACHE_MAP_
97
98 // if _CACHE_MAP_ exists, so should the block files
99 if (!cacheFilesExist) {
100 *corruptInfo = nsDiskCache::kBlockFilesShouldExist;
101 goto error_exit;
102 }
103
104 CACHE_LOG_DEBUG(
105 ("CACHE: nsDiskCacheMap::Open [this=%p] reading map", this));
106
107 // read the header
108 uint32_t bytesRead = PR_Read(mMapFD, &mHeader, sizeof(nsDiskCacheHeader));
109 if (sizeof(nsDiskCacheHeader) != bytesRead) {
110 *corruptInfo = nsDiskCache::kHeaderSizeNotRead;
111 goto error_exit;
112 }
113 mHeader.Unswap();
114
115 if (mHeader.mIsDirty) {
116 *corruptInfo = nsDiskCache::kHeaderIsDirty;
117 goto error_exit;
118 }
119
120 if (mHeader.mVersion != nsDiskCache::kCurrentVersion) {
121 *corruptInfo = nsDiskCache::kVersionMismatch;
122 goto error_exit;
123 }
124
125 uint32_t recordArraySize = mHeader.mRecordCount * sizeof(nsDiskCacheRecord);
126 if (mapSize < recordArraySize + sizeof(nsDiskCacheHeader)) {
127 *corruptInfo = nsDiskCache::kRecordsIncomplete;
128 goto error_exit;
129 }
130
131 // Get the space for the records
132 mRecordArray = (nsDiskCacheRecord *)malloc(recordArraySize);
133 if (!mRecordArray) {
134 *corruptInfo = nsDiskCache::kOutOfMemory;
135 rv = NS_ERROR_OUT_OF_MEMORY;
136 goto error_exit;
137 }
138
139 // Read the records
140 bytesRead = PR_Read(mMapFD, mRecordArray, recordArraySize);
141 if (bytesRead < recordArraySize) {
142 *corruptInfo = nsDiskCache::kNotEnoughToRead;
143 goto error_exit;
144 }
145
146 // Unswap each record
147 int32_t total = 0;
148 for (int32_t i = 0; i < mHeader.mRecordCount; ++i) {
149 if (mRecordArray[i].HashNumber()) {
150 #if defined(IS_LITTLE_ENDIAN)
151 mRecordArray[i].Unswap();
152 #endif
153 total++;
154 }
155 }
156
157 // verify entry count
158 if (total != mHeader.mEntryCount) {
159 *corruptInfo = nsDiskCache::kEntryCountIncorrect;
160 goto error_exit;
161 }
162
163 } else {
164 *corruptInfo = nsDiskCache::kHeaderIncomplete;
165 goto error_exit;
166 }
167
168 rv = OpenBlockFiles(corruptInfo);
169 if (NS_FAILED(rv)) {
170 // corruptInfo is set in the call to OpenBlockFiles
171 goto error_exit;
172 }
173
174 // set dirty bit and flush header
175 mHeader.mIsDirty = true;
176 rv = FlushHeader();
177 if (NS_FAILED(rv)) {
178 *corruptInfo = nsDiskCache::kFlushHeaderError;
179 goto error_exit;
180 }
181
182 Telemetry::Accumulate(Telemetry::HTTP_DISK_CACHE_OVERHEAD,
183 (uint32_t)SizeOfExcludingThis(moz_malloc_size_of));
184
185 *corruptInfo = nsDiskCache::kNotCorrupt;
186 return NS_OK;
187
188 error_exit:
189 (void)Close(false);
190
191 return rv;
192 }
193
Close(bool flush)194 nsresult nsDiskCacheMap::Close(bool flush) {
195 nsCacheService::AssertOwnsLock();
196 nsresult rv = NS_OK;
197
198 // Cancel any pending cache validation event, the FlushRecords call below
199 // will validate the cache.
200 if (mCleanCacheTimer) {
201 mCleanCacheTimer->Cancel();
202 }
203
204 // If cache map file and its block files are still open, close them
205 if (mMapFD) {
206 // close block files
207 rv = CloseBlockFiles(flush);
208 if (NS_SUCCEEDED(rv) && flush && mRecordArray) {
209 // write the map records
210 rv = FlushRecords(false); // don't bother swapping buckets back
211 if (NS_SUCCEEDED(rv)) {
212 // clear dirty bit
213 mHeader.mIsDirty = false;
214 rv = FlushHeader();
215 }
216 }
217 if ((PR_Close(mMapFD) != PR_SUCCESS) && (NS_SUCCEEDED(rv)))
218 rv = NS_ERROR_UNEXPECTED;
219
220 mMapFD = nullptr;
221 }
222
223 if (mCleanFD) {
224 PR_Close(mCleanFD);
225 mCleanFD = nullptr;
226 }
227
228 free(mRecordArray);
229 mRecordArray = nullptr;
230 free(mBuffer);
231 mBuffer = nullptr;
232 mBufferSize = 0;
233 return rv;
234 }
235
Trim()236 nsresult nsDiskCacheMap::Trim() {
237 nsresult rv, rv2 = NS_OK;
238 for (int i = 0; i < kNumBlockFiles; ++i) {
239 rv = mBlockFile[i].Trim();
240 if (NS_FAILED(rv)) rv2 = rv; // if one or more errors, report at least one
241 }
242 // Try to shrink the records array
243 rv = ShrinkRecords();
244 if (NS_FAILED(rv)) rv2 = rv; // if one or more errors, report at least one
245 return rv2;
246 }
247
FlushHeader()248 nsresult nsDiskCacheMap::FlushHeader() {
249 if (!mMapFD) return NS_ERROR_NOT_AVAILABLE;
250
251 // seek to beginning of cache map
252 int32_t filePos = PR_Seek(mMapFD, 0, PR_SEEK_SET);
253 if (filePos != 0) return NS_ERROR_UNEXPECTED;
254
255 // write the header
256 mHeader.Swap();
257 int32_t bytesWritten = PR_Write(mMapFD, &mHeader, sizeof(nsDiskCacheHeader));
258 mHeader.Unswap();
259 if (sizeof(nsDiskCacheHeader) != bytesWritten) {
260 return NS_ERROR_UNEXPECTED;
261 }
262
263 PRStatus err = PR_Sync(mMapFD);
264 if (err != PR_SUCCESS) return NS_ERROR_UNEXPECTED;
265
266 // If we have a clean header then revalidate the cache clean file
267 if (!mHeader.mIsDirty) {
268 RevalidateCache();
269 }
270
271 return NS_OK;
272 }
273
FlushRecords(bool unswap)274 nsresult nsDiskCacheMap::FlushRecords(bool unswap) {
275 if (!mMapFD) return NS_ERROR_NOT_AVAILABLE;
276
277 // seek to beginning of buckets
278 int32_t filePos = PR_Seek(mMapFD, sizeof(nsDiskCacheHeader), PR_SEEK_SET);
279 if (filePos != sizeof(nsDiskCacheHeader)) return NS_ERROR_UNEXPECTED;
280
281 #if defined(IS_LITTLE_ENDIAN)
282 // Swap each record
283 for (int32_t i = 0; i < mHeader.mRecordCount; ++i) {
284 if (mRecordArray[i].HashNumber()) mRecordArray[i].Swap();
285 }
286 #endif
287
288 int32_t recordArraySize = sizeof(nsDiskCacheRecord) * mHeader.mRecordCount;
289
290 int32_t bytesWritten = PR_Write(mMapFD, mRecordArray, recordArraySize);
291 if (bytesWritten != recordArraySize) return NS_ERROR_UNEXPECTED;
292
293 #if defined(IS_LITTLE_ENDIAN)
294 if (unswap) {
295 // Unswap each record
296 for (int32_t i = 0; i < mHeader.mRecordCount; ++i) {
297 if (mRecordArray[i].HashNumber()) mRecordArray[i].Unswap();
298 }
299 }
300 #endif
301
302 return NS_OK;
303 }
304
305 /**
306 * Record operations
307 */
308
GetBucketRank(uint32_t bucketIndex,uint32_t targetRank)309 uint32_t nsDiskCacheMap::GetBucketRank(uint32_t bucketIndex,
310 uint32_t targetRank) {
311 nsDiskCacheRecord *records = GetFirstRecordInBucket(bucketIndex);
312 uint32_t rank = 0;
313
314 for (int i = mHeader.mBucketUsage[bucketIndex] - 1; i >= 0; i--) {
315 if ((rank < records[i].EvictionRank()) &&
316 ((targetRank == 0) || (records[i].EvictionRank() < targetRank)))
317 rank = records[i].EvictionRank();
318 }
319 return rank;
320 }
321
GrowRecords()322 nsresult nsDiskCacheMap::GrowRecords() {
323 if (mHeader.mRecordCount >= mMaxRecordCount) return NS_OK;
324 CACHE_LOG_DEBUG(("CACHE: GrowRecords\n"));
325
326 // Resize the record array
327 int32_t newCount = mHeader.mRecordCount << 1;
328 if (newCount > mMaxRecordCount) newCount = mMaxRecordCount;
329 nsDiskCacheRecord *newArray = (nsDiskCacheRecord *)realloc(
330 mRecordArray, newCount * sizeof(nsDiskCacheRecord));
331 if (!newArray) return NS_ERROR_OUT_OF_MEMORY;
332
333 // Space out the buckets
334 uint32_t oldRecordsPerBucket = GetRecordsPerBucket();
335 uint32_t newRecordsPerBucket = newCount / kBuckets;
336 // Work from back to space out each bucket to the new array
337 for (int bucketIndex = kBuckets - 1; bucketIndex >= 0; --bucketIndex) {
338 // Move bucket
339 nsDiskCacheRecord *newRecords =
340 newArray + bucketIndex * newRecordsPerBucket;
341 const uint32_t count = mHeader.mBucketUsage[bucketIndex];
342 memmove(newRecords, newArray + bucketIndex * oldRecordsPerBucket,
343 count * sizeof(nsDiskCacheRecord));
344 // clear unused records
345 memset(newRecords + count, 0,
346 (newRecordsPerBucket - count) * sizeof(nsDiskCacheRecord));
347 }
348
349 // Set as the new record array
350 mRecordArray = newArray;
351 mHeader.mRecordCount = newCount;
352
353 InvalidateCache();
354
355 return NS_OK;
356 }
357
ShrinkRecords()358 nsresult nsDiskCacheMap::ShrinkRecords() {
359 if (mHeader.mRecordCount <= kMinRecordCount) return NS_OK;
360 CACHE_LOG_DEBUG(("CACHE: ShrinkRecords\n"));
361
362 // Verify if we can shrink the record array: all buckets must be less than
363 // 1/2 filled
364 uint32_t maxUsage = 0, bucketIndex;
365 for (bucketIndex = 0; bucketIndex < kBuckets; ++bucketIndex) {
366 if (maxUsage < mHeader.mBucketUsage[bucketIndex])
367 maxUsage = mHeader.mBucketUsage[bucketIndex];
368 }
369 // Determine new bucket size, halve size until maxUsage
370 uint32_t oldRecordsPerBucket = GetRecordsPerBucket();
371 uint32_t newRecordsPerBucket = oldRecordsPerBucket;
372 while (maxUsage < (newRecordsPerBucket >> 1)) newRecordsPerBucket >>= 1;
373 if (newRecordsPerBucket < (kMinRecordCount / kBuckets))
374 newRecordsPerBucket = (kMinRecordCount / kBuckets);
375 NS_ASSERTION(newRecordsPerBucket <= oldRecordsPerBucket,
376 "ShrinkRecords() can't grow records!");
377 if (newRecordsPerBucket == oldRecordsPerBucket) return NS_OK;
378 // Move the buckets close to each other
379 for (bucketIndex = 1; bucketIndex < kBuckets; ++bucketIndex) {
380 // Move bucket
381 memmove(mRecordArray + bucketIndex * newRecordsPerBucket,
382 mRecordArray + bucketIndex * oldRecordsPerBucket,
383 newRecordsPerBucket * sizeof(nsDiskCacheRecord));
384 }
385
386 // Shrink the record array memory block itself
387 uint32_t newCount = newRecordsPerBucket * kBuckets;
388 nsDiskCacheRecord *newArray = (nsDiskCacheRecord *)realloc(
389 mRecordArray, newCount * sizeof(nsDiskCacheRecord));
390 if (!newArray) return NS_ERROR_OUT_OF_MEMORY;
391
392 // Set as the new record array
393 mRecordArray = newArray;
394 mHeader.mRecordCount = newCount;
395
396 InvalidateCache();
397
398 return NS_OK;
399 }
400
AddRecord(nsDiskCacheRecord * mapRecord,nsDiskCacheRecord * oldRecord)401 nsresult nsDiskCacheMap::AddRecord(nsDiskCacheRecord *mapRecord,
402 nsDiskCacheRecord *oldRecord) {
403 CACHE_LOG_DEBUG(("CACHE: AddRecord [%x]\n", mapRecord->HashNumber()));
404
405 const uint32_t hashNumber = mapRecord->HashNumber();
406 const uint32_t bucketIndex = GetBucketIndex(hashNumber);
407 const uint32_t count = mHeader.mBucketUsage[bucketIndex];
408
409 oldRecord->SetHashNumber(0); // signify no record
410
411 if (count == GetRecordsPerBucket()) {
412 // Ignore failure to grow the record space, we will then reuse old records
413 GrowRecords();
414 }
415
416 nsDiskCacheRecord *records = GetFirstRecordInBucket(bucketIndex);
417 if (count < GetRecordsPerBucket()) {
418 // stick the new record at the end
419 records[count] = *mapRecord;
420 mHeader.mEntryCount++;
421 mHeader.mBucketUsage[bucketIndex]++;
422 if (mHeader.mEvictionRank[bucketIndex] < mapRecord->EvictionRank())
423 mHeader.mEvictionRank[bucketIndex] = mapRecord->EvictionRank();
424 InvalidateCache();
425 } else {
426 // Find the record with the highest eviction rank
427 nsDiskCacheRecord *mostEvictable = &records[0];
428 for (int i = count - 1; i > 0; i--) {
429 if (records[i].EvictionRank() > mostEvictable->EvictionRank())
430 mostEvictable = &records[i];
431 }
432 *oldRecord = *mostEvictable; // i == GetRecordsPerBucket(), so
433 // evict the mostEvictable
434 *mostEvictable = *mapRecord; // replace it with the new record
435 // check if we need to update mostEvictable entry in header
436 if (mHeader.mEvictionRank[bucketIndex] < mapRecord->EvictionRank())
437 mHeader.mEvictionRank[bucketIndex] = mapRecord->EvictionRank();
438 if (oldRecord->EvictionRank() >= mHeader.mEvictionRank[bucketIndex])
439 mHeader.mEvictionRank[bucketIndex] = GetBucketRank(bucketIndex, 0);
440 InvalidateCache();
441 }
442
443 NS_ASSERTION(
444 mHeader.mEvictionRank[bucketIndex] == GetBucketRank(bucketIndex, 0),
445 "eviction rank out of sync");
446 return NS_OK;
447 }
448
UpdateRecord(nsDiskCacheRecord * mapRecord)449 nsresult nsDiskCacheMap::UpdateRecord(nsDiskCacheRecord *mapRecord) {
450 CACHE_LOG_DEBUG(("CACHE: UpdateRecord [%x]\n", mapRecord->HashNumber()));
451
452 const uint32_t hashNumber = mapRecord->HashNumber();
453 const uint32_t bucketIndex = GetBucketIndex(hashNumber);
454 nsDiskCacheRecord *records = GetFirstRecordInBucket(bucketIndex);
455
456 for (int i = mHeader.mBucketUsage[bucketIndex] - 1; i >= 0; i--) {
457 if (records[i].HashNumber() == hashNumber) {
458 const uint32_t oldRank = records[i].EvictionRank();
459
460 // stick the new record here
461 records[i] = *mapRecord;
462
463 // update eviction rank in header if necessary
464 if (mHeader.mEvictionRank[bucketIndex] < mapRecord->EvictionRank())
465 mHeader.mEvictionRank[bucketIndex] = mapRecord->EvictionRank();
466 else if (mHeader.mEvictionRank[bucketIndex] == oldRank)
467 mHeader.mEvictionRank[bucketIndex] = GetBucketRank(bucketIndex, 0);
468
469 InvalidateCache();
470
471 NS_ASSERTION(
472 mHeader.mEvictionRank[bucketIndex] == GetBucketRank(bucketIndex, 0),
473 "eviction rank out of sync");
474 return NS_OK;
475 }
476 }
477 NS_NOTREACHED("record not found");
478 return NS_ERROR_UNEXPECTED;
479 }
480
FindRecord(uint32_t hashNumber,nsDiskCacheRecord * result)481 nsresult nsDiskCacheMap::FindRecord(uint32_t hashNumber,
482 nsDiskCacheRecord *result) {
483 const uint32_t bucketIndex = GetBucketIndex(hashNumber);
484 nsDiskCacheRecord *records = GetFirstRecordInBucket(bucketIndex);
485
486 for (int i = mHeader.mBucketUsage[bucketIndex] - 1; i >= 0; i--) {
487 if (records[i].HashNumber() == hashNumber) {
488 *result = records[i]; // copy the record
489 NS_ASSERTION(result->ValidRecord(), "bad cache map record");
490 return NS_OK;
491 }
492 }
493 return NS_ERROR_CACHE_KEY_NOT_FOUND;
494 }
495
DeleteRecord(nsDiskCacheRecord * mapRecord)496 nsresult nsDiskCacheMap::DeleteRecord(nsDiskCacheRecord *mapRecord) {
497 CACHE_LOG_DEBUG(("CACHE: DeleteRecord [%x]\n", mapRecord->HashNumber()));
498
499 const uint32_t hashNumber = mapRecord->HashNumber();
500 const uint32_t bucketIndex = GetBucketIndex(hashNumber);
501 nsDiskCacheRecord *records = GetFirstRecordInBucket(bucketIndex);
502 uint32_t last = mHeader.mBucketUsage[bucketIndex] - 1;
503
504 for (int i = last; i >= 0; i--) {
505 if (records[i].HashNumber() == hashNumber) {
506 // found it, now delete it.
507 uint32_t evictionRank = records[i].EvictionRank();
508 NS_ASSERTION(evictionRank == mapRecord->EvictionRank(),
509 "evictionRank out of sync");
510 // if not the last record, shift last record into opening
511 records[i] = records[last];
512 records[last].SetHashNumber(0); // clear last record
513 mHeader.mBucketUsage[bucketIndex] = last;
514 mHeader.mEntryCount--;
515
516 // update eviction rank
517 uint32_t bucketIndex = GetBucketIndex(mapRecord->HashNumber());
518 if (mHeader.mEvictionRank[bucketIndex] <= evictionRank) {
519 mHeader.mEvictionRank[bucketIndex] = GetBucketRank(bucketIndex, 0);
520 }
521
522 InvalidateCache();
523
524 NS_ASSERTION(
525 mHeader.mEvictionRank[bucketIndex] == GetBucketRank(bucketIndex, 0),
526 "eviction rank out of sync");
527 return NS_OK;
528 }
529 }
530 return NS_ERROR_UNEXPECTED;
531 }
532
VisitEachRecord(uint32_t bucketIndex,nsDiskCacheRecordVisitor * visitor,uint32_t evictionRank)533 int32_t nsDiskCacheMap::VisitEachRecord(uint32_t bucketIndex,
534 nsDiskCacheRecordVisitor *visitor,
535 uint32_t evictionRank) {
536 int32_t rv = kVisitNextRecord;
537 uint32_t count = mHeader.mBucketUsage[bucketIndex];
538 nsDiskCacheRecord *records = GetFirstRecordInBucket(bucketIndex);
539
540 // call visitor for each entry (matching any eviction rank)
541 for (int i = count - 1; i >= 0; i--) {
542 if (evictionRank > records[i].EvictionRank()) continue;
543
544 rv = visitor->VisitRecord(&records[i]);
545 if (rv == kStopVisitingRecords) break; // Stop visiting records
546
547 if (rv == kDeleteRecordAndContinue) {
548 --count;
549 records[i] = records[count];
550 records[count].SetHashNumber(0);
551 InvalidateCache();
552 }
553 }
554
555 if (mHeader.mBucketUsage[bucketIndex] - count != 0) {
556 mHeader.mEntryCount -= mHeader.mBucketUsage[bucketIndex] - count;
557 mHeader.mBucketUsage[bucketIndex] = count;
558 // recalc eviction rank
559 mHeader.mEvictionRank[bucketIndex] = GetBucketRank(bucketIndex, 0);
560 }
561 NS_ASSERTION(
562 mHeader.mEvictionRank[bucketIndex] == GetBucketRank(bucketIndex, 0),
563 "eviction rank out of sync");
564
565 return rv;
566 }
567
568 /**
569 * VisitRecords
570 *
571 * Visit every record in cache map in the most convenient order
572 */
VisitRecords(nsDiskCacheRecordVisitor * visitor)573 nsresult nsDiskCacheMap::VisitRecords(nsDiskCacheRecordVisitor *visitor) {
574 for (int bucketIndex = 0; bucketIndex < kBuckets; ++bucketIndex) {
575 if (VisitEachRecord(bucketIndex, visitor, 0) == kStopVisitingRecords) break;
576 }
577 return NS_OK;
578 }
579
580 /**
581 * EvictRecords
582 *
583 * Just like VisitRecords, but visits the records in order of their eviction
584 * rank
585 */
EvictRecords(nsDiskCacheRecordVisitor * visitor)586 nsresult nsDiskCacheMap::EvictRecords(nsDiskCacheRecordVisitor *visitor) {
587 uint32_t tempRank[kBuckets];
588 int bucketIndex = 0;
589
590 // copy eviction rank array
591 for (bucketIndex = 0; bucketIndex < kBuckets; ++bucketIndex)
592 tempRank[bucketIndex] = mHeader.mEvictionRank[bucketIndex];
593
594 // Maximum number of iterations determined by number of records
595 // as a safety limiter for the loop. Use a copy of mHeader.mEntryCount since
596 // the value could decrease if some entry is evicted.
597 int32_t entryCount = mHeader.mEntryCount;
598 for (int n = 0; n < entryCount; ++n) {
599 // find bucket with highest eviction rank
600 uint32_t rank = 0;
601 for (int i = 0; i < kBuckets; ++i) {
602 if (rank < tempRank[i]) {
603 rank = tempRank[i];
604 bucketIndex = i;
605 }
606 }
607
608 if (rank == 0) break; // we've examined all the records
609
610 // visit records in bucket with eviction ranks >= target eviction rank
611 if (VisitEachRecord(bucketIndex, visitor, rank) == kStopVisitingRecords)
612 break;
613
614 // find greatest rank less than 'rank'
615 tempRank[bucketIndex] = GetBucketRank(bucketIndex, rank);
616 }
617 return NS_OK;
618 }
619
OpenBlockFiles(nsDiskCache::CorruptCacheInfo * corruptInfo)620 nsresult nsDiskCacheMap::OpenBlockFiles(
621 nsDiskCache::CorruptCacheInfo *corruptInfo) {
622 NS_ENSURE_ARG_POINTER(corruptInfo);
623
624 // create nsIFile for block file
625 nsCOMPtr<nsIFile> blockFile;
626 nsresult rv = NS_OK;
627 *corruptInfo = nsDiskCache::kUnexpectedError;
628
629 for (int i = 0; i < kNumBlockFiles; ++i) {
630 rv = GetBlockFileForIndex(i, getter_AddRefs(blockFile));
631 if (NS_FAILED(rv)) {
632 *corruptInfo = nsDiskCache::kCouldNotGetBlockFileForIndex;
633 break;
634 }
635
636 uint32_t blockSize =
637 GetBlockSizeForIndex(i + 1); // +1 to match file selectors 1,2,3
638 uint32_t bitMapSize = GetBitMapSizeForIndex(i + 1);
639 rv = mBlockFile[i].Open(blockFile, blockSize, bitMapSize, corruptInfo);
640 if (NS_FAILED(rv)) {
641 // corruptInfo was set inside the call to mBlockFile[i].Open
642 break;
643 }
644 }
645 // close all files in case of any error
646 if (NS_FAILED(rv))
647 (void)CloseBlockFiles(false); // we already have an error to report
648
649 return rv;
650 }
651
CloseBlockFiles(bool flush)652 nsresult nsDiskCacheMap::CloseBlockFiles(bool flush) {
653 nsresult rv, rv2 = NS_OK;
654 for (int i = 0; i < kNumBlockFiles; ++i) {
655 rv = mBlockFile[i].Close(flush);
656 if (NS_FAILED(rv)) rv2 = rv; // if one or more errors, report at least one
657 }
658 return rv2;
659 }
660
CacheFilesExist()661 bool nsDiskCacheMap::CacheFilesExist() {
662 nsCOMPtr<nsIFile> blockFile;
663 nsresult rv;
664
665 for (int i = 0; i < kNumBlockFiles; ++i) {
666 bool exists;
667 rv = GetBlockFileForIndex(i, getter_AddRefs(blockFile));
668 if (NS_FAILED(rv)) return false;
669
670 rv = blockFile->Exists(&exists);
671 if (NS_FAILED(rv) || !exists) return false;
672 }
673
674 return true;
675 }
676
CreateCacheSubDirectories()677 nsresult nsDiskCacheMap::CreateCacheSubDirectories() {
678 if (!mCacheDirectory) return NS_ERROR_UNEXPECTED;
679
680 for (int32_t index = 0; index < 16; index++) {
681 nsCOMPtr<nsIFile> file;
682 nsresult rv = mCacheDirectory->Clone(getter_AddRefs(file));
683 if (NS_FAILED(rv)) return rv;
684
685 rv = file->AppendNative(nsPrintfCString("%X", index));
686 if (NS_FAILED(rv)) return rv;
687
688 rv = file->Create(nsIFile::DIRECTORY_TYPE, 0700);
689 if (NS_FAILED(rv)) return rv;
690 }
691
692 return NS_OK;
693 }
694
ReadDiskCacheEntry(nsDiskCacheRecord * record)695 nsDiskCacheEntry *nsDiskCacheMap::ReadDiskCacheEntry(
696 nsDiskCacheRecord *record) {
697 CACHE_LOG_DEBUG(("CACHE: ReadDiskCacheEntry [%x]\n", record->HashNumber()));
698
699 nsresult rv = NS_ERROR_UNEXPECTED;
700 nsDiskCacheEntry *diskEntry = nullptr;
701 uint32_t metaFile = record->MetaFile();
702 int32_t bytesRead = 0;
703
704 if (!record->MetaLocationInitialized()) return nullptr;
705
706 if (metaFile == 0) { // entry/metadata stored in separate file
707 // open and read the file
708 nsCOMPtr<nsIFile> file;
709 rv = GetLocalFileForDiskCacheRecord(record, nsDiskCache::kMetaData, false,
710 getter_AddRefs(file));
711 NS_ENSURE_SUCCESS(rv, nullptr);
712
713 CACHE_LOG_DEBUG(
714 ("CACHE: nsDiskCacheMap::ReadDiskCacheEntry"
715 "[this=%p] reading disk cache entry",
716 this));
717
718 PRFileDesc *fd = nullptr;
719
720 // open the file - restricted to user, the data could be confidential
721 rv = file->OpenNSPRFileDesc(PR_RDONLY, 00600, &fd);
722 NS_ENSURE_SUCCESS(rv, nullptr);
723
724 int32_t fileSize = PR_Available(fd);
725 if (fileSize < 0) {
726 // an error occurred. We could call PR_GetError(), but how would that
727 // help?
728 rv = NS_ERROR_UNEXPECTED;
729 } else {
730 rv = EnsureBuffer(fileSize);
731 if (NS_SUCCEEDED(rv)) {
732 bytesRead = PR_Read(fd, mBuffer, fileSize);
733 if (bytesRead < fileSize) {
734 rv = NS_ERROR_UNEXPECTED;
735 }
736 }
737 }
738 PR_Close(fd);
739 NS_ENSURE_SUCCESS(rv, nullptr);
740
741 } else if (metaFile < (kNumBlockFiles + 1)) {
742 // entry/metadata stored in cache block file
743
744 // allocate buffer
745 uint32_t blockCount = record->MetaBlockCount();
746 bytesRead = blockCount * GetBlockSizeForIndex(metaFile);
747
748 rv = EnsureBuffer(bytesRead);
749 NS_ENSURE_SUCCESS(rv, nullptr);
750
751 // read diskEntry, note when the blocks are at the end of file,
752 // bytesRead may be less than blockSize*blockCount.
753 // But the bytesRead should at least agree with the real disk entry size.
754 rv = mBlockFile[metaFile - 1].ReadBlocks(mBuffer, record->MetaStartBlock(),
755 blockCount, &bytesRead);
756 NS_ENSURE_SUCCESS(rv, nullptr);
757 }
758 diskEntry = (nsDiskCacheEntry *)mBuffer;
759 diskEntry->Unswap(); // disk to memory
760 // Check if calculated size agrees with bytesRead
761 if (bytesRead < 0 || (uint32_t)bytesRead < diskEntry->Size()) return nullptr;
762
763 // Return the buffer containing the diskEntry structure
764 return diskEntry;
765 }
766
767 /**
768 * CreateDiskCacheEntry(nsCacheEntry * entry)
769 *
770 * Prepare an nsCacheEntry for writing to disk
771 */
CreateDiskCacheEntry(nsDiskCacheBinding * binding,uint32_t * aSize)772 nsDiskCacheEntry *nsDiskCacheMap::CreateDiskCacheEntry(
773 nsDiskCacheBinding *binding, uint32_t *aSize) {
774 nsCacheEntry *entry = binding->mCacheEntry;
775 if (!entry) return nullptr;
776
777 // Store security info, if it is serializable
778 nsCOMPtr<nsISupports> infoObj = entry->SecurityInfo();
779 nsCOMPtr<nsISerializable> serializable = do_QueryInterface(infoObj);
780 if (infoObj && !serializable) return nullptr;
781 if (serializable) {
782 nsCString info;
783 nsresult rv = NS_SerializeToString(serializable, info);
784 if (NS_FAILED(rv)) return nullptr;
785 rv = entry->SetMetaDataElement("security-info", info.get());
786 if (NS_FAILED(rv)) return nullptr;
787 }
788
789 uint32_t keySize = entry->Key()->Length() + 1;
790 uint32_t metaSize = entry->MetaDataSize();
791 uint32_t size = sizeof(nsDiskCacheEntry) + keySize + metaSize;
792
793 if (aSize) *aSize = size;
794
795 nsresult rv = EnsureBuffer(size);
796 if (NS_FAILED(rv)) return nullptr;
797
798 nsDiskCacheEntry *diskEntry = (nsDiskCacheEntry *)mBuffer;
799 diskEntry->mHeaderVersion = nsDiskCache::kCurrentVersion;
800 diskEntry->mMetaLocation = binding->mRecord.MetaLocation();
801 diskEntry->mFetchCount = entry->FetchCount();
802 diskEntry->mLastFetched = entry->LastFetched();
803 diskEntry->mLastModified = entry->LastModified();
804 diskEntry->mExpirationTime = entry->ExpirationTime();
805 diskEntry->mDataSize = entry->DataSize();
806 diskEntry->mKeySize = keySize;
807 diskEntry->mMetaDataSize = metaSize;
808
809 memcpy(diskEntry->Key(), entry->Key()->get(), keySize);
810
811 rv = entry->FlattenMetaData(diskEntry->MetaData(), metaSize);
812 if (NS_FAILED(rv)) return nullptr;
813
814 return diskEntry;
815 }
816
WriteDiskCacheEntry(nsDiskCacheBinding * binding)817 nsresult nsDiskCacheMap::WriteDiskCacheEntry(nsDiskCacheBinding *binding) {
818 CACHE_LOG_DEBUG(
819 ("CACHE: WriteDiskCacheEntry [%x]\n", binding->mRecord.HashNumber()));
820
821 nsresult rv = NS_OK;
822 uint32_t size;
823 nsDiskCacheEntry *diskEntry = CreateDiskCacheEntry(binding, &size);
824 if (!diskEntry) return NS_ERROR_UNEXPECTED;
825
826 uint32_t fileIndex = CalculateFileIndex(size);
827
828 // Deallocate old storage if necessary
829 if (binding->mRecord.MetaLocationInitialized()) {
830 // we have existing storage
831
832 if ((binding->mRecord.MetaFile() == 0) &&
833 (fileIndex == 0)) { // keeping the separate file
834 // just decrement total
835 DecrementTotalSize(binding->mRecord.MetaFileSize());
836 NS_ASSERTION(
837 binding->mRecord.MetaFileGeneration() == binding->mGeneration,
838 "generations out of sync");
839 } else {
840 rv = DeleteStorage(&binding->mRecord, nsDiskCache::kMetaData);
841 NS_ENSURE_SUCCESS(rv, rv);
842 }
843 }
844
845 binding->mRecord.SetEvictionRank(ULONG_MAX - SecondsFromPRTime(PR_Now()));
846 // write entry data to disk cache block file
847 diskEntry->Swap();
848
849 if (fileIndex != 0) {
850 while (1) {
851 uint32_t blockSize = GetBlockSizeForIndex(fileIndex);
852 uint32_t blocks = ((size - 1) / blockSize) + 1;
853
854 int32_t startBlock;
855 rv = mBlockFile[fileIndex - 1].WriteBlocks(diskEntry, size, blocks,
856 &startBlock);
857 if (NS_SUCCEEDED(rv)) {
858 // update binding and cache map record
859 binding->mRecord.SetMetaBlocks(fileIndex, startBlock, blocks);
860
861 rv = UpdateRecord(&binding->mRecord);
862 NS_ENSURE_SUCCESS(rv, rv);
863
864 // XXX we should probably write out bucket ourselves
865
866 IncrementTotalSize(blocks, blockSize);
867 break;
868 }
869
870 if (fileIndex == kNumBlockFiles) {
871 fileIndex = 0; // write data to separate file
872 break;
873 }
874
875 // try next block file
876 fileIndex++;
877 }
878 }
879
880 if (fileIndex == 0) {
881 // Write entry data to separate file
882 uint32_t metaFileSizeK = ((size + 0x03FF) >> 10); // round up to nearest 1k
883 if (metaFileSizeK > kMaxDataSizeK) metaFileSizeK = kMaxDataSizeK;
884
885 binding->mRecord.SetMetaFileGeneration(binding->mGeneration);
886 binding->mRecord.SetMetaFileSize(metaFileSizeK);
887 rv = UpdateRecord(&binding->mRecord);
888 NS_ENSURE_SUCCESS(rv, rv);
889
890 nsCOMPtr<nsIFile> localFile;
891 rv = GetLocalFileForDiskCacheRecord(&binding->mRecord,
892 nsDiskCache::kMetaData, true,
893 getter_AddRefs(localFile));
894 NS_ENSURE_SUCCESS(rv, rv);
895
896 // open the file
897 PRFileDesc *fd;
898 // open the file - restricted to user, the data could be confidential
899 rv = localFile->OpenNSPRFileDesc(PR_RDWR | PR_TRUNCATE | PR_CREATE_FILE,
900 00600, &fd);
901 NS_ENSURE_SUCCESS(rv, rv);
902
903 // write the file
904 int32_t bytesWritten = PR_Write(fd, diskEntry, size);
905
906 PRStatus err = PR_Close(fd);
907 if ((bytesWritten != (int32_t)size) || (err != PR_SUCCESS)) {
908 return NS_ERROR_UNEXPECTED;
909 }
910
911 IncrementTotalSize(metaFileSizeK);
912 }
913
914 return rv;
915 }
916
ReadDataCacheBlocks(nsDiskCacheBinding * binding,char * buffer,uint32_t size)917 nsresult nsDiskCacheMap::ReadDataCacheBlocks(nsDiskCacheBinding *binding,
918 char *buffer, uint32_t size) {
919 CACHE_LOG_DEBUG(("CACHE: ReadDataCacheBlocks [%x size=%u]\n",
920 binding->mRecord.HashNumber(), size));
921
922 uint32_t fileIndex = binding->mRecord.DataFile();
923 int32_t readSize = size;
924
925 nsresult rv = mBlockFile[fileIndex - 1].ReadBlocks(
926 buffer, binding->mRecord.DataStartBlock(),
927 binding->mRecord.DataBlockCount(), &readSize);
928 NS_ENSURE_SUCCESS(rv, rv);
929 if (readSize < (int32_t)size) {
930 rv = NS_ERROR_UNEXPECTED;
931 }
932 return rv;
933 }
934
WriteDataCacheBlocks(nsDiskCacheBinding * binding,char * buffer,uint32_t size)935 nsresult nsDiskCacheMap::WriteDataCacheBlocks(nsDiskCacheBinding *binding,
936 char *buffer, uint32_t size) {
937 CACHE_LOG_DEBUG(("CACHE: WriteDataCacheBlocks [%x size=%u]\n",
938 binding->mRecord.HashNumber(), size));
939
940 nsresult rv = NS_OK;
941
942 // determine block file & number of blocks
943 uint32_t fileIndex = CalculateFileIndex(size);
944 uint32_t blockCount = 0;
945 int32_t startBlock = 0;
946
947 if (size > 0) {
948 // if fileIndex is 0, bad things happen below, which makes gcc 4.7
949 // complain, but it's not supposed to happen. See bug 854105.
950 MOZ_ASSERT(fileIndex);
951 while (fileIndex) {
952 uint32_t blockSize = GetBlockSizeForIndex(fileIndex);
953 blockCount = ((size - 1) / blockSize) + 1;
954
955 rv = mBlockFile[fileIndex - 1].WriteBlocks(buffer, size, blockCount,
956 &startBlock);
957 if (NS_SUCCEEDED(rv)) {
958 IncrementTotalSize(blockCount, blockSize);
959 break;
960 }
961
962 if (fileIndex == kNumBlockFiles) return rv;
963
964 fileIndex++;
965 }
966 }
967
968 // update binding and cache map record
969 binding->mRecord.SetDataBlocks(fileIndex, startBlock, blockCount);
970 if (!binding->mDoomed) {
971 rv = UpdateRecord(&binding->mRecord);
972 }
973 return rv;
974 }
975
DeleteStorage(nsDiskCacheRecord * record)976 nsresult nsDiskCacheMap::DeleteStorage(nsDiskCacheRecord *record) {
977 nsresult rv1 = DeleteStorage(record, nsDiskCache::kData);
978 nsresult rv2 = DeleteStorage(record, nsDiskCache::kMetaData);
979 return NS_FAILED(rv1) ? rv1 : rv2;
980 }
981
DeleteStorage(nsDiskCacheRecord * record,bool metaData)982 nsresult nsDiskCacheMap::DeleteStorage(nsDiskCacheRecord *record,
983 bool metaData) {
984 CACHE_LOG_DEBUG(
985 ("CACHE: DeleteStorage [%x %u]\n", record->HashNumber(), metaData));
986
987 nsresult rv = NS_ERROR_UNEXPECTED;
988 uint32_t fileIndex = metaData ? record->MetaFile() : record->DataFile();
989 nsCOMPtr<nsIFile> file;
990
991 if (fileIndex == 0) {
992 // delete the file
993 uint32_t sizeK = metaData ? record->MetaFileSize() : record->DataFileSize();
994 // XXX if sizeK == USHRT_MAX, stat file for actual size
995
996 rv = GetFileForDiskCacheRecord(record, metaData, false,
997 getter_AddRefs(file));
998 if (NS_SUCCEEDED(rv)) {
999 rv = file->Remove(false); // false == non-recursive
1000 }
1001 DecrementTotalSize(sizeK);
1002
1003 } else if (fileIndex < (kNumBlockFiles + 1)) {
1004 // deallocate blocks
1005 uint32_t startBlock =
1006 metaData ? record->MetaStartBlock() : record->DataStartBlock();
1007 uint32_t blockCount =
1008 metaData ? record->MetaBlockCount() : record->DataBlockCount();
1009
1010 rv = mBlockFile[fileIndex - 1].DeallocateBlocks(startBlock, blockCount);
1011 DecrementTotalSize(blockCount, GetBlockSizeForIndex(fileIndex));
1012 }
1013 if (metaData)
1014 record->ClearMetaLocation();
1015 else
1016 record->ClearDataLocation();
1017
1018 return rv;
1019 }
1020
GetFileForDiskCacheRecord(nsDiskCacheRecord * record,bool meta,bool createPath,nsIFile ** result)1021 nsresult nsDiskCacheMap::GetFileForDiskCacheRecord(nsDiskCacheRecord *record,
1022 bool meta, bool createPath,
1023 nsIFile **result) {
1024 if (!mCacheDirectory) return NS_ERROR_NOT_AVAILABLE;
1025
1026 nsCOMPtr<nsIFile> file;
1027 nsresult rv = mCacheDirectory->Clone(getter_AddRefs(file));
1028 if (NS_FAILED(rv)) return rv;
1029
1030 uint32_t hash = record->HashNumber();
1031
1032 // The file is stored under subdirectories according to the hash number:
1033 // 0x01234567 -> 0/12/
1034 rv = file->AppendNative(nsPrintfCString("%X", hash >> 28));
1035 if (NS_FAILED(rv)) return rv;
1036 rv = file->AppendNative(nsPrintfCString("%02X", (hash >> 20) & 0xFF));
1037 if (NS_FAILED(rv)) return rv;
1038
1039 bool exists;
1040 if (createPath && (NS_FAILED(file->Exists(&exists)) || !exists)) {
1041 rv = file->Create(nsIFile::DIRECTORY_TYPE, 0700);
1042 if (NS_FAILED(rv)) return rv;
1043 }
1044
1045 int16_t generation = record->Generation();
1046 char name[32];
1047 // Cut the beginning of the hash that was used in the path
1048 ::SprintfLiteral(name, "%05X%c%02X", hash & 0xFFFFF, (meta ? 'm' : 'd'),
1049 generation);
1050 rv = file->AppendNative(nsDependentCString(name));
1051 if (NS_FAILED(rv)) return rv;
1052
1053 NS_IF_ADDREF(*result = file);
1054 return rv;
1055 }
1056
GetLocalFileForDiskCacheRecord(nsDiskCacheRecord * record,bool meta,bool createPath,nsIFile ** result)1057 nsresult nsDiskCacheMap::GetLocalFileForDiskCacheRecord(
1058 nsDiskCacheRecord *record, bool meta, bool createPath, nsIFile **result) {
1059 nsCOMPtr<nsIFile> file;
1060 nsresult rv =
1061 GetFileForDiskCacheRecord(record, meta, createPath, getter_AddRefs(file));
1062 if (NS_FAILED(rv)) return rv;
1063
1064 NS_IF_ADDREF(*result = file);
1065 return rv;
1066 }
1067
GetBlockFileForIndex(uint32_t index,nsIFile ** result)1068 nsresult nsDiskCacheMap::GetBlockFileForIndex(uint32_t index,
1069 nsIFile **result) {
1070 if (!mCacheDirectory) return NS_ERROR_NOT_AVAILABLE;
1071
1072 nsCOMPtr<nsIFile> file;
1073 nsresult rv = mCacheDirectory->Clone(getter_AddRefs(file));
1074 if (NS_FAILED(rv)) return rv;
1075
1076 char name[32];
1077 ::SprintfLiteral(name, "_CACHE_%03d_", index + 1);
1078 rv = file->AppendNative(nsDependentCString(name));
1079 if (NS_FAILED(rv)) return rv;
1080
1081 NS_IF_ADDREF(*result = file);
1082
1083 return rv;
1084 }
1085
CalculateFileIndex(uint32_t size)1086 uint32_t nsDiskCacheMap::CalculateFileIndex(uint32_t size) {
1087 // We prefer to use block file with larger block if the wasted space would
1088 // be the same. E.g. store entry with size of 3073 bytes in 1 4K-block
1089 // instead of in 4 1K-blocks.
1090
1091 if (size <= 3 * BLOCK_SIZE_FOR_INDEX(1)) return 1;
1092 if (size <= 3 * BLOCK_SIZE_FOR_INDEX(2)) return 2;
1093 if (size <= 4 * BLOCK_SIZE_FOR_INDEX(3)) return 3;
1094 return 0;
1095 }
1096
EnsureBuffer(uint32_t bufSize)1097 nsresult nsDiskCacheMap::EnsureBuffer(uint32_t bufSize) {
1098 if (mBufferSize < bufSize) {
1099 char *buf = (char *)realloc(mBuffer, bufSize);
1100 if (!buf) {
1101 mBufferSize = 0;
1102 return NS_ERROR_OUT_OF_MEMORY;
1103 }
1104 mBuffer = buf;
1105 mBufferSize = bufSize;
1106 }
1107 return NS_OK;
1108 }
1109
NotifyCapacityChange(uint32_t capacity)1110 void nsDiskCacheMap::NotifyCapacityChange(uint32_t capacity) {
1111 // Heuristic 1. average cache entry size is probably around 1KB
1112 // Heuristic 2. we don't want more than 32MB reserved to store the record
1113 // map in memory.
1114 const int32_t RECORD_COUNT_LIMIT =
1115 32 * 1024 * 1024 / sizeof(nsDiskCacheRecord);
1116 int32_t maxRecordCount = std::min(int32_t(capacity), RECORD_COUNT_LIMIT);
1117 if (mMaxRecordCount < maxRecordCount) {
1118 // We can only grow
1119 mMaxRecordCount = maxRecordCount;
1120 }
1121 }
1122
SizeOfExcludingThis(MallocSizeOf aMallocSizeOf)1123 size_t nsDiskCacheMap::SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) {
1124 size_t usage = aMallocSizeOf(mRecordArray);
1125
1126 usage += aMallocSizeOf(mBuffer);
1127 usage += aMallocSizeOf(mMapFD);
1128 usage += aMallocSizeOf(mCleanFD);
1129 usage += aMallocSizeOf(mCacheDirectory);
1130 usage += aMallocSizeOf(mCleanCacheTimer);
1131
1132 for (int i = 0; i < kNumBlockFiles; i++) {
1133 usage += mBlockFile[i].SizeOfExcludingThis(aMallocSizeOf);
1134 }
1135
1136 return usage;
1137 }
1138
InitCacheClean(nsIFile * cacheDirectory,nsDiskCache::CorruptCacheInfo * corruptInfo)1139 nsresult nsDiskCacheMap::InitCacheClean(
1140 nsIFile *cacheDirectory, nsDiskCache::CorruptCacheInfo *corruptInfo) {
1141 // The _CACHE_CLEAN_ file will be used in the future to determine
1142 // if the cache is clean or not.
1143 bool cacheCleanFileExists = false;
1144 nsCOMPtr<nsIFile> cacheCleanFile;
1145 nsresult rv = cacheDirectory->GetParent(getter_AddRefs(cacheCleanFile));
1146 if (NS_SUCCEEDED(rv)) {
1147 rv = cacheCleanFile->AppendNative(NS_LITERAL_CSTRING("_CACHE_CLEAN_"));
1148 if (NS_SUCCEEDED(rv)) {
1149 // Check if the file already exists, if it does, we will later read the
1150 // value and report it to telemetry.
1151 cacheCleanFile->Exists(&cacheCleanFileExists);
1152 }
1153 }
1154 if (NS_FAILED(rv)) {
1155 NS_WARNING("Could not build cache clean file path");
1156 *corruptInfo = nsDiskCache::kCacheCleanFilePathError;
1157 return rv;
1158 }
1159
1160 // Make sure the _CACHE_CLEAN_ file exists
1161 rv = cacheCleanFile->OpenNSPRFileDesc(PR_RDWR | PR_CREATE_FILE, 00600,
1162 &mCleanFD);
1163 if (NS_FAILED(rv)) {
1164 NS_WARNING("Could not open cache clean file");
1165 *corruptInfo = nsDiskCache::kCacheCleanOpenFileError;
1166 return rv;
1167 }
1168
1169 if (cacheCleanFileExists) {
1170 char clean = '0';
1171 int32_t bytesRead = PR_Read(mCleanFD, &clean, 1);
1172 if (bytesRead != 1) {
1173 NS_WARNING("Could not read _CACHE_CLEAN_ file contents");
1174 }
1175 }
1176
1177 // Create a timer that will be used to validate the cache
1178 // as long as an activity threshold was met
1179 mCleanCacheTimer =
1180 NS_NewTimer(nsCacheService::GlobalInstance()->mCacheIOThread);
1181 rv = mCleanCacheTimer ? ResetCacheTimer() : NS_ERROR_OUT_OF_MEMORY;
1182
1183 if (NS_FAILED(rv)) {
1184 NS_WARNING("Could not create cache clean timer");
1185 mCleanCacheTimer = nullptr;
1186 *corruptInfo = nsDiskCache::kCacheCleanTimerError;
1187 return rv;
1188 }
1189
1190 return NS_OK;
1191 }
1192
WriteCacheClean(bool clean)1193 nsresult nsDiskCacheMap::WriteCacheClean(bool clean) {
1194 nsCacheService::AssertOwnsLock();
1195 if (!mCleanFD) {
1196 NS_WARNING("Cache clean file is not open!");
1197 return NS_ERROR_FAILURE;
1198 }
1199
1200 CACHE_LOG_DEBUG(("CACHE: WriteCacheClean: %d\n", clean ? 1 : 0));
1201 // I'm using a simple '1' or '0' to denote cache clean
1202 // since it can be edited easily by any text editor for testing.
1203 char data = clean ? '1' : '0';
1204 int32_t filePos = PR_Seek(mCleanFD, 0, PR_SEEK_SET);
1205 if (filePos != 0) {
1206 NS_WARNING("Could not seek in cache clean file!");
1207 return NS_ERROR_FAILURE;
1208 }
1209 int32_t bytesWritten = PR_Write(mCleanFD, &data, 1);
1210 if (bytesWritten != 1) {
1211 NS_WARNING("Could not write cache clean file!");
1212 return NS_ERROR_FAILURE;
1213 }
1214 PRStatus err = PR_Sync(mCleanFD);
1215 if (err != PR_SUCCESS) {
1216 NS_WARNING("Could not flush cache clean file!");
1217 }
1218
1219 return NS_OK;
1220 }
1221
InvalidateCache()1222 nsresult nsDiskCacheMap::InvalidateCache() {
1223 nsCacheService::AssertOwnsLock();
1224 CACHE_LOG_DEBUG(("CACHE: InvalidateCache\n"));
1225 nsresult rv;
1226
1227 if (!mIsDirtyCacheFlushed) {
1228 rv = WriteCacheClean(false);
1229 if (NS_FAILED(rv)) {
1230 return rv;
1231 }
1232
1233 mIsDirtyCacheFlushed = true;
1234 }
1235
1236 rv = ResetCacheTimer();
1237 NS_ENSURE_SUCCESS(rv, rv);
1238
1239 return NS_OK;
1240 }
1241
ResetCacheTimer(int32_t timeout)1242 nsresult nsDiskCacheMap::ResetCacheTimer(int32_t timeout) {
1243 mCleanCacheTimer->Cancel();
1244 nsresult rv = mCleanCacheTimer->InitWithNamedFuncCallback(
1245 RevalidateTimerCallback, nullptr, timeout, nsITimer::TYPE_ONE_SHOT,
1246 "nsDiskCacheMap::ResetCacheTimer");
1247 NS_ENSURE_SUCCESS(rv, rv);
1248 mLastInvalidateTime = PR_IntervalNow();
1249
1250 return rv;
1251 }
1252
RevalidateTimerCallback(nsITimer * aTimer,void * arg)1253 void nsDiskCacheMap::RevalidateTimerCallback(nsITimer *aTimer, void *arg) {
1254 nsCacheServiceAutoLock lock;
1255 if (!nsCacheService::gService->mDiskDevice ||
1256 !nsCacheService::gService->mDiskDevice->Initialized()) {
1257 return;
1258 }
1259
1260 nsDiskCacheMap *diskCacheMap =
1261 &nsCacheService::gService->mDiskDevice->mCacheMap;
1262
1263 // If we have less than kRevalidateCacheTimeout since the last timer was
1264 // issued then another thread called InvalidateCache. This won't catch
1265 // all cases where we wanted to cancel the timer, but under the lock it
1266 // is always OK to revalidate as long as IsCacheInSafeState() returns
1267 // true. We just want to avoid revalidating when we can to reduce IO
1268 // and this check will do that.
1269 uint32_t delta = PR_IntervalToMilliseconds(
1270 PR_IntervalNow() - diskCacheMap->mLastInvalidateTime) +
1271 kRevalidateCacheTimeoutTolerance;
1272 if (delta < kRevalidateCacheTimeout) {
1273 diskCacheMap->ResetCacheTimer();
1274 return;
1275 }
1276
1277 nsresult rv = diskCacheMap->RevalidateCache();
1278 if (NS_FAILED(rv)) {
1279 diskCacheMap->ResetCacheTimer(kRevalidateCacheErrorTimeout);
1280 }
1281 }
1282
IsCacheInSafeState()1283 bool nsDiskCacheMap::IsCacheInSafeState() {
1284 return nsCacheService::GlobalInstance()->IsDoomListEmpty();
1285 }
1286
RevalidateCache()1287 nsresult nsDiskCacheMap::RevalidateCache() {
1288 CACHE_LOG_DEBUG(("CACHE: RevalidateCache\n"));
1289 nsresult rv;
1290
1291 if (!IsCacheInSafeState()) {
1292 CACHE_LOG_DEBUG(
1293 ("CACHE: Revalidation should not performed because "
1294 "cache not in a safe state\n"));
1295 // Normally we would return an error here, but there is a bug where
1296 // the doom list sometimes gets an entry 'stuck' and doens't clear it
1297 // until browser shutdown. So we allow revalidation for the time being
1298 // to get proper telemetry data of how much the cache corruption plan
1299 // would help.
1300 }
1301
1302 // If telemetry data shows it is worth it, we'll be flushing headers and
1303 // records before flushing the clean cache file.
1304
1305 // Write out the _CACHE_CLEAN_ file with '1'
1306 rv = WriteCacheClean(true);
1307 if (NS_FAILED(rv)) {
1308 return rv;
1309 }
1310
1311 mIsDirtyCacheFlushed = false;
1312
1313 return NS_OK;
1314 }
1315