// -----------------------------------------------------------------------------
// Copyright (c) 1995      Southeastern Universities Research Association,
//                         Continuous Electron Beam Accelerator Facility
//
// This software was developed under a United States Government license
// described in the NOTICE file included as part of this distribution.
//
// -----------------------------------------------------------------------------
//
// Description:
//	This header file contains the class definitions for the classes
//	associated with the construction of a script service.
//
// Author:  Walt Akers
//
// Revision History:
//   ScriptService.cc,v
// Revision 1.3  1997/02/14  20:43:41  akers
// Ongoing improvement
//
// Revision 1.2  1997/02/14  20:06:32  akers
// Ongoing improvement
//
// Revision 1.1  1997/02/11  17:37:27  akers
// Directory restructure
//
// Revision 1.3  1997/01/31  18:51:27  akers
// Ongoing development
//
// Revision 1.2  1997/01/31  16:21:24  akers
// Ongoing development
//
// Revision 1.1  1997/01/30  20:35:35  akers
// Initial installation of Script Service
//
// -----------------------------------------------------------------------------

#include <signal.h>

#include "ScriptService.h"
#include "cdevDirectory.h"
#include "cdevClock.h"
#include "ScriptList.h"

cdevSelector ScriptList::selector;

// ****************************************************************************
// * This function is an interrupt handler that will be executed whenever the
// * program receives a SIGCHLD signal. When called it will execute the poll 
// * method of the default server, in order to remove any dead Script Service
// * processes.
// ****************************************************************************
static void SIGCHLD_handler (int)
	{
	ScriptList::selector.insertEvent();
	}


// *********************************************************************
// * newScriptService :
// *	This method is called by the cdevSystem object to load the 
// *	initial instance of the ScriptService.
// *********************************************************************
cdevService * newScriptService ( char * name, cdevSystem * system )
	{
	signal(SIGCHLD, SIGCHLD_handler);
	return new ScriptService(name, *system);	
	}


// *********************************************************************
// * ScriptService::ScriptService :
// *	This is the constructor for the script service.  It determines
// *	the tag number for the "filename" tag if it does not already
// *	exist and allocates the ScriptList object that will be used
// *	to hold the individual script instances.
// *********************************************************************
ScriptService::ScriptService ( char * name, cdevSystem & system ) 
	: cdevService(name, system), 
	  FILENAME_TAG(1000),
	  asyncCallback(asyncCallbackFunc, NULL)
	{
	scripts = new ScriptList;
	
	while(cdevData::tagC2I("filename", &FILENAME_TAG)!=CDEV_SUCCESS)
		{
		cdevData::insertTag(++FILENAME_TAG, "filename");
		}
	}
	
// *********************************************************************
// * ScriptService::~ScriptService :
// *	This is the destructor for the script service,it does nothing
// *	but act as a placeholder for the virtual destructor.
// *********************************************************************
ScriptService::~ScriptService ( void )
	{
	}
	
// *********************************************************************
// * ScriptService::getRequestObject :
// *	This is the interface that cdev objects will use to obtain a
// *	ScriptRequestObject object.  The ScriptRequestObject 
// *	represents a combined device and message pair that is associated 
// *	with the ScriptService.
// *
// *	Returns CDEV_SUCCESS on success or CDEV_ERROR on error.
// *********************************************************************
int ScriptService::getRequestObject ( char * device, char * message, cdevRequestObject * &req)
	{
	req = new ScriptRequestObject (device, message, system_);
	return (req ? CDEV_SUCCESS : CDEV_ERROR);
	}

// *****************************************************************************
// * ScriptService::getFd
// *	This function will return the list of file descriptors that the
// *	ScriptService is using.  This will allow the file descriptors to be 
// *	used  in global select and poll calls performed at the cdevSystem class 
// *	level.
// *
// *	Returns CDEV_SUCCESS on success or CDEV_ERROR on error.
// *****************************************************************************
int ScriptService::getFd ( int * &fd, int & numFd )
	{
	fd    = scripts->fdList;
	numFd = scripts->fdCount;

	return CDEV_SUCCESS;	
	}


// *********************************************************************
// * ScriptService::flush :
// *	This function flushes all communications buffers that the 
// *	service may have open.
// *
// *	Returns CDEV_SUCCESS on success or CDEV_ERROR on error.
// *********************************************************************
int ScriptService::flush ( void )
	{
	return CDEV_SUCCESS;
	}


// *****************************************************************************
// * ScriptService::poll :
// *	This function polls the file descriptors used by the service
// *	until one of them becomes active or a discrete amount of time
// *	has expired.
// *
// *	Returns CDEV_SUCCESS on success or CDEV_ERROR on error.
// *****************************************************************************
int ScriptService::poll ( void )
	{
	if(scripts->fdCount>0) scripts->poll();

	return CDEV_SUCCESS;
	}
	


// *****************************************************************************
// * ScriptService::pend :
// *	Pends until the named file descriptor (or any file descriptor
// *	if fd =  -1) is ready.  Will pend forever if the descriptor does
// *	not become active.
// *
// *	Returns CDEV_SUCCESS on success or CDEV_ERROR on error.
// *****************************************************************************
int ScriptService::pend ( int )
	{	
	while(scripts->fdCount>0 && scripts->poll()==0);

	return CDEV_SUCCESS;
	}



// *****************************************************************************
// * ScriptService::pend :
// *	Pends until the named file descriptor (or any file descriptor
// *	if fd = -1) is ready.  Will pend for no longer than the user
// *	specified number of seconds.
// *
// *	Returns CDEV_SUCCESS on success or CDEV_ERROR on error.
// *****************************************************************************
int ScriptService::pend ( double seconds, int )
	{	
	if(scripts->fdCount > 0)
		{
		int           nHandled = 0;
		cdevTimeValue sec      = seconds;
		cdevClock     timer;
	
		timer.schedule(NULL, sec);
	
		do	{
			nHandled = scripts->poll();
			} while(!nHandled        && 
			        !timer.expired() && 
			        scripts->fdCount>0);
		}
	return CDEV_SUCCESS;
	}
	

// *****************************************************************************
// * ScriptService::submit :
// *	This is the mechanism that the request object will use to submit a
// *	message to the service.  It is important to note that all of the 
// *	data provided to this object becomes the property of the service and
// *	must not be accessed afterwords.
// *****************************************************************************
int ScriptService::submit ( cdevTranObj &xobj, cdevData &data )
	{
	int status = CDEV_SUCCESS;

	// *********************************************************************
	// * Attempt to read the script filename from the DDL file.
	// *********************************************************************
	ScriptData *script = new ScriptData(xobj);
	cdevData    temp;
	char        DDL_request[256];

  	sprintf (DDL_request, 
  		 "resolveServiceData %s %s", 
  		 xobj.reqObj_->device().name(), 
  		 xobj.reqObj_->message());

  	if((system_.nameServer()).send(DDL_request, NULL, &temp)==CDEV_SUCCESS)
	  	{
		*DDL_request = 0;
		temp.get (FILENAME_TAG, DDL_request, 256);
		}
	else *DDL_request = 0;

	// *********************************************************************
	// * If the script file was loaded, then begin the process of creating
	// * a pipe to retrieve the output of the script, and forking off an 
	// * additional process to handle execution.
	// *********************************************************************
	if(*DDL_request)
		{
		char * parms  = NULL;
		int    bufLen = 0;
				
		// *************************************************************
		// * Extract the data from the user provided output cdevData 
		// * object into a string that can be passed to the script.
		// *************************************************************
		ScriptData::data2Buffer(data, parms, bufLen);
				
		// *************************************************************
		// * Make a copy standard output for later use to restore the
		// * standard output file descriptor in the main thread...
		// * This is because the behaviour of vfork on some systems
		// * will leave the file descriptor in the main thread modified.
		// *************************************************************
		int fd = dup(1);
				
		// *************************************************************
		// * Set the standard output file descriptor to the write file 
		// * descriptor of the socket pair.
		// *************************************************************
		dup2(script->sp.writeFD, 1);

		// *************************************************************
		// * Fork the process to allow the user application to continue
		// * running in the original branch, and the script to be 
		// * executed in the new (child) process.
		// *************************************************************
		switch((script->process = vfork()))
			{
			// *****************************************************
			// * 0 Indicates that we are in the new child process.
			// *****************************************************
			case 0:	
				{
				if(bufLen)
					{
					execl(DDL_request, 
					      DDL_request,
					      xobj.reqObj_->device().name(), 
					      xobj.reqObj_->message(), 
					      parms, NULL);
					}
				else	{
					execl(DDL_request, 
					      DDL_request,
					      xobj.reqObj_->device().name(), 
					      xobj.reqObj_->message(), 
					      NULL);
					}
				exit(-1);
				}
			break;
			
			// *****************************************************
			// * -1 Indicates that a new process could not be 
			// * forked.
			// *****************************************************
			case -1:
				{
				// *********************************************
				// * Restore the standard output file descriptor
				// *********************************************
				dup2(fd, 1);
				close(fd);
			
				system_.reportError(
					CDEV_SEVERITY_ERROR, 
					"ScriptService", 
					xobj.reqObj_,
					"Failed to fork a new process");

				script->fireCallback(CDEV_ERROR, 
						     xobj.userCallback_->userarg(),
						    *xobj.reqObj_,
						    *script->data);

				status = CDEV_ERROR;
				delete script;
				}
			break;
				
			// *****************************************************
			// * Any other value indicates that we are in the parent 
			// * and the value is the process ID of the new child.
			// *****************************************************
			default:
				{
				// *********************************************
				// * Restore the standard output file descriptor
				// *********************************************
				dup2(fd, 1);
				close(fd);

				// *********************************************
				// * Insert the script into the list.
				// *********************************************
				scripts->insert(*script);
				}
			break;
			}

		// *************************************************************
		// * Delete parameters (if any).
		// *************************************************************
		if(bufLen) delete parms;
		}
	else	{
		system_.reportError(
			CDEV_SEVERITY_ERROR, 
			"ScriptService", 
			xobj.reqObj_,
			"No filename specified for request %s %s",
			xobj.reqObj_->device().name(), 
			xobj.reqObj_->message());
			
		script->fireCallback(CDEV_ERROR, 
				     xobj.userCallback_->userarg(),
				    *xobj.reqObj_,
				    *script->data);
		status = CDEV_ERROR;
		delete script;
		}

	return status;
	}

// *****************************************************************************
// * ScriptService::getNameServer :
// *	This function should obtain the default name server for this object.
// *	It does nothing for now.
// *****************************************************************************
int ScriptService::getNameServer(cdevDevice * &ns)
	{
	ns = 0;
	return CDEV_SUCCESS;
	}
