//
// The developer of the original code and/or files is Tripwire, Inc.
// Portions created by Tripwire, Inc. are copyright (C) 2000 Tripwire,
// Inc. Tripwire is a registered trademark of Tripwire, Inc.  All rights
// reserved.
// 
// This program is free software.  The contents of this file are subject
// to the terms of the GNU General Public License as published by the
// Free Software Foundation; either version 2 of the License, or (at your
// option) any later version.  You may redistribute it and/or modify it
// only in compliance with the GNU General Public License.
// 
// This program is distributed in the hope that it will be useful.
// However, this program is distributed AS-IS WITHOUT ANY
// WARRANTY; INCLUDING THE IMPLIED WARRANTY OF MERCHANTABILITY OR FITNESS
// FOR A PARTICULAR PURPOSE.  Please see the GNU General Public License
// for more details.
// 
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307,
// USA.
// 
// Nothing in the GNU General Public License or any other license to use
// the code or files shall permit you to use Tripwire's trademarks,
// service marks, or other intellectual property without Tripwire's
// prior written consent.
// 
// If you have any questions, please contact Tripwire, Inc. at either
// info@tripwire.org or www.tripwire.org.
//
///////////////////////////////////////////////////////////////////////////////
// archive.cpp -- classes that abstract a raw byte archive
//
// cArchive			-- interface for single-direction (one pass) reads and writes
// cBidirArchive	-- interface for a random-access archive
// cMemArchive		-- implementation of a bidirectional archive in memory
// cFileArchive		-- implementation of a bidirectional archive as a file

#include "stdcore.h"
#include "archive.h"
#include "fsservices.h"

#include <stdio.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <iostream>
#include "file.h"
#include "stringutil.h"

#include "corestrings.h"    // for: STR_ERR2_ARCH_CRYPTO_ERR



#if FSEEK_TAKES_INT32
#define FSEEK(x, y, z) fseek((x), (int32)(y), (z))
#else
#define FSEEK(x, y, z) fseek((x), (y), (z)) 
#endif 


//=============================================================================
// Utility Functions
//=============================================================================

///////////////////////////////////////////////////////////////////////////////
// util_IsDir -- returns true if a given file is a directory
///////////////////////////////////////////////////////////////////////////////
bool util_IsDir( const TSTRING& fileName )
{
    cFSStatArgs s;
    try
    {
        iFSServices::GetInstance()->Stat( fileName, s );        
    }
    catch( eFSServices )
    {
        return false;
    }
    
    return( s.mFileType == cFSStatArgs::TY_DIR );
}

//=============================================================================
// eArchiveCrypto
//=============================================================================
TSTRING eArchiveCrypto::GetMsg( ) const
{
    // RAD: Updated this to use new stringtable
    return ( mMsg + TSS_GetString( cCore, core::STR_ERR2_ARCH_CRYPTO_ERR ) );
}


//=============================================================================
// cArchive 
//=============================================================================

// convenience methods
//
// Specific Read functions throw eArchive if EOF is reached because
// if the caller is requesting a certain amount of data to be present, 
// reaching EOF is unexpected
//
// ReadBlob and WriteBlob return number of bytes read or written.  Notice
// that ReadBlob does not throw an exception since eventually EOF is expected.
//
// ReadBlob can take NULL as a destination pointer
// 
// All write functions throw exceptions for unexpected events like 
// running out of memory or disk space.
//

void cArchive::ReadInt16(int16& ret) // throw(eArchive)
{
    if (ReadBlob(&ret, sizeof(int16)) != sizeof(int16))
        throw eArchiveEOF();

    ret = tw_ntohs(ret);
}

void cArchive::ReadInt32(int32& ret) // throw(eArchive)
{
    if (ReadBlob(&ret, sizeof(int32)) != sizeof(int32))
        throw eArchiveEOF();

    ret = tw_ntohl(ret);
}

void cArchive::ReadInt64(int64& ret) // throw(eArchive)
{
    if (ReadBlob(&ret, sizeof(int64)) != sizeof(int64))
        throw eArchiveEOF();

    ret = tw_ntohll(ret);
}

// NOTE:BAM 10/11/99 -- we store unsigned size, but it really only works with
// lengths < INT16_MAX due to sign extension in integral promotion during the 
// resize() in ReadString().
// format for written string: 16-bit unsigned size, then a list of 16-bit UCS2 (Unicode) characters
// not including terminating NULL
void cArchive::ReadString(TSTRING& ret) // throw(eArchive)
{
    // read in size of string
    int16 size;
    ReadInt16( size );

    // create buffer for WCHAR16 string
    wc16_string ws;
    ws.resize( size );
    WCHAR16* pwc = (WCHAR16*)ws.data();

    for( int n = 0; n < size; n++ )
    {
        int16 i16;
        ReadInt16( i16 );
        *pwc++ = i16;
    }

    // convert WCHAR16 string to a TSTRING
    ret = cStringUtil::WstrToTstr( ws );
}

int  cArchive::ReadBlob(void* pBlob, int count)
{
    return Read(pBlob, count);
}

void cArchive::WriteInt16(int16 i) // throw(eArchive)
{
    i = tw_htons(i);
    WriteBlob(&i, sizeof(int16));
}

void cArchive::WriteInt32(int32 i) // throw(eArchive)
{
    i = tw_htonl(i);
    WriteBlob(&i, sizeof(int32));
}

void cArchive::WriteInt64(int64 i) // throw(eArchive)
{
    i = tw_htonll(i);
    WriteBlob(&i, sizeof(int64));
}

// NOTE:BAM 10/11/99 -- we store unsigned size, but it really only works with
// lengths < INT16_MAX due to sign extension in integral promotion during the 
// resize() in ReadString().
// format for written string: 16-bit unsigned size, then a list of 16-bit UCS2 (Unicode) characters
// not including terminating NULL
void cArchive::WriteString(TSTRING s) // throw(eArchive)
{
    // convert string to a UCS2 string
    wc16_string ws;
    cStringUtil::Convert( ws, s );  // Make convert "type-dispatched"

    // we assume that we can represent the size as a unsigned 16-bit number    
    // (we actually write  it as a signed number, but we cast it)
    if( ws.length() > TSS_INT16_MAX )
        ThrowAndAssert( eArchiveStringTooLong() );

    WriteInt16( static_cast<int16>( ws.length() ) );

    // write out each 16 bit character
    // RAD:09/03/99 -- Optimized for performance with "const"
    wc16_string::const_iterator at = ws.begin();
    while ( at != ws.end() )
        WriteInt16( *at++ );
}


void cArchive::WriteBlob(const void* pBlob, int count) // throw(eArchive)
{
    if (Write(pBlob, count) < count)
        ThrowAndAssert(eArchiveWrite());
}

int32 cArchive::GetStorageSize(const TSTRING& str)
{
	int32 size = sizeof(int32);	// the length is always stored
	//
	// after the length, all of the characters in the string are written as 16-bit values, 
	// except for the null character
	//
	size += ( str.length() * 2 );

	return size;
}

int64 cArchive::Copy(cArchive* pFrom, int64 amt)
{
	enum { BUF_SIZE = 2048 };
	int8	buf[BUF_SIZE];
	int64	amtLeft = amt;

	while(amtLeft > 0)
	{
		int64 amtToRead = amtLeft > BUF_SIZE ? BUF_SIZE : amtLeft;
		int64 amtRead = pFrom->ReadBlob(buf, static_cast<int>( amtToRead ) );
		amtLeft -= amtRead;
		WriteBlob(buf, static_cast<int>( amtRead ) );
		if(amtRead < amtToRead)
			break;
	}

	// return the amount copied ...
	return (amt - amtLeft);
}

///////////////////////////////////////////////////////////////////////////////
// class cMemMappedArchive -- Archive that can be memory mapped.
///////////////////////////////////////////////////////////////////////////////

cMemMappedArchive::cMemMappedArchive()
{
    mpMappedMem = 0;
    mMappedOffset = 0;
    mMappedLength = 0;
}

cMemMappedArchive::~cMemMappedArchive()
{
}

int64 cMemMappedArchive::GetMappedOffset() const // throw(eArchive)
{
    if (mpMappedMem == 0)
        ThrowAndAssert(eArchiveMemmap());

    return mMappedOffset;
}

int64 cMemMappedArchive::GetMappedLength() const // throw(eArchive)
{
    if (mpMappedMem == 0)
        ThrowAndAssert(eArchiveMemmap());

    return mMappedLength;
}

const void* cMemMappedArchive::GetMap() const // throw(eArchive)
{
    if (mpMappedMem == 0)
        ThrowAndAssert(eArchiveMemmap());

    return mpMappedMem;
}

void* cMemMappedArchive::GetMap() // throw(eArchive)
{
    if (mpMappedMem == 0)
        ThrowAndAssert(eArchiveMemmap());

    return mpMappedMem;
}

void cMemMappedArchive::SetNewMap(void* pMap, int64 offset, int64 length) const
{
    if (pMap == 0)
    {
        mpMappedMem = 0;
        mMappedOffset = 0;
        mMappedLength = 0;
    }
    else
    {
        mpMappedMem = pMap;
        mMappedOffset = offset;
        mMappedLength = length;
    }
}

///////////////////////////////////////////////////////////////////////////////
// class cMemoryArchive -- An archive that stores itself in a memory buffer.
//      This buffer can grow as needed up until a pre-specified maximum
//      size.  The buffer can be read and written to and can be memory
//      mapped.
///////////////////////////////////////////////////////////////////////////////

cMemoryArchive::cMemoryArchive(int maxSize)
:   mMaxAllocatedLen(maxSize)
{
    ASSERT(maxSize > 0);
    mpMemory = 0;
    mAllocatedLen = 0;
    mLogicalSize = 0;
    mReadHead = 0;
}

cMemoryArchive::~cMemoryArchive()
{
    delete [] mpMemory;
}

bool cMemoryArchive::EndOfFile()
{
    return mReadHead >= mLogicalSize;
}

void cMemoryArchive::Seek(int64 offset, SeekFrom from) // throw(eArchive)
{
    switch (from)
    {
    case cBidirArchive::BEGINNING:
        break;
    case cBidirArchive::CURRENT:
        offset = mReadHead + (int)offset;
        break;
    case cBidirArchive::END:
        offset = mLogicalSize + (int)offset;
        break;
    default:
        ThrowAndAssert(eArchiveSeek(TSS_GetString( cCore, core::STR_MEMARCHIVE_FILENAME), TSS_GetString( cCore, core::STR_MEMARCHIVE_ERRSTR)));
    }

    if (offset > mLogicalSize)
        ThrowAndAssert(eArchiveSeek(TSS_GetString( cCore, core::STR_MEMARCHIVE_FILENAME), TSS_GetString( cCore, core::STR_MEMARCHIVE_ERRSTR)));

    mReadHead = static_cast<int>( offset );
}

int64 cMemoryArchive::CurrentPos() const
{
    return mReadHead;
}

int64 cMemoryArchive::Length() const
{
    return mLogicalSize;
}

void cMemoryArchive::Truncate()
{
    ASSERT(mReadHead >= 0);

    mLogicalSize = mReadHead;
    AllocateMemory(mLogicalSize);
}

void cMemoryArchive::MapArchive(int64 offset, int64 len) // throw(eArchive)
{
    if ( offset + (int)len > mLogicalSize )
        AllocateMemory( static_cast<int>( offset + len ) );

    SetNewMap(mpMemory + offset, offset, len);
}

void cMemoryArchive::MapArchive(int64 offset, int64 len) const // throw(eArchive)
{
    if (offset + (int)len > mLogicalSize)
        ThrowAndAssert(eArchiveMemmap());

    SetNewMap(mpMemory + offset, offset, len);
}

int cMemoryArchive::Read(void* pDest, int count)
{
    if (mReadHead + count > mLogicalSize)
        count = mLogicalSize - mReadHead;

    if (pDest != 0)
        memcpy(pDest, mpMemory + mReadHead, count);

    mReadHead += count;

    return count;
}

int cMemoryArchive::Write(const void* pDest, int count) // throw(eArchive)
{
    if (mReadHead + count > mLogicalSize)
    {
        AllocateMemory(mReadHead + count);
    }

    memcpy(mpMemory + mReadHead, pDest, count);

    mReadHead += count;

    return count;
}

void cMemoryArchive::AllocateMemory(int len) // throw(eArchive)
{
    const int MIN_ALLOCATED_SIZE = 1024;

    if (len > mAllocatedLen)
    {
        // grow the buffer
		// only error if we are in debug mode
#ifdef _DEBUG
        if (len > mMaxAllocatedLen)
            ThrowAndAssert(eArchiveOutOfMem());
#endif

        if( 0 == mAllocatedLen )
            mAllocatedLen = MIN_ALLOCATED_SIZE;
        
        while (mAllocatedLen < len)
            mAllocatedLen *= 2;

        int8* pNewMem = new int8[mAllocatedLen];
        if (mpMemory != 0)
        {
            memcpy(pNewMem, mpMemory, mLogicalSize);
            delete [] mpMemory;
        }
        mpMemory = pNewMem;
        mLogicalSize = len;

        // update memory map if there is one
        if (mpMappedMem)
            SetNewMap(mpMemory + mMappedOffset, mMappedOffset, mMappedLength);
    }
    else
    {
        // check for memory map conflict
        if (mpMappedMem && len < mMappedOffset + mMappedLength)
            ThrowAndAssert(eArchiveMemmap());

        if (len < (mAllocatedLen >> 1) && mAllocatedLen > MIN_ALLOCATED_SIZE)
        {
            // shrink the buffer
            int8* pNewMem = new int8[len];
            ASSERT(mpMemory);
            memcpy(pNewMem, mpMemory, len);
            delete [] mpMemory;
            mpMemory = pNewMem;
            mLogicalSize = len;

            // update memory map if there is one
            if (mpMappedMem)
                SetNewMap(mpMemory + mMappedOffset, mMappedOffset, mMappedLength);
        }
        else
        {
            // no need to grow or shrink
            mLogicalSize = len;
        }
    }
}
/*
class cFixedMemArchive : public cBidirArchive
{
public:

	int8*	mpMemory;
	int32	mSize;
	int32	mReadHead;
};
*/

//-----------------------------------------------------------------------------
// cFixedMemArchive
//-----------------------------------------------------------------------------
cFixedMemArchive::cFixedMemArchive()
:	mpMemory	(0),
	mSize		(0),
	mReadHead	(0)
{
}

cFixedMemArchive::cFixedMemArchive( int8* pMem, int32 size )
:	mpMemory	(0),
	mSize		(0),
	mReadHead	(0)
{
	Attach( pMem, size );
}

cFixedMemArchive::~cFixedMemArchive()
{
}

void cFixedMemArchive::Attach( int8* pMem, int32 size )
{
	mpMemory	= pMem;
	mSize		= size;
	mReadHead	= 0;
}

void cFixedMemArchive::Seek(int64 offset, SeekFrom from)  // throw(eArchive)
{
    switch (from)
    {
    case cBidirArchive::BEGINNING:
        break;
    case cBidirArchive::CURRENT:
        offset = mReadHead + (int)offset;
        break;
    case cBidirArchive::END:
        offset = mSize + (int)offset;
        break;
    default:
        ThrowAndAssert(eArchiveSeek(TSS_GetString( cCore, core::STR_MEMARCHIVE_FILENAME), TSS_GetString( cCore, core::STR_MEMARCHIVE_ERRSTR)));
    }

    if (offset > mSize)
        ThrowAndAssert(eArchiveSeek(TSS_GetString( cCore, core::STR_MEMARCHIVE_FILENAME), TSS_GetString( cCore, core::STR_MEMARCHIVE_ERRSTR)));

    mReadHead = static_cast<int32>( offset );
}

int64 cFixedMemArchive::CurrentPos() const 
{
	return mReadHead;
}

int64 cFixedMemArchive::Length() const 
{
	return mSize;
}

bool cFixedMemArchive::EndOfFile()
{
	return (mReadHead >= mSize);
}

int cFixedMemArchive::Read(void* pDest, int count)			// throw(eArchive)
{
	ASSERT( pDest );
    if (mReadHead + count > mSize)
	{
		count = static_cast<int>( mSize - mReadHead );
		if (count <= 0)
			return 0;
	}

    if (pDest != 0)
        memcpy(pDest, mpMemory + mReadHead, count);

    mReadHead += count;

    return count;
}

int cFixedMemArchive::Write(const void* pDest, int count)	// throw(eArchive)
{
    if (mReadHead + count > mSize)
    {
		ASSERT( false );
		throw eArchiveWrite();
    }

    memcpy(mpMemory + mReadHead, pDest, count);

    mReadHead += count;

    return count;
}


///////////////////////////////////////////////////////////////////////////////
// class cFileArchive -- Archive for files...
///////////////////////////////////////////////////////////////////////////////

//Ctor -- Initialize member variables to 0 or NULL equivalents.
cFileArchive::cFileArchive() :
	mFileSize(0),
	mReadHead(0),
	isWritable(false)
{}

cFileArchive::~cFileArchive()
{
}

bool cFileArchive::EndOfFile()
{
	return ( mReadHead >= mFileSize );
}

////////////////////////////////////////////////////////////////////////
// Seek -- This is where the actual offset is performed.  The default
// for each archive will be 0.
/////////////////////////////////////////////////////////////////////////
void cFileArchive::Seek( int64 offset, SeekFrom from) // throw(eArchive)
{
	try 
	{
		switch (from)
		{
		case cBidirArchive::BEGINNING:
			break;
		case cBidirArchive::CURRENT:
			offset = mReadHead + offset;
			break;
		case cBidirArchive::END:
			offset = mFileSize + offset;
			break;
		default:
			throw eArchiveSeek( mCurrentFilename, iFSServices::GetInstance()->GetErrString() ) ;
		}

		if ( offset > mFileSize )
			throw eArchiveSeek( mCurrentFilename, iFSServices::GetInstance()->GetErrString() ) ;
		mReadHead = offset;

		mCurrentFile.Seek(mReadHead, cFile::SEEK_BEGIN);
			//This is where the actual read/writehead is set!!
	}//try
	catch( eFile& fileError )
	{
		throw( eArchiveSeek( mCurrentFilename, fileError.GetDescription() ) );
	}
}

int64 cFileArchive::CurrentPos(void) const
{
	return mReadHead;
}

/////////////////////////////////////////////////////////////////////////
// Length -- Returns the size of the current file archive.
/////////////////////////////////////////////////////////////////////////
int64 cFileArchive::Length(void) const
{
	try 
	{
		return mCurrentFile.GetSize();
	}
	catch(eFile& fileError)
	{
		throw( eArchiveSeek( mCurrentFilename, fileError.GetDescription() ) );
	}
}

/////////////////////////////////////////////////////////////////////////
// OpenRead -- Opens the file to be read only.
/////////////////////////////////////////////////////////////////////////
void cFileArchive::OpenRead(const TCHAR* filename, uint32 openFlags)
{
	try 
	{
        // set up open flags
        uint32 flags = cFile::OPEN_READ;
        flags |= ( ( openFlags & FA_OPEN_TRUNCATE ) ? cFile::OPEN_TRUNCATE : 0 );
        flags |= ( ( openFlags & FA_OPEN_TEXT     ) ? cFile::OPEN_TEXT     : 0 );

		mCurrentFilename = filename;
		mCurrentFile.Open( filename, flags );
		isWritable = false;

		mFileSize			= mCurrentFile.GetSize();
		mReadHead			= mCurrentFile.Seek( 0, cFile::SEEK_BEGIN );
	}
	catch(eFile& fileError)
	{
		throw(eArchiveOpen( mCurrentFilename, fileError.GetDescription() ) );
	}
}

/////////////////////////////////////////////////////////////////////////
// OpenReadWrite -- Opens the file to be read or written to
/////////////////////////////////////////////////////////////////////////
void cFileArchive::OpenReadWrite(const TCHAR* filename, uint32 openFlags)
{
	try 
	{        
        // set up open flags
        uint32 flags = cFile::OPEN_WRITE;
        flags |= ( ( openFlags & FA_OPEN_TRUNCATE ) ? cFile::OPEN_TRUNCATE : 0 );
        flags |= ( ( openFlags & FA_OPEN_TEXT     ) ? cFile::OPEN_TEXT     : 0 );

		mCurrentFilename = filename;
		mCurrentFile.Open( filename, flags );
		isWritable = true;

		mFileSize = mCurrentFile.GetSize();
		mReadHead = mCurrentFile.Seek( 0, cFile::SEEK_BEGIN );
	}
	catch(eFile& fileError)
	{
		throw( eArchiveOpen( mCurrentFilename, fileError.GetDescription() ) );
	}
}

/////////////////////////////////////////////////////////////////////////
// GetCurrentFilename -- Returns the name of the file currently associated
//		with the FileArchive.
/////////////////////////////////////////////////////////////////////////
TSTRING cFileArchive::GetCurrentFilename(void) const
{
	return mCurrentFilename;
}

/////////////////////////////////////////////////////////////////////////
// Close -- Closes the file currently referenced by mpCurrStream
/////////////////////////////////////////////////////////////////////////
void cFileArchive::Close()
{
	try 
    {
		mCurrentFile.Close();
		mFileSize		= 0;
		mReadHead		= 0;

		mCurrentFilename = _T("");
	}
	catch(eFile& fileError)
	{
		throw( eArchive( mCurrentFilename, fileError.GetDescription() ) );
	}
}

/////////////////////////////////////////////////////////////////////////
// Read -- Read places bytes in location designated by pDest.  Returns 
// The actual amount read into *pDest.
/////////////////////////////////////////////////////////////////////////
int cFileArchive::Read(void* pDest, int count)
{
	try 
    {
	    if ( mReadHead + count > mFileSize )
		    count = static_cast<int>( mFileSize - mReadHead );

	    if ( pDest != NULL )
	    {
		    int nbRead = 
                static_cast<int>( mCurrentFile.Read( pDest, count ) );

            // 'count' may not be equal to 'nbRead' if the file is open in
            // text mode.
            count = nbRead;
		    if(count < 0) count = 0;
	    }
	    else
        {
            int i;
            int32 dummy;
            for (i = count; ; i -= sizeof(int32))
            {
                if (i < (int)sizeof(int32))
                {
                    if (i > 0)
					    mCurrentFile.Read( &dummy, i );
                    break;
                }
			    mCurrentFile.Read( &dummy, i );
            }
        }

	    mReadHead += count;
	    return count;
	}
	catch( eFile& fileError )
	{
		throw( eArchiveRead( mCurrentFilename, fileError.GetDescription() ) );
	}

}

/////////////////////////////////////////////////////////////////////////
// Write -- Writes to file designated by fh.  If isWritable is not set,
// function returns 0.  Otherwise, the actual # written is returned.
/////////////////////////////////////////////////////////////////////////
int cFileArchive::Write(const void* pDest, int count) // throw(eArchive)
{
    try 
    {
        int64 actual_count = 0;
        ASSERT( mCurrentFile.isWritable );

        actual_count = mCurrentFile.Write( pDest, count );

        if ( actual_count < count )
        {
            //Disk full??
            throw eArchiveWrite( mCurrentFilename, iFSServices::GetInstance()->GetErrString() ) ;
        }

        // increment the read/write head
        mReadHead += actual_count;

        // increase the size, if needed
        if( mReadHead > mFileSize )
        {
            #if 0 // IS_SUNPRO
            // These two lines seem to be all there is between code that crashes and code that works for sunpro
            cDebug d("cFileArchive::Write()");
            d.TraceDebug(_T("file(%s) adjusted mFileSize = %d mReadHead = %d\n"), mCurrentFilename.c_str(), (int)mFileSize, (int)mReadHead);
            #endif
            mFileSize = mReadHead;
        }

        return (int)actual_count;
    }
    catch( eFile& fileError )
    {
        throw( eArchiveWrite( mCurrentFilename, fileError.GetDescription() ) );
    }
}


/////////////////////////////////////////////////////////////////////////
// Truncate
/////////////////////////////////////////////////////////////////////////
void cFileArchive::Truncate() // throw(eArchive) 
{
	ASSERT( mCurrentFile.IsOpen() );
	ASSERT( mCurrentFile.isWritable );

	try 
	{
		mCurrentFile.Truncate ( mReadHead );
	}
	catch( eFile& fileError )
	{
		//TODO: create an error number for truncate...
		throw( eArchiveWrite( mCurrentFilename, fileError.GetDescription() ) );
	}

	mFileSize = mReadHead;
}



/////////////////////////////////////////////////////////////////////////
// OpenReadWrite -- Opens the file to be read or written to
//
// since we'll never open an existing file, the truncateFile flag is unnecessary.
/////////////////////////////////////////////////////////////////////////
void cLockedTemporaryFileArchive::OpenReadWrite( const TCHAR* filename, uint32 openFlags )
{
  TSTRING strTempFile;

  try {

    ASSERT( !mCurrentFile.IsOpen() ); // shouldn't be able to create a new file when we're already open
    if	  ( mCurrentFile.IsOpen() ) 
		throw( eArchive( mCurrentFilename, _T("Internal Error") ) );


    ///////////////////////////////////////////////////////////////////////////////
    // if filename is NULL, create a temp file for the caller
    if( filename == NULL )
      {
        try
          {
            iFSServices::GetInstance()->GetTempDirName( strTempFile );
            strTempFile += _T("twtempXXXXXX");  
            iFSServices::GetInstance()->MakeTempFilename( strTempFile );
          }
        catch( eFSServices& e)
          {
            TSTRING errStr = TSS_GetString( cCore, core::STR_BAD_TEMPDIRECTORY );
            eArchiveOpen e(strTempFile, errStr);
            throw e;
          }
      }
    ///////////////////////////////////////////////////////////////////////////////

    ///////////////////////////////////////////////////////////////////////////////
    // create file

    // set up flags
    uint32 flags = cFile::OPEN_WRITE | cFile::OPEN_LOCKED_TEMP | cFile::OPEN_CREATE | cFile::OPEN_EXCLUSIVE;
    if ( openFlags & FA_OPEN_TRUNCATE ) 
      flags |= cFile::OPEN_TRUNCATE;
    if ( openFlags & FA_OPEN_TEXT ) 
      flags |= cFile::OPEN_TEXT;

    // open file
    mCurrentFilename = filename ? filename : strTempFile.c_str();
    mCurrentFile.Open( mCurrentFilename, flags );
    
    isWritable = true;
    mFileSize = mCurrentFile.GetSize();
    mReadHead = mCurrentFile.Seek( 0, cFile::SEEK_BEGIN );

#if 0 // IS_SUNPRO
    cDebug d("cLockedTemporaryFileArchive::OpenReadWrite()");
    d.TraceDebug(_T("file(%s) set mFileSize to %d mReadHead to %d\n"), mCurrentFilename.c_str(), (int)mFileSize, (int)mReadHead);
#endif

  }//try
  catch (eFile& fileError) {
    TSTRING errStr = TSS_GetString( cCore, core::STR_BAD_TEMPDIRECTORY );
    eArchiveOpen e(strTempFile, errStr);
    throw e;
  }

  ///////////////////////////////////////////////////////////////////////////////
}

/////////////////////////////////////////////////////////////////////////
// Close -- Closes the file currently referenced by fh
void cLockedTemporaryFileArchive::Close()
{
    // Note: this deletes the file as well
    cFileArchive::Close();
}

