// -----------------------------------------------------------------------------
// 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:
//   ScriptList.cc,v
// Revision 1.3  1997/02/14  20:43:40  akers
// Ongoing improvement
//
// Revision 1.2  1997/02/14  20:06:32  akers
// Ongoing improvement
//
// Revision 1.1  1997/02/11  17:37:22  akers
// Directory restructure
//
// Revision 1.7  1997/02/10  20:31:23  akers
// Ongoing improvement
//
// Revision 1.6  1997/02/07  16:19:43  akers
// Ongoing development
//
// Revision 1.5  1997/02/06  20:27:48  akers
// Ongoing development
//
// Revision 1.4  1997/01/31  21:09:58  akers
// Ongoing Development
//
// Revision 1.3  1997/01/31  18:51:26  akers
// Ongoing development
//
// Revision 1.2  1997/01/31  16:21:22  akers
// Ongoing development
//
// Revision 1.1  1997/01/30  20:35:33  akers
// Initial installation of Script Service
//
// -----------------------------------------------------------------------------
#include <ScriptList.h>

// *****************************************************************************
// * ScriptData::ScriptData :
// *	Constructor for the class...
// *****************************************************************************
ScriptData::ScriptData ( cdevTranObj & Xobj ) 
	: next(NULL), data(new cdevData), 
	  xobj(&Xobj), inputBuf(NULL), 
	  inputBufLen(0), amntRead(0),
	  process(0), finished(0), callbackCount(0)
	{
	// *********************************************************************
	// * Attempt to create the pipe that will be used to return
	// * the result to the class. 
	// *********************************************************************
	if(pipe((int *)&sp)!=0)
		{
		sp.readFD  = -1;
		sp.writeFD = -1;
		}
	}

// *****************************************************************************
// * ScriptData::data2Buffer :
// *	This method is used to convert a cdevData object into a
// *	series of strings that can be used by the script.
// *****************************************************************************
int ScriptData::data2Buffer ( cdevData & data, char *& buf, int &bufLen)
	{
	cdevDataIterator iter(&data);
	int    i;
	int    idx = 0;
	struct DataPack
		{
		char       * tag;
		char      ** ptr;
		double     * dPtr;
		size_t       ptrCnt;
		int          quoted;
		cdevBounds * bounds;
		size_t       boundsCnt;
		} dataPack[256]; 
	
	memset(dataPack, 0, sizeof(dataPack));
	
	// *********************************************************************
	// * Iterate through the cdevData object and collect the data 
	// * that is stored into an array of character strings.
	// *********************************************************************
	for(bufLen=0, iter.init(); !iter && idx<256; idx++)
		{
		// *************************************************************
		// * Get the tag string and add the length of that plus
		// * the length of a seperating equals sign (=) to the
		// * buffer length.
		// *************************************************************
		data.tagI2C(iter.tag(), dataPack[idx].tag);
		bufLen+=strlen(dataPack[idx].tag)+2;

		// *************************************************************
		// * Get the number of elements in the array.
		// *************************************************************
		data.getElems(dataPack[idx].tag, &dataPack[idx].ptrCnt);

		// *************************************************************
		// * If the data object is a string, add two times the
		// * number of elements to the buffer length (to account
		// * for double quotes around each entry.)
		// *************************************************************
		if(data.getType(dataPack[idx].tag)==CDEV_STRING ||
		   data.getType(dataPack[idx].tag)==CDEV_TIMESTAMP) 
			{
			dataPack[idx].quoted = 1;
			bufLen+=(2*dataPack[idx].ptrCnt);
			
			// *****************************************************
			// * Allocate an array of character strings to 
			// * be used to extract the array as a 
			// * collection of strings and increase the 
			// * buffer length to accomodate all of the 
			// * items.
			// *****************************************************
			dataPack[idx].ptr = new char * [dataPack[idx].ptrCnt];
			data.get(dataPack[idx].tag, dataPack[idx].ptr);
			for(i=0; i<dataPack[idx].ptrCnt; i++)
				{
				bufLen+=strlen(dataPack[idx].ptr[i]);
				}
			}
		else
			{
			// *****************************************************
			// * Allocate an array of doubles to 
			// * be used to extract the array as a 
			// * collection and increase the buffer length
			// * to accomodate all of the items.
			// *****************************************************
			dataPack[idx].dPtr = new double [dataPack[idx].ptrCnt];
			data.get(dataPack[idx].tag, dataPack[idx].dPtr);
			bufLen += 21*dataPack[idx].ptrCnt;
			}
		// *************************************************************
		// * Add 1 character for each entry to account for
		// * commas between entries.
		// *************************************************************
		bufLen += dataPack[idx].ptrCnt;

		// *************************************************************
		// * Get the number of dimensions in the tagged data
		// * item.
		// *************************************************************
		data.getDim(dataPack[idx].tag, &dataPack[idx].boundsCnt);
		
		// *************************************************************
		// * If the data is an array or vector, then calculate
		// * the amount of space that will have to be used
		// * to store the separating brace characters (and commas).
		// *************************************************************
		if(dataPack[idx].boundsCnt!=0)
			{
			int braceCount = 1;
			
			dataPack[idx].bounds = new cdevBounds[dataPack[idx].boundsCnt];
			data.getBounds(dataPack[idx].tag, dataPack[idx].bounds, dataPack[idx].boundsCnt);
			
			for(i=0; i<dataPack[idx].boundsCnt-1; i++) 
				{
				if(i==0) braceCount += 
					 dataPack[idx].bounds[i].length;
				else     braceCount += 
					 dataPack[idx].bounds[i-1].length*
					 dataPack[idx].bounds[i].length;
				}
			bufLen += braceCount*3;
			}
		
		// *************************************************************
		// * Add space for the separating carriage return.
		// *************************************************************
		bufLen+=1;
		
		// *************************************************************
		// * Move to the next item in the list.
		// *************************************************************
		++iter;
		}
	
	// *********************************************************************
	// * Allocate a buffer of sufficient size to hold the result
	// * plus a NULL terminating character and an opening and closing 
	// * single quote.
	// *********************************************************************
	if(idx) {
		buf  = new char [bufLen+1+2];
		*buf = 0;
		}
	else	{
		buf = NULL;
		bufLen = 0;
		}
		
	// *********************************************************************
	// * Walk through each item in the list and place it in the 
	// * data buffer.
	// *********************************************************************
	int max = idx;
	for(idx=0; idx<max; idx++)
		{
		char * ptr = buf+strlen(buf);
		sprintf(ptr, "%s=", dataPack[idx].tag);
		
		if(!dataPack[idx].boundsCnt)
			{
			if(dataPack[idx].quoted) 
				sprintf(ptr+strlen(ptr), "\"%s\"\n", dataPack[idx].ptr[0]);
			else    sprintf(ptr+strlen(ptr), "%g\n", dataPack[idx].dPtr[0]);
			}
		else	{
			size_t ptrIdx = 0;
			strcat(ptr, "{");
			if(dataPack[idx].quoted)
				{
				dataArray2Buffer(ptr+strlen(ptr), 
			                dataPack[idx].ptr,
			                ptrIdx, 
				        dataPack[idx].bounds, 
				        dataPack[idx].boundsCnt,
				        0);
				}
			else
				{
				dataArray2Buffer(ptr+strlen(ptr), 
			                dataPack[idx].dPtr,
			                ptrIdx, 
				        dataPack[idx].bounds, 
				        dataPack[idx].boundsCnt,
				        0);
				}
			strcat(ptr, "}\n");
			}

		// *************************************************************
		// * Delete the allocated contents of the dataPack once
		// * it is no longer needed.
		// *************************************************************
		if(dataPack[idx].quoted)
			{
			for(int j=0; j<dataPack[idx].ptrCnt; j++) 
				{
				delete dataPack[idx].ptr[j];
				}
			delete dataPack[idx].ptr;
			}
		else delete dataPack[idx].dPtr;
		delete dataPack[idx].bounds;
		}
	return CDEV_SUCCESS;
	}

// *****************************************************************************
// * ScriptList::dataArray2Buffer :
// *	This is a method that is called recursively to dump the contents
// *	of a string array to the buffer.
// *****************************************************************************
void ScriptData::dataArray2Buffer (char       * output, 
				   char      ** ptr, 
				   size_t     & ptrIdx, 	
				   cdevBounds * bounds, 
				   size_t       nBounds, 
				   size_t       boundsIdx)
	{
	for(int i=0; i<bounds[boundsIdx].length; i++)
		{
		if(boundsIdx<nBounds-1)
			{
			strcat(output, "{");
			dataArray2Buffer(output+strlen(output), ptr, ptrIdx, bounds, nBounds, boundsIdx+1);
			if(i+1<bounds[boundsIdx].length) strcat(output, "},");
			else strcat(output, "}");
			}
		else	{
			output+=strlen(output);
			if(i+1<bounds[boundsIdx].length) sprintf(output, "\"%s\",", ptr[ptrIdx++]);
			else sprintf(output, "\"%s\"", ptr[ptrIdx++]);
			}
		}
	}

// *****************************************************************************
// * ScriptList::dataArray2Buffer :
// *	This is a method that is called recursively to dump the contents
// *	of a double array to the buffer.
// *****************************************************************************
void ScriptData::dataArray2Buffer (char       * output, 
				   double     * ptr, 
				   size_t     & ptrIdx, 
				   cdevBounds * bounds, 
				   size_t       nBounds, 
				   size_t       boundsIdx)
	{
	for(int i=0; i<bounds[boundsIdx].length; i++)
		{
		if(boundsIdx<nBounds-1)
			{
			strcat(output, "{");
			dataArray2Buffer(output+strlen(output), ptr, ptrIdx, bounds, nBounds, boundsIdx+1);
			if(i+1<bounds[boundsIdx].length) strcat(output, "},");
			else strcat(output, "}");
			}
		else	{
			output+=strlen(output);
			if(i+1<bounds[boundsIdx].length) sprintf(output, "%g,", ptr[ptrIdx++]);
			else sprintf(output, "%g", ptr[ptrIdx++]);
			}
		}
	}

// *****************************************************************************
// * ScriptData::buffer2Data :
// *	This method allows the ScriptData class to read the inbound 
// *	results of the script inot a cdevData buffer.
// *
// *	Each line of the result should be structured as such.
// *
// *	Tag value\n
// *
// *	All results are assumed to be doubles unless they are enclosed
// *	in double quotes (").
// *
// *	If an array of items is returned, it should be enclosed in 
// *	braces as described below.
// *
// *	1) Returning a string:
// *
// *		value="Test"\n
// *		status "Running very well"\n
// *	
// *	2) Returning a double:
// *		value=10.01\n
// *		status=1\n
// *
// *	3) Returning an array of strings
// *		value={"John","Jacob","Jingleheimer","Schmidt"}\n
// *	
// *	4) Returning a two dimensional array of doubles: ie x[2][3]
// *		value={{1.0,2.0,3.0}{4.0,5.0,6.0}}\n
// *
// *****************************************************************************
int ScriptData::buffer2Data ( char * &buf, cdevData & data )
	{
	int    done = 0;
	char * ptr  = buf;
	
	while(ptr && *ptr)
		{
		while(isspace(*ptr)) ptr++;
		char * tag = ptr;
					
		// *************************************************************
		// * Find the end of the tag and remove any trailing
		// * spaces that are associated with it.
		// *************************************************************
		if(*tag)  ptr = strchr(ptr, '=');

		// *************************************************************
		// * If the pointer does not point to a valid line, jump to
		// * the next line and continue from the top of the loop.
		// *************************************************************
		if(!ptr || !(*ptr))
			{
			ptr = strchr(ptr, '\n');
			continue;
			}
		else	{
			char *temp = ptr-1;
			*(ptr++) = 0;
			while(isspace(*temp)) *(temp--)=0;
			}

		// *************************************************************
		// * Find the beginning of the data.
		// *************************************************************
		while(*ptr && isspace(*ptr)) ptr++;
		
		// *************************************************************
		// * Beginning of an array of things.
		// *************************************************************
		if(*ptr=='{')
			{
			cdevDataTypes dataType = CDEV_DOUBLE;
			char   ** strData  = NULL;
			double  * dblData  = NULL;
			char    * tPtr     = ptr;
			cdevBounds * bounds;
			int nDim = 0;
			int dimIdx = 0;
			int itmCnt = 0;
			int itmIdx = 0;
			int chkSum = 1;
			
			// *****************************************************
			// * Count the number of opening braces, this will 
			// * indicate the number of dimensions in the array.
			// *****************************************************
			while(*(tPtr++)=='{') nDim++;
			bounds = new cdevBounds[nDim];
			for(dimIdx=0; dimIdx<nDim; dimIdx++)
				{
				bounds[dimIdx].offset = 0;
				bounds[dimIdx].length = 0;
				}
							
			// *****************************************************
			// * Now count the number of the dimensions in the array
			// *****************************************************
			for(tPtr=ptr+1, dimIdx=0; dimIdx>=0 && *tPtr; tPtr++)
				{
				if(*tPtr=='"')
					{
					tPtr = strchr(tPtr+1, '"');
					dataType = CDEV_STRING;
					}
				else if(*tPtr=='}')
					{
					if(dimIdx==nDim-1) bounds[dimIdx].length++;
					dimIdx--;
					}
				else if(*tPtr=='{')
					{
					bounds[dimIdx++].length++;
					}
				else if(*tPtr==',' && dimIdx==nDim-1)
					{
					bounds[dimIdx].length++;
					}
				}
				
			// *****************************************************
			// * At this point, the last element of the bounds
			// * array will contain the total count of items.
			// *****************************************************
			itmCnt = bounds[nDim-1].length;

			// *****************************************************
			// * To set each bounds element to its actual array 
			// * dimension, it will be necessary to divide it by 
			// * the size of its predecessor - working backwards.
			// *****************************************************
			for(dimIdx = nDim-2; dimIdx>=0; dimIdx--)
				{
				bounds[dimIdx+1].length /= bounds[dimIdx].length;
				}
			for(dimIdx = 0; dimIdx<nDim; dimIdx++)
				{
				chkSum*=bounds[dimIdx].length;
				}

			if(chkSum==itmCnt && chkSum>0)	
				{
				// *****************************************************
				// * Allocate a buffer of the correct type to hold the
				// * data.
				// *****************************************************
				if(dataType == CDEV_STRING) strData  = new char *[itmCnt];
				else dblData  = new double [itmCnt];
				
				// *****************************************************
				// * Walk through the data and accumulate the items 
				// * into the array.
				// *****************************************************
				tPtr   = ptr;
				dimIdx = -1;

				do	{
					if(*tPtr=='{')
						{
						int jumpTheComma = 0;

						dimIdx++;
						tPtr++;	

						if(*tPtr && dimIdx==nDim-1) do	
							{
							if(*tPtr==',' && jumpTheComma) tPtr++;
							
							while(isspace(*tPtr)) tPtr++;
							if(dataType==CDEV_DOUBLE)
								{
								dblData[itmIdx++] = atof(tPtr);
								while(*tPtr && *tPtr!=',' && *tPtr!='}') tPtr++;
								}
							else	{
								char   oldEndVal;
								char * ePtr;
								if(*tPtr=='"')
									{
									tPtr++;
									ePtr = strchr(tPtr, '"');
									}
								else for(ePtr=tPtr; *ePtr && *ePtr!=',' && *ePtr!='}'; ePtr++);
	
								if(ePtr)
									{
									oldEndVal = *ePtr;
									*ePtr     = 0;
									}
								
								strData[itmIdx++] = strdup(tPtr);
							
								if(ePtr)
									{
									*ePtr = oldEndVal;
									tPtr = (*ePtr=='"')?ePtr+1:ePtr;
									}
								else tPtr = tPtr+strlen(tPtr);
								} 
							jumpTheComma = (*tPtr==',')?1:0;
							} while(*tPtr!='}' && *tPtr);
						}
					else if(*tPtr=='}') 
						{
						dimIdx--;
						tPtr++;
						}
					else tPtr++;
					} while (*tPtr && itmIdx<itmCnt && dimIdx>=0);
					
				if(dataType==CDEV_STRING) 
					{
					data.insert(tag, strData, itmCnt, nDim);
					for(int x=0; x<itmCnt; x++) delete strData[x];
					delete strData;
					}
				else	{
					data.insert(tag, dblData, itmCnt, nDim);
					delete dblData;
					}

				data.setBounds(tag, bounds, nDim);
				}		

			delete bounds;
			for(ptr=tPtr; *ptr && *ptr!='\n'; ptr++);
			}
		// *************************************************************
		// * Beginning of a string.
		// *************************************************************
		else if(*ptr=='"')
			{
			ptr++;
			char * temp = ptr;
			while(*temp!='"') temp++;
			if(*temp=='"')
				{
				*(temp++)=0;
				data.insert(tag, ptr);
				}
			for(ptr = temp; *ptr && *ptr!='\n'; ptr++);
			}
		// *************************************************************
		// * Beginning of a double.
		// *************************************************************
		else if(*ptr)
			{
			data.insert(tag, atof(ptr));
			while(*ptr && *ptr!='\n') ptr++;
			}
		}		
	return CDEV_SUCCESS;
	}
	

// *****************************************************************************
// * ScriptData::fireCallback :
// *	Deploys the callback associated with the class instance.
// *****************************************************************************
void ScriptData::fireCallback ( int result, void * arg, cdevRequestObject &req, cdevData & data, int partial)
	{
	// *********************************************************************
	// * Fire the user provided callback and increment the counter.
	// *********************************************************************
	xobj->userCallback_->fireCallback ( result, arg, req, data, partial );
	callbackCount++;
	}
	
// *****************************************************************************
// * ScriptData::dispatch :
// *	This method is responsible for collecting data from the
// *	read file descriptor, and executing the callback associated
// * 	with it.
// * 
// *	This method will return the number of messages that it handled.
// *****************************************************************************
int ScriptData::dispatch ( void )
	{
	int nHandled = 0;
	if(!finished)
		{
		int count = 0;
		int stat_val;
		// *************************************************************
		// * Find out how many bytes are ready to read.
		// *************************************************************
		while(ioctl(sp.readFD, FIONREAD, &count)==0 && count>0)
			{
			// *****************************************************
			// * If the input buffer has not already been 
			// * allocated, then allocate a new one that
			// * is of sufficient size in increments of
			// * 1024 bytes.
			// *****************************************************
			if(inputBufLen==0)
				{
				inputBufLen = (count/1024+1)*1024;
				inputBuf = (char *)malloc(inputBufLen);
				}
			// *****************************************************
			// * If the existing buffer is not sufficiently
			// * large, then allocate a new one that is
			// * of sufficient size in increments of
			// * 1024 bytes.
			// *********************************************	
			else if(amntRead + count > inputBufLen)
				{
				inputBufLen = ((amntRead+count)/1024+1)*1024;
				inputBuf = (char *)realloc(inputBuf, inputBufLen);
				}
			
			// *****************************************************
			// * Read the data that is ready into the buffer
			// *****************************************************
			amntRead += read(sp.readFD, inputBuf+amntRead, count);

			// *****************************************************
			// * NULL terminate the buffer to prevent memory
			// * overruns while performing string operations
			// *****************************************************
			inputBuf[amntRead] = 0;
			
			int done  = 0;
			
			// *****************************************************
			// * Search the data for the "done" or "end" keywords
			// * on a line by themselves...  Either of these 
			// * keywords indicate the end of a data transmission.
			// *****************************************************
			do 	{
				char * ePtr = NULL;

				if (strncmp(inputBuf, "end\n", 4)==0 || 
				    strncmp(inputBuf, "done\n", 5)==0)
				    	{
				    	ePtr = inputBuf;
				    	}
				else if((ePtr = strstr(inputBuf, "\nend\n"))!=NULL ||
				        (ePtr = strstr(inputBuf, "\ndone\n"))!=NULL)
					{
					ePtr++;
					}
				else done = 1;

				if(ePtr && *ePtr)
					{
					if(*ePtr=='d')
						{
						*ePtr    = 0;
						ePtr    += 5;
						finished = 1;
						}
					else	{
						*ePtr    = 0;
						ePtr    += 4;
						}

					buffer2Data (inputBuf, *data);
					strcpy(inputBuf, ePtr);
					amntRead = strlen(inputBuf);

					// *************************************
					// * If no more data is ready,
					// * determine if the process
					// * is still alive.
					// *************************************
					stat_val = 0;
					if(waitpid(process, &stat_val, WNOHANG)==process &&
					   (errno==ECHILD || WIFEXITED(stat_val)))
						{
				    		finished = 1;
				    		}
					
					// *************************************
					// * Transfer the data from the
					// * temporary data object into
					// * the user-defined result
					// * data object.
					// *************************************
					if(xobj->resultData_==NULL) 
						xobj->resultData_  =  data;
					else  *(xobj->resultData_) = *data;

					// *************************************
					// * Fire the user provided 
					// * callback and increment the
					// * callback counter.  The status as
					// * returned in the cdevData object 
					// * is the status for the request.
					// *************************************
					int status = CDEV_SUCCESS;
					xobj->resultData_->get("status", &status);
					fireCallback
						(
						status,
						xobj->userCallback_->userarg(),
						*xobj->reqObj_,
						*xobj->resultData_,
						!finished);
					nHandled++;
					}
				} while(!done && !finished);
			}

		// *************************************************************
		// * When no more data is ready, determine if the process is 
		// * still alive.
		// *************************************************************
		if(!finished && waitpid(process, &stat_val, WNOHANG)==process &&
		   (errno==ECHILD || WIFEXITED(stat_val)))
			{
			finished = 1;
			}
		}
	return nHandled;	
	}

// *****************************************************************************
// * ScriptData::~ScriptData :
// *	The destructor is protected to prevent it from being deleted by 
// *	anyone except the ScriptList class.
// *****************************************************************************
ScriptData::~ScriptData ( void )
	{
	static int killCount = 0;
	if(callbackCount==0)
		{
		data->remove();
		fireCallback (
			CDEV_SUCCESS,
			xobj->userCallback_->userarg(),
			*xobj->reqObj_,
			*data);
		}

	if(inputBuf)         delete inputBuf;
	if(sp.readFD != -1)  close(sp.readFD);
	if(sp.writeFD != -1) close(sp.writeFD);
	if(data)             delete data;
	if(xobj)             
		{
		xobj->removeFromGrps();
		delete xobj;
		}
	}


// *****************************************************************************
// * ScriptList::ScriptList :
// *	This is the constructor for the ScriptList class.  It
// *	initializes the internals of the class to 0.
// *****************************************************************************
ScriptList::ScriptList ( void ) : scripts(NULL), fdList(NULL), fdCount(0), fdMax(0) 
	{
	};

// *****************************************************************************
// * ScriptList::ScriptList :
// *	This method will walk through the list of ScriptData objects and
// *	will delete each of them in sequence.
// *
// *	Note that is no callbacks have been delivered for the specified
// *	ScriptData object, it will deliver a single callback with 
// *	CDEV_ERROR as the status prior to terminating.
// *****************************************************************************
ScriptList::~ScriptList ( void )
	{
	ScriptData * curr;
	while(scripts!=NULL)
		{
		curr    = scripts;
		scripts = scripts->next;
		delete    curr;
		}
	}

// *****************************************************************************
// * ScriptList::find :
// *	This method allows the caller to locate the ScriptData object 
// *	that is associated with a specified file descriptor.
// *****************************************************************************
ScriptData * ScriptList::find ( int fd )
	{
	ScriptData * curr = scripts;
	while(curr->sp.readFD != fd && curr!=NULL)
		{
		curr = curr->next;
		}
	return curr;
	}
	
// *****************************************************************************
// * ScriptList::insert :
// *	This method allows the caller to insert a new ScriptData object
// *	into the list of scripts that is managed by this list.  Once
// *	added to the list the file descriptor associated with the 
// *	ScriptData object will be placed on the list of file
// *	descriptors that will be polled by the cdevSystem object.
// *****************************************************************************
void ScriptList::insert ( ScriptData & script )
	{
	script.next = scripts;
	scripts     = &script;
	
	if(fdCount >= fdMax)
		{
		fdMax  = (fdMax>1)?(fdMax*2):16;
		fdList = (int *)realloc(fdList, fdMax*sizeof(int));
		}
	if(fdCount==0) fdList[fdCount++] = selector.readfd();
	fdList[fdCount++] = script.sp.readFD;
	}

// *****************************************************************************
// * ScriptList::remove :
// *	This method calls the remove(fd) method to remove the ScriptData
// *	object that is specified by the caller.
// *****************************************************************************
void ScriptList::remove ( ScriptData & script )
	{
	remove(script.sp.readFD);
	}

// *****************************************************************************
// * ScriptList::remove :
// *	This method will walk through all scripts in the list until it
// *	locates the file descriptor specified by fd.  The method will 
// *	then remove the ScriptData object that is associated with that
// *	file descriptor.
// *
// *	The fdList and fdCount variables will be updated once the dead
// *	file descriptor has been removed.
// *****************************************************************************
void ScriptList::remove ( int fd )
	{
	ScriptData * prev = NULL;
	ScriptData * curr = scripts;
	
	while(curr!=NULL && curr->sp.readFD!=fd)
		{
		prev = curr;
		curr = prev->next;
		}
	if(curr!=NULL)
		{
		if(prev!=NULL) prev->next = curr->next;
		else           scripts    = curr->next;
		delete curr;
		
		for(fdCount=0, curr = scripts; curr!=NULL; curr=curr->next)
			{
			fdList[fdCount++] = curr->sp.readFD;
			}
		}
	}
	
// *****************************************************************************
// * ScriptList::getFd :
// *	This method allows the caller to obtain the list of file
// *	descriptors that are in use in all scripts.  These descriptors
// *	will be used to perform ending at the cdevSystem level.
// *
// *	Note that this list is updated and maintained each time the
// *	user calls "insert" to add a new file descriptor of "remove" to
// *	extract an old one.
// *****************************************************************************
int ScriptList::getFd  ( int * &fd, int & numFd )
	{
	fd    = fdList;
	numFd = fdCount;

	return CDEV_SUCCESS;
	}

// *****************************************************************************
// * ScriptList::poll :
// *	This method is called to poll for events on a list of scripts
// *	that are currently executing.  This method will return the 
// *	count of callbacks that were dispatched during this pass through
// *	poll.
// *****************************************************************************
int ScriptList::poll ( void )
	{
	int          nHandled = 0;
	ScriptData * prev     = NULL;
	ScriptData * curr     = scripts;
	
	// *********************************************************************
	// * Clear any events that might be stored in the selector object.
	// *********************************************************************
	selector.purge();
	
	// *********************************************************************
	// * Walk through each script and call the dispatch method to
	// * fire callbacks for all file descriptors that have data
	// * ready.
	// *********************************************************************
	for(curr=scripts; curr!=NULL; curr = curr->next) 
		{
		nHandled+=curr->dispatch();
		}

	// *********************************************************************
	// * Walk through the list of scripts again.  This time, check 
	// * the finished flag to determine if each script has run to
	// * completion.  If so, remove the script from the list of
	// * scripts and delete it.
	// *********************************************************************
	for(prev=NULL, curr=scripts; curr!=NULL; )
		{
		if(curr->finished) 
			{
			int deadFd = curr->sp.readFD;
			curr = curr->next;
			remove(deadFd);
			}
		else	{
			prev = curr;
			curr = prev->next;
			}
		}

	// *********************************************************************
	// * Return the total number of callbacks that were fired.  This
	// * value will be used to allow for polling until at least one
	// * event has been processed.
	// *********************************************************************
	return nHandled;
	}



