Line data Source code
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/. */
6 :
7 : #include <limits.h>
8 :
9 : #include "mozilla/DebugOnly.h"
10 :
11 : #include "nsCache.h"
12 : #include "nsIMemoryReporter.h"
13 :
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
22 :
23 : #include "prthread.h"
24 :
25 : #include "private/pprio.h"
26 :
27 : #include "nsDiskCacheDevice.h"
28 : #include "nsDiskCacheEntry.h"
29 : #include "nsDiskCacheMap.h"
30 : #include "nsDiskCacheStreams.h"
31 :
32 : #include "nsDiskCache.h"
33 :
34 : #include "nsCacheService.h"
35 :
36 : #include "nsDeleteDir.h"
37 :
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"
45 :
46 : #include "nsThreadUtils.h"
47 : #include "mozilla/MemoryReporting.h"
48 : #include "mozilla/Telemetry.h"
49 :
50 : static const char DISK_CACHE_DEVICE_ID[] = { "disk" };
51 : using namespace mozilla;
52 :
53 0 : class nsDiskCacheDeviceDeactivateEntryEvent : public Runnable {
54 : public:
55 0 : nsDiskCacheDeviceDeactivateEntryEvent(nsDiskCacheDevice* device,
56 : nsCacheEntry* entry,
57 : nsDiskCacheBinding* binding)
58 0 : : mozilla::Runnable("nsDiskCacheDeviceDeactivateEntryEvent")
59 : , mCanceled(false)
60 : , mEntry(entry)
61 : , mDevice(device)
62 0 : , mBinding(binding)
63 : {
64 0 : }
65 :
66 0 : NS_IMETHOD Run() override
67 : {
68 0 : nsCacheServiceAutoLock lock;
69 0 : CACHE_LOG_DEBUG(("nsDiskCacheDeviceDeactivateEntryEvent[%p]\n", this));
70 0 : if (!mCanceled) {
71 0 : (void) mDevice->DeactivateEntry_Private(mEntry, mBinding);
72 : }
73 0 : return NS_OK;
74 : }
75 :
76 0 : void CancelEvent() { mCanceled = true; }
77 : private:
78 : bool mCanceled;
79 : nsCacheEntry *mEntry;
80 : nsDiskCacheDevice *mDevice;
81 : nsDiskCacheBinding *mBinding;
82 : };
83 :
84 0 : class nsEvictDiskCacheEntriesEvent : public Runnable {
85 : public:
86 0 : explicit nsEvictDiskCacheEntriesEvent(nsDiskCacheDevice* device)
87 0 : : mozilla::Runnable("nsEvictDiskCacheEntriesEvent")
88 0 : , mDevice(device)
89 : {
90 0 : }
91 :
92 0 : NS_IMETHOD Run() override
93 : {
94 0 : nsCacheServiceAutoLock lock;
95 0 : mDevice->EvictDiskCacheEntries(mDevice->mCacheCapacity);
96 0 : return NS_OK;
97 : }
98 :
99 : private:
100 : nsDiskCacheDevice *mDevice;
101 : };
102 :
103 : /******************************************************************************
104 : * nsDiskCacheEvictor
105 : *
106 : * Helper class for nsDiskCacheDevice.
107 : *
108 : *****************************************************************************/
109 :
110 : class nsDiskCacheEvictor : public nsDiskCacheRecordVisitor
111 : {
112 : public:
113 0 : nsDiskCacheEvictor( nsDiskCacheMap * cacheMap,
114 : nsDiskCacheBindery * cacheBindery,
115 : uint32_t targetSize,
116 : const char * clientID)
117 0 : : mCacheMap(cacheMap)
118 : , mBindery(cacheBindery)
119 : , mTargetSize(targetSize)
120 0 : , mClientID(clientID)
121 : {
122 0 : mClientIDSize = clientID ? strlen(clientID) : 0;
123 0 : }
124 :
125 : virtual int32_t VisitRecord(nsDiskCacheRecord * mapRecord);
126 :
127 : private:
128 : nsDiskCacheMap * mCacheMap;
129 : nsDiskCacheBindery * mBindery;
130 : uint32_t mTargetSize;
131 : const char * mClientID;
132 : uint32_t mClientIDSize;
133 : };
134 :
135 :
136 : int32_t
137 0 : nsDiskCacheEvictor::VisitRecord(nsDiskCacheRecord * mapRecord)
138 : {
139 0 : if (mCacheMap->TotalSize() < mTargetSize)
140 0 : return kStopVisitingRecords;
141 :
142 0 : if (mClientID) {
143 : // we're just evicting records for a specific client
144 0 : nsDiskCacheEntry * diskEntry = mCacheMap->ReadDiskCacheEntry(mapRecord);
145 0 : if (!diskEntry)
146 0 : return kVisitNextRecord; // XXX or delete record?
147 :
148 : // Compare clientID's without malloc
149 0 : if ((diskEntry->mKeySize <= mClientIDSize) ||
150 0 : (diskEntry->Key()[mClientIDSize] != ':') ||
151 0 : (memcmp(diskEntry->Key(), mClientID, mClientIDSize) != 0)) {
152 0 : return kVisitNextRecord; // clientID doesn't match, skip it
153 : }
154 : }
155 :
156 0 : nsDiskCacheBinding * binding = mBindery->FindActiveBinding(mapRecord->HashNumber());
157 0 : if (binding) {
158 : // If the entry is pending deactivation, cancel deactivation and doom
159 : // the entry
160 0 : if (binding->mDeactivateEvent) {
161 0 : binding->mDeactivateEvent->CancelEvent();
162 0 : binding->mDeactivateEvent = nullptr;
163 : }
164 : // We are currently using this entry, so all we can do is doom it.
165 : // Since we're enumerating the records, we don't want to call
166 : // DeleteRecord when nsCacheService::DoomEntry() calls us back.
167 0 : binding->mDoomed = true; // mark binding record as 'deleted'
168 0 : nsCacheService::DoomEntry(binding->mCacheEntry);
169 : } else {
170 : // entry not in use, just delete storage because we're enumerating the records
171 0 : (void) mCacheMap->DeleteStorage(mapRecord);
172 : }
173 :
174 0 : return kDeleteRecordAndContinue; // this will REALLY delete the record
175 : }
176 :
177 :
178 : /******************************************************************************
179 : * nsDiskCacheDeviceInfo
180 : *****************************************************************************/
181 :
182 : class nsDiskCacheDeviceInfo : public nsICacheDeviceInfo {
183 : public:
184 : NS_DECL_ISUPPORTS
185 : NS_DECL_NSICACHEDEVICEINFO
186 :
187 0 : explicit nsDiskCacheDeviceInfo(nsDiskCacheDevice* device)
188 0 : : mDevice(device)
189 : {
190 0 : }
191 :
192 : private:
193 0 : virtual ~nsDiskCacheDeviceInfo() {}
194 :
195 : nsDiskCacheDevice* mDevice;
196 : };
197 :
198 0 : NS_IMPL_ISUPPORTS(nsDiskCacheDeviceInfo, nsICacheDeviceInfo)
199 :
200 0 : NS_IMETHODIMP nsDiskCacheDeviceInfo::GetDescription(char ** aDescription)
201 : {
202 0 : NS_ENSURE_ARG_POINTER(aDescription);
203 0 : *aDescription = NS_strdup("Disk cache device");
204 0 : return *aDescription ? NS_OK : NS_ERROR_OUT_OF_MEMORY;
205 : }
206 :
207 0 : NS_IMETHODIMP nsDiskCacheDeviceInfo::GetUsageReport(char ** usageReport)
208 : {
209 0 : NS_ENSURE_ARG_POINTER(usageReport);
210 0 : nsCString buffer;
211 :
212 : buffer.AssignLiteral(" <tr>\n"
213 : " <th>Cache Directory:</th>\n"
214 0 : " <td>");
215 0 : nsCOMPtr<nsIFile> cacheDir;
216 0 : nsAutoString path;
217 0 : mDevice->getCacheDirectory(getter_AddRefs(cacheDir));
218 0 : nsresult rv = cacheDir->GetPath(path);
219 0 : if (NS_SUCCEEDED(rv)) {
220 0 : AppendUTF16toUTF8(path, buffer);
221 : } else {
222 0 : buffer.AppendLiteral("directory unavailable");
223 : }
224 : buffer.AppendLiteral("</td>\n"
225 0 : " </tr>\n");
226 :
227 0 : *usageReport = ToNewCString(buffer);
228 0 : if (!*usageReport) return NS_ERROR_OUT_OF_MEMORY;
229 :
230 0 : return NS_OK;
231 : }
232 :
233 0 : NS_IMETHODIMP nsDiskCacheDeviceInfo::GetEntryCount(uint32_t *aEntryCount)
234 : {
235 0 : NS_ENSURE_ARG_POINTER(aEntryCount);
236 0 : *aEntryCount = mDevice->getEntryCount();
237 0 : return NS_OK;
238 : }
239 :
240 0 : NS_IMETHODIMP nsDiskCacheDeviceInfo::GetTotalSize(uint32_t *aTotalSize)
241 : {
242 0 : NS_ENSURE_ARG_POINTER(aTotalSize);
243 : // Returned unit's are in bytes
244 0 : *aTotalSize = mDevice->getCacheSize() * 1024;
245 0 : return NS_OK;
246 : }
247 :
248 0 : NS_IMETHODIMP nsDiskCacheDeviceInfo::GetMaximumSize(uint32_t *aMaximumSize)
249 : {
250 0 : NS_ENSURE_ARG_POINTER(aMaximumSize);
251 : // Returned unit's are in bytes
252 0 : *aMaximumSize = mDevice->getCacheCapacity() * 1024;
253 0 : return NS_OK;
254 : }
255 :
256 :
257 : /******************************************************************************
258 : * nsDiskCache
259 : *****************************************************************************/
260 :
261 : /**
262 : * nsDiskCache::Hash(const char * key, PLDHashNumber initval)
263 : *
264 : * See http://burtleburtle.net/bob/hash/evahash.html for more information
265 : * about this hash function.
266 : *
267 : * This algorithm of this method implies nsDiskCacheRecords will be stored
268 : * in a certain order on disk. If the algorithm changes, existing cache
269 : * map files may become invalid, and therefore the kCurrentVersion needs
270 : * to be revised.
271 : */
272 :
273 0 : static inline void hashmix(uint32_t& a, uint32_t& b, uint32_t& c)
274 : {
275 0 : a -= b; a -= c; a ^= (c>>13);
276 0 : b -= c; b -= a; b ^= (a<<8);
277 0 : c -= a; c -= b; c ^= (b>>13);
278 0 : a -= b; a -= c; a ^= (c>>12);
279 0 : b -= c; b -= a; b ^= (a<<16);
280 0 : c -= a; c -= b; c ^= (b>>5);
281 0 : a -= b; a -= c; a ^= (c>>3);
282 0 : b -= c; b -= a; b ^= (a<<10);
283 0 : c -= a; c -= b; c ^= (b>>15);
284 0 : }
285 :
286 : PLDHashNumber
287 0 : nsDiskCache::Hash(const char * key, PLDHashNumber initval)
288 : {
289 0 : const uint8_t *k = reinterpret_cast<const uint8_t*>(key);
290 : uint32_t a, b, c, len, length;
291 :
292 0 : length = strlen(key);
293 : /* Set up the internal state */
294 0 : len = length;
295 0 : a = b = 0x9e3779b9; /* the golden ratio; an arbitrary value */
296 0 : c = initval; /* variable initialization of internal state */
297 :
298 : /*---------------------------------------- handle most of the key */
299 0 : while (len >= 12)
300 : {
301 0 : a += k[0] + (uint32_t(k[1])<<8) + (uint32_t(k[2])<<16) + (uint32_t(k[3])<<24);
302 0 : b += k[4] + (uint32_t(k[5])<<8) + (uint32_t(k[6])<<16) + (uint32_t(k[7])<<24);
303 0 : c += k[8] + (uint32_t(k[9])<<8) + (uint32_t(k[10])<<16) + (uint32_t(k[11])<<24);
304 0 : hashmix(a, b, c);
305 0 : k += 12; len -= 12;
306 : }
307 :
308 : /*------------------------------------- handle the last 11 bytes */
309 0 : c += length;
310 0 : switch(len) { /* all the case statements fall through */
311 0 : case 11: c += (uint32_t(k[10])<<24); MOZ_FALLTHROUGH;
312 0 : case 10: c += (uint32_t(k[9])<<16); MOZ_FALLTHROUGH;
313 0 : case 9 : c += (uint32_t(k[8])<<8); MOZ_FALLTHROUGH;
314 : /* the low-order byte of c is reserved for the length */
315 0 : case 8 : b += (uint32_t(k[7])<<24); MOZ_FALLTHROUGH;
316 0 : case 7 : b += (uint32_t(k[6])<<16); MOZ_FALLTHROUGH;
317 0 : case 6 : b += (uint32_t(k[5])<<8); MOZ_FALLTHROUGH;
318 0 : case 5 : b += k[4]; MOZ_FALLTHROUGH;
319 0 : case 4 : a += (uint32_t(k[3])<<24); MOZ_FALLTHROUGH;
320 0 : case 3 : a += (uint32_t(k[2])<<16); MOZ_FALLTHROUGH;
321 0 : case 2 : a += (uint32_t(k[1])<<8); MOZ_FALLTHROUGH;
322 0 : case 1 : a += k[0];
323 : /* case 0: nothing left to add */
324 : }
325 0 : hashmix(a, b, c);
326 :
327 0 : return c;
328 : }
329 :
330 : nsresult
331 0 : nsDiskCache::Truncate(PRFileDesc * fd, uint32_t newEOF)
332 : {
333 : // use modified SetEOF from nsFileStreams::SetEOF()
334 :
335 : #if defined(XP_UNIX)
336 0 : if (ftruncate(PR_FileDesc2NativeHandle(fd), newEOF) != 0) {
337 0 : NS_ERROR("ftruncate failed");
338 0 : return NS_ERROR_FAILURE;
339 : }
340 :
341 : #elif defined(XP_WIN)
342 : int32_t cnt = PR_Seek(fd, newEOF, PR_SEEK_SET);
343 : if (cnt == -1) return NS_ERROR_FAILURE;
344 : if (!SetEndOfFile((HANDLE) PR_FileDesc2NativeHandle(fd))) {
345 : NS_ERROR("SetEndOfFile failed");
346 : return NS_ERROR_FAILURE;
347 : }
348 :
349 : #else
350 : // add implementations for other platforms here
351 : #endif
352 0 : return NS_OK;
353 : }
354 :
355 :
356 : /******************************************************************************
357 : * nsDiskCacheDevice
358 : *****************************************************************************/
359 :
360 0 : nsDiskCacheDevice::nsDiskCacheDevice()
361 : : mCacheCapacity(0)
362 : , mMaxEntrySize(-1) // -1 means "no limit"
363 : , mInitialized(false)
364 0 : , mClearingDiskCache(false)
365 : {
366 0 : }
367 :
368 0 : nsDiskCacheDevice::~nsDiskCacheDevice()
369 : {
370 0 : Shutdown();
371 0 : }
372 :
373 :
374 : /**
375 : * methods of nsCacheDevice
376 : */
377 : nsresult
378 0 : nsDiskCacheDevice::Init()
379 : {
380 : nsresult rv;
381 :
382 0 : if (Initialized()) {
383 0 : NS_ERROR("Disk cache already initialized!");
384 0 : return NS_ERROR_UNEXPECTED;
385 : }
386 :
387 0 : if (!mCacheDirectory)
388 0 : return NS_ERROR_FAILURE;
389 :
390 0 : mBindery.Init();
391 :
392 : // Open Disk Cache
393 0 : rv = OpenDiskCache();
394 0 : if (NS_FAILED(rv)) {
395 0 : (void) mCacheMap.Close(false);
396 0 : return rv;
397 : }
398 :
399 0 : mInitialized = true;
400 0 : return NS_OK;
401 : }
402 :
403 :
404 : /**
405 : * NOTE: called while holding the cache service lock
406 : */
407 : nsresult
408 0 : nsDiskCacheDevice::Shutdown()
409 : {
410 0 : nsCacheService::AssertOwnsLock();
411 :
412 0 : nsresult rv = Shutdown_Private(true);
413 0 : if (NS_FAILED(rv))
414 0 : return rv;
415 :
416 0 : return NS_OK;
417 : }
418 :
419 :
420 : nsresult
421 0 : nsDiskCacheDevice::Shutdown_Private(bool flush)
422 : {
423 0 : CACHE_LOG_DEBUG(("CACHE: disk Shutdown_Private [%u]\n", flush));
424 :
425 0 : if (Initialized()) {
426 : // check cache limits in case we need to evict.
427 0 : EvictDiskCacheEntries(mCacheCapacity);
428 :
429 : // At this point there may be a number of pending cache-requests on the
430 : // cache-io thread. Wait for all these to run before we wipe out our
431 : // datastructures (see bug #620660)
432 0 : (void) nsCacheService::SyncWithCacheIOThread();
433 :
434 : // write out persistent information about the cache.
435 0 : (void) mCacheMap.Close(flush);
436 :
437 0 : mBindery.Reset();
438 :
439 0 : mInitialized = false;
440 : }
441 :
442 0 : return NS_OK;
443 : }
444 :
445 :
446 : const char *
447 0 : nsDiskCacheDevice::GetDeviceID()
448 : {
449 0 : return DISK_CACHE_DEVICE_ID;
450 : }
451 :
452 : /**
453 : * FindEntry -
454 : *
455 : * cases: key not in disk cache, hash number free
456 : * key not in disk cache, hash number used
457 : * key in disk cache
458 : *
459 : * NOTE: called while holding the cache service lock
460 : */
461 : nsCacheEntry *
462 0 : nsDiskCacheDevice::FindEntry(nsCString * key, bool *collision)
463 : {
464 0 : Telemetry::AutoTimer<Telemetry::CACHE_DISK_SEARCH_2> timer;
465 0 : if (!Initialized()) return nullptr; // NS_ERROR_NOT_INITIALIZED
466 0 : if (mClearingDiskCache) return nullptr;
467 0 : nsDiskCacheRecord record;
468 0 : nsDiskCacheBinding * binding = nullptr;
469 0 : PLDHashNumber hashNumber = nsDiskCache::Hash(key->get());
470 :
471 0 : *collision = false;
472 :
473 0 : binding = mBindery.FindActiveBinding(hashNumber);
474 0 : if (binding && !binding->mCacheEntry->Key()->Equals(*key)) {
475 0 : *collision = true;
476 0 : return nullptr;
477 0 : } else if (binding && binding->mDeactivateEvent) {
478 0 : binding->mDeactivateEvent->CancelEvent();
479 0 : binding->mDeactivateEvent = nullptr;
480 0 : CACHE_LOG_DEBUG(("CACHE: reusing deactivated entry %p " \
481 : "req-key=%s entry-key=%s\n",
482 : binding->mCacheEntry, key->get(),
483 : binding->mCacheEntry->Key()->get()));
484 :
485 0 : return binding->mCacheEntry; // just return this one, observing that
486 : // FindActiveBinding() does not return
487 : // bindings to doomed entries
488 : }
489 0 : binding = nullptr;
490 :
491 : // lookup hash number in cache map
492 0 : nsresult rv = mCacheMap.FindRecord(hashNumber, &record);
493 0 : if (NS_FAILED(rv)) return nullptr; // XXX log error?
494 :
495 0 : nsDiskCacheEntry * diskEntry = mCacheMap.ReadDiskCacheEntry(&record);
496 0 : if (!diskEntry) return nullptr;
497 :
498 : // compare key to be sure
499 0 : if (!key->Equals(diskEntry->Key())) {
500 0 : *collision = true;
501 0 : return nullptr;
502 : }
503 :
504 0 : nsCacheEntry * entry = diskEntry->CreateCacheEntry(this);
505 0 : if (entry) {
506 0 : binding = mBindery.CreateBinding(entry, &record);
507 0 : if (!binding) {
508 0 : delete entry;
509 0 : entry = nullptr;
510 : }
511 : }
512 :
513 0 : if (!entry) {
514 0 : (void) mCacheMap.DeleteStorage(&record);
515 0 : (void) mCacheMap.DeleteRecord(&record);
516 : }
517 :
518 0 : return entry;
519 : }
520 :
521 :
522 : /**
523 : * NOTE: called while holding the cache service lock
524 : */
525 : nsresult
526 0 : nsDiskCacheDevice::DeactivateEntry(nsCacheEntry * entry)
527 : {
528 0 : nsDiskCacheBinding * binding = GetCacheEntryBinding(entry);
529 0 : if (!IsValidBinding(binding))
530 0 : return NS_ERROR_UNEXPECTED;
531 :
532 0 : CACHE_LOG_DEBUG(("CACHE: disk DeactivateEntry [%p %x]\n",
533 : entry, binding->mRecord.HashNumber()));
534 :
535 : nsDiskCacheDeviceDeactivateEntryEvent *event =
536 0 : new nsDiskCacheDeviceDeactivateEntryEvent(this, entry, binding);
537 :
538 : // ensure we can cancel the event via the binding later if necessary
539 0 : binding->mDeactivateEvent = event;
540 :
541 0 : DebugOnly<nsresult> rv = nsCacheService::DispatchToCacheIOThread(event);
542 0 : NS_ASSERTION(NS_SUCCEEDED(rv), "DeactivateEntry: Failed dispatching "
543 : "deactivation event");
544 0 : return NS_OK;
545 : }
546 :
547 : /**
548 : * NOTE: called while holding the cache service lock
549 : */
550 : nsresult
551 0 : nsDiskCacheDevice::DeactivateEntry_Private(nsCacheEntry * entry,
552 : nsDiskCacheBinding * binding)
553 : {
554 0 : nsresult rv = NS_OK;
555 0 : if (entry->IsDoomed()) {
556 : // delete data, entry, record from disk for entry
557 0 : rv = mCacheMap.DeleteStorage(&binding->mRecord);
558 :
559 : } else {
560 : // save stuff to disk for entry
561 0 : rv = mCacheMap.WriteDiskCacheEntry(binding);
562 0 : if (NS_FAILED(rv)) {
563 : // clean up as best we can
564 0 : (void) mCacheMap.DeleteStorage(&binding->mRecord);
565 0 : (void) mCacheMap.DeleteRecord(&binding->mRecord);
566 0 : binding->mDoomed = true; // record is no longer in cache map
567 : }
568 : }
569 :
570 0 : mBindery.RemoveBinding(binding); // extract binding from collision detection stuff
571 0 : delete entry; // which will release binding
572 0 : return rv;
573 : }
574 :
575 :
576 : /**
577 : * BindEntry()
578 : * no hash number collision -> no problem
579 : * collision
580 : * record not active -> evict, no problem
581 : * record is active
582 : * record is already doomed -> record shouldn't have been in map, no problem
583 : * record is not doomed -> doom, and replace record in map
584 : *
585 : * walk matching hashnumber list to find lowest generation number
586 : * take generation number from other (data/meta) location,
587 : * or walk active list
588 : *
589 : * NOTE: called while holding the cache service lock
590 : */
591 : nsresult
592 0 : nsDiskCacheDevice::BindEntry(nsCacheEntry * entry)
593 : {
594 0 : if (!Initialized()) return NS_ERROR_NOT_INITIALIZED;
595 0 : if (mClearingDiskCache) return NS_ERROR_NOT_AVAILABLE;
596 0 : nsresult rv = NS_OK;
597 0 : nsDiskCacheRecord record, oldRecord;
598 : nsDiskCacheBinding *binding;
599 0 : PLDHashNumber hashNumber = nsDiskCache::Hash(entry->Key()->get());
600 :
601 : // Find out if there is already an active binding for this hash. If yes it
602 : // should have another key since BindEntry() shouldn't be called twice for
603 : // the same entry. Doom the old entry, the new one will get another
604 : // generation number so files won't collide.
605 0 : binding = mBindery.FindActiveBinding(hashNumber);
606 0 : if (binding) {
607 0 : NS_ASSERTION(!binding->mCacheEntry->Key()->Equals(*entry->Key()),
608 : "BindEntry called for already bound entry!");
609 : // If the entry is pending deactivation, cancel deactivation
610 0 : if (binding->mDeactivateEvent) {
611 0 : binding->mDeactivateEvent->CancelEvent();
612 0 : binding->mDeactivateEvent = nullptr;
613 : }
614 0 : nsCacheService::DoomEntry(binding->mCacheEntry);
615 0 : binding = nullptr;
616 : }
617 :
618 : // Lookup hash number in cache map. There can be a colliding inactive entry.
619 : // See bug #321361 comment 21 for the scenario. If there is such entry,
620 : // delete it.
621 0 : rv = mCacheMap.FindRecord(hashNumber, &record);
622 0 : if (NS_SUCCEEDED(rv)) {
623 0 : nsDiskCacheEntry * diskEntry = mCacheMap.ReadDiskCacheEntry(&record);
624 0 : if (diskEntry) {
625 : // compare key to be sure
626 0 : if (!entry->Key()->Equals(diskEntry->Key())) {
627 0 : mCacheMap.DeleteStorage(&record);
628 0 : rv = mCacheMap.DeleteRecord(&record);
629 0 : if (NS_FAILED(rv)) return rv;
630 : }
631 : }
632 0 : record = nsDiskCacheRecord();
633 : }
634 :
635 : // create a new record for this entry
636 0 : record.SetHashNumber(nsDiskCache::Hash(entry->Key()->get()));
637 0 : record.SetEvictionRank(ULONG_MAX - SecondsFromPRTime(PR_Now()));
638 :
639 0 : CACHE_LOG_DEBUG(("CACHE: disk BindEntry [%p %x]\n",
640 : entry, record.HashNumber()));
641 :
642 0 : if (!entry->IsDoomed()) {
643 : // if entry isn't doomed, add it to the cache map
644 0 : rv = mCacheMap.AddRecord(&record, &oldRecord); // deletes old record, if any
645 0 : if (NS_FAILED(rv)) return rv;
646 :
647 0 : uint32_t oldHashNumber = oldRecord.HashNumber();
648 0 : if (oldHashNumber) {
649 : // gotta evict this one first
650 0 : nsDiskCacheBinding * oldBinding = mBindery.FindActiveBinding(oldHashNumber);
651 0 : if (oldBinding) {
652 : // XXX if debug : compare keys for hashNumber collision
653 :
654 0 : if (!oldBinding->mCacheEntry->IsDoomed()) {
655 : // If the old entry is pending deactivation, cancel deactivation
656 0 : if (oldBinding->mDeactivateEvent) {
657 0 : oldBinding->mDeactivateEvent->CancelEvent();
658 0 : oldBinding->mDeactivateEvent = nullptr;
659 : }
660 : // we've got a live one!
661 0 : nsCacheService::DoomEntry(oldBinding->mCacheEntry);
662 : // storage will be delete when oldBinding->mCacheEntry is Deactivated
663 : }
664 : } else {
665 : // delete storage
666 : // XXX if debug : compare keys for hashNumber collision
667 0 : rv = mCacheMap.DeleteStorage(&oldRecord);
668 0 : if (NS_FAILED(rv)) return rv; // XXX delete record we just added?
669 : }
670 : }
671 : }
672 :
673 : // Make sure this entry has its associated nsDiskCacheBinding attached.
674 0 : binding = mBindery.CreateBinding(entry, &record);
675 0 : NS_ASSERTION(binding, "nsDiskCacheDevice::BindEntry");
676 0 : if (!binding) return NS_ERROR_OUT_OF_MEMORY;
677 0 : NS_ASSERTION(binding->mRecord.ValidRecord(), "bad cache map record");
678 :
679 0 : return NS_OK;
680 : }
681 :
682 :
683 : /**
684 : * NOTE: called while holding the cache service lock
685 : */
686 : void
687 0 : nsDiskCacheDevice::DoomEntry(nsCacheEntry * entry)
688 : {
689 0 : CACHE_LOG_DEBUG(("CACHE: disk DoomEntry [%p]\n", entry));
690 :
691 0 : nsDiskCacheBinding * binding = GetCacheEntryBinding(entry);
692 0 : NS_ASSERTION(binding, "DoomEntry: binding == nullptr");
693 0 : if (!binding)
694 0 : return;
695 :
696 0 : if (!binding->mDoomed) {
697 : // so it can't be seen by FindEntry() ever again.
698 : #ifdef DEBUG
699 : nsresult rv =
700 : #endif
701 0 : mCacheMap.DeleteRecord(&binding->mRecord);
702 0 : NS_ASSERTION(NS_SUCCEEDED(rv),"DeleteRecord failed.");
703 0 : binding->mDoomed = true; // record in no longer in cache map
704 : }
705 : }
706 :
707 :
708 : /**
709 : * NOTE: called while holding the cache service lock
710 : */
711 : nsresult
712 0 : nsDiskCacheDevice::OpenInputStreamForEntry(nsCacheEntry * entry,
713 : nsCacheAccessMode mode,
714 : uint32_t offset,
715 : nsIInputStream ** result)
716 : {
717 0 : CACHE_LOG_DEBUG(("CACHE: disk OpenInputStreamForEntry [%p %x %u]\n",
718 : entry, mode, offset));
719 :
720 0 : NS_ENSURE_ARG_POINTER(entry);
721 0 : NS_ENSURE_ARG_POINTER(result);
722 :
723 : nsresult rv;
724 0 : nsDiskCacheBinding * binding = GetCacheEntryBinding(entry);
725 0 : if (!IsValidBinding(binding))
726 0 : return NS_ERROR_UNEXPECTED;
727 :
728 0 : NS_ASSERTION(binding->mCacheEntry == entry, "binding & entry don't point to each other");
729 :
730 0 : rv = binding->EnsureStreamIO();
731 0 : if (NS_FAILED(rv)) return rv;
732 :
733 0 : return binding->mStreamIO->GetInputStream(offset, result);
734 : }
735 :
736 :
737 : /**
738 : * NOTE: called while holding the cache service lock
739 : */
740 : nsresult
741 0 : nsDiskCacheDevice::OpenOutputStreamForEntry(nsCacheEntry * entry,
742 : nsCacheAccessMode mode,
743 : uint32_t offset,
744 : nsIOutputStream ** result)
745 : {
746 0 : CACHE_LOG_DEBUG(("CACHE: disk OpenOutputStreamForEntry [%p %x %u]\n",
747 : entry, mode, offset));
748 :
749 0 : NS_ENSURE_ARG_POINTER(entry);
750 0 : NS_ENSURE_ARG_POINTER(result);
751 :
752 : nsresult rv;
753 0 : nsDiskCacheBinding * binding = GetCacheEntryBinding(entry);
754 0 : if (!IsValidBinding(binding))
755 0 : return NS_ERROR_UNEXPECTED;
756 :
757 0 : NS_ASSERTION(binding->mCacheEntry == entry, "binding & entry don't point to each other");
758 :
759 0 : rv = binding->EnsureStreamIO();
760 0 : if (NS_FAILED(rv)) return rv;
761 :
762 0 : return binding->mStreamIO->GetOutputStream(offset, result);
763 : }
764 :
765 :
766 : /**
767 : * NOTE: called while holding the cache service lock
768 : */
769 : nsresult
770 0 : nsDiskCacheDevice::GetFileForEntry(nsCacheEntry * entry,
771 : nsIFile ** result)
772 : {
773 0 : NS_ENSURE_ARG_POINTER(result);
774 0 : *result = nullptr;
775 :
776 : nsresult rv;
777 :
778 0 : nsDiskCacheBinding * binding = GetCacheEntryBinding(entry);
779 0 : if (!IsValidBinding(binding))
780 0 : return NS_ERROR_UNEXPECTED;
781 :
782 : // check/set binding->mRecord for separate file, sync w/mCacheMap
783 0 : if (binding->mRecord.DataLocationInitialized()) {
784 0 : if (binding->mRecord.DataFile() != 0)
785 0 : return NS_ERROR_NOT_AVAILABLE; // data not stored as separate file
786 :
787 0 : NS_ASSERTION(binding->mRecord.DataFileGeneration() == binding->mGeneration, "error generations out of sync");
788 : } else {
789 0 : binding->mRecord.SetDataFileGeneration(binding->mGeneration);
790 0 : binding->mRecord.SetDataFileSize(0); // 1k minimum
791 0 : if (!binding->mDoomed) {
792 : // record stored in cache map, so update it
793 0 : rv = mCacheMap.UpdateRecord(&binding->mRecord);
794 0 : if (NS_FAILED(rv)) return rv;
795 : }
796 : }
797 :
798 0 : nsCOMPtr<nsIFile> file;
799 0 : rv = mCacheMap.GetFileForDiskCacheRecord(&binding->mRecord,
800 : nsDiskCache::kData,
801 : false,
802 0 : getter_AddRefs(file));
803 0 : if (NS_FAILED(rv)) return rv;
804 :
805 0 : NS_IF_ADDREF(*result = file);
806 0 : return NS_OK;
807 : }
808 :
809 :
810 : /**
811 : * This routine will get called every time an open descriptor is written to.
812 : *
813 : * NOTE: called while holding the cache service lock
814 : */
815 : nsresult
816 0 : nsDiskCacheDevice::OnDataSizeChange(nsCacheEntry * entry, int32_t deltaSize)
817 : {
818 0 : CACHE_LOG_DEBUG(("CACHE: disk OnDataSizeChange [%p %d]\n",
819 : entry, deltaSize));
820 :
821 : // If passed a negative value, then there's nothing to do.
822 0 : if (deltaSize < 0)
823 0 : return NS_OK;
824 :
825 0 : nsDiskCacheBinding * binding = GetCacheEntryBinding(entry);
826 0 : if (!IsValidBinding(binding))
827 0 : return NS_ERROR_UNEXPECTED;
828 :
829 0 : NS_ASSERTION(binding->mRecord.ValidRecord(), "bad record");
830 :
831 0 : uint32_t newSize = entry->DataSize() + deltaSize;
832 0 : uint32_t newSizeK = ((newSize + 0x3FF) >> 10);
833 :
834 : // If the new size is larger than max. file size or larger than
835 : // 1/8 the cache capacity (which is in KiB's), doom the entry and abort.
836 0 : if (EntryIsTooBig(newSize)) {
837 : #ifdef DEBUG
838 : nsresult rv =
839 : #endif
840 0 : nsCacheService::DoomEntry(entry);
841 0 : NS_ASSERTION(NS_SUCCEEDED(rv),"DoomEntry() failed.");
842 0 : return NS_ERROR_ABORT;
843 : }
844 :
845 0 : uint32_t sizeK = ((entry->DataSize() + 0x03FF) >> 10); // round up to next 1k
846 :
847 : // In total count we ignore anything over kMaxDataSizeK (bug #651100), so
848 : // the target capacity should be calculated the same way.
849 0 : if (sizeK > kMaxDataSizeK) sizeK = kMaxDataSizeK;
850 0 : if (newSizeK > kMaxDataSizeK) newSizeK = kMaxDataSizeK;
851 :
852 : // pre-evict entries to make space for new data
853 0 : uint32_t targetCapacity = mCacheCapacity > (newSizeK - sizeK)
854 0 : ? mCacheCapacity - (newSizeK - sizeK)
855 0 : : 0;
856 0 : EvictDiskCacheEntries(targetCapacity);
857 :
858 0 : return NS_OK;
859 : }
860 :
861 :
862 : /******************************************************************************
863 : * EntryInfoVisitor
864 : *****************************************************************************/
865 : class EntryInfoVisitor : public nsDiskCacheRecordVisitor
866 : {
867 : public:
868 0 : EntryInfoVisitor(nsDiskCacheMap * cacheMap,
869 : nsICacheVisitor * visitor)
870 0 : : mCacheMap(cacheMap)
871 0 : , mVisitor(visitor)
872 0 : {}
873 :
874 0 : virtual int32_t VisitRecord(nsDiskCacheRecord * mapRecord)
875 : {
876 : // XXX optimization: do we have this record in memory?
877 :
878 : // read in the entry (metadata)
879 0 : nsDiskCacheEntry * diskEntry = mCacheMap->ReadDiskCacheEntry(mapRecord);
880 0 : if (!diskEntry) {
881 0 : return kVisitNextRecord;
882 : }
883 :
884 : // create nsICacheEntryInfo
885 0 : nsDiskCacheEntryInfo * entryInfo = new nsDiskCacheEntryInfo(DISK_CACHE_DEVICE_ID, diskEntry);
886 0 : if (!entryInfo) {
887 0 : return kStopVisitingRecords;
888 : }
889 0 : nsCOMPtr<nsICacheEntryInfo> ref(entryInfo);
890 :
891 : bool keepGoing;
892 0 : (void)mVisitor->VisitEntry(DISK_CACHE_DEVICE_ID, entryInfo, &keepGoing);
893 0 : return keepGoing ? kVisitNextRecord : kStopVisitingRecords;
894 : }
895 :
896 : private:
897 : nsDiskCacheMap * mCacheMap;
898 : nsICacheVisitor * mVisitor;
899 : };
900 :
901 :
902 : nsresult
903 0 : nsDiskCacheDevice::Visit(nsICacheVisitor * visitor)
904 : {
905 0 : if (!Initialized()) return NS_ERROR_NOT_INITIALIZED;
906 0 : nsDiskCacheDeviceInfo* deviceInfo = new nsDiskCacheDeviceInfo(this);
907 0 : nsCOMPtr<nsICacheDeviceInfo> ref(deviceInfo);
908 :
909 : bool keepGoing;
910 0 : nsresult rv = visitor->VisitDevice(DISK_CACHE_DEVICE_ID, deviceInfo, &keepGoing);
911 0 : if (NS_FAILED(rv)) return rv;
912 :
913 0 : if (keepGoing) {
914 0 : EntryInfoVisitor infoVisitor(&mCacheMap, visitor);
915 0 : return mCacheMap.VisitRecords(&infoVisitor);
916 : }
917 :
918 0 : return NS_OK;
919 : }
920 :
921 : // Max allowed size for an entry is currently MIN(mMaxEntrySize, 1/8 CacheCapacity)
922 : bool
923 0 : nsDiskCacheDevice::EntryIsTooBig(int64_t entrySize)
924 : {
925 0 : if (mMaxEntrySize == -1) // no limit
926 0 : return entrySize > (static_cast<int64_t>(mCacheCapacity) * 1024 / 8);
927 : else
928 0 : return entrySize > mMaxEntrySize ||
929 0 : entrySize > (static_cast<int64_t>(mCacheCapacity) * 1024 / 8);
930 : }
931 :
932 : nsresult
933 0 : nsDiskCacheDevice::EvictEntries(const char * clientID)
934 : {
935 0 : CACHE_LOG_DEBUG(("CACHE: disk EvictEntries [%s]\n", clientID));
936 :
937 0 : if (!Initialized()) return NS_ERROR_NOT_INITIALIZED;
938 : nsresult rv;
939 :
940 0 : if (clientID == nullptr) {
941 : // we're clearing the entire disk cache
942 0 : rv = ClearDiskCache();
943 0 : if (rv != NS_ERROR_CACHE_IN_USE)
944 0 : return rv;
945 : }
946 :
947 0 : nsDiskCacheEvictor evictor(&mCacheMap, &mBindery, 0, clientID);
948 0 : rv = mCacheMap.VisitRecords(&evictor);
949 :
950 0 : if (clientID == nullptr) // we tried to clear the entire cache
951 0 : rv = mCacheMap.Trim(); // so trim cache block files (if possible)
952 0 : return rv;
953 : }
954 :
955 :
956 : /**
957 : * private methods
958 : */
959 :
960 : nsresult
961 0 : nsDiskCacheDevice::OpenDiskCache()
962 : {
963 0 : Telemetry::AutoTimer<Telemetry::NETWORK_DISK_CACHE_OPEN> timer;
964 : // if we don't have a cache directory, create one and open it
965 : bool exists;
966 0 : nsresult rv = mCacheDirectory->Exists(&exists);
967 0 : if (NS_FAILED(rv))
968 0 : return rv;
969 :
970 0 : if (exists) {
971 : // Try opening cache map file.
972 : nsDiskCache::CorruptCacheInfo corruptInfo;
973 0 : rv = mCacheMap.Open(mCacheDirectory, &corruptInfo);
974 :
975 0 : if (rv == NS_ERROR_ALREADY_INITIALIZED) {
976 0 : NS_WARNING("nsDiskCacheDevice::OpenDiskCache: already open!");
977 0 : } else if (NS_FAILED(rv)) {
978 : // Consider cache corrupt: delete it
979 : // delay delete by 1 minute to avoid IO thrash at startup
980 0 : rv = nsDeleteDir::DeleteDir(mCacheDirectory, true, 60000);
981 0 : if (NS_FAILED(rv))
982 0 : return rv;
983 0 : exists = false;
984 : }
985 : }
986 :
987 : // if we don't have a cache directory, create one and open it
988 0 : if (!exists) {
989 0 : nsCacheService::MarkStartingFresh();
990 0 : rv = mCacheDirectory->Create(nsIFile::DIRECTORY_TYPE, 0777);
991 0 : CACHE_LOG_PATH(LogLevel::Info, "\ncreate cache directory: %s\n", mCacheDirectory);
992 0 : CACHE_LOG_INFO(("mCacheDirectory->Create() = %" PRIx32 "\n",
993 : static_cast<uint32_t>(rv)));
994 0 : if (NS_FAILED(rv))
995 0 : return rv;
996 :
997 : // reopen the cache map
998 : nsDiskCache::CorruptCacheInfo corruptInfo;
999 0 : rv = mCacheMap.Open(mCacheDirectory, &corruptInfo);
1000 0 : if (NS_FAILED(rv))
1001 0 : return rv;
1002 : }
1003 :
1004 0 : return NS_OK;
1005 : }
1006 :
1007 :
1008 : nsresult
1009 0 : nsDiskCacheDevice::ClearDiskCache()
1010 : {
1011 0 : if (mBindery.ActiveBindings())
1012 0 : return NS_ERROR_CACHE_IN_USE;
1013 :
1014 0 : mClearingDiskCache = true;
1015 :
1016 0 : nsresult rv = Shutdown_Private(false); // false: don't bother flushing
1017 0 : if (NS_FAILED(rv))
1018 0 : return rv;
1019 :
1020 0 : mClearingDiskCache = false;
1021 :
1022 : // If the disk cache directory is already gone, then it's not an error if
1023 : // we fail to delete it ;-)
1024 0 : rv = nsDeleteDir::DeleteDir(mCacheDirectory, true);
1025 0 : if (NS_FAILED(rv) && rv != NS_ERROR_FILE_TARGET_DOES_NOT_EXIST)
1026 0 : return rv;
1027 :
1028 0 : return Init();
1029 : }
1030 :
1031 :
1032 : nsresult
1033 0 : nsDiskCacheDevice::EvictDiskCacheEntries(uint32_t targetCapacity)
1034 : {
1035 0 : CACHE_LOG_DEBUG(("CACHE: disk EvictDiskCacheEntries [%u]\n",
1036 : targetCapacity));
1037 :
1038 0 : NS_ASSERTION(targetCapacity > 0, "oops");
1039 :
1040 0 : if (mCacheMap.TotalSize() < targetCapacity)
1041 0 : return NS_OK;
1042 :
1043 : // targetCapacity is in KiB's
1044 0 : nsDiskCacheEvictor evictor(&mCacheMap, &mBindery, targetCapacity, nullptr);
1045 0 : return mCacheMap.EvictRecords(&evictor);
1046 : }
1047 :
1048 :
1049 : /**
1050 : * methods for prefs
1051 : */
1052 :
1053 : void
1054 0 : nsDiskCacheDevice::SetCacheParentDirectory(nsIFile * parentDir)
1055 : {
1056 : nsresult rv;
1057 : bool exists;
1058 :
1059 0 : if (Initialized()) {
1060 0 : NS_ASSERTION(false, "Cannot switch cache directory when initialized");
1061 0 : return;
1062 : }
1063 :
1064 0 : if (!parentDir) {
1065 0 : mCacheDirectory = nullptr;
1066 0 : return;
1067 : }
1068 :
1069 : // ensure parent directory exists
1070 0 : rv = parentDir->Exists(&exists);
1071 0 : if (NS_SUCCEEDED(rv) && !exists)
1072 0 : rv = parentDir->Create(nsIFile::DIRECTORY_TYPE, 0700);
1073 0 : if (NS_FAILED(rv)) return;
1074 :
1075 : // ensure cache directory exists
1076 0 : nsCOMPtr<nsIFile> directory;
1077 :
1078 0 : rv = parentDir->Clone(getter_AddRefs(directory));
1079 0 : if (NS_FAILED(rv)) return;
1080 0 : rv = directory->AppendNative(NS_LITERAL_CSTRING("Cache"));
1081 0 : if (NS_FAILED(rv)) return;
1082 :
1083 0 : mCacheDirectory = do_QueryInterface(directory);
1084 : }
1085 :
1086 :
1087 : void
1088 0 : nsDiskCacheDevice::getCacheDirectory(nsIFile ** result)
1089 : {
1090 0 : *result = mCacheDirectory;
1091 0 : NS_IF_ADDREF(*result);
1092 0 : }
1093 :
1094 :
1095 : /**
1096 : * NOTE: called while holding the cache service lock
1097 : */
1098 : void
1099 0 : nsDiskCacheDevice::SetCapacity(uint32_t capacity)
1100 : {
1101 : // Units are KiB's
1102 0 : mCacheCapacity = capacity;
1103 0 : if (Initialized()) {
1104 0 : if (NS_IsMainThread()) {
1105 : // Do not evict entries on the main thread
1106 : nsCacheService::DispatchToCacheIOThread(
1107 0 : new nsEvictDiskCacheEntriesEvent(this));
1108 : } else {
1109 : // start evicting entries if the new size is smaller!
1110 0 : EvictDiskCacheEntries(mCacheCapacity);
1111 : }
1112 : }
1113 : // Let cache map know of the new capacity
1114 0 : mCacheMap.NotifyCapacityChange(capacity);
1115 0 : }
1116 :
1117 :
1118 0 : uint32_t nsDiskCacheDevice::getCacheCapacity()
1119 : {
1120 0 : return mCacheCapacity;
1121 : }
1122 :
1123 :
1124 0 : uint32_t nsDiskCacheDevice::getCacheSize()
1125 : {
1126 0 : return mCacheMap.TotalSize();
1127 : }
1128 :
1129 :
1130 0 : uint32_t nsDiskCacheDevice::getEntryCount()
1131 : {
1132 0 : return mCacheMap.EntryCount();
1133 : }
1134 :
1135 : void
1136 0 : nsDiskCacheDevice::SetMaxEntrySize(int32_t maxSizeInKilobytes)
1137 : {
1138 : // Internal units are bytes. Changing this only takes effect *after* the
1139 : // change and has no consequences for existing cache-entries
1140 0 : if (maxSizeInKilobytes >= 0)
1141 0 : mMaxEntrySize = maxSizeInKilobytes * 1024;
1142 : else
1143 0 : mMaxEntrySize = -1;
1144 0 : }
1145 :
1146 : size_t
1147 0 : nsDiskCacheDevice::SizeOfIncludingThis(MallocSizeOf aMallocSizeOf)
1148 : {
1149 0 : size_t usage = aMallocSizeOf(this);
1150 :
1151 0 : usage += mCacheMap.SizeOfExcludingThis(aMallocSizeOf);
1152 0 : usage += mBindery.SizeOfExcludingThis(aMallocSizeOf);
1153 :
1154 0 : return usage;
1155 : }
|