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