From ec848b1965f29eda62f389bfcd653540fe58ca12 Mon Sep 17 00:00:00 2001 From: Johannes Sixt Date: Sat, 24 Nov 2007 22:53:20 +0100 Subject: [PATCH 1/4] Shuffle path lookup functions. We want to make them static later, and we need them in the proper order for this. There is otherwise no code change. Signed-off-by: Johannes Sixt --- compat/mingw.c | 158 ++++++++++++++++++++++++------------------------- 1 file changed, 79 insertions(+), 79 deletions(-) diff --git a/compat/mingw.c b/compat/mingw.c index f2d18a7410..850ee2ad5c 100644 --- a/compat/mingw.c +++ b/compat/mingw.c @@ -449,85 +449,6 @@ const char *parse_interpreter(const char *cmd) return p+1; } -static int try_shell_exec(const char *cmd, char *const *argv, char *const *env) -{ - const char **sh_argv; - int n; - const char *interpr = parse_interpreter(cmd); - if (!interpr) - return 0; - - /* - * expand - * git-foo args... - * into - * sh git-foo args... - */ - for (n = 0; argv[n];) n++; - sh_argv = xmalloc((n+2)*sizeof(char*)); - sh_argv[0] = interpr; - sh_argv[1] = quote_arg(cmd); - quote_argv(&sh_argv[2], (const char *const *)&argv[1]); - n = spawnvpe(_P_WAIT, interpr, sh_argv, (const char *const *)env); - if (n == -1) - return 1; /* indicate that we tried but failed */ - exit(n); -} - -void mingw_execve(const char *cmd, char *const *argv, char *const *env) -{ - /* check if git_command is a shell script */ - if (!try_shell_exec(cmd, argv, env)) { - const char **qargv; - int n; - for (n = 0; argv[n];) n++; - qargv = xmalloc((n+1)*sizeof(char*)); - quote_argv(qargv, (const char *const *)argv); - int ret = spawnve(_P_WAIT, cmd, qargv, - (const char *const *)env); - if (ret != -1) - exit(ret); - } -} - -static char *lookup_prog(const char *dir, const char *cmd, int tryexe) -{ - char path[MAX_PATH]; - snprintf(path, sizeof(path), "%s/%s.exe", dir, cmd); - - if (tryexe && access(path, 0) == 0) - return xstrdup(path); - path[strlen(path)-4] = '\0'; - if (access(path, 0) == 0) - return xstrdup(path); - return NULL; -} - -/* - * Determines the absolute path of cmd using the the split path in path. - * If cmd contains a slash or backslash, no lookup is performed. - */ -char *mingw_path_lookup(const char *cmd, char **path) -{ - char **p = path; - char *prog = NULL; - int len = strlen(cmd); - int tryexe = len < 4 || strcasecmp(cmd+len-4, ".exe"); - - if (strchr(cmd, '/') || strchr(cmd, '\\')) - prog = xstrdup(cmd); - - while (!prog && *p) { - prog = lookup_prog(*p++, cmd, tryexe); - } - if (!prog) { - prog = lookup_prog(".", cmd, tryexe); - if (!prog) - prog = xstrdup(cmd); - } - return prog; -} - /* * Splits the PATH into parts. */ @@ -578,6 +499,85 @@ void mingw_free_path_split(char **path) free(path); } +static char *lookup_prog(const char *dir, const char *cmd, int tryexe) +{ + char path[MAX_PATH]; + snprintf(path, sizeof(path), "%s/%s.exe", dir, cmd); + + if (tryexe && access(path, 0) == 0) + return xstrdup(path); + path[strlen(path)-4] = '\0'; + if (access(path, 0) == 0) + return xstrdup(path); + return NULL; +} + +/* + * Determines the absolute path of cmd using the the split path in path. + * If cmd contains a slash or backslash, no lookup is performed. + */ +char *mingw_path_lookup(const char *cmd, char **path) +{ + char **p = path; + char *prog = NULL; + int len = strlen(cmd); + int tryexe = len < 4 || strcasecmp(cmd+len-4, ".exe"); + + if (strchr(cmd, '/') || strchr(cmd, '\\')) + prog = xstrdup(cmd); + + while (!prog && *p) { + prog = lookup_prog(*p++, cmd, tryexe); + } + if (!prog) { + prog = lookup_prog(".", cmd, tryexe); + if (!prog) + prog = xstrdup(cmd); + } + return prog; +} + +static int try_shell_exec(const char *cmd, char *const *argv, char *const *env) +{ + const char **sh_argv; + int n; + const char *interpr = parse_interpreter(cmd); + if (!interpr) + return 0; + + /* + * expand + * git-foo args... + * into + * sh git-foo args... + */ + for (n = 0; argv[n];) n++; + sh_argv = xmalloc((n+2)*sizeof(char*)); + sh_argv[0] = interpr; + sh_argv[1] = quote_arg(cmd); + quote_argv(&sh_argv[2], (const char *const *)&argv[1]); + n = spawnvpe(_P_WAIT, interpr, sh_argv, (const char *const *)env); + if (n == -1) + return 1; /* indicate that we tried but failed */ + exit(n); +} + +void mingw_execve(const char *cmd, char *const *argv, char *const *env) +{ + /* check if git_command is a shell script */ + if (!try_shell_exec(cmd, argv, env)) { + const char **qargv; + int n; + for (n = 0; argv[n];) n++; + qargv = xmalloc((n+1)*sizeof(char*)); + quote_argv(qargv, (const char *const *)argv); + int ret = spawnve(_P_WAIT, cmd, qargv, + (const char *const *)env); + if (ret != -1) + exit(ret); + } +} + void mingw_execvp(const char *cmd, char *const *argv) { char **path = mingw_get_path_split(); From a020210cc8edd757b6e0f6d2ac1ba767f9d662ed Mon Sep 17 00:00:00 2001 From: Johannes Sixt Date: Sat, 24 Nov 2007 21:32:48 +0100 Subject: [PATCH 2/4] Move MinGW specific path lookup into compat/mingw.c. By doing so the only external user of the path handling and functions is removed, and these functions can be made static. Signed-off-by: Johannes Sixt --- compat/mingw.c | 41 +++++++++++++++++++++++++++++++++++------ git-compat-util.h | 11 ++--------- run-command.c | 23 +---------------------- 3 files changed, 38 insertions(+), 37 deletions(-) diff --git a/compat/mingw.c b/compat/mingw.c index 850ee2ad5c..6d2e518256 100644 --- a/compat/mingw.c +++ b/compat/mingw.c @@ -407,14 +407,14 @@ static const char *quote_arg(const char *arg) return q; } -void quote_argv(const char **dst, const char *const *src) +static void quote_argv(const char **dst, const char *const *src) { while (*src) *dst++ = quote_arg(*src++); *dst = NULL; } -const char *parse_interpreter(const char *cmd) +static const char *parse_interpreter(const char *cmd) { static char buf[100]; char *p, *opt; @@ -452,7 +452,7 @@ const char *parse_interpreter(const char *cmd) /* * Splits the PATH into parts. */ -char **mingw_get_path_split(void) +static char **mingw_get_path_split(void) { char *p, **path, *envpath = getenv("PATH"); int i, n = 0; @@ -488,7 +488,7 @@ char **mingw_get_path_split(void) return path; } -void mingw_free_path_split(char **path) +static void mingw_free_path_split(char **path) { if (!path) return; @@ -516,7 +516,7 @@ static char *lookup_prog(const char *dir, const char *cmd, int tryexe) * Determines the absolute path of cmd using the the split path in path. * If cmd contains a slash or backslash, no lookup is performed. */ -char *mingw_path_lookup(const char *cmd, char **path) +static char *mingw_path_lookup(const char *cmd, char **path) { char **p = path; char *prog = NULL; @@ -537,6 +537,35 @@ char *mingw_path_lookup(const char *cmd, char **path) return prog; } +pid_t mingw_spawnvpe(const char *cmd, const char **argv, char **env) +{ + pid_t pid; + char **path = mingw_get_path_split(); + const char **qargv; + char *prog = mingw_path_lookup(cmd, path); + const char *interpr = parse_interpreter(prog); + int argc; + + for (argc = 0; argv[argc];) argc++; + qargv = xmalloc((argc+2)*sizeof(char*)); + if (!interpr) { + quote_argv(qargv, argv); + pid = spawnve(_P_NOWAIT, prog, qargv, (const char **)env); + } else { + qargv[0] = interpr; + qargv[1] = quote_arg(prog); + quote_argv(&qargv[2], &argv[1]); + pid = spawnvpe(_P_NOWAIT, interpr, qargv, (const char **)env); + } + + free(qargv); /* TODO: quoted args should be freed, too */ + free(prog); + + mingw_free_path_split(path); + + return pid; +} + static int try_shell_exec(const char *cmd, char *const *argv, char *const *env) { const char **sh_argv; @@ -562,7 +591,7 @@ static int try_shell_exec(const char *cmd, char *const *argv, char *const *env) exit(n); } -void mingw_execve(const char *cmd, char *const *argv, char *const *env) +static void mingw_execve(const char *cmd, char *const *argv, char *const *env) { /* check if git_command is a shell script */ if (!try_shell_exec(cmd, argv, env)) { diff --git a/git-compat-util.h b/git-compat-util.h index 31d43152d2..5235d31f27 100644 --- a/git-compat-util.h +++ b/git-compat-util.h @@ -470,11 +470,10 @@ static inline int kill(pid_t pid, int sig) static inline unsigned int alarm(unsigned int seconds) { return 0; } -void mingw_execve(const char *cmd, char *const *argv, char * const *env); -#define execve mingw_execve +typedef int pid_t; +extern pid_t mingw_spawnvpe(const char *cmd, const char **argv, char **env); extern void mingw_execvp(const char *cmd, char *const *argv); #define execvp mingw_execvp -typedef int pid_t; static inline int waitpid(pid_t pid, unsigned *status, unsigned options) { if (options == 0) @@ -552,12 +551,6 @@ static inline int getppid(void) { return 1; } static inline void sync(void) {} extern int getpagesize(void); /* defined in MinGW's libgcc.a */ -extern void quote_argv(const char **dst, const char *const *src); -extern const char *parse_interpreter(const char *cmd); -extern char *mingw_path_lookup(const char *cmd, char **path); -extern char **mingw_get_path_split(void); -extern void mingw_free_path_split(char **path); - /* Use mingw_lstat() instead of lstat()/stat() and * mingw_fstat() instead of fstat() on Windows. * struct stat is redefined because it lacks the st_blocks member. diff --git a/run-command.c b/run-command.c index 05934f708f..cf1377de64 100644 --- a/run-command.c +++ b/run-command.c @@ -160,29 +160,8 @@ int start_command(struct child_process *cmd) cmd->argv[0] = git_cmd.buf; } - char **path = mingw_get_path_split(); - const char *argv0 = cmd->argv[0]; - const char **qargv; - char *prog = mingw_path_lookup(argv0, path); - const char *interpr = parse_interpreter(prog); - int argc; + cmd->pid = mingw_spawnvpe(cmd->argv[0], cmd->argv, env); - for (argc = 0; cmd->argv[argc];) argc++; - qargv = xmalloc((argc+2)*sizeof(char*)); - if (!interpr) { - quote_argv(qargv, cmd->argv); - cmd->pid = spawnve(_P_NOWAIT, prog, qargv, (const char **)env); - } else { - qargv[0] = interpr; - cmd->argv[0] = prog; - quote_argv(&qargv[1], cmd->argv); - cmd->pid = spawnvpe(_P_NOWAIT, interpr, qargv, (const char **)env); - } - - free(qargv); /* TODO: quoted args should be freed, too */ - free(prog); - - mingw_free_path_split(path); /* TODO: if (cmd->env) free env; */ if (cmd->git_cmd) From 72d58fde995f38454069a9be80a747f0f1a844cc Mon Sep 17 00:00:00 2001 From: Johannes Sixt Date: Sat, 24 Nov 2007 22:49:16 +0100 Subject: [PATCH 3/4] Implement a custom spawnve() on Windows. The problem with Windows's own implementation is that it tries to be clever when a console program is invoked from a GUI application: In this case it sometimes automatically allocates a new console windows. As a consequence, the IO channels of the spawned program are directed to the console, but the invoking application listens on channels that are now directed to nowhere. In this implementation we use the lowlevel facilities of CreateProcess(), which offers a flag to tell the system not to open a console. As a side effect, only stdin, stdout, and stderr channels will be accessible from C programs that are spawned. Other channels (file handles, pipe handles, etc.) are still inherited by the spawned program, but it doesn't get enough information to access them. Johannes Schindelin integrated path quoting and unified the various *execv* and *spawnv* helpers. Signed-off-by: Johannes Sixt --- compat/mingw.c | 215 ++++++++++++++++++++++++++++++++++++------------- 1 file changed, 161 insertions(+), 54 deletions(-) diff --git a/compat/mingw.c b/compat/mingw.c index 6d2e518256..432e56ccd5 100644 --- a/compat/mingw.c +++ b/compat/mingw.c @@ -1,5 +1,6 @@ #include #include "../git-compat-util.h" +#include "../strbuf.h" unsigned int _CRT_fmode = _O_BINARY; @@ -351,7 +352,10 @@ void openlog(const char *ident, int option, int facility) { } -/* See http://msdn2.microsoft.com/en-us/library/17w5ykft(vs.71).aspx (Parsing C++ Command-Line Arguments */ +/* + * See http://msdn2.microsoft.com/en-us/library/17w5ykft(vs.71).aspx + * (Parsing C++ Command-Line Arguments) + */ static const char *quote_arg(const char *arg) { /* count chars to quote */ @@ -407,13 +411,6 @@ static const char *quote_arg(const char *arg) return q; } -static void quote_argv(const char **dst, const char *const *src) -{ - while (*src) - *dst++ = quote_arg(*src++); - *dst = NULL; -} - static const char *parse_interpreter(const char *cmd) { static char buf[100]; @@ -537,73 +534,183 @@ static char *mingw_path_lookup(const char *cmd, char **path) return prog; } +static int env_compare(const void *a, const void *b) +{ + char *const *ea = a; + char *const *eb = b; + return strcasecmp(*ea, *eb); +} + +static pid_t mingw_spawnve(const char *cmd, const char **argv, char **env, + int prepend_cmd) +{ + STARTUPINFO si; + PROCESS_INFORMATION pi; + struct strbuf envblk, args; + unsigned flags; + BOOL ret; + + /* Determine whether or not we are associated to a console */ + HANDLE cons = CreateFile("CONOUT$", GENERIC_WRITE, + FILE_SHARE_WRITE, NULL, OPEN_EXISTING, + FILE_ATTRIBUTE_NORMAL, NULL); + if (cons == INVALID_HANDLE_VALUE) { + /* There is no console associated with this process. + * Since the child is a console process, Windows + * would normally create a console window. But + * since we'll be redirecting std streams, we do + * not need the console. + */ + flags = CREATE_NO_WINDOW; + } else { + /* There is already a console. If we specified + * CREATE_NO_WINDOW here, too, Windows would + * disassociate the child from the console. + * Go figure! + */ + flags = 0; + CloseHandle(cons); + } + memset(&si, 0, sizeof(si)); + si.cb = sizeof(si); + si.dwFlags = STARTF_USESTDHANDLES; + si.hStdInput = (HANDLE) _get_osfhandle(0); + si.hStdOutput = (HANDLE) _get_osfhandle(1); + si.hStdError = (HANDLE) _get_osfhandle(2); + + /* concatenate argv, quoting args as we go */ + strbuf_init(&args, 0); + if (prepend_cmd) { + char *quoted = (char *)quote_arg(cmd); + strbuf_addstr(&args, quoted); + if (quoted != cmd) + free(quoted); + } + for (; *argv; argv++) { + char *quoted = (char *)quote_arg(*argv); + if (*args.buf) + strbuf_addch(&args, ' '); + strbuf_addstr(&args, quoted); + if (quoted != *argv) + free(quoted); + } + + if (env) { + int count = 0; + char **e, **sorted_env; + + for (e = env; *e; e++) + count++; + + /* environment must be sorted */ + sorted_env = xmalloc(sizeof(*sorted_env) * (count + 1)); + memcpy(sorted_env, env, sizeof(*sorted_env) * (count + 1)); + qsort(sorted_env, count, sizeof(*sorted_env), env_compare); + + strbuf_init(&envblk, 0); + for (e = sorted_env; *e; e++) { + strbuf_addstr(&envblk, *e); + strbuf_addch(&envblk, '\0'); + } + free(sorted_env); + } + + memset(&pi, 0, sizeof(pi)); + ret = CreateProcess(cmd, args.buf, NULL, NULL, TRUE, flags, + env ? envblk.buf : NULL, NULL, &si, &pi); + + if (env) + strbuf_release(&envblk); + strbuf_release(&args); + + if (!ret) { + errno = ENOENT; + return -1; + } + CloseHandle(pi.hThread); + return (pid_t)pi.hProcess; +} + pid_t mingw_spawnvpe(const char *cmd, const char **argv, char **env) { pid_t pid; char **path = mingw_get_path_split(); - const char **qargv; char *prog = mingw_path_lookup(cmd, path); - const char *interpr = parse_interpreter(prog); - int argc; - for (argc = 0; argv[argc];) argc++; - qargv = xmalloc((argc+2)*sizeof(char*)); - if (!interpr) { - quote_argv(qargv, argv); - pid = spawnve(_P_NOWAIT, prog, qargv, (const char **)env); - } else { - qargv[0] = interpr; - qargv[1] = quote_arg(prog); - quote_argv(&qargv[2], &argv[1]); - pid = spawnvpe(_P_NOWAIT, interpr, qargv, (const char **)env); + if (!prog) { + errno = ENOENT; + pid = -1; } + else { + const char *interpr = parse_interpreter(prog); - free(qargv); /* TODO: quoted args should be freed, too */ - free(prog); - + if (interpr) { + const char *argv0 = argv[0]; + char *iprog = mingw_path_lookup(interpr, path); + argv[0] = prog; + if (!iprog) { + errno = ENOENT; + pid = -1; + } + else { + pid = mingw_spawnve(iprog, argv, env, 1); + free(iprog); + } + argv[0] = argv0; + } + else + pid = mingw_spawnve(prog, argv, env, 0); + free(prog); + } mingw_free_path_split(path); - return pid; } -static int try_shell_exec(const char *cmd, char *const *argv, char *const *env) +static int try_shell_exec(const char *cmd, char *const *argv, char **env) { - const char **sh_argv; - int n; const char *interpr = parse_interpreter(cmd); + char **path; + char *prog; + int pid = 0; + if (!interpr) return 0; - - /* - * expand - * git-foo args... - * into - * sh git-foo args... - */ - for (n = 0; argv[n];) n++; - sh_argv = xmalloc((n+2)*sizeof(char*)); - sh_argv[0] = interpr; - sh_argv[1] = quote_arg(cmd); - quote_argv(&sh_argv[2], (const char *const *)&argv[1]); - n = spawnvpe(_P_WAIT, interpr, sh_argv, (const char *const *)env); - if (n == -1) - return 1; /* indicate that we tried but failed */ - exit(n); + path = mingw_get_path_split(); + prog = mingw_path_lookup(interpr, path); + if (prog) { + int argc = 0; + const char **argv2; + while (argv[argc]) argc++; + argv2 = xmalloc(sizeof(*argv) * (argc+1)); + argv2[0] = (char *)cmd; /* full path to the script file */ + memcpy(&argv2[1], &argv[1], sizeof(*argv) * argc); + pid = mingw_spawnve(prog, argv2, env, 1); + if (pid >= 0) { + int status; + if (waitpid(pid, &status, 0) < 0) + status = 255; + exit(status); + } + pid = 1; /* indicate that we tried but failed */ + free(prog); + free(argv2); + } + mingw_free_path_split(path); + return pid; } static void mingw_execve(const char *cmd, char *const *argv, char *const *env) { /* check if git_command is a shell script */ - if (!try_shell_exec(cmd, argv, env)) { - const char **qargv; - int n; - for (n = 0; argv[n];) n++; - qargv = xmalloc((n+1)*sizeof(char*)); - quote_argv(qargv, (const char *const *)argv); - int ret = spawnve(_P_WAIT, cmd, qargv, - (const char *const *)env); - if (ret != -1) - exit(ret); + if (!try_shell_exec(cmd, argv, (char **)env)) { + int pid, status; + + pid = mingw_spawnve(cmd, (const char **)argv, (char **)env, 0); + if (pid < 0) + return; + if (waitpid(pid, &status, 0) < 0) + status = 255; + exit(status); } } From 3920a6b76b8be1a405d5e30c69e3d0ba63ce5a5d Mon Sep 17 00:00:00 2001 From: Johannes Sixt Date: Fri, 23 Nov 2007 23:23:39 +0100 Subject: [PATCH 4/4] Look up interpreters only as .exe files. After a program was determined to be a script (which implies that it did not have a file extension), then the interpreter is looked up. This change makes sure that we will only find .exe files when we are looking for an interpreter. Otherwise, we could find a directory 'perl' that is somewhere earlier in the path than 'perl.exe'. Signed-off-by: Johannes Sixt --- compat/mingw.c | 26 +++++++++++++++----------- 1 file changed, 15 insertions(+), 11 deletions(-) diff --git a/compat/mingw.c b/compat/mingw.c index 432e56ccd5..a25063b7f7 100644 --- a/compat/mingw.c +++ b/compat/mingw.c @@ -496,15 +496,19 @@ static void mingw_free_path_split(char **path) free(path); } -static char *lookup_prog(const char *dir, const char *cmd, int tryexe) +/* + * exe_only means that we only want to detect .exe files, but not scripts + * (which do not have an extension) + */ +static char *lookup_prog(const char *dir, const char *cmd, int isexe, int exe_only) { char path[MAX_PATH]; snprintf(path, sizeof(path), "%s/%s.exe", dir, cmd); - if (tryexe && access(path, 0) == 0) + if (!isexe && access(path, F_OK) == 0) return xstrdup(path); path[strlen(path)-4] = '\0'; - if (access(path, 0) == 0) + if ((!exe_only || isexe) && access(path, F_OK) == 0) return xstrdup(path); return NULL; } @@ -513,21 +517,21 @@ static char *lookup_prog(const char *dir, const char *cmd, int tryexe) * Determines the absolute path of cmd using the the split path in path. * If cmd contains a slash or backslash, no lookup is performed. */ -static char *mingw_path_lookup(const char *cmd, char **path) +static char *mingw_path_lookup(const char *cmd, char **path, int exe_only) { char **p = path; char *prog = NULL; int len = strlen(cmd); - int tryexe = len < 4 || strcasecmp(cmd+len-4, ".exe"); + int isexe = len >= 4 && !strcasecmp(cmd+len-4, ".exe"); if (strchr(cmd, '/') || strchr(cmd, '\\')) prog = xstrdup(cmd); while (!prog && *p) { - prog = lookup_prog(*p++, cmd, tryexe); + prog = lookup_prog(*p++, cmd, isexe, exe_only); } if (!prog) { - prog = lookup_prog(".", cmd, tryexe); + prog = lookup_prog(".", cmd, isexe, exe_only); if (!prog) prog = xstrdup(cmd); } @@ -635,7 +639,7 @@ pid_t mingw_spawnvpe(const char *cmd, const char **argv, char **env) { pid_t pid; char **path = mingw_get_path_split(); - char *prog = mingw_path_lookup(cmd, path); + char *prog = mingw_path_lookup(cmd, path, 0); if (!prog) { errno = ENOENT; @@ -646,7 +650,7 @@ pid_t mingw_spawnvpe(const char *cmd, const char **argv, char **env) if (interpr) { const char *argv0 = argv[0]; - char *iprog = mingw_path_lookup(interpr, path); + char *iprog = mingw_path_lookup(interpr, path, 1); argv[0] = prog; if (!iprog) { errno = ENOENT; @@ -676,7 +680,7 @@ static int try_shell_exec(const char *cmd, char *const *argv, char **env) if (!interpr) return 0; path = mingw_get_path_split(); - prog = mingw_path_lookup(interpr, path); + prog = mingw_path_lookup(interpr, path, 1); if (prog) { int argc = 0; const char **argv2; @@ -717,7 +721,7 @@ static void mingw_execve(const char *cmd, char *const *argv, char *const *env) void mingw_execvp(const char *cmd, char *const *argv) { char **path = mingw_get_path_split(); - char *prog = mingw_path_lookup(cmd, path); + char *prog = mingw_path_lookup(cmd, path, 0); if (prog) { mingw_execve(prog, argv, environ);