home *** CD-ROM | disk | FTP | other *** search
- //************************************************************************
- //
- // 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
-
-