Back to index

lightning-sunbird  0.9+nobinonly
nsDiskCacheBlockFile.cpp
Go to the documentation of this file.
00001 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
00002  *
00003  * ***** BEGIN LICENSE BLOCK *****
00004  * Version: MPL 1.1/GPL 2.0/LGPL 2.1
00005  *
00006  * The contents of this file are subject to the Mozilla Public License Version
00007  * 1.1 (the "License"); you may not use this file except in compliance with
00008  * the License. You may obtain a copy of the License at
00009  * http://www.mozilla.org/MPL/
00010  *
00011  * Software distributed under the License is distributed on an "AS IS" basis,
00012  * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
00013  * for the specific language governing rights and limitations under the
00014  * License.
00015  *
00016  * The Original Code is nsDiskCacheBlockFile.cpp, released
00017  * April 12, 2001.
00018  *
00019  * The Initial Developer of the Original Code is
00020  * Netscape Communications Corporation.
00021  * Portions created by the Initial Developer are Copyright (C) 2001
00022  * the Initial Developer. All Rights Reserved.
00023  *
00024  * Contributor(s):
00025  *   Gordon Sheridan  <gordon@netscape.com>
00026  *
00027  * Alternatively, the contents of this file may be used under the terms of
00028  * either the GNU General Public License Version 2 or later (the "GPL"), or
00029  * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
00030  * in which case the provisions of the GPL or the LGPL are applicable instead
00031  * of those above. If you wish to allow use of your version of this file only
00032  * under the terms of either the GPL or the LGPL, and not to allow others to
00033  * use your version of this file under the terms of the MPL, indicate your
00034  * decision by deleting the provisions above and replace them with the notice
00035  * and other provisions required by the GPL or the LGPL. If you do not delete
00036  * the provisions above, a recipient may use your version of this file under
00037  * the terms of any one of the MPL, the GPL or the LGPL.
00038  *
00039  * ***** END LICENSE BLOCK ***** */
00040 
00041 #include "nsCRT.h"
00042 #include "nsDiskCache.h"
00043 #include "nsDiskCacheBlockFile.h"
00044 
00045 /******************************************************************************
00046  * nsDiskCacheBlockFile - 
00047  *****************************************************************************/
00048 
00049 
00050 /******************************************************************************
00051  *  Open
00052  *****************************************************************************/
00053 nsresult
00054 nsDiskCacheBlockFile::Open( nsILocalFile *  blockFile, PRUint32  blockSize)
00055 {
00056     PRInt32   fileSize;
00057 
00058     mBlockSize = blockSize;
00059     
00060     // open the file - restricted to user, the data could be confidential
00061     nsresult rv = blockFile->OpenNSPRFileDesc(PR_RDWR | PR_CREATE_FILE, 00600, &mFD);
00062     if (NS_FAILED(rv))  return rv;  // unable to open or create file
00063     
00064     // allocate bit map buffer
00065     mBitMap = new PRUint8[kBitMapBytes];
00066     if (!mBitMap) {
00067         rv = NS_ERROR_OUT_OF_MEMORY;
00068         goto error_exit;
00069     }
00070     
00071     // check if we just creating the file
00072     fileSize = PR_Available(mFD);
00073     if (fileSize < 0) {
00074         // XXX an error occurred. We could call PR_GetError(), but how would that help?
00075         rv = NS_ERROR_UNEXPECTED;
00076         goto error_exit;
00077     }
00078     mEndOfFile = fileSize;
00079     if (mEndOfFile == 0) {
00080         // initialize bit map and write it
00081         memset(mBitMap, 0, kBitMapBytes);
00082         PRInt32 bytesWritten = PR_Write(mFD, mBitMap, kBitMapBytes);
00083         if (bytesWritten < kBitMapBytes) goto error_exit;
00084         mEndOfFile = kBitMapBytes;
00085         
00086     } else if (mEndOfFile < kBitMapBytes) {
00087         rv = NS_ERROR_UNEXPECTED;  // XXX NS_ERROR_CACHE_INVALID;
00088         goto error_exit;
00089         
00090     } else {
00091         // read the bit map
00092         PRInt32 bytesRead = PR_Read(mFD, mBitMap, kBitMapBytes);
00093         if (bytesRead < kBitMapBytes) {
00094             rv = NS_ERROR_UNEXPECTED;
00095             goto error_exit;
00096         }
00097         
00098         // validate block file
00099         rv = ValidateFile();
00100         if (NS_FAILED(rv)) goto error_exit;
00101     }
00102     
00103     return NS_OK;
00104 
00105 error_exit:
00106     if (mFD) {
00107         (void) PR_Close(mFD);
00108         mFD = nsnull;
00109     }
00110     
00111     if (mBitMap) {
00112         delete [] mBitMap;
00113         mBitMap = nsnull;
00114     }
00115     return rv;
00116 }
00117 
00118 
00119 /******************************************************************************
00120  *  Close
00121  *****************************************************************************/
00122 nsresult
00123 nsDiskCacheBlockFile::Close(PRBool flush)
00124 {
00125     if (!mFD)  return NS_OK;
00126     nsresult rv = NS_OK;
00127 
00128     if (flush)
00129         rv  = FlushBitMap();
00130 
00131     PRStatus err = PR_Close(mFD);
00132     mFD = nsnull;
00133     
00134     if (mBitMap) {
00135         delete [] mBitMap;
00136         mBitMap = nsnull;
00137     }
00138     
00139     if (NS_SUCCEEDED(rv) && (err != PR_SUCCESS))
00140         rv = NS_ERROR_UNEXPECTED;
00141         
00142     return rv;
00143 }
00144 
00145 
00146 /******************************************************************************
00147  *  Trim
00148  *
00149  *  Truncate the block file to the end of the last allocated block.
00150  *
00151  *****************************************************************************/
00152 nsresult
00153 nsDiskCacheBlockFile::Trim()
00154 {
00155     PRInt32  estimatedSize = kBitMapBytes;
00156     PRInt32  lastBlock = LastBlock();
00157     if (lastBlock >= 0)
00158         estimatedSize += (lastBlock + 1) * mBlockSize;
00159             
00160     nsresult rv = nsDiskCache::Truncate(mFD, estimatedSize);
00161     return rv;
00162 }
00163 
00164 
00165 /******************************************************************************
00166  *  AllocateBlocks
00167  *
00168  *  Allocates 1-4 blocks, using a first fit strategy,
00169  *  so that no group of blocks spans a quad block boundary.
00170  *
00171  *  Returns block number of first block allocated or -1 on failure.
00172  *
00173  *****************************************************************************/
00174 PRInt32
00175 nsDiskCacheBlockFile::AllocateBlocks(PRInt32 numBlocks)
00176 {
00177     if (!mFD)  return -1;  // NS_ERROR_NOT_AVAILABLE;
00178     // return -1 if unable to allocate blocks
00179     // PRUint8  mask = (0x01 << numBlocks) - 1;    
00180     int     i = 0;
00181     PRUint8 mapByte;
00182     PRUint8 mask;
00183     
00184     // presume allocation will succeed
00185     PRBool oldValue = mBitMapDirty;
00186     mBitMapDirty = PR_TRUE;
00187     
00188     while ((mBitMap[i] == 0xFF) && (i < kBitMapBytes)) ++i;     // find first block with a free bit
00189     
00190     if (numBlocks == 1) {
00191         if (i < kBitMapBytes) {
00192             // don't need a while loop, because we know there's at least 1 free bit in this byte
00193             mapByte = ~mBitMap[i]; // flip bits so free bits are 1
00194 /*
00195  *          // Linear search for first free bit in byte
00196  *          mask = 0x01;
00197  *          for (int j=0; j<8; ++j, mask <<= 1)
00198  *              if (mask & mapByte) {mBitMap[i] |= mask; return (i * 8 + j); }
00199  */
00200             // Binary search for first free bit in byte
00201             PRUint8 bit = 0;
00202             if ((mapByte & 0x0F) == 0) { bit |= 4; mapByte >>= 4; }
00203             if ((mapByte & 0x03) == 0) { bit |= 2; mapByte >>= 2; }
00204             if ((mapByte & 0x01) == 0) { bit |= 1; mapByte >>= 1; }
00205             mBitMap[i] |= (PRUint8)1 << bit;
00206             return i * 8 + bit;
00207         }
00208     } else if (numBlocks == 2) {
00209         while (i < kBitMapBytes) {
00210             mapByte = ~mBitMap[i]; // flip bits so free bits are 1
00211             mask = 0x03;
00212             // check for fit in lower quad bits
00213             if ((mask & mapByte) == mask) { mBitMap[i] |= mask; return (i * 8); }       mask <<= 1;
00214             if ((mask & mapByte) == mask) { mBitMap[i] |= mask; return (i * 8 + 1); }   mask <<= 1;
00215             if ((mask & mapByte) == mask) { mBitMap[i] |= mask; return (i * 8 + 2); }   mask <<= 2;
00216             // check for fit in upper quad bits
00217             if ((mask & mapByte) == mask) { mBitMap[i] |= mask; return (i * 8 + 4); }   mask <<= 1;
00218             if ((mask & mapByte) == mask) { mBitMap[i] |= mask; return (i * 8 + 5); }   mask <<= 1;
00219             if ((mask & mapByte) == mask) { mBitMap[i] |= mask; return (i * 8 + 6); }
00220             ++i;
00221         }
00222     } else if (numBlocks == 3) {
00223         while (i < kBitMapBytes) {
00224             mapByte = ~mBitMap[i]; // flip bits so free bits are 1
00225             mask = 0x07;
00226             // check for fit in lower quad bits
00227             if ((mask & mapByte) == mask) { mBitMap[i] |= mask; return (i * 8); }       mask <<= 1;
00228             if ((mask & mapByte) == mask) { mBitMap[i] |= mask; return (i * 8 + 1); }   mask <<= 3;
00229             // check for fit in upper quad bits
00230             if ((mask & mapByte) == mask) { mBitMap[i] |= mask; return (i * 8 + 4); }   mask <<= 1;
00231             if ((mask & mapByte) == mask) { mBitMap[i] |= mask; return (i * 8 + 5); }
00232             ++i;
00233         }
00234     } else if (numBlocks == 4) {
00235         while (i < kBitMapBytes) {
00236             mapByte = ~mBitMap[i]; // flip bits so free bits are 1
00237             mask = 0x0F;
00238             // check for fit in lower quad bits
00239             if ((mask & mapByte) == mask) { mBitMap[i] |= mask; return (i * 8); }      mask <<= 4;
00240             // check for fit in upper quad bits
00241             if ((mask & mapByte) == mask) { mBitMap[i] |= mask; return (i * 8 + 4); }
00242             ++i;
00243         }
00244     }
00245     
00246     mBitMapDirty = oldValue;
00247     return -1;
00248 }
00249 
00250 
00251 /******************************************************************************
00252  *  DeallocateBlocks
00253  *****************************************************************************/
00254 nsresult
00255 nsDiskCacheBlockFile::DeallocateBlocks( PRInt32  startBlock, PRInt32  numBlocks)
00256 {
00257     if (!mFD)  return NS_ERROR_NOT_AVAILABLE;
00258     if ((startBlock < 0) || (startBlock > kBitMapBytes * 8 - 1) ||
00259         (numBlocks < 1)  || (numBlocks > 4))
00260        return NS_ERROR_ILLEGAL_VALUE;
00261            
00262     PRInt32 startByte = startBlock / 8;
00263     PRUint8 startBit  = startBlock % 8;
00264     
00265     // make sure requested deallocation doesn't span a byte boundary
00266     if ((startBlock + numBlocks - 1) / 8 != startByte)  return NS_ERROR_UNEXPECTED;
00267     PRUint8 mask = ((0x01 << numBlocks) - 1) << startBit;
00268     
00269     PRUint8 mapByte = ~mBitMap[startByte]; // flip so allocated bits are zero
00270     
00271     // make sure requested deallocation is currently allocated
00272     if (mapByte & mask)  return NS_ERROR_ABORT;
00273     
00274     mBitMap[startByte] ^= mask;    // flips the bits off;
00275     mBitMapDirty = PR_TRUE;
00276     // XXX rv = FlushBitMap();  // coherency vs. performance
00277     return NS_OK;
00278 }
00279 
00280 
00281 /******************************************************************************
00282  *  WriteBlocks
00283  *****************************************************************************/
00284 nsresult
00285 nsDiskCacheBlockFile::WriteBlocks( void *   buffer,
00286                                    PRInt32  startBlock,
00287                                    PRInt32  numBlocks)
00288 {
00289     // presume buffer != nsnull
00290     if (!mFD)  return NS_ERROR_NOT_AVAILABLE;
00291     nsresult rv = VerifyAllocation(startBlock, numBlocks);
00292     if (NS_FAILED(rv))  return rv;
00293     
00294     // seek to block position
00295     PRInt32 blockPos = kBitMapBytes + startBlock * mBlockSize;
00296     PRInt32 filePos = PR_Seek(mFD, blockPos, PR_SEEK_SET);
00297     if (filePos != blockPos)  return NS_ERROR_UNEXPECTED;
00298     
00299     if (mEndOfFile < (blockPos + numBlocks * mBlockSize))
00300         mEndOfFile = (blockPos + numBlocks * mBlockSize);
00301     
00302     // write the blocks
00303     PRInt32 bytesToWrite = numBlocks * mBlockSize;
00304     PRInt32 bytesWritten = PR_Write(mFD, buffer, bytesToWrite);
00305     if (bytesWritten < bytesToWrite)  return NS_ERROR_UNEXPECTED;
00306     
00307     // write the bit map and flush the file
00308     // XXX except we would take a severe performance hit
00309     // XXX rv = FlushBitMap();
00310     return rv;
00311 }
00312 
00313 
00314 /******************************************************************************
00315  *  ReadBlocks
00316  *****************************************************************************/
00317 nsresult
00318 nsDiskCacheBlockFile::ReadBlocks(  void *    buffer,
00319                                    PRInt32   startBlock,
00320                                    PRInt32   numBlocks)
00321 {
00322     // presume buffer != nsnull
00323     if (!mFD)  return NS_ERROR_NOT_AVAILABLE;
00324     nsresult rv = VerifyAllocation(startBlock, numBlocks);
00325     if (NS_FAILED(rv))  return rv;
00326     
00327     // seek to block position
00328     PRInt32 blockPos = kBitMapBytes + startBlock * mBlockSize;
00329     PRInt32 filePos = PR_Seek(mFD, blockPos, PR_SEEK_SET);
00330     if (filePos != blockPos)  return NS_ERROR_UNEXPECTED;
00331 
00332     // read the blocks
00333     PRInt32 bytesToRead = numBlocks * mBlockSize;
00334     PRInt32 bytesRead = PR_Read(mFD, buffer, bytesToRead);
00335     if (bytesRead < bytesToRead)  return NS_ERROR_UNEXPECTED;
00336     
00337     return rv;
00338 }
00339 
00340 
00341 /******************************************************************************
00342  *  FlushBitMap
00343  *****************************************************************************/
00344 nsresult
00345 nsDiskCacheBlockFile::FlushBitMap()
00346 {
00347     if (!mBitMapDirty)  return NS_OK;
00348 
00349     // seek to bitmap
00350     PRInt32 filePos = PR_Seek(mFD, 0, PR_SEEK_SET);
00351     if (filePos != 0)  return NS_ERROR_UNEXPECTED;
00352     
00353     // write bitmap
00354     PRInt32 bytesWritten = PR_Write(mFD, mBitMap, kBitMapBytes);
00355     if (bytesWritten < kBitMapBytes)  return NS_ERROR_UNEXPECTED;
00356 
00357     PRStatus err = PR_Sync(mFD);
00358     if (err != PR_SUCCESS)  return NS_ERROR_UNEXPECTED;
00359 
00360     mBitMapDirty = PR_FALSE;
00361     return NS_OK;
00362 }
00363 
00364 
00365 /******************************************************************************
00366  *  ValidateFile
00367  *
00368  *  Check size of file against last bit allocated for mBlockSize.
00369  *
00370  *****************************************************************************/
00371 nsresult
00372 nsDiskCacheBlockFile::ValidateFile()
00373 {
00374     PRInt32  estimatedSize = kBitMapBytes;
00375     PRInt32  lastBlock = LastBlock();
00376     if (lastBlock >= 0)
00377         estimatedSize += (lastBlock + 1) * mBlockSize;
00378         
00379     // seek to beginning
00380     PRInt32 filePos = PR_Seek(mFD, 0, PR_SEEK_SET);
00381     if (filePos != 0)  return NS_ERROR_UNEXPECTED;
00382     
00383     PRInt32 fileSize = PR_Available(mFD);
00384 
00385     if (estimatedSize > fileSize)
00386         return NS_ERROR_UNEXPECTED;
00387 
00388     return NS_OK;
00389 }
00390 
00391 
00392 /******************************************************************************
00393  *  VerfiyAllocation
00394  *
00395  *  Return values:
00396  *      NS_OK if all bits are marked allocated
00397  *      NS_ERROR_ILLEGAL_VALUE if parameters don't obey constraints
00398  *      NS_ERROR_FAILURE if some or all the bits are marked unallocated
00399  *
00400  *****************************************************************************/
00401 nsresult
00402 nsDiskCacheBlockFile::VerifyAllocation( PRInt32  startBlock, PRInt32  numBlocks)
00403 {
00404     if ((startBlock < 0) || (startBlock > kBitMapBytes * 8 - 1) ||
00405         (numBlocks < 1)  || (numBlocks > 4))
00406        return NS_ERROR_ILLEGAL_VALUE;
00407     
00408     PRInt32 startByte = startBlock / 8;
00409     PRUint8 startBit  = startBlock % 8;
00410     
00411     // make sure requested deallocation doesn't span a byte boundary
00412     if ((startBlock + numBlocks - 1) / 8 != startByte)  return NS_ERROR_ILLEGAL_VALUE;
00413     PRUint8 mask = ((0x01 << numBlocks) - 1) << startBit;
00414     
00415     // check if all specified blocks are currently allocated
00416     if ((mBitMap[startByte] & mask) != mask)    return NS_ERROR_FAILURE;
00417     
00418     return NS_OK;
00419 }
00420 
00421 
00422 /******************************************************************************
00423  *  LastBlock
00424  *
00425  *  Return last block allocated or -1 if no blocks are allocated.
00426  *
00427  *****************************************************************************/
00428 PRInt32
00429 nsDiskCacheBlockFile::LastBlock()
00430 {
00431     // search for last byte in mBitMap with allocated bits
00432     PRInt32 i = kBitMapBytes;
00433     while (--i >= 0) {
00434         if (mBitMap[i]) break;
00435     }
00436 
00437     if (i >= 0) {
00438         // binary search to find last allocated bit in byte
00439         PRUint8 mapByte = mBitMap[i];
00440         PRUint8 lastBit = 7;
00441         if ((mapByte & 0xF0) == 0) { lastBit ^= 4; mapByte <<= 4; }
00442         if ((mapByte & 0xC0) == 0) { lastBit ^= 2; mapByte <<= 2; }
00443         if ((mapByte & 0x80) == 0) { lastBit ^= 1; mapByte <<= 1; }
00444         return i * 8 + lastBit;
00445     }
00446     
00447     return -1;
00448 
00449 }