nsDiskCacheDevice.cpp 34 KB

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