ftp.nice.ch/pub/next/developer/resources/classes/misckit/MiscKit.1.10.0.s.gnutar.gz#/MiscKit/Source/MiscKit/MiscSubprocess.m

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.