Planet
navi homePPSaboutscreenshotsdownloaddevelopmentforum

source: downloads/tcl8.5.2/generic/tclPipe.c @ 35

Last change on this file since 35 was 25, checked in by landauf, 17 years ago

added tcl to libs

File size: 30.5 KB
Line 
1/*
2 * tclPipe.c --
3 *
4 *      This file contains the generic portion of the command channel driver
5 *      as well as various utility routines used in managing subprocesses.
6 *
7 * Copyright (c) 1997 by Sun Microsystems, Inc.
8 *
9 * See the file "license.terms" for information on usage and redistribution of
10 * this file, and for a DISCLAIMER OF ALL WARRANTIES.
11 *
12 * RCS: @(#) $Id: tclPipe.c,v 1.19 2007/04/20 06:10:58 kennykb Exp $
13 */
14
15#include "tclInt.h"
16
17/*
18 * A linked list of the following structures is used to keep track of child
19 * processes that have been detached but haven't exited yet, so we can make
20 * sure that they're properly "reaped" (officially waited for) and don't lie
21 * around as zombies cluttering the system.
22 */
23
24typedef struct Detached {
25    Tcl_Pid pid;                /* Id of process that's been detached but
26                                 * isn't known to have exited. */
27    struct Detached *nextPtr;   /* Next in list of all detached processes. */
28} Detached;
29
30static Detached *detList = NULL;/* List of all detached proceses. */
31TCL_DECLARE_MUTEX(pipeMutex)    /* Guard access to detList. */
32
33/*
34 * Declarations for local functions defined in this file:
35 */
36
37static TclFile          FileForRedirect(Tcl_Interp *interp, CONST char *spec,
38                            int atOk, CONST char *arg, CONST char *nextArg,
39                            int flags, int *skipPtr, int *closePtr,
40                            int *releasePtr);
41
42/*
43 *----------------------------------------------------------------------
44 *
45 * FileForRedirect --
46 *
47 *      This function does much of the work of parsing redirection operators.
48 *      It handles "@" if specified and allowed, and a file name, and opens
49 *      the file if necessary.
50 *
51 * Results:
52 *      The return value is the descriptor number for the file. If an error
53 *      occurs then NULL is returned and an error message is left in the
54 *      interp's result. Several arguments are side-effected; see the argument
55 *      list below for details.
56 *
57 * Side effects:
58 *      None.
59 *
60 *----------------------------------------------------------------------
61 */
62
63static TclFile
64FileForRedirect(
65    Tcl_Interp *interp,         /* Intepreter to use for error reporting. */
66    CONST char *spec,           /* Points to character just after redirection
67                                 * character. */
68    int atOK,                   /* Non-zero means that '@' notation can be
69                                 * used to specify a channel, zero means that
70                                 * it isn't. */
71    CONST char *arg,            /* Pointer to entire argument containing spec:
72                                 * used for error reporting. */
73    CONST char *nextArg,        /* Next argument in argc/argv array, if needed
74                                 * for file name or channel name. May be
75                                 * NULL. */
76    int flags,                  /* Flags to use for opening file or to specify
77                                 * mode for channel. */
78    int *skipPtr,               /* Filled with 1 if redirection target was in
79                                 * spec, 2 if it was in nextArg. */
80    int *closePtr,              /* Filled with one if the caller should close
81                                 * the file when done with it, zero
82                                 * otherwise. */
83    int *releasePtr)
84{
85    int writing = (flags & O_WRONLY);
86    Tcl_Channel chan;
87    TclFile file;
88
89    *skipPtr = 1;
90    if ((atOK != 0) && (*spec == '@')) {
91        spec++;
92        if (*spec == '\0') {
93            spec = nextArg;
94            if (spec == NULL) {
95                goto badLastArg;
96            }
97            *skipPtr = 2;
98        }
99        chan = Tcl_GetChannel(interp, spec, NULL);
100        if (chan == (Tcl_Channel) NULL) {
101            return NULL;
102        }
103        file = TclpMakeFile(chan, writing ? TCL_WRITABLE : TCL_READABLE);
104        if (file == NULL) {
105            Tcl_AppendResult(interp, "channel \"", Tcl_GetChannelName(chan),
106                    "\" wasn't opened for ",
107                    ((writing) ? "writing" : "reading"), NULL);
108            return NULL;
109        }
110        *releasePtr = 1;
111        if (writing) {
112            /*
113             * Be sure to flush output to the file, so that anything written
114             * by the child appears after stuff we've already written.
115             */
116
117            Tcl_Flush(chan);
118        }
119    } else {
120        CONST char *name;
121        Tcl_DString nameString;
122
123        if (*spec == '\0') {
124            spec = nextArg;
125            if (spec == NULL) {
126                goto badLastArg;
127            }
128            *skipPtr = 2;
129        }
130        name = Tcl_TranslateFileName(interp, spec, &nameString);
131        if (name == NULL) {
132            return NULL;
133        }
134        file = TclpOpenFile(name, flags);
135        Tcl_DStringFree(&nameString);
136        if (file == NULL) {
137            Tcl_AppendResult(interp, "couldn't ",
138                    ((writing) ? "write" : "read"), " file \"", spec, "\": ",
139                    Tcl_PosixError(interp), NULL);
140            return NULL;
141        }
142        *closePtr = 1;
143    }
144    return file;
145
146  badLastArg:
147    Tcl_AppendResult(interp, "can't specify \"", arg,
148            "\" as last word in command", NULL);
149    return NULL;
150}
151
152/*
153 *----------------------------------------------------------------------
154 *
155 * Tcl_DetachPids --
156 *
157 *      This function is called to indicate that one or more child processes
158 *      have been placed in background and will never be waited for; they
159 *      should eventually be reaped by Tcl_ReapDetachedProcs.
160 *
161 * Results:
162 *      None.
163 *
164 * Side effects:
165 *      None.
166 *
167 *----------------------------------------------------------------------
168 */
169
170void
171Tcl_DetachPids(
172    int numPids,                /* Number of pids to detach: gives size of
173                                 * array pointed to by pidPtr. */
174    Tcl_Pid *pidPtr)            /* Array of pids to detach. */
175{
176    register Detached *detPtr;
177    int i;
178
179    Tcl_MutexLock(&pipeMutex);
180    for (i = 0; i < numPids; i++) {
181        detPtr = (Detached *) ckalloc(sizeof(Detached));
182        detPtr->pid = pidPtr[i];
183        detPtr->nextPtr = detList;
184        detList = detPtr;
185    }
186    Tcl_MutexUnlock(&pipeMutex);
187
188}
189
190/*
191 *----------------------------------------------------------------------
192 *
193 * Tcl_ReapDetachedProcs --
194 *
195 *      This function checks to see if any detached processes have exited and,
196 *      if so, it "reaps" them by officially waiting on them. It should be
197 *      called "occasionally" to make sure that all detached processes are
198 *      eventually reaped.
199 *
200 * Results:
201 *      None.
202 *
203 * Side effects:
204 *      Processes are waited on, so that they can be reaped by the system.
205 *
206 *----------------------------------------------------------------------
207 */
208
209void
210Tcl_ReapDetachedProcs(void)
211{
212    register Detached *detPtr;
213    Detached *nextPtr, *prevPtr;
214    int status;
215    Tcl_Pid pid;
216
217    Tcl_MutexLock(&pipeMutex);
218    for (detPtr = detList, prevPtr = NULL; detPtr != NULL; ) {
219        pid = Tcl_WaitPid(detPtr->pid, &status, WNOHANG);
220        if ((pid == 0) || ((pid == (Tcl_Pid) -1) && (errno != ECHILD))) {
221            prevPtr = detPtr;
222            detPtr = detPtr->nextPtr;
223            continue;
224        }
225        nextPtr = detPtr->nextPtr;
226        if (prevPtr == NULL) {
227            detList = detPtr->nextPtr;
228        } else {
229            prevPtr->nextPtr = detPtr->nextPtr;
230        }
231        ckfree((char *) detPtr);
232        detPtr = nextPtr;
233    }
234    Tcl_MutexUnlock(&pipeMutex);
235}
236
237/*
238 *----------------------------------------------------------------------
239 *
240 * TclCleanupChildren --
241 *
242 *      This is a utility function used to wait for child processes to exit,
243 *      record information about abnormal exits, and then collect any stderr
244 *      output generated by them.
245 *
246 * Results:
247 *      The return value is a standard Tcl result. If anything at weird
248 *      happened with the child processes, TCL_ERROR is returned and a message
249 *      is left in the interp's result.
250 *
251 * Side effects:
252 *      If the last character of the interp's result is a newline, then it is
253 *      removed unless keepNewline is non-zero. File errorId gets closed, and
254 *      pidPtr is freed back to the storage allocator.
255 *
256 *----------------------------------------------------------------------
257 */
258
259int
260TclCleanupChildren(
261    Tcl_Interp *interp,         /* Used for error messages. */
262    int numPids,                /* Number of entries in pidPtr array. */
263    Tcl_Pid *pidPtr,            /* Array of process ids of children. */
264    Tcl_Channel errorChan)      /* Channel for file containing stderr output
265                                 * from pipeline. NULL means there isn't any
266                                 * stderr output. */
267{
268    int result = TCL_OK;
269    int i, abnormalExit, anyErrorInfo;
270    Tcl_Pid pid;
271    WAIT_STATUS_TYPE waitStatus;
272    CONST char *msg;
273    unsigned long resolvedPid;
274
275    abnormalExit = 0;
276    for (i = 0; i < numPids; i++) {
277        /*
278         * We need to get the resolved pid before we wait on it as the windows
279         * implimentation of Tcl_WaitPid deletes the information such that any
280         * following calls to TclpGetPid fail.
281         */
282
283        resolvedPid = TclpGetPid(pidPtr[i]);
284        pid = Tcl_WaitPid(pidPtr[i], (int *) &waitStatus, 0);
285        if (pid == (Tcl_Pid) -1) {
286            result = TCL_ERROR;
287            if (interp != NULL) {
288                msg = Tcl_PosixError(interp);
289                if (errno == ECHILD) {
290                    /*
291                     * This changeup in message suggested by Mark Diekhans to
292                     * remind people that ECHILD errors can occur on some
293                     * systems if SIGCHLD isn't in its default state.
294                     */
295
296                    msg =
297                        "child process lost (is SIGCHLD ignored or trapped?)";
298                }
299                Tcl_AppendResult(interp, "error waiting for process to exit: ",
300                        msg, NULL);
301            }
302            continue;
303        }
304
305        /*
306         * Create error messages for unusual process exits. An extra newline
307         * gets appended to each error message, but it gets removed below (in
308         * the same fashion that an extra newline in the command's output is
309         * removed).
310         */
311
312        if (!WIFEXITED(waitStatus) || (WEXITSTATUS(waitStatus) != 0)) {
313            char msg1[TCL_INTEGER_SPACE], msg2[TCL_INTEGER_SPACE];
314
315            result = TCL_ERROR;
316            sprintf(msg1, "%lu", resolvedPid);
317            if (WIFEXITED(waitStatus)) {
318                if (interp != (Tcl_Interp *) NULL) {
319                    sprintf(msg2, "%lu",
320                            (unsigned long) WEXITSTATUS(waitStatus));
321                    Tcl_SetErrorCode(interp, "CHILDSTATUS", msg1, msg2, NULL);
322                }
323                abnormalExit = 1;
324            } else if (interp != NULL) {
325                CONST char *p;
326
327                if (WIFSIGNALED(waitStatus)) {
328                    p = Tcl_SignalMsg((int) (WTERMSIG(waitStatus)));
329                    Tcl_SetErrorCode(interp, "CHILDKILLED", msg1,
330                            Tcl_SignalId((int) (WTERMSIG(waitStatus))), p,
331                            NULL);
332                    Tcl_AppendResult(interp, "child killed: ", p, "\n", NULL);
333                } else if (WIFSTOPPED(waitStatus)) {
334                    p = Tcl_SignalMsg((int) (WSTOPSIG(waitStatus)));
335                    Tcl_SetErrorCode(interp, "CHILDSUSP", msg1,
336                            Tcl_SignalId((int) (WSTOPSIG(waitStatus))), p,
337                            NULL);
338                    Tcl_AppendResult(interp, "child suspended: ", p, "\n",
339                            NULL);
340                } else {
341                    Tcl_AppendResult(interp,
342                            "child wait status didn't make sense\n", NULL);
343                }
344            }
345        }
346    }
347
348    /*
349     * Read the standard error file. If there's anything there, then return an
350     * error and add the file's contents to the result string.
351     */
352
353    anyErrorInfo = 0;
354    if (errorChan != NULL) {
355        /*
356         * Make sure we start at the beginning of the file.
357         */
358
359        if (interp != NULL) {
360            int count;
361            Tcl_Obj *objPtr;
362
363            Tcl_Seek(errorChan, (Tcl_WideInt)0, SEEK_SET);
364            objPtr = Tcl_NewObj();
365            count = Tcl_ReadChars(errorChan, objPtr, -1, 0);
366            if (count < 0) {
367                result = TCL_ERROR;
368                Tcl_DecrRefCount(objPtr);
369                Tcl_ResetResult(interp);
370                Tcl_AppendResult(interp, "error reading stderr output file: ",
371                        Tcl_PosixError(interp), NULL);
372            } else if (count > 0) {
373                anyErrorInfo = 1;
374                Tcl_SetObjResult(interp, objPtr);
375                result = TCL_ERROR;
376            } else {
377                Tcl_DecrRefCount(objPtr);
378            }
379        }
380        Tcl_Close(NULL, errorChan);
381    }
382
383    /*
384     * If a child exited abnormally but didn't output any error information at
385     * all, generate an error message here.
386     */
387
388    if ((abnormalExit != 0) && (anyErrorInfo == 0) && (interp != NULL)) {
389        Tcl_AppendResult(interp, "child process exited abnormally", NULL);
390    }
391    return result;
392}
393
394/*
395 *----------------------------------------------------------------------
396 *
397 * TclCreatePipeline --
398 *
399 *      Given an argc/argv array, instantiate a pipeline of processes as
400 *      described by the argv.
401 *
402 *      This function is unofficially exported for use by BLT.
403 *
404 * Results:
405 *      The return value is a count of the number of new processes created, or
406 *      -1 if an error occurred while creating the pipeline. *pidArrayPtr is
407 *      filled in with the address of a dynamically allocated array giving the
408 *      ids of all of the processes. It is up to the caller to free this array
409 *      when it isn't needed anymore. If inPipePtr is non-NULL, *inPipePtr is
410 *      filled in with the file id for the input pipe for the pipeline (if
411 *      any): the caller must eventually close this file. If outPipePtr isn't
412 *      NULL, then *outPipePtr is filled in with the file id for the output
413 *      pipe from the pipeline: the caller must close this file. If errFilePtr
414 *      isn't NULL, then *errFilePtr is filled with a file id that may be used
415 *      to read error output after the pipeline completes.
416 *
417 * Side effects:
418 *      Processes and pipes are created.
419 *
420 *----------------------------------------------------------------------
421 */
422
423int
424TclCreatePipeline(
425    Tcl_Interp *interp,         /* Interpreter to use for error reporting. */
426    int argc,                   /* Number of entries in argv. */
427    CONST char **argv,          /* Array of strings describing commands in
428                                 * pipeline plus I/O redirection with <, <<,
429                                 * >, etc. Argv[argc] must be NULL. */
430    Tcl_Pid **pidArrayPtr,      /* Word at *pidArrayPtr gets filled in with
431                                 * address of array of pids for processes in
432                                 * pipeline (first pid is first process in
433                                 * pipeline). */
434    TclFile *inPipePtr,         /* If non-NULL, input to the pipeline comes
435                                 * from a pipe (unless overridden by
436                                 * redirection in the command). The file id
437                                 * with which to write to this pipe is stored
438                                 * at *inPipePtr. NULL means command specified
439                                 * its own input source. */
440    TclFile *outPipePtr,        /* If non-NULL, output to the pipeline goes to
441                                 * a pipe, unless overriden by redirection in
442                                 * the command. The file id with which to read
443                                 * frome this pipe is stored at *outPipePtr.
444                                 * NULL means command specified its own output
445                                 * sink. */
446    TclFile *errFilePtr)        /* If non-NULL, all stderr output from the
447                                 * pipeline will go to a temporary file
448                                 * created here, and a descriptor to read the
449                                 * file will be left at *errFilePtr. The file
450                                 * will be removed already, so closing this
451                                 * descriptor will be the end of the file. If
452                                 * this is NULL, then all stderr output goes
453                                 * to our stderr. If the pipeline specifies
454                                 * redirection then the file will still be
455                                 * created but it will never get any data. */
456{
457    Tcl_Pid *pidPtr = NULL;     /* Points to malloc-ed array holding all the
458                                 * pids of child processes. */
459    int numPids;                /* Actual number of processes that exist at
460                                 * *pidPtr right now. */
461    int cmdCount;               /* Count of number of distinct commands found
462                                 * in argc/argv. */
463    CONST char *inputLiteral = NULL;
464                                /* If non-null, then this points to a string
465                                 * containing input data (specified via <<) to
466                                 * be piped to the first process in the
467                                 * pipeline. */
468    TclFile inputFile = NULL;   /* If != NULL, gives file to use as input for
469                                 * first process in pipeline (specified via <
470                                 * or <@). */
471    int inputClose = 0;         /* If non-zero, then inputFile should be
472                                 * closed when cleaning up. */
473    int inputRelease = 0;
474    TclFile outputFile = NULL;  /* Writable file for output from last command
475                                 * in pipeline (could be file or pipe). NULL
476                                 * means use stdout. */
477    int outputClose = 0;        /* If non-zero, then outputFile should be
478                                 * closed when cleaning up. */
479    int outputRelease = 0;
480    TclFile errorFile = NULL;   /* Writable file for error output from all
481                                 * commands in pipeline. NULL means use
482                                 * stderr. */
483    int errorClose = 0;         /* If non-zero, then errorFile should be
484                                 * closed when cleaning up. */
485    int errorRelease = 0;
486    CONST char *p;
487    CONST char *nextArg;
488    int skip, lastBar, lastArg, i, j, atOK, flags, needCmd, errorToOutput = 0;
489    Tcl_DString execBuffer;
490    TclFile pipeIn;
491    TclFile curInFile, curOutFile, curErrFile;
492    Tcl_Channel channel;
493
494    if (inPipePtr != NULL) {
495        *inPipePtr = NULL;
496    }
497    if (outPipePtr != NULL) {
498        *outPipePtr = NULL;
499    }
500    if (errFilePtr != NULL) {
501        *errFilePtr = NULL;
502    }
503
504    Tcl_DStringInit(&execBuffer);
505
506    pipeIn = NULL;
507    curInFile = NULL;
508    curOutFile = NULL;
509    numPids = 0;
510
511    /*
512     * First, scan through all the arguments to figure out the structure of
513     * the pipeline. Process all of the input and output redirection arguments
514     * and remove them from the argument list in the pipeline. Count the
515     * number of distinct processes (it's the number of "|" arguments plus
516     * one) but don't remove the "|" arguments because they'll be used in the
517     * second pass to seperate the individual child processes. Cannot start
518     * the child processes in this pass because the redirection symbols may
519     * appear anywhere in the command line - e.g., the '<' that specifies the
520     * input to the entire pipe may appear at the very end of the argument
521     * list.
522     */
523
524    lastBar = -1;
525    cmdCount = 1;
526    needCmd = 1;
527    for (i = 0; i < argc; i++) {
528        errorToOutput = 0;
529        skip = 0;
530        p = argv[i];
531        switch (*p++) {
532        case '|':
533            if (*p == '&') {
534                p++;
535            }
536            if (*p == '\0') {
537                if ((i == (lastBar + 1)) || (i == (argc - 1))) {
538                    Tcl_SetResult(interp, "illegal use of | or |& in command",
539                            TCL_STATIC);
540                    goto error;
541                }
542            }
543            lastBar = i;
544            cmdCount++;
545            needCmd = 1;
546            break;
547
548        case '<':
549            if (inputClose != 0) {
550                inputClose = 0;
551                TclpCloseFile(inputFile);
552            }
553            if (inputRelease != 0) {
554                inputRelease = 0;
555                TclpReleaseFile(inputFile);
556            }
557            if (*p == '<') {
558                inputFile = NULL;
559                inputLiteral = p + 1;
560                skip = 1;
561                if (*inputLiteral == '\0') {
562                    inputLiteral = ((i + 1) == argc) ? NULL : argv[i + 1];
563                    if (inputLiteral == NULL) {
564                        Tcl_AppendResult(interp, "can't specify \"", argv[i],
565                                "\" as last word in command", NULL);
566                        goto error;
567                    }
568                    skip = 2;
569                }
570            } else {
571                nextArg = ((i + 1) == argc) ? NULL : argv[i + 1];
572                inputLiteral = NULL;
573                inputFile = FileForRedirect(interp, p, 1, argv[i], nextArg,
574                        O_RDONLY, &skip, &inputClose, &inputRelease);
575                if (inputFile == NULL) {
576                    goto error;
577                }
578            }
579            break;
580
581        case '>':
582            atOK = 1;
583            flags = O_WRONLY | O_CREAT | O_TRUNC;
584            if (*p == '>') {
585                p++;
586                atOK = 0;
587
588                /*
589                 * Note that the O_APPEND flag only has an effect on POSIX
590                 * platforms. On Windows, we just have to carry on regardless.
591                 */
592
593                flags = O_WRONLY | O_CREAT | O_APPEND;
594            }
595            if (*p == '&') {
596                if (errorClose != 0) {
597                    errorClose = 0;
598                    TclpCloseFile(errorFile);
599                }
600                errorToOutput = 1;
601                p++;
602            }
603
604            /*
605             * Close the old output file, but only if the error file is not
606             * also using it.
607             */
608
609            if (outputClose != 0) {
610                outputClose = 0;
611                if (errorFile == outputFile) {
612                    errorClose = 1;
613                } else {
614                    TclpCloseFile(outputFile);
615                }
616            }
617            if (outputRelease != 0) {
618                outputRelease = 0;
619                if (errorFile == outputFile) {
620                    errorRelease = 1;
621                } else {
622                    TclpReleaseFile(outputFile);
623                }
624            }
625            nextArg = ((i + 1) == argc) ? NULL : argv[i + 1];
626            outputFile = FileForRedirect(interp, p, atOK, argv[i], nextArg,
627                    flags, &skip, &outputClose, &outputRelease);
628            if (outputFile == NULL) {
629                goto error;
630            }
631            if (errorToOutput) {
632                if (errorClose != 0) {
633                    errorClose = 0;
634                    TclpCloseFile(errorFile);
635                }
636                if (errorRelease != 0) {
637                    errorRelease = 0;
638                    TclpReleaseFile(errorFile);
639                }
640                errorFile = outputFile;
641            }
642            break;
643
644        case '2':
645            if (*p != '>') {
646                break;
647            }
648            p++;
649            atOK = 1;
650            flags = O_WRONLY | O_CREAT | O_TRUNC;
651            if (*p == '>') {
652                p++;
653                atOK = 0;
654                flags = O_WRONLY | O_CREAT;
655            }
656            if (errorClose != 0) {
657                errorClose = 0;
658                TclpCloseFile(errorFile);
659            }
660            if (errorRelease != 0) {
661                errorRelease = 0;
662                TclpReleaseFile(errorFile);
663            }
664            if (atOK && p[0] == '@' && p[1] == '1' && p[2] == '\0') {
665                /*
666                 * Special case handling of 2>@1 to redirect stderr to the
667                 * exec/open output pipe as well. This is meant for the end of
668                 * the command string, otherwise use |& between commands.
669                 */
670
671                if (i != argc-1) {
672                    Tcl_AppendResult(interp, "must specify \"", argv[i],
673                            "\" as last word in command", NULL);
674                    goto error;
675                }
676                errorFile = outputFile;
677                errorToOutput = 2;
678                skip = 1;
679            } else {
680                nextArg = ((i + 1) == argc) ? NULL : argv[i + 1];
681                errorFile = FileForRedirect(interp, p, atOK, argv[i],
682                        nextArg, flags, &skip, &errorClose, &errorRelease);
683                if (errorFile == NULL) {
684                    goto error;
685                }
686            }
687            break;
688
689        default:
690          /* Got a command word, not a redirection */
691          needCmd = 0;
692          break;
693        }
694
695        if (skip != 0) {
696            for (j = i + skip; j < argc; j++) {
697                argv[j - skip] = argv[j];
698            }
699            argc -= skip;
700            i -= 1;
701        }
702    }
703
704    if (needCmd) {
705        /* We had a bar followed only by redirections. */
706
707        Tcl_SetResult(interp,
708                      "illegal use of | or |& in command",
709                      TCL_STATIC);
710        goto error;
711    }
712
713    if (inputFile == NULL) {
714        if (inputLiteral != NULL) {
715            /*
716             * The input for the first process is immediate data coming from
717             * Tcl. Create a temporary file for it and put the data into the
718             * file.
719             */
720
721            inputFile = TclpCreateTempFile(inputLiteral);
722            if (inputFile == NULL) {
723                Tcl_AppendResult(interp,
724                        "couldn't create input file for command: ",
725                        Tcl_PosixError(interp), NULL);
726                goto error;
727            }
728            inputClose = 1;
729        } else if (inPipePtr != NULL) {
730            /*
731             * The input for the first process in the pipeline is to come from
732             * a pipe that can be written from by the caller.
733             */
734
735            if (TclpCreatePipe(&inputFile, inPipePtr) == 0) {
736                Tcl_AppendResult(interp,
737                        "couldn't create input pipe for command: ",
738                        Tcl_PosixError(interp), NULL);
739                goto error;
740            }
741            inputClose = 1;
742        } else {
743            /*
744             * The input for the first process comes from stdin.
745             */
746
747            channel = Tcl_GetStdChannel(TCL_STDIN);
748            if (channel != NULL) {
749                inputFile = TclpMakeFile(channel, TCL_READABLE);
750                if (inputFile != NULL) {
751                    inputRelease = 1;
752                }
753            }
754        }
755    }
756
757    if (outputFile == NULL) {
758        if (outPipePtr != NULL) {
759            /*
760             * Output from the last process in the pipeline is to go to a pipe
761             * that can be read by the caller.
762             */
763
764            if (TclpCreatePipe(outPipePtr, &outputFile) == 0) {
765                Tcl_AppendResult(interp,
766                        "couldn't create output pipe for command: ",
767                        Tcl_PosixError(interp), NULL);
768                goto error;
769            }
770            outputClose = 1;
771        } else {
772            /*
773             * The output for the last process goes to stdout.
774             */
775
776            channel = Tcl_GetStdChannel(TCL_STDOUT);
777            if (channel) {
778                outputFile = TclpMakeFile(channel, TCL_WRITABLE);
779                if (outputFile != NULL) {
780                    outputRelease = 1;
781                }
782            }
783        }
784    }
785
786    if (errorFile == NULL) {
787        if (errorToOutput == 2) {
788            /*
789             * Handle 2>@1 special case at end of cmd line.
790             */
791
792            errorFile = outputFile;
793        } else if (errFilePtr != NULL) {
794            /*
795             * Set up the standard error output sink for the pipeline, if
796             * requested. Use a temporary file which is opened, then deleted.
797             * Could potentially just use pipe, but if it filled up it could
798             * cause the pipeline to deadlock: we'd be waiting for processes
799             * to complete before reading stderr, and processes couldn't
800             * complete because stderr was backed up.
801             */
802
803            errorFile = TclpCreateTempFile(NULL);
804            if (errorFile == NULL) {
805                Tcl_AppendResult(interp,
806                        "couldn't create error file for command: ",
807                        Tcl_PosixError(interp), NULL);
808                goto error;
809            }
810            *errFilePtr = errorFile;
811        } else {
812            /*
813             * Errors from the pipeline go to stderr.
814             */
815
816            channel = Tcl_GetStdChannel(TCL_STDERR);
817            if (channel) {
818                errorFile = TclpMakeFile(channel, TCL_WRITABLE);
819                if (errorFile != NULL) {
820                    errorRelease = 1;
821                }
822            }
823        }
824    }
825
826    /*
827     * Scan through the argc array, creating a process for each group of
828     * arguments between the "|" characters.
829     */
830
831    Tcl_ReapDetachedProcs();
832    pidPtr = (Tcl_Pid *) ckalloc((unsigned) (cmdCount * sizeof(Tcl_Pid)));
833
834    curInFile = inputFile;
835
836    for (i = 0; i < argc; i = lastArg + 1) {
837        int result, joinThisError;
838        Tcl_Pid pid;
839        CONST char *oldName;
840
841        /*
842         * Convert the program name into native form.
843         */
844
845        if (Tcl_TranslateFileName(interp, argv[i], &execBuffer) == NULL) {
846            goto error;
847        }
848
849        /*
850         * Find the end of the current segment of the pipeline.
851         */
852
853        joinThisError = 0;
854        for (lastArg = i; lastArg < argc; lastArg++) {
855            if (argv[lastArg][0] != '|') {
856                continue;
857            }
858            if (argv[lastArg][1] == '\0') {
859                break;
860            }
861            if ((argv[lastArg][1] == '&') && (argv[lastArg][2] == '\0')) {
862                joinThisError = 1;
863                break;
864            }
865        }
866
867        /*
868         * If this is the last segment, use the specified outputFile.
869         * Otherwise create an intermediate pipe. pipeIn will become the
870         * curInFile for the next segment of the pipe.
871         */
872
873        if (lastArg == argc) {
874            curOutFile = outputFile;
875        } else {
876            argv[lastArg] = NULL;
877            if (TclpCreatePipe(&pipeIn, &curOutFile) == 0) {
878                Tcl_AppendResult(interp, "couldn't create pipe: ",
879                        Tcl_PosixError(interp), NULL);
880                goto error;
881            }
882        }
883
884        if (joinThisError != 0) {
885            curErrFile = curOutFile;
886        } else {
887            curErrFile = errorFile;
888        }
889
890        /*
891         * Restore argv[i], since a caller wouldn't expect the contents of
892         * argv to be modified.
893         */
894
895        oldName = argv[i];
896        argv[i] = Tcl_DStringValue(&execBuffer);
897        result = TclpCreateProcess(interp, lastArg - i, argv + i,
898                curInFile, curOutFile, curErrFile, &pid);
899        argv[i] = oldName;
900        if (result != TCL_OK) {
901            goto error;
902        }
903        Tcl_DStringFree(&execBuffer);
904
905        pidPtr[numPids] = pid;
906        numPids++;
907
908        /*
909         * Close off our copies of file descriptors that were set up for this
910         * child, then set up the input for the next child.
911         */
912
913        if ((curInFile != NULL) && (curInFile != inputFile)) {
914            TclpCloseFile(curInFile);
915        }
916        curInFile = pipeIn;
917        pipeIn = NULL;
918
919        if ((curOutFile != NULL) && (curOutFile != outputFile)) {
920            TclpCloseFile(curOutFile);
921        }
922        curOutFile = NULL;
923    }
924
925    *pidArrayPtr = pidPtr;
926
927    /*
928     * All done. Cleanup open files lying around and then return.
929     */
930
931  cleanup:
932    Tcl_DStringFree(&execBuffer);
933
934    if (inputClose) {
935        TclpCloseFile(inputFile);
936    } else if (inputRelease) {
937        TclpReleaseFile(inputFile);
938    }
939    if (outputClose) {
940        TclpCloseFile(outputFile);
941    } else if (outputRelease) {
942        TclpReleaseFile(outputFile);
943    }
944    if (errorClose) {
945        TclpCloseFile(errorFile);
946    } else if (errorRelease) {
947        TclpReleaseFile(errorFile);
948    }
949    return numPids;
950
951    /*
952     * An error occurred. There could have been extra files open, such as
953     * pipes between children. Clean them all up. Detach any child processes
954     * that have been created.
955     */
956
957  error:
958    if (pipeIn != NULL) {
959        TclpCloseFile(pipeIn);
960    }
961    if ((curOutFile != NULL) && (curOutFile != outputFile)) {
962        TclpCloseFile(curOutFile);
963    }
964    if ((curInFile != NULL) && (curInFile != inputFile)) {
965        TclpCloseFile(curInFile);
966    }
967    if ((inPipePtr != NULL) && (*inPipePtr != NULL)) {
968        TclpCloseFile(*inPipePtr);
969        *inPipePtr = NULL;
970    }
971    if ((outPipePtr != NULL) && (*outPipePtr != NULL)) {
972        TclpCloseFile(*outPipePtr);
973        *outPipePtr = NULL;
974    }
975    if ((errFilePtr != NULL) && (*errFilePtr != NULL)) {
976        TclpCloseFile(*errFilePtr);
977        *errFilePtr = NULL;
978    }
979    if (pidPtr != NULL) {
980        for (i = 0; i < numPids; i++) {
981            if (pidPtr[i] != (Tcl_Pid) -1) {
982                Tcl_DetachPids(1, &pidPtr[i]);
983            }
984        }
985        ckfree((char *) pidPtr);
986    }
987    numPids = -1;
988    goto cleanup;
989}
990
991/*
992 *----------------------------------------------------------------------
993 *
994 * Tcl_OpenCommandChannel --
995 *
996 *      Opens an I/O channel to one or more subprocesses specified by argc and
997 *      argv. The flags argument determines the disposition of the stdio
998 *      handles. If the TCL_STDIN flag is set then the standard input for the
999 *      first subprocess will be tied to the channel: writing to the channel
1000 *      will provide input to the subprocess. If TCL_STDIN is not set, then
1001 *      standard input for the first subprocess will be the same as this
1002 *      application's standard input. If TCL_STDOUT is set then standard
1003 *      output from the last subprocess can be read from the channel;
1004 *      otherwise it goes to this application's standard output. If TCL_STDERR
1005 *      is set, standard error output for all subprocesses is returned to the
1006 *      channel and results in an error when the channel is closed; otherwise
1007 *      it goes to this application's standard error. If TCL_ENFORCE_MODE is
1008 *      not set, then argc and argv can redirect the stdio handles to override
1009 *      TCL_STDIN, TCL_STDOUT, and TCL_STDERR; if it is set, then it is an
1010 *      error for argc and argv to override stdio channels for which
1011 *      TCL_STDIN, TCL_STDOUT, and TCL_STDERR have been set.
1012 *
1013 * Results:
1014 *      A new command channel, or NULL on failure with an error message left
1015 *      in interp.
1016 *
1017 * Side effects:
1018 *      Creates processes, opens pipes.
1019 *
1020 *----------------------------------------------------------------------
1021 */
1022
1023Tcl_Channel
1024Tcl_OpenCommandChannel(
1025    Tcl_Interp *interp,         /* Interpreter for error reporting. Can NOT be
1026                                 * NULL. */
1027    int argc,                   /* How many arguments. */
1028    CONST char **argv,          /* Array of arguments for command pipe. */
1029    int flags)                  /* Or'ed combination of TCL_STDIN, TCL_STDOUT,
1030                                 * TCL_STDERR, and TCL_ENFORCE_MODE. */
1031{
1032    TclFile *inPipePtr, *outPipePtr, *errFilePtr;
1033    TclFile inPipe, outPipe, errFile;
1034    int numPids;
1035    Tcl_Pid *pidPtr;
1036    Tcl_Channel channel;
1037
1038    inPipe = outPipe = errFile = NULL;
1039
1040    inPipePtr = (flags & TCL_STDIN) ? &inPipe : NULL;
1041    outPipePtr = (flags & TCL_STDOUT) ? &outPipe : NULL;
1042    errFilePtr = (flags & TCL_STDERR) ? &errFile : NULL;
1043
1044    numPids = TclCreatePipeline(interp, argc, argv, &pidPtr, inPipePtr,
1045            outPipePtr, errFilePtr);
1046
1047    if (numPids < 0) {
1048        goto error;
1049    }
1050
1051    /*
1052     * Verify that the pipes that were created satisfy the readable/writable
1053     * constraints.
1054     */
1055
1056    if (flags & TCL_ENFORCE_MODE) {
1057        if ((flags & TCL_STDOUT) && (outPipe == NULL)) {
1058            Tcl_AppendResult(interp, "can't read output from command:"
1059                    " standard output was redirected", NULL);
1060            goto error;
1061        }
1062        if ((flags & TCL_STDIN) && (inPipe == NULL)) {
1063            Tcl_AppendResult(interp, "can't write input to command:"
1064                    " standard input was redirected", NULL);
1065            goto error;
1066        }
1067    }
1068
1069    channel = TclpCreateCommandChannel(outPipe, inPipe, errFile,
1070            numPids, pidPtr);
1071
1072    if (channel == (Tcl_Channel) NULL) {
1073        Tcl_AppendResult(interp, "pipe for command could not be created",
1074                NULL);
1075        goto error;
1076    }
1077    return channel;
1078
1079  error:
1080    if (numPids > 0) {
1081        Tcl_DetachPids(numPids, pidPtr);
1082        ckfree((char *) pidPtr);
1083    }
1084    if (inPipe != NULL) {
1085        TclpCloseFile(inPipe);
1086    }
1087    if (outPipe != NULL) {
1088        TclpCloseFile(outPipe);
1089    }
1090    if (errFile != NULL) {
1091        TclpCloseFile(errFile);
1092    }
1093    return NULL;
1094}
1095
1096/*
1097 * Local Variables:
1098 * mode: c
1099 * c-basic-offset: 4
1100 * fill-column: 78
1101 * End:
1102 */
Note: See TracBrowser for help on using the repository browser.