netwerk/cache/nsDiskCacheDevice.cpp

Thu, 15 Jan 2015 15:55:04 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Thu, 15 Jan 2015 15:55:04 +0100
branch
TOR_BUG_9701
changeset 9
a63d609f5ebe
permissions
-rw-r--r--

Back out 97036ab72558 which inappropriately compared turds to third parties.

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

mercurial