/* We ignore errors in the $ZYERROR routine. When an error occurs, we unwind all stack frames upto and including * zyerr_frame. MUM_TSTART then transfers control to the $ZTRAP frame. */ boolean_t clean_mum_tstart(void) { stack_frame *save_zyerr_frame, *fp, *fpprev; boolean_t save_check_flag; if (NULL != zyerr_frame) { while ((NULL != frame_pointer) && (NULL != zyerr_frame)) { GOFRAMES(1, TRUE, FALSE); } assert(NULL != frame_pointer); proc_act_type = 0; if (indr_stringpool.base == stringpool.base) { /* switch to run time stringpool */ indr_stringpool = stringpool; stringpool = rts_stringpool; } return TRUE; } return (NULL != err_act); }
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: PUSH: 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_TRIGR_CALLD; /* Do not return to this frame via MUM_TSTART */ DBGTRIGR((stderr, "gtm_trigger: Setting SFF_TRIGR_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 assert(((0 == gtm_trigger_depth) && (ch_at_trigger_init == ctxt->ch)) || ((0 < gtm_trigger_depth) && (&mdb_condition_handler == 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")); 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_TRIGR_CALLD & frame_pointer->flags); frame_pointer->flags &= SFF_TRIGR_CALLD_OFF; DBGTRIGR((stderr, "gtm_trigger: turning off SFF_TRIGR_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; }