/****************************************************************
 *								*
 * Copyright (c) 2001-2019 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_string.h"

#include "gdsroot.h"
#include "gdskill.h"
#include "gdsblk.h"
#include "gtm_facility.h"
#include "fileinfo.h"
#include "gdsbt.h"
#include "gdsfhead.h"
#include "gdscc.h"
#include "copy.h"
#include "filestruct.h"
#include "jnl.h"
#include "buddy_list.h"		/* needed for tp.h */
#include "hashtab_int4.h"
#include "tp.h"
#include "gvcst_blk_build.h"
#include "gvcst_delete_blk.h"

GBLREF	kill_set	*kill_set_tail;
GBLREF	sgm_info	*sgm_info_ptr;
GBLREF	uint4		dollar_tlevel;
GBLREF  gv_namehead	*gv_target;
GBLREF  boolean_t	horiz_growth;
GBLREF	sgmnt_addrs	*cs_addrs;
GBLREF	unsigned int	t_tries;
#ifdef VMS
GBLREF	boolean_t	tp_has_kill_t_cse; /* cse->mode of kill_t_write or kill_t_create got created in this transaction */
#endif

void	gvcst_delete_blk(block_id blk, int level, boolean_t committed)
{
	cw_set_element	*cse, *old_cse;
	kill_set	*ks;
	off_chain	chain;
	srch_blk_status	*tp_srch_status;
	uint4		iter;
	ht_ent_int4	*tabent;
	DEBUG_ONLY(
	boolean_t	block_already_in_hist = FALSE;
	)
	DCL_THREADGBL_ACCESS;

	SETUP_THREADGBL_ACCESS;
	horiz_growth = FALSE;
	if (!dollar_tlevel)
		ks = kill_set_tail;
	else
	{
		PUT_BLK_ID(&chain, blk);
		tp_srch_status = NULL;
		if (chain.flag == 1)
		{
			assert((SIZEOF(int) * 8) >= CW_INDEX_MAX_BITS);
			tp_get_cw(sgm_info_ptr->first_cw_set, (int)chain.cw_index, &cse);
		} else
		{
			if (NULL != (tabent = lookup_hashtab_int4(sgm_info_ptr->blks_in_use, (uint4 *)&blk)))
				tp_srch_status = (srch_blk_status *)tabent->value;
			cse = tp_srch_status ? tp_srch_status->cse : NULL;
		}
		if (cse)
		{
			if (!committed)
			{
				assert(dollar_tlevel >= cse->t_level);
				if (NULL != cse->high_tlevel)
				{	/* this is possible only if this block is already part of either of the tree paths
					 * in gv_target->hist or alt_hist (see gvcst_kill.c) and got a newer cse created as
					 * part of a gvcst_kill_blk on this block in gvcst_kill.c a little before the call
					 * to gvcst_kill_blk of a parent block which in turn decided to delete this block
					 * as part of its records that are getting removed. this is a guaranteed restartable
					 * situation. since gvcst_delete_blk does not return any status, proceed with the
					 * newer cse and return. the restart will be later detected by tp_hist.
					 */
					 cse = cse->high_tlevel;
					 DEBUG_ONLY(TREF(donot_commit) |= DONOTCOMMIT_GVCST_DELETE_BLK_CSE_TLEVEL;)
					 DEBUG_ONLY(block_already_in_hist = TRUE;)
				}
				assert(!cse->high_tlevel);
				if (cse->t_level != dollar_tlevel)
				{	/* this part of the code is almost similar to that in t_write(),
					 * any changes in one should be reflected in the other */
					horiz_growth = TRUE;
					old_cse = cse;
					cse = (cw_set_element *)get_new_free_element(sgm_info_ptr->tlvl_cw_set_list);
					memcpy(cse, old_cse, SIZEOF(cw_set_element));
					cse->low_tlevel = old_cse;
					cse->high_tlevel = NULL;
					old_cse->high_tlevel = cse;
					cse->t_level = dollar_tlevel;
					assert(2 == (SIZEOF(cse->undo_offset) / SIZEOF(cse->undo_offset[0])));
					assert(2 == (SIZEOF(cse->undo_next_off) / SIZEOF(cse->undo_next_off[0])));
					for (iter = 0; iter < 2; iter++)
						cse->undo_next_off[iter] = cse->undo_offset[iter] = 0;
					if (old_cse->done)
					{
						assert(NULL != old_cse->new_buff);
						cse->new_buff = (unsigned char *)get_new_free_element(sgm_info_ptr->new_buff_list);
						memcpy(cse->new_buff, old_cse->new_buff, ((blk_hdr_ptr_t)old_cse->new_buff)->bsiz);
					} else
						cse->new_buff = NULL;
					assert(!block_already_in_hist);
					/* tp_hist (called from gvcst_kill) updates "->cse" fields for all blocks that are
					 * part of the left or right histories of the M-kill. But this block is not one of
					 * those. Hence tp_srch_status->cse has to be updated here explicitly.
					 */
					if (tp_srch_status)
						tp_srch_status->cse = (void *)cse;
				}
				switch (cse->mode)
				{
				case gds_t_create:
					cse->mode = kill_t_create;
					VMS_ONLY(tp_has_kill_t_cse = TRUE;)
					if (level == 0)
					    return;
					break;
				case gds_t_write:
					cse->mode = kill_t_write;
					VMS_ONLY(tp_has_kill_t_cse = TRUE;)
					break;
				default:
					;
				}
			} else
			{
				switch(cse->mode)
				{
				case kill_t_create:
					VMS_ONLY(assert(tp_has_kill_t_cse));
					if (level == 0)
						return;
					break;
				default:
					if (chain.flag)
					{
						chain.flag = 0;
						blk = cse->blk;
					}
					break;
				}
			}
		}
		ks = sgm_info_ptr->kill_set_tail;
		if (NULL == ks)		/* Allocate first kill set to sgm_info_ptr block */
		{
			ks = sgm_info_ptr->kill_set_tail = sgm_info_ptr->kill_set_head = (kill_set *)malloc(SIZEOF(kill_set));
			assert(NULL == sgm_info_ptr->kip_csa);
			ks->used = 0;
			ks->next_kill_set = NULL;
		}
	}
	while (ks->used >= BLKS_IN_KILL_SET)
	{
		if (ks->next_kill_set == NULL)
		{
			ks->next_kill_set = (kill_set *)malloc(SIZEOF(kill_set));
			ks->next_kill_set->used = 0;
			ks->next_kill_set->next_kill_set = NULL;
		}
		ks = kill_set_tail
		   = ks->next_kill_set;
	}
	ks->blk[ks->used].level = (level) ? 1 : 0;
	if (!dollar_tlevel || !chain.flag)
	{
		assert((CDB_STAGNATE > t_tries) || (blk < cs_addrs->ti->total_blks));
		ks->blk[ks->used].block = blk;
		ks->blk[ks->used].flag = 0;
	} else
	{
		ks->blk[ks->used].block = chain.cw_index;
		ks->blk[ks->used].flag = chain.flag;
	}
	++ks->used;
	assert(ks->used <= BLKS_IN_KILL_SET);
}
