int Jim_MakeTempFile(Jim_Interp *interp, const char *filename_template, int unlink_file) { char name[MAX_PATH]; HANDLE handle; if (!GetTempPath(MAX_PATH, name) || !GetTempFileName(name, filename_template ? filename_template : "JIM", 0, name)) { return -1; } handle = CreateFile(name, GENERIC_READ | GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_TEMPORARY | (unlink_file ? FILE_FLAG_DELETE_ON_CLOSE : 0), NULL); if (handle == INVALID_HANDLE_VALUE) { goto error; } Jim_SetResultString(interp, name, -1); return _open_osfhandle((int)handle, _O_RDWR | _O_TEXT); error: Jim_SetResultErrno(interp, name); DeleteFile(name); return -1; }
static int JimCreateTemp(Jim_Interp *interp, const char *contents, int len) { 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; } unlink(inName); if (contents) { if (write(fd, contents, len) != len) { Jim_SetResultErrno(interp, "couldn't write temp file"); close(fd); return -1; } lseek(fd, 0L, SEEK_SET); } return fd; }
int Jim_MakeTempFile(Jim_Interp *interp, const char *filename_template, int unlink_file) { int fd; mode_t mask; Jim_Obj *filenameObj; if (filename_template == NULL) { const char *tmpdir = getenv("TMPDIR"); if (tmpdir == NULL || *tmpdir == '\0' || access(tmpdir, W_OK) != 0) { tmpdir = "/tmp/"; } filenameObj = Jim_NewStringObj(interp, tmpdir, -1); if (tmpdir[0] && tmpdir[strlen(tmpdir) - 1] != '/') { Jim_AppendString(interp, filenameObj, "/", 1); } Jim_AppendString(interp, filenameObj, "tcl.tmp.XXXXXX", -1); } else { filenameObj = Jim_NewStringObj(interp, filename_template, -1); } /* Update the template name directly with the filename */ mask = umask(S_IXUSR | S_IRWXG | S_IRWXO); #ifdef HAVE_MKSTEMP fd = mkstemp(filenameObj->bytes); #else if (mktemp(filenameObj->bytes) == NULL) { fd = -1; } else { fd = open(filenameObj->bytes, O_RDWR | O_CREAT | O_TRUNC); } #endif umask(mask); if (fd < 0) { Jim_SetResultErrno(interp, Jim_String(filenameObj)); Jim_FreeNewObj(interp, filenameObj); return -1; } if (unlink_file) { remove(Jim_String(filenameObj)); } Jim_SetResult(interp, filenameObj); return fd; }
static HANDLE JimCreateTemp(Jim_Interp *interp, const char *contents, int len) { 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 (fwrite(contents, len, 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; }
/* *---------------------------------------------------------------------- * * JimCreatePipeline -- * * Given an argc/argv array, instantiate a pipeline of processes * as described by the argv. * * Results: * The return value is a count of the number of new processes * created, or -1 if an error occurred while creating the pipeline. * *pidArrayPtr is filled in with the address of a dynamically * allocated array giving the ids of all of the processes. It * is up to the caller to free this array when it isn't needed * anymore. If inPipePtr is non-NULL, *inPipePtr is filled in * with the file id for the input pipe for the pipeline (if any): * the caller must eventually close this file. If outPipePtr * isn't NULL, then *outPipePtr is filled in with the file id * for the output pipe from the pipeline: the caller must close * this file. If errFilePtr isn't NULL, then *errFilePtr is filled * with a file id that may be used to read error output after the * pipeline completes. * * Side effects: * Processes and pipes are created. * *---------------------------------------------------------------------- */ static int JimCreatePipeline(Jim_Interp *interp, int argc, Jim_Obj *const *argv, pidtype **pidArrayPtr, fdtype *inPipePtr, fdtype *outPipePtr, fdtype *errFilePtr) { 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. */ int cmdCount; /* Count of number of distinct commands * found in argc/argv. */ const char *input = NULL; /* Describes input for pipeline, depending * on "inputFile". NULL means take input * from stdin/pipe. */ int input_len = 0; /* Length of input, if relevant */ #define FILE_NAME 0 /* input/output: filename */ #define FILE_APPEND 1 /* output only: filename, append */ #define FILE_HANDLE 2 /* input/output: filehandle */ #define FILE_TEXT 3 /* input only: input is actual text */ int inputFile = FILE_NAME; /* 1 means input is name of input file. * 2 means input is filehandle name. * 0 means input holds actual * text to be input to command. */ int outputFile = FILE_NAME; /* 0 means output is the name of output file. * 1 means output is the name of output file, and append. * 2 means output is filehandle name. * All this is ignored if output is NULL */ int errorFile = FILE_NAME; /* 0 means error is the name of error file. * 1 means error is the name of error file, and append. * 2 means error is filehandle name. * All this is ignored if error is NULL */ const char *output = NULL; /* Holds name of output file to pipe to, * 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. */ 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. */ fdtype outputId = JIM_BAD_FD; /* Writable file id for output from current * command in pipeline (could be file or pipe). * 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. */ fdtype lastOutputId = JIM_BAD_FD; /* Write file id for output from last command * in pipeline (could be file or pipe). * -1 means use stdout. */ 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; 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; JimReapDetachedPids(table); if (inPipePtr != NULL) { *inPipePtr = JIM_BAD_FD; } if (outPipePtr != NULL) { *outPipePtr = JIM_BAD_FD; } if (errFilePtr != NULL) { *errFilePtr = JIM_BAD_FD; } pipeIds[0] = pipeIds[1] = JIM_BAD_FD; /* * First, scan through all the arguments to figure out the structure * of the pipeline. Count the number of distinct processes (it's the * number of "|" arguments). If there are "<", "<<", or ">" arguments * then make note of input and output redirection and remove these * arguments and the arguments that follow them. */ cmdCount = 1; lastBar = -1; for (i = 0; i < argc; i++) { const char *arg = Jim_String(argv[i]); if (arg[0] == '<') { inputFile = FILE_NAME; input = arg + 1; if (*input == '<') { inputFile = FILE_TEXT; input_len = Jim_Length(argv[i]) - 2; input++; } else if (*input == '@') { inputFile = FILE_HANDLE; input++; } if (!*input && ++i < argc) { input = Jim_GetString(argv[i], &input_len); } } else if (arg[0] == '>') { int dup_error = 0; outputFile = FILE_NAME; output = arg + 1; if (*output == '>') { outputFile = FILE_APPEND; output++; } if (*output == '&') { /* Redirect stderr too */ output++; dup_error = 1; } if (*output == '@') { outputFile = FILE_HANDLE; output++; } if (!*output && ++i < argc) { output = Jim_String(argv[i]); } if (dup_error) { errorFile = outputFile; error = output; } } else if (arg[0] == '2' && arg[1] == '>') { error = arg + 2; errorFile = FILE_NAME; if (*error == '@') { errorFile = FILE_HANDLE; error++; } else if (*error == '>') { errorFile = FILE_APPEND; error++; } if (!*error && ++i < argc) { error = Jim_String(argv[i]); } } else { if (strcmp(arg, "|") == 0 || strcmp(arg, "|&") == 0) { if (i == lastBar + 1 || i == argc - 1) { Jim_SetResultString(interp, "illegal use of | or |& in command", -1); goto badargs; } lastBar = i; cmdCount++; } /* Either |, |& or a "normal" arg, so store it in the arg array */ arg_array[arg_count++] = (char *)arg; continue; } if (i >= argc) { Jim_SetResultFormatted(interp, "can't specify \"%s\" as last word in command", arg); goto badargs; } } if (arg_count == 0) { Jim_SetResultString(interp, "didn't specify command to execute", -1); badargs: Jim_Free(arg_array); return -1; } /* Must do this before vfork(), so do it now */ save_environ = JimSaveEnv(JimBuildEnv(interp)); /* * Set up the redirected input source for the pipeline, if * so requested. */ if (input != NULL) { if (inputFile == FILE_TEXT) { /* * Immediate data in command. Create temporary file and * put data into file. */ inputId = JimCreateTemp(interp, input, input_len); if (inputId == JIM_BAD_FD) { goto error; } } else if (inputFile == FILE_HANDLE) { /* Should be a file descriptor */ FILE *fh = JimGetAioFilehandle(interp, input); if (fh == NULL) { goto error; } inputId = JimDupFd(JimFileno(fh)); } else { /* * File redirection. Just open the file. */ 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 (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] = JIM_BAD_FD; } /* * Set up the redirected output sink for the pipeline from one * of two places, if requested. */ if (output != NULL) { if (outputFile == FILE_HANDLE) { FILE *fh = JimGetAioFilehandle(interp, output); if (fh == NULL) { goto error; } fflush(fh); lastOutputId = JimDupFd(JimFileno(fh)); } else { /* * Output is to go to a file. */ lastOutputId = JimOpenForWrite(output, outputFile == FILE_APPEND); if (lastOutputId == JIM_BAD_FD) { Jim_SetResultFormatted(interp, "couldn't write file \"%s\": %s", output, JimStrError()); goto error; } } } else if (outPipePtr != NULL) { /* * Output is to go to a pipe. */ if (JimPipe(pipeIds) != 0) { Jim_SetResultErrno(interp, "couldn't create output pipe"); goto error; } lastOutputId = pipeIds[1]; *outPipePtr = pipeIds[0]; 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 != JIM_BAD_FD) { errorId = JimDupFd(lastOutputId); } else { /* No redirection of stdout, so just use 2>@stdout */ error = "stdout"; } } if (errorId == JIM_BAD_FD) { FILE *fh = JimGetAioFilehandle(interp, error); if (fh == NULL) { goto error; } fflush(fh); errorId = JimDupFd(JimFileno(fh)); } } else { /* * Output is to go to a file. */ errorId = JimOpenForWrite(error, errorFile == FILE_APPEND); if (errorId == JIM_BAD_FD) { Jim_SetResultFormatted(interp, "couldn't write file \"%s\": %s", error, JimStrError()); goto error; } } } else if (errFilePtr != NULL) { /* * Set up the standard error output sink for the pipeline, if * requested. Use a temporary file which is opened, then deleted. * Could potentially just use pipe, but if it filled up it could * cause the pipeline to deadlock: we'd be waiting for processes * to complete before reading stderr, and processes couldn't complete * because stderr was backed up. */ errorId = JimCreateTemp(interp, NULL, 0); if (errorId == JIM_BAD_FD) { goto error; } *errFilePtr = JimDupFd(errorId); } /* * Scan through the argc array, forking off a process for each * group of arguments between "|" arguments. */ pidPtr = Jim_Alloc(cmdCount * sizeof(*pidPtr)); for (i = 0; i < numPids; i++) { pidPtr[i] = JIM_BAD_PID; } for (firstArg = 0; firstArg < arg_count; numPids++, firstArg = lastArg + 1) { int pipe_dup_err = 0; fdtype origErrorId = errorId; for (lastArg = firstArg; lastArg < arg_count; lastArg++) { if (arg_array[lastArg][0] == '|') { if (arg_array[lastArg][1] == '&') { pipe_dup_err = 1; } break; } } /* Replace | with NULL for execv() */ arg_array[lastArg] = NULL; if (lastArg == arg_count) { outputId = lastOutputId; } else { if (JimPipe(pipeIds) != 0) { Jim_SetResultErrno(interp, "couldn't create pipe"); goto error; } outputId = pipeIds[1]; } /* 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 * can potentially interfere with other application code that * expects to handle SIGPIPEs; what's really needed is an * arbiter for signals to allow them to be "shared". */ if (table->info == NULL) { (void)signal(SIGPIPE, SIG_IGN); } /* Need to do this befor vfork() */ if (pipe_dup_err) { errorId = outputId; } /* * Make a new process and enter it into the table if the fork * is successful. */ pid = vfork(); if (pid < 0) { Jim_SetResultErrno(interp, "couldn't fork child process"); goto error; } if (pid == 0) { /* Child */ if (inputId != -1) dup2(inputId, 0); if (outputId != -1) dup2(outputId, 1); if (errorId != -1) dup2(errorId, 2); for (i = 3; (i <= outputId) || (i <= inputId) || (i <= errorId); i++) { close(i); } execvpe(arg_array[firstArg], &arg_array[firstArg], Jim_GetEnviron()); /* Need to prep an error message before vfork(), just in case */ fprintf(stderr, "couldn't exec \"%s\"", arg_array[firstArg]); _exit(127); } #endif /* parent */ /* * Enlarge the wait table if there isn't enough space for a new * entry. */ if (table->used == table->size) { table->size += WAIT_TABLE_GROW_BY; table->info = Jim_Realloc(table->info, table->size * sizeof(*table->info)); } table->info[table->used].pid = pid; table->info[table->used].flags = 0; table->used++; pidPtr[numPids] = pid; /* Restore in case of pipe_dup_err */ errorId = origErrorId; /* * Close off our copies of file descriptors that were set up for * this child, then set up the input for the next child. */ if (inputId != JIM_BAD_FD) { JimCloseFd(inputId); } if (outputId != JIM_BAD_FD) { JimCloseFd(outputId); } inputId = pipeIds[0]; pipeIds[0] = pipeIds[1] = JIM_BAD_FD; } *pidArrayPtr = pidPtr; /* * All done. Cleanup open files lying around and then return. */ cleanup: if (inputId != JIM_BAD_FD) { JimCloseFd(inputId); } if (lastOutputId != JIM_BAD_FD) { JimCloseFd(lastOutputId); } if (errorId != JIM_BAD_FD) { JimCloseFd(errorId); } Jim_Free(arg_array); JimRestoreEnv(save_environ); return numPids; /* * An error occurred. There could have been extra files open, such * as pipes between children. Clean them all up. Detach any child * processes that have been created. */ error: if ((inPipePtr != NULL) && (*inPipePtr != JIM_BAD_FD)) { JimCloseFd(*inPipePtr); *inPipePtr = JIM_BAD_FD; } if ((outPipePtr != NULL) && (*outPipePtr != JIM_BAD_FD)) { JimCloseFd(*outPipePtr); *outPipePtr = JIM_BAD_FD; } if ((errFilePtr != NULL) && (*errFilePtr != JIM_BAD_FD)) { JimCloseFd(*errFilePtr); *errFilePtr = JIM_BAD_FD; } if (pipeIds[0] != JIM_BAD_FD) { JimCloseFd(pipeIds[0]); } if (pipeIds[1] != JIM_BAD_FD) { JimCloseFd(pipeIds[1]); } if (pidPtr != NULL) { for (i = 0; i < numPids; i++) { if (pidPtr[i] != JIM_BAD_PID) { JimDetachPids(interp, 1, &pidPtr[i]); } } Jim_Free(pidPtr); } numPids = -1; goto cleanup; }
/* * The main [exec] command */ static int Jim_ExecCmd(Jim_Interp *interp, int argc, Jim_Obj *const *argv) { fdtype outputId; /* File id for output pipe. -1 * means command overrode. */ fdtype errorId; /* File id for temporary file * containing error output. */ pidtype *pidPtr; int numPids, result; /* * See if the command is to be run in background; if so, create * the command, detach it, and return. */ if (argc > 1 && Jim_CompareStringImmediate(interp, argv[argc - 1], "&")) { Jim_Obj *listObj; int i; argc--; 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, (long)pidPtr[i])); } Jim_SetResult(interp, listObj); JimDetachPids(interp, numPids, pidPtr); Jim_Free(pidPtr); return JIM_OK; } /* * Create the command's pipeline. */ numPids = JimCreatePipeline(interp, argc - 1, argv + 1, &pidPtr, NULL, &outputId, &errorId); if (numPids < 0) { return JIM_ERR; } /* * Read the child's output (if any) and put it into the result. */ Jim_SetResultString(interp, "", 0); result = JIM_OK; if (outputId != JIM_BAD_FD) { result = JimAppendStreamToString(interp, outputId, Jim_GetResult(interp)); if (result < 0) { Jim_SetResultErrno(interp, "error reading from output pipe"); } } if (JimCleanupChildren(interp, numPids, pidPtr, errorId) != JIM_OK) { result = JIM_ERR; } return result; }