nsDiskCacheMap.cpp 46 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427
  1. /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
  2. /* vim:set ts=4 sw=4 sts=4 cin et: */
  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 "nsCache.h"
  7. #include "nsDiskCacheMap.h"
  8. #include "nsDiskCacheBinding.h"
  9. #include "nsDiskCacheEntry.h"
  10. #include "nsDiskCacheDevice.h"
  11. #include "nsCacheService.h"
  12. #include <string.h>
  13. #include "nsPrintfCString.h"
  14. #include "nsISerializable.h"
  15. #include "nsSerializationHelper.h"
  16. #include "mozilla/MemoryReporting.h"
  17. #include "mozilla/Sprintf.h"
  18. #include <algorithm>
  19. using namespace mozilla;
  20. /******************************************************************************
  21. * nsDiskCacheMap
  22. *****************************************************************************/
  23. /**
  24. * File operations
  25. */
  26. nsresult
  27. nsDiskCacheMap::Open(nsIFile * cacheDirectory,
  28. nsDiskCache::CorruptCacheInfo * corruptInfo)
  29. {
  30. NS_ENSURE_ARG_POINTER(corruptInfo);
  31. // Assume we have an unexpected error until we find otherwise.
  32. *corruptInfo = nsDiskCache::kUnexpectedError;
  33. NS_ENSURE_ARG_POINTER(cacheDirectory);
  34. if (mMapFD) return NS_ERROR_ALREADY_INITIALIZED;
  35. mCacheDirectory = cacheDirectory; // save a reference for ourselves
  36. // create nsIFile for _CACHE_MAP_
  37. nsresult rv;
  38. nsCOMPtr<nsIFile> file;
  39. rv = cacheDirectory->Clone(getter_AddRefs(file));
  40. rv = file->AppendNative(NS_LITERAL_CSTRING("_CACHE_MAP_"));
  41. NS_ENSURE_SUCCESS(rv, rv);
  42. // open the file - restricted to user, the data could be confidential
  43. rv = file->OpenNSPRFileDesc(PR_RDWR | PR_CREATE_FILE, 00600, &mMapFD);
  44. if (NS_FAILED(rv)) {
  45. *corruptInfo = nsDiskCache::kOpenCacheMapError;
  46. NS_WARNING("Could not open cache map file");
  47. return NS_ERROR_FILE_CORRUPTED;
  48. }
  49. bool cacheFilesExist = CacheFilesExist();
  50. rv = NS_ERROR_FILE_CORRUPTED; // presume the worst
  51. uint32_t mapSize = PR_Available(mMapFD);
  52. if (NS_FAILED(InitCacheClean(cacheDirectory,
  53. corruptInfo))) {
  54. // corruptInfo is set in the call to InitCacheClean
  55. goto error_exit;
  56. }
  57. // check size of map file
  58. if (mapSize == 0) { // creating a new _CACHE_MAP_
  59. // block files shouldn't exist if we're creating the _CACHE_MAP_
  60. if (cacheFilesExist) {
  61. *corruptInfo = nsDiskCache::kBlockFilesShouldNotExist;
  62. goto error_exit;
  63. }
  64. if (NS_FAILED(CreateCacheSubDirectories())) {
  65. *corruptInfo = nsDiskCache::kCreateCacheSubdirectories;
  66. goto error_exit;
  67. }
  68. // create the file - initialize in memory
  69. memset(&mHeader, 0, sizeof(nsDiskCacheHeader));
  70. mHeader.mVersion = nsDiskCache::kCurrentVersion;
  71. mHeader.mRecordCount = kMinRecordCount;
  72. mRecordArray = (nsDiskCacheRecord *)
  73. PR_CALLOC(mHeader.mRecordCount * sizeof(nsDiskCacheRecord));
  74. if (!mRecordArray) {
  75. *corruptInfo = nsDiskCache::kOutOfMemory;
  76. rv = NS_ERROR_OUT_OF_MEMORY;
  77. goto error_exit;
  78. }
  79. } else if (mapSize >= sizeof(nsDiskCacheHeader)) { // read existing _CACHE_MAP_
  80. // if _CACHE_MAP_ exists, so should the block files
  81. if (!cacheFilesExist) {
  82. *corruptInfo = nsDiskCache::kBlockFilesShouldExist;
  83. goto error_exit;
  84. }
  85. CACHE_LOG_DEBUG(("CACHE: nsDiskCacheMap::Open [this=%p] reading map", this));
  86. // read the header
  87. uint32_t bytesRead = PR_Read(mMapFD, &mHeader, sizeof(nsDiskCacheHeader));
  88. if (sizeof(nsDiskCacheHeader) != bytesRead) {
  89. *corruptInfo = nsDiskCache::kHeaderSizeNotRead;
  90. goto error_exit;
  91. }
  92. mHeader.Unswap();
  93. if (mHeader.mIsDirty) {
  94. *corruptInfo = nsDiskCache::kHeaderIsDirty;
  95. goto error_exit;
  96. }
  97. if (mHeader.mVersion != nsDiskCache::kCurrentVersion) {
  98. *corruptInfo = nsDiskCache::kVersionMismatch;
  99. goto error_exit;
  100. }
  101. uint32_t recordArraySize =
  102. mHeader.mRecordCount * sizeof(nsDiskCacheRecord);
  103. if (mapSize < recordArraySize + sizeof(nsDiskCacheHeader)) {
  104. *corruptInfo = nsDiskCache::kRecordsIncomplete;
  105. goto error_exit;
  106. }
  107. // Get the space for the records
  108. mRecordArray = (nsDiskCacheRecord *) PR_MALLOC(recordArraySize);
  109. if (!mRecordArray) {
  110. *corruptInfo = nsDiskCache::kOutOfMemory;
  111. rv = NS_ERROR_OUT_OF_MEMORY;
  112. goto error_exit;
  113. }
  114. // Read the records
  115. bytesRead = PR_Read(mMapFD, mRecordArray, recordArraySize);
  116. if (bytesRead < recordArraySize) {
  117. *corruptInfo = nsDiskCache::kNotEnoughToRead;
  118. goto error_exit;
  119. }
  120. // Unswap each record
  121. int32_t total = 0;
  122. for (int32_t i = 0; i < mHeader.mRecordCount; ++i) {
  123. if (mRecordArray[i].HashNumber()) {
  124. #if defined(IS_LITTLE_ENDIAN)
  125. mRecordArray[i].Unswap();
  126. #endif
  127. total ++;
  128. }
  129. }
  130. // verify entry count
  131. if (total != mHeader.mEntryCount) {
  132. *corruptInfo = nsDiskCache::kEntryCountIncorrect;
  133. goto error_exit;
  134. }
  135. } else {
  136. *corruptInfo = nsDiskCache::kHeaderIncomplete;
  137. goto error_exit;
  138. }
  139. rv = OpenBlockFiles(corruptInfo);
  140. if (NS_FAILED(rv)) {
  141. // corruptInfo is set in the call to OpenBlockFiles
  142. goto error_exit;
  143. }
  144. // set dirty bit and flush header
  145. mHeader.mIsDirty = true;
  146. rv = FlushHeader();
  147. if (NS_FAILED(rv)) {
  148. *corruptInfo = nsDiskCache::kFlushHeaderError;
  149. goto error_exit;
  150. }
  151. *corruptInfo = nsDiskCache::kNotCorrupt;
  152. return NS_OK;
  153. error_exit:
  154. (void) Close(false);
  155. return rv;
  156. }
  157. nsresult
  158. nsDiskCacheMap::Close(bool flush)
  159. {
  160. nsCacheService::AssertOwnsLock();
  161. nsresult rv = NS_OK;
  162. // Cancel any pending cache validation event, the FlushRecords call below
  163. // will validate the cache.
  164. if (mCleanCacheTimer) {
  165. mCleanCacheTimer->Cancel();
  166. }
  167. // If cache map file and its block files are still open, close them
  168. if (mMapFD) {
  169. // close block files
  170. rv = CloseBlockFiles(flush);
  171. if (NS_SUCCEEDED(rv) && flush && mRecordArray) {
  172. // write the map records
  173. rv = FlushRecords(false); // don't bother swapping buckets back
  174. if (NS_SUCCEEDED(rv)) {
  175. // clear dirty bit
  176. mHeader.mIsDirty = false;
  177. rv = FlushHeader();
  178. }
  179. }
  180. if ((PR_Close(mMapFD) != PR_SUCCESS) && (NS_SUCCEEDED(rv)))
  181. rv = NS_ERROR_UNEXPECTED;
  182. mMapFD = nullptr;
  183. }
  184. if (mCleanFD) {
  185. PR_Close(mCleanFD);
  186. mCleanFD = nullptr;
  187. }
  188. PR_FREEIF(mRecordArray);
  189. PR_FREEIF(mBuffer);
  190. mBufferSize = 0;
  191. return rv;
  192. }
  193. nsresult
  194. nsDiskCacheMap::Trim()
  195. {
  196. nsresult rv, rv2 = NS_OK;
  197. for (int i=0; i < kNumBlockFiles; ++i) {
  198. rv = mBlockFile[i].Trim();
  199. if (NS_FAILED(rv)) rv2 = rv; // if one or more errors, report at least one
  200. }
  201. // Try to shrink the records array
  202. rv = ShrinkRecords();
  203. if (NS_FAILED(rv)) rv2 = rv; // if one or more errors, report at least one
  204. return rv2;
  205. }
  206. nsresult
  207. nsDiskCacheMap::FlushHeader()
  208. {
  209. if (!mMapFD) return NS_ERROR_NOT_AVAILABLE;
  210. // seek to beginning of cache map
  211. int32_t filePos = PR_Seek(mMapFD, 0, PR_SEEK_SET);
  212. if (filePos != 0) return NS_ERROR_UNEXPECTED;
  213. // write the header
  214. mHeader.Swap();
  215. int32_t bytesWritten = PR_Write(mMapFD, &mHeader, sizeof(nsDiskCacheHeader));
  216. mHeader.Unswap();
  217. if (sizeof(nsDiskCacheHeader) != bytesWritten) {
  218. return NS_ERROR_UNEXPECTED;
  219. }
  220. PRStatus err = PR_Sync(mMapFD);
  221. if (err != PR_SUCCESS) return NS_ERROR_UNEXPECTED;
  222. // If we have a clean header then revalidate the cache clean file
  223. if (!mHeader.mIsDirty) {
  224. RevalidateCache();
  225. }
  226. return NS_OK;
  227. }
  228. nsresult
  229. nsDiskCacheMap::FlushRecords(bool unswap)
  230. {
  231. if (!mMapFD) return NS_ERROR_NOT_AVAILABLE;
  232. // seek to beginning of buckets
  233. int32_t filePos = PR_Seek(mMapFD, sizeof(nsDiskCacheHeader), PR_SEEK_SET);
  234. if (filePos != sizeof(nsDiskCacheHeader))
  235. return NS_ERROR_UNEXPECTED;
  236. #if defined(IS_LITTLE_ENDIAN)
  237. // Swap each record
  238. for (int32_t i = 0; i < mHeader.mRecordCount; ++i) {
  239. if (mRecordArray[i].HashNumber())
  240. mRecordArray[i].Swap();
  241. }
  242. #endif
  243. int32_t recordArraySize = sizeof(nsDiskCacheRecord) * mHeader.mRecordCount;
  244. int32_t bytesWritten = PR_Write(mMapFD, mRecordArray, recordArraySize);
  245. if (bytesWritten != recordArraySize)
  246. return NS_ERROR_UNEXPECTED;
  247. #if defined(IS_LITTLE_ENDIAN)
  248. if (unswap) {
  249. // Unswap each record
  250. for (int32_t i = 0; i < mHeader.mRecordCount; ++i) {
  251. if (mRecordArray[i].HashNumber())
  252. mRecordArray[i].Unswap();
  253. }
  254. }
  255. #endif
  256. return NS_OK;
  257. }
  258. /**
  259. * Record operations
  260. */
  261. uint32_t
  262. nsDiskCacheMap::GetBucketRank(uint32_t bucketIndex, uint32_t targetRank)
  263. {
  264. nsDiskCacheRecord * records = GetFirstRecordInBucket(bucketIndex);
  265. uint32_t rank = 0;
  266. for (int i = mHeader.mBucketUsage[bucketIndex]-1; i >= 0; i--) {
  267. if ((rank < records[i].EvictionRank()) &&
  268. ((targetRank == 0) || (records[i].EvictionRank() < targetRank)))
  269. rank = records[i].EvictionRank();
  270. }
  271. return rank;
  272. }
  273. nsresult
  274. nsDiskCacheMap::GrowRecords()
  275. {
  276. if (mHeader.mRecordCount >= mMaxRecordCount)
  277. return NS_OK;
  278. CACHE_LOG_DEBUG(("CACHE: GrowRecords\n"));
  279. // Resize the record array
  280. int32_t newCount = mHeader.mRecordCount << 1;
  281. if (newCount > mMaxRecordCount)
  282. newCount = mMaxRecordCount;
  283. nsDiskCacheRecord *newArray = (nsDiskCacheRecord *)
  284. PR_REALLOC(mRecordArray, newCount * sizeof(nsDiskCacheRecord));
  285. if (!newArray)
  286. return NS_ERROR_OUT_OF_MEMORY;
  287. // Space out the buckets
  288. uint32_t oldRecordsPerBucket = GetRecordsPerBucket();
  289. uint32_t newRecordsPerBucket = newCount / kBuckets;
  290. // Work from back to space out each bucket to the new array
  291. for (int bucketIndex = kBuckets - 1; bucketIndex >= 0; --bucketIndex) {
  292. // Move bucket
  293. nsDiskCacheRecord *newRecords = newArray + bucketIndex * newRecordsPerBucket;
  294. const uint32_t count = mHeader.mBucketUsage[bucketIndex];
  295. memmove(newRecords,
  296. newArray + bucketIndex * oldRecordsPerBucket,
  297. count * sizeof(nsDiskCacheRecord));
  298. // clear unused records
  299. memset(newRecords + count, 0,
  300. (newRecordsPerBucket - count) * sizeof(nsDiskCacheRecord));
  301. }
  302. // Set as the new record array
  303. mRecordArray = newArray;
  304. mHeader.mRecordCount = newCount;
  305. InvalidateCache();
  306. return NS_OK;
  307. }
  308. nsresult
  309. nsDiskCacheMap::ShrinkRecords()
  310. {
  311. if (mHeader.mRecordCount <= kMinRecordCount)
  312. return NS_OK;
  313. CACHE_LOG_DEBUG(("CACHE: ShrinkRecords\n"));
  314. // Verify if we can shrink the record array: all buckets must be less than
  315. // 1/2 filled
  316. uint32_t maxUsage = 0, bucketIndex;
  317. for (bucketIndex = 0; bucketIndex < kBuckets; ++bucketIndex) {
  318. if (maxUsage < mHeader.mBucketUsage[bucketIndex])
  319. maxUsage = mHeader.mBucketUsage[bucketIndex];
  320. }
  321. // Determine new bucket size, halve size until maxUsage
  322. uint32_t oldRecordsPerBucket = GetRecordsPerBucket();
  323. uint32_t newRecordsPerBucket = oldRecordsPerBucket;
  324. while (maxUsage < (newRecordsPerBucket >> 1))
  325. newRecordsPerBucket >>= 1;
  326. if (newRecordsPerBucket < (kMinRecordCount / kBuckets))
  327. newRecordsPerBucket = (kMinRecordCount / kBuckets);
  328. NS_ASSERTION(newRecordsPerBucket <= oldRecordsPerBucket,
  329. "ShrinkRecords() can't grow records!");
  330. if (newRecordsPerBucket == oldRecordsPerBucket)
  331. return NS_OK;
  332. // Move the buckets close to each other
  333. for (bucketIndex = 1; bucketIndex < kBuckets; ++bucketIndex) {
  334. // Move bucket
  335. memmove(mRecordArray + bucketIndex * newRecordsPerBucket,
  336. mRecordArray + bucketIndex * oldRecordsPerBucket,
  337. newRecordsPerBucket * sizeof(nsDiskCacheRecord));
  338. }
  339. // Shrink the record array memory block itself
  340. uint32_t newCount = newRecordsPerBucket * kBuckets;
  341. nsDiskCacheRecord* newArray = (nsDiskCacheRecord *)
  342. PR_REALLOC(mRecordArray, newCount * sizeof(nsDiskCacheRecord));
  343. if (!newArray)
  344. return NS_ERROR_OUT_OF_MEMORY;
  345. // Set as the new record array
  346. mRecordArray = newArray;
  347. mHeader.mRecordCount = newCount;
  348. InvalidateCache();
  349. return NS_OK;
  350. }
  351. nsresult
  352. nsDiskCacheMap::AddRecord( nsDiskCacheRecord * mapRecord,
  353. nsDiskCacheRecord * oldRecord)
  354. {
  355. CACHE_LOG_DEBUG(("CACHE: AddRecord [%x]\n", mapRecord->HashNumber()));
  356. const uint32_t hashNumber = mapRecord->HashNumber();
  357. const uint32_t bucketIndex = GetBucketIndex(hashNumber);
  358. const uint32_t count = mHeader.mBucketUsage[bucketIndex];
  359. oldRecord->SetHashNumber(0); // signify no record
  360. if (count == GetRecordsPerBucket()) {
  361. // Ignore failure to grow the record space, we will then reuse old records
  362. GrowRecords();
  363. }
  364. nsDiskCacheRecord * records = GetFirstRecordInBucket(bucketIndex);
  365. if (count < GetRecordsPerBucket()) {
  366. // stick the new record at the end
  367. records[count] = *mapRecord;
  368. mHeader.mEntryCount++;
  369. mHeader.mBucketUsage[bucketIndex]++;
  370. if (mHeader.mEvictionRank[bucketIndex] < mapRecord->EvictionRank())
  371. mHeader.mEvictionRank[bucketIndex] = mapRecord->EvictionRank();
  372. InvalidateCache();
  373. } else {
  374. // Find the record with the highest eviction rank
  375. nsDiskCacheRecord * mostEvictable = &records[0];
  376. for (int i = count-1; i > 0; i--) {
  377. if (records[i].EvictionRank() > mostEvictable->EvictionRank())
  378. mostEvictable = &records[i];
  379. }
  380. *oldRecord = *mostEvictable; // i == GetRecordsPerBucket(), so
  381. // evict the mostEvictable
  382. *mostEvictable = *mapRecord; // replace it with the new record
  383. // check if we need to update mostEvictable entry in header
  384. if (mHeader.mEvictionRank[bucketIndex] < mapRecord->EvictionRank())
  385. mHeader.mEvictionRank[bucketIndex] = mapRecord->EvictionRank();
  386. if (oldRecord->EvictionRank() >= mHeader.mEvictionRank[bucketIndex])
  387. mHeader.mEvictionRank[bucketIndex] = GetBucketRank(bucketIndex, 0);
  388. InvalidateCache();
  389. }
  390. NS_ASSERTION(mHeader.mEvictionRank[bucketIndex] == GetBucketRank(bucketIndex, 0),
  391. "eviction rank out of sync");
  392. return NS_OK;
  393. }
  394. nsresult
  395. nsDiskCacheMap::UpdateRecord( nsDiskCacheRecord * mapRecord)
  396. {
  397. CACHE_LOG_DEBUG(("CACHE: UpdateRecord [%x]\n", mapRecord->HashNumber()));
  398. const uint32_t hashNumber = mapRecord->HashNumber();
  399. const uint32_t bucketIndex = GetBucketIndex(hashNumber);
  400. nsDiskCacheRecord * records = GetFirstRecordInBucket(bucketIndex);
  401. for (int i = mHeader.mBucketUsage[bucketIndex]-1; i >= 0; i--) {
  402. if (records[i].HashNumber() == hashNumber) {
  403. const uint32_t oldRank = records[i].EvictionRank();
  404. // stick the new record here
  405. records[i] = *mapRecord;
  406. // update eviction rank in header if necessary
  407. if (mHeader.mEvictionRank[bucketIndex] < mapRecord->EvictionRank())
  408. mHeader.mEvictionRank[bucketIndex] = mapRecord->EvictionRank();
  409. else if (mHeader.mEvictionRank[bucketIndex] == oldRank)
  410. mHeader.mEvictionRank[bucketIndex] = GetBucketRank(bucketIndex, 0);
  411. InvalidateCache();
  412. NS_ASSERTION(mHeader.mEvictionRank[bucketIndex] == GetBucketRank(bucketIndex, 0),
  413. "eviction rank out of sync");
  414. return NS_OK;
  415. }
  416. }
  417. NS_NOTREACHED("record not found");
  418. return NS_ERROR_UNEXPECTED;
  419. }
  420. nsresult
  421. nsDiskCacheMap::FindRecord( uint32_t hashNumber, nsDiskCacheRecord * result)
  422. {
  423. const uint32_t bucketIndex = GetBucketIndex(hashNumber);
  424. nsDiskCacheRecord * records = GetFirstRecordInBucket(bucketIndex);
  425. for (int i = mHeader.mBucketUsage[bucketIndex]-1; i >= 0; i--) {
  426. if (records[i].HashNumber() == hashNumber) {
  427. *result = records[i]; // copy the record
  428. NS_ASSERTION(result->ValidRecord(), "bad cache map record");
  429. return NS_OK;
  430. }
  431. }
  432. return NS_ERROR_CACHE_KEY_NOT_FOUND;
  433. }
  434. nsresult
  435. nsDiskCacheMap::DeleteRecord( nsDiskCacheRecord * mapRecord)
  436. {
  437. CACHE_LOG_DEBUG(("CACHE: DeleteRecord [%x]\n", mapRecord->HashNumber()));
  438. const uint32_t hashNumber = mapRecord->HashNumber();
  439. const uint32_t bucketIndex = GetBucketIndex(hashNumber);
  440. nsDiskCacheRecord * records = GetFirstRecordInBucket(bucketIndex);
  441. uint32_t last = mHeader.mBucketUsage[bucketIndex]-1;
  442. for (int i = last; i >= 0; i--) {
  443. if (records[i].HashNumber() == hashNumber) {
  444. // found it, now delete it.
  445. uint32_t evictionRank = records[i].EvictionRank();
  446. NS_ASSERTION(evictionRank == mapRecord->EvictionRank(),
  447. "evictionRank out of sync");
  448. // if not the last record, shift last record into opening
  449. records[i] = records[last];
  450. records[last].SetHashNumber(0); // clear last record
  451. mHeader.mBucketUsage[bucketIndex] = last;
  452. mHeader.mEntryCount--;
  453. // update eviction rank
  454. uint32_t bucketIndex = GetBucketIndex(mapRecord->HashNumber());
  455. if (mHeader.mEvictionRank[bucketIndex] <= evictionRank) {
  456. mHeader.mEvictionRank[bucketIndex] = GetBucketRank(bucketIndex, 0);
  457. }
  458. InvalidateCache();
  459. NS_ASSERTION(mHeader.mEvictionRank[bucketIndex] ==
  460. GetBucketRank(bucketIndex, 0), "eviction rank out of sync");
  461. return NS_OK;
  462. }
  463. }
  464. return NS_ERROR_UNEXPECTED;
  465. }
  466. int32_t
  467. nsDiskCacheMap::VisitEachRecord(uint32_t bucketIndex,
  468. nsDiskCacheRecordVisitor * visitor,
  469. uint32_t evictionRank)
  470. {
  471. int32_t rv = kVisitNextRecord;
  472. uint32_t count = mHeader.mBucketUsage[bucketIndex];
  473. nsDiskCacheRecord * records = GetFirstRecordInBucket(bucketIndex);
  474. // call visitor for each entry (matching any eviction rank)
  475. for (int i = count-1; i >= 0; i--) {
  476. if (evictionRank > records[i].EvictionRank()) continue;
  477. rv = visitor->VisitRecord(&records[i]);
  478. if (rv == kStopVisitingRecords)
  479. break; // Stop visiting records
  480. if (rv == kDeleteRecordAndContinue) {
  481. --count;
  482. records[i] = records[count];
  483. records[count].SetHashNumber(0);
  484. InvalidateCache();
  485. }
  486. }
  487. if (mHeader.mBucketUsage[bucketIndex] - count != 0) {
  488. mHeader.mEntryCount -= mHeader.mBucketUsage[bucketIndex] - count;
  489. mHeader.mBucketUsage[bucketIndex] = count;
  490. // recalc eviction rank
  491. mHeader.mEvictionRank[bucketIndex] = GetBucketRank(bucketIndex, 0);
  492. }
  493. NS_ASSERTION(mHeader.mEvictionRank[bucketIndex] ==
  494. GetBucketRank(bucketIndex, 0), "eviction rank out of sync");
  495. return rv;
  496. }
  497. /**
  498. * VisitRecords
  499. *
  500. * Visit every record in cache map in the most convenient order
  501. */
  502. nsresult
  503. nsDiskCacheMap::VisitRecords( nsDiskCacheRecordVisitor * visitor)
  504. {
  505. for (int bucketIndex = 0; bucketIndex < kBuckets; ++bucketIndex) {
  506. if (VisitEachRecord(bucketIndex, visitor, 0) == kStopVisitingRecords)
  507. break;
  508. }
  509. return NS_OK;
  510. }
  511. /**
  512. * EvictRecords
  513. *
  514. * Just like VisitRecords, but visits the records in order of their eviction rank
  515. */
  516. nsresult
  517. nsDiskCacheMap::EvictRecords( nsDiskCacheRecordVisitor * visitor)
  518. {
  519. uint32_t tempRank[kBuckets];
  520. int bucketIndex = 0;
  521. // copy eviction rank array
  522. for (bucketIndex = 0; bucketIndex < kBuckets; ++bucketIndex)
  523. tempRank[bucketIndex] = mHeader.mEvictionRank[bucketIndex];
  524. // Maximum number of iterations determined by number of records
  525. // as a safety limiter for the loop. Use a copy of mHeader.mEntryCount since
  526. // the value could decrease if some entry is evicted.
  527. int32_t entryCount = mHeader.mEntryCount;
  528. for (int n = 0; n < entryCount; ++n) {
  529. // find bucket with highest eviction rank
  530. uint32_t rank = 0;
  531. for (int i = 0; i < kBuckets; ++i) {
  532. if (rank < tempRank[i]) {
  533. rank = tempRank[i];
  534. bucketIndex = i;
  535. }
  536. }
  537. if (rank == 0) break; // we've examined all the records
  538. // visit records in bucket with eviction ranks >= target eviction rank
  539. if (VisitEachRecord(bucketIndex, visitor, rank) == kStopVisitingRecords)
  540. break;
  541. // find greatest rank less than 'rank'
  542. tempRank[bucketIndex] = GetBucketRank(bucketIndex, rank);
  543. }
  544. return NS_OK;
  545. }
  546. nsresult
  547. nsDiskCacheMap::OpenBlockFiles(nsDiskCache::CorruptCacheInfo * corruptInfo)
  548. {
  549. NS_ENSURE_ARG_POINTER(corruptInfo);
  550. // create nsIFile for block file
  551. nsCOMPtr<nsIFile> blockFile;
  552. nsresult rv = NS_OK;
  553. *corruptInfo = nsDiskCache::kUnexpectedError;
  554. for (int i = 0; i < kNumBlockFiles; ++i) {
  555. rv = GetBlockFileForIndex(i, getter_AddRefs(blockFile));
  556. if (NS_FAILED(rv)) {
  557. *corruptInfo = nsDiskCache::kCouldNotGetBlockFileForIndex;
  558. break;
  559. }
  560. uint32_t blockSize = GetBlockSizeForIndex(i+1); // +1 to match file selectors 1,2,3
  561. uint32_t bitMapSize = GetBitMapSizeForIndex(i+1);
  562. rv = mBlockFile[i].Open(blockFile, blockSize, bitMapSize, corruptInfo);
  563. if (NS_FAILED(rv)) {
  564. // corruptInfo was set inside the call to mBlockFile[i].Open
  565. break;
  566. }
  567. }
  568. // close all files in case of any error
  569. if (NS_FAILED(rv))
  570. (void)CloseBlockFiles(false); // we already have an error to report
  571. return rv;
  572. }
  573. nsresult
  574. nsDiskCacheMap::CloseBlockFiles(bool flush)
  575. {
  576. nsresult rv, rv2 = NS_OK;
  577. for (int i=0; i < kNumBlockFiles; ++i) {
  578. rv = mBlockFile[i].Close(flush);
  579. if (NS_FAILED(rv)) rv2 = rv; // if one or more errors, report at least one
  580. }
  581. return rv2;
  582. }
  583. bool
  584. nsDiskCacheMap::CacheFilesExist()
  585. {
  586. nsCOMPtr<nsIFile> blockFile;
  587. nsresult rv;
  588. for (int i = 0; i < kNumBlockFiles; ++i) {
  589. bool exists;
  590. rv = GetBlockFileForIndex(i, getter_AddRefs(blockFile));
  591. if (NS_FAILED(rv)) return false;
  592. rv = blockFile->Exists(&exists);
  593. if (NS_FAILED(rv) || !exists) return false;
  594. }
  595. return true;
  596. }
  597. nsresult
  598. nsDiskCacheMap::CreateCacheSubDirectories()
  599. {
  600. if (!mCacheDirectory)
  601. return NS_ERROR_UNEXPECTED;
  602. for (int32_t index = 0 ; index < 16 ; index++) {
  603. nsCOMPtr<nsIFile> file;
  604. nsresult rv = mCacheDirectory->Clone(getter_AddRefs(file));
  605. if (NS_FAILED(rv))
  606. return rv;
  607. rv = file->AppendNative(nsPrintfCString("%X", index));
  608. if (NS_FAILED(rv))
  609. return rv;
  610. rv = file->Create(nsIFile::DIRECTORY_TYPE, 0700);
  611. if (NS_FAILED(rv))
  612. return rv;
  613. }
  614. return NS_OK;
  615. }
  616. nsDiskCacheEntry *
  617. nsDiskCacheMap::ReadDiskCacheEntry(nsDiskCacheRecord * record)
  618. {
  619. CACHE_LOG_DEBUG(("CACHE: ReadDiskCacheEntry [%x]\n", record->HashNumber()));
  620. nsresult rv = NS_ERROR_UNEXPECTED;
  621. nsDiskCacheEntry * diskEntry = nullptr;
  622. uint32_t metaFile = record->MetaFile();
  623. int32_t bytesRead = 0;
  624. if (!record->MetaLocationInitialized()) return nullptr;
  625. if (metaFile == 0) { // entry/metadata stored in separate file
  626. // open and read the file
  627. nsCOMPtr<nsIFile> file;
  628. rv = GetLocalFileForDiskCacheRecord(record,
  629. nsDiskCache::kMetaData,
  630. false,
  631. getter_AddRefs(file));
  632. NS_ENSURE_SUCCESS(rv, nullptr);
  633. CACHE_LOG_DEBUG(("CACHE: nsDiskCacheMap::ReadDiskCacheEntry"
  634. "[this=%p] reading disk cache entry", this));
  635. PRFileDesc * fd = nullptr;
  636. // open the file - restricted to user, the data could be confidential
  637. rv = file->OpenNSPRFileDesc(PR_RDONLY, 00600, &fd);
  638. NS_ENSURE_SUCCESS(rv, nullptr);
  639. int32_t fileSize = PR_Available(fd);
  640. if (fileSize < 0) {
  641. // an error occurred. We could call PR_GetError(), but how would that help?
  642. rv = NS_ERROR_UNEXPECTED;
  643. } else {
  644. rv = EnsureBuffer(fileSize);
  645. if (NS_SUCCEEDED(rv)) {
  646. bytesRead = PR_Read(fd, mBuffer, fileSize);
  647. if (bytesRead < fileSize) {
  648. rv = NS_ERROR_UNEXPECTED;
  649. }
  650. }
  651. }
  652. PR_Close(fd);
  653. NS_ENSURE_SUCCESS(rv, nullptr);
  654. } else if (metaFile < (kNumBlockFiles + 1)) {
  655. // entry/metadata stored in cache block file
  656. // allocate buffer
  657. uint32_t blockCount = record->MetaBlockCount();
  658. bytesRead = blockCount * GetBlockSizeForIndex(metaFile);
  659. rv = EnsureBuffer(bytesRead);
  660. NS_ENSURE_SUCCESS(rv, nullptr);
  661. // read diskEntry, note when the blocks are at the end of file,
  662. // bytesRead may be less than blockSize*blockCount.
  663. // But the bytesRead should at least agree with the real disk entry size.
  664. rv = mBlockFile[metaFile - 1].ReadBlocks(mBuffer,
  665. record->MetaStartBlock(),
  666. blockCount,
  667. &bytesRead);
  668. NS_ENSURE_SUCCESS(rv, nullptr);
  669. }
  670. diskEntry = (nsDiskCacheEntry *)mBuffer;
  671. diskEntry->Unswap(); // disk to memory
  672. // Check if calculated size agrees with bytesRead
  673. if (bytesRead < 0 || (uint32_t)bytesRead < diskEntry->Size())
  674. return nullptr;
  675. // Return the buffer containing the diskEntry structure
  676. return diskEntry;
  677. }
  678. /**
  679. * CreateDiskCacheEntry(nsCacheEntry * entry)
  680. *
  681. * Prepare an nsCacheEntry for writing to disk
  682. */
  683. nsDiskCacheEntry *
  684. nsDiskCacheMap::CreateDiskCacheEntry(nsDiskCacheBinding * binding,
  685. uint32_t * aSize)
  686. {
  687. nsCacheEntry * entry = binding->mCacheEntry;
  688. if (!entry) return nullptr;
  689. // Store security info, if it is serializable
  690. nsCOMPtr<nsISupports> infoObj = entry->SecurityInfo();
  691. nsCOMPtr<nsISerializable> serializable = do_QueryInterface(infoObj);
  692. if (infoObj && !serializable) return nullptr;
  693. if (serializable) {
  694. nsCString info;
  695. nsresult rv = NS_SerializeToString(serializable, info);
  696. if (NS_FAILED(rv)) return nullptr;
  697. rv = entry->SetMetaDataElement("security-info", info.get());
  698. if (NS_FAILED(rv)) return nullptr;
  699. }
  700. uint32_t keySize = entry->Key()->Length() + 1;
  701. uint32_t metaSize = entry->MetaDataSize();
  702. uint32_t size = sizeof(nsDiskCacheEntry) + keySize + metaSize;
  703. if (aSize) *aSize = size;
  704. nsresult rv = EnsureBuffer(size);
  705. if (NS_FAILED(rv)) return nullptr;
  706. nsDiskCacheEntry *diskEntry = (nsDiskCacheEntry *)mBuffer;
  707. diskEntry->mHeaderVersion = nsDiskCache::kCurrentVersion;
  708. diskEntry->mMetaLocation = binding->mRecord.MetaLocation();
  709. diskEntry->mFetchCount = entry->FetchCount();
  710. diskEntry->mLastFetched = entry->LastFetched();
  711. diskEntry->mLastModified = entry->LastModified();
  712. diskEntry->mExpirationTime = entry->ExpirationTime();
  713. diskEntry->mDataSize = entry->DataSize();
  714. diskEntry->mKeySize = keySize;
  715. diskEntry->mMetaDataSize = metaSize;
  716. memcpy(diskEntry->Key(), entry->Key()->get(), keySize);
  717. rv = entry->FlattenMetaData(diskEntry->MetaData(), metaSize);
  718. if (NS_FAILED(rv)) return nullptr;
  719. return diskEntry;
  720. }
  721. nsresult
  722. nsDiskCacheMap::WriteDiskCacheEntry(nsDiskCacheBinding * binding)
  723. {
  724. CACHE_LOG_DEBUG(("CACHE: WriteDiskCacheEntry [%x]\n",
  725. binding->mRecord.HashNumber()));
  726. nsresult rv = NS_OK;
  727. uint32_t size;
  728. nsDiskCacheEntry * diskEntry = CreateDiskCacheEntry(binding, &size);
  729. if (!diskEntry) return NS_ERROR_UNEXPECTED;
  730. uint32_t fileIndex = CalculateFileIndex(size);
  731. // Deallocate old storage if necessary
  732. if (binding->mRecord.MetaLocationInitialized()) {
  733. // we have existing storage
  734. if ((binding->mRecord.MetaFile() == 0) &&
  735. (fileIndex == 0)) { // keeping the separate file
  736. // just decrement total
  737. DecrementTotalSize(binding->mRecord.MetaFileSize());
  738. NS_ASSERTION(binding->mRecord.MetaFileGeneration() == binding->mGeneration,
  739. "generations out of sync");
  740. } else {
  741. rv = DeleteStorage(&binding->mRecord, nsDiskCache::kMetaData);
  742. NS_ENSURE_SUCCESS(rv, rv);
  743. }
  744. }
  745. binding->mRecord.SetEvictionRank(ULONG_MAX - SecondsFromPRTime(PR_Now()));
  746. // write entry data to disk cache block file
  747. diskEntry->Swap();
  748. if (fileIndex != 0) {
  749. while (1) {
  750. uint32_t blockSize = GetBlockSizeForIndex(fileIndex);
  751. uint32_t blocks = ((size - 1) / blockSize) + 1;
  752. int32_t startBlock;
  753. rv = mBlockFile[fileIndex - 1].WriteBlocks(diskEntry, size, blocks,
  754. &startBlock);
  755. if (NS_SUCCEEDED(rv)) {
  756. // update binding and cache map record
  757. binding->mRecord.SetMetaBlocks(fileIndex, startBlock, blocks);
  758. rv = UpdateRecord(&binding->mRecord);
  759. NS_ENSURE_SUCCESS(rv, rv);
  760. // XXX we should probably write out bucket ourselves
  761. IncrementTotalSize(blocks, blockSize);
  762. break;
  763. }
  764. if (fileIndex == kNumBlockFiles) {
  765. fileIndex = 0; // write data to separate file
  766. break;
  767. }
  768. // try next block file
  769. fileIndex++;
  770. }
  771. }
  772. if (fileIndex == 0) {
  773. // Write entry data to separate file
  774. uint32_t metaFileSizeK = ((size + 0x03FF) >> 10); // round up to nearest 1k
  775. if (metaFileSizeK > kMaxDataSizeK)
  776. metaFileSizeK = kMaxDataSizeK;
  777. binding->mRecord.SetMetaFileGeneration(binding->mGeneration);
  778. binding->mRecord.SetMetaFileSize(metaFileSizeK);
  779. rv = UpdateRecord(&binding->mRecord);
  780. NS_ENSURE_SUCCESS(rv, rv);
  781. nsCOMPtr<nsIFile> localFile;
  782. rv = GetLocalFileForDiskCacheRecord(&binding->mRecord,
  783. nsDiskCache::kMetaData,
  784. true,
  785. getter_AddRefs(localFile));
  786. NS_ENSURE_SUCCESS(rv, rv);
  787. // open the file
  788. PRFileDesc * fd;
  789. // open the file - restricted to user, the data could be confidential
  790. rv = localFile->OpenNSPRFileDesc(PR_RDWR | PR_TRUNCATE | PR_CREATE_FILE, 00600, &fd);
  791. NS_ENSURE_SUCCESS(rv, rv);
  792. // write the file
  793. int32_t bytesWritten = PR_Write(fd, diskEntry, size);
  794. PRStatus err = PR_Close(fd);
  795. if ((bytesWritten != (int32_t)size) || (err != PR_SUCCESS)) {
  796. return NS_ERROR_UNEXPECTED;
  797. }
  798. IncrementTotalSize(metaFileSizeK);
  799. }
  800. return rv;
  801. }
  802. nsresult
  803. nsDiskCacheMap::ReadDataCacheBlocks(nsDiskCacheBinding * binding, char * buffer, uint32_t size)
  804. {
  805. CACHE_LOG_DEBUG(("CACHE: ReadDataCacheBlocks [%x size=%u]\n",
  806. binding->mRecord.HashNumber(), size));
  807. uint32_t fileIndex = binding->mRecord.DataFile();
  808. int32_t readSize = size;
  809. nsresult rv = mBlockFile[fileIndex - 1].ReadBlocks(buffer,
  810. binding->mRecord.DataStartBlock(),
  811. binding->mRecord.DataBlockCount(),
  812. &readSize);
  813. NS_ENSURE_SUCCESS(rv, rv);
  814. if (readSize < (int32_t)size) {
  815. rv = NS_ERROR_UNEXPECTED;
  816. }
  817. return rv;
  818. }
  819. nsresult
  820. nsDiskCacheMap::WriteDataCacheBlocks(nsDiskCacheBinding * binding, char * buffer, uint32_t size)
  821. {
  822. CACHE_LOG_DEBUG(("CACHE: WriteDataCacheBlocks [%x size=%u]\n",
  823. binding->mRecord.HashNumber(), size));
  824. nsresult rv = NS_OK;
  825. // determine block file & number of blocks
  826. uint32_t fileIndex = CalculateFileIndex(size);
  827. uint32_t blockCount = 0;
  828. int32_t startBlock = 0;
  829. if (size > 0) {
  830. // if fileIndex is 0, bad things happen below, which makes gcc 4.7
  831. // complain, but it's not supposed to happen. See bug 854105.
  832. MOZ_ASSERT(fileIndex);
  833. while (fileIndex) {
  834. uint32_t blockSize = GetBlockSizeForIndex(fileIndex);
  835. blockCount = ((size - 1) / blockSize) + 1;
  836. rv = mBlockFile[fileIndex - 1].WriteBlocks(buffer, size, blockCount,
  837. &startBlock);
  838. if (NS_SUCCEEDED(rv)) {
  839. IncrementTotalSize(blockCount, blockSize);
  840. break;
  841. }
  842. if (fileIndex == kNumBlockFiles)
  843. return rv;
  844. fileIndex++;
  845. }
  846. }
  847. // update binding and cache map record
  848. binding->mRecord.SetDataBlocks(fileIndex, startBlock, blockCount);
  849. if (!binding->mDoomed) {
  850. rv = UpdateRecord(&binding->mRecord);
  851. }
  852. return rv;
  853. }
  854. nsresult
  855. nsDiskCacheMap::DeleteStorage(nsDiskCacheRecord * record)
  856. {
  857. nsresult rv1 = DeleteStorage(record, nsDiskCache::kData);
  858. nsresult rv2 = DeleteStorage(record, nsDiskCache::kMetaData);
  859. return NS_FAILED(rv1) ? rv1 : rv2;
  860. }
  861. nsresult
  862. nsDiskCacheMap::DeleteStorage(nsDiskCacheRecord * record, bool metaData)
  863. {
  864. CACHE_LOG_DEBUG(("CACHE: DeleteStorage [%x %u]\n", record->HashNumber(),
  865. metaData));
  866. nsresult rv = NS_ERROR_UNEXPECTED;
  867. uint32_t fileIndex = metaData ? record->MetaFile() : record->DataFile();
  868. nsCOMPtr<nsIFile> file;
  869. if (fileIndex == 0) {
  870. // delete the file
  871. uint32_t sizeK = metaData ? record->MetaFileSize() : record->DataFileSize();
  872. // XXX if sizeK == USHRT_MAX, stat file for actual size
  873. rv = GetFileForDiskCacheRecord(record, metaData, false, getter_AddRefs(file));
  874. if (NS_SUCCEEDED(rv)) {
  875. rv = file->Remove(false); // false == non-recursive
  876. }
  877. DecrementTotalSize(sizeK);
  878. } else if (fileIndex < (kNumBlockFiles + 1)) {
  879. // deallocate blocks
  880. uint32_t startBlock = metaData ? record->MetaStartBlock() : record->DataStartBlock();
  881. uint32_t blockCount = metaData ? record->MetaBlockCount() : record->DataBlockCount();
  882. rv = mBlockFile[fileIndex - 1].DeallocateBlocks(startBlock, blockCount);
  883. DecrementTotalSize(blockCount, GetBlockSizeForIndex(fileIndex));
  884. }
  885. if (metaData) record->ClearMetaLocation();
  886. else record->ClearDataLocation();
  887. return rv;
  888. }
  889. nsresult
  890. nsDiskCacheMap::GetFileForDiskCacheRecord(nsDiskCacheRecord * record,
  891. bool meta,
  892. bool createPath,
  893. nsIFile ** result)
  894. {
  895. if (!mCacheDirectory) return NS_ERROR_NOT_AVAILABLE;
  896. nsCOMPtr<nsIFile> file;
  897. nsresult rv = mCacheDirectory->Clone(getter_AddRefs(file));
  898. if (NS_FAILED(rv)) return rv;
  899. uint32_t hash = record->HashNumber();
  900. // The file is stored under subdirectories according to the hash number:
  901. // 0x01234567 -> 0/12/
  902. rv = file->AppendNative(nsPrintfCString("%X", hash >> 28));
  903. if (NS_FAILED(rv)) return rv;
  904. rv = file->AppendNative(nsPrintfCString("%02X", (hash >> 20) & 0xFF));
  905. if (NS_FAILED(rv)) return rv;
  906. bool exists;
  907. if (createPath && (NS_FAILED(file->Exists(&exists)) || !exists)) {
  908. rv = file->Create(nsIFile::DIRECTORY_TYPE, 0700);
  909. if (NS_FAILED(rv)) return rv;
  910. }
  911. int16_t generation = record->Generation();
  912. char name[32];
  913. // Cut the beginning of the hash that was used in the path
  914. ::SprintfLiteral(name, "%05X%c%02X", hash & 0xFFFFF, (meta ? 'm' : 'd'),
  915. generation);
  916. rv = file->AppendNative(nsDependentCString(name));
  917. if (NS_FAILED(rv)) return rv;
  918. NS_IF_ADDREF(*result = file);
  919. return rv;
  920. }
  921. nsresult
  922. nsDiskCacheMap::GetLocalFileForDiskCacheRecord(nsDiskCacheRecord * record,
  923. bool meta,
  924. bool createPath,
  925. nsIFile ** result)
  926. {
  927. nsCOMPtr<nsIFile> file;
  928. nsresult rv = GetFileForDiskCacheRecord(record,
  929. meta,
  930. createPath,
  931. getter_AddRefs(file));
  932. if (NS_FAILED(rv)) return rv;
  933. NS_IF_ADDREF(*result = file);
  934. return rv;
  935. }
  936. nsresult
  937. nsDiskCacheMap::GetBlockFileForIndex(uint32_t index, nsIFile ** result)
  938. {
  939. if (!mCacheDirectory) return NS_ERROR_NOT_AVAILABLE;
  940. nsCOMPtr<nsIFile> file;
  941. nsresult rv = mCacheDirectory->Clone(getter_AddRefs(file));
  942. if (NS_FAILED(rv)) return rv;
  943. char name[32];
  944. ::SprintfLiteral(name, "_CACHE_%03d_", index + 1);
  945. rv = file->AppendNative(nsDependentCString(name));
  946. if (NS_FAILED(rv)) return rv;
  947. NS_IF_ADDREF(*result = file);
  948. return rv;
  949. }
  950. uint32_t
  951. nsDiskCacheMap::CalculateFileIndex(uint32_t size)
  952. {
  953. // We prefer to use block file with larger block if the wasted space would
  954. // be the same. E.g. store entry with size of 3073 bytes in 1 4K-block
  955. // instead of in 4 1K-blocks.
  956. if (size <= 3 * BLOCK_SIZE_FOR_INDEX(1)) return 1;
  957. if (size <= 3 * BLOCK_SIZE_FOR_INDEX(2)) return 2;
  958. if (size <= 4 * BLOCK_SIZE_FOR_INDEX(3)) return 3;
  959. return 0;
  960. }
  961. nsresult
  962. nsDiskCacheMap::EnsureBuffer(uint32_t bufSize)
  963. {
  964. if (mBufferSize < bufSize) {
  965. char * buf = (char *)PR_REALLOC(mBuffer, bufSize);
  966. if (!buf) {
  967. mBufferSize = 0;
  968. return NS_ERROR_OUT_OF_MEMORY;
  969. }
  970. mBuffer = buf;
  971. mBufferSize = bufSize;
  972. }
  973. return NS_OK;
  974. }
  975. void
  976. nsDiskCacheMap::NotifyCapacityChange(uint32_t capacity)
  977. {
  978. // Heuristic 1. average cache entry size is probably around 1KB
  979. // Heuristic 2. we don't want more than 32MB reserved to store the record
  980. // map in memory.
  981. const int32_t RECORD_COUNT_LIMIT = 32 * 1024 * 1024 / sizeof(nsDiskCacheRecord);
  982. int32_t maxRecordCount = std::min(int32_t(capacity), RECORD_COUNT_LIMIT);
  983. if (mMaxRecordCount < maxRecordCount) {
  984. // We can only grow
  985. mMaxRecordCount = maxRecordCount;
  986. }
  987. }
  988. size_t
  989. nsDiskCacheMap::SizeOfExcludingThis(MallocSizeOf aMallocSizeOf)
  990. {
  991. size_t usage = aMallocSizeOf(mRecordArray);
  992. usage += aMallocSizeOf(mBuffer);
  993. usage += aMallocSizeOf(mMapFD);
  994. usage += aMallocSizeOf(mCleanFD);
  995. usage += aMallocSizeOf(mCacheDirectory);
  996. usage += aMallocSizeOf(mCleanCacheTimer);
  997. for (int i = 0; i < kNumBlockFiles; i++) {
  998. usage += mBlockFile[i].SizeOfExcludingThis(aMallocSizeOf);
  999. }
  1000. return usage;
  1001. }
  1002. nsresult
  1003. nsDiskCacheMap::InitCacheClean(nsIFile * cacheDirectory,
  1004. nsDiskCache::CorruptCacheInfo * corruptInfo)
  1005. {
  1006. // The _CACHE_CLEAN_ file will be used in the future to determine
  1007. // if the cache is clean or not.
  1008. bool cacheCleanFileExists = false;
  1009. nsCOMPtr<nsIFile> cacheCleanFile;
  1010. nsresult rv = cacheDirectory->GetParent(getter_AddRefs(cacheCleanFile));
  1011. if (NS_SUCCEEDED(rv)) {
  1012. rv = cacheCleanFile->AppendNative(
  1013. NS_LITERAL_CSTRING("_CACHE_CLEAN_"));
  1014. if (NS_SUCCEEDED(rv)) {
  1015. // Check if the file already exists, if it does, we will later read the
  1016. // value and report it to telemetry.
  1017. cacheCleanFile->Exists(&cacheCleanFileExists);
  1018. }
  1019. }
  1020. if (NS_FAILED(rv)) {
  1021. NS_WARNING("Could not build cache clean file path");
  1022. *corruptInfo = nsDiskCache::kCacheCleanFilePathError;
  1023. return rv;
  1024. }
  1025. // Make sure the _CACHE_CLEAN_ file exists
  1026. rv = cacheCleanFile->OpenNSPRFileDesc(PR_RDWR | PR_CREATE_FILE,
  1027. 00600, &mCleanFD);
  1028. if (NS_FAILED(rv)) {
  1029. NS_WARNING("Could not open cache clean file");
  1030. *corruptInfo = nsDiskCache::kCacheCleanOpenFileError;
  1031. return rv;
  1032. }
  1033. if (cacheCleanFileExists) {
  1034. char clean = '0';
  1035. int32_t bytesRead = PR_Read(mCleanFD, &clean, 1);
  1036. if (bytesRead != 1) {
  1037. NS_WARNING("Could not read _CACHE_CLEAN_ file contents");
  1038. }
  1039. }
  1040. // Create a timer that will be used to validate the cache
  1041. // as long as an activity threshold was met
  1042. mCleanCacheTimer = do_CreateInstance("@mozilla.org/timer;1", &rv);
  1043. if (NS_SUCCEEDED(rv)) {
  1044. mCleanCacheTimer->SetTarget(nsCacheService::GlobalInstance()->mCacheIOThread);
  1045. rv = ResetCacheTimer();
  1046. }
  1047. if (NS_FAILED(rv)) {
  1048. NS_WARNING("Could not create cache clean timer");
  1049. mCleanCacheTimer = nullptr;
  1050. *corruptInfo = nsDiskCache::kCacheCleanTimerError;
  1051. return rv;
  1052. }
  1053. return NS_OK;
  1054. }
  1055. nsresult
  1056. nsDiskCacheMap::WriteCacheClean(bool clean)
  1057. {
  1058. nsCacheService::AssertOwnsLock();
  1059. if (!mCleanFD) {
  1060. NS_WARNING("Cache clean file is not open!");
  1061. return NS_ERROR_FAILURE;
  1062. }
  1063. CACHE_LOG_DEBUG(("CACHE: WriteCacheClean: %d\n", clean? 1 : 0));
  1064. // I'm using a simple '1' or '0' to denote cache clean
  1065. // since it can be edited easily by any text editor for testing.
  1066. char data = clean? '1' : '0';
  1067. int32_t filePos = PR_Seek(mCleanFD, 0, PR_SEEK_SET);
  1068. if (filePos != 0) {
  1069. NS_WARNING("Could not seek in cache clean file!");
  1070. return NS_ERROR_FAILURE;
  1071. }
  1072. int32_t bytesWritten = PR_Write(mCleanFD, &data, 1);
  1073. if (bytesWritten != 1) {
  1074. NS_WARNING("Could not write cache clean file!");
  1075. return NS_ERROR_FAILURE;
  1076. }
  1077. PRStatus err = PR_Sync(mCleanFD);
  1078. if (err != PR_SUCCESS) {
  1079. NS_WARNING("Could not flush cache clean file!");
  1080. }
  1081. return NS_OK;
  1082. }
  1083. nsresult
  1084. nsDiskCacheMap::InvalidateCache()
  1085. {
  1086. nsCacheService::AssertOwnsLock();
  1087. CACHE_LOG_DEBUG(("CACHE: InvalidateCache\n"));
  1088. nsresult rv;
  1089. if (!mIsDirtyCacheFlushed) {
  1090. rv = WriteCacheClean(false);
  1091. if (NS_FAILED(rv)) {
  1092. return rv;
  1093. }
  1094. mIsDirtyCacheFlushed = true;
  1095. }
  1096. rv = ResetCacheTimer();
  1097. NS_ENSURE_SUCCESS(rv, rv);
  1098. return NS_OK;
  1099. }
  1100. nsresult
  1101. nsDiskCacheMap::ResetCacheTimer(int32_t timeout)
  1102. {
  1103. mCleanCacheTimer->Cancel();
  1104. nsresult rv =
  1105. mCleanCacheTimer->InitWithFuncCallback(RevalidateTimerCallback,
  1106. nullptr, timeout,
  1107. nsITimer::TYPE_ONE_SHOT);
  1108. NS_ENSURE_SUCCESS(rv, rv);
  1109. mLastInvalidateTime = PR_IntervalNow();
  1110. return rv;
  1111. }
  1112. void
  1113. nsDiskCacheMap::RevalidateTimerCallback(nsITimer *aTimer, void *arg)
  1114. {
  1115. nsCacheServiceAutoLock lock;
  1116. if (!nsCacheService::gService->mDiskDevice ||
  1117. !nsCacheService::gService->mDiskDevice->Initialized()) {
  1118. return;
  1119. }
  1120. nsDiskCacheMap *diskCacheMap =
  1121. &nsCacheService::gService->mDiskDevice->mCacheMap;
  1122. // If we have less than kRevalidateCacheTimeout since the last timer was
  1123. // issued then another thread called InvalidateCache. This won't catch
  1124. // all cases where we wanted to cancel the timer, but under the lock it
  1125. // is always OK to revalidate as long as IsCacheInSafeState() returns
  1126. // true. We just want to avoid revalidating when we can to reduce IO
  1127. // and this check will do that.
  1128. uint32_t delta =
  1129. PR_IntervalToMilliseconds(PR_IntervalNow() -
  1130. diskCacheMap->mLastInvalidateTime) +
  1131. kRevalidateCacheTimeoutTolerance;
  1132. if (delta < kRevalidateCacheTimeout) {
  1133. diskCacheMap->ResetCacheTimer();
  1134. return;
  1135. }
  1136. nsresult rv = diskCacheMap->RevalidateCache();
  1137. if (NS_FAILED(rv)) {
  1138. diskCacheMap->ResetCacheTimer(kRevalidateCacheErrorTimeout);
  1139. }
  1140. }
  1141. bool
  1142. nsDiskCacheMap::IsCacheInSafeState()
  1143. {
  1144. return nsCacheService::GlobalInstance()->IsDoomListEmpty();
  1145. }
  1146. nsresult
  1147. nsDiskCacheMap::RevalidateCache()
  1148. {
  1149. CACHE_LOG_DEBUG(("CACHE: RevalidateCache\n"));
  1150. nsresult rv;
  1151. if (!IsCacheInSafeState()) {
  1152. CACHE_LOG_DEBUG(("CACHE: Revalidation should not performed because "
  1153. "cache not in a safe state\n"));
  1154. // Normally we would return an error here, but there is a bug where
  1155. // the doom list sometimes gets an entry 'stuck' and doens't clear it
  1156. // until browser shutdown. So we allow revalidation for the time being
  1157. // to get proper telemetry data of how much the cache corruption plan
  1158. // would help.
  1159. }
  1160. // If telemetry data shows it is worth it, we'll be flushing headers and
  1161. // records before flushing the clean cache file.
  1162. // Write out the _CACHE_CLEAN_ file with '1'
  1163. rv = WriteCacheClean(true);
  1164. if (NS_FAILED(rv)) {
  1165. return rv;
  1166. }
  1167. mIsDirtyCacheFlushed = false;
  1168. return NS_OK;
  1169. }