Coverage Report

Created: 2018-09-25 14:53

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