/**************************************************************** * * * Copyright 2001, 2012 Fidelity Information Services, Inc * * * * This source code contains the intellectual property * * of its copyright holder(s), and is made available * * under a license. If you do not know the terms of * * the license, please stop and do not read further. * * * ****************************************************************/ #include "mdef.h" #include "gtm_string.h" #include "gtm_stdlib.h" #include "gtm_inet.h" /* Required for gtmsource.h */ #include "gtm_stdio.h" #ifdef VMS #include /* required for gtmsource.h */ #include #endif #ifdef UNIX #include #endif #include "ast.h" #include "gdsroot.h" #include "gdsbt.h" #include "gtm_facility.h" #include "fileinfo.h" #include "gdsblk.h" #include "gdsfhead.h" #include "gdscc.h" #include "gdskill.h" #include "gt_timer.h" #include "filestruct.h" #include "gtmdbglvl.h" #include "error.h" #include "hashtab_mname.h" #include "io.h" #include "io_params.h" #include "jnl.h" #include "lv_val.h" #include #include "mv_stent.h" #include "outofband.h" #include "stack_frame.h" #include "stringpool.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 "xfer_enum.h" #include "mlkdef.h" #include "repl_msg.h" #include "gtmsource.h" #include "zwrite.h" #include "cache.h" #include "cache_cleanup.h" #include "objlabel.h" #include "op.h" #include "dpgbldir.h" #include "preemptive_ch.h" #include "compiler.h" /* needed for MAX_SRCLINE */ #include "show_source_line.h" #include "trans_code_cleanup.h" #include "dm_setup.h" #include "util.h" #include "tp_restart.h" #include "dollar_zlevel.h" #include "error_trap.h" #include "golevel.h" #include "send_msg.h" #include "jobinterrupt_process_cleanup.h" #include "fix_xfer_entry.h" #include "change_reg.h" #include "tp_change_reg.h" #include "alias.h" #include "create_fatal_error_zshow_dmp.h" #include "have_crit.h" #ifdef UNIX # include "iormdef.h" # include "ftok_sems.h" #endif #ifdef GTM_TRIGGER # include "gv_trigger.h" # include "gtm_trigger.h" #endif GBLREF spdesc stringpool, rts_stringpool, indr_stringpool; GBLREF volatile int4 outofband; GBLREF volatile bool std_dev_outbnd; GBLREF unsigned char *restart_pc; GBLREF unsigned char *restart_ctxt; GBLREF unsigned char *stackwarn, *tpstackwarn; GBLREF unsigned char *stacktop, *tpstacktop; GBLREF unsigned char *msp, *tp_sp; GBLREF mv_stent *mv_chain; GBLREF stack_frame *frame_pointer, *zyerr_frame, *error_frame; GBLREF tp_frame *tp_pointer; GBLREF io_desc *active_device; GBLREF lv_val *active_lv; GBLREF io_pair io_std_device, io_curr_device; GBLREF mval dollar_ztrap; GBLREF volatile bool neterr_pending; GBLREF xfer_entry_t xfer_table[]; GBLREF unsigned short proc_act_type; GBLREF int mumps_status; GBLREF mstr *err_act; GBLREF tp_region *tp_reg_list; /* Chained list of regions used in this transaction not cleared on tp_restart */ GBLREF uint4 gtmDebugLevel; /* Debug level */ GBLREF uint4 process_id; GBLREF jnlpool_addrs jnlpool; GBLREF boolean_t pool_init; GBLREF boolean_t created_core; GBLREF boolean_t dont_want_core; GBLREF mval dollar_zstatus, dollar_zerror; GBLREF mval dollar_etrap; GBLREF volatile int4 gtmMallocDepth; GBLREF int4 exi_condition; GBLREF inctn_opcode_t inctn_opcode; #ifdef VMS GBLREF struct chf$signal_array *tp_restart_fail_sig; GBLREF boolean_t tp_restart_fail_sig_used; #endif GBLREF int merge_args; GBLREF lvzwrite_datablk *lvzwrite_block; GBLREF volatile boolean_t dollar_zininterrupt; GBLREF boolean_t ztrap_explicit_null; /* whether $ZTRAP was explicitly set to NULL in this frame */ GBLREF dollar_ecode_type dollar_ecode; /* structure containing $ECODE related information */ GBLREF boolean_t in_gvcst_incr; GBLREF gv_namehead *gv_target; GBLREF gd_region *gv_cur_region; GBLREF sgmnt_addrs *cs_addrs; GBLREF sgmnt_data_ptr_t cs_data; GBLREF sgm_info *first_sgm_info; GBLREF dollar_stack_type dollar_stack; GBLREF mval *alias_retarg; #ifdef UNIX GBLREF jnl_gbls_t jgbl; #endif GBLREF io_desc *gtm_err_dev; #ifdef GTM_TRIGGER GBLREF int tprestart_state; /* When triggers restart, multiple states possible. See tp_restart.h */ GBLREF int4 gtm_trigger_depth; #endif #ifdef DEBUG GBLREF boolean_t donot_INVOKE_MUMTSTART; #endif error_def(ERR_ASSERT); error_def(ERR_CTRAP); error_def(ERR_CTRLC); error_def(ERR_CTRLY); error_def(ERR_GTMASSERT); error_def(ERR_GTMASSERT2); error_def(ERR_GTMCHECK); error_def(ERR_GTMERREXIT); error_def(ERR_JOBINTRRETHROW); error_def(ERR_JOBINTRRQST); error_def(ERR_LABELMISSING); error_def(ERR_MEMORY); error_def(ERR_NOEXCNOZTRAP); error_def(ERR_NOTPRINCIO); error_def(ERR_OUTOFSPACE); error_def(ERR_REPEATERROR); error_def(ERR_RESTART); error_def(ERR_RTSLOC); error_def(ERR_SRCLOCUNKNOWN); error_def(ERR_STACKCRIT); error_def(ERR_STACKOFLOW); error_def(ERR_TPRETRY); error_def(ERR_TPSTACKCRIT); error_def(ERR_TPSTACKOFLOW); error_def(ERR_TPTIMEOUT); error_def(ERR_UNSOLCNTERR); error_def(ERR_VMSMEMORY); boolean_t clean_mum_tstart(void); #ifdef GTM_TRIGGER /* When we go to restart generated code after handling an error, verify that we are not in frame or one created on its * behalf that invoked a trigger and caused a dynamic TSTART to be done on its behalf. This can happen for example if * a trigger is invoked for the first time but get a compilation or link failure error. This error is thrown from * gtm_trigger() while no trigger based error handling is in effect so no rollback of the dynamic frame occurs which * will result in unhandled TPQUIT errors, perhaps interminably. */ #define MUM_TSTART_FRAME_CHECK \ { \ if ((0 == gtm_trigger_depth) && tp_pointer && tp_pointer->implicit_tstart) \ { \ DEBUG_ONLY(donot_INVOKE_MUMTSTART = FALSE); \ OP_TROLLBACK(-1); /* Unroll implicit TP frame */ \ } \ } #else #define MUM_TSTART_FRAME_CHECK #endif /* 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); } CONDITION_HANDLER(mdb_condition_handler) { unsigned char *cp, *context, *sp_base; boolean_t dm_action; /* did the error occur on a action from direct mode */ boolean_t trans_action; /* did the error occur during "transcendental" code */ char src_line[MAX_ENTRYREF_LEN]; char source_line_buff[MAX_SRCLINE + SIZEOF(ARROW)]; mstr src_line_d; io_desc *err_dev; tp_region *tr; gd_region *reg_top, *reg_save, *reg_local; gd_addr *addr_ptr; sgmnt_addrs *csa; stack_frame *fp; boolean_t error_in_zyerror; boolean_t repeat_error, etrap_handling, reset_mpc; int level, rc; lv_val *lvptr; boolean_t reserve_sock_dev = FALSE; # ifdef UNIX unix_db_info *udi; # endif START_CH; DBGEHND((stderr, "mdb_condition_handler: Entered with SIGNAL=%d frame_pointer=0x"lvaddr"\n", SIGNAL, frame_pointer)); if (NULL != gtm_err_dev) { /* It is possible that we entered here from a bad compile of the OPEN exception handler * for a device. If gtm_err_dev is still set from the previous mdb_condition_handler * invocation that drove the error handler that occurred during the OPEN command, then its * structures should be released now. */ if (gtmsocket == gtm_err_dev->type) iosocket_destroy(gtm_err_dev); # ifdef UNIX else remove_rms(gtm_err_dev); # endif gtm_err_dev = NULL; } if (repeat_error = (ERR_REPEATERROR == SIGNAL)) /* assignment and comparison */ SIGNAL = dollar_ecode.error_last_ecode; preemptive_ch(SEVERITY); if (NULL != alias_retarg) { /* An error has occurred while an alias return arg was in-flight. Delivery won't happen now * so we need to remove the extra counts that were added in unw_retarg() and dis-enchant * the alias container itself. */ assert(alias_retarg->mvtype & MV_ALIASCONT); if (alias_retarg->mvtype & MV_ALIASCONT) { /* Protect the refs were are about to make in case ptr got banged up somehow */ lvptr = (lv_val *)alias_retarg->str.addr; assert(LV_IS_BASE_VAR(lvptr)); DECR_CREFCNT(lvptr); DECR_TREFCNT(lvptr); } alias_retarg->mvtype = 0; /* Kill the temp var (no longer a container) */ alias_retarg = NULL; /* .. and no more in-flight return argument */ } if ((int)ERR_UNSOLCNTERR == SIGNAL) { /* This is here for linking purposes. We want to delay the receipt of * network errors in gtm until we are ready to deal with them. Hence * the transfer table hijinx. To avoid doing this in the gvcmz routine, * we signal the error and do it here */ neterr_pending = TRUE; FIX_XFER_ENTRY(xf_linefetch, op_fetchintrrpt); FIX_XFER_ENTRY(xf_linestart, op_startintrrpt); FIX_XFER_ENTRY(xf_forchk1, op_startintrrpt); FIX_XFER_ENTRY(xf_forloop, op_forintrrpt); CONTINUE; } MDB_START; assert(FALSE == in_gvcst_incr); /* currently there is no known case where this can be TRUE at this point */ in_gvcst_incr = FALSE; /* reset this just in case gvcst_incr/gvcst_put failed to do a good job of resetting */ /* Ideally merge should have a condition handler to reset followings, but generated code can call other routines * during MERGE command (MERGE command invokes multiple op-codes depending on source vs target). So it is not * easy to establish a condition handler there. Easy solution is following one line code. */ merge_args = 0; TREF(in_zwrite) = FALSE; inctn_opcode = inctn_invalid_op; if ((SUCCESS != SEVERITY) && (INFO != SEVERITY)) { if (lvzwrite_block) /* If lvzwrite_block does not (yet) exist, no harm, no foul */ lvzwrite_block->curr_subsc = lvzwrite_block->subsc_count = 0; } if ((int)ERR_TPRETRY == SIGNAL) { /* Put the restart here for linking purposes. * Utilities use T_RETRY, so calling from there causes * all sorts of linking overlaps. */ VMS_ONLY(assert(FALSE == tp_restart_fail_sig_used)); # ifdef GTM_TRIGGER /* Assert that we never end up invoking the MUM_TSTART macro handler in case of an implicit tstart restart. * See GBLDEF of skip_INVOKE_RESTART and donot_INVOKE_MUMTSTART in gbldefs.c for more information. * Note that it is possible for this macro to be invoked from generated code in a trigger frame (in which * case gtm_trigger/tp_restart ensure control passed to mdb_condition_handler only until the outermost * implicit tstart in which case they return). Assert accordingly. */ assert(!donot_INVOKE_MUMTSTART || gtm_trigger_depth); TRIGGER_BASE_FRAME_UNWIND_IF_NOMANSLAND; # endif rc = tp_restart(1, TP_RESTART_HANDLES_ERRORS); DBGEHND((stderr, "mdb_condition_handler: tp_restart returned with rc=%d. state=%d, and SIGNAL=%d\n", rc, tprestart_state, error_condition)); # ifdef GTM_TRIGGER if (0 != rc) { /* The only time "tp_restart" will return non-zero is if the error needs to be * rethrown. To accomplish that, we will unwind this handler which will return to * the inner most initiating dm_start() with the return code set to whatever mumps_status * is set to. */ assert(TPRESTART_STATE_NORMAL != tprestart_state); assert(rc == SIGNAL); assertpro((SFT_TRIGR & frame_pointer->type) && (0 < gtm_trigger_depth)); mumps_status = rc; DBGEHND((stderr, "mdb_condition_handler: Unwind-return to caller (gtm_trigger)\n")); UNWIND(NULL, NULL); } /* "tp_restart" has succeeded so we have unwound back to the return point but check if the * transaction was initiated by an implicit trigger TSTART. This can occur if an error was * encountered in a trigger before the trigger base-frame was setup. It can occur at any trigger * level if a triggered update is preceeded by a TROLLBACK. */ if (!(SFT_TRIGR & frame_pointer->type) && tp_pointer && tp_pointer->implicit_tstart) { mumps_status = rc; DBGEHND((stderr, "mdb_condition_handler: Returning to implicit TSTART originator\n")); UNWIND(NULL, NULL); } assert(!donot_INVOKE_MUMTSTART); # endif # ifdef UNIX if (ERR_TPRETRY == SIGNAL) /* (signal value undisturbed) */ # elif defined VMS if (!tp_restart_fail_sig_used) /* If tp_restart ran clean */ # endif { /* Clean up both stacks, and set mumps program counter back tstart level 1 */ TREF(ind_result_sp) = TREF(ind_result_array); /* clean up any active indirection pool usages */ TREF(ind_source_sp) = TREF(ind_source_array); MUM_TSTART; } # ifdef VMS else { /* Otherwise tp_restart had a signal that we must now deal with -- replace the TPRETRY * information with that saved from tp_restart. * Assert that we have room for these arguments - the array malloc is in tp_restart */ assert(TPRESTART_ARG_CNT >= tp_restart_fail_sig->chf$is_sig_args); memcpy(sig, tp_restart_fail_sig, (tp_restart_fail_sig->chf$l_sig_args + 1) * SIZEOF(int)); tp_restart_fail_sig_used = FALSE; } # endif } /* Ensure gv_target and cs_addrs are in sync. If not, make them so. */ if (NULL != gv_target) { csa = gv_target->gd_csa; if ((NULL != csa) && (csa != cs_addrs)) { assert(0 < csa->regcnt); /* If csa->regcnt is > 1, it is possible that csa->region is different from the actual gv_cur_region * (before we encountered the runtime error). This is a case of two regions mapping to the same csa. * The only issue with this is that some user-level error messages that have the region name (as * opposed to the database file name) could print incorrect values. But other than that there should * be no issues since finally the csa (corresponding to the physical database file) is what matters * and that is the same for both the regions. Given that the region mismatch potential exists only * until the next global reference which is different from $REFERENCE, we consider this acceptable. */ gv_cur_region = csa->region; assert(gv_cur_region->open); assert((dba_mm == gv_cur_region->dyn.addr->acc_meth) || (dba_bg == gv_cur_region->dyn.addr->acc_meth)); /* The above assert is needed to ensure that change_reg/tp_change_reg (invoked below) * will set cs_addrs, cs_data etc. to non-zero values. */ if (NULL != first_sgm_info) change_reg(); /* updates "cs_addrs", "cs_data", "sgm_info_ptr" and maybe "first_sgm_info" */ else { /* We are either inside a non-TP transaction or in a TP transaction that has done NO database * references. In either case, we do NOT want to setting sgm_info_ptr or first_sgm_info. * Hence use tp_change_reg instead of change_reg below. */ tp_change_reg(); /* updates "cs_addrs", "cs_data" */ } assert(cs_addrs == csa); assert(cs_data == csa->hdr); assert(NULL != cs_data); } } if (DUMPABLE) { /* Certain conditions we don't want to attempt to create the M-level ZSHOW dump. * 1) Unix: If gtmMallocDepth > 0 indicating memory manager was active and could be reentered. * 2) Unix: If we have a SIGBUS or SIGSEGV (could be likely to occur again * in the local variable code which would cause immediate shutdown with * no cleanup). * 3) VMS: If we got an ACCVIO for the same as reason (2). * Note that we will bypass checks 2 and 3 if GDL_ZSHOWDumpOnSignal debug flag is on */ SET_PROCESS_EXITING_TRUE; /* So zshow doesn't push stuff on stack to "protect" it when * we potentially already have a stack overflow */ cancel_timer(0); /* No interruptions now that we are dying */ if (!repeat_error UNIX_ONLY(&& (0 == gtmMallocDepth))) { src_line_d.addr = src_line; /* Setup entry point buffer for set_zstatus() */ src_line_d.len = 0; SET_ZSTATUS(NULL); } /* Create the ZSHOW dump file if it can be created */ create_fatal_error_zshow_dmp(SIGNAL, repeat_error); /* If we are about to core/exit on a stack over flow, only do the core part if a debug * flag requests this behaviour. Otherwise, supress the core and just exit. * 2006-03-07 se: If a stack overflow occurs on VMS, it has happened that the stack is no * longer well formed so attempting to unwind it as it does in MUMPS_EXIT causes things * to really become screwed up. For this reason, this niceness of avoiding a dump on a * stack overflow on VMS is being disabled. The dump can be controlled wih set proc/dump * (or not) as desired. * 2008-01-29 (se): Added fatal MEMORY error so we no longer generate a core for it by * default unless the DumpOnStackOFlow flag is turned on. Since this flag is not a user-exposed * interface, I'm avoiding renaming it for now. Note the core avoidance applies to both UNIX * and VMS since stack formation is not at issue in this sort of memory request. * Finally note that in UNIX, ch_cond_core (called by DRIVECH macro which invoked this condition * handler has likely already created the core and set the created_core flag which will prevent * this process from creating another core for the same SIGNAL. We leave this code in here in * case methods exist in the future for this module to be driven without invoking cond_core_ch * first. */ if (!(GDL_DumpOnStackOFlow & gtmDebugLevel) && VMS_ONLY((int)ERR_VMSMEMORY == SIGNAL) UNIX_ONLY(((int)ERR_STACKOFLOW == SIGNAL || (int)ERR_STACKOFLOW == arg || (int)ERR_MEMORY == SIGNAL || (int)ERR_MEMORY == arg))) { # ifdef VMS /* Inside this ifdef, we are definitely here because of ERR_VMSMEMORY. If the conditions * of the above if change, revisit these assmuptions. * For VMSMEMORY error, we have to send the message to the operator log and to the * console ourselves because the MUMP_EXIT method of exiting on a fatal error does * not preserve the substitution parameters for the message making it useless. After * sending the message change the status code so we exit with something other than the * duplicate message. */ assert(ERR_VMSMEMORY == SIGNAL); send_msg(VARLSTCNT(4) ERR_VMSMEMORY, 2, *(int **)(&sig->chf$is_sig_arg1 + 1), *(int **)(&sig->chf$is_sig_arg1 + 2)); gtm_putmsg(VARLSTCNT(4) ERR_VMSMEMORY, 2, *(int **)(&sig->chf$is_sig_arg1 + 1), *(int **)(&sig->chf$is_sig_arg1 + 2)); SIGNAL = ERR_GTMERREXIT; /* Override reason for "stop" */ # endif MUMPS_EXIT; /* Do a clean exit rather than messy core exit */ } gtm_dump(); TERMINATE; } # ifdef GTM_TRIGGER assertpro(TPRESTART_STATE_NORMAL == tprestart_state); /* Can't leave half-restarted transaction around - out of design */ # endif if (active_lv) { if (!LV_IS_VAL_DEFINED(active_lv) && !LV_HAS_CHILD(active_lv)) op_kill(active_lv); active_lv = (lv_val *)0; } /* * If error is at least severity "WARNING", do some cleanups. Note: crit is no longer unconditionally * released here. It is now released if NOT in TP (any retry) or if in TP but NOT in the final retry. * But we still cleanup the replication instance file lock and the replication crit lock. We do this * because when starting the final retry GTM only holds the grab_crit locks but not the grab_lock or * ftok_sem_lock locks. The grab_lock is done only at commit time (at which point there cannot be any * programmatic runtime errors possible). Errors inside jnlpool_init (which cause the ftok-sem-lock * to be held) can never be programmatically caused. * * On the other hand, while running in the final retry holding crit, a program can cause an arbitrary * error (e.g. 1/0 error or similar). Hence the distinction. The reasoning is that any of these could * potentially take an arbitrary amount of time and we don't want to be holding critical locks while * doing these. But one can argue that the error trap is nothing but an extension of the transaction * and that if GT.M is fine executing arbitrary application M code in the final retry holding crit, * it should be fine doing the same for the error trap M code as well. So the entire point of not * releasing crit is to avoid indefinite TP retries due to runtime errors. But this means the error * trap should be well coded (i.e. not have any long running commands etc.) to avoid crit hangs * and/or deadlocks. */ if ((SUCCESS != SEVERITY) && (INFO != SEVERITY)) { ENABLE_AST; if ((0 == dollar_tlevel) || (CDB_STAGNATE > t_tries)) { /* Only release crit if we are NOT in TP *or* if we are in TP, we aren't in final retry */ for (addr_ptr = get_next_gdr(NULL); addr_ptr; addr_ptr = get_next_gdr(addr_ptr)) { for (reg_local = addr_ptr->regions, reg_top = reg_local + addr_ptr->n_regions; reg_local < reg_top; reg_local++) { if (reg_local->open && !reg_local->was_open) { csa = (sgmnt_addrs *)&FILE_INFO(reg_local)->s_addrs; if (csa && csa->now_crit) { assert(!csa->hold_onto_crit UNIX_ONLY(|| jgbl.onlnrlbk)); if (csa->hold_onto_crit) csa->hold_onto_crit = FALSE; /* Fix it in pro */ rel_crit(reg_local); } } } } } # ifdef UNIX /* Release FTOK lock on the replication instance file if holding it (possible if error in jnlpool_init) */ assert((NULL == jnlpool.jnlpool_dummy_reg) || jnlpool.jnlpool_dummy_reg->open || !pool_init); if ((NULL != jnlpool.jnlpool_dummy_reg) && jnlpool.jnlpool_dummy_reg->open) { udi = FILE_INFO(jnlpool.jnlpool_dummy_reg); assert(NULL != udi); if (NULL != udi) { if (udi->grabbed_ftok_sem) ftok_sem_release(jnlpool.jnlpool_dummy_reg, FALSE, FALSE); assert(!udi->grabbed_ftok_sem); } } # endif /* Release crit lock on journal pool if holding it */ if (pool_init) /* atleast one region replicated and we have done jnlpool init */ { csa = (sgmnt_addrs *)&FILE_INFO(jnlpool.jnlpool_dummy_reg)->s_addrs; if (csa && csa->now_crit) rel_lock(jnlpool.jnlpool_dummy_reg); } TREF(in_op_fnnext) = FALSE; /* in case we were in $NEXT */ /* Global values that may need cleanups */ if (INTRPT_OK_TO_INTERRUPT != intrpt_ok_state) ENABLE_INTERRUPTS(intrpt_ok_state); /* If interrupts were deferred, re-enable them now */ } # ifdef GTM_TRIGGER /* At this point, we are past the point where the frame pointer is allowed to be resting on a trigger frame * (this is possible in a TPRETRY situation where gtm_trigger must return to gtm_trigger() signaling a * restart is necessary). If we are on a trigger base frame, unwind it so the error is recognized in * the invoker's frame. */ if (SFT_TRIGR & frame_pointer->type) { /* Better be an error in here info or success messages want to continue, not be unwound but * we cannot go past this point in a trigger frame or the frame_pointer back reference below * will fail. */ assert((SUCCESS != SEVERITY) && (INFO != SEVERITY)); /* These outofband conditions depend on saving the current stack frame info in restart_pc which * is of course no longer valid once the frame is unrolled so they must be avoided. At the time * of this writing, there are no conditions that these should validly be called in this * situation so this check is more for the future. */ assert(((int)ERR_CTRLY != SIGNAL) && ((int)ERR_CTRLC != SIGNAL) && ((int)ERR_CTRAP != SIGNAL) && ((int)ERR_JOBINTRRQST != SIGNAL) && ((int)ERR_JOBINTRRETHROW != SIGNAL)); gtm_trigger_fini(TRUE, FALSE); DBGEHND((stderr, "mdb_condition_handler: Current trigger frame unwound so error is thrown" " on trigger invoker's frame instead.\n")); } # endif err_dev = active_device; active_device = (io_desc *)NULL; TREF(ind_result_sp) = TREF(ind_result_array); /* clean up any active indirection pool usages */ TREF(ind_source_sp) = TREF(ind_source_array); dm_action = (frame_pointer->old_frame_pointer->type & SFT_DM) || (TREF(compile_time) && (frame_pointer->type & SFT_DM)); /* The errors are said to be transcendental when they occur during compilation/execution * of the error trap ({z,e}trap, device exception) or $zinterrupt. The errors in other * indirect code frames (zbreak, zstep, xecute etc.) aren't defined to be trancendental * and will be treated as if they occured in a regular code frame. */ trans_action = proc_act_type || (frame_pointer->type & SFT_ZTRAP) || (frame_pointer->type & SFT_DEV_ACT); src_line_d.addr = src_line; src_line_d.len = 0; flush_pio(); if ((int)ERR_CTRLY == SIGNAL) { outofband_clear(); /* Verify not indirect or that context is unchanged before reset context */ assert(NULL != restart_pc); assert((!(SFF_INDCE & frame_pointer->flags)) || (restart_ctxt == frame_pointer->ctxt)); DBGEHND((stderr, "mdb_condition_handler(1): Resetting frame 0x"lvaddr" mpc/context with restart_pc/ctxt" "0x"lvaddr"/0x"lvaddr" - frame has type 0x%04lx\n", frame_pointer, restart_pc, restart_ctxt, frame_pointer->type)); frame_pointer->mpc = restart_pc; frame_pointer->ctxt = restart_ctxt; MUM_TSTART; } else if ((int)ERR_CTRLC == SIGNAL) { outofband_clear(); if (!trans_action && !dm_action) { /* Verify not indirect or that context is unchanged before reset context */ assert(NULL != restart_pc); assert((!(SFF_INDCE & frame_pointer->flags)) || (restart_ctxt == frame_pointer->ctxt)); DBGEHND((stderr, "mdb_condition_handler(2): Resetting frame 0x"lvaddr" mpc/context with restart_pc/ctxt" "0x"lvaddr"/0x"lvaddr" - frame has type 0x%04lx\n", frame_pointer, restart_pc, restart_ctxt, frame_pointer->type)); frame_pointer->mpc = restart_pc; frame_pointer->ctxt = restart_ctxt; if (!(frame_pointer->type & SFT_DM)) dm_setup(); } else if (frame_pointer->type & SFT_DM) { frame_pointer->ctxt = GTM_CONTEXT(call_dm); frame_pointer->mpc = CODE_ADDRESS(call_dm); } else { /* Do cleanup on indirect frames prior to reset */ IF_INDR_FRAME_CLEANUP_CACHE_ENTRY_AND_UNMARK(frame_pointer); frame_pointer->ctxt = GTM_CONTEXT(pseudo_ret); frame_pointer->mpc = CODE_ADDRESS(pseudo_ret); } frame_pointer->flags &= SFF_IMPLTSTART_CALLD_OFF; /* Frame enterable now with mpc reset */ GTMTRIG_ONLY( DBGTRIGR((stderr, "mdb_condition_handler: turning off SFF_IMPLTSTART_CALLD_OFF (1) in frame 0x"lvaddr"\n", frame_pointer))); PRN_ERROR; if (io_curr_device.out != io_std_device.out) { dec_err(VARLSTCNT(4) ERR_NOTPRINCIO, 2, io_curr_device.out->trans_name->len, io_curr_device.out->trans_name->dollar_io); } MUM_TSTART; } else if ((int)ERR_CTRAP == SIGNAL) { if (!repeat_error) /* This has already been done if we are re-throwing the error */ outofband_clear(); if (!trans_action && !dm_action && !(frame_pointer->type & SFT_DM)) { sp_base = stringpool.base; if (sp_base != rts_stringpool.base) { indr_stringpool = stringpool; /* update indr_stringpool */ stringpool = rts_stringpool; /* change for set_zstatus */ } if (!repeat_error) { dollar_ecode.error_last_b_line = SET_ZSTATUS(NULL); } if (sp_base != rts_stringpool.base) { rts_stringpool = stringpool; /* update rts_stringpool */ stringpool = indr_stringpool; /* change back */ } assert(NULL != dollar_ecode.error_last_b_line); /* Only (re)set restart_pc if we are in the original frame. This is needed to restart execution at the * beginning of the line but only happens when $ZTRAP is in effect. If $ETRAP is in control, control * naturally returns to the caller unless the $ECODE value is not set. In that case, the CTRAP error * will be rethrown one level down as $ETRAP normally does. Note in case of a rethrow, we avoid resetting * mpc/ctxt as those values are only appropriate for the level in which they were saved. */ if (!repeat_error && ((0 != dollar_ztrap.str.len) || ztrap_explicit_null)) { /* Verify not indirect or that context is unchanged before reset context */ assert(NULL != restart_pc); assert((!(SFF_INDCE & frame_pointer->flags)) || (restart_ctxt == frame_pointer->ctxt)); DBGEHND((stderr, "mdb_condition_handler(3): Resetting frame 0x"lvaddr" mpc/context with restart_pc/" "ctxt 0x"lvaddr"/0x"lvaddr" - frame has type 0x%04lx\n", frame_pointer, restart_pc, restart_ctxt, frame_pointer->type)); frame_pointer->mpc = restart_pc; frame_pointer->ctxt = restart_ctxt; } err_act = NULL; dollar_ecode.error_last_ecode = SIGNAL; if (std_dev_outbnd && io_std_device.in && (tt == io_std_device.in->type) && io_std_device.in->error_handler.len) { proc_act_type = SFT_DEV_ACT; err_act = &io_std_device.in->error_handler; } else if (!std_dev_outbnd && err_dev && (tt == err_dev->type) && err_dev->error_handler.len) { proc_act_type = SFT_DEV_ACT; err_act = &err_dev->error_handler; } else if (NULL != error_frame) { /* a primary error occurred already. irrespective of whether ZTRAP or ETRAP is active now, * we need to consider this as a nested error and trigger nested error processing. */ goerrorframe(); /* unwind back to error_frame */ proc_act_type = 0; } else if ((0 != dollar_etrap.str.len) || (0 != dollar_ztrap.str.len)) { assert(!ztrap_explicit_null); proc_act_type = SFT_ZTRAP; err_act = (0 != dollar_etrap.str.len) ? &dollar_etrap.str : &dollar_ztrap.str; } else { /* Either $ETRAP is empty-string or ztrap_explicit_null is set * * If ztrap_explicit_null is FALSE * - Use empty-string $ETRAP for error-handling * * If ztrap_explicit_null is TRUE * - unwind as many frames as possible until we see a frame where ztrap_explicit_null is * FALSE and $ZTRAP is not NULL (i.e. we find a $ETRAP handler). In that frame, use $ETRAP * for error-handling. If no such frame is found, exit after printing the error. */ etrap_handling = TRUE; if (ztrap_explicit_null) { assert(0 == dollar_etrap.str.len); for (level = dollar_zlevel() - 1; level > 0; level--) { GOLEVEL(level, FALSE); assert(level == dollar_zlevel()); if (ETRAP_IN_EFFECT) break; } if (0 >= level) { assert(0 == level); etrap_handling = FALSE; DBGEHND((stderr, "mdb_condition_handler: Unwound to stack start - exiting\n")); } /* Note that trans_code will set error_frame appropriately for this condition */ } if (SFF_CI & frame_pointer->flags) { /* Unhandled errors from called-in routines should return to gtm_ci() with error status */ mumps_status = SIGNAL; MUM_TSTART_FRAME_CHECK; MUM_TSTART; } else if (etrap_handling) { proc_act_type = SFT_ZTRAP; err_act = &dollar_etrap.str; } else { PRN_ERROR; rts_error(VARLSTCNT(1) ERR_NOEXCNOZTRAP); } } if (clean_mum_tstart()) { MUM_TSTART_FRAME_CHECK; MUM_TSTART; } } else if (frame_pointer->type & SFT_DM) { frame_pointer->ctxt = GTM_CONTEXT(call_dm); frame_pointer->mpc = CODE_ADDRESS(call_dm); frame_pointer->flags &= SFF_IMPLTSTART_CALLD_OFF; /* Frame enterable now with mpc reset */ GTMTRIG_ONLY(DBGTRIGR((stderr, "mdb_condition_handler: turning off SFF_IMPLTSTART_CALLD (2) in frame 0x" lvaddr"\n", frame_pointer))); } else { /* Do cleanup on indirect frames prior to reset */ IF_INDR_FRAME_CLEANUP_CACHE_ENTRY_AND_UNMARK(frame_pointer); frame_pointer->ctxt = GTM_CONTEXT(pseudo_ret); frame_pointer->mpc = CODE_ADDRESS(pseudo_ret); frame_pointer->flags &= SFF_IMPLTSTART_CALLD_OFF; /* Frame enterable now with mpc reset */ GTMTRIG_ONLY(DBGTRIGR((stderr, "mdb_condition_handler: turning off SFF_IMPLTSTART_CALLD (3) in frame 0x" lvaddr"\n", frame_pointer))); } PRN_ERROR; if (io_curr_device.out != io_std_device.out) { dec_err(VARLSTCNT(4) ERR_NOTPRINCIO, 2, io_curr_device.out->trans_name->len, io_curr_device.out->trans_name->dollar_io); } MUM_TSTART_FRAME_CHECK; MUM_TSTART; } else if ((int)ERR_JOBINTRRQST == SIGNAL) { /* Verify not indirect or that context is unchanged before reset context */ assert(NULL != restart_pc); assert((!(SFF_INDCE & frame_pointer->flags)) || (restart_ctxt == frame_pointer->ctxt)); DBGEHND((stderr, "mdb_condition_handler(4): Resetting frame 0x"lvaddr" mpc/context with restart_pc/ctxt" "0x"lvaddr"/0x"lvaddr" - frame has type 0x%04lx\n", frame_pointer, restart_pc, restart_ctxt, frame_pointer->type)); frame_pointer->mpc = restart_pc; frame_pointer->ctxt = restart_ctxt; assert(!dollar_zininterrupt); dollar_zininterrupt = TRUE; /* Note done before outofband is cleared to prevent nesting */ outofband_clear(); proc_act_type = SFT_ZINTR | SFT_COUNT; /* trans_code will invoke jobinterrupt_process for us */ MUM_TSTART; } else if ((int)ERR_JOBINTRRETHROW == SIGNAL) { /* Job interrupt is rethrown from TC/TRO */ assert(!dollar_zininterrupt); dollar_zininterrupt = TRUE; proc_act_type = SFT_ZINTR | SFT_COUNT; /* trans_code will invoke jobinterrupt_process for us */ MUM_TSTART; } else if ((int)ERR_STACKCRIT == SIGNAL) { assert(msp > stacktop); assert(stackwarn > stacktop); cp = stackwarn; stackwarn = stacktop; push_stck(cp, 0, (void**)&stackwarn, MVST_STCK_SP); } if (!repeat_error) dollar_ecode.error_last_b_line = NULL; /* Error from direct mode actions does not set $ZSTATUS and is not restarted (dollar_ecode.error_last_b_line = NULL); * Error from transcendental code does set $ZSTATUS but does not restart the line. */ if (!dm_action) { sp_base = stringpool.base; if (sp_base != rts_stringpool.base) { indr_stringpool = stringpool; /* update indr_stringpool */ stringpool = rts_stringpool; /* change for set_zstatus */ } if (!repeat_error) { dollar_ecode.error_last_b_line = SET_ZSTATUS(&context); } assert(NULL != dollar_ecode.error_last_b_line); if (sp_base != rts_stringpool.base) { rts_stringpool = stringpool; /* update rts_stringpool */ stringpool = indr_stringpool; /* change back */ } } if ((SUCCESS == SEVERITY) || (INFO == SEVERITY)) { PRN_ERROR; CONTINUE; } /* Error from direct mode actions or "transcendental" code does not invoke MUMPS error handling routines */ if (!dm_action && !trans_action) { DBGEHND((stderr, "mdb_condition_handler: Handler to dispatch selection checks\n")); err_act = NULL; dollar_ecode.error_last_ecode = SIGNAL; reset_mpc = FALSE; if (err_dev && err_dev->error_handler.len && ((int)ERR_TPTIMEOUT != SIGNAL)) { proc_act_type = SFT_DEV_ACT; err_act = &err_dev->error_handler; reserve_sock_dev = TRUE; /* Reset mpc to beginning of the current line (to retry after processing the IO exception handler) */ reset_mpc = TRUE; DBGEHND((stderr, "mdb_condition_handler: dispatching device error handler [%.*s]\n", err_act->len, err_act->addr)); } else if (NULL != error_frame) { /* A primary error occurred already. irrespective of whether ZTRAP or ETRAP is active now, we need to * consider this as a nested error and trigger nested error processing. */ goerrorframe(); /* unwind upto error_frame */ proc_act_type = 0; DBGEHND((stderr, "mdb_condition_handler: Have unwound to error frame via goerrorframe() and am " "re-dispatching error frame\n")); MUM_TSTART_FRAME_CHECK; MUM_TSTART; /* unwind the current C-stack and restart executing from the top of the current M-stack */ assert(FALSE); } else if ((0 != dollar_etrap.str.len) || (0 != dollar_ztrap.str.len)) { assert(!ztrap_explicit_null); proc_act_type = SFT_ZTRAP; err_act = (0 != dollar_etrap.str.len) ? &dollar_etrap.str : &dollar_ztrap.str; DBGEHND((stderr, "mdb_condition_handler: Dispatching %s error handler [%.*s]\n", (0 != dollar_etrap.str.len) ? "$ETRAP" : "$ZTRAP", err_act->len, err_act->addr)); /* Reset mpc to beginning of the current line (to retry after invoking $ZTRAP) */ if (0 != dollar_ztrap.str.len) reset_mpc = TRUE; } else { /* Either $ETRAP is empty string or ztrap_explicit_null is set. * * If ztrap_explicit_null is FALSE * - Use empty-string $ETRAP for error-handling * * If ztrap_explicit_null is TRUE * - Unwind as many frames as possible until we see a frame where ztrap_explicit_null is FALSE and * $ZTRAP is *not* NULL (i.e. we found an $ETRAP). In that frame, use $ETRAP for error-handling. * If no such frame is found, exit after printing the error. */ etrap_handling = TRUE; if (ztrap_explicit_null) { GTMTRIG_ONLY(assert(0 == gtm_trigger_depth)); /* Should never happen in a trigger */ DBGEHND((stderr, "mdb_condition_handler: ztrap_explicit_null set - unwinding till find handler\n")); assert(0 == dollar_etrap.str.len); for (level = dollar_zlevel() - 1; level > 0; level--) { GOLEVEL(level, FALSE); assert(level == dollar_zlevel()); if (ETRAP_IN_EFFECT) /* Break if found an $ETRAP covered frame */ break; } if (0 >= level) { assert(0 == level); etrap_handling = FALSE; DBGEHND((stderr, "mdb_condition_handler: Unwound to stack start - exiting\n")); } /* note that trans_code will set error_frame appropriately for this condition */ } if (SFF_CI & frame_pointer->flags) { /* Unhandled errors from called-in routines should return to gtm_ci() with error status */ mumps_status = SIGNAL; DBGEHND((stderr, "mdb_condition_handler: Call in base frame found - returnning to callins\n")); MUM_TSTART_FRAME_CHECK; MUM_TSTART; } else if (etrap_handling) { proc_act_type = SFT_ZTRAP; err_act = &dollar_etrap.str; DBGEHND((stderr, "mdb_condition_handler: $ETRAP handler being dispatched [%.*s]\n", err_act->len, err_act->addr)); } } /* if the err_act points to the address err_dev->errro_handler, we cannot destroy socket here */ if (err_dev && (gtmsocket == err_dev->type) && err_dev->newly_created && !reserve_sock_dev) { assert(err_dev->state != dev_open); iosocket_destroy(err_dev); err_dev = NULL; } if (reset_mpc) { /* Reset the mpc such that * (a) If the current frame is a counted frame, the error line is retried after the error is handled, * (b) If the current frame is "transcendental" code, set frame to return. * * If we are in $ZYERROR, we don't care about restarting the line that errored since we will * unwind all frames upto and including zyerr_frame. * * If this is a rethrown error (ERR_REPEATERROR) from a child frame, do NOT reset mpc of the current * frame in that case. We do NOT want to retry the current line (after the error has been * processed) because the error did not occur in this line and therefore re-executing the same * line could cause undesirable effects at the M-user level. We will resume normal execution * once the error is handled. Not that it matters, but note that in the case of a rethrown error * (repeat_error is TRUE), we would NOT have noted down dollar_ecode.error_last_b_line so cannot * use that to reset mpc anyways. */ if ((NULL == zyerr_frame) && !repeat_error) { DBGEHND((stderr, "mdb_condition_handler: reset_mpc triggered\n")); for (fp = frame_pointer; fp; fp = fp->old_frame_pointer) { /* See if this is a $ZINTERRUPT frame. If yes, we want to restart *this* line * at the beginning. Since it is always an indirect frame, we can use the context * pointer to start over. $ETRAP does things somewhat differently in that the current * frame is always returned from. */ if (SFT_ZINTR & fp->type) { assert(SFF_INDCE & fp->flags); fp->mpc = fp->ctxt; break; } /* Do cleanup on indirect frames prior to reset */ IF_INDR_FRAME_CLEANUP_CACHE_ENTRY_AND_UNMARK(fp); /* mpc points to PTEXT */ if (ADDR_IN_CODE(fp->mpc, fp->rvector)) { /* GT.M specific error trapping retries the line with the error */ fp->mpc = dollar_ecode.error_last_b_line; fp->ctxt = context; break; } else { fp->ctxt = GTM_CONTEXT(pseudo_ret); fp->mpc = CODE_ADDRESS(pseudo_ret); } fp->flags &= SFF_IMPLTSTART_CALLD_OFF; /* Frame enterable now with mpc reset */ GTMTRIG_ONLY( DBGTRIGR((stderr, "mdb_condition_handler: turning off SFF_IMPLTSTART_CALLD (4) " "in frame 0x"lvaddr"\n", frame_pointer))); } } } if (clean_mum_tstart()) { if (err_dev) { # ifdef UNIX /* On z/OS, if opening a fifo which is not read only we need to fix the err_dev type to rm */ # ifdef __MVS__ if ((dev_open != err_dev->state) && (ff == err_dev->type)) { assert(NULL != err_dev->pair.out); if (rm == err_dev->pair.out->type) { /* Have to massage the device so remove_rms will cleanup the partially * created fifo. Refer to io_open_try.c for creation of split fifo device. */ err_dev->newly_created = 1; err_dev->type = rm; err_dev->dev_sp = err_dev->pair.out->dev_sp; err_dev->pair.out->dev_sp = NULL; } } # endif if ((dev_open != err_dev->state) && (rm == err_dev->type)) { gtm_err_dev = err_dev; /* structures pointed to by err_dev were freed so make sure it's not used again */ err_dev = NULL; } # endif if (err_dev && (gtmsocket == err_dev->type) && err_dev->newly_created) { assert(err_dev->state != dev_open); assert(reserve_sock_dev); gtm_err_dev = err_dev; err_dev = NULL; } } MUM_TSTART_FRAME_CHECK; MUM_TSTART; } else { /* err_act is null, we can remove the socket device */ if (err_dev && (gtmsocket == err_dev->type) && err_dev->newly_created && !reserve_sock_dev) { assert(err_act == NULL); assert(err_dev->state != dev_open); iosocket_destroy(err_dev); err_dev = NULL; } DBGEHND((stderr, "mdb_condition_handler: clean_mum_tstart returned FALSE\n")); } } else { DBGEHND((stderr, "mdb_condition_handler: Transient or direct mode frame -- bypassing handler dispatch\n")); if (err_dev) { # ifdef UNIX /* Executed from the direct mode so do the rms check and cleanup if necessary. On z/OS, if opening a fifo * which is not read only we need to fix the type for the err_dev to rm. */ # ifdef __MVS__ if ((dev_open != err_dev->state) && (ff == err_dev->type)) { assert(NULL != err_dev->pair.out); if (rm == err_dev->pair.out->type) { /* Have to massage the device so remove_rms will cleanup the partially created fifo */ err_dev->newly_created = 1; err_dev->type = rm; err_dev->dev_sp = err_dev->pair.out->dev_sp; err_dev->pair.out->dev_sp = NULL; } } # endif if (dev_open != err_dev->state && (rm == err_dev->type)) { remove_rms(err_dev); err_dev = NULL; } # endif if (err_dev && (gtmsocket == err_dev->type) && err_dev->newly_created) { assert(err_dev->state != dev_open); iosocket_destroy(err_dev); err_dev = NULL; } } } if ((SFT_ZINTR | SFT_COUNT) != proc_act_type || 0 == dollar_ecode.error_last_b_line) { /* No user console error for $zinterrupt compile problems and if not direct mode. Accomplish * this by bypassing the code inside this if which *will* be executed for most cases */ DBGEHND((stderr, "mdb_condition_handler: Printing error status\n")); PRN_ERROR; if (TREF(compile_time) && ((int)ERR_LABELMISSING) != SIGNAL) show_source_line(source_line_buff, SIZEOF(source_line_buff), TRUE); } if (!dm_action && !trans_action && (0 != src_line_d.len)) { if (MSG_OUTPUT) dec_err(VARLSTCNT(4) ERR_RTSLOC, 2, src_line_d.len, src_line_d.addr); } else { if (trans_action || dm_action) { /* If true transcendental, do trans_code_cleanup(). If our counted frame is * masquerading as a transcendental frame, run jobinterrupt_process_cleanup(). */ DBGEHND((stderr, "mdb_condition_handler: trans_code_cleanup() or jobinterrupt_process_cleanup being " "dispatched\n")); if (!(SFT_ZINTR & proc_act_type)) trans_code_cleanup(); else jobinterrupt_process_cleanup(); MUM_TSTART_FRAME_CHECK; MUM_TSTART; } else if (MSG_OUTPUT) { /* If a message about the location is needed, it should be possible to pull the location * out of the $STACK array. If it exists, use it instead. */ if ((NULL != dollar_stack.array) && (0 < dollar_stack.index)) { /* Error entry exists */ src_line_d = dollar_stack.array[dollar_stack.index - 1].place_str; assert(src_line_d.len); assert(src_line_d.addr); dec_err(VARLSTCNT(4) ERR_RTSLOC, 2, src_line_d.len, src_line_d.addr); } else dec_err(VARLSTCNT(1) ERR_SRCLOCUNKNOWN); } } DBGEHND((stderr, "mdb_condition_handler: Condition not handled -- defaulting to process exit\n")); MUMPS_EXIT; }