/******************************************************************************
	$Id: CFile.cpp,v 1.2 2003/07/22 18:43:39 dasenbro Exp $

	File:		CFile.cpp

	Contains:	Mac OS versions of fstreams that use Async file system calls.
				Created to provide identical, high-performance file I/O calls
				for Mac OS and Rhapsody CDatabase code base.

	Version:	Apple Mail Server - Mac OS X :  $Revision: 1.2 $

	Written by:	Chris Jalbert

	Copyright:	© 1996-2001 by Apple Computer, Inc., all rights reserved.

 	NOT_FOR_OPEN_SOURCE <to be reevaluated at a later time>

	Change History:

		$Log: CFile.cpp,v $
		Revision 1.2  2003/07/22 18:43:39  dasenbro
		Mark messages that have been migrated.
		
		Revision 1.1  2003/04/20 23:29:40  dasenbro
		Initial check-in.
		
		Revision 1.24  2002/07/16 01:16:34  dasenbro
		Added better handling of missing data files.
		
		Revision 1.23  2002/05/09 16:59:04  dasenbro
		Changed all str... calls to CUtils::Str... to be NULL safe.
		
		Revision 1.22  2002/04/18 18:09:16  dasenbro
		Changed bool to Bool for word alignment.
		
		Revision 1.21  2002/04/15 21:48:31  dasenbro
		Only roll logs on size and not every 24 hours.
		
		Revision 1.20  2002/03/21 16:41:46  dasenbro
		Updated file version information.
		
		Revision 1.19  2002/01/23 22:48:47  dasenbro
		Added file locking members.
		
		Revision 1.18  2002/01/14 17:04:09  dasenbro
		Initial S4 updates.
		
		Revision 1.17  2001/08/13 23:58:59  dasenbro
		Fixed a leak when file cannot be found.
		
		Revision 1.16  2001/06/21 20:07:42  dasenbro
		*** empty log message ***
		

	Projector History:

		 <1>	04/07/98	CPJ		Initial breakout.

	To Do:

 *****************************************************************************/


#include "CFile.h"
#include "CUtils.h"

#include <stdio.h>				// for statfs() and structs
#include <stdlib.h>				// for malloc()
#include <sys/mount.h>			// for statfs() and structs
#include <fcntl.h>				// for open() flags
#include <errno.h>				// for errno
#include <sys/stat.h>			// for fstat(), stat() and structs


//--------------------------------------------------------------------------------------------------
//	* CFile ()
//
//--------------------------------------------------------------------------------------------------

CFile::CFile ( void ) throw()
	:	fFilePath( nil ),
		fFileRef( kBadFileRef ),
		fRollLog( false ),
		fReadPos( 0 ),
		fWritePos( 0 ),
		fReadPosOK( false ),
		fWritePosOK( false )
{
} // CFile


//--------------------------------------------------------------------------------------------------
//	* CFile ()
//
//--------------------------------------------------------------------------------------------------

CFile::CFile ( const char *inFilePath, const Bool inCreate, const Bool inRoll ) throw( OSErr )
	:	fFilePath( nil ),
		fFileRef( kBadFileRef ),
		fRollLog( inRoll ),
		fReadPos( 0 ),
		fWritePos( 0 ),
		fReadPosOK( false ),
		fWritePosOK( false )
{
	this->open( inFilePath, inCreate );
} // CFile


//--------------------------------------------------------------------------------------------------
//	* CFile ()
//
//--------------------------------------------------------------------------------------------------

CFile::CFile ( const char *inFilePath, const eFilePermissions inPerm, Boolean inCreate ) throw( OSErr )
	:	fFilePath( nil ),
		fFileRef( kBadFileRef ),
		fRollLog( false ),
		fReadPos( 0 ),
		fWritePos( 0 ),
		fReadPosOK( false ),
		fWritePosOK( false )
{
	this->open( inFilePath, inCreate );

	::chmod( inFilePath, inPerm );
} // CFile


//--------------------------------------------------------------------------------------------------
//	* CFile ()
//
//--------------------------------------------------------------------------------------------------

CFile::CFile ( const char *inFilePath, const uInt32 inUID, const uInt32 inGID, const uInt32 inPerm ) throw( OSErr )
	:	fFilePath( nil ),
		fFileRef( kBadFileRef ),
		fRollLog( false ),
		fReadPos( 0 ),
		fWritePos( 0 ),
		fReadPosOK( false ),
		fWritePosOK( false )
{
	this->open( inFilePath, true );

	::chmod( inFilePath, 0100600 );
	::chown( inFilePath, inUID, inGID );
} // CFile


//--------------------------------------------------------------------------------------------------
//	* ~CFile ()
//
//--------------------------------------------------------------------------------------------------

CFile::~CFile ( void )
{
	this->close();
	if ( fFilePath != nil )
	{
		free( fFilePath );
		fFilePath = nil;
	}
} // ~CFile


//--------------------------------------------------------------------------------------------------
//	* CFile
//
//--------------------------------------------------------------------------------------------------

void CFile::open ( const char *inFilePath, const Boolean inCreate )	throw ( OSErr )
{
	register FILE	   *aFileRef		= kBadFileRef;
	char			   *pTmpFilePath	= nil;
	Bool				bNewPath		= true;
	struct	 stat		aStatStruct;

	if ( inCreate == true )
	{
		if ( ::stat( inFilePath, &aStatStruct) != -1 )
		{
			// file already exists, open it for read/write
			if ( kBadFileRef != (aFileRef = ::fopen( inFilePath, "r+" )) )
			{
				::rewind( aFileRef );
			}
		}
		else
		{
			// file does not exist, create it and open for read/write
			if ( kBadFileRef != (aFileRef = ::fopen( inFilePath, "w+" )) )
			{
				::rewind( aFileRef );
			}
		}
	}
	else
	{
		aFileRef = ::fopen( inFilePath, "r+" );
	}

	if ( fFilePath != nil )
	{
		if ( CUtils::Strcmp( fFilePath, inFilePath ) == 0 )
		{
			bNewPath = false;
		}
	}

	if ( bNewPath == true )
	{
		pTmpFilePath = (char *)::malloc( CUtils::Strlen( inFilePath ) + 1 );
		if ( pTmpFilePath != nil )
		{
			CUtils::Strcpy( pTmpFilePath, inFilePath );
			if ( fFilePath != nil )
			{
				free( fFilePath );
				fFilePath = nil;
			}
			fFilePath = pTmpFilePath;
		}
		else
		{
			throw( (OSErr)kMemFullErr );
		}
	}

	fOpenTime	= ::time( nil );
	fLastChecked= ::time( nil );

	if ( kBadFileRef == aFileRef )
	{
		if ( fFilePath != nil )
		{
			free( fFilePath );
			fFilePath = nil;
		}
		if ( errno == ENOENT )
		{
			throw( (OSErr)kFnfErr );
		}
		throw( (OSErr)kPermErr );
	}

	fFileRef	= aFileRef;
	fReadPos	= 0;
	fWritePos	= 0;
	fReadPosOK	= true;
	fWritePosOK = true;

} // open


//--------------------------------------------------------------------------------------------------
//	* seteof ()
//
//--------------------------------------------------------------------------------------------------

CFile& CFile::seteof ( sInt64 lEOF ) throw ( OSErr )
{
	OSErr		nError;

	if ( fFileRef == kBadFileRef )
	{
		throw( (OSErr)kFnOpnErr );
	}

	nError = ::ftruncate( fileno( fFileRef ), lEOF );
	fReadPosOK	= false;
	fWritePosOK	= false;

	if ( nError )
	{
		// ********* Put a proper error code here!
		throw( (OSErr)kFnOpnErr );
	}

	return( *this );

} // seteof


//--------------------------------------------------------------------------------------------------
//	* close
//
//--------------------------------------------------------------------------------------------------

void CFile::close ( void ) throw ( OSErr )
{
	if ( fFileRef == kBadFileRef )
	{
		return;
	}

	::fflush( fFileRef );
	::fclose( fFileRef );

	fFileRef = kBadFileRef;
//	this->syncdisk();

} // close


//--------------------------------------------------------------------------------------------------
//	* Lock ()
//
//--------------------------------------------------------------------------------------------------

int CFile::Lock ( void ) throw ( OSErr )
{
	int		iResult	= kNoErr;

	if ( fFileRef == kBadFileRef )
	{
		return( -1 );
	}

	iResult = ::flock( fileno( fFileRef ), LOCK_EX );

	return( iResult );

} // Lock


//--------------------------------------------------------------------------------------------------
//	* LockNoBlock ()
//
//--------------------------------------------------------------------------------------------------

int CFile::LockNoBlock ( void ) throw ( OSErr )
{
	int		iResult	= kNoErr;

	if ( fFileRef == kBadFileRef )
	{
		return( -1 );
	}

	iResult = ::flock( fileno( fFileRef ), LOCK_EX | LOCK_NB );

	return( iResult );

} // LockNoBlock


//--------------------------------------------------------------------------------------------------
//	* WaitLock ()
//
//--------------------------------------------------------------------------------------------------

int CFile::WaitLock ( void ) throw ( OSErr )
{
	int				iResult	= kNoErr;
	struct stat		ssFile;

	if ( fFileRef == kBadFileRef )
	{
		return( -1 );
	}

	while ( ((iResult = ::flock( fileno( fFileRef ), LOCK_EX | LOCK_NB )) < 0) && (errno == EINTR) )
		continue;

	return( iResult );

} // WaitLock


//--------------------------------------------------------------------------------------------------
//	* freespace
//
//--------------------------------------------------------------------------------------------------

sInt64 CFile::freespace ( void ) const throw ( OSErr )
{
	struct statfs	ssStats;

	if ( fFileRef == kBadFileRef )
	{
		throw( (OSErr)kFnOpnErr );
	}

	::fstatfs( fileno( fFileRef ), &ssStats );

	return( (sInt64)ssStats.f_bsize * (sInt64)ssStats.f_bavail );

} // freespace


//--------------------------------------------------------------------------------------------------
//	* ReadBlock ()
//
//		block read that returns some useful info like the number of bytes read
//--------------------------------------------------------------------------------------------------

ssize_t CFile::ReadBlock ( void *pData, streamsize nBytes ) throw ( OSErr )
{
	register ssize_t	lRead	= 0;
			 off_t		offset	= 0;

	if ( fFileRef == kBadFileRef )
	{
		throw( (OSErr)kFnOpnErr );
	}

	if ( !fReadPosOK )
	{
		offset = ::lseek( fileno( fFileRef ), fReadPos, SEEK_SET );
		if ( -1 == offset )
		{
			throw( (OSErr)kGfpErr );
		}
	}

	lRead = ::read( fileno( fFileRef ), pData, nBytes );
	if ( -1 == lRead )
	{
		throw( (OSErr)kReadErr );
	}

	// Update the position marker.
	fReadPos   += (sInt64)lRead;
	fReadPosOK	= true;
	fWritePosOK	= false;

	return( lRead );

} // ReadBlock


//--------------------------------------------------------------------------------------------------
//	* Read ()
//
//		block io
//--------------------------------------------------------------------------------------------------

CFile& CFile::Read ( void *pData, streamsize nBytes ) throw ( OSErr )
{
	register ssize_t	lRead;
			 off_t		offset;

	if ( fFileRef == kBadFileRef )
	{
		throw( (OSErr)kFnOpnErr );
	}

	if ( !fReadPosOK )
	{
		offset = ::lseek( fileno( fFileRef ), fReadPos, SEEK_SET );
		if ( -1 == offset )
		{
			throw( (OSErr)kGfpErr );
		}
	}

	lRead = ::read( fileno( fFileRef ), pData, nBytes );
	if ( -1 == lRead )
	{
		throw( (OSErr)kReadErr );
	}

	// Update the position marker.
	fReadPos   += (sInt64)lRead;
	fReadPosOK	= true;
	fWritePosOK	= false;

	return( *this );

} // Read


//--------------------------------------------------------------------------------------------------
//	* write ()
//
//--------------------------------------------------------------------------------------------------

CFile& CFile::write ( const void *pData, streamsize nBytes ) throw ( OSErr )
{
	sInt32				i			= 0;
	register ssize_t	lWrite		= 0;
	register struct tm *tmPtr		= nil;
	char			   *pBuff_1 	= nil;
	char			   *pBuff_2 	= nil;
	uTime_t				seconds		= 0;
	int					error		= kNoErr;
	streamsize			strSize		= 0;
	streamsize			buffSize	= 0;
	Bool				bRollLog	= false;
	char				dateStr	[ 256 ];

	try
	{
		if ( fFileRef == kBadFileRef )
		{
			throw( (OSErr)kFnOpnErr );
		}

		if ( !fWritePosOK )
		{
			if ( -1 == ::lseek( fileno( fFileRef ), fWritePos, SEEK_SET) )
			{
				throw( (OSErr)kGfpErr );
			}
		}
		::fflush( fFileRef );

		if ( -1 == (lWrite = ::fwrite( pData, sizeof( char ), nBytes, fFileRef )) )
		{
			throw( (OSErr)kWritErr );
		}
		::fflush( fFileRef );

		if ( fRollLog == true )
		{
			// Is it time to check the size of the log files
			seconds = (uTime_t)::time( nil );
			if ( seconds > (fLastChecked + 60) )
			{
				// Is the file > 2 meg
				if ( this->FileSize() > 2048000 )
				{
					bRollLog = true;
				}
			}

			if ( bRollLog == true )
			{
				// Create temp buffers
				//	Name of the file plus the new extension plus more
				buffSize = CUtils::Strlen( fFilePath ) + 1024;

				pBuff_1 = (char *)::calloc( buffSize, sizeof( char ) );
				if ( pBuff_1 == nil )
				{
					// We have bigger things to worry about if this fails
					throw( (OSErr)kMemFullErr );
				}

				pBuff_2 = (char *)::calloc( buffSize, sizeof( char ) );
				if ( pBuff_1 == nil )
				{
					// We have bigger things to worry about if this fails
					throw( (OSErr)kMemFullErr );
				}

				// Remove the oldest
				::sprintf( pBuff_1, "%s.%lu", fFilePath, kMaxFiles );

				// It may not exist so ignore the error
				(void)::remove( pBuff_1 );

				// Now we rename the files
				for ( i = (kMaxFiles - 1); i >= 0; i-- )
				{
					// New name
					::sprintf( pBuff_1, "%s.%lu", fFilePath, i + 1 );

					// Old name
					if ( i == 0 )
					{
						::sprintf( pBuff_2, "%s", fFilePath );
					}
					else
					{
						::sprintf( pBuff_2, "%s.%lu", fFilePath, i );
					}

					// Rename it
					// It may not exist so ignore the error except for the current file
					error = rename( pBuff_2, pBuff_1 );
					if ( (error != kNoErr) && (i == 0) )
					{
						// Log the error and bail
						::sprintf( pBuff_1, kRenameErrorStr, error );
						lWrite = ::fwrite( pBuff_1, sizeof( char ), CUtils::Strlen( pBuff_1 ), fFileRef );
						if ( lWrite == -1 )
						{
							free( pBuff_1 );
							free( pBuff_2 );
							throw( (OSErr)kWritErr );
						}
						::fflush( fFileRef );

						free( pBuff_1 );
						free( pBuff_2 );
						throw( (OSErr)kPermErr );
					}

					// Only tag the current log file
					if ( i == 0 )
					{
						// Log the end tag
						tmPtr = ::localtime( (time_t *)&seconds );
						::strftime( dateStr, 255, "%b %e %Y %X", tmPtr );	// Dec 25 1998 12:00:00
		
						::sprintf( pBuff_1, kRollLogMessageEndStr, dateStr );
						strSize = CUtils::Strlen( pBuff_1 );
		
						lWrite = ::fwrite( pBuff_1, sizeof( char ), CUtils::Strlen( pBuff_1 ), fFileRef );
						if ( lWrite == -1 )
						{
							free( pBuff_1 );
							free( pBuff_2 );
							throw( (OSErr)kWritErr );
						}
						::fflush( fFileRef );
					}
				}

				// Close the old file and open a new one
				this->close();
				this->open( fFilePath, true );

				// Tag the head of the new log
				::sprintf( pBuff_1, kRollLogMessageStartStr, dateStr );
				strSize = CUtils::Strlen( pBuff_1 );

				lWrite = ::fwrite( pBuff_1, sizeof( char ), CUtils::Strlen( pBuff_1 ), fFileRef );
				if ( lWrite == -1 )
				{
					free( pBuff_1 );
					free( pBuff_2 );
					throw( (OSErr)kWritErr );
				}
				::fflush( fFileRef );

				// Free up the memory
				free( pBuff_1 );
				free( pBuff_2 );
				pBuff_1 = nil;
				pBuff_2 = nil;
			}
		}

		// Update the position marker.
		fWritePos	+= (sInt64)lWrite;
		fWritePosOK	 = true;
		fReadPosOK	 = false;
	}

	catch ( OSErr err )
	{
		throw( err );
	}

	catch ( ... )
	{
		throw( kiIOAbort );
	}

	return( *this );

} // write


//--------------------------------------------------------------------------------------------------
//	* FileSize
//
//		positioning
//--------------------------------------------------------------------------------------------------

sInt64 CFile::FileSize ( void ) throw ( OSErr )
{
	struct stat		ssFile;

	if ( fFileRef == kBadFileRef )
	{
		throw( (OSErr)kFnOpnErr );
	}

	if ( -1 == ::fstat( fileno( fFileRef ), &ssFile ) )
	{
		throw( (OSErr)kGfpErr );
	}

	return( ssFile.st_size );

} // FileSize


//--------------------------------------------------------------------------------------------------
//	* seekg ()
//
//--------------------------------------------------------------------------------------------------

CFile& CFile::seekg ( sInt64 lOffset, ios::seek_dir inMark ) throw ( OSErr )
{
	register sInt64	lEOF;

	if ( fFileRef == kBadFileRef )
	{
		throw( (OSErr)kFnOpnErr );
	}

	lEOF = FileSize();

	switch ( inMark )
	{
		case ios::beg:
			if ( fReadPos == lOffset )
			{
				return( *this );
			}
			if ( lOffset <= 0 )
			{
				fReadPos = 0;
			}
			else if ( lOffset > lEOF )
			{
				fReadPos = lEOF;
			}
			else
			{
				fReadPos = lOffset;
			}
			break;

		case ios::cur:
			if ( !lOffset )
			{
				return( *this );
			}

			fReadPos += lOffset;
			if ( fReadPos <= 0 )
			{
				fReadPos = 0;
			}
			else if ( fReadPos > lEOF )
			{
				fReadPos = lEOF;
			}
			break;

		case ios::end:
			if ( lOffset > 0 )
			{
				fReadPos = lEOF;
			}
			else	// Got EOF and lOffset <= 0
			{
				fReadPos = lEOF + lOffset;
			}
			break;
	}

	fReadPosOK	= false;
	fWritePosOK	= false;

	return( *this );

} // seekg


//--------------------------------------------------------------------------------------------------
//	* seekp
//
//--------------------------------------------------------------------------------------------------

CFile& CFile::seekp ( sInt64 lOffset, ios::seek_dir inMark ) throw ( OSErr )
{
	register sInt64	lEOF;

	if ( fFileRef == kBadFileRef )
	{
		throw( (OSErr)kFnOpnErr );
	}

	switch ( inMark )
	{
		case ios::beg:
			if ( fWritePos == lOffset )
			{
				return( *this );
			}
			fWritePos = lOffset;
			break;

		case ios::cur:
			if (!lOffset)
			{
				return( *this );
			}
			fWritePos += lOffset;
			break;

		case ios::end:
			lEOF = FileSize();
			fWritePos = lEOF + lOffset;
			break;
	}

	if ( fWritePos < 0 )
	{
		fWritePos = 0;
	}

	fReadPosOK = false;
	fWritePosOK = false;

	return( *this );

} // seekp


//--------------------------------------------------------------------------------------------------
//	* syncdisk
//
//--------------------------------------------------------------------------------------------------

void CFile::syncdisk ( void ) const throw()
{
	::sync();
} // syncdisk


//--------------------------------------------------------------------------------------------------
//	* is_open
//
//--------------------------------------------------------------------------------------------------

int CFile::is_open( void ) const throw()
{
	return ( fFileRef != kBadFileRef );
} // is_open


//--------------------------------------------------------------------------------------------------
//	* flush
//
//--------------------------------------------------------------------------------------------------

