]>
Commit | Line | Data |
---|---|---|
6a5fa4e0 MG |
1 | /* |
2 | * tclUnixUtil.c -- | |
3 | * | |
4 | * This file contains a collection of utility procedures that | |
5 | * are present in the Tcl's UNIX core but not in the generic | |
6 | * core. For example, they do file manipulation and process | |
7 | * manipulation. | |
8 | * | |
9 | * The Tcl_Fork and Tcl_WaitPids procedures are based on code | |
10 | * contributed by Karl Lehenbauer, Mark Diekhans and Peter | |
11 | * da Silva. | |
12 | * | |
13 | * Copyright 1991 Regents of the University of California | |
14 | * Permission to use, copy, modify, and distribute this | |
15 | * software and its documentation for any purpose and without | |
16 | * fee is hereby granted, provided that this copyright | |
17 | * notice appears in all copies. The University of California | |
18 | * makes no representations about the suitability of this | |
19 | * software for any purpose. It is provided "as is" without | |
20 | * express or implied warranty. | |
21 | */ | |
22 | ||
23 | #ifndef lint | |
24 | static char rcsid[] = "$Header: /user6/ouster/tcl/RCS/tclUnixUtil.c,v 1.18 91/11/21 14:53:46 ouster Exp $ SPRITE (Berkeley)"; | |
25 | #endif /* not lint */ | |
26 | ||
27 | #include "tclint.h" | |
28 | #include "tclunix.h" | |
29 | ||
30 | /* | |
31 | * Data structures of the following type are used by Tcl_Fork and | |
32 | * Tcl_WaitPids to keep track of child processes. | |
33 | */ | |
34 | ||
35 | typedef struct { | |
36 | int pid; /* Process id of child. */ | |
37 | WAIT_STATUS_TYPE status; /* Status returned when child exited or | |
38 | * suspended. */ | |
39 | int flags; /* Various flag bits; see below for | |
40 | * definitions. */ | |
41 | } WaitInfo; | |
42 | ||
43 | /* | |
44 | * Flag bits in WaitInfo structures: | |
45 | * | |
46 | * WI_READY - Non-zero means process has exited or | |
47 | * suspended since it was forked or last | |
48 | * returned by Tcl_WaitPids. | |
49 | * WI_DETACHED - Non-zero means no-one cares about the | |
50 | * process anymore. Ignore it until it | |
51 | * exits, then forget about it. | |
52 | */ | |
53 | ||
54 | #define WI_READY 1 | |
55 | #define WI_DETACHED 2 | |
56 | ||
57 | static WaitInfo *waitTable = NULL; | |
58 | static int waitTableSize = 0; /* Total number of entries available in | |
59 | * waitTable. */ | |
60 | static int waitTableUsed = 0; /* Number of entries in waitTable that | |
61 | * are actually in use right now. Active | |
62 | * entries are always at the beginning | |
63 | * of the table. */ | |
64 | #define WAIT_TABLE_GROW_BY 4 | |
65 | \f | |
66 | /* | |
67 | *---------------------------------------------------------------------- | |
68 | * | |
69 | * Tcl_EvalFile -- | |
70 | * | |
71 | * Read in a file and process the entire file as one gigantic | |
72 | * Tcl command. | |
73 | * | |
74 | * Results: | |
75 | * A standard Tcl result, which is either the result of executing | |
76 | * the file or an error indicating why the file couldn't be read. | |
77 | * | |
78 | * Side effects: | |
79 | * Depends on the commands in the file. | |
80 | * | |
81 | *---------------------------------------------------------------------- | |
82 | */ | |
83 | ||
84 | int | |
85 | Tcl_EvalFile(interp, fileName) | |
86 | Tcl_Interp *interp; /* Interpreter in which to process file. */ | |
87 | char *fileName; /* Name of file to process. Tilde-substitution | |
88 | * will be performed on this name. */ | |
89 | { | |
90 | int fileId, result; | |
91 | struct stat statBuf; | |
92 | char *cmdBuffer, *end, *oldScriptFile; | |
93 | Interp *iPtr = (Interp *) interp; | |
94 | ||
95 | oldScriptFile = iPtr->scriptFile; | |
96 | iPtr->scriptFile = fileName; | |
97 | fileName = Tcl_TildeSubst(interp, fileName); | |
98 | if (fileName == NULL) { | |
99 | goto error; | |
100 | } | |
101 | #ifdef MSDOS | |
102 | filename2DOS(fileName); | |
103 | #endif | |
104 | fileId = open(fileName, O_RDONLY, 0); | |
105 | ||
106 | if (fileId < 0) { | |
107 | Tcl_AppendResult(interp, "couldn't read file \"", fileName, | |
108 | "\": ", Tcl_UnixError(interp), (char *) NULL); | |
109 | goto error; | |
110 | } | |
111 | if (fstat(fileId, &statBuf) == -1) { | |
112 | Tcl_AppendResult(interp, "couldn't stat file \"", fileName, | |
113 | "\": ", Tcl_UnixError(interp), (char *) NULL); | |
114 | close(fileId); | |
115 | goto error; | |
116 | } | |
117 | cmdBuffer = (char *) ckalloc((unsigned) statBuf.st_size+1); | |
118 | #ifdef MSDOS | |
119 | if (read(fileId, cmdBuffer, (int) statBuf.st_size) < 0) { | |
120 | #else | |
121 | if (read(fileId, cmdBuffer, (int) statBuf.st_size) != statBuf.st_size) { | |
122 | #endif | |
123 | Tcl_AppendResult(interp, "error in reading file \"", fileName, | |
124 | "\": ", Tcl_UnixError(interp), (char *) NULL); | |
125 | close(fileId); | |
126 | goto error; | |
127 | } | |
128 | if (close(fileId) != 0) { | |
129 | Tcl_AppendResult(interp, "error closing file \"", fileName, | |
130 | "\": ", Tcl_UnixError(interp), (char *) NULL); | |
131 | goto error; | |
132 | } | |
133 | cmdBuffer[statBuf.st_size] = 0; | |
134 | result = Tcl_Eval(interp, cmdBuffer, 0, &end); | |
135 | if (result == TCL_RETURN) { | |
136 | result = TCL_OK; | |
137 | } | |
138 | if (result == TCL_ERROR) { | |
139 | char msg[200]; | |
140 | ||
141 | /* | |
142 | * Record information telling where the error occurred. | |
143 | */ | |
144 | ||
145 | sprintf(msg, "\n (file \"%.150s\" line %d)", fileName, | |
146 | interp->errorLine); | |
147 | Tcl_AddErrorInfo(interp, msg); | |
148 | } | |
149 | ckfree(cmdBuffer); | |
150 | iPtr->scriptFile = oldScriptFile; | |
151 | return result; | |
152 | ||
153 | error: | |
154 | iPtr->scriptFile = oldScriptFile; | |
155 | return TCL_ERROR; | |
156 | } | |
157 | \f | |
158 | /* | |
159 | *---------------------------------------------------------------------- | |
160 | * | |
161 | * Tcl_Fork -- | |
162 | * | |
163 | * Create a new process using the vfork system call, and keep | |
164 | * track of it for "safe" waiting with Tcl_WaitPids. | |
165 | * | |
166 | * Results: | |
167 | * The return value is the value returned by the vfork system | |
168 | * call (0 means child, > 0 means parent (value is child id), | |
169 | * < 0 means error). | |
170 | * | |
171 | * Side effects: | |
172 | * A new process is created, and an entry is added to an internal | |
173 | * table of child processes if the process is created successfully. | |
174 | * | |
175 | *---------------------------------------------------------------------- | |
176 | */ | |
177 | ||
178 | int | |
179 | Tcl_Fork() | |
180 | { | |
181 | WaitInfo *waitPtr; | |
182 | pid_t pid; | |
183 | ||
184 | /* | |
185 | * Disable SIGPIPE signals: if they were allowed, this process | |
186 | * might go away unexpectedly if children misbehave. This code | |
187 | * can potentially interfere with other application code that | |
188 | * expects to handle SIGPIPEs; what's really needed is an | |
189 | * arbiter for signals to allow them to be "shared". | |
190 | */ | |
191 | ||
192 | if (waitTable == NULL) { | |
193 | (void) signal(SIGPIPE, SIG_IGN); | |
194 | } | |
195 | ||
196 | /* | |
197 | * Enlarge the wait table if there isn't enough space for a new | |
198 | * entry. | |
199 | */ | |
200 | ||
201 | if (waitTableUsed == waitTableSize) { | |
202 | int newSize; | |
203 | WaitInfo *newWaitTable; | |
204 | ||
205 | newSize = waitTableSize + WAIT_TABLE_GROW_BY; | |
206 | newWaitTable = (WaitInfo *) ckalloc((unsigned) | |
207 | (newSize * sizeof(WaitInfo))); | |
208 | memcpy((VOID *) newWaitTable, (VOID *) waitTable, | |
209 | (waitTableSize * sizeof(WaitInfo))); | |
210 | if (waitTable != NULL) { | |
211 | ckfree((char *) waitTable); | |
212 | } | |
213 | waitTable = newWaitTable; | |
214 | waitTableSize = newSize; | |
215 | } | |
216 | ||
217 | /* | |
218 | * Make a new process and enter it into the table if the fork | |
219 | * is successful. | |
220 | */ | |
221 | ||
222 | waitPtr = &waitTable[waitTableUsed]; | |
223 | pid = fork(); | |
224 | if (pid > 0) { | |
225 | waitPtr->pid = pid; | |
226 | waitPtr->flags = 0; | |
227 | waitTableUsed++; | |
228 | } | |
229 | return pid; | |
230 | } | |
231 | \f | |
232 | /* | |
233 | *---------------------------------------------------------------------- | |
234 | * | |
235 | * Tcl_WaitPids -- | |
236 | * | |
237 | * This procedure is used to wait for one or more processes created | |
238 | * by Tcl_Fork to exit or suspend. It records information about | |
239 | * all processes that exit or suspend, even those not waited for, | |
240 | * so that later waits for them will be able to get the status | |
241 | * information. | |
242 | * | |
243 | * Results: | |
244 | * -1 is returned if there is an error in the wait kernel call. | |
245 | * Otherwise the pid of an exited/suspended process from *pidPtr | |
246 | * is returned and *statusPtr is set to the status value returned | |
247 | * by the wait kernel call. | |
248 | * | |
249 | * Side effects: | |
250 | * Doesn't return until one of the pids at *pidPtr exits or suspends. | |
251 | * | |
252 | *---------------------------------------------------------------------- | |
253 | */ | |
254 | ||
255 | int | |
256 | Tcl_WaitPids(numPids, pidPtr, statusPtr) | |
257 | int numPids; /* Number of pids to wait on: gives size | |
258 | * of array pointed to by pidPtr. */ | |
259 | int *pidPtr; /* Pids to wait on: return when one of | |
260 | * these processes exits or suspends. */ | |
261 | int *statusPtr; /* Wait status is returned here. */ | |
262 | { | |
263 | int i, count, pid; | |
264 | register WaitInfo *waitPtr; | |
265 | int anyProcesses; | |
266 | WAIT_STATUS_TYPE status; | |
267 | ||
268 | while (1) { | |
269 | /* | |
270 | * Scan the table of child processes to see if one of the | |
271 | * specified children has already exited or suspended. If so, | |
272 | * remove it from the table and return its status. | |
273 | */ | |
274 | ||
275 | anyProcesses = 0; | |
276 | for (waitPtr = waitTable, count = waitTableUsed; | |
277 | count > 0; waitPtr++, count--) { | |
278 | for (i = 0; i < numPids; i++) { | |
279 | if (pidPtr[i] != waitPtr->pid) { | |
280 | continue; | |
281 | } | |
282 | anyProcesses = 1; | |
283 | if (waitPtr->flags & WI_READY) { | |
284 | *statusPtr = *((int *) &waitPtr->status); | |
285 | pid = waitPtr->pid; | |
286 | if (WIFEXITED(waitPtr->status) | |
287 | || WIFSIGNALED(waitPtr->status)) { | |
288 | *waitPtr = waitTable[waitTableUsed-1]; | |
289 | waitTableUsed--; | |
290 | } else { | |
291 | waitPtr->flags &= ~WI_READY; | |
292 | } | |
293 | return pid; | |
294 | } | |
295 | } | |
296 | } | |
297 | ||
298 | /* | |
299 | * Make sure that the caller at least specified one valid | |
300 | * process to wait for. | |
301 | */ | |
302 | ||
303 | if (!anyProcesses) { | |
304 | errno = ECHILD; | |
305 | return -1; | |
306 | } | |
307 | ||
308 | /* | |
309 | * Wait for a process to exit or suspend, then update its | |
310 | * entry in the table and go back to the beginning of the | |
311 | * loop to see if it's one of the desired processes. | |
312 | */ | |
313 | ||
314 | pid = wait(&status); | |
315 | if (pid < 0) { | |
316 | return pid; | |
317 | } | |
318 | for (waitPtr = waitTable, count = waitTableUsed; ; | |
319 | waitPtr++, count--) { | |
320 | if (count == 0) { | |
321 | break; /* Ignore unknown processes. */ | |
322 | } | |
323 | if (pid != waitPtr->pid) { | |
324 | continue; | |
325 | } | |
326 | ||
327 | /* | |
328 | * If the process has been detached, then ignore anything | |
329 | * other than an exit, and drop the entry on exit. | |
330 | */ | |
331 | ||
332 | if (waitPtr->flags & WI_DETACHED) { | |
333 | if (WIFEXITED(status) || WIFSIGNALED(status)) { | |
334 | *waitPtr = waitTable[waitTableUsed-1]; | |
335 | waitTableUsed--; | |
336 | } | |
337 | } else { | |
338 | waitPtr->status = status; | |
339 | waitPtr->flags |= WI_READY; | |
340 | } | |
341 | break; | |
342 | } | |
343 | } | |
344 | } | |
345 | \f | |
346 | /* | |
347 | *---------------------------------------------------------------------- | |
348 | * | |
349 | * Tcl_DetachPids -- | |
350 | * | |
351 | * This procedure is called to indicate that one or more child | |
352 | * processes have been placed in background and are no longer | |
353 | * cared about. They should be ignored in future calls to | |
354 | * Tcl_WaitPids. | |
355 | * | |
356 | * Results: | |
357 | * None. | |
358 | * | |
359 | * Side effects: | |
360 | * None. | |
361 | * | |
362 | *---------------------------------------------------------------------- | |
363 | */ | |
364 | ||
365 | void | |
366 | Tcl_DetachPids(numPids, pidPtr) | |
367 | int numPids; /* Number of pids to detach: gives size | |
368 | * of array pointed to by pidPtr. */ | |
369 | int *pidPtr; /* Array of pids to detach: must have | |
370 | * been created by Tcl_Fork. */ | |
371 | { | |
372 | register WaitInfo *waitPtr; | |
373 | int i, count, pid; | |
374 | ||
375 | for (i = 0; i < numPids; i++) { | |
376 | pid = pidPtr[i]; | |
377 | for (waitPtr = waitTable, count = waitTableUsed; | |
378 | count > 0; waitPtr++, count--) { | |
379 | if (pid != waitPtr->pid) { | |
380 | continue; | |
381 | } | |
382 | ||
383 | /* | |
384 | * If the process has already exited then destroy its | |
385 | * table entry now. | |
386 | */ | |
387 | ||
388 | if ((waitPtr->flags & WI_READY) && (WIFEXITED(waitPtr->status) | |
389 | || WIFSIGNALED(waitPtr->status))) { | |
390 | *waitPtr = waitTable[waitTableUsed-1]; | |
391 | waitTableUsed--; | |
392 | } else { | |
393 | waitPtr->flags |= WI_DETACHED; | |
394 | } | |
395 | goto nextPid; | |
396 | } | |
397 | panic("Tcl_Detach couldn't find process"); | |
398 | ||
399 | nextPid: | |
400 | continue; | |
401 | } | |
402 | } | |
403 | \f | |
404 | /* | |
405 | *---------------------------------------------------------------------- | |
406 | * | |
407 | * Tcl_CreatePipeline -- | |
408 | * | |
409 | * Given an argc/argv array, instantiate a pipeline of processes | |
410 | * as described by the argv. | |
411 | * | |
412 | * Results: | |
413 | * The return value is a count of the number of new processes | |
414 | * created, or -1 if an error occurred while creating the pipeline. | |
415 | * *pidArrayPtr is filled in with the address of a dynamically | |
416 | * allocated array giving the ids of all of the processes. It | |
417 | * is up to the caller to free this array when it isn't needed | |
418 | * anymore. If inPipePtr is non-NULL, *inPipePtr is filled in | |
419 | * with the file id for the input pipe for the pipeline (if any): | |
420 | * the caller must eventually close this file. If outPipePtr | |
421 | * isn't NULL, then *outPipePtr is filled in with the file id | |
422 | * for the output pipe from the pipeline: the caller must close | |
423 | * this file. If errFilePtr isn't NULL, then *errFilePtr is filled | |
424 | * with a file id that may be used to read error output after the | |
425 | * pipeline completes. | |
426 | * | |
427 | * Side effects: | |
428 | * Processes and pipes are created. | |
429 | * | |
430 | *---------------------------------------------------------------------- | |
431 | */ | |
432 | ||
433 | int | |
434 | Tcl_CreatePipeline(interp, argc, argv, pidArrayPtr, inPipePtr, | |
435 | outPipePtr, errFilePtr) | |
436 | Tcl_Interp *interp; /* Interpreter to use for error reporting. */ | |
437 | int argc; /* Number of entries in argv. */ | |
438 | char **argv; /* Array of strings describing commands in | |
439 | * pipeline plus I/O redirection with <, | |
440 | * <<, and >. Argv[argc] must be NULL. */ | |
441 | int **pidArrayPtr; /* Word at *pidArrayPtr gets filled in with | |
442 | * address of array of pids for processes | |
443 | * in pipeline (first pid is first process | |
444 | * in pipeline). */ | |
445 | int *inPipePtr; /* If non-NULL, input to the pipeline comes | |
446 | * from a pipe (unless overridden by | |
447 | * redirection in the command). The file | |
448 | * id with which to write to this pipe is | |
449 | * stored at *inPipePtr. -1 means command | |
450 | * specified its own input source. */ | |
451 | int *outPipePtr; /* If non-NULL, output to the pipeline goes | |
452 | * to a pipe, unless overriden by redirection | |
453 | * in the command. The file id with which to | |
454 | * read frome this pipe is stored at | |
455 | * *outPipePtr. -1 means command specified | |
456 | * its own output sink. */ | |
457 | int *errFilePtr; /* If non-NULL, all stderr output from the | |
458 | * pipeline will go to a temporary file | |
459 | * created here, and a descriptor to read | |
460 | * the file will be left at *errFilePtr. | |
461 | * The file will be removed already, so | |
462 | * closing this descriptor will be the end | |
463 | * of the file. If this is NULL, then | |
464 | * all stderr output goes to our stderr. */ | |
465 | { | |
466 | int *pidPtr = NULL; /* Points to malloc-ed array holding all | |
467 | * the pids of child processes. */ | |
468 | int numPids = 0; /* Actual number of processes that exist | |
469 | * at *pidPtr right now. */ | |
470 | int cmdCount; /* Count of number of distinct commands | |
471 | * found in argc/argv. */ | |
472 | char *input = NULL; /* Describes input for pipeline, depending | |
473 | * on "inputFile". NULL means take input | |
474 | * from stdin/pipe. */ | |
475 | int inputFile = 0; /* Non-zero means input is name of input | |
476 | * file. Zero means input holds actual | |
477 | * text to be input to command. */ | |
478 | char *output = NULL; /* Holds name of output file to pipe to, | |
479 | * or NULL if output goes to stdout/pipe. */ | |
480 | int inputId = -1; /* Readable file id input to current command in | |
481 | * pipeline (could be file or pipe). -1 | |
482 | * means use stdin. */ | |
483 | int outputId = -1; /* Writable file id for output from current | |
484 | * command in pipeline (could be file or pipe). | |
485 | * -1 means use stdout. */ | |
486 | int errorId = -1; /* Writable file id for all standard error | |
487 | * output from all commands in pipeline. -1 | |
488 | * means use stderr. */ | |
489 | int lastOutputId = -1; /* Write file id for output from last command | |
490 | * in pipeline (could be file or pipe). | |
491 | * -1 means use stdout. */ | |
492 | int pipeIds[2]; /* File ids for pipe that's being created. */ | |
493 | int firstArg, lastArg; /* Indexes of first and last arguments in | |
494 | * current command. */ | |
495 | int lastBar; | |
496 | char *execName; | |
497 | int i, j, pid; | |
498 | ||
499 | if (inPipePtr != NULL) { | |
500 | *inPipePtr = -1; | |
501 | } | |
502 | if (outPipePtr != NULL) { | |
503 | *outPipePtr = -1; | |
504 | } | |
505 | if (errFilePtr != NULL) { | |
506 | *errFilePtr = -1; | |
507 | } | |
508 | pipeIds[0] = pipeIds[1] = -1; | |
509 | ||
510 | /* | |
511 | * First, scan through all the arguments to figure out the structure | |
512 | * of the pipeline. Count the number of distinct processes (it's the | |
513 | * number of "|" arguments). If there are "<", "<<", or ">" arguments | |
514 | * then make note of input and output redirection and remove these | |
515 | * arguments and the arguments that follow them. | |
516 | */ | |
517 | ||
518 | cmdCount = 1; | |
519 | lastBar = -1; | |
520 | for (i = 0; i < argc; i++) { | |
521 | if ((argv[i][0] == '|') && ((argv[i][1] == 0))) { | |
522 | if ((i == (lastBar+1)) || (i == (argc-1))) { | |
523 | interp->result = "illegal use of | in command"; | |
524 | return -1; | |
525 | } | |
526 | lastBar = i; | |
527 | cmdCount++; | |
528 | continue; | |
529 | } else if (argv[i][0] == '<') { | |
530 | if (argv[i][1] == 0) { | |
531 | input = argv[i+1]; | |
532 | inputFile = 1; | |
533 | } else if ((argv[i][1] == '<') && (argv[i][2] == 0)) { | |
534 | input = argv[i+1]; | |
535 | inputFile = 0; | |
536 | } else { | |
537 | continue; | |
538 | } | |
539 | } else if ((argv[i][0] == '>') && (argv[i][1] == 0)) { | |
540 | output = argv[i+1]; | |
541 | } else { | |
542 | continue; | |
543 | } | |
544 | if (i >= (argc-1)) { | |
545 | Tcl_AppendResult(interp, "can't specify \"", argv[i], | |
546 | "\" as last word in command", (char *) NULL); | |
547 | return -1; | |
548 | } | |
549 | for (j = i+2; j < argc; j++) { | |
550 | argv[j-2] = argv[j]; | |
551 | } | |
552 | argc -= 2; | |
553 | i--; /* Process new arg from same position. */ | |
554 | } | |
555 | if (argc == 0) { | |
556 | interp->result = "didn't specify command to execute"; | |
557 | return -1; | |
558 | } | |
559 | ||
560 | /* | |
561 | * Set up the redirected input source for the pipeline, if | |
562 | * so requested. | |
563 | */ | |
564 | ||
565 | if (input != NULL) { | |
566 | if (!inputFile) { | |
567 | /* | |
568 | * Immediate data in command. Create temporary file and | |
569 | * put data into file. | |
570 | */ | |
571 | ||
572 | #ifdef MSDOS | |
573 | # define TMP_STDIN_NAME "tcl.in" | |
574 | #else | |
575 | # define TMP_STDIN_NAME "/tmp/tcl.in.XXXXXX" | |
576 | #endif | |
577 | char inName[sizeof(TMP_STDIN_NAME) + 1]; | |
578 | int length; | |
579 | ||
580 | strcpy(inName, TMP_STDIN_NAME); | |
581 | mkstemp(inName); | |
582 | inputId = open(inName, O_RDWR|O_CREAT|O_TRUNC, 0600); | |
583 | if (inputId < 0) { | |
584 | Tcl_AppendResult(interp, | |
585 | "couldn't create input file for command: ", | |
586 | Tcl_UnixError(interp), (char *) NULL); | |
587 | goto error; | |
588 | } | |
589 | length = strlen(input); | |
590 | #ifdef MSDOS | |
591 | if (write(inputId, input, length) < 0) { | |
592 | #else | |
593 | if (write(inputId, input, length) != length) { | |
594 | #endif | |
595 | Tcl_AppendResult(interp, | |
596 | "couldn't write file input for command: ", | |
597 | Tcl_UnixError(interp), (char *) NULL); | |
598 | goto error; | |
599 | } | |
600 | if ((lseek(inputId, 0L, 0) == -1) || (unlink(inName) == -1)) { | |
601 | Tcl_AppendResult(interp, | |
602 | "couldn't reset or remove input file for command: ", | |
603 | Tcl_UnixError(interp), (char *) NULL); | |
604 | goto error; | |
605 | } | |
606 | } else { | |
607 | /* | |
608 | * File redirection. Just open the file. | |
609 | */ | |
610 | ||
611 | inputId = open(input, O_RDONLY, 0); | |
612 | if (inputId < 0) { | |
613 | Tcl_AppendResult(interp, | |
614 | "couldn't read file \"", input, "\": ", | |
615 | Tcl_UnixError(interp), (char *) NULL); | |
616 | goto error; | |
617 | } | |
618 | } | |
619 | } else if (inPipePtr != NULL) { | |
620 | if (pipe(pipeIds) != 0) { | |
621 | Tcl_AppendResult(interp, | |
622 | "couldn't create input pipe for command: ", | |
623 | Tcl_UnixError(interp), (char *) NULL); | |
624 | goto error; | |
625 | } | |
626 | inputId = pipeIds[0]; | |
627 | *inPipePtr = pipeIds[1]; | |
628 | pipeIds[0] = pipeIds[1] = -1; | |
629 | } | |
630 | ||
631 | /* | |
632 | * Set up the redirected output sink for the pipeline from one | |
633 | * of two places, if requested. | |
634 | */ | |
635 | ||
636 | if (output != NULL) { | |
637 | /* | |
638 | * Output is to go to a file. | |
639 | */ | |
640 | ||
641 | lastOutputId = open(output, O_WRONLY|O_CREAT|O_TRUNC, 0666); | |
642 | if (lastOutputId < 0) { | |
643 | Tcl_AppendResult(interp, | |
644 | "couldn't write file \"", output, "\": ", | |
645 | Tcl_UnixError(interp), (char *) NULL); | |
646 | goto error; | |
647 | } | |
648 | } else if (outPipePtr != NULL) { | |
649 | /* | |
650 | * Output is to go to a pipe. | |
651 | */ | |
652 | ||
653 | if (pipe(pipeIds) != 0) { | |
654 | Tcl_AppendResult(interp, | |
655 | "couldn't create output pipe: ", | |
656 | Tcl_UnixError(interp), (char *) NULL); | |
657 | goto error; | |
658 | } | |
659 | lastOutputId = pipeIds[1]; | |
660 | *outPipePtr = pipeIds[0]; | |
661 | pipeIds[0] = pipeIds[1] = -1; | |
662 | } | |
663 | ||
664 | /* | |
665 | * Set up the standard error output sink for the pipeline, if | |
666 | * requested. Use a temporary file which is opened, then deleted. | |
667 | * Could potentially just use pipe, but if it filled up it could | |
668 | * cause the pipeline to deadlock: we'd be waiting for processes | |
669 | * to complete before reading stderr, and processes couldn't complete | |
670 | * because stderr was backed up. | |
671 | */ | |
672 | ||
673 | if (errFilePtr != NULL) { | |
674 | # define TMP_STDERR_NAME "/tmp/tcl.err.XXXXXX" | |
675 | char errName[sizeof(TMP_STDERR_NAME) + 1]; | |
676 | ||
677 | strcpy(errName, TMP_STDERR_NAME); | |
678 | mkstemp(errName); | |
679 | errorId = open(errName, O_WRONLY|O_CREAT|O_TRUNC, 0600); | |
680 | if (errorId < 0) { | |
681 | errFileError: | |
682 | Tcl_AppendResult(interp, | |
683 | "couldn't create error file for command: ", | |
684 | Tcl_UnixError(interp), (char *) NULL); | |
685 | goto error; | |
686 | } | |
687 | *errFilePtr = open(errName, O_RDONLY, 0); | |
688 | if (*errFilePtr < 0) { | |
689 | goto errFileError; | |
690 | } | |
691 | if (unlink(errName) == -1) { | |
692 | Tcl_AppendResult(interp, | |
693 | "couldn't remove error file for command: ", | |
694 | Tcl_UnixError(interp), (char *) NULL); | |
695 | goto error; | |
696 | } | |
697 | } | |
698 | ||
699 | /* | |
700 | * Scan through the argc array, forking off a process for each | |
701 | * group of arguments between "|" arguments. | |
702 | */ | |
703 | ||
704 | pidPtr = (int *) ckalloc((unsigned) (cmdCount * sizeof(int))); | |
705 | for (i = 0; i < numPids; i++) { | |
706 | pidPtr[i] = -1; | |
707 | } | |
708 | for (firstArg = 0; firstArg < argc; numPids++, firstArg = lastArg+1) { | |
709 | for (lastArg = firstArg; lastArg < argc; lastArg++) { | |
710 | if ((argv[lastArg][0] == '|') && (argv[lastArg][1] == 0)) { | |
711 | break; | |
712 | } | |
713 | } | |
714 | argv[lastArg] = NULL; | |
715 | if (lastArg == argc) { | |
716 | outputId = lastOutputId; | |
717 | } else { | |
718 | if (pipe(pipeIds) != 0) { | |
719 | Tcl_AppendResult(interp, "couldn't create pipe: ", | |
720 | Tcl_UnixError(interp), (char *) NULL); | |
721 | goto error; | |
722 | } | |
723 | outputId = pipeIds[1]; | |
724 | } | |
725 | execName = Tcl_TildeSubst(interp, argv[firstArg]); | |
726 | pid = Tcl_Fork(); | |
727 | if (pid == -1) { | |
728 | Tcl_AppendResult(interp, "couldn't fork child process: ", | |
729 | Tcl_UnixError(interp), (char *) NULL); | |
730 | goto error; | |
731 | } | |
732 | if (pid == 0) { | |
733 | char errSpace[200]; | |
734 | ||
735 | if (((inputId != -1) && (dup2(inputId, 0) == -1)) | |
736 | || ((outputId != -1) && (dup2(outputId, 1) == -1)) | |
737 | || ((errorId != -1) && (dup2(errorId, 2) == -1))) { | |
738 | char *err; | |
739 | err = "forked process couldn't set up input/output\n"; | |
740 | write(errorId < 0 ? 2 : errorId, err, strlen(err)); | |
741 | _exit(1); | |
742 | } | |
743 | for (i = 3; (i <= outputId) || (i <= inputId) || (i <= errorId); | |
744 | i++) { | |
745 | close(i); | |
746 | } | |
747 | execvp(execName, &argv[firstArg]); | |
748 | sprintf(errSpace, "couldn't find \"%.150s\" to execute\n", | |
749 | argv[firstArg]); | |
750 | write(2, errSpace, strlen(errSpace)); | |
751 | _exit(1); | |
752 | } else { | |
753 | pidPtr[numPids] = pid; | |
754 | } | |
755 | ||
756 | /* | |
757 | * Close off our copies of file descriptors that were set up for | |
758 | * this child, then set up the input for the next child. | |
759 | */ | |
760 | ||
761 | if (inputId != -1) { | |
762 | close(inputId); | |
763 | } | |
764 | if (outputId != -1) { | |
765 | close(outputId); | |
766 | } | |
767 | inputId = pipeIds[0]; | |
768 | pipeIds[0] = pipeIds[1] = -1; | |
769 | } | |
770 | *pidArrayPtr = pidPtr; | |
771 | ||
772 | /* | |
773 | * All done. Cleanup open files lying around and then return. | |
774 | */ | |
775 | ||
776 | cleanup: | |
777 | if (inputId != -1) { | |
778 | close(inputId); | |
779 | } | |
780 | if (lastOutputId != -1) { | |
781 | close(lastOutputId); | |
782 | } | |
783 | if (errorId != -1) { | |
784 | close(errorId); | |
785 | } | |
786 | return numPids; | |
787 | ||
788 | /* | |
789 | * An error occurred. There could have been extra files open, such | |
790 | * as pipes between children. Clean them all up. Detach any child | |
791 | * processes that have been created. | |
792 | */ | |
793 | ||
794 | error: | |
795 | if ((inPipePtr != NULL) && (*inPipePtr != -1)) { | |
796 | close(*inPipePtr); | |
797 | *inPipePtr = -1; | |
798 | } | |
799 | if ((outPipePtr != NULL) && (*outPipePtr != -1)) { | |
800 | close(*outPipePtr); | |
801 | *outPipePtr = -1; | |
802 | } | |
803 | if ((errFilePtr != NULL) && (*errFilePtr != -1)) { | |
804 | close(*errFilePtr); | |
805 | *errFilePtr = -1; | |
806 | } | |
807 | if (pipeIds[0] != -1) { | |
808 | close(pipeIds[0]); | |
809 | } | |
810 | if (pipeIds[1] != -1) { | |
811 | close(pipeIds[1]); | |
812 | } | |
813 | if (pidPtr != NULL) { | |
814 | for (i = 0; i < numPids; i++) { | |
815 | if (pidPtr[i] != -1) { | |
816 | Tcl_DetachPids(1, &pidPtr[i]); | |
817 | } | |
818 | } | |
819 | ckfree((char *) pidPtr); | |
820 | } | |
821 | numPids = -1; | |
822 | goto cleanup; | |
823 | } | |
824 | \f | |
825 | /* | |
826 | *---------------------------------------------------------------------- | |
827 | * | |
828 | * Tcl_UnixError -- | |
829 | * | |
830 | * This procedure is typically called after UNIX kernel calls | |
831 | * return errors. It stores machine-readable information about | |
832 | * the error in $errorCode returns an information string for | |
833 | * the caller's use. | |
834 | * | |
835 | * Results: | |
836 | * The return value is a human-readable string describing the | |
837 | * error, as returned by strerror. | |
838 | * | |
839 | * Side effects: | |
840 | * The global variable $errorCode is reset. | |
841 | * | |
842 | *---------------------------------------------------------------------- | |
843 | */ | |
844 | ||
845 | char * | |
846 | Tcl_UnixError(interp) | |
847 | Tcl_Interp *interp; /* Interpreter whose $errorCode variable | |
848 | * is to be changed. */ | |
849 | { | |
850 | char *id, *msg; | |
851 | ||
852 | id = Tcl_ErrnoId(); | |
853 | msg = strerror(errno); | |
854 | Tcl_SetErrorCode(interp, "UNIX", id, msg, (char *) NULL); | |
855 | return msg; | |
856 | } | |
857 | \f | |
858 | /* | |
859 | *---------------------------------------------------------------------- | |
860 | * | |
861 | * TclMakeFileTable -- | |
862 | * | |
863 | * Create or enlarge the file table for the interpreter, so that | |
864 | * there is room for a given index. | |
865 | * | |
866 | * Results: | |
867 | * None. | |
868 | * | |
869 | * Side effects: | |
870 | * The file table for iPtr will be created if it doesn't exist | |
871 | * (and entries will be added for stdin, stdout, and stderr). | |
872 | * If it already exists, then it will be grown if necessary. | |
873 | * | |
874 | *---------------------------------------------------------------------- | |
875 | */ | |
876 | ||
877 | void | |
878 | TclMakeFileTable(iPtr, index) | |
879 | Interp *iPtr; /* Interpreter whose table of files is | |
880 | * to be manipulated. */ | |
881 | int index; /* Make sure table is large enough to | |
882 | * hold at least this index. */ | |
883 | { | |
884 | /* | |
885 | * If the table doesn't even exist, then create it and initialize | |
886 | * entries for standard files. | |
887 | */ | |
888 | ||
889 | if (iPtr->numFiles == 0) { | |
890 | OpenFile *filePtr; | |
891 | int i; | |
892 | ||
893 | if (index < 2) { | |
894 | iPtr->numFiles = 3; | |
895 | } else { | |
896 | iPtr->numFiles = index+1; | |
897 | } | |
898 | iPtr->filePtrArray = (OpenFile **) ckalloc((unsigned) | |
899 | ((iPtr->numFiles)*sizeof(OpenFile *))); | |
900 | for (i = iPtr->numFiles-1; i >= 0; i--) { | |
901 | iPtr->filePtrArray[i] = NULL; | |
902 | } | |
903 | ||
904 | filePtr = (OpenFile *) ckalloc(sizeof(OpenFile)); | |
905 | filePtr->f = stdin; | |
906 | filePtr->f2 = NULL; | |
907 | filePtr->readable = 1; | |
908 | filePtr->writable = 0; | |
909 | filePtr->numPids = 0; | |
910 | filePtr->pidPtr = NULL; | |
911 | filePtr->errorId = -1; | |
912 | iPtr->filePtrArray[0] = filePtr; | |
913 | ||
914 | filePtr = (OpenFile *) ckalloc(sizeof(OpenFile)); | |
915 | filePtr->f = stdout; | |
916 | filePtr->f2 = NULL; | |
917 | filePtr->readable = 0; | |
918 | filePtr->writable = 1; | |
919 | filePtr->numPids = 0; | |
920 | filePtr->pidPtr = NULL; | |
921 | filePtr->errorId = -1; | |
922 | iPtr->filePtrArray[1] = filePtr; | |
923 | ||
924 | filePtr = (OpenFile *) ckalloc(sizeof(OpenFile)); | |
925 | filePtr->f = stderr; | |
926 | filePtr->f2 = NULL; | |
927 | filePtr->readable = 0; | |
928 | filePtr->writable = 1; | |
929 | filePtr->numPids = 0; | |
930 | filePtr->pidPtr = NULL; | |
931 | filePtr->errorId = -1; | |
932 | iPtr->filePtrArray[2] = filePtr; | |
933 | } else if (index >= iPtr->numFiles) { | |
934 | int newSize; | |
935 | OpenFile **newPtrArray; | |
936 | int i; | |
937 | ||
938 | newSize = index+1; | |
939 | newPtrArray = (OpenFile **) ckalloc((unsigned) | |
940 | ((newSize)*sizeof(OpenFile *))); | |
941 | memcpy((VOID *) newPtrArray, (VOID *) iPtr->filePtrArray, | |
942 | iPtr->numFiles*sizeof(OpenFile *)); | |
943 | for (i = iPtr->numFiles; i < newSize; i++) { | |
944 | newPtrArray[i] = NULL; | |
945 | } | |
946 | ckfree((char *) iPtr->filePtrArray); | |
947 | iPtr->numFiles = newSize; | |
948 | iPtr->filePtrArray = newPtrArray; | |
949 | } | |
950 | } | |
951 | \f | |
952 | /* | |
953 | *---------------------------------------------------------------------- | |
954 | * | |
955 | * TclGetOpenFile -- | |
956 | * | |
957 | * Given a string identifier for an open file, find the corresponding | |
958 | * open file structure, if there is one. | |
959 | * | |
960 | * Results: | |
961 | * A standard Tcl return value. If the open file is successfully | |
962 | * located, *filePtrPtr is modified to point to its structure. | |
963 | * If TCL_ERROR is returned then interp->result contains an error | |
964 | * message. | |
965 | * | |
966 | * Side effects: | |
967 | * None. | |
968 | * | |
969 | *---------------------------------------------------------------------- | |
970 | */ | |
971 | ||
972 | int | |
973 | TclGetOpenFile(interp, string, filePtrPtr) | |
974 | Tcl_Interp *interp; /* Interpreter in which to find file. */ | |
975 | char *string; /* String that identifies file. */ | |
976 | OpenFile **filePtrPtr; /* Address of word in which to store pointer | |
977 | * to structure about open file. */ | |
978 | { | |
979 | int fd = 0; /* Initial value needed only to stop compiler | |
980 | * warnings. */ | |
981 | Interp *iPtr = (Interp *) interp; | |
982 | ||
983 | if ((string[0] == 'f') && (string[1] == 'i') && (string[2] == 'l') | |
984 | & (string[3] == 'e')) { | |
985 | char *end; | |
986 | ||
987 | fd = strtoul(string+4, &end, 10); | |
988 | if ((end == string+4) || (*end != 0)) { | |
989 | goto badId; | |
990 | } | |
991 | } else if ((string[0] == 's') && (string[1] == 't') | |
992 | && (string[2] == 'd')) { | |
993 | if (strcmp(string+3, "in") == 0) { | |
994 | fd = 0; | |
995 | } else if (strcmp(string+3, "out") == 0) { | |
996 | fd = 1; | |
997 | } else if (strcmp(string+3, "err") == 0) { | |
998 | fd = 2; | |
999 | } else { | |
1000 | goto badId; | |
1001 | } | |
1002 | } else { | |
1003 | badId: | |
1004 | Tcl_AppendResult(interp, "bad file identifier \"", string, | |
1005 | "\"", (char *) NULL); | |
1006 | return TCL_ERROR; | |
1007 | } | |
1008 | ||
1009 | if (fd >= iPtr->numFiles) { | |
1010 | if ((iPtr->numFiles == 0) && (fd <= 2)) { | |
1011 | TclMakeFileTable(iPtr, fd); | |
1012 | } else { | |
1013 | notOpen: | |
1014 | Tcl_AppendResult(interp, "file \"", string, "\" isn't open", | |
1015 | (char *) NULL); | |
1016 | return TCL_ERROR; | |
1017 | } | |
1018 | } | |
1019 | if (iPtr->filePtrArray[fd] == NULL) { | |
1020 | goto notOpen; | |
1021 | } | |
1022 | *filePtrPtr = iPtr->filePtrArray[fd]; | |
1023 | return TCL_OK; | |
1024 | } | |
1025 | ||
1026 | #ifdef MSDOS | |
1027 | int | |
1028 | filename2DOS(name) | |
1029 | char *name; | |
1030 | { | |
1031 | for ( ; *name; name++) if (*name == '/') *name = '\\'; | |
1032 | } | |
1033 | #endif |