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