Fix zombie chess engine processes.
[cboard.git] / src / engine.c
blob2045cd2153a96ccde214cff5ca7dfed656337e05
1 /* vim:tw=78:ts=8:sw=4:set ft=c: */
2 /*
3 Copyright (C) 2002-2006 Ben Kibbey <bjk@luxsci.net>
5 This program is free software; you can redistribute it and/or modify
6 it under the terms of the GNU General Public License as published by
7 the Free Software Foundation; either version 2 of the License, or
8 (at your option) any later version.
10 This program is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 GNU General Public License for more details.
15 You should have received a copy of the GNU General Public License
16 along with this program; if not, write to the Free Software
17 Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
19 #include <stdio.h>
20 #include <stdlib.h>
21 #include <unistd.h>
22 #include <err.h>
23 #include <limits.h>
24 #include <string.h>
25 #include <sys/types.h>
26 #include <signal.h>
27 #include <sys/stat.h>
28 #include <sys/time.h>
29 #include <fcntl.h>
30 #include <ctype.h>
31 #include <stdarg.h>
32 #include <errno.h>
33 #include <paths.h>
35 #ifdef HAVE_CONFIG_H
36 #include <config.h>
37 #endif
39 #include "chess.h"
40 #include "conf.h"
41 #include "misc.h"
42 #include "strings.h"
43 #include "window.h"
44 #include "common.h"
45 #include "engine.h"
47 #ifdef WITH_DMALLOC
48 #include <dmalloc.h>
49 #endif
51 void send_to_engine(GAME *g, const char *format, ...)
53 va_list ap;
54 int len;
55 char *line;
56 int try = 0;
57 struct userdata_s *d = g->data;
59 if (!d->engine || d->engine->status == ENGINE_OFFLINE)
60 return;
62 d->engine->status = ENGINE_THINKING;
63 update_status_window(*g);
64 refresh_all();
66 va_start(ap, format);
67 #ifdef HAVE_VASPRINTF
68 len = vasprintf(&line, format, ap);
69 #else
70 line = Malloc(LINE_MAX);
71 len = vsnprintf(line, LINE_MAX, format, ap);
72 #endif
73 va_end(ap);
75 while (1) {
76 int n;
77 fd_set fds;
78 struct timeval tv;
80 FD_ZERO(&fds);
81 FD_SET(d->engine->fd[ENGINE_OUT_FD], &fds);
83 tv.tv_sec = 0;
84 tv.tv_usec = 0;
86 if ((n = select(d->engine->fd[ENGINE_OUT_FD] + 1, NULL, &fds, NULL,
87 &tv)) > 0) {
88 if (FD_ISSET(d->engine->fd[ENGINE_OUT_FD], &fds)) {
89 n = write(d->engine->fd[ENGINE_OUT_FD], line, len);
91 if (n == -1) {
92 if (errno == EAGAIN)
93 continue;
95 if (kill(d->engine->pid, 0) == -1) {
96 message(ERROR, ANYKEY, "Could not write to engine. "
97 "Process no longer exists.");
98 d->engine->status = ENGINE_OFFLINE;
99 //update_status_window(NULL);
100 return;
103 message(ERROR, ANYKEY, "Attempt #%i. write(): %s", ++try,
104 strerror(errno));
105 continue;
108 if (len != n) {
109 message(NULL, ANYKEY, "try #%i: write() error to engine. "
110 "Expected %i, got %i.", ++try, len, n);
111 continue;
114 break;
117 else {
118 /* timeout */
122 free(line);
125 #ifndef UNIX98
126 /* From the Xboard and screen packages. */
127 static int get_pty(char *pty_name)
129 int i;
130 int fd;
132 for (i = 0; PTY_MAJOR[i]; i++) {
133 int n;
135 for (n = 0; PTY_MINOR[n]; n++) {
136 sprintf(pty_name, "%spty%c%c", _PATH_DEV, PTY_MAJOR[i],
137 PTY_MINOR[n]);
139 if ((fd = open(pty_name, O_RDWR | O_NOCTTY)) == -1)
140 continue;
142 sprintf(pty_name, "%stty%c%c", _PATH_DEV, PTY_MAJOR[i],
143 PTY_MINOR[n]);
145 if (access(pty_name, R_OK | W_OK) == -1) {
146 close(fd);
147 continue;
150 return fd;
154 return -1;
156 #endif
158 static char **parseargs(char *str)
160 char **pptr, *s;
161 char arg[255];
162 int n = 0;
163 int quote = 0;
164 int lastchar = 0;
165 int i;
167 if (!str)
168 return NULL;
170 if (!(pptr = malloc(sizeof(char *))))
171 return NULL;
173 for (i = 0, s = str; *s; lastchar = *s++) {
174 if ((*s == '\"' || *s == '\'') && lastchar != '\\') {
175 quote = (quote) ? 0 : 1;
176 continue;
179 if (*s == ' ' && !quote) {
180 arg[i] = 0;
181 pptr = realloc(pptr, (n + 2) * sizeof(char *));
182 pptr[n++] = strdup(arg);
183 arg[0] = i = 0;
184 continue;
187 if ((i + 1) == sizeof(arg))
188 continue;
190 arg[i++] = *s;
193 arg[i] = 0;
195 if (arg[0]) {
196 pptr = realloc(pptr, (n + 2) * sizeof(char *));
197 pptr[n++] = strdup(arg);
200 pptr[n] = NULL;
201 return pptr;
204 /* Is this dangerous if pty permissions are wrong? */
205 static pid_t init_chess_engine(GAME *g, char **args)
207 pid_t pid;
208 int from[2], to[2];
209 struct userdata_s *d = g->data;
210 #ifndef UNIX98
211 char pty[FILENAME_MAX];
213 if ((to[1] = get_pty(pty)) == -1) {
214 errno = 0;
215 return -1;
217 #else
218 if ((to[1] = open("/dev/ptmx", O_RDWR | O_NOCTTY)) == -1) {
219 return -1;
222 if (grantpt(to[1]) == -1) {
223 return -1;
226 if (unlockpt(to[1]) == -1) {
227 return -1;
229 #endif
231 from[0] = to[1];
232 errno = 0;
234 #ifndef UNIX98
235 if ((to[0] = open(pty, O_RDWR, 0)) == -1)
236 #else
237 if ((to[0] = open(ptsname(to[1]), O_RDWR, 0)) == -1)
238 #endif
239 return -1;
241 from[1] = to[0];
242 errno = 0;
244 switch ((pid = fork())) {
245 case -1:
246 return -2;
247 case 0:
248 dup2(to[0], STDIN_FILENO);
249 dup2(from[1], STDOUT_FILENO);
250 close(to[0]);
251 close(to[1]);
252 close(from[0]);
253 close(from[1]);
254 dup2(STDOUT_FILENO, STDERR_FILENO);
255 execvp(args[0], args);
256 _exit(EXIT_FAILURE);
257 default:
258 break;
261 sleep(1);
263 if (kill(pid, 0) == -1)
264 return -2;
266 close(to[0]);
267 close(from[1]);
269 d->engine->fd[ENGINE_IN_FD] = from[0];
270 d->engine->fd[ENGINE_OUT_FD] = to[1];
271 fcntl(d->engine->fd[ENGINE_IN_FD], F_SETFL, O_NONBLOCK | O_DIRECT);
272 fcntl(d->engine->fd[ENGINE_OUT_FD], F_SETFL, O_NONBLOCK | O_DIRECT);
273 d->engine->pid = pid;
274 return 0;
277 void stop_engine(GAME *g)
279 struct userdata_s *d = g->data;
280 int s;
282 if (!d->engine || d->engine->status == ENGINE_OFFLINE)
283 return;
285 send_to_engine(g, "quit\n");
287 if (kill(d->engine->pid, 0) != -1)
288 kill(d->engine->pid, SIGTERM);
290 if (kill(d->engine->pid, 0) != -1)
291 kill(d->engine->pid, SIGKILL);
293 waitpid(d->engine->pid, &s, 0);
296 void set_engine_defaults(GAME *g, char **init)
298 int i;
300 if (!init)
301 return;
303 for (i = 0; init[i]; i++)
304 send_to_engine(g, "%s\n", init[i]);
307 int start_chess_engine(GAME *g)
309 char **args;
310 int i;
311 int ret = 1;
312 struct userdata_s *d = g->data;
314 args = parseargs(config.engine_cmd);
316 d->engine = Calloc(1, sizeof(struct engine_s));
317 d->engine->status = ENGINE_INITIALIZING;
318 update_status_window(*g);
319 refresh_all();
321 switch (init_chess_engine(g, args)) {
322 case -1:
323 /* Pty allocation. */
324 message(ERROR, ANYKEY, "Could not allocate PTY");
325 d->engine->status = ENGINE_OFFLINE;
326 break;
327 case -2:
328 /* Could not execute engine. */
329 message(ERROR, ANYKEY, "%s: %s", args[0], strerror(errno));
330 d->engine->status = ENGINE_OFFLINE;
331 break;
332 default:
333 ret = 0;
334 set_engine_defaults(g, config.einit);
335 d->engine->status = ENGINE_READY;
336 break;
339 for (i = 0; args[i]; i++)
340 free(args[i]);
342 free(args);
343 update_status_window(*g);
344 return ret;
347 /* Once the PGN parser has been well tested, validate_move() from the human
348 * move can disappear.
350 void parse_gnuchess_line(GAME *g, char *str)
352 char m[MAX_SAN_MOVE_LEN + 1] = {0}, *p = m;
353 int count;
354 struct userdata_s *d = g->data;
356 /* Human move. Add it to the move history. */
357 if (sscanf(str, "%*d%*1[.]%*1[ ]%[a-zA-Z0-9+=#-]%n", m, &count) == 1) {
359 if (validate_move(g, m)) {
360 invalid_move(0, m);
361 return;
365 if (history_total(g->hp) == 0 && g->side == BLACK)
366 SET_FLAG(g->flags, GF_BLACK_OPENING);
368 history_add(g, p);
369 SET_FLAG(g->flags, GF_MODIFIED);
370 pgn_switch_turn(g);
371 str += count;
372 return;
375 /* Engine move. */
376 if (sscanf(str, "%*d%*1[.]%*1[ ]%*3[.]%*1[ ]%[a-zA-Z0-9+=#-]%n", m,
377 &count) == 1) {
378 /* Moves from the engine are in a2a4 format (Xboard protocol) so we
379 * need to convert them.
381 //FIXME
382 if ((p = pgn_a2a4tosan(g, g->b, m)) == NULL)
383 return;
385 if (pgn_validate_move(g, g->b, p)) {
386 invalid_move(0, p);
387 RETURN(d);
390 if (history_total(g->hp) == 0 && g->side == BLACK)
391 SET_FLAG(g->flags, GF_BLACK_OPENING);
393 history_add(g, p);
394 SET_FLAG(g->flags, GF_MODIFIED);
395 pgn_switch_turn(g);
396 str += count;
398 if (TEST_FLAG(g->flags, GF_GAMEOVER)) {
399 //history_update_board(g, g.htotal); FIXME
400 RETURN(d);
403 if (d && TEST_FLAG(d->flags, CF_ENGINE_LOOP)) {
404 send_to_engine(g, "go\n");
405 return;
408 RETURN(d);
411 if (TEST_FLAG(g->flags, GF_GAMEOVER)) {
412 //history_update_board(g); FIXME
413 RETURN(d);
416 /* Miscellaneous one-liners. */
417 /* 'switch' command. */
418 if (strncmp(str, "White to move", 13) == 0) {
419 g->side = g->turn = WHITE;
420 RETURN(d);
422 else if (strncmp(str, "Black to move", 13) == 0) {
423 g->side = g->turn = BLACK;
424 RETURN(d);
427 /* Bad engine command or move. */
428 if (strncmp(str, "Illegal move: ", 14) == 0) {
429 invalid_move(0, p);
430 RETURN(d);
434 static void parse_engine_line(GAME *g, char *line)
436 line = trim(line);
438 if (!*line)
439 return;
441 parse_gnuchess_line(g, line);
444 void parse_engine_output(GAME *g, char *str)
446 char buf[LINE_MAX], *p = buf;
448 while (*str) {
449 *p = '\0';
451 /* FIXME test this ("White ... : ", "Black ... : "). Needed for the
452 * 'g'o command. */
453 if (*str == ':') {
454 p = buf;
455 str++;
456 continue;
459 if (*str == '\n') {
460 *p = '\0';
461 parse_engine_line(g, buf);
462 str++;
463 p = buf;
464 continue;
467 *p++ = *str++;