home *** CD-ROM | disk | FTP | other *** search
/ Nebula 1995 August / NEBULA.mdf / SourceCode / MiscKit1.2.6 / Source / MiscSubprocess.m < prev    next >
Encoding:
Text File  |  1994-05-30  |  12.0 KB  |  510 lines

  1. //
  2. //    MiscSubprocess.m -- a Obj-C wrapper around Unix child processes
  3. //        Originally written by Drew Davidson.
  4. //        Copyright (c) 1994 by Drew Davidson.
  5. //        Modified by Don Yacktman for inclusion into the MiscKit.
  6. //        Fixed up by Carl Lindberg and Don Yacktman.
  7. //                Version 1.2.  All rights reserved.
  8. //        This notice may not be removed from this source code.
  9. //
  10. //    This object is included in the MiscKit by permission from the author
  11. //    and its use is governed by the MiscKit license, found in the file
  12. //    "LICENSE.rtf" in the MiscKit distribution.  Please refer to that file
  13. //    for a list of all applicable permissions and restrictions.
  14. //    
  15.  
  16. /*----------------------------------------------------------------------------
  17.     $Source$
  18.  
  19.     SYNOPSIS
  20.         Handles a subprocess that is fork()'ed and handles delegate
  21.         notification of events that occur such as output and errors.
  22.  
  23.     From Subprocess example by Charles L. Oei
  24.             pty support (removed) by Joe Freeman
  25.             with encouragement from Kristofer Younger
  26.             Subprocess Example, Release 2.0
  27.             NeXT Computer, Inc.
  28.                                     
  29.     Modified to support signals SIGSTOP and SIGCONT, and to wait for error
  30.     code from terminated process, and to add delegate methods for the output by
  31.     Drew Davidson
  32.     
  33.     Modified for MiscKit inclusion by Don Yacktman
  34.     Debugged by Carl Lindberg and Don Yacktman
  35.     PTY support added back in by Carl Lindberg
  36.     
  37.     REVISIONS
  38.     $Log$
  39. ----------------------------------------------------------------------------*/
  40. #import <libc.h>
  41. #import <misckit/MiscString.h>
  42. #import <misckit/MiscStringArray.h>
  43. #import <misckit/MiscSubprocess.h>
  44.  
  45. extern int    wait4(int, union wait *, int, struct rusage *);
  46. static void    stdoutFdHandler(int theFd, id self);
  47. static void    stderrFdHandler(int theFd, id self);
  48.  
  49. #define PIPE_ERROR    "Error starting UNIX pipes to subprocess."
  50. #define VFORK_ERROR    "Error starting UNIX vfork of subprocess."
  51. #define EXEC_ERROR    "Error starting UNIX exec of subprocess."
  52. #define    PTY_TEMPLATE "/dev/pty??"
  53. #define    PTY_LENGTH 11
  54.  
  55. @interface MiscSubprocess(private)
  56.  
  57. - _childDidExit;
  58. - _stdoutBuffer;
  59. - _stderrBuffer;
  60. - _fdHandler:(int)theFd method:(SEL)aSelector buffer:aString;
  61. - _setRunning:(BOOL)yn;
  62.  
  63. @end
  64.  
  65. static void
  66. getptys (int *master, int *slave)
  67.     // attempt to setup the ptys
  68. {
  69.     char device[PTY_LENGTH];
  70.     char *block, *num;
  71.     char *blockLoc; // specifies the location of block for the device string
  72.     char *numLoc; // specifies the pty name with the digit ptyxD
  73.     char *msLoc; // specifies the master (ptyxx) or slave (ttyxx)
  74.     
  75.     struct sgttyb setp =
  76.     {B9600, B9600, (char)0x7f, (char)0x15, (CRMOD|ANYP)};
  77.     struct tchars setc =
  78.     {CINTR, CQUIT, CSTART, CSTOP, CEOF, CBRK};
  79.     struct ltchars sltc =
  80.     {CSUSP, CDSUSP, CRPRNT, CFLUSH, CWERASE, CLNEXT};
  81.     int    lset =
  82.     (LCRTBS|LCRTERA|LCRTKIL|LCTLECH|LPENDIN|LDECCTQ);
  83.     int    setd = NTTYDISC;
  84.     
  85.     strcpy(device, PTY_TEMPLATE); // string constants are not writable
  86.     blockLoc = &device[ strlen("/dev/pty") ];
  87.     numLoc = &device[ strlen("/dev/pty?") ];
  88.     msLoc = &device[ strlen("/dev/") ];
  89.     for (block = "pqrs"; *block; block++)
  90.     {
  91.     *blockLoc = *block;
  92.     for (num = "0123456789abcdef"; *num; num++)
  93.     {
  94.         *numLoc = *num;
  95.         *master = open(device, O_RDWR);
  96.         if (*master >= 0)
  97.         {
  98.         *msLoc = 't';
  99.         *slave = open(device, O_RDWR);
  100.         if (*slave >= 0)
  101.         {
  102.             (void) ioctl(*slave, TIOCSETP, (char *)&setp);
  103.             (void) ioctl(*slave, TIOCSETC, (char *)&setc);
  104.             (void) ioctl(*slave, TIOCSETD, (char *)&setd);
  105.             (void) ioctl(*slave, TIOCSLTC, (char *)&sltc);
  106.             (void) ioctl(*slave, TIOCLSET, (char *)&lset);
  107.             return;
  108.         }
  109.         }
  110.     } /* hunting through a bank of ptys */
  111.     } /* hunting through blocks of ptys in all the right places */
  112.     *master = -1;
  113.     *slave = -1;
  114. }
  115.  
  116.  
  117. @implementation MiscSubprocess
  118.  
  119. /*----------------------------< PRIVATE METHODS >----------------------------*/
  120. /*
  121.  * cleanup after a child process exits
  122.  */
  123. - _childDidExit
  124. {
  125.     union wait w; int status = 0;
  126.     MiscSubprocessEndCode code = Misc_UnknownEndCode;
  127.  
  128.     if (wait4(childPid,&w,WUNTRACED,NULL) > 0) {
  129.         IMP done;
  130.         if (WIFEXITED(w)) {
  131.             code = Misc_Exited;
  132.             status = w.w_retcode;
  133.         } else {
  134.             if (WIFSTOPPED(w)) {
  135.                 code = Misc_Stopped;
  136.                 status = w.w_stopsig;
  137.             } else {
  138.                 if (WIFSIGNALED(w)) {
  139.                     code = Misc_Signaled;
  140.                     status = w.w_termsig;
  141.                 }
  142.             }
  143.         }
  144.         DPSRemoveFD(stdoutFromChild);
  145.         DPSRemoveFD(stderrFromChild);
  146.         fclose(fpFromChild);
  147.         close(stdoutFromChild);
  148.         close(stderrFromChild);
  149.         fclose(fpToChild);
  150.         running = NO;
  151.         done = [delegate methodFor:@selector(subprocess:done::)];
  152.         done(delegate,@selector(subprocess:done::),self,status,code);
  153.     }
  154.     return self;
  155. }
  156.  
  157. - _stdoutBuffer
  158. {
  159.     return stdoutBuffer;
  160. }
  161.  
  162. - _stderrBuffer
  163. {
  164.     return stderrBuffer;
  165. }
  166.  
  167. /*
  168.  * DPS handler for output from subprocess
  169.  */
  170.  
  171. - _fdHandler:(int)theFd method:(SEL)aSelector buffer:aString
  172. { // re-written by Carl Lindberg to make it safer.
  173.     int  bufferCount, currleft = BUFSIZ / 2;
  174.     do {
  175.         int  currentLength = [aString length];
  176.         char buf[(currleft * 2) + 1];
  177.  
  178.         currleft *=2;
  179.         bufferCount = read(theFd, buf, currleft);
  180.         if (bufferCount <= 0) {
  181.             if (currentLength > 0)
  182.                 [self flushBuffer:aString];
  183.             [self _childDidExit];
  184.             return self;
  185.         }
  186.         buf[bufferCount] = 0;
  187.         [aString cat:buf];
  188.     } while (bufferCount == currleft);
  189.     [self flushBuffer:aString];
  190.     return self;
  191. }
  192.  
  193. /*
  194.  * DPS handler for output from subprocess
  195.  */
  196. static void stdoutFdHandler(int theFd,id self)
  197. {
  198.     [self _fdHandler:theFd method:@selector(subprocess:output:)
  199.             buffer:[self _stdoutBuffer]];
  200. }
  201.  
  202. static void stderrFdHandler(int theFd,id self)
  203. {
  204.     [self _fdHandler:theFd method:@selector(subprocess:stderrOutput:)
  205.             buffer:[self _stderrBuffer]];
  206. }
  207.  
  208. - _setRunning:(BOOL)yn
  209. {
  210.     running = yn;
  211.     return self;
  212. }
  213.  
  214. /*---------------------------< INIT/FREE METHODS >---------------------------*/
  215. - init
  216. {
  217.     return [self init:NULL];
  218. }
  219.  
  220. - init:(const char *)aString
  221. {
  222.     return [self init:aString withDelegate:nil];
  223. }
  224.  
  225. - init:(const char *)aString withDelegate:theDelegate
  226. {
  227.     return [self init:aString withDelegate:theDelegate 
  228.              keepEnvironment:YES withPtys:NO];
  229. }
  230.  
  231. - init:(const char *)aString withDelegate:theDelegate
  232.         keepEnvironment:(BOOL)flag
  233. {
  234.     return [self init:aString withDelegate:theDelegate 
  235.                  keepEnvironment:YES withPtys:NO];
  236. }
  237.  
  238. - init:(const char *)aString withDelegate:theDelegate
  239.         keepEnvironment:(BOOL)flag withPtys:(BOOL)ptyFlag
  240. {
  241.     [super init];
  242.  
  243.     stdoutBuffer = [[MiscString allocFromZone:[self zone]]
  244.         initCapacity:BUFSIZ];
  245.     stderrBuffer = [[MiscString allocFromZone:[self zone]] 
  246.         initCapacity:BUFSIZ];
  247.     environment = [[MiscStringArray allocFromZone:[self zone]] init];
  248.     if (flag) {
  249.         int i;
  250.         for (i=0; environ[i]; i++)
  251.             [environment addString:environ[i]];
  252.     }
  253.     [self setDelegate:theDelegate];
  254.     if (aString) [self execute:aString withPtys:ptyFlag];
  255.     return self;
  256. }
  257.  
  258. - free
  259. {
  260.     [self terminate:self];
  261.     [stdoutBuffer free];
  262.     [stderrBuffer free];
  263.     [environment free];
  264.     return [super free];
  265. }
  266.  
  267. /*-----------------------------< OTHER METHODS >-----------------------------*/
  268. - execute:(const char *)aString
  269. { return [self execute:aString withPtys:NO];}
  270.  
  271. - execute:(const char *)aString withPtys:(BOOL)ptyFlag
  272. {
  273.     int pipeTo[2], pipeFrom[2], pipeStderr[2]; // for stderr to different fd
  274.     int tty, numFds, fd, processGroup;
  275.     char hail[BUFSIZ];
  276.  
  277.     if ([self isRunning]) return nil;
  278.     if (ptyFlag)
  279.     {
  280.           tty = open("/dev/tty", O_RDWR);
  281.       getptys(&masterPty,&slavePty);
  282.       if (masterPty <= 0 || slavePty <= 0 || pipe(pipeStderr) < 0) {
  283.         [delegate perform:@selector(subprocess:error:) with:self 
  284.               with:(void *)"Error grabbing ptys for subprocess."];
  285.         return self;
  286.       }
  287.       // remove the controlling tty if launched from a shell,
  288.       // but not Workspace;
  289.       // so that we have job control over the parent application in shell
  290.       // and so that subprocesses can be restarted in Workspace
  291.       if  ((tty<0) && ((tty = open("/dev/tty", 2))>=0)) {
  292.         printf("shouldn't be here\n");
  293.         ioctl(tty, TIOCNOTTY, 0);
  294.         close(tty);
  295.       }
  296.         }
  297.     else {
  298.       if (pipe(pipeTo) < 0 || pipe(pipeFrom) < 0 || pipe(pipeStderr) < 0) {
  299.           [delegate perform:@selector(subprocess:error:)
  300.                 with:self with:(void *)PIPE_ERROR];
  301.           return nil;
  302.         }
  303.     }
  304.     switch (childPid = vfork()) {
  305.         case -1:                            /* error */
  306.             [delegate perform:@selector(subprocess:error:)
  307.                     with:self with:(void *)VFORK_ERROR];
  308.             return self;
  309.             break;
  310.         case 0:                                /* child */
  311.             if (ptyFlag) {
  312.               dup2(slavePty, 0);
  313.               dup2(slavePty, 1);
  314.               dup2(pipeStderr[1], 2);  //stderr
  315.              }
  316.             else {
  317.               dup2(pipeTo[0], 0);
  318.               dup2(pipeFrom[1], 1);            /* get stdout from process */
  319.               dup2(pipeStderr[1], 2);            /* get stderr here */
  320.              }
  321.             numFds = getdtablesize();
  322.             for (fd = 3; fd < numFds; fd++) close(fd);
  323.             processGroup = getpid();
  324.             ioctl(0, TIOCSPGRP, (char *)&processGroup);
  325.             setpgrp(0, processGroup);
  326.             fgets(hail, BUFSIZ, stdin);        // wait for parent
  327.             [self execChild:aString];
  328.             [delegate perform:@selector(subprocess:error:)
  329.                     with:self with:(void *)EXEC_ERROR];
  330.             [self error:"vfork (child) returned!"];
  331.             break;
  332.         default:                            /* parent */
  333.             if (ptyFlag) {
  334.               close(slavePty);
  335.               close(pipeStderr[1]);
  336.               fpToChild = fdopen(masterPty,"w");
  337.               stdoutFromChild = masterPty;
  338.               stderrFromChild = pipeStderr[0];
  339.              }
  340.             else {
  341.               close(pipeTo[0]);
  342.               close(pipeFrom[1]);
  343.               close(pipeStderr[1]);
  344.               fpToChild = fdopen(pipeTo[1], "w");
  345.               stdoutFromChild = pipeFrom[0];
  346.               fpFromChild = fdopen(pipeFrom[0],"r");
  347.               stderrFromChild = pipeStderr[0];
  348.              }
  349.             // Set buffering method, also make it use its own buffers
  350.             setbuf(fpToChild, NULL);                    /* no buffering */
  351.             if (!ptyFlag) setbuf(fpFromChild, NULL);
  352.             DPSAddFD(stdoutFromChild, (DPSFDProc)stdoutFdHandler,
  353.                     self, NX_MODALRESPTHRESHOLD + 1);
  354.             DPSAddFD(stderrFromChild, (DPSFDProc)stderrFdHandler,
  355.                     self, NX_MODALRESPTHRESHOLD + 1);
  356.             running = YES;
  357.             fputs("Run away!  Run away!\n", fpToChild); // tell child to go
  358.             break;
  359.     }
  360.     return self;
  361. }
  362.  
  363. - execChild:(const char *)aString
  364. {
  365.     /*
  366.      * we exec a /bin/sh so that cmds are easier to specify for the user
  367.      */
  368.     execle("/bin/sh", "sh", "-c", aString, 0, [environment stringArray]);
  369.     return self;
  370. }
  371.  
  372. - setDelegate:anObject
  373. {
  374.     delegate = anObject;
  375.     return self;
  376. }
  377.  
  378. - delegate
  379. {
  380.     return delegate;
  381. }
  382.  
  383. - environment
  384. {
  385.     return environment;
  386. }
  387.  
  388. - (SEL)outputMethodForBuffer:aBuffer
  389. {
  390.     SEL aSelector;
  391.     if (aBuffer == [self _stdoutBuffer])
  392.         aSelector = @selector(subprocess:output:);
  393.     else {
  394.         if (aBuffer == [self _stderrBuffer])
  395.             aSelector = @selector(subprocess:stderrOutput:);
  396.         else aSelector = NULL;
  397.     }
  398.     return aSelector;
  399. }
  400.  
  401. - flushBuffer:aString as:aBuffer
  402. {
  403.     if ([aString strlen] > 0) {
  404.         SEL aSelector = [self outputMethodForBuffer:aBuffer];
  405.         if (aSelector)
  406.             [delegate perform:aSelector
  407.                     with:self with:(id)[aString stringValue]];
  408.         [aString setStringValue:""];
  409.     }
  410.     return nil;
  411. }
  412.  
  413. - flushBuffer:aString
  414. {
  415.     return [self flushBuffer:aString as:aString];
  416. }
  417.  
  418. - send:(const char *)string withNewline:(BOOL)wantNewline
  419. {
  420.     fputs(string,fpToChild);
  421.     if (wantNewline)
  422.         fputc('\n',fpToChild);
  423.     return self;
  424. }
  425.  
  426. - send:(const char *)string
  427. {
  428.     [self send:string withNewline:YES];
  429.     return self;
  430. }
  431.  
  432. /*
  433.  * Returns the process id of the process (and therefore the process group
  434.  * of the job)
  435.  */
  436. - (int)pid
  437. {
  438.     return childPid;
  439. }
  440.  
  441. - pause:sender
  442. {
  443.     if (!paused) {
  444.         killpg(childPid,SIGSTOP);            /* pause the process group */
  445.         paused = YES;
  446.     }
  447.     return self;
  448. }
  449.  
  450. - resume:sender
  451. {
  452.     if (paused) {
  453.         killpg(childPid,SIGCONT);            /* resume the process group */
  454.         paused = NO;
  455.     }
  456.     return self;
  457. }
  458.  
  459. - (BOOL)isPaused
  460. {
  461.     return paused;
  462. }
  463.  
  464. - (BOOL)isRunning
  465. {
  466.     return running;
  467. }
  468.  
  469. - terminate:sender
  470. {
  471.     if (running)
  472.         killpg(childPid,SIGKILL);
  473.     return self;
  474. }
  475.  
  476. /*
  477.  * effectively sends an EOF to the child process stdin
  478.  */
  479. - terminateInput
  480. {
  481.     fclose(fpToChild);
  482.     return self;
  483. }
  484.  
  485. @end
  486.  
  487. @implementation Object(MiscSubprocessDelegate)
  488.  
  489. - subprocess:sender done:(int)status :(MiscSubprocessEndCode)code
  490. {
  491.     return self;
  492. }
  493.  
  494. - subprocess:sender output:(const char *)buffer
  495. {
  496.     return self;
  497. }
  498.  
  499. - subprocess:sender stderrOutput:(const char *)buffer
  500. {
  501.     return self;
  502. }
  503.  
  504. - subprocess:sender error:(const char *)errorString
  505. {
  506.     perror(errorString);
  507.     return self;
  508. }
  509.  
  510. @end