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