mirror of
https://github.com/git/git.git
synced 2026-03-13 18:33:25 +01:00
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 <johannes.sixt@telecom.at>
This commit is contained in:
215
compat/mingw.c
215
compat/mingw.c
@@ -1,5 +1,6 @@
|
||||
#include <stdint.h>
|
||||
#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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user