/****************************************************************
 *								*
 *	Copyright 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 <errno.h>
#include "gtm_stdio.h"
#include "gtm_ctype.h"

#include "gdsroot.h"
#include "gdsbt.h"
#include "gdsfhead.h"
#include "filestruct.h"
#include "repl_msg.h"
#include "gtmsource.h"
#include "error.h"
#include "repl_sem.h"
#include "gtmimagename.h"
#include "hashtab_str.h"
#include "eintr_wrappers.h"
#include "gtmmsg.h"
#include "anticipatory_freeze.h"

#define MAX_TAG_LEN				128	/* Maximum size of an error mnemonic */
#define MAX_READ_SZ				1024	/* Mnemonic + flags shouldn't exceed this limit */
#define COMMENT_DELIMITER			';'
#define NEWLINE					0x0A
#define EOL_REACHED				(char *)(-1)
#define EOF_REACHED				(char *)(-2)

#define EXHAUST_CURRENT_LINE(BUFF, HANDLE, FGETS_RC)				\
{										\
	assert(NEWLINE != BUFF[STRLEN(BUFF) - 1]);				\
	while (TRUE)								\
	{									\
		FGETS_FILE(BUFF, MAX_READ_SZ, HANDLE, FGETS_RC);		\
		if ((NULL == FGETS_RC) || NEWLINE == BUFF[STRLEN(BUFF) - 1])	\
			break;							\
	}									\
}

error_def(ERR_CUSTERRNOTFND);
error_def(ERR_CUSTERRSYNTAX);
error_def(ERR_CUSTOMFILOPERR);
error_def(ERR_DSKSPCAVAILABLE);
error_def(ERR_ENOSPCQIODEFER);
error_def(ERR_REPLINSTFREEZECOMMENT);
error_def(ERR_REPLINSTFROZEN);
error_def(ERR_TEXT);

GBLREF	jnlpool_addrs		jnlpool;
GBLREF	jnlpool_ctl_ptr_t	jnlpool_ctl;
GBLREF	boolean_t		is_src_server;
GBLREF	boolean_t		holds_sem[NUM_SEM_SETS][NUM_SRC_SEMS];
GBLREF	sgmnt_addrs		*cs_addrs;
GBLREF	gd_region		*gv_cur_region;

/* Typically prototypes are included in the header file. But, in this case the static function - get_mnemonic_offset - has the
 * hash_table_str as one of the function parameters which means all the files which includes anticipatory_freeze.h needs to include
 * hashtab_str.h and since there are lot of such C files, we chose to define static function prototypes in the C file itself.
 */
STATICFNDCL char	*scan_space(FILE *handle, char *buff, char *buffptr, char *buff_top);
STATICFNDCL int		get_mnemonic_offset(hash_table_str **err_hashtab, char *mnemonic_buf, int mnemonic_len);

/* Scan through whitespace in the current buffer (read more if required) */
STATICFNDEF char 	*scan_space(FILE *handle, char *buff, char *buffptr, char *buff_top)
{
	char		*fgets_rc;

	while (TRUE)
	{
		for (; (buffptr < buff_top) && (ISSPACE_ASCII(*buffptr)); buffptr++)
			;
		if (buffptr < buff_top)
			return buffptr;	/* first non-whitespace character */
		if (NEWLINE == *(buffptr - 1))
			return EOL_REACHED;
		/* current buffer is exhausted and we haven't seen a newline; read more */
		FGETS_FILE(buff, MAX_READ_SZ, handle, fgets_rc);
		if (NULL == fgets_rc)
			break;
		buffptr = buff;
		buff_top = buffptr + STRLEN(buff);
	}
	return EOF_REACHED;
}

STATICFNDEF int		get_mnemonic_offset(hash_table_str **err_hashtab, char *mnemonic_buf, int mnemonic_len)
{
	const err_msg			*msg_beg, *msg_top;
	hash_table_str			*tmp_err_hashtab;
	ht_ent_str			*err_htent;
	stringkey			key;
	err_msg				*msg_info;
	boolean_t			added;
	DEBUG_ONLY(int			idx;)

	msg_beg = merrors_ctl.fst_msg;
	msg_top = msg_beg + merrors_ctl.msg_cnt;
	assert('\0' == mnemonic_buf[mnemonic_len]);
	if (NULL == (tmp_err_hashtab = *err_hashtab))
	{	/* create and populate hash-table for future lookups */
		tmp_err_hashtab = (hash_table_str *)malloc(SIZEOF(hash_table_str));
		DEBUG_ONLY(tmp_err_hashtab->base = NULL);
		init_hashtab_str(tmp_err_hashtab, 0, HASHTAB_NO_COMPACT, HASHTAB_NO_SPARE_TABLE);
		assert(tmp_err_hashtab->base);
		for (msg_info = (err_msg *)msg_beg; msg_info < msg_top; msg_info++)
		{
			key.str.addr = msg_info->tag;
			key.str.len = STRLEN(msg_info->tag);
			COMPUTE_HASH_STR(&key);
			added = add_hashtab_str(tmp_err_hashtab, &key, msg_info, &err_htent);
			assert(added);
			assert(err_htent->value);
			assert(msg_info->tag == ((err_msg *)(err_htent->value))->tag);
		}
		*err_hashtab = tmp_err_hashtab;
	}
	assert(NULL != tmp_err_hashtab);
	/* lookup for the mnemonic */
	key.str.addr = mnemonic_buf;
	key.str.len = mnemonic_len;
	COMPUTE_HASH_STR(&key);
	if (NULL == (err_htent = lookup_hashtab_str(tmp_err_hashtab, &key)))
		return -1;
	msg_info = (err_msg *)(err_htent->value);
	assert(msg_info >= msg_beg && msg_info < msg_top);
	return msg_info - msg_beg;
}

/* Determine whether a given msg_id qualifies for an anticipatory freeze or not */
boolean_t	is_anticipatory_freeze_needed(int msg_id)
{
	const err_ctl		*ctl;
	int			idx;
	sgmnt_addrs		*csa;

	assert(NULL != jnlpool.jnlpool_ctl);
	/* Certain error messages should NOT trigger a freeze even if they are so configured in the custom errors file as they might
	 * result in instance freezes that can be set indefinitely. Currently, we know of at least two such messages:
	 * 1. ENOSPCQIODEFER : To ensure we don't set anticipatory freeze if we don't/can't hold crit (due to possible deadlock)
	 * 2. DSKSPCAVAILABLE : To ensure we don't set anticipatory freeze if the disk space becomes available after an initial
	 *			lack of space.
	 */
	if ((ERR_ENOSPCQIODEFER == msg_id) || (ERR_DSKSPCAVAILABLE == msg_id))
		return FALSE;
	ctl = err_check(msg_id);
	if (NULL != ctl)
	{
		GET_MSG_IDX(msg_id, ctl, idx);
		assert(idx < ARRAYSIZE(jnlpool_ctl->merrors_array));
		if (jnlpool_ctl->merrors_array[idx] & AFREEZE_MASK)
		{
			assert(NULL != gv_cur_region);
			assert(&FILE_INFO(gv_cur_region)->s_addrs == cs_addrs);
			csa = &FILE_INFO(gv_cur_region)->s_addrs;
			assert((NULL != csa) && (NULL != csa->hdr));
			if (csa->hdr->freeze_on_fail)
				return TRUE;
		}
	}
	return FALSE;
}

/* set the anticipatory freeze in the journal pool */
void		set_anticipatory_freeze(int msg_id)
{
	boolean_t			was_crit;
	sgmnt_addrs			*repl_csa;
#	ifdef DEBUG
	qw_off_t			write_addr;
	uint4				write;
#	endif

	assert(is_anticipatory_freeze_needed(msg_id));
	DEBUG_ONLY(
		write_addr = jnlpool_ctl->write_addr;
		write = jnlpool_ctl->write;
	)
	assert(write == write_addr % jnlpool_ctl->jnlpool_size);
	repl_csa = &FILE_INFO(jnlpool.jnlpool_dummy_reg)->s_addrs;
	assert(NULL != repl_csa);
	was_crit = repl_csa->now_crit;
	if (!was_crit && !repl_csa->hold_onto_crit)
		grab_lock(jnlpool.jnlpool_dummy_reg, GRAB_LOCK_ONLY);
	/* Now that we hold necessary locks, set the freeze and the comment field */
	jnlpool.jnlpool_ctl->freeze = TRUE;
	GENERATE_INST_FROZEN_COMMENT(jnlpool.jnlpool_ctl->freeze_comment, SIZEOF(jnlpool.jnlpool_ctl->freeze_comment), msg_id);
	/* TODO : Do we need a SHM_WRITE_MEMORY_BARRIER ? */
	if (!was_crit && !repl_csa->hold_onto_crit)
		rel_lock(jnlpool.jnlpool_dummy_reg);
}

/* initialize jnlpool_ctl->merrors_array to set up the list of errors that should trigger anticipatory freeze errors */
boolean_t		init_anticipatory_freeze_errors()
{
	int				idx, save_errno, status, mnemonic_len, offset, line_no;
	FILE				*handle;
	char				*fgets_rc;
	char				buff[MAX_READ_SZ], mnemonic_buf[MAX_TAG_LEN];
	char				*buffptr, *buff_top, *errptr, *errptr_top;
	mstr				custom_err_file;
	hash_table_str			*err_hashtab = NULL;
	DCL_THREADGBL_ACCESS;

	SETUP_THREADGBL_ACCESS;
	/* TODO : Currently, we process errors that belong to merrors[] as those are the ones related to database/journal. Need
	 * to check if cmerrors/cmierrors also need to be included in this list or not.
	 */
	assert(IS_MUPIP_IMAGE); 				/* is_src_server is not initialized at this point */
	assert(jnlpool_ctl && !jnlpool_ctl->pool_initialized);	/* should be invoked BEFORE the journal pool is fully-initialized */
	assert(holds_sem[SOURCE][JNL_POOL_ACCESS_SEM]);		/* should hold journal pool access control semaphore */
	/* Now, read the custom errors file and populate the journal pool */
	custom_err_file = TREF(gtm_custom_errors);
	handle = Fopen(custom_err_file.addr, "r");
	if (NULL == handle)
	{
		save_errno = errno;
		send_msg(VARLSTCNT(6) ERR_CUSTOMFILOPERR, 4, LEN_AND_LIT("fopen"), custom_err_file.len, custom_err_file.addr,
				save_errno);
		gtm_putmsg(VARLSTCNT(6) ERR_CUSTOMFILOPERR, 4, LEN_AND_LIT("fopen"), custom_err_file.len, custom_err_file.addr,
				save_errno);
		return FALSE;
	}
	line_no = 0;
	/* The code below parses a custom errors file in the following format.
	 *
	 * file		::= line*
	 * line		::= mnemonic SPACE* comment? EOL |
	 * 		    comment EOL
	 * mnemonic	::= ALNUM+
	 * comment	::= COMMENT_DELIMITER ANY*
	 *
	 * SPACE		::= any ASCII white space character
	 * COMMENT_DELIMITER	::= ';'
	 * ANY			::= any ASCII character except end of line
	 * EOL			::= ASCII end of line character
	 * ALNUM		::= any ASCII alphanumeric character
	 *
	 * NOTES:
	 *	"*" denotes zero-or-more of the previous item
	 *	"?" denotes zero-or-one of the previous item
	 *	"+" denotes one-or-more of the previous item
	 *	"|" denotes multiple alternatives
	 * 	The mnemonic must match an entry in the GT.M error message list.
	 *	Anything between the COMMENT_DELIMITER and EOL is ignored.
	 *	Each iteration of the loop parses one line.
	 */
	while (TRUE)
	{
		FGETS_FILE(buff, MAX_READ_SZ, handle, fgets_rc);
		line_no++;
		if (NULL == fgets_rc)
			break;
		buffptr = buff;
		buff_top = buffptr + STRLEN(buff);
		errptr = &mnemonic_buf[0];
		errptr_top = errptr + MAX_TAG_LEN;
		/* The first character has to be alpha-numeric or a comment */
		if (!ISALNUM_ASCII(*buffptr) && (COMMENT_DELIMITER != *buffptr))
		{
			send_msg(VARLSTCNT(9) ERR_CUSTERRSYNTAX, 3, custom_err_file.len, custom_err_file.addr, line_no,
					ERR_TEXT, 2, LEN_AND_LIT("First character should be comment (;) or alpha numeric"));
			gtm_putmsg(VARLSTCNT(9) ERR_CUSTERRSYNTAX, 3, custom_err_file.len, custom_err_file.addr, line_no,
					ERR_TEXT, 2, LEN_AND_LIT("First character should be comment (;) or alpha numeric"));
			return FALSE;
		}
		while (ISALNUM_ASCII(*buffptr))
		{
			*errptr++ = *buffptr++;
			if (errptr > errptr_top)
			{
				send_msg(VARLSTCNT(9) ERR_CUSTERRSYNTAX, 3, custom_err_file.len, custom_err_file.addr, line_no,
						ERR_TEXT, 2, LEN_AND_LIT("Mnemonic too long"));
				gtm_putmsg(VARLSTCNT(9) ERR_CUSTERRSYNTAX, 3, custom_err_file.len, custom_err_file.addr, line_no,
						ERR_TEXT, 2, LEN_AND_LIT("Mnemonic too long"));
				return FALSE;
			}
			assert(buffptr < buff_top); /* errptr > errptr_top should fail before this */
		}
		*errptr = '\0';
		if (0 < (mnemonic_len = (errptr - &mnemonic_buf[0])))
		{	/* Non-empty error mnemonic found; look it up */
			if (-1 == (offset = get_mnemonic_offset(&err_hashtab, mnemonic_buf, mnemonic_len)))
			{
				send_msg(VARLSTCNT(4) ERR_CUSTERRNOTFND, 2, mnemonic_len, mnemonic_buf);
				gtm_putmsg(VARLSTCNT(4) ERR_CUSTERRNOTFND, 2, mnemonic_len, mnemonic_buf);
				return FALSE;
			}
			jnlpool_ctl->merrors_array[offset] |= AFREEZE_MASK; /* duplicate entries are not considered an error */
		}
		assert(ISSPACE_ASCII(*buffptr) || (COMMENT_DELIMITER == *buffptr));
		if (EOL_REACHED == (buffptr = scan_space(handle, buff, buffptr, buff_top)))
			continue;
		else if (EOF_REACHED == buffptr)
			break;
		assert(buffptr < buff_top);
		if (COMMENT_DELIMITER != *buffptr)
		{
			send_msg(VARLSTCNT(9) ERR_CUSTERRSYNTAX, 3, custom_err_file.len, custom_err_file.addr, line_no,
					ERR_TEXT, 2, LEN_AND_LIT("Unexpected character found after mnemonic"));
			gtm_putmsg(VARLSTCNT(9) ERR_CUSTERRSYNTAX, 3, custom_err_file.len, custom_err_file.addr, line_no,
					ERR_TEXT, 2, LEN_AND_LIT("Unexpected character found after mnemonic"));
			return FALSE;
		}
		/* Need to ignore the rest of the current buffer and exhaust the current line */
		if (NEWLINE != *(buff_top - 1))
			EXHAUST_CURRENT_LINE(buff, handle, fgets_rc);
	}
	if (err_hashtab)
	{
		free_hashtab_str(err_hashtab);
		free(err_hashtab);
	}
	if (!feof(handle))
	{
		save_errno = errno;
		send_msg(VARLSTCNT(6) ERR_CUSTOMFILOPERR, 4, LEN_AND_LIT("fgets"), custom_err_file.len, custom_err_file.addr,
				save_errno);
		gtm_putmsg(VARLSTCNT(6) ERR_CUSTOMFILOPERR, 4, LEN_AND_LIT("fgets"), custom_err_file.len, custom_err_file.addr,
				save_errno);
		return FALSE;
	}
	FCLOSE(handle, status);
	if (SS_NORMAL != status)
	{
		save_errno = errno;
		send_msg(VARLSTCNT(6) ERR_CUSTOMFILOPERR, 4, LEN_AND_LIT("fclose"), custom_err_file.len, custom_err_file.addr,
				save_errno);
		gtm_putmsg(VARLSTCNT(6) ERR_CUSTOMFILOPERR, 4, LEN_AND_LIT("fclose"), custom_err_file.len, custom_err_file.addr,
				save_errno);
		return FALSE;
	}
	return TRUE;
}
