Add pkg-config support: jimtcl.pc
[jimtcl.git] / jim-exec.c
blob9059ca2a00624f3615a5b1bf9e6aaebbfd0d5a07
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 #ifndef _GNU_SOURCE
24 #define _GNU_SOURCE
25 #endif
26 #include <string.h>
27 #include <ctype.h>
29 #include "jimautoconf.h"
30 #include <jim.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);
41 int i, j;
42 int rc;
44 /* Create a quoted command line */
45 for (i = 1; i < argc; i++) {
46 int len;
47 const char *arg = Jim_GetString(argv[i], &len);
49 if (i > 1) {
50 Jim_AppendString(interp, cmdlineObj, " ", 1);
52 if (strpbrk(arg, "\\\" ") == NULL) {
53 /* No quoting required */
54 Jim_AppendString(interp, cmdlineObj, arg, len);
55 continue;
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);
71 if (rc) {
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);
77 return JIM_ERR;
80 return JIM_OK;
83 int Jim_execInit(Jim_Interp *interp)
85 if (Jim_PackageProvide(interp, "exec", "1.0", JIM_ERRMSG))
86 return JIM_ERR;
88 Jim_CreateCommand(interp, "exec", Jim_ExecCmd, NULL, NULL);
89 return JIM_OK;
91 #else
92 /* Full exec implementation for unix and mingw */
94 #include <errno.h>
95 #include <signal.h>
97 #if defined(__MINGW32__)
98 /* XXX: Should we use this implementation for cygwin too? msvc? */
99 #ifndef STRICT
100 #define STRICT
101 #endif
102 #define WIN32_LEAN_AND_MEAN
103 #include <windows.h>
104 #include <fcntl.h>
106 typedef HANDLE fdtype;
107 typedef HANDLE pidtype;
108 #define JIM_BAD_FD INVALID_HANDLE_VALUE
109 #define JIM_BAD_PID INVALID_HANDLE_VALUE
110 #define JimCloseFd CloseHandle
112 #define WIFEXITED(STATUS) 1
113 #define WEXITSTATUS(STATUS) (STATUS)
114 #define WIFSIGNALED(STATUS) 0
115 #define WTERMSIG(STATUS) 0
116 #define WNOHANG 1
118 static fdtype JimFileno(FILE *fh);
119 static pidtype JimWaitPid(pidtype pid, int *status, int nohang);
120 static fdtype JimDupFd(fdtype infd);
121 static fdtype JimOpenForRead(const char *filename);
122 static FILE *JimFdOpenForRead(fdtype fd);
123 static int JimPipe(fdtype pipefd[2]);
124 static pidtype JimStartWinProcess(Jim_Interp *interp, char **argv, char **env,
125 fdtype inputId, fdtype outputId, fdtype errorId);
126 static int JimErrno(void);
127 #else
128 #include "jim-signal.h"
129 #include <unistd.h>
130 #include <fcntl.h>
131 #include <sys/wait.h>
132 #include <sys/stat.h>
134 typedef int fdtype;
135 typedef int pidtype;
136 #define JimPipe pipe
137 #define JimErrno() errno
138 #define JIM_BAD_FD -1
139 #define JIM_BAD_PID -1
140 #define JimFileno fileno
141 #define JimReadFd read
142 #define JimCloseFd close
143 #define JimWaitPid waitpid
144 #define JimDupFd dup
145 #define JimFdOpenForRead(FD) fdopen((FD), "r")
146 #define JimOpenForRead(NAME) open((NAME), O_RDONLY, 0)
148 #ifndef HAVE_EXECVPE
149 #define execvpe(ARG0, ARGV, ENV) execvp(ARG0, ARGV)
150 #endif
151 #endif
153 static const char *JimStrError(void);
154 static char **JimOriginalEnviron(void);
155 static char **JimSaveEnv(char **env);
156 static void JimRestoreEnv(char **env);
157 static int JimCreatePipeline(Jim_Interp *interp, int argc, Jim_Obj *const *argv,
158 pidtype **pidArrayPtr, fdtype *inPipePtr, fdtype *outPipePtr, fdtype *errFilePtr);
159 static void JimDetachPids(Jim_Interp *interp, int numPids, const pidtype *pidPtr);
160 static int JimCleanupChildren(Jim_Interp *interp, int numPids, pidtype *pidPtr, Jim_Obj *errStrObj);
161 static fdtype JimCreateTemp(Jim_Interp *interp, const char *contents, int len);
162 static fdtype JimOpenForWrite(const char *filename, int append);
163 static int JimRewindFd(fdtype fd);
165 static void Jim_SetResultErrno(Jim_Interp *interp, const char *msg)
167 Jim_SetResultFormatted(interp, "%s: %s", msg, JimStrError());
170 static const char *JimStrError(void)
172 return strerror(JimErrno());
176 * If the last character of 'objPtr' is a newline, then remove
177 * the newline character.
179 static void Jim_RemoveTrailingNewline(Jim_Obj *objPtr)
181 int len;
182 const char *s = Jim_GetString(objPtr, &len);
184 if (len > 0 && s[len - 1] == '\n') {
185 objPtr->length--;
186 objPtr->bytes[objPtr->length] = '\0';
191 * Read from 'fd', append the data to strObj and close 'fd'.
192 * Returns 1 if data was added, 0 if not, or -1 on error.
194 static int JimAppendStreamToString(Jim_Interp *interp, fdtype fd, Jim_Obj *strObj)
196 char buf[256];
197 FILE *fh = JimFdOpenForRead(fd);
198 int ret = 0;
200 if (fh == NULL) {
201 return -1;
204 while (1) {
205 int retval = fread(buf, 1, sizeof(buf), fh);
206 if (retval > 0) {
207 ret = 1;
208 Jim_AppendString(interp, strObj, buf, retval);
210 if (retval != sizeof(buf)) {
211 break;
214 fclose(fh);
215 return ret;
219 * Builds the environment array from $::env
221 * If $::env is not set, simply returns environ.
223 * Otherwise allocates the environ array from the contents of $::env
225 * If the exec fails, memory can be freed via JimFreeEnv()
227 static char **JimBuildEnv(Jim_Interp *interp)
229 int i;
230 int size;
231 int num;
232 int n;
233 char **envptr;
234 char *envdata;
236 Jim_Obj *objPtr = Jim_GetGlobalVariableStr(interp, "env", JIM_NONE);
238 if (!objPtr) {
239 return JimOriginalEnviron();
242 /* We build the array as a single block consisting of the pointers followed by
243 * the strings. This has the advantage of being easy to allocate/free and being
244 * compatible with both unix and windows
247 /* Calculate the required size */
248 num = Jim_ListLength(interp, objPtr);
249 if (num % 2) {
250 /* Silently drop the last element if not a valid dictionary */
251 num--;
253 /* We need one \0 and one equal sign for each element.
254 * A list has at least one space for each element except the first.
255 * We need one extra char for the extra null terminator and one for the equal sign.
257 size = Jim_Length(objPtr) + 2;
259 envptr = Jim_Alloc(sizeof(*envptr) * (num / 2 + 1) + size);
260 envdata = (char *)&envptr[num / 2 + 1];
262 n = 0;
263 for (i = 0; i < num; i += 2) {
264 const char *s1, *s2;
265 Jim_Obj *elemObj;
267 Jim_ListIndex(interp, objPtr, i, &elemObj, JIM_NONE);
268 s1 = Jim_String(elemObj);
269 Jim_ListIndex(interp, objPtr, i + 1, &elemObj, JIM_NONE);
270 s2 = Jim_String(elemObj);
272 envptr[n] = envdata;
273 envdata += sprintf(envdata, "%s=%s", s1, s2);
274 envdata++;
275 n++;
277 envptr[n] = NULL;
278 *envdata = 0;
280 return envptr;
284 * Frees the environment allocated by JimBuildEnv()
286 * Must pass original_environ.
288 static void JimFreeEnv(char **env, char **original_environ)
290 if (env != original_environ) {
291 Jim_Free(env);
295 #ifndef jim_ext_signal
296 /* Implement trivial Jim_SignalId() and Jim_SignalName(), just good enough for JimCheckWaitStatus() */
297 const char *Jim_SignalId(int sig)
299 static char buf[10];
300 snprintf(buf, sizeof(buf), "%d", sig);
301 return buf;
304 const char *Jim_SignalName(int sig)
306 return Jim_SignalId(sig);
308 #endif
311 * Create and store an appropriate value for the global variable $::errorCode
312 * Based on pid and waitStatus.
314 * Returns JIM_OK for a normal exit with code 0, otherwise returns JIM_ERR.
316 * Note that $::errorCode is left unchanged for a normal exit.
317 * Details of any abnormal exit is appended to the errStrObj, unless it is NULL.
319 static int JimCheckWaitStatus(Jim_Interp *interp, pidtype pid, int waitStatus, Jim_Obj *errStrObj)
321 Jim_Obj *errorCode;
323 if (WIFEXITED(waitStatus) && WEXITSTATUS(waitStatus) == 0) {
324 return JIM_OK;
326 errorCode = Jim_NewListObj(interp, NULL, 0);
328 if (WIFEXITED(waitStatus)) {
329 Jim_ListAppendElement(interp, errorCode, Jim_NewStringObj(interp, "CHILDSTATUS", -1));
330 Jim_ListAppendElement(interp, errorCode, Jim_NewIntObj(interp, (long)pid));
331 Jim_ListAppendElement(interp, errorCode, Jim_NewIntObj(interp, WEXITSTATUS(waitStatus)));
333 else {
334 const char *type;
335 const char *action;
337 if (WIFSIGNALED(waitStatus)) {
338 type = "CHILDKILLED";
339 action = "killed";
341 else {
342 type = "CHILDSUSP";
343 action = "suspended";
346 Jim_ListAppendElement(interp, errorCode, Jim_NewStringObj(interp, type, -1));
348 if (errStrObj) {
349 /* Append the message to 'errStrObj' with a newline.
350 * The last newline will be stripped later
352 Jim_AppendStrings(interp, errStrObj, "child ", action, " by signal ", Jim_SignalId(WTERMSIG(waitStatus)), "\n", NULL);
355 Jim_ListAppendElement(interp, errorCode, Jim_NewIntObj(interp, (long)pid));
356 Jim_ListAppendElement(interp, errorCode, Jim_NewStringObj(interp, Jim_SignalId(WTERMSIG(waitStatus)), -1));
357 Jim_ListAppendElement(interp, errorCode, Jim_NewStringObj(interp, Jim_SignalName(WTERMSIG(waitStatus)), -1));
359 Jim_SetGlobalVariableStr(interp, "errorCode", errorCode);
361 return JIM_ERR;
365 * Data structures of the following type are used by JimFork and
366 * JimWaitPids to keep track of child processes.
369 struct WaitInfo
371 pidtype pid; /* Process id of child. */
372 int status; /* Status returned when child exited or suspended. */
373 int flags; /* Various flag bits; see below for definitions. */
376 struct WaitInfoTable {
377 struct WaitInfo *info; /* Table of outstanding processes */
378 int size; /* Size of the allocated table */
379 int used; /* Number of entries in use */
383 * Flag bits in WaitInfo structures:
385 * WI_DETACHED - Non-zero means no-one cares about the
386 * process anymore. Ignore it until it
387 * exits, then forget about it.
390 #define WI_DETACHED 2
392 #define WAIT_TABLE_GROW_BY 4
394 static void JimFreeWaitInfoTable(struct Jim_Interp *interp, void *privData)
396 struct WaitInfoTable *table = privData;
398 Jim_Free(table->info);
399 Jim_Free(table);
402 static struct WaitInfoTable *JimAllocWaitInfoTable(void)
404 struct WaitInfoTable *table = Jim_Alloc(sizeof(*table));
405 table->info = NULL;
406 table->size = table->used = 0;
408 return table;
412 * The main [exec] command
414 static int Jim_ExecCmd(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
416 fdtype outputId; /* File id for output pipe. -1 means command overrode. */
417 fdtype errorId; /* File id for temporary file containing error output. */
418 pidtype *pidPtr;
419 int numPids, result;
420 int child_siginfo = 1;
421 Jim_Obj *childErrObj;
422 Jim_Obj *errStrObj;
425 * See if the command is to be run in the background; if so, create
426 * the command, detach it, and return.
428 if (argc > 1 && Jim_CompareStringImmediate(interp, argv[argc - 1], "&")) {
429 Jim_Obj *listObj;
430 int i;
432 argc--;
433 numPids = JimCreatePipeline(interp, argc - 1, argv + 1, &pidPtr, NULL, NULL, NULL);
434 if (numPids < 0) {
435 return JIM_ERR;
437 /* The return value is a list of the pids */
438 listObj = Jim_NewListObj(interp, NULL, 0);
439 for (i = 0; i < numPids; i++) {
440 Jim_ListAppendElement(interp, listObj, Jim_NewIntObj(interp, (long)pidPtr[i]));
442 Jim_SetResult(interp, listObj);
443 JimDetachPids(interp, numPids, pidPtr);
444 Jim_Free(pidPtr);
445 return JIM_OK;
449 * Create the command's pipeline.
451 numPids =
452 JimCreatePipeline(interp, argc - 1, argv + 1, &pidPtr, NULL, &outputId, &errorId);
454 if (numPids < 0) {
455 return JIM_ERR;
458 result = JIM_OK;
460 errStrObj = Jim_NewStringObj(interp, "", 0);
462 /* Read from the output pipe until EOF */
463 if (outputId != JIM_BAD_FD) {
464 if (JimAppendStreamToString(interp, outputId, errStrObj) < 0) {
465 result = JIM_ERR;
466 Jim_SetResultErrno(interp, "error reading from output pipe");
470 /* Now wait for children to finish. Any abnormal results are appended to childErrObj */
471 childErrObj = Jim_NewStringObj(interp, "", 0);
472 Jim_IncrRefCount(childErrObj);
474 if (JimCleanupChildren(interp, numPids, pidPtr, childErrObj) != JIM_OK) {
475 result = JIM_ERR;
479 * Read the child's error output (if any) and put it into the result.
481 * Note that unlike Tcl, the presence of stderr output does not cause
482 * exec to return an error.
484 if (errorId != JIM_BAD_FD) {
485 int ret;
486 JimRewindFd(errorId);
487 ret = JimAppendStreamToString(interp, errorId, errStrObj);
488 if (ret < 0) {
489 Jim_SetResultErrno(interp, "error reading from error pipe");
490 result = JIM_ERR;
492 else if (ret > 0) {
493 /* Got some error output, so discard the abnormal info string */
494 child_siginfo = 0;
498 if (child_siginfo) {
499 /* Append the child siginfo to the result */
500 Jim_AppendObj(interp, errStrObj, childErrObj);
502 Jim_DecrRefCount(interp, childErrObj);
504 /* Finally remove any trailing newline from the result */
505 Jim_RemoveTrailingNewline(errStrObj);
507 /* Set this as the result */
508 Jim_SetResult(interp, errStrObj);
510 return result;
513 static void JimReapDetachedPids(struct WaitInfoTable *table)
515 struct WaitInfo *waitPtr;
516 int count;
517 int dest;
519 if (!table) {
520 return;
523 waitPtr = table->info;
524 dest = 0;
525 for (count = table->used; count > 0; waitPtr++, count--) {
526 if (waitPtr->flags & WI_DETACHED) {
527 int status;
528 pidtype pid = JimWaitPid(waitPtr->pid, &status, WNOHANG);
529 if (pid == waitPtr->pid) {
530 /* Process has exited, so remove it from the table */
531 table->used--;
532 continue;
535 if (waitPtr != &table->info[dest]) {
536 table->info[dest] = *waitPtr;
538 dest++;
543 * Does waitpid() on the given pid, and then removes the
544 * entry from the wait table.
546 * Returns the pid if OK and updates *statusPtr with the status,
547 * or JIM_BAD_PID if the pid was not in the table.
549 static pidtype JimWaitForProcess(struct WaitInfoTable *table, pidtype pid, int *statusPtr)
551 int i;
553 /* Find it in the table */
554 for (i = 0; i < table->used; i++) {
555 if (pid == table->info[i].pid) {
556 /* wait for it */
557 JimWaitPid(pid, statusPtr, 0);
559 /* Remove it from the table */
560 if (i != table->used - 1) {
561 table->info[i] = table->info[table->used - 1];
563 table->used--;
564 return pid;
568 /* Not found */
569 return JIM_BAD_PID;
573 * Indicates that one or more child processes have been placed in
574 * background and are no longer cared about.
575 * These children can be cleaned up with JimReapDetachedPids().
577 static void JimDetachPids(Jim_Interp *interp, int numPids, const pidtype *pidPtr)
579 int j;
580 struct WaitInfoTable *table = Jim_CmdPrivData(interp);
582 for (j = 0; j < numPids; j++) {
583 /* Find it in the table */
584 int i;
585 for (i = 0; i < table->used; i++) {
586 if (pidPtr[j] == table->info[i].pid) {
587 table->info[i].flags |= WI_DETACHED;
588 break;
594 static FILE *JimGetAioFilehandle(Jim_Interp *interp, const char *name)
596 FILE *fh;
597 Jim_Obj *fhObj;
599 fhObj = Jim_NewStringObj(interp, name, -1);
600 Jim_IncrRefCount(fhObj);
601 fh = Jim_AioFilehandle(interp, fhObj);
602 Jim_DecrRefCount(interp, fhObj);
604 return fh;
608 *----------------------------------------------------------------------
610 * JimCreatePipeline --
612 * Given an argc/argv array, instantiate a pipeline of processes
613 * as described by the argv.
615 * Results:
616 * The return value is a count of the number of new processes
617 * created, or -1 if an error occurred while creating the pipeline.
618 * *pidArrayPtr is filled in with the address of a dynamically
619 * allocated array giving the ids of all of the processes. It
620 * is up to the caller to free this array when it isn't needed
621 * anymore. If inPipePtr is non-NULL, *inPipePtr is filled in
622 * with the file id for the input pipe for the pipeline (if any):
623 * the caller must eventually close this file. If outPipePtr
624 * isn't NULL, then *outPipePtr is filled in with the file id
625 * for the output pipe from the pipeline: the caller must close
626 * this file. If errFilePtr isn't NULL, then *errFilePtr is filled
627 * with a file id that may be used to read error output after the
628 * pipeline completes.
630 * Side effects:
631 * Processes and pipes are created.
633 *----------------------------------------------------------------------
635 static int
636 JimCreatePipeline(Jim_Interp *interp, int argc, Jim_Obj *const *argv, pidtype **pidArrayPtr,
637 fdtype *inPipePtr, fdtype *outPipePtr, fdtype *errFilePtr)
639 pidtype *pidPtr = NULL; /* Points to malloc-ed array holding all
640 * the pids of child processes. */
641 int numPids = 0; /* Actual number of processes that exist
642 * at *pidPtr right now. */
643 int cmdCount; /* Count of number of distinct commands
644 * found in argc/argv. */
645 const char *input = NULL; /* Describes input for pipeline, depending
646 * on "inputFile". NULL means take input
647 * from stdin/pipe. */
648 int input_len = 0; /* Length of input, if relevant */
650 #define FILE_NAME 0 /* input/output: filename */
651 #define FILE_APPEND 1 /* output only: filename, append */
652 #define FILE_HANDLE 2 /* input/output: filehandle */
653 #define FILE_TEXT 3 /* input only: input is actual text */
655 int inputFile = FILE_NAME; /* 1 means input is name of input file.
656 * 2 means input is filehandle name.
657 * 0 means input holds actual
658 * text to be input to command. */
660 int outputFile = FILE_NAME; /* 0 means output is the name of output file.
661 * 1 means output is the name of output file, and append.
662 * 2 means output is filehandle name.
663 * All this is ignored if output is NULL
665 int errorFile = FILE_NAME; /* 0 means error is the name of error file.
666 * 1 means error is the name of error file, and append.
667 * 2 means error is filehandle name.
668 * All this is ignored if error is NULL
670 const char *output = NULL; /* Holds name of output file to pipe to,
671 * or NULL if output goes to stdout/pipe. */
672 const char *error = NULL; /* Holds name of stderr file to pipe to,
673 * or NULL if stderr goes to stderr/pipe. */
674 fdtype inputId = JIM_BAD_FD;
675 /* Readable file id input to current command in
676 * pipeline (could be file or pipe). JIM_BAD_FD
677 * means use stdin. */
678 fdtype outputId = JIM_BAD_FD;
679 /* Writable file id for output from current
680 * command in pipeline (could be file or pipe).
681 * JIM_BAD_FD means use stdout. */
682 fdtype errorId = JIM_BAD_FD;
683 /* Writable file id for all standard error
684 * output from all commands in pipeline. JIM_BAD_FD
685 * means use stderr. */
686 fdtype lastOutputId = JIM_BAD_FD;
687 /* Write file id for output from last command
688 * in pipeline (could be file or pipe).
689 * -1 means use stdout. */
690 fdtype pipeIds[2]; /* File ids for pipe that's being created. */
691 int firstArg, lastArg; /* Indexes of first and last arguments in
692 * current command. */
693 int lastBar;
694 int i;
695 pidtype pid;
696 char **save_environ;
697 struct WaitInfoTable *table = Jim_CmdPrivData(interp);
699 /* Holds the args which will be used to exec */
700 char **arg_array = Jim_Alloc(sizeof(*arg_array) * (argc + 1));
701 int arg_count = 0;
703 JimReapDetachedPids(table);
705 if (inPipePtr != NULL) {
706 *inPipePtr = JIM_BAD_FD;
708 if (outPipePtr != NULL) {
709 *outPipePtr = JIM_BAD_FD;
711 if (errFilePtr != NULL) {
712 *errFilePtr = JIM_BAD_FD;
714 pipeIds[0] = pipeIds[1] = JIM_BAD_FD;
717 * First, scan through all the arguments to figure out the structure
718 * of the pipeline. Count the number of distinct processes (it's the
719 * number of "|" arguments). If there are "<", "<<", or ">" arguments
720 * then make note of input and output redirection and remove these
721 * arguments and the arguments that follow them.
723 cmdCount = 1;
724 lastBar = -1;
725 for (i = 0; i < argc; i++) {
726 const char *arg = Jim_String(argv[i]);
728 if (arg[0] == '<') {
729 inputFile = FILE_NAME;
730 input = arg + 1;
731 if (*input == '<') {
732 inputFile = FILE_TEXT;
733 input_len = Jim_Length(argv[i]) - 2;
734 input++;
736 else if (*input == '@') {
737 inputFile = FILE_HANDLE;
738 input++;
741 if (!*input && ++i < argc) {
742 input = Jim_GetString(argv[i], &input_len);
745 else if (arg[0] == '>') {
746 int dup_error = 0;
748 outputFile = FILE_NAME;
750 output = arg + 1;
751 if (*output == '>') {
752 outputFile = FILE_APPEND;
753 output++;
755 if (*output == '&') {
756 /* Redirect stderr too */
757 output++;
758 dup_error = 1;
760 if (*output == '@') {
761 outputFile = FILE_HANDLE;
762 output++;
764 if (!*output && ++i < argc) {
765 output = Jim_String(argv[i]);
767 if (dup_error) {
768 errorFile = outputFile;
769 error = output;
772 else if (arg[0] == '2' && arg[1] == '>') {
773 error = arg + 2;
774 errorFile = FILE_NAME;
776 if (*error == '@') {
777 errorFile = FILE_HANDLE;
778 error++;
780 else if (*error == '>') {
781 errorFile = FILE_APPEND;
782 error++;
784 if (!*error && ++i < argc) {
785 error = Jim_String(argv[i]);
788 else {
789 if (strcmp(arg, "|") == 0 || strcmp(arg, "|&") == 0) {
790 if (i == lastBar + 1 || i == argc - 1) {
791 Jim_SetResultString(interp, "illegal use of | or |& in command", -1);
792 goto badargs;
794 lastBar = i;
795 cmdCount++;
797 /* Either |, |& or a "normal" arg, so store it in the arg array */
798 arg_array[arg_count++] = (char *)arg;
799 continue;
802 if (i >= argc) {
803 Jim_SetResultFormatted(interp, "can't specify \"%s\" as last word in command", arg);
804 goto badargs;
808 if (arg_count == 0) {
809 Jim_SetResultString(interp, "didn't specify command to execute", -1);
810 badargs:
811 Jim_Free(arg_array);
812 return -1;
815 /* Must do this before vfork(), so do it now */
816 save_environ = JimSaveEnv(JimBuildEnv(interp));
819 * Set up the redirected input source for the pipeline, if
820 * so requested.
822 if (input != NULL) {
823 if (inputFile == FILE_TEXT) {
825 * Immediate data in command. Create temporary file and
826 * put data into file.
828 inputId = JimCreateTemp(interp, input, input_len);
829 if (inputId == JIM_BAD_FD) {
830 goto error;
833 else if (inputFile == FILE_HANDLE) {
834 /* Should be a file descriptor */
835 FILE *fh = JimGetAioFilehandle(interp, input);
837 if (fh == NULL) {
838 goto error;
840 inputId = JimDupFd(JimFileno(fh));
842 else {
844 * File redirection. Just open the file.
846 inputId = JimOpenForRead(input);
847 if (inputId == JIM_BAD_FD) {
848 Jim_SetResultFormatted(interp, "couldn't read file \"%s\": %s", input, JimStrError());
849 goto error;
853 else if (inPipePtr != NULL) {
854 if (JimPipe(pipeIds) != 0) {
855 Jim_SetResultErrno(interp, "couldn't create input pipe for command");
856 goto error;
858 inputId = pipeIds[0];
859 *inPipePtr = pipeIds[1];
860 pipeIds[0] = pipeIds[1] = JIM_BAD_FD;
864 * Set up the redirected output sink for the pipeline from one
865 * of two places, if requested.
867 if (output != NULL) {
868 if (outputFile == FILE_HANDLE) {
869 FILE *fh = JimGetAioFilehandle(interp, output);
870 if (fh == NULL) {
871 goto error;
873 fflush(fh);
874 lastOutputId = JimDupFd(JimFileno(fh));
876 else {
878 * Output is to go to a file.
880 lastOutputId = JimOpenForWrite(output, outputFile == FILE_APPEND);
881 if (lastOutputId == JIM_BAD_FD) {
882 Jim_SetResultFormatted(interp, "couldn't write file \"%s\": %s", output, JimStrError());
883 goto error;
887 else if (outPipePtr != NULL) {
889 * Output is to go to a pipe.
891 if (JimPipe(pipeIds) != 0) {
892 Jim_SetResultErrno(interp, "couldn't create output pipe");
893 goto error;
895 lastOutputId = pipeIds[1];
896 *outPipePtr = pipeIds[0];
897 pipeIds[0] = pipeIds[1] = JIM_BAD_FD;
899 /* If we are redirecting stderr with 2>filename or 2>@fileId, then we ignore errFilePtr */
900 if (error != NULL) {
901 if (errorFile == FILE_HANDLE) {
902 if (strcmp(error, "1") == 0) {
903 /* Special 2>@1 */
904 if (lastOutputId != JIM_BAD_FD) {
905 errorId = JimDupFd(lastOutputId);
907 else {
908 /* No redirection of stdout, so just use 2>@stdout */
909 error = "stdout";
912 if (errorId == JIM_BAD_FD) {
913 FILE *fh = JimGetAioFilehandle(interp, error);
914 if (fh == NULL) {
915 goto error;
917 fflush(fh);
918 errorId = JimDupFd(JimFileno(fh));
921 else {
923 * Output is to go to a file.
925 errorId = JimOpenForWrite(error, errorFile == FILE_APPEND);
926 if (errorId == JIM_BAD_FD) {
927 Jim_SetResultFormatted(interp, "couldn't write file \"%s\": %s", error, JimStrError());
928 goto error;
932 else if (errFilePtr != NULL) {
934 * Set up the standard error output sink for the pipeline, if
935 * requested. Use a temporary file which is opened, then deleted.
936 * Could potentially just use pipe, but if it filled up it could
937 * cause the pipeline to deadlock: we'd be waiting for processes
938 * to complete before reading stderr, and processes couldn't complete
939 * because stderr was backed up.
941 errorId = JimCreateTemp(interp, NULL, 0);
942 if (errorId == JIM_BAD_FD) {
943 goto error;
945 *errFilePtr = JimDupFd(errorId);
949 * Scan through the argc array, forking off a process for each
950 * group of arguments between "|" arguments.
953 pidPtr = Jim_Alloc(cmdCount * sizeof(*pidPtr));
954 for (i = 0; i < numPids; i++) {
955 pidPtr[i] = JIM_BAD_PID;
957 for (firstArg = 0; firstArg < arg_count; numPids++, firstArg = lastArg + 1) {
958 int pipe_dup_err = 0;
959 fdtype origErrorId = errorId;
961 for (lastArg = firstArg; lastArg < arg_count; lastArg++) {
962 if (arg_array[lastArg][0] == '|') {
963 if (arg_array[lastArg][1] == '&') {
964 pipe_dup_err = 1;
966 break;
969 /* Replace | with NULL for execv() */
970 arg_array[lastArg] = NULL;
971 if (lastArg == arg_count) {
972 outputId = lastOutputId;
974 else {
975 if (JimPipe(pipeIds) != 0) {
976 Jim_SetResultErrno(interp, "couldn't create pipe");
977 goto error;
979 outputId = pipeIds[1];
982 /* Need to do this befor vfork() */
983 if (pipe_dup_err) {
984 errorId = outputId;
987 /* Now fork the child */
989 #ifdef __MINGW32__
990 pid = JimStartWinProcess(interp, &arg_array[firstArg], save_environ, inputId, outputId, errorId);
991 if (pid == JIM_BAD_PID) {
992 Jim_SetResultFormatted(interp, "couldn't exec \"%s\"", arg_array[firstArg]);
993 goto error;
995 #else
997 * Make a new process and enter it into the table if the fork
998 * is successful.
1000 pid = vfork();
1001 if (pid < 0) {
1002 Jim_SetResultErrno(interp, "couldn't fork child process");
1003 goto error;
1005 if (pid == 0) {
1006 /* Child */
1008 if (inputId != -1) dup2(inputId, 0);
1009 if (outputId != -1) dup2(outputId, 1);
1010 if (errorId != -1) dup2(errorId, 2);
1012 for (i = 3; (i <= outputId) || (i <= inputId) || (i <= errorId); i++) {
1013 close(i);
1016 /* Restore SIGPIPE behaviour */
1017 (void)signal(SIGPIPE, SIG_DFL);
1019 execvpe(arg_array[firstArg], &arg_array[firstArg], Jim_GetEnviron());
1021 /* Need to prep an error message before vfork(), just in case */
1022 fprintf(stderr, "couldn't exec \"%s\"\n", arg_array[firstArg]);
1023 #ifdef JIM_MAINTAINER
1025 /* Keep valgrind happy */
1026 static char *const false_argv[2] = {"false", NULL};
1027 execvp(false_argv[0],false_argv);
1029 #endif
1030 _exit(127);
1032 #endif
1034 /* parent */
1037 * Enlarge the wait table if there isn't enough space for a new
1038 * entry.
1040 if (table->used == table->size) {
1041 table->size += WAIT_TABLE_GROW_BY;
1042 table->info = Jim_Realloc(table->info, table->size * sizeof(*table->info));
1045 table->info[table->used].pid = pid;
1046 table->info[table->used].flags = 0;
1047 table->used++;
1049 pidPtr[numPids] = pid;
1051 /* Restore in case of pipe_dup_err */
1052 errorId = origErrorId;
1055 * Close off our copies of file descriptors that were set up for
1056 * this child, then set up the input for the next child.
1059 if (inputId != JIM_BAD_FD) {
1060 JimCloseFd(inputId);
1062 if (outputId != JIM_BAD_FD) {
1063 JimCloseFd(outputId);
1064 outputId = JIM_BAD_FD;
1066 inputId = pipeIds[0];
1067 pipeIds[0] = pipeIds[1] = JIM_BAD_FD;
1069 *pidArrayPtr = pidPtr;
1072 * All done. Cleanup open files lying around and then return.
1075 cleanup:
1076 if (inputId != JIM_BAD_FD) {
1077 JimCloseFd(inputId);
1079 if (lastOutputId != JIM_BAD_FD) {
1080 JimCloseFd(lastOutputId);
1082 if (errorId != JIM_BAD_FD) {
1083 JimCloseFd(errorId);
1085 Jim_Free(arg_array);
1087 JimRestoreEnv(save_environ);
1089 return numPids;
1092 * An error occurred. There could have been extra files open, such
1093 * as pipes between children. Clean them all up. Detach any child
1094 * processes that have been created.
1097 error:
1098 if ((inPipePtr != NULL) && (*inPipePtr != JIM_BAD_FD)) {
1099 JimCloseFd(*inPipePtr);
1100 *inPipePtr = JIM_BAD_FD;
1102 if ((outPipePtr != NULL) && (*outPipePtr != JIM_BAD_FD)) {
1103 JimCloseFd(*outPipePtr);
1104 *outPipePtr = JIM_BAD_FD;
1106 if ((errFilePtr != NULL) && (*errFilePtr != JIM_BAD_FD)) {
1107 JimCloseFd(*errFilePtr);
1108 *errFilePtr = JIM_BAD_FD;
1110 if (pipeIds[0] != JIM_BAD_FD) {
1111 JimCloseFd(pipeIds[0]);
1113 if (pipeIds[1] != JIM_BAD_FD) {
1114 JimCloseFd(pipeIds[1]);
1116 if (pidPtr != NULL) {
1117 for (i = 0; i < numPids; i++) {
1118 if (pidPtr[i] != JIM_BAD_PID) {
1119 JimDetachPids(interp, 1, &pidPtr[i]);
1122 Jim_Free(pidPtr);
1124 numPids = -1;
1125 goto cleanup;
1129 *----------------------------------------------------------------------
1131 * JimCleanupChildren --
1133 * This is a utility procedure used to wait for child processes
1134 * to exit, record information about abnormal exits.
1136 * Results:
1137 * The return value is a standard Tcl result. If anything at
1138 * weird happened with the child processes, JIM_ERR is returned
1139 * and a structured message is left in $::errorCode.
1140 * If errStrObj is not NULL, abnormal exit details are appended to this object.
1142 * Side effects:
1143 * pidPtr is freed
1145 *----------------------------------------------------------------------
1148 static int JimCleanupChildren(Jim_Interp *interp, int numPids, pidtype *pidPtr, Jim_Obj *errStrObj)
1150 struct WaitInfoTable *table = Jim_CmdPrivData(interp);
1151 int result = JIM_OK;
1152 int i;
1154 /* Now check the return status of each child */
1155 for (i = 0; i < numPids; i++) {
1156 int waitStatus = 0;
1157 if (JimWaitForProcess(table, pidPtr[i], &waitStatus) != JIM_BAD_PID) {
1158 if (JimCheckWaitStatus(interp, pidPtr[i], waitStatus, errStrObj) != JIM_OK) {
1159 result = JIM_ERR;
1163 Jim_Free(pidPtr);
1165 return result;
1168 int Jim_execInit(Jim_Interp *interp)
1170 if (Jim_PackageProvide(interp, "exec", "1.0", JIM_ERRMSG))
1171 return JIM_ERR;
1173 #ifdef SIGPIPE
1175 * Disable SIGPIPE signals: if they were allowed, this process
1176 * might go away unexpectedly if children misbehave. This code
1177 * can potentially interfere with other application code that
1178 * expects to handle SIGPIPEs.
1180 * By doing this in the init function, applications can override
1181 * this later. Note that child processes have SIGPIPE restored
1182 * to the default after vfork().
1184 (void)signal(SIGPIPE, SIG_IGN);
1185 #endif
1187 Jim_CreateCommand(interp, "exec", Jim_ExecCmd, JimAllocWaitInfoTable(), JimFreeWaitInfoTable);
1188 return JIM_OK;
1191 #if defined(__MINGW32__)
1192 /* Windows-specific (mingw) implementation */
1194 static SECURITY_ATTRIBUTES *JimStdSecAttrs(void)
1196 static SECURITY_ATTRIBUTES secAtts;
1198 secAtts.nLength = sizeof(SECURITY_ATTRIBUTES);
1199 secAtts.lpSecurityDescriptor = NULL;
1200 secAtts.bInheritHandle = TRUE;
1201 return &secAtts;
1204 static int JimErrno(void)
1206 switch (GetLastError()) {
1207 case ERROR_FILE_NOT_FOUND: return ENOENT;
1208 case ERROR_PATH_NOT_FOUND: return ENOENT;
1209 case ERROR_TOO_MANY_OPEN_FILES: return EMFILE;
1210 case ERROR_ACCESS_DENIED: return EACCES;
1211 case ERROR_INVALID_HANDLE: return EBADF;
1212 case ERROR_BAD_ENVIRONMENT: return E2BIG;
1213 case ERROR_BAD_FORMAT: return ENOEXEC;
1214 case ERROR_INVALID_ACCESS: return EACCES;
1215 case ERROR_INVALID_DRIVE: return ENOENT;
1216 case ERROR_CURRENT_DIRECTORY: return EACCES;
1217 case ERROR_NOT_SAME_DEVICE: return EXDEV;
1218 case ERROR_NO_MORE_FILES: return ENOENT;
1219 case ERROR_WRITE_PROTECT: return EROFS;
1220 case ERROR_BAD_UNIT: return ENXIO;
1221 case ERROR_NOT_READY: return EBUSY;
1222 case ERROR_BAD_COMMAND: return EIO;
1223 case ERROR_CRC: return EIO;
1224 case ERROR_BAD_LENGTH: return EIO;
1225 case ERROR_SEEK: return EIO;
1226 case ERROR_WRITE_FAULT: return EIO;
1227 case ERROR_READ_FAULT: return EIO;
1228 case ERROR_GEN_FAILURE: return EIO;
1229 case ERROR_SHARING_VIOLATION: return EACCES;
1230 case ERROR_LOCK_VIOLATION: return EACCES;
1231 case ERROR_SHARING_BUFFER_EXCEEDED: return ENFILE;
1232 case ERROR_HANDLE_DISK_FULL: return ENOSPC;
1233 case ERROR_NOT_SUPPORTED: return ENODEV;
1234 case ERROR_REM_NOT_LIST: return EBUSY;
1235 case ERROR_DUP_NAME: return EEXIST;
1236 case ERROR_BAD_NETPATH: return ENOENT;
1237 case ERROR_NETWORK_BUSY: return EBUSY;
1238 case ERROR_DEV_NOT_EXIST: return ENODEV;
1239 case ERROR_TOO_MANY_CMDS: return EAGAIN;
1240 case ERROR_ADAP_HDW_ERR: return EIO;
1241 case ERROR_BAD_NET_RESP: return EIO;
1242 case ERROR_UNEXP_NET_ERR: return EIO;
1243 case ERROR_NETNAME_DELETED: return ENOENT;
1244 case ERROR_NETWORK_ACCESS_DENIED: return EACCES;
1245 case ERROR_BAD_DEV_TYPE: return ENODEV;
1246 case ERROR_BAD_NET_NAME: return ENOENT;
1247 case ERROR_TOO_MANY_NAMES: return ENFILE;
1248 case ERROR_TOO_MANY_SESS: return EIO;
1249 case ERROR_SHARING_PAUSED: return EAGAIN;
1250 case ERROR_REDIR_PAUSED: return EAGAIN;
1251 case ERROR_FILE_EXISTS: return EEXIST;
1252 case ERROR_CANNOT_MAKE: return ENOSPC;
1253 case ERROR_OUT_OF_STRUCTURES: return ENFILE;
1254 case ERROR_ALREADY_ASSIGNED: return EEXIST;
1255 case ERROR_INVALID_PASSWORD: return EPERM;
1256 case ERROR_NET_WRITE_FAULT: return EIO;
1257 case ERROR_NO_PROC_SLOTS: return EAGAIN;
1258 case ERROR_DISK_CHANGE: return EXDEV;
1259 case ERROR_BROKEN_PIPE: return EPIPE;
1260 case ERROR_OPEN_FAILED: return ENOENT;
1261 case ERROR_DISK_FULL: return ENOSPC;
1262 case ERROR_NO_MORE_SEARCH_HANDLES: return EMFILE;
1263 case ERROR_INVALID_TARGET_HANDLE: return EBADF;
1264 case ERROR_INVALID_NAME: return ENOENT;
1265 case ERROR_PROC_NOT_FOUND: return ESRCH;
1266 case ERROR_WAIT_NO_CHILDREN: return ECHILD;
1267 case ERROR_CHILD_NOT_COMPLETE: return ECHILD;
1268 case ERROR_DIRECT_ACCESS_HANDLE: return EBADF;
1269 case ERROR_SEEK_ON_DEVICE: return ESPIPE;
1270 case ERROR_BUSY_DRIVE: return EAGAIN;
1271 case ERROR_DIR_NOT_EMPTY: return EEXIST;
1272 case ERROR_NOT_LOCKED: return EACCES;
1273 case ERROR_BAD_PATHNAME: return ENOENT;
1274 case ERROR_LOCK_FAILED: return EACCES;
1275 case ERROR_ALREADY_EXISTS: return EEXIST;
1276 case ERROR_FILENAME_EXCED_RANGE: return ENAMETOOLONG;
1277 case ERROR_BAD_PIPE: return EPIPE;
1278 case ERROR_PIPE_BUSY: return EAGAIN;
1279 case ERROR_PIPE_NOT_CONNECTED: return EPIPE;
1280 case ERROR_DIRECTORY: return ENOTDIR;
1282 return EINVAL;
1285 static int JimPipe(fdtype pipefd[2])
1287 if (CreatePipe(&pipefd[0], &pipefd[1], NULL, 0)) {
1288 return 0;
1290 return -1;
1293 static fdtype JimDupFd(fdtype infd)
1295 fdtype dupfd;
1296 pidtype pid = GetCurrentProcess();
1298 if (DuplicateHandle(pid, infd, pid, &dupfd, 0, TRUE, DUPLICATE_SAME_ACCESS)) {
1299 return dupfd;
1301 return JIM_BAD_FD;
1304 static int JimRewindFd(fdtype fd)
1306 return SetFilePointer(fd, 0, NULL, FILE_BEGIN) == INVALID_SET_FILE_POINTER ? -1 : 0;
1309 #if 0
1310 static int JimReadFd(fdtype fd, char *buffer, size_t len)
1312 DWORD num;
1314 if (ReadFile(fd, buffer, len, &num, NULL)) {
1315 return num;
1317 if (GetLastError() == ERROR_HANDLE_EOF || GetLastError() == ERROR_BROKEN_PIPE) {
1318 return 0;
1320 return -1;
1322 #endif
1324 static FILE *JimFdOpenForRead(fdtype fd)
1326 return _fdopen(_open_osfhandle((int)fd, _O_RDONLY | _O_TEXT), "r");
1329 static fdtype JimFileno(FILE *fh)
1331 return (fdtype)_get_osfhandle(_fileno(fh));
1334 static fdtype JimOpenForRead(const char *filename)
1336 return CreateFile(filename, GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE,
1337 JimStdSecAttrs(), OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
1340 static fdtype JimOpenForWrite(const char *filename, int append)
1342 fdtype fd = CreateFile(filename, GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE,
1343 JimStdSecAttrs(), append ? OPEN_ALWAYS : CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, (HANDLE) NULL);
1344 if (append && fd != JIM_BAD_FD) {
1345 SetFilePointer(fd, 0, NULL, FILE_END);
1347 return fd;
1350 static FILE *JimFdOpenForWrite(fdtype fd)
1352 return _fdopen(_open_osfhandle((int)fd, _O_TEXT), "w");
1355 static pidtype JimWaitPid(pidtype pid, int *status, int nohang)
1357 DWORD ret = WaitForSingleObject(pid, nohang ? 0 : INFINITE);
1358 if (ret == WAIT_TIMEOUT || ret == WAIT_FAILED) {
1359 /* WAIT_TIMEOUT can only happend with WNOHANG */
1360 return JIM_BAD_PID;
1362 GetExitCodeProcess(pid, &ret);
1363 *status = ret;
1364 CloseHandle(pid);
1365 return pid;
1368 static HANDLE JimCreateTemp(Jim_Interp *interp, const char *contents, int len)
1370 char name[MAX_PATH];
1371 HANDLE handle;
1373 if (!GetTempPath(MAX_PATH, name) || !GetTempFileName(name, "JIM", 0, name)) {
1374 return JIM_BAD_FD;
1377 handle = CreateFile(name, GENERIC_READ | GENERIC_WRITE, 0, JimStdSecAttrs(),
1378 CREATE_ALWAYS, FILE_ATTRIBUTE_TEMPORARY | FILE_FLAG_DELETE_ON_CLOSE,
1379 NULL);
1381 if (handle == INVALID_HANDLE_VALUE) {
1382 goto error;
1385 if (contents != NULL) {
1386 /* Use fdopen() to get automatic text-mode translation */
1387 FILE *fh = JimFdOpenForWrite(JimDupFd(handle));
1388 if (fh == NULL) {
1389 goto error;
1392 if (fwrite(contents, len, 1, fh) != 1) {
1393 fclose(fh);
1394 goto error;
1396 fseek(fh, 0, SEEK_SET);
1397 fclose(fh);
1399 return handle;
1401 error:
1402 Jim_SetResultErrno(interp, "failed to create temp file");
1403 CloseHandle(handle);
1404 DeleteFile(name);
1405 return JIM_BAD_FD;
1408 static int
1409 JimWinFindExecutable(const char *originalName, char fullPath[MAX_PATH])
1411 int i;
1412 static char extensions[][5] = {".exe", "", ".bat"};
1414 for (i = 0; i < (int) (sizeof(extensions) / sizeof(extensions[0])); i++) {
1415 snprintf(fullPath, MAX_PATH, "%s%s", originalName, extensions[i]);
1417 if (SearchPath(NULL, fullPath, NULL, MAX_PATH, fullPath, NULL) == 0) {
1418 continue;
1420 if (GetFileAttributes(fullPath) & FILE_ATTRIBUTE_DIRECTORY) {
1421 continue;
1423 return 0;
1426 return -1;
1429 static char **JimSaveEnv(char **env)
1431 return env;
1434 static void JimRestoreEnv(char **env)
1436 JimFreeEnv(env, Jim_GetEnviron());
1439 static char **JimOriginalEnviron(void)
1441 return NULL;
1444 static Jim_Obj *
1445 JimWinBuildCommandLine(Jim_Interp *interp, char **argv)
1447 char *start, *special;
1448 int quote, i;
1450 Jim_Obj *strObj = Jim_NewStringObj(interp, "", 0);
1452 for (i = 0; argv[i]; i++) {
1453 if (i > 0) {
1454 Jim_AppendString(interp, strObj, " ", 1);
1457 if (argv[i][0] == '\0') {
1458 quote = 1;
1460 else {
1461 quote = 0;
1462 for (start = argv[i]; *start != '\0'; start++) {
1463 if (isspace(UCHAR(*start))) {
1464 quote = 1;
1465 break;
1469 if (quote) {
1470 Jim_AppendString(interp, strObj, "\"" , 1);
1473 start = argv[i];
1474 for (special = argv[i]; ; ) {
1475 if ((*special == '\\') && (special[1] == '\\' ||
1476 special[1] == '"' || (quote && special[1] == '\0'))) {
1477 Jim_AppendString(interp, strObj, start, special - start);
1478 start = special;
1479 while (1) {
1480 special++;
1481 if (*special == '"' || (quote && *special == '\0')) {
1483 * N backslashes followed a quote -> insert
1484 * N * 2 + 1 backslashes then a quote.
1487 Jim_AppendString(interp, strObj, start, special - start);
1488 break;
1490 if (*special != '\\') {
1491 break;
1494 Jim_AppendString(interp, strObj, start, special - start);
1495 start = special;
1497 if (*special == '"') {
1498 if (special == start) {
1499 Jim_AppendString(interp, strObj, "\"", 1);
1501 else {
1502 Jim_AppendString(interp, strObj, start, special - start);
1504 Jim_AppendString(interp, strObj, "\\\"", 2);
1505 start = special + 1;
1507 if (*special == '\0') {
1508 break;
1510 special++;
1512 Jim_AppendString(interp, strObj, start, special - start);
1513 if (quote) {
1514 Jim_AppendString(interp, strObj, "\"", 1);
1517 return strObj;
1520 static pidtype
1521 JimStartWinProcess(Jim_Interp *interp, char **argv, char **env, fdtype inputId, fdtype outputId, fdtype errorId)
1523 STARTUPINFO startInfo;
1524 PROCESS_INFORMATION procInfo;
1525 HANDLE hProcess, h;
1526 char execPath[MAX_PATH];
1527 pidtype pid = JIM_BAD_PID;
1528 Jim_Obj *cmdLineObj;
1529 char *winenv;
1531 if (JimWinFindExecutable(argv[0], execPath) < 0) {
1532 return JIM_BAD_PID;
1534 argv[0] = execPath;
1536 hProcess = GetCurrentProcess();
1537 cmdLineObj = JimWinBuildCommandLine(interp, argv);
1540 * STARTF_USESTDHANDLES must be used to pass handles to child process.
1541 * Using SetStdHandle() and/or dup2() only works when a console mode
1542 * parent process is spawning an attached console mode child process.
1545 ZeroMemory(&startInfo, sizeof(startInfo));
1546 startInfo.cb = sizeof(startInfo);
1547 startInfo.dwFlags = STARTF_USESTDHANDLES;
1548 startInfo.hStdInput = INVALID_HANDLE_VALUE;
1549 startInfo.hStdOutput= INVALID_HANDLE_VALUE;
1550 startInfo.hStdError = INVALID_HANDLE_VALUE;
1553 * Duplicate all the handles which will be passed off as stdin, stdout
1554 * and stderr of the child process. The duplicate handles are set to
1555 * be inheritable, so the child process can use them.
1557 if (inputId == JIM_BAD_FD) {
1558 if (CreatePipe(&startInfo.hStdInput, &h, JimStdSecAttrs(), 0) != FALSE) {
1559 CloseHandle(h);
1561 } else {
1562 DuplicateHandle(hProcess, inputId, hProcess, &startInfo.hStdInput,
1563 0, TRUE, DUPLICATE_SAME_ACCESS);
1565 if (startInfo.hStdInput == JIM_BAD_FD) {
1566 goto end;
1569 if (outputId == JIM_BAD_FD) {
1570 startInfo.hStdOutput = CreateFile("NUL:", GENERIC_WRITE, 0,
1571 JimStdSecAttrs(), OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
1572 } else {
1573 DuplicateHandle(hProcess, outputId, hProcess, &startInfo.hStdOutput,
1574 0, TRUE, DUPLICATE_SAME_ACCESS);
1576 if (startInfo.hStdOutput == JIM_BAD_FD) {
1577 goto end;
1580 if (errorId == JIM_BAD_FD) {
1582 * If handle was not set, errors should be sent to an infinitely
1583 * deep sink.
1586 startInfo.hStdError = CreateFile("NUL:", GENERIC_WRITE, 0,
1587 JimStdSecAttrs(), OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
1588 } else {
1589 DuplicateHandle(hProcess, errorId, hProcess, &startInfo.hStdError,
1590 0, TRUE, DUPLICATE_SAME_ACCESS);
1592 if (startInfo.hStdError == JIM_BAD_FD) {
1593 goto end;
1596 /* If env is NULL, use the original environment.
1597 * If env[0] is NULL, use an empty environment.
1598 * Otherwise use the environment starting at env[0]
1600 if (env == NULL) {
1601 /* Use the original environment */
1602 winenv = NULL;
1604 else if (env[0] == NULL) {
1605 winenv = (char *)"\0";
1607 else {
1608 winenv = env[0];
1611 if (!CreateProcess(NULL, (char *)Jim_String(cmdLineObj), NULL, NULL, TRUE,
1612 0, winenv, NULL, &startInfo, &procInfo)) {
1613 goto end;
1617 * "When an application spawns a process repeatedly, a new thread
1618 * instance will be created for each process but the previous
1619 * instances may not be cleaned up. This results in a significant
1620 * virtual memory loss each time the process is spawned. If there
1621 * is a WaitForInputIdle() call between CreateProcess() and
1622 * CloseHandle(), the problem does not occur." PSS ID Number: Q124121
1625 WaitForInputIdle(procInfo.hProcess, 5000);
1626 CloseHandle(procInfo.hThread);
1628 pid = procInfo.hProcess;
1630 end:
1631 Jim_FreeNewObj(interp, cmdLineObj);
1632 if (startInfo.hStdInput != JIM_BAD_FD) {
1633 CloseHandle(startInfo.hStdInput);
1635 if (startInfo.hStdOutput != JIM_BAD_FD) {
1636 CloseHandle(startInfo.hStdOutput);
1638 if (startInfo.hStdError != JIM_BAD_FD) {
1639 CloseHandle(startInfo.hStdError);
1641 return pid;
1643 #else
1644 /* Unix-specific implementation */
1645 static int JimOpenForWrite(const char *filename, int append)
1647 return open(filename, O_WRONLY | O_CREAT | (append ? O_APPEND : O_TRUNC), 0666);
1650 static int JimRewindFd(int fd)
1652 return lseek(fd, 0L, SEEK_SET);
1655 static int JimCreateTemp(Jim_Interp *interp, const char *contents, int len)
1657 int fd = Jim_MakeTempFile(interp, NULL);
1659 if (fd != JIM_BAD_FD) {
1660 unlink(Jim_String(Jim_GetResult(interp)));
1661 if (contents) {
1662 if (write(fd, contents, len) != len) {
1663 Jim_SetResultErrno(interp, "couldn't write temp file");
1664 close(fd);
1665 return -1;
1667 lseek(fd, 0L, SEEK_SET);
1670 return fd;
1673 static char **JimOriginalEnviron(void)
1675 return Jim_GetEnviron();
1678 static char **JimSaveEnv(char **env)
1680 char **saveenv = Jim_GetEnviron();
1681 Jim_SetEnviron(env);
1682 return saveenv;
1685 static void JimRestoreEnv(char **env)
1687 JimFreeEnv(Jim_GetEnviron(), env);
1688 Jim_SetEnviron(env);
1690 #endif
1691 #endif