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