/****************************************************************
 *								*
 * Copyright (c) 2012-2018 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.	*
 *								*
 ****************************************************************/

#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"
#ifdef DEBUG
#include "dpgbldir.h"
#include "is_proc_alive.h"
#endif

#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_ASSERT);
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);
error_def(ERR_INSTFRZDEFER);

GBLREF	jnlpool_addrs_ptr_t	jnlpool;
GBLREF	boolean_t		is_src_server;
GBLREF	boolean_t		holds_sem[NUM_SEM_SETS][NUM_SRC_SEMS];
#ifdef DEBUG
GBLREF	uint4			process_id;
GBLREF	volatile boolean_t	timer_in_handler;
GBLREF	boolean_t		multi_thread_in_use;
#endif

/* 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);
STATICFNDCL boolean_t	is_message_excluded(int msg_id);

/* 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, (msg_top - msg_beg) * (100.0 / HT_LOAD_FACTOR),
				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 if the given msg_id has been explicitly excluded from being able to effectualte an anticipatory freeze */
STATICFNDCL boolean_t	is_message_excluded(int msg_offset)
{
	int		cnt;

	/* See if msg_id is one of the undocumented messages */
	for (cnt = 0; cnt < merrors_ctl.undocmsg_cnt; cnt++)	/* small list of 25-30 messages */
		if (msg_offset == merrors_ctl.undocmsg[cnt])
			return TRUE;
	assert(cnt == merrors_ctl.undocmsg_cnt);
	return FALSE;
}

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

	/* 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 3 such messages:
	 * 1. ENOSPCQIODEFER and INSTFRZDEFER : 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.
	 * These messages have csa == NULL so they are guarranteed to not trigger a freeze.
	 * However, ENOSPCQIODEFER is returned and later passed to rts_error with a non-NULL csa, so return FALSE in that case.
	 */

	if (!csa || !csa->nl || !csa->hdr || !csa->hdr->freeze_on_fail || (ERR_ENOSPCQIODEFER == msg_id))
		return FALSE;
	assert((ERR_DSKSPCAVAILABLE != msg_id) && (ERR_INSTFRZDEFER != msg_id));
	ctl = err_check(msg_id);
	if (NULL != ctl)
	{
		GET_MSG_IDX(msg_id, ctl, idx);
		local_jnlpool = csa->jnlpool ? csa->jnlpool : jnlpool;
		assert(local_jnlpool && local_jnlpool->jnlpool_ctl);
		assert(idx < ARRAYSIZE(local_jnlpool->jnlpool_ctl->merrors_array));
		if (local_jnlpool->jnlpool_ctl->merrors_array[idx] & AFREEZE_MASK)
			return TRUE;
	}
	return FALSE;
}

/* set the anticipatory freeze in the journal pool */
void		set_anticipatory_freeze(sgmnt_addrs *csa, int msg_id)
{
	boolean_t			was_crit;
	sgmnt_addrs			*repl_csa;
	const err_msg			*msginfo;
	jnlpool_addrs_ptr_t		save_jnlpool;

	assert(is_anticipatory_freeze_needed(csa, msg_id));
	save_jnlpool = jnlpool;
	if (csa->jnlpool && (csa->jnlpool != jnlpool))
		jnlpool = csa->jnlpool;
	assert(jnlpool && jnlpool->jnlpool_ctl);
	assert(jnlpool->jnlpool_dummy_reg);	/* other asserts in is anticipatory_freeze_needed */
	repl_csa = &FILE_INFO(jnlpool->jnlpool_dummy_reg)->s_addrs;
	assert(NULL != repl_csa);
	was_crit = repl_csa->now_crit;
	if (!was_crit)
	{
		if (csa->now_crit)
			grab_lock(jnlpool->jnlpool_dummy_reg, TRUE, GRAB_LOCK_ONLY);
		else if (FALSE == grab_lock(jnlpool->jnlpool_dummy_reg, FALSE, GRAB_LOCK_ONLY))
		{
			MSGID_TO_ERRMSG(msg_id, msginfo);
			send_msg_csa(CSA_ARG(NULL) VARLSTCNT(6) ERR_INSTFRZDEFER, 4, LEN_AND_STR(msginfo->tag),
				     REG_LEN_STR(csa->region));
			if (save_jnlpool != jnlpool)
				jnlpool = save_jnlpool;
			return;
		}
	}
	/* 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)
		rel_lock(jnlpool->jnlpool_dummy_reg);
	if (save_jnlpool != jnlpool)
		jnlpool = save_jnlpool;
}

/* 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 */
	/* invoke when not previously initialized */
	assert(jnlpool && jnlpool->jnlpool_ctl && !jnlpool->jnlpool_ctl->instfreeze_environ_inited);
	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);
	Fopen(handle, custom_err_file.addr, "r");
	if (NULL == handle)
	{
		save_errno = errno;
		send_msg_csa(CSA_ARG(NULL) VARLSTCNT(6) ERR_CUSTOMFILOPERR, 4, LEN_AND_LIT("fopen"), custom_err_file.len,
				custom_err_file.addr, save_errno);
		gtm_putmsg_csa(CSA_ARG(NULL) 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_csa(CSA_ARG(NULL) 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_csa(CSA_ARG(NULL) 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_csa(CSA_ARG(NULL) 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_csa(CSA_ARG(NULL) 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_csa(CSA_ARG(NULL) VARLSTCNT(4) ERR_CUSTERRNOTFND, 2, mnemonic_len, mnemonic_buf);
				gtm_putmsg_csa(CSA_ARG(NULL) VARLSTCNT(4) ERR_CUSTERRNOTFND, 2, mnemonic_len, mnemonic_buf);
				return FALSE;
			}
			if (!is_message_excluded(offset))
				jnlpool->jnlpool_ctl->merrors_array[offset] |= AFREEZE_MASK; /* duplicate entries are not 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_csa(CSA_ARG(NULL) 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_csa(CSA_ARG(NULL) 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_csa(CSA_ARG(NULL) VARLSTCNT(6) ERR_CUSTOMFILOPERR, 4, LEN_AND_LIT("fgets"), custom_err_file.len,
				custom_err_file.addr, save_errno);
		gtm_putmsg_csa(CSA_ARG(NULL) 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_csa(CSA_ARG(NULL) VARLSTCNT(6) ERR_CUSTOMFILOPERR, 4, LEN_AND_LIT("fclose"), custom_err_file.len,
				custom_err_file.addr, save_errno);
		gtm_putmsg_csa(CSA_ARG(NULL) VARLSTCNT(6) ERR_CUSTOMFILOPERR, 4, LEN_AND_LIT("fclose"), custom_err_file.len,
				custom_err_file.addr, save_errno);
		return FALSE;
	}
	jnlpool->jnlpool_ctl->instfreeze_environ_inited = TRUE;
	return TRUE;
}

#ifdef DEBUG
void clear_fake_enospc_if_master_dead(void)
{
	gd_addr				*addr_ptr;
	gd_region			*r_top, *r_local;
	sgmnt_addrs			*csa;

	assert(!multi_thread_in_use);	/* fake-enospc would not have been set if in threaded-code */
	assert(jnlpool && jnlpool->jnlpool_ctl);
	if((jnlpool->jnlpool_ctl->jnlpool_creator_pid != process_id)
		&& !is_proc_alive(jnlpool->jnlpool_ctl->jnlpool_creator_pid, 0))
	{
		for (addr_ptr = get_next_gdr(NULL); addr_ptr; addr_ptr = get_next_gdr(addr_ptr))
		{
			for (r_local = addr_ptr->regions, r_top = r_local + addr_ptr->n_regions; r_local < r_top; r_local++)
			{
				if (!IS_REG_BG_OR_MM(r_local))
					continue;
				csa = REG2CSA(r_local);
				if ((NULL != csa) && (NULL != csa->nl))
					if (csa->nl->fake_db_enospc || csa->nl->fake_jnl_enospc)
					{
						csa->nl->fake_db_enospc = FALSE;
						csa->nl->fake_jnl_enospc = FALSE;
						send_msg_csa(CSA_ARG(NULL) VARLSTCNT(8) ERR_TEXT, 2, DB_LEN_STR(r_local), ERR_TEXT,
							     2, LEN_AND_LIT("Resetting fake_db_enospc and fake_jnl_enospc because "
									    "fake ENOSPC master is dead"));
					}
			}
		}
	}
}
#endif
