This is MiscSubprocess.m in view mode; [Download] [Up]
// // 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, Don Yacktman, and Steve Hayman. // Version 1.3. 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, Don Yacktman, Steve Hayman PTY support added back in by Carl Lindberg Synchronous operation by Steve Hayman REVISIONS $Log$ ----------------------------------------------------------------------------*/ #import <libc.h> #import <misckit/MiscString.h> #import <misckit/MiscStringArray.h> #import <misckit/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; } // Change it back if the open of the "slave" of the pty fails. *msLoc = 'p'; // to cause it to open the master end of pty // the next time through the loop. } } /* 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; } } } if ( dpsStdoutFromChild >= 0 ) DPSRemoveFD(dpsStdoutFromChild); if ( dpsStderrFromChild >= 0 ) DPSRemoveFD(dpsStderrFromChild); fclose(fpFromChild); close(stdoutFromChild); close(stderrFromChild); fclose(fpToChild); running = NO; if (delegate && [delegate respondsTo:@selector(subprocess:done::)]) { 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; // BOOL firstLoop = YES; 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]; /* * DPSAddFd() just happens to invoke the handler * function even though there may be no data to * read on this fd. So don't blindly exit the * first time read returns 0; wait until * both stderr and stdout * have closed before calling _childDidExit; otherwise * you risk losing data if stderr happens to be * done first. */ // if (firstLoop) { // only want to mark on the first pass. If we read // exactly BUFSIZ bytes and then try and read again // before more is ready, we get zero back and falsely // mark an EOF; making the first pass do this should // be safer. // The first loop stuff isn commented out for now // because we're not sure if this is the right approach. if ( theFd == stdoutFromChild ) stdoutIsDone = YES; else if ( theFd == stderrFromChild ) stderrIsDone = YES; // } if ( stdoutIsDone && stderrIsDone ) [self _childDidExit]; return self; } buf[bufferCount] = 0; [aString cat:buf]; // firstLoop = NO; } 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:(const char *)aString withDelegate:theDelegate keepEnvironment:(BOOL)flag withPtys:(BOOL)ptyFlag asynchronously:(BOOL)async { [super init]; usePtys = ptyFlag; asynchronous = async; stdoutBuffer = [[MiscString allocFromZone:[self zone]] initCapacity:BUFSIZ]; stderrBuffer = [[MiscString allocFromZone:[self zone]] initCapacity:BUFSIZ]; environment = [[MiscStringArray allocFromZone:[self zone]] init]; dpsStdoutFromChild = dpsStderrFromChild = -1; // DPSAddFd() not in use yet if (flag) { int i; for (i=0; environ[i]; i++) [environment addString:environ[i]]; } [self setDelegate:theDelegate]; [self setExecArgs:"/bin/sh" :"sh" :"-c"]; if (aString) [self execute:aString withPtys:usePtys asynchronously:asynchronous]; return self; } - free { free(execArgs[0]); free(execArgs[1]); free(execArgs[2]); [self terminate:self]; [self _childDidExit]; // does DPSRemoveFd for us [stdoutBuffer free]; [stderrBuffer free]; [environment free]; return [super free]; } /*-----------------------------< OTHER METHODS >-----------------------------*/ - execute:(const char *)aString withPtys:(BOOL)ptyFlag asynchronously:(BOOL)async { 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)) { 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); if ( async ) { // Don't read output now; // Tell DPS to notify us when data is ready. stdoutIsDone = NO; DPSAddFD(dpsStdoutFromChild = stdoutFromChild, (DPSFDProc)stdoutFdHandler, self, NX_MODALRESPTHRESHOLD + 1); stderrIsDone = NO; DPSAddFD(dpsStderrFromChild = stderrFromChild, (DPSFDProc)stderrFdHandler, self, NX_MODALRESPTHRESHOLD + 1); running = YES; fputs("Run away! Run away!\n", fpToChild); // tell child to go } else { //synchronous // from Steve Hayman for synch processes... // modified by Nicolas Droux and Carl Lindberg // Tell the subprocess to go right now, we'll read all its // output until end-of-file and return when the subprocess // has exited. We still fork, but we won't return to the // main thread until the child exits... fd_set readfds; int highestFdNum; running = YES; dpsStdoutFromChild = dpsStderrFromChild = -1; //Not using DPSAddFd() highestFdNum = stdoutFromChild > stderrFromChild ? stdoutFromChild : stderrFromChild; stdoutIsDone = stderrIsDone = NO; fputs("Run away! Run away!\n", fpToChild); do { // set up the set of fd's we are using FD_ZERO(&readfds); FD_SET(stdoutFromChild, &readfds); FD_SET(stderrFromChild, &readfds); // select the ones that nead reading from select(highestFdNum+1,&readfds,NULL,NULL,NULL); if (FD_ISSET(stdoutFromChild, &readfds)) stdoutFdHandler(stdoutFromChild, self); if (FD_ISSET(stderrFromChild, &readfds)) stderrFdHandler(stderrFromChild, self); } while(!stdoutIsDone || !stderrIsDone); // The above condition is the same one that _fdHandler uses to // call _childDidExit, so that call has already been done by this point. } break; } return self; } - setExecArgs:(const char *)a0 :(const char *)a1 :(const char *)a2 { int i; for (i=0; i<3; i++) if (execArgs[i]) free(execArgs[i]); execArgs[0] = NXCopyStringBufferFromZone(a0, [self zone]); execArgs[1] = NXCopyStringBufferFromZone(a1, [self zone]); execArgs[2] = NXCopyStringBufferFromZone(a2, [self zone]); return self; } - execChild:(const char *)aString { /* * we exec a /bin/sh so that cmds are easier to specify for the user * Unlike the old method, we don't automatically * use "/bin/sh", "sh", "-c" anymore */ execle( execArgs[0], execArgs[1], execArgs[2], aString, 0, [environment stringArray] ); // old: execle("/bin/sh", "sh", "-c", aString, 0, [environment stringArray]); return self; // shouldn't get here } - 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 length] > 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; } - (BOOL)usePtys { return usePtys; } - setUsePtys:(BOOL)flag { usePtys = flag; return self; } - (BOOL)asynchronous { return asynchronous; } - setAsynchronous:(BOOL)flag { asynchronous = flag; return self; } - 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 @implementation MiscSubprocess(Convenience) // These were automatically generated using cnvwrap. - init { return [self init:NULL withDelegate:nil keepEnvironment:YES withPtys:NO asynchronously:YES]; } - init:(const char *)aString { return [self init:aString withDelegate:nil keepEnvironment:YES withPtys:NO asynchronously:YES]; } - init:(const char *)aString withDelegate:theDelegate { return [self init:aString withDelegate:theDelegate keepEnvironment:YES withPtys:NO asynchronously:YES]; } - init:(const char *)aString keepEnvironment:(BOOL)flag { return [self init:aString withDelegate:nil keepEnvironment:flag withPtys:NO asynchronously:YES]; } - init:(const char *)aString withDelegate:theDelegate keepEnvironment:(BOOL)flag { return [self init:aString withDelegate:theDelegate keepEnvironment:flag withPtys:NO asynchronously:YES]; } - init:(const char *)aString withPtys:(BOOL)ptyFlag { return [self init:aString withDelegate:nil keepEnvironment:YES withPtys:ptyFlag asynchronously:YES]; } - init:(const char *)aString withDelegate:theDelegate withPtys:(BOOL)ptyFlag { return [self init:aString withDelegate:theDelegate keepEnvironment:YES withPtys:ptyFlag asynchronously:YES]; } - init:(const char *)aString keepEnvironment:(BOOL)flag withPtys:(BOOL)ptyFlag { return [self init:aString withDelegate:nil keepEnvironment:flag withPtys:ptyFlag asynchronously:YES]; } - init:(const char *)aString withDelegate:theDelegate keepEnvironment:(BOOL)flag withPtys:(BOOL)ptyFlag { return [self init:aString withDelegate:theDelegate keepEnvironment:flag withPtys:ptyFlag asynchronously:YES]; } - init:(const char *)aString asynchronously:(BOOL)async { return [self init:aString withDelegate:nil keepEnvironment:YES withPtys:NO asynchronously:async]; } - init:(const char *)aString withDelegate:theDelegate asynchronously:(BOOL)async { return [self init:aString withDelegate:theDelegate keepEnvironment:YES withPtys:NO asynchronously:async]; } - init:(const char *)aString keepEnvironment:(BOOL)flag asynchronously:(BOOL)async { return [self init:aString withDelegate:nil keepEnvironment:flag withPtys:NO asynchronously:async]; } - init:(const char *)aString withDelegate:theDelegate keepEnvironment:(BOOL)flag asynchronously:(BOOL)async { return [self init:aString withDelegate:theDelegate keepEnvironment:flag withPtys:NO asynchronously:async]; } - init:(const char *)aString withPtys:(BOOL)ptyFlag asynchronously:(BOOL)async { return [self init:aString withDelegate:nil keepEnvironment:YES withPtys:ptyFlag asynchronously:async]; } - init:(const char *)aString withDelegate:theDelegate withPtys:(BOOL)ptyFlag asynchronously:(BOOL)async { return [self init:aString withDelegate:theDelegate keepEnvironment:YES withPtys:ptyFlag asynchronously:async]; } - init:(const char *)aString keepEnvironment:(BOOL)flag withPtys:(BOOL)ptyFlag asynchronously:(BOOL)async { return [self init:aString withDelegate:nil keepEnvironment:flag withPtys:ptyFlag asynchronously:async]; } - execute:(const char *)aString { return [self execute:aString withPtys:usePtys asynchronously:asynchronous]; } - execute:(const char *)aString withPtys:(BOOL)ptyFlag { return [self execute:aString withPtys:ptyFlag asynchronously:asynchronous]; } - execute:(const char *)aString asynchronously:(BOOL)async { return [self execute:aString withPtys:usePtys asynchronously:async]; } @end
These are the contents of the former NiCE NeXT User Group NeXTSTEP/OpenStep software archive, currently hosted by Netfuture.ch.