home *** CD-ROM | disk | FTP | other *** search
- //
- // MiscSubprocess.m -- a Obj-C wrapper around Unix child processes
- // Originally written by Drew Davidson (c) 1994 by Drew Davidson.
- // Modified by Don Yacktman for inclusion into the MiscKit.
- // Version 1.0. 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 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
-
- REVISIONS
- $Log$
- ----------------------------------------------------------------------------*/
- #import <libc.h>
- #import <misckit/misckit.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."
-
- @interface MiscSubprocess(private)
-
- - _childDidExit;
- - _stdoutBuffer;
- - _stderrBuffer;
- - _fdHandler:(int)theFd method:(SEL)aSelector buffer:aString;
- - _setRunning:(BOOL)yn;
-
- @end
-
- @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
- { int bufferCount,
- maxLength;
-
- do
- { int currentLength = [aString strlen];
-
- maxLength = [aString capacity] - currentLength;
- bufferCount = read(theFd,[aString buffer] + currentLength,maxLength);
- if (bufferCount <= 0)
- { if (currentLength > 0)
- [self flushBuffer:aString];
- [self _childDidExit];
- return(self);
- }
- [aString fixStringLengthAt:(bufferCount + currentLength)];
- if (bufferCount == maxLength)
- [aString setCapacity:([aString capacity] * 2.0)];
- } while (bufferCount == maxLength);
- [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
- {
- [super init];
-
- stdoutBuffer = [[MiscString allocFromZone:[self zone]] initCapacity:BUFSIZ];
- stderrBuffer = [[MiscString allocFromZone:[self zone]] initCapacity:BUFSIZ];
- environment = [[MiscStringArray allocFromZone:[self zone]] init];
- [self setDelegate:theDelegate];
- if (aString)
- [self execute:aString];
- return(self);
- }
-
- - free
- {
- [self terminate:self];
- [stdoutBuffer free];
- [stderrBuffer free];
- [environment free];
- return([super free]);
- }
-
- /*-----------------------------< OTHER METHODS >-----------------------------*/
- - execute:(const char *)aString
- { int pipeTo[2];
- int pipeFrom[2];
- int pipeStderr[2]; /* for stderr to different fd */
- int numFds,
- fd;
- int processGroup;
- char hail[BUFSIZ];
-
- if ([self isRunning])
- return(nil);
- 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 */
- 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);
-
- [self execChild:aString];
- [delegate perform:@selector(subprocess:error:) with:self with:(void *)EXEC_ERROR];
- [self error:"vfork (child) returned!"];
- break;
- default: /* parent */
- 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 */
- setbuf(fpFromChild,NULL);
- DPSAddFD(stdoutFromChild,(DPSFDProc)stdoutFdHandler,self,NX_MODALRESPTHRESHOLD+1);
- DPSAddFD(stderrFromChild,(DPSFDProc)stderrFdHandler,self,NX_MODALRESPTHRESHOLD+1);
- running = YES;
- fputs("Run, you bastard!\n",fpToChild);
- 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 freeString];
- }
- 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