Use sigaction() rather than signal().
[cboard.git] / src / engine.c
blob15c8dcb4597ba72e4a06b89a7cf734ff9530d994
1 /* vim:tw=78:ts=8:sw=4:set ft=c: */
2 /*
3 Copyright (C) 2002-2018 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
59 send_signal_to_engine (pid_t pid, int sig)
61 if (kill (pid, sig) == -1)
62 return 1;
64 return 0;
67 int
68 send_to_engine (GAME g, int status, const char *format, ...)
70 va_list ap;
71 int len;
72 char *line;
73 struct userdata_s *d = g->data;
75 if (!d->engine || d->engine->status == ENGINE_OFFLINE ||
76 TEST_FLAG (d->flags, CF_HUMAN))
77 return 1;
79 va_start (ap, format);
80 #ifdef HAVE_VASPRINTF
81 len = vasprintf (&line, format, ap);
82 #else
83 line = Malloc (LINE_MAX);
84 len = vsnprintf (line, LINE_MAX, format, ap);
85 #endif
86 va_end (ap);
88 while (1)
90 int n;
91 fd_set fds;
92 struct timeval tv;
94 FD_ZERO (&fds);
95 FD_SET (d->engine->fd[ENGINE_OUT_FD], &fds);
97 tv.tv_sec = 0;
98 tv.tv_usec = 0;
100 if ((n = select (d->engine->fd[ENGINE_OUT_FD] + 1, NULL, &fds, NULL,
101 &tv)) > 0)
103 if (FD_ISSET (d->engine->fd[ENGINE_OUT_FD], &fds))
105 n = write (d->engine->fd[ENGINE_OUT_FD], line, len);
107 if (n == -1)
109 if (errno == EAGAIN)
111 usleep (50000);
112 continue;
115 if (kill (d->engine->pid, 0) == -1)
117 message (ERROR_STR, ANY_KEY_STR,
118 _("Could not write to engine. "
119 "Process no longer exists."));
120 d->engine->status = ENGINE_OFFLINE;
121 break;
124 message (ERROR_STR, ANY_KEY_STR,
125 "write() error to engine: %s", strerror (errno));
126 d->engine->status = ENGINE_OFFLINE;
127 break;
130 if (len != n)
132 message (NULL, ANY_KEY_STR,
134 ("short write() count to engine. Expected %i, got %i."),
135 len, n);
136 d->engine->status = ENGINE_OFFLINE;
137 break;
140 break;
143 else
145 /* timeout */
149 if (d->engine->status == ENGINE_OFFLINE)
150 stop_engine (g);
151 else
152 d->engine->status = status;
154 free (line);
155 return d->engine->status == ENGINE_OFFLINE ? 1 : 0;
158 #ifndef UNIX98
159 /* From the Xboard and screen packages. */
160 static int
161 get_pty (char *pty_name)
163 int i;
164 int fd;
166 for (i = 0; PTY_MAJOR[i]; i++)
168 int n;
170 for (n = 0; PTY_MINOR[n]; n++)
172 sprintf (pty_name, "%spty%c%c", _PATH_DEV, PTY_MAJOR[i],
173 PTY_MINOR[n]);
175 if ((fd = open (pty_name, O_RDWR | O_NOCTTY)) == -1)
176 continue;
178 sprintf (pty_name, "%stty%c%c", _PATH_DEV, PTY_MAJOR[i],
179 PTY_MINOR[n]);
181 if (access (pty_name, R_OK | W_OK) == -1)
183 close (fd);
184 continue;
187 return fd;
191 return -1;
193 #endif
195 static char **
196 parseargs (char *str)
198 char **pptr, *s;
199 char arg[255];
200 int n = 0;
201 int quote = 0;
202 int lastchar = 0;
203 int i;
205 if (!str)
206 return NULL;
208 if (!(pptr = malloc (sizeof (char *))))
209 return NULL;
211 for (i = 0, s = str; *s; lastchar = *s++)
213 if ((*s == '\"' || *s == '\'') && lastchar != '\\')
215 quote = (quote) ? 0 : 1;
216 continue;
219 if (*s == ' ' && !quote)
221 arg[i] = 0;
222 pptr = realloc (pptr, (n + 2) * sizeof (char *));
223 pptr[n++] = strdup (arg);
224 arg[0] = i = 0;
225 continue;
228 if ((i + 1) == sizeof (arg))
229 continue;
231 arg[i++] = *s;
234 arg[i] = 0;
236 if (arg[0])
238 pptr = realloc (pptr, (n + 2) * sizeof (char *));
239 pptr[n++] = strdup (arg);
242 pptr[n] = NULL;
243 return pptr;
247 init_chess_engine (GAME g)
249 struct userdata_s *d = g->data;
250 char *tmp;
252 if (start_chess_engine (g) > 0)
254 d->sp.icon = 0;
255 return 1;
258 tmp = pgn_game_to_fen (g, d->b);
259 add_engine_command (g, ENGINE_READY, "setboard %s\n", tmp);
260 free (tmp);
262 if (config.fmpolyglot && !TEST_FLAG (d->flags, CF_HUMAN) )
264 add_engine_command (g, ENGINE_READY, "%s\n",
265 g->side == WHITE ? "white" : "black");
266 if (g->turn == WHITE && g->side == BLACK)
267 add_engine_command (g, ENGINE_READY, "%s\n", "go");
268 else if (g->turn == BLACK && g->side == WHITE)
269 add_engine_command (g, ENGINE_READY, "%s\n", "go");
272 return 0;
275 /* Is this dangerous if pty permissions are wrong? */
276 static pid_t
277 exec_chess_engine (GAME g, char **args)
279 pid_t pid;
280 int from[2], to[2];
281 struct userdata_s *d = g->data;
282 #ifndef UNIX98
283 char pty[FILENAME_MAX];
285 if ((to[1] = get_pty (pty)) == -1)
287 errno = 0;
288 return -1;
290 #else
291 if ((to[1] = open ("/dev/ptmx", O_RDWR | O_NOCTTY)) == -1)
293 return -1;
296 if (grantpt (to[1]) == -1)
298 return -1;
301 if (unlockpt (to[1]) == -1)
303 return -1;
305 #endif
307 from[0] = to[1];
308 errno = 0;
310 #ifndef UNIX98
311 if ((to[0] = open (pty, O_RDWR, 0)) == -1)
312 #else
313 if ((to[0] = open (ptsname (to[1]), O_RDWR | O_NOCTTY, 0)) == -1)
314 #endif
315 return -1;
317 from[1] = to[0];
318 errno = 0;
320 switch ((pid = fork ()))
322 case -1:
323 return -2;
324 case 0:
325 dup2 (to[0], STDIN_FILENO);
326 dup2 (from[1], STDOUT_FILENO);
327 close (to[0]);
328 close (to[1]);
329 close (from[0]);
330 close (from[1]);
331 dup2 (STDOUT_FILENO, STDERR_FILENO);
332 execvp (args[0], args);
333 _exit (EXIT_FAILURE);
334 default:
335 break;
339 * FIXME shouldn't block here. When theres a high load average, the engine
340 * process will fail to start. Better to handle this in game_loop() and
341 * give up after a timer expires.
343 sleep (1);
345 if (send_signal_to_engine (pid, 0))
346 return -2;
348 close (to[0]);
349 close (from[1]);
351 d->engine->fd[ENGINE_IN_FD] = from[0];
352 d->engine->fd[ENGINE_OUT_FD] = to[1];
354 if (fcntl (d->engine->fd[ENGINE_IN_FD], F_SETFL, O_NONBLOCK) == -1)
355 return -2;
357 if (fcntl (d->engine->fd[ENGINE_OUT_FD], F_SETFL, O_NONBLOCK) == -1)
358 return -2;
360 d->engine->pid = pid;
361 return 0;
364 void
365 stop_engine (GAME g)
367 struct userdata_s *d = g->data;
368 int s;
370 if (!d->engine || d->engine->pid == -1
371 || d->engine->status == ENGINE_OFFLINE)
372 return;
374 send_to_engine (g, ENGINE_READY, "quit\n");
376 if (!send_signal_to_engine (d->engine->pid, 0))
378 if (!send_signal_to_engine (d->engine->pid, SIGTERM))
379 send_signal_to_engine (d->engine->pid, SIGKILL);
382 waitpid (d->engine->pid, &s, WNOHANG);
383 d->engine->pid = -1;
384 d->engine->status = ENGINE_OFFLINE;
387 void
388 set_engine_defaults (GAME g, wchar_t ** init)
390 int i;
392 if (!init)
393 return;
395 for (i = 0; init[i]; i++)
396 add_engine_command (g, ENGINE_READY, "%ls\n", init[i]);
400 start_chess_engine (GAME g)
402 char **args;
403 int i;
404 int ret = 1;
405 struct userdata_s *d = g->data;
407 if (d->engine && d->engine->status != ENGINE_OFFLINE)
408 return -1;
410 args = parseargs (config.engine_cmd);
412 if (!d->engine)
413 d->engine = Calloc (1, sizeof (struct engine_s));
415 d->engine->status = ENGINE_INITIALIZING;
416 update_status_window (g);
417 update_panels ();
418 doupdate ();
420 switch (exec_chess_engine (g, args))
422 case -1:
423 /* Pty allocation. */
424 message (ERROR_STR, ANY_KEY_STR, "Could not allocate PTY");
425 d->engine->status = ENGINE_OFFLINE;
426 break;
427 case -2:
428 /* Could not execute engine. */
429 message (ERROR_STR, ANY_KEY_STR, "%s: %s", args[0], strerror (errno));
430 d->engine->status = ENGINE_OFFLINE;
431 break;
432 default:
433 ret = 0;
434 d->engine->status = ENGINE_READY;
435 set_engine_defaults (g, config.einit);
436 break;
439 for (i = 0; args[i]; i++)
440 free (args[i]);
442 free (args);
443 return ret;
446 static void
447 parse_xboard_line (GAME g, char *str)
449 char m[MAX_SAN_MOVE_LEN + 1] = { 0 }, *p = m;
450 struct userdata_s *d = g->data;
451 int n;
452 char *frfr;
453 int count;
455 p = str;
457 // 1. a2a4 (gnuchess)
458 while (isdigit (*p))
459 p++;
461 // gnuchess echos our input move so we don't need the duplicate (above).
462 if (*p == '.' && *(p + 1) == ' ' && *(p + 2) != '.')
463 return;
465 // 1. ... a2a4 (gnuchess/engine move)
466 if (*p == '.' && *(p + 1) == ' ' && *(p + 2) == '.')
468 while (*p == ' ' || *p == '.')
469 p++;
472 if (strncmp (str, "move ", 5) == 0)
473 p = str + 5;
475 if (strlen (p) > MAX_SAN_MOVE_LEN)
476 return;
478 // We should now have the real move which may be in SAN or frfr format.
479 if (sscanf (p, "%[0-9a-hprnqkxPRNBQKO+=#-]%n", m, &count) == 1)
481 char *buf = NULL;
484 * For engine commands (the '|' key). Don't try and validate them.
486 if (count != strlen (p))
487 return;
489 if (TEST_FLAG (g->flags, GF_GAMEOVER))
491 gameover (g);
492 return;
495 if (strlen (m) < 2)
496 RETURN (d);
498 p = m + strlen (m) - 1;
500 // Test SAN or a2a4 format.
501 if (!isdigit (*p) && *p != 'O' && *p != '+' && *p != '#'
502 && ((*(p - 1) != '=' || !isdigit (*(p - 1)))
503 && pgn_piece_to_int (*p) == -1))
504 return;
506 buf = strdup (m);
507 if ((n = pgn_parse_move (g, d->b, &buf, &frfr)) != E_PGN_OK)
509 invalid_move (d->n + 1, n, m);
510 RETURN (d);
513 strcpy (d->pm_frfr, frfr);
515 free (frfr);
516 pgn_history_add (g, d->b, buf);
517 SET_FLAG (d->flags, CF_MODIFIED);
518 pgn_switch_turn (g);
520 if (TEST_FLAG (d->flags, CF_ENGINE_LOOP))
522 free (buf);
523 update_cursor (g, g->hindex);
525 if (TEST_FLAG (g->flags, GF_GAMEOVER))
527 gameover (g);
528 return;
531 add_engine_command (g, ENGINE_THINKING, "go\n");
532 return;
535 if (TEST_FLAG (gp->flags, GF_GAMEOVER))
537 free (buf);
538 gameover (g);
539 return;
542 if (g->side == g->turn)
544 if (config.turn_cmd)
546 char *cmd = string_replace (config.turn_cmd, "%m", buf);
548 switch (fork ())
550 case 0:
551 system (cmd);
552 _exit (0);
553 case -1:
554 break;
555 default:
556 break;
559 free (cmd);
562 free (buf);
563 RETURN (d);
566 free (buf);
567 d->engine->status = ENGINE_THINKING;
570 return;
573 void
574 parse_engine_output (GAME g, char *str)
576 char buf[255], *p = buf;
578 while (*str)
580 if (*str == '\n' || *str == '\r')
582 *p = '\0';
584 if (*buf)
586 append_enginebuf (g, buf);
587 parse_xboard_line (g, buf);
590 str++;
592 if (*str == '\n')
593 str++;
595 p = buf;
596 continue;
599 *p++ = *str++;
602 update_all (gp);
605 void
606 send_engine_command (GAME g)
608 struct userdata_s *d = g->data;
609 struct queue_s **q;
610 int i;
612 if (!d->engine || !d->engine->queue)
613 return;
615 q = d->engine->queue;
617 if (send_to_engine (g, q[0]->status, "%s", q[0]->line))
618 return;
620 free (q[0]->line);
621 free (q[0]);
623 for (i = 0; q[i]; i++)
625 if (q[i + 1])
626 q[i] = q[i + 1];
627 else
629 q[i] = NULL;
630 break;
634 if (!q[0])
636 free (d->engine->queue);
637 d->engine->queue = NULL;
641 void
642 add_engine_command (GAME g, int s, const char *fmt, ...)
644 va_list ap;
645 int i = 0;
646 struct userdata_s *d = g->data;
647 struct queue_s **q = NULL;
648 char *line;
650 if (!d->engine || d->engine->status == ENGINE_OFFLINE)
652 if (init_chess_engine (g))
653 return;
656 if (!d->engine)
657 return;
659 q = d->engine->queue;
661 if (q)
662 for (i = 0; q[i]; i++);
664 q = Realloc (q, (i + 2) * sizeof (struct queue_s *));
665 va_start (ap, fmt);
666 #ifdef HAVE_VASPRINTF
667 vasprintf (&line, fmt, ap);
668 #else
669 line = Malloc (LINE_MAX + 1);
670 vsnprintf (line, LINE_MAX, fmt, ap);
671 #endif
672 va_end (ap);
673 q[i] = Malloc (sizeof (struct queue_s));
674 q[i]->line = line;
675 q[i++]->status = (s == -1) ? d->engine->status : s;
676 q[i] = NULL;
677 d->engine->queue = q;