Update copyright year to 2015.
[cboard.git] / src / engine.c
blob6c9f34c0744c5ba9b8a8c8b9c92ae63b613be1ea
1 /* vim:tw=78:ts=8:sw=4:set ft=c: */
2 /*
3 Copyright (C) 2002-2015 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 #ifdef HAVE_CONFIG_H
20 #include <config.h>
21 #endif
23 #include <stdio.h>
24 #include <stdlib.h>
25 #include <unistd.h>
26 #include <limits.h>
27 #include <string.h>
28 #include <sys/types.h>
29 #include <signal.h>
30 #include <sys/stat.h>
31 #include <sys/time.h>
32 #include <fcntl.h>
33 #include <ctype.h>
34 #include <stdarg.h>
35 #include <errno.h>
36 #include <err.h>
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_SYS_WAIT_H
47 #include <sys/wait.h>
48 #endif
50 #include "common.h"
51 #include "conf.h"
52 #include "misc.h"
53 #include "strings.h"
54 #include "window.h"
55 #include "message.h"
56 #include "engine.h"
58 int send_signal_to_engine(pid_t pid, int sig)
60 if (kill(pid, sig) == -1)
61 return 1;
63 return 0;
66 int send_to_engine(GAME g, int status, const char *format, ...)
68 va_list ap;
69 int len;
70 char *line;
71 struct userdata_s *d = g->data;
73 if (!d->engine || d->engine->status == ENGINE_OFFLINE ||
74 TEST_FLAG(d->flags, CF_HUMAN))
75 return 1;
77 va_start(ap, format);
78 #ifdef HAVE_VASPRINTF
79 len = vasprintf(&line, format, ap);
80 #else
81 line = Malloc(LINE_MAX);
82 len = vsnprintf(line, LINE_MAX, format, ap);
83 #endif
84 va_end(ap);
86 while (1) {
87 int n;
88 fd_set fds;
89 struct timeval tv;
91 FD_ZERO(&fds);
92 FD_SET(d->engine->fd[ENGINE_OUT_FD], &fds);
94 tv.tv_sec = 0;
95 tv.tv_usec = 0;
97 if ((n = select(d->engine->fd[ENGINE_OUT_FD] + 1, NULL, &fds, NULL,
98 &tv)) > 0) {
99 if (FD_ISSET(d->engine->fd[ENGINE_OUT_FD], &fds)) {
100 n = write(d->engine->fd[ENGINE_OUT_FD], line, len);
102 if (n == -1) {
103 if (errno == EAGAIN) {
104 usleep (50000);
105 continue;
108 if (kill(d->engine->pid, 0) == -1) {
109 message(ERROR_STR, ANY_KEY_STR,
110 _("Could not write to engine. "
111 "Process no longer exists."));
112 d->engine->status = ENGINE_OFFLINE;
113 break;
116 message(ERROR_STR, ANY_KEY_STR,
117 "write() error to engine: %s", strerror(errno));
118 d->engine->status = ENGINE_OFFLINE;
119 break;
122 if (len != n) {
123 message(NULL, ANY_KEY_STR,
124 _("short write() count to engine. Expected %i, got %i."),
125 len, n);
126 d->engine->status = ENGINE_OFFLINE;
127 break;
130 break;
133 else {
134 /* timeout */
138 if (d->engine->status == ENGINE_OFFLINE)
139 stop_engine (g);
140 else
141 d->engine->status = status;
143 free(line);
144 return d->engine->status == ENGINE_OFFLINE ? 1 : 0;
147 #ifndef UNIX98
148 /* From the Xboard and screen packages. */
149 static int get_pty(char *pty_name)
151 int i;
152 int fd;
154 for (i = 0; PTY_MAJOR[i]; i++) {
155 int n;
157 for (n = 0; PTY_MINOR[n]; n++) {
158 sprintf(pty_name, "%spty%c%c", _PATH_DEV, PTY_MAJOR[i],
159 PTY_MINOR[n]);
161 if ((fd = open(pty_name, O_RDWR | O_NOCTTY)) == -1)
162 continue;
164 sprintf(pty_name, "%stty%c%c", _PATH_DEV, PTY_MAJOR[i],
165 PTY_MINOR[n]);
167 if (access(pty_name, R_OK | W_OK) == -1) {
168 close(fd);
169 continue;
172 return fd;
176 return -1;
178 #endif
180 static char **parseargs(char *str)
182 char **pptr, *s;
183 char arg[255];
184 int n = 0;
185 int quote = 0;
186 int lastchar = 0;
187 int i;
189 if (!str)
190 return NULL;
192 if (!(pptr = malloc(sizeof(char *))))
193 return NULL;
195 for (i = 0, s = str; *s; lastchar = *s++) {
196 if ((*s == '\"' || *s == '\'') && lastchar != '\\') {
197 quote = (quote) ? 0 : 1;
198 continue;
201 if (*s == ' ' && !quote) {
202 arg[i] = 0;
203 pptr = realloc(pptr, (n + 2) * sizeof(char *));
204 pptr[n++] = strdup(arg);
205 arg[0] = i = 0;
206 continue;
209 if ((i + 1) == sizeof(arg))
210 continue;
212 arg[i++] = *s;
215 arg[i] = 0;
217 if (arg[0]) {
218 pptr = realloc(pptr, (n + 2) * sizeof(char *));
219 pptr[n++] = strdup(arg);
222 pptr[n] = NULL;
223 return pptr;
226 int init_chess_engine(GAME g)
228 struct userdata_s *d = g->data;
229 char *tmp;
231 if (start_chess_engine(g) > 0) {
232 d->sp.icon = 0;
233 return 1;
236 tmp = pgn_game_to_fen(g, d->b);
237 add_engine_command(g, ENGINE_READY, "setboard %s\n", tmp);
238 free (tmp);
239 return 0;
242 /* Is this dangerous if pty permissions are wrong? */
243 static pid_t exec_chess_engine(GAME g, char **args)
245 pid_t pid;
246 int from[2], to[2];
247 struct userdata_s *d = g->data;
248 #ifndef UNIX98
249 char pty[FILENAME_MAX];
251 if ((to[1] = get_pty(pty)) == -1) {
252 errno = 0;
253 return -1;
255 #else
256 if ((to[1] = open("/dev/ptmx", O_RDWR | O_NOCTTY)) == -1) {
257 return -1;
260 if (grantpt(to[1]) == -1) {
261 return -1;
264 if (unlockpt(to[1]) == -1) {
265 return -1;
267 #endif
269 from[0] = to[1];
270 errno = 0;
272 #ifndef UNIX98
273 if ((to[0] = open(pty, O_RDWR, 0)) == -1)
274 #else
275 if ((to[0] = open(ptsname(to[1]), O_RDWR | O_NOCTTY, 0)) == -1)
276 #endif
277 return -1;
279 from[1] = to[0];
280 errno = 0;
282 switch ((pid = fork())) {
283 case -1:
284 return -2;
285 case 0:
286 dup2(to[0], STDIN_FILENO);
287 dup2(from[1], STDOUT_FILENO);
288 close(to[0]);
289 close(to[1]);
290 close(from[0]);
291 close(from[1]);
292 dup2(STDOUT_FILENO, STDERR_FILENO);
293 execvp(args[0], args);
294 _exit(EXIT_FAILURE);
295 default:
296 break;
300 * FIXME shouldn't block here. When theres a high load average, the engine
301 * process will fail to start. Better to handle this in game_loop() and
302 * give up after a timer expires.
304 sleep(1);
306 if (send_signal_to_engine(pid, 0))
307 return -2;
309 close(to[0]);
310 close(from[1]);
312 d->engine->fd[ENGINE_IN_FD] = from[0];
313 d->engine->fd[ENGINE_OUT_FD] = to[1];
315 if (fcntl(d->engine->fd[ENGINE_IN_FD], F_SETFL, O_NONBLOCK) == -1)
316 return -2;
318 if (fcntl(d->engine->fd[ENGINE_OUT_FD], F_SETFL, O_NONBLOCK) == -1)
319 return -2;
321 d->engine->pid = pid;
322 return 0;
325 void stop_engine(GAME g)
327 struct userdata_s *d = g->data;
328 int s;
330 if (!d->engine || d->engine->pid == -1 || d->engine->status == ENGINE_OFFLINE)
331 return;
333 send_to_engine(g, ENGINE_READY, "quit\n");
335 if (!send_signal_to_engine(d->engine->pid, 0)) {
336 if (!send_signal_to_engine(d->engine->pid, SIGTERM))
337 send_signal_to_engine(d->engine->pid, SIGKILL);
340 waitpid(d->engine->pid, &s, WNOHANG);
341 d->engine->pid = -1;
342 d->engine->status = ENGINE_OFFLINE;
345 void set_engine_defaults(GAME g, wchar_t **init)
347 int i;
349 if (!init)
350 return;
352 for (i = 0; init[i]; i++)
353 add_engine_command(g, ENGINE_READY, "%ls\n", init[i]);
356 int start_chess_engine(GAME g)
358 char **args;
359 int i;
360 int ret = 1;
361 struct userdata_s *d = g->data;
363 if (d->engine && d->engine->status != ENGINE_OFFLINE)
364 return -1;
366 args = parseargs(config.engine_cmd);
368 if (!d->engine)
369 d->engine = Calloc(1, sizeof(struct engine_s));
371 d->engine->status = ENGINE_INITIALIZING;
372 update_status_window(g);
373 update_panels();
374 doupdate();
376 switch (exec_chess_engine(g, args)) {
377 case -1:
378 /* Pty allocation. */
379 message(ERROR_STR, ANY_KEY_STR, "Could not allocate PTY");
380 d->engine->status = ENGINE_OFFLINE;
381 break;
382 case -2:
383 /* Could not execute engine. */
384 message(ERROR_STR, ANY_KEY_STR, "%s: %s", args[0], strerror(errno));
385 d->engine->status = ENGINE_OFFLINE;
386 break;
387 default:
388 ret = 0;
389 d->engine->status = ENGINE_READY;
390 set_engine_defaults(g, config.einit);
391 break;
394 for (i = 0; args[i]; i++)
395 free(args[i]);
397 free(args);
398 return ret;
401 static void parse_xboard_line(GAME g, char *str)
403 char m[MAX_SAN_MOVE_LEN + 1] = {0}, *p = m;
404 struct userdata_s *d = g->data;
405 int n;
406 char *frfr;
407 int count;
409 p = str;
411 // 1. a2a4 (gnuchess)
412 while (isdigit(*p))
413 p++;
415 // gnuchess echos our input move so we don't need the duplicate (above).
416 if (*p == '.' && *(p + 1) == ' ' && *(p + 2) != '.')
417 return;
419 // 1. ... a2a4 (gnuchess/engine move)
420 if (*p == '.' && *(p + 1) == ' ' && *(p + 2) == '.') {
421 while (*p == ' ' || *p == '.')
422 p++;
425 if (strncmp(str, "move ", 5) == 0)
426 p = str + 5;
428 if (strlen(p) > MAX_SAN_MOVE_LEN)
429 return;
431 // We should now have the real move which may be in SAN or frfr format.
432 if (sscanf(p, "%[0-9a-hprnqkxPRNBQKO+=#-]%n", m, &count) == 1) {
433 char *buf = NULL;
436 * For engine commands (the '|' key). Don't try and validate them.
438 if (count != strlen(p))
439 return;
441 if (TEST_FLAG(g->flags, GF_GAMEOVER)) {
442 gameover(g);
443 return;
446 if (strlen(m) < 2)
447 RETURN(d);
449 p = m + strlen(m) - 1;
451 // Test SAN or a2a4 format.
452 if (!isdigit(*p) && *p != 'O' && *p != '+' && *p != '#'
453 && ((*(p - 1) != '=' || !isdigit(*(p - 1)))
454 && pgn_piece_to_int(*p) == -1))
455 return;
457 buf = strdup (m);
458 if ((n = pgn_parse_move(g, d->b, &buf, &frfr)) != E_PGN_OK) {
459 invalid_move(d->n + 1, n, m);
460 RETURN(d);
463 strcpy(d->pm_frfr, frfr);
465 free (frfr);
466 pgn_history_add(g, d->b, buf);
467 free (buf);
468 SET_FLAG(d->flags, CF_MODIFIED);
469 pgn_switch_turn(g);
471 if (TEST_FLAG(d->flags, CF_ENGINE_LOOP)) {
472 update_cursor(g, g->hindex);
474 if (TEST_FLAG(g->flags, GF_GAMEOVER)) {
475 gameover(g);
476 return;
479 add_engine_command(g, ENGINE_THINKING, "go\n");
480 return;
483 if (TEST_FLAG(gp->flags, GF_GAMEOVER)) {
484 gameover (g);
485 return;
488 if (g->side == g->turn)
489 RETURN(d);
491 d->engine->status = ENGINE_THINKING;
494 return;
497 void parse_engine_output(GAME g, char *str)
499 char buf[255], *p = buf;
501 while (*str) {
502 if (*str == '\n' || *str == '\r') {
503 *p = '\0';
505 if (*buf) {
506 append_enginebuf(g, buf);
507 parse_xboard_line(g, buf);
510 str++;
512 if (*str == '\n')
513 str++;
515 p = buf;
516 continue;
519 *p++ = *str++;
522 update_all(gp);
525 void send_engine_command(GAME g)
527 struct userdata_s *d = g->data;
528 struct queue_s **q;
529 int i;
531 if (!d->engine || !d->engine->queue)
532 return;
534 q = d->engine->queue;
536 if (send_to_engine(g, q[0]->status, "%s", q[0]->line))
537 return;
539 free(q[0]->line);
540 free(q[0]);
542 for (i = 0; q[i]; i++) {
543 if (q[i+1])
544 q[i] = q[i+1];
545 else {
546 q[i] = NULL;
547 break;
551 if (!q[0]) {
552 free (d->engine->queue);
553 d->engine->queue = NULL;
557 void add_engine_command(GAME g, int s, char *fmt, ...)
559 va_list ap;
560 int i = 0;
561 struct userdata_s *d = g->data;
562 struct queue_s **q = NULL;
563 char *line;
565 if (!d->engine || d->engine->status == ENGINE_OFFLINE) {
566 if (init_chess_engine(g))
567 return;
570 if (!d->engine)
571 return;
573 q = d->engine->queue;
575 if (q)
576 for (i = 0; q[i]; i++);
578 q = Realloc(q, (i + 2) * sizeof(struct queue_s *));
579 va_start(ap, fmt);
580 #ifdef HAVE_VASPRINTF
581 vasprintf(&line, fmt, ap);
582 #else
583 line = Malloc(LINE_MAX + 1);
584 vsnprintf(line, LINE_MAX, fmt, ap);
585 #endif
586 va_end(ap);
587 q[i] = Malloc(sizeof(struct queue_s));
588 q[i]->line = line;
589 q[i++]->status = (s == -1) ? d->engine->status : s;
590 q[i] = NULL;
591 d->engine->queue = q;