1121 lines
44 KiB
C
1121 lines
44 KiB
C
/****************************************************************
|
|
* *
|
|
* 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 <descrip.h> /* required for gtmsource.h */
|
|
#include <ssdef.h>
|
|
#endif
|
|
#ifdef UNIX
|
|
#include <signal.h>
|
|
#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 <rtnhdr.h>
|
|
#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;
|
|
}
|