home *** CD-ROM | disk | FTP | other *** search
/ Nebula 1994 June / NEBULA_SE.ISO / SourceCode / MiscKit / Source / MiscSubprocess.m < prev    next >
Encoding:
Text File  |  1994-02-25  |  8.6 KB  |  400 lines

  1. //
  2. //    MiscSubprocess.m -- a Obj-C wrapper around Unix child processes
  3. //        Originally written by Drew Davidson (c) 1994 by Drew Davidson.
  4. //        Modified by Don Yacktman for inclusion into the MiscKit.
  5. //                Version 1.0.  All rights reserved.
  6. //        This notice may not be removed from this source code.
  7. //
  8. //    This object is included in the MiscKit by permission from the author
  9. //    and its use is governed by the MiscKit license, found in the file
  10. //    "LICENSE.rtf" in the MiscKit distribution.  Please refer to that file
  11. //    for a list of all applicable permissions and restrictions.
  12. //    
  13.  
  14. /*----------------------------------------------------------------------------
  15.     $Source$
  16.  
  17.     SYNOPSIS
  18.         Handles a subprocess that is fork()'ed and handles delegate notification
  19.     of events that occur such as output and errors.
  20.  
  21.     From Subprocess example by Charles L. Oei
  22.                                     pty support by Joe Freeman
  23.                                     with encouragement from Kristofer Younger
  24.                                     Subprocess Example, Release 2.0
  25.                                     NeXT Computer, Inc.
  26.                                     
  27.     Modified to support signals SIGSTOP and SIGCONT, and to wait for error
  28.     code from terminated process, and to add delegate methods for the output by
  29.     Drew Davidson
  30.     
  31.     REVISIONS
  32.     $Log$
  33. ----------------------------------------------------------------------------*/
  34. #import <libc.h>
  35. #import <misckit/misckit.h>
  36.  
  37. extern int    wait4(int, union wait *, int, struct rusage *);
  38. static void    stdoutFdHandler(int theFd, id self);
  39. static void    stderrFdHandler(int theFd, id self);
  40.  
  41. #define PIPE_ERROR    "Error starting UNIX pipes to subprocess."
  42. #define VFORK_ERROR    "Error starting UNIX vfork of subprocess."
  43. #define EXEC_ERROR    "Error starting UNIX exec of subprocess."
  44.  
  45. @interface MiscSubprocess(private)
  46.  
  47. - _childDidExit;
  48. - _stdoutBuffer;
  49. - _stderrBuffer;
  50. - _fdHandler:(int)theFd method:(SEL)aSelector buffer:aString;
  51. - _setRunning:(BOOL)yn;
  52.  
  53. @end
  54.  
  55. @implementation MiscSubprocess
  56.  
  57. /*----------------------------< PRIVATE METHODS >----------------------------*/
  58. /*
  59.  * cleanup after a child process exits
  60.  */
  61. - _childDidExit
  62. {    union wait                w;
  63.     int                        status = 0;
  64.     MiscSubprocessEndCode    code = Misc_UnknownEndCode;
  65.     
  66.     if (wait4(childPid,&w,WUNTRACED,NULL) > 0)
  67.     {    IMP        done;
  68.     
  69.         if (WIFEXITED(w))
  70.         {    code = Misc_Exited;
  71.             status = w.w_retcode;
  72.         }
  73.         else
  74.         {    if (WIFSTOPPED(w))
  75.             {    code = Misc_Stopped;
  76.                 status = w.w_stopsig;
  77.             }
  78.             else
  79.             {    if (WIFSIGNALED(w))
  80.                 {    code = Misc_Signaled;
  81.                     status = w.w_termsig;
  82.                 }
  83.             }
  84.         }
  85.         DPSRemoveFD(stdoutFromChild);
  86.         DPSRemoveFD(stderrFromChild);
  87.         fclose(fpFromChild);
  88.         close(stdoutFromChild);
  89.         close(stderrFromChild);
  90.         fclose(fpToChild);
  91.         running = NO;
  92.         done = [delegate methodFor:@selector(subprocess:done::)];
  93.         done(delegate,@selector(subprocess:done::),self,status,code);
  94.     }
  95.     return(self);
  96. }
  97.  
  98. - _stdoutBuffer
  99. {
  100.     return(stdoutBuffer);
  101. }
  102.  
  103. - _stderrBuffer
  104. {
  105.     return(stderrBuffer);
  106. }
  107.  
  108. /*
  109.  * DPS handler for output from subprocess
  110.  */
  111. - _fdHandler:(int)theFd method:(SEL)aSelector buffer:aString
  112. {    int            bufferCount,
  113.                     maxLength;
  114.  
  115.     do
  116.     {    int            currentLength = [aString strlen];
  117.         
  118.         maxLength = [aString capacity] - currentLength;
  119.         bufferCount = read(theFd,[aString buffer] + currentLength,maxLength);
  120.         if (bufferCount <= 0)
  121.         {    if (currentLength > 0)
  122.                 [self flushBuffer:aString];
  123.             [self _childDidExit];
  124.             return(self);
  125.         }
  126.         [aString fixStringLengthAt:(bufferCount + currentLength)];
  127.         if (bufferCount == maxLength)
  128.             [aString setCapacity:([aString capacity] * 2.0)];
  129.     } while (bufferCount == maxLength);
  130.     [self flushBuffer:aString];
  131.     return(self);
  132. }
  133.  
  134. /*
  135.  * DPS handler for output from subprocess
  136.  */
  137. static void stdoutFdHandler(int theFd,id self)
  138. {
  139.     [self _fdHandler:theFd method:@selector(subprocess:output:) buffer:[self _stdoutBuffer]];
  140. }
  141.  
  142. static void stderrFdHandler(int theFd,id self)
  143. {
  144.     [self _fdHandler:theFd method:@selector(subprocess:stderrOutput:) buffer:[self _stderrBuffer]];
  145. }
  146.  
  147. - _setRunning:(BOOL)yn
  148. {
  149.     running = yn;
  150.     return(self);
  151. }
  152.  
  153. /*---------------------------< INIT/FREE METHODS >---------------------------*/
  154. - init
  155. {
  156.     return([self init:NULL]);
  157. }
  158.  
  159. - init:(const char *)aString
  160. {
  161.     return([self init:aString withDelegate:nil]);
  162. }
  163.  
  164. - init:(const char *)aString withDelegate:theDelegate
  165. {
  166.     [super init];
  167.     
  168.     stdoutBuffer = [[MiscString allocFromZone:[self zone]] initCapacity:BUFSIZ];
  169.     stderrBuffer = [[MiscString allocFromZone:[self zone]] initCapacity:BUFSIZ];
  170.     environment = [[MiscStringArray allocFromZone:[self zone]] init];
  171.     [self setDelegate:theDelegate];
  172.     if (aString)
  173.         [self execute:aString];
  174.     return(self);
  175. }
  176.  
  177. - free
  178. {
  179.     [self terminate:self];
  180.     [stdoutBuffer free];
  181.     [stderrBuffer free];
  182.     [environment free];
  183.     return([super free]);
  184. }
  185.  
  186. /*-----------------------------< OTHER METHODS >-----------------------------*/
  187. - execute:(const char *)aString
  188. {    int        pipeTo[2];
  189.     int        pipeFrom[2];
  190.     int        pipeStderr[2];    /* for stderr to different fd */
  191.     int        numFds,
  192.                 fd;
  193.     int        processGroup;
  194.     char        hail[BUFSIZ];
  195.     
  196.     if ([self isRunning])
  197.         return(nil);
  198.     if (pipe(pipeTo) < 0 || pipe(pipeFrom) < 0 || pipe(pipeStderr) < 0)
  199.     {    [delegate perform:@selector(subprocess:error:) with:self with:(void *)PIPE_ERROR];
  200.         return(nil);
  201.     }
  202.     
  203.     switch (childPid = vfork())
  204.     {    case -1:                        /* error */
  205.             [delegate perform:@selector(subprocess:error:) with:self with:(void *)VFORK_ERROR];
  206.             return(self);
  207.             break;
  208.         case 0:                        /* child */
  209.             dup2(pipeTo[0], 0);
  210.             dup2(pipeFrom[1], 1);            /* get stdout from process */
  211.             dup2(pipeStderr[1], 2);            /* get stderr here */
  212.             
  213.             numFds = getdtablesize();
  214.             for (fd = 3; fd < numFds; fd++)
  215.                 close(fd);
  216.             
  217.             processGroup = getpid();
  218.             ioctl(0,TIOCSPGRP,(char *)&processGroup);
  219.             setpgrp(0,processGroup);
  220.             
  221.             fgets(hail,BUFSIZ,stdin);
  222.             
  223.             [self execChild:aString];
  224.             [delegate perform:@selector(subprocess:error:) with:self with:(void *)EXEC_ERROR];
  225.             [self error:"vfork (child) returned!"];
  226.             break;
  227.         default:                    /* parent */
  228.             close(pipeTo[0]);
  229.             close(pipeFrom[1]);
  230.             close(pipeStderr[1]);
  231.  
  232.             fpToChild = fdopen(pipeTo[1], "w");
  233.             stdoutFromChild = pipeFrom[0];
  234.             fpFromChild = fdopen(pipeFrom[0],"r");
  235.                 
  236.             stderrFromChild = pipeStderr[0];
  237.     
  238.             /*
  239.              * Set buffering method, also make it use its own buffers
  240.              */
  241.             setbuf(fpToChild,NULL);                    /* no buffering */
  242.             setbuf(fpFromChild,NULL);
  243.             DPSAddFD(stdoutFromChild,(DPSFDProc)stdoutFdHandler,self,NX_MODALRESPTHRESHOLD+1);
  244.             DPSAddFD(stderrFromChild,(DPSFDProc)stderrFdHandler,self,NX_MODALRESPTHRESHOLD+1);
  245.             running = YES;
  246.             fputs("Run, you bastard!\n",fpToChild);
  247.             break;
  248.     }
  249.     return(self);
  250. }
  251.  
  252. - execChild:(const char *)aString
  253. {
  254.     /*
  255.      * we exec a /bin/sh so that cmds are easier to specify for the user
  256.      */
  257.     execle("/bin/sh", "sh", "-c", aString, 0, [environment stringArray]);
  258.     return(self);
  259. }
  260.  
  261. - setDelegate:anObject
  262. {
  263.     delegate = anObject;
  264.     return(self);
  265. }
  266.  
  267. - delegate
  268. {
  269.     return(delegate);
  270. }
  271.  
  272. - environment
  273. {
  274.     return(environment);
  275. }
  276.  
  277. - (SEL)outputMethodForBuffer:aBuffer
  278. {    SEL        aSelector;
  279.  
  280.     if (aBuffer == [self _stdoutBuffer])
  281.         aSelector = @selector(subprocess:output:);
  282.     else
  283.     {    if (aBuffer == [self _stderrBuffer])
  284.             aSelector = @selector(subprocess:stderrOutput:);
  285.         else
  286.             aSelector = NULL;
  287.     }
  288.     return(aSelector);
  289. }
  290.  
  291. - flushBuffer:aString as:aBuffer
  292. {
  293.     if ([aString strlen] > 0)
  294.     {    SEL        aSelector = [self outputMethodForBuffer:aBuffer];
  295.     
  296.         if (aSelector)
  297.             [delegate perform:aSelector with:self with:(id)[aString stringValue]];
  298.         [aString freeString];
  299.     }
  300.     return(nil);
  301. }
  302.  
  303. - flushBuffer:aString
  304. {
  305.     return([self flushBuffer:aString as:aString]);
  306. }
  307.  
  308. - send:(const char *)string withNewline:(BOOL)wantNewline
  309. {
  310.     fputs(string,fpToChild);
  311.     if (wantNewline)
  312.         fputc('\n',fpToChild);
  313.     return(self);
  314. }
  315.  
  316. - send:(const char *)string
  317. {
  318.     [self send:string withNewline:YES];
  319.     return(self);
  320. }
  321.  
  322. /*
  323.  * Returns the process id of the process (and therefore the process group
  324.  * of the job)
  325.  */
  326. - (int)pid
  327. {
  328.     return(childPid);
  329. }
  330.  
  331. - pause:sender
  332. {
  333.     if (!paused)
  334.     {    killpg(childPid,SIGSTOP);            /* pause the process group */
  335.         paused = YES;
  336.     }
  337.     return(self);
  338. }
  339.  
  340. - resume:sender
  341. {
  342.     if (paused)
  343.     {    killpg(childPid,SIGCONT);            /* resume the process group */
  344.         paused = NO;
  345.     }
  346.     return(self);
  347. }
  348.  
  349. - (BOOL)isPaused
  350. {
  351.     return(paused);
  352. }
  353.  
  354. - (BOOL)isRunning
  355. {
  356.     return(running);
  357. }
  358.  
  359. - terminate:sender
  360. {
  361.     if (running)
  362.         killpg(childPid,SIGKILL);
  363.     return(self);
  364. }
  365.  
  366. /*
  367.  * effectively sends an EOF to the child process stdin
  368.  */
  369. - terminateInput
  370. {
  371.     fclose(fpToChild);
  372.     return(self);
  373. }
  374.  
  375. @end
  376.  
  377. @implementation Object(MiscSubprocessDelegate)
  378.  
  379. - subprocess:sender done:(int)status :(MiscSubprocessEndCode)code
  380. {
  381.     return(self);
  382. }
  383.  
  384. - subprocess:sender output:(const char *)buffer
  385. {
  386.     return(self);
  387. }
  388.  
  389. - subprocess:sender stderrOutput:(const char *)buffer
  390. {
  391.     return(self);
  392. }
  393.  
  394. - subprocess:sender error:(const char *)errorString
  395. {
  396.     perror(errorString);
  397.     return(self);
  398. }
  399.  
  400. @end