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/. */
8 #include "nsDiskCache.h"
9 #include "nsDiskCacheBlockFile.h"
10 #include "mozilla/FileUtils.h"
12 using namespace mozilla
;
14 /******************************************************************************
15 * nsDiskCacheBlockFile -
16 *****************************************************************************/
18 /******************************************************************************
20 *****************************************************************************/
22 nsDiskCacheBlockFile::Open(nsIFile
* blockFile
,
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
);
42 *corruptInfo
= nsDiskCache::kCouldNotCreateBlockFile
;
43 CACHE_LOG_DEBUG(("CACHE: nsDiskCacheBlockFile::Open "
44 "[this=%p] unable to open or create file: %d",
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
);
55 // XXX an error occurred. We could call PR_GetError(), but how would that help?
56 *corruptInfo
= nsDiskCache::kBlockFileSizeError
;
57 rv
= NS_ERROR_UNEXPECTED
;
61 // initialize bit map and write it
62 memset(mBitMap
, 0, bitMapBytes
);
63 if (!Write(0, mBitMap
, bitMapBytes
)) {
64 *corruptInfo
= nsDiskCache::kBlockFileBitMapWriteError
;
68 } else if ((PRUint32
)mFileSize
< bitMapBytes
) {
69 *corruptInfo
= nsDiskCache::kBlockFileSizeLessThanBitMap
;
70 rv
= NS_ERROR_UNEXPECTED
; // XXX NS_ERROR_CACHE_INVALID;
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
;
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
]);
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
;
97 CACHE_LOG_DEBUG(("CACHE: nsDiskCacheBlockFile::Open [this=%p] succeeded",
102 CACHE_LOG_DEBUG(("CACHE: nsDiskCacheBlockFile::Open [this=%p] failed with "
103 "error %d", this, rv
));
109 /******************************************************************************
111 *****************************************************************************/
113 nsDiskCacheBlockFile::Close(bool flush
)
120 PRStatus err
= PR_Close(mFD
);
121 if (NS_SUCCEEDED(rv
) && (err
!= PR_SUCCESS
))
122 rv
= NS_ERROR_UNEXPECTED
;
135 /******************************************************************************
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 *****************************************************************************/
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
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
;
165 return (PRInt32
)i
* 32 + bit
;
175 /******************************************************************************
177 *****************************************************************************/
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;
199 // XXX rv = FlushBitMap(); // coherency vs. performance
204 /******************************************************************************
206 *****************************************************************************/
208 nsDiskCacheBlockFile::WriteBlocks( void * buffer
,
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
);
219 return NS_ERROR_NOT_AVAILABLE
;
221 // seek to block position
222 PRInt32 blockPos
= mBitMapWords
* 4 + *startBlock
* mBlockSize
;
225 return Write(blockPos
, buffer
, size
) ? NS_OK
: NS_ERROR_FAILURE
;
229 /******************************************************************************
231 *****************************************************************************/
233 nsDiskCacheBlockFile::ReadBlocks( void * buffer
,
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
;
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
));
263 /******************************************************************************
265 *****************************************************************************/
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
]);
278 PRUint32
*bitmap
= mBitMap
;
282 bool written
= Write(0, bitmap
, mBitMapWords
* 4);
283 #if defined(IS_LITTLE_ENDIAN)
287 return NS_ERROR_UNEXPECTED
;
289 PRStatus err
= PR_Sync(mFD
);
290 if (err
!= PR_SUCCESS
) return NS_ERROR_UNEXPECTED
;
292 mBitMapDirty
= false;
297 /******************************************************************************
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 *****************************************************************************/
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
;
327 /******************************************************************************
330 * Return size of the block file according to the bits set in mBitmap
332 *****************************************************************************/
334 nsDiskCacheBlockFile::CalcBlockFileSize()
336 // search for last byte in mBitMap with allocated bits
337 PRUint32 estimatedSize
= mBitMapWords
* 4;
338 PRInt32 i
= mBitMapWords
;
340 if (mBitMap
[i
]) break;
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 /******************************************************************************
361 * Wrapper around PR_Write that grows file in larger chunks to combat fragmentation
363 *****************************************************************************/
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
) {
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
;
382 // Grow quickly between 1MB to 20MB
384 while(mFileSize
< upTo
)
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
)
394 return PR_Write(mFD
, buf
, amount
) == amount
;