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.
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
)
40 const char *s
= Jim_GetString(objPtr
, &len
);
42 if (len
> 0 && s
[len
- 1] == '\n') {
44 objPtr
->bytes
[objPtr
->length
] = '\0';
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
)
58 count
= read(fd
, buffer
, sizeof(buffer
));
61 Jim_RemoveTrailingNewline(strObj
);
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
)
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) {
100 else if (WIFSIGNALED(waitStatus
)) {
101 #ifdef jim_ext_signal
102 Jim_SetResultFormatted(interp
, "child killed by signal %s",
103 Jim_SignalId(WTERMSIG(waitStatus
)));
105 Jim_SetResultFormatted(interp
, "child killed by signal %d", WTERMSIG(waitStatus
));
108 else if (WIFSTOPPED(waitStatus
)) {
109 Jim_SetResultString(interp
, "child suspended", -1);
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. */
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], "&")) {
138 numPids
= Jim_CreatePipeline(interp
, argc
- 1, argv
+ 1, &pidPtr
, NULL
, NULL
, NULL
);
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
);
154 * Create the command's pipeline.
157 Jim_CreatePipeline(interp
, argc
- 1, argv
+ 1, &pidPtr
, (int *)NULL
, &outputId
, &errorId
);
163 * Read the child's output (if any) and put it into the result.
165 Jim_SetResultString(interp
, "", 0);
168 if (outputId
!= -1) {
169 result
= JimAppendStreamToString(interp
, outputId
, Jim_GetResult(interp
));
171 Jim_SetResultErrno(interp
, "error reading from output pipe");
176 if (Jim_CleanupChildren(interp
, numPids
, pidPtr
, errorId
) != JIM_OK
) {
183 * Data structures of the following type are used by JimFork and
184 * JimWaitPids to keep track of child processes.
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. */
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.
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
215 #define WAIT_TABLE_GROW_BY 4
218 *----------------------------------------------------------------------
222 * Create a new process using the vfork system call, and keep
223 * track of it for "safe" waiting with JimWaitPids.
226 * The return value is the value returned by the vfork system
227 * call (0 means child, > 0 means parent (value is child id),
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)
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
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
266 waitPtr
= &waitTable
[waitTableUsed
];
277 *----------------------------------------------------------------------
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
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.
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
)
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.
313 for (waitPtr
= waitTable
, count
= waitTableUsed
; count
> 0; waitPtr
++, count
--) {
314 for (i
= 0; i
< numPids
; i
++) {
315 if (pidPtr
[i
] != waitPtr
->pid
) {
319 if (waitPtr
->flags
& WI_READY
) {
320 *statusPtr
= *((int *)&waitPtr
->status
);
322 if (WIFEXITED(waitPtr
->status
) || WIFSIGNALED(waitPtr
->status
)) {
323 if (waitPtr
!= &waitTable
[waitTableUsed
- 1]) {
324 *waitPtr
= waitTable
[waitTableUsed
- 1];
329 waitPtr
->flags
&= ~WI_READY
;
337 * Make sure that the caller at least specified one valid
338 * process to wait for.
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.
355 for (waitPtr
= waitTable
, count
= waitTableUsed
;; waitPtr
++, count
--) {
357 break; /* Ignore unknown processes. */
359 if (pid
!= waitPtr
->pid
) {
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];
374 waitPtr
->status
= status
;
375 waitPtr
->flags
|= WI_READY
;
383 *----------------------------------------------------------------------
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
398 *----------------------------------------------------------------------
401 static void JimDetachPids(Jim_Interp
*interp
, int numPids
, int *pidPtr
)
406 for (i
= 0; i
< numPids
; i
++) {
408 for (waitPtr
= waitTable
, count
= waitTableUsed
; count
> 0; waitPtr
++, count
--) {
409 if (pid
!= waitPtr
->pid
) {
414 * If the process has already exited then destroy its
418 if ((waitPtr
->flags
& WI_READY
) && (WIFEXITED(waitPtr
->status
)
419 || WIFSIGNALED(waitPtr
->status
))) {
420 *waitPtr
= waitTable
[waitTableUsed
- 1];
424 waitPtr
->flags
|= WI_DETACHED
;
428 Jim_Panic(interp
, "Jim_Detach couldn't find process");
436 *----------------------------------------------------------------------
438 * Jim_CreatePipeline --
440 * Given an argc/argv array, instantiate a pipeline of processes
441 * as described by the argv.
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.
459 * Processes and pipes are created.
461 *----------------------------------------------------------------------
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. */
520 /* Holds the args which will be used to exec */
521 char **arg_array
= Jim_Alloc(sizeof(*arg_array
) * (argc
+ 1));
524 if (inPipePtr
!= NULL
) {
527 if (outPipePtr
!= NULL
) {
530 if (errFilePtr
!= NULL
) {
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.
544 for (i
= 0; i
< argc
; i
++) {
545 const char *arg
= Jim_GetString(argv
[i
], NULL
);
548 inputFile
= FILE_NAME
;
551 inputFile
= FILE_TEXT
;
554 else if (*input
== '@') {
555 inputFile
= FILE_HANDLE
;
559 if (!*input
&& ++i
< argc
) {
560 input
= Jim_GetString(argv
[i
], NULL
);
563 else if (arg
[0] == '>') {
566 outputFile
= FILE_NAME
;
569 if (*output
== '>') {
570 outputFile
= FILE_APPEND
;
573 if (*output
== '&') {
574 /* Redirect stderr too */
578 if (*output
== '@') {
579 outputFile
= FILE_HANDLE
;
582 if (!*output
&& ++i
< argc
) {
583 output
= Jim_GetString(argv
[i
], NULL
);
586 errorFile
= outputFile
;
590 else if (arg
[0] == '2' && arg
[1] == '>') {
592 errorFile
= FILE_NAME
;
595 errorFile
= FILE_HANDLE
;
598 else if (*error
== '>') {
599 errorFile
= FILE_APPEND
;
602 if (!*error
&& ++i
< argc
) {
603 error
= Jim_GetString(argv
[i
], NULL
);
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);
616 /* Either |, |& or a "normal" arg, so store it in the arg array */
617 arg_array
[arg_count
++] = (char *)arg
;
622 Jim_SetResultFormatted(interp
, "can't specify \"%s\" as last word in command", arg
);
628 if (arg_count
== 0) {
629 Jim_SetResultString(interp
, "didn't specify command to execute", -1);
635 * Set up the redirected input source for the pipeline, if
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];
649 strcpy(inName
, TMP_STDIN_NAME
);
650 inputId
= mkstemp(inName
);
652 Jim_SetResultErrno(interp
, "couldn't create input file for command");
655 length
= strlen(input
);
656 if (write(inputId
, input
, length
) != length
) {
657 Jim_SetResultErrno(interp
, "couldn't write file input for command");
660 if (lseek(inputId
, 0L, SEEK_SET
) == -1 || unlink(inName
) == -1) {
661 Jim_SetResultErrno(interp
, "couldn't reset or remove input file for command");
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
);
674 inputId
= dup(fileno(fh
));
678 * File redirection. Just open the file.
680 inputId
= open(input
, O_RDONLY
, 0);
682 Jim_SetResultFormatted(interp
, "couldn't read file \"%s\": %s", input
,
688 else if (inPipePtr
!= NULL
) {
689 if (pipe(pipeIds
) != 0) {
690 Jim_SetResultErrno(interp
, "couldn't create input pipe for command");
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
);
712 lastOutputId
= dup(fileno(fh
));
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
,
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");
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 */
747 if (errorFile
== FILE_HANDLE
) {
748 if (strcmp(error
, "1") == 0) {
750 if (lastOutputId
>= 0) {
751 errorId
= dup(lastOutputId
);
754 /* No redirection stdout, so just use 2>@stdout */
759 Jim_Obj
*fhObj
= Jim_NewStringObj(interp
, error
, -1);
760 FILE *fh
= Jim_AioFilehandle(interp
, fhObj
);
762 Jim_FreeNewObj(interp
, fhObj
);
767 errorId
= dup(fileno(fh
));
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);
782 Jim_SetResultFormatted(interp
, "couldn't write file \"%s\": %s", error
,
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
);
804 Jim_SetResultErrno(interp
, "couldn't create error file for command");
807 *errFilePtr
= open(errName
, O_RDONLY
, 0);
808 if (*errFilePtr
< 0) {
811 if (unlink(errName
) == -1) {
812 Jim_SetResultErrno(interp
, "couldn't remove error file for command");
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
++) {
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] == '&') {
837 /* Replace | with NULL for execv() */
838 arg_array
[lastArg
] = NULL
;
839 if (lastArg
== arg_count
) {
840 outputId
= lastOutputId
;
843 if (pipe(pipeIds
) != 0) {
844 Jim_SetResultErrno(interp
, "couldn't create pipe");
847 outputId
= pipeIds
[1];
849 execName
= arg_array
[firstArg
];
852 Jim_SetResultErrno(interp
, "couldn't fork child process");
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
));
872 for (i
= 3; (i
<= outputId
) || (i
<= inputId
) || (i
<= errorId
); 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
));
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.
892 if (outputId
!= -1) {
895 inputId
= pipeIds
[0];
896 pipeIds
[0] = pipeIds
[1] = -1;
898 *pidArrayPtr
= pidPtr
;
901 * All done. Cleanup open files lying around and then return.
908 if (lastOutputId
!= -1) {
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.
925 if ((inPipePtr
!= NULL
) && (*inPipePtr
!= -1)) {
929 if ((outPipePtr
!= NULL
) && (*outPipePtr
!= -1)) {
933 if ((errFilePtr
!= NULL
) && (*errFilePtr
!= -1)) {
937 if (pipeIds
[0] != -1) {
940 if (pipeIds
[1] != -1) {
943 if (pidPtr
!= NULL
) {
944 for (i
= 0; i
< numPids
; i
++) {
945 if (pidPtr
[i
] != -1) {
946 JimDetachPids(interp
, 1, &pidPtr
[i
]);
956 *----------------------------------------------------------------------
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.
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.
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
)
982 for (i
= 0; i
< numPids
; i
++) {
984 int pid
= JimWaitPids(1, &pidPtr
[i
], &waitStatus
);
986 if (pid
>= 0 && JimCheckWaitStatus(interp
, pid
, waitStatus
) != JIM_OK
) {
993 * Read the standard error file. If there's anything there,
994 * then add the file's contents to the result
998 if (JimAppendStreamToString(interp
, errorId
, Jim_GetResult(interp
)) != JIM_OK
) {
999 Jim_SetResultErrno(interp
, "error reading from stderr output file");
1005 JimTrimTrailingNewline(interp
);
1010 static int Jim_ExecCmd(Jim_Interp
*interp
, int argc
, Jim_Obj
*const *argv
)
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
);
1027 Jim_SetResultErrno(interp
, "couldn't create temp file file for exec");
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); */
1040 /* Use vfork and send output to this temporary file */
1045 open("/dev/null", O_RDONLY
);
1047 if (dup(tmpfd
) != -1) {
1049 if (dup(tmpfd
) != -1) {
1051 execvp(nargv
[0], nargv
);
1057 /* Wait for the child to exit */
1058 waitpid(pid
, &status
, 0);
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");
1077 JimTrimTrailingNewline(interp
);
1083 int Jim_execInit(Jim_Interp
*interp
)
1085 Jim_CreateCommand(interp
, "exec", Jim_ExecCmd
, NULL
, NULL
);