home *** CD-ROM | disk | FTP | other *** search
- //
- // MiscSubprocess.m -- a Obj-C wrapper around Unix child processes
- // Originally written by Drew Davidson.
- // Copyright (c) 1994 by Drew Davidson.
- // Modified by Don Yacktman for inclusion into the MiscKit.
- // Fixed up by Carl Lindberg and Don Yacktman.
- // Version 1.2. All rights reserved.
- // This notice may not be removed from this source code.
- //
- // This object is included in the MiscKit by permission from the author
- // and its use is governed by the MiscKit license, found in the file
- // "LICENSE.rtf" in the MiscKit distribution. Please refer to that file
- // for a list of all applicable permissions and restrictions.
- //
-
- /*----------------------------------------------------------------------------
- $Source$
-
- SYNOPSIS
- Handles a subprocess that is fork()'ed and handles delegate
- notification of events that occur such as output and errors.
-
- From Subprocess example by Charles L. Oei
- pty support (removed) by Joe Freeman
- with encouragement from Kristofer Younger
- Subprocess Example, Release 2.0
- NeXT Computer, Inc.
-
- Modified to support signals SIGSTOP and SIGCONT, and to wait for error
- code from terminated process, and to add delegate methods for the output by
- Drew Davidson
-
- Modified for MiscKit inclusion by Don Yacktman
- Debugged by Carl Lindberg and Don Yacktman
-
- REVISIONS
- $Log$
- ----------------------------------------------------------------------------*/
- #import <libc.h>
- #import <misckit/MiscString.h>
- #import <misckit/MiscStringArray.h>
- #import "MiscSubprocess.h"
-
- extern int wait4(int, union wait *, int, struct rusage *);
- static void stdoutFdHandler(int theFd, id self);
- static void stderrFdHandler(int theFd, id self);
-
- #define PIPE_ERROR "Error starting UNIX pipes to subprocess."
- #define VFORK_ERROR "Error starting UNIX vfork of subprocess."
- #define EXEC_ERROR "Error starting UNIX exec of subprocess."
- #define PTY_TEMPLATE "/dev/pty??"
- #define PTY_LENGTH 11
-
- @interface MiscSubprocess(private)
-
- - _childDidExit;
- - _stdoutBuffer;
- - _stderrBuffer;
- - _fdHandler:(int)theFd method:(SEL)aSelector buffer:aString;
- - _setRunning:(BOOL)yn;
-
- @end
-
- static void
- getptys (int *master, int *slave)
- // attempt to setup the ptys
- {
- 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;
-
- 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);
- if (*master >= 0)
- {
- *msLoc = 't';
- *slave = open(device, O_RDWR);
- if (*slave >= 0)
- {
- (void) ioctl(*slave, TIOCSETP, (char *)&setp);
- (void) ioctl(*slave, TIOCSETC, (char *)&setc);
- (void) ioctl(*slave, TIOCSETD, (char *)&setd);
- (void) ioctl(*slave, TIOCSLTC, (char *)&sltc);
- (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;
- *slave = -1;
- }
-
-
- @implementation MiscSubprocess
-
- /*----------------------------< PRIVATE METHODS >----------------------------*/
- /*
- * cleanup after a child process exits
- */
- - _childDidExit
- {
- union wait w; int status = 0;
- MiscSubprocessEndCode code = Misc_UnknownEndCode;
-
- if (wait4(childPid,&w,WUNTRACED,NULL) > 0) {
- IMP done;
- if (WIFEXITED(w)) {
- code = Misc_Exited;
- status = w.w_retcode;
- } else {
- if (WIFSTOPPED(w)) {
- code = Misc_Stopped;
- status = w.w_stopsig;
- } else {
- if (WIFSIGNALED(w)) {
- code = Misc_Signaled;
- status = w.w_termsig;
- }
- }
- }
- DPSRemoveFD(stdoutFromChild);
- DPSRemoveFD(stderrFromChild);
- fclose(fpFromChild);
- close(stdoutFromChild);
- close(stderrFromChild);
- fclose(fpToChild);
- running = NO;
- done = [delegate methodFor:@selector(subprocess:done::)];
- done(delegate,@selector(subprocess:done::),self,status,code);
- }
- return self;
- }
-
- - _stdoutBuffer
- {
- return stdoutBuffer;
- }
-
- - _stderrBuffer
- {
- return stderrBuffer;
- }
-
- /*
- * DPS handler for output from subprocess
- */
-
- - _fdHandler:(int)theFd method:(SEL)aSelector buffer:aString
- { // re-written by Carl Lindberg to make it safer.
- int bufferCount, currleft = BUFSIZ / 2;
- do {
- int currentLength = [aString length];
- char buf[(currleft * 2) + 1];
-
- currleft *=2;
- bufferCount = read(theFd, buf, currleft);
- if (bufferCount <= 0) {
- if (currentLength > 0)
- [self flushBuffer:aString];
- [self _childDidExit];
- return self;
- }
- buf[bufferCount] = 0;
- [aString cat:buf];
- } while (bufferCount == currleft);
- [self flushBuffer:aString];
- return self;
- }
-
- /*
- * DPS handler for output from subprocess
- */
- static void stdoutFdHandler(int theFd,id self)
- {
- [self _fdHandler:theFd method:@selector(subprocess:output:)
- buffer:[self _stdoutBuffer]];
- }
-
- static void stderrFdHandler(int theFd,id self)
- {
- [self _fdHandler:theFd method:@selector(subprocess:stderrOutput:)
- buffer:[self _stderrBuffer]];
- }
-
- - _setRunning:(BOOL)yn
- {
- running = yn;
- return self;
- }
-
- /*---------------------------< INIT/FREE METHODS >---------------------------*/
- - init
- {
- return [self init:NULL];
- }
-
- - init:(const char *)aString
- {
- return [self init:aString withDelegate:nil];
- }
-
- - init:(const char *)aString withDelegate:theDelegate
- {
- return [self init:aString withDelegate:theDelegate
- keepEnvironment:YES withPtys:NO];
- }
-
- - init:(const char *)aString withDelegate:theDelegate
- keepEnvironment:(BOOL)flag
- {
- return [self init:aString withDelegate:theDelegate
- keepEnvironment:YES withPtys:NO];
- }
-
- - init:(const char *)aString withDelegate:theDelegate
- keepEnvironment:(BOOL)flag withPtys:(BOOL)ptyFlag
- {
- [super init];
-
- stdoutBuffer = [[MiscString allocFromZone:[self zone]]
- initCapacity:BUFSIZ];
- stderrBuffer = [[MiscString allocFromZone:[self zone]]
- initCapacity:BUFSIZ];
- environment = [[MiscStringArray allocFromZone:[self zone]] init];
- if (flag) {
- int i;
- for (i=0; environ[i]; i++)
- [environment addString:environ[i]];
- }
- [self setDelegate:theDelegate];
- if (aString) [self execute:aString withPtys:ptyFlag];
- return self;
- }
-
- - free
- {
- [self terminate:self];
- [stdoutBuffer free];
- [stderrBuffer free];
- [environment free];
- return [super free];
- }
-
- /*-----------------------------< OTHER METHODS >-----------------------------*/
- - execute:(const char *)aString
- { return [self execute:aString withPtys:NO];}
-
- - execute:(const char *)aString withPtys:(BOOL)ptyFlag
- {
- int pipeTo[2], pipeFrom[2], pipeStderr[2]; // for stderr to different fd
- int tty, numFds, fd, processGroup;
- char hail[BUFSIZ];
-
- if ([self isRunning]) return nil;
- if (ptyFlag) {
- tty = open("/dev/tty", O_RDWR);
- getptys(&masterPty, &slavePty);
- if (masterPty <= 0 || slavePty <= 0 || pipe(pipeStderr) < 0) {
- [delegate perform:@selector(subprocess:error:) with:self
- with:(void *)"Error grabbing ptys for subprocess."];
- 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)) {
- printf("shouldn't be here\n");
- ioctl(tty, TIOCNOTTY, 0);
- close(tty);
- }
- } else {
- if (pipe(pipeTo) < 0 || pipe(pipeFrom) < 0 || pipe(pipeStderr) < 0) {
- [delegate perform:@selector(subprocess:error:)
- with:self with:(void *)PIPE_ERROR];
- return nil;
- }
- }
- switch (childPid = vfork()) {
- case -1: /* error */
- [delegate perform:@selector(subprocess:error:)
- with:self with:(void *)VFORK_ERROR];
- return self;
- break;
- case 0: /* child */
- if (ptyFlag) {
- dup2(slavePty, 0);
- dup2(slavePty, 1);
- dup2(pipeStderr[1], 2); //stderr
- } else {
- dup2(pipeTo[0], 0);
- dup2(pipeFrom[1], 1); /* get stdout from process */
- dup2(pipeStderr[1], 2); /* get stderr here */
- }
- numFds = getdtablesize();
- for (fd = 3; fd < numFds; fd++) close(fd);
- processGroup = getpid();
- ioctl(0, TIOCSPGRP, (char *)&processGroup);
- setpgrp(0, processGroup);
- fgets(hail, BUFSIZ, stdin); // wait for parent
- [self execChild:aString];
- [delegate perform:@selector(subprocess:error:)
- with:self with:(void *)EXEC_ERROR];
- [self error:"vfork (child) returned!"];
- break;
- default: /* parent */
- if (ptyFlag) {
- close(slavePty);
- close(pipeStderr[1]);
- fpToChild = fdopen(masterPty,"w");
- stdoutFromChild = masterPty;
- stderrFromChild = pipeStderr[0];
- } else {
- close(pipeTo[0]);
- close(pipeFrom[1]);
- close(pipeStderr[1]);
- fpToChild = fdopen(pipeTo[1], "w");
- stdoutFromChild = pipeFrom[0];
- fpFromChild = fdopen(pipeFrom[0],"r");
- stderrFromChild = pipeStderr[0];
- }
- // Set buffering method, also make it use its own buffers
- setbuf(fpToChild, NULL); /* no buffering */
- if (!ptyFlag) setbuf(fpFromChild, NULL);
- DPSAddFD(stdoutFromChild, (DPSFDProc)stdoutFdHandler,
- self, NX_MODALRESPTHRESHOLD + 1);
- DPSAddFD(stderrFromChild, (DPSFDProc)stderrFdHandler,
- self, NX_MODALRESPTHRESHOLD + 1);
- running = YES;
- fputs("Run away! Run away!\n", fpToChild); // tell child to go
- break;
- }
- return self;
- }
-
- - execChild:(const char *)aString
- {
- /*
- * we exec a /bin/sh so that cmds are easier to specify for the user
- */
- execle("/bin/sh", "sh", "-c", aString, 0, [environment stringArray]);
- return self;
- }
-
- - setDelegate:anObject
- {
- delegate = anObject;
- return self;
- }
-
- - delegate
- {
- return delegate;
- }
-
- - environment
- {
- return environment;
- }
-
- - (SEL)outputMethodForBuffer:aBuffer
- {
- SEL aSelector;
- if (aBuffer == [self _stdoutBuffer])
- aSelector = @selector(subprocess:output:);
- else {
- if (aBuffer == [self _stderrBuffer])
- aSelector = @selector(subprocess:stderrOutput:);
- else aSelector = NULL;
- }
- return aSelector;
- }
-
- - flushBuffer:aString as:aBuffer
- {
- if ([aString strlen] > 0) {
- SEL aSelector = [self outputMethodForBuffer:aBuffer];
- if (aSelector)
- [delegate perform:aSelector
- with:self with:(id)[aString stringValue]];
- [aString setStringValue:""];
- }
- return nil;
- }
-
- - flushBuffer:aString
- {
- return [self flushBuffer:aString as:aString];
- }
-
- - send:(const char *)string withNewline:(BOOL)wantNewline
- {
- fputs(string,fpToChild);
- if (wantNewline)
- fputc('\n',fpToChild);
- return self;
- }
-
- - send:(const char *)string
- {
- [self send:string withNewline:YES];
- return self;
- }
-
- /*
- * Returns the process id of the process (and therefore the process group
- * of the job)
- */
- - (int)pid
- {
- return childPid;
- }
-
- - pause:sender
- {
- if (!paused) {
- killpg(childPid,SIGSTOP); /* pause the process group */
- paused = YES;
- }
- return self;
- }
-
- - resume:sender
- {
- if (paused) {
- killpg(childPid,SIGCONT); /* resume the process group */
- paused = NO;
- }
- return self;
- }
-
- - (BOOL)isPaused
- {
- return paused;
- }
-
- - (BOOL)isRunning
- {
- return running;
- }
-
- - terminate:sender
- {
- if (running)
- killpg(childPid,SIGKILL);
- return self;
- }
-
- /*
- * effectively sends an EOF to the child process stdin
- */
- - terminateInput
- {
- fclose(fpToChild);
- return self;
- }
-
- @end
-
- @implementation Object(MiscSubprocessDelegate)
-
- - subprocess:sender done:(int)status :(MiscSubprocessEndCode)code
- {
- return self;
- }
-
- - subprocess:sender output:(const char *)buffer
- {
- return self;
- }
-
- - subprocess:sender stderrOutput:(const char *)buffer
- {
- return self;
- }
-
- - subprocess:sender error:(const char *)errorString
- {
- perror(errorString);
- return self;
- }
-
- @end