diff options
-rw-r--r-- | jim-exec.c | 1003 | ||||
-rw-r--r-- | tclcompat.tcl | 2 | ||||
-rw-r--r-- | tests/exec.test | 6 | ||||
-rw-r--r-- | tests/exec2.test | 16 |
4 files changed, 762 insertions, 265 deletions
@@ -1,5 +1,4 @@ - -/* +/* * (c) 2008 Steve Bennett <steveb@workware.net.au> * * Implements the exec command for Jim @@ -22,30 +21,142 @@ */ #include <string.h> -#include <signal.h> +#include <ctype.h> #include "jim.h" #include "jimautoconf.h" -#if defined(HAVE_VFORK) && defined(HAVE_WAITPID) +#if (!defined(HAVE_VFORK) || !defined(HAVE_WAITPID)) && !defined(__MINGW32__) +/* Poor man's implementation of exec with system() + * The system() call *may* do command line redirection, etc. + * The standard output is not available. + * Can't redirect filehandles. + */ +static int Jim_ExecCmd(Jim_Interp *interp, int argc, Jim_Obj *const *argv) +{ + Jim_Obj *cmdlineObj = Jim_NewEmptyStringObj(interp); + int i, j; + int rc; + + /* Create a quoted command line */ + for (i = 1; i < argc; i++) { + int len; + const char *arg = Jim_GetString(argv[i], &len); + + if (i > 1) { + Jim_AppendString(interp, cmdlineObj, " ", 1); + } + if (strpbrk(arg, "\\\" ") == NULL) { + /* No quoting required */ + Jim_AppendString(interp, cmdlineObj, arg, len); + continue; + } + + Jim_AppendString(interp, cmdlineObj, "\"", 1); + for (j = 0; j < len; j++) { + if (arg[j] == '\\' || arg[j] == '"') { + Jim_AppendString(interp, cmdlineObj, "\\", 1); + } + Jim_AppendString(interp, cmdlineObj, &arg[j], 1); + } + Jim_AppendString(interp, cmdlineObj, "\"", 1); + } + rc = system(Jim_String(cmdlineObj)); + + Jim_FreeNewObj(interp, cmdlineObj); + + if (rc) { + Jim_Obj *errorCode = Jim_NewListObj(interp, NULL, 0); + Jim_ListAppendElement(interp, errorCode, Jim_NewStringObj(interp, "CHILDSTATUS", -1)); + Jim_ListAppendElement(interp, errorCode, Jim_NewIntObj(interp, 0)); + Jim_ListAppendElement(interp, errorCode, Jim_NewIntObj(interp, rc)); + Jim_SetGlobalVariableStr(interp, "errorCode", errorCode); + return JIM_ERR; + } -#include "jim-signal.h" + return JIM_OK; +} + +int Jim_execInit(Jim_Interp *interp) +{ + if (Jim_PackageProvide(interp, "exec", "1.0", JIM_ERRMSG)) + return JIM_ERR; + Jim_CreateCommand(interp, "exec", Jim_ExecCmd, NULL, NULL); + return JIM_OK; +} +#else +/* Full exec implementation for unix and mingw */ -#include <unistd.h> -#include <fcntl.h> #include <errno.h> -#include <sys/wait.h> +#include <signal.h> -#if defined(__GNUC__) && !defined(__clang__) -#define IGNORE_RC(EXPR) ((EXPR) < 0 ? -1 : 0) +#define XXX printf("@%s:%d\n", __FILE__, __LINE__); fflush(stdout); + +#if defined(__MINGW32__) + /* XXX: Should we use this implementation for cygwin too? */ + #include <fcntl.h> + + typedef HANDLE fdtype; + typedef HANDLE pidtype; + #define JIM_BAD_FD INVALID_HANDLE_VALUE + #define JIM_BAD_PID INVALID_HANDLE_VALUE + #define JimCloseFd CloseHandle + + #define WIFEXITED(STATUS) 1 + #define WEXITSTATUS(STATUS) (STATUS) + #define WIFSIGNALED(STATUS) 0 + #define WTERMSIG(STATUS) 0 + #define WNOHANG 1 + + static fdtype JimFileno(FILE *fh); + static pidtype JimWaitPid(pidtype pid, int *status, int nohang); + static fdtype JimDupFd(fdtype infd); + static fdtype JimOpenForRead(const char *filename); + static FILE *JimFdOpenForRead(fdtype fd); + static int JimPipe(fdtype pipefd[2]); + static pidtype JimStartWinProcess(Jim_Interp *interp, char **argv, char *env, + fdtype inputId, fdtype outputId, fdtype errorId); + static int JimErrno(void); #else -#define IGNORE_RC(EXPR) EXPR + #include "jim-signal.h" + #include <unistd.h> + #include <fcntl.h> + #include <sys/wait.h> + + typedef int fdtype; + typedef int pidtype; + #define JimPipe pipe + #define JimErrno() errno + #define JIM_BAD_FD -1 + #define JIM_BAD_PID -1 + #define JimFileno fileno + #define JimReadFd read + #define JimCloseFd close + #define JimWaitPid waitpid + #define JimDupFd dup + #define JimFdOpenForRead(FD) fdopen((FD), "r") + #define JimOpenForRead(NAME) open((NAME), O_RDONLY, 0) #endif -/* These two could be moved into the Tcl core */ +static const char *JimStrError(void); +static char **JimSaveEnv(char **env); +static void JimRestoreEnv(char **env); +static int JimCreatePipeline(Jim_Interp *interp, int argc, Jim_Obj *const *argv, + pidtype **pidArrayPtr, fdtype *inPipePtr, fdtype *outPipePtr, fdtype *errFilePtr); +static void JimDetachPids(Jim_Interp *interp, int numPids, const pidtype *pidPtr); +static int JimCleanupChildren(Jim_Interp *interp, int numPids, pidtype *pidPtr, fdtype errorId); +static fdtype JimCreateTemp(Jim_Interp *interp, const char *contents); +static fdtype JimOpenForWrite(const char *filename, int append); +static int JimRewindFd(fdtype fd); + static void Jim_SetResultErrno(Jim_Interp *interp, const char *msg) { - Jim_SetResultFormatted(interp, "%s: %s", msg, strerror(errno)); + Jim_SetResultFormatted(interp, "%s: %s", msg, JimStrError()); +} + +static const char *JimStrError(void) +{ + return strerror(JimErrno()); } static void Jim_RemoveTrailingNewline(Jim_Obj *objPtr) @@ -60,26 +171,29 @@ static void Jim_RemoveTrailingNewline(Jim_Obj *objPtr) } /** - * Read from 'fd' and append the data to strObj + * Read from 'fd', append the data to strObj and close 'fd'. * Returns JIM_OK if OK, or JIM_ERR on error. */ -static int JimAppendStreamToString(Jim_Interp *interp, int fd, Jim_Obj *strObj) +static int JimAppendStreamToString(Jim_Interp *interp, fdtype fd, Jim_Obj *strObj) { - while (1) { - char buffer[256]; - int count; - - count = read(fd, buffer, sizeof(buffer)); + char buf[256]; + FILE *fh = JimFdOpenForRead(fd); + if (fh == NULL) { + return JIM_ERR; + } - if (count == 0) { - Jim_RemoveTrailingNewline(strObj); - return JIM_OK; + while (1) { + int retval = fread(buf, 1, sizeof(buf), fh); + if (retval > 0) { + Jim_AppendString(interp, strObj, buf, retval); } - if (count < 0) { - return JIM_ERR; + if (retval != sizeof(buf)) { + break; } - Jim_AppendString(interp, strObj, buffer, count); } + Jim_RemoveTrailingNewline(strObj); + fclose(fh); + return JIM_OK; } /* @@ -110,11 +224,13 @@ static void JimTrimTrailingNewline(Jim_Interp *interp) */ static char **JimBuildEnv(Jim_Interp *interp) { -#ifdef jim_ext_tclcompat +#if defined(jim_ext_tclcompat) int i; - int len; + int size; + int num; int n; - char **env; + char **envptr; + char *envdata; Jim_Obj *objPtr = Jim_GetGlobalVariableStr(interp, "env", JIM_NONE); @@ -122,32 +238,45 @@ static char **JimBuildEnv(Jim_Interp *interp) return Jim_GetEnviron(); } + /* We build the array as a single block consisting of the pointers followed by + * the strings. This has the advantage of being easy to allocate/free and being + * compatible with both unix and windows + */ + /* Calculate the required size */ - len = Jim_ListLength(interp, objPtr); - if (len % 2) { - len--; + num = Jim_ListLength(interp, objPtr); + if (num % 2) { + num--; } + size = Jim_Length(objPtr); + /* We need one \0 and one equal sign for each element. + * A list has at least one space for each element except the first. + * We only need one extra char for the extra null terminator. + */ + size++; - env = Jim_Alloc(sizeof(*env) * (len / 2 + 1)); + envptr = Jim_Alloc(sizeof(*envptr) * (num / 2 + 1) + size); + envdata = (char *)&envptr[num / 2 + 1]; n = 0; - for (i = 0; i < len; i += 2) { - int l1, l2; + for (i = 0; i < num; i += 2) { const char *s1, *s2; Jim_Obj *elemObj; Jim_ListIndex(interp, objPtr, i, &elemObj, JIM_NONE); - s1 = Jim_GetString(elemObj, &l1); + s1 = Jim_String(elemObj); Jim_ListIndex(interp, objPtr, i + 1, &elemObj, JIM_NONE); - s2 = Jim_GetString(elemObj, &l2); + s2 = Jim_String(elemObj); - env[n] = Jim_Alloc(l1 + l2 + 2); - sprintf(env[n], "%s=%s", s1, s2); + envptr[n] = envdata; + envdata += sprintf(envdata, "%s=%s", s1, s2); + envdata++; n++; } - env[n] = NULL; + envptr[n] = NULL; + *envdata = 0; - return env; + return envptr; #else return Jim_GetEnviron(); #endif @@ -158,14 +287,10 @@ static char **JimBuildEnv(Jim_Interp *interp) * * Must pass original_environ. */ -static void JimFreeEnv(Jim_Interp *interp, char **env, char **original_environ) +static void JimFreeEnv(char **env, char **original_environ) { #ifdef jim_ext_tclcompat if (env != original_environ) { - int i; - for (i = 0; env[i]; i++) { - Jim_Free(env[i]); - } Jim_Free(env); } #endif @@ -177,7 +302,7 @@ static void JimFreeEnv(Jim_Interp *interp, char **env, char **original_environ) * it gets removed below (in the same fashion that an * extra newline in the command's output is removed). */ -static int JimCheckWaitStatus(Jim_Interp *interp, int pid, int waitStatus) +static int JimCheckWaitStatus(Jim_Interp *interp, pidtype pid, int waitStatus) { Jim_Obj *errorCode = Jim_NewListObj(interp, NULL, 0); int rc = JIM_ERR; @@ -189,7 +314,7 @@ static int JimCheckWaitStatus(Jim_Interp *interp, int pid, int waitStatus) } else { Jim_ListAppendElement(interp, errorCode, Jim_NewStringObj(interp, "CHILDSTATUS", -1)); - Jim_ListAppendElement(interp, errorCode, Jim_NewIntObj(interp, pid)); + Jim_ListAppendElement(interp, errorCode, Jim_NewIntObj(interp, (long)pid)); Jim_ListAppendElement(interp, errorCode, Jim_NewIntObj(interp, WEXITSTATUS(waitStatus))); } } @@ -216,7 +341,7 @@ static int JimCheckWaitStatus(Jim_Interp *interp, int pid, int waitStatus) #else Jim_SetResultFormatted(interp, "child %s by signal %d", action, WTERMSIG(waitStatus)); Jim_ListAppendElement(interp, errorCode, Jim_NewIntObj(interp, WTERMSIG(waitStatus))); - Jim_ListAppendElement(interp, errorCode, Jim_NewIntObj(interp, pid)); + Jim_ListAppendElement(interp, errorCode, Jim_NewIntObj(interp, (long)pid)); Jim_ListAppendElement(interp, errorCode, Jim_NewIntObj(interp, WTERMSIG(waitStatus))); #endif } @@ -231,7 +356,7 @@ static int JimCheckWaitStatus(Jim_Interp *interp, int pid, int waitStatus) struct WaitInfo { - int pid; /* Process id of child. */ + pidtype pid; /* Process id of child. */ int status; /* Status returned when child exited or suspended. */ int flags; /* Various flag bits; see below for definitions. */ }; @@ -271,18 +396,16 @@ static struct WaitInfoTable *JimAllocWaitInfoTable(void) return table; } -static int Jim_CreatePipeline(Jim_Interp *interp, int argc, Jim_Obj *const *argv, - int **pidArrayPtr, int *inPipePtr, int *outPipePtr, int *errFilePtr); -static void JimDetachPids(Jim_Interp *interp, int numPids, const int *pidPtr); -static int Jim_CleanupChildren(Jim_Interp *interp, int numPids, int *pidPtr, int errorId); - +/* + * The main [exec] command + */ static int Jim_ExecCmd(Jim_Interp *interp, int argc, Jim_Obj *const *argv) { - int outputId; /* File id for output pipe. -1 + fdtype outputId; /* File id for output pipe. -1 * means command overrode. */ - int errorId; /* File id for temporary file + fdtype errorId; /* File id for temporary file * containing error output. */ - int *pidPtr; + pidtype *pidPtr; int numPids, result; /* @@ -294,14 +417,14 @@ static int Jim_ExecCmd(Jim_Interp *interp, int argc, Jim_Obj *const *argv) int i; argc--; - numPids = Jim_CreatePipeline(interp, argc - 1, argv + 1, &pidPtr, NULL, NULL, NULL); + numPids = JimCreatePipeline(interp, argc - 1, argv + 1, &pidPtr, NULL, NULL, NULL); if (numPids < 0) { return JIM_ERR; } /* The return value is a list of the pids */ listObj = Jim_NewListObj(interp, NULL, 0); for (i = 0; i < numPids; i++) { - Jim_ListAppendElement(interp, listObj, Jim_NewIntObj(interp, pidPtr[i])); + Jim_ListAppendElement(interp, listObj, Jim_NewIntObj(interp, (long)pidPtr[i])); } Jim_SetResult(interp, listObj); JimDetachPids(interp, numPids, pidPtr); @@ -313,7 +436,8 @@ static int Jim_ExecCmd(Jim_Interp *interp, int argc, Jim_Obj *const *argv) * Create the command's pipeline. */ numPids = - Jim_CreatePipeline(interp, argc - 1, argv + 1, &pidPtr, (int *)NULL, &outputId, &errorId); + JimCreatePipeline(interp, argc - 1, argv + 1, &pidPtr, NULL, &outputId, &errorId); + if (numPids < 0) { return JIM_ERR; } @@ -324,21 +448,20 @@ static int Jim_ExecCmd(Jim_Interp *interp, int argc, Jim_Obj *const *argv) Jim_SetResultString(interp, "", 0); result = JIM_OK; - if (outputId != -1) { + if (outputId != JIM_BAD_FD) { result = JimAppendStreamToString(interp, outputId, Jim_GetResult(interp)); if (result < 0) { Jim_SetResultErrno(interp, "error reading from output pipe"); } - close(outputId); } - if (Jim_CleanupChildren(interp, numPids, pidPtr, errorId) != JIM_OK) { + if (JimCleanupChildren(interp, numPids, pidPtr, errorId) != JIM_OK) { result = JIM_ERR; } return result; } -void Jim_ReapDetachedPids(struct WaitInfoTable *table) +static void JimReapDetachedPids(struct WaitInfoTable *table) { struct WaitInfo *waitPtr; int count; @@ -350,8 +473,8 @@ void Jim_ReapDetachedPids(struct WaitInfoTable *table) for (waitPtr = table->info, count = table->used; count > 0; waitPtr++, count--) { if (waitPtr->flags & WI_DETACHED) { int status; - int pid = waitpid(waitPtr->pid, &status, WNOHANG); - if (pid > 0) { + pidtype pid = JimWaitPid(waitPtr->pid, &status, WNOHANG); + if (pid != JIM_BAD_PID) { if (waitPtr != &table->info[table->used - 1]) { *waitPtr = table->info[table->used - 1]; } @@ -364,11 +487,11 @@ void Jim_ReapDetachedPids(struct WaitInfoTable *table) /** * Does waitpid() on the given pid, and then removes the * entry from the wait table. - * + * * Returns the pid if OK and updates *statusPtr with the status, - * or -1 if the pid was not in the table. + * or JIM_BAD_PID if the pid was not in the table. */ -static int JimWaitPid(struct WaitInfoTable *table, int pid, int *statusPtr) +static pidtype JimWaitForProcess(struct WaitInfoTable *table, pidtype pid, int *statusPtr) { int i; @@ -376,7 +499,7 @@ static int JimWaitPid(struct WaitInfoTable *table, int pid, int *statusPtr) for (i = 0; i < table->used; i++) { if (pid == table->info[i].pid) { /* wait for it */ - waitpid(pid, statusPtr, 0); + JimWaitPid(pid, statusPtr, 0); /* Remove it from the table */ if (i != table->used - 1) { @@ -388,7 +511,7 @@ static int JimWaitPid(struct WaitInfoTable *table, int pid, int *statusPtr) } /* Not found */ - return -1; + return JIM_BAD_PID; } /* @@ -409,7 +532,7 @@ static int JimWaitPid(struct WaitInfoTable *table, int pid, int *statusPtr) *---------------------------------------------------------------------- */ -static void JimDetachPids(Jim_Interp *interp, int numPids, const int *pidPtr) +static void JimDetachPids(Jim_Interp *interp, int numPids, const pidtype *pidPtr) { int j; struct WaitInfoTable *table = Jim_CmdPrivData(interp); @@ -429,7 +552,7 @@ static void JimDetachPids(Jim_Interp *interp, int numPids, const int *pidPtr) /* *---------------------------------------------------------------------- * - * Jim_CreatePipeline -- + * JimCreatePipeline -- * * Given an argc/argv array, instantiate a pipeline of processes * as described by the argv. @@ -455,10 +578,10 @@ static void JimDetachPids(Jim_Interp *interp, int numPids, const int *pidPtr) *---------------------------------------------------------------------- */ static int -Jim_CreatePipeline(Jim_Interp *interp, int argc, Jim_Obj *const *argv, int **pidArrayPtr, - int *inPipePtr, int *outPipePtr, int *errFilePtr) +JimCreatePipeline(Jim_Interp *interp, int argc, Jim_Obj *const *argv, pidtype **pidArrayPtr, + fdtype *inPipePtr, fdtype *outPipePtr, fdtype *errFilePtr) { - int *pidPtr = NULL; /* Points to malloc-ed array holding all + pidtype *pidPtr = NULL; /* Points to malloc-ed array holding all * the pids of child processes. */ int numPids = 0; /* Actual number of processes that exist * at *pidPtr right now. */ @@ -492,43 +615,47 @@ Jim_CreatePipeline(Jim_Interp *interp, int argc, Jim_Obj *const *argv, int **pid * or NULL if output goes to stdout/pipe. */ const char *error = NULL; /* Holds name of stderr file to pipe to, * or NULL if stderr goes to stderr/pipe. */ - int inputId = -1; /* Readable file id input to current command in - * pipeline (could be file or pipe). -1 + fdtype inputId = JIM_BAD_FD; + /* Readable file id input to current command in + * pipeline (could be file or pipe). JIM_BAD_FD * means use stdin. */ - int outputId = -1; /* Writable file id for output from current + fdtype outputId = JIM_BAD_FD; + /* Writable file id for output from current * command in pipeline (could be file or pipe). - * -1 means use stdout. */ - int errorId = -1; /* Writable file id for all standard error - * output from all commands in pipeline. -1 + * JIM_BAD_FD means use stdout. */ + fdtype errorId = JIM_BAD_FD; + /* Writable file id for all standard error + * output from all commands in pipeline. JIM_BAD_FD * means use stderr. */ - int lastOutputId = -1; /* Write file id for output from last command + fdtype lastOutputId = JIM_BAD_FD; + /* Write file id for output from last command * in pipeline (could be file or pipe). * -1 means use stdout. */ - int pipeIds[2]; /* File ids for pipe that's being created. */ + fdtype pipeIds[2]; /* File ids for pipe that's being created. */ int firstArg, lastArg; /* Indexes of first and last arguments in * current command. */ int lastBar; - char *execName; - int i, pid; - char **orig_environ; + int i; + pidtype pid; + char **save_environ; struct WaitInfoTable *table = Jim_CmdPrivData(interp); /* Holds the args which will be used to exec */ char **arg_array = Jim_Alloc(sizeof(*arg_array) * (argc + 1)); int arg_count = 0; - Jim_ReapDetachedPids(table); + JimReapDetachedPids(table); if (inPipePtr != NULL) { - *inPipePtr = -1; + *inPipePtr = JIM_BAD_FD; } if (outPipePtr != NULL) { - *outPipePtr = -1; + *outPipePtr = JIM_BAD_FD; } if (errFilePtr != NULL) { - *errFilePtr = -1; + *errFilePtr = JIM_BAD_FD; } - pipeIds[0] = pipeIds[1] = -1; + pipeIds[0] = pipeIds[1] = JIM_BAD_FD; /* * First, scan through all the arguments to figure out the structure @@ -629,8 +756,7 @@ badargs: } /* Must do this before vfork(), so do it now */ - orig_environ = Jim_GetEnviron(); - Jim_SetEnviron(JimBuildEnv(interp)); + save_environ = JimSaveEnv(JimBuildEnv(interp)); /* * Set up the redirected input source for the pipeline, if @@ -642,24 +768,8 @@ badargs: * Immediate data in command. Create temporary file and * put data into file. */ - -#define TMP_STDIN_NAME "/tmp/tcl.in.XXXXXX" - char inName[sizeof(TMP_STDIN_NAME) + 1]; - int length; - - strcpy(inName, TMP_STDIN_NAME); - inputId = mkstemp(inName); - if (inputId < 0) { - Jim_SetResultErrno(interp, "couldn't create input file for command"); - goto error; - } - length = strlen(input); - if (write(inputId, input, length) != length) { - Jim_SetResultErrno(interp, "couldn't write file input for command"); - goto error; - } - if (lseek(inputId, 0L, SEEK_SET) == -1 || unlink(inName) == -1) { - Jim_SetResultErrno(interp, "couldn't reset or remove input file for command"); + inputId = JimCreateTemp(interp, input); + if (inputId == JIM_BAD_FD) { goto error; } } @@ -672,28 +782,27 @@ badargs: if (fh == NULL) { goto error; } - inputId = dup(fileno(fh)); + inputId = JimDupFd(JimFileno(fh)); } else { /* * File redirection. Just open the file. */ - inputId = open(input, O_RDONLY, 0); - if (inputId < 0) { - Jim_SetResultFormatted(interp, "couldn't read file \"%s\": %s", input, - strerror(errno)); + inputId = JimOpenForRead(input); + if (inputId == JIM_BAD_FD) { + Jim_SetResultFormatted(interp, "couldn't read file \"%s\": %s", input, JimStrError()); goto error; } } } else if (inPipePtr != NULL) { - if (pipe(pipeIds) != 0) { + if (JimPipe(pipeIds) != 0) { Jim_SetResultErrno(interp, "couldn't create input pipe for command"); goto error; } inputId = pipeIds[0]; *inPipePtr = pipeIds[1]; - pipeIds[0] = pipeIds[1] = -1; + pipeIds[0] = pipeIds[1] = JIM_BAD_FD; } /* @@ -710,22 +819,15 @@ badargs: goto error; } fflush(fh); - lastOutputId = dup(fileno(fh)); + lastOutputId = JimDupFd(JimFileno(fh)); } else { /* * Output is to go to a file. */ - int mode = O_WRONLY | O_CREAT | O_TRUNC; - - if (outputFile == FILE_APPEND) { - mode = O_WRONLY | O_CREAT | O_APPEND; - } - - lastOutputId = open(output, mode, 0666); - if (lastOutputId < 0) { - Jim_SetResultFormatted(interp, "couldn't write file \"%s\": %s", output, - strerror(errno)); + lastOutputId = JimOpenForWrite(output, outputFile == FILE_APPEND); + if (lastOutputId == JIM_BAD_FD) { + Jim_SetResultFormatted(interp, "couldn't write file \"%s\": %s", output, JimStrError()); goto error; } } @@ -734,29 +836,28 @@ badargs: /* * Output is to go to a pipe. */ - if (pipe(pipeIds) != 0) { + if (JimPipe(pipeIds) != 0) { Jim_SetResultErrno(interp, "couldn't create output pipe"); goto error; } lastOutputId = pipeIds[1]; *outPipePtr = pipeIds[0]; - pipeIds[0] = pipeIds[1] = -1; + pipeIds[0] = pipeIds[1] = JIM_BAD_FD; } - /* If we are redirecting stderr with 2>filename or 2>@fileId, then we ignore errFilePtr */ if (error != NULL) { if (errorFile == FILE_HANDLE) { if (strcmp(error, "1") == 0) { /* Special 2>@1 */ - if (lastOutputId >= 0) { - errorId = dup(lastOutputId); + if (lastOutputId != JIM_BAD_FD) { + errorId = JimDupFd(lastOutputId); } else { /* No redirection of stdout, so just use 2>@stdout */ error = "stdout"; } } - if (errorId < 0) { + if (errorId == JIM_BAD_FD) { Jim_Obj *fhObj = Jim_NewStringObj(interp, error, -1); FILE *fh = Jim_AioFilehandle(interp, fhObj); @@ -765,23 +866,17 @@ badargs: goto error; } fflush(fh); - errorId = dup(fileno(fh)); + errorId = JimDupFd(JimFileno(fh)); } } else { /* * Output is to go to a file. */ - int mode = O_WRONLY | O_CREAT | O_TRUNC; - - if (errorFile == FILE_APPEND) { - mode = O_WRONLY | O_CREAT | O_APPEND; - } - - errorId = open(error, mode, 0666); - if (errorId < 0) { - Jim_SetResultFormatted(interp, "couldn't write file \"%s\": %s", error, - strerror(errno)); + errorId = JimOpenForWrite(error, errorFile == FILE_APPEND); + if (errorId == JIM_BAD_FD) { + Jim_SetResultFormatted(interp, "couldn't write file \"%s\": %s", error, JimStrError()); + goto error; } } } @@ -794,25 +889,11 @@ badargs: * to complete before reading stderr, and processes couldn't complete * because stderr was backed up. */ - -#define TMP_STDERR_NAME "/tmp/tcl.err.XXXXXX" - char errName[sizeof(TMP_STDERR_NAME) + 1]; - - strcpy(errName, TMP_STDERR_NAME); - errorId = mkstemp(errName); - if (errorId < 0) { - errFileError: - Jim_SetResultErrno(interp, "couldn't create error file for command"); - goto error; - } - *errFilePtr = open(errName, O_RDONLY, 0); - if (*errFilePtr < 0) { - goto errFileError; - } - if (unlink(errName) == -1) { - Jim_SetResultErrno(interp, "couldn't remove error file for command"); + errorId = JimCreateTemp(interp, NULL); + if (errorId == JIM_BAD_FD) { goto error; } + *errFilePtr = JimDupFd(errorId); } /* @@ -820,15 +901,13 @@ badargs: * group of arguments between "|" arguments. */ - pidPtr = (int *)Jim_Alloc(cmdCount * sizeof(*pidPtr)); + pidPtr = Jim_Alloc(cmdCount * sizeof(*pidPtr)); for (i = 0; i < numPids; i++) { - pidPtr[i] = -1; + pidPtr[i] = JIM_BAD_PID; } for (firstArg = 0; firstArg < arg_count; numPids++, firstArg = lastArg + 1) { int pipe_dup_err = 0; - int origErrorId = errorId; - char execerr[64]; - int execerrlen; + fdtype origErrorId = errorId; for (lastArg = firstArg; lastArg < arg_count; lastArg++) { if (arg_array[lastArg][0] == '|') { @@ -844,16 +923,22 @@ badargs: outputId = lastOutputId; } else { - if (pipe(pipeIds) != 0) { + if (JimPipe(pipeIds) != 0) { Jim_SetResultErrno(interp, "couldn't create pipe"); goto error; } outputId = pipeIds[1]; } - execName = arg_array[firstArg]; /* Now fork the child */ +#ifdef __MINGW32__ + pid = JimStartWinProcess(interp, &arg_array[firstArg], save_environ ? save_environ[0] : NULL, inputId, outputId, errorId); + if (pid == JIM_BAD_PID) { + Jim_SetResultFormatted(interp, "couldn't exec \"%s\"", arg_array[firstArg]); + goto error; + } +#else /* * Disable SIGPIPE signals: if they were allowed, this process * might go away unexpectedly if children misbehave. This code @@ -870,10 +955,6 @@ badargs: errorId = outputId; } - /* Need to prep an error message before vfork(), just in case */ - snprintf(execerr, sizeof(execerr), "couldn't exec \"%s\"", execName); - execerrlen = strlen(execerr); - /* * Make a new process and enter it into the table if the fork * is successful. @@ -894,12 +975,13 @@ badargs: close(i); } - execvp(execName, &arg_array[firstArg]); + execvp(arg_array[firstArg], &arg_array[firstArg]); - /* we really can ignore the error here! */ - IGNORE_RC(write(2, execerr, execerrlen)); + /* Need to prep an error message before vfork(), just in case */ + fprintf(stderr, "couldn't exec \"%s\"", arg_array[firstArg]); _exit(127); } +#endif /* parent */ @@ -926,14 +1008,14 @@ badargs: * this child, then set up the input for the next child. */ - if (inputId != -1) { - close(inputId); + if (inputId != JIM_BAD_FD) { + JimCloseFd(inputId); } - if (outputId != -1) { - close(outputId); + if (outputId != JIM_BAD_FD) { + JimCloseFd(outputId); } inputId = pipeIds[0]; - pipeIds[0] = pipeIds[1] = -1; + pipeIds[0] = pipeIds[1] = JIM_BAD_FD; } *pidArrayPtr = pidPtr; @@ -942,19 +1024,18 @@ badargs: */ cleanup: - if (inputId != -1) { - close(inputId); + if (inputId != JIM_BAD_FD) { + JimCloseFd(inputId); } - if (lastOutputId != -1) { - close(lastOutputId); + if (lastOutputId != JIM_BAD_FD) { + JimCloseFd(lastOutputId); } - if (errorId != -1) { - close(errorId); + if (errorId != JIM_BAD_FD) { + JimCloseFd(errorId); } Jim_Free(arg_array); - JimFreeEnv(interp, Jim_GetEnviron(), orig_environ); - Jim_SetEnviron(orig_environ); + JimRestoreEnv(save_environ); return numPids; @@ -965,27 +1046,27 @@ badargs: */ error: - if ((inPipePtr != NULL) && (*inPipePtr != -1)) { - close(*inPipePtr); - *inPipePtr = -1; + if ((inPipePtr != NULL) && (*inPipePtr != JIM_BAD_FD)) { + JimCloseFd(*inPipePtr); + *inPipePtr = JIM_BAD_FD; } - if ((outPipePtr != NULL) && (*outPipePtr != -1)) { - close(*outPipePtr); - *outPipePtr = -1; + if ((outPipePtr != NULL) && (*outPipePtr != JIM_BAD_FD)) { + JimCloseFd(*outPipePtr); + *outPipePtr = JIM_BAD_FD; } - if ((errFilePtr != NULL) && (*errFilePtr != -1)) { - close(*errFilePtr); - *errFilePtr = -1; + if ((errFilePtr != NULL) && (*errFilePtr != JIM_BAD_FD)) { + JimCloseFd(*errFilePtr); + *errFilePtr = JIM_BAD_FD; } - if (pipeIds[0] != -1) { - close(pipeIds[0]); + if (pipeIds[0] != JIM_BAD_FD) { + JimCloseFd(pipeIds[0]); } - if (pipeIds[1] != -1) { - close(pipeIds[1]); + if (pipeIds[1] != JIM_BAD_FD) { + JimCloseFd(pipeIds[1]); } if (pidPtr != NULL) { for (i = 0; i < numPids; i++) { - if (pidPtr[i] != -1) { + if (pidPtr[i] != JIM_BAD_PID) { JimDetachPids(interp, 1, &pidPtr[i]); } } @@ -998,7 +1079,7 @@ badargs: /* *---------------------------------------------------------------------- * - * CleanupChildren -- + * JimCleanupChildren -- * * This is a utility procedure used to wait for child processes * to exit, record information about abnormal exits, and then @@ -1017,7 +1098,7 @@ badargs: *---------------------------------------------------------------------- */ -static int Jim_CleanupChildren(Jim_Interp *interp, int numPids, int *pidPtr, int errorId) +static int JimCleanupChildren(Jim_Interp *interp, int numPids, pidtype *pidPtr, fdtype errorId) { struct WaitInfoTable *table = Jim_CmdPrivData(interp); int result = JIM_OK; @@ -1025,7 +1106,7 @@ static int Jim_CleanupChildren(Jim_Interp *interp, int numPids, int *pidPtr, int for (i = 0; i < numPids; i++) { int waitStatus = 0; - if (JimWaitPid(table, pidPtr[i], &waitStatus) > 0) { + if (JimWaitForProcess(table, pidPtr[i], &waitStatus) != JIM_BAD_PID) { if (JimCheckWaitStatus(interp, pidPtr[i], waitStatus) != JIM_OK) { result = JIM_ERR; } @@ -1038,12 +1119,11 @@ static int Jim_CleanupChildren(Jim_Interp *interp, int numPids, int *pidPtr, int * then add the file's contents to the result * string. */ - if (errorId >= 0) { + if (errorId != JIM_BAD_FD) { + JimRewindFd(errorId); if (JimAppendStreamToString(interp, errorId, Jim_GetResult(interp)) != JIM_OK) { - Jim_SetResultErrno(interp, "error reading from stderr output file"); result = JIM_ERR; } - close(errorId); } JimTrimTrailingNewline(interp); @@ -1055,67 +1135,484 @@ int Jim_execInit(Jim_Interp *interp) { if (Jim_PackageProvide(interp, "exec", "1.0", JIM_ERRMSG)) return JIM_ERR; - Jim_CreateCommand(interp, "exec", Jim_ExecCmd, JimAllocWaitInfoTable(), JimFreeWaitInfoTable); return JIM_OK; } -#else -/* e.g. Windows. Poor mans implementation of exec with system() - * The system() call *may* do command line redirection, etc. - * The standard output is not available. - * Can't redirect filehandles. - */ -static int Jim_ExecCmd(Jim_Interp *interp, int argc, Jim_Obj *const *argv) + +#if defined(__MINGW32__) +/* Windows-specific (mingw) implementation */ + +static SECURITY_ATTRIBUTES *JimStdSecAttrs(void) { - Jim_Obj *cmdlineObj = Jim_NewEmptyStringObj(interp); - int i, j; - int rc; + static SECURITY_ATTRIBUTES secAtts; - /* Create a quoted command line */ - for (i = 1; i < argc; i++) { - int len; - const char *arg = Jim_GetString(argv[i], &len); + secAtts.nLength = sizeof(SECURITY_ATTRIBUTES); + secAtts.lpSecurityDescriptor = NULL; + secAtts.bInheritHandle = TRUE; + return &secAtts; +} - if (i > 1) { - Jim_AppendString(interp, cmdlineObj, " ", 1); +static int JimErrno(void) +{ + switch (GetLastError()) { + case ERROR_FILE_NOT_FOUND: return ENOENT; + case ERROR_PATH_NOT_FOUND: return ENOENT; + case ERROR_TOO_MANY_OPEN_FILES: return EMFILE; + case ERROR_ACCESS_DENIED: return EACCES; + case ERROR_INVALID_HANDLE: return EBADF; + case ERROR_BAD_ENVIRONMENT: return E2BIG; + case ERROR_BAD_FORMAT: return ENOEXEC; + case ERROR_INVALID_ACCESS: return EACCES; + case ERROR_INVALID_DRIVE: return ENOENT; + case ERROR_CURRENT_DIRECTORY: return EACCES; + case ERROR_NOT_SAME_DEVICE: return EXDEV; + case ERROR_NO_MORE_FILES: return ENOENT; + case ERROR_WRITE_PROTECT: return EROFS; + case ERROR_BAD_UNIT: return ENXIO; + case ERROR_NOT_READY: return EBUSY; + case ERROR_BAD_COMMAND: return EIO; + case ERROR_CRC: return EIO; + case ERROR_BAD_LENGTH: return EIO; + case ERROR_SEEK: return EIO; + case ERROR_WRITE_FAULT: return EIO; + case ERROR_READ_FAULT: return EIO; + case ERROR_GEN_FAILURE: return EIO; + case ERROR_SHARING_VIOLATION: return EACCES; + case ERROR_LOCK_VIOLATION: return EACCES; + case ERROR_SHARING_BUFFER_EXCEEDED: return ENFILE; + case ERROR_HANDLE_DISK_FULL: return ENOSPC; + case ERROR_NOT_SUPPORTED: return ENODEV; + case ERROR_REM_NOT_LIST: return EBUSY; + case ERROR_DUP_NAME: return EEXIST; + case ERROR_BAD_NETPATH: return ENOENT; + case ERROR_NETWORK_BUSY: return EBUSY; + case ERROR_DEV_NOT_EXIST: return ENODEV; + case ERROR_TOO_MANY_CMDS: return EAGAIN; + case ERROR_ADAP_HDW_ERR: return EIO; + case ERROR_BAD_NET_RESP: return EIO; + case ERROR_UNEXP_NET_ERR: return EIO; + case ERROR_NETNAME_DELETED: return ENOENT; + case ERROR_NETWORK_ACCESS_DENIED: return EACCES; + case ERROR_BAD_DEV_TYPE: return ENODEV; + case ERROR_BAD_NET_NAME: return ENOENT; + case ERROR_TOO_MANY_NAMES: return ENFILE; + case ERROR_TOO_MANY_SESS: return EIO; + case ERROR_SHARING_PAUSED: return EAGAIN; + case ERROR_REDIR_PAUSED: return EAGAIN; + case ERROR_FILE_EXISTS: return EEXIST; + case ERROR_CANNOT_MAKE: return ENOSPC; + case ERROR_OUT_OF_STRUCTURES: return ENFILE; + case ERROR_ALREADY_ASSIGNED: return EEXIST; + case ERROR_INVALID_PASSWORD: return EPERM; + case ERROR_NET_WRITE_FAULT: return EIO; + case ERROR_NO_PROC_SLOTS: return EAGAIN; + case ERROR_DISK_CHANGE: return EXDEV; + case ERROR_BROKEN_PIPE: return EPIPE; + case ERROR_OPEN_FAILED: return ENOENT; + case ERROR_DISK_FULL: return ENOSPC; + case ERROR_NO_MORE_SEARCH_HANDLES: return EMFILE; + case ERROR_INVALID_TARGET_HANDLE: return EBADF; + case ERROR_INVALID_NAME: return ENOENT; + case ERROR_PROC_NOT_FOUND: return ESRCH; + case ERROR_WAIT_NO_CHILDREN: return ECHILD; + case ERROR_CHILD_NOT_COMPLETE: return ECHILD; + case ERROR_DIRECT_ACCESS_HANDLE: return EBADF; + case ERROR_SEEK_ON_DEVICE: return ESPIPE; + case ERROR_BUSY_DRIVE: return EAGAIN; + case ERROR_DIR_NOT_EMPTY: return EEXIST; + case ERROR_NOT_LOCKED: return EACCES; + case ERROR_BAD_PATHNAME: return ENOENT; + case ERROR_LOCK_FAILED: return EACCES; + case ERROR_ALREADY_EXISTS: return EEXIST; + case ERROR_FILENAME_EXCED_RANGE: return ENAMETOOLONG; + case ERROR_BAD_PIPE: return EPIPE; + case ERROR_PIPE_BUSY: return EAGAIN; + case ERROR_PIPE_NOT_CONNECTED: return EPIPE; + case ERROR_DIRECTORY: return ENOTDIR; + } + return EINVAL; +} + +static int JimPipe(fdtype pipefd[2]) +{ + if (CreatePipe(&pipefd[0], &pipefd[1], NULL, 0)) { + return 0; + } + return -1; +} + +static fdtype JimDupFd(fdtype infd) +{ + fdtype dupfd; + pidtype pid = GetCurrentProcess(); + + if (DuplicateHandle(pid, infd, pid, &dupfd, 0, TRUE, DUPLICATE_SAME_ACCESS)) { + return dupfd; + } + return JIM_BAD_FD; +} + +static int JimRewindFd(fdtype fd) +{ + return SetFilePointer(fd, 0, NULL, FILE_BEGIN) == INVALID_SET_FILE_POINTER ? -1 : 0; +} + +#if 0 +static int JimReadFd(fdtype fd, char *buffer, size_t len) +{ + DWORD num; + + if (ReadFile(fd, buffer, len, &num, NULL)) { + return num; + } + if (GetLastError() == ERROR_HANDLE_EOF || GetLastError() == ERROR_BROKEN_PIPE) { + return 0; + } + return -1; +} +#endif + +static FILE *JimFdOpenForRead(fdtype fd) +{ + return _fdopen(_open_osfhandle((int)fd, _O_RDONLY | _O_TEXT), "r"); +} + +static fdtype JimFileno(FILE *fh) +{ + return (fdtype)_get_osfhandle(_fileno(fh)); +} + +static fdtype JimOpenForRead(const char *filename) +{ + return CreateFile(filename, GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE, + JimStdSecAttrs(), OPEN_EXISTING, 0, NULL); +} + +static fdtype JimOpenForWrite(const char *filename, int append) +{ + return CreateFile(filename, append ? FILE_APPEND_DATA : GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, + JimStdSecAttrs(), append ? OPEN_ALWAYS : CREATE_ALWAYS, 0, (HANDLE) NULL); +} + +static FILE *JimFdOpenForWrite(fdtype fd) +{ + return _fdopen(_open_osfhandle((int)fd, _O_TEXT), "w"); +} + +static pidtype JimWaitPid(pidtype pid, int *status, int nohang) +{ + DWORD ret = WaitForSingleObject(pid, nohang ? 0 : INFINITE); + if (ret == WAIT_TIMEOUT || ret == WAIT_FAILED) { + /* WAIT_TIMEOUT can only happend with WNOHANG */ + return JIM_BAD_PID; + } + GetExitCodeProcess(pid, &ret); + *status = ret; + CloseHandle(pid); + return pid; +} + +static HANDLE JimCreateTemp(Jim_Interp *interp, const char *contents) +{ + char name[MAX_PATH]; + HANDLE handle; + + if (!GetTempPath(MAX_PATH, name) || !GetTempFileName(name, "JIM", 0, name)) { + return JIM_BAD_FD; + } + + handle = CreateFile(name, GENERIC_READ | GENERIC_WRITE, 0, JimStdSecAttrs(), + CREATE_ALWAYS, FILE_ATTRIBUTE_TEMPORARY | FILE_FLAG_DELETE_ON_CLOSE, + NULL); + + if (handle == INVALID_HANDLE_VALUE) { + goto error; + } + + if (contents != NULL) { + /* Use fdopen() to get automatic text-mode translation */ + FILE *fh = JimFdOpenForWrite(JimDupFd(handle)); + if (fh == NULL) { + goto error; } - if (strpbrk(arg, "\\\" ") == NULL) { - /* No quoting required */ - Jim_AppendString(interp, cmdlineObj, arg, len); + + if (fwrite(contents, strlen(contents), 1, fh) != 1) { + fclose(fh); + goto error; + } + fseek(fh, 0, SEEK_SET); + fclose(fh); + } + return handle; + + error: + Jim_SetResultErrno(interp, "failed to create temp file"); + CloseHandle(handle); + DeleteFile(name); + return JIM_BAD_FD; +} + +static int +JimWinFindExecutable(const char *originalName, char fullPath[MAX_PATH]) +{ + int i; + static char extensions[][5] = {".exe", "", ".bat"}; + + for (i = 0; i < (int) (sizeof(extensions) / sizeof(extensions[0])); i++) { + lstrcpyn(fullPath, originalName, MAX_PATH - 5); + lstrcat(fullPath, extensions[i]); + + if (SearchPath(NULL, fullPath, NULL, MAX_PATH, fullPath, NULL) == 0) { continue; } + if (GetFileAttributes(fullPath) & FILE_ATTRIBUTE_DIRECTORY) { + continue; + } + return 0; + } - Jim_AppendString(interp, cmdlineObj, "\"", 1); - for (j = 0; j < len; j++) { - if (arg[j] == '\\' || arg[j] == '"') { - Jim_AppendString(interp, cmdlineObj, "\\", 1); + return -1; +} + +static char **JimSaveEnv(char **env) +{ + return env; +} + +static void JimRestoreEnv(char **env) +{ + JimFreeEnv(env, NULL); +} + +static Jim_Obj * +JimWinBuildCommandLine(Jim_Interp *interp, char **argv) +{ + char *start, *special; + int quote, i; + + Jim_Obj *strObj = Jim_NewStringObj(interp, "", 0); + + for (i = 0; argv[i]; i++) { + if (i > 0) { + Jim_AppendString(interp, strObj, " ", 1); + } + + if (argv[i][0] == '\0') { + quote = 1; + } + else { + quote = 0; + for (start = argv[i]; *start != '\0'; start++) { + if (isspace(UCHAR(*start))) { + quote = 1; + break; + } } - Jim_AppendString(interp, cmdlineObj, &arg[j], 1); } - Jim_AppendString(interp, cmdlineObj, "\"", 1); + if (quote) { + Jim_AppendString(interp, strObj, "\"" , 1); + } + + start = argv[i]; + for (special = argv[i]; ; ) { + if ((*special == '\\') && (special[1] == '\\' || + special[1] == '"' || (quote && special[1] == '\0'))) { + Jim_AppendString(interp, strObj, start, special - start); + start = special; + while (1) { + special++; + if (*special == '"' || (quote && *special == '\0')) { + /* + * N backslashes followed a quote -> insert + * N * 2 + 1 backslashes then a quote. + */ + + Jim_AppendString(interp, strObj, start, special - start); + break; + } + if (*special != '\\') { + break; + } + } + Jim_AppendString(interp, strObj, start, special - start); + start = special; + } + if (*special == '"') { + if (special == start) { + Jim_AppendString(interp, strObj, "\"", 1); + } + else { + Jim_AppendString(interp, strObj, start, special - start); + } + Jim_AppendString(interp, strObj, "\\\"", 2); + start = special + 1; + } + if (*special == '\0') { + break; + } + special++; + } + Jim_AppendString(interp, strObj, start, special - start); + if (quote) { + Jim_AppendString(interp, strObj, "\"", 1); + } } - rc = system(Jim_String(cmdlineObj)); + return strObj; +} - Jim_FreeNewObj(interp, cmdlineObj); +static pidtype +JimStartWinProcess(Jim_Interp *interp, char **argv, char *env, fdtype inputId, fdtype outputId, fdtype errorId) +{ + STARTUPINFO startInfo; + PROCESS_INFORMATION procInfo; + HANDLE hProcess, h; + char execPath[MAX_PATH]; + char *originalName; + pidtype pid = JIM_BAD_PID; + Jim_Obj *cmdLineObj; + + if (JimWinFindExecutable(argv[0], execPath) < 0) { + return JIM_BAD_PID; + } + originalName = argv[0]; + argv[0] = execPath; - if (rc) { - Jim_Obj *errorCode = Jim_NewListObj(interp, NULL, 0); - Jim_ListAppendElement(interp, errorCode, Jim_NewStringObj(interp, "CHILDSTATUS", -1)); - Jim_ListAppendElement(interp, errorCode, Jim_NewIntObj(interp, 0)); - Jim_ListAppendElement(interp, errorCode, Jim_NewIntObj(interp, rc)); - Jim_SetGlobalVariableStr(interp, "errorCode", errorCode); - return JIM_ERR; + hProcess = GetCurrentProcess(); + cmdLineObj = JimWinBuildCommandLine(interp, argv); + + /* + * STARTF_USESTDHANDLES must be used to pass handles to child process. + * Using SetStdHandle() and/or dup2() only works when a console mode + * parent process is spawning an attached console mode child process. + */ + + ZeroMemory(&startInfo, sizeof(startInfo)); + startInfo.cb = sizeof(startInfo); + startInfo.dwFlags = STARTF_USESTDHANDLES; + startInfo.hStdInput = INVALID_HANDLE_VALUE; + startInfo.hStdOutput= INVALID_HANDLE_VALUE; + startInfo.hStdError = INVALID_HANDLE_VALUE; + + /* + * Duplicate all the handles which will be passed off as stdin, stdout + * and stderr of the child process. The duplicate handles are set to + * be inheritable, so the child process can use them. + */ + if (inputId == JIM_BAD_FD) { + if (CreatePipe(&startInfo.hStdInput, &h, JimStdSecAttrs(), 0) != FALSE) { + CloseHandle(h); + } + } else { + DuplicateHandle(hProcess, inputId, hProcess, &startInfo.hStdInput, + 0, TRUE, DUPLICATE_SAME_ACCESS); + } + if (startInfo.hStdInput == JIM_BAD_FD) { + goto end; } - return JIM_OK; + if (outputId == JIM_BAD_FD) { + startInfo.hStdOutput = CreateFile("NUL:", GENERIC_WRITE, 0, + JimStdSecAttrs(), OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL); + } else { + DuplicateHandle(hProcess, outputId, hProcess, &startInfo.hStdOutput, + 0, TRUE, DUPLICATE_SAME_ACCESS); + } + if (startInfo.hStdOutput == JIM_BAD_FD) { + goto end; + } + + if (errorId == JIM_BAD_FD) { + /* + * If handle was not set, errors should be sent to an infinitely + * deep sink. + */ + + startInfo.hStdError = CreateFile("NUL:", GENERIC_WRITE, 0, + JimStdSecAttrs(), OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); + } else { + DuplicateHandle(hProcess, errorId, hProcess, &startInfo.hStdError, + 0, TRUE, DUPLICATE_SAME_ACCESS); + } + if (startInfo.hStdError == JIM_BAD_FD) { + goto end; + } + + if (!CreateProcess(NULL, (char *)Jim_String(cmdLineObj), NULL, NULL, TRUE, + 0, env, NULL, &startInfo, &procInfo)) { + goto end; + } + + /* + * "When an application spawns a process repeatedly, a new thread + * instance will be created for each process but the previous + * instances may not be cleaned up. This results in a significant + * virtual memory loss each time the process is spawned. If there + * is a WaitForInputIdle() call between CreateProcess() and + * CloseHandle(), the problem does not occur." PSS ID Number: Q124121 + */ + + WaitForInputIdle(procInfo.hProcess, 5000); + CloseHandle(procInfo.hThread); + + pid = procInfo.hProcess; + + end: + Jim_FreeNewObj(interp, cmdLineObj); + if (startInfo.hStdInput != JIM_BAD_FD) { + CloseHandle(startInfo.hStdInput); + } + if (startInfo.hStdOutput != JIM_BAD_FD) { + CloseHandle(startInfo.hStdOutput); + } + if (startInfo.hStdError != JIM_BAD_FD) { + CloseHandle(startInfo.hStdError); + } + return pid; +} +#else +/* Unix-specific implementation */ +static int JimOpenForWrite(const char *filename, int append) +{ + return open(filename, O_WRONLY | O_CREAT | (append ? O_APPEND : O_TRUNC), 0666); } -int Jim_execInit(Jim_Interp *interp) +static int JimRewindFd(int fd) { - if (Jim_PackageProvide(interp, "exec", "1.0", JIM_ERRMSG)) - return JIM_ERR; + return lseek(fd, 0L, SEEK_SET); +} - Jim_CreateCommand(interp, "exec", Jim_ExecCmd, NULL, NULL); - return JIM_OK; +static int JimCreateTemp(Jim_Interp *interp, const char *contents) +{ + char inName[] = "/tmp/tcl.tmp.XXXXXX"; + + int fd = mkstemp(inName); + if (fd == JIM_BAD_FD) { + Jim_SetResultErrno(interp, "couldn't create temp file"); + return -1; + } + if (contents) { + int length = strlen(contents); + if (unlink(inName) == -1 || write(fd, contents, length) != length) { + Jim_SetResultErrno(interp, "couldn't write temp file"); + close(fd); + return -1; + } + lseek(fd, 0L, SEEK_SET); + } + return fd; +} + +static char **JimSaveEnv(char **env) +{ + char **saveenv = Jim_GetEnviron(); + Jim_SetEnviron(env); + return saveenv; } + +static void JimRestoreEnv(char **env) +{ + JimFreeEnv(Jim_GetEnviron(), env); + Jim_SetEnviron(env); +} +#endif #endif diff --git a/tclcompat.tcl b/tclcompat.tcl index 430af80..8e5b128 100644 --- a/tclcompat.tcl +++ b/tclcompat.tcl @@ -2,7 +2,7 @@ # # Loads some Tcl-compatible features. # I/O commands, case, lassign, parray, errorInfo, ::tcl_platform, ::env -# try, throw, file copy, info nameofexecutable +# try, throw, file copy, file delete -force # Set up the ::env array set env [env] diff --git a/tests/exec.test b/tests/exec.test index 8b9a743..07770cd 100644 --- a/tests/exec.test +++ b/tests/exec.test @@ -263,8 +263,8 @@ test exec-6.2 {redirecting stderr through a pipeline} { } "foo bar" test exec-6.3 {redirecting stderr through a pipeline} { exec sh -c "echo foo bar 1>&2" \ - |& sh -c "echo second msg 1>&2 ; cat" |& cat -} "second msg\nfoo bar" + |& cat |& cat +} "foo bar" # I/O redirection: combinations. @@ -409,7 +409,7 @@ exec sleep 3 test exec-12.1 {reaping background processes} { for {set i 0} {$i < 20} {incr i} { - exec echo foo > /dev/null & + exec echo foo > exec.tmp1 & } exec sleep 1 catch {exec ps | fgrep "echo foo" | fgrep -v fgrep | wc} msg diff --git a/tests/exec2.test b/tests/exec2.test index a15e409..103d29b 100644 --- a/tests/exec2.test +++ b/tests/exec2.test @@ -19,25 +19,25 @@ test exec2-1.2 "Quoting - Word Grouping" { } {6} test exec2-2.1 "Add to exec environment" { - set env(testenv) "the value" - exec printenv | sed -n -e /^testenv=/p -} {testenv=the value} + set env(TESTENV) "the value" + exec printenv | sed -n -e /^TESTENV=/p +} {TESTENV=the value} test exec2-2.2 "Remove from exec environment" { - set env(testenv2) "new value" - unset env(testenv) - exec printenv | sed -n -e /^testenv=/p + set env(TESTENV2) "new value" + unset env(TESTENV) + exec printenv | sed -n -e /^TESTENV=/p } {} test exec2-2.3 "Remove all exec environment" { array unset env * - exec printenv | sed -n -e /^testenv2=/p + exec printenv | sed -n -e /^TESTENV2=/p } {} test exec2-2.4 "Remove all env var" { unset -nocomplain env - exec printenv | sed -n -e /^testenv2=/p + exec printenv | sed -n -e /^TESTENV2=/p } {} testreport |