//************************************************************************
//
//	Subprocess.m  
//		
//		Launch and monitor UNIX subprocesses. 
// 
//			by	Felipe A. Rodriguez		
//
//	The base for this file was derived from:
//	
//			Subprocess.m	(v10)
//			by Charles L. Oei
//			pty support by Joe Freeman
//			Subprocess Example, Release 2.0
//			NeXT Computer, Inc. 
//
//	This code is supplied "as is" the author makes no warranty as to its 
//	suitability for any purpose.  This code is free and may be distributed 
//	in accordance with the terms of the:
//		
//			GNU GENERAL PUBLIC LICENSE
//			Version 2, June 1991
//			copyright (C) 1989, 1991 Free Software Foundation, Inc.
// 			675 Mass Ave, Cambridge, MA 02139, USA
//
//************************************************************************

#import "GKdefs.h"
#import "Subprocess.h"
#import "Coordinator.h"

#import <appkit/nextstd.h>
#import <appkit/Application.h>
#import <appkit/Panel.h>
#import <ansi/string.h>
#import <dpsclient/dpsNeXT.h>
#import <defaults/defaults.h>

#define	PTY_TEMPLATE "/dev/pty??"
#define	PTY_LENGTH 11

static void showError();

//************************************************************************
//
// Private Instance Methods
//
//************************************************************************

@interface Subprocess(Private)

- childDidExit;
- fdHandler:(int)theFd;

@end
@implementation Subprocess(Private)

- childDidExit
    // cleanup after a child process exits
{
    if (childPid)
		{
		DPSRemoveFD(fromChild);
		close(fromChild);
		fclose(fpToChild);
		childPid=0;				// specify that child is dead
		if (delegate && [delegate respondsTo:@selector(subprocessDone)])
			[delegate perform:@selector(subprocessDone)];
		}
		
    return self;
}
//************************************************************************
//
//		Called when output from UNIX subprocess is available	
//
//************************************************************************

- fdHandler:(int)theFd
    // DPS handler for output from subprocess
{
    if (((bufferCount = read(theFd, outputBuffer, BUFFERSIZE-1)) < 0) || 	
			(!bufferCount))
		{
		[self childDidExit];
		return self;
		}
    outputBuffer[bufferCount] = '\0';
	[delegate subprocessOutput:(void *)&outputBuffer];
			
    return self;
}

@end

//************************************************************************
//
//		Private Utility Routines
//
//************************************************************************
 
static void showError (const char *errorString, id theDelegate)
    // ensure errors never get dropped on the floor
{
    if(theDelegate && [theDelegate respondsTo:@selector(showAlert:)])
		[theDelegate perform:@selector(showAlert:)
	    	with:(void *)errorString];
    else if(NXApp)		// no delegate, but we're running w/in an App
			NXRunAlertPanel(0, errorString, 0, 0, 0);
    	 else
			perror(errorString); 				//output errstr on stderr
}
//*****************************************************************************
//
//		DPS registered function which recieves output from UNIX subprocess
//
//		void DPSAddFD(int fd, DPSFDProc handler, void *userData, int priority)
//      fdhandler registered to read file descriptor fromChild
//
//*****************************************************************************

static void fdHandler(int theFd, id self)
{
    [self fdHandler:theFd];
}
//*****************************************************************************
//
//		attempt to setup the pty pair
//
//*****************************************************************************

static void getptys (int *master, int *slave)
{
    char device[PTY_LENGTH];
    char *block, *num;
    char *blockLoc; // specifies the location of block for the device string
    char *numLoc; 	// specifies the pty name with the digit ptyxD
    char *msLoc;	// specifies the master (ptyxx) or slave (ttyxx)
    struct sgttyb setp = {B9600, B9600, (char)0x7f, (char)0x15, (CRMOD|ANYP)};
    struct tchars setc = {CINTR, CQUIT, CSTART, CSTOP, CEOF, CBRK};
    struct ltchars sltc = {CSUSP, CDSUSP, CRPRNT, CFLUSH, CWERASE, CLNEXT};
    int	lset = (LCRTBS|LCRTERA|LCRTKIL|LCTLECH|LPENDIN|LDECCTQ);
    int	setd = NTTYDISC;		/* new tty discipline */
    
    strcpy(device, PTY_TEMPLATE); 	// string constants are not writable
    blockLoc = &device[ strlen("/dev/pty") ];
    numLoc = &device[ strlen("/dev/pty?") ];
    msLoc = &device[ strlen("/dev/") ];
    for (block = "pqrs"; *block; block++)	
		{									
		*blockLoc = *block;
		for (num = "0123456789abcdef"; *num; num++)
			{
			*numLoc = *num;
			*master = open(device, O_RDWR);	//Open device assign fd to master
			if (*master >= 0)				//If master 0 or >, open succesful
				{
				*msLoc = 't';
				*slave = open(device, O_RDWR);
				if (*slave >= 0)
					{
						/* set parameters -- stty */
					(void) ioctl(*slave, TIOCSETP, (char *)&setp);
						/* set special characters */
					(void) ioctl(*slave, TIOCSETC, (char *)&setc);
						/* set line discipline */
					(void) ioctl(*slave, TIOCSETD, (char *)&setd);
						/* set local special chars */
					(void) ioctl(*slave, TIOCSLTC, (char *)&sltc);
						/* set entire local mode word */
					(void) ioctl(*slave, TIOCLSET, (char *)&lset);
					
					return;
					}
				}
			} /* hunting through a bank of ptys */
		} /* hunting through blocks of ptys in all the right places */
		
    *master = -1;		//If we get here we were unable to open 
    *slave = -1;		//master and/or slave pty's 
}

@implementation Subprocess

//************************ Public Instance Methods ****************************
//*****************************************************************************
//
// 		initializes an instance of Subprocess and corresponding UNIX process
//
//*****************************************************************************

- init:(const char *)subprocessString withDelegate:theDelegate
    		andStdErr:(BOOL)wantsStdErr
{
static char logFileName[MAXPATHLEN + 1];		// syslogd logging file
int tty, numFds, fd;							// for temporary use
int processGroup;
int pidChild;		// childPid does not exist until Subprocess is instantiated

					// reset syslogd daemon 
	if(theDelegate && [theDelegate respondsTo:@selector(syslogdReset)])
		[theDelegate perform:@selector(syslogdReset)];	
    								// /dev/tty kernel synm for controlling tty
	tty = open("/dev/tty", O_RDWR);			// open the controlling tty 
	getptys(&masterPty,&slavePty);			// find and open psuedo terms
	strcpy(logFileName, NXGetDefaultValue([NXApp appName], "locFIFO"));
	if (masterPty <= 0 || slavePty <= 0)
		{
		showError("Error grabbing ptys for subprocess.", theDelegate);
		return self;
		}
			// remove the controlling tty if launched from a shell,
			// but not Workspace; so that we have job control over
			// the parent application in shell and so that subprocesses 
			// can be restarted in Workspace
	if((tty<0) && ((tty = open("/dev/tty", 2)) >= 0))
		{
		ioctl(tty, TIOCNOTTY, 0);
		close(tty);
		}
    switch (pidChild = fork())				// fork to create child process
   	 	{									// which will monitor pppd
    	case -1:	// error
		
			showError("Error starting UNIX vfork of subprocess.", theDelegate);
			return self;
			
    	case 0:  	// child -- Vfork returns 0 in the child's context
		
			dup2(slavePty, 0);		// dup slavePty to be stdin
			dup2(slavePty, 1);		// dup slavePty to be stdout
			if(wantsStdErr)
				dup2(slavePty, 2);	// dup slavePty to be stderr
			numFds = getdtablesize();
			for(fd=3; fd < numFds; fd++)		// Child should close all open
				close(fd);						// fd's beyond our basic 3
			processGroup = getpid();			// return current pid
				// think -- tty was opened by parent so set pgrp of tty
				//	to that of ourselves
			ioctl(0, TIOCSPGRP, (char *)&processGroup);		
			setpgrp (0, processGroup);  // make curr process process grp leader

			if(strcmp(NXGetDefaultValue([NXApp appName],"useFIFO"), 
														"YES") == 0)
				{ 
				if((log = open(logFileName, O_RDONLY)) < 0)		// open FIFO
					perror("Unable to open FIFO special file.");
				}
			else
				{											// open log file
				if(!(fp = fopen(logFileName, "r")))
					perror("Unable to open log file.");
				else
    				fseek(fp, 0, SEEK_END);
				}

			switch (vfork())					// fork to exec pppd, parent	
				{								// will monitor pppd
				case -1:	// error
				
					showError("Error during vfork to exec pppd", theDelegate);
					return self;
					
				case 0:  	// child -- Vfork returns 0 in the child's context
							
					execl("/bin/sh", "sh", "-c", subprocessString, (char *)0);
					perror("error exec'ing pppd"); 
					exit(1);
		
				default:	// parent --  vfork returns pid of the child  		
						
    				if(strcmp(NXGetDefaultValue([NXApp appName],"useFIFO"), 
																"YES") == 0) 
						[self readFromFIFO];	// use a FIFO as IPC w/pppd
					else
						[self readFromFile];	// use a File as IPC w/pppd
				}		
			perror("vfork 1st (child)"); 
			exit(1);
			
    	default:	// parent --  vfork returns pid of the child in the 		
					//				parent's context
    		delegate = theDelegate;
			childPid = pidChild;
			close(slavePty);
			fpToChild = fdopen(masterPty, "w");	//return ptr to masterPty
			fromChild = masterPty;
			setbuf(fpToChild, NULL);	// no buffering
				// Registers fdhandler to be called when their is activity with 
				// file descriptor fromChild
				// void DPSAddFD(int fd, DPSFDProc handler, void *userData, 
				// int priority)    
			DPSAddFD(fromChild, (DPSFDProc)fdHandler, (id)self, 	
					NX_BASETHRESHOLD+1);

			return self;
    	}
}
//*****************************************************************************
//
// 		Attempt to kill our child as well as pppd and its subprocesses at any // 		stage in their respective lifetimes.  This is trickier than it sounds.
//
//*****************************************************************************

- terminate:sender
{
FILE *ff; 
int pid;
int prgrp;
char lockPath[32] = {"rm usr/spool/uucp/LCK/LCK.."};	
					
    if((ff = fopen( "/etc/ppp/ppp0.pid", "r")) == NULL)
		{
		if(childPid)
			{								// get the child's process group
			prgrp =  getpgrp(childPid);		// kill the child's group with
			killpg(prgrp, SIGKILL);			// extreme prejudice
			strcpy(Path, lockPath);		
			strcat(Path, NXGetDefaultValue([NXApp appName], MODEMPORT));				
			system(Path);					// remove uucp lock from port
			}
		}
	else			// attempt to do a clean hit on a properly running pppd 
		{
    	if( fscanf( ff, "%d", &pid)<1) 
        	perror("Unable to read pid from ppp0.pid\n");
    	fclose( ff);
    	if( kill( pid, SIGINT)==-1) 
        	perror("killing pppd");
		if (childPid)
			kill(childPid, SIGTERM);
    	}
	[self childDidExit];
	
    return self;
}
//*****************************************************************************
//
// 		read pppd output written by syslogd to a FIFO 
//
//*****************************************************************************

- readFromFIFO
{
static int nread;
static char buff[BUFFERSIZE]; 

	for(;;)		// reads log and writes to pty/user 
		{
		if((nread = read(log, buff, BUFFERSIZE)) < 0)
			{
			perror("Error reading FIFO.");
			break;
			}
		if(write(1, buff, nread) != nread)
			perror("Error writing to Slave Pty.");
		}

    return self;
}
//*****************************************************************************
//
// 		read pppd output written by syslogd to an ordinary file 
//
//*****************************************************************************

- readFromFile
{
register int ch;
struct timeval second;
fd_set zero;

	FD_ZERO(&zero);
	second.tv_sec = 1;
	second.tv_usec = 0;

	for(;;)		// reads log and writes to pty/user 
		{
		while ((ch = getc(fp)) != EOF)
			if (putchar(ch) == EOF)
				perror("Error writing to Slave Pty.");
		if (ferror(fp)) 
			{
			perror("Error reading FIFO.");
			break;
			}
		(void)fflush(stdout);
				/* Sleep(3) is eight system calls.  Do it fast. */
		if (select(0, &zero, &zero, &zero, &second) == -1)
			perror("select: %s");
		clearerr(fp);
		}

    return self;
}


@end

