use Win32 CreateProcess() rather than mingw spawnv() (#4531)
[ghc-hetmet.git] / driver / utils / cwrapper.c
1
2 /* gcc on mingw is hardcoded to use /mingw (which is c:/mingw) to
3    find various files. If this is a different version of mingw to the
4    one that we have in the GHC tree then things can go wrong. We
5    therefore need to add various -B flags to the gcc commandline,
6    so that it uses our in-tree mingw. Hence this wrapper. */
7
8 #include "cwrapper.h"
9 #include <stdio.h>
10 #include <stdlib.h>
11 #include <stdarg.h>
12 #include <string.h>
13 #include <windows.h>
14
15 void die(const char *fmt, ...) {
16     va_list argp;
17
18     va_start(argp, fmt);
19     vfprintf(stderr, fmt, argp);
20     va_end(argp);
21     exit(1);
22 }
23
24 char *mkString(const char *fmt, ...) {
25     char *p;
26     int i, j;
27     va_list argp;
28
29     va_start(argp, fmt);
30     i = vsnprintf(NULL, 0, fmt, argp);
31     va_end(argp);
32
33     if (i < 0) {
34         die("snprintf 0 failed: errno %d: %s\n", errno, strerror(errno));
35     }
36
37     p = malloc(i + 1);
38     if (p == NULL) {
39         die("malloc failed: errno %d: %s\n", errno, strerror(errno));
40     }
41
42     va_start(argp, fmt);
43     j = vsnprintf(p, i + 1, fmt, argp);
44     va_end(argp);
45     if (i < 0) {
46         die("snprintf with %d failed: errno %d: %s\n",
47             i + 1, errno, strerror(errno));
48     }
49
50     return p;
51 }
52
53 char *flattenAndQuoteArgs(char *ptr, int argc, char *argv[])
54 {
55     int i;
56     char *src;
57
58     for (i = 0; i < argc; i++) {
59         *ptr++ = '"';
60         src = argv[i];
61         while(*src) {
62             if (*src == '"') {
63                 *ptr++ = '\\';
64             }
65             *ptr++ = *src++;
66         }
67         *ptr++ = '"';
68         *ptr++ = ' ';
69     }
70     return ptr;
71 }
72
73 __attribute__((noreturn)) int run (char *exePath,
74                                    int numArgs1, char **args1,
75                                    int numArgs2, char **args2)
76 {
77     int i, cmdline_len;
78     char *new_cmdline, *ptr;
79
80     STARTUPINFO si;
81     PROCESS_INFORMATION pi;
82
83     ZeroMemory(&pi, sizeof(PROCESS_INFORMATION));
84     ZeroMemory(&si, sizeof(STARTUPINFO));
85     si.cb = sizeof(STARTUPINFO);
86
87     /* Compute length of the flattened 'argv'.  for each arg:
88      *   + 1 for the space
89      *   + chars * 2 (accounting for possible escaping)
90      *   + 2 for quotes
91      */
92     cmdline_len = 1 + strlen(exePath)*2 + 2;
93     for (i=0; i < numArgs1; i++) {
94         cmdline_len += 1 + strlen(args1[i])*2 + 2;
95     }
96     for (i=0; i < numArgs2; i++) {
97         cmdline_len += 1 + strlen(args2[i])*2 + 2;
98     }
99
100     new_cmdline = (char*)malloc(sizeof(char) * (cmdline_len + 1));
101     if (!new_cmdline) {
102         die("failed to start up %s; insufficient memory", exePath);
103     }
104
105     ptr = flattenAndQuoteArgs(new_cmdline, 1, &exePath);
106     ptr = flattenAndQuoteArgs(ptr, numArgs1, args1);
107     ptr = flattenAndQuoteArgs(ptr, numArgs2, args2);
108     *--ptr = '\0'; // replace the final space with \0
109
110     /* Note: Used to use _spawnv(_P_WAIT, ...) here, but it suffered
111        from the parent intercepting console events such as Ctrl-C,
112        which it shouldn't. Installing an ignore-all console handler
113        didn't do the trick either.
114
115        Irrespective of this issue, using CreateProcess() is preferable,
116        as it makes this wrapper work on both mingw and cygwin.
117     */
118 #if 0
119     fprintf(stderr, "Invoking %s\n", new_cmdline); fflush(stderr);
120 #endif
121     if (!CreateProcess(exePath,
122                        new_cmdline,
123                        NULL,
124                        NULL,
125                        TRUE,
126                        0, /* dwCreationFlags */
127                        NULL, /* lpEnvironment */
128                        NULL, /* lpCurrentDirectory */
129                        &si,  /* lpStartupInfo */
130                        &pi) ) {
131         die("Unable to start %s (error code: %lu)\n", exePath, GetLastError());
132     }
133     /* Disable handling of console events in the parent by dropping its
134      * connection to the console. This has the (minor) downside of not being
135      * able to subsequently emit any error messages to the console.
136      */
137     FreeConsole();
138
139     switch (WaitForSingleObject(pi.hProcess, INFINITE) ) {
140     case WAIT_OBJECT_0:
141     {
142         DWORD pExitCode;
143         if (GetExitCodeProcess(pi.hProcess, &pExitCode) == 0) {
144             exit(1);
145         }
146         exit(pExitCode);
147     }
148     case WAIT_ABANDONED:
149     case WAIT_FAILED:
150         /* in the event we get any hard errors, bring the child to a halt. */
151         TerminateProcess(pi.hProcess,1);
152         exit(1);
153     default:
154         exit(1);
155     }
156 }