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
5 : *
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/
10 : *
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.
15 : *
16 : * The Original Code is nsDiskCacheMap.cpp, released
17 : * March 23, 2001.
18 : *
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.
23 : *
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>
29 : *
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.
41 : *
42 : * ***** END LICENSE BLOCK ***** */
43 :
44 : #include "nsDiskCacheMap.h"
45 : #include "nsDiskCacheBinding.h"
46 : #include "nsDiskCacheEntry.h"
47 :
48 : #include "nsCache.h"
49 :
50 : #include <string.h>
51 : #include "nsPrintfCString.h"
52 :
53 : #include "nsISerializable.h"
54 : #include "nsSerializationHelper.h"
55 :
56 : #include "mozilla/Telemetry.h"
57 :
58 : /******************************************************************************
59 : * nsDiskCacheMap
60 : *****************************************************************************/
61 :
62 : /**
63 : * File operations
64 : */
65 :
66 : nsresult
67 236 : nsDiskCacheMap::Open(nsILocalFile * cacheDirectory)
68 : {
69 236 : NS_ENSURE_ARG_POINTER(cacheDirectory);
70 236 : if (mMapFD) return NS_ERROR_ALREADY_INITIALIZED;
71 :
72 236 : mCacheDirectory = cacheDirectory; // save a reference for ourselves
73 :
74 : // create nsILocalFile for _CACHE_MAP_
75 : nsresult rv;
76 472 : nsCOMPtr<nsIFile> file;
77 236 : rv = cacheDirectory->Clone(getter_AddRefs(file));
78 472 : nsCOMPtr<nsILocalFile> localFile(do_QueryInterface(file, &rv));
79 236 : NS_ENSURE_SUCCESS(rv, rv);
80 236 : rv = localFile->AppendNative(NS_LITERAL_CSTRING("_CACHE_MAP_"));
81 236 : NS_ENSURE_SUCCESS(rv, rv);
82 :
83 : // open the file - restricted to user, the data could be confidential
84 236 : rv = localFile->OpenNSPRFileDesc(PR_RDWR | PR_CREATE_FILE, 00600, &mMapFD);
85 236 : NS_ENSURE_SUCCESS(rv, NS_ERROR_FILE_CORRUPTED);
86 :
87 236 : bool cacheFilesExist = CacheFilesExist();
88 236 : rv = NS_ERROR_FILE_CORRUPTED; // presume the worst
89 :
90 : // check size of map file
91 236 : PRUint32 mapSize = PR_Available(mMapFD);
92 236 : if (mapSize == 0) { // creating a new _CACHE_MAP_
93 :
94 : // block files shouldn't exist if we're creating the _CACHE_MAP_
95 236 : if (cacheFilesExist)
96 0 : goto error_exit;
97 :
98 236 : if (NS_FAILED(CreateCacheSubDirectories()))
99 0 : goto error_exit;
100 :
101 : // create the file - initialize in memory
102 236 : memset(&mHeader, 0, sizeof(nsDiskCacheHeader));
103 236 : mHeader.mVersion = nsDiskCache::kCurrentVersion;
104 236 : mHeader.mRecordCount = kMinRecordCount;
105 : mRecordArray = (nsDiskCacheRecord *)
106 236 : PR_CALLOC(mHeader.mRecordCount * sizeof(nsDiskCacheRecord));
107 236 : if (!mRecordArray) {
108 0 : rv = NS_ERROR_OUT_OF_MEMORY;
109 0 : goto error_exit;
110 : }
111 0 : } else if (mapSize >= sizeof(nsDiskCacheHeader)) { // read existing _CACHE_MAP_
112 :
113 : // if _CACHE_MAP_ exists, so should the block files
114 0 : if (!cacheFilesExist)
115 0 : goto error_exit;
116 :
117 : // read the header
118 0 : PRUint32 bytesRead = PR_Read(mMapFD, &mHeader, sizeof(nsDiskCacheHeader));
119 0 : if (sizeof(nsDiskCacheHeader) != bytesRead) goto error_exit;
120 0 : mHeader.Unswap();
121 :
122 0 : if (mHeader.mIsDirty || (mHeader.mVersion != nsDiskCache::kCurrentVersion))
123 : goto error_exit;
124 :
125 : PRUint32 recordArraySize =
126 0 : mHeader.mRecordCount * sizeof(nsDiskCacheRecord);
127 0 : if (mapSize < recordArraySize + sizeof(nsDiskCacheHeader))
128 0 : goto error_exit;
129 :
130 : // Get the space for the records
131 0 : mRecordArray = (nsDiskCacheRecord *) PR_MALLOC(recordArraySize);
132 0 : if (!mRecordArray) {
133 0 : rv = NS_ERROR_OUT_OF_MEMORY;
134 0 : goto error_exit;
135 : }
136 :
137 : // Read the records
138 0 : bytesRead = PR_Read(mMapFD, mRecordArray, recordArraySize);
139 0 : if (bytesRead < recordArraySize)
140 0 : goto error_exit;
141 :
142 : // Unswap each record
143 0 : PRInt32 total = 0;
144 0 : for (PRInt32 i = 0; i < mHeader.mRecordCount; ++i) {
145 0 : if (mRecordArray[i].HashNumber()) {
146 : #if defined(IS_LITTLE_ENDIAN)
147 0 : mRecordArray[i].Unswap();
148 : #endif
149 0 : total ++;
150 : }
151 : }
152 :
153 : // verify entry count
154 0 : if (total != mHeader.mEntryCount)
155 0 : goto error_exit;
156 :
157 : } else {
158 0 : goto error_exit;
159 : }
160 :
161 236 : rv = OpenBlockFiles();
162 236 : if (NS_FAILED(rv)) goto error_exit;
163 :
164 : // set dirty bit and flush header
165 236 : mHeader.mIsDirty = true;
166 236 : rv = FlushHeader();
167 236 : if (NS_FAILED(rv)) goto error_exit;
168 :
169 : {
170 : // extra scope so the compiler doesn't barf on the above gotos jumping
171 : // past this declaration down here
172 236 : PRUint32 overhead = moz_malloc_size_of(mRecordArray);
173 : mozilla::Telemetry::Accumulate(mozilla::Telemetry::HTTP_DISK_CACHE_OVERHEAD,
174 236 : overhead);
175 : }
176 :
177 236 : return NS_OK;
178 :
179 : error_exit:
180 0 : (void) Close(false);
181 :
182 0 : return rv;
183 : }
184 :
185 :
186 : nsresult
187 411 : nsDiskCacheMap::Close(bool flush)
188 : {
189 411 : nsresult rv = NS_OK;
190 :
191 : // If cache map file and its block files are still open, close them
192 411 : if (mMapFD) {
193 : // close block files
194 236 : rv = CloseBlockFiles(flush);
195 236 : if (NS_SUCCEEDED(rv) && flush && mRecordArray) {
196 : // write the map records
197 175 : rv = FlushRecords(false); // don't bother swapping buckets back
198 175 : if (NS_SUCCEEDED(rv)) {
199 : // clear dirty bit
200 175 : mHeader.mIsDirty = false;
201 175 : rv = FlushHeader();
202 : }
203 : }
204 236 : if ((PR_Close(mMapFD) != PR_SUCCESS) && (NS_SUCCEEDED(rv)))
205 0 : rv = NS_ERROR_UNEXPECTED;
206 :
207 236 : mMapFD = nsnull;
208 : }
209 411 : PR_FREEIF(mRecordArray);
210 411 : PR_FREEIF(mBuffer);
211 411 : mBufferSize = 0;
212 411 : return rv;
213 : }
214 :
215 :
216 : nsresult
217 0 : nsDiskCacheMap::Trim()
218 : {
219 0 : nsresult rv, rv2 = NS_OK;
220 0 : for (int i=0; i < kNumBlockFiles; ++i) {
221 0 : rv = mBlockFile[i].Trim();
222 0 : if (NS_FAILED(rv)) rv2 = rv; // if one or more errors, report at least one
223 : }
224 : // Try to shrink the records array
225 0 : rv = ShrinkRecords();
226 0 : if (NS_FAILED(rv)) rv2 = rv; // if one or more errors, report at least one
227 0 : return rv2;
228 : }
229 :
230 :
231 : nsresult
232 411 : nsDiskCacheMap::FlushHeader()
233 : {
234 411 : if (!mMapFD) return NS_ERROR_NOT_AVAILABLE;
235 :
236 : // seek to beginning of cache map
237 411 : PRInt32 filePos = PR_Seek(mMapFD, 0, PR_SEEK_SET);
238 411 : if (filePos != 0) return NS_ERROR_UNEXPECTED;
239 :
240 : // write the header
241 411 : mHeader.Swap();
242 411 : PRInt32 bytesWritten = PR_Write(mMapFD, &mHeader, sizeof(nsDiskCacheHeader));
243 411 : mHeader.Unswap();
244 411 : if (sizeof(nsDiskCacheHeader) != bytesWritten) {
245 0 : return NS_ERROR_UNEXPECTED;
246 : }
247 :
248 411 : PRStatus err = PR_Sync(mMapFD);
249 411 : if (err != PR_SUCCESS) return NS_ERROR_UNEXPECTED;
250 :
251 411 : return NS_OK;
252 : }
253 :
254 :
255 : nsresult
256 175 : nsDiskCacheMap::FlushRecords(bool unswap)
257 : {
258 175 : if (!mMapFD) return NS_ERROR_NOT_AVAILABLE;
259 :
260 : // seek to beginning of buckets
261 175 : PRInt32 filePos = PR_Seek(mMapFD, sizeof(nsDiskCacheHeader), PR_SEEK_SET);
262 175 : if (filePos != sizeof(nsDiskCacheHeader))
263 0 : return NS_ERROR_UNEXPECTED;
264 :
265 : #if defined(IS_LITTLE_ENDIAN)
266 : // Swap each record
267 89775 : for (PRInt32 i = 0; i < mHeader.mRecordCount; ++i) {
268 89600 : if (mRecordArray[i].HashNumber())
269 376 : mRecordArray[i].Swap();
270 : }
271 : #endif
272 :
273 175 : PRInt32 recordArraySize = sizeof(nsDiskCacheRecord) * mHeader.mRecordCount;
274 :
275 175 : PRInt32 bytesWritten = PR_Write(mMapFD, mRecordArray, recordArraySize);
276 175 : if (bytesWritten != recordArraySize)
277 0 : return NS_ERROR_UNEXPECTED;
278 :
279 : #if defined(IS_LITTLE_ENDIAN)
280 175 : if (unswap) {
281 : // Unswap each record
282 0 : for (PRInt32 i = 0; i < mHeader.mRecordCount; ++i) {
283 0 : if (mRecordArray[i].HashNumber())
284 0 : mRecordArray[i].Unswap();
285 : }
286 : }
287 : #endif
288 :
289 175 : return NS_OK;
290 : }
291 :
292 :
293 : /**
294 : * Record operations
295 : */
296 :
297 : PRUint32
298 4289 : nsDiskCacheMap::GetBucketRank(PRUint32 bucketIndex, PRUint32 targetRank)
299 : {
300 4289 : nsDiskCacheRecord * records = GetFirstRecordInBucket(bucketIndex);
301 4289 : PRUint32 rank = 0;
302 :
303 8600 : for (int i = mHeader.mBucketUsage[bucketIndex]-1; i >= 0; i--) {
304 4311 : if ((rank < records[i].EvictionRank()) &&
305 0 : ((targetRank == 0) || (records[i].EvictionRank() < targetRank)))
306 4176 : rank = records[i].EvictionRank();
307 : }
308 4289 : return rank;
309 : }
310 :
311 : nsresult
312 0 : nsDiskCacheMap::GrowRecords()
313 : {
314 0 : if (mHeader.mRecordCount >= mMaxRecordCount)
315 0 : return NS_OK;
316 0 : CACHE_LOG_DEBUG(("CACHE: GrowRecords\n"));
317 :
318 : // Resize the record array
319 0 : PRInt32 newCount = mHeader.mRecordCount << 1;
320 0 : if (newCount > mMaxRecordCount)
321 0 : newCount = mMaxRecordCount;
322 : nsDiskCacheRecord *newArray = (nsDiskCacheRecord *)
323 0 : PR_REALLOC(mRecordArray, newCount * sizeof(nsDiskCacheRecord));
324 0 : if (!newArray)
325 0 : return NS_ERROR_OUT_OF_MEMORY;
326 :
327 : // Space out the buckets
328 0 : PRUint32 oldRecordsPerBucket = GetRecordsPerBucket();
329 0 : PRUint32 newRecordsPerBucket = newCount / kBuckets;
330 : // Work from back to space out each bucket to the new array
331 0 : for (int bucketIndex = kBuckets - 1; bucketIndex >= 0; --bucketIndex) {
332 : // Move bucket
333 0 : nsDiskCacheRecord *newRecords = newArray + bucketIndex * newRecordsPerBucket;
334 0 : const PRUint32 count = mHeader.mBucketUsage[bucketIndex];
335 : memmove(newRecords,
336 : newArray + bucketIndex * oldRecordsPerBucket,
337 0 : count * sizeof(nsDiskCacheRecord));
338 : // clear unused records
339 0 : memset(newRecords + count, 0,
340 0 : (newRecordsPerBucket - count) * sizeof(nsDiskCacheRecord));
341 : }
342 :
343 : // Set as the new record array
344 0 : mRecordArray = newArray;
345 0 : mHeader.mRecordCount = newCount;
346 0 : return NS_OK;
347 : }
348 :
349 : nsresult
350 0 : nsDiskCacheMap::ShrinkRecords()
351 : {
352 0 : if (mHeader.mRecordCount <= kMinRecordCount)
353 0 : return NS_OK;
354 0 : CACHE_LOG_DEBUG(("CACHE: ShrinkRecords\n"));
355 :
356 : // Verify if we can shrink the record array: all buckets must be less than
357 : // 1/2 filled
358 0 : PRUint32 maxUsage = 0, bucketIndex;
359 0 : for (bucketIndex = 0; bucketIndex < kBuckets; ++bucketIndex) {
360 0 : if (maxUsage < mHeader.mBucketUsage[bucketIndex])
361 0 : maxUsage = mHeader.mBucketUsage[bucketIndex];
362 : }
363 : // Determine new bucket size, halve size until maxUsage
364 0 : PRUint32 oldRecordsPerBucket = GetRecordsPerBucket();
365 0 : PRUint32 newRecordsPerBucket = oldRecordsPerBucket;
366 0 : while (maxUsage < (newRecordsPerBucket >> 1))
367 0 : newRecordsPerBucket >>= 1;
368 0 : if (newRecordsPerBucket < kMinRecordCount)
369 0 : newRecordsPerBucket = kMinRecordCount;
370 0 : if (newRecordsPerBucket == oldRecordsPerBucket)
371 0 : return NS_OK;
372 : // Move the buckets close to each other
373 0 : for (bucketIndex = 0; bucketIndex < kBuckets; ++bucketIndex) {
374 : // Move bucket
375 0 : memmove(mRecordArray + bucketIndex * newRecordsPerBucket,
376 : mRecordArray + bucketIndex * oldRecordsPerBucket,
377 0 : mHeader.mBucketUsage[bucketIndex] * sizeof(nsDiskCacheRecord));
378 : }
379 :
380 : // Shrink the record array memory block itself
381 0 : PRUint32 newCount = newRecordsPerBucket * kBuckets;
382 : nsDiskCacheRecord* newArray = (nsDiskCacheRecord *)
383 0 : PR_REALLOC(mRecordArray, newCount * sizeof(nsDiskCacheRecord));
384 0 : if (!newArray)
385 0 : return NS_ERROR_OUT_OF_MEMORY;
386 :
387 : // Set as the new record array
388 0 : mRecordArray = newArray;
389 0 : mHeader.mRecordCount = newCount;
390 0 : return NS_OK;
391 : }
392 :
393 : nsresult
394 551 : nsDiskCacheMap::AddRecord( nsDiskCacheRecord * mapRecord,
395 : nsDiskCacheRecord * oldRecord)
396 : {
397 551 : CACHE_LOG_DEBUG(("CACHE: AddRecord [%x]\n", mapRecord->HashNumber()));
398 :
399 551 : const PRUint32 hashNumber = mapRecord->HashNumber();
400 551 : const PRUint32 bucketIndex = GetBucketIndex(hashNumber);
401 551 : const PRUint32 count = mHeader.mBucketUsage[bucketIndex];
402 :
403 551 : oldRecord->SetHashNumber(0); // signify no record
404 :
405 551 : if (count == GetRecordsPerBucket()) {
406 : // Ignore failure to grow the record space, we will then reuse old records
407 0 : GrowRecords();
408 : }
409 :
410 551 : nsDiskCacheRecord * records = GetFirstRecordInBucket(bucketIndex);
411 551 : if (count < GetRecordsPerBucket()) {
412 : // stick the new record at the end
413 551 : records[count] = *mapRecord;
414 551 : mHeader.mEntryCount++;
415 551 : mHeader.mBucketUsage[bucketIndex]++;
416 551 : if (mHeader.mEvictionRank[bucketIndex] < mapRecord->EvictionRank())
417 497 : mHeader.mEvictionRank[bucketIndex] = mapRecord->EvictionRank();
418 : } else {
419 : // Find the record with the highest eviction rank
420 0 : nsDiskCacheRecord * mostEvictable = &records[0];
421 0 : for (int i = count-1; i > 0; i--) {
422 0 : if (records[i].EvictionRank() > mostEvictable->EvictionRank())
423 0 : mostEvictable = &records[i];
424 : }
425 0 : *oldRecord = *mostEvictable; // i == GetRecordsPerBucket(), so
426 : // evict the mostEvictable
427 0 : *mostEvictable = *mapRecord; // replace it with the new record
428 : // check if we need to update mostEvictable entry in header
429 0 : if (mHeader.mEvictionRank[bucketIndex] < mapRecord->EvictionRank())
430 0 : mHeader.mEvictionRank[bucketIndex] = mapRecord->EvictionRank();
431 0 : if (oldRecord->EvictionRank() >= mHeader.mEvictionRank[bucketIndex])
432 0 : mHeader.mEvictionRank[bucketIndex] = GetBucketRank(bucketIndex, 0);
433 : }
434 :
435 551 : NS_ASSERTION(mHeader.mEvictionRank[bucketIndex] == GetBucketRank(bucketIndex, 0),
436 : "eviction rank out of sync");
437 551 : return NS_OK;
438 : }
439 :
440 :
441 : nsresult
442 1786 : nsDiskCacheMap::UpdateRecord( nsDiskCacheRecord * mapRecord)
443 : {
444 1786 : CACHE_LOG_DEBUG(("CACHE: UpdateRecord [%x]\n", mapRecord->HashNumber()));
445 :
446 1786 : const PRUint32 hashNumber = mapRecord->HashNumber();
447 1786 : const PRUint32 bucketIndex = GetBucketIndex(hashNumber);
448 1786 : nsDiskCacheRecord * records = GetFirstRecordInBucket(bucketIndex);
449 :
450 1834 : for (int i = mHeader.mBucketUsage[bucketIndex]-1; i >= 0; i--) {
451 1834 : if (records[i].HashNumber() == hashNumber) {
452 1786 : const PRUint32 oldRank = records[i].EvictionRank();
453 :
454 : // stick the new record here
455 1786 : records[i] = *mapRecord;
456 :
457 : // update eviction rank in header if necessary
458 1786 : if (mHeader.mEvictionRank[bucketIndex] < mapRecord->EvictionRank())
459 0 : mHeader.mEvictionRank[bucketIndex] = mapRecord->EvictionRank();
460 1786 : else if (mHeader.mEvictionRank[bucketIndex] == oldRank)
461 1621 : mHeader.mEvictionRank[bucketIndex] = GetBucketRank(bucketIndex, 0);
462 :
463 1786 : NS_ASSERTION(mHeader.mEvictionRank[bucketIndex] == GetBucketRank(bucketIndex, 0),
464 : "eviction rank out of sync");
465 1786 : return NS_OK;
466 : }
467 : }
468 0 : NS_NOTREACHED("record not found");
469 0 : return NS_ERROR_UNEXPECTED;
470 : }
471 :
472 :
473 : nsresult
474 1979 : nsDiskCacheMap::FindRecord( PRUint32 hashNumber, nsDiskCacheRecord * result)
475 : {
476 1979 : const PRUint32 bucketIndex = GetBucketIndex(hashNumber);
477 1979 : nsDiskCacheRecord * records = GetFirstRecordInBucket(bucketIndex);
478 :
479 2134 : for (int i = mHeader.mBucketUsage[bucketIndex]-1; i >= 0; i--) {
480 434 : if (records[i].HashNumber() == hashNumber) {
481 279 : *result = records[i]; // copy the record
482 279 : NS_ASSERTION(result->ValidRecord(), "bad cache map record");
483 279 : return NS_OK;
484 : }
485 : }
486 1700 : return NS_ERROR_CACHE_KEY_NOT_FOUND;
487 : }
488 :
489 :
490 : nsresult
491 169 : nsDiskCacheMap::DeleteRecord( nsDiskCacheRecord * mapRecord)
492 : {
493 169 : CACHE_LOG_DEBUG(("CACHE: DeleteRecord [%x]\n", mapRecord->HashNumber()));
494 :
495 169 : const PRUint32 hashNumber = mapRecord->HashNumber();
496 169 : const PRUint32 bucketIndex = GetBucketIndex(hashNumber);
497 169 : nsDiskCacheRecord * records = GetFirstRecordInBucket(bucketIndex);
498 169 : PRUint32 last = mHeader.mBucketUsage[bucketIndex]-1;
499 :
500 173 : for (int i = last; i >= 0; i--) {
501 173 : if (records[i].HashNumber() == hashNumber) {
502 : // found it, now delete it.
503 169 : PRUint32 evictionRank = records[i].EvictionRank();
504 169 : NS_ASSERTION(evictionRank == mapRecord->EvictionRank(),
505 : "evictionRank out of sync");
506 : // if not the last record, shift last record into opening
507 169 : records[i] = records[last];
508 169 : records[last].SetHashNumber(0); // clear last record
509 169 : mHeader.mBucketUsage[bucketIndex] = last;
510 169 : mHeader.mEntryCount--;
511 :
512 : // update eviction rank
513 169 : PRUint32 bucketIndex = GetBucketIndex(mapRecord->HashNumber());
514 169 : if (mHeader.mEvictionRank[bucketIndex] <= evictionRank) {
515 159 : mHeader.mEvictionRank[bucketIndex] = GetBucketRank(bucketIndex, 0);
516 : }
517 :
518 169 : NS_ASSERTION(mHeader.mEvictionRank[bucketIndex] ==
519 : GetBucketRank(bucketIndex, 0), "eviction rank out of sync");
520 169 : return NS_OK;
521 : }
522 : }
523 0 : return NS_ERROR_UNEXPECTED;
524 : }
525 :
526 :
527 : PRInt32
528 1 : nsDiskCacheMap::VisitEachRecord(PRUint32 bucketIndex,
529 : nsDiskCacheRecordVisitor * visitor,
530 : PRUint32 evictionRank)
531 : {
532 1 : PRInt32 rv = kVisitNextRecord;
533 1 : PRUint32 count = mHeader.mBucketUsage[bucketIndex];
534 1 : nsDiskCacheRecord * records = GetFirstRecordInBucket(bucketIndex);
535 :
536 : // call visitor for each entry (matching any eviction rank)
537 2 : for (int i = count-1; i >= 0; i--) {
538 1 : if (evictionRank > records[i].EvictionRank()) continue;
539 :
540 1 : rv = visitor->VisitRecord(&records[i]);
541 1 : if (rv == kStopVisitingRecords)
542 0 : break; // Stop visiting records
543 :
544 1 : if (rv == kDeleteRecordAndContinue) {
545 1 : --count;
546 1 : records[i] = records[count];
547 1 : records[count].SetHashNumber(0);
548 : }
549 : }
550 :
551 1 : if (mHeader.mBucketUsage[bucketIndex] - count != 0) {
552 1 : mHeader.mEntryCount -= mHeader.mBucketUsage[bucketIndex] - count;
553 1 : mHeader.mBucketUsage[bucketIndex] = count;
554 : // recalc eviction rank
555 1 : mHeader.mEvictionRank[bucketIndex] = GetBucketRank(bucketIndex, 0);
556 : }
557 1 : NS_ASSERTION(mHeader.mEvictionRank[bucketIndex] ==
558 : GetBucketRank(bucketIndex, 0), "eviction rank out of sync");
559 :
560 1 : return rv;
561 : }
562 :
563 :
564 : /**
565 : * VisitRecords
566 : *
567 : * Visit every record in cache map in the most convenient order
568 : */
569 : nsresult
570 0 : nsDiskCacheMap::VisitRecords( nsDiskCacheRecordVisitor * visitor)
571 : {
572 0 : for (int bucketIndex = 0; bucketIndex < kBuckets; ++bucketIndex) {
573 0 : if (VisitEachRecord(bucketIndex, visitor, 0) == kStopVisitingRecords)
574 0 : break;
575 : }
576 0 : return NS_OK;
577 : }
578 :
579 :
580 : /**
581 : * EvictRecords
582 : *
583 : * Just like VisitRecords, but visits the records in order of their eviction rank
584 : */
585 : nsresult
586 17 : nsDiskCacheMap::EvictRecords( nsDiskCacheRecordVisitor * visitor)
587 : {
588 : PRUint32 tempRank[kBuckets];
589 17 : int bucketIndex = 0;
590 :
591 : // copy eviction rank array
592 561 : for (bucketIndex = 0; bucketIndex < kBuckets; ++bucketIndex)
593 544 : tempRank[bucketIndex] = mHeader.mEvictionRank[bucketIndex];
594 :
595 : // Maximum number of iterations determined by number of records
596 : // as a safety limiter for the loop. Use a copy of mHeader.mEntryCount since
597 : // the value could decrease if some entry is evicted.
598 17 : PRInt32 entryCount = mHeader.mEntryCount;
599 18 : for (int n = 0; n < entryCount; ++n) {
600 :
601 : // find bucket with highest eviction rank
602 1 : PRUint32 rank = 0;
603 33 : for (int i = 0; i < kBuckets; ++i) {
604 32 : if (rank < tempRank[i]) {
605 1 : rank = tempRank[i];
606 1 : bucketIndex = i;
607 : }
608 : }
609 :
610 1 : if (rank == 0) break; // we've examined all the records
611 :
612 : // visit records in bucket with eviction ranks >= target eviction rank
613 1 : if (VisitEachRecord(bucketIndex, visitor, rank) == kStopVisitingRecords)
614 0 : break;
615 :
616 : // find greatest rank less than 'rank'
617 1 : tempRank[bucketIndex] = GetBucketRank(bucketIndex, rank);
618 : }
619 17 : return NS_OK;
620 : }
621 :
622 :
623 :
624 : nsresult
625 236 : nsDiskCacheMap::OpenBlockFiles()
626 : {
627 : // create nsILocalFile for block file
628 472 : nsCOMPtr<nsILocalFile> blockFile;
629 236 : nsresult rv = NS_OK;
630 :
631 944 : for (int i = 0; i < kNumBlockFiles; ++i) {
632 708 : rv = GetBlockFileForIndex(i, getter_AddRefs(blockFile));
633 708 : if (NS_FAILED(rv)) break;
634 :
635 708 : PRUint32 blockSize = GetBlockSizeForIndex(i+1); // +1 to match file selectors 1,2,3
636 708 : PRUint32 bitMapSize = GetBitMapSizeForIndex(i+1);
637 708 : rv = mBlockFile[i].Open(blockFile, blockSize, bitMapSize);
638 708 : if (NS_FAILED(rv)) break;
639 : }
640 : // close all files in case of any error
641 236 : if (NS_FAILED(rv))
642 0 : (void)CloseBlockFiles(false); // we already have an error to report
643 :
644 236 : return rv;
645 : }
646 :
647 :
648 : nsresult
649 236 : nsDiskCacheMap::CloseBlockFiles(bool flush)
650 : {
651 236 : nsresult rv, rv2 = NS_OK;
652 944 : for (int i=0; i < kNumBlockFiles; ++i) {
653 708 : rv = mBlockFile[i].Close(flush);
654 708 : if (NS_FAILED(rv)) rv2 = rv; // if one or more errors, report at least one
655 : }
656 236 : return rv2;
657 : }
658 :
659 :
660 : bool
661 236 : nsDiskCacheMap::CacheFilesExist()
662 : {
663 472 : nsCOMPtr<nsILocalFile> blockFile;
664 : nsresult rv;
665 :
666 236 : for (int i = 0; i < kNumBlockFiles; ++i) {
667 : bool exists;
668 236 : rv = GetBlockFileForIndex(i, getter_AddRefs(blockFile));
669 236 : if (NS_FAILED(rv)) return false;
670 :
671 236 : rv = blockFile->Exists(&exists);
672 236 : if (NS_FAILED(rv) || !exists) return false;
673 : }
674 :
675 0 : return true;
676 : }
677 :
678 :
679 : nsresult
680 236 : nsDiskCacheMap::CreateCacheSubDirectories()
681 : {
682 236 : if (!mCacheDirectory)
683 0 : return NS_ERROR_UNEXPECTED;
684 :
685 4012 : for (PRInt32 index = 0 ; index < 16 ; index++) {
686 7552 : nsCOMPtr<nsIFile> file;
687 3776 : nsresult rv = mCacheDirectory->Clone(getter_AddRefs(file));
688 3776 : if (NS_FAILED(rv))
689 0 : return rv;
690 :
691 3776 : rv = file->AppendNative(nsPrintfCString("%X", index));
692 3776 : if (NS_FAILED(rv))
693 0 : return rv;
694 :
695 7552 : nsCOMPtr<nsILocalFile> localFile = do_QueryInterface(file, &rv);
696 3776 : rv = localFile->Create(nsIFile::DIRECTORY_TYPE, 0700);
697 3776 : if (NS_FAILED(rv))
698 0 : return rv;
699 : }
700 :
701 236 : return NS_OK;
702 : }
703 :
704 :
705 : nsDiskCacheEntry *
706 279 : nsDiskCacheMap::ReadDiskCacheEntry(nsDiskCacheRecord * record)
707 : {
708 279 : CACHE_LOG_DEBUG(("CACHE: ReadDiskCacheEntry [%x]\n", record->HashNumber()));
709 :
710 279 : nsresult rv = NS_ERROR_UNEXPECTED;
711 279 : nsDiskCacheEntry * diskEntry = nsnull;
712 279 : PRUint32 metaFile = record->MetaFile();
713 279 : PRInt32 bytesRead = 0;
714 :
715 279 : if (!record->MetaLocationInitialized()) return nsnull;
716 :
717 279 : if (metaFile == 0) { // entry/metadata stored in separate file
718 : // open and read the file
719 0 : nsCOMPtr<nsILocalFile> file;
720 : rv = GetLocalFileForDiskCacheRecord(record,
721 : nsDiskCache::kMetaData,
722 : false,
723 0 : getter_AddRefs(file));
724 0 : NS_ENSURE_SUCCESS(rv, nsnull);
725 :
726 0 : PRFileDesc * fd = nsnull;
727 : // open the file - restricted to user, the data could be confidential
728 0 : rv = file->OpenNSPRFileDesc(PR_RDONLY, 00600, &fd);
729 0 : NS_ENSURE_SUCCESS(rv, nsnull);
730 :
731 0 : PRInt32 fileSize = PR_Available(fd);
732 0 : if (fileSize < 0) {
733 : // an error occurred. We could call PR_GetError(), but how would that help?
734 0 : rv = NS_ERROR_UNEXPECTED;
735 : } else {
736 0 : rv = EnsureBuffer(fileSize);
737 0 : if (NS_SUCCEEDED(rv)) {
738 0 : bytesRead = PR_Read(fd, mBuffer, fileSize);
739 0 : if (bytesRead < fileSize) {
740 0 : rv = NS_ERROR_UNEXPECTED;
741 : }
742 : }
743 : }
744 0 : PR_Close(fd);
745 0 : NS_ENSURE_SUCCESS(rv, nsnull);
746 :
747 279 : } else if (metaFile < (kNumBlockFiles + 1)) {
748 : // entry/metadata stored in cache block file
749 :
750 : // allocate buffer
751 279 : PRUint32 blockCount = record->MetaBlockCount();
752 279 : bytesRead = blockCount * GetBlockSizeForIndex(metaFile);
753 :
754 279 : rv = EnsureBuffer(bytesRead);
755 279 : NS_ENSURE_SUCCESS(rv, nsnull);
756 :
757 : // read diskEntry, note when the blocks are at the end of file,
758 : // bytesRead may be less than blockSize*blockCount.
759 : // But the bytesRead should at least agree with the real disk entry size.
760 279 : rv = mBlockFile[metaFile - 1].ReadBlocks(mBuffer,
761 279 : record->MetaStartBlock(),
762 : blockCount,
763 837 : &bytesRead);
764 279 : NS_ENSURE_SUCCESS(rv, nsnull);
765 : }
766 279 : diskEntry = (nsDiskCacheEntry *)mBuffer;
767 279 : diskEntry->Unswap(); // disk to memory
768 : // Check if calculated size agrees with bytesRead
769 279 : if (bytesRead < 0 || (PRUint32)bytesRead < diskEntry->Size())
770 0 : return nsnull;
771 :
772 : // Return the buffer containing the diskEntry structure
773 279 : return diskEntry;
774 : }
775 :
776 :
777 : /**
778 : * CreateDiskCacheEntry(nsCacheEntry * entry)
779 : *
780 : * Prepare an nsCacheEntry for writing to disk
781 : */
782 : nsDiskCacheEntry *
783 660 : nsDiskCacheMap::CreateDiskCacheEntry(nsDiskCacheBinding * binding,
784 : PRUint32 * aSize)
785 : {
786 660 : nsCacheEntry * entry = binding->mCacheEntry;
787 660 : if (!entry) return nsnull;
788 :
789 : // Store security info, if it is serializable
790 : nsCOMPtr<nsISerializable> serializable =
791 1320 : do_QueryInterface(entry->SecurityInfo());
792 660 : if (serializable) {
793 8 : nsCString info;
794 4 : NS_SerializeToString(serializable, info);
795 4 : entry->SetMetaDataElement("security-info", info.get());
796 : }
797 :
798 660 : PRUint32 keySize = entry->Key()->Length() + 1;
799 660 : PRUint32 metaSize = entry->MetaDataSize();
800 660 : PRUint32 size = sizeof(nsDiskCacheEntry) + keySize + metaSize;
801 :
802 660 : if (aSize) *aSize = size;
803 :
804 660 : nsresult rv = EnsureBuffer(size);
805 660 : if (NS_FAILED(rv)) return nsnull;
806 :
807 660 : nsDiskCacheEntry *diskEntry = (nsDiskCacheEntry *)mBuffer;
808 660 : diskEntry->mHeaderVersion = nsDiskCache::kCurrentVersion;
809 660 : diskEntry->mMetaLocation = binding->mRecord.MetaLocation();
810 660 : diskEntry->mFetchCount = entry->FetchCount();
811 660 : diskEntry->mLastFetched = entry->LastFetched();
812 660 : diskEntry->mLastModified = entry->LastModified();
813 660 : diskEntry->mExpirationTime = entry->ExpirationTime();
814 660 : diskEntry->mDataSize = entry->DataSize();
815 660 : diskEntry->mKeySize = keySize;
816 660 : diskEntry->mMetaDataSize = metaSize;
817 :
818 660 : memcpy(diskEntry->Key(), entry->Key()->get(), keySize);
819 :
820 660 : rv = entry->FlattenMetaData(diskEntry->MetaData(), metaSize);
821 660 : if (NS_FAILED(rv)) return nsnull;
822 :
823 660 : return diskEntry;
824 : }
825 :
826 :
827 : nsresult
828 660 : nsDiskCacheMap::WriteDiskCacheEntry(nsDiskCacheBinding * binding)
829 : {
830 660 : CACHE_LOG_DEBUG(("CACHE: WriteDiskCacheEntry [%x]\n",
831 : binding->mRecord.HashNumber()));
832 :
833 660 : nsresult rv = NS_OK;
834 : PRUint32 size;
835 660 : nsDiskCacheEntry * diskEntry = CreateDiskCacheEntry(binding, &size);
836 660 : if (!diskEntry) return NS_ERROR_UNEXPECTED;
837 :
838 660 : PRUint32 fileIndex = CalculateFileIndex(size);
839 :
840 : // Deallocate old storage if necessary
841 660 : if (binding->mRecord.MetaLocationInitialized()) {
842 : // we have existing storage
843 :
844 163 : if ((binding->mRecord.MetaFile() == 0) &&
845 : (fileIndex == 0)) { // keeping the separate file
846 : // just decrement total
847 0 : DecrementTotalSize(binding->mRecord.MetaFileSize());
848 0 : NS_ASSERTION(binding->mRecord.MetaFileGeneration() == binding->mGeneration,
849 : "generations out of sync");
850 : } else {
851 163 : rv = DeleteStorage(&binding->mRecord, nsDiskCache::kMetaData);
852 163 : NS_ENSURE_SUCCESS(rv, rv);
853 : }
854 : }
855 :
856 660 : binding->mRecord.SetEvictionRank(ULONG_MAX - SecondsFromPRTime(PR_Now()));
857 : // write entry data to disk cache block file
858 660 : diskEntry->Swap();
859 :
860 660 : if (fileIndex != 0) {
861 0 : while (1) {
862 660 : PRUint32 blockSize = GetBlockSizeForIndex(fileIndex);
863 660 : PRUint32 blocks = ((size - 1) / blockSize) + 1;
864 :
865 : PRInt32 startBlock;
866 660 : rv = mBlockFile[fileIndex - 1].WriteBlocks(diskEntry, size, blocks,
867 1320 : &startBlock);
868 660 : if (NS_SUCCEEDED(rv)) {
869 : // update binding and cache map record
870 660 : binding->mRecord.SetMetaBlocks(fileIndex, startBlock, blocks);
871 :
872 660 : rv = UpdateRecord(&binding->mRecord);
873 660 : NS_ENSURE_SUCCESS(rv, rv);
874 :
875 : // XXX we should probably write out bucket ourselves
876 :
877 660 : IncrementTotalSize(blocks, blockSize);
878 660 : break;
879 : }
880 :
881 0 : if (fileIndex == kNumBlockFiles) {
882 0 : fileIndex = 0; // write data to separate file
883 0 : break;
884 : }
885 :
886 : // try next block file
887 0 : fileIndex++;
888 : }
889 : }
890 :
891 660 : if (fileIndex == 0) {
892 : // Write entry data to separate file
893 0 : PRUint32 metaFileSizeK = ((size + 0x03FF) >> 10); // round up to nearest 1k
894 0 : if (metaFileSizeK > kMaxDataSizeK)
895 0 : metaFileSizeK = kMaxDataSizeK;
896 :
897 0 : binding->mRecord.SetMetaFileGeneration(binding->mGeneration);
898 0 : binding->mRecord.SetMetaFileSize(metaFileSizeK);
899 0 : rv = UpdateRecord(&binding->mRecord);
900 0 : NS_ENSURE_SUCCESS(rv, rv);
901 :
902 0 : nsCOMPtr<nsILocalFile> localFile;
903 : rv = GetLocalFileForDiskCacheRecord(&binding->mRecord,
904 : nsDiskCache::kMetaData,
905 : true,
906 0 : getter_AddRefs(localFile));
907 0 : NS_ENSURE_SUCCESS(rv, rv);
908 :
909 : // open the file
910 : PRFileDesc * fd;
911 : // open the file - restricted to user, the data could be confidential
912 0 : rv = localFile->OpenNSPRFileDesc(PR_RDWR | PR_TRUNCATE | PR_CREATE_FILE, 00600, &fd);
913 0 : NS_ENSURE_SUCCESS(rv, rv);
914 :
915 : // write the file
916 0 : PRInt32 bytesWritten = PR_Write(fd, diskEntry, size);
917 :
918 0 : PRStatus err = PR_Close(fd);
919 0 : if ((bytesWritten != (PRInt32)size) || (err != PR_SUCCESS)) {
920 0 : return NS_ERROR_UNEXPECTED;
921 : }
922 :
923 0 : IncrementTotalSize(metaFileSizeK);
924 : }
925 :
926 660 : return rv;
927 : }
928 :
929 :
930 : nsresult
931 160 : nsDiskCacheMap::ReadDataCacheBlocks(nsDiskCacheBinding * binding, char * buffer, PRUint32 size)
932 : {
933 160 : CACHE_LOG_DEBUG(("CACHE: ReadDataCacheBlocks [%x size=%u]\n",
934 : binding->mRecord.HashNumber(), size));
935 :
936 160 : PRUint32 fileIndex = binding->mRecord.DataFile();
937 160 : PRInt32 readSize = size;
938 :
939 160 : nsresult rv = mBlockFile[fileIndex - 1].ReadBlocks(buffer,
940 160 : binding->mRecord.DataStartBlock(),
941 160 : binding->mRecord.DataBlockCount(),
942 480 : &readSize);
943 160 : NS_ENSURE_SUCCESS(rv, rv);
944 160 : if (readSize < (PRInt32)size) {
945 0 : rv = NS_ERROR_UNEXPECTED;
946 : }
947 160 : return rv;
948 : }
949 :
950 :
951 : nsresult
952 476 : nsDiskCacheMap::WriteDataCacheBlocks(nsDiskCacheBinding * binding, char * buffer, PRUint32 size)
953 : {
954 476 : CACHE_LOG_DEBUG(("CACHE: WriteDataCacheBlocks [%x size=%u]\n",
955 : binding->mRecord.HashNumber(), size));
956 :
957 476 : nsresult rv = NS_OK;
958 :
959 : // determine block file & number of blocks
960 476 : PRUint32 fileIndex = CalculateFileIndex(size);
961 476 : PRUint32 blockCount = 0;
962 476 : PRInt32 startBlock = 0;
963 :
964 476 : if (size > 0) {
965 0 : while (1) {
966 476 : PRUint32 blockSize = GetBlockSizeForIndex(fileIndex);
967 476 : blockCount = ((size - 1) / blockSize) + 1;
968 :
969 476 : rv = mBlockFile[fileIndex - 1].WriteBlocks(buffer, size, blockCount,
970 952 : &startBlock);
971 476 : if (NS_SUCCEEDED(rv)) {
972 476 : IncrementTotalSize(blockCount, blockSize);
973 : break;
974 : }
975 :
976 0 : if (fileIndex == kNumBlockFiles)
977 0 : return rv;
978 :
979 0 : fileIndex++;
980 : }
981 : }
982 :
983 : // update binding and cache map record
984 476 : binding->mRecord.SetDataBlocks(fileIndex, startBlock, blockCount);
985 476 : if (!binding->mDoomed) {
986 476 : rv = UpdateRecord(&binding->mRecord);
987 : }
988 476 : return rv;
989 : }
990 :
991 :
992 : nsresult
993 170 : nsDiskCacheMap::DeleteStorage(nsDiskCacheRecord * record)
994 : {
995 170 : nsresult rv1 = DeleteStorage(record, nsDiskCache::kData);
996 170 : nsresult rv2 = DeleteStorage(record, nsDiskCache::kMetaData);
997 170 : return NS_FAILED(rv1) ? rv1 : rv2;
998 : }
999 :
1000 :
1001 : nsresult
1002 511 : nsDiskCacheMap::DeleteStorage(nsDiskCacheRecord * record, bool metaData)
1003 : {
1004 511 : CACHE_LOG_DEBUG(("CACHE: DeleteStorage [%x %u]\n", record->HashNumber(),
1005 : metaData));
1006 :
1007 511 : nsresult rv = NS_ERROR_UNEXPECTED;
1008 511 : PRUint32 fileIndex = metaData ? record->MetaFile() : record->DataFile();
1009 1022 : nsCOMPtr<nsIFile> file;
1010 :
1011 511 : if (fileIndex == 0) {
1012 : // delete the file
1013 88 : PRUint32 sizeK = metaData ? record->MetaFileSize() : record->DataFileSize();
1014 : // XXX if sizeK == USHRT_MAX, stat file for actual size
1015 :
1016 88 : rv = GetFileForDiskCacheRecord(record, metaData, false, getter_AddRefs(file));
1017 88 : if (NS_SUCCEEDED(rv)) {
1018 88 : rv = file->Remove(false); // false == non-recursive
1019 : }
1020 88 : DecrementTotalSize(sizeK);
1021 :
1022 423 : } else if (fileIndex < (kNumBlockFiles + 1)) {
1023 : // deallocate blocks
1024 423 : PRUint32 startBlock = metaData ? record->MetaStartBlock() : record->DataStartBlock();
1025 423 : PRUint32 blockCount = metaData ? record->MetaBlockCount() : record->DataBlockCount();
1026 :
1027 423 : rv = mBlockFile[fileIndex - 1].DeallocateBlocks(startBlock, blockCount);
1028 423 : DecrementTotalSize(blockCount, GetBlockSizeForIndex(fileIndex));
1029 : }
1030 511 : if (metaData) record->ClearMetaLocation();
1031 178 : else record->ClearDataLocation();
1032 :
1033 511 : return rv;
1034 : }
1035 :
1036 :
1037 : nsresult
1038 130 : nsDiskCacheMap::GetFileForDiskCacheRecord(nsDiskCacheRecord * record,
1039 : bool meta,
1040 : bool createPath,
1041 : nsIFile ** result)
1042 : {
1043 130 : if (!mCacheDirectory) return NS_ERROR_NOT_AVAILABLE;
1044 :
1045 260 : nsCOMPtr<nsIFile> file;
1046 130 : nsresult rv = mCacheDirectory->Clone(getter_AddRefs(file));
1047 130 : if (NS_FAILED(rv)) return rv;
1048 :
1049 130 : PRUint32 hash = record->HashNumber();
1050 :
1051 : // The file is stored under subdirectories according to the hash number:
1052 : // 0x01234567 -> 0/12/
1053 130 : rv = file->AppendNative(nsPrintfCString("%X", hash >> 28));
1054 130 : if (NS_FAILED(rv)) return rv;
1055 130 : rv = file->AppendNative(nsPrintfCString("%02X", (hash >> 20) & 0xFF));
1056 130 : if (NS_FAILED(rv)) return rv;
1057 :
1058 : bool exists;
1059 130 : if (createPath && (NS_FAILED(file->Exists(&exists)) || !exists)) {
1060 22 : nsCOMPtr<nsILocalFile> localFile = do_QueryInterface(file, &rv);
1061 11 : rv = localFile->Create(nsIFile::DIRECTORY_TYPE, 0700);
1062 11 : if (NS_FAILED(rv)) return rv;
1063 : }
1064 :
1065 130 : PRInt16 generation = record->Generation();
1066 : char name[32];
1067 : // Cut the beginning of the hash that was used in the path
1068 : ::sprintf(name, "%05X%c%02X", hash & 0xFFFFF, (meta ? 'm' : 'd'),
1069 130 : generation);
1070 130 : rv = file->AppendNative(nsDependentCString(name));
1071 130 : if (NS_FAILED(rv)) return rv;
1072 :
1073 130 : NS_IF_ADDREF(*result = file);
1074 130 : return rv;
1075 : }
1076 :
1077 :
1078 : nsresult
1079 42 : nsDiskCacheMap::GetLocalFileForDiskCacheRecord(nsDiskCacheRecord * record,
1080 : bool meta,
1081 : bool createPath,
1082 : nsILocalFile ** result)
1083 : {
1084 84 : nsCOMPtr<nsIFile> file;
1085 : nsresult rv = GetFileForDiskCacheRecord(record,
1086 : meta,
1087 : createPath,
1088 42 : getter_AddRefs(file));
1089 42 : if (NS_FAILED(rv)) return rv;
1090 :
1091 84 : nsCOMPtr<nsILocalFile> localFile = do_QueryInterface(file, &rv);
1092 42 : if (NS_FAILED(rv)) return rv;
1093 :
1094 42 : NS_IF_ADDREF(*result = localFile);
1095 42 : return rv;
1096 : }
1097 :
1098 :
1099 : nsresult
1100 944 : nsDiskCacheMap::GetBlockFileForIndex(PRUint32 index, nsILocalFile ** result)
1101 : {
1102 944 : if (!mCacheDirectory) return NS_ERROR_NOT_AVAILABLE;
1103 :
1104 1888 : nsCOMPtr<nsIFile> file;
1105 944 : nsresult rv = mCacheDirectory->Clone(getter_AddRefs(file));
1106 944 : if (NS_FAILED(rv)) return rv;
1107 :
1108 : char name[32];
1109 944 : ::sprintf(name, "_CACHE_%03d_", index + 1);
1110 944 : rv = file->AppendNative(nsDependentCString(name));
1111 944 : if (NS_FAILED(rv)) return rv;
1112 :
1113 1888 : nsCOMPtr<nsILocalFile> localFile = do_QueryInterface(file, &rv);
1114 944 : NS_IF_ADDREF(*result = localFile);
1115 :
1116 944 : return rv;
1117 : }
1118 :
1119 :
1120 : PRUint32
1121 1136 : nsDiskCacheMap::CalculateFileIndex(PRUint32 size)
1122 : {
1123 : // We prefer to use block file with larger block if the wasted space would
1124 : // be the same. E.g. store entry with size of 3073 bytes in 1 4K-block
1125 : // instead of in 4 1K-blocks.
1126 :
1127 1136 : if (size <= 3 * BLOCK_SIZE_FOR_INDEX(1)) return 1;
1128 238 : if (size <= 3 * BLOCK_SIZE_FOR_INDEX(2)) return 2;
1129 102 : if (size <= 4 * BLOCK_SIZE_FOR_INDEX(3)) return 3;
1130 0 : return 0;
1131 : }
1132 :
1133 : nsresult
1134 939 : nsDiskCacheMap::EnsureBuffer(PRUint32 bufSize)
1135 : {
1136 939 : if (mBufferSize < bufSize) {
1137 191 : char * buf = (char *)PR_REALLOC(mBuffer, bufSize);
1138 191 : if (!buf) {
1139 0 : mBufferSize = 0;
1140 0 : return NS_ERROR_OUT_OF_MEMORY;
1141 : }
1142 191 : mBuffer = buf;
1143 191 : mBufferSize = bufSize;
1144 : }
1145 939 : return NS_OK;
1146 : }
1147 :
1148 : void
1149 176 : nsDiskCacheMap::NotifyCapacityChange(PRUint32 capacity)
1150 : {
1151 : // Heuristic 1. average cache entry size is probably around 1KB
1152 : // Heuristic 2. we don't want more than 32MB reserved to store the record
1153 : // map in memory.
1154 176 : const PRInt32 RECORD_COUNT_LIMIT = 32 * 1024 * 1024 / sizeof(nsDiskCacheRecord);
1155 176 : PRInt32 maxRecordCount = NS_MIN(PRInt32(capacity), RECORD_COUNT_LIMIT);
1156 176 : if (mMaxRecordCount < maxRecordCount) {
1157 : // We can only grow
1158 175 : mMaxRecordCount = maxRecordCount;
1159 : }
1160 176 : }
|