/****************************************************************
 *								*
 *	Copyright 2001, 2012 Fidelity Information Services, Inc	*
 *								*
 *	This source code contains the intellectual property	*
 *	of its copyright holder(s), and is made available	*
 *	under a license.  If you do not know the terms of	*
 *	the license, please stop and do not read further.	*
 *								*
 ****************************************************************/

#include "mdef.h"

#include "gtm_string.h"

#include "gdsroot.h"
#include "gdsblk.h"
#include "gtm_facility.h"
#include "fileinfo.h"
#include "gdsbt.h"
#include "gdsfhead.h"
#include "gdsbml.h"
#include "copy.h"
#include "subscript.h"
#include "filestruct.h"
#include "gdscc.h"
#include "gdskill.h"    /* needed for tp.h */
#include "jnl.h"        /* needed for tp.h */
#include "buddy_list.h" /* needed for tp.h */
#include "hashtab_int4.h"	/* needed for tp.h */
#include "tp.h"
#include "error.h"
#include "mmemory.h"
#include "gtm_ffs.h"
#include "cert_blk.h"
#include "gtm_ctype.h"
#ifdef GTM_TRIGGER
#include <rtnhdr.h>
#include "gv_trigger.h"
#endif

GBLREF	uint4		dollar_tlevel;
GBLREF	boolean_t	dse_running;

error_def(ERR_DBBLEVMX);
error_def(ERR_DBBLEVMN);
error_def(ERR_DBBSIZMN);
error_def(ERR_DBBSIZMX);
error_def(ERR_DBRSIZMN);
error_def(ERR_DBRSIZMX);
error_def(ERR_DBCMPNZRO);
error_def(ERR_DBSTARSIZ);
error_def(ERR_DBSTARCMP);
error_def(ERR_DBCMPMX);
error_def(ERR_DBKEYMX);
error_def(ERR_DBKEYMN);
error_def(ERR_DBCMPBAD);
error_def(ERR_DBKEYORD);
error_def(ERR_DBPTRNOTPOS);
error_def(ERR_DBPTRMX);
error_def(ERR_DBPTRMAP);
error_def(ERR_DBLVLINC);
error_def(ERR_DBBMSIZE);
error_def(ERR_DBBMBARE);
error_def(ERR_DBBMINV);
error_def(ERR_DBBMMSTR);
error_def(ERR_DBROOTBURN);
error_def(ERR_DBDIRTSUBSC);
error_def(ERR_DBMAXNRSUBS); /* same error as ERR_MAXNRSUBSCRIPTS, but has a string output as well */
error_def(ERR_DBINVGBL);
error_def(ERR_DBBDBALLOC);

#define BITS_PER_UCHAR	8
#define BLKS_PER_UINT4	((SIZEOF(uint4) / SIZEOF(unsigned char)) * BITS_PER_UCHAR) / BML_BITS_PER_BLK
#define BLOCK_WINDOW 8
#define LEVEL_WINDOW 3
#define OFFSET_WINDOW 4

#define TEXT1 ":     "
#define TEXT2 " "
#define TEXT3 ":"
#define TEXT4 ", "

#define MAX_UTIL_LEN 40
#define	RTS_ERROR_FUNC(err, buff)	\
{					\
	if (gtmassert_on_error)		\
		GTMASSERT;		\
	rts_error_func(err, buff);	\
}

void rts_error_func(int err, uchar_ptr_t buff);

int cert_blk (gd_region *reg, block_id blk, blk_hdr_ptr_t bp, block_id root, boolean_t gtmassert_on_error)
{
	block_id		child, prev_child;
	rec_hdr_ptr_t		rp, r_top;
	int			num_subscripts;
	uint4			bplmap, mask1, offset;
	sm_uint_ptr_t		chunk_p;			/* Value is unaligned so will be assigned to chunk */
	uint4			chunk;
	sm_uc_ptr_t		blk_top, blk_id_ptr, next_tp_child_ptr, key_base, mp, b_ptr;
	unsigned short		rec_cmpc, min_cmpc;	/* the minimum cmpc expected in any record (except star-key) in a gvt */
	int			tmp_cmpc;
	unsigned char		ch, prior_expkey[MAX_KEY_SZ + 1];
	unsigned int		prior_expkeylen;
	unsigned short		temp_ushort;
	int			blk_levl;
	int			blk_size, rec_size, comp_length, rec_offset, key_size;
	unsigned char		util_buff[MAX_UTIL_LEN];
	int			util_len;
	off_chain		chain;
	sgmnt_addrs		*csa;
	sgmnt_data_ptr_t	csd;
	boolean_t		is_gvt, is_directory, first_key, full, prev_char_is_delimiter;

	csa = &FILE_INFO(reg)->s_addrs;
	csd = csa->hdr;
	bplmap = csd->bplmap;
	assert(bplmap == BLKS_PER_LMAP);
	blk_levl = bp->levl;
	blk_size = bp->bsiz;
	offset = (uint4)blk / bplmap;

	util_len=0;
	i2hex_blkfill(blk,&util_buff[util_len], BLOCK_WINDOW);
	util_len += BLOCK_WINDOW;
	MEMCPY_LIT(&util_buff[util_len], TEXT1); /* OFFSET_WINDOW + 1 spaces */
	util_len += SIZEOF(TEXT3) - 1;
	util_len += OFFSET_WINDOW +1;
	i2hex_blkfill(blk_levl, &util_buff[util_len], LEVEL_WINDOW);
	util_len += LEVEL_WINDOW;
	MEMCPY_LIT(&util_buff[util_len], TEXT2);
	util_len += SIZEOF(TEXT2) - 1;
	util_buff[util_len] = 0;

	chain = *(off_chain *)&blk;
	/* Assert that if at all chain.flag is non-zero (i.e. a created block), we are in TP and not yet in the commit logic.
	 * The only exception to this rule is if we are in TP and inside phase1 of the commit logic and trying to certify a
	 * block that was killed inside of the transaction (possible if cert_blk is called directly from tp_tend). In this case,
	 * the block number passed is a special value GDS_CREATE_BLK_MAX so check that.
	 */
	assert(!chain.flag || dollar_tlevel && (!csa->t_commit_crit
						|| ((T_COMMIT_CRIT_PHASE1 == csa->t_commit_crit) && (GDS_CREATE_BLK_MAX == blk))));
	if (!chain.flag && ((offset * bplmap) == (uint4)blk))					/* it's a bitmap */
	{
		if ((unsigned char)blk_levl != LCL_MAP_LEVL)
		{
			RTS_ERROR_FUNC(MAKE_MSG_INFO(ERR_DBLVLINC), util_buff);
			return FALSE;
		}
		if (blk_size != BM_SIZE(bplmap))
		{
			RTS_ERROR_FUNC(ERR_DBBMSIZE, util_buff);
			return FALSE;
		}
		mp = (sm_uc_ptr_t)bp + SIZEOF(blk_hdr);
		if ((*mp & 1) != 0)
		{	/* bitmap doesn't protect itself */
			RTS_ERROR_FUNC(ERR_DBBMBARE, util_buff);
			return FALSE;
		}
		full = TRUE;
		offset = ((csa->ti->total_blks - blk) >= bplmap) ? bplmap : (csa->ti->total_blks - blk);
		blk_top = (sm_uc_ptr_t)bp + BM_SIZE(offset + (BITS_PER_UCHAR / BML_BITS_PER_BLK) - 1);
		for (chunk_p = (sm_uint_ptr_t)mp ;  (sm_uc_ptr_t)chunk_p - blk_top < 0 ;  chunk_p++)
		{
			GET_LONG(chunk, chunk_p);		/* Obtain unalinged unit4 value */
			/* The following code is NOT independent of the bitmap layout: */
			mask1 = chunk & SIXTEEN_BLKS_FREE;	/* mask 'recycled' blocks to 'free' blocks */
			if ((mask1 != 0) && full)		/* check for free blocks */
			{	/* if (full bitmap || full chunk || regular scan of a "short" bitmap) */
				if ((offset == bplmap) || ((blk_top - (sm_uc_ptr_t)chunk_p) > SIZEOF(chunk))
					|| (NO_FREE_SPACE != bml_find_free((int4)((sm_uc_ptr_t)chunk_p - mp), mp, offset)))
				{
					full = FALSE;
				}
			}
			mask1 ^= SIXTEEN_BLKS_FREE;		/* complement to busy */
			mask1 <<= 1;				/* shift to reused position */
			mask1 &= chunk;				/* check against the original contents */
			if (mask1 != 0)				/* busy and reused should never appear together */
			{
				RTS_ERROR_FUNC(ERR_DBBMINV, util_buff);
				return FALSE;
			}

		}
		if (full == (NO_FREE_SPACE != gtm_ffs(blk / bplmap, MM_ADDR(csd), MASTER_MAP_BITS_PER_LMAP)))
		{
			RTS_ERROR_FUNC(ERR_DBBMMSTR, util_buff);
			/* DSE CACHE -VERIFY used to fail occasionally with the DBBMMSTR error because of passing
			 * an older twin global buffer that contained stale bitmap information. That is now fixed.
			 * So we dont expect any more such failures. Assert accordingly.
			 */
			assert(!dse_running);
			return FALSE;
		}
		return TRUE;
	}
	if (blk_levl > MAX_BT_DEPTH)
	{
		RTS_ERROR_FUNC(ERR_DBBLEVMX, util_buff);
		return FALSE;
	}
	if (blk_levl < 0)
	{
		RTS_ERROR_FUNC(ERR_DBBLEVMN, util_buff);
		return FALSE;
	}
	if (blk_levl == 0)
	{	/* data block */
		if ((DIR_ROOT == blk) || (blk == root))
		{	/* headed for where an index block should be */
			RTS_ERROR_FUNC(ERR_DBROOTBURN, util_buff);
			return FALSE;
		}
		if (blk_size < SIZEOF(blk_hdr))
		{
			RTS_ERROR_FUNC(ERR_DBBSIZMN, util_buff);
			return FALSE;
		}
	} else
	{	/* index block */
		if (blk_size < (SIZEOF(blk_hdr) + SIZEOF(rec_hdr) + SIZEOF(block_id)))
		{	/* must have at least one record */
			RTS_ERROR_FUNC(ERR_DBBSIZMN, util_buff);
			return FALSE;
		}
	}
	if (blk_size > csd->blk_size)
	{
		RTS_ERROR_FUNC(ERR_DBBSIZMX, util_buff);
		return FALSE;
	}
	if (0 == root)
	{
		is_directory = FALSE;
		is_gvt = FALSE;
		/* if both "is_directory" and "is_gvt" are FALSE, then we dont know YET if the given block is a directory or gvt */
	} else if (DIR_ROOT == root)
	{
		is_directory = TRUE;
		is_gvt = FALSE;
	} else
	{
		is_directory = FALSE;
		is_gvt = TRUE;
	}
	blk_top = (sm_uc_ptr_t)bp + blk_size;
	first_key = TRUE;
	min_cmpc = 0;
	prior_expkeylen = 0;
	comp_length = 2 * SIZEOF(char);		/* for double NUL to indicate no prior key */
	prior_expkey[0] = prior_expkey[1] = 0;	/* double NUL also works for memvcmp test for key order */
	next_tp_child_ptr = NULL;
	prev_child = 0;

	for (rp = (rec_hdr_ptr_t)((sm_uc_ptr_t)bp + SIZEOF(blk_hdr)) ;  rp < (rec_hdr_ptr_t)blk_top ;  rp = r_top)
	{
		GET_RSIZ(rec_size, rp);
		rec_offset = (int)((sm_ulong_t)rp - (sm_ulong_t)bp);
		/*add util_buff here*/

		util_len=0;
		i2hex_blkfill(blk,&util_buff[util_len], BLOCK_WINDOW);
		util_len += BLOCK_WINDOW;
		MEMCPY_LIT(&util_buff[util_len], TEXT1); /* OFFSET_WINDOW + 1 spaces */
		util_len += SIZEOF(TEXT3) - 1;
		i2hex_nofill(rec_offset, &util_buff[util_len], OFFSET_WINDOW);
		util_len += OFFSET_WINDOW + 1;
		i2hex_blkfill(blk_levl, &util_buff[util_len], LEVEL_WINDOW);
		util_len += LEVEL_WINDOW;
		MEMCPY_LIT(&util_buff[util_len], TEXT2);
		util_len += SIZEOF(TEXT2) - 1;
		util_buff[util_len] = 0;

		if (rec_size <= SIZEOF(rec_hdr))
		{
			RTS_ERROR_FUNC(ERR_DBRSIZMN, util_buff);
			return FALSE;
		}
		if (rec_size > (unsigned short)((sm_ulong_t)blk_top - (sm_ulong_t)rp))
		{
			RTS_ERROR_FUNC(ERR_DBRSIZMX, util_buff);
			return FALSE;
		}
		r_top = (rec_hdr_ptr_t)((sm_ulong_t)rp + rec_size);
		rec_cmpc = EVAL_CMPC(rp);
		if (first_key)
		{
			if (rec_cmpc)
			{
				RTS_ERROR_FUNC(ERR_DBCMPNZRO, util_buff);
				return FALSE;
			}
			if (0 == blk_levl)
			{
				ch = *((sm_uc_ptr_t)rp + SIZEOF(rec_hdr));
				if (!(VALFIRSTCHAR_WITH_TRIG(ch)))
					GTMASSERT;
			}
		}
		if (r_top == (rec_hdr_ptr_t)blk_top && blk_levl)
		{	/* star key */
			if (rec_size != SIZEOF(rec_hdr) + SIZEOF(block_id))
			{
				RTS_ERROR_FUNC(ERR_DBSTARSIZ, util_buff);
				return FALSE;
			}
			if (rec_cmpc)
			{
				RTS_ERROR_FUNC(ERR_DBSTARCMP, util_buff);
				return FALSE;
			}
			blk_id_ptr = (sm_uc_ptr_t)rp + SIZEOF(rec_hdr);
		} else
		{	/* non-star key */
			key_base = (sm_uc_ptr_t)rp + SIZEOF(rec_hdr);
			/* num_subscripts = number of full subscripts found in the key (including the compressed part) */
			num_subscripts = 0;
			prev_char_is_delimiter = FALSE;
			if (rec_cmpc)
			{
				if (rec_cmpc >= prior_expkeylen)
				{
					RTS_ERROR_FUNC(ERR_DBCMPMX, util_buff);
					return FALSE;
				}
				for (b_ptr = prior_expkey; b_ptr < (prior_expkey + rec_cmpc); b_ptr++)
				{
					if (KEY_DELIMITER == *b_ptr)
						num_subscripts++;
				}
				prev_char_is_delimiter = (KEY_DELIMITER == prior_expkey[rec_cmpc - 1]);
			}
			assert(key_base < (sm_uc_ptr_t)r_top);	/* otherwise we would have signalled ERR_DBRSIZMN error */
			for (blk_id_ptr = key_base ;  ; )
			{
				if (KEY_DELIMITER == *blk_id_ptr++)
				{
					if (!min_cmpc)
					{	/* note down the length of the global-variable (without subscripts) from the
						 * first key in the block. every other record in this block (except the star-key
						 * in case of an index block) should have a rec_cmpc that is atleast this much
						 * (of course this is TRUE only if we are in a GVT).
						 */
						min_cmpc = blk_id_ptr - key_base;
					}
					if (prev_char_is_delimiter)
						break;	/* found key terminator */
					prev_char_is_delimiter = TRUE;
					num_subscripts++;
				} else
					prev_char_is_delimiter = FALSE;
				if (blk_id_ptr >= (sm_uc_ptr_t)r_top)
				{
					RTS_ERROR_FUNC(ERR_DBKEYMX, util_buff);
					return FALSE;
				}
			}
			num_subscripts--;	/* the global variable name was counted above as a subscript. adjust that */
			key_size = (int4)(blk_id_ptr - key_base);
			if (!first_key && (rec_cmpc < min_cmpc))
			{	/* name-level change between consecutive records in the block, this should be a directory block */
				if (is_gvt)
				{	/* this is a contradiction. a block cannot be a directory and gvt at the same time.
					 * gvt should contain all keys with the same global name */
					RTS_ERROR_FUNC(ERR_DBINVGBL, util_buff);
					return FALSE;
				}
				is_directory = TRUE;	/* no need to do this if it was already TRUE but we save an if check */
			}
			if (num_subscripts)
			{	/* key has subscripts, should therefore be a GVT block */
				if (is_directory)
				{	/* this is a contradiction. a block cannot be a directory and gvt at the same time.
					 * the directory tree should contain only name-level (i.e. unsubscripted) globals */
					RTS_ERROR_FUNC(ERR_DBDIRTSUBSC, util_buff);
					return FALSE;
				}
				is_gvt = TRUE;	/* no need to do this if it was already TRUE but we save an if check */
			}
			if (MAX_GVSUBSCRIPTS <= num_subscripts)
			{
				RTS_ERROR_FUNC(ERR_DBMAXNRSUBS, util_buff);
				return FALSE;
			}
			if (blk_levl && (key_size != (rec_size - SIZEOF(block_id) - SIZEOF(rec_hdr))))
			{
				RTS_ERROR_FUNC(ERR_DBKEYMN, util_buff);
				return FALSE;
			}
			assert(first_key || (rec_cmpc < prior_expkeylen));
			if (!first_key)
			{
				if (prior_expkey[rec_cmpc] == key_base[0])
				{
					RTS_ERROR_FUNC(ERR_DBCMPBAD, util_buff);
					return FALSE;
				}
				if (((unsigned int)prior_expkey[rec_cmpc] >= (unsigned int)key_base[0]))
				{
					RTS_ERROR_FUNC(ERR_DBKEYORD, util_buff);
					return FALSE;
				}
			}
			memcpy(prior_expkey + rec_cmpc, key_base, key_size);
			prior_expkeylen = rec_cmpc + key_size;
		}
		/* Check for proper child block numbers */
		if ((0 != blk_levl) || (0 != is_directory))
		{
			GET_LONG(child, blk_id_ptr);
			chain = *(off_chain *)&child;
			/* In TP, child block number can be greater than the total_blks for blocks created within TP.
			 * Dont do any checks on such blocks.
			 */
			if (!dollar_tlevel || !chain.flag)
			{
				if (child <= 0)
				{
					RTS_ERROR_FUNC(ERR_DBPTRNOTPOS, util_buff);
					return FALSE;
				}
				if (child > csa->ti->total_blks)
				{
					RTS_ERROR_FUNC(ERR_DBPTRMX, util_buff);
					return FALSE;
				}
				if (!(child % bplmap))
				{
					RTS_ERROR_FUNC(ERR_DBPTRMAP, util_buff);
					return FALSE;
				}
				if (child == prev_child)
				{
					RTS_ERROR_FUNC(ERR_DBBDBALLOC, util_buff);
					return FALSE;
				}
				prev_child = child;
			} else
			{
				if ((blk_id_ptr != next_tp_child_ptr) && (NULL != next_tp_child_ptr))
				{
					RTS_ERROR_FUNC(ERR_DBPTRNOTPOS, util_buff);
					return FALSE;
				}
				next_tp_child_ptr = blk_id_ptr + chain.next_off;
			}
		}
		first_key = FALSE;
		comp_length = prior_expkeylen;
	}
	assert(!is_directory || !is_gvt);	/* the block cannot be a directory AND gvt at the same time */
	return TRUE;

}

void rts_error_func(int err, uchar_ptr_t buff)
{
	rts_error(VARLSTCNT(4) MAKE_MSG_INFO(err), 2, LEN_AND_STR((char_ptr_t)buff));
}
