/*******************************************************************************
 * Copyright (c) 2005 Intel Corporation.
 * 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
 *
 * $Id: ProcessController_md.cpp,v 1.9 2005/10/28 19:48:51 kcallaghan Exp $ 
 *******************************************************************************/ 

#include "TPTPProcessController.h"
#include "tptp/TPTPCommon.h"
#include "tptp/TPTPSupportUtils.h"
#include "tptp/agents/AgentLog.h"

#ifndef _WIN32
#include <sys/types.h>
#include <sys/wait.h>
#endif

#ifdef _WIN32
///////////////////////////////////////////////////
//
// int ProcessController::findRunningProcessByHandle(TPTP_HANDLE hProc, tptp_process_t** proc)
//
///////////////////////////////////////////////////

int ProcessController::findRunningProcessByHandle(const TPTP_HANDLE hProc,
												  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->hProcess == hProc  )
		{
			*proc = cloneProcessT(tmpProc);
			tptp_releaseReadLock(&runningProcessListLock);
			return 0;
		}
	}
	tptp_releaseReadLock(&runningProcessListLock);

	return -1;
}

///////////////////////////////////////////////////
//
// void ProcessController::closeRunningProcessHandles( void )
//
///////////////////////////////////////////////////

void ProcessController::closeRunningProcessHandles( void )
{
	tptp_node_t*    node;
	tptp_process_t* proc;

	if (runningProcessList.count == 0)
		return;

	tptp_getWriteLock(&runningProcessListLock);
	for (node = runningProcessList.head; node != 0; node = node->next )
	{
		proc = (tptp_process_t*)node->data;
		CLOSE_TPTP_HANDLE(proc->hProcess);
		proc->hProcess = INVALID_HANDLE_VALUE;
	}
	tptp_releaseWriteLock(&runningProcessListLock);
	return;

}

///////////////////////////////////////////////////
//
// long ProcessController::getRunningProcessHandleList(TPTP_HANDLE* &procHandles)
//
// NOTE: Only the handleProcessEvents() function is expected to call on
// getRunningProcessHandleList().
//
// This function should NOT be called by just anyone because it has the
// side effect of reseting the ProcessListChanged event, which is used to
// sync with the thread that causes processes to be started/stopped and thus
// added to or deleted from the runningProcessList.
//
// If this functionality is needed for general use in the future, just need
// to add a parameter that indicates if the reset event should not happen.
//
///////////////////////////////////////////////////

long ProcessController::getRunningProcessHandleList(TPTP_HANDLE* &procHandles)
{
	TPTP_HANDLE*    procHandleList;
	tptp_node_t*    node;
	tptp_process_t* proc;

	tptp_getReadLock(&runningProcessListLock);
	if (runningProcessList.count == 0)
	{
		ResetEvent(hProcListChangedEvent);
		tptp_releaseReadLock(&runningProcessListLock);
		return 0;
	}

	procHandleList = (TPTP_HANDLE*) tptp_malloc(sizeof(TPTP_HANDLE) * runningProcessList.count);
	if (!procHandleList)
	{
		//TODO: Internal Error, insufficient memory
		tptp_releaseReadLock(&runningProcessListLock);
		return -1;
	}

	long i=0;
	for (node = runningProcessList.head; node != 0; node = node->next )
	{
		proc = (tptp_process_t*)node->data;
		procHandleList[i] = proc->hProcess;
		i++;
	}

	// Now that we have the new list of processes, we can reset the event.
	// The reset must occur prior to releasing the read lock on the list.
	// This ensures that a write to the list doesn't occur between reading the list
	// and resetting the event.
	ResetEvent(hProcListChangedEvent);
	tptp_releaseReadLock(&runningProcessListLock);

	procHandles = procHandleList;

	return i; //i should be equal to runningProcessList.count
}

///////////////////////////////////////////////////
//
// void ProcessController::handleProcessEvents_win32( void )
//
// Waits on a list of processes which the PC started or was
// asked to monitor - plus a semaphore which will indicate if
// the process list changed and thus needs to be updated.
//
// If a process terminates, we come out of the wait, identify
// which process and send event notifications.
//
// If the running process list changed event brings us out of the
// wait, we simply need to get a new list of process handles and then
// go wait again.
//
///////////////////////////////////////////////////

void ProcessController::handleProcessEvents_win32( void )
{
	unsigned long exitStatus = 0;
	TPTP_HANDLE* procHandles = 0;
	TPTP_HANDLE* objHandles = 0;
	long numProcs = 0;
	unsigned long waitRet = 0;
	unsigned long waitTime;
	tptp_process_t* proc;

	if (!hProcListChangedEvent)
	{
		// If we did not create a sync object, we need to bail out.
		TPTP_LOG_ERROR_MSG(this, "Error - Exiting, failed to create synch event for ProcessListChanged.");
		return;
	}

	while (1)
	{
		// numRunningProcesses() gives you a process count for a moment in time.
		// It may not match the number of procs returned by the getProcessHandleList()
		// which gets called a little later.
		// Its used to decide how long to wait the first time through the loop which
		// gets the list of processes. Subsequently, the count returned by
		// getProcessHandleList() is used because that method syncs up with the
		// ProcessListChanged event.
		numProcs = numRunningProcesses();
		TPTP_LOG_DEBUG_MSG1(this, "PC handleProcessEvents: # of running procs = %d", numProcs);

		// Get list of processes to wait on.
		do
		{
			// How long we wait for the ProcessListChangedEvent depends on whether
			// we have any running processes.
			// If there are no running processes, wait forever (i.e., until a process gets added).
			// If there are running processes, just check if we need a new list before
			// getting back to monitoring the processes.
			if (numProcs > 0)
				waitTime = 0;
			else
				waitTime = INFINITE;
			// TPTP_LOG_DEBUG_MSG1(this, "PC handleProcessEvents: Waiting for process list (waitTime=%d)",waitTime);
			waitRet=WaitForSingleObject(hProcListChangedEvent, waitTime);
			if (waitRet == WAIT_OBJECT_0)  //Event is signaled, so list has changed
			{
				// Cleanup previous handle lists	
				if (procHandles)
				{
					tptp_free(procHandles);
					procHandles=0;
				}
				if (objHandles)
				{
					tptp_free(objHandles);
					objHandles=0;
				}

				// Getting the new list causes the event to be reset.
				numProcs = getRunningProcessHandleList(procHandles);
				TPTP_LOG_DEBUG_MSG1(this, "PC handleProcessEvents: got new list of running procs, numProcs = %d", numProcs);
				if (numProcs == -1)
				{
					TPTP_LOG_ERROR_MSG(this, "PC handleProcessEvents: Error - could not get running process list");
					return;
				}
				if (numProcs == 0)
				{
					// Empty process list, go wait for something to be added.
					continue;
				}

				// Create a list of handles so that the ProcessListChanged event is included
				// in the wait along with the list of processes that are currently running.
				objHandles = (TPTP_HANDLE*) tptp_malloc((numProcs +1) * sizeof(TPTP_HANDLE));
				if (!objHandles)
				{
					TPTP_LOG_ERROR_MSG(this, "PC handleProcessEvents: Error - alloc failed for Wait objects");
					return;
				}
				for (int i=0; i<numProcs; i++) 
				{
					objHandles[i] = procHandles[i];
				}
				objHandles[numProcs] = hProcListChangedEvent;

			}
			else if (waitRet == WAIT_TIMEOUT)
			{
				TPTP_LOG_DEBUG_MSG(this, "PC handleProcessEvents: Process list hasn't changed, using the old one.");
			}
			else
			{
				if (waitRet == WAIT_FAILED)
				{
					TPTP_LOG_ERROR_MSG1(this, "PC handleProcessEvents: Error - Failure waiting for list changed event (err:0x%x)", GetLastError());
				}
				//Any other error means the sync event isn't functioning, bail out.
				TPTP_LOG_ERROR_MSG(this, "PC handleProcessEvents: Error - Exiting due to internal synch event error.");
				return;
			}
			
		} while( numProcs <= 0 );

		waitRet=WaitForMultipleObjects(numProcs+1, objHandles, FALSE, INFINITE); 
		if (waitRet == WAIT_FAILED)
		{
			//TODO: Figure out what causes us to hit this condition.
			//TPTP_LOG_ERROR_MSG1(this, "PC handleProcessEvents: Error - Failure waiting for process events (err:0x%x)", GetLastError());
			continue;
		}

		// We were released from the wait for one or both of the following reasons:
		// A) the process list changed (i.e., ProcessListChangeEvent was signaled)
		// B) one or more processes had a state change (i.e. 1 or more procs were signaled, stopped/terminated)
		//
		// First check for any process handles that were signaled, these are
		// found in the first "numProc" elements of the objHandles list.
		// The Wait function's return value gives you the index of the first item
		// in the list that was signaled, but there can be more than one. So starting
		// with the index returned, check the status of each process.
		//
		// The ProcessListChanged event won't be dealt with until we loop back
		// up in the outter while loop.
		long index = waitRet - WAIT_OBJECT_0; //index of first obj in the list which was signaled		
		while (index>=0 && index<numProcs)
		{
			if ( GetExitCodeProcess(procHandles[index],&exitStatus) ) // successful
			{
				TPTP_LOG_ERROR_MSG1(this, "PC handleProcessEvents: Process has exited with status %lu.", exitStatus);

				if (exitStatus != STILL_ACTIVE)
				{
					//Event listeners are established by prior requests.
					//Must send an event notification cmd to each registered listener.
					if (findRunningProcessByHandle(procHandles[index], &proc) == 0)
					{
						TPTP_LOG_DEBUG_MSG1(this, "PC handleProcessEvents: Found the exited process %lu in our list.", proc->pid);
						sendEventNotifications(proc->pid, exitStatus);

						// Update the runningProcessList (i.e.,remove this dead process)
						removeRunningProcess(proc->pid);
					}
					else
					{
						// Having a process handle that does not correspond to a pid in
						// our running process list will only happen if our process list
						// has gotten corrupted.
						TPTP_LOG_DEBUG_MSG1(this, "PC handleProcessEvents: Failed to find PID %lu using our process handle", proc->pid);
					}
				}
				else
				{
					// Don't know how we would have gotten an event for a process
					// that is still running, but in case something weird happens,
					// we'll log a msg.
					TPTP_LOG_DEBUG_MSG1(this, "PC handleProcessEvents: Recv'd event for active process PID %lu.", proc->pid);
				}
			} 
			else
			{
				// Couldn't get the exit status for a process.
				// TODO: Report an error (?remove the process from the list?)
				TPTP_LOG_ERROR_MSG(this, "PC handleProcessEvents: Failed to get exit status of signaled process.");
				//printCurrentSysError() ;
			}

			index++;
		} //end while

		// If the ProcessListChangedEvent was signaled, leave it in the signaled
		// state, as we will now loop back to the top of our infinite while loop
		// and Wait on that specific event.

	} //end while(1)
	return;
}

#else  //Non-Windows process event handling

///////////////////////////////////////////////////
//
// void ProcessController::handleChildExited(PID pid, int status)
//
///////////////////////////////////////////////////
void ProcessController::handleChildExited(PID pid, int status)
{
	unsigned long exitStatus = 0;

	// A process has terminated or has been signaled such that it
	// is about to terminate.
	if (WIFEXITED(status))
		exitStatus = WEXITSTATUS(status);
	else if (WIFSIGNALED(status))
		exitStatus = WTERMSIG(status); //TODO: NEED to distinguish sig from exit code
	else if (WIFSTOPPED(status))
		exitStatus = WSTOPSIG(status);//TODO: DO we care/report stopped processes?
	else
	{
		//TODO: Should never get here - log an error
		exitStatus = 0; //TODO: Need an UNKNOWN exit status
	}

	sendEventNotifications(pid, exitStatus);
	// Update the runningProcessList, remove this dead process
	removeRunningProcess(pid);			
}

/* AK -- Linux is now using a sighandler to get child process closed events
///////////////////////////////////////////////////
//
// void ProcessController::handleProcessEvents_linux( void )
//
///////////////////////////////////////////////////

void ProcessController::handleProcessEvents_linux( void )
{
	unsigned long exitStatus = 0;
	PID pid;
	int status = 0;
	
	return;

	while (1)
	{
		if (numRunningProcesses() > 0)
		{
			// Check if any process we launched (i.e. child process) has been terminated.
			// so that we can inform any listeners and update our process list.
			pid = waitpid(-1, &status, WNOHANG | WUNTRACED);
			if (pid == -1) {
				//TODO: Handle Error
			}
			else if (pid != 0)
			{
				// A process has terminated or has been signaled such that it
				// is about to terminate.
				if (WIFEXITED(status))
					exitStatus = WEXITSTATUS(status);
				else if (WIFSIGNALED(status))
					exitStatus = WTERMSIG(status); //TODO: NEED to distinguish sig from exit code
				else if (WIFSTOPPED(status))
					exitStatus = WSTOPSIG(status);//TODO: DO we care/report stopped processes?
				else
				{
					//TODO: Should never get here - log an error
					exitStatus = 0; //TODO: Need an UNKNOWN exit status
				}

				sendEventNotifications(pid, exitStatus);
				// Update the runningProcessList, remove this dead process
				removeRunningProcess(pid);			
			}
		}

	} //end while(1)
	return;
}
*/
#endif

