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

 /*
 * -------------------------------------------------------
 * This routine starts a new child process and passes the
 * job parameters to it.
 * -------------------------------------------------------
 */
#include "mdef.h"

#include <errno.h>
#include <sys/wait.h>
#include <signal.h>
#include "gtm_fcntl.h"
#include "gtm_stdlib.h"
#include "gtm_unistd.h"
#include "gtm_stdio.h"
#include "gtm_stat.h"
#include "gtm_string.h"
#if defined(SYS_ERRLIST_INCLUDE) && !defined(__CYGWIN__)
#include SYS_ERRLIST_INCLUDE
#endif

#include "job.h"
#include "error.h"
#include <rtnhdr.h>
#include "io.h"
#include "iosp.h"
#include "eintr_wrappers.h"
#include "compiler.h"
#include "job_addr.h"
#include "util.h"
#include "gtm_facility.h"
#include "fileinfo.h"
#include "gtmio.h"

#define MAX_JOB_LEN		8192	/* Arbitrary length maximum used for checking job arguments and parameters */
#define MAX_PATH		 128	/* Maximum file path length */
#define MAX_LAB_LEN		  32		/* Maximum Label string length */
#define MAX_RTN_LEN		  32		/* Maximum Routine string length */
#define TEMP_BUFF_SIZE		1024
#define PARM_STRING_SIZE	   9
#define MAX_NUM_LEN		  10	/* Maximum length number will be when converted to string */
#define MAX_JOB_QUALS		  12	/* Maximum environ variables set for job qualifiers */
#define	MUMPS_EXE_STR		"/mumps"
#define	MUMPS_DIRECT_STR	"-direct"
#define GTMJ_FMT		"gtmj%03d="
#define PARM_STR		"gtmj000="

static 	int		joberr = joberr_gen;
static 	boolean_t	job_launched = FALSE;

GBLREF	bool			jobpid;		/* job's output files should have the pid appended to them. */
GBLREF	volatile boolean_t		ojtimeout;
GBLREF	boolean_t			job_try_again;
GBLREF	uint4			process_id;
GBLREF	boolean_t	gtm_pipe_child;
#ifndef SYS_ERRLIST_INCLUDE
/* currently either stdio.h or errno.h both of which are included above */
/*	needed by TIMEOUT_ERROR in jobsp.h */
#if !defined(__sun) && !defined(___MVS__)
GBLREF	int			sys_nerr;
#endif
#endif

#ifdef	__osf__
/* environ is from the O/S which only uses 64-bit pointers on OSF/1. */
#pragma	pointer_size (save)
#pragma pointer_size (long)
#endif

GBLREF char	**environ;

#ifdef	__osf__
#pragma pointer_size (restore)
#endif

error_def(ERR_JOBFAIL);
error_def(ERR_JOBPARTOOLONG);
error_def(ERR_LOGTOOLONG);
error_def(ERR_TEXT);

#define KILL_N_REAP(PROCESS_ID, SIGNAL, RET_VAL)					\
{											\
	if (-1 != (RET_VAL = kill(PROCESS_ID, SIGNAL)))					\
	{										\
		/* reap the just killed child, so there wont be any zombies */		\
		WAITPID(PROCESS_ID, &wait_status, 0, done_pid);				\
		assert(done_pid == PROCESS_ID);						\
	}										\
}

/* Note that this module uses _exit instead of exit to avoid running the inherited
   exit handlers which this mid-level process does not want to run */

/* if we get an error assembling the child environment,
	we don't want a rogue child running loose */

void job_term_handler(int sig);

static CONDITION_HANDLER(bad_child)
{
	PRN_ERROR;
	_exit(joberr);
}

#define FAILED_TO_LAUNCH 1

/* This is to close the window of racing condition where the timeout occurs and actually
 * by that time, the middle process had already successfully forked the job
 */

void job_term_handler(int sig){
	if (job_launched)
		_exit(0);
	else
		_exit(FAILED_TO_LAUNCH);
}

/*
 * ---------------------------------------------------------------------------------------------------------------------
 * The current process (P) FORKs a middle child process (M) that tests various job parameters. It then forks off the
 * actual Job (J) and exits, culminating the parent's (P) wait. The Job process (J) sets up its env and exexs mumps.
 *
 * Arguments
 * 	First argument is a pointer to the structure holding Job parameter values.
 * 	Second argument is the number of parameters being passed.
 * 	The third boolean argument indicates to the caller if the return from this function was due to an exit from the
 *		middle process or due to reasons other than that. It is set to true for the latter case of return.
 *	Fourth argument is the pair of file descriptors [opened by pipe] for the child process (M) to write PID
 *		of the jobbed off process (J).
 *
 * Return:
 *	Exit status of child (that the parent gets by WAITing) in case the return was after an exit from the middle process.
 *	errno in other cases with the third argument set to TRUE and returned by pointer.
 *	TIMEOUT_ERROR in case a timeout occured.
 *	Return zero indicates success.
 * ---------------------------------------------------------------------------------------------------------------------
 */

int ojstartchild (job_params_type *jparms, int argcnt, boolean_t *non_exit_return, int pipe_fds[])
{
	char			cbuff[TEMP_BUFF_SIZE], pbuff[TEMP_BUFF_SIZE];
	char			tbuff[MAX_JOB_LEN], tbuff2[MAX_JOB_LEN];
	char			*pgbldir_str;
	char			*transfer_addr;
	int4			index, environ_count, string_len, temp;
	int			wait_status, save_errno, kill_ret;
	int			rc;
	bool			status;
	pid_t			par_pid, child_pid, done_pid;
	job_parm		*jp;
	rhdtyp			*base_addr;
	struct sigaction	act, old_act;
	int			pipe_status, env_len;

#ifdef	__osf__
/* These must be O/S-compatible 64-bit pointers for OSF/1.  */
#pragma	pointer_size (save)
#pragma pointer_size (long)
#endif

	char		*c1, *c2, **c3;
	char		*argv[3];
	char		**env_ary, **env_ind;
	char		**new_env_cur, **new_env_top, **old_env_cur, **old_env_top, *env_end;

#ifdef	__osf__
#pragma pointer_size (restore)
#endif
	job_launched = FALSE;
	par_pid = process_id;
	if (-1 == (child_pid = fork()))	/* BYPASSOK: we die after creating a child, no FORK_CLEAN needed */
	{
		if (EAGAIN == errno || ENOMEM == errno)
			job_try_again = TRUE;
		*non_exit_return = TRUE;
		return (errno);
	}

	if (child_pid == 0)
	{
		/* This is a child process (middle process, M) */
		/* Test out various parameters and setup everything possible for the actual Job (J), so it(J) can
		 * start off without much hitch. If any error occurs during this, exit with appropriate status so
		 * the waiting parent can diagnose.
		 */

		/* set to TRUE so any child process associated with a pipe device will know it is not the parent in iorm_close() */
		gtm_pipe_child = TRUE;
		joberr = joberr_gen;
		ESTABLISH_RET(bad_child, 0);

		sigemptyset(&act.sa_mask);
		act.sa_flags = 0;
		act.sa_handler = job_term_handler;
		sigaction(SIGTERM, &act, &old_act);

		if (!jobpid)		/* if the Job pid need not be appended to the std-in/out/err file names */
		{
			joberr = joberr_io;
			/* attempt to open output files */
			/* this also redirects stdin/out/err, so any error messages by this process during
			 * the creation of the Job will get redirected */
			if (!(status = ojchildioset(jparms)))
			{
				if (job_try_again)
					joberr += joberr_tryagain;
				rts_error(VARLSTCNT(6) ERR_JOBFAIL, 0, ERR_TEXT, 2,
						LEN_AND_LIT("Failed to set STDIN/OUT/ERR for the job"));
			}
		} /* else, all the errors during the creation of Job go to the STDOUT/ERR of the parent process */

		joberr = joberr_cd;		/* pass current directory to child */
		if (jparms->directory.len != 0)
		{
			/* If directory is specified, change it */
			if (jparms->directory.len > TEMP_BUFF_SIZE)
				rts_error(VARLSTCNT(1) ERR_JOBPARTOOLONG);

			strncpy(pbuff, jparms->directory.addr, jparms->directory.len);
			*(pbuff + jparms->directory.len) = '\0';
			if (CHDIR(pbuff) != 0)
			{
				if (ETIMEDOUT == errno)		/* atleast on AIX */
					joberr += joberr_tryagain;
				rts_error(VARLSTCNT(7) ERR_JOBFAIL, 0, ERR_TEXT, 2,
						LEN_AND_LIT("Error changing directory for the Job."),errno);
			}
		}

		joberr = joberr_rtn;
		job_addr(&jparms->routine, &jparms->label, jparms->offset, (char **)&base_addr, &transfer_addr);

		joberr = joberr_syscall;
		if (-1 == setsid())
			rts_error(VARLSTCNT(7) ERR_JOBFAIL, 0, ERR_TEXT, 2,
					LEN_AND_LIT("Error setting session id for the Job."), errno);

		joberr = joberr_frk;
		/* clone self and exit */
		if (0 != (child_pid = fork()))	/* BYPASSOK: we exec immediately, no FORK_CLEAN needed */
		{
			/* This is still the middle process.  */
			if (0 > child_pid)
			{
				if (EAGAIN == errno || ENOMEM == errno)
					joberr += joberr_tryagain;
				rts_error(VARLSTCNT(7) ERR_JOBFAIL, 0, ERR_TEXT, 2,
						LEN_AND_LIT("Error forking the Job."), errno);
			} else
			{
				job_launched = TRUE;

				assert(SIZEOF(pid_t) == SIZEOF(child_pid));
				/* write child_pid into pipe to be read by parent process(P) for $ZJOB */
				DOWRITERC(pipe_fds[1], &child_pid, SIZEOF(child_pid), pipe_status);
				if (0 != pipe_status)
				{
					rts_error(VARLSTCNT(7) ERR_JOBFAIL, 0, ERR_TEXT, 2,
							LEN_AND_LIT("Error writing to pipe"), errno);
				}
				_exit(0);
			}
		}

		/* This is now the grandchild process (actual Job process) -- an orphan as soon as the exit(0) above occurs. */
		/* set up the environment and exec */

		sigaction(SIGTERM, &old_act, 0);		/* restore the SIGTERM handler */

		joberr = joberr_io;

		/* Run down any open flat files to reclaim their file descriptors */
		io_rundown(RUNDOWN_EXCEPT_STD);

		/* release the pipe opened by grand parent (P) */
		CLOSEFILE_RESET(pipe_fds[0], pipe_status);	/* resets "pipe_fds[0]" to FD_INVALID */
		CLOSEFILE_RESET(pipe_fds[1], pipe_status);	/* resets "pipe_fds[1]" to FD_INVALID */

		/* do common cleanup in child */
		ojchildioclean();

		/* Count the number of environment variables.  */
		for (environ_count = 0, c3 = environ, c2 = *c3;  c2;  c3++, c2 = *c3)
			environ_count++;
#ifdef	__osf__
/* Since we're creating an array of pointers for the O/S, make sure SIZEOF(char *) is correct for 64-bit pointers for OSF/1.  */
#pragma	pointer_size (save)
#pragma pointer_size (long)
#endif

		/* the environment array passed to the grandchild is constructed by prefixing the job related environment
		 * variables ahead of the current environment (pointed to by the "environ" variable)
		 *
		 * e.g. if the current environment has only two environment variables env1=one and env2=two,
		 * and the job command is as follows
		 * 	job ^x(1,2):(output="x.mjo":error="x.mje")
		 *
		 * then the environment array passed is as follows
		 *	 gtmj0=			// parent pid
		 *	 gtmgbldir=mumps.gld	// current global directory
		 *	 gtmjb=			// startup parameter to job command
		 *	 gtmj3=/dev/null	// input file parameter to job command
		 *	 gtmj4=x.mjo		// output file parameter to job command
		 *	 gtmj5=x.mje		// error file parameter to job command
		 *	 gtmj7=x		// routine name to job off
		 *	 gtmj8=			// label name to job off
		 *	 gtmj9=0		// offset to job off
		 *	 gtmja=			// base priority;
		 *	 gtmj000=1		// parameter 1 to routine ^x
		 *	 gtmj001=2		// parameter 2 to routine ^x
		 *	 gtmjcnt=2		// number of parameters to routine ^x
		 *	 env1=one		// old environment
		 *	 env2=two		// old environment
		 *
		 *	those parameters that are NULL or 0 are not passed.
		 *	each line above is an entry in the environment array.
		 */

		env_ind = env_ary = (char **)malloc((environ_count + MAX_JOB_QUALS + argcnt + 1)*SIZEOF(char *));

#ifdef	__osf__
#pragma pointer_size (restore)
#endif

		string_len = STRLEN("%s=%d") + STRLEN(CHILD_FLAG_ENV) + MAX_NUM_LEN - 4;
		if (string_len > MAX_JOB_LEN)
			rts_error(VARLSTCNT(1) ERR_JOBPARTOOLONG);
		c1 = (char *)malloc(string_len + 1);
#ifdef KEEP_zOS_EBCDIC
#pragma convlit(suspend)
#endif
		SPRINTF_ENV_NUM(c1, CHILD_FLAG_ENV, par_pid, env_ind);
#ifdef KEEP_zOS_EBCDIC
#pragma convlit(resume)
#endif
		/* Pass all information about the job via shell's environment.
		 * The grandchild will get those variables to obtain the info about the job.
		 */

		/* pass global directory to child */
		if (jparms->gbldir.len != 0)
		{
			if (jparms->gbldir.len > TEMP_BUFF_SIZE)
				rts_error(VARLSTCNT(1) ERR_JOBPARTOOLONG);
			strncpy(pbuff, jparms->gbldir.addr, jparms->gbldir.len);
			*(pbuff + jparms->gbldir.len) = '\0';
			string_len = STRLEN("%s=%s") + STRLEN(GBLDIR_ENV) + STRLEN(pbuff) - 4;
			if (string_len > TEMP_BUFF_SIZE)
				rts_error(VARLSTCNT(1) ERR_JOBPARTOOLONG);
			c1 = (char *)malloc(string_len + 1);
#ifdef KEEP_zOS_EBCDIC
#pragma convlit(suspend)
#endif
			SPRINTF_ENV_STR(c1, GBLDIR_ENV, pbuff, env_ind);
#ifdef KEEP_zOS_EBCDIC
#pragma convlit(resume)
#endif
		}
		/* pass startup program to child */
		if (jparms->startup.len != 0)
		{
			if (jparms->startup.len > TEMP_BUFF_SIZE)
				rts_error(VARLSTCNT(1) ERR_JOBPARTOOLONG);
			strncpy(pbuff, jparms->startup.addr, jparms->startup.len);
			*(pbuff + jparms->startup.len) = '\0';
			string_len = STRLEN("%s=%s") + STRLEN(STARTUP_ENV) + STRLEN(pbuff) - 4;
			if (string_len > TEMP_BUFF_SIZE)
				rts_error(VARLSTCNT(1) ERR_JOBPARTOOLONG);
			c1 = (char *)malloc(string_len + 1);
#ifdef KEEP_zOS_EBCDIC
#pragma convlit(suspend)
#endif
			SPRINTF_ENV_STR(c1, STARTUP_ENV, pbuff, env_ind);
#ifdef KEEP_zOS_EBCDIC
#pragma convlit(resume)
#endif
		}
		/* pass input file to child */
		if (jparms->input.len != 0)
		{
			if (jparms->input.len > TEMP_BUFF_SIZE)
				rts_error(VARLSTCNT(1) ERR_JOBPARTOOLONG);
			strncpy(pbuff, jparms->input.addr, jparms->input.len);
			*(pbuff + jparms->input.len) = '\0';
			string_len = STRLEN("%s=%s") + STRLEN(IN_FILE_ENV) + STRLEN(pbuff) - 4;
			if (string_len > TEMP_BUFF_SIZE)
				rts_error(VARLSTCNT(1) ERR_JOBPARTOOLONG);
			c1 = (char *)malloc(string_len + 1);
#ifdef KEEP_zOS_EBCDIC
#pragma convlit(suspend)
#endif
			SPRINTF_ENV_STR(c1, IN_FILE_ENV, pbuff, env_ind);
#ifdef KEEP_zOS_EBCDIC
#pragma convlit(resume)
#endif
		}

		/* pass output file to child */
		if (jparms->output.addr != 0)
		{
			if (jparms->output.len > TEMP_BUFF_SIZE)
				rts_error(VARLSTCNT(1) ERR_JOBPARTOOLONG);
			strncpy(pbuff, jparms->output.addr, jparms->output.len);
			*(pbuff + jparms->output.len) = '\0';
			if (jobpid)
				SPRINTF(&pbuff[jparms->output.len], ".%d", getpid());
			string_len = STRLEN("%s=%s") + STRLEN(OUT_FILE_ENV) + STRLEN(pbuff) - 4;
			if (string_len > TEMP_BUFF_SIZE)
				rts_error(VARLSTCNT(1) ERR_JOBPARTOOLONG);
			c1 = (char *)malloc(string_len + 1);
#ifdef KEEP_zOS_EBCDIC
#pragma convlit(suspend)
#endif
			SPRINTF_ENV_STR(c1, OUT_FILE_ENV, pbuff, env_ind);
#ifdef KEEP_zOS_EBCDIC
#pragma convlit(resume)
#endif
		}

		/* pass error file to child */
		if (jparms->error.len != 0)
		{
			if (jparms->error.len > TEMP_BUFF_SIZE)
				rts_error(VARLSTCNT(1) ERR_JOBPARTOOLONG);
			strncpy(pbuff, jparms->error.addr, jparms->error.len);
			*(pbuff + jparms->error.len) = '\0';
			if (jobpid)
				SPRINTF(&pbuff[jparms->error.len], ".%d", getpid());
			string_len = STRLEN("%s=%s") + STRLEN(ERR_FILE_ENV) + STRLEN(pbuff) - 4;
			if (string_len > TEMP_BUFF_SIZE)
				rts_error(VARLSTCNT(1) ERR_JOBPARTOOLONG);
			c1 = (char *)malloc(string_len + 1);
#ifdef KEEP_zOS_EBCDIC
#pragma convlit(suspend)
#endif
			SPRINTF_ENV_STR(c1, ERR_FILE_ENV, pbuff, env_ind);
#ifdef KEEP_zOS_EBCDIC
#pragma convlit(resume)
#endif
		}

		/* pass routine name to child */
		if (jparms->routine.len != 0)
		{
			if (jparms->routine.len > TEMP_BUFF_SIZE)
				rts_error(VARLSTCNT(1) ERR_JOBPARTOOLONG);
			strncpy(pbuff, jparms->routine.addr, jparms->routine.len);
			*(pbuff + jparms->routine.len) = '\0';
			string_len = STRLEN("%s=%s") + STRLEN(ROUTINE_ENV) + STRLEN(pbuff) - 4;
			if (string_len > TEMP_BUFF_SIZE)
				rts_error(VARLSTCNT(1) ERR_JOBPARTOOLONG);
			c1 = (char *)malloc(string_len + 1);
#ifdef KEEP_zOS_EBCDIC
#pragma convlit(suspend)
#endif
			SPRINTF_ENV_STR(c1, ROUTINE_ENV, pbuff, env_ind);
#ifdef KEEP_zOS_EBCDIC
#pragma convlit(resume)
#endif
		}

		/* pass label name to child */
		if (jparms->label.len > TEMP_BUFF_SIZE)
			rts_error(VARLSTCNT(1) ERR_JOBPARTOOLONG);
		strncpy(pbuff, jparms->label.addr, jparms->label.len);
		*(pbuff + jparms->label.len) = '\0';
		string_len = STRLEN("%s=%s") + STRLEN(LABEL_ENV) + STRLEN(pbuff) - 4;
		if (string_len > TEMP_BUFF_SIZE)
			rts_error(VARLSTCNT(1) ERR_JOBPARTOOLONG);
		c1 = (char *)malloc(string_len + 1);
#ifdef KEEP_zOS_EBCDIC
#pragma convlit(suspend)
#endif
		SPRINTF_ENV_STR(c1, LABEL_ENV, pbuff, env_ind);
#ifdef KEEP_zOS_EBCDIC
#pragma convlit(resume)
#endif

		/* pass the offset */
		string_len = STRLEN("%s=%ld") + STRLEN(OFFSET_ENV) + MAX_NUM_LEN - 5;
		if (string_len > TEMP_BUFF_SIZE)
			rts_error(VARLSTCNT(1) ERR_JOBPARTOOLONG);
		c1 = (char *)malloc(string_len + 1);
#ifdef KEEP_zOS_EBCDIC
#pragma convlit(suspend)
#endif
		SPRINTF_ENV_NUM(c1, OFFSET_ENV, jparms->offset, env_ind);
#ifdef KEEP_zOS_EBCDIC
#pragma convlit(resume)
#endif

		/* pass Priority to child */
		if (jparms->baspri != 0)
		{
			string_len = STRLEN("%s=%ld") + STRLEN(PRIORITY_ENV) + MAX_NUM_LEN - 5;
			if (string_len > TEMP_BUFF_SIZE)
				rts_error(VARLSTCNT(1) ERR_JOBPARTOOLONG);
			c1 = (char *)malloc(string_len + 1);
#ifdef KEEP_zOS_EBCDIC
#pragma convlit(suspend)
#endif
			SPRINTF_ENV_NUM(c1, PRIORITY_ENV, jparms->baspri, env_ind);
#ifdef KEEP_zOS_EBCDIC
#pragma convlit(resume)
#endif
		}

		for (index = 0, jp = jparms->parms;  jp ;  index++, jp = jp->next)
		{
			if (jp->parm->str.len > MAX_JOB_LEN - 2)
				rts_error(VARLSTCNT(1) ERR_JOBPARTOOLONG);
			if (0 != jp->parm->mvtype)
			{
				MV_FORCE_STR(jp->parm);
				string_len = STRLEN(PARM_STR) + jp->parm->str.len + 1;
				if (string_len > MAX_JOB_LEN)
					rts_error(VARLSTCNT(1) ERR_JOBPARTOOLONG);
				c1 = (char *)malloc(string_len);
#			ifdef KEEP_zOS_EBCDIC
				__getEstring1_a_copy(c1, STR_AND_LEN(PARM_STRING));
				__getEstring1_a_copy(c1 + strlen(PARM_STRING), jp->parm->str.addr, jp->parm->str.len);
#			else
				SPRINTF(c1, GTMJ_FMT, index);
				memcpy(c1 + strlen(PARM_STR), jp->parm->str.addr, jp->parm->str.len);
#			endif
				*(c1 + string_len - 1) = 0;
				*env_ind++ = c1;
			}
		}
		string_len = STRLEN("%s=%ld") + STRLEN(GTMJCNT_ENV) + MAX_NUM_LEN - 5;
		if (string_len > TEMP_BUFF_SIZE)
			rts_error(VARLSTCNT(1) ERR_JOBPARTOOLONG);
		c1 = (char *)malloc(string_len + 1);
#ifdef KEEP_zOS_EBCDIC
#pragma convlit(suspend)
#endif
		SPRINTF_ENV_NUM(c1, GTMJCNT_ENV, index, env_ind);
#ifdef KEEP_zOS_EBCDIC
#pragma convlit(resume)
#endif

#ifdef	__osf__
/* Make sure SIZEOF(char *) is correct.  */
#pragma pointer_size (save)
#pragma pointer_size (long)
#endif
		/* before appending the old environment into the environment array, do not add those
		 * lines that correspond to any of the above already initialized environment variables.
		 * this prevents indefinite growing of the environment array with nesting of job commands
		 * which otherwise would show up eventually as an "Arg list too long" error from EXECVE() below.
		 */
		new_env_top = env_ind;
		old_env_top = &environ[environ_count];
		DEBUG_ONLY(
			/* check that all new environment variables begin with the string "gtm".
			 * this assumption is used later in the for loop below.
			 */
			for (new_env_cur = env_ary; new_env_cur < new_env_top; new_env_cur++)
				assert(!STRNCMP_LIT(*new_env_cur, "gtm"));
		)
		for (old_env_cur = environ; old_env_cur < old_env_top; old_env_cur++)
		{
			env_end = strchr(*old_env_cur, '=');
			if ((NULL != env_end) && !STRNCMP_LIT(*old_env_cur, "gtm"))
			{
				env_len = (int)(env_end - *old_env_cur + 1);	/* include the '=' too */
				assert(env_len <= strlen(*old_env_cur));
				for (new_env_cur = env_ary; new_env_cur < new_env_top; new_env_cur++)
				{
					if (0 == strncmp(*new_env_cur, *old_env_cur, env_len))
						break;
				}
				if (new_env_cur < new_env_top)
					continue;
			}
			*env_ind++ = *old_env_cur;
		}
		*env_ind = NULL;	/* null terminator required by execve() */

#ifdef	__osf__
#pragma pointer_size (restore)
#endif

		c1 = GETENV("gtm_dist");
		string_len = STRLEN(c1);
		if ((string_len + SIZEOF(MUMPS_EXE_STR)) < SIZEOF(tbuff))
		{
			memcpy(tbuff, c1, string_len);
			c2 = &tbuff[string_len];
			strcpy(c2, MUMPS_EXE_STR);
		} else
			rts_error(VARLSTCNT(5) ERR_LOGTOOLONG, 3, string_len, c1, SIZEOF(tbuff) - SIZEOF(MUMPS_EXE_STR));

#		ifdef KEEP_zOS_EBCDIC_	/* use real strcpy to preserve env in native code set */
#		pragma convlit(suspend)
#		endif
		strcpy(cbuff, MUMPS_DIRECT_STR);
#		ifdef KEEP_zOS_EBCDIC_
#		pragma convlit(resume)
#		endif

#ifdef KEEP_zOS_EBCDIC
		__getEstring1_a_copy(tbuff2, STR_AND_LEN(tbuff));
		argv[0] = tbuff2;
#else
		argv[0] = tbuff;
#endif
		argv[1] = cbuff;
		argv[2] = (char *)0;

		EXECVE(tbuff, argv, env_ary);
		/* if we got here, error starting the Job */
		rts_error(VARLSTCNT(7) ERR_JOBFAIL, 0, ERR_TEXT, 2, LEN_AND_LIT("Exec error in Job"), errno);
		REVERT;
	} else
	{
		/* Parent, wait for the Middle process */
		/* Wait ends under the following conditions
		 *	The middle process successfully forks off the actual Job or exits with error before doing that.
		 *	Timeout for the Job command.
		 * For the first case, return the wait status as got from wait() syscall.
		 * For the second condition, set non_exit_return to TRUE and return the relevant errno.
		 */

		do
		{	/* note : macro, WAITPID would not be suitable here because we want to catch our timeout
			 * waitpid expects an integer wait_status even for _BSD cases, but WIF* macros expect
			 * a union wait argument (on AIX)
			 */
			done_pid = waitpid(child_pid, &wait_status, 0);	/* BYPASSOK */
		} while(!ojtimeout && 0 > done_pid && EINTR == errno);
		if (done_pid == child_pid)
			return (wait_status);
		else if (0 > done_pid && EINTR == errno && TRUE == ojtimeout)
		{
			/* Kill the middle process with SIGTERM and check the exit status from
			 * the handler to see if the Middle process had actually successfully forked the Job */
			KILL_N_REAP(child_pid, SIGTERM, kill_ret);
			if (-1 == kill_ret && ESRCH == errno)		/* if the middle process finished by now */
			{
				WAITPID(child_pid, &wait_status, 0, done_pid);
				if (done_pid == child_pid)
					return (wait_status);
			} else if (-1 != kill_ret && done_pid == child_pid && 0 == wait_status)
				return 0;		/* timer popped in the window of child fork and middle process exit */
			*non_exit_return = TRUE;
			return TIMEOUT_ERROR;	/* return special value so as to eliminate the window where the timer
						 * might pop after this routine returns to the callee and before the callee
						 * analyses the return status (ojtimeout may not be a reliable indicator) */
		} else if (0 > done_pid)
		{
			*non_exit_return = TRUE;
			save_errno = errno;
			KILL_N_REAP(child_pid, SIGKILL, kill_ret);
			return (save_errno);
		} else if (0 == done_pid)		/* this case should never arise */
		{
			assert(FALSE);
			*non_exit_return = TRUE;
			KILL_N_REAP(child_pid, SIGKILL, kill_ret);
			return (EINVAL);		/* return some error condition */
		}
	}

	return (EINVAL); /* This should never get executed, added to make compiler happy */
}
