Change Jim_EvalObjPrefix to accept arbitrary objects
[jimtcl.git] / jim-exec.c
blob50917f27a1568715c8ab886073da21d1bfa897d8
2 /*
3 * (c) 2008 Steve Bennett <steveb@workware.net.au>
5 * Implements the exec command for Jim
7 * Based on code originally from Tcl 6.7 by John Ousterhout.
8 * From that code:
10 * The Tcl_Fork and Tcl_WaitPids procedures are based on code
11 * contributed by Karl Lehenbauer, Mark Diekhans and Peter
12 * da Silva.
14 * Copyright 1987-1991 Regents of the University of California
15 * Permission to use, copy, modify, and distribute this
16 * software and its documentation for any purpose and without
17 * fee is hereby granted, provided that the above copyright
18 * notice appear in all copies. The University of California
19 * makes no representations about the suitability of this
20 * software for any purpose. It is provided "as is" without
21 * express or implied warranty.
24 #include <string.h>
25 #include <signal.h>
27 #include "jim.h"
28 #include "jimautoconf.h"
30 #if defined(HAVE_VFORK) && defined(HAVE_WAITPID)
32 #include "jim-signal.h"
34 #include <unistd.h>
35 #include <fcntl.h>
36 #include <errno.h>
37 #include <sys/wait.h>
39 #if defined(__GNUC__) && !defined(__clang__)
40 #define IGNORE_RC(EXPR) ((EXPR) < 0 ? -1 : 0)
41 #else
42 #define IGNORE_RC(EXPR) EXPR
43 #endif
45 /* These two could be moved into the Tcl core */
46 static void Jim_SetResultErrno(Jim_Interp *interp, const char *msg)
48 Jim_SetResultFormatted(interp, "%s: %s", msg, strerror(errno));
51 static void Jim_RemoveTrailingNewline(Jim_Obj *objPtr)
53 int len;
54 const char *s = Jim_GetString(objPtr, &len);
56 if (len > 0 && s[len - 1] == '\n') {
57 objPtr->length--;
58 objPtr->bytes[objPtr->length] = '\0';
62 /**
63 * Read from 'fd' and append the data to strObj
64 * Returns JIM_OK if OK, or JIM_ERR on error.
66 static int JimAppendStreamToString(Jim_Interp *interp, int fd, Jim_Obj *strObj)
68 while (1) {
69 char buffer[256];
70 int count;
72 count = read(fd, buffer, sizeof(buffer));
74 if (count == 0) {
75 Jim_RemoveTrailingNewline(strObj);
76 return JIM_OK;
78 if (count < 0) {
79 return JIM_ERR;
81 Jim_AppendString(interp, strObj, buffer, count);
86 * If the last character of the result is a newline, then remove
87 * the newline character (the newline would just confuse things).
89 * Note: Ideally we could do this by just reducing the length of stringrep
90 * by 1, but there is no API for this :-(
92 static void JimTrimTrailingNewline(Jim_Interp *interp)
94 int len;
95 const char *p = Jim_GetString(Jim_GetResult(interp), &len);
97 if (len > 0 && p[len - 1] == '\n') {
98 Jim_SetResultString(interp, p, len - 1);
103 * Builds the environment array from $::env
105 * If $::env is not set, simply returns environ.
107 * Otherwise allocates the environ array from the contents of $::env
109 * If the exec fails, memory can be freed via JimFreeEnv()
111 static char **JimBuildEnv(Jim_Interp *interp)
113 #ifdef jim_ext_tclcompat
114 int i;
115 int len;
116 int n;
117 char **env;
119 Jim_Obj *objPtr = Jim_GetGlobalVariableStr(interp, "env", JIM_NONE);
121 if (!objPtr) {
122 return Jim_GetEnviron();
125 /* Calculate the required size */
126 len = Jim_ListLength(interp, objPtr);
127 if (len % 2) {
128 len--;
131 env = Jim_Alloc(sizeof(*env) * (len / 2 + 1));
133 n = 0;
134 for (i = 0; i < len; i += 2) {
135 int l1, l2;
136 const char *s1, *s2;
137 Jim_Obj *elemObj;
139 Jim_ListIndex(interp, objPtr, i, &elemObj, JIM_NONE);
140 s1 = Jim_GetString(elemObj, &l1);
141 Jim_ListIndex(interp, objPtr, i + 1, &elemObj, JIM_NONE);
142 s2 = Jim_GetString(elemObj, &l2);
144 env[n] = Jim_Alloc(l1 + l2 + 2);
145 sprintf(env[n], "%s=%s", s1, s2);
146 n++;
148 env[n] = NULL;
150 return env;
151 #else
152 return Jim_GetEnviron();
153 #endif
157 * Frees the environment allocated by JimBuildEnv()
159 * Must pass original_environ.
161 static void JimFreeEnv(Jim_Interp *interp, char **env, char **original_environ)
163 #ifdef jim_ext_tclcompat
164 if (env != original_environ) {
165 int i;
166 for (i = 0; env[i]; i++) {
167 Jim_Free(env[i]);
169 Jim_Free(env);
171 #endif
175 * Create error messages for unusual process exits. An
176 * extra newline gets appended to each error message, but
177 * it gets removed below (in the same fashion that an
178 * extra newline in the command's output is removed).
180 static int JimCheckWaitStatus(Jim_Interp *interp, int pid, int waitStatus)
182 Jim_Obj *errorCode = Jim_NewListObj(interp, NULL, 0);
183 int rc = JIM_ERR;
185 if (WIFEXITED(waitStatus)) {
186 if (WEXITSTATUS(waitStatus) == 0) {
187 Jim_ListAppendElement(interp, errorCode, Jim_NewStringObj(interp, "NONE", -1));
188 rc = JIM_OK;
190 else {
191 Jim_ListAppendElement(interp, errorCode, Jim_NewStringObj(interp, "CHILDSTATUS", -1));
192 Jim_ListAppendElement(interp, errorCode, Jim_NewIntObj(interp, pid));
193 Jim_ListAppendElement(interp, errorCode, Jim_NewIntObj(interp, WEXITSTATUS(waitStatus)));
196 else {
197 const char *type;
198 const char *action;
200 if (WIFSIGNALED(waitStatus)) {
201 type = "CHILDKILLED";
202 action = "killed";
204 else {
205 type = "CHILDSUSP";
206 action = "suspended";
209 Jim_ListAppendElement(interp, errorCode, Jim_NewStringObj(interp, type, -1));
211 #ifdef jim_ext_signal
212 Jim_SetResultFormatted(interp, "child %s by signal %s", action, Jim_SignalId(WTERMSIG(waitStatus)));
213 Jim_ListAppendElement(interp, errorCode, Jim_NewStringObj(interp, Jim_SignalId(WTERMSIG(waitStatus)), -1));
214 Jim_ListAppendElement(interp, errorCode, Jim_NewIntObj(interp, pid));
215 Jim_ListAppendElement(interp, errorCode, Jim_NewStringObj(interp, Jim_SignalName(WTERMSIG(waitStatus)), -1));
216 #else
217 Jim_SetResultFormatted(interp, "child %s by signal %d", action, WTERMSIG(waitStatus));
218 Jim_ListAppendElement(interp, errorCode, Jim_NewIntObj(interp, WTERMSIG(waitStatus)));
219 Jim_ListAppendElement(interp, errorCode, Jim_NewIntObj(interp, pid));
220 Jim_ListAppendElement(interp, errorCode, Jim_NewIntObj(interp, WTERMSIG(waitStatus)));
221 #endif
223 Jim_SetGlobalVariableStr(interp, "errorCode", errorCode);
224 return rc;
228 * Data structures of the following type are used by JimFork and
229 * JimWaitPids to keep track of child processes.
232 struct WaitInfo
234 int pid; /* Process id of child. */
235 int status; /* Status returned when child exited or suspended. */
236 int flags; /* Various flag bits; see below for definitions. */
239 struct WaitInfoTable {
240 struct WaitInfo *info;
241 int size;
242 int used;
246 * Flag bits in WaitInfo structures:
248 * WI_DETACHED - Non-zero means no-one cares about the
249 * process anymore. Ignore it until it
250 * exits, then forget about it.
253 #define WI_DETACHED 2
255 #define WAIT_TABLE_GROW_BY 4
257 static void JimFreeWaitInfoTable(struct Jim_Interp *interp, void *privData)
259 struct WaitInfoTable *table = privData;
261 Jim_Free(table->info);
262 Jim_Free(table);
265 static struct WaitInfoTable *JimAllocWaitInfoTable(void)
267 struct WaitInfoTable *table = Jim_Alloc(sizeof(*table));
268 table->info = NULL;
269 table->size = table->used = 0;
271 return table;
274 static int Jim_CreatePipeline(Jim_Interp *interp, int argc, Jim_Obj *const *argv,
275 int **pidArrayPtr, int *inPipePtr, int *outPipePtr, int *errFilePtr);
276 static void JimDetachPids(Jim_Interp *interp, int numPids, const int *pidPtr);
277 static int Jim_CleanupChildren(Jim_Interp *interp, int numPids, int *pidPtr, int errorId);
279 static int Jim_ExecCmd(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
281 int outputId; /* File id for output pipe. -1
282 * means command overrode. */
283 int errorId; /* File id for temporary file
284 * containing error output. */
285 int *pidPtr;
286 int numPids, result;
289 * See if the command is to be run in background; if so, create
290 * the command, detach it, and return.
292 if (argc > 1 && Jim_CompareStringImmediate(interp, argv[argc - 1], "&")) {
293 Jim_Obj *listObj;
294 int i;
296 argc--;
297 numPids = Jim_CreatePipeline(interp, argc - 1, argv + 1, &pidPtr, NULL, NULL, NULL);
298 if (numPids < 0) {
299 return JIM_ERR;
301 /* The return value is a list of the pids */
302 listObj = Jim_NewListObj(interp, NULL, 0);
303 for (i = 0; i < numPids; i++) {
304 Jim_ListAppendElement(interp, listObj, Jim_NewIntObj(interp, pidPtr[i]));
306 Jim_SetResult(interp, listObj);
307 JimDetachPids(interp, numPids, pidPtr);
308 Jim_Free(pidPtr);
309 return JIM_OK;
313 * Create the command's pipeline.
315 numPids =
316 Jim_CreatePipeline(interp, argc - 1, argv + 1, &pidPtr, (int *)NULL, &outputId, &errorId);
317 if (numPids < 0) {
318 return JIM_ERR;
322 * Read the child's output (if any) and put it into the result.
324 Jim_SetResultString(interp, "", 0);
326 result = JIM_OK;
327 if (outputId != -1) {
328 result = JimAppendStreamToString(interp, outputId, Jim_GetResult(interp));
329 if (result < 0) {
330 Jim_SetResultErrno(interp, "error reading from output pipe");
332 close(outputId);
335 if (Jim_CleanupChildren(interp, numPids, pidPtr, errorId) != JIM_OK) {
336 result = JIM_ERR;
338 return result;
341 void Jim_ReapDetachedPids(struct WaitInfoTable *table)
343 struct WaitInfo *waitPtr;
344 int count;
346 if (!table) {
347 return;
350 for (waitPtr = table->info, count = table->used; count > 0; waitPtr++, count--) {
351 if (waitPtr->flags & WI_DETACHED) {
352 int status;
353 int pid = waitpid(waitPtr->pid, &status, WNOHANG);
354 if (pid > 0) {
355 if (waitPtr != &table->info[table->used - 1]) {
356 *waitPtr = table->info[table->used - 1];
358 table->used--;
365 * Does waitpid() on the given pid, and then removes the
366 * entry from the wait table.
368 * Returns the pid if OK and updates *statusPtr with the status,
369 * or -1 if the pid was not in the table.
371 static int JimWaitPid(struct WaitInfoTable *table, int pid, int *statusPtr)
373 int i;
375 /* Find it in the table */
376 for (i = 0; i < table->used; i++) {
377 if (pid == table->info[i].pid) {
378 /* wait for it */
379 waitpid(pid, statusPtr, 0);
381 /* Remove it from the table */
382 if (i != table->used - 1) {
383 table->info[i] = table->info[table->used - 1];
385 table->used--;
386 return pid;
390 /* Not found */
391 return -1;
395 *----------------------------------------------------------------------
397 * JimDetachPids --
399 * This procedure is called to indicate that one or more child
400 * processes have been placed in background and are no longer
401 * cared about. These children can be cleaned up with JimReapDetachedPids().
403 * Results:
404 * None.
406 * Side effects:
407 * None.
409 *----------------------------------------------------------------------
412 static void JimDetachPids(Jim_Interp *interp, int numPids, const int *pidPtr)
414 int j;
415 struct WaitInfoTable *table = Jim_CmdPrivData(interp);
417 for (j = 0; j < numPids; j++) {
418 /* Find it in the table */
419 int i;
420 for (i = 0; i < table->used; i++) {
421 if (pidPtr[j] == table->info[i].pid) {
422 table->info[i].flags |= WI_DETACHED;
423 break;
430 *----------------------------------------------------------------------
432 * Jim_CreatePipeline --
434 * Given an argc/argv array, instantiate a pipeline of processes
435 * as described by the argv.
437 * Results:
438 * The return value is a count of the number of new processes
439 * created, or -1 if an error occurred while creating the pipeline.
440 * *pidArrayPtr is filled in with the address of a dynamically
441 * allocated array giving the ids of all of the processes. It
442 * is up to the caller to free this array when it isn't needed
443 * anymore. If inPipePtr is non-NULL, *inPipePtr is filled in
444 * with the file id for the input pipe for the pipeline (if any):
445 * the caller must eventually close this file. If outPipePtr
446 * isn't NULL, then *outPipePtr is filled in with the file id
447 * for the output pipe from the pipeline: the caller must close
448 * this file. If errFilePtr isn't NULL, then *errFilePtr is filled
449 * with a file id that may be used to read error output after the
450 * pipeline completes.
452 * Side effects:
453 * Processes and pipes are created.
455 *----------------------------------------------------------------------
457 static int
458 Jim_CreatePipeline(Jim_Interp *interp, int argc, Jim_Obj *const *argv, int **pidArrayPtr,
459 int *inPipePtr, int *outPipePtr, int *errFilePtr)
461 int *pidPtr = NULL; /* Points to malloc-ed array holding all
462 * the pids of child processes. */
463 int numPids = 0; /* Actual number of processes that exist
464 * at *pidPtr right now. */
465 int cmdCount; /* Count of number of distinct commands
466 * found in argc/argv. */
467 const char *input = NULL; /* Describes input for pipeline, depending
468 * on "inputFile". NULL means take input
469 * from stdin/pipe. */
471 #define FILE_NAME 0 /* input/output: filename */
472 #define FILE_APPEND 1 /* output only: filename, append */
473 #define FILE_HANDLE 2 /* input/output: filehandle */
474 #define FILE_TEXT 3 /* input only: input is actual text */
476 int inputFile = FILE_NAME; /* 1 means input is name of input file.
477 * 2 means input is filehandle name.
478 * 0 means input holds actual
479 * text to be input to command. */
481 int outputFile = FILE_NAME; /* 0 means output is the name of output file.
482 * 1 means output is the name of output file, and append.
483 * 2 means output is filehandle name.
484 * All this is ignored if output is NULL
486 int errorFile = FILE_NAME; /* 0 means error is the name of error file.
487 * 1 means error is the name of error file, and append.
488 * 2 means error is filehandle name.
489 * All this is ignored if error is NULL
491 const char *output = NULL; /* Holds name of output file to pipe to,
492 * or NULL if output goes to stdout/pipe. */
493 const char *error = NULL; /* Holds name of stderr file to pipe to,
494 * or NULL if stderr goes to stderr/pipe. */
495 int inputId = -1; /* Readable file id input to current command in
496 * pipeline (could be file or pipe). -1
497 * means use stdin. */
498 int outputId = -1; /* Writable file id for output from current
499 * command in pipeline (could be file or pipe).
500 * -1 means use stdout. */
501 int errorId = -1; /* Writable file id for all standard error
502 * output from all commands in pipeline. -1
503 * means use stderr. */
504 int lastOutputId = -1; /* Write file id for output from last command
505 * in pipeline (could be file or pipe).
506 * -1 means use stdout. */
507 int pipeIds[2]; /* File ids for pipe that's being created. */
508 int firstArg, lastArg; /* Indexes of first and last arguments in
509 * current command. */
510 int lastBar;
511 char *execName;
512 int i, pid;
513 char **orig_environ;
514 struct WaitInfoTable *table = Jim_CmdPrivData(interp);
516 /* Holds the args which will be used to exec */
517 char **arg_array = Jim_Alloc(sizeof(*arg_array) * (argc + 1));
518 int arg_count = 0;
520 Jim_ReapDetachedPids(table);
522 if (inPipePtr != NULL) {
523 *inPipePtr = -1;
525 if (outPipePtr != NULL) {
526 *outPipePtr = -1;
528 if (errFilePtr != NULL) {
529 *errFilePtr = -1;
531 pipeIds[0] = pipeIds[1] = -1;
534 * First, scan through all the arguments to figure out the structure
535 * of the pipeline. Count the number of distinct processes (it's the
536 * number of "|" arguments). If there are "<", "<<", or ">" arguments
537 * then make note of input and output redirection and remove these
538 * arguments and the arguments that follow them.
540 cmdCount = 1;
541 lastBar = -1;
542 for (i = 0; i < argc; i++) {
543 const char *arg = Jim_String(argv[i]);
545 if (arg[0] == '<') {
546 inputFile = FILE_NAME;
547 input = arg + 1;
548 if (*input == '<') {
549 inputFile = FILE_TEXT;
550 input++;
552 else if (*input == '@') {
553 inputFile = FILE_HANDLE;
554 input++;
557 if (!*input && ++i < argc) {
558 input = Jim_String(argv[i]);
561 else if (arg[0] == '>') {
562 int dup_error = 0;
564 outputFile = FILE_NAME;
566 output = arg + 1;
567 if (*output == '>') {
568 outputFile = FILE_APPEND;
569 output++;
571 if (*output == '&') {
572 /* Redirect stderr too */
573 output++;
574 dup_error = 1;
576 if (*output == '@') {
577 outputFile = FILE_HANDLE;
578 output++;
580 if (!*output && ++i < argc) {
581 output = Jim_String(argv[i]);
583 if (dup_error) {
584 errorFile = outputFile;
585 error = output;
588 else if (arg[0] == '2' && arg[1] == '>') {
589 error = arg + 2;
590 errorFile = FILE_NAME;
592 if (*error == '@') {
593 errorFile = FILE_HANDLE;
594 error++;
596 else if (*error == '>') {
597 errorFile = FILE_APPEND;
598 error++;
600 if (!*error && ++i < argc) {
601 error = Jim_String(argv[i]);
604 else {
605 if (strcmp(arg, "|") == 0 || strcmp(arg, "|&") == 0) {
606 if (i == lastBar + 1 || i == argc - 1) {
607 Jim_SetResultString(interp, "illegal use of | or |& in command", -1);
608 goto badargs;
610 lastBar = i;
611 cmdCount++;
613 /* Either |, |& or a "normal" arg, so store it in the arg array */
614 arg_array[arg_count++] = (char *)arg;
615 continue;
618 if (i >= argc) {
619 Jim_SetResultFormatted(interp, "can't specify \"%s\" as last word in command", arg);
620 goto badargs;
624 if (arg_count == 0) {
625 Jim_SetResultString(interp, "didn't specify command to execute", -1);
626 badargs:
627 Jim_Free(arg_array);
628 return -1;
631 /* Must do this before vfork(), so do it now */
632 orig_environ = Jim_GetEnviron();
633 Jim_SetEnviron(JimBuildEnv(interp));
636 * Set up the redirected input source for the pipeline, if
637 * so requested.
639 if (input != NULL) {
640 if (inputFile == FILE_TEXT) {
642 * Immediate data in command. Create temporary file and
643 * put data into file.
646 #define TMP_STDIN_NAME "/tmp/tcl.in.XXXXXX"
647 char inName[sizeof(TMP_STDIN_NAME) + 1];
648 int length;
650 strcpy(inName, TMP_STDIN_NAME);
651 inputId = mkstemp(inName);
652 if (inputId < 0) {
653 Jim_SetResultErrno(interp, "couldn't create input file for command");
654 goto error;
656 length = strlen(input);
657 if (write(inputId, input, length) != length) {
658 Jim_SetResultErrno(interp, "couldn't write file input for command");
659 goto error;
661 if (lseek(inputId, 0L, SEEK_SET) == -1 || unlink(inName) == -1) {
662 Jim_SetResultErrno(interp, "couldn't reset or remove input file for command");
663 goto error;
666 else if (inputFile == FILE_HANDLE) {
667 /* Should be a file descriptor */
668 Jim_Obj *fhObj = Jim_NewStringObj(interp, input, -1);
669 FILE *fh = Jim_AioFilehandle(interp, fhObj);
671 Jim_FreeNewObj(interp, fhObj);
672 if (fh == NULL) {
673 goto error;
675 inputId = dup(fileno(fh));
677 else {
679 * File redirection. Just open the file.
681 inputId = open(input, O_RDONLY, 0);
682 if (inputId < 0) {
683 Jim_SetResultFormatted(interp, "couldn't read file \"%s\": %s", input,
684 strerror(errno));
685 goto error;
689 else if (inPipePtr != NULL) {
690 if (pipe(pipeIds) != 0) {
691 Jim_SetResultErrno(interp, "couldn't create input pipe for command");
692 goto error;
694 inputId = pipeIds[0];
695 *inPipePtr = pipeIds[1];
696 pipeIds[0] = pipeIds[1] = -1;
700 * Set up the redirected output sink for the pipeline from one
701 * of two places, if requested.
703 if (output != NULL) {
704 if (outputFile == FILE_HANDLE) {
705 Jim_Obj *fhObj = Jim_NewStringObj(interp, output, -1);
706 FILE *fh = Jim_AioFilehandle(interp, fhObj);
708 Jim_FreeNewObj(interp, fhObj);
709 if (fh == NULL) {
710 goto error;
712 fflush(fh);
713 lastOutputId = dup(fileno(fh));
715 else {
717 * Output is to go to a file.
719 int mode = O_WRONLY | O_CREAT | O_TRUNC;
721 if (outputFile == FILE_APPEND) {
722 mode = O_WRONLY | O_CREAT | O_APPEND;
725 lastOutputId = open(output, mode, 0666);
726 if (lastOutputId < 0) {
727 Jim_SetResultFormatted(interp, "couldn't write file \"%s\": %s", output,
728 strerror(errno));
729 goto error;
733 else if (outPipePtr != NULL) {
735 * Output is to go to a pipe.
737 if (pipe(pipeIds) != 0) {
738 Jim_SetResultErrno(interp, "couldn't create output pipe");
739 goto error;
741 lastOutputId = pipeIds[1];
742 *outPipePtr = pipeIds[0];
743 pipeIds[0] = pipeIds[1] = -1;
746 /* If we are redirecting stderr with 2>filename or 2>@fileId, then we ignore errFilePtr */
747 if (error != NULL) {
748 if (errorFile == FILE_HANDLE) {
749 if (strcmp(error, "1") == 0) {
750 /* Special 2>@1 */
751 if (lastOutputId >= 0) {
752 errorId = dup(lastOutputId);
754 else {
755 /* No redirection of stdout, so just use 2>@stdout */
756 error = "stdout";
759 if (errorId < 0) {
760 Jim_Obj *fhObj = Jim_NewStringObj(interp, error, -1);
761 FILE *fh = Jim_AioFilehandle(interp, fhObj);
763 Jim_FreeNewObj(interp, fhObj);
764 if (fh == NULL) {
765 goto error;
767 fflush(fh);
768 errorId = dup(fileno(fh));
771 else {
773 * Output is to go to a file.
775 int mode = O_WRONLY | O_CREAT | O_TRUNC;
777 if (errorFile == FILE_APPEND) {
778 mode = O_WRONLY | O_CREAT | O_APPEND;
781 errorId = open(error, mode, 0666);
782 if (errorId < 0) {
783 Jim_SetResultFormatted(interp, "couldn't write file \"%s\": %s", error,
784 strerror(errno));
788 else if (errFilePtr != NULL) {
790 * Set up the standard error output sink for the pipeline, if
791 * requested. Use a temporary file which is opened, then deleted.
792 * Could potentially just use pipe, but if it filled up it could
793 * cause the pipeline to deadlock: we'd be waiting for processes
794 * to complete before reading stderr, and processes couldn't complete
795 * because stderr was backed up.
798 #define TMP_STDERR_NAME "/tmp/tcl.err.XXXXXX"
799 char errName[sizeof(TMP_STDERR_NAME) + 1];
801 strcpy(errName, TMP_STDERR_NAME);
802 errorId = mkstemp(errName);
803 if (errorId < 0) {
804 errFileError:
805 Jim_SetResultErrno(interp, "couldn't create error file for command");
806 goto error;
808 *errFilePtr = open(errName, O_RDONLY, 0);
809 if (*errFilePtr < 0) {
810 goto errFileError;
812 if (unlink(errName) == -1) {
813 Jim_SetResultErrno(interp, "couldn't remove error file for command");
814 goto error;
819 * Scan through the argc array, forking off a process for each
820 * group of arguments between "|" arguments.
823 pidPtr = (int *)Jim_Alloc(cmdCount * sizeof(*pidPtr));
824 for (i = 0; i < numPids; i++) {
825 pidPtr[i] = -1;
827 for (firstArg = 0; firstArg < arg_count; numPids++, firstArg = lastArg + 1) {
828 int pipe_dup_err = 0;
829 int origErrorId = errorId;
830 char execerr[64];
831 int execerrlen;
833 for (lastArg = firstArg; lastArg < arg_count; lastArg++) {
834 if (arg_array[lastArg][0] == '|') {
835 if (arg_array[lastArg][1] == '&') {
836 pipe_dup_err = 1;
838 break;
841 /* Replace | with NULL for execv() */
842 arg_array[lastArg] = NULL;
843 if (lastArg == arg_count) {
844 outputId = lastOutputId;
846 else {
847 if (pipe(pipeIds) != 0) {
848 Jim_SetResultErrno(interp, "couldn't create pipe");
849 goto error;
851 outputId = pipeIds[1];
853 execName = arg_array[firstArg];
855 /* Now fork the child */
858 * Disable SIGPIPE signals: if they were allowed, this process
859 * might go away unexpectedly if children misbehave. This code
860 * can potentially interfere with other application code that
861 * expects to handle SIGPIPEs; what's really needed is an
862 * arbiter for signals to allow them to be "shared".
864 if (table->info == NULL) {
865 (void)signal(SIGPIPE, SIG_IGN);
868 /* Need to do this befor vfork() */
869 if (pipe_dup_err) {
870 errorId = outputId;
873 /* Need to prep an error message before vfork(), just in case */
874 snprintf(execerr, sizeof(execerr), "couldn't exec \"%s\"", execName);
875 execerrlen = strlen(execerr);
878 * Make a new process and enter it into the table if the fork
879 * is successful.
881 pid = vfork();
882 if (pid < 0) {
883 Jim_SetResultErrno(interp, "couldn't fork child process");
884 goto error;
886 if (pid == 0) {
887 /* Child */
889 if (inputId != -1) dup2(inputId, 0);
890 if (outputId != -1) dup2(outputId, 1);
891 if (errorId != -1) dup2(errorId, 2);
893 for (i = 3; (i <= outputId) || (i <= inputId) || (i <= errorId); i++) {
894 close(i);
897 execvp(execName, &arg_array[firstArg]);
899 /* we really can ignore the error here! */
900 IGNORE_RC(write(2, execerr, execerrlen));
901 _exit(127);
904 /* parent */
907 * Enlarge the wait table if there isn't enough space for a new
908 * entry.
910 if (table->used == table->size) {
911 table->size += WAIT_TABLE_GROW_BY;
912 table->info = Jim_Realloc(table->info, table->size * sizeof(*table->info));
915 table->info[table->used].pid = pid;
916 table->info[table->used].flags = 0;
917 table->used++;
919 pidPtr[numPids] = pid;
921 /* Restore in case of pipe_dup_err */
922 errorId = origErrorId;
925 * Close off our copies of file descriptors that were set up for
926 * this child, then set up the input for the next child.
929 if (inputId != -1) {
930 close(inputId);
932 if (outputId != -1) {
933 close(outputId);
935 inputId = pipeIds[0];
936 pipeIds[0] = pipeIds[1] = -1;
938 *pidArrayPtr = pidPtr;
941 * All done. Cleanup open files lying around and then return.
944 cleanup:
945 if (inputId != -1) {
946 close(inputId);
948 if (lastOutputId != -1) {
949 close(lastOutputId);
951 if (errorId != -1) {
952 close(errorId);
954 Jim_Free(arg_array);
956 JimFreeEnv(interp, Jim_GetEnviron(), orig_environ);
957 Jim_SetEnviron(orig_environ);
959 return numPids;
962 * An error occurred. There could have been extra files open, such
963 * as pipes between children. Clean them all up. Detach any child
964 * processes that have been created.
967 error:
968 if ((inPipePtr != NULL) && (*inPipePtr != -1)) {
969 close(*inPipePtr);
970 *inPipePtr = -1;
972 if ((outPipePtr != NULL) && (*outPipePtr != -1)) {
973 close(*outPipePtr);
974 *outPipePtr = -1;
976 if ((errFilePtr != NULL) && (*errFilePtr != -1)) {
977 close(*errFilePtr);
978 *errFilePtr = -1;
980 if (pipeIds[0] != -1) {
981 close(pipeIds[0]);
983 if (pipeIds[1] != -1) {
984 close(pipeIds[1]);
986 if (pidPtr != NULL) {
987 for (i = 0; i < numPids; i++) {
988 if (pidPtr[i] != -1) {
989 JimDetachPids(interp, 1, &pidPtr[i]);
992 Jim_Free(pidPtr);
994 numPids = -1;
995 goto cleanup;
999 *----------------------------------------------------------------------
1001 * CleanupChildren --
1003 * This is a utility procedure used to wait for child processes
1004 * to exit, record information about abnormal exits, and then
1005 * collect any stderr output generated by them.
1007 * Results:
1008 * The return value is a standard Tcl result. If anything at
1009 * weird happened with the child processes, JIM_ERROR is returned
1010 * and a message is left in interp->result.
1012 * Side effects:
1013 * If the last character of interp->result is a newline, then it
1014 * is removed. File errorId gets closed, and pidPtr is freed
1015 * back to the storage allocator.
1017 *----------------------------------------------------------------------
1020 static int Jim_CleanupChildren(Jim_Interp *interp, int numPids, int *pidPtr, int errorId)
1022 struct WaitInfoTable *table = Jim_CmdPrivData(interp);
1023 int result = JIM_OK;
1024 int i;
1026 for (i = 0; i < numPids; i++) {
1027 int waitStatus = 0;
1028 if (JimWaitPid(table, pidPtr[i], &waitStatus) > 0) {
1029 if (JimCheckWaitStatus(interp, pidPtr[i], waitStatus) != JIM_OK) {
1030 result = JIM_ERR;
1034 Jim_Free(pidPtr);
1037 * Read the standard error file. If there's anything there,
1038 * then add the file's contents to the result
1039 * string.
1041 if (errorId >= 0) {
1042 if (JimAppendStreamToString(interp, errorId, Jim_GetResult(interp)) != JIM_OK) {
1043 Jim_SetResultErrno(interp, "error reading from stderr output file");
1044 result = JIM_ERR;
1046 close(errorId);
1049 JimTrimTrailingNewline(interp);
1051 return result;
1054 int Jim_execInit(Jim_Interp *interp)
1056 if (Jim_PackageProvide(interp, "exec", "1.0", JIM_ERRMSG))
1057 return JIM_ERR;
1059 Jim_CreateCommand(interp, "exec", Jim_ExecCmd, JimAllocWaitInfoTable(), JimFreeWaitInfoTable);
1060 return JIM_OK;
1062 #else
1063 /* e.g. Windows. Poor mans implementation of exec with system()
1064 * The system() call *may* do command line redirection, etc.
1065 * The standard output is not available.
1066 * Can't redirect filehandles.
1068 static int Jim_ExecCmd(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
1070 Jim_Obj *cmdlineObj = Jim_NewEmptyStringObj(interp);
1071 int i, j;
1072 int rc;
1074 /* Create a quoted command line */
1075 for (i = 1; i < argc; i++) {
1076 int len;
1077 const char *arg = Jim_GetString(argv[i], &len);
1079 if (i > 1) {
1080 Jim_AppendString(interp, cmdlineObj, " ", 1);
1082 if (strpbrk(arg, "\\\" ") == NULL) {
1083 /* No quoting required */
1084 Jim_AppendString(interp, cmdlineObj, arg, len);
1085 continue;
1088 Jim_AppendString(interp, cmdlineObj, "\"", 1);
1089 for (j = 0; j < len; j++) {
1090 if (arg[j] == '\\' || arg[j] == '"') {
1091 Jim_AppendString(interp, cmdlineObj, "\\", 1);
1093 Jim_AppendString(interp, cmdlineObj, &arg[j], 1);
1095 Jim_AppendString(interp, cmdlineObj, "\"", 1);
1097 rc = system(Jim_String(cmdlineObj));
1099 Jim_FreeNewObj(interp, cmdlineObj);
1101 if (rc) {
1102 Jim_Obj *errorCode = Jim_NewListObj(interp, NULL, 0);
1103 Jim_ListAppendElement(interp, errorCode, Jim_NewStringObj(interp, "CHILDSTATUS", -1));
1104 Jim_ListAppendElement(interp, errorCode, Jim_NewIntObj(interp, 0));
1105 Jim_ListAppendElement(interp, errorCode, Jim_NewIntObj(interp, rc));
1106 Jim_SetGlobalVariableStr(interp, "errorCode", errorCode);
1107 return JIM_ERR;
1110 return JIM_OK;
1113 int Jim_execInit(Jim_Interp *interp)
1115 if (Jim_PackageProvide(interp, "exec", "1.0", JIM_ERRMSG))
1116 return JIM_ERR;
1118 Jim_CreateCommand(interp, "exec", Jim_ExecCmd, NULL, NULL);
1119 return JIM_OK;
1121 #endif