OpenCores
URL https://opencores.org/ocsvn/or1k/or1k/trunk

Subversion Repositories or1k

[/] [or1k/] [trunk/] [insight/] [tcl/] [generic/] [tclPipe.c] - Blame information for rev 1765

Details | Compare with Previous | View Log

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

powered by: WebSVN 2.1.0

© copyright 1999-2024 OpenCores.org, equivalent to Oliscience, all rights reserved. OpenCores®, registered trademark.