1 // winsupport.cpp                               Copyright (C) 2021 Codemist
2 
3 // $Id: winsupport.cpp 5728 2021-03-13 14:54:03Z arthurcnorman $
4 
5 
6 /**************************************************************************
7  * Copyright (C) 2021, Codemist.                         A C Norman       *
8  *                                                                        *
9  * Redistribution and use in source and binary forms, with or without     *
10  * modification, are permitted provided that the following conditions are *
11  * met:                                                                   *
12  *                                                                        *
13  *     * Redistributions of source code must retain the relevant          *
14  *       copyright notice, this list of conditions and the following      *
15  *       disclaimer.                                                      *
16  *     * Redistributions in binary form must reproduce the above          *
17  *       copyright notice, this list of conditions and the following      *
18  *       disclaimer in the documentation and/or other materials provided  *
19  *       with the distribution.                                           *
20  *                                                                        *
21  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS    *
22  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT      *
23  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS      *
24  * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE         *
25  * COPYRIGHT OWNERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,   *
26  * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,   *
27  * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS  *
28  * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND *
29  * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR  *
30  * TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF     *
31  * THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH   *
32  * DAMAGE.                                                                *
33  *************************************************************************/
34 
35 // Code that needs <windows.h>, separated out into this file because
36 // <windows.h> clashes with some of the Cygwin header files that I have
37 // included generally.
38 
39 #include "config.h"
40 
41 #define LONGEST_LEGAL_FILENAME 1024
42 
43 #ifdef WIN32
44 
45 #include <cstdio>
46 #include <cstring>
47 
48 #include "winsupport.h"
49 
win32_stacklimit(uintptr_t & C_stacklimit)50 void win32_stacklimit(uintptr_t &C_stacklimit)
51 {   HMODULE h = GetModuleHandle(nullptr); // For current executable
52     if (h != nullptr)
53     {   IMAGE_DOS_HEADER *dh = (IMAGE_DOS_HEADER*)h;
54         IMAGE_NT_HEADERS *NTh =
55             (IMAGE_NT_HEADERS*)((BYTE*)dh + dh->e_lfanew);
56         int64_t maxStackSize =
57             (int64_t)NTh->OptionalHeader.SizeOfStackReserve;
58 // If the limit recovered above is under 200K I will pretend it is
59 // just plain wrong and increase it to that. The effect may be that I
60 // end up with an untidy stack overflow but at least I get closer to
61 // using all the space that I have.
62         if (maxStackSize < 200*1024) maxStackSize = 200*1024;
63 // I also assume that any figure over 20 Mbytes is a mess so ignore it
64         if (maxStackSize <= 20*1024*1024)
65         {   // I try to give myself 64K spare...
66             C_stacklimit = (uintptr_t)C_stackbase - maxStackSize + 0x10000;
67         }
68     }
69 }
70 
getMemorySize()71 size_t getMemorySize()
72 {   MEMORYSTATUSEX s;
73     s.dwLength = sizeof(s);
74     GlobalMemoryStatusEx(&s);
75     return s.ullTotalPhys;
76 }
77 
windowsGetPid()78 int windowsGetPid()
79 {   return _getpid();
80 }
81 
82 static char error_name[32];
83 
WSAErrName(int i)84 const char *WSAErrName(int i)
85 {   switch (i)
86     {   default:                 std::sprintf(error_name,
87                                               "Socket error %d", i);
88                                  return error_name;
89         case WSAEINTR:           return "WSAEINTR";
90         case WSAEBADF:           return "WSAEBADF";
91         case WSAEACCES:          return "WSAEACCES";
92 #ifdef WSAEDISCON
93         case WSAEDISCON:         return "WSAEDISCON";
94 #endif
95         case WSAEFAULT:          return "WSAEFAULT";
96         case WSAEINVAL:          return "WSAEINVAL";
97         case WSAEMFILE:          return "WSAEMFILE";
98         case WSAEWOULDBLOCK:     return "WSAEWOULDBLOCK";
99         case WSAEINPROGRESS:     return "WSAEINPROGRESS";
100         case WSAEALREADY:        return "WSAEALREADY";
101         case WSAENOTSOCK:        return "WSAENOTSOCK";
102         case WSAEDESTADDRREQ:    return "WSAEDESTADDRREQ";
103         case WSAEMSGSIZE:        return "WSAEMSGSIZE";
104         case WSAEPROTOTYPE:      return "WSAEPROTOTYPE";
105         case WSAENOPROTOOPT:     return "WSAENOPROTOOPT";
106         case WSAEPROTONOSUPPORT: return "WSAEPROTONOSUPPORT";
107         case WSAESOCKTNOSUPPORT: return "WSAESOCKTNOSUPPORT";
108         case WSAEOPNOTSUPP:      return "WSAEOPNOTSUPP";
109         case WSAEPFNOSUPPORT:    return "WSAEPFNOSUPPORT";
110         case WSAEAFNOSUPPORT:    return "WSAEAFNOSUPPORT";
111         case WSAEADDRINUSE:      return "WSAEADDRINUSE";
112         case WSAEADDRNOTAVAIL:   return "WSAEADDRNOTAVAIL";
113         case WSAENETDOWN:        return "WSAENETDOWN";
114         case WSAENETUNREACH:     return "WSAENETUNREACH";
115         case WSAENETRESET:       return "WSAENETRESET";
116         case WSAECONNABORTED:    return "WSAECONNABORTED";
117         case WSAECONNRESET:      return "WSAECONNRESET";
118         case WSAENOBUFS:         return "WSAENOBUFS";
119         case WSAEISCONN:         return "WSAEISCONN";
120         case WSAENOTCONN:        return "WSAENOTCONN";
121         case WSAESHUTDOWN:       return "WSAESHUTDOWN";
122         case WSAETOOMANYREFS:    return "WSAETOOMANYREFS";
123         case WSAETIMEDOUT:       return "WSAETIMEDOUT";
124         case WSAECONNREFUSED:    return "WSAECONNREFUSED";
125         case WSAELOOP:           return "WSAELOOP";
126         case WSAENAMETOOLONG:    return "WSAENAMETOOLONG";
127         case WSAEHOSTDOWN:       return "WSAEHOSTDOWN";
128         case WSAEHOSTUNREACH:    return "WSAEHOSTUNREACH";
129         case WSASYSNOTREADY:     return "WSASYSNOTREADY";
130         case WSAVERNOTSUPPORTED: return "WSAVERNOTSUPPORTED";
131         case WSANOTINITIALISED:  return "WSANOTINITIALISED";
132         case WSAHOST_NOT_FOUND:  return "WSAHOST_NOT_FOUND";
133         case WSATRY_AGAIN:       return "WSATRY_AGAIN";
134         case WSANO_RECOVERY:     return "WSANO_RECOVERY";
135         case WSANO_DATA:         return "WSANO_DATA";
136     }
137 }
138 
windowsPrepareSockets()139 int windowsPrepareSockets()
140 {
141 // Under Windows the socket stuff is not automatically active, so some
142 // system calls have to be made at the start of a run. I demand a
143 // Winsock 1.1, and fail if that is not available.
144     WSADATA wsadata;
145     int i = WSAStartup(MAKEWORD(1,1), &wsadata);
146     if (i != 0) return i;   /* Failed to start winsock for some reason */;
147     if (LOBYTE(wsadata.wVersion) != 1 ||
148         HIBYTE(wsadata.wVersion) != 1)
149     {   WSACleanup();
150         return 1;      // Version 1.1 of winsock needed
151     }
152     return 0;
153 }
154 
155 HANDLE gnuplot_process = 0;
156 HWND gnuplot_handle = 0;
157 bool gnuplotActive = false;
158 
159 static GnuplotClass gnuplotAlive;
160 
find_text(HWND h,LPARAM)161 BOOL CALLBACK find_text(HWND h, LPARAM)
162 {   char buffer[24];
163     GetClassName(h, buffer, 20);
164     if (std::strcmp(buffer, "wgnuplot_text") != 0) return TRUE;
165     gnuplot_handle = h;
166     return FALSE;
167 }
168 
windowsFindGnuplot(const char * command,const char * direction)169 FILE *windowsFindGnuplot(const char *command, const char *direction)
170 {   int i = 0;
171 // The following test would trigger if the string "wgnuplot.exe" was present
172 // within the path even if it was not the final component. I think that at
173 // present I will take the view that anybody who finds themselves hurt because
174 // of that has only themselves to blame.
175     if (std::strstr(command, "wgnuplot.exe") != nullptr)
176     {   HWND parent = 0;
177 // Win32 would rather I used the following long-winded version, which provides
178 // a pile of generality that is irrelevant here!
179         STARTUPINFO startup;
180         PROCESS_INFORMATION process;
181         char c1[LONGEST_LEGAL_FILENAME];
182         std::clock_t t0, t1;
183         std::memset(&startup, 0, sizeof(STARTUPINFO));
184         startup.cb = sizeof(startup);
185         startup.lpReserved = nullptr;
186         startup.lpDesktop = nullptr;
187         startup.lpTitle = nullptr;
188         startup.dwFlags = STARTF_USESHOWWINDOW;
189         startup.wShowWindow = SW_SHOWMINIMIZED;
190         startup.cbReserved2 = 0;
191         startup.lpReserved2 = nullptr;
192         std::strncpy(c1, command, sizeof(c1));
193         c1[sizeof(c1)-1] = 0;
194         if (!CreateProcess(nullptr, c1, nullptr, nullptr, FALSE,
195                            0, nullptr, nullptr, &startup, &process))
196             return nullptr;
197         gnuplot_process = process.hProcess;
198         gnuplotActive = true;
199         gnuplot_handle = 0;
200         t0 = std::clock();
201         for (i=0; i<25; i++)  // Give it 5 seconds to appear
202         {   parent = FindWindow((LPSTR)"wgnuplot_parent",
203                                 (LPSTR)"gnuplot");
204             if (parent != 0) break;
205             t0 += CLOCKS_PER_SEC/5;
206             while ((t1 = std::clock()) < t0) ; // a busy-wait here
207             t0 = t1;
208         }
209         if (parent != 0)
210         {   for (i=0; i<10; i++)   // 2 more seconds for the child
211             {   EnumChildWindows(parent, find_text, 0);
212                 if (gnuplot_handle != 0) break;
213                 t0 += CLOCKS_PER_SEC/5;
214                 while ((t1 = std::clock()) < t0) ; // busy-wait
215                 t0 = t1;
216             }
217         }
218         // special handle for the gnuplot pipe
219         return reinterpret_cast<std::FILE *>(-1);
220     }
221     return _popen(command, direction);
222 }
223 
224 #ifdef HAVE_PUTC_UNLOCKED
225 #define PUTC(x, y) putc_unlocked((x), (y))
226 #else
227 #ifdef HAVE__PUTC_NOLOCK
228 #define PUTC(x, y) _putc_nolock((x), (y))
229 #else
230 #define PUTC(x, y) putc((x), (y))
231 #endif
232 #endif
233 
234 
my_pipe_putc(int c,std::FILE * f)235 int my_pipe_putc(int c, std::FILE *f)
236 {   if (f == (std::FILE *)(-1))
237     {   if (gnuplot_handle == 0) return EOF;
238         if (c == '\n') c = '\r';
239         SendMessage(gnuplot_handle, WM_CHAR, c, 1L);
240         return c;
241     }
242     else return PUTC(c, f);
243 }
244 
my_pipe_flush(std::FILE * f)245 int my_pipe_flush(std::FILE *f)
246 {
247     if (f == (std::FILE *)(-1)) return 0;
248     return std::fflush(f);
249 }
250 
my_pclose(std::FILE * stream)251 void my_pclose(std::FILE *stream)
252 {
253     if (stream == (std::FILE *)(-1))
254     {   SendMessage(gnuplot_handle, WM_CHAR, 'q', 1L);
255         SendMessage(gnuplot_handle, WM_CHAR, 'u', 1L);
256         SendMessage(gnuplot_handle, WM_CHAR, 'i', 1L);
257         SendMessage(gnuplot_handle, WM_CHAR, 't', 1L);
258         SendMessage(gnuplot_handle, WM_CHAR, '\r', 1L);
259         return;
260     }
261     _pclose(stream);
262 }
263 
read_clock()264 uint64_t read_clock()
265 {   FILETIME t0, t1, t2, t3;
266     if (GetProcessTimes(GetCurrentProcess(), &t0, &t1, &t2, &t3) == 0)
267         return 0;
268 // The 4 times are: CreationTime, ExitTime, KernelTime, UserTime
269     ULARGE_INTEGER ul;
270 // Times are returned in FILETIME format so I need to convert to an arithmetic
271 // type that I can use.
272     ul.LowPart = t3.dwLowDateTime;
273     ul.HighPart = t3.dwHighDateTime;
274     uint64_t n = ul.QuadPart;
275 // Times are returned in units of 100ns, so I divide by 10 to get
276 // microseconds.
277     return n/10;
278 }
279 
windowsFindGnuplot1(char * name)280 int windowsFindGnuplot1(char *name)
281 {   HKEY keyhandle;
282 // I need to use RegQueryValueEx here rather than RegGetValue if I am to
283 // support 32-bit Windows XP. Note that buffer overflow in the path to
284 // gnuplot could leave an unterminated string, but that should not happen
285 // here.
286     DWORD length = LONGEST_LEGAL_FILENAME, type;
287     LONG r = RegOpenKeyEx(
288                  HKEY_LOCAL_MACHINE,
289                  "Software\\Microsoft\\Windows\\CurrentVersion\\"
290                      "App Paths\\gnuplot.exe",
291                  0,
292                  KEY_QUERY_VALUE,
293                  &keyhandle);
294     if (r == ERROR_SUCCESS)
295     {   r = RegQueryValueEx(keyhandle,
296                             nullptr,
297                             nullptr,
298                             (LPDWORD)&type,
299                             (LPBYTE)name,
300                             (LPDWORD)&length);
301         name[LONGEST_LEGAL_FILENAME-1] = 0;
302         if (r == ERROR_SUCCESS) return 1;
303     }
304     return 0;
305 }
306 
windowsGetTempPath(size_t n,char * s)307 size_t windowsGetTempPath(size_t n, char *s)
308 {   return GetTempPath(n, s);
309 }
310 
311 // On Windows I can query the page that the address is within, and accept
312 // it if there is read/write access and if it is not a guard page.
313 
valid_address(void * pointer)314 bool valid_address(void *pointer)
315 {   MEMORY_BASIC_INFORMATION mbi = {0};
316     if (::VirtualQuery(pointer, &mbi, sizeof(mbi)))
317     {   // check the page is not a guard page
318         if (mbi.Protect & (PAGE_GUARD|PAGE_NOACCESS)) return false;
319         return ((mbi.Protect & (PAGE_READWRITE|PAGE_EXECUTE_READWRITE)) != 0);
320     }
321     return false;  // ::VirtualQuery failed.
322 }
323 
324 #elif defined __CYGWIN__
325 
326 // The aim here is to avoid use of the Microsoft versions of printf and
327 // friends and (hence) allow g++ to parse and check format strings reliably.
328 #define __USE_MINGW_ANSI_STDIO 1
329 
330 #include <winsock.h>
331 #include <process.h>
332 #include <windows.h>
333 
334 #include <cstring>
335 #include <cstdio>
336 
337 #include "winsupport.h"
338 
windowsFindGnuplot2(char * name)339 int windowsFindGnuplot2(char *name)
340 {   HKEY keyhandle;
341 // I need to use RegQueryValueEx here rather than RegGetValue if I am to
342 // support 32-bit Windows XP. Note that buffer overflow in the path to
343 // gnuplot could leave an unterminated string, but that should not happen
344 // here.
345     DWORD length = LONGEST_LEGAL_FILENAME, type;
346     int ll, i;
347     LONG r = RegOpenKeyEx(
348                  HKEY_LOCAL_MACHINE,
349                  "Software\\Microsoft\\Windows\\CurrentVersion\\"
350                      "App Paths\\wgnuplot.exe",
351                  0,
352                  KEY_QUERY_VALUE,
353                  &keyhandle);
354     if (r == ERROR_SUCCESS)
355     {   r = RegQueryValueEx(keyhandle,
356                             nullptr,
357                             nullptr,
358                             (LPDWORD)&type,
359                             (LPBYTE)name,
360                             (LPDWORD)&length);
361         name[LONGEST_LEGAL_FILENAME-1] = 0;
362         if (r == ERROR_SUCCESS)
363         {
364 // Now I have a further delight. The path I have just identified is a
365 // Windows one, but I need to convert it into a Cygwin-style one. It
366 // should start "x:\" with a drive name so I map that onto "/cygdrive/x/"
367 // and convert every "\" to a "/". The code here is rather grotty with the
368 // numeric "magic offsets" and the use of sprintf followed by patching
369 // up after the terminating null from that, but it is at least concise.
370             ll = std::strlen(name);
371             for (i=ll; i>=0; i--)
372                 name[i+9] = name[i]=='\\' ? '/' : name[i];
373             std::sprintf(name, "/cygdrive/%c", name[0]);
374             name[11] = '/';
375             return 1;
376         }
377     }
378     return 0;
379 }
380 
windowsGetTempPath(size_t n,char * s)381 size_t windowsGetTempPath(size_t n, char *s)
382 {   return GetTempPath(n, s);
383 }
384 
385 #endif // Windows & Cygwin
386 
387 // end of winsupport.cpp
388