/* 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
Free Software Foundation, Inc.
This file is part of the libiberty library.
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 <windows.h>
+
+#ifdef HAVE_STDLIB_H
+#include <stdlib.h>
+#endif
#ifdef HAVE_STRING_H
#include <string.h>
#endif
#include <io.h>
#include <fcntl.h>
#include <signal.h>
+#include <sys/stat.h>
/* mingw32 headers may not define the following. */
# define WAIT_GRANDCHILD 1
#endif
+#define MINGW_NAME "Minimalist GNU for Windows"
+#define MINGW_NAME_LEN (sizeof(MINGW_NAME) - 1)
+
+/* 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;
+}
+
/* This is a kludge to get around the Microsoft C spawn functions' propensity
to remove the outermost set of double quotes from all arguments. */
static const char * const *
-fix_argv (argvec)
- char **argvec;
+fix_argv (char * const *argvec)
{
+ char **argv;
int i;
- char * command0 = argvec[0];
+ char *command0;
- /* Ensure that the executable pathname uses Win32 backslashes. */
- for (; *command0 != '\0'; command0++)
+ /* See whether we need to change anything. */
+ for (command0 = argvec[0]; *command0 != '\0'; command0++)
if (*command0 == '/')
- *command0 = '\\';
-
- for (i = 1; argvec[i] != 0; i++)
+ break;
+ if (*command0 == '\0')
+ {
+ for (i = 1; argvec[i] != NULL; i++)
+ if (strpbrk (argvec[i], "\" \t") != NULL)
+ break;
+
+ if (argvec[i] == NULL)
+ return (const char * const *) argvec;
+ }
+
+ for (i = 0; argvec[i] != NULL; i++)
+ ;
+ argv = XNEWVEC (char *, i + 2);
+
+ argv++; /* Leave space at the beginning of argv
+ for potential #! handling */
+
+ for (i = 0; argvec[i] != NULL; i++)
+ argv[i] = xstrdup (argvec[i]);
+ argv[i] = NULL;
+
+ backslashify (argv[0]);
+
+ for (i = 1; argv[i] != 0; i++)
{
int len, j;
char *temp, *newtemp;
- temp = argvec[i];
+ temp = argv[i];
len = strlen (temp);
for (j = 0; j < len; j++)
{
if (temp[j] == '"')
{
- newtemp = xmalloc (len + 2);
+ newtemp = XNEWVEC (char, len + 2);
strncpy (newtemp, temp, j);
newtemp [j] = '\\';
strncpy (&newtemp [j+1], &temp [j], len-j);
}
}
- argvec[i] = temp;
- }
+ if (argv[i] != temp)
+ {
+ free (argv[i]);
+ argv[i] = temp;
+ }
+ }
- for (i = 0; argvec[i] != 0; i++)
+ for (i = 0; argv[i] != 0; i++)
{
- if (strpbrk (argvec[i], " \t"))
+ if (strpbrk (argv[i], " \t"))
{
int len, trailing_backslash;
char *temp;
- len = strlen (argvec[i]);
+ len = strlen (argv[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.
+ 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] != '\\')
+ if (len > 1
+ && argv[i][len-1] == '\\'
+ && argv[i][len-2] != '\\')
{
trailing_backslash = 1;
++len; /* to escape the final backslash. */
len += 2; /* and for the enclosing quotes. */
- temp = xmalloc (len + 1);
+ temp = XNEWVEC (char, len + 1);
temp[0] = '"';
- strcpy (temp + 1, argvec[i]);
+ strcpy (temp + 1, argv[i]);
if (trailing_backslash)
- temp[len-2] = '\\';
- temp[len-1] = '"';
+ temp[len - 2] = '\\';
+ temp[len - 1] = '"';
temp[len] = '\0';
- argvec[i] = temp;
+ free (argv[i]);
+ argv[i] = temp;
}
}
- return (const char * const *) argvec;
+ return (const char * const *) argv;
}
-/* 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)
- {
- *errmsg_fmt = "pipe";
- *errmsg_arg = NULL;
- return -1;
- }
- output_desc = pdes[WRITE_PORT];
- last_pipe_input = pdes[READ_PORT];
+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 long pex_win32_exec_child (struct pex_obj *, int, const char *,
+ char * const *, int, int, int,
+ const char **, int *);
+static int pex_win32_close (struct pex_obj *, int);
+static int pex_win32_wait (struct pex_obj *, long, 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);
+
+/* 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,
+ 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++)
+ {
+ LONG res;
+ hTmp = hKey;
+ res = RegOpenKey (hTmp, *keys, &hKey);
+
+ if (hTmp != HKEY_LOCAL_MACHINE)
+ RegCloseKey (hTmp);
+
+ if (res != ERROR_SUCCESS)
+ return NULL;
}
- else
+ 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)
{
- /* Last process. */
- output_desc = STDOUT_FILE_NO;
- last_pipe_input = STDIN_FILE_NO;
+ 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;
}
- if (input_desc != STDIN_FILE_NO)
+ /* 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++)
{
- org_stdin = dup (STDIN_FILE_NO);
- dup2 (input_desc, STDIN_FILE_NO);
- close (input_desc);
+ 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 (output_desc != STDOUT_FILE_NO)
+ /* If foundbuf is empty, we didn't find anything. Punt. */
+ if (!foundbuf[0])
{
- org_stdout = dup (STDOUT_FILE_NO);
- dup2 (output_desc, STDOUT_FILE_NO);
- close (output_desc);
+ free (foundbuf);
+ RegCloseKey (hKey);
+ return executable;
}
- pid = (flags & PEXECUTE_SEARCH ? _spawnvp : _spawnv)
- (_P_NOWAIT, program, fix_argv(argv));
+ /* 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;
- if (input_desc != STDIN_FILE_NO)
+ maxlen = 0;
+ /* Get the length of the value pointed to by InstallLocation */
+ if (RegQueryValueEx (hTmp, "InstallLocation", 0, NULL, NULL,
+ &maxlen) != ERROR_SUCCESS || maxlen == 0)
{
- dup2 (org_stdin, STDIN_FILE_NO);
- close (org_stdin);
+ RegCloseKey (hTmp);
+ return executable;
}
- if (output_desc != STDOUT_FILE_NO)
+ /* Allocate space for the install location */
+ foundbuf = XNEWVEC (char, maxlen + strlen (executable));
+ if (!foundbuf)
{
- dup2 (org_stdout, STDOUT_FILE_NO);
- close (org_stdout);
+ free (foundbuf);
+ RegCloseKey (hTmp);
}
- if (pid == -1)
+ /* Read the install location into the buffer */
+ res = RegQueryValueEx (hTmp, "InstallLocation", 0, NULL, (LPBYTE) foundbuf,
+ &maxlen);
+ RegCloseKey (hTmp);
+ if (res != ERROR_SUCCESS)
{
- *errmsg_fmt = install_error_msg;
- *errmsg_arg = (char*) program;
- return -1;
+ 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 */
+ if (buf)
+ free (buf);
+ return executable;
+}
+#endif
+
+static long
+spawn_script (const char *executable, const char * const * argv)
+{
+ int pid = -1;
+ int save_errno = errno;
+ int fd = _open (executable, _O_RDONLY);
+
+ 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)
+ {
+ char *executable1;
+ const char ** avhere = (const char **) --argv;
+ do
+ *eol = '\0';
+ while (*--eol == '\r' || *eol == ' ' || *eol == '\t');
+ for (executable1 = buf + 2; *executable1 == ' ' || *executable1 == '\t'; executable1++)
+ continue;
+
+ backslashify (executable1);
+ *avhere = executable1;
+#ifndef USE_MINGW_MSYS
+ executable = strrchr (executable1, '\\') + 1;
+ if (!executable)
+ executable = executable1;
+ pid = _spawnvp (_P_NOWAIT, executable, argv);
+#else
+ if (strchr (executable1, '\\') == NULL)
+ pid = _spawnvp (_P_NOWAIT, executable1, argv);
+ else if (executable1[0] != '\\')
+ pid = _spawnv (_P_NOWAIT, executable1, argv);
+ else
+ {
+ const char *newex = mingw_rootify (executable1);
+ *avhere = newex;
+ pid = _spawnv (_P_NOWAIT, newex, argv);
+ if (executable1 != newex)
+ free ((char *) newex);
+ if (pid < 0)
+ {
+ newex = msys_rootify (executable1);
+ if (newex != executable1)
+ {
+ *avhere = newex;
+ pid = _spawnv (_P_NOWAIT, newex, argv);
+ free ((char *) newex);
+ }
+ }
+ }
+#endif
+ }
+ }
+ }
+ if (pid < 0)
+ 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 long
+pex_win32_exec_child (struct pex_obj *obj ATTRIBUTE_UNUSED, int flags,
+ const char *executable, char * const * argv,
+ int in, int out, int errdes, const char **errmsg,
+ int *err)
+{
+ int org_in, org_out, org_errdes;
+ long pid;
+ const char * const * newargv;
+
+ org_in = -1;
+ org_out = -1;
+ org_errdes = -1;
+
+ if (in != STDIN_FILE_NO)
+ {
+ org_in = _dup (STDIN_FILE_NO);
+ if (org_in < 0)
+ {
+ *err = errno;
+ *errmsg = "_dup";
+ return -1;
+ }
+ if (_dup2 (in, STDIN_FILE_NO) < 0)
+ {
+ *err = errno;
+ *errmsg = "_dup2";
+ return -1;
+ }
+ if (_close (in) < 0)
+ {
+ *err = errno;
+ *errmsg = "_close";
+ return -1;
+ }
+ }
+
+ if (out != STDOUT_FILE_NO)
+ {
+ org_out = _dup (STDOUT_FILE_NO);
+ if (org_out < 0)
+ {
+ *err = errno;
+ *errmsg = "_dup";
+ return -1;
+ }
+ if (_dup2 (out, STDOUT_FILE_NO) < 0)
+ {
+ *err = errno;
+ *errmsg = "_dup2";
+ return -1;
+ }
+ if (_close (out) < 0)
+ {
+ *err = errno;
+ *errmsg = "_close";
+ return -1;
+ }
+ }
+
+ if (errdes != STDERR_FILE_NO
+ || (flags & PEX_STDERR_TO_STDOUT) != 0)
+ {
+ org_errdes = _dup (STDERR_FILE_NO);
+ if (org_errdes < 0)
+ {
+ *err = errno;
+ *errmsg = "_dup";
+ return -1;
+ }
+ if (_dup2 ((flags & PEX_STDERR_TO_STDOUT) != 0 ? STDOUT_FILE_NO : errdes,
+ STDERR_FILE_NO) < 0)
+ {
+ *err = errno;
+ *errmsg = "_dup2";
+ return -1;
+ }
+ if (errdes != STDERR_FILE_NO)
+ {
+ if (_close (errdes) < 0)
+ {
+ *err = errno;
+ *errmsg = "_close";
+ return -1;
+ }
+ }
+ }
+
+ newargv = fix_argv (argv);
+ pid = (((flags & PEX_SEARCH) != 0 ? _spawnvp : _spawnv)
+ (_P_NOWAIT, executable, newargv));
+
+ if (pid == -1)
+ pid = spawn_script (executable, newargv);
+
+ if (pid == -1)
+ {
+ *err = errno;
+ *errmsg = ((flags & PEX_SEARCH) != 0) ? "_spawnvp" : "_spawnv";
+ }
+
+ if (in != STDIN_FILE_NO)
+ {
+ if (_dup2 (org_in, STDIN_FILE_NO) < 0)
+ {
+ *err = errno;
+ *errmsg = "_dup2";
+ return -1;
+ }
+ if (_close (org_in) < 0)
+ {
+ *err = errno;
+ *errmsg = "_close";
+ return -1;
+ }
+ }
+
+ if (out != STDOUT_FILE_NO)
+ {
+ if (_dup2 (org_out, STDOUT_FILE_NO) < 0)
+ {
+ *err = errno;
+ *errmsg = "_dup2";
+ return -1;
+ }
+ if (_close (org_out) < 0)
+ {
+ *err = errno;
+ *errmsg = "_close";
+ return -1;
+ }
+ }
+
+ if (errdes != STDERR_FILE_NO
+ || (flags & PEX_STDERR_TO_STDOUT) != 0)
+ {
+ if (_dup2 (org_errdes, STDERR_FILE_NO) < 0)
+ {
+ *err = errno;
+ *errmsg = "_dup2";
+ return -1;
+ }
+ if (_close (org_errdes) < 0)
+ {
+ *err = errno;
+ *errmsg = "_close";
+ return -1;
+ }
+ }
+
+ 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 int
+pex_win32_wait (struct pex_obj *obj ATTRIBUTE_UNUSED, long pid,
+ int *status, struct pex_time *time, int done ATTRIBUTE_UNUSED,
+ const char **errmsg, int *err)
{
int termstat;
- pid = _cwait (&termstat, pid, WAIT_CHILD);
+ if (time != NULL)
+ memset (time, 0, sizeof *time);
+
+ /* FIXME: If done is non-zero, we should probably try to kill the
+ process. */
+
+ if (_cwait (&termstat, pid, WAIT_CHILD) < 0)
+ {
+ *err = errno;
+ *errmsg = "_cwait";
+ return -1;
+ }
- /* ??? Here's an opportunity to canonicalize the values in STATUS.
- Needed? */
+ /* cwait returns the child process exit code in termstat. 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. */
- /* cwait returns the child process exit code in termstat.
- 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);
}
+
+/* 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)
+{
+ return fdopen (fd, binary ? "rb" : "r");
+}
+
+#ifdef MAIN
+#include <stdio.h>
+
+int
+main (int argc ATTRIBUTE_UNUSED, char **argv)
+{
+ char const *errmsg;
+ int err;
+ argv++;
+ printf ("%ld\n", pex_win32_exec_child (NULL, PEX_SEARCH, argv[0], argv, 0, 1, 2, &errmsg, &err));
+ exit (0);
+}
+#endif