netwerk/cache/nsDiskCacheBlockFile.cpp

Wed, 31 Dec 2014 06:09:35 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Wed, 31 Dec 2014 06:09:35 +0100
changeset 0
6474c204b198
permissions
-rw-r--r--

Cloned upstream origin tor-browser at tor-browser-31.3.0esr-4.5-1-build1
revision ID fc1c9ff7c1b2defdbc039f12214767608f46423f for hacking purpose.

     1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
     2  *
     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"
    11 #include "mozilla/MemoryReporting.h"
    12 #include <algorithm>
    14 using namespace mozilla;
    16 /******************************************************************************
    17  * nsDiskCacheBlockFile - 
    18  *****************************************************************************/
    20 /******************************************************************************
    21  *  Open
    22  *****************************************************************************/
    23 nsresult
    24 nsDiskCacheBlockFile::Open(nsIFile * blockFile,
    25                            uint32_t  blockSize,
    26                            uint32_t  bitMapSize,
    27                            nsDiskCache::CorruptCacheInfo *  corruptInfo)
    28 {
    29     NS_ENSURE_ARG_POINTER(corruptInfo);
    30     *corruptInfo = nsDiskCache::kUnexpectedError;
    32     if (bitMapSize % 32) {
    33         *corruptInfo = nsDiskCache::kInvalidArgPointer;
    34         return NS_ERROR_INVALID_ARG;
    35     }
    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);
    43     if (NS_FAILED(rv)) {
    44         *corruptInfo = nsDiskCache::kCouldNotCreateBlockFile;
    45         CACHE_LOG_DEBUG(("CACHE: nsDiskCacheBlockFile::Open "
    46                          "[this=%p] unable to open or create file: %d",
    47                          this, rv));
    48         return rv;  // unable to open or create file
    49     }
    51     // allocate bit map buffer
    52     mBitMap = new uint32_t[mBitMapWords];
    54     // check if we just creating the file
    55     mFileSize = PR_Available(mFD);
    56     if (mFileSize < 0) {
    57         // XXX an error occurred. We could call PR_GetError(), but how would that help?
    58         *corruptInfo = nsDiskCache::kBlockFileSizeError;
    59         rv = NS_ERROR_UNEXPECTED;
    60         goto error_exit;
    61     }
    62     if (mFileSize == 0) {
    63         // initialize bit map and write it
    64         memset(mBitMap, 0, bitMapBytes);
    65         if (!Write(0, mBitMap, bitMapBytes)) {
    66             *corruptInfo = nsDiskCache::kBlockFileBitMapWriteError;
    67             goto error_exit;
    68         }
    70     } else if ((uint32_t)mFileSize < bitMapBytes) {
    71         *corruptInfo = nsDiskCache::kBlockFileSizeLessThanBitMap;
    72         rv = NS_ERROR_UNEXPECTED;  // XXX NS_ERROR_CACHE_INVALID;
    73         goto error_exit;
    75     } else {
    76         // read the bit map
    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;
    81             goto error_exit;
    82         }
    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]);
    87 #endif
    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;
    96             goto error_exit;
    97         }
    98     }
    99     CACHE_LOG_DEBUG(("CACHE: nsDiskCacheBlockFile::Open [this=%p] succeeded",
   100                       this));
   101     return NS_OK;
   103 error_exit:
   104     CACHE_LOG_DEBUG(("CACHE: nsDiskCacheBlockFile::Open [this=%p] failed with "
   105                      "error %d", this, rv));
   106     Close(false);
   107     return rv;
   108 }
   111 /******************************************************************************
   112  *  Close
   113  *****************************************************************************/
   114 nsresult
   115 nsDiskCacheBlockFile::Close(bool flush)
   116 {
   117     nsresult rv = NS_OK;
   119     if (mFD) {
   120         if (flush)
   121             rv  = FlushBitMap();
   122         PRStatus err = PR_Close(mFD);
   123         if (NS_SUCCEEDED(rv) && (err != PR_SUCCESS))
   124             rv = NS_ERROR_UNEXPECTED;
   125         mFD = nullptr;
   126     }
   128      if (mBitMap) {
   129          delete [] mBitMap;
   130          mBitMap = nullptr;
   131      }
   133     return rv;
   134 }
   137 /******************************************************************************
   138  *  AllocateBlocks
   139  *
   140  *  Allocates 1-4 blocks, using a first fit strategy,
   141  *  so that no group of blocks spans a quad block boundary.
   142  *
   143  *  Returns block number of first block allocated or -1 on failure.
   144  *
   145  *****************************************************************************/
   146 int32_t
   147 nsDiskCacheBlockFile::AllocateBlocks(int32_t numBlocks)
   148 {
   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
   155             int bit = 0;
   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; 
   166                     mBitMapDirty = true;
   167                     return (int32_t)i * 32 + bit;
   168                 }
   169             }
   170         }
   171     }
   173     return -1;
   174 }
   177 /******************************************************************************
   178  *  DeallocateBlocks
   179  *****************************************************************************/
   180 nsresult
   181 nsDiskCacheBlockFile::DeallocateBlocks( int32_t  startBlock, int32_t  numBlocks)
   182 {
   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;
   200     mBitMapDirty = true;
   201     // XXX rv = FlushBitMap();  // coherency vs. performance
   202     return NS_OK;
   203 }
   206 /******************************************************************************
   207  *  WriteBlocks
   208  *****************************************************************************/
   209 nsresult
   210 nsDiskCacheBlockFile::WriteBlocks( void *   buffer,
   211                                    uint32_t size,
   212                                    int32_t  numBlocks,
   213                                    int32_t * startBlock)
   214 {
   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);
   220     if (*startBlock < 0)
   221         return NS_ERROR_NOT_AVAILABLE;
   223     // seek to block position
   224     int32_t blockPos = mBitMapWords * 4 + *startBlock * mBlockSize;
   226     // write the blocks
   227     return Write(blockPos, buffer, size) ? NS_OK : NS_ERROR_FAILURE;
   228 }
   231 /******************************************************************************
   232  *  ReadBlocks
   233  *****************************************************************************/
   234 nsresult
   235 nsDiskCacheBlockFile::ReadBlocks( void *    buffer,
   236                                   int32_t   startBlock,
   237                                   int32_t   numBlocks,
   238                                   int32_t * bytesRead)
   239 {
   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;
   251     // read the blocks
   252     int32_t bytesToRead = *bytesRead;
   253     if ((bytesToRead <= 0) || ((uint32_t)bytesToRead > mBlockSize * numBlocks)) {
   254         bytesToRead = mBlockSize * numBlocks;
   255     }
   256     *bytesRead = PR_Read(mFD, buffer, bytesToRead);
   258     CACHE_LOG_DEBUG(("CACHE: nsDiskCacheBlockFile::Read [this=%p] "
   259                      "returned %d / %d bytes", this, *bytesRead, bytesToRead));
   261     return NS_OK;
   262 }
   265 /******************************************************************************
   266  *  FlushBitMap
   267  *****************************************************************************/
   268 nsresult
   269 nsDiskCacheBlockFile::FlushBitMap()
   270 {
   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]);
   279 #else
   280     uint32_t *bitmap = mBitMap;
   281 #endif
   283     // write bitmap
   284     bool written = Write(0, bitmap, mBitMapWords * 4);
   285 #if defined(IS_LITTLE_ENDIAN)
   286     delete [] bitmap;
   287 #endif
   288     if (!written)
   289         return NS_ERROR_UNEXPECTED;
   291     PRStatus err = PR_Sync(mFD);
   292     if (err != PR_SUCCESS)  return NS_ERROR_UNEXPECTED;
   294     mBitMapDirty = false;
   295     return NS_OK;
   296 }
   299 /******************************************************************************
   300  *  VerifyAllocation
   301  *
   302  *  Return values:
   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
   306  *
   307  *****************************************************************************/
   308 nsresult
   309 nsDiskCacheBlockFile::VerifyAllocation( int32_t  startBlock, int32_t  numBlocks)
   310 {
   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;
   325     return NS_OK;
   326 }
   329 /******************************************************************************
   330  *  CalcBlockFileSize
   331  *
   332  *  Return size of the block file according to the bits set in mBitmap
   333  *
   334  *****************************************************************************/
   335 uint32_t
   336 nsDiskCacheBlockFile::CalcBlockFileSize()
   337 {
   338     // search for last byte in mBitMap with allocated bits
   339     uint32_t  estimatedSize = mBitMapWords * 4;
   340     int32_t   i = mBitMapWords;
   341     while (--i >= 0) {
   342         if (mBitMap[i]) break;
   343     }
   345     if (i >= 0) {
   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;
   355     }
   357     return estimatedSize;
   358 }
   360 /******************************************************************************
   361  *  Write
   362  *
   363  *  Wrapper around PR_Write that grows file in larger chunks to combat fragmentation
   364  *
   365  *****************************************************************************/
   366 bool
   367 nsDiskCacheBlockFile::Write(int32_t offset, const void *buf, int32_t amount)
   368 {
   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.
   372      */
   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) {
   378         // maximal file size
   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;
   383         } else {
   384             // Grow quickly between 1MB to 20MB
   385             if (mFileSize)
   386                 while(mFileSize < upTo)
   387                     mFileSize *= 2;
   388             mFileSize = clamped(mFileSize, minPreallocate, maxPreallocate);
   389         }
   390         mFileSize = std::min(mFileSize, maxFileSize);
   391 #if !defined(XP_MACOSX)
   392         mozilla::fallocate(mFD, mFileSize);
   393 #endif
   394     }
   395     if (PR_Seek(mFD, offset, PR_SEEK_SET) != offset)
   396         return false;
   397     return PR_Write(mFD, buf, amount) == amount;
   398 }
   400 size_t
   401 nsDiskCacheBlockFile::SizeOfExcludingThis(MallocSizeOf aMallocSizeOf)
   402 {
   403     return aMallocSizeOf(mBitMap) + aMallocSizeOf(mFD);
   404 }

mercurial