Bumping manifests a=b2g-bump
[gecko.git] / netwerk / cache / nsDiskCacheDevice.cpp
blob771414107f9fe82de780613f6e9c5acfc9abd4fd
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 "mozilla/DebugOnly.h"
11 #include "nsCache.h"
12 #include "nsIMemoryReporter.h"
14 // include files for ftruncate (or equivalent)
15 #if defined(XP_UNIX)
16 #include <unistd.h>
17 #elif defined(XP_WIN)
18 #include <windows.h>
19 #else
20 // XXX add necessary include file for ftruncate (or equivalent)
21 #endif
23 #include "prthread.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"
36 #include "nsDeleteDir.h"
38 #include "nsICacheVisitor.h"
39 #include "nsReadableUtils.h"
40 #include "nsIInputStream.h"
41 #include "nsIOutputStream.h"
42 #include "nsCRT.h"
43 #include "nsCOMArray.h"
44 #include "nsISimpleEnumerator.h"
46 #include "nsThreadUtils.h"
47 #include "mozilla/MemoryReporting.h"
48 #include "mozilla/Telemetry.h"
50 static const char DISK_CACHE_DEVICE_ID[] = { "disk" };
51 using namespace mozilla;
53 class nsDiskCacheDeviceDeactivateEntryEvent : public nsRunnable {
54 public:
55 nsDiskCacheDeviceDeactivateEntryEvent(nsDiskCacheDevice *device,
56 nsCacheEntry * entry,
57 nsDiskCacheBinding * binding)
58 : mCanceled(false),
59 mEntry(entry),
60 mDevice(device),
61 mBinding(binding)
65 NS_IMETHOD Run()
67 nsCacheServiceAutoLock lock(LOCK_TELEM(NSDISKCACHEDEVICEDEACTIVATEENTRYEVENT_RUN));
68 #ifdef PR_LOGGING
69 CACHE_LOG_DEBUG(("nsDiskCacheDeviceDeactivateEntryEvent[%p]\n", this));
70 #endif
71 if (!mCanceled) {
72 (void) mDevice->DeactivateEntry_Private(mEntry, mBinding);
74 return NS_OK;
77 void CancelEvent() { mCanceled = true; }
78 private:
79 bool mCanceled;
80 nsCacheEntry *mEntry;
81 nsDiskCacheDevice *mDevice;
82 nsDiskCacheBinding *mBinding;
85 class nsEvictDiskCacheEntriesEvent : public nsRunnable {
86 public:
87 explicit nsEvictDiskCacheEntriesEvent(nsDiskCacheDevice *device)
88 : mDevice(device) {}
90 NS_IMETHOD Run()
92 nsCacheServiceAutoLock lock(LOCK_TELEM(NSEVICTDISKCACHEENTRIESEVENT_RUN));
93 mDevice->EvictDiskCacheEntries(mDevice->mCacheCapacity);
94 return NS_OK;
97 private:
98 nsDiskCacheDevice *mDevice;
101 /******************************************************************************
102 * nsDiskCacheEvictor
104 * Helper class for nsDiskCacheDevice.
106 *****************************************************************************/
108 class nsDiskCacheEvictor : public nsDiskCacheRecordVisitor
110 public:
111 nsDiskCacheEvictor( nsDiskCacheMap * cacheMap,
112 nsDiskCacheBindery * cacheBindery,
113 uint32_t targetSize,
114 const char * clientID)
115 : mCacheMap(cacheMap)
116 , mBindery(cacheBindery)
117 , mTargetSize(targetSize)
118 , mClientID(clientID)
120 mClientIDSize = clientID ? strlen(clientID) : 0;
123 virtual int32_t VisitRecord(nsDiskCacheRecord * mapRecord);
125 private:
126 nsDiskCacheMap * mCacheMap;
127 nsDiskCacheBindery * mBindery;
128 uint32_t mTargetSize;
129 const char * mClientID;
130 uint32_t mClientIDSize;
134 int32_t
135 nsDiskCacheEvictor::VisitRecord(nsDiskCacheRecord * mapRecord)
137 if (mCacheMap->TotalSize() < mTargetSize)
138 return kStopVisitingRecords;
140 if (mClientID) {
141 // we're just evicting records for a specific client
142 nsDiskCacheEntry * diskEntry = mCacheMap->ReadDiskCacheEntry(mapRecord);
143 if (!diskEntry)
144 return kVisitNextRecord; // XXX or delete record?
146 // Compare clientID's without malloc
147 if ((diskEntry->mKeySize <= mClientIDSize) ||
148 (diskEntry->Key()[mClientIDSize] != ':') ||
149 (memcmp(diskEntry->Key(), mClientID, mClientIDSize) != 0)) {
150 return kVisitNextRecord; // clientID doesn't match, skip it
154 nsDiskCacheBinding * binding = mBindery->FindActiveBinding(mapRecord->HashNumber());
155 if (binding) {
156 // If the entry is pending deactivation, cancel deactivation and doom
157 // the entry
158 if (binding->mDeactivateEvent) {
159 binding->mDeactivateEvent->CancelEvent();
160 binding->mDeactivateEvent = nullptr;
162 // We are currently using this entry, so all we can do is doom it.
163 // Since we're enumerating the records, we don't want to call
164 // DeleteRecord when nsCacheService::DoomEntry() calls us back.
165 binding->mDoomed = true; // mark binding record as 'deleted'
166 nsCacheService::DoomEntry(binding->mCacheEntry);
167 } else {
168 // entry not in use, just delete storage because we're enumerating the records
169 (void) mCacheMap->DeleteStorage(mapRecord);
172 return kDeleteRecordAndContinue; // this will REALLY delete the record
176 /******************************************************************************
177 * nsDiskCacheDeviceInfo
178 *****************************************************************************/
180 class nsDiskCacheDeviceInfo : public nsICacheDeviceInfo {
181 public:
182 NS_DECL_ISUPPORTS
183 NS_DECL_NSICACHEDEVICEINFO
185 explicit nsDiskCacheDeviceInfo(nsDiskCacheDevice* device)
186 : mDevice(device)
190 private:
191 virtual ~nsDiskCacheDeviceInfo() {}
193 nsDiskCacheDevice* mDevice;
196 NS_IMPL_ISUPPORTS(nsDiskCacheDeviceInfo, nsICacheDeviceInfo)
198 /* readonly attribute string description; */
199 NS_IMETHODIMP nsDiskCacheDeviceInfo::GetDescription(char ** aDescription)
201 NS_ENSURE_ARG_POINTER(aDescription);
202 *aDescription = NS_strdup("Disk cache device");
203 return *aDescription ? NS_OK : NS_ERROR_OUT_OF_MEMORY;
206 /* readonly attribute string usageReport; */
207 NS_IMETHODIMP nsDiskCacheDeviceInfo::GetUsageReport(char ** usageReport)
209 NS_ENSURE_ARG_POINTER(usageReport);
210 nsCString buffer;
212 buffer.AssignLiteral(" <tr>\n"
213 " <th>Cache Directory:</th>\n"
214 " <td>");
215 nsCOMPtr<nsIFile> cacheDir;
216 nsAutoString path;
217 mDevice->getCacheDirectory(getter_AddRefs(cacheDir));
218 nsresult rv = cacheDir->GetPath(path);
219 if (NS_SUCCEEDED(rv)) {
220 AppendUTF16toUTF8(path, buffer);
221 } else {
222 buffer.AppendLiteral("directory unavailable");
224 buffer.AppendLiteral("</td>\n"
225 " </tr>\n");
227 *usageReport = ToNewCString(buffer);
228 if (!*usageReport) return NS_ERROR_OUT_OF_MEMORY;
230 return NS_OK;
233 /* readonly attribute unsigned long entryCount; */
234 NS_IMETHODIMP nsDiskCacheDeviceInfo::GetEntryCount(uint32_t *aEntryCount)
236 NS_ENSURE_ARG_POINTER(aEntryCount);
237 *aEntryCount = mDevice->getEntryCount();
238 return NS_OK;
241 /* readonly attribute unsigned long totalSize; */
242 NS_IMETHODIMP nsDiskCacheDeviceInfo::GetTotalSize(uint32_t *aTotalSize)
244 NS_ENSURE_ARG_POINTER(aTotalSize);
245 // Returned unit's are in bytes
246 *aTotalSize = mDevice->getCacheSize() * 1024;
247 return NS_OK;
250 /* readonly attribute unsigned long maximumSize; */
251 NS_IMETHODIMP nsDiskCacheDeviceInfo::GetMaximumSize(uint32_t *aMaximumSize)
253 NS_ENSURE_ARG_POINTER(aMaximumSize);
254 // Returned unit's are in bytes
255 *aMaximumSize = mDevice->getCacheCapacity() * 1024;
256 return NS_OK;
260 /******************************************************************************
261 * nsDiskCache
262 *****************************************************************************/
265 * nsDiskCache::Hash(const char * key, PLDHashNumber initval)
267 * See http://burtleburtle.net/bob/hash/evahash.html for more information
268 * about this hash function.
270 * This algorithm of this method implies nsDiskCacheRecords will be stored
271 * in a certain order on disk. If the algorithm changes, existing cache
272 * map files may become invalid, and therefore the kCurrentVersion needs
273 * to be revised.
276 static inline void hashmix(uint32_t& a, uint32_t& b, uint32_t& c)
278 a -= b; a -= c; a ^= (c>>13);
279 b -= c; b -= a; b ^= (a<<8);
280 c -= a; c -= b; c ^= (b>>13);
281 a -= b; a -= c; a ^= (c>>12);
282 b -= c; b -= a; b ^= (a<<16);
283 c -= a; c -= b; c ^= (b>>5);
284 a -= b; a -= c; a ^= (c>>3);
285 b -= c; b -= a; b ^= (a<<10);
286 c -= a; c -= b; c ^= (b>>15);
289 PLDHashNumber
290 nsDiskCache::Hash(const char * key, PLDHashNumber initval)
292 const uint8_t *k = reinterpret_cast<const uint8_t*>(key);
293 uint32_t a, b, c, len, length;
295 length = strlen(key);
296 /* Set up the internal state */
297 len = length;
298 a = b = 0x9e3779b9; /* the golden ratio; an arbitrary value */
299 c = initval; /* variable initialization of internal state */
301 /*---------------------------------------- handle most of the key */
302 while (len >= 12)
304 a += k[0] + (uint32_t(k[1])<<8) + (uint32_t(k[2])<<16) + (uint32_t(k[3])<<24);
305 b += k[4] + (uint32_t(k[5])<<8) + (uint32_t(k[6])<<16) + (uint32_t(k[7])<<24);
306 c += k[8] + (uint32_t(k[9])<<8) + (uint32_t(k[10])<<16) + (uint32_t(k[11])<<24);
307 hashmix(a, b, c);
308 k += 12; len -= 12;
311 /*------------------------------------- handle the last 11 bytes */
312 c += length;
313 switch(len) { /* all the case statements fall through */
314 case 11: c += (uint32_t(k[10])<<24);
315 case 10: c += (uint32_t(k[9])<<16);
316 case 9 : c += (uint32_t(k[8])<<8);
317 /* the low-order byte of c is reserved for the length */
318 case 8 : b += (uint32_t(k[7])<<24);
319 case 7 : b += (uint32_t(k[6])<<16);
320 case 6 : b += (uint32_t(k[5])<<8);
321 case 5 : b += k[4];
322 case 4 : a += (uint32_t(k[3])<<24);
323 case 3 : a += (uint32_t(k[2])<<16);
324 case 2 : a += (uint32_t(k[1])<<8);
325 case 1 : a += k[0];
326 /* case 0: nothing left to add */
328 hashmix(a, b, c);
330 return c;
333 nsresult
334 nsDiskCache::Truncate(PRFileDesc * fd, uint32_t newEOF)
336 // use modified SetEOF from nsFileStreams::SetEOF()
338 #if defined(XP_UNIX)
339 if (ftruncate(PR_FileDesc2NativeHandle(fd), newEOF) != 0) {
340 NS_ERROR("ftruncate failed");
341 return NS_ERROR_FAILURE;
344 #elif defined(XP_WIN)
345 int32_t cnt = PR_Seek(fd, newEOF, PR_SEEK_SET);
346 if (cnt == -1) return NS_ERROR_FAILURE;
347 if (!SetEndOfFile((HANDLE) PR_FileDesc2NativeHandle(fd))) {
348 NS_ERROR("SetEndOfFile failed");
349 return NS_ERROR_FAILURE;
352 #else
353 // add implementations for other platforms here
354 #endif
355 return NS_OK;
359 /******************************************************************************
360 * nsDiskCacheDevice
361 *****************************************************************************/
363 nsDiskCacheDevice::nsDiskCacheDevice()
364 : mCacheCapacity(0)
365 , mMaxEntrySize(-1) // -1 means "no limit"
366 , mInitialized(false)
367 , mClearingDiskCache(false)
371 nsDiskCacheDevice::~nsDiskCacheDevice()
373 Shutdown();
378 * methods of nsCacheDevice
380 nsresult
381 nsDiskCacheDevice::Init()
383 nsresult rv;
385 if (Initialized()) {
386 NS_ERROR("Disk cache already initialized!");
387 return NS_ERROR_UNEXPECTED;
390 if (!mCacheDirectory)
391 return NS_ERROR_FAILURE;
393 rv = mBindery.Init();
394 if (NS_FAILED(rv))
395 return rv;
397 // Open Disk Cache
398 rv = OpenDiskCache();
399 if (NS_FAILED(rv)) {
400 (void) mCacheMap.Close(false);
401 return rv;
404 mInitialized = true;
405 return NS_OK;
410 * NOTE: called while holding the cache service lock
412 nsresult
413 nsDiskCacheDevice::Shutdown()
415 nsCacheService::AssertOwnsLock();
417 nsresult rv = Shutdown_Private(true);
418 if (NS_FAILED(rv))
419 return rv;
421 return NS_OK;
425 nsresult
426 nsDiskCacheDevice::Shutdown_Private(bool flush)
428 CACHE_LOG_DEBUG(("CACHE: disk Shutdown_Private [%u]\n", flush));
430 if (Initialized()) {
431 // check cache limits in case we need to evict.
432 EvictDiskCacheEntries(mCacheCapacity);
434 // At this point there may be a number of pending cache-requests on the
435 // cache-io thread. Wait for all these to run before we wipe out our
436 // datastructures (see bug #620660)
437 (void) nsCacheService::SyncWithCacheIOThread();
439 // write out persistent information about the cache.
440 (void) mCacheMap.Close(flush);
442 mBindery.Reset();
444 mInitialized = false;
447 return NS_OK;
451 const char *
452 nsDiskCacheDevice::GetDeviceID()
454 return DISK_CACHE_DEVICE_ID;
458 * FindEntry -
460 * cases: key not in disk cache, hash number free
461 * key not in disk cache, hash number used
462 * key in disk cache
464 * NOTE: called while holding the cache service lock
466 nsCacheEntry *
467 nsDiskCacheDevice::FindEntry(nsCString * key, bool *collision)
469 Telemetry::AutoTimer<Telemetry::CACHE_DISK_SEARCH_2> timer;
470 if (!Initialized()) return nullptr; // NS_ERROR_NOT_INITIALIZED
471 if (mClearingDiskCache) return nullptr;
472 nsDiskCacheRecord record;
473 nsDiskCacheBinding * binding = nullptr;
474 PLDHashNumber hashNumber = nsDiskCache::Hash(key->get());
476 *collision = false;
478 binding = mBindery.FindActiveBinding(hashNumber);
479 if (binding && !binding->mCacheEntry->Key()->Equals(*key)) {
480 *collision = true;
481 return nullptr;
482 } else if (binding && binding->mDeactivateEvent) {
483 binding->mDeactivateEvent->CancelEvent();
484 binding->mDeactivateEvent = nullptr;
485 CACHE_LOG_DEBUG(("CACHE: reusing deactivated entry %p " \
486 "req-key=%s entry-key=%s\n",
487 binding->mCacheEntry, key, binding->mCacheEntry->Key()));
489 return binding->mCacheEntry; // just return this one, observing that
490 // FindActiveBinding() does not return
491 // bindings to doomed entries
493 binding = nullptr;
495 // lookup hash number in cache map
496 nsresult rv = mCacheMap.FindRecord(hashNumber, &record);
497 if (NS_FAILED(rv)) return nullptr; // XXX log error?
499 nsDiskCacheEntry * diskEntry = mCacheMap.ReadDiskCacheEntry(&record);
500 if (!diskEntry) return nullptr;
502 // compare key to be sure
503 if (!key->Equals(diskEntry->Key())) {
504 *collision = true;
505 return nullptr;
508 nsCacheEntry * entry = diskEntry->CreateCacheEntry(this);
509 if (entry) {
510 binding = mBindery.CreateBinding(entry, &record);
511 if (!binding) {
512 delete entry;
513 entry = nullptr;
517 if (!entry) {
518 (void) mCacheMap.DeleteStorage(&record);
519 (void) mCacheMap.DeleteRecord(&record);
522 return entry;
527 * NOTE: called while holding the cache service lock
529 nsresult
530 nsDiskCacheDevice::DeactivateEntry(nsCacheEntry * entry)
532 nsDiskCacheBinding * binding = GetCacheEntryBinding(entry);
533 if (!IsValidBinding(binding))
534 return NS_ERROR_UNEXPECTED;
536 CACHE_LOG_DEBUG(("CACHE: disk DeactivateEntry [%p %x]\n",
537 entry, binding->mRecord.HashNumber()));
539 nsDiskCacheDeviceDeactivateEntryEvent *event =
540 new nsDiskCacheDeviceDeactivateEntryEvent(this, entry, binding);
542 // ensure we can cancel the event via the binding later if necessary
543 binding->mDeactivateEvent = event;
545 DebugOnly<nsresult> rv = nsCacheService::DispatchToCacheIOThread(event);
546 NS_ASSERTION(NS_SUCCEEDED(rv), "DeactivateEntry: Failed dispatching "
547 "deactivation event");
548 return NS_OK;
552 * NOTE: called while holding the cache service lock
554 nsresult
555 nsDiskCacheDevice::DeactivateEntry_Private(nsCacheEntry * entry,
556 nsDiskCacheBinding * binding)
558 nsresult rv = NS_OK;
559 if (entry->IsDoomed()) {
560 // delete data, entry, record from disk for entry
561 rv = mCacheMap.DeleteStorage(&binding->mRecord);
563 } else {
564 // save stuff to disk for entry
565 rv = mCacheMap.WriteDiskCacheEntry(binding);
566 if (NS_FAILED(rv)) {
567 // clean up as best we can
568 (void) mCacheMap.DeleteStorage(&binding->mRecord);
569 (void) mCacheMap.DeleteRecord(&binding->mRecord);
570 binding->mDoomed = true; // record is no longer in cache map
574 mBindery.RemoveBinding(binding); // extract binding from collision detection stuff
575 delete entry; // which will release binding
576 return rv;
581 * BindEntry()
582 * no hash number collision -> no problem
583 * collision
584 * record not active -> evict, no problem
585 * record is active
586 * record is already doomed -> record shouldn't have been in map, no problem
587 * record is not doomed -> doom, and replace record in map
589 * walk matching hashnumber list to find lowest generation number
590 * take generation number from other (data/meta) location,
591 * or walk active list
593 * NOTE: called while holding the cache service lock
595 nsresult
596 nsDiskCacheDevice::BindEntry(nsCacheEntry * entry)
598 if (!Initialized()) return NS_ERROR_NOT_INITIALIZED;
599 if (mClearingDiskCache) return NS_ERROR_NOT_AVAILABLE;
600 nsresult rv = NS_OK;
601 nsDiskCacheRecord record, oldRecord;
602 nsDiskCacheBinding *binding;
603 PLDHashNumber hashNumber = nsDiskCache::Hash(entry->Key()->get());
605 // Find out if there is already an active binding for this hash. If yes it
606 // should have another key since BindEntry() shouldn't be called twice for
607 // the same entry. Doom the old entry, the new one will get another
608 // generation number so files won't collide.
609 binding = mBindery.FindActiveBinding(hashNumber);
610 if (binding) {
611 NS_ASSERTION(!binding->mCacheEntry->Key()->Equals(*entry->Key()),
612 "BindEntry called for already bound entry!");
613 // If the entry is pending deactivation, cancel deactivation
614 if (binding->mDeactivateEvent) {
615 binding->mDeactivateEvent->CancelEvent();
616 binding->mDeactivateEvent = nullptr;
618 nsCacheService::DoomEntry(binding->mCacheEntry);
619 binding = nullptr;
622 // Lookup hash number in cache map. There can be a colliding inactive entry.
623 // See bug #321361 comment 21 for the scenario. If there is such entry,
624 // delete it.
625 rv = mCacheMap.FindRecord(hashNumber, &record);
626 if (NS_SUCCEEDED(rv)) {
627 nsDiskCacheEntry * diskEntry = mCacheMap.ReadDiskCacheEntry(&record);
628 if (diskEntry) {
629 // compare key to be sure
630 if (!entry->Key()->Equals(diskEntry->Key())) {
631 mCacheMap.DeleteStorage(&record);
632 rv = mCacheMap.DeleteRecord(&record);
633 if (NS_FAILED(rv)) return rv;
636 record = nsDiskCacheRecord();
639 // create a new record for this entry
640 record.SetHashNumber(nsDiskCache::Hash(entry->Key()->get()));
641 record.SetEvictionRank(ULONG_MAX - SecondsFromPRTime(PR_Now()));
643 CACHE_LOG_DEBUG(("CACHE: disk BindEntry [%p %x]\n",
644 entry, record.HashNumber()));
646 if (!entry->IsDoomed()) {
647 // if entry isn't doomed, add it to the cache map
648 rv = mCacheMap.AddRecord(&record, &oldRecord); // deletes old record, if any
649 if (NS_FAILED(rv)) return rv;
651 uint32_t oldHashNumber = oldRecord.HashNumber();
652 if (oldHashNumber) {
653 // gotta evict this one first
654 nsDiskCacheBinding * oldBinding = mBindery.FindActiveBinding(oldHashNumber);
655 if (oldBinding) {
656 // XXX if debug : compare keys for hashNumber collision
658 if (!oldBinding->mCacheEntry->IsDoomed()) {
659 // If the old entry is pending deactivation, cancel deactivation
660 if (oldBinding->mDeactivateEvent) {
661 oldBinding->mDeactivateEvent->CancelEvent();
662 oldBinding->mDeactivateEvent = nullptr;
664 // we've got a live one!
665 nsCacheService::DoomEntry(oldBinding->mCacheEntry);
666 // storage will be delete when oldBinding->mCacheEntry is Deactivated
668 } else {
669 // delete storage
670 // XXX if debug : compare keys for hashNumber collision
671 rv = mCacheMap.DeleteStorage(&oldRecord);
672 if (NS_FAILED(rv)) return rv; // XXX delete record we just added?
677 // Make sure this entry has its associated nsDiskCacheBinding attached.
678 binding = mBindery.CreateBinding(entry, &record);
679 NS_ASSERTION(binding, "nsDiskCacheDevice::BindEntry");
680 if (!binding) return NS_ERROR_OUT_OF_MEMORY;
681 NS_ASSERTION(binding->mRecord.ValidRecord(), "bad cache map record");
683 return NS_OK;
688 * NOTE: called while holding the cache service lock
690 void
691 nsDiskCacheDevice::DoomEntry(nsCacheEntry * entry)
693 CACHE_LOG_DEBUG(("CACHE: disk DoomEntry [%p]\n", entry));
695 nsDiskCacheBinding * binding = GetCacheEntryBinding(entry);
696 NS_ASSERTION(binding, "DoomEntry: binding == nullptr");
697 if (!binding)
698 return;
700 if (!binding->mDoomed) {
701 // so it can't be seen by FindEntry() ever again.
702 #ifdef DEBUG
703 nsresult rv =
704 #endif
705 mCacheMap.DeleteRecord(&binding->mRecord);
706 NS_ASSERTION(NS_SUCCEEDED(rv),"DeleteRecord failed.");
707 binding->mDoomed = true; // record in no longer in cache map
713 * NOTE: called while holding the cache service lock
715 nsresult
716 nsDiskCacheDevice::OpenInputStreamForEntry(nsCacheEntry * entry,
717 nsCacheAccessMode mode,
718 uint32_t offset,
719 nsIInputStream ** result)
721 CACHE_LOG_DEBUG(("CACHE: disk OpenInputStreamForEntry [%p %x %u]\n",
722 entry, mode, offset));
724 NS_ENSURE_ARG_POINTER(entry);
725 NS_ENSURE_ARG_POINTER(result);
727 nsresult rv;
728 nsDiskCacheBinding * binding = GetCacheEntryBinding(entry);
729 if (!IsValidBinding(binding))
730 return NS_ERROR_UNEXPECTED;
732 NS_ASSERTION(binding->mCacheEntry == entry, "binding & entry don't point to each other");
734 rv = binding->EnsureStreamIO();
735 if (NS_FAILED(rv)) return rv;
737 return binding->mStreamIO->GetInputStream(offset, result);
742 * NOTE: called while holding the cache service lock
744 nsresult
745 nsDiskCacheDevice::OpenOutputStreamForEntry(nsCacheEntry * entry,
746 nsCacheAccessMode mode,
747 uint32_t offset,
748 nsIOutputStream ** result)
750 CACHE_LOG_DEBUG(("CACHE: disk OpenOutputStreamForEntry [%p %x %u]\n",
751 entry, mode, offset));
753 NS_ENSURE_ARG_POINTER(entry);
754 NS_ENSURE_ARG_POINTER(result);
756 nsresult rv;
757 nsDiskCacheBinding * binding = GetCacheEntryBinding(entry);
758 if (!IsValidBinding(binding))
759 return NS_ERROR_UNEXPECTED;
761 NS_ASSERTION(binding->mCacheEntry == entry, "binding & entry don't point to each other");
763 rv = binding->EnsureStreamIO();
764 if (NS_FAILED(rv)) return rv;
766 return binding->mStreamIO->GetOutputStream(offset, result);
771 * NOTE: called while holding the cache service lock
773 nsresult
774 nsDiskCacheDevice::GetFileForEntry(nsCacheEntry * entry,
775 nsIFile ** result)
777 NS_ENSURE_ARG_POINTER(result);
778 *result = nullptr;
780 nsresult rv;
782 nsDiskCacheBinding * binding = GetCacheEntryBinding(entry);
783 if (!IsValidBinding(binding))
784 return NS_ERROR_UNEXPECTED;
786 // check/set binding->mRecord for separate file, sync w/mCacheMap
787 if (binding->mRecord.DataLocationInitialized()) {
788 if (binding->mRecord.DataFile() != 0)
789 return NS_ERROR_NOT_AVAILABLE; // data not stored as separate file
791 NS_ASSERTION(binding->mRecord.DataFileGeneration() == binding->mGeneration, "error generations out of sync");
792 } else {
793 binding->mRecord.SetDataFileGeneration(binding->mGeneration);
794 binding->mRecord.SetDataFileSize(0); // 1k minimum
795 if (!binding->mDoomed) {
796 // record stored in cache map, so update it
797 rv = mCacheMap.UpdateRecord(&binding->mRecord);
798 if (NS_FAILED(rv)) return rv;
802 nsCOMPtr<nsIFile> file;
803 rv = mCacheMap.GetFileForDiskCacheRecord(&binding->mRecord,
804 nsDiskCache::kData,
805 false,
806 getter_AddRefs(file));
807 if (NS_FAILED(rv)) return rv;
809 NS_IF_ADDREF(*result = file);
810 return NS_OK;
815 * This routine will get called every time an open descriptor is written to.
817 * NOTE: called while holding the cache service lock
819 nsresult
820 nsDiskCacheDevice::OnDataSizeChange(nsCacheEntry * entry, int32_t deltaSize)
822 CACHE_LOG_DEBUG(("CACHE: disk OnDataSizeChange [%p %d]\n",
823 entry, deltaSize));
825 // If passed a negative value, then there's nothing to do.
826 if (deltaSize < 0)
827 return NS_OK;
829 nsDiskCacheBinding * binding = GetCacheEntryBinding(entry);
830 if (!IsValidBinding(binding))
831 return NS_ERROR_UNEXPECTED;
833 NS_ASSERTION(binding->mRecord.ValidRecord(), "bad record");
835 uint32_t newSize = entry->DataSize() + deltaSize;
836 uint32_t newSizeK = ((newSize + 0x3FF) >> 10);
838 // If the new size is larger than max. file size or larger than
839 // 1/8 the cache capacity (which is in KiB's), doom the entry and abort.
840 if (EntryIsTooBig(newSize)) {
841 #ifdef DEBUG
842 nsresult rv =
843 #endif
844 nsCacheService::DoomEntry(entry);
845 NS_ASSERTION(NS_SUCCEEDED(rv),"DoomEntry() failed.");
846 return NS_ERROR_ABORT;
849 uint32_t sizeK = ((entry->DataSize() + 0x03FF) >> 10); // round up to next 1k
851 // In total count we ignore anything over kMaxDataSizeK (bug #651100), so
852 // the target capacity should be calculated the same way.
853 if (sizeK > kMaxDataSizeK) sizeK = kMaxDataSizeK;
854 if (newSizeK > kMaxDataSizeK) newSizeK = kMaxDataSizeK;
856 // pre-evict entries to make space for new data
857 uint32_t targetCapacity = mCacheCapacity > (newSizeK - sizeK)
858 ? mCacheCapacity - (newSizeK - sizeK)
859 : 0;
860 EvictDiskCacheEntries(targetCapacity);
862 return NS_OK;
866 /******************************************************************************
867 * EntryInfoVisitor
868 *****************************************************************************/
869 class EntryInfoVisitor : public nsDiskCacheRecordVisitor
871 public:
872 EntryInfoVisitor(nsDiskCacheMap * cacheMap,
873 nsICacheVisitor * visitor)
874 : mCacheMap(cacheMap)
875 , mVisitor(visitor)
878 virtual int32_t VisitRecord(nsDiskCacheRecord * mapRecord)
880 // XXX optimization: do we have this record in memory?
882 // read in the entry (metadata)
883 nsDiskCacheEntry * diskEntry = mCacheMap->ReadDiskCacheEntry(mapRecord);
884 if (!diskEntry) {
885 return kVisitNextRecord;
888 // create nsICacheEntryInfo
889 nsDiskCacheEntryInfo * entryInfo = new nsDiskCacheEntryInfo(DISK_CACHE_DEVICE_ID, diskEntry);
890 if (!entryInfo) {
891 return kStopVisitingRecords;
893 nsCOMPtr<nsICacheEntryInfo> ref(entryInfo);
895 bool keepGoing;
896 (void)mVisitor->VisitEntry(DISK_CACHE_DEVICE_ID, entryInfo, &keepGoing);
897 return keepGoing ? kVisitNextRecord : kStopVisitingRecords;
900 private:
901 nsDiskCacheMap * mCacheMap;
902 nsICacheVisitor * mVisitor;
906 nsresult
907 nsDiskCacheDevice::Visit(nsICacheVisitor * visitor)
909 if (!Initialized()) return NS_ERROR_NOT_INITIALIZED;
910 nsDiskCacheDeviceInfo* deviceInfo = new nsDiskCacheDeviceInfo(this);
911 nsCOMPtr<nsICacheDeviceInfo> ref(deviceInfo);
913 bool keepGoing;
914 nsresult rv = visitor->VisitDevice(DISK_CACHE_DEVICE_ID, deviceInfo, &keepGoing);
915 if (NS_FAILED(rv)) return rv;
917 if (keepGoing) {
918 EntryInfoVisitor infoVisitor(&mCacheMap, visitor);
919 return mCacheMap.VisitRecords(&infoVisitor);
922 return NS_OK;
925 // Max allowed size for an entry is currently MIN(mMaxEntrySize, 1/8 CacheCapacity)
926 bool
927 nsDiskCacheDevice::EntryIsTooBig(int64_t entrySize)
929 if (mMaxEntrySize == -1) // no limit
930 return entrySize > (static_cast<int64_t>(mCacheCapacity) * 1024 / 8);
931 else
932 return entrySize > mMaxEntrySize ||
933 entrySize > (static_cast<int64_t>(mCacheCapacity) * 1024 / 8);
936 nsresult
937 nsDiskCacheDevice::EvictEntries(const char * clientID)
939 CACHE_LOG_DEBUG(("CACHE: disk EvictEntries [%s]\n", clientID));
941 if (!Initialized()) return NS_ERROR_NOT_INITIALIZED;
942 nsresult rv;
944 if (clientID == nullptr) {
945 // we're clearing the entire disk cache
946 rv = ClearDiskCache();
947 if (rv != NS_ERROR_CACHE_IN_USE)
948 return rv;
951 nsDiskCacheEvictor evictor(&mCacheMap, &mBindery, 0, clientID);
952 rv = mCacheMap.VisitRecords(&evictor);
954 if (clientID == nullptr) // we tried to clear the entire cache
955 rv = mCacheMap.Trim(); // so trim cache block files (if possible)
956 return rv;
961 * private methods
964 nsresult
965 nsDiskCacheDevice::OpenDiskCache()
967 Telemetry::AutoTimer<Telemetry::NETWORK_DISK_CACHE_OPEN> timer;
968 // if we don't have a cache directory, create one and open it
969 bool exists;
970 nsresult rv = mCacheDirectory->Exists(&exists);
971 if (NS_FAILED(rv))
972 return rv;
974 if (exists) {
975 // Try opening cache map file.
976 nsDiskCache::CorruptCacheInfo corruptInfo;
977 rv = mCacheMap.Open(mCacheDirectory, &corruptInfo, true);
979 if (NS_SUCCEEDED(rv)) {
980 Telemetry::Accumulate(Telemetry::DISK_CACHE_CORRUPT_DETAILS,
981 corruptInfo);
982 } else if (rv == NS_ERROR_ALREADY_INITIALIZED) {
983 NS_WARNING("nsDiskCacheDevice::OpenDiskCache: already open!");
984 } else {
985 // Consider cache corrupt: delete it
986 Telemetry::Accumulate(Telemetry::DISK_CACHE_CORRUPT_DETAILS,
987 corruptInfo);
988 // delay delete by 1 minute to avoid IO thrash at startup
989 rv = nsDeleteDir::DeleteDir(mCacheDirectory, true, 60000);
990 if (NS_FAILED(rv))
991 return rv;
992 exists = false;
996 // if we don't have a cache directory, create one and open it
997 if (!exists) {
998 nsCacheService::MarkStartingFresh();
999 rv = mCacheDirectory->Create(nsIFile::DIRECTORY_TYPE, 0777);
1000 CACHE_LOG_PATH(PR_LOG_ALWAYS, "\ncreate cache directory: %s\n", mCacheDirectory);
1001 CACHE_LOG_ALWAYS(("mCacheDirectory->Create() = %x\n", rv));
1002 if (NS_FAILED(rv))
1003 return rv;
1005 // reopen the cache map
1006 nsDiskCache::CorruptCacheInfo corruptInfo;
1007 rv = mCacheMap.Open(mCacheDirectory, &corruptInfo, false);
1008 if (NS_FAILED(rv))
1009 return rv;
1012 return NS_OK;
1016 nsresult
1017 nsDiskCacheDevice::ClearDiskCache()
1019 if (mBindery.ActiveBindings())
1020 return NS_ERROR_CACHE_IN_USE;
1022 mClearingDiskCache = true;
1024 nsresult rv = Shutdown_Private(false); // false: don't bother flushing
1025 if (NS_FAILED(rv))
1026 return rv;
1028 mClearingDiskCache = false;
1030 // If the disk cache directory is already gone, then it's not an error if
1031 // we fail to delete it ;-)
1032 rv = nsDeleteDir::DeleteDir(mCacheDirectory, true);
1033 if (NS_FAILED(rv) && rv != NS_ERROR_FILE_TARGET_DOES_NOT_EXIST)
1034 return rv;
1036 return Init();
1040 nsresult
1041 nsDiskCacheDevice::EvictDiskCacheEntries(uint32_t targetCapacity)
1043 CACHE_LOG_DEBUG(("CACHE: disk EvictDiskCacheEntries [%u]\n",
1044 targetCapacity));
1046 NS_ASSERTION(targetCapacity > 0, "oops");
1048 if (mCacheMap.TotalSize() < targetCapacity)
1049 return NS_OK;
1051 // targetCapacity is in KiB's
1052 nsDiskCacheEvictor evictor(&mCacheMap, &mBindery, targetCapacity, nullptr);
1053 return mCacheMap.EvictRecords(&evictor);
1058 * methods for prefs
1061 void
1062 nsDiskCacheDevice::SetCacheParentDirectory(nsIFile * parentDir)
1064 nsresult rv;
1065 bool exists;
1067 if (Initialized()) {
1068 NS_ASSERTION(false, "Cannot switch cache directory when initialized");
1069 return;
1072 if (!parentDir) {
1073 mCacheDirectory = nullptr;
1074 return;
1077 // ensure parent directory exists
1078 rv = parentDir->Exists(&exists);
1079 if (NS_SUCCEEDED(rv) && !exists)
1080 rv = parentDir->Create(nsIFile::DIRECTORY_TYPE, 0700);
1081 if (NS_FAILED(rv)) return;
1083 // ensure cache directory exists
1084 nsCOMPtr<nsIFile> directory;
1086 rv = parentDir->Clone(getter_AddRefs(directory));
1087 if (NS_FAILED(rv)) return;
1088 rv = directory->AppendNative(NS_LITERAL_CSTRING("Cache"));
1089 if (NS_FAILED(rv)) return;
1091 mCacheDirectory = do_QueryInterface(directory);
1095 void
1096 nsDiskCacheDevice::getCacheDirectory(nsIFile ** result)
1098 *result = mCacheDirectory;
1099 NS_IF_ADDREF(*result);
1104 * NOTE: called while holding the cache service lock
1106 void
1107 nsDiskCacheDevice::SetCapacity(uint32_t capacity)
1109 // Units are KiB's
1110 mCacheCapacity = capacity;
1111 if (Initialized()) {
1112 if (NS_IsMainThread()) {
1113 // Do not evict entries on the main thread
1114 nsCacheService::DispatchToCacheIOThread(
1115 new nsEvictDiskCacheEntriesEvent(this));
1116 } else {
1117 // start evicting entries if the new size is smaller!
1118 EvictDiskCacheEntries(mCacheCapacity);
1121 // Let cache map know of the new capacity
1122 mCacheMap.NotifyCapacityChange(capacity);
1126 uint32_t nsDiskCacheDevice::getCacheCapacity()
1128 return mCacheCapacity;
1132 uint32_t nsDiskCacheDevice::getCacheSize()
1134 return mCacheMap.TotalSize();
1138 uint32_t nsDiskCacheDevice::getEntryCount()
1140 return mCacheMap.EntryCount();
1143 void
1144 nsDiskCacheDevice::SetMaxEntrySize(int32_t maxSizeInKilobytes)
1146 // Internal units are bytes. Changing this only takes effect *after* the
1147 // change and has no consequences for existing cache-entries
1148 if (maxSizeInKilobytes >= 0)
1149 mMaxEntrySize = maxSizeInKilobytes * 1024;
1150 else
1151 mMaxEntrySize = -1;
1154 size_t
1155 nsDiskCacheDevice::SizeOfIncludingThis(MallocSizeOf aMallocSizeOf)
1157 size_t usage = aMallocSizeOf(this);
1159 usage += mCacheMap.SizeOfExcludingThis(aMallocSizeOf);
1160 usage += mBindery.SizeOfExcludingThis(aMallocSizeOf);
1162 return usage;