1501 lines
45 KiB
C
1501 lines
45 KiB
C
/*
|
|
* (c) 2008 Steve Bennett <steveb@workware.net.au>
|
|
*
|
|
* Implements the exec command for Jim
|
|
*
|
|
* Based on code originally from Tcl 6.7 by John Ousterhout.
|
|
* From that code:
|
|
*
|
|
* The Tcl_Fork and Tcl_WaitPids procedures are based on code
|
|
* contributed by Karl Lehenbauer, Mark Diekhans and Peter
|
|
* da Silva.
|
|
*
|
|
* Copyright 1987-1991 Regents of the University of California
|
|
* Permission to use, copy, modify, and distribute this
|
|
* software and its documentation for any purpose and without
|
|
* fee is hereby granted, provided that the above copyright
|
|
* notice appear in all copies. The University of California
|
|
* makes no representations about the suitability of this
|
|
* software for any purpose. It is provided "as is" without
|
|
* express or implied warranty.
|
|
*/
|
|
|
|
#ifndef _GNU_SOURCE
|
|
#define _GNU_SOURCE
|
|
#endif
|
|
#include <string.h>
|
|
#include <ctype.h>
|
|
|
|
#include "jimautoconf.h"
|
|
#include <jim.h>
|
|
|
|
#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;
|
|
}
|
|
|
|
return JIM_OK;
|
|
}
|
|
|
|
int Jim_execInit(Jim_Interp *interp)
|
|
{
|
|
Jim_PackageProvideCheck(interp, "exec");
|
|
Jim_CreateCommand(interp, "exec", Jim_ExecCmd, NULL, NULL);
|
|
return JIM_OK;
|
|
}
|
|
#else
|
|
/* Full exec implementation for unix and mingw */
|
|
|
|
#include <errno.h>
|
|
#include <signal.h>
|
|
#include "jim-signal.h"
|
|
#include "jimiocompat.h"
|
|
#include <sys/stat.h>
|
|
|
|
struct WaitInfoTable;
|
|
|
|
static char **JimOriginalEnviron(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, int *inPipePtr, int *outPipePtr, int *errFilePtr);
|
|
static void JimDetachPids(struct WaitInfoTable *table, int numPids, const pidtype *pidPtr);
|
|
static int JimCleanupChildren(Jim_Interp *interp, int numPids, pidtype *pidPtr, Jim_Obj *errStrObj);
|
|
static int Jim_WaitCommand(Jim_Interp *interp, int argc, Jim_Obj *const *argv);
|
|
|
|
#if defined(__MINGW32__)
|
|
static pidtype JimStartWinProcess(Jim_Interp *interp, char **argv, char **env, int inputId, int outputId, int errorId);
|
|
#endif
|
|
|
|
/*
|
|
* If the last character of 'objPtr' is a newline, then remove
|
|
* the newline character.
|
|
*/
|
|
static void Jim_RemoveTrailingNewline(Jim_Obj *objPtr)
|
|
{
|
|
int len;
|
|
const char *s = Jim_GetString(objPtr, &len);
|
|
|
|
if (len > 0 && s[len - 1] == '\n') {
|
|
objPtr->length--;
|
|
objPtr->bytes[objPtr->length] = '\0';
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Read from 'fd', append the data to strObj and close 'fd'.
|
|
* Returns 1 if data was added, 0 if not, or -1 on error.
|
|
*/
|
|
static int JimAppendStreamToString(Jim_Interp *interp, int fd, Jim_Obj *strObj)
|
|
{
|
|
char buf[256];
|
|
FILE *fh = fdopen(fd, "r");
|
|
int ret = 0;
|
|
|
|
if (fh == NULL) {
|
|
return -1;
|
|
}
|
|
|
|
while (1) {
|
|
int retval = fread(buf, 1, sizeof(buf), fh);
|
|
if (retval > 0) {
|
|
ret = 1;
|
|
Jim_AppendString(interp, strObj, buf, retval);
|
|
}
|
|
if (retval != sizeof(buf)) {
|
|
break;
|
|
}
|
|
}
|
|
fclose(fh);
|
|
return ret;
|
|
}
|
|
|
|
/**
|
|
* Builds the environment array from $::env
|
|
*
|
|
* If $::env is not set, simply returns environ.
|
|
*
|
|
* Otherwise allocates the environ array from the contents of $::env
|
|
*
|
|
* If the exec fails, memory can be freed via JimFreeEnv()
|
|
*/
|
|
static char **JimBuildEnv(Jim_Interp *interp)
|
|
{
|
|
int i;
|
|
int size;
|
|
int num;
|
|
int n;
|
|
char **envptr;
|
|
char *envdata;
|
|
|
|
Jim_Obj *objPtr = Jim_GetGlobalVariableStr(interp, "env", JIM_NONE);
|
|
|
|
if (!objPtr) {
|
|
return JimOriginalEnviron();
|
|
}
|
|
|
|
/* 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 */
|
|
num = Jim_ListLength(interp, objPtr);
|
|
if (num % 2) {
|
|
/* Silently drop the last element if not a valid dictionary */
|
|
num--;
|
|
}
|
|
/* 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 need one extra char for the extra null terminator and one for the equal sign.
|
|
*/
|
|
size = Jim_Length(objPtr) + 2;
|
|
|
|
envptr = Jim_Alloc(sizeof(*envptr) * (num / 2 + 1) + size);
|
|
envdata = (char *)&envptr[num / 2 + 1];
|
|
|
|
n = 0;
|
|
for (i = 0; i < num; i += 2) {
|
|
const char *s1, *s2;
|
|
Jim_Obj *elemObj;
|
|
|
|
Jim_ListIndex(interp, objPtr, i, &elemObj, JIM_NONE);
|
|
s1 = Jim_String(elemObj);
|
|
Jim_ListIndex(interp, objPtr, i + 1, &elemObj, JIM_NONE);
|
|
s2 = Jim_String(elemObj);
|
|
|
|
envptr[n] = envdata;
|
|
envdata += sprintf(envdata, "%s=%s", s1, s2);
|
|
envdata++;
|
|
n++;
|
|
}
|
|
envptr[n] = NULL;
|
|
*envdata = 0;
|
|
|
|
return envptr;
|
|
}
|
|
|
|
/**
|
|
* Frees the environment allocated by JimBuildEnv()
|
|
*
|
|
* Must pass original_environ.
|
|
*/
|
|
static void JimFreeEnv(char **env, char **original_environ)
|
|
{
|
|
if (env != original_environ) {
|
|
Jim_Free(env);
|
|
}
|
|
}
|
|
|
|
static Jim_Obj *JimMakeErrorCode(Jim_Interp *interp, pidtype pid, int waitStatus, Jim_Obj *errStrObj)
|
|
{
|
|
Jim_Obj *errorCode = Jim_NewListObj(interp, NULL, 0);
|
|
|
|
if (pid == JIM_BAD_PID || pid == JIM_NO_PID) {
|
|
Jim_ListAppendElement(interp, errorCode, Jim_NewStringObj(interp, "NONE", -1));
|
|
Jim_ListAppendElement(interp, errorCode, Jim_NewIntObj(interp, (long)pid));
|
|
Jim_ListAppendElement(interp, errorCode, Jim_NewIntObj(interp, -1));
|
|
}
|
|
else if (WIFEXITED(waitStatus)) {
|
|
Jim_ListAppendElement(interp, errorCode, Jim_NewStringObj(interp, "CHILDSTATUS", -1));
|
|
Jim_ListAppendElement(interp, errorCode, Jim_NewIntObj(interp, (long)pid));
|
|
Jim_ListAppendElement(interp, errorCode, Jim_NewIntObj(interp, WEXITSTATUS(waitStatus)));
|
|
}
|
|
else {
|
|
const char *type;
|
|
const char *action;
|
|
const char *signame;
|
|
|
|
if (WIFSIGNALED(waitStatus)) {
|
|
type = "CHILDKILLED";
|
|
action = "killed";
|
|
signame = Jim_SignalId(WTERMSIG(waitStatus));
|
|
}
|
|
else {
|
|
type = "CHILDSUSP";
|
|
action = "suspended";
|
|
signame = "none";
|
|
}
|
|
|
|
Jim_ListAppendElement(interp, errorCode, Jim_NewStringObj(interp, type, -1));
|
|
|
|
if (errStrObj) {
|
|
/* Append the message to 'errStrObj' with a newline.
|
|
* The last newline will be stripped later
|
|
*/
|
|
Jim_AppendStrings(interp, errStrObj, "child ", action, " by signal ", Jim_SignalId(WTERMSIG(waitStatus)), "\n", NULL);
|
|
}
|
|
|
|
Jim_ListAppendElement(interp, errorCode, Jim_NewIntObj(interp, (long)pid));
|
|
Jim_ListAppendElement(interp, errorCode, Jim_NewStringObj(interp, signame, -1));
|
|
}
|
|
return errorCode;
|
|
}
|
|
|
|
/*
|
|
* Create and store an appropriate value for the global variable $::errorCode
|
|
* Based on pid and waitStatus.
|
|
*
|
|
* Returns JIM_OK for a normal exit with code 0, otherwise returns JIM_ERR.
|
|
*
|
|
* Note that $::errorCode is left unchanged for a normal exit.
|
|
* Details of any abnormal exit is appended to the errStrObj, unless it is NULL.
|
|
*/
|
|
static int JimCheckWaitStatus(Jim_Interp *interp, pidtype pid, int waitStatus, Jim_Obj *errStrObj)
|
|
{
|
|
if (WIFEXITED(waitStatus) && WEXITSTATUS(waitStatus) == 0) {
|
|
return JIM_OK;
|
|
}
|
|
Jim_SetGlobalVariableStr(interp, "errorCode", JimMakeErrorCode(interp, pid, waitStatus, errStrObj));
|
|
|
|
return JIM_ERR;
|
|
}
|
|
|
|
/*
|
|
* Data structures of the following type are used by exec and
|
|
* wait to keep track of child processes.
|
|
*/
|
|
|
|
struct WaitInfo
|
|
{
|
|
pidtype pid; /* Process id of child. */
|
|
int status; /* Status returned when child exited or suspended. */
|
|
int flags; /* Various flag bits; see below for definitions. */
|
|
};
|
|
|
|
/* This table is shared by exec and wait */
|
|
struct WaitInfoTable {
|
|
struct WaitInfo *info; /* Table of outstanding processes */
|
|
int size; /* Size of the allocated table */
|
|
int used; /* Number of entries in use */
|
|
int refcount; /* Free the table once the refcount drops to 0 */
|
|
};
|
|
|
|
/*
|
|
* Flag bits in WaitInfo structures:
|
|
*
|
|
* WI_DETACHED - Non-zero means no-one cares about the
|
|
* process anymore. Ignore it until it
|
|
* exits, then forget about it.
|
|
*/
|
|
|
|
#define WI_DETACHED 2
|
|
|
|
#define WAIT_TABLE_GROW_BY 4
|
|
|
|
static void JimFreeWaitInfoTable(struct Jim_Interp *interp, void *privData)
|
|
{
|
|
struct WaitInfoTable *table = privData;
|
|
|
|
if (--table->refcount == 0) {
|
|
Jim_Free(table->info);
|
|
Jim_Free(table);
|
|
}
|
|
}
|
|
|
|
static struct WaitInfoTable *JimAllocWaitInfoTable(void)
|
|
{
|
|
struct WaitInfoTable *table = Jim_Alloc(sizeof(*table));
|
|
table->info = NULL;
|
|
table->size = table->used = 0;
|
|
table->refcount = 1;
|
|
|
|
return table;
|
|
}
|
|
|
|
/**
|
|
* Removes the given pid from the wait table.
|
|
*
|
|
* Returns 0 if OK or -1 if not found.
|
|
*/
|
|
static int JimWaitRemove(struct WaitInfoTable *table, pidtype pid)
|
|
{
|
|
int i;
|
|
|
|
/* Find it in the table */
|
|
for (i = 0; i < table->used; i++) {
|
|
if (pid == table->info[i].pid) {
|
|
if (i != table->used - 1) {
|
|
table->info[i] = table->info[table->used - 1];
|
|
}
|
|
table->used--;
|
|
return 0;
|
|
}
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
/*
|
|
* 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 means command overrode. */
|
|
int errorId; /* File id for temporary file containing error output. */
|
|
pidtype *pidPtr;
|
|
int numPids, result;
|
|
int child_siginfo = 1;
|
|
Jim_Obj *childErrObj;
|
|
Jim_Obj *errStrObj;
|
|
struct WaitInfoTable *table = Jim_CmdPrivData(interp);
|
|
|
|
/*
|
|
* See if the command is to be run in the 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(table, 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;
|
|
}
|
|
|
|
result = JIM_OK;
|
|
|
|
errStrObj = Jim_NewStringObj(interp, "", 0);
|
|
|
|
/* Read from the output pipe until EOF */
|
|
if (outputId != -1) {
|
|
if (JimAppendStreamToString(interp, outputId, errStrObj) < 0) {
|
|
result = JIM_ERR;
|
|
Jim_SetResultErrno(interp, "error reading from output pipe");
|
|
}
|
|
}
|
|
|
|
/* Now wait for children to finish. Any abnormal results are appended to childErrObj */
|
|
childErrObj = Jim_NewStringObj(interp, "", 0);
|
|
Jim_IncrRefCount(childErrObj);
|
|
|
|
if (JimCleanupChildren(interp, numPids, pidPtr, childErrObj) != JIM_OK) {
|
|
result = JIM_ERR;
|
|
}
|
|
|
|
/*
|
|
* Read the child's error output (if any) and put it into the result.
|
|
*
|
|
* Note that unlike Tcl, the presence of stderr output does not cause
|
|
* exec to return an error.
|
|
*/
|
|
if (errorId != -1) {
|
|
int ret;
|
|
lseek(errorId, 0, SEEK_SET);
|
|
ret = JimAppendStreamToString(interp, errorId, errStrObj);
|
|
if (ret < 0) {
|
|
Jim_SetResultErrno(interp, "error reading from error pipe");
|
|
result = JIM_ERR;
|
|
}
|
|
else if (ret > 0) {
|
|
/* Got some error output, so discard the abnormal info string */
|
|
child_siginfo = 0;
|
|
}
|
|
}
|
|
|
|
if (child_siginfo) {
|
|
/* Append the child siginfo to the result */
|
|
Jim_AppendObj(interp, errStrObj, childErrObj);
|
|
}
|
|
Jim_DecrRefCount(interp, childErrObj);
|
|
|
|
/* Finally remove any trailing newline from the result */
|
|
Jim_RemoveTrailingNewline(errStrObj);
|
|
|
|
/* Set this as the result */
|
|
Jim_SetResult(interp, errStrObj);
|
|
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
* 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 JIM_BAD_PID if the pid was not in the table.
|
|
*/
|
|
static pidtype JimWaitForProcess(struct WaitInfoTable *table, pidtype pid, int *statusPtr)
|
|
{
|
|
if (JimWaitRemove(table, pid) == 0) {
|
|
/* wait for it */
|
|
waitpid(pid, statusPtr, 0);
|
|
return pid;
|
|
}
|
|
|
|
/* Not found */
|
|
return JIM_BAD_PID;
|
|
}
|
|
|
|
/**
|
|
* Indicates that one or more child processes have been placed in
|
|
* background and are no longer cared about.
|
|
* These children can be cleaned up with JimReapDetachedPids().
|
|
*/
|
|
static void JimDetachPids(struct WaitInfoTable *table, int numPids, const pidtype *pidPtr)
|
|
{
|
|
int j;
|
|
|
|
for (j = 0; j < numPids; j++) {
|
|
/* Find it in the table */
|
|
int i;
|
|
for (i = 0; i < table->used; i++) {
|
|
if (pidPtr[j] == table->info[i].pid) {
|
|
table->info[i].flags |= WI_DETACHED;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Use 'name getfd' to get the file descriptor associated with channel 'name'
|
|
* Returns the file descriptor or -1 on error
|
|
*/
|
|
static int JimGetChannelFd(Jim_Interp *interp, const char *name)
|
|
{
|
|
Jim_Obj *objv[2];
|
|
|
|
objv[0] = Jim_NewStringObj(interp, name, -1);
|
|
objv[1] = Jim_NewStringObj(interp, "getfd", -1);
|
|
|
|
if (Jim_EvalObjVector(interp, 2, objv) == JIM_OK) {
|
|
jim_wide fd;
|
|
if (Jim_GetWide(interp, Jim_GetResult(interp), &fd) == JIM_OK) {
|
|
return fd;
|
|
}
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
static void JimReapDetachedPids(struct WaitInfoTable *table)
|
|
{
|
|
struct WaitInfo *waitPtr;
|
|
int count;
|
|
int dest;
|
|
|
|
if (!table) {
|
|
return;
|
|
}
|
|
|
|
waitPtr = table->info;
|
|
dest = 0;
|
|
for (count = table->used; count > 0; waitPtr++, count--) {
|
|
if (waitPtr->flags & WI_DETACHED) {
|
|
int status;
|
|
pidtype pid = waitpid(waitPtr->pid, &status, WNOHANG);
|
|
if (pid == waitPtr->pid) {
|
|
/* Process has exited, so remove it from the table */
|
|
table->used--;
|
|
continue;
|
|
}
|
|
}
|
|
if (waitPtr != &table->info[dest]) {
|
|
table->info[dest] = *waitPtr;
|
|
}
|
|
dest++;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* wait ?-nohang? ?pid?
|
|
*
|
|
* An interface to waitpid(2)
|
|
*
|
|
* Returns a 3 element list.
|
|
*
|
|
* If the process has not exited or doesn't exist, returns:
|
|
*
|
|
* {NONE x x}
|
|
*
|
|
* If the process exited normally, returns:
|
|
*
|
|
* {CHILDSTATUS <pid> <exit-status>}
|
|
*
|
|
* If the process terminated on a signal, returns:
|
|
*
|
|
* {CHILDKILLED <pid> <signal>}
|
|
*
|
|
* Otherwise (core dump, stopped, continued, ...), returns:
|
|
*
|
|
* {CHILDSUSP <pid> none}
|
|
*
|
|
* With no arguments, reaps any finished background processes started by exec ... &
|
|
*/
|
|
static int Jim_WaitCommand(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
|
|
{
|
|
struct WaitInfoTable *table = Jim_CmdPrivData(interp);
|
|
int nohang = 0;
|
|
pidtype pid;
|
|
long pidarg;
|
|
int status;
|
|
Jim_Obj *errCodeObj;
|
|
|
|
/* With no arguments, reap detached children */
|
|
if (argc == 1) {
|
|
JimReapDetachedPids(table);
|
|
return JIM_OK;
|
|
}
|
|
|
|
if (argc > 1 && Jim_CompareStringImmediate(interp, argv[1], "-nohang")) {
|
|
nohang = 1;
|
|
}
|
|
if (argc != nohang + 2) {
|
|
Jim_WrongNumArgs(interp, 1, argv, "?-nohang? ?pid?");
|
|
return JIM_ERR;
|
|
}
|
|
if (Jim_GetLong(interp, argv[nohang + 1], &pidarg) != JIM_OK) {
|
|
return JIM_ERR;
|
|
}
|
|
|
|
pid = waitpid((pidtype)pidarg, &status, nohang ? WNOHANG : 0);
|
|
|
|
errCodeObj = JimMakeErrorCode(interp, pid, status, NULL);
|
|
|
|
if (pid != JIM_BAD_PID && (WIFEXITED(status) || WIFSIGNALED(status))) {
|
|
/* The process has finished. Remove it from the wait table if it exists there */
|
|
JimWaitRemove(table, pid);
|
|
}
|
|
Jim_SetResult(interp, errCodeObj);
|
|
return JIM_OK;
|
|
}
|
|
|
|
static int Jim_PidCommand(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
|
|
{
|
|
if (argc != 1) {
|
|
Jim_WrongNumArgs(interp, 1, argv, "");
|
|
return JIM_ERR;
|
|
}
|
|
|
|
Jim_SetResultInt(interp, (jim_wide)getpid());
|
|
return JIM_OK;
|
|
}
|
|
|
|
/*
|
|
*----------------------------------------------------------------------
|
|
*
|
|
* 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,
|
|
int *inPipePtr, int *outPipePtr, int *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. */
|
|
int inputId = -1;
|
|
/* Readable file id input to current command in
|
|
* pipeline (could be file or pipe). -1
|
|
* means use stdin. */
|
|
int outputId = -1;
|
|
/* 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
|
|
* means use stderr. */
|
|
int lastOutputId = -1;
|
|
/* 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. */
|
|
int firstArg, lastArg; /* Indexes of first and last arguments in
|
|
* current command. */
|
|
int lastBar;
|
|
int i;
|
|
pidtype pid;
|
|
char **save_environ;
|
|
#ifndef __MINGW32__
|
|
char **child_environ;
|
|
#endif
|
|
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;
|
|
|
|
if (inPipePtr != NULL) {
|
|
*inPipePtr = -1;
|
|
}
|
|
if (outPipePtr != NULL) {
|
|
*outPipePtr = -1;
|
|
}
|
|
if (errFilePtr != NULL) {
|
|
*errFilePtr = -1;
|
|
}
|
|
pipeIds[0] = pipeIds[1] = -1;
|
|
|
|
/*
|
|
* 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 = Jim_MakeTempFile(interp, NULL, 1);
|
|
if (inputId == -1) {
|
|
goto error;
|
|
}
|
|
if (write(inputId, input, input_len) != input_len) {
|
|
Jim_SetResultErrno(interp, "couldn't write temp file");
|
|
close(inputId);
|
|
goto error;
|
|
}
|
|
lseek(inputId, 0L, SEEK_SET);
|
|
}
|
|
else if (inputFile == FILE_HANDLE) {
|
|
int fd = JimGetChannelFd(interp, input);
|
|
|
|
if (fd < 0) {
|
|
goto error;
|
|
}
|
|
inputId = dup(fd);
|
|
}
|
|
else {
|
|
/*
|
|
* File redirection. Just open the file.
|
|
*/
|
|
inputId = Jim_OpenForRead(input);
|
|
if (inputId == -1) {
|
|
Jim_SetResultFormatted(interp, "couldn't read file \"%s\": %s", input, strerror(Jim_Errno()));
|
|
goto error;
|
|
}
|
|
}
|
|
}
|
|
else if (inPipePtr != NULL) {
|
|
if (pipe(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;
|
|
}
|
|
|
|
/*
|
|
* Set up the redirected output sink for the pipeline from one
|
|
* of two places, if requested.
|
|
*/
|
|
if (output != NULL) {
|
|
if (outputFile == FILE_HANDLE) {
|
|
int fd = JimGetChannelFd(interp, output);
|
|
if (fd < 0) {
|
|
goto error;
|
|
}
|
|
lastOutputId = dup(fd);
|
|
}
|
|
else {
|
|
/*
|
|
* Output is to go to a file.
|
|
*/
|
|
lastOutputId = Jim_OpenForWrite(output, outputFile == FILE_APPEND);
|
|
if (lastOutputId == -1) {
|
|
Jim_SetResultFormatted(interp, "couldn't write file \"%s\": %s", output, strerror(Jim_Errno()));
|
|
goto error;
|
|
}
|
|
}
|
|
}
|
|
else if (outPipePtr != NULL) {
|
|
/*
|
|
* Output is to go to a pipe.
|
|
*/
|
|
if (pipe(pipeIds) != 0) {
|
|
Jim_SetResultErrno(interp, "couldn't create output pipe");
|
|
goto error;
|
|
}
|
|
lastOutputId = pipeIds[1];
|
|
*outPipePtr = pipeIds[0];
|
|
pipeIds[0] = pipeIds[1] = -1;
|
|
}
|
|
/* 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 != -1) {
|
|
errorId = dup(lastOutputId);
|
|
}
|
|
else {
|
|
/* No redirection of stdout, so just use 2>@stdout */
|
|
error = "stdout";
|
|
}
|
|
}
|
|
if (errorId == -1) {
|
|
int fd = JimGetChannelFd(interp, error);
|
|
if (fd < 0) {
|
|
goto error;
|
|
}
|
|
errorId = dup(fd);
|
|
}
|
|
}
|
|
else {
|
|
/*
|
|
* Output is to go to a file.
|
|
*/
|
|
errorId = Jim_OpenForWrite(error, errorFile == FILE_APPEND);
|
|
if (errorId == -1) {
|
|
Jim_SetResultFormatted(interp, "couldn't write file \"%s\": %s", error, strerror(Jim_Errno()));
|
|
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 = Jim_MakeTempFile(interp, NULL, 1);
|
|
if (errorId == -1) {
|
|
goto error;
|
|
}
|
|
*errFilePtr = dup(errorId);
|
|
}
|
|
|
|
/*
|
|
* Scan through the argc array, forking off a process for each
|
|
* group of arguments between "|" arguments.
|
|
*/
|
|
|
|
pidPtr = Jim_Alloc(cmdCount * sizeof(*pidPtr));
|
|
for (firstArg = 0; firstArg < arg_count; numPids++, firstArg = lastArg + 1) {
|
|
int pipe_dup_err = 0;
|
|
int origErrorId = errorId;
|
|
|
|
for (lastArg = firstArg; lastArg < arg_count; lastArg++) {
|
|
if (strcmp(arg_array[lastArg], "|") == 0) {
|
|
break;
|
|
}
|
|
if (strcmp(arg_array[lastArg], "|&") == 0) {
|
|
pipe_dup_err = 1;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (lastArg == firstArg) {
|
|
Jim_SetResultString(interp, "missing command to exec", -1);
|
|
goto error;
|
|
}
|
|
|
|
/* Replace | with NULL for execv() */
|
|
arg_array[lastArg] = NULL;
|
|
if (lastArg == arg_count) {
|
|
outputId = lastOutputId;
|
|
lastOutputId = -1;
|
|
}
|
|
else {
|
|
if (pipe(pipeIds) != 0) {
|
|
Jim_SetResultErrno(interp, "couldn't create pipe");
|
|
goto error;
|
|
}
|
|
outputId = pipeIds[1];
|
|
}
|
|
|
|
/* Need to do this before vfork() */
|
|
if (pipe_dup_err) {
|
|
errorId = outputId;
|
|
}
|
|
|
|
/* Now fork the child */
|
|
|
|
#ifdef __MINGW32__
|
|
pid = JimStartWinProcess(interp, &arg_array[firstArg], save_environ, inputId, outputId, errorId);
|
|
if (pid == JIM_BAD_PID) {
|
|
Jim_SetResultFormatted(interp, "couldn't exec \"%s\"", arg_array[firstArg]);
|
|
goto error;
|
|
}
|
|
#else
|
|
i = strlen(arg_array[firstArg]);
|
|
|
|
child_environ = Jim_GetEnviron();
|
|
/*
|
|
* Make a new process and enter it into the table if the vfork
|
|
* is successful.
|
|
*/
|
|
pid = vfork();
|
|
if (pid < 0) {
|
|
Jim_SetResultErrno(interp, "couldn't fork child process");
|
|
goto error;
|
|
}
|
|
if (pid == 0) {
|
|
/* Child */
|
|
/* Set up stdin, stdout, stderr */
|
|
if (inputId != -1 && inputId != fileno(stdin)) {
|
|
dup2(inputId, fileno(stdin));
|
|
close(inputId);
|
|
}
|
|
if (outputId != -1 && outputId != fileno(stdout)) {
|
|
dup2(outputId, fileno(stdout));
|
|
if (outputId != errorId) {
|
|
close(outputId);
|
|
}
|
|
}
|
|
if (errorId != -1 && errorId != fileno(stderr)) {
|
|
dup2(errorId, fileno(stderr));
|
|
close(errorId);
|
|
}
|
|
/* Close parent-only file descriptors */
|
|
if (outPipePtr && *outPipePtr != -1) {
|
|
close(*outPipePtr);
|
|
}
|
|
if (errFilePtr && *errFilePtr != -1) {
|
|
close(*errFilePtr);
|
|
}
|
|
if (pipeIds[0] != -1) {
|
|
close(pipeIds[0]);
|
|
}
|
|
if (lastOutputId != -1) {
|
|
close(lastOutputId);
|
|
}
|
|
|
|
execvpe(arg_array[firstArg], &arg_array[firstArg], child_environ);
|
|
|
|
if (write(fileno(stderr), "couldn't exec \"", 15) &&
|
|
write(fileno(stderr), arg_array[firstArg], i) &&
|
|
write(fileno(stderr), "\"\n", 2)) {
|
|
/* nothing */
|
|
}
|
|
#ifdef JIM_MAINTAINER
|
|
{
|
|
/* Keep valgrind happy */
|
|
static char *const false_argv[2] = {"false", NULL};
|
|
execvp(false_argv[0],false_argv);
|
|
}
|
|
#endif
|
|
_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 != -1) {
|
|
close(inputId);
|
|
}
|
|
if (outputId != -1) {
|
|
close(outputId);
|
|
}
|
|
inputId = pipeIds[0];
|
|
pipeIds[0] = pipeIds[1] = -1;
|
|
}
|
|
*pidArrayPtr = pidPtr;
|
|
|
|
/*
|
|
* All done. Cleanup open files lying around and then return.
|
|
*/
|
|
|
|
cleanup:
|
|
if (inputId != -1) {
|
|
close(inputId);
|
|
}
|
|
if (lastOutputId != -1) {
|
|
close(lastOutputId);
|
|
}
|
|
if (errorId != -1) {
|
|
close(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 != -1)) {
|
|
close(*inPipePtr);
|
|
*inPipePtr = -1;
|
|
}
|
|
if ((outPipePtr != NULL) && (*outPipePtr != -1)) {
|
|
close(*outPipePtr);
|
|
*outPipePtr = -1;
|
|
}
|
|
if ((errFilePtr != NULL) && (*errFilePtr != -1)) {
|
|
close(*errFilePtr);
|
|
*errFilePtr = -1;
|
|
}
|
|
if (pipeIds[0] != -1) {
|
|
close(pipeIds[0]);
|
|
}
|
|
if (pipeIds[1] != -1) {
|
|
close(pipeIds[1]);
|
|
}
|
|
if (pidPtr != NULL) {
|
|
for (i = 0; i < numPids; i++) {
|
|
if (pidPtr[i] != JIM_BAD_PID) {
|
|
JimDetachPids(table, 1, &pidPtr[i]);
|
|
}
|
|
}
|
|
Jim_Free(pidPtr);
|
|
}
|
|
numPids = -1;
|
|
goto cleanup;
|
|
}
|
|
|
|
/*
|
|
*----------------------------------------------------------------------
|
|
*
|
|
* JimCleanupChildren --
|
|
*
|
|
* This is a utility procedure used to wait for child processes
|
|
* to exit, record information about abnormal exits.
|
|
*
|
|
* Results:
|
|
* The return value is a standard Tcl result. If anything at
|
|
* weird happened with the child processes, JIM_ERR is returned
|
|
* and a structured message is left in $::errorCode.
|
|
* If errStrObj is not NULL, abnormal exit details are appended to this object.
|
|
*
|
|
* Side effects:
|
|
* pidPtr is freed
|
|
*
|
|
*----------------------------------------------------------------------
|
|
*/
|
|
|
|
static int JimCleanupChildren(Jim_Interp *interp, int numPids, pidtype *pidPtr, Jim_Obj *errStrObj)
|
|
{
|
|
struct WaitInfoTable *table = Jim_CmdPrivData(interp);
|
|
int result = JIM_OK;
|
|
int i;
|
|
|
|
/* Now check the return status of each child */
|
|
for (i = 0; i < numPids; i++) {
|
|
int waitStatus = 0;
|
|
if (JimWaitForProcess(table, pidPtr[i], &waitStatus) != JIM_BAD_PID) {
|
|
if (JimCheckWaitStatus(interp, pidPtr[i], waitStatus, errStrObj) != JIM_OK) {
|
|
result = JIM_ERR;
|
|
}
|
|
}
|
|
}
|
|
Jim_Free(pidPtr);
|
|
|
|
return result;
|
|
}
|
|
|
|
int Jim_execInit(Jim_Interp *interp)
|
|
{
|
|
struct WaitInfoTable *waitinfo;
|
|
|
|
Jim_PackageProvideCheck(interp, "exec");
|
|
|
|
waitinfo = JimAllocWaitInfoTable();
|
|
Jim_CreateCommand(interp, "exec", Jim_ExecCmd, waitinfo, JimFreeWaitInfoTable);
|
|
waitinfo->refcount++;
|
|
Jim_CreateCommand(interp, "wait", Jim_WaitCommand, waitinfo, JimFreeWaitInfoTable);
|
|
Jim_CreateCommand(interp, "pid", Jim_PidCommand, 0, 0);
|
|
|
|
return JIM_OK;
|
|
}
|
|
|
|
#if defined(__MINGW32__)
|
|
/* Windows-specific (mingw) implementation */
|
|
|
|
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++) {
|
|
snprintf(fullPath, MAX_PATH, "%s%s", originalName, extensions[i]);
|
|
|
|
if (SearchPath(NULL, fullPath, NULL, MAX_PATH, fullPath, NULL) == 0) {
|
|
continue;
|
|
}
|
|
if (GetFileAttributes(fullPath) & FILE_ATTRIBUTE_DIRECTORY) {
|
|
continue;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
return -1;
|
|
}
|
|
|
|
static char **JimSaveEnv(char **env)
|
|
{
|
|
return env;
|
|
}
|
|
|
|
static void JimRestoreEnv(char **env)
|
|
{
|
|
JimFreeEnv(env, Jim_GetEnviron());
|
|
}
|
|
|
|
static char **JimOriginalEnviron(void)
|
|
{
|
|
return 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;
|
|
}
|
|
}
|
|
}
|
|
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);
|
|
}
|
|
}
|
|
return strObj;
|
|
}
|
|
|
|
/**
|
|
* Note that inputId, etc. are osf_handles.
|
|
*/
|
|
static pidtype
|
|
JimStartWinProcess(Jim_Interp *interp, char **argv, char **env, int inputId, int outputId, int errorId)
|
|
{
|
|
STARTUPINFO startInfo;
|
|
PROCESS_INFORMATION procInfo;
|
|
HANDLE hProcess;
|
|
char execPath[MAX_PATH];
|
|
pidtype pid = JIM_BAD_PID;
|
|
Jim_Obj *cmdLineObj;
|
|
char *winenv;
|
|
|
|
if (JimWinFindExecutable(argv[0], execPath) < 0) {
|
|
return JIM_BAD_PID;
|
|
}
|
|
argv[0] = execPath;
|
|
|
|
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 stdin was not redirected, input should come from the parent's stdin
|
|
*/
|
|
if (inputId == -1) {
|
|
inputId = _fileno(stdin);
|
|
}
|
|
DuplicateHandle(hProcess, (HANDLE)_get_osfhandle(inputId), hProcess, &startInfo.hStdInput,
|
|
0, TRUE, DUPLICATE_SAME_ACCESS);
|
|
if (startInfo.hStdInput == INVALID_HANDLE_VALUE) {
|
|
goto end;
|
|
}
|
|
|
|
/*
|
|
* If stdout was not redirected, output should go to the parent's stdout
|
|
*/
|
|
if (outputId == -1) {
|
|
outputId = _fileno(stdout);
|
|
}
|
|
DuplicateHandle(hProcess, (HANDLE)_get_osfhandle(outputId), hProcess, &startInfo.hStdOutput,
|
|
0, TRUE, DUPLICATE_SAME_ACCESS);
|
|
if (startInfo.hStdOutput == INVALID_HANDLE_VALUE) {
|
|
goto end;
|
|
}
|
|
|
|
/* Ditto stderr */
|
|
if (errorId == -1) {
|
|
errorId = _fileno(stderr);
|
|
}
|
|
DuplicateHandle(hProcess, (HANDLE)_get_osfhandle(errorId), hProcess, &startInfo.hStdError,
|
|
0, TRUE, DUPLICATE_SAME_ACCESS);
|
|
if (startInfo.hStdError == INVALID_HANDLE_VALUE) {
|
|
goto end;
|
|
}
|
|
|
|
/* If env is NULL, use the original environment.
|
|
* If env[0] is NULL, use an empty environment.
|
|
* Otherwise use the environment starting at env[0]
|
|
*/
|
|
if (env == NULL) {
|
|
/* Use the original environment */
|
|
winenv = NULL;
|
|
}
|
|
else if (env[0] == NULL) {
|
|
winenv = (char *)"\0";
|
|
}
|
|
else {
|
|
winenv = env[0];
|
|
}
|
|
|
|
if (!CreateProcess(NULL, (char *)Jim_String(cmdLineObj), NULL, NULL, TRUE,
|
|
0, winenv, 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 != INVALID_HANDLE_VALUE) {
|
|
CloseHandle(startInfo.hStdInput);
|
|
}
|
|
if (startInfo.hStdOutput != INVALID_HANDLE_VALUE) {
|
|
CloseHandle(startInfo.hStdOutput);
|
|
}
|
|
if (startInfo.hStdError != INVALID_HANDLE_VALUE) {
|
|
CloseHandle(startInfo.hStdError);
|
|
}
|
|
return pid;
|
|
}
|
|
|
|
#else
|
|
|
|
static char **JimOriginalEnviron(void)
|
|
{
|
|
return Jim_GetEnviron();
|
|
}
|
|
|
|
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
|