/****************************************************************
 *								*
 *	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 "cdb_sc.h"
#include "copy.h"
#include "spec_type.h"
#include "stringpool.h"
#include "filestruct.h"		/* needed for jnl.h */
#include "gdscc.h"		/* needed for tp.h */
#include "jnl.h"		/* needed for tp.h */
#include "gdskill.h"		/* needed for tp.h */
#include "buddy_list.h"		/* needed for tp.h */
#include "hashtab_int4.h"	/* needed for tp.h */
#include "tp.h"			/* needed for T_BEGIN_READ_NONTP_OR_TP macro */

#include "t_end.h"		/* prototypes */
#include "t_retry.h"
#include "t_begin.h"
#include "gvcst_protos.h"	/* for gvcst_search,gvcst_root_search prototype */
#include "get_spec.h"
#include "collseq.h"
#ifdef UNIX
#include "error.h"
#endif

GBLREF	gv_key		*gv_currkey, *gv_altkey;
GBLREF	int4		gv_keysize;
GBLREF	gv_namehead	*gv_target;
GBLREF	sgmnt_addrs	*cs_addrs;
GBLREF	gd_region	*gv_cur_region;
GBLREF	uint4		dollar_tlevel;
GBLREF	uint4		dollar_trestart;
GBLREF	unsigned int	t_tries;
GBLREF	gv_namehead	*reset_gv_target;
GBLREF	boolean_t	mu_reorg_process;
GBLREF	boolean_t	mupip_jnl_recover;
#ifdef UNIX
# ifdef DEBUG
GBLREF	boolean_t	is_rcvr_server;
GBLREF	boolean_t	is_src_server;
GBLDEF	unsigned char	t_fail_hist_dbg[T_FAIL_HIST_DBG_SIZE];
GBLDEF	unsigned int	t_tries_dbg;
# endif
GBLREF	jnl_gbls_t	jgbl;
GBLREF	unsigned char	t_fail_hist[CDB_MAX_TRIES];
GBLREF	trans_num	start_tn;
GBLREF	uint4		update_trans;
GBLREF	inctn_opcode_t	inctn_opcode;
GBLREF	uint4		t_err;
#endif
#ifdef GTM_TRIGGER
GBLREF	boolean_t		skip_INVOKE_RESTART;
#endif

error_def(ERR_GVGETFAIL);

static	mstr	global_collation_mstr;

#ifdef GTM_TRIGGER
# define TRIG_TP_SET_SGM					\
{								\
	if (dollar_tlevel)					\
	{							\
		assert(skip_INVOKE_RESTART);			\
		tp_set_sgm();					\
	}							\
}
#else
# define TRIG_TP_SET_SGM
#endif

#define T_RETRY_AND_CLEANUP(STATUS, DONOT_RESTART)						\
{												\
	gv_target->clue.end = 0;								\
	RESET_GV_TARGET_LCL_AND_CLR_GBL(save_targ, DO_GVT_GVKEY_CHECK);				\
	if (DONOT_RESTART)									\
		return status; /* caller will handle the restart */				\
	t_retry(STATUS);									\
	save_targ->root = 0;	/* May have been found by gvcst_redo_root_search.		\
				 * Reset and allow gvcst_root_search to find it itself.		\
				 */								\
	TRIG_TP_SET_SGM;									\
}

#ifdef UNIX
#define SAVE_ROOTSRCH_ENTRY_STATE								\
{												\
	int				idx;							\
	redo_root_search_context	*rootsrch_ctxt_ptr;					\
												\
	rootsrch_ctxt_ptr = &(TREF(redo_rootsrch_ctxt));					\
	rootsrch_ctxt_ptr->t_tries = t_tries;							\
	for (idx = 0; CDB_MAX_TRIES > idx; idx++)						\
		rootsrch_ctxt_ptr->t_fail_hist[idx] = t_fail_hist[idx];				\
	rootsrch_ctxt_ptr->prev_t_tries = TREF(prev_t_tries);					\
	DEBUG_ONLY(										\
		rootsrch_ctxt_ptr->t_tries_dbg = t_tries_dbg;					\
		for (idx = 0; T_FAIL_HIST_DBG_SIZE > idx; idx++)				\
			rootsrch_ctxt_ptr->t_fail_hist_dbg[idx] = t_fail_hist_dbg[idx];		\
	)											\
	rootsrch_ctxt_ptr->start_tn = start_tn;							\
	rootsrch_ctxt_ptr->update_trans = update_trans;						\
	rootsrch_ctxt_ptr->inctn_opcode = inctn_opcode;						\
	inctn_opcode = 0;									\
	rootsrch_ctxt_ptr->t_err = t_err;							\
	rootsrch_ctxt_ptr->hold_onto_crit = cs_addrs->hold_onto_crit;				\
	if (CDB_STAGNATE <= t_tries)								\
	{											\
		assert(cs_addrs->now_crit);							\
		cs_addrs->hold_onto_crit = TRUE;						\
	}											\
	if (mu_reorg_process)									\
	{	/* In case gv_currkey/gv_target are out of sync. */				\
		rootsrch_ctxt_ptr->gv_currkey = (gv_key *)&rootsrch_ctxt_ptr->currkey[0];	\
		MEMCPY_KEY(rootsrch_ctxt_ptr->gv_currkey, gv_currkey);				\
		SET_GV_CURRKEY_FROM_REORG_GV_TARGET;						\
	}											\
}

#define RESTORE_ROOTSRCH_ENTRY_STATE								\
{												\
	int				idx;							\
	redo_root_search_context	*rootsrch_ctxt_ptr;					\
												\
	rootsrch_ctxt_ptr = &(TREF(redo_rootsrch_ctxt));					\
	t_tries = rootsrch_ctxt_ptr->t_tries;							\
	for (idx = 0; CDB_MAX_TRIES > idx; idx++)						\
		t_fail_hist[idx] = rootsrch_ctxt_ptr->t_fail_hist[idx];				\
	TREF(prev_t_tries) = rootsrch_ctxt_ptr->prev_t_tries;					\
	DEBUG_ONLY(										\
		t_tries_dbg = rootsrch_ctxt_ptr->t_tries_dbg;					\
		for (idx = 0; T_FAIL_HIST_DBG_SIZE > idx; idx++)				\
			t_fail_hist_dbg[idx] = rootsrch_ctxt_ptr->t_fail_hist_dbg[idx];		\
	)											\
	start_tn = rootsrch_ctxt_ptr->start_tn;							\
	update_trans = rootsrch_ctxt_ptr->update_trans;						\
	inctn_opcode = rootsrch_ctxt_ptr->inctn_opcode;						\
	t_err = rootsrch_ctxt_ptr->t_err;							\
	cs_addrs->hold_onto_crit = rootsrch_ctxt_ptr->hold_onto_crit;				\
	TREF(in_gvcst_redo_root_search) = FALSE;						\
	if (mu_reorg_process)									\
		/* Restore gv_currkey */							\
		MEMCPY_KEY(gv_currkey, rootsrch_ctxt_ptr->gv_currkey);				\
}

CONDITION_HANDLER(gvcst_redo_root_search_ch)
{
	START_CH;

	RESTORE_ROOTSRCH_ENTRY_STATE;

	NEXTCH;
}

void gvcst_redo_root_search()
{
	DEBUG_ONLY(boolean_t	dbg_now_crit;)
	uint4			lcl_onln_rlbkd_cycle;
	DCL_THREADGBL_ACCESS;

	SETUP_THREADGBL_ACCESS;
	ESTABLISH(gvcst_redo_root_search_ch);
	assert(!TREF(in_gvcst_redo_root_search)); /* Should never recurse. However, can be called from non-redo gvcst_root_search,
						   * e.g. from op_gvname. In that case, the results of gvcst_redo_root_search are
						   * discarded and the outer root search correctly sets gv_target->root.
						   */
	TREF(in_gvcst_redo_root_search) = TRUE;
	assert(0 < t_tries);
	assert(!is_src_server && !is_rcvr_server);
	assert(!jgbl.onlnrlbk);
	assert((NULL != gv_target) && !gv_target->root);
	assert(cs_addrs == gv_target->gd_csa);
	assert(!dollar_tlevel);
	DEBUG_ONLY(dbg_now_crit = cs_addrs->now_crit);
	/* save global variables now that we are going to do the root search in the middle of the current transaction */
	SAVE_ROOTSRCH_ENTRY_STATE;
	lcl_onln_rlbkd_cycle = cs_addrs->db_onln_rlbkd_cycle;
	GVCST_ROOT_SEARCH;
	if (lcl_onln_rlbkd_cycle != cs_addrs->nl->db_onln_rlbkd_cycle)
		TREF(rlbk_during_redo_root) = TRUE;
	assert(cs_addrs->now_crit == dbg_now_crit); /* ensure crit state remains same AFTER gvcst_root_search */
	/* restore global variables now that we are continuing with the original transaction */
	RESTORE_ROOTSRCH_ENTRY_STATE;
	REVERT;
}
#endif

enum cdb_sc gvcst_root_search(boolean_t donot_restart)
{
	srch_blk_status	*h0;
	sm_uc_ptr_t	rp;
	unsigned short	rlen, hdr_len;
	uchar_ptr_t	subrec_ptr;
	enum cdb_sc	status;
	boolean_t	gbl_target_was_set;
	gv_namehead	*save_targ;
	mname_entry	*gvent;
	int		altkeylen;
	int		tmp_cmpc;
	block_id	lcl_root;
	DCL_THREADGBL_ACCESS;

	SETUP_THREADGBL_ACCESS;
	assert((dba_bg == gv_cur_region->dyn.addr->acc_meth) || (dba_mm == gv_cur_region->dyn.addr->acc_meth));
	SET_GV_ALTKEY_TO_GBLNAME_FROM_GV_CURRKEY;	/* set up gv_altkey to be just the gblname */
	save_targ = gv_target;
	/* Check if "gv_target->gvname" matches "gv_altkey->base". If not, there is a name mismatch (out-of-design situation).
	 * This check is temporary until we catch the situation that caused D9H02-002641.
	 * It's suspected the original situation has been fixed (see D9I08-002695). But the assertpro will remain until
	 * gvcst_redo_root_search has been well-tested.
	 */
	/* --- Check BEGIN --- */
	gvent = &save_targ->gvname;
	altkeylen = gv_altkey->end - 1;
	assertpro(altkeylen && (altkeylen == gvent->var_name.len)
			&& (0 == memcmp(gv_altkey->base, gvent->var_name.addr, gvent->var_name.len)));
	/* --- Check END   --- */
	if (INVALID_GV_TARGET != reset_gv_target)
		gbl_target_was_set = TRUE;
	else
	{
		gbl_target_was_set = FALSE;
		reset_gv_target = save_targ;
	}
	gv_target = cs_addrs->dir_tree;
	lcl_root = 0;
	T_BEGIN_READ_NONTP_OR_TP(ERR_GVGETFAIL);
	/* We better hold crit in the final retry (TP & non-TP). Only exception is journal recovery */
	assert((t_tries < CDB_STAGNATE) || cs_addrs->now_crit || mupip_jnl_recover);
	for (;;)
	{
		hdr_len = rlen = 0;
		gv_target = cs_addrs->dir_tree;
		if (dollar_trestart)
			gv_target->clue.end = 0;
		assert(0 == save_targ->root);
		if (cdb_sc_normal == (status = gvcst_search(gv_altkey, 0)))
		{
			if (gv_altkey->end + 1 == gv_target->hist.h[0].curr_rec.match)
			{
				h0 = gv_target->hist.h;
				rp = (h0->buffaddr + h0->curr_rec.offset);
				hdr_len = SIZEOF(rec_hdr) + gv_altkey->end + 1 - EVAL_CMPC((rec_hdr_ptr_t)rp);
				GET_USHORT(rlen, rp);
				if (FALSE == (CHKRECLEN(rp, h0->buffaddr, rlen)) || (rlen < hdr_len + SIZEOF(block_id)))
				{
					T_RETRY_AND_CLEANUP(cdb_sc_rmisalign, donot_restart);
					continue;
				}
				GET_LONG(lcl_root, (rp + hdr_len));
				if (rlen > hdr_len + SIZEOF(block_id))
				{
					assert(NULL != global_collation_mstr.addr || 0 == global_collation_mstr.len);
					if (global_collation_mstr.len < rlen - (hdr_len + SIZEOF(block_id)))
					{
						if (NULL != global_collation_mstr.addr)
							free(global_collation_mstr.addr);
						global_collation_mstr.len = rlen - (hdr_len + SIZEOF(block_id));
						global_collation_mstr.addr = (char *)malloc(global_collation_mstr.len);
					}
					/* the memcpy needs to be done here instead of out of for loop for
					 * concurrency consideration. We don't use s2pool because the pointer rp is 64 bits
					 */
					memcpy(global_collation_mstr.addr, rp + hdr_len + SIZEOF(block_id),
							rlen - (hdr_len + SIZEOF(block_id)));
				}
				if (dollar_tlevel)
				{
					status = tp_hist(NULL);
					if (cdb_sc_normal != status)
					{
						T_RETRY_AND_CLEANUP(status, donot_restart);
						continue;
					}
					break;
				}
			}
			if (!dollar_tlevel)
			{
				if ((trans_num)0 != t_end(&gv_target->hist, NULL, TN_NOT_SPECIFIED))
					break;
			} else
			{
				status = tp_hist(NULL);
				if (cdb_sc_normal == status)
					break;
				T_RETRY_AND_CLEANUP(status, donot_restart);
				continue;
			}
		} else
		{
			T_RETRY_AND_CLEANUP(status, donot_restart);
			continue;
		}
	}
	save_targ->root = lcl_root;	/* now that we know the transaction validated fine, set root block in gv_target */
	RESET_GV_TARGET_LCL_AND_CLR_GBL(save_targ, DO_GVT_GVKEY_CHECK);
	if (rlen > hdr_len + SIZEOF(block_id))
	{
		assert(NULL != global_collation_mstr.addr);
		subrec_ptr = get_spec((uchar_ptr_t)global_collation_mstr.addr,
				      (int)(rlen - (hdr_len + SIZEOF(block_id))), COLL_SPEC);
		if (subrec_ptr)
		{
			gv_target->nct = *(subrec_ptr + COLL_NCT_OFFSET);
			gv_target->act = *(subrec_ptr + COLL_ACT_OFFSET);
			gv_target->ver = *(subrec_ptr + COLL_VER_OFFSET);
		} else
		{
			gv_target->nct = 0;
			gv_target->act = 0;
			gv_target->ver = 0;
		}
	} else
	{
		gv_target->nct = 0;
		gv_target->act = cs_addrs->hdr->def_coll;
		gv_target->ver = cs_addrs->hdr->def_coll_ver;
	}
	if (gv_target->act)
		act_in_gvt();
	assert(gv_target->act || NULL == gv_target->collseq);
	return cdb_sc_normal;
}
