Wed, 31 Dec 2014 06:09:35 +0100
Cloned upstream origin tor-browser at tor-browser-31.3.0esr-4.5-1-build1
revision ID fc1c9ff7c1b2defdbc039f12214767608f46423f for hacking purpose.
1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
2 *
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)
62 {
63 }
65 NS_IMETHOD Run()
66 {
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);
73 }
74 return NS_OK;
75 }
77 void CancelEvent() { mCanceled = true; }
78 private:
79 bool mCanceled;
80 nsCacheEntry *mEntry;
81 nsDiskCacheDevice *mDevice;
82 nsDiskCacheBinding *mBinding;
83 };
85 class nsEvictDiskCacheEntriesEvent : public nsRunnable {
86 public:
87 nsEvictDiskCacheEntriesEvent(nsDiskCacheDevice *device)
88 : mDevice(device) {}
90 NS_IMETHOD Run()
91 {
92 nsCacheServiceAutoLock lock(LOCK_TELEM(NSEVICTDISKCACHEENTRIESEVENT_RUN));
93 mDevice->EvictDiskCacheEntries(mDevice->mCacheCapacity);
94 return NS_OK;
95 }
97 private:
98 nsDiskCacheDevice *mDevice;
99 };
101 /******************************************************************************
102 * nsDiskCacheEvictor
103 *
104 * Helper class for nsDiskCacheDevice.
105 *
106 *****************************************************************************/
108 class nsDiskCacheEvictor : public nsDiskCacheRecordVisitor
109 {
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)
119 {
120 mClientIDSize = clientID ? strlen(clientID) : 0;
121 }
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;
131 };
134 int32_t
135 nsDiskCacheEvictor::VisitRecord(nsDiskCacheRecord * mapRecord)
136 {
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
151 }
152 }
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;
161 }
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);
170 }
172 return kDeleteRecordAndContinue; // this will REALLY delete the record
173 }
176 /******************************************************************************
177 * nsDiskCacheDeviceInfo
178 *****************************************************************************/
180 class nsDiskCacheDeviceInfo : public nsICacheDeviceInfo {
181 public:
182 NS_DECL_ISUPPORTS
183 NS_DECL_NSICACHEDEVICEINFO
185 nsDiskCacheDeviceInfo(nsDiskCacheDevice* device)
186 : mDevice(device)
187 {
188 }
190 virtual ~nsDiskCacheDeviceInfo() {}
192 private:
193 nsDiskCacheDevice* mDevice;
194 };
196 NS_IMPL_ISUPPORTS(nsDiskCacheDeviceInfo, nsICacheDeviceInfo)
198 /* readonly attribute string description; */
199 NS_IMETHODIMP nsDiskCacheDeviceInfo::GetDescription(char ** aDescription)
200 {
201 NS_ENSURE_ARG_POINTER(aDescription);
202 *aDescription = NS_strdup("Disk cache device");
203 return *aDescription ? NS_OK : NS_ERROR_OUT_OF_MEMORY;
204 }
206 /* readonly attribute string usageReport; */
207 NS_IMETHODIMP nsDiskCacheDeviceInfo::GetUsageReport(char ** usageReport)
208 {
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");
223 }
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;
231 }
233 /* readonly attribute unsigned long entryCount; */
234 NS_IMETHODIMP nsDiskCacheDeviceInfo::GetEntryCount(uint32_t *aEntryCount)
235 {
236 NS_ENSURE_ARG_POINTER(aEntryCount);
237 *aEntryCount = mDevice->getEntryCount();
238 return NS_OK;
239 }
241 /* readonly attribute unsigned long totalSize; */
242 NS_IMETHODIMP nsDiskCacheDeviceInfo::GetTotalSize(uint32_t *aTotalSize)
243 {
244 NS_ENSURE_ARG_POINTER(aTotalSize);
245 // Returned unit's are in bytes
246 *aTotalSize = mDevice->getCacheSize() * 1024;
247 return NS_OK;
248 }
250 /* readonly attribute unsigned long maximumSize; */
251 NS_IMETHODIMP nsDiskCacheDeviceInfo::GetMaximumSize(uint32_t *aMaximumSize)
252 {
253 NS_ENSURE_ARG_POINTER(aMaximumSize);
254 // Returned unit's are in bytes
255 *aMaximumSize = mDevice->getCacheCapacity() * 1024;
256 return NS_OK;
257 }
260 /******************************************************************************
261 * nsDiskCache
262 *****************************************************************************/
264 /**
265 * nsDiskCache::Hash(const char * key, PLDHashNumber initval)
266 *
267 * See http://burtleburtle.net/bob/hash/evahash.html for more information
268 * about this hash function.
269 *
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.
274 */
276 static inline void hashmix(uint32_t& a, uint32_t& b, uint32_t& c)
277 {
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);
287 }
289 PLDHashNumber
290 nsDiskCache::Hash(const char * key, PLDHashNumber initval)
291 {
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)
303 {
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;
309 }
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 */
327 }
328 hashmix(a, b, c);
330 return c;
331 }
333 nsresult
334 nsDiskCache::Truncate(PRFileDesc * fd, uint32_t newEOF)
335 {
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;
342 }
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;
350 }
352 #else
353 // add implementations for other platforms here
354 #endif
355 return NS_OK;
356 }
359 /******************************************************************************
360 * nsDiskCacheDevice
361 *****************************************************************************/
363 nsDiskCacheDevice::nsDiskCacheDevice()
364 : mCacheCapacity(0)
365 , mMaxEntrySize(-1) // -1 means "no limit"
366 , mInitialized(false)
367 , mClearingDiskCache(false)
368 {
369 }
371 nsDiskCacheDevice::~nsDiskCacheDevice()
372 {
373 Shutdown();
374 }
377 /**
378 * methods of nsCacheDevice
379 */
380 nsresult
381 nsDiskCacheDevice::Init()
382 {
383 nsresult rv;
385 if (Initialized()) {
386 NS_ERROR("Disk cache already initialized!");
387 return NS_ERROR_UNEXPECTED;
388 }
390 if (!mCacheDirectory)
391 return NS_ERROR_FAILURE;
393 rv = mBindery.Init();
394 if (NS_FAILED(rv))
395 return rv;
397 nsDeleteDir::RemoveOldTrashes(mCacheDirectory);
399 // Open Disk Cache
400 rv = OpenDiskCache();
401 if (NS_FAILED(rv)) {
402 (void) mCacheMap.Close(false);
403 return rv;
404 }
406 mInitialized = true;
407 return NS_OK;
408 }
411 /**
412 * NOTE: called while holding the cache service lock
413 */
414 nsresult
415 nsDiskCacheDevice::Shutdown()
416 {
417 nsCacheService::AssertOwnsLock();
419 nsresult rv = Shutdown_Private(true);
420 if (NS_FAILED(rv))
421 return rv;
423 return NS_OK;
424 }
427 nsresult
428 nsDiskCacheDevice::Shutdown_Private(bool flush)
429 {
430 CACHE_LOG_DEBUG(("CACHE: disk Shutdown_Private [%u]\n", flush));
432 if (Initialized()) {
433 // check cache limits in case we need to evict.
434 EvictDiskCacheEntries(mCacheCapacity);
436 // At this point there may be a number of pending cache-requests on the
437 // cache-io thread. Wait for all these to run before we wipe out our
438 // datastructures (see bug #620660)
439 (void) nsCacheService::SyncWithCacheIOThread();
441 // write out persistent information about the cache.
442 (void) mCacheMap.Close(flush);
444 mBindery.Reset();
446 mInitialized = false;
447 }
449 return NS_OK;
450 }
453 const char *
454 nsDiskCacheDevice::GetDeviceID()
455 {
456 return DISK_CACHE_DEVICE_ID;
457 }
459 /**
460 * FindEntry -
461 *
462 * cases: key not in disk cache, hash number free
463 * key not in disk cache, hash number used
464 * key in disk cache
465 *
466 * NOTE: called while holding the cache service lock
467 */
468 nsCacheEntry *
469 nsDiskCacheDevice::FindEntry(nsCString * key, bool *collision)
470 {
471 Telemetry::AutoTimer<Telemetry::CACHE_DISK_SEARCH_2> timer;
472 if (!Initialized()) return nullptr; // NS_ERROR_NOT_INITIALIZED
473 if (mClearingDiskCache) return nullptr;
474 nsDiskCacheRecord record;
475 nsDiskCacheBinding * binding = nullptr;
476 PLDHashNumber hashNumber = nsDiskCache::Hash(key->get());
478 *collision = false;
480 binding = mBindery.FindActiveBinding(hashNumber);
481 if (binding && !binding->mCacheEntry->Key()->Equals(*key)) {
482 *collision = true;
483 return nullptr;
484 } else if (binding && binding->mDeactivateEvent) {
485 binding->mDeactivateEvent->CancelEvent();
486 binding->mDeactivateEvent = nullptr;
487 CACHE_LOG_DEBUG(("CACHE: reusing deactivated entry %p " \
488 "req-key=%s entry-key=%s\n",
489 binding->mCacheEntry, key, binding->mCacheEntry->Key()));
491 return binding->mCacheEntry; // just return this one, observing that
492 // FindActiveBinding() does not return
493 // bindings to doomed entries
494 }
495 binding = nullptr;
497 // lookup hash number in cache map
498 nsresult rv = mCacheMap.FindRecord(hashNumber, &record);
499 if (NS_FAILED(rv)) return nullptr; // XXX log error?
501 nsDiskCacheEntry * diskEntry = mCacheMap.ReadDiskCacheEntry(&record);
502 if (!diskEntry) return nullptr;
504 // compare key to be sure
505 if (!key->Equals(diskEntry->Key())) {
506 *collision = true;
507 return nullptr;
508 }
510 nsCacheEntry * entry = diskEntry->CreateCacheEntry(this);
511 if (entry) {
512 binding = mBindery.CreateBinding(entry, &record);
513 if (!binding) {
514 delete entry;
515 entry = nullptr;
516 }
517 }
519 if (!entry) {
520 (void) mCacheMap.DeleteStorage(&record);
521 (void) mCacheMap.DeleteRecord(&record);
522 }
524 return entry;
525 }
528 /**
529 * NOTE: called while holding the cache service lock
530 */
531 nsresult
532 nsDiskCacheDevice::DeactivateEntry(nsCacheEntry * entry)
533 {
534 nsDiskCacheBinding * binding = GetCacheEntryBinding(entry);
535 if (!IsValidBinding(binding))
536 return NS_ERROR_UNEXPECTED;
538 CACHE_LOG_DEBUG(("CACHE: disk DeactivateEntry [%p %x]\n",
539 entry, binding->mRecord.HashNumber()));
541 nsDiskCacheDeviceDeactivateEntryEvent *event =
542 new nsDiskCacheDeviceDeactivateEntryEvent(this, entry, binding);
544 // ensure we can cancel the event via the binding later if necessary
545 binding->mDeactivateEvent = event;
547 DebugOnly<nsresult> rv = nsCacheService::DispatchToCacheIOThread(event);
548 NS_ASSERTION(NS_SUCCEEDED(rv), "DeactivateEntry: Failed dispatching "
549 "deactivation event");
550 return NS_OK;
551 }
553 /**
554 * NOTE: called while holding the cache service lock
555 */
556 nsresult
557 nsDiskCacheDevice::DeactivateEntry_Private(nsCacheEntry * entry,
558 nsDiskCacheBinding * binding)
559 {
560 nsresult rv = NS_OK;
561 if (entry->IsDoomed()) {
562 // delete data, entry, record from disk for entry
563 rv = mCacheMap.DeleteStorage(&binding->mRecord);
565 } else {
566 // save stuff to disk for entry
567 rv = mCacheMap.WriteDiskCacheEntry(binding);
568 if (NS_FAILED(rv)) {
569 // clean up as best we can
570 (void) mCacheMap.DeleteStorage(&binding->mRecord);
571 (void) mCacheMap.DeleteRecord(&binding->mRecord);
572 binding->mDoomed = true; // record is no longer in cache map
573 }
574 }
576 mBindery.RemoveBinding(binding); // extract binding from collision detection stuff
577 delete entry; // which will release binding
578 return rv;
579 }
582 /**
583 * BindEntry()
584 * no hash number collision -> no problem
585 * collision
586 * record not active -> evict, no problem
587 * record is active
588 * record is already doomed -> record shouldn't have been in map, no problem
589 * record is not doomed -> doom, and replace record in map
590 *
591 * walk matching hashnumber list to find lowest generation number
592 * take generation number from other (data/meta) location,
593 * or walk active list
594 *
595 * NOTE: called while holding the cache service lock
596 */
597 nsresult
598 nsDiskCacheDevice::BindEntry(nsCacheEntry * entry)
599 {
600 if (!Initialized()) return NS_ERROR_NOT_INITIALIZED;
601 if (mClearingDiskCache) return NS_ERROR_NOT_AVAILABLE;
602 nsresult rv = NS_OK;
603 nsDiskCacheRecord record, oldRecord;
604 nsDiskCacheBinding *binding;
605 PLDHashNumber hashNumber = nsDiskCache::Hash(entry->Key()->get());
607 // Find out if there is already an active binding for this hash. If yes it
608 // should have another key since BindEntry() shouldn't be called twice for
609 // the same entry. Doom the old entry, the new one will get another
610 // generation number so files won't collide.
611 binding = mBindery.FindActiveBinding(hashNumber);
612 if (binding) {
613 NS_ASSERTION(!binding->mCacheEntry->Key()->Equals(*entry->Key()),
614 "BindEntry called for already bound entry!");
615 // If the entry is pending deactivation, cancel deactivation
616 if (binding->mDeactivateEvent) {
617 binding->mDeactivateEvent->CancelEvent();
618 binding->mDeactivateEvent = nullptr;
619 }
620 nsCacheService::DoomEntry(binding->mCacheEntry);
621 binding = nullptr;
622 }
624 // Lookup hash number in cache map. There can be a colliding inactive entry.
625 // See bug #321361 comment 21 for the scenario. If there is such entry,
626 // delete it.
627 rv = mCacheMap.FindRecord(hashNumber, &record);
628 if (NS_SUCCEEDED(rv)) {
629 nsDiskCacheEntry * diskEntry = mCacheMap.ReadDiskCacheEntry(&record);
630 if (diskEntry) {
631 // compare key to be sure
632 if (!entry->Key()->Equals(diskEntry->Key())) {
633 mCacheMap.DeleteStorage(&record);
634 rv = mCacheMap.DeleteRecord(&record);
635 if (NS_FAILED(rv)) return rv;
636 }
637 }
638 record = nsDiskCacheRecord();
639 }
641 // create a new record for this entry
642 record.SetHashNumber(nsDiskCache::Hash(entry->Key()->get()));
643 record.SetEvictionRank(ULONG_MAX - SecondsFromPRTime(PR_Now()));
645 CACHE_LOG_DEBUG(("CACHE: disk BindEntry [%p %x]\n",
646 entry, record.HashNumber()));
648 if (!entry->IsDoomed()) {
649 // if entry isn't doomed, add it to the cache map
650 rv = mCacheMap.AddRecord(&record, &oldRecord); // deletes old record, if any
651 if (NS_FAILED(rv)) return rv;
653 uint32_t oldHashNumber = oldRecord.HashNumber();
654 if (oldHashNumber) {
655 // gotta evict this one first
656 nsDiskCacheBinding * oldBinding = mBindery.FindActiveBinding(oldHashNumber);
657 if (oldBinding) {
658 // XXX if debug : compare keys for hashNumber collision
660 if (!oldBinding->mCacheEntry->IsDoomed()) {
661 // If the old entry is pending deactivation, cancel deactivation
662 if (oldBinding->mDeactivateEvent) {
663 oldBinding->mDeactivateEvent->CancelEvent();
664 oldBinding->mDeactivateEvent = nullptr;
665 }
666 // we've got a live one!
667 nsCacheService::DoomEntry(oldBinding->mCacheEntry);
668 // storage will be delete when oldBinding->mCacheEntry is Deactivated
669 }
670 } else {
671 // delete storage
672 // XXX if debug : compare keys for hashNumber collision
673 rv = mCacheMap.DeleteStorage(&oldRecord);
674 if (NS_FAILED(rv)) return rv; // XXX delete record we just added?
675 }
676 }
677 }
679 // Make sure this entry has its associated nsDiskCacheBinding attached.
680 binding = mBindery.CreateBinding(entry, &record);
681 NS_ASSERTION(binding, "nsDiskCacheDevice::BindEntry");
682 if (!binding) return NS_ERROR_OUT_OF_MEMORY;
683 NS_ASSERTION(binding->mRecord.ValidRecord(), "bad cache map record");
685 return NS_OK;
686 }
689 /**
690 * NOTE: called while holding the cache service lock
691 */
692 void
693 nsDiskCacheDevice::DoomEntry(nsCacheEntry * entry)
694 {
695 CACHE_LOG_DEBUG(("CACHE: disk DoomEntry [%p]\n", entry));
697 nsDiskCacheBinding * binding = GetCacheEntryBinding(entry);
698 NS_ASSERTION(binding, "DoomEntry: binding == nullptr");
699 if (!binding)
700 return;
702 if (!binding->mDoomed) {
703 // so it can't be seen by FindEntry() ever again.
704 #ifdef DEBUG
705 nsresult rv =
706 #endif
707 mCacheMap.DeleteRecord(&binding->mRecord);
708 NS_ASSERTION(NS_SUCCEEDED(rv),"DeleteRecord failed.");
709 binding->mDoomed = true; // record in no longer in cache map
710 }
711 }
714 /**
715 * NOTE: called while holding the cache service lock
716 */
717 nsresult
718 nsDiskCacheDevice::OpenInputStreamForEntry(nsCacheEntry * entry,
719 nsCacheAccessMode mode,
720 uint32_t offset,
721 nsIInputStream ** result)
722 {
723 CACHE_LOG_DEBUG(("CACHE: disk OpenInputStreamForEntry [%p %x %u]\n",
724 entry, mode, offset));
726 NS_ENSURE_ARG_POINTER(entry);
727 NS_ENSURE_ARG_POINTER(result);
729 nsresult rv;
730 nsDiskCacheBinding * binding = GetCacheEntryBinding(entry);
731 if (!IsValidBinding(binding))
732 return NS_ERROR_UNEXPECTED;
734 NS_ASSERTION(binding->mCacheEntry == entry, "binding & entry don't point to each other");
736 rv = binding->EnsureStreamIO();
737 if (NS_FAILED(rv)) return rv;
739 return binding->mStreamIO->GetInputStream(offset, result);
740 }
743 /**
744 * NOTE: called while holding the cache service lock
745 */
746 nsresult
747 nsDiskCacheDevice::OpenOutputStreamForEntry(nsCacheEntry * entry,
748 nsCacheAccessMode mode,
749 uint32_t offset,
750 nsIOutputStream ** result)
751 {
752 CACHE_LOG_DEBUG(("CACHE: disk OpenOutputStreamForEntry [%p %x %u]\n",
753 entry, mode, offset));
755 NS_ENSURE_ARG_POINTER(entry);
756 NS_ENSURE_ARG_POINTER(result);
758 nsresult rv;
759 nsDiskCacheBinding * binding = GetCacheEntryBinding(entry);
760 if (!IsValidBinding(binding))
761 return NS_ERROR_UNEXPECTED;
763 NS_ASSERTION(binding->mCacheEntry == entry, "binding & entry don't point to each other");
765 rv = binding->EnsureStreamIO();
766 if (NS_FAILED(rv)) return rv;
768 return binding->mStreamIO->GetOutputStream(offset, result);
769 }
772 /**
773 * NOTE: called while holding the cache service lock
774 */
775 nsresult
776 nsDiskCacheDevice::GetFileForEntry(nsCacheEntry * entry,
777 nsIFile ** result)
778 {
779 NS_ENSURE_ARG_POINTER(result);
780 *result = nullptr;
782 nsresult rv;
784 nsDiskCacheBinding * binding = GetCacheEntryBinding(entry);
785 if (!IsValidBinding(binding))
786 return NS_ERROR_UNEXPECTED;
788 // check/set binding->mRecord for separate file, sync w/mCacheMap
789 if (binding->mRecord.DataLocationInitialized()) {
790 if (binding->mRecord.DataFile() != 0)
791 return NS_ERROR_NOT_AVAILABLE; // data not stored as separate file
793 NS_ASSERTION(binding->mRecord.DataFileGeneration() == binding->mGeneration, "error generations out of sync");
794 } else {
795 binding->mRecord.SetDataFileGeneration(binding->mGeneration);
796 binding->mRecord.SetDataFileSize(0); // 1k minimum
797 if (!binding->mDoomed) {
798 // record stored in cache map, so update it
799 rv = mCacheMap.UpdateRecord(&binding->mRecord);
800 if (NS_FAILED(rv)) return rv;
801 }
802 }
804 nsCOMPtr<nsIFile> file;
805 rv = mCacheMap.GetFileForDiskCacheRecord(&binding->mRecord,
806 nsDiskCache::kData,
807 false,
808 getter_AddRefs(file));
809 if (NS_FAILED(rv)) return rv;
811 NS_IF_ADDREF(*result = file);
812 return NS_OK;
813 }
816 /**
817 * This routine will get called every time an open descriptor is written to.
818 *
819 * NOTE: called while holding the cache service lock
820 */
821 nsresult
822 nsDiskCacheDevice::OnDataSizeChange(nsCacheEntry * entry, int32_t deltaSize)
823 {
824 CACHE_LOG_DEBUG(("CACHE: disk OnDataSizeChange [%p %d]\n",
825 entry, deltaSize));
827 // If passed a negative value, then there's nothing to do.
828 if (deltaSize < 0)
829 return NS_OK;
831 nsDiskCacheBinding * binding = GetCacheEntryBinding(entry);
832 if (!IsValidBinding(binding))
833 return NS_ERROR_UNEXPECTED;
835 NS_ASSERTION(binding->mRecord.ValidRecord(), "bad record");
837 uint32_t newSize = entry->DataSize() + deltaSize;
838 uint32_t newSizeK = ((newSize + 0x3FF) >> 10);
840 // If the new size is larger than max. file size or larger than
841 // 1/8 the cache capacity (which is in KiB's), doom the entry and abort.
842 if (EntryIsTooBig(newSize)) {
843 #ifdef DEBUG
844 nsresult rv =
845 #endif
846 nsCacheService::DoomEntry(entry);
847 NS_ASSERTION(NS_SUCCEEDED(rv),"DoomEntry() failed.");
848 return NS_ERROR_ABORT;
849 }
851 uint32_t sizeK = ((entry->DataSize() + 0x03FF) >> 10); // round up to next 1k
853 // In total count we ignore anything over kMaxDataSizeK (bug #651100), so
854 // the target capacity should be calculated the same way.
855 if (sizeK > kMaxDataSizeK) sizeK = kMaxDataSizeK;
856 if (newSizeK > kMaxDataSizeK) newSizeK = kMaxDataSizeK;
858 // pre-evict entries to make space for new data
859 uint32_t targetCapacity = mCacheCapacity > (newSizeK - sizeK)
860 ? mCacheCapacity - (newSizeK - sizeK)
861 : 0;
862 EvictDiskCacheEntries(targetCapacity);
864 return NS_OK;
865 }
868 /******************************************************************************
869 * EntryInfoVisitor
870 *****************************************************************************/
871 class EntryInfoVisitor : public nsDiskCacheRecordVisitor
872 {
873 public:
874 EntryInfoVisitor(nsDiskCacheMap * cacheMap,
875 nsICacheVisitor * visitor)
876 : mCacheMap(cacheMap)
877 , mVisitor(visitor)
878 {}
880 virtual int32_t VisitRecord(nsDiskCacheRecord * mapRecord)
881 {
882 // XXX optimization: do we have this record in memory?
884 // read in the entry (metadata)
885 nsDiskCacheEntry * diskEntry = mCacheMap->ReadDiskCacheEntry(mapRecord);
886 if (!diskEntry) {
887 return kVisitNextRecord;
888 }
890 // create nsICacheEntryInfo
891 nsDiskCacheEntryInfo * entryInfo = new nsDiskCacheEntryInfo(DISK_CACHE_DEVICE_ID, diskEntry);
892 if (!entryInfo) {
893 return kStopVisitingRecords;
894 }
895 nsCOMPtr<nsICacheEntryInfo> ref(entryInfo);
897 bool keepGoing;
898 (void)mVisitor->VisitEntry(DISK_CACHE_DEVICE_ID, entryInfo, &keepGoing);
899 return keepGoing ? kVisitNextRecord : kStopVisitingRecords;
900 }
902 private:
903 nsDiskCacheMap * mCacheMap;
904 nsICacheVisitor * mVisitor;
905 };
908 nsresult
909 nsDiskCacheDevice::Visit(nsICacheVisitor * visitor)
910 {
911 if (!Initialized()) return NS_ERROR_NOT_INITIALIZED;
912 nsDiskCacheDeviceInfo* deviceInfo = new nsDiskCacheDeviceInfo(this);
913 nsCOMPtr<nsICacheDeviceInfo> ref(deviceInfo);
915 bool keepGoing;
916 nsresult rv = visitor->VisitDevice(DISK_CACHE_DEVICE_ID, deviceInfo, &keepGoing);
917 if (NS_FAILED(rv)) return rv;
919 if (keepGoing) {
920 EntryInfoVisitor infoVisitor(&mCacheMap, visitor);
921 return mCacheMap.VisitRecords(&infoVisitor);
922 }
924 return NS_OK;
925 }
927 // Max allowed size for an entry is currently MIN(mMaxEntrySize, 1/8 CacheCapacity)
928 bool
929 nsDiskCacheDevice::EntryIsTooBig(int64_t entrySize)
930 {
931 if (mMaxEntrySize == -1) // no limit
932 return entrySize > (static_cast<int64_t>(mCacheCapacity) * 1024 / 8);
933 else
934 return entrySize > mMaxEntrySize ||
935 entrySize > (static_cast<int64_t>(mCacheCapacity) * 1024 / 8);
936 }
938 nsresult
939 nsDiskCacheDevice::EvictEntries(const char * clientID)
940 {
941 CACHE_LOG_DEBUG(("CACHE: disk EvictEntries [%s]\n", clientID));
943 if (!Initialized()) return NS_ERROR_NOT_INITIALIZED;
944 nsresult rv;
946 if (clientID == nullptr) {
947 // we're clearing the entire disk cache
948 rv = ClearDiskCache();
949 if (rv != NS_ERROR_CACHE_IN_USE)
950 return rv;
951 }
953 nsDiskCacheEvictor evictor(&mCacheMap, &mBindery, 0, clientID);
954 rv = mCacheMap.VisitRecords(&evictor);
956 if (clientID == nullptr) // we tried to clear the entire cache
957 rv = mCacheMap.Trim(); // so trim cache block files (if possible)
958 return rv;
959 }
962 /**
963 * private methods
964 */
966 nsresult
967 nsDiskCacheDevice::OpenDiskCache()
968 {
969 Telemetry::AutoTimer<Telemetry::NETWORK_DISK_CACHE_OPEN> timer;
970 // if we don't have a cache directory, create one and open it
971 bool exists;
972 nsresult rv = mCacheDirectory->Exists(&exists);
973 if (NS_FAILED(rv))
974 return rv;
976 if (exists) {
977 // Try opening cache map file.
978 nsDiskCache::CorruptCacheInfo corruptInfo;
979 rv = mCacheMap.Open(mCacheDirectory, &corruptInfo, true);
981 if (NS_SUCCEEDED(rv)) {
982 Telemetry::Accumulate(Telemetry::DISK_CACHE_CORRUPT_DETAILS,
983 corruptInfo);
984 } else if (rv == NS_ERROR_ALREADY_INITIALIZED) {
985 NS_WARNING("nsDiskCacheDevice::OpenDiskCache: already open!");
986 } else {
987 // Consider cache corrupt: delete it
988 Telemetry::Accumulate(Telemetry::DISK_CACHE_CORRUPT_DETAILS,
989 corruptInfo);
990 // delay delete by 1 minute to avoid IO thrash at startup
991 rv = nsDeleteDir::DeleteDir(mCacheDirectory, true, 60000);
992 if (NS_FAILED(rv))
993 return rv;
994 exists = false;
995 }
996 }
998 // if we don't have a cache directory, create one and open it
999 if (!exists) {
1000 nsCacheService::MarkStartingFresh();
1001 rv = mCacheDirectory->Create(nsIFile::DIRECTORY_TYPE, 0777);
1002 CACHE_LOG_PATH(PR_LOG_ALWAYS, "\ncreate cache directory: %s\n", mCacheDirectory);
1003 CACHE_LOG_ALWAYS(("mCacheDirectory->Create() = %x\n", rv));
1004 if (NS_FAILED(rv))
1005 return rv;
1007 // reopen the cache map
1008 nsDiskCache::CorruptCacheInfo corruptInfo;
1009 rv = mCacheMap.Open(mCacheDirectory, &corruptInfo, false);
1010 if (NS_FAILED(rv))
1011 return rv;
1012 }
1014 return NS_OK;
1015 }
1018 nsresult
1019 nsDiskCacheDevice::ClearDiskCache()
1020 {
1021 if (mBindery.ActiveBindings())
1022 return NS_ERROR_CACHE_IN_USE;
1024 mClearingDiskCache = true;
1026 nsresult rv = Shutdown_Private(false); // false: don't bother flushing
1027 if (NS_FAILED(rv))
1028 return rv;
1030 mClearingDiskCache = false;
1032 // If the disk cache directory is already gone, then it's not an error if
1033 // we fail to delete it ;-)
1034 rv = nsDeleteDir::DeleteDir(mCacheDirectory, true);
1035 if (NS_FAILED(rv) && rv != NS_ERROR_FILE_TARGET_DOES_NOT_EXIST)
1036 return rv;
1038 return Init();
1039 }
1042 nsresult
1043 nsDiskCacheDevice::EvictDiskCacheEntries(uint32_t targetCapacity)
1044 {
1045 CACHE_LOG_DEBUG(("CACHE: disk EvictDiskCacheEntries [%u]\n",
1046 targetCapacity));
1048 NS_ASSERTION(targetCapacity > 0, "oops");
1050 if (mCacheMap.TotalSize() < targetCapacity)
1051 return NS_OK;
1053 // targetCapacity is in KiB's
1054 nsDiskCacheEvictor evictor(&mCacheMap, &mBindery, targetCapacity, nullptr);
1055 return mCacheMap.EvictRecords(&evictor);
1056 }
1059 /**
1060 * methods for prefs
1061 */
1063 void
1064 nsDiskCacheDevice::SetCacheParentDirectory(nsIFile * parentDir)
1065 {
1066 nsresult rv;
1067 bool exists;
1069 if (Initialized()) {
1070 NS_ASSERTION(false, "Cannot switch cache directory when initialized");
1071 return;
1072 }
1074 if (!parentDir) {
1075 mCacheDirectory = nullptr;
1076 return;
1077 }
1079 // ensure parent directory exists
1080 rv = parentDir->Exists(&exists);
1081 if (NS_SUCCEEDED(rv) && !exists)
1082 rv = parentDir->Create(nsIFile::DIRECTORY_TYPE, 0700);
1083 if (NS_FAILED(rv)) return;
1085 // ensure cache directory exists
1086 nsCOMPtr<nsIFile> directory;
1088 rv = parentDir->Clone(getter_AddRefs(directory));
1089 if (NS_FAILED(rv)) return;
1090 rv = directory->AppendNative(NS_LITERAL_CSTRING("Cache"));
1091 if (NS_FAILED(rv)) return;
1093 mCacheDirectory = do_QueryInterface(directory);
1094 }
1097 void
1098 nsDiskCacheDevice::getCacheDirectory(nsIFile ** result)
1099 {
1100 *result = mCacheDirectory;
1101 NS_IF_ADDREF(*result);
1102 }
1105 /**
1106 * NOTE: called while holding the cache service lock
1107 */
1108 void
1109 nsDiskCacheDevice::SetCapacity(uint32_t capacity)
1110 {
1111 // Units are KiB's
1112 mCacheCapacity = capacity;
1113 if (Initialized()) {
1114 if (NS_IsMainThread()) {
1115 // Do not evict entries on the main thread
1116 nsCacheService::DispatchToCacheIOThread(
1117 new nsEvictDiskCacheEntriesEvent(this));
1118 } else {
1119 // start evicting entries if the new size is smaller!
1120 EvictDiskCacheEntries(mCacheCapacity);
1121 }
1122 }
1123 // Let cache map know of the new capacity
1124 mCacheMap.NotifyCapacityChange(capacity);
1125 }
1128 uint32_t nsDiskCacheDevice::getCacheCapacity()
1129 {
1130 return mCacheCapacity;
1131 }
1134 uint32_t nsDiskCacheDevice::getCacheSize()
1135 {
1136 return mCacheMap.TotalSize();
1137 }
1140 uint32_t nsDiskCacheDevice::getEntryCount()
1141 {
1142 return mCacheMap.EntryCount();
1143 }
1145 void
1146 nsDiskCacheDevice::SetMaxEntrySize(int32_t maxSizeInKilobytes)
1147 {
1148 // Internal units are bytes. Changing this only takes effect *after* the
1149 // change and has no consequences for existing cache-entries
1150 if (maxSizeInKilobytes >= 0)
1151 mMaxEntrySize = maxSizeInKilobytes * 1024;
1152 else
1153 mMaxEntrySize = -1;
1154 }
1156 size_t
1157 nsDiskCacheDevice::SizeOfIncludingThis(MallocSizeOf aMallocSizeOf)
1158 {
1159 size_t usage = aMallocSizeOf(this);
1161 usage += mCacheMap.SizeOfExcludingThis(aMallocSizeOf);
1162 usage += mBindery.SizeOfExcludingThis(aMallocSizeOf);
1164 return usage;
1165 }