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 by John Ousterhout.
10 * The Tcl_Fork and Tcl_WaitPids procedures are based on code
11 * contributed by Karl Lehenbauer, Mark Diekhans and Peter
14 * Copyright 1987-1991 Regents of the University of California
15 * Permission to use, copy, modify, and distribute this
16 * software and its documentation for any purpose and without
17 * fee is hereby granted, provided that the above copyright
18 * notice appear in all copies. The University of California
19 * makes no representations about the suitability of this
20 * software for any purpose. It is provided "as is" without
21 * express or implied warranty.
28 #include "jimautoconf.h"
30 #if defined(HAVE_VFORK) && defined(HAVE_WAITPID)
32 #include "jim-signal.h"
39 #if defined(__GNUC__) && !defined(__clang__)
40 #define IGNORE_RC(EXPR) ((EXPR) < 0 ? -1 : 0)
42 #define IGNORE_RC(EXPR) EXPR
45 /* These two could be moved into the Tcl core */
46 static void Jim_SetResultErrno(Jim_Interp
*interp
, const char *msg
)
48 Jim_SetResultFormatted(interp
, "%s: %s", msg
, strerror(errno
));
51 static void Jim_RemoveTrailingNewline(Jim_Obj
*objPtr
)
54 const char *s
= Jim_GetString(objPtr
, &len
);
56 if (len
> 0 && s
[len
- 1] == '\n') {
58 objPtr
->bytes
[objPtr
->length
] = '\0';
63 * Read from 'fd' and append the data to strObj
64 * Returns JIM_OK if OK, or JIM_ERR on error.
66 static int JimAppendStreamToString(Jim_Interp
*interp
, int fd
, Jim_Obj
*strObj
)
72 count
= read(fd
, buffer
, sizeof(buffer
));
75 Jim_RemoveTrailingNewline(strObj
);
81 Jim_AppendString(interp
, strObj
, buffer
, count
);
86 * If the last character of the result is a newline, then remove
87 * the newline character (the newline would just confuse things).
89 * Note: Ideally we could do this by just reducing the length of stringrep
90 * by 1, but there is no API for this :-(
92 static void JimTrimTrailingNewline(Jim_Interp
*interp
)
95 const char *p
= Jim_GetString(Jim_GetResult(interp
), &len
);
97 if (len
> 0 && p
[len
- 1] == '\n') {
98 Jim_SetResultString(interp
, p
, len
- 1);
103 * Builds the environment array from $::env
105 * If $::env is not set, simply returns environ.
107 * Otherwise allocates the environ array from the contents of $::env
109 * If the exec fails, memory can be freed via JimFreeEnv()
111 static char **JimBuildEnv(Jim_Interp
*interp
)
113 #ifdef jim_ext_tclcompat
119 Jim_Obj
*objPtr
= Jim_GetGlobalVariableStr(interp
, "env", JIM_NONE
);
122 return Jim_GetEnviron();
125 /* Calculate the required size */
126 len
= Jim_ListLength(interp
, objPtr
);
131 env
= Jim_Alloc(sizeof(*env
) * (len
/ 2 + 1));
134 for (i
= 0; i
< len
; i
+= 2) {
139 Jim_ListIndex(interp
, objPtr
, i
, &elemObj
, JIM_NONE
);
140 s1
= Jim_GetString(elemObj
, &l1
);
141 Jim_ListIndex(interp
, objPtr
, i
+ 1, &elemObj
, JIM_NONE
);
142 s2
= Jim_GetString(elemObj
, &l2
);
144 env
[n
] = Jim_Alloc(l1
+ l2
+ 2);
145 sprintf(env
[n
], "%s=%s", s1
, s2
);
152 return Jim_GetEnviron();
157 * Frees the environment allocated by JimBuildEnv()
159 * Must pass original_environ.
161 static void JimFreeEnv(Jim_Interp
*interp
, char **env
, char **original_environ
)
163 #ifdef jim_ext_tclcompat
164 if (env
!= original_environ
) {
166 for (i
= 0; env
[i
]; i
++) {
175 * Create error messages for unusual process exits. An
176 * extra newline gets appended to each error message, but
177 * it gets removed below (in the same fashion that an
178 * extra newline in the command's output is removed).
180 static int JimCheckWaitStatus(Jim_Interp
*interp
, int pid
, int waitStatus
)
182 Jim_Obj
*errorCode
= Jim_NewListObj(interp
, NULL
, 0);
185 if (WIFEXITED(waitStatus
)) {
186 if (WEXITSTATUS(waitStatus
) == 0) {
187 Jim_ListAppendElement(interp
, errorCode
, Jim_NewStringObj(interp
, "NONE", -1));
191 Jim_ListAppendElement(interp
, errorCode
, Jim_NewStringObj(interp
, "CHILDSTATUS", -1));
192 Jim_ListAppendElement(interp
, errorCode
, Jim_NewIntObj(interp
, pid
));
193 Jim_ListAppendElement(interp
, errorCode
, Jim_NewIntObj(interp
, WEXITSTATUS(waitStatus
)));
200 if (WIFSIGNALED(waitStatus
)) {
201 type
= "CHILDKILLED";
206 action
= "suspended";
209 Jim_ListAppendElement(interp
, errorCode
, Jim_NewStringObj(interp
, type
, -1));
211 #ifdef jim_ext_signal
212 Jim_SetResultFormatted(interp
, "child %s by signal %s", action
, Jim_SignalId(WTERMSIG(waitStatus
)));
213 Jim_ListAppendElement(interp
, errorCode
, Jim_NewStringObj(interp
, Jim_SignalId(WTERMSIG(waitStatus
)), -1));
214 Jim_ListAppendElement(interp
, errorCode
, Jim_NewIntObj(interp
, pid
));
215 Jim_ListAppendElement(interp
, errorCode
, Jim_NewStringObj(interp
, Jim_SignalName(WTERMSIG(waitStatus
)), -1));
217 Jim_SetResultFormatted(interp
, "child %s by signal %d", action
, WTERMSIG(waitStatus
));
218 Jim_ListAppendElement(interp
, errorCode
, Jim_NewIntObj(interp
, WTERMSIG(waitStatus
)));
219 Jim_ListAppendElement(interp
, errorCode
, Jim_NewIntObj(interp
, pid
));
220 Jim_ListAppendElement(interp
, errorCode
, Jim_NewIntObj(interp
, WTERMSIG(waitStatus
)));
223 Jim_SetGlobalVariableStr(interp
, "errorCode", errorCode
);
228 * Data structures of the following type are used by JimFork and
229 * JimWaitPids to keep track of child processes.
234 int pid
; /* Process id of child. */
235 int status
; /* Status returned when child exited or suspended. */
236 int flags
; /* Various flag bits; see below for definitions. */
239 struct WaitInfoTable
{
240 struct WaitInfo
*info
;
246 * Flag bits in WaitInfo structures:
248 * WI_DETACHED - Non-zero means no-one cares about the
249 * process anymore. Ignore it until it
250 * exits, then forget about it.
253 #define WI_DETACHED 2
255 #define WAIT_TABLE_GROW_BY 4
257 static void JimFreeWaitInfoTable(struct Jim_Interp
*interp
, void *privData
)
259 struct WaitInfoTable
*table
= privData
;
261 Jim_Free(table
->info
);
265 static struct WaitInfoTable
*JimAllocWaitInfoTable(void)
267 struct WaitInfoTable
*table
= Jim_Alloc(sizeof(*table
));
269 table
->size
= table
->used
= 0;
274 static int Jim_CreatePipeline(Jim_Interp
*interp
, int argc
, Jim_Obj
*const *argv
,
275 int **pidArrayPtr
, int *inPipePtr
, int *outPipePtr
, int *errFilePtr
);
276 static void JimDetachPids(Jim_Interp
*interp
, int numPids
, const int *pidPtr
);
277 static int Jim_CleanupChildren(Jim_Interp
*interp
, int numPids
, int *pidPtr
, int errorId
);
279 static int Jim_ExecCmd(Jim_Interp
*interp
, int argc
, Jim_Obj
*const *argv
)
281 int outputId
; /* File id for output pipe. -1
282 * means command overrode. */
283 int errorId
; /* File id for temporary file
284 * containing error output. */
289 * See if the command is to be run in background; if so, create
290 * the command, detach it, and return.
292 if (argc
> 1 && Jim_CompareStringImmediate(interp
, argv
[argc
- 1], "&")) {
297 numPids
= Jim_CreatePipeline(interp
, argc
- 1, argv
+ 1, &pidPtr
, NULL
, NULL
, NULL
);
301 /* The return value is a list of the pids */
302 listObj
= Jim_NewListObj(interp
, NULL
, 0);
303 for (i
= 0; i
< numPids
; i
++) {
304 Jim_ListAppendElement(interp
, listObj
, Jim_NewIntObj(interp
, pidPtr
[i
]));
306 Jim_SetResult(interp
, listObj
);
307 JimDetachPids(interp
, numPids
, pidPtr
);
313 * Create the command's pipeline.
316 Jim_CreatePipeline(interp
, argc
- 1, argv
+ 1, &pidPtr
, (int *)NULL
, &outputId
, &errorId
);
322 * Read the child's output (if any) and put it into the result.
324 Jim_SetResultString(interp
, "", 0);
327 if (outputId
!= -1) {
328 result
= JimAppendStreamToString(interp
, outputId
, Jim_GetResult(interp
));
330 Jim_SetResultErrno(interp
, "error reading from output pipe");
335 if (Jim_CleanupChildren(interp
, numPids
, pidPtr
, errorId
) != JIM_OK
) {
341 void Jim_ReapDetachedPids(struct WaitInfoTable
*table
)
343 struct WaitInfo
*waitPtr
;
350 for (waitPtr
= table
->info
, count
= table
->used
; count
> 0; waitPtr
++, count
--) {
351 if (waitPtr
->flags
& WI_DETACHED
) {
353 int pid
= waitpid(waitPtr
->pid
, &status
, WNOHANG
);
355 if (waitPtr
!= &table
->info
[table
->used
- 1]) {
356 *waitPtr
= table
->info
[table
->used
- 1];
365 * Does waitpid() on the given pid, and then removes the
366 * entry from the wait table.
368 * Returns the pid if OK and updates *statusPtr with the status,
369 * or -1 if the pid was not in the table.
371 static int JimWaitPid(struct WaitInfoTable
*table
, int pid
, int *statusPtr
)
375 /* Find it in the table */
376 for (i
= 0; i
< table
->used
; i
++) {
377 if (pid
== table
->info
[i
].pid
) {
379 waitpid(pid
, statusPtr
, 0);
381 /* Remove it from the table */
382 if (i
!= table
->used
- 1) {
383 table
->info
[i
] = table
->info
[table
->used
- 1];
395 *----------------------------------------------------------------------
399 * This procedure is called to indicate that one or more child
400 * processes have been placed in background and are no longer
401 * cared about. These children can be cleaned up with JimReapDetachedPids().
409 *----------------------------------------------------------------------
412 static void JimDetachPids(Jim_Interp
*interp
, int numPids
, const int *pidPtr
)
415 struct WaitInfoTable
*table
= Jim_CmdPrivData(interp
);
417 for (j
= 0; j
< numPids
; j
++) {
418 /* Find it in the table */
420 for (i
= 0; i
< table
->used
; i
++) {
421 if (pidPtr
[j
] == table
->info
[i
].pid
) {
422 table
->info
[i
].flags
|= WI_DETACHED
;
430 *----------------------------------------------------------------------
432 * Jim_CreatePipeline --
434 * Given an argc/argv array, instantiate a pipeline of processes
435 * as described by the argv.
438 * The return value is a count of the number of new processes
439 * created, or -1 if an error occurred while creating the pipeline.
440 * *pidArrayPtr is filled in with the address of a dynamically
441 * allocated array giving the ids of all of the processes. It
442 * is up to the caller to free this array when it isn't needed
443 * anymore. If inPipePtr is non-NULL, *inPipePtr is filled in
444 * with the file id for the input pipe for the pipeline (if any):
445 * the caller must eventually close this file. If outPipePtr
446 * isn't NULL, then *outPipePtr is filled in with the file id
447 * for the output pipe from the pipeline: the caller must close
448 * this file. If errFilePtr isn't NULL, then *errFilePtr is filled
449 * with a file id that may be used to read error output after the
450 * pipeline completes.
453 * Processes and pipes are created.
455 *----------------------------------------------------------------------
458 Jim_CreatePipeline(Jim_Interp
*interp
, int argc
, Jim_Obj
*const *argv
, int **pidArrayPtr
,
459 int *inPipePtr
, int *outPipePtr
, int *errFilePtr
)
461 int *pidPtr
= NULL
; /* Points to malloc-ed array holding all
462 * the pids of child processes. */
463 int numPids
= 0; /* Actual number of processes that exist
464 * at *pidPtr right now. */
465 int cmdCount
; /* Count of number of distinct commands
466 * found in argc/argv. */
467 const char *input
= NULL
; /* Describes input for pipeline, depending
468 * on "inputFile". NULL means take input
469 * from stdin/pipe. */
471 #define FILE_NAME 0 /* input/output: filename */
472 #define FILE_APPEND 1 /* output only: filename, append */
473 #define FILE_HANDLE 2 /* input/output: filehandle */
474 #define FILE_TEXT 3 /* input only: input is actual text */
476 int inputFile
= FILE_NAME
; /* 1 means input is name of input file.
477 * 2 means input is filehandle name.
478 * 0 means input holds actual
479 * text to be input to command. */
481 int outputFile
= FILE_NAME
; /* 0 means output is the name of output file.
482 * 1 means output is the name of output file, and append.
483 * 2 means output is filehandle name.
484 * All this is ignored if output is NULL
486 int errorFile
= FILE_NAME
; /* 0 means error is the name of error file.
487 * 1 means error is the name of error file, and append.
488 * 2 means error is filehandle name.
489 * All this is ignored if error is NULL
491 const char *output
= NULL
; /* Holds name of output file to pipe to,
492 * or NULL if output goes to stdout/pipe. */
493 const char *error
= NULL
; /* Holds name of stderr file to pipe to,
494 * or NULL if stderr goes to stderr/pipe. */
495 int inputId
= -1; /* Readable file id input to current command in
496 * pipeline (could be file or pipe). -1
497 * means use stdin. */
498 int outputId
= -1; /* Writable file id for output from current
499 * command in pipeline (could be file or pipe).
500 * -1 means use stdout. */
501 int errorId
= -1; /* Writable file id for all standard error
502 * output from all commands in pipeline. -1
503 * means use stderr. */
504 int lastOutputId
= -1; /* Write file id for output from last command
505 * in pipeline (could be file or pipe).
506 * -1 means use stdout. */
507 int pipeIds
[2]; /* File ids for pipe that's being created. */
508 int firstArg
, lastArg
; /* Indexes of first and last arguments in
509 * current command. */
514 struct WaitInfoTable
*table
= Jim_CmdPrivData(interp
);
516 /* Holds the args which will be used to exec */
517 char **arg_array
= Jim_Alloc(sizeof(*arg_array
) * (argc
+ 1));
520 Jim_ReapDetachedPids(table
);
522 if (inPipePtr
!= NULL
) {
525 if (outPipePtr
!= NULL
) {
528 if (errFilePtr
!= NULL
) {
531 pipeIds
[0] = pipeIds
[1] = -1;
534 * First, scan through all the arguments to figure out the structure
535 * of the pipeline. Count the number of distinct processes (it's the
536 * number of "|" arguments). If there are "<", "<<", or ">" arguments
537 * then make note of input and output redirection and remove these
538 * arguments and the arguments that follow them.
542 for (i
= 0; i
< argc
; i
++) {
543 const char *arg
= Jim_String(argv
[i
]);
546 inputFile
= FILE_NAME
;
549 inputFile
= FILE_TEXT
;
552 else if (*input
== '@') {
553 inputFile
= FILE_HANDLE
;
557 if (!*input
&& ++i
< argc
) {
558 input
= Jim_String(argv
[i
]);
561 else if (arg
[0] == '>') {
564 outputFile
= FILE_NAME
;
567 if (*output
== '>') {
568 outputFile
= FILE_APPEND
;
571 if (*output
== '&') {
572 /* Redirect stderr too */
576 if (*output
== '@') {
577 outputFile
= FILE_HANDLE
;
580 if (!*output
&& ++i
< argc
) {
581 output
= Jim_String(argv
[i
]);
584 errorFile
= outputFile
;
588 else if (arg
[0] == '2' && arg
[1] == '>') {
590 errorFile
= FILE_NAME
;
593 errorFile
= FILE_HANDLE
;
596 else if (*error
== '>') {
597 errorFile
= FILE_APPEND
;
600 if (!*error
&& ++i
< argc
) {
601 error
= Jim_String(argv
[i
]);
605 if (strcmp(arg
, "|") == 0 || strcmp(arg
, "|&") == 0) {
606 if (i
== lastBar
+ 1 || i
== argc
- 1) {
607 Jim_SetResultString(interp
, "illegal use of | or |& in command", -1);
613 /* Either |, |& or a "normal" arg, so store it in the arg array */
614 arg_array
[arg_count
++] = (char *)arg
;
619 Jim_SetResultFormatted(interp
, "can't specify \"%s\" as last word in command", arg
);
624 if (arg_count
== 0) {
625 Jim_SetResultString(interp
, "didn't specify command to execute", -1);
631 /* Must do this before vfork(), so do it now */
632 orig_environ
= Jim_GetEnviron();
633 Jim_SetEnviron(JimBuildEnv(interp
));
636 * Set up the redirected input source for the pipeline, if
640 if (inputFile
== FILE_TEXT
) {
642 * Immediate data in command. Create temporary file and
643 * put data into file.
646 #define TMP_STDIN_NAME "/tmp/tcl.in.XXXXXX"
647 char inName
[sizeof(TMP_STDIN_NAME
) + 1];
650 strcpy(inName
, TMP_STDIN_NAME
);
651 inputId
= mkstemp(inName
);
653 Jim_SetResultErrno(interp
, "couldn't create input file for command");
656 length
= strlen(input
);
657 if (write(inputId
, input
, length
) != length
) {
658 Jim_SetResultErrno(interp
, "couldn't write file input for command");
661 if (lseek(inputId
, 0L, SEEK_SET
) == -1 || unlink(inName
) == -1) {
662 Jim_SetResultErrno(interp
, "couldn't reset or remove input file for command");
666 else if (inputFile
== FILE_HANDLE
) {
667 /* Should be a file descriptor */
668 Jim_Obj
*fhObj
= Jim_NewStringObj(interp
, input
, -1);
669 FILE *fh
= Jim_AioFilehandle(interp
, fhObj
);
671 Jim_FreeNewObj(interp
, fhObj
);
675 inputId
= dup(fileno(fh
));
679 * File redirection. Just open the file.
681 inputId
= open(input
, O_RDONLY
, 0);
683 Jim_SetResultFormatted(interp
, "couldn't read file \"%s\": %s", input
,
689 else if (inPipePtr
!= NULL
) {
690 if (pipe(pipeIds
) != 0) {
691 Jim_SetResultErrno(interp
, "couldn't create input pipe for command");
694 inputId
= pipeIds
[0];
695 *inPipePtr
= pipeIds
[1];
696 pipeIds
[0] = pipeIds
[1] = -1;
700 * Set up the redirected output sink for the pipeline from one
701 * of two places, if requested.
703 if (output
!= NULL
) {
704 if (outputFile
== FILE_HANDLE
) {
705 Jim_Obj
*fhObj
= Jim_NewStringObj(interp
, output
, -1);
706 FILE *fh
= Jim_AioFilehandle(interp
, fhObj
);
708 Jim_FreeNewObj(interp
, fhObj
);
713 lastOutputId
= dup(fileno(fh
));
717 * Output is to go to a file.
719 int mode
= O_WRONLY
| O_CREAT
| O_TRUNC
;
721 if (outputFile
== FILE_APPEND
) {
722 mode
= O_WRONLY
| O_CREAT
| O_APPEND
;
725 lastOutputId
= open(output
, mode
, 0666);
726 if (lastOutputId
< 0) {
727 Jim_SetResultFormatted(interp
, "couldn't write file \"%s\": %s", output
,
733 else if (outPipePtr
!= NULL
) {
735 * Output is to go to a pipe.
737 if (pipe(pipeIds
) != 0) {
738 Jim_SetResultErrno(interp
, "couldn't create output pipe");
741 lastOutputId
= pipeIds
[1];
742 *outPipePtr
= pipeIds
[0];
743 pipeIds
[0] = pipeIds
[1] = -1;
746 /* If we are redirecting stderr with 2>filename or 2>@fileId, then we ignore errFilePtr */
748 if (errorFile
== FILE_HANDLE
) {
749 if (strcmp(error
, "1") == 0) {
751 if (lastOutputId
>= 0) {
752 errorId
= dup(lastOutputId
);
755 /* No redirection of stdout, so just use 2>@stdout */
760 Jim_Obj
*fhObj
= Jim_NewStringObj(interp
, error
, -1);
761 FILE *fh
= Jim_AioFilehandle(interp
, fhObj
);
763 Jim_FreeNewObj(interp
, fhObj
);
768 errorId
= dup(fileno(fh
));
773 * Output is to go to a file.
775 int mode
= O_WRONLY
| O_CREAT
| O_TRUNC
;
777 if (errorFile
== FILE_APPEND
) {
778 mode
= O_WRONLY
| O_CREAT
| O_APPEND
;
781 errorId
= open(error
, mode
, 0666);
783 Jim_SetResultFormatted(interp
, "couldn't write file \"%s\": %s", error
,
788 else if (errFilePtr
!= NULL
) {
790 * Set up the standard error output sink for the pipeline, if
791 * requested. Use a temporary file which is opened, then deleted.
792 * Could potentially just use pipe, but if it filled up it could
793 * cause the pipeline to deadlock: we'd be waiting for processes
794 * to complete before reading stderr, and processes couldn't complete
795 * because stderr was backed up.
798 #define TMP_STDERR_NAME "/tmp/tcl.err.XXXXXX"
799 char errName
[sizeof(TMP_STDERR_NAME
) + 1];
801 strcpy(errName
, TMP_STDERR_NAME
);
802 errorId
= mkstemp(errName
);
805 Jim_SetResultErrno(interp
, "couldn't create error file for command");
808 *errFilePtr
= open(errName
, O_RDONLY
, 0);
809 if (*errFilePtr
< 0) {
812 if (unlink(errName
) == -1) {
813 Jim_SetResultErrno(interp
, "couldn't remove error file for command");
819 * Scan through the argc array, forking off a process for each
820 * group of arguments between "|" arguments.
823 pidPtr
= (int *)Jim_Alloc(cmdCount
* sizeof(*pidPtr
));
824 for (i
= 0; i
< numPids
; i
++) {
827 for (firstArg
= 0; firstArg
< arg_count
; numPids
++, firstArg
= lastArg
+ 1) {
828 int pipe_dup_err
= 0;
829 int origErrorId
= errorId
;
833 for (lastArg
= firstArg
; lastArg
< arg_count
; lastArg
++) {
834 if (arg_array
[lastArg
][0] == '|') {
835 if (arg_array
[lastArg
][1] == '&') {
841 /* Replace | with NULL for execv() */
842 arg_array
[lastArg
] = NULL
;
843 if (lastArg
== arg_count
) {
844 outputId
= lastOutputId
;
847 if (pipe(pipeIds
) != 0) {
848 Jim_SetResultErrno(interp
, "couldn't create pipe");
851 outputId
= pipeIds
[1];
853 execName
= arg_array
[firstArg
];
855 /* Now fork the child */
858 * Disable SIGPIPE signals: if they were allowed, this process
859 * might go away unexpectedly if children misbehave. This code
860 * can potentially interfere with other application code that
861 * expects to handle SIGPIPEs; what's really needed is an
862 * arbiter for signals to allow them to be "shared".
864 if (table
->info
== NULL
) {
865 (void)signal(SIGPIPE
, SIG_IGN
);
868 /* Need to do this befor vfork() */
873 /* Need to prep an error message before vfork(), just in case */
874 snprintf(execerr
, sizeof(execerr
), "couldn't exec \"%s\"", execName
);
875 execerrlen
= strlen(execerr
);
878 * Make a new process and enter it into the table if the fork
883 Jim_SetResultErrno(interp
, "couldn't fork child process");
889 if (inputId
!= -1) dup2(inputId
, 0);
890 if (outputId
!= -1) dup2(outputId
, 1);
891 if (errorId
!= -1) dup2(errorId
, 2);
893 for (i
= 3; (i
<= outputId
) || (i
<= inputId
) || (i
<= errorId
); i
++) {
897 execvp(execName
, &arg_array
[firstArg
]);
899 /* we really can ignore the error here! */
900 IGNORE_RC(write(2, execerr
, execerrlen
));
907 * Enlarge the wait table if there isn't enough space for a new
910 if (table
->used
== table
->size
) {
911 table
->size
+= WAIT_TABLE_GROW_BY
;
912 table
->info
= Jim_Realloc(table
->info
, table
->size
* sizeof(*table
->info
));
915 table
->info
[table
->used
].pid
= pid
;
916 table
->info
[table
->used
].flags
= 0;
919 pidPtr
[numPids
] = pid
;
921 /* Restore in case of pipe_dup_err */
922 errorId
= origErrorId
;
925 * Close off our copies of file descriptors that were set up for
926 * this child, then set up the input for the next child.
932 if (outputId
!= -1) {
935 inputId
= pipeIds
[0];
936 pipeIds
[0] = pipeIds
[1] = -1;
938 *pidArrayPtr
= pidPtr
;
941 * All done. Cleanup open files lying around and then return.
948 if (lastOutputId
!= -1) {
956 JimFreeEnv(interp
, Jim_GetEnviron(), orig_environ
);
957 Jim_SetEnviron(orig_environ
);
962 * An error occurred. There could have been extra files open, such
963 * as pipes between children. Clean them all up. Detach any child
964 * processes that have been created.
968 if ((inPipePtr
!= NULL
) && (*inPipePtr
!= -1)) {
972 if ((outPipePtr
!= NULL
) && (*outPipePtr
!= -1)) {
976 if ((errFilePtr
!= NULL
) && (*errFilePtr
!= -1)) {
980 if (pipeIds
[0] != -1) {
983 if (pipeIds
[1] != -1) {
986 if (pidPtr
!= NULL
) {
987 for (i
= 0; i
< numPids
; i
++) {
988 if (pidPtr
[i
] != -1) {
989 JimDetachPids(interp
, 1, &pidPtr
[i
]);
999 *----------------------------------------------------------------------
1001 * CleanupChildren --
1003 * This is a utility procedure used to wait for child processes
1004 * to exit, record information about abnormal exits, and then
1005 * collect any stderr output generated by them.
1008 * The return value is a standard Tcl result. If anything at
1009 * weird happened with the child processes, JIM_ERROR is returned
1010 * and a message is left in interp->result.
1013 * If the last character of interp->result is a newline, then it
1014 * is removed. File errorId gets closed, and pidPtr is freed
1015 * back to the storage allocator.
1017 *----------------------------------------------------------------------
1020 static int Jim_CleanupChildren(Jim_Interp
*interp
, int numPids
, int *pidPtr
, int errorId
)
1022 struct WaitInfoTable
*table
= Jim_CmdPrivData(interp
);
1023 int result
= JIM_OK
;
1026 for (i
= 0; i
< numPids
; i
++) {
1028 if (JimWaitPid(table
, pidPtr
[i
], &waitStatus
) > 0) {
1029 if (JimCheckWaitStatus(interp
, pidPtr
[i
], waitStatus
) != JIM_OK
) {
1037 * Read the standard error file. If there's anything there,
1038 * then add the file's contents to the result
1042 if (JimAppendStreamToString(interp
, errorId
, Jim_GetResult(interp
)) != JIM_OK
) {
1043 Jim_SetResultErrno(interp
, "error reading from stderr output file");
1049 JimTrimTrailingNewline(interp
);
1054 int Jim_execInit(Jim_Interp
*interp
)
1056 if (Jim_PackageProvide(interp
, "exec", "1.0", JIM_ERRMSG
))
1059 Jim_CreateCommand(interp
, "exec", Jim_ExecCmd
, JimAllocWaitInfoTable(), JimFreeWaitInfoTable
);
1063 /* e.g. Windows. Poor mans implementation of exec with system()
1064 * The system() call *may* do command line redirection, etc.
1065 * The standard output is not available.
1066 * Can't redirect filehandles.
1068 static int Jim_ExecCmd(Jim_Interp
*interp
, int argc
, Jim_Obj
*const *argv
)
1070 Jim_Obj
*cmdlineObj
= Jim_NewEmptyStringObj(interp
);
1074 /* Create a quoted command line */
1075 for (i
= 1; i
< argc
; i
++) {
1077 const char *arg
= Jim_GetString(argv
[i
], &len
);
1080 Jim_AppendString(interp
, cmdlineObj
, " ", 1);
1082 if (strpbrk(arg
, "\\\" ") == NULL
) {
1083 /* No quoting required */
1084 Jim_AppendString(interp
, cmdlineObj
, arg
, len
);
1088 Jim_AppendString(interp
, cmdlineObj
, "\"", 1);
1089 for (j
= 0; j
< len
; j
++) {
1090 if (arg
[j
] == '\\' || arg
[j
] == '"') {
1091 Jim_AppendString(interp
, cmdlineObj
, "\\", 1);
1093 Jim_AppendString(interp
, cmdlineObj
, &arg
[j
], 1);
1095 Jim_AppendString(interp
, cmdlineObj
, "\"", 1);
1097 rc
= system(Jim_String(cmdlineObj
));
1099 Jim_FreeNewObj(interp
, cmdlineObj
);
1102 Jim_Obj
*errorCode
= Jim_NewListObj(interp
, NULL
, 0);
1103 Jim_ListAppendElement(interp
, errorCode
, Jim_NewStringObj(interp
, "CHILDSTATUS", -1));
1104 Jim_ListAppendElement(interp
, errorCode
, Jim_NewIntObj(interp
, 0));
1105 Jim_ListAppendElement(interp
, errorCode
, Jim_NewIntObj(interp
, rc
));
1106 Jim_SetGlobalVariableStr(interp
, "errorCode", errorCode
);
1113 int Jim_execInit(Jim_Interp
*interp
)
1115 if (Jim_PackageProvide(interp
, "exec", "1.0", JIM_ERRMSG
))
1118 Jim_CreateCommand(interp
, "exec", Jim_ExecCmd
, NULL
, NULL
);