From 0d676b85ae437226df3e9704072153bde2328ed9 Mon Sep 17 00:00:00 2001 From: Christopher Faylor Date: Thu, 15 Sep 2005 00:46:20 +0000 Subject: [PATCH] pex-win32.c: Include "windows.h". * pex-win32.c: Include "windows.h". (backslashify): New function. (fix_argv): Use backslashify to convert path to windows format. Allocate one more place in new argv for potential executable from '#!' parsing. (tack_on_executable): New function. Conditional on USE_MINGW_MSYS (openkey): Ditto. (mingw_rootify): Ditto. (msys_rootify): Ditto. (spawn_script): New function. (pex_win32_exec_child): Save translated argv in newargv. Pass to spawn_script if spawnv* fails. (main): New function. Conditional on MAIN. Useful for testing. From-SVN: r104292 --- libiberty/ChangeLog | 16 +++ libiberty/pex-win32.c | 305 ++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 307 insertions(+), 14 deletions(-) diff --git a/libiberty/ChangeLog b/libiberty/ChangeLog index 3e2e92e6068..4cf1a404b11 100644 --- a/libiberty/ChangeLog +++ b/libiberty/ChangeLog @@ -1,3 +1,19 @@ +2005-09-14 Christopher Faylor + + * pex-win32.c: Include "windows.h". + (backslashify): New function. + (fix_argv): Use backslashify to convert path to windows format. + Allocate one more place in new argv for potential executable from '#!' + parsing. + (tack_on_executable): New function. Conditional on USE_MINGW_MSYS + (openkey): Ditto. + (mingw_rootify): Ditto. + (msys_rootify): Ditto. + (spawn_script): New function. + (pex_win32_exec_child): Save translated argv in newargv. Pass to + spawn_script if spawnv* fails. + (main): New function. Conditional on MAIN. Useful for testing. + 2005-08-17 Mark Kettenis * floatformat.c (floatformat_always_valid): Change type of last diff --git a/libiberty/pex-win32.c b/libiberty/pex-win32.c index 10262fbfeed..ed45e5b8bb8 100644 --- a/libiberty/pex-win32.c +++ b/libiberty/pex-win32.c @@ -21,6 +21,8 @@ Boston, MA 02110-1301, USA. */ #include "pex-common.h" +#include + #ifdef HAVE_STDLIB_H #include #endif @@ -53,6 +55,23 @@ Boston, MA 02110-1301, USA. */ # 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. */ @@ -79,20 +98,16 @@ fix_argv (char * const *argvec) for (i = 0; argvec[i] != NULL; i++) ; - argv = XNEWVEC (char *, i + 1); + 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; - /* 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. */ - for (command0 = argv[0]; *command0 != '\0'; command0++) - if (*command0 == '/') - *command0 = '\\'; + backslashify (argv[0]); for (i = 1; argv[i] != 0; i++) { @@ -137,11 +152,11 @@ fix_argv (char * const *argvec) 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 - && argv[i][len-1] == '\\' + if (len > 1 + && argv[i][len-1] == '\\' && argv[i][len-2] != '\\') { trailing_backslash = 1; @@ -230,6 +245,249 @@ 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); + if (namebuf) + free (namebuf); + if (foundbuf) + 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 */ + 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; +} + /* Execute a child. */ static long @@ -240,6 +498,7 @@ pex_win32_exec_child (struct pex_obj *obj ATTRIBUTE_UNUSED, int flags, { int org_in, org_out, org_errdes; long pid; + const char * const * newargv; org_in = -1; org_out = -1; @@ -319,8 +578,12 @@ pex_win32_exec_child (struct pex_obj *obj ATTRIBUTE_UNUSED, int flags, } } + newargv = fix_argv (argv); pid = (((flags & PEX_SEARCH) != 0 ? _spawnvp : _spawnv) - (_P_NOWAIT, executable, fix_argv (argv))); + (_P_NOWAIT, executable, newargv)); + + if (pid == -1) + pid = spawn_script (executable, newargv); if (pid == -1) { @@ -438,3 +701,17 @@ pex_win32_fdopenr (struct pex_obj *obj ATTRIBUTE_UNUSED, int fd, { return fdopen (fd, binary ? "rb" : "r"); } + +#ifdef MAIN +#include + +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