aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--jim-exec.c149
-rw-r--r--jim_tcl.txt4
-rw-r--r--tests/exec2.test39
3 files changed, 180 insertions, 12 deletions
diff --git a/jim-exec.c b/jim-exec.c
index 88b7f53..77e6afd 100644
--- a/jim-exec.c
+++ b/jim-exec.c
@@ -27,6 +27,8 @@
#include "jim-subcmd.h"
#include "jim-signal.h"
+extern char **environ;
+
/* These two could be moved into the Tcl core */
static void Jim_SetResultErrno(Jim_Interp *interp, const char *msg)
{
@@ -84,6 +86,60 @@ static void JimTrimTrailingNewline(Jim_Interp *interp)
}
}
+/**
+ * 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)
+{
+#ifdef jim_ext_tclcompat
+ int i;
+ int len;
+ int n;
+ char **env;
+
+ Jim_Obj *objPtr = Jim_GetGlobalVariableStr(interp, "env", JIM_NONE);
+
+ if (!objPtr) {
+ return environ;
+ }
+
+ /* Calculate the required size */
+ len = Jim_ListLength(interp, objPtr);
+ if (len % 2) {
+ len--;
+ }
+
+ env = Jim_Alloc(sizeof(*env) * (len / 2 + 1));
+
+ n = 0;
+ for (i = 0; i < len; i += 2) {
+ int l1, l2;
+ const char *s1, *s2;
+ Jim_Obj *elemObj;
+
+ Jim_ListIndex(interp, objPtr, i, &elemObj, JIM_NONE);
+ s1 = Jim_GetString(elemObj, &l1);
+ Jim_ListIndex(interp, objPtr, i + 1, &elemObj, JIM_NONE);
+ s2 = Jim_GetString(elemObj, &l2);
+
+ env[n] = Jim_Alloc(l1 + l2 + 2);
+ sprintf(env[n], "%s=%s", s1, s2);
+ n++;
+ }
+ env[n] = NULL;
+
+ return env;
+#else
+ return environ;
+#endif
+}
+
/*
* Create error messages for unusual process exits. An
* extra newline gets appended to each error message, but
@@ -898,10 +954,13 @@ Jim_CreatePipeline(Jim_Interp *interp, int argc, Jim_Obj *const *argv, int **pid
for (i = 3; (i <= outputId) || (i <= inputId) || (i <= errorId); i++) {
close(i);
}
+ /* Build new environ from the current ::env dict */
+ environ = JimBuildEnv(interp);
execvp(execName, &arg_array[firstArg]);
- sprintf(errSpace, "couldn't find \"%.150s\" to execute\n", arg_array[firstArg]);
+
+ snprintf(errSpace, sizeof(errSpace), "couldn't find \"%.150s\" to execute\n", arg_array[firstArg]);
rc = write(2, errSpace, strlen(errSpace));
- _exit(1);
+ _exit(127);
}
else {
pidPtr[numPids] = pid;
@@ -1033,18 +1092,74 @@ static int Jim_CleanupChildren(Jim_Interp *interp, int numPids, int *pidPtr, int
return result;
}
#else
+/**
+ * Frees the environment allocated by JimBuildEnv()
+ *
+ * Must pass original_environ.
+ */
+static void JimFreeEnv(Jim_Interp *interp, 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
+}
+
+/**
+ * Joins the given args into a single quoted, space-separate string.
+ */
+static Jim_Obj *JimQuoteArgs(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
+{
+ Jim_Obj *cmdlineObj = Jim_NewEmptyStringObj(interp);
+ int i, j;
+
+ /* Create an escape command lines */
+ for (i = 0; i < argc; i++) {
+ int len;
+ const char *arg = Jim_GetString(argv[i], &len);
+
+ if (i) {
+ Jim_AppendString(interp, cmdlineObj, " ", 1);
+ }
+ for (j = 0; j < len; j++) {
+ if (arg[j] == '\\') {
+ /* Need to double escape backslash */
+ Jim_AppendString(interp, cmdlineObj, "\\\\\\\\", 4);
+ continue;
+ }
+ if (arg[j] == '"' || arg[j] == '`' || arg[j] == '\'' || arg[j] == '\n') {
+ /* Escape these */
+ Jim_AppendString(interp, cmdlineObj, "\\", 1);
+ }
+ Jim_AppendString(interp, cmdlineObj, &arg[j], 1);
+ }
+ }
+
+ return cmdlineObj;
+}
+
static int Jim_ExecCmd(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
{
int pid;
int tmpfd;
int status;
int result;
- char **nargv;
- int i;
+ char *nargv[4];
+ char **new_environ;
+ Jim_Obj *cmdlineObj;
#define TMP_NAME "/tmp/tcl.exec.XXXXXX"
char tmpname[sizeof(TMP_NAME) + 1];
+ if (argc > 1 && Jim_CompareStringImmediate(interp, argv[argc - 1], "&")) {
+ Jim_SetResultString(interp, "unsupported: background exec", -1);
+ return JIM_ERR;
+ }
/* Create a temporary file for the output from our exec command */
strcpy(tmpname, TMP_NAME);
@@ -1054,18 +1169,26 @@ static int Jim_ExecCmd(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
return JIM_ERR;
}
- nargv = Jim_Alloc(sizeof(*nargv) * argc);
- for (i = 1; i < argc; i++) {
- nargv[i - 1] = (char *)Jim_GetString(argv[i], NULL);
- }
- nargv[i - 1] = NULL;
+
+ /* Execute: sh -c "arguments..."
+ *
+ * Note we do it this way in order to get command line redirection, etc.
+ */
+ cmdlineObj = JimQuoteArgs(interp, argc - 1, argv + 1);
+
+ nargv[0] = "sh";
+ nargv[1] = "-c";
+ nargv[2] = (char *)Jim_GetString(cmdlineObj, NULL);
+ nargv[3] = NULL;
/*printf("Writing output to %s, fd=%d\n", tmpname, tmpfd); */
unlink(tmpname);
+ /* Must do this before vfork() */
+ new_environ = JimBuildEnv(interp);
+
/* Use vfork and send output to this temporary file */
pid = vfork();
- //pid = 0;
if (pid == 0) {
close(0);
open("/dev/null", O_RDONLY);
@@ -1074,7 +1197,7 @@ static int Jim_ExecCmd(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
close(2);
if (dup(tmpfd) != -1) {
close(tmpfd);
- execvp(nargv[0], nargv);
+ execve("/bin/sh", nargv, new_environ);
}
}
_exit(127);
@@ -1083,7 +1206,9 @@ static int Jim_ExecCmd(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
/* Wait for the child to exit */
waitpid(pid, &status, 0);
- Jim_Free(nargv);
+ JimFreeEnv(interp, new_environ, environ);
+
+ Jim_FreeNewObj(interp, cmdlineObj);
result = JimCheckWaitStatus(interp, pid, status);
diff --git a/jim_tcl.txt b/jim_tcl.txt
index d76b6c9..126e260 100644
--- a/jim_tcl.txt
+++ b/jim_tcl.txt
@@ -80,6 +80,7 @@ Since v0.62:
18. Command pipelines via open "|..." are now supported
19. Add 'info references'
20. Add support for 'after *ms*', 'after idle', 'after info', 'update'
+21. 'exec' now sets environment based on $::env
Since v0.61:
@@ -1917,6 +1918,9 @@ option in 'catch') will be set to a list, as follows:
identifier (in decimal) and the code element will be the
exit code returned by the process (also in decimal).
+The environment for the executed command is set from $::env (unless
+this variable is unset, in which case the original environment is used).
+
exit
~~~~
+*exit* '?returnCode?'+
diff --git a/tests/exec2.test b/tests/exec2.test
new file mode 100644
index 0000000..e3d8a25
--- /dev/null
+++ b/tests/exec2.test
@@ -0,0 +1,39 @@
+# These tests are design especially for the vfork() implementation
+# of exec where sh -c must be used and thus we must take extra care
+# in quoting arguments to exec.
+
+source testing.tcl
+
+set d \"
+set s '
+set b \\
+
+test exec2-1.1 "Quoting - Result" {
+ exec echo ${d}double quoted${d} ${s}single quoted${s} ${b}backslash quoted${b}
+} "\"double\ quoted\"\ 'single quoted'\ \\backslash\ quoted\\"
+
+test exec2-1.2 "Quoting - Word Grouping" {
+ string trim [exec echo ${d}double quoted${d} ${s}single quoted${s} ${b}backslash quoted${b} | wc -w]
+} {6}
+
+test exec2-2.1 "Add to exec environment" {
+ 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
+} {}
+
+
+test exec2-2.3 "Remove all exec environment" {
+ array unset env *
+ 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
+} {}