/****************************************************************
 *								*
 *	Copyright 2001, 2012 Fidelity Information Services, Inc	*
 *								*
 *	This source code contains the intellectual property	*
 *	of its copyright holder(s), and is made available	*
 *	under a license.  If you do not know the terms of	*
 *	the license, please stop and do not read further.	*
 *								*
 ****************************************************************/

#include "mdef.h"

#include <stdarg.h>
#if defined(UNIX) && defined(DEBUG)
# include "gtm_syslog.h"	/* Needed for white box case in VTK_STORDUMP */
#endif
#include "gtm_string.h"
#include "gtm_stdio.h"

#include "gtmio.h"
#include "util.h"
#include "have_crit.h"
#include "gdsroot.h"
#include "gtm_facility.h"
#include "fileinfo.h"
#include "gdsbt.h"
#include "gdsfhead.h"
#include "filestruct.h"
#include "cmd_qlf.h"
#include "collseq.h"
#include "error.h"
#include "iosp.h"
#include "jnl.h"
#include "lv_val.h"
#include "view.h"
#include "send_msg.h"
#include "op.h"
#include "change_reg.h"
#include "patcode.h"
#include "mprof.h"
#include "cmidef.h"
#include "gvcmz.h"
#include "testpt.h"
#include "mvalconv.h"
#include "dpgbldir.h"	/* for get_next_gdr() prototype */
#include "ast.h"
#include "wcs_flu.h"
#include "stringpool.h"
#include "gtmdbglvl.h"
#include "gtm_malloc.h"
#include "alias.h"
#include "fullbool.h"
#ifdef GTM_TRIGGER
# include "rtnhdr.h"		/* for rtn_tabent in gv_trigger.h */
# include "gv_trigger.h"
# include "gtm_trigger.h"
#endif
#ifdef UNIX
# include "wbox_test_init.h"
# include "mutex.h"
# ifdef DEBUG
#  include "gtmsecshr.h"
# endif
#endif

GBLREF	boolean_t		certify_all_blocks;
GBLREF	bool			undef_inhibit, jobpid;
GBLREF	bool			view_debug1, view_debug2, view_debug3, view_debug4;
GBLREF	bool			zdefactive;
GBLREF	unsigned short		zdefbufsiz;
GBLREF	int4			break_message_mask;
GBLREF	command_qualifier	cmd_qlf, glb_cmd_qlf;
GBLREF	gd_addr			*gd_header;
GBLREF	gd_region		*gv_cur_region;
GBLREF	gv_namehead		*gv_target, *gv_target_list;
GBLREF	gv_namehead		*reset_gv_target;
GBLREF	sgmnt_addrs		*cs_addrs;
GBLREF	sgmnt_data_ptr_t	cs_data;
GBLREF	symval			*curr_symval;
GBLREF	trans_num		local_tn;	/* transaction number for THIS PROCESS */
GBLREF	int4			zdir_form;
GBLREF	boolean_t		gvdupsetnoop; /* if TRUE, duplicate SETs update journal but not database (except for curr_tn++) */
GBLREF	boolean_t		badchar_inhibit;
GBLREF	int			gv_fillfactor;
GBLREF	symval			*curr_symval;
GBLREF	uint4			gtmDebugLevel;
GBLREF	boolean_t		lvmon_enabled;
GBLREF	spdesc			stringpool;
GBLREF	boolean_t		is_updproc;
GBLREF	pid_t			process_id;

error_def(ERR_ACTRANGE);
error_def(ERR_COLLATIONUNDEF);
error_def(ERR_COLLDATAEXISTS);
error_def(ERR_INVZDIRFORM);
error_def(ERR_ISOLATIONSTSCHN);
error_def(ERR_JNLFLUSH);
error_def(ERR_PATLOAD);
error_def(ERR_PATTABNOTFND);
error_def(ERR_REQDVIEWPARM);
error_def(ERR_TEXT);
error_def(ERR_TRACEON);
error_def(ERR_VIEWCMD);
error_def(ERR_YDIRTSZ);
error_def(ERR_ZDEFACTIVE);

#define MAX_YDIRTSTR 32
#define ZDEFMIN 1024
#define ZDEFDEF 32767
#define ZDEFMAX 65535

#define WRITE_LITERAL(x) (outval.str.len = SIZEOF(x) - 1, outval.str.addr = (x), op_write(&outval))

/* if changing noisolation status within TP and already referenced the global, then error */
#define SET_GVNH_NOISOLATION_STATUS(gvnh, status)							\
{													\
	GBLREF	uint4			dollar_tlevel;							\
													\
	if (!dollar_tlevel || gvnh->read_local_tn != local_tn || status == gvnh->noisolation)		\
		gvnh->noisolation = status;								\
	else												\
		rts_error(VARLSTCNT(6) ERR_ISOLATIONSTSCHN, 4, gvnh->gvname.var_name.len, 		\
			gvnh->gvname.var_name.addr, gvnh->noisolation, status);				\
}

void	op_view(UNIX_ONLY_COMMA(int numarg) mval *keyword, ...)
{
	int4			testvalue, tmpzdefbufsiz;
	uint4			jnl_status, dummy_errno;
	int			status, lcnt, icnt;
	gd_region		*reg, *r_top, *save_reg;
	gv_namehead		*gvnh;
	mval			*arg, *nextarg, outval;
	mstr			tmpstr;
	va_list			var;
	viewparm		parmblk;
	viewtab_entry		*vtp;
	gd_addr			*addr_ptr;
	noisolation_element	*gvnh_entry;
	int			lct, ncol, nct;
	collseq			*new_lcl_collseq;
	ht_ent_mname		*tabent, *topent;
	lv_val			*lv;
	symval			*cstab;
	sgmnt_addrs		*csa;
	sgmnt_data_ptr_t	csd;
	jnl_buffer_ptr_t	jb;
	int			table_size_orig;
	ht_ent_mname		*table_base_orig;
	hash_table_mname	*table;
	boolean_t		dbgdmpenabled, was_crit;
	symval			*lvlsymtab;
	lv_blk			*lvbp;
	lv_val			*lvp, *lvp_top;
	VMS_ONLY(int		numarg;)

	static readonly char msg1[] = "Caution: Database Block Certification Has Been ";
	static readonly char msg2[] = "Disabled";
	static readonly char msg3[] = "Enabled";
	static readonly char lv_msg1[] =
		"Caution: GT.M reserved local variable string pointer duplicate check diagnostic has been";
	static readonly char upper[] = "UPPER";
	static readonly char lower[] = "LOWER";
	DCL_THREADGBL_ACCESS;

	SETUP_THREADGBL_ACCESS;
	VAR_START(var, keyword);
	VMS_ONLY(va_count(numarg));
		jnl_status = 0;
	if (numarg < 1)
		GTMASSERT;
	MV_FORCE_STR(keyword);
	numarg--;	/* remove keyword from count */
	if (0 < numarg)
	{
		arg = va_arg(var, mval *);
		MV_FORCE_STR(arg);
	} else
		arg = (mval *)NULL;
	vtp = viewkeys(&keyword->str);
	view_arg_convert(vtp, arg, &parmblk);
	switch(vtp->keycode)
	{
#		ifdef UNICODE_SUPPORTED
		case VTK_BADCHAR:
			badchar_inhibit = FALSE;
			break;
		case VTK_NOBADCHAR:
			badchar_inhibit = TRUE;
			break;
#		endif
		case VTK_BREAKMSG:
			break_message_mask = MV_FORCE_INT(parmblk.value);
			break;
		case VTK_DEBUG1:
			outval.mvtype = MV_STR;
			view_debug1 = (0 != MV_FORCE_INT(parmblk.value));
			break;
		case VTK_DEBUG2:
			outval.mvtype = MV_STR;
			view_debug2 = (0 != MV_FORCE_INT(parmblk.value));
			break;
		case VTK_DEBUG3:
			outval.mvtype = MV_STR;
			view_debug3 = (0 != MV_FORCE_INT(parmblk.value));
			break;
		case VTK_DEBUG4:
			outval.mvtype = MV_STR;
			view_debug4 = (0 != MV_FORCE_INT(parmblk.value));
			break;
		case VTK_FLUSH:
			if (NULL == gd_header)		/* open gbldir */
				gvinit();
			save_reg = gv_cur_region;
			if (NULL == parmblk.gv_ptr)
			{	/* flush all regions */
				reg = gd_header->regions;
				r_top = reg + gd_header->n_regions - 1;
			} else	/* flush selected region */
				r_top = reg = parmblk.gv_ptr;
			for (;  reg <= r_top;  reg++)
			{
				if (!reg->open)
					gv_init_reg(reg);
				if (!reg->read_only)
				{
					gv_cur_region = reg;
					change_reg(); /* for jnl_ensure_open */
					ENSURE_JNL_OPEN(cs_addrs, gv_cur_region);
					/* We should NOT invoke wcs_recover here because it's possible we are in the final retry
					 * of a TP transaction. In this case, we likely have pointers to non-dirty global buffers
					 * in our transaction histories. Doing cache recovery could dry clean most of these buffers.
					 * And that might cause us to get confused, attempt to restart, and incorrectly issue a
					 * TPFAIL error because we are already in the final retry. By passing the WCSFLU_IN_COMMIT
					 * bit, we instruct wcs_flu to avoid wcs_recover.
					 */
					wcs_flu(WCSFLU_FLUSH_HDR | WCSFLU_WRITE_EPOCH | WCSFLU_SYNC_EPOCH | WCSFLU_IN_COMMIT);
				}
			}
			gv_cur_region = save_reg;
			change_reg();
			break;
		case VTK_FULLBOOL:
			TREF(gtm_fullbool) = FULL_BOOL;
			break;
		case VTK_FULLBOOLWARN:
			TREF(gtm_fullbool) = FULL_BOOL_WARN;
			break;
		case VTK_NOFULLBOOL:
			TREF(gtm_fullbool) = GTM_BOOL;
			break;
		case VTK_GDSCERT0:
			outval.mvtype = MV_STR;
			op_wteol(1);
			WRITE_LITERAL(msg1);
			certify_all_blocks = FALSE;
			WRITE_LITERAL(msg2);
			op_wteol(1);
			break;
		case VTK_GDSCERT1:
			outval.mvtype = MV_STR;
			op_wteol(1);
			WRITE_LITERAL(msg1);
			certify_all_blocks = TRUE;
			WRITE_LITERAL(msg3);
			op_wteol(1);
			break;
		case VTK_GDSCERT:
			outval.mvtype = MV_STR;
			op_wteol(1);
			WRITE_LITERAL(msg1);
			certify_all_blocks = (0 != MV_FORCE_INT(parmblk.value));
			if (certify_all_blocks)
				WRITE_LITERAL(msg3);
			else
				WRITE_LITERAL(msg2);
			op_wteol(1);
			break;
		case VTK_GVDUPSETNOOP:
			gvdupsetnoop = (0 != MV_FORCE_INT(parmblk.value));
			break;
		case VTK_LVDUPCHECK:
			/* This feature is not needed any more. This is a noop now */
			break;
		case VTK_LVNULLSUBS:
			TREF(lv_null_subs) = LVNULLSUBS_OK;
			break;
		case VTK_NOLVNULLSUBS:
			TREF(lv_null_subs) = LVNULLSUBS_NO;
			break;
		case VTK_NEVERLVNULLSUBS:
			TREF(lv_null_subs) = LVNULLSUBS_NEVER;
			break;
#		ifndef VMS
		case VTK_JNLERROR:
			/* In case of update process, dont let this variable be user-controlled. We always want to error out
			 * if we are about to invoke "jnl_file_lost". This way we will force the operator to fix whatever
			 * caused the jnl_file_lost invocation (e.g. permissions, disk space issues etc.) and restart
			 * the receiver server so replication can resume from wherever we last left. On the other hand,
			 * automatically turning off journaling (and hence replication) (like is a choice for GT.M)
			 * would make resumption of replication much more effort for the user.
			 */
			assert(!is_updproc || (JNL_FILE_LOST_ERRORS == TREF(error_on_jnl_file_lost)));
			if (!is_updproc)
			{
				TREF(error_on_jnl_file_lost) = MV_FORCE_INT(parmblk.value);
				if (MAX_JNL_FILE_LOST_OPT < TREF(error_on_jnl_file_lost))
					TREF(error_on_jnl_file_lost) = JNL_FILE_LOST_TURN_OFF;
				if (NULL == gd_header)		/* open gbldir */
					gvinit();
				save_reg = gv_cur_region;
				/* change all regions */
				reg = gd_header->regions;
				r_top = reg + gd_header->n_regions - 1;
				for (;  reg <= r_top;  reg++)
				{
					if (!reg->open)
						gv_init_reg(reg);
					if (!reg->read_only)
					{
						gv_cur_region = reg;
						change_reg();
						csa = cs_addrs;
						csd = csa->hdr;
						if (JNL_ENABLED(csd))
						{
							was_crit = csa->now_crit;
							if (!was_crit)
								grab_crit(reg);
							if (JNL_ENABLED(csd))
								csa->jnl->error_reported = FALSE;
							if (!was_crit)
								rel_crit(reg);
						}
					}
				}
			}
			break;
#		endif
		case VTK_JNLFLUSH:
			if (NULL == gd_header)		/* open gbldir */
				gvinit();
			save_reg = gv_cur_region;
			if (NULL == parmblk.gv_ptr)
			{	/* flush all journal files */
				reg = gd_header->regions;
				r_top = reg + gd_header->n_regions - 1;
			} else	/* flush journal for selected region */
				r_top = reg = parmblk.gv_ptr;
			for (;  reg <= r_top;  reg++)
			{
				if (!reg->open)
					gv_init_reg(reg);
				gv_cur_region = reg;
				change_reg();
				csa = cs_addrs;
				csd = csa->hdr;
				if (JNL_ENABLED(csd))
				{
					was_crit = csa->now_crit;
					if (!was_crit)
						grab_crit(reg);
					if (JNL_ENABLED(csd))
					{
						jnl_status = jnl_ensure_open();
						if (0 == jnl_status)
						{
							jb = csa->jnl->jnl_buff;
							if (SS_NORMAL == (jnl_status = jnl_flush(reg)))
							{
								assert(jb->dskaddr == jb->freeaddr);
								UNIX_ONLY(jnl_fsync(reg, jb->dskaddr));
								UNIX_ONLY(assert(jb->freeaddr == jb->fsync_dskaddr));
							} else
							{
								send_msg(VARLSTCNT(9) ERR_JNLFLUSH, 2, JNL_LEN_STR(csd),
									ERR_TEXT, 2,
									RTS_ERROR_TEXT("Error with journal flush during op_view"),
									jnl_status);
								assert(FALSE);
							}
						} else
							send_msg(VARLSTCNT(6) jnl_status, 4, JNL_LEN_STR(csd), DB_LEN_STR(reg));
					}
					if (!was_crit)
						rel_crit(reg);
				}
			}
			gv_cur_region = save_reg;
			change_reg();
			break;
		case VTK_JNLWAIT:
			/* go through all regions that could have possibly been open across all global directories */
			for (addr_ptr = get_next_gdr(NULL); addr_ptr; addr_ptr = get_next_gdr(addr_ptr))
				for (reg = addr_ptr->regions, r_top = reg + addr_ptr->n_regions; reg < r_top; reg++)
					jnl_wait(reg);
			break;
		case VTK_JOBPID:
			outval.mvtype = MV_STR;
			jobpid = (0 != MV_FORCE_INT(parmblk.value));
			break;
		case VTK_LABELS:
			if ((SIZEOF(upper) - 1) == parmblk.value->str.len)
			{
				if (!memcmp(upper, parmblk.value->str.addr, SIZEOF(upper) - 1))
					glb_cmd_qlf.qlf &= ~CQ_LOWER_LABELS;
				else  if (!memcmp(lower, parmblk.value->str.addr, SIZEOF(lower) - 1))
					glb_cmd_qlf.qlf |= CQ_LOWER_LABELS;
				cmd_qlf.qlf = glb_cmd_qlf.qlf;
			}
			break;
		case VTK_NOISOLATION:
			if (NOISOLATION_NULL == parmblk.ni_list.type)
			{
				for (gvnh = gv_target_list; NULL != gvnh; gvnh = gvnh->next_gvnh)
					SET_GVNH_NOISOLATION_STATUS(gvnh, FALSE);
				parmblk.ni_list.type = NOISOLATION_PLUS;
			}
			assert((NOISOLATION_PLUS == parmblk.ni_list.type) || (NOISOLATION_MINUS == parmblk.ni_list.type));
			if (NOISOLATION_PLUS == parmblk.ni_list.type)
			{
				for (gvnh_entry = parmblk.ni_list.gvnh_list; gvnh_entry; gvnh_entry = gvnh_entry->next)
					SET_GVNH_NOISOLATION_STATUS(gvnh_entry->gvnh, TRUE);
			} else
			{
				for (gvnh_entry = parmblk.ni_list.gvnh_list; gvnh_entry; gvnh_entry = gvnh_entry->next)
					SET_GVNH_NOISOLATION_STATUS(gvnh_entry->gvnh, FALSE);
			}
			break;
		case VTK_PATCODE:
			if (arg)
				tmpstr = parmblk.value->str;
			else
				tmpstr.len = 0;
			status = setpattab(&tmpstr);
			if (0 == status)
			{
				va_end(var);
				rts_error(VARLSTCNT(4) ERR_PATTABNOTFND, 2, parmblk.value->str.len, parmblk.value->str.addr);
			}
			break;
		case VTK_RESETGVSTATS:
			for (addr_ptr = get_next_gdr(NULL); addr_ptr; addr_ptr = get_next_gdr(addr_ptr))
			{
				for (reg = addr_ptr->regions, r_top = reg + addr_ptr->n_regions; reg < r_top; reg++)
				{
					if (!reg->open)
						continue;
					switch(reg->dyn.addr->acc_meth)
					{
						case dba_mm:
						case dba_bg:
							csa = &FILE_INFO(reg)->s_addrs;
							memset(&csa->gvstats_rec, 0, SIZEOF(gvstats_rec_t));
							break;
						case dba_cm:
						case dba_usr:
							break;
						default:
							GTMASSERT;
							break;
					}
				}
			}
			break;
		case VTK_YDIRTVAL:
			/* This is an internal use only call to VIEW which is used to modify directory
			 * tree entries.  It places a string in in the static array view_ydirt_str.  A subsequent
			 * view to YDIRTREE causes the value to be put in the directory tree.
			 * For example VIEW "YDIRTVAL":$C(55,0,0,0),"YDIRTREE":XYZ would cause global ^XYZ
			 * to have a root block of 0x00000055, on a little-endian computer.
			 * NOTE: it is easy to corrupt a database with these calls.
			 * The use of the "Y" sentinal character indicates that the VIEW keyword is interal
			 * FIS use only, and that FIS will not provide upward compatibility to the user.
			 * These keywords should NOT be used outside of FIS software
			 */
			if (NULL == TREF(view_ydirt_str))
				TREF(view_ydirt_str) = (char *)malloc(MAX_YDIRTSTR + 1);
			TREF(view_ydirt_str_len) = parmblk.value->str.len;
			if (TREF(view_ydirt_str_len) > MAX_YDIRTSTR)
			{
				va_end(var);
				rts_error(VARLSTCNT(1) ERR_YDIRTSZ);
			}
			if (TREF(view_ydirt_str_len) > 0)
				memcpy(TREF(view_ydirt_str), parmblk.value->str.addr, TREF(view_ydirt_str_len));
			break;
		case VTK_YDIRTREE:
			/* See comment under YDIRTVAL above
			 * Restriction: This program will update an existing entry in a directory tree but
			 * will fail if the entry is not present.  So make sure that the global exists before
			 * a YDIRTREE update is performed
			 */
			op_gvname(VARLSTCNT(1) parmblk.value);
			assert(INVALID_GV_TARGET == reset_gv_target);
			reset_gv_target = gv_target;
			gv_target = cs_addrs->dir_tree;		/* Trick the put program into using the directory tree */
			outval.mvtype = MV_STR;
			outval.str.len = TREF(view_ydirt_str_len);
			outval.str.addr = (char *)TREF(view_ydirt_str);
			op_gvput(&outval);
			RESET_GV_TARGET(DO_GVT_GVKEY_CHECK);
			gv_target->root = 0;
			/* Now that root is set to 0, the next access to this global will go through gvcst_root_search which will
			 * re-initialize "nct", "act" and "ver" accordingly and also initialize "collseq" ONLY IF "act" is non-zero.
			 * Since we do not know if "act" will be 0 or not in gvcst_root_search, let us reset "collseq" to NULL here.
			 */
			gv_target->collseq = NULL;
			break;
		case VTK_YLCT:
			if (arg)
				lct = MV_FORCE_INT(parmblk.value);
			else
			{
				va_end(var);
				rts_error(VARLSTCNT(1) ERR_REQDVIEWPARM);
			}
			nextarg = NULL;
			ncol = -1;
			if (numarg > 1)
			{
				nextarg = va_arg(var, mval *);
				ncol = MV_FORCE_INT(nextarg);
			}
			nextarg = NULL;
			nct = -1;
			if (numarg > 2)
			{
				nextarg = va_arg(var, mval *);
				nct = MV_FORCE_INT(nextarg);
			}
			if ((-1 == lct) && (-1 == ncol) && (-1 == nct))
				break;
			/* lct = -1 indicates user wants to change only ncol, not lct */
			if (-1 != lct)
			{
				if ((lct < MIN_COLLTYPE) || (lct > MAX_COLLTYPE))
				{
					va_end(var);
					rts_error(VARLSTCNT(3) ERR_ACTRANGE, 1, lct);
				}
			}
			/* at this point, verify that there is no local data with subscripts */
			for (cstab = curr_symval; cstab; cstab = cstab->last_tab)
			{
				assert(cstab->h_symtab.top == cstab->h_symtab.base + cstab->h_symtab.size);
				for (tabent = cstab->h_symtab.base, topent = cstab->h_symtab.top; tabent < topent; tabent++)
				{
					if (HTENT_VALID_MNAME(tabent, lv_val, lv))
					{
						assert(LV_IS_BASE_VAR(lv));
						if (lv && LV_HAS_CHILD(lv))
						{
							va_end(var);
							rts_error(VARLSTCNT(1) ERR_COLLDATAEXISTS);
						}
					}
				}
			}
			if (-1 != lct)
			{
				if (0 != lct)
				{
					new_lcl_collseq = ready_collseq(lct);
					if (0 == new_lcl_collseq)
					{
						va_end(var);
						rts_error(VARLSTCNT(3) ERR_COLLATIONUNDEF, 1, lct);
					}
					TREF(local_collseq) = new_lcl_collseq;
				} else
				{
					TREF(local_collseq) = 0;
					if (NULL != TREF(lcl_coll_xform_buff))
					{
						assert(0 < TREF(max_lcl_coll_xform_bufsiz));
						free(TREF(lcl_coll_xform_buff));
						TREF(lcl_coll_xform_buff) = NULL;
						TREF(max_lcl_coll_xform_bufsiz) = 0;
					}
				}
			}
			if (-1 != ncol)
				TREF(local_collseq_stdnull) = (ncol ? TRUE: FALSE);
			if (-1 != nct)
				TREF(local_coll_nums_as_strings) = (nct ? TRUE: FALSE);
			break;
		case VTK_PATLOAD:
			if (!load_pattern_table(parmblk.value->str.len, parmblk.value->str.addr))
			{
				va_end(var);
				rts_error(VARLSTCNT(4) ERR_PATLOAD, 2, parmblk.value->str.len, parmblk.value->str.addr);
			}
			break;
		case VTK_NOUNDEF:
			undef_inhibit = TRUE;
			break;
		case VTK_UNDEF:
			undef_inhibit = FALSE;
			break;
		case VTK_TESTPOINT:
			testvalue = MV_FORCE_INT(parmblk.value);
			set_testpoint(testvalue);
			break;
		case VTK_ZDEFER:
			if (zdefactive)
			{
				va_end(var);
				rts_error(VARLSTCNT(1) ERR_ZDEFACTIVE);
			}
			zdefactive = TRUE;
			tmpzdefbufsiz = MV_FORCE_INT(parmblk.value);
			if (1 == tmpzdefbufsiz)
				tmpzdefbufsiz = ZDEFDEF;
			else  if (tmpzdefbufsiz < ZDEFMIN)
				tmpzdefbufsiz = ZDEFMIN;
			else  if (tmpzdefbufsiz > ZDEFMAX)
				tmpzdefbufsiz = ZDEFMAX;
			zdefbufsiz = (unsigned short)tmpzdefbufsiz;
			break;
		case VTK_ZFLUSH:
			gvcmz_zflush();
			break;
		case VTK_TRACE :
			testvalue = MV_FORCE_INT(parmblk.value);
			if (testvalue)
			{
				if (2 > numarg)
				{
					va_end(var);
					rts_error(VARLSTCNT(1) ERR_TRACEON);
				}
				arg = va_arg(var, mval *);
				MV_FORCE_STR(arg);
				turn_tracing_on(arg, FALSE, TRUE);
			} else
			{
				if (2 == numarg)
				{
					arg = va_arg(var, mval *);
					MV_FORCE_STR(arg);
					turn_tracing_off(arg);
				} else
					turn_tracing_off(NULL);
			}
			break;
		case VTK_ZDIR_FORM:
			VMS_ONLY(
				if (NULL != arg)
				{
					testvalue = MV_FORCE_INT(parmblk.value);
					if (!IS_VALID_ZDIR_FORM(testvalue))
					{
						va_end(var);
						rts_error(VARLSTCNT(3) ERR_INVZDIRFORM, 1, testvalue);
					}
				} else
					testvalue = ZDIR_FORM_FULLPATH;
				zdir_form = testvalue;
			)
			break;
		case VTK_FILLFACTOR:
			testvalue = MV_FORCE_INT(parmblk.value);
			if (MAX_FILLFACTOR < testvalue)
				testvalue = MAX_FILLFACTOR;
			else if (MIN_FILLFACTOR > testvalue)
				testvalue = MIN_FILLFACTOR;
			gv_fillfactor = testvalue;
			break;
		case VTK_STPGCOL:
			INVOKE_STP_GCOL(INTCAST(stringpool.top - stringpool.free) + 1);/* computation to avoid assert in stp_gcol */
			break;
		case VTK_LVGCOL:
			als_lvval_gc();
			break;
		case VTK_LVREHASH:
			/* This doesn't actually expand or contract the local variable hash table but does cause it to be
			 * rebuilt. Then we need to do the same sort of cleanup that add_hashtab_mname_symval does. */
			/* Step 1: remember table we started with */
			table = &curr_symval->h_symtab;
			table_base_orig = table->base;
			table_size_orig = table->size;
			/* Step 2 - rebuild the local variable hash table */
			/* We'll do the base release once we do the reparations */
			DEFER_BASE_REL_HASHTAB(table, TRUE);
			expand_hashtab_mname(&curr_symval->h_symtab, curr_symval->h_symtab.size);
			/* Step 3 - repair the l_symtab entries on the stack from the rebuilt hash table */
			if (table_base_orig != curr_symval->h_symtab.base)
			{	/* Only needed if expansion was successful */
				als_lsymtab_repair(table, table_base_orig, table_size_orig);
				FREE_BASE_HASHTAB(table, table_base_orig);
			}
			DEFER_BASE_REL_HASHTAB(table, FALSE);
			break;
		case VTK_STORDUMP:
#			if defined(DEBUG) && defined(UNIX)
			if (gtm_white_box_test_case_enabled
			    && (WBTEST_HOLD_CRIT_TILL_LCKALERT == gtm_white_box_test_case_number))
			{	/* Hold crit for a long enough interval to generate lock alert which then does a continue_proc */
				grab_crit(gv_cur_region);
				icnt = TREF(continue_proc_cnt);
				DBGGSSHR((LOGFLAGS, "op_view: Pid %d, initial icnt: %d\n", process_id, icnt));
				for (lcnt = 0; (MUTEXLCKALERT_INTERVAL * 12) > lcnt; lcnt++)
				{	/* Poll for icnt to be increased - check every quarter second */
					SHORT_SLEEP(250);	/* Quarter second nap */
					if (TREF(continue_proc_cnt) > icnt)
						break;
					DBGGSSHR((LOGFLAGS, "op_view: loop: %d - continue_proc_cnt: %d\n", lcnt,
						  TREF(continue_proc_cnt)));
				}
				DBGGSSHR((LOGFLAGS, "op_view: pid %d, lcnt: %d, icnt: %d, continue_proc_cnt: %d\n",
					  process_id, lcnt, icnt, TREF(continue_proc_cnt)));
				rel_crit(gv_cur_region);
			} else		/* If we do the white box test, avoid the rest */
#			endif
			if (gtmDebugLevel)
			{	/* gtmdbglvl must be non-zero to have hope of printing a storage dump */
				dbgdmpenabled = (GDL_SmDump & gtmDebugLevel);
				gtmDebugLevel |= GDL_SmDump;		/* Turn on indicator to force print */
				printMallocDump();
				if (!dbgdmpenabled)
					gtmDebugLevel &= (~GDL_SmDump);	/* Shut indicator back off */
			}
			break;
#		ifdef DEBUG_ALIAS
		case VTK_LVMONOUT:
			als_lvmon_output();
			break;
		case VTK_LVMONSTART:
			lvmon_enabled = TRUE;	/* Enable lv_val monitoring */
			/* Clear any existing marks on all lv_vals */
			for (lvlsymtab = curr_symval; lvlsymtab; lvlsymtab = lvlsymtab->last_tab)
			{
				for (lvbp = curr_symval->lv_first_block; lvbp; lvbp = lvbp->next)
				{
					for (lvp = (lv_val *)LV_BLK_GET_BASE(lvbp), lvp_top = LV_BLK_GET_FREE(lvbp, lvp);
							lvp < lvp_top; lvp++)
					{
						assert(LV_IS_BASE_VAR(lv));
						lvp->lvmon_mark = FALSE;
					}
				}
			}
			break;
		case VTK_LVMONSTOP:
			als_lvmon_output();
			lvmon_enabled = FALSE;
			break;
#		endif
#		ifdef DEBUG
		case VTK_LVDMP:		/* Write partial lv_val into to output device */
			outval.mvtype = MV_STR;
			lv = (lv_val *)parmblk.value;
			util_out_print("", RESET);	/* Reset the buffer */
			util_out_print("LV: !AD  addr: 0x!XJ  mvtype: 0x!4XW  sign: !UB  exp: !UL  m[0]: !UL [0x!XL]  "
				       "m[1]: !UL [0x!XL]  str.len: !UL  str.addr: 0x!XJ", SPRINT, arg->str.len, arg->str.addr,
				       lv, lv->v.mvtype, lv->v.sgn, lv->v.e, lv->v.m[0], lv->v.m[0], lv->v.m[1], lv->v.m[1],
				       lv->v.str.len, lv->v.str.addr);
			outval.str.addr = TREF(util_outptr);
			outval.str.len = STRLEN(TREF(util_outptr));
			op_write(&outval);
			op_wteol(1);
			break;
#		endif
		default:
			va_end(var);
			rts_error(VARLSTCNT(1) ERR_VIEWCMD);
	}
	va_end(var);
	return;
}
