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
16 * The Original Code is nsDiskCacheMap.cpp, released
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.
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"
52 #include "nsISerializable.h"
53 #include "nsSerializationHelper.h"
55 /******************************************************************************
57 *****************************************************************************/
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_
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_
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
));
102 rv
= NS_ERROR_OUT_OF_MEMORY
;
105 } else if (mapSize
>= sizeof(nsDiskCacheHeader
)) { // read existing _CACHE_MAP_
107 // if _CACHE_MAP_ exists, so should the block files
108 if (!cacheFilesExist
)
112 PRUint32 bytesRead
= PR_Read(mMapFD
, &mHeader
, sizeof(nsDiskCacheHeader
));
113 if (sizeof(nsDiskCacheHeader
) != bytesRead
) goto error_exit
;
116 if (mHeader
.mIsDirty
|| (mHeader
.mVersion
!= nsDiskCache::kCurrentVersion
))
119 PRUint32 recordArraySize
=
120 mHeader
.mRecordCount
* sizeof(nsDiskCacheRecord
);
121 if (mapSize
< recordArraySize
+ sizeof(nsDiskCacheHeader
))
124 // Get the space for the records
125 mRecordArray
= (nsDiskCacheRecord
*) PR_MALLOC(recordArraySize
);
127 rv
= NS_ERROR_OUT_OF_MEMORY
;
132 bytesRead
= PR_Read(mMapFD
, mRecordArray
, recordArraySize
);
133 if (bytesRead
< recordArraySize
)
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
)
155 rv
= OpenBlockFiles();
156 if (NS_FAILED(rv
)) goto error_exit
;
158 // set dirty bit and flush header
159 mHeader
.mIsDirty
= PR_TRUE
;
161 if (NS_FAILED(rv
)) goto error_exit
;
166 (void) Close(PR_FALSE
);
173 nsDiskCacheMap::Close(PRBool flush
)
177 // If cache map file and its block files are still open, close them
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
)) {
186 mHeader
.mIsDirty
= PR_FALSE
;
190 if ((PR_Close(mMapFD
) != PR_SUCCESS
) && (NS_SUCCEEDED(rv
)))
191 rv
= NS_ERROR_UNEXPECTED
;
195 PR_FREEIF(mRecordArray
);
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
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
;
228 PRInt32 bytesWritten
= PR_Write(mMapFD
, &mHeader
, sizeof(nsDiskCacheHeader
));
230 if (sizeof(nsDiskCacheHeader
) != bytesWritten
) {
231 return NS_ERROR_UNEXPECTED
;
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)
250 for (PRInt32 i
= 0; i
< mHeader
.mRecordCount
; ++i
) {
251 if (mRecordArray
[i
].HashNumber())
252 mRecordArray
[i
].Swap();
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)
264 // Unswap each record
265 for (PRInt32 i
= 0; i
< mHeader
.mRecordCount
; ++i
) {
266 if (mRecordArray
[i
].HashNumber())
267 mRecordArray
[i
].Unswap();
281 nsDiskCacheMap::GetBucketRank(PRUint32 bucketIndex
, PRUint32 targetRank
)
283 nsDiskCacheRecord
* records
= GetFirstRecordInBucket(bucketIndex
);
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();
295 nsDiskCacheMap::GrowRecords()
297 if (mHeader
.mRecordCount
>= kMaxRecordCount
)
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
));
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
) {
316 nsDiskCacheRecord
*newRecords
= newArray
+ bucketIndex
* newRecordsPerBucket
;
317 const PRUint32 count
= mHeader
.mBucketUsage
[bucketIndex
];
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
;
333 nsDiskCacheMap::ShrinkRecords()
335 if (mHeader
.mRecordCount
<= kMinRecordCount
)
337 CACHE_LOG_DEBUG(("CACHE: ShrinkRecords\n"));
339 // Verify if we can shrink the record array: all buckets must be less than
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
)
355 // Move the buckets close to each other
356 for (bucketIndex
= 0; bucketIndex
< kBuckets
; ++bucketIndex
) {
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
));
368 return NS_ERROR_OUT_OF_MEMORY
;
370 // Set as the new record array
371 mRecordArray
= newArray
;
372 mHeader
.mRecordCount
= newCount
;
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
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();
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");
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");
451 NS_NOTREACHED("record not found");
452 return NS_ERROR_UNEXPECTED
;
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");
469 return NS_ERROR_CACHE_KEY_NOT_FOUND
;
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");
506 return NS_ERROR_UNEXPECTED
;
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
) {
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");
550 * Visit every record in cache map in the most convenient order
553 nsDiskCacheMap::VisitRecords( nsDiskCacheRecordVisitor
* visitor
)
555 for (int bucketIndex
= 0; bucketIndex
< kBuckets
; ++bucketIndex
) {
556 if (VisitEachRecord(bucketIndex
, visitor
, 0) == kStopVisitingRecords
)
566 * Just like VisitRecords, but visits the records in order of their eviction rank
569 nsDiskCacheMap::EvictRecords( nsDiskCacheRecordVisitor
* visitor
)
571 PRUint32 tempRank
[kBuckets
];
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
584 for (int i
= 0; i
< kBuckets
; ++i
) {
585 if (rank
< tempRank
[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
)
597 // find greatest rank less than 'rank'
598 tempRank
[bucketIndex
] = GetBucketRank(bucketIndex
, rank
);
606 nsDiskCacheMap::OpenBlockFiles()
608 // create nsILocalFile for block file
609 nsCOMPtr
<nsILocalFile
> blockFile
;
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
622 (void)CloseBlockFiles(PR_FALSE
); // we already have an error to report
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
641 nsDiskCacheMap::CacheFilesExist()
643 nsCOMPtr
<nsILocalFile
> blockFile
;
646 for (int i
= 0; i
< 3; ++i
) {
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
;
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
);
684 // an error occurred. We could call PR_GetError(), but how would that help?
685 rv
= NS_ERROR_UNEXPECTED
;
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
;
696 NS_ENSURE_SUCCESS(rv
, nsnull
);
698 } else if (metaFile
< 4) { // XXX magic number: use constant
699 // entry/metadata stored in cache block file
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(),
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())
723 // Return the buffer containing the diskEntry structure
729 * CreateDiskCacheEntry(nsCacheEntry * entry)
731 * Prepare an nsCacheEntry for writing to disk
734 nsDiskCacheMap::CreateDiskCacheEntry(nsDiskCacheBinding
* binding
,
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());
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
;
779 nsDiskCacheMap::WriteDiskCacheEntry(nsDiskCacheBinding
* binding
)
781 CACHE_LOG_DEBUG(("CACHE: WriteDiskCacheEntry [%x]\n",
782 binding
->mRecord
.HashNumber()));
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");
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
);
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
);
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
);
844 PRUint32 blockSize
= GetBlockSizeForIndex(fileIndex
);
845 PRUint32 blocks
= ((size
- 1) / blockSize
) + 1;
847 // write entry data to disk cache block file
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
);
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(),
879 NS_ENSURE_SUCCESS(rv
, rv
);
880 if (readSize
< (PRInt32
)size
) {
881 rv
= NS_ERROR_UNEXPECTED
;
888 nsDiskCacheMap::WriteDataCacheBlocks(nsDiskCacheBinding
* binding
, char * buffer
, PRUint32 size
)
890 CACHE_LOG_DEBUG(("CACHE: WriteDataCacheBlocks [%x size=%u]\n",
891 binding
->mRecord
.HashNumber(), size
));
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;
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
);
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
;
929 nsDiskCacheMap::DeleteStorage(nsDiskCacheRecord
* record
, PRBool metaData
)
931 CACHE_LOG_DEBUG(("CACHE: DeleteStorage [%x %u]\n", record
->HashNumber(),
934 nsresult rv
= NS_ERROR_UNEXPECTED
;
935 PRUint32 fileIndex
= metaData
? record
->MetaFile() : record
->DataFile();
936 nsCOMPtr
<nsIFile
> file
;
938 if (fileIndex
== 0) {
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) {
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();
965 nsDiskCacheMap::GetFileForDiskCacheRecord(nsDiskCacheRecord
* record
,
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();
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
);
987 nsDiskCacheMap::GetLocalFileForDiskCacheRecord(nsDiskCacheRecord
* record
,
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
);
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
;
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
);
1025 nsDiskCacheMap::CalculateFileIndex(PRUint32 size
)
1027 if (size
<= 1024) return 1;
1028 if (size
<= 4096) return 2;
1029 if (size
<= 16384) return 3;
1034 nsDiskCacheMap::EnsureBuffer(PRUint32 bufSize
)
1036 if (mBufferSize
< bufSize
) {
1037 char * buf
= (char *)realloc(mBuffer
, bufSize
);
1040 return NS_ERROR_OUT_OF_MEMORY
;
1043 mBufferSize
= bufSize
;