//-------------------------------------------------------------------------
// Desc:	Rollback logging.
// Tabs:	3
//
//		Copyright (c) 1991-2006 Novell, Inc. All Rights Reserved.
//
//		This program is free software; you can redistribute it and/or
//		modify it under the terms of version 2 of the GNU General Public
//		License as published by the Free Software Foundation.
//
//		This program is distributed in the hope that it will be useful,
//		but WITHOUT ANY WARRANTY; without even the implied warranty of
//		MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 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, contact Novell, Inc.
//
//		To contact Novell about this file by physical or electronic mail,
//		you may find current contact information at www.novell.com
//
// $Id: translog.cpp 12315 2006-01-19 15:16:37 -0700 (Thu, 19 Jan 2006) dsanders $
//-------------------------------------------------------------------------

#include "flaimsys.h"

FSTATIC void FLMAPI lgWriteComplete(
	IF_IOBuffer *		pIOBuffer,
	void *				pvData);

/****************************************************************************
Desc:
****************************************************************************/
#ifdef FLM_DBG_LOG
void scaLogWrite(
	FLMUINT		uiFFileId,
	FLMUINT		uiWriteAddress,
	FLMBYTE *	pucBlkBuf,
	FLMUINT		uiBufferLen,
	FLMUINT		uiBlockSize,
	char *		pszEvent)
{
	FLMUINT	uiOffset = 0;
	FLMUINT	uiBlkAddress;

	while (uiOffset < uiBufferLen)
	{
		uiBlkAddress = (FLMUINT)(GET_BH_ADDR( pucBlkBuf));

		// A uiWriteAddress of zero means we are writing exactly at the
		// block address - i.e., it is the data block, not the log block.

		flmDbgLogWrite( uiFFileId, uiBlkAddress,
							(FLMUINT)((uiWriteAddress)
										 ? uiWriteAddress + uiOffset
										 : uiBlkAddress),
			(FLMUINT)(FB2UD( &pucBlkBuf [BH_TRANS_ID])), pszEvent);
		uiOffset += uiBlockSize;
		pucBlkBuf += uiBlockSize;
	}
}
#endif

/****************************************************************************
Desc:	This is the callback routine that is called when a disk write is
		completed.
****************************************************************************/
FSTATIC void FLMAPI lgWriteComplete(
	IF_IOBuffer * 		pIOBuffer,
	void *				pvData)
{
#ifdef FLM_DBG_LOG
	FFILE *		pFile = (FFILE *)pIOBuffer->getCallbackData( 0);
	FLMUINT		uiBlockSize = pFile->FileHdr.uiBlockSize;
	FLMUINT		uiLength = pIOBuffer->getBufferSize();
	char *		pszEvent;
#endif
	DB_STATS *	pDbStats = (DB_STATS *)pvData;

#ifdef FLM_DBG_LOG
	pszEvent = (char *)(RC_OK( pIOBuffer->getCompletionCode())
							  ? (char *)"LGWRT"
							  : (char *)"LGWRT-FAIL");
	scaLogWrite( pFile->uiFFileId, 0, pIOBuffer->getBuffer(), uiLength,
							 uiBlockSize, pszEvent);
#endif

	if (pDbStats)
	{
		// Must lock mutex, because this may be called from async write
		// completion at any time.

		f_mutexLock( gv_FlmSysData.hShareMutex);
		pDbStats->LogBlockWrites.ui64ElapMilli += pIOBuffer->getElapsedTime();
		f_mutexUnlock( gv_FlmSysData.hShareMutex);
	}
}

/****************************************************************************
Desc:	This routine flushes a log buffer to the log file.
****************************************************************************/
RCODE lgFlushLogBuffer(
	DB_STATS *			pDbStats,
	F_SuperFileHdl *	pSFileHdl,
	FFILE *				pFile)
{
	RCODE					rc = FERR_OK;

	if( pDbStats)
	{
		pDbStats->bHaveStats = TRUE;
		pDbStats->LogBlockWrites.ui64Count++;
		pDbStats->LogBlockWrites.ui64TotalBytes += pFile->uiCurrLogWriteOffset;
	}

	pFile->pCurrLogBuffer->setCompletionCallback( lgWriteComplete, pDbStats);
	pFile->pCurrLogBuffer->addCallbackData( (void *)pFile);
	
	pSFileHdl->setMaxAutoExtendSize( pFile->uiMaxFileSize);
	pSFileHdl->setExtendSize( pFile->uiFileExtendSize);

	if( RC_BAD( rc = pSFileHdl->writeBlock( pFile->uiCurrLogBlkAddr,
				pFile->uiCurrLogWriteOffset, pFile->pCurrLogBuffer)))
	{
		if (pDbStats)
		{
			pDbStats->uiWriteErrors++;
		}
		
		goto Exit;
	}
	
Exit:

	pFile->uiCurrLogWriteOffset = 0;
	pFile->pCurrLogBuffer->Release();
	pFile->pCurrLogBuffer = NULL;

	return( rc);
}

/****************************************************************************
Desc:	This routine writes a block to the log file.
****************************************************************************/
RCODE lgOutputBlock(
	DB_STATS	*			pDbStats,
	F_SuperFileHdl *	pSFileHdl,
	FFILE *				pFile,
	SCACHE *				pLogBlock,
	FLMBYTE *			pucBlk,
	FLMUINT *			puiLogEofRV)
{
	RCODE					rc = FERR_OK;
	FLMUINT				uiFilePos = *puiLogEofRV;
	FLMUINT				uiBlkSize = pFile->FileHdr.uiBlockSize;
	FLMBYTE *			pucLogBlk;
	FLMUINT				uiBlkAddress;
	FLMUINT				uiLogBufferSize;

	// Time for a new block file?
	
	if (FSGetFileOffset( uiFilePos) >= pFile->uiMaxFileSize)
	{
		FLMUINT	uiFileNumber;

		// Write out the current buffer, if it has anything in it.

		if (pFile->uiCurrLogWriteOffset)
		{
			if (RC_BAD( rc = lgFlushLogBuffer( pDbStats, pSFileHdl, pFile)))
			{
				goto Exit;
			}
		}

		uiFileNumber = FSGetFileNumber( uiFilePos);

		if (!uiFileNumber)
		{
			uiFileNumber = FIRST_LOG_BLOCK_FILE_NUMBER(
									pFile->FileHdr.uiVersionNum);
		}
		else
		{
			uiFileNumber++;
		}

		if (uiFileNumber > MAX_LOG_BLOCK_FILE_NUMBER(
									pFile->FileHdr.uiVersionNum))
		{
			rc = RC_SET( FERR_DB_FULL);
			goto Exit;
		}

		if (RC_BAD( rc = pSFileHdl->createFile( uiFileNumber )))
		{
			goto Exit;
		}
		uiFilePos = FSBlkAddress( uiFileNumber, 0 );
	}

	// Copy the log block to the log buffer.

	if (!pFile->uiCurrLogWriteOffset)
	{
		pFile->uiCurrLogBlkAddr = uiFilePos;

		// Get a buffer for logging.
		//
		// NOTE: Buffers are not kept by the FFILE's buffer manager,
		// so once we are done with this buffer, it will be freed

		uiLogBufferSize = MAX_LOG_BUFFER_SIZE;

		for( ;;)
		{
			if (RC_BAD( rc = pFile->pBufferMgr->getBuffer( 
				uiLogBufferSize, &pFile->pCurrLogBuffer)))
			{
				// If we failed to get a buffer of the requested size,
				// reduce the buffer size by half and try again

				if( rc == FERR_MEM)
				{
					uiLogBufferSize /= 2;
					if( uiLogBufferSize < uiBlkSize)
					{
						goto Exit;
					}
					
					rc = FERR_OK;
					continue;
				}
				goto Exit;
			}
			break;
		}
	}

	// Copy data from log block to the log buffer

	pucLogBlk = pFile->pCurrLogBuffer->getBufferPtr() +
						pFile->uiCurrLogWriteOffset;
	f_memcpy( pucLogBlk, pLogBlock->pucBlk, uiBlkSize);

	// If we are logging this block for the current update
	// transaction, set the BEFORE IMAGE (BI) flag in the block header
	// so we will know that this block is a before image block that
	// needs to be restored when aborting the current update
	// transaction

	if (pLogBlock->ui16Flags & CA_WRITE_TO_LOG)
	{
		BH_SET_BI( pucLogBlk);
	}

	// If this is an index block, and it is encrypted, we need to encrypt
	// it before we calculate the checksum
	
	if (BH_GET_TYPE( pucLogBlk) != BHT_FREE && pucLogBlk[ BH_ENCRYPTED])
	{
		FLMUINT		uiBufLen = getEncryptSize( pucLogBlk);

		flmAssert( uiBufLen <= uiBlkSize);

		if (RC_BAD( rc = ScaEncryptBlock( pLogBlock->pFile,
													 pucLogBlk,
													 uiBufLen,
													 uiBlkSize)))
		{
			goto Exit;
		}
	}
	
	// Calculate the block checksum

	uiBlkAddress = GET_BH_ADDR( pucLogBlk);
	BlkCheckSum( pucLogBlk, CHECKSUM_SET, uiBlkAddress, uiBlkSize);

	// Set up for next log block write

	pFile->uiCurrLogWriteOffset += uiBlkSize;

	// If this log buffer is full, write it out

	if( pFile->uiCurrLogWriteOffset == 
		pFile->pCurrLogBuffer->getBufferSize())
	{
		if( RC_BAD( rc = lgFlushLogBuffer( pDbStats, pSFileHdl, pFile)))
		{
			goto Exit;
		}
	}

	// Save the previous block address into the modified block's
	// block header area.  Also save the transaction id

	UD2FBA( (FLMUINT32)uiFilePos, &pucBlk [BH_PREV_BLK_ADDR]);
	f_memcpy( &pucBlk [BH_PREV_TRANS_ID], &pLogBlock->pucBlk [BH_TRANS_ID], 4);

	*puiLogEofRV = uiFilePos + uiBlkSize;

Exit:

	return( rc);
}
