]> git.pond.sub.org Git - empserver/blob - src/client/play.c
f02399bab8439c106abedf9b2179f259735ddf03
[empserver] / src / client / play.c
1 /*
2  *  Empire - A multi-player, client/server Internet based war game.
3  *  Copyright (C) 1986-2015, Dave Pare, Jeff Bailey, Thomas Ruschak,
4  *                Ken Stevens, Steve McClure, Markus Armbruster
5  *
6  *  Empire is free software: you can redistribute it and/or modify
7  *  it under the terms of the GNU General Public License as published by
8  *  the Free Software Foundation, either version 3 of the License, or
9  *  (at your option) any later version.
10  *
11  *  This program is distributed in the hope that it will be useful,
12  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
13  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  *  GNU General Public License for more details.
15  *
16  *  You should have received a copy of the GNU General Public License
17  *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
18  *
19  *  ---
20  *
21  *  See files README, COPYING and CREDITS in the root of the source
22  *  tree for related information and legal notices.  It is expected
23  *  that future projects/authors will amend these files as needed.
24  *
25  *  ---
26  *
27  *  play.c: Playing the game
28  *
29  *  Known contributors to this file:
30  *     Markus Armbruster, 2007-2017
31  *     Ron Koenderink, 2007-2009
32  *     Martin Haukeli, 2015
33  */
34
35 #include <config.h>
36
37 #include <assert.h>
38 #include <errno.h>
39 #include <signal.h>
40 #include <stdio.h>
41 #include <stdlib.h>
42 #ifdef _WIN32
43 #include <process.h>
44 #include <sys/socket.h>
45 #else
46 #include <sys/select.h>
47 #endif
48 #include <unistd.h>
49 #include "linebuf.h"
50 #include "misc.h"
51 #include "proto.h"
52 #include "ringbuf.h"
53 #include "secure.h"
54
55 #ifdef HAVE_LIBREADLINE
56 #  if defined(HAVE_READLINE_READLINE_H)
57 #    include <readline/readline.h>
58 #  elif defined(HAVE_READLINE_H)
59 #    include <readline.h>
60 #  else /* !defined(HAVE_READLINE_H) */
61 extern char *readline ();
62 #  endif /* !defined(HAVE_READLINE_H) */
63 #endif /* HAVE_LIBREADLINE */
64
65 #ifdef HAVE_READLINE_HISTORY
66 #  if defined(HAVE_READLINE_HISTORY_H)
67 #    include <readline/history.h>
68 #  elif defined(HAVE_HISTORY_H)
69 #    include <history.h>
70 #  else /* !defined(HAVE_HISTORY_H) */
71 extern void add_history ();
72 extern int write_history ();
73 extern int read_history ();
74 #  endif /* !defined(HAVE_HISTORY_H) */
75 #endif /* HAVE_READLINE_HISTORY */
76
77 #define EOF_COOKIE "ctld\n"
78 #define INTR_COOKIE "aborted\n"
79
80 /*
81  * Player input file descriptor
82  * 0 while reading interactive input
83  * >0 while reading a batch file
84  * <0 during error handling
85  */
86 static int input_fd;
87
88 static volatile sig_atomic_t send_intr; /* need to send INTR_COOKIE */
89
90 #ifdef _WIN32
91 static CRITICAL_SECTION signal_critical_section;
92 static LPCRITICAL_SECTION signal_critical_section_ptr = NULL;
93
94 static unsigned char bounce_buf[RING_SIZE];
95 /*
96  * Set bounce_empty to indicate bounce_buf is available for the stdin thread
97  * to use.
98  */
99 static HANDLE bounce_empty;
100 /*
101  * Set bounce_full to indicate bounce_buf is contains data from the
102  * stdin thread and is available for recv_input
103  */
104 static HANDLE bounce_full;
105 /* Ctrl-C (SIGINT) was detected, generate EINTR for the w32_select() */
106 static HANDLE ctrl_c_event;
107 static int bounce_status, bounce_error;
108
109 struct sigaction {
110     int sa_flags;
111     void (*sa_handler)(int sig);
112 };
113
114 #define SIGPIPE -1
115 static void (*ctrl_handler)(int sig) = { SIG_DFL };
116
117 /*
118  * Ctrl-C handler for emulating the SIGINT in WIN32
119  */
120 static BOOL WINAPI
121 w32_signal_handler(DWORD ctrl_type)
122 {
123     if (ctrl_type == CTRL_C_EVENT) {
124         EnterCriticalSection(signal_critical_section_ptr);
125         if (ctrl_handler != SIG_DFL) {
126             ctrl_handler(SIGINT);
127             LeaveCriticalSection(signal_critical_section_ptr);
128             SetEvent(ctrl_c_event);
129             return TRUE;
130         } else
131             LeaveCriticalSection(signal_critical_section_ptr);
132     }
133     return FALSE;
134 }
135
136 /*
137  * WIN32 equivalent for sigaction supports the following:
138  * set handler for SIGINT using WIN32 Ctrl-C handler
139  * reset handler SIGINT to SIG_DFL
140  * ignore SIGPIPE
141  */
142 static int
143 sigaction(int signal, struct sigaction *action, struct sigaction *oaction)
144 {
145     assert(!oaction);
146     assert(action);
147
148     if (signal == SIGPIPE)
149         assert(action->sa_handler == SIG_IGN);
150     else {
151         assert(signal == SIGINT && action->sa_handler != SIG_IGN);
152         if (ctrl_handler == action->sa_handler)
153             return 0;
154         if (signal_critical_section_ptr == NULL) {
155             signal_critical_section_ptr = &signal_critical_section;
156             InitializeCriticalSection(signal_critical_section_ptr);
157         }
158         EnterCriticalSection(signal_critical_section_ptr);
159         if (!SetConsoleCtrlHandler(w32_signal_handler,
160                                    action->sa_handler != SIG_DFL)) {
161             errno = GetLastError();
162             LeaveCriticalSection(signal_critical_section_ptr);
163             return -1;
164         }
165         ctrl_handler = action->sa_handler;
166         LeaveCriticalSection(signal_critical_section_ptr);
167     }
168     return 0;
169 }
170
171 /*
172  * Read the stdin in WIN32 environment
173  * WIN32 does not support select type function on console input
174  * so the client uses a separate thread to read input
175  */
176 static void
177 stdin_read_thread(void *dummy)
178 {
179     for (;;) {
180         switch (WaitForSingleObject(bounce_empty, INFINITE)) {
181         case WAIT_FAILED:
182             bounce_status = -1;
183             bounce_error = GetLastError();
184             break;
185         case WAIT_OBJECT_0:
186             bounce_status = _read(0, bounce_buf, sizeof(bounce_buf));
187             bounce_error = errno;
188             break;
189         case WAIT_ABANDONED:
190             return;
191         default:
192             assert(0);
193         }
194         SetEvent(bounce_full);
195     }
196 }
197
198 /*
199  * Initialize and start the stdin reading thread for WIN32
200  */
201 static void
202 sysdep_stdin_init(void)
203 {
204     bounce_empty = CreateEvent(NULL, FALSE, TRUE, NULL);
205     bounce_full = CreateEvent(NULL, TRUE, FALSE, NULL);
206     ctrl_c_event = CreateEvent(NULL, FALSE, FALSE, NULL);
207     _beginthread(stdin_read_thread, 0, NULL);
208 }
209
210 /*
211  * This function uses to WaitForMultipleObjects to wait for both
212  * stdin and socket reading or writing.
213  * Stdin is treated special in WIN32.  Waiting for stdin is done
214  * via a bounce_full event which is set in the stdin thread.
215  * Execute command file reading is done via handle.
216  * WaitForMultipleObjects will only respond with one object,
217  * so an additonal select is also done to determine
218  * which individual events are active.
219  */
220 static int
221 w32_select(int nfds, fd_set *rdfd, fd_set *wrfd, fd_set *errfd,
222            struct timeval *time)
223 {
224     HANDLE handles[3];
225     SOCKET sock;
226     int inp, sockfd, result, s_result, num_handles;
227     struct timeval tv_time = {0, 0};
228     fd_set rdsock, wrsock;
229
230     switch (rdfd->fd_count) {
231     case 1:
232         inp = -1;
233         sockfd = rdfd->fd_array[0];
234         break;
235     case 2:
236         inp = rdfd->fd_array[0];
237         sockfd = rdfd->fd_array[1];
238         break;
239     default:
240         assert(0);
241     }
242     sock = w32_fd2socket(sockfd);
243
244     assert(wrfd->fd_count == 0
245            || (wrfd->fd_count == 1 && wrfd->fd_array[0] == (SOCKET)sockfd));
246     assert(inp < 0 || inp == input_fd);
247
248     num_handles = 0;
249     handles[num_handles++] = ctrl_c_event;
250     if (inp >= 0)
251         handles[num_handles++]
252             = inp ? (HANDLE)_get_osfhandle(inp) : bounce_full;
253     /* always wait on the socket */
254     handles[num_handles++] = WSACreateEvent();
255
256     if (wrfd->fd_count > 0)
257         WSAEventSelect(sock, handles[num_handles - 1],
258                        FD_READ | FD_WRITE | FD_CLOSE);
259     else
260         WSAEventSelect(sock, handles[num_handles - 1],
261                        FD_READ | FD_CLOSE);
262
263     result = WaitForMultipleObjects(num_handles, handles, 0, INFINITE);
264     if (result < 0) {
265         errno = GetLastError();
266         WSACloseEvent(handles[num_handles - 1]);
267         return -1;
268     }
269     WSACloseEvent(handles[num_handles - 1]);
270
271     if (result == WAIT_OBJECT_0) {
272         errno = EINTR;
273         return -1;
274     }
275
276     FD_ZERO(&rdsock);
277     FD_ZERO(&wrsock);
278     FD_SET(sock, &rdsock);
279     if (wrfd->fd_count)
280         FD_SET(sock, &wrsock);
281     s_result = select(sock + 1, &rdsock, &wrsock, NULL, &tv_time);
282
283     if (s_result < 0) {
284         w32_set_winsock_errno();
285         return s_result;
286     }
287
288     if (!FD_ISSET(sock, &rdsock))
289         FD_CLR((SOCKET)sockfd, rdfd);
290     if (!FD_ISSET(sock, &wrsock))
291         FD_CLR((SOCKET)sockfd, wrfd);
292     if (inp >= 0 && result == WAIT_OBJECT_0 + 1)
293         s_result++;
294     else
295         FD_CLR((SOCKET)inp, rdfd);
296
297     return s_result;
298 }
299
300 /*
301  * Read input from the user either stdin or from file.
302  * For stdin, read from bounce_buf which filled by the stdin thread
303  * otherwise use the regular ring_from_file.
304  */
305 static int
306 w32_ring_from_file_or_bounce_buf(struct ring *r, int fd)
307 {
308     int i, res;
309
310     if (fd)
311         return ring_from_file(r, fd);
312
313     if (bounce_status < 0) {
314         errno = bounce_error;
315         res = bounce_status;
316     } else {
317         for (i = 0; i < bounce_status; i++) {
318             if (ring_putc(r, bounce_buf[i]) == EOF) {
319                 /* more work to do, hold on to bounce_buf */
320                 memmove(bounce_buf, bounce_buf + i, bounce_status - i);
321                 bounce_status -= i;
322                 return i;
323             }
324         }
325         res = i;
326     }
327
328     ResetEvent(bounce_full);
329     SetEvent(bounce_empty);
330     return res;
331 }
332 #define ring_from_file w32_ring_from_file_or_bounce_buf
333 #define select(nfds, rd, wr, error, time) \
334         w32_select((nfds), (rd), (wr), (error), (time))
335 #define sigemptyset(mask) ((void)0)
336 #else
337 #define sysdep_stdin_init() ((void)0)
338 #endif
339
340 /*
341  * Receive and process server output from @sock.
342  * Return number of characters received on success, -1 on error.
343  */
344 static int
345 recv_output(int sock)
346 {
347     /*
348      * Read a chunk of server output and feed its characters into a
349      * simple state machine.
350      * Initial state is SCANNING_ID.
351      * In state SCANNING_ID, buffer the character.  If it's a space,
352      * decode the id that has been buffered, and enter state BUFFERING
353      * or COPYING depending on its value.
354      * In state BUFFERING, buffer the character.  If it's newline,
355      * pass id and buffered text to servercmd(), then enter state
356      * SCANNING_ID.
357      * In state COPYING, pass the character to outch().  If it's
358      * newline, enter state SCANNING_ID.
359      */
360     static enum {
361         SCANNING_ID, BUFFERING, COPYING
362     } state = SCANNING_ID;
363     static int id;
364     static struct lbuf lbuf;
365     char buf[4096];
366     ssize_t n;
367     int i, ch, len, fd;
368     char *line;
369
370     n = read(sock, buf, sizeof(buf));
371     if (n < 0)
372         return -1;
373
374     for (i = 0; i < n; i++) {
375         ch = buf[i];
376         switch (state) {
377         case SCANNING_ID:
378             if (ch == '\n') {
379                 /* FIXME gripe unexpected! */
380                 lbuf_init(&lbuf);
381                 break;
382             }
383             lbuf_putc(&lbuf, ch);
384             if (ch != ' ')
385                 break;
386             line = lbuf_line(&lbuf);
387             id = parseid(line);
388             lbuf_init(&lbuf);
389
390             switch (id) {
391             case C_PROMPT:
392             case C_FLUSH:
393             case C_EXECUTE:
394             case C_EXIT:
395             case C_FLASH:
396             case C_INFORM:
397             case C_PIPE:
398             case C_REDIR:
399                 state = BUFFERING;
400                 break;
401             default:
402                 /* unknown or unexpected id, treat like C_DATA */
403             case C_DATA:
404                 state = COPYING;
405                 break;
406             }
407             break;
408
409         case BUFFERING:
410             len = lbuf_putc(&lbuf, ch);
411             if (len) {
412                 line = lbuf_line(&lbuf);
413                 fd = servercmd(id, line, len);
414                 if (fd < 0) {
415                     /* failed execute */
416                     if (input_fd)
417                         close(input_fd);
418                     input_fd = 0;
419                     send_intr = 1;
420                 } else if (fd > 0) {
421                     /* successful execute, switch to batch file */
422                     assert(!input_fd);
423                     input_fd = fd;
424                 }
425                 lbuf_init(&lbuf);
426                 state = SCANNING_ID;
427             }
428             break;
429
430         case COPYING:
431             outch(ch);
432             if (ch == '\n')
433                 state = SCANNING_ID;
434         }
435     }
436
437     return n;
438 }
439
440 #ifdef HAVE_LIBREADLINE
441 static char *input_from_rl;
442 static int has_rl_input;
443
444 static void
445 input_handler(char *line)
446 {
447     input_from_rl = line;
448     has_rl_input = 1;
449 #ifdef HAVE_READLINE_HISTORY
450     if (line && *line)
451         add_history(line);
452 #endif /* HAVE_READLINE_HISTORY */
453 }
454 #endif /* HAVE_LIBREADLINE */
455
456 /*
457  * Receive player input from @fd into @inbuf.
458  * Return 1 on receipt of input, zero on EOF, -1 on error.
459  */
460 static int
461 recv_input(int fd, struct ring *inbuf)
462 {
463     int n;
464     int res = 1;
465 #ifdef HAVE_LIBREADLINE
466     size_t len;
467
468     if (fd == 0) {
469         if (!has_rl_input)
470             rl_callback_read_char();
471         if (!has_rl_input)
472             return 1;
473         if (input_from_rl) {
474             len = strlen(input_from_rl);
475             n = ring_space(inbuf);
476             assert(n);
477             if (len >= (size_t)n) {
478                 ring_putm(inbuf, input_from_rl, n);
479                 memmove(input_from_rl, input_from_rl + n, len - n + 1);
480             } else {
481                 ring_putm(inbuf, input_from_rl, len);
482                 ring_putc(inbuf, '\n');
483                 free(input_from_rl);
484                 has_rl_input = 0;
485                 n = len + 1;
486             }
487         } else
488             n = 0;
489     } else
490 #endif
491         n = ring_from_file(inbuf, fd);
492     if (n < 0)
493         return -1;
494     if (n == 0) {
495         /*
496          * Can't put EOF cookie into INBUF here, it may not fit.
497          * Leave it to caller.
498          */
499         res = 0;
500     }
501
502     return res;
503 }
504
505 static int
506 send_input(int fd, struct ring *inbuf)
507 {
508     struct iovec iov[2];
509     int cnt, i, ch;
510     ssize_t res;
511
512     cnt = ring_to_iovec(inbuf, iov);
513     res = writev(fd, iov, cnt);
514     if (res < 0)
515         return res;
516
517     /* Copy input to @auxfp etc. */
518     for (i = 0; i < res; i++) {
519         ch = ring_getc(inbuf);
520         assert(ch != EOF);
521         if (ch != '\r')
522             save_input(ch);
523         if (auxfp)
524             putc(ch, auxfp);
525     }
526
527     return res;
528 }
529
530 static void
531 intr(int sig)
532 {
533     send_intr = 1;
534 }
535
536 /*
537  * Play on @sock.
538  * @history_file is the name of the history file, or null.
539  * The session must be in the playing phase.
540  * Return 0 when the session ended, -1 on error.
541  */
542 int
543 play(int sock, char *history_file)
544 {
545     /*
546      * Player input flows from INPUT_FD through recv_input() into ring
547      * buffer INBUF, which drains into SOCK.  This must not block.
548      * Server output flows from SOCK into recv_output().  Reading SOCK
549      * must not block.
550      */
551     struct sigaction sa;
552     struct ring inbuf;          /* input buffer, draining to SOCK */
553     int eof_fd0;                /* read fd 0 hit EOF? */
554     int partial_line_sent;      /* partial input line sent? */
555     int send_eof;               /* need to send EOF_COOKIE */
556     fd_set rdfd, wrfd;
557     int n;
558     int ret = -1;
559
560     sa.sa_flags = 0;
561     sigemptyset(&sa.sa_mask);
562     sa.sa_handler = intr;
563     sigaction(SIGINT, &sa, NULL);
564     sa.sa_handler = SIG_IGN;
565     sigaction(SIGPIPE, &sa, NULL);
566 #ifdef HAVE_LIBREADLINE
567     rl_already_prompted = 1;
568 #ifdef HAVE_READLINE_HISTORY
569     if (history_file)
570         read_history(history_file);
571 #endif /* HAVE_READLINE_HISTORY */
572     rl_bind_key('\t', rl_insert);  /* Disable tab completion */
573     rl_callback_handler_install("", input_handler);
574 #endif /* HAVE_LIBREADLINE */
575
576     ring_init(&inbuf);
577     eof_fd0 = partial_line_sent = send_eof = send_intr = 0;
578     input_fd = 0;
579     sysdep_stdin_init();
580
581     for (;;) {
582         FD_ZERO(&rdfd);
583         FD_ZERO(&wrfd);
584
585         /*
586          * Want to read player input only when we don't need to send
587          * cookies, haven't reached EOF on fd 0, and @inbuf can accept
588          * some.
589          */
590         if (!send_intr && !send_eof && (input_fd || !eof_fd0)
591             && ring_space(&inbuf))
592             FD_SET(input_fd, &rdfd);
593         /* Want to send player input only when we have something */
594         if (send_intr || send_eof || ring_len(&inbuf))
595             FD_SET(sock, &wrfd);
596         /* Always want to read server output */
597         FD_SET(sock, &rdfd);
598
599         n = select(MAX(input_fd, sock) + 1, &rdfd, &wrfd, NULL, NULL);
600         if (n < 0) {
601             if (errno != EINTR) {
602                 perror("select");
603                 break;
604             }
605         }
606
607         if ((send_eof || send_intr) && partial_line_sent
608             && ring_putc(&inbuf, '\n') != EOF)
609             partial_line_sent = 0;
610         if (send_eof && !partial_line_sent
611             && ring_putm(&inbuf, EOF_COOKIE, sizeof(EOF_COOKIE) - 1) >= 0)
612             send_eof = 0;
613         if (send_intr && !partial_line_sent
614             && ring_putm(&inbuf, INTR_COOKIE, sizeof(INTR_COOKIE) - 1) >= 0) {
615             send_intr = 0;
616             if (input_fd) {
617                 /* execute aborted, switch back to fd 0 */
618                 close(input_fd);
619                 input_fd = 0;
620             }
621         }
622         if (n < 0)
623             continue;
624
625         /* read player input */
626         if (FD_ISSET(input_fd, &rdfd) && ring_space(&inbuf)) {
627             n = recv_input(input_fd, &inbuf);
628             if (n <= 0) {
629                 if (input_fd) {
630                     /* execute done, switch back to fd 0 */
631                     if (n < 0) {
632                         perror("read batch file");
633                         send_intr = 1;
634                     } else
635                         send_eof = 1;
636                     close(input_fd);
637                     input_fd = 0;
638                 } else {
639                     /* stop reading input, drain socket ring buffers */
640                     if (n < 0)
641                         perror("read stdin");
642                     send_eof = 1;
643                     eof_fd0 = 1;
644                     sa.sa_handler = SIG_DFL;
645                     sigaction(SIGINT, &sa, NULL);
646                     send_intr = 0;
647                 }
648             } else if (ring_len(&inbuf) > 0)
649                 partial_line_sent = ring_peek(&inbuf, -1) != '\n';
650         }
651
652         /* send it to the server */
653         if (FD_ISSET(sock, &wrfd)) {
654             n = send_input(sock, &inbuf);
655             if (n < 0) {
656                 perror("write socket");
657                 break;
658             }
659         }
660
661         /* read server output and print it */
662         if (FD_ISSET(sock, &rdfd)) {
663             n = recv_output(sock);
664             if (n < 0) {
665                 perror("read socket");
666                 break;
667             }
668             if (n == 0) {
669                 ret = 0;
670                 break;
671             }
672         }
673     }
674
675 #ifdef HAVE_LIBREADLINE
676     rl_callback_handler_remove();
677 #ifdef HAVE_READLINE_HISTORY
678     if (history_file)
679         write_history(history_file);
680 #endif /* HAVE_READLINE_HISTORY */
681 #endif
682     return ret;
683 }