/****************************************************************
 * Copyright (c) 2001-2017 Fidelity National Information	*
 * Services, Inc. and/or its subsidiaries. All rights reserved.	*
 *								*
 *	This source code contains the intellectual property	*
 *	of its copyright holder(s), and is made available	*
 *	under a license.  If you do not know the terms of	*
 *	the license, please stop and do not read further.	*
 *								*
 ****************************************************************/

#ifndef ERROR_TRAP_H
#define ERROR_TRAP_H

#define IS_ETRAP	(&(TREF(dollar_etrap)).str == err_act)
#define ETRAP_IN_EFFECT (!ztrap_explicit_null && (0 == (TREF(dollar_ztrap)).str.len))
#define NULLIFY_TRAP(TRAP) {(TRAP).mvtype = MV_STR; (TRAP).str.len = 0;}

#define	DOLLAR_ECODE_MAXINDEX	32	/* maximum of 32 ecodes in $ECODE */
#define	DOLLAR_STACK_MAXINDEX	256	/* maximum of 256 levels will be stored for $STACK(level) */

#define	DOLLAR_ECODE_ALLOC	(1 << 15)	/* 32K chunk memory malloced for $ECODE */
#define	DOLLAR_STACK_ALLOC	(1 << 15)	/* 32K chunk memory malloced for $STACK(level) */

#define	STACK_ZTRAP_EXPLICIT_NULL	-1	/* used to indicate an explicit SET $ZTRAP = "" */

typedef	void	(*error_ret_fnptr)(void);

typedef struct dollar_ecode
{
	mstr		ecode_str;
} dollar_ecode_struct;

typedef struct dollar_stack	/* contents of a single $STACK(level) entry */
{
	mstr			mode_str;	/* $STACK(level) */
	dollar_ecode_struct	*ecode_ptr;	/* $STACK(level,"ECODE") */
	mstr			mcode_str;	/* $STACK(level,"MCODE") */
	mstr			place_str;	/* $STACK(level,"PLACE") */
} dollar_stack_struct;

typedef enum stack_mode
{
	DOLLAR_STACK_INVALID,
	DOLLAR_STACK_ECODE,	/* $STACK(level,"ECODE") */
	DOLLAR_STACK_PLACE,	/* $STACK(level,"PLACE") */
	DOLLAR_STACK_MCODE,	/* $STACK(level,"MCODE") */
	DOLLAR_STACK_MODE	/* $STACK(level) i.e. how this frame got created */
} stack_mode_t;

typedef	struct
{
	char			*begin;	/* beginning of malloced memory holding the complete $ECODE */
	char			*end;	/* pointer to where next $ECODE can be added */
	char			*top;	/* allocated end of malloced memory holding the complete $ECODE */
	dollar_ecode_struct	*array;	/* array of DOLLAR_ECODE_MAXINDEX dollar_ecode_struct structures */
	uint4			index;	/* current count of number of filled structures in array */
	int4			error_last_ecode; /* last error code number */
	unsigned char		*error_last_b_line;	/* ptr to beginning of line where error occurred */
	struct stack_frame_struct *first_ecode_error_frame;	/* "frame_pointer" at the time of adding the first ECODE */
	unsigned char		*error_rtn_addr;	/* CODE_ADDRESS(ERROR_RTN) */
	unsigned char		*error_rtn_ctxt;	/* GTM_CONTEXT(ERROR_RTN) */
	error_ret_fnptr		error_return_addr;	/* CODE_ADDRESS(ERROR_RETURN) */
} dollar_ecode_type;

typedef struct
{
	char			*begin;		/* beginning of malloced memory holding all $STACK(level) detail */
	char			*end;		/* pointer to where next $STACK(level) detail can be added */
	char			*top;		/* allocated end of malloced memory holding all $STACK(level) detail */
	dollar_stack_struct	*array;		/* array of DOLLAR_STACK_MAXINDEX dollar_stack_struct structures */
	uint4			index;		/* current count of number of filled structures in array */
	boolean_t		incomplete;	/* TRUE if we were not able to fit in all $STACK info */
} dollar_stack_type;

/* reset all $ECODE related variables to correspond to $ECODE = NULL state */
#define	NULLIFY_DOLLAR_ECODE						\
{									\
	GBLREF	dollar_ecode_type	dollar_ecode;			\
	GBLREF	dollar_stack_type	dollar_stack;			\
									\
	dollar_ecode.end = dollar_ecode.begin;				\
	dollar_ecode.index = 0;						\
	dollar_stack.end = dollar_stack.begin;				\
	dollar_stack.index = 0;						\
	dollar_stack.incomplete = FALSE;				\
	dollar_ecode.first_ecode_error_frame = NULL;			\
}

/* nullify "error_frame" */
#define	NULLIFY_ERROR_FRAME											\
{														\
	GBLREF	stack_frame	*error_frame;									\
														\
	DBGEHND((stderr, "%s: Nullifying previous error_frame (was 0x"lvaddr")\n", __FILE__, error_frame));	\
	error_frame = NULL;											\
}

/* Set "error_frame" to point to "frame_pointer" and mark it as an error frame type. This is an indication that
 * whenever we unwind back to this frame, we need to transfer control to error_rtn_addr/ctxt (taken care of by getframe).
 */
#define SET_ERROR_FRAME(fp)								\
{											\
	GBLREF	stack_frame	*error_frame;						\
											\
	fp->flags |= SFF_ETRAP_ERR;							\
	error_frame = fp;								\
	DBGEHND((stderr, "%s: Setting error_frame as 0x"lvaddr"\n", __FILE__, fp));	\
}

/* invoke the function error_return() if the necessity of error-rethrow is detected. Note the extra
 * "&" value on the various assert(FALSE) type statements. This allows us to know which of these asserts
 * failed.
 */
#define	INVOKE_ERROR_RET_IF_NEEDED										\
{														\
	GBLREF	dollar_ecode_type	dollar_ecode;								\
	GBLREF	stack_frame		*error_frame;								\
														\
	if (NULL != error_frame)										\
	{													\
		if (error_frame == frame_pointer)								\
		{												\
			if (dollar_ecode.index)		/* non-zero implies non-NULL $ECODE */			\
			{	/* this is an error frame and $ECODE is non-NULL during QUIT out of this frame. \
				 * rethrow the error at lower level */						\
				(*dollar_ecode.error_return_addr)();						\
				/* While error_return does not usually return in UNIX, it can if we are 	\
				 * unwinding a job-interrupt frame because error rethrowing terminates when a	\
				 * job-interrupt frame is unwound which instead of re-throwing in the 		\
				 * interrupted frame sends an error to the operator log. But in VMS, we dont	\
				 * do the latter so it is possible if the current frame is of type SFT_DM that	\
				 * we don't rethrow and don't do a MUM_TSTART either. Assert accordingly.	\
				 */										\
				VMS_ONLY(assert(SFT_DM & frame_pointer->type);)					\
			} else											\
			{											\
				assert(FALSE & 2);								\
				NULLIFY_ERROR_FRAME;	/* don't know how we reached here. reset it in PRO */	\
			}											\
		} else if (error_frame < frame_pointer)								\
		{												\
			assert(FALSE & 3);									\
			NULLIFY_ERROR_FRAME;	/* don't know how we reached here. reset it in PRO */		\
		}												\
	}													\
}

void		ecode_init(void);
void		ecode_get(int level, mval *result);	/* return $ECODE (if "level" < 0) or $STACK(level,"ECODE") in "result" */
void		ecode_set(int errnum);			/* convert "errnum" to error-string and call ecode_add() */
boolean_t	ecode_add(mstr *str);			/* add "str" to $ECODE */
void		error_return(void);
#ifdef VMS
void		error_return_vms(void);
#endif

void	get_dollar_stack_info(int level, stack_mode_t mode,  mval *result);
void	get_frame_creation_info(int level, int cur_zlevel, mval *result);
void	get_frame_place_mcode(int level, stack_mode_t mode, int cur_zlevel, mval *result);

#endif /* ERROR_TRAP_H */
