/*******************************************************************************
 * Copyright (c) 2005, 2009 Intel Corporation and others.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *    Karla Callaghan, Intel - Initial API and Implementation
 *    Spundun Bhatt (spundun@gmail.com), with the support and encouragement of the University of Southern California Information Sciences Institute Distributed Scalable Systems Division.
 *
 * $Id: TPTPProcessController.cpp,v 1.90 2009/09/24 21:02:06 jwest Exp $
 *******************************************************************************/

#include "TPTPProcessController.h"
#include "tptp/TPTPErrorCode.h"
#include "tptp/TPTPCommon.h"
#include "tptp/hashtable.h"
#include "tptp/TPTPSupportUtils.h"
#include "tptp/TransportSupport.h"
#include "tptp/agents/AgentLog.h"
#include "tptp/TPTPConfigBinding.h"

#ifndef _WIN32
	#include <string.h>
	#include <stdlib.h>  //atoi, atol
	#include <signal.h>
	#include <sys/wait.h>
	#include <locale.h>

	#if !defined(_SOLARIS) && !defined(_AIX) && !defined(MVS)
		#include <sys/ptrace.h>
	#else
		#include <dirent.h>
		#include <sys/types.h>
	#endif

	#if defined(__linux__) || defined(MVS)
		// Included for the getpriority(...) call.
		#include <sys/time.h>
		#include <sys/resource.h>
		#include <errno.h>
	#endif

#endif

#ifdef __APPLE__
	#define PTRACE_ATTACH PT_ATTACH
	#define PTRACE_DETACH PT_DETACH
	#define PTRACE_CONT PT_CONTINUE
#endif /*__APPLE__*/

#define APPLICATION_TAG			"<Application"
#define APPLICATION_END_TAG		"</Application>"
#define APPLICATION_END_TAG2	"/>"

Semaphore_t startProcessCmdSem;
Semaphore_t consoleConnectResponseSem;
Semaphore_t applAliasesResponseSem;

//This type provides the hash table lookup for uuid's
typedef struct
{
	PID pid;
	tptp_string* uuid;
} pidEntry_t;

typedef struct
{
	ProcessController* agent;
	PID pid;
} activeProcessInfo_t;

static THREAD_USER_FUNC_RET_TYPE startProcesses(void* pProcessController);
static THREAD_USER_FUNC_RET_TYPE monitorProcessEvents(void* pProcController);
static THREAD_USER_FUNC_RET_TYPE checkForProcessDeath(void* procInfo);

#define BUFFER_LENGTH 1024

///////////////////////////////////////////////////
//
// ProcessController::ProcessController(char* name, char* type)
//
///////////////////////////////////////////////////

ProcessController::ProcessController(char* name) : BaseAgentImpl(name)
{
	tptp_initializeLock(&runningProcessListLock);
	tptp_list_init(&runningProcessList);
	tptp_list_setNodeDestructor(&runningProcessList, destroyProcessNode);
	tptp_list_setNodeCopier(&runningProcessList, copyProcessNode);

	tptp_list_init(&eventListenerList);
	tptp_list_setNodeDestructor(&eventListenerList, destroyListenerNode);
	tptp_list_setNodeCopier(&eventListenerList, copyListenerNode);

	tptp_initializeLock(&startProcessListLock);
	tptp_list_init(&startProcessList);
	tptp_list_setNodeDestructor(&startProcessList, destroyProcessNode);
	tptp_list_setNodeCopier(&startProcessList, copyProcessNode);

	appAliasesList = NULL;
	launchAliasesOnly = false;

	consoleConnected = false;
	processConnId = 0;
	tptp_initializeLock(&consoleConnectedLock);
	//Create the pid/uuid hashtable, used in getProcessUUID.
	pidUUIDHashTable = tableCreate();
#ifdef _WIN32
	// Create synch event used by handleProcessEvents() which runs on the PC's main thread.
	// Used to discover when the running process list has changed - either a new process
	// gets added because of a startProcess request or a process gets deleted because it
	// terminates.
	// Any method that adds/removes a process in the runningProcess list
	// should set the ProcessListChanged event.
	// The method which gets the list of processes for handleProcessEvents() to
	// use, should do the reset of this event.
	// NOTE: Only used on Windows implementation to notify handleProcessEvent of a change
	// in the event list. Linux implementation waits on all child processes rather
	// than on a specific list.
	hProcListChangedEvent = CreateEvent(NULL /*not inheritable*/, TRUE /*Manual reset*/, FALSE /*initial state*/,
										NULL /*"TPTPProcessListChanged"*/);
#endif
	return;
}

///////////////////////////////////////////////////
//
// ProcessController::~ProcessController()
//
///////////////////////////////////////////////////

ProcessController::~ProcessController()
{
#ifdef _WIN32
	closeRunningProcessHandles();
#endif
	tptp_list_clear(&runningProcessList);
	tptp_deleteLock(&runningProcessListLock);

	tptp_list_clear(&eventListenerList);

	tptp_list_clear(&startProcessList);
	tptp_deleteLock(&startProcessListLock);

	tptp_list_clear(appAliasesList);
	tptp_free(appAliasesList);

	tptp_deleteLock(&consoleConnectedLock);

	tableDelete(pidUUIDHashTable);
	return;
}

int ProcessController::preRegisterInitialization()
{
	char getAppCmd[1024];

	// Load list of application aliases which are located in the AC's config file.
	// When a startProcess request comes in, we'll check to see if it is an alias
	// and adjust the launch parameters.
	sprintf( getAppCmd, "<Cmd src=\"%d\" dest=\"%d\" ctxt=\"%d\"> "
					    "<getApplicationAliases iid=\"%s\"> "
					    "</getApplicationAliases></Cmd>",
					    getAgentID(), AGENT_MANAGER, getNextContext(),
					    AGENT_MANAGER_IID );

	sendCommand(getAppCmd);
	int rc = tptp_waitSemaphore(&applAliasesResponseSem);
	if (rc)
	{
		TPTP_LOG_ERROR_MSG(this, "PC initialize: Error waiting for applicationAliases response");
		return rc;
	}

	return 0;
}

///////////////////////////////////////////////////
//
// int ProcessController::processFlagCommand(MsgBlock *pMsgBlk)
//
// Handles any commands from the AC's Transport Layer (TL) to
// the Process Controller.
//
// The TL uses the flag field of a msg rather than using XML
// formatted strings. The XML msgs are handled by processCommand().
//
///////////////////////////////////////////////////

int ProcessController::processFlagCommand(MsgBlock *pMsgBlk)
{
	unsigned int flags;

	//Handle common agent flag commands
	BaseAgentImpl::processFlagCommand(pMsgBlk);

	flags = pMsgBlk->getFlags();

	if ((flags & CONSOLE_CONNECT_COMPLETE) == CONSOLE_CONNECT_COMPLETE)
	{
		unsigned char *pMsgData = NULL ;
		pMsgData = pMsgBlk->getMsg();

		// Set flag which indicates the previous console connect request
		// is successful and store the accompanying connection ID.
		tptp_getWriteLock(&consoleConnectedLock);
		consoleConnected = true;
		unsigned int tmp;
		readUINTFromBuffer(pMsgData, &tmp);
		processConnId = (int)tmp;
		tptp_releaseWriteLock(&consoleConnectedLock);

		//Notify startProcesses thread that a response has arrived.
		tptp_postSemaphore(&consoleConnectResponseSem); //TODO:check return code
		TPTP_LOG_DEBUG_MSG(this, "Process Controller: Received response CONSOLE_CONNECT_COMPLETE");
	}
	else if ((flags & CONSOLE_CONNECT_FAILED) == CONSOLE_CONNECT_FAILED)
	{
		// Set flag which indicates the previous console connect request
		// failed.
		tptp_getWriteLock(&consoleConnectedLock);
		consoleConnected = false;
		tptp_releaseWriteLock(&consoleConnectedLock);

		//Notify startProcesses thread that a response has arrived.
		tptp_postSemaphore(&consoleConnectResponseSem); //TODO:check return code
		TPTP_LOG_DEBUG_MSG(this, "Process Controller: Received response CONSOLE_CONNECT_FAILED");
	}
	return 0;
}

///////////////////////////////////////////////////
//
// int ProcessController::sendCONNECT_CONSOLECommand(char* pUniqueId, unsigned int *procConnId)
//
// Sends a CONSOLE_CONNECT request to the TL and then waits for a completion response.
// If a successful completion, returns the new connection Id for this process's console
// I/O.   (Note: Need to bind the connection Id from the client side which came in with
// the startProcess request to this connection Id in order for the data path to be completed.)
// If succeeds, returns 0.
//
///////////////////////////////////////////////////

int ProcessController::sendCONNECT_CONSOLECommand(char* pUniqueId, int *procConnId)
{
	int rc;
	tptp_console_connect_cmd_t cmd ;
	int cmdLength ;

	*procConnId = 0; //init to 0 in case of failure

	cmd.replyConnId = BaseAgentImpl::getAgentID(); //Connection Id for the response
	cmd.uuidLength  = strlen(pUniqueId) ;
	strcpy(cmd.uuid, pUniqueId) ;

	cmdLength = sizeof(unsigned int) + sizeof(unsigned int) + sizeof(int) + cmd.uuidLength + 1 ;

	rc = sendCommand((char *)&cmd, cmdLength, CONNECT_CONSOLE);
	if (rc)
	{
		TPTP_LOG_ERROR_MSG(this, "PC: Error sending CONNECT_CONSOLE request to TL");
		return rc;
	}

	// Wait for a response to CONNECT_CONSOLE to arrive in the msg handler
	rc = tptp_waitSemaphore(&consoleConnectResponseSem);
	if (rc)
	{
		TPTP_LOG_ERROR_MSG(this, "PC: Error waiting for a CONNECT_CONSOLE response");
		return rc;
	}

	// Check whether we got the successful or failed reply from the TL
	tptp_getReadLock(&consoleConnectedLock);
	bool connectSuccess = consoleConnected;
	if (connectSuccess) *procConnId = processConnId;
	tptp_releaseReadLock(&consoleConnectedLock);

	if (!connectSuccess)
	{
		TPTP_LOG_ERROR_MSG(this, "PC: CONNECT_CONSOLE request FAILED");
		return TPTP_PC_CONSOLE_CONNECT_FAILED;
	}

	TPTP_LOG_DEBUG_MSG(this, "PC: CONNECT_CONSOLE request SUCCESSFUL");

	// Clear the per-process member variables that are used by the PC to
	// get a value from the msg handling thread to this start processes thread.
	tptp_getWriteLock(&consoleConnectedLock);
	consoleConnected = false;
	processConnId = 0;
	tptp_releaseWriteLock(&consoleConnectedLock);

	return 0;
}

///////////////////////////////////////////////////
//
// int ProcessController::sendCONSOLE_PROCESS_LAUNCHEDCommand(unsigned int procConnId, PID pid)
//
// Sends a CONSOLE_PROCESS_LAUNCHED message to the TL to provide the
// process ID for a previous CONSOLE_CONNECT message.  The UniqueID
// must match that of the previous CONSOLE_CONNECT.
// No reply is expected from the TL.
//
// If succeeds, returns 0.
//
///////////////////////////////////////////////////

int ProcessController::sendCONSOLE_PROCESS_LAUNCHEDCommand(int procConnId, PID pid)
{
	tptp_console_launch_cmd_t cmd;
	int cmdLength;

	cmd.processConnId = procConnId;
	cmd.processId    = pid ;

	cmdLength = sizeof(cmd);

	TPTP_LOG_DEBUG_MSG(this, "PC: Sending CONSOLE_PROCESS_LAUNCHED cmd to TL.");

	return (sendCommand((char *) &cmd, cmdLength, CONSOLE_PROCESS_LAUNCHED));
}


///////////////////////////////////////////////////
//
// int ProcessController::validateExecutable(char* appName, unsigned long& errCode)
//
// Check if appName exists and is a valid executable file.
// Returns 1 if it is, otherwise returns 0 and sets errCode.
//
///////////////////////////////////////////////////

int ProcessController::validateExecutable(const char* appName, unsigned long& errCode)
{
	return(checkExecutable(appName, &errCode));
}

///////////////////////////////////////////////////
//
// int ProcessController::validateDirectory(char* dir, unsigned long& errCode)
//
// Check if the directory path provided has read access.
// Returns 1 if it does, otherwise 0 is returned and errCode is set.
//
///////////////////////////////////////////////////
//
int ProcessController::validateDirectory(const char* dir, unsigned long& errCode)
{
	return(checkDirectoryReadAccess(dir, &errCode));
}

///////////////////////////////////////////////////
//
// void ProcessController::sendEventNotifications(PID pid, unsigned long exitStatus)
//
// Sends Event messages to: process owner, process monitors and anyone registered
// as an event listener.  An event listener will be notified of events occuring
// for any process.
//
// Currently, the ProcessController only has one event, which is part
// of the processController interface.  So only the processExitedEvent
// is sent.
//
// TODO: Add an event-type parameter so that a "process started" event can be sent
//
///////////////////////////////////////////////////

void ProcessController::sendEventNotifications(const PID pid,
											   const unsigned long exitStatus)
{
	tptp_node_t*     node;
	tptp_listener_t* listener;
	char eventCmd[1000];
	tptp_process_t* proc; //ptr to process, if found
	char cmdFormat[] =
	  "<Cmd src=\"%d\" dest=\"%d\" ctxt=\"%d\"><processExitedEvent iid=\"%s\"><processID>%lu</processID><exitCode>%lu</exitCode></processExitedEvent></Cmd>";



	if (0 == findRunningProcessByPid(pid, &proc)) // Found the process in our list.
	{

		// Send event to owner of the process, unless they requested not to be
		// notified of events.

		if (proc->noNotices) {
			TPTP_LOG_DEBUG_MSG1(this, "PC: Not sending event to Owner of pid %lu (owner declined notices)", (unsigned long)pid);
		}
		else if (proc->ownerID > 0)
		{
			TPTP_LOG_DEBUG_MSG1(this, "PC: Sending event to Owner of pid %lu", (unsigned long)pid);
			sprintf( eventCmd, cmdFormat,
								BaseAgentImpl::getAgentID(),
								proc->ownerID,
								proc->ownerContext,
								PROCESS_CONTROLLER_IID,
								(unsigned long)pid,
								exitStatus);

			sendCommand(eventCmd);
		}
		else {
			TPTP_LOG_DEBUG_MSG1(this, "PC: Not sending event to Owner of pid %lu (no owner)", (unsigned long)pid);
		}

		// Send event to monitors of this particular process
		TPTP_LOG_DEBUG_MSG2(this, "PC: Sending event to %ld Monitors of pid %lu", proc->monitors->count, (unsigned long)pid);
		if (proc->monitors->count > 0)
		{
			for (node = proc->monitors->head; node != 0; node = node->next )
			{
				listener = (tptp_listener_t*)node->data;
				sprintf( eventCmd, cmdFormat,
									BaseAgentImpl::getAgentID(),
									listener->destID,
									listener->listenerID,
									PROCESS_CONTROLLER_IID,
									(unsigned long)pid,
									exitStatus);

				sendCommand(eventCmd);
			}
		}
	}


	// Send events to general listener list.  These listeners get events
	// about every process.
	TPTP_LOG_DEBUG_MSG2(this, "PC: Sending event to %ld Listeners for pid %lu", eventListenerList.count, (unsigned long)pid);

	if (eventListenerList.count == 0)
	{
		return;
	}

	for (node = eventListenerList.head; node != 0; node = node->next )
	{
		listener = (tptp_listener_t*)node->data;

		// Process Controller only has a single interface ID, used for both
		// commands and events.  So we only match on it.
		// If a distinct set of events were to be defined, we would
		// expand the case below to check for each.
		// If something wants to get events from multiple interfaces, they
		// must register for each individually.
		if ( isEqualString(listener->eventInterfaceID, PROCESS_CONTROLLER_IID) )
		{
			// Create and send an event
			sprintf( eventCmd, cmdFormat,
								 BaseAgentImpl::getAgentID(),
								 listener->destID,
								 listener->listenerID,
								 PROCESS_CONTROLLER_IID,
								 (unsigned long)pid,
								 exitStatus);

			sendCommand(eventCmd);
		}
	}
	return;
}

///////////////////////////////////////////////////
//
// int ProcessController::addEventListener(char* eventsIID, int listenerID, int replyDest, int replyContext)
//
// eventsIID - the interface ID string of the events they want to hear about
// listenerIID - the value to use for the "context" of the event when sent
// replyDest - where to send the event notification
//
// Returns:
// 0 if success
// non-zero if failure
//
// Only returns an error if unable to send either an error or successful response command.
//
///////////////////////////////////////////////////

int ProcessController::addEventListener(const char* eventsIID,
										const int listenerID,
										const int replyDest,
										const int replyContext)
{
	tptp_listener_t* listener;
	char replyCmd[1024];

	// Create a listener structure
	listener = initListenerT(replyDest, listenerID, eventsIID);
	if ( listener == NULL )
	{
		// Report internal memory error.
		return (sendErrorCommand(replyDest, replyContext,
			    TPTP_PC_UNABLE_TO_ADD_EVENT_LSTNR, 0));
	}

	tptp_list_add(&eventListenerList, (void*)listener); //TODO: Check for error return

	sprintf( replyCmd, "<Cmd src=\"%d\" dest=\"%d\" ctxt=\"%d\"> "
				   "<listenerAccepted iid=\"%s\">"
				   "</listenerAccepted></Cmd>",
				   BaseAgentImpl::getAgentID(), replyDest, replyContext,
				   EVENT_PROVIDER_IID);

	return (sendCommand(replyCmd));
}

///////////////////////////////////////////////////
//
// ProcessController::findRunningProcessByPid( const PID  pid, tptp_process_t** proc )
//
// Lookup the pid in the list of processes started by this ProcessController.
// If found, fill-in the process info and return 0.
// Else, return -1.
//
///////////////////////////////////////////////////

int ProcessController::findRunningProcessByPid(const PID pid,
											   tptp_process_t** proc)
{
	tptp_node_t*    node;
	tptp_process_t* tmpProc;

	tptp_getReadLock(&runningProcessListLock);
	for (node = runningProcessList.head; node != 0; node = node->next)
	{
		tmpProc = (tptp_process_t*)node->data;
		if (!tmpProc) continue;  //empty data field, skip this node
		if ( tmpProc->pid == pid  )
		{
			*proc = cloneProcessT(tmpProc);
			tptp_releaseReadLock(&runningProcessListLock);
			return 0;
		}
	}
	tptp_releaseReadLock(&runningProcessListLock);

	return -1;
}

int ProcessController::addProcessMonitor(const PID pid, tptp_listener_t* monitor) {
	tptp_node_t*    node;
	tptp_process_t* proc;
	int r = -1;

	tptp_getReadLock(&runningProcessListLock);
	for (node = runningProcessList.head; node != 0; node = node->next) {
		proc = (tptp_process_t*)node->data;
		if (!proc) continue;  //empty data field, skip this node

		if (proc->pid == pid) {
			tptp_list_add(proc->monitors, monitor);
			r = 0;
			break;
		}
	}
	tptp_releaseReadLock(&runningProcessListLock);

	return r;
}

///////////////////////////////////////////////////
//
// void ProcessController::addRunningProcess(tptp_process_t* proc)
//
// Add a process to the list of executing processes,
// making sure another thread isn't reading that list at the same time.
// Then set the event which indicates the list changed.
//
///////////////////////////////////////////////////

void ProcessController::addRunningProcess(tptp_process_t* proc)
{
	// Must set list-changed event prior to releasing the write lock to ensure that
	// the process list is not read (which includes a reset of the list-changed event)
	// in between the list_add and the setting of the event.
	tptp_getWriteLock(&runningProcessListLock);
	tptp_list_add( &runningProcessList, (void*)proc ); //TODO: Error ret check?
#ifdef _WIN32
	SetEvent(hProcListChangedEvent);
#endif
	tptp_releaseWriteLock(&runningProcessListLock);

	return;
}

///////////////////////////////////////////////////
//
// void ProcessController::removeRunningProcess(PID pid)
//
// Delete a process from the list of executing processes, making sure
// another thread is not reading that list at the same time.
// Then set list-changed event.
//
///////////////////////////////////////////////////

void ProcessController::removeRunningProcess(PID pid)
{
	tptp_node_t*    node;
	tptp_process_t* proc=NULL;
	tptp_process_t* tmpProc;

	//Find the process in the runningProcessList
	tptp_getWriteLock(&runningProcessListLock);
	for (node = runningProcessList.head; node != 0; node = node->next )
	{
		tmpProc = (tptp_process_t*)node->data;
		if (!tmpProc) continue;  //empty data field, skip this node
		if (tmpProc->pid == pid)
		{
			proc = tmpProc;
			break;
		}
	}

	if (!proc)	// Process not found
	{
		tptp_releaseWriteLock(&runningProcessListLock);
		return;
	}

#ifdef _WIN32
	// Close the process handle
	if(proc->hProcess && proc->hProcess != INVALID_HANDLE_VALUE)
	{
		CLOSE_TPTP_HANDLE(proc->hProcess);
		proc->hProcess = 0;
	}
#endif

	// Remove the process from the list of executing processes then
	// set the event which indicates the list changed.
	// Must do this prior to releasing the write lock to ensure that
	// the process list is not read (which includes a reset of this event)
	// in between the changing of the list and the setting of the event.
	//
	tptp_list_remove(&runningProcessList, proc); //TODO: Error ret check?
#ifdef _WIN32
	SetEvent(hProcListChangedEvent);
#endif
	tptp_releaseWriteLock(&runningProcessListLock);

	return;
}


///////////////////////////////////////////////////
//
// int ProcessController::numRunningProcesses( void )
//
// Returns the number of processes in the runningProcessList.
//
///////////////////////////////////////////////////

int ProcessController::numRunningProcesses( void )
{
	long count=0;

	tptp_getReadLock(&runningProcessListLock);
	count = runningProcessList.count;
	tptp_releaseReadLock(&runningProcessListLock);

	return count;
}

///////////////////////////////////////////////////
//
// static THREAD_USER_FUNC_RET_TYPE monitorProcessEvents(void* pProcController)
//
// Thread entry point for handling system events for processes the
// Process Controller has launched or is monitoring.
//
///////////////////////////////////////////////////

static THREAD_USER_FUNC_RET_TYPE monitorProcessEvents(void* pProcController)
{
	ProcessController *pcInstance;

	if (pProcController)
	{
		pcInstance = (ProcessController*)pProcController;
	}
	else
	{
		// TODO: what should return value be on error?
		// Can't log this message to the AC since we don't have a valid instance ptr.
		//TPTP_LOG_ERROR_MSG("monitorProcessEvents Thread: Error - Invalid ProcessController handle");
		return 0;
	}

	pcInstance->handleProcessEvents();
	return 0;
}


///////////////////////////////////////////////////
//
// void ProcessController::handleProcessEvents(void)
//
// Calls the platform-specific version of handleProcessEvents.
//
///////////////////////////////////////////////////

void ProcessController::handleProcessEvents(void)
{
#ifdef _WIN32
	handleProcessEvents_win32();
#else
	/* AK -- I've changed Linux to use the child sig handler */
//	handleProcessEvents_linux();
#endif
}

///////////////////////////////////////////////////
//
// void ProcessController::addStartProcessInfo(tptp_process_t* proc)
//
// Add process launching info to the startProcessList.
//
///////////////////////////////////////////////////

void ProcessController::addStartProcessInfo(tptp_process_t* proc)
{
	// Add the process to the list of processes waiting to be started,
	// making sure another thread isn't accessing that list at the same time.
	//
	tptp_getWriteLock(&startProcessListLock);
	tptp_list_add( &startProcessList, (void*)proc ); //TODO: Error ret check?
	tptp_releaseWriteLock(&startProcessListLock);

	return;
}

///////////////////////////////////////////////////
//
// int ProcessController::getStartProcessInfo(tptp_process_t** proc)
//
// Returns a copy of the first process structure found on the startProcessList.
// then deletes that process from the startProcessList.
//
///////////////////////////////////////////////////

int ProcessController::getStartProcessInfo(tptp_process_t** proc)
{
	tptp_node_t* node;
	tptp_process_t* firstProc;

	if (!proc) return TPTP_UNEXPECTED_NULL_ARG;

	tptp_getWriteLock(&startProcessListLock);

	// Handle startProcess requests in the order they came in, so
	// simply take the first process off the list.
	node = startProcessList.head;
	if (!node || !node->data)
	{
		//Error. List is empty or no data
		TPTP_LOG_ERROR_MSG(this, "PC getStartProcessInfo: Error - Empty startProcessList is unexpected");
		tptp_releaseWriteLock(&startProcessListLock);
		return TPTP_PC_START_PROC_LIST_EMPTY;
	}

	firstProc = (tptp_process_t*)node->data;
	*proc = cloneProcessT(firstProc);

	//Delete this process from the list, also destroys the contents of the
	//process node using a function specified by tptp_list_setNodeDestructor().
	//Hence we make a copy of it first.
	tptp_list_remove(&startProcessList, node->data); //TODO: Error ret check?

	tptp_releaseWriteLock(&startProcessListLock);
	return 0;
}


///////////////////////////////////////////////////
//
// ProcessController::doStopProcess(const PID pid, int requestorID, int requestorCtxt)
//
//
// Terminate the process identified by the pid argument.
// If the requestor is not the process owner, they will be added as a monitor of this
// process so that they will receive an event when the process stops.
//
///////////////////////////////////////////////////
int ProcessController::doStopProcess( const PID pid,
									  const int requestorID,
									  const int requestorCtxt )
{
	tptp_process_t* proc; //ptr to process, if found

	// See if we know anything about the process. It will be in
	// the runningProcessList if we launched it or are monitoring it.

	if (0 != findRunningProcessByPid(pid, &proc)) //Did not find pid in the runningProcessList
	{
		pidEntry_t *pidEntry;

		pidEntry = (pidEntry_t *) tableGet(pidUUIDHashTable, pid);
		if (pidEntry == NULL) {
		// Cannot stop a process we did not start.
			TPTP_LOG_ERROR_MSG1(this, "PC doStopProcess: PC did not start process %d, so cannot stop it.",pid);
			return TPTP_PC_PROC_NOT_FOUND;
		} else
		{
			//The PC did not start this process we know it is an agent
			//Compatibility with the RAC needs the PC to kill this process for us.
			tptp_process_t unknownProc;
			tptp_int32 status;
			char eventCmd[4000];

			unknownProc.pid = pid;
			//Terminate the process
			status= terminateProc(&unknownProc);
			TPTP_LOG_DEBUG_MSG1(this, "PC: Sending event to Owner of pid %lu", (unsigned long)pid);

			//Send a message to the client that the process has exited.
			sprintf( eventCmd, "<Cmd src=\"%d\" dest=\"%d\" ctxt=\"%d\"> "
								"<processExitedEvent iid=\"%s\">"
								"<processID>%lu</processID>"
								"<exitCode>%d</exitCode>"
								"</processExitedEvent></Cmd>",
								BaseAgentImpl::getAgentID(),
								requestorID,
								requestorCtxt,
								PROCESS_CONTROLLER_IID,
								(unsigned long)pid,
								status);

			sendCommand(eventCmd);
			return status;
		}
	}

	// If we are only monitoring the process, we won't have sufficient
	// permissions to kill it.

	/*
	 * Bug 192235 : need to disable the following block of code in order to terminate a process not
	 * launched by the AC. Need to revisit the design to see if this fix is appropriate.
	 */
#if 0
	if (proc->ownerID <= 0)
	{
		// Cannot stop a process we are simply monitoring.
		TPTP_LOG_ERROR_MSG1(this, "PC doStopProcess: PC is only monitoring process %d, so cannot stop it.", pid);
		return TPTP_PC_PROC_NO_STOP;
	}
#endif
	// The requestor of a stop should be notified of the event that results when the
	// process terminates. This event is generated in the thread monitoring processes,
	// so in order to cause the notification, we simply add the requestor to the
	// monitors list for this process.
	//
	// Note: Currently, adding a process to the monitor list does not check for duplicates
	// so if the stop requestor previously did an explict monitor request for this process
	// this implicit monitor which is done for a stop request will result in multiple
	// event notices being sent.
	//
	// If the requestor of the stop is also the process's owner, do not add
	// this ID as a monitor becaues the owner will also get an event, unless they specified
	// noNotices when starting the process (in which case they don't want the event).
	if (requestorID != proc->ownerID)
	{
		// Create a new monitor (listener) with the requestor's info.
		tptp_listener_t* newMonitor = initListenerT(requestorID, requestorCtxt, NULL);
		if (!newMonitor)
		{
			TPTP_LOG_ERROR_MSG(this, "PC doStopProcess: Failed to create a new monitor");
			return TPTP_SYS_NO_MEM;
		}

		// Add to proc's monitor list so that the stop event caused by the terminateProc()
		// will be sent to the requestor.
		tptp_list_add(proc->monitors, newMonitor);
	}

	// Kill the process.
	// Do not remove the process from the runningProcessList at this
	// point. The handleProcessEvent() method will note that this process
	// was stopped and remove it.

	return (terminateProc(proc));
}

///////////////////////////////////////////////////
//
// int ProcessController::doMonitorProcessState(PID pid, int requestorID, int requestorCtxt)
//
//
//
///////////////////////////////////////////////////
int ProcessController::doMonitorProcessState(const PID pid,
											 const int requestorID,
											 const int requestorCtxt)
{
	tptp_process_t* proc = NULL; //ptr to a process, if found
	tptp_listener_t* newMonitor = NULL;
	int rc = 0;
	activeProcessInfo_t *procInfo;

	// Create/initialize new monitor (listener) with the requestor's info.
	newMonitor = initListenerT(requestorID, requestorCtxt, NULL);
	if (!newMonitor)
	{
		TPTP_LOG_ERROR_MSG(this, "PC doMonitorProcessState: Failed to create a new monitor");
		return TPTP_SYS_NO_MEM;
	}

	// Check our runningProcessList to see if we launched the application.
	rc = findRunningProcessByPid(pid, &proc);

	if (rc == 0) //found it
	{
		//Add requestor id & context to monitor list to get events of this process.
		addProcessMonitor (pid, newMonitor);
	}
	else
	{
		// The PC did not launch this process, try to get a handle on it
		// so that we can monitor the state of this process.
	#ifdef _WIN32
//TODO: Move to _md file as this is platform specific code

		//Get a handle on this process which will allow us
		//to do a wait on it and then query for exit info.
		HANDLE hProcess = OpenProcess(PROCESS_QUERY_INFORMATION | SYNCHRONIZE, FALSE, pid);
		if (hProcess == 0)
		{
			TPTP_LOG_DEBUG_MSG(this, "OpenProcess() call failed.");
			//printCurrentSysError();
			//TODO: Set errCode
			tptp_free (newMonitor);
			return -1;
		}
	#elif defined(__linux__) || defined(_SOLARIS) || defined(_AIX) || defined(MVS)
		// We don't need to attach to the process using ptrace() here
	#else
		rc = ptrace (PTRACE_ATTACH, pid, NULL, NULL);
		if (rc < 0) {
			// This is a known issue on some systems (i.e. SLES 10)
			// so do not log the error if it was due to ptrace being called on
			// it's own process (tptpProcessController)
			if (pid != getpid())
			{
				TPTP_LOG_ERROR_MSG1(this, "ptrace() call failed for pid %d", pid);
			}
			tptp_free (newMonitor);
			return -1;
		}
	#endif //_WIN32


		// Create a process structure for the process we are to monitor, but
		// which we did not start.
		// The structure will be sparse, having no owner or application info.
		// Put the requestorID and context into the monitorsList for the process.
		// Add the process to our runningProcessList which will result in the
		// event handling thread being notified that there is a new process on the
		// list for it to check the state of.
		//
		proc = initProcessT(0, 0, NULL, NULL, NULL, NULL, 0, 0);
		if (!proc)
		{
			TPTP_LOG_ERROR_MSG(this, "PC doMonitorProcessState: Failed to create a new proc struct");
			return TPTP_SYS_NO_MEM;
		}

		proc->pid = pid;
#ifdef _WIN32
		proc->hProcess = hProcess;
#endif
		tptp_list_add(proc->monitors, newMonitor);
		addRunningProcess(proc);

	}

	// Once the process has been added to the list by the above code, we begin a thread to monitor it
	#if defined(__linux__) || defined(_SOLARIS) || defined(_AIX) || defined(MVS)
		TID checkThreadId;
		HANDLE checkThreadHandle;
		procInfo = (activeProcessInfo_t *) tptp_malloc(sizeof(activeProcessInfo_t));
		procInfo->agent = this;
		procInfo->pid = pid;
		startNewThread(checkForProcessDeath, (void*)procInfo, &checkThreadId, &checkThreadHandle);
	#endif

	return 0;
}

///////////////////////////////////////////////////
//
// int ProcessController::doTerminateChildProcesses(int ownerSourceID)
//
///////////////////////////////////////////////////
int ProcessController::doTerminateChildProcesses(const int ownerSourceID)
{
	tptp_list_t		listCopy;
	tptp_node_t*    node;
	tptp_process_t* tmpProc;
	int rc;

	// Get a copy of the runningProcessList.  Cannot work off of
	// the actual list as it could cause lockups in the thread which handles
	// the process events which also accesses the runningProcessList.
	// The terminateProc() call should result in process terminated events
	// being generated by the system.
	tptp_getReadLock(&runningProcessListLock);
	tptp_list_clone(&listCopy, &runningProcessList);
	tptp_releaseReadLock(&runningProcessListLock);

	// Find all processes started by the ownerID and terminate them.
	for (node = listCopy.head; node != 0; node = node->next)
	{
		tmpProc = (tptp_process_t*)node->data;
		if (!tmpProc) continue;  //empty data field, skip this node
			
		#ifdef MVS			
			// If we are being asked to terminate ourselves, then skip this node
			if (tmpProc->pid == getpid()) {
				continue;
			}
		#endif
			
		if ( (tmpProc->ownerID == ownerSourceID) && !tmpProc->keepProcess )
		{
			// Kill the process. If one doesn't succeed, just keep going.
			// This is just our best attempt at cleaning up the processes.
			rc = terminateProc(tmpProc);
			if (rc)
			{
				TPTP_LOG_ERROR_MSG2(this, "PC doTerminateChildProcesses: Failed to terminate process %d (rc=%d)",
									tmpProc->pid, rc);
			}
		}
	}

	tptp_list_clear(&listCopy);
	return 0;
}

//This routine is called when the process controller is about to exit and we need
//to terminate all the agents that we know about. Called from main.
int ProcessController::terminateAllChildProcesses()
{
	tptp_list_t		listCopy;
	tptp_node_t*    node;
	tptp_process_t* tmpProc;
	int rc;

	// Get a copy of the runningProcessList.  Cannot work off of
	// the actual list as it could cause lockups in the thread which handles
	// the process events which also accesses the runningProcessList.
	// The terminateProc() call should result in process terminated events
	// being generated by the system.
	tptp_getReadLock(&runningProcessListLock);
	tptp_list_clone(&listCopy, &runningProcessList);
	tptp_releaseReadLock(&runningProcessListLock);

	// Find all processes and terminate them.
	for (node = listCopy.head; node != 0; node = node->next)
	{
		tmpProc = (tptp_process_t*)node->data;
		
		if (!tmpProc) continue;  //empty data field, skip this node
			
		#ifdef MVS			
			// If we are being asked to terminate ourselves, then skip this node
			if (tmpProc->pid == getpid()) {
				continue;
			}
		#endif
			
		if (  !tmpProc->keepProcess )
		{
			// Kill the process. If one doesn't succeed, just keep going.
			// This is just our best attempt at cleaning up the processes.
			rc = terminateProc(tmpProc);
			if (rc)
			{
				TPTP_LOG_ERROR_MSG2(this, "PC terminateAllChildProcesses: Failed to terminate process %d (rc=%d)",
									tmpProc->pid, rc);
			}
		}
	}

	tptp_list_clear(&listCopy);
	return 0;
}

///////////////////////////////////////////////////
//
// void ProcessController::closeConsoleIOConnections(RemoteConsole_t *console)
//
///////////////////////////////////////////////////
void ProcessController::closeConsoleIOConnections(RemoteConsole_t *console)
{
	if (!console)
	{
		TPTP_LOG_ERROR_MSG(this, "PC closeConsoleIOConnections: Unexpected Null console arg");
		return;
	}

	if (console->in) cleanPipeUp(&console->in);
	if (console->out) cleanPipeUp(&console->out);
	if (console->err) cleanPipeUp(&console->err);
	return;
}


///////////////////////////////////////////////////
//
// int ProcessController::createConsoleIOConnections(char *pUniqueID, RemoteConsole_t *console)
//
// Create named pipe connections to use for transferring data for the
// I/O of a console application.
// Three pipes are created, for stdin, stdout, and stderr.
// The pipe names are of a fixed format as the transport layer in the AC
// must be able to create the same name to open the other end of the
// pipe.
// pipename= namespace + unique ID string + suffix
// namespace: tptp prefix
// unique ID: an auto-generated numeric string
// suffix: either "-stdin" or "-stdout" or "-stderr"
//
///////////////////////////////////////////////////
int ProcessController::createConsoleIOConnections(char *pUniqueID, RemoteConsole_t *console)
{
	char *pPipeName = NULL;

	// Allocate enough space to add suffix: -stdin or -stdout or -sterr
	pPipeName = (char*)tptp_malloc( strlen(pUniqueID) + strlen(PARAM_STDOUT) + 1 );
	if ( !pPipeName )
	{
		TPTP_LOG_ERROR_MSG(this, "PC createConsoleIOConnections: Failed to alloc pipe name");
		return TPTP_SYS_NO_MEM;
	}

	sprintf(pPipeName, "%s%s",pUniqueID, PARAM_STDIN);
	console->in = createReadOnlyNamedPipe(RA_PIPE_NAMESPACE, pPipeName, TRUE);

	sprintf(pPipeName, "%s%s",pUniqueID, PARAM_STDOUT);
	console->out = createWriteOnlyNamedPipe(RA_PIPE_NAMESPACE, pPipeName, TRUE);

	sprintf(pPipeName, "%s%s",pUniqueID, PARAM_STDERR);
	console->err = createWriteOnlyNamedPipe(RA_PIPE_NAMESPACE, pPipeName, TRUE);

	tptp_free(pPipeName);

	if ((console->in  == INVALID_HANDLE_VALUE) ||
		(console->out == INVALID_HANDLE_VALUE) ||
		(console->err == INVALID_HANDLE_VALUE))
	{
		TPTP_LOG_ERROR_MSG(this, "PC createConsoleIOConnections: Failed to create console pipes for stdin/out/err");
		return TPTP_PC_CONSOLE_PIPE_CREATE_FAILED;
	}

	return 0;
}

///////////////////////////////////////////////////
//
// int ProcessController::openConsoleIOConnections(char *pUniqueID, RemoteConsole_t *console)
//
// Open the named pipes which were previously created for console IO.
//
// Under Linux, createNamedPipe() call only creates the file and marks it a FIFO,
// but does not open it. As a result, the file handle returned by createNamedPipe()
// is not usable. It requires a connectToNamedPipe() or openNamedPipe() to open
// it and obtain the pipe handle for processing.
// The pipe will only open successfully if two ends have been opened with read and write.
//
///////////////////////////////////////////////////

int ProcessController::openConsoleIOConnections(char *pUniqueID, RemoteConsole_t *console)
{
	char *pPipeName = NULL;

	// Allocate enough space to add suffix: -stdin or -stdout or -sterr
	pPipeName = (char*)tptp_malloc( strlen(pUniqueID) + strlen("-stdout") + 1 );
	if ( !pPipeName )
	{
		TPTP_LOG_ERROR_MSG(this, "PC openConsoleIOConnections: Failed to alloc pipe name");
		return TPTP_SYS_NO_MEM;
	}

	sprintf(pPipeName, "%s-stdin",pUniqueID);
	console->in = openReadOnlyNamedPipe(RA_PIPE_NAMESPACE, pPipeName, TRUE);

	sprintf(pPipeName, "%s-stdout",pUniqueID);
	console->out = openWriteOnlyNamedPipe(RA_PIPE_NAMESPACE, pPipeName, TRUE);

	sprintf(pPipeName, "%s-stderr",pUniqueID);
	console->err = openWriteOnlyNamedPipe(RA_PIPE_NAMESPACE, pPipeName, TRUE);

	tptp_free(pPipeName);

	if ((console->in  == INVALID_HANDLE_VALUE) ||
		(console->out == INVALID_HANDLE_VALUE) ||
		(console->err == INVALID_HANDLE_VALUE))
	{
		TPTP_LOG_ERROR_MSG(this, "PC openConsoleIOConnections: Failed to open console pipes for stdin/out/err");
		return TPTP_PC_CONSOLE_PIPE_OPEN_FAILED;
	}

	return 0;
}

///////////////////////////////////////////////////
//
// int ProcessController::doCreateProcess(tptp_process_t* proc, unsigned long &errCode)
//
// The proc parameter contains:
// appName - name of executable to be launched
// cmdLineArgs - arguments to be passed to the application
// workingDir - directory from which to launch the application
// envVars - environment variable settings to use for the app
// consoleConnID - 0 indicates the app does not require I/O on its console; otherwise,
//                 contains the connection ID of a data channel that has already been established
//				   to be used to send the I/O across.
// pid - returns the process ID if launch of appName was successful
//
// errCode - returns an error code if launch failed
//
// Returns 0 on success.
// Returns -1 plus errCode on failure.  If fails, the calling function
// is responsible for freeing up the proc structure and its contents.
//
///////////////////////////////////////////////////

int ProcessController::doCreateProcess(tptp_process_t* proc, unsigned long &errCode)
{
	//TODO: Set a value for errCode for each error return and/or log a detailed msg.
	int rc=0;
	char *pUniqueID = NULL;
	RemoteConsole_t console;
	int procConnID = 0;
	pidEntry_t* pidEntry;

	// Set the console to be NULL initially
	console.in = TPTP_HANDLE_NULL;
	console.out = TPTP_HANDLE_NULL;
	console.err = TPTP_HANDLE_NULL;

	// If we have a console app, need to setup a connection for stdin, stdout, and stderr
	// I/O to transfer from the process being created across a data channel to the console
	// handler on the client side.

	if (proc->consoleConnID >= 0)
	{
		// Open the pipes using a unique id string plus "stdin",
		// "stdout", or "stderr".
		pUniqueID = generateUUID();
		if (!pUniqueID)
		{
			TPTP_LOG_ERROR_MSG(this, "PC doCreateProcess: Error generating UUID for pipe name");
			return TPTP_UNEXPECTED_NULL_ARG;
		}

		rc = createConsoleIOConnections(pUniqueID, &console);
		if ( rc )
		{
			// TODO: Report an  error
			TPTP_LOG_ERROR_MSG(this, "PC doCreateProcess: Error creating Console IO connections");
			tptp_free(pUniqueID);
			return rc;
		}

		// Store the string in the process structure  to indicate this process
		// is expected to have console connections for IO.
		// Note: pUniqueID must be malloc'd space as the
		// proc will own it (i.e.,it will get free'd when the proc does).
		proc->consoleUniqueID = pUniqueID;

		// Send CONNECT_CONSOLE request to TL, waiting for response.
		// Must wait until TL sends a response otherwise we would have
		// a race between the app starting to use its I/O prior to the channels
		// being established.
		// The process connection ID returned is the TL side of the connection
		// to be used for the console I/O data. It must be bound to the
		// console connection ID in order to complete the data channel for the
		// console I/O.
		rc = sendCONNECT_CONSOLECommand(pUniqueID, &procConnID);
		if ( rc )
		{
			TPTP_LOG_ERROR_MSG(this, "PC doCreateProcess: Error sending CONNECT_CONSOLE to TL.");
			closeConsoleIOConnections(&console);
			return rc;
		}


#ifndef _WIN32
		// Must do an open after the other end of the (named pipe) connection is established
		// (which occurs in the transport layer as a result of the CONNECT_CONSOLE
		// request) in order to get valid connection handles.
		rc = openConsoleIOConnections(pUniqueID, &console);
		if ( rc )
		{
			TPTP_LOG_ERROR_MSG(this, "PC doCreateProcess: Failed to open Console IO connections.");
			// TODO: check if need to destroy the FIFO that was created by the open.
			// Don't need to call closeConsoleIOConnections() since those handles didn't get
			// opened unless on a Windows system.
			return rc;
		}

		TPTP_LOG_DEBUG_MSG(this, "PC doCreateProcess: opened Console IO connections successfully.");
#endif

		// Have the AC bind the console connection provided in the startProcess
		// request with the process's connection Id returned by the TL.
		char bindCmd[1024];

		sprintf( bindCmd, "<Cmd src=\"%d\" dest=\"%d\" ctxt=\"%d\"> "
						   "<bindDataConnections iid=\"%s\">"
						   "<dataConnection1>%d</dataConnection1>"
						   "<dataConnection2>%d</dataConnection2>"
						   "</bindDataConnections></Cmd>",
						   getAgentID(), getAgentControllerID(), getNextContext(),
						   AGENT_MANAGER_IID,
						   proc->consoleConnID, procConnID);

		TPTP_LOG_DEBUG_MSG(this, "PC doCreateProcess: Sending bindConnections Command for Console to AC");

		rc= sendCommand(bindCmd);
		if ( rc )
		{
			TPTP_LOG_ERROR_MSG(this, "PC doCreateProcess: Failed to send bind for Console IO connections.");
			closeConsoleIOConnections(&console);
			return rc;
		}

		//TODO: Need to handle the response to a Bind Cmd, either an error or
		//dataConnectionsBound iid="org.eclipse.tptp.dataProvider"
		//in the message handling functions for the Process Controller.

	} //end of if (ConsoleConnID)

	// NOTE: Do NOT insert a bunch of code between the connectConsole() call and the
	// createProc() call.  Need timely creation of the application and either send
	// the PID or close the pipe handles. The TL needs the PID quickly so that it can
	// form the header and move the data off the pipes and through the data channel.
	//
	rc = createProc( proc, &console, &errCode);
	if (rc) // failed to launch the app
	{
		TPTP_LOG_ERROR_MSG1(this, "PC doCreateProcess: Error starting process exe %s", proc->appName);
		closeConsoleIOConnections(&console);
		return rc;
	}
	//Compatibility needs to ability to associate a pid with a uuid.
	pidEntry = (pidEntry_t *) tableGet(pidUUIDHashTable, proc->pid);
	if (pidEntry == NULL) {
		pidEntry = (pidEntry_t *) tptp_malloc(sizeof(pidEntry_t));
		pidEntry->pid = proc->pid;
		pidEntry->uuid = generateUUID();
		tablePut(pidUUIDHashTable, proc->pid, pidEntry);
	}
	proc->uuid = pidEntry->uuid;

 	TPTP_LOG_DEBUG_MSG2(this, "PC doCreateProcess: Successfully launched process exe '%s', pid %d", proc->appName, proc->pid);

	if (proc->consoleConnID >= 0)
	{
		// Send the processID to associate with these console pipes to TL.
		// Not checking error return since there is nothing to be done.
		// If it fails, it logs an error
		rc = sendCONSOLE_PROCESS_LAUNCHEDCommand(procConnID, proc->pid);
		if ( rc )
		{
			// TODO: Do we want to return here? App is launched but couldn't let the TL
			// know what the pid is so I/O will not flow.  If we return here, must
			// kill the app we just launched and close the console I/O handles.
			TPTP_LOG_ERROR_MSG(this, "PC doCreateProcess: Failed to send CONSOLE_PROCESS_LAUNCHED cmd to TL.");
		}
		else
		{
			 TPTP_LOG_DEBUG_MSG(this, "PC doCreateProcess: sent CONSOLE_PROCESS_LAUNCHED cmd to TL.");
		}
	}

	// Add this process to the runningProcessList.
	// Once added to this list, the proc struct will get cleaned
	// up when removed from the list upon termination of that pid.
	addRunningProcess(proc);


	// Close PC's copy of the handles. If launch failed, closing these handles
	// will cause the TL to get an end of pipe error right away since the app
	// won't be holding a copy.  If launch succeeds, we need to close this copy
	// so that the end of pipe error will occur when the app that is using them dies.
	closeConsoleIOConnections(&console);

	return 0;
}

///////////////////////////////////////////////////
//
// int ProcessController::validateProcessToLaunch(char* appName, char* workingDir, int replyDest, int replyContext)
//
// Check the launch parameters (executable name and working directory) for accuracy
// and permissions without actually starting the process.
// This would typically be used by a client during configuration to check the user's input.
// The requestor is sent a response notice indicating if the specified application exists
// and is executable and if the specified directory exists and is writable.
//
// If unable to perform the validation, an error notice (CBE format) is sent to the requestor
// (e.g., invalid parameters).
//
///////////////////////////////////////////////////
int ProcessController::validateProcessToLaunch(const char* appName,
											   const char* workingDir,
											   const int replyDest,
											   const int replyContext)
{
	char replyCmd[1024];  //TODO: Eliminate constant
	int validApp=0;
	int validWorkingDir=0;
	unsigned long errCodeApp=0;
	unsigned long errCodeDir=0;


	validApp = validateExecutable(appName, errCodeApp);
	validWorkingDir = validateDirectory(workingDir, errCodeDir);

	TPTP_LOG_DEBUG_MSG2(this, "PC validateProcessToLaunch sending reply with validApp=%d, validWorkingDir=%d",
		validApp, validWorkingDir);
	//TODO: Need to return details about how the validation failed.

	// Send success reply
	sprintf( replyCmd, "<Cmd src=\"%d\" dest=\"%d\" ctxt=\"%d\"> "
					   "<processValidationResults iid=\"%s\">"
					   "<validApp>%d</validApp>"
					   "<validWorkingDir>%d</validWorkingDir>"
					   "</processValidationResults></Cmd>",
					   BaseAgentImpl::getAgentID(), replyDest, replyContext,
					   PROCESS_CONTROLLER_IID,
					   validApp, validWorkingDir);

	sendCommand(replyCmd);

	return 0;
}

///////////////////////////////////////////////////
//
// static THREAD_USER_FUNC_RET_TYPE startProcesses(void* pProcController)
//
// Thread entry point for startProcesses().
//
// Waits for the message handler thread to get a startProcess command
// and place the launch information for that process on the startProcessList
// and set a semaphore to tell this thread it is there.
//
///////////////////////////////////////////////////

static THREAD_USER_FUNC_RET_TYPE startProcesses(void* pProcController)
{
	int rc = 0;
	ProcessController *pcInstance = NULL;

#ifdef MVS
	// We set the thread state to be cancelable by a pthread_cancel call in main().
	pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, 0);
	pthread_setcanceltype(PTHREAD_CANCEL_ASYNCHRONOUS, 0);
#endif

	if (pProcController)
	{
		pcInstance = (ProcessController*)pProcController;
	}
	else
	{
		// Can't log this as we don't have a valid instance ptr here.
		//TPTP_LOG_ERROR_MSG(this, "startProcesses Thread: Error - Invalid ProcessController handle");
		return 0;
	}

	TPTP_LOG_DEBUG_MSG(pcInstance, "startProcesses Thread: begin wait...");
	while (1)
	{
		// Wait for the msg handling thread to get a startProcess cmd and put the
		// process launching info in a list.  The semaphore count will increment
		// with each item put on the list, so we'll know to keep pulling items off
		// the list if several are placed there while we are processing one.
		rc = tptp_waitSemaphore(&startProcessCmdSem);
		if ( rc )
		{
			TPTP_LOG_DEBUG_MSG(pcInstance, "startProcesses Thread: Error waiting for a startProcess cmd");
		} else
		{
			//Call startProcess() to retrieve the proc info from a list,
			//then launch the process.
			TPTP_LOG_DEBUG_MSG(pcInstance, "startProcesses Thread: Received notification of new process to launch");
			pcInstance->startProcess();
		}
	}

	return 0;
}

///////////////////////////////////////////////////
//
// int ProcessController::startProcess(void)
//
// Retrieve process launch information from the startProcessList and then launch
// the process.  This is called by the startProcesses thread which uses semaphores to
// determine when a process has been added to the list.
//
// Returns:
// 0 if successful
// non-zero if failure
//
// If an error reply was sent because there was something wrong with the start request,
// it is not considered a failure of this function.
//
///////////////////////////////////////////////////

int ProcessController::startProcess(void)
{
	tptp_process_t* pProcess;

	//Read the process launch info from the startProcessList, delete it from that list,
	//then launch the process.
	TPTP_LOG_DEBUG_MSG(this, "PC startProcess: Retrieving process launch info from startProcessList");

	int rc = getStartProcessInfo(&pProcess);

	if ( rc )
	{
		// Couldn't get process from startProcessList
		TPTP_LOG_ERROR_MSG(this, "PC startProcess: Error - FAILED to retrieve process launch info from startProcessList");
		return rc;
	}

	rc = startProcess(pProcess);
	if ( rc )
	{
		// Error reply was sent in startProcess, just need to cleanup unused
		// process structure here.  If call had been successful, the process
		// would go on the runningProcessList and get cleaned up when that proc
		// terminated.
		destroyProcessT(pProcess); //free individual elements first
		tptp_free(pProcess);
	}

	return 0;
}

///////////////////////////////////////////////////
//
// int ProcessController::startProcess(tptp_process_t* pProcess)
//
// Launch a process using the application info contained in the process structure arg.
// If successful, send a reply containing the PID of the new process, and at some point
// in the future, send an event notice to indicate when the process terminates.
// This means that the requestor for a start process may receive 2 messages as a result of
// the request, the first comes immediately with information on the success of the process
// launch, and if that was successful, a second message will be sent when that process dies.
//
// In addition to the application launch information parameters (i.e., executable name, arguments,
// working directory, and environment variables), there are options to specify whether
// or not this process should be terminated when the parent (i.e., this start process requestor)
// terminates (by default keepProcess is false, so this process will be terminated) and whether
// or not the requestor wants to receive a termination event notice (by default noNotice
// is false, so a notice will be sent).
//
// If process launch was successful, a process started notice will be sent to anyone listening
// for PC events in addition to the notice sent as a response to the startProcess request itself..
//
// If the process launch fails, an error notice (CBE format) is sent to the requestor containing
// information as to why it failed (e.g. invalid parameters for the process info, executable not
// found, invalid file type, invalid permission, etc).
//
// Returns:
// 0 if successful
// non-zero if failure
//
// We return non-zero failure after sending an error reply so that the calling process knows
// that it must cleanup the process structure passed in because it did not get put on the
// runningProcesses list.
//
///////////////////////////////////////////////////

int ProcessController::startProcess(tptp_process_t* pProcess)
{
	char replyCmd[1024];  //TODO: Eliminate constant
	unsigned long errCode=0;
	int rc=0;
	char* lpszVariable=0;
	int bufferCount = 0;
	char* endTags = "</envVarList></processStarted></Cmd>";

	if (validateExecutable(pProcess->appName, errCode) != 1)
	{
		//Error - no valid name to launch
		char * errStr = tptp_getErrorString(errCode);
		TPTP_LOG_ERROR_MSG2(this, "PC startProcess: Error (%lu), invalid executable '%s'", errCode, pProcess->appName);
		sendErrorCommand(pProcess->ownerID,
						 pProcess->ownerContext,
						 TPTP_PC_UNABLE_TO_START_PROCESS,
						 errStr);
		tptp_free(errStr);
		return -1;
	}

	// If we were given a dir, make sure it exists and is writable
	if (pProcess->workingDir && !isEqualString("", pProcess->workingDir))
	{
		if (validateDirectory(pProcess->workingDir, errCode) != 1)
		{
			//Error - working directory is not useable
			char * errStr = tptp_getErrorString(errCode);
			TPTP_LOG_DEBUG_MSG2(this, "PC startProcess: Error (%lu), invalid working dir '%s'", errCode, pProcess->workingDir);
			sendErrorCommand(pProcess->ownerID,
							 pProcess->ownerContext,
							 TPTP_PC_UNABLE_TO_START_PROCESS,
							 errStr);
			tptp_free(errStr);
			return -1;
		}
	} else {
		//Must be the case the workingDir is "" ... our code expects workingDir to be NULL in this case.
		pProcess->workingDir = NULL;
	}

	// Start the process
	rc = doCreateProcess(pProcess, errCode);

	if ( rc )
	{
		//Error - could not start the process
		char * errStr = tptp_getErrorString(errCode);
		TPTP_LOG_ERROR_MSG2(this, "PC startProcess: Error (%lu), failed to create process '%s'", errCode, pProcess->appName);
		sendErrorCommand(pProcess->ownerID,
						 pProcess->ownerContext,
						 TPTP_PC_UNABLE_TO_START_PROCESS,
						 errStr);
		tptp_free(errStr);
		return -1;
	}

	int EnvLength = 0;
	int EnvVarCount = 0;
	// Calculate the number of strings in the environment
	if (pProcess->envVars)
	{
		for (lpszVariable = pProcess->envVars; *lpszVariable; lpszVariable++)
		{
			while (*lpszVariable)
			{
				lpszVariable++;
				EnvLength++;
			}
			EnvLength++;
			EnvVarCount++;
			if(*lpszVariable == '\0' && *(lpszVariable+1) == '\0')
			{
				EnvLength++;
				break;
			}
		}
	}
	//printf("The Env Length is - %d\n", EnvLength);

	// Send success reply
	sprintf( replyCmd, "<Cmd src=\"%d\" dest=\"%d\" ctxt=\"%d\"> "
					  "<processStarted iid=\"%s\">"
					  "<processID>%lu</processID>"
					  "<processUUID>%s</processUUID><envVarCount>%d</envVarCount><envVarList>",
					  BaseAgentImpl::getAgentID(), pProcess->ownerID, pProcess->ownerContext,
					  PROCESS_CONTROLLER_IID,
					  (unsigned long)pProcess->pid,
					  pProcess->uuid, EnvVarCount);


	// Calculate encoded string length
	int encodedEnvLength = tptp_encodeBase64((unsigned char*)pProcess->envVars, EnvLength, NULL, 0);

	// Total Env String length that goes between <environment> and </environment> tags
	bufferCount = strlen(replyCmd) + encodedEnvLength + strlen(endTags) + 1;

	char* replyCommandBuffer = (char*) tptp_malloc(bufferCount);

	// Copy the processStarted command tag
	memcpy(replyCommandBuffer, replyCmd, strlen(replyCmd));

	// Encode (base64 encoding) the Environment string to get the encoded environ string
	// Remember that the encoded string is longer than actual string
	// We are encoding/decoding the envinroment due to it's format which contains null terminators
	// after each environment string
	char* envStartPtr = replyCommandBuffer+strlen(replyCmd);
	if (EnvLength > 0)
	{
		tptp_encodeBase64( (unsigned char*)pProcess->envVars, EnvLength, envStartPtr, encodedEnvLength );
	}
	//printf("The base 64 env length count %d\n", envLengthCount);

	// Copy the end tags
	memcpy(replyCommandBuffer+strlen(replyCmd)+encodedEnvLength-1, endTags, strlen(endTags)); /* -1 since the encoded buffer contains a null */

	// Add a null terminator
	*(replyCommandBuffer+strlen(replyCmd)+encodedEnvLength+strlen(endTags)) = '\0';
	*(replyCommandBuffer+strlen(replyCmd)+encodedEnvLength+strlen(endTags)-1) = '\0';	// since encodedEnvLength counts terminating null

	//printf("Sending Command from PC - %s and command length is - %d\n", replyCommandBuffer, bufferCount);

	// Send command
	sendCommand(replyCommandBuffer, bufferCount, 0);

	if (replyCommandBuffer) tptp_free(replyCommandBuffer);

	return 0;
}

///////////////////////////////////////////////////
//
// int ProcessController::stopProcess(const PID pid, int replyDest, replyContext)
//
// Stop the specified process ID.  After the process terminates, an event is sent to the
// requestor indicating the terminated state of the process. Additionaly, the requestor of
// the startProcess for this PID (if different than the stop requestor) and anyone that is
// monitoring the process state or listening for PC events, will receive a process stopped
// event.
//
// This request will terminate a process without regard for the keepProcess option
// (i.e., such a process will be terminated by this request even when keepProcess is true).
//
// If the process termination fails, an error notice (CBE format) is sent to the requestor
// (e.g., non-existent PID, insufficient permission). A request to stop a process which the
// PC did not start will fail due to insufficient permission.
//
///////////////////////////////////////////////////

int ProcessController::stopProcess(const PID pid,
								   const int replyDest,
								   const int replyContext)
{
	int rc=0;

	rc = doStopProcess(pid, replyDest, replyContext);
	if ( rc )
	{
		// Error - could not stop the process
		char* errStr = tptp_getErrorString(rc);
		TPTP_LOG_ERROR_MSG1(this, "PC stopProcess: Failed to stop process %d", pid);
		sendErrorCommand(replyDest,
						 replyContext,
						 TPTP_PC_UNABLE_TO_STOP_PROCESS,
						 errStr);
		tptp_free(errStr);
	}

	// No reply command is sent since this thread will
	// not know if the process stop request actually succeeded.
	// The thread monitoring processes will get the OS event/signal
	// and report the event.
	return 0;
}

int ProcessController::getProcessUUID(const PID pid,
								   const int replyDest,
								   const int replyContext)
{
	char replyCmd[1024];
	pidEntry_t* pidEntry;

	pidEntry = (pidEntry_t *) tableGet(pidUUIDHashTable, pid);
	if (pidEntry == NULL) {
		//In this case the PC did not start the process.
		//We need to generate the uuid.
		pidEntry = (pidEntry_t *) tptp_malloc(sizeof(pidEntry_t));
		pidEntry->pid = pid;
		pidEntry->uuid = generateUUID();
		tablePut(pidUUIDHashTable, pid, pidEntry);
	}

	sprintf( replyCmd, "<Cmd src=\"%d\" dest=\"%d\" ctxt=\"%d\"> "
								"<getProcessUUID iid=\"%s\">"
								"<processUUID>%s</processUUID>"
								"</getProcessUUID></Cmd>",
				   BaseAgentImpl::getAgentID(), replyDest, replyContext,
				   PROCESS_CONTROLLER_IID,
				   pidEntry->uuid);

	return (sendCommand(replyCmd));
}

///////////////////////////////////////////////////
//
// int ProcessController::monitorProcessState(PID pid, int replyDest, int replyContext)
//
// Requests that an event notice be sent to the requestor when the indicated PID terminates.
// If the PC did not start the specified PID, it will attempt to monitor that process anyway,
// but the success and timeliness of the notice is dependent on the services provided by the
// platform OS.  (For instance, on Windows one can obtain a process handle and simply wait for
// a state change, resulting in a non-intrusive yet prompt notice being sent. However, on Linux
// one needs to poll the internal /proc structure or attach via ptrace in order to get process
// state info for an un-related pid.)
//
// If the attempt to monitor a process fails, an error notice (CBE format) is sent to the
// requestor.
//
// Note: The AC will use this function so that it can rely on the PC to tell it when any agent
// process dies - including those agents that were not started by the AC.
//
///////////////////////////////////////////////////

int ProcessController::monitorProcessState(const PID pid,
										   const int replyDest,
										   const int replyContext)
{
	int rc=0;
	rc = doMonitorProcessState(pid, replyDest, replyContext);

	if ( rc )
	{
		char* errStr = tptp_getErrorString(rc);
		TPTP_LOG_ERROR_MSG1(this, "PC monitorProcessState: Failed to monitor process %d", pid);
		sendErrorCommand(replyDest,
						 replyContext,
						 TPTP_PC_UNABLE_TO_MONITOR_PROCESS,
						 errStr);
		tptp_free(errStr);
	}

	//No reply sent if monitor request is successful.

	return 0;
}

///////////////////////////////////////////////////
//
// int ProcessController::terminateChildProcesses(int ownerSourceID, int replyDest, int replyContext)
//
// Find all processes that were started by a particular Source ID and terminate them,
// provided they were: a) launched using this PC and, b) not started with the option
// to keep running after parent's death, where the parent is that which issued the
// startProcess request.
//
// The requestor does not automatically receive process stopped notices for those
// processes which get terminated as a result of this request. For each process that
// is terminated, a process stopped notice will be sent to the requestor of the
// startProcess and to anyone that is monitoring the process state or listening for PC events.
//
// If an error occurs while trying to perform this request, an error notice (CBE format)
// is sent to the requestor.
//
// This can be used by the parent process, as a general cleanup of processes it started
// before it exits, but it is not equivalent to calling stopProcess on each of its processes.
// The stopProcess would kill a process that was started with the option set to allow it to
// continue executing after its parent's death, whereas this will not terminate such a process.
//
// The more common use is expected to be for cleaning up (i.e., terminating) the processes of
// a parent that terminated without doing its own cleanup.  The object requesting this cleanup
// would be monitoring the parent, but know nothing of its child processes.  It would not know
// if the parent truly had any child processes and as such, wouldn't know whether to expect
// process stopped notices. Thus, the requestor does not automatically receive stopped notices
// like the requestor of a stop process would get.
//
// This request takes a Source ID as the identification for the parent process to do the cleanup
// on, which prevents it from being used by just anyone.  Typically, this information would only
// be known by the Agent Controller and the "source" itself.  The Source ID is a unique value
// that originates with the Connection Manager used to identify the sender of a message coming
// into the AC.  Any process creating an XML Cmd to be sent to the AC must know this value, so
// this request can be used to cleanup its own child processes.  But only the AC knows the
// Source ID of someone other than itself.
//
// Note: The AC would use this function to clean up processes started by agents, after an agent
// dies.  The extent of the process cleanup is limited to those processes which the PC both
// knows about and can control (vs. simply monitor for status).  Hence, the dead parent agent
// must have used this PC to launch its processes otherwise the PC will have no record of the
// children and not terminate them.  It does not matter if the dead agent itself had been
// launched by the PC, only that its children were.
//
///////////////////////////////////////////////////

int ProcessController::terminateChildProcesses(const int ownerSourceID,
											   const int replyDest,
											   const int replyContext)
{
	int rc=0;

	rc = doTerminateChildProcesses(ownerSourceID);

	if ( rc )
	{
		char* errStr = tptp_getErrorString(rc);
		TPTP_LOG_ERROR_MSG1(this, "PC terminateChildProcesses: Failed to terminate child processes for ownerID %d", ownerSourceID);
		sendErrorCommand(replyDest,
						 replyContext,
						 TPTP_PC_UNABLE_TO_TERM_CHILD_PROCESSES,
						 errStr);
		tptp_free(errStr);
	}

	//No reply sent if terminateChildProcesses request is successful.

	return 0;
}

///////////////////////////////////////////////////
//
// int ProcessController::processCommand(CmdBlock* cmd)
//
// This function is called by the msg handler thread when commands come in from
// the AgentController. The thread is started during registerAgent().
//
// Only commands we know/care about are processed.  Other commands will fall
// through and should cause an error response command to be sent indicating an
// unknown command.  Unknown commands will not cause an error return from this function.
//
// Basically, any user-input type error results in an error command sent back to the
// requestor and is not considered an error of this function.  However, an internal
// error (e.g., failure to allocate memory or set a semaphore) does return failure
// of this function.
//
// Once the XML command string has been successfully processed and a call made to the
// handler of that command, it is up to the handler code to deal with any errors that
// result, all of which should include sending error replies back to the requestor.
//
///////////////////////////////////////////////////

int ProcessController::processCommand(CmdBlock* cmd)
{
	int            sourceID;
	int            context;
	char*          interfaceID = 0;
	char*          cmdName = 0;
	tptp_list_t*   paramList;

	//Handle basic agent commands
	int handled = BaseAgentImpl::processCommand(cmd);

	if (handled == 0)
	{
		TPTP_LOG_DEBUG_MSG1(this, "PC processCommand: BaseAgent handled cmd:%s", cmd->getCommandName());
		return handled;
	}

	sourceID = cmd->getSourceID();
	context = cmd->getContextID();
	interfaceID = cmd->getIID();
	cmdName = cmd->getCommandName();
	paramList = cmd->getParamList();

	TPTP_LOG_DEBUG_MSG1(this, "PC processCommand: processing cmd:%s", cmdName);

	// Handle incoming commands from the processController Interface
	if ( isEqualString( interfaceID, PROCESS_CONTROLLER_IID))
	{
		if (isEqualString(cmdName, "validateProcessToLaunch"))
		{
			// Command: validateProcessToLaunch
			// Expected parameters: applicationName, workingDir
			// If no params found report an error.
			// Missing and extra params are ignored.
			// If dup params exist, use the last one listed.
			char* appName=NULL;
			char* workingDir=NULL;

			if (0 != getStringParam("applicationName", paramList, &appName))
			{
				// Error - Missing arg for this command
				TPTP_LOG_ERROR_MSG1(this, "PC processCommand: Command %s is missing required arg", cmdName);
				sendErrorCommand(sourceID,
								 context,
								 TPTP_CMD_MISSING_ARG,
								 0);
				return 0;
			}

			if (0 != getStringParam("workingDir", paramList, &workingDir))
			{
				// Not an error - workingDir not required.
			}

			validateProcessToLaunch(appName, workingDir, sourceID, context);
		}
		else if (isEqualString(cmdName, "startProcess"))
		{
			// Command: startProcess
			// Expected parameters: applicationName, commandLineArgs,
			//						workingDir, environmentVars,
			//						consoleConnectID
			// The applicationName parameter is required, otherwise get an error reply.
			// The consoleConnectID indicates that we are starting a console application
			// and its value is the connection ID of the data channel to use in sending
			// the application's I/O (stdin, stdout, and stderr).
			//
			// Other missing or extra params are ignored.
			// If dup params exist, we'll use the last one listed.
			char* appName=NULL;
			char* cmdLineArgs=NULL;
			char* workingDir=NULL;
			char* envVars=NULL;
			char* appTagStr=NULL;
			int   consoleConnID=-1;
			unsigned int optionsFlag=0;
			App_Label_t *appInfo=NULL;

			tptp_process_t* proc;

			if (0 != getStringParam("launchInfo", paramList, &appTagStr))
			{
				// Error - Missing arg for this command
				TPTP_LOG_ERROR_MSG1(this, "PC processCommand: Command %s is missing required arg", cmdName);
				sendErrorCommand(sourceID,
								 context,
								 TPTP_CMD_MISSING_ARG,
								 "Application");
				return 0;

			} else //Process the string further since it is an xml fragment
			{
				int endLen; /* length of the end tag */

				char *aptr = strstr (appTagStr, APPLICATION_TAG);
				if (aptr == NULL) return -1;

				char *eptr = strstr (aptr, APPLICATION_END_TAG);
				if (eptr == NULL) {
					eptr = strstr (aptr, APPLICATION_END_TAG2);
					if (eptr == NULL) {
						return -1;
					}
					else {
						endLen = strlen(APPLICATION_END_TAG2);
					}
				}
				else {
					endLen = strlen(APPLICATION_END_TAG);
				}

				*(eptr += endLen) = 0;		// to cut currently unprocessed tags (<keepRunning>, <noNotices>) off

				// Parse the Application tag into its component parts.
				int rc = parseApplication(aptr, &appInfo);
				if (rc)
				{
					TPTP_LOG_ERROR_MSG2(this, "PC processCommand: Command %s cannot parse Application parameter: %s", cmdName, appTagStr);
					sendErrorCommand(sourceID,
									 context,
									 TPTP_CMD_BAD_ARG,
									 "Application");
					tptp_free(appTagStr);
					return 0;
				}
				tptp_free(appTagStr); //appInfo has the info, so free the string

				// Must have an executable specified or we can do nothing.
				if (!appInfo->executable)
				{
					TPTP_LOG_ERROR_MSG1(this, "PC processCommand: Command %s missing Application executable name", cmdName);
					sendErrorCommand(sourceID,
									 context,
									 TPTP_CMD_MISSING_ARG,
									 "Application executable");
					destroyAppLabelT(appInfo);
					return 0;
				}

				// See if the executable name passed in on the startProcess cmd is actually an
				// alias.  If it is, get the application's launch info from that stored
				// in the alias.
				tptp_node_t* node=NULL;
				App_Label_t* appAlias=NULL;
				bool foundAlias = false;

				if (appAliasesList)
				{
					for (node = appAliasesList->head; node != 0; node = node->next)
					{
						appAlias = (App_Label_t*)node->data;
						if (!appAlias) continue;  //empty data field, skip this node
						if ( isEqualString(appAlias->executable, appInfo->executable) )
						{
							foundAlias = true;
							break;
						}
					}
				}

				// See if we have been configured to only allow launching of a predefined list
				// of applications. (ie., those in the Application Aliases list).
				if ( !foundAlias && launchAliasesOnly )
				{
					//TPTP_LOG_ERROR_MSG2(this, "PC processCommand: Configuration requires use of Application Alias to be used in launch, but not found for exe %s.", appInfo->executable);
					sendErrorCommand(sourceID,
									 context,
									 TPTP_PC_APP_ALIAS_REQUIRED,
									 appInfo->executable);
					destroyAppLabelT(appInfo);
					return 0;
				}

				if (foundAlias)
				{
					// Use exe name/path specified by the alias
					appName = appAlias->path;
					if (!appName)
					{
						TPTP_LOG_ERROR_MSG2(this, "PC processCommand: Command %s is missing executable pathname in Application alias %s", cmdName, appInfo->executable);
						sendErrorCommand(sourceID,
										 context,
										 TPTP_CMD_MISSING_ARG,
										 "Application alias's path");
						destroyAppLabelT(appInfo);
						return 0;
					}

#ifdef _WIN32
// On Windows, the appName might have spaces in it, so we need to wrap it with quotation marks
// This is intended as a temporary workaround until the code can be restructured so that the
//    quotation marks are added inside the modifyApplicationParameters function.
					char *tempAppName = (char *)tptp_malloc( strlen(appName) + 3 );
					if ( !tempAppName )
					{
						TPTP_LOG_ERROR_MSG1(this, "PC processCommand: Out of memory while allocating space for executable name: %s", appName);
						sendErrorCommand(sourceID,
										 context,
										 TPTP_SYS_NO_MEM,
										 appName);
						destroyAppLabelT(appInfo);
						return 0;
					}

					strcpy( tempAppName, "\"" );
					strcat( tempAppName, appName );
					strcat( tempAppName, "\"" );
#endif // _WIN32


					// Use the workingDir from the incoming cmd if it exists,
					// otherwise use that of the alias if it exists.
					// If the alias has no location specified, it should be a Null.
					// Note: To avoid extra string copies, just the ptr is stored in
					// the local var 'workingDir' at this time. When the process object
					// is created in initProcessT, a copy of the string will be created.
					// So don't free appInfo until after that happens.  appAlias is in a
					// list that lives as long as the PC, so it is ok to use until copied too.
					if (appInfo->location)
					{
						//if appInfo->location is "" this should be treated as a NULL.
						//In this case we should use the alias location.
						if (strcmp(appInfo->location, "") != 0)
						{
							workingDir = appInfo->location;
						} else {
							workingDir = appAlias->location; //could be null
						}
					}
					else
						workingDir = appAlias->location; //could be null

					// CmdLineArgs should be a combination of those specified on the
					// incoming cmd with those specified in the alias.
					// Incoming cmd takes precedence over alias's settings.
					// Either/both lists could be empty
#ifdef _WIN32
					cmdLineArgs = modifyApplicationParameters(appInfo->parameters, tempAppName);
					tptp_free(tempAppName);
#else
					cmdLineArgs = modifyApplicationParameters(appInfo->parameters, appName);
#endif // _WIN32
					char* tmpCmdLineArgs = modifyApplicationParameters(appAlias->parameters, cmdLineArgs);

					// Free the old args before replacing
					tptp_free( cmdLineArgs );
					cmdLineArgs = tmpCmdLineArgs;
					tmpCmdLineArgs = NULL;

					// EnvVars should be a combination of:
					// 1) default env set when this process controller was launched
					// 2) env variables defined in the alias
					// 3) env variables defined by the incoming cmd
					// These must be combined in this order so that the incoming cmd
					// settings get the highest precedence.
					char *tmpEnv = (char *)getEnvironmentStrings();
					char *tmpEnvWithAppAliasMods;
					tmpEnvWithAppAliasMods = (char *)modifyApplicationEnvironment(appAlias->variables, tmpEnv);
					freeEnvironmentStrings(tmpEnv);
					envVars = (char *)modifyApplicationEnvironment(appInfo->variables, tmpEnvWithAppAliasMods);

					tptp_free(tmpEnvWithAppAliasMods);
				} else	// If no match to an alias, use info from incoming cmd parameters
				{
#ifdef _WIN32
// On Windows, the appName might have spaces in it, so we need to wrap it with quotation marks
// This is intended as a temporary workaround until the code can be restructured so that the
//    quotation marks are added inside the modifyApplicationParameters function.
					char *tempAppName = (char *)tptp_malloc( strlen(appInfo->executable) + 3 );
					if ( !tempAppName )
					{
						TPTP_LOG_ERROR_MSG1(this, "PC processCommand: Out of memory while allocating space for executable name: %s", appName);
						sendErrorCommand(sourceID,
										 context,
										 TPTP_SYS_NO_MEM,
										 appName);
						destroyAppLabelT(appInfo);
						return 0;
					}

					strcpy( tempAppName, "\"" );
					strcat( tempAppName, appInfo->executable );
					strcat( tempAppName, "\"" );
#endif // _WIN32
					appName = appInfo->executable;
					workingDir = appInfo->location;
#ifdef _WIN32
					cmdLineArgs = modifyApplicationParameters(appInfo->parameters, tempAppName);
					tptp_free(tempAppName);
#else
					cmdLineArgs = modifyApplicationParameters(appInfo->parameters, appName);
#endif
					char *tmpEnv = (char *)getEnvironmentStrings();
					envVars = (char *)modifyApplicationEnvironment(appInfo->variables, tmpEnv);
					freeEnvironmentStrings(tmpEnv);
				}
			}

			if (0 != getIntegerParam("consoleConnectID", paramList, &consoleConnID))
			{
				// Not an error - consoleConnectID not required.
			}

			if (0 != getIntegerParam("optionsFlag", paramList, (int *)&optionsFlag))
			{
				// Not an error - options flag not required.
			}


			// Must have a separate thread handling startProcess, because if we have
			// an app requiring console I/O, we must wait for a msg indicating
			// the TL has established its ends of the the stdin/out/err pipes before
			// the process creation can occur.  Hence, this msg handling thread must
			// be able to receive msgs while a startProcess is in-progress.
			//
			// Note: We could use a separate thread only for starting console processes, but
			// but don't do that. It would mean that a client could send several startProcess
			// requests, some with console I/O and some without, and the processStarted
			// msgs that get sent back would not occur in the same order that the
			// start requests came in.
			//
			// Store the process launching information in the list for the
			// startProcesses thread, then set the semaphore that lets
			// that thread know that something has been put on the list.

			proc = initProcessT(sourceID, context,
								appName, cmdLineArgs,
								workingDir, envVars,
								consoleConnID, optionsFlag);
			if (!proc)
			{
				/* Report failure caused by an internal error */
				TPTP_LOG_ERROR_MSG(this, "PC processCommand: Error - failed to alloc proc obj for startProcess request.");
				sendErrorCommand(sourceID,
								 context,
								 TPTP_PC_UNABLE_TO_START_PROCESS,
								 "Internal process allocation error");

				//TODO: Exit this thread or keep processing commands?
				//If initProcess failed because of a system resource error we
				//should exit as that won't go away.

				tptp_free( envVars );
				tptp_free( cmdLineArgs );

				destroyAppLabelT(appInfo);
				return -1;
			}
			destroyAppLabelT(appInfo); //Info is copied into process, so can free it.

			addStartProcessInfo(proc);

			tptp_free( envVars );
			tptp_free( cmdLineArgs );

			int rc = tptp_postSemaphore(&startProcessCmdSem);
			if ( rc )
			{
				/* Report an internal error */
				TPTP_LOG_ERROR_MSG(this, "PC processCommand: Error - Failed to notify startProcesses thread of new item on list");
				sendErrorCommand(sourceID,
								 context,
								 TPTP_PC_UNABLE_TO_START_PROCESS,
								 "Internal notification error");
				return -1;
			}

			TPTP_LOG_DEBUG_MSG(this, "PC processCommand: Notified startProcesses thread of new item on list");
		}
		else if (isEqualString(cmdName, "stopProcess"))
		{
			// Command: stopProcess
			// Expected parameters: processID
			// If no params found report an error.
			// Missing and extra params are ignored.
			// If dup params exist, use the last one listed.
			PID processID = TPTP_INVALID_PID;

			if (0 != getPIDParam("processID", paramList, &processID))
			{
				// Error - Missing arg for this command
				TPTP_LOG_ERROR_MSG1(this, "PC processCommand: Command %s is missing required arg", cmdName);
				sendErrorCommand(sourceID,
								 context,
								 TPTP_CMD_MISSING_ARG,
								 "processID");
				return 0;
			}

			stopProcess(processID, sourceID, context);

		}
		else if (isEqualString(cmdName, "getProcessUUID"))
		{
			// Command: getProcessUUID
			// Expected parameters: processID
			// If no params found report an error.
			// Missing and extra params are ignored.
			// If dup params exist, use the last one listed.
			PID processID = TPTP_INVALID_PID;


			if (0 != getPIDParam("processID", paramList, &processID))
			{
				// Error - Missing arg for this command
				TPTP_LOG_ERROR_MSG1(this, "PC processCommand: Command %s is missing required arg", cmdName);
				sendErrorCommand(sourceID,
								 context,
								 TPTP_CMD_MISSING_ARG,
								 "processID");
				return 0;
			}

			getProcessUUID(processID, sourceID, context);

		}
		else if (isEqualString(cmdName, "monitorProcessState"))
		{
			// Command: monitorProcessState
			// Expected parameters: processID
			// If no params found report an error.
			// Missing and extra params are ignored.
			// If dup params exist, use the last one listed.
			PID processID = TPTP_INVALID_PID;

			if (0 != getPIDParam("processID", paramList, &processID))
			{
				// Error - Missing arg for this command
				TPTP_LOG_ERROR_MSG1(this, "PC processCommand: Command %s is missing required arg", cmdName);
				sendErrorCommand(sourceID,
								 context,
								 TPTP_CMD_MISSING_ARG,
								 "processID");
				return 0;
			}

			monitorProcessState(processID, sourceID, context);

		}
		else if (isEqualString(cmdName, "terminateChildProcesses"))
		{
			// Command: terminateChildProcesses
			// Expected parameters: sourceID
			// If no params found report an error.
			// Missing and extra params are ignored.
			// If dup params exist, use the last one listed.
			int procOwnerSourceID = -1;

			if (0 != getIntegerParam("sourceID", paramList, &procOwnerSourceID))
			{
				// Error - Missing arg for this command
				TPTP_LOG_ERROR_MSG1(this, "PC processCommand: Command %s is missing required arg", cmdName);
				sendErrorCommand(sourceID,
								 context,
								 TPTP_CMD_MISSING_ARG,
								 "sourceID");
				return 0;
			}

			terminateChildProcesses(procOwnerSourceID, sourceID, context);

		}
		else
		{
			TPTP_LOG_DEBUG_MSG2(this, "PC processCommand: WARNING - '%s' is unrecognized cmd for interface '%s'", cmdName, PROCESS_CONTROLLER_IID);
			// Error - unhandled command from processController interface
			sendErrorCommand(sourceID,
							 context,
							 TPTP_CMD_UNKNOWN,
							 cmdName);
			return 0;
		}

	} else // Handle incoming commands from the AgentController Interface
	if ( isEqualString( interfaceID, AGENT_MANAGER_IID))
	{
		if (isEqualString(cmdName, "applicationAliases"))
		{
			// Command: applicationAliases
			// Expected parameters: aliases
			// If no params found - not an error.
			// Missing and extra params are ignored.

			char* aliases=NULL;


			if (0 != getStringParam("aliases", paramList, &aliases))
			{
				// Not an error - application aliases not required.
				// PC's appAliasesList just remains Null.
				TPTP_LOG_DEBUG_MSG(this, "PC processCommand: No Application aliases found by the Agent Controller");
			} else
			{
				int tmpLAO;
				// Process the string of Application tags and initialize the
				// PC's appAliasesList.
				if (parseApplicationAliases(aliases, &appAliasesList, &tmpLAO))
				{
					TPTP_LOG_DEBUG_MSG(this, "PC processCommand: Failed to parse Application Alias list");
				}

				if (tmpLAO)
					launchAliasesOnly = true;
				else
					launchAliasesOnly = false;

			}
			int rc = tptp_postSemaphore(&applAliasesResponseSem);
			if ( rc )
			{
				/* Report an internal error */
				TPTP_LOG_ERROR_MSG(this, "PC processCommand: Error - Failed to notify startProcesses thread of applicationAliases response");
				sendErrorCommand(sourceID,
								 context,
								 TPTP_SYS_SEMAPHORE_FAILED,
								 "Internal error processing applicationAliases response");
				return -1;
			}

		}

	} else // Handle incoming commands from the eventProvider Interface
	if ( isEqualString( interfaceID, EVENT_PROVIDER_IID))
	{
		if (isEqualString(cmdName, "addEventListener"))
		{
			// Command: addEventListener
			// Expected parameters: interfaceID, listenerID
			// If no params found report an error.
			// Missing and extra params are ignored.
			// If dup params exist, use the last one listed.
			char* listenToEventIID=NULL;
			int listenerID=0;

			if (0 != getStringParam("interfaceID", paramList, &listenToEventIID))
			{
				// Not an error - interfaceID event filter not required.
			}

			if (0 != getIntegerParam("listenerID", paramList, &listenerID))
			{
				// Not an error - listenerID (context value to put in event msg) not required.
			}

			addEventListener(listenToEventIID, listenerID, sourceID, context);

		} else
		{
			TPTP_LOG_DEBUG_MSG2(this, "PC processCommand: WARNING - '%s' is unrecognized cmd for interface '%s'", cmdName, EVENT_PROVIDER_IID);
			// Error - unhandled command from eventProvider interface
			sendErrorCommand(sourceID,
							 context,
							 TPTP_CMD_UNKNOWN,
							 cmdName);
			return 0;
		}
	}
	else
	{
		TPTP_LOG_DEBUG_MSG1(this, "PC processCommand: WARNING - Interface '%s',not handled by processController", interfaceID);
		// Command from interface not handled by the processCommand,
		// but could have been handled by another, such as base agent's.
		return 0;
	}

	return 0;
}

ProcessController* ProcessControllerAgent = 0;

#if defined (__linux__) || defined (__APPLE__)
void doCheckProcess (int _pid, int mode) {
	int s, pid, count = 0;

	while (1) {
		pid = waitpid(_pid, &s, mode);
		//see bugzilla 245173
		//tptpProcessController receives the SIGCHLD but the
		//return value of the child process is not yet available

		if (pid < 0 || count >= 10) break;
		if (pid == 0) {
			count++;		//allow a maximum wait of 10ms for the information
			tptpSleep(1);	//to become available
			continue;
		}
		if (WIFEXITED(s)) {
			ptrace (PTRACE_DETACH, pid, NULL, NULL);
		}
		else if (WIFSIGNALED(s)) {
			ptrace (PTRACE_DETACH, pid, NULL, NULL);
		}
		else if (WIFSTOPPED(s)) {
			int sig = WSTOPSIG(s);

			if (sig == SIGSTOP) {
				ptrace (PTRACE_CONT, pid, NULL, NULL);
				pid = 0;
			}
			else {
				ptrace (PTRACE_DETACH, pid, NULL, NULL);
				kill (pid, sig);
#ifdef __ia64
				pid = 0;	// to supress false notification, bug #218845
#endif
			}
		}

		if (pid > 0 && ProcessControllerAgent != NULL) {
			ProcessControllerAgent->handleChildExited(pid, s);
		}
	}
}

///////////////////////////////////////////////////
//
// cleanupChild(int sig)
//
///////////////////////////////////////////////////
void cleanupChild(int sig)
{
	/* Read our child's exit code so it won't be a zombie */
	doCheckProcess (-1, WNOHANG);
}
#endif

#if defined(__linux__) || defined(_SOLARIS) || defined(_AIX) || defined(MVS)
// In order to prevent defunct/zombie processes, we use this empty signal handler whose only purpose is to call wait() on signals we received from the child handler.
// For more information, see the 'wait' man page.
void emptyWaitSignalHandler(int sig) {
		int status;
		wait(&status);
		return;
}

#endif

///////////////////////////////////////////////////
//
// static THREAD_USER_FUNC_RET_TYPE checkForProcessDeath(void* pid_val)
//
// Thread entry point to check process death
//
// the pid_val point must be an activeProcessInfo_t* struct
//
///////////////////////////////////////////////////
static THREAD_USER_FUNC_RET_TYPE checkForProcessDeath(void* pid_val) {
	activeProcessInfo_t* procInfo;

	if (pid_val) {
		procInfo = (activeProcessInfo_t*)pid_val;
	} else {
		return 0;
	}

	#if defined(__linux__) || defined(_SOLARIS) || defined(_AIX) || defined(MVS)
		while ( true ) {
			bool found = FALSE;

			errno = 0;
			if(-1 == getpriority(PRIO_PROCESS, procInfo->pid) && errno != 0) {
	        found = FALSE;
			} else {
	        found = TRUE;
			}

			if ( found == TRUE ) {
				// We found a match - the process hasn't died, so sleep
				tptpSleep(750);
			} else {
				// The process has died - send the death notification
				procInfo->agent->sendEventNotifications(procInfo->pid, 0);
				procInfo->agent->removeRunningProcess(procInfo->pid);
				return 0;
			}
		}
	#endif

	tptp_free(procInfo);
	return 0;
}


///////////////////////////////////////////////////
//
//  Main thread for the Process Controller.
//
///////////////////////////////////////////////////
int main(int argc, char* argv[])
{
	char* agentName = "ProcessController";  // default name
	int rc = 0;
	TID    myThreadIDs[3]; // holds the thread IDs of all threads started here
	HANDLE myThreadHandles[3]; // holds the handles of all threads started here
#ifndef _WIN32
	int *status;
#endif

	// This parallels changes made in bug 242899 to the agent controller. This line allows
	// Xerces-C to properly handle character strings that include non-ASCII characters (bug 272357)
	#ifndef _WIN32
		// Set the program locale to that of the operating system
		setlocale(LC_ALL, "");
	#endif

	ProcessControllerAgent = new ProcessController(agentName);
	if ( !ProcessControllerAgent )
	{
		return -1;
	}

#if defined(__linux__) || defined(_SOLARIS) || defined(_AIX) || defined(MVS)
	struct sigaction sigActSIGCHLD;
	BZERO(&sigActSIGCHLD, sizeof(struct sigaction));

	sigActSIGCHLD.sa_handler=&emptyWaitSignalHandler;

	sigaction(SIGCHLD, &sigActSIGCHLD, NULL);

	// Do nothing
#elif !defined(_WIN32)
	/* Cleanup our children when they exit */
	struct sigaction sigActSIGCHLD;
	BZERO(&sigActSIGCHLD, sizeof(struct sigaction));
	sigActSIGCHLD.sa_handler=&cleanupChild;
			/* RKD:  On all of the platforms except solaris we can
			         successfully listen for our children to exit so
					 that we can clean up there zombie state manually.
					 On Solaris we are going to specify not to allow
					 zombies to occur and to not tell us when the process
					 exits.  THIS SHOULD BE REVISITED.
			*/
	#if defined _SOLARIS
		sigActSIGCHLD.sa_flags=SA_NOCLDSTOP | SA_NOCLDWAIT;
	#endif

	sigaction(SIGCHLD, &sigActSIGCHLD, NULL);
#endif

	// Create semaphore to tell the startProcesses() thread when the applicationAliases
	// cmd has arrived in response to a getApplicationAliases request. The msg handling
	// thread will read the list of aliases and store them for use when processing
	// startProcess requests, prior to posting the semaphore.
	rc = tptp_initializeSemaphore(&applAliasesResponseSem); //TODO:destroy this semaphore
	if ( rc )
	{
		// If we can't create a synch object, we need to bail out.
		TPTP_LOG_ERROR_MSG(ProcessControllerAgent, "ProcessController: Error - Exiting, failed to create synch event for applicationAliases.");
		return -1;
	}

	// Create semaphore to tell the startProcesses() that a startProcess cmd
	// has arrived in the msg handling thread. The msg handling thread will
	// store the process launch info in the startProcessList prior to posting
	// the semaphore.
	rc = tptp_initializeSemaphore(&startProcessCmdSem); //TODO:destroy this semaphore
	if ( rc )
	{
		// If we can't create a synch object, we need to bail out.
		TPTP_LOG_ERROR_MSG(ProcessControllerAgent, "ProcessController: Error - Exiting, failed to create synch event for StartProcessCmd.");
		return -1;
	}

	// Create semaphore to tell the startProcesses() thread that the response cmd to
	// a CONSOLE_CONNECT request has arrived. The msg handling thread will
	// set the boolean 'consoleConnected' prior to posting the semaphore.
	// TRUE if a successful reply came in (i.e., CONSOLE_CONNECT_COMPLETE) or
	// FALSE if a failure reply came in (i.e., CONSOLE_CONNECT_FAILED).
	rc = tptp_initializeSemaphore(&consoleConnectResponseSem); //TODO:destroy this semaphore
	if ( rc )
	{
		// If we can't create a synch object, we need to bail out.
		TPTP_LOG_ERROR_MSG(ProcessControllerAgent, "ProcessController: Error - Exiting, failed to create synch event for ConsoleConnectResponse.");
		return -1;
	}

	ProcessControllerAgent->processCommandLine(argc, argv);


	//Start thread which will create processes in response to startProcess cmds.
	TPTP_LOG_DEBUG_MSG(ProcessControllerAgent, "ProcessController: Starting the startProcesses thread...");
	rc = startNewThread(startProcesses, (void *)ProcessControllerAgent, &myThreadIDs[0], &myThreadHandles[0]);
	if ( rc )
	{
		TPTP_LOG_ERROR_MSG(ProcessControllerAgent, "ProcessController: Error - Exiting, failed to create thread startProcesses");
		return -1;
	}

	//The BaseAgentImpl for registerAgent does the following:
	//	connects with AC
	//	sets up private named pipe for AC to send msgs to this agent
	//	starts the msg handling thread which listens on the pipe and
	//		calls this agent's processCommand.
	TPTP_LOG_DEBUG_MSG(ProcessControllerAgent, "ProcessController: Calling registerAgent.");
	rc = ProcessControllerAgent->registerAgent();
	if ( rc )
	{
		// If register fails, we don't have our incoming cmd handling thread.
		TPTP_LOG_ERROR_MSG(ProcessControllerAgent, "ProcessController: Error - Exiting, failed to register with AC");
		return -1;
	}
	myThreadHandles[1] = ProcessControllerAgent->getMsgHandlerThreadHandle();
	myThreadIDs[1] = ProcessControllerAgent->getMsgHandlerThreadID();

	//Start a thread to wait for any events coming in for the processes
	//that have previously been started by startProcess cmds.
	//Anyone that registered to get process events will be sent event commands.
	TPTP_LOG_DEBUG_MSG(ProcessControllerAgent, "ProcessController: Starting the monitorProcessEvents thread...");
	rc = startNewThread(monitorProcessEvents, (void *)ProcessControllerAgent, &myThreadIDs[2], &myThreadHandles[2]);
	if ( rc )
	{
		TPTP_LOG_ERROR_MSG(ProcessControllerAgent, "ProcessController: Error - Exiting, failed to create thread monitorProcessEvents");
		return -1;
	}

#ifdef _WIN32

	// Main is done, wait until a child thread returns and do any cleanup.
	// Note: These error messages assume a particular order in which the threads
	// were stored in the list.
	unsigned long waitRet = WaitForMultipleObjects(3, myThreadHandles, FALSE, INFINITE);
	if (waitRet == WAIT_OBJECT_0)
	{
		TPTP_LOG_ERROR_MSG(ProcessControllerAgent, "ProcessController: EXITING - startProcesses thread has terminated");
	}
	else if (waitRet == WAIT_OBJECT_0+1)
	{
		TPTP_LOG_ERROR_MSG(ProcessControllerAgent, "ProcessController: EXITING - handleMessages thread has terminated");
	}
	else if (waitRet == WAIT_OBJECT_0+2)
	{
		TPTP_LOG_ERROR_MSG(ProcessControllerAgent, "ProcessController: EXITING - monitorProcessEvents thread has terminated");
	} else
	{
		TPTP_LOG_ERROR_MSG(ProcessControllerAgent, "ProcessController: EXITING - Error in wait for threads");
	}
#else
	// TODO: Should wait on all threads, need to figure out how to do this on Linux.
	// For now, just wait on the handleMessages thread so that we bail out if the AC
	// breaks the communication channel it has with the PC.

	status = (int *)tptp_malloc(sizeof(int));
	rc = pthread_join(myThreadIDs[1], (void **)(&status));
	if ( rc != 0 )
	{
		TPTP_LOG_ERROR_MSG(ProcessControllerAgent, "ProcessController: EXITING - Error in wait for handleMessages thread");
	}
	else
	{
		TPTP_LOG_ERROR_MSG(ProcessControllerAgent, "ProcessController: EXITING - handleMessages thread has terminated");
	}
	
	#ifdef MVS
		pthread_cancel(myThreadIDs[0]);
	#else
		pthread_kill(myThreadIDs[0], SIGKILL);
	#endif
	pthread_kill(myThreadIDs[2], SIGKILL);
#endif
	// Sleep(50000); // For debug
	//

	//Clean up the agents that we started.
	//This routine does not terminate the agents that requested
	//to keep alive.
	ProcessControllerAgent->terminateAllChildProcesses();

	TPTP_LOG_DEBUG_MSG(ProcessControllerAgent, "Process Controller EXITING");

	#ifdef MVS
		rc = tptp_deleteSemaphore(&applAliasesResponseSem); 
		rc = tptp_deleteSemaphore(&startProcessCmdSem);
		rc = tptp_deleteSemaphore(&consoleConnectResponseSem); 
		tptp_deleteSemaphore(ProcessControllerAgent->getTerminationSemaphore());
	#endif


	return -1;
}
