1007 lines
44 KiB
C
1007 lines
44 KiB
C
/****************************************************************
|
|
* *
|
|
* Copyright 2010, 2013 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_stdlib.h"
|
|
#include "gtm_string.h"
|
|
#include "gtm_limits.h"
|
|
#include "gtm_stdio.h"
|
|
#include "gtm_unistd.h"
|
|
|
|
#include <errno.h>
|
|
|
|
#include "cmd_qlf.h"
|
|
#include "compiler.h"
|
|
#include "error.h"
|
|
#include <rtnhdr.h>
|
|
#include "stack_frame.h"
|
|
#include "lv_val.h"
|
|
#include "mv_stent.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 "jnl.h"
|
|
#include "gv_trigger.h"
|
|
#include "gtm_trigger.h"
|
|
#include "trigger.h"
|
|
#include "op.h"
|
|
#include "gtmio.h"
|
|
#include "stringpool.h"
|
|
#include "alias.h"
|
|
#include "urx.h"
|
|
#include "zbreak.h"
|
|
#include "gtm_text_alloc.h"
|
|
#include "hashtab_int4.h" /* needed for tp.h */
|
|
#include "buddy_list.h" /* needed for tp.h */
|
|
#include "tp.h"
|
|
#include "tp_frame.h"
|
|
#include "gvname_info.h"
|
|
#include "op_merge.h"
|
|
#include <auto_zlink.h>
|
|
#include "golevel.h"
|
|
#include "flush_jmp.h"
|
|
#include "dollar_zlevel.h"
|
|
#include "gtmimagename.h"
|
|
#include "wbox_test_init.h"
|
|
#include "have_crit.h"
|
|
#include "srcline.h"
|
|
#include "zshow.h"
|
|
#include "zwrite.h"
|
|
|
|
#ifdef GTM_TRIGGER
|
|
#define PREFIX_SPACE " "
|
|
#define ERROR_CAUSING_JUNK "XX XX XX XX"
|
|
#define NEWLINE "\n"
|
|
#define OBJECT_PARM " -OBJECT="
|
|
#define OBJECT_FTYPE DOTOBJ
|
|
#define NAMEOFRTN_PARM " -NAMEOFRTN="
|
|
#define S_CUTOFF 7
|
|
#define GTM_TRIGGER_SOURCE_NAME "GTM Trigger"
|
|
#define MAX_MKSTEMP_RETRIES 100
|
|
|
|
GBLREF boolean_t run_time;
|
|
GBLREF mv_stent *mv_chain;
|
|
GBLREF stack_frame *frame_pointer;
|
|
GBLREF uint4 trigger_name_cntr;
|
|
GBLREF int dollar_truth;
|
|
GBLREF mstr extnam_str;
|
|
GBLREF mval dollar_zsource;
|
|
GBLREF unsigned char *stackbase, *stacktop, *msp, *stackwarn;
|
|
GBLREF symval *curr_symval;
|
|
GBLREF int4 gtm_trigger_depth;
|
|
GBLREF int4 tstart_trigger_depth;
|
|
GBLREF mval dollar_etrap;
|
|
GBLREF mval dollar_ztrap;
|
|
GBLREF mval gtm_trigger_etrap;
|
|
GBLREF mstr *dollar_ztname;
|
|
GBLREF mval *dollar_ztdata;
|
|
GBLREF mval *dollar_ztoldval;
|
|
GBLREF mval *dollar_ztriggerop;
|
|
GBLREF mval *dollar_ztupdate;
|
|
GBLREF mval *dollar_ztvalue;
|
|
GBLREF boolean_t *ztvalue_changed_ptr;
|
|
GBLREF rtn_tabent *rtn_names, *rtn_names_end;
|
|
GBLREF boolean_t ztrap_explicit_null;
|
|
GBLREF int mumps_status;
|
|
GBLREF tp_frame *tp_pointer;
|
|
GBLREF boolean_t skip_dbtriggers; /* see gbldefs.c for description of this global */
|
|
GBLREF uint4 dollar_tlevel;
|
|
GBLREF symval *trigr_symval_list;
|
|
GBLREF trans_num local_tn;
|
|
GBLREF int merge_args;
|
|
GBLREF uint4 zwrtacindx;
|
|
GBLREF merge_glvn_ptr mglvnp;
|
|
GBLREF gvzwrite_datablk *gvzwrite_block;
|
|
GBLREF lvzwrite_datablk *lvzwrite_block;
|
|
GBLREF zshow_out *zwr_output;
|
|
GBLREF zwr_hash_table *zwrhtab;
|
|
#ifdef DEBUG
|
|
GBLREF ch_ret_type (*ch_at_trigger_init)();
|
|
GBLREF boolean_t donot_INVOKE_MUMTSTART;
|
|
/* For debugging purposes - since a stack unroll does not let us see past the current GTM invocation, knowing
|
|
* what these parms are can be the determining factor in debugging an issue -- knowing what gtm_trigger() is
|
|
* attempting. For that reason, these values are also saved/restored.
|
|
*/
|
|
GBLREF gv_trigger_t *gtm_trigdsc_last;
|
|
GBLREF gtm_trigger_parms *gtm_trigprm_last;
|
|
GBLREF mval dollar_ztwormhole;
|
|
#endif
|
|
|
|
LITREF mval literal_null;
|
|
LITREF char alphanumeric_table[];
|
|
LITREF int alphanumeric_table_len;
|
|
|
|
STATICDEF int4 gtm_trigger_comp_prev_run_time;
|
|
|
|
error_def(ERR_ASSERT);
|
|
error_def(ERR_GTMASSERT);
|
|
error_def(ERR_GTMASSERT2);
|
|
error_def(ERR_GTMCHECK);
|
|
error_def(ERR_LABELUNKNOWN);
|
|
error_def(ERR_MAXTRIGNEST);
|
|
error_def(ERR_MEMORY);
|
|
error_def(ERR_OUTOFSPACE);
|
|
error_def(ERR_REPEATERROR);
|
|
error_def(ERR_STACKCRIT);
|
|
error_def(ERR_STACKOFLOW);
|
|
error_def(ERR_SYSCALL);
|
|
error_def(ERR_TEXT);
|
|
error_def(ERR_TPRETRY);
|
|
error_def(ERR_TRIGCOMPFAIL);
|
|
error_def(ERR_TRIGNAMEUNIQ);
|
|
error_def(ERR_TRIGTLVLCHNG);
|
|
|
|
/* Macro to re-initialize a symval block that was on a previously-used free chain */
|
|
#define REINIT_SYMVAL_BLK(svb, prev) \
|
|
{ \
|
|
symval *ptr; \
|
|
lv_blk *lvbp; \
|
|
lv_val *lv_base, *lv_free; \
|
|
\
|
|
ptr = svb; \
|
|
assert(NULL == ptr->xnew_var_list); \
|
|
assert(NULL == ptr->xnew_ref_list); \
|
|
reinitialize_hashtab_mname(&ptr->h_symtab); \
|
|
ptr->lv_flist = NULL; \
|
|
ptr->tp_save_all = 0; \
|
|
ptr->alias_activity = FALSE; \
|
|
ptr->last_tab = (prev); \
|
|
ptr->symvlvl = prev->symvlvl + 1; \
|
|
/* The lv_blk chain can remain as is but need to reinit each block so no elements are "used" */ \
|
|
for (lvbp = ptr->lv_first_block; lvbp; lvbp = lvbp->next) \
|
|
{ /* Likely only one of these blocks (some few lvvals) but loop in case.. */ \
|
|
lv_base = (lv_val *)LV_BLK_GET_BASE(lvbp); \
|
|
lv_free = LV_BLK_GET_FREE(lvbp, lv_base); \
|
|
clrlen = INTCAST((char *)lv_free - (char *)lv_base); \
|
|
if (0 != clrlen) \
|
|
{ \
|
|
memset(lv_base, '\0', clrlen); \
|
|
lvbp->numUsed = 0; \
|
|
} \
|
|
} \
|
|
}
|
|
|
|
#if defined(__hpux) && defined(__hppa)
|
|
/* HPUX-HPPA (PA-RISC) has an undetermined space register corruption issue with nested triggers. This
|
|
* same issue would likely exist with call-ins except call-ins uses the slower longjmp() method to return.
|
|
* For this one platform, we adopt the longjmp() return method to avoid the problems.
|
|
*/
|
|
void ci_ret_code(void); /* Defined in gtmci.h but want to avoid pulling that into this module */
|
|
#else
|
|
/* All other platforms use this much faster direct return */
|
|
void gtm_levl_ret_code(void);
|
|
#endif
|
|
STATICFNDEF int gtm_trigger_invoke(void);
|
|
|
|
/* gtm_trigger - saves (some of) current environment, sets up new environment and drives a trigger.
|
|
|
|
Triggers are one of two places where compiled M code is driven while the C stack is not at a constant level.
|
|
The other place that does this is call-ins. Because this M code invocation needs to be separate from other
|
|
running code, a new running environment is setup with its own base frame to prevent random unwinding back
|
|
into earlier levels. All returns from the invoked generated code come back through gtm_trigger_invoke() with
|
|
the exception of error handling looking for a handler or not having an error "handled" (clearing $ECODE) can
|
|
just keep unwinding until all trigger levels are gone.
|
|
|
|
Trigger names:
|
|
|
|
Triggers have a base name set by MUPIP TRIGGER in the TRIGNAME hasht entry which is read by gv_trigger.c and
|
|
passed to us. If it collides with an existing trigger name, we add some suffixing to it (up to two chars)
|
|
and create it with that name.
|
|
|
|
Trigger compilation:
|
|
|
|
- When a trigger is presented to us for the first time, it needs to be compiled. We do this by writing it out
|
|
using a system generated unique name to a temp file and compiling it with the -NAMEOFRTN parameter which
|
|
sets the name of the routine different than the unique random object name.
|
|
- The file is then linked in and its address recorded so the compilation only happens once.
|
|
|
|
Trigger M stack format:
|
|
|
|
- First created frame is a "base frame" (created by base_frame). This frame is set up to return to us
|
|
(the caller) and has no backchain (old_frame_pointer is null). It also has the type SFT_TRIGR | SFT_COUNT
|
|
so it is a counted frame (it is important to be counted so the mv_stents we create don't try to backup to
|
|
a previous counted frame.
|
|
- The second created frame is for the trigger being executed. We fill in the stack_frame from the trigger
|
|
description and then let it rip by calling dm_start(). When the trigger returns through the base frame
|
|
which calls gtm_levl_ret_code and pulls the return address of our call to dm_start off the stack and
|
|
unwinds the appropriate saved regs, it returns back to us.
|
|
|
|
Error handling in a trigger frame:
|
|
|
|
- $ETRAP only. $ZTRAP is forbidden. Standard rules apply.
|
|
- Error handling does not return to the trigger base frame but unwinds the base frame doing a rollback if
|
|
necessary.
|
|
*/
|
|
|
|
CONDITION_HANDLER(gtm_trigger_complink_ch)
|
|
{ /* Condition handler for trigger compilation and link - be noisy but don't error out. Note that compilations do
|
|
* have their own handler but other errors are still possible. The primary use of this handler is (1) to remove
|
|
* the mv_stent we created and (2) most importantly to turn off the trigger_compile flag.
|
|
*/
|
|
START_CH;
|
|
TREF(trigger_compile) = FALSE;
|
|
run_time = gtm_trigger_comp_prev_run_time;
|
|
if (((unsigned char *)mv_chain == msp) && (MVST_MSAV == mv_chain->mv_st_type)
|
|
&& (&dollar_zsource == mv_chain->mv_st_cont.mvs_msav.addr))
|
|
{ /* Top mv_stent is one we pushed on there - get rid of it */
|
|
dollar_zsource = mv_chain->mv_st_cont.mvs_msav.v;
|
|
POP_MV_STENT();
|
|
}
|
|
if (DUMPABLE)
|
|
/* Treat fatal errors thusly for a ch that may give better diagnostics */
|
|
NEXTCH;
|
|
if (ERR_TRIGCOMPFAIL != SIGNAL)
|
|
{
|
|
/* Echo error message if not general trigger compile failure message (which gtm_trigger outputs anyway */
|
|
PRN_ERROR;
|
|
}
|
|
if ((SUCCESS == SEVERITY) || (INFO == SEVERITY))
|
|
{ /* Just keep going for non-error issues */
|
|
CONTINUE;
|
|
}
|
|
UNWIND(NULL, NULL);
|
|
}
|
|
|
|
CONDITION_HANDLER(gtm_trigger_ch)
|
|
{ /* Condition handler for trigger execution - This handler is pushed on first for a given trigger level, then
|
|
mdb_condition_handler is pushed on so will appearr multiple times as trigger depth increases. There is
|
|
always an mdb_condition_handler behind us for an earlier trigger level and we let it handle severe
|
|
errors for us as it gives better diagnostics (e.g. GTM_FATAL_ERROR dumps) in addition to the file core dump.
|
|
*/
|
|
START_CH;
|
|
DBGTRIGR((stderr, "gtm_trigger_ch: Failsafe condition cond handler entered with SIGNAL = %d\n", SIGNAL));
|
|
if (DUMPABLE)
|
|
/* Treat fatal errors thusly */
|
|
NEXTCH;
|
|
if ((SUCCESS == SEVERITY) || (INFO == SEVERITY))
|
|
{ /* Just keep going for non-error issues */
|
|
CONTINUE;
|
|
}
|
|
mumps_status = SIGNAL;
|
|
/* We are about to no longer have a trigger stack frame and thus re-enter trigger no-mans-land */
|
|
DEFER_INTERRUPTS(INTRPT_IN_TRIGGER_NOMANS_LAND);
|
|
gtm_trigger_depth--; /* Bypassing gtm_trigger_invoke() so do maint on depth indicator */
|
|
assert(0 <= gtm_trigger_depth);
|
|
/* Return back to gtm_trigger with error code */
|
|
UNWIND(NULL, NULL);
|
|
}
|
|
|
|
STATICFNDEF int gtm_trigger_invoke(void)
|
|
{ /* Invoke trigger M routine. Separate so error returns to gtm_trigger with proper retcode */
|
|
int rc;
|
|
|
|
ESTABLISH_RET(gtm_trigger_ch, mumps_status);
|
|
gtm_trigger_depth++;
|
|
DBGTRIGR((stderr, "gtm_trigger: Dispatching trigger at depth %d\n", gtm_trigger_depth));
|
|
assert(0 < gtm_trigger_depth);
|
|
assert(GTM_TRIGGER_DEPTH_MAX >= gtm_trigger_depth);
|
|
/* Allow interrupts to occur while the trigger is running */
|
|
ENABLE_INTERRUPTS(INTRPT_IN_TRIGGER_NOMANS_LAND);
|
|
rc = dm_start();
|
|
/* Now that we no longer have a trigger stack frame, we are back in trigger no-mans-land */
|
|
DEFER_INTERRUPTS(INTRPT_IN_TRIGGER_NOMANS_LAND);
|
|
gtm_trigger_depth--;
|
|
DBGTRIGR((stderr, "gtm_trigger: Trigger returns with rc %d\n", rc));
|
|
REVERT;
|
|
assert(frame_pointer->type & SFT_TRIGR);
|
|
assert(0 <= gtm_trigger_depth);
|
|
CHECKHIGHBOUND(ctxt);
|
|
CHECKLOWBOUND(ctxt);
|
|
CHECKHIGHBOUND(active_ch);
|
|
CHECKLOWBOUND(active_ch);
|
|
return rc;
|
|
}
|
|
|
|
int gtm_trigger_complink(gv_trigger_t *trigdsc, boolean_t dolink)
|
|
{
|
|
char rtnname[GTM_PATH_MAX + 1], rtnname_template[GTM_PATH_MAX + 1];
|
|
char objname[GTM_PATH_MAX + 1];
|
|
char zcomp_parms[(GTM_PATH_MAX * 2) + SIZEOF(mident_fixed) + SIZEOF(OBJECT_PARM) + SIZEOF(NAMEOFRTN_PARM)];
|
|
mstr save_zsource;
|
|
int rtnfd, rc, lenrtnname, lenobjname, len, alphnum_len, retry, save_errno;
|
|
char *mident_suffix_p1, *mident_suffix_p2, *mident_suffix_top, *namesub1, *namesub2, *zcomp_parms_ptr;
|
|
mval zlfile, zcompprm;
|
|
DCL_THREADGBL_ACCESS;
|
|
|
|
SETUP_THREADGBL_ACCESS;
|
|
DBGTRIGR_ONLY(memcpy(rtnname, trigdsc->rtn_desc.rt_name.addr, trigdsc->rtn_desc.rt_name.len));
|
|
DBGTRIGR_ONLY(rtnname[trigdsc->rtn_desc.rt_name.len] = 0);
|
|
DBGTRIGR((stderr, "gtm_trigger_complink: (Re)compiling trigger %s\n", rtnname));
|
|
ESTABLISH_RET(gtm_trigger_complink_ch, ((0 == error_condition) ? TREF(dollar_zcstatus) : error_condition ));
|
|
/* Verify there are 2 available chars for uniqueness */
|
|
assert((MAX_MIDENT_LEN - TRIGGER_NAME_RESERVED_SPACE) >= (trigdsc->rtn_desc.rt_name.len));
|
|
assert(NULL == trigdsc->rtn_desc.rt_adr);
|
|
gtm_trigger_comp_prev_run_time = run_time;
|
|
run_time = TRUE; /* Required by compiler */
|
|
/* Verify the routine name set by MUPIP TRIGGER and read by gvtr_db_read_hasht() is not in use */
|
|
if (NULL != find_rtn_hdr(&trigdsc->rtn_desc.rt_name))
|
|
{ /* Ooops .. need name to be more unique.. */
|
|
alphnum_len = alphanumeric_table_len; /* Local copy so can override it for whitebox test */
|
|
namesub1 = trigdsc->rtn_desc.rt_name.addr + trigdsc->rtn_desc.rt_name.len++;
|
|
/* If WBTEST_HELPOUT_TRIGNAMEUNIQ is defined, set alphnum_len to 1. This way, we make the maximum
|
|
* possible combinations for the uniqe trigger names to be 3 which is significantly lesser than
|
|
* the actual number of combinations (62x62 = 3844). For eg., if ^a is a global having triggers defined
|
|
* in 4 global directories, then the possible unique trigger names are a#1# ; a#1#A ; a#1#AA.
|
|
*/
|
|
GTM_WHITE_BOX_TEST(WBTEST_HELPOUT_TRIGNAMEUNIQ, alphnum_len, 1);
|
|
mident_suffix_top = (char *)alphanumeric_table + alphnum_len;
|
|
/* Phase 1. See if any single character can add uniqueness */
|
|
for (mident_suffix_p1 = (char *)alphanumeric_table; mident_suffix_p1 < mident_suffix_top; mident_suffix_p1++)
|
|
{
|
|
*namesub1 = *mident_suffix_p1;
|
|
if (NULL == find_rtn_hdr(&trigdsc->rtn_desc.rt_name))
|
|
break;
|
|
}
|
|
if (mident_suffix_p1 == mident_suffix_top)
|
|
{ /* Phase 2. Phase 1 could not find uniqueness .. Find it with 2 char variations */
|
|
namesub2 = trigdsc->rtn_desc.rt_name.addr + trigdsc->rtn_desc.rt_name.len++;
|
|
for (mident_suffix_p1 = (char *)alphanumeric_table; mident_suffix_p1 < mident_suffix_top;
|
|
mident_suffix_p1++)
|
|
{ /* First char loop */
|
|
for (mident_suffix_p2 = (char *)alphanumeric_table; mident_suffix_p2 < mident_suffix_top;
|
|
mident_suffix_p2++)
|
|
{ /* 2nd char loop */
|
|
*namesub1 = *mident_suffix_p1;
|
|
*namesub2 = *mident_suffix_p2;
|
|
if (NULL == find_rtn_hdr(&trigdsc->rtn_desc.rt_name))
|
|
{
|
|
mident_suffix_p1 = mident_suffix_top + 1; /* Break out of both loops */
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
if (mident_suffix_p1 == mident_suffix_top)
|
|
{ /* Phase 3: Punt */
|
|
assert(WBTEST_HELPOUT_TRIGNAMEUNIQ == gtm_white_box_test_case_number);
|
|
rts_error(VARLSTCNT(5) ERR_TRIGNAMEUNIQ, 3, trigdsc->rtn_desc.rt_name.len - 2,
|
|
trigdsc->rtn_desc.rt_name.addr, alphnum_len * alphnum_len);
|
|
}
|
|
}
|
|
}
|
|
/* Write trigger execute string out to temporary file and compile it */
|
|
assert(MAX_XECUTE_LEN >= trigdsc->xecute_str.str.len);
|
|
rc = SNPRINTF(rtnname_template, GTM_PATH_MAX, "%s/trgtmpXXXXXX", DEFAULT_GTM_TMP);
|
|
assert(0 < rc); /* Note rc is return code aka length - we expect a non-zero length */
|
|
assert(GTM_PATH_MAX >= rc);
|
|
/* The mkstemp() routine is known to bogus-fail for no apparent reason at all especially on AIX 6.1. In the event
|
|
* this shortcoming plagues other platforms as well, we add a low-cost retry wrapper.
|
|
*/
|
|
retry = MAX_MKSTEMP_RETRIES;
|
|
do
|
|
{
|
|
strcpy(rtnname, rtnname_template);
|
|
rtnfd = mkstemp(rtnname);
|
|
} while ((-1 == rtnfd) && (EEXIST == errno) && (0 < --retry));
|
|
if (-1 == rtnfd)
|
|
{
|
|
save_errno = errno;
|
|
assert(FALSE);
|
|
rts_error(VARLSTCNT(12) ERR_SYSCALL, 5, RTS_ERROR_LITERAL("mkstemp()"), CALLFROM,
|
|
ERR_TEXT, 2, RTS_ERROR_TEXT(rtnname), save_errno);
|
|
}
|
|
assert(0 < rtnfd); /* Verify file descriptor */
|
|
rc = 0;
|
|
# ifdef GEN_TRIGCOMPFAIL_ERROR
|
|
{ /* Used ONLY to generate an error in a trigger compile by adding some junk in a previous line */
|
|
DOWRITERC(rtnfd, ERROR_CAUSING_JUNK, strlen(ERROR_CAUSING_JUNK), rc); /* BYPASSOK */
|
|
if (0 != rc)
|
|
{
|
|
UNLINK(rtnname);
|
|
rts_error(VARLSTCNT(8) ERR_SYSCALL, 5, RTS_ERROR_LITERAL("write()"), CALLFROM, rc);
|
|
}
|
|
}
|
|
# endif
|
|
DOWRITERC(rtnfd, trigdsc->xecute_str.str.addr, trigdsc->xecute_str.str.len, rc);
|
|
if (0 != rc)
|
|
{
|
|
UNLINK(rtnname);
|
|
rts_error(VARLSTCNT(8) ERR_SYSCALL, 5, RTS_ERROR_LITERAL("write()"), CALLFROM, rc);
|
|
}
|
|
if (NULL == memchr(trigdsc->xecute_str.str.addr, '\n', trigdsc->xecute_str.str.len))
|
|
{
|
|
DOWRITERC(rtnfd, NEWLINE, strlen(NEWLINE), rc); /* BYPASSOK */
|
|
if (0 != rc)
|
|
{
|
|
UNLINK(rtnname);
|
|
rts_error(VARLSTCNT(8) ERR_SYSCALL, 5, RTS_ERROR_LITERAL("write()"), CALLFROM, rc);
|
|
}
|
|
}
|
|
CLOSEFILE(rtnfd, rc);
|
|
if (0 != rc)
|
|
{
|
|
UNLINK(rtnname);
|
|
rts_error(VARLSTCNT(8) ERR_SYSCALL, 5, RTS_ERROR_LITERAL("close()"), CALLFROM, rc);
|
|
}
|
|
assert(MAX_MIDENT_LEN > trigdsc->rtn_desc.rt_name.len);
|
|
zcomp_parms_ptr = zcomp_parms;
|
|
lenrtnname = STRLEN(rtnname);
|
|
MEMCPY_LIT(zcomp_parms_ptr, NAMEOFRTN_PARM);
|
|
zcomp_parms_ptr += STRLEN(NAMEOFRTN_PARM);
|
|
memcpy(zcomp_parms_ptr, trigdsc->rtn_desc.rt_name.addr, trigdsc->rtn_desc.rt_name.len);
|
|
zcomp_parms_ptr += trigdsc->rtn_desc.rt_name.len;
|
|
MEMCPY_LIT(zcomp_parms_ptr, OBJECT_PARM);
|
|
zcomp_parms_ptr += STRLEN(OBJECT_PARM);
|
|
strcpy(objname, rtnname); /* Make copy of rtnname to become object name */
|
|
strcat(objname, OBJECT_FTYPE); /* Turn into object file reference */
|
|
lenobjname = lenrtnname + STRLEN(OBJECT_FTYPE);
|
|
memcpy(zcomp_parms_ptr, objname, lenobjname);
|
|
zcomp_parms_ptr += lenobjname;
|
|
*zcomp_parms_ptr++ = ' ';
|
|
memcpy(zcomp_parms_ptr, rtnname, lenrtnname);
|
|
zcomp_parms_ptr += lenrtnname;
|
|
*zcomp_parms_ptr = '\0'; /* Null tail */
|
|
len = INTCAST(zcomp_parms_ptr - zcomp_parms);
|
|
assert((SIZEOF(zcomp_parms) - 1) > len); /* Verify no overflow */
|
|
zcompprm.mvtype = MV_STR;
|
|
zcompprm.str.addr = zcomp_parms;
|
|
zcompprm.str.len = len;
|
|
/* Backup dollar_zsource so trigger doesn't show */
|
|
PUSH_MV_STENT(MVST_MSAV);
|
|
mv_chain->mv_st_cont.mvs_msav.v = dollar_zsource;
|
|
mv_chain->mv_st_cont.mvs_msav.addr = &dollar_zsource;
|
|
TREF(trigger_compile) = TRUE; /* Set flag so compiler knows this is a special trigger compile */
|
|
op_zcompile(&zcompprm, TRUE); /* Compile but don't use $ZCOMPILE qualifiers */
|
|
TREF(trigger_compile) = FALSE; /* compile_source_file() establishes handler so always returns */
|
|
if (0 != TREF(dollar_zcstatus))
|
|
{ /* Someone err'd.. */
|
|
run_time = gtm_trigger_comp_prev_run_time;
|
|
REVERT;
|
|
UNLINK(objname); /* Remove files before return error */
|
|
UNLINK(rtnname);
|
|
return ERR_TRIGCOMPFAIL;
|
|
}
|
|
if (dolink)
|
|
{ /* Link is optional as MUPIP TRIGGER doesn't need link */
|
|
zlfile.mvtype = MV_STR;
|
|
zlfile.str.addr = objname;
|
|
zlfile.str.len = lenobjname;
|
|
/* Specifying literal_null for a second arg (as opposed to NULL or 0) allows us to specify
|
|
* linking the object file (no compilation or looking for source). The 2nd arg is parms for
|
|
* recompilation and is non-null in an explicit zlink which we need to emulate.
|
|
*/
|
|
# ifdef GEN_TRIGLINKFAIL_ERROR
|
|
UNLINK(objname); /* delete object before it can be used */
|
|
# endif
|
|
op_zlink(&zlfile, (mval *)&literal_null); /* need cast due to "extern const" attributes */
|
|
/* No return here if link fails for some reason */
|
|
trigdsc->rtn_desc.rt_adr = find_rtn_hdr(&trigdsc->rtn_desc.rt_name);
|
|
/* Verify can find routine we just put there. Catastrophic if not */
|
|
assertpro(NULL != trigdsc->rtn_desc.rt_adr);
|
|
/* Replace the randomly generated source name with the constant "GTM Trigger" */
|
|
trigdsc->rtn_desc.rt_adr->src_full_name.addr = GTM_TRIGGER_SOURCE_NAME;
|
|
trigdsc->rtn_desc.rt_adr->src_full_name.len = STRLEN(GTM_TRIGGER_SOURCE_NAME);
|
|
trigdsc->rtn_desc.rt_adr->trigr_handle = trigdsc; /* Back pointer to trig def */
|
|
}
|
|
if (MVST_MSAV == mv_chain->mv_st_type && &dollar_zsource == mv_chain->mv_st_cont.mvs_msav.addr)
|
|
{ /* Top mv_stent is one we pushed on there - restore dollar_zsource and get rid of it */
|
|
dollar_zsource = mv_chain->mv_st_cont.mvs_msav.v;
|
|
POP_MV_STENT();
|
|
} else
|
|
assert(FALSE); /* This mv_stent should be the one we just pushed */
|
|
/* Remove temporary files created */
|
|
UNLINK(objname); /* Delete the object file first since rtnname is the unique key */
|
|
UNLINK(rtnname); /* Delete the source file */
|
|
run_time = gtm_trigger_comp_prev_run_time;
|
|
REVERT;
|
|
return 0;
|
|
}
|
|
|
|
int gtm_trigger(gv_trigger_t *trigdsc, gtm_trigger_parms *trigprm)
|
|
{
|
|
mval *lvvalue;
|
|
lnr_tabent *lbl_offset_p;
|
|
uchar_ptr_t transfer_addr;
|
|
lv_val *lvval;
|
|
mname_entry *mne_p;
|
|
uint4 *indx_p;
|
|
ht_ent_mname *tabent;
|
|
boolean_t added;
|
|
int clrlen, rc, i, unwinds;
|
|
mval **lvvalarray;
|
|
mv_stent *mv_st_ent;
|
|
symval *new_symval;
|
|
uint4 dollar_tlevel_start;
|
|
stack_frame *fp, *fpprev;
|
|
DCL_THREADGBL_ACCESS;
|
|
|
|
SETUP_THREADGBL_ACCESS;
|
|
assert(!skip_dbtriggers); /* should not come here if triggers are not supposed to be invoked */
|
|
assert(trigdsc);
|
|
assert(trigprm);
|
|
assert((NULL != trigdsc->rtn_desc.rt_adr) || ((MV_STR & trigdsc->xecute_str.mvtype)
|
|
&& (0 != trigdsc->xecute_str.str.len)
|
|
&& (NULL != trigdsc->xecute_str.str.addr)));
|
|
assert(dollar_tlevel);
|
|
/* Determine if trigger needs to be compiled */
|
|
if (NULL == trigdsc->rtn_desc.rt_adr)
|
|
{ /* No routine hdr addr exists. Need to do compile */
|
|
if (0 != gtm_trigger_complink(trigdsc, TRUE))
|
|
{
|
|
PRN_ERROR; /* Leave record of what error caused the compilation failure if any */
|
|
rts_error(VARLSTCNT(4) ERR_TRIGCOMPFAIL, 2, trigdsc->rtn_desc.rt_name.len, trigdsc->rtn_desc.rt_name.addr);
|
|
}
|
|
}
|
|
assert(trigdsc->rtn_desc.rt_adr);
|
|
assert(trigdsc->rtn_desc.rt_adr == CURRENT_RHEAD_ADR(trigdsc->rtn_desc.rt_adr));
|
|
/* Setup trigger environment stack frame(s) for execution */
|
|
if (!(frame_pointer->type & SFT_TRIGR))
|
|
{ /* Create new trigger base frame first that back-stops stack unrolling and return to us */
|
|
if (GTM_TRIGGER_DEPTH_MAX < (gtm_trigger_depth + 1)) /* Verify we won't nest too deep */
|
|
rts_error(VARLSTCNT(3) ERR_MAXTRIGNEST, 1, GTM_TRIGGER_DEPTH_MAX);
|
|
DBGTRIGR((stderr, "gtm_trigger: Invoking new trigger at frame_pointer 0x%016lx ctxt value: 0x%016lx\n",
|
|
frame_pointer, ctxt));
|
|
/* Protect against interrupts while we have only a trigger base frame on the stack */
|
|
DEFER_INTERRUPTS(INTRPT_IN_TRIGGER_NOMANS_LAND);
|
|
/* The current frame invoked a trigger. We cannot return to it for a TP restart or other reason unless
|
|
* either the total operation (including trigger) succeeds and we unwind normally or unless the mpc is reset
|
|
* (like what happens in various error or restart conditions) because right now it returns to where a database
|
|
* command (KILL, SET or ZTRIGGER) was entered. Set flag in the frame to prevent MUM_TSTART unless the frame gets
|
|
* reset.
|
|
*/
|
|
frame_pointer->flags |= SFF_IMPLTSTART_CALLD; /* Do not return to this frame via MUM_TSTART */
|
|
DBGTRIGR((stderr, "gtm_trigger: Setting SFF_IMPLTSTART_CALLD in frame 0x"lvaddr"\n", frame_pointer));
|
|
base_frame(trigdsc->rtn_desc.rt_adr);
|
|
/* Finish base frame initialization - reset mpc/context to return to us without unwinding base frame */
|
|
frame_pointer->type |= SFT_TRIGR;
|
|
# if defined(__hpux) && defined(__hppa)
|
|
/* For HPUX-HPPA (PA-RISC), we use longjmp() to return to gtm_trigger() to avoid some some space register
|
|
* corruption issues. Use call-ins already existing mechanism for doing this. Although we no longer support
|
|
* HPUX-HPPA for triggers due to some unlocated space register error, this code (effectively always ifdef'd
|
|
* out) left in in case it gets resurrected in the future (01/2010 SE).
|
|
*/
|
|
frame_pointer->mpc = CODE_ADDRESS(ci_ret_code);
|
|
frame_pointer->ctxt = GTM_CONTEXT(ci_ret_code);
|
|
# else
|
|
frame_pointer->mpc = CODE_ADDRESS(gtm_levl_ret_code);
|
|
frame_pointer->ctxt = GTM_CONTEXT(gtm_levl_ret_code);
|
|
# endif
|
|
/* This base stack frame is also where we save environmental info for all triggers invoked at this stack level.
|
|
* Subsequent triggers fired at this level in this trigger invocation need only reinitialize a few things but
|
|
* can avoid "the big save".
|
|
*/
|
|
if (NULL == trigr_symval_list)
|
|
{ /* No available symvals for use with this trigger, create one */
|
|
symbinit(); /* Initialize a symbol table the trigger will use */
|
|
curr_symval->trigr_symval = TRUE; /* Mark as trigger symval so will be saved not decommissioned */
|
|
} else
|
|
{ /* Trigger symval is available for reuse */
|
|
new_symval = trigr_symval_list;
|
|
assert(new_symval->trigr_symval);
|
|
trigr_symval_list = new_symval->last_tab; /* dequeue new curr_symval from list */
|
|
REINIT_SYMVAL_BLK(new_symval, curr_symval);
|
|
curr_symval = new_symval;
|
|
PUSH_MV_STENT(MVST_STAB);
|
|
mv_chain->mv_st_cont.mvs_stab = new_symval; /* So unw_mv_ent() can requeue it for later use */
|
|
}
|
|
/* Push our trigger environment save mv_stent onto the chain */
|
|
PUSH_MV_STENT(MVST_TRIGR);
|
|
mv_st_ent = mv_chain;
|
|
/* Initialize the mv_stent elements processed by stp_gcol which can be called by either op_gvsavtarg() or
|
|
* by the extnam saving code below. This initialization keeps stp_gcol - should it be called - from attempting
|
|
* to process unset fields filled with garbage in them as valid mstr address/length pairs.
|
|
*/
|
|
mv_st_ent->mv_st_cont.mvs_trigr.savtarg.str.len = 0;
|
|
mv_st_ent->mv_st_cont.mvs_trigr.savextref.len = 0;
|
|
mv_st_ent->mv_st_cont.mvs_trigr.dollar_etrap_save.str.len = 0;
|
|
mv_st_ent->mv_st_cont.mvs_trigr.dollar_ztrap_save.str.len = 0;
|
|
mv_st_ent->mv_st_cont.mvs_trigr.saved_dollar_truth = dollar_truth;
|
|
op_gvsavtarg(&mv_st_ent->mv_st_cont.mvs_trigr.savtarg);
|
|
if (extnam_str.len)
|
|
{
|
|
ENSURE_STP_FREE_SPACE(extnam_str.len);
|
|
mv_st_ent->mv_st_cont.mvs_trigr.savextref.addr = (char *)stringpool.free;
|
|
memcpy(mv_st_ent->mv_st_cont.mvs_trigr.savextref.addr, extnam_str.addr, extnam_str.len);
|
|
stringpool.free += extnam_str.len;
|
|
assert(stringpool.free <= stringpool.top);
|
|
}
|
|
mv_st_ent->mv_st_cont.mvs_trigr.savextref.len = extnam_str.len;
|
|
mv_st_ent->mv_st_cont.mvs_trigr.ztname_save = dollar_ztname;
|
|
mv_st_ent->mv_st_cont.mvs_trigr.ztdata_save = dollar_ztdata;
|
|
mv_st_ent->mv_st_cont.mvs_trigr.ztoldval_save = dollar_ztoldval;
|
|
mv_st_ent->mv_st_cont.mvs_trigr.ztriggerop_save = dollar_ztriggerop;
|
|
mv_st_ent->mv_st_cont.mvs_trigr.ztupdate_save = dollar_ztupdate;
|
|
mv_st_ent->mv_st_cont.mvs_trigr.ztvalue_save = dollar_ztvalue;
|
|
mv_st_ent->mv_st_cont.mvs_trigr.ztvalue_changed_ptr = ztvalue_changed_ptr;
|
|
# ifdef DEBUG
|
|
/* In a debug process, these fields give clues of what trigger we are working on */
|
|
mv_st_ent->mv_st_cont.mvs_trigr.gtm_trigdsc_last_save = trigdsc;
|
|
mv_st_ent->mv_st_cont.mvs_trigr.gtm_trigprm_last_save = trigprm;
|
|
# endif
|
|
/* If this is a spanning node update, a spanning node condition handler may be at the front of the line. However,
|
|
* the condition handler just behind it should be either mdb_condition_handler or ch_at_trigger_init.
|
|
*/
|
|
assert(((0 == gtm_trigger_depth)
|
|
&& (((ch_at_trigger_init == ctxt->ch)
|
|
|| ((ch_at_trigger_init == (ctxt - 1)->ch)
|
|
&& ((&gvcst_put_ch == ctxt->ch) || (&gvcst_kill_ch == ctxt->ch))))))
|
|
|| ((0 < gtm_trigger_depth)
|
|
&& (((&mdb_condition_handler == ctxt->ch)
|
|
|| ((&mdb_condition_handler == (ctxt - 1)->ch)
|
|
&& ((&gvcst_put_ch == ctxt->ch) || (&gvcst_kill_ch == ctxt->ch)))))));
|
|
mv_st_ent->mv_st_cont.mvs_trigr.ctxt_save = ctxt;
|
|
mv_st_ent->mv_st_cont.mvs_trigr.gtm_trigger_depth_save = gtm_trigger_depth;
|
|
if (0 == gtm_trigger_depth)
|
|
{ /* Only back up $*trap settings when initiating the first trigger level */
|
|
mv_st_ent->mv_st_cont.mvs_trigr.dollar_etrap_save = dollar_etrap;
|
|
mv_st_ent->mv_st_cont.mvs_trigr.dollar_ztrap_save = dollar_ztrap;
|
|
mv_st_ent->mv_st_cont.mvs_trigr.ztrap_explicit_null_save = ztrap_explicit_null;
|
|
dollar_ztrap.str.len = 0;
|
|
ztrap_explicit_null = FALSE;
|
|
if (NULL != gtm_trigger_etrap.str.addr)
|
|
/* An etrap was defined for the trigger environment - Else existing $etrap persists */
|
|
dollar_etrap = gtm_trigger_etrap;
|
|
}
|
|
mv_st_ent->mv_st_cont.mvs_trigr.mumps_status_save = mumps_status;
|
|
mv_st_ent->mv_st_cont.mvs_trigr.run_time_save = run_time;
|
|
/* See if a MERGE launched the trigger. If yes, save some state so ZWRITE, ZSHOW and/or MERGE can be
|
|
* run in the trigger we dispatch. */
|
|
if ((0 != merge_args) || TREF(in_zwrite))
|
|
PUSH_MVST_MRGZWRSV;
|
|
mumps_status = 0;
|
|
run_time = TRUE; /* Previous value saved just above restored when frame pops */
|
|
} else
|
|
{ /* Trigger base frame exists so reinitialize the symbol table for new trigger invocation */
|
|
REINIT_SYMVAL_BLK(curr_symval, curr_symval->last_tab);
|
|
/* Locate the MVST_TRIGR mv_stent containing the backed up values. Some of those values need
|
|
* to be restored so the 2nd trigger has the same environment as the previous trigger at this level
|
|
*/
|
|
for (mv_st_ent = mv_chain;
|
|
(NULL != mv_st_ent) && (MVST_TRIGR != mv_st_ent->mv_st_type);
|
|
mv_st_ent = (mv_stent *)(mv_st_ent->mv_st_next + (char *)mv_st_ent))
|
|
;
|
|
assert(NULL != mv_st_ent);
|
|
assert((char *)mv_st_ent < (char *)frame_pointer); /* Ensure mv_stent associated this trigger frame */
|
|
/* Reinit backed up values from the trigger environment backup */
|
|
dollar_truth = mv_st_ent->mv_st_cont.mvs_trigr.saved_dollar_truth;
|
|
op_gvrectarg(&mv_st_ent->mv_st_cont.mvs_trigr.savtarg);
|
|
extnam_str.len = mv_st_ent->mv_st_cont.mvs_trigr.savextref.len;
|
|
if (extnam_str.len)
|
|
memcpy(extnam_str.addr, mv_st_ent->mv_st_cont.mvs_trigr.savextref.addr, extnam_str.len);
|
|
mumps_status = 0;
|
|
assert(run_time);
|
|
/* Note we do not reset the handlers for parallel triggers - set one time only when enter first level
|
|
* trigger. After that, whatever happens in trigger world, stays in trigger world.
|
|
*/
|
|
}
|
|
assert(frame_pointer->type & SFT_TRIGR);
|
|
# ifdef DEBUG
|
|
gtm_trigdsc_last = trigdsc;
|
|
gtm_trigprm_last = trigprm;
|
|
# endif
|
|
/* Set new value of trigger ISVs. Previous values already saved in trigger base frame */
|
|
dollar_ztname = &trigdsc->rtn_desc.rt_name;
|
|
dollar_ztdata = (mval *)trigprm->ztdata_new;
|
|
dollar_ztoldval = trigprm->ztoldval_new;
|
|
dollar_ztriggerop = (mval *)trigprm->ztriggerop_new;
|
|
dollar_ztupdate = trigprm->ztupdate_new;
|
|
dollar_ztvalue = trigprm->ztvalue_new;
|
|
ztvalue_changed_ptr = &trigprm->ztvalue_changed;
|
|
/* Set values associated with trigger into symbol table */
|
|
lvvalarray = trigprm->lvvalarray;
|
|
for (i = 0, mne_p = trigdsc->lvnamearray, indx_p = trigdsc->lvindexarray;
|
|
i < trigdsc->numlvsubs; ++i, ++mne_p, ++indx_p)
|
|
{ /* Once thru for each subscript we are to set */
|
|
lvvalue = lvvalarray[*indx_p]; /* Locate mval that contains value */
|
|
assert(NULL != lvvalue);
|
|
assert(MV_DEFINED(lvvalue)); /* No sense in defining the undefined */
|
|
lvval = lv_getslot(curr_symval); /* Allocate an lvval to put into symbol table */
|
|
LVVAL_INIT(lvval, curr_symval);
|
|
lvval->v = *lvvalue; /* Copy mval into lvval */
|
|
added = add_hashtab_mname_symval(&curr_symval->h_symtab, mne_p, lvval, &tabent);
|
|
assert(added);
|
|
assert(NULL != tabent);
|
|
}
|
|
/* While the routine header is available in trigdsc, we also need the <null> label address associated with
|
|
* the first (and only) line of code.
|
|
*/
|
|
lbl_offset_p = LNRTAB_ADR(trigdsc->rtn_desc.rt_adr);
|
|
transfer_addr = (uchar_ptr_t)LINE_NUMBER_ADDR(trigdsc->rtn_desc.rt_adr, lbl_offset_p);
|
|
/* Create new stack frame for invoked trigger in same fashion as gtm_init_env() creates its 2ndary frame */
|
|
# ifdef HAS_LITERAL_SECT
|
|
new_stack_frame(trigdsc->rtn_desc.rt_adr, (unsigned char *)LINKAGE_ADR(trigdsc->rtn_desc.rt_adr), transfer_addr);
|
|
# else
|
|
/* Any platform that does not follow pv-based linkage model either
|
|
* (1) uses the following calculation to determine the context pointer value, or
|
|
* (2) doesn't need a context pointer
|
|
*/
|
|
new_stack_frame(trigdsc->rtn_desc.rt_adr, PTEXT_ADR(trigdsc->rtn_desc.rt_adr), transfer_addr);
|
|
# endif
|
|
dollar_tlevel_start = dollar_tlevel;
|
|
assert(gv_target->gd_csa == cs_addrs);
|
|
gv_target->trig_local_tn = local_tn; /* Record trigger being driven for this global */
|
|
/* Invoke trigger generated code */
|
|
rc = gtm_trigger_invoke();
|
|
if (1 == rc)
|
|
{ /* Normal return code (from dm_start). Check if TP has been unwound or not */
|
|
assert(dollar_tlevel <= dollar_tlevel_start); /* Bigger would be quite the surprise */
|
|
if (dollar_tlevel < dollar_tlevel_start)
|
|
{ /* Our TP level was unwound during the trigger so throw an error */
|
|
DBGTRIGR((stderr, "gtm_trigger: $TLEVEL less than at start - throwing TRIGTLVLCHNG\n"));
|
|
gtm_trigger_fini(TRUE, FALSE); /* dump this trigger level */
|
|
rts_error(VARLSTCNT(4) ERR_TRIGTLVLCHNG, 2, trigdsc->rtn_desc.rt_name.len,
|
|
trigdsc->rtn_desc.rt_name.addr);
|
|
}
|
|
rc = 0; /* Be polite and return 0 for the (hopefully common) success case */
|
|
} else if (ERR_TPRETRY == rc)
|
|
{ /* We are restarting the entire transaction. There are two possibilities here:
|
|
* 1) This is a nested trigger level in which case we need to unwind further or
|
|
* the outer trigger level was created by M code. If either is true, just
|
|
* rethrow the TPRETRY error.
|
|
* 2) This is the outer trigger and the call to op_tstart() was done by our caller.
|
|
* In this case, we just return to our caller with a code signifying they need
|
|
* to restart the implied transaction.
|
|
*/
|
|
assert(dollar_tlevel && (tstart_trigger_depth <= gtm_trigger_depth));
|
|
if ((tstart_trigger_depth < gtm_trigger_depth) || !tp_pointer->implicit_tstart || !tp_pointer->implicit_trigger)
|
|
{ /* Unwind a trigger level to restart level or to next trigger boundary */
|
|
gtm_trigger_fini(FALSE, FALSE); /* Get rid of this trigger level - we won't be returning */
|
|
DBGTRIGR((stderr, "gtm_trigger: dm_start returned rethrow code - rethrowing ERR_TPRETRY\n"));
|
|
/* The bottommost mdb_condition handler better not be catching this restart if we did an implicit
|
|
* tstart. mdb_condition_handler will try to unwind further, and the process will inadvertently exit.
|
|
*/
|
|
assert((&mdb_condition_handler != ch_at_trigger_init)
|
|
|| ((&mdb_condition_handler == ctxt->ch) && (&mdb_condition_handler == chnd[1].ch)
|
|
&& (!tp_pointer->implicit_tstart || (&chnd[1] < ctxt))));
|
|
INVOKE_RESTART;
|
|
} else
|
|
{ /* It is possible we are restarting a transaction that never got around to creating a base
|
|
* frame yet the implicit TStart was done. So if there is no trigger base frame, do not
|
|
* run gtm_trigger_fini() but instead do the one piece of cleanup it does that we still need.
|
|
*/
|
|
assert(donot_INVOKE_MUMTSTART);
|
|
if (SFT_TRIGR & frame_pointer->type)
|
|
{ /* Normal case when TP restart unwinding back to implicit beginning */
|
|
gtm_trigger_fini(FALSE, FALSE);
|
|
DBGTRIGR((stderr, "gtm_trigger: dm_start returned rethrow code - returning to gvcst_<caller>\n"));
|
|
} else
|
|
{ /* Unusual case of trigger that died in no-mans-land before trigger base frame established.
|
|
* Remove the "do not return to me" flag only on non-error unwinds */
|
|
assert(tp_pointer->implicit_tstart);
|
|
assert(SFF_IMPLTSTART_CALLD & frame_pointer->flags);
|
|
frame_pointer->flags &= SFF_IMPLTSTART_CALLD_OFF;
|
|
DBGTRIGR((stderr, "gtm_trigger: turning off SFF_IMPLTSTART_CALLD (1) in frame 0x"lvaddr"\n",
|
|
frame_pointer));
|
|
DBGTRIGR((stderr, "gtm_trigger: unwinding no-base-frame trigger for TP restart\n"));
|
|
}
|
|
}
|
|
/* Fall out and return ERR_TPRETRY to caller */
|
|
} else if (0 == rc)
|
|
/* We should never get a return code of 0. This would be out-of-design and a signal that something
|
|
* is quite broken. We cannot "rethrow" outside the trigger because it was not initially an error so
|
|
* mdb_condition_handler would have no record of it (rethrown errors must have originally occurred in
|
|
* or to be RE-thrown) and assert fail at best.
|
|
*/
|
|
GTMASSERT;
|
|
else
|
|
{ /* We have an unexpected return code due to some error during execution of the trigger that tripped
|
|
* gtm_trigger's safety handler (i.e. an error occurred in mdb_condition_handler() established by
|
|
* dm_start(). Since we are going to unwind the trigger frame and rethrow the error, we also have
|
|
* to unwind all the stack frames on top of the trigger frame. Figure out how many frames that is,
|
|
* unwind them all plus the trigger base frame before rethrowing the error.
|
|
*/
|
|
for (unwinds = 0, fp = frame_pointer; (NULL != fp) && !(SFT_TRIGR & fp->type); fp = fp->old_frame_pointer)
|
|
unwinds++;
|
|
assert((NULL != fp) && (SFT_TRIGR & fp->type));
|
|
GOFRAMES(unwinds, TRUE, FALSE);
|
|
assert((NULL != frame_pointer) && !(SFT_TRIGR & frame_pointer->type));
|
|
DBGTRIGR((stderr, "gtm_trigger: Unsupported return code (%d) - unwound %d frames and now rethrowing error\n",
|
|
rc, unwinds));
|
|
rts_error(VARLSTCNT(1) ERR_REPEATERROR);
|
|
}
|
|
return rc;
|
|
}
|
|
|
|
/* Unwind a trigger level, pop all the associated mv_stents and dispense with the base frame.
|
|
* Note that this unwind restores the condition handler stack pointer and correct gtm_trigger_depth value in
|
|
* order to maintain these values properly in the event of a major unwind. This routine is THE routine to use to unwind
|
|
* trigger base frames in all cases due to the cleanups it takes care of.
|
|
*/
|
|
void gtm_trigger_fini(boolean_t forced_unwind, boolean_t fromzgoto)
|
|
{
|
|
/* Would normally be an assert but potential frame stack damage so severe and resulting debug difficulty that we
|
|
* assertpro() instead.
|
|
*/
|
|
assertpro(frame_pointer->type & SFT_TRIGR);
|
|
/* Unwind the trigger base frame */
|
|
op_unwind();
|
|
/* restore frame_pointer stored at msp (see base_frame.c) */
|
|
frame_pointer = *(stack_frame**)msp;
|
|
msp += SIZEOF(stack_frame *); /* Remove frame save pointer from stack */
|
|
if (!forced_unwind)
|
|
{ /* Remove the "do not return to me" flag only on non-error unwinds. Note this flag may have already been
|
|
* turned off by an earlier tp_restart if this is not an implicit_tstart situation.
|
|
*/
|
|
assert(!tp_pointer->implicit_tstart || (SFF_IMPLTSTART_CALLD & frame_pointer->flags));
|
|
frame_pointer->flags &= SFF_IMPLTSTART_CALLD_OFF;
|
|
DBGTRIGR((stderr, "gtm_trigger_fini: turning off SFF_IMPLTSTART_CALLD (2) in frame 0x"lvaddr"\n", frame_pointer));
|
|
} else
|
|
{ /* Error unwind, make sure certain cleanups are done */
|
|
# ifdef DEBUG
|
|
assert(!dollar_tlevel || (tstart_trigger_depth <= gtm_trigger_depth));
|
|
if (tstart_trigger_depth == gtm_trigger_depth) /* Unwinding gvcst_put() so get rid of flag it potentially set */
|
|
donot_INVOKE_MUMTSTART = FALSE;
|
|
# endif
|
|
if (tp_pointer)
|
|
{ /* This TP transaction can never be allowed to commit if this is the first trigger
|
|
* (see comment in tp_frame.h against "cannot_commit" field for details).
|
|
*/
|
|
if ((0 == gtm_trigger_depth) && !fromzgoto)
|
|
{
|
|
DBGTRIGR((stderr, "gtm_trigger: cannot_commit flag set to TRUE\n"))
|
|
tp_pointer->cannot_commit = TRUE;
|
|
}
|
|
if ((tp_pointer->fp == frame_pointer) && tp_pointer->implicit_tstart)
|
|
OP_TROLLBACK(-1); /* We just unrolled the implicitly started TSTART so unroll what it did */
|
|
}
|
|
}
|
|
DBGTRIGR((stderr, "gtm_trigger: Unwound to trigger invoking frame: frame_pointer 0x%016lx ctxt value: 0x%016lx\n",
|
|
frame_pointer, ctxt));
|
|
/* Re-allow interruptions now that our base frame is gone */
|
|
if (forced_unwind)
|
|
{ /* Since we are being force-unwound, we don't know the state of things except that it it should be either
|
|
* the state we set it to or the ok-to-interrupt state. Assert that and if we are changing the state,
|
|
* be sure to run the deferred handler.
|
|
*/
|
|
assert((INTRPT_IN_TRIGGER_NOMANS_LAND == intrpt_ok_state) || (INTRPT_OK_TO_INTERRUPT == intrpt_ok_state));
|
|
ENABLE_INTERRUPTS(intrpt_ok_state);
|
|
} else
|
|
{ /* Normal unwind should be ok with this macro */
|
|
ENABLE_INTERRUPTS(INTRPT_IN_TRIGGER_NOMANS_LAND);
|
|
}
|
|
}
|
|
|
|
/* Routine to eliminate the zlinked trigger code for a given trigger about to be deleted. Operations performed
|
|
* differ depending on platform type (shared binary or not).
|
|
*/
|
|
void gtm_trigger_cleanup(gv_trigger_t *trigdsc)
|
|
{
|
|
rtn_tabent *rbot, *mid, *rtop;
|
|
mident *rtnname;
|
|
rhdtyp *rtnhdr;
|
|
textElem *telem;
|
|
int comp, size;
|
|
stack_frame *fp, *fpprev;
|
|
mname_entry key;
|
|
ht_ent_mname *tabent;
|
|
routine_source *src_tbl;
|
|
DCL_THREADGBL_ACCESS;
|
|
|
|
SETUP_THREADGBL_ACCESS;
|
|
/* First thing to do is release trigger source field if it exists */
|
|
if (0 < trigdsc->xecute_str.str.len)
|
|
{
|
|
free(trigdsc->xecute_str.str.addr);
|
|
trigdsc->xecute_str.str.len = 0;
|
|
trigdsc->xecute_str.str.addr = NULL;
|
|
}
|
|
/* Next thing to do is find the routine header in the rtn_names list so we can remove it. */
|
|
rtnname = &trigdsc->rtn_desc.rt_name;
|
|
rtnhdr = trigdsc->rtn_desc.rt_adr;
|
|
rbot = rtn_names;
|
|
rtop = rtn_names_end;
|
|
for (;;)
|
|
{ /* See if routine exists in list via a binary search which reverts to serial
|
|
search when # of items drops below the threshold S_CUTOFF.
|
|
*/
|
|
if ((rtop - rbot) < S_CUTOFF)
|
|
{
|
|
comp = -1;
|
|
for (mid = rbot; mid <= rtop ; mid++)
|
|
{
|
|
MIDENT_CMP(&mid->rt_name, rtnname, comp);
|
|
if (0 == comp)
|
|
break;
|
|
assertpro(0 >= comp); /* Routine should be found */
|
|
}
|
|
break;
|
|
} else
|
|
{ mid = rbot + (rtop - rbot)/2;
|
|
MIDENT_CMP(&mid->rt_name, rtnname, comp);
|
|
if (0 == comp)
|
|
break;
|
|
else if (0 > comp)
|
|
{
|
|
rbot = mid + 1;
|
|
continue;
|
|
} else
|
|
{
|
|
rtop = mid - 1;
|
|
continue;
|
|
}
|
|
}
|
|
}
|
|
# ifdef DEBUG
|
|
assert(rtnhdr == mid->rt_adr);
|
|
/* Verify trigger routine we want to remove is not currently active. If it is, we need to assert fail.
|
|
* Triggers are not like regular routines since they should only ever be referenced from the stack during a
|
|
* transaction. Likewise, we should only ever load the triggers as the first action in that transaction.
|
|
*/
|
|
for (fp = frame_pointer; fp ; fp = fpprev)
|
|
{
|
|
fpprev = fp->old_frame_pointer;
|
|
# ifdef GTM_TRIGGER
|
|
if (NULL != fpprev && SFT_TRIGR & fpprev->type)
|
|
fpprev = *(stack_frame **)(fpprev + 1);
|
|
# endif
|
|
/* Only one possible version of a trigger routine */
|
|
assert(USHBIN_ONLY(NULL) NON_USHBIN_ONLY(fp->rvector) == OLD_RHEAD_ADR(CURRENT_RHEAD_ADR(fp->rvector)));
|
|
assert(fp->rvector != rtnhdr);
|
|
}
|
|
# endif
|
|
/* Remove break points in this routine before rmv from rtntbl */
|
|
zr_remove(rtnhdr, BREAKMSG);
|
|
/* Release any $TEXT() info this trigger has loaded */
|
|
if (NULL != (TREF(rt_name_tbl)).base)
|
|
{
|
|
key.var_name = mid->rt_name;
|
|
COMPUTE_HASH_MNAME(&key);
|
|
if (NULL != (tabent = lookup_hashtab_mname(TADR(rt_name_tbl), &key))) /* note assignment */
|
|
{ /* We have a hash entry. Whether it has a value or not, it has a key name (if this is
|
|
* the first time this trigger is being deleted) that may be pointing into the routine we
|
|
* are about to remove. Migrate this key name to the stringpool.
|
|
*/
|
|
s2pool(&tabent->key.var_name);
|
|
if (NULL != tabent->value)
|
|
{ /* Has value, release the source. Entries and source are malloc'd in two blocks on UNIX */
|
|
src_tbl = (routine_source *)tabent->value;
|
|
if (NULL != src_tbl->srcbuff)
|
|
free(src_tbl->srcbuff);
|
|
free(src_tbl);
|
|
tabent->value = NULL;
|
|
}
|
|
}
|
|
}
|
|
/* Remove the routine from the rtn_table */
|
|
size = INTCAST((char *)rtn_names_end - (char *)mid);
|
|
if (0 < size)
|
|
memmove((char *)mid, (char *)(mid + 1), size); /* Remove this routine name from sorted table */
|
|
rtn_names_end--;
|
|
urx_remove(rtnhdr); /* Remove any unresolved entries */
|
|
# ifdef USHBIN_SUPPORTED
|
|
stp_move((char *)rtnhdr->literal_text_adr,
|
|
(char *)(rtnhdr->literal_text_adr + rtnhdr->literal_text_len));
|
|
GTM_TEXT_FREE(rtnhdr->ptext_adr); /* R/O releasable section */
|
|
free(RW_REL_START_ADR(rtnhdr)); /* R/W releasable section part 1 */
|
|
free(rtnhdr->linkage_adr); /* R/W releasable section part 2 */
|
|
free(rtnhdr->labtab_adr); /* Usually non-releasable but triggers don't have labels so
|
|
* this is just cleaning up a dangling null malloc
|
|
*/
|
|
free(rtnhdr);
|
|
# else
|
|
# if (!defined(__linux__) && !defined(__CYGWIN__)) || !defined(__i386) || !defined(COMP_GTA)
|
|
# error Unsupported NON-USHBIN platform
|
|
# endif
|
|
/* For a non-shared binary platform we need to get an approximate addr range for stp_move. This is not
|
|
* done when a routine is replaced on these platforms but in this case we are able to due to the isolated
|
|
* environment if we only take the precautions of migrating potential literal text which may have been
|
|
* pointed to by any set environment variables.
|
|
* In this format, the only platform we support currently is Linux-x86 (i386) which uses GTM_TEXT_ALLOC
|
|
* to allocate special storage for it to put executable code in. We can access the storage header for
|
|
* this storage and find out how big it is and use that information to give stp_move a good range since
|
|
* the literal segment occurs right at the end of allocated storage (for which there is no pointer
|
|
* in the fileheader).
|
|
*/
|
|
telem = (textElem *)((char *)rtnhdr - offsetof(textElem, userStorage));
|
|
assert(TextAllocated == telem->state);
|
|
stp_move((char *)LNRTAB_ADR(rtnhdr) + (rtnhdr->lnrtab_len * SIZEOF(lnr_tabent)),
|
|
(char *)rtnhdr + telem->realLen);
|
|
GTM_TEXT_FREE(rtnhdr);
|
|
# endif
|
|
}
|
|
#endif /* GTM_TRIGGER */
|