1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/netwerk/cache/nsDiskCacheMap.cpp Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,1458 @@ 1.4 +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ 1.5 +/* vim:set ts=4 sw=4 sts=4 cin et: */ 1.6 +/* This Source Code Form is subject to the terms of the Mozilla Public 1.7 + * License, v. 2.0. If a copy of the MPL was not distributed with this 1.8 + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 1.9 + 1.10 +#include "nsCache.h" 1.11 +#include "nsDiskCacheMap.h" 1.12 +#include "nsDiskCacheBinding.h" 1.13 +#include "nsDiskCacheEntry.h" 1.14 +#include "nsDiskCacheDevice.h" 1.15 +#include "nsCacheService.h" 1.16 + 1.17 +#include <string.h> 1.18 +#include "nsPrintfCString.h" 1.19 + 1.20 +#include "nsISerializable.h" 1.21 +#include "nsSerializationHelper.h" 1.22 + 1.23 +#include "mozilla/MemoryReporting.h" 1.24 +#include "mozilla/Telemetry.h" 1.25 +#include "mozilla/VisualEventTracer.h" 1.26 +#include <algorithm> 1.27 + 1.28 +using namespace mozilla; 1.29 + 1.30 +/****************************************************************************** 1.31 + * nsDiskCacheMap 1.32 + *****************************************************************************/ 1.33 + 1.34 +/** 1.35 + * File operations 1.36 + */ 1.37 + 1.38 +nsresult 1.39 +nsDiskCacheMap::Open(nsIFile * cacheDirectory, 1.40 + nsDiskCache::CorruptCacheInfo * corruptInfo, 1.41 + bool reportCacheCleanTelemetryData) 1.42 +{ 1.43 + NS_ENSURE_ARG_POINTER(corruptInfo); 1.44 + 1.45 + // Assume we have an unexpected error until we find otherwise. 1.46 + *corruptInfo = nsDiskCache::kUnexpectedError; 1.47 + NS_ENSURE_ARG_POINTER(cacheDirectory); 1.48 + if (mMapFD) return NS_ERROR_ALREADY_INITIALIZED; 1.49 + 1.50 + mCacheDirectory = cacheDirectory; // save a reference for ourselves 1.51 + 1.52 + // create nsIFile for _CACHE_MAP_ 1.53 + nsresult rv; 1.54 + nsCOMPtr<nsIFile> file; 1.55 + rv = cacheDirectory->Clone(getter_AddRefs(file)); 1.56 + rv = file->AppendNative(NS_LITERAL_CSTRING("_CACHE_MAP_")); 1.57 + NS_ENSURE_SUCCESS(rv, rv); 1.58 + 1.59 + // open the file - restricted to user, the data could be confidential 1.60 + rv = file->OpenNSPRFileDesc(PR_RDWR | PR_CREATE_FILE, 00600, &mMapFD); 1.61 + if (NS_FAILED(rv)) { 1.62 + *corruptInfo = nsDiskCache::kOpenCacheMapError; 1.63 + NS_WARNING("Could not open cache map file"); 1.64 + return NS_ERROR_FILE_CORRUPTED; 1.65 + } 1.66 + 1.67 + bool cacheFilesExist = CacheFilesExist(); 1.68 + rv = NS_ERROR_FILE_CORRUPTED; // presume the worst 1.69 + uint32_t mapSize = PR_Available(mMapFD); 1.70 + 1.71 + if (NS_FAILED(InitCacheClean(cacheDirectory, 1.72 + corruptInfo, 1.73 + reportCacheCleanTelemetryData))) { 1.74 + // corruptInfo is set in the call to InitCacheClean 1.75 + goto error_exit; 1.76 + } 1.77 + 1.78 + // check size of map file 1.79 + if (mapSize == 0) { // creating a new _CACHE_MAP_ 1.80 + 1.81 + // block files shouldn't exist if we're creating the _CACHE_MAP_ 1.82 + if (cacheFilesExist) { 1.83 + *corruptInfo = nsDiskCache::kBlockFilesShouldNotExist; 1.84 + goto error_exit; 1.85 + } 1.86 + 1.87 + if (NS_FAILED(CreateCacheSubDirectories())) { 1.88 + *corruptInfo = nsDiskCache::kCreateCacheSubdirectories; 1.89 + goto error_exit; 1.90 + } 1.91 + 1.92 + // create the file - initialize in memory 1.93 + memset(&mHeader, 0, sizeof(nsDiskCacheHeader)); 1.94 + mHeader.mVersion = nsDiskCache::kCurrentVersion; 1.95 + mHeader.mRecordCount = kMinRecordCount; 1.96 + mRecordArray = (nsDiskCacheRecord *) 1.97 + PR_CALLOC(mHeader.mRecordCount * sizeof(nsDiskCacheRecord)); 1.98 + if (!mRecordArray) { 1.99 + *corruptInfo = nsDiskCache::kOutOfMemory; 1.100 + rv = NS_ERROR_OUT_OF_MEMORY; 1.101 + goto error_exit; 1.102 + } 1.103 + } else if (mapSize >= sizeof(nsDiskCacheHeader)) { // read existing _CACHE_MAP_ 1.104 + 1.105 + // if _CACHE_MAP_ exists, so should the block files 1.106 + if (!cacheFilesExist) { 1.107 + *corruptInfo = nsDiskCache::kBlockFilesShouldExist; 1.108 + goto error_exit; 1.109 + } 1.110 + 1.111 + CACHE_LOG_DEBUG(("CACHE: nsDiskCacheMap::Open [this=%p] reading map", this)); 1.112 + 1.113 + // read the header 1.114 + uint32_t bytesRead = PR_Read(mMapFD, &mHeader, sizeof(nsDiskCacheHeader)); 1.115 + if (sizeof(nsDiskCacheHeader) != bytesRead) { 1.116 + *corruptInfo = nsDiskCache::kHeaderSizeNotRead; 1.117 + goto error_exit; 1.118 + } 1.119 + mHeader.Unswap(); 1.120 + 1.121 + if (mHeader.mIsDirty) { 1.122 + *corruptInfo = nsDiskCache::kHeaderIsDirty; 1.123 + goto error_exit; 1.124 + } 1.125 + 1.126 + if (mHeader.mVersion != nsDiskCache::kCurrentVersion) { 1.127 + *corruptInfo = nsDiskCache::kVersionMismatch; 1.128 + goto error_exit; 1.129 + } 1.130 + 1.131 + uint32_t recordArraySize = 1.132 + mHeader.mRecordCount * sizeof(nsDiskCacheRecord); 1.133 + if (mapSize < recordArraySize + sizeof(nsDiskCacheHeader)) { 1.134 + *corruptInfo = nsDiskCache::kRecordsIncomplete; 1.135 + goto error_exit; 1.136 + } 1.137 + 1.138 + // Get the space for the records 1.139 + mRecordArray = (nsDiskCacheRecord *) PR_MALLOC(recordArraySize); 1.140 + if (!mRecordArray) { 1.141 + *corruptInfo = nsDiskCache::kOutOfMemory; 1.142 + rv = NS_ERROR_OUT_OF_MEMORY; 1.143 + goto error_exit; 1.144 + } 1.145 + 1.146 + // Read the records 1.147 + bytesRead = PR_Read(mMapFD, mRecordArray, recordArraySize); 1.148 + if (bytesRead < recordArraySize) { 1.149 + *corruptInfo = nsDiskCache::kNotEnoughToRead; 1.150 + goto error_exit; 1.151 + } 1.152 + 1.153 + // Unswap each record 1.154 + int32_t total = 0; 1.155 + for (int32_t i = 0; i < mHeader.mRecordCount; ++i) { 1.156 + if (mRecordArray[i].HashNumber()) { 1.157 +#if defined(IS_LITTLE_ENDIAN) 1.158 + mRecordArray[i].Unswap(); 1.159 +#endif 1.160 + total ++; 1.161 + } 1.162 + } 1.163 + 1.164 + // verify entry count 1.165 + if (total != mHeader.mEntryCount) { 1.166 + *corruptInfo = nsDiskCache::kEntryCountIncorrect; 1.167 + goto error_exit; 1.168 + } 1.169 + 1.170 + } else { 1.171 + *corruptInfo = nsDiskCache::kHeaderIncomplete; 1.172 + goto error_exit; 1.173 + } 1.174 + 1.175 + rv = OpenBlockFiles(corruptInfo); 1.176 + if (NS_FAILED(rv)) { 1.177 + // corruptInfo is set in the call to OpenBlockFiles 1.178 + goto error_exit; 1.179 + } 1.180 + 1.181 + // set dirty bit and flush header 1.182 + mHeader.mIsDirty = true; 1.183 + rv = FlushHeader(); 1.184 + if (NS_FAILED(rv)) { 1.185 + *corruptInfo = nsDiskCache::kFlushHeaderError; 1.186 + goto error_exit; 1.187 + } 1.188 + 1.189 + Telemetry::Accumulate(Telemetry::HTTP_DISK_CACHE_OVERHEAD, 1.190 + (uint32_t)SizeOfExcludingThis(moz_malloc_size_of)); 1.191 + 1.192 + *corruptInfo = nsDiskCache::kNotCorrupt; 1.193 + return NS_OK; 1.194 + 1.195 +error_exit: 1.196 + (void) Close(false); 1.197 + 1.198 + return rv; 1.199 +} 1.200 + 1.201 + 1.202 +nsresult 1.203 +nsDiskCacheMap::Close(bool flush) 1.204 +{ 1.205 + nsCacheService::AssertOwnsLock(); 1.206 + nsresult rv = NS_OK; 1.207 + 1.208 + // Cancel any pending cache validation event, the FlushRecords call below 1.209 + // will validate the cache. 1.210 + if (mCleanCacheTimer) { 1.211 + mCleanCacheTimer->Cancel(); 1.212 + } 1.213 + 1.214 + // If cache map file and its block files are still open, close them 1.215 + if (mMapFD) { 1.216 + // close block files 1.217 + rv = CloseBlockFiles(flush); 1.218 + if (NS_SUCCEEDED(rv) && flush && mRecordArray) { 1.219 + // write the map records 1.220 + rv = FlushRecords(false); // don't bother swapping buckets back 1.221 + if (NS_SUCCEEDED(rv)) { 1.222 + // clear dirty bit 1.223 + mHeader.mIsDirty = false; 1.224 + rv = FlushHeader(); 1.225 + } 1.226 + } 1.227 + if ((PR_Close(mMapFD) != PR_SUCCESS) && (NS_SUCCEEDED(rv))) 1.228 + rv = NS_ERROR_UNEXPECTED; 1.229 + 1.230 + mMapFD = nullptr; 1.231 + } 1.232 + 1.233 + if (mCleanFD) { 1.234 + PR_Close(mCleanFD); 1.235 + mCleanFD = nullptr; 1.236 + } 1.237 + 1.238 + PR_FREEIF(mRecordArray); 1.239 + PR_FREEIF(mBuffer); 1.240 + mBufferSize = 0; 1.241 + return rv; 1.242 +} 1.243 + 1.244 + 1.245 +nsresult 1.246 +nsDiskCacheMap::Trim() 1.247 +{ 1.248 + nsresult rv, rv2 = NS_OK; 1.249 + for (int i=0; i < kNumBlockFiles; ++i) { 1.250 + rv = mBlockFile[i].Trim(); 1.251 + if (NS_FAILED(rv)) rv2 = rv; // if one or more errors, report at least one 1.252 + } 1.253 + // Try to shrink the records array 1.254 + rv = ShrinkRecords(); 1.255 + if (NS_FAILED(rv)) rv2 = rv; // if one or more errors, report at least one 1.256 + return rv2; 1.257 +} 1.258 + 1.259 + 1.260 +nsresult 1.261 +nsDiskCacheMap::FlushHeader() 1.262 +{ 1.263 + if (!mMapFD) return NS_ERROR_NOT_AVAILABLE; 1.264 + 1.265 + // seek to beginning of cache map 1.266 + int32_t filePos = PR_Seek(mMapFD, 0, PR_SEEK_SET); 1.267 + if (filePos != 0) return NS_ERROR_UNEXPECTED; 1.268 + 1.269 + // write the header 1.270 + mHeader.Swap(); 1.271 + int32_t bytesWritten = PR_Write(mMapFD, &mHeader, sizeof(nsDiskCacheHeader)); 1.272 + mHeader.Unswap(); 1.273 + if (sizeof(nsDiskCacheHeader) != bytesWritten) { 1.274 + return NS_ERROR_UNEXPECTED; 1.275 + } 1.276 + 1.277 + PRStatus err = PR_Sync(mMapFD); 1.278 + if (err != PR_SUCCESS) return NS_ERROR_UNEXPECTED; 1.279 + 1.280 + // If we have a clean header then revalidate the cache clean file 1.281 + if (!mHeader.mIsDirty) { 1.282 + RevalidateCache(); 1.283 + } 1.284 + 1.285 + return NS_OK; 1.286 +} 1.287 + 1.288 + 1.289 +nsresult 1.290 +nsDiskCacheMap::FlushRecords(bool unswap) 1.291 +{ 1.292 + if (!mMapFD) return NS_ERROR_NOT_AVAILABLE; 1.293 + 1.294 + // seek to beginning of buckets 1.295 + int32_t filePos = PR_Seek(mMapFD, sizeof(nsDiskCacheHeader), PR_SEEK_SET); 1.296 + if (filePos != sizeof(nsDiskCacheHeader)) 1.297 + return NS_ERROR_UNEXPECTED; 1.298 + 1.299 +#if defined(IS_LITTLE_ENDIAN) 1.300 + // Swap each record 1.301 + for (int32_t i = 0; i < mHeader.mRecordCount; ++i) { 1.302 + if (mRecordArray[i].HashNumber()) 1.303 + mRecordArray[i].Swap(); 1.304 + } 1.305 +#endif 1.306 + 1.307 + int32_t recordArraySize = sizeof(nsDiskCacheRecord) * mHeader.mRecordCount; 1.308 + 1.309 + int32_t bytesWritten = PR_Write(mMapFD, mRecordArray, recordArraySize); 1.310 + if (bytesWritten != recordArraySize) 1.311 + return NS_ERROR_UNEXPECTED; 1.312 + 1.313 +#if defined(IS_LITTLE_ENDIAN) 1.314 + if (unswap) { 1.315 + // Unswap each record 1.316 + for (int32_t i = 0; i < mHeader.mRecordCount; ++i) { 1.317 + if (mRecordArray[i].HashNumber()) 1.318 + mRecordArray[i].Unswap(); 1.319 + } 1.320 + } 1.321 +#endif 1.322 + 1.323 + return NS_OK; 1.324 +} 1.325 + 1.326 + 1.327 +/** 1.328 + * Record operations 1.329 + */ 1.330 + 1.331 +uint32_t 1.332 +nsDiskCacheMap::GetBucketRank(uint32_t bucketIndex, uint32_t targetRank) 1.333 +{ 1.334 + nsDiskCacheRecord * records = GetFirstRecordInBucket(bucketIndex); 1.335 + uint32_t rank = 0; 1.336 + 1.337 + for (int i = mHeader.mBucketUsage[bucketIndex]-1; i >= 0; i--) { 1.338 + if ((rank < records[i].EvictionRank()) && 1.339 + ((targetRank == 0) || (records[i].EvictionRank() < targetRank))) 1.340 + rank = records[i].EvictionRank(); 1.341 + } 1.342 + return rank; 1.343 +} 1.344 + 1.345 +nsresult 1.346 +nsDiskCacheMap::GrowRecords() 1.347 +{ 1.348 + if (mHeader.mRecordCount >= mMaxRecordCount) 1.349 + return NS_OK; 1.350 + CACHE_LOG_DEBUG(("CACHE: GrowRecords\n")); 1.351 + 1.352 + // Resize the record array 1.353 + int32_t newCount = mHeader.mRecordCount << 1; 1.354 + if (newCount > mMaxRecordCount) 1.355 + newCount = mMaxRecordCount; 1.356 + nsDiskCacheRecord *newArray = (nsDiskCacheRecord *) 1.357 + PR_REALLOC(mRecordArray, newCount * sizeof(nsDiskCacheRecord)); 1.358 + if (!newArray) 1.359 + return NS_ERROR_OUT_OF_MEMORY; 1.360 + 1.361 + // Space out the buckets 1.362 + uint32_t oldRecordsPerBucket = GetRecordsPerBucket(); 1.363 + uint32_t newRecordsPerBucket = newCount / kBuckets; 1.364 + // Work from back to space out each bucket to the new array 1.365 + for (int bucketIndex = kBuckets - 1; bucketIndex >= 0; --bucketIndex) { 1.366 + // Move bucket 1.367 + nsDiskCacheRecord *newRecords = newArray + bucketIndex * newRecordsPerBucket; 1.368 + const uint32_t count = mHeader.mBucketUsage[bucketIndex]; 1.369 + memmove(newRecords, 1.370 + newArray + bucketIndex * oldRecordsPerBucket, 1.371 + count * sizeof(nsDiskCacheRecord)); 1.372 + // clear unused records 1.373 + memset(newRecords + count, 0, 1.374 + (newRecordsPerBucket - count) * sizeof(nsDiskCacheRecord)); 1.375 + } 1.376 + 1.377 + // Set as the new record array 1.378 + mRecordArray = newArray; 1.379 + mHeader.mRecordCount = newCount; 1.380 + 1.381 + InvalidateCache(); 1.382 + 1.383 + return NS_OK; 1.384 +} 1.385 + 1.386 +nsresult 1.387 +nsDiskCacheMap::ShrinkRecords() 1.388 +{ 1.389 + if (mHeader.mRecordCount <= kMinRecordCount) 1.390 + return NS_OK; 1.391 + CACHE_LOG_DEBUG(("CACHE: ShrinkRecords\n")); 1.392 + 1.393 + // Verify if we can shrink the record array: all buckets must be less than 1.394 + // 1/2 filled 1.395 + uint32_t maxUsage = 0, bucketIndex; 1.396 + for (bucketIndex = 0; bucketIndex < kBuckets; ++bucketIndex) { 1.397 + if (maxUsage < mHeader.mBucketUsage[bucketIndex]) 1.398 + maxUsage = mHeader.mBucketUsage[bucketIndex]; 1.399 + } 1.400 + // Determine new bucket size, halve size until maxUsage 1.401 + uint32_t oldRecordsPerBucket = GetRecordsPerBucket(); 1.402 + uint32_t newRecordsPerBucket = oldRecordsPerBucket; 1.403 + while (maxUsage < (newRecordsPerBucket >> 1)) 1.404 + newRecordsPerBucket >>= 1; 1.405 + if (newRecordsPerBucket < (kMinRecordCount / kBuckets)) 1.406 + newRecordsPerBucket = (kMinRecordCount / kBuckets); 1.407 + NS_ASSERTION(newRecordsPerBucket <= oldRecordsPerBucket, 1.408 + "ShrinkRecords() can't grow records!"); 1.409 + if (newRecordsPerBucket == oldRecordsPerBucket) 1.410 + return NS_OK; 1.411 + // Move the buckets close to each other 1.412 + for (bucketIndex = 1; bucketIndex < kBuckets; ++bucketIndex) { 1.413 + // Move bucket 1.414 + memmove(mRecordArray + bucketIndex * newRecordsPerBucket, 1.415 + mRecordArray + bucketIndex * oldRecordsPerBucket, 1.416 + newRecordsPerBucket * sizeof(nsDiskCacheRecord)); 1.417 + } 1.418 + 1.419 + // Shrink the record array memory block itself 1.420 + uint32_t newCount = newRecordsPerBucket * kBuckets; 1.421 + nsDiskCacheRecord* newArray = (nsDiskCacheRecord *) 1.422 + PR_REALLOC(mRecordArray, newCount * sizeof(nsDiskCacheRecord)); 1.423 + if (!newArray) 1.424 + return NS_ERROR_OUT_OF_MEMORY; 1.425 + 1.426 + // Set as the new record array 1.427 + mRecordArray = newArray; 1.428 + mHeader.mRecordCount = newCount; 1.429 + 1.430 + InvalidateCache(); 1.431 + 1.432 + return NS_OK; 1.433 +} 1.434 + 1.435 +nsresult 1.436 +nsDiskCacheMap::AddRecord( nsDiskCacheRecord * mapRecord, 1.437 + nsDiskCacheRecord * oldRecord) 1.438 +{ 1.439 + CACHE_LOG_DEBUG(("CACHE: AddRecord [%x]\n", mapRecord->HashNumber())); 1.440 + 1.441 + const uint32_t hashNumber = mapRecord->HashNumber(); 1.442 + const uint32_t bucketIndex = GetBucketIndex(hashNumber); 1.443 + const uint32_t count = mHeader.mBucketUsage[bucketIndex]; 1.444 + 1.445 + oldRecord->SetHashNumber(0); // signify no record 1.446 + 1.447 + if (count == GetRecordsPerBucket()) { 1.448 + // Ignore failure to grow the record space, we will then reuse old records 1.449 + GrowRecords(); 1.450 + } 1.451 + 1.452 + nsDiskCacheRecord * records = GetFirstRecordInBucket(bucketIndex); 1.453 + if (count < GetRecordsPerBucket()) { 1.454 + // stick the new record at the end 1.455 + records[count] = *mapRecord; 1.456 + mHeader.mEntryCount++; 1.457 + mHeader.mBucketUsage[bucketIndex]++; 1.458 + if (mHeader.mEvictionRank[bucketIndex] < mapRecord->EvictionRank()) 1.459 + mHeader.mEvictionRank[bucketIndex] = mapRecord->EvictionRank(); 1.460 + InvalidateCache(); 1.461 + } else { 1.462 + // Find the record with the highest eviction rank 1.463 + nsDiskCacheRecord * mostEvictable = &records[0]; 1.464 + for (int i = count-1; i > 0; i--) { 1.465 + if (records[i].EvictionRank() > mostEvictable->EvictionRank()) 1.466 + mostEvictable = &records[i]; 1.467 + } 1.468 + *oldRecord = *mostEvictable; // i == GetRecordsPerBucket(), so 1.469 + // evict the mostEvictable 1.470 + *mostEvictable = *mapRecord; // replace it with the new record 1.471 + // check if we need to update mostEvictable entry in header 1.472 + if (mHeader.mEvictionRank[bucketIndex] < mapRecord->EvictionRank()) 1.473 + mHeader.mEvictionRank[bucketIndex] = mapRecord->EvictionRank(); 1.474 + if (oldRecord->EvictionRank() >= mHeader.mEvictionRank[bucketIndex]) 1.475 + mHeader.mEvictionRank[bucketIndex] = GetBucketRank(bucketIndex, 0); 1.476 + InvalidateCache(); 1.477 + } 1.478 + 1.479 + NS_ASSERTION(mHeader.mEvictionRank[bucketIndex] == GetBucketRank(bucketIndex, 0), 1.480 + "eviction rank out of sync"); 1.481 + return NS_OK; 1.482 +} 1.483 + 1.484 + 1.485 +nsresult 1.486 +nsDiskCacheMap::UpdateRecord( nsDiskCacheRecord * mapRecord) 1.487 +{ 1.488 + CACHE_LOG_DEBUG(("CACHE: UpdateRecord [%x]\n", mapRecord->HashNumber())); 1.489 + 1.490 + const uint32_t hashNumber = mapRecord->HashNumber(); 1.491 + const uint32_t bucketIndex = GetBucketIndex(hashNumber); 1.492 + nsDiskCacheRecord * records = GetFirstRecordInBucket(bucketIndex); 1.493 + 1.494 + for (int i = mHeader.mBucketUsage[bucketIndex]-1; i >= 0; i--) { 1.495 + if (records[i].HashNumber() == hashNumber) { 1.496 + const uint32_t oldRank = records[i].EvictionRank(); 1.497 + 1.498 + // stick the new record here 1.499 + records[i] = *mapRecord; 1.500 + 1.501 + // update eviction rank in header if necessary 1.502 + if (mHeader.mEvictionRank[bucketIndex] < mapRecord->EvictionRank()) 1.503 + mHeader.mEvictionRank[bucketIndex] = mapRecord->EvictionRank(); 1.504 + else if (mHeader.mEvictionRank[bucketIndex] == oldRank) 1.505 + mHeader.mEvictionRank[bucketIndex] = GetBucketRank(bucketIndex, 0); 1.506 + 1.507 + InvalidateCache(); 1.508 + 1.509 +NS_ASSERTION(mHeader.mEvictionRank[bucketIndex] == GetBucketRank(bucketIndex, 0), 1.510 + "eviction rank out of sync"); 1.511 + return NS_OK; 1.512 + } 1.513 + } 1.514 + NS_NOTREACHED("record not found"); 1.515 + return NS_ERROR_UNEXPECTED; 1.516 +} 1.517 + 1.518 + 1.519 +nsresult 1.520 +nsDiskCacheMap::FindRecord( uint32_t hashNumber, nsDiskCacheRecord * result) 1.521 +{ 1.522 + const uint32_t bucketIndex = GetBucketIndex(hashNumber); 1.523 + nsDiskCacheRecord * records = GetFirstRecordInBucket(bucketIndex); 1.524 + 1.525 + for (int i = mHeader.mBucketUsage[bucketIndex]-1; i >= 0; i--) { 1.526 + if (records[i].HashNumber() == hashNumber) { 1.527 + *result = records[i]; // copy the record 1.528 + NS_ASSERTION(result->ValidRecord(), "bad cache map record"); 1.529 + return NS_OK; 1.530 + } 1.531 + } 1.532 + return NS_ERROR_CACHE_KEY_NOT_FOUND; 1.533 +} 1.534 + 1.535 + 1.536 +nsresult 1.537 +nsDiskCacheMap::DeleteRecord( nsDiskCacheRecord * mapRecord) 1.538 +{ 1.539 + CACHE_LOG_DEBUG(("CACHE: DeleteRecord [%x]\n", mapRecord->HashNumber())); 1.540 + 1.541 + const uint32_t hashNumber = mapRecord->HashNumber(); 1.542 + const uint32_t bucketIndex = GetBucketIndex(hashNumber); 1.543 + nsDiskCacheRecord * records = GetFirstRecordInBucket(bucketIndex); 1.544 + uint32_t last = mHeader.mBucketUsage[bucketIndex]-1; 1.545 + 1.546 + for (int i = last; i >= 0; i--) { 1.547 + if (records[i].HashNumber() == hashNumber) { 1.548 + // found it, now delete it. 1.549 + uint32_t evictionRank = records[i].EvictionRank(); 1.550 + NS_ASSERTION(evictionRank == mapRecord->EvictionRank(), 1.551 + "evictionRank out of sync"); 1.552 + // if not the last record, shift last record into opening 1.553 + records[i] = records[last]; 1.554 + records[last].SetHashNumber(0); // clear last record 1.555 + mHeader.mBucketUsage[bucketIndex] = last; 1.556 + mHeader.mEntryCount--; 1.557 + 1.558 + // update eviction rank 1.559 + uint32_t bucketIndex = GetBucketIndex(mapRecord->HashNumber()); 1.560 + if (mHeader.mEvictionRank[bucketIndex] <= evictionRank) { 1.561 + mHeader.mEvictionRank[bucketIndex] = GetBucketRank(bucketIndex, 0); 1.562 + } 1.563 + 1.564 + InvalidateCache(); 1.565 + 1.566 + NS_ASSERTION(mHeader.mEvictionRank[bucketIndex] == 1.567 + GetBucketRank(bucketIndex, 0), "eviction rank out of sync"); 1.568 + return NS_OK; 1.569 + } 1.570 + } 1.571 + return NS_ERROR_UNEXPECTED; 1.572 +} 1.573 + 1.574 + 1.575 +int32_t 1.576 +nsDiskCacheMap::VisitEachRecord(uint32_t bucketIndex, 1.577 + nsDiskCacheRecordVisitor * visitor, 1.578 + uint32_t evictionRank) 1.579 +{ 1.580 + int32_t rv = kVisitNextRecord; 1.581 + uint32_t count = mHeader.mBucketUsage[bucketIndex]; 1.582 + nsDiskCacheRecord * records = GetFirstRecordInBucket(bucketIndex); 1.583 + 1.584 + // call visitor for each entry (matching any eviction rank) 1.585 + for (int i = count-1; i >= 0; i--) { 1.586 + if (evictionRank > records[i].EvictionRank()) continue; 1.587 + 1.588 + rv = visitor->VisitRecord(&records[i]); 1.589 + if (rv == kStopVisitingRecords) 1.590 + break; // Stop visiting records 1.591 + 1.592 + if (rv == kDeleteRecordAndContinue) { 1.593 + --count; 1.594 + records[i] = records[count]; 1.595 + records[count].SetHashNumber(0); 1.596 + InvalidateCache(); 1.597 + } 1.598 + } 1.599 + 1.600 + if (mHeader.mBucketUsage[bucketIndex] - count != 0) { 1.601 + mHeader.mEntryCount -= mHeader.mBucketUsage[bucketIndex] - count; 1.602 + mHeader.mBucketUsage[bucketIndex] = count; 1.603 + // recalc eviction rank 1.604 + mHeader.mEvictionRank[bucketIndex] = GetBucketRank(bucketIndex, 0); 1.605 + } 1.606 + NS_ASSERTION(mHeader.mEvictionRank[bucketIndex] == 1.607 + GetBucketRank(bucketIndex, 0), "eviction rank out of sync"); 1.608 + 1.609 + return rv; 1.610 +} 1.611 + 1.612 + 1.613 +/** 1.614 + * VisitRecords 1.615 + * 1.616 + * Visit every record in cache map in the most convenient order 1.617 + */ 1.618 +nsresult 1.619 +nsDiskCacheMap::VisitRecords( nsDiskCacheRecordVisitor * visitor) 1.620 +{ 1.621 + for (int bucketIndex = 0; bucketIndex < kBuckets; ++bucketIndex) { 1.622 + if (VisitEachRecord(bucketIndex, visitor, 0) == kStopVisitingRecords) 1.623 + break; 1.624 + } 1.625 + return NS_OK; 1.626 +} 1.627 + 1.628 + 1.629 +/** 1.630 + * EvictRecords 1.631 + * 1.632 + * Just like VisitRecords, but visits the records in order of their eviction rank 1.633 + */ 1.634 +nsresult 1.635 +nsDiskCacheMap::EvictRecords( nsDiskCacheRecordVisitor * visitor) 1.636 +{ 1.637 + uint32_t tempRank[kBuckets]; 1.638 + int bucketIndex = 0; 1.639 + 1.640 + // copy eviction rank array 1.641 + for (bucketIndex = 0; bucketIndex < kBuckets; ++bucketIndex) 1.642 + tempRank[bucketIndex] = mHeader.mEvictionRank[bucketIndex]; 1.643 + 1.644 + // Maximum number of iterations determined by number of records 1.645 + // as a safety limiter for the loop. Use a copy of mHeader.mEntryCount since 1.646 + // the value could decrease if some entry is evicted. 1.647 + int32_t entryCount = mHeader.mEntryCount; 1.648 + for (int n = 0; n < entryCount; ++n) { 1.649 + 1.650 + // find bucket with highest eviction rank 1.651 + uint32_t rank = 0; 1.652 + for (int i = 0; i < kBuckets; ++i) { 1.653 + if (rank < tempRank[i]) { 1.654 + rank = tempRank[i]; 1.655 + bucketIndex = i; 1.656 + } 1.657 + } 1.658 + 1.659 + if (rank == 0) break; // we've examined all the records 1.660 + 1.661 + // visit records in bucket with eviction ranks >= target eviction rank 1.662 + if (VisitEachRecord(bucketIndex, visitor, rank) == kStopVisitingRecords) 1.663 + break; 1.664 + 1.665 + // find greatest rank less than 'rank' 1.666 + tempRank[bucketIndex] = GetBucketRank(bucketIndex, rank); 1.667 + } 1.668 + return NS_OK; 1.669 +} 1.670 + 1.671 + 1.672 + 1.673 +nsresult 1.674 +nsDiskCacheMap::OpenBlockFiles(nsDiskCache::CorruptCacheInfo * corruptInfo) 1.675 +{ 1.676 + NS_ENSURE_ARG_POINTER(corruptInfo); 1.677 + 1.678 + // create nsIFile for block file 1.679 + nsCOMPtr<nsIFile> blockFile; 1.680 + nsresult rv = NS_OK; 1.681 + *corruptInfo = nsDiskCache::kUnexpectedError; 1.682 + 1.683 + for (int i = 0; i < kNumBlockFiles; ++i) { 1.684 + rv = GetBlockFileForIndex(i, getter_AddRefs(blockFile)); 1.685 + if (NS_FAILED(rv)) { 1.686 + *corruptInfo = nsDiskCache::kCouldNotGetBlockFileForIndex; 1.687 + break; 1.688 + } 1.689 + 1.690 + uint32_t blockSize = GetBlockSizeForIndex(i+1); // +1 to match file selectors 1,2,3 1.691 + uint32_t bitMapSize = GetBitMapSizeForIndex(i+1); 1.692 + rv = mBlockFile[i].Open(blockFile, blockSize, bitMapSize, corruptInfo); 1.693 + if (NS_FAILED(rv)) { 1.694 + // corruptInfo was set inside the call to mBlockFile[i].Open 1.695 + break; 1.696 + } 1.697 + } 1.698 + // close all files in case of any error 1.699 + if (NS_FAILED(rv)) 1.700 + (void)CloseBlockFiles(false); // we already have an error to report 1.701 + 1.702 + return rv; 1.703 +} 1.704 + 1.705 + 1.706 +nsresult 1.707 +nsDiskCacheMap::CloseBlockFiles(bool flush) 1.708 +{ 1.709 + nsresult rv, rv2 = NS_OK; 1.710 + for (int i=0; i < kNumBlockFiles; ++i) { 1.711 + rv = mBlockFile[i].Close(flush); 1.712 + if (NS_FAILED(rv)) rv2 = rv; // if one or more errors, report at least one 1.713 + } 1.714 + return rv2; 1.715 +} 1.716 + 1.717 + 1.718 +bool 1.719 +nsDiskCacheMap::CacheFilesExist() 1.720 +{ 1.721 + nsCOMPtr<nsIFile> blockFile; 1.722 + nsresult rv; 1.723 + 1.724 + for (int i = 0; i < kNumBlockFiles; ++i) { 1.725 + bool exists; 1.726 + rv = GetBlockFileForIndex(i, getter_AddRefs(blockFile)); 1.727 + if (NS_FAILED(rv)) return false; 1.728 + 1.729 + rv = blockFile->Exists(&exists); 1.730 + if (NS_FAILED(rv) || !exists) return false; 1.731 + } 1.732 + 1.733 + return true; 1.734 +} 1.735 + 1.736 + 1.737 +nsresult 1.738 +nsDiskCacheMap::CreateCacheSubDirectories() 1.739 +{ 1.740 + if (!mCacheDirectory) 1.741 + return NS_ERROR_UNEXPECTED; 1.742 + 1.743 + for (int32_t index = 0 ; index < 16 ; index++) { 1.744 + nsCOMPtr<nsIFile> file; 1.745 + nsresult rv = mCacheDirectory->Clone(getter_AddRefs(file)); 1.746 + if (NS_FAILED(rv)) 1.747 + return rv; 1.748 + 1.749 + rv = file->AppendNative(nsPrintfCString("%X", index)); 1.750 + if (NS_FAILED(rv)) 1.751 + return rv; 1.752 + 1.753 + rv = file->Create(nsIFile::DIRECTORY_TYPE, 0700); 1.754 + if (NS_FAILED(rv)) 1.755 + return rv; 1.756 + } 1.757 + 1.758 + return NS_OK; 1.759 +} 1.760 + 1.761 + 1.762 +nsDiskCacheEntry * 1.763 +nsDiskCacheMap::ReadDiskCacheEntry(nsDiskCacheRecord * record) 1.764 +{ 1.765 + CACHE_LOG_DEBUG(("CACHE: ReadDiskCacheEntry [%x]\n", record->HashNumber())); 1.766 + 1.767 + nsresult rv = NS_ERROR_UNEXPECTED; 1.768 + nsDiskCacheEntry * diskEntry = nullptr; 1.769 + uint32_t metaFile = record->MetaFile(); 1.770 + int32_t bytesRead = 0; 1.771 + 1.772 + if (!record->MetaLocationInitialized()) return nullptr; 1.773 + 1.774 + if (metaFile == 0) { // entry/metadata stored in separate file 1.775 + // open and read the file 1.776 + nsCOMPtr<nsIFile> file; 1.777 + rv = GetLocalFileForDiskCacheRecord(record, 1.778 + nsDiskCache::kMetaData, 1.779 + false, 1.780 + getter_AddRefs(file)); 1.781 + NS_ENSURE_SUCCESS(rv, nullptr); 1.782 + 1.783 + CACHE_LOG_DEBUG(("CACHE: nsDiskCacheMap::ReadDiskCacheEntry" 1.784 + "[this=%p] reading disk cache entry", this)); 1.785 + 1.786 + PRFileDesc * fd = nullptr; 1.787 + 1.788 + // open the file - restricted to user, the data could be confidential 1.789 + rv = file->OpenNSPRFileDesc(PR_RDONLY, 00600, &fd); 1.790 + NS_ENSURE_SUCCESS(rv, nullptr); 1.791 + 1.792 + int32_t fileSize = PR_Available(fd); 1.793 + if (fileSize < 0) { 1.794 + // an error occurred. We could call PR_GetError(), but how would that help? 1.795 + rv = NS_ERROR_UNEXPECTED; 1.796 + } else { 1.797 + rv = EnsureBuffer(fileSize); 1.798 + if (NS_SUCCEEDED(rv)) { 1.799 + bytesRead = PR_Read(fd, mBuffer, fileSize); 1.800 + if (bytesRead < fileSize) { 1.801 + rv = NS_ERROR_UNEXPECTED; 1.802 + } 1.803 + } 1.804 + } 1.805 + PR_Close(fd); 1.806 + NS_ENSURE_SUCCESS(rv, nullptr); 1.807 + 1.808 + } else if (metaFile < (kNumBlockFiles + 1)) { 1.809 + // entry/metadata stored in cache block file 1.810 + 1.811 + // allocate buffer 1.812 + uint32_t blockCount = record->MetaBlockCount(); 1.813 + bytesRead = blockCount * GetBlockSizeForIndex(metaFile); 1.814 + 1.815 + rv = EnsureBuffer(bytesRead); 1.816 + NS_ENSURE_SUCCESS(rv, nullptr); 1.817 + 1.818 + // read diskEntry, note when the blocks are at the end of file, 1.819 + // bytesRead may be less than blockSize*blockCount. 1.820 + // But the bytesRead should at least agree with the real disk entry size. 1.821 + rv = mBlockFile[metaFile - 1].ReadBlocks(mBuffer, 1.822 + record->MetaStartBlock(), 1.823 + blockCount, 1.824 + &bytesRead); 1.825 + NS_ENSURE_SUCCESS(rv, nullptr); 1.826 + } 1.827 + diskEntry = (nsDiskCacheEntry *)mBuffer; 1.828 + diskEntry->Unswap(); // disk to memory 1.829 + // Check if calculated size agrees with bytesRead 1.830 + if (bytesRead < 0 || (uint32_t)bytesRead < diskEntry->Size()) 1.831 + return nullptr; 1.832 + 1.833 + // Return the buffer containing the diskEntry structure 1.834 + return diskEntry; 1.835 +} 1.836 + 1.837 + 1.838 +/** 1.839 + * CreateDiskCacheEntry(nsCacheEntry * entry) 1.840 + * 1.841 + * Prepare an nsCacheEntry for writing to disk 1.842 + */ 1.843 +nsDiskCacheEntry * 1.844 +nsDiskCacheMap::CreateDiskCacheEntry(nsDiskCacheBinding * binding, 1.845 + uint32_t * aSize) 1.846 +{ 1.847 + nsCacheEntry * entry = binding->mCacheEntry; 1.848 + if (!entry) return nullptr; 1.849 + 1.850 + // Store security info, if it is serializable 1.851 + nsCOMPtr<nsISupports> infoObj = entry->SecurityInfo(); 1.852 + nsCOMPtr<nsISerializable> serializable = do_QueryInterface(infoObj); 1.853 + if (infoObj && !serializable) return nullptr; 1.854 + if (serializable) { 1.855 + nsCString info; 1.856 + nsresult rv = NS_SerializeToString(serializable, info); 1.857 + if (NS_FAILED(rv)) return nullptr; 1.858 + rv = entry->SetMetaDataElement("security-info", info.get()); 1.859 + if (NS_FAILED(rv)) return nullptr; 1.860 + } 1.861 + 1.862 + uint32_t keySize = entry->Key()->Length() + 1; 1.863 + uint32_t metaSize = entry->MetaDataSize(); 1.864 + uint32_t size = sizeof(nsDiskCacheEntry) + keySize + metaSize; 1.865 + 1.866 + if (aSize) *aSize = size; 1.867 + 1.868 + nsresult rv = EnsureBuffer(size); 1.869 + if (NS_FAILED(rv)) return nullptr; 1.870 + 1.871 + nsDiskCacheEntry *diskEntry = (nsDiskCacheEntry *)mBuffer; 1.872 + diskEntry->mHeaderVersion = nsDiskCache::kCurrentVersion; 1.873 + diskEntry->mMetaLocation = binding->mRecord.MetaLocation(); 1.874 + diskEntry->mFetchCount = entry->FetchCount(); 1.875 + diskEntry->mLastFetched = entry->LastFetched(); 1.876 + diskEntry->mLastModified = entry->LastModified(); 1.877 + diskEntry->mExpirationTime = entry->ExpirationTime(); 1.878 + diskEntry->mDataSize = entry->DataSize(); 1.879 + diskEntry->mKeySize = keySize; 1.880 + diskEntry->mMetaDataSize = metaSize; 1.881 + 1.882 + memcpy(diskEntry->Key(), entry->Key()->get(), keySize); 1.883 + 1.884 + rv = entry->FlattenMetaData(diskEntry->MetaData(), metaSize); 1.885 + if (NS_FAILED(rv)) return nullptr; 1.886 + 1.887 + return diskEntry; 1.888 +} 1.889 + 1.890 + 1.891 +nsresult 1.892 +nsDiskCacheMap::WriteDiskCacheEntry(nsDiskCacheBinding * binding) 1.893 +{ 1.894 + CACHE_LOG_DEBUG(("CACHE: WriteDiskCacheEntry [%x]\n", 1.895 + binding->mRecord.HashNumber())); 1.896 + 1.897 + mozilla::eventtracer::AutoEventTracer writeDiskCacheEntry( 1.898 + binding->mCacheEntry, 1.899 + mozilla::eventtracer::eExec, 1.900 + mozilla::eventtracer::eDone, 1.901 + "net::cache::WriteDiskCacheEntry"); 1.902 + 1.903 + nsresult rv = NS_OK; 1.904 + uint32_t size; 1.905 + nsDiskCacheEntry * diskEntry = CreateDiskCacheEntry(binding, &size); 1.906 + if (!diskEntry) return NS_ERROR_UNEXPECTED; 1.907 + 1.908 + uint32_t fileIndex = CalculateFileIndex(size); 1.909 + 1.910 + // Deallocate old storage if necessary 1.911 + if (binding->mRecord.MetaLocationInitialized()) { 1.912 + // we have existing storage 1.913 + 1.914 + if ((binding->mRecord.MetaFile() == 0) && 1.915 + (fileIndex == 0)) { // keeping the separate file 1.916 + // just decrement total 1.917 + DecrementTotalSize(binding->mRecord.MetaFileSize()); 1.918 + NS_ASSERTION(binding->mRecord.MetaFileGeneration() == binding->mGeneration, 1.919 + "generations out of sync"); 1.920 + } else { 1.921 + rv = DeleteStorage(&binding->mRecord, nsDiskCache::kMetaData); 1.922 + NS_ENSURE_SUCCESS(rv, rv); 1.923 + } 1.924 + } 1.925 + 1.926 + binding->mRecord.SetEvictionRank(ULONG_MAX - SecondsFromPRTime(PR_Now())); 1.927 + // write entry data to disk cache block file 1.928 + diskEntry->Swap(); 1.929 + 1.930 + if (fileIndex != 0) { 1.931 + while (1) { 1.932 + uint32_t blockSize = GetBlockSizeForIndex(fileIndex); 1.933 + uint32_t blocks = ((size - 1) / blockSize) + 1; 1.934 + 1.935 + int32_t startBlock; 1.936 + rv = mBlockFile[fileIndex - 1].WriteBlocks(diskEntry, size, blocks, 1.937 + &startBlock); 1.938 + if (NS_SUCCEEDED(rv)) { 1.939 + // update binding and cache map record 1.940 + binding->mRecord.SetMetaBlocks(fileIndex, startBlock, blocks); 1.941 + 1.942 + rv = UpdateRecord(&binding->mRecord); 1.943 + NS_ENSURE_SUCCESS(rv, rv); 1.944 + 1.945 + // XXX we should probably write out bucket ourselves 1.946 + 1.947 + IncrementTotalSize(blocks, blockSize); 1.948 + break; 1.949 + } 1.950 + 1.951 + if (fileIndex == kNumBlockFiles) { 1.952 + fileIndex = 0; // write data to separate file 1.953 + break; 1.954 + } 1.955 + 1.956 + // try next block file 1.957 + fileIndex++; 1.958 + } 1.959 + } 1.960 + 1.961 + if (fileIndex == 0) { 1.962 + // Write entry data to separate file 1.963 + uint32_t metaFileSizeK = ((size + 0x03FF) >> 10); // round up to nearest 1k 1.964 + if (metaFileSizeK > kMaxDataSizeK) 1.965 + metaFileSizeK = kMaxDataSizeK; 1.966 + 1.967 + binding->mRecord.SetMetaFileGeneration(binding->mGeneration); 1.968 + binding->mRecord.SetMetaFileSize(metaFileSizeK); 1.969 + rv = UpdateRecord(&binding->mRecord); 1.970 + NS_ENSURE_SUCCESS(rv, rv); 1.971 + 1.972 + nsCOMPtr<nsIFile> localFile; 1.973 + rv = GetLocalFileForDiskCacheRecord(&binding->mRecord, 1.974 + nsDiskCache::kMetaData, 1.975 + true, 1.976 + getter_AddRefs(localFile)); 1.977 + NS_ENSURE_SUCCESS(rv, rv); 1.978 + 1.979 + // open the file 1.980 + PRFileDesc * fd; 1.981 + // open the file - restricted to user, the data could be confidential 1.982 + rv = localFile->OpenNSPRFileDesc(PR_RDWR | PR_TRUNCATE | PR_CREATE_FILE, 00600, &fd); 1.983 + NS_ENSURE_SUCCESS(rv, rv); 1.984 + 1.985 + // write the file 1.986 + int32_t bytesWritten = PR_Write(fd, diskEntry, size); 1.987 + 1.988 + PRStatus err = PR_Close(fd); 1.989 + if ((bytesWritten != (int32_t)size) || (err != PR_SUCCESS)) { 1.990 + return NS_ERROR_UNEXPECTED; 1.991 + } 1.992 + 1.993 + IncrementTotalSize(metaFileSizeK); 1.994 + } 1.995 + 1.996 + return rv; 1.997 +} 1.998 + 1.999 + 1.1000 +nsresult 1.1001 +nsDiskCacheMap::ReadDataCacheBlocks(nsDiskCacheBinding * binding, char * buffer, uint32_t size) 1.1002 +{ 1.1003 + CACHE_LOG_DEBUG(("CACHE: ReadDataCacheBlocks [%x size=%u]\n", 1.1004 + binding->mRecord.HashNumber(), size)); 1.1005 + 1.1006 + uint32_t fileIndex = binding->mRecord.DataFile(); 1.1007 + int32_t readSize = size; 1.1008 + 1.1009 + nsresult rv = mBlockFile[fileIndex - 1].ReadBlocks(buffer, 1.1010 + binding->mRecord.DataStartBlock(), 1.1011 + binding->mRecord.DataBlockCount(), 1.1012 + &readSize); 1.1013 + NS_ENSURE_SUCCESS(rv, rv); 1.1014 + if (readSize < (int32_t)size) { 1.1015 + rv = NS_ERROR_UNEXPECTED; 1.1016 + } 1.1017 + return rv; 1.1018 +} 1.1019 + 1.1020 + 1.1021 +nsresult 1.1022 +nsDiskCacheMap::WriteDataCacheBlocks(nsDiskCacheBinding * binding, char * buffer, uint32_t size) 1.1023 +{ 1.1024 + CACHE_LOG_DEBUG(("CACHE: WriteDataCacheBlocks [%x size=%u]\n", 1.1025 + binding->mRecord.HashNumber(), size)); 1.1026 + 1.1027 + mozilla::eventtracer::AutoEventTracer writeDataCacheBlocks( 1.1028 + binding->mCacheEntry, 1.1029 + mozilla::eventtracer::eExec, 1.1030 + mozilla::eventtracer::eDone, 1.1031 + "net::cache::WriteDataCacheBlocks"); 1.1032 + 1.1033 + nsresult rv = NS_OK; 1.1034 + 1.1035 + // determine block file & number of blocks 1.1036 + uint32_t fileIndex = CalculateFileIndex(size); 1.1037 + uint32_t blockCount = 0; 1.1038 + int32_t startBlock = 0; 1.1039 + 1.1040 + if (size > 0) { 1.1041 + // if fileIndex is 0, bad things happen below, which makes gcc 4.7 1.1042 + // complain, but it's not supposed to happen. See bug 854105. 1.1043 + MOZ_ASSERT(fileIndex); 1.1044 + while (fileIndex) { 1.1045 + uint32_t blockSize = GetBlockSizeForIndex(fileIndex); 1.1046 + blockCount = ((size - 1) / blockSize) + 1; 1.1047 + 1.1048 + rv = mBlockFile[fileIndex - 1].WriteBlocks(buffer, size, blockCount, 1.1049 + &startBlock); 1.1050 + if (NS_SUCCEEDED(rv)) { 1.1051 + IncrementTotalSize(blockCount, blockSize); 1.1052 + break; 1.1053 + } 1.1054 + 1.1055 + if (fileIndex == kNumBlockFiles) 1.1056 + return rv; 1.1057 + 1.1058 + fileIndex++; 1.1059 + } 1.1060 + } 1.1061 + 1.1062 + // update binding and cache map record 1.1063 + binding->mRecord.SetDataBlocks(fileIndex, startBlock, blockCount); 1.1064 + if (!binding->mDoomed) { 1.1065 + rv = UpdateRecord(&binding->mRecord); 1.1066 + } 1.1067 + return rv; 1.1068 +} 1.1069 + 1.1070 + 1.1071 +nsresult 1.1072 +nsDiskCacheMap::DeleteStorage(nsDiskCacheRecord * record) 1.1073 +{ 1.1074 + nsresult rv1 = DeleteStorage(record, nsDiskCache::kData); 1.1075 + nsresult rv2 = DeleteStorage(record, nsDiskCache::kMetaData); 1.1076 + return NS_FAILED(rv1) ? rv1 : rv2; 1.1077 +} 1.1078 + 1.1079 + 1.1080 +nsresult 1.1081 +nsDiskCacheMap::DeleteStorage(nsDiskCacheRecord * record, bool metaData) 1.1082 +{ 1.1083 + CACHE_LOG_DEBUG(("CACHE: DeleteStorage [%x %u]\n", record->HashNumber(), 1.1084 + metaData)); 1.1085 + 1.1086 + nsresult rv = NS_ERROR_UNEXPECTED; 1.1087 + uint32_t fileIndex = metaData ? record->MetaFile() : record->DataFile(); 1.1088 + nsCOMPtr<nsIFile> file; 1.1089 + 1.1090 + if (fileIndex == 0) { 1.1091 + // delete the file 1.1092 + uint32_t sizeK = metaData ? record->MetaFileSize() : record->DataFileSize(); 1.1093 + // XXX if sizeK == USHRT_MAX, stat file for actual size 1.1094 + 1.1095 + rv = GetFileForDiskCacheRecord(record, metaData, false, getter_AddRefs(file)); 1.1096 + if (NS_SUCCEEDED(rv)) { 1.1097 + rv = file->Remove(false); // false == non-recursive 1.1098 + } 1.1099 + DecrementTotalSize(sizeK); 1.1100 + 1.1101 + } else if (fileIndex < (kNumBlockFiles + 1)) { 1.1102 + // deallocate blocks 1.1103 + uint32_t startBlock = metaData ? record->MetaStartBlock() : record->DataStartBlock(); 1.1104 + uint32_t blockCount = metaData ? record->MetaBlockCount() : record->DataBlockCount(); 1.1105 + 1.1106 + rv = mBlockFile[fileIndex - 1].DeallocateBlocks(startBlock, blockCount); 1.1107 + DecrementTotalSize(blockCount, GetBlockSizeForIndex(fileIndex)); 1.1108 + } 1.1109 + if (metaData) record->ClearMetaLocation(); 1.1110 + else record->ClearDataLocation(); 1.1111 + 1.1112 + return rv; 1.1113 +} 1.1114 + 1.1115 + 1.1116 +nsresult 1.1117 +nsDiskCacheMap::GetFileForDiskCacheRecord(nsDiskCacheRecord * record, 1.1118 + bool meta, 1.1119 + bool createPath, 1.1120 + nsIFile ** result) 1.1121 +{ 1.1122 + if (!mCacheDirectory) return NS_ERROR_NOT_AVAILABLE; 1.1123 + 1.1124 + nsCOMPtr<nsIFile> file; 1.1125 + nsresult rv = mCacheDirectory->Clone(getter_AddRefs(file)); 1.1126 + if (NS_FAILED(rv)) return rv; 1.1127 + 1.1128 + uint32_t hash = record->HashNumber(); 1.1129 + 1.1130 + // The file is stored under subdirectories according to the hash number: 1.1131 + // 0x01234567 -> 0/12/ 1.1132 + rv = file->AppendNative(nsPrintfCString("%X", hash >> 28)); 1.1133 + if (NS_FAILED(rv)) return rv; 1.1134 + rv = file->AppendNative(nsPrintfCString("%02X", (hash >> 20) & 0xFF)); 1.1135 + if (NS_FAILED(rv)) return rv; 1.1136 + 1.1137 + bool exists; 1.1138 + if (createPath && (NS_FAILED(file->Exists(&exists)) || !exists)) { 1.1139 + rv = file->Create(nsIFile::DIRECTORY_TYPE, 0700); 1.1140 + if (NS_FAILED(rv)) return rv; 1.1141 + } 1.1142 + 1.1143 + int16_t generation = record->Generation(); 1.1144 + char name[32]; 1.1145 + // Cut the beginning of the hash that was used in the path 1.1146 + ::sprintf(name, "%05X%c%02X", hash & 0xFFFFF, (meta ? 'm' : 'd'), 1.1147 + generation); 1.1148 + rv = file->AppendNative(nsDependentCString(name)); 1.1149 + if (NS_FAILED(rv)) return rv; 1.1150 + 1.1151 + NS_IF_ADDREF(*result = file); 1.1152 + return rv; 1.1153 +} 1.1154 + 1.1155 + 1.1156 +nsresult 1.1157 +nsDiskCacheMap::GetLocalFileForDiskCacheRecord(nsDiskCacheRecord * record, 1.1158 + bool meta, 1.1159 + bool createPath, 1.1160 + nsIFile ** result) 1.1161 +{ 1.1162 + nsCOMPtr<nsIFile> file; 1.1163 + nsresult rv = GetFileForDiskCacheRecord(record, 1.1164 + meta, 1.1165 + createPath, 1.1166 + getter_AddRefs(file)); 1.1167 + if (NS_FAILED(rv)) return rv; 1.1168 + 1.1169 + NS_IF_ADDREF(*result = file); 1.1170 + return rv; 1.1171 +} 1.1172 + 1.1173 + 1.1174 +nsresult 1.1175 +nsDiskCacheMap::GetBlockFileForIndex(uint32_t index, nsIFile ** result) 1.1176 +{ 1.1177 + if (!mCacheDirectory) return NS_ERROR_NOT_AVAILABLE; 1.1178 + 1.1179 + nsCOMPtr<nsIFile> file; 1.1180 + nsresult rv = mCacheDirectory->Clone(getter_AddRefs(file)); 1.1181 + if (NS_FAILED(rv)) return rv; 1.1182 + 1.1183 + char name[32]; 1.1184 + ::sprintf(name, "_CACHE_%03d_", index + 1); 1.1185 + rv = file->AppendNative(nsDependentCString(name)); 1.1186 + if (NS_FAILED(rv)) return rv; 1.1187 + 1.1188 + NS_IF_ADDREF(*result = file); 1.1189 + 1.1190 + return rv; 1.1191 +} 1.1192 + 1.1193 + 1.1194 +uint32_t 1.1195 +nsDiskCacheMap::CalculateFileIndex(uint32_t size) 1.1196 +{ 1.1197 + // We prefer to use block file with larger block if the wasted space would 1.1198 + // be the same. E.g. store entry with size of 3073 bytes in 1 4K-block 1.1199 + // instead of in 4 1K-blocks. 1.1200 + 1.1201 + if (size <= 3 * BLOCK_SIZE_FOR_INDEX(1)) return 1; 1.1202 + if (size <= 3 * BLOCK_SIZE_FOR_INDEX(2)) return 2; 1.1203 + if (size <= 4 * BLOCK_SIZE_FOR_INDEX(3)) return 3; 1.1204 + return 0; 1.1205 +} 1.1206 + 1.1207 +nsresult 1.1208 +nsDiskCacheMap::EnsureBuffer(uint32_t bufSize) 1.1209 +{ 1.1210 + if (mBufferSize < bufSize) { 1.1211 + char * buf = (char *)PR_REALLOC(mBuffer, bufSize); 1.1212 + if (!buf) { 1.1213 + mBufferSize = 0; 1.1214 + return NS_ERROR_OUT_OF_MEMORY; 1.1215 + } 1.1216 + mBuffer = buf; 1.1217 + mBufferSize = bufSize; 1.1218 + } 1.1219 + return NS_OK; 1.1220 +} 1.1221 + 1.1222 +void 1.1223 +nsDiskCacheMap::NotifyCapacityChange(uint32_t capacity) 1.1224 +{ 1.1225 + // Heuristic 1. average cache entry size is probably around 1KB 1.1226 + // Heuristic 2. we don't want more than 32MB reserved to store the record 1.1227 + // map in memory. 1.1228 + const int32_t RECORD_COUNT_LIMIT = 32 * 1024 * 1024 / sizeof(nsDiskCacheRecord); 1.1229 + int32_t maxRecordCount = std::min(int32_t(capacity), RECORD_COUNT_LIMIT); 1.1230 + if (mMaxRecordCount < maxRecordCount) { 1.1231 + // We can only grow 1.1232 + mMaxRecordCount = maxRecordCount; 1.1233 + } 1.1234 +} 1.1235 + 1.1236 +size_t 1.1237 +nsDiskCacheMap::SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) 1.1238 +{ 1.1239 + size_t usage = aMallocSizeOf(mRecordArray); 1.1240 + 1.1241 + usage += aMallocSizeOf(mBuffer); 1.1242 + usage += aMallocSizeOf(mMapFD); 1.1243 + usage += aMallocSizeOf(mCleanFD); 1.1244 + usage += aMallocSizeOf(mCacheDirectory); 1.1245 + usage += aMallocSizeOf(mCleanCacheTimer); 1.1246 + 1.1247 + for (int i = 0; i < kNumBlockFiles; i++) { 1.1248 + usage += mBlockFile[i].SizeOfExcludingThis(aMallocSizeOf); 1.1249 + } 1.1250 + 1.1251 + return usage; 1.1252 +} 1.1253 + 1.1254 +nsresult 1.1255 +nsDiskCacheMap::InitCacheClean(nsIFile * cacheDirectory, 1.1256 + nsDiskCache::CorruptCacheInfo * corruptInfo, 1.1257 + bool reportCacheCleanTelemetryData) 1.1258 +{ 1.1259 + // The _CACHE_CLEAN_ file will be used in the future to determine 1.1260 + // if the cache is clean or not. 1.1261 + bool cacheCleanFileExists = false; 1.1262 + nsCOMPtr<nsIFile> cacheCleanFile; 1.1263 + nsresult rv = cacheDirectory->GetParent(getter_AddRefs(cacheCleanFile)); 1.1264 + if (NS_SUCCEEDED(rv)) { 1.1265 + rv = cacheCleanFile->AppendNative( 1.1266 + NS_LITERAL_CSTRING("_CACHE_CLEAN_")); 1.1267 + if (NS_SUCCEEDED(rv)) { 1.1268 + // Check if the file already exists, if it does, we will later read the 1.1269 + // value and report it to telemetry. 1.1270 + cacheCleanFile->Exists(&cacheCleanFileExists); 1.1271 + } 1.1272 + } 1.1273 + if (NS_FAILED(rv)) { 1.1274 + NS_WARNING("Could not build cache clean file path"); 1.1275 + *corruptInfo = nsDiskCache::kCacheCleanFilePathError; 1.1276 + return rv; 1.1277 + } 1.1278 + 1.1279 + // Make sure the _CACHE_CLEAN_ file exists 1.1280 + rv = cacheCleanFile->OpenNSPRFileDesc(PR_RDWR | PR_CREATE_FILE, 1.1281 + 00600, &mCleanFD); 1.1282 + if (NS_FAILED(rv)) { 1.1283 + NS_WARNING("Could not open cache clean file"); 1.1284 + *corruptInfo = nsDiskCache::kCacheCleanOpenFileError; 1.1285 + return rv; 1.1286 + } 1.1287 + 1.1288 + if (cacheCleanFileExists) { 1.1289 + char clean = '0'; 1.1290 + int32_t bytesRead = PR_Read(mCleanFD, &clean, 1); 1.1291 + if (bytesRead != 1) { 1.1292 + NS_WARNING("Could not read _CACHE_CLEAN_ file contents"); 1.1293 + } else if (reportCacheCleanTelemetryData) { 1.1294 + Telemetry::Accumulate(Telemetry::DISK_CACHE_REDUCTION_TRIAL, 1.1295 + clean == '1' ? 1 : 0); 1.1296 + } 1.1297 + } 1.1298 + 1.1299 + // Create a timer that will be used to validate the cache 1.1300 + // as long as an activity threshold was met 1.1301 + mCleanCacheTimer = do_CreateInstance("@mozilla.org/timer;1", &rv); 1.1302 + if (NS_SUCCEEDED(rv)) { 1.1303 + mCleanCacheTimer->SetTarget(nsCacheService::GlobalInstance()->mCacheIOThread); 1.1304 + rv = ResetCacheTimer(); 1.1305 + } 1.1306 + 1.1307 + if (NS_FAILED(rv)) { 1.1308 + NS_WARNING("Could not create cache clean timer"); 1.1309 + mCleanCacheTimer = nullptr; 1.1310 + *corruptInfo = nsDiskCache::kCacheCleanTimerError; 1.1311 + return rv; 1.1312 + } 1.1313 + 1.1314 + return NS_OK; 1.1315 +} 1.1316 + 1.1317 +nsresult 1.1318 +nsDiskCacheMap::WriteCacheClean(bool clean) 1.1319 +{ 1.1320 + nsCacheService::AssertOwnsLock(); 1.1321 + if (!mCleanFD) { 1.1322 + NS_WARNING("Cache clean file is not open!"); 1.1323 + return NS_ERROR_FAILURE; 1.1324 + } 1.1325 + 1.1326 + CACHE_LOG_DEBUG(("CACHE: WriteCacheClean: %d\n", clean? 1 : 0)); 1.1327 + // I'm using a simple '1' or '0' to denote cache clean 1.1328 + // since it can be edited easily by any text editor for testing. 1.1329 + char data = clean? '1' : '0'; 1.1330 + int32_t filePos = PR_Seek(mCleanFD, 0, PR_SEEK_SET); 1.1331 + if (filePos != 0) { 1.1332 + NS_WARNING("Could not seek in cache clean file!"); 1.1333 + return NS_ERROR_FAILURE; 1.1334 + } 1.1335 + int32_t bytesWritten = PR_Write(mCleanFD, &data, 1); 1.1336 + if (bytesWritten != 1) { 1.1337 + NS_WARNING("Could not write cache clean file!"); 1.1338 + return NS_ERROR_FAILURE; 1.1339 + } 1.1340 + PRStatus err = PR_Sync(mCleanFD); 1.1341 + if (err != PR_SUCCESS) { 1.1342 + NS_WARNING("Could not flush cache clean file!"); 1.1343 + } 1.1344 + 1.1345 + return NS_OK; 1.1346 +} 1.1347 + 1.1348 +nsresult 1.1349 +nsDiskCacheMap::InvalidateCache() 1.1350 +{ 1.1351 + nsCacheService::AssertOwnsLock(); 1.1352 + CACHE_LOG_DEBUG(("CACHE: InvalidateCache\n")); 1.1353 + nsresult rv; 1.1354 + 1.1355 + if (!mIsDirtyCacheFlushed) { 1.1356 + rv = WriteCacheClean(false); 1.1357 + if (NS_FAILED(rv)) { 1.1358 + Telemetry::Accumulate(Telemetry::DISK_CACHE_INVALIDATION_SUCCESS, 0); 1.1359 + return rv; 1.1360 + } 1.1361 + 1.1362 + Telemetry::Accumulate(Telemetry::DISK_CACHE_INVALIDATION_SUCCESS, 1); 1.1363 + mIsDirtyCacheFlushed = true; 1.1364 + } 1.1365 + 1.1366 + rv = ResetCacheTimer(); 1.1367 + NS_ENSURE_SUCCESS(rv, rv); 1.1368 + 1.1369 + return NS_OK; 1.1370 +} 1.1371 + 1.1372 +nsresult 1.1373 +nsDiskCacheMap::ResetCacheTimer(int32_t timeout) 1.1374 +{ 1.1375 + mCleanCacheTimer->Cancel(); 1.1376 + nsresult rv = 1.1377 + mCleanCacheTimer->InitWithFuncCallback(RevalidateTimerCallback, 1.1378 + nullptr, timeout, 1.1379 + nsITimer::TYPE_ONE_SHOT); 1.1380 + NS_ENSURE_SUCCESS(rv, rv); 1.1381 + mLastInvalidateTime = PR_IntervalNow(); 1.1382 + 1.1383 + return rv; 1.1384 +} 1.1385 + 1.1386 +void 1.1387 +nsDiskCacheMap::RevalidateTimerCallback(nsITimer *aTimer, void *arg) 1.1388 +{ 1.1389 + nsCacheServiceAutoLock lock(LOCK_TELEM(NSDISKCACHEMAP_REVALIDATION)); 1.1390 + if (!nsCacheService::gService->mDiskDevice || 1.1391 + !nsCacheService::gService->mDiskDevice->Initialized()) { 1.1392 + return; 1.1393 + } 1.1394 + 1.1395 + nsDiskCacheMap *diskCacheMap = 1.1396 + &nsCacheService::gService->mDiskDevice->mCacheMap; 1.1397 + 1.1398 + // If we have less than kRevalidateCacheTimeout since the last timer was 1.1399 + // issued then another thread called InvalidateCache. This won't catch 1.1400 + // all cases where we wanted to cancel the timer, but under the lock it 1.1401 + // is always OK to revalidate as long as IsCacheInSafeState() returns 1.1402 + // true. We just want to avoid revalidating when we can to reduce IO 1.1403 + // and this check will do that. 1.1404 + uint32_t delta = 1.1405 + PR_IntervalToMilliseconds(PR_IntervalNow() - 1.1406 + diskCacheMap->mLastInvalidateTime) + 1.1407 + kRevalidateCacheTimeoutTolerance; 1.1408 + if (delta < kRevalidateCacheTimeout) { 1.1409 + diskCacheMap->ResetCacheTimer(); 1.1410 + return; 1.1411 + } 1.1412 + 1.1413 + nsresult rv = diskCacheMap->RevalidateCache(); 1.1414 + if (NS_FAILED(rv)) { 1.1415 + diskCacheMap->ResetCacheTimer(kRevalidateCacheErrorTimeout); 1.1416 + } 1.1417 +} 1.1418 + 1.1419 +bool 1.1420 +nsDiskCacheMap::IsCacheInSafeState() 1.1421 +{ 1.1422 + return nsCacheService::GlobalInstance()->IsDoomListEmpty(); 1.1423 +} 1.1424 + 1.1425 +nsresult 1.1426 +nsDiskCacheMap::RevalidateCache() 1.1427 +{ 1.1428 + CACHE_LOG_DEBUG(("CACHE: RevalidateCache\n")); 1.1429 + nsresult rv; 1.1430 + 1.1431 + if (!IsCacheInSafeState()) { 1.1432 + Telemetry::Accumulate(Telemetry::DISK_CACHE_REVALIDATION_SAFE, 0); 1.1433 + CACHE_LOG_DEBUG(("CACHE: Revalidation should not performed because " 1.1434 + "cache not in a safe state\n")); 1.1435 + // Normally we would return an error here, but there is a bug where 1.1436 + // the doom list sometimes gets an entry 'stuck' and doens't clear it 1.1437 + // until browser shutdown. So we allow revalidation for the time being 1.1438 + // to get proper telemetry data of how much the cache corruption plan 1.1439 + // would help. 1.1440 + } else { 1.1441 + Telemetry::Accumulate(Telemetry::DISK_CACHE_REVALIDATION_SAFE, 1); 1.1442 + } 1.1443 + 1.1444 + // We want this after the lock to prove that flushing a file isn't that expensive 1.1445 + Telemetry::AutoTimer<Telemetry::NETWORK_DISK_CACHE_REVALIDATION> totalTimer; 1.1446 + 1.1447 + // If telemetry data shows it is worth it, we'll be flushing headers and 1.1448 + // records before flushing the clean cache file. 1.1449 + 1.1450 + // Write out the _CACHE_CLEAN_ file with '1' 1.1451 + rv = WriteCacheClean(true); 1.1452 + if (NS_FAILED(rv)) { 1.1453 + Telemetry::Accumulate(Telemetry::DISK_CACHE_REVALIDATION_SUCCESS, 0); 1.1454 + return rv; 1.1455 + } 1.1456 + 1.1457 + Telemetry::Accumulate(Telemetry::DISK_CACHE_REVALIDATION_SUCCESS, 1); 1.1458 + mIsDirtyCacheFlushed = false; 1.1459 + 1.1460 + return NS_OK; 1.1461 +}