/*
* Copyright 1995, 2003 Perforce Software. All rights reserved.
*
* This file is part of Perforce - the FAST SCM System.
*/
/*
* fileiobuf.cc -- FileIOBuffer methods
*/
# ifdef USE_EBCDIC
# define NEED_EBCDIC
# endif
# include <stdhdrs.h>
# include <error.h>
# include <strbuf.h>
# include "filesys.h"
# include "fileio.h"
void
FileIOBuffer::Open( FileOpenMode mode, Error *e )
{
// Start w/ binary open
FileIOBinary::Open( mode, e );
// Clear send/receive buffers
snd = rcv = 0;
}
void
FileIOBuffer::FlushBuffer( Error *e )
{
#if defined( USE_EBCDIC ) && defined( NO_EBCDIC_FILES )
__etoa_l( iobuf.Text(), iobuf.Length() );
#endif
FileIOBinary::Write( iobuf.Text(), snd, e );
snd = 0;
}
void
FileIOBuffer::SetBufferSize( size_t l )
{
// You can only do this before you open the file.
if( fd == -1 )
iobuf.SetBufferSize( l );
}
void
FileIOBuffer::Close( Error *e )
{
// Flush buffers
while( snd && !e->Test())
FlushBuffer( e );
// finish with binary close
FileIOBinary::Close( e );
}
void
FileIOBuffer::Write( const char *buf, int len, Error *e )
{
// Write logic: copy whole lines that end in \n,
// translate the \n to a \r, and arrange so that
// a \n is added thereafter.
// addnl: saw a \r, must add a \n
int addnl = 0;
while( len || addnl )
{
// If iobuf is full, flush
if( snd == iobuf.Length() )
{
FlushBuffer( e );
if( e->Test() )
return;
}
// If we owe a \n because we just sent a \r,
// add it now (that we know we have space).
if( addnl )
addnl = 0, iobuf.Text()[ snd++ ] = '\n';
// buffer what we can
char *p;
int l = iobuf.Length() - snd;
if( l > len )
l = len;
switch( lineType )
{
case LineTypeRaw:
case LineTypeLfcrlf:
// Straight copy.
// LFCRLF writes LF.
memcpy( iobuf.Text() + snd, buf, l );
break;
case LineTypeCr:
// Copy out to the next \n. If we hit one, translate
// it to a \r.
if( p = (char *)memccpy( iobuf.Text() + snd, buf, '\n', l ) )
{
p[-1] = '\r';
l = p - iobuf.Text() - snd;
}
break;
case LineTypeCrLf:
// Copy out to the next \n. If we hit one, translate
// it to a \r and set addnl so as to write the \n on
// the next loop (when we're sure to have space).
if( p = (char *)memccpy( iobuf.Text() + snd, buf, '\n', l ) )
{
p[-1] = '\r';
l = p - iobuf.Text() - snd;
addnl = 1;
}
break;
}
snd += l;
buf += l;
len -= l;
}
}
void
FileIOBuffer::FillBuffer( Error *e )
{
rcv = FileIOBinary::Read( iobuf.Text(), iobuf.Length(), e );
#if defined( USE_EBCDIC ) && defined( NO_EBCDIC_FILES )
if( rcv > 0 )
__atoe_l( iobuf.Text(), rcv );
#endif
}
int
FileIOBuffer::Read( char *buf, int len, Error *e )
{
// Read logic: read whole lines that end in \r, and
// arrange so that a following \n translates the \r
// into a \n and the \n is dropped.
// soaknl: we saw a \r, skip this \n
int ilen = len;
int soaknl = 0;
while( len || soaknl )
{
// Nothing in the buffer?
if( !rcv )
{
ptr = iobuf.Text();
FillBuffer( e );
if( e->Test() )
return -1;
if( !rcv )
break;
}
// Skipping \n because we saw a \r?
if( soaknl )
{
if( *ptr == '\n' )
++ptr, --rcv, buf[-1] = '\n';
soaknl = 0;
}
// Trim avail to what's needed
// Fill user buffer; stop at \r
char *p;
int l = rcv < len ? rcv : len ;
switch( lineType )
{
case LineTypeRaw:
// Straight copy.
memcpy( buf, ptr, l );
break;
case LineTypeCr:
// Copy to the next \r. If we hit one, translate
// it to \n.
if( p = (char *)memccpy( buf, ptr, '\r', l ) )
{
l = p - buf;
p[-1] = '\n';
}
break;
case LineTypeCrLf:
// Copy to next \r. If we hit one, arrange so that
// if we see \n the next time through (when we know
// there'll be data in the buffer), we translate this
// \r to a \n and drop the subsequent \n.
// LFCRLF reads CRLF.
if( p = (char *)memccpy( buf, ptr, '\r', l ) )
{
l = p - buf;
soaknl = 1;
}
break;
case LineTypeLfcrlf:
if( p = (char *)memccpy( buf, ptr, '\r', l ) )
{
l = p - buf;
p[-1] = '\n';
soaknl = 1;
}
break;
}
ptr += l;
rcv -= l;
buf += l;
len -= l;
}
return ilen - len;
}
void
FileIOBuffer::Seek( offL_t pos, Error *e )
{
if( mode == FOM_WRITE && snd > 0 )
FlushBuffer( e );
if( !e->Test() )
FileIOBinary::Seek( pos, e );
// Clear send/receive buffers
snd = rcv = 0;
}
offL_t
FileIOBuffer::Tell()
{
if( mode == FOM_READ )
return tellpos - rcv;
return tellpos + snd;
}
/*
* This is an optimized version of FileSys::ReadLine to take advantage
* of the buffering we have.
*
* The macro STRICT_LINEENDING sets up the code to strictly follow
* the specified FileSys line ending, but that does not match the
* reality of the prior existing FileSys::ReadLine. I'm submitting
* with this macro option in case we decide to use this strict case
* in the future instead of having to rediscover how to do that. - JAA
*
* See the test program readlinetest in the tests directory.
*
* Return values...
* 0 if end of file
* 1 if complete line read
* -1 partial line read - size excedded before line ended
*/
int
FileIOBuffer::ReadLine( StrBuf *buf, Error *e )
{
buf->Clear();
int size = iobuf.Length();
// Read logic: read whole lines that end in \r, and
// arrange so that a following \n translates the \r
// into a \n and the \n is dropped.
// soaknl: we saw a \r, skip this \n
int soaknl = 0;
int linedone = 0;
while( ( !linedone && buf->Length() < size ) || soaknl )
{
// Nothing in the buffer?
if( !rcv )
{
ptr = iobuf.Text();
FillBuffer( e );
if( e->Test() || !rcv )
{
if( linedone || buf->Length() > 0 )
break; // this really exits 1
return 0;
}
}
// Skipping \n because we saw a \r?
if( soaknl )
{
if( *ptr == '\n' )
++ptr, --rcv;
soaknl = 0;
}
// Trim avail to what's needed
// Fill user buffer; stop at \r
if( linedone || buf->Length() >= size )
break;
char *p;
int l = rcv < size ? rcv : size;
switch( lineType )
{
case LineTypeRaw:
// Straight copy.
p = (char *)memchr( ptr, '\n', l );
if( p )
{
l = p - ptr;
buf->Extend( ptr, l );
l++;
linedone = 1;
}
else
buf->Extend( ptr, l );
break;
case LineTypeCr:
// Copy to the next \r.
#ifndef STRICT_LINEENDING
// or the next \n
if( p = (char *)memchr( ptr, '\n', l ) )
{
l = p - ptr;
if( p = (char *)memchr( ptr, '\r', l ) )
l = p - ptr;
buf->Extend( ptr, l );
l++;
linedone = 1;
}
else
#endif
if( p = (char *)memchr( ptr, '\r', l ) )
{
l = p - ptr;
buf->Extend( ptr, l );
l++;
linedone = 1;
}
else
buf->Extend( ptr, l );
break;
case LineTypeCrLf:
#ifdef STRICT_LINEENDING
// Copy to next \r. If we hit one, arrange so that
// if we see \n the next time through (when we know
// there'll be data in the buffer) drop it.
if( p = (char *)memchr( ptr, '\r', l ) )
{
l = p - ptr;
buf->Extend( ptr, l );
l++;
soaknl = linedone = 1;
}
else
buf->Extend( ptr, l );
break;
#else
// fall through...
#endif
case LineTypeLfcrlf:
if( p = (char *)memchr( ptr, '\n', l ) )
{
l = p - ptr;
if( p > ptr && p[-1] == '\r' )
buf->Extend( ptr, l-1 );
else
buf->Extend( ptr, l );
l++;
linedone = 1;
}
else if( ptr[l-1] == '\r' )
{
buf->Extend( ptr, l-1 );
soaknl = linedone = 1;
}
else
buf->Extend( ptr, l );
break;
}
ptr += l;
rcv -= l;
}
buf->Terminate();
return linedone ? 1 : -1;
}