You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
981 lines
26 KiB
981 lines
26 KiB
/* Utilities to execute a program in a subprocess (possibly linked by pipes |
|
with other subprocesses), and wait for it. Generic Win32 specialization. |
|
Copyright (C) 1996, 1997, 1998, 1999, 2000, 2001, 2003, 2004, 2005, 2006 |
|
Free Software Foundation, Inc. |
|
|
|
This file is part of the libiberty library. |
|
Libiberty is free software; you can redistribute it and/or |
|
modify it under the terms of the GNU Library General Public |
|
License as published by the Free Software Foundation; either |
|
version 2 of the License, or (at your option) any later version. |
|
|
|
Libiberty is distributed in the hope that it will be useful, |
|
but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
|
Library General Public License for more details. |
|
|
|
You should have received a copy of the GNU Library General Public |
|
License along with libiberty; see the file COPYING.LIB. If not, |
|
write to the Free Software Foundation, Inc., 51 Franklin Street - Fifth Floor, |
|
Boston, MA 02110-1301, USA. */ |
|
|
|
#include "pex-common.h" |
|
|
|
#include <windows.h> |
|
|
|
#ifdef HAVE_STDLIB_H |
|
#include <stdlib.h> |
|
#endif |
|
#ifdef HAVE_STRING_H |
|
#include <string.h> |
|
#endif |
|
#ifdef HAVE_UNISTD_H |
|
#include <unistd.h> |
|
#endif |
|
#ifdef HAVE_SYS_WAIT_H |
|
#include <sys/wait.h> |
|
#endif |
|
|
|
#include <assert.h> |
|
#include <process.h> |
|
#include <io.h> |
|
#include <fcntl.h> |
|
#include <signal.h> |
|
#include <sys/stat.h> |
|
#include <errno.h> |
|
#include <ctype.h> |
|
|
|
/* mingw32 headers may not define the following. */ |
|
|
|
#ifndef _P_WAIT |
|
# define _P_WAIT 0 |
|
# define _P_NOWAIT 1 |
|
# define _P_OVERLAY 2 |
|
# define _P_NOWAITO 3 |
|
# define _P_DETACH 4 |
|
|
|
# define WAIT_CHILD 0 |
|
# define WAIT_GRANDCHILD 1 |
|
#endif |
|
|
|
#define MINGW_NAME "Minimalist GNU for Windows" |
|
#define MINGW_NAME_LEN (sizeof(MINGW_NAME) - 1) |
|
|
|
extern char *stpcpy (char *dst, const char *src); |
|
|
|
/* Ensure that the executable pathname uses Win32 backslashes. This |
|
is not necessary on NT, but on W9x, forward slashes causes |
|
failure of spawn* and exec* functions (and probably any function |
|
that calls CreateProcess) *iff* the executable pathname (argv[0]) |
|
is a quoted string. And quoting is necessary in case a pathname |
|
contains embedded white space. You can't win. */ |
|
static void |
|
backslashify (char *s) |
|
{ |
|
while ((s = strchr (s, '/')) != NULL) |
|
*s = '\\'; |
|
return; |
|
} |
|
|
|
static int pex_win32_open_read (struct pex_obj *, const char *, int); |
|
static int pex_win32_open_write (struct pex_obj *, const char *, int, int); |
|
static pid_t pex_win32_exec_child (struct pex_obj *, int, const char *, |
|
char * const *, char * const *, |
|
int, int, int, int, |
|
const char **, int *); |
|
static int pex_win32_close (struct pex_obj *, int); |
|
static pid_t pex_win32_wait (struct pex_obj *, pid_t, int *, |
|
struct pex_time *, int, const char **, int *); |
|
static int pex_win32_pipe (struct pex_obj *, int *, int); |
|
static FILE *pex_win32_fdopenr (struct pex_obj *, int, int); |
|
static FILE *pex_win32_fdopenw (struct pex_obj *, int, int); |
|
|
|
/* The list of functions we pass to the common routines. */ |
|
|
|
const struct pex_funcs funcs = |
|
{ |
|
pex_win32_open_read, |
|
pex_win32_open_write, |
|
pex_win32_exec_child, |
|
pex_win32_close, |
|
pex_win32_wait, |
|
pex_win32_pipe, |
|
pex_win32_fdopenr, |
|
pex_win32_fdopenw, |
|
NULL /* cleanup */ |
|
}; |
|
|
|
/* Return a newly initialized pex_obj structure. */ |
|
|
|
struct pex_obj * |
|
pex_init (int flags, const char *pname, const char *tempbase) |
|
{ |
|
return pex_init_common (flags, pname, tempbase, &funcs); |
|
} |
|
|
|
/* Open a file for reading. */ |
|
|
|
static int |
|
pex_win32_open_read (struct pex_obj *obj ATTRIBUTE_UNUSED, const char *name, |
|
int binary) |
|
{ |
|
return _open (name, _O_RDONLY | (binary ? _O_BINARY : _O_TEXT)); |
|
} |
|
|
|
/* Open a file for writing. */ |
|
|
|
static int |
|
pex_win32_open_write (struct pex_obj *obj ATTRIBUTE_UNUSED, const char *name, |
|
int binary, int append) |
|
{ |
|
/* Note that we can't use O_EXCL here because gcc may have already |
|
created the temporary file via make_temp_file. */ |
|
if (append) |
|
return -1; |
|
return _open (name, |
|
(_O_WRONLY | _O_CREAT | _O_TRUNC |
|
| (binary ? _O_BINARY : _O_TEXT)), |
|
_S_IREAD | _S_IWRITE); |
|
} |
|
|
|
/* Close a file. */ |
|
|
|
static int |
|
pex_win32_close (struct pex_obj *obj ATTRIBUTE_UNUSED, int fd) |
|
{ |
|
return _close (fd); |
|
} |
|
|
|
#ifdef USE_MINGW_MSYS |
|
static const char *mingw_keys[] = {"SOFTWARE", "Microsoft", "Windows", "CurrentVersion", "Uninstall", NULL}; |
|
|
|
/* Tack the executable on the end of a (possibly slash terminated) buffer |
|
and convert everything to \. */ |
|
static const char * |
|
tack_on_executable (char *buf, const char *executable) |
|
{ |
|
char *p = strchr (buf, '\0'); |
|
if (p > buf && (p[-1] == '\\' || p[-1] == '/')) |
|
p[-1] = '\0'; |
|
backslashify (strcat (buf, executable)); |
|
return buf; |
|
} |
|
|
|
/* Walk down a registry hierarchy until the end. Return the key. */ |
|
static HKEY |
|
openkey (HKEY hStart, const char *keys[]) |
|
{ |
|
HKEY hKey, hTmp; |
|
for (hKey = hStart; *keys; keys++) |
|
{ |
|
LONG res; |
|
hTmp = hKey; |
|
res = RegOpenKey (hTmp, *keys, &hKey); |
|
|
|
if (hTmp != HKEY_LOCAL_MACHINE) |
|
RegCloseKey (hTmp); |
|
|
|
if (res != ERROR_SUCCESS) |
|
return NULL; |
|
} |
|
return hKey; |
|
} |
|
|
|
/* Return the "mingw root" as derived from the mingw uninstall information. */ |
|
static const char * |
|
mingw_rootify (const char *executable) |
|
{ |
|
HKEY hKey, hTmp; |
|
DWORD maxlen; |
|
char *namebuf, *foundbuf; |
|
DWORD i; |
|
LONG res; |
|
|
|
/* Open the uninstall "directory". */ |
|
hKey = openkey (HKEY_LOCAL_MACHINE, mingw_keys); |
|
|
|
/* Not found. */ |
|
if (!hKey) |
|
return executable; |
|
|
|
/* Need to enumerate all of the keys here looking for one the most recent |
|
one for MinGW. */ |
|
if (RegQueryInfoKey (hKey, NULL, NULL, NULL, NULL, &maxlen, NULL, NULL, |
|
NULL, NULL, NULL, NULL) != ERROR_SUCCESS) |
|
{ |
|
RegCloseKey (hKey); |
|
return executable; |
|
} |
|
namebuf = XNEWVEC (char, ++maxlen); |
|
foundbuf = XNEWVEC (char, maxlen); |
|
foundbuf[0] = '\0'; |
|
if (!namebuf || !foundbuf) |
|
{ |
|
RegCloseKey (hKey); |
|
free (namebuf); |
|
free (foundbuf); |
|
return executable; |
|
} |
|
|
|
/* Look through all of the keys for one that begins with Minimal GNU... |
|
Try to get the latest version by doing a string compare although that |
|
string never really works with version number sorting. */ |
|
for (i = 0; RegEnumKey (hKey, i, namebuf, maxlen) == ERROR_SUCCESS; i++) |
|
{ |
|
int match = strcasecmp (namebuf, MINGW_NAME); |
|
if (match < 0) |
|
continue; |
|
if (match > 0 && strncasecmp (namebuf, MINGW_NAME, MINGW_NAME_LEN) > 0) |
|
continue; |
|
if (strcasecmp (namebuf, foundbuf) > 0) |
|
strcpy (foundbuf, namebuf); |
|
} |
|
free (namebuf); |
|
|
|
/* If foundbuf is empty, we didn't find anything. Punt. */ |
|
if (!foundbuf[0]) |
|
{ |
|
free (foundbuf); |
|
RegCloseKey (hKey); |
|
return executable; |
|
} |
|
|
|
/* Open the key that we wanted */ |
|
res = RegOpenKey (hKey, foundbuf, &hTmp); |
|
RegCloseKey (hKey); |
|
free (foundbuf); |
|
|
|
/* Don't know why this would fail, but you gotta check */ |
|
if (res != ERROR_SUCCESS) |
|
return executable; |
|
|
|
maxlen = 0; |
|
/* Get the length of the value pointed to by InstallLocation */ |
|
if (RegQueryValueEx (hTmp, "InstallLocation", 0, NULL, NULL, |
|
&maxlen) != ERROR_SUCCESS || maxlen == 0) |
|
{ |
|
RegCloseKey (hTmp); |
|
return executable; |
|
} |
|
|
|
/* Allocate space for the install location */ |
|
foundbuf = XNEWVEC (char, maxlen + strlen (executable)); |
|
if (!foundbuf) |
|
{ |
|
free (foundbuf); |
|
RegCloseKey (hTmp); |
|
} |
|
|
|
/* Read the install location into the buffer */ |
|
res = RegQueryValueEx (hTmp, "InstallLocation", 0, NULL, (LPBYTE) foundbuf, |
|
&maxlen); |
|
RegCloseKey (hTmp); |
|
if (res != ERROR_SUCCESS) |
|
{ |
|
free (foundbuf); |
|
return executable; |
|
} |
|
|
|
/* Concatenate the install location and the executable, turn all slashes |
|
to backslashes, and return that. */ |
|
return tack_on_executable (foundbuf, executable); |
|
} |
|
|
|
/* Read the install location of msys from it's installation file and |
|
rootify the executable based on that. */ |
|
static const char * |
|
msys_rootify (const char *executable) |
|
{ |
|
size_t bufsize = 64; |
|
size_t execlen = strlen (executable) + 1; |
|
char *buf; |
|
DWORD res = 0; |
|
for (;;) |
|
{ |
|
buf = XNEWVEC (char, bufsize + execlen); |
|
if (!buf) |
|
break; |
|
res = GetPrivateProfileString ("InstallSettings", "InstallPath", NULL, |
|
buf, bufsize, "msys.ini"); |
|
if (!res) |
|
break; |
|
if (strlen (buf) < bufsize) |
|
break; |
|
res = 0; |
|
free (buf); |
|
bufsize *= 2; |
|
if (bufsize > 65536) |
|
{ |
|
buf = NULL; |
|
break; |
|
} |
|
} |
|
|
|
if (res) |
|
return tack_on_executable (buf, executable); |
|
|
|
/* failed */ |
|
free (buf); |
|
return executable; |
|
} |
|
#endif |
|
|
|
/* Return the number of arguments in an argv array, not including the null |
|
terminating argument. */ |
|
|
|
static int |
|
argv_to_argc (char *const *argv) |
|
{ |
|
char *const *i = argv; |
|
while (*i) |
|
i++; |
|
return i - argv; |
|
} |
|
|
|
/* Return a Windows command-line from ARGV. It is the caller's |
|
responsibility to free the string returned. */ |
|
|
|
static char * |
|
argv_to_cmdline (char *const *argv) |
|
{ |
|
char *cmdline; |
|
char *p; |
|
size_t cmdline_len; |
|
int i, j, k; |
|
int needs_quotes; |
|
|
|
cmdline_len = 0; |
|
for (i = 0; argv[i]; i++) |
|
{ |
|
/* We only quote arguments that contain spaces, \t or " characters to |
|
prevent wasting 2 chars per argument of the CreateProcess 32k char |
|
limit. We need only escape embedded double-quotes and immediately |
|
preceeding backslash characters. A sequence of backslach characters |
|
that is not follwed by a double quote character will not be |
|
escaped. */ |
|
needs_quotes = 0; |
|
for (j = 0; argv[i][j]; j++) |
|
{ |
|
if (argv[i][j] == ' ' || argv[i][j] == '\t' || argv[i][j] == '"') |
|
{ |
|
needs_quotes = 1; |
|
} |
|
|
|
if (argv[i][j] == '"') |
|
{ |
|
/* Escape preceeding backslashes. */ |
|
for (k = j - 1; k >= 0 && argv[i][k] == '\\'; k--) |
|
cmdline_len++; |
|
/* Escape the qote character. */ |
|
cmdline_len++; |
|
} |
|
} |
|
/* Trailing backslashes also need to be escaped because they will be |
|
followed by the terminating quote. */ |
|
if (needs_quotes) |
|
{ |
|
for (k = j - 1; k >= 0 && argv[i][k] == '\\'; k--) |
|
cmdline_len++; |
|
} |
|
cmdline_len += j; |
|
/* for leading and trailing quotes and space */ |
|
cmdline_len += needs_quotes * 2 + 1; |
|
} |
|
cmdline = XNEWVEC (char, cmdline_len); |
|
p = cmdline; |
|
for (i = 0; argv[i]; i++) |
|
{ |
|
needs_quotes = 0; |
|
for (j = 0; argv[i][j]; j++) |
|
{ |
|
if (argv[i][j] == ' ' || argv[i][j] == '\t' || argv[i][j] == '"') |
|
{ |
|
needs_quotes = 1; |
|
break; |
|
} |
|
} |
|
|
|
if (needs_quotes) |
|
{ |
|
*p++ = '"'; |
|
} |
|
for (j = 0; argv[i][j]; j++) |
|
{ |
|
if (argv[i][j] == '"') |
|
{ |
|
for (k = j - 1; k >= 0 && argv[i][k] == '\\'; k--) |
|
*p++ = '\\'; |
|
*p++ = '\\'; |
|
} |
|
*p++ = argv[i][j]; |
|
} |
|
if (needs_quotes) |
|
{ |
|
for (k = j - 1; k >= 0 && argv[i][k] == '\\'; k--) |
|
*p++ = '\\'; |
|
*p++ = '"'; |
|
} |
|
*p++ = ' '; |
|
} |
|
p[-1] = '\0'; |
|
return cmdline; |
|
} |
|
|
|
/* We'll try the passed filename with all the known standard |
|
extensions, and then without extension. We try no extension |
|
last so that we don't try to run some random extension-less |
|
file that might be hanging around. We try both extension |
|
and no extension so that we don't need any fancy logic |
|
to determine if a file has extension. */ |
|
static const char *const |
|
std_suffixes[] = { |
|
".com", |
|
".exe", |
|
".bat", |
|
".cmd", |
|
"", |
|
0 |
|
}; |
|
|
|
/* Returns the full path to PROGRAM. If SEARCH is true, look for |
|
PROGRAM in each directory in PATH. */ |
|
|
|
static char * |
|
find_executable (const char *program, BOOL search) |
|
{ |
|
char *full_executable; |
|
char *e; |
|
size_t fe_len; |
|
const char *path = 0; |
|
const char *const *ext; |
|
const char *p, *q; |
|
size_t proglen = strlen (program); |
|
int has_slash = (strchr (program, '/') || strchr (program, '\\')); |
|
HANDLE h; |
|
|
|
if (has_slash) |
|
search = FALSE; |
|
|
|
if (search) |
|
path = getenv ("PATH"); |
|
if (!path) |
|
path = ""; |
|
|
|
fe_len = 0; |
|
for (p = path; *p; p = q) |
|
{ |
|
q = p; |
|
while (*q != ';' && *q != '\0') |
|
q++; |
|
if ((size_t)(q - p) > fe_len) |
|
fe_len = q - p; |
|
if (*q == ';') |
|
q++; |
|
} |
|
fe_len = fe_len + 1 + proglen + 5 /* space for extension */; |
|
full_executable = XNEWVEC (char, fe_len); |
|
|
|
p = path; |
|
do |
|
{ |
|
q = p; |
|
while (*q != ';' && *q != '\0') |
|
q++; |
|
|
|
e = full_executable; |
|
memcpy (e, p, q - p); |
|
e += (q - p); |
|
if (q - p) |
|
*e++ = '\\'; |
|
strcpy (e, program); |
|
|
|
if (*q == ';') |
|
q++; |
|
|
|
for (e = full_executable; *e; e++) |
|
if (*e == '/') |
|
*e = '\\'; |
|
|
|
/* At this point, e points to the terminating NUL character for |
|
full_executable. */ |
|
for (ext = std_suffixes; *ext; ext++) |
|
{ |
|
/* Remove any current extension. */ |
|
*e = '\0'; |
|
/* Add the new one. */ |
|
strcat (full_executable, *ext); |
|
|
|
/* Attempt to open this file. */ |
|
h = CreateFile (full_executable, GENERIC_READ, |
|
FILE_SHARE_READ | FILE_SHARE_WRITE, |
|
0, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0); |
|
if (h != INVALID_HANDLE_VALUE) |
|
goto found; |
|
} |
|
p = q; |
|
} |
|
while (*p); |
|
free (full_executable); |
|
return 0; |
|
|
|
found: |
|
CloseHandle (h); |
|
return full_executable; |
|
} |
|
|
|
/* Low-level process creation function and helper. */ |
|
|
|
static int |
|
env_compare (const void *a_ptr, const void *b_ptr) |
|
{ |
|
const char *a; |
|
const char *b; |
|
unsigned char c1; |
|
unsigned char c2; |
|
|
|
a = *(const char **) a_ptr; |
|
b = *(const char **) b_ptr; |
|
|
|
/* a and b will be of the form: VAR=VALUE |
|
We compare only the variable name part here using a case-insensitive |
|
comparison algorithm. It might appear that in fact strcasecmp () can |
|
take the place of this whole function, and indeed it could, save for |
|
the fact that it would fail in cases such as comparing A1=foo and |
|
A=bar (because 1 is less than = in the ASCII character set). |
|
(Environment variables containing no numbers would work in such a |
|
scenario.) */ |
|
|
|
do |
|
{ |
|
c1 = (unsigned char) tolower (*a++); |
|
c2 = (unsigned char) tolower (*b++); |
|
|
|
if (c1 == '=') |
|
c1 = '\0'; |
|
|
|
if (c2 == '=') |
|
c2 = '\0'; |
|
} |
|
while (c1 == c2 && c1 != '\0'); |
|
|
|
return c1 - c2; |
|
} |
|
|
|
/* Execute a Windows executable as a child process. This will fail if the |
|
* target is not actually an executable, such as if it is a shell script. */ |
|
|
|
static pid_t |
|
win32_spawn (const char *executable, |
|
BOOL search, |
|
char *const *argv, |
|
char *const *env, /* array of strings of the form: VAR=VALUE */ |
|
DWORD dwCreationFlags, |
|
LPSTARTUPINFO si, |
|
LPPROCESS_INFORMATION pi) |
|
{ |
|
char *full_executable; |
|
char *cmdline; |
|
char **env_copy; |
|
char *env_block = NULL; |
|
|
|
full_executable = NULL; |
|
cmdline = NULL; |
|
|
|
if (env) |
|
{ |
|
int env_size; |
|
|
|
/* Count the number of environment bindings supplied. */ |
|
for (env_size = 0; env[env_size]; env_size++) |
|
continue; |
|
|
|
/* Assemble an environment block, if required. This consists of |
|
VAR=VALUE strings juxtaposed (with one null character between each |
|
pair) and an additional null at the end. */ |
|
if (env_size > 0) |
|
{ |
|
int var; |
|
int total_size = 1; /* 1 is for the final null. */ |
|
char *bufptr; |
|
|
|
/* Windows needs the members of the block to be sorted by variable |
|
name. */ |
|
env_copy = (char **) alloca (sizeof (char *) * env_size); |
|
memcpy (env_copy, env, sizeof (char *) * env_size); |
|
qsort (env_copy, env_size, sizeof (char *), env_compare); |
|
|
|
for (var = 0; var < env_size; var++) |
|
total_size += strlen (env[var]) + 1; |
|
|
|
env_block = XNEWVEC (char, total_size); |
|
bufptr = env_block; |
|
for (var = 0; var < env_size; var++) |
|
bufptr = stpcpy (bufptr, env_copy[var]) + 1; |
|
|
|
*bufptr = '\0'; |
|
} |
|
} |
|
|
|
full_executable = find_executable (executable, search); |
|
if (!full_executable) |
|
goto error; |
|
cmdline = argv_to_cmdline (argv); |
|
if (!cmdline) |
|
goto error; |
|
|
|
/* Create the child process. */ |
|
if (!CreateProcess (full_executable, cmdline, |
|
/*lpProcessAttributes=*/NULL, |
|
/*lpThreadAttributes=*/NULL, |
|
/*bInheritHandles=*/TRUE, |
|
dwCreationFlags, |
|
(LPVOID) env_block, |
|
/*lpCurrentDirectory=*/NULL, |
|
si, |
|
pi)) |
|
{ |
|
free (env_block); |
|
|
|
free (full_executable); |
|
|
|
return (pid_t) -1; |
|
} |
|
|
|
/* Clean up. */ |
|
CloseHandle (pi->hThread); |
|
free (full_executable); |
|
free (env_block); |
|
|
|
return (pid_t) pi->hProcess; |
|
|
|
error: |
|
free (env_block); |
|
free (cmdline); |
|
free (full_executable); |
|
|
|
return (pid_t) -1; |
|
} |
|
|
|
/* Spawn a script. This simulates the Unix script execution mechanism. |
|
This function is called as a fallback if win32_spawn fails. */ |
|
|
|
static pid_t |
|
spawn_script (const char *executable, char *const *argv, |
|
char* const *env, |
|
DWORD dwCreationFlags, |
|
LPSTARTUPINFO si, |
|
LPPROCESS_INFORMATION pi) |
|
{ |
|
pid_t pid = (pid_t) -1; |
|
int save_errno = errno; |
|
int fd = _open (executable, _O_RDONLY); |
|
|
|
/* Try to open script, check header format, extract interpreter path, |
|
and spawn script using that interpretter. */ |
|
if (fd >= 0) |
|
{ |
|
char buf[MAX_PATH + 5]; |
|
int len = _read (fd, buf, sizeof (buf) - 1); |
|
_close (fd); |
|
if (len > 3) |
|
{ |
|
char *eol; |
|
buf[len] = '\0'; |
|
eol = strchr (buf, '\n'); |
|
if (eol && strncmp (buf, "#!", 2) == 0) |
|
{ |
|
|
|
/* Header format is OK. */ |
|
char *executable1; |
|
int new_argc; |
|
const char **avhere; |
|
|
|
/* Extract interpreter path. */ |
|
do |
|
*eol = '\0'; |
|
while (*--eol == '\r' || *eol == ' ' || *eol == '\t'); |
|
for (executable1 = buf + 2; *executable1 == ' ' || *executable1 == '\t'; executable1++) |
|
continue; |
|
backslashify (executable1); |
|
|
|
/* Duplicate argv, prepending the interpreter path. */ |
|
new_argc = argv_to_argc (argv) + 1; |
|
avhere = XNEWVEC (const char *, new_argc + 1); |
|
*avhere = executable1; |
|
memcpy (avhere + 1, argv, new_argc * sizeof(*argv)); |
|
argv = (char *const *)avhere; |
|
|
|
/* Spawn the child. */ |
|
#ifndef USE_MINGW_MSYS |
|
executable = strrchr (executable1, '\\') + 1; |
|
if (!executable) |
|
executable = executable1; |
|
pid = win32_spawn (executable, TRUE, argv, env, |
|
dwCreationFlags, si, pi); |
|
#else |
|
if (strchr (executable1, '\\') == NULL) |
|
pid = win32_spawn (executable1, TRUE, argv, env, |
|
dwCreationFlags, si, pi); |
|
else if (executable1[0] != '\\') |
|
pid = win32_spawn (executable1, FALSE, argv, env, |
|
dwCreationFlags, si, pi); |
|
else |
|
{ |
|
const char *newex = mingw_rootify (executable1); |
|
*avhere = newex; |
|
pid = win32_spawn (newex, FALSE, argv, env, |
|
dwCreationFlags, si, pi); |
|
if (executable1 != newex) |
|
free ((char *) newex); |
|
if (pid == (pid_t) -1) |
|
{ |
|
newex = msys_rootify (executable1); |
|
if (newex != executable1) |
|
{ |
|
*avhere = newex; |
|
pid = win32_spawn (newex, FALSE, argv, env, |
|
dwCreationFlags, si, pi); |
|
free ((char *) newex); |
|
} |
|
} |
|
} |
|
#endif |
|
free (avhere); |
|
} |
|
} |
|
} |
|
if (pid == (pid_t) -1) |
|
errno = save_errno; |
|
return pid; |
|
} |
|
|
|
/* Execute a child. */ |
|
|
|
static pid_t |
|
pex_win32_exec_child (struct pex_obj *obj ATTRIBUTE_UNUSED, int flags, |
|
const char *executable, char * const * argv, |
|
char* const* env, |
|
int in, int out, int errdes, |
|
int toclose ATTRIBUTE_UNUSED, |
|
const char **errmsg, |
|
int *err) |
|
{ |
|
pid_t pid; |
|
HANDLE stdin_handle; |
|
HANDLE stdout_handle; |
|
HANDLE stderr_handle; |
|
DWORD dwCreationFlags; |
|
OSVERSIONINFO version_info; |
|
STARTUPINFO si; |
|
PROCESS_INFORMATION pi; |
|
int orig_out, orig_in, orig_err; |
|
BOOL separate_stderr = !(flags & PEX_STDERR_TO_STDOUT); |
|
|
|
/* Ensure we have inheritable descriptors to pass to the child. */ |
|
orig_in = in; |
|
in = _dup (orig_in); |
|
|
|
orig_out = out; |
|
out = _dup (orig_out); |
|
|
|
if (separate_stderr) |
|
{ |
|
orig_err = errdes; |
|
errdes = _dup (orig_err); |
|
} |
|
|
|
stdin_handle = INVALID_HANDLE_VALUE; |
|
stdout_handle = INVALID_HANDLE_VALUE; |
|
stderr_handle = INVALID_HANDLE_VALUE; |
|
|
|
stdin_handle = (HANDLE) _get_osfhandle (in); |
|
stdout_handle = (HANDLE) _get_osfhandle (out); |
|
if (separate_stderr) |
|
stderr_handle = (HANDLE) _get_osfhandle (errdes); |
|
else |
|
stderr_handle = stdout_handle; |
|
|
|
/* Determine the version of Windows we are running on. */ |
|
version_info.dwOSVersionInfoSize = sizeof (version_info); |
|
GetVersionEx (&version_info); |
|
if (version_info.dwPlatformId == VER_PLATFORM_WIN32_WINDOWS) |
|
/* On Windows 95/98/ME the CREATE_NO_WINDOW flag is not |
|
supported, so we cannot avoid creating a console window. */ |
|
dwCreationFlags = 0; |
|
else |
|
{ |
|
HANDLE conout_handle; |
|
|
|
/* Determine whether or not we have an associated console. */ |
|
conout_handle = CreateFile("CONOUT$", |
|
GENERIC_WRITE, |
|
FILE_SHARE_WRITE, |
|
/*lpSecurityAttributes=*/NULL, |
|
OPEN_EXISTING, |
|
FILE_ATTRIBUTE_NORMAL, |
|
/*hTemplateFile=*/NULL); |
|
if (conout_handle == INVALID_HANDLE_VALUE) |
|
/* There is no console associated with this process. Since |
|
the child is a console process, the OS would normally |
|
create a new console Window for the child. Since we'll be |
|
redirecting the child's standard streams, we do not need |
|
the console window. */ |
|
dwCreationFlags = CREATE_NO_WINDOW; |
|
else |
|
{ |
|
/* There is a console associated with the process, so the OS |
|
will not create a new console. And, if we use |
|
CREATE_NO_WINDOW in this situation, the child will have |
|
no associated console. Therefore, if the child's |
|
standard streams are connected to the console, the output |
|
will be discarded. */ |
|
CloseHandle(conout_handle); |
|
dwCreationFlags = 0; |
|
} |
|
} |
|
|
|
/* Since the child will be a console process, it will, by default, |
|
connect standard input/output to its console. However, we want |
|
the child to use the handles specifically designated above. In |
|
addition, if there is no console (such as when we are running in |
|
a Cygwin X window), then we must redirect the child's |
|
input/output, as there is no console for the child to use. */ |
|
memset (&si, 0, sizeof (si)); |
|
si.cb = sizeof (si); |
|
si.dwFlags = STARTF_USESTDHANDLES; |
|
si.hStdInput = stdin_handle; |
|
si.hStdOutput = stdout_handle; |
|
si.hStdError = stderr_handle; |
|
|
|
/* Create the child process. */ |
|
pid = win32_spawn (executable, (flags & PEX_SEARCH) != 0, |
|
argv, env, dwCreationFlags, &si, &pi); |
|
if (pid == (pid_t) -1) |
|
pid = spawn_script (executable, argv, env, dwCreationFlags, |
|
&si, &pi); |
|
if (pid == (pid_t) -1) |
|
{ |
|
*err = ENOENT; |
|
*errmsg = "CreateProcess"; |
|
} |
|
|
|
/* If the child was created successfully, close the original file |
|
descriptors. If the process creation fails, these are closed by |
|
pex_run_in_environment instead. We must not close them twice as |
|
that seems to cause a Windows exception. */ |
|
|
|
if (pid != (pid_t) -1) |
|
{ |
|
if (orig_in != STDIN_FILENO) |
|
_close (orig_in); |
|
if (orig_out != STDOUT_FILENO) |
|
_close (orig_out); |
|
if (separate_stderr |
|
&& orig_err != STDERR_FILENO) |
|
_close (orig_err); |
|
} |
|
|
|
/* Close the standard input, standard output and standard error handles |
|
in the parent. */ |
|
|
|
_close (in); |
|
_close (out); |
|
if (separate_stderr) |
|
_close (errdes); |
|
|
|
return pid; |
|
} |
|
|
|
/* Wait for a child process to complete. MS CRTDLL doesn't return |
|
enough information in status to decide if the child exited due to a |
|
signal or not, rather it simply returns an integer with the exit |
|
code of the child; eg., if the child exited with an abort() call |
|
and didn't have a handler for SIGABRT, it simply returns with |
|
status == 3. We fix the status code to conform to the usual WIF* |
|
macros. Note that WIFSIGNALED will never be true under CRTDLL. */ |
|
|
|
static pid_t |
|
pex_win32_wait (struct pex_obj *obj ATTRIBUTE_UNUSED, pid_t pid, |
|
int *status, struct pex_time *time, int done ATTRIBUTE_UNUSED, |
|
const char **errmsg, int *err) |
|
{ |
|
DWORD termstat; |
|
HANDLE h; |
|
|
|
if (time != NULL) |
|
memset (time, 0, sizeof *time); |
|
|
|
h = (HANDLE) pid; |
|
|
|
/* FIXME: If done is non-zero, we should probably try to kill the |
|
process. */ |
|
if (WaitForSingleObject (h, INFINITE) != WAIT_OBJECT_0) |
|
{ |
|
CloseHandle (h); |
|
*err = ECHILD; |
|
*errmsg = "WaitForSingleObject"; |
|
return -1; |
|
} |
|
|
|
GetExitCodeProcess (h, &termstat); |
|
CloseHandle (h); |
|
|
|
/* A value of 3 indicates that the child caught a signal, but not |
|
which one. Since only SIGABRT, SIGFPE and SIGINT do anything, we |
|
report SIGABRT. */ |
|
if (termstat == 3) |
|
*status = SIGABRT; |
|
else |
|
*status = (termstat & 0xff) << 8; |
|
|
|
return 0; |
|
} |
|
|
|
/* Create a pipe. */ |
|
|
|
static int |
|
pex_win32_pipe (struct pex_obj *obj ATTRIBUTE_UNUSED, int *p, |
|
int binary) |
|
{ |
|
return _pipe (p, 256, (binary ? _O_BINARY : _O_TEXT) | _O_NOINHERIT); |
|
} |
|
|
|
/* Get a FILE pointer to read from a file descriptor. */ |
|
|
|
static FILE * |
|
pex_win32_fdopenr (struct pex_obj *obj ATTRIBUTE_UNUSED, int fd, |
|
int binary) |
|
{ |
|
HANDLE h = (HANDLE) _get_osfhandle (fd); |
|
if (h == INVALID_HANDLE_VALUE) |
|
return NULL; |
|
if (! SetHandleInformation (h, HANDLE_FLAG_INHERIT, 0)) |
|
return NULL; |
|
return fdopen (fd, binary ? "rb" : "r"); |
|
} |
|
|
|
static FILE * |
|
pex_win32_fdopenw (struct pex_obj *obj ATTRIBUTE_UNUSED, int fd, |
|
int binary) |
|
{ |
|
HANDLE h = (HANDLE) _get_osfhandle (fd); |
|
if (h == INVALID_HANDLE_VALUE) |
|
return NULL; |
|
if (! SetHandleInformation (h, HANDLE_FLAG_INHERIT, 0)) |
|
return NULL; |
|
return fdopen (fd, binary ? "wb" : "w"); |
|
} |
|
|
|
#ifdef MAIN |
|
#include <stdio.h> |
|
|
|
int |
|
main (int argc ATTRIBUTE_UNUSED, char **argv) |
|
{ |
|
char const *errmsg; |
|
int err; |
|
argv++; |
|
printf ("%ld\n", (long) pex_win32_exec_child (NULL, PEX_SEARCH, argv[0], argv, NULL, 0, 0, 1, 2, &errmsg, &err)); |
|
exit (0); |
|
} |
|
#endif
|
|
|