]> 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)
16 files changed:
1  2 
Make.mk
configure.ac
include/prototypes.h
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 --combined Make.mk
index 5746cc380e69a226303af8da7300647bfa731fb0,6ea44d09c4cd1504f016c5afd41586cc6d65c98f..8c921d3091a4ace436247923307745231d3d5ce5
+++ b/Make.mk
@@@ -1,6 -1,6 +1,6 @@@
  #
  #   Empire - A multi-player, client/server Internet based war game.
 -#   Copyright (C) 1986-2015, Dave Pare, Jeff Bailey, Thomas Ruschak,
 +#   Copyright (C) 1986-2016, Dave Pare, Jeff Bailey, Thomas Ruschak,
  #                 Ken Stevens, Steve McClure, Markus Armbruster
  #
  #   Empire is free software: you can redistribute it and/or modify
@@@ -27,7 -27,7 +27,7 @@@
  #   Make.mk: The real Makefile, included by GNUmakefile
  #
  #   Known contributors to this file:
 -#      Markus Armbruster, 2005-2015
 +#      Markus Armbruster, 2005-2016
  #
  
  # This makefile was inspired by `Recursive Make Considered Harmful',
@@@ -47,13 -47,8 +47,13 @@@ all
  # Source files
  ifeq ($(revctrl),git)
  src := $(shell cd $(srcdir) && git ls-files | uniq)
 +version := $(shell cd $(srcdir) && build-aux/git-version-gen /dev/null)
  else
  include $(srcdir)/sources.mk
 +version := $(shell cat $(srcdir)/.tarball-version || echo "UNKNOWN")
 +endif
 +ifeq ($(version),UNKNOWN)
 +$(error cannot figure out version)
  endif
  dirs := $(sort $(dir $(src)))
  csrc := $(filter %.c, $(src))
@@@ -106,8 -101,10 +106,8 @@@ subst.in = sed 
  
  # Generated files
  # See `Cleanliness' below
 -mk :=
 -ifeq ($(revctrl),git)
 -mk += $(srcdir)/sources.mk
 -endif
 +# Generated makefiles, distributed by dist-source from $(srcdir):
 +mk := sources.mk
  # Generated by Autoconf, not distributed:
  ac := config.h config.log config.status info.html info.nr lib stamp-h
  ac += $(basename $(filter %.in, $(src)))
@@@ -128,6 -125,7 +128,6 @@@ client := src/client/empire$(EXEEXT
  server := src/server/emp_server$(EXEEXT)
  # Info subjects:
  tsubj := $(addprefix info/, $(addsuffix .t, $(subjects)))
 -ttop := info/TOP.t
  # Formatted info:
  info.nr := $(addprefix info.nr/, $(info))
  info.html := $(addprefix info.html/, $(addsuffix .html, $(info)))
@@@ -158,14 -156,11 +158,14 @@@ endi
  # Each generated file should be in one of the following sets.
  # Removed by clean:
  clean := $(obj) $(deps) $(libs) $(util) $(client) $(server) $(tsubj)  \
 -$(ttop) $(info.all) $(empth_obj) $(empth_lib) sandbox
 +info/toc info/TOP.t $(info.all) $(empth_obj) $(empth_lib) sandbox
  # Removed by distclean:
 -distclean := $(ac) $(mk)
 +distclean := $(ac)
 +ifeq ($(revctrl),git)
 +distclean += $(addprefix $(srcdir)/, $(mk))
 +endif
  # Distributed by dist-source from $(srcdir):
 -src_distgen := $(acdist)
 +src_distgen := $(acdist) $(mk)
  # Distributed by dist-client from $(srcdir)/src/client; removed by distclean:
  cli_distgen := $(acdistcli)
  
@@@ -265,8 -260,6 +265,8 @@@ ifeq ($(empthread),LWP
        $(srcdir)/tests/torpedo-test $(srcdir)
        $(srcdir)/tests/bridgefall-test $(srcdir)
        $(srcdir)/tests/retreat-test $(srcdir)
 +      $(srcdir)/tests/update-test $(srcdir)
 +      $(srcdir)/tests/version-test $(srcdir)
  else
        @echo "$(srcdir)/tests/smoke-test SKIPPED"
        @echo "$(srcdir)/tests/actofgod-test SKIPPED"
        @echo "$(srcdir)/tests/torpedo-test SKIPPED"
        @echo "$(srcdir)/tests/bridgefall-test SKIPPED"
        @echo "$(srcdir)/tests/retreat-test SKIPPED"
 +      @echo "$(srcdir)/tests/update-test SKIPPED"
 +      @echo "$(srcdir)/tests/version-test SKIPPED"
  endif
        $(srcdir)/tests/empdump-test $(srcdir)
  
@@@ -323,7 -314,7 +323,7 @@@ info.html/%.html: info/%.
  $(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)
@@@ -338,19 -329,6 +338,19 @@@ $(libs) $(empth_lib)
        $(call quiet-command,$(AR) rc $@ $?,AR $@)
        $(RANLIB) $@
  
 +src/lib/global/version.o: CPPFLAGS += -DVERSION='"$(version)"'
 +src/lib/global/version.o: $(src)
 +
 +ifneq ($(revctrl),git)
 +$(srcdir)/.tarball-version: $(src)
 +      v=`sed -e 's/-dirty$$//' <$@`; echo "$$v-dirty" >$@
 +# Force Make to start over after updating .tarball-version, so that
 +# $(version) gets the new value
 +$(srcdir)/.dirty-stamp: .tarball-version
 +      >$@
 +include $(srcdir)/.dirty-stamp
 +endif
 +
  # Info formatting
  
  # mksubj.pl reads $(tsrc) and writes $(tsubj).  A naive rule
@@@ -364,7 -342,7 +364,7 @@@ info/stamp-subj: info/mksubj.pl $(tsrc
        $(call quiet-command,perl $(srcdir)/info/mksubj.pl $(subjects) $(filter %.t, $^),GEN '$(tsubj) info/toc')
        >$@
  
 -$(ttop): info/mktop.pl info/subjects.mk
 +info/TOP.t: info/mktop.pl info/subjects.mk
        $(call quiet-command,perl $(srcdir)/info/mktop.pl $@ $(subjects),GEN $@)
  
  info.nr/all: $(filter-out info.nr/all, $(info.nr))
@@@ -378,28 -356,26 +378,29 @@@ $(info.nr): info/CRT.MAC info/INFO.MAC 
  
  $(info.html): info/emp2html.pl
  
 -info.ps: info/TROFF.MAC info/INFO.MAC $(ttop) $(tsubj) $(tsrc)
 +info.ps: info/TROFF.MAC info/INFO.MAC info/TOP.t $(tsubj) $(tsrc)
        groff $^ >$@
  
  # Distributing
  
  .PHONY: dist-source
  dist-source: $(src_distgen)
 +      $(tarball) $(TARNAME) $(version) -C $(srcdir) $(src_distgen) $(src)
 +
  ifeq ($(revctrl),git)
 -      echo "src := $(src)" >$(srcdir)/sources.mk
 +.PHONY: $(srcdir)/sources.mk
 +$(srcdir)/sources.mk:
 +      $(call quiet-command,echo "src := $(src)" >$@,GEN $@)
  endif
 -      $(tarball) $(TARNAME)-$(VERSION) -C $(srcdir) $(src_distgen) $(src) sources.mk
  
  .PHONY: dist-client
  dist-client: $(cli_distgen)
 -      $(tarball) $(TARNAME)-client-$(VERSION)                         \
 +      $(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                               \
  
  .PHONY: dist-info
  dist-info: info html
 -      $(tarball) $(TARNAME)-info-text-$(VERSION) -C info.nr $(info)
 -      $(tarball) $(TARNAME)-info-html-$(VERSION) -C info.html $(addsuffix .html, $(info))
 +      $(tarball) $(TARNAME)-info-text $(version) -C info.nr $(info)
 +      $(tarball) $(TARNAME)-info-html $(version) -C info.html $(addsuffix .html, $(info))
  
  # Dependencies
  
@@@ -457,5 -433,5 +458,5 @@@ $(srcdir)/src/client/config.h.in: src/c
        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 $^ >$@
diff --combined configure.ac
index 8c1c6cd281bd056cad73b92fbbf42974ee4bbf18,7e56c02af49a5fb31dc053896b5368164ae79d0d..a30010816ad7bd080f5787fc6e447607dbbd684b
@@@ -1,6 -1,6 +1,6 @@@
  #
  #   Empire - A multi-player, client/server Internet based war game.
 -#   Copyright (C) 1986-2015, Dave Pare, Jeff Bailey, Thomas Ruschak,
 +#   Copyright (C) 1986-2016, Dave Pare, Jeff Bailey, Thomas Ruschak,
  #                 Ken Stevens, Steve McClure, Markus Armbruster
  #
  #   Empire is free software: you can redistribute it and/or modify
@@@ -27,7 -27,7 +27,7 @@@
  #   configure.ac: Autoconf input file
  #
  #   Known contributors to this file:
 -#      Markus Armbruster, 2005-2015
 +#      Markus Armbruster, 2005-2016
  #
  # Process this file with autoconf to produce a configure script.
  
  # POSIX, and when something breaks on some oddball machine, see
  # whether it's worth fixing.
  
 -AC_PREREQ(2.64)
 -AC_INIT([Wolfpack Empire], [4.3.34], [wolfpack@wolfpackempire.com], [empire],
 +AC_PREREQ(2.69)
 +AC_INIT([Wolfpack Empire],
 +      m4_esyscmd([build-aux/git-version-gen .tarball-version]),
 +      [wolfpack@wolfpackempire.com], [empire],
        [http://www.wolfpackempire.com/])
  AC_CONFIG_SRCDIR([include/combat.h])
  AC_CONFIG_AUX_DIR([build-aux])
@@@ -60,13 -58,9 +60,13 @@@ AC_ARG_VAR(NROFF, [nroff command]
  AC_CHECK_PROG(NROFF, groff, [GROFF_NO_SGR= groff -Tascii -U], nroff)
  
  # not really a check for a program, but close enough
 -if test -d $srcdir/.git; then revctrl=git
 +if test -d $srcdir/.git
 +then revctrl=git
  else revctrl=
  fi
 +if test "$revctrl" && test -r .tarball-version
 +then AC_MSG_ERROR([.tarball-version must not exist])
 +fi
  AC_SUBST(revctrl,$revctrl)
  
  # Not a program, but need to check this early
@@@ -79,6 -73,7 +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 -235,7 +241,7 @@@ AC_OUTPU
  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 --combined include/prototypes.h
index 0471335bc860a29dcb0ddd543ec0a985ad5b9dad,6fca449d3331f5c97d949b7a04073c5e88265683..ec89b40df09d2435bf357e2bd653a8331e9735d7
@@@ -1,6 -1,6 +1,6 @@@
  /*
   *  Empire - A multi-player, client/server Internet based war game.
 - *  Copyright (C) 1986-2015, Dave Pare, Jeff Bailey, Thomas Ruschak,
 + *  Copyright (C) 1986-2016, Dave Pare, Jeff Bailey, Thomas Ruschak,
   *                Ken Stevens, Steve McClure, Markus Armbruster
   *
   *  Empire is free software: you can redistribute it and/or modify
@@@ -59,6 -59,7 +59,6 @@@ extern int edit_sect_i(struct sctstr *
  extern int load_comm_ok(struct sctstr *, natid, i_type, int);
  extern void gift(natid, natid, void *, char *);
  extern int display_mark(i_type, int);
 -extern int count_pop(int);
  extern int line_of_sight(char **rad, int ax, int ay, int bx, int by);
  extern void plane_sona(struct emp_qelem *, int, int, struct shiplist **);
  extern char *prsub(struct shpstr *);
@@@ -225,6 -226,7 +225,6 @@@ int togg(void)
  int torp(void);
  int trad(void);
  int tran(void);
 -int trea(void);
  int turn(void);
  int upda(void);
  int upgr(void);
@@@ -234,6 -236,7 +234,6 @@@ int wing(void)
  int wipe(void);
  int work(void);
  int xdump(void);
 -int xedit(void);
  int zdon(void);
  
  /*
@@@ -267,7 -270,6 +267,7 @@@ extern int read_schedule(char *, time_t
  /* res_pop.c */
  extern int max_population(float, int, int);
  extern int max_pop(float, struct sctstr *);
 +extern int max_workers(float, struct sctstr *);
  /* stmtch.c */
  /* in match.h */
  /* type.c */
@@@ -285,8 -287,7 +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);
@@@ -399,7 -400,6 +398,7 @@@ extern int check_trade_ok(struct trdst
  /* coastal.c */
  extern void set_coastal(struct sctstr *, int, int);
  /* control.c */
 +extern double security_strength(struct sctstr *, int *);
  extern int military_control(struct sctstr *);
  extern int abandon_askyn(struct sctstr *, i_type, int, struct ulist *);
  extern int would_abandon(struct sctstr *, i_type, int, struct ulist *);
@@@ -418,6 -418,8 +417,6 @@@ extern int commdamage(int, int, i_type)
  extern int detonate(struct nukstr *, coord, coord, int);
  /* disloan.c */
  extern int disloan(int, struct lonstr *);
 -/* distrea.c */
 -extern int distrea(int, struct trtstr *);
  /* fileinit.c */
  extern void ef_init_srv(int);
  extern void ef_fin_srv(void);
@@@ -443,13 -445,14 +442,13 @@@ extern int roundrange(double)
  /* list.c */
  extern int shipsatxy(coord, coord, int, int, int);
  extern int carriersatxy(coord, coord, natid);
 -extern int unitsatxy(coord, coord, int, int, int);
 +extern int unitsatxy(coord, coord, int, int);
  extern int planesatxy(coord, coord, int, int);
  extern int asw_shipsatxy(coord, coord, int, int, struct plnstr *,
                         struct shiplist **);
  extern void print_shiplist(struct shiplist *);
  extern int has_units(coord, coord, natid);
  extern int adj_units(coord, coord, natid);
 -extern int islist(char *);
  /* maps.c */
  /* in map.h */
  /* mission.c */
@@@ -563,7 -566,9 +562,7 @@@ extern int delty(struct range *, coord)
  extern void radmap(int, int, int, double, int, double);
  extern void rad_map_set(natid, int, int, int, double, int);
  /* rej.c */
 -extern void setrel(natid, natid, int);
 -extern void setcont(natid, natid, int);
 -extern void setrej(natid, natid, int, int);
 +/* in nat.h */
  /* retreat.c */
  /* in retreat.h */
  /* sarg.c */
@@@ -630,6 -635,8 +629,6 @@@ extern void takeover_ship(struct shpst
  extern void takeover_plane(struct plnstr *, natid);
  /* trdsub.c */
  /* in trade.h */
 -/* trechk.c */
 -extern int trechk(natid, natid, int);
  /* whatitem.c */
  /* in item.h */
  /* wu.c */
@@@ -640,7 -647,78 +639,7 @@@ extern int wu(natid, natid, char *, ...
  /*
   * src/lib/update/ *.c
   */
 -/* age.c */
 -extern int age_people(int, int);
 -extern void age_levels(int);
 -/* anno.c */
 -extern void delete_old_announcements(void);
 -/* bp.c */
 -/* in budg.h */
 -/* deliver.c */
 -extern void dodeliver(struct sctstr *);
 -/* distribute.c */
 -extern int dodistribute(struct sctstr *, int, double);
 -/* finish.c */
 -extern void finish_sects(int);
 -/* human.c */
 -extern int new_work(struct sctstr *, int);
 -extern int do_feed(struct sctstr *, struct natstr *, short *, int *, int);
 -extern int feed_people(short *, int);
 -extern double food_needed(short *, int);
 -extern int famine_victims(short *, int);
 -/* land.c */
 -extern int prod_land(int, int, struct bp *, int);
 -/* main.c */
 -/* in server.h */
 -/* material.c */
 -/* in budg.h */
 -/* mobility.c */
 -extern void mob_sect(void);
 -extern void mob_ship(void);
 -extern void mob_land(void);
 -extern void mob_plane(void);
 -extern void sct_do_upd_mob(struct sctstr *sp);
 -extern void shp_do_upd_mob(struct shpstr *sp);
 -extern void lnd_do_upd_mob(struct lndstr *lp);
 -extern void pln_do_upd_mob(struct plnstr *pp);
 -/* move_sat.c */
 -extern void move_sat(struct plnstr *);
 -/* nat.c */
 -extern void prod_nat(int);
 -/* nxtitemp.c */
 -/* in nsc.h */
 -/* plague.c */
 -extern void do_plague(struct sctstr *, struct natstr *, int);
 -extern int plague_people(struct natstr *, short *, int *, int *, int);
 -/* plane.c */
 -extern int prod_plane(int, int, struct bp *, int);
 -/* populace.c */
 -extern void populace(struct natstr *, struct sctstr *, int);
 -extern int total_work(int, int, int, int, int, int);
 -/* prepare.c */
 -extern void tax(struct sctstr *, struct natstr *, int, int *, int *,
 -              int *, int *);
 -extern int upd_slmilcosts(natid, int);
 -extern void prepare_sects(int, struct bp *);
 -extern int bank_income(struct sctstr *, int);
 -/* produce.c */
 -extern int produce(struct natstr *, struct sctstr *, short *, int, int,
 -                 int, int *, int *);
 -extern int prod_materials_cost(struct pchrstr *, short[], int *);
 -extern int prod_resource_limit(struct pchrstr *, unsigned char *);
 -extern double prod_eff(int, float);
 -/* removewants.c */
 -extern int update_removewants(void);
 -/* revolt.c */
 -extern void revolt(struct sctstr *);
 -extern void guerrilla(struct sctstr *);
 -/* sect.c */
 -extern void do_fallout(struct sctstr *, int);
 -extern void spread_fallout(struct sctstr *, int);
 -extern void decay_fallout(struct sctstr *, int);
 -extern void produce_sect(int, int, struct bp *, int[][2]);
 -/* ship.c */
 -extern int prod_ship(int, int, struct bp *, int);
 +/* in update.h */
  
  /*
   * src/server
diff --combined src/client/Makefile.in
index 998e737a8ef866db257d02baba6a05f6f09d49c7,f794c2fd72644f9ccf2e7bbb39e9d5326b8b7eea..e7154c311a97bc4eae8c3a29493025e3a4177a42
@@@ -1,6 -1,6 +1,6 @@@
  #
  #   Empire - A multi-player, client/server Internet based war game.
 -#   Copyright (C) 1986-2015, Dave Pare, Jeff Bailey, Thomas Ruschak,
 +#   Copyright (C) 1986-2016, Dave Pare, Jeff Bailey, Thomas Ruschak,
  #                 Ken Stevens, Steve McClure, Markus Armbruster
  #
  #   Empire is free software: you can redistribute it and/or modify
@@@ -28,7 -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 +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 +84,7 @@@ expect.$O: misc.h proto.
  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
diff --combined src/client/configure.ac
index 9352989f002a7aadf4652cdc47ebcc9e722c38f6,2baebcad007060219fff2ed0b1d82038db099822..f529d721d8802479b35314dfac46fe05cbea54c6
@@@ -1,6 -1,6 +1,6 @@@
  #
  #   Empire - A multi-player, client/server Internet based war game.
 -#   Copyright (C) 1986-2015, Dave Pare, Jeff Bailey, Thomas Ruschak,
 +#   Copyright (C) 1986-2016, Dave Pare, Jeff Bailey, Thomas Ruschak,
  #                 Ken Stevens, Steve McClure, Markus Armbruster
  #
  #   Empire is free software: you can redistribute it and/or modify
@@@ -27,7 -27,7 +27,7 @@@
  #   configure.ac: Autoconf input file
  #
  #   Known contributors to this file:
 -#      Markus Armbruster, 2005-2015
 +#      Markus Armbruster, 2005-2016
  #
  # Process this file with autoconf to produce a configure script.
  
  # POSIX, and when something breaks on some oddball machine, see
  # whether it's worth fixing.
  
 -AC_PREREQ(2.64)
 -AC_INIT([Empire Client], [4.3.34], [wolfpack@wolfpackempire.com],,
 +AC_PREREQ(2.69)
 +AC_INIT([Wolfpack Empire Client],
 +      m4_esyscmd([cat .tarball-version]),
 +      [wolfpack@wolfpackempire.com],,
        [http://www.wolfpackempire.com/])
  AC_CONFIG_SRCDIR([empire.6])
  AC_CONFIG_HEADERS([config.h])
@@@ -60,6 -58,7 +60,7 @@@ if test "$Windows_API" = yes; the
        AC_LIBOBJ([w32/w32io])
        AC_LIBOBJ([w32/w32sockets])
  fi
+ MY_WITH_READLINE
  
  
  ### Checks for header files.
@@@ -90,13 -89,12 +91,14 @@@ MY_WITH_TERMINF
  
  ### Output
  
 +AC_DEFINE_UNQUOTED(VERSION, "`cat .tarball-version`", [Tarball version])
 +
  AC_CONFIG_FILES([Makefile ipglob.c])
  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])
diff --combined src/client/main.c
index f7c1c534491c53c0986270acae4fa2af765447ed,190380b7982b406a878fb802c80f4684f93d7c62..9cfba7112ca2b994f9c109b71b05b827f0faaf73
@@@ -1,6 -1,6 +1,6 @@@
  /*
   *  Empire - A multi-player, client/server Internet based war game.
 - *  Copyright (C) 1986-2015, Dave Pare, Jeff Bailey, Thomas Ruschak,
 + *  Copyright (C) 1986-2016, Dave Pare, Jeff Bailey, Thomas Ruschak,
   *                Ken Stevens, Steve McClure, Markus Armbruster
   *
   *  Empire is free software: you can redistribute it and/or modify
@@@ -30,8 -30,9 +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 +58,7 @@@
  
  struct passwd {
      char *pw_name;
+     char *pw_dir;
  };
  
  static struct passwd *w32_getpw(void);
@@@ -72,6 -76,10 +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 +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;
      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;
      if (!host)
        host = empirehost;
      uname = getenv("LOGNAME");
-     if (uname == NULL) {
+     udir = getenv("HOME");
+     if (!uname || !udir) {
        struct passwd *pwd;
  
        pwd = getpwuid(getuid());
            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);
  
      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;
  
  #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;
  
            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;
  }
  
diff --combined src/client/misc.h
index c7a476d08509feac1347237cff3e50c4a3573f1c,dacaf12b4ed6bdba3ebec98e7b30c1859884add9..e92454a1357caf469dbd07c55626ca3a11f89e8e
@@@ -1,6 -1,6 +1,6 @@@
  /*
   *  Empire - A multi-player, client/server Internet based war game.
 - *  Copyright (C) 1986-2015, Dave Pare, Jeff Bailey, Thomas Ruschak,
 + *  Copyright (C) 1986-2016, Dave Pare, Jeff Bailey, Thomas Ruschak,
   *                Ken Stevens, Steve McClure, Markus Armbruster
   *
   *  Empire is free software: you can redistribute it and/or modify
@@@ -28,6 -28,7 +28,7 @@@
   *
   *  Known contributors to this file:
   *     Steve McClure, 1998
+  *     Markus Armbruster, 2004-2017
   */
  
  #ifndef MISC_H
@@@ -41,8 -42,6 +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 +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
diff --combined src/client/play.c
index 3f6400177ecbe1f7bc4c0711f83bb900ffffa2ba,5349f323e91192c1d08d401686a2622213b34322..87a698662ab5236dd26b3f8f66e9a4cc7e403411
@@@ -1,6 -1,6 +1,6 @@@
  /*
   *  Empire - A multi-player, client/server Internet based war game.
 - *  Copyright (C) 1986-2015, Dave Pare, Jeff Bailey, Thomas Ruschak,
 + *  Copyright (C) 1986-2016, Dave Pare, Jeff Bailey, Thomas Ruschak,
   *                Ken Stevens, Steve McClure, Markus Armbruster
   *
   *  Empire is free software: you can redistribute it and/or modify
@@@ -27,8 -27,9 +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 +320,6 @@@ w32_ring_from_file_or_bounce_buf(struc
  #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 +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));
            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;
            }
      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.
        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 +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
      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);
      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;
  
        /*
         * 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))
        if (n < 0) {
            if (errno != EINTR) {
                perror("select");
-               return -1;
+               break;
            }
        }
  
            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;
            }
        }
  
            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);
+     }
  }
diff --combined src/client/ringbuf.c
index 9e04bc81a2ac357f9f398b187082f6403be289b7,2e4c885756f064f243d7542be2c9f02773d20c7b..9782d914324770db4f6bd0e799f81a1b222ddcce
@@@ -1,6 -1,6 +1,6 @@@
  /*
   *  Empire - A multi-player, client/server Internet based war game.
 - *  Copyright (C) 1986-2015, Dave Pare, Jeff Bailey, Thomas Ruschak,
 + *  Copyright (C) 1986-2016, Dave Pare, Jeff Bailey, Thomas Ruschak,
   *                Ken Stevens, Steve McClure, Markus Armbruster
   *
   *  Empire is free software: you can redistribute it and/or modify
@@@ -27,7 -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 +35,6 @@@
  #include <assert.h>
  #include <stdio.h>
  #include <string.h>
- #include <sys/uio.h>
  #include "ringbuf.h"
  
  /*
@@@ -82,9 -81,17 +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 +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 +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;
  }
diff --combined src/client/ringbuf.h
index a3794e8bd80fe00808432a4b64c6d270344b429d,545a2fd369fef2313abfe14d704f71c04da3c3d7..9d1a58bf8a620ac1531bc5fa79ba2bb16397eca5
@@@ -1,6 -1,6 +1,6 @@@
  /*
   *  Empire - A multi-player, client/server Internet based war game.
 - *  Copyright (C) 1986-2015, Dave Pare, Jeff Bailey, Thomas Ruschak,
 + *  Copyright (C) 1986-2016, Dave Pare, Jeff Bailey, Thomas Ruschak,
   *                Ken Stevens, Steve McClure, Markus Armbruster
   *
   *  Empire is free software: you can redistribute it and/or modify
   *  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 +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
diff --combined src/client/secure.c
index 808301882e217870be088dc8471b349c38b081e0,c2e3cd24b5c58d517b3d025d6ccec8441c4f9aa1..14bb6d1fe27ef93eef90a16b8371177debffb51b
@@@ -1,6 -1,6 +1,6 @@@
  /*
   *  Empire - A multi-player, client/server Internet based war game.
 - *  Copyright (C) 1986-2015, Dave Pare, Jeff Bailey, Thomas Ruschak,
 + *  Copyright (C) 1986-2016, Dave Pare, Jeff Bailey, Thomas Ruschak,
   *                Ken Stevens, Steve McClure, Markus Armbruster
   *
   *  Empire is free software: you can redistribute it and/or modify
   *  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;
      }
  }
diff --combined src/client/secure.h
index 425cd797654878a1db7ab5c89a6f786d53ab4978,c5462f681530a110f49fd1650bb2c0a5db0ca461..60986ac5047daa7cf15b560f041b2265f5927576
@@@ -1,6 -1,6 +1,6 @@@
  /*
   *  Empire - A multi-player, client/server Internet based war game.
 - *  Copyright (C) 1986-2015, Dave Pare, Jeff Bailey, Thomas Ruschak,
 + *  Copyright (C) 1986-2016, Dave Pare, Jeff Bailey, Thomas Ruschak,
   *                Ken Stevens, Steve McClure, Markus Armbruster
   *
   *  Empire is free software: you can redistribute it and/or modify
@@@ -27,7 -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 +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
diff --combined src/client/servcmd.c
index f2d0e3fc6334b44ec44ce4d08077417fbd637090,ba6d974e68fd4db66987fd92516ff4eeed5dc400..fd83f90362fa5aa9b08666817b4534c0f2ed9411
@@@ -1,6 -1,6 +1,6 @@@
  /*
   *  Empire - A multi-player, client/server Internet based war game.
 - *  Copyright (C) 1986-2015, Dave Pare, Jeff Bailey, Thomas Ruschak,
 + *  Copyright (C) 1986-2016, Dave Pare, Jeff Bailey, Thomas Ruschak,
   *                Ken Stevens, Steve McClure, Markus Armbruster
   *
   *  Empire is free software: you can redistribute it and/or modify
@@@ -30,7 -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 +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 +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 +68,8 @@@
      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);
                (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;
        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)
        if (arg[0] != '\n') {
            snprintf(teles, sizeof(teles), "(%.*s) ", len - 1, arg);
            if (!redir_fp) {
+               outch('\n');
                putchar('\07');
                prompt(code, the_prompt, teles);
            }
        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 +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;
        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 +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;
      }
  
  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;
      }
  
        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 +254,7 @@@ doexecute(char *p
  {
      int fd;
  
-     if (!redir_authorized(p, "execute batch file", 1))
+     if (!exec_authorized(p))
        return -1;
  
      p = fname(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;
      }
diff --combined src/lib/common/conftab.c
index 72f1d62d25c568cc428b29904e682f7096100cd9,2c5d48e34d1bdce8b5df628263b43f5fad89c3d1..ae79d13cbeb41e23cc76efbf96b3096870b11890
@@@ -1,6 -1,6 +1,6 @@@
  /*
   *  Empire - A multi-player, client/server Internet based war game.
 - *  Copyright (C) 1986-2015, Dave Pare, Jeff Bailey, Thomas Ruschak,
 + *  Copyright (C) 1986-2016, Dave Pare, Jeff Bailey, Thomas Ruschak,
   *                Ken Stevens, Steve McClure, Markus Armbruster
   *
   *  Empire is free software: you can redistribute it and/or modify
@@@ -35,6 -35,7 +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,d084e3a1ff1907276e371331c37d624aa8bff4a1..90299ddb2a50c1e5c5aab9a7c3cb47c9203a3ded
@@@ -1,6 -1,6 +1,6 @@@
  /*
   *  Empire - A multi-player, client/server Internet based war game.
 - *  Copyright (C) 1986-2015, Dave Pare, Jeff Bailey, Thomas Ruschak,
 + *  Copyright (C) 1986-2016, Dave Pare, Jeff Bailey, Thomas Ruschak,
   *                Ken Stevens, Steve McClure, Markus Armbruster
   *
   *  Empire is free software: you can redistribute it and/or modify
@@@ -50,6 -50,7 +50,7 @@@
  #include <string.h>
  #include <unistd.h>
  
+ #include "fnameat.h"
  #include "misc.h"
  #include "optlist.h"
  #include "prototypes.h"
diff --combined src/lib/gen/fnameat.c
index 098b0322463aed83c9f73a3ae8e85bc101fe2fde,d970372c9d8d4e9139bf3edbc22b11b4338213d9..5a44bb25fea2251fab45f481341124255f139646
@@@ -1,6 -1,6 +1,6 @@@
  /*
   *  Empire - A multi-player, client/server Internet based war game.
 - *  Copyright (C) 1986-2015, Dave Pare, Jeff Bailey, Thomas Ruschak,
 + *  Copyright (C) 1986-2016, Dave Pare, Jeff Bailey, Thomas Ruschak,
   *                Ken Stevens, Steve McClure, Markus Armbruster
   *
   *  Empire is free software: you can redistribute it and/or modify
@@@ -33,7 -33,9 +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 *);