/****************************************************************
 *								*
 * Copyright (c) 2001-2020 Fidelity National Information	*
 * Services, Inc. and/or its subsidiaries. All rights reserved.	*
 *								*
 *	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 <stdarg.h>
#include <stddef.h>		/* for OFFSETOF macro */
#include "gtm_string.h"
#include "gtm_stdio.h"

#include "gtmio.h"
#include "gdsroot.h"
#include "gdsblk.h"
#include "gtm_facility.h"
#include "fileinfo.h"
#include "gdsbt.h"
#include "gdsfhead.h"
#include "filestruct.h"
#include "gdscc.h"
#include "gdskill.h"
#include "cdb_sc.h"
#include "lv_val.h"
#include "jnl.h"
#include "mlkdef.h"
#include <rtnhdr.h>
#include "mv_stent.h"
#include "stack_frame.h"
#include "tp_frame.h"
#include "buddy_list.h"		/* needed for tp.h */
#include "tp.h"
#include "tp_timeout.h"
#include "op.h"
#include "have_crit.h"
#include "gtm_caseconv.h"
#include "gvcst_protos.h"	/* for gvcst_tp_init prototype */
#include "dpgbldir.h"
#include "longset.h"		/* needed for cws_insert.h */
#include "cws_insert.h"		/* for cw_stagnate_reinitialized */
#include "alias.h"
#include "stp_parms.h"
#include "stringpool.h"
#ifdef GTM_TRIGGER
#include "gtm_trigger_trc.h"
#endif
#include "gvt_inline.h"

#ifdef UTF8_SUPPORTED
#include "gtm_icu_api.h"	/* needed by *TYPEMASK* macros defined in gtm_utf8.h */
#include "gtm_utf8.h"
#endif

error_def(ERR_STACKCRIT);
error_def(ERR_STACKOFLOW);
error_def(ERR_TPMIXUP);
error_def(ERR_TPTOODEEP);

GBLREF	jnl_fence_control	jnl_fence_ctl;
GBLREF	uint4			dollar_tlevel;
GBLREF	boolean_t		dollar_truth;
GBLREF	mval			dollar_zgbldir;
GBLREF	sgmnt_addrs		*cs_addrs;
GBLREF	gd_addr			*gd_header;
GBLREF	gv_key			*gv_currkey;
GBLREF	gv_namehead		*gv_target;
GBLREF	stack_frame		*frame_pointer;
GBLREF	tp_frame		*tp_pointer;
GBLREF	mv_stent		*mv_chain;
GBLREF	mlk_pvtblk		*mlk_pvt_root;
GBLREF	symval			*curr_symval;
GBLREF	unsigned char		*msp, *stacktop, *stackwarn, *tpstackbase, *tpstacktop, *tp_sp, t_fail_hist[CDB_MAX_TRIES];
GBLREF  unsigned int		t_tries;
GBLREF	tp_region		*tp_reg_list, *tp_reg_free_list;
GBLREF	trans_num		local_tn;		/* transaction number for THIS PROCESS */
GBLREF	trans_num		tstart_local_tn;	/* copy of global variable "local_tn" at op_tstart time */
GBLREF	boolean_t		mupip_jnl_recover;
GBLREF 	void			(*tp_timeout_start_timer_ptr)(int4 tmout_sec);
GBLREF  sgm_info                *first_sgm_info;
GBLREF  global_tlvl_info	*global_tlvl_info_head;
GBLREF  buddy_list		*global_tlvl_info_list;
GBLREF  sgmnt_data_ptr_t	cs_data;
GBLREF  sgmnt_addrs		*cs_addrs;
GBLREF  gd_region		*gv_cur_region;
GBLREF	struct_jrec_tcom	tcom_record;
GBLREF	jnl_gbls_t		jgbl;
GBLREF	boolean_t		gtm_utf8_mode;
GBLREF	uint4			tstartcycle;
GBLREF	char			*update_array_ptr;
GBLREF	ua_list			*curr_ua, *first_ua;
GBLREF	mstr			extnam_str;
GBLREF	lv_val			*active_lv;
#ifdef GTM_TRIGGER
GBLREF	mval			dollar_ztwormhole;
GBLREF	int4			gtm_trigger_depth;
GBLREF	int4			tstart_trigger_depth;
GBLREF	mval			dollar_ztslate;
LITREF	mval			literal_null;
#endif
#ifdef VMS
GBLREF	boolean_t		tp_has_kill_t_cse; /* cse->mode of kill_t_write or kill_t_create got created in this transaction */
#endif
#ifdef DEBUG
GBLREF	sgmnt_addrs		*reorg_encrypt_restart_csa;
GBLREF	uint4			update_trans;
#endif

#define NORESTART -1
#define ALLLOCAL  -2
#define TP_STACK_SIZE ((TP_MAX_NEST + 1) * SIZEOF(tp_frame))	/* Size of TP stack frame with no-overflow pad */

void	op_tstart(int implicit_flag, ...) /* value of $T when TSTART */
{
	boolean_t		serial;			/* whether SERIAL keyword was present */
	int			prescnt,		/* number of names to save, -1 = no restart, -2 = preserve all */
				pres, len;
	char			*ptr;
	lv_val			*lv;
	mlk_pvtblk		*pre_lock;
	mlk_tp			*lck_tp;
	mval			*preserve,		/* list of names to save */
				*tid,			/* transaction id */
				*mvname;
	mv_stent		*mv_st_ent, *mvst_tmp, *mvst_prev;
	stack_frame		*fp, *fp_fix;
	tp_frame		*tf;
	unsigned char		*old_sp, *top, *tstack_ptr, *ptrstart, *ptrend, *ptrinvalidbegin;
	va_list			varlst, lvname;
	tp_region		*tr, *tr_next;
	sgm_info		*si;
	tlevel_info		*tli, *new_tli, *prev_tli;
	global_tlvl_info	*gtli, *new_gtli, *prev_gtli;
	kill_set		*ks, *prev_ks;
	jnl_format_buffer 	*jfb, *prev_jfb;
	gd_region		*r_top, *r_local;
	gd_addr			*addr_ptr;
	unsigned char		tp_bat[TP_BATCH_LEN];
	mname_entry		tpvent;
	ht_ent_mname		*tabent, *curent, *topent;
	sgmnt_addrs		*csa;
	int4			shift_size;
	boolean_t		tphold_noshift = FALSE, implicit_tstart;
	lv_val			**lvarraycur = NULL, **lvarray = NULL, **lvarraytop, **lvptr;
	GTMTRIG_ONLY(boolean_t	implicit_trigger;)
	DCL_THREADGBL_ACCESS;

	SETUP_THREADGBL_ACCESS;
	assert(NULL == reorg_encrypt_restart_csa);
	implicit_tstart = 0 != (implicit_flag & IMPLICIT_TSTART);
	GTMTRIG_ONLY(implicit_trigger = 0 != (implicit_flag & IMPLICIT_TRIGGER_TSTART));
	GTMTRIG_ONLY(assert(!implicit_trigger || (implicit_trigger && implicit_tstart)));
#	if ((defined(GTM_TRIGGER) && defined(DEBUG_TRIGR)) || defined(DEBUG_REFCNT))
	DBGFPF((stderr, "\n\nop_tstart: Entered - dollar_tlevel: %d, implicit_flag: %d, mpc: 0x"lvaddr"\n", dollar_tlevel,
		implicit_flag, frame_pointer->mpc));
#	endif
	assert(dollar_tlevel || implicit_tstart || !update_trans);
	if (implicit_tstart)
		/* An implicit op_tstart is being done. In this case, even if we are in direct mode, we want to do
		 * regular TPHOLD processing (no setting of tphold in the parent frame and shifting of all mv_stents).
		 * This is ok because the life of the TP transaction will be done before the implicit operation()s are done
		 * done so it will not persist across M lines like it normally would in direct mode.
		 */
		tphold_noshift = TRUE;
	if (0 != jnl_fence_ctl.level)
		rts_error_csa(CSA_ARG(NULL) VARLSTCNT(4) ERR_TPMIXUP, 2, "An M", "a fenced logical");
	if (dollar_tlevel + 1 >= TP_MAX_NEST)
		rts_error_csa(CSA_ARG(NULL) VARLSTCNT(1) ERR_TPTOODEEP);
	va_start(varlst, implicit_flag);	/* no argument count first */
	serial = va_arg(varlst, int);
	tid = va_arg(varlst, mval *);
	prescnt = va_arg(varlst, int);
	MV_FORCE_STR(tid);
	if (!dollar_tlevel)
	{
		jnl_fence_ctl.fence_list = JNL_FENCE_LIST_END;
		jgbl.cumul_jnl_rec_len = 0;
#		ifdef DEBUG
		if (1 == jgbl.cumul_index)	/* when 1 == jgbl.cumul_index and  0 == jgbl.cumul_index, non-TP morphed into TP */
			jgbl.cumul_index = 0;
		else
			assert(0 == jgbl.cumul_index);
		TREF(tp_restart_dont_counts) = 0;
#		endif
		assert(0 == jgbl.cu_jnl_index);
		GTMTRIG_ONLY(memcpy(&dollar_ztslate, &literal_null, SIZEOF(mval)));	/* Zap $ZTSLate at start of lvl 1 trans */
		GTMTRIG_ONLY(if (!implicit_tstart || !implicit_trigger))
		{	/* This is the path for all non-implicit-trigger type TP fences including the implicit fences
			 * created by the update process and by mupip recover forward.
			 * Note: For recover/rollback, t_tries is set to CDB_STAGNATE because unlike Non-TP restarts, TP restarts
			 * need more context to determine the restart point which is non-trivial for recover/rollback since the
			 * updates are done with journal records extracted by sequentially reading the journal file.
			 */
			t_tries = (FALSE == mupip_jnl_recover) ? 0 : CDB_STAGNATE;
			t_fail_hist[t_tries] = cdb_sc_normal;
			/* ensure that we don't have crit on any region at the beginning of a TP transaction (be it GT.M or MUPIP).
			 * The only exception is ONLINE ROLLBACK or MUPIP TRIGGER -UPGRADE which holds crit for the entire duration
			 */
			assert(0 == have_crit(CRIT_HAVE_ANY_REG) UNIX_ONLY(|| jgbl.onlnrlbk || TREF(in_trigger_upgrade)));
		}
		else
		{
			/* This is an implicit TP wrap created for an explicit update. In such case, we do not want to reset
			 * t_tries for below reasons:
			 * (a) If an explicit non-tp update undergoes 3 restarts (t_tries = 3) and on the final retry gvcst_put
			 * sees that triggers are defined (due to concurrent $ZTRIGGER or MUPIP TRIGGER), it would have invoked
			 * op_tstart to create a TP wrap in final retry in which case we should NOT reset t_tries to zero as we
			 * would be holding crit at that time.
			 *
			 * (b) If an explicit non-tp update underwent 1 restart and on the next try(t_tries = 1), gvcst_put
			 * sees that triggers are defined (due to concurrent $ZTRIGGER or MUPIP TRIGGER), it would have invoked
			 * op_tstart to create a TP wrap in which case we should NOT reset t_tries. In the event, we did the
			 * reset of t_tries and no triggers were defined for the update in question, op_tcommit would be done
			 * and we will be back in non-TP BUT with t_tries reset to 0. The above can continue in a cycle causing
			 * a live spin-lock that does not let the final-retry optimistic -> pessimistic concurrency scheme from
			 * kicking in at all.
			 */
			 /* recovery logic does not invoke triggers */
			assert(!(SSF_NORET_VIA_MUMTSTART & frame_pointer->flags) || (FALSE == mupip_jnl_recover));
		}
		for (tr = tp_reg_list; NULL != tr; tr = tr_next)
		{	/* start with empty list, place all existing entries on free list */
			tp_reg_list = tr_next = tr->fPtr;	/* Remove from queue */
			tr->fPtr = tp_reg_free_list;
			tp_reg_free_list = tr; 			/* Place on free queue */
		}
		++local_tn;					/* Begin new local transaction */
		INCR_TSTARTCYCLE;				/* All local var save/restores operate under this cycle */
		tstart_local_tn = local_tn;
		/* In journal recovery forward phase, we set jgbl.tp_ztp_jnl_upd_num to whatever update_num the journal record
		 * has so it is ok for the global variable to be a non-zero value at the start of a TP transaction (possible if
		 * ZTP of one process is in progress when TP of another process starts in the journal file). But otherwise
		 * (in GT.M runtime) we expect it to be 0 at beginning of each TP or ZTP.
		 */
		assert((0 == jgbl.tp_ztp_jnl_upd_num) || jgbl.forw_phase_recovery);
		jgbl.wait_for_jnl_hard = TRUE;
		GTMTRIG_ONLY(
			assert(NULL == jgbl.prev_ztworm_ptr);	/* should have been cleared by tp_clean_up of previous TP */
			assert(NULL == jgbl.save_ztworm_ptr);
				/* should have been NULL almost always except for a small window in gvcst_put/gvcst_kill */
			tstart_trigger_depth = gtm_trigger_depth; /* note down what trigger depth an outermost tstart occurs in */
		)
		memset(tcom_record.jnl_tid, 0, TID_STR_SIZE);
		if (0 != tid->str.len)
		{
			if (!gtm_utf8_mode)
			{
				if (tid->str.len > TID_STR_SIZE)
					tid->str.len = TID_STR_SIZE;
			}
#			ifdef UTF8_SUPPORTED
			else
			{	/* In UTF8 mode, take only as many valid multi-byte characters as can fit in TID_STR_SIZE */
				if (gtm_utf8_mode)
				{
					MV_FORCE_LEN(tid); /* issues BADCHAR error if appropriate */
					if (tid->str.len > TID_STR_SIZE)
					{
						ptrstart = (unsigned char *)tid->str.addr;
						ptrend = ptrstart + TID_STR_SIZE;
						UTF8_LEADING_BYTE(ptrend, ptrstart, ptrinvalidbegin)
						tid->str.len = INTCAST(ptrinvalidbegin - ptrstart);
					}
				}
			}
#			endif
			assert(TID_STR_SIZE >= tid->str.len);
			memcpy(tcom_record.jnl_tid, (char *)tid->str.addr, tid->str.len);
			if ((TP_BATCH_SHRT == tid->str.len) || (TP_BATCH_LEN == tid->str.len))
			{
				lower_to_upper(tp_bat, (uchar_ptr_t)tid->str.addr, (int)tid->str.len);
				if (0 == memcmp(TP_BATCH_ID, tp_bat, tid->str.len))
					jgbl.wait_for_jnl_hard = FALSE;
			}
		}
		VMS_ONLY(tp_has_kill_t_cse = FALSE;)
		assert(!TREF(donot_commit));
	}
	/* Either cw_stagnate has not been initialized at all or previous-non-TP or tp_hist should have done CWS_RESET */
	assert((0 == cw_stagnate.size) || cw_stagnate_reinitialized);
	if (prescnt > 0)
	{
		VAR_COPY(lvname, varlst);
		for (pres = 0;  pres < prescnt;  ++pres)
		{
			preserve = va_arg(lvname, mval *);
			assert(MV_IS_STRING(preserve));		/* Check if this loop can be eliminated */
			MV_FORCE_STR(preserve);
		}
		va_end(lvname);
	}
	if (NULL == gd_header)
		gvinit();
	assert(NULL != gd_header);
	if (!tphold_noshift && (SFT_DM & frame_pointer->old_frame_pointer->type))
	{	/* Put a TPHOLD underneath dmode frame */
		assert(frame_pointer->old_frame_pointer->old_frame_pointer);
		fp = frame_pointer->old_frame_pointer->old_frame_pointer;
		top = (unsigned char *)(frame_pointer->old_frame_pointer + 1);
		old_sp = msp;
		shift_size = mvs_size[MVST_TPHOLD];
		msp -= shift_size;
		if (msp <= stackwarn)
		{
			va_end(varlst);
			if (msp <= stacktop)
			{
				msp = old_sp;
				rts_error_csa(CSA_ARG(NULL) VARLSTCNT(1) ERR_STACKOFLOW);
			} else
				rts_error_csa(CSA_ARG(NULL) VARLSTCNT(1) ERR_STACKCRIT);
		}
		memmove(msp, old_sp, top - (unsigned char *)old_sp);	/* Shift stack w/possible overlapping ranges */
		mv_st_ent = (mv_stent *)(top - shift_size);
		mv_st_ent->mv_st_type = MVST_TPHOLD;
		ADJUST_FRAME_POINTER(frame_pointer, shift_size);
		for (fp_fix = frame_pointer;  fp_fix != fp;  fp_fix = fp_fix->old_frame_pointer)
		{
			if ((unsigned char *)fp_fix->l_symtab < top  &&  (unsigned char *)fp_fix->l_symtab > stacktop)
				fp_fix->l_symtab = (ht_ent_mname **)((char *)fp_fix->l_symtab - shift_size);
			if (fp_fix->temps_ptr < top  &&  fp_fix->temps_ptr > stacktop)
				fp_fix->temps_ptr -= shift_size;
			if (fp_fix->vartab_ptr < (char *)top  &&  fp_fix->vartab_ptr > (char *)stacktop)
				fp_fix->vartab_ptr -= shift_size;
			if ((unsigned char *)fp_fix->old_frame_pointer < top  &&
			   (char *)fp_fix->old_frame_pointer > (char *)stacktop)
			{
				ADJUST_FRAME_POINTER(fp_fix->old_frame_pointer, shift_size);
			}
		}
		if ((unsigned char *)mv_chain >= top)
		{
			mv_st_ent->mv_st_next = (unsigned int)((char *)mv_chain - (char *)mv_st_ent);
			mv_chain = mv_st_ent;
		} else
		{
			top -= shift_size + SIZEOF(stack_frame);
			mv_chain = (mv_stent *)((char *)mv_chain - shift_size);
			mvst_tmp = mv_chain;
			mvst_prev = (mv_stent *)((char *)mvst_tmp + mvst_tmp->mv_st_next);
			while (mvst_prev < (mv_stent *)top)
			{
				mvst_tmp = mvst_prev;
				mvst_prev = (mv_stent *)((char *)mvst_tmp + mvst_tmp->mv_st_next);
			}
			mvst_tmp->mv_st_next = (unsigned int)((char *)mv_st_ent - (char *)mvst_tmp);
			mv_st_ent->mv_st_next = (unsigned int)((char *)mvst_prev - (char *)mv_st_ent + shift_size);
		}
	} else
	{
		PUSH_MV_STENT(MVST_TPHOLD);
		mv_st_ent = mv_chain;
		fp = frame_pointer;
	}
	mv_st_ent->mv_st_cont.mvs_tp_holder.tphold_tlevel = dollar_tlevel;
#	ifdef GTM_TRIGGER
	if (!dollar_tlevel)
		/* We only save this on level 0 - Note if this is made further conditional, be sure to visit
		 * stp_gcol_src.h where it is GC'd and tp_restart() to adjust the conditions there as well.
		 */
		memcpy(&mv_st_ent->mv_st_cont.mvs_tp_holder.ztwormhole_save, &dollar_ztwormhole, SIZEOF(mval));
#	endif
	if (NULL == tpstackbase)
	{
		tstack_ptr = (unsigned char *)malloc(TP_STACK_SIZE);
		tp_sp = tpstackbase = tstack_ptr + TP_STACK_SIZE;
		tpstacktop = tstack_ptr;
		tp_pointer = NULL;
	}

	/* Add a new tp_frame in the TP stack */
	DBGRFCT((stderr, "\n\n********* op_tstart: *** Entering $TLEVEL = %d\n", dollar_tlevel + 1));
	tf = (tp_frame *)(tp_sp -= SIZEOF(tp_frame));
	assert((unsigned char *)tf > tpstacktop);	/* Block should lie entirely within tp stack area */
	tf->restart_pc = fp->mpc;
	tf->restart_ctxt = fp->ctxt;
	tf->fp = fp;
	tf->serial = serial;
	tf->trans_id = *tid;
	tf->restartable = (NORESTART != prescnt);
	tf->old_locks = (NULL != mlk_pvt_root);
#	ifdef DEBUG
	/* Do check that gv_currkey and gv_target are in sync.
	 * In case of forward phase of journal recovery, gv_currkey is set by caller (mur_forward) only
	 * after the call to op_tstart so avoid doing gv_currkey check. Also in case of trigger_upgrade,
	 * we could start a TP transaction from almost anywhere (e.g. inside name-level $order in op_gvorder)
	 * and so we are not guaranteed this check will succeed. Since this check is not necessary anymore
	 * (see comment before macro definition) we skip this check in that case. A broad (but not accurate)
	 * way to check for trigger_upgrade is if gv_target is directory-tree (i.e. gvname is "").
	 */
	if (!jgbl.forw_phase_recovery && (NULL != gv_target) && gv_target->gvname.var_name.len)
	{
		DBG_CHECK_GVTARGET_GVCURRKEY_IN_SYNC(CHECK_CSA_TRUE);
	}
#	endif
	if (!dollar_tlevel)
	{	/* If the TP structures have not yet been initialized, do that now. */
		if (NULL == TREF(gv_tporigkey_ptr))
		{	/* This need only be set once */
			TREF(gv_tporigkey_ptr) = (gv_key_buf *)malloc(SIZEOF(gv_key_buf));
			memset(TREF(gv_tporigkey_ptr), 0, SIZEOF(gv_key_buf));
		}
		tf->orig_key = (gv_key *)&((TREF(gv_tporigkey_ptr))->key);
		assert(NULL != gv_currkey);
		MEMCPY_KEY(tf->orig_key, gv_currkey);
		tf->gd_header = gd_header;
		tf->gd_reg = gv_cur_region;
		tf->orig_gv_target = gv_target;
		tf->zgbldir = dollar_zgbldir;
		tf->dlr_t = dollar_truth;
		len = extnam_str.len;
		tf->extnam_str.len = len;
		if (len)
		{	/* Take a copy of extnam_str */
			if ((TREF(gv_tporig_extnam_str)).len < len)
			{
				if (NULL != (TREF(gv_tporig_extnam_str)).addr)
					free((TREF(gv_tporig_extnam_str)).addr);
				(TREF(gv_tporig_extnam_str)).addr = malloc(len);
				(TREF(gv_tporig_extnam_str)).len = len;	/* this is the allocated buffer size */
			}
			ptr = (TREF(gv_tporig_extnam_str)).addr;
			memcpy(ptr, extnam_str.addr, len);
			tf->extnam_str.addr = ptr;
		}
		tf->active_lv = active_lv;
		if (implicit_tstart && (NULL != active_lv))
		{	/* If active_lv is non-NULL at the start of an implicit TP (possible for example if implicit TP was
			 * started from an op_gvdata call made inside op_merge.c) we should not tamper with this active_lv
			 * as part of the tp_unwind processing of the implicit TP. That active_lv is only for the caller
			 * (op_merge in this case) to play with. So save this active_lv in the TP frame (to be restored
			 * later at commit or rollback time) and set active_lv to NULL for the duration of this TP.
			 * Note: We cannot do this save/restore of active_lv for explicit transactions as those might actually
			 * tamper with active_lv by freeing that memory (e.g. kill * operations).
			 */
			SET_ACTIVE_LV(NULL, implicit_tstart ? FALSE : TRUE, actlv_op_tstart);
		}
	}
#	ifdef DEBUG
	else
	{	/* Set up fields in "tf" to impossible values for nested TP frames. These should never be used.
		 * This will help us get an assert failure or SIG-11 right should any of these get referenced.
		 */
		tf->orig_key = NULL;
		tf->gd_header = NULL;
		tf->gd_reg = NULL;
		tf->orig_gv_target = NULL;
		/* Unfortunately tf->dlr_t has just one bit to store 0 or 1 (valid values) so an impossible value of say -1
		 * cannot be stored here. We therefore skip initializing this one.
		 */
		tf->zgbldir.mvtype = 0;	/* impossible value */
		tf->extnam_str.len = -1; /* impossible value */
		tf->active_lv = NULL;
	}
#	endif
	DBG_CHECK_GVTARGET_CSADDRS_IN_SYNC;
	tf->mvc = mv_st_ent;
	tf->sym = curr_symval;
	tf->tp_save_all_flg = FALSE;
	if (NULL == tp_pointer)
	{
		tf->implicit_tstart = implicit_tstart;
		GTMTRIG_ONLY(tf->implicit_trigger = implicit_trigger);
	} else
	{
		tf->implicit_tstart = tp_pointer->implicit_tstart;
		GTMTRIG_ONLY(tf->implicit_trigger = tp_pointer->implicit_trigger);
	}
	GTMTRIG_ONLY(tf->cannot_commit = FALSE;)
	tf->vars = (tp_var *)NULL;
	tf->old_tp_frame = tp_pointer;
	tp_pointer = tf;
	if (prescnt > 0)
	{
		DBGRFCT((stderr, "\nop_tstart: Beginning processing of varname parameters\n"));
		VAR_COPY(lvname, varlst);
		for (pres = 0;  pres < prescnt;  ++pres)
		{
			preserve = va_arg(lvname, mval *);
			/* Note: the assumption (according to the comment below) is that this mval points into the literal table
			 * and thus could not possibly be undefined. In that case, I do not understand why the earlier loop to
			 * do MV_FORCE_STR on these variables. Future todo -- verify if that loop is needed. On the assumption
			 * that it is not, the below assert will verify that the mval is defined to catch any NOUNDEF case.
			 */
			assert(MV_DEFINED(preserve));
			/* The incoming 'preserve' is the pointer to a literal mval table entry. For the indirect code
			 * (eg. Direct Mode), since the literal table is no longer on the M stack, we should not shift
			 * the incoming va_arg pointer (C9D01-002205).
			 */
			mvname = preserve;
			if (0 != mvname->str.len)
			{	/* Convert mval to mident and see if it's in the symbol table */
				tpvent.var_name.len = mvname->str.len;
				tpvent.var_name.addr = mvname->str.addr;
				DBGRFCT((stderr, "\nop_tstart: Processing var %.*s\n", tpvent.var_name.len, tpvent.var_name.addr));
				COMPUTE_HASH_MNAME(&tpvent);
				tpvent.marked = FALSE;
				if (add_hashtab_mname_symval(&curr_symval->h_symtab, &tpvent, NULL, &tabent))
					lv_newname(tabent, curr_symval);
				lv = (lv_val *)tabent->value;
				assert(lv);
				assert(LV_IS_BASE_VAR(lv));
				assert(0 < lv->stats.trefcnt);
				assert(lv->stats.crefcnt <= lv->stats.trefcnt);
				/* In order to allow restart of a sub-transaction, this should chain rather than back stop,
				 * with appropriate changes to lv_var_clone and tp_unwind.
				 */
				if (NULL == lv->tp_var)
				{
					TP_SAVE_RESTART_VAR(lv, tf, &tabent->key);
					if (LV_HAS_CHILD(lv))
					{
						ADD_TO_STPARRAY(lv, lvarray, lvarraycur, lvarraytop, lv_val);
					}
				}
			}
		}
		va_end(lvname);
		DBGRFCT((stderr, "\nop_tstart: Completed processing of varname parameters\n"));
	} else if (ALLLOCAL == prescnt)
	{	/* Preserve all variables */
		DBGRFCT((stderr, "\nop_tstart: Beginning processing of all vars due to TSTART *\n"));
		tf->tp_save_all_flg = TRUE;
		++curr_symval->tp_save_all;
		for (curent = curr_symval->h_symtab.base, topent = curr_symval->h_symtab.top;  curent < topent;  curent++)
		{
			if (HTENT_VALID_MNAME(curent, lv_val, lv) && ('$' != *curent->key.var_name.addr))
			{
				assert(lv);
				assert(LV_IS_BASE_VAR(lv));
				assert(0 < lv->stats.trefcnt);
				assert(lv->stats.crefcnt <= lv->stats.trefcnt);
				if (NULL == lv->tp_var)
				{
					DBGRFCT((stderr, "\nop_tstart: Creating save point for var '%.*s' at lv_val 0x"lvaddr
						 " and processing any alias containers found in it\n", curent->key.var_name.len,
						 curent->key.var_name.addr, lv));
					TP_SAVE_RESTART_VAR(lv, tf, &curent->key);
					if (LV_HAS_CHILD(lv))
					{
						ADD_TO_STPARRAY(lv, lvarray, lvarraycur, lvarraytop, lv_val);
					}
				}
			}
		}
	}
	va_end(varlst);
	/* Need to go through list of saved lvs to do tpsav processing for containers in their lv trees */
	for (lvptr = lvarray; lvptr < lvarraycur; ++lvptr)
	{	/* save *lvptr in local variable before passing to macro to avoid multiple *lvptr references inside macro */
		lv = *lvptr;
		TPSAV_CNTNRS_IN_TREE(lv);
	}
	/* Store existing state of locks */
	for (pre_lock = mlk_pvt_root;  NULL != pre_lock;  pre_lock = pre_lock->next)
	{
		if ((NULL == pre_lock->tp) || (pre_lock->tp->level != pre_lock->level)
			|| (pre_lock->tp->zalloc != pre_lock->zalloc))
		{	/* Only stack locks that have changed since last TSTART */
			lck_tp = (mlk_tp *)malloc(SIZEOF(mlk_tp));
			lck_tp->tplevel = dollar_tlevel;
			lck_tp->level = pre_lock->level;
			lck_tp->zalloc = pre_lock->zalloc;
			lck_tp->next = pre_lock->tp;
			pre_lock->tp = lck_tp;
		}
	}
	++dollar_tlevel;
	/* Store the global (across all segments) dollar_tlevel specific information. */
	if (NULL != first_sgm_info) /* database activity existed in prior levels */
	{
		for (prev_gtli = NULL, gtli = global_tlvl_info_head; gtli; gtli = gtli->next_global_tlvl_info)
			prev_gtli = gtli;
		new_gtli = (global_tlvl_info *)get_new_element(global_tlvl_info_list, 1);
		new_gtli->global_tlvl_fence_info = jnl_fence_ctl.fence_list;
		new_gtli->t_level = dollar_tlevel;
		GTMTRIG_ONLY(new_gtli->tlvl_prev_ztworm_ptr = jgbl.prev_ztworm_ptr;)
		new_gtli->tlvl_cumul_jrec_len = jgbl.cumul_jnl_rec_len;
		DEBUG_ONLY(new_gtli->tlvl_cumul_index = jgbl.cumul_index;)
		new_gtli->tlvl_tp_ztp_jnl_upd_num = jgbl.tp_ztp_jnl_upd_num;
		/* Store current state of update_array global variables */
		assert((NULL != first_ua) && (NULL != curr_ua)); /* Since first_sgm_info is NOT NULL, database activity existed */
		new_gtli->curr_ua = (struct ua_list *)(curr_ua);
		new_gtli->upd_array_ptr = update_array_ptr;
		GTMTRIG_ONLY(new_gtli->ztrigbuffLen = TREF(ztrigbuffLen);)
		new_gtli->next_global_tlvl_info = NULL;
		if (prev_gtli)
		{
			assert(prev_gtli->t_level + 1 == dollar_tlevel);
			prev_gtli->next_global_tlvl_info = new_gtli;
		} else
			global_tlvl_info_head = new_gtli;
	}
	/* Store the dollar_tlevel specific information */
	for (si = first_sgm_info; NULL != si; si = si->next_sgm_info)
	{
		for (prev_tli = NULL, tli = si->tlvl_info_head; (NULL != tli); tli = tli->next_tlevel_info)
			prev_tli = tli;
		new_tli = (tlevel_info *)get_new_element(si->tlvl_info_list, 1);
		new_tli->t_level = dollar_tlevel;
		for (prev_ks = NULL, ks = si->kill_set_head; ks; ks = ks->next_kill_set)
			prev_ks = ks;
		new_tli->tlvl_kill_set = prev_ks;
		if (prev_ks)
			new_tli->tlvl_kill_used = prev_ks->used;
		else
			new_tli->tlvl_kill_used = 0;
		new_tli->tlvl_tp_hist_info = si->last_tp_hist;
		/* Prepare for journaling logical records if journaling is enabled on this region OR if replication was
		 * allowed on this region (this is a case where replication was ON originally but later transitioned
		 * into WAS_ON state and journaling got turned OFF.
		 */
		csa = si->tp_csa;
		if (JNL_WRITE_LOGICAL_RECS(csa))
		{
			assert((NULL != si->jnl_head) || (NULL == csa->next_fenced));
			assert((NULL == si->jnl_head) || (NULL != csa->next_fenced));
			assert((NULL == csa->next_fenced) || (JNL_FENCE_LIST_END == csa->next_fenced)
								|| (NULL != csa->next_fenced->sgm_info_ptr->jnl_head));
			assert(NULL == *si->jnl_tail);
			SET_PREV_JFB(si, prev_jfb);
			new_tli->tlvl_jfb_info = prev_jfb;
			assert(NULL != si->jnl_list);
			assert(NULL != si->format_buff_list);
			new_tli->jnl_list_elems = si->jnl_list->nElems;
			new_tli->jfb_list_elems = si->format_buff_list->nElems;
		}
		new_tli->next_tlevel_info = NULL;
		new_tli->update_trans = si->update_trans;
		if (prev_tli)
			prev_tli->next_tlevel_info = new_tli;
		else
			si->tlvl_info_head = new_tli;
	}
	/* If starting first TP level, also start TP timer if set to non-default value */
	assert(0 <= TREF(dollar_zmaxtptime));
	if ((0 < TREF(dollar_zmaxtptime)) && (1 == dollar_tlevel))
		(*tp_timeout_start_timer_ptr)(TREF(dollar_zmaxtptime));
	DBGRFCT((stderr, "\nop_tstart: complete\n"));
}
