/****************************************************************
 *								*
 * Copyright (c) 2009-2020 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_fcntl.h"
#include "gtm_stat.h"
#include "gtm_unistd.h"
#include <sys/mman.h>
#include <sys/shm.h>
#include "gtm_permissions.h"
#include "gtm_string.h"
#include "gtm_stdio.h"
#include "gtm_stdlib.h"
#include "gtm_tempnam.h"
#include "gtm_time.h"

#include "gdsroot.h"
#include "gdskill.h"
#include "gtm_facility.h"
#include "fileinfo.h"
#include "gdsbt.h"
#include "gdsblk.h"
#include "gdsfhead.h"
#include "filestruct.h"
#include "iosp.h"
#include "error.h"
#include "cli.h"
#include "eintr_wrappers.h"
#include "gtmio.h"
#include "gtm_file_stat.h"
#include "gtmimagename.h"
#include "interlock.h"
#include "wcs_phase2_commit_wait.h"
#include "util.h"
#include "ipcrmid.h"
#include "shmpool.h"
#include "db_snapshot.h"
#include "ss_lock_facility.h"

GBLREF	sgmnt_addrs		*cs_addrs;
GBLREF	sgmnt_data		*cs_data;
GBLREF	gd_region		*gv_cur_region;
GBLREF	uint4			process_id;
GBLREF	enum gtmImageTypes	image_type;

error_def(ERR_COMMITWAITSTUCK);
error_def(ERR_SYSCALL);
error_def(ERR_SSFILCLNUPFAIL);
error_def(ERR_SSSHMCLNUPFAIL);

#define CLEANUP_SHADOW_FILE(preserve_snapshot, shadow_file, csa)					\
{													\
	int		status;										\
	struct stat	stat_buf;									\
	int		save_errno;									\
													\
	if (!preserve_snapshot && (-1 != stat(shadow_file, &stat_buf)))					\
	{												\
		status = UNLINK(shadow_file);								\
		if (0 != status)									\
		{											\
			save_errno = errno;								\
			if (IS_MUMPS_IMAGE)								\
				send_msg_csa(CSA_ARG(csa) VARLSTCNT(5)					\
					ERR_SSFILCLNUPFAIL, 2, LEN_AND_STR(shadow_file), save_errno);	\
			else										\
				gtm_putmsg_csa(CSA_ARG(csa) VARLSTCNT(5)				\
					ERR_SSFILCLNUPFAIL, 2, LEN_AND_STR(shadow_file), save_errno);	\
		}											\
	}												\
}													\

#define CLEANUP_SHARED_MEMORY(ss_shmid, csa)									\
{														\
	int		save_errno;										\
	struct shmid_ds ss_shmstat;										\
														\
	if ((INVALID_SHMID != ss_shmid) && (0 == shmctl((int)ss_shmid, IPC_STAT, &ss_shmstat)))			\
	{													\
		if (0 != shmctl((int)ss_shmid, IPC_RMID, &ss_shmstat))						\
		{												\
			save_errno = errno;									\
			if (IS_MUMPS_IMAGE)									\
				send_msg_csa(CSA_ARG(csa) VARLSTCNT(6)						\
					ERR_SSSHMCLNUPFAIL, 3, LEN_AND_LIT("shmctl"), ss_shmid, save_errno);	\
			else											\
				gtm_putmsg_csa(CSA_ARG(csa) VARLSTCNT(6)					\
					ERR_SSSHMCLNUPFAIL, 3, LEN_AND_LIT("shmctl"), ss_shmid, save_errno);	\
		}												\
	}													\
}														\

#define ADJUST_SHARED_MEMORY_FIELDS(cnl, ss_shm_ptr)				\
{										\
	if (0 < cnl->num_snapshots_in_effect)					\
		cnl->num_snapshots_in_effect--;					\
	if (0 == cnl->num_snapshots_in_effect)					\
	{									\
		CLEAR_SNAPSHOTS_IN_PROG(cnl);					\
		cnl->ss_shmid = INVALID_SHMID;					\
	}									\
	SS_DEFAULT_INIT_POOL(ss_shm_ptr);					\
	cnl->ss_shmcycle++;							\
}

void		ss_release(snapshot_context_ptr_t *ss_ctx)
{
	int			status;
	long			ss_shmid;
	block_id		ss_shmsize;
	boolean_t		preserve_snapshot = FALSE;
	sgmnt_addrs		*csa;
	DEBUG_ONLY(sgmnt_data	*csd;)
	node_local_ptr_t	cnl;
	shm_snapshot_ptr_t	ss_shm_ptr;
	snapshot_context_ptr_t	lcl_ss_ctx;
	boolean_t		was_crit;
	struct shmid_ds		ss_shmstat;
	struct stat		stat_buf;
	char			shadow_file[GTM_PATH_MAX];
	ss_proc_status		cur_state;

	assert(NULL != cs_addrs && (NULL != gv_cur_region));
	csa = cs_addrs;
	cnl = csa->nl;
	was_crit = csa->now_crit;
	DEBUG_ONLY(csd = csa->hdr;)

	if (NULL == ss_ctx) /* orphaned cleanup */
	{
		assert(ss_lock_held_by_us(gv_cur_region));
		ss_shm_ptr = (shm_snapshot_ptr_t)(SS_GETSTARTPTR(csa));
		DBG_ENSURE_PTR_WITHIN_SS_BOUNDS(csa, (sm_uc_ptr_t)ss_shm_ptr);
		ss_shmid = ss_shm_ptr->ss_info.ss_shmid;
		CLEANUP_SHADOW_FILE(FALSE, ss_shm_ptr->ss_info.shadow_file, csa);
		CLEANUP_SHARED_MEMORY(ss_shmid, csa);
		ADJUST_SHARED_MEMORY_FIELDS(cnl, ss_shm_ptr);
		return;
	}
	assert(NULL != *ss_ctx);
	lcl_ss_ctx = *ss_ctx;
	cur_state = lcl_ss_ctx->cur_state;
	if (SNAPSHOT_INIT_DONE == cur_state)
	{
		/* Ensure that we don't try to grab_crit and wait for Phase2 commits to complete if we are dying */
		assert(!process_exiting);
		ss_shm_ptr = lcl_ss_ctx->ss_shm_ptr;
		DBG_ENSURE_PTR_WITHIN_SS_BOUNDS(csa, (sm_uc_ptr_t)ss_shm_ptr);
		ss_shmsize = ss_shm_ptr->ss_info.ss_shmsize;
		assert(SNAPSHOTS_IN_PROG(cnl));
		assert(ss_shm_ptr->in_use);
		preserve_snapshot = ss_shm_ptr->preserve_snapshot;
		/* Irrespective of whether there was an error or not, announce GT.M that it should now stop writing before images.
		 * After rel_crit below any process that enters transaction logic will either see cnl->snapshot_in_prog set to FALSE
		 * or a change in cnl->ss_shmid and cnl->ss_shmcycle in which case it will restart itself in t_end or tp_tend.
		 */
		if (!was_crit)
			grab_crit(gv_cur_region, WS_99);
		assert(cs_data == cs_addrs->hdr);
		if (dba_bg == cs_data->acc_meth)
		{
			/* Now that we have crit, wait for any pending phase2 updates to finish. Since phase2 updates happen
			 * outside of crit, we dont want them to keep writing to the snapshot file even after the snapshot
			 * is complete. This is needed as otherwise a GT.M process might see the value of csa->snapshot_in_prog
			 * as TRUE and before it can proceed any further(starvation, maybe), we went ahead and removed the
			 * snapshot file(below). Now, if the GT.M process resumes execution, it might end up writing
			 * the before image to a temporary file which is no longer available.
			 */
			if (cnl->wcs_phase2_commit_pidcnt && !wcs_phase2_commit_wait(csa, NULL))
			{
				assert(FALSE);
				gtm_putmsg_csa(CSA_ARG(csa) VARLSTCNT(7) ERR_COMMITWAITSTUCK, 5, process_id, 1,
						cnl->wcs_phase2_commit_pidcnt, DB_LEN_STR(gv_cur_region));
			}
		}
		ADJUST_SHARED_MEMORY_FIELDS(cnl, ss_shm_ptr);
		if (!was_crit)
			rel_crit(gv_cur_region);
		if (preserve_snapshot)
		{
			assert(lcl_ss_ctx->shadow_vbn >= csd->start_vbn);
			assert(ss_shmsize == (lcl_ss_ctx->shadow_vbn - csd->start_vbn) * DISK_BLOCK_SIZE);
			LSEEKWRITE(lcl_ss_ctx->shdw_fd, 0, (sm_uc_ptr_t)(lcl_ss_ctx->start_shmaddr), ss_shmsize, status);
			if (0 != status)
			{
				util_out_print("!/Failed while writing the snapshot file header. ",
						TRUE);
				assert(FALSE);
			}
		}
	}
	STRCPY(shadow_file, lcl_ss_ctx->shadow_file);
	ss_shmid = lcl_ss_ctx->attach_shmid;
	ss_destroy_context(lcl_ss_ctx);
	/* Do cleanup depending on what state of the snapshot init we are in */
	switch(cur_state)
	{
		case SNAPSHOT_INIT_DONE:
		case AFTER_SHM_CREAT:
			/* Remove shared memory identifier. Note, because of a race condition, it is possible that some GT.M
			 * process is still attached to this identifier. In such a case, the identifier will be marked to be
			 * delted by the OS until number of processes attached to this shared segment becomes zero. GT.M
			 * at the end of each transaction, will try to detach itself from this shared memory segment and will
			 * eventually lead to number of attached processes becoming zero.*/
			CLEANUP_SHARED_MEMORY(ss_shmid, csa);
		/* intentional fall through */
		case AFTER_SHADOW_FIL_CREAT:
			CLEANUP_SHADOW_FILE(preserve_snapshot, shadow_file, csa);
		/* intentional fall through */
		case BEFORE_SHADOW_FIL_CREAT:
			if (NULL != *ss_ctx)
			{
				free(*ss_ctx);
				*ss_ctx = NULL;
			}
			break;
		default:
			assert(FALSE);
			assertpro(FALSE);
	}
	return;
}
