#include "config.h"

#include <string.h>

#include "daedef.h"
#include "ssib.h"
#include "pib.h"
#include "fmp.h"
#include "page.h"
#include "node.h"
#include "fucb.h"
#include "stapi.h"
#include "stint.h"
#include "logapi.h"
#include "log.h"
#include "util.h"

DeclAssertFile;					/* Declare file name for assert macros */

LOCAL VOID PMIInsertReorganize( SSIB *pssib, LINE *rgline, INT cline );
LOCAL ERR ErrPMIReplaceReorganize( SSIB *pssib, LINE *rgline, INT cline, INT cbDif );


#ifdef DEBUG
VOID CheckPgno( PAGE *ppage, PN pn ) 
	{
	ULONG ulPgno;

	LFromThreeBytes(ulPgno, (ppage)->pgnoThisPage);
	Assert( ulPgno == PgnoOfPn(pn));
	Assert( ulPgno != 0 );
	}
#endif


#ifdef DEBUG
VOID CheckPage( PAGE *ppage )
	{
	Assert( (ppage)->pghdr.cbFreeTotal >= 0 );
	Assert( (ppage)->pghdr.cbFreeTotal < cbPage );
	Assert( (ppage)->pghdr.ibLastUsed >=
		( (INT)sizeof(PGHDR) + (ppage)->pghdr.ctagMac * (INT)sizeof(TAG) ) );
	Assert( (ppage)->pghdr.ibLastUsed <= cbPage - (INT)sizeof(PGTRLR) );
	Assert( (ppage)->pghdr.ctagMac >= 0 );
	Assert( (ppage)->pghdr.ctagMac <= 256 );
	Assert( (ppage)->pghdr.itagFree == itagNil ||
		(ppage)->pghdr.itagFree < (ppage)->pghdr.ctagMac );
	}
#else
#define CheckPage( ppage )
#endif


//+api---------------------------------------------------------------------
//
//	PMNewPage
//	========================================================================
//
//	PMNewPage( PAGE *ppage, PGNO pgno, PGTYP pgtyp, PGNO pgnoFDP )
//
//	PMNewPage takes a buffer and initializes it for use by other
//	page manager functions.
//
//	PARAMETERS	ppage 		pointer to buffer to be initialized
//				pgno  		pgno of page (ppage->pgnoThisPage)
//
//-------------------------------------------------------------------------

VOID PMNewPage( PAGE *ppage, PGNO pgno, PGTYP pgtyp, PGNO pgnoFDP )
	{
	#ifdef DEBUG
		memset( ppage, '_', sizeof(PAGE) );
	#endif
	memset( ppage, 0, sizeof(PGHDR) );
	ppage->pghdr.ibLastUsed		= cbPage - sizeof(PGTRLR);

	ppage->pghdr.cbFreeTotal	= CbLastFreeSpace(ppage);
	Assert( ppage->pghdr.cbFreeTotal >= 0 && ppage->pghdr.cbFreeTotal < cbPage );
	Assert( ppage->pghdr.ctagMac == 0 );
	ppage->pghdr.itagFree		= itagNil;

	/*	reset "last-flushed" counter
	/**/
	Assert( ppage->pghdr.ulDBTime == 0 );
	/* ThreeBytesFromL( ppage->pghdr.pgnoPrev, 0 ); */
	/* ThreeBytesFromL( ppage->pghdr.pgnoNext, 0 ); */

	SetPgno( ppage, pgno );
	PMSetPageType( ppage, pgtyp );
	PMSetPgnoFDP( ppage, pgnoFDP );
	}



//+api---------------------------------------------------------------------
//
//	ErrPMInsert
//	===========================================================================
//
//	ErrPMInsert( SSIB *pssib, LINE *rgline, INT cline )
//
//	ErrPMInsert concatenates the buffers (pointed to by rgline) and inserts
//	them into the page indicated by pssib.  ErrPMInsert is guaranteed to work
//	if a tag can be allocated, and there is enough free room in page.  If page
//	is too fragmented to insert new lines, it will be reorganized.  NB: this
//	implies that real pointers into a page may be invalid across calls to
//	ErrPMInsert (the same is true for ErrPMUpdate).
//
//	PARAMETERS  pssib->ppage		points to page to insert into
//			   	rgline				LINES (buffers) to be inserted into page
//			   	cline  				number of LINES in rgline
//
//	RETURNS
//		JET_errSuccess;
//		errPMOutOfPageSpace			not enough free space in page
//		errPMTagsUsedUp				if !fNoTagLimit
//			   						no free tags ( !fUseNewTag )
//			   						no new tags ( fUseNewTag )
//									  	
//-------------------------------------------------------------------------

INT ItagPMQueryNextItag( SSIB *pssib )
	{
	PAGE	*ppage = pssib->pbf->ppage;

	CheckSSIB( pssib );

	if ( ppage->pghdr.itagFree == itagNil )
		{
#ifdef DEBUG
		if ( ppage->pghdr.ctagMac == ctagMax )
			return itagNil;
#endif
		Assert( ppage->pghdr.ctagMac < ctagMax );
		return ppage->pghdr.ctagMac;
		}
	else
		{
		return ppage->pghdr.itagFree;
		}
	}


ERR ErrPMInsert( SSIB *pssib, LINE *rgline, INT cline )
	{
	PAGE	*ppage = pssib->pbf->ppage;
	LINE	*pline;
	LINE	*plineMax;
	BYTE	*pb;
	INT		ib;
	INT		cb;
	INT		itag;

	CheckSSIB( pssib );
	CheckPage( ppage );
	AssertBFDirty( pssib->pbf );
	Assert( cline > 0 );
	Assert( !( FBFWriteLatchConflict( pssib->ppib, pssib->pbf ) ) );

	/*	calculate size of line
	/**/
	cb = 0;
	plineMax = rgline + cline;
	for ( pline = rgline; pline < plineMax; pline++ )
		cb += pline->cb;
	Assert( cb != 0 );

	if ( ppage->pghdr.itagFree == itagNil )
		{
		if ( ppage->pghdr.ctagMac == ctagMax )
			return errPMTagsUsedUp;

		if ( cb + (INT) sizeof(TAG) > ppage->pghdr.cbFreeTotal )
			return errPMOutOfPageSpace;

		/*	allocate tag from end of tag array
		/*	if new tag overlaps data then reorganize
		/**/
		pssib->itag = itag = ppage->pghdr.ctagMac;
		if ( (INT) sizeof(PGHDR) + (INT) sizeof(TAG) * ( itag + 1 ) + cb > ppage->pghdr.ibLastUsed )
			{
			PMIInsertReorganize( pssib, rgline, cline );
			goto Succeed;
			}

		++ppage->pghdr.ctagMac;
		ppage->pghdr.cbFreeTotal -= ( cb + sizeof(TAG) );
		Assert( ppage->pghdr.cbFreeTotal >= 0 && ppage->pghdr.cbFreeTotal < cbPage );
		}
	else
		{
		if ( cb > ppage->pghdr.cbFreeTotal )
			return errPMOutOfPageSpace;

		pssib->itag =
		itag = ppage->pghdr.itagFree;
		Assert( itag < ppage->pghdr.ctagMac );
		ppage->pghdr.itagFree = ppage->rgtag[ itag ].ib;

		if ( (INT) CbLastFreeSpace(ppage) < cb )
			{
			PMIInsertReorganize( pssib, rgline, cline );
			goto Succeed;
			}

		ppage->pghdr.cbFreeTotal -= cb;
		Assert( ppage->pghdr.cbFreeTotal >= 0 && ppage->pghdr.cbFreeTotal < cbPage );
		}

	ppage->pghdr.ibLastUsed -= cb;
	ib = ppage->pghdr.ibLastUsed;

	Assert( (UINT) ib < (UINT) sizeof( PAGE ) );
	Assert( (UINT) sizeof(PGHDR) + ppage->pghdr.ctagMac * (SHORT) sizeof(TAG)
		<= (UINT) ib );

	Assert( itag < ppage->pghdr.ctagMac );
	Assert( (SHORT) sizeof(PGHDR) + ppage->pghdr.ctagMac * (SHORT) sizeof(TAG)
		<= ppage->pghdr.ibLastUsed );

	/*	add line
	/**/
	pssib->line.pb = pb = (BYTE *)ppage + ib;
	for ( pline = rgline; pline < plineMax; pline++ )
		{
		Assert( pline->cb >= 0 && pline->cb < cbPage );
		memcpy( pb, pline->pb, pline->cb );
		Assert( (UINT) ib < (UINT) sizeof( PAGE ) );
		pb += pline->cb;
		}

	PtagFromIbCb( &ppage->rgtag[itag], ib, cb);
	Assert( (UINT) ib < (UINT) sizeof( PAGE ) );

	/*	set return values
	/**/
	Assert( pssib->itag == itag );
	pssib->line.cb = cb;
	Assert( pssib->line.pb == (BYTE *)ppage + ib );

	Assert( pssib->itag < ctagMax );
	
Succeed:

#ifdef DEBUG
	{
	/*	if intrinsic son, then node space should exist for pgno in
	/*	son table, and first byte of pgno should be 0.
	/**/
	if ( !pssib->fDisableAssert &&
		pssib->itag != 0 &&
		( ( *pssib->line.pb & 0x08 ) != 0 ) &&
		!( ( ( *pssib->line.pb & 0x04 ) != 0 ) ) &&
		*(pssib->line.pb + 2 + *( pssib->line.pb + 1 )) ==  1 )
		{
		Assert( ( ( *pssib->line.pb & 0x20 ) != 0 ) || pssib->line.cb != 9 );
//		Assert( *(pssib->line.pb + 4 + *( pssib->line.pb + 1 )) != 0 );
		}
	}
#endif

	AssertBTFOP(pssib);
	CheckPage( ppage );
	return JET_errSuccess;
	}



//+api------------------------------------------------------------------------
//
//	ErrPMReplace
//	===========================================================================
//
//	ErrPMReplace( SSIB *pssib, LINE *rgline, INT cline )
//
//	ErrPMReplace will replace the contents of line pssib->itag with the
//	contents of the buffers indicated by rgline.
//
//	SEE ALSO ErrPMInsert
//
//----------------------------------------------------------------------------
ERR ErrPMReplace( SSIB *pssib, LINE *rgline, INT cline )
	{
	ERR		err;
	PAGE	*ppage = pssib->pbf->ppage;
	INT		cbLine;
	INT		cbDif;
	INT		ibReplace;
	INT		cbReplace;
	LINE	*pline;
	LINE	*plineMax = rgline + cline;

	#ifdef DEBUG
		INT	itag = pssib->itag;
		Assert( TsPMTagstatus( ppage, itag ) == tsLine );
	#endif

	#ifdef DEBUG
		{
		BYTE	bT = *rgline[0].pb;
		BOOL	fSon = (bT & 0x08);
		BOOL	fVis = (bT & 0x04);
//		Assert( pssib->itag != 0 || fVis || fSon );
		}
	#endif

	CheckSSIB( pssib );
	CheckPage( ppage );
	AssertBFDirty( pssib->pbf );
	Assert( cline > 0 );
	Assert( pssib->itag < ppage->pghdr.ctagMac );
	Assert( !( FBFWriteLatchConflict( pssib->ppib, pssib->pbf ) ) );

	cbLine = 0;
	for ( pline = rgline; pline < plineMax; pline++ )
		{
		cbLine += pline->cb;
		}

	IbCbFromPtag( ibReplace, cbReplace, &ppage->rgtag[pssib->itag] );

	/*	tag should not be of deleted line
	/**/
	Assert( cbReplace > 0 );

	cbDif = cbLine - cbReplace;

	/*	if new line is same size or smaller then update in place
	/*	dont reclaim space at end of line if new line is smaller
	/**/
	if ( cbDif == 0 )
		{
		BYTE	*pb = pssib->line.pb = (BYTE *)ppage + ibReplace;
		for ( pline = rgline; pline < plineMax; pline++ )
			{
			Assert( pline->cb >= 0 && pline->cb < cbPage );
			memcpy( pb, pline->pb, pline->cb );
			pb += pline->cb;
			}
		pssib->line.cb = cbLine;
		goto Succeed;
		}

	if ( cbDif < 0 )
		{
		BYTE	*pb = pssib->line.pb = (BYTE *)ppage + ibReplace;
		for ( pline = rgline; pline < plineMax; pline++ )
			{
			Assert( pline->cb >= 0 && pline->cb < cbPage );
			memcpy( pb, pline->pb, pline->cb );
			pb += pline->cb;
			}
		pssib->line.cb = cbLine;
		PtagFromIbCb( &ppage->rgtag[pssib->itag], ibReplace, cbLine );
		ppage->pghdr.cbFreeTotal -= cbDif;
		Assert( ppage->pghdr.cbFreeTotal >= 0 && ppage->pghdr.cbFreeTotal < cbPage );
		goto Succeed;
		}

	/*	if line is ibLastUsed then try to copy/overwrite line.
	/*	Note that this can only be done when buffer is not write
	/*	latched, since overwrite will modify data that pointer may
	/*	be cached on.
	/*
	/*	Note that we must check cbFreeTotal since some space may be
	/*	reserved for rollback.
	/**/
	if ( ibReplace == ppage->pghdr.ibLastUsed &&
		(INT) CbLastFreeSpace(ppage) >= cbDif &&
		(INT) ppage->pghdr.cbFreeTotal >= cbDif )
		{
		BYTE	*pb;

		Assert( (SHORT) sizeof(PGHDR) + ppage->pghdr.ctagMac * (SHORT) sizeof(TAG) <= ppage->pghdr.ibLastUsed );
		Assert( cbDif > 0 );
		pssib->line.pb = pb = (BYTE *)ppage + ibReplace - cbDif;
		ppage->pghdr.ibLastUsed -= cbDif;
		ppage->pghdr.cbFreeTotal -= cbDif;
		Assert( ppage->pghdr.cbFreeTotal >= 0 && ppage->pghdr.cbFreeTotal < cbPage );

		for ( pline = rgline; pline < plineMax; pline++ )
			{
			Assert( pline->cb >= 0 && pline->cb < cbPage );
			memcpy( pb, pline->pb, pline->cb );
			pb += pline->cb;
			}

		/*	set return values
		/**/
		pssib->line.cb = cbLine;
		PtagFromIbCb( &ppage->rgtag[pssib->itag], ibReplace - cbDif, cbLine );
		goto Succeed;
		}

	/*	try to move line to ibLastUsed
	/**/
	if ( (INT) CbLastFreeSpace(ppage) >= cbLine &&
		ppage->pghdr.cbFreeTotal >= cbLine )
		{
		INT	ib;
		BYTE	*pb;

		Assert( cbDif > 0 );
		ppage->pghdr.cbFreeTotal -= cbDif;
		Assert( ppage->pghdr.cbFreeTotal >= 0 && ppage->pghdr.cbFreeTotal < cbPage );
		ppage->pghdr.ibLastUsed -= cbLine;
		ib = ppage->pghdr.ibLastUsed;
		Assert( (SHORT) sizeof(PGHDR) + ppage->pghdr.ctagMac * (SHORT) sizeof(TAG) <= ppage->pghdr.ibLastUsed );

		/*	insert line
		/**/
		pssib->line.pb = pb = (BYTE *)ppage + ib;
		for ( pline = rgline; pline < plineMax; pline++ )
			{
			Assert( pline->cb >= 0 && pline->cb < cbPage );
			memcpy( pb, pline->pb, pline->cb );
			pb += pline->cb;
			}

		PtagFromIbCb( &ppage->rgtag[pssib->itag], ib, cbLine );

		/*	set return values
		/**/
		pssib->line.cb = cbLine;
		Assert( pssib->line.pb == (BYTE *)ppage + ib );
		goto Succeed;
		}

	/*	if insufficient space return error
	/**/
	Assert( cbDif > 0 );
	if ( ppage->pghdr.cbFreeTotal < cbDif )
		{
		return errPMOutOfPageSpace;
		}

	/*	replace line while reorganizing page
	/**/
	err = ErrPMIReplaceReorganize( pssib, rgline, cline, cbDif );
	CheckPage( ppage );
	return err;
	
Succeed:

#ifdef DEBUG
	{
	/*	if intrinsic son, then node space should exist for pgno in
	/*	son table, and first byte of pgno should be 0.
	/**/
	if ( !pssib->fDisableAssert &&
		pssib->itag != 0 &&
		( ( *pssib->line.pb & 0x08 ) != 0 ) &&
		!( ( ( *pssib->line.pb & 0x04 ) != 0 ) ) &&
		*(pssib->line.pb + 2 + *( pssib->line.pb + 1 )) ==  1 )
		{
		Assert( pssib->line.cb == 8 || (int)pssib->line.cb >= (int)(1 + *( pssib->line.pb + 1 ) + 1 + 4 ) );
//		Assert( ( 0xff000000 & (*(PGNO *)(pssib->line.pb + 1 + *( pssib->line.pb + 1 ) + 1)) ) == 0 );
		}
	}
#endif

	AssertBTFOP(pssib);
	CheckPage( ppage );
	return JET_errSuccess;
	}



//+api------------------------------------------------------------------------
//
//	PMDelete
//	===========================================================================
//
//	PMDelete( PAGE *ppage, INT itag )
//
//	PMDelete will free up space allocated to a line of data in a PAGE.
//
//----------------------------------------------------------------------------

VOID PMDelete( SSIB *pssib )
	{
	PAGE	*ppage = pssib->pbf->ppage;
	INT		itag = pssib->itag;
	INT		ib;
	INT		cb;

	CheckSSIB( pssib );
	CheckPage( ppage );
	AssertBFDirty( pssib->pbf );
	Assert( itag < ppage->pghdr.ctagMac );
	Assert( TsPMTagstatus( ppage, itag ) == tsLine );
	IbCbFromPtag( ib, cb, &ppage->rgtag[itag] );
	Assert( cb );

	//	FREE DATA
	if ( ib == ppage->pghdr.ibLastUsed )
		{
		ppage->pghdr.ibLastUsed += cb;
		}
	ppage->pghdr.cbFreeTotal += cb;
	Assert( ppage->pghdr.cbFreeTotal >= 0 && ppage->pghdr.cbFreeTotal < cbPage );

	//	FREE TAG
	cb = 0;
	ib = ppage->pghdr.itagFree;
	PtagFromIbCb( &ppage->rgtag[itag], ib, cb );
	ppage->pghdr.itagFree = itag;
	Assert( ppage->pghdr.itagFree < ppage->pghdr.ctagMac );

	CheckPage( ppage );
	return;
	}


//+api------------------------------------------------------------------------
//	ErrPMGet
//	===========================================================================
//
//	ErrPMGet( SSIB *pssib, INT itag )
//
//	ErrPMGet will calculate and return a real pointer to a given line within
//	the page.
//
//	PARAMETERS		pssib->pbf->ppage		ppage to read line from
//						itag						itag of line
//
//----------------------------------------------------------------------------
ERR ErrPMGet( SSIB *pssib, INT itag )
	{
	PAGE	*ppage = pssib->pbf->ppage;
	INT		ib;
	INT		cb;

#ifdef DEBUG
	PGNO pgnoP;
	PGNO pgnoN;
	LFromThreeBytes( pgnoP, pssib->pbf->ppage->pghdr.pgnoPrev );
	Assert(pgnoP != PgnoOfPn(pssib->pbf->pn));
	LFromThreeBytes( pgnoN, pssib->pbf->ppage->pghdr.pgnoNext );
	Assert(pgnoN != PgnoOfPn(pssib->pbf->pn));
	Assert(pgnoN == 0 || pgnoN != pgnoP);
#endif

	CheckSSIB( pssib );
	CheckPage( ppage );
	if ( itag >= ppage->pghdr.ctagMac )
		{
		return errPMItagTooBig;
		}
	IbCbFromPtag( ib, cb, &ppage->rgtag[itag] );
	if ( !cb )
		{
		return errPMRecDeleted;
		}
	//	UNDONE:	fix goto bookmark logic and remove line check
	if ( TsPMTagstatus( ppage, itag ) != tsLine )
		{
		return errPMRecDeleted;
		}
	Assert( TsPMTagstatus( ppage, itag ) == tsLine );
	pssib->line.cb = cb;
	pssib->line.pb = (BYTE *)ppage + ib;
	return JET_errSuccess;
	}


//======Local Routines ====================================================

#ifdef MULTI_PROCESS
SemDefine( semPMReorganize );
#endif

static BYTE	rgbCopy[ cbPage - sizeof(PGTRLR) ];


LOCAL VOID PMIInsertReorganize( SSIB *pssib, LINE *rgline, INT cline )
	{
	UINT 	ibT = sizeof( rgbCopy );
	PAGE 	*ppage = pssib->pbf->ppage;
	TAG		*ptag;
	TAG		*ptagMax;
	INT		ibAdd;
	INT		cbAdd;
	LINE  	*pline;

	/* ulDBTime of the page maybe not effective any more,
	 * lets reset it again
	 */
	BFDirty( pssib->pbf );

	#ifdef MULTI_PROCESS
	SgSemRequest( semPMReorganize );
	#endif

	/*	add line
	/**/
	cbAdd = 0;
	for ( pline = rgline + cline - 1; pline >= rgline; pline-- )
		{
		ibT -= pline->cb;
		Assert( ibT <= sizeof(rgbCopy) );
		Assert( pline->cb >= 0 && pline->cb < cbPage );
		memcpy( rgbCopy + ibT, pline->pb, pline->cb );
		cbAdd += pline->cb;
		}
	ibAdd = ibT;

	/*	copy and compact existing page lines
	/**/
	ptag = ppage->rgtag;
	ptagMax = ptag + ppage->pghdr.ctagMac;
	for ( ; ptag < ptagMax; ptag++ )
		{
		INT ib, cb;

		IbCbFromPtag( ib, cb, ptag );
		if ( ( *(LONG *)ptag & bitLink ) == 0 && cb > 0 )
			{
			ibT -= cb;
			Assert( ibT <= sizeof(rgbCopy) );
			Assert( cb >= 0 && cb < cbPage );
			memcpy( rgbCopy + ibT, (BYTE *)ppage + ib, cb );
			ib = ibT;
			PtagFromIbCb( ptag, ib, cb );
			}
		}

	Assert( ibT <= sizeof(rgbCopy) );
	memcpy( (BYTE *)ppage + ibT, rgbCopy + ibT, sizeof(rgbCopy) - ibT );

	#ifdef MULTI_PROCESS
	SgSemRelease( semPMReorganize );
	#endif

	PtagFromIbCb( &ppage->rgtag[pssib->itag], ibAdd, cbAdd );

	/* set page header
	/**/
	ppage->pghdr.ibLastUsed = ibT;
	if ( pssib->itag == ppage->pghdr.ctagMac )
		{
		ppage->pghdr.ctagMac++;
		ppage->pghdr.cbFreeTotal -= cbAdd + sizeof(TAG);
		Assert( ppage->pghdr.cbFreeTotal >= 0 && ppage->pghdr.cbFreeTotal < cbPage );
		}
	else
		{
		ppage->pghdr.cbFreeTotal -= cbAdd;
		Assert( ppage->pghdr.cbFreeTotal >= 0 && ppage->pghdr.cbFreeTotal < cbPage );
		}

	/*	set return values
	/**/
	pssib->line.cb = cbAdd;
	pssib->line.pb = (BYTE *) ppage + ibAdd;

	return;
	}


LOCAL ERR ErrPMIReplaceReorganize( SSIB *pssib, LINE *rgline, INT cline, INT cbDif )
	{
	UINT  	ibT				= sizeof( rgbCopy );
	PAGE  	*ppage			= pssib->pbf->ppage;
	TAG		*ptagReplace	= &ppage->rgtag[pssib->itag];
	TAG		*ptag;
	TAG		*ptagMax;
	INT		ibReplace;
	INT		cbReplace;
	LINE  	*pline;

	/*	ulDBTime of the page maybe not effective any more,
	/*	lets reset it again
	/**/
	BFDirty( pssib->pbf );

	#ifdef MULTI_PROCESS
	SgSemRequest( semPMReorganize );
	#endif

	/*	insert replace line in reorganize buffer
	/**/
	cbReplace = 0;
	for ( pline = rgline + cline - 1; pline >= rgline; pline-- )
		{
		ibT -= pline->cb;
		Assert( ibT <= sizeof(rgbCopy) );
		Assert( pline->cb >= 0 && pline->cb < cbPage );
		memcpy( rgbCopy + ibT, pline->pb, pline->cb );
		cbReplace += pline->cb;
		}
	ibReplace = ibT;

	/*	copy and compact existing page lines, but not the line being
	/*	replaced since it has already been copied.
	/**/
	ptag = ppage->rgtag;
	ptagMax = ptag + ppage->pghdr.ctagMac;
	for ( ; ptag < ptagMax; ptag++ )
		{
		INT ib, cb;

		if ( ptag == ptagReplace )
			{
			PtagFromIbCb( ptag, ibReplace, cbReplace );
			continue;
			}

		IbCbFromPtag( ib, cb, ptag );
		if ( ( *(LONG *)ptag & bitLink ) == 0 && cb > 0 )
			{
			ibT -= cb;
			Assert( ibT <= sizeof(rgbCopy) );
			memcpy( rgbCopy + ibT, (BYTE *)ppage + ib, cb );
			ib = ibT;
			PtagFromIbCb( ptag, ib, cb );
			}
		}

	Assert( ibT <= sizeof(rgbCopy) );
	memcpy( (BYTE *)ppage + ibT, rgbCopy + ibT, sizeof(rgbCopy) - ibT );

	#ifdef MULTI_PROCESS
	SgSemRelease( semPMReorganize );
	#endif

	/* set page header
	/**/
	ppage->pghdr.ibLastUsed = ibT;
	ppage->pghdr.cbFreeTotal -= cbDif;
	Assert( ppage->pghdr.cbFreeTotal >= 0 && ppage->pghdr.cbFreeTotal < cbPage );

	/*	set return values
	/**/
	pssib->line.cb = cbReplace;
	pssib->line.pb = (BYTE *) ppage + ibReplace;

#ifdef DEBUG
	{
	/*	if intrinsic son, then node space should exist for pgno in
	/*	son table, and first byte of pgno should be 0.
	/**/
	if ( !pssib->fDisableAssert &&
		pssib->itag != 0 &&
		( ( *pssib->line.pb & 0x08 ) != 0 ) &&
		!( ( ( *pssib->line.pb & 0x04 ) != 0 ) ) &&
		*(pssib->line.pb + 2 + *( pssib->line.pb + 1 )) ==  1 )
		{
		Assert( pssib->line.cb == 8 || (int)pssib->line.cb >= (int)(1 + *( pssib->line.pb + 1 ) + 1 + 4 ) );
//		Assert( ( 0xff000000 & (*(PGNO *)(pssib->line.pb + 1 + *( pssib->line.pb + 1 ) + 1)) ) == 0 );
		}
	}
#endif

	return JET_errSuccess;
	}


TS TsPMTagstatus( PAGE *ppage, INT itag )
	{
	TAG	tag;

	Assert( itag < ppage->pghdr.ctagMac );
	tag = ppage->rgtag[itag];
	if ( *(LONG *)&tag & bitLink )
		{
		return tsLink;
		}
	if ( tag.cb == 0 )
		return tsVacant;
	return tsLine;
	}


#ifdef DEBUG
VOID PMSetModified( SSIB *pssib )
	{
	PGTYP	pgtyp;

	CheckSSIB( pssib );
	CheckPage( pssib->pbf->ppage );
	AssertBFDirty( pssib->pbf );
	pgtyp = (PGTYP)PgtypPMSetModified( pssib->pbf->ppage );
	pssib->pbf->ppage->pgtyp = pgtyp;
	CheckPgno( pssib->pbf->ppage, pssib->pbf->pn );
	}


VOID PMResetModified( SSIB *pssib )
	{
	PGTYP	pgtyp;

	CheckSSIB( pssib );
	CheckPage( pssib->pbf->ppage );
	AssertBFDirty( pssib->pbf );
	pgtyp = PgtypPMPageTypeOfPage( pssib->pbf->ppage );
	pssib->pbf->ppage->pgtyp = pgtyp;
	CheckPgno( pssib->pbf->ppage, pssib->pbf->pn );
	}
#endif


VOID PMGetLink( SSIB *pssib, INT itag, LINK *plink )
	{
	CheckSSIB( pssib );
	CheckPage( pssib->pbf->ppage );
	*plink = *(LINK *)&(pssib->pbf->ppage->rgtag[itag]);
	Assert( *(LONG *)plink & bitLink );
	*(LONG *)plink &= ~bitLink;
	}


VOID PMReplaceWithLink( SSIB *pssib, SRID srid )
	{
	PAGE  	*ppage = pssib->pbf->ppage;
	INT		ib;
	INT		cb;

	CheckSSIB( pssib );
	CheckPage( pssib->pbf->ppage );
	AssertBFDirty( pssib->pbf );
	Assert( pssib->itag != 0 );
	Assert( pssib->itag < ppage->pghdr.ctagMac );
	IbCbFromPtag( ib, cb, &ppage->rgtag[pssib->itag] );
	Assert( cb > 0 );

	/*	free data space
	/**/
	if ( ib == ppage->pghdr.ibLastUsed )
		{
		ppage->pghdr.ibLastUsed += cb;
		}
	ppage->pghdr.cbFreeTotal += cb;
	Assert( ppage->pghdr.cbFreeTotal >= 0 && ppage->pghdr.cbFreeTotal < cbPage );

	/*	convert tag to link
	/**/
	Assert( ( *(LONG *)&srid & bitLink ) == 0 );
	*(SRID *)&ppage->rgtag[pssib->itag] = srid | bitLink;
	}


VOID PMReplaceLink( SSIB *pssib, SRID srid )
	{
	PAGE	*ppage = pssib->pbf->ppage;

	CheckSSIB( pssib );
	CheckPage( pssib->pbf->ppage );
	AssertBFDirty( pssib->pbf );
	Assert( pssib->itag != 0 );
	Assert( pssib->itag < ppage->pghdr.ctagMac );
	Assert( ( *(LONG *)&srid & bitLink ) == 0 );
	Assert( ( *(LONG *)&ppage->rgtag[pssib->itag] & bitLink ) != 0 );
	*(SRID *)&ppage->rgtag[pssib->itag] = srid | bitLink;
	}


VOID PMExpungeLink( SSIB *pssib )
	{
	PAGE  	*ppage = pssib->pbf->ppage;
	TAG		*ptag;
	INT		itag = pssib->itag;

	CheckSSIB( pssib );
	CheckPage( pssib->pbf->ppage );
	AssertBFDirty( pssib->pbf );
	Assert( itag != 0 );
	Assert( itag < ppage->pghdr.ctagMac );

	/*	free tag
	/**/
	ptag = &ppage->rgtag[itag];
	ptag->cb = 0;
	ptag->ib = ppage->pghdr.itagFree;
	ppage->pghdr.itagFree = itag;
	Assert( ppage->pghdr.itagFree < ppage->pghdr.ctagMac );
	}

/* checks if current node is the only node in the page [other than the FOP]
/**/
BOOL FPMLastNodeToDelete( SSIB *pssib )
	{
	INT cFreeTags = CPMIFreeTag( pssib->pbf->ppage );
	INT cUsedTags = ctagMax - cFreeTags;

	CheckSSIB( pssib );
	CheckPage( pssib->pbf->ppage );

#ifdef DEBUG
#define itagFOP	0		// same as in dirapi.h
	Assert( cUsedTags >= 2 );
	if ( cUsedTags == 1 )
		{
		AssertPMGet( pssib, itagFOP );
		}
#endif
	return( cUsedTags == 2 );
	}


/* checks if page has only one line -- that holding FOP
/**/
BOOL FPMEmptyPage( SSIB *pssib )
	{
	INT cFreeTags = CPMIFreeTag( pssib->pbf->ppage );
	INT cUsedTags = ctagMax - cFreeTags;

	CheckSSIB( pssib );
//	CheckPage( pssib->pbf->ppage );
	Assert( cUsedTags >= 1 );

#ifdef DEBUG
#define itagFOP	0		// same as in dirapi.h
	if ( cUsedTags == 1 )
		{
		AssertPMGet( pssib, itagFOP );
		}
#endif

	return( cUsedTags == 1 );
	}


/* returns number of free tags in page
/**/
INT CPMIFreeTag( PAGE *ppage )
 	{
 	INT	citag = ctagMax - ppage->pghdr.ctagMac;
 	INT	itag = ppage->pghdr.itagFree;
 	TAG	*ptag;

//	CheckPage( ppage );

	for (; itag != itagNil; )
 		{
 		citag++;
 		ptag = &ppage->rgtag[itag];
 		Assert( ptag->cb == 0 );
 		itag = ptag->ib;
 		}
 	return ( citag );
 	}


BOOL FPMFreeTag( SSIB *pssib, INT citagReq )
	{
	PAGE *ppage = pssib->pbf->ppage;

	CheckSSIB( pssib );
//	CheckPage( pssib->pbf->ppage );
	
	return ( ppage->pghdr.ctagMac + citagReq <= ctagMax ||
		CPMIFreeTag( ppage ) >= citagReq );
	}


/*	returns count of bytes used for links.  Called by split
/*	to determine total count of data and data node tags in
/*	page for split selection.
/**/
INT CbPMLinkSpace( SSIB *pssib )
	{
	INT		itag;
	INT		cLinks = 0;
	PAGE	*ppage = pssib->pbf->ppage;

	CheckSSIB( pssib );
//	CheckPage( pssib->pbf->ppage );

	for ( itag = 0; itag < ppage->pghdr.ctagMac; itag++ )
		{
		if ( *(LONG *)&ppage->rgtag[itag] & bitLink )
			cLinks++;
		}

	return cLinks*sizeof(TAG);
	}


VOID PMDirty( SSIB *pssib )
	{
	PAGE	*ppage = pssib->pbf->ppage;

	CheckSSIB( pssib );
	CheckPage( pssib->pbf->ppage );

	/* if current transaction is oldest then timestamp the BF
	/**/
	if ( FLGOn() && CmpLgpos( &pssib->pbf->lgposRC, &pssib->ppib->lgposStart ) > 0 )
		pssib->pbf->lgposRC = pssib->ppib->lgposStart;
	BFDirty( pssib->pbf );
	}

//====== Debugging Routines ===============================================

#ifdef DEBUG
VOID AssertPMGet( SSIB *pssib, INT itag )
	{
	ERR		err;
	SSIB 	ssib;

	CheckSSIB( pssib );
	CheckPage( pssib->pbf->ppage );

#ifdef DEBUG
		{
		ULONG ulPgno;
		LFromThreeBytes(ulPgno, pssib->pbf->ppage->pgnoThisPage);
		Assert( ulPgno == PgnoOfPn(pssib->pbf->pn));
		Assert( ulPgno != 0 );
		}
#endif

	ssib.pbf = pssib->pbf;
	err = ErrPMGet( &ssib, itag );
	Assert( err == JET_errSuccess );
	Assert( ssib.line.pb == pssib->line.pb );
	Assert( ssib.line.cb == pssib->line.cb );
	}


VOID PageConsistent( PAGE *ppage )
	{
	INT		cbTotal = 0;
	INT		itag, itagTmp;
	TAG		tag, tagTmp;
	INT		ibStart, ibEnd;
	INT		ibLastUsed = sizeof(PAGE) - sizeof(PGTRLR);
	BYTE	*pbFirstFree = (BYTE *)(&ppage->rgtag[ppage->pghdr.ctagMac]);
	BYTE	*pbLine;

#if DEBUGGING
	{
	ULONG ulTmp;

	LFromThreeBytes(ulTmp, ppage->pgnoThisPage);
	printf("Checking if Page Consistent %lu\n", ulTmp);
	}
#endif

	for ( itag = 0; itag < ppage->pghdr.ctagMac; itag++ )
		{
		tag = ppage->rgtag[itag];
		if ( !tag.cb )
			continue;
		if ( *(LONG *)&tag & bitLink )
			continue;
		Assert( tag.ib > sizeof(PGHDR) );
		Assert( tag.ib < sizeof(PAGE) - sizeof(PGTRLR) );
		Assert( tag.cb <= sizeof(PAGE) - sizeof(PGHDR) - sizeof(PGTRLR) );
		cbTotal += tag.cb;
		ibStart = tag.ib;
		ibEnd = tag.ib + tag.cb;

		if ( ibStart < ibLastUsed )
			ibLastUsed = ibStart;

		pbLine = (BYTE*) ppage + tag.ib;
		Assert( FNDNullSon(*pbLine) || CbNDSon(pbLine) != 0);

		Assert( pbFirstFree <= (BYTE *)ppage + ibStart );
		Assert( ibEnd <= cbPage - sizeof(PGTRLR) );

		/* make sure there is no overlap */
		for ( itagTmp = 0; itagTmp < ppage->pghdr.ctagMac; itagTmp++ )
			{
			tagTmp = ppage->rgtag[itagTmp];
			if ( itag != itagTmp && ( ( *(LONG *)&tagTmp & bitLink ) == 0 ) )
				Assert( tagTmp.ib < ibStart || tagTmp.ib >= ibEnd );
			}
		}

	Assert( ibLastUsed >= ppage->pghdr.ibLastUsed );
	Assert( ibLastUsed - ( pbFirstFree - (BYTE *)ppage) <= ppage->pghdr.cbFreeTotal );

	cbTotal += sizeof(PGHDR);
	cbTotal += sizeof(TAG) * ppage->pghdr.ctagMac;
	cbTotal += ppage->pghdr.cbFreeTotal;
	cbTotal += sizeof(PGTRLR);
	Assert( cbTotal == cbPage );
	}

#endif
