Bug 783551 - Get tooltool running on the b2g on OS X builds. r=respindola
[gecko.git] / netwerk / cache / nsDiskCacheDevice.cpp
blob6988fc3fe4d1a8dc5c34421c09b20b68f97ce5e5
1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
3 * This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
7 #include <limits.h>
9 // include files for ftruncate (or equivalent)
10 #if defined(XP_UNIX)
11 #include <unistd.h>
12 #elif defined(XP_WIN)
13 #include <windows.h>
14 #elif defined(XP_OS2)
15 #define INCL_DOSERRORS
16 #include <os2.h>
17 #else
18 // XXX add necessary include file for ftruncate (or equivalent)
19 #endif
21 #include "prtypes.h"
22 #include "prthread.h"
23 #include "prbit.h"
25 #include "private/pprio.h"
27 #include "nsDiskCacheDevice.h"
28 #include "nsDiskCacheEntry.h"
29 #include "nsDiskCacheMap.h"
30 #include "nsDiskCacheStreams.h"
32 #include "nsDiskCache.h"
34 #include "nsCacheService.h"
35 #include "nsCache.h"
37 #include "nsDeleteDir.h"
39 #include "nsICacheVisitor.h"
40 #include "nsReadableUtils.h"
41 #include "nsIInputStream.h"
42 #include "nsIOutputStream.h"
43 #include "nsCRT.h"
44 #include "nsCOMArray.h"
45 #include "nsISimpleEnumerator.h"
47 #include "mozilla/FunctionTimer.h"
48 #include "nsThreadUtils.h"
49 #include "mozilla/Telemetry.h"
51 static const char DISK_CACHE_DEVICE_ID[] = { "disk" };
52 using namespace mozilla;
54 class nsDiskCacheDeviceDeactivateEntryEvent : public nsRunnable {
55 public:
56 nsDiskCacheDeviceDeactivateEntryEvent(nsDiskCacheDevice *device,
57 nsCacheEntry * entry,
58 nsDiskCacheBinding * binding)
59 : mCanceled(false),
60 mEntry(entry),
61 mDevice(device),
62 mBinding(binding)
66 NS_IMETHOD Run()
68 nsCacheServiceAutoLock lock(LOCK_TELEM(NSDISKCACHEDEVICEDEACTIVATEENTRYEVENT_RUN));
69 #ifdef PR_LOGGING
70 CACHE_LOG_DEBUG(("nsDiskCacheDeviceDeactivateEntryEvent[%p]\n", this));
71 #endif
72 if (!mCanceled) {
73 (void) mDevice->DeactivateEntry_Private(mEntry, mBinding);
75 return NS_OK;
78 void CancelEvent() { mCanceled = true; }
79 private:
80 bool mCanceled;
81 nsCacheEntry *mEntry;
82 nsDiskCacheDevice *mDevice;
83 nsDiskCacheBinding *mBinding;
86 class nsEvictDiskCacheEntriesEvent : public nsRunnable {
87 public:
88 nsEvictDiskCacheEntriesEvent(nsDiskCacheDevice *device)
89 : mDevice(device) {}
91 NS_IMETHOD Run()
93 nsCacheServiceAutoLock lock(LOCK_TELEM(NSEVICTDISKCACHEENTRIESEVENT_RUN));
94 mDevice->EvictDiskCacheEntries(mDevice->mCacheCapacity);
95 return NS_OK;
98 private:
99 nsDiskCacheDevice *mDevice;
102 /******************************************************************************
103 * nsDiskCacheEvictor
105 * Helper class for nsDiskCacheDevice.
107 *****************************************************************************/
109 class nsDiskCacheEvictor : public nsDiskCacheRecordVisitor
111 public:
112 nsDiskCacheEvictor( nsDiskCacheMap * cacheMap,
113 nsDiskCacheBindery * cacheBindery,
114 PRUint32 targetSize,
115 const char * clientID)
116 : mCacheMap(cacheMap)
117 , mBindery(cacheBindery)
118 , mTargetSize(targetSize)
119 , mClientID(clientID)
121 mClientIDSize = clientID ? strlen(clientID) : 0;
124 virtual PRInt32 VisitRecord(nsDiskCacheRecord * mapRecord);
126 private:
127 nsDiskCacheMap * mCacheMap;
128 nsDiskCacheBindery * mBindery;
129 PRUint32 mTargetSize;
130 const char * mClientID;
131 PRUint32 mClientIDSize;
135 PRInt32
136 nsDiskCacheEvictor::VisitRecord(nsDiskCacheRecord * mapRecord)
138 if (mCacheMap->TotalSize() < mTargetSize)
139 return kStopVisitingRecords;
141 if (mClientID) {
142 // we're just evicting records for a specific client
143 nsDiskCacheEntry * diskEntry = mCacheMap->ReadDiskCacheEntry(mapRecord);
144 if (!diskEntry)
145 return kVisitNextRecord; // XXX or delete record?
147 // Compare clientID's without malloc
148 if ((diskEntry->mKeySize <= mClientIDSize) ||
149 (diskEntry->Key()[mClientIDSize] != ':') ||
150 (memcmp(diskEntry->Key(), mClientID, mClientIDSize) != 0)) {
151 return kVisitNextRecord; // clientID doesn't match, skip it
155 nsDiskCacheBinding * binding = mBindery->FindActiveBinding(mapRecord->HashNumber());
156 if (binding) {
157 // If the entry is pending deactivation, cancel deactivation and doom
158 // the entry
159 if (binding->mDeactivateEvent) {
160 binding->mDeactivateEvent->CancelEvent();
161 binding->mDeactivateEvent = nullptr;
163 // We are currently using this entry, so all we can do is doom it.
164 // Since we're enumerating the records, we don't want to call
165 // DeleteRecord when nsCacheService::DoomEntry() calls us back.
166 binding->mDoomed = true; // mark binding record as 'deleted'
167 nsCacheService::DoomEntry(binding->mCacheEntry);
168 } else {
169 // entry not in use, just delete storage because we're enumerating the records
170 (void) mCacheMap->DeleteStorage(mapRecord);
173 return kDeleteRecordAndContinue; // this will REALLY delete the record
177 /******************************************************************************
178 * nsDiskCacheDeviceInfo
179 *****************************************************************************/
181 class nsDiskCacheDeviceInfo : public nsICacheDeviceInfo {
182 public:
183 NS_DECL_ISUPPORTS
184 NS_DECL_NSICACHEDEVICEINFO
186 nsDiskCacheDeviceInfo(nsDiskCacheDevice* device)
187 : mDevice(device)
191 virtual ~nsDiskCacheDeviceInfo() {}
193 private:
194 nsDiskCacheDevice* mDevice;
197 NS_IMPL_ISUPPORTS1(nsDiskCacheDeviceInfo, nsICacheDeviceInfo)
199 /* readonly attribute string description; */
200 NS_IMETHODIMP nsDiskCacheDeviceInfo::GetDescription(char ** aDescription)
202 NS_ENSURE_ARG_POINTER(aDescription);
203 *aDescription = NS_strdup("Disk cache device");
204 return *aDescription ? NS_OK : NS_ERROR_OUT_OF_MEMORY;
207 /* readonly attribute string usageReport; */
208 NS_IMETHODIMP nsDiskCacheDeviceInfo::GetUsageReport(char ** usageReport)
210 NS_ENSURE_ARG_POINTER(usageReport);
211 nsCString buffer;
213 buffer.AssignLiteral(" <tr>\n"
214 " <th>Cache Directory:</th>\n"
215 " <td>");
216 nsCOMPtr<nsIFile> cacheDir;
217 nsAutoString path;
218 mDevice->getCacheDirectory(getter_AddRefs(cacheDir));
219 nsresult rv = cacheDir->GetPath(path);
220 if (NS_SUCCEEDED(rv)) {
221 AppendUTF16toUTF8(path, buffer);
222 } else {
223 buffer.AppendLiteral("directory unavailable");
225 buffer.AppendLiteral("</td>\n"
226 " </tr>\n");
228 *usageReport = ToNewCString(buffer);
229 if (!*usageReport) return NS_ERROR_OUT_OF_MEMORY;
231 return NS_OK;
234 /* readonly attribute unsigned long entryCount; */
235 NS_IMETHODIMP nsDiskCacheDeviceInfo::GetEntryCount(PRUint32 *aEntryCount)
237 NS_ENSURE_ARG_POINTER(aEntryCount);
238 *aEntryCount = mDevice->getEntryCount();
239 return NS_OK;
242 /* readonly attribute unsigned long totalSize; */
243 NS_IMETHODIMP nsDiskCacheDeviceInfo::GetTotalSize(PRUint32 *aTotalSize)
245 NS_ENSURE_ARG_POINTER(aTotalSize);
246 // Returned unit's are in bytes
247 *aTotalSize = mDevice->getCacheSize() * 1024;
248 return NS_OK;
251 /* readonly attribute unsigned long maximumSize; */
252 NS_IMETHODIMP nsDiskCacheDeviceInfo::GetMaximumSize(PRUint32 *aMaximumSize)
254 NS_ENSURE_ARG_POINTER(aMaximumSize);
255 // Returned unit's are in bytes
256 *aMaximumSize = mDevice->getCacheCapacity() * 1024;
257 return NS_OK;
261 /******************************************************************************
262 * nsDiskCache
263 *****************************************************************************/
266 * nsDiskCache::Hash(const char * key, PLDHashNumber initval)
268 * See http://burtleburtle.net/bob/hash/evahash.html for more information
269 * about this hash function.
271 * This algorithm of this method implies nsDiskCacheRecords will be stored
272 * in a certain order on disk. If the algorithm changes, existing cache
273 * map files may become invalid, and therefore the kCurrentVersion needs
274 * to be revised.
277 static inline void hashmix(PRUint32& a, PRUint32& b, PRUint32& c)
279 a -= b; a -= c; a ^= (c>>13);
280 b -= c; b -= a; b ^= (a<<8);
281 c -= a; c -= b; c ^= (b>>13);
282 a -= b; a -= c; a ^= (c>>12);
283 b -= c; b -= a; b ^= (a<<16);
284 c -= a; c -= b; c ^= (b>>5);
285 a -= b; a -= c; a ^= (c>>3);
286 b -= c; b -= a; b ^= (a<<10);
287 c -= a; c -= b; c ^= (b>>15);
290 PLDHashNumber
291 nsDiskCache::Hash(const char * key, PLDHashNumber initval)
293 const PRUint8 *k = reinterpret_cast<const PRUint8*>(key);
294 PRUint32 a, b, c, len, length;
296 length = PL_strlen(key);
297 /* Set up the internal state */
298 len = length;
299 a = b = 0x9e3779b9; /* the golden ratio; an arbitrary value */
300 c = initval; /* variable initialization of internal state */
302 /*---------------------------------------- handle most of the key */
303 while (len >= 12)
305 a += k[0] + (PRUint32(k[1])<<8) + (PRUint32(k[2])<<16) + (PRUint32(k[3])<<24);
306 b += k[4] + (PRUint32(k[5])<<8) + (PRUint32(k[6])<<16) + (PRUint32(k[7])<<24);
307 c += k[8] + (PRUint32(k[9])<<8) + (PRUint32(k[10])<<16) + (PRUint32(k[11])<<24);
308 hashmix(a, b, c);
309 k += 12; len -= 12;
312 /*------------------------------------- handle the last 11 bytes */
313 c += length;
314 switch(len) { /* all the case statements fall through */
315 case 11: c += (PRUint32(k[10])<<24);
316 case 10: c += (PRUint32(k[9])<<16);
317 case 9 : c += (PRUint32(k[8])<<8);
318 /* the low-order byte of c is reserved for the length */
319 case 8 : b += (PRUint32(k[7])<<24);
320 case 7 : b += (PRUint32(k[6])<<16);
321 case 6 : b += (PRUint32(k[5])<<8);
322 case 5 : b += k[4];
323 case 4 : a += (PRUint32(k[3])<<24);
324 case 3 : a += (PRUint32(k[2])<<16);
325 case 2 : a += (PRUint32(k[1])<<8);
326 case 1 : a += k[0];
327 /* case 0: nothing left to add */
329 hashmix(a, b, c);
331 return c;
334 nsresult
335 nsDiskCache::Truncate(PRFileDesc * fd, PRUint32 newEOF)
337 // use modified SetEOF from nsFileStreams::SetEOF()
339 #if defined(XP_UNIX)
340 if (ftruncate(PR_FileDesc2NativeHandle(fd), newEOF) != 0) {
341 NS_ERROR("ftruncate failed");
342 return NS_ERROR_FAILURE;
345 #elif defined(XP_WIN)
346 PRInt32 cnt = PR_Seek(fd, newEOF, PR_SEEK_SET);
347 if (cnt == -1) return NS_ERROR_FAILURE;
348 if (!SetEndOfFile((HANDLE) PR_FileDesc2NativeHandle(fd))) {
349 NS_ERROR("SetEndOfFile failed");
350 return NS_ERROR_FAILURE;
353 #elif defined(XP_OS2)
354 if (DosSetFileSize((HFILE) PR_FileDesc2NativeHandle(fd), newEOF) != NO_ERROR) {
355 NS_ERROR("DosSetFileSize failed");
356 return NS_ERROR_FAILURE;
358 #else
359 // add implementations for other platforms here
360 #endif
361 return NS_OK;
365 /******************************************************************************
366 * nsDiskCacheDevice
367 *****************************************************************************/
369 nsDiskCacheDevice::nsDiskCacheDevice()
370 : mCacheCapacity(0)
371 , mMaxEntrySize(-1) // -1 means "no limit"
372 , mInitialized(false)
373 , mClearingDiskCache(false)
377 nsDiskCacheDevice::~nsDiskCacheDevice()
379 Shutdown();
384 * methods of nsCacheDevice
386 nsresult
387 nsDiskCacheDevice::Init()
389 NS_TIME_FUNCTION;
391 nsresult rv;
393 if (Initialized()) {
394 NS_ERROR("Disk cache already initialized!");
395 return NS_ERROR_UNEXPECTED;
398 if (!mCacheDirectory)
399 return NS_ERROR_FAILURE;
401 rv = mBindery.Init();
402 if (NS_FAILED(rv))
403 return rv;
405 nsDeleteDir::RemoveOldTrashes(mCacheDirectory);
407 // Open Disk Cache
408 rv = OpenDiskCache();
409 if (NS_FAILED(rv)) {
410 (void) mCacheMap.Close(false);
411 return rv;
414 mInitialized = true;
415 return NS_OK;
420 * NOTE: called while holding the cache service lock
422 nsresult
423 nsDiskCacheDevice::Shutdown()
425 nsCacheService::AssertOwnsLock();
427 nsresult rv = Shutdown_Private(true);
428 if (NS_FAILED(rv))
429 return rv;
431 return NS_OK;
435 nsresult
436 nsDiskCacheDevice::Shutdown_Private(bool flush)
438 CACHE_LOG_DEBUG(("CACHE: disk Shutdown_Private [%u]\n", flush));
440 if (Initialized()) {
441 // check cache limits in case we need to evict.
442 EvictDiskCacheEntries(mCacheCapacity);
444 // At this point there may be a number of pending cache-requests on the
445 // cache-io thread. Wait for all these to run before we wipe out our
446 // datastructures (see bug #620660)
447 (void) nsCacheService::SyncWithCacheIOThread();
449 // write out persistent information about the cache.
450 (void) mCacheMap.Close(flush);
452 mBindery.Reset();
454 mInitialized = false;
457 return NS_OK;
461 const char *
462 nsDiskCacheDevice::GetDeviceID()
464 return DISK_CACHE_DEVICE_ID;
468 * FindEntry -
470 * cases: key not in disk cache, hash number free
471 * key not in disk cache, hash number used
472 * key in disk cache
474 * NOTE: called while holding the cache service lock
476 nsCacheEntry *
477 nsDiskCacheDevice::FindEntry(nsCString * key, bool *collision)
479 Telemetry::AutoTimer<Telemetry::CACHE_DISK_SEARCH> timer;
480 if (!Initialized()) return nullptr; // NS_ERROR_NOT_INITIALIZED
481 if (mClearingDiskCache) return nullptr;
482 nsDiskCacheRecord record;
483 nsDiskCacheBinding * binding = nullptr;
484 PLDHashNumber hashNumber = nsDiskCache::Hash(key->get());
486 *collision = false;
488 binding = mBindery.FindActiveBinding(hashNumber);
489 if (binding && !binding->mCacheEntry->Key()->Equals(*key)) {
490 *collision = true;
491 return nullptr;
492 } else if (binding && binding->mDeactivateEvent) {
493 binding->mDeactivateEvent->CancelEvent();
494 binding->mDeactivateEvent = nullptr;
495 CACHE_LOG_DEBUG(("CACHE: reusing deactivated entry %p " \
496 "req-key=%s entry-key=%s\n",
497 binding->mCacheEntry, key, binding->mCacheEntry->Key()));
499 return binding->mCacheEntry; // just return this one, observing that
500 // FindActiveBinding() does not return
501 // bindings to doomed entries
503 binding = nullptr;
505 // lookup hash number in cache map
506 nsresult rv = mCacheMap.FindRecord(hashNumber, &record);
507 if (NS_FAILED(rv)) return nullptr; // XXX log error?
509 nsDiskCacheEntry * diskEntry = mCacheMap.ReadDiskCacheEntry(&record);
510 if (!diskEntry) return nullptr;
512 // compare key to be sure
513 if (!key->Equals(diskEntry->Key())) {
514 *collision = true;
515 return nullptr;
518 nsCacheEntry * entry = diskEntry->CreateCacheEntry(this);
519 if (entry) {
520 binding = mBindery.CreateBinding(entry, &record);
521 if (!binding) {
522 delete entry;
523 entry = nullptr;
527 if (!entry) {
528 (void) mCacheMap.DeleteStorage(&record);
529 (void) mCacheMap.DeleteRecord(&record);
532 return entry;
537 * NOTE: called while holding the cache service lock
539 nsresult
540 nsDiskCacheDevice::DeactivateEntry(nsCacheEntry * entry)
542 nsresult rv = NS_OK;
543 nsDiskCacheBinding * binding = GetCacheEntryBinding(entry);
544 if (!IsValidBinding(binding))
545 return NS_ERROR_UNEXPECTED;
547 CACHE_LOG_DEBUG(("CACHE: disk DeactivateEntry [%p %x]\n",
548 entry, binding->mRecord.HashNumber()));
550 nsDiskCacheDeviceDeactivateEntryEvent *event =
551 new nsDiskCacheDeviceDeactivateEntryEvent(this, entry, binding);
553 // ensure we can cancel the event via the binding later if necessary
554 binding->mDeactivateEvent = event;
556 rv = nsCacheService::DispatchToCacheIOThread(event);
557 NS_ASSERTION(NS_SUCCEEDED(rv), "DeactivateEntry: Failed dispatching "
558 "deactivation event");
559 return NS_OK;
563 * NOTE: called while holding the cache service lock
565 nsresult
566 nsDiskCacheDevice::DeactivateEntry_Private(nsCacheEntry * entry,
567 nsDiskCacheBinding * binding)
569 nsresult rv = NS_OK;
570 if (entry->IsDoomed()) {
571 // delete data, entry, record from disk for entry
572 rv = mCacheMap.DeleteStorage(&binding->mRecord);
574 } else {
575 // save stuff to disk for entry
576 rv = mCacheMap.WriteDiskCacheEntry(binding);
577 if (NS_FAILED(rv)) {
578 // clean up as best we can
579 (void) mCacheMap.DeleteStorage(&binding->mRecord);
580 (void) mCacheMap.DeleteRecord(&binding->mRecord);
581 binding->mDoomed = true; // record is no longer in cache map
585 mBindery.RemoveBinding(binding); // extract binding from collision detection stuff
586 delete entry; // which will release binding
587 return rv;
592 * BindEntry()
593 * no hash number collision -> no problem
594 * collision
595 * record not active -> evict, no problem
596 * record is active
597 * record is already doomed -> record shouldn't have been in map, no problem
598 * record is not doomed -> doom, and replace record in map
600 * walk matching hashnumber list to find lowest generation number
601 * take generation number from other (data/meta) location,
602 * or walk active list
604 * NOTE: called while holding the cache service lock
606 nsresult
607 nsDiskCacheDevice::BindEntry(nsCacheEntry * entry)
609 if (!Initialized()) return NS_ERROR_NOT_INITIALIZED;
610 if (mClearingDiskCache) return NS_ERROR_NOT_AVAILABLE;
611 nsresult rv = NS_OK;
612 nsDiskCacheRecord record, oldRecord;
613 nsDiskCacheBinding *binding;
614 PLDHashNumber hashNumber = nsDiskCache::Hash(entry->Key()->get());
616 // Find out if there is already an active binding for this hash. If yes it
617 // should have another key since BindEntry() shouldn't be called twice for
618 // the same entry. Doom the old entry, the new one will get another
619 // generation number so files won't collide.
620 binding = mBindery.FindActiveBinding(hashNumber);
621 if (binding) {
622 NS_ASSERTION(!binding->mCacheEntry->Key()->Equals(*entry->Key()),
623 "BindEntry called for already bound entry!");
624 // If the entry is pending deactivation, cancel deactivation
625 if (binding->mDeactivateEvent) {
626 binding->mDeactivateEvent->CancelEvent();
627 binding->mDeactivateEvent = nullptr;
629 nsCacheService::DoomEntry(binding->mCacheEntry);
630 binding = nullptr;
633 // Lookup hash number in cache map. There can be a colliding inactive entry.
634 // See bug #321361 comment 21 for the scenario. If there is such entry,
635 // delete it.
636 rv = mCacheMap.FindRecord(hashNumber, &record);
637 if (NS_SUCCEEDED(rv)) {
638 nsDiskCacheEntry * diskEntry = mCacheMap.ReadDiskCacheEntry(&record);
639 if (diskEntry) {
640 // compare key to be sure
641 if (!entry->Key()->Equals(diskEntry->Key())) {
642 mCacheMap.DeleteStorage(&record);
643 rv = mCacheMap.DeleteRecord(&record);
644 if (NS_FAILED(rv)) return rv;
647 record = nsDiskCacheRecord();
650 // create a new record for this entry
651 record.SetHashNumber(nsDiskCache::Hash(entry->Key()->get()));
652 record.SetEvictionRank(ULONG_MAX - SecondsFromPRTime(PR_Now()));
654 CACHE_LOG_DEBUG(("CACHE: disk BindEntry [%p %x]\n",
655 entry, record.HashNumber()));
657 if (!entry->IsDoomed()) {
658 // if entry isn't doomed, add it to the cache map
659 rv = mCacheMap.AddRecord(&record, &oldRecord); // deletes old record, if any
660 if (NS_FAILED(rv)) return rv;
662 PRUint32 oldHashNumber = oldRecord.HashNumber();
663 if (oldHashNumber) {
664 // gotta evict this one first
665 nsDiskCacheBinding * oldBinding = mBindery.FindActiveBinding(oldHashNumber);
666 if (oldBinding) {
667 // XXX if debug : compare keys for hashNumber collision
669 if (!oldBinding->mCacheEntry->IsDoomed()) {
670 // If the old entry is pending deactivation, cancel deactivation
671 if (oldBinding->mDeactivateEvent) {
672 oldBinding->mDeactivateEvent->CancelEvent();
673 oldBinding->mDeactivateEvent = nullptr;
675 // we've got a live one!
676 nsCacheService::DoomEntry(oldBinding->mCacheEntry);
677 // storage will be delete when oldBinding->mCacheEntry is Deactivated
679 } else {
680 // delete storage
681 // XXX if debug : compare keys for hashNumber collision
682 rv = mCacheMap.DeleteStorage(&oldRecord);
683 if (NS_FAILED(rv)) return rv; // XXX delete record we just added?
688 // Make sure this entry has its associated nsDiskCacheBinding attached.
689 binding = mBindery.CreateBinding(entry, &record);
690 NS_ASSERTION(binding, "nsDiskCacheDevice::BindEntry");
691 if (!binding) return NS_ERROR_OUT_OF_MEMORY;
692 NS_ASSERTION(binding->mRecord.ValidRecord(), "bad cache map record");
694 return NS_OK;
699 * NOTE: called while holding the cache service lock
701 void
702 nsDiskCacheDevice::DoomEntry(nsCacheEntry * entry)
704 CACHE_LOG_DEBUG(("CACHE: disk DoomEntry [%p]\n", entry));
706 nsDiskCacheBinding * binding = GetCacheEntryBinding(entry);
707 NS_ASSERTION(binding, "DoomEntry: binding == nullptr");
708 if (!binding)
709 return;
711 if (!binding->mDoomed) {
712 // so it can't be seen by FindEntry() ever again.
713 #ifdef DEBUG
714 nsresult rv =
715 #endif
716 mCacheMap.DeleteRecord(&binding->mRecord);
717 NS_ASSERTION(NS_SUCCEEDED(rv),"DeleteRecord failed.");
718 binding->mDoomed = true; // record in no longer in cache map
724 * NOTE: called while holding the cache service lock
726 nsresult
727 nsDiskCacheDevice::OpenInputStreamForEntry(nsCacheEntry * entry,
728 nsCacheAccessMode mode,
729 PRUint32 offset,
730 nsIInputStream ** result)
732 CACHE_LOG_DEBUG(("CACHE: disk OpenInputStreamForEntry [%p %x %u]\n",
733 entry, mode, offset));
735 NS_ENSURE_ARG_POINTER(entry);
736 NS_ENSURE_ARG_POINTER(result);
738 nsresult rv;
739 nsDiskCacheBinding * binding = GetCacheEntryBinding(entry);
740 if (!IsValidBinding(binding))
741 return NS_ERROR_UNEXPECTED;
743 NS_ASSERTION(binding->mCacheEntry == entry, "binding & entry don't point to each other");
745 rv = binding->EnsureStreamIO();
746 if (NS_FAILED(rv)) return rv;
748 return binding->mStreamIO->GetInputStream(offset, result);
753 * NOTE: called while holding the cache service lock
755 nsresult
756 nsDiskCacheDevice::OpenOutputStreamForEntry(nsCacheEntry * entry,
757 nsCacheAccessMode mode,
758 PRUint32 offset,
759 nsIOutputStream ** result)
761 CACHE_LOG_DEBUG(("CACHE: disk OpenOutputStreamForEntry [%p %x %u]\n",
762 entry, mode, offset));
764 NS_ENSURE_ARG_POINTER(entry);
765 NS_ENSURE_ARG_POINTER(result);
767 nsresult rv;
768 nsDiskCacheBinding * binding = GetCacheEntryBinding(entry);
769 if (!IsValidBinding(binding))
770 return NS_ERROR_UNEXPECTED;
772 NS_ASSERTION(binding->mCacheEntry == entry, "binding & entry don't point to each other");
774 rv = binding->EnsureStreamIO();
775 if (NS_FAILED(rv)) return rv;
777 return binding->mStreamIO->GetOutputStream(offset, result);
782 * NOTE: called while holding the cache service lock
784 nsresult
785 nsDiskCacheDevice::GetFileForEntry(nsCacheEntry * entry,
786 nsIFile ** result)
788 NS_ENSURE_ARG_POINTER(result);
789 *result = nullptr;
791 nsresult rv;
793 nsDiskCacheBinding * binding = GetCacheEntryBinding(entry);
794 if (!IsValidBinding(binding))
795 return NS_ERROR_UNEXPECTED;
797 // check/set binding->mRecord for separate file, sync w/mCacheMap
798 if (binding->mRecord.DataLocationInitialized()) {
799 if (binding->mRecord.DataFile() != 0)
800 return NS_ERROR_NOT_AVAILABLE; // data not stored as separate file
802 NS_ASSERTION(binding->mRecord.DataFileGeneration() == binding->mGeneration, "error generations out of sync");
803 } else {
804 binding->mRecord.SetDataFileGeneration(binding->mGeneration);
805 binding->mRecord.SetDataFileSize(0); // 1k minimum
806 if (!binding->mDoomed) {
807 // record stored in cache map, so update it
808 rv = mCacheMap.UpdateRecord(&binding->mRecord);
809 if (NS_FAILED(rv)) return rv;
813 nsCOMPtr<nsIFile> file;
814 rv = mCacheMap.GetFileForDiskCacheRecord(&binding->mRecord,
815 nsDiskCache::kData,
816 false,
817 getter_AddRefs(file));
818 if (NS_FAILED(rv)) return rv;
820 NS_IF_ADDREF(*result = file);
821 return NS_OK;
826 * This routine will get called every time an open descriptor is written to.
828 * NOTE: called while holding the cache service lock
830 nsresult
831 nsDiskCacheDevice::OnDataSizeChange(nsCacheEntry * entry, PRInt32 deltaSize)
833 CACHE_LOG_DEBUG(("CACHE: disk OnDataSizeChange [%p %d]\n",
834 entry, deltaSize));
836 // If passed a negative value, then there's nothing to do.
837 if (deltaSize < 0)
838 return NS_OK;
840 nsDiskCacheBinding * binding = GetCacheEntryBinding(entry);
841 if (!IsValidBinding(binding))
842 return NS_ERROR_UNEXPECTED;
844 NS_ASSERTION(binding->mRecord.ValidRecord(), "bad record");
846 PRUint32 newSize = entry->DataSize() + deltaSize;
847 PRUint32 newSizeK = ((newSize + 0x3FF) >> 10);
849 // If the new size is larger than max. file size or larger than
850 // 1/8 the cache capacity (which is in KiB's), and the entry has
851 // not been marked for file storage, doom the entry and abort.
852 if (EntryIsTooBig(newSize) &&
853 entry->StoragePolicy() != nsICache::STORE_ON_DISK_AS_FILE) {
854 #ifdef DEBUG
855 nsresult rv =
856 #endif
857 nsCacheService::DoomEntry(entry);
858 NS_ASSERTION(NS_SUCCEEDED(rv),"DoomEntry() failed.");
859 return NS_ERROR_ABORT;
862 PRUint32 sizeK = ((entry->DataSize() + 0x03FF) >> 10); // round up to next 1k
864 // In total count we ignore anything over kMaxDataSizeK (bug #651100), so
865 // the target capacity should be calculated the same way.
866 if (sizeK > kMaxDataSizeK) sizeK = kMaxDataSizeK;
867 if (newSizeK > kMaxDataSizeK) newSizeK = kMaxDataSizeK;
869 // pre-evict entries to make space for new data
870 PRUint32 targetCapacity = mCacheCapacity > (newSizeK - sizeK)
871 ? mCacheCapacity - (newSizeK - sizeK)
872 : 0;
873 EvictDiskCacheEntries(targetCapacity);
875 return NS_OK;
879 /******************************************************************************
880 * EntryInfoVisitor
881 *****************************************************************************/
882 class EntryInfoVisitor : public nsDiskCacheRecordVisitor
884 public:
885 EntryInfoVisitor(nsDiskCacheMap * cacheMap,
886 nsICacheVisitor * visitor)
887 : mCacheMap(cacheMap)
888 , mVisitor(visitor)
891 virtual PRInt32 VisitRecord(nsDiskCacheRecord * mapRecord)
893 // XXX optimization: do we have this record in memory?
895 // read in the entry (metadata)
896 nsDiskCacheEntry * diskEntry = mCacheMap->ReadDiskCacheEntry(mapRecord);
897 if (!diskEntry) {
898 return kVisitNextRecord;
901 // create nsICacheEntryInfo
902 nsDiskCacheEntryInfo * entryInfo = new nsDiskCacheEntryInfo(DISK_CACHE_DEVICE_ID, diskEntry);
903 if (!entryInfo) {
904 return kStopVisitingRecords;
906 nsCOMPtr<nsICacheEntryInfo> ref(entryInfo);
908 bool keepGoing;
909 (void)mVisitor->VisitEntry(DISK_CACHE_DEVICE_ID, entryInfo, &keepGoing);
910 return keepGoing ? kVisitNextRecord : kStopVisitingRecords;
913 private:
914 nsDiskCacheMap * mCacheMap;
915 nsICacheVisitor * mVisitor;
919 nsresult
920 nsDiskCacheDevice::Visit(nsICacheVisitor * visitor)
922 if (!Initialized()) return NS_ERROR_NOT_INITIALIZED;
923 nsDiskCacheDeviceInfo* deviceInfo = new nsDiskCacheDeviceInfo(this);
924 nsCOMPtr<nsICacheDeviceInfo> ref(deviceInfo);
926 bool keepGoing;
927 nsresult rv = visitor->VisitDevice(DISK_CACHE_DEVICE_ID, deviceInfo, &keepGoing);
928 if (NS_FAILED(rv)) return rv;
930 if (keepGoing) {
931 EntryInfoVisitor infoVisitor(&mCacheMap, visitor);
932 return mCacheMap.VisitRecords(&infoVisitor);
935 return NS_OK;
938 // Max allowed size for an entry is currently MIN(mMaxEntrySize, 1/8 CacheCapacity)
939 bool
940 nsDiskCacheDevice::EntryIsTooBig(PRInt64 entrySize)
942 if (mMaxEntrySize == -1) // no limit
943 return entrySize > (static_cast<PRInt64>(mCacheCapacity) * 1024 / 8);
944 else
945 return entrySize > mMaxEntrySize ||
946 entrySize > (static_cast<PRInt64>(mCacheCapacity) * 1024 / 8);
949 nsresult
950 nsDiskCacheDevice::EvictEntries(const char * clientID)
952 CACHE_LOG_DEBUG(("CACHE: disk EvictEntries [%s]\n", clientID));
954 if (!Initialized()) return NS_ERROR_NOT_INITIALIZED;
955 nsresult rv;
957 if (clientID == nullptr) {
958 // we're clearing the entire disk cache
959 rv = ClearDiskCache();
960 if (rv != NS_ERROR_CACHE_IN_USE)
961 return rv;
964 nsDiskCacheEvictor evictor(&mCacheMap, &mBindery, 0, clientID);
965 rv = mCacheMap.VisitRecords(&evictor);
967 if (clientID == nullptr) // we tried to clear the entire cache
968 rv = mCacheMap.Trim(); // so trim cache block files (if possible)
969 return rv;
974 * private methods
977 nsresult
978 nsDiskCacheDevice::OpenDiskCache()
980 Telemetry::AutoTimer<Telemetry::NETWORK_DISK_CACHE_OPEN> timer;
981 // if we don't have a cache directory, create one and open it
982 bool exists;
983 nsresult rv = mCacheDirectory->Exists(&exists);
984 if (NS_FAILED(rv))
985 return rv;
987 if (exists) {
988 // Try opening cache map file.
989 nsDiskCache::CorruptCacheInfo corruptInfo;
990 rv = mCacheMap.Open(mCacheDirectory, &corruptInfo);
992 if (NS_SUCCEEDED(rv)) {
993 Telemetry::Accumulate(Telemetry::DISK_CACHE_CORRUPT_DETAILS,
994 corruptInfo);
995 } else if (rv == NS_ERROR_ALREADY_INITIALIZED) {
996 NS_WARNING("nsDiskCacheDevice::OpenDiskCache: already open!");
997 } else {
998 // Consider cache corrupt: delete it
999 Telemetry::Accumulate(Telemetry::DISK_CACHE_CORRUPT_DETAILS,
1000 corruptInfo);
1001 // delay delete by 1 minute to avoid IO thrash at startup
1002 rv = nsDeleteDir::DeleteDir(mCacheDirectory, true, 60000);
1003 if (NS_FAILED(rv))
1004 return rv;
1005 exists = false;
1009 // if we don't have a cache directory, create one and open it
1010 if (!exists) {
1011 nsCacheService::MarkStartingFresh();
1012 rv = mCacheDirectory->Create(nsIFile::DIRECTORY_TYPE, 0777);
1013 CACHE_LOG_PATH(PR_LOG_ALWAYS, "\ncreate cache directory: %s\n", mCacheDirectory);
1014 CACHE_LOG_ALWAYS(("mCacheDirectory->Create() = %x\n", rv));
1015 if (NS_FAILED(rv))
1016 return rv;
1018 // reopen the cache map
1019 nsDiskCache::CorruptCacheInfo corruptInfo;
1020 rv = mCacheMap.Open(mCacheDirectory, &corruptInfo);
1021 if (NS_FAILED(rv))
1022 return rv;
1025 return NS_OK;
1029 nsresult
1030 nsDiskCacheDevice::ClearDiskCache()
1032 if (mBindery.ActiveBindings())
1033 return NS_ERROR_CACHE_IN_USE;
1035 mClearingDiskCache = true;
1037 nsresult rv = Shutdown_Private(false); // false: don't bother flushing
1038 if (NS_FAILED(rv))
1039 return rv;
1041 mClearingDiskCache = false;
1043 // If the disk cache directory is already gone, then it's not an error if
1044 // we fail to delete it ;-)
1045 rv = nsDeleteDir::DeleteDir(mCacheDirectory, true);
1046 if (NS_FAILED(rv) && rv != NS_ERROR_FILE_TARGET_DOES_NOT_EXIST)
1047 return rv;
1049 return Init();
1053 nsresult
1054 nsDiskCacheDevice::EvictDiskCacheEntries(PRUint32 targetCapacity)
1056 CACHE_LOG_DEBUG(("CACHE: disk EvictDiskCacheEntries [%u]\n",
1057 targetCapacity));
1059 NS_ASSERTION(targetCapacity > 0, "oops");
1061 if (mCacheMap.TotalSize() < targetCapacity)
1062 return NS_OK;
1064 // targetCapacity is in KiB's
1065 nsDiskCacheEvictor evictor(&mCacheMap, &mBindery, targetCapacity, nullptr);
1066 return mCacheMap.EvictRecords(&evictor);
1071 * methods for prefs
1074 void
1075 nsDiskCacheDevice::SetCacheParentDirectory(nsIFile * parentDir)
1077 nsresult rv;
1078 bool exists;
1080 if (Initialized()) {
1081 NS_ASSERTION(false, "Cannot switch cache directory when initialized");
1082 return;
1085 if (!parentDir) {
1086 mCacheDirectory = nullptr;
1087 return;
1090 // ensure parent directory exists
1091 rv = parentDir->Exists(&exists);
1092 if (NS_SUCCEEDED(rv) && !exists)
1093 rv = parentDir->Create(nsIFile::DIRECTORY_TYPE, 0700);
1094 if (NS_FAILED(rv)) return;
1096 // ensure cache directory exists
1097 nsCOMPtr<nsIFile> directory;
1099 rv = parentDir->Clone(getter_AddRefs(directory));
1100 if (NS_FAILED(rv)) return;
1101 rv = directory->AppendNative(NS_LITERAL_CSTRING("Cache"));
1102 if (NS_FAILED(rv)) return;
1104 mCacheDirectory = do_QueryInterface(directory);
1108 void
1109 nsDiskCacheDevice::getCacheDirectory(nsIFile ** result)
1111 *result = mCacheDirectory;
1112 NS_IF_ADDREF(*result);
1117 * NOTE: called while holding the cache service lock
1119 void
1120 nsDiskCacheDevice::SetCapacity(PRUint32 capacity)
1122 // Units are KiB's
1123 mCacheCapacity = capacity;
1124 if (Initialized()) {
1125 if (NS_IsMainThread()) {
1126 // Do not evict entries on the main thread
1127 nsCacheService::DispatchToCacheIOThread(
1128 new nsEvictDiskCacheEntriesEvent(this));
1129 } else {
1130 // start evicting entries if the new size is smaller!
1131 EvictDiskCacheEntries(mCacheCapacity);
1134 // Let cache map know of the new capacity
1135 mCacheMap.NotifyCapacityChange(capacity);
1139 PRUint32 nsDiskCacheDevice::getCacheCapacity()
1141 return mCacheCapacity;
1145 PRUint32 nsDiskCacheDevice::getCacheSize()
1147 return mCacheMap.TotalSize();
1151 PRUint32 nsDiskCacheDevice::getEntryCount()
1153 return mCacheMap.EntryCount();
1156 void
1157 nsDiskCacheDevice::SetMaxEntrySize(PRInt32 maxSizeInKilobytes)
1159 // Internal units are bytes. Changing this only takes effect *after* the
1160 // change and has no consequences for existing cache-entries
1161 if (maxSizeInKilobytes >= 0)
1162 mMaxEntrySize = maxSizeInKilobytes * 1024;
1163 else
1164 mMaxEntrySize = -1;