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"
14 #include "nsPrintfCString.h"
16 #include "nsISerializable.h"
17 #include "nsSerializationHelper.h"
19 #include "mozilla/Telemetry.h"
21 /******************************************************************************
23 *****************************************************************************/
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_
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
);
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
;
70 if (NS_FAILED(CreateCacheSubDirectories())) {
71 *corruptInfo
= nsDiskCache::kCreateCacheSubdirectories
;
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
));
82 *corruptInfo
= nsDiskCache::kOutOfMemory
;
83 rv
= NS_ERROR_OUT_OF_MEMORY
;
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
;
94 CACHE_LOG_DEBUG(("CACHE: nsDiskCacheMap::Open [this=%p] reading map", this));
97 PRUint32 bytesRead
= PR_Read(mMapFD
, &mHeader
, sizeof(nsDiskCacheHeader
));
98 if (sizeof(nsDiskCacheHeader
) != bytesRead
) {
99 *corruptInfo
= nsDiskCache::kHeaderSizeNotRead
;
104 if (mHeader
.mIsDirty
) {
105 *corruptInfo
= nsDiskCache::kHeaderIsDirty
;
109 if (mHeader
.mVersion
!= nsDiskCache::kCurrentVersion
) {
110 *corruptInfo
= nsDiskCache::kVersionMismatch
;
114 PRUint32 recordArraySize
=
115 mHeader
.mRecordCount
* sizeof(nsDiskCacheRecord
);
116 if (mapSize
< recordArraySize
+ sizeof(nsDiskCacheHeader
)) {
117 *corruptInfo
= nsDiskCache::kRecordsIncomplete
;
121 // Get the space for the records
122 mRecordArray
= (nsDiskCacheRecord
*) PR_MALLOC(recordArraySize
);
124 *corruptInfo
= nsDiskCache::kOutOfMemory
;
125 rv
= NS_ERROR_OUT_OF_MEMORY
;
130 bytesRead
= PR_Read(mMapFD
, mRecordArray
, recordArraySize
);
131 if (bytesRead
< recordArraySize
) {
132 *corruptInfo
= nsDiskCache::kNotEnoughToRead
;
136 // Unswap each record
138 for (PRInt32 i
= 0; i
< mHeader
.mRecordCount
; ++i
) {
139 if (mRecordArray
[i
].HashNumber()) {
140 #if defined(IS_LITTLE_ENDIAN)
141 mRecordArray
[i
].Unswap();
147 // verify entry count
148 if (total
!= mHeader
.mEntryCount
) {
149 *corruptInfo
= nsDiskCache::kEntryCountIncorrect
;
154 *corruptInfo
= nsDiskCache::kHeaderIncomplete
;
158 rv
= OpenBlockFiles(corruptInfo
);
160 // corruptInfo is set in the call to OpenBlockFiles
164 // set dirty bit and flush header
165 mHeader
.mIsDirty
= true;
168 *corruptInfo
= nsDiskCache::kFlushHeaderError
;
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
,
180 *corruptInfo
= nsDiskCache::kNotCorrupt
;
191 nsDiskCacheMap::Close(bool flush
)
195 // If cache map file and its block files are still open, close them
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
)) {
204 mHeader
.mIsDirty
= false;
208 if ((PR_Close(mMapFD
) != PR_SUCCESS
) && (NS_SUCCEEDED(rv
)))
209 rv
= NS_ERROR_UNEXPECTED
;
213 PR_FREEIF(mRecordArray
);
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
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
;
246 PRInt32 bytesWritten
= PR_Write(mMapFD
, &mHeader
, sizeof(nsDiskCacheHeader
));
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
;
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)
271 for (PRInt32 i
= 0; i
< mHeader
.mRecordCount
; ++i
) {
272 if (mRecordArray
[i
].HashNumber())
273 mRecordArray
[i
].Swap();
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)
285 // Unswap each record
286 for (PRInt32 i
= 0; i
< mHeader
.mRecordCount
; ++i
) {
287 if (mRecordArray
[i
].HashNumber())
288 mRecordArray
[i
].Unswap();
302 nsDiskCacheMap::GetBucketRank(PRUint32 bucketIndex
, PRUint32 targetRank
)
304 nsDiskCacheRecord
* records
= GetFirstRecordInBucket(bucketIndex
);
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();
316 nsDiskCacheMap::GrowRecords()
318 if (mHeader
.mRecordCount
>= mMaxRecordCount
)
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
));
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
) {
337 nsDiskCacheRecord
*newRecords
= newArray
+ bucketIndex
* newRecordsPerBucket
;
338 const PRUint32 count
= mHeader
.mBucketUsage
[bucketIndex
];
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
;
354 nsDiskCacheMap::ShrinkRecords()
356 if (mHeader
.mRecordCount
<= kMinRecordCount
)
358 CACHE_LOG_DEBUG(("CACHE: ShrinkRecords\n"));
360 // Verify if we can shrink the record array: all buckets must be less than
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
)
378 // Move the buckets close to each other
379 for (bucketIndex
= 1; bucketIndex
< kBuckets
; ++bucketIndex
) {
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
));
391 return NS_ERROR_OUT_OF_MEMORY
;
393 // Set as the new record array
394 mRecordArray
= newArray
;
395 mHeader
.mRecordCount
= newCount
;
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
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();
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");
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");
474 NS_NOTREACHED("record not found");
475 return NS_ERROR_UNEXPECTED
;
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");
492 return NS_ERROR_CACHE_KEY_NOT_FOUND
;
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");
529 return NS_ERROR_UNEXPECTED
;
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
) {
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");
573 * Visit every record in cache map in the most convenient order
576 nsDiskCacheMap::VisitRecords( nsDiskCacheRecordVisitor
* visitor
)
578 for (int bucketIndex
= 0; bucketIndex
< kBuckets
; ++bucketIndex
) {
579 if (VisitEachRecord(bucketIndex
, visitor
, 0) == kStopVisitingRecords
)
589 * Just like VisitRecords, but visits the records in order of their eviction rank
592 nsDiskCacheMap::EvictRecords( nsDiskCacheRecordVisitor
* visitor
)
594 PRUint32 tempRank
[kBuckets
];
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
609 for (int i
= 0; i
< kBuckets
; ++i
) {
610 if (rank
< tempRank
[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
)
622 // find greatest rank less than 'rank'
623 tempRank
[bucketIndex
] = GetBucketRank(bucketIndex
, rank
);
631 nsDiskCacheMap::OpenBlockFiles(nsDiskCache::CorruptCacheInfo
* corruptInfo
)
633 NS_ENSURE_ARG_POINTER(corruptInfo
);
635 // create nsIFile for block file
636 nsCOMPtr
<nsIFile
> blockFile
;
638 *corruptInfo
= nsDiskCache::kUnexpectedError
;
640 for (int i
= 0; i
< kNumBlockFiles
; ++i
) {
641 rv
= GetBlockFileForIndex(i
, getter_AddRefs(blockFile
));
643 *corruptInfo
= nsDiskCache::kCouldNotGetBlockFileForIndex
;
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
);
651 // corruptInfo was set inside the call to mBlockFile[i].Open
655 // close all files in case of any error
657 (void)CloseBlockFiles(false); // we already have an error to report
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
676 nsDiskCacheMap::CacheFilesExist()
678 nsCOMPtr
<nsIFile
> blockFile
;
681 for (int i
= 0; i
< kNumBlockFiles
; ++i
) {
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;
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
));
706 rv
= file
->AppendNative(nsPrintfCString("%X", index
));
710 rv
= file
->Create(nsIFile::DIRECTORY_TYPE
, 0700);
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
,
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
);
751 // an error occurred. We could call PR_GetError(), but how would that help?
752 rv
= NS_ERROR_UNEXPECTED
;
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
;
763 NS_ENSURE_SUCCESS(rv
, nullptr);
765 } else if (metaFile
< (kNumBlockFiles
+ 1)) {
766 // entry/metadata stored in cache block file
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(),
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())
790 // Return the buffer containing the diskEntry structure
796 * CreateDiskCacheEntry(nsCacheEntry * entry)
798 * Prepare an nsCacheEntry for writing to disk
801 nsDiskCacheMap::CreateDiskCacheEntry(nsDiskCacheBinding
* binding
,
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;
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;
849 nsDiskCacheMap::WriteDiskCacheEntry(nsDiskCacheBinding
* binding
)
851 CACHE_LOG_DEBUG(("CACHE: WriteDiskCacheEntry [%x]\n",
852 binding
->mRecord
.HashNumber()));
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");
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
881 if (fileIndex
!= 0) {
883 PRUint32 blockSize
= GetBlockSizeForIndex(fileIndex
);
884 PRUint32 blocks
= ((size
- 1) / blockSize
) + 1;
887 rv
= mBlockFile
[fileIndex
- 1].WriteBlocks(diskEntry
, size
, blocks
,
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
);
902 if (fileIndex
== kNumBlockFiles
) {
903 fileIndex
= 0; // write data to separate file
907 // try next block file
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
,
927 getter_AddRefs(localFile
));
928 NS_ENSURE_SUCCESS(rv
, rv
);
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
);
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
);
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(),
964 NS_ENSURE_SUCCESS(rv
, rv
);
965 if (readSize
< (PRInt32
)size
) {
966 rv
= NS_ERROR_UNEXPECTED
;
973 nsDiskCacheMap::WriteDataCacheBlocks(nsDiskCacheBinding
* binding
, char * buffer
, PRUint32 size
)
975 CACHE_LOG_DEBUG(("CACHE: WriteDataCacheBlocks [%x size=%u]\n",
976 binding
->mRecord
.HashNumber(), size
));
980 // determine block file & number of blocks
981 PRUint32 fileIndex
= CalculateFileIndex(size
);
982 PRUint32 blockCount
= 0;
983 PRInt32 startBlock
= 0;
987 PRUint32 blockSize
= GetBlockSizeForIndex(fileIndex
);
988 blockCount
= ((size
- 1) / blockSize
) + 1;
990 rv
= mBlockFile
[fileIndex
- 1].WriteBlocks(buffer
, size
, blockCount
,
992 if (NS_SUCCEEDED(rv
)) {
993 IncrementTotalSize(blockCount
, blockSize
);
997 if (fileIndex
== kNumBlockFiles
)
1004 // update binding and cache map record
1005 binding
->mRecord
.SetDataBlocks(fileIndex
, startBlock
, blockCount
);
1006 if (!binding
->mDoomed
) {
1007 rv
= UpdateRecord(&binding
->mRecord
);
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
;
1023 nsDiskCacheMap::DeleteStorage(nsDiskCacheRecord
* record
, bool metaData
)
1025 CACHE_LOG_DEBUG(("CACHE: DeleteStorage [%x %u]\n", record
->HashNumber(),
1028 nsresult rv
= NS_ERROR_UNEXPECTED
;
1029 PRUint32 fileIndex
= metaData
? record
->MetaFile() : record
->DataFile();
1030 nsCOMPtr
<nsIFile
> file
;
1032 if (fileIndex
== 0) {
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();
1059 nsDiskCacheMap::GetFileForDiskCacheRecord(nsDiskCacheRecord
* record
,
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
;
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();
1087 // Cut the beginning of the hash that was used in the path
1088 ::sprintf(name
, "%05X%c%02X", hash
& 0xFFFFF, (meta
? 'm' : 'd'),
1090 rv
= file
->AppendNative(nsDependentCString(name
));
1091 if (NS_FAILED(rv
)) return rv
;
1093 NS_IF_ADDREF(*result
= file
);
1099 nsDiskCacheMap::GetLocalFileForDiskCacheRecord(nsDiskCacheRecord
* record
,
1104 nsCOMPtr
<nsIFile
> file
;
1105 nsresult rv
= GetFileForDiskCacheRecord(record
,
1108 getter_AddRefs(file
));
1109 if (NS_FAILED(rv
)) return rv
;
1111 NS_IF_ADDREF(*result
= file
);
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
;
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
);
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;
1150 nsDiskCacheMap::EnsureBuffer(PRUint32 bufSize
)
1152 if (mBufferSize
< bufSize
) {
1153 char * buf
= (char *)PR_REALLOC(mBuffer
, bufSize
);
1156 return NS_ERROR_OUT_OF_MEMORY
;
1159 mBufferSize
= bufSize
;
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
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
) {
1174 mMaxRecordCount
= maxRecordCount
;