1 // stub2020.c Copyright (C) A C Norman 2020
2
3
4 /**************************************************************************
5 * Copyright (C) 2020, Codemist Ltd. A C Norman *
6 * *
7 * Redistribution and use in source and binary forms, with or without *
8 * modification, are permitted provided that the following conditions are *
9 * met: *
10 * *
11 * * Redistributions of source code must retain the relevant *
12 * copyright notice, this list of conditions and the following *
13 * disclaimer. *
14 * * Redistributions in binary form must reproduce the above *
15 * copyright notice, this list of conditions and the following *
16 * disclaimer in the documentation and/or other materials provided *
17 * with the distribution. *
18 * *
19 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS *
20 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT *
21 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS *
22 * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE *
23 * COPYRIGHT OWNERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, *
24 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, *
25 * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS *
26 * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND *
27 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR *
28 * TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF *
29 * THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH *
30 * DAMAGE. *
31 *************************************************************************/
32
33 // $Id: newstub.c 2462 2014-03-26 20:48:39Z arthurcnorman $
34
35
36
37
38 //
39 // The purpose of this program is to decide whether CSL/Reduce should
40 // run as a Cygwin or a Native Windows program. It also wants to help
41 // me sort out as between 32- and 64-bit variants of each.
42 //
43 // When compiling it you must define NAME to be the name of the eventual
44 // target.
45 //
46 // So some fundamentals first:
47 // This must be compiled using
48 // i686-w64-mingw32-gcc -static -DNAME=xxx ...
49 // where the "-static" is needed so that this is sufficiently self contained,
50 // and it is a 32-bit executable so it will run on both 32 and 64-bit versions
51 // of Windows. I had tried a version of this built using g++ not gcc but the
52 // C++ libraries that got sucked in made the executable unreasonably large.
53 //
54 // If compiled with NO_32_BIT defined it will only support 64-bit cases
55 // and should be built using x86_64-w64-mingw32-gcc
56 // With NO_CYGWIN it will not support the Cygwin variants. If you defined
57 // both at once you would get something that was pretty pointless because
58 // it would always just chain to the 64-bit Windows binary!
59
60
61 #include <windows.h>
62 #include <stdio.h>
63 #include <string.h>
64 #include <stdlib.h>
65 #include <stdint.h>
66 #include <inttypes.h>
67
68 #define ERROR_NO_MEMORY 84
69 #define ERROR_PROCESS_INFO 85
70 #define ERROR_CREATEPROCESS 86
71
72 #define str(x) stringify(x)
73 #define stringify(x) #x
74
75 // runResource executes a program from the reduce.resources directory
76 // and gives back its return code. It sticks "extra" as an additional forced
77 // first argument on the command line (if extra!=NULL).
78
runResource(const char * modulename,const char * extra)79 int runResource(const char *modulename, const char *extra)
80 {
81 #ifdef DEBUG
82 printf("runResource %s %s\n", modulename, extra==NULL ? "" : extra);
83 #endif
84 // If this program is $P/XXX.exe then I want to look in the directory
85 // $P/reduce.resources for the program I am to run. So first I need to
86 // find out what $P is.
87 char pPath[MAX_PATH];
88 GetModuleFileName(NULL, pPath, sizeof(pPath));
89 #ifdef DEBUG
90 printf("my name is %s\n", pPath);
91 #endif
92 char modPath[MAX_PATH];
93 strcpy(modPath, pPath);
94 char *lastsep = strrchr(modPath, '\\');
95 sprintf(lastsep, "\\reduce.resources\\%s.exe", modulename);
96 #ifdef DEBUG
97 printf("Code to run %s\n", modPath);
98 #endif
99 // I will pass my own command line down to the program that I execute.
100 const char *cmd = GetCommandLine();
101 #ifdef DEBUG
102 printf("GetCommandLine returns <%s>\n", cmd);
103 #endif
104 char *cmd1 = (char *)malloc(strlen(cmd) + strlen(pPath) + 12);
105 if (cmd1 == NULL)
106 { printf("No memory for new command line\n"); fflush(stdout);
107 return ERROR_NO_MEMORY;
108 }
109 // I am going to put my own fully-rooted path at the start of the command-
110 // line. I enclose it in quotes in case it might have embedded white-space.
111 // First strip off the existing start...
112 if (*cmd == '"')
113 { cmd++;
114 while (cmd!=0 && *cmd!='"') cmd++;
115 if (*cmd!=0) cmd++;
116 }
117 else
118 { while (*cmd!=0 && *cmd!=' ') cmd++;
119 }
120 if (extra == NULL) extra = "";
121 if (pPath[0] == '"') sprintf(cmd1, "%s %s %s", pPath, extra, cmd);
122 else sprintf(cmd1, "\"%s\" %s %s", pPath, extra, cmd);
123 STARTUPINFO peStartUpInformation;
124 PROCESS_INFORMATION peProcessInformation;
125 memset(&peStartUpInformation, 0, sizeof(STARTUPINFO));
126 peStartUpInformation.cb = sizeof(STARTUPINFO);
127 memset(&peProcessInformation, 0, sizeof(PROCESS_INFORMATION));
128 #ifdef DEBUG
129 printf("Launch <%s> cmd line <%s>\n", modPath, cmd1); fflush(stdout);
130 #endif
131 SetErrorMode(SEM_FAILCRITICALERRORS | SEM_NOGPFAULTERRORBOX);
132 // _set_abort_behavior(0,_WRITE_ABORT_MSG | _CALL_REPORTFAULT);
133 if (CreateProcessA(modPath, // appname to launch
134 cmd1, // command line
135 NULL, // process attributes
136 NULL, // thread attributes
137 1, // inherits handles
138 0, // allow it to run now
139 NULL, // environment
140 NULL, // current directory
141 &peStartUpInformation,
142 &peProcessInformation))
143 {
144 // If the sub-task is successfully started I will want to wait until it
145 // terminates and then recover its exit code.
146 WaitForSingleObject(peProcessInformation.hProcess, INFINITE);
147 DWORD rc;
148 if (GetExitCodeProcess(peProcessInformation.hProcess, &rc) == 0)
149 rc = ERROR_PROCESS_INFO; // Getting the return code failed!
150 #ifdef DEBUG
151 printf("CreateProcess happened, rc reported as %d = %#x\n", rc, rc);
152 #endif
153 fflush(stdout);
154 CloseHandle(peProcessInformation.hProcess);
155 CloseHandle(peProcessInformation.hThread);
156 return rc;
157 }
158 else
159 {
160 #ifdef DEBUG
161 printf("CreateProcess failed\n"); fflush(stdout);
162 DWORD dw = GetLastError();
163 LPVOID lpMsgBuf;
164 FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER |
165 FORMAT_MESSAGE_FROM_SYSTEM |
166 FORMAT_MESSAGE_IGNORE_INSERTS,
167 NULL,
168 dw,
169 MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
170 (LPSTR)&lpMsgBuf,
171 0,
172 NULL);
173 printf("CreateProcess failed (%d): %s\n", dw, lpMsgBuf);
174 fflush(stdout);
175 #endif
176 return ERROR_CREATEPROCESS;
177 }
178 }
179
180 #ifndef NO_CYGWIN
181
182 static const char *dll64[] =
183 {
184 // "dll64.c" has been created by a bit of script that starts
185 // objdump -p reduce.exe | grep DLL | sed ...
186 // to build a list of all the DLLs mentioned by reduce.exe, each as
187 // a string with a comma at the end of the line. I need the NULL here to
188 // follow on from the final comma.
189 #include "dll64.c"
190 NULL
191 };
192
193 #ifndef NO_32_BIT
194
195 const char *dll32[] =
196 {
197 // "dll32.c" has been created by a bit of script that starts
198 // objdump -p reduce.exe | grep DLL | sed ...
199 // to build a list of all the DLLs mentioned by reduce.exe, each as
200 // a string with a comma at the end of the line. I need the NULL here to
201 // follow on from the final comma.
202 #include "dll32.c"
203 NULL
204 };
205
206 #endif // NO_32_BIT
207
208 // This reads a DLL and checks its headers to verify the architecture
209 // it had been created for.
210
PEcheck(FILE * f,int width)211 int PEcheck(FILE *f, int width)
212 { unsigned char buffer[1024];
213 memset(buffer, 0, sizeof(buffer));
214 size_t len = fread(buffer, 1, sizeof(buffer), f);
215 if (len < sizeof(struct _IMAGE_DOS_HEADER)) return 0;
216 LONG pe = ((struct _IMAGE_DOS_HEADER *)buffer)->e_lfanew;
217 #ifdef DEBUG
218 printf("Offset of PE header is %d = %#x\n", pe, pe);
219 #endif
220 if (len < pe+6) return 0;
221 unsigned int mc = *(uint16_t *)&buffer[pe+4];
222 #ifdef DEBUG
223 printf("Machine type given as %#x\n", mc);
224 #endif
225 return width==32 ?
226 (mc==IMAGE_FILE_MACHINE_I386) :
227 (mc==IMAGE_FILE_MACHINE_AMD64);
228 }
229
230 const char *PATH=NULL;
231
dllcheck(const char * name,int width)232 int dllcheck(const char *name, int width)
233 { if (PATH == NULL) PATH = getenv("PATH");
234 // D1 ; D2 ; ...
235 // where each directory D is given without a trailing "\"
236 int i=0, j=0;
237 for (;;)
238 { while (PATH[j] != 0 && PATH[j] != ';') j++;
239 char location[MAX_PATH];
240 sprintf(location, "%.*s\\%s", j-i, &PATH[i], name);
241 #ifdef DEBUG
242 printf("Check |%s|\n", location);
243 #endif
244 FILE *f = fopen(location, "rb");
245 if (f != NULL)
246 { int res = PEcheck(f, width);
247 fclose(f);
248 return res;
249 }
250 if (PATH[j] == 0) return 0;
251 i = j = j+1;
252 }
253 }
254
cygcheck(const char * dlls[],int width)255 int cygcheck(const char *dlls[], int width)
256 { if (dllcheck("cygwin1.dll", width) == 0) return 0;
257 for (int i=0; dlls[i] != 0; i++)
258 if (strcmp(dlls[i], "cygwin1.dll") != 0 &&
259 dllcheck(dlls[i], width) == 0) return 0;
260 return 1;
261 }
262
263 #endif // NO_CYGWIN
264
265 #ifndef NO_32_BIT
266
267 // The next function is as shown at
268 // msdn.microsoft.com/en-us/library/windows/desktop/ms684139%28v=vs.85%29.aspx
269 // as the proper way to detect when the current 32-bit program is in fact
270 // running under Wow64 (ie with a 64-bit version of Windows beneath its feet).
271
272 typedef BOOL (WINAPI *LPFN_ISWOW64PROCESS)(HANDLE, PBOOL);
273
274 LPFN_ISWOW64PROCESS fnIsWow64Process;
275
IsWow64()276 BOOL IsWow64()
277 {
278 BOOL bIsWow64 = FALSE;
279 // IsWow64Process is not available on all supported versions of Windows.
280 // Use GetModuleHandle to get a handle to the DLL that contains the function
281 // and GetProcAddress to get a pointer to the function if available. Well
282 // probably these days it is always available on operating systems that matter,
283 // but the hack here is fairly simple and local so to be kind to the
284 // historical world I will preserve it for a while!
285 fnIsWow64Process = (LPFN_ISWOW64PROCESS) GetProcAddress(
286 GetModuleHandle(TEXT("kernel32")),"IsWow64Process");
287 if(NULL != fnIsWow64Process)
288 {
289 if (!fnIsWow64Process(GetCurrentProcess(),&bIsWow64))
290 { //handle error - well here I just return "no"
291 return FALSE;
292 }
293 }
294 return bIsWow64;
295 }
296
297 #endif // NO_32_BIT
298
299 // Run either the 32-bit Windows binary or the 64-bit one. If the regular
300 // name if the program is XXX then this launches wither XXXwin32 or
301 // XXXwin64. The names I will in fact use are "boot", "wboot, "red" and
302 // "wred" and I should probably include "csl" and "wcsl".
303
runWindows(const char * extra)304 int runWindows(const char *extra)
305 {
306 #ifdef NO_32_BIT
307 return runResource(str(NAME) "win64", extra);
308 #else // NO_32_BIT
309 // This stub runs as 32-bit code, but if it is on a 64-bit version of
310 // windows it will be running under "Wow64". Checking for that should allow
311 // me to know if there is any prospect of running a 64-bit version of the
312 // application - and if I can then I should!
313 //
314 if (IsWow64()) return runResource(str(NAME) "win64", extra);
315 else return runResource(str(NAME) "win32", extra);
316 #endif // NO_32_BIT
317 }
318
main(int argc,char * argv[])319 int main(int argc, char *argv[])
320 {
321 #ifndef NO_CYGWIN
322 // I expect that if I have been invoked from a Unix-like shell rather than
323 // either from a DOS prompt (including the Windows Powershell) that
324 // environment variables TERM and SHELL will both be set. So if either of
325 // those is NOT set I should just use a Native Windows version.
326 if (getenv("TERM") == NULL ||
327 getenv("SHELL") == NULL) return runWindows(NULL);
328 // Now (unless DISPLAY is set, in which case I might want to use X11) if
329 // I have not forced use of a console mode version using "-w, -w- or --nogui
330 // I will also use a Windows binary. This will include cases where there
331 // has been redirection of the standard input or output.
332 if (getenv("DISPLAY") == NULL)
333 { int nogui = 0;
334 for (int i=1; i<argc; i++)
335 { if (strcmp(argv[i], "-w") == 0 ||
336 strcmp(argv[i], "-w-") == 0 ||
337 strcmp(argv[i], "--nogui") == 0)
338 { nogui = 1;
339 break;
340 }
341 }
342 // Well as an extra complication I will check if I am in a session that
343 // is running over ssh. The SSH_CLIENT environment variable is set up by
344 // openssh in such cases. Then rather than letting a lack of "-w" allow
345 // me to use a Windows GUI I am liable to want to use Cygwin, so I treat
346 // running under ssh but without DISPLAY set as much the same as having
347 // ased for --nogui.
348 if (getenv("SSH_CLIENT") != 0) nogui = 1;
349 if (nogui == 0) return runWindows(NULL);
350 }
351 // Here I will use a Cygwin version if it is available.
352 #ifdef NO_32_BIT
353 if (cygcheck(dll64, 64)) return runResource(str(NAME) "cyg64", NULL);
354 #else // NO_32_BIT
355 // This has to involve considering both 32 and 64-bit cygwin if I am
356 // on a 64-bit platform or just the 32-bit one if on a 32-bit machine.
357 int cygwin;
358 if (IsWow64() && cygcheck(dll64, 64))
359 return runResource(str(NAME) "cyg64", NULL);
360 else if (cygcheck(dll32, 32))
361 return runResource(str(NAME) "cyg32", NULL);
362 #endif // NO_32_BIT
363 #endif // NO_CYGWIN
364 return runWindows(NULL);
365 }
366
367 // end of stub2020.c
368