Back to index

lightning-sunbird  0.9+nobinonly
nr_bufio.c
Go to the documentation of this file.
00001 /* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*-
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 Mozilla Communicator.
00017  *
00018  * The Initial Developer of the Original Code is
00019  * Netscape Communications Corporation.
00020  * Portions created by the Initial Developer are Copyright (C) 1999
00021  * the Initial Developer. All Rights Reserved.
00022  *
00023  * Contributor(s):
00024  *   Daniel Veditz <dveditz@netscape.com>
00025  *   Edward Kandrot <kandrot@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 /*------------------------------------------------------------------------
00042  * nr_bufio
00043  * 
00044  * Buffered I/O routines to improve registry performance
00045  * the routines mirror fopen(), fclose() et al
00046  *
00047  * Inspired by the performance gains James L. Nance <jim_nance@yahoo.com>
00048  * got using NSPR memory-mapped I/O for the registry. Unfortunately NSPR 
00049  * doesn't support mmapio on the Mac.
00050  *-----------------------------------------------------------------------*/
00051 
00052 #include <stdio.h>
00053 #include <string.h>
00054 #include <errno.h>
00055 #if defined(XP_MAC) || defined(XP_MACOSX)
00056   #include <Errors.h>
00057 #endif
00058 
00059 #if defined(SUNOS4)
00060 #include <unistd.h>  /* for SEEK_SET */
00061 #endif /* SUNOS4 */
00062 
00063 #include "prerror.h"
00064 #include "prlog.h"
00065 
00066 #include "vr_stubs.h"
00067 #include "nr_bufio.h"
00068 
00069 
00070 #define BUFIO_BUFSIZE_DEFAULT   0x2000
00071 
00072 #define STARTS_IN_BUF(f) ((f->fpos >= f->datastart) && \
00073                          (f->fpos < (f->datastart+f->datasize)))
00074 
00075 #define ENDS_IN_BUF(f,c) (((f->fpos + c) > (PRUint32)f->datastart) && \
00076                          ((f->fpos + c) <= (PRUint32)(f->datastart+f->datasize)))
00077 
00078 #if DEBUG_dougt
00079 static num_reads = 0;
00080 #endif
00081 
00082 
00083 struct BufioFileStruct 
00084 {
00085     FILE    *fd;        /* real file descriptor */
00086     PRInt32 fsize;      /* total size of file */
00087     PRInt32 fpos;       /* our logical position in the file */
00088     PRInt32 datastart;  /* the file position at which the buffer starts */
00089     PRInt32 datasize;   /* the amount of data actually in the buffer*/
00090     PRInt32 bufsize; /* size of the in memory buffer */
00091     PRBool  bufdirty;   /* whether the buffer been written to */
00092     PRInt32 dirtystart;
00093     PRInt32 dirtyend;
00094     PRBool  readOnly;   /* whether the file allows writing or not */
00095 #ifdef DEBUG_dveditzbuf
00096     PRUint32 reads;
00097     PRUint32 writes;
00098 #endif
00099     char    *data;      /* the data buffer */
00100 };
00101 
00102 
00103 static PRBool _bufio_loadBuf( BufioFile* file, PRUint32 count );
00104 static int    _bufio_flushBuf( BufioFile* file );
00105 
00106 #ifdef XP_OS2
00107 #include <fcntl.h>
00108 #include <sys/stat.h>
00109 #include <share.h>
00110 #include <io.h>
00111 
00112 FILE* os2_fileopen(const char* name, const char* mode)
00113 {
00114     int access = O_RDWR;
00115     int descriptor;
00116     int pmode = 0;
00117 
00118     /* Fail if only one character is passed in - this shouldn't happen */
00119     if (mode[1] == '\0') {
00120         return NULL;
00121     }
00122     /* Only possible options are wb+, rb+, wb and rb */
00123     if (mode[0] == 'w' && mode[1] == 'b') {
00124         access |= (O_TRUNC | O_CREAT);
00125         if (mode[2] == '+') {
00126             access |= O_RDWR;
00127             pmode = S_IREAD | S_IWRITE;
00128         } else {
00129             access |= O_WRONLY;
00130             pmode = S_IWRITE;
00131         }
00132     }
00133     if (mode[0] == 'r' && mode[1] == 'b') {
00134         if (mode[2] == '+') {
00135             access |= O_RDWR;
00136             pmode = S_IREAD | S_IWRITE;
00137         } else {
00138             access = O_RDONLY;
00139             pmode = S_IREAD;
00140         }
00141     }
00142 
00143     descriptor = sopen(name, access, SH_DENYNO, pmode);
00144     if (descriptor != -1) {
00145         return fdopen(descriptor, mode);
00146     }
00147     return NULL;
00148 }
00149 #endif
00150 
00154 BufioFile*  bufio_Open(const char* name, const char* mode)
00155 {
00156     FILE        *fd;
00157     BufioFile   *file = NULL;
00158 
00159 #ifdef XP_OS2
00160     fd = os2_fileopen( name, mode );
00161 #else
00162     fd = fopen( name, mode );
00163 #endif
00164     
00165     if ( fd )
00166     {
00167         /* file opened successfully, initialize the bufio structure */
00168 
00169         file = PR_NEWZAP( BufioFile );
00170         if ( file )
00171         {
00172             file->fd = fd;
00173             file->bufsize = BUFIO_BUFSIZE_DEFAULT;  /* set the default buffer size */
00174 
00175             file->data = (char*)PR_Malloc( file->bufsize );
00176             if ( file->data )
00177             {
00178                 /* get file size to finish initialization of bufio */
00179                 if ( !fseek( fd, 0, SEEK_END ) )
00180                 {
00181                     file->fsize = ftell( fd );
00182 
00183                     file->readOnly = strcmp(mode,XP_FILE_READ) == 0 || 
00184                                      strcmp(mode,XP_FILE_READ_BIN) == 0;
00185                 }
00186                 else
00187                 {
00188                     PR_Free( file->data );
00189                     PR_DELETE( file );
00190                 }
00191             }
00192             else
00193                 PR_DELETE( file );
00194         }
00195 
00196         /* close file if we couldn't create BufioFile */
00197         if (!file)
00198         {
00199             fclose( fd );
00200             PR_SetError( PR_OUT_OF_MEMORY_ERROR, 0 );
00201         }
00202     }
00203     else
00204     {
00205         /* couldn't open file. Figure out why and set NSPR errors */
00206         
00207         switch (errno)
00208         {
00209             /* file not found */
00210 #if defined(XP_MAC) || defined(XP_MACOSX)
00211             case fnfErr:
00212 #else
00213             case ENOENT:
00214 #endif
00215                 PR_SetError(PR_FILE_NOT_FOUND_ERROR,0);
00216                 break;
00217 
00218             /* file in use */
00219 #if defined(XP_MAC) || defined(XP_MACOSX)
00220             case opWrErr:
00221 #else
00222             case EACCES:
00223 #endif
00224                 PR_SetError(PR_NO_ACCESS_RIGHTS_ERROR,0);
00225                 break;
00226 
00227             default:
00228                 PR_SetError(PR_UNKNOWN_ERROR,0);
00229                 break;
00230         }
00231     }
00232 
00233     return file;
00234 }
00235 
00236 
00237 
00241 int bufio_Close(BufioFile* file)
00242 {
00243     int retval = -1;
00244 
00245     if ( file )
00246     {
00247         if ( file->bufdirty )
00248             _bufio_flushBuf( file );
00249 
00250         retval = fclose( file->fd );
00251 
00252         if ( file->data )
00253             PR_Free( file->data );
00254 
00255         PR_DELETE( file );
00256 #if DEBUG_dougt
00257         printf(" --- > Buffered registry read fs hits (%d)\n", num_reads);
00258 #endif
00259     }
00260 
00261     return retval;
00262 }
00263 
00264 
00265 
00269 int bufio_Seek(BufioFile* file, PRInt32 offset, int whence)
00270 {
00271     if (!file)
00272         return -1;
00273 
00274     switch(whence) 
00275     {
00276       case SEEK_SET:
00277            file->fpos = offset;
00278            break;
00279          case SEEK_END:
00280            file->fpos = file->fsize + offset;
00281            break;
00282          case SEEK_CUR:
00283            file->fpos = file->fpos + offset;
00284            break;
00285          default:
00286            return -1;
00287     }
00288 
00289     if ( file->fpos < 0 ) 
00290         file->fpos = 0;
00291 
00292     return 0;
00293 }
00294 
00295 
00296 
00300 PRInt32 bufio_Tell(BufioFile* file)
00301 {
00302     if (file)
00303         return file->fpos;
00304     else
00305         return -1;
00306 }
00307 
00308 
00309 
00310 PRUint32 bufio_Read(BufioFile* file, char* dest, PRUint32 count)
00311 {
00312     PRInt32     startOffset;
00313     PRInt32     endOffset;
00314     PRInt32     leftover;
00315     PRUint32    bytesCopied;
00316     PRUint32    bytesRead;
00317     PRUint32    retcount = 0;
00318 
00319     /* sanity check arguments */
00320     if ( !file || !dest || count == 0 || file->fpos >= file->fsize )
00321         return 0;
00322 
00323     /* Adjust amount to read if we're near EOF */
00324     if ( (file->fpos + count) > (PRUint32)file->fsize )
00325         count = file->fsize - file->fpos;
00326 
00327 
00328     /* figure out how much of the data we want is already buffered */
00329 
00330     startOffset = file->fpos - file->datastart;
00331     endOffset = startOffset + count;
00332 
00333     if ( startOffset >= 0 && startOffset < file->datasize )
00334     {
00335         /* The beginning of what we want is in the buffer  */
00336         /* so copy as much as is available of what we want */
00337 
00338         if ( endOffset <= file->datasize )
00339             bytesCopied = count;
00340         else
00341             bytesCopied = file->datasize - startOffset;
00342 
00343         memcpy( dest, file->data + startOffset, bytesCopied );
00344         retcount = bytesCopied;
00345         file->fpos += bytesCopied;
00346 #ifdef DEBUG_dveditzbuf
00347         file->reads++;
00348 #endif
00349 
00350         /* Was that all we wanted, or do we need to get more? */
00351 
00352         leftover = count - bytesCopied;
00353         PR_ASSERT( leftover >= 0 );     /* negative left? something's wrong! */
00354 
00355         if ( leftover )
00356         {
00357             /* need data that's not in the buffer */
00358 
00359             /* if what's left fits in a buffer then load the buffer with the */
00360             /* new area before giving the data, otherwise just read right    */
00361             /* into the user's dest buffer */
00362 
00363             if ( _bufio_loadBuf( file, leftover ) )
00364             {
00365                 startOffset = file->fpos - file->datastart;
00366 
00367                 /* we may not have been able to load as much as we wanted */
00368                 if ( startOffset > file->datasize )
00369                     bytesRead = 0;
00370                 else if ( startOffset+leftover <= file->datasize )
00371                     bytesRead = leftover;
00372                 else
00373                     bytesRead = file->datasize - startOffset;
00374 
00375                 if ( bytesRead )
00376                 {
00377                     memcpy( dest+bytesCopied, file->data+startOffset, bytesRead );
00378                     file->fpos += bytesRead;
00379                     retcount += bytesRead;
00380 #ifdef DEBUG_dveditzbuf
00381                     file->reads++;
00382 #endif
00383                 }
00384             }
00385             else 
00386             {
00387                 /* we need more than we could load into a buffer, so */
00388                 /* skip buffering and just read the data directly    */
00389 
00390                 if ( fseek( file->fd, file->fpos, SEEK_SET ) == 0 )
00391                 {
00392 #if DEBUG_dougt
00393                     ++num_reads;
00394 #endif
00395                     bytesRead = fread(dest+bytesCopied, 1, leftover, file->fd);
00396                     file->fpos += bytesRead;
00397                     retcount += bytesRead;
00398                 }
00399                 else 
00400                 {
00401                     /* XXX seek failed, couldn't load more data -- help! */
00402                     /* should we call PR_SetError() ? */
00403                 }
00404             }
00405         }
00406     }
00407     else
00408     {
00409         /* range doesn't start in the loaded buffer but it might end there */
00410         if ( endOffset > 0 && endOffset <= file->datasize )
00411             bytesCopied = endOffset;
00412         else
00413             bytesCopied = 0;
00414 
00415         leftover = count - bytesCopied;
00416 
00417         if ( bytesCopied )
00418         {
00419             /* the tail end of the range we want is already buffered */
00420             /* first copy the buffered data to the dest area         */
00421             memcpy( dest+leftover, file->data, bytesCopied );
00422 #ifdef DEBUG_dveditzbuf
00423             file->reads++;
00424 #endif
00425         }
00426             
00427         /* now pick up the part that's not already in the buffer */
00428 
00429         if ( _bufio_loadBuf( file, leftover ) )
00430         {
00431             /* we were able to load some data */
00432             startOffset = file->fpos - file->datastart;
00433 
00434             /* we may not have been able to read as much as we wanted */
00435             if ( startOffset > file->datasize )
00436                 bytesRead = 0;
00437             else if ( startOffset+leftover <= file->datasize )
00438                 bytesRead = leftover;
00439             else
00440                 bytesRead = file->datasize - startOffset;
00441 
00442             if ( bytesRead )
00443             {
00444                 memcpy( dest, file->data+startOffset, bytesRead );
00445 #ifdef DEBUG_dveditzbuf
00446                 file->reads++;
00447 #endif
00448             }
00449         }
00450         else
00451         {
00452             /* leftover data doesn't fit so skip buffering */
00453             if ( fseek( file->fd, file->fpos, SEEK_SET ) == 0 )
00454             {
00455                 bytesRead = fread(dest, 1, leftover, file->fd);
00456 #if DEBUG_dougt
00457                 ++num_reads;
00458 #endif        
00459             }
00460             else
00461                 bytesRead = 0;
00462         }
00463 
00464         /* if we couldn't read all the leftover, don't tell caller */
00465         /* about the tail end we copied from the first buffer      */
00466         if ( bytesRead == (PRUint32)leftover )
00467             retcount = bytesCopied + bytesRead;
00468         else
00469             retcount = bytesRead;
00470 
00471         file->fpos += retcount;
00472     }
00473 
00474     return retcount;
00475 }
00476 
00477 
00478 
00482 PRUint32 bufio_Write(BufioFile* file, const char* src, PRUint32 count)
00483 {
00484     const char* newsrc;
00485     PRInt32  startOffset;
00486     PRInt32  endOffset;
00487     PRUint32 leftover;
00488     PRUint32 retcount = 0;
00489     PRUint32 bytesWritten = 0;
00490     PRUint32 bytesCopied = 0;
00491 
00492     /* sanity check arguments */
00493     if ( !file || !src || count == 0 || file->readOnly )
00494         return 0;
00495 
00496     /* Write to the current buffer if we can, otherwise load a new buffer */
00497 
00498     startOffset = file->fpos - file->datastart;
00499     endOffset = startOffset + count;
00500 
00501     if ( startOffset >= 0 && startOffset <  file->bufsize )
00502     {
00503         /* the area we want to write starts in the buffer */
00504 
00505         if ( endOffset <= file->bufsize )
00506             bytesCopied = count;
00507         else
00508             bytesCopied = file->bufsize - startOffset;
00509 
00510         memcpy( file->data + startOffset, src, bytesCopied );
00511         file->bufdirty = PR_TRUE;
00512         endOffset = startOffset + bytesCopied;
00513         file->dirtystart = PR_MIN( startOffset, file->dirtystart );
00514         file->dirtyend   = PR_MAX( endOffset,   file->dirtyend );
00515 #ifdef DEBUG_dveditzbuf
00516         file->writes++;
00517 #endif
00518 
00519         if ( endOffset > file->datasize )
00520             file->datasize = endOffset;
00521 
00522         retcount = bytesCopied;
00523         file->fpos += bytesCopied;
00524 
00525         /* was that all we had to write, or is there more? */
00526         leftover = count - bytesCopied;
00527         newsrc = src+bytesCopied;
00528     }
00529     else
00530     {
00531         /* range doesn't start in the loaded buffer but it might end there */
00532         if ( endOffset > 0 && endOffset <= file->bufsize )
00533             bytesCopied = endOffset;
00534         else
00535             bytesCopied = 0;
00536 
00537         leftover = count - bytesCopied;
00538         newsrc = src;
00539 
00540         if ( bytesCopied )
00541         {
00542             /* the tail end of the write range is already in the buffer */
00543             memcpy( file->data, src+leftover, bytesCopied );
00544             file->bufdirty      = PR_TRUE;
00545             file->dirtystart    = 0;
00546             file->dirtyend      = PR_MAX( endOffset, file->dirtyend );
00547 #ifdef DEBUG_dveditzbuf
00548             file->writes++;
00549 #endif
00550 
00551             if ( endOffset > file->datasize )
00552                 file->datasize = endOffset;
00553         }
00554     }
00555 
00556     /* if we only wrote part of the request pick up the leftovers */
00557     if ( leftover )
00558     {
00559         /* load the buffer with the new range, if possible */
00560         if ( _bufio_loadBuf( file, leftover ) )
00561         {
00562             startOffset = file->fpos - file->datastart;
00563             endOffset   = startOffset + leftover;
00564 
00565             memcpy( file->data+startOffset, newsrc, leftover );
00566             file->bufdirty      = PR_TRUE;
00567             file->dirtystart    = startOffset;
00568             file->dirtyend      = endOffset;
00569 #ifdef DEBUG_dveditzbuf
00570             file->writes++;
00571 #endif
00572             if ( endOffset > file->datasize )
00573                 file->datasize = endOffset;
00574 
00575             bytesWritten = leftover;
00576         }
00577         else
00578         {
00579             /* request didn't fit in a buffer, write directly */
00580             if ( fseek( file->fd, file->fpos, SEEK_SET ) == 0 )
00581                 bytesWritten = fwrite( newsrc, 1, leftover, file->fd );
00582             else
00583                 bytesWritten = 0; /* seek failed! */
00584         }
00585 
00586         if ( retcount )
00587         {
00588             /* we already counted the first part we wrote */
00589             retcount    += bytesWritten;
00590             file->fpos  += bytesWritten;
00591         }
00592         else
00593         {
00594             retcount    = bytesCopied + bytesWritten;
00595             file->fpos  += retcount;
00596         }
00597     }
00598 
00599     if ( file->fpos > file->fsize )
00600         file->fsize = file->fpos;
00601     
00602     return retcount;
00603 }
00604 
00605 
00606 
00607 int bufio_Flush(BufioFile* file)
00608 {
00609     if ( file->bufdirty )
00610         _bufio_flushBuf( file );
00611     
00612     return fflush(file->fd);
00613 }
00614 
00615 
00616 
00617 /*---------------------------------------------------------------------------*
00618  * internal helper functions
00619  *---------------------------------------------------------------------------*/
00626 static PRBool _bufio_loadBuf( BufioFile* file, PRUint32 count )
00627 {
00628     PRInt32     startBuf;
00629     PRInt32     endPos;
00630     PRInt32     endBuf;
00631     PRUint32    bytesRead;
00632 
00633     /* no point in buffering more than the physical buffer will hold */
00634     if ( count > (PRUint32)file->bufsize )
00635         return PR_FALSE;
00636 
00637     /* Is caller asking for data we already have? */
00638     if ( STARTS_IN_BUF(file) && ENDS_IN_BUF(file,count) )
00639     {   
00640         PR_ASSERT(0);
00641         return PR_TRUE;
00642     }
00643 
00644     /* if the buffer's dirty make sure we successfully flush it */
00645     if ( file->bufdirty && _bufio_flushBuf(file) != 0 )
00646         return PR_FALSE;
00647 
00648     /* For now we're not trying anything smarter than simple paging. */
00649     /* Slide over if necessary to fit the entire request             */
00650     startBuf = ( file->fpos / file->bufsize ) * file->bufsize;
00651     endPos = file->fpos + count;
00652     endBuf = startBuf + file->bufsize;
00653     if ( endPos > endBuf )
00654         startBuf += (endPos - endBuf);
00655 
00656     if ( fseek( file->fd, startBuf, SEEK_SET ) != 0 )
00657         return PR_FALSE;
00658     else
00659     {
00660 #if DEBUG_dougt
00661         ++num_reads;
00662 #endif
00663         bytesRead = fread( file->data, 1, file->bufsize, file->fd );
00664         file->datastart  = startBuf;
00665         file->datasize   = bytesRead;
00666         file->bufdirty   = PR_FALSE;
00667         file->dirtystart = file->bufsize;
00668         file->dirtyend   = 0;
00669 #ifdef DEBUG_dveditzbuf
00670         printf("REG: buffer read %d (%d) after %d reads\n",startBuf,file->fpos,file->reads);
00671         file->reads = 0;
00672         file->writes = 0;
00673 #endif
00674         return PR_TRUE;
00675     }
00676 }
00677 
00678 
00679 
00680 static int _bufio_flushBuf( BufioFile* file )
00681 {
00682     PRUint32 written;
00683     PRUint32 dirtyamt;
00684     PRInt32  startpos;
00685 
00686     PR_ASSERT(file);
00687     if ( !file || !file->bufdirty )
00688         return 0;
00689 
00690     startpos = file->datastart + file->dirtystart;
00691     if ( !fseek( file->fd, startpos, SEEK_SET ) )
00692     {
00693         dirtyamt = file->dirtyend - file->dirtystart;
00694         written = fwrite( file->data+file->dirtystart, 1, dirtyamt, file->fd );
00695         if ( written == dirtyamt )
00696         {
00697 #ifdef DEBUG_dveditzbuf
00698             printf("REG: buffer flush %d - %d after %d writes\n",startpos,startpos+written,file->writes);
00699             file->writes = 0;
00700 #endif
00701             file->bufdirty   = PR_FALSE;
00702             file->dirtystart = file->bufsize;
00703             file->dirtyend   = 0;
00704             return 0;
00705         }
00706     }
00707     return -1;
00708 }
00709 
00710 
00711 
00712 /*
00713 *  sets the file buffer size to bufsize, clearing the buffer in the process.
00714 *
00715 *  accepts bufsize of -1 to mean default buffer size, defined by BUFIO_BUFSIZE_DEFAULT
00716 *  returns new buffers size, or -1 if error occured
00717 */
00718 
00719 int bufio_SetBufferSize(BufioFile* file, int bufsize)
00720 {
00721     char    *newBuffer;
00722     int     retVal = -1;
00723 
00724     PR_ASSERT(file);
00725     if (!file)
00726         return retVal;
00727 
00728     if (bufsize == -1)
00729         bufsize = BUFIO_BUFSIZE_DEFAULT;
00730     if (bufsize == file->bufsize)
00731         return bufsize;
00732 
00733     newBuffer = (char*)PR_Malloc( bufsize );
00734     if (newBuffer)
00735     {
00736         /* if the buffer's dirty make sure we successfully flush it */
00737         if ( file->bufdirty && _bufio_flushBuf(file) != 0 )
00738         {
00739             PR_Free( newBuffer );
00740             return -1;
00741         }
00742 
00743 
00744         file->bufsize = bufsize;
00745         if ( file->data )
00746             PR_Free( file->data );
00747         file->data = newBuffer;
00748         file->datasize = 0;
00749         file->datastart = 0;
00750         retVal = bufsize;
00751     }
00752  
00753     return retVal;
00754 }
00755 
00756 
00757 /* EOF nr_bufio.c */