home *** CD-ROM | disk | FTP | other *** search
- /*----------------------------------------------------------------------------
- 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
-
-