aio recvfrom was not null terminating the result
[jimtcl.git] / jim-exec.c
blobdc77442422fc0ea73dae7e930ed477814215aa5b
2 /*
3 * (c) 2008 Steve Bennett <steveb@workware.net.au>
5 * Implements the exec command for Jim
7 * Based on code originally from Tcl 6.7:
9 * Copyright 1987-1991 Regents of the University of California
10 * Permission to use, copy, modify, and distribute this
11 * software and its documentation for any purpose and without
12 * fee is hereby granted, provided that the above copyright
13 * notice appear in all copies. The University of California
14 * makes no representations about the suitability of this
15 * software for any purpose. It is provided "as is" without
16 * express or implied warranty.
19 #include <string.h>
20 #include <unistd.h>
21 #include <signal.h>
22 #include <fcntl.h>
23 #include <errno.h>
24 #include <sys/wait.h>
26 #include "jim.h"
27 #include "jim-subcmd.h"
28 #include "jim-signal.h"
31 /* These two could be moved into the Tcl core */
32 static void Jim_SetResultErrno(Jim_Interp *interp, const char *msg)
34 Jim_SetResultFormatted(interp, "%s: %s", msg, strerror(errno));
37 static void Jim_RemoveTrailingNewline(Jim_Obj *objPtr)
39 int len;
40 const char *s = Jim_GetString(objPtr, &len);
42 if (len > 0 && s[len - 1] == '\n') {
43 objPtr->length--;
44 objPtr->bytes[objPtr->length] = '\0';
48 /**
49 * Read from 'fd' and append the data to strObj
50 * Returns JIM_OK if OK, or JIM_ERR on error.
52 static int JimAppendStreamToString(Jim_Interp *interp, int fd, Jim_Obj *strObj)
54 while (1) {
55 char buffer[256];
56 int count;
58 count = read(fd, buffer, sizeof(buffer));
60 if (count == 0) {
61 Jim_RemoveTrailingNewline(strObj);
62 return JIM_OK;
64 if (count < 0) {
65 return JIM_ERR;
67 Jim_AppendString(interp, strObj, buffer, count);
72 * If the last character of the result is a newline, then remove
73 * the newline character (the newline would just confuse things).
75 * Note: Ideally we could do this by just reducing the length of stringrep
76 * by 1, but there is no API for this :-(
78 static void JimTrimTrailingNewline(Jim_Interp *interp)
80 int len;
81 const char *p = Jim_GetString(Jim_GetResult(interp), &len);
83 if (len > 0 && p[len - 1] == '\n') {
84 Jim_SetResultString(interp, p, len - 1);
89 * Create error messages for unusual process exits. An
90 * extra newline gets appended to each error message, but
91 * it gets removed below (in the same fashion that an
92 * extra newline in the command's output is removed).
94 static int JimCheckWaitStatus(Jim_Interp *interp, int pid, int waitStatus)
96 /* REVISIT: Child exit status is lost here */
97 if (WIFEXITED(waitStatus) && WEXITSTATUS(waitStatus) == 0) {
98 return JIM_OK;
100 else if (WIFSIGNALED(waitStatus)) {
101 #ifdef jim_ext_signal
102 Jim_SetResultFormatted(interp, "child killed by signal %s",
103 Jim_SignalId(WTERMSIG(waitStatus)));
104 #else
105 Jim_SetResultFormatted(interp, "child killed by signal %d", WTERMSIG(waitStatus));
106 #endif
108 else if (WIFSTOPPED(waitStatus)) {
109 Jim_SetResultString(interp, "child suspended", -1);
111 return JIM_ERR;
114 #if defined(HAVE_FORK) && !defined(HAVE_NO_FORK)
115 static int Jim_CreatePipeline(Jim_Interp *interp, int argc, Jim_Obj *const *argv,
116 int **pidArrayPtr, int *inPipePtr, int *outPipePtr, int *errFilePtr);
117 static void JimDetachPids(Jim_Interp *interp, int numPids, int *pidPtr);
118 static int Jim_CleanupChildren(Jim_Interp *interp, int numPids, int *pidPtr, int errorId);
120 static int Jim_ExecCmd(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
122 int outputId; /* File id for output pipe. -1
123 * means command overrode. */
124 int errorId; /* File id for temporary file
125 * containing error output. */
126 int *pidPtr;
127 int numPids, result;
130 * See if the command is to be run in background; if so, create
131 * the command, detach it, and return.
133 if (argc > 1 && Jim_CompareStringImmediate(interp, argv[argc - 1], "&")) {
134 Jim_Obj *listObj;
135 int i;
137 argc--;
138 numPids = Jim_CreatePipeline(interp, argc - 1, argv + 1, &pidPtr, NULL, NULL, NULL);
139 if (numPids < 0) {
140 return JIM_ERR;
142 /* The return value is a list of the pids */
143 listObj = Jim_NewListObj(interp, NULL, 0);
144 for (i = 0; i < numPids; i++) {
145 Jim_ListAppendElement(interp, listObj, Jim_NewIntObj(interp, pidPtr[i]));
147 Jim_SetResult(interp, listObj);
148 JimDetachPids(interp, numPids, pidPtr);
149 Jim_Free(pidPtr);
150 return JIM_OK;
154 * Create the command's pipeline.
156 numPids =
157 Jim_CreatePipeline(interp, argc - 1, argv + 1, &pidPtr, (int *)NULL, &outputId, &errorId);
158 if (numPids < 0) {
159 return JIM_ERR;
163 * Read the child's output (if any) and put it into the result.
165 Jim_SetResultString(interp, "", 0);
167 result = JIM_OK;
168 if (outputId != -1) {
169 result = JimAppendStreamToString(interp, outputId, Jim_GetResult(interp));
170 if (result < 0) {
171 Jim_SetResultErrno(interp, "error reading from output pipe");
173 close(outputId);
176 if (Jim_CleanupChildren(interp, numPids, pidPtr, errorId) != JIM_OK) {
177 result = JIM_ERR;
179 return result;
183 * Data structures of the following type are used by JimFork and
184 * JimWaitPids to keep track of child processes.
187 typedef struct
189 int pid; /* Process id of child. */
190 int status; /* Status returned when child exited or suspended. */
191 int flags; /* Various flag bits; see below for definitions. */
192 } WaitInfo;
195 * Flag bits in WaitInfo structures:
197 * WI_READY - Non-zero means process has exited or
198 * suspended since it was forked or last
199 * returned by JimWaitPids.
200 * WI_DETACHED - Non-zero means no-one cares about the
201 * process anymore. Ignore it until it
202 * exits, then forget about it.
205 #define WI_READY 1
206 #define WI_DETACHED 2
208 /* REVISIT: Should be per-interpreter */
209 static WaitInfo *waitTable = NULL;
210 static int waitTableSize = 0; /* Total number of entries available in waitTable. */
211 static int waitTableUsed = 0; /* Number of entries in waitTable that
212 * are actually in use right now. Active
213 * entries are always at the beginning
214 * of the table. */
215 #define WAIT_TABLE_GROW_BY 4
218 *----------------------------------------------------------------------
220 * JimFork --
222 * Create a new process using the vfork system call, and keep
223 * track of it for "safe" waiting with JimWaitPids.
225 * Results:
226 * The return value is the value returned by the vfork system
227 * call (0 means child, > 0 means parent (value is child id),
228 * < 0 means error).
230 * Side effects:
231 * A new process is created, and an entry is added to an internal
232 * table of child processes if the process is created successfully.
234 *----------------------------------------------------------------------
236 static int JimFork(void)
238 WaitInfo *waitPtr;
239 pid_t pid;
242 * Disable SIGPIPE signals: if they were allowed, this process
243 * might go away unexpectedly if children misbehave. This code
244 * can potentially interfere with other application code that
245 * expects to handle SIGPIPEs; what's really needed is an
246 * arbiter for signals to allow them to be "shared".
248 if (waitTable == NULL) {
249 (void)signal(SIGPIPE, SIG_IGN);
253 * Enlarge the wait table if there isn't enough space for a new
254 * entry.
256 if (waitTableUsed == waitTableSize) {
257 waitTableSize += WAIT_TABLE_GROW_BY;
258 waitTable = (WaitInfo *) realloc(waitTable, waitTableSize * sizeof(WaitInfo));
262 * Make a new process and enter it into the table if the fork
263 * is successful.
266 waitPtr = &waitTable[waitTableUsed];
267 pid = fork();
268 if (pid > 0) {
269 waitPtr->pid = pid;
270 waitPtr->flags = 0;
271 waitTableUsed++;
273 return pid;
277 *----------------------------------------------------------------------
279 * JimWaitPids --
281 * This procedure is used to wait for one or more processes created
282 * by JimFork to exit or suspend. It records information about
283 * all processes that exit or suspend, even those not waited for,
284 * so that later waits for them will be able to get the status
285 * information.
287 * Results:
288 * -1 is returned if there is an error in the wait kernel call.
289 * Otherwise the pid of an exited/suspended process from *pidPtr
290 * is returned and *statusPtr is set to the status value returned
291 * by the wait kernel call.
293 * Side effects:
294 * Doesn't return until one of the pids at *pidPtr exits or suspends.
296 *----------------------------------------------------------------------
298 static int JimWaitPids(int numPids, int *pidPtr, int *statusPtr)
300 int i, count, pid;
301 WaitInfo *waitPtr;
302 int anyProcesses;
303 int status;
305 while (1) {
307 * Scan the table of child processes to see if one of the
308 * specified children has already exited or suspended. If so,
309 * remove it from the table and return its status.
312 anyProcesses = 0;
313 for (waitPtr = waitTable, count = waitTableUsed; count > 0; waitPtr++, count--) {
314 for (i = 0; i < numPids; i++) {
315 if (pidPtr[i] != waitPtr->pid) {
316 continue;
318 anyProcesses = 1;
319 if (waitPtr->flags & WI_READY) {
320 *statusPtr = *((int *)&waitPtr->status);
321 pid = waitPtr->pid;
322 if (WIFEXITED(waitPtr->status) || WIFSIGNALED(waitPtr->status)) {
323 if (waitPtr != &waitTable[waitTableUsed - 1]) {
324 *waitPtr = waitTable[waitTableUsed - 1];
326 waitTableUsed--;
328 else {
329 waitPtr->flags &= ~WI_READY;
331 return pid;
337 * Make sure that the caller at least specified one valid
338 * process to wait for.
340 if (!anyProcesses) {
341 errno = ECHILD;
342 return -1;
346 * Wait for a process to exit or suspend, then update its
347 * entry in the table and go back to the beginning of the
348 * loop to see if it's one of the desired processes.
351 pid = wait(&status);
352 if (pid < 0) {
353 return pid;
355 for (waitPtr = waitTable, count = waitTableUsed;; waitPtr++, count--) {
356 if (count == 0) {
357 break; /* Ignore unknown processes. */
359 if (pid != waitPtr->pid) {
360 continue;
364 * If the process has been detached, then ignore anything
365 * other than an exit, and drop the entry on exit.
367 if (waitPtr->flags & WI_DETACHED) {
368 if (WIFEXITED(status) || WIFSIGNALED(status)) {
369 *waitPtr = waitTable[waitTableUsed - 1];
370 waitTableUsed--;
373 else {
374 waitPtr->status = status;
375 waitPtr->flags |= WI_READY;
377 break;
383 *----------------------------------------------------------------------
385 * JimDetachPids --
387 * This procedure is called to indicate that one or more child
388 * processes have been placed in background and are no longer
389 * cared about. They should be ignored in future calls to
390 * JimWaitPids.
392 * Results:
393 * None.
395 * Side effects:
396 * None.
398 *----------------------------------------------------------------------
401 static void JimDetachPids(Jim_Interp *interp, int numPids, int *pidPtr)
403 WaitInfo *waitPtr;
404 int i, count, pid;
406 for (i = 0; i < numPids; i++) {
407 pid = pidPtr[i];
408 for (waitPtr = waitTable, count = waitTableUsed; count > 0; waitPtr++, count--) {
409 if (pid != waitPtr->pid) {
410 continue;
414 * If the process has already exited then destroy its
415 * table entry now.
418 if ((waitPtr->flags & WI_READY) && (WIFEXITED(waitPtr->status)
419 || WIFSIGNALED(waitPtr->status))) {
420 *waitPtr = waitTable[waitTableUsed - 1];
421 waitTableUsed--;
423 else {
424 waitPtr->flags |= WI_DETACHED;
426 goto nextPid;
428 Jim_Panic(interp, "Jim_Detach couldn't find process");
430 nextPid:
431 continue;
436 *----------------------------------------------------------------------
438 * Jim_CreatePipeline --
440 * Given an argc/argv array, instantiate a pipeline of processes
441 * as described by the argv.
443 * Results:
444 * The return value is a count of the number of new processes
445 * created, or -1 if an error occurred while creating the pipeline.
446 * *pidArrayPtr is filled in with the address of a dynamically
447 * allocated array giving the ids of all of the processes. It
448 * is up to the caller to free this array when it isn't needed
449 * anymore. If inPipePtr is non-NULL, *inPipePtr is filled in
450 * with the file id for the input pipe for the pipeline (if any):
451 * the caller must eventually close this file. If outPipePtr
452 * isn't NULL, then *outPipePtr is filled in with the file id
453 * for the output pipe from the pipeline: the caller must close
454 * this file. If errFilePtr isn't NULL, then *errFilePtr is filled
455 * with a file id that may be used to read error output after the
456 * pipeline completes.
458 * Side effects:
459 * Processes and pipes are created.
461 *----------------------------------------------------------------------
463 static int
464 Jim_CreatePipeline(Jim_Interp *interp, int argc, Jim_Obj *const *argv, int **pidArrayPtr,
465 int *inPipePtr, int *outPipePtr, int *errFilePtr)
467 int *pidPtr = NULL; /* Points to malloc-ed array holding all
468 * the pids of child processes. */
469 int numPids = 0; /* Actual number of processes that exist
470 * at *pidPtr right now. */
471 int cmdCount; /* Count of number of distinct commands
472 * found in argc/argv. */
473 const char *input = NULL; /* Describes input for pipeline, depending
474 * on "inputFile". NULL means take input
475 * from stdin/pipe. */
477 #define FILE_NAME 0 /* input/output: filename */
478 #define FILE_APPEND 1 /* output only: filename, append */
479 #define FILE_HANDLE 2 /* input/output: filehandle */
480 #define FILE_TEXT 3 /* input only: input is actual text */
482 int inputFile = FILE_NAME; /* 1 means input is name of input file.
483 * 2 means input is filehandle name.
484 * 0 means input holds actual
485 * text to be input to command. */
487 int outputFile = FILE_NAME; /* 0 means output is the name of output file.
488 * 1 means output is the name of output file, and append.
489 * 2 means output is filehandle name.
490 * All this is ignored if output is NULL
492 int errorFile = FILE_NAME; /* 0 means error is the name of error file.
493 * 1 means error is the name of error file, and append.
494 * 2 means error is filehandle name.
495 * All this is ignored if error is NULL
497 const char *output = NULL; /* Holds name of output file to pipe to,
498 * or NULL if output goes to stdout/pipe. */
499 const char *error = NULL; /* Holds name of stderr file to pipe to,
500 * or NULL if stderr goes to stderr/pipe. */
501 int inputId = -1; /* Readable file id input to current command in
502 * pipeline (could be file or pipe). -1
503 * means use stdin. */
504 int outputId = -1; /* Writable file id for output from current
505 * command in pipeline (could be file or pipe).
506 * -1 means use stdout. */
507 int errorId = -1; /* Writable file id for all standard error
508 * output from all commands in pipeline. -1
509 * means use stderr. */
510 int lastOutputId = -1; /* Write file id for output from last command
511 * in pipeline (could be file or pipe).
512 * -1 means use stdout. */
513 int pipeIds[2]; /* File ids for pipe that's being created. */
514 int firstArg, lastArg; /* Indexes of first and last arguments in
515 * current command. */
516 int lastBar;
517 char *execName;
518 int i, pid;
520 /* Holds the args which will be used to exec */
521 char **arg_array = Jim_Alloc(sizeof(*arg_array) * (argc + 1));
522 int arg_count = 0;
524 if (inPipePtr != NULL) {
525 *inPipePtr = -1;
527 if (outPipePtr != NULL) {
528 *outPipePtr = -1;
530 if (errFilePtr != NULL) {
531 *errFilePtr = -1;
533 pipeIds[0] = pipeIds[1] = -1;
536 * First, scan through all the arguments to figure out the structure
537 * of the pipeline. Count the number of distinct processes (it's the
538 * number of "|" arguments). If there are "<", "<<", or ">" arguments
539 * then make note of input and output redirection and remove these
540 * arguments and the arguments that follow them.
542 cmdCount = 1;
543 lastBar = -1;
544 for (i = 0; i < argc; i++) {
545 const char *arg = Jim_GetString(argv[i], NULL);
547 if (arg[0] == '<') {
548 inputFile = FILE_NAME;
549 input = arg + 1;
550 if (*input == '<') {
551 inputFile = FILE_TEXT;
552 input++;
554 else if (*input == '@') {
555 inputFile = FILE_HANDLE;
556 input++;
559 if (!*input && ++i < argc) {
560 input = Jim_GetString(argv[i], NULL);
563 else if (arg[0] == '>') {
564 int dup_error = 0;
566 outputFile = FILE_NAME;
568 output = arg + 1;
569 if (*output == '>') {
570 outputFile = FILE_APPEND;
571 output++;
573 if (*output == '&') {
574 /* Redirect stderr too */
575 output++;
576 dup_error = 1;
578 if (*output == '@') {
579 outputFile = FILE_HANDLE;
580 output++;
582 if (!*output && ++i < argc) {
583 output = Jim_GetString(argv[i], NULL);
585 if (dup_error) {
586 errorFile = outputFile;
587 error = output;
590 else if (arg[0] == '2' && arg[1] == '>') {
591 error = arg + 2;
592 errorFile = FILE_NAME;
594 if (*error == '@') {
595 errorFile = FILE_HANDLE;
596 error++;
598 else if (*error == '>') {
599 errorFile = FILE_APPEND;
600 error++;
602 if (!*error && ++i < argc) {
603 error = Jim_GetString(argv[i], NULL);
606 else {
607 if (strcmp(arg, "|") == 0 || strcmp(arg, "|&") == 0) {
608 if (i == lastBar + 1 || i == argc - 1) {
609 Jim_SetResultString(interp, "illegal use of | or |& in command", -1);
610 Jim_Free(arg_array);
611 return -1;
613 lastBar = i;
614 cmdCount++;
616 /* Either |, |& or a "normal" arg, so store it in the arg array */
617 arg_array[arg_count++] = (char *)arg;
618 continue;
621 if (i >= argc) {
622 Jim_SetResultFormatted(interp, "can't specify \"%s\" as last word in command", arg);
623 Jim_Free(arg_array);
624 return -1;
628 if (arg_count == 0) {
629 Jim_SetResultString(interp, "didn't specify command to execute", -1);
630 Jim_Free(arg_array);
631 return -1;
635 * Set up the redirected input source for the pipeline, if
636 * so requested.
638 if (input != NULL) {
639 if (inputFile == FILE_TEXT) {
641 * Immediate data in command. Create temporary file and
642 * put data into file.
645 #define TMP_STDIN_NAME "/tmp/tcl.in.XXXXXX"
646 char inName[sizeof(TMP_STDIN_NAME) + 1];
647 int length;
649 strcpy(inName, TMP_STDIN_NAME);
650 inputId = mkstemp(inName);
651 if (inputId < 0) {
652 Jim_SetResultErrno(interp, "couldn't create input file for command");
653 goto error;
655 length = strlen(input);
656 if (write(inputId, input, length) != length) {
657 Jim_SetResultErrno(interp, "couldn't write file input for command");
658 goto error;
660 if (lseek(inputId, 0L, SEEK_SET) == -1 || unlink(inName) == -1) {
661 Jim_SetResultErrno(interp, "couldn't reset or remove input file for command");
662 goto error;
665 else if (inputFile == FILE_HANDLE) {
666 /* Should be a file descriptor */
667 Jim_Obj *fhObj = Jim_NewStringObj(interp, input, -1);
668 FILE *fh = Jim_AioFilehandle(interp, fhObj);
670 Jim_FreeNewObj(interp, fhObj);
671 if (fh == NULL) {
672 goto error;
674 inputId = dup(fileno(fh));
676 else {
678 * File redirection. Just open the file.
680 inputId = open(input, O_RDONLY, 0);
681 if (inputId < 0) {
682 Jim_SetResultFormatted(interp, "couldn't read file \"%s\": %s", input,
683 strerror(errno));
684 goto error;
688 else if (inPipePtr != NULL) {
689 if (pipe(pipeIds) != 0) {
690 Jim_SetResultErrno(interp, "couldn't create input pipe for command");
691 goto error;
693 inputId = pipeIds[0];
694 *inPipePtr = pipeIds[1];
695 pipeIds[0] = pipeIds[1] = -1;
699 * Set up the redirected output sink for the pipeline from one
700 * of two places, if requested.
702 if (output != NULL) {
703 if (outputFile == FILE_HANDLE) {
704 Jim_Obj *fhObj = Jim_NewStringObj(interp, output, -1);
705 FILE *fh = Jim_AioFilehandle(interp, fhObj);
707 Jim_FreeNewObj(interp, fhObj);
708 if (fh == NULL) {
709 goto error;
711 fflush(fh);
712 lastOutputId = dup(fileno(fh));
714 else {
716 * Output is to go to a file.
718 int mode = O_WRONLY | O_CREAT | O_TRUNC;
720 if (outputFile == FILE_APPEND) {
721 mode = O_WRONLY | O_CREAT | O_APPEND;
724 lastOutputId = open(output, mode, 0666);
725 if (lastOutputId < 0) {
726 Jim_SetResultFormatted(interp, "couldn't write file \"%s\": %s", output,
727 strerror(errno));
728 goto error;
732 else if (outPipePtr != NULL) {
734 * Output is to go to a pipe.
736 if (pipe(pipeIds) != 0) {
737 Jim_SetResultErrno(interp, "couldn't create output pipe");
738 goto error;
740 lastOutputId = pipeIds[1];
741 *outPipePtr = pipeIds[0];
742 pipeIds[0] = pipeIds[1] = -1;
745 /* If we are redirecting stderr with 2>filename or 2>@fileId, then we ignore errFilePtr */
746 if (error != NULL) {
747 if (errorFile == FILE_HANDLE) {
748 if (strcmp(error, "1") == 0) {
749 /* Special 2>@1 */
750 if (lastOutputId >= 0) {
751 errorId = dup(lastOutputId);
753 else {
754 /* No redirection stdout, so just use 2>@stdout */
755 error = "stdout";
758 if (errorId < 0) {
759 Jim_Obj *fhObj = Jim_NewStringObj(interp, error, -1);
760 FILE *fh = Jim_AioFilehandle(interp, fhObj);
762 Jim_FreeNewObj(interp, fhObj);
763 if (fh == NULL) {
764 goto error;
766 fflush(fh);
767 errorId = dup(fileno(fh));
770 else {
772 * Output is to go to a file.
774 int mode = O_WRONLY | O_CREAT | O_TRUNC;
776 if (errorFile == FILE_APPEND) {
777 mode = O_WRONLY | O_CREAT | O_APPEND;
780 errorId = open(error, mode, 0666);
781 if (errorId < 0) {
782 Jim_SetResultFormatted(interp, "couldn't write file \"%s\": %s", error,
783 strerror(errno));
787 else if (errFilePtr != NULL) {
789 * Set up the standard error output sink for the pipeline, if
790 * requested. Use a temporary file which is opened, then deleted.
791 * Could potentially just use pipe, but if it filled up it could
792 * cause the pipeline to deadlock: we'd be waiting for processes
793 * to complete before reading stderr, and processes couldn't complete
794 * because stderr was backed up.
797 #define TMP_STDERR_NAME "/tmp/tcl.err.XXXXXX"
798 char errName[sizeof(TMP_STDERR_NAME) + 1];
800 strcpy(errName, TMP_STDERR_NAME);
801 errorId = mkstemp(errName);
802 if (errorId < 0) {
803 errFileError:
804 Jim_SetResultErrno(interp, "couldn't create error file for command");
805 goto error;
807 *errFilePtr = open(errName, O_RDONLY, 0);
808 if (*errFilePtr < 0) {
809 goto errFileError;
811 if (unlink(errName) == -1) {
812 Jim_SetResultErrno(interp, "couldn't remove error file for command");
813 goto error;
818 * Scan through the argc array, forking off a process for each
819 * group of arguments between "|" arguments.
822 pidPtr = (int *)Jim_Alloc(cmdCount * sizeof(*pidPtr));
823 for (i = 0; i < numPids; i++) {
824 pidPtr[i] = -1;
826 for (firstArg = 0; firstArg < arg_count; numPids++, firstArg = lastArg + 1) {
827 int pipe_dup_err = 0;
829 for (lastArg = firstArg; lastArg < arg_count; lastArg++) {
830 if (arg_array[lastArg][0] == '|') {
831 if (arg_array[lastArg][1] == '&') {
832 pipe_dup_err = 1;
834 break;
837 /* Replace | with NULL for execv() */
838 arg_array[lastArg] = NULL;
839 if (lastArg == arg_count) {
840 outputId = lastOutputId;
842 else {
843 if (pipe(pipeIds) != 0) {
844 Jim_SetResultErrno(interp, "couldn't create pipe");
845 goto error;
847 outputId = pipeIds[1];
849 execName = arg_array[firstArg];
850 pid = JimFork();
851 if (pid == -1) {
852 Jim_SetResultErrno(interp, "couldn't fork child process");
853 goto error;
855 if (pid == 0) {
856 char errSpace[200];
857 int rc;
859 if (pipe_dup_err) {
860 errorId = outputId;
863 if ((inputId != -1 && dup2(inputId, 0) == -1)
864 || (outputId != -1 && dup2(outputId, 1) == -1)
865 || (errorId != -1 && (dup2(errorId, 2) == -1))) {
867 static const char err[] = "forked process couldn't set up input/output\n";
869 rc = write(errorId < 0 ? 2 : errorId, err, strlen(err));
870 _exit(1);
872 for (i = 3; (i <= outputId) || (i <= inputId) || (i <= errorId); i++) {
873 close(i);
875 execvp(execName, &arg_array[firstArg]);
876 sprintf(errSpace, "couldn't find \"%.150s\" to execute\n", arg_array[firstArg]);
877 rc = write(2, errSpace, strlen(errSpace));
878 _exit(1);
880 else {
881 pidPtr[numPids] = pid;
885 * Close off our copies of file descriptors that were set up for
886 * this child, then set up the input for the next child.
889 if (inputId != -1) {
890 close(inputId);
892 if (outputId != -1) {
893 close(outputId);
895 inputId = pipeIds[0];
896 pipeIds[0] = pipeIds[1] = -1;
898 *pidArrayPtr = pidPtr;
901 * All done. Cleanup open files lying around and then return.
904 cleanup:
905 if (inputId != -1) {
906 close(inputId);
908 if (lastOutputId != -1) {
909 close(lastOutputId);
911 if (errorId != -1) {
912 close(errorId);
914 Jim_Free(arg_array);
916 return numPids;
919 * An error occurred. There could have been extra files open, such
920 * as pipes between children. Clean them all up. Detach any child
921 * processes that have been created.
924 error:
925 if ((inPipePtr != NULL) && (*inPipePtr != -1)) {
926 close(*inPipePtr);
927 *inPipePtr = -1;
929 if ((outPipePtr != NULL) && (*outPipePtr != -1)) {
930 close(*outPipePtr);
931 *outPipePtr = -1;
933 if ((errFilePtr != NULL) && (*errFilePtr != -1)) {
934 close(*errFilePtr);
935 *errFilePtr = -1;
937 if (pipeIds[0] != -1) {
938 close(pipeIds[0]);
940 if (pipeIds[1] != -1) {
941 close(pipeIds[1]);
943 if (pidPtr != NULL) {
944 for (i = 0; i < numPids; i++) {
945 if (pidPtr[i] != -1) {
946 JimDetachPids(interp, 1, &pidPtr[i]);
949 Jim_Free(pidPtr);
951 numPids = -1;
952 goto cleanup;
956 *----------------------------------------------------------------------
958 * CleanupChildren --
960 * This is a utility procedure used to wait for child processes
961 * to exit, record information about abnormal exits, and then
962 * collect any stderr output generated by them.
964 * Results:
965 * The return value is a standard Tcl result. If anything at
966 * weird happened with the child processes, JIM_ERROR is returned
967 * and a message is left in interp->result.
969 * Side effects:
970 * If the last character of interp->result is a newline, then it
971 * is removed. File errorId gets closed, and pidPtr is freed
972 * back to the storage allocator.
974 *----------------------------------------------------------------------
977 static int Jim_CleanupChildren(Jim_Interp *interp, int numPids, int *pidPtr, int errorId)
979 int result = JIM_OK;
980 int i;
982 for (i = 0; i < numPids; i++) {
983 int waitStatus = 0;
984 int pid = JimWaitPids(1, &pidPtr[i], &waitStatus);
986 if (pid >= 0 && JimCheckWaitStatus(interp, pid, waitStatus) != JIM_OK) {
987 result = JIM_ERR;
990 Jim_Free(pidPtr);
993 * Read the standard error file. If there's anything there,
994 * then add the file's contents to the result
995 * string.
997 if (errorId >= 0) {
998 if (JimAppendStreamToString(interp, errorId, Jim_GetResult(interp)) != JIM_OK) {
999 Jim_SetResultErrno(interp, "error reading from stderr output file");
1000 result = JIM_ERR;
1002 close(errorId);
1005 JimTrimTrailingNewline(interp);
1007 return result;
1009 #else
1010 static int Jim_ExecCmd(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
1012 int pid;
1013 int tmpfd;
1014 int status;
1015 int result;
1016 char **nargv;
1017 int i;
1019 #define TMP_NAME "/tmp/tcl.exec.XXXXXX"
1020 char tmpname[sizeof(TMP_NAME) + 1];
1023 /* Create a temporary file for the output from our exec command */
1024 strcpy(tmpname, TMP_NAME);
1025 tmpfd = mkstemp(tmpname);
1026 if (tmpfd < 0) {
1027 Jim_SetResultErrno(interp, "couldn't create temp file file for exec");
1028 return JIM_ERR;
1031 nargv = Jim_Alloc(sizeof(*nargv) * argc);
1032 for (i = 1; i < argc; i++) {
1033 nargv[i - 1] = (char *)Jim_GetString(argv[i], NULL);
1035 nargv[i - 1] = NULL;
1037 /*printf("Writing output to %s, fd=%d\n", tmpname, tmpfd); */
1038 unlink(tmpname);
1040 /* Use vfork and send output to this temporary file */
1041 pid = vfork();
1042 //pid = 0;
1043 if (pid == 0) {
1044 close(0);
1045 open("/dev/null", O_RDONLY);
1046 close(1);
1047 if (dup(tmpfd) != -1) {
1048 close(2);
1049 if (dup(tmpfd) != -1) {
1050 close(tmpfd);
1051 execvp(nargv[0], nargv);
1054 _exit(127);
1057 /* Wait for the child to exit */
1058 waitpid(pid, &status, 0);
1060 Jim_Free(nargv);
1062 result = JimCheckWaitStatus(interp, pid, status);
1065 * Read the child's output (if any) and put it into the result.
1067 lseek(tmpfd, 0L, SEEK_SET);
1069 Jim_SetResultString(interp, "", 0);
1071 if (JimAppendStreamToString(interp, tmpfd, Jim_GetResult(interp)) != JIM_OK) {
1072 Jim_SetResultErrno(interp, "error reading from stderr output file");
1073 result = JIM_ERR;
1075 close(tmpfd);
1077 JimTrimTrailingNewline(interp);
1079 return result;
1081 #endif
1083 int Jim_execInit(Jim_Interp *interp)
1085 Jim_CreateCommand(interp, "exec", Jim_ExecCmd, NULL, NULL);
1086 return JIM_OK;