12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427 |
- /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
- /* vim:set ts=4 sw=4 sts=4 cin et: */
- /* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
- #include "nsCache.h"
- #include "nsDiskCacheMap.h"
- #include "nsDiskCacheBinding.h"
- #include "nsDiskCacheEntry.h"
- #include "nsDiskCacheDevice.h"
- #include "nsCacheService.h"
- #include <string.h>
- #include "nsPrintfCString.h"
- #include "nsISerializable.h"
- #include "nsSerializationHelper.h"
- #include "mozilla/MemoryReporting.h"
- #include "mozilla/Sprintf.h"
- #include <algorithm>
- using namespace mozilla;
- /******************************************************************************
- * nsDiskCacheMap
- *****************************************************************************/
- /**
- * File operations
- */
- nsresult
- nsDiskCacheMap::Open(nsIFile * cacheDirectory,
- nsDiskCache::CorruptCacheInfo * corruptInfo)
- {
- NS_ENSURE_ARG_POINTER(corruptInfo);
- // Assume we have an unexpected error until we find otherwise.
- *corruptInfo = nsDiskCache::kUnexpectedError;
- NS_ENSURE_ARG_POINTER(cacheDirectory);
- if (mMapFD) return NS_ERROR_ALREADY_INITIALIZED;
- mCacheDirectory = cacheDirectory; // save a reference for ourselves
-
- // create nsIFile for _CACHE_MAP_
- nsresult rv;
- nsCOMPtr<nsIFile> file;
- rv = cacheDirectory->Clone(getter_AddRefs(file));
- rv = file->AppendNative(NS_LITERAL_CSTRING("_CACHE_MAP_"));
- NS_ENSURE_SUCCESS(rv, rv);
- // open the file - restricted to user, the data could be confidential
- rv = file->OpenNSPRFileDesc(PR_RDWR | PR_CREATE_FILE, 00600, &mMapFD);
- if (NS_FAILED(rv)) {
- *corruptInfo = nsDiskCache::kOpenCacheMapError;
- NS_WARNING("Could not open cache map file");
- return NS_ERROR_FILE_CORRUPTED;
- }
- bool cacheFilesExist = CacheFilesExist();
- rv = NS_ERROR_FILE_CORRUPTED; // presume the worst
- uint32_t mapSize = PR_Available(mMapFD);
- if (NS_FAILED(InitCacheClean(cacheDirectory,
- corruptInfo))) {
- // corruptInfo is set in the call to InitCacheClean
- goto error_exit;
- }
- // check size of map file
- if (mapSize == 0) { // creating a new _CACHE_MAP_
- // block files shouldn't exist if we're creating the _CACHE_MAP_
- if (cacheFilesExist) {
- *corruptInfo = nsDiskCache::kBlockFilesShouldNotExist;
- goto error_exit;
- }
- if (NS_FAILED(CreateCacheSubDirectories())) {
- *corruptInfo = nsDiskCache::kCreateCacheSubdirectories;
- goto error_exit;
- }
- // create the file - initialize in memory
- memset(&mHeader, 0, sizeof(nsDiskCacheHeader));
- mHeader.mVersion = nsDiskCache::kCurrentVersion;
- mHeader.mRecordCount = kMinRecordCount;
- mRecordArray = (nsDiskCacheRecord *)
- PR_CALLOC(mHeader.mRecordCount * sizeof(nsDiskCacheRecord));
- if (!mRecordArray) {
- *corruptInfo = nsDiskCache::kOutOfMemory;
- rv = NS_ERROR_OUT_OF_MEMORY;
- goto error_exit;
- }
- } else if (mapSize >= sizeof(nsDiskCacheHeader)) { // read existing _CACHE_MAP_
-
- // if _CACHE_MAP_ exists, so should the block files
- if (!cacheFilesExist) {
- *corruptInfo = nsDiskCache::kBlockFilesShouldExist;
- goto error_exit;
- }
- CACHE_LOG_DEBUG(("CACHE: nsDiskCacheMap::Open [this=%p] reading map", this));
- // read the header
- uint32_t bytesRead = PR_Read(mMapFD, &mHeader, sizeof(nsDiskCacheHeader));
- if (sizeof(nsDiskCacheHeader) != bytesRead) {
- *corruptInfo = nsDiskCache::kHeaderSizeNotRead;
- goto error_exit;
- }
- mHeader.Unswap();
- if (mHeader.mIsDirty) {
- *corruptInfo = nsDiskCache::kHeaderIsDirty;
- goto error_exit;
- }
-
- if (mHeader.mVersion != nsDiskCache::kCurrentVersion) {
- *corruptInfo = nsDiskCache::kVersionMismatch;
- goto error_exit;
- }
- uint32_t recordArraySize =
- mHeader.mRecordCount * sizeof(nsDiskCacheRecord);
- if (mapSize < recordArraySize + sizeof(nsDiskCacheHeader)) {
- *corruptInfo = nsDiskCache::kRecordsIncomplete;
- goto error_exit;
- }
- // Get the space for the records
- mRecordArray = (nsDiskCacheRecord *) PR_MALLOC(recordArraySize);
- if (!mRecordArray) {
- *corruptInfo = nsDiskCache::kOutOfMemory;
- rv = NS_ERROR_OUT_OF_MEMORY;
- goto error_exit;
- }
- // Read the records
- bytesRead = PR_Read(mMapFD, mRecordArray, recordArraySize);
- if (bytesRead < recordArraySize) {
- *corruptInfo = nsDiskCache::kNotEnoughToRead;
- goto error_exit;
- }
- // Unswap each record
- int32_t total = 0;
- for (int32_t i = 0; i < mHeader.mRecordCount; ++i) {
- if (mRecordArray[i].HashNumber()) {
- #if defined(IS_LITTLE_ENDIAN)
- mRecordArray[i].Unswap();
- #endif
- total ++;
- }
- }
-
- // verify entry count
- if (total != mHeader.mEntryCount) {
- *corruptInfo = nsDiskCache::kEntryCountIncorrect;
- goto error_exit;
- }
- } else {
- *corruptInfo = nsDiskCache::kHeaderIncomplete;
- goto error_exit;
- }
- rv = OpenBlockFiles(corruptInfo);
- if (NS_FAILED(rv)) {
- // corruptInfo is set in the call to OpenBlockFiles
- goto error_exit;
- }
- // set dirty bit and flush header
- mHeader.mIsDirty = true;
- rv = FlushHeader();
- if (NS_FAILED(rv)) {
- *corruptInfo = nsDiskCache::kFlushHeaderError;
- goto error_exit;
- }
-
- *corruptInfo = nsDiskCache::kNotCorrupt;
- return NS_OK;
-
- error_exit:
- (void) Close(false);
-
- return rv;
- }
- nsresult
- nsDiskCacheMap::Close(bool flush)
- {
- nsCacheService::AssertOwnsLock();
- nsresult rv = NS_OK;
- // Cancel any pending cache validation event, the FlushRecords call below
- // will validate the cache.
- if (mCleanCacheTimer) {
- mCleanCacheTimer->Cancel();
- }
- // If cache map file and its block files are still open, close them
- if (mMapFD) {
- // close block files
- rv = CloseBlockFiles(flush);
- if (NS_SUCCEEDED(rv) && flush && mRecordArray) {
- // write the map records
- rv = FlushRecords(false); // don't bother swapping buckets back
- if (NS_SUCCEEDED(rv)) {
- // clear dirty bit
- mHeader.mIsDirty = false;
- rv = FlushHeader();
- }
- }
- if ((PR_Close(mMapFD) != PR_SUCCESS) && (NS_SUCCEEDED(rv)))
- rv = NS_ERROR_UNEXPECTED;
- mMapFD = nullptr;
- }
- if (mCleanFD) {
- PR_Close(mCleanFD);
- mCleanFD = nullptr;
- }
- PR_FREEIF(mRecordArray);
- PR_FREEIF(mBuffer);
- mBufferSize = 0;
- return rv;
- }
- nsresult
- nsDiskCacheMap::Trim()
- {
- nsresult rv, rv2 = NS_OK;
- for (int i=0; i < kNumBlockFiles; ++i) {
- rv = mBlockFile[i].Trim();
- if (NS_FAILED(rv)) rv2 = rv; // if one or more errors, report at least one
- }
- // Try to shrink the records array
- rv = ShrinkRecords();
- if (NS_FAILED(rv)) rv2 = rv; // if one or more errors, report at least one
- return rv2;
- }
- nsresult
- nsDiskCacheMap::FlushHeader()
- {
- if (!mMapFD) return NS_ERROR_NOT_AVAILABLE;
-
- // seek to beginning of cache map
- int32_t filePos = PR_Seek(mMapFD, 0, PR_SEEK_SET);
- if (filePos != 0) return NS_ERROR_UNEXPECTED;
-
- // write the header
- mHeader.Swap();
- int32_t bytesWritten = PR_Write(mMapFD, &mHeader, sizeof(nsDiskCacheHeader));
- mHeader.Unswap();
- if (sizeof(nsDiskCacheHeader) != bytesWritten) {
- return NS_ERROR_UNEXPECTED;
- }
- PRStatus err = PR_Sync(mMapFD);
- if (err != PR_SUCCESS) return NS_ERROR_UNEXPECTED;
- // If we have a clean header then revalidate the cache clean file
- if (!mHeader.mIsDirty) {
- RevalidateCache();
- }
- return NS_OK;
- }
- nsresult
- nsDiskCacheMap::FlushRecords(bool unswap)
- {
- if (!mMapFD) return NS_ERROR_NOT_AVAILABLE;
-
- // seek to beginning of buckets
- int32_t filePos = PR_Seek(mMapFD, sizeof(nsDiskCacheHeader), PR_SEEK_SET);
- if (filePos != sizeof(nsDiskCacheHeader))
- return NS_ERROR_UNEXPECTED;
-
- #if defined(IS_LITTLE_ENDIAN)
- // Swap each record
- for (int32_t i = 0; i < mHeader.mRecordCount; ++i) {
- if (mRecordArray[i].HashNumber())
- mRecordArray[i].Swap();
- }
- #endif
-
- int32_t recordArraySize = sizeof(nsDiskCacheRecord) * mHeader.mRecordCount;
- int32_t bytesWritten = PR_Write(mMapFD, mRecordArray, recordArraySize);
- if (bytesWritten != recordArraySize)
- return NS_ERROR_UNEXPECTED;
- #if defined(IS_LITTLE_ENDIAN)
- if (unswap) {
- // Unswap each record
- for (int32_t i = 0; i < mHeader.mRecordCount; ++i) {
- if (mRecordArray[i].HashNumber())
- mRecordArray[i].Unswap();
- }
- }
- #endif
-
- return NS_OK;
- }
- /**
- * Record operations
- */
- uint32_t
- nsDiskCacheMap::GetBucketRank(uint32_t bucketIndex, uint32_t targetRank)
- {
- nsDiskCacheRecord * records = GetFirstRecordInBucket(bucketIndex);
- uint32_t rank = 0;
- for (int i = mHeader.mBucketUsage[bucketIndex]-1; i >= 0; i--) {
- if ((rank < records[i].EvictionRank()) &&
- ((targetRank == 0) || (records[i].EvictionRank() < targetRank)))
- rank = records[i].EvictionRank();
- }
- return rank;
- }
- nsresult
- nsDiskCacheMap::GrowRecords()
- {
- if (mHeader.mRecordCount >= mMaxRecordCount)
- return NS_OK;
- CACHE_LOG_DEBUG(("CACHE: GrowRecords\n"));
- // Resize the record array
- int32_t newCount = mHeader.mRecordCount << 1;
- if (newCount > mMaxRecordCount)
- newCount = mMaxRecordCount;
- nsDiskCacheRecord *newArray = (nsDiskCacheRecord *)
- PR_REALLOC(mRecordArray, newCount * sizeof(nsDiskCacheRecord));
- if (!newArray)
- return NS_ERROR_OUT_OF_MEMORY;
- // Space out the buckets
- uint32_t oldRecordsPerBucket = GetRecordsPerBucket();
- uint32_t newRecordsPerBucket = newCount / kBuckets;
- // Work from back to space out each bucket to the new array
- for (int bucketIndex = kBuckets - 1; bucketIndex >= 0; --bucketIndex) {
- // Move bucket
- nsDiskCacheRecord *newRecords = newArray + bucketIndex * newRecordsPerBucket;
- const uint32_t count = mHeader.mBucketUsage[bucketIndex];
- memmove(newRecords,
- newArray + bucketIndex * oldRecordsPerBucket,
- count * sizeof(nsDiskCacheRecord));
- // clear unused records
- memset(newRecords + count, 0,
- (newRecordsPerBucket - count) * sizeof(nsDiskCacheRecord));
- }
- // Set as the new record array
- mRecordArray = newArray;
- mHeader.mRecordCount = newCount;
- InvalidateCache();
- return NS_OK;
- }
- nsresult
- nsDiskCacheMap::ShrinkRecords()
- {
- if (mHeader.mRecordCount <= kMinRecordCount)
- return NS_OK;
- CACHE_LOG_DEBUG(("CACHE: ShrinkRecords\n"));
- // Verify if we can shrink the record array: all buckets must be less than
- // 1/2 filled
- uint32_t maxUsage = 0, bucketIndex;
- for (bucketIndex = 0; bucketIndex < kBuckets; ++bucketIndex) {
- if (maxUsage < mHeader.mBucketUsage[bucketIndex])
- maxUsage = mHeader.mBucketUsage[bucketIndex];
- }
- // Determine new bucket size, halve size until maxUsage
- uint32_t oldRecordsPerBucket = GetRecordsPerBucket();
- uint32_t newRecordsPerBucket = oldRecordsPerBucket;
- while (maxUsage < (newRecordsPerBucket >> 1))
- newRecordsPerBucket >>= 1;
- if (newRecordsPerBucket < (kMinRecordCount / kBuckets))
- newRecordsPerBucket = (kMinRecordCount / kBuckets);
- NS_ASSERTION(newRecordsPerBucket <= oldRecordsPerBucket,
- "ShrinkRecords() can't grow records!");
- if (newRecordsPerBucket == oldRecordsPerBucket)
- return NS_OK;
- // Move the buckets close to each other
- for (bucketIndex = 1; bucketIndex < kBuckets; ++bucketIndex) {
- // Move bucket
- memmove(mRecordArray + bucketIndex * newRecordsPerBucket,
- mRecordArray + bucketIndex * oldRecordsPerBucket,
- newRecordsPerBucket * sizeof(nsDiskCacheRecord));
- }
- // Shrink the record array memory block itself
- uint32_t newCount = newRecordsPerBucket * kBuckets;
- nsDiskCacheRecord* newArray = (nsDiskCacheRecord *)
- PR_REALLOC(mRecordArray, newCount * sizeof(nsDiskCacheRecord));
- if (!newArray)
- return NS_ERROR_OUT_OF_MEMORY;
- // Set as the new record array
- mRecordArray = newArray;
- mHeader.mRecordCount = newCount;
- InvalidateCache();
- return NS_OK;
- }
- nsresult
- nsDiskCacheMap::AddRecord( nsDiskCacheRecord * mapRecord,
- nsDiskCacheRecord * oldRecord)
- {
- CACHE_LOG_DEBUG(("CACHE: AddRecord [%x]\n", mapRecord->HashNumber()));
- const uint32_t hashNumber = mapRecord->HashNumber();
- const uint32_t bucketIndex = GetBucketIndex(hashNumber);
- const uint32_t count = mHeader.mBucketUsage[bucketIndex];
- oldRecord->SetHashNumber(0); // signify no record
- if (count == GetRecordsPerBucket()) {
- // Ignore failure to grow the record space, we will then reuse old records
- GrowRecords();
- }
-
- nsDiskCacheRecord * records = GetFirstRecordInBucket(bucketIndex);
- if (count < GetRecordsPerBucket()) {
- // stick the new record at the end
- records[count] = *mapRecord;
- mHeader.mEntryCount++;
- mHeader.mBucketUsage[bucketIndex]++;
- if (mHeader.mEvictionRank[bucketIndex] < mapRecord->EvictionRank())
- mHeader.mEvictionRank[bucketIndex] = mapRecord->EvictionRank();
- InvalidateCache();
- } else {
- // Find the record with the highest eviction rank
- nsDiskCacheRecord * mostEvictable = &records[0];
- for (int i = count-1; i > 0; i--) {
- if (records[i].EvictionRank() > mostEvictable->EvictionRank())
- mostEvictable = &records[i];
- }
- *oldRecord = *mostEvictable; // i == GetRecordsPerBucket(), so
- // evict the mostEvictable
- *mostEvictable = *mapRecord; // replace it with the new record
- // check if we need to update mostEvictable entry in header
- if (mHeader.mEvictionRank[bucketIndex] < mapRecord->EvictionRank())
- mHeader.mEvictionRank[bucketIndex] = mapRecord->EvictionRank();
- if (oldRecord->EvictionRank() >= mHeader.mEvictionRank[bucketIndex])
- mHeader.mEvictionRank[bucketIndex] = GetBucketRank(bucketIndex, 0);
- InvalidateCache();
- }
- NS_ASSERTION(mHeader.mEvictionRank[bucketIndex] == GetBucketRank(bucketIndex, 0),
- "eviction rank out of sync");
- return NS_OK;
- }
- nsresult
- nsDiskCacheMap::UpdateRecord( nsDiskCacheRecord * mapRecord)
- {
- CACHE_LOG_DEBUG(("CACHE: UpdateRecord [%x]\n", mapRecord->HashNumber()));
- const uint32_t hashNumber = mapRecord->HashNumber();
- const uint32_t bucketIndex = GetBucketIndex(hashNumber);
- nsDiskCacheRecord * records = GetFirstRecordInBucket(bucketIndex);
- for (int i = mHeader.mBucketUsage[bucketIndex]-1; i >= 0; i--) {
- if (records[i].HashNumber() == hashNumber) {
- const uint32_t oldRank = records[i].EvictionRank();
- // stick the new record here
- records[i] = *mapRecord;
- // update eviction rank in header if necessary
- if (mHeader.mEvictionRank[bucketIndex] < mapRecord->EvictionRank())
- mHeader.mEvictionRank[bucketIndex] = mapRecord->EvictionRank();
- else if (mHeader.mEvictionRank[bucketIndex] == oldRank)
- mHeader.mEvictionRank[bucketIndex] = GetBucketRank(bucketIndex, 0);
- InvalidateCache();
- NS_ASSERTION(mHeader.mEvictionRank[bucketIndex] == GetBucketRank(bucketIndex, 0),
- "eviction rank out of sync");
- return NS_OK;
- }
- }
- NS_NOTREACHED("record not found");
- return NS_ERROR_UNEXPECTED;
- }
- nsresult
- nsDiskCacheMap::FindRecord( uint32_t hashNumber, nsDiskCacheRecord * result)
- {
- const uint32_t bucketIndex = GetBucketIndex(hashNumber);
- nsDiskCacheRecord * records = GetFirstRecordInBucket(bucketIndex);
- for (int i = mHeader.mBucketUsage[bucketIndex]-1; i >= 0; i--) {
- if (records[i].HashNumber() == hashNumber) {
- *result = records[i]; // copy the record
- NS_ASSERTION(result->ValidRecord(), "bad cache map record");
- return NS_OK;
- }
- }
- return NS_ERROR_CACHE_KEY_NOT_FOUND;
- }
- nsresult
- nsDiskCacheMap::DeleteRecord( nsDiskCacheRecord * mapRecord)
- {
- CACHE_LOG_DEBUG(("CACHE: DeleteRecord [%x]\n", mapRecord->HashNumber()));
- const uint32_t hashNumber = mapRecord->HashNumber();
- const uint32_t bucketIndex = GetBucketIndex(hashNumber);
- nsDiskCacheRecord * records = GetFirstRecordInBucket(bucketIndex);
- uint32_t last = mHeader.mBucketUsage[bucketIndex]-1;
- for (int i = last; i >= 0; i--) {
- if (records[i].HashNumber() == hashNumber) {
- // found it, now delete it.
- uint32_t evictionRank = records[i].EvictionRank();
- NS_ASSERTION(evictionRank == mapRecord->EvictionRank(),
- "evictionRank out of sync");
- // if not the last record, shift last record into opening
- records[i] = records[last];
- records[last].SetHashNumber(0); // clear last record
- mHeader.mBucketUsage[bucketIndex] = last;
- mHeader.mEntryCount--;
- // update eviction rank
- uint32_t bucketIndex = GetBucketIndex(mapRecord->HashNumber());
- if (mHeader.mEvictionRank[bucketIndex] <= evictionRank) {
- mHeader.mEvictionRank[bucketIndex] = GetBucketRank(bucketIndex, 0);
- }
- InvalidateCache();
- NS_ASSERTION(mHeader.mEvictionRank[bucketIndex] ==
- GetBucketRank(bucketIndex, 0), "eviction rank out of sync");
- return NS_OK;
- }
- }
- return NS_ERROR_UNEXPECTED;
- }
- int32_t
- nsDiskCacheMap::VisitEachRecord(uint32_t bucketIndex,
- nsDiskCacheRecordVisitor * visitor,
- uint32_t evictionRank)
- {
- int32_t rv = kVisitNextRecord;
- uint32_t count = mHeader.mBucketUsage[bucketIndex];
- nsDiskCacheRecord * records = GetFirstRecordInBucket(bucketIndex);
- // call visitor for each entry (matching any eviction rank)
- for (int i = count-1; i >= 0; i--) {
- if (evictionRank > records[i].EvictionRank()) continue;
- rv = visitor->VisitRecord(&records[i]);
- if (rv == kStopVisitingRecords)
- break; // Stop visiting records
-
- if (rv == kDeleteRecordAndContinue) {
- --count;
- records[i] = records[count];
- records[count].SetHashNumber(0);
- InvalidateCache();
- }
- }
- if (mHeader.mBucketUsage[bucketIndex] - count != 0) {
- mHeader.mEntryCount -= mHeader.mBucketUsage[bucketIndex] - count;
- mHeader.mBucketUsage[bucketIndex] = count;
- // recalc eviction rank
- mHeader.mEvictionRank[bucketIndex] = GetBucketRank(bucketIndex, 0);
- }
- NS_ASSERTION(mHeader.mEvictionRank[bucketIndex] ==
- GetBucketRank(bucketIndex, 0), "eviction rank out of sync");
- return rv;
- }
- /**
- * VisitRecords
- *
- * Visit every record in cache map in the most convenient order
- */
- nsresult
- nsDiskCacheMap::VisitRecords( nsDiskCacheRecordVisitor * visitor)
- {
- for (int bucketIndex = 0; bucketIndex < kBuckets; ++bucketIndex) {
- if (VisitEachRecord(bucketIndex, visitor, 0) == kStopVisitingRecords)
- break;
- }
- return NS_OK;
- }
- /**
- * EvictRecords
- *
- * Just like VisitRecords, but visits the records in order of their eviction rank
- */
- nsresult
- nsDiskCacheMap::EvictRecords( nsDiskCacheRecordVisitor * visitor)
- {
- uint32_t tempRank[kBuckets];
- int bucketIndex = 0;
-
- // copy eviction rank array
- for (bucketIndex = 0; bucketIndex < kBuckets; ++bucketIndex)
- tempRank[bucketIndex] = mHeader.mEvictionRank[bucketIndex];
- // Maximum number of iterations determined by number of records
- // as a safety limiter for the loop. Use a copy of mHeader.mEntryCount since
- // the value could decrease if some entry is evicted.
- int32_t entryCount = mHeader.mEntryCount;
- for (int n = 0; n < entryCount; ++n) {
-
- // find bucket with highest eviction rank
- uint32_t rank = 0;
- for (int i = 0; i < kBuckets; ++i) {
- if (rank < tempRank[i]) {
- rank = tempRank[i];
- bucketIndex = i;
- }
- }
-
- if (rank == 0) break; // we've examined all the records
- // visit records in bucket with eviction ranks >= target eviction rank
- if (VisitEachRecord(bucketIndex, visitor, rank) == kStopVisitingRecords)
- break;
- // find greatest rank less than 'rank'
- tempRank[bucketIndex] = GetBucketRank(bucketIndex, rank);
- }
- return NS_OK;
- }
- nsresult
- nsDiskCacheMap::OpenBlockFiles(nsDiskCache::CorruptCacheInfo * corruptInfo)
- {
- NS_ENSURE_ARG_POINTER(corruptInfo);
- // create nsIFile for block file
- nsCOMPtr<nsIFile> blockFile;
- nsresult rv = NS_OK;
- *corruptInfo = nsDiskCache::kUnexpectedError;
-
- for (int i = 0; i < kNumBlockFiles; ++i) {
- rv = GetBlockFileForIndex(i, getter_AddRefs(blockFile));
- if (NS_FAILED(rv)) {
- *corruptInfo = nsDiskCache::kCouldNotGetBlockFileForIndex;
- break;
- }
-
- uint32_t blockSize = GetBlockSizeForIndex(i+1); // +1 to match file selectors 1,2,3
- uint32_t bitMapSize = GetBitMapSizeForIndex(i+1);
- rv = mBlockFile[i].Open(blockFile, blockSize, bitMapSize, corruptInfo);
- if (NS_FAILED(rv)) {
- // corruptInfo was set inside the call to mBlockFile[i].Open
- break;
- }
- }
- // close all files in case of any error
- if (NS_FAILED(rv))
- (void)CloseBlockFiles(false); // we already have an error to report
- return rv;
- }
- nsresult
- nsDiskCacheMap::CloseBlockFiles(bool flush)
- {
- nsresult rv, rv2 = NS_OK;
- for (int i=0; i < kNumBlockFiles; ++i) {
- rv = mBlockFile[i].Close(flush);
- if (NS_FAILED(rv)) rv2 = rv; // if one or more errors, report at least one
- }
- return rv2;
- }
- bool
- nsDiskCacheMap::CacheFilesExist()
- {
- nsCOMPtr<nsIFile> blockFile;
- nsresult rv;
-
- for (int i = 0; i < kNumBlockFiles; ++i) {
- bool exists;
- rv = GetBlockFileForIndex(i, getter_AddRefs(blockFile));
- if (NS_FAILED(rv)) return false;
- rv = blockFile->Exists(&exists);
- if (NS_FAILED(rv) || !exists) return false;
- }
- return true;
- }
- nsresult
- nsDiskCacheMap::CreateCacheSubDirectories()
- {
- if (!mCacheDirectory)
- return NS_ERROR_UNEXPECTED;
- for (int32_t index = 0 ; index < 16 ; index++) {
- nsCOMPtr<nsIFile> file;
- nsresult rv = mCacheDirectory->Clone(getter_AddRefs(file));
- if (NS_FAILED(rv))
- return rv;
- rv = file->AppendNative(nsPrintfCString("%X", index));
- if (NS_FAILED(rv))
- return rv;
- rv = file->Create(nsIFile::DIRECTORY_TYPE, 0700);
- if (NS_FAILED(rv))
- return rv;
- }
- return NS_OK;
- }
- nsDiskCacheEntry *
- nsDiskCacheMap::ReadDiskCacheEntry(nsDiskCacheRecord * record)
- {
- CACHE_LOG_DEBUG(("CACHE: ReadDiskCacheEntry [%x]\n", record->HashNumber()));
- nsresult rv = NS_ERROR_UNEXPECTED;
- nsDiskCacheEntry * diskEntry = nullptr;
- uint32_t metaFile = record->MetaFile();
- int32_t bytesRead = 0;
-
- if (!record->MetaLocationInitialized()) return nullptr;
-
- if (metaFile == 0) { // entry/metadata stored in separate file
- // open and read the file
- nsCOMPtr<nsIFile> file;
- rv = GetLocalFileForDiskCacheRecord(record,
- nsDiskCache::kMetaData,
- false,
- getter_AddRefs(file));
- NS_ENSURE_SUCCESS(rv, nullptr);
- CACHE_LOG_DEBUG(("CACHE: nsDiskCacheMap::ReadDiskCacheEntry"
- "[this=%p] reading disk cache entry", this));
- PRFileDesc * fd = nullptr;
- // open the file - restricted to user, the data could be confidential
- rv = file->OpenNSPRFileDesc(PR_RDONLY, 00600, &fd);
- NS_ENSURE_SUCCESS(rv, nullptr);
-
- int32_t fileSize = PR_Available(fd);
- if (fileSize < 0) {
- // an error occurred. We could call PR_GetError(), but how would that help?
- rv = NS_ERROR_UNEXPECTED;
- } else {
- rv = EnsureBuffer(fileSize);
- if (NS_SUCCEEDED(rv)) {
- bytesRead = PR_Read(fd, mBuffer, fileSize);
- if (bytesRead < fileSize) {
- rv = NS_ERROR_UNEXPECTED;
- }
- }
- }
- PR_Close(fd);
- NS_ENSURE_SUCCESS(rv, nullptr);
- } else if (metaFile < (kNumBlockFiles + 1)) {
- // entry/metadata stored in cache block file
-
- // allocate buffer
- uint32_t blockCount = record->MetaBlockCount();
- bytesRead = blockCount * GetBlockSizeForIndex(metaFile);
- rv = EnsureBuffer(bytesRead);
- NS_ENSURE_SUCCESS(rv, nullptr);
-
- // read diskEntry, note when the blocks are at the end of file,
- // bytesRead may be less than blockSize*blockCount.
- // But the bytesRead should at least agree with the real disk entry size.
- rv = mBlockFile[metaFile - 1].ReadBlocks(mBuffer,
- record->MetaStartBlock(),
- blockCount,
- &bytesRead);
- NS_ENSURE_SUCCESS(rv, nullptr);
- }
- diskEntry = (nsDiskCacheEntry *)mBuffer;
- diskEntry->Unswap(); // disk to memory
- // Check if calculated size agrees with bytesRead
- if (bytesRead < 0 || (uint32_t)bytesRead < diskEntry->Size())
- return nullptr;
- // Return the buffer containing the diskEntry structure
- return diskEntry;
- }
- /**
- * CreateDiskCacheEntry(nsCacheEntry * entry)
- *
- * Prepare an nsCacheEntry for writing to disk
- */
- nsDiskCacheEntry *
- nsDiskCacheMap::CreateDiskCacheEntry(nsDiskCacheBinding * binding,
- uint32_t * aSize)
- {
- nsCacheEntry * entry = binding->mCacheEntry;
- if (!entry) return nullptr;
-
- // Store security info, if it is serializable
- nsCOMPtr<nsISupports> infoObj = entry->SecurityInfo();
- nsCOMPtr<nsISerializable> serializable = do_QueryInterface(infoObj);
- if (infoObj && !serializable) return nullptr;
- if (serializable) {
- nsCString info;
- nsresult rv = NS_SerializeToString(serializable, info);
- if (NS_FAILED(rv)) return nullptr;
- rv = entry->SetMetaDataElement("security-info", info.get());
- if (NS_FAILED(rv)) return nullptr;
- }
- uint32_t keySize = entry->Key()->Length() + 1;
- uint32_t metaSize = entry->MetaDataSize();
- uint32_t size = sizeof(nsDiskCacheEntry) + keySize + metaSize;
-
- if (aSize) *aSize = size;
-
- nsresult rv = EnsureBuffer(size);
- if (NS_FAILED(rv)) return nullptr;
- nsDiskCacheEntry *diskEntry = (nsDiskCacheEntry *)mBuffer;
- diskEntry->mHeaderVersion = nsDiskCache::kCurrentVersion;
- diskEntry->mMetaLocation = binding->mRecord.MetaLocation();
- diskEntry->mFetchCount = entry->FetchCount();
- diskEntry->mLastFetched = entry->LastFetched();
- diskEntry->mLastModified = entry->LastModified();
- diskEntry->mExpirationTime = entry->ExpirationTime();
- diskEntry->mDataSize = entry->DataSize();
- diskEntry->mKeySize = keySize;
- diskEntry->mMetaDataSize = metaSize;
-
- memcpy(diskEntry->Key(), entry->Key()->get(), keySize);
-
- rv = entry->FlattenMetaData(diskEntry->MetaData(), metaSize);
- if (NS_FAILED(rv)) return nullptr;
-
- return diskEntry;
- }
- nsresult
- nsDiskCacheMap::WriteDiskCacheEntry(nsDiskCacheBinding * binding)
- {
- CACHE_LOG_DEBUG(("CACHE: WriteDiskCacheEntry [%x]\n",
- binding->mRecord.HashNumber()));
- nsresult rv = NS_OK;
- uint32_t size;
- nsDiskCacheEntry * diskEntry = CreateDiskCacheEntry(binding, &size);
- if (!diskEntry) return NS_ERROR_UNEXPECTED;
-
- uint32_t fileIndex = CalculateFileIndex(size);
- // Deallocate old storage if necessary
- if (binding->mRecord.MetaLocationInitialized()) {
- // we have existing storage
- if ((binding->mRecord.MetaFile() == 0) &&
- (fileIndex == 0)) { // keeping the separate file
- // just decrement total
- DecrementTotalSize(binding->mRecord.MetaFileSize());
- NS_ASSERTION(binding->mRecord.MetaFileGeneration() == binding->mGeneration,
- "generations out of sync");
- } else {
- rv = DeleteStorage(&binding->mRecord, nsDiskCache::kMetaData);
- NS_ENSURE_SUCCESS(rv, rv);
- }
- }
- binding->mRecord.SetEvictionRank(ULONG_MAX - SecondsFromPRTime(PR_Now()));
- // write entry data to disk cache block file
- diskEntry->Swap();
- if (fileIndex != 0) {
- while (1) {
- uint32_t blockSize = GetBlockSizeForIndex(fileIndex);
- uint32_t blocks = ((size - 1) / blockSize) + 1;
- int32_t startBlock;
- rv = mBlockFile[fileIndex - 1].WriteBlocks(diskEntry, size, blocks,
- &startBlock);
- if (NS_SUCCEEDED(rv)) {
- // update binding and cache map record
- binding->mRecord.SetMetaBlocks(fileIndex, startBlock, blocks);
- rv = UpdateRecord(&binding->mRecord);
- NS_ENSURE_SUCCESS(rv, rv);
- // XXX we should probably write out bucket ourselves
- IncrementTotalSize(blocks, blockSize);
- break;
- }
- if (fileIndex == kNumBlockFiles) {
- fileIndex = 0; // write data to separate file
- break;
- }
- // try next block file
- fileIndex++;
- }
- }
- if (fileIndex == 0) {
- // Write entry data to separate file
- uint32_t metaFileSizeK = ((size + 0x03FF) >> 10); // round up to nearest 1k
- if (metaFileSizeK > kMaxDataSizeK)
- metaFileSizeK = kMaxDataSizeK;
- binding->mRecord.SetMetaFileGeneration(binding->mGeneration);
- binding->mRecord.SetMetaFileSize(metaFileSizeK);
- rv = UpdateRecord(&binding->mRecord);
- NS_ENSURE_SUCCESS(rv, rv);
- nsCOMPtr<nsIFile> localFile;
- rv = GetLocalFileForDiskCacheRecord(&binding->mRecord,
- nsDiskCache::kMetaData,
- true,
- getter_AddRefs(localFile));
- NS_ENSURE_SUCCESS(rv, rv);
-
- // open the file
- PRFileDesc * fd;
- // open the file - restricted to user, the data could be confidential
- rv = localFile->OpenNSPRFileDesc(PR_RDWR | PR_TRUNCATE | PR_CREATE_FILE, 00600, &fd);
- NS_ENSURE_SUCCESS(rv, rv);
- // write the file
- int32_t bytesWritten = PR_Write(fd, diskEntry, size);
-
- PRStatus err = PR_Close(fd);
- if ((bytesWritten != (int32_t)size) || (err != PR_SUCCESS)) {
- return NS_ERROR_UNEXPECTED;
- }
- IncrementTotalSize(metaFileSizeK);
- }
- return rv;
- }
- nsresult
- nsDiskCacheMap::ReadDataCacheBlocks(nsDiskCacheBinding * binding, char * buffer, uint32_t size)
- {
- CACHE_LOG_DEBUG(("CACHE: ReadDataCacheBlocks [%x size=%u]\n",
- binding->mRecord.HashNumber(), size));
- uint32_t fileIndex = binding->mRecord.DataFile();
- int32_t readSize = size;
-
- nsresult rv = mBlockFile[fileIndex - 1].ReadBlocks(buffer,
- binding->mRecord.DataStartBlock(),
- binding->mRecord.DataBlockCount(),
- &readSize);
- NS_ENSURE_SUCCESS(rv, rv);
- if (readSize < (int32_t)size) {
- rv = NS_ERROR_UNEXPECTED;
- }
- return rv;
- }
- nsresult
- nsDiskCacheMap::WriteDataCacheBlocks(nsDiskCacheBinding * binding, char * buffer, uint32_t size)
- {
- CACHE_LOG_DEBUG(("CACHE: WriteDataCacheBlocks [%x size=%u]\n",
- binding->mRecord.HashNumber(), size));
- nsresult rv = NS_OK;
-
- // determine block file & number of blocks
- uint32_t fileIndex = CalculateFileIndex(size);
- uint32_t blockCount = 0;
- int32_t startBlock = 0;
- if (size > 0) {
- // if fileIndex is 0, bad things happen below, which makes gcc 4.7
- // complain, but it's not supposed to happen. See bug 854105.
- MOZ_ASSERT(fileIndex);
- while (fileIndex) {
- uint32_t blockSize = GetBlockSizeForIndex(fileIndex);
- blockCount = ((size - 1) / blockSize) + 1;
- rv = mBlockFile[fileIndex - 1].WriteBlocks(buffer, size, blockCount,
- &startBlock);
- if (NS_SUCCEEDED(rv)) {
- IncrementTotalSize(blockCount, blockSize);
- break;
- }
- if (fileIndex == kNumBlockFiles)
- return rv;
- fileIndex++;
- }
- }
- // update binding and cache map record
- binding->mRecord.SetDataBlocks(fileIndex, startBlock, blockCount);
- if (!binding->mDoomed) {
- rv = UpdateRecord(&binding->mRecord);
- }
- return rv;
- }
- nsresult
- nsDiskCacheMap::DeleteStorage(nsDiskCacheRecord * record)
- {
- nsresult rv1 = DeleteStorage(record, nsDiskCache::kData);
- nsresult rv2 = DeleteStorage(record, nsDiskCache::kMetaData);
- return NS_FAILED(rv1) ? rv1 : rv2;
- }
- nsresult
- nsDiskCacheMap::DeleteStorage(nsDiskCacheRecord * record, bool metaData)
- {
- CACHE_LOG_DEBUG(("CACHE: DeleteStorage [%x %u]\n", record->HashNumber(),
- metaData));
- nsresult rv = NS_ERROR_UNEXPECTED;
- uint32_t fileIndex = metaData ? record->MetaFile() : record->DataFile();
- nsCOMPtr<nsIFile> file;
-
- if (fileIndex == 0) {
- // delete the file
- uint32_t sizeK = metaData ? record->MetaFileSize() : record->DataFileSize();
- // XXX if sizeK == USHRT_MAX, stat file for actual size
- rv = GetFileForDiskCacheRecord(record, metaData, false, getter_AddRefs(file));
- if (NS_SUCCEEDED(rv)) {
- rv = file->Remove(false); // false == non-recursive
- }
- DecrementTotalSize(sizeK);
-
- } else if (fileIndex < (kNumBlockFiles + 1)) {
- // deallocate blocks
- uint32_t startBlock = metaData ? record->MetaStartBlock() : record->DataStartBlock();
- uint32_t blockCount = metaData ? record->MetaBlockCount() : record->DataBlockCount();
-
- rv = mBlockFile[fileIndex - 1].DeallocateBlocks(startBlock, blockCount);
- DecrementTotalSize(blockCount, GetBlockSizeForIndex(fileIndex));
- }
- if (metaData) record->ClearMetaLocation();
- else record->ClearDataLocation();
-
- return rv;
- }
- nsresult
- nsDiskCacheMap::GetFileForDiskCacheRecord(nsDiskCacheRecord * record,
- bool meta,
- bool createPath,
- nsIFile ** result)
- {
- if (!mCacheDirectory) return NS_ERROR_NOT_AVAILABLE;
-
- nsCOMPtr<nsIFile> file;
- nsresult rv = mCacheDirectory->Clone(getter_AddRefs(file));
- if (NS_FAILED(rv)) return rv;
- uint32_t hash = record->HashNumber();
- // The file is stored under subdirectories according to the hash number:
- // 0x01234567 -> 0/12/
- rv = file->AppendNative(nsPrintfCString("%X", hash >> 28));
- if (NS_FAILED(rv)) return rv;
- rv = file->AppendNative(nsPrintfCString("%02X", (hash >> 20) & 0xFF));
- if (NS_FAILED(rv)) return rv;
- bool exists;
- if (createPath && (NS_FAILED(file->Exists(&exists)) || !exists)) {
- rv = file->Create(nsIFile::DIRECTORY_TYPE, 0700);
- if (NS_FAILED(rv)) return rv;
- }
- int16_t generation = record->Generation();
- char name[32];
- // Cut the beginning of the hash that was used in the path
- ::SprintfLiteral(name, "%05X%c%02X", hash & 0xFFFFF, (meta ? 'm' : 'd'),
- generation);
- rv = file->AppendNative(nsDependentCString(name));
- if (NS_FAILED(rv)) return rv;
-
- NS_IF_ADDREF(*result = file);
- return rv;
- }
- nsresult
- nsDiskCacheMap::GetLocalFileForDiskCacheRecord(nsDiskCacheRecord * record,
- bool meta,
- bool createPath,
- nsIFile ** result)
- {
- nsCOMPtr<nsIFile> file;
- nsresult rv = GetFileForDiskCacheRecord(record,
- meta,
- createPath,
- getter_AddRefs(file));
- if (NS_FAILED(rv)) return rv;
-
- NS_IF_ADDREF(*result = file);
- return rv;
- }
- nsresult
- nsDiskCacheMap::GetBlockFileForIndex(uint32_t index, nsIFile ** result)
- {
- if (!mCacheDirectory) return NS_ERROR_NOT_AVAILABLE;
-
- nsCOMPtr<nsIFile> file;
- nsresult rv = mCacheDirectory->Clone(getter_AddRefs(file));
- if (NS_FAILED(rv)) return rv;
-
- char name[32];
- ::SprintfLiteral(name, "_CACHE_%03d_", index + 1);
- rv = file->AppendNative(nsDependentCString(name));
- if (NS_FAILED(rv)) return rv;
-
- NS_IF_ADDREF(*result = file);
- return rv;
- }
- uint32_t
- nsDiskCacheMap::CalculateFileIndex(uint32_t size)
- {
- // We prefer to use block file with larger block if the wasted space would
- // be the same. E.g. store entry with size of 3073 bytes in 1 4K-block
- // instead of in 4 1K-blocks.
- if (size <= 3 * BLOCK_SIZE_FOR_INDEX(1)) return 1;
- if (size <= 3 * BLOCK_SIZE_FOR_INDEX(2)) return 2;
- if (size <= 4 * BLOCK_SIZE_FOR_INDEX(3)) return 3;
- return 0;
- }
- nsresult
- nsDiskCacheMap::EnsureBuffer(uint32_t bufSize)
- {
- if (mBufferSize < bufSize) {
- char * buf = (char *)PR_REALLOC(mBuffer, bufSize);
- if (!buf) {
- mBufferSize = 0;
- return NS_ERROR_OUT_OF_MEMORY;
- }
- mBuffer = buf;
- mBufferSize = bufSize;
- }
- return NS_OK;
- }
- void
- nsDiskCacheMap::NotifyCapacityChange(uint32_t capacity)
- {
- // Heuristic 1. average cache entry size is probably around 1KB
- // Heuristic 2. we don't want more than 32MB reserved to store the record
- // map in memory.
- const int32_t RECORD_COUNT_LIMIT = 32 * 1024 * 1024 / sizeof(nsDiskCacheRecord);
- int32_t maxRecordCount = std::min(int32_t(capacity), RECORD_COUNT_LIMIT);
- if (mMaxRecordCount < maxRecordCount) {
- // We can only grow
- mMaxRecordCount = maxRecordCount;
- }
- }
- size_t
- nsDiskCacheMap::SizeOfExcludingThis(MallocSizeOf aMallocSizeOf)
- {
- size_t usage = aMallocSizeOf(mRecordArray);
- usage += aMallocSizeOf(mBuffer);
- usage += aMallocSizeOf(mMapFD);
- usage += aMallocSizeOf(mCleanFD);
- usage += aMallocSizeOf(mCacheDirectory);
- usage += aMallocSizeOf(mCleanCacheTimer);
- for (int i = 0; i < kNumBlockFiles; i++) {
- usage += mBlockFile[i].SizeOfExcludingThis(aMallocSizeOf);
- }
- return usage;
- }
- nsresult
- nsDiskCacheMap::InitCacheClean(nsIFile * cacheDirectory,
- nsDiskCache::CorruptCacheInfo * corruptInfo)
- {
- // The _CACHE_CLEAN_ file will be used in the future to determine
- // if the cache is clean or not.
- bool cacheCleanFileExists = false;
- nsCOMPtr<nsIFile> cacheCleanFile;
- nsresult rv = cacheDirectory->GetParent(getter_AddRefs(cacheCleanFile));
- if (NS_SUCCEEDED(rv)) {
- rv = cacheCleanFile->AppendNative(
- NS_LITERAL_CSTRING("_CACHE_CLEAN_"));
- if (NS_SUCCEEDED(rv)) {
- // Check if the file already exists, if it does, we will later read the
- // value and report it to telemetry.
- cacheCleanFile->Exists(&cacheCleanFileExists);
- }
- }
- if (NS_FAILED(rv)) {
- NS_WARNING("Could not build cache clean file path");
- *corruptInfo = nsDiskCache::kCacheCleanFilePathError;
- return rv;
- }
- // Make sure the _CACHE_CLEAN_ file exists
- rv = cacheCleanFile->OpenNSPRFileDesc(PR_RDWR | PR_CREATE_FILE,
- 00600, &mCleanFD);
- if (NS_FAILED(rv)) {
- NS_WARNING("Could not open cache clean file");
- *corruptInfo = nsDiskCache::kCacheCleanOpenFileError;
- return rv;
- }
- if (cacheCleanFileExists) {
- char clean = '0';
- int32_t bytesRead = PR_Read(mCleanFD, &clean, 1);
- if (bytesRead != 1) {
- NS_WARNING("Could not read _CACHE_CLEAN_ file contents");
- }
- }
- // Create a timer that will be used to validate the cache
- // as long as an activity threshold was met
- mCleanCacheTimer = do_CreateInstance("@mozilla.org/timer;1", &rv);
- if (NS_SUCCEEDED(rv)) {
- mCleanCacheTimer->SetTarget(nsCacheService::GlobalInstance()->mCacheIOThread);
- rv = ResetCacheTimer();
- }
- if (NS_FAILED(rv)) {
- NS_WARNING("Could not create cache clean timer");
- mCleanCacheTimer = nullptr;
- *corruptInfo = nsDiskCache::kCacheCleanTimerError;
- return rv;
- }
- return NS_OK;
- }
- nsresult
- nsDiskCacheMap::WriteCacheClean(bool clean)
- {
- nsCacheService::AssertOwnsLock();
- if (!mCleanFD) {
- NS_WARNING("Cache clean file is not open!");
- return NS_ERROR_FAILURE;
- }
- CACHE_LOG_DEBUG(("CACHE: WriteCacheClean: %d\n", clean? 1 : 0));
- // I'm using a simple '1' or '0' to denote cache clean
- // since it can be edited easily by any text editor for testing.
- char data = clean? '1' : '0';
- int32_t filePos = PR_Seek(mCleanFD, 0, PR_SEEK_SET);
- if (filePos != 0) {
- NS_WARNING("Could not seek in cache clean file!");
- return NS_ERROR_FAILURE;
- }
- int32_t bytesWritten = PR_Write(mCleanFD, &data, 1);
- if (bytesWritten != 1) {
- NS_WARNING("Could not write cache clean file!");
- return NS_ERROR_FAILURE;
- }
- PRStatus err = PR_Sync(mCleanFD);
- if (err != PR_SUCCESS) {
- NS_WARNING("Could not flush cache clean file!");
- }
- return NS_OK;
- }
- nsresult
- nsDiskCacheMap::InvalidateCache()
- {
- nsCacheService::AssertOwnsLock();
- CACHE_LOG_DEBUG(("CACHE: InvalidateCache\n"));
- nsresult rv;
-
- if (!mIsDirtyCacheFlushed) {
- rv = WriteCacheClean(false);
- if (NS_FAILED(rv)) {
- return rv;
- }
- mIsDirtyCacheFlushed = true;
- }
- rv = ResetCacheTimer();
- NS_ENSURE_SUCCESS(rv, rv);
- return NS_OK;
- }
- nsresult
- nsDiskCacheMap::ResetCacheTimer(int32_t timeout)
- {
- mCleanCacheTimer->Cancel();
- nsresult rv =
- mCleanCacheTimer->InitWithFuncCallback(RevalidateTimerCallback,
- nullptr, timeout,
- nsITimer::TYPE_ONE_SHOT);
- NS_ENSURE_SUCCESS(rv, rv);
- mLastInvalidateTime = PR_IntervalNow();
- return rv;
- }
- void
- nsDiskCacheMap::RevalidateTimerCallback(nsITimer *aTimer, void *arg)
- {
- nsCacheServiceAutoLock lock;
- if (!nsCacheService::gService->mDiskDevice ||
- !nsCacheService::gService->mDiskDevice->Initialized()) {
- return;
- }
- nsDiskCacheMap *diskCacheMap =
- &nsCacheService::gService->mDiskDevice->mCacheMap;
- // If we have less than kRevalidateCacheTimeout since the last timer was
- // issued then another thread called InvalidateCache. This won't catch
- // all cases where we wanted to cancel the timer, but under the lock it
- // is always OK to revalidate as long as IsCacheInSafeState() returns
- // true. We just want to avoid revalidating when we can to reduce IO
- // and this check will do that.
- uint32_t delta =
- PR_IntervalToMilliseconds(PR_IntervalNow() -
- diskCacheMap->mLastInvalidateTime) +
- kRevalidateCacheTimeoutTolerance;
- if (delta < kRevalidateCacheTimeout) {
- diskCacheMap->ResetCacheTimer();
- return;
- }
- nsresult rv = diskCacheMap->RevalidateCache();
- if (NS_FAILED(rv)) {
- diskCacheMap->ResetCacheTimer(kRevalidateCacheErrorTimeout);
- }
- }
- bool
- nsDiskCacheMap::IsCacheInSafeState()
- {
- return nsCacheService::GlobalInstance()->IsDoomListEmpty();
- }
- nsresult
- nsDiskCacheMap::RevalidateCache()
- {
- CACHE_LOG_DEBUG(("CACHE: RevalidateCache\n"));
- nsresult rv;
- if (!IsCacheInSafeState()) {
- CACHE_LOG_DEBUG(("CACHE: Revalidation should not performed because "
- "cache not in a safe state\n"));
- // Normally we would return an error here, but there is a bug where
- // the doom list sometimes gets an entry 'stuck' and doens't clear it
- // until browser shutdown. So we allow revalidation for the time being
- // to get proper telemetry data of how much the cache corruption plan
- // would help.
- }
- // If telemetry data shows it is worth it, we'll be flushing headers and
- // records before flushing the clean cache file.
-
- // Write out the _CACHE_CLEAN_ file with '1'
- rv = WriteCacheClean(true);
- if (NS_FAILED(rv)) {
- return rv;
- }
- mIsDirtyCacheFlushed = false;
- return NS_OK;
- }
|