Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
att
GitHub Repository: att/ast
Path: blob/master/src/lib/libtksh/tcl/tclIOUtil.c
1810 views
1
/*
2
* tclIOUtil.c --
3
*
4
* This file contains a collection of utility procedures that
5
* are shared by the platform specific IO drivers.
6
*
7
* Parts of this file are based on code contributed by Karl
8
* Lehenbauer, Mark Diekhans and Peter da Silva.
9
*
10
* Copyright (c) 1991-1994 The Regents of the University of California.
11
* Copyright (c) 1994-1996 Sun Microsystems, Inc.
12
*
13
* See the file "license.terms" for information on usage and redistribution
14
* of this file, and for a DISCLAIMER OF ALL WARRANTIES.
15
*
16
* SCCS: @(#) tclIOUtil.c 1.128 96/10/02 12:25:36
17
*/
18
19
#include "tclInt.h"
20
#include "tclPort.h"
21
22
extern Tcl_Channel TclCreateCommandChannel _ANSI_ARGS_((Tcl_File, Tcl_File,
23
Tcl_File, int, int*));
24
25
/*
26
* A linked list of the following structures is used to keep track
27
* of child processes that have been detached but haven't exited
28
* yet, so we can make sure that they're properly "reaped" (officially
29
* waited for) and don't lie around as zombies cluttering the
30
* system.
31
*/
32
33
typedef struct Detached {
34
int pid; /* Id of process that's been detached
35
* but isn't known to have exited. */
36
struct Detached *nextPtr; /* Next in list of all detached
37
* processes. */
38
} Detached;
39
40
static Detached *detList = NULL; /* List of all detached proceses. */
41
42
/*
43
* Declarations for local procedures defined in this file:
44
*/
45
46
#if 0
47
static Tcl_File FileForRedirect _ANSI_ARGS_((Tcl_Interp *interp,
48
char *spec, int atOk, char *arg, char *nextArg,
49
int flags, int *skipPtr, int *closePtr,
50
Tcl_DString *namePtr));
51
52
/*
53
*----------------------------------------------------------------------
54
*
55
* FileForRedirect --
56
*
57
* This procedure does much of the work of parsing redirection
58
* operators. It handles "@" if specified and allowed, and a file
59
* name, and opens the file if necessary.
60
*
61
* Results:
62
* The return value is the descriptor number for the file. If an
63
* error occurs then NULL is returned and an error message is left
64
* in interp->result. Several arguments are side-effected; see
65
* the argument list below for details.
66
*
67
* Side effects:
68
* None.
69
*
70
*----------------------------------------------------------------------
71
*/
72
73
static Tcl_File
74
FileForRedirect(interp, spec, atOK, arg, nextArg, flags, skipPtr, closePtr,
75
namePtr)
76
Tcl_Interp *interp; /* Intepreter to use for error reporting. */
77
char *spec; /* Points to character just after
78
* redirection character. */
79
char *arg; /* Pointer to entire argument containing
80
* spec: used for error reporting. */
81
int atOK; /* Non-zero means that '@' notation can be
82
* used to specify a channel, zero means that
83
* it isn't. */
84
char *nextArg; /* Next argument in argc/argv array, if needed
85
* for file name or channel name. May be
86
* NULL. */
87
int flags; /* Flags to use for opening file or to
88
* specify mode for channel. */
89
int *skipPtr; /* Filled with 1 if redirection target was
90
* in spec, 2 if it was in nextArg. */
91
int *closePtr; /* Filled with one if the caller should
92
* close the file when done with it, zero
93
* otherwise. */
94
Tcl_DString *namePtr; /* Pointer to an initialized Tcl_DString that
95
* is filled with the name of the file that
96
* was opened. Unmodified if spec refers
97
* to a channel. */
98
{
99
int writing = (flags & O_ACCMODE) == O_WRONLY;
100
Tcl_Channel chan;
101
Tcl_File file;
102
103
*skipPtr = 1;
104
if ((atOK != 0) && (*spec == '@')) {
105
spec++;
106
if (*spec == '\0') {
107
spec = nextArg;
108
if (spec == NULL) {
109
goto badLastArg;
110
}
111
*skipPtr = 2;
112
}
113
chan = Tcl_GetChannel(interp, spec, NULL);
114
if (chan == (Tcl_Channel) NULL) {
115
return NULL;
116
}
117
file = Tcl_GetChannelFile(chan, writing ? TCL_WRITABLE : TCL_READABLE);
118
if (file == NULL) {
119
Tcl_AppendResult(interp, "channel \"", Tcl_GetChannelName(chan),
120
"\" wasn't opened for ",
121
((writing) ? "writing" : "reading"), (char *) NULL);
122
return NULL;
123
}
124
if (writing) {
125
126
/*
127
* Be sure to flush output to the file, so that anything
128
* written by the child appears after stuff we've already
129
* written.
130
*/
131
132
Tcl_Flush(chan);
133
}
134
} else {
135
char *name;
136
137
if (*spec == '\0') {
138
spec = nextArg;
139
if (spec == NULL) {
140
goto badLastArg;
141
}
142
*skipPtr = 2;
143
}
144
name = Tcl_TranslateFileName(interp, spec, namePtr);
145
if (name != NULL) {
146
file = TclOpenFile(name, flags);
147
} else {
148
file = NULL;
149
}
150
if (file == NULL) {
151
Tcl_AppendResult(interp, "couldn't ",
152
((writing) ? "write" : "read"), " file \"", spec, "\": ",
153
Tcl_PosixError(interp), (char *) NULL);
154
Tcl_DStringFree(namePtr);
155
return NULL;
156
}
157
*closePtr = 1;
158
}
159
return file;
160
161
badLastArg:
162
Tcl_AppendResult(interp, "can't specify \"", arg,
163
"\" as last word in command", (char *) NULL);
164
return NULL;
165
}
166
#endif
167
168
/*
169
*----------------------------------------------------------------------
170
*
171
* TclGetOpenMode --
172
*
173
* Description:
174
* Computes a POSIX mode mask for opening a file, from a given string,
175
* and also sets a flag to indicate whether the caller should seek to
176
* EOF after opening the file.
177
*
178
* Results:
179
* On success, returns mode to pass to "open". If an error occurs, the
180
* returns -1 and if interp is not NULL, sets interp->result to an
181
* error message.
182
*
183
* Side effects:
184
* Sets the integer referenced by seekFlagPtr to 1 to tell the caller
185
* to seek to EOF after opening the file.
186
*
187
* Special note:
188
* This code is based on a prototype implementation contributed
189
* by Mark Diekhans.
190
*
191
*----------------------------------------------------------------------
192
*/
193
194
int
195
TclGetOpenMode(interp, string, seekFlagPtr)
196
Tcl_Interp *interp; /* Interpreter to use for error
197
* reporting - may be NULL. */
198
char *string; /* Mode string, e.g. "r+" or
199
* "RDONLY CREAT". */
200
int *seekFlagPtr; /* Set this to 1 if the caller
201
* should seek to EOF during the
202
* opening of the file. */
203
{
204
int mode, modeArgc, c, i, gotRW;
205
char **modeArgv, *flag;
206
#define RW_MODES (O_RDONLY|O_WRONLY|O_RDWR)
207
208
/*
209
* Check for the simpler fopen-like access modes (e.g. "r"). They
210
* are distinguished from the POSIX access modes by the presence
211
* of a lower-case first letter.
212
*/
213
214
*seekFlagPtr = 0;
215
mode = 0;
216
if (islower(UCHAR(string[0]))) {
217
switch (string[0]) {
218
case 'r':
219
mode = O_RDONLY;
220
break;
221
case 'w':
222
mode = O_WRONLY|O_CREAT|O_TRUNC;
223
break;
224
case 'a':
225
mode = O_WRONLY|O_CREAT;
226
*seekFlagPtr = 1;
227
break;
228
default:
229
error:
230
if (interp != (Tcl_Interp *) NULL) {
231
Tcl_AppendResult(interp,
232
"illegal access mode \"", string, "\"",
233
(char *) NULL);
234
}
235
return -1;
236
}
237
if (string[1] == '+') {
238
mode &= ~O_ACCMODE;
239
mode |= O_RDWR;
240
if (string[2] != 0) {
241
goto error;
242
}
243
} else if (string[1] != 0) {
244
goto error;
245
}
246
return mode;
247
}
248
249
/*
250
* The access modes are specified using a list of POSIX modes
251
* such as O_CREAT.
252
*
253
* IMPORTANT NOTE: We rely on Tcl_SplitList working correctly when
254
* a NULL interpreter is passed in.
255
*/
256
257
if (Tcl_SplitList(interp, string, &modeArgc, &modeArgv) != TCL_OK) {
258
if (interp != (Tcl_Interp *) NULL) {
259
Tcl_AddErrorInfo(interp,
260
"\n while processing open access modes \"");
261
Tcl_AddErrorInfo(interp, string);
262
Tcl_AddErrorInfo(interp, "\"");
263
}
264
return -1;
265
}
266
267
gotRW = 0;
268
for (i = 0; i < modeArgc; i++) {
269
flag = modeArgv[i];
270
c = flag[0];
271
if ((c == 'R') && (strcmp(flag, "RDONLY") == 0)) {
272
mode = (mode & ~O_ACCMODE) | O_RDONLY;
273
gotRW = 1;
274
} else if ((c == 'W') && (strcmp(flag, "WRONLY") == 0)) {
275
mode = (mode & ~O_ACCMODE) | O_WRONLY;
276
gotRW = 1;
277
} else if ((c == 'R') && (strcmp(flag, "RDWR") == 0)) {
278
mode = (mode & ~O_ACCMODE) | O_RDWR;
279
gotRW = 1;
280
} else if ((c == 'A') && (strcmp(flag, "APPEND") == 0)) {
281
mode |= O_APPEND;
282
*seekFlagPtr = 1;
283
} else if ((c == 'C') && (strcmp(flag, "CREAT") == 0)) {
284
mode |= O_CREAT;
285
} else if ((c == 'E') && (strcmp(flag, "EXCL") == 0)) {
286
mode |= O_EXCL;
287
} else if ((c == 'N') && (strcmp(flag, "NOCTTY") == 0)) {
288
#ifdef O_NOCTTY
289
mode |= O_NOCTTY;
290
#else
291
if (interp != (Tcl_Interp *) NULL) {
292
Tcl_AppendResult(interp, "access mode \"", flag,
293
"\" not supported by this system", (char *) NULL);
294
}
295
ckfree((char *) modeArgv);
296
return -1;
297
#endif
298
} else if ((c == 'N') && (strcmp(flag, "NONBLOCK") == 0)) {
299
#if defined(O_NDELAY) || defined(O_NONBLOCK)
300
# ifdef O_NONBLOCK
301
mode |= O_NONBLOCK;
302
# else
303
mode |= O_NDELAY;
304
# endif
305
#else
306
if (interp != (Tcl_Interp *) NULL) {
307
Tcl_AppendResult(interp, "access mode \"", flag,
308
"\" not supported by this system", (char *) NULL);
309
}
310
ckfree((char *) modeArgv);
311
return -1;
312
#endif
313
} else if ((c == 'T') && (strcmp(flag, "TRUNC") == 0)) {
314
mode |= O_TRUNC;
315
} else {
316
if (interp != (Tcl_Interp *) NULL) {
317
Tcl_AppendResult(interp, "invalid access mode \"", flag,
318
"\": must be RDONLY, WRONLY, RDWR, APPEND, CREAT",
319
" EXCL, NOCTTY, NONBLOCK, or TRUNC", (char *) NULL);
320
}
321
ckfree((char *) modeArgv);
322
return -1;
323
}
324
}
325
ckfree((char *) modeArgv);
326
if (!gotRW) {
327
if (interp != (Tcl_Interp *) NULL) {
328
Tcl_AppendResult(interp, "access mode must include either",
329
" RDONLY, WRONLY, or RDWR", (char *) NULL);
330
}
331
return -1;
332
}
333
return mode;
334
}
335
336
#if 0
337
/*
338
*----------------------------------------------------------------------
339
*
340
* Tcl_EvalFile --
341
*
342
* Read in a file and process the entire file as one gigantic
343
* Tcl command.
344
*
345
* Results:
346
* A standard Tcl result, which is either the result of executing
347
* the file or an error indicating why the file couldn't be read.
348
*
349
* Side effects:
350
* Depends on the commands in the file.
351
*
352
*----------------------------------------------------------------------
353
*/
354
355
int
356
Tcl_EvalFile(interp, fileName)
357
Tcl_Interp *interp; /* Interpreter in which to process file. */
358
char *fileName; /* Name of file to process. Tilde-substitution
359
* will be performed on this name. */
360
{
361
int result;
362
struct stat statBuf;
363
char *cmdBuffer = (char *) NULL;
364
char *oldScriptFile = (char *) NULL;
365
Interp *iPtr = (Interp *) interp;
366
Tcl_DString buffer;
367
char *nativeName = (char *) NULL;
368
Tcl_Channel chan = (Tcl_Channel) NULL;
369
370
Tcl_ResetResult(interp);
371
oldScriptFile = iPtr->scriptFile;
372
iPtr->scriptFile = fileName;
373
Tcl_DStringInit(&buffer);
374
nativeName = Tcl_TranslateFileName(interp, fileName, &buffer);
375
if (nativeName == NULL) {
376
goto error;
377
}
378
379
/*
380
* If Tcl_TranslateFileName didn't already copy the file name, do it
381
* here. This way we don't depend on fileName staying constant
382
* throughout the execution of the script (e.g., what if it happens
383
* to point to a Tcl variable that the script could change?).
384
*/
385
386
if (nativeName != Tcl_DStringValue(&buffer)) {
387
Tcl_DStringSetLength(&buffer, 0);
388
Tcl_DStringAppend(&buffer, nativeName, -1);
389
nativeName = Tcl_DStringValue(&buffer);
390
}
391
if (stat(nativeName, &statBuf) == -1) {
392
Tcl_SetErrno(errno);
393
Tcl_AppendResult(interp, "couldn't read file \"", fileName,
394
"\": ", Tcl_PosixError(interp), (char *) NULL);
395
goto error;
396
}
397
chan = Tcl_OpenFileChannel(interp, nativeName, "r", 0644);
398
if (chan == (Tcl_Channel) NULL) {
399
Tcl_ResetResult(interp);
400
Tcl_AppendResult(interp, "couldn't read file \"", fileName,
401
"\": ", Tcl_PosixError(interp), (char *) NULL);
402
goto error;
403
}
404
cmdBuffer = (char *) ckalloc((unsigned) statBuf.st_size+1);
405
result = Tcl_Read(chan, cmdBuffer, statBuf.st_size);
406
if (result < 0) {
407
Tcl_Close(interp, chan);
408
Tcl_AppendResult(interp, "couldn't read file \"", fileName,
409
"\": ", Tcl_PosixError(interp), (char *) NULL);
410
goto error;
411
}
412
cmdBuffer[result] = 0;
413
if (Tcl_Close(interp, chan) != TCL_OK) {
414
goto error;
415
}
416
417
result = Tcl_Eval(interp, cmdBuffer);
418
if (result == TCL_RETURN) {
419
result = TclUpdateReturnInfo(iPtr);
420
} else if (result == TCL_ERROR) {
421
char msg[200];
422
423
/*
424
* Record information telling where the error occurred.
425
*/
426
427
sprintf(msg, "\n (file \"%.150s\" line %d)", fileName,
428
interp->errorLine);
429
Tcl_AddErrorInfo(interp, msg);
430
}
431
iPtr->scriptFile = oldScriptFile;
432
ckfree(cmdBuffer);
433
Tcl_DStringFree(&buffer);
434
return result;
435
436
error:
437
if (cmdBuffer != (char *) NULL) {
438
ckfree(cmdBuffer);
439
}
440
iPtr->scriptFile = oldScriptFile;
441
Tcl_DStringFree(&buffer);
442
return TCL_ERROR;
443
}
444
445
/*
446
*----------------------------------------------------------------------
447
*
448
* Tcl_DetachPids --
449
*
450
* This procedure is called to indicate that one or more child
451
* processes have been placed in background and will never be
452
* waited for; they should eventually be reaped by
453
* Tcl_ReapDetachedProcs.
454
*
455
* Results:
456
* None.
457
*
458
* Side effects:
459
* None.
460
*
461
*----------------------------------------------------------------------
462
*/
463
464
void
465
Tcl_DetachPids(numPids, pidPtr)
466
int numPids; /* Number of pids to detach: gives size
467
* of array pointed to by pidPtr. */
468
int *pidPtr; /* Array of pids to detach. */
469
{
470
register Detached *detPtr;
471
int i;
472
473
for (i = 0; i < numPids; i++) {
474
detPtr = (Detached *) ckalloc(sizeof(Detached));
475
detPtr->pid = pidPtr[i];
476
detPtr->nextPtr = detList;
477
detList = detPtr;
478
}
479
}
480
481
/*
482
*----------------------------------------------------------------------
483
*
484
* Tcl_ReapDetachedProcs --
485
*
486
* This procedure checks to see if any detached processes have
487
* exited and, if so, it "reaps" them by officially waiting on
488
* them. It should be called "occasionally" to make sure that
489
* all detached processes are eventually reaped.
490
*
491
* Results:
492
* None.
493
*
494
* Side effects:
495
* Processes are waited on, so that they can be reaped by the
496
* system.
497
*
498
*----------------------------------------------------------------------
499
*/
500
501
void
502
Tcl_ReapDetachedProcs()
503
{
504
register Detached *detPtr;
505
Detached *nextPtr, *prevPtr;
506
int status;
507
int pid;
508
509
for (detPtr = detList, prevPtr = NULL; detPtr != NULL; ) {
510
pid = (int) Tcl_WaitPid(detPtr->pid, &status, WNOHANG);
511
if ((pid == 0) || ((pid == -1) && (errno != ECHILD))) {
512
prevPtr = detPtr;
513
detPtr = detPtr->nextPtr;
514
continue;
515
}
516
nextPtr = detPtr->nextPtr;
517
if (prevPtr == NULL) {
518
detList = detPtr->nextPtr;
519
} else {
520
prevPtr->nextPtr = detPtr->nextPtr;
521
}
522
ckfree((char *) detPtr);
523
detPtr = nextPtr;
524
}
525
}
526
527
/*
528
*----------------------------------------------------------------------
529
*
530
* TclCleanupChildren --
531
*
532
* This is a utility procedure used to wait for child processes
533
* to exit, record information about abnormal exits, and then
534
* collect any stderr output generated by them.
535
*
536
* Results:
537
* The return value is a standard Tcl result. If anything at
538
* weird happened with the child processes, TCL_ERROR is returned
539
* and a message is left in interp->result.
540
*
541
* Side effects:
542
* If the last character of interp->result is a newline, then it
543
* is removed unless keepNewline is non-zero. File errorId gets
544
* closed, and pidPtr is freed back to the storage allocator.
545
*
546
*----------------------------------------------------------------------
547
*/
548
549
int
550
TclCleanupChildren(interp, numPids, pidPtr, errorChan)
551
Tcl_Interp *interp; /* Used for error messages. */
552
int numPids; /* Number of entries in pidPtr array. */
553
int *pidPtr; /* Array of process ids of children. */
554
Tcl_Channel errorChan; /* Channel for file containing stderr output
555
* from pipeline. NULL means there isn't any
556
* stderr output. */
557
{
558
int result = TCL_OK;
559
int i, pid, abnormalExit, anyErrorInfo;
560
WAIT_STATUS_TYPE waitStatus;
561
char *msg;
562
563
abnormalExit = 0;
564
for (i = 0; i < numPids; i++) {
565
pid = (int) Tcl_WaitPid(pidPtr[i], (int *) &waitStatus, 0);
566
if (pid == -1) {
567
result = TCL_ERROR;
568
if (interp != (Tcl_Interp *) NULL) {
569
msg = Tcl_PosixError(interp);
570
if (errno == ECHILD) {
571
/*
572
* This changeup in message suggested by Mark Diekhans
573
* to remind people that ECHILD errors can occur on
574
* some systems if SIGCHLD isn't in its default state.
575
*/
576
577
msg =
578
"child process lost (is SIGCHLD ignored or trapped?)";
579
}
580
Tcl_AppendResult(interp, "error waiting for process to exit: ",
581
msg, (char *) NULL);
582
}
583
continue;
584
}
585
586
/*
587
* Create error messages for unusual process exits. An
588
* extra newline gets appended to each error message, but
589
* it gets removed below (in the same fashion that an
590
* extra newline in the command's output is removed).
591
*/
592
593
if (!WIFEXITED(waitStatus) || (WEXITSTATUS(waitStatus) != 0)) {
594
char msg1[20], msg2[20];
595
596
result = TCL_ERROR;
597
sprintf(msg1, "%d", pid);
598
if (WIFEXITED(waitStatus)) {
599
if (interp != (Tcl_Interp *) NULL) {
600
sprintf(msg2, "%d", WEXITSTATUS(waitStatus));
601
Tcl_SetErrorCode(interp, "CHILDSTATUS", msg1, msg2,
602
(char *) NULL);
603
}
604
abnormalExit = 1;
605
} else if (WIFSIGNALED(waitStatus)) {
606
if (interp != (Tcl_Interp *) NULL) {
607
char *p;
608
609
p = Tcl_SignalMsg((int) (WTERMSIG(waitStatus)));
610
Tcl_SetErrorCode(interp, "CHILDKILLED", msg1,
611
Tcl_SignalId((int) (WTERMSIG(waitStatus))), p,
612
(char *) NULL);
613
Tcl_AppendResult(interp, "child killed: ", p, "\n",
614
(char *) NULL);
615
}
616
} else if (WIFSTOPPED(waitStatus)) {
617
if (interp != (Tcl_Interp *) NULL) {
618
char *p;
619
620
p = Tcl_SignalMsg((int) (WSTOPSIG(waitStatus)));
621
Tcl_SetErrorCode(interp, "CHILDSUSP", msg1,
622
Tcl_SignalId((int) (WSTOPSIG(waitStatus))),
623
p, (char *) NULL);
624
Tcl_AppendResult(interp, "child suspended: ", p, "\n",
625
(char *) NULL);
626
}
627
} else {
628
if (interp != (Tcl_Interp *) NULL) {
629
Tcl_AppendResult(interp,
630
"child wait status didn't make sense\n",
631
(char *) NULL);
632
}
633
}
634
}
635
}
636
637
/*
638
* Read the standard error file. If there's anything there,
639
* then return an error and add the file's contents to the result
640
* string.
641
*/
642
643
anyErrorInfo = 0;
644
if (errorChan != NULL) {
645
646
/*
647
* Make sure we start at the beginning of the file.
648
*/
649
650
Tcl_Seek(errorChan, 0L, SEEK_SET);
651
652
if (interp != (Tcl_Interp *) NULL) {
653
while (1) {
654
#define BUFFER_SIZE 1000
655
char buffer[BUFFER_SIZE+1];
656
int count;
657
658
count = Tcl_Read(errorChan, buffer, BUFFER_SIZE);
659
if (count == 0) {
660
break;
661
}
662
result = TCL_ERROR;
663
if (count < 0) {
664
Tcl_AppendResult(interp,
665
"error reading stderr output file: ",
666
Tcl_PosixError(interp), (char *) NULL);
667
break; /* out of the "while (1)" loop. */
668
}
669
buffer[count] = 0;
670
Tcl_AppendResult(interp, buffer, (char *) NULL);
671
anyErrorInfo = 1;
672
}
673
}
674
675
Tcl_Close((Tcl_Interp *) NULL, errorChan);
676
}
677
678
/*
679
* If a child exited abnormally but didn't output any error information
680
* at all, generate an error message here.
681
*/
682
683
if (abnormalExit && !anyErrorInfo && (interp != (Tcl_Interp *) NULL)) {
684
Tcl_AppendResult(interp, "child process exited abnormally",
685
(char *) NULL);
686
}
687
688
return result;
689
}
690
691
/*
692
*----------------------------------------------------------------------
693
*
694
* TclCreatePipeline --
695
*
696
* Given an argc/argv array, instantiate a pipeline of processes
697
* as described by the argv.
698
*
699
* Results:
700
* The return value is a count of the number of new processes
701
* created, or -1 if an error occurred while creating the pipeline.
702
* *pidArrayPtr is filled in with the address of a dynamically
703
* allocated array giving the ids of all of the processes. It
704
* is up to the caller to free this array when it isn't needed
705
* anymore. If inPipePtr is non-NULL, *inPipePtr is filled in
706
* with the file id for the input pipe for the pipeline (if any):
707
* the caller must eventually close this file. If outPipePtr
708
* isn't NULL, then *outPipePtr is filled in with the file id
709
* for the output pipe from the pipeline: the caller must close
710
* this file. If errFilePtr isn't NULL, then *errFilePtr is filled
711
* with a file id that may be used to read error output after the
712
* pipeline completes.
713
*
714
* Side effects:
715
* Processes and pipes are created.
716
*
717
*----------------------------------------------------------------------
718
*/
719
720
int
721
TclCreatePipeline(interp, argc, argv, pidArrayPtr, inPipePtr,
722
outPipePtr, errFilePtr)
723
Tcl_Interp *interp; /* Interpreter to use for error reporting. */
724
int argc; /* Number of entries in argv. */
725
char **argv; /* Array of strings describing commands in
726
* pipeline plus I/O redirection with <,
727
* <<, >, etc. Argv[argc] must be NULL. */
728
int **pidArrayPtr; /* Word at *pidArrayPtr gets filled in with
729
* address of array of pids for processes
730
* in pipeline (first pid is first process
731
* in pipeline). */
732
Tcl_File *inPipePtr; /* If non-NULL, input to the pipeline comes
733
* from a pipe (unless overridden by
734
* redirection in the command). The file
735
* id with which to write to this pipe is
736
* stored at *inPipePtr. NULL means command
737
* specified its own input source. */
738
Tcl_File *outPipePtr; /* If non-NULL, output to the pipeline goes
739
* to a pipe, unless overriden by redirection
740
* in the command. The file id with which to
741
* read frome this pipe is stored at
742
* *outPipePtr. NULL means command specified
743
* its own output sink. */
744
Tcl_File *errFilePtr; /* If non-NULL, all stderr output from the
745
* pipeline will go to a temporary file
746
* created here, and a descriptor to read
747
* the file will be left at *errFilePtr.
748
* The file will be removed already, so
749
* closing this descriptor will be the end
750
* of the file. If this is NULL, then
751
* all stderr output goes to our stderr.
752
* If the pipeline specifies redirection
753
* then the file will still be created
754
* but it will never get any data. */
755
{
756
#if defined( MAC_TCL )
757
Tcl_AppendResult(interp,
758
"command pipelines not supported on Macintosh OS", NULL);
759
return -1;
760
#else /* !MAC_TCL */
761
int *pidPtr = NULL; /* Points to malloc-ed array holding all
762
* the pids of child processes. */
763
int numPids = 0; /* Actual number of processes that exist
764
* at *pidPtr right now. */
765
int cmdCount; /* Count of number of distinct commands
766
* found in argc/argv. */
767
char *inputLiteral = NULL; /* If non-null, then this points to a
768
* string containing input data (specified
769
* via <<) to be piped to the first process
770
* in the pipeline. */
771
Tcl_File inputFile = NULL; /* If != NULL, gives file to use as input for
772
* first process in pipeline (specified via <
773
* or <@). */
774
Tcl_DString inputFileName; /* If non-empty, gives name of file that
775
* corresponds to inputFile. */
776
int inputClose = 0; /* If non-zero, then inputFile should be
777
* closed when cleaning up. */
778
Tcl_File outputFile = NULL; /* Writable file for output from last command
779
* in pipeline (could be file or pipe). NULL
780
* means use stdout. */
781
Tcl_DString outputFileName; /* If non-empty, gives name of file that
782
* corresponds to outputFile. */
783
int outputClose = 0; /* If non-zero, then outputFile should be
784
* closed when cleaning up. */
785
Tcl_File errorFile = NULL; /* Writable file for error output from all
786
* commands in pipeline. NULL means use
787
* stderr. */
788
Tcl_DString errorFileName; /* If non-empty, gives name of file that
789
* corresponds to errorFile. */
790
int errorClose = 0; /* If non-zero, then errorFile should be
791
* closed when cleaning up. */
792
char *p;
793
int skip, lastBar, lastArg, i, j, atOK, flags, errorToOutput;
794
Tcl_DString execBuffer;
795
Tcl_File pipeIn;
796
Tcl_File curInFile, curOutFile, curErrFile;
797
char *curInFileName, *curOutFileName, *curErrFileName;
798
Tcl_Channel channel;
799
800
if (inPipePtr != NULL) {
801
*inPipePtr = NULL;
802
}
803
if (outPipePtr != NULL) {
804
*outPipePtr = NULL;
805
}
806
if (errFilePtr != NULL) {
807
*errFilePtr = NULL;
808
}
809
810
Tcl_DStringInit(&inputFileName);
811
Tcl_DStringInit(&outputFileName);
812
Tcl_DStringInit(&errorFileName);
813
Tcl_DStringInit(&execBuffer);
814
815
pipeIn = NULL;
816
curInFile = NULL;
817
curOutFile = NULL;
818
curErrFile = NULL;
819
820
numPids = 0;
821
pidPtr = NULL;
822
823
/*
824
* First, scan through all the arguments to figure out the structure
825
* of the pipeline. Process all of the input and output redirection
826
* arguments and remove them from the argument list in the pipeline.
827
* Count the number of distinct processes (it's the number of "|"
828
* arguments plus one) but don't remove the "|" arguments because
829
* they'll be used in the second pass to seperate the individual
830
* child processes. Cannot start the child processes in this pass
831
* because the redirection symbols may appear anywhere in the
832
* command line -- e.g., the '<' that specifies the input to the
833
* entire pipe may appear at the very end of the argument list.
834
*/
835
836
lastBar = -1;
837
cmdCount = 1;
838
for (i = 0; i < argc; i++) {
839
skip = 0;
840
p = argv[i];
841
switch (*p++) {
842
case '|':
843
if (*p == '&') {
844
p++;
845
}
846
if (*p == '\0') {
847
if ((i == (lastBar + 1)) || (i == (argc - 1))) {
848
interp->result = "illegal use of | or |& in command";
849
goto error;
850
}
851
}
852
lastBar = i;
853
cmdCount++;
854
break;
855
856
case '<':
857
if (inputClose != 0) {
858
inputClose = 0;
859
Tcl_DStringFree(&inputFileName);
860
TclCloseFile(inputFile);
861
}
862
if (*p == '<') {
863
inputFile = NULL;
864
inputLiteral = p + 1;
865
skip = 1;
866
if (*inputLiteral == '\0') {
867
inputLiteral = argv[i + 1];
868
if (inputLiteral == NULL) {
869
Tcl_AppendResult(interp, "can't specify \"", argv[i],
870
"\" as last word in command", (char *) NULL);
871
goto error;
872
}
873
skip = 2;
874
}
875
} else {
876
inputLiteral = NULL;
877
inputFile = FileForRedirect(interp, p, 1, argv[i],
878
argv[i + 1], O_RDONLY, &skip, &inputClose,
879
&inputFileName);
880
if (inputFile == NULL) {
881
goto error;
882
}
883
}
884
break;
885
886
case '>':
887
atOK = 1;
888
flags = O_WRONLY | O_CREAT | O_TRUNC;
889
errorToOutput = 0;
890
if (*p == '>') {
891
p++;
892
atOK = 0;
893
flags = O_WRONLY | O_CREAT;
894
}
895
if (*p == '&') {
896
if (errorClose != 0) {
897
errorClose = 0;
898
Tcl_DStringFree(&errorFileName);
899
TclCloseFile(errorFile);
900
}
901
errorToOutput = 1;
902
p++;
903
}
904
905
if (outputClose != 0) {
906
outputClose = 0;
907
Tcl_DStringFree(&outputFileName);
908
TclCloseFile(outputFile);
909
}
910
outputFile = FileForRedirect(interp, p, atOK, argv[i],
911
argv[i + 1], flags, &skip, &outputClose,
912
&outputFileName);
913
if (outputFile == NULL) {
914
goto error;
915
}
916
if (atOK == 0) {
917
TclSeekFile(outputFile, 0, SEEK_END);
918
}
919
if (errorToOutput) {
920
errorClose = 0;
921
errorFile = outputFile;
922
}
923
break;
924
925
case '2':
926
if (*p != '>') {
927
break;
928
}
929
p++;
930
atOK = 1;
931
flags = O_WRONLY | O_CREAT | O_TRUNC;
932
if (*p == '>') {
933
p++;
934
atOK = 0;
935
flags = O_WRONLY | O_CREAT;
936
}
937
if (errorClose != 0) {
938
errorClose = 0;
939
Tcl_DStringFree(&errorFileName);
940
TclCloseFile(errorFile);
941
}
942
errorFile = FileForRedirect(interp, p, atOK, argv[i],
943
argv[i + 1], flags, &skip, &errorClose,
944
&errorFileName);
945
if (errorFile == NULL) {
946
goto error;
947
}
948
if (atOK == 0) {
949
TclSeekFile(errorFile, 0, SEEK_END);
950
}
951
break;
952
}
953
954
if (skip != 0) {
955
for (j = i + skip; j < argc; j++) {
956
argv[j - skip] = argv[j];
957
}
958
argc -= skip;
959
i -= 1;
960
}
961
}
962
963
if (inputFile == NULL) {
964
if (inputLiteral != NULL) {
965
/*
966
* The input for the first process is immediate data coming from
967
* Tcl. Create a temporary file for it and put the data into the
968
* file.
969
*/
970
inputFile = TclCreateTempFile(inputLiteral, &inputFileName);
971
if (inputFile == NULL) {
972
Tcl_AppendResult(interp,
973
"couldn't create input file for command: ",
974
Tcl_PosixError(interp), (char *) NULL);
975
goto error;
976
}
977
inputClose = 1;
978
} else if (inPipePtr != NULL) {
979
/*
980
* The input for the first process in the pipeline is to
981
* come from a pipe that can be written from by the caller.
982
*/
983
984
if (TclCreatePipe(&inputFile, inPipePtr) == 0) {
985
Tcl_AppendResult(interp,
986
"couldn't create input pipe for command: ",
987
Tcl_PosixError(interp), (char *) NULL);
988
goto error;
989
}
990
inputClose = 1;
991
} else {
992
/*
993
* The input for the first process comes from stdin.
994
*/
995
996
channel = Tcl_GetStdChannel(TCL_STDIN);
997
if (channel != NULL) {
998
inputFile = Tcl_GetChannelFile(channel, TCL_READABLE);
999
}
1000
}
1001
}
1002
1003
if (outputFile == NULL) {
1004
if (outPipePtr != NULL) {
1005
/*
1006
* Output from the last process in the pipeline is to go to a
1007
* pipe that can be read by the caller.
1008
*/
1009
1010
if (TclCreatePipe(outPipePtr, &outputFile) == 0) {
1011
Tcl_AppendResult(interp,
1012
"couldn't create output pipe for command: ",
1013
Tcl_PosixError(interp), (char *) NULL);
1014
goto error;
1015
}
1016
outputClose = 1;
1017
} else {
1018
/*
1019
* The output for the last process goes to stdout.
1020
*/
1021
1022
channel = Tcl_GetStdChannel(TCL_STDOUT);
1023
if (channel) {
1024
outputFile = Tcl_GetChannelFile(channel, TCL_WRITABLE);
1025
}
1026
}
1027
}
1028
1029
if (errorFile == NULL) {
1030
if (errFilePtr != NULL) {
1031
/*
1032
* Set up the standard error output sink for the pipeline, if
1033
* requested. Use a temporary file which is opened, then deleted.
1034
* Could potentially just use pipe, but if it filled up it could
1035
* cause the pipeline to deadlock: we'd be waiting for processes
1036
* to complete before reading stderr, and processes couldn't
1037
* complete because stderr was backed up.
1038
*/
1039
1040
errorFile = TclCreateTempFile(NULL, &errorFileName);
1041
if (errorFile == NULL) {
1042
Tcl_AppendResult(interp,
1043
"couldn't create error file for command: ",
1044
Tcl_PosixError(interp), (char *) NULL);
1045
goto error;
1046
}
1047
*errFilePtr = errorFile;
1048
} else {
1049
/*
1050
* Errors from the pipeline go to stderr.
1051
*/
1052
1053
channel = Tcl_GetStdChannel(TCL_STDERR);
1054
if (channel) {
1055
errorFile = Tcl_GetChannelFile(channel, TCL_WRITABLE);
1056
}
1057
}
1058
}
1059
1060
/*
1061
* Scan through the argc array, creating a process for each
1062
* group of arguments between the "|" characters.
1063
*/
1064
1065
Tcl_ReapDetachedProcs();
1066
pidPtr = (int *) ckalloc((unsigned) (cmdCount * sizeof(int)));
1067
1068
curInFile = inputFile;
1069
curInFileName = Tcl_DStringValue(&inputFileName);
1070
if (curInFileName[0] == '\0') {
1071
curInFileName = NULL;
1072
}
1073
1074
for (i = 0; i < argc; i = lastArg + 1) {
1075
int joinThisError, pid;
1076
1077
/*
1078
* Convert the program name into native form.
1079
*/
1080
1081
argv[i] = Tcl_TranslateFileName(interp, argv[i], &execBuffer);
1082
if (argv[i] == NULL) {
1083
goto error;
1084
}
1085
1086
/*
1087
* Find the end of the current segment of the pipeline.
1088
*/
1089
1090
joinThisError = 0;
1091
for (lastArg = i; lastArg < argc; lastArg++) {
1092
if (argv[lastArg][0] == '|') {
1093
if (argv[lastArg][1] == '\0') {
1094
break;
1095
}
1096
if ((argv[lastArg][1] == '&') && (argv[lastArg][2] == '\0')) {
1097
joinThisError = 1;
1098
break;
1099
}
1100
}
1101
}
1102
argv[lastArg] = NULL;
1103
1104
/*
1105
* If this is the last segment, use the specified outputFile.
1106
* Otherwise create an intermediate pipe. pipeIn will become the
1107
* curInFile for the next segment of the pipe.
1108
*/
1109
1110
if (lastArg == argc) {
1111
curOutFile = outputFile;
1112
curOutFileName = Tcl_DStringValue(&outputFileName);
1113
if (curOutFileName[0] == '\0') {
1114
curOutFileName = NULL;
1115
}
1116
} else {
1117
if (TclCreatePipe(&pipeIn, &curOutFile) == 0) {
1118
Tcl_AppendResult(interp, "couldn't create pipe: ",
1119
Tcl_PosixError(interp), (char *) NULL);
1120
goto error;
1121
}
1122
curOutFileName = NULL;
1123
}
1124
1125
if (joinThisError != 0) {
1126
curErrFile = curOutFile;
1127
curErrFileName = curOutFileName;
1128
} else {
1129
curErrFile = errorFile;
1130
curErrFileName = Tcl_DStringValue(&errorFileName);
1131
if (curErrFileName[0] == '\0') {
1132
curErrFileName = NULL;
1133
}
1134
}
1135
1136
if (TclpCreateProcess(interp, lastArg - i, argv + i,
1137
curInFile, curOutFile, curErrFile, curInFileName,
1138
curOutFileName, curErrFileName, &pid) != TCL_OK) {
1139
goto error;
1140
}
1141
Tcl_DStringFree(&execBuffer);
1142
1143
pidPtr[numPids] = pid;
1144
numPids++;
1145
1146
/*
1147
* Close off our copies of file descriptors that were set up for
1148
* this child, then set up the input for the next child.
1149
*/
1150
1151
if ((curInFile != NULL) && (curInFile != inputFile)) {
1152
TclCloseFile(curInFile);
1153
}
1154
curInFile = pipeIn;
1155
curInFileName = NULL;
1156
pipeIn = NULL;
1157
1158
if ((curOutFile != NULL) && (curOutFile != outputFile)) {
1159
TclCloseFile(curOutFile);
1160
}
1161
curOutFile = NULL;
1162
}
1163
1164
*pidArrayPtr = pidPtr;
1165
1166
/*
1167
* All done. Cleanup open files lying around and then return.
1168
*/
1169
1170
cleanup:
1171
Tcl_DStringFree(&inputFileName);
1172
Tcl_DStringFree(&outputFileName);
1173
Tcl_DStringFree(&errorFileName);
1174
Tcl_DStringFree(&execBuffer);
1175
1176
if (inputClose) {
1177
TclCloseFile(inputFile);
1178
}
1179
if (outputClose) {
1180
TclCloseFile(outputFile);
1181
}
1182
if (errorClose) {
1183
TclCloseFile(errorFile);
1184
}
1185
return numPids;
1186
1187
/*
1188
* An error occurred. There could have been extra files open, such
1189
* as pipes between children. Clean them all up. Detach any child
1190
* processes that have been created.
1191
*/
1192
1193
error:
1194
if (pipeIn != NULL) {
1195
TclCloseFile(pipeIn);
1196
}
1197
if ((curOutFile != NULL) && (curOutFile != outputFile)) {
1198
TclCloseFile(curOutFile);
1199
}
1200
if ((curInFile != NULL) && (curInFile != inputFile)) {
1201
TclCloseFile(curInFile);
1202
}
1203
if ((inPipePtr != NULL) && (*inPipePtr != NULL)) {
1204
TclCloseFile(*inPipePtr);
1205
*inPipePtr = NULL;
1206
}
1207
if ((outPipePtr != NULL) && (*outPipePtr != NULL)) {
1208
TclCloseFile(*outPipePtr);
1209
*outPipePtr = NULL;
1210
}
1211
if ((errFilePtr != NULL) && (*errFilePtr != NULL)) {
1212
TclCloseFile(*errFilePtr);
1213
*errFilePtr = NULL;
1214
}
1215
if (pidPtr != NULL) {
1216
for (i = 0; i < numPids; i++) {
1217
if (pidPtr[i] != -1) {
1218
Tcl_DetachPids(1, &pidPtr[i]);
1219
}
1220
}
1221
ckfree((char *) pidPtr);
1222
}
1223
numPids = -1;
1224
goto cleanup;
1225
#endif /* !MAC_TCL */
1226
}
1227
#endif
1228
1229
/*
1230
*----------------------------------------------------------------------
1231
*
1232
* Tcl_GetErrno --
1233
*
1234
* Gets the current value of the Tcl error code variable. This is
1235
* currently the global variable "errno" but could in the future
1236
* change to something else.
1237
*
1238
* Results:
1239
* The value of the Tcl error code variable.
1240
*
1241
* Side effects:
1242
* None. Note that the value of the Tcl error code variable is
1243
* UNDEFINED if a call to Tcl_SetErrno did not precede this call.
1244
*
1245
*----------------------------------------------------------------------
1246
*/
1247
1248
int
1249
Tcl_GetErrno()
1250
{
1251
return errno;
1252
}
1253
1254
/*
1255
*----------------------------------------------------------------------
1256
*
1257
* Tcl_SetErrno --
1258
*
1259
* Sets the Tcl error code variable to the supplied value.
1260
*
1261
* Results:
1262
* None.
1263
*
1264
* Side effects:
1265
* Modifies the value of the Tcl error code variable.
1266
*
1267
*----------------------------------------------------------------------
1268
*/
1269
1270
void
1271
Tcl_SetErrno(err)
1272
int err; /* The new value. */
1273
{
1274
errno = err;
1275
}
1276
1277
/*
1278
*----------------------------------------------------------------------
1279
*
1280
* Tcl_PosixError --
1281
*
1282
* This procedure is typically called after UNIX kernel calls
1283
* return errors. It stores machine-readable information about
1284
* the error in $errorCode returns an information string for
1285
* the caller's use.
1286
*
1287
* Results:
1288
* The return value is a human-readable string describing the
1289
* error.
1290
*
1291
* Side effects:
1292
* The global variable $errorCode is reset.
1293
*
1294
*----------------------------------------------------------------------
1295
*/
1296
1297
char *
1298
Tcl_PosixError(interp)
1299
Tcl_Interp *interp; /* Interpreter whose $errorCode variable
1300
* is to be changed. */
1301
{
1302
char *id, *msg;
1303
1304
msg = Tcl_ErrnoMsg(errno);
1305
id = Tcl_ErrnoId();
1306
Tcl_SetErrorCode(interp, "POSIX", id, msg, (char *) NULL);
1307
return msg;
1308
}
1309
1310
#if 1
1311
/*
1312
*----------------------------------------------------------------------
1313
*
1314
* Tcl_OpenCommandChannel --
1315
*
1316
* Opens an I/O channel to one or more subprocesses specified
1317
* by argc and argv. The flags argument determines the
1318
* disposition of the stdio handles. If the TCL_STDIN flag is
1319
* set then the standard input for the first subprocess will
1320
* be tied to the channel: writing to the channel will provide
1321
* input to the subprocess. If TCL_STDIN is not set, then
1322
* standard input for the first subprocess will be the same as
1323
* this application's standard input. If TCL_STDOUT is set then
1324
* standard output from the last subprocess can be read from the
1325
* channel; otherwise it goes to this application's standard
1326
* output. If TCL_STDERR is set, standard error output for all
1327
* subprocesses is returned to the channel and results in an error
1328
* when the channel is closed; otherwise it goes to this
1329
* application's standard error. If TCL_ENFORCE_MODE is not set,
1330
* then argc and argv can redirect the stdio handles to override
1331
* TCL_STDIN, TCL_STDOUT, and TCL_STDERR; if it is set, then it
1332
* is an error for argc and argv to override stdio channels for
1333
* which TCL_STDIN, TCL_STDOUT, and TCL_STDERR have been set.
1334
*
1335
* Results:
1336
* A new command channel, or NULL on failure with an error
1337
* message left in interp.
1338
*
1339
* Side effects:
1340
* Creates processes, opens pipes.
1341
*
1342
*----------------------------------------------------------------------
1343
*/
1344
1345
Tcl_Channel
1346
Tcl_OpenCommandChannel(interp, argc, argv, flags)
1347
Tcl_Interp *interp; /* Interpreter for error reporting. Can
1348
* NOT be NULL. */
1349
int argc; /* How many arguments. */
1350
char **argv; /* Array of arguments for command pipe. */
1351
int flags; /* Or'ed combination of TCL_STDIN, TCL_STDOUT,
1352
* TCL_STDERR, and TCL_ENFORCE_MODE. */
1353
{
1354
Tcl_File *inPipePtr, *outPipePtr, *errFilePtr;
1355
Tcl_File inPipe, outPipe, errFile;
1356
int numPids, *pidPtr;
1357
Tcl_Channel channel;
1358
1359
inPipe = outPipe = errFile = NULL;
1360
1361
inPipePtr = (flags & TCL_STDIN) ? &inPipe : NULL;
1362
outPipePtr = (flags & TCL_STDOUT) ? &outPipe : NULL;
1363
errFilePtr = (flags & TCL_STDERR) ? &errFile : NULL;
1364
1365
numPids = TclCreatePipeline(interp, argc, argv, &pidPtr, inPipePtr,
1366
outPipePtr, errFilePtr);
1367
1368
if (numPids < 0) {
1369
goto error;
1370
}
1371
1372
/*
1373
* Verify that the pipes that were created satisfy the
1374
* readable/writable constraints.
1375
*/
1376
1377
if (flags & TCL_ENFORCE_MODE) {
1378
if ((flags & TCL_STDOUT) && (outPipe == NULL)) {
1379
Tcl_AppendResult(interp, "can't read output from command:",
1380
" standard output was redirected", (char *) NULL);
1381
goto error;
1382
}
1383
if ((flags & TCL_STDIN) && (inPipe == NULL)) {
1384
Tcl_AppendResult(interp, "can't write input to command:",
1385
" standard input was redirected", (char *) NULL);
1386
goto error;
1387
}
1388
}
1389
1390
channel = TclCreateCommandChannel(outPipe, inPipe, errFile,
1391
numPids, pidPtr);
1392
1393
if (channel == (Tcl_Channel) NULL) {
1394
Tcl_AppendResult(interp, "pipe for command could not be created",
1395
(char *) NULL);
1396
goto error;
1397
}
1398
return channel;
1399
1400
error:
1401
#if 0
1402
if (numPids > 0) {
1403
Tcl_DetachPids(numPids, pidPtr);
1404
ckfree((char *) pidPtr);
1405
}
1406
#endif
1407
if (inPipe != NULL) {
1408
TclClosePipeFile(inPipe);
1409
}
1410
if (outPipe != NULL) {
1411
TclClosePipeFile(outPipe);
1412
}
1413
if (errFile != NULL) {
1414
TclClosePipeFile(errFile);
1415
}
1416
return NULL;
1417
}
1418
#endif
1419
1420