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

/****************************************************************
 *      dbcertify_certify_phase2.c - Database certification phase 2
 *
 *      - Verify phase 1 input file.
 *      - Locate and open database after getting standalong access.
 *      - Read the identified blocks in and if they are still too
 *        large, split them.
 *      - Certify the database as "clean" if no errors encountered.
 *
 *      Note: Most routines in this utility are self-contained
 *            meaning they do not reference GT.M library routines
 *            (with some notable exceptions). This is because
 *            phase-2 is going to run against V4 format databases
 *            but any linked routines would be compiled for V5
 *            databases.
****************************************************************/

#include "mdef.h"

#ifdef VMS
#include <descrip.h>
#include <rms.h>
#include <ssdef.h>
#endif

#include <errno.h>
#include "gtm_stat.h"
#include "gtm_ctype.h"
#include "gtm_stdio.h"
#include "gtm_string.h"
#include "gtm_unistd.h"
#include "gtm_stdlib.h"
#include "gtm_fcntl.h"

#ifdef __MVS__
#include "gtm_zos_io.h"
#endif
#include "gtmio.h"
#include "cli.h"
#include "copy.h"
#include "iosp.h"
#include "gdsroot.h"
#include "v15_gdsroot.h"
#include "gtm_facility.h"
#include "fileinfo.h"
#include "gdsbt.h"
#include "v15_gdsbt.h"
#include "gdsfhead.h"
#include "v15_gdsfhead.h"
#include "filestruct.h"
#include "v15_filestruct.h"
#include "gdsblk.h"
#include "gdsbml.h"
#include "gdscc.h"
#include "bmm_find_free.h"
#include "gdsblkops.h"
#include "bit_set.h"
#include "bit_clear.h"
#include "min_max.h"
#include "gtmmsg.h"
#ifdef VMS
#  include "is_file_identical.h"
#endif
#include "error.h"
#include "mupip_exit.h"
#include "util.h"
#include "dbcertify.h"

GBLREF	char_ptr_t		update_array, update_array_ptr;
GBLREF	uint4			update_array_size;
GBLREF	VSIG_ATOMIC_T		forced_exit;			/* Signal came in while we were in critical section */
GBLREF	int4			exi_condition;
GBLREF	phase_static_area	*psa_gbl;

boolean_t dbc_split_blk(phase_static_area *psa, block_id blk_num, enum gdsblk_type blk_type, v15_trans_num tn, int blk_levl);
void dbc_flush_fhead(phase_static_area *psa);
void dbc_read_p1out(phase_static_area *psa, void *obuf, int olen);

error_def(ERR_DEVOPENFAIL);
error_def(ERR_FILENOTFND);
error_def(ERR_DBCSCNNOTCMPLT);
error_def(ERR_DBCBADFILE);
error_def(ERR_DBCMODBLK2BIG);
error_def(ERR_DBCINTEGERR);
error_def(ERR_DBCNOEXTND);
error_def(ERR_DBCDBCERTIFIED);
error_def(ERR_DBCNOTSAMEDB);
error_def(ERR_DBCDBNOCERTIFY);
error_def(ERR_DBCREC2BIGINBLK);
error_def(ERR_SYSCALL);
error_def(ERR_TEXT);
error_def(ERR_BITMAPSBAD);
error_def(ERR_MUPCLIERR);
error_def(ERR_GBLOFLOW);
ZOS_ONLY(error_def(ERR_BADTAG);)

/* The final certify phase of certification process */
void dbcertify_certify_phase(void)
{
	int		save_errno, len, rc, restart_cnt, maxkeystructsize;
	uint4		rec_num;
	char_ptr_t	errmsg;
	boolean_t	restart_transaction, p1rec_read;
	unsigned short	buff_len;
	int		tmp_cmpc;
	char		ans[2];
	unsigned char	dbfn[MAX_FN_LEN + 1];
	file_control	*fc;
	phase_static_area *psa;
	ZOS_ONLY(int	realfiletag;)

	psa = psa_gbl;
	DBC_DEBUG(("DBC_DEBUG: Beginning certification phase\n"));
	psa->phase_one = FALSE;
	UNIX_ONLY(atexit(dbc_certify_phase_cleanup));
	psa->block_depth = psa->block_depth_hwm = -1;		/* Initialize no cache */

	/* Check parsing results */
	if (CLI_PRESENT == cli_present("BLOCKS"))
	{
		if (!cli_get_hex("BLOCKS", &psa->blocks_to_process))
			EXIT(EXIT_FAILURE);		/* Error message already raised */
	} else
		psa->blocks_to_process = MAXTOTALBLKS_V4;
	if (CLI_PRESENT == cli_present("TEMPFILE_DIR"))
	{	/* Want to put temp files in this directory */
		buff_len = SIZEOF(psa->tmpfiledir) - 1;
		if (0 == cli_get_str("TEMPFILE_DIR", (char_ptr_t)psa->tmpfiledir, &buff_len))
			mupip_exit(ERR_MUPCLIERR);
	}
	psa->keep_temp_files = (CLI_PRESENT == cli_present("KEEP_TEMPS"));
	buff_len = SIZEOF(psa->outfn) - 1;
	if (0 == cli_get_str("P1OUTFILE", (char_ptr_t)psa->outfn, &buff_len))
		mupip_exit(ERR_MUPCLIERR);

	/* Open phase-1 output file (our input file) */
	psa->outfd = OPEN((char_ptr_t)psa->outfn, O_RDONLY RMS_OPEN_BIN);
	if (FD_INVALID == psa->outfd)
	{
		save_errno = errno;
		if (save_errno == ENOENT)
			rts_error_csa(CSA_ARG(NULL) VARLSTCNT(4) ERR_FILENOTFND, 2, RTS_ERROR_STRING((char_ptr_t)psa->outfn));
		else
		{
			errmsg = STRERROR(save_errno);
			rts_error_csa(CSA_ARG(NULL) VARLSTCNT(8) ERR_DEVOPENFAIL, 2, RTS_ERROR_STRING((char_ptr_t)psa->outfn),
				  ERR_TEXT, 2, RTS_ERROR_STRING(errmsg));
		}
	}
#	ifdef __MVS__
	if (-1 == gtm_zos_tag_to_policy(psa->outfd, TAG_BINARY, &realfiletag))
		TAG_POLICY_GTM_PUTMSG((char_ptr_t)psa->outfn, errno, realfiletag, TAG_BINARY);
#	endif
	dbc_read_p1out(psa, &psa->ofhdr, SIZEOF(p1hdr));		/* Read phase 1 output file header */
	if (0 != memcmp(psa->ofhdr.p1hdr_tag, P1HDR_TAG, SIZEOF(psa->ofhdr.p1hdr_tag)))
		rts_error_csa(CSA_ARG(NULL) VARLSTCNT(4) ERR_DBCBADFILE, 2, RTS_ERROR_STRING((char_ptr_t)psa->outfn));
	if (0 == psa->ofhdr.tot_blocks)
		/* Sanity check that the output file was finished and completed */
		rts_error_csa(CSA_ARG(NULL) VARLSTCNT(4) ERR_DBCSCNNOTCMPLT, 2, RTS_ERROR_STRING((char_ptr_t)psa->outfn));
	assert(0 != psa->ofhdr.tn);

	/* Check if region name still associates to the same file */
	dbc_find_database_filename(psa, psa->ofhdr.regname, dbfn);

	/* Notify user this is a critical change and give them the opportunity to abort */
	util_out_print("--------------------------------------------------------------------------------", FLUSH);
	util_out_print("You must have a backup of database !AD before you proceed!!", FLUSH,
		       RTS_ERROR_STRING((char_ptr_t)psa->ofhdr.dbfn));
	util_out_print("An abnormal termination can damage the database while doing the certification !!", FLUSH);
	util_out_print("Proceeding will also turn off replication and/or journaling if enabled", FLUSH);
	util_out_print("--------------------------------------------------------------------------------", FLUSH);
	util_out_print("Proceed? [y/n]:", FLUSH);
	SCANF("%1s", ans);	/* We only need one char, any more would overflow our buffer */
	if ('y' != ans[0] && 'Y' != ans[0])
	{
		util_out_print("Certification phase aborted\n", FLUSH);
		return;
	}
	util_out_print("Certification phase for database !AD beginning", FLUSH, RTS_ERROR_STRING((char_ptr_t)psa->ofhdr.dbfn));

	/* Build database structures */
	MALLOC_INIT(psa->dbc_gv_cur_region, SIZEOF(gd_region));
	MALLOC_INIT(psa->dbc_gv_cur_region->dyn.addr, SIZEOF(gd_segment));
	psa->dbc_gv_cur_region->dyn.addr->acc_meth = dba_bg;
	len = STRLEN((char_ptr_t)psa->ofhdr.dbfn);
	strcpy((char_ptr_t)psa->dbc_gv_cur_region->dyn.addr->fname, (char_ptr_t)dbfn);
	psa->dbc_gv_cur_region->dyn.addr->fname_len = len;
	FILE_CNTL_INIT(psa->dbc_gv_cur_region->dyn.addr);
	psa->dbc_cs_data = malloc(SIZEOF(*psa->dbc_cs_data));
	fc = psa->fc = psa->dbc_gv_cur_region->dyn.addr->file_cntl;

	/* Initialize for db processing - open and read in file-header, get "real" filename for comparison */
	dbc_init_db(psa);
	if (0 != strcmp((char_ptr_t)psa->dbc_gv_cur_region->dyn.addr->fname, (char_ptr_t)psa->ofhdr.dbfn))
		/* File name change means db was moved or at least is not as it was when it was scanned */
		rts_error_csa(CSA_ARG(NULL) VARLSTCNT(1) ERR_DBCNOTSAMEDB);
	if (psa->ofhdr.tn > psa->dbc_cs_data->trans_hist.curr_tn)
		rts_error_csa(CSA_ARG(NULL) VARLSTCNT(1) ERR_DBCNOTSAMEDB);
	psa->max_blk_len = psa->dbc_cs_data->blk_size - psa->dbc_cs_data->reserved_bytes;

	/* Initialize maximum key we may need later if we encounter gvtroot blocks */
	maxkeystructsize = SIZEOF(dbc_gv_key) + MAX_DBC_KEY_SZ - 1;
	MALLOC_INIT(psa->max_key, maxkeystructsize);
	psa->max_key->top = maxkeystructsize;
	psa->max_key->gvn_len = 1;
	*psa->max_key->base = (unsigned char)0xFF;
	/* Key format: 0xFF, 0x00, 0x00 : This is higher than any valid key would be */
	psa->max_key->end = MAX_DBC_KEY_SZ - 1;

	/* Allocate update array based on fileheader values */
	psa->dbc_cs_data->max_update_array_size = psa->dbc_cs_data->max_non_bm_update_array_size =
		(uint4)ROUND_UP2(MAX_NON_BITMAP_UPDATE_ARRAY_SIZE(psa->dbc_cs_data), UPDATE_ARRAY_ALIGN_SIZE);
	psa->dbc_cs_data->max_update_array_size += (int4)(ROUND_UP2(MAX_BITMAP_UPDATE_ARRAY_SIZE, UPDATE_ARRAY_ALIGN_SIZE));
	update_array = malloc(psa->dbc_cs_data->max_update_array_size);
	update_array_size = psa->dbc_cs_data->max_update_array_size;

	/* Now to the real work -- Read and split each block the phase 1 file recorded that still needs
	 * to be split (concurrent updates may have "fixed" some blocks).
	 */
	psa->hint_blk = psa->hint_lcl = 1;
	restart_transaction = p1rec_read = FALSE;
	restart_cnt = 0;
	for (rec_num = 0; rec_num < psa->ofhdr.blk_count || 0 < psa->gvtroot_rchildren_cnt;)
	{	/* There is the possibility that we are restarting the processing of a given record. In
		 * that case we will not read the next record in but process what is already in the buffer.
		 * This can occur if we have extended the database.
		 */
		if (!restart_transaction)
		{	/* First to check is if we have any queued gvtroot_rchildren to process (described elsewhere).
			 * If we have these, we process them now without bumping the record count.
			 */
			p1rec_read = FALSE;	/* Assume we did NOT read from the file */
			if (0 < psa->gvtroot_rchildren_cnt)
			{
				psa->gvtroot_rchildren_cnt--;
				memcpy((char *)&psa->rhdr, (char *)&psa->gvtroot_rchildren[psa->gvtroot_rchildren_cnt],
				       SIZEOF(p1rec));
				psa->gvtrchildren++;	/* counter */
				DBC_DEBUG(("DBC_DEBUG: Pulling p1rec from queued gvtroot_rchildren array (%d)\n",
					   psa->gvtroot_rchildren_cnt));
			} else
			{	/* Normal processing - read record from phase one file */
				if (rec_num == psa->blocks_to_process)
				{	/* Maximum records processed */
					DBC_DEBUG(("DBC_DEBUG: Maximum records to process limit reached "
						   "- premature exit to main loop\n"));
					break;
				}
				DBC_DEBUG(("DBC_DEBUG: ****************** Reading new p1out record (%d) *****************\n",
					   (rec_num + 1)));
				dbc_read_p1out(psa, &psa->rhdr, SIZEOF(p1rec));
				if (0 != psa->rhdr.akey_len)
				{	/* This module does not need the ascii key so just bypass it if it exists */
					if (0 != psa->rhdr.blk_levl || SIZEOF(psa->rslt_buff) < psa->rhdr.akey_len )
						assertpro(FALSE);		/* Must be corrupted file? */
					dbc_read_p1out(psa, (char_ptr_t)psa->rslt_buff, psa->rhdr.akey_len);
				}
				p1rec_read = TRUE;	/* Note, not reset by restarted transaction */
			}
			/* Don't want to reset the high water mark on a restarted transaction */
			if (psa->block_depth > psa->block_depth_hwm)
				psa->block_depth_hwm = psa->block_depth;	/* Keep track of maximum indexes we have used */
			restart_cnt = 0;
		} else
		{
			++restart_cnt;
			if (MAX_RESTART_CNT < restart_cnt)
				assertpro(FALSE);			/* No idea what could cause this.. */
			DBC_DEBUG(("DBC_DEBUG: ****************** Restarted transaction (%d) *****************\n",
				   (rec_num + 1)));
			/* "restart_transaction" is either set or cleared by dbc_split_blk() below */
		}
		assert((int)psa->rhdr.blk_type);
		/* Note assignment in "if" below */
		if (restart_transaction = dbc_split_blk(psa, psa->rhdr.blk_num, psa->rhdr.blk_type,
							psa->rhdr.tn, psa->rhdr.blk_levl))
			psa->block_depth_hwm = -1;	/* Zaps cache so all blocks are re-read */
		else if (p1rec_read)			/* If rec processed was from scan phase, bump record counter */
			rec_num++;
	} /* for each record in phase-1 output file or each restart or each queued rh child */

	/* Reaching this point, the database has been updated, with no errors. We can now certify
	 * this database as ready for the current version of GT.M
	 */
	util_out_print("", FLUSH);	/* New line for below message in case MUPIP extension leaves prompt */
	if (0 == psa->blk_process_errors)
	{
		if (psa->blocks_to_process != rec_num)
		{
			((sgmnt_data_ptr_t)psa->dbc_cs_data)->certified_for_upgrade_to = GDSV6;
			psa->dbc_fhdr_dirty = TRUE;
			gtm_putmsg_csa(CSA_ARG(NULL) VARLSTCNT(6) ERR_DBCDBCERTIFIED, 4,
				RTS_ERROR_STRING((char_ptr_t)psa->ofhdr.dbfn), RTS_ERROR_LITERAL("GT.M V5"));
		} else
		{
			DBC_DEBUG(("DBC_DEBUG: Database certification bypassed due to records to process limit being reached\n"));
		}
	} else
		gtm_putmsg_csa(CSA_ARG(NULL) VARLSTCNT(4) ERR_DBCDBNOCERTIFY, 2, RTS_ERROR_STRING((char_ptr_t)psa->ofhdr.dbfn));

	dbc_flush_fhead(psa);
	dbc_close_db(psa);
	CLOSEFILE_RESET(psa->outfd, rc);	/* resets "psa->outfd" to FD_INVALID */

	PRINTF("\n");
	PRINTF("Total blocks in scan phase file --   %12d [0x%08x]\n", psa->ofhdr.blk_count, psa->ofhdr.blk_count);
	PRINTF("Blocks bypassed ------------------   %12d [0x%08x]\n", psa->blks_bypassed, psa->blks_bypassed);
	PRINTF("Blocks processed -----------------   %12d [0x%08x]\n", psa->blks_processed, psa->blks_processed);
	PRINTF("Blocks read ----------------------   %12d [0x%08x]\n", psa->blks_read, psa->blks_read);
	PRINTF("Blocks read from cache -----------   %12d [0x%08x]\n", psa->blks_cached, psa->blks_cached);
	PRINTF("Blocks updated -------------------   %12d [0x%08x]\n", psa->blks_updated, psa->blks_updated);
	PRINTF("Blocks created -------------------   %12d [0x%08x]\n", psa->blks_created, psa->blks_created);
	PRINTF("GVTROOT right children processed -   %12d [0x%08x]\n", psa->gvtrchildren, psa->gvtrchildren);

	/* Release resources */
	free(update_array);
	free(psa->dbc_cs_data);
#ifdef VMS
	/* Some extra freeing of control blocks on VMS */
	if (NULL != FILE_INFO(psa->dbc_gv_cur_region)->fab)
		free(FILE_INFO(psa->dbc_gv_cur_region)->fab);
	if (NULL != FILE_INFO(psa->dbc_gv_cur_region)->nam)
	{
		if (NULL != FILE_INFO(psa->dbc_gv_cur_region)->nam->nam$l_esa)
			free(FILE_INFO(psa->dbc_gv_cur_region)->nam->nam$l_esa);
		free(FILE_INFO(psa->dbc_gv_cur_region)->nam);
	}
	if (NULL != FILE_INFO(psa->dbc_gv_cur_region)->xabfhc)
		free(FILE_INFO(psa->dbc_gv_cur_region)->xabfhc);
	if (NULL != FILE_INFO(psa->dbc_gv_cur_region)->xabpro)
		free(FILE_INFO(psa->dbc_gv_cur_region)->xabpro);
#endif
	free(psa->dbc_gv_cur_region->dyn.addr->file_cntl->file_info);
	free(psa->dbc_gv_cur_region->dyn.addr->file_cntl);
	free(psa->dbc_gv_cur_region->dyn.addr);
	free(psa->dbc_gv_cur_region);
	psa->dbc_gv_cur_region = NULL;
	if (psa->first_rec_key)
		free(psa->first_rec_key);
	free(psa);
	assert(psa == psa_gbl);
	psa_gbl = NULL;	/* nullify the global variable now that it has been freed */
}

/* Routine to handle the processing (splitting) of a given database block. If the current block process needs
 * to be restarted, this function returns TRUE. else if processing completed normally, returns FALSE.
 *
 * Routine notes:
 *
 * This routine implements a "simplistic" mini database engine. It is "simplistic" in the regards to fact that it
 * doesn't need to worry about concurrency issues. It also has one design assumption that we will NEVER add a
 * record to a level 0 block (either DT or GVT). Because of this assumption, many complications from gvcst_put(), on
 * which it is largely based, were non-issues and were removed (e.g. no TP). This routine has its own concepts of
 * "cache", cw_set elements, update arrays, etc. Following is a brief description of how these things are implemented
 * in this routine:
 *
 * The primary control block in this scheme is the block_info block which serves as a cache record, change array anchor,
 * gv_target, and so on. In short, everything that is known about a given database block is contained in this one
 * structure. There is an array of these structures with the name "blk_set" which is a global variable array dimensioned
 * at a thoroughly outrageous amount for the worst case scenario.
 *
 * There are areas within the blk_set array that are worth describing:
 *
 * - The block_depth global variable always holds the top in use index into blk_set.
 * - blk_set[0] describes the block that was fed to us from the phase 1 scan. It is the primary block that needs to
 *   be split. If nothing needs to happen to it, we go to the next record and blk_set[0] get a new block in it.
 * - Starting with blk_set[1] through blk_set[bottom_tree_index] are first the directory tree (DT) blocks and then
 *   (if primary was a GVT block) the global variable tree (GVT) blocks.
 * - Starting with blk_set[bottom_tree_index + 1] through blk_set[bottom_created_index] are newly created blocks during
 *   split processing.
 * - Starting with blk_set[bottom_created_index + 1] through blk_set[block_depth] are local bit map blocks that are being
 *   modified for the "transaction".
 *
 * This engine has a very simple cache mechanism. If a block we need is somewhere in the blk_set array (a global variable
 * block_depth_hwm maintains a high water mark), the cache version is used rather than forcing a re-read from disk. It is
 * fairly simple but seems to save a lot of reads, especially of the directory tree and the local bit_maps.
 *
 * Like gvcst_put(), once we have the blocks from the tree loaded, they are processed in reverse order as a split in one
 * block requires a record to be inserted into the parent block. We start with the primary block (blk_set[0]) and then
 * move to blk_set[bottom_tree_index] and work backwards from there until either we get to a block for which there are
 * no updates or we hit a root block (GVT or DT depending) at which time we are done with the primary update loop.
 *
 * After performing the block splits and creating new blocks, we double check that we have room to hold them all. If not,
 * we make a call to MUPIP EXTEND to extend the database for us. Since this means we have to close the file and give up
 * our locks on it, we also restart the transaction and force all blocks to be re-read from disk.
 *
 * Once assured we have sufficient free blocks, we start at blk_set[bottom_created_index] and work down to
 * blk_set[bottom_tree_index + 1] allocating and assigning block numbers to the created blocks. Part of this process also
 * puts the block numbers into places where the update arrays will pick them up when the referencing blocks are built.
 *
 * Once all the new blocks have been assigned, we loop through blk_set[bottom_tree_index] to blk_set[0] and create the
 * new versions of the blocks (for those blocks marked as being updated). A note here is that this engine does not build
 * update array entries for bitmap blocks, preferring instead to just update the local bitmap block buffers directly.
 *
 * The last major loop is to write to disk all the new and changed blocks to disk. There is no processing but simple IO
 * in this loop to minimize the potential of something going wrong. There is no recovery at this point. If this loop fails
 * in mid-stream, the database is toast.
 */
boolean_t dbc_split_blk(phase_static_area *psa, block_id blk_num, enum gdsblk_type blk_type, v15_trans_num tn, int blk_levl)
{
	int		blk_len, blk_size, restart_cnt, save_block_depth, tmp_blk_levl;
	int		gvtblk_index, dtblk_index, blk_index, bottom_tree_index;
	int		curr_blk_len, curr_blk_levl, curr_rec_len, ins_rec_len;
	int		curr_rec_shrink, curr_rec_offset, blks_this_lmap;
	int		prev_rec_offset, new_blk_len, new_rec_len, remain_offset, remain_len, blk_seg_cnt;
	int		new_lh_blk_len, new_rh_blk_len, created_blocks, extent_size;
	int		local_map_max, lbm_blk_index, lcl_blk, curr_rec_cmpc;
	int		bplmap;
	int4		lclmap_not_full;
	uint4		total_blks;
	boolean_t	dummy_bool;
	boolean_t	got_root, level_0, completed, insert_point, restart_transaction;
	blk_segment	*bs_ptr, *bs1, *blk_sega_p, *blk_array_top;
	rec_hdr_ptr_t	ins_rec_hdr, next_rec_hdr, new_star_hdr;
	dbc_gv_key	*last_rec_key;
	uchar_ptr_t	blk_endp, blk_p, bmp_ptr, chr_p, cp1, lcl_map_p, mid_point, new_blk_p, next_rec_p, rec_p;
	unsigned short	us_rec_len;
	v15_trans_num	curr_tn;
	block_id	blk_ptr;
	block_id	allocated_blk_num, bitmap_blk_num, bitnum, *lhs_block_id_p, *rhs_block_id_p;
	block_info	*blk_set_p, *blk_set_new_p, *blk_set_prnt_p, *blk_set_bm_p, *blk_set_rhs_p;
	block_info	restart_blk_set;
	enum gdsblk_type	tmp_blk_type;
	DEBUG_ONLY(
		boolean_t	first_time = FALSE;
	)

	/* First order of business is to read the required block in. Reset block_depth so this read happens in psa->blk_set[0].
	 * Needed because following code relies on the block-to-be-split being psa->blk_set[0].
	 */
	psa->block_depth = -1;
	dbc_read_dbblk(psa, blk_num, blk_type);
	blk_size = psa->dbc_cs_data->blk_size;	/* BLK_FINI macro needs a local copy */
	assert(0 == psa->block_depth);
	/* Now that we have read the block, is it still "too-full"? If so and it is a data block and the tn has changed since the
	 * scan, force the user to rerun the scan for the 3 possible reasons explained in the below comment. Defer index blocks
	 * because, when we insert a record before the first record in the block, we may put the new record into the new (LH)
	 * sibling and leave the entire existing block unmodified, except for a new transaction number, as the RH side. The net
	 * result is a too full unsplit index block. This never happens with a created block, Also, gvtroot blocks (a special case
	 * of an index block) require special processing before we can decide if they are still "too-full" and in need of a split.
	 */
	blk_p = psa->blk_set[0].old_buff;
	assert(blk_p);
	blk_len = psa->blk_set[0].blk_len;
	GET_ULONG(curr_tn, &((v15_blk_hdr_ptr_t)blk_p)->tn);
	if ((UNIX_ONLY(8) VMS_ONLY(9) > blk_size - blk_len) && (curr_tn != tn) && (gdsblk_gvtleaf == blk_type))
	{
		/* Leaf block has been modified since the scan: Three possible reasons it is still too-full (i.e. needs split).
		 * 1) The user was playing with reserved bytes and set it too low allowing some
		 *		large blocks to be created we did not know about (but thankfully just caught).
		 * 2) User ran a mupip journal recover -backward after running phase 1 that re-introduced some too-large
		 *		blocks. This is a documented no-no but we have no way to enforce it on V4.
		 * 3) There was a TN reset done.
		 *
		 * All three of these causes require a rerun of the scan phase.
		 */
		rts_error_csa(CSA_ARG(NULL) VARLSTCNT(3) ERR_DBCMODBLK2BIG, 1, blk_num);
	}
	/* Isolate the full key in the first record of the block. If needed, descend down blocks with only *-key */
	dbc_init_key(psa, &psa->first_rec_key);
	tmp_blk_levl = psa->blk_set[0].blk_levl;
	tmp_blk_type = blk_type;
	new_blk_p = blk_p;
	do
	{
		rec_p = new_blk_p + SIZEOF(v15_blk_hdr);
		dbc_find_key(psa, psa->first_rec_key, rec_p, tmp_blk_levl);
		if (!tmp_blk_levl && psa->first_rec_key->end)
		{	/* Read key and is a leaf level block. Can stop search. If non-leaf block, we cannot stop search
			 * since it is possible this is a DT index block. Global names stored in DT index blocks are likely
			 * to not be found when searched for later in "dbc_find_dtblk".
			 */
			if (new_blk_p != blk_p)
			{	/* We read additional blocks in this do/while loop. Since that confuses
				 * "dbc_find_dtblk" (and maybe others), undo effects of the read by resetting the cache.
				 */
				psa->block_depth = 0;
				psa->block_depth_hwm = 0;
			}
			break;	/* Found a non-* key. Proceed with search */
		}
		/* Descend down until we find non-* key */
		assert(tmp_blk_levl);
		GET_ULONG(blk_ptr, rec_p + ((rec_hdr_ptr_t)rec_p)->rsiz - SIZEOF(block_id));
		blk_index = dbc_read_dbblk(psa, blk_ptr, tmp_blk_type);
		/* Note that we cannot do a "tmp_blk_levl--" here but instead should get the child block level from the block
		 * header. This is because it is possible the block we started out with is no longer marked BUSY in the bitmap
		 * (because of updates after the "dbcertify scan" but before the "dbcertify certify"). Since we don't check
		 * a block's bitmap status first (to avoid performance overhead of reading bitmap for every block that is
		 * processed by certify), we need to add robustness within dbcertify to handle that.
		 */
		tmp_blk_levl = psa->blk_set[blk_index].blk_levl;
		new_blk_p = psa->blk_set[blk_index].old_buff;
		switch(tmp_blk_type)
		{
			case gdsblk_dtindex:
			case gdsblk_dtleaf:
			case gdsblk_dtroot:
				tmp_blk_type = (tmp_blk_levl ? gdsblk_dtindex : gdsblk_dtleaf);
				break;
			case gdsblk_gvtindex:
			case gdsblk_gvtleaf:
				tmp_blk_type = (tmp_blk_levl ? gdsblk_gvtindex : gdsblk_gvtleaf);
				break;
			default:
				assert(FALSE && tmp_blk_type);
				break;						/* NOTREACHED */
		}
	} while (TRUE);
	assert(psa->first_rec_key->end);
	psa->first_rec_key->gvn_len = USTRLEN((char_ptr_t)psa->first_rec_key->base);	/* The GVN we need to lookup in the DT */
	/* Possibilities at this point:
	 * 1) We are looking for a DT (directory tree) block, and pass the block level we are searching for as a parameter
	 * 2) We are looking for a GVT (global variable tree) block and -1 as the block level as we need a complete search
	 *     of the leaf level DT in order to find the GVN.
	 *
	 * If (1), once we arrive at the block, validation is complete and verification and (later) block splitting can begin
	 * If (2), we need to take the pointer from the found DT record which points to the GVT root block and start our
	 * search again from there using the level from the original block as a stopping point. If our target block was a
	 * gvtroot block, we don't need to traverse the GVT tree to find it, as the directory tree already identified it
	 */
	switch(blk_type)
	{
		case gdsblk_dtindex:
		case gdsblk_dtleaf:
		case gdsblk_dtroot:
			/* Since our search is to end in the dt tree, stop when we get to the requisite level */
			blk_index = dbc_find_dtblk(psa, psa->first_rec_key, blk_levl);
			if (0 > blk_index)
			{	/* Integrity error encountered or record not found. We cannot proceed */
				assert(FALSE);
				rts_error_csa(CSA_ARG(NULL) VARLSTCNT(8)
					ERR_DBCINTEGERR, 2, RTS_ERROR_STRING((char_ptr_t)psa->ofhdr.dbfn),
					ERR_TEXT, 2, RTS_ERROR_LITERAL("Unable to find index (DT) record for an existing global"));
			}
			break;
		case gdsblk_gvtindex:
		case gdsblk_gvtleaf:
			/* Search all the way down to lvl 0 to get a dtleaf block */
			dtblk_index = dbc_find_dtblk(psa, psa->first_rec_key, 0);
			if (0 > dtblk_index)
			{	/* Integrity error encountered or record not found. We cannot proceed */
				assert(FALSE);
				rts_error_csa(CSA_ARG(NULL) VARLSTCNT(8)
					ERR_DBCINTEGERR, 2, RTS_ERROR_STRING((char_ptr_t)psa->ofhdr.dbfn),
					ERR_TEXT, 2, RTS_ERROR_LITERAL("Unable to locate DT leaf (root) block"));
			}
			if (0 == dtblk_index)
			{	/* Located the to-be-split gvtindex/gvtleaf block in the directory tree.
				 * This is possible only if the gvt block became FREE (due to a KILL and then
				 * got reused as part of a directory tree update done either by M updates since
				 * the scan or by this dbcertify certify itself). In either case, this block
				 * does not need processing anymore as it would not be too-full when it became
				 * BUSY again.
				 */
				DBC_DEBUG(("DBC_DEBUG: Block not processed as it was in GVT but is now in DT\n"));
				psa->blks_bypassed++;
				psa->blks_read++;
				if (psa->blk_set[0].found_in_cache)
					psa->blks_cached++;
				return FALSE;	/* No restart needed */
			}
			assert(0 == ((v15_blk_hdr_ptr_t)psa->blk_set[dtblk_index].old_buff)->levl);
			/* Note level 0 directory blocks can have collation data in them but it would be AFTER
			 * the block pointer which is the first thing in the record after the key.
			 */
			GET_ULONG(blk_ptr, (psa->blk_set[dtblk_index].curr_rec + SIZEOF(rec_hdr)
					    + psa->blk_set[dtblk_index].curr_blk_key->end + 1
					    - EVAL_CMPC((rec_hdr *)psa->blk_set[dtblk_index].curr_rec)));
			gvtblk_index = dbc_read_dbblk(psa, blk_ptr, gdsblk_gvtroot);
			assert(-1 != gvtblk_index);
			/* If our target block was not the gvtroot block we just read in then we keep scanning for our
			 * target record. Otherwise, the scan stops here.
			 */
			if (0 != gvtblk_index)
			{
				blk_index = dbc_find_record(psa, psa->first_rec_key, gvtblk_index, blk_levl, gdsblk_gvtroot);
				if (0 > blk_index)
				{
					if (-1 == blk_index)
					{	/* Integrity error encountered. We cannot proceed */
						assert(FALSE);
						rts_error_csa(CSA_ARG(NULL) VARLSTCNT(8) ERR_DBCINTEGERR, 2,
							  RTS_ERROR_STRING((char_ptr_t)psa->ofhdr.dbfn),
							  ERR_TEXT, 2,
							  RTS_ERROR_LITERAL("Unable to find index record for an existing global"));
					} else if (-2 == blk_index)
					{	/* Record was not found. Record has been deleted since we last
						 * found it. Elicits a warning message in DEBUG mode but is otherwise ignored.
						 */
						DBC_DEBUG(("DBC_DEBUG: Block split of blk 0x%x bypassed because its "
							   "key could not be located in the GVT\n", blk_num));
						psa->blks_bypassed++;
						psa->blks_read += psa->block_depth;
						/* Only way to properly update the count of cached records is to run the list
						 * and check them.
						 */
						for (blk_index = psa->block_depth, blk_set_p = &psa->blk_set[blk_index];
						     0 <= blk_index;
						     --blk_index, --blk_set_p)
						{	/* Check each block we read */
							if (gdsblk_create != blk_set_p->usage && blk_set_p->found_in_cache)
								psa->blks_cached++;
						}
						return FALSE;	/* No restart necessary */
					} else
						assertpro(FALSE);
				}
			} else
			{	/* This is a gvtroot block and is the subject of our search */
				blk_index = gvtblk_index;
				assert(gdsblk_gvtroot == psa->blk_set[0].blk_type);
			}
			break;
		default:
			assertpro(FALSE);
	}
	/* The most recently accessed block (that terminated the search) should be the block we are looking for
	 * (which should have been found in the cache as block 0. If not, there is an integrity error and we should
	 * not continue. The only exception though is if this block has been modified since the scan. If so, it is
	 * more likely the block's status (part-of-GVT OR part-of-DT OR marked-free-in-bitmap) changed since the scan
	 * than that there is an integrity error. If the block tn has changed and is no longer "too-full", assume db
	 * is clean in this case and bypass processing this block.
	 */
	if (0 != blk_index)
	{
		if ((curr_tn != tn) && (8 <= (blk_size - blk_len)))
		{
			DBC_DEBUG(("DBC_DEBUG: Block not processed as its GVT/DT/Bitmap status changed since scan\n"));
			psa->blks_bypassed++;
			psa->blks_read++;
			if (psa->blk_set[0].found_in_cache)
				psa->blks_cached++;
			return FALSE;	/* No restart needed */
		}
		/* Integrity error encountered. We cannot proceed */
		assert(FALSE);
		rts_error_csa(CSA_ARG(NULL) VARLSTCNT(8) ERR_DBCINTEGERR, 2, RTS_ERROR_STRING((char_ptr_t)psa->ofhdr.dbfn),
			  ERR_TEXT, 2,
			  RTS_ERROR_LITERAL("Did not locate record in same block as we started searching for"));
	}
	/* If this is a gvtroot type block, we have some extra processing to do. Following is a description of
	 * the issue we are addressing here. If a gvtroot block is "too large" and was too large at the time
	 * the scan was run, it will of course be identified by the scan as too large. Prior to running the scan,
	 * the reserved bytes field was set so no more too-full blocks can be created. But if a gvtroot block is
	 * identified by the scan and subsequently has to be split by normal GTM processing before the certify
	 * can be done, the too-full part of the block can (in totality) end up in the right hand child of the
	 * gvtroot block (not obeying the reserved bytes rule). But the gvtroot block is the only one that was
	 * identified by the scan and certify may now miss the too-full block in the right child. Theoretically,
	 * the entire right child chain of the gvtroot block can be too full. Our purpose here is that when we
	 * have identified a gvtblock as being too full, we pause here to read the right child chain coming off
	 * of that block all the way down to (but not including) block level 0. Each of these blocks will be
	 * processed to check for being too full. The way we do this is to run the chain and build p1rec entries
	 * in the gvtroot_rchildren[] array. When we are at the top of the processing loop, we will take these
	 * array entries over records from the phase one input file. We only load up the array if it is empty.
	 * Otherwise, the assumption is that we are re-processing and the issue has already been handled.
	 */
	blk_set_p = &psa->blk_set[0];
	if ((gdsblk_gvtroot == blk_set_p->blk_type) && (0 == psa->gvtroot_rchildren_cnt))
	{
		DBC_DEBUG(("DBC_DEBUG: Encountered gvtroot block (block %d [0x%08x]), finding/queueing children\n",
			   blk_set_p->blk_num, blk_set_p->blk_num));
		save_block_depth = psa->block_depth;	/* These reads are temporary and should not remain in cache so
							 * we will restore block_depth after we are done.
							 */
		/* Attempting to locate the maximum possible key for this database should read the list of right
		 * children into the cache. Pretty much any returncode from dbc_find_record is possible. We usually
		 * aren't going to find the global which may come up as not found or an integrity error or it could
		 * possibly even be found. Just go with what it gives us. Not much verification we can do on it.
		 */
		blk_index = dbc_find_record(psa, psa->max_key, 0, 0, gdsblk_gvtroot);
		/* Pull children (if any) out of cache and put into queue for later processing */
		for (blk_index = save_block_depth + 1;
		     blk_index <= psa->block_depth && gdsblk_gvtleaf != psa->blk_set[blk_index].blk_type;
		     ++blk_index, ++psa->gvtroot_rchildren_cnt)
		{	/* Fill in p1rec type entry in gvtroot_rchildren[] for later */
			DBC_DEBUG(("DBC_DEBUG: Right child block: blk_index: %d  blk_num: %d [0x%08x]  blk_levl: %d\n",
				   blk_index, psa->blk_set[blk_index].blk_num, psa->blk_set[blk_index].blk_num,
				   psa->blk_set[blk_index].blk_levl));
			psa->gvtroot_rchildren[psa->gvtroot_rchildren_cnt].tn = psa->blk_set[blk_index].tn;
			psa->gvtroot_rchildren[psa->gvtroot_rchildren_cnt].blk_num = psa->blk_set[blk_index].blk_num;
			psa->gvtroot_rchildren[psa->gvtroot_rchildren_cnt].blk_type = psa->blk_set[blk_index].blk_type;
			psa->gvtroot_rchildren[psa->gvtroot_rchildren_cnt].blk_levl = psa->blk_set[blk_index].blk_levl;
			psa->gvtroot_rchildren[psa->gvtroot_rchildren_cnt].akey_len = 0;
		}
		psa->block_depth = save_block_depth;
		blk_index = 0;	/* reset to start *our* work in the very first block */
	}
	/* Now we have done the gvtroot check if we were going to. If this particular block has sufficient room in it
	 * we don't need to split it of course.
	 */
	if (UNIX_ONLY(8) VMS_ONLY(9) <= blk_size - blk_len)
	{	/* This block has room now - no longer need to split it */
		DBC_DEBUG(("DBC_DEBUG: Block not processed as it now has sufficient room\n"));
		psa->blks_bypassed++;
		psa->blks_read++;
		if (psa->blk_set[0].found_in_cache)
			psa->blks_cached++;
		return FALSE;	/* No restart needed */
	}

	/* Beginning of block update/split logic. We need to process the blocks in the reverse order from the
	 * tree path. This means blk_set[0] which is actually the block we want to split must be the first
	 * in our path. We then need to process the block array backwards in case the changes made to those
	 * records cause subsequent splits.
	 *
	 * First order of business is to find a suitable place to split this block .. Run through
	 * the records in the block until we are "halfway" through the block. Split so that the first record
	 * (after the first) whose end point is in the "second half" of the block will be the first record of
	 * the second half or right hand side block after the split. This makes sure that the left side has at
	 * least one record in it. We already know that this block has at least 2 records in it or it would not
	 * need splitting.
	 */
	rec_p = blk_p + SIZEOF(v15_blk_hdr);
	blk_set_p->curr_rec = rec_p;
	dbc_find_key(psa, blk_set_p->curr_blk_key, rec_p, blk_set_p->blk_levl);
	GET_USHORT(us_rec_len, &((rec_hdr *)rec_p)->rsiz);
	curr_rec_len = us_rec_len;
	next_rec_p = rec_p + curr_rec_len;
	blk_set_p->curr_match = 0;		/* First record of block always cmpc 0 */
	blk_len = ((v15_blk_hdr_ptr_t)blk_p)->bsiz;
	blk_endp = blk_p + blk_len;
	mid_point = blk_p + blk_size / 2;
	do
	{	/* Keep scanning the next record until you find the split point which is the first record that straddles the
		 * mid-point of the block. This loop makes sure the prev_key and curr_key fields are correctly set when we
		 * enter the processing loop below.
		 */
		blk_set_p->prev_match = blk_set_p->curr_match;
		memcpy(blk_set_p->prev_blk_key, blk_set_p->curr_blk_key, (SIZEOF(dbc_gv_key) + blk_set_p->curr_blk_key->end));
		rec_p = next_rec_p;	/* Must be at least one record in LHS and one in RHS */
		blk_set_p->prev_rec = blk_set_p->curr_rec;
		blk_set_p->curr_rec = rec_p;
		GET_USHORT(us_rec_len, &((rec_hdr *)rec_p)->rsiz);
		curr_rec_len = us_rec_len;
		dbc_find_key(psa, blk_set_p->curr_blk_key, rec_p, blk_set_p->blk_levl);
		blk_set_p->curr_match = EVAL_CMPC((rec_hdr *)rec_p);
		next_rec_p = rec_p + curr_rec_len;
		if (next_rec_p >= blk_endp)	/* We have reached the last record in the block. Cannot skip anymore. */
			break;
		if (next_rec_p >= mid_point)
		{	/* The current record straddles the mid-point of the almost-full block. This is most likely going
			 * to be the split point. If splitting at the current record causes the RHS block to continue to
			 * be too-full and there is still room in the LHS block we will scan one more record in this loop.
			 * Scanning this one more record should make the RHS block no longer too-full. This is asserted below.
			 */
			/* Compute the sizes of the LHS and RHS blocks assuming the current record moves into each of them */
			if (blk_set_p->blk_levl)
			{	/* Index block. The current record is changed into a *-key (a simple star key rec) */
				new_lh_blk_len = (int)((rec_p - blk_p) + BSTAR_REC_SIZE);
			} else
			{	/* Data block. Always simple split (no inserted record) */
				new_lh_blk_len = (int)(next_rec_p - blk_p);
				assert(gdsblk_gvtleaf == blk_set_p->blk_type || gdsblk_dtleaf == blk_set_p->blk_type);
			}
			assert(0 < new_lh_blk_len);
			/* assert that the LHS block without the current record is guaranteed not to be too-full */
			assert((new_lh_blk_len - (next_rec_p - rec_p)) <= psa->max_blk_len);
			/* Right hand side has key of curr_rec expanded since is first key of blcok */
			new_rh_blk_len = (int)(SIZEOF(v15_blk_hdr) + blk_set_p->curr_match + blk_len - (rec_p - blk_p) );
			assert(0 < new_rh_blk_len);
			if ((new_rh_blk_len <= psa->max_blk_len) || (new_lh_blk_len > psa->max_blk_len))
				break;
			assert(FALSE == first_time);	/* assert we never scan more than one record past mid-point of the block */
			DEBUG_ONLY(first_time = TRUE;)
		}
	} while (TRUE);
	assert((rec_p - blk_p) < ((v15_blk_hdr_ptr_t)blk_p)->bsiz);

	/* Block processing loop */
	bottom_tree_index = psa->block_depth;	/* Record end of the tree in case need bit map blocks later */
	update_array_ptr = update_array;		/* Reset udpate array */
	DBC_DEBUG(("DBC_DEBUG: Beginning split processing loop\n"));
	for (completed = FALSE; !completed;)
	{	/* Backwards process until we hit a block with no changes to it */
		DBC_DEBUG(("DBC_DEBUG: ******** Top of blk process loop for block index %d\n", blk_index));
		assert(0 <= blk_index);
		blk_set_p = &psa->blk_set[blk_index];
		assert(blk_set_p->blk_len == ((v15_blk_hdr_ptr_t)blk_set_p->old_buff)->bsiz);
		assert(blk_set_p->blk_levl == ((v15_blk_hdr_ptr_t)blk_set_p->old_buff)->levl);
		curr_blk_len = blk_set_p->blk_len;
		curr_blk_levl = blk_set_p->blk_levl;
		if (0 != blk_set_p->ins_rec.ins_key->end)
		{	/* We only ever insert index records */
			ins_rec_len = blk_set_p->ins_rec.ins_key->end + 1 + SIZEOF(block_id);
		} else
			ins_rec_len = 0;
		/* If ins_rec_len has a non-zero value, then we need to reset the values for prev_match and
		 * key_match. These values were computed using the original scan key as their basis. Now we
		 * are using these fields to insert a new key. The positioning is still correct but the
		 * number of matching characters is potentially different.
		 */
		if (ins_rec_len)
		{
			if (0 != blk_set_p->prev_blk_key->end)
			{	/* There is a "previous record" */
				insert_point = dbc_match_key(blk_set_p->prev_blk_key, blk_set_p->blk_levl,
							     blk_set_p->ins_rec.ins_key, &blk_set_p->prev_match);
				assert(!insert_point);	/* This is prior to insert point (sanity check) */
			}
			insert_point = dbc_match_key(blk_set_p->curr_blk_key, blk_set_p->blk_levl,
						     blk_set_p->ins_rec.ins_key, &blk_set_p->curr_match);
			assert(insert_point);	/* This is supposed to *be* the insert point */
		}
		/* Make convenient copies of some commonly used record fields */
		curr_rec_cmpc = EVAL_CMPC((rec_hdr *)blk_set_p->curr_rec);
		curr_rec_shrink = blk_set_p->curr_match - curr_rec_cmpc;
		curr_rec_offset = (int)(blk_set_p->curr_rec - blk_set_p->old_buff);
		GET_USHORT(us_rec_len, &((rec_hdr *)blk_set_p->curr_rec)->rsiz);
		curr_rec_len = us_rec_len;
		prev_rec_offset = (int)(blk_set_p->prev_rec - blk_set_p->old_buff);
		got_root = (gdsblk_dtroot == blk_set_p->blk_type) || (gdsblk_gvtroot == blk_set_p->blk_type);
		/* Decide if this record insert (if an insert exists) will cause a block split or not. If this
		 * is the first block in the tree (the one we got from the phase 1 file), there will be no insert.
		 * If we find a block that does not need to change, we are done and can exit the loop.
		 * This differs from the regular GT.M runtime which must keep checking even the split blocks
		 * but since we never add data to a level 0 block being split, we will never create split-off
		 * blocks that themselves are (still) too full.
		 */
		assert(gdsblk_read == blk_set_p->usage);
		new_blk_len = (int)(ins_rec_len ? (curr_blk_len + curr_rec_cmpc + SIZEOF(rec_hdr) + ins_rec_len
					      - blk_set_p->prev_match - blk_set_p->curr_match)
			       : curr_blk_len);		/* No inserted rec, size does not change */
		if (new_blk_len <= psa->max_blk_len)
		{	/* "Simple" case .. we do not need a block split - only (possibly) a record added. Note
			 * that this is the only path where there may not be a "previous" record so we
			 * have to watch for that possibility.
			 */
			assert(0 != blk_index);		/* Never insert a record into target blk so should never be here */
			/* In this path we should always have an inserted record length. We should have detected we
			 * were done in an earlier loop iteration.
			 */
			assert(ins_rec_len);
			DBC_DEBUG(("DBC_DEBUG: Block index %d is a simple update\n", blk_index));
			/* We must have an insert at this point and since we only ever insert records into
			 * index blocks, we must be in that situation.
			 */
			assert(0 != curr_blk_levl);
			blk_set_p->usage = gdsblk_update;	/* It's official .. blk is being modified */
			/* We have a record to insert into this block but no split is needed */
			BLK_INIT(bs_ptr, bs1);
			blk_set_p->upd_addr = bs1;		/* Save address of our update array */
			if (0 != blk_set_p->prev_blk_key->end)
			{	/* First piece is block prior to the record + key + value */
				BLK_SEG(bs_ptr,
					blk_set_p->old_buff + SIZEOF(v15_blk_hdr),
					(curr_rec_offset - SIZEOF(v15_blk_hdr)));
			}
			BLK_ADDR(ins_rec_hdr, SIZEOF(rec_hdr), rec_hdr);
			/* Setup new record header */
			new_rec_len = (int)(SIZEOF(rec_hdr) + ins_rec_len - blk_set_p->prev_match);
			ins_rec_hdr->rsiz = new_rec_len;
			SET_CMPC(ins_rec_hdr, blk_set_p->prev_match);
			BLK_SEG(bs_ptr, (sm_uc_ptr_t)ins_rec_hdr, SIZEOF(rec_hdr));
			/* Setup key */
			BLK_ADDR(cp1,
				 blk_set_p->ins_rec.ins_key->end + 1 - blk_set_p->prev_match,
				 unsigned char);
			memcpy(cp1, blk_set_p->ins_rec.ins_key->base + blk_set_p->prev_match,
			       blk_set_p->ins_rec.ins_key->end + 1 - blk_set_p->prev_match);
			BLK_SEG(bs_ptr, cp1, blk_set_p->ins_rec.ins_key->end + 1 - blk_set_p->prev_match);
			/* Setup value (all index records have value of size "block_id". The proper value is
			 * either there already or will be when we go to commit these changes.
			 */
			BLK_SEG(bs_ptr, (sm_uc_ptr_t)&blk_set_p->ins_rec.blk_id, SIZEOF(block_id));
			/* For index blocks, we know that since a star key is the last record in the block (which is the
			 * last record that can be curr_rec) that there is a trailing portion of the block we need to output.
			 */
			BLK_ADDR(next_rec_hdr, SIZEOF(rec_hdr), rec_hdr);	/* Replacement rec header */
			next_rec_hdr->rsiz = curr_rec_len - curr_rec_shrink;
			SET_CMPC(next_rec_hdr, blk_set_p->curr_match);
			BLK_SEG(bs_ptr, (sm_uc_ptr_t)next_rec_hdr, SIZEOF(rec_hdr));
			remain_offset = curr_rec_shrink + SIZEOF(rec_hdr); /* Where rest of record + any further records begin */
			remain_len = curr_blk_len - curr_rec_offset;
			BLK_SEG(bs_ptr,
				blk_set_p->curr_rec + remain_offset,
				remain_len - remain_offset);
			if (0 == BLK_FINI(bs_ptr, bs1))
				assertpro(FALSE);
			assert(blk_seg_cnt == new_blk_len);
			DBC_DEBUG(("DBC_DEBUG: Stopping block scan after simple update (no further inserts to previous lvls)\n"));
			completed = TRUE;
			break;
		} else
		{	/* The block is either already too large or would be too large when the record is inserted
			 * and so it must be split.
			 *
			 * There are two different ways a block can be split. It can either be split so that:
			 *
			 * (1) the inserted record is at the end of the left block or,
			 *
			 * (2) the record is the first record in the right half.
			 *
			 * Compute the left/right block sizes for these two options and see which one does not
			 * force a secondary block split (one of them must be true here unlike in GT.M code because
			 * here we are NEVER adding a record to a level 0 block, we only split lvl 0 blocks as
			 * needed). Note that the case where we are splitting a level 0 block with no record insert
			 * is treated as an unremarkable variant of option (1) as described above.
			 *
			 * Follow the conventions of gvcst_put (LHS to new block, RHS to old block):
			 *
			 * (1) If we are inserting the record into the lefthand side then a new split-off block will
			 * receive the first part of the block including the record. The remainder of the block is
			 * placed into the current (existing) block.
			 *
			 * (2) If we are putting the record into the righthand side, then a new split-off block will
			 * receive the first part of the block. The new record plus the remainder of the block is
			 * placed into the current block.
			 *
			 * The sole exception to the above is if a root block (either DT or GVT) is being split. In
			 * that case, BOTH the LHS and RHS become NEW blocks and the root block is (a) increased in
			 * level and (b) contains only entries for the two created blocks.
			 *
			 * Note that gvcst_put has several additional checks and balances here that we are forgoing
			 * such as making sure the blocks are as balanced as possible, concurrency concerns, etc. They
			 * add un-needed complications to this one-time code. Any inefficiencies here can be undone
			 * with a pass of MUPIP REORG.
			 */
			DBC_DEBUG(("DBC_DEBUG: Block index %d needs to be split\n", blk_index));
			/*  First up is split so that the inserted record (if any) is the last record in the left
			 *  hand block. Note if this is an index block, the last record must be a star key rec as per
			 *  option (1) above.
			 */
			if (curr_blk_levl)
				/* Index block. Two cases: (a) We are adding a key to the end in which case it is just
				 * a simple star key rec or (b) No record is being added so the previous record is
				 * changed into a star key rec.
				 */
				new_lh_blk_len = (int)(curr_rec_offset + BSTAR_REC_SIZE
					- (ins_rec_len ? 0 : (blk_set_p->curr_rec - blk_set_p->prev_rec)));
			else
			{
				/* Data block. Always simple split (no inserted record) */
				new_lh_blk_len = curr_rec_offset;
				assert(gdsblk_gvtleaf == blk_set_p->blk_type || gdsblk_dtleaf == blk_set_p->blk_type);
			}
			assert(0 < new_lh_blk_len);
			/* Right hand side has key of curr_rec expanded since is first key of blcok */
			new_rh_blk_len = (int)(SIZEOF(v15_blk_hdr) + EVAL_CMPC((rec_hdr *)blk_set_p->curr_rec) +
					       (curr_blk_len - curr_rec_offset));
			assert(0 < new_rh_blk_len);
			/* Common initialization */
			++psa->block_depth;			/* Need a new block to split into */
			if (MAX_BLOCK_INFO_DEPTH <= psa->block_depth)
				assertpro(FALSE);
			DBC_DEBUG(("DBC_DEBUG: Block index %d used for newly created split (lhs) block\n", psa->block_depth));
			blk_set_new_p = &psa->blk_set[psa->block_depth];
			dbc_init_blk(psa, blk_set_new_p, -1, gdsblk_create, new_lh_blk_len, curr_blk_levl);
			if (got_root)
				/* If root, the LHS sub-block is a different type */
				blk_set_new_p->blk_type = (gdsblk_gvtroot == blk_set_p->blk_type)
					? gdsblk_gvtindex : gdsblk_dtindex;
			else
				blk_set_new_p->blk_type = blk_set_p->blk_type;
			/* Complete our LHS block */
			BLK_INIT(bs_ptr, bs1);		/* Our new block to create */
			blk_set_new_p->upd_addr = bs1;
			level_0 = (0 == curr_blk_levl);
			/* See if they fit in their respective blocks */
			if (level_0 || (new_lh_blk_len <= psa->max_blk_len) && (new_rh_blk_len <= psa->max_blk_len))
			{	/* Method 1 - record goes to left-hand side */
				DBC_DEBUG(("DBC_DEBUG: Method 1 block lengths: lh: %d  rh: %d  max_blk_len: %d\n",
					   new_lh_blk_len, new_rh_blk_len, psa->max_blk_len));
				/* New update array for new block */
				if (level_0)
				{	/* Level 0 block, we are only splitting it -- never adding a record */
					assert(curr_rec_offset <= psa->max_blk_len);
					BLK_SEG(bs_ptr, blk_set_p->old_buff + SIZEOF(v15_blk_hdr),
						curr_rec_offset - SIZEOF(v15_blk_hdr));
					assert(0 == ins_rec_len);	/* Never insert records to lvl0 */
					if (new_rh_blk_len > psa->max_blk_len)
					{	/* Case of a data block that has a DBCREC2BIG error unnoticed by DBCERTIFY SCAN.
						 * Should not happen normally. But in case it does in production, we will handle
						 * it by NOT certifying the database and requiring a rerun of the SCAN
						 */
						assert(FALSE);
						gtm_putmsg_csa(CSA_ARG(NULL) VARLSTCNT(6) ERR_DBCREC2BIGINBLK, 4,
							   blk_num, psa->dbc_cs_data->max_rec_size,
							   psa->dbc_gv_cur_region->dyn.addr->fname_len,
							   psa->dbc_gv_cur_region->dyn.addr->fname);
						psa->blk_process_errors++;	/* must be zero to certify db at end */
					}
				} else
				{	/* Index block -- may or may not be adding a record.
					 * If adding a record, the inserted record becomes a star key record.
					 * If not adding a record the last record is morphed into a star key record.
					 */
					BLK_SEG(bs_ptr, blk_set_p->old_buff + SIZEOF(v15_blk_hdr),
						(ins_rec_len ? curr_rec_offset : prev_rec_offset)
						- SIZEOF(v15_blk_hdr));
					BLK_ADDR(new_star_hdr, SIZEOF(rec_hdr), rec_hdr);
					new_star_hdr->rsiz = BSTAR_REC_SIZE;
					SET_CMPC(new_star_hdr, 0);
					BLK_SEG(bs_ptr, (uchar_ptr_t)new_star_hdr, SIZEOF(rec_hdr));
					BLK_SEG(bs_ptr, (ins_rec_len ? (uchar_ptr_t)&blk_set_p->ins_rec.blk_id
							 : (blk_set_p->prev_rec + SIZEOF(rec_hdr)
							    + blk_set_p->prev_blk_key->end + 1
							    - EVAL_CMPC((rec_hdr *)blk_set_p->prev_rec))),
						SIZEOF(block_id));
				}
				/* Complete our LHS block */
				if (0 == BLK_FINI(bs_ptr, bs1))
					assertpro(FALSE);
				assert(blk_seg_cnt == new_lh_blk_len);
				/* Remember key of last record in this block */
				if (0 == ins_rec_len)
					last_rec_key = blk_set_p->prev_blk_key;
				else
					last_rec_key = blk_set_p->ins_rec.ins_key;
				if (!got_root)
				{	/* New block created, insert record to it in parent block. To do this we create
					 * a record with the last key in this LH block to be inserted between curr_rec
					 * and prev_rec of the parent block.
					 */
					if (0 == blk_index)
						blk_set_prnt_p = &psa->blk_set[bottom_tree_index]; /* Cycle back up to parent */
					else
						blk_set_prnt_p = blk_set_p - 1;
					assert(blk_set_prnt_p != &psa->blk_set[0]);
					assert(NULL != last_rec_key);
					/* Note: We do not need the "+ 1" on the key length since SIZEOF(dbc_gv_key) contains
					 * the first character of the key so the "+ 1" to get the last byte of the key is
					 * already integrated into the length
					 */
					memcpy(blk_set_prnt_p->ins_rec.ins_key, last_rec_key,
					       SIZEOF(dbc_gv_key) + last_rec_key->end);
					/* Setup so that creation of the blk_set_new_p block can then set its block id into
					 * our parent block's insert rec buffer which will be made part of the inserted
					 * record at block build time
					 */
					blk_set_new_p->ins_blk_id_p = &blk_set_prnt_p->ins_rec.blk_id;
					blk_set_rhs_p = blk_set_p;	/* Use original block for rhs */
					blk_set_rhs_p->usage = gdsblk_update;
				} else
				{	/* Have root block: need to put the RHS into a new block too */
					DBC_DEBUG(("DBC_DEBUG: Splitting root block, extra block to be created\n"));
					++psa->block_depth;			/* Need a new block to split into */
					if (MAX_BLOCK_INFO_DEPTH <= psa->block_depth)
						assertpro(FALSE);
					blk_set_rhs_p = &psa->blk_set[psa->block_depth];
					dbc_init_blk(psa, blk_set_rhs_p, -1, gdsblk_create, new_rh_blk_len, curr_blk_levl);
					/* We will put the pointers to both this block and the RHS we build next
					 * into the original root block -- done later when RHS is complete.
					 * If root, the RHS sub-block is a different type.
					 */
					blk_set_rhs_p->blk_type = (gdsblk_gvtroot == blk_set_p->blk_type)
						? gdsblk_gvtindex : gdsblk_dtindex;
				}
				/**** Now build RHS into either current or new block ****/
				BLK_INIT(bs_ptr, bs1);
				blk_set_rhs_p->upd_addr = bs1;		/* Block building roadmap.. */
				BLK_ADDR(next_rec_hdr, SIZEOF(rec_hdr), rec_hdr);
				next_rec_hdr->rsiz = curr_rec_len + curr_rec_cmpc;
				SET_CMPC(next_rec_hdr, 0);
				BLK_SEG(bs_ptr, (uchar_ptr_t)next_rec_hdr, SIZEOF(rec_hdr));
				/* Copy the previously compressed part of the key out of curr_rec.
				 * Note, if this key is a star rec key, nothing is written because cmpc is zero.
				 */
				if (curr_rec_cmpc)
				{
					BLK_ADDR(cp1, curr_rec_cmpc, unsigned char);
					memcpy(cp1, blk_set_p->curr_blk_key->base, curr_rec_cmpc);
					BLK_SEG(bs_ptr, cp1, curr_rec_cmpc);
				}
				/* Remainder of existing block */
				BLK_SEG(bs_ptr,
					blk_set_p->curr_rec + SIZEOF(rec_hdr),
					curr_blk_len - curr_rec_offset - SIZEOF(rec_hdr));
				/* Complete update array */
				if (0 == BLK_FINI(bs_ptr, bs1))
					assertpro(FALSE);
				assert(blk_seg_cnt == new_rh_blk_len);
			} else
			{	/* Recompute sizes for inserted record being in righthand block as per
				   method (2) */
				DBC_DEBUG(("DBC_DEBUG: Method 1 created invalid blocks: lh: %d  rh: %d  "
					   "max_blk_len: %d -- trying method 2\n", new_lh_blk_len, new_rh_blk_len,
					   psa->max_blk_len));
				/* By definition we *must* have an inserted record in this path */
				assert(0 != ins_rec_len);
				/* New block sizes - note because we *must* be inserting a record in this method,
				 * the only case considered here is when we are operating on an index block.
				 */
				assert(!level_0);
				/* Last record turns into star key record */
				new_lh_blk_len = (int)(curr_rec_offset + BSTAR_REC_SIZE -
						       (blk_set_p->curr_rec - blk_set_p->prev_rec) );
				assert(0 < new_lh_blk_len);
				new_rh_blk_len = (int)(SIZEOF(v15_blk_hdr) + SIZEOF(rec_hdr) +
						       ins_rec_len  + curr_blk_len - (curr_rec_offset) - curr_rec_shrink);
				assert(0 < new_rh_blk_len);
				if (new_lh_blk_len > psa->max_blk_len || new_rh_blk_len > psa->max_blk_len)
				{	/* This is possible if we are inserting a record into a block (and thus we are
					 * not picking the insertion point) and the insertion point is either the first or
					 * next-to-last record in the block such that neither method 1 nor 2 can create blocks
					 * of acceptable size. In this case, although this problem block is likely on the
					 * list of blocks to process, we cannot wait and thus must perform the split now.
					 * To do that, we call this same routine recursively with the necessary parms to
					 * process *THIS* block. Since this will destroy all the structures we had built
					 * up, signal a transaction restart which will re-read everything and should allow
					 * the transaction we were processing to proceed.
					 */
					if (curr_blk_len <= psa->max_blk_len)
						/* Well, that wasn't the problem, something else is wrong */
						rts_error_csa(CSA_ARG(NULL) VARLSTCNT(8) ERR_DBCINTEGERR, 2,
							  RTS_ERROR_STRING((char_ptr_t)psa->ofhdr.dbfn),
							  ERR_TEXT, 2, RTS_ERROR_LITERAL("Unable to split block appropriately"));
					/* If we do have to restart, we won't be able to reinvoke dbc_split_blk() with the
					 * parms taken from the current blk_set_p as that array will be overwritten by the
					 * recursion. Save the current blk_set_p so we can use it in a restartable context.
					 */
					restart_blk_set = *blk_set_p;
					for (restart_cnt = 0, restart_transaction = TRUE;
					     restart_transaction;
					     ++restart_cnt)
					{
						if (MAX_RESTART_CNT < restart_cnt)
							assertpro(FALSE);		/* No idea what could cause this */
						DBC_DEBUG(("DBC_DEBUG: *** *** Recursive call to handle too large block 0x%x\n",
							   restart_blk_set.blk_num));
						psa->block_depth_hwm = -1;	/* Zaps cache so all blocks are re-read */
						restart_transaction = dbc_split_blk(psa, restart_blk_set.blk_num,
										    restart_blk_set.blk_type, restart_blk_set.tn,
										    restart_blk_set.blk_levl);
					}
					return TRUE;	/* This transaction must restart */
				}
				DBC_DEBUG(("DBC_DEBUG: Method 2 block lengths: lh: %d  rh: %d  max_blk_len: %d\n",
					   new_lh_blk_len, new_rh_blk_len, psa->max_blk_len));
				/* Start building (new) LHS block - for this index record, the record before the split
				 * becomes a new *-key.
				 * Note:  If the block split was caused by our appending the new record
				 * to the end of the block, this code causes the record PRIOR to the
				 * current *-key to become the new *-key.
				 */
				BLK_SEG(bs_ptr, blk_set_p->old_buff + SIZEOF(v15_blk_hdr), prev_rec_offset - SIZEOF(v15_blk_hdr));
				/* Replace last record with star key rec */
				BLK_ADDR(new_star_hdr, SIZEOF(rec_hdr), rec_hdr);
				new_star_hdr->rsiz = BSTAR_REC_SIZE;
				SET_CMPC(new_star_hdr, 0);
				BLK_SEG(bs_ptr, (uchar_ptr_t)new_star_hdr, SIZEOF(rec_hdr));
				/* Output pointer from prev_rec as star key record's value */
				BLK_SEG(bs_ptr,	blk_set_p->curr_rec - SIZEOF(block_id), SIZEOF(block_id));
				/* Complete our LHS block */
				if (0 == BLK_FINI(bs_ptr, bs1))
					assertpro(FALSE);
				assert(blk_seg_cnt == new_lh_blk_len);
				if (!got_root)
				{
					/* New block created, insert record to it in parent block. To do this we create
					 * a record with the last key in this LH block to be inserted between curr_rec
					 * and prev_rec of the parent block.
					 */
					if (0 == blk_index)
						blk_set_prnt_p = &psa->blk_set[bottom_tree_index]; /* Cycle back up to parent */
					else
						blk_set_prnt_p = blk_set_p - 1;
					assert(blk_set_prnt_p != &psa->blk_set[0]);
					assert(NULL != blk_set_p->prev_blk_key);
					/* Note: We do not need the "+ 1" on the key length since SIZEOF(dbc_gv_key) contains
					 * the first character of the key so the "+ 1" to get the last byte of the key is
					 * already integrated into the length
					 */
					memcpy(blk_set_prnt_p->ins_rec.ins_key, blk_set_p->prev_blk_key,
					       SIZEOF(dbc_gv_key) + blk_set_p->prev_blk_key->end);
					/* Setup so that creation of the blk_set_new_p block can then set its block id into
					 * our parent block's insert rec buffer which will be made part of the inserted
					 * record at block build time
					 */
					blk_set_new_p->ins_blk_id_p = &blk_set_prnt_p->ins_rec.blk_id;
					blk_set_rhs_p = blk_set_p;	/* Use original block for rhs */
					blk_set_rhs_p->usage = gdsblk_update;
				} else
				{	/* Have root block: need to put the RHS into a new block too */
					DBC_DEBUG(("DBC_DEBUG: Splitting root block, extra block to be created\n"));
					++psa->block_depth;			/* Need a new block to split into */
					if (MAX_BLOCK_INFO_DEPTH <= psa->block_depth)
						assertpro(FALSE);
					blk_set_rhs_p = &psa->blk_set[psa->block_depth];
					/* Key for last record in the LHS block used to (re)construct root block */
					last_rec_key = blk_set_p->curr_blk_key;
					dbc_init_blk(psa, blk_set_rhs_p, -1, gdsblk_create, new_rh_blk_len, curr_blk_levl);
					/* We will put the pointers to both this block and the RHS we build next
					 * into the original root block -- done later when RHS is complete.
					 * If root, the RHS sub-block is a different type.
					 */
					blk_set_rhs_p->blk_type = (gdsblk_gvtroot == blk_set_p->blk_type)
						? gdsblk_gvtindex : gdsblk_dtindex;
				}
				/**** Now build RHS into current block ****/
				BLK_INIT(bs_ptr, bs1);
				blk_set_rhs_p->upd_addr = bs1;		/* Block building roadmap.. */
				/* Build record header for inserted record. Inserted record is always for index type blocks */
				BLK_ADDR(ins_rec_hdr, SIZEOF(rec_hdr), rec_hdr);
				ins_rec_hdr->rsiz = SIZEOF(rec_hdr) + blk_set_p->ins_rec.ins_key->end + 1
					+ SIZEOF(block_id);
				SET_CMPC(ins_rec_hdr, 0);
				BLK_SEG(bs_ptr, (uchar_ptr_t)ins_rec_hdr, SIZEOF(rec_hdr));
				/* Now for the inserted record key */
				BLK_SEG(bs_ptr,
					blk_set_p->ins_rec.ins_key->base,
					blk_set_p->ins_rec.ins_key->end + 1);
				/* Finally the inserted record value always comes from the block_id field.
				 * It is not filled in now but will be when the block it refers to is created.
				 */
				BLK_SEG(bs_ptr,	(uchar_ptr_t)&blk_set_p->ins_rec.blk_id, SIZEOF(block_id));
				/* Record that was first in RH side now needs its cmpc (and length) reset since
				 * it is now the second record in the new block.
				 */
				BLK_ADDR(next_rec_hdr, SIZEOF(rec_hdr), rec_hdr);
				next_rec_hdr->rsiz = curr_rec_len - curr_rec_shrink;
				SET_CMPC(next_rec_hdr, blk_set_p->curr_match);
				BLK_SEG(bs_ptr, (uchar_ptr_t)next_rec_hdr, SIZEOF(rec_hdr));
				remain_offset = curr_rec_shrink + SIZEOF(rec_hdr);/* Where rest of record + anymore records begin */
				remain_len = curr_blk_len - curr_rec_offset;
				BLK_SEG(bs_ptr,
					blk_set_p->curr_rec + remain_offset,
					remain_len - remain_offset);
				if (0 == BLK_FINI(bs_ptr, bs1))
					assertpro(FALSE);
				assert(blk_seg_cnt == new_rh_blk_len);
			} /* else method (2) */
			if (got_root)
			{	/* If we have split a root block, we need to now set the pointers to the new LHS
				 * and RHS blocks into the root block as the only records. Note this requires a
				 * level increase of the tree. Hopefully we will not come across a database that is
				 * already at maximum level. If so, the only way to reduce the level is to run
				 * MUPIP REORG with a fairly recent vintage of GT.M
				 */
				BLK_INIT(bs_ptr, bs1);
				blk_set_p->usage = gdsblk_update;	/* It's official .. blk is being modified */
				blk_set_p->upd_addr = bs1;		/* Block building roadmap.. */
				blk_set_p->blk_levl++;			/* Needs to be at a new level */
				if (MAX_BT_DEPTH <= blk_set_p->blk_levl)
					assertpro(FALSE);	/* Tree is too high */
				/* First record will have last key in LHS block */
				BLK_ADDR(next_rec_hdr, SIZEOF(rec_hdr), rec_hdr);
				next_rec_hdr->rsiz = SIZEOF(rec_hdr) + last_rec_key->end + 1 + SIZEOF(block_id);
				SET_CMPC(next_rec_hdr, 0);
				BLK_SEG(bs_ptr, (uchar_ptr_t)next_rec_hdr, SIZEOF(rec_hdr));
				BLK_SEG(bs_ptr, last_rec_key->base, (last_rec_key->end + 1));
				BLK_ADDR(lhs_block_id_p, SIZEOF(block_id), block_id);	/* First record's value */
				BLK_SEG(bs_ptr, (uchar_ptr_t)lhs_block_id_p, SIZEOF(block_id));
				blk_set_new_p->ins_blk_id_p = lhs_block_id_p;	/* Receives block id when created */
				/* Second record is a star key record pointing to the RHS block */
				BLK_ADDR(new_star_hdr, SIZEOF(rec_hdr), rec_hdr);
				new_star_hdr->rsiz = BSTAR_REC_SIZE;
				SET_CMPC(new_star_hdr, 0);
				BLK_SEG(bs_ptr, (uchar_ptr_t)new_star_hdr, SIZEOF(rec_hdr));
				BLK_ADDR(rhs_block_id_p, SIZEOF(block_id), block_id);	/* First record's value */
				BLK_SEG(bs_ptr, (uchar_ptr_t)rhs_block_id_p, SIZEOF(block_id));
				blk_set_rhs_p->ins_blk_id_p = rhs_block_id_p;	/* Receives block id when created */
				/* Complete update array */
				if (0 == BLK_FINI(bs_ptr, bs1))
					assertpro(FALSE);
				/* The root block is the last one we need to change */
				DBC_DEBUG(("DBC_DEBUG: Stopping block scan as blk_index %d is a root block\n", blk_index));
				completed = TRUE;
				break;
			}
		} /* else need block split */
		if (0 != blk_index)
			blk_index--;	/* working backwards.. */
		else
			blk_index = bottom_tree_index;
	} /* while !completed */
	assert(completed);

	/* Check that we have sufficient free blocks to create the blccks we need (if any) */
	created_blocks = psa->block_depth - bottom_tree_index;
	if (created_blocks > psa->dbc_cs_data->trans_hist.free_blocks)
	{	/* We have a slight problem in that this transaction requires more free blocks than are
		 * available. Our recourse is to flush the current file-header preserving any changes we
		 * have already made, close the file and execute a mupip command to perform an extension before
		 * re-opening the db for further processing.
		 */
		DBC_DEBUG(("DBC_DEBUG: Insufficient free blocks for this transaction - calling MUPIP EXTEND\n"));
		dbc_flush_fhead(psa);
		dbc_close_db(psa);
		extent_size = MAX(psa->dbc_cs_data->extension_size, created_blocks);
		/* Now build command file to perform the MUPIP EXTEND for the region */
		dbc_open_command_file(psa);
		dbc_write_command_file(psa, MUPIP_START);
		strcpy((char_ptr_t)psa->util_cmd_buff, MUPIP_EXTEND);
		strcat((char_ptr_t)psa->util_cmd_buff, (char_ptr_t)psa->ofhdr.regname);
		strcat((char_ptr_t)psa->util_cmd_buff, " "OPTDELIM"B=");
		chr_p = psa->util_cmd_buff + strlen((char_ptr_t)psa->util_cmd_buff);
		chr_p = i2asc(chr_p, extent_size);
		*chr_p = 0;
		dbc_write_command_file(psa, (char_ptr_t)psa->util_cmd_buff);
		UNIX_ONLY(dbc_write_command_file(psa, "EOF"));
		dbc_close_command_file(psa);
		dbc_run_command_file(psa, "MUPIP", (char_ptr_t)psa->util_cmd_buff, FALSE);
		/* Seeing as how it is very difficult to (in portable code) get a coherent error code back from
		 * an executed command, we will just assume it worked, open the database back in and see if in
		 * fact it did actually extend sufficiently. If not, this is a perm error and we stop here.
		 */
		dbc_init_db(psa);
		if (created_blocks > psa->dbc_cs_data->trans_hist.free_blocks)
			rts_error_csa(CSA_ARG(NULL) VARLSTCNT(4) ERR_DBCNOEXTND, 2, RTS_ERROR_STRING((char_ptr_t)psa->ofhdr.dbfn));
		/* Database is now extended -- safest bet is to restart this particular update so that it is certain
		 * nothing else got in besides the extention.
		 */
		DBC_DEBUG(("DBC_DEBUG: Restarting processing of this p1rec due to DB extension\n"));
		return TRUE;
	}
	/* The update arrarys are complete, we know there are sufficient free blocks in the database to accomodate
	 * the splitting we have to do. From here on out are bit map blocks: psa->block_depth
	 */
	bplmap = psa->dbc_cs_data->bplmap;
	if (0 != created_blocks)
	{	/* Run through the created blocks assigning block numbers and filling the numbers into the buffers
		 * that need them. If we didn't create any blocks, we know we didn't split any and there is nothing
		 * to do for this p1 record.
		 */
		total_blks = psa->dbc_cs_data->trans_hist.total_blks;
		local_map_max = DIVIDE_ROUND_UP(total_blks, bplmap);
		DBC_DEBUG(("DBC_DEBUG: Assigning block numbers to created DB blocks\n"));
		for (blk_index = psa->block_depth, blk_set_p = &psa->blk_set[blk_index];
		     bottom_tree_index < blk_index; --blk_index, --blk_set_p)
		{
			assert(gdsblk_create == blk_set_p->usage);
			assert(NULL != blk_set_p->upd_addr);
			/* Find and allocate a database block for this created block */
			assert(NULL != blk_set_p->ins_blk_id_p);	/* Must be a place to put the block id */
			/* First find local bit map with some room in it */
			lclmap_not_full = bmm_find_free(psa->hint_blk / bplmap,
							(sm_uc_ptr_t)psa->dbc_cs_data->master_map,
							local_map_max);
			if (NO_FREE_SPACE == lclmap_not_full)
			{
				assert(FALSE);
				rts_error_csa(CSA_ARG(NULL) VARLSTCNT(4) ERR_GBLOFLOW, 2,
					RTS_ERROR_STRING((char_ptr_t)psa->ofhdr.dbfn));
			}
			if (ROUND_DOWN2(psa->hint_blk, bplmap) != lclmap_not_full)
				psa->hint_lcl = 1;
			bitmap_blk_num = lclmap_not_full * bplmap;
			/* Read this bitmap in. Note it may already exist in the cache (likely for multiple creates) */
			lbm_blk_index = dbc_read_dbblk(psa, bitmap_blk_num, gdsblk_bitmap);
			blk_set_bm_p = &psa->blk_set[lbm_blk_index];
			assert(IS_BML(blk_set_bm_p->old_buff));	/* Verify we have a bit map block */
			assert(ROUND_DOWN2(blk_set_bm_p->blk_num, bplmap) == blk_set_bm_p->blk_num);
			if (ROUND_DOWN2(psa->dbc_cs_data->trans_hist.total_blks, bplmap) == bitmap_blk_num)
				/* This bitmap is the last one .. compute total blks in partial this bitmap */
				blks_this_lmap = (psa->dbc_cs_data->trans_hist.total_blks - bitmap_blk_num);
			else
				/* Regular bitmap (not last one) */
				blks_this_lmap = bplmap;
			lcl_map_p = blk_set_bm_p->old_buff + SIZEOF(v15_blk_hdr);
			lcl_blk = psa->hint_lcl = bm_find_blk(psa->hint_lcl, lcl_map_p, blks_this_lmap, &dummy_bool);
			if (NO_FREE_SPACE == lcl_blk)
			{
				assert(FALSE);
				rts_error_csa(CSA_ARG(NULL) VARLSTCNT(5)
					ERR_DBCINTEGERR, 2, RTS_ERROR_STRING((char_ptr_t)psa->ofhdr.dbfn), ERR_BITMAPSBAD);
			}
			/* Found a free block, mark it busy. Note that bitmap blocks are treated somewhat differently
			 * than other blocks. We do not create an update array for them but just change the copy in
			 * old_buff as appropriate.
			 */
			bml_busy(lcl_blk, lcl_map_p);
			blk_set_bm_p->usage = gdsblk_update;
			/* See if entire block is full - if yes, we need to mark master map too */
			psa->hint_lcl = bml_find_free(psa->hint_lcl, lcl_map_p, blks_this_lmap);
			if (NO_FREE_SPACE == psa->hint_lcl)
			{	/* Local map was filled .. clear appropriate master map bit */
				DBC_DEBUG(("DBC_DEBUG: -- Local map now full - marking master map\n"));
				bit_clear(bitmap_blk_num / bplmap, psa->dbc_cs_data->master_map);
			}
			assert(lcl_blk);	/* Shouldn't be zero as that is for the lcl bitmap itself */
			allocated_blk_num = psa->hint_blk = bitmap_blk_num + lcl_blk;
			DBC_DEBUG(("DBC_DEBUG: -- The newly allocated block for block index %d is 0x%x\n",
				   blk_index, allocated_blk_num));
			/* Fill this block number in the places it needs to be filled */
			assert(-1 == blk_set_p->blk_num);
			blk_set_p->blk_num = allocated_blk_num;
			*blk_set_p->ins_blk_id_p = allocated_blk_num;
			psa->dbc_cs_data->trans_hist.free_blocks--;	/* There is one fewer free blocks tonite */
			assert(0 <= (int)psa->dbc_cs_data->trans_hist.free_blocks);
			psa->dbc_fhdr_dirty = TRUE;
		}
		/* Now that all the block insertions have been filled in, run the entire chain looking for
		 * both created and updated blocks. Build the new versions of their blocks in new_buff.
		 */
		DBC_DEBUG(("DBC_DEBUG: Create new and changed blocks via their update arrays\n"));
		for (blk_index = psa->block_depth, blk_set_p = &psa->blk_set[blk_index]; 0 <= blk_index; --blk_index, --blk_set_p)
		{	/* Run through the update array for this block */
			blk_sega_p = (blk_segment *)blk_set_p->upd_addr;
			if (gdsblk_bitmap == blk_set_p->blk_type || gdsblk_read == blk_set_p->usage)
			{	/* Bitmap blocks are updated in place and of course read blocks have no updates */
				DBC_DEBUG(("DBC_DEBUG: -- Block index %d bypassed for type (%d) or usage (%d)\n",
					   blk_index,  blk_set_p->blk_type, blk_set_p->usage));
				assert(NULL == blk_sega_p);
				continue;
			}
			DBC_DEBUG(("DBC_DEBUG: -- Block index %d being (re)built\n", blk_index));
			assert(NULL != blk_sega_p);
			new_blk_len = INTCAST(blk_sega_p->len);
			new_blk_p = blk_set_p->new_buff;
			((v15_blk_hdr_ptr_t)new_blk_p)->bsiz = blk_set_p->blk_len = new_blk_len;
			((v15_blk_hdr_ptr_t)new_blk_p)->levl = blk_set_p->blk_levl;
			/* VMS has an unalighed tn. All UNIX variants have an aligned TN */
			VMS_ONLY(PUT_ULONG(&((v15_blk_hdr_ptr_t)new_blk_p)->tn, psa->dbc_cs_data->trans_hist.curr_tn));
			UNIX_ONLY(((v15_blk_hdr_ptr_t)new_blk_p)->tn = psa->dbc_cs_data->trans_hist.curr_tn);
			new_blk_p += SIZEOF(v15_blk_hdr);
			for (blk_array_top = (blk_segment *)blk_sega_p->addr, blk_sega_p++;
			     blk_sega_p <= blk_array_top; blk_sega_p++)
			{	/* Start with first subtantive array entry ([1]) and do the segment move thing */
				memcpy(new_blk_p, blk_sega_p->addr, blk_sega_p->len);
				new_blk_p += blk_sega_p->len;
			}
			assert((new_blk_p - blk_set_p->new_buff) == new_blk_len);
		}
		/* One last pass through the block list to do the physical IO on the database */
		psa->fc->op = FC_WRITE;
		psa->fc->op_len = blk_size;		/* Just write the full block out regardless */
		DBC_DEBUG(("DBC_DEBUG: Flush all modified blocks out to disk for this transaction\n"));
		psa->dbc_critical = TRUE;
		for (blk_index = psa->block_depth, blk_set_p = &psa->blk_set[blk_index]; 0 <= blk_index; --blk_index, --blk_set_p)
		{	/* Output all modified/created blocks */
			if (gdsblk_create != blk_set_p->usage)
			{	/* We read everything but created blocks and some of them were found in cache */
				psa->blks_read++;
				if (blk_set_p->found_in_cache)
					psa->blks_cached++;
			}
			if (gdsblk_read == blk_set_p->usage)
			{
				DBC_DEBUG(("DBC_DEBUG: -- Block index %d bypassed for usage (read)\n", blk_index));
				continue;	/* Nothing to do for read-only block */
			}
			if (gdsblk_bitmap == blk_set_p->blk_type)
			{	/* Bitmap blocks are built in old_buff, swap with new_buff. This also lets the
				 * buffer be reused correctly (by dbc_read_dbblk) if we read this block into
				 * the same place later.
				 */
				blk_p = blk_set_p->new_buff;
				blk_set_p->new_buff = blk_set_p->old_buff;
				blk_set_p->old_buff = blk_p;
			}
			DBC_DEBUG(("DBC_DEBUG: -- Block index %d being written as block 0x%x\n", blk_index,
				   blk_set_p->blk_num));
			psa->fc->op_buff = blk_set_p->new_buff;
			psa->fc->op_pos = psa->dbc_cs_data->start_vbn
				+ ((gtm_int64_t)(blk_size / DISK_BLOCK_SIZE) * blk_set_p->blk_num);
			dbcertify_dbfilop(psa);
			if (gdsblk_create == blk_set_p->usage)
				psa->blks_created++;
			else
				psa->blks_updated++;
		}
		psa->dbc_critical = FALSE;
		if (forced_exit)
		{	/* Our exit was deferred until we cleared the critical section area */
			UNIX_ONLY(dbcertify_deferred_signal_handler());
			VMS_ONLY(sys$exit(exi_condition));
		}
		/* Update the transaction number in the fileheader for the next transaction */
		psa->dbc_cs_data->trans_hist.curr_tn++;
		psa->dbc_fhdr_dirty = TRUE;
	} else
		assertpro(FALSE);	/* If we got this far we should have split a block which would create a block */
	DBC_DEBUG(("DBC_DEBUG: Block processing completed\n"));
	psa->blks_processed++;
	return FALSE;	/* No transaction restart necessary */
}

/* Flush the file-header back out to the database */
void dbc_flush_fhead(phase_static_area *psa)
{
	if (!psa->dbc_fhdr_dirty)
		return;		/* Nothing to do if it wasn't dirtied */
	psa->fc->op = FC_WRITE;
	psa->fc->op_buff = (uchar_ptr_t)psa->dbc_cs_data;
	psa->fc->op_pos = 1;
	psa->fc->op_len = SIZEOF(v15_sgmnt_data);
	dbcertify_dbfilop(psa);
	psa->dbc_fhdr_dirty = FALSE;
	return;
}

/* Read the next output record into the buffer provided */
void dbc_read_p1out(phase_static_area *psa, void *obuf, int olen)
{
	int		rc, save_errno;
	char_ptr_t	errmsg;

	DOREADRC(psa->outfd, obuf, olen, rc);
	if (-1 == rc)
	{
		save_errno = errno;
		errmsg = STRERROR(save_errno);
		assert(FALSE);
		rts_error_csa(CSA_ARG(NULL) VARLSTCNT(11) ERR_SYSCALL, 5, RTS_ERROR_LITERAL("read()"), CALLFROM,
			  ERR_TEXT, 2, RTS_ERROR_STRING(errmsg));
	}
}

/* Exit/cleanup routine */
void dbc_certify_phase_cleanup(void)
{
	phase_static_area	*psa;

	psa = psa_gbl;
	if (NULL == psa)
		return;
	if (psa->dbc_gv_cur_region && psa->dbc_gv_cur_region->dyn.addr && psa->dbc_gv_cur_region->dyn.addr->file_cntl)
	{
		dbc_flush_fhead(psa);
		dbc_close_db(psa);
		if (psa->dbc_critical)
			rts_error_csa(CSA_ARG(NULL) VARLSTCNT(4) ERR_TEXT,
				  2, RTS_ERROR_LITERAL("Failure while in critical section -- database damage likely"));
	}
	UNIX_ONLY(dbc_release_standalone_access(psa));
	if (psa_gbl->tmp_file_names_gend)
	{	/* Only close/delete if we know what they are */
		if (psa->tcfp)
			dbc_close_command_file(psa);
		if (!psa->keep_temp_files)
			dbc_remove_command_file(psa);
		if (psa->trfp)
			dbc_close_result_file(psa);
		if (!psa->keep_temp_files)
			dbc_remove_result_file(psa);
	}
}
