LCOV - code coverage report
Current view: directory - netwerk/cache - nsDiskCacheMap.cpp (source / functions) Found Hit Coverage
Test: app.info Lines: 556 385 69.2 %
Date: 2012-06-02 Functions: 32 28 87.5 %

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

Generated by: LCOV version 1.7