/src/mozilla-central/netwerk/cache/nsDiskCacheBlockFile.cpp
Line | Count | Source (jump to first uncovered line) |
1 | | /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- |
2 | | * |
3 | | * This Source Code Form is subject to the terms of the Mozilla Public |
4 | | * License, v. 2.0. If a copy of the MPL was not distributed with this |
5 | | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ |
6 | | |
7 | | #include "nsCache.h" |
8 | | #include "nsDiskCache.h" |
9 | | #include "nsDiskCacheBlockFile.h" |
10 | | #include "mozilla/FileUtils.h" |
11 | | #include "mozilla/IntegerPrintfMacros.h" |
12 | | #include "mozilla/MemoryReporting.h" |
13 | | #include <algorithm> |
14 | | |
15 | | using namespace mozilla; |
16 | | |
17 | | /****************************************************************************** |
18 | | * nsDiskCacheBlockFile - |
19 | | *****************************************************************************/ |
20 | | |
21 | | /****************************************************************************** |
22 | | * Open |
23 | | *****************************************************************************/ |
24 | | nsresult |
25 | | nsDiskCacheBlockFile::Open(nsIFile * blockFile, |
26 | | uint32_t blockSize, |
27 | | uint32_t bitMapSize, |
28 | | nsDiskCache::CorruptCacheInfo * corruptInfo) |
29 | 0 | { |
30 | 0 | NS_ENSURE_ARG_POINTER(corruptInfo); |
31 | 0 | *corruptInfo = nsDiskCache::kUnexpectedError; |
32 | 0 |
|
33 | 0 | if (bitMapSize % 32) { |
34 | 0 | *corruptInfo = nsDiskCache::kInvalidArgPointer; |
35 | 0 | return NS_ERROR_INVALID_ARG; |
36 | 0 | } |
37 | 0 | |
38 | 0 | mBlockSize = blockSize; |
39 | 0 | mBitMapWords = bitMapSize / 32; |
40 | 0 | uint32_t bitMapBytes = mBitMapWords * 4; |
41 | 0 |
|
42 | 0 | // open the file - restricted to user, the data could be confidential |
43 | 0 | nsresult rv = blockFile->OpenNSPRFileDesc(PR_RDWR | PR_CREATE_FILE, 00600, &mFD); |
44 | 0 | if (NS_FAILED(rv)) { |
45 | 0 | *corruptInfo = nsDiskCache::kCouldNotCreateBlockFile; |
46 | 0 | CACHE_LOG_DEBUG(("CACHE: nsDiskCacheBlockFile::Open " |
47 | 0 | "[this=%p] unable to open or create file: %" PRId32, |
48 | 0 | this, static_cast<uint32_t>(rv))); |
49 | 0 | return rv; // unable to open or create file |
50 | 0 | } |
51 | 0 |
|
52 | 0 | // allocate bit map buffer |
53 | 0 | mBitMap = new uint32_t[mBitMapWords]; |
54 | 0 |
|
55 | 0 | // check if we just creating the file |
56 | 0 | mFileSize = PR_Available(mFD); |
57 | 0 | if (mFileSize < 0) { |
58 | 0 | // XXX an error occurred. We could call PR_GetError(), but how would that help? |
59 | 0 | *corruptInfo = nsDiskCache::kBlockFileSizeError; |
60 | 0 | rv = NS_ERROR_UNEXPECTED; |
61 | 0 | goto error_exit; |
62 | 0 | } |
63 | 0 | if (mFileSize == 0) { |
64 | 0 | // initialize bit map and write it |
65 | 0 | memset(mBitMap, 0, bitMapBytes); |
66 | 0 | if (!Write(0, mBitMap, bitMapBytes)) { |
67 | 0 | *corruptInfo = nsDiskCache::kBlockFileBitMapWriteError; |
68 | 0 | goto error_exit; |
69 | 0 | } |
70 | 0 | |
71 | 0 | } else if ((uint32_t)mFileSize < bitMapBytes) { |
72 | 0 | *corruptInfo = nsDiskCache::kBlockFileSizeLessThanBitMap; |
73 | 0 | rv = NS_ERROR_UNEXPECTED; // XXX NS_ERROR_CACHE_INVALID; |
74 | 0 | goto error_exit; |
75 | 0 |
|
76 | 0 | } else { |
77 | 0 | // read the bit map |
78 | 0 | const int32_t bytesRead = PR_Read(mFD, mBitMap, bitMapBytes); |
79 | 0 | if ((bytesRead < 0) || ((uint32_t)bytesRead < bitMapBytes)) { |
80 | 0 | *corruptInfo = nsDiskCache::kBlockFileBitMapReadError; |
81 | 0 | rv = NS_ERROR_UNEXPECTED; |
82 | 0 | goto error_exit; |
83 | 0 | } |
84 | 0 | #if defined(IS_LITTLE_ENDIAN) |
85 | 0 | // Swap from network format |
86 | 0 | for (unsigned int i = 0; i < mBitMapWords; ++i) |
87 | 0 | mBitMap[i] = ntohl(mBitMap[i]); |
88 | 0 | #endif |
89 | 0 | // validate block file size |
90 | 0 | // Because not whole blocks are written, the size may be a |
91 | 0 | // little bit smaller than used blocks times blocksize, |
92 | 0 | // because the last block will generally not be 'whole'. |
93 | 0 | const uint32_t estimatedSize = CalcBlockFileSize(); |
94 | 0 | if ((uint32_t)mFileSize + blockSize < estimatedSize) { |
95 | 0 | *corruptInfo = nsDiskCache::kBlockFileEstimatedSizeError; |
96 | 0 | rv = NS_ERROR_UNEXPECTED; |
97 | 0 | goto error_exit; |
98 | 0 | } |
99 | 0 | } |
100 | 0 | CACHE_LOG_DEBUG(("CACHE: nsDiskCacheBlockFile::Open [this=%p] succeeded", |
101 | 0 | this)); |
102 | 0 | return NS_OK; |
103 | 0 |
|
104 | 0 | error_exit: |
105 | 0 | CACHE_LOG_DEBUG(("CACHE: nsDiskCacheBlockFile::Open [this=%p] failed with " |
106 | 0 | "error %" PRId32, this, static_cast<uint32_t>(rv))); |
107 | 0 | Close(false); |
108 | 0 | return rv; |
109 | 0 | } |
110 | | |
111 | | |
112 | | /****************************************************************************** |
113 | | * Close |
114 | | *****************************************************************************/ |
115 | | nsresult |
116 | | nsDiskCacheBlockFile::Close(bool flush) |
117 | 0 | { |
118 | 0 | nsresult rv = NS_OK; |
119 | 0 |
|
120 | 0 | if (mFD) { |
121 | 0 | if (flush) |
122 | 0 | rv = FlushBitMap(); |
123 | 0 | PRStatus err = PR_Close(mFD); |
124 | 0 | if (NS_SUCCEEDED(rv) && (err != PR_SUCCESS)) |
125 | 0 | rv = NS_ERROR_UNEXPECTED; |
126 | 0 | mFD = nullptr; |
127 | 0 | } |
128 | 0 |
|
129 | 0 | if (mBitMap) { |
130 | 0 | delete [] mBitMap; |
131 | 0 | mBitMap = nullptr; |
132 | 0 | } |
133 | 0 |
|
134 | 0 | return rv; |
135 | 0 | } |
136 | | |
137 | | |
138 | | /****************************************************************************** |
139 | | * AllocateBlocks |
140 | | * |
141 | | * Allocates 1-4 blocks, using a first fit strategy, |
142 | | * so that no group of blocks spans a quad block boundary. |
143 | | * |
144 | | * Returns block number of first block allocated or -1 on failure. |
145 | | * |
146 | | *****************************************************************************/ |
147 | | int32_t |
148 | | nsDiskCacheBlockFile::AllocateBlocks(int32_t numBlocks) |
149 | 0 | { |
150 | 0 | const int maxPos = 32 - numBlocks; |
151 | 0 | const uint32_t mask = (0x01 << numBlocks) - 1; |
152 | 0 | for (unsigned int i = 0; i < mBitMapWords; ++i) { |
153 | 0 | uint32_t mapWord = ~mBitMap[i]; // flip bits so free bits are 1 |
154 | 0 | if (mapWord) { // At least one free bit |
155 | 0 | // Binary search for first free bit in word |
156 | 0 | int bit = 0; |
157 | 0 | if ((mapWord & 0x0FFFF) == 0) { bit |= 16; mapWord >>= 16; } |
158 | 0 | if ((mapWord & 0x000FF) == 0) { bit |= 8; mapWord >>= 8; } |
159 | 0 | if ((mapWord & 0x0000F) == 0) { bit |= 4; mapWord >>= 4; } |
160 | 0 | if ((mapWord & 0x00003) == 0) { bit |= 2; mapWord >>= 2; } |
161 | 0 | if ((mapWord & 0x00001) == 0) { bit |= 1; mapWord >>= 1; } |
162 | 0 | // Find first fit for mask |
163 | 0 | for (; bit <= maxPos; ++bit) { |
164 | 0 | // all bits selected by mask are 1, so free |
165 | 0 | if ((mask & mapWord) == mask) { |
166 | 0 | mBitMap[i] |= mask << bit; |
167 | 0 | mBitMapDirty = true; |
168 | 0 | return (int32_t)i * 32 + bit; |
169 | 0 | } |
170 | 0 | } |
171 | 0 | } |
172 | 0 | } |
173 | 0 |
|
174 | 0 | return -1; |
175 | 0 | } |
176 | | |
177 | | |
178 | | /****************************************************************************** |
179 | | * DeallocateBlocks |
180 | | *****************************************************************************/ |
181 | | nsresult |
182 | | nsDiskCacheBlockFile::DeallocateBlocks( int32_t startBlock, int32_t numBlocks) |
183 | 0 | { |
184 | 0 | if (!mFD) return NS_ERROR_NOT_AVAILABLE; |
185 | 0 | |
186 | 0 | if ((startBlock < 0) || ((uint32_t)startBlock > mBitMapWords * 32 - 1) || |
187 | 0 | (numBlocks < 1) || (numBlocks > 4)) |
188 | 0 | return NS_ERROR_ILLEGAL_VALUE; |
189 | 0 | |
190 | 0 | const int32_t startWord = startBlock >> 5; // Divide by 32 |
191 | 0 | const uint32_t startBit = startBlock & 31; // Modulo by 32 |
192 | 0 |
|
193 | 0 | // make sure requested deallocation doesn't span a word boundary |
194 | 0 | if (startBit + numBlocks > 32) return NS_ERROR_UNEXPECTED; |
195 | 0 | uint32_t mask = ((0x01 << numBlocks) - 1) << startBit; |
196 | 0 |
|
197 | 0 | // make sure requested deallocation is currently allocated |
198 | 0 | if ((mBitMap[startWord] & mask) != mask) return NS_ERROR_ABORT; |
199 | 0 | |
200 | 0 | mBitMap[startWord] ^= mask; // flips the bits off; |
201 | 0 | mBitMapDirty = true; |
202 | 0 | // XXX rv = FlushBitMap(); // coherency vs. performance |
203 | 0 | return NS_OK; |
204 | 0 | } |
205 | | |
206 | | |
207 | | /****************************************************************************** |
208 | | * WriteBlocks |
209 | | *****************************************************************************/ |
210 | | nsresult |
211 | | nsDiskCacheBlockFile::WriteBlocks( void * buffer, |
212 | | uint32_t size, |
213 | | int32_t numBlocks, |
214 | | int32_t * startBlock) |
215 | 0 | { |
216 | 0 | // presume buffer != nullptr and startBlock != nullptr |
217 | 0 | NS_ENSURE_TRUE(mFD, NS_ERROR_NOT_AVAILABLE); |
218 | 0 |
|
219 | 0 | // allocate some blocks in the cache block file |
220 | 0 | *startBlock = AllocateBlocks(numBlocks); |
221 | 0 | if (*startBlock < 0) |
222 | 0 | return NS_ERROR_NOT_AVAILABLE; |
223 | 0 | |
224 | 0 | // seek to block position |
225 | 0 | int32_t blockPos = mBitMapWords * 4 + *startBlock * mBlockSize; |
226 | 0 |
|
227 | 0 | // write the blocks |
228 | 0 | return Write(blockPos, buffer, size) ? NS_OK : NS_ERROR_FAILURE; |
229 | 0 | } |
230 | | |
231 | | |
232 | | /****************************************************************************** |
233 | | * ReadBlocks |
234 | | *****************************************************************************/ |
235 | | nsresult |
236 | | nsDiskCacheBlockFile::ReadBlocks( void * buffer, |
237 | | int32_t startBlock, |
238 | | int32_t numBlocks, |
239 | | int32_t * bytesRead) |
240 | 0 | { |
241 | 0 | // presume buffer != nullptr and bytesRead != bytesRead |
242 | 0 |
|
243 | 0 | if (!mFD) return NS_ERROR_NOT_AVAILABLE; |
244 | 0 | nsresult rv = VerifyAllocation(startBlock, numBlocks); |
245 | 0 | if (NS_FAILED(rv)) return rv; |
246 | 0 | |
247 | 0 | // seek to block position |
248 | 0 | int32_t blockPos = mBitMapWords * 4 + startBlock * mBlockSize; |
249 | 0 | int32_t filePos = PR_Seek(mFD, blockPos, PR_SEEK_SET); |
250 | 0 | if (filePos != blockPos) return NS_ERROR_UNEXPECTED; |
251 | 0 | |
252 | 0 | // read the blocks |
253 | 0 | int32_t bytesToRead = *bytesRead; |
254 | 0 | if ((bytesToRead <= 0) || ((uint32_t)bytesToRead > mBlockSize * numBlocks)) { |
255 | 0 | bytesToRead = mBlockSize * numBlocks; |
256 | 0 | } |
257 | 0 | *bytesRead = PR_Read(mFD, buffer, bytesToRead); |
258 | 0 |
|
259 | 0 | CACHE_LOG_DEBUG(("CACHE: nsDiskCacheBlockFile::Read [this=%p] " |
260 | 0 | "returned %d / %d bytes", this, *bytesRead, bytesToRead)); |
261 | 0 |
|
262 | 0 | return NS_OK; |
263 | 0 | } |
264 | | |
265 | | |
266 | | /****************************************************************************** |
267 | | * FlushBitMap |
268 | | *****************************************************************************/ |
269 | | nsresult |
270 | | nsDiskCacheBlockFile::FlushBitMap() |
271 | 0 | { |
272 | 0 | if (!mBitMapDirty) return NS_OK; |
273 | 0 | |
274 | 0 | #if defined(IS_LITTLE_ENDIAN) |
275 | 0 | uint32_t *bitmap = new uint32_t[mBitMapWords]; |
276 | 0 | // Copy and swap to network format |
277 | 0 | uint32_t *p = bitmap; |
278 | 0 | for (unsigned int i = 0; i < mBitMapWords; ++i, ++p) |
279 | 0 | *p = htonl(mBitMap[i]); |
280 | | #else |
281 | | uint32_t *bitmap = mBitMap; |
282 | | #endif |
283 | |
|
284 | 0 | // write bitmap |
285 | 0 | bool written = Write(0, bitmap, mBitMapWords * 4); |
286 | 0 | #if defined(IS_LITTLE_ENDIAN) |
287 | 0 | delete [] bitmap; |
288 | 0 | #endif |
289 | 0 | if (!written) |
290 | 0 | return NS_ERROR_UNEXPECTED; |
291 | 0 | |
292 | 0 | PRStatus err = PR_Sync(mFD); |
293 | 0 | if (err != PR_SUCCESS) return NS_ERROR_UNEXPECTED; |
294 | 0 | |
295 | 0 | mBitMapDirty = false; |
296 | 0 | return NS_OK; |
297 | 0 | } |
298 | | |
299 | | |
300 | | /****************************************************************************** |
301 | | * VerifyAllocation |
302 | | * |
303 | | * Return values: |
304 | | * NS_OK if all bits are marked allocated |
305 | | * NS_ERROR_ILLEGAL_VALUE if parameters don't obey constraints |
306 | | * NS_ERROR_FAILURE if some or all the bits are marked unallocated |
307 | | * |
308 | | *****************************************************************************/ |
309 | | nsresult |
310 | | nsDiskCacheBlockFile::VerifyAllocation( int32_t startBlock, int32_t numBlocks) |
311 | 0 | { |
312 | 0 | if ((startBlock < 0) || ((uint32_t)startBlock > mBitMapWords * 32 - 1) || |
313 | 0 | (numBlocks < 1) || (numBlocks > 4)) |
314 | 0 | return NS_ERROR_ILLEGAL_VALUE; |
315 | 0 | |
316 | 0 | const int32_t startWord = startBlock >> 5; // Divide by 32 |
317 | 0 | const uint32_t startBit = startBlock & 31; // Modulo by 32 |
318 | 0 |
|
319 | 0 | // make sure requested deallocation doesn't span a word boundary |
320 | 0 | if (startBit + numBlocks > 32) return NS_ERROR_ILLEGAL_VALUE; |
321 | 0 | uint32_t mask = ((0x01 << numBlocks) - 1) << startBit; |
322 | 0 |
|
323 | 0 | // check if all specified blocks are currently allocated |
324 | 0 | if ((mBitMap[startWord] & mask) != mask) return NS_ERROR_FAILURE; |
325 | 0 | |
326 | 0 | return NS_OK; |
327 | 0 | } |
328 | | |
329 | | |
330 | | /****************************************************************************** |
331 | | * CalcBlockFileSize |
332 | | * |
333 | | * Return size of the block file according to the bits set in mBitmap |
334 | | * |
335 | | *****************************************************************************/ |
336 | | uint32_t |
337 | | nsDiskCacheBlockFile::CalcBlockFileSize() |
338 | 0 | { |
339 | 0 | // search for last byte in mBitMap with allocated bits |
340 | 0 | uint32_t estimatedSize = mBitMapWords * 4; |
341 | 0 | int32_t i = mBitMapWords; |
342 | 0 | while (--i >= 0) { |
343 | 0 | if (mBitMap[i]) break; |
344 | 0 | } |
345 | 0 |
|
346 | 0 | if (i >= 0) { |
347 | 0 | // binary search to find last allocated bit in byte |
348 | 0 | uint32_t mapWord = mBitMap[i]; |
349 | 0 | uint32_t lastBit = 31; |
350 | 0 | if ((mapWord & 0xFFFF0000) == 0) { lastBit ^= 16; mapWord <<= 16; } |
351 | 0 | if ((mapWord & 0xFF000000) == 0) { lastBit ^= 8; mapWord <<= 8; } |
352 | 0 | if ((mapWord & 0xF0000000) == 0) { lastBit ^= 4; mapWord <<= 4; } |
353 | 0 | if ((mapWord & 0xC0000000) == 0) { lastBit ^= 2; mapWord <<= 2; } |
354 | 0 | if ((mapWord & 0x80000000) == 0) { lastBit ^= 1; mapWord <<= 1; } |
355 | 0 | estimatedSize += (i * 32 + lastBit + 1) * mBlockSize; |
356 | 0 | } |
357 | 0 |
|
358 | 0 | return estimatedSize; |
359 | 0 | } |
360 | | |
361 | | /****************************************************************************** |
362 | | * Write |
363 | | * |
364 | | * Wrapper around PR_Write that grows file in larger chunks to combat fragmentation |
365 | | * |
366 | | *****************************************************************************/ |
367 | | bool |
368 | | nsDiskCacheBlockFile::Write(int32_t offset, const void *buf, int32_t amount) |
369 | 0 | { |
370 | 0 | /* Grow the file to 4mb right away, then double it until the file grows to 20mb. |
371 | 0 | 20mb is a magic threshold because OSX stops autodefragging files bigger than that. |
372 | 0 | Beyond 20mb grow in 4mb chunks. |
373 | 0 | */ |
374 | 0 | const int32_t upTo = offset + amount; |
375 | 0 | // Use a conservative definition of 20MB |
376 | 0 | const int32_t minPreallocate = 4*1024*1024; |
377 | 0 | const int32_t maxPreallocate = 20*1000*1000; |
378 | 0 | if (mFileSize < upTo) { |
379 | 0 | // maximal file size |
380 | 0 | const int32_t maxFileSize = mBitMapWords * 4 * (mBlockSize * 8 + 1); |
381 | 0 | if (upTo > maxPreallocate) { |
382 | 0 | // grow the file as a multiple of minPreallocate |
383 | 0 | mFileSize = ((upTo + minPreallocate - 1) / minPreallocate) * minPreallocate; |
384 | 0 | } else { |
385 | 0 | // Grow quickly between 1MB to 20MB |
386 | 0 | if (mFileSize) |
387 | 0 | while(mFileSize < upTo) |
388 | 0 | mFileSize *= 2; |
389 | 0 | mFileSize = clamped(mFileSize, minPreallocate, maxPreallocate); |
390 | 0 | } |
391 | 0 | mFileSize = std::min(mFileSize, maxFileSize); |
392 | 0 | #if !defined(XP_MACOSX) |
393 | 0 | mozilla::fallocate(mFD, mFileSize); |
394 | 0 | #endif |
395 | 0 | } |
396 | 0 | if (PR_Seek(mFD, offset, PR_SEEK_SET) != offset) |
397 | 0 | return false; |
398 | 0 | return PR_Write(mFD, buf, amount) == amount; |
399 | 0 | } |
400 | | |
401 | | size_t |
402 | | nsDiskCacheBlockFile::SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) |
403 | 0 | { |
404 | 0 | return aMallocSizeOf(mBitMap) + aMallocSizeOf(mFD); |
405 | 0 | } |