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