2 * (c) 2008 Steve Bennett <steveb@workware.net.au>
4 * Implements the exec command for Jim
6 * Based on code originally from Tcl 6.7 by John Ousterhout.
9 * The Tcl_Fork and Tcl_WaitPids procedures are based on code
10 * contributed by Karl Lehenbauer, Mark Diekhans and Peter
13 * Copyright 1987-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 the above copyright
17 * notice appear 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.
29 #include "jimautoconf.h"
32 #if (!defined(HAVE_VFORK) || !defined(HAVE_WAITPID)) && !defined(__MINGW32__)
33 /* Poor man's implementation of exec with system()
34 * The system() call *may* do command line redirection, etc.
35 * The standard output is not available.
36 * Can't redirect filehandles.
38 static int Jim_ExecCmd(Jim_Interp
*interp
, int argc
, Jim_Obj
*const *argv
)
40 Jim_Obj
*cmdlineObj
= Jim_NewEmptyStringObj(interp
);
44 /* Create a quoted command line */
45 for (i
= 1; i
< argc
; i
++) {
47 const char *arg
= Jim_GetString(argv
[i
], &len
);
50 Jim_AppendString(interp
, cmdlineObj
, " ", 1);
52 if (strpbrk(arg
, "\\\" ") == NULL
) {
53 /* No quoting required */
54 Jim_AppendString(interp
, cmdlineObj
, arg
, len
);
58 Jim_AppendString(interp
, cmdlineObj
, "\"", 1);
59 for (j
= 0; j
< len
; j
++) {
60 if (arg
[j
] == '\\' || arg
[j
] == '"') {
61 Jim_AppendString(interp
, cmdlineObj
, "\\", 1);
63 Jim_AppendString(interp
, cmdlineObj
, &arg
[j
], 1);
65 Jim_AppendString(interp
, cmdlineObj
, "\"", 1);
67 rc
= system(Jim_String(cmdlineObj
));
69 Jim_FreeNewObj(interp
, cmdlineObj
);
72 Jim_Obj
*errorCode
= Jim_NewListObj(interp
, NULL
, 0);
73 Jim_ListAppendElement(interp
, errorCode
, Jim_NewStringObj(interp
, "CHILDSTATUS", -1));
74 Jim_ListAppendElement(interp
, errorCode
, Jim_NewIntObj(interp
, 0));
75 Jim_ListAppendElement(interp
, errorCode
, Jim_NewIntObj(interp
, rc
));
76 Jim_SetGlobalVariableStr(interp
, "errorCode", errorCode
);
83 int Jim_execInit(Jim_Interp
*interp
)
85 if (Jim_PackageProvide(interp
, "exec", "1.0", JIM_ERRMSG
))
88 Jim_CreateCommand(interp
, "exec", Jim_ExecCmd
, NULL
, NULL
);
92 /* Full exec implementation for unix and mingw */
96 #include "jim-signal.h"
97 #include "jimiocompat.h"
100 struct WaitInfoTable
;
102 static char **JimOriginalEnviron(void);
103 static char **JimSaveEnv(char **env
);
104 static void JimRestoreEnv(char **env
);
105 static int JimCreatePipeline(Jim_Interp
*interp
, int argc
, Jim_Obj
*const *argv
,
106 pidtype
**pidArrayPtr
, int *inPipePtr
, int *outPipePtr
, int *errFilePtr
);
107 static void JimDetachPids(struct WaitInfoTable
*table
, int numPids
, const pidtype
*pidPtr
);
108 static int JimCleanupChildren(Jim_Interp
*interp
, int numPids
, pidtype
*pidPtr
, Jim_Obj
*errStrObj
);
109 static int Jim_WaitCommand(Jim_Interp
*interp
, int argc
, Jim_Obj
*const *argv
);
111 #if defined(__MINGW32__)
112 static pidtype
JimStartWinProcess(Jim_Interp
*interp
, char **argv
, char **env
, int inputId
, int outputId
, int errorId
);
116 * If the last character of 'objPtr' is a newline, then remove
117 * the newline character.
119 static void Jim_RemoveTrailingNewline(Jim_Obj
*objPtr
)
122 const char *s
= Jim_GetString(objPtr
, &len
);
124 if (len
> 0 && s
[len
- 1] == '\n') {
126 objPtr
->bytes
[objPtr
->length
] = '\0';
131 * Read from 'fd', append the data to strObj and close 'fd'.
132 * Returns 1 if data was added, 0 if not, or -1 on error.
134 static int JimAppendStreamToString(Jim_Interp
*interp
, int fd
, Jim_Obj
*strObj
)
137 FILE *fh
= fdopen(fd
, "r");
145 int retval
= fread(buf
, 1, sizeof(buf
), fh
);
148 Jim_AppendString(interp
, strObj
, buf
, retval
);
150 if (retval
!= sizeof(buf
)) {
159 * Builds the environment array from $::env
161 * If $::env is not set, simply returns environ.
163 * Otherwise allocates the environ array from the contents of $::env
165 * If the exec fails, memory can be freed via JimFreeEnv()
167 static char **JimBuildEnv(Jim_Interp
*interp
)
176 Jim_Obj
*objPtr
= Jim_GetGlobalVariableStr(interp
, "env", JIM_NONE
);
179 return JimOriginalEnviron();
182 /* We build the array as a single block consisting of the pointers followed by
183 * the strings. This has the advantage of being easy to allocate/free and being
184 * compatible with both unix and windows
187 /* Calculate the required size */
188 num
= Jim_ListLength(interp
, objPtr
);
190 /* Silently drop the last element if not a valid dictionary */
193 /* We need one \0 and one equal sign for each element.
194 * A list has at least one space for each element except the first.
195 * We need one extra char for the extra null terminator and one for the equal sign.
197 size
= Jim_Length(objPtr
) + 2;
199 envptr
= Jim_Alloc(sizeof(*envptr
) * (num
/ 2 + 1) + size
);
200 envdata
= (char *)&envptr
[num
/ 2 + 1];
203 for (i
= 0; i
< num
; i
+= 2) {
207 Jim_ListIndex(interp
, objPtr
, i
, &elemObj
, JIM_NONE
);
208 s1
= Jim_String(elemObj
);
209 Jim_ListIndex(interp
, objPtr
, i
+ 1, &elemObj
, JIM_NONE
);
210 s2
= Jim_String(elemObj
);
213 envdata
+= sprintf(envdata
, "%s=%s", s1
, s2
);
224 * Frees the environment allocated by JimBuildEnv()
226 * Must pass original_environ.
228 static void JimFreeEnv(char **env
, char **original_environ
)
230 if (env
!= original_environ
) {
235 static Jim_Obj
*JimMakeErrorCode(Jim_Interp
*interp
, pidtype pid
, int waitStatus
, Jim_Obj
*errStrObj
)
237 Jim_Obj
*errorCode
= Jim_NewListObj(interp
, NULL
, 0);
239 if (pid
== JIM_BAD_PID
|| pid
== JIM_NO_PID
) {
240 Jim_ListAppendElement(interp
, errorCode
, Jim_NewStringObj(interp
, "NONE", -1));
241 Jim_ListAppendElement(interp
, errorCode
, Jim_NewIntObj(interp
, (long)pid
));
242 Jim_ListAppendElement(interp
, errorCode
, Jim_NewIntObj(interp
, -1));
244 else if (WIFEXITED(waitStatus
)) {
245 Jim_ListAppendElement(interp
, errorCode
, Jim_NewStringObj(interp
, "CHILDSTATUS", -1));
246 Jim_ListAppendElement(interp
, errorCode
, Jim_NewIntObj(interp
, (long)pid
));
247 Jim_ListAppendElement(interp
, errorCode
, Jim_NewIntObj(interp
, WEXITSTATUS(waitStatus
)));
254 if (WIFSIGNALED(waitStatus
)) {
255 type
= "CHILDKILLED";
257 signame
= Jim_SignalId(WTERMSIG(waitStatus
));
261 action
= "suspended";
265 Jim_ListAppendElement(interp
, errorCode
, Jim_NewStringObj(interp
, type
, -1));
268 /* Append the message to 'errStrObj' with a newline.
269 * The last newline will be stripped later
271 Jim_AppendStrings(interp
, errStrObj
, "child ", action
, " by signal ", Jim_SignalId(WTERMSIG(waitStatus
)), "\n", NULL
);
274 Jim_ListAppendElement(interp
, errorCode
, Jim_NewIntObj(interp
, (long)pid
));
275 Jim_ListAppendElement(interp
, errorCode
, Jim_NewStringObj(interp
, signame
, -1));
281 * Create and store an appropriate value for the global variable $::errorCode
282 * Based on pid and waitStatus.
284 * Returns JIM_OK for a normal exit with code 0, otherwise returns JIM_ERR.
286 * Note that $::errorCode is left unchanged for a normal exit.
287 * Details of any abnormal exit is appended to the errStrObj, unless it is NULL.
289 static int JimCheckWaitStatus(Jim_Interp
*interp
, pidtype pid
, int waitStatus
, Jim_Obj
*errStrObj
)
291 if (WIFEXITED(waitStatus
) && WEXITSTATUS(waitStatus
) == 0) {
294 Jim_SetGlobalVariableStr(interp
, "errorCode", JimMakeErrorCode(interp
, pid
, waitStatus
, errStrObj
));
300 * Data structures of the following type are used by exec and
301 * wait to keep track of child processes.
306 pidtype pid
; /* Process id of child. */
307 int status
; /* Status returned when child exited or suspended. */
308 int flags
; /* Various flag bits; see below for definitions. */
311 /* This table is shared by exec and wait */
312 struct WaitInfoTable
{
313 struct WaitInfo
*info
; /* Table of outstanding processes */
314 int size
; /* Size of the allocated table */
315 int used
; /* Number of entries in use */
316 int refcount
; /* Free the table once the refcount drops to 0 */
320 * Flag bits in WaitInfo structures:
322 * WI_DETACHED - Non-zero means no-one cares about the
323 * process anymore. Ignore it until it
324 * exits, then forget about it.
327 #define WI_DETACHED 2
329 #define WAIT_TABLE_GROW_BY 4
331 static void JimFreeWaitInfoTable(struct Jim_Interp
*interp
, void *privData
)
333 struct WaitInfoTable
*table
= privData
;
335 if (--table
->refcount
== 0) {
336 Jim_Free(table
->info
);
341 static struct WaitInfoTable
*JimAllocWaitInfoTable(void)
343 struct WaitInfoTable
*table
= Jim_Alloc(sizeof(*table
));
345 table
->size
= table
->used
= 0;
352 * Removes the given pid from the wait table.
354 * Returns 0 if OK or -1 if not found.
356 static int JimWaitRemove(struct WaitInfoTable
*table
, pidtype pid
)
360 /* Find it in the table */
361 for (i
= 0; i
< table
->used
; i
++) {
362 if (pid
== table
->info
[i
].pid
) {
363 if (i
!= table
->used
- 1) {
364 table
->info
[i
] = table
->info
[table
->used
- 1];
374 * The main [exec] command
376 static int Jim_ExecCmd(Jim_Interp
*interp
, int argc
, Jim_Obj
*const *argv
)
378 int outputId
; /* File id for output pipe. -1 means command overrode. */
379 int errorId
; /* File id for temporary file containing error output. */
382 int child_siginfo
= 1;
383 Jim_Obj
*childErrObj
;
385 struct WaitInfoTable
*table
= Jim_CmdPrivData(interp
);
388 * See if the command is to be run in the background; if so, create
389 * the command, detach it, and return.
391 if (argc
> 1 && Jim_CompareStringImmediate(interp
, argv
[argc
- 1], "&")) {
396 numPids
= JimCreatePipeline(interp
, argc
- 1, argv
+ 1, &pidPtr
, NULL
, NULL
, NULL
);
400 /* The return value is a list of the pids */
401 listObj
= Jim_NewListObj(interp
, NULL
, 0);
402 for (i
= 0; i
< numPids
; i
++) {
403 Jim_ListAppendElement(interp
, listObj
, Jim_NewIntObj(interp
, (long)pidPtr
[i
]));
405 Jim_SetResult(interp
, listObj
);
406 JimDetachPids(table
, numPids
, pidPtr
);
412 * Create the command's pipeline.
415 JimCreatePipeline(interp
, argc
- 1, argv
+ 1, &pidPtr
, NULL
, &outputId
, &errorId
);
423 errStrObj
= Jim_NewStringObj(interp
, "", 0);
425 /* Read from the output pipe until EOF */
426 if (outputId
!= -1) {
427 if (JimAppendStreamToString(interp
, outputId
, errStrObj
) < 0) {
429 Jim_SetResultErrno(interp
, "error reading from output pipe");
433 /* Now wait for children to finish. Any abnormal results are appended to childErrObj */
434 childErrObj
= Jim_NewStringObj(interp
, "", 0);
435 Jim_IncrRefCount(childErrObj
);
437 if (JimCleanupChildren(interp
, numPids
, pidPtr
, childErrObj
) != JIM_OK
) {
442 * Read the child's error output (if any) and put it into the result.
444 * Note that unlike Tcl, the presence of stderr output does not cause
445 * exec to return an error.
449 lseek(errorId
, 0, SEEK_SET
);
450 ret
= JimAppendStreamToString(interp
, errorId
, errStrObj
);
452 Jim_SetResultErrno(interp
, "error reading from error pipe");
456 /* Got some error output, so discard the abnormal info string */
462 /* Append the child siginfo to the result */
463 Jim_AppendObj(interp
, errStrObj
, childErrObj
);
465 Jim_DecrRefCount(interp
, childErrObj
);
467 /* Finally remove any trailing newline from the result */
468 Jim_RemoveTrailingNewline(errStrObj
);
470 /* Set this as the result */
471 Jim_SetResult(interp
, errStrObj
);
477 * Does waitpid() on the given pid, and then removes the
478 * entry from the wait table.
480 * Returns the pid if OK and updates *statusPtr with the status,
481 * or JIM_BAD_PID if the pid was not in the table.
483 static pidtype
JimWaitForProcess(struct WaitInfoTable
*table
, pidtype pid
, int *statusPtr
)
485 if (JimWaitRemove(table
, pid
) == 0) {
487 waitpid(pid
, statusPtr
, 0);
496 * Indicates that one or more child processes have been placed in
497 * background and are no longer cared about.
498 * These children can be cleaned up with JimReapDetachedPids().
500 static void JimDetachPids(struct WaitInfoTable
*table
, int numPids
, const pidtype
*pidPtr
)
504 for (j
= 0; j
< numPids
; j
++) {
505 /* Find it in the table */
507 for (i
= 0; i
< table
->used
; i
++) {
508 if (pidPtr
[j
] == table
->info
[i
].pid
) {
509 table
->info
[i
].flags
|= WI_DETACHED
;
516 /* Use 'name getfd' to get the file descriptor associated with channel 'name'
517 * Returns the file descriptor or -1 on error
519 static int JimGetChannelFd(Jim_Interp
*interp
, const char *name
)
523 objv
[0] = Jim_NewStringObj(interp
, name
, -1);
524 objv
[1] = Jim_NewStringObj(interp
, "getfd", -1);
526 if (Jim_EvalObjVector(interp
, 2, objv
) == JIM_OK
) {
528 if (Jim_GetWide(interp
, Jim_GetResult(interp
), &fd
) == JIM_OK
) {
535 static void JimReapDetachedPids(struct WaitInfoTable
*table
)
537 struct WaitInfo
*waitPtr
;
545 waitPtr
= table
->info
;
547 for (count
= table
->used
; count
> 0; waitPtr
++, count
--) {
548 if (waitPtr
->flags
& WI_DETACHED
) {
550 pidtype pid
= waitpid(waitPtr
->pid
, &status
, WNOHANG
);
551 if (pid
== waitPtr
->pid
) {
552 /* Process has exited, so remove it from the table */
557 if (waitPtr
!= &table
->info
[dest
]) {
558 table
->info
[dest
] = *waitPtr
;
565 * wait ?-nohang? ?pid?
567 * An interface to waitpid(2)
569 * Returns a 3 element list.
571 * If the process has not exited or doesn't exist, returns:
575 * If the process exited normally, returns:
577 * {CHILDSTATUS <pid> <exit-status>}
579 * If the process terminated on a signal, returns:
581 * {CHILDKILLED <pid> <signal>}
583 * Otherwise (core dump, stopped, continued, ...), returns:
585 * {CHILDSUSP <pid> none}
587 * With no arguments, reaps any finished background processes started by exec ... &
589 static int Jim_WaitCommand(Jim_Interp
*interp
, int argc
, Jim_Obj
*const *argv
)
591 struct WaitInfoTable
*table
= Jim_CmdPrivData(interp
);
598 /* With no arguments, reap detached children */
600 JimReapDetachedPids(table
);
604 if (argc
> 1 && Jim_CompareStringImmediate(interp
, argv
[1], "-nohang")) {
607 if (argc
!= nohang
+ 2) {
608 Jim_WrongNumArgs(interp
, 1, argv
, "?-nohang? ?pid?");
611 if (Jim_GetLong(interp
, argv
[nohang
+ 1], &pidarg
) != JIM_OK
) {
615 pid
= waitpid((pidtype
)pidarg
, &status
, nohang
? WNOHANG
: 0);
617 errCodeObj
= JimMakeErrorCode(interp
, pid
, status
, NULL
);
619 if (pid
!= JIM_BAD_PID
&& (WIFEXITED(status
) || WIFSIGNALED(status
))) {
620 /* The process has finished. Remove it from the wait table if it exists there */
621 JimWaitRemove(table
, pid
);
623 Jim_SetResult(interp
, errCodeObj
);
627 static int Jim_PidCommand(Jim_Interp
*interp
, int argc
, Jim_Obj
*const *argv
)
630 Jim_WrongNumArgs(interp
, 1, argv
, "");
634 Jim_SetResultInt(interp
, (jim_wide
)getpid());
639 *----------------------------------------------------------------------
641 * JimCreatePipeline --
643 * Given an argc/argv array, instantiate a pipeline of processes
644 * as described by the argv.
647 * The return value is a count of the number of new processes
648 * created, or -1 if an error occurred while creating the pipeline.
649 * *pidArrayPtr is filled in with the address of a dynamically
650 * allocated array giving the ids of all of the processes. It
651 * is up to the caller to free this array when it isn't needed
652 * anymore. If inPipePtr is non-NULL, *inPipePtr is filled in
653 * with the file id for the input pipe for the pipeline (if any):
654 * the caller must eventually close this file. If outPipePtr
655 * isn't NULL, then *outPipePtr is filled in with the file id
656 * for the output pipe from the pipeline: the caller must close
657 * this file. If errFilePtr isn't NULL, then *errFilePtr is filled
658 * with a file id that may be used to read error output after the
659 * pipeline completes.
662 * Processes and pipes are created.
664 *----------------------------------------------------------------------
667 JimCreatePipeline(Jim_Interp
*interp
, int argc
, Jim_Obj
*const *argv
, pidtype
**pidArrayPtr
,
668 int *inPipePtr
, int *outPipePtr
, int *errFilePtr
)
670 pidtype
*pidPtr
= NULL
; /* Points to malloc-ed array holding all
671 * the pids of child processes. */
672 int numPids
= 0; /* Actual number of processes that exist
673 * at *pidPtr right now. */
674 int cmdCount
; /* Count of number of distinct commands
675 * found in argc/argv. */
676 const char *input
= NULL
; /* Describes input for pipeline, depending
677 * on "inputFile". NULL means take input
678 * from stdin/pipe. */
679 int input_len
= 0; /* Length of input, if relevant */
681 #define FILE_NAME 0 /* input/output: filename */
682 #define FILE_APPEND 1 /* output only: filename, append */
683 #define FILE_HANDLE 2 /* input/output: filehandle */
684 #define FILE_TEXT 3 /* input only: input is actual text */
686 int inputFile
= FILE_NAME
; /* 1 means input is name of input file.
687 * 2 means input is filehandle name.
688 * 0 means input holds actual
689 * text to be input to command. */
691 int outputFile
= FILE_NAME
; /* 0 means output is the name of output file.
692 * 1 means output is the name of output file, and append.
693 * 2 means output is filehandle name.
694 * All this is ignored if output is NULL
696 int errorFile
= FILE_NAME
; /* 0 means error is the name of error file.
697 * 1 means error is the name of error file, and append.
698 * 2 means error is filehandle name.
699 * All this is ignored if error is NULL
701 const char *output
= NULL
; /* Holds name of output file to pipe to,
702 * or NULL if output goes to stdout/pipe. */
703 const char *error
= NULL
; /* Holds name of stderr file to pipe to,
704 * or NULL if stderr goes to stderr/pipe. */
706 /* Readable file id input to current command in
707 * pipeline (could be file or pipe). -1
708 * means use stdin. */
710 /* Writable file id for output from current
711 * command in pipeline (could be file or pipe).
712 * -1 means use stdout. */
714 /* Writable file id for all standard error
715 * output from all commands in pipeline. -1
716 * means use stderr. */
717 int lastOutputId
= -1;
718 /* Write file id for output from last command
719 * in pipeline (could be file or pipe).
720 * -1 means use stdout. */
721 int pipeIds
[2]; /* File ids for pipe that's being created. */
722 int firstArg
, lastArg
; /* Indexes of first and last arguments in
723 * current command. */
729 char **child_environ
;
731 struct WaitInfoTable
*table
= Jim_CmdPrivData(interp
);
733 /* Holds the args which will be used to exec */
734 char **arg_array
= Jim_Alloc(sizeof(*arg_array
) * (argc
+ 1));
737 if (inPipePtr
!= NULL
) {
740 if (outPipePtr
!= NULL
) {
743 if (errFilePtr
!= NULL
) {
746 pipeIds
[0] = pipeIds
[1] = -1;
749 * First, scan through all the arguments to figure out the structure
750 * of the pipeline. Count the number of distinct processes (it's the
751 * number of "|" arguments). If there are "<", "<<", or ">" arguments
752 * then make note of input and output redirection and remove these
753 * arguments and the arguments that follow them.
757 for (i
= 0; i
< argc
; i
++) {
758 const char *arg
= Jim_String(argv
[i
]);
761 inputFile
= FILE_NAME
;
764 inputFile
= FILE_TEXT
;
765 input_len
= Jim_Length(argv
[i
]) - 2;
768 else if (*input
== '@') {
769 inputFile
= FILE_HANDLE
;
773 if (!*input
&& ++i
< argc
) {
774 input
= Jim_GetString(argv
[i
], &input_len
);
777 else if (arg
[0] == '>') {
780 outputFile
= FILE_NAME
;
783 if (*output
== '>') {
784 outputFile
= FILE_APPEND
;
787 if (*output
== '&') {
788 /* Redirect stderr too */
792 if (*output
== '@') {
793 outputFile
= FILE_HANDLE
;
796 if (!*output
&& ++i
< argc
) {
797 output
= Jim_String(argv
[i
]);
800 errorFile
= outputFile
;
804 else if (arg
[0] == '2' && arg
[1] == '>') {
806 errorFile
= FILE_NAME
;
809 errorFile
= FILE_HANDLE
;
812 else if (*error
== '>') {
813 errorFile
= FILE_APPEND
;
816 if (!*error
&& ++i
< argc
) {
817 error
= Jim_String(argv
[i
]);
821 if (strcmp(arg
, "|") == 0 || strcmp(arg
, "|&") == 0) {
822 if (i
== lastBar
+ 1 || i
== argc
- 1) {
823 Jim_SetResultString(interp
, "illegal use of | or |& in command", -1);
829 /* Either |, |& or a "normal" arg, so store it in the arg array */
830 arg_array
[arg_count
++] = (char *)arg
;
835 Jim_SetResultFormatted(interp
, "can't specify \"%s\" as last word in command", arg
);
840 if (arg_count
== 0) {
841 Jim_SetResultString(interp
, "didn't specify command to execute", -1);
847 /* Must do this before vfork(), so do it now */
848 save_environ
= JimSaveEnv(JimBuildEnv(interp
));
851 * Set up the redirected input source for the pipeline, if
855 if (inputFile
== FILE_TEXT
) {
857 * Immediate data in command. Create temporary file and
858 * put data into file.
860 inputId
= Jim_MakeTempFile(interp
, NULL
, 1);
864 if (write(inputId
, input
, input_len
) != input_len
) {
865 Jim_SetResultErrno(interp
, "couldn't write temp file");
869 lseek(inputId
, 0L, SEEK_SET
);
871 else if (inputFile
== FILE_HANDLE
) {
872 int fd
= JimGetChannelFd(interp
, input
);
881 * File redirection. Just open the file.
883 inputId
= Jim_OpenForRead(input
);
885 Jim_SetResultFormatted(interp
, "couldn't read file \"%s\": %s", input
, strerror(Jim_Errno()));
890 else if (inPipePtr
!= NULL
) {
891 if (pipe(pipeIds
) != 0) {
892 Jim_SetResultErrno(interp
, "couldn't create input pipe for command");
895 inputId
= pipeIds
[0];
896 *inPipePtr
= pipeIds
[1];
897 pipeIds
[0] = pipeIds
[1] = -1;
901 * Set up the redirected output sink for the pipeline from one
902 * of two places, if requested.
904 if (output
!= NULL
) {
905 if (outputFile
== FILE_HANDLE
) {
906 int fd
= JimGetChannelFd(interp
, output
);
910 lastOutputId
= dup(fd
);
914 * Output is to go to a file.
916 lastOutputId
= Jim_OpenForWrite(output
, outputFile
== FILE_APPEND
);
917 if (lastOutputId
== -1) {
918 Jim_SetResultFormatted(interp
, "couldn't write file \"%s\": %s", output
, strerror(Jim_Errno()));
923 else if (outPipePtr
!= NULL
) {
925 * Output is to go to a pipe.
927 if (pipe(pipeIds
) != 0) {
928 Jim_SetResultErrno(interp
, "couldn't create output pipe");
931 lastOutputId
= pipeIds
[1];
932 *outPipePtr
= pipeIds
[0];
933 pipeIds
[0] = pipeIds
[1] = -1;
935 /* If we are redirecting stderr with 2>filename or 2>@fileId, then we ignore errFilePtr */
937 if (errorFile
== FILE_HANDLE
) {
938 if (strcmp(error
, "1") == 0) {
940 if (lastOutputId
!= -1) {
941 errorId
= dup(lastOutputId
);
944 /* No redirection of stdout, so just use 2>@stdout */
949 int fd
= JimGetChannelFd(interp
, error
);
958 * Output is to go to a file.
960 errorId
= Jim_OpenForWrite(error
, errorFile
== FILE_APPEND
);
962 Jim_SetResultFormatted(interp
, "couldn't write file \"%s\": %s", error
, strerror(Jim_Errno()));
967 else if (errFilePtr
!= NULL
) {
969 * Set up the standard error output sink for the pipeline, if
970 * requested. Use a temporary file which is opened, then deleted.
971 * Could potentially just use pipe, but if it filled up it could
972 * cause the pipeline to deadlock: we'd be waiting for processes
973 * to complete before reading stderr, and processes couldn't complete
974 * because stderr was backed up.
976 errorId
= Jim_MakeTempFile(interp
, NULL
, 1);
980 *errFilePtr
= dup(errorId
);
984 * Scan through the argc array, forking off a process for each
985 * group of arguments between "|" arguments.
988 pidPtr
= Jim_Alloc(cmdCount
* sizeof(*pidPtr
));
989 for (i
= 0; i
< numPids
; i
++) {
990 pidPtr
[i
] = JIM_BAD_PID
;
992 for (firstArg
= 0; firstArg
< arg_count
; numPids
++, firstArg
= lastArg
+ 1) {
993 int pipe_dup_err
= 0;
994 int origErrorId
= errorId
;
996 for (lastArg
= firstArg
; lastArg
< arg_count
; lastArg
++) {
997 if (strcmp(arg_array
[lastArg
], "|") == 0) {
1000 if (strcmp(arg_array
[lastArg
], "|&") == 0) {
1006 if (lastArg
== firstArg
) {
1007 Jim_SetResultString(interp
, "missing command to exec", -1);
1011 /* Replace | with NULL for execv() */
1012 arg_array
[lastArg
] = NULL
;
1013 if (lastArg
== arg_count
) {
1014 outputId
= lastOutputId
;
1018 if (pipe(pipeIds
) != 0) {
1019 Jim_SetResultErrno(interp
, "couldn't create pipe");
1022 outputId
= pipeIds
[1];
1025 /* Need to do this before vfork() */
1030 /* Now fork the child */
1033 pid
= JimStartWinProcess(interp
, &arg_array
[firstArg
], save_environ
, inputId
, outputId
, errorId
);
1034 if (pid
== JIM_BAD_PID
) {
1035 Jim_SetResultFormatted(interp
, "couldn't exec \"%s\"", arg_array
[firstArg
]);
1039 i
= strlen(arg_array
[firstArg
]);
1041 child_environ
= Jim_GetEnviron();
1043 * Make a new process and enter it into the table if the vfork
1048 Jim_SetResultErrno(interp
, "couldn't fork child process");
1053 /* Set up stdin, stdout, stderr */
1054 if (inputId
!= -1) {
1055 dup2(inputId
, fileno(stdin
));
1058 if (outputId
!= -1) {
1059 dup2(outputId
, fileno(stdout
));
1060 if (outputId
!= errorId
) {
1064 if (errorId
!= -1) {
1065 dup2(errorId
, fileno(stderr
));
1068 /* Close parent-only file descriptors */
1075 if (pipeIds
[0] != -1) {
1078 if (lastOutputId
!= -1) {
1079 close(lastOutputId
);
1082 execvpe(arg_array
[firstArg
], &arg_array
[firstArg
], child_environ
);
1084 if (write(fileno(stderr
), "couldn't exec \"", 15) &&
1085 write(fileno(stderr
), arg_array
[firstArg
], i
) &&
1086 write(fileno(stderr
), "\"\n", 2)) {
1089 #ifdef JIM_MAINTAINER
1091 /* Keep valgrind happy */
1092 static char *const false_argv
[2] = {"false", NULL
};
1093 execvp(false_argv
[0],false_argv
);
1103 * Enlarge the wait table if there isn't enough space for a new
1106 if (table
->used
== table
->size
) {
1107 table
->size
+= WAIT_TABLE_GROW_BY
;
1108 table
->info
= Jim_Realloc(table
->info
, table
->size
* sizeof(*table
->info
));
1111 table
->info
[table
->used
].pid
= pid
;
1112 table
->info
[table
->used
].flags
= 0;
1115 pidPtr
[numPids
] = pid
;
1117 /* Restore in case of pipe_dup_err */
1118 errorId
= origErrorId
;
1121 * Close off our copies of file descriptors that were set up for
1122 * this child, then set up the input for the next child.
1125 if (inputId
!= -1) {
1128 if (outputId
!= -1) {
1131 inputId
= pipeIds
[0];
1132 pipeIds
[0] = pipeIds
[1] = -1;
1134 *pidArrayPtr
= pidPtr
;
1137 * All done. Cleanup open files lying around and then return.
1141 if (inputId
!= -1) {
1144 if (lastOutputId
!= -1) {
1145 close(lastOutputId
);
1147 if (errorId
!= -1) {
1150 Jim_Free(arg_array
);
1152 JimRestoreEnv(save_environ
);
1157 * An error occurred. There could have been extra files open, such
1158 * as pipes between children. Clean them all up. Detach any child
1159 * processes that have been created.
1163 if ((inPipePtr
!= NULL
) && (*inPipePtr
!= -1)) {
1167 if ((outPipePtr
!= NULL
) && (*outPipePtr
!= -1)) {
1171 if ((errFilePtr
!= NULL
) && (*errFilePtr
!= -1)) {
1175 if (pipeIds
[0] != -1) {
1178 if (pipeIds
[1] != -1) {
1181 if (pidPtr
!= NULL
) {
1182 for (i
= 0; i
< numPids
; i
++) {
1183 if (pidPtr
[i
] != JIM_BAD_PID
) {
1184 JimDetachPids(table
, 1, &pidPtr
[i
]);
1194 *----------------------------------------------------------------------
1196 * JimCleanupChildren --
1198 * This is a utility procedure used to wait for child processes
1199 * to exit, record information about abnormal exits.
1202 * The return value is a standard Tcl result. If anything at
1203 * weird happened with the child processes, JIM_ERR is returned
1204 * and a structured message is left in $::errorCode.
1205 * If errStrObj is not NULL, abnormal exit details are appended to this object.
1210 *----------------------------------------------------------------------
1213 static int JimCleanupChildren(Jim_Interp
*interp
, int numPids
, pidtype
*pidPtr
, Jim_Obj
*errStrObj
)
1215 struct WaitInfoTable
*table
= Jim_CmdPrivData(interp
);
1216 int result
= JIM_OK
;
1219 /* Now check the return status of each child */
1220 for (i
= 0; i
< numPids
; i
++) {
1222 if (JimWaitForProcess(table
, pidPtr
[i
], &waitStatus
) != JIM_BAD_PID
) {
1223 if (JimCheckWaitStatus(interp
, pidPtr
[i
], waitStatus
, errStrObj
) != JIM_OK
) {
1233 int Jim_execInit(Jim_Interp
*interp
)
1235 struct WaitInfoTable
*waitinfo
;
1236 if (Jim_PackageProvide(interp
, "exec", "1.0", JIM_ERRMSG
))
1239 waitinfo
= JimAllocWaitInfoTable();
1240 Jim_CreateCommand(interp
, "exec", Jim_ExecCmd
, waitinfo
, JimFreeWaitInfoTable
);
1241 waitinfo
->refcount
++;
1242 Jim_CreateCommand(interp
, "wait", Jim_WaitCommand
, waitinfo
, JimFreeWaitInfoTable
);
1243 Jim_CreateCommand(interp
, "pid", Jim_PidCommand
, 0, 0);
1248 #if defined(__MINGW32__)
1249 /* Windows-specific (mingw) implementation */
1252 JimWinFindExecutable(const char *originalName
, char fullPath
[MAX_PATH
])
1255 static char extensions
[][5] = {".exe", "", ".bat"};
1257 for (i
= 0; i
< (int) (sizeof(extensions
) / sizeof(extensions
[0])); i
++) {
1258 snprintf(fullPath
, MAX_PATH
, "%s%s", originalName
, extensions
[i
]);
1260 if (SearchPath(NULL
, fullPath
, NULL
, MAX_PATH
, fullPath
, NULL
) == 0) {
1263 if (GetFileAttributes(fullPath
) & FILE_ATTRIBUTE_DIRECTORY
) {
1272 static char **JimSaveEnv(char **env
)
1277 static void JimRestoreEnv(char **env
)
1279 JimFreeEnv(env
, Jim_GetEnviron());
1282 static char **JimOriginalEnviron(void)
1288 JimWinBuildCommandLine(Jim_Interp
*interp
, char **argv
)
1290 char *start
, *special
;
1293 Jim_Obj
*strObj
= Jim_NewStringObj(interp
, "", 0);
1295 for (i
= 0; argv
[i
]; i
++) {
1297 Jim_AppendString(interp
, strObj
, " ", 1);
1300 if (argv
[i
][0] == '\0') {
1305 for (start
= argv
[i
]; *start
!= '\0'; start
++) {
1306 if (isspace(UCHAR(*start
))) {
1313 Jim_AppendString(interp
, strObj
, "\"" , 1);
1317 for (special
= argv
[i
]; ; ) {
1318 if ((*special
== '\\') && (special
[1] == '\\' ||
1319 special
[1] == '"' || (quote
&& special
[1] == '\0'))) {
1320 Jim_AppendString(interp
, strObj
, start
, special
- start
);
1324 if (*special
== '"' || (quote
&& *special
== '\0')) {
1326 * N backslashes followed a quote -> insert
1327 * N * 2 + 1 backslashes then a quote.
1330 Jim_AppendString(interp
, strObj
, start
, special
- start
);
1333 if (*special
!= '\\') {
1337 Jim_AppendString(interp
, strObj
, start
, special
- start
);
1340 if (*special
== '"') {
1341 if (special
== start
) {
1342 Jim_AppendString(interp
, strObj
, "\"", 1);
1345 Jim_AppendString(interp
, strObj
, start
, special
- start
);
1347 Jim_AppendString(interp
, strObj
, "\\\"", 2);
1348 start
= special
+ 1;
1350 if (*special
== '\0') {
1355 Jim_AppendString(interp
, strObj
, start
, special
- start
);
1357 Jim_AppendString(interp
, strObj
, "\"", 1);
1364 * Note that inputId, etc. are osf_handles.
1367 JimStartWinProcess(Jim_Interp
*interp
, char **argv
, char **env
, int inputId
, int outputId
, int errorId
)
1369 STARTUPINFO startInfo
;
1370 PROCESS_INFORMATION procInfo
;
1372 char execPath
[MAX_PATH
];
1373 pidtype pid
= JIM_BAD_PID
;
1374 Jim_Obj
*cmdLineObj
;
1377 if (JimWinFindExecutable(argv
[0], execPath
) < 0) {
1382 hProcess
= GetCurrentProcess();
1383 cmdLineObj
= JimWinBuildCommandLine(interp
, argv
);
1386 * STARTF_USESTDHANDLES must be used to pass handles to child process.
1387 * Using SetStdHandle() and/or dup2() only works when a console mode
1388 * parent process is spawning an attached console mode child process.
1391 ZeroMemory(&startInfo
, sizeof(startInfo
));
1392 startInfo
.cb
= sizeof(startInfo
);
1393 startInfo
.dwFlags
= STARTF_USESTDHANDLES
;
1394 startInfo
.hStdInput
= INVALID_HANDLE_VALUE
;
1395 startInfo
.hStdOutput
= INVALID_HANDLE_VALUE
;
1396 startInfo
.hStdError
= INVALID_HANDLE_VALUE
;
1399 * Duplicate all the handles which will be passed off as stdin, stdout
1400 * and stderr of the child process. The duplicate handles are set to
1401 * be inheritable, so the child process can use them.
1404 * If stdin was not redirected, input should come from the parent's stdin
1406 if (inputId
== -1) {
1407 inputId
= _fileno(stdin
);
1409 DuplicateHandle(hProcess
, (HANDLE
)_get_osfhandle(inputId
), hProcess
, &startInfo
.hStdInput
,
1410 0, TRUE
, DUPLICATE_SAME_ACCESS
);
1411 if (startInfo
.hStdInput
== INVALID_HANDLE_VALUE
) {
1416 * If stdout was not redirected, output should go to the parent's stdout
1418 if (outputId
== -1) {
1419 outputId
= _fileno(stdout
);
1421 DuplicateHandle(hProcess
, (HANDLE
)_get_osfhandle(outputId
), hProcess
, &startInfo
.hStdOutput
,
1422 0, TRUE
, DUPLICATE_SAME_ACCESS
);
1423 if (startInfo
.hStdOutput
== INVALID_HANDLE_VALUE
) {
1428 if (errorId
== -1) {
1429 errorId
= _fileno(stderr
);
1431 DuplicateHandle(hProcess
, (HANDLE
)_get_osfhandle(errorId
), hProcess
, &startInfo
.hStdError
,
1432 0, TRUE
, DUPLICATE_SAME_ACCESS
);
1433 if (startInfo
.hStdError
== INVALID_HANDLE_VALUE
) {
1437 /* If env is NULL, use the original environment.
1438 * If env[0] is NULL, use an empty environment.
1439 * Otherwise use the environment starting at env[0]
1442 /* Use the original environment */
1445 else if (env
[0] == NULL
) {
1446 winenv
= (char *)"\0";
1452 if (!CreateProcess(NULL
, (char *)Jim_String(cmdLineObj
), NULL
, NULL
, TRUE
,
1453 0, winenv
, NULL
, &startInfo
, &procInfo
)) {
1458 * "When an application spawns a process repeatedly, a new thread
1459 * instance will be created for each process but the previous
1460 * instances may not be cleaned up. This results in a significant
1461 * virtual memory loss each time the process is spawned. If there
1462 * is a WaitForInputIdle() call between CreateProcess() and
1463 * CloseHandle(), the problem does not occur." PSS ID Number: Q124121
1466 WaitForInputIdle(procInfo
.hProcess
, 5000);
1467 CloseHandle(procInfo
.hThread
);
1469 pid
= procInfo
.hProcess
;
1472 Jim_FreeNewObj(interp
, cmdLineObj
);
1473 if (startInfo
.hStdInput
!= INVALID_HANDLE_VALUE
) {
1474 CloseHandle(startInfo
.hStdInput
);
1476 if (startInfo
.hStdOutput
!= INVALID_HANDLE_VALUE
) {
1477 CloseHandle(startInfo
.hStdOutput
);
1479 if (startInfo
.hStdError
!= INVALID_HANDLE_VALUE
) {
1480 CloseHandle(startInfo
.hStdError
);
1487 static char **JimOriginalEnviron(void)
1489 return Jim_GetEnviron();
1492 static char **JimSaveEnv(char **env
)
1494 char **saveenv
= Jim_GetEnviron();
1495 Jim_SetEnviron(env
);
1499 static void JimRestoreEnv(char **env
)
1501 JimFreeEnv(Jim_GetEnviron(), env
);
1502 Jim_SetEnviron(env
);