1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
3 * ***** BEGIN LICENSE BLOCK *****
4 * Version: MPL 1.1/GPL 2.0/LGPL 2.1
6 * The contents of this file are subject to the Mozilla Public License Version
7 * 1.1 (the "License"); you may not use this file except in compliance with
8 * the License. You may obtain a copy of the License at
9 * http://www.mozilla.org/MPL/
11 * Software distributed under the License is distributed on an "AS IS" basis,
12 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
13 * for the specific language governing rights and limitations under the
16 * The Original Code is nsDiskCacheBlockFile.cpp, released
19 * The Initial Developer of the Original Code is
20 * Netscape Communications Corporation.
21 * Portions created by the Initial Developer are Copyright (C) 2001
22 * the Initial Developer. All Rights Reserved.
25 * Gordon Sheridan <gordon@netscape.com>
27 * Alternatively, the contents of this file may be used under the terms of
28 * either the GNU General Public License Version 2 or later (the "GPL"), or
29 * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
30 * in which case the provisions of the GPL or the LGPL are applicable instead
31 * of those above. If you wish to allow use of your version of this file only
32 * under the terms of either the GPL or the LGPL, and not to allow others to
33 * use your version of this file under the terms of the MPL, indicate your
34 * decision by deleting the provisions above and replace them with the notice
35 * and other provisions required by the GPL or the LGPL. If you do not delete
36 * the provisions above, a recipient may use your version of this file under
37 * the terms of any one of the MPL, the GPL or the LGPL.
39 * ***** END LICENSE BLOCK ***** */
41 #include "nsDiskCache.h"
42 #include "nsDiskCacheBlockFile.h"
43 #include "mozilla/FileUtils.h"
45 /******************************************************************************
46 * nsDiskCacheBlockFile -
47 *****************************************************************************/
49 /******************************************************************************
51 *****************************************************************************/
53 nsDiskCacheBlockFile::Open(nsILocalFile
* blockFile
,
58 return NS_ERROR_INVALID_ARG
;
60 mBlockSize
= blockSize
;
61 mBitMapWords
= bitMapSize
/ 32;
62 PRUint32 bitMapBytes
= mBitMapWords
* 4;
64 // open the file - restricted to user, the data could be confidential
65 nsresult rv
= blockFile
->OpenNSPRFileDesc(PR_RDWR
| PR_CREATE_FILE
, 00600, &mFD
);
66 if (NS_FAILED(rv
)) return rv
; // unable to open or create file
68 // allocate bit map buffer
69 mBitMap
= new PRUint32
[mBitMapWords
];
71 rv
= NS_ERROR_OUT_OF_MEMORY
;
75 // check if we just creating the file
76 mFileSize
= PR_Available(mFD
);
78 // XXX an error occurred. We could call PR_GetError(), but how would that help?
79 rv
= NS_ERROR_UNEXPECTED
;
83 // initialize bit map and write it
84 memset(mBitMap
, 0, bitMapBytes
);
85 if (!Write(0, mBitMap
, bitMapBytes
))
88 } else if ((PRUint32
)mFileSize
< bitMapBytes
) {
89 rv
= NS_ERROR_UNEXPECTED
; // XXX NS_ERROR_CACHE_INVALID;
94 const PRInt32 bytesRead
= PR_Read(mFD
, mBitMap
, bitMapBytes
);
95 if ((bytesRead
< 0) || ((PRUint32
)bytesRead
< bitMapBytes
)) {
96 rv
= NS_ERROR_UNEXPECTED
;
99 #if defined(IS_LITTLE_ENDIAN)
100 // Swap from network format
101 for (unsigned int i
= 0; i
< mBitMapWords
; ++i
)
102 mBitMap
[i
] = ntohl(mBitMap
[i
]);
104 // validate block file size
105 // Because not whole blocks are written, the size may be a
106 // little bit smaller than used blocks times blocksize,
107 // because the last block will generally not be 'whole'.
108 const PRUint32 estimatedSize
= CalcBlockFileSize();
109 if ((PRUint32
)mFileSize
+ blockSize
< estimatedSize
) {
110 rv
= NS_ERROR_UNEXPECTED
;
122 /******************************************************************************
124 *****************************************************************************/
126 nsDiskCacheBlockFile::Close(PRBool flush
)
133 PRStatus err
= PR_Close(mFD
);
134 if (NS_SUCCEEDED(rv
) && (err
!= PR_SUCCESS
))
135 rv
= NS_ERROR_UNEXPECTED
;
148 /******************************************************************************
151 * Allocates 1-4 blocks, using a first fit strategy,
152 * so that no group of blocks spans a quad block boundary.
154 * Returns block number of first block allocated or -1 on failure.
156 *****************************************************************************/
158 nsDiskCacheBlockFile::AllocateBlocks(PRInt32 numBlocks
)
160 const int maxPos
= 32 - numBlocks
;
161 const PRUint32 mask
= (0x01 << numBlocks
) - 1;
162 for (unsigned int i
= 0; i
< mBitMapWords
; ++i
) {
163 PRUint32 mapWord
= ~mBitMap
[i
]; // flip bits so free bits are 1
164 if (mapWord
) { // At least one free bit
165 // Binary search for first free bit in word
167 if ((mapWord
& 0x0FFFF) == 0) { bit
|= 16; mapWord
>>= 16; }
168 if ((mapWord
& 0x000FF) == 0) { bit
|= 8; mapWord
>>= 8; }
169 if ((mapWord
& 0x0000F) == 0) { bit
|= 4; mapWord
>>= 4; }
170 if ((mapWord
& 0x00003) == 0) { bit
|= 2; mapWord
>>= 2; }
171 if ((mapWord
& 0x00001) == 0) { bit
|= 1; mapWord
>>= 1; }
172 // Find first fit for mask
173 for (; bit
<= maxPos
; ++bit
) {
174 // all bits selected by mask are 1, so free
175 if ((mask
& mapWord
) == mask
) {
176 mBitMap
[i
] |= mask
<< bit
;
177 mBitMapDirty
= PR_TRUE
;
178 return (PRInt32
)i
* 32 + bit
;
188 /******************************************************************************
190 *****************************************************************************/
192 nsDiskCacheBlockFile::DeallocateBlocks( PRInt32 startBlock
, PRInt32 numBlocks
)
194 if (!mFD
) return NS_ERROR_NOT_AVAILABLE
;
196 if ((startBlock
< 0) || ((PRUint32
)startBlock
> mBitMapWords
* 32 - 1) ||
197 (numBlocks
< 1) || (numBlocks
> 4))
198 return NS_ERROR_ILLEGAL_VALUE
;
200 const PRInt32 startWord
= startBlock
>> 5; // Divide by 32
201 const PRUint32 startBit
= startBlock
& 31; // Modulo by 32
203 // make sure requested deallocation doesn't span a word boundary
204 if (startBit
+ numBlocks
> 32) return NS_ERROR_UNEXPECTED
;
205 PRUint32 mask
= ((0x01 << numBlocks
) - 1) << startBit
;
207 // make sure requested deallocation is currently allocated
208 if ((mBitMap
[startWord
] & mask
) != mask
) return NS_ERROR_ABORT
;
210 mBitMap
[startWord
] ^= mask
; // flips the bits off;
211 mBitMapDirty
= PR_TRUE
;
212 // XXX rv = FlushBitMap(); // coherency vs. performance
217 /******************************************************************************
219 *****************************************************************************/
221 nsDiskCacheBlockFile::WriteBlocks( void * buffer
,
224 PRInt32
* startBlock
)
226 // presume buffer != nsnull and startBlock != nsnull
227 NS_ENSURE_TRUE(mFD
, NS_ERROR_NOT_AVAILABLE
);
229 // allocate some blocks in the cache block file
230 *startBlock
= AllocateBlocks(numBlocks
);
232 return NS_ERROR_NOT_AVAILABLE
;
234 // seek to block position
235 PRInt32 blockPos
= mBitMapWords
* 4 + *startBlock
* mBlockSize
;
238 return Write(blockPos
, buffer
, size
) ? NS_OK
: NS_ERROR_FAILURE
;
242 /******************************************************************************
244 *****************************************************************************/
246 nsDiskCacheBlockFile::ReadBlocks( void * buffer
,
251 // presume buffer != nsnull and bytesRead != bytesRead
253 if (!mFD
) return NS_ERROR_NOT_AVAILABLE
;
254 nsresult rv
= VerifyAllocation(startBlock
, numBlocks
);
255 if (NS_FAILED(rv
)) return rv
;
257 // seek to block position
258 PRInt32 blockPos
= mBitMapWords
* 4 + startBlock
* mBlockSize
;
259 PRInt32 filePos
= PR_Seek(mFD
, blockPos
, PR_SEEK_SET
);
260 if (filePos
!= blockPos
) return NS_ERROR_UNEXPECTED
;
263 PRInt32 bytesToRead
= *bytesRead
;
264 if ((bytesToRead
<= 0) || ((PRUint32
)bytesToRead
> mBlockSize
* numBlocks
)) {
265 bytesToRead
= mBlockSize
* numBlocks
;
267 *bytesRead
= PR_Read(mFD
, buffer
, bytesToRead
);
273 /******************************************************************************
275 *****************************************************************************/
277 nsDiskCacheBlockFile::FlushBitMap()
279 if (!mBitMapDirty
) return NS_OK
;
281 #if defined(IS_LITTLE_ENDIAN)
282 PRUint32
*bitmap
= new PRUint32
[mBitMapWords
];
283 // Copy and swap to network format
284 PRUint32
*p
= bitmap
;
285 for (unsigned int i
= 0; i
< mBitMapWords
; ++i
, ++p
)
286 *p
= htonl(mBitMap
[i
]);
288 PRUint32
*bitmap
= mBitMap
;
292 bool written
= Write(0, bitmap
, mBitMapWords
* 4);
293 #if defined(IS_LITTLE_ENDIAN)
297 return NS_ERROR_UNEXPECTED
;
299 PRStatus err
= PR_Sync(mFD
);
300 if (err
!= PR_SUCCESS
) return NS_ERROR_UNEXPECTED
;
302 mBitMapDirty
= PR_FALSE
;
307 /******************************************************************************
311 * NS_OK if all bits are marked allocated
312 * NS_ERROR_ILLEGAL_VALUE if parameters don't obey constraints
313 * NS_ERROR_FAILURE if some or all the bits are marked unallocated
315 *****************************************************************************/
317 nsDiskCacheBlockFile::VerifyAllocation( PRInt32 startBlock
, PRInt32 numBlocks
)
319 if ((startBlock
< 0) || ((PRUint32
)startBlock
> mBitMapWords
* 32 - 1) ||
320 (numBlocks
< 1) || (numBlocks
> 4))
321 return NS_ERROR_ILLEGAL_VALUE
;
323 const PRInt32 startWord
= startBlock
>> 5; // Divide by 32
324 const PRUint32 startBit
= startBlock
& 31; // Modulo by 32
326 // make sure requested deallocation doesn't span a word boundary
327 if (startBit
+ numBlocks
> 32) return NS_ERROR_ILLEGAL_VALUE
;
328 PRUint32 mask
= ((0x01 << numBlocks
) - 1) << startBit
;
330 // check if all specified blocks are currently allocated
331 if ((mBitMap
[startWord
] & mask
) != mask
) return NS_ERROR_FAILURE
;
337 /******************************************************************************
340 * Return size of the block file according to the bits set in mBitmap
342 *****************************************************************************/
344 nsDiskCacheBlockFile::CalcBlockFileSize()
346 // search for last byte in mBitMap with allocated bits
347 PRUint32 estimatedSize
= mBitMapWords
* 4;
348 PRInt32 i
= mBitMapWords
;
350 if (mBitMap
[i
]) break;
354 // binary search to find last allocated bit in byte
355 PRUint32 mapWord
= mBitMap
[i
];
356 PRUint32 lastBit
= 31;
357 if ((mapWord
& 0xFFFF0000) == 0) { lastBit
^= 16; mapWord
<<= 16; }
358 if ((mapWord
& 0xFF000000) == 0) { lastBit
^= 8; mapWord
<<= 8; }
359 if ((mapWord
& 0xF0000000) == 0) { lastBit
^= 4; mapWord
<<= 4; }
360 if ((mapWord
& 0xC0000000) == 0) { lastBit
^= 2; mapWord
<<= 2; }
361 if ((mapWord
& 0x80000000) == 0) { lastBit
^= 1; mapWord
<<= 1; }
362 estimatedSize
+= (i
* 32 + lastBit
+ 1) * mBlockSize
;
365 return estimatedSize
;
368 /******************************************************************************
371 * Wrapper around PR_Write that grows file in larger chunks to combat fragmentation
373 *****************************************************************************/
375 nsDiskCacheBlockFile::Write(PRInt32 offset
, const void *buf
, PRInt32 amount
)
377 /* Grow the file to 4mb right away, then double it until the file grows to 20mb.
378 20mb is a magic threshold because OSX stops autodefragging files bigger than that.
379 Beyond 20mb grow in 4mb chunks.
381 const PRInt32 upTo
= offset
+ amount
;
382 // Use a conservative definition of 20MB
383 const PRInt32 minPreallocate
= 4*1024*1024;
384 const PRInt32 maxPreallocate
= 20*1000*1000;
385 if (mFileSize
< upTo
) {
387 const PRInt32 maxFileSize
= mBitMapWords
* 4 * (mBlockSize
* 8 + 1);
388 if (upTo
> maxPreallocate
) {
389 // grow the file as a multiple of minPreallocate
390 mFileSize
= ((upTo
+ minPreallocate
- 1) / minPreallocate
) * minPreallocate
;
392 // Grow quickly between 1MB to 20MB
394 while(mFileSize
< upTo
)
396 mFileSize
= PR_MIN(maxPreallocate
, PR_MAX(mFileSize
, minPreallocate
));
398 mFileSize
= PR_MIN(mFileSize
, maxFileSize
);
399 // Appears to cause bug 617123? Disabled for now.
400 //mozilla::fallocate(mFD, mFileSize);
402 if (PR_Seek(mFD
, offset
, PR_SEEK_SET
) != offset
)
404 return PR_Write(mFD
, buf
, amount
) == amount
;