1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/netwerk/cache/nsDiskCacheBlockFile.cpp Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,404 @@ 1.4 +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- 1.5 + * 1.6 + * This Source Code Form is subject to the terms of the Mozilla Public 1.7 + * License, v. 2.0. If a copy of the MPL was not distributed with this 1.8 + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 1.9 + 1.10 +#include "nsCache.h" 1.11 +#include "nsDiskCache.h" 1.12 +#include "nsDiskCacheBlockFile.h" 1.13 +#include "mozilla/FileUtils.h" 1.14 +#include "mozilla/MemoryReporting.h" 1.15 +#include <algorithm> 1.16 + 1.17 +using namespace mozilla; 1.18 + 1.19 +/****************************************************************************** 1.20 + * nsDiskCacheBlockFile - 1.21 + *****************************************************************************/ 1.22 + 1.23 +/****************************************************************************** 1.24 + * Open 1.25 + *****************************************************************************/ 1.26 +nsresult 1.27 +nsDiskCacheBlockFile::Open(nsIFile * blockFile, 1.28 + uint32_t blockSize, 1.29 + uint32_t bitMapSize, 1.30 + nsDiskCache::CorruptCacheInfo * corruptInfo) 1.31 +{ 1.32 + NS_ENSURE_ARG_POINTER(corruptInfo); 1.33 + *corruptInfo = nsDiskCache::kUnexpectedError; 1.34 + 1.35 + if (bitMapSize % 32) { 1.36 + *corruptInfo = nsDiskCache::kInvalidArgPointer; 1.37 + return NS_ERROR_INVALID_ARG; 1.38 + } 1.39 + 1.40 + mBlockSize = blockSize; 1.41 + mBitMapWords = bitMapSize / 32; 1.42 + uint32_t bitMapBytes = mBitMapWords * 4; 1.43 + 1.44 + // open the file - restricted to user, the data could be confidential 1.45 + nsresult rv = blockFile->OpenNSPRFileDesc(PR_RDWR | PR_CREATE_FILE, 00600, &mFD); 1.46 + if (NS_FAILED(rv)) { 1.47 + *corruptInfo = nsDiskCache::kCouldNotCreateBlockFile; 1.48 + CACHE_LOG_DEBUG(("CACHE: nsDiskCacheBlockFile::Open " 1.49 + "[this=%p] unable to open or create file: %d", 1.50 + this, rv)); 1.51 + return rv; // unable to open or create file 1.52 + } 1.53 + 1.54 + // allocate bit map buffer 1.55 + mBitMap = new uint32_t[mBitMapWords]; 1.56 + 1.57 + // check if we just creating the file 1.58 + mFileSize = PR_Available(mFD); 1.59 + if (mFileSize < 0) { 1.60 + // XXX an error occurred. We could call PR_GetError(), but how would that help? 1.61 + *corruptInfo = nsDiskCache::kBlockFileSizeError; 1.62 + rv = NS_ERROR_UNEXPECTED; 1.63 + goto error_exit; 1.64 + } 1.65 + if (mFileSize == 0) { 1.66 + // initialize bit map and write it 1.67 + memset(mBitMap, 0, bitMapBytes); 1.68 + if (!Write(0, mBitMap, bitMapBytes)) { 1.69 + *corruptInfo = nsDiskCache::kBlockFileBitMapWriteError; 1.70 + goto error_exit; 1.71 + } 1.72 + 1.73 + } else if ((uint32_t)mFileSize < bitMapBytes) { 1.74 + *corruptInfo = nsDiskCache::kBlockFileSizeLessThanBitMap; 1.75 + rv = NS_ERROR_UNEXPECTED; // XXX NS_ERROR_CACHE_INVALID; 1.76 + goto error_exit; 1.77 + 1.78 + } else { 1.79 + // read the bit map 1.80 + const int32_t bytesRead = PR_Read(mFD, mBitMap, bitMapBytes); 1.81 + if ((bytesRead < 0) || ((uint32_t)bytesRead < bitMapBytes)) { 1.82 + *corruptInfo = nsDiskCache::kBlockFileBitMapReadError; 1.83 + rv = NS_ERROR_UNEXPECTED; 1.84 + goto error_exit; 1.85 + } 1.86 +#if defined(IS_LITTLE_ENDIAN) 1.87 + // Swap from network format 1.88 + for (unsigned int i = 0; i < mBitMapWords; ++i) 1.89 + mBitMap[i] = ntohl(mBitMap[i]); 1.90 +#endif 1.91 + // validate block file size 1.92 + // Because not whole blocks are written, the size may be a 1.93 + // little bit smaller than used blocks times blocksize, 1.94 + // because the last block will generally not be 'whole'. 1.95 + const uint32_t estimatedSize = CalcBlockFileSize(); 1.96 + if ((uint32_t)mFileSize + blockSize < estimatedSize) { 1.97 + *corruptInfo = nsDiskCache::kBlockFileEstimatedSizeError; 1.98 + rv = NS_ERROR_UNEXPECTED; 1.99 + goto error_exit; 1.100 + } 1.101 + } 1.102 + CACHE_LOG_DEBUG(("CACHE: nsDiskCacheBlockFile::Open [this=%p] succeeded", 1.103 + this)); 1.104 + return NS_OK; 1.105 + 1.106 +error_exit: 1.107 + CACHE_LOG_DEBUG(("CACHE: nsDiskCacheBlockFile::Open [this=%p] failed with " 1.108 + "error %d", this, rv)); 1.109 + Close(false); 1.110 + return rv; 1.111 +} 1.112 + 1.113 + 1.114 +/****************************************************************************** 1.115 + * Close 1.116 + *****************************************************************************/ 1.117 +nsresult 1.118 +nsDiskCacheBlockFile::Close(bool flush) 1.119 +{ 1.120 + nsresult rv = NS_OK; 1.121 + 1.122 + if (mFD) { 1.123 + if (flush) 1.124 + rv = FlushBitMap(); 1.125 + PRStatus err = PR_Close(mFD); 1.126 + if (NS_SUCCEEDED(rv) && (err != PR_SUCCESS)) 1.127 + rv = NS_ERROR_UNEXPECTED; 1.128 + mFD = nullptr; 1.129 + } 1.130 + 1.131 + if (mBitMap) { 1.132 + delete [] mBitMap; 1.133 + mBitMap = nullptr; 1.134 + } 1.135 + 1.136 + return rv; 1.137 +} 1.138 + 1.139 + 1.140 +/****************************************************************************** 1.141 + * AllocateBlocks 1.142 + * 1.143 + * Allocates 1-4 blocks, using a first fit strategy, 1.144 + * so that no group of blocks spans a quad block boundary. 1.145 + * 1.146 + * Returns block number of first block allocated or -1 on failure. 1.147 + * 1.148 + *****************************************************************************/ 1.149 +int32_t 1.150 +nsDiskCacheBlockFile::AllocateBlocks(int32_t numBlocks) 1.151 +{ 1.152 + const int maxPos = 32 - numBlocks; 1.153 + const uint32_t mask = (0x01 << numBlocks) - 1; 1.154 + for (unsigned int i = 0; i < mBitMapWords; ++i) { 1.155 + uint32_t mapWord = ~mBitMap[i]; // flip bits so free bits are 1 1.156 + if (mapWord) { // At least one free bit 1.157 + // Binary search for first free bit in word 1.158 + int bit = 0; 1.159 + if ((mapWord & 0x0FFFF) == 0) { bit |= 16; mapWord >>= 16; } 1.160 + if ((mapWord & 0x000FF) == 0) { bit |= 8; mapWord >>= 8; } 1.161 + if ((mapWord & 0x0000F) == 0) { bit |= 4; mapWord >>= 4; } 1.162 + if ((mapWord & 0x00003) == 0) { bit |= 2; mapWord >>= 2; } 1.163 + if ((mapWord & 0x00001) == 0) { bit |= 1; mapWord >>= 1; } 1.164 + // Find first fit for mask 1.165 + for (; bit <= maxPos; ++bit) { 1.166 + // all bits selected by mask are 1, so free 1.167 + if ((mask & mapWord) == mask) { 1.168 + mBitMap[i] |= mask << bit; 1.169 + mBitMapDirty = true; 1.170 + return (int32_t)i * 32 + bit; 1.171 + } 1.172 + } 1.173 + } 1.174 + } 1.175 + 1.176 + return -1; 1.177 +} 1.178 + 1.179 + 1.180 +/****************************************************************************** 1.181 + * DeallocateBlocks 1.182 + *****************************************************************************/ 1.183 +nsresult 1.184 +nsDiskCacheBlockFile::DeallocateBlocks( int32_t startBlock, int32_t numBlocks) 1.185 +{ 1.186 + if (!mFD) return NS_ERROR_NOT_AVAILABLE; 1.187 + 1.188 + if ((startBlock < 0) || ((uint32_t)startBlock > mBitMapWords * 32 - 1) || 1.189 + (numBlocks < 1) || (numBlocks > 4)) 1.190 + return NS_ERROR_ILLEGAL_VALUE; 1.191 + 1.192 + const int32_t startWord = startBlock >> 5; // Divide by 32 1.193 + const uint32_t startBit = startBlock & 31; // Modulo by 32 1.194 + 1.195 + // make sure requested deallocation doesn't span a word boundary 1.196 + if (startBit + numBlocks > 32) return NS_ERROR_UNEXPECTED; 1.197 + uint32_t mask = ((0x01 << numBlocks) - 1) << startBit; 1.198 + 1.199 + // make sure requested deallocation is currently allocated 1.200 + if ((mBitMap[startWord] & mask) != mask) return NS_ERROR_ABORT; 1.201 + 1.202 + mBitMap[startWord] ^= mask; // flips the bits off; 1.203 + mBitMapDirty = true; 1.204 + // XXX rv = FlushBitMap(); // coherency vs. performance 1.205 + return NS_OK; 1.206 +} 1.207 + 1.208 + 1.209 +/****************************************************************************** 1.210 + * WriteBlocks 1.211 + *****************************************************************************/ 1.212 +nsresult 1.213 +nsDiskCacheBlockFile::WriteBlocks( void * buffer, 1.214 + uint32_t size, 1.215 + int32_t numBlocks, 1.216 + int32_t * startBlock) 1.217 +{ 1.218 + // presume buffer != nullptr and startBlock != nullptr 1.219 + NS_ENSURE_TRUE(mFD, NS_ERROR_NOT_AVAILABLE); 1.220 + 1.221 + // allocate some blocks in the cache block file 1.222 + *startBlock = AllocateBlocks(numBlocks); 1.223 + if (*startBlock < 0) 1.224 + return NS_ERROR_NOT_AVAILABLE; 1.225 + 1.226 + // seek to block position 1.227 + int32_t blockPos = mBitMapWords * 4 + *startBlock * mBlockSize; 1.228 + 1.229 + // write the blocks 1.230 + return Write(blockPos, buffer, size) ? NS_OK : NS_ERROR_FAILURE; 1.231 +} 1.232 + 1.233 + 1.234 +/****************************************************************************** 1.235 + * ReadBlocks 1.236 + *****************************************************************************/ 1.237 +nsresult 1.238 +nsDiskCacheBlockFile::ReadBlocks( void * buffer, 1.239 + int32_t startBlock, 1.240 + int32_t numBlocks, 1.241 + int32_t * bytesRead) 1.242 +{ 1.243 + // presume buffer != nullptr and bytesRead != bytesRead 1.244 + 1.245 + if (!mFD) return NS_ERROR_NOT_AVAILABLE; 1.246 + nsresult rv = VerifyAllocation(startBlock, numBlocks); 1.247 + if (NS_FAILED(rv)) return rv; 1.248 + 1.249 + // seek to block position 1.250 + int32_t blockPos = mBitMapWords * 4 + startBlock * mBlockSize; 1.251 + int32_t filePos = PR_Seek(mFD, blockPos, PR_SEEK_SET); 1.252 + if (filePos != blockPos) return NS_ERROR_UNEXPECTED; 1.253 + 1.254 + // read the blocks 1.255 + int32_t bytesToRead = *bytesRead; 1.256 + if ((bytesToRead <= 0) || ((uint32_t)bytesToRead > mBlockSize * numBlocks)) { 1.257 + bytesToRead = mBlockSize * numBlocks; 1.258 + } 1.259 + *bytesRead = PR_Read(mFD, buffer, bytesToRead); 1.260 + 1.261 + CACHE_LOG_DEBUG(("CACHE: nsDiskCacheBlockFile::Read [this=%p] " 1.262 + "returned %d / %d bytes", this, *bytesRead, bytesToRead)); 1.263 + 1.264 + return NS_OK; 1.265 +} 1.266 + 1.267 + 1.268 +/****************************************************************************** 1.269 + * FlushBitMap 1.270 + *****************************************************************************/ 1.271 +nsresult 1.272 +nsDiskCacheBlockFile::FlushBitMap() 1.273 +{ 1.274 + if (!mBitMapDirty) return NS_OK; 1.275 + 1.276 +#if defined(IS_LITTLE_ENDIAN) 1.277 + uint32_t *bitmap = new uint32_t[mBitMapWords]; 1.278 + // Copy and swap to network format 1.279 + uint32_t *p = bitmap; 1.280 + for (unsigned int i = 0; i < mBitMapWords; ++i, ++p) 1.281 + *p = htonl(mBitMap[i]); 1.282 +#else 1.283 + uint32_t *bitmap = mBitMap; 1.284 +#endif 1.285 + 1.286 + // write bitmap 1.287 + bool written = Write(0, bitmap, mBitMapWords * 4); 1.288 +#if defined(IS_LITTLE_ENDIAN) 1.289 + delete [] bitmap; 1.290 +#endif 1.291 + if (!written) 1.292 + return NS_ERROR_UNEXPECTED; 1.293 + 1.294 + PRStatus err = PR_Sync(mFD); 1.295 + if (err != PR_SUCCESS) return NS_ERROR_UNEXPECTED; 1.296 + 1.297 + mBitMapDirty = false; 1.298 + return NS_OK; 1.299 +} 1.300 + 1.301 + 1.302 +/****************************************************************************** 1.303 + * VerifyAllocation 1.304 + * 1.305 + * Return values: 1.306 + * NS_OK if all bits are marked allocated 1.307 + * NS_ERROR_ILLEGAL_VALUE if parameters don't obey constraints 1.308 + * NS_ERROR_FAILURE if some or all the bits are marked unallocated 1.309 + * 1.310 + *****************************************************************************/ 1.311 +nsresult 1.312 +nsDiskCacheBlockFile::VerifyAllocation( int32_t startBlock, int32_t numBlocks) 1.313 +{ 1.314 + if ((startBlock < 0) || ((uint32_t)startBlock > mBitMapWords * 32 - 1) || 1.315 + (numBlocks < 1) || (numBlocks > 4)) 1.316 + return NS_ERROR_ILLEGAL_VALUE; 1.317 + 1.318 + const int32_t startWord = startBlock >> 5; // Divide by 32 1.319 + const uint32_t startBit = startBlock & 31; // Modulo by 32 1.320 + 1.321 + // make sure requested deallocation doesn't span a word boundary 1.322 + if (startBit + numBlocks > 32) return NS_ERROR_ILLEGAL_VALUE; 1.323 + uint32_t mask = ((0x01 << numBlocks) - 1) << startBit; 1.324 + 1.325 + // check if all specified blocks are currently allocated 1.326 + if ((mBitMap[startWord] & mask) != mask) return NS_ERROR_FAILURE; 1.327 + 1.328 + return NS_OK; 1.329 +} 1.330 + 1.331 + 1.332 +/****************************************************************************** 1.333 + * CalcBlockFileSize 1.334 + * 1.335 + * Return size of the block file according to the bits set in mBitmap 1.336 + * 1.337 + *****************************************************************************/ 1.338 +uint32_t 1.339 +nsDiskCacheBlockFile::CalcBlockFileSize() 1.340 +{ 1.341 + // search for last byte in mBitMap with allocated bits 1.342 + uint32_t estimatedSize = mBitMapWords * 4; 1.343 + int32_t i = mBitMapWords; 1.344 + while (--i >= 0) { 1.345 + if (mBitMap[i]) break; 1.346 + } 1.347 + 1.348 + if (i >= 0) { 1.349 + // binary search to find last allocated bit in byte 1.350 + uint32_t mapWord = mBitMap[i]; 1.351 + uint32_t lastBit = 31; 1.352 + if ((mapWord & 0xFFFF0000) == 0) { lastBit ^= 16; mapWord <<= 16; } 1.353 + if ((mapWord & 0xFF000000) == 0) { lastBit ^= 8; mapWord <<= 8; } 1.354 + if ((mapWord & 0xF0000000) == 0) { lastBit ^= 4; mapWord <<= 4; } 1.355 + if ((mapWord & 0xC0000000) == 0) { lastBit ^= 2; mapWord <<= 2; } 1.356 + if ((mapWord & 0x80000000) == 0) { lastBit ^= 1; mapWord <<= 1; } 1.357 + estimatedSize += (i * 32 + lastBit + 1) * mBlockSize; 1.358 + } 1.359 + 1.360 + return estimatedSize; 1.361 +} 1.362 + 1.363 +/****************************************************************************** 1.364 + * Write 1.365 + * 1.366 + * Wrapper around PR_Write that grows file in larger chunks to combat fragmentation 1.367 + * 1.368 + *****************************************************************************/ 1.369 +bool 1.370 +nsDiskCacheBlockFile::Write(int32_t offset, const void *buf, int32_t amount) 1.371 +{ 1.372 + /* Grow the file to 4mb right away, then double it until the file grows to 20mb. 1.373 + 20mb is a magic threshold because OSX stops autodefragging files bigger than that. 1.374 + Beyond 20mb grow in 4mb chunks. 1.375 + */ 1.376 + const int32_t upTo = offset + amount; 1.377 + // Use a conservative definition of 20MB 1.378 + const int32_t minPreallocate = 4*1024*1024; 1.379 + const int32_t maxPreallocate = 20*1000*1000; 1.380 + if (mFileSize < upTo) { 1.381 + // maximal file size 1.382 + const int32_t maxFileSize = mBitMapWords * 4 * (mBlockSize * 8 + 1); 1.383 + if (upTo > maxPreallocate) { 1.384 + // grow the file as a multiple of minPreallocate 1.385 + mFileSize = ((upTo + minPreallocate - 1) / minPreallocate) * minPreallocate; 1.386 + } else { 1.387 + // Grow quickly between 1MB to 20MB 1.388 + if (mFileSize) 1.389 + while(mFileSize < upTo) 1.390 + mFileSize *= 2; 1.391 + mFileSize = clamped(mFileSize, minPreallocate, maxPreallocate); 1.392 + } 1.393 + mFileSize = std::min(mFileSize, maxFileSize); 1.394 +#if !defined(XP_MACOSX) 1.395 + mozilla::fallocate(mFD, mFileSize); 1.396 +#endif 1.397 + } 1.398 + if (PR_Seek(mFD, offset, PR_SEEK_SET) != offset) 1.399 + return false; 1.400 + return PR_Write(mFD, buf, amount) == amount; 1.401 +} 1.402 + 1.403 +size_t 1.404 +nsDiskCacheBlockFile::SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) 1.405 +{ 1.406 + return aMallocSizeOf(mBitMap) + aMallocSizeOf(mFD); 1.407 +}