/****************************************************************
 *								*
 * 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.	*
 *								*
 ****************************************************************/

#include "mdef.h"

#include "gtm_ipc.h"
#include "gtm_unistd.h"
#include "gtm_string.h"
#include "gtm_inet.h"
#include "gtm_fcntl.h"

#include <sys/sem.h>
#include <errno.h>
#include <stddef.h>

#include "gdsroot.h"
#include "gdsblk.h"
#include "gtm_facility.h"
#include "fileinfo.h"
#include "gdsbt.h"
#include "gdsfhead.h"
#include "filestruct.h"
#include "iosp.h"
#include "gtmrecv.h"
#include "repl_msg.h"
#include "gtmsource.h"
#include "gtm_logicals.h"
#include "jnl.h"
#include "repl_sem.h"
#include "repl_shutdcode.h"
#include "io.h"
#include "trans_log_name.h"
#include "repl_instance.h"
#include "mu_gv_cur_reg_init.h"
#include "gtmmsg.h"
#include "gtm_sem.h"
#include "mu_rndwn_replpool.h"
#include "ftok_sems.h"
#include "util.h"
#include "anticipatory_freeze.h"

#define REMOVE_SEM_SET(SEM_CREATED, POOL_TYPE)	/* Assumes 'sem_created_ptr' is is already declared */		\
{														\
	if (SEM_CREATED)											\
		remove_sem_set(JNLPOOL_SEGMENT == POOL_TYPE ? SOURCE : RECV);					\
	*sem_created_ptr = SEM_CREATED = FALSE;									\
}

#define DO_CLNUP_AND_RETURN(SAVE_ERRNO, SEM_CREATED, POOL_TYPE, INSTFILENAME, INSTFILELEN, SEM_ID, FAILED_OP)			\
{																\
	REMOVE_SEM_SET(SEM_CREATED, REPLPOOL_ID);										\
	gtm_putmsg_csa(CSA_ARG(NULL) VARLSTCNT(5) ERR_REPLACCSEM, 3, SEM_ID, INSTFILELEN, INSTFILENAME);			\
	gtm_putmsg_csa(CSA_ARG(NULL) VARLSTCNT(8) ERR_SYSCALL, 5, LEN_AND_LIT(FAILED_OP), CALLFROM, SAVE_ERRNO);		\
	return -1;														\
}

#define RELEASE_ALREADY_HELD_SEMAPHORE(SEM_SET, SEM_NUM)									\
{																\
	int		status, lcl_save_errno;											\
																\
	assert(holds_sem[SEM_SET][SEM_NUM]);											\
	status = rel_sem_immediate(SEM_SET, SEM_NUM);										\
	if (-1 == status)													\
	{															\
		lcl_save_errno = errno;												\
		gtm_putmsg_csa(CSA_ARG(NULL) VARLSTCNT(8) ERR_SYSCALL, 5, LEN_AND_LIT("semop()"), CALLFROM, lcl_save_errno);	\
	}															\
}

#define DECR_ALREADY_INCREMENTED_SEMAPHORE(SEM_SET, SEM_NUM)									\
{																\
	int		status, lcl_save_errno;											\
																\
	assert(holds_sem[SEM_SET][SEM_NUM]);											\
	status = decr_sem(SEM_SET, SEM_NUM);											\
	if (-1 == status)													\
	{															\
		lcl_save_errno = errno;												\
		gtm_putmsg_csa(CSA_ARG(NULL) VARLSTCNT(8) ERR_SYSCALL, 5, LEN_AND_LIT("semop()"), CALLFROM, lcl_save_errno);	\
	}															\
}

GBLREF	jnlpool_addrs_ptr_t	jnlpool;
GBLREF	recvpool_addrs		recvpool;
GBLREF	gd_region		*gv_cur_region;
GBLREF	jnl_gbls_t		jgbl;
GBLREF	boolean_t		holds_sem[NUM_SEM_SETS][NUM_SRC_SEMS];
GBLREF	boolean_t		argumentless_rundown;

error_def(ERR_JNLPOOLSETUP);
error_def(ERR_RECVPOOLSETUP);
error_def(ERR_REPLACCSEM);
error_def(ERR_REPLFTOKSEM);
error_def(ERR_SYSCALL);
error_def(ERR_TEXT);

/*
 * Description:
 * 	Grab ftok semaphore on replication instance file
 *	Grab all replication semaphores for the instance (both jnlpool and recvpool)
 * 	Release ftok semaphore
 * Parameters:
 * Return Value: SS_NORMAL, if succsessful
 *	         -1, if fails.
 */
int mu_replpool_grab_sem(repl_inst_hdr_ptr_t repl_inst_filehdr, char pool_type, boolean_t *sem_created_ptr, boolean_t immediate)
{
	int			status, save_errno, sem_id, semval, semnum, instfilelen;
	time_t			semctime;
	boolean_t		sem_created, force_increment;
	char			*instfilename;
	union semun		semarg;
	struct semid_ds		semstat;
	gd_region		*replreg;
	DEBUG_ONLY(unix_db_info	*udi;)
	DCL_THREADGBL_ACCESS;

	SETUP_THREADGBL_ACCESS;
	*sem_created_ptr = sem_created = FALSE; /* assume semaphore not created by default */
	assert(!jgbl.mur_rollback || !jgbl.mur_options_forward); /* ROLLBACK -FORWARD should not call this function */
	force_increment = (jgbl.onlnrlbk || (!jgbl.mur_rollback && !argumentless_rundown && INST_FREEZE_ON_ERROR_POLICY));
	/* First ensure that the caller has grabbed the ftok semaphore on the replication instance file */
	assert(!jnlpool || ((NULL != jnlpool->jnlpool_dummy_reg)
		&& (jnlpool->jnlpool_dummy_reg == recvpool.recvpool_dummy_reg)));
	if (jnlpool && (NULL != jnlpool->jnlpool_dummy_reg))
		replreg = jnlpool->jnlpool_dummy_reg;
	else
		replreg = recvpool.recvpool_dummy_reg;
	assert(NULL != replreg);
	DEBUG_ONLY(udi = FILE_INFO(replreg));
	assert(udi->grabbed_ftok_sem); /* the caller should have grabbed ftok semaphore */
	instfilename = (char *)replreg->dyn.addr->fname;
	instfilelen = replreg->dyn.addr->fname_len;
	assert((NULL != instfilename) && (0 != instfilelen) && ('\0' == instfilename[instfilelen]));
	assert((JNLPOOL_SEGMENT == pool_type) || (RECVPOOL_SEGMENT == pool_type));
	if (JNLPOOL_SEGMENT == pool_type)
	{
		sem_id = repl_inst_filehdr->jnlpool_semid;
		semctime = repl_inst_filehdr->jnlpool_semid_ctime;
	}
	else
	{
		sem_id = repl_inst_filehdr->recvpool_semid;
		semctime = repl_inst_filehdr->recvpool_semid_ctime;
	}
	semarg.buf = &semstat;
	if ((INVALID_SEMID == sem_id) || (-1 == semctl(sem_id, 0, IPC_STAT, semarg)) || (semctime != semarg.buf->sem_ctime))
	{	/* Semaphore doesn't exist. Create new ones */
		if (JNLPOOL_SEGMENT == pool_type)
		{
			repl_inst_filehdr->jnlpool_semid = INVALID_SEMID; /* Invalidate previous semid in file header */
			repl_inst_filehdr->jnlpool_semid_ctime = 0;
			sem_id = init_sem_set_source(IPC_PRIVATE, NUM_SRC_SEMS, RWDALL | IPC_CREAT);
		} else
		{
			repl_inst_filehdr->recvpool_semid = INVALID_SEMID; /* Invalidate previous semid in file header */
			repl_inst_filehdr->recvpool_semid_ctime = 0;
			sem_id = init_sem_set_recvr(IPC_PRIVATE, NUM_RECV_SEMS, RWDALL | IPC_CREAT);
		}
		if (INVALID_SEMID == sem_id)
		{
			save_errno = errno;
			DO_CLNUP_AND_RETURN(save_errno, sem_created, pool_type, instfilename, instfilelen, sem_id, "semget()");
		}
		*sem_created_ptr = sem_created = TRUE;
		semarg.val = GTM_ID;
		if (-1 == semctl(sem_id, SOURCE_ID_SEM, SETVAL, semarg))
		{
			save_errno = errno;
			DO_CLNUP_AND_RETURN(save_errno, sem_created, pool_type, instfilename, instfilelen, sem_id, "semctl()");
		}
		semarg.buf = &semstat;
		if (-1 == semctl(sem_id, DB_CONTROL_SEM, IPC_STAT, semarg))
		{
			save_errno = errno;
			DO_CLNUP_AND_RETURN(save_errno, sem_created, pool_type, instfilename, instfilelen, sem_id, "semctl()");
		}
		semctime = semarg.buf->sem_ctime;
	} else if (JNLPOOL_SEGMENT == pool_type)
		set_sem_set_src(sem_id);
	else
		set_sem_set_recvr(sem_id);
	/* Semaphores are setup. Grab them */
	if (JNLPOOL_SEGMENT == pool_type)
	{
		assert(!jgbl.onlnrlbk || !immediate);
		if (!immediate)
			status = grab_sem(SOURCE, JNL_POOL_ACCESS_SEM); /* want to wait in case of online rollback */
		else
			status = grab_sem_immediate(SOURCE, JNL_POOL_ACCESS_SEM);
		if (SS_NORMAL != status)
		{
			save_errno = errno;
			DO_CLNUP_AND_RETURN(save_errno, sem_created, pool_type, instfilename, instfilelen, sem_id, "semop()");
		}
		semval = semctl(sem_id, SRC_SERV_COUNT_SEM, GETVAL);
		if (-1 == semval)
		{
			save_errno = errno;
			RELEASE_ALREADY_HELD_SEMAPHORE(SOURCE, JNL_POOL_ACCESS_SEM);
			DO_CLNUP_AND_RETURN(save_errno, sem_created, pool_type, instfilename, instfilelen, sem_id, "semctl()");
		}
		if (0 < semval && !force_increment)
		{
			RELEASE_ALREADY_HELD_SEMAPHORE(SOURCE, JNL_POOL_ACCESS_SEM);
			util_out_print("Replpool semaphore (id = !UL) for replication instance !AD is in use by another process.",
						TRUE, sem_id, instfilelen, instfilename);
			REMOVE_SEM_SET(sem_created, pool_type);
			return -1;
		}
		status = incr_sem(SOURCE, SRC_SERV_COUNT_SEM);
		if (SS_NORMAL != status)
		{
			save_errno = errno;
			RELEASE_ALREADY_HELD_SEMAPHORE(SOURCE, JNL_POOL_ACCESS_SEM);
			assert(FALSE); /* we hold it, so there is no reason why we cannot release it */
			DO_CLNUP_AND_RETURN(save_errno, sem_created, pool_type, instfilename, instfilelen, sem_id, "semop()");
		}
		holds_sem[SOURCE][SRC_SERV_COUNT_SEM] = TRUE;
		repl_inst_filehdr->jnlpool_semid = sem_id;
		repl_inst_filehdr->jnlpool_semid_ctime = semctime;
	}
	else
	{
		if (!immediate)
			status = grab_sem(RECV, RECV_POOL_ACCESS_SEM);
		else
			status = grab_sem_immediate(RECV, RECV_POOL_ACCESS_SEM);
		if (SS_NORMAL != status)
		{
			save_errno = errno;
			DO_CLNUP_AND_RETURN(save_errno, sem_created, pool_type, instfilename, instfilelen, sem_id, "semop()");
		}
		if (!immediate)
			status = grab_sem(RECV, RECV_SERV_OPTIONS_SEM);
		else
			status = grab_sem_immediate(RECV, RECV_SERV_OPTIONS_SEM);
		if (SS_NORMAL != status)
		{
			save_errno = errno;
			RELEASE_ALREADY_HELD_SEMAPHORE(RECV, RECV_POOL_ACCESS_SEM);
			DO_CLNUP_AND_RETURN(save_errno, sem_created, pool_type, instfilename, instfilelen, sem_id, "semop()");
		}
		assert(RECV_SERV_COUNT_SEM + 1 == UPD_PROC_COUNT_SEM);
		for (semnum = RECV_SERV_COUNT_SEM; semnum <= UPD_PROC_COUNT_SEM; semnum++)
		{
			semval = semctl(sem_id, semnum, GETVAL);
			if (-1 == semval)
			{
				save_errno = errno;
				RELEASE_ALREADY_HELD_SEMAPHORE(RECV, RECV_POOL_ACCESS_SEM);
				RELEASE_ALREADY_HELD_SEMAPHORE(RECV, RECV_SERV_OPTIONS_SEM);
				DO_CLNUP_AND_RETURN(save_errno, sem_created, pool_type, instfilename, instfilelen,
							sem_id, "semctl()");
			}
			if ((0 < semval) && !jgbl.onlnrlbk)
			{
				RELEASE_ALREADY_HELD_SEMAPHORE(RECV, RECV_POOL_ACCESS_SEM);
				RELEASE_ALREADY_HELD_SEMAPHORE(RECV, RECV_SERV_OPTIONS_SEM);
				if (UPD_PROC_COUNT_SEM == semnum)
				{	/* Need to decrement RECV_SERV_COUNT_SEM before returning to the caller since we have
					 * incremented it before
					 */
					DECR_ALREADY_INCREMENTED_SEMAPHORE(RECV, RECV_SERV_COUNT_SEM);
				}
				util_out_print("Replpool semaphore (id = !UL) for replication instance !AD is in use by "
						"another process.", TRUE, sem_id, instfilelen, instfilename);
				REMOVE_SEM_SET(sem_created, pool_type);
				return -1;
			}
			status = incr_sem(RECV, semnum);
			if (SS_NORMAL != status)
			{
				save_errno = errno;
				if (UPD_PROC_COUNT_SEM == semnum)
				{	/* Need to decrement RECV_SERV_COUNT_SEM before returning to the caller since we have
					 * incremented it before
					 */
					DECR_ALREADY_INCREMENTED_SEMAPHORE(RECV, RECV_SERV_COUNT_SEM);
				}
				DO_CLNUP_AND_RETURN(save_errno, sem_created, pool_type, instfilename, instfilelen,
							sem_id, "semop()");
			}
			holds_sem[RECV][semnum] = TRUE;
		}
		repl_inst_filehdr->recvpool_semid = sem_id;
		repl_inst_filehdr->recvpool_semid_ctime = semctime;
	}
	return SS_NORMAL;
}
