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"
11 #include "mozilla/MemoryReporting.h"
14 using namespace mozilla
;
16 /******************************************************************************
17 * nsDiskCacheBlockFile -
18 *****************************************************************************/
20 /******************************************************************************
22 *****************************************************************************/
24 nsDiskCacheBlockFile::Open(nsIFile
* blockFile
,
27 nsDiskCache::CorruptCacheInfo
* corruptInfo
)
29 NS_ENSURE_ARG_POINTER(corruptInfo
);
30 *corruptInfo
= nsDiskCache::kUnexpectedError
;
32 if (bitMapSize
% 32) {
33 *corruptInfo
= nsDiskCache::kInvalidArgPointer
;
34 return NS_ERROR_INVALID_ARG
;
37 mBlockSize
= blockSize
;
38 mBitMapWords
= bitMapSize
/ 32;
39 uint32_t bitMapBytes
= mBitMapWords
* 4;
41 // open the file - restricted to user, the data could be confidential
42 nsresult rv
= blockFile
->OpenNSPRFileDesc(PR_RDWR
| PR_CREATE_FILE
, 00600, &mFD
);
44 *corruptInfo
= nsDiskCache::kCouldNotCreateBlockFile
;
45 CACHE_LOG_DEBUG(("CACHE: nsDiskCacheBlockFile::Open "
46 "[this=%p] unable to open or create file: %d",
48 return rv
; // unable to open or create file
51 // allocate bit map buffer
52 mBitMap
= new uint32_t[mBitMapWords
];
54 // check if we just creating the file
55 mFileSize
= PR_Available(mFD
);
57 // XXX an error occurred. We could call PR_GetError(), but how would that help?
58 *corruptInfo
= nsDiskCache::kBlockFileSizeError
;
59 rv
= NS_ERROR_UNEXPECTED
;
63 // initialize bit map and write it
64 memset(mBitMap
, 0, bitMapBytes
);
65 if (!Write(0, mBitMap
, bitMapBytes
)) {
66 *corruptInfo
= nsDiskCache::kBlockFileBitMapWriteError
;
70 } else if ((uint32_t)mFileSize
< bitMapBytes
) {
71 *corruptInfo
= nsDiskCache::kBlockFileSizeLessThanBitMap
;
72 rv
= NS_ERROR_UNEXPECTED
; // XXX NS_ERROR_CACHE_INVALID;
77 const int32_t bytesRead
= PR_Read(mFD
, mBitMap
, bitMapBytes
);
78 if ((bytesRead
< 0) || ((uint32_t)bytesRead
< bitMapBytes
)) {
79 *corruptInfo
= nsDiskCache::kBlockFileBitMapReadError
;
80 rv
= NS_ERROR_UNEXPECTED
;
83 #if defined(IS_LITTLE_ENDIAN)
84 // Swap from network format
85 for (unsigned int i
= 0; i
< mBitMapWords
; ++i
)
86 mBitMap
[i
] = ntohl(mBitMap
[i
]);
88 // validate block file size
89 // Because not whole blocks are written, the size may be a
90 // little bit smaller than used blocks times blocksize,
91 // because the last block will generally not be 'whole'.
92 const uint32_t estimatedSize
= CalcBlockFileSize();
93 if ((uint32_t)mFileSize
+ blockSize
< estimatedSize
) {
94 *corruptInfo
= nsDiskCache::kBlockFileEstimatedSizeError
;
95 rv
= NS_ERROR_UNEXPECTED
;
99 CACHE_LOG_DEBUG(("CACHE: nsDiskCacheBlockFile::Open [this=%p] succeeded",
104 CACHE_LOG_DEBUG(("CACHE: nsDiskCacheBlockFile::Open [this=%p] failed with "
105 "error %d", this, rv
));
111 /******************************************************************************
113 *****************************************************************************/
115 nsDiskCacheBlockFile::Close(bool flush
)
122 PRStatus err
= PR_Close(mFD
);
123 if (NS_SUCCEEDED(rv
) && (err
!= PR_SUCCESS
))
124 rv
= NS_ERROR_UNEXPECTED
;
137 /******************************************************************************
140 * Allocates 1-4 blocks, using a first fit strategy,
141 * so that no group of blocks spans a quad block boundary.
143 * Returns block number of first block allocated or -1 on failure.
145 *****************************************************************************/
147 nsDiskCacheBlockFile::AllocateBlocks(int32_t numBlocks
)
149 const int maxPos
= 32 - numBlocks
;
150 const uint32_t mask
= (0x01 << numBlocks
) - 1;
151 for (unsigned int i
= 0; i
< mBitMapWords
; ++i
) {
152 uint32_t mapWord
= ~mBitMap
[i
]; // flip bits so free bits are 1
153 if (mapWord
) { // At least one free bit
154 // Binary search for first free bit in word
156 if ((mapWord
& 0x0FFFF) == 0) { bit
|= 16; mapWord
>>= 16; }
157 if ((mapWord
& 0x000FF) == 0) { bit
|= 8; mapWord
>>= 8; }
158 if ((mapWord
& 0x0000F) == 0) { bit
|= 4; mapWord
>>= 4; }
159 if ((mapWord
& 0x00003) == 0) { bit
|= 2; mapWord
>>= 2; }
160 if ((mapWord
& 0x00001) == 0) { bit
|= 1; mapWord
>>= 1; }
161 // Find first fit for mask
162 for (; bit
<= maxPos
; ++bit
) {
163 // all bits selected by mask are 1, so free
164 if ((mask
& mapWord
) == mask
) {
165 mBitMap
[i
] |= mask
<< bit
;
167 return (int32_t)i
* 32 + bit
;
177 /******************************************************************************
179 *****************************************************************************/
181 nsDiskCacheBlockFile::DeallocateBlocks( int32_t startBlock
, int32_t numBlocks
)
183 if (!mFD
) return NS_ERROR_NOT_AVAILABLE
;
185 if ((startBlock
< 0) || ((uint32_t)startBlock
> mBitMapWords
* 32 - 1) ||
186 (numBlocks
< 1) || (numBlocks
> 4))
187 return NS_ERROR_ILLEGAL_VALUE
;
189 const int32_t startWord
= startBlock
>> 5; // Divide by 32
190 const uint32_t startBit
= startBlock
& 31; // Modulo by 32
192 // make sure requested deallocation doesn't span a word boundary
193 if (startBit
+ numBlocks
> 32) return NS_ERROR_UNEXPECTED
;
194 uint32_t mask
= ((0x01 << numBlocks
) - 1) << startBit
;
196 // make sure requested deallocation is currently allocated
197 if ((mBitMap
[startWord
] & mask
) != mask
) return NS_ERROR_ABORT
;
199 mBitMap
[startWord
] ^= mask
; // flips the bits off;
201 // XXX rv = FlushBitMap(); // coherency vs. performance
206 /******************************************************************************
208 *****************************************************************************/
210 nsDiskCacheBlockFile::WriteBlocks( void * buffer
,
213 int32_t * startBlock
)
215 // presume buffer != nullptr and startBlock != nullptr
216 NS_ENSURE_TRUE(mFD
, NS_ERROR_NOT_AVAILABLE
);
218 // allocate some blocks in the cache block file
219 *startBlock
= AllocateBlocks(numBlocks
);
221 return NS_ERROR_NOT_AVAILABLE
;
223 // seek to block position
224 int32_t blockPos
= mBitMapWords
* 4 + *startBlock
* mBlockSize
;
227 return Write(blockPos
, buffer
, size
) ? NS_OK
: NS_ERROR_FAILURE
;
231 /******************************************************************************
233 *****************************************************************************/
235 nsDiskCacheBlockFile::ReadBlocks( void * buffer
,
240 // presume buffer != nullptr and bytesRead != bytesRead
242 if (!mFD
) return NS_ERROR_NOT_AVAILABLE
;
243 nsresult rv
= VerifyAllocation(startBlock
, numBlocks
);
244 if (NS_FAILED(rv
)) return rv
;
246 // seek to block position
247 int32_t blockPos
= mBitMapWords
* 4 + startBlock
* mBlockSize
;
248 int32_t filePos
= PR_Seek(mFD
, blockPos
, PR_SEEK_SET
);
249 if (filePos
!= blockPos
) return NS_ERROR_UNEXPECTED
;
252 int32_t bytesToRead
= *bytesRead
;
253 if ((bytesToRead
<= 0) || ((uint32_t)bytesToRead
> mBlockSize
* numBlocks
)) {
254 bytesToRead
= mBlockSize
* numBlocks
;
256 *bytesRead
= PR_Read(mFD
, buffer
, bytesToRead
);
258 CACHE_LOG_DEBUG(("CACHE: nsDiskCacheBlockFile::Read [this=%p] "
259 "returned %d / %d bytes", this, *bytesRead
, bytesToRead
));
265 /******************************************************************************
267 *****************************************************************************/
269 nsDiskCacheBlockFile::FlushBitMap()
271 if (!mBitMapDirty
) return NS_OK
;
273 #if defined(IS_LITTLE_ENDIAN)
274 uint32_t *bitmap
= new uint32_t[mBitMapWords
];
275 // Copy and swap to network format
276 uint32_t *p
= bitmap
;
277 for (unsigned int i
= 0; i
< mBitMapWords
; ++i
, ++p
)
278 *p
= htonl(mBitMap
[i
]);
280 uint32_t *bitmap
= mBitMap
;
284 bool written
= Write(0, bitmap
, mBitMapWords
* 4);
285 #if defined(IS_LITTLE_ENDIAN)
289 return NS_ERROR_UNEXPECTED
;
291 PRStatus err
= PR_Sync(mFD
);
292 if (err
!= PR_SUCCESS
) return NS_ERROR_UNEXPECTED
;
294 mBitMapDirty
= false;
299 /******************************************************************************
303 * NS_OK if all bits are marked allocated
304 * NS_ERROR_ILLEGAL_VALUE if parameters don't obey constraints
305 * NS_ERROR_FAILURE if some or all the bits are marked unallocated
307 *****************************************************************************/
309 nsDiskCacheBlockFile::VerifyAllocation( int32_t startBlock
, int32_t numBlocks
)
311 if ((startBlock
< 0) || ((uint32_t)startBlock
> mBitMapWords
* 32 - 1) ||
312 (numBlocks
< 1) || (numBlocks
> 4))
313 return NS_ERROR_ILLEGAL_VALUE
;
315 const int32_t startWord
= startBlock
>> 5; // Divide by 32
316 const uint32_t startBit
= startBlock
& 31; // Modulo by 32
318 // make sure requested deallocation doesn't span a word boundary
319 if (startBit
+ numBlocks
> 32) return NS_ERROR_ILLEGAL_VALUE
;
320 uint32_t mask
= ((0x01 << numBlocks
) - 1) << startBit
;
322 // check if all specified blocks are currently allocated
323 if ((mBitMap
[startWord
] & mask
) != mask
) return NS_ERROR_FAILURE
;
329 /******************************************************************************
332 * Return size of the block file according to the bits set in mBitmap
334 *****************************************************************************/
336 nsDiskCacheBlockFile::CalcBlockFileSize()
338 // search for last byte in mBitMap with allocated bits
339 uint32_t estimatedSize
= mBitMapWords
* 4;
340 int32_t i
= mBitMapWords
;
342 if (mBitMap
[i
]) break;
346 // binary search to find last allocated bit in byte
347 uint32_t mapWord
= mBitMap
[i
];
348 uint32_t lastBit
= 31;
349 if ((mapWord
& 0xFFFF0000) == 0) { lastBit
^= 16; mapWord
<<= 16; }
350 if ((mapWord
& 0xFF000000) == 0) { lastBit
^= 8; mapWord
<<= 8; }
351 if ((mapWord
& 0xF0000000) == 0) { lastBit
^= 4; mapWord
<<= 4; }
352 if ((mapWord
& 0xC0000000) == 0) { lastBit
^= 2; mapWord
<<= 2; }
353 if ((mapWord
& 0x80000000) == 0) { lastBit
^= 1; mapWord
<<= 1; }
354 estimatedSize
+= (i
* 32 + lastBit
+ 1) * mBlockSize
;
357 return estimatedSize
;
360 /******************************************************************************
363 * Wrapper around PR_Write that grows file in larger chunks to combat fragmentation
365 *****************************************************************************/
367 nsDiskCacheBlockFile::Write(int32_t offset
, const void *buf
, int32_t amount
)
369 /* Grow the file to 4mb right away, then double it until the file grows to 20mb.
370 20mb is a magic threshold because OSX stops autodefragging files bigger than that.
371 Beyond 20mb grow in 4mb chunks.
373 const int32_t upTo
= offset
+ amount
;
374 // Use a conservative definition of 20MB
375 const int32_t minPreallocate
= 4*1024*1024;
376 const int32_t maxPreallocate
= 20*1000*1000;
377 if (mFileSize
< upTo
) {
379 const int32_t maxFileSize
= mBitMapWords
* 4 * (mBlockSize
* 8 + 1);
380 if (upTo
> maxPreallocate
) {
381 // grow the file as a multiple of minPreallocate
382 mFileSize
= ((upTo
+ minPreallocate
- 1) / minPreallocate
) * minPreallocate
;
384 // Grow quickly between 1MB to 20MB
386 while(mFileSize
< upTo
)
388 mFileSize
= clamped(mFileSize
, minPreallocate
, maxPreallocate
);
390 mFileSize
= std::min(mFileSize
, maxFileSize
);
391 #if !defined(XP_MACOSX)
392 mozilla::fallocate(mFD
, mFileSize
);
395 if (PR_Seek(mFD
, offset
, PR_SEEK_SET
) != offset
)
397 return PR_Write(mFD
, buf
, amount
) == amount
;
401 nsDiskCacheBlockFile::SizeOfExcludingThis(MallocSizeOf aMallocSizeOf
)
403 return aMallocSizeOf(mBitMap
) + aMallocSizeOf(mFD
);