]> git.pond.sub.org Git - empserver/blob - src/client/play.c
9979ad697a580ddf49aa8833e2705f79902d9363
[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
455 static int
456 ring_from_rl(struct ring *inbuf)
457 {
458     size_t len;
459     int n;
460
461     assert(has_rl_input && input_from_rl);
462
463     len = strlen(input_from_rl);
464     n = ring_space(inbuf);
465     assert(n);
466
467     if (len >= (size_t)n) {
468         ring_putm(inbuf, input_from_rl, n);
469         memmove(input_from_rl, input_from_rl + n, len - n + 1);
470     } else {
471         ring_putm(inbuf, input_from_rl, len);
472         ring_putc(inbuf, '\n');
473         free(input_from_rl);
474         has_rl_input = 0;
475         n = len + 1;
476     }
477
478     return n;
479 }
480 #endif /* HAVE_LIBREADLINE */
481
482 /*
483  * Receive player input from @fd into @inbuf.
484  * Return 1 on receipt of input, zero on EOF, -1 on error.
485  */
486 static int
487 recv_input(int fd, struct ring *inbuf)
488 {
489     int n;
490     int res = 1;
491
492 #ifdef HAVE_LIBREADLINE
493     if (fd == 0) {
494         if (!has_rl_input)
495             rl_callback_read_char();
496         if (!has_rl_input)
497             return 1;
498         if (input_from_rl) {
499             n = ring_from_rl(inbuf);
500         } else
501             n = 0;
502     } else
503 #endif
504         n = ring_from_file(inbuf, fd);
505     if (n < 0)
506         return -1;
507     if (n == 0) {
508         /*
509          * Can't put EOF cookie into INBUF here, it may not fit.
510          * Leave it to caller.
511          */
512         res = 0;
513     }
514
515     return res;
516 }
517
518 static int
519 send_input(int fd, struct ring *inbuf)
520 {
521     struct iovec iov[2];
522     int cnt, i, ch;
523     ssize_t res;
524
525     cnt = ring_to_iovec(inbuf, iov);
526     res = writev(fd, iov, cnt);
527     if (res < 0)
528         return res;
529
530     /* Copy input to @auxfp etc. */
531     for (i = 0; i < res; i++) {
532         ch = ring_getc(inbuf);
533         assert(ch != EOF);
534         if (ch != '\r')
535             save_input(ch);
536         if (auxfp)
537             putc(ch, auxfp);
538     }
539
540 #ifdef HAVE_LIBREADLINE
541     if (fd == 0 && has_rl_input && input_from_rl)
542         ring_from_rl(inbuf);
543 #endif
544
545     return res;
546 }
547
548 static void
549 intr(int sig)
550 {
551     send_intr = 1;
552 }
553
554 /*
555  * Play on @sock.
556  * @history_file is the name of the history file, or null.
557  * The session must be in the playing phase.
558  * Return 0 when the session ended, -1 on error.
559  */
560 int
561 play(int sock, char *history_file)
562 {
563     /*
564      * Player input flows from INPUT_FD through recv_input() into ring
565      * buffer INBUF, which drains into SOCK.  This must not block.
566      * Server output flows from SOCK into recv_output().  Reading SOCK
567      * must not block.
568      */
569     struct sigaction sa;
570     struct ring inbuf;          /* input buffer, draining to SOCK */
571     int eof_fd0;                /* read fd 0 hit EOF? */
572     int partial_line_sent;      /* partial input line sent? */
573     int send_eof;               /* need to send EOF_COOKIE */
574     fd_set rdfd, wrfd;
575     int n;
576     int ret = -1;
577
578     sa.sa_flags = 0;
579     sigemptyset(&sa.sa_mask);
580     sa.sa_handler = intr;
581     sigaction(SIGINT, &sa, NULL);
582     sa.sa_handler = SIG_IGN;
583     sigaction(SIGPIPE, &sa, NULL);
584 #ifdef HAVE_LIBREADLINE
585     rl_already_prompted = 1;
586 #ifdef HAVE_READLINE_HISTORY
587     if (history_file)
588         read_history(history_file);
589 #endif /* HAVE_READLINE_HISTORY */
590     rl_bind_key('\t', rl_insert);  /* Disable tab completion */
591     rl_callback_handler_install("", input_handler);
592 #endif /* HAVE_LIBREADLINE */
593
594     ring_init(&inbuf);
595     eof_fd0 = partial_line_sent = send_eof = send_intr = 0;
596     input_fd = 0;
597     sysdep_stdin_init();
598
599     for (;;) {
600         FD_ZERO(&rdfd);
601         FD_ZERO(&wrfd);
602
603         /*
604          * Want to read player input only when we don't need to send
605          * cookies, haven't reached EOF on fd 0, and @inbuf can accept
606          * some.
607          */
608         if (!send_intr && !send_eof && (input_fd || !eof_fd0)
609             && ring_space(&inbuf))
610             FD_SET(input_fd, &rdfd);
611         /* Want to send player input only when we have something */
612         if (send_intr || send_eof || ring_len(&inbuf))
613             FD_SET(sock, &wrfd);
614         /* Always want to read server output */
615         FD_SET(sock, &rdfd);
616
617         n = select(MAX(input_fd, sock) + 1, &rdfd, &wrfd, NULL, NULL);
618         if (n < 0) {
619             if (errno != EINTR) {
620                 perror("select");
621                 break;
622             }
623         }
624
625         if ((send_eof || send_intr) && partial_line_sent
626             && ring_putc(&inbuf, '\n') != EOF)
627             partial_line_sent = 0;
628         if (send_eof && !partial_line_sent
629             && ring_putm(&inbuf, EOF_COOKIE, sizeof(EOF_COOKIE) - 1) >= 0)
630             send_eof = 0;
631         if (send_intr && !partial_line_sent
632             && ring_putm(&inbuf, INTR_COOKIE, sizeof(INTR_COOKIE) - 1) >= 0) {
633             send_intr = 0;
634             if (input_fd) {
635                 /* execute aborted, switch back to fd 0 */
636                 close(input_fd);
637                 input_fd = 0;
638             }
639         }
640         if (n < 0)
641             continue;
642
643         /* read player input */
644         if (FD_ISSET(input_fd, &rdfd) && ring_space(&inbuf)) {
645             n = recv_input(input_fd, &inbuf);
646             if (n <= 0) {
647                 if (input_fd) {
648                     /* execute done, switch back to fd 0 */
649                     if (n < 0) {
650                         perror("read batch file");
651                         send_intr = 1;
652                     } else
653                         send_eof = 1;
654                     close(input_fd);
655                     input_fd = 0;
656                 } else {
657                     /* stop reading input, drain socket ring buffers */
658                     if (n < 0)
659                         perror("read stdin");
660                     send_eof = 1;
661                     eof_fd0 = 1;
662                     sa.sa_handler = SIG_DFL;
663                     sigaction(SIGINT, &sa, NULL);
664                     send_intr = 0;
665                 }
666             } else if (ring_len(&inbuf) > 0)
667                 partial_line_sent = ring_peek(&inbuf, -1) != '\n';
668         }
669
670         /* send it to the server */
671         if (FD_ISSET(sock, &wrfd)) {
672             n = send_input(sock, &inbuf);
673             if (n < 0) {
674                 perror("write socket");
675                 break;
676             }
677         }
678
679         /* read server output and print it */
680         if (FD_ISSET(sock, &rdfd)) {
681             n = recv_output(sock);
682             if (n < 0) {
683                 perror("read socket");
684                 break;
685             }
686             if (n == 0) {
687                 ret = 0;
688                 break;
689             }
690         }
691     }
692
693 #ifdef HAVE_LIBREADLINE
694     rl_callback_handler_remove();
695 #ifdef HAVE_READLINE_HISTORY
696     if (history_file)
697         write_history(history_file);
698 #endif /* HAVE_READLINE_HISTORY */
699 #endif
700     return ret;
701 }
702
703 void
704 prompt(int code, char *prompt, char *teles)
705 {
706     char pr[1024];
707
708     snprintf(pr, sizeof(pr), "%s%s", teles, prompt);
709 #ifdef HAVE_LIBREADLINE
710     rl_set_prompt(pr);
711     rl_forced_update_display();
712 #else  /* !HAVE_LIBREADLINE */
713     printf("%s", pr);
714     fflush(stdout);
715 #endif /* !HAVE_LIBREADLINE */
716     if (auxfp) {
717         fprintf(auxfp, "%s%s", teles, prompt);
718         fflush(auxfp);
719     }
720 }