Bug 783551 - Get tooltool running on the b2g on OS X builds. r=respindola
[gecko.git] / netwerk / cache / nsDiskCacheMap.cpp
blob5410258b5b7ba0cd0457a3374a4b7b7b262aa7c7
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/. */
7 #include "nsDiskCacheMap.h"
8 #include "nsDiskCacheBinding.h"
9 #include "nsDiskCacheEntry.h"
11 #include "nsCache.h"
13 #include <string.h>
14 #include "nsPrintfCString.h"
16 #include "nsISerializable.h"
17 #include "nsSerializationHelper.h"
19 #include "mozilla/Telemetry.h"
21 /******************************************************************************
22 * nsDiskCacheMap
23 *****************************************************************************/
25 /**
26 * File operations
29 nsresult
30 nsDiskCacheMap::Open(nsIFile * cacheDirectory,
31 nsDiskCache::CorruptCacheInfo * corruptInfo)
33 NS_ENSURE_ARG_POINTER(corruptInfo);
35 // Assume we have an unexpected error until we find otherwise.
36 *corruptInfo = nsDiskCache::kUnexpectedError;
37 NS_ENSURE_ARG_POINTER(cacheDirectory);
38 if (mMapFD) return NS_ERROR_ALREADY_INITIALIZED;
40 mCacheDirectory = cacheDirectory; // save a reference for ourselves
42 // create nsIFile for _CACHE_MAP_
43 nsresult rv;
44 nsCOMPtr<nsIFile> file;
45 rv = cacheDirectory->Clone(getter_AddRefs(file));
46 rv = file->AppendNative(NS_LITERAL_CSTRING("_CACHE_MAP_"));
47 NS_ENSURE_SUCCESS(rv, rv);
49 // open the file - restricted to user, the data could be confidential
50 rv = file->OpenNSPRFileDesc(PR_RDWR | PR_CREATE_FILE, 00600, &mMapFD);
51 if (NS_FAILED(rv)) {
52 *corruptInfo = nsDiskCache::kOpenCacheMapError;
53 NS_WARNING("Could not open cache map file");
54 return NS_ERROR_FILE_CORRUPTED;
57 bool cacheFilesExist = CacheFilesExist();
58 rv = NS_ERROR_FILE_CORRUPTED; // presume the worst
60 // check size of map file
61 PRUint32 mapSize = PR_Available(mMapFD);
62 if (mapSize == 0) { // creating a new _CACHE_MAP_
64 // block files shouldn't exist if we're creating the _CACHE_MAP_
65 if (cacheFilesExist) {
66 *corruptInfo = nsDiskCache::kBlockFilesShouldNotExist;
67 goto error_exit;
70 if (NS_FAILED(CreateCacheSubDirectories())) {
71 *corruptInfo = nsDiskCache::kCreateCacheSubdirectories;
72 goto error_exit;
75 // create the file - initialize in memory
76 memset(&mHeader, 0, sizeof(nsDiskCacheHeader));
77 mHeader.mVersion = nsDiskCache::kCurrentVersion;
78 mHeader.mRecordCount = kMinRecordCount;
79 mRecordArray = (nsDiskCacheRecord *)
80 PR_CALLOC(mHeader.mRecordCount * sizeof(nsDiskCacheRecord));
81 if (!mRecordArray) {
82 *corruptInfo = nsDiskCache::kOutOfMemory;
83 rv = NS_ERROR_OUT_OF_MEMORY;
84 goto error_exit;
86 } else if (mapSize >= sizeof(nsDiskCacheHeader)) { // read existing _CACHE_MAP_
88 // if _CACHE_MAP_ exists, so should the block files
89 if (!cacheFilesExist) {
90 *corruptInfo = nsDiskCache::kBlockFilesShouldExist;
91 goto error_exit;
94 CACHE_LOG_DEBUG(("CACHE: nsDiskCacheMap::Open [this=%p] reading map", this));
96 // read the header
97 PRUint32 bytesRead = PR_Read(mMapFD, &mHeader, sizeof(nsDiskCacheHeader));
98 if (sizeof(nsDiskCacheHeader) != bytesRead) {
99 *corruptInfo = nsDiskCache::kHeaderSizeNotRead;
100 goto error_exit;
102 mHeader.Unswap();
104 if (mHeader.mIsDirty) {
105 *corruptInfo = nsDiskCache::kHeaderIsDirty;
106 goto error_exit;
109 if (mHeader.mVersion != nsDiskCache::kCurrentVersion) {
110 *corruptInfo = nsDiskCache::kVersionMismatch;
111 goto error_exit;
114 PRUint32 recordArraySize =
115 mHeader.mRecordCount * sizeof(nsDiskCacheRecord);
116 if (mapSize < recordArraySize + sizeof(nsDiskCacheHeader)) {
117 *corruptInfo = nsDiskCache::kRecordsIncomplete;
118 goto error_exit;
121 // Get the space for the records
122 mRecordArray = (nsDiskCacheRecord *) PR_MALLOC(recordArraySize);
123 if (!mRecordArray) {
124 *corruptInfo = nsDiskCache::kOutOfMemory;
125 rv = NS_ERROR_OUT_OF_MEMORY;
126 goto error_exit;
129 // Read the records
130 bytesRead = PR_Read(mMapFD, mRecordArray, recordArraySize);
131 if (bytesRead < recordArraySize) {
132 *corruptInfo = nsDiskCache::kNotEnoughToRead;
133 goto error_exit;
136 // Unswap each record
137 PRInt32 total = 0;
138 for (PRInt32 i = 0; i < mHeader.mRecordCount; ++i) {
139 if (mRecordArray[i].HashNumber()) {
140 #if defined(IS_LITTLE_ENDIAN)
141 mRecordArray[i].Unswap();
142 #endif
143 total ++;
147 // verify entry count
148 if (total != mHeader.mEntryCount) {
149 *corruptInfo = nsDiskCache::kEntryCountIncorrect;
150 goto error_exit;
153 } else {
154 *corruptInfo = nsDiskCache::kHeaderIncomplete;
155 goto error_exit;
158 rv = OpenBlockFiles(corruptInfo);
159 if (NS_FAILED(rv)) {
160 // corruptInfo is set in the call to OpenBlockFiles
161 goto error_exit;
164 // set dirty bit and flush header
165 mHeader.mIsDirty = true;
166 rv = FlushHeader();
167 if (NS_FAILED(rv)) {
168 *corruptInfo = nsDiskCache::kFlushHeaderError;
169 goto error_exit;
173 // extra scope so the compiler doesn't barf on the above gotos jumping
174 // past this declaration down here
175 PRUint32 overhead = moz_malloc_size_of(mRecordArray);
176 mozilla::Telemetry::Accumulate(mozilla::Telemetry::HTTP_DISK_CACHE_OVERHEAD,
177 overhead);
180 *corruptInfo = nsDiskCache::kNotCorrupt;
181 return NS_OK;
183 error_exit:
184 (void) Close(false);
186 return rv;
190 nsresult
191 nsDiskCacheMap::Close(bool flush)
193 nsresult rv = NS_OK;
195 // If cache map file and its block files are still open, close them
196 if (mMapFD) {
197 // close block files
198 rv = CloseBlockFiles(flush);
199 if (NS_SUCCEEDED(rv) && flush && mRecordArray) {
200 // write the map records
201 rv = FlushRecords(false); // don't bother swapping buckets back
202 if (NS_SUCCEEDED(rv)) {
203 // clear dirty bit
204 mHeader.mIsDirty = false;
205 rv = FlushHeader();
208 if ((PR_Close(mMapFD) != PR_SUCCESS) && (NS_SUCCEEDED(rv)))
209 rv = NS_ERROR_UNEXPECTED;
211 mMapFD = nullptr;
213 PR_FREEIF(mRecordArray);
214 PR_FREEIF(mBuffer);
215 mBufferSize = 0;
216 return rv;
220 nsresult
221 nsDiskCacheMap::Trim()
223 nsresult rv, rv2 = NS_OK;
224 for (int i=0; i < kNumBlockFiles; ++i) {
225 rv = mBlockFile[i].Trim();
226 if (NS_FAILED(rv)) rv2 = rv; // if one or more errors, report at least one
228 // Try to shrink the records array
229 rv = ShrinkRecords();
230 if (NS_FAILED(rv)) rv2 = rv; // if one or more errors, report at least one
231 return rv2;
235 nsresult
236 nsDiskCacheMap::FlushHeader()
238 if (!mMapFD) return NS_ERROR_NOT_AVAILABLE;
240 // seek to beginning of cache map
241 PRInt32 filePos = PR_Seek(mMapFD, 0, PR_SEEK_SET);
242 if (filePos != 0) return NS_ERROR_UNEXPECTED;
244 // write the header
245 mHeader.Swap();
246 PRInt32 bytesWritten = PR_Write(mMapFD, &mHeader, sizeof(nsDiskCacheHeader));
247 mHeader.Unswap();
248 if (sizeof(nsDiskCacheHeader) != bytesWritten) {
249 return NS_ERROR_UNEXPECTED;
252 PRStatus err = PR_Sync(mMapFD);
253 if (err != PR_SUCCESS) return NS_ERROR_UNEXPECTED;
255 return NS_OK;
259 nsresult
260 nsDiskCacheMap::FlushRecords(bool unswap)
262 if (!mMapFD) return NS_ERROR_NOT_AVAILABLE;
264 // seek to beginning of buckets
265 PRInt32 filePos = PR_Seek(mMapFD, sizeof(nsDiskCacheHeader), PR_SEEK_SET);
266 if (filePos != sizeof(nsDiskCacheHeader))
267 return NS_ERROR_UNEXPECTED;
269 #if defined(IS_LITTLE_ENDIAN)
270 // Swap each record
271 for (PRInt32 i = 0; i < mHeader.mRecordCount; ++i) {
272 if (mRecordArray[i].HashNumber())
273 mRecordArray[i].Swap();
275 #endif
277 PRInt32 recordArraySize = sizeof(nsDiskCacheRecord) * mHeader.mRecordCount;
279 PRInt32 bytesWritten = PR_Write(mMapFD, mRecordArray, recordArraySize);
280 if (bytesWritten != recordArraySize)
281 return NS_ERROR_UNEXPECTED;
283 #if defined(IS_LITTLE_ENDIAN)
284 if (unswap) {
285 // Unswap each record
286 for (PRInt32 i = 0; i < mHeader.mRecordCount; ++i) {
287 if (mRecordArray[i].HashNumber())
288 mRecordArray[i].Unswap();
291 #endif
293 return NS_OK;
298 * Record operations
301 PRUint32
302 nsDiskCacheMap::GetBucketRank(PRUint32 bucketIndex, PRUint32 targetRank)
304 nsDiskCacheRecord * records = GetFirstRecordInBucket(bucketIndex);
305 PRUint32 rank = 0;
307 for (int i = mHeader.mBucketUsage[bucketIndex]-1; i >= 0; i--) {
308 if ((rank < records[i].EvictionRank()) &&
309 ((targetRank == 0) || (records[i].EvictionRank() < targetRank)))
310 rank = records[i].EvictionRank();
312 return rank;
315 nsresult
316 nsDiskCacheMap::GrowRecords()
318 if (mHeader.mRecordCount >= mMaxRecordCount)
319 return NS_OK;
320 CACHE_LOG_DEBUG(("CACHE: GrowRecords\n"));
322 // Resize the record array
323 PRInt32 newCount = mHeader.mRecordCount << 1;
324 if (newCount > mMaxRecordCount)
325 newCount = mMaxRecordCount;
326 nsDiskCacheRecord *newArray = (nsDiskCacheRecord *)
327 PR_REALLOC(mRecordArray, newCount * sizeof(nsDiskCacheRecord));
328 if (!newArray)
329 return NS_ERROR_OUT_OF_MEMORY;
331 // Space out the buckets
332 PRUint32 oldRecordsPerBucket = GetRecordsPerBucket();
333 PRUint32 newRecordsPerBucket = newCount / kBuckets;
334 // Work from back to space out each bucket to the new array
335 for (int bucketIndex = kBuckets - 1; bucketIndex >= 0; --bucketIndex) {
336 // Move bucket
337 nsDiskCacheRecord *newRecords = newArray + bucketIndex * newRecordsPerBucket;
338 const PRUint32 count = mHeader.mBucketUsage[bucketIndex];
339 memmove(newRecords,
340 newArray + bucketIndex * oldRecordsPerBucket,
341 count * sizeof(nsDiskCacheRecord));
342 // clear unused records
343 memset(newRecords + count, 0,
344 (newRecordsPerBucket - count) * sizeof(nsDiskCacheRecord));
347 // Set as the new record array
348 mRecordArray = newArray;
349 mHeader.mRecordCount = newCount;
350 return NS_OK;
353 nsresult
354 nsDiskCacheMap::ShrinkRecords()
356 if (mHeader.mRecordCount <= kMinRecordCount)
357 return NS_OK;
358 CACHE_LOG_DEBUG(("CACHE: ShrinkRecords\n"));
360 // Verify if we can shrink the record array: all buckets must be less than
361 // 1/2 filled
362 PRUint32 maxUsage = 0, bucketIndex;
363 for (bucketIndex = 0; bucketIndex < kBuckets; ++bucketIndex) {
364 if (maxUsage < mHeader.mBucketUsage[bucketIndex])
365 maxUsage = mHeader.mBucketUsage[bucketIndex];
367 // Determine new bucket size, halve size until maxUsage
368 PRUint32 oldRecordsPerBucket = GetRecordsPerBucket();
369 PRUint32 newRecordsPerBucket = oldRecordsPerBucket;
370 while (maxUsage < (newRecordsPerBucket >> 1))
371 newRecordsPerBucket >>= 1;
372 if (newRecordsPerBucket < (kMinRecordCount / kBuckets))
373 newRecordsPerBucket = (kMinRecordCount / kBuckets);
374 NS_ASSERTION(newRecordsPerBucket <= oldRecordsPerBucket,
375 "ShrinkRecords() can't grow records!");
376 if (newRecordsPerBucket == oldRecordsPerBucket)
377 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));
386 // Shrink the record array memory block itself
387 PRUint32 newCount = newRecordsPerBucket * kBuckets;
388 nsDiskCacheRecord* newArray = (nsDiskCacheRecord *)
389 PR_REALLOC(mRecordArray, newCount * sizeof(nsDiskCacheRecord));
390 if (!newArray)
391 return NS_ERROR_OUT_OF_MEMORY;
393 // Set as the new record array
394 mRecordArray = newArray;
395 mHeader.mRecordCount = newCount;
396 return NS_OK;
399 nsresult
400 nsDiskCacheMap::AddRecord( nsDiskCacheRecord * mapRecord,
401 nsDiskCacheRecord * oldRecord)
403 CACHE_LOG_DEBUG(("CACHE: AddRecord [%x]\n", mapRecord->HashNumber()));
405 const PRUint32 hashNumber = mapRecord->HashNumber();
406 const PRUint32 bucketIndex = GetBucketIndex(hashNumber);
407 const PRUint32 count = mHeader.mBucketUsage[bucketIndex];
409 oldRecord->SetHashNumber(0); // signify no record
411 if (count == GetRecordsPerBucket()) {
412 // Ignore failure to grow the record space, we will then reuse old records
413 GrowRecords();
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 } else {
425 // Find the record with the highest eviction rank
426 nsDiskCacheRecord * mostEvictable = &records[0];
427 for (int i = count-1; i > 0; i--) {
428 if (records[i].EvictionRank() > mostEvictable->EvictionRank())
429 mostEvictable = &records[i];
431 *oldRecord = *mostEvictable; // i == GetRecordsPerBucket(), so
432 // evict the mostEvictable
433 *mostEvictable = *mapRecord; // replace it with the new record
434 // check if we need to update mostEvictable entry in header
435 if (mHeader.mEvictionRank[bucketIndex] < mapRecord->EvictionRank())
436 mHeader.mEvictionRank[bucketIndex] = mapRecord->EvictionRank();
437 if (oldRecord->EvictionRank() >= mHeader.mEvictionRank[bucketIndex])
438 mHeader.mEvictionRank[bucketIndex] = GetBucketRank(bucketIndex, 0);
441 NS_ASSERTION(mHeader.mEvictionRank[bucketIndex] == GetBucketRank(bucketIndex, 0),
442 "eviction rank out of sync");
443 return NS_OK;
447 nsresult
448 nsDiskCacheMap::UpdateRecord( nsDiskCacheRecord * mapRecord)
450 CACHE_LOG_DEBUG(("CACHE: UpdateRecord [%x]\n", mapRecord->HashNumber()));
452 const PRUint32 hashNumber = mapRecord->HashNumber();
453 const PRUint32 bucketIndex = GetBucketIndex(hashNumber);
454 nsDiskCacheRecord * records = GetFirstRecordInBucket(bucketIndex);
456 for (int i = mHeader.mBucketUsage[bucketIndex]-1; i >= 0; i--) {
457 if (records[i].HashNumber() == hashNumber) {
458 const PRUint32 oldRank = records[i].EvictionRank();
460 // stick the new record here
461 records[i] = *mapRecord;
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);
469 NS_ASSERTION(mHeader.mEvictionRank[bucketIndex] == GetBucketRank(bucketIndex, 0),
470 "eviction rank out of sync");
471 return NS_OK;
474 NS_NOTREACHED("record not found");
475 return NS_ERROR_UNEXPECTED;
479 nsresult
480 nsDiskCacheMap::FindRecord( PRUint32 hashNumber, nsDiskCacheRecord * result)
482 const PRUint32 bucketIndex = GetBucketIndex(hashNumber);
483 nsDiskCacheRecord * records = GetFirstRecordInBucket(bucketIndex);
485 for (int i = mHeader.mBucketUsage[bucketIndex]-1; i >= 0; i--) {
486 if (records[i].HashNumber() == hashNumber) {
487 *result = records[i]; // copy the record
488 NS_ASSERTION(result->ValidRecord(), "bad cache map record");
489 return NS_OK;
492 return NS_ERROR_CACHE_KEY_NOT_FOUND;
496 nsresult
497 nsDiskCacheMap::DeleteRecord( nsDiskCacheRecord * mapRecord)
499 CACHE_LOG_DEBUG(("CACHE: DeleteRecord [%x]\n", mapRecord->HashNumber()));
501 const PRUint32 hashNumber = mapRecord->HashNumber();
502 const PRUint32 bucketIndex = GetBucketIndex(hashNumber);
503 nsDiskCacheRecord * records = GetFirstRecordInBucket(bucketIndex);
504 PRUint32 last = mHeader.mBucketUsage[bucketIndex]-1;
506 for (int i = last; i >= 0; i--) {
507 if (records[i].HashNumber() == hashNumber) {
508 // found it, now delete it.
509 PRUint32 evictionRank = records[i].EvictionRank();
510 NS_ASSERTION(evictionRank == mapRecord->EvictionRank(),
511 "evictionRank out of sync");
512 // if not the last record, shift last record into opening
513 records[i] = records[last];
514 records[last].SetHashNumber(0); // clear last record
515 mHeader.mBucketUsage[bucketIndex] = last;
516 mHeader.mEntryCount--;
518 // update eviction rank
519 PRUint32 bucketIndex = GetBucketIndex(mapRecord->HashNumber());
520 if (mHeader.mEvictionRank[bucketIndex] <= evictionRank) {
521 mHeader.mEvictionRank[bucketIndex] = GetBucketRank(bucketIndex, 0);
524 NS_ASSERTION(mHeader.mEvictionRank[bucketIndex] ==
525 GetBucketRank(bucketIndex, 0), "eviction rank out of sync");
526 return NS_OK;
529 return NS_ERROR_UNEXPECTED;
533 PRInt32
534 nsDiskCacheMap::VisitEachRecord(PRUint32 bucketIndex,
535 nsDiskCacheRecordVisitor * visitor,
536 PRUint32 evictionRank)
538 PRInt32 rv = kVisitNextRecord;
539 PRUint32 count = mHeader.mBucketUsage[bucketIndex];
540 nsDiskCacheRecord * records = GetFirstRecordInBucket(bucketIndex);
542 // call visitor for each entry (matching any eviction rank)
543 for (int i = count-1; i >= 0; i--) {
544 if (evictionRank > records[i].EvictionRank()) continue;
546 rv = visitor->VisitRecord(&records[i]);
547 if (rv == kStopVisitingRecords)
548 break; // Stop visiting records
550 if (rv == kDeleteRecordAndContinue) {
551 --count;
552 records[i] = records[count];
553 records[count].SetHashNumber(0);
557 if (mHeader.mBucketUsage[bucketIndex] - count != 0) {
558 mHeader.mEntryCount -= mHeader.mBucketUsage[bucketIndex] - count;
559 mHeader.mBucketUsage[bucketIndex] = count;
560 // recalc eviction rank
561 mHeader.mEvictionRank[bucketIndex] = GetBucketRank(bucketIndex, 0);
563 NS_ASSERTION(mHeader.mEvictionRank[bucketIndex] ==
564 GetBucketRank(bucketIndex, 0), "eviction rank out of sync");
566 return rv;
571 * VisitRecords
573 * Visit every record in cache map in the most convenient order
575 nsresult
576 nsDiskCacheMap::VisitRecords( nsDiskCacheRecordVisitor * visitor)
578 for (int bucketIndex = 0; bucketIndex < kBuckets; ++bucketIndex) {
579 if (VisitEachRecord(bucketIndex, visitor, 0) == kStopVisitingRecords)
580 break;
582 return NS_OK;
587 * EvictRecords
589 * Just like VisitRecords, but visits the records in order of their eviction rank
591 nsresult
592 nsDiskCacheMap::EvictRecords( nsDiskCacheRecordVisitor * visitor)
594 PRUint32 tempRank[kBuckets];
595 int bucketIndex = 0;
597 // copy eviction rank array
598 for (bucketIndex = 0; bucketIndex < kBuckets; ++bucketIndex)
599 tempRank[bucketIndex] = mHeader.mEvictionRank[bucketIndex];
601 // Maximum number of iterations determined by number of records
602 // as a safety limiter for the loop. Use a copy of mHeader.mEntryCount since
603 // the value could decrease if some entry is evicted.
604 PRInt32 entryCount = mHeader.mEntryCount;
605 for (int n = 0; n < entryCount; ++n) {
607 // find bucket with highest eviction rank
608 PRUint32 rank = 0;
609 for (int i = 0; i < kBuckets; ++i) {
610 if (rank < tempRank[i]) {
611 rank = tempRank[i];
612 bucketIndex = i;
616 if (rank == 0) break; // we've examined all the records
618 // visit records in bucket with eviction ranks >= target eviction rank
619 if (VisitEachRecord(bucketIndex, visitor, rank) == kStopVisitingRecords)
620 break;
622 // find greatest rank less than 'rank'
623 tempRank[bucketIndex] = GetBucketRank(bucketIndex, rank);
625 return NS_OK;
630 nsresult
631 nsDiskCacheMap::OpenBlockFiles(nsDiskCache::CorruptCacheInfo * corruptInfo)
633 NS_ENSURE_ARG_POINTER(corruptInfo);
635 // create nsIFile for block file
636 nsCOMPtr<nsIFile> blockFile;
637 nsresult rv = NS_OK;
638 *corruptInfo = nsDiskCache::kUnexpectedError;
640 for (int i = 0; i < kNumBlockFiles; ++i) {
641 rv = GetBlockFileForIndex(i, getter_AddRefs(blockFile));
642 if (NS_FAILED(rv)) {
643 *corruptInfo = nsDiskCache::kCouldNotGetBlockFileForIndex;
644 break;
647 PRUint32 blockSize = GetBlockSizeForIndex(i+1); // +1 to match file selectors 1,2,3
648 PRUint32 bitMapSize = GetBitMapSizeForIndex(i+1);
649 rv = mBlockFile[i].Open(blockFile, blockSize, bitMapSize, corruptInfo);
650 if (NS_FAILED(rv)) {
651 // corruptInfo was set inside the call to mBlockFile[i].Open
652 break;
655 // close all files in case of any error
656 if (NS_FAILED(rv))
657 (void)CloseBlockFiles(false); // we already have an error to report
659 return rv;
663 nsresult
664 nsDiskCacheMap::CloseBlockFiles(bool flush)
666 nsresult rv, rv2 = NS_OK;
667 for (int i=0; i < kNumBlockFiles; ++i) {
668 rv = mBlockFile[i].Close(flush);
669 if (NS_FAILED(rv)) rv2 = rv; // if one or more errors, report at least one
671 return rv2;
675 bool
676 nsDiskCacheMap::CacheFilesExist()
678 nsCOMPtr<nsIFile> blockFile;
679 nsresult rv;
681 for (int i = 0; i < kNumBlockFiles; ++i) {
682 bool exists;
683 rv = GetBlockFileForIndex(i, getter_AddRefs(blockFile));
684 if (NS_FAILED(rv)) return false;
686 rv = blockFile->Exists(&exists);
687 if (NS_FAILED(rv) || !exists) return false;
690 return true;
694 nsresult
695 nsDiskCacheMap::CreateCacheSubDirectories()
697 if (!mCacheDirectory)
698 return NS_ERROR_UNEXPECTED;
700 for (PRInt32 index = 0 ; index < 16 ; index++) {
701 nsCOMPtr<nsIFile> file;
702 nsresult rv = mCacheDirectory->Clone(getter_AddRefs(file));
703 if (NS_FAILED(rv))
704 return rv;
706 rv = file->AppendNative(nsPrintfCString("%X", index));
707 if (NS_FAILED(rv))
708 return rv;
710 rv = file->Create(nsIFile::DIRECTORY_TYPE, 0700);
711 if (NS_FAILED(rv))
712 return rv;
715 return NS_OK;
719 nsDiskCacheEntry *
720 nsDiskCacheMap::ReadDiskCacheEntry(nsDiskCacheRecord * record)
722 CACHE_LOG_DEBUG(("CACHE: ReadDiskCacheEntry [%x]\n", record->HashNumber()));
724 nsresult rv = NS_ERROR_UNEXPECTED;
725 nsDiskCacheEntry * diskEntry = nullptr;
726 PRUint32 metaFile = record->MetaFile();
727 PRInt32 bytesRead = 0;
729 if (!record->MetaLocationInitialized()) return nullptr;
731 if (metaFile == 0) { // entry/metadata stored in separate file
732 // open and read the file
733 nsCOMPtr<nsIFile> file;
734 rv = GetLocalFileForDiskCacheRecord(record,
735 nsDiskCache::kMetaData,
736 false,
737 getter_AddRefs(file));
738 NS_ENSURE_SUCCESS(rv, nullptr);
740 CACHE_LOG_DEBUG(("CACHE: nsDiskCacheMap::ReadDiskCacheEntry"
741 "[this=%p] reading disk cache entry", this));
743 PRFileDesc * fd = nullptr;
745 // open the file - restricted to user, the data could be confidential
746 rv = file->OpenNSPRFileDesc(PR_RDONLY, 00600, &fd);
747 NS_ENSURE_SUCCESS(rv, nullptr);
749 PRInt32 fileSize = PR_Available(fd);
750 if (fileSize < 0) {
751 // an error occurred. We could call PR_GetError(), but how would that help?
752 rv = NS_ERROR_UNEXPECTED;
753 } else {
754 rv = EnsureBuffer(fileSize);
755 if (NS_SUCCEEDED(rv)) {
756 bytesRead = PR_Read(fd, mBuffer, fileSize);
757 if (bytesRead < fileSize) {
758 rv = NS_ERROR_UNEXPECTED;
762 PR_Close(fd);
763 NS_ENSURE_SUCCESS(rv, nullptr);
765 } else if (metaFile < (kNumBlockFiles + 1)) {
766 // entry/metadata stored in cache block file
768 // allocate buffer
769 PRUint32 blockCount = record->MetaBlockCount();
770 bytesRead = blockCount * GetBlockSizeForIndex(metaFile);
772 rv = EnsureBuffer(bytesRead);
773 NS_ENSURE_SUCCESS(rv, nullptr);
775 // read diskEntry, note when the blocks are at the end of file,
776 // bytesRead may be less than blockSize*blockCount.
777 // But the bytesRead should at least agree with the real disk entry size.
778 rv = mBlockFile[metaFile - 1].ReadBlocks(mBuffer,
779 record->MetaStartBlock(),
780 blockCount,
781 &bytesRead);
782 NS_ENSURE_SUCCESS(rv, nullptr);
784 diskEntry = (nsDiskCacheEntry *)mBuffer;
785 diskEntry->Unswap(); // disk to memory
786 // Check if calculated size agrees with bytesRead
787 if (bytesRead < 0 || (PRUint32)bytesRead < diskEntry->Size())
788 return nullptr;
790 // Return the buffer containing the diskEntry structure
791 return diskEntry;
796 * CreateDiskCacheEntry(nsCacheEntry * entry)
798 * Prepare an nsCacheEntry for writing to disk
800 nsDiskCacheEntry *
801 nsDiskCacheMap::CreateDiskCacheEntry(nsDiskCacheBinding * binding,
802 PRUint32 * aSize)
804 nsCacheEntry * entry = binding->mCacheEntry;
805 if (!entry) return nullptr;
807 // Store security info, if it is serializable
808 nsCOMPtr<nsISupports> infoObj = entry->SecurityInfo();
809 nsCOMPtr<nsISerializable> serializable = do_QueryInterface(infoObj);
810 if (infoObj && !serializable) return nullptr;
811 if (serializable) {
812 nsCString info;
813 nsresult rv = NS_SerializeToString(serializable, info);
814 if (NS_FAILED(rv)) return nullptr;
815 rv = entry->SetMetaDataElement("security-info", info.get());
816 if (NS_FAILED(rv)) return nullptr;
819 PRUint32 keySize = entry->Key()->Length() + 1;
820 PRUint32 metaSize = entry->MetaDataSize();
821 PRUint32 size = sizeof(nsDiskCacheEntry) + keySize + metaSize;
823 if (aSize) *aSize = size;
825 nsresult rv = EnsureBuffer(size);
826 if (NS_FAILED(rv)) return nullptr;
828 nsDiskCacheEntry *diskEntry = (nsDiskCacheEntry *)mBuffer;
829 diskEntry->mHeaderVersion = nsDiskCache::kCurrentVersion;
830 diskEntry->mMetaLocation = binding->mRecord.MetaLocation();
831 diskEntry->mFetchCount = entry->FetchCount();
832 diskEntry->mLastFetched = entry->LastFetched();
833 diskEntry->mLastModified = entry->LastModified();
834 diskEntry->mExpirationTime = entry->ExpirationTime();
835 diskEntry->mDataSize = entry->DataSize();
836 diskEntry->mKeySize = keySize;
837 diskEntry->mMetaDataSize = metaSize;
839 memcpy(diskEntry->Key(), entry->Key()->get(), keySize);
841 rv = entry->FlattenMetaData(diskEntry->MetaData(), metaSize);
842 if (NS_FAILED(rv)) return nullptr;
844 return diskEntry;
848 nsresult
849 nsDiskCacheMap::WriteDiskCacheEntry(nsDiskCacheBinding * binding)
851 CACHE_LOG_DEBUG(("CACHE: WriteDiskCacheEntry [%x]\n",
852 binding->mRecord.HashNumber()));
854 nsresult rv = NS_OK;
855 PRUint32 size;
856 nsDiskCacheEntry * diskEntry = CreateDiskCacheEntry(binding, &size);
857 if (!diskEntry) return NS_ERROR_UNEXPECTED;
859 PRUint32 fileIndex = CalculateFileIndex(size);
861 // Deallocate old storage if necessary
862 if (binding->mRecord.MetaLocationInitialized()) {
863 // we have existing storage
865 if ((binding->mRecord.MetaFile() == 0) &&
866 (fileIndex == 0)) { // keeping the separate file
867 // just decrement total
868 DecrementTotalSize(binding->mRecord.MetaFileSize());
869 NS_ASSERTION(binding->mRecord.MetaFileGeneration() == binding->mGeneration,
870 "generations out of sync");
871 } else {
872 rv = DeleteStorage(&binding->mRecord, nsDiskCache::kMetaData);
873 NS_ENSURE_SUCCESS(rv, rv);
877 binding->mRecord.SetEvictionRank(ULONG_MAX - SecondsFromPRTime(PR_Now()));
878 // write entry data to disk cache block file
879 diskEntry->Swap();
881 if (fileIndex != 0) {
882 while (1) {
883 PRUint32 blockSize = GetBlockSizeForIndex(fileIndex);
884 PRUint32 blocks = ((size - 1) / blockSize) + 1;
886 PRInt32 startBlock;
887 rv = mBlockFile[fileIndex - 1].WriteBlocks(diskEntry, size, blocks,
888 &startBlock);
889 if (NS_SUCCEEDED(rv)) {
890 // update binding and cache map record
891 binding->mRecord.SetMetaBlocks(fileIndex, startBlock, blocks);
893 rv = UpdateRecord(&binding->mRecord);
894 NS_ENSURE_SUCCESS(rv, rv);
896 // XXX we should probably write out bucket ourselves
898 IncrementTotalSize(blocks, blockSize);
899 break;
902 if (fileIndex == kNumBlockFiles) {
903 fileIndex = 0; // write data to separate file
904 break;
907 // try next block file
908 fileIndex++;
912 if (fileIndex == 0) {
913 // Write entry data to separate file
914 PRUint32 metaFileSizeK = ((size + 0x03FF) >> 10); // round up to nearest 1k
915 if (metaFileSizeK > kMaxDataSizeK)
916 metaFileSizeK = kMaxDataSizeK;
918 binding->mRecord.SetMetaFileGeneration(binding->mGeneration);
919 binding->mRecord.SetMetaFileSize(metaFileSizeK);
920 rv = UpdateRecord(&binding->mRecord);
921 NS_ENSURE_SUCCESS(rv, rv);
923 nsCOMPtr<nsIFile> localFile;
924 rv = GetLocalFileForDiskCacheRecord(&binding->mRecord,
925 nsDiskCache::kMetaData,
926 true,
927 getter_AddRefs(localFile));
928 NS_ENSURE_SUCCESS(rv, rv);
930 // open the file
931 PRFileDesc * fd;
932 // open the file - restricted to user, the data could be confidential
933 rv = localFile->OpenNSPRFileDesc(PR_RDWR | PR_TRUNCATE | PR_CREATE_FILE, 00600, &fd);
934 NS_ENSURE_SUCCESS(rv, rv);
936 // write the file
937 PRInt32 bytesWritten = PR_Write(fd, diskEntry, size);
939 PRStatus err = PR_Close(fd);
940 if ((bytesWritten != (PRInt32)size) || (err != PR_SUCCESS)) {
941 return NS_ERROR_UNEXPECTED;
944 IncrementTotalSize(metaFileSizeK);
947 return rv;
951 nsresult
952 nsDiskCacheMap::ReadDataCacheBlocks(nsDiskCacheBinding * binding, char * buffer, PRUint32 size)
954 CACHE_LOG_DEBUG(("CACHE: ReadDataCacheBlocks [%x size=%u]\n",
955 binding->mRecord.HashNumber(), size));
957 PRUint32 fileIndex = binding->mRecord.DataFile();
958 PRInt32 readSize = size;
960 nsresult rv = mBlockFile[fileIndex - 1].ReadBlocks(buffer,
961 binding->mRecord.DataStartBlock(),
962 binding->mRecord.DataBlockCount(),
963 &readSize);
964 NS_ENSURE_SUCCESS(rv, rv);
965 if (readSize < (PRInt32)size) {
966 rv = NS_ERROR_UNEXPECTED;
968 return rv;
972 nsresult
973 nsDiskCacheMap::WriteDataCacheBlocks(nsDiskCacheBinding * binding, char * buffer, PRUint32 size)
975 CACHE_LOG_DEBUG(("CACHE: WriteDataCacheBlocks [%x size=%u]\n",
976 binding->mRecord.HashNumber(), size));
978 nsresult rv = NS_OK;
980 // determine block file & number of blocks
981 PRUint32 fileIndex = CalculateFileIndex(size);
982 PRUint32 blockCount = 0;
983 PRInt32 startBlock = 0;
985 if (size > 0) {
986 while (1) {
987 PRUint32 blockSize = GetBlockSizeForIndex(fileIndex);
988 blockCount = ((size - 1) / blockSize) + 1;
990 rv = mBlockFile[fileIndex - 1].WriteBlocks(buffer, size, blockCount,
991 &startBlock);
992 if (NS_SUCCEEDED(rv)) {
993 IncrementTotalSize(blockCount, blockSize);
994 break;
997 if (fileIndex == kNumBlockFiles)
998 return rv;
1000 fileIndex++;
1004 // update binding and cache map record
1005 binding->mRecord.SetDataBlocks(fileIndex, startBlock, blockCount);
1006 if (!binding->mDoomed) {
1007 rv = UpdateRecord(&binding->mRecord);
1009 return rv;
1013 nsresult
1014 nsDiskCacheMap::DeleteStorage(nsDiskCacheRecord * record)
1016 nsresult rv1 = DeleteStorage(record, nsDiskCache::kData);
1017 nsresult rv2 = DeleteStorage(record, nsDiskCache::kMetaData);
1018 return NS_FAILED(rv1) ? rv1 : rv2;
1022 nsresult
1023 nsDiskCacheMap::DeleteStorage(nsDiskCacheRecord * record, bool metaData)
1025 CACHE_LOG_DEBUG(("CACHE: DeleteStorage [%x %u]\n", record->HashNumber(),
1026 metaData));
1028 nsresult rv = NS_ERROR_UNEXPECTED;
1029 PRUint32 fileIndex = metaData ? record->MetaFile() : record->DataFile();
1030 nsCOMPtr<nsIFile> file;
1032 if (fileIndex == 0) {
1033 // delete the file
1034 PRUint32 sizeK = metaData ? record->MetaFileSize() : record->DataFileSize();
1035 // XXX if sizeK == USHRT_MAX, stat file for actual size
1037 rv = GetFileForDiskCacheRecord(record, metaData, false, getter_AddRefs(file));
1038 if (NS_SUCCEEDED(rv)) {
1039 rv = file->Remove(false); // false == non-recursive
1041 DecrementTotalSize(sizeK);
1043 } else if (fileIndex < (kNumBlockFiles + 1)) {
1044 // deallocate blocks
1045 PRUint32 startBlock = metaData ? record->MetaStartBlock() : record->DataStartBlock();
1046 PRUint32 blockCount = metaData ? record->MetaBlockCount() : record->DataBlockCount();
1048 rv = mBlockFile[fileIndex - 1].DeallocateBlocks(startBlock, blockCount);
1049 DecrementTotalSize(blockCount, GetBlockSizeForIndex(fileIndex));
1051 if (metaData) record->ClearMetaLocation();
1052 else record->ClearDataLocation();
1054 return rv;
1058 nsresult
1059 nsDiskCacheMap::GetFileForDiskCacheRecord(nsDiskCacheRecord * record,
1060 bool meta,
1061 bool createPath,
1062 nsIFile ** result)
1064 if (!mCacheDirectory) return NS_ERROR_NOT_AVAILABLE;
1066 nsCOMPtr<nsIFile> file;
1067 nsresult rv = mCacheDirectory->Clone(getter_AddRefs(file));
1068 if (NS_FAILED(rv)) return rv;
1070 PRUint32 hash = record->HashNumber();
1072 // The file is stored under subdirectories according to the hash number:
1073 // 0x01234567 -> 0/12/
1074 rv = file->AppendNative(nsPrintfCString("%X", hash >> 28));
1075 if (NS_FAILED(rv)) return rv;
1076 rv = file->AppendNative(nsPrintfCString("%02X", (hash >> 20) & 0xFF));
1077 if (NS_FAILED(rv)) return rv;
1079 bool exists;
1080 if (createPath && (NS_FAILED(file->Exists(&exists)) || !exists)) {
1081 rv = file->Create(nsIFile::DIRECTORY_TYPE, 0700);
1082 if (NS_FAILED(rv)) return rv;
1085 PRInt16 generation = record->Generation();
1086 char name[32];
1087 // Cut the beginning of the hash that was used in the path
1088 ::sprintf(name, "%05X%c%02X", hash & 0xFFFFF, (meta ? 'm' : 'd'),
1089 generation);
1090 rv = file->AppendNative(nsDependentCString(name));
1091 if (NS_FAILED(rv)) return rv;
1093 NS_IF_ADDREF(*result = file);
1094 return rv;
1098 nsresult
1099 nsDiskCacheMap::GetLocalFileForDiskCacheRecord(nsDiskCacheRecord * record,
1100 bool meta,
1101 bool createPath,
1102 nsIFile ** result)
1104 nsCOMPtr<nsIFile> file;
1105 nsresult rv = GetFileForDiskCacheRecord(record,
1106 meta,
1107 createPath,
1108 getter_AddRefs(file));
1109 if (NS_FAILED(rv)) return rv;
1111 NS_IF_ADDREF(*result = file);
1112 return rv;
1116 nsresult
1117 nsDiskCacheMap::GetBlockFileForIndex(PRUint32 index, nsIFile ** result)
1119 if (!mCacheDirectory) return NS_ERROR_NOT_AVAILABLE;
1121 nsCOMPtr<nsIFile> file;
1122 nsresult rv = mCacheDirectory->Clone(getter_AddRefs(file));
1123 if (NS_FAILED(rv)) return rv;
1125 char name[32];
1126 ::sprintf(name, "_CACHE_%03d_", index + 1);
1127 rv = file->AppendNative(nsDependentCString(name));
1128 if (NS_FAILED(rv)) return rv;
1130 NS_IF_ADDREF(*result = file);
1132 return rv;
1136 PRUint32
1137 nsDiskCacheMap::CalculateFileIndex(PRUint32 size)
1139 // We prefer to use block file with larger block if the wasted space would
1140 // be the same. E.g. store entry with size of 3073 bytes in 1 4K-block
1141 // instead of in 4 1K-blocks.
1143 if (size <= 3 * BLOCK_SIZE_FOR_INDEX(1)) return 1;
1144 if (size <= 3 * BLOCK_SIZE_FOR_INDEX(2)) return 2;
1145 if (size <= 4 * BLOCK_SIZE_FOR_INDEX(3)) return 3;
1146 return 0;
1149 nsresult
1150 nsDiskCacheMap::EnsureBuffer(PRUint32 bufSize)
1152 if (mBufferSize < bufSize) {
1153 char * buf = (char *)PR_REALLOC(mBuffer, bufSize);
1154 if (!buf) {
1155 mBufferSize = 0;
1156 return NS_ERROR_OUT_OF_MEMORY;
1158 mBuffer = buf;
1159 mBufferSize = bufSize;
1161 return NS_OK;
1164 void
1165 nsDiskCacheMap::NotifyCapacityChange(PRUint32 capacity)
1167 // Heuristic 1. average cache entry size is probably around 1KB
1168 // Heuristic 2. we don't want more than 32MB reserved to store the record
1169 // map in memory.
1170 const PRInt32 RECORD_COUNT_LIMIT = 32 * 1024 * 1024 / sizeof(nsDiskCacheRecord);
1171 PRInt32 maxRecordCount = NS_MIN(PRInt32(capacity), RECORD_COUNT_LIMIT);
1172 if (mMaxRecordCount < maxRecordCount) {
1173 // We can only grow
1174 mMaxRecordCount = maxRecordCount;