X-Git-Url: http://git.sourceforge.jp/view?a=blobdiff_plain;f=libiberty%2Fpex-win32.c;h=44274067482c772ce21115b730d46747040e1492;hb=875ffd1d934c038e3651f6d67b9a37a5c8a809f8;hp=27a5bb4e309ea5885122a35cdc715684f49d665f;hpb=945220d63a29e1085e54320564d51db781637849;p=pf3gnuchains%2Fgcc-fork.git diff --git a/libiberty/pex-win32.c b/libiberty/pex-win32.c index 27a5bb4e309..44274067482 100644 --- a/libiberty/pex-win32.c +++ b/libiberty/pex-win32.c @@ -1,6 +1,6 @@ /* 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 + Copyright (C) 1996, 1997, 1998, 1999, 2000, 2001, 2003, 2004, 2005, 2006 Free Software Foundation, Inc. This file is part of the libiberty library. @@ -16,11 +16,16 @@ 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., 59 Temple Place - Suite 330, -Boston, MA 02111-1307, USA. */ +write to the Free Software Foundation, Inc., 51 Franklin Street - Fifth Floor, +Boston, MA 02110-1301, USA. */ #include "pex-common.h" +#include + +#ifdef HAVE_STDLIB_H +#include +#endif #ifdef HAVE_STRING_H #include #endif @@ -31,10 +36,14 @@ Boston, MA 02111-1307, USA. */ #include #endif +#include #include #include #include #include +#include +#include +#include /* mingw32 headers may not define the following. */ @@ -49,202 +58,893 @@ Boston, MA 02111-1307, USA. */ # define WAIT_GRANDCHILD 1 #endif -/* This is a kludge to get around the Microsoft C spawn functions' propensity - to remove the outermost set of double quotes from all arguments. */ +#define MINGW_NAME "Minimalist GNU for Windows" +#define MINGW_NAME_LEN (sizeof(MINGW_NAME) - 1) + +extern char *stpcpy (char *dst, const char *src); -static const char * const * -fix_argv (argvec) - char **argvec; +/* 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) { - int i; - char * command0 = argvec[0]; + while ((s = strchr (s, '/')) != NULL) + *s = '\\'; + return; +} - /* Ensure that the executable pathname uses Win32 backslashes. */ - for (; *command0 != '\0'; command0++) - if (*command0 == '/') - *command0 = '\\'; - - for (i = 1; argvec[i] != 0; i++) +static int pex_win32_open_read (struct pex_obj *, const char *, int); +static int pex_win32_open_write (struct pex_obj *, const char *, 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) +{ + /* Note that we can't use O_EXCL here because gcc may have already + created the temporary file via make_temp_file. */ + 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++) { - int len, j; - char *temp, *newtemp; + LONG res; + hTmp = hKey; + res = RegOpenKey (hTmp, *keys, &hKey); - temp = argvec[i]; - len = strlen (temp); - for (j = 0; j < len; j++) - { - if (temp[j] == '"') - { - newtemp = xmalloc (len + 2); - strncpy (newtemp, temp, j); - newtemp [j] = '\\'; - strncpy (&newtemp [j+1], &temp [j], len-j); - newtemp [len+1] = 0; - temp = newtemp; - len++; - j++; - } - } + if (hTmp != HKEY_LOCAL_MACHINE) + RegCloseKey (hTmp); - argvec[i] = temp; - } + if (res != ERROR_SUCCESS) + return NULL; + } + return hKey; +} - for (i = 0; argvec[i] != 0; i++) +/* 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) { - if (strpbrk (argvec[i], " \t")) - { - int len, trailing_backslash; - char *temp; - - len = strlen (argvec[i]); - trailing_backslash = 0; - - /* There is an added complication when an arg with embedded white - space ends in a backslash (such as in the case of -iprefix arg - passed to cpp). The resulting quoted strings gets misinterpreted - by the command interpreter -- it thinks that the ending quote - is escaped by the trailing backslash and things get confused. - We handle this case by escaping the trailing backslash, provided - it was not escaped in the first place. */ - if (len > 1 - && argvec[i][len-1] == '\\' - && argvec[i][len-2] != '\\') - { - trailing_backslash = 1; - ++len; /* to escape the final backslash. */ - } + RegCloseKey (hKey); + return executable; + } + namebuf = XNEWVEC (char, ++maxlen); + foundbuf = XNEWVEC (char, maxlen); + foundbuf[0] = '\0'; + if (!namebuf || !foundbuf) + { + RegCloseKey (hKey); + if (namebuf) + free (namebuf); + if (foundbuf) + free (foundbuf); + return executable; + } - len += 2; /* and for the enclosing quotes. */ + /* 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); - temp = xmalloc (len + 1); - temp[0] = '"'; - strcpy (temp + 1, argvec[i]); - if (trailing_backslash) - temp[len-2] = '\\'; - temp[len-1] = '"'; - temp[len] = '\0'; + /* 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); + } - argvec[i] = temp; + /* 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; } } - return (const char * const *) argvec; + if (res) + return tack_on_executable (buf, executable); + + /* failed */ + if (buf) + free (buf); + return executable; } +#endif -/* Win32 supports pipes */ -int -pexecute (program, argv, this_pname, temp_base, errmsg_fmt, errmsg_arg, flags) - const char *program; - char * const *argv; - const char *this_pname ATTRIBUTE_UNUSED; - const char *temp_base ATTRIBUTE_UNUSED; - char **errmsg_fmt, **errmsg_arg; - int flags; -{ - int pid; - int pdes[2]; - int org_stdin = -1; - int org_stdout = -1; - int input_desc, output_desc; - - /* Pipe waiting from last process, to be used as input for the next one. - Value is STDIN_FILE_NO if no pipe is waiting - (i.e. the next command is the first of a group). */ - static int last_pipe_input; - - /* If this is the first process, initialize. */ - if (flags & PEXECUTE_FIRST) - last_pipe_input = STDIN_FILE_NO; - - input_desc = last_pipe_input; - - /* If this isn't the last process, make a pipe for its output, - and record it as waiting to be the input to the next process. */ - if (! (flags & PEXECUTE_LAST)) - { - if (_pipe (pdes, 256, O_BINARY) < 0) +/* 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; + + cmdline_len = 0; + for (i = 0; argv[i]; i++) + { + /* We quote every last argument. This simplifies the problem; + 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. */ + for (j = 0; argv[i][j]; j++) { - *errmsg_fmt = "pipe"; - *errmsg_arg = NULL; - return -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++; + } } - output_desc = pdes[WRITE_PORT]; - last_pipe_input = pdes[READ_PORT]; + /* Trailing backslashes also need to be escaped because they will be + followed by the terminating quote. */ + for (k = j - 1; k >= 0 && argv[i][k] == '\\'; k--) + cmdline_len++; + cmdline_len += j; + cmdline_len += 3; /* for leading and trailing quotes and space */ } - else + cmdline = XNEWVEC (char, cmdline_len); + p = cmdline; + for (i = 0; argv[i]; i++) { - /* Last process. */ - output_desc = STDOUT_FILE_NO; - last_pipe_input = STDIN_FILE_NO; + *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]; + } + for (k = j - 1; k >= 0 && argv[i][k] == '\\'; k--) + *p++ = '\\'; + *p++ = '"'; + *p++ = ' '; } + p[-1] = '\0'; + return cmdline; +} - if (input_desc != STDIN_FILE_NO) +/* 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) { - org_stdin = dup (STDIN_FILE_NO); - dup2 (input_desc, STDIN_FILE_NO); - close (input_desc); + 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); - if (output_desc != STDOUT_FILE_NO) + p = path; + do { - org_stdout = dup (STDOUT_FILE_NO); - dup2 (output_desc, STDOUT_FILE_NO); - close (output_desc); + 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; - pid = (flags & PEXECUTE_SEARCH ? _spawnvp : _spawnv) - (_P_NOWAIT, program, fix_argv(argv)); + found: + CloseHandle (h); + return full_executable; +} - if (input_desc != STDIN_FILE_NO) +/* 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 { - dup2 (org_stdin, STDIN_FILE_NO); - close (org_stdin); + c1 = (unsigned char) tolower (*a++); + c2 = (unsigned char) tolower (*b++); + + if (c1 == '=') + c1 = '\0'; + + if (c2 == '=') + c2 = '\0'; } + while (c1 == c2 && c1 != '\0'); - if (output_desc != STDOUT_FILE_NO) + 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) { - dup2 (org_stdout, STDOUT_FILE_NO); - close (org_stdout); + 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'; + } } - if (pid == -1) + 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)) { - *errmsg_fmt = install_error_msg; - *errmsg_arg = (char*) program; - return -1; + if (env_block) + free (env_block); + + free (full_executable); + + return (pid_t) -1; } + /* Clean up. */ + CloseHandle (pi->hThread); + free (full_executable); + if (env_block) + free (env_block); + + return (pid_t) pi->hProcess; + + error: + if (env_block) + free (env_block); + if (cmdline) + free (cmdline); + if (full_executable) + 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; } -/* 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. */ +/* Execute a child. */ -int -pwait (pid, status, flags) - int pid; - int *status; - int flags ATTRIBUTE_UNUSED; +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, and close the + original descriptors. */ + orig_in = in; + in = _dup (orig_in); + if (orig_in != STDIN_FILENO) + _close (orig_in); + + orig_out = out; + out = _dup (orig_out); + if (orig_out != STDOUT_FILENO) + _close (orig_out); + + if (separate_stderr) + { + orig_err = errdes; + errdes = _dup (orig_err); + if (orig_err != STDERR_FILENO) + _close (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"; + } + + /* 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) { - int termstat; + DWORD termstat; + HANDLE h; - pid = _cwait (&termstat, pid, WAIT_CHILD); + if (time != NULL) + memset (time, 0, sizeof *time); - /* ??? Here's an opportunity to canonicalize the values in STATUS. - Needed? */ + 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; + } - /* cwait returns the child process exit code in termstat. - A value of 3 indicates that the child caught a signal, but not + 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); + *status = (termstat & 0xff) << 8; - return pid; + 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 + +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