verson 1.4.54b
[polyglot.git] / pipex_win32.c
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
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
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
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(): cannot change directory: %s\n",
102                      strerror(errno));
103         }
104        if(CreateProcess(NULL,
105                          (LPSTR) szProcFile,
106                          NULL,
107                          NULL,
108                          TRUE,
109                          DETACHED_PROCESS | CREATE_NEW_PROCESS_GROUP,
110                          NULL,
111                          NULL,
112                          &si,
113                          &pi)){
114             pipex->hProcess=pi.hProcess;
115             CloseHandle(pi.hThread);
116             CloseHandle(hStdinRead);
117             CloseHandle(hStdoutWrite);
118             pipex->hInput = hStdoutRead;
119             pipex->hOutput = hStdinWrite;
120             pipex->bConsole = FALSE;
121             pipex->bPipe=TRUE;
122         }else{
123           my_fatal("pipex_open(): %s: %s",szProcFile,win32_error());
124         }
125         _chdir(szCurrentDir);
126     }
127     if (pipex->bConsole) {
128         SetConsoleMode(pipex->hInput,
129                        dwMode & ~(ENABLE_MOUSE_INPUT | ENABLE_WINDOW_INPUT));
130         FlushConsoleInputBuffer(pipex->hInput);
131     } 
132     fdInput=_open_osfhandle((long) pipex->hInput,_O_RDONLY);
133     if(fdInput==-1){
134         my_fatal("pipex_open(): %s",strerror(errno));
135     }
136     pipex->fpInput=fdopen(fdInput,"r");
137     if(pipex->fpInput==NULL){
138         my_fatal("pipex_open(): %s",strerror(errno));
139     }
140     pipex->nReadEnd = 0;
141     pipex->lpFeedEnd = NULL;
142     InitializeCriticalSection(&(pipex->CriticalSection));
143     pipex->hEvent=CreateEvent(NULL,           // default security
144                        FALSE,          // auto reset
145                        FALSE,          // not signaled
146                        NULL            // nameless
147                        );
148     if(!(pipex->hEvent)){
149         my_fatal("pipex_open(): %s",win32_error());
150     }
151     hThread=CreateThread(NULL,         // default security
152                          0,            // default stacksize
153                          ThreadProc,   // worker function
154                          pipex,        // tell worker about ourselves
155                          0,            // run immediately
156                          &dwThreadId   // dropped, but needed for the call to work in Win9x
157                          );          
158     if(!hThread){
159         my_fatal("pipex_open(): %s",win32_error());
160     }
161     pipex->dwWriteIndex=0;
162     pipex_set_active(pipex);
163 }
164
165
166 // pipex_wait_event(pipex)
167
168 void pipex_wait_event(pipex_t *pipex[]){
169     HANDLE hHandles[dwMaxHandles]; 
170     DWORD dwHandleCount=0;
171     pipex_t *p;
172     while((p=pipex[dwHandleCount])!=NULL){
173         ASSERT((p->hEvent)!=0);
174         if(dwHandleCount>=dwMaxHandles){
175             my_fatal("pipex_wait_event(): Too many objects to wait for");
176         }
177         hHandles[dwHandleCount++]=p->hEvent;
178     }
179     WaitForMultipleObjects(dwHandleCount,   // count
180                            hHandles,        //
181                            FALSE,           // return if one object is signaled
182                            INFINITE         // no timeout
183                            );
184 }
185
186 // pipex_send_eof()
187
188 void pipex_send_eof(pipex_t *pipex)  {
189     my_log("Adapter->%s: EOF\n",pipex->name);
190     CloseHandle(pipex->hOutput);
191 }
192
193 // pipex_exit()
194
195 void pipex_exit(pipex_t *pipex) {
196     DWORD lpexit;
197     CloseHandle(pipex->hInput);
198     CloseHandle(pipex->hOutput);
199     if(!pipex->quit_pending){
200       // suppress further errors
201       pipex->quit_pending=TRUE;
202       my_fatal("pipex_exit(): %s: child exited unexpectedly.\n",pipex->command);
203     }
204     if(GetExitCodeProcess(pipex->hProcess,&lpexit)){
205         if(lpexit==STILL_ACTIVE)
206                 //must be java,hammer it down!
207             TerminateProcess(pipex->hProcess,lpexit);
208     }
209         CloseHandle(pipex->hProcess);
210 }
211
212 // pipex_eof_input()
213
214 static bool pipex_eof_input(pipex_t *pipex){ 
215     int ret;
216     EnterCriticalSection(&(pipex->CriticalSection));
217     ret=(pipex->state)&PIPEX_EOF;
218     LeaveCriticalSection(&(pipex->CriticalSection));
219     return ret;
220 }
221
222 // pipex_set_eof_input()
223
224 static void pipex_set_eof_input(pipex_t *pipex){
225     EnterCriticalSection(&(pipex->CriticalSection));
226     (pipex->state)|=PIPEX_EOF;
227     LeaveCriticalSection(&(pipex->CriticalSection));
228  }
229
230 // pipex_active()
231
232 /*
233  * This function returns TRUE if and only if the pipes have succesfully
234  * been created and the client has been started.
235  *
236  */
237
238 bool pipex_active(pipex_t *pipex){
239     int ret;
240     EnterCriticalSection(&(pipex->CriticalSection));
241     ret=(pipex->state)&PIPEX_ACTIVE;
242     LeaveCriticalSection(&(pipex->CriticalSection));
243     return ret;
244 }
245
246 // pipex_set_active()
247
248 static void pipex_set_active(pipex_t *pipex){
249     EnterCriticalSection(&(pipex->CriticalSection));
250     (pipex->state)|=PIPEX_ACTIVE;
251     LeaveCriticalSection(&(pipex->CriticalSection));
252 }
253
254 // pipex_read_data()
255
256 static int pipex_read_data(pipex_t *pipex){
257     DWORD dwBytes;
258     char * ret;
259         // No protection. Access to nReadEnd is atomic.
260         // It is not a problem that nReadEnd becomes smaller after the call.
261         // This just means we have read less than we could have. 
262     ret=fgets(pipex->lpReadBuffer,
263               LINE_INPUT_MAX_CHAR-(pipex->nReadEnd),
264               pipex->fpInput);
265     if(!ret){
266         pipex_set_eof_input(pipex);
267         (pipex->lpReadBuffer)[0]='\0';
268         return 0;
269     }
270     dwBytes=strlen(pipex->lpReadBuffer);
271     (pipex->lpReadBuffer)[dwBytes]='\0';
272     return dwBytes;
273 }
274
275 // pipex_read_input()
276
277 static void pipex_read_input(pipex_t *pipex) {
278   int ret;
279   BOOL bSetEvent=FALSE;
280       // ReadData is outside the critical section otherwise everything
281       // would block during the blocking read
282   ret=pipex_read_data(pipex);
283   EnterCriticalSection(&(pipex->CriticalSection));
284   if(!pipex_eof_input(pipex)){
285       if(ret+(pipex->nReadEnd)>=LINE_INPUT_MAX_CHAR){
286           my_fatal("pipex_read_input(): Internal error: buffer overflow\n");
287       }
288       memcpy((pipex->lpBuffer)+(pipex->nReadEnd),(pipex->lpReadBuffer),ret+1);
289       (pipex->nReadEnd) += ret;
290       if(!(pipex->lpFeedEnd)){
291           (pipex->lpFeedEnd) =
292               (char *) memchr(pipex->lpBuffer,'\n',pipex->nReadEnd);
293       }
294       if(pipex->lpFeedEnd){
295           bSetEvent=TRUE;
296       }else if((pipex->nReadEnd)>=LINE_INPUT_MAX_CHAR-1){
297           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);
298       }
299   }
300   LeaveCriticalSection(&(pipex->CriticalSection));
301   if(pipex_eof_input(pipex) || bSetEvent){
302       SetEvent(pipex->hEvent);
303   }
304 }
305
306 // pipex_eof()
307
308 /*
309  * This function returns TRUE if and only if the input buffer does not
310  * contain a full line of data and EOF was encountered by
311  * the working thread.
312  *
313  */
314
315 bool pipex_eof(pipex_t *pipex){
316   int ret;
317   EnterCriticalSection(&(pipex->CriticalSection));
318   if((pipex->lpFeedEnd) != NULL){
319     ret=FALSE;
320   }else if(pipex_eof_input(pipex)){
321     ret=TRUE;
322   }else{
323     ret=FALSE;
324   }
325   LeaveCriticalSection(&(pipex->CriticalSection));
326   return ret;
327 }
328
329 // pipex_readln_nb()
330
331 /*
332  * This function returns FALSE if and only if the asynchronously filled
333  * input buffer does not contain a full line of data.
334  * In other words it operates in non-blocking mode.
335  *
336  */
337
338 bool pipex_readln_nb(pipex_t *pipex, char *szLineStr) {
339   int nFeedEnd;
340   int ret;
341   int src, dst;
342   char c;
343   EnterCriticalSection(&(pipex->CriticalSection));
344   if ((pipex->lpFeedEnd) == NULL) {
345     ret=FALSE;
346   } else {
347     nFeedEnd = pipex->lpFeedEnd - pipex->lpBuffer;
348     memcpy(szLineStr, pipex->lpBuffer, nFeedEnd+1);
349     szLineStr[nFeedEnd] = '\0';
350     
351         // temp hack: stolen from util.c
352         // remove CRs and LFs
353     src = 0;
354     dst = 0;
355     while ((c=szLineStr[src++]) != '\0') {
356         if (c != '\r' && c != '\n') szLineStr[dst++] = c;
357     }
358     szLineStr[dst] = '\0';    
359     ASSERT(strchr(szLineStr,'\n')==NULL)
360     ASSERT(strchr(szLineStr,'\r')==NULL)
361         
362     nFeedEnd ++;
363     pipex->nReadEnd -= nFeedEnd;
364     memcpy(pipex->lpBuffer, pipex->lpBuffer + nFeedEnd, pipex->nReadEnd+1);
365     pipex->lpFeedEnd =
366         (char *) memchr(pipex->lpBuffer, '\n', pipex->nReadEnd);
367     ret=TRUE;
368   }
369   LeaveCriticalSection(&(pipex->CriticalSection));
370   if(ret){
371       my_log("%s->Adapter: %s\n",pipex->name,szLineStr);
372   }
373   return ret;
374 }
375
376 // pipex_readln()
377
378 /*
379  * This function returns FALSE if and only if EOF has been encountered by 
380  * the working thread and no full line of data is present in the input buffer.
381  *
382  * If there is a full line of data present in the input buffer it returns 
383  * TRUE. 
384  *
385  * If none of these conditions is satisfied it blocks.
386  *
387  * As the name say this function is strictly for line input. 
388  * An incomplete line of data (i.e. not ending with \n) is lost when EOF
389  * is encountered.
390  *
391  */
392
393 bool pipex_readln(pipex_t *pipex, char *szLineStr) {
394   while(!pipex_eof(pipex)){
395       if (pipex_readln_nb(pipex,szLineStr)) {
396           return TRUE;
397       }else{
398           WaitForSingleObject(pipex->hEvent,INFINITE);
399       }
400   }
401   my_log("%s->Adapter: EOF\n",pipex->name);
402   szLineStr[0]='\0';
403   return FALSE;
404 }
405
406 //  GetWin32Priority()
407
408 static DWORD GetWin32Priority(int nice)
409 {
410 /*
411 REALTIME_PRIORITY_CLASS     0x00000100
412 HIGH_PRIORITY_CLASS         0x00000080
413 ABOVE_NORMAL_PRIORITY_CLASS 0x00008000
414 NORMAL_PRIORITY_CLASS       0x00000020
415 BELOW_NORMAL_PRIORITY_CLASS 0x00004000
416 IDLE_PRIORITY_CLASS         0x00000040
417 */
418         if (nice < -15) return 0x00000080;
419         if (nice < 0)   return 0x00008000;
420         if (nice == 0)  return 0x00000020;
421         if (nice < 15)  return 0x00004000;
422         return 0x00000040;
423 }
424
425 // pipex_set_priority()
426
427 void pipex_set_priority(pipex_t *pipex, int value){
428     if(pipex->hProcess){
429         if(!SetPriorityClass(pipex->hProcess,
430                              GetWin32Priority(value))){
431             my_log("POLYGLOT Unable to change priority\n");
432         }
433     }
434 }
435
436 // pipex_set_affinit()
437
438 typedef void (WINAPI *SPAM)(HANDLE, int);
439 void pipex_set_affinity(pipex_t *pipex, int value){
440     SPAM pSPAM;
441
442     if(pipex->hProcess) return;
443     if(value==-1) return;
444
445     pSPAM = (SPAM) GetProcAddress(
446         GetModuleHandle(TEXT("kernel32.dll")), 
447         "SetProcessAffinityMask");
448     if(NULL != pSPAM){
449             // [HGM] avoid crash on Win95 by first checking if API call exists
450         pSPAM(pipex->hProcess,value);
451     }else{
452         my_log("POLYGLOT API call \"SetProcessAffinityMask\" not available\n");
453     }
454 }
455
456 // pipex_write()
457
458 void pipex_write(pipex_t *pipex, const char *szLineStr) {
459     int size,written;
460     size=sizeof(pipex->szWriteBuffer)-pipex->dwWriteIndex;
461     written=snprintf(pipex->szWriteBuffer + pipex->dwWriteIndex,
462                      size,
463                      "%s",
464                      szLineStr);
465         // snprintf returns how many bytes should have been written
466         // (not including the trailing zero)
467         // old versions of glibc and msvcrt return -1 in
468         // case of truncated output.
469     if(written>=size || written<0){
470         my_fatal("engine_send(): write_buffer overflow\n");
471     }
472     pipex->dwWriteIndex+=written;
473     
474 }
475
476
477 // pipex_writeln()
478
479 void pipex_writeln(pipex_t *pipex, const char *szLineStr) {
480   DWORD dwBytes;
481   DWORD dwLengthWriteBuffer;
482   pipex_write(pipex, szLineStr);
483   my_log("Adapter->%s: %s\n",pipex->name,pipex->szWriteBuffer);
484   if(pipex->bPipe){
485       dwLengthWriteBuffer = strlen(pipex->szWriteBuffer);
486       if(dwLengthWriteBuffer>=sizeof(pipex->szWriteBuffer)-3){
487           my_fatal("pipex_writeln(): write buffer overflow\n");
488       }
489       pipex->szWriteBuffer[dwLengthWriteBuffer] = '\r';
490       pipex->szWriteBuffer[dwLengthWriteBuffer + 1] = '\n';
491           // for easy debugging
492       pipex->szWriteBuffer[dwLengthWriteBuffer + 2] = '\0';  
493       WriteFile(pipex->hOutput, pipex->szWriteBuffer,
494                 dwLengthWriteBuffer + 2,
495                 &dwBytes, NULL);
496   }else{
497       printf("%s\n",pipex->szWriteBuffer);
498       fflush(stdout);
499   }
500   pipex->dwWriteIndex = 0;
501 }
502 #endif