]> git.pond.sub.org Git - empserver/commitdiff
Merge branch 'readline'
authorMarkus Armbruster <armbru@pond.sub.org>
Sat, 8 Jul 2017 18:43:29 +0000 (20:43 +0200)
committerMarkus Armbruster <armbru@pond.sub.org>
Mon, 7 Aug 2017 07:37:44 +0000 (09:37 +0200)
20 files changed:
Make.mk
configure.ac
include/fnameat.h [new file with mode: 0644]
include/prototypes.h
m4/my_lib_readline.m4 [new file with mode: 0644]
m4/my_terminfo.m4
man/empire.6
src/client/Makefile.in
src/client/configure.ac
src/client/main.c
src/client/misc.h
src/client/play.c
src/client/ringbuf.c
src/client/ringbuf.h
src/client/secure.c
src/client/secure.h
src/client/servcmd.c
src/lib/common/conftab.c
src/lib/common/emp_config.c
src/lib/gen/fnameat.c

diff --git a/Make.mk b/Make.mk
index 5746cc380e69a226303af8da7300647bfa731fb0..8c921d3091a4ace436247923307745231d3d5ce5 100644 (file)
--- a/Make.mk
+++ b/Make.mk
@@ -323,7 +323,7 @@ info.html/%.html: info/%.t
 $(server): $(filter src/server/% src/lib/commands/% src/lib/player/% src/lib/subs/% src/lib/update/%, $(obj)) $(empth_obj) $(empth_lib) $(libs)
        $(call quiet-command,$(LINK.o) $^ $(LOADLIBES) $(LDLIBS) -o $@,LINK $@)
 
-$(client): $(filter src/client/%, $(obj)) src/lib/global/version.o
+$(client): $(filter src/client/%, $(obj)) src/lib/global/version.o src/lib/gen/fnameat.o
        $(call quiet-command,$(LINK.o) $^ $(LOADLIBES) $(LDLIBS) -o $@,LINK $@)
 
 $(util): $(libs)
@@ -398,8 +398,9 @@ dist-client: $(cli_distgen)
        $(tarball) $(TARNAME)-client $(version)                         \
        -C $(srcdir)/src/client                                         \
                $(notdir $(filter src/client/%, $(src)) $(cli_distgen)) \
-       -C $(srcdir)/include proto.h version.h                          \
+       -C $(srcdir)/include fnameat.h proto.h version.h                \
        -C $(srcdir)/src/lib/global version.c                           \
+       -C $(srcdir)/src/lib/gen fnameat.c                              \
        -C $(srcdir)/src/lib $(addprefix w32/, $(client/w32))           \
        -C $(srcdir)/man empire.6                                       \
        -C $(srcdir)/build-aux install-sh                               \
@@ -457,5 +458,5 @@ $(srcdir)/src/client/config.h.in: src/client/configure.ac src/client/aclocal.m4
        cd $(dir $@) && autoheader
        touch $@
 
-$(srcdir)/src/client/aclocal.m4: m4/ax_lib_socket_nsl.m4 m4/my_terminfo.m4 m4/my_windows_api.m4
+$(srcdir)/src/client/aclocal.m4: m4/ax_lib_socket_nsl.m4 m4/my_lib_readline.m4 m4/my_terminfo.m4 m4/my_windows_api.m4
        cat $^ >$@
index 8c1c6cd281bd056cad73b92fbbf42974ee4bbf18..a30010816ad7bd080f5787fc6e447607dbbd684b 100644 (file)
@@ -79,6 +79,7 @@ LIBS_util="$LIBS"
 LIBS="$LIBS_SOCKETS $LIBS"
 AX_LIB_SOCKET_NSL
 LIBS_server="$LIBS"
+MY_WITH_READLINE
 
 
 ### Checks for header files
@@ -240,6 +241,7 @@ AC_OUTPUT
 AC_MSG_NOTICE([])
 AC_MSG_NOTICE([-= Configuration summary =-])
 AC_MSG_NOTICE([Thread package: $empthread])
+AC_MSG_NOTICE([      readline: $with_readline])
 AC_MSG_NOTICE([      terminfo: $with_terminfo])
 AC_MSG_NOTICE([    EMPIREHOST: $EMPIREHOST])
 AC_MSG_NOTICE([    EMPIREPORT: $EMPIREPORT])
diff --git a/include/fnameat.h b/include/fnameat.h
new file mode 100644 (file)
index 0000000..5bd91c2
--- /dev/null
@@ -0,0 +1,41 @@
+/*
+ *  Empire - A multi-player, client/server Internet based war game.
+ *  Copyright (C) 1986-2015, Dave Pare, Jeff Bailey, Thomas Ruschak,
+ *                Ken Stevens, Steve McClure, Markus Armbruster
+ *
+ *  Empire is free software: you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation, either version 3 of the License, or
+ *  (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ *  ---
+ *
+ *  See files README, COPYING and CREDITS in the root of the source
+ *  tree for related information and legal notices.  It is expected
+ *  that future projects/authors will amend these files as needed.
+ *
+ *  ---
+ *
+ *  fnameat.h: Interpret file names relative to a directory
+ *
+ *  Known contributors to this file:
+ *     Markus Armbruster, 2015
+ */
+
+#ifndef FNAMEAT_H
+#define FNAMEAT_H
+
+#include <stdio.h>
+
+extern char *fnameat(const char *, const char *);
+extern FILE *fopenat(const char *, const char *, const char *);
+
+#endif
index 0471335bc860a29dcb0ddd543ec0a985ad5b9dad..ec89b40df09d2435bf357e2bd653a8331e9735d7 100644 (file)
@@ -285,8 +285,7 @@ extern int demandupdatecheck(void);
 /* disassoc.c */
 extern int disassoc(void);
 /* fnameat.c */
-extern char *fnameat(const char *, const char *);
-extern FILE *fopenat(const char *, const char *, const char *);
+/* in fnameat.h */
 /* fsize.c */
 extern int fsize(int);
 extern int blksize(int);
diff --git a/m4/my_lib_readline.m4 b/m4/my_lib_readline.m4
new file mode 100644 (file)
index 0000000..37fedec
--- /dev/null
@@ -0,0 +1,40 @@
+AC_DEFUN([MY_LIB_READLINE], [
+    have_readline=no
+    for readline_lib in readline edit editline; do
+       for termcap_lib in "" termlib termcap curses ncurses; do
+         AC_CHECK_LIB([$readline_lib], [add_history],
+             [have_readline=yes; break 2], [], [$termcap_lib])
+       done
+    done
+
+    if test "$have_readline" = yes; then
+       AC_CHECK_HEADER([readline/readline.h], [], [have_readline=no],
+                       [AC_INCLUDES_DEFAULT])
+       AC_CHECK_HEADER([readline/history.h], [], [have_readline=no],
+                       [AC_INCLUDES_DEFAULT])
+    fi
+
+    if test "$have_readline" = yes; then
+       if test "x$termcap_lib" != x; then
+           LIBS="-l$termcap_lib $LIBS"
+       fi
+       LIBS="-l$readline_lib $LIBS"
+       AC_DEFINE([HAVE_LIBREADLINE], [1],
+                 [Define if you have libreadline])
+    fi
+])
+
+AC_DEFUN([MY_WITH_READLINE],
+[
+  AC_ARG_WITH([readline],
+    [AS_HELP_STRING([--with-readline],
+      [support fancy command line editing @<:@default=check@:>@])],
+    [],
+    [with_readline=check])
+  if test "x$with_readline" != xno; then
+      MY_LIB_READLINE
+      if test "x$have_readline$with_readline" = xnoyes; then
+         AC_MSG_FAILURE([--with-readline was given, but test for readline failed])
+      fi
+      with_readline="$have_readline"
+  fi])
index 844bda3e5bf0402d4161c7ffa15a8ede09b1d5e5..f4d33bdfef0335d2782572ad99eebc5c2887ce5a 100644 (file)
@@ -14,11 +14,11 @@ AC_DEFUN([MY_WITH_TERMINFO],
 [
        AC_ARG_WITH([terminfo],
                AS_HELP_STRING([--with-terminfo],
-                       [use terminfo for highlighting (default check)]))
+                       [use terminfo for highlighting @<:@default check@:>@]))
        if test "x$with_terminfo" != xno; then
                MY_CURSES_TERMINFO
                if test "$have_terminfo$with_terminfo" = noyes
-               then AC_MSG_FAILURE([Can't satisfy --with-terminfo])
+               then AC_MSG_FAILURE([--with-terminfo was given, but test for terminfo failed])
                fi
                with_terminfo="$have_terminfo"
        fi
index 4be599412ea7283aa701a6b078a118c4e5c9efd2..764811e6751c29855c357a39520fdda12cb3c9aa 100644 (file)
@@ -5,6 +5,7 @@ empire \- Empire client
 .B empire
 [\fB\-hkruv\fP]
 [\fB\-2\fP \fIoutfile\fP]
+[\fB\-H\fP \fIhistfile\fP]
 [\fB\-s\fP \fI[host:]port\fP]
 [\fIcountry\fP
 [\fIpassword\fP]]
@@ -24,11 +25,21 @@ the thin veneer of civilization that hides the maniac within.
 .B \-h
 Help.  Print brief usage information and exit.
 .TP
+.BI \-H " histfile"
+Load command history from \fIhistfile\fP, and save it back.  Default
+is '~/.empire_history' without \fB\-r\fP, and none with \fB-r\fP.  You
+might want to protect your history file from prying eyes.
+.IP
+Only available when compiled the GNU \fBreadline\fP library.
+.TP
 .B \-k
 If someone else is connected to your country, kill their connection.
 .TP
 .B \-r
-Restricted mode: disable redirections and execute command.
+Restricted mode: disable redirections and execute command.  This is
+useful when you want to grant somebody access to just Empire, but not
+to the host system's user account that runs the client.  Be careful
+with \fB\-H\fP and \fB\-2\fP then.
 .TP
 .BI \-s " [host:]port"
 Specify server \fIhost\fR and \fIport\fR.
@@ -74,8 +85,21 @@ representative.
 .TP
 .I LOGNAME
 Your user name.
+.TP
+.I INPUTRC
+The filename for the \fBreadline\fP startup file, overriding the
+default of \fI~/.inputrc\fP (see \fBREADLINE\fP below).
+.SH READLINE
+When compiled with the GNU \fBreadline\fP library, the client supports
+fancy line editing and persistent history.  Its application name for
+application-specific settings is \fBEmpire\fP.  See the readline
+documentation for details.
 .SH "SEE ALSO"
-\fIemp_server\fR(6).
+\fIemp_server\fR(6), \fIreadline\fR(3).
+.SH FILES
+.TP
+.I ~/.inputrc
+Individual \fBreadline\fP initialization file
 .SH AUTHORS
 .nf
 Primary Author is Dave Pare <mr-frog@scam.berkeley.edu>
@@ -85,6 +109,7 @@ Jeff Anton <anton@postgres.berkeley.edu>
 Markus Armbruster <armbru@pond.sub.org>
 Phill Everson <everson@compsci.bristol.ac.uk>
 Steven Grimm <koreth@ucscb.UCSC.EDU>
+Martin Haukeli <martin.haukeli@gmail.com>
 Lewis R. Jansen <lrj@helios.tn.cornell.edu>
 Mike St. Johns <stjohns@edn-vax.arpa>
 Ron Koenderink <rkoenderink@yahoo.ca>
index 998e737a8ef866db257d02baba6a05f6f09d49c7..e7154c311a97bc4eae8c3a29493025e3a4177a42 100644 (file)
@@ -28,7 +28,7 @@
 #   Makefile.in: Makefile template for configure
 #
 #   Known contributors to this file:
-#      Markus Armbruster, 2005-2013
+#      Markus Armbruster, 2005-2015
 #
 
 CC = @CC@
@@ -53,8 +53,9 @@ srcdir = @srcdir@
 VPATH = @srcdir@
 
 prog = empire$E
-obj = expect.$O host.$O ipglob.$O linebuf.$O login.$O main.$O play.$O  \
-ringbuf.$O secure.$O servcmd.$O termlib.$O version.$O $(LIBOBJS)
+obj = expect.$O fnameat.$O host.$O ipglob.$O linebuf.$O login.$O       \
+main.$O play.$O ringbuf.$O secure.$O servcmd.$O termlib.$O version.$O  \
+$(LIBOBJS)
 
 all: $(prog)
 
@@ -83,7 +84,7 @@ expect.$O: misc.h proto.h
 host.$O: misc.h
 linebuf.$O: linebuf.h
 login.$O: misc.h proto.h
-main.$O: misc.h version.h
+main.$O: fnameat.h misc.h version.h
 play.$O: linebuf.h misc.h proto.h ringbuf.h secure.h
 ringbuf.$O: ringbuf.h
 secure.$O: ringbuf.h secure.h
index 9352989f002a7aadf4652cdc47ebcc9e722c38f6..f529d721d8802479b35314dfac46fe05cbea54c6 100644 (file)
@@ -60,6 +60,7 @@ if test "$Windows_API" = yes; then
        AC_LIBOBJ([w32/w32io])
        AC_LIBOBJ([w32/w32sockets])
 fi
+MY_WITH_READLINE
 
 
 ### Checks for header files.
@@ -97,6 +98,7 @@ AC_OUTPUT
 
 AC_MSG_NOTICE([])
 AC_MSG_NOTICE([-= Configuration summary =-])
+AC_MSG_NOTICE([      readline: $with_readline])
 AC_MSG_NOTICE([      terminfo: $with_terminfo])
 AC_MSG_NOTICE([    EMPIREHOST: $EMPIREHOST])
 AC_MSG_NOTICE([    EMPIREPORT: $EMPIREPORT])
index f7c1c534491c53c0986270acae4fa2af765447ed..9cfba7112ca2b994f9c109b71b05b827f0faaf73 100644 (file)
@@ -30,8 +30,9 @@
  *     Dave Pare, 1986
  *     Steve McClure, 1998
  *     Ron Koenderink, 2004-2007
- *     Markus Armbruster, 2005-2010
+ *     Markus Armbruster, 2005-2015
  *     Tom Dickson-Hunt, 2010
+ *     Martin Haukeli, 2015
  */
 
 #include <config.h>
 #include <string.h>
 #ifdef _WIN32
 #include <windows.h>
+#include <shlobj.h>
 #include "sys/socket.h"
 #else
 #include <pwd.h>
 #endif
 #include <unistd.h>
+#include "fnameat.h"
 #include "misc.h"
 #include "version.h"
 
@@ -55,6 +58,7 @@
 
 struct passwd {
     char *pw_name;
+    char *pw_dir;
 };
 
 static struct passwd *w32_getpw(void);
@@ -72,6 +76,10 @@ print_usage(char *program_name)
           "  -r              Restricted mode, no redirections\n"
           "  -s [HOST:]PORT  Specify server HOST and PORT\n"
           "  -u              Use UTF-8\n"
+#ifdef HAVE_LIBREADLINE
+          "  -H FILE         Load and save command history from FILE\n"
+          "                  (default ~/.empire_history with -r, none without -r)\n"
+#endif /* HAVE_LIBREADLINE */
           "  -h              display this help and exit\n"
           "  -v              display version information and exit\n",
           program_name);
@@ -82,6 +90,7 @@ main(int argc, char **argv)
 {
     int opt;
     char *auxfname = NULL;
+    char *history_file = NULL;
     int send_kill = 0;
     char *host = NULL;
     char *port = NULL;
@@ -90,14 +99,20 @@ main(int argc, char **argv)
     char *country;
     char *passwd;
     char *uname;
+    char *udir;
     char *colon;
     int sock;
 
-    while ((opt = getopt(argc, argv, "2:krs:uhv")) != EOF) {
+    while ((opt = getopt(argc, argv, "2:H:krs:uhv")) != EOF) {
        switch (opt) {
        case '2':
            auxfname = optarg;
            break;
+#ifdef HAVE_LIBREADLINE
+       case 'H':
+           history_file = optarg;
+           break;
+#endif /* HAVE_LIBREADLINE */
        case 'k':
            send_kill = 1;
            break;
@@ -146,7 +161,8 @@ main(int argc, char **argv)
     if (!host)
        host = empirehost;
     uname = getenv("LOGNAME");
-    if (uname == NULL) {
+    udir = getenv("HOME");
+    if (!uname || !udir) {
        struct passwd *pwd;
 
        pwd = getpwuid(getuid());
@@ -154,7 +170,10 @@ main(int argc, char **argv)
            fprintf(stderr, "You don't exist.  Go away\n");
            exit(1);
        }
-       uname = pwd->pw_name;
+       if (!uname)
+           uname = pwd->pw_name;
+       if (!udir)
+           udir = pwd->pw_dir;
     }
     if (*ap) {
        fprintf(stderr, "%s: extra operand %s\n", argv[0], *ap);
@@ -172,10 +191,15 @@ main(int argc, char **argv)
 
     sock = tcp_connect(host, port);
 
+    if (!restricted && !history_file)
+       history_file = ".empire_history";
+    if (history_file)
+       history_file = fnameat(history_file, udir);
+
     if (!login(sock, uname, country, passwd, send_kill, utf8))
        exit(1);
 
-    if (play(sock) < 0)
+    if (play(sock, history_file) < 0)
        exit(1);
 
     return 0;
@@ -183,12 +207,13 @@ main(int argc, char **argv)
 
 #ifdef _WIN32
 /*
- * Get Windows user name
+ * Get Windows user name and directory
  */
 static struct passwd *
 w32_getpw(void)
 {
     static char unamebuf[128];
+    static char udirbuf[MAX_PATH];
     static struct passwd pwd;
     DWORD unamesize;
 
@@ -199,6 +224,9 @@ w32_getpw(void)
            pwd.pw_name = "nobody";
     } else
        pwd.pw_name = "nobody";
+    if (SUCCEEDED(SHGetFolderPathA(NULL, CSIDL_PROFILE, NULL, 0, udirbuf))
+       && strlen(udirbuf) == 0)
+       pwd.pw_dir = udirbuf;
     return &pwd;
 }
 
index c7a476d08509feac1347237cff3e50c4a3573f1c..e92454a1357caf469dbd07c55626ca3a11f89e8e 100644 (file)
@@ -28,6 +28,7 @@
  *
  *  Known contributors to this file:
  *     Steve McClure, 1998
+ *     Markus Armbruster, 2004-2017
  */
 
 #ifndef MISC_H
@@ -41,8 +42,6 @@
 extern char empirehost[];
 extern char empireport[];
 extern int eight_bit_clean;
-extern int input_fd;
-extern int send_eof;
 extern FILE *auxfp;
 extern int restricted;
 
@@ -61,9 +60,10 @@ int parseid(char *);
 int expect(int s, int match, char *buf);
 int tcp_connect(char *, char *);
 int login(int s, char *uname, char *cname, char *cpass, int kill_proc, int);
-int play(int);
+int play(int, char *);
+void prompt(int, char *, char *);
 void sendcmd(int s, char *cmd, char *arg);
-void servercmd(int, char *, int);
+int servercmd(int, char *, int);
 void outch(char);
 
 #ifdef _MSC_VER
index 3f6400177ecbe1f7bc4c0711f83bb900ffffa2ba..87a698662ab5236dd26b3f8f66e9a4cc7e403411 100644 (file)
@@ -27,8 +27,9 @@
  *  play.c: Playing the game
  *
  *  Known contributors to this file:
- *     Markus Armbruster, 2007-2010
+ *     Markus Armbruster, 2007-2017
  *     Ron Koenderink, 2007-2009
+ *     Martin Haukeli, 2015
  */
 
 #include <config.h>
 #include "ringbuf.h"
 #include "secure.h"
 
+#ifdef HAVE_LIBREADLINE
+#include <readline/readline.h>
+#include <readline/history.h>
+#endif
+
+#define EOF_COOKIE "ctld\n"
+#define INTR_COOKIE "aborted\n"
+
+/*
+ * Player input file descriptor
+ * 0 while reading interactive input
+ * >0 while reading a batch file
+ * <0 during error handling
+ */
+static int input_fd;
+
+static volatile sig_atomic_t send_intr; /* need to send INTR_COOKIE */
+
 #ifdef _WIN32
 static CRITICAL_SECTION signal_critical_section;
 static LPCRITICAL_SECTION signal_critical_section_ptr = NULL;
@@ -301,13 +320,6 @@ w32_ring_from_file_or_bounce_buf(struct ring *r, int fd)
 #define sysdep_stdin_init() ((void)0)
 #endif
 
-#define EOF_COOKIE "ctld\n"
-#define INTR_COOKIE "aborted\n"
-
-int input_fd;
-int send_eof;                          /* need to send EOF_COOKIE */
-static volatile sig_atomic_t send_intr; /* need to send INTR_COOKIE */
-
 /*
  * Receive and process server output from @sock.
  * Return number of characters received on success, -1 on error.
@@ -335,7 +347,7 @@ recv_output(int sock)
     static struct lbuf lbuf;
     char buf[4096];
     ssize_t n;
-    int i, ch, len;
+    int i, ch, len, fd;
     char *line;
 
     n = read(sock, buf, sizeof(buf));
@@ -381,7 +393,18 @@ recv_output(int sock)
            len = lbuf_putc(&lbuf, ch);
            if (len) {
                line = lbuf_line(&lbuf);
-               servercmd(id, line, len);
+               fd = servercmd(id, line, len);
+               if (fd < 0) {
+                   /* failed execute */
+                   if (input_fd)
+                       close(input_fd);
+                   input_fd = 0;
+                   send_intr = 1;
+               } else if (fd > 0) {
+                   /* successful execute, switch to batch file */
+                   assert(!input_fd);
+                   input_fd = fd;
+               }
                lbuf_init(&lbuf);
                state = SCANNING_ID;
            }
@@ -397,28 +420,73 @@ recv_output(int sock)
     return n;
 }
 
+#ifdef HAVE_LIBREADLINE
+static int use_readline;
+static char *input_from_rl;
+static int has_rl_input;
+
+static void
+input_handler(char *line)
+{
+    input_from_rl = line;
+    has_rl_input = 1;
+    if (line && *line)
+       add_history(line);
+}
+
+static int
+ring_from_rl(struct ring *inbuf)
+{
+    size_t len;
+    int n;
+
+    assert(has_rl_input && input_from_rl);
+
+    len = strlen(input_from_rl);
+    n = ring_space(inbuf);
+    assert(n);
+
+    if (len >= (size_t)n) {
+       ring_putm(inbuf, input_from_rl, n);
+       memmove(input_from_rl, input_from_rl + n, len - n + 1);
+    } else {
+       ring_putm(inbuf, input_from_rl, len);
+       ring_putc(inbuf, '\n');
+       free(input_from_rl);
+       has_rl_input = 0;
+       n = len + 1;
+    }
+
+    return n;
+}
+#endif /* HAVE_LIBREADLINE */
+
 /*
- * Receive command input from @fd into @inbuf.
+ * Receive player input from @fd into @inbuf.
  * Return 1 on receipt of input, zero on EOF, -1 on error.
  */
 static int
 recv_input(int fd, struct ring *inbuf)
 {
-    static struct lbuf cmdbuf;
-    int n, i, ch;
-    char *line;
+    int n;
     int res = 1;
 
-    n = ring_from_file(inbuf, fd);
+#ifdef HAVE_LIBREADLINE
+    if (fd == 0 && use_readline) {
+       if (!has_rl_input)
+           rl_callback_read_char();
+       if (!has_rl_input)
+           return 1;
+       if (input_from_rl) {
+           n = ring_from_rl(inbuf);
+       } else
+           n = 0;
+    } else
+#endif
+       n = ring_from_file(inbuf, fd);
     if (n < 0)
        return -1;
     if (n == 0) {
-       /* EOF on input */
-       if (lbuf_len(&cmdbuf)) {
-           /* incomplete line */
-           ring_putc(inbuf, '\n');
-           n++;
-       }
        /*
         * Can't put EOF cookie into INBUF here, it may not fit.
         * Leave it to caller.
@@ -426,19 +494,36 @@ recv_input(int fd, struct ring *inbuf)
        res = 0;
     }
 
-    /* copy input to AUXFP etc. */
-    for (i = -n; i < 0; i++) {
-       ch = ring_peek(inbuf, i);
+    return res;
+}
+
+static int
+send_input(int fd, struct ring *inbuf)
+{
+    struct iovec iov[2];
+    int cnt, i, ch;
+    ssize_t res;
+
+    cnt = ring_to_iovec(inbuf, iov);
+    res = writev(fd, iov, cnt);
+    if (res < 0)
+       return res;
+
+    /* Copy input to @auxfp etc. */
+    for (i = 0; i < res; i++) {
+       ch = ring_getc(inbuf);
        assert(ch != EOF);
-       if (ch != '\r' && lbuf_putc(&cmdbuf, ch) > 0) {
-           line = lbuf_line(&cmdbuf);
-           save_input(line);
-           lbuf_init(&cmdbuf);
-       }
+       if (ch != '\r')
+           save_input(ch);
        if (auxfp)
            putc(ch, auxfp);
     }
 
+#ifdef HAVE_LIBREADLINE
+    if (fd == 0 && use_readline && has_rl_input && input_from_rl)
+       ring_from_rl(inbuf);
+#endif
+
     return res;
 }
 
@@ -450,11 +535,12 @@ intr(int sig)
 
 /*
  * Play on @sock.
+ * @history_file is the name of the history file, or null.
  * The session must be in the playing phase.
  * Return 0 when the session ended, -1 on error.
  */
 int
-play(int sock)
+play(int sock, char *history_file)
 {
     /*
      * Player input flows from INPUT_FD through recv_input() into ring
@@ -466,8 +552,10 @@ play(int sock)
     struct ring inbuf;         /* input buffer, draining to SOCK */
     int eof_fd0;               /* read fd 0 hit EOF? */
     int partial_line_sent;     /* partial input line sent? */
+    int send_eof;              /* need to send EOF_COOKIE */
     fd_set rdfd, wrfd;
     int n;
+    int ret = -1;
 
     sa.sa_flags = 0;
     sigemptyset(&sa.sa_mask);
@@ -475,6 +563,17 @@ play(int sock)
     sigaction(SIGINT, &sa, NULL);
     sa.sa_handler = SIG_IGN;
     sigaction(SIGPIPE, &sa, NULL);
+#ifdef HAVE_LIBREADLINE
+    if (isatty(0)) {
+       use_readline = 1;
+       rl_already_prompted = 1;
+       rl_readline_name = "Empire";
+       if (history_file)
+           read_history(history_file);
+       rl_bind_key('\t', rl_insert);  /* Disable tab completion */
+       rl_callback_handler_install("", input_handler);
+    }
+#endif /* HAVE_LIBREADLINE */
 
     ring_init(&inbuf);
     eof_fd0 = partial_line_sent = send_eof = send_intr = 0;
@@ -487,10 +586,11 @@ play(int sock)
 
        /*
         * Want to read player input only when we don't need to send
-        * cookies, and INPUT_FD is still open, and INBUF can accept
+        * cookies, haven't reached EOF on fd 0, and @inbuf can accept
         * some.
         */
-       if (!send_intr && !send_eof && input_fd >= 0 && ring_space(&inbuf))
+       if (!send_intr && !send_eof && (input_fd || !eof_fd0)
+           && ring_space(&inbuf))
            FD_SET(input_fd, &rdfd);
        /* Want to send player input only when we have something */
        if (send_intr || send_eof || ring_len(&inbuf))
@@ -502,7 +602,7 @@ play(int sock)
        if (n < 0) {
            if (errno != EINTR) {
                perror("select");
-               return -1;
+               break;
            }
        }
 
@@ -511,51 +611,52 @@ play(int sock)
            partial_line_sent = 0;
        if (send_eof && !partial_line_sent
            && ring_putm(&inbuf, EOF_COOKIE, sizeof(EOF_COOKIE) - 1) >= 0)
-           send_eof--;
+           send_eof = 0;
        if (send_intr && !partial_line_sent
            && ring_putm(&inbuf, INTR_COOKIE, sizeof(INTR_COOKIE) - 1) >= 0) {
            send_intr = 0;
            if (input_fd) {
                /* execute aborted, switch back to fd 0 */
                close(input_fd);
-               input_fd = eof_fd0 ? -1 : 0;
+               input_fd = 0;
            }
        }
-
        if (n < 0)
            continue;
 
        /* read player input */
-       if (input_fd >= 0 && FD_ISSET(input_fd, &rdfd)) {
+       if (FD_ISSET(input_fd, &rdfd) && ring_space(&inbuf)) {
            n = recv_input(input_fd, &inbuf);
-           if (n < 0) {
-               perror("read stdin"); /* FIXME stdin misleading, could be execing */
-               n = 0;
-           }
-           if (n == 0) {
-               /* EOF on input */
-               send_eof++;
+           if (n <= 0) {
                if (input_fd) {
                    /* execute done, switch back to fd 0 */
+                   if (n < 0) {
+                       perror("read batch file");
+                       send_intr = 1;
+                   } else
+                       send_eof = 1;
                    close(input_fd);
-                   input_fd = eof_fd0 ? -1 : 0;
+                   input_fd = 0;
                } else {
                    /* stop reading input, drain socket ring buffers */
+                   if (n < 0)
+                       perror("read stdin");
+                   send_eof = 1;
                    eof_fd0 = 1;
-                   input_fd = -1;
                    sa.sa_handler = SIG_DFL;
                    sigaction(SIGINT, &sa, NULL);
+                   send_intr = 0;
                }
-           } else
+           } else if (ring_len(&inbuf) > 0)
                partial_line_sent = ring_peek(&inbuf, -1) != '\n';
        }
 
        /* send it to the server */
        if (FD_ISSET(sock, &wrfd)) {
-           n = ring_to_file(&inbuf, sock);
+           n = send_input(sock, &inbuf);
            if (n < 0) {
                perror("write socket");
-               return -1;
+               break;
            }
        }
 
@@ -564,10 +665,43 @@ play(int sock)
            n = recv_output(sock);
            if (n < 0) {
                perror("read socket");
-               return -1;
+               break;
+           }
+           if (n == 0) {
+               ret = 0;
+               break;
            }
-           if (n == 0)
-               return 0;
        }
     }
+
+#ifdef HAVE_LIBREADLINE
+    if (use_readline) {
+       rl_callback_handler_remove();
+       if (history_file)
+           write_history(history_file);
+    }
+#endif
+    return ret;
+}
+
+void
+prompt(int code, char *prompt, char *teles)
+{
+    char pr[1024];
+
+    snprintf(pr, sizeof(pr), "%s%s", teles, prompt);
+#ifdef HAVE_LIBREADLINE
+    if (use_readline) {
+       rl_set_prompt(pr);
+       rl_forced_update_display();
+    } else
+#endif /* HAVE_LIBREADLINE */
+    {
+       printf("%s", pr);
+       fflush(stdout);
+    }
+    if (auxfp) {
+       fprintf(auxfp, "%s%s", teles, prompt);
+       fflush(auxfp);
+    }
 }
index 9e04bc81a2ac357f9f398b187082f6403be289b7..9782d914324770db4f6bd0e799f81a1b222ddcce 100644 (file)
@@ -27,7 +27,7 @@
  *  ringbuf.c: Simple ring buffer
  *
  *  Known contributors to this file:
- *     Markus Armbruster, 2007-2009
+ *     Markus Armbruster, 2007-2017
  */
 
 #include <config.h>
@@ -35,7 +35,6 @@
 #include <assert.h>
 #include <stdio.h>
 #include <string.h>
-#include <sys/uio.h>
 #include "ringbuf.h"
 
 /*
@@ -82,9 +81,17 @@ ring_peek(struct ring *r, int n)
 
     assert(-RING_SIZE - 1 <= n && n <= RING_SIZE);
 
-    idx = n >= 0 ? r->cons + n : r->prod - -n;
-    if (idx < r->cons && idx >= r->prod)
-       return EOF;
+    if (n >= 0) {
+       idx = r->cons + n;
+       if (idx >= r->prod)
+           return EOF;
+    } else {
+       /* Beware, r->prod - -n can wrap around, avoid that */
+       if (r->prod < r->cons + -n)
+           return EOF;
+       idx = r->prod - -n;
+    }
+
     return r->buf[idx % RING_SIZE];
 }
 
@@ -149,16 +156,17 @@ ring_discard(struct ring *r, int n)
 
 /*
  * Search the ring buffer for zero-terminated string S.
- * If found, return a non-negative value referring to the beginning of
- * S in the buffer when passed to ring_peek().  Else return -1.
+ * Start at the @(n+1)-th byte to be gotten.
+ * If found, return the number of bytes in the buffer before S.
+ * Else return -1.
  */
 int
-ring_search(struct ring *r, char *s)
+ring_search(struct ring *r, char *s, int n)
 {
     size_t len = strlen(s);
     size_t i, j;
 
-    for (i = r->cons; i + len <= r->prod; i++) {
+    for (i = r->cons + n; i + len <= r->prod; i++) {
        for (j = 0; s[j] && s[j] == (char)r->buf[(i + j) % RING_SIZE]; j++)
            ;
        if (!s[j])
@@ -206,39 +214,29 @@ ring_from_file(struct ring *r, int fd)
 }
 
 /*
- * Drain ring buffer to file referred by file descriptor @fd.
- * If ring buffer is already empty, do nothing and return 0.
- * Else attempt to write complete contents with writev(), and return
- * its value.
+ * Set up @iov[] to describe complete contents of ring buffer.
+ * @iov[] must have at least two elements.
+ * Return number of elements used (zero for an empty ring buffer).
  */
 int
-ring_to_file(struct ring *r, int fd)
+ring_to_iovec(struct ring *r, struct iovec iov[])
 {
     unsigned cons = r->cons % RING_SIZE;
     unsigned prod = r->prod % RING_SIZE;
-    struct iovec iov[2];
-    int cnt;
-    ssize_t res;
 
     if (r->cons == r->prod)
        return 0;
 
     iov[0].iov_base = r->buf + cons;
-    if (prod <= cons) {
-       /* r->buf[cons..] */
-       iov[0].iov_len = RING_SIZE - cons;
-       /* r->buf[..prod-1] */
-       iov[1].iov_base = r->buf;
-       iov[1].iov_len = prod;
-       cnt = 2;
-    } else {
+    if (prod > cons) {
        /* r->buf[cons..prod-1] */
        iov[0].iov_len = prod - cons;
-       cnt = 1;
+       return 1;
     }
-    res = writev(fd, iov, cnt);
-    if (res < 0)
-       return res;
-    r->cons += res;
-    return res;
+    /* r->buf[cons..] */
+    iov[0].iov_len = RING_SIZE - cons;
+    /* r->buf[..prod-1] */
+    iov[1].iov_base = r->buf;
+    iov[1].iov_len = prod;
+    return 2;
 }
index a3794e8bd80fe00808432a4b64c6d270344b429d..9d1a58bf8a620ac1531bc5fa79ba2bb16397eca5 100644 (file)
  *  ringbuf.h: Simple ring buffer
  *
  *  Known contributors to this file:
- *     Markus Armbruster, 2007
+ *     Markus Armbruster, 2007-2017
  */
 
 #ifndef RINGBUF_H
 #define RINGBUF_H
 
 #include <stddef.h>
+#include <sys/uio.h>
 
 #define RING_SIZE 4096
 
@@ -59,8 +60,8 @@ extern int ring_getc(struct ring *);
 extern int ring_putc(struct ring *, unsigned char);
 extern int ring_putm(struct ring *, void *, size_t);
 extern void ring_discard(struct ring *, int);
-extern int ring_search(struct ring *, char *);
+extern int ring_search(struct ring *, char *, int);
 extern int ring_from_file(struct ring *, int fd);
-extern int ring_to_file(struct ring *, int fd);
+extern int ring_to_iovec(struct ring *, struct iovec[]);
 
 #endif
index 808301882e217870be088dc8471b349c38b081e0..14bb6d1fe27ef93eef90a16b8371177debffb51b 100644 (file)
  *  secure.c: Check redir etc. to protect against tampering deity
  *
  *  Known contributors to this file:
- *     Markus Armbruster, 2007
+ *     Markus Armbruster, 2007-2017
  */
 
 #include <config.h>
 
 #include <assert.h>
+#include <ctype.h>
+#include <stdio.h>
 #include <string.h>
 #include "ringbuf.h"
 #include "secure.h"
 
 static struct ring recent_input;
-static size_t saved_bytes;
 
 /*
- * Remember line of input @inp for a while.
- * It must end with a newline.
- * Return value is suitable for forget_input(): it makes it forget all
- * input up to and including this line.
+ * Remember input @inp for a while.
  */
-size_t
-save_input(char *inp)
+void
+save_input(char inp)
 {
-    size_t len = strlen(inp);
     int eol;
 
-    assert(len && inp[len - 1] == '\n');
-
-    while (ring_putm(&recent_input, inp, len) < 0) {
-       eol = ring_search(&recent_input, "\n");
+    while (ring_putc(&recent_input, inp) < 0) {
+       eol = ring_search(&recent_input, "\n", 0);
        assert(eol >= 0);
        ring_discard(&recent_input, eol + 1);
     }
-    saved_bytes += len;
-    return saved_bytes;
 }
 
 /*
  * Can you still remember a line of input that ends with @tail?
  * It must end with a newline.
- * Return non-zero iff @tail can be remembered.
- * Passing that value to forget_input() will forget all input up to
- * and including this line.
  */
-size_t
+int
 seen_input(char *tail)
 {
     size_t len = strlen(tail);
-    size_t remembered = ring_len(&recent_input);
-    int dist;
 
     assert(len && tail[len - 1] == '\n');
-
-    dist = ring_search(&recent_input, tail);
-    if (dist < 0)
-       return 0;
-
-    assert(dist + len <= remembered && remembered <= saved_bytes);
-    return saved_bytes - remembered + dist + len;
+    return ring_search(&recent_input, tail, 0) >= 0;
 }
 
 /*
- * Forget remembered input up to @seen.
- * @seen should be obtained from save_input() or seen_input().
+ * Can you still remember input that looks like an execute @arg?
+ * @arg must end with a newline.
  */
-void
-forget_input(size_t seen)
+int
+seen_exec_input(char *arg)
 {
-    size_t forgotten = saved_bytes - ring_len(&recent_input);
+    size_t len = strlen(arg);
+    int n, i, j, ch;
+    unsigned char buf[RING_SIZE + 1];
+
+    assert(len && arg[len - 1] == '\n');
+
+    n = 1;
+    for (;;) {
+       /* find next line ending with arg */
+       n = ring_search(&recent_input, arg, n + 1);
+       if (n <= 0)
+           return 0;
 
-    assert(seen);
+       /* extract command (same or previous line) */
+       i = n - 1;
+       if (ring_peek(&recent_input, i) == '\n')
+           i--;
+       j = sizeof(buf);
+       buf[--j] = 0;
+       for (; i >= 0 && (ch = ring_peek(&recent_input, i)) != '\n'; i--)
+           buf[--j] = ch;
 
-    if (seen > forgotten) {
-       assert(ring_peek(&recent_input, seen - forgotten - 1) == '\n');
-       ring_discard(&recent_input, seen - forgotten);
+       /* compare command */
+       for (; isspace(buf[j]); j++) ;
+       for (i = j; buf[i] && !isspace(buf[i]); i++) ;
+       if (i - j >= 2 && !strncmp("execute", (char *)buf + j, i - j))
+           return 1;
     }
 }
index 425cd797654878a1db7ab5c89a6f786d53ab4978..60986ac5047daa7cf15b560f041b2265f5927576 100644 (file)
@@ -27,7 +27,7 @@
  *  secure.h: Check redir etc. to protect against tampering deity
  *
  *  Known contributors to this file:
- *     Markus Armbruster, 2007-2009
+ *     Markus Armbruster, 2007-2017
  */
 
 #ifndef SECURE_H
@@ -35,8 +35,8 @@
 
 #include <stddef.h>
 
-extern size_t save_input(char *);
-extern size_t seen_input(char *);
-extern void forget_input(size_t);
+extern void save_input(char);
+extern int seen_input(char *);
+extern int seen_exec_input(char *);
 
 #endif
index f2d0e3fc6334b44ec44ce4d08077417fbd637090..fd83f90362fa5aa9b08666817b4534c0f2ed9411 100644 (file)
@@ -30,7 +30,7 @@
  *     Dave Pare, 1989
  *     Steve McClure, 1998
  *     Ron Koenderink, 2005
- *     Markus Armbruster, 2005-2010
+ *     Markus Armbruster, 2005-2017
  */
 
 #include <config.h>
@@ -41,6 +41,7 @@
 #include <fcntl.h>
 #include <stdio.h>
 #include <string.h>
+#include <unistd.h>
 #include "misc.h"
 #include "proto.h"
 #include "secure.h"
@@ -52,14 +53,12 @@ int restricted;
 static FILE *redir_fp;
 static int redir_is_pipe;
 static int executing;
-static size_t input_to_forget;
 
-static void prompt(int, char *, char *);
 static void doredir(char *p);
 static void dopipe(char *p);
 static int doexecute(char *p);
 
-void
+int
 servercmd(int code, char *arg, int len)
 {
     static int nmin, nbtu, fd;
@@ -69,7 +68,8 @@ servercmd(int code, char *arg, int len)
     switch (code) {
     case C_PROMPT:
        if (sscanf(arg, "%d %d", &nmin, &nbtu) != 2) {
-           fprintf(stderr, "prompt: bad server prompt %s\n", arg);
+           fprintf(stderr, "Warning: server sent malformed prompt %s",
+                   arg);
        }
        snprintf(the_prompt, sizeof(the_prompt), "[%d:%d] Command : ",
                 nmin, nbtu);
@@ -80,10 +80,7 @@ servercmd(int code, char *arg, int len)
                (void)fclose(redir_fp);
            redir_fp = NULL;
        }
-       if (input_to_forget) {
-           forget_input(input_to_forget);
-           input_to_forget = 0;
-       }
+       outch('\n');
        prompt(code, the_prompt, teles);
        executing = 0;
        break;
@@ -93,13 +90,9 @@ servercmd(int code, char *arg, int len)
        break;
     case C_EXECUTE:
        fd = doexecute(arg);
-       if (fd < 0)
-           send_eof++;
-       else {
-           input_fd = fd;
+       if (fd >= 0)
            executing = 1;
-       }
-       break;
+       return fd;
     case C_EXIT:
        printf("Exit: %s", arg);
        if (auxfp)
@@ -114,6 +107,7 @@ servercmd(int code, char *arg, int len)
        if (arg[0] != '\n') {
            snprintf(teles, sizeof(teles), "(%.*s) ", len - 1, arg);
            if (!redir_fp) {
+               outch('\n');
                putchar('\07');
                prompt(code, the_prompt, teles);
            }
@@ -130,20 +124,8 @@ servercmd(int code, char *arg, int len)
        assert(0);
        break;
     }
-}
 
-static void
-prompt(int code, char *prompt, char *teles)
-{
-    char *nl;
-
-    nl = code == C_PROMPT || code == C_INFORM ? "\n" : "";
-    printf("%s%s%s", nl, teles, prompt);
-    fflush(stdout);
-    if (auxfp) {
-       fprintf(auxfp, "%s%s%s", nl, teles, prompt);
-       fflush(auxfp);
-    }
+    return 0;
 }
 
 static char *
@@ -158,10 +140,8 @@ fname(char *s)
 }
 
 static int
-redir_authorized(char *arg, char *attempt, int expected)
+common_authorized(char *arg, char *attempt)
 {
-    size_t seen = seen_input(arg);
-
     if (restricted) {
        fprintf(stderr, "Can't %s in restricted mode\n", attempt);
        return 0;
@@ -171,20 +151,37 @@ redir_authorized(char *arg, char *attempt, int expected)
        fprintf(stderr, "Can't %s in a batch file\n", attempt);
        return 0;
     }
+    return 1;
+}
 
-    if (!expected) {
-       fprintf(stderr, "WARNING!  Server attempted to %s unexpectedly\n",
-               attempt);
+static int
+redir_authorized(char *arg, char *attempt)
+{
+    if (redir_fp) {
+       fprintf(stderr, "Warning: dropped conflicting %s %s",
+               attempt, arg);
        return 0;
     }
 
-    if (!seen || (input_to_forget && input_to_forget != seen)) {
-       fprintf(stderr, "WARNING!  Server attempted to %s %s\n",
+    if (!seen_input(arg)) {
+       fprintf(stderr, "Warning: server attempted to %s %s",
                attempt, arg);
        return 0;
     }
-    input_to_forget = seen;
-    return 1;
+
+    return common_authorized(arg, attempt);
+}
+
+static int
+exec_authorized(char *arg)
+{
+    if (!seen_exec_input(arg)) {
+       fprintf(stderr,
+               "Warning: server attempted to execute batch file %s", arg);
+       return 0;
+    }
+
+    return common_authorized(arg, "execute batch file");
 }
 
 static void
@@ -193,10 +190,10 @@ doredir(char *p)
     int mode;
     int fd;
 
-    if (!redir_authorized(p, "redirect to file", !redir_fp))
+    if (!redir_authorized(p, "redirect to file"))
        return;
     if (*p++ != '>') {
-       fprintf(stderr, "WARNING!  Weird redirection %s", p);
+       fprintf(stderr, "Warning: dropped weird redirection %s", p);
        return;
     }
 
@@ -228,10 +225,10 @@ doredir(char *p)
 static void
 dopipe(char *p)
 {
-    if (!redir_authorized(p, "pipe to shell command", !redir_fp))
+    if (!redir_authorized(p, "pipe to shell command"))
        return;
     if (*p++ != '|') {
-       fprintf(stderr, "WARNING!  Weird pipe %s", p);
+       fprintf(stderr, "Warning: dropped weird pipe %s", p);
        return;
     }
 
@@ -241,10 +238,14 @@ dopipe(char *p)
        return;
     }
 
+    /* strip newline */
+    p[strlen(p) - 1] = 0;
+
     redir_is_pipe = 1;
+    errno = 0;
     if ((redir_fp = popen(p, "w")) == NULL) {
-       fprintf(stderr, "Can't redirect to pipe %s%s\n",
-               p, strerror(errno));
+       fprintf(stderr, "Can't redirect to pipe %s%s%s\n",
+               p, errno ? ": " : "", errno ? strerror(errno) : "");
     }
 }
 
@@ -253,7 +254,7 @@ doexecute(char *p)
 {
     int fd;
 
-    if (!redir_authorized(p, "execute batch file", 1))
+    if (!exec_authorized(p))
        return -1;
 
     p = fname(p);
@@ -263,7 +264,7 @@ doexecute(char *p)
     }
 
     if ((fd = open(p, O_RDONLY)) < 0) {
-       fprintf(stderr, "Can't open execute file %s: %s\n",
+       fprintf(stderr, "Can't open batch file %s: %s\n",
                p, strerror(errno));
        return -1;
     }
index 72f1d62d25c568cc428b29904e682f7096100cd9..ae79d13cbeb41e23cc76efbf96b3096870b11890 100644 (file)
@@ -35,6 +35,7 @@
 #include <errno.h>
 #include <stdio.h>
 #include "file.h"
+#include "fnameat.h"
 #include "optlist.h"
 #include "prototypes.h"
 #include "xdump.h"
index fa9fb4f59203424557bf132ad5ce166a78732a6a..90299ddb2a50c1e5c5aab9a7c3cb47c9203a3ded 100644 (file)
@@ -50,6 +50,7 @@
 #include <string.h>
 #include <unistd.h>
 
+#include "fnameat.h"
 #include "misc.h"
 #include "optlist.h"
 #include "prototypes.h"
index 098b0322463aed83c9f73a3ae8e85bc101fe2fde..5a44bb25fea2251fab45f481341124255f139646 100644 (file)
@@ -33,7 +33,9 @@
 #include <config.h>
 
 #include <errno.h>
-#include "prototypes.h"
+#include <stdlib.h>
+#include <string.h>
+#include "fnameat.h"
 
 static int fname_is_abs(const char *);