12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148 |
- /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
- *
- * 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 <limits.h>
- #include "mozilla/DebugOnly.h"
- #include "nsCache.h"
- #include "nsIMemoryReporter.h"
- // include files for ftruncate (or equivalent)
- #if defined(XP_UNIX)
- #include <unistd.h>
- #elif defined(XP_WIN)
- #include <windows.h>
- #else
- // XXX add necessary include file for ftruncate (or equivalent)
- #endif
- #include "prthread.h"
- #include "private/pprio.h"
- #include "nsDiskCacheDevice.h"
- #include "nsDiskCacheEntry.h"
- #include "nsDiskCacheMap.h"
- #include "nsDiskCacheStreams.h"
- #include "nsDiskCache.h"
- #include "nsCacheService.h"
- #include "nsDeleteDir.h"
- #include "nsICacheVisitor.h"
- #include "nsReadableUtils.h"
- #include "nsIInputStream.h"
- #include "nsIOutputStream.h"
- #include "nsCRT.h"
- #include "nsCOMArray.h"
- #include "nsISimpleEnumerator.h"
- #include "nsThreadUtils.h"
- #include "mozilla/MemoryReporting.h"
- #include "mozilla/Telemetry.h"
- static const char DISK_CACHE_DEVICE_ID[] = { "disk" };
- using namespace mozilla;
- class nsDiskCacheDeviceDeactivateEntryEvent : public Runnable {
- public:
- nsDiskCacheDeviceDeactivateEntryEvent(nsDiskCacheDevice *device,
- nsCacheEntry * entry,
- nsDiskCacheBinding * binding)
- : mCanceled(false),
- mEntry(entry),
- mDevice(device),
- mBinding(binding)
- {
- }
- NS_IMETHOD Run() override
- {
- nsCacheServiceAutoLock lock;
- CACHE_LOG_DEBUG(("nsDiskCacheDeviceDeactivateEntryEvent[%p]\n", this));
- if (!mCanceled) {
- (void) mDevice->DeactivateEntry_Private(mEntry, mBinding);
- }
- return NS_OK;
- }
- void CancelEvent() { mCanceled = true; }
- private:
- bool mCanceled;
- nsCacheEntry *mEntry;
- nsDiskCacheDevice *mDevice;
- nsDiskCacheBinding *mBinding;
- };
- class nsEvictDiskCacheEntriesEvent : public Runnable {
- public:
- explicit nsEvictDiskCacheEntriesEvent(nsDiskCacheDevice *device)
- : mDevice(device) {}
- NS_IMETHOD Run() override
- {
- nsCacheServiceAutoLock lock;
- mDevice->EvictDiskCacheEntries(mDevice->mCacheCapacity);
- return NS_OK;
- }
- private:
- nsDiskCacheDevice *mDevice;
- };
- /******************************************************************************
- * nsDiskCacheEvictor
- *
- * Helper class for nsDiskCacheDevice.
- *
- *****************************************************************************/
- class nsDiskCacheEvictor : public nsDiskCacheRecordVisitor
- {
- public:
- nsDiskCacheEvictor( nsDiskCacheMap * cacheMap,
- nsDiskCacheBindery * cacheBindery,
- uint32_t targetSize,
- const char * clientID)
- : mCacheMap(cacheMap)
- , mBindery(cacheBindery)
- , mTargetSize(targetSize)
- , mClientID(clientID)
- {
- mClientIDSize = clientID ? strlen(clientID) : 0;
- }
-
- virtual int32_t VisitRecord(nsDiskCacheRecord * mapRecord);
-
- private:
- nsDiskCacheMap * mCacheMap;
- nsDiskCacheBindery * mBindery;
- uint32_t mTargetSize;
- const char * mClientID;
- uint32_t mClientIDSize;
- };
- int32_t
- nsDiskCacheEvictor::VisitRecord(nsDiskCacheRecord * mapRecord)
- {
- if (mCacheMap->TotalSize() < mTargetSize)
- return kStopVisitingRecords;
-
- if (mClientID) {
- // we're just evicting records for a specific client
- nsDiskCacheEntry * diskEntry = mCacheMap->ReadDiskCacheEntry(mapRecord);
- if (!diskEntry)
- return kVisitNextRecord; // XXX or delete record?
-
- // Compare clientID's without malloc
- if ((diskEntry->mKeySize <= mClientIDSize) ||
- (diskEntry->Key()[mClientIDSize] != ':') ||
- (memcmp(diskEntry->Key(), mClientID, mClientIDSize) != 0)) {
- return kVisitNextRecord; // clientID doesn't match, skip it
- }
- }
-
- nsDiskCacheBinding * binding = mBindery->FindActiveBinding(mapRecord->HashNumber());
- if (binding) {
- // If the entry is pending deactivation, cancel deactivation and doom
- // the entry
- if (binding->mDeactivateEvent) {
- binding->mDeactivateEvent->CancelEvent();
- binding->mDeactivateEvent = nullptr;
- }
- // We are currently using this entry, so all we can do is doom it.
- // Since we're enumerating the records, we don't want to call
- // DeleteRecord when nsCacheService::DoomEntry() calls us back.
- binding->mDoomed = true; // mark binding record as 'deleted'
- nsCacheService::DoomEntry(binding->mCacheEntry);
- } else {
- // entry not in use, just delete storage because we're enumerating the records
- (void) mCacheMap->DeleteStorage(mapRecord);
- }
- return kDeleteRecordAndContinue; // this will REALLY delete the record
- }
- /******************************************************************************
- * nsDiskCacheDeviceInfo
- *****************************************************************************/
- class nsDiskCacheDeviceInfo : public nsICacheDeviceInfo {
- public:
- NS_DECL_ISUPPORTS
- NS_DECL_NSICACHEDEVICEINFO
- explicit nsDiskCacheDeviceInfo(nsDiskCacheDevice* device)
- : mDevice(device)
- {
- }
- private:
- virtual ~nsDiskCacheDeviceInfo() {}
- nsDiskCacheDevice* mDevice;
- };
- NS_IMPL_ISUPPORTS(nsDiskCacheDeviceInfo, nsICacheDeviceInfo)
- NS_IMETHODIMP nsDiskCacheDeviceInfo::GetDescription(char ** aDescription)
- {
- NS_ENSURE_ARG_POINTER(aDescription);
- *aDescription = NS_strdup("Disk cache device");
- return *aDescription ? NS_OK : NS_ERROR_OUT_OF_MEMORY;
- }
- NS_IMETHODIMP nsDiskCacheDeviceInfo::GetUsageReport(char ** usageReport)
- {
- NS_ENSURE_ARG_POINTER(usageReport);
- nsCString buffer;
-
- buffer.AssignLiteral(" <tr>\n"
- " <th>Cache Directory:</th>\n"
- " <td>");
- nsCOMPtr<nsIFile> cacheDir;
- nsAutoString path;
- mDevice->getCacheDirectory(getter_AddRefs(cacheDir));
- nsresult rv = cacheDir->GetPath(path);
- if (NS_SUCCEEDED(rv)) {
- AppendUTF16toUTF8(path, buffer);
- } else {
- buffer.AppendLiteral("directory unavailable");
- }
- buffer.AppendLiteral("</td>\n"
- " </tr>\n");
- *usageReport = ToNewCString(buffer);
- if (!*usageReport) return NS_ERROR_OUT_OF_MEMORY;
- return NS_OK;
- }
- NS_IMETHODIMP nsDiskCacheDeviceInfo::GetEntryCount(uint32_t *aEntryCount)
- {
- NS_ENSURE_ARG_POINTER(aEntryCount);
- *aEntryCount = mDevice->getEntryCount();
- return NS_OK;
- }
- NS_IMETHODIMP nsDiskCacheDeviceInfo::GetTotalSize(uint32_t *aTotalSize)
- {
- NS_ENSURE_ARG_POINTER(aTotalSize);
- // Returned unit's are in bytes
- *aTotalSize = mDevice->getCacheSize() * 1024;
- return NS_OK;
- }
- NS_IMETHODIMP nsDiskCacheDeviceInfo::GetMaximumSize(uint32_t *aMaximumSize)
- {
- NS_ENSURE_ARG_POINTER(aMaximumSize);
- // Returned unit's are in bytes
- *aMaximumSize = mDevice->getCacheCapacity() * 1024;
- return NS_OK;
- }
- /******************************************************************************
- * nsDiskCache
- *****************************************************************************/
- /**
- * nsDiskCache::Hash(const char * key, PLDHashNumber initval)
- *
- * See http://burtleburtle.net/bob/hash/evahash.html for more information
- * about this hash function.
- *
- * This algorithm of this method implies nsDiskCacheRecords will be stored
- * in a certain order on disk. If the algorithm changes, existing cache
- * map files may become invalid, and therefore the kCurrentVersion needs
- * to be revised.
- */
- static inline void hashmix(uint32_t& a, uint32_t& b, uint32_t& c)
- {
- a -= b; a -= c; a ^= (c>>13);
- b -= c; b -= a; b ^= (a<<8);
- c -= a; c -= b; c ^= (b>>13);
- a -= b; a -= c; a ^= (c>>12);
- b -= c; b -= a; b ^= (a<<16);
- c -= a; c -= b; c ^= (b>>5);
- a -= b; a -= c; a ^= (c>>3);
- b -= c; b -= a; b ^= (a<<10);
- c -= a; c -= b; c ^= (b>>15);
- }
- PLDHashNumber
- nsDiskCache::Hash(const char * key, PLDHashNumber initval)
- {
- const uint8_t *k = reinterpret_cast<const uint8_t*>(key);
- uint32_t a, b, c, len, length;
- length = strlen(key);
- /* Set up the internal state */
- len = length;
- a = b = 0x9e3779b9; /* the golden ratio; an arbitrary value */
- c = initval; /* variable initialization of internal state */
- /*---------------------------------------- handle most of the key */
- while (len >= 12)
- {
- a += k[0] + (uint32_t(k[1])<<8) + (uint32_t(k[2])<<16) + (uint32_t(k[3])<<24);
- b += k[4] + (uint32_t(k[5])<<8) + (uint32_t(k[6])<<16) + (uint32_t(k[7])<<24);
- c += k[8] + (uint32_t(k[9])<<8) + (uint32_t(k[10])<<16) + (uint32_t(k[11])<<24);
- hashmix(a, b, c);
- k += 12; len -= 12;
- }
- /*------------------------------------- handle the last 11 bytes */
- c += length;
- switch(len) { /* all the case statements fall through */
- case 11: c += (uint32_t(k[10])<<24); MOZ_FALLTHROUGH;
- case 10: c += (uint32_t(k[9])<<16); MOZ_FALLTHROUGH;
- case 9 : c += (uint32_t(k[8])<<8); MOZ_FALLTHROUGH;
- /* the low-order byte of c is reserved for the length */
- case 8 : b += (uint32_t(k[7])<<24); MOZ_FALLTHROUGH;
- case 7 : b += (uint32_t(k[6])<<16); MOZ_FALLTHROUGH;
- case 6 : b += (uint32_t(k[5])<<8); MOZ_FALLTHROUGH;
- case 5 : b += k[4]; MOZ_FALLTHROUGH;
- case 4 : a += (uint32_t(k[3])<<24); MOZ_FALLTHROUGH;
- case 3 : a += (uint32_t(k[2])<<16); MOZ_FALLTHROUGH;
- case 2 : a += (uint32_t(k[1])<<8); MOZ_FALLTHROUGH;
- case 1 : a += k[0];
- /* case 0: nothing left to add */
- }
- hashmix(a, b, c);
- return c;
- }
- nsresult
- nsDiskCache::Truncate(PRFileDesc * fd, uint32_t newEOF)
- {
- // use modified SetEOF from nsFileStreams::SetEOF()
- #if defined(XP_UNIX)
- if (ftruncate(PR_FileDesc2NativeHandle(fd), newEOF) != 0) {
- NS_ERROR("ftruncate failed");
- return NS_ERROR_FAILURE;
- }
- #elif defined(XP_WIN)
- int32_t cnt = PR_Seek(fd, newEOF, PR_SEEK_SET);
- if (cnt == -1) return NS_ERROR_FAILURE;
- if (!SetEndOfFile((HANDLE) PR_FileDesc2NativeHandle(fd))) {
- NS_ERROR("SetEndOfFile failed");
- return NS_ERROR_FAILURE;
- }
- #else
- // add implementations for other platforms here
- #endif
- return NS_OK;
- }
- /******************************************************************************
- * nsDiskCacheDevice
- *****************************************************************************/
- nsDiskCacheDevice::nsDiskCacheDevice()
- : mCacheCapacity(0)
- , mMaxEntrySize(-1) // -1 means "no limit"
- , mInitialized(false)
- , mClearingDiskCache(false)
- {
- }
- nsDiskCacheDevice::~nsDiskCacheDevice()
- {
- Shutdown();
- }
- /**
- * methods of nsCacheDevice
- */
- nsresult
- nsDiskCacheDevice::Init()
- {
- nsresult rv;
- if (Initialized()) {
- NS_ERROR("Disk cache already initialized!");
- return NS_ERROR_UNEXPECTED;
- }
- if (!mCacheDirectory)
- return NS_ERROR_FAILURE;
- mBindery.Init();
- // Open Disk Cache
- rv = OpenDiskCache();
- if (NS_FAILED(rv)) {
- (void) mCacheMap.Close(false);
- return rv;
- }
- mInitialized = true;
- return NS_OK;
- }
- /**
- * NOTE: called while holding the cache service lock
- */
- nsresult
- nsDiskCacheDevice::Shutdown()
- {
- nsCacheService::AssertOwnsLock();
- nsresult rv = Shutdown_Private(true);
- if (NS_FAILED(rv))
- return rv;
- return NS_OK;
- }
- nsresult
- nsDiskCacheDevice::Shutdown_Private(bool flush)
- {
- CACHE_LOG_DEBUG(("CACHE: disk Shutdown_Private [%u]\n", flush));
- if (Initialized()) {
- // check cache limits in case we need to evict.
- EvictDiskCacheEntries(mCacheCapacity);
- // At this point there may be a number of pending cache-requests on the
- // cache-io thread. Wait for all these to run before we wipe out our
- // datastructures (see bug #620660)
- (void) nsCacheService::SyncWithCacheIOThread();
- // write out persistent information about the cache.
- (void) mCacheMap.Close(flush);
- mBindery.Reset();
- mInitialized = false;
- }
- return NS_OK;
- }
- const char *
- nsDiskCacheDevice::GetDeviceID()
- {
- return DISK_CACHE_DEVICE_ID;
- }
- /**
- * FindEntry -
- *
- * cases: key not in disk cache, hash number free
- * key not in disk cache, hash number used
- * key in disk cache
- *
- * NOTE: called while holding the cache service lock
- */
- nsCacheEntry *
- nsDiskCacheDevice::FindEntry(nsCString * key, bool *collision)
- {
- if (!Initialized()) return nullptr; // NS_ERROR_NOT_INITIALIZED
- if (mClearingDiskCache) return nullptr;
- nsDiskCacheRecord record;
- nsDiskCacheBinding * binding = nullptr;
- PLDHashNumber hashNumber = nsDiskCache::Hash(key->get());
- *collision = false;
- binding = mBindery.FindActiveBinding(hashNumber);
- if (binding && !binding->mCacheEntry->Key()->Equals(*key)) {
- *collision = true;
- return nullptr;
- } else if (binding && binding->mDeactivateEvent) {
- binding->mDeactivateEvent->CancelEvent();
- binding->mDeactivateEvent = nullptr;
- CACHE_LOG_DEBUG(("CACHE: reusing deactivated entry %p " \
- "req-key=%s entry-key=%s\n",
- binding->mCacheEntry, key, binding->mCacheEntry->Key()));
- return binding->mCacheEntry; // just return this one, observing that
- // FindActiveBinding() does not return
- // bindings to doomed entries
- }
- binding = nullptr;
- // lookup hash number in cache map
- nsresult rv = mCacheMap.FindRecord(hashNumber, &record);
- if (NS_FAILED(rv)) return nullptr; // XXX log error?
-
- nsDiskCacheEntry * diskEntry = mCacheMap.ReadDiskCacheEntry(&record);
- if (!diskEntry) return nullptr;
-
- // compare key to be sure
- if (!key->Equals(diskEntry->Key())) {
- *collision = true;
- return nullptr;
- }
-
- nsCacheEntry * entry = diskEntry->CreateCacheEntry(this);
- if (entry) {
- binding = mBindery.CreateBinding(entry, &record);
- if (!binding) {
- delete entry;
- entry = nullptr;
- }
- }
- if (!entry) {
- (void) mCacheMap.DeleteStorage(&record);
- (void) mCacheMap.DeleteRecord(&record);
- }
-
- return entry;
- }
- /**
- * NOTE: called while holding the cache service lock
- */
- nsresult
- nsDiskCacheDevice::DeactivateEntry(nsCacheEntry * entry)
- {
- nsDiskCacheBinding * binding = GetCacheEntryBinding(entry);
- if (!IsValidBinding(binding))
- return NS_ERROR_UNEXPECTED;
- CACHE_LOG_DEBUG(("CACHE: disk DeactivateEntry [%p %x]\n",
- entry, binding->mRecord.HashNumber()));
- nsDiskCacheDeviceDeactivateEntryEvent *event =
- new nsDiskCacheDeviceDeactivateEntryEvent(this, entry, binding);
- // ensure we can cancel the event via the binding later if necessary
- binding->mDeactivateEvent = event;
- DebugOnly<nsresult> rv = nsCacheService::DispatchToCacheIOThread(event);
- NS_ASSERTION(NS_SUCCEEDED(rv), "DeactivateEntry: Failed dispatching "
- "deactivation event");
- return NS_OK;
- }
- /**
- * NOTE: called while holding the cache service lock
- */
- nsresult
- nsDiskCacheDevice::DeactivateEntry_Private(nsCacheEntry * entry,
- nsDiskCacheBinding * binding)
- {
- nsresult rv = NS_OK;
- if (entry->IsDoomed()) {
- // delete data, entry, record from disk for entry
- rv = mCacheMap.DeleteStorage(&binding->mRecord);
- } else {
- // save stuff to disk for entry
- rv = mCacheMap.WriteDiskCacheEntry(binding);
- if (NS_FAILED(rv)) {
- // clean up as best we can
- (void) mCacheMap.DeleteStorage(&binding->mRecord);
- (void) mCacheMap.DeleteRecord(&binding->mRecord);
- binding->mDoomed = true; // record is no longer in cache map
- }
- }
- mBindery.RemoveBinding(binding); // extract binding from collision detection stuff
- delete entry; // which will release binding
- return rv;
- }
- /**
- * BindEntry()
- * no hash number collision -> no problem
- * collision
- * record not active -> evict, no problem
- * record is active
- * record is already doomed -> record shouldn't have been in map, no problem
- * record is not doomed -> doom, and replace record in map
- *
- * walk matching hashnumber list to find lowest generation number
- * take generation number from other (data/meta) location,
- * or walk active list
- *
- * NOTE: called while holding the cache service lock
- */
- nsresult
- nsDiskCacheDevice::BindEntry(nsCacheEntry * entry)
- {
- if (!Initialized()) return NS_ERROR_NOT_INITIALIZED;
- if (mClearingDiskCache) return NS_ERROR_NOT_AVAILABLE;
- nsresult rv = NS_OK;
- nsDiskCacheRecord record, oldRecord;
- nsDiskCacheBinding *binding;
- PLDHashNumber hashNumber = nsDiskCache::Hash(entry->Key()->get());
- // Find out if there is already an active binding for this hash. If yes it
- // should have another key since BindEntry() shouldn't be called twice for
- // the same entry. Doom the old entry, the new one will get another
- // generation number so files won't collide.
- binding = mBindery.FindActiveBinding(hashNumber);
- if (binding) {
- NS_ASSERTION(!binding->mCacheEntry->Key()->Equals(*entry->Key()),
- "BindEntry called for already bound entry!");
- // If the entry is pending deactivation, cancel deactivation
- if (binding->mDeactivateEvent) {
- binding->mDeactivateEvent->CancelEvent();
- binding->mDeactivateEvent = nullptr;
- }
- nsCacheService::DoomEntry(binding->mCacheEntry);
- binding = nullptr;
- }
- // Lookup hash number in cache map. There can be a colliding inactive entry.
- // See bug #321361 comment 21 for the scenario. If there is such entry,
- // delete it.
- rv = mCacheMap.FindRecord(hashNumber, &record);
- if (NS_SUCCEEDED(rv)) {
- nsDiskCacheEntry * diskEntry = mCacheMap.ReadDiskCacheEntry(&record);
- if (diskEntry) {
- // compare key to be sure
- if (!entry->Key()->Equals(diskEntry->Key())) {
- mCacheMap.DeleteStorage(&record);
- rv = mCacheMap.DeleteRecord(&record);
- if (NS_FAILED(rv)) return rv;
- }
- }
- record = nsDiskCacheRecord();
- }
- // create a new record for this entry
- record.SetHashNumber(nsDiskCache::Hash(entry->Key()->get()));
- record.SetEvictionRank(ULONG_MAX - SecondsFromPRTime(PR_Now()));
- CACHE_LOG_DEBUG(("CACHE: disk BindEntry [%p %x]\n",
- entry, record.HashNumber()));
- if (!entry->IsDoomed()) {
- // if entry isn't doomed, add it to the cache map
- rv = mCacheMap.AddRecord(&record, &oldRecord); // deletes old record, if any
- if (NS_FAILED(rv)) return rv;
-
- uint32_t oldHashNumber = oldRecord.HashNumber();
- if (oldHashNumber) {
- // gotta evict this one first
- nsDiskCacheBinding * oldBinding = mBindery.FindActiveBinding(oldHashNumber);
- if (oldBinding) {
- // XXX if debug : compare keys for hashNumber collision
- if (!oldBinding->mCacheEntry->IsDoomed()) {
- // If the old entry is pending deactivation, cancel deactivation
- if (oldBinding->mDeactivateEvent) {
- oldBinding->mDeactivateEvent->CancelEvent();
- oldBinding->mDeactivateEvent = nullptr;
- }
- // we've got a live one!
- nsCacheService::DoomEntry(oldBinding->mCacheEntry);
- // storage will be delete when oldBinding->mCacheEntry is Deactivated
- }
- } else {
- // delete storage
- // XXX if debug : compare keys for hashNumber collision
- rv = mCacheMap.DeleteStorage(&oldRecord);
- if (NS_FAILED(rv)) return rv; // XXX delete record we just added?
- }
- }
- }
-
- // Make sure this entry has its associated nsDiskCacheBinding attached.
- binding = mBindery.CreateBinding(entry, &record);
- NS_ASSERTION(binding, "nsDiskCacheDevice::BindEntry");
- if (!binding) return NS_ERROR_OUT_OF_MEMORY;
- NS_ASSERTION(binding->mRecord.ValidRecord(), "bad cache map record");
- return NS_OK;
- }
- /**
- * NOTE: called while holding the cache service lock
- */
- void
- nsDiskCacheDevice::DoomEntry(nsCacheEntry * entry)
- {
- CACHE_LOG_DEBUG(("CACHE: disk DoomEntry [%p]\n", entry));
- nsDiskCacheBinding * binding = GetCacheEntryBinding(entry);
- NS_ASSERTION(binding, "DoomEntry: binding == nullptr");
- if (!binding)
- return;
- if (!binding->mDoomed) {
- // so it can't be seen by FindEntry() ever again.
- #ifdef DEBUG
- nsresult rv =
- #endif
- mCacheMap.DeleteRecord(&binding->mRecord);
- NS_ASSERTION(NS_SUCCEEDED(rv),"DeleteRecord failed.");
- binding->mDoomed = true; // record in no longer in cache map
- }
- }
- /**
- * NOTE: called while holding the cache service lock
- */
- nsresult
- nsDiskCacheDevice::OpenInputStreamForEntry(nsCacheEntry * entry,
- nsCacheAccessMode mode,
- uint32_t offset,
- nsIInputStream ** result)
- {
- CACHE_LOG_DEBUG(("CACHE: disk OpenInputStreamForEntry [%p %x %u]\n",
- entry, mode, offset));
- NS_ENSURE_ARG_POINTER(entry);
- NS_ENSURE_ARG_POINTER(result);
- nsresult rv;
- nsDiskCacheBinding * binding = GetCacheEntryBinding(entry);
- if (!IsValidBinding(binding))
- return NS_ERROR_UNEXPECTED;
- NS_ASSERTION(binding->mCacheEntry == entry, "binding & entry don't point to each other");
- rv = binding->EnsureStreamIO();
- if (NS_FAILED(rv)) return rv;
- return binding->mStreamIO->GetInputStream(offset, result);
- }
- /**
- * NOTE: called while holding the cache service lock
- */
- nsresult
- nsDiskCacheDevice::OpenOutputStreamForEntry(nsCacheEntry * entry,
- nsCacheAccessMode mode,
- uint32_t offset,
- nsIOutputStream ** result)
- {
- CACHE_LOG_DEBUG(("CACHE: disk OpenOutputStreamForEntry [%p %x %u]\n",
- entry, mode, offset));
-
- NS_ENSURE_ARG_POINTER(entry);
- NS_ENSURE_ARG_POINTER(result);
- nsresult rv;
- nsDiskCacheBinding * binding = GetCacheEntryBinding(entry);
- if (!IsValidBinding(binding))
- return NS_ERROR_UNEXPECTED;
-
- NS_ASSERTION(binding->mCacheEntry == entry, "binding & entry don't point to each other");
- rv = binding->EnsureStreamIO();
- if (NS_FAILED(rv)) return rv;
- return binding->mStreamIO->GetOutputStream(offset, result);
- }
- /**
- * NOTE: called while holding the cache service lock
- */
- nsresult
- nsDiskCacheDevice::GetFileForEntry(nsCacheEntry * entry,
- nsIFile ** result)
- {
- NS_ENSURE_ARG_POINTER(result);
- *result = nullptr;
- nsresult rv;
-
- nsDiskCacheBinding * binding = GetCacheEntryBinding(entry);
- if (!IsValidBinding(binding))
- return NS_ERROR_UNEXPECTED;
- // check/set binding->mRecord for separate file, sync w/mCacheMap
- if (binding->mRecord.DataLocationInitialized()) {
- if (binding->mRecord.DataFile() != 0)
- return NS_ERROR_NOT_AVAILABLE; // data not stored as separate file
- NS_ASSERTION(binding->mRecord.DataFileGeneration() == binding->mGeneration, "error generations out of sync");
- } else {
- binding->mRecord.SetDataFileGeneration(binding->mGeneration);
- binding->mRecord.SetDataFileSize(0); // 1k minimum
- if (!binding->mDoomed) {
- // record stored in cache map, so update it
- rv = mCacheMap.UpdateRecord(&binding->mRecord);
- if (NS_FAILED(rv)) return rv;
- }
- }
-
- nsCOMPtr<nsIFile> file;
- rv = mCacheMap.GetFileForDiskCacheRecord(&binding->mRecord,
- nsDiskCache::kData,
- false,
- getter_AddRefs(file));
- if (NS_FAILED(rv)) return rv;
-
- NS_IF_ADDREF(*result = file);
- return NS_OK;
- }
- /**
- * This routine will get called every time an open descriptor is written to.
- *
- * NOTE: called while holding the cache service lock
- */
- nsresult
- nsDiskCacheDevice::OnDataSizeChange(nsCacheEntry * entry, int32_t deltaSize)
- {
- CACHE_LOG_DEBUG(("CACHE: disk OnDataSizeChange [%p %d]\n",
- entry, deltaSize));
- // If passed a negative value, then there's nothing to do.
- if (deltaSize < 0)
- return NS_OK;
- nsDiskCacheBinding * binding = GetCacheEntryBinding(entry);
- if (!IsValidBinding(binding))
- return NS_ERROR_UNEXPECTED;
- NS_ASSERTION(binding->mRecord.ValidRecord(), "bad record");
- uint32_t newSize = entry->DataSize() + deltaSize;
- uint32_t newSizeK = ((newSize + 0x3FF) >> 10);
- // If the new size is larger than max. file size or larger than
- // 1/8 the cache capacity (which is in KiB's), doom the entry and abort.
- if (EntryIsTooBig(newSize)) {
- #ifdef DEBUG
- nsresult rv =
- #endif
- nsCacheService::DoomEntry(entry);
- NS_ASSERTION(NS_SUCCEEDED(rv),"DoomEntry() failed.");
- return NS_ERROR_ABORT;
- }
- uint32_t sizeK = ((entry->DataSize() + 0x03FF) >> 10); // round up to next 1k
- // In total count we ignore anything over kMaxDataSizeK (bug #651100), so
- // the target capacity should be calculated the same way.
- if (sizeK > kMaxDataSizeK) sizeK = kMaxDataSizeK;
- if (newSizeK > kMaxDataSizeK) newSizeK = kMaxDataSizeK;
- // pre-evict entries to make space for new data
- uint32_t targetCapacity = mCacheCapacity > (newSizeK - sizeK)
- ? mCacheCapacity - (newSizeK - sizeK)
- : 0;
- EvictDiskCacheEntries(targetCapacity);
-
- return NS_OK;
- }
- /******************************************************************************
- * EntryInfoVisitor
- *****************************************************************************/
- class EntryInfoVisitor : public nsDiskCacheRecordVisitor
- {
- public:
- EntryInfoVisitor(nsDiskCacheMap * cacheMap,
- nsICacheVisitor * visitor)
- : mCacheMap(cacheMap)
- , mVisitor(visitor)
- {}
-
- virtual int32_t VisitRecord(nsDiskCacheRecord * mapRecord)
- {
- // XXX optimization: do we have this record in memory?
-
- // read in the entry (metadata)
- nsDiskCacheEntry * diskEntry = mCacheMap->ReadDiskCacheEntry(mapRecord);
- if (!diskEntry) {
- return kVisitNextRecord;
- }
- // create nsICacheEntryInfo
- nsDiskCacheEntryInfo * entryInfo = new nsDiskCacheEntryInfo(DISK_CACHE_DEVICE_ID, diskEntry);
- if (!entryInfo) {
- return kStopVisitingRecords;
- }
- nsCOMPtr<nsICacheEntryInfo> ref(entryInfo);
-
- bool keepGoing;
- (void)mVisitor->VisitEntry(DISK_CACHE_DEVICE_ID, entryInfo, &keepGoing);
- return keepGoing ? kVisitNextRecord : kStopVisitingRecords;
- }
-
- private:
- nsDiskCacheMap * mCacheMap;
- nsICacheVisitor * mVisitor;
- };
- nsresult
- nsDiskCacheDevice::Visit(nsICacheVisitor * visitor)
- {
- if (!Initialized()) return NS_ERROR_NOT_INITIALIZED;
- nsDiskCacheDeviceInfo* deviceInfo = new nsDiskCacheDeviceInfo(this);
- nsCOMPtr<nsICacheDeviceInfo> ref(deviceInfo);
-
- bool keepGoing;
- nsresult rv = visitor->VisitDevice(DISK_CACHE_DEVICE_ID, deviceInfo, &keepGoing);
- if (NS_FAILED(rv)) return rv;
-
- if (keepGoing) {
- EntryInfoVisitor infoVisitor(&mCacheMap, visitor);
- return mCacheMap.VisitRecords(&infoVisitor);
- }
- return NS_OK;
- }
- // Max allowed size for an entry is currently MIN(mMaxEntrySize, 1/8 CacheCapacity)
- bool
- nsDiskCacheDevice::EntryIsTooBig(int64_t entrySize)
- {
- if (mMaxEntrySize == -1) // no limit
- return entrySize > (static_cast<int64_t>(mCacheCapacity) * 1024 / 8);
- else
- return entrySize > mMaxEntrySize ||
- entrySize > (static_cast<int64_t>(mCacheCapacity) * 1024 / 8);
- }
- nsresult
- nsDiskCacheDevice::EvictEntries(const char * clientID)
- {
- CACHE_LOG_DEBUG(("CACHE: disk EvictEntries [%s]\n", clientID));
- if (!Initialized()) return NS_ERROR_NOT_INITIALIZED;
- nsresult rv;
- if (clientID == nullptr) {
- // we're clearing the entire disk cache
- rv = ClearDiskCache();
- if (rv != NS_ERROR_CACHE_IN_USE)
- return rv;
- }
- nsDiskCacheEvictor evictor(&mCacheMap, &mBindery, 0, clientID);
- rv = mCacheMap.VisitRecords(&evictor);
-
- if (clientID == nullptr) // we tried to clear the entire cache
- rv = mCacheMap.Trim(); // so trim cache block files (if possible)
- return rv;
- }
- /**
- * private methods
- */
- nsresult
- nsDiskCacheDevice::OpenDiskCache()
- {
- // if we don't have a cache directory, create one and open it
- bool exists;
- nsresult rv = mCacheDirectory->Exists(&exists);
- if (NS_FAILED(rv))
- return rv;
- if (exists) {
- // Try opening cache map file.
- nsDiskCache::CorruptCacheInfo corruptInfo;
- rv = mCacheMap.Open(mCacheDirectory, &corruptInfo);
- if (rv == NS_ERROR_ALREADY_INITIALIZED) {
- NS_WARNING("nsDiskCacheDevice::OpenDiskCache: already open!");
- } else if (NS_FAILED(rv)) {
- // Consider cache corrupt: delete it
- // delay delete by 1 minute to avoid IO thrash at startup
- rv = nsDeleteDir::DeleteDir(mCacheDirectory, true, 60000);
- if (NS_FAILED(rv))
- return rv;
- exists = false;
- }
- }
- // if we don't have a cache directory, create one and open it
- if (!exists) {
- nsCacheService::MarkStartingFresh();
- rv = mCacheDirectory->Create(nsIFile::DIRECTORY_TYPE, 0777);
- CACHE_LOG_PATH(LogLevel::Info, "\ncreate cache directory: %s\n", mCacheDirectory);
- CACHE_LOG_INFO(("mCacheDirectory->Create() = %x\n", rv));
- if (NS_FAILED(rv))
- return rv;
-
- // reopen the cache map
- nsDiskCache::CorruptCacheInfo corruptInfo;
- rv = mCacheMap.Open(mCacheDirectory, &corruptInfo);
- if (NS_FAILED(rv))
- return rv;
- }
- return NS_OK;
- }
- nsresult
- nsDiskCacheDevice::ClearDiskCache()
- {
- if (mBindery.ActiveBindings())
- return NS_ERROR_CACHE_IN_USE;
- mClearingDiskCache = true;
- nsresult rv = Shutdown_Private(false); // false: don't bother flushing
- if (NS_FAILED(rv))
- return rv;
- mClearingDiskCache = false;
- // If the disk cache directory is already gone, then it's not an error if
- // we fail to delete it ;-)
- rv = nsDeleteDir::DeleteDir(mCacheDirectory, true);
- if (NS_FAILED(rv) && rv != NS_ERROR_FILE_TARGET_DOES_NOT_EXIST)
- return rv;
- return Init();
- }
- nsresult
- nsDiskCacheDevice::EvictDiskCacheEntries(uint32_t targetCapacity)
- {
- CACHE_LOG_DEBUG(("CACHE: disk EvictDiskCacheEntries [%u]\n",
- targetCapacity));
- NS_ASSERTION(targetCapacity > 0, "oops");
- if (mCacheMap.TotalSize() < targetCapacity)
- return NS_OK;
- // targetCapacity is in KiB's
- nsDiskCacheEvictor evictor(&mCacheMap, &mBindery, targetCapacity, nullptr);
- return mCacheMap.EvictRecords(&evictor);
- }
- /**
- * methods for prefs
- */
- void
- nsDiskCacheDevice::SetCacheParentDirectory(nsIFile * parentDir)
- {
- nsresult rv;
- bool exists;
- if (Initialized()) {
- NS_ASSERTION(false, "Cannot switch cache directory when initialized");
- return;
- }
- if (!parentDir) {
- mCacheDirectory = nullptr;
- return;
- }
- // ensure parent directory exists
- rv = parentDir->Exists(&exists);
- if (NS_SUCCEEDED(rv) && !exists)
- rv = parentDir->Create(nsIFile::DIRECTORY_TYPE, 0700);
- if (NS_FAILED(rv)) return;
- // ensure cache directory exists
- nsCOMPtr<nsIFile> directory;
-
- rv = parentDir->Clone(getter_AddRefs(directory));
- if (NS_FAILED(rv)) return;
- rv = directory->AppendNative(NS_LITERAL_CSTRING("Cache"));
- if (NS_FAILED(rv)) return;
-
- mCacheDirectory = do_QueryInterface(directory);
- }
- void
- nsDiskCacheDevice::getCacheDirectory(nsIFile ** result)
- {
- *result = mCacheDirectory;
- NS_IF_ADDREF(*result);
- }
- /**
- * NOTE: called while holding the cache service lock
- */
- void
- nsDiskCacheDevice::SetCapacity(uint32_t capacity)
- {
- // Units are KiB's
- mCacheCapacity = capacity;
- if (Initialized()) {
- if (NS_IsMainThread()) {
- // Do not evict entries on the main thread
- nsCacheService::DispatchToCacheIOThread(
- new nsEvictDiskCacheEntriesEvent(this));
- } else {
- // start evicting entries if the new size is smaller!
- EvictDiskCacheEntries(mCacheCapacity);
- }
- }
- // Let cache map know of the new capacity
- mCacheMap.NotifyCapacityChange(capacity);
- }
- uint32_t nsDiskCacheDevice::getCacheCapacity()
- {
- return mCacheCapacity;
- }
- uint32_t nsDiskCacheDevice::getCacheSize()
- {
- return mCacheMap.TotalSize();
- }
- uint32_t nsDiskCacheDevice::getEntryCount()
- {
- return mCacheMap.EntryCount();
- }
- void
- nsDiskCacheDevice::SetMaxEntrySize(int32_t maxSizeInKilobytes)
- {
- // Internal units are bytes. Changing this only takes effect *after* the
- // change and has no consequences for existing cache-entries
- if (maxSizeInKilobytes >= 0)
- mMaxEntrySize = maxSizeInKilobytes * 1024;
- else
- mMaxEntrySize = -1;
- }
- size_t
- nsDiskCacheDevice::SizeOfIncludingThis(MallocSizeOf aMallocSizeOf)
- {
- size_t usage = aMallocSizeOf(this);
- usage += mCacheMap.SizeOfExcludingThis(aMallocSizeOf);
- usage += mBindery.SizeOfExcludingThis(aMallocSizeOf);
- return usage;
- }
|