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