LCOV - code coverage report
Current view: top level - netwerk/cache - nsDiskCacheMap.cpp (source / functions) Hit Total Coverage
Test: output.info Lines: 0 722 0.0 %
Date: 2017-07-14 16:53:18 Functions: 0 40 0.0 %
Legend: Lines: hit not hit

          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             : }

Generated by: LCOV version 1.13