netwerk/cache/nsDiskCacheDevice.cpp

changeset 0
6474c204b198
     1.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     1.2 +++ b/netwerk/cache/nsDiskCacheDevice.cpp	Wed Dec 31 06:09:35 2014 +0100
     1.3 @@ -0,0 +1,1166 @@
     1.4 +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
     1.5 + *
     1.6 + * This Source Code Form is subject to the terms of the Mozilla Public
     1.7 + * License, v. 2.0. If a copy of the MPL was not distributed with this
     1.8 + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
     1.9 +
    1.10 +#include <limits.h>
    1.11 +
    1.12 +#include "mozilla/DebugOnly.h"
    1.13 +
    1.14 +#include "nsCache.h"
    1.15 +#include "nsIMemoryReporter.h"
    1.16 +
    1.17 +// include files for ftruncate (or equivalent)
    1.18 +#if defined(XP_UNIX)
    1.19 +#include <unistd.h>
    1.20 +#elif defined(XP_WIN)
    1.21 +#include <windows.h>
    1.22 +#else
    1.23 +// XXX add necessary include file for ftruncate (or equivalent)
    1.24 +#endif
    1.25 +
    1.26 +#include "prthread.h"
    1.27 +
    1.28 +#include "private/pprio.h"
    1.29 +
    1.30 +#include "nsDiskCacheDevice.h"
    1.31 +#include "nsDiskCacheEntry.h"
    1.32 +#include "nsDiskCacheMap.h"
    1.33 +#include "nsDiskCacheStreams.h"
    1.34 +
    1.35 +#include "nsDiskCache.h"
    1.36 +
    1.37 +#include "nsCacheService.h"
    1.38 +
    1.39 +#include "nsDeleteDir.h"
    1.40 +
    1.41 +#include "nsICacheVisitor.h"
    1.42 +#include "nsReadableUtils.h"
    1.43 +#include "nsIInputStream.h"
    1.44 +#include "nsIOutputStream.h"
    1.45 +#include "nsCRT.h"
    1.46 +#include "nsCOMArray.h"
    1.47 +#include "nsISimpleEnumerator.h"
    1.48 +
    1.49 +#include "nsThreadUtils.h"
    1.50 +#include "mozilla/MemoryReporting.h"
    1.51 +#include "mozilla/Telemetry.h"
    1.52 +
    1.53 +static const char DISK_CACHE_DEVICE_ID[] = { "disk" };
    1.54 +using namespace mozilla;
    1.55 +
    1.56 +class nsDiskCacheDeviceDeactivateEntryEvent : public nsRunnable {
    1.57 +public:
    1.58 +    nsDiskCacheDeviceDeactivateEntryEvent(nsDiskCacheDevice *device,
    1.59 +                                          nsCacheEntry * entry,
    1.60 +                                          nsDiskCacheBinding * binding)
    1.61 +        : mCanceled(false),
    1.62 +          mEntry(entry),
    1.63 +          mDevice(device),
    1.64 +          mBinding(binding)
    1.65 +    {
    1.66 +    }
    1.67 +
    1.68 +    NS_IMETHOD Run()
    1.69 +    {
    1.70 +        nsCacheServiceAutoLock lock(LOCK_TELEM(NSDISKCACHEDEVICEDEACTIVATEENTRYEVENT_RUN));
    1.71 +#ifdef PR_LOGGING
    1.72 +        CACHE_LOG_DEBUG(("nsDiskCacheDeviceDeactivateEntryEvent[%p]\n", this));
    1.73 +#endif
    1.74 +        if (!mCanceled) {
    1.75 +            (void) mDevice->DeactivateEntry_Private(mEntry, mBinding);
    1.76 +        }
    1.77 +        return NS_OK;
    1.78 +    }
    1.79 +
    1.80 +    void CancelEvent() { mCanceled = true; }
    1.81 +private:
    1.82 +    bool mCanceled;
    1.83 +    nsCacheEntry *mEntry;
    1.84 +    nsDiskCacheDevice *mDevice;
    1.85 +    nsDiskCacheBinding *mBinding;
    1.86 +};
    1.87 +
    1.88 +class nsEvictDiskCacheEntriesEvent : public nsRunnable {
    1.89 +public:
    1.90 +    nsEvictDiskCacheEntriesEvent(nsDiskCacheDevice *device)
    1.91 +        : mDevice(device) {}
    1.92 +
    1.93 +    NS_IMETHOD Run()
    1.94 +    {
    1.95 +        nsCacheServiceAutoLock lock(LOCK_TELEM(NSEVICTDISKCACHEENTRIESEVENT_RUN));
    1.96 +        mDevice->EvictDiskCacheEntries(mDevice->mCacheCapacity);
    1.97 +        return NS_OK;
    1.98 +    }
    1.99 +
   1.100 +private:
   1.101 +    nsDiskCacheDevice *mDevice;
   1.102 +};
   1.103 +
   1.104 +/******************************************************************************
   1.105 + *  nsDiskCacheEvictor
   1.106 + *
   1.107 + *  Helper class for nsDiskCacheDevice.
   1.108 + *
   1.109 + *****************************************************************************/
   1.110 +
   1.111 +class nsDiskCacheEvictor : public nsDiskCacheRecordVisitor
   1.112 +{
   1.113 +public:
   1.114 +    nsDiskCacheEvictor( nsDiskCacheMap *      cacheMap,
   1.115 +                        nsDiskCacheBindery *  cacheBindery,
   1.116 +                        uint32_t              targetSize,
   1.117 +                        const char *          clientID)
   1.118 +        : mCacheMap(cacheMap)
   1.119 +        , mBindery(cacheBindery)
   1.120 +        , mTargetSize(targetSize)
   1.121 +        , mClientID(clientID)
   1.122 +    { 
   1.123 +        mClientIDSize = clientID ? strlen(clientID) : 0;
   1.124 +    }
   1.125 +    
   1.126 +    virtual int32_t  VisitRecord(nsDiskCacheRecord *  mapRecord);
   1.127 + 
   1.128 +private:
   1.129 +        nsDiskCacheMap *     mCacheMap;
   1.130 +        nsDiskCacheBindery * mBindery;
   1.131 +        uint32_t             mTargetSize;
   1.132 +        const char *         mClientID;
   1.133 +        uint32_t             mClientIDSize;
   1.134 +};
   1.135 +
   1.136 +
   1.137 +int32_t
   1.138 +nsDiskCacheEvictor::VisitRecord(nsDiskCacheRecord *  mapRecord)
   1.139 +{
   1.140 +    if (mCacheMap->TotalSize() < mTargetSize)
   1.141 +        return kStopVisitingRecords;
   1.142 +    
   1.143 +    if (mClientID) {
   1.144 +        // we're just evicting records for a specific client
   1.145 +        nsDiskCacheEntry * diskEntry = mCacheMap->ReadDiskCacheEntry(mapRecord);
   1.146 +        if (!diskEntry)
   1.147 +            return kVisitNextRecord;  // XXX or delete record?
   1.148 +    
   1.149 +        // Compare clientID's without malloc
   1.150 +        if ((diskEntry->mKeySize <= mClientIDSize) ||
   1.151 +            (diskEntry->Key()[mClientIDSize] != ':') ||
   1.152 +            (memcmp(diskEntry->Key(), mClientID, mClientIDSize) != 0)) {
   1.153 +            return kVisitNextRecord;  // clientID doesn't match, skip it
   1.154 +        }
   1.155 +    }
   1.156 +    
   1.157 +    nsDiskCacheBinding * binding = mBindery->FindActiveBinding(mapRecord->HashNumber());
   1.158 +    if (binding) {
   1.159 +        // If the entry is pending deactivation, cancel deactivation and doom
   1.160 +        // the entry
   1.161 +        if (binding->mDeactivateEvent) {
   1.162 +            binding->mDeactivateEvent->CancelEvent();
   1.163 +            binding->mDeactivateEvent = nullptr;
   1.164 +        }
   1.165 +        // We are currently using this entry, so all we can do is doom it.
   1.166 +        // Since we're enumerating the records, we don't want to call
   1.167 +        // DeleteRecord when nsCacheService::DoomEntry() calls us back.
   1.168 +        binding->mDoomed = true;         // mark binding record as 'deleted'
   1.169 +        nsCacheService::DoomEntry(binding->mCacheEntry);
   1.170 +    } else {
   1.171 +        // entry not in use, just delete storage because we're enumerating the records
   1.172 +        (void) mCacheMap->DeleteStorage(mapRecord);
   1.173 +    }
   1.174 +
   1.175 +    return kDeleteRecordAndContinue;  // this will REALLY delete the record
   1.176 +}
   1.177 +
   1.178 +
   1.179 +/******************************************************************************
   1.180 + *  nsDiskCacheDeviceInfo
   1.181 + *****************************************************************************/
   1.182 +
   1.183 +class nsDiskCacheDeviceInfo : public nsICacheDeviceInfo {
   1.184 +public:
   1.185 +    NS_DECL_ISUPPORTS
   1.186 +    NS_DECL_NSICACHEDEVICEINFO
   1.187 +
   1.188 +    nsDiskCacheDeviceInfo(nsDiskCacheDevice* device)
   1.189 +        :   mDevice(device)
   1.190 +    {
   1.191 +    }
   1.192 +
   1.193 +    virtual ~nsDiskCacheDeviceInfo() {}
   1.194 +    
   1.195 +private:
   1.196 +    nsDiskCacheDevice* mDevice;
   1.197 +};
   1.198 +
   1.199 +NS_IMPL_ISUPPORTS(nsDiskCacheDeviceInfo, nsICacheDeviceInfo)
   1.200 +
   1.201 +/* readonly attribute string description; */
   1.202 +NS_IMETHODIMP nsDiskCacheDeviceInfo::GetDescription(char ** aDescription)
   1.203 +{
   1.204 +    NS_ENSURE_ARG_POINTER(aDescription);
   1.205 +    *aDescription = NS_strdup("Disk cache device");
   1.206 +    return *aDescription ? NS_OK : NS_ERROR_OUT_OF_MEMORY;
   1.207 +}
   1.208 +
   1.209 +/* readonly attribute string usageReport; */
   1.210 +NS_IMETHODIMP nsDiskCacheDeviceInfo::GetUsageReport(char ** usageReport)
   1.211 +{
   1.212 +    NS_ENSURE_ARG_POINTER(usageReport);
   1.213 +    nsCString buffer;
   1.214 +    
   1.215 +    buffer.AssignLiteral("  <tr>\n"
   1.216 +                         "    <th>Cache Directory:</th>\n"
   1.217 +                         "    <td>");
   1.218 +    nsCOMPtr<nsIFile> cacheDir;
   1.219 +    nsAutoString path;
   1.220 +    mDevice->getCacheDirectory(getter_AddRefs(cacheDir)); 
   1.221 +    nsresult rv = cacheDir->GetPath(path);
   1.222 +    if (NS_SUCCEEDED(rv)) {
   1.223 +        AppendUTF16toUTF8(path, buffer);
   1.224 +    } else {
   1.225 +        buffer.AppendLiteral("directory unavailable");
   1.226 +    }
   1.227 +    buffer.AppendLiteral("</td>\n"
   1.228 +                         "  </tr>\n");
   1.229 +
   1.230 +    *usageReport = ToNewCString(buffer);
   1.231 +    if (!*usageReport) return NS_ERROR_OUT_OF_MEMORY;
   1.232 +
   1.233 +    return NS_OK;
   1.234 +}
   1.235 +
   1.236 +/* readonly attribute unsigned long entryCount; */
   1.237 +NS_IMETHODIMP nsDiskCacheDeviceInfo::GetEntryCount(uint32_t *aEntryCount)
   1.238 +{
   1.239 +    NS_ENSURE_ARG_POINTER(aEntryCount);
   1.240 +    *aEntryCount = mDevice->getEntryCount();
   1.241 +    return NS_OK;
   1.242 +}
   1.243 +
   1.244 +/* readonly attribute unsigned long totalSize; */
   1.245 +NS_IMETHODIMP nsDiskCacheDeviceInfo::GetTotalSize(uint32_t *aTotalSize)
   1.246 +{
   1.247 +    NS_ENSURE_ARG_POINTER(aTotalSize);
   1.248 +    // Returned unit's are in bytes
   1.249 +    *aTotalSize = mDevice->getCacheSize() * 1024;
   1.250 +    return NS_OK;
   1.251 +}
   1.252 +
   1.253 +/* readonly attribute unsigned long maximumSize; */
   1.254 +NS_IMETHODIMP nsDiskCacheDeviceInfo::GetMaximumSize(uint32_t *aMaximumSize)
   1.255 +{
   1.256 +    NS_ENSURE_ARG_POINTER(aMaximumSize);
   1.257 +    // Returned unit's are in bytes
   1.258 +    *aMaximumSize = mDevice->getCacheCapacity() * 1024;
   1.259 +    return NS_OK;
   1.260 +}
   1.261 +
   1.262 +
   1.263 +/******************************************************************************
   1.264 + *  nsDiskCache
   1.265 + *****************************************************************************/
   1.266 +
   1.267 +/**
   1.268 + *  nsDiskCache::Hash(const char * key, PLDHashNumber initval)
   1.269 + *
   1.270 + *  See http://burtleburtle.net/bob/hash/evahash.html for more information
   1.271 + *  about this hash function.
   1.272 + *
   1.273 + *  This algorithm of this method implies nsDiskCacheRecords will be stored
   1.274 + *  in a certain order on disk.  If the algorithm changes, existing cache
   1.275 + *  map files may become invalid, and therefore the kCurrentVersion needs
   1.276 + *  to be revised.
   1.277 + */
   1.278 +
   1.279 +static inline void hashmix(uint32_t& a, uint32_t& b, uint32_t& c)
   1.280 +{
   1.281 +  a -= b; a -= c; a ^= (c>>13);
   1.282 +  b -= c; b -= a; b ^= (a<<8);
   1.283 +  c -= a; c -= b; c ^= (b>>13);
   1.284 +  a -= b; a -= c; a ^= (c>>12); 
   1.285 +  b -= c; b -= a; b ^= (a<<16);
   1.286 +  c -= a; c -= b; c ^= (b>>5);
   1.287 +  a -= b; a -= c; a ^= (c>>3);
   1.288 +  b -= c; b -= a; b ^= (a<<10);
   1.289 +  c -= a; c -= b; c ^= (b>>15);
   1.290 +}
   1.291 +
   1.292 +PLDHashNumber
   1.293 +nsDiskCache::Hash(const char * key, PLDHashNumber initval)
   1.294 +{
   1.295 +  const uint8_t *k = reinterpret_cast<const uint8_t*>(key);
   1.296 +  uint32_t a, b, c, len, length;
   1.297 +
   1.298 +  length = strlen(key);
   1.299 +  /* Set up the internal state */
   1.300 +  len = length;
   1.301 +  a = b = 0x9e3779b9;  /* the golden ratio; an arbitrary value */
   1.302 +  c = initval;         /* variable initialization of internal state */
   1.303 +
   1.304 +  /*---------------------------------------- handle most of the key */
   1.305 +  while (len >= 12)
   1.306 +  {
   1.307 +    a += k[0] + (uint32_t(k[1])<<8) + (uint32_t(k[2])<<16) + (uint32_t(k[3])<<24);
   1.308 +    b += k[4] + (uint32_t(k[5])<<8) + (uint32_t(k[6])<<16) + (uint32_t(k[7])<<24);
   1.309 +    c += k[8] + (uint32_t(k[9])<<8) + (uint32_t(k[10])<<16) + (uint32_t(k[11])<<24);
   1.310 +    hashmix(a, b, c);
   1.311 +    k += 12; len -= 12;
   1.312 +  }
   1.313 +
   1.314 +  /*------------------------------------- handle the last 11 bytes */
   1.315 +  c += length;
   1.316 +  switch(len) {              /* all the case statements fall through */
   1.317 +    case 11: c += (uint32_t(k[10])<<24);
   1.318 +    case 10: c += (uint32_t(k[9])<<16);
   1.319 +    case 9 : c += (uint32_t(k[8])<<8);
   1.320 +    /* the low-order byte of c is reserved for the length */
   1.321 +    case 8 : b += (uint32_t(k[7])<<24);
   1.322 +    case 7 : b += (uint32_t(k[6])<<16);
   1.323 +    case 6 : b += (uint32_t(k[5])<<8);
   1.324 +    case 5 : b += k[4];
   1.325 +    case 4 : a += (uint32_t(k[3])<<24);
   1.326 +    case 3 : a += (uint32_t(k[2])<<16);
   1.327 +    case 2 : a += (uint32_t(k[1])<<8);
   1.328 +    case 1 : a += k[0];
   1.329 +    /* case 0: nothing left to add */
   1.330 +  }
   1.331 +  hashmix(a, b, c);
   1.332 +
   1.333 +  return c;
   1.334 +}
   1.335 +
   1.336 +nsresult
   1.337 +nsDiskCache::Truncate(PRFileDesc *  fd, uint32_t  newEOF)
   1.338 +{
   1.339 +    // use modified SetEOF from nsFileStreams::SetEOF()
   1.340 +
   1.341 +#if defined(XP_UNIX)
   1.342 +    if (ftruncate(PR_FileDesc2NativeHandle(fd), newEOF) != 0) {
   1.343 +        NS_ERROR("ftruncate failed");
   1.344 +        return NS_ERROR_FAILURE;
   1.345 +    }
   1.346 +
   1.347 +#elif defined(XP_WIN)
   1.348 +    int32_t cnt = PR_Seek(fd, newEOF, PR_SEEK_SET);
   1.349 +    if (cnt == -1)  return NS_ERROR_FAILURE;
   1.350 +    if (!SetEndOfFile((HANDLE) PR_FileDesc2NativeHandle(fd))) {
   1.351 +        NS_ERROR("SetEndOfFile failed");
   1.352 +        return NS_ERROR_FAILURE;
   1.353 +    }
   1.354 +
   1.355 +#else
   1.356 +    // add implementations for other platforms here
   1.357 +#endif
   1.358 +    return NS_OK;
   1.359 +}
   1.360 +
   1.361 +
   1.362 +/******************************************************************************
   1.363 + *  nsDiskCacheDevice
   1.364 + *****************************************************************************/
   1.365 +
   1.366 +nsDiskCacheDevice::nsDiskCacheDevice()
   1.367 +    : mCacheCapacity(0)
   1.368 +    , mMaxEntrySize(-1) // -1 means "no limit"
   1.369 +    , mInitialized(false)
   1.370 +    , mClearingDiskCache(false)
   1.371 +{
   1.372 +}
   1.373 +
   1.374 +nsDiskCacheDevice::~nsDiskCacheDevice()
   1.375 +{
   1.376 +    Shutdown();
   1.377 +}
   1.378 +
   1.379 +
   1.380 +/**
   1.381 + *  methods of nsCacheDevice
   1.382 + */
   1.383 +nsresult
   1.384 +nsDiskCacheDevice::Init()
   1.385 +{
   1.386 +    nsresult rv;
   1.387 +
   1.388 +    if (Initialized()) {
   1.389 +        NS_ERROR("Disk cache already initialized!");
   1.390 +        return NS_ERROR_UNEXPECTED;
   1.391 +    }
   1.392 +       
   1.393 +    if (!mCacheDirectory)
   1.394 +        return NS_ERROR_FAILURE;
   1.395 +
   1.396 +    rv = mBindery.Init();
   1.397 +    if (NS_FAILED(rv))
   1.398 +        return rv;
   1.399 +
   1.400 +    nsDeleteDir::RemoveOldTrashes(mCacheDirectory);
   1.401 +
   1.402 +    // Open Disk Cache
   1.403 +    rv = OpenDiskCache();
   1.404 +    if (NS_FAILED(rv)) {
   1.405 +        (void) mCacheMap.Close(false);
   1.406 +        return rv;
   1.407 +    }
   1.408 +
   1.409 +    mInitialized = true;
   1.410 +    return NS_OK;
   1.411 +}
   1.412 +
   1.413 +
   1.414 +/**
   1.415 + *  NOTE: called while holding the cache service lock
   1.416 + */
   1.417 +nsresult
   1.418 +nsDiskCacheDevice::Shutdown()
   1.419 +{
   1.420 +    nsCacheService::AssertOwnsLock();
   1.421 +
   1.422 +    nsresult rv = Shutdown_Private(true);
   1.423 +    if (NS_FAILED(rv))
   1.424 +        return rv;
   1.425 +
   1.426 +    return NS_OK;
   1.427 +}
   1.428 +
   1.429 +
   1.430 +nsresult
   1.431 +nsDiskCacheDevice::Shutdown_Private(bool    flush)
   1.432 +{
   1.433 +    CACHE_LOG_DEBUG(("CACHE: disk Shutdown_Private [%u]\n", flush));
   1.434 +
   1.435 +    if (Initialized()) {
   1.436 +        // check cache limits in case we need to evict.
   1.437 +        EvictDiskCacheEntries(mCacheCapacity);
   1.438 +
   1.439 +        // At this point there may be a number of pending cache-requests on the
   1.440 +        // cache-io thread. Wait for all these to run before we wipe out our
   1.441 +        // datastructures (see bug #620660)
   1.442 +        (void) nsCacheService::SyncWithCacheIOThread();
   1.443 +
   1.444 +        // write out persistent information about the cache.
   1.445 +        (void) mCacheMap.Close(flush);
   1.446 +
   1.447 +        mBindery.Reset();
   1.448 +
   1.449 +        mInitialized = false;
   1.450 +    }
   1.451 +
   1.452 +    return NS_OK;
   1.453 +}
   1.454 +
   1.455 +
   1.456 +const char *
   1.457 +nsDiskCacheDevice::GetDeviceID()
   1.458 +{
   1.459 +    return DISK_CACHE_DEVICE_ID;
   1.460 +}
   1.461 +
   1.462 +/**
   1.463 + *  FindEntry -
   1.464 + *
   1.465 + *      cases:  key not in disk cache, hash number free
   1.466 + *              key not in disk cache, hash number used
   1.467 + *              key in disk cache
   1.468 + *
   1.469 + *  NOTE: called while holding the cache service lock
   1.470 + */
   1.471 +nsCacheEntry *
   1.472 +nsDiskCacheDevice::FindEntry(nsCString * key, bool *collision)
   1.473 +{
   1.474 +    Telemetry::AutoTimer<Telemetry::CACHE_DISK_SEARCH_2> timer;
   1.475 +    if (!Initialized())  return nullptr;  // NS_ERROR_NOT_INITIALIZED
   1.476 +    if (mClearingDiskCache)  return nullptr;
   1.477 +    nsDiskCacheRecord       record;
   1.478 +    nsDiskCacheBinding *    binding = nullptr;
   1.479 +    PLDHashNumber           hashNumber = nsDiskCache::Hash(key->get());
   1.480 +
   1.481 +    *collision = false;
   1.482 +
   1.483 +    binding = mBindery.FindActiveBinding(hashNumber);
   1.484 +    if (binding && !binding->mCacheEntry->Key()->Equals(*key)) {
   1.485 +        *collision = true;
   1.486 +        return nullptr;
   1.487 +    } else if (binding && binding->mDeactivateEvent) {
   1.488 +        binding->mDeactivateEvent->CancelEvent();
   1.489 +        binding->mDeactivateEvent = nullptr;
   1.490 +        CACHE_LOG_DEBUG(("CACHE: reusing deactivated entry %p " \
   1.491 +                         "req-key=%s  entry-key=%s\n",
   1.492 +                         binding->mCacheEntry, key, binding->mCacheEntry->Key()));
   1.493 +
   1.494 +        return binding->mCacheEntry; // just return this one, observing that
   1.495 +                                     // FindActiveBinding() does not return
   1.496 +                                     // bindings to doomed entries
   1.497 +    }
   1.498 +    binding = nullptr;
   1.499 +
   1.500 +    // lookup hash number in cache map
   1.501 +    nsresult rv = mCacheMap.FindRecord(hashNumber, &record);
   1.502 +    if (NS_FAILED(rv))  return nullptr;  // XXX log error?
   1.503 +    
   1.504 +    nsDiskCacheEntry * diskEntry = mCacheMap.ReadDiskCacheEntry(&record);
   1.505 +    if (!diskEntry) return nullptr;
   1.506 +    
   1.507 +    // compare key to be sure
   1.508 +    if (!key->Equals(diskEntry->Key())) {
   1.509 +        *collision = true;
   1.510 +        return nullptr;
   1.511 +    }
   1.512 +    
   1.513 +    nsCacheEntry * entry = diskEntry->CreateCacheEntry(this);
   1.514 +    if (entry) {
   1.515 +        binding = mBindery.CreateBinding(entry, &record);
   1.516 +        if (!binding) {
   1.517 +            delete entry;
   1.518 +            entry = nullptr;
   1.519 +        }
   1.520 +    }
   1.521 +
   1.522 +    if (!entry) {
   1.523 +      (void) mCacheMap.DeleteStorage(&record);
   1.524 +      (void) mCacheMap.DeleteRecord(&record);
   1.525 +    }
   1.526 +    
   1.527 +    return entry;
   1.528 +}
   1.529 +
   1.530 +
   1.531 +/**
   1.532 + *  NOTE: called while holding the cache service lock
   1.533 + */
   1.534 +nsresult
   1.535 +nsDiskCacheDevice::DeactivateEntry(nsCacheEntry * entry)
   1.536 +{
   1.537 +    nsDiskCacheBinding * binding = GetCacheEntryBinding(entry);
   1.538 +    if (!IsValidBinding(binding))
   1.539 +        return NS_ERROR_UNEXPECTED;
   1.540 +
   1.541 +    CACHE_LOG_DEBUG(("CACHE: disk DeactivateEntry [%p %x]\n",
   1.542 +        entry, binding->mRecord.HashNumber()));
   1.543 +
   1.544 +    nsDiskCacheDeviceDeactivateEntryEvent *event =
   1.545 +        new nsDiskCacheDeviceDeactivateEntryEvent(this, entry, binding);
   1.546 +
   1.547 +    // ensure we can cancel the event via the binding later if necessary
   1.548 +    binding->mDeactivateEvent = event;
   1.549 +
   1.550 +    DebugOnly<nsresult> rv = nsCacheService::DispatchToCacheIOThread(event);
   1.551 +    NS_ASSERTION(NS_SUCCEEDED(rv), "DeactivateEntry: Failed dispatching "
   1.552 +                                   "deactivation event");
   1.553 +    return NS_OK;
   1.554 +}
   1.555 +
   1.556 +/**
   1.557 + *  NOTE: called while holding the cache service lock
   1.558 + */
   1.559 +nsresult
   1.560 +nsDiskCacheDevice::DeactivateEntry_Private(nsCacheEntry * entry,
   1.561 +                                           nsDiskCacheBinding * binding)
   1.562 +{
   1.563 +    nsresult rv = NS_OK;
   1.564 +    if (entry->IsDoomed()) {
   1.565 +        // delete data, entry, record from disk for entry
   1.566 +        rv = mCacheMap.DeleteStorage(&binding->mRecord);
   1.567 +
   1.568 +    } else {
   1.569 +        // save stuff to disk for entry
   1.570 +        rv = mCacheMap.WriteDiskCacheEntry(binding);
   1.571 +        if (NS_FAILED(rv)) {
   1.572 +            // clean up as best we can
   1.573 +            (void) mCacheMap.DeleteStorage(&binding->mRecord);
   1.574 +            (void) mCacheMap.DeleteRecord(&binding->mRecord);
   1.575 +            binding->mDoomed = true; // record is no longer in cache map
   1.576 +        }
   1.577 +    }
   1.578 +
   1.579 +    mBindery.RemoveBinding(binding); // extract binding from collision detection stuff
   1.580 +    delete entry;   // which will release binding
   1.581 +    return rv;
   1.582 +}
   1.583 +
   1.584 +
   1.585 +/**
   1.586 + * BindEntry()
   1.587 + *      no hash number collision -> no problem
   1.588 + *      collision
   1.589 + *          record not active -> evict, no problem
   1.590 + *          record is active
   1.591 + *              record is already doomed -> record shouldn't have been in map, no problem
   1.592 + *              record is not doomed -> doom, and replace record in map
   1.593 + *              
   1.594 + *              walk matching hashnumber list to find lowest generation number
   1.595 + *              take generation number from other (data/meta) location,
   1.596 + *                  or walk active list
   1.597 + *
   1.598 + *  NOTE: called while holding the cache service lock
   1.599 + */
   1.600 +nsresult
   1.601 +nsDiskCacheDevice::BindEntry(nsCacheEntry * entry)
   1.602 +{
   1.603 +    if (!Initialized())  return  NS_ERROR_NOT_INITIALIZED;
   1.604 +    if (mClearingDiskCache)  return NS_ERROR_NOT_AVAILABLE;
   1.605 +    nsresult rv = NS_OK;
   1.606 +    nsDiskCacheRecord record, oldRecord;
   1.607 +    nsDiskCacheBinding *binding;
   1.608 +    PLDHashNumber hashNumber = nsDiskCache::Hash(entry->Key()->get());
   1.609 +
   1.610 +    // Find out if there is already an active binding for this hash. If yes it
   1.611 +    // should have another key since BindEntry() shouldn't be called twice for
   1.612 +    // the same entry. Doom the old entry, the new one will get another
   1.613 +    // generation number so files won't collide.
   1.614 +    binding = mBindery.FindActiveBinding(hashNumber);
   1.615 +    if (binding) {
   1.616 +        NS_ASSERTION(!binding->mCacheEntry->Key()->Equals(*entry->Key()),
   1.617 +                     "BindEntry called for already bound entry!");
   1.618 +        // If the entry is pending deactivation, cancel deactivation
   1.619 +        if (binding->mDeactivateEvent) {
   1.620 +            binding->mDeactivateEvent->CancelEvent();
   1.621 +            binding->mDeactivateEvent = nullptr;
   1.622 +        }
   1.623 +        nsCacheService::DoomEntry(binding->mCacheEntry);
   1.624 +        binding = nullptr;
   1.625 +    }
   1.626 +
   1.627 +    // Lookup hash number in cache map. There can be a colliding inactive entry.
   1.628 +    // See bug #321361 comment 21 for the scenario. If there is such entry,
   1.629 +    // delete it.
   1.630 +    rv = mCacheMap.FindRecord(hashNumber, &record);
   1.631 +    if (NS_SUCCEEDED(rv)) {
   1.632 +        nsDiskCacheEntry * diskEntry = mCacheMap.ReadDiskCacheEntry(&record);
   1.633 +        if (diskEntry) {
   1.634 +            // compare key to be sure
   1.635 +            if (!entry->Key()->Equals(diskEntry->Key())) {
   1.636 +                mCacheMap.DeleteStorage(&record);
   1.637 +                rv = mCacheMap.DeleteRecord(&record);
   1.638 +                if (NS_FAILED(rv))  return rv;
   1.639 +            }
   1.640 +        }
   1.641 +        record = nsDiskCacheRecord();
   1.642 +    }
   1.643 +
   1.644 +    // create a new record for this entry
   1.645 +    record.SetHashNumber(nsDiskCache::Hash(entry->Key()->get()));
   1.646 +    record.SetEvictionRank(ULONG_MAX - SecondsFromPRTime(PR_Now()));
   1.647 +
   1.648 +    CACHE_LOG_DEBUG(("CACHE: disk BindEntry [%p %x]\n",
   1.649 +        entry, record.HashNumber()));
   1.650 +
   1.651 +    if (!entry->IsDoomed()) {
   1.652 +        // if entry isn't doomed, add it to the cache map
   1.653 +        rv = mCacheMap.AddRecord(&record, &oldRecord); // deletes old record, if any
   1.654 +        if (NS_FAILED(rv))  return rv;
   1.655 +        
   1.656 +        uint32_t    oldHashNumber = oldRecord.HashNumber();
   1.657 +        if (oldHashNumber) {
   1.658 +            // gotta evict this one first
   1.659 +            nsDiskCacheBinding * oldBinding = mBindery.FindActiveBinding(oldHashNumber);
   1.660 +            if (oldBinding) {
   1.661 +                // XXX if debug : compare keys for hashNumber collision
   1.662 +
   1.663 +                if (!oldBinding->mCacheEntry->IsDoomed()) {
   1.664 +                    // If the old entry is pending deactivation, cancel deactivation
   1.665 +                    if (oldBinding->mDeactivateEvent) {
   1.666 +                        oldBinding->mDeactivateEvent->CancelEvent();
   1.667 +                        oldBinding->mDeactivateEvent = nullptr;
   1.668 +                    }
   1.669 +                // we've got a live one!
   1.670 +                    nsCacheService::DoomEntry(oldBinding->mCacheEntry);
   1.671 +                    // storage will be delete when oldBinding->mCacheEntry is Deactivated
   1.672 +                }
   1.673 +            } else {
   1.674 +                // delete storage
   1.675 +                // XXX if debug : compare keys for hashNumber collision
   1.676 +                rv = mCacheMap.DeleteStorage(&oldRecord);
   1.677 +                if (NS_FAILED(rv))  return rv;  // XXX delete record we just added?
   1.678 +            }
   1.679 +        }
   1.680 +    }
   1.681 +    
   1.682 +    // Make sure this entry has its associated nsDiskCacheBinding attached.
   1.683 +    binding = mBindery.CreateBinding(entry, &record);
   1.684 +    NS_ASSERTION(binding, "nsDiskCacheDevice::BindEntry");
   1.685 +    if (!binding) return NS_ERROR_OUT_OF_MEMORY;
   1.686 +    NS_ASSERTION(binding->mRecord.ValidRecord(), "bad cache map record");
   1.687 +
   1.688 +    return NS_OK;
   1.689 +}
   1.690 +
   1.691 +
   1.692 +/**
   1.693 + *  NOTE: called while holding the cache service lock
   1.694 + */
   1.695 +void
   1.696 +nsDiskCacheDevice::DoomEntry(nsCacheEntry * entry)
   1.697 +{
   1.698 +    CACHE_LOG_DEBUG(("CACHE: disk DoomEntry [%p]\n", entry));
   1.699 +
   1.700 +    nsDiskCacheBinding * binding = GetCacheEntryBinding(entry);
   1.701 +    NS_ASSERTION(binding, "DoomEntry: binding == nullptr");
   1.702 +    if (!binding)
   1.703 +        return;
   1.704 +
   1.705 +    if (!binding->mDoomed) {
   1.706 +        // so it can't be seen by FindEntry() ever again.
   1.707 +#ifdef DEBUG
   1.708 +        nsresult rv =
   1.709 +#endif
   1.710 +            mCacheMap.DeleteRecord(&binding->mRecord);
   1.711 +        NS_ASSERTION(NS_SUCCEEDED(rv),"DeleteRecord failed.");
   1.712 +        binding->mDoomed = true; // record in no longer in cache map
   1.713 +    }
   1.714 +}
   1.715 +
   1.716 +
   1.717 +/**
   1.718 + *  NOTE: called while holding the cache service lock
   1.719 + */
   1.720 +nsresult
   1.721 +nsDiskCacheDevice::OpenInputStreamForEntry(nsCacheEntry *      entry,
   1.722 +                                           nsCacheAccessMode   mode, 
   1.723 +                                           uint32_t            offset,
   1.724 +                                           nsIInputStream **   result)
   1.725 +{
   1.726 +    CACHE_LOG_DEBUG(("CACHE: disk OpenInputStreamForEntry [%p %x %u]\n",
   1.727 +        entry, mode, offset));
   1.728 +
   1.729 +    NS_ENSURE_ARG_POINTER(entry);
   1.730 +    NS_ENSURE_ARG_POINTER(result);
   1.731 +
   1.732 +    nsresult             rv;
   1.733 +    nsDiskCacheBinding * binding = GetCacheEntryBinding(entry);
   1.734 +    if (!IsValidBinding(binding))
   1.735 +        return NS_ERROR_UNEXPECTED;
   1.736 +
   1.737 +    NS_ASSERTION(binding->mCacheEntry == entry, "binding & entry don't point to each other");
   1.738 +
   1.739 +    rv = binding->EnsureStreamIO();
   1.740 +    if (NS_FAILED(rv)) return rv;
   1.741 +
   1.742 +    return binding->mStreamIO->GetInputStream(offset, result);
   1.743 +}
   1.744 +
   1.745 +
   1.746 +/**
   1.747 + *  NOTE: called while holding the cache service lock
   1.748 + */
   1.749 +nsresult
   1.750 +nsDiskCacheDevice::OpenOutputStreamForEntry(nsCacheEntry *      entry,
   1.751 +                                            nsCacheAccessMode   mode, 
   1.752 +                                            uint32_t            offset,
   1.753 +                                            nsIOutputStream **  result)
   1.754 +{
   1.755 +    CACHE_LOG_DEBUG(("CACHE: disk OpenOutputStreamForEntry [%p %x %u]\n",
   1.756 +        entry, mode, offset));
   1.757 + 
   1.758 +    NS_ENSURE_ARG_POINTER(entry);
   1.759 +    NS_ENSURE_ARG_POINTER(result);
   1.760 +
   1.761 +    nsresult             rv;
   1.762 +    nsDiskCacheBinding * binding = GetCacheEntryBinding(entry);
   1.763 +    if (!IsValidBinding(binding))
   1.764 +        return NS_ERROR_UNEXPECTED;
   1.765 +    
   1.766 +    NS_ASSERTION(binding->mCacheEntry == entry, "binding & entry don't point to each other");
   1.767 +
   1.768 +    rv = binding->EnsureStreamIO();
   1.769 +    if (NS_FAILED(rv)) return rv;
   1.770 +
   1.771 +    return binding->mStreamIO->GetOutputStream(offset, result);
   1.772 +}
   1.773 +
   1.774 +
   1.775 +/**
   1.776 + *  NOTE: called while holding the cache service lock
   1.777 + */
   1.778 +nsresult
   1.779 +nsDiskCacheDevice::GetFileForEntry(nsCacheEntry *    entry,
   1.780 +                                   nsIFile **        result)
   1.781 +{
   1.782 +    NS_ENSURE_ARG_POINTER(result);
   1.783 +    *result = nullptr;
   1.784 +
   1.785 +    nsresult             rv;
   1.786 +        
   1.787 +    nsDiskCacheBinding * binding = GetCacheEntryBinding(entry);
   1.788 +    if (!IsValidBinding(binding))
   1.789 +        return NS_ERROR_UNEXPECTED;
   1.790 +
   1.791 +    // check/set binding->mRecord for separate file, sync w/mCacheMap
   1.792 +    if (binding->mRecord.DataLocationInitialized()) {
   1.793 +        if (binding->mRecord.DataFile() != 0)
   1.794 +            return NS_ERROR_NOT_AVAILABLE;  // data not stored as separate file
   1.795 +
   1.796 +        NS_ASSERTION(binding->mRecord.DataFileGeneration() == binding->mGeneration, "error generations out of sync");
   1.797 +    } else {
   1.798 +        binding->mRecord.SetDataFileGeneration(binding->mGeneration);
   1.799 +        binding->mRecord.SetDataFileSize(0);    // 1k minimum
   1.800 +        if (!binding->mDoomed) {
   1.801 +            // record stored in cache map, so update it
   1.802 +            rv = mCacheMap.UpdateRecord(&binding->mRecord);
   1.803 +            if (NS_FAILED(rv))  return rv;
   1.804 +        }
   1.805 +    }
   1.806 +    
   1.807 +    nsCOMPtr<nsIFile>  file;
   1.808 +    rv = mCacheMap.GetFileForDiskCacheRecord(&binding->mRecord,
   1.809 +                                             nsDiskCache::kData,
   1.810 +                                             false,
   1.811 +                                             getter_AddRefs(file));
   1.812 +    if (NS_FAILED(rv))  return rv;
   1.813 +    
   1.814 +    NS_IF_ADDREF(*result = file);
   1.815 +    return NS_OK;
   1.816 +}
   1.817 +
   1.818 +
   1.819 +/**
   1.820 + *  This routine will get called every time an open descriptor is written to.
   1.821 + *
   1.822 + *  NOTE: called while holding the cache service lock
   1.823 + */
   1.824 +nsresult
   1.825 +nsDiskCacheDevice::OnDataSizeChange(nsCacheEntry * entry, int32_t deltaSize)
   1.826 +{
   1.827 +    CACHE_LOG_DEBUG(("CACHE: disk OnDataSizeChange [%p %d]\n",
   1.828 +        entry, deltaSize));
   1.829 +
   1.830 +    // If passed a negative value, then there's nothing to do.
   1.831 +    if (deltaSize < 0)
   1.832 +        return NS_OK;
   1.833 +
   1.834 +    nsDiskCacheBinding * binding = GetCacheEntryBinding(entry);
   1.835 +    if (!IsValidBinding(binding))
   1.836 +        return NS_ERROR_UNEXPECTED;
   1.837 +
   1.838 +    NS_ASSERTION(binding->mRecord.ValidRecord(), "bad record");
   1.839 +
   1.840 +    uint32_t  newSize = entry->DataSize() + deltaSize;
   1.841 +    uint32_t  newSizeK =  ((newSize + 0x3FF) >> 10);
   1.842 +
   1.843 +    // If the new size is larger than max. file size or larger than
   1.844 +    // 1/8 the cache capacity (which is in KiB's), doom the entry and abort.
   1.845 +    if (EntryIsTooBig(newSize)) {
   1.846 +#ifdef DEBUG
   1.847 +        nsresult rv =
   1.848 +#endif
   1.849 +            nsCacheService::DoomEntry(entry);
   1.850 +        NS_ASSERTION(NS_SUCCEEDED(rv),"DoomEntry() failed.");
   1.851 +        return NS_ERROR_ABORT;
   1.852 +    }
   1.853 +
   1.854 +    uint32_t  sizeK = ((entry->DataSize() + 0x03FF) >> 10); // round up to next 1k
   1.855 +
   1.856 +    // In total count we ignore anything over kMaxDataSizeK (bug #651100), so
   1.857 +    // the target capacity should be calculated the same way.
   1.858 +    if (sizeK > kMaxDataSizeK) sizeK = kMaxDataSizeK;
   1.859 +    if (newSizeK > kMaxDataSizeK) newSizeK = kMaxDataSizeK;
   1.860 +
   1.861 +    // pre-evict entries to make space for new data
   1.862 +    uint32_t  targetCapacity = mCacheCapacity > (newSizeK - sizeK)
   1.863 +                             ? mCacheCapacity - (newSizeK - sizeK)
   1.864 +                             : 0;
   1.865 +    EvictDiskCacheEntries(targetCapacity);
   1.866 +    
   1.867 +    return NS_OK;
   1.868 +}
   1.869 +
   1.870 +
   1.871 +/******************************************************************************
   1.872 + *  EntryInfoVisitor
   1.873 + *****************************************************************************/
   1.874 +class EntryInfoVisitor : public nsDiskCacheRecordVisitor
   1.875 +{
   1.876 +public:
   1.877 +    EntryInfoVisitor(nsDiskCacheMap *    cacheMap,
   1.878 +                     nsICacheVisitor *   visitor)
   1.879 +        : mCacheMap(cacheMap)
   1.880 +        , mVisitor(visitor)
   1.881 +    {}
   1.882 +    
   1.883 +    virtual int32_t  VisitRecord(nsDiskCacheRecord *  mapRecord)
   1.884 +    {
   1.885 +        // XXX optimization: do we have this record in memory?
   1.886 +        
   1.887 +        // read in the entry (metadata)
   1.888 +        nsDiskCacheEntry * diskEntry = mCacheMap->ReadDiskCacheEntry(mapRecord);
   1.889 +        if (!diskEntry) {
   1.890 +            return kVisitNextRecord;
   1.891 +        }
   1.892 +
   1.893 +        // create nsICacheEntryInfo
   1.894 +        nsDiskCacheEntryInfo * entryInfo = new nsDiskCacheEntryInfo(DISK_CACHE_DEVICE_ID, diskEntry);
   1.895 +        if (!entryInfo) {
   1.896 +            return kStopVisitingRecords;
   1.897 +        }
   1.898 +        nsCOMPtr<nsICacheEntryInfo> ref(entryInfo);
   1.899 +        
   1.900 +        bool    keepGoing;
   1.901 +        (void)mVisitor->VisitEntry(DISK_CACHE_DEVICE_ID, entryInfo, &keepGoing);
   1.902 +        return keepGoing ? kVisitNextRecord : kStopVisitingRecords;
   1.903 +    }
   1.904 + 
   1.905 +private:
   1.906 +        nsDiskCacheMap *    mCacheMap;
   1.907 +        nsICacheVisitor *   mVisitor;
   1.908 +};
   1.909 +
   1.910 +
   1.911 +nsresult
   1.912 +nsDiskCacheDevice::Visit(nsICacheVisitor * visitor)
   1.913 +{
   1.914 +    if (!Initialized())  return NS_ERROR_NOT_INITIALIZED;
   1.915 +    nsDiskCacheDeviceInfo* deviceInfo = new nsDiskCacheDeviceInfo(this);
   1.916 +    nsCOMPtr<nsICacheDeviceInfo> ref(deviceInfo);
   1.917 +    
   1.918 +    bool keepGoing;
   1.919 +    nsresult rv = visitor->VisitDevice(DISK_CACHE_DEVICE_ID, deviceInfo, &keepGoing);
   1.920 +    if (NS_FAILED(rv)) return rv;
   1.921 +    
   1.922 +    if (keepGoing) {
   1.923 +        EntryInfoVisitor  infoVisitor(&mCacheMap, visitor);
   1.924 +        return mCacheMap.VisitRecords(&infoVisitor);
   1.925 +    }
   1.926 +
   1.927 +    return NS_OK;
   1.928 +}
   1.929 +
   1.930 +// Max allowed size for an entry is currently MIN(mMaxEntrySize, 1/8 CacheCapacity)
   1.931 +bool
   1.932 +nsDiskCacheDevice::EntryIsTooBig(int64_t entrySize)
   1.933 +{
   1.934 +    if (mMaxEntrySize == -1) // no limit
   1.935 +        return entrySize > (static_cast<int64_t>(mCacheCapacity) * 1024 / 8);
   1.936 +    else 
   1.937 +        return entrySize > mMaxEntrySize ||
   1.938 +               entrySize > (static_cast<int64_t>(mCacheCapacity) * 1024 / 8);
   1.939 +}
   1.940 +
   1.941 +nsresult
   1.942 +nsDiskCacheDevice::EvictEntries(const char * clientID)
   1.943 +{
   1.944 +    CACHE_LOG_DEBUG(("CACHE: disk EvictEntries [%s]\n", clientID));
   1.945 +
   1.946 +    if (!Initialized())  return NS_ERROR_NOT_INITIALIZED;
   1.947 +    nsresult  rv;
   1.948 +
   1.949 +    if (clientID == nullptr) {
   1.950 +        // we're clearing the entire disk cache
   1.951 +        rv = ClearDiskCache();
   1.952 +        if (rv != NS_ERROR_CACHE_IN_USE)
   1.953 +            return rv;
   1.954 +    }
   1.955 +
   1.956 +    nsDiskCacheEvictor  evictor(&mCacheMap, &mBindery, 0, clientID);
   1.957 +    rv = mCacheMap.VisitRecords(&evictor);
   1.958 +    
   1.959 +    if (clientID == nullptr)     // we tried to clear the entire cache
   1.960 +        rv = mCacheMap.Trim(); // so trim cache block files (if possible)
   1.961 +    return rv;
   1.962 +}
   1.963 +
   1.964 +
   1.965 +/**
   1.966 + *  private methods
   1.967 + */
   1.968 +
   1.969 +nsresult
   1.970 +nsDiskCacheDevice::OpenDiskCache()
   1.971 +{
   1.972 +    Telemetry::AutoTimer<Telemetry::NETWORK_DISK_CACHE_OPEN> timer;
   1.973 +    // if we don't have a cache directory, create one and open it
   1.974 +    bool exists;
   1.975 +    nsresult rv = mCacheDirectory->Exists(&exists);
   1.976 +    if (NS_FAILED(rv))
   1.977 +        return rv;
   1.978 +
   1.979 +    if (exists) {
   1.980 +        // Try opening cache map file.
   1.981 +        nsDiskCache::CorruptCacheInfo corruptInfo;
   1.982 +        rv = mCacheMap.Open(mCacheDirectory, &corruptInfo, true);
   1.983 +
   1.984 +        if (NS_SUCCEEDED(rv)) {
   1.985 +            Telemetry::Accumulate(Telemetry::DISK_CACHE_CORRUPT_DETAILS,
   1.986 +                                  corruptInfo);
   1.987 +        } else if (rv == NS_ERROR_ALREADY_INITIALIZED) {
   1.988 +          NS_WARNING("nsDiskCacheDevice::OpenDiskCache: already open!");
   1.989 +        } else {
   1.990 +            // Consider cache corrupt: delete it
   1.991 +            Telemetry::Accumulate(Telemetry::DISK_CACHE_CORRUPT_DETAILS,
   1.992 +                                  corruptInfo);
   1.993 +            // delay delete by 1 minute to avoid IO thrash at startup
   1.994 +            rv = nsDeleteDir::DeleteDir(mCacheDirectory, true, 60000);
   1.995 +            if (NS_FAILED(rv))
   1.996 +                return rv;
   1.997 +            exists = false;
   1.998 +        }
   1.999 +    }
  1.1000 +
  1.1001 +    // if we don't have a cache directory, create one and open it
  1.1002 +    if (!exists) {
  1.1003 +        nsCacheService::MarkStartingFresh();
  1.1004 +        rv = mCacheDirectory->Create(nsIFile::DIRECTORY_TYPE, 0777);
  1.1005 +        CACHE_LOG_PATH(PR_LOG_ALWAYS, "\ncreate cache directory: %s\n", mCacheDirectory);
  1.1006 +        CACHE_LOG_ALWAYS(("mCacheDirectory->Create() = %x\n", rv));
  1.1007 +        if (NS_FAILED(rv))
  1.1008 +            return rv;
  1.1009 +    
  1.1010 +        // reopen the cache map     
  1.1011 +        nsDiskCache::CorruptCacheInfo corruptInfo;
  1.1012 +        rv = mCacheMap.Open(mCacheDirectory, &corruptInfo, false);
  1.1013 +        if (NS_FAILED(rv))
  1.1014 +            return rv;
  1.1015 +    }
  1.1016 +
  1.1017 +    return NS_OK;
  1.1018 +}
  1.1019 +
  1.1020 +
  1.1021 +nsresult
  1.1022 +nsDiskCacheDevice::ClearDiskCache()
  1.1023 +{
  1.1024 +    if (mBindery.ActiveBindings())
  1.1025 +        return NS_ERROR_CACHE_IN_USE;
  1.1026 +
  1.1027 +    mClearingDiskCache = true;
  1.1028 +
  1.1029 +    nsresult rv = Shutdown_Private(false);  // false: don't bother flushing
  1.1030 +    if (NS_FAILED(rv))
  1.1031 +        return rv;
  1.1032 +
  1.1033 +    mClearingDiskCache = false;
  1.1034 +
  1.1035 +    // If the disk cache directory is already gone, then it's not an error if
  1.1036 +    // we fail to delete it ;-)
  1.1037 +    rv = nsDeleteDir::DeleteDir(mCacheDirectory, true);
  1.1038 +    if (NS_FAILED(rv) && rv != NS_ERROR_FILE_TARGET_DOES_NOT_EXIST)
  1.1039 +        return rv;
  1.1040 +
  1.1041 +    return Init();
  1.1042 +}
  1.1043 +
  1.1044 +
  1.1045 +nsresult
  1.1046 +nsDiskCacheDevice::EvictDiskCacheEntries(uint32_t  targetCapacity)
  1.1047 +{
  1.1048 +    CACHE_LOG_DEBUG(("CACHE: disk EvictDiskCacheEntries [%u]\n",
  1.1049 +        targetCapacity));
  1.1050 +
  1.1051 +    NS_ASSERTION(targetCapacity > 0, "oops");
  1.1052 +
  1.1053 +    if (mCacheMap.TotalSize() < targetCapacity)
  1.1054 +        return NS_OK;
  1.1055 +
  1.1056 +    // targetCapacity is in KiB's
  1.1057 +    nsDiskCacheEvictor  evictor(&mCacheMap, &mBindery, targetCapacity, nullptr);
  1.1058 +    return mCacheMap.EvictRecords(&evictor);
  1.1059 +}
  1.1060 +
  1.1061 +
  1.1062 +/**
  1.1063 + *  methods for prefs
  1.1064 + */
  1.1065 +
  1.1066 +void
  1.1067 +nsDiskCacheDevice::SetCacheParentDirectory(nsIFile * parentDir)
  1.1068 +{
  1.1069 +    nsresult rv;
  1.1070 +    bool    exists;
  1.1071 +
  1.1072 +    if (Initialized()) {
  1.1073 +        NS_ASSERTION(false, "Cannot switch cache directory when initialized");
  1.1074 +        return;
  1.1075 +    }
  1.1076 +
  1.1077 +    if (!parentDir) {
  1.1078 +        mCacheDirectory = nullptr;
  1.1079 +        return;
  1.1080 +    }
  1.1081 +
  1.1082 +    // ensure parent directory exists
  1.1083 +    rv = parentDir->Exists(&exists);
  1.1084 +    if (NS_SUCCEEDED(rv) && !exists)
  1.1085 +        rv = parentDir->Create(nsIFile::DIRECTORY_TYPE, 0700);
  1.1086 +    if (NS_FAILED(rv))  return;
  1.1087 +
  1.1088 +    // ensure cache directory exists
  1.1089 +    nsCOMPtr<nsIFile> directory;
  1.1090 +    
  1.1091 +    rv = parentDir->Clone(getter_AddRefs(directory));
  1.1092 +    if (NS_FAILED(rv))  return;
  1.1093 +    rv = directory->AppendNative(NS_LITERAL_CSTRING("Cache"));
  1.1094 +    if (NS_FAILED(rv))  return;
  1.1095 +    
  1.1096 +    mCacheDirectory = do_QueryInterface(directory);
  1.1097 +}
  1.1098 +
  1.1099 +
  1.1100 +void
  1.1101 +nsDiskCacheDevice::getCacheDirectory(nsIFile ** result)
  1.1102 +{
  1.1103 +    *result = mCacheDirectory;
  1.1104 +    NS_IF_ADDREF(*result);
  1.1105 +}
  1.1106 +
  1.1107 +
  1.1108 +/**
  1.1109 + *  NOTE: called while holding the cache service lock
  1.1110 + */
  1.1111 +void
  1.1112 +nsDiskCacheDevice::SetCapacity(uint32_t  capacity)
  1.1113 +{
  1.1114 +    // Units are KiB's
  1.1115 +    mCacheCapacity = capacity;
  1.1116 +    if (Initialized()) {
  1.1117 +        if (NS_IsMainThread()) {
  1.1118 +            // Do not evict entries on the main thread
  1.1119 +            nsCacheService::DispatchToCacheIOThread(
  1.1120 +                new nsEvictDiskCacheEntriesEvent(this));
  1.1121 +        } else {
  1.1122 +            // start evicting entries if the new size is smaller!
  1.1123 +            EvictDiskCacheEntries(mCacheCapacity);
  1.1124 +        }
  1.1125 +    }
  1.1126 +    // Let cache map know of the new capacity
  1.1127 +    mCacheMap.NotifyCapacityChange(capacity);
  1.1128 +}
  1.1129 +
  1.1130 +
  1.1131 +uint32_t nsDiskCacheDevice::getCacheCapacity()
  1.1132 +{
  1.1133 +    return mCacheCapacity;
  1.1134 +}
  1.1135 +
  1.1136 +
  1.1137 +uint32_t nsDiskCacheDevice::getCacheSize()
  1.1138 +{
  1.1139 +    return mCacheMap.TotalSize();
  1.1140 +}
  1.1141 +
  1.1142 +
  1.1143 +uint32_t nsDiskCacheDevice::getEntryCount()
  1.1144 +{
  1.1145 +    return mCacheMap.EntryCount();
  1.1146 +}
  1.1147 +
  1.1148 +void
  1.1149 +nsDiskCacheDevice::SetMaxEntrySize(int32_t maxSizeInKilobytes)
  1.1150 +{
  1.1151 +    // Internal units are bytes. Changing this only takes effect *after* the
  1.1152 +    // change and has no consequences for existing cache-entries
  1.1153 +    if (maxSizeInKilobytes >= 0)
  1.1154 +        mMaxEntrySize = maxSizeInKilobytes * 1024;
  1.1155 +    else
  1.1156 +        mMaxEntrySize = -1;
  1.1157 +}
  1.1158 +
  1.1159 +size_t
  1.1160 +nsDiskCacheDevice::SizeOfIncludingThis(MallocSizeOf aMallocSizeOf)
  1.1161 +{
  1.1162 +    size_t usage = aMallocSizeOf(this);
  1.1163 +
  1.1164 +    usage += mCacheMap.SizeOfExcludingThis(aMallocSizeOf);
  1.1165 +    usage += mBindery.SizeOfExcludingThis(aMallocSizeOf);
  1.1166 +
  1.1167 +    return usage;
  1.1168 +}
  1.1169 +

mercurial