jim-interp.c: fix compile warning with GCC 8.1.1
[jimtcl.git] / jim-exec.c
blob005b28b8fe0bb75b3b476a2408bf816c314d69f7
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>
96 #include "jim-signal.h"
97 #include "jimiocompat.h"
98 #include <sys/stat.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);
113 #endif
116 * If the last character of 'objPtr' is a newline, then remove
117 * the newline character.
119 static void Jim_RemoveTrailingNewline(Jim_Obj *objPtr)
121 int len;
122 const char *s = Jim_GetString(objPtr, &len);
124 if (len > 0 && s[len - 1] == '\n') {
125 objPtr->length--;
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)
136 char buf[256];
137 FILE *fh = fdopen(fd, "r");
138 int ret = 0;
140 if (fh == NULL) {
141 return -1;
144 while (1) {
145 int retval = fread(buf, 1, sizeof(buf), fh);
146 if (retval > 0) {
147 ret = 1;
148 Jim_AppendString(interp, strObj, buf, retval);
150 if (retval != sizeof(buf)) {
151 break;
154 fclose(fh);
155 return ret;
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)
169 int i;
170 int size;
171 int num;
172 int n;
173 char **envptr;
174 char *envdata;
176 Jim_Obj *objPtr = Jim_GetGlobalVariableStr(interp, "env", JIM_NONE);
178 if (!objPtr) {
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);
189 if (num % 2) {
190 /* Silently drop the last element if not a valid dictionary */
191 num--;
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];
202 n = 0;
203 for (i = 0; i < num; i += 2) {
204 const char *s1, *s2;
205 Jim_Obj *elemObj;
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);
212 envptr[n] = envdata;
213 envdata += sprintf(envdata, "%s=%s", s1, s2);
214 envdata++;
215 n++;
217 envptr[n] = NULL;
218 *envdata = 0;
220 return envptr;
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) {
231 Jim_Free(env);
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)));
249 else {
250 const char *type;
251 const char *action;
252 const char *signame;
254 if (WIFSIGNALED(waitStatus)) {
255 type = "CHILDKILLED";
256 action = "killed";
257 signame = Jim_SignalId(WTERMSIG(waitStatus));
259 else {
260 type = "CHILDSUSP";
261 action = "suspended";
262 signame = "none";
265 Jim_ListAppendElement(interp, errorCode, Jim_NewStringObj(interp, type, -1));
267 if (errStrObj) {
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));
277 return errorCode;
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) {
292 return JIM_OK;
294 Jim_SetGlobalVariableStr(interp, "errorCode", JimMakeErrorCode(interp, pid, waitStatus, errStrObj));
296 return JIM_ERR;
300 * Data structures of the following type are used by exec and
301 * wait to keep track of child processes.
304 struct WaitInfo
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);
337 Jim_Free(table);
341 static struct WaitInfoTable *JimAllocWaitInfoTable(void)
343 struct WaitInfoTable *table = Jim_Alloc(sizeof(*table));
344 table->info = NULL;
345 table->size = table->used = 0;
346 table->refcount = 1;
348 return table;
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)
358 int i;
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];
366 table->used--;
367 return 0;
370 return -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. */
380 pidtype *pidPtr;
381 int numPids, result;
382 int child_siginfo = 1;
383 Jim_Obj *childErrObj;
384 Jim_Obj *errStrObj;
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], "&")) {
392 Jim_Obj *listObj;
393 int i;
395 argc--;
396 numPids = JimCreatePipeline(interp, argc - 1, argv + 1, &pidPtr, NULL, NULL, NULL);
397 if (numPids < 0) {
398 return JIM_ERR;
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);
407 Jim_Free(pidPtr);
408 return JIM_OK;
412 * Create the command's pipeline.
414 numPids =
415 JimCreatePipeline(interp, argc - 1, argv + 1, &pidPtr, NULL, &outputId, &errorId);
417 if (numPids < 0) {
418 return JIM_ERR;
421 result = JIM_OK;
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) {
428 result = JIM_ERR;
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) {
438 result = JIM_ERR;
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.
447 if (errorId != -1) {
448 int ret;
449 lseek(errorId, 0, SEEK_SET);
450 ret = JimAppendStreamToString(interp, errorId, errStrObj);
451 if (ret < 0) {
452 Jim_SetResultErrno(interp, "error reading from error pipe");
453 result = JIM_ERR;
455 else if (ret > 0) {
456 /* Got some error output, so discard the abnormal info string */
457 child_siginfo = 0;
461 if (child_siginfo) {
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);
473 return result;
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) {
486 /* wait for it */
487 waitpid(pid, statusPtr, 0);
488 return pid;
491 /* Not found */
492 return JIM_BAD_PID;
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)
502 int j;
504 for (j = 0; j < numPids; j++) {
505 /* Find it in the table */
506 int i;
507 for (i = 0; i < table->used; i++) {
508 if (pidPtr[j] == table->info[i].pid) {
509 table->info[i].flags |= WI_DETACHED;
510 break;
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)
521 Jim_Obj *objv[2];
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) {
527 jim_wide fd;
528 if (Jim_GetWide(interp, Jim_GetResult(interp), &fd) == JIM_OK) {
529 return fd;
532 return -1;
535 static void JimReapDetachedPids(struct WaitInfoTable *table)
537 struct WaitInfo *waitPtr;
538 int count;
539 int dest;
541 if (!table) {
542 return;
545 waitPtr = table->info;
546 dest = 0;
547 for (count = table->used; count > 0; waitPtr++, count--) {
548 if (waitPtr->flags & WI_DETACHED) {
549 int status;
550 pidtype pid = waitpid(waitPtr->pid, &status, WNOHANG);
551 if (pid == waitPtr->pid) {
552 /* Process has exited, so remove it from the table */
553 table->used--;
554 continue;
557 if (waitPtr != &table->info[dest]) {
558 table->info[dest] = *waitPtr;
560 dest++;
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:
573 * {NONE x x}
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);
592 int nohang = 0;
593 pidtype pid;
594 long pidarg;
595 int status;
596 Jim_Obj *errCodeObj;
598 /* With no arguments, reap detached children */
599 if (argc == 1) {
600 JimReapDetachedPids(table);
601 return JIM_OK;
604 if (argc > 1 && Jim_CompareStringImmediate(interp, argv[1], "-nohang")) {
605 nohang = 1;
607 if (argc != nohang + 2) {
608 Jim_WrongNumArgs(interp, 1, argv, "?-nohang? ?pid?");
609 return JIM_ERR;
611 if (Jim_GetLong(interp, argv[nohang + 1], &pidarg) != JIM_OK) {
612 return JIM_ERR;
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);
624 return JIM_OK;
627 static int Jim_PidCommand(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
629 if (argc != 1) {
630 Jim_WrongNumArgs(interp, 1, argv, "");
631 return JIM_ERR;
634 Jim_SetResultInt(interp, (jim_wide)getpid());
635 return JIM_OK;
639 *----------------------------------------------------------------------
641 * JimCreatePipeline --
643 * Given an argc/argv array, instantiate a pipeline of processes
644 * as described by the argv.
646 * Results:
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.
661 * Side effects:
662 * Processes and pipes are created.
664 *----------------------------------------------------------------------
666 static int
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. */
705 int inputId = -1;
706 /* Readable file id input to current command in
707 * pipeline (could be file or pipe). -1
708 * means use stdin. */
709 int outputId = -1;
710 /* Writable file id for output from current
711 * command in pipeline (could be file or pipe).
712 * -1 means use stdout. */
713 int errorId = -1;
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. */
724 int lastBar;
725 int i;
726 pidtype pid;
727 char **save_environ;
728 #ifndef __MINGW32__
729 char **child_environ;
730 #endif
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));
735 int arg_count = 0;
737 if (inPipePtr != NULL) {
738 *inPipePtr = -1;
740 if (outPipePtr != NULL) {
741 *outPipePtr = -1;
743 if (errFilePtr != NULL) {
744 *errFilePtr = -1;
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.
755 cmdCount = 1;
756 lastBar = -1;
757 for (i = 0; i < argc; i++) {
758 const char *arg = Jim_String(argv[i]);
760 if (arg[0] == '<') {
761 inputFile = FILE_NAME;
762 input = arg + 1;
763 if (*input == '<') {
764 inputFile = FILE_TEXT;
765 input_len = Jim_Length(argv[i]) - 2;
766 input++;
768 else if (*input == '@') {
769 inputFile = FILE_HANDLE;
770 input++;
773 if (!*input && ++i < argc) {
774 input = Jim_GetString(argv[i], &input_len);
777 else if (arg[0] == '>') {
778 int dup_error = 0;
780 outputFile = FILE_NAME;
782 output = arg + 1;
783 if (*output == '>') {
784 outputFile = FILE_APPEND;
785 output++;
787 if (*output == '&') {
788 /* Redirect stderr too */
789 output++;
790 dup_error = 1;
792 if (*output == '@') {
793 outputFile = FILE_HANDLE;
794 output++;
796 if (!*output && ++i < argc) {
797 output = Jim_String(argv[i]);
799 if (dup_error) {
800 errorFile = outputFile;
801 error = output;
804 else if (arg[0] == '2' && arg[1] == '>') {
805 error = arg + 2;
806 errorFile = FILE_NAME;
808 if (*error == '@') {
809 errorFile = FILE_HANDLE;
810 error++;
812 else if (*error == '>') {
813 errorFile = FILE_APPEND;
814 error++;
816 if (!*error && ++i < argc) {
817 error = Jim_String(argv[i]);
820 else {
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);
824 goto badargs;
826 lastBar = i;
827 cmdCount++;
829 /* Either |, |& or a "normal" arg, so store it in the arg array */
830 arg_array[arg_count++] = (char *)arg;
831 continue;
834 if (i >= argc) {
835 Jim_SetResultFormatted(interp, "can't specify \"%s\" as last word in command", arg);
836 goto badargs;
840 if (arg_count == 0) {
841 Jim_SetResultString(interp, "didn't specify command to execute", -1);
842 badargs:
843 Jim_Free(arg_array);
844 return -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
852 * so requested.
854 if (input != NULL) {
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);
861 if (inputId == -1) {
862 goto error;
864 if (write(inputId, input, input_len) != input_len) {
865 Jim_SetResultErrno(interp, "couldn't write temp file");
866 close(inputId);
867 goto error;
869 lseek(inputId, 0L, SEEK_SET);
871 else if (inputFile == FILE_HANDLE) {
872 int fd = JimGetChannelFd(interp, input);
874 if (fd < 0) {
875 goto error;
877 inputId = dup(fd);
879 else {
881 * File redirection. Just open the file.
883 inputId = Jim_OpenForRead(input);
884 if (inputId == -1) {
885 Jim_SetResultFormatted(interp, "couldn't read file \"%s\": %s", input, strerror(Jim_Errno()));
886 goto error;
890 else if (inPipePtr != NULL) {
891 if (pipe(pipeIds) != 0) {
892 Jim_SetResultErrno(interp, "couldn't create input pipe for command");
893 goto error;
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);
907 if (fd < 0) {
908 goto error;
910 lastOutputId = dup(fd);
912 else {
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()));
919 goto error;
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");
929 goto error;
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 */
936 if (error != NULL) {
937 if (errorFile == FILE_HANDLE) {
938 if (strcmp(error, "1") == 0) {
939 /* Special 2>@1 */
940 if (lastOutputId != -1) {
941 errorId = dup(lastOutputId);
943 else {
944 /* No redirection of stdout, so just use 2>@stdout */
945 error = "stdout";
948 if (errorId == -1) {
949 int fd = JimGetChannelFd(interp, error);
950 if (fd < 0) {
951 goto error;
953 errorId = dup(fd);
956 else {
958 * Output is to go to a file.
960 errorId = Jim_OpenForWrite(error, errorFile == FILE_APPEND);
961 if (errorId == -1) {
962 Jim_SetResultFormatted(interp, "couldn't write file \"%s\": %s", error, strerror(Jim_Errno()));
963 goto error;
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);
977 if (errorId == -1) {
978 goto error;
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) {
998 break;
1000 if (strcmp(arg_array[lastArg], "|&") == 0) {
1001 pipe_dup_err = 1;
1002 break;
1006 if (lastArg == firstArg) {
1007 Jim_SetResultString(interp, "missing command to exec", -1);
1008 goto error;
1011 /* Replace | with NULL for execv() */
1012 arg_array[lastArg] = NULL;
1013 if (lastArg == arg_count) {
1014 outputId = lastOutputId;
1015 lastOutputId = -1;
1017 else {
1018 if (pipe(pipeIds) != 0) {
1019 Jim_SetResultErrno(interp, "couldn't create pipe");
1020 goto error;
1022 outputId = pipeIds[1];
1025 /* Need to do this before vfork() */
1026 if (pipe_dup_err) {
1027 errorId = outputId;
1030 /* Now fork the child */
1032 #ifdef __MINGW32__
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]);
1036 goto error;
1038 #else
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
1044 * is successful.
1046 pid = vfork();
1047 if (pid < 0) {
1048 Jim_SetResultErrno(interp, "couldn't fork child process");
1049 goto error;
1051 if (pid == 0) {
1052 /* Child */
1053 /* Set up stdin, stdout, stderr */
1054 if (inputId != -1) {
1055 dup2(inputId, fileno(stdin));
1056 close(inputId);
1058 if (outputId != -1) {
1059 dup2(outputId, fileno(stdout));
1060 if (outputId != errorId) {
1061 close(outputId);
1064 if (errorId != -1) {
1065 dup2(errorId, fileno(stderr));
1066 close(errorId);
1068 /* Close parent-only file descriptors */
1069 if (outPipePtr) {
1070 close(*outPipePtr);
1072 if (errFilePtr) {
1073 close(*errFilePtr);
1075 if (pipeIds[0] != -1) {
1076 close(pipeIds[0]);
1078 if (lastOutputId != -1) {
1079 close(lastOutputId);
1082 /* Restore SIGPIPE behaviour */
1083 (void)signal(SIGPIPE, SIG_DFL);
1085 execvpe(arg_array[firstArg], &arg_array[firstArg], child_environ);
1087 if (write(fileno(stderr), "couldn't exec \"", 15) &&
1088 write(fileno(stderr), arg_array[firstArg], i) &&
1089 write(fileno(stderr), "\"\n", 2)) {
1090 /* nothing */
1092 #ifdef JIM_MAINTAINER
1094 /* Keep valgrind happy */
1095 static char *const false_argv[2] = {"false", NULL};
1096 execvp(false_argv[0],false_argv);
1098 #endif
1099 _exit(127);
1101 #endif
1103 /* parent */
1106 * Enlarge the wait table if there isn't enough space for a new
1107 * entry.
1109 if (table->used == table->size) {
1110 table->size += WAIT_TABLE_GROW_BY;
1111 table->info = Jim_Realloc(table->info, table->size * sizeof(*table->info));
1114 table->info[table->used].pid = pid;
1115 table->info[table->used].flags = 0;
1116 table->used++;
1118 pidPtr[numPids] = pid;
1120 /* Restore in case of pipe_dup_err */
1121 errorId = origErrorId;
1124 * Close off our copies of file descriptors that were set up for
1125 * this child, then set up the input for the next child.
1128 if (inputId != -1) {
1129 close(inputId);
1131 if (outputId != -1) {
1132 close(outputId);
1134 inputId = pipeIds[0];
1135 pipeIds[0] = pipeIds[1] = -1;
1137 *pidArrayPtr = pidPtr;
1140 * All done. Cleanup open files lying around and then return.
1143 cleanup:
1144 if (inputId != -1) {
1145 close(inputId);
1147 if (lastOutputId != -1) {
1148 close(lastOutputId);
1150 if (errorId != -1) {
1151 close(errorId);
1153 Jim_Free(arg_array);
1155 JimRestoreEnv(save_environ);
1157 return numPids;
1160 * An error occurred. There could have been extra files open, such
1161 * as pipes between children. Clean them all up. Detach any child
1162 * processes that have been created.
1165 error:
1166 if ((inPipePtr != NULL) && (*inPipePtr != -1)) {
1167 close(*inPipePtr);
1168 *inPipePtr = -1;
1170 if ((outPipePtr != NULL) && (*outPipePtr != -1)) {
1171 close(*outPipePtr);
1172 *outPipePtr = -1;
1174 if ((errFilePtr != NULL) && (*errFilePtr != -1)) {
1175 close(*errFilePtr);
1176 *errFilePtr = -1;
1178 if (pipeIds[0] != -1) {
1179 close(pipeIds[0]);
1181 if (pipeIds[1] != -1) {
1182 close(pipeIds[1]);
1184 if (pidPtr != NULL) {
1185 for (i = 0; i < numPids; i++) {
1186 if (pidPtr[i] != JIM_BAD_PID) {
1187 JimDetachPids(table, 1, &pidPtr[i]);
1190 Jim_Free(pidPtr);
1192 numPids = -1;
1193 goto cleanup;
1197 *----------------------------------------------------------------------
1199 * JimCleanupChildren --
1201 * This is a utility procedure used to wait for child processes
1202 * to exit, record information about abnormal exits.
1204 * Results:
1205 * The return value is a standard Tcl result. If anything at
1206 * weird happened with the child processes, JIM_ERR is returned
1207 * and a structured message is left in $::errorCode.
1208 * If errStrObj is not NULL, abnormal exit details are appended to this object.
1210 * Side effects:
1211 * pidPtr is freed
1213 *----------------------------------------------------------------------
1216 static int JimCleanupChildren(Jim_Interp *interp, int numPids, pidtype *pidPtr, Jim_Obj *errStrObj)
1218 struct WaitInfoTable *table = Jim_CmdPrivData(interp);
1219 int result = JIM_OK;
1220 int i;
1222 /* Now check the return status of each child */
1223 for (i = 0; i < numPids; i++) {
1224 int waitStatus = 0;
1225 if (JimWaitForProcess(table, pidPtr[i], &waitStatus) != JIM_BAD_PID) {
1226 if (JimCheckWaitStatus(interp, pidPtr[i], waitStatus, errStrObj) != JIM_OK) {
1227 result = JIM_ERR;
1231 Jim_Free(pidPtr);
1233 return result;
1236 int Jim_execInit(Jim_Interp *interp)
1238 struct WaitInfoTable *waitinfo;
1239 if (Jim_PackageProvide(interp, "exec", "1.0", JIM_ERRMSG))
1240 return JIM_ERR;
1242 #ifdef SIGPIPE
1244 * Disable SIGPIPE signals: if they were allowed, this process
1245 * might go away unexpectedly if children misbehave. This code
1246 * can potentially interfere with other application code that
1247 * expects to handle SIGPIPEs.
1249 * By doing this in the init function, applications can override
1250 * this later. Note that child processes have SIGPIPE restored
1251 * to the default after vfork().
1253 (void)signal(SIGPIPE, SIG_IGN);
1254 #endif
1256 waitinfo = JimAllocWaitInfoTable();
1257 Jim_CreateCommand(interp, "exec", Jim_ExecCmd, waitinfo, JimFreeWaitInfoTable);
1258 waitinfo->refcount++;
1259 Jim_CreateCommand(interp, "wait", Jim_WaitCommand, waitinfo, JimFreeWaitInfoTable);
1260 Jim_CreateCommand(interp, "pid", Jim_PidCommand, 0, 0);
1262 return JIM_OK;
1265 #if defined(__MINGW32__)
1266 /* Windows-specific (mingw) implementation */
1268 static int
1269 JimWinFindExecutable(const char *originalName, char fullPath[MAX_PATH])
1271 int i;
1272 static char extensions[][5] = {".exe", "", ".bat"};
1274 for (i = 0; i < (int) (sizeof(extensions) / sizeof(extensions[0])); i++) {
1275 snprintf(fullPath, MAX_PATH, "%s%s", originalName, extensions[i]);
1277 if (SearchPath(NULL, fullPath, NULL, MAX_PATH, fullPath, NULL) == 0) {
1278 continue;
1280 if (GetFileAttributes(fullPath) & FILE_ATTRIBUTE_DIRECTORY) {
1281 continue;
1283 return 0;
1286 return -1;
1289 static char **JimSaveEnv(char **env)
1291 return env;
1294 static void JimRestoreEnv(char **env)
1296 JimFreeEnv(env, Jim_GetEnviron());
1299 static char **JimOriginalEnviron(void)
1301 return NULL;
1304 static Jim_Obj *
1305 JimWinBuildCommandLine(Jim_Interp *interp, char **argv)
1307 char *start, *special;
1308 int quote, i;
1310 Jim_Obj *strObj = Jim_NewStringObj(interp, "", 0);
1312 for (i = 0; argv[i]; i++) {
1313 if (i > 0) {
1314 Jim_AppendString(interp, strObj, " ", 1);
1317 if (argv[i][0] == '\0') {
1318 quote = 1;
1320 else {
1321 quote = 0;
1322 for (start = argv[i]; *start != '\0'; start++) {
1323 if (isspace(UCHAR(*start))) {
1324 quote = 1;
1325 break;
1329 if (quote) {
1330 Jim_AppendString(interp, strObj, "\"" , 1);
1333 start = argv[i];
1334 for (special = argv[i]; ; ) {
1335 if ((*special == '\\') && (special[1] == '\\' ||
1336 special[1] == '"' || (quote && special[1] == '\0'))) {
1337 Jim_AppendString(interp, strObj, start, special - start);
1338 start = special;
1339 while (1) {
1340 special++;
1341 if (*special == '"' || (quote && *special == '\0')) {
1343 * N backslashes followed a quote -> insert
1344 * N * 2 + 1 backslashes then a quote.
1347 Jim_AppendString(interp, strObj, start, special - start);
1348 break;
1350 if (*special != '\\') {
1351 break;
1354 Jim_AppendString(interp, strObj, start, special - start);
1355 start = special;
1357 if (*special == '"') {
1358 if (special == start) {
1359 Jim_AppendString(interp, strObj, "\"", 1);
1361 else {
1362 Jim_AppendString(interp, strObj, start, special - start);
1364 Jim_AppendString(interp, strObj, "\\\"", 2);
1365 start = special + 1;
1367 if (*special == '\0') {
1368 break;
1370 special++;
1372 Jim_AppendString(interp, strObj, start, special - start);
1373 if (quote) {
1374 Jim_AppendString(interp, strObj, "\"", 1);
1377 return strObj;
1381 * Note that inputId, etc. are osf_handles.
1383 static pidtype
1384 JimStartWinProcess(Jim_Interp *interp, char **argv, char **env, int inputId, int outputId, int errorId)
1386 STARTUPINFO startInfo;
1387 PROCESS_INFORMATION procInfo;
1388 HANDLE hProcess;
1389 char execPath[MAX_PATH];
1390 pidtype pid = JIM_BAD_PID;
1391 Jim_Obj *cmdLineObj;
1392 char *winenv;
1394 if (JimWinFindExecutable(argv[0], execPath) < 0) {
1395 return JIM_BAD_PID;
1397 argv[0] = execPath;
1399 hProcess = GetCurrentProcess();
1400 cmdLineObj = JimWinBuildCommandLine(interp, argv);
1403 * STARTF_USESTDHANDLES must be used to pass handles to child process.
1404 * Using SetStdHandle() and/or dup2() only works when a console mode
1405 * parent process is spawning an attached console mode child process.
1408 ZeroMemory(&startInfo, sizeof(startInfo));
1409 startInfo.cb = sizeof(startInfo);
1410 startInfo.dwFlags = STARTF_USESTDHANDLES;
1411 startInfo.hStdInput = INVALID_HANDLE_VALUE;
1412 startInfo.hStdOutput= INVALID_HANDLE_VALUE;
1413 startInfo.hStdError = INVALID_HANDLE_VALUE;
1416 * Duplicate all the handles which will be passed off as stdin, stdout
1417 * and stderr of the child process. The duplicate handles are set to
1418 * be inheritable, so the child process can use them.
1421 * If stdin was not redirected, input should come from the parent's stdin
1423 if (inputId == -1) {
1424 inputId = _fileno(stdin);
1426 DuplicateHandle(hProcess, (HANDLE)_get_osfhandle(inputId), hProcess, &startInfo.hStdInput,
1427 0, TRUE, DUPLICATE_SAME_ACCESS);
1428 if (startInfo.hStdInput == INVALID_HANDLE_VALUE) {
1429 goto end;
1433 * If stdout was not redirected, output should go to the parent's stdout
1435 if (outputId == -1) {
1436 outputId = _fileno(stdout);
1438 DuplicateHandle(hProcess, (HANDLE)_get_osfhandle(outputId), hProcess, &startInfo.hStdOutput,
1439 0, TRUE, DUPLICATE_SAME_ACCESS);
1440 if (startInfo.hStdOutput == INVALID_HANDLE_VALUE) {
1441 goto end;
1444 /* Ditto stderr */
1445 if (errorId == -1) {
1446 errorId = _fileno(stderr);
1448 DuplicateHandle(hProcess, (HANDLE)_get_osfhandle(errorId), hProcess, &startInfo.hStdError,
1449 0, TRUE, DUPLICATE_SAME_ACCESS);
1450 if (startInfo.hStdError == INVALID_HANDLE_VALUE) {
1451 goto end;
1454 /* If env is NULL, use the original environment.
1455 * If env[0] is NULL, use an empty environment.
1456 * Otherwise use the environment starting at env[0]
1458 if (env == NULL) {
1459 /* Use the original environment */
1460 winenv = NULL;
1462 else if (env[0] == NULL) {
1463 winenv = (char *)"\0";
1465 else {
1466 winenv = env[0];
1469 if (!CreateProcess(NULL, (char *)Jim_String(cmdLineObj), NULL, NULL, TRUE,
1470 0, winenv, NULL, &startInfo, &procInfo)) {
1471 goto end;
1475 * "When an application spawns a process repeatedly, a new thread
1476 * instance will be created for each process but the previous
1477 * instances may not be cleaned up. This results in a significant
1478 * virtual memory loss each time the process is spawned. If there
1479 * is a WaitForInputIdle() call between CreateProcess() and
1480 * CloseHandle(), the problem does not occur." PSS ID Number: Q124121
1483 WaitForInputIdle(procInfo.hProcess, 5000);
1484 CloseHandle(procInfo.hThread);
1486 pid = procInfo.hProcess;
1488 end:
1489 Jim_FreeNewObj(interp, cmdLineObj);
1490 if (startInfo.hStdInput != INVALID_HANDLE_VALUE) {
1491 CloseHandle(startInfo.hStdInput);
1493 if (startInfo.hStdOutput != INVALID_HANDLE_VALUE) {
1494 CloseHandle(startInfo.hStdOutput);
1496 if (startInfo.hStdError != INVALID_HANDLE_VALUE) {
1497 CloseHandle(startInfo.hStdError);
1499 return pid;
1502 #else
1504 static char **JimOriginalEnviron(void)
1506 return Jim_GetEnviron();
1509 static char **JimSaveEnv(char **env)
1511 char **saveenv = Jim_GetEnviron();
1512 Jim_SetEnviron(env);
1513 return saveenv;
1516 static void JimRestoreEnv(char **env)
1518 JimFreeEnv(Jim_GetEnviron(), env);
1519 Jim_SetEnviron(env);
1521 #endif
1522 #endif