Fix for terminating the chess engine. The pid was thought to be -1
[cboard.git] / src / engine.c
blob5469f9b1a12d3323d4f28eafb0b7c0f1e781aa02
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 <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 if (send_signal_to_engine(d->engine->pid, SIGINT))
92 return 1;
94 va_start(ap, format);
95 #ifdef HAVE_VASPRINTF
96 len = vasprintf(&line, format, ap);
97 #else
98 line = Malloc(LINE_MAX);
99 len = vsnprintf(line, LINE_MAX, format, ap);
100 #endif
101 va_end(ap);
103 while (1) {
104 int n;
105 fd_set fds;
106 struct timeval tv;
108 FD_ZERO(&fds);
109 FD_SET(d->engine->fd[ENGINE_OUT_FD], &fds);
111 tv.tv_sec = 0;
112 tv.tv_usec = 0;
114 if ((n = select(d->engine->fd[ENGINE_OUT_FD] + 1, NULL, &fds, NULL,
115 &tv)) > 0) {
116 if (FD_ISSET(d->engine->fd[ENGINE_OUT_FD], &fds)) {
117 n = write(d->engine->fd[ENGINE_OUT_FD], line, len);
119 if (n == -1) {
120 if (errno == EAGAIN)
121 continue;
123 if (kill(d->engine->pid, 0) == -1) {
124 message(ERROR, ANYKEY, "Could not write to engine. "
125 "Process no longer exists.");
126 d->engine->status = ENGINE_OFFLINE;
127 return 1;
130 message(ERROR, ANYKEY, "Attempt #%i. write(): %s", ++try,
131 strerror(errno));
132 continue;
135 if (len != n) {
136 message(NULL, ANYKEY, "try #%i: write() error to engine. "
137 "Expected %i, got %i.", ++try, len, n);
138 continue;
141 break;
144 else {
145 /* timeout */
149 d->engine->status = status;
150 free(line);
151 return 0;
154 #ifndef UNIX98
155 /* From the Xboard and screen packages. */
156 static int get_pty(char *pty_name)
158 int i;
159 int fd;
161 for (i = 0; PTY_MAJOR[i]; i++) {
162 int n;
164 for (n = 0; PTY_MINOR[n]; n++) {
165 sprintf(pty_name, "%spty%c%c", _PATH_DEV, PTY_MAJOR[i],
166 PTY_MINOR[n]);
168 if ((fd = open(pty_name, O_RDWR | O_NOCTTY)) == -1)
169 continue;
171 sprintf(pty_name, "%stty%c%c", _PATH_DEV, PTY_MAJOR[i],
172 PTY_MINOR[n]);
174 if (access(pty_name, R_OK | W_OK) == -1) {
175 close(fd);
176 continue;
179 return fd;
183 return -1;
185 #endif
187 static char **parseargs(char *str)
189 char **pptr, *s;
190 char arg[255];
191 int n = 0;
192 int quote = 0;
193 int lastchar = 0;
194 int i;
196 if (!str)
197 return NULL;
199 if (!(pptr = malloc(sizeof(char *))))
200 return NULL;
202 for (i = 0, s = str; *s; lastchar = *s++) {
203 if ((*s == '\"' || *s == '\'') && lastchar != '\\') {
204 quote = (quote) ? 0 : 1;
205 continue;
208 if (*s == ' ' && !quote) {
209 arg[i] = 0;
210 pptr = realloc(pptr, (n + 2) * sizeof(char *));
211 pptr[n++] = strdup(arg);
212 arg[0] = i = 0;
213 continue;
216 if ((i + 1) == sizeof(arg))
217 continue;
219 arg[i++] = *s;
222 arg[i] = 0;
224 if (arg[0]) {
225 pptr = realloc(pptr, (n + 2) * sizeof(char *));
226 pptr[n++] = strdup(arg);
229 pptr[n] = NULL;
230 return pptr;
233 /* Is this dangerous if pty permissions are wrong? */
234 static pid_t init_chess_engine(GAME g, char **args)
236 pid_t pid;
237 int from[2], to[2];
238 struct userdata_s *d = g->data;
239 #ifndef UNIX98
240 char pty[FILENAME_MAX];
242 if ((to[1] = get_pty(pty)) == -1) {
243 errno = 0;
244 return -1;
246 #else
247 if ((to[1] = open("/dev/ptmx", O_RDWR | O_NOCTTY)) == -1) {
248 return -1;
251 if (grantpt(to[1]) == -1) {
252 return -1;
255 if (unlockpt(to[1]) == -1) {
256 return -1;
258 #endif
260 from[0] = to[1];
261 errno = 0;
263 #ifndef UNIX98
264 if ((to[0] = open(pty, O_RDWR, 0)) == -1)
265 #else
266 if ((to[0] = open(ptsname(to[1]), O_RDWR, 0)) == -1)
267 #endif
268 return -1;
270 from[1] = to[0];
271 errno = 0;
273 switch ((pid = fork())) {
274 case -1:
275 return -2;
276 case 0:
277 dup2(to[0], STDIN_FILENO);
278 dup2(from[1], STDOUT_FILENO);
279 close(to[0]);
280 close(to[1]);
281 close(from[0]);
282 close(from[1]);
283 dup2(STDOUT_FILENO, STDERR_FILENO);
284 execvp(args[0], args);
285 _exit(EXIT_FAILURE);
286 default:
287 break;
290 sleep(1);
292 if (kill(pid, 0) == -1)
293 return -2;
295 close(to[0]);
296 close(from[1]);
298 d->engine->fd[ENGINE_IN_FD] = from[0];
299 d->engine->fd[ENGINE_OUT_FD] = to[1];
300 fcntl(d->engine->fd[ENGINE_IN_FD], F_SETFL, O_NONBLOCK);
301 fcntl(d->engine->fd[ENGINE_OUT_FD], F_SETFL, O_NONBLOCK);
302 d->engine->pid = pid;
303 return 0;
306 void stop_engine(GAME g)
308 struct userdata_s *d = g->data;
309 int s;
311 if (!d->engine || d->engine->pid == -1 || d->engine->status == ENGINE_OFFLINE)
312 return;
314 send_to_engine(g, ENGINE_READY, "quit\n");
316 if (!send_signal_to_engine(d->engine->pid, 0)) {
317 if (!send_signal_to_engine(d->engine->pid, SIGTERM))
318 send_signal_to_engine(d->engine->pid, SIGKILL);
321 waitpid(d->engine->pid, &s, 0);
322 d->engine->pid = -1;
325 void set_engine_defaults(GAME g, char **init)
327 int i;
329 if (!init)
330 return;
332 for (i = 0; init[i]; i++)
333 add_engine_command(g, ENGINE_READY, "%s\n", init[i]);
336 int start_chess_engine(GAME g)
338 char **args;
339 int i;
340 int ret = 1;
341 struct userdata_s *d = g->data;
343 if (d->engine)
344 return -1;
346 args = parseargs(config.engine_cmd);
348 d->engine = Calloc(1, sizeof(struct engine_s));
349 d->engine->status = ENGINE_INITIALIZING;
350 update_status_window(g);
351 refresh_all();
353 switch (init_chess_engine(g, args)) {
354 case -1:
355 /* Pty allocation. */
356 message(ERROR, ANYKEY, "Could not allocate PTY");
357 d->engine->status = ENGINE_OFFLINE;
358 break;
359 case -2:
360 /* Could not execute engine. */
361 message(ERROR, ANYKEY, "%s: %s", args[0], strerror(errno));
362 d->engine->status = ENGINE_OFFLINE;
363 break;
364 default:
365 ret = 0;
366 set_engine_defaults(g, config.einit);
367 d->engine->status = ENGINE_READY;
368 break;
371 for (i = 0; args[i]; i++)
372 free(args[i]);
374 free(args);
375 update_status_window(g);
376 return ret;
379 static void parse_xboard_line(GAME g, char *str)
381 char m[MAX_SAN_MOVE_LEN + 1] = {0}, *p = m;
382 struct userdata_s *d = g->data;
383 int n;
384 char *frfr;
385 int count;
387 p = str;
389 // 1. a2a4 (gnuchess)
390 while (isdigit(*p))
391 p++;
393 // gnuchess echos our input move so we don't need the duplicate (above).
394 if (*p == '.' && *(p + 1) == ' ' && *(p + 2) != '.')
395 return;
397 // 1. ... a2a4 (gnuchess/engine move)
398 if (*p == '.' && *(p + 1) == ' ' && *(p + 2) == '.') {
399 while (*p == ' ' || *p == '.')
400 p++;
403 if (strncmp(str, "move ", 5) == 0)
404 p = str + 5;
406 if (strlen(p) > MAX_SAN_MOVE_LEN)
407 return;
409 // We should now have the real move which may be in SAN or frfr format.
410 if (sscanf(p, "%[0-9a-hprnqkxPRNBQKO+=#-]%n", m, &count) == 1) {
412 * For engine commands (the '|' key). Don't try and validate them.
414 if (count != strlen(p))
415 RETURN(d);
417 if (TEST_FLAG(g->flags, GF_GAMEOVER)) {
418 stop_engine(g);
419 RETURN(d);
422 if (strlen(m) < 2)
423 RETURN(d);
425 p = m + strlen(m) - 1;
427 // Test SAN or a2a4 format.
428 if (!isdigit(*p) && *p != 'O' && *p != '+' && *p != '#' &&
429 ((*(p - 1) != '=' || !isdigit(*(p - 1))) &&
430 pgn_piece_to_int(*p) == -1))
431 return;
433 p = m;
435 if ((n = pgn_parse_move(g, d->b, &p, &frfr)) != E_PGN_OK) {
436 invalid_move(d->n + 1, n, m);
437 RETURN(d);
440 pgn_history_add(g, p);
441 SET_FLAG(d->flags, CF_MODIFIED);
442 pgn_switch_turn(g);
444 if (TEST_FLAG(d->flags, CF_ENGINE_LOOP)) {
445 update_cursor(g, g->hindex);
447 if (TEST_FLAG(g->flags, GF_GAMEOVER)) {
448 stop_engine(g);
449 RETURN(d);
452 add_engine_command(g, ENGINE_THINKING, "go\n");
455 if (g->side == g->turn)
456 RETURN(d);
459 return;
462 void parse_engine_output(GAME g, char *str)
464 char buf[255], *p = buf;
466 while (*str) {
467 if (*str == '\n' || *str == '\r') {
468 *p = '\0';
470 if (*buf) {
471 append_enginebuf(g, buf);
472 parse_xboard_line(g, buf);
475 str++;
477 if (*str == '\n')
478 str++;
480 p = buf;
481 continue;
484 *p++ = *str++;
488 void send_engine_command(GAME g)
490 struct userdata_s *d = g->data;
491 struct queue_s **q = d->engine->queue;
492 int i;
494 if (!d->engine || !d->engine->queue)
495 return;
497 if (!q || !q[0])
498 return;
500 if (send_to_engine(g, q[0]->status, "%s", q[0]->line) == 0) {
501 if (g == game[gindex])
502 update_status_window(g);
505 free(q[0]->line);
506 free(q[0]);
508 for (i = 0; q[i]; i++) {
509 if (q[i+1])
510 q[i] = q[i+1];
511 else {
512 q[i] = NULL;
513 break;
518 void add_engine_command(GAME g, int s, char *fmt, ...)
520 va_list ap;
521 int i = 0;
522 struct userdata_s *d = g->data;
523 struct queue_s **q;
524 char *line;
526 if (!d->engine)
527 return;
529 q = d->engine->queue;
531 if (q)
532 for (i = 0; q[i]; i++);
534 q = Realloc(q, (i + 2) * sizeof(struct queue_s *));
535 va_start(ap, fmt);
536 #ifdef HAVE_VASPRINTF
537 vasprintf(&line, fmt, ap);
538 #else
539 line = Malloc(LINE_MAX + 1);
540 vsnprintf(line, LINE_MAX, fmt, ap);
541 #endif
542 va_end(ap);
543 q[i] = Malloc(sizeof(struct queue_s));
544 q[i]->line = line;
545 q[i++]->status = (s == -1) ? d->engine->status : s;
546 q[i] = NULL;
547 d->engine->queue = q;