Line data Source code
1 : /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 : /* vim:set ts=4 sw=4 sts=4 cin et: */
3 : /* This Source Code Form is subject to the terms of the Mozilla Public
4 : * License, v. 2.0. If a copy of the MPL was not distributed with this
5 : * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6 :
7 : #include "nsCache.h"
8 : #include "nsDiskCacheMap.h"
9 : #include "nsDiskCacheBinding.h"
10 : #include "nsDiskCacheEntry.h"
11 : #include "nsDiskCacheDevice.h"
12 : #include "nsCacheService.h"
13 :
14 : #include <string.h>
15 : #include "nsPrintfCString.h"
16 :
17 : #include "nsISerializable.h"
18 : #include "nsSerializationHelper.h"
19 :
20 : #include "mozilla/MemoryReporting.h"
21 : #include "mozilla/Sprintf.h"
22 : #include "mozilla/Telemetry.h"
23 : #include <algorithm>
24 :
25 : using namespace mozilla;
26 :
27 : /******************************************************************************
28 : * nsDiskCacheMap
29 : *****************************************************************************/
30 :
31 : /**
32 : * File operations
33 : */
34 :
35 : nsresult
36 0 : nsDiskCacheMap::Open(nsIFile * cacheDirectory,
37 : nsDiskCache::CorruptCacheInfo * corruptInfo)
38 : {
39 0 : NS_ENSURE_ARG_POINTER(corruptInfo);
40 :
41 : // Assume we have an unexpected error until we find otherwise.
42 0 : *corruptInfo = nsDiskCache::kUnexpectedError;
43 0 : NS_ENSURE_ARG_POINTER(cacheDirectory);
44 0 : if (mMapFD) return NS_ERROR_ALREADY_INITIALIZED;
45 :
46 0 : mCacheDirectory = cacheDirectory; // save a reference for ourselves
47 :
48 : // create nsIFile for _CACHE_MAP_
49 : nsresult rv;
50 0 : nsCOMPtr<nsIFile> file;
51 0 : rv = cacheDirectory->Clone(getter_AddRefs(file));
52 0 : rv = file->AppendNative(NS_LITERAL_CSTRING("_CACHE_MAP_"));
53 0 : NS_ENSURE_SUCCESS(rv, rv);
54 :
55 : // open the file - restricted to user, the data could be confidential
56 0 : rv = file->OpenNSPRFileDesc(PR_RDWR | PR_CREATE_FILE, 00600, &mMapFD);
57 0 : if (NS_FAILED(rv)) {
58 0 : *corruptInfo = nsDiskCache::kOpenCacheMapError;
59 0 : NS_WARNING("Could not open cache map file");
60 0 : return NS_ERROR_FILE_CORRUPTED;
61 : }
62 :
63 0 : bool cacheFilesExist = CacheFilesExist();
64 0 : rv = NS_ERROR_FILE_CORRUPTED; // presume the worst
65 0 : uint32_t mapSize = PR_Available(mMapFD);
66 :
67 0 : if (NS_FAILED(InitCacheClean(cacheDirectory,
68 : corruptInfo))) {
69 : // corruptInfo is set in the call to InitCacheClean
70 0 : goto error_exit;
71 : }
72 :
73 : // check size of map file
74 0 : if (mapSize == 0) { // creating a new _CACHE_MAP_
75 :
76 : // block files shouldn't exist if we're creating the _CACHE_MAP_
77 0 : if (cacheFilesExist) {
78 0 : *corruptInfo = nsDiskCache::kBlockFilesShouldNotExist;
79 0 : goto error_exit;
80 : }
81 :
82 0 : if (NS_FAILED(CreateCacheSubDirectories())) {
83 0 : *corruptInfo = nsDiskCache::kCreateCacheSubdirectories;
84 0 : goto error_exit;
85 : }
86 :
87 : // create the file - initialize in memory
88 0 : memset(&mHeader, 0, sizeof(nsDiskCacheHeader));
89 0 : mHeader.mVersion = nsDiskCache::kCurrentVersion;
90 0 : mHeader.mRecordCount = kMinRecordCount;
91 0 : mRecordArray = (nsDiskCacheRecord*)
92 0 : calloc(mHeader.mRecordCount, sizeof(nsDiskCacheRecord));
93 0 : if (!mRecordArray) {
94 0 : *corruptInfo = nsDiskCache::kOutOfMemory;
95 0 : rv = NS_ERROR_OUT_OF_MEMORY;
96 0 : goto error_exit;
97 : }
98 0 : } else if (mapSize >= sizeof(nsDiskCacheHeader)) { // read existing _CACHE_MAP_
99 :
100 : // if _CACHE_MAP_ exists, so should the block files
101 0 : if (!cacheFilesExist) {
102 0 : *corruptInfo = nsDiskCache::kBlockFilesShouldExist;
103 0 : goto error_exit;
104 : }
105 :
106 0 : CACHE_LOG_DEBUG(("CACHE: nsDiskCacheMap::Open [this=%p] reading map", this));
107 :
108 : // read the header
109 0 : uint32_t bytesRead = PR_Read(mMapFD, &mHeader, sizeof(nsDiskCacheHeader));
110 0 : if (sizeof(nsDiskCacheHeader) != bytesRead) {
111 0 : *corruptInfo = nsDiskCache::kHeaderSizeNotRead;
112 0 : goto error_exit;
113 : }
114 0 : mHeader.Unswap();
115 :
116 0 : if (mHeader.mIsDirty) {
117 0 : *corruptInfo = nsDiskCache::kHeaderIsDirty;
118 0 : goto error_exit;
119 : }
120 :
121 0 : if (mHeader.mVersion != nsDiskCache::kCurrentVersion) {
122 0 : *corruptInfo = nsDiskCache::kVersionMismatch;
123 0 : goto error_exit;
124 : }
125 :
126 : uint32_t recordArraySize =
127 0 : mHeader.mRecordCount * sizeof(nsDiskCacheRecord);
128 0 : if (mapSize < recordArraySize + sizeof(nsDiskCacheHeader)) {
129 0 : *corruptInfo = nsDiskCache::kRecordsIncomplete;
130 0 : goto error_exit;
131 : }
132 :
133 : // Get the space for the records
134 0 : mRecordArray = (nsDiskCacheRecord*) malloc(recordArraySize);
135 0 : if (!mRecordArray) {
136 0 : *corruptInfo = nsDiskCache::kOutOfMemory;
137 0 : rv = NS_ERROR_OUT_OF_MEMORY;
138 0 : goto error_exit;
139 : }
140 :
141 : // Read the records
142 0 : bytesRead = PR_Read(mMapFD, mRecordArray, recordArraySize);
143 0 : if (bytesRead < recordArraySize) {
144 0 : *corruptInfo = nsDiskCache::kNotEnoughToRead;
145 0 : goto error_exit;
146 : }
147 :
148 : // Unswap each record
149 0 : int32_t total = 0;
150 0 : for (int32_t i = 0; i < mHeader.mRecordCount; ++i) {
151 0 : if (mRecordArray[i].HashNumber()) {
152 : #if defined(IS_LITTLE_ENDIAN)
153 0 : mRecordArray[i].Unswap();
154 : #endif
155 0 : total ++;
156 : }
157 : }
158 :
159 : // verify entry count
160 0 : if (total != mHeader.mEntryCount) {
161 0 : *corruptInfo = nsDiskCache::kEntryCountIncorrect;
162 0 : goto error_exit;
163 : }
164 :
165 : } else {
166 0 : *corruptInfo = nsDiskCache::kHeaderIncomplete;
167 0 : goto error_exit;
168 : }
169 :
170 0 : rv = OpenBlockFiles(corruptInfo);
171 0 : if (NS_FAILED(rv)) {
172 : // corruptInfo is set in the call to OpenBlockFiles
173 0 : goto error_exit;
174 : }
175 :
176 : // set dirty bit and flush header
177 0 : mHeader.mIsDirty = true;
178 0 : rv = FlushHeader();
179 0 : if (NS_FAILED(rv)) {
180 0 : *corruptInfo = nsDiskCache::kFlushHeaderError;
181 0 : goto error_exit;
182 : }
183 :
184 0 : Telemetry::Accumulate(Telemetry::HTTP_DISK_CACHE_OVERHEAD,
185 0 : (uint32_t)SizeOfExcludingThis(moz_malloc_size_of));
186 :
187 0 : *corruptInfo = nsDiskCache::kNotCorrupt;
188 0 : return NS_OK;
189 :
190 : error_exit:
191 0 : (void) Close(false);
192 :
193 0 : return rv;
194 : }
195 :
196 :
197 : nsresult
198 0 : nsDiskCacheMap::Close(bool flush)
199 : {
200 0 : nsCacheService::AssertOwnsLock();
201 0 : nsresult rv = NS_OK;
202 :
203 : // Cancel any pending cache validation event, the FlushRecords call below
204 : // will validate the cache.
205 0 : if (mCleanCacheTimer) {
206 0 : mCleanCacheTimer->Cancel();
207 : }
208 :
209 : // If cache map file and its block files are still open, close them
210 0 : if (mMapFD) {
211 : // close block files
212 0 : rv = CloseBlockFiles(flush);
213 0 : if (NS_SUCCEEDED(rv) && flush && mRecordArray) {
214 : // write the map records
215 0 : rv = FlushRecords(false); // don't bother swapping buckets back
216 0 : if (NS_SUCCEEDED(rv)) {
217 : // clear dirty bit
218 0 : mHeader.mIsDirty = false;
219 0 : rv = FlushHeader();
220 : }
221 : }
222 0 : if ((PR_Close(mMapFD) != PR_SUCCESS) && (NS_SUCCEEDED(rv)))
223 0 : rv = NS_ERROR_UNEXPECTED;
224 :
225 0 : mMapFD = nullptr;
226 : }
227 :
228 0 : if (mCleanFD) {
229 0 : PR_Close(mCleanFD);
230 0 : mCleanFD = nullptr;
231 : }
232 :
233 0 : free(mRecordArray);
234 0 : mRecordArray = nullptr;
235 0 : free(mBuffer);
236 0 : mBuffer = nullptr;
237 0 : mBufferSize = 0;
238 0 : return rv;
239 : }
240 :
241 :
242 : nsresult
243 0 : nsDiskCacheMap::Trim()
244 : {
245 0 : nsresult rv, rv2 = NS_OK;
246 0 : for (int i=0; i < kNumBlockFiles; ++i) {
247 0 : rv = mBlockFile[i].Trim();
248 0 : if (NS_FAILED(rv)) rv2 = rv; // if one or more errors, report at least one
249 : }
250 : // Try to shrink the records array
251 0 : rv = ShrinkRecords();
252 0 : if (NS_FAILED(rv)) rv2 = rv; // if one or more errors, report at least one
253 0 : return rv2;
254 : }
255 :
256 :
257 : nsresult
258 0 : nsDiskCacheMap::FlushHeader()
259 : {
260 0 : if (!mMapFD) return NS_ERROR_NOT_AVAILABLE;
261 :
262 : // seek to beginning of cache map
263 0 : int32_t filePos = PR_Seek(mMapFD, 0, PR_SEEK_SET);
264 0 : if (filePos != 0) return NS_ERROR_UNEXPECTED;
265 :
266 : // write the header
267 0 : mHeader.Swap();
268 0 : int32_t bytesWritten = PR_Write(mMapFD, &mHeader, sizeof(nsDiskCacheHeader));
269 0 : mHeader.Unswap();
270 0 : if (sizeof(nsDiskCacheHeader) != bytesWritten) {
271 0 : return NS_ERROR_UNEXPECTED;
272 : }
273 :
274 0 : PRStatus err = PR_Sync(mMapFD);
275 0 : if (err != PR_SUCCESS) return NS_ERROR_UNEXPECTED;
276 :
277 : // If we have a clean header then revalidate the cache clean file
278 0 : if (!mHeader.mIsDirty) {
279 0 : RevalidateCache();
280 : }
281 :
282 0 : return NS_OK;
283 : }
284 :
285 :
286 : nsresult
287 0 : nsDiskCacheMap::FlushRecords(bool unswap)
288 : {
289 0 : if (!mMapFD) return NS_ERROR_NOT_AVAILABLE;
290 :
291 : // seek to beginning of buckets
292 0 : int32_t filePos = PR_Seek(mMapFD, sizeof(nsDiskCacheHeader), PR_SEEK_SET);
293 0 : if (filePos != sizeof(nsDiskCacheHeader))
294 0 : return NS_ERROR_UNEXPECTED;
295 :
296 : #if defined(IS_LITTLE_ENDIAN)
297 : // Swap each record
298 0 : for (int32_t i = 0; i < mHeader.mRecordCount; ++i) {
299 0 : if (mRecordArray[i].HashNumber())
300 0 : mRecordArray[i].Swap();
301 : }
302 : #endif
303 :
304 0 : int32_t recordArraySize = sizeof(nsDiskCacheRecord) * mHeader.mRecordCount;
305 :
306 0 : int32_t bytesWritten = PR_Write(mMapFD, mRecordArray, recordArraySize);
307 0 : if (bytesWritten != recordArraySize)
308 0 : return NS_ERROR_UNEXPECTED;
309 :
310 : #if defined(IS_LITTLE_ENDIAN)
311 0 : if (unswap) {
312 : // Unswap each record
313 0 : for (int32_t i = 0; i < mHeader.mRecordCount; ++i) {
314 0 : if (mRecordArray[i].HashNumber())
315 0 : mRecordArray[i].Unswap();
316 : }
317 : }
318 : #endif
319 :
320 0 : return NS_OK;
321 : }
322 :
323 :
324 : /**
325 : * Record operations
326 : */
327 :
328 : uint32_t
329 0 : nsDiskCacheMap::GetBucketRank(uint32_t bucketIndex, uint32_t targetRank)
330 : {
331 0 : nsDiskCacheRecord * records = GetFirstRecordInBucket(bucketIndex);
332 0 : uint32_t rank = 0;
333 :
334 0 : for (int i = mHeader.mBucketUsage[bucketIndex]-1; i >= 0; i--) {
335 0 : if ((rank < records[i].EvictionRank()) &&
336 0 : ((targetRank == 0) || (records[i].EvictionRank() < targetRank)))
337 0 : rank = records[i].EvictionRank();
338 : }
339 0 : return rank;
340 : }
341 :
342 : nsresult
343 0 : nsDiskCacheMap::GrowRecords()
344 : {
345 0 : if (mHeader.mRecordCount >= mMaxRecordCount)
346 0 : return NS_OK;
347 0 : CACHE_LOG_DEBUG(("CACHE: GrowRecords\n"));
348 :
349 : // Resize the record array
350 0 : int32_t newCount = mHeader.mRecordCount << 1;
351 0 : if (newCount > mMaxRecordCount)
352 0 : newCount = mMaxRecordCount;
353 : nsDiskCacheRecord* newArray = (nsDiskCacheRecord *)
354 0 : realloc(mRecordArray, newCount * sizeof(nsDiskCacheRecord));
355 0 : if (!newArray)
356 0 : return NS_ERROR_OUT_OF_MEMORY;
357 :
358 : // Space out the buckets
359 0 : uint32_t oldRecordsPerBucket = GetRecordsPerBucket();
360 0 : uint32_t newRecordsPerBucket = newCount / kBuckets;
361 : // Work from back to space out each bucket to the new array
362 0 : for (int bucketIndex = kBuckets - 1; bucketIndex >= 0; --bucketIndex) {
363 : // Move bucket
364 0 : nsDiskCacheRecord *newRecords = newArray + bucketIndex * newRecordsPerBucket;
365 0 : const uint32_t count = mHeader.mBucketUsage[bucketIndex];
366 0 : memmove(newRecords,
367 0 : newArray + bucketIndex * oldRecordsPerBucket,
368 0 : count * sizeof(nsDiskCacheRecord));
369 : // clear unused records
370 0 : memset(newRecords + count, 0,
371 0 : (newRecordsPerBucket - count) * sizeof(nsDiskCacheRecord));
372 : }
373 :
374 : // Set as the new record array
375 0 : mRecordArray = newArray;
376 0 : mHeader.mRecordCount = newCount;
377 :
378 0 : InvalidateCache();
379 :
380 0 : return NS_OK;
381 : }
382 :
383 : nsresult
384 0 : nsDiskCacheMap::ShrinkRecords()
385 : {
386 0 : if (mHeader.mRecordCount <= kMinRecordCount)
387 0 : return NS_OK;
388 0 : CACHE_LOG_DEBUG(("CACHE: ShrinkRecords\n"));
389 :
390 : // Verify if we can shrink the record array: all buckets must be less than
391 : // 1/2 filled
392 0 : uint32_t maxUsage = 0, bucketIndex;
393 0 : for (bucketIndex = 0; bucketIndex < kBuckets; ++bucketIndex) {
394 0 : if (maxUsage < mHeader.mBucketUsage[bucketIndex])
395 0 : maxUsage = mHeader.mBucketUsage[bucketIndex];
396 : }
397 : // Determine new bucket size, halve size until maxUsage
398 0 : uint32_t oldRecordsPerBucket = GetRecordsPerBucket();
399 0 : uint32_t newRecordsPerBucket = oldRecordsPerBucket;
400 0 : while (maxUsage < (newRecordsPerBucket >> 1))
401 0 : newRecordsPerBucket >>= 1;
402 0 : if (newRecordsPerBucket < (kMinRecordCount / kBuckets))
403 0 : newRecordsPerBucket = (kMinRecordCount / kBuckets);
404 0 : NS_ASSERTION(newRecordsPerBucket <= oldRecordsPerBucket,
405 : "ShrinkRecords() can't grow records!");
406 0 : if (newRecordsPerBucket == oldRecordsPerBucket)
407 0 : return NS_OK;
408 : // Move the buckets close to each other
409 0 : for (bucketIndex = 1; bucketIndex < kBuckets; ++bucketIndex) {
410 : // Move bucket
411 0 : memmove(mRecordArray + bucketIndex * newRecordsPerBucket,
412 0 : mRecordArray + bucketIndex * oldRecordsPerBucket,
413 0 : newRecordsPerBucket * sizeof(nsDiskCacheRecord));
414 : }
415 :
416 : // Shrink the record array memory block itself
417 0 : uint32_t newCount = newRecordsPerBucket * kBuckets;
418 : nsDiskCacheRecord* newArray = (nsDiskCacheRecord *)
419 0 : realloc(mRecordArray, newCount * sizeof(nsDiskCacheRecord));
420 0 : if (!newArray)
421 0 : return NS_ERROR_OUT_OF_MEMORY;
422 :
423 : // Set as the new record array
424 0 : mRecordArray = newArray;
425 0 : mHeader.mRecordCount = newCount;
426 :
427 0 : InvalidateCache();
428 :
429 0 : return NS_OK;
430 : }
431 :
432 : nsresult
433 0 : nsDiskCacheMap::AddRecord( nsDiskCacheRecord * mapRecord,
434 : nsDiskCacheRecord * oldRecord)
435 : {
436 0 : CACHE_LOG_DEBUG(("CACHE: AddRecord [%x]\n", mapRecord->HashNumber()));
437 :
438 0 : const uint32_t hashNumber = mapRecord->HashNumber();
439 0 : const uint32_t bucketIndex = GetBucketIndex(hashNumber);
440 0 : const uint32_t count = mHeader.mBucketUsage[bucketIndex];
441 :
442 0 : oldRecord->SetHashNumber(0); // signify no record
443 :
444 0 : if (count == GetRecordsPerBucket()) {
445 : // Ignore failure to grow the record space, we will then reuse old records
446 0 : GrowRecords();
447 : }
448 :
449 0 : nsDiskCacheRecord * records = GetFirstRecordInBucket(bucketIndex);
450 0 : if (count < GetRecordsPerBucket()) {
451 : // stick the new record at the end
452 0 : records[count] = *mapRecord;
453 0 : mHeader.mEntryCount++;
454 0 : mHeader.mBucketUsage[bucketIndex]++;
455 0 : if (mHeader.mEvictionRank[bucketIndex] < mapRecord->EvictionRank())
456 0 : mHeader.mEvictionRank[bucketIndex] = mapRecord->EvictionRank();
457 0 : InvalidateCache();
458 : } else {
459 : // Find the record with the highest eviction rank
460 0 : nsDiskCacheRecord * mostEvictable = &records[0];
461 0 : for (int i = count-1; i > 0; i--) {
462 0 : if (records[i].EvictionRank() > mostEvictable->EvictionRank())
463 0 : mostEvictable = &records[i];
464 : }
465 0 : *oldRecord = *mostEvictable; // i == GetRecordsPerBucket(), so
466 : // evict the mostEvictable
467 0 : *mostEvictable = *mapRecord; // replace it with the new record
468 : // check if we need to update mostEvictable entry in header
469 0 : if (mHeader.mEvictionRank[bucketIndex] < mapRecord->EvictionRank())
470 0 : mHeader.mEvictionRank[bucketIndex] = mapRecord->EvictionRank();
471 0 : if (oldRecord->EvictionRank() >= mHeader.mEvictionRank[bucketIndex])
472 0 : mHeader.mEvictionRank[bucketIndex] = GetBucketRank(bucketIndex, 0);
473 0 : InvalidateCache();
474 : }
475 :
476 0 : NS_ASSERTION(mHeader.mEvictionRank[bucketIndex] == GetBucketRank(bucketIndex, 0),
477 : "eviction rank out of sync");
478 0 : return NS_OK;
479 : }
480 :
481 :
482 : nsresult
483 0 : nsDiskCacheMap::UpdateRecord( nsDiskCacheRecord * mapRecord)
484 : {
485 0 : CACHE_LOG_DEBUG(("CACHE: UpdateRecord [%x]\n", mapRecord->HashNumber()));
486 :
487 0 : const uint32_t hashNumber = mapRecord->HashNumber();
488 0 : const uint32_t bucketIndex = GetBucketIndex(hashNumber);
489 0 : nsDiskCacheRecord * records = GetFirstRecordInBucket(bucketIndex);
490 :
491 0 : for (int i = mHeader.mBucketUsage[bucketIndex]-1; i >= 0; i--) {
492 0 : if (records[i].HashNumber() == hashNumber) {
493 0 : const uint32_t oldRank = records[i].EvictionRank();
494 :
495 : // stick the new record here
496 0 : records[i] = *mapRecord;
497 :
498 : // update eviction rank in header if necessary
499 0 : if (mHeader.mEvictionRank[bucketIndex] < mapRecord->EvictionRank())
500 0 : mHeader.mEvictionRank[bucketIndex] = mapRecord->EvictionRank();
501 0 : else if (mHeader.mEvictionRank[bucketIndex] == oldRank)
502 0 : mHeader.mEvictionRank[bucketIndex] = GetBucketRank(bucketIndex, 0);
503 :
504 0 : InvalidateCache();
505 :
506 0 : NS_ASSERTION(mHeader.mEvictionRank[bucketIndex] == GetBucketRank(bucketIndex, 0),
507 : "eviction rank out of sync");
508 0 : return NS_OK;
509 : }
510 : }
511 0 : NS_NOTREACHED("record not found");
512 0 : return NS_ERROR_UNEXPECTED;
513 : }
514 :
515 :
516 : nsresult
517 0 : nsDiskCacheMap::FindRecord( uint32_t hashNumber, nsDiskCacheRecord * result)
518 : {
519 0 : const uint32_t bucketIndex = GetBucketIndex(hashNumber);
520 0 : nsDiskCacheRecord * records = GetFirstRecordInBucket(bucketIndex);
521 :
522 0 : for (int i = mHeader.mBucketUsage[bucketIndex]-1; i >= 0; i--) {
523 0 : if (records[i].HashNumber() == hashNumber) {
524 0 : *result = records[i]; // copy the record
525 0 : NS_ASSERTION(result->ValidRecord(), "bad cache map record");
526 0 : return NS_OK;
527 : }
528 : }
529 0 : return NS_ERROR_CACHE_KEY_NOT_FOUND;
530 : }
531 :
532 :
533 : nsresult
534 0 : nsDiskCacheMap::DeleteRecord( nsDiskCacheRecord * mapRecord)
535 : {
536 0 : CACHE_LOG_DEBUG(("CACHE: DeleteRecord [%x]\n", mapRecord->HashNumber()));
537 :
538 0 : const uint32_t hashNumber = mapRecord->HashNumber();
539 0 : const uint32_t bucketIndex = GetBucketIndex(hashNumber);
540 0 : nsDiskCacheRecord * records = GetFirstRecordInBucket(bucketIndex);
541 0 : uint32_t last = mHeader.mBucketUsage[bucketIndex]-1;
542 :
543 0 : for (int i = last; i >= 0; i--) {
544 0 : if (records[i].HashNumber() == hashNumber) {
545 : // found it, now delete it.
546 0 : uint32_t evictionRank = records[i].EvictionRank();
547 0 : NS_ASSERTION(evictionRank == mapRecord->EvictionRank(),
548 : "evictionRank out of sync");
549 : // if not the last record, shift last record into opening
550 0 : records[i] = records[last];
551 0 : records[last].SetHashNumber(0); // clear last record
552 0 : mHeader.mBucketUsage[bucketIndex] = last;
553 0 : mHeader.mEntryCount--;
554 :
555 : // update eviction rank
556 0 : uint32_t bucketIndex = GetBucketIndex(mapRecord->HashNumber());
557 0 : if (mHeader.mEvictionRank[bucketIndex] <= evictionRank) {
558 0 : mHeader.mEvictionRank[bucketIndex] = GetBucketRank(bucketIndex, 0);
559 : }
560 :
561 0 : InvalidateCache();
562 :
563 0 : NS_ASSERTION(mHeader.mEvictionRank[bucketIndex] ==
564 : GetBucketRank(bucketIndex, 0), "eviction rank out of sync");
565 0 : return NS_OK;
566 : }
567 : }
568 0 : return NS_ERROR_UNEXPECTED;
569 : }
570 :
571 :
572 : int32_t
573 0 : nsDiskCacheMap::VisitEachRecord(uint32_t bucketIndex,
574 : nsDiskCacheRecordVisitor * visitor,
575 : uint32_t evictionRank)
576 : {
577 0 : int32_t rv = kVisitNextRecord;
578 0 : uint32_t count = mHeader.mBucketUsage[bucketIndex];
579 0 : nsDiskCacheRecord * records = GetFirstRecordInBucket(bucketIndex);
580 :
581 : // call visitor for each entry (matching any eviction rank)
582 0 : for (int i = count-1; i >= 0; i--) {
583 0 : if (evictionRank > records[i].EvictionRank()) continue;
584 :
585 0 : rv = visitor->VisitRecord(&records[i]);
586 0 : if (rv == kStopVisitingRecords)
587 0 : break; // Stop visiting records
588 :
589 0 : if (rv == kDeleteRecordAndContinue) {
590 0 : --count;
591 0 : records[i] = records[count];
592 0 : records[count].SetHashNumber(0);
593 0 : InvalidateCache();
594 : }
595 : }
596 :
597 0 : if (mHeader.mBucketUsage[bucketIndex] - count != 0) {
598 0 : mHeader.mEntryCount -= mHeader.mBucketUsage[bucketIndex] - count;
599 0 : mHeader.mBucketUsage[bucketIndex] = count;
600 : // recalc eviction rank
601 0 : mHeader.mEvictionRank[bucketIndex] = GetBucketRank(bucketIndex, 0);
602 : }
603 0 : NS_ASSERTION(mHeader.mEvictionRank[bucketIndex] ==
604 : GetBucketRank(bucketIndex, 0), "eviction rank out of sync");
605 :
606 0 : return rv;
607 : }
608 :
609 :
610 : /**
611 : * VisitRecords
612 : *
613 : * Visit every record in cache map in the most convenient order
614 : */
615 : nsresult
616 0 : nsDiskCacheMap::VisitRecords( nsDiskCacheRecordVisitor * visitor)
617 : {
618 0 : for (int bucketIndex = 0; bucketIndex < kBuckets; ++bucketIndex) {
619 0 : if (VisitEachRecord(bucketIndex, visitor, 0) == kStopVisitingRecords)
620 0 : break;
621 : }
622 0 : return NS_OK;
623 : }
624 :
625 :
626 : /**
627 : * EvictRecords
628 : *
629 : * Just like VisitRecords, but visits the records in order of their eviction rank
630 : */
631 : nsresult
632 0 : nsDiskCacheMap::EvictRecords( nsDiskCacheRecordVisitor * visitor)
633 : {
634 : uint32_t tempRank[kBuckets];
635 0 : int bucketIndex = 0;
636 :
637 : // copy eviction rank array
638 0 : for (bucketIndex = 0; bucketIndex < kBuckets; ++bucketIndex)
639 0 : tempRank[bucketIndex] = mHeader.mEvictionRank[bucketIndex];
640 :
641 : // Maximum number of iterations determined by number of records
642 : // as a safety limiter for the loop. Use a copy of mHeader.mEntryCount since
643 : // the value could decrease if some entry is evicted.
644 0 : int32_t entryCount = mHeader.mEntryCount;
645 0 : for (int n = 0; n < entryCount; ++n) {
646 :
647 : // find bucket with highest eviction rank
648 0 : uint32_t rank = 0;
649 0 : for (int i = 0; i < kBuckets; ++i) {
650 0 : if (rank < tempRank[i]) {
651 0 : rank = tempRank[i];
652 0 : bucketIndex = i;
653 : }
654 : }
655 :
656 0 : if (rank == 0) break; // we've examined all the records
657 :
658 : // visit records in bucket with eviction ranks >= target eviction rank
659 0 : if (VisitEachRecord(bucketIndex, visitor, rank) == kStopVisitingRecords)
660 0 : break;
661 :
662 : // find greatest rank less than 'rank'
663 0 : tempRank[bucketIndex] = GetBucketRank(bucketIndex, rank);
664 : }
665 0 : return NS_OK;
666 : }
667 :
668 :
669 :
670 : nsresult
671 0 : nsDiskCacheMap::OpenBlockFiles(nsDiskCache::CorruptCacheInfo * corruptInfo)
672 : {
673 0 : NS_ENSURE_ARG_POINTER(corruptInfo);
674 :
675 : // create nsIFile for block file
676 0 : nsCOMPtr<nsIFile> blockFile;
677 0 : nsresult rv = NS_OK;
678 0 : *corruptInfo = nsDiskCache::kUnexpectedError;
679 :
680 0 : for (int i = 0; i < kNumBlockFiles; ++i) {
681 0 : rv = GetBlockFileForIndex(i, getter_AddRefs(blockFile));
682 0 : if (NS_FAILED(rv)) {
683 0 : *corruptInfo = nsDiskCache::kCouldNotGetBlockFileForIndex;
684 0 : break;
685 : }
686 :
687 0 : uint32_t blockSize = GetBlockSizeForIndex(i+1); // +1 to match file selectors 1,2,3
688 0 : uint32_t bitMapSize = GetBitMapSizeForIndex(i+1);
689 0 : rv = mBlockFile[i].Open(blockFile, blockSize, bitMapSize, corruptInfo);
690 0 : if (NS_FAILED(rv)) {
691 : // corruptInfo was set inside the call to mBlockFile[i].Open
692 0 : break;
693 : }
694 : }
695 : // close all files in case of any error
696 0 : if (NS_FAILED(rv))
697 0 : (void)CloseBlockFiles(false); // we already have an error to report
698 :
699 0 : return rv;
700 : }
701 :
702 :
703 : nsresult
704 0 : nsDiskCacheMap::CloseBlockFiles(bool flush)
705 : {
706 0 : nsresult rv, rv2 = NS_OK;
707 0 : for (int i=0; i < kNumBlockFiles; ++i) {
708 0 : rv = mBlockFile[i].Close(flush);
709 0 : if (NS_FAILED(rv)) rv2 = rv; // if one or more errors, report at least one
710 : }
711 0 : return rv2;
712 : }
713 :
714 :
715 : bool
716 0 : nsDiskCacheMap::CacheFilesExist()
717 : {
718 0 : nsCOMPtr<nsIFile> blockFile;
719 : nsresult rv;
720 :
721 0 : for (int i = 0; i < kNumBlockFiles; ++i) {
722 : bool exists;
723 0 : rv = GetBlockFileForIndex(i, getter_AddRefs(blockFile));
724 0 : if (NS_FAILED(rv)) return false;
725 :
726 0 : rv = blockFile->Exists(&exists);
727 0 : if (NS_FAILED(rv) || !exists) return false;
728 : }
729 :
730 0 : return true;
731 : }
732 :
733 :
734 : nsresult
735 0 : nsDiskCacheMap::CreateCacheSubDirectories()
736 : {
737 0 : if (!mCacheDirectory)
738 0 : return NS_ERROR_UNEXPECTED;
739 :
740 0 : for (int32_t index = 0 ; index < 16 ; index++) {
741 0 : nsCOMPtr<nsIFile> file;
742 0 : nsresult rv = mCacheDirectory->Clone(getter_AddRefs(file));
743 0 : if (NS_FAILED(rv))
744 0 : return rv;
745 :
746 0 : rv = file->AppendNative(nsPrintfCString("%X", index));
747 0 : if (NS_FAILED(rv))
748 0 : return rv;
749 :
750 0 : rv = file->Create(nsIFile::DIRECTORY_TYPE, 0700);
751 0 : if (NS_FAILED(rv))
752 0 : return rv;
753 : }
754 :
755 0 : return NS_OK;
756 : }
757 :
758 :
759 : nsDiskCacheEntry *
760 0 : nsDiskCacheMap::ReadDiskCacheEntry(nsDiskCacheRecord * record)
761 : {
762 0 : CACHE_LOG_DEBUG(("CACHE: ReadDiskCacheEntry [%x]\n", record->HashNumber()));
763 :
764 0 : nsresult rv = NS_ERROR_UNEXPECTED;
765 0 : nsDiskCacheEntry * diskEntry = nullptr;
766 0 : uint32_t metaFile = record->MetaFile();
767 0 : int32_t bytesRead = 0;
768 :
769 0 : if (!record->MetaLocationInitialized()) return nullptr;
770 :
771 0 : if (metaFile == 0) { // entry/metadata stored in separate file
772 : // open and read the file
773 0 : nsCOMPtr<nsIFile> file;
774 0 : rv = GetLocalFileForDiskCacheRecord(record,
775 : nsDiskCache::kMetaData,
776 : false,
777 0 : getter_AddRefs(file));
778 0 : NS_ENSURE_SUCCESS(rv, nullptr);
779 :
780 0 : CACHE_LOG_DEBUG(("CACHE: nsDiskCacheMap::ReadDiskCacheEntry"
781 : "[this=%p] reading disk cache entry", this));
782 :
783 0 : PRFileDesc * fd = nullptr;
784 :
785 : // open the file - restricted to user, the data could be confidential
786 0 : rv = file->OpenNSPRFileDesc(PR_RDONLY, 00600, &fd);
787 0 : NS_ENSURE_SUCCESS(rv, nullptr);
788 :
789 0 : int32_t fileSize = PR_Available(fd);
790 0 : if (fileSize < 0) {
791 : // an error occurred. We could call PR_GetError(), but how would that help?
792 0 : rv = NS_ERROR_UNEXPECTED;
793 : } else {
794 0 : rv = EnsureBuffer(fileSize);
795 0 : if (NS_SUCCEEDED(rv)) {
796 0 : bytesRead = PR_Read(fd, mBuffer, fileSize);
797 0 : if (bytesRead < fileSize) {
798 0 : rv = NS_ERROR_UNEXPECTED;
799 : }
800 : }
801 : }
802 0 : PR_Close(fd);
803 0 : NS_ENSURE_SUCCESS(rv, nullptr);
804 :
805 0 : } else if (metaFile < (kNumBlockFiles + 1)) {
806 : // entry/metadata stored in cache block file
807 :
808 : // allocate buffer
809 0 : uint32_t blockCount = record->MetaBlockCount();
810 0 : bytesRead = blockCount * GetBlockSizeForIndex(metaFile);
811 :
812 0 : rv = EnsureBuffer(bytesRead);
813 0 : NS_ENSURE_SUCCESS(rv, nullptr);
814 :
815 : // read diskEntry, note when the blocks are at the end of file,
816 : // bytesRead may be less than blockSize*blockCount.
817 : // But the bytesRead should at least agree with the real disk entry size.
818 0 : rv = mBlockFile[metaFile - 1].ReadBlocks(mBuffer,
819 0 : record->MetaStartBlock(),
820 : blockCount,
821 0 : &bytesRead);
822 0 : NS_ENSURE_SUCCESS(rv, nullptr);
823 : }
824 0 : diskEntry = (nsDiskCacheEntry *)mBuffer;
825 0 : diskEntry->Unswap(); // disk to memory
826 : // Check if calculated size agrees with bytesRead
827 0 : if (bytesRead < 0 || (uint32_t)bytesRead < diskEntry->Size())
828 0 : return nullptr;
829 :
830 : // Return the buffer containing the diskEntry structure
831 0 : return diskEntry;
832 : }
833 :
834 :
835 : /**
836 : * CreateDiskCacheEntry(nsCacheEntry * entry)
837 : *
838 : * Prepare an nsCacheEntry for writing to disk
839 : */
840 : nsDiskCacheEntry *
841 0 : nsDiskCacheMap::CreateDiskCacheEntry(nsDiskCacheBinding * binding,
842 : uint32_t * aSize)
843 : {
844 0 : nsCacheEntry * entry = binding->mCacheEntry;
845 0 : if (!entry) return nullptr;
846 :
847 : // Store security info, if it is serializable
848 0 : nsCOMPtr<nsISupports> infoObj = entry->SecurityInfo();
849 0 : nsCOMPtr<nsISerializable> serializable = do_QueryInterface(infoObj);
850 0 : if (infoObj && !serializable) return nullptr;
851 0 : if (serializable) {
852 0 : nsCString info;
853 0 : nsresult rv = NS_SerializeToString(serializable, info);
854 0 : if (NS_FAILED(rv)) return nullptr;
855 0 : rv = entry->SetMetaDataElement("security-info", info.get());
856 0 : if (NS_FAILED(rv)) return nullptr;
857 : }
858 :
859 0 : uint32_t keySize = entry->Key()->Length() + 1;
860 0 : uint32_t metaSize = entry->MetaDataSize();
861 0 : uint32_t size = sizeof(nsDiskCacheEntry) + keySize + metaSize;
862 :
863 0 : if (aSize) *aSize = size;
864 :
865 0 : nsresult rv = EnsureBuffer(size);
866 0 : if (NS_FAILED(rv)) return nullptr;
867 :
868 0 : nsDiskCacheEntry *diskEntry = (nsDiskCacheEntry *)mBuffer;
869 0 : diskEntry->mHeaderVersion = nsDiskCache::kCurrentVersion;
870 0 : diskEntry->mMetaLocation = binding->mRecord.MetaLocation();
871 0 : diskEntry->mFetchCount = entry->FetchCount();
872 0 : diskEntry->mLastFetched = entry->LastFetched();
873 0 : diskEntry->mLastModified = entry->LastModified();
874 0 : diskEntry->mExpirationTime = entry->ExpirationTime();
875 0 : diskEntry->mDataSize = entry->DataSize();
876 0 : diskEntry->mKeySize = keySize;
877 0 : diskEntry->mMetaDataSize = metaSize;
878 :
879 0 : memcpy(diskEntry->Key(), entry->Key()->get(), keySize);
880 :
881 0 : rv = entry->FlattenMetaData(diskEntry->MetaData(), metaSize);
882 0 : if (NS_FAILED(rv)) return nullptr;
883 :
884 0 : return diskEntry;
885 : }
886 :
887 :
888 : nsresult
889 0 : nsDiskCacheMap::WriteDiskCacheEntry(nsDiskCacheBinding * binding)
890 : {
891 0 : CACHE_LOG_DEBUG(("CACHE: WriteDiskCacheEntry [%x]\n",
892 : binding->mRecord.HashNumber()));
893 :
894 0 : nsresult rv = NS_OK;
895 : uint32_t size;
896 0 : nsDiskCacheEntry * diskEntry = CreateDiskCacheEntry(binding, &size);
897 0 : if (!diskEntry) return NS_ERROR_UNEXPECTED;
898 :
899 0 : uint32_t fileIndex = CalculateFileIndex(size);
900 :
901 : // Deallocate old storage if necessary
902 0 : if (binding->mRecord.MetaLocationInitialized()) {
903 : // we have existing storage
904 :
905 0 : if ((binding->mRecord.MetaFile() == 0) &&
906 : (fileIndex == 0)) { // keeping the separate file
907 : // just decrement total
908 0 : DecrementTotalSize(binding->mRecord.MetaFileSize());
909 0 : NS_ASSERTION(binding->mRecord.MetaFileGeneration() == binding->mGeneration,
910 : "generations out of sync");
911 : } else {
912 0 : rv = DeleteStorage(&binding->mRecord, nsDiskCache::kMetaData);
913 0 : NS_ENSURE_SUCCESS(rv, rv);
914 : }
915 : }
916 :
917 0 : binding->mRecord.SetEvictionRank(ULONG_MAX - SecondsFromPRTime(PR_Now()));
918 : // write entry data to disk cache block file
919 0 : diskEntry->Swap();
920 :
921 0 : if (fileIndex != 0) {
922 : while (1) {
923 0 : uint32_t blockSize = GetBlockSizeForIndex(fileIndex);
924 0 : uint32_t blocks = ((size - 1) / blockSize) + 1;
925 :
926 : int32_t startBlock;
927 0 : rv = mBlockFile[fileIndex - 1].WriteBlocks(diskEntry, size, blocks,
928 0 : &startBlock);
929 0 : if (NS_SUCCEEDED(rv)) {
930 : // update binding and cache map record
931 0 : binding->mRecord.SetMetaBlocks(fileIndex, startBlock, blocks);
932 :
933 0 : rv = UpdateRecord(&binding->mRecord);
934 0 : NS_ENSURE_SUCCESS(rv, rv);
935 :
936 : // XXX we should probably write out bucket ourselves
937 :
938 0 : IncrementTotalSize(blocks, blockSize);
939 0 : break;
940 : }
941 :
942 0 : if (fileIndex == kNumBlockFiles) {
943 0 : fileIndex = 0; // write data to separate file
944 0 : break;
945 : }
946 :
947 : // try next block file
948 0 : fileIndex++;
949 0 : }
950 : }
951 :
952 0 : if (fileIndex == 0) {
953 : // Write entry data to separate file
954 0 : uint32_t metaFileSizeK = ((size + 0x03FF) >> 10); // round up to nearest 1k
955 0 : if (metaFileSizeK > kMaxDataSizeK)
956 0 : metaFileSizeK = kMaxDataSizeK;
957 :
958 0 : binding->mRecord.SetMetaFileGeneration(binding->mGeneration);
959 0 : binding->mRecord.SetMetaFileSize(metaFileSizeK);
960 0 : rv = UpdateRecord(&binding->mRecord);
961 0 : NS_ENSURE_SUCCESS(rv, rv);
962 :
963 0 : nsCOMPtr<nsIFile> localFile;
964 0 : rv = GetLocalFileForDiskCacheRecord(&binding->mRecord,
965 : nsDiskCache::kMetaData,
966 : true,
967 0 : getter_AddRefs(localFile));
968 0 : NS_ENSURE_SUCCESS(rv, rv);
969 :
970 : // open the file
971 : PRFileDesc * fd;
972 : // open the file - restricted to user, the data could be confidential
973 0 : rv = localFile->OpenNSPRFileDesc(PR_RDWR | PR_TRUNCATE | PR_CREATE_FILE, 00600, &fd);
974 0 : NS_ENSURE_SUCCESS(rv, rv);
975 :
976 : // write the file
977 0 : int32_t bytesWritten = PR_Write(fd, diskEntry, size);
978 :
979 0 : PRStatus err = PR_Close(fd);
980 0 : if ((bytesWritten != (int32_t)size) || (err != PR_SUCCESS)) {
981 0 : return NS_ERROR_UNEXPECTED;
982 : }
983 :
984 0 : IncrementTotalSize(metaFileSizeK);
985 : }
986 :
987 0 : return rv;
988 : }
989 :
990 :
991 : nsresult
992 0 : nsDiskCacheMap::ReadDataCacheBlocks(nsDiskCacheBinding * binding, char * buffer, uint32_t size)
993 : {
994 0 : CACHE_LOG_DEBUG(("CACHE: ReadDataCacheBlocks [%x size=%u]\n",
995 : binding->mRecord.HashNumber(), size));
996 :
997 0 : uint32_t fileIndex = binding->mRecord.DataFile();
998 0 : int32_t readSize = size;
999 :
1000 0 : nsresult rv = mBlockFile[fileIndex - 1].ReadBlocks(buffer,
1001 0 : binding->mRecord.DataStartBlock(),
1002 0 : binding->mRecord.DataBlockCount(),
1003 0 : &readSize);
1004 0 : NS_ENSURE_SUCCESS(rv, rv);
1005 0 : if (readSize < (int32_t)size) {
1006 0 : rv = NS_ERROR_UNEXPECTED;
1007 : }
1008 0 : return rv;
1009 : }
1010 :
1011 :
1012 : nsresult
1013 0 : nsDiskCacheMap::WriteDataCacheBlocks(nsDiskCacheBinding * binding, char * buffer, uint32_t size)
1014 : {
1015 0 : CACHE_LOG_DEBUG(("CACHE: WriteDataCacheBlocks [%x size=%u]\n",
1016 : binding->mRecord.HashNumber(), size));
1017 :
1018 0 : nsresult rv = NS_OK;
1019 :
1020 : // determine block file & number of blocks
1021 0 : uint32_t fileIndex = CalculateFileIndex(size);
1022 0 : uint32_t blockCount = 0;
1023 0 : int32_t startBlock = 0;
1024 :
1025 0 : if (size > 0) {
1026 : // if fileIndex is 0, bad things happen below, which makes gcc 4.7
1027 : // complain, but it's not supposed to happen. See bug 854105.
1028 0 : MOZ_ASSERT(fileIndex);
1029 0 : while (fileIndex) {
1030 0 : uint32_t blockSize = GetBlockSizeForIndex(fileIndex);
1031 0 : blockCount = ((size - 1) / blockSize) + 1;
1032 :
1033 0 : rv = mBlockFile[fileIndex - 1].WriteBlocks(buffer, size, blockCount,
1034 0 : &startBlock);
1035 0 : if (NS_SUCCEEDED(rv)) {
1036 0 : IncrementTotalSize(blockCount, blockSize);
1037 0 : break;
1038 : }
1039 :
1040 0 : if (fileIndex == kNumBlockFiles)
1041 0 : return rv;
1042 :
1043 0 : fileIndex++;
1044 : }
1045 : }
1046 :
1047 : // update binding and cache map record
1048 0 : binding->mRecord.SetDataBlocks(fileIndex, startBlock, blockCount);
1049 0 : if (!binding->mDoomed) {
1050 0 : rv = UpdateRecord(&binding->mRecord);
1051 : }
1052 0 : return rv;
1053 : }
1054 :
1055 :
1056 : nsresult
1057 0 : nsDiskCacheMap::DeleteStorage(nsDiskCacheRecord * record)
1058 : {
1059 0 : nsresult rv1 = DeleteStorage(record, nsDiskCache::kData);
1060 0 : nsresult rv2 = DeleteStorage(record, nsDiskCache::kMetaData);
1061 0 : return NS_FAILED(rv1) ? rv1 : rv2;
1062 : }
1063 :
1064 :
1065 : nsresult
1066 0 : nsDiskCacheMap::DeleteStorage(nsDiskCacheRecord * record, bool metaData)
1067 : {
1068 0 : CACHE_LOG_DEBUG(("CACHE: DeleteStorage [%x %u]\n", record->HashNumber(),
1069 : metaData));
1070 :
1071 0 : nsresult rv = NS_ERROR_UNEXPECTED;
1072 0 : uint32_t fileIndex = metaData ? record->MetaFile() : record->DataFile();
1073 0 : nsCOMPtr<nsIFile> file;
1074 :
1075 0 : if (fileIndex == 0) {
1076 : // delete the file
1077 0 : uint32_t sizeK = metaData ? record->MetaFileSize() : record->DataFileSize();
1078 : // XXX if sizeK == USHRT_MAX, stat file for actual size
1079 :
1080 0 : rv = GetFileForDiskCacheRecord(record, metaData, false, getter_AddRefs(file));
1081 0 : if (NS_SUCCEEDED(rv)) {
1082 0 : rv = file->Remove(false); // false == non-recursive
1083 : }
1084 0 : DecrementTotalSize(sizeK);
1085 :
1086 0 : } else if (fileIndex < (kNumBlockFiles + 1)) {
1087 : // deallocate blocks
1088 0 : uint32_t startBlock = metaData ? record->MetaStartBlock() : record->DataStartBlock();
1089 0 : uint32_t blockCount = metaData ? record->MetaBlockCount() : record->DataBlockCount();
1090 :
1091 0 : rv = mBlockFile[fileIndex - 1].DeallocateBlocks(startBlock, blockCount);
1092 0 : DecrementTotalSize(blockCount, GetBlockSizeForIndex(fileIndex));
1093 : }
1094 0 : if (metaData) record->ClearMetaLocation();
1095 0 : else record->ClearDataLocation();
1096 :
1097 0 : return rv;
1098 : }
1099 :
1100 :
1101 : nsresult
1102 0 : nsDiskCacheMap::GetFileForDiskCacheRecord(nsDiskCacheRecord * record,
1103 : bool meta,
1104 : bool createPath,
1105 : nsIFile ** result)
1106 : {
1107 0 : if (!mCacheDirectory) return NS_ERROR_NOT_AVAILABLE;
1108 :
1109 0 : nsCOMPtr<nsIFile> file;
1110 0 : nsresult rv = mCacheDirectory->Clone(getter_AddRefs(file));
1111 0 : if (NS_FAILED(rv)) return rv;
1112 :
1113 0 : uint32_t hash = record->HashNumber();
1114 :
1115 : // The file is stored under subdirectories according to the hash number:
1116 : // 0x01234567 -> 0/12/
1117 0 : rv = file->AppendNative(nsPrintfCString("%X", hash >> 28));
1118 0 : if (NS_FAILED(rv)) return rv;
1119 0 : rv = file->AppendNative(nsPrintfCString("%02X", (hash >> 20) & 0xFF));
1120 0 : if (NS_FAILED(rv)) return rv;
1121 :
1122 : bool exists;
1123 0 : if (createPath && (NS_FAILED(file->Exists(&exists)) || !exists)) {
1124 0 : rv = file->Create(nsIFile::DIRECTORY_TYPE, 0700);
1125 0 : if (NS_FAILED(rv)) return rv;
1126 : }
1127 :
1128 0 : int16_t generation = record->Generation();
1129 : char name[32];
1130 : // Cut the beginning of the hash that was used in the path
1131 0 : ::SprintfLiteral(name, "%05X%c%02X", hash & 0xFFFFF, (meta ? 'm' : 'd'),
1132 0 : generation);
1133 0 : rv = file->AppendNative(nsDependentCString(name));
1134 0 : if (NS_FAILED(rv)) return rv;
1135 :
1136 0 : NS_IF_ADDREF(*result = file);
1137 0 : return rv;
1138 : }
1139 :
1140 :
1141 : nsresult
1142 0 : nsDiskCacheMap::GetLocalFileForDiskCacheRecord(nsDiskCacheRecord * record,
1143 : bool meta,
1144 : bool createPath,
1145 : nsIFile ** result)
1146 : {
1147 0 : nsCOMPtr<nsIFile> file;
1148 0 : nsresult rv = GetFileForDiskCacheRecord(record,
1149 : meta,
1150 : createPath,
1151 0 : getter_AddRefs(file));
1152 0 : if (NS_FAILED(rv)) return rv;
1153 :
1154 0 : NS_IF_ADDREF(*result = file);
1155 0 : return rv;
1156 : }
1157 :
1158 :
1159 : nsresult
1160 0 : nsDiskCacheMap::GetBlockFileForIndex(uint32_t index, nsIFile ** result)
1161 : {
1162 0 : if (!mCacheDirectory) return NS_ERROR_NOT_AVAILABLE;
1163 :
1164 0 : nsCOMPtr<nsIFile> file;
1165 0 : nsresult rv = mCacheDirectory->Clone(getter_AddRefs(file));
1166 0 : if (NS_FAILED(rv)) return rv;
1167 :
1168 : char name[32];
1169 0 : ::SprintfLiteral(name, "_CACHE_%03d_", index + 1);
1170 0 : rv = file->AppendNative(nsDependentCString(name));
1171 0 : if (NS_FAILED(rv)) return rv;
1172 :
1173 0 : NS_IF_ADDREF(*result = file);
1174 :
1175 0 : return rv;
1176 : }
1177 :
1178 :
1179 : uint32_t
1180 0 : nsDiskCacheMap::CalculateFileIndex(uint32_t size)
1181 : {
1182 : // We prefer to use block file with larger block if the wasted space would
1183 : // be the same. E.g. store entry with size of 3073 bytes in 1 4K-block
1184 : // instead of in 4 1K-blocks.
1185 :
1186 0 : if (size <= 3 * BLOCK_SIZE_FOR_INDEX(1)) return 1;
1187 0 : if (size <= 3 * BLOCK_SIZE_FOR_INDEX(2)) return 2;
1188 0 : if (size <= 4 * BLOCK_SIZE_FOR_INDEX(3)) return 3;
1189 0 : return 0;
1190 : }
1191 :
1192 : nsresult
1193 0 : nsDiskCacheMap::EnsureBuffer(uint32_t bufSize)
1194 : {
1195 0 : if (mBufferSize < bufSize) {
1196 0 : char* buf = (char*) realloc(mBuffer, bufSize);
1197 0 : if (!buf) {
1198 0 : mBufferSize = 0;
1199 0 : return NS_ERROR_OUT_OF_MEMORY;
1200 : }
1201 0 : mBuffer = buf;
1202 0 : mBufferSize = bufSize;
1203 : }
1204 0 : return NS_OK;
1205 : }
1206 :
1207 : void
1208 0 : nsDiskCacheMap::NotifyCapacityChange(uint32_t capacity)
1209 : {
1210 : // Heuristic 1. average cache entry size is probably around 1KB
1211 : // Heuristic 2. we don't want more than 32MB reserved to store the record
1212 : // map in memory.
1213 0 : const int32_t RECORD_COUNT_LIMIT = 32 * 1024 * 1024 / sizeof(nsDiskCacheRecord);
1214 0 : int32_t maxRecordCount = std::min(int32_t(capacity), RECORD_COUNT_LIMIT);
1215 0 : if (mMaxRecordCount < maxRecordCount) {
1216 : // We can only grow
1217 0 : mMaxRecordCount = maxRecordCount;
1218 : }
1219 0 : }
1220 :
1221 : size_t
1222 0 : nsDiskCacheMap::SizeOfExcludingThis(MallocSizeOf aMallocSizeOf)
1223 : {
1224 0 : size_t usage = aMallocSizeOf(mRecordArray);
1225 :
1226 0 : usage += aMallocSizeOf(mBuffer);
1227 0 : usage += aMallocSizeOf(mMapFD);
1228 0 : usage += aMallocSizeOf(mCleanFD);
1229 0 : usage += aMallocSizeOf(mCacheDirectory);
1230 0 : usage += aMallocSizeOf(mCleanCacheTimer);
1231 :
1232 0 : for (int i = 0; i < kNumBlockFiles; i++) {
1233 0 : usage += mBlockFile[i].SizeOfExcludingThis(aMallocSizeOf);
1234 : }
1235 :
1236 0 : return usage;
1237 : }
1238 :
1239 : nsresult
1240 0 : nsDiskCacheMap::InitCacheClean(nsIFile * cacheDirectory,
1241 : nsDiskCache::CorruptCacheInfo * corruptInfo)
1242 : {
1243 : // The _CACHE_CLEAN_ file will be used in the future to determine
1244 : // if the cache is clean or not.
1245 0 : bool cacheCleanFileExists = false;
1246 0 : nsCOMPtr<nsIFile> cacheCleanFile;
1247 0 : nsresult rv = cacheDirectory->GetParent(getter_AddRefs(cacheCleanFile));
1248 0 : if (NS_SUCCEEDED(rv)) {
1249 0 : rv = cacheCleanFile->AppendNative(
1250 0 : NS_LITERAL_CSTRING("_CACHE_CLEAN_"));
1251 0 : if (NS_SUCCEEDED(rv)) {
1252 : // Check if the file already exists, if it does, we will later read the
1253 : // value and report it to telemetry.
1254 0 : cacheCleanFile->Exists(&cacheCleanFileExists);
1255 : }
1256 : }
1257 0 : if (NS_FAILED(rv)) {
1258 0 : NS_WARNING("Could not build cache clean file path");
1259 0 : *corruptInfo = nsDiskCache::kCacheCleanFilePathError;
1260 0 : return rv;
1261 : }
1262 :
1263 : // Make sure the _CACHE_CLEAN_ file exists
1264 0 : rv = cacheCleanFile->OpenNSPRFileDesc(PR_RDWR | PR_CREATE_FILE,
1265 0 : 00600, &mCleanFD);
1266 0 : if (NS_FAILED(rv)) {
1267 0 : NS_WARNING("Could not open cache clean file");
1268 0 : *corruptInfo = nsDiskCache::kCacheCleanOpenFileError;
1269 0 : return rv;
1270 : }
1271 :
1272 0 : if (cacheCleanFileExists) {
1273 0 : char clean = '0';
1274 0 : int32_t bytesRead = PR_Read(mCleanFD, &clean, 1);
1275 0 : if (bytesRead != 1) {
1276 0 : NS_WARNING("Could not read _CACHE_CLEAN_ file contents");
1277 : }
1278 : }
1279 :
1280 : // Create a timer that will be used to validate the cache
1281 : // as long as an activity threshold was met
1282 0 : mCleanCacheTimer = do_CreateInstance("@mozilla.org/timer;1", &rv);
1283 0 : if (NS_SUCCEEDED(rv)) {
1284 0 : mCleanCacheTimer->SetTarget(nsCacheService::GlobalInstance()->mCacheIOThread);
1285 0 : rv = ResetCacheTimer();
1286 : }
1287 :
1288 0 : if (NS_FAILED(rv)) {
1289 0 : NS_WARNING("Could not create cache clean timer");
1290 0 : mCleanCacheTimer = nullptr;
1291 0 : *corruptInfo = nsDiskCache::kCacheCleanTimerError;
1292 0 : return rv;
1293 : }
1294 :
1295 0 : return NS_OK;
1296 : }
1297 :
1298 : nsresult
1299 0 : nsDiskCacheMap::WriteCacheClean(bool clean)
1300 : {
1301 0 : nsCacheService::AssertOwnsLock();
1302 0 : if (!mCleanFD) {
1303 0 : NS_WARNING("Cache clean file is not open!");
1304 0 : return NS_ERROR_FAILURE;
1305 : }
1306 :
1307 0 : CACHE_LOG_DEBUG(("CACHE: WriteCacheClean: %d\n", clean? 1 : 0));
1308 : // I'm using a simple '1' or '0' to denote cache clean
1309 : // since it can be edited easily by any text editor for testing.
1310 0 : char data = clean? '1' : '0';
1311 0 : int32_t filePos = PR_Seek(mCleanFD, 0, PR_SEEK_SET);
1312 0 : if (filePos != 0) {
1313 0 : NS_WARNING("Could not seek in cache clean file!");
1314 0 : return NS_ERROR_FAILURE;
1315 : }
1316 0 : int32_t bytesWritten = PR_Write(mCleanFD, &data, 1);
1317 0 : if (bytesWritten != 1) {
1318 0 : NS_WARNING("Could not write cache clean file!");
1319 0 : return NS_ERROR_FAILURE;
1320 : }
1321 0 : PRStatus err = PR_Sync(mCleanFD);
1322 0 : if (err != PR_SUCCESS) {
1323 0 : NS_WARNING("Could not flush cache clean file!");
1324 : }
1325 :
1326 0 : return NS_OK;
1327 : }
1328 :
1329 : nsresult
1330 0 : nsDiskCacheMap::InvalidateCache()
1331 : {
1332 0 : nsCacheService::AssertOwnsLock();
1333 0 : CACHE_LOG_DEBUG(("CACHE: InvalidateCache\n"));
1334 : nsresult rv;
1335 :
1336 0 : if (!mIsDirtyCacheFlushed) {
1337 0 : rv = WriteCacheClean(false);
1338 0 : if (NS_FAILED(rv)) {
1339 0 : return rv;
1340 : }
1341 :
1342 0 : mIsDirtyCacheFlushed = true;
1343 : }
1344 :
1345 0 : rv = ResetCacheTimer();
1346 0 : NS_ENSURE_SUCCESS(rv, rv);
1347 :
1348 0 : return NS_OK;
1349 : }
1350 :
1351 : nsresult
1352 0 : nsDiskCacheMap::ResetCacheTimer(int32_t timeout)
1353 : {
1354 0 : mCleanCacheTimer->Cancel();
1355 0 : nsresult rv = mCleanCacheTimer->InitWithNamedFuncCallback(
1356 : RevalidateTimerCallback,
1357 : nullptr,
1358 : timeout,
1359 : nsITimer::TYPE_ONE_SHOT,
1360 0 : "nsDiskCacheMap::ResetCacheTimer");
1361 0 : NS_ENSURE_SUCCESS(rv, rv);
1362 0 : mLastInvalidateTime = PR_IntervalNow();
1363 :
1364 0 : return rv;
1365 : }
1366 :
1367 : void
1368 0 : nsDiskCacheMap::RevalidateTimerCallback(nsITimer *aTimer, void *arg)
1369 : {
1370 0 : nsCacheServiceAutoLock lock;
1371 0 : if (!nsCacheService::gService->mDiskDevice ||
1372 0 : !nsCacheService::gService->mDiskDevice->Initialized()) {
1373 0 : return;
1374 : }
1375 :
1376 : nsDiskCacheMap *diskCacheMap =
1377 0 : &nsCacheService::gService->mDiskDevice->mCacheMap;
1378 :
1379 : // If we have less than kRevalidateCacheTimeout since the last timer was
1380 : // issued then another thread called InvalidateCache. This won't catch
1381 : // all cases where we wanted to cancel the timer, but under the lock it
1382 : // is always OK to revalidate as long as IsCacheInSafeState() returns
1383 : // true. We just want to avoid revalidating when we can to reduce IO
1384 : // and this check will do that.
1385 : uint32_t delta =
1386 0 : PR_IntervalToMilliseconds(PR_IntervalNow() -
1387 0 : diskCacheMap->mLastInvalidateTime) +
1388 0 : kRevalidateCacheTimeoutTolerance;
1389 0 : if (delta < kRevalidateCacheTimeout) {
1390 0 : diskCacheMap->ResetCacheTimer();
1391 0 : return;
1392 : }
1393 :
1394 0 : nsresult rv = diskCacheMap->RevalidateCache();
1395 0 : if (NS_FAILED(rv)) {
1396 0 : diskCacheMap->ResetCacheTimer(kRevalidateCacheErrorTimeout);
1397 : }
1398 : }
1399 :
1400 : bool
1401 0 : nsDiskCacheMap::IsCacheInSafeState()
1402 : {
1403 0 : return nsCacheService::GlobalInstance()->IsDoomListEmpty();
1404 : }
1405 :
1406 : nsresult
1407 0 : nsDiskCacheMap::RevalidateCache()
1408 : {
1409 0 : CACHE_LOG_DEBUG(("CACHE: RevalidateCache\n"));
1410 : nsresult rv;
1411 :
1412 0 : if (!IsCacheInSafeState()) {
1413 0 : CACHE_LOG_DEBUG(("CACHE: Revalidation should not performed because "
1414 : "cache not in a safe state\n"));
1415 : // Normally we would return an error here, but there is a bug where
1416 : // the doom list sometimes gets an entry 'stuck' and doens't clear it
1417 : // until browser shutdown. So we allow revalidation for the time being
1418 : // to get proper telemetry data of how much the cache corruption plan
1419 : // would help.
1420 : }
1421 :
1422 : // If telemetry data shows it is worth it, we'll be flushing headers and
1423 : // records before flushing the clean cache file.
1424 :
1425 : // Write out the _CACHE_CLEAN_ file with '1'
1426 0 : rv = WriteCacheClean(true);
1427 0 : if (NS_FAILED(rv)) {
1428 0 : return rv;
1429 : }
1430 :
1431 0 : mIsDirtyCacheFlushed = false;
1432 :
1433 0 : return NS_OK;
1434 : }
|