ftp.nice.ch/pub/next/developer/resources/classes/SubprocessPlus.s.tar.gz#/SubprocessPlus/Subprocess.m

This is Subprocess.m in view mode; [Download] [Up]

/*----------------------------------------------------------------------------
	Subprocess.m
	
	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.
	
	You may freely copy, distribute and reuse the code in this example.
	NeXT disclaims any warranty of any kind, expressed or implied, as to
	its fitness for any particular use.
		
	SYNOPSIS
		Handles a UNIX process that runs asynchronously.

	REVISIONS
	$Log$
----------------------------------------------------------------------------*/
# import <sys/wait.h>
# import <sys/resource.h>
# import <appkit/nextstd.h>
# import <appkit/Application.h>
# import <appkit/Panel.h>
# import <nextutils.h>
# import "Subprocess.h"

extern int		wait4(int, union wait *, int, struct rusage *);
static void		fdHandler(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."

@implementation Subprocess

/*---------------------------< INIT/FREE METHODS >---------------------------*/
/*
 * Cover for the init:withDelegate: with a nil delegate
 */
- init:(const char *)subprocessString
{
	return([self init:subprocessString
					withDelegate:nil]);
}

- init:(const char *)subprocessString withDelegate:theDelegate
{	int		pipeTo[2];
	int		pipeFrom[2];
	int		pipeStderr[2];	/* for stderr to different fd */
	int		numFds,
				fd;
	int		processGroup;
	
	[super init];
	
	outputBufferLen = 0;
	stderrBufferLen = 0;
	[self setDelegate:theDelegate];
	
	if (pipe(pipeTo) < 0 || pipe(pipeFrom) < 0 || pipe(pipeStderr) < 0)
	{	[delegate perform:@selector(subprocess:error:)
						 with:self
	    				 with:(void *)PIPE_ERROR];
		return(self);
	}
	
	switch (childPid = vfork())
	{	case -1:						/* error */
			[delegate perform:@selector(subprocess:error:)
							 with:self
	    					 with:(void *)VFORK_ERROR];
			return(self);
	
		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);
			
			/*
			 * we exec a /bin/sh so that cmds are easier to specify for the user
			 */
			execl("/bin/sh", "sh", "-c", subprocessString, 0);
			perror("vfork (child)");		/* should never gets here tho */
			exit(1);
		
		default:					/* parent */
			running = YES;
			
			close(pipeTo[0]);
			close(pipeFrom[1]);
			close(pipeStderr[1]);

			fpToChild = fdopen(pipeTo[1], "w");
			fromChild = 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(fromChild,(DPSFDProc)fdHandler,(id)self,
						NX_MODALRESPTHRESHOLD+1);

			DPSAddFD(stderrFromChild,(DPSFDProc)stderrFdHandler,(id)self,
						NX_MODALRESPTHRESHOLD+1);
			
			return(self);
	}
}

/*-----------------------------< OTHER METHODS >-----------------------------*/
/*
 * cleanup after a child process exits
 */
- childDidExit
{	union wait		w;
	int				status = 0;
	int				thePid;
	
	thePid = wait4(childPid,&w,WUNTRACED,NULL);
# ifdef DEBUG
	fprintf(stderr,"%s: wait4() on process id #%d returned %d, with "
				"w_status = %d, w_retcode = %d, w_stopval = %d, "
				"w_stopsig = %d, w_termsig = %d\n",
				[self name],childPid,thePid,w.w_status,w.w_retcode,
				w.w_stopval,w.w_stopsig,w.w_termsig);
# endif
	if (thePid > 0)
	{	
		if (WIFEXITED(w))
			status = (w.w_status >> 8);
		else
		{	if (WIFSTOPPED(w))
				status = SUBPROCESS_STOPPED;
			else
			{	if (WIFSIGNALED(w))
					status = SUBPROCESS_SIGNALED;
			}
		}
		DPSRemoveFD(fromChild);
		DPSRemoveFD(stderrFromChild);
		fclose(fpFromChild);
		close(fromChild);
		close(stderrFromChild);
		fclose(fpToChild);
		running = NO;
		[delegate perform:@selector(subprocess:done:)
							with:self
							with:(void *)status];
	}
	return(self);
}

/*
 * DPS handler for output from subprocess
 */
- fdHandler:(int)theFd
{	char		*s,
				*linep;
	int		bufferCount;
		
	bufferCount = read(theFd,outputBuffer+outputBufferLen,
								BUFSIZ-outputBufferLen);
	if (bufferCount <= 0)
	{	[self childDidExit];
		return(self);
	}
	outputBuffer[bufferCount + outputBufferLen] = '\0';

	/*
	 * Send lines in the buffer to the delegate
	 */
	s = linep = outputBuffer;
	while (s != NULL)
	{	if ((s = index(linep,'\n')) != NULL)
		{	*s = EOSTR;
			[delegate perform:@selector(subprocess:output:)
							 with:self
							 with:(void *)linep];
			linep = s+1;
		}
	}
	
	/*
	 * Copy the last part of the line back into the input buffer for next
	 * time (incomplete line)
	 */
	outputBufferLen = strlen(linep);
	strncpy(outputBuffer,linep,outputBufferLen);
	
	return(self);
}

/*
 * DPS handler for output from subprocess
 */
static void fdHandler(int theFd, id self)
{
	[self fdHandler:theFd];
}

- stderrFdHandler:(int)theFd
{	char		*s,
				*linep;
	int		bufferCount;
				
	bufferCount = read(theFd,stderrBuffer+stderrBufferLen,
								BUFSIZ-stderrBufferLen);
	if (bufferCount <= 0)
		return(self);

	stderrBuffer[bufferCount + stderrBufferLen] = '\0';

	/*
	 * Send lines in the buffer to the delegate
	 */
	s = linep = stderrBuffer;
	while (s != NULL)
	{	if ((s = index(linep,'\n')) != NULL)
		{	*s = EOSTR;
			[delegate perform:@selector(subprocess:stderrOutput:)
							 with:self
							 with:(void *)linep];
			linep = s+1;
		}
	}
	
	/*
	 * Copy the last part of the line back into the input buffer for next
	 * time (incomplete line)
	 */
	stderrBufferLen = strlen(linep);
	strncpy(stderrBuffer,linep,stderrBufferLen);
	
	return(self);
}

/*
 * And standard error from subprocess
 */
static void stderrFdHandler(int theFd, id self)
{
	[self stderrFdHandler:theFd];
}

- 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);
}

- (BOOL)isPaused
{
	return(paused);
}

- resume:sender
{
	if (paused)
	{	killpg(childPid,SIGCONT);			/* resume the process group */
		paused = NO;
	}
	return(self);
}

- pause:sender
{
	if (!paused)
	{	killpg(childPid,SIGSTOP);			/* pause the process group */
		paused = YES;
	}
	return(self);
}

- (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);
}

- setDelegate:anObject
{
	delegate = anObject;
	return(self);
}

- delegate
{
	return(delegate);
}

@end

@implementation Object(SubprocessDelegate)

- subprocess:sender done:(int)exitStatus
{
	return(self);
}

- subprocess:sender output:(char *)buffer
{
	return(self);
}

- subprocess:sender stderrOutput:(char *)buffer
{
	return(self);
}

- subprocess:sender error:(const char *)errorString
{
	if (NXApp)
		NXRunAlertPanel(0, errorString, 0, 0, 0);
	else
		perror(errorString);
	return(self);
}

@end

These are the contents of the former NiCE NeXT User Group NeXTSTEP/OpenStep software archive, currently hosted by Netfuture.ch.