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

#define _POSIX_EXIT	/* Needed for VMS system() call */ /* BYPASSOK: system() used insode the comment, no SYSTEM() needed */
#include "mdef.h"

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

#include <errno.h>
#include "sys/wait.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"

#include "gtmio.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 "gdsblk.h"
#include "patcode.h"
#include "gdsblkops.h"
#include "filestruct.h"
#include "v15_filestruct.h"
#include "gtmmsg.h"
#include "eintr_wrappers.h"
#include "min_max.h"
#include "error.h"
#include "jnl.h"
#include "trans_log_name.h"
#include "have_crit.h"
#include "dbcertify.h"

#define FILETAB		"File  	"
#define REGIONTAB 	"Region	"

error_def(ERR_DEVOPENFAIL);
error_def(ERR_SYSCALL);
error_def(ERR_DBRDONLY);
error_def(ERR_DBOPNERR);
error_def(ERR_DBCCMDFAIL);
error_def(ERR_DBCINTEGERR);
error_def(ERR_PREMATEOF);
error_def(ERR_TEXT);
error_def(ERR_TRNLOGFAIL);
error_def(ERR_NOREGION);
error_def(ERR_DBNOTGDS);
error_def(ERR_BADDBVER);
error_def(ERR_DBMINRESBYTES);
error_def(ERR_DBMAXREC2BIG);
error_def(ERR_DBCKILLIP);
error_def(ERR_DBCNOTSAMEDB);
error_def(ERR_LOGTOOLONG);
error_def(ERR_GTMDISTUNDEF);

/* Open the temporary file that hold the command(s) we are going to execute */
void dbc_open_command_file(phase_static_area *psa)
{
	char_ptr_t	errmsg;
	int		rc, save_errno;
	int4		status;
	char		*dist_ptr;
	mstr		gtm_dist_m, gtm_dist_path;
	char		gtm_dist_path_buff[MAX_FN_LEN + 1];

	assert(NULL != psa && NULL == psa->tcfp);
	if (!psa->tmp_file_names_gend)
		dbc_gen_temp_file_names(psa);
	gtm_dist_m.addr = UNIX_ONLY("$")GTM_DIST;
	gtm_dist_m.len = SIZEOF(UNIX_ONLY("$")GTM_DIST) - 1;
	status = TRANS_LOG_NAME(&gtm_dist_m, &gtm_dist_path, gtm_dist_path_buff, SIZEOF(gtm_dist_path_buff),
					dont_sendmsg_on_log2long);
#ifdef UNIX
	if (SS_LOG2LONG == status)
		rts_error_csa(CSA_ARG(NULL) VARLSTCNT(5) ERR_LOGTOOLONG, 3,
				gtm_dist_m.len, gtm_dist_m.addr, SIZEOF(gtm_dist_path_buff) - 1);
	else
#endif
	if (SS_NORMAL != status)
		rts_error_csa(CSA_ARG(NULL) VARLSTCNT(1) ERR_GTMDISTUNDEF);
	assert(0 < gtm_dist_path.len);
	VMS_ONLY(dbc_remove_command_file(psa));	/* If we don't do this, the command files versions pile up fast */
	Fopen(psa->tcfp, (char_ptr_t)psa->tmpcmdfile, "w");
	if (NULL == psa->tcfp)
	{
		save_errno = errno;
		errmsg = STRERROR(save_errno);
		rts_error_csa(CSA_ARG(NULL) VARLSTCNT(8) ERR_DEVOPENFAIL, 2, psa->tmpcmdfile_len, psa->tmpcmdfile,
			  ERR_TEXT, 2, RTS_ERROR_TEXT(errmsg));
	}
	UNIX_ONLY(dbc_write_command_file(psa, SHELL_START));
	MEMCPY_LIT(psa->util_cmd_buff, SETDISTLOGENV);
	memcpy(psa->util_cmd_buff + SIZEOF(SETDISTLOGENV) - 1, gtm_dist_path.addr, gtm_dist_path.len);
	psa->util_cmd_buff[SIZEOF(SETDISTLOGENV) - 1 + gtm_dist_path.len] = 0;	/* Null temrinator */
	dbc_write_command_file(psa, (char_ptr_t)psa->util_cmd_buff);
}

/* Write a record to temporary command file */
void dbc_write_command_file(phase_static_area *psa, char_ptr_t cmd)
{
	char_ptr_t	errmsg;
	int		rc, save_errno;

	assert(NULL != psa && NULL != psa->tcfp);
	assert(psa->tmp_file_names_gend);
	rc = FPRINTF(psa->tcfp, "%s\n", cmd);
	if (-1 == rc)
	{
		save_errno = errno;
		errmsg = STRERROR(save_errno);
		rts_error_csa(CSA_ARG(NULL) VARLSTCNT(11) ERR_SYSCALL, 5, RTS_ERROR_LITERAL("fprintf()"), CALLFROM, /* BYPASSOK */
			  ERR_TEXT, 2, RTS_ERROR_TEXT(errmsg));
	}
	rc = CHMOD((char_ptr_t)psa->tmpcmdfile, S_IRUSR + S_IWUSR + S_IXUSR + S_IRGRP + S_IROTH); /* Change to 744 */
	if (-1 == rc)
	{
		save_errno = errno;
		errmsg = STRERROR(save_errno);
		rts_error_csa(CSA_ARG(NULL) VARLSTCNT(15) ERR_SYSCALL, 5, RTS_ERROR_LITERAL("chmod()"), CALLFROM,
			  ERR_TEXT, 2, psa->tmpcmdfile_len, psa->tmpcmdfile,
			  ERR_TEXT, 2, RTS_ERROR_TEXT(errmsg));
	}
}

/* Close the temporary command file */
void dbc_close_command_file(phase_static_area *psa)
{
	int status;

	assert(NULL != psa && NULL != psa->tcfp);
	assert(psa->tmp_file_names_gend);
	FCLOSE(psa->tcfp, status);
	psa->tcfp = NULL;
}

/* Execute the temporary command file */
void dbc_run_command_file(phase_static_area *psa, char_ptr_t cmdname, char_ptr_t cmdargs, boolean_t piped_result)
{
	int		rc, cmd_len;
	unsigned char	cmdbuf1[MAX_FN_LEN + 256], cmdbuf2[MAX_FN_LEN + 1], *cp;

	assert(NULL != psa && NULL == psa->tcfp);
	assert(psa->tmp_file_names_gend);
	MEMCPY_LIT(cmdbuf1, RUN_CMD);
	cp = cmdbuf1 + SIZEOF(RUN_CMD) - 1;
	memcpy(cp, psa->tmpcmdfile, psa->tmpcmdfile_len);
	cp += psa->tmpcmdfile_len;
	cmd_len = (int)(cp - cmdbuf1);
	*cp = '\0';

	rc = dbc_syscmd((char_ptr_t)cmdbuf1);
	if (0 != rc)
	{	/* If piped_result, they can't see what went wrong (error messages likely in result file */
		if (piped_result)
		{	/* Output result file so they can see what happened -- this may or may not work but it is
			   the best we can do at this point */
			MEMCPY_LIT(cmdbuf2, DUMPRSLTFILE);
			cp = cmdbuf2 + SIZEOF(DUMPRSLTFILE) - 1;
			memcpy(cp, psa->tmprsltfile, psa->tmprsltfile_len);
			cp += psa->tmprsltfile_len;
			*cp = '\0';
			dbc_syscmd((char_ptr_t)cmdbuf2);
		}
		rts_error_csa(CSA_ARG(NULL) VARLSTCNT(13) ERR_DBCCMDFAIL, 7, rc, cmd_len, cmdbuf1, RTS_ERROR_TEXT(cmdname),
			  RTS_ERROR_TEXT(cmdargs), ERR_TEXT, 2,
			  RTS_ERROR_LITERAL("Note that the "UNIX_ONLY("environment variable $")VMS_ONLY("logical ")GTM_DIST
					    " must point to the current GT.M V4 installation"));
	}
}

/* Remove/dalete command file - normally only needed at cleanup since open with "W" will delete any existing file. */
void dbc_remove_command_file(phase_static_area *psa)
{
	char_ptr_t	errmsg;
	int		rc, save_errno;

	assert(NULL != psa && NULL == psa->tcfp);		/* Must be closed */
	if (!psa->tmp_file_names_gend)
		dbc_gen_temp_file_names(psa);
	rc = remove((char_ptr_t)psa->tmpcmdfile);
	if (-1 == rc && ENOENT != errno)
	{
		save_errno = errno;
		errmsg = STRERROR(save_errno);
		rts_error_csa(CSA_ARG(NULL) VARLSTCNT(15) ERR_SYSCALL, 5, RTS_ERROR_LITERAL("remove()"), CALLFROM,
			  ERR_TEXT, 2, psa->tmpcmdfile_len, psa->tmpcmdfile,
			  ERR_TEXT, 2, RTS_ERROR_TEXT(errmsg));
	}
}

/* Open result file */
void dbc_open_result_file(phase_static_area *psa)
{
	char_ptr_t	errmsg;
	int		rc, save_errno;

	assert(NULL != psa && NULL == psa->trfp);
	assert(psa->tmp_file_names_gend);
	Fopen(psa->trfp, (char_ptr_t)psa->tmprsltfile, "r");
	if (0 == psa->trfp)
	{
		save_errno = errno;
		errmsg = STRERROR(save_errno);
		rts_error_csa(CSA_ARG(NULL) VARLSTCNT(8) ERR_DEVOPENFAIL, 2, psa->tmprsltfile_len, psa->tmprsltfile,
			  ERR_TEXT, 2, RTS_ERROR_TEXT(errmsg));
	}
}

/* Read a record from temporary result file */
uchar_ptr_t dbc_read_result_file(phase_static_area *psa, int rderrmsg, uchar_ptr_t arg)
{
	int    		save_errno;
	char_ptr_t	errmsg;
	char_ptr_t	fgs;
	char		emsg[MAX_FN_LEN + 256];

	assert(NULL != psa && NULL != psa->trfp);
	FGETS((char_ptr_t)psa->rslt_buff, MAX_ZWR_KEY_SZ, psa->trfp, fgs);
	if (NULL == fgs)
	{
		if (!feof(psa->trfp))
		{	/* Non-EOF message */
			save_errno = errno;
			errmsg = STRERROR(save_errno);
			rts_error_csa(CSA_ARG(NULL) VARLSTCNT(11) ERR_SYSCALL, 5, RTS_ERROR_LITERAL("fgets()"), CALLFROM,
				  ERR_TEXT, 2, RTS_ERROR_TEXT(errmsg));
		} else
		{	/* We have EOF */
			if (0 != rderrmsg)
				rts_error_csa(CSA_ARG(NULL) VARLSTCNT(4) rderrmsg, 2, RTS_ERROR_TEXT((char_ptr_t)arg));
			else
			{
				strcpy(emsg, "Temporary results file (");
				strcat(emsg, (char_ptr_t)psa->tmprsltfile);
				strcat(emsg, " had unexpected values");
				rts_error_csa(CSA_ARG(NULL) VARLSTCNT(6) ERR_PREMATEOF, 0, ERR_TEXT, 2,
					  RTS_ERROR_TEXT(emsg));
			}
		}
		EXIT(EXIT_FAILURE);	/* We shouldn't come here but in case... */
	}
	return (uchar_ptr_t)fgs;
}

/* Close the temporary command file */
void dbc_close_result_file(phase_static_area *psa)
{
	int status;

	assert(NULL != psa && NULL != psa->trfp);
	FCLOSE(psa->trfp, status);
	psa->trfp = NULL;
}

/* Remove/dalete result file */
void dbc_remove_result_file(phase_static_area *psa)
{
	int		rc, save_errno;
	char_ptr_t	errmsg;

	assert(NULL != psa && NULL == psa->trfp);		/* Must be closed */
	if (!psa->tmp_file_names_gend)
		dbc_gen_temp_file_names(psa);
	rc = remove((char_ptr_t)psa->tmprsltfile);
	if (-1 == rc && ENOENT != errno)
	{
		save_errno = errno;
		errmsg = STRERROR(save_errno);
		rts_error_csa(CSA_ARG(NULL) VARLSTCNT(15) ERR_SYSCALL, 5, RTS_ERROR_LITERAL("remove()"), CALLFROM,
			  ERR_TEXT, 2, psa->tmprsltfile_len, psa->tmprsltfile,
			  ERR_TEXT, 2, RTS_ERROR_TEXT(errmsg));
	}
}

/* Create the temporary file names we need. This is a hardcoded prefix following by the region name
   of the file we are processing. This should create a unique temporary command script and command
   result file for a given invocation and allows multiple copies of DBCERTIFY to run against databases
   in the same directory.
*/
void dbc_gen_temp_file_names(phase_static_area *psa)
{
	unsigned char	*cp, *regname_p;
	int		len, dir_len;

	assert(NULL != psa && !psa->tmp_file_names_gend);
	assert(0 == psa->tmprsltfile[0]);
	assert(0 == psa->tmpcmdfile[0]);
	/* See if we have region name information yet. Where this region name information is
	   kept depends on the phase we are in. Scan phase it is in psa->regname (is an argument
	   to the phase). In certify phase the region name is in the scan phase outfile file header.
	*/
	if (psa->phase_one)
		/* Scan phase, region name in regname */
		regname_p = psa->regname;
	else
		regname_p = (uchar_ptr_t)psa->ofhdr.regname;
	assert(0 != *regname_p);	/* We should have a regname of substance */

	/* Temp command file name: <tempfiledir>TMPFILEPFX_<regionname>.TMPFILESFX. Note that
	   tempfiledir has no default if not specified therefore defaults to the current directory.
	*/
	cp = psa->tmpcmdfile;
	if (0 != *psa->tmpfiledir)
	{	/* A temporary file directory was specified .. use it */
		len = STRLEN((char_ptr_t)psa->tmpfiledir);
		memcpy(cp, psa->tmpfiledir, len);
		cp += len;
#ifdef VMS
		if (']' != *(cp - 1) && '>' != *(cp - 1) && ':' != *(cp - 1))
			*cp++ = ':';
#else
		if ('/' != *(cp - 1))
			*cp++ = '/';
#endif
		dir_len = (int)(cp - psa->tmpcmdfile);
	} else
		dir_len = 0;
	MEMCPY_LIT(cp, TMPFILEPFX);
	cp = psa->tmpcmdfile + SIZEOF(TMPFILEPFX) - 1;
	*cp++ = '_';
	len = STRLEN((char_ptr_t)regname_p);
	memcpy(cp, regname_p, len);
	cp += len;
	MEMCPY_LIT(cp, TMPCMDFILSFX);
	cp += SIZEOF(TMPCMDFILSFX) - 1;
	psa->tmpcmdfile_len = (int)(cp - psa->tmpcmdfile);
	*cp = '\0';	/* Null terminate */

	/* Now same thing for temporary results file */
	cp = psa->tmprsltfile;
	if (0 != dir_len)
	{	/* Dir will be same as for command file so use that.. */
		memcpy(cp, psa->tmpcmdfile, dir_len);
		cp += dir_len;
	}
	MEMCPY_LIT(cp, TMPFILEPFX);
	cp = psa->tmprsltfile + SIZEOF(TMPFILEPFX) - 1;
	*cp++ = '_';
	len = STRLEN((char_ptr_t)regname_p);
	memcpy(cp, regname_p, len);
	cp += len;
	MEMCPY_LIT(cp, TMPRSLTFILSFX);
	cp += SIZEOF(TMPRSLTFILSFX) - 1;
	psa->tmprsltfile_len = (int)(cp - psa->tmprsltfile);
	*cp = '\0';	/* Null terminate */


	psa->tmp_file_names_gend = TRUE;
}

/* Execute the given system command. Note even in VMS we are getting a POSIX style return code, not the
   VMS style return code due to the _POSIX_EXIT macro defined at the top of this module. See the C library
   reference manual for details on VMS.
 */
int dbc_syscmd(char_ptr_t cmdparm)
{
	int		rc;
#ifdef _BSD
	union wait	wait_stat;
#else
	int4		wait_stat;
#endif

	rc = SYSTEM(cmdparm);
	if (-1 == rc)
		rc = errno;
	else
	{
#ifdef _BSD
		wait_stat.w_status = rc;
#else
		wait_stat = rc;
#endif
		rc = WEXITSTATUS(wait_stat);
	}
	return rc;
}

/* Find the region name associated with the given database.

   Parse the output from "DSE /REG" to determine.
*/
void dbc_find_database_filename(phase_static_area *psa, uchar_ptr_t regname, uchar_ptr_t dbfn)
{
	int		len;
	uchar_ptr_t	rptr;

	dbc_open_command_file(psa);
#ifdef VMS
	strcpy((char_ptr_t)psa->util_cmd_buff, RESULT_ASGN);
	strcat((char_ptr_t)psa->util_cmd_buff, (char_ptr_t)psa->tmprsltfile);
	dbc_write_command_file(psa, (char_ptr_t)psa->util_cmd_buff);
	dbc_write_command_file(psa, DSE_START);
#else
	strcpy((char_ptr_t)psa->util_cmd_buff, DSE_START_PIPE_RSLT1);
	strcat((char_ptr_t)psa->util_cmd_buff, (char_ptr_t)psa->tmprsltfile);
	strcat((char_ptr_t)psa->util_cmd_buff, DSE_START_PIPE_RSLT2);
	dbc_write_command_file(psa, (char_ptr_t)psa->util_cmd_buff);
#endif
	dbc_write_command_file(psa, DSE_FIND_REG_ALL);
	dbc_write_command_file(psa, DSE_QUIT);
	UNIX_ONLY(dbc_write_command_file(psa, "EOF"));
	dbc_close_command_file(psa);
	dbc_remove_result_file(psa);
	dbc_run_command_file(psa, "DSE", DSE_FIND_REG_ALL, TRUE);

	dbc_open_result_file(psa);
	/* There should be 7 lines of uninteresting stuff before we get to the file/region pairs but since other errors may
	   pop up at the head of this list (notorious example -- waiting for ftok semaphore from DSE) we will just eat lines
	   until we get to the first DSE prompt at which point our command should be outputing. If we run out of lines (hit
	   EOF), the read routine will return an appropriate error.
	*/
	do
	{
		rptr = dbc_read_result_file(psa, 0, NULL);
	} while (0 != MEMCMP_LIT(rptr, DSE_REG_LIST_START));
	/* And two more lines after that are also bogus */
	dbc_read_result_file(psa, 0, NULL);
	dbc_read_result_file(psa, 0, NULL);

	/* Now we get to the triplets of Filename, Regionname, and a blank line for each region in GLD */
	while(1)
	{
		rptr = dbc_read_result_file(psa, ERR_NOREGION, regname);	/* Should have filename */
		assertpro(0 == MEMCMP_LIT(rptr, FILETAB));
		rptr += STR_LIT_LEN(FILETAB);
		len = STRLEN((char_ptr_t)rptr) - 1;
		assert((MAX_FN_LEN >= len) && (0 < len));
		len = MIN(len, MAX_FN_LEN);
		memcpy(dbfn, rptr, len);
		dbfn[len] = 0;
		rptr = dbc_read_result_file(psa, ERR_NOREGION, regname);
		assertpro(0 == MEMCMP_LIT(rptr, REGIONTAB));
		len = STRLEN((char_ptr_t)rptr + SIZEOF(REGIONTAB) - 1) - 1;
		assert(len);
		*(rptr + SIZEOF(REGIONTAB) - 1 + len) = 0;	/* Get rid of trailing \n */
		if (0 == strcmp(((char_ptr_t)rptr + SIZEOF(REGIONTAB) - 1), (char_ptr_t)regname))
			break;				/* Found match */
		rptr = dbc_read_result_file(psa, ERR_NOREGION, regname);	/* Ingored blank line */
		len = STRLEN((char_ptr_t)rptr);
		if ('\n' == rptr[len - 1]) --len;	/* Note last record of output file does not have '\n' so test
							   before adjusting 'len' */
		if ('\r' == rptr[len - 1]) --len;	/* Same for carriage return (mostly for VMS) */
		assertpro((0 == len) || (((SIZEOF("DSE> ") - 1) == len) && (0 == memcmp(rptr, "DSE> ", len))));
	}
	dbc_close_result_file(psa);
	return;
}

/* Read a given database block into the next block_set (or return existing one) */
int dbc_read_dbblk(phase_static_area *psa, int blk_num, enum gdsblk_type blk_type)
{
	uchar_ptr_t	tucp, src_blk_p;
	int		blk_index;
	block_info	*blk_set_p, *blk_set_new_p;

	assert(0 <= blk_num);
	if (NULL == psa->blk_set)
	{	/* Need a few of these.. */
		psa->blk_set = malloc(SIZEOF(block_info) * MAX_BLOCK_INFO_DEPTH);
		memset(psa->blk_set, 0, SIZEOF(block_info) * MAX_BLOCK_INFO_DEPTH);
		assert(-1 == psa->block_depth);
	}

	DBC_DEBUG(("DBC_DEBUG: Requesting database block 0x%x (type = %d)\n", blk_num, blk_type));
	/* Scan the blocks we have thus far to make sure this block is not amongst them.
	   Block duplication is possible in two instances: (1) We have reached the target
	   block or (2) this is a bitmap block we are reading in to modify. These conditions
	   are also "asserted".
	*/
	assert(blk_num < psa->dbc_cs_data->trans_hist.total_blks);
	for (blk_index = 0, blk_set_p = &psa->blk_set[0]; blk_index <= psa->block_depth; ++blk_index, ++blk_set_p)
	{
		if (blk_set_p->blk_num == blk_num)
		{	/* This block already exists in our cache */
			assert(0xff == ((v15_blk_hdr_ptr_t)blk_set_p->old_buff)->levl || 0 == blk_index);
			if (gdsblk_gvtroot == blk_type)
			{
				assert(gdsblk_gvtindex == blk_set_p->blk_type);
				/* Override preexisting type to say this is the GVT root block which we
				   did not know before when we read it in.
				*/
				blk_set_p->blk_type = gdsblk_gvtroot;
			}
			DBC_DEBUG(("DBC_DEBUG: Found block in active cache - blk_index %d\n", blk_index));
			blk_set_p->found_in_cache = TRUE;
			return blk_index;
		}
	}

	++psa->block_depth;			/* Going to another level of block usage */
	assertpro(MAX_BLOCK_INFO_DEPTH > psa->block_depth);
	blk_set_new_p = &psa->blk_set[psa->block_depth];

	/* See if this block already occupies this or another slot in the "inactive cache" which is any block
	   that was read into a slot >= block_depth but <= block_depth_hwm (our high water mark).
	*/
	for (blk_index = psa->block_depth, blk_set_p = &psa->blk_set[psa->block_depth];
	     blk_index <= psa->block_depth_hwm;
	     blk_index++, blk_set_p++)
	{	/* Note the blk_set_p->blk_num != 0 represents a minor inefficiency in that it forces us to always
		   reload block 0 the first time it is used in a transaction but does not cause any correctness
		   issues. After this initial load, it will be found on subsequent searches by the loop above. The
		   check against 0 prevents us from picking up a block that was not in use before. An alternative
		   method to this check would be to initialize the block numbers of the entire cache to some value
		   (preferrably other than -1 as that value has another meaning [created block]). This is something
		   that could be done in the future if this processing turns out to be burdensome which we fully
		   do not expect to be the case with v5cbsu.m handling the bulk of the ocnversion workload. SE 5/2005.
		*/
		if (blk_num == blk_set_p->blk_num && 0 != blk_set_p->blk_num)
		{	/* Block already exists in this slot so we can avoid I/O. If this is the slot we were going
			   to put the new block in (blk_index == block_depth) then everything is in the right place
			   and we only need minor resets. Else, copy information from found block to current block
			   as necessary
			*/
			if (blk_index > psa->block_depth)
			{	/* Block exists in an older slot. Copy info to new slot */
				dbc_init_blk(psa, blk_set_new_p, blk_set_p->blk_num, gdsblk_read, blk_set_p->blk_len,
					     blk_set_p->blk_levl);
				blk_set_new_p->blk_type = blk_set_p->blk_type;
				memcpy(blk_set_new_p->old_buff,
				       ((gdsblk_read == blk_set_p->usage) ? blk_set_p->old_buff : blk_set_p->new_buff),
				       psa->dbc_cs_data->blk_size);
				blk_set_p->blk_num = -1;	/* Effectively invalidate this (now) older cache entry */
				DBC_DEBUG(("DBC_DEBUG: Found block in inactive cache differemt slot (%d) for blk_index %d\n",
					   blk_index, psa->block_depth));
			} else
			{
				assert(blk_index == psa->block_depth);
				switch(blk_set_new_p->usage)
				{
					case gdsblk_create:
					case gdsblk_update:
						/* Both of these have the current value for the buffer in new_buff.
						   Swap new_buff and old_buff to get things into proper order */
						assert(blk_set_p->old_buff);
						assert(blk_set_p->new_buff);
						tucp = blk_set_p->old_buff;
						blk_set_p->old_buff = blk_set_p->new_buff;
						blk_set_p->new_buff = tucp;
						/* Fall into code for block that was only read (it is all setup already) */
					case gdsblk_read:
						/* If block was not disturbed, the buffer pointers are already in the
						   correct configuration */
						dbc_init_blk(psa, blk_set_p, blk_set_p->blk_num, gdsblk_read, blk_set_p->blk_len,
							     blk_set_p->blk_levl);
						DBC_DEBUG(("DBC_DEBUG: Found block in inactive cache same slot for blk_index"
							   " %d\n", psa->block_depth));
						break;
					default:
						assertpro(FALSE);
				}
			}
			VMS_ONLY(GET_ULONG(blk_set_new_p->tn, &((v15_blk_hdr_ptr_t)blk_set_new_p->old_buff)->tn));
			UNIX_ONLY(blk_set_new_p->tn = ((v15_blk_hdr_ptr_t)blk_set_new_p->old_buff)->tn);
			blk_set_new_p->found_in_cache = TRUE;
			return psa->block_depth;
		}
	}

	/* Initialize block we are about to use. Some values not yet known until after read so this serves mainly
	   to make sure all the structures (such as read buffer) are allocated.
	*/
	dbc_init_blk(psa, blk_set_new_p, blk_num, gdsblk_read, 0, 0);
	/* Now read the block */
	DBC_DEBUG(("DBC_DEBUG: Reading in database block 0x%x into blk_index %d\n", blk_num, psa->block_depth));
	psa->fc->op = FC_READ;
	psa->fc->op_buff = (sm_uc_ptr_t)blk_set_new_p->old_buff;
	psa->fc->op_pos = psa->dbc_cs_data->start_vbn + ((gtm_int64_t)(psa->dbc_cs_data->blk_size / DISK_BLOCK_SIZE) * blk_num);
	psa->fc->op_len = psa->dbc_cs_data->blk_size;	/* In case length field was modified during a file-extension */
	dbcertify_dbfilop(psa);				/* Read data/index block (no return if error) */
	/* Now that we know some value, call initialize again to set the values the way we want */
	dbc_init_blk(psa, blk_set_new_p, blk_num, gdsblk_read, ((v15_blk_hdr_ptr_t)blk_set_new_p->old_buff)->bsiz,
		     ((v15_blk_hdr_ptr_t)blk_set_new_p->old_buff)->levl);
	VMS_ONLY(GET_ULONG(blk_set_new_p->tn, &((v15_blk_hdr_ptr_t)blk_set_new_p->old_buff)->tn));
	UNIX_ONLY(blk_set_new_p->tn = ((v15_blk_hdr_ptr_t)blk_set_new_p->old_buff)->tn);
	/* recalculate block type if necessary */
	switch(blk_type)
	{
		case gdsblk_gvtroot:
		case gdsblk_gvtindex:
		case gdsblk_gvtleaf:
		case gdsblk_dtroot:
		case gdsblk_dtindex:
		case gdsblk_dtleaf:
		case gdsblk_bitmap:
			blk_set_new_p->blk_type = blk_type;
			break;
		case gdsblk_gvtgeneric:
			if (0 == ((v15_blk_hdr_ptr_t)blk_set_new_p->old_buff)->levl)
				blk_set_new_p->blk_type = gdsblk_gvtleaf;
			else
				blk_set_new_p->blk_type = gdsblk_gvtindex;
			break;
		case gdsblk_dtgeneric:
			if (0 == ((v15_blk_hdr_ptr_t)blk_set_new_p->old_buff)->levl)
				blk_set_new_p->blk_type = gdsblk_dtleaf;
			else
				blk_set_new_p->blk_type = gdsblk_dtindex;
			break;
		default:
			assertpro(FALSE);
	}
	return psa->block_depth;
}

/* Find the end of a key using the current value of the key as a base */
void dbc_find_key(phase_static_area *psa, dbc_gv_key *key, uchar_ptr_t rec_p, int blk_levl)
{
	int		cmpc, rec_len;
	int		tmp_cmpc;
	unsigned short	us_rec_len;
	uchar_ptr_t	key_targ_p, key_src_p;

	cmpc = EVAL_CMPC((rec_hdr *)rec_p);
	GET_USHORT(us_rec_len, &((rec_hdr_ptr_t)rec_p)->rsiz);
	rec_len = us_rec_len;
	if (BSTAR_REC_SIZE == rec_len && 0 < blk_levl)
	{	/* This is a star key record .. there is no key */
		key->end = 0;
		DBC_DEBUG(("DBC_DEBUG: Found star key record in dbc_find_key\n"));
		return;
	}
	/* Loop till we find key termination */
	key_targ_p = key->base + cmpc;
	key_src_p = rec_p + SIZEOF(rec_hdr);
	while (1)
	{
		for (; *key_src_p; ++key_targ_p, ++key_src_p)
			*key_targ_p = *key_src_p;
		if (0 == *(key_src_p + 1))
		{	/* Just found the end of the key */
			*key_targ_p++ = 0;		/* Create key ending null */
			*key_targ_p = 0;		/* Install 2nd 0x00 as part of key but not part of length */
			key->end = (uint4)(key_targ_p - key->base);
			break;
		}
		/* Else, copy subscript separator char and keep scanning */
		*key_targ_p++ = *key_src_p++;
		assert((key_src_p - rec_p) < rec_len);	/* Sanity check */
	}
	assert(cmpc <= key->end);		       	/* Overrun sanity check */
	return;
}

/* Find the gvt root block by looking up the GVN in the directory tree */
int dbc_find_dtblk(phase_static_area *psa, dbc_gv_key *key, int min_levl)
{
	uchar_ptr_t		rec_p;
	int			blk_index;

	assert(MAX_MIDENT_LEN >= key->gvn_len);
	dbc_init_key(psa, &psa->gvn_key);
	memcpy(psa->gvn_key, key, SIZEOF(dbc_gv_key) + key->gvn_len);	/* Make key with GVN only (including trailing null) */
	psa->gvn_key->end = key->gvn_len;
	/* Look up GVN in directory tree */
	blk_index = dbc_find_record(psa, psa->gvn_key, (psa->phase_one ? 0 : 1), min_levl, gdsblk_dtroot);
	return blk_index;
}

/* Find the record in a given block
   rc = -1   : integrity error detected
      = -2   : record not found
      = else : the index of the block in the cache where record was found (curr_blk_key is set for matching record).
   Note since this routine is used in certify phase to lookup all the right hand siblings of a gvtroot block given a
   maximum key, this flag tells us that failure is ok .. we just wanted to populate the cache with siblings.
*/
int dbc_find_record(phase_static_area *psa, dbc_gv_key *key, int blk_index, int min_levl, enum gdsblk_type newblk_type)
{
	uchar_ptr_t	rec_p, blk_p, blk_top, key1, key2;
	unsigned short	us_rec_len;
	int		blk_ptr, blk_levl, key_len, key_len1, key_len2, rec_len;
	int		tmp_cmpc;
	enum gdsblk_type blk_type;
	block_info	*blk_set_p;

	DBC_DEBUG(("DBC_DEBUG: dbc_find_record: Beginning scan of block index %d\n", blk_index));
	/* If blk_index is 0, there is no starting block in the cache/set so we read block 1 (root) */
	assert(0 <= min_levl);
	blk_type = newblk_type;
	if (gdsblk_dtroot == blk_type)
	{
		assert((psa->phase_one && 0 == blk_index) || (!psa->phase_one && 1 == blk_index));
		assert((psa->phase_one && -1 == psa->block_depth) || (!psa->phase_one && 0 == psa->block_depth));
		blk_index = dbc_read_dbblk(psa, 1, gdsblk_dtroot);
		blk_type = gdsblk_dtgeneric;		/* Type of future blocks */
	} else if (gdsblk_gvtroot == blk_type)
		blk_type = gdsblk_gvtgeneric;		/* Type of future read blocks */
	blk_set_p = &psa->blk_set[blk_index];
	blk_levl = ((v15_blk_hdr_ptr_t)blk_set_p->old_buff)->levl;

	/* If we have reached the minimum level, we are done but ONLY if this is also our target block (blk_index == 0).
	   If we are not at blk_index 0 then we need to find where our key fits into this block. This is typcially
	   in the first search of the directory tree where min_levl is 0 but when we hit the dtleaf block we still
	   want to search it to find the pointer to the gvt root block.
	*/
	if (min_levl == blk_levl && 0 == blk_index)
	{	/* This is the level we were looking for and record is found */
		DBC_DEBUG(("DBC_DEBUG: dbc_find_record: Reached minimum block level and found block we were looking for\n"));
		return blk_index;
	}
	/* Find first block that is greater than or equal to our gvn or if we find a star-key */
	blk_p = blk_set_p->old_buff;
	rec_p = blk_p + SIZEOF(v15_blk_hdr);
	blk_top = blk_p + ((v15_blk_hdr_ptr_t)blk_set_p->old_buff)->bsiz;
	while (rec_p < blk_top)
	{
		blk_set_p->prev_match = blk_set_p->curr_match;
		blk_set_p->curr_match = 0;
		GET_USHORT(us_rec_len, &((rec_hdr *)rec_p)->rsiz);
		rec_len = us_rec_len;
		if (0 >= rec_len || rec_len > psa->dbc_cs_data->max_rec_size)
			/* Something messed up integrity wise - matters for phase-2 but not for phase-1 */
			return -1;
		if (0 != blk_levl && BSTAR_REC_SIZE == rec_len)
		{	/* We have a star record - This is the record we are looking for in this block */
			blk_set_p->curr_blk_key->end = 0;			/* Key length is zero for this type */
			if (min_levl == blk_levl)
			{	/* Block record and key information set up. We can return now */
				DBC_DEBUG(("DBC_DEBUG: dbc_find_record: Reached minimum block level -- matching scan was a star"
					   " key record\n"));
				return blk_index;
			}
			DBC_DEBUG(("DBC_DEBUG: dbc_find_record: Recursing down a level via star key record at offset 0x%lx\n",
				   (rec_p - blk_p)));
			GET_ULONG(blk_ptr, rec_p + VMS_ONLY(3) UNIX_ONLY(4));
			blk_index = dbc_read_dbblk(psa, blk_ptr, blk_type);
			/* Keep looking next level down */
			return dbc_find_record(psa, key, blk_index, min_levl, blk_type);
		}
		/* Determine key for this record */
		dbc_find_key(psa, blk_set_p->curr_blk_key, rec_p, blk_set_p->blk_levl);
		/* Perform key comparison keeping track of how many chars match (compression count) */
		if (dbc_match_key(blk_set_p->curr_blk_key, blk_set_p->blk_levl,
				  key, &blk_set_p->curr_match))
		{	/* Found our record - If the record is in an index block, recurse. Else return the record we found */
			if (gdsblk_gvtleaf == blk_set_p->blk_type || min_levl == blk_levl)
			{	/* This is a terminal block. It is the end of the road */
				DBC_DEBUG(("DBC_DEBUG: dbc_find_record: Reached minimum block level (or leaf level) -- matching"
					   " scan was a normal keyed record at offset 0x%lx\n", (rec_p - blk_p)));
				return blk_index;
			}
			/* We already know that the current block is not the one we are interested in and therefore
			   this record is known to contain a pointer to another block. Read the block in and
			   recurse to continue the search for the key.
			*/
			DBC_DEBUG(("DBC_DEBUG: dbc_find_record: Recursing down a level via keyed index record at offset 0x%lx\n",
				   (rec_p - blk_p)));
			GET_ULONG(blk_ptr, (rec_p + SIZEOF(rec_hdr) + blk_set_p->curr_blk_key->end
					   - EVAL_CMPC((rec_hdr *)rec_p) + 1));
			blk_index = dbc_read_dbblk(psa, blk_ptr, blk_type);
			return dbc_find_record(psa, key, blk_index, min_levl, blk_type);
		}
		/* We want to be able to find the previous record */
		blk_set_p->prev_rec = rec_p;
		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 += rec_len;					/* Point to next record in block */
		blk_set_p->curr_rec = rec_p;
	}
	/* Key not found */
	DBC_DEBUG(("DBC_DEBUG: dbc_find_record: Searched for key was not found\n"));
	return -2;
}

/* Compare two keys. If key1 is logically greater than or equal to key2, return TRUE, else FALSE.
   Also, set output parameter matchc with the number of chars that matched */
boolean_t dbc_match_key(dbc_gv_key *key1, int blk_levl1, dbc_gv_key *key2, unsigned int *matchc)
{
	uchar_ptr_t 	key_val1, key_val2;
	int		key_len1, key_len2, key_len, lcl_matchc;

	lcl_matchc = 0;
	key_val1 = key1->base;
	key_val2 = key2->base;
	key_len1 = key1->end + 1;
	if (1 == key_len1 && 0 < blk_levl1)
	{	/* This is a star key record. It is always greater than the second key but
		   has no matching characters.
		*/
		*matchc = 0;
		return TRUE;
	}
	assert(1 < key_len1); 		/* Otherwise we expect to see a key here */
	key_len2 =  key2->end + 1;
	assert(1 < key_len2);		/* Not expecting a star key record in second position */
	key_len = MIN(key_len1, key_len2);
	for (; key_len; key_val1++, key_val2++, key_len--)
	{
		if (*key_val1 == *key_val2)
			++lcl_matchc;
		else
			break;
	}
	*matchc = lcl_matchc;
	if ((0 == key_len && key_len1 >= key_len2) || (0 != key_len && *key_val1 > *key_val2))
		return TRUE;
	return FALSE;
}

/* Initialize the given gv_key to a null status so dbc_find_key() start off with a new key */
void dbc_init_key(phase_static_area *psa, dbc_gv_key **key)
{
	if (NULL == *key)
	{	/* Need a key allocated for this block */
		*key = malloc(SIZEOF(dbc_gv_key) + psa->dbc_cs_data->max_key_size);
		(*key)->top = (uint4)(SIZEOF(dbc_gv_key) + psa->dbc_cs_data->max_key_size);
	}
	(*key)->end  = (*key)->gvn_len = 0;
	return;
}

/* Routine to initialize a blk_set block */
void dbc_init_blk(phase_static_area *psa, block_info *blk_set_p, int blk_num, enum gdsblk_usage blk_usage, int blk_len,
		  int blk_levl)
{
	blk_set_p->blk_num = blk_num;
	blk_set_p->usage = blk_usage;
	blk_set_p->ins_rec.blk_id = 0;
	blk_set_p->found_in_cache = FALSE;
	dbc_init_key(psa, &blk_set_p->curr_blk_key);
	dbc_init_key(psa, &blk_set_p->prev_blk_key);
	dbc_init_key(psa, &blk_set_p->ins_rec.ins_key);
	blk_set_p->blk_len = blk_len;
	blk_set_p->blk_levl = blk_levl;
	blk_set_p->curr_match = blk_set_p->prev_match = 0;
	if (NULL == blk_set_p->old_buff)
		blk_set_p->old_buff = malloc(psa->dbc_cs_data->blk_size);
	if (NULL == blk_set_p->new_buff)
		blk_set_p->new_buff = malloc(psa->dbc_cs_data->blk_size);
	blk_set_p->curr_rec = blk_set_p->prev_rec = blk_set_p->old_buff + SIZEOF(v15_blk_hdr);
	blk_set_p->upd_addr = NULL;
	blk_set_p->ins_blk_id_p = NULL;
	return;
}

/* Initialize database usage - open it and read in the file-header */
void dbc_init_db(phase_static_area *psa)
{	/* This routine does the database open and initialization for both scan (phase1) and certify
	   phases however the requirements for these phases differ somewhat. For the scan phase we
	   just want to open the database, read the file-header into dbc_cs_data and verify this is a
	   database. For the certify phase, we also have to obtain standalone access, make sure the file
	   hasn't moved around since the scan phase and add reserved_bytes and max_rec_size checks. But
	   standalone access is somewhat tricky as part of the standalone verification process involves
	   (for later V4 versions) knowing the key of the shared memory segment which is kept in the
	   file-header meaning we have to read the file-header before we can lock it on UNIX. Because
	   of this, on UNIX we will RE-READ the file-header after obtaining the lock to make sure there
	   are no stale values.
	*/

	/* We must have RW access in scan phase in order to do the DSE buffer flush to assure we are looking
	   at the correct database blocks and of course the certify phase needs R/W access to do its job.
	*/
	if (0 != ACCESS((char_ptr_t)psa->dbc_gv_cur_region->dyn.addr->fname, (R_OK | W_OK)))
	{
		if (EACCES == errno)
			rts_error_csa(CSA_ARG(NULL) VARLSTCNT(4) ERR_DBRDONLY, 2, DB_LEN_STR(psa->dbc_gv_cur_region));
		else
			rts_error_csa(CSA_ARG(NULL) VARLSTCNT(5) ERR_DBOPNERR, 2, DB_LEN_STR(psa->dbc_gv_cur_region), errno);
	}
	/* Open the database which on VMS gives standalone access (for phase 2) */
	psa->fc->op = FC_OPEN;
	dbcertify_dbfilop(psa);		/* Knows this is a phase 2 open so gives standalone access on VMS */
	psa->dbc_gv_cur_region->open = TRUE;

	if (!psa->phase_one)
	{	/* Verify the fileid has not changed */
		if (!is_gdid_gdid_identical(&psa->ofhdr.unique_id.uid,
					    &FILE_INFO(psa->dbc_gv_cur_region)->UNIX_ONLY(fileid)VMS_ONLY(file_id)))
			rts_error_csa(CSA_ARG(NULL) VARLSTCNT(1) ERR_DBCNOTSAMEDB);
	}

	/* Read in database file header */
	psa->fc->op = FC_READ;
	psa->fc->op_buff = (sm_uc_ptr_t)psa->dbc_cs_data;
	psa->fc->op_len = SIZEOF(*psa->dbc_cs_data);
	psa->fc->op_pos = 1;
	dbcertify_dbfilop(psa);

	/* Verify we (still) have a GT.M V4 database */
	if (0 != memcmp(psa->dbc_cs_data->label, V15_GDS_LABEL, GDS_LABEL_SZ - 1))
	{
		if (memcmp(psa->dbc_cs_data->label, V15_GDS_LABEL, GDS_LABEL_SZ - 3))
			rts_error_csa(CSA_ARG(NULL) VARLSTCNT(4) ERR_DBNOTGDS, 2, RTS_ERROR_TEXT((char_ptr_t)psa->ofhdr.dbfn));
		else
			rts_error_csa(CSA_ARG(NULL) VARLSTCNT(4) ERR_BADDBVER, 2, RTS_ERROR_TEXT((char_ptr_t)psa->ofhdr.regname));
	}

	if (!psa->phase_one)
	{
#ifdef UNIX
		/* Obtain standalone access to database.

		In UNIX, this requires creation/access to the "ftok" database semaphore. This is
		the main semaphore in some V4 releases and the "startup/rundown" semaphore in other
		releases. But in all cases, its creation and locking will govern standalone access
		to the database in question.

		On VMS, we just don't open the file as shared and we are guarranteed standalone
		access to it.
		*/
		dbc_aquire_standalone_access(psa);
		dbcertify_dbfilop(psa);		/* Re-read file header */
#endif
		/* Verify reserved_bytes and max_rec_len again and verify kill_in_prog is not set */
		if (VMS_ONLY(9) UNIX_ONLY(8) > psa->dbc_cs_data->reserved_bytes)
			rts_error_csa(CSA_ARG(NULL) VARLSTCNT(4) ERR_DBMINRESBYTES, 2,
					VMS_ONLY(9) UNIX_ONLY(8), psa->dbc_cs_data->reserved_bytes);
		if (SIZEOF(blk_hdr) > (psa->dbc_cs_data->blk_size - psa->dbc_cs_data->max_rec_size))
			rts_error_csa(CSA_ARG(NULL) VARLSTCNT(5) ERR_DBMAXREC2BIG, 3,
					psa->dbc_cs_data->max_rec_size, psa->dbc_cs_data->blk_size,
					  (psa->dbc_cs_data->blk_size - SIZEOF(blk_hdr)));
		if (0 != psa->dbc_cs_data->kill_in_prog)
			rts_error_csa(CSA_ARG(NULL) VARLSTCNT(4) ERR_DBCKILLIP, 2,
					RTS_ERROR_TEXT((char_ptr_t)psa->ofhdr.dbfn));
		/* Turn off replication and/or journaling for our trip here */
		if (jnl_open == psa->dbc_cs_data->jnl_state)
		{
			psa->dbc_cs_data->jnl_state = jnl_closed;
			psa->dbc_cs_data->repl_state = repl_closed;
		}
	}
	return;
}

/* Close the database and remove any semaphores we had protecting it */
void dbc_close_db(phase_static_area *psa)
{
	if (psa->dbc_gv_cur_region->open)
	{	/* If region is open.. close it and release the semaphores.. */
		psa->fc->op = FC_CLOSE;
		dbcertify_dbfilop(psa);
		psa->dbc_gv_cur_region->open = FALSE;
		UNIX_ONLY(if (!psa->phase_one) dbc_release_standalone_access(psa));
	}
	return;
}

#ifdef UNIX
/* Aquire semaphores that on on all V4.x releases are the access control semaphores. In pre V4.2 releases
   they were based on an FTOK of the database name with an ID of '1'. In V4.2 and later, they are based on
   the FTOK of the database name with an ID of '43'. Since we do not know which flavor of database we are
   dealing with, we must create and acquire both flavors of semaphore and hold them for the duration of
   the phase 2 run. But just holding these semaphore is not sufficient to guarrantee standalone access. We
   also must attempt to attach to the shared memory for the segment. If it is found, standalone access
   is not achieved. Early V4 versions (prior to V4.2) created the shared memory with the same FTOK id as the
   semaphore. Later versions would have had the key of the created private section in the file-header. Use
   both approaches and fail our attempt if either succeeds.
*/
void dbc_aquire_standalone_access(phase_static_area *psa)
{
	mu_all_version_get_standalone((char_ptr_t)psa->dbc_gv_cur_region->dyn.addr->fname, &psa->sem_inf[0]);
}

void dbc_release_standalone_access(phase_static_area *psa)
{
	mu_all_version_release_standalone(&psa->sem_inf[0]);
}
#endif
