/****************************************************************
 *								*
 *	Copyright 2001, 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 "gtm_string.h"

#include "cdb_sc.h"		/* atleast for cdb_sc_* codes */
#include "copy.h"		/* atleast for the GET_USHORT macros */
#include "gdsroot.h"		/* atleast for gds_file_id used by sgmnt_data in gdsfhead.h */
#include "gdskill.h"		/* atleast for the kill_set and blk_ident structures */
#include "gdsblk.h"		/* atleast for the blk_hdr and rec_hdr structures */
#include "gtm_facility.h"	/* atleast for gdsfhead.h */
#include "fileinfo.h"		/* atleast for gdsfhead.h */
#include "gdsbt.h"		/* atleast for gdsfhead.h */
#include "gdsfhead.h"		/* atleast for cs_addrs, cs_data etc. */
#include "filestruct.h"		/* atleast for the FILE_INFO macro */
#include "gdscc.h"		/* atleast for cw_set_element in tp.h */
#include "jnl.h"		/* atleast for tp.h */
#include "buddy_list.h"		/* atleast for tp.h */
#include "hashtab_int4.h"	/* needed for tp.h */
#include "tp.h"			/* atleast for off_chain */
#include "t_qread.h"
#include "gvcst_bmp_mark_free.h"
#include "gvcst_delete_blk.h"
#include "gvcst_kill_sort.h"
#include "gvcst_expand_free_subtree.h"
#include "rc_cpt_ops.h"
#include "wcs_phase2_commit_wait.h"

GBLREF	gd_region		*gv_cur_region;
GBLREF	sgmnt_addrs		*cs_addrs;
GBLREF	sgmnt_data_ptr_t	cs_data;
GBLREF	sgm_info		*sgm_info_ptr;
GBLREF	uint4			dollar_tlevel;
GBLREF	unsigned char		rdfail_detail;
GBLREF	inctn_opcode_t		inctn_opcode;

error_def(ERR_GVKILLFAIL);
error_def(ERR_IGNBMPMRKFREE);

void	gvcst_expand_free_subtree(kill_set *ks_head)
{
	blk_hdr_ptr_t		bp;
	blk_ident		*ksb;
	block_id		blk;
	boolean_t		flush_cache = FALSE, was_crit;
	cache_rec_ptr_t		cr;
	int			cnt, cycle;
	int4			kill_error, temp_long;
	kill_set		*ks;
    	off_chain		chain;
	rec_hdr_ptr_t		rp, rp1, rtop;
	uint4			save_dollar_tlevel;
	sm_uc_ptr_t		temp_buff;
	sgmnt_addrs		*csa;
	sgmnt_data_ptr_t	csd;
	unsigned short		temp_ushort;
	trans_num		ret_tn;
	inctn_opcode_t		save_inctn_opcode;
	bt_rec_ptr_t		bt;
	unsigned int		level;

	csa = cs_addrs;
	csd = cs_data;
	/* If ever the following assert is removed, "flush_cache" shouldn't be set to FALSE unconditionally as it is now */
	assert(!csd->dsid);	/* see related comment in gvcst_kill before the call to this routine */
	temp_buff = (unsigned char *)malloc(cs_data->blk_size);
	for (ks = ks_head; NULL != ks; ks = ks->next_kill_set)
	{
		for (cnt = 0; cnt < ks->used; ++cnt)
		{
			ksb = &ks->blk[cnt];
			if (0 != ksb->level)
			{
				if (!(was_crit = csa->now_crit))
					grab_crit(gv_cur_region);
#				ifdef UNIX
				if (csa->onln_rlbk_cycle != csa->nl->onln_rlbk_cycle)
				{	/* Concurrent online rollback. We don't want to continue with rest of the logic to add more
					 * blocks to the kill-set and do the gvcst_bmp_mark_free. Return to the caller. Since we
					 * haven't sync'ed the cycles, the next tranasction commit will detect the online rollback
					 * and the restart logic will handle it appropriately.
					 */
					free(temp_buff);
					rel_crit(gv_cur_region);
					send_msg(VARLSTCNT(6) ERR_IGNBMPMRKFREE, 4, REG_LEN_STR(gv_cur_region),
							DB_LEN_STR(gv_cur_region));
					return;
				}
#				endif
				if (dollar_tlevel && ksb->flag)
				{
					chain.flag = 1;
					chain.next_off = 0;
					chain.cw_index = ksb->block;
					assert(SIZEOF(chain) == SIZEOF(blk));
					blk = *(block_id *)&chain;
				} else
					blk = ksb->block;
				if (!(bp = (blk_hdr_ptr_t)t_qread(blk, (sm_int_ptr_t)&cycle, &cr)))
				{	/* This should have worked because t_qread was done in crit */
					free(temp_buff);
					rts_error(VARLSTCNT(4) ERR_GVKILLFAIL, 2, 1, &rdfail_detail);
				}
				if (NULL != cr)
				{	/* It is possible that t_qread returned a buffer from first_tp_srch_status.
					 * In that case, t_qread does not wait for cr->in_tend to be zero since
					 * there is no need to wait as long as all this is done inside of the TP
					 * transaction. But the gvcst_expand_free_subtree logic is special in that it
					 * is done AFTER the TP transaction is committed but with dollar_tlevel still
					 * set to non-zero. So it is possible that cr->in_tend is non-zero in this case.
					 * Hence we need to check if cr->in_tend is non-zero and if so wait for commit
					 * to complete before scanning the block for child-block #s to free.
					 */
					if (dollar_tlevel && cr->in_tend)
						wcs_phase2_commit_wait(csa, cr);
					assert(!cr->twin || cr->bt_index);
					assert((NULL == (bt = bt_get(blk)))
						|| (CR_NOTVALID == bt->cache_index)
						|| (cr == (cache_rec_ptr_t)GDS_REL2ABS(bt->cache_index)) && (0 == cr->in_tend));
				}
				memcpy(temp_buff, bp, bp->bsiz);
				if (!was_crit)
					rel_crit(gv_cur_region);
				for (rp = (rec_hdr_ptr_t)(temp_buff + SIZEOF(blk_hdr)),
					rtop = (rec_hdr_ptr_t)(temp_buff + ((blk_hdr_ptr_t)temp_buff)->bsiz);
				     rp < rtop;
				     rp = rp1)
				{
					GET_USHORT(temp_ushort, &rp->rsiz);
					rp1 = (rec_hdr_ptr_t)((sm_uc_ptr_t)rp + temp_ushort);
					if ((sm_uc_ptr_t)rp1 < (sm_uc_ptr_t)(rp + 1) + SIZEOF(block_id))
					{	/* This should have worked because a local copy was made while crit */
						assert(FALSE);
						kill_error = cdb_sc_rmisalign;
						free(temp_buff);
						rts_error(VARLSTCNT(4) ERR_GVKILLFAIL, 2, 1, &kill_error);
					}
					GET_LONG(temp_long, (block_id_ptr_t)((sm_uc_ptr_t)rp1 - SIZEOF(block_id)));
					if (dollar_tlevel)
					{
						chain = *(off_chain *)&temp_long;
						if ((1 == chain.flag) && ((int)chain.cw_index >= sgm_info_ptr->cw_set_depth))
						{
							assert(sgm_info_ptr->tp_csa == cs_addrs);
							GTMASSERT;
						}
						assert(chain.flag || temp_long < csa->ti->total_blks);
					}
					level = ((blk_hdr_ptr_t)temp_buff)->levl;
					gvcst_delete_blk(temp_long, level - 1, TRUE);
					if ((1 == level) && !dollar_tlevel && cs_data->dsid && !flush_cache)
						rc_cpt_entry(temp_long);	/* Invalidate single block */
				}
				ksb->level = 0;
			} else
			{
				if (!dollar_tlevel && cs_data->dsid && !flush_cache)
					rc_cpt_entry(ksb->block);
			}
		}
		gvcst_kill_sort(ks);
		save_dollar_tlevel = dollar_tlevel;
		assert(1 >= dollar_tlevel);
		dollar_tlevel = 0;	/* temporarily for gvcst_bmp_mark_free */
		GVCST_BMP_MARK_FREE(ks, ret_tn, inctn_invalid_op, inctn_bmp_mark_free_gtm, inctn_opcode, csa)
		dollar_tlevel = save_dollar_tlevel;
	}
	free(temp_buff);
}
