/****************************************************************
 *								*
 * Copyright (c) 2010-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"

#ifdef GTM_TRIGGER
#include "gtm_stdio.h"
#include "gtm_string.h"

#include "nametabtyp.h"
#include "namelook.h"
#include "op.h"
#include "util.h"
#include "gdsroot.h"
#include "gdsblk.h"
#include "gtm_facility.h"
#include "fileinfo.h"
#include "gdsbt.h"
#include "gdsfhead.h"
#include "error.h"
#include "filestruct.h"
#include "gdscc.h"
#include "gdskill.h"
#include "jnl.h"
#include "io.h"
#include "hashtab_str.h"
#include "gv_trigger.h"
#include "trigger.h"
#include "trigger_update_protos.h"
#include "trigger_select_protos.h"
#include "trigger_trgfile_protos.h"
#include "restrict.h"
#include "xfer_enum.h"
#include "code_address_type.h"
#include "fix_xfer_entry.h"
#include "repl_msg.h"			/* for gtmsource.h */
#include "gtmsource.h"			/* for jnlpool_addrs_ptr_t */
#include "stringpool.h"

GBLREF	boolean_t		dollar_ztrigger_invoked;
GBLREF	gd_addr			*gd_header;
GBLREF	gd_region		*gv_cur_region;
GBLREF	gv_key			*gv_currkey;
GBLREF	gv_namehead		*gv_target;
GBLREF	int4			gtm_trigger_depth;
GBLREF	jnlpool_addrs_ptr_t	jnlpool;
GBLREF	mstr			*dollar_ztname;
GBLREF	sgmnt_data_ptr_t 	cs_data;
GBLREF spdesc			rts_stringpool, stringpool;
GBLREF	uint4			dollar_tlevel;
#ifdef DEBUG
GBLREF	boolean_t		donot_INVOKE_MUMTSTART;
#endif

LITREF	mval	literal_zero;
LITREF	mval	literal_one;

STATICDEF boolean_t		save_gv_last_subsc_null, save_gv_some_subsc_null;
STATICDEF gd_addr		*save_gd_header;
STATICDEF gv_key_buf		save_currkey;
STATICDEF gv_key		*save_gv_currkey;
STATICDEF gv_namehead		*save_gv_target;
STATICDEF gd_region		*save_gv_cur_region;
STATICDEF jnlpool_addrs_ptr_t	save_jnlpool;
#ifdef DEBUG
STATICDEF boolean_t		in_op_fnztrigger;
#endif

error_def(ERR_DZTRIGINTRIG);
error_def(ERR_FILENAMETOOLONG);
error_def(ERR_RESTRICTEDOP);
error_def(ERR_ZTRIGINVACT);


enum ztrprms
{
	ZTRP_FILE,	/* $ZTRIGGER() FILE parameter - process given file of triggers */
	ZTRP_ITEM,	/* $ZTRIGGER() ITEM parameter - process single line of trigger file */
	ZTRP_SELECT	/* $ZTRIGGER() SELECT parameter - perform mupip trigger select with given parms */
};

LITDEF nametabent ztrprm_names[] =
{
	{1, "F"}, {4, "FILE"},
	{1, "I"}, {4, "ITEM"},
	{1, "S"}, {6, "SELECT"}
};
LITDEF unsigned char ztrprm_index[27] =
{
	 0,  0,  0,  0,  0,  0,  2,  2,  2,	/* a b c d e f g h i */
	 4,  4,  4,  4,  4,  4,  4,  4,  4,	/* j k l m n o p q r */
	 4,  6,  6,  6,  6,  6,  6,  6,  6	/* s t u v w x y z ~ */
};
LITDEF enum ztrprms ztrprm_data[] =
{
	ZTRP_FILE, ZTRP_FILE,
	ZTRP_ITEM, ZTRP_ITEM,
	ZTRP_SELECT, ZTRP_SELECT
};

/* reset global variables from the values noted down at op_fnztrigger entry */
#define RESTORE_ZTRIGGER_ENTRY_STATE												\
{																\
	DCL_THREADGBL_ACCESS;													\
																\
	SETUP_THREADGBL_ACCESS;													\
	/* Reset global variables noted down at function entry */								\
	gd_header = save_gd_header;												\
	if (NULL != save_gv_cur_region)												\
	{															\
		gv_cur_region = save_gv_cur_region;										\
		TP_CHANGE_REG(gv_cur_region);											\
	}															\
	gv_target = save_gv_target;												\
	jnlpool = save_jnlpool;													\
	if (NULL != save_gv_currkey)												\
	{	/* gv_currkey->top could have changed if we opened a database with bigger keysize				\
		 * inside the trigger* functions above. Therefore take care not to overwrite that				\
		 * part of the gv_currkey structure. Restore everything else.							\
		 */														\
		COPY_KEY(gv_currkey, save_gv_currkey);										\
	} else if (NULL != gv_currkey)												\
	{															\
		gv_currkey->end = 0;												\
		gv_currkey->base[0] = KEY_DELIMITER;										\
		TREF(gv_last_subsc_null) = save_gv_last_subsc_null;								\
		TREF(gv_some_subsc_null) = save_gv_some_subsc_null;								\
	}															\
	DEBUG_ONLY(in_op_fnztrigger = FALSE);											\
}

/* In case of an rts_error deep inside the op_fnztrigger invocation, we need to restore certain global variables that could
 * have been modified.
 */
CONDITION_HANDLER(op_fnztrigger_ch)
{
	START_CH(TRUE);

	RESTORE_ZTRIGGER_ENTRY_STATE;
	DEBUG_ONLY(donot_INVOKE_MUMTSTART = FALSE);

	NEXTCH;

}

void op_fnztrigger(mval *func, mval *arg1, mval *arg2, mval *dst)
{
	boolean_t			failed;
	char				filename[MAX_FN_LEN + 1];
	int				inparm_len, index;
	uint4				filename_len;
	DCL_THREADGBL_ACCESS;

	SETUP_THREADGBL_ACCESS;
	assert(FALSE == in_op_fnztrigger); /* $ZTRIGGER() should not nest */
	DEBUG_ONLY(in_op_fnztrigger = TRUE);
	/* $ZTRIGGER() is not allowed while already inside the trigger frame */
	if (0 < gtm_trigger_depth)
	{
		DEBUG_ONLY(in_op_fnztrigger = FALSE);
		rts_error_csa(CSA_ARG(NULL) VARLSTCNT(4) ERR_DZTRIGINTRIG, 2, dollar_ztname->len, dollar_ztname->addr);
	}
	assert(rts_stringpool.base == stringpool.base); /* because stp management and trigger compilation have a history */
	MV_FORCE_STR(func);
	MV_FORCE_STR(arg1);
	/* MV_FORCE_STR(arg2); optional arg2 not currently used - added so parm easily implemented when added */
	inparm_len = func->str.len;
	if ((0 >= inparm_len) || (NAME_ENTRY_SZ < inparm_len) || !ISALPHA_ASCII(func->str.addr[0]))
	{
		DEBUG_ONLY(in_op_fnztrigger = FALSE;)
		/* We have a wrong-sized keyword */
		rts_error_csa(CSA_ARG(NULL) VARLSTCNT(3) ERR_ZTRIGINVACT, 1, 1);
	}
	if (0 > (index = namelook(ztrprm_index, ztrprm_names, func->str.addr, inparm_len)))	/* Note assignment */
	{
		DEBUG_ONLY(in_op_fnztrigger = FALSE);
		/* Specified parm was not found */
		rts_error_csa(CSA_ARG(NULL) VARLSTCNT(3) ERR_ZTRIGINVACT, 1, 1);
	}
	if ((0 < arg1->str.len) && (0 == arg1->str.addr[0]))
	{
		DEBUG_ONLY(in_op_fnztrigger = FALSE);
		/* 2nd parameter is invalid */
		rts_error_csa(CSA_ARG(NULL) VARLSTCNT(3) ERR_ZTRIGINVACT, 1, 2);
	}
	save_gd_header = gd_header;
	save_gv_target = gv_target;
	save_gv_cur_region = gv_cur_region;
	save_jnlpool = jnlpool;
	if (NULL != gv_currkey)
	{
		save_gv_currkey = (gv_key *)&save_currkey.key;
		MEMCPY_KEY(save_gv_currkey, gv_currkey);
		save_gv_last_subsc_null = TREF(gv_last_subsc_null);
		save_gv_some_subsc_null = TREF(gv_some_subsc_null);
	} else
		save_gv_currkey = NULL;
	util_out_print(NULL, RESET);	/* Make sure the util_out_print_gtmio() messages start at the beginning of the buffer
					 * in case there are any stale messages already in the buffer - for example rts_error
					 * output left over from a trapped error and hence not printed
					 */
	/* Any time gv_currkey is manipulated, the global variables - gv_last_subsc_null and gv_some_subsc_null should also be
	 * set appropriately. Although the BUILD_HASHT_*_CURRKEY_T macros maintain this property, the
	 * INITIAL_HASHT_ROOT_SEARCH_IF_NEEDED macro does not maintain the global variables. Since the macro is used in various
	 * other places (including update process and GT.M runtime), the implications of setting the global variables is not
	 * clear. So, reset gv_currkey and the global variables at the $ztrigger() entry as they will be restored before exiting
	 * from this function.
	 */
	if (gv_currkey)
	{
		gv_currkey->end = 0;
		gv_currkey->base[0] = KEY_DELIMITER;
	}
	TREF(gv_last_subsc_null) = TREF(gv_some_subsc_null) = FALSE;
	ESTABLISH(op_fnztrigger_ch);
	dollar_ztrigger_invoked = TRUE;			/* reset after use when the transaction commits, restarts or rollbacks */
	switch(ztrprm_data[index])
	{
		case ZTRP_FILE:
			if (RESTRICTED(trigger_mod))
				rts_error_csa(CSA_ARG(NULL) VARLSTCNT(3) ERR_RESTRICTEDOP, 1, "$ZTRIGGER(FILE)");
			/* If 2nd parameter is empty, do nothing (but dont issue error) */
			if (arg1->str.len)
			{
				if (MAX_FN_LEN < arg1->str.len)
					rts_error_csa(CSA_ARG(NULL) VARLSTCNT(1) ERR_FILENAMETOOLONG);
				/* The file name is in string pool so make a local copy in case GC happens */
				strncpy(filename, arg1->str.addr, arg1->str.len);
				filename_len = arg1->str.len;
				filename[filename_len] = '\0';
				failed = trigger_trgfile_tpwrap(filename, filename_len, TRUE);
			} else
				failed = FALSE;
			break;
		case ZTRP_ITEM:
			if (RESTRICTED(trigger_mod))
				rts_error_csa(CSA_ARG(NULL) VARLSTCNT(3) ERR_RESTRICTEDOP, 1, "$ZTRIGGER(ITEM)");
			/* If 2nd parameter is empty, do nothing (but dont issue error) */
			failed = (arg1->str.len) ? trigger_update(arg1) : FALSE;
			break;
		case ZTRP_SELECT:
			/* We changed the interface for item (above) to pass an mval because stringpool management sometimes
			 * moved strings ajusting the associated mval, is blinded to any other pointer, which than becomes stale,
			 * which causes nasty intermittant problems, as stp_gcol runs randoml-ish (compilation is likely simulus)
			 * and even when it does the old location may work well enough. We have some concern select could have
			 * similar issues, athough we are not aware of any symptoms. The called code does try to protect its working
			 * variables, but there is a window to perhaps create a tstart, which could have potential risk. */
			failed = trigger_select_tpwrap(arg1->str.addr, arg1->str.len, NULL, 0);
			break;
		default:
			assertpro(FALSE && ztrprm_data[index]);	/* Should never happen with checks above */
			break;
	}
	REVERT;
	RESTORE_ZTRIGGER_ENTRY_STATE;
	memcpy(dst, (failed ? &literal_zero : &literal_one), SIZEOF(mval));
	return;
}
#else /* !GTM_TRIGGER */

error_def(ERR_UNIMPLOP);

void op_fnztrigger(mval *func, mval *arg1, mval *arg2, mval *dst)
{
	rts_error_csa(CSA_ARG(NULL) VARLSTCNT(1) ERR_UNIMPLOP);
}
#endif
