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