Update autosetup to latest version
[jimtcl.git] / jim-exec.c
blob40319a0610b4ede67fee1de53ad73c0a90a7bf29
1 /*
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.
7 * From that code:
9 * The Tcl_Fork and Tcl_WaitPids procedures are based on code
10 * contributed by Karl Lehenbauer, Mark Diekhans and Peter
11 * da Silva.
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.
23 #include <string.h>
24 #include <ctype.h>
26 #include "jimautoconf.h"
27 #include <jim.h>
29 #if (!defined(HAVE_VFORK) || !defined(HAVE_WAITPID)) && !defined(__MINGW32__)
30 /* Poor man's implementation of exec with system()
31 * The system() call *may* do command line redirection, etc.
32 * The standard output is not available.
33 * Can't redirect filehandles.
35 static int Jim_ExecCmd(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
37 Jim_Obj *cmdlineObj = Jim_NewEmptyStringObj(interp);
38 int i, j;
39 int rc;
41 /* Create a quoted command line */
42 for (i = 1; i < argc; i++) {
43 int len;
44 const char *arg = Jim_GetString(argv[i], &len);
46 if (i > 1) {
47 Jim_AppendString(interp, cmdlineObj, " ", 1);
49 if (strpbrk(arg, "\\\" ") == NULL) {
50 /* No quoting required */
51 Jim_AppendString(interp, cmdlineObj, arg, len);
52 continue;
55 Jim_AppendString(interp, cmdlineObj, "\"", 1);
56 for (j = 0; j < len; j++) {
57 if (arg[j] == '\\' || arg[j] == '"') {
58 Jim_AppendString(interp, cmdlineObj, "\\", 1);
60 Jim_AppendString(interp, cmdlineObj, &arg[j], 1);
62 Jim_AppendString(interp, cmdlineObj, "\"", 1);
64 rc = system(Jim_String(cmdlineObj));
66 Jim_FreeNewObj(interp, cmdlineObj);
68 if (rc) {
69 Jim_Obj *errorCode = Jim_NewListObj(interp, NULL, 0);
70 Jim_ListAppendElement(interp, errorCode, Jim_NewStringObj(interp, "CHILDSTATUS", -1));
71 Jim_ListAppendElement(interp, errorCode, Jim_NewIntObj(interp, 0));
72 Jim_ListAppendElement(interp, errorCode, Jim_NewIntObj(interp, rc));
73 Jim_SetGlobalVariableStr(interp, "errorCode", errorCode);
74 return JIM_ERR;
77 return JIM_OK;
80 int Jim_execInit(Jim_Interp *interp)
82 if (Jim_PackageProvide(interp, "exec", "1.0", JIM_ERRMSG))
83 return JIM_ERR;
85 Jim_CreateCommand(interp, "exec", Jim_ExecCmd, NULL, NULL);
86 return JIM_OK;
88 #else
89 /* Full exec implementation for unix and mingw */
91 #include <errno.h>
92 #include <signal.h>
94 #if defined(__MINGW32__)
95 /* XXX: Should we use this implementation for cygwin too? msvc? */
96 #ifndef STRICT
97 #define STRICT
98 #endif
99 #define WIN32_LEAN_AND_MEAN
100 #include <windows.h>
101 #include <fcntl.h>
103 typedef HANDLE fdtype;
104 typedef HANDLE pidtype;
105 #define JIM_BAD_FD INVALID_HANDLE_VALUE
106 #define JIM_BAD_PID INVALID_HANDLE_VALUE
107 #define JimCloseFd CloseHandle
109 #define WIFEXITED(STATUS) 1
110 #define WEXITSTATUS(STATUS) (STATUS)
111 #define WIFSIGNALED(STATUS) 0
112 #define WTERMSIG(STATUS) 0
113 #define WNOHANG 1
115 static fdtype JimFileno(FILE *fh);
116 static pidtype JimWaitPid(pidtype pid, int *status, int nohang);
117 static fdtype JimDupFd(fdtype infd);
118 static fdtype JimOpenForRead(const char *filename);
119 static FILE *JimFdOpenForRead(fdtype fd);
120 static int JimPipe(fdtype pipefd[2]);
121 static pidtype JimStartWinProcess(Jim_Interp *interp, char **argv, char *env,
122 fdtype inputId, fdtype outputId, fdtype errorId);
123 static int JimErrno(void);
124 #else
125 #include "jim-signal.h"
126 #include <unistd.h>
127 #include <fcntl.h>
128 #include <sys/wait.h>
129 #include <sys/stat.h>
131 typedef int fdtype;
132 typedef int pidtype;
133 #define JimPipe pipe
134 #define JimErrno() errno
135 #define JIM_BAD_FD -1
136 #define JIM_BAD_PID -1
137 #define JimFileno fileno
138 #define JimReadFd read
139 #define JimCloseFd close
140 #define JimWaitPid waitpid
141 #define JimDupFd dup
142 #define JimFdOpenForRead(FD) fdopen((FD), "r")
143 #define JimOpenForRead(NAME) open((NAME), O_RDONLY, 0)
145 #ifndef HAVE_EXECVPE
146 #define execvpe(ARG0, ARGV, ENV) execvp(ARG0, ARGV)
147 #endif
148 #endif
150 static const char *JimStrError(void);
151 static char **JimSaveEnv(char **env);
152 static void JimRestoreEnv(char **env);
153 static int JimCreatePipeline(Jim_Interp *interp, int argc, Jim_Obj *const *argv,
154 pidtype **pidArrayPtr, fdtype *inPipePtr, fdtype *outPipePtr, fdtype *errFilePtr);
155 static void JimDetachPids(Jim_Interp *interp, int numPids, const pidtype *pidPtr);
156 static int JimCleanupChildren(Jim_Interp *interp, int numPids, pidtype *pidPtr, int child_siginfo);
157 static fdtype JimCreateTemp(Jim_Interp *interp, const char *contents, int len);
158 static fdtype JimOpenForWrite(const char *filename, int append);
159 static int JimRewindFd(fdtype fd);
161 static void Jim_SetResultErrno(Jim_Interp *interp, const char *msg)
163 Jim_SetResultFormatted(interp, "%s: %s", msg, JimStrError());
166 static const char *JimStrError(void)
168 return strerror(JimErrno());
172 * If the last character of 'objPtr' is a newline, then remove
173 * the newline character.
175 static void Jim_RemoveTrailingNewline(Jim_Obj *objPtr)
177 int len;
178 const char *s = Jim_GetString(objPtr, &len);
180 if (len > 0 && s[len - 1] == '\n') {
181 objPtr->length--;
182 objPtr->bytes[objPtr->length] = '\0';
187 * Read from 'fd', append the data to strObj and close 'fd'.
188 * Returns 1 if data was added, 0 if not, or -1 on error.
190 static int JimAppendStreamToString(Jim_Interp *interp, fdtype fd, Jim_Obj *strObj)
192 char buf[256];
193 FILE *fh = JimFdOpenForRead(fd);
194 int ret = 0;
196 if (fh == NULL) {
197 return -1;
200 while (1) {
201 int retval = fread(buf, 1, sizeof(buf), fh);
202 if (retval > 0) {
203 ret = 1;
204 Jim_AppendString(interp, strObj, buf, retval);
206 if (retval != sizeof(buf)) {
207 break;
210 fclose(fh);
211 return ret;
215 * Builds the environment array from $::env
217 * If $::env is not set, simply returns environ.
219 * Otherwise allocates the environ array from the contents of $::env
221 * If the exec fails, memory can be freed via JimFreeEnv()
223 static char **JimBuildEnv(Jim_Interp *interp)
225 int i;
226 int size;
227 int num;
228 int n;
229 char **envptr;
230 char *envdata;
232 Jim_Obj *objPtr = Jim_GetGlobalVariableStr(interp, "env", JIM_NONE);
234 if (!objPtr) {
235 return Jim_GetEnviron();
238 /* We build the array as a single block consisting of the pointers followed by
239 * the strings. This has the advantage of being easy to allocate/free and being
240 * compatible with both unix and windows
243 /* Calculate the required size */
244 num = Jim_ListLength(interp, objPtr);
245 if (num % 2) {
246 /* Silently drop the last element if not a valid dictionary */
247 num--;
249 /* We need one \0 and one equal sign for each element.
250 * A list has at least one space for each element except the first.
251 * We need one extra char for the extra null terminator and one for the equal sign.
253 size = Jim_Length(objPtr) + 2;
255 envptr = Jim_Alloc(sizeof(*envptr) * (num / 2 + 1) + size);
256 envdata = (char *)&envptr[num / 2 + 1];
258 n = 0;
259 for (i = 0; i < num; i += 2) {
260 const char *s1, *s2;
261 Jim_Obj *elemObj;
263 Jim_ListIndex(interp, objPtr, i, &elemObj, JIM_NONE);
264 s1 = Jim_String(elemObj);
265 Jim_ListIndex(interp, objPtr, i + 1, &elemObj, JIM_NONE);
266 s2 = Jim_String(elemObj);
268 envptr[n] = envdata;
269 envdata += sprintf(envdata, "%s=%s", s1, s2);
270 envdata++;
271 n++;
273 envptr[n] = NULL;
274 *envdata = 0;
276 return envptr;
280 * Frees the environment allocated by JimBuildEnv()
282 * Must pass original_environ.
284 static void JimFreeEnv(char **env, char **original_environ)
286 if (env != original_environ) {
287 Jim_Free(env);
291 #ifndef jim_ext_signal
292 /* Implement trivial Jim_SignalId() and Jim_SignalName(), just good enough for JimCheckWaitStatus() */
293 const char *Jim_SignalId(int sig)
295 static char buf[10];
296 snprintf(buf, sizeof(buf), "%d", sig);
297 return buf;
300 const char *Jim_SignalName(int sig)
302 return Jim_SignalId(sig);
304 #endif
307 * Create and store an appropriate value for the global variable $::errorCode
308 * Based on pid and waitStatus.
310 * Returns JIM_OK for a normal exit with code 0, otherwise returns JIM_ERR.
312 * Note that $::errorCode is left unchanged for a normal exit.
314 static int JimCheckWaitStatus(Jim_Interp *interp, pidtype pid, int waitStatus, int child_siginfo)
316 Jim_Obj *errorCode;
318 if (WIFEXITED(waitStatus) && WEXITSTATUS(waitStatus) == 0) {
319 return JIM_OK;
321 errorCode = Jim_NewListObj(interp, NULL, 0);
323 if (WIFEXITED(waitStatus)) {
324 Jim_ListAppendElement(interp, errorCode, Jim_NewStringObj(interp, "CHILDSTATUS", -1));
325 Jim_ListAppendElement(interp, errorCode, Jim_NewIntObj(interp, (long)pid));
326 Jim_ListAppendElement(interp, errorCode, Jim_NewIntObj(interp, WEXITSTATUS(waitStatus)));
328 else {
329 const char *type;
330 const char *action;
332 if (WIFSIGNALED(waitStatus)) {
333 type = "CHILDKILLED";
334 action = "killed";
336 else {
337 type = "CHILDSUSP";
338 action = "suspended";
341 Jim_ListAppendElement(interp, errorCode, Jim_NewStringObj(interp, type, -1));
343 /* Append the message to the result with a newline.
344 * The last newline will be stripped later
346 if (child_siginfo) {
347 Jim_SetResultFormatted(interp, "%#schild %s by signal %s\n", Jim_GetResult(interp), action, Jim_SignalId(WTERMSIG(waitStatus)));
349 Jim_ListAppendElement(interp, errorCode, Jim_NewIntObj(interp, pid));
350 Jim_ListAppendElement(interp, errorCode, Jim_NewStringObj(interp, Jim_SignalId(WTERMSIG(waitStatus)), -1));
351 Jim_ListAppendElement(interp, errorCode, Jim_NewStringObj(interp, Jim_SignalName(WTERMSIG(waitStatus)), -1));
353 Jim_SetGlobalVariableStr(interp, "errorCode", errorCode);
355 return JIM_ERR;
359 * Data structures of the following type are used by JimFork and
360 * JimWaitPids to keep track of child processes.
363 struct WaitInfo
365 pidtype pid; /* Process id of child. */
366 int status; /* Status returned when child exited or suspended. */
367 int flags; /* Various flag bits; see below for definitions. */
370 struct WaitInfoTable {
371 struct WaitInfo *info; /* Table of outstanding processes */
372 int size; /* Size of the allocated table */
373 int used; /* Number of entries in use */
377 * Flag bits in WaitInfo structures:
379 * WI_DETACHED - Non-zero means no-one cares about the
380 * process anymore. Ignore it until it
381 * exits, then forget about it.
384 #define WI_DETACHED 2
386 #define WAIT_TABLE_GROW_BY 4
388 static void JimFreeWaitInfoTable(struct Jim_Interp *interp, void *privData)
390 struct WaitInfoTable *table = privData;
392 Jim_Free(table->info);
393 Jim_Free(table);
396 static struct WaitInfoTable *JimAllocWaitInfoTable(void)
398 struct WaitInfoTable *table = Jim_Alloc(sizeof(*table));
399 table->info = NULL;
400 table->size = table->used = 0;
402 return table;
406 * The main [exec] command
408 static int Jim_ExecCmd(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
410 fdtype outputId; /* File id for output pipe. -1 means command overrode. */
411 fdtype errorId; /* File id for temporary file containing error output. */
412 pidtype *pidPtr;
413 int numPids, result;
414 int child_siginfo = 1;
417 * See if the command is to be run in the background; if so, create
418 * the command, detach it, and return.
420 if (argc > 1 && Jim_CompareStringImmediate(interp, argv[argc - 1], "&")) {
421 Jim_Obj *listObj;
422 int i;
424 argc--;
425 numPids = JimCreatePipeline(interp, argc - 1, argv + 1, &pidPtr, NULL, NULL, NULL);
426 if (numPids < 0) {
427 return JIM_ERR;
429 /* The return value is a list of the pids */
430 listObj = Jim_NewListObj(interp, NULL, 0);
431 for (i = 0; i < numPids; i++) {
432 Jim_ListAppendElement(interp, listObj, Jim_NewIntObj(interp, (long)pidPtr[i]));
434 Jim_SetResult(interp, listObj);
435 JimDetachPids(interp, numPids, pidPtr);
436 Jim_Free(pidPtr);
437 return JIM_OK;
441 * Create the command's pipeline.
443 numPids =
444 JimCreatePipeline(interp, argc - 1, argv + 1, &pidPtr, NULL, &outputId, &errorId);
446 if (numPids < 0) {
447 return JIM_ERR;
451 * Read the child's output (if any) and put it into the result.
453 Jim_SetResultString(interp, "", 0);
455 result = JIM_OK;
456 if (outputId != JIM_BAD_FD) {
457 if (JimAppendStreamToString(interp, outputId, Jim_GetResult(interp)) < 0) {
458 result = JIM_ERR;
459 Jim_SetResultErrno(interp, "error reading from output pipe");
464 * Read the child's error output (if any) and put it into the result.
466 * Note that unlike Tcl, the presence of stderr output does not cause
467 * exec to return an error.
469 if (errorId != JIM_BAD_FD) {
470 int ret;
471 JimRewindFd(errorId);
472 ret = JimAppendStreamToString(interp, errorId, Jim_GetResult(interp));
473 if (ret < 0) {
474 Jim_SetResultErrno(interp, "error reading from error pipe");
475 result = JIM_ERR;
477 else if (ret > 0) {
478 child_siginfo = 0;
482 if (JimCleanupChildren(interp, numPids, pidPtr, child_siginfo) != JIM_OK) {
483 result = JIM_ERR;
486 /* Finally remove any trailing newline from the result */
487 Jim_RemoveTrailingNewline(Jim_GetResult(interp));
489 return result;
492 static void JimReapDetachedPids(struct WaitInfoTable *table)
494 struct WaitInfo *waitPtr;
495 int count;
496 int dest;
498 if (!table) {
499 return;
502 waitPtr = table->info;
503 dest = 0;
504 for (count = table->used; count > 0; waitPtr++, count--) {
505 if (waitPtr->flags & WI_DETACHED) {
506 int status;
507 pidtype pid = JimWaitPid(waitPtr->pid, &status, WNOHANG);
508 if (pid == waitPtr->pid) {
509 /* Process has exited, so remove it from the table */
510 table->used--;
511 continue;
514 if (waitPtr != &table->info[dest]) {
515 table->info[dest] = *waitPtr;
517 dest++;
522 * Does waitpid() on the given pid, and then removes the
523 * entry from the wait table.
525 * Returns the pid if OK and updates *statusPtr with the status,
526 * or JIM_BAD_PID if the pid was not in the table.
528 static pidtype JimWaitForProcess(struct WaitInfoTable *table, pidtype pid, int *statusPtr)
530 int i;
532 /* Find it in the table */
533 for (i = 0; i < table->used; i++) {
534 if (pid == table->info[i].pid) {
535 /* wait for it */
536 JimWaitPid(pid, statusPtr, 0);
538 /* Remove it from the table */
539 if (i != table->used - 1) {
540 table->info[i] = table->info[table->used - 1];
542 table->used--;
543 return pid;
547 /* Not found */
548 return JIM_BAD_PID;
552 * Indicates that one or more child processes have been placed in
553 * background and are no longer cared about.
554 * These children can be cleaned up with JimReapDetachedPids().
556 static void JimDetachPids(Jim_Interp *interp, int numPids, const pidtype *pidPtr)
558 int j;
559 struct WaitInfoTable *table = Jim_CmdPrivData(interp);
561 for (j = 0; j < numPids; j++) {
562 /* Find it in the table */
563 int i;
564 for (i = 0; i < table->used; i++) {
565 if (pidPtr[j] == table->info[i].pid) {
566 table->info[i].flags |= WI_DETACHED;
567 break;
573 static FILE *JimGetAioFilehandle(Jim_Interp *interp, const char *name)
575 FILE *fh;
576 Jim_Obj *fhObj;
578 fhObj = Jim_NewStringObj(interp, name, -1);
579 Jim_IncrRefCount(fhObj);
580 fh = Jim_AioFilehandle(interp, fhObj);
581 Jim_DecrRefCount(interp, fhObj);
583 return fh;
587 *----------------------------------------------------------------------
589 * JimCreatePipeline --
591 * Given an argc/argv array, instantiate a pipeline of processes
592 * as described by the argv.
594 * Results:
595 * The return value is a count of the number of new processes
596 * created, or -1 if an error occurred while creating the pipeline.
597 * *pidArrayPtr is filled in with the address of a dynamically
598 * allocated array giving the ids of all of the processes. It
599 * is up to the caller to free this array when it isn't needed
600 * anymore. If inPipePtr is non-NULL, *inPipePtr is filled in
601 * with the file id for the input pipe for the pipeline (if any):
602 * the caller must eventually close this file. If outPipePtr
603 * isn't NULL, then *outPipePtr is filled in with the file id
604 * for the output pipe from the pipeline: the caller must close
605 * this file. If errFilePtr isn't NULL, then *errFilePtr is filled
606 * with a file id that may be used to read error output after the
607 * pipeline completes.
609 * Side effects:
610 * Processes and pipes are created.
612 *----------------------------------------------------------------------
614 static int
615 JimCreatePipeline(Jim_Interp *interp, int argc, Jim_Obj *const *argv, pidtype **pidArrayPtr,
616 fdtype *inPipePtr, fdtype *outPipePtr, fdtype *errFilePtr)
618 pidtype *pidPtr = NULL; /* Points to malloc-ed array holding all
619 * the pids of child processes. */
620 int numPids = 0; /* Actual number of processes that exist
621 * at *pidPtr right now. */
622 int cmdCount; /* Count of number of distinct commands
623 * found in argc/argv. */
624 const char *input = NULL; /* Describes input for pipeline, depending
625 * on "inputFile". NULL means take input
626 * from stdin/pipe. */
627 int input_len = 0; /* Length of input, if relevant */
629 #define FILE_NAME 0 /* input/output: filename */
630 #define FILE_APPEND 1 /* output only: filename, append */
631 #define FILE_HANDLE 2 /* input/output: filehandle */
632 #define FILE_TEXT 3 /* input only: input is actual text */
634 int inputFile = FILE_NAME; /* 1 means input is name of input file.
635 * 2 means input is filehandle name.
636 * 0 means input holds actual
637 * text to be input to command. */
639 int outputFile = FILE_NAME; /* 0 means output is the name of output file.
640 * 1 means output is the name of output file, and append.
641 * 2 means output is filehandle name.
642 * All this is ignored if output is NULL
644 int errorFile = FILE_NAME; /* 0 means error is the name of error file.
645 * 1 means error is the name of error file, and append.
646 * 2 means error is filehandle name.
647 * All this is ignored if error is NULL
649 const char *output = NULL; /* Holds name of output file to pipe to,
650 * or NULL if output goes to stdout/pipe. */
651 const char *error = NULL; /* Holds name of stderr file to pipe to,
652 * or NULL if stderr goes to stderr/pipe. */
653 fdtype inputId = JIM_BAD_FD;
654 /* Readable file id input to current command in
655 * pipeline (could be file or pipe). JIM_BAD_FD
656 * means use stdin. */
657 fdtype outputId = JIM_BAD_FD;
658 /* Writable file id for output from current
659 * command in pipeline (could be file or pipe).
660 * JIM_BAD_FD means use stdout. */
661 fdtype errorId = JIM_BAD_FD;
662 /* Writable file id for all standard error
663 * output from all commands in pipeline. JIM_BAD_FD
664 * means use stderr. */
665 fdtype lastOutputId = JIM_BAD_FD;
666 /* Write file id for output from last command
667 * in pipeline (could be file or pipe).
668 * -1 means use stdout. */
669 fdtype pipeIds[2]; /* File ids for pipe that's being created. */
670 int firstArg, lastArg; /* Indexes of first and last arguments in
671 * current command. */
672 int lastBar;
673 int i;
674 pidtype pid;
675 char **save_environ;
676 struct WaitInfoTable *table = Jim_CmdPrivData(interp);
678 /* Holds the args which will be used to exec */
679 char **arg_array = Jim_Alloc(sizeof(*arg_array) * (argc + 1));
680 int arg_count = 0;
682 JimReapDetachedPids(table);
684 if (inPipePtr != NULL) {
685 *inPipePtr = JIM_BAD_FD;
687 if (outPipePtr != NULL) {
688 *outPipePtr = JIM_BAD_FD;
690 if (errFilePtr != NULL) {
691 *errFilePtr = JIM_BAD_FD;
693 pipeIds[0] = pipeIds[1] = JIM_BAD_FD;
696 * First, scan through all the arguments to figure out the structure
697 * of the pipeline. Count the number of distinct processes (it's the
698 * number of "|" arguments). If there are "<", "<<", or ">" arguments
699 * then make note of input and output redirection and remove these
700 * arguments and the arguments that follow them.
702 cmdCount = 1;
703 lastBar = -1;
704 for (i = 0; i < argc; i++) {
705 const char *arg = Jim_String(argv[i]);
707 if (arg[0] == '<') {
708 inputFile = FILE_NAME;
709 input = arg + 1;
710 if (*input == '<') {
711 inputFile = FILE_TEXT;
712 input_len = Jim_Length(argv[i]) - 2;
713 input++;
715 else if (*input == '@') {
716 inputFile = FILE_HANDLE;
717 input++;
720 if (!*input && ++i < argc) {
721 input = Jim_GetString(argv[i], &input_len);
724 else if (arg[0] == '>') {
725 int dup_error = 0;
727 outputFile = FILE_NAME;
729 output = arg + 1;
730 if (*output == '>') {
731 outputFile = FILE_APPEND;
732 output++;
734 if (*output == '&') {
735 /* Redirect stderr too */
736 output++;
737 dup_error = 1;
739 if (*output == '@') {
740 outputFile = FILE_HANDLE;
741 output++;
743 if (!*output && ++i < argc) {
744 output = Jim_String(argv[i]);
746 if (dup_error) {
747 errorFile = outputFile;
748 error = output;
751 else if (arg[0] == '2' && arg[1] == '>') {
752 error = arg + 2;
753 errorFile = FILE_NAME;
755 if (*error == '@') {
756 errorFile = FILE_HANDLE;
757 error++;
759 else if (*error == '>') {
760 errorFile = FILE_APPEND;
761 error++;
763 if (!*error && ++i < argc) {
764 error = Jim_String(argv[i]);
767 else {
768 if (strcmp(arg, "|") == 0 || strcmp(arg, "|&") == 0) {
769 if (i == lastBar + 1 || i == argc - 1) {
770 Jim_SetResultString(interp, "illegal use of | or |& in command", -1);
771 goto badargs;
773 lastBar = i;
774 cmdCount++;
776 /* Either |, |& or a "normal" arg, so store it in the arg array */
777 arg_array[arg_count++] = (char *)arg;
778 continue;
781 if (i >= argc) {
782 Jim_SetResultFormatted(interp, "can't specify \"%s\" as last word in command", arg);
783 goto badargs;
787 if (arg_count == 0) {
788 Jim_SetResultString(interp, "didn't specify command to execute", -1);
789 badargs:
790 Jim_Free(arg_array);
791 return -1;
794 /* Must do this before vfork(), so do it now */
795 save_environ = JimSaveEnv(JimBuildEnv(interp));
798 * Set up the redirected input source for the pipeline, if
799 * so requested.
801 if (input != NULL) {
802 if (inputFile == FILE_TEXT) {
804 * Immediate data in command. Create temporary file and
805 * put data into file.
807 inputId = JimCreateTemp(interp, input, input_len);
808 if (inputId == JIM_BAD_FD) {
809 goto error;
812 else if (inputFile == FILE_HANDLE) {
813 /* Should be a file descriptor */
814 FILE *fh = JimGetAioFilehandle(interp, input);
816 if (fh == NULL) {
817 goto error;
819 inputId = JimDupFd(JimFileno(fh));
821 else {
823 * File redirection. Just open the file.
825 inputId = JimOpenForRead(input);
826 if (inputId == JIM_BAD_FD) {
827 Jim_SetResultFormatted(interp, "couldn't read file \"%s\": %s", input, JimStrError());
828 goto error;
832 else if (inPipePtr != NULL) {
833 if (JimPipe(pipeIds) != 0) {
834 Jim_SetResultErrno(interp, "couldn't create input pipe for command");
835 goto error;
837 inputId = pipeIds[0];
838 *inPipePtr = pipeIds[1];
839 pipeIds[0] = pipeIds[1] = JIM_BAD_FD;
843 * Set up the redirected output sink for the pipeline from one
844 * of two places, if requested.
846 if (output != NULL) {
847 if (outputFile == FILE_HANDLE) {
848 FILE *fh = JimGetAioFilehandle(interp, output);
849 if (fh == NULL) {
850 goto error;
852 fflush(fh);
853 lastOutputId = JimDupFd(JimFileno(fh));
855 else {
857 * Output is to go to a file.
859 lastOutputId = JimOpenForWrite(output, outputFile == FILE_APPEND);
860 if (lastOutputId == JIM_BAD_FD) {
861 Jim_SetResultFormatted(interp, "couldn't write file \"%s\": %s", output, JimStrError());
862 goto error;
866 else if (outPipePtr != NULL) {
868 * Output is to go to a pipe.
870 if (JimPipe(pipeIds) != 0) {
871 Jim_SetResultErrno(interp, "couldn't create output pipe");
872 goto error;
874 lastOutputId = pipeIds[1];
875 *outPipePtr = pipeIds[0];
876 pipeIds[0] = pipeIds[1] = JIM_BAD_FD;
878 /* If we are redirecting stderr with 2>filename or 2>@fileId, then we ignore errFilePtr */
879 if (error != NULL) {
880 if (errorFile == FILE_HANDLE) {
881 if (strcmp(error, "1") == 0) {
882 /* Special 2>@1 */
883 if (lastOutputId != JIM_BAD_FD) {
884 errorId = JimDupFd(lastOutputId);
886 else {
887 /* No redirection of stdout, so just use 2>@stdout */
888 error = "stdout";
891 if (errorId == JIM_BAD_FD) {
892 FILE *fh = JimGetAioFilehandle(interp, error);
893 if (fh == NULL) {
894 goto error;
896 fflush(fh);
897 errorId = JimDupFd(JimFileno(fh));
900 else {
902 * Output is to go to a file.
904 errorId = JimOpenForWrite(error, errorFile == FILE_APPEND);
905 if (errorId == JIM_BAD_FD) {
906 Jim_SetResultFormatted(interp, "couldn't write file \"%s\": %s", error, JimStrError());
907 goto error;
911 else if (errFilePtr != NULL) {
913 * Set up the standard error output sink for the pipeline, if
914 * requested. Use a temporary file which is opened, then deleted.
915 * Could potentially just use pipe, but if it filled up it could
916 * cause the pipeline to deadlock: we'd be waiting for processes
917 * to complete before reading stderr, and processes couldn't complete
918 * because stderr was backed up.
920 errorId = JimCreateTemp(interp, NULL, 0);
921 if (errorId == JIM_BAD_FD) {
922 goto error;
924 *errFilePtr = JimDupFd(errorId);
928 * Scan through the argc array, forking off a process for each
929 * group of arguments between "|" arguments.
932 pidPtr = Jim_Alloc(cmdCount * sizeof(*pidPtr));
933 for (i = 0; i < numPids; i++) {
934 pidPtr[i] = JIM_BAD_PID;
936 for (firstArg = 0; firstArg < arg_count; numPids++, firstArg = lastArg + 1) {
937 int pipe_dup_err = 0;
938 fdtype origErrorId = errorId;
940 for (lastArg = firstArg; lastArg < arg_count; lastArg++) {
941 if (arg_array[lastArg][0] == '|') {
942 if (arg_array[lastArg][1] == '&') {
943 pipe_dup_err = 1;
945 break;
948 /* Replace | with NULL for execv() */
949 arg_array[lastArg] = NULL;
950 if (lastArg == arg_count) {
951 outputId = lastOutputId;
953 else {
954 if (JimPipe(pipeIds) != 0) {
955 Jim_SetResultErrno(interp, "couldn't create pipe");
956 goto error;
958 outputId = pipeIds[1];
961 /* Need to do this befor vfork() */
962 if (pipe_dup_err) {
963 errorId = outputId;
966 /* Now fork the child */
968 #ifdef __MINGW32__
969 pid = JimStartWinProcess(interp, &arg_array[firstArg], save_environ ? save_environ[0] : NULL, inputId, outputId, errorId);
970 if (pid == JIM_BAD_PID) {
971 Jim_SetResultFormatted(interp, "couldn't exec \"%s\"", arg_array[firstArg]);
972 goto error;
974 #else
976 * Make a new process and enter it into the table if the fork
977 * is successful.
979 pid = vfork();
980 if (pid < 0) {
981 Jim_SetResultErrno(interp, "couldn't fork child process");
982 goto error;
984 if (pid == 0) {
985 /* Child */
987 if (inputId != -1) dup2(inputId, 0);
988 if (outputId != -1) dup2(outputId, 1);
989 if (errorId != -1) dup2(errorId, 2);
991 for (i = 3; (i <= outputId) || (i <= inputId) || (i <= errorId); i++) {
992 close(i);
995 /* Restore SIGPIPE behaviour */
996 (void)signal(SIGPIPE, SIG_DFL);
998 execvpe(arg_array[firstArg], &arg_array[firstArg], Jim_GetEnviron());
1000 /* Need to prep an error message before vfork(), just in case */
1001 fprintf(stderr, "couldn't exec \"%s\"\n", arg_array[firstArg]);
1002 _exit(127);
1004 #endif
1006 /* parent */
1009 * Enlarge the wait table if there isn't enough space for a new
1010 * entry.
1012 if (table->used == table->size) {
1013 table->size += WAIT_TABLE_GROW_BY;
1014 table->info = Jim_Realloc(table->info, table->size * sizeof(*table->info));
1017 table->info[table->used].pid = pid;
1018 table->info[table->used].flags = 0;
1019 table->used++;
1021 pidPtr[numPids] = pid;
1023 /* Restore in case of pipe_dup_err */
1024 errorId = origErrorId;
1027 * Close off our copies of file descriptors that were set up for
1028 * this child, then set up the input for the next child.
1031 if (inputId != JIM_BAD_FD) {
1032 JimCloseFd(inputId);
1034 if (outputId != JIM_BAD_FD) {
1035 JimCloseFd(outputId);
1037 inputId = pipeIds[0];
1038 pipeIds[0] = pipeIds[1] = JIM_BAD_FD;
1040 *pidArrayPtr = pidPtr;
1043 * All done. Cleanup open files lying around and then return.
1046 cleanup:
1047 if (inputId != JIM_BAD_FD) {
1048 JimCloseFd(inputId);
1050 if (lastOutputId != JIM_BAD_FD) {
1051 JimCloseFd(lastOutputId);
1053 if (errorId != JIM_BAD_FD) {
1054 JimCloseFd(errorId);
1056 Jim_Free(arg_array);
1058 JimRestoreEnv(save_environ);
1060 return numPids;
1063 * An error occurred. There could have been extra files open, such
1064 * as pipes between children. Clean them all up. Detach any child
1065 * processes that have been created.
1068 error:
1069 if ((inPipePtr != NULL) && (*inPipePtr != JIM_BAD_FD)) {
1070 JimCloseFd(*inPipePtr);
1071 *inPipePtr = JIM_BAD_FD;
1073 if ((outPipePtr != NULL) && (*outPipePtr != JIM_BAD_FD)) {
1074 JimCloseFd(*outPipePtr);
1075 *outPipePtr = JIM_BAD_FD;
1077 if ((errFilePtr != NULL) && (*errFilePtr != JIM_BAD_FD)) {
1078 JimCloseFd(*errFilePtr);
1079 *errFilePtr = JIM_BAD_FD;
1081 if (pipeIds[0] != JIM_BAD_FD) {
1082 JimCloseFd(pipeIds[0]);
1084 if (pipeIds[1] != JIM_BAD_FD) {
1085 JimCloseFd(pipeIds[1]);
1087 if (pidPtr != NULL) {
1088 for (i = 0; i < numPids; i++) {
1089 if (pidPtr[i] != JIM_BAD_PID) {
1090 JimDetachPids(interp, 1, &pidPtr[i]);
1093 Jim_Free(pidPtr);
1095 numPids = -1;
1096 goto cleanup;
1100 *----------------------------------------------------------------------
1102 * JimCleanupChildren --
1104 * This is a utility procedure used to wait for child processes
1105 * to exit, record information about abnormal exits, and then
1106 * collect any stderr output generated by them.
1108 * Results:
1109 * The return value is a standard Tcl result. If anything at
1110 * weird happened with the child processes, JIM_ERR is returned
1111 * and a message is left in interp->result.
1113 * Side effects:
1114 * pidPtr is freed
1116 *----------------------------------------------------------------------
1119 static int JimCleanupChildren(Jim_Interp *interp, int numPids, pidtype *pidPtr, int child_siginfo)
1121 struct WaitInfoTable *table = Jim_CmdPrivData(interp);
1122 int result = JIM_OK;
1123 int i;
1125 /* Now check the return status of each child */
1126 for (i = 0; i < numPids; i++) {
1127 int waitStatus = 0;
1128 if (JimWaitForProcess(table, pidPtr[i], &waitStatus) != JIM_BAD_PID) {
1129 if (JimCheckWaitStatus(interp, pidPtr[i], waitStatus, child_siginfo) != JIM_OK) {
1130 result = JIM_ERR;
1134 Jim_Free(pidPtr);
1136 return result;
1139 int Jim_execInit(Jim_Interp *interp)
1141 if (Jim_PackageProvide(interp, "exec", "1.0", JIM_ERRMSG))
1142 return JIM_ERR;
1144 #ifdef SIGPIPE
1146 * Disable SIGPIPE signals: if they were allowed, this process
1147 * might go away unexpectedly if children misbehave. This code
1148 * can potentially interfere with other application code that
1149 * expects to handle SIGPIPEs.
1151 * By doing this in the init function, applications can override
1152 * this later. Note that child processes have SIGPIPE restored
1153 * to the default after vfork().
1155 (void)signal(SIGPIPE, SIG_IGN);
1156 #endif
1158 Jim_CreateCommand(interp, "exec", Jim_ExecCmd, JimAllocWaitInfoTable(), JimFreeWaitInfoTable);
1159 return JIM_OK;
1162 #if defined(__MINGW32__)
1163 /* Windows-specific (mingw) implementation */
1165 static SECURITY_ATTRIBUTES *JimStdSecAttrs(void)
1167 static SECURITY_ATTRIBUTES secAtts;
1169 secAtts.nLength = sizeof(SECURITY_ATTRIBUTES);
1170 secAtts.lpSecurityDescriptor = NULL;
1171 secAtts.bInheritHandle = TRUE;
1172 return &secAtts;
1175 static int JimErrno(void)
1177 switch (GetLastError()) {
1178 case ERROR_FILE_NOT_FOUND: return ENOENT;
1179 case ERROR_PATH_NOT_FOUND: return ENOENT;
1180 case ERROR_TOO_MANY_OPEN_FILES: return EMFILE;
1181 case ERROR_ACCESS_DENIED: return EACCES;
1182 case ERROR_INVALID_HANDLE: return EBADF;
1183 case ERROR_BAD_ENVIRONMENT: return E2BIG;
1184 case ERROR_BAD_FORMAT: return ENOEXEC;
1185 case ERROR_INVALID_ACCESS: return EACCES;
1186 case ERROR_INVALID_DRIVE: return ENOENT;
1187 case ERROR_CURRENT_DIRECTORY: return EACCES;
1188 case ERROR_NOT_SAME_DEVICE: return EXDEV;
1189 case ERROR_NO_MORE_FILES: return ENOENT;
1190 case ERROR_WRITE_PROTECT: return EROFS;
1191 case ERROR_BAD_UNIT: return ENXIO;
1192 case ERROR_NOT_READY: return EBUSY;
1193 case ERROR_BAD_COMMAND: return EIO;
1194 case ERROR_CRC: return EIO;
1195 case ERROR_BAD_LENGTH: return EIO;
1196 case ERROR_SEEK: return EIO;
1197 case ERROR_WRITE_FAULT: return EIO;
1198 case ERROR_READ_FAULT: return EIO;
1199 case ERROR_GEN_FAILURE: return EIO;
1200 case ERROR_SHARING_VIOLATION: return EACCES;
1201 case ERROR_LOCK_VIOLATION: return EACCES;
1202 case ERROR_SHARING_BUFFER_EXCEEDED: return ENFILE;
1203 case ERROR_HANDLE_DISK_FULL: return ENOSPC;
1204 case ERROR_NOT_SUPPORTED: return ENODEV;
1205 case ERROR_REM_NOT_LIST: return EBUSY;
1206 case ERROR_DUP_NAME: return EEXIST;
1207 case ERROR_BAD_NETPATH: return ENOENT;
1208 case ERROR_NETWORK_BUSY: return EBUSY;
1209 case ERROR_DEV_NOT_EXIST: return ENODEV;
1210 case ERROR_TOO_MANY_CMDS: return EAGAIN;
1211 case ERROR_ADAP_HDW_ERR: return EIO;
1212 case ERROR_BAD_NET_RESP: return EIO;
1213 case ERROR_UNEXP_NET_ERR: return EIO;
1214 case ERROR_NETNAME_DELETED: return ENOENT;
1215 case ERROR_NETWORK_ACCESS_DENIED: return EACCES;
1216 case ERROR_BAD_DEV_TYPE: return ENODEV;
1217 case ERROR_BAD_NET_NAME: return ENOENT;
1218 case ERROR_TOO_MANY_NAMES: return ENFILE;
1219 case ERROR_TOO_MANY_SESS: return EIO;
1220 case ERROR_SHARING_PAUSED: return EAGAIN;
1221 case ERROR_REDIR_PAUSED: return EAGAIN;
1222 case ERROR_FILE_EXISTS: return EEXIST;
1223 case ERROR_CANNOT_MAKE: return ENOSPC;
1224 case ERROR_OUT_OF_STRUCTURES: return ENFILE;
1225 case ERROR_ALREADY_ASSIGNED: return EEXIST;
1226 case ERROR_INVALID_PASSWORD: return EPERM;
1227 case ERROR_NET_WRITE_FAULT: return EIO;
1228 case ERROR_NO_PROC_SLOTS: return EAGAIN;
1229 case ERROR_DISK_CHANGE: return EXDEV;
1230 case ERROR_BROKEN_PIPE: return EPIPE;
1231 case ERROR_OPEN_FAILED: return ENOENT;
1232 case ERROR_DISK_FULL: return ENOSPC;
1233 case ERROR_NO_MORE_SEARCH_HANDLES: return EMFILE;
1234 case ERROR_INVALID_TARGET_HANDLE: return EBADF;
1235 case ERROR_INVALID_NAME: return ENOENT;
1236 case ERROR_PROC_NOT_FOUND: return ESRCH;
1237 case ERROR_WAIT_NO_CHILDREN: return ECHILD;
1238 case ERROR_CHILD_NOT_COMPLETE: return ECHILD;
1239 case ERROR_DIRECT_ACCESS_HANDLE: return EBADF;
1240 case ERROR_SEEK_ON_DEVICE: return ESPIPE;
1241 case ERROR_BUSY_DRIVE: return EAGAIN;
1242 case ERROR_DIR_NOT_EMPTY: return EEXIST;
1243 case ERROR_NOT_LOCKED: return EACCES;
1244 case ERROR_BAD_PATHNAME: return ENOENT;
1245 case ERROR_LOCK_FAILED: return EACCES;
1246 case ERROR_ALREADY_EXISTS: return EEXIST;
1247 case ERROR_FILENAME_EXCED_RANGE: return ENAMETOOLONG;
1248 case ERROR_BAD_PIPE: return EPIPE;
1249 case ERROR_PIPE_BUSY: return EAGAIN;
1250 case ERROR_PIPE_NOT_CONNECTED: return EPIPE;
1251 case ERROR_DIRECTORY: return ENOTDIR;
1253 return EINVAL;
1256 static int JimPipe(fdtype pipefd[2])
1258 if (CreatePipe(&pipefd[0], &pipefd[1], NULL, 0)) {
1259 return 0;
1261 return -1;
1264 static fdtype JimDupFd(fdtype infd)
1266 fdtype dupfd;
1267 pidtype pid = GetCurrentProcess();
1269 if (DuplicateHandle(pid, infd, pid, &dupfd, 0, TRUE, DUPLICATE_SAME_ACCESS)) {
1270 return dupfd;
1272 return JIM_BAD_FD;
1275 static int JimRewindFd(fdtype fd)
1277 return SetFilePointer(fd, 0, NULL, FILE_BEGIN) == INVALID_SET_FILE_POINTER ? -1 : 0;
1280 #if 0
1281 static int JimReadFd(fdtype fd, char *buffer, size_t len)
1283 DWORD num;
1285 if (ReadFile(fd, buffer, len, &num, NULL)) {
1286 return num;
1288 if (GetLastError() == ERROR_HANDLE_EOF || GetLastError() == ERROR_BROKEN_PIPE) {
1289 return 0;
1291 return -1;
1293 #endif
1295 static FILE *JimFdOpenForRead(fdtype fd)
1297 return _fdopen(_open_osfhandle((int)fd, _O_RDONLY | _O_TEXT), "r");
1300 static fdtype JimFileno(FILE *fh)
1302 return (fdtype)_get_osfhandle(_fileno(fh));
1305 static fdtype JimOpenForRead(const char *filename)
1307 return CreateFile(filename, GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE,
1308 JimStdSecAttrs(), OPEN_EXISTING, 0, NULL);
1311 static fdtype JimOpenForWrite(const char *filename, int append)
1313 return CreateFile(filename, append ? FILE_APPEND_DATA : GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE,
1314 JimStdSecAttrs(), append ? OPEN_ALWAYS : CREATE_ALWAYS, 0, (HANDLE) NULL);
1317 static FILE *JimFdOpenForWrite(fdtype fd)
1319 return _fdopen(_open_osfhandle((int)fd, _O_TEXT), "w");
1322 static pidtype JimWaitPid(pidtype pid, int *status, int nohang)
1324 DWORD ret = WaitForSingleObject(pid, nohang ? 0 : INFINITE);
1325 if (ret == WAIT_TIMEOUT || ret == WAIT_FAILED) {
1326 /* WAIT_TIMEOUT can only happend with WNOHANG */
1327 return JIM_BAD_PID;
1329 GetExitCodeProcess(pid, &ret);
1330 *status = ret;
1331 CloseHandle(pid);
1332 return pid;
1335 static HANDLE JimCreateTemp(Jim_Interp *interp, const char *contents, int len)
1337 char name[MAX_PATH];
1338 HANDLE handle;
1340 if (!GetTempPath(MAX_PATH, name) || !GetTempFileName(name, "JIM", 0, name)) {
1341 return JIM_BAD_FD;
1344 handle = CreateFile(name, GENERIC_READ | GENERIC_WRITE, 0, JimStdSecAttrs(),
1345 CREATE_ALWAYS, FILE_ATTRIBUTE_TEMPORARY | FILE_FLAG_DELETE_ON_CLOSE,
1346 NULL);
1348 if (handle == INVALID_HANDLE_VALUE) {
1349 goto error;
1352 if (contents != NULL) {
1353 /* Use fdopen() to get automatic text-mode translation */
1354 FILE *fh = JimFdOpenForWrite(JimDupFd(handle));
1355 if (fh == NULL) {
1356 goto error;
1359 if (fwrite(contents, len, 1, fh) != 1) {
1360 fclose(fh);
1361 goto error;
1363 fseek(fh, 0, SEEK_SET);
1364 fclose(fh);
1366 return handle;
1368 error:
1369 Jim_SetResultErrno(interp, "failed to create temp file");
1370 CloseHandle(handle);
1371 DeleteFile(name);
1372 return JIM_BAD_FD;
1375 static int
1376 JimWinFindExecutable(const char *originalName, char fullPath[MAX_PATH])
1378 int i;
1379 static char extensions[][5] = {".exe", "", ".bat"};
1381 for (i = 0; i < (int) (sizeof(extensions) / sizeof(extensions[0])); i++) {
1382 lstrcpyn(fullPath, originalName, MAX_PATH - 5);
1383 lstrcat(fullPath, extensions[i]);
1385 if (SearchPath(NULL, fullPath, NULL, MAX_PATH, fullPath, NULL) == 0) {
1386 continue;
1388 if (GetFileAttributes(fullPath) & FILE_ATTRIBUTE_DIRECTORY) {
1389 continue;
1391 return 0;
1394 return -1;
1397 static char **JimSaveEnv(char **env)
1399 return env;
1402 static void JimRestoreEnv(char **env)
1404 JimFreeEnv(env, Jim_GetEnviron());
1407 static Jim_Obj *
1408 JimWinBuildCommandLine(Jim_Interp *interp, char **argv)
1410 char *start, *special;
1411 int quote, i;
1413 Jim_Obj *strObj = Jim_NewStringObj(interp, "", 0);
1415 for (i = 0; argv[i]; i++) {
1416 if (i > 0) {
1417 Jim_AppendString(interp, strObj, " ", 1);
1420 if (argv[i][0] == '\0') {
1421 quote = 1;
1423 else {
1424 quote = 0;
1425 for (start = argv[i]; *start != '\0'; start++) {
1426 if (isspace(UCHAR(*start))) {
1427 quote = 1;
1428 break;
1432 if (quote) {
1433 Jim_AppendString(interp, strObj, "\"" , 1);
1436 start = argv[i];
1437 for (special = argv[i]; ; ) {
1438 if ((*special == '\\') && (special[1] == '\\' ||
1439 special[1] == '"' || (quote && special[1] == '\0'))) {
1440 Jim_AppendString(interp, strObj, start, special - start);
1441 start = special;
1442 while (1) {
1443 special++;
1444 if (*special == '"' || (quote && *special == '\0')) {
1446 * N backslashes followed a quote -> insert
1447 * N * 2 + 1 backslashes then a quote.
1450 Jim_AppendString(interp, strObj, start, special - start);
1451 break;
1453 if (*special != '\\') {
1454 break;
1457 Jim_AppendString(interp, strObj, start, special - start);
1458 start = special;
1460 if (*special == '"') {
1461 if (special == start) {
1462 Jim_AppendString(interp, strObj, "\"", 1);
1464 else {
1465 Jim_AppendString(interp, strObj, start, special - start);
1467 Jim_AppendString(interp, strObj, "\\\"", 2);
1468 start = special + 1;
1470 if (*special == '\0') {
1471 break;
1473 special++;
1475 Jim_AppendString(interp, strObj, start, special - start);
1476 if (quote) {
1477 Jim_AppendString(interp, strObj, "\"", 1);
1480 return strObj;
1483 static pidtype
1484 JimStartWinProcess(Jim_Interp *interp, char **argv, char *env, fdtype inputId, fdtype outputId, fdtype errorId)
1486 STARTUPINFO startInfo;
1487 PROCESS_INFORMATION procInfo;
1488 HANDLE hProcess, h;
1489 char execPath[MAX_PATH];
1490 pidtype pid = JIM_BAD_PID;
1491 Jim_Obj *cmdLineObj;
1493 if (JimWinFindExecutable(argv[0], execPath) < 0) {
1494 return JIM_BAD_PID;
1496 argv[0] = execPath;
1498 hProcess = GetCurrentProcess();
1499 cmdLineObj = JimWinBuildCommandLine(interp, argv);
1502 * STARTF_USESTDHANDLES must be used to pass handles to child process.
1503 * Using SetStdHandle() and/or dup2() only works when a console mode
1504 * parent process is spawning an attached console mode child process.
1507 ZeroMemory(&startInfo, sizeof(startInfo));
1508 startInfo.cb = sizeof(startInfo);
1509 startInfo.dwFlags = STARTF_USESTDHANDLES;
1510 startInfo.hStdInput = INVALID_HANDLE_VALUE;
1511 startInfo.hStdOutput= INVALID_HANDLE_VALUE;
1512 startInfo.hStdError = INVALID_HANDLE_VALUE;
1515 * Duplicate all the handles which will be passed off as stdin, stdout
1516 * and stderr of the child process. The duplicate handles are set to
1517 * be inheritable, so the child process can use them.
1519 if (inputId == JIM_BAD_FD) {
1520 if (CreatePipe(&startInfo.hStdInput, &h, JimStdSecAttrs(), 0) != FALSE) {
1521 CloseHandle(h);
1523 } else {
1524 DuplicateHandle(hProcess, inputId, hProcess, &startInfo.hStdInput,
1525 0, TRUE, DUPLICATE_SAME_ACCESS);
1527 if (startInfo.hStdInput == JIM_BAD_FD) {
1528 goto end;
1531 if (outputId == JIM_BAD_FD) {
1532 startInfo.hStdOutput = CreateFile("NUL:", GENERIC_WRITE, 0,
1533 JimStdSecAttrs(), OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
1534 } else {
1535 DuplicateHandle(hProcess, outputId, hProcess, &startInfo.hStdOutput,
1536 0, TRUE, DUPLICATE_SAME_ACCESS);
1538 if (startInfo.hStdOutput == JIM_BAD_FD) {
1539 goto end;
1542 if (errorId == JIM_BAD_FD) {
1544 * If handle was not set, errors should be sent to an infinitely
1545 * deep sink.
1548 startInfo.hStdError = CreateFile("NUL:", GENERIC_WRITE, 0,
1549 JimStdSecAttrs(), OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
1550 } else {
1551 DuplicateHandle(hProcess, errorId, hProcess, &startInfo.hStdError,
1552 0, TRUE, DUPLICATE_SAME_ACCESS);
1554 if (startInfo.hStdError == JIM_BAD_FD) {
1555 goto end;
1558 if (!CreateProcess(NULL, (char *)Jim_String(cmdLineObj), NULL, NULL, TRUE,
1559 0, env, NULL, &startInfo, &procInfo)) {
1560 goto end;
1564 * "When an application spawns a process repeatedly, a new thread
1565 * instance will be created for each process but the previous
1566 * instances may not be cleaned up. This results in a significant
1567 * virtual memory loss each time the process is spawned. If there
1568 * is a WaitForInputIdle() call between CreateProcess() and
1569 * CloseHandle(), the problem does not occur." PSS ID Number: Q124121
1572 WaitForInputIdle(procInfo.hProcess, 5000);
1573 CloseHandle(procInfo.hThread);
1575 pid = procInfo.hProcess;
1577 end:
1578 Jim_FreeNewObj(interp, cmdLineObj);
1579 if (startInfo.hStdInput != JIM_BAD_FD) {
1580 CloseHandle(startInfo.hStdInput);
1582 if (startInfo.hStdOutput != JIM_BAD_FD) {
1583 CloseHandle(startInfo.hStdOutput);
1585 if (startInfo.hStdError != JIM_BAD_FD) {
1586 CloseHandle(startInfo.hStdError);
1588 return pid;
1590 #else
1591 /* Unix-specific implementation */
1592 static int JimOpenForWrite(const char *filename, int append)
1594 return open(filename, O_WRONLY | O_CREAT | (append ? O_APPEND : O_TRUNC), 0666);
1597 static int JimRewindFd(int fd)
1599 return lseek(fd, 0L, SEEK_SET);
1602 static int JimCreateTemp(Jim_Interp *interp, const char *contents, int len)
1604 int fd = Jim_MakeTempFile(interp, NULL);
1606 if (fd != JIM_BAD_FD) {
1607 unlink(Jim_String(Jim_GetResult(interp)));
1608 if (contents) {
1609 if (write(fd, contents, len) != len) {
1610 Jim_SetResultErrno(interp, "couldn't write temp file");
1611 close(fd);
1612 return -1;
1614 lseek(fd, 0L, SEEK_SET);
1617 return fd;
1620 static char **JimSaveEnv(char **env)
1622 char **saveenv = Jim_GetEnviron();
1623 Jim_SetEnviron(env);
1624 return saveenv;
1627 static void JimRestoreEnv(char **env)
1629 JimFreeEnv(Jim_GetEnviron(), env);
1630 Jim_SetEnviron(env);
1632 #endif
1633 #endif