OSDN Git Service

Please enter the commit message for your changes. Lines starting
[eos/base.git] / util / src / TclTk / blt2.5 / generic / bltUnixPipe.c
1 /*
2  * bltUnixPipe.c --
3  *
4  *      Originally taken from tclPipe.c and tclUnixPipe.c in the Tcl
5  *      distribution, implements the former Tcl_CreatePipeline API.
6  *      This file contains the generic portion of the command channel
7  *      driver as well as various utility routines used in managing
8  *      subprocesses.
9  *
10  * Copyright (c) 1997 by Sun Microsystems, Inc.
11  *
12  * See the file "license.terms" for information on usage and redistribution
13  * of this file, and for a DISCLAIMER OF ALL WARRANTIES.
14  *
15  */
16
17 #include "bltInt.h"
18 #include <fcntl.h>
19 #include <signal.h>
20
21 #include "bltWait.h"
22
23 #if (TCL_MAJOR_VERSION == 7)
24 typedef pid_t Tcl_Pid;
25
26 #define FILEHANDLER_USES_TCLFILES 1
27
28 static int
29 Tcl_GetChannelHandle(channel, direction, clientDataPtr)
30     Tcl_Channel channel;
31     int direction;
32     ClientData *clientDataPtr;
33 {
34     Tcl_File file;
35
36     file = Tcl_GetChannelFile(channel, direction);
37     if (file == NULL) {
38         return TCL_ERROR;
39     }
40     *clientDataPtr = (ClientData)Tcl_GetFileInfo(file, NULL);
41     return TCL_OK;
42 }
43
44 #else
45 typedef int Tcl_File;
46 #endif /* TCL_MAJOR_VERSION == 7 */
47
48 /*
49  *----------------------------------------------------------------------
50  *
51  * OpenFile --
52  *
53  *      Open a file for use in a pipeline.
54  *
55  * Results:
56  *      Returns a new TclFile handle or NULL on failure.
57  *
58  * Side effects:
59  *      May cause a file to be created on the file system.
60  *
61  *----------------------------------------------------------------------
62  */
63
64 static int
65 OpenFile(fname, mode)
66     char *fname;                /* The name of the file to open. */
67     int mode;                   /* In what mode to open the file? */
68 {
69     int fd;
70
71     fd = open(fname, mode, 0666);
72     if (fd != -1) {
73         fcntl(fd, F_SETFD, FD_CLOEXEC);
74
75         /*
76          * If the file is being opened for writing, seek to the end
77          * so we can append to any data already in the file.
78          */
79
80         if (mode & O_WRONLY) {
81             lseek(fd, 0, SEEK_END);
82         }
83         return fd;
84     }
85     return -1;
86 }
87 \f
88 /*
89  *----------------------------------------------------------------------
90  *
91  * CreateTempFile --
92  *
93  *      This function creates a temporary file initialized with an
94  *      optional string, and returns a file handle with the file pointer
95  *      at the beginning of the file.
96  *
97  * Results:
98  *      A handle to a file.
99  *
100  * Side effects:
101  *      None.
102  *
103  *----------------------------------------------------------------------
104  */
105
106 static int
107 CreateTempFile(contents)
108     char *contents;             /* String to write into temp file, or NULL. */
109 {
110     char fileName[L_tmpnam];
111     int fd;
112     size_t length = (contents == NULL) ? 0 : strlen(contents);
113
114     mkstemp(fileName);
115     fd = OpenFile(fileName, O_RDWR | O_CREAT | O_TRUNC);
116     unlink(fileName);
117
118     if ((fd >= 0) && (length > 0)) {
119         for (;;) {
120             if (write(fd, contents, length) != -1) {
121                 break;
122             } else if (errno != EINTR) {
123                 close(fd);
124                 return -1;
125             }
126         }
127         lseek(fd, 0, SEEK_SET);
128     }
129     return fd;
130 }
131 \f
132 /*
133  *----------------------------------------------------------------------
134  *
135  * CreatePipe --
136  *
137  *      Creates a pipe - simply calls the pipe() function.
138  *
139  * Results:
140  *      Returns 1 on success, 0 on failure.
141  *
142  * Side effects:
143  *      Creates a pipe.
144  *
145  *----------------------------------------------------------------------
146  */
147
148 static int
149 CreatePipe(inFilePtr, outFilePtr)
150     int *inFilePtr;             /* (out) Descriptor for read side of pipe. */
151     int *outFilePtr;            /* (out) Descriptor for write side of pipe. */
152 {
153     int pipeIds[2];
154
155     if (pipe(pipeIds) != 0) {
156         return 0;
157     }
158     fcntl(pipeIds[0], F_SETFD, FD_CLOEXEC);
159     fcntl(pipeIds[1], F_SETFD, FD_CLOEXEC);
160
161     *inFilePtr = pipeIds[0];
162     *outFilePtr = pipeIds[1];
163     return 1;
164 }
165
166 /*
167  *----------------------------------------------------------------------
168  *
169  * CloseFile --
170  *
171  *      Implements a mechanism to close a UNIX file.
172  *
173  * Results:
174  *      Returns 0 on success, or -1 on error, setting errno.
175  *
176  * Side effects:
177  *      The file is closed.
178  *
179  *----------------------------------------------------------------------
180  */
181
182 static int
183 CloseFile(fd)
184     int fd;                     /* File descriptor to be closed. */
185 {
186     if ((fd == 0) || (fd == 1) || (fd == 2)) {
187         return 0;               /* Don't close stdin, stdout or stderr. */
188     }
189 #if (TCL_MAJOR_VERSION > 7)
190     Tcl_DeleteFileHandler(fd);
191 #endif
192     return close(fd);
193 }
194 \f
195 /*
196  *----------------------------------------------------------------------
197  *
198  * RestoreSignals --
199  *
200  *      This procedure is invoked in a forked child process just before
201  *      exec-ing a new program to restore all signals to their default
202  *      settings.
203  *
204  * Results:
205  *      None.
206  *
207  * Side effects:
208  *      Signal settings get changed.
209  *
210  *----------------------------------------------------------------------
211  */
212
213 static void
214 RestoreSignals()
215 {
216 #ifdef SIGABRT
217     signal(SIGABRT, SIG_DFL);
218 #endif
219 #ifdef SIGALRM
220     signal(SIGALRM, SIG_DFL);
221 #endif
222 #ifdef SIGFPE
223     signal(SIGFPE, SIG_DFL);
224 #endif
225 #ifdef SIGHUP
226     signal(SIGHUP, SIG_DFL);
227 #endif
228 #ifdef SIGILL
229     signal(SIGILL, SIG_DFL);
230 #endif
231 #ifdef SIGINT
232     signal(SIGINT, SIG_DFL);
233 #endif
234 #ifdef SIGPIPE
235     signal(SIGPIPE, SIG_DFL);
236 #endif
237 #ifdef SIGQUIT
238     signal(SIGQUIT, SIG_DFL);
239 #endif
240 #ifdef SIGSEGV
241     signal(SIGSEGV, SIG_DFL);
242 #endif
243 #ifdef SIGTERM
244     signal(SIGTERM, SIG_DFL);
245 #endif
246 #ifdef SIGUSR1
247     signal(SIGUSR1, SIG_DFL);
248 #endif
249 #ifdef SIGUSR2
250     signal(SIGUSR2, SIG_DFL);
251 #endif
252 #ifdef SIGCHLD
253     signal(SIGCHLD, SIG_DFL);
254 #endif
255 #ifdef SIGCONT
256     signal(SIGCONT, SIG_DFL);
257 #endif
258 #ifdef SIGTSTP
259     signal(SIGTSTP, SIG_DFL);
260 #endif
261 #ifdef SIGTTIN
262     signal(SIGTTIN, SIG_DFL);
263 #endif
264 #ifdef SIGTTOU
265     signal(SIGTTOU, SIG_DFL);
266 #endif
267 }
268
269 /*
270  *----------------------------------------------------------------------
271  *
272  * SetupStdFile --
273  *
274  *      Set up stdio file handles for the child process, using the
275  *      current standard channels if no other files are specified.
276  *      If no standard channel is defined, or if no file is associated
277  *      with the channel, then the corresponding standard fd is closed.
278  *
279  * Results:
280  *      Returns 1 on success, or 0 on failure.
281  *
282  * Side effects:
283  *      Replaces stdio fds.
284  *
285  *----------------------------------------------------------------------
286  */
287
288 static int
289 SetupStdFile(fd, type)
290     int fd;                     /* File descriptor to dup, or -1. */
291     int type;                   /* One of TCL_STDIN, TCL_STDOUT, TCL_STDERR */
292 {
293     int targetFd = 0;           /* Initializations here needed only to */
294     int direction = 0;          /* prevent warnings about using uninitialized
295                                  * variables. */
296
297     switch (type) {
298     case TCL_STDIN:
299         targetFd = 0;
300         direction = TCL_READABLE;
301         break;
302     case TCL_STDOUT:
303         targetFd = 1;
304         direction = TCL_WRITABLE;
305         break;
306     case TCL_STDERR:
307         targetFd = 2;
308         direction = TCL_WRITABLE;
309         break;
310     }
311     if (fd < 0) {
312         Tcl_Channel channel;
313
314         channel = Tcl_GetStdChannel(type);
315         if (channel) {
316             Tcl_GetChannelHandle(channel, direction, (ClientData *)&fd);
317         }
318     }
319     if (fd >= 0) {
320         if (fd != targetFd) {
321             if (dup2(fd, targetFd) == -1) {
322                 return 0;
323             }
324             /*
325              * Must clear the close-on-exec flag for the target FD, since
326              * some systems (e.g. Ultrix) do not clear the CLOEXEC flag on
327              * the target FD.
328              */
329
330             fcntl(targetFd, F_SETFD, 0);
331         } else {
332             /*
333              * Since we aren't dup'ing the file, we need to explicitly clear
334              * the close-on-exec flag.
335              */
336             fcntl(fd, F_SETFD, 0);
337         }
338     } else {
339         close(targetFd);
340     }
341     return 1;
342 }
343
344 /*
345  *----------------------------------------------------------------------
346  *
347  * CreateProcess --
348  *
349  *      Create a child process that has the specified files as its
350  *      standard input, output, and error.  The child process runs
351  *      asynchronously and runs with the same environment variables
352  *      as the creating process.
353  *
354  *      The path is searched to find the specified executable.
355  *
356  * Results:
357  *      The return value is TCL_ERROR and an error message is left in
358  *      interp->result if there was a problem creating the child
359  *      process.  Otherwise, the return value is TCL_OK and *pidPtr is
360  *      filled with the process id of the child process.
361  *
362  * Side effects:
363  *      A process is created.
364  *
365  *----------------------------------------------------------------------
366  */
367
368 /* ARGSUSED */
369 static int
370 CreateProcess(interp, argc, argv, inputFile, outputFile, errorFile, pidPtr)
371     Tcl_Interp *interp;         /* Interpreter in which to leave errors that
372                                  * occurred when creating the child process.
373                                  * Error messages from the child process
374                                  * itself are sent to errorFile. */
375     int argc;                   /* Number of arguments in following array. */
376     char **argv;                /* Array of argument strings.  argv[0]
377                                  * contains the name of the executable
378                                  * converted to native format (using the
379                                  * Tcl_TranslateFileName call).  Additional
380                                  * arguments have not been converted. */
381     int inputFile;              /* If non-NULL, gives the file to use as
382                                  * input for the child process.  If inputFile
383                                  * file is not readable or is NULL, the child
384                                  * will receive no standard input. */
385     int outputFile;             /* If non-NULL, gives the file that
386                                  * receives output from the child process.  If
387                                  * outputFile file is not writeable or is
388                                  * NULL, output from the child will be
389                                  * discarded. */
390     int errorFile;              /* If non-NULL, gives the file that
391                                  * receives errors from the child process.  If
392                                  * errorFile file is not writeable or is NULL,
393                                  * errors from the child will be discarded.
394                                  * errorFile may be the same as outputFile. */
395     int *pidPtr;                /* If this procedure is successful, pidPtr
396                                  * is filled with the process id of the child
397                                  * process. */
398 {
399     int errPipeIn, errPipeOut;
400     int joinThisError, count, status, fd;
401     char errSpace[200];
402     int pid;
403     /* 
404      * declarations for utf-8 handling, copied verbatim from tclUnixPipe 8.4.4
405      */
406     Tcl_DString *dsArray;
407     char **newArgv;
408     int i;
409
410     errPipeIn = errPipeOut = -1;
411     pid = -1;
412
413     /*
414      * Create a pipe that the child can use to return error
415      * information if anything goes wrong.
416      */
417
418     if (CreatePipe(&errPipeIn, &errPipeOut) == 0) {
419         Tcl_AppendResult(interp, "can't create pipe: ",
420             Tcl_PosixError(interp), (char *)NULL);
421         goto error;
422     }
423
424     /* Proper handling of utf-8 to system encoding: copied verbatim
425      * from tclUnixPipe (8.4.4)
426      * We need to allocate and convert this before the fork
427      * so it is properly deallocated later
428      */
429     dsArray = (Tcl_DString *) ckalloc(argc * sizeof(Tcl_DString));
430     newArgv = (char **) ckalloc((argc+1) * sizeof(char *));
431     newArgv[argc] = NULL;
432     for (i = 0; i < argc; i++) {
433         newArgv[i] = Tcl_UtfToExternalDString(NULL, argv[i], -1, &dsArray[i]);
434     }
435
436     joinThisError = (errorFile == outputFile);
437     pid = fork();
438     if (pid == 0) {
439         fd = errPipeOut;
440
441         /*
442          * Set up stdio file handles for the child process.
443          */
444
445         if (!SetupStdFile(inputFile, TCL_STDIN) ||
446             !SetupStdFile(outputFile, TCL_STDOUT) ||
447             (!joinThisError && !SetupStdFile(errorFile, TCL_STDERR)) ||
448             (joinThisError &&
449                 ((dup2(1, 2) == -1) || (fcntl(2, F_SETFD, 0) != 0)))) {
450             sprintf(errSpace, "%dforked process can't set up input/output: ",
451                 errno);
452             write(fd, errSpace, (size_t) strlen(errSpace));
453             _exit(1);
454         }
455         /*
456          * Close the input side of the error pipe.
457          */
458
459         RestoreSignals();
460         /* execvp(argv[0], &argv[0]); not good for utf-8 */ 
461         execvp(newArgv[0], newArgv);                    /* INTL: Native. */
462         sprintf(errSpace, "%dcan't execute \"%.150s\": ", errno, argv[0]);
463         write(fd, errSpace, (size_t) strlen(errSpace));
464         _exit(1);
465     }
466
467     /*
468      * Free the mem we used for the fork (again verbatim copy from 
469      * tclUnixPipe 8.4.4)
470      */
471     for (i = 0; i < argc; i++) {
472       Tcl_DStringFree(&dsArray[i]);
473     }
474     ckfree((char *) dsArray);
475     ckfree((char *) newArgv);
476     
477     if (pid == -1) {
478         Tcl_AppendResult(interp, "can't fork child process: ",
479             Tcl_PosixError(interp), (char *)NULL);
480         goto error;
481     }
482     /*
483      * Read back from the error pipe to see if the child started
484      * up OK.  The info in the pipe (if any) consists of a decimal
485      * errno value followed by an error message.
486      */
487
488     CloseFile(errPipeOut);
489     errPipeOut = -1;
490
491     fd = errPipeIn;
492     count = read(fd, errSpace, (size_t) (sizeof(errSpace) - 1));
493     if (count > 0) {
494         char *end;
495
496         errSpace[count] = 0;
497         errno = strtol(errSpace, &end, 10);
498         Tcl_AppendResult(interp, end, Tcl_PosixError(interp), (char *)NULL);
499         goto error;
500     }
501     CloseFile(errPipeIn);
502     *pidPtr = pid;
503     return TCL_OK;
504
505   error:
506     if (pid != -1) {
507         /*
508          * Reap the child process now if an error occurred during its
509          * startup.
510          */
511         Tcl_WaitPid((Tcl_Pid)pid, &status, WNOHANG);
512     }
513     if (errPipeIn >= 0) {
514         CloseFile(errPipeIn);
515     }
516     if (errPipeOut >= 0) {
517         CloseFile(errPipeOut);
518     }
519     return TCL_ERROR;
520 }
521
522 /*
523  *----------------------------------------------------------------------
524  *
525  * FileForRedirect --
526  *
527  *      This procedure does much of the work of parsing redirection
528  *      operators.  It handles "@" if specified and allowed, and a file
529  *      name, and opens the file if necessary.
530  *
531  * Results:
532  *      The return value is the descriptor number for the file.  If an
533  *      error occurs then NULL is returned and an error message is left
534  *      in interp->result.  Several arguments are side-effected; see
535  *      the argument list below for details.
536  *
537  * Side effects:
538  *      None.
539  *
540  *----------------------------------------------------------------------
541  */
542
543 static int
544 FileForRedirect(interp, spec, atOK, arg, nextArg, flags, skipPtr, closePtr)
545     Tcl_Interp *interp;         /* Intepreter to use for error reporting. */
546     char *spec;                 /* Points to character just after
547                                  * redirection character. */
548     char *arg;                  /* Pointer to entire argument containing
549                                  * spec:  used for error reporting. */
550     int atOK;                   /* Non-zero means that '@' notation can be
551                                  * used to specify a channel, zero means that
552                                  * it isn't. */
553     char *nextArg;              /* Next argument in argc/argv array, if needed
554                                  * for file name or channel name.  May be
555                                  * NULL. */
556     int flags;                  /* Flags to use for opening file or to
557                                  * specify mode for channel. */
558     int *skipPtr;               /* Filled with 1 if redirection target was
559                                  * in spec, 2 if it was in nextArg. */
560     int *closePtr;              /* Filled with one if the caller should
561                                  * close the file when done with it, zero
562                                  * otherwise. */
563 {
564     int writing = (flags & O_WRONLY);
565     Tcl_Channel chan;
566     int fd;
567     int direction;
568
569     *skipPtr = 1;
570     if ((atOK != 0) && (*spec == '@')) {
571         spec++;
572         if (*spec == '\0') {
573             spec = nextArg;
574             if (spec == NULL) {
575                 goto badLastArg;
576             }
577             *skipPtr = 2;
578         }
579         if (*spec == '1' && spec[1] == 0) {
580             spec = "stdout";
581         }
582         chan = Tcl_GetChannel(interp, spec, NULL);
583         if (chan == NULL) {
584             return -1;
585         }
586         direction = (writing) ? TCL_WRITABLE : TCL_READABLE;
587         if (Tcl_GetChannelHandle(chan, direction, (ClientData *)&fd) != TCL_OK) {
588             fd = -1;
589         }
590         if (fd < 0) {
591             Tcl_AppendResult(interp, "channel \"", Tcl_GetChannelName(chan),
592                 "\" wasn't opened for ",
593                 ((writing) ? "writing" : "reading"), (char *)NULL);
594             return -1;
595         }
596         if (writing) {
597             /*
598              * Be sure to flush output to the file, so that anything
599              * written by the child appears after stuff we've already
600              * written.
601              */
602             Tcl_Flush(chan);
603         }
604     } else {
605         char *name;
606         Tcl_DString nameString;
607
608         if (*spec == '\0') {
609             spec = nextArg;
610             if (spec == NULL) {
611                 goto badLastArg;
612             }
613             *skipPtr = 2;
614         }
615         name = Tcl_TranslateFileName(interp, spec, &nameString);
616
617         if (name != NULL) {
618             fd = OpenFile(name, flags);
619         } else {
620             fd = -1;
621         }
622         Tcl_DStringFree(&nameString);
623         if (fd < 0) {
624             Tcl_AppendResult(interp, "can't ",
625                 ((writing) ? "write" : "read"), " file \"", spec, "\": ",
626                 Tcl_PosixError(interp), (char *)NULL);
627             return -1;
628         }
629         *closePtr = 1;
630     }
631     return fd;
632
633   badLastArg:
634     Tcl_AppendResult(interp, "can't specify \"", arg,
635         "\" as last word in command", (char *)NULL);
636     return -1;
637 }
638 \f
639 /*
640  *----------------------------------------------------------------------
641  *
642  * Blt_CreatePipeline --
643  *
644  *      Given an argc/argv array, instantiate a pipeline of processes
645  *      as described by the argv.
646  *
647  * Results:
648  *      The return value is a count of the number of new processes
649  *      created, or -1 if an error occurred while creating the pipeline.
650  *      *pidArrayPtr is filled in with the address of a dynamically
651  *      allocated array giving the ids of all of the processes.  It
652  *      is up to the caller to free this array when it isn't needed
653  *      anymore.  If inPipePtr is non-NULL, *inPipePtr is filled in
654  *      with the file id for the input pipe for the pipeline (if any):
655  *      the caller must eventually close this file.  If outPipePtr
656  *      isn't NULL, then *outPipePtr is filled in with the file id
657  *      for the output pipe from the pipeline:  the caller must close
658  *      this file.  If errPipePtr isn't NULL, then *errPipePtr is filled
659  *      with a file id that may be used to read error output after the
660  *      pipeline completes.
661  *
662  * Side effects:
663  *      Processes and pipes are created.
664  *
665  *----------------------------------------------------------------------
666  */
667
668 int
669 Blt_CreatePipeline(interp, argc, argv, pidArrayPtr, inPipePtr,
670     outPipePtr, errPipePtr)
671     Tcl_Interp *interp;         /* Interpreter to use for error reporting. */
672     int argc;                   /* Number of entries in argv. */
673     char **argv;                /* Array of strings describing commands in
674                                  * pipeline plus I/O redirection with <,
675                                  * <<,  >, etc.  Argv[argc] must be NULL. */
676     int **pidArrayPtr;          /* Word at *pidArrayPtr gets filled in with
677                                  * address of array of pids for processes
678                                  * in pipeline (first pid is first process
679                                  * in pipeline). */
680     int *inPipePtr;             /* If non-NULL, input to the pipeline comes
681                                  * from a pipe (unless overridden by
682                                  * redirection in the command).  The file
683                                  * id with which to write to this pipe is
684                                  * stored at *inPipePtr.  NULL means command
685                                  * specified its own input source. */
686     int *outPipePtr;            /* If non-NULL, output to the pipeline goes
687                                  * to a pipe, unless overriden by redirection
688                                  * in the command.  The file id with which to
689                                  * read frome this pipe is stored at
690                                  * *outPipePtr.  NULL means command specified
691                                  * its own output sink. */
692     int *errPipePtr;            /* If non-NULL, all stderr output from the
693                                  * pipeline will go to a temporary file
694                                  * created here, and a descriptor to read
695                                  * the file will be left at *errPipePtr.
696                                  * The file will be removed already, so
697                                  * closing this descriptor will be the end
698                                  * of the file.  If this is NULL, then
699                                  * all stderr output goes to our stderr.
700                                  * If the pipeline specifies redirection
701                                  * then the file will still be created
702                                  * but it will never get any data. */
703 {
704     int *pidPtr = NULL;         /* Points to malloc-ed array holding all
705                                  * the pids of child processes. */
706     int nPids;                  /* Actual number of processes that exist
707                                  * at *pidPtr right now. */
708     int cmdCount;               /* Count of number of distinct commands
709                                  * found in argc/argv. */
710     char *inputLiteral = NULL;  /* If non-null, then this points to a
711                                  * string containing input data (specified
712                                  * via <<) to be piped to the first process
713                                  * in the pipeline. */
714     int inputFd = -1;           /* If != NULL, gives file to use as input for
715                                  * first process in pipeline (specified via <
716                                  * or <@). */
717     int inputClose = 0;         /* If non-zero, then inputFd should be
718                                  * closed when cleaning up. */
719     int outputFd = -1;          /* Writable file for output from last command
720                                  * in pipeline (could be file or pipe).  NULL
721                                  * means use stdout. */
722     int outputClose = 0;        /* If non-zero, then outputFd should be
723                                  * closed when cleaning up. */
724     int errorFd = -1;           /* Writable file for error output from all
725                                  * commands in pipeline.  NULL means use
726                                  * stderr. */
727     int errorClose = 0;         /* If non-zero, then errorFd should be
728                                  * closed when cleaning up. */
729     char *p;
730     int skip, lastBar, lastArg, i, j, atOK, flags, errorToOutput;
731     Tcl_DString execBuffer;
732     int pipeIn;
733     int curInFd, curOutFd, curErrFd;
734
735     if (inPipePtr != NULL) {
736         *inPipePtr = -1;
737     }
738     if (outPipePtr != NULL) {
739         *outPipePtr = -1;
740     }
741     if (errPipePtr != NULL) {
742         *errPipePtr = -1;
743     }
744     Tcl_DStringInit(&execBuffer);
745
746     pipeIn = curInFd = curOutFd = -1;
747     nPids = 0;
748
749     /*
750      * First, scan through all the arguments to figure out the structure
751      * of the pipeline.  Process all of the input and output redirection
752      * arguments and remove them from the argument list in the pipeline.
753      * Count the number of distinct processes (it's the number of "|"
754      * arguments plus one) but don't remove the "|" arguments because
755      * they'll be used in the second pass to seperate the individual
756      * child processes.  Cannot start the child processes in this pass
757      * because the redirection symbols may appear anywhere in the
758      * command line -- e.g., the '<' that specifies the input to the
759      * entire pipe may appear at the very end of the argument list.
760      */
761
762     lastBar = -1;
763     cmdCount = 1;
764     for (i = 0; i < argc; i++) {
765         skip = 0;
766         p = argv[i];
767         switch (*p++) {
768         case '\\':
769             continue;
770
771         case '|':
772             if (*p == '&') {
773                 p++;
774             }
775             if (*p == '\0') {
776                 if ((i == (lastBar + 1)) || (i == (argc - 1))) {
777                     Tcl_AppendResult(interp, 
778                                      "illegal use of | or |& in command",
779                                      (char *)NULL);
780                     goto error;
781                 }
782             }
783             lastBar = i;
784             cmdCount++;
785             break;
786
787         case '<':
788             if (inputClose != 0) {
789                 inputClose = 0;
790                 CloseFile(inputFd);
791             }
792             if (*p == '<') {
793                 inputFd = -1;
794                 inputLiteral = p + 1;
795                 skip = 1;
796                 if (*inputLiteral == '\0') {
797                     inputLiteral = argv[i + 1];
798                     if (inputLiteral == NULL) {
799                         Tcl_AppendResult(interp, "can't specify \"", argv[i],
800                             "\" as last word in command", (char *)NULL);
801                         goto error;
802                     }
803                     skip = 2;
804                 }
805             } else {
806                 inputLiteral = NULL;
807                 inputFd = FileForRedirect(interp, p, 1, argv[i], argv[i + 1],
808                     O_RDONLY, &skip, &inputClose);
809                 if (inputFd < 0) {
810                     goto error;
811                 }
812             }
813             break;
814
815         case '>':
816             atOK = 1;
817             flags = O_WRONLY | O_CREAT | O_TRUNC;
818             errorToOutput = 0;
819             if (*p == '>') {
820                 p++;
821                 atOK = 0;
822                 flags = O_WRONLY | O_CREAT;
823             }
824             if (*p == '&') {
825                 if (errorClose != 0) {
826                     errorClose = 0;
827                     CloseFile(errorFd);
828                 }
829                 errorToOutput = 1;
830                 p++;
831             }
832             if (outputClose != 0) {
833                 outputClose = 0;
834                 CloseFile(outputFd);
835             }
836             outputFd = FileForRedirect(interp, p, atOK, argv[i], argv[i + 1],
837                 flags, &skip, &outputClose);
838             if (outputFd < 0) {
839                 goto error;
840             }
841             if (errorToOutput) {
842                 errorClose = 0;
843                 errorFd = outputFd;
844             }
845             break;
846
847         case '2':
848             if (*p != '>') {
849                 break;
850             }
851             p++;
852             atOK = 1;
853             flags = O_WRONLY | O_CREAT | O_TRUNC;
854             if (*p == '>') {
855                 p++;
856                 atOK = 0;
857                 flags = O_WRONLY | O_CREAT;
858             }
859             if (errorClose != 0) {
860                 errorClose = 0;
861                 CloseFile(errorFd);
862             }
863             errorFd = FileForRedirect(interp, p, atOK, argv[i], argv[i + 1],
864                 flags, &skip, &errorClose);
865             if (errorFd < 0) {
866                 goto error;
867             }
868             break;
869         }
870
871         if (skip != 0) {
872             for (j = i + skip; j < argc; j++) {
873                 argv[j - skip] = argv[j];
874             }
875             argc -= skip;
876             i -= 1;
877         }
878     }
879
880     if (inputFd == -1) {
881         if (inputLiteral != NULL) {
882             /*
883              * The input for the first process is immediate data coming from
884              * Tcl.  Create a temporary file for it and put the data into the
885              * file.
886              */
887             inputFd = CreateTempFile(inputLiteral);
888             if (inputFd < 0) {
889                 Tcl_AppendResult(interp,
890                     "can't create input file for command: ",
891                     Tcl_PosixError(interp), (char *)NULL);
892                 goto error;
893             }
894             inputClose = 1;
895         } else if (inPipePtr != NULL) {
896             /*
897              * The input for the first process in the pipeline is to
898              * come from a pipe that can be written from by the caller.
899              */
900
901             if (CreatePipe(&inputFd, inPipePtr) == 0) {
902                 Tcl_AppendResult(interp,
903                     "can't create input pipe for command: ",
904                     Tcl_PosixError(interp), (char *)NULL);
905                 goto error;
906             }
907             inputClose = 1;
908         } else {
909             /*
910              * The input for the first process comes from stdin.
911              */
912
913             inputFd = 0;
914         }
915     }
916     if (outputFd == -1) {
917         if (outPipePtr != NULL) {
918             /*
919              * Output from the last process in the pipeline is to go to a
920              * pipe that can be read by the caller.
921              */
922
923             if (CreatePipe(outPipePtr, &outputFd) == 0) {
924                 Tcl_AppendResult(interp,
925                     "can't create output pipe for command: ",
926                     Tcl_PosixError(interp), (char *)NULL);
927                 goto error;
928             }
929             outputClose = 1;
930         } else {
931             /*
932              * The output for the last process goes to stdout.
933              */
934             outputFd = 1;
935         }
936     }
937     if (errorFd == -1) {
938         if (errPipePtr != NULL) {
939             /*
940              * Stderr from the last process in the pipeline is to go to a
941              * pipe that can be read by the caller.
942              */
943             if (CreatePipe(errPipePtr, &errorFd) == 0) {
944                 Tcl_AppendResult(interp,
945                     "can't create error pipe for command: ",
946                     Tcl_PosixError(interp), (char *)NULL);
947                 goto error;
948             }
949             errorClose = 1;
950         } else {
951             /*
952              * Errors from the pipeline go to stderr.
953              */
954             errorFd = 2;
955         }
956     }
957     /*
958      * Scan through the argc array, creating a process for each
959      * group of arguments between the "|" characters.
960      */
961
962     Tcl_ReapDetachedProcs();
963     pidPtr = Blt_Malloc((unsigned)(cmdCount * sizeof(int)));
964
965     curInFd = inputFd;
966
967     lastArg = 0;                /* Suppress compiler warning */
968     for (i = 0; i < argc; i = lastArg + 1) {
969         int joinThisError;
970         int pid;
971
972         /*
973          * Convert the program name into native form.
974          */
975
976         argv[i] = Tcl_TranslateFileName(interp, argv[i], &execBuffer);
977         if (argv[i] == NULL) {
978             goto error;
979         }
980         /*
981          * Find the end of the current segment of the pipeline.
982          */
983         joinThisError = 0;
984         for (lastArg = i; lastArg < argc; lastArg++) {
985             if (argv[lastArg][0] == '|') {
986                 if (argv[lastArg][1] == '\0') {
987                     break;
988                 }
989                 if ((argv[lastArg][1] == '&') && (argv[lastArg][2] == '\0')) {
990                     joinThisError = 1;
991                     break;
992                 }
993             }
994         }
995         argv[lastArg] = NULL;
996
997         /*
998          * If this is the last segment, use the specified outputFile.
999          * Otherwise create an intermediate pipe.  pipeIn will become the
1000          * curInFile for the next segment of the pipe.
1001          */
1002
1003         if (lastArg == argc) {
1004             curOutFd = outputFd;
1005         } else {
1006             if (CreatePipe(&pipeIn, &curOutFd) == 0) {
1007                 Tcl_AppendResult(interp, "can't create pipe: ",
1008                     Tcl_PosixError(interp), (char *)NULL);
1009                 goto error;
1010             }
1011         }
1012
1013         if (joinThisError != 0) {
1014             curErrFd = curOutFd;
1015         } else {
1016             curErrFd = errorFd;
1017         }
1018
1019         if (CreateProcess(interp, lastArg - i, argv + i,
1020                 curInFd, curOutFd, curErrFd, &pid) != TCL_OK) {
1021             goto error;
1022         }
1023         Tcl_DStringFree(&execBuffer);
1024
1025         pidPtr[nPids] = pid;
1026         nPids++;
1027
1028
1029         /*
1030          * Close off our copies of file descriptors that were set up for
1031          * this child, then set up the input for the next child.
1032          */
1033
1034         if ((curInFd >= 0) && (curInFd != inputFd)) {
1035             CloseFile(curInFd);
1036         }
1037         curInFd = pipeIn;
1038         pipeIn = -1;
1039
1040         if ((curOutFd >= 0) && (curOutFd != outputFd)) {
1041             CloseFile(curOutFd);
1042         }
1043         curOutFd = -1;
1044     }
1045
1046     *pidArrayPtr = pidPtr;
1047
1048     /*
1049      * All done.  Cleanup open files lying around and then return.
1050      */
1051
1052   cleanup:
1053     Tcl_DStringFree(&execBuffer);
1054
1055     if (inputClose) {
1056         CloseFile(inputFd);
1057     }
1058     if (outputClose) {
1059         CloseFile(outputFd);
1060     }
1061     if (errorClose) {
1062         CloseFile(errorFd);
1063     }
1064     return nPids;
1065
1066     /*
1067      * An error occurred.  There could have been extra files open, such
1068      * as pipes between children.  Clean them all up.  Detach any child
1069      * processes that have been created.
1070      */
1071
1072   error:
1073     if (pipeIn >= 0) {
1074         CloseFile(pipeIn);
1075     }
1076     if ((curOutFd >= 0) && (curOutFd != outputFd)) {
1077         CloseFile(curOutFd);
1078     }
1079     if ((curInFd >= 0) && (curInFd != inputFd)) {
1080         CloseFile(curInFd);
1081     }
1082     if ((inPipePtr != NULL) && (*inPipePtr >= 0)) {
1083         CloseFile(*inPipePtr);
1084         *inPipePtr = -1;
1085     }
1086     if ((outPipePtr != NULL) && (*outPipePtr >= 0)) {
1087         CloseFile(*outPipePtr);
1088         *outPipePtr = -1;
1089     }
1090     if ((errPipePtr != NULL) && (*errPipePtr >= 0)) {
1091         CloseFile(*errPipePtr);
1092         *errPipePtr = -1;
1093     }
1094     if (pidPtr != NULL) {
1095         for (i = 0; i < nPids; i++) {
1096             if (pidPtr[i] != -1) {
1097 #if (TCL_MAJOR_VERSION == 7)
1098                 Tcl_DetachPids(1, &pidPtr[i]);
1099 #else
1100                 Tcl_DetachPids(1, (Tcl_Pid *)&pidPtr[i]);
1101 #endif
1102             }
1103         }
1104         Blt_Free(pidPtr);
1105     }
1106     nPids = -1;
1107     goto cleanup;
1108 }