No longer send SIGINT to the engine before each command.
[cboard.git] / src / engine.c
blob21123ae8f5463141f76ac48b435ac9e413c8e842
1 /* vim:tw=78:ts=8:sw=4:set ft=c: */
2 /*
3 Copyright (C) 2002-2007 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 <limits.h>
23 #include <string.h>
24 #include <sys/types.h>
25 #include <signal.h>
26 #include <sys/stat.h>
27 #include <sys/time.h>
28 #include <fcntl.h>
29 #include <ctype.h>
30 #include <stdarg.h>
31 #include <errno.h>
32 #include <err.h>
34 #ifdef HAVE_CONFIG_H
35 #include <config.h>
36 #endif
38 #ifdef HAVE_PATHS_H
39 #include <paths.h>
40 #endif
42 #ifdef HAVE_STDARG_H
43 #include <stdarg.h>
44 #endif
46 #ifdef HAVE_CURSES_H
47 #include <curses.h>
48 #endif
50 #ifdef HAVE_PANEL_H
51 #include <panel.h>
52 #endif
54 #ifdef HAVE_SYS_WAIT_H
55 #include <sys/wait.h>
56 #endif
58 #include "chess.h"
59 #include "conf.h"
60 #include "misc.h"
61 #include "strings.h"
62 #include "window.h"
63 #include "message.h"
64 #include "common.h"
65 #include "engine.h"
67 #ifdef WITH_DMALLOC
68 #include <dmalloc.h>
69 #endif
71 int send_signal_to_engine(pid_t pid, int sig)
73 if (kill(pid, sig) == -1)
74 return 1;
76 return 0;
79 int send_to_engine(GAME g, int status, const char *format, ...)
81 va_list ap;
82 int len;
83 char *line;
84 int try = 0;
85 struct userdata_s *d = g->data;
87 if (!d->engine || d->engine->status == ENGINE_OFFLINE ||
88 TEST_FLAG(d->flags, CF_HUMAN))
89 return 1;
91 va_start(ap, format);
92 #ifdef HAVE_VASPRINTF
93 len = vasprintf(&line, format, ap);
94 #else
95 line = Malloc(LINE_MAX);
96 len = vsnprintf(line, LINE_MAX, format, ap);
97 #endif
98 va_end(ap);
100 while (1) {
101 int n;
102 fd_set fds;
103 struct timeval tv;
105 FD_ZERO(&fds);
106 FD_SET(d->engine->fd[ENGINE_OUT_FD], &fds);
108 tv.tv_sec = 0;
109 tv.tv_usec = 0;
111 if ((n = select(d->engine->fd[ENGINE_OUT_FD] + 1, NULL, &fds, NULL,
112 &tv)) > 0) {
113 if (FD_ISSET(d->engine->fd[ENGINE_OUT_FD], &fds)) {
114 n = write(d->engine->fd[ENGINE_OUT_FD], line, len);
116 if (n == -1) {
117 if (errno == EAGAIN)
118 continue;
120 if (kill(d->engine->pid, 0) == -1) {
121 message(ERROR, ANYKEY, "Could not write to engine. "
122 "Process no longer exists.");
123 d->engine->status = ENGINE_OFFLINE;
124 return 1;
127 message(ERROR, ANYKEY, "Attempt #%i. write(): %s", ++try,
128 strerror(errno));
129 continue;
132 if (len != n) {
133 message(NULL, ANYKEY, "try #%i: write() error to engine. "
134 "Expected %i, got %i.", ++try, len, n);
135 continue;
138 break;
141 else {
142 /* timeout */
146 d->engine->status = status;
147 free(line);
148 return 0;
151 #ifndef UNIX98
152 /* From the Xboard and screen packages. */
153 static int get_pty(char *pty_name)
155 int i;
156 int fd;
158 for (i = 0; PTY_MAJOR[i]; i++) {
159 int n;
161 for (n = 0; PTY_MINOR[n]; n++) {
162 sprintf(pty_name, "%spty%c%c", _PATH_DEV, PTY_MAJOR[i],
163 PTY_MINOR[n]);
165 if ((fd = open(pty_name, O_RDWR | O_NOCTTY)) == -1)
166 continue;
168 sprintf(pty_name, "%stty%c%c", _PATH_DEV, PTY_MAJOR[i],
169 PTY_MINOR[n]);
171 if (access(pty_name, R_OK | W_OK) == -1) {
172 close(fd);
173 continue;
176 return fd;
180 return -1;
182 #endif
184 static char **parseargs(char *str)
186 char **pptr, *s;
187 char arg[255];
188 int n = 0;
189 int quote = 0;
190 int lastchar = 0;
191 int i;
193 if (!str)
194 return NULL;
196 if (!(pptr = malloc(sizeof(char *))))
197 return NULL;
199 for (i = 0, s = str; *s; lastchar = *s++) {
200 if ((*s == '\"' || *s == '\'') && lastchar != '\\') {
201 quote = (quote) ? 0 : 1;
202 continue;
205 if (*s == ' ' && !quote) {
206 arg[i] = 0;
207 pptr = realloc(pptr, (n + 2) * sizeof(char *));
208 pptr[n++] = strdup(arg);
209 arg[0] = i = 0;
210 continue;
213 if ((i + 1) == sizeof(arg))
214 continue;
216 arg[i++] = *s;
219 arg[i] = 0;
221 if (arg[0]) {
222 pptr = realloc(pptr, (n + 2) * sizeof(char *));
223 pptr[n++] = strdup(arg);
226 pptr[n] = NULL;
227 return pptr;
230 int init_chess_engine(GAME g)
232 struct userdata_s *d = g->data;
233 int w, x;
235 if (start_chess_engine(g) > 0) {
236 d->sp.icon = 0;
237 return 1;
240 x = pgn_tag_find(g->tag, "FEN");
241 w = pgn_tag_find(g->tag, "SetUp");
243 if ((w >= 0 && x >= 0 && atoi(g->tag[w]->value) == 1) ||
244 (x >= 0 && w == -1))
245 add_engine_command(g, ENGINE_READY, "setboard %s\n", g->tag[x]->value);
246 else
247 add_engine_command(g, ENGINE_READY, "setboard %s\n",
248 pgn_game_to_fen(g, d->b));
250 return 0;
253 /* Is this dangerous if pty permissions are wrong? */
254 static pid_t exec_chess_engine(GAME g, char **args)
256 pid_t pid;
257 int from[2], to[2];
258 struct userdata_s *d = g->data;
259 #ifndef UNIX98
260 char pty[FILENAME_MAX];
262 if ((to[1] = get_pty(pty)) == -1) {
263 errno = 0;
264 return -1;
266 #else
267 if ((to[1] = open("/dev/ptmx", O_RDWR | O_NOCTTY)) == -1) {
268 return -1;
271 if (grantpt(to[1]) == -1) {
272 return -1;
275 if (unlockpt(to[1]) == -1) {
276 return -1;
278 #endif
280 from[0] = to[1];
281 errno = 0;
283 #ifndef UNIX98
284 if ((to[0] = open(pty, O_RDWR, 0)) == -1)
285 #else
286 if ((to[0] = open(ptsname(to[1]), O_RDWR, 0)) == -1)
287 #endif
288 return -1;
290 from[1] = to[0];
291 errno = 0;
293 switch ((pid = fork())) {
294 case -1:
295 return -2;
296 case 0:
297 dup2(to[0], STDIN_FILENO);
298 dup2(from[1], STDOUT_FILENO);
299 close(to[0]);
300 close(to[1]);
301 close(from[0]);
302 close(from[1]);
303 dup2(STDOUT_FILENO, STDERR_FILENO);
304 execvp(args[0], args);
305 _exit(EXIT_FAILURE);
306 default:
307 break;
311 * FIXME shouldn't block here. When theres a high load average, the engine
312 * process will fail to start. Better to handle this in game_loop() and
313 * give up after a timer expires.
315 sleep(1);
317 if (send_signal_to_engine(pid, 0))
318 return -2;
320 close(to[0]);
321 close(from[1]);
323 d->engine->fd[ENGINE_IN_FD] = from[0];
324 d->engine->fd[ENGINE_OUT_FD] = to[1];
325 fcntl(d->engine->fd[ENGINE_IN_FD], F_SETFL, O_NONBLOCK);
326 fcntl(d->engine->fd[ENGINE_OUT_FD], F_SETFL, O_NONBLOCK);
327 d->engine->pid = pid;
328 return 0;
331 void stop_engine(GAME g)
333 struct userdata_s *d = g->data;
334 int s;
336 if (!d->engine || d->engine->pid == -1 || d->engine->status == ENGINE_OFFLINE)
337 return;
339 send_to_engine(g, ENGINE_READY, "quit\n");
341 if (!send_signal_to_engine(d->engine->pid, 0)) {
342 if (!send_signal_to_engine(d->engine->pid, SIGTERM))
343 send_signal_to_engine(d->engine->pid, SIGKILL);
346 waitpid(d->engine->pid, &s, 0);
347 d->engine->pid = -1;
348 d->engine->status = ENGINE_OFFLINE;
351 void set_engine_defaults(GAME g, char **init)
353 int i;
355 if (!init)
356 return;
358 for (i = 0; init[i]; i++)
359 add_engine_command(g, ENGINE_READY, "%s\n", init[i]);
362 int start_chess_engine(GAME g)
364 char **args;
365 int i;
366 int ret = 1;
367 struct userdata_s *d = g->data;
369 if (d->engine)
370 return -1;
372 args = parseargs(config.engine_cmd);
374 d->engine = Calloc(1, sizeof(struct engine_s));
375 d->engine->status = ENGINE_INITIALIZING;
376 update_status_window(g);
377 update_panels();
378 doupdate();
380 switch (exec_chess_engine(g, args)) {
381 case -1:
382 /* Pty allocation. */
383 message(ERROR, ANYKEY, "Could not allocate PTY");
384 d->engine->status = ENGINE_OFFLINE;
385 break;
386 case -2:
387 /* Could not execute engine. */
388 message(ERROR, ANYKEY, "%s: %s", args[0], strerror(errno));
389 d->engine->status = ENGINE_OFFLINE;
390 break;
391 default:
392 ret = 0;
393 set_engine_defaults(g, config.einit);
394 d->engine->status = ENGINE_READY;
395 break;
398 for (i = 0; args[i]; i++)
399 free(args[i]);
401 free(args);
402 return ret;
405 static void parse_xboard_line(GAME g, char *str)
407 char m[MAX_SAN_MOVE_LEN + 1] = {0}, *p = m;
408 struct userdata_s *d = g->data;
409 int n;
410 char *frfr;
411 int count;
413 p = str;
415 // 1. a2a4 (gnuchess)
416 while (isdigit(*p))
417 p++;
419 // gnuchess echos our input move so we don't need the duplicate (above).
420 if (*p == '.' && *(p + 1) == ' ' && *(p + 2) != '.')
421 return;
423 // 1. ... a2a4 (gnuchess/engine move)
424 if (*p == '.' && *(p + 1) == ' ' && *(p + 2) == '.') {
425 while (*p == ' ' || *p == '.')
426 p++;
429 if (strncmp(str, "move ", 5) == 0)
430 p = str + 5;
432 if (strlen(p) > MAX_SAN_MOVE_LEN)
433 return;
435 // We should now have the real move which may be in SAN or frfr format.
436 if (sscanf(p, "%[0-9a-hprnqkxPRNBQKO+=#-]%n", m, &count) == 1) {
438 * For engine commands (the '|' key). Don't try and validate them.
440 if (count != strlen(p))
441 return;
443 if (TEST_FLAG(g->flags, GF_GAMEOVER)) {
444 gameover(g);
445 return;
448 if (strlen(m) < 2)
449 RETURN(d);
451 p = m + strlen(m) - 1;
453 // Test SAN or a2a4 format.
454 if (!isdigit(*p) && *p != 'O' && *p != '+' && *p != '#' &&
455 ((*(p - 1) != '=' || !isdigit(*(p - 1))) &&
456 pgn_piece_to_int(*p) == -1))
457 return;
459 p = m;
461 if ((n = pgn_parse_move(g, d->b, &p, &frfr)) != E_PGN_OK) {
462 invalid_move(d->n + 1, n, m);
463 RETURN(d);
466 pgn_history_add(g, p);
467 SET_FLAG(d->flags, CF_MODIFIED);
468 pgn_switch_turn(g);
470 if (TEST_FLAG(d->flags, CF_ENGINE_LOOP)) {
471 update_cursor(g, g->hindex);
473 if (TEST_FLAG(g->flags, GF_GAMEOVER)) {
474 gameover(g);
475 return;
478 add_engine_command(g, ENGINE_THINKING, "go\n");
479 return;
482 if (g->side == g->turn)
483 RETURN(d);
485 d->engine->status = ENGINE_THINKING;
488 return;
491 void parse_engine_output(GAME g, char *str)
493 char buf[255], *p = buf;
495 while (*str) {
496 if (*str == '\n' || *str == '\r') {
497 *p = '\0';
499 if (*buf) {
500 append_enginebuf(g, buf);
501 parse_xboard_line(g, buf);
504 str++;
506 if (*str == '\n')
507 str++;
509 p = buf;
510 continue;
513 *p++ = *str++;
516 update_all(gp);
519 void send_engine_command(GAME g)
521 struct userdata_s *d = g->data;
522 struct queue_s **q = d->engine->queue;
523 int i;
525 if (!d->engine || !d->engine->queue)
526 return;
528 if (!q || !q[0])
529 return;
531 if (send_to_engine(g, q[0]->status, "%s", q[0]->line))
532 return;
534 free(q[0]->line);
535 free(q[0]);
537 for (i = 0; q[i]; i++) {
538 if (q[i+1])
539 q[i] = q[i+1];
540 else {
541 q[i] = NULL;
542 break;
547 void add_engine_command(GAME g, int s, char *fmt, ...)
549 va_list ap;
550 int i = 0;
551 struct userdata_s *d = g->data;
552 struct queue_s **q;
553 char *line;
555 if (!d->engine || d->engine->status == ENGINE_OFFLINE) {
556 if (init_chess_engine(g))
557 return;
560 q = d->engine->queue;
562 if (q)
563 for (i = 0; q[i]; i++);
565 q = Realloc(q, (i + 2) * sizeof(struct queue_s *));
566 va_start(ap, fmt);
567 #ifdef HAVE_VASPRINTF
568 vasprintf(&line, fmt, ap);
569 #else
570 line = Malloc(LINE_MAX + 1);
571 vsnprintf(line, LINE_MAX, fmt, ap);
572 #endif
573 va_end(ap);
574 q[i] = Malloc(sizeof(struct queue_s));
575 q[i]->line = line;
576 q[i++]->status = (s == -1) ? d->engine->status : s;
577 q[i] = NULL;
578 d->engine->queue = q;