Bug 458861. Validate TrueType headers before activating downloaded font. r=roc, sr...
[wine-gecko.git] / netwerk / cache / src / nsDiskCacheMap.cpp
blobb7b7469728a22110ab8bd1b895503158df9cf865
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 /* ***** BEGIN LICENSE BLOCK *****
4 * Version: MPL 1.1/GPL 2.0/LGPL 2.1
6 * The contents of this file are subject to the Mozilla Public License Version
7 * 1.1 (the "License"); you may not use this file except in compliance with
8 * the License. You may obtain a copy of the License at
9 * http://www.mozilla.org/MPL/
11 * Software distributed under the License is distributed on an "AS IS" basis,
12 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
13 * for the specific language governing rights and limitations under the
14 * License.
16 * The Original Code is nsDiskCacheMap.cpp, released
17 * March 23, 2001.
19 * The Initial Developer of the Original Code is
20 * Netscape Communications Corporation.
21 * Portions created by the Initial Developer are Copyright (C) 2001
22 * the Initial Developer. All Rights Reserved.
24 * Contributor(s):
25 * Patrick C. Beard <beard@netscape.com>
26 * Gordon Sheridan <gordon@netscape.com>
27 * Alfred Kayser <alfredkayser@nl.ibm.com>
28 * Darin Fisher <darin@meer.net>
30 * Alternatively, the contents of this file may be used under the terms of
31 * either the GNU General Public License Version 2 or later (the "GPL"), or
32 * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
33 * in which case the provisions of the GPL or the LGPL are applicable instead
34 * of those above. If you wish to allow use of your version of this file only
35 * under the terms of either the GPL or the LGPL, and not to allow others to
36 * use your version of this file under the terms of the MPL, indicate your
37 * decision by deleting the provisions above and replace them with the notice
38 * and other provisions required by the GPL or the LGPL. If you do not delete
39 * the provisions above, a recipient may use your version of this file under
40 * the terms of any one of the MPL, the GPL or the LGPL.
42 * ***** END LICENSE BLOCK ***** */
44 #include "nsDiskCacheMap.h"
45 #include "nsDiskCacheBinding.h"
46 #include "nsDiskCacheEntry.h"
48 #include "nsCache.h"
50 #include <string.h>
52 #include "nsISerializable.h"
53 #include "nsSerializationHelper.h"
55 /******************************************************************************
56 * nsDiskCacheMap
57 *****************************************************************************/
59 /**
60 * File operations
63 nsresult
64 nsDiskCacheMap::Open(nsILocalFile * cacheDirectory)
66 NS_ENSURE_ARG_POINTER(cacheDirectory);
67 if (mMapFD) return NS_ERROR_ALREADY_INITIALIZED;
69 mCacheDirectory = cacheDirectory; // save a reference for ourselves
71 // create nsILocalFile for _CACHE_MAP_
72 nsresult rv;
73 nsCOMPtr<nsIFile> file;
74 rv = cacheDirectory->Clone(getter_AddRefs(file));
75 nsCOMPtr<nsILocalFile> localFile(do_QueryInterface(file, &rv));
76 NS_ENSURE_SUCCESS(rv, rv);
77 rv = localFile->AppendNative(NS_LITERAL_CSTRING("_CACHE_MAP_"));
78 NS_ENSURE_SUCCESS(rv, rv);
80 // open the file - restricted to user, the data could be confidential
81 rv = localFile->OpenNSPRFileDesc(PR_RDWR | PR_CREATE_FILE, 00600, &mMapFD);
82 NS_ENSURE_SUCCESS(rv, NS_ERROR_FILE_CORRUPTED);
84 PRBool cacheFilesExist = CacheFilesExist();
85 rv = NS_ERROR_FILE_CORRUPTED; // presume the worst
87 // check size of map file
88 PRUint32 mapSize = PR_Available(mMapFD);
89 if (mapSize == 0) { // creating a new _CACHE_MAP_
91 // block files shouldn't exist if we're creating the _CACHE_MAP_
92 if (cacheFilesExist)
93 goto error_exit;
95 // create the file - initialize in memory
96 memset(&mHeader, 0, sizeof(nsDiskCacheHeader));
97 mHeader.mVersion = nsDiskCache::kCurrentVersion;
98 mHeader.mRecordCount = kMinRecordCount;
99 mRecordArray = (nsDiskCacheRecord *)
100 PR_CALLOC(mHeader.mRecordCount * sizeof(nsDiskCacheRecord));
101 if (!mRecordArray) {
102 rv = NS_ERROR_OUT_OF_MEMORY;
103 goto error_exit;
105 } else if (mapSize >= sizeof(nsDiskCacheHeader)) { // read existing _CACHE_MAP_
107 // if _CACHE_MAP_ exists, so should the block files
108 if (!cacheFilesExist)
109 goto error_exit;
111 // read the header
112 PRUint32 bytesRead = PR_Read(mMapFD, &mHeader, sizeof(nsDiskCacheHeader));
113 if (sizeof(nsDiskCacheHeader) != bytesRead) goto error_exit;
114 mHeader.Unswap();
116 if (mHeader.mIsDirty || (mHeader.mVersion != nsDiskCache::kCurrentVersion))
117 goto error_exit;
119 PRUint32 recordArraySize =
120 mHeader.mRecordCount * sizeof(nsDiskCacheRecord);
121 if (mapSize < recordArraySize + sizeof(nsDiskCacheHeader))
122 goto error_exit;
124 // Get the space for the records
125 mRecordArray = (nsDiskCacheRecord *) PR_MALLOC(recordArraySize);
126 if (!mRecordArray) {
127 rv = NS_ERROR_OUT_OF_MEMORY;
128 goto error_exit;
131 // Read the records
132 bytesRead = PR_Read(mMapFD, mRecordArray, recordArraySize);
133 if (bytesRead < recordArraySize)
134 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 goto error_exit;
151 } else {
152 goto error_exit;
155 rv = OpenBlockFiles();
156 if (NS_FAILED(rv)) goto error_exit;
158 // set dirty bit and flush header
159 mHeader.mIsDirty = PR_TRUE;
160 rv = FlushHeader();
161 if (NS_FAILED(rv)) goto error_exit;
163 return NS_OK;
165 error_exit:
166 (void) Close(PR_FALSE);
168 return rv;
172 nsresult
173 nsDiskCacheMap::Close(PRBool flush)
175 nsresult rv = NS_OK;
177 // If cache map file and its block files are still open, close them
178 if (mMapFD) {
179 // close block files
180 rv = CloseBlockFiles(flush);
181 if (NS_SUCCEEDED(rv) && flush && mRecordArray) {
182 // write the map records
183 rv = FlushRecords(PR_FALSE); // don't bother swapping buckets back
184 if (NS_SUCCEEDED(rv)) {
185 // clear dirty bit
186 mHeader.mIsDirty = PR_FALSE;
187 rv = FlushHeader();
190 if ((PR_Close(mMapFD) != PR_SUCCESS) && (NS_SUCCEEDED(rv)))
191 rv = NS_ERROR_UNEXPECTED;
193 mMapFD = nsnull;
195 PR_FREEIF(mRecordArray);
196 PR_FREEIF(mBuffer);
197 mBufferSize = 0;
198 return rv;
202 nsresult
203 nsDiskCacheMap::Trim()
205 nsresult rv, rv2 = NS_OK;
206 for (int i=0; i < 3; ++i) {
207 rv = mBlockFile[i].Trim();
208 if (NS_FAILED(rv)) rv2 = rv; // if one or more errors, report at least one
210 // Try to shrink the records array
211 rv = ShrinkRecords();
212 if (NS_FAILED(rv)) rv2 = rv; // if one or more errors, report at least one
213 return rv2;
217 nsresult
218 nsDiskCacheMap::FlushHeader()
220 if (!mMapFD) return NS_ERROR_NOT_AVAILABLE;
222 // seek to beginning of cache map
223 PRInt32 filePos = PR_Seek(mMapFD, 0, PR_SEEK_SET);
224 if (filePos != 0) return NS_ERROR_UNEXPECTED;
226 // write the header
227 mHeader.Swap();
228 PRInt32 bytesWritten = PR_Write(mMapFD, &mHeader, sizeof(nsDiskCacheHeader));
229 mHeader.Unswap();
230 if (sizeof(nsDiskCacheHeader) != bytesWritten) {
231 return NS_ERROR_UNEXPECTED;
234 return NS_OK;
238 nsresult
239 nsDiskCacheMap::FlushRecords(PRBool unswap)
241 if (!mMapFD) return NS_ERROR_NOT_AVAILABLE;
243 // seek to beginning of buckets
244 PRInt32 filePos = PR_Seek(mMapFD, sizeof(nsDiskCacheHeader), PR_SEEK_SET);
245 if (filePos != sizeof(nsDiskCacheHeader))
246 return NS_ERROR_UNEXPECTED;
248 #if defined(IS_LITTLE_ENDIAN)
249 // Swap each record
250 for (PRInt32 i = 0; i < mHeader.mRecordCount; ++i) {
251 if (mRecordArray[i].HashNumber())
252 mRecordArray[i].Swap();
254 #endif
256 PRInt32 recordArraySize = sizeof(nsDiskCacheRecord) * mHeader.mRecordCount;
258 PRInt32 bytesWritten = PR_Write(mMapFD, mRecordArray, recordArraySize);
259 if (bytesWritten != recordArraySize)
260 return NS_ERROR_UNEXPECTED;
262 #if defined(IS_LITTLE_ENDIAN)
263 if (unswap) {
264 // Unswap each record
265 for (PRInt32 i = 0; i < mHeader.mRecordCount; ++i) {
266 if (mRecordArray[i].HashNumber())
267 mRecordArray[i].Unswap();
270 #endif
272 return NS_OK;
277 * Record operations
280 PRUint32
281 nsDiskCacheMap::GetBucketRank(PRUint32 bucketIndex, PRUint32 targetRank)
283 nsDiskCacheRecord * records = GetFirstRecordInBucket(bucketIndex);
284 PRUint32 rank = 0;
286 for (int i = mHeader.mBucketUsage[bucketIndex]-1; i >= 0; i--) {
287 if ((rank < records[i].EvictionRank()) &&
288 ((targetRank == 0) || (records[i].EvictionRank() < targetRank)))
289 rank = records[i].EvictionRank();
291 return rank;
294 nsresult
295 nsDiskCacheMap::GrowRecords()
297 if (mHeader.mRecordCount >= kMaxRecordCount)
298 return NS_OK;
299 CACHE_LOG_DEBUG(("CACHE: GrowRecords\n"));
301 // Resize the record array
302 PRUint32 newCount = mHeader.mRecordCount << 1;
303 if (newCount > kMaxRecordCount)
304 newCount = kMaxRecordCount;
305 nsDiskCacheRecord *newArray = (nsDiskCacheRecord *)
306 PR_REALLOC(mRecordArray, newCount * sizeof(nsDiskCacheRecord));
307 if (!newArray)
308 return NS_ERROR_OUT_OF_MEMORY;
310 // Space out the buckets
311 PRUint32 oldRecordsPerBucket = GetRecordsPerBucket();
312 PRUint32 newRecordsPerBucket = newCount / kBuckets;
313 // Work from back to space out each bucket to the new array
314 for (int bucketIndex = kBuckets - 1; bucketIndex >= 0; --bucketIndex) {
315 // Move bucket
316 nsDiskCacheRecord *newRecords = newArray + bucketIndex * newRecordsPerBucket;
317 const PRUint32 count = mHeader.mBucketUsage[bucketIndex];
318 memmove(newRecords,
319 newArray + bucketIndex * oldRecordsPerBucket,
320 count * sizeof(nsDiskCacheRecord));
321 // Clear the new empty entries
322 for (PRUint32 i = count; i < newRecordsPerBucket; ++i)
323 newRecords[i].SetHashNumber(0);
326 // Set as the new record array
327 mRecordArray = newArray;
328 mHeader.mRecordCount = newCount;
329 return NS_OK;
332 nsresult
333 nsDiskCacheMap::ShrinkRecords()
335 if (mHeader.mRecordCount <= kMinRecordCount)
336 return NS_OK;
337 CACHE_LOG_DEBUG(("CACHE: ShrinkRecords\n"));
339 // Verify if we can shrink the record array: all buckets must be less than
340 // 1/2 filled
341 PRUint32 maxUsage = 0, bucketIndex;
342 for (bucketIndex = 0; bucketIndex < kBuckets; ++bucketIndex) {
343 if (maxUsage < mHeader.mBucketUsage[bucketIndex])
344 maxUsage = mHeader.mBucketUsage[bucketIndex];
346 // Determine new bucket size, halve size until maxUsage
347 PRUint32 oldRecordsPerBucket = GetRecordsPerBucket();
348 PRUint32 newRecordsPerBucket = oldRecordsPerBucket;
349 while (maxUsage < (newRecordsPerBucket >> 1))
350 newRecordsPerBucket >>= 1;
351 if (newRecordsPerBucket < kMinRecordCount)
352 newRecordsPerBucket = kMinRecordCount;
353 if (newRecordsPerBucket == oldRecordsPerBucket)
354 return NS_OK;
355 // Move the buckets close to each other
356 for (bucketIndex = 0; bucketIndex < kBuckets; ++bucketIndex) {
357 // Move bucket
358 memmove(mRecordArray + bucketIndex * newRecordsPerBucket,
359 mRecordArray + bucketIndex * oldRecordsPerBucket,
360 mHeader.mBucketUsage[bucketIndex] * sizeof(nsDiskCacheRecord));
363 // Shrink the record array memory block itself
364 PRUint32 newCount = newRecordsPerBucket * kBuckets;
365 nsDiskCacheRecord* newArray = (nsDiskCacheRecord *)
366 PR_REALLOC(mRecordArray, newCount * sizeof(nsDiskCacheRecord));
367 if (!newArray)
368 return NS_ERROR_OUT_OF_MEMORY;
370 // Set as the new record array
371 mRecordArray = newArray;
372 mHeader.mRecordCount = newCount;
373 return NS_OK;
376 nsresult
377 nsDiskCacheMap::AddRecord( nsDiskCacheRecord * mapRecord,
378 nsDiskCacheRecord * oldRecord)
380 CACHE_LOG_DEBUG(("CACHE: AddRecord [%x]\n", mapRecord->HashNumber()));
382 const PRUint32 hashNumber = mapRecord->HashNumber();
383 const PRUint32 bucketIndex = GetBucketIndex(hashNumber);
384 const PRUint32 count = mHeader.mBucketUsage[bucketIndex];
386 oldRecord->SetHashNumber(0); // signify no record
388 if (count == GetRecordsPerBucket()) {
389 // Ignore failure to grow the record space, we will then reuse old records
390 GrowRecords();
393 nsDiskCacheRecord * records = GetFirstRecordInBucket(bucketIndex);
394 if (count < GetRecordsPerBucket()) {
395 // stick the new record at the end
396 records[count] = *mapRecord;
397 mHeader.mEntryCount++;
398 mHeader.mBucketUsage[bucketIndex]++;
399 if (mHeader.mEvictionRank[bucketIndex] < mapRecord->EvictionRank())
400 mHeader.mEvictionRank[bucketIndex] = mapRecord->EvictionRank();
401 } else {
402 // Find the record with the highest eviction rank
403 nsDiskCacheRecord * mostEvictable = &records[0];
404 for (int i = count-1; i > 0; i--) {
405 if (records[i].EvictionRank() > mostEvictable->EvictionRank())
406 mostEvictable = &records[i];
408 *oldRecord = *mostEvictable; // i == GetRecordsPerBucket(), so
409 // evict the mostEvictable
410 *mostEvictable = *mapRecord; // replace it with the new record
411 // check if we need to update mostEvictable entry in header
412 if (mHeader.mEvictionRank[bucketIndex] < mapRecord->EvictionRank())
413 mHeader.mEvictionRank[bucketIndex] = mapRecord->EvictionRank();
414 if (oldRecord->EvictionRank() >= mHeader.mEvictionRank[bucketIndex])
415 mHeader.mEvictionRank[bucketIndex] = GetBucketRank(bucketIndex, 0);
418 NS_ASSERTION(mHeader.mEvictionRank[bucketIndex] == GetBucketRank(bucketIndex, 0),
419 "eviction rank out of sync");
420 return NS_OK;
424 nsresult
425 nsDiskCacheMap::UpdateRecord( nsDiskCacheRecord * mapRecord)
427 CACHE_LOG_DEBUG(("CACHE: UpdateRecord [%x]\n", mapRecord->HashNumber()));
429 const PRUint32 hashNumber = mapRecord->HashNumber();
430 const PRUint32 bucketIndex = GetBucketIndex(hashNumber);
431 nsDiskCacheRecord * records = GetFirstRecordInBucket(bucketIndex);
433 for (int i = mHeader.mBucketUsage[bucketIndex]-1; i >= 0; i--) {
434 if (records[i].HashNumber() == hashNumber) {
435 const PRUint32 oldRank = records[i].EvictionRank();
437 // stick the new record here
438 records[i] = *mapRecord;
440 // update eviction rank in header if necessary
441 if (mHeader.mEvictionRank[bucketIndex] < mapRecord->EvictionRank())
442 mHeader.mEvictionRank[bucketIndex] = mapRecord->EvictionRank();
443 else if (mHeader.mEvictionRank[bucketIndex] == oldRank)
444 mHeader.mEvictionRank[bucketIndex] = GetBucketRank(bucketIndex, 0);
446 NS_ASSERTION(mHeader.mEvictionRank[bucketIndex] == GetBucketRank(bucketIndex, 0),
447 "eviction rank out of sync");
448 return NS_OK;
451 NS_NOTREACHED("record not found");
452 return NS_ERROR_UNEXPECTED;
456 nsresult
457 nsDiskCacheMap::FindRecord( PRUint32 hashNumber, nsDiskCacheRecord * result)
459 const PRUint32 bucketIndex = GetBucketIndex(hashNumber);
460 nsDiskCacheRecord * records = GetFirstRecordInBucket(bucketIndex);
462 for (int i = mHeader.mBucketUsage[bucketIndex]-1; i >= 0; i--) {
463 if (records[i].HashNumber() == hashNumber) {
464 *result = records[i]; // copy the record
465 NS_ASSERTION(result->ValidRecord(), "bad cache map record");
466 return NS_OK;
469 return NS_ERROR_CACHE_KEY_NOT_FOUND;
473 nsresult
474 nsDiskCacheMap::DeleteRecord( nsDiskCacheRecord * mapRecord)
476 CACHE_LOG_DEBUG(("CACHE: DeleteRecord [%x]\n", mapRecord->HashNumber()));
478 const PRUint32 hashNumber = mapRecord->HashNumber();
479 const PRUint32 bucketIndex = GetBucketIndex(hashNumber);
480 nsDiskCacheRecord * records = GetFirstRecordInBucket(bucketIndex);
481 PRUint32 last = mHeader.mBucketUsage[bucketIndex]-1;
483 for (int i = last; i >= 0; i--) {
484 if (records[i].HashNumber() == hashNumber) {
485 // found it, now delete it.
486 PRUint32 evictionRank = records[i].EvictionRank();
487 NS_ASSERTION(evictionRank == mapRecord->EvictionRank(),
488 "evictionRank out of sync");
489 // if not the last record, shift last record into opening
490 records[i] = records[last];
491 records[last].SetHashNumber(0); // clear last record
492 mHeader.mBucketUsage[bucketIndex] = last;
493 mHeader.mEntryCount--;
495 // update eviction rank
496 PRUint32 bucketIndex = GetBucketIndex(mapRecord->HashNumber());
497 if (mHeader.mEvictionRank[bucketIndex] <= evictionRank) {
498 mHeader.mEvictionRank[bucketIndex] = GetBucketRank(bucketIndex, 0);
501 NS_ASSERTION(mHeader.mEvictionRank[bucketIndex] ==
502 GetBucketRank(bucketIndex, 0), "eviction rank out of sync");
503 return NS_OK;
506 return NS_ERROR_UNEXPECTED;
510 PRInt32
511 nsDiskCacheMap::VisitEachRecord(PRUint32 bucketIndex,
512 nsDiskCacheRecordVisitor * visitor,
513 PRUint32 evictionRank)
515 PRInt32 rv = kVisitNextRecord;
516 PRUint32 count = mHeader.mBucketUsage[bucketIndex];
517 nsDiskCacheRecord * records = GetFirstRecordInBucket(bucketIndex);
519 // call visitor for each entry (matching any eviction rank)
520 for (int i = count-1; i >= 0; i--) {
521 if (evictionRank > records[i].EvictionRank()) continue;
523 rv = visitor->VisitRecord(&records[i]);
524 if (rv == kStopVisitingRecords)
525 break; // Stop visiting records
527 if (rv == kDeleteRecordAndContinue) {
528 --count;
529 records[i] = records[count];
530 records[count].SetHashNumber(0);
534 if (mHeader.mBucketUsage[bucketIndex] - count != 0) {
535 mHeader.mEntryCount -= mHeader.mBucketUsage[bucketIndex] - count;
536 mHeader.mBucketUsage[bucketIndex] = count;
537 // recalc eviction rank
538 mHeader.mEvictionRank[bucketIndex] = GetBucketRank(bucketIndex, 0);
540 NS_ASSERTION(mHeader.mEvictionRank[bucketIndex] ==
541 GetBucketRank(bucketIndex, 0), "eviction rank out of sync");
543 return rv;
548 * VisitRecords
550 * Visit every record in cache map in the most convenient order
552 nsresult
553 nsDiskCacheMap::VisitRecords( nsDiskCacheRecordVisitor * visitor)
555 for (int bucketIndex = 0; bucketIndex < kBuckets; ++bucketIndex) {
556 if (VisitEachRecord(bucketIndex, visitor, 0) == kStopVisitingRecords)
557 break;
559 return NS_OK;
564 * EvictRecords
566 * Just like VisitRecords, but visits the records in order of their eviction rank
568 nsresult
569 nsDiskCacheMap::EvictRecords( nsDiskCacheRecordVisitor * visitor)
571 PRUint32 tempRank[kBuckets];
572 int bucketIndex = 0;
574 // copy eviction rank array
575 for (bucketIndex = 0; bucketIndex < kBuckets; ++bucketIndex)
576 tempRank[bucketIndex] = mHeader.mEvictionRank[bucketIndex];
578 // Maximum number of iterations determined by number of records
579 // as a safety limiter for the loop
580 for (int n = 0; n < mHeader.mEntryCount; ++n) {
582 // find bucket with highest eviction rank
583 PRUint32 rank = 0;
584 for (int i = 0; i < kBuckets; ++i) {
585 if (rank < tempRank[i]) {
586 rank = tempRank[i];
587 bucketIndex = i;
591 if (rank == 0) break; // we've examined all the records
593 // visit records in bucket with eviction ranks >= target eviction rank
594 if (VisitEachRecord(bucketIndex, visitor, rank) == kStopVisitingRecords)
595 break;
597 // find greatest rank less than 'rank'
598 tempRank[bucketIndex] = GetBucketRank(bucketIndex, rank);
600 return NS_OK;
605 nsresult
606 nsDiskCacheMap::OpenBlockFiles()
608 // create nsILocalFile for block file
609 nsCOMPtr<nsILocalFile> blockFile;
610 nsresult rv = NS_OK;
612 for (int i = 0; i < 3; ++i) {
613 rv = GetBlockFileForIndex(i, getter_AddRefs(blockFile));
614 if (NS_FAILED(rv)) break;
616 PRUint32 blockSize = GetBlockSizeForIndex(i+1); // +1 to match file selectors 1,2,3
617 rv = mBlockFile[i].Open(blockFile, blockSize);
618 if (NS_FAILED(rv)) break;
620 // close all files in case of any error
621 if (NS_FAILED(rv))
622 (void)CloseBlockFiles(PR_FALSE); // we already have an error to report
624 return rv;
628 nsresult
629 nsDiskCacheMap::CloseBlockFiles(PRBool flush)
631 nsresult rv, rv2 = NS_OK;
632 for (int i=0; i < 3; ++i) {
633 rv = mBlockFile[i].Close(flush);
634 if (NS_FAILED(rv)) rv2 = rv; // if one or more errors, report at least one
636 return rv2;
640 PRBool
641 nsDiskCacheMap::CacheFilesExist()
643 nsCOMPtr<nsILocalFile> blockFile;
644 nsresult rv;
646 for (int i = 0; i < 3; ++i) {
647 PRBool exists;
648 rv = GetBlockFileForIndex(i, getter_AddRefs(blockFile));
649 if (NS_FAILED(rv)) return PR_FALSE;
651 rv = blockFile->Exists(&exists);
652 if (NS_FAILED(rv) || !exists) return PR_FALSE;
655 return PR_TRUE;
659 nsDiskCacheEntry *
660 nsDiskCacheMap::ReadDiskCacheEntry(nsDiskCacheRecord * record)
662 CACHE_LOG_DEBUG(("CACHE: ReadDiskCacheEntry [%x]\n", record->HashNumber()));
664 nsresult rv = NS_ERROR_UNEXPECTED;
665 nsDiskCacheEntry * diskEntry = nsnull;
666 PRUint32 metaFile = record->MetaFile();
667 PRInt32 bytesRead = 0;
669 if (!record->MetaLocationInitialized()) return nsnull;
671 if (metaFile == 0) { // entry/metadata stored in separate file
672 // open and read the file
673 nsCOMPtr<nsILocalFile> file;
674 rv = GetLocalFileForDiskCacheRecord(record, nsDiskCache::kMetaData, getter_AddRefs(file));
675 NS_ENSURE_SUCCESS(rv, nsnull);
677 PRFileDesc * fd = nsnull;
678 // open the file - restricted to user, the data could be confidential
679 rv = file->OpenNSPRFileDesc(PR_RDONLY, 00600, &fd);
680 NS_ENSURE_SUCCESS(rv, nsnull);
682 PRInt32 fileSize = PR_Available(fd);
683 if (fileSize < 0) {
684 // an error occurred. We could call PR_GetError(), but how would that help?
685 rv = NS_ERROR_UNEXPECTED;
686 } else {
687 rv = EnsureBuffer(fileSize);
688 if (NS_SUCCEEDED(rv)) {
689 bytesRead = PR_Read(fd, mBuffer, fileSize);
690 if (bytesRead < fileSize) {
691 rv = NS_ERROR_UNEXPECTED;
695 PR_Close(fd);
696 NS_ENSURE_SUCCESS(rv, nsnull);
698 } else if (metaFile < 4) { // XXX magic number: use constant
699 // entry/metadata stored in cache block file
701 // allocate buffer
702 PRUint32 blockCount = record->MetaBlockCount();
703 bytesRead = blockCount * GetBlockSizeForIndex(metaFile);
705 rv = EnsureBuffer(bytesRead);
706 NS_ENSURE_SUCCESS(rv, nsnull);
708 // read diskEntry, note when the blocks are at the end of file,
709 // bytesRead may be less than blockSize*blockCount.
710 // But the bytesRead should at least agree with the real disk entry size.
711 rv = mBlockFile[metaFile - 1].ReadBlocks(mBuffer,
712 record->MetaStartBlock(),
713 blockCount,
714 &bytesRead);
715 NS_ENSURE_SUCCESS(rv, nsnull);
717 diskEntry = (nsDiskCacheEntry *)mBuffer;
718 diskEntry->Unswap(); // disk to memory
719 // Check if calculated size agrees with bytesRead
720 if (bytesRead < 0 || (PRUint32)bytesRead < diskEntry->Size())
721 return nsnull;
723 // Return the buffer containing the diskEntry structure
724 return diskEntry;
729 * CreateDiskCacheEntry(nsCacheEntry * entry)
731 * Prepare an nsCacheEntry for writing to disk
733 nsDiskCacheEntry *
734 nsDiskCacheMap::CreateDiskCacheEntry(nsDiskCacheBinding * binding,
735 PRUint32 * aSize)
737 nsCacheEntry * entry = binding->mCacheEntry;
738 if (!entry) return nsnull;
740 // Store security info, if it is serializable
741 nsCOMPtr<nsISerializable> serializable =
742 do_QueryInterface(entry->SecurityInfo());
743 if (serializable) {
744 nsCString info;
745 NS_SerializeToString(serializable, info);
746 entry->SetMetaDataElement("security-info", info.get());
749 PRUint32 keySize = entry->Key()->Length() + 1;
750 PRUint32 metaSize = entry->MetaDataSize();
751 PRUint32 size = sizeof(nsDiskCacheEntry) + keySize + metaSize;
753 if (aSize) *aSize = size;
755 nsresult rv = EnsureBuffer(size);
756 if (NS_FAILED(rv)) return nsnull;
758 nsDiskCacheEntry *diskEntry = (nsDiskCacheEntry *)mBuffer;
759 diskEntry->mHeaderVersion = nsDiskCache::kCurrentVersion;
760 diskEntry->mMetaLocation = binding->mRecord.MetaLocation();
761 diskEntry->mFetchCount = entry->FetchCount();
762 diskEntry->mLastFetched = entry->LastFetched();
763 diskEntry->mLastModified = entry->LastModified();
764 diskEntry->mExpirationTime = entry->ExpirationTime();
765 diskEntry->mDataSize = entry->DataSize();
766 diskEntry->mKeySize = keySize;
767 diskEntry->mMetaDataSize = metaSize;
769 memcpy(diskEntry->Key(), entry->Key()->get(), keySize);
771 rv = entry->FlattenMetaData(diskEntry->MetaData(), metaSize);
772 if (NS_FAILED(rv)) return nsnull;
774 return diskEntry;
778 nsresult
779 nsDiskCacheMap::WriteDiskCacheEntry(nsDiskCacheBinding * binding)
781 CACHE_LOG_DEBUG(("CACHE: WriteDiskCacheEntry [%x]\n",
782 binding->mRecord.HashNumber()));
784 nsresult rv = NS_OK;
785 PRUint32 size;
786 nsDiskCacheEntry * diskEntry = CreateDiskCacheEntry(binding, &size);
787 if (!diskEntry) return NS_ERROR_UNEXPECTED;
789 PRUint32 fileIndex = CalculateFileIndex(size);
791 // Deallocate old storage if necessary
792 if (binding->mRecord.MetaLocationInitialized()) {
793 // we have existing storage
795 if ((binding->mRecord.MetaFile() == 0) &&
796 (fileIndex == 0)) { // keeping the separate file
797 // just decrement total
798 // XXX if bindRecord.MetaFileSize == USHRT_MAX, stat the file to see how big it is
799 DecrementTotalSize(binding->mRecord.MetaFileSize());
800 NS_ASSERTION(binding->mRecord.MetaFileGeneration() == binding->mGeneration,
801 "generations out of sync");
802 } else {
803 rv = DeleteStorage(&binding->mRecord, nsDiskCache::kMetaData);
804 NS_ENSURE_SUCCESS(rv, rv);
808 binding->mRecord.SetEvictionRank(ULONG_MAX - SecondsFromPRTime(PR_Now()));
810 if (fileIndex == 0) {
811 // Write entry data to separate file
812 PRUint32 metaFileSizeK = ((size + 0x03FF) >> 10); // round up to nearest 1k
813 nsCOMPtr<nsILocalFile> localFile;
815 // XXX handle metaFileSizeK > USHRT_MAX
816 binding->mRecord.SetMetaFileGeneration(binding->mGeneration);
817 binding->mRecord.SetMetaFileSize(metaFileSizeK);
818 rv = UpdateRecord(&binding->mRecord);
819 NS_ENSURE_SUCCESS(rv, rv);
821 rv = GetLocalFileForDiskCacheRecord(&binding->mRecord,
822 nsDiskCache::kMetaData,
823 getter_AddRefs(localFile));
824 NS_ENSURE_SUCCESS(rv, rv);
826 // open the file
827 PRFileDesc * fd;
828 // open the file - restricted to user, the data could be confidential
829 rv = localFile->OpenNSPRFileDesc(PR_RDWR | PR_TRUNCATE | PR_CREATE_FILE, 00600, &fd);
830 NS_ENSURE_SUCCESS(rv, rv);
832 // write the file
833 diskEntry->Swap();
834 PRInt32 bytesWritten = PR_Write(fd, diskEntry, size);
836 PRStatus err = PR_Close(fd);
837 if ((bytesWritten != (PRInt32)size) || (err != PR_SUCCESS)) {
838 return NS_ERROR_UNEXPECTED;
840 // XXX handle metaFileSizeK == USHRT_MAX
841 IncrementTotalSize(metaFileSizeK);
843 } else {
844 PRUint32 blockSize = GetBlockSizeForIndex(fileIndex);
845 PRUint32 blocks = ((size - 1) / blockSize) + 1;
847 // write entry data to disk cache block file
848 diskEntry->Swap();
849 PRInt32 startBlock;
850 rv = mBlockFile[fileIndex - 1].WriteBlocks(diskEntry, size, blocks, &startBlock);
851 NS_ENSURE_SUCCESS(rv, rv);
853 // update binding and cache map record
854 binding->mRecord.SetMetaBlocks(fileIndex, startBlock, blocks);
855 rv = UpdateRecord(&binding->mRecord);
856 NS_ENSURE_SUCCESS(rv, rv);
857 // XXX we should probably write out bucket ourselves
859 IncrementTotalSize(blocks, blockSize);
862 return rv;
866 nsresult
867 nsDiskCacheMap::ReadDataCacheBlocks(nsDiskCacheBinding * binding, char * buffer, PRUint32 size)
869 CACHE_LOG_DEBUG(("CACHE: ReadDataCacheBlocks [%x size=%u]\n",
870 binding->mRecord.HashNumber(), size));
872 PRUint32 fileIndex = binding->mRecord.DataFile();
873 PRInt32 readSize = size;
875 nsresult rv = mBlockFile[fileIndex - 1].ReadBlocks(buffer,
876 binding->mRecord.DataStartBlock(),
877 binding->mRecord.DataBlockCount(),
878 &readSize);
879 NS_ENSURE_SUCCESS(rv, rv);
880 if (readSize < (PRInt32)size) {
881 rv = NS_ERROR_UNEXPECTED;
883 return rv;
887 nsresult
888 nsDiskCacheMap::WriteDataCacheBlocks(nsDiskCacheBinding * binding, char * buffer, PRUint32 size)
890 CACHE_LOG_DEBUG(("CACHE: WriteDataCacheBlocks [%x size=%u]\n",
891 binding->mRecord.HashNumber(), size));
893 nsresult rv = NS_OK;
895 // determine block file & number of blocks
896 PRUint32 fileIndex = CalculateFileIndex(size);
897 PRUint32 blockSize = GetBlockSizeForIndex(fileIndex);
898 PRUint32 blockCount = 0;
899 PRInt32 startBlock = 0;
901 if (size > 0) {
902 blockCount = ((size - 1) / blockSize) + 1;
904 rv = mBlockFile[fileIndex - 1].WriteBlocks(buffer, size, blockCount, &startBlock);
905 NS_ENSURE_SUCCESS(rv, rv);
907 IncrementTotalSize(blockCount, blockSize);
910 // update binding and cache map record
911 binding->mRecord.SetDataBlocks(fileIndex, startBlock, blockCount);
912 if (!binding->mDoomed) {
913 rv = UpdateRecord(&binding->mRecord);
915 return rv;
919 nsresult
920 nsDiskCacheMap::DeleteStorage(nsDiskCacheRecord * record)
922 nsresult rv1 = DeleteStorage(record, nsDiskCache::kData);
923 nsresult rv2 = DeleteStorage(record, nsDiskCache::kMetaData);
924 return NS_FAILED(rv1) ? rv1 : rv2;
928 nsresult
929 nsDiskCacheMap::DeleteStorage(nsDiskCacheRecord * record, PRBool metaData)
931 CACHE_LOG_DEBUG(("CACHE: DeleteStorage [%x %u]\n", record->HashNumber(),
932 metaData));
934 nsresult rv = NS_ERROR_UNEXPECTED;
935 PRUint32 fileIndex = metaData ? record->MetaFile() : record->DataFile();
936 nsCOMPtr<nsIFile> file;
938 if (fileIndex == 0) {
939 // delete the file
940 PRUint32 sizeK = metaData ? record->MetaFileSize() : record->DataFileSize();
941 // XXX if sizeK == USHRT_MAX, stat file for actual size
943 rv = GetFileForDiskCacheRecord(record, metaData, getter_AddRefs(file));
944 if (NS_SUCCEEDED(rv)) {
945 rv = file->Remove(PR_FALSE); // false == non-recursive
947 DecrementTotalSize(sizeK);
949 } else if (fileIndex < 4) {
950 // deallocate blocks
951 PRUint32 startBlock = metaData ? record->MetaStartBlock() : record->DataStartBlock();
952 PRUint32 blockCount = metaData ? record->MetaBlockCount() : record->DataBlockCount();
954 rv = mBlockFile[fileIndex - 1].DeallocateBlocks(startBlock, blockCount);
955 DecrementTotalSize(blockCount, GetBlockSizeForIndex(fileIndex));
957 if (metaData) record->ClearMetaLocation();
958 else record->ClearDataLocation();
960 return rv;
964 nsresult
965 nsDiskCacheMap::GetFileForDiskCacheRecord(nsDiskCacheRecord * record,
966 PRBool meta,
967 nsIFile ** result)
969 if (!mCacheDirectory) return NS_ERROR_NOT_AVAILABLE;
971 nsCOMPtr<nsIFile> file;
972 nsresult rv = mCacheDirectory->Clone(getter_AddRefs(file));
973 if (NS_FAILED(rv)) return rv;
975 PRInt16 generation = record->Generation();
976 char name[32];
977 ::sprintf(name, "%08X%c%02X", record->HashNumber(), (meta ? 'm' : 'd'), generation);
978 rv = file->AppendNative(nsDependentCString(name));
979 if (NS_FAILED(rv)) return rv;
981 NS_IF_ADDREF(*result = file);
982 return rv;
986 nsresult
987 nsDiskCacheMap::GetLocalFileForDiskCacheRecord(nsDiskCacheRecord * record,
988 PRBool meta,
989 nsILocalFile ** result)
991 nsCOMPtr<nsIFile> file;
992 nsresult rv = GetFileForDiskCacheRecord(record, meta, getter_AddRefs(file));
993 if (NS_FAILED(rv)) return rv;
995 nsCOMPtr<nsILocalFile> localFile = do_QueryInterface(file, &rv);
996 if (NS_FAILED(rv)) return rv;
998 NS_IF_ADDREF(*result = localFile);
999 return rv;
1003 nsresult
1004 nsDiskCacheMap::GetBlockFileForIndex(PRUint32 index, nsILocalFile ** result)
1006 if (!mCacheDirectory) return NS_ERROR_NOT_AVAILABLE;
1008 nsCOMPtr<nsIFile> file;
1009 nsresult rv = mCacheDirectory->Clone(getter_AddRefs(file));
1010 if (NS_FAILED(rv)) return rv;
1012 char name[32];
1013 ::sprintf(name, "_CACHE_%03d_", index + 1);
1014 rv = file->AppendNative(nsDependentCString(name));
1015 if (NS_FAILED(rv)) return rv;
1017 nsCOMPtr<nsILocalFile> localFile = do_QueryInterface(file, &rv);
1018 NS_IF_ADDREF(*result = localFile);
1020 return rv;
1024 PRUint32
1025 nsDiskCacheMap::CalculateFileIndex(PRUint32 size)
1027 if (size <= 1024) return 1;
1028 if (size <= 4096) return 2;
1029 if (size <= 16384) return 3;
1030 return 0;
1033 nsresult
1034 nsDiskCacheMap::EnsureBuffer(PRUint32 bufSize)
1036 if (mBufferSize < bufSize) {
1037 char * buf = (char *)realloc(mBuffer, bufSize);
1038 if (!buf) {
1039 mBufferSize = 0;
1040 return NS_ERROR_OUT_OF_MEMORY;
1042 mBuffer = buf;
1043 mBufferSize = bufSize;
1045 return NS_OK;