1 // pipex_win32.c
2 
3 #ifdef _WIN32
4 
5 // includes
6 
7 #include <io.h>
8 #include <fcntl.h>
9 #include "pipex.h"
10 #include "util.h"
11 
12 // defines
13 
14 #define ErrorBufferSize 4096
15 #define dwMaxHandles      32
16 
17 // variables
18 
19 static char ErrorBuffer[ErrorBufferSize];
20 
21 // prototypes
22 
23 static bool pipex_eof_input(pipex_t *pipex);
24 static void pipex_set_eof_input(pipex_t *pipex);
25 static void pipex_set_active(pipex_t *pipex);
26 static int  pipex_read_data(pipex_t *pipex);
27 static void pipex_read_input(pipex_t *pipex);
28 
29 // functions
30 
31 // win32_error()
32 
win32_error()33 static char * win32_error(){
34     FormatMessage(
35         FORMAT_MESSAGE_FROM_SYSTEM,
36         NULL,
37         GetLastError(),
38         LANG_USER_DEFAULT,
39         ErrorBuffer,
40         ErrorBufferSize,
41         NULL);
42     return ErrorBuffer;
43 }
44 
45 // TreadProc()
46 
ThreadProc(LPVOID lpParam)47 static DWORD WINAPI ThreadProc(LPVOID lpParam){
48     pipex_t *p=(pipex_t *) lpParam;
49     while(!pipex_eof_input(p)){
50         if(p->nReadEnd<LINE_INPUT_MAX_CHAR-1){
51             pipex_read_input(p);
52         }else{
53                 // wait until there is room in buffer
54             Sleep(10);
55         }
56     }
57     return 0;
58 }
59 
60 // pipex_open()
61 
pipex_open(pipex_t * pipex,const char * szName,const char * szWorkingDir,const char * szProcFile)62 void pipex_open(pipex_t *pipex,
63                 const char *szName,
64                 const char *szWorkingDir,
65                 const char *szProcFile) {
66     DWORD dwMode, dwThreadId;
67     HANDLE hStdinRead, hStdinWrite, hStdoutRead, hStdoutWrite, hThread;
68     SECURITY_ATTRIBUTES sa;
69     STARTUPINFO si;
70     PROCESS_INFORMATION pi;
71     int fdInput;
72     char *szCurrentDir;
73     pipex->state=0;
74     pipex->name=szName;
75     pipex->command=szProcFile;
76     pipex->quit_pending=FALSE;
77     pipex->hProcess=NULL;
78     if (szProcFile == NULL) {
79         pipex->hInput = GetStdHandle(STD_INPUT_HANDLE);
80         pipex->hOutput = GetStdHandle(STD_OUTPUT_HANDLE);
81         pipex->bConsole = GetConsoleMode(pipex->hInput, &dwMode);
82         pipex->bPipe=FALSE;
83     } else {
84         sa.nLength = sizeof(SECURITY_ATTRIBUTES);
85         sa.bInheritHandle = TRUE;
86         sa.lpSecurityDescriptor = NULL;
87         CreatePipe(&hStdinRead, &hStdinWrite, &sa, 0);
88         CreatePipe(&hStdoutRead, &hStdoutWrite, &sa, 0);
89         si.cb = sizeof(STARTUPINFO);
90         si.lpReserved = si.lpDesktop = si.lpTitle = NULL;
91         si.dwFlags = STARTF_USESTDHANDLES;
92         si.cbReserved2 = 0;
93         si.lpReserved2 = NULL;
94         si.hStdInput = hStdinRead;
95         si.hStdOutput = hStdoutWrite;
96         si.hStdError = hStdoutWrite;
97         if((szCurrentDir = (char*)_getcwd( NULL, 0 )) == NULL )
98             my_fatal("pipex_open(): no current directory: %s\n",
99                      strerror(errno));
100         if(_chdir(szWorkingDir)){
101             my_fatal("pipex_open(): %s: %s\n",
102 		     szWorkingDir,
103                      strerror(errno));
104         }
105        if(CreateProcess(NULL,
106                          (LPSTR) szProcFile,
107                          NULL,
108                          NULL,
109                          TRUE,
110                          DETACHED_PROCESS | CREATE_NEW_PROCESS_GROUP,
111                          NULL,
112                          NULL,
113                          &si,
114                          &pi)){
115             pipex->hProcess=pi.hProcess;
116             CloseHandle(pi.hThread);
117             CloseHandle(hStdinRead);
118             CloseHandle(hStdoutWrite);
119             pipex->hInput = hStdoutRead;
120             pipex->hOutput = hStdinWrite;
121             pipex->bConsole = FALSE;
122             pipex->bPipe=TRUE;
123         }else{
124 	  my_fatal("pipex_open(): %s: %s",szProcFile,win32_error());
125         }
126         _chdir(szCurrentDir);
127     }
128     if (pipex->bConsole) {
129         SetConsoleMode(pipex->hInput,
130                        dwMode & ~(ENABLE_MOUSE_INPUT | ENABLE_WINDOW_INPUT));
131         FlushConsoleInputBuffer(pipex->hInput);
132     }
133     fdInput=_open_osfhandle((long) pipex->hInput,_O_RDONLY);
134     if(fdInput==-1){
135         my_fatal("pipex_open(): %s",strerror(errno));
136     }
137     pipex->fpInput=fdopen(fdInput,"r");
138     if(pipex->fpInput==NULL){
139         my_fatal("pipex_open(): %s",strerror(errno));
140     }
141     pipex->nReadEnd = 0;
142     pipex->lpFeedEnd = NULL;
143     InitializeCriticalSection(&(pipex->CriticalSection));
144     pipex->hEvent=CreateEvent(NULL,           // default security
145                        FALSE,          // auto reset
146                        FALSE,          // not signaled
147                        NULL            // nameless
148                        );
149     if(!(pipex->hEvent)){
150         my_fatal("pipex_open(): %s",win32_error());
151     }
152     hThread=CreateThread(NULL,         // default security
153                          0,            // default stacksize
154                          ThreadProc,   // worker function
155                          pipex,        // tell worker about ourselves
156                          0,            // run immediately
157                          &dwThreadId   // dropped, but needed for the call to work in Win9x
158                          );
159     if(!hThread){
160         my_fatal("pipex_open(): %s",win32_error());
161     }
162     pipex->dwWriteIndex=0;
163     pipex_set_active(pipex);
164 }
165 
166 
167 // pipex_wait_event(pipex)
168 
pipex_wait_event(pipex_t * pipex[])169 void pipex_wait_event(pipex_t *pipex[]){
170     HANDLE hHandles[dwMaxHandles];
171     DWORD dwHandleCount=0;
172     pipex_t *p;
173     while((p=pipex[dwHandleCount])!=NULL){
174         ASSERT((p->hEvent)!=0);
175         if(dwHandleCount>=dwMaxHandles){
176             my_fatal("pipex_wait_event(): Too many objects to wait for");
177         }
178         hHandles[dwHandleCount++]=p->hEvent;
179     }
180     WaitForMultipleObjects(dwHandleCount,   // count
181                            hHandles,        //
182                            FALSE,           // return if one object is signaled
183                            INFINITE         // no timeout
184                            );
185 }
186 
187 // pipex_send_eof()
188 
pipex_send_eof(pipex_t * pipex)189 void pipex_send_eof(pipex_t *pipex)  {
190     my_log("Adapter->%s: EOF\n",pipex->name);
191     CloseHandle(pipex->hOutput);
192 }
193 
194 // pipex_exit()
195 
196 /* This routine waits for kill_timeout milliseconds for
197  * the process to exit by itself. If that doesn't
198  * happen it will kill the process.
199  */
200 
pipex_exit(pipex_t * pipex,int kill_timeout)201 void pipex_exit(pipex_t *pipex, int kill_timeout) {
202     DWORD lpexit;
203     int elapsed_time;
204     bool exited;
205     CloseHandle(pipex->hInput);
206     CloseHandle(pipex->hOutput);
207     //	ExitProcess(pipex->hProcess,0);
208 
209     my_log("POLYGLOT Waiting for child process to exit.\n");
210     elapsed_time=0;
211     exited=FALSE;
212 
213     while(elapsed_time<kill_timeout){
214 	GetExitCodeProcess(pipex->hProcess,&lpexit);
215 	if(lpexit==STILL_ACTIVE){
216 	    my_log("POLYGLOT Child has not exited yet. Sleeping %dms.\n", WAIT_GRANULARITY);
217 	    my_sleep(WAIT_GRANULARITY);
218 	    elapsed_time+=WAIT_GRANULARITY;
219 	}else{
220 	    exited=TRUE;
221 	    break;
222 	}
223     }
224 
225     if(!exited){
226       my_log("POLYGLOT Child wouldn't exit by itself. Terminating it.\n");
227       TerminateProcess(pipex->hProcess,lpexit);
228     }
229     CloseHandle(pipex->hProcess);
230 
231     if(!pipex->quit_pending){
232       // suppress further errors
233       pipex->quit_pending=TRUE;
234       my_fatal("pipex_exit(): %s: child exited unexpectedly.\n",pipex->command);
235     }
236 
237 }
238 
239 // pipex_eof_input()
240 
pipex_eof_input(pipex_t * pipex)241 static bool pipex_eof_input(pipex_t *pipex){
242     int ret;
243     EnterCriticalSection(&(pipex->CriticalSection));
244     ret=(pipex->state)&PIPEX_EOF;
245     LeaveCriticalSection(&(pipex->CriticalSection));
246     return ret;
247 }
248 
249 // pipex_set_eof_input()
250 
pipex_set_eof_input(pipex_t * pipex)251 static void pipex_set_eof_input(pipex_t *pipex){
252     EnterCriticalSection(&(pipex->CriticalSection));
253     (pipex->state)|=PIPEX_EOF;
254     LeaveCriticalSection(&(pipex->CriticalSection));
255  }
256 
257 // pipex_active()
258 
259 /*
260  * This function returns TRUE if and only if the pipes have succesfully
261  * been created and the client has been started.
262  *
263  */
264 
pipex_active(pipex_t * pipex)265 bool pipex_active(pipex_t *pipex){
266     int ret;
267     EnterCriticalSection(&(pipex->CriticalSection));
268     ret=(pipex->state)&PIPEX_ACTIVE;
269     LeaveCriticalSection(&(pipex->CriticalSection));
270     return ret;
271 }
272 
273 // pipex_set_active()
274 
pipex_set_active(pipex_t * pipex)275 static void pipex_set_active(pipex_t *pipex){
276     EnterCriticalSection(&(pipex->CriticalSection));
277     (pipex->state)|=PIPEX_ACTIVE;
278     LeaveCriticalSection(&(pipex->CriticalSection));
279 }
280 
281 // pipex_read_data()
282 
pipex_read_data(pipex_t * pipex)283 static int pipex_read_data(pipex_t *pipex){
284     DWORD dwBytes;
285     char * ret;
286         // No protection. Access to nReadEnd is atomic.
287         // It is not a problem that nReadEnd becomes smaller after the call.
288         // This just means we have read less than we could have.
289     ret=fgets(pipex->lpReadBuffer,
290               LINE_INPUT_MAX_CHAR-(pipex->nReadEnd),
291               pipex->fpInput);
292     if(!ret){
293         pipex_set_eof_input(pipex);
294         (pipex->lpReadBuffer)[0]='\0';
295         return 0;
296     }
297     dwBytes=strlen(pipex->lpReadBuffer);
298     (pipex->lpReadBuffer)[dwBytes]='\0';
299     return dwBytes;
300 }
301 
302 // pipex_read_input()
303 
pipex_read_input(pipex_t * pipex)304 static void pipex_read_input(pipex_t *pipex) {
305   int ret;
306   BOOL bSetEvent=FALSE;
307       // ReadData is outside the critical section otherwise everything
308       // would block during the blocking read
309   ret=pipex_read_data(pipex);
310   EnterCriticalSection(&(pipex->CriticalSection));
311   if(!pipex_eof_input(pipex)){
312       if(ret+(pipex->nReadEnd)>=LINE_INPUT_MAX_CHAR){
313           my_fatal("pipex_read_input(): Internal error: buffer overflow\n");
314       }
315       memcpy((pipex->lpBuffer)+(pipex->nReadEnd),(pipex->lpReadBuffer),ret+1);
316       (pipex->nReadEnd) += ret;
317       if(!(pipex->lpFeedEnd)){
318           (pipex->lpFeedEnd) =
319               (char *) memchr(pipex->lpBuffer,'\n',pipex->nReadEnd);
320       }
321       if(pipex->lpFeedEnd){
322           bSetEvent=TRUE;
323       }else if((pipex->nReadEnd)>=LINE_INPUT_MAX_CHAR-1){
324           my_fatal("pipex_read_input(): LINE_INPUT_MAX_CHAR is equal to %d which is too small to contain a full line of engine output or GUI input.\n",LINE_INPUT_MAX_CHAR);
325       }
326   }
327   LeaveCriticalSection(&(pipex->CriticalSection));
328   if(pipex_eof_input(pipex) || bSetEvent){
329       SetEvent(pipex->hEvent);
330   }
331 }
332 
333 // pipex_eof()
334 
335 /*
336  * This function returns TRUE if and only if the input buffer does not
337  * contain a full line of data and EOF was encountered by
338  * the working thread.
339  *
340  */
341 
pipex_eof(pipex_t * pipex)342 bool pipex_eof(pipex_t *pipex){
343   int ret;
344   EnterCriticalSection(&(pipex->CriticalSection));
345   if((pipex->lpFeedEnd) != NULL){
346     ret=FALSE;
347   }else if(pipex_eof_input(pipex)){
348     ret=TRUE;
349   }else{
350     ret=FALSE;
351   }
352   LeaveCriticalSection(&(pipex->CriticalSection));
353   return ret;
354 }
355 
356 // pipex_readln_nb()
357 
358 /*
359  * This function returns FALSE if and only if the asynchronously filled
360  * input buffer does not contain a full line of data.
361  * In other words it operates in non-blocking mode.
362  *
363  */
364 
pipex_readln_nb(pipex_t * pipex,char * szLineStr)365 bool pipex_readln_nb(pipex_t *pipex, char *szLineStr) {
366   int nFeedEnd;
367   int ret;
368   int src, dst;
369   char c;
370   EnterCriticalSection(&(pipex->CriticalSection));
371   if ((pipex->lpFeedEnd) == NULL) {
372     ret=FALSE;
373   } else {
374     nFeedEnd = pipex->lpFeedEnd - pipex->lpBuffer;
375     memcpy(szLineStr, pipex->lpBuffer, nFeedEnd+1);
376     szLineStr[nFeedEnd] = '\0';
377 
378         // temp hack: stolen from util.c
379         // remove CRs and LFs
380     src = 0;
381     dst = 0;
382     while ((c=szLineStr[src++]) != '\0') {
383         if (c != '\r' && c != '\n') szLineStr[dst++] = c;
384     }
385     szLineStr[dst] = '\0';
386     ASSERT(strchr(szLineStr,'\n')==NULL)
387     ASSERT(strchr(szLineStr,'\r')==NULL)
388 
389     nFeedEnd ++;
390     pipex->nReadEnd -= nFeedEnd;
391     memcpy(pipex->lpBuffer, pipex->lpBuffer + nFeedEnd, pipex->nReadEnd+1);
392     pipex->lpFeedEnd =
393         (char *) memchr(pipex->lpBuffer, '\n', pipex->nReadEnd);
394     ret=TRUE;
395   }
396   LeaveCriticalSection(&(pipex->CriticalSection));
397   if(ret){
398       my_log("%s->Adapter: %s\n",pipex->name,szLineStr);
399   }
400   return ret;
401 }
402 
403 // pipex_readln()
404 
405 /*
406  * This function returns FALSE if and only if EOF has been encountered by
407  * the working thread and no full line of data is present in the input buffer.
408  *
409  * If there is a full line of data present in the input buffer it returns
410  * TRUE.
411  *
412  * If none of these conditions is satisfied it blocks.
413  *
414  * As the name say this function is strictly for line input.
415  * An incomplete line of data (i.e. not ending with \n) is lost when EOF
416  * is encountered.
417  *
418  */
419 
pipex_readln(pipex_t * pipex,char * szLineStr)420 bool pipex_readln(pipex_t *pipex, char *szLineStr) {
421   while(!pipex_eof(pipex)){
422       if (pipex_readln_nb(pipex,szLineStr)) {
423           return TRUE;
424       }else{
425           WaitForSingleObject(pipex->hEvent,INFINITE);
426       }
427   }
428   my_log("%s->Adapter: EOF\n",pipex->name);
429   szLineStr[0]='\0';
430   return FALSE;
431 }
432 
433 //  GetWin32Priority()
434 
GetWin32Priority(int nice)435 static DWORD GetWin32Priority(int nice)
436 {
437 /*
438 REALTIME_PRIORITY_CLASS     0x00000100
439 HIGH_PRIORITY_CLASS         0x00000080
440 ABOVE_NORMAL_PRIORITY_CLASS 0x00008000
441 NORMAL_PRIORITY_CLASS       0x00000020
442 BELOW_NORMAL_PRIORITY_CLASS 0x00004000
443 IDLE_PRIORITY_CLASS         0x00000040
444 */
445 	if (nice < -15) return 0x00000080;
446 	if (nice < 0)   return 0x00008000;
447 	if (nice == 0)  return 0x00000020;
448 	if (nice < 15)  return 0x00004000;
449 	return 0x00000040;
450 }
451 
452 // pipex_set_priority()
453 
pipex_set_priority(pipex_t * pipex,int value)454 void pipex_set_priority(pipex_t *pipex, int value){
455     if(pipex->hProcess){
456         if(!SetPriorityClass(pipex->hProcess,
457                              GetWin32Priority(value))){
458             my_log("POLYGLOT Unable to change priority\n");
459         }
460     }
461 }
462 
463 // pipex_set_affinit()
464 
465 typedef void (WINAPI *SPAM)(HANDLE, int);
pipex_set_affinity(pipex_t * pipex,int value)466 void pipex_set_affinity(pipex_t *pipex, int value){
467     SPAM pSPAM;
468 
469     if(pipex->hProcess) return;
470     if(value==-1) return;
471 
472     pSPAM = (SPAM) GetProcAddress(
473         GetModuleHandle(TEXT("kernel32.dll")),
474         "SetProcessAffinityMask");
475     if(NULL != pSPAM){
476             // [HGM] avoid crash on Win95 by first checking if API call exists
477         pSPAM(pipex->hProcess,value);
478     }else{
479         my_log("POLYGLOT API call \"SetProcessAffinityMask\" not available\n");
480     }
481 }
482 
483 // pipex_write()
484 
pipex_write(pipex_t * pipex,const char * szLineStr)485 void pipex_write(pipex_t *pipex, const char *szLineStr) {
486     int size,written;
487     size=sizeof(pipex->szWriteBuffer)-pipex->dwWriteIndex;
488     written=snprintf(pipex->szWriteBuffer + pipex->dwWriteIndex,
489                      size,
490                      "%s",
491                      szLineStr);
492         // snprintf returns how many bytes should have been written
493         // (not including the trailing zero)
494         // old versions of glibc and msvcrt return -1 in
495         // case of truncated output.
496     if(written>=size || written<0){
497         my_fatal("engine_send(): write_buffer overflow\n");
498     }
499     pipex->dwWriteIndex+=written;
500 
501 }
502 
503 
504 // pipex_writeln()
505 
pipex_writeln(pipex_t * pipex,const char * szLineStr)506 void pipex_writeln(pipex_t *pipex, const char *szLineStr) {
507   DWORD dwBytes;
508   DWORD dwLengthWriteBuffer;
509   pipex_write(pipex, szLineStr);
510   my_log("Adapter->%s: %s\n",pipex->name,pipex->szWriteBuffer);
511   if(pipex->bPipe){
512       dwLengthWriteBuffer = strlen(pipex->szWriteBuffer);
513       if(dwLengthWriteBuffer>=sizeof(pipex->szWriteBuffer)-3){
514           my_fatal("pipex_writeln(): write buffer overflow\n");
515       }
516       pipex->szWriteBuffer[dwLengthWriteBuffer] = '\r';
517       pipex->szWriteBuffer[dwLengthWriteBuffer + 1] = '\n';
518           // for easy debugging
519       pipex->szWriteBuffer[dwLengthWriteBuffer + 2] = '\0';
520       WriteFile(pipex->hOutput, pipex->szWriteBuffer,
521                 dwLengthWriteBuffer + 2,
522                 &dwBytes, NULL);
523   }else{
524       printf("%s\n",pipex->szWriteBuffer);
525       fflush(stdout);
526   }
527   pipex->dwWriteIndex = 0;
528 }
529 #endif
530