version 1.4.35b
[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         // not quit the right place
223     my_log("%s->Adapter: EOF\n",pipex->name);
224
225 }
226
227 // pipex_active()
228
229 /*
230  * This function returns TRUE if and only if the pipes have succesfully
231  * been created and the client has been started.
232  *
233  */
234
235 bool pipex_active(pipex_t *pipex){
236     int ret;
237     EnterCriticalSection(&(pipex->CriticalSection));
238     ret=(pipex->state)&PIPEX_ACTIVE;
239     LeaveCriticalSection(&(pipex->CriticalSection));
240     return ret;
241 }
242
243 // pipex_set_active()
244
245 static void pipex_set_active(pipex_t *pipex){
246     EnterCriticalSection(&(pipex->CriticalSection));
247     (pipex->state)|=PIPEX_ACTIVE;
248     LeaveCriticalSection(&(pipex->CriticalSection));
249 }
250
251 // pipex_read_data()
252
253 static int pipex_read_data(pipex_t *pipex){
254     DWORD dwBytes;
255     char * ret;
256         // No protection. Access to nReadEnd is atomic.
257         // It is not a problem that nReadEnd becomes smaller after the call.
258         // This just means we have read less than we could have. 
259     ret=fgets(pipex->lpReadBuffer,
260               LINE_INPUT_MAX_CHAR-(pipex->nReadEnd),
261               pipex->fpInput);
262     if(!ret){
263         pipex_set_eof_input(pipex);
264         (pipex->lpReadBuffer)[0]='\0';
265         return 0;
266     }
267     dwBytes=strlen(pipex->lpReadBuffer);
268     (pipex->lpReadBuffer)[dwBytes]='\0';
269     return dwBytes;
270 }
271
272 // pipex_read_input()
273
274 static void pipex_read_input(pipex_t *pipex) {
275   int ret;
276   BOOL bSetEvent=FALSE;
277       // ReadData is outside the critical section otherwise everything
278       // would block during the blocking read
279   ret=pipex_read_data(pipex);
280   EnterCriticalSection(&(pipex->CriticalSection));
281   if(!pipex_eof_input(pipex)){
282       if(ret+(pipex->nReadEnd)>=LINE_INPUT_MAX_CHAR){
283           my_fatal("pipex_read_input(): Internal error: buffer overflow\n");
284       }
285       memcpy((pipex->lpBuffer)+(pipex->nReadEnd),(pipex->lpReadBuffer),ret+1);
286       (pipex->nReadEnd) += ret;
287       if(!(pipex->lpFeedEnd)){
288           (pipex->lpFeedEnd) =
289               (char *) memchr(pipex->lpBuffer,'\n',pipex->nReadEnd);
290       }
291       if(pipex->lpFeedEnd){
292           bSetEvent=TRUE;
293       }else if((pipex->nReadEnd)>=LINE_INPUT_MAX_CHAR-1){
294           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);
295       }
296   }
297   LeaveCriticalSection(&(pipex->CriticalSection));
298   if(pipex_eof_input(pipex) || bSetEvent){
299       SetEvent(pipex->hEvent);
300   }
301 }
302
303 // pipex_eof()
304
305 /*
306  * This function returns TRUE if and only if the input buffer does not
307  * contain a full line of data and EOF was encountered by
308  * the working thread.
309  *
310  */
311
312 bool pipex_eof(pipex_t *pipex){
313   int ret;
314   EnterCriticalSection(&(pipex->CriticalSection));
315   if((pipex->lpFeedEnd) != NULL){
316     ret=FALSE;
317   }else if(pipex_eof_input(pipex)){
318     ret=TRUE;
319   }else{
320     ret=FALSE;
321   }
322   LeaveCriticalSection(&(pipex->CriticalSection));
323   return ret;
324 }
325
326 // pipex_readln_nb()
327
328 /*
329  * This function returns FALSE if and only if the asynchronously filled
330  * input buffer does not contain a full line of data.
331  * In other words it operates in non-blocking mode.
332  *
333  */
334
335 bool pipex_readln_nb(pipex_t *pipex, char *szLineStr) {
336   int nFeedEnd;
337   int ret;
338   int src, dst;
339   char c;
340   EnterCriticalSection(&(pipex->CriticalSection));
341   if ((pipex->lpFeedEnd) == NULL) {
342     ret=FALSE;
343   } else {
344     nFeedEnd = pipex->lpFeedEnd - pipex->lpBuffer;
345     memcpy(szLineStr, pipex->lpBuffer, nFeedEnd+1);
346     szLineStr[nFeedEnd] = '\0';
347     
348         // temp hack: stolen from util.c
349         // remove CRs and LFs
350     src = 0;
351     dst = 0;
352     while ((c=szLineStr[src++]) != '\0') {
353         if (c != '\r' && c != '\n') szLineStr[dst++] = c;
354     }
355     szLineStr[dst] = '\0';    
356     ASSERT(strchr(szLineStr,'\n')==NULL)
357     ASSERT(strchr(szLineStr,'\r')==NULL)
358         
359     nFeedEnd ++;
360     pipex->nReadEnd -= nFeedEnd;
361     memcpy(pipex->lpBuffer, pipex->lpBuffer + nFeedEnd, pipex->nReadEnd+1);
362     pipex->lpFeedEnd =
363         (char *) memchr(pipex->lpBuffer, '\n', pipex->nReadEnd);
364     ret=TRUE;
365   }
366   LeaveCriticalSection(&(pipex->CriticalSection));
367   if(ret){
368       my_log("%s->Adapter: %s\n",pipex->name,szLineStr);
369   }
370   return ret;
371 }
372
373 // pipex_readln()
374
375 /*
376  * This function returns FALSE if and only if EOF has been encountered by 
377  * the working thread and no full line of data is present in the input buffer.
378  *
379  * If there is a full line of data present in the input buffer it returns 
380  * TRUE. 
381  *
382  * If none of these conditions is satisfied it blocks.
383  *
384  * As the name say this function is strictly for line input. 
385  * An incomplete line of data (i.e. not ending with \n) is lost when EOF
386  * is encountered.
387  *
388  */
389
390 bool pipex_readln(pipex_t *pipex, char *szLineStr) {
391   while(!pipex_eof(pipex)){
392       if (pipex_readln_nb(pipex,szLineStr)) {
393           return TRUE;
394       }else{
395           WaitForSingleObject(pipex->hEvent,INFINITE);
396       }
397   }
398   szLineStr[0]='\0';
399   return FALSE;
400 }
401
402 //  GetWin32Priority()
403
404 static DWORD GetWin32Priority(int nice)
405 {
406 /*
407 REALTIME_PRIORITY_CLASS     0x00000100
408 HIGH_PRIORITY_CLASS         0x00000080
409 ABOVE_NORMAL_PRIORITY_CLASS 0x00008000
410 NORMAL_PRIORITY_CLASS       0x00000020
411 BELOW_NORMAL_PRIORITY_CLASS 0x00004000
412 IDLE_PRIORITY_CLASS         0x00000040
413 */
414         if (nice < -15) return 0x00000080;
415         if (nice < 0)   return 0x00008000;
416         if (nice == 0)  return 0x00000020;
417         if (nice < 15)  return 0x00004000;
418         return 0x00000040;
419 }
420
421 // pipex_set_priority()
422
423 void pipex_set_priority(pipex_t *pipex, int value){
424     if(pipex->hProcess){
425         if(!SetPriorityClass(pipex->hProcess,
426                              GetWin32Priority(value))){
427             my_log("POLYGLOT Unable to change priority\n");
428         }
429     }
430 }
431
432 // pipex_set_affinit()
433
434 void pipex_set_affinity(pipex_t *pipex, int value){
435     if(pipex->hProcess) return;
436     if(value==-1) return;
437
438     typedef void (WINAPI *SPAM)(HANDLE, int);
439     SPAM pSPAM;
440     pSPAM = (SPAM) GetProcAddress(
441         GetModuleHandle(TEXT("kernel32.dll")), 
442         "SetProcessAffinityMask");
443     if(NULL != pSPAM){
444             // [HGM] avoid crash on Win95 by first checking if API call exists
445         pSPAM(pipex->hProcess,value);
446     }else{
447         my_log("POLYGLOT API call \"SetProcessAffinityMask\" not available\n");
448     }
449 }
450
451 // pipex_write()
452
453 void pipex_write(pipex_t *pipex, const char *szLineStr) {
454     int size,written;
455     size=sizeof(pipex->szWriteBuffer)-pipex->dwWriteIndex;
456     written=snprintf(pipex->szWriteBuffer + pipex->dwWriteIndex,
457                      size,
458                      "%s",
459                      szLineStr);
460         // snprintf returns how many bytes should have been written
461         // (not including the trailing zero)
462         // old versions of glibc and msvcrt return -1 in
463         // case of truncated output.
464     if(written>=size || written<0){
465         my_fatal("engine_send(): write_buffer overflow\n");
466     }
467     pipex->dwWriteIndex+=written;
468     
469 }
470
471
472 // pipex_writeln()
473
474 void pipex_writeln(pipex_t *pipex, const char *szLineStr) {
475   DWORD dwBytes;
476   DWORD dwLengthWriteBuffer;
477   my_log("Adapter->%s: %s\n",pipex->name,pipex->szWriteBuffer);
478   pipex_write(pipex, szLineStr);
479   if(pipex->bPipe){
480       dwLengthWriteBuffer = strlen(pipex->szWriteBuffer);
481       if(dwLengthWriteBuffer>=sizeof(pipex->szWriteBuffer)-3){
482           my_fatal("pipex_writeln(): write buffer overflow\n");
483       }
484       pipex->szWriteBuffer[dwLengthWriteBuffer] = '\r';
485       pipex->szWriteBuffer[dwLengthWriteBuffer + 1] = '\n';
486           // for easy debugging
487       pipex->szWriteBuffer[dwLengthWriteBuffer + 2] = '\0';  
488       WriteFile(pipex->hOutput, pipex->szWriteBuffer,
489                 dwLengthWriteBuffer + 2,
490                 &dwBytes, NULL);
491   }else{
492       printf("%s\n",pipex->szWriteBuffer);
493       fflush(stdout);
494   }
495   pipex->dwWriteIndex = 0;
496 }
497 #endif