client: Add readline support to empire client

Readline provides fancy command line editing such as <Arrow Up> for
previous commands and CTRL+A to jump to the beginning of the line.

This patch does not add any completion on <tab> key, a TODO, if you
will.

A new command line flag, -H, turns on saving the history to disk.
This may have security implications on shared computers, as all
commands are saved as-is.  Thus "change re 1234" would be logged
directly to the file.

Signed-off-by: Martin Haukeli <martin.haukeli@gmail.com>

Rebase on top of preparatory work, fix a few bugs, and tidy up:

* Update the standalone client build, too.

* Fix the Windows build.

* Keep command line options sorted case-insensitively.

* Error out when $HOME is unset and getpwuid() fails, just like we do
  for $LOGNAME.

* Give @input_from_rl, @has_rl_input static linkage.

* @has_rl_input is a flag, not a counter, set and test it accordingly.

* Save all input in history, not just commands.  Martin's attempt to
  recognize commands works only as long as the server sends prompts
  faster than the user sends input.  Drop that part, and update commit
  message accordingly.

* Fix recv_input() not to truncate value of strlen() to int, and to
  use memmove() for updating @input_from_rl in place.

* Clean up whitespace in a few places.

* Tweak commit message.

Signed-off-by: Markus Armbruster <armbru@pond.sub.org>
This commit is contained in:
Martin Haukeli 2015-11-08 23:57:14 +01:00 committed by Markus Armbruster
parent 594cd20f76
commit f1fc0df03d
9 changed files with 256 additions and 14 deletions

View file

@ -58,6 +58,7 @@ if test "$Windows_API" = yes; then
AC_LIBOBJ([w32/w32io])
AC_LIBOBJ([w32/w32sockets])
fi
AX_LIB_READLINE
### Checks for header files.

View file

@ -40,6 +40,7 @@
#include <string.h>
#ifdef _WIN32
#include <windows.h>
#include <shlobj.h>
#include "sys/socket.h"
#else
#include <pwd.h>
@ -55,6 +56,7 @@
struct passwd {
char *pw_name;
char *pw_dir;
};
static struct passwd *w32_getpw(void);
@ -72,6 +74,9 @@ 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_READLINE_HISTORY
" -H Save readline command history to file\n"
#endif /* HAVE_READLINE_HISTORY */
" -h display this help and exit\n"
" -v display version information and exit\n",
program_name);
@ -82,6 +87,7 @@ main(int argc, char **argv)
{
int opt;
char *auxfname = NULL;
int use_history_file = 0;
int send_kill = 0;
char *host = NULL;
char *port = NULL;
@ -90,14 +96,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:krs:uHhv")) != EOF) {
switch (opt) {
case '2':
auxfname = optarg;
break;
#ifdef HAVE_READLINE_HISTORY
case 'H':
use_history_file = 1;
break;
#endif /* HAVE_READLINE_HISTORY */
case 'k':
send_kill = 1;
break;
@ -146,7 +158,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 +167,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,6 +188,12 @@ main(int argc, char **argv)
sock = tcp_connect(host, port);
if (use_history_file) {
history_file = malloc(1024);
strncpy(history_file, udir, 1000);
strcat(history_file, "/.empire.history");
}
if (!login(sock, uname, country, passwd, send_kill, utf8))
exit(1);
@ -183,12 +205,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 +222,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;
}

View file

@ -44,6 +44,7 @@ extern char empireport[];
extern int eight_bit_clean;
extern FILE *auxfp;
extern int restricted;
extern char *history_file;
#ifdef HAVE_CURSES_TERMINFO
void getsose(void);

View file

@ -51,6 +51,28 @@
#include "ringbuf.h"
#include "secure.h"
#ifdef HAVE_LIBREADLINE
# if defined(HAVE_READLINE_READLINE_H)
# include <readline/readline.h>
# elif defined(HAVE_READLINE_H)
# include <readline.h>
# else /* !defined(HAVE_READLINE_H) */
extern char *readline ();
# endif /* !defined(HAVE_READLINE_H) */
#endif /* HAVE_LIBREADLINE */
#ifdef HAVE_READLINE_HISTORY
# if defined(HAVE_READLINE_HISTORY_H)
# include <readline/history.h>
# elif defined(HAVE_HISTORY_H)
# include <history.h>
# else /* !defined(HAVE_HISTORY_H) */
extern void add_history ();
extern int write_history ();
extern int read_history ();
# endif /* !defined(HAVE_HISTORY_H) */
#endif /* HAVE_READLINE_HISTORY */
#define EOF_COOKIE "ctld\n"
#define INTR_COOKIE "aborted\n"
@ -414,6 +436,24 @@ recv_output(int sock)
return n;
}
char *history_file = NULL;
#ifdef HAVE_LIBREADLINE
static char *input_from_rl;
static int has_rl_input;
static void
input_handler(char *line)
{
input_from_rl = line;
has_rl_input = 1;
rl_already_prompted = 1;
#ifdef HAVE_READLINE_HISTORY
if (line && *line)
add_history(line);
#endif /* HAVE_READLINE_HISTORY */
}
#endif /* HAVE_LIBREADLINE */
/*
* Receive player input from @fd into @inbuf.
* Return 1 on receipt of input, zero on EOF, -1 on error.
@ -423,8 +463,33 @@ recv_input(int fd, struct ring *inbuf)
{
int n;
int res = 1;
#ifdef HAVE_LIBREADLINE
size_t len;
n = ring_from_file(inbuf, fd);
if (fd == 0) {
if (!has_rl_input)
rl_callback_read_char();
if (!has_rl_input)
return 1;
if (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;
}
} else
n = 0;
} else
#endif
n = ring_from_file(inbuf, fd);
if (n < 0)
return -1;
if (n == 0) {
@ -490,6 +555,7 @@ play(int sock)
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);
@ -497,6 +563,14 @@ play(int sock)
sigaction(SIGINT, &sa, NULL);
sa.sa_handler = SIG_IGN;
sigaction(SIGPIPE, &sa, NULL);
#ifdef HAVE_LIBREADLINE
#ifdef HAVE_READLINE_HISTORY
if (history_file)
read_history(history_file);
#endif /* HAVE_READLINE_HISTORY */
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;
@ -525,7 +599,7 @@ play(int sock)
if (n < 0) {
if (errno != EINTR) {
perror("select");
return -1;
break;
}
}
@ -544,7 +618,6 @@ play(int sock)
input_fd = 0;
}
}
if (n < 0)
continue;
@ -571,7 +644,7 @@ play(int sock)
sigaction(SIGINT, &sa, NULL);
send_intr = 0;
}
} else
} else if (ring_len(&inbuf) > 0)
partial_line_sent = ring_peek(&inbuf, -1) != '\n';
}
@ -580,7 +653,7 @@ play(int sock)
n = send_input(sock, &inbuf);
if (n < 0) {
perror("write socket");
return -1;
break;
}
}
@ -589,10 +662,22 @@ play(int sock)
n = recv_output(sock);
if (n < 0) {
perror("read socket");
return -1;
break;
}
if (n == 0) {
#ifdef HAVE_LIBREADLINE
#ifdef HAVE_READLINE_HISTORY
if (history_file)
write_history(history_file);
#endif /* HAVE_READLINE_HISTORY */
#endif /* HAVE_LIBREADLINE */
ret = 0;
break;
}
if (n == 0)
return 0;
}
}
#ifdef HAVE_LIBREADLINE
rl_callback_handler_remove();
#endif
return ret;
}

View file

@ -46,6 +46,14 @@
#include "proto.h"
#include "secure.h"
#ifdef HAVE_LIBREADLINE
# if defined(HAVE_READLINE_READLINE_H)
# include <readline/readline.h>
# elif defined(HAVE_READLINE_H)
# include <readline.h>
# endif /* defined(HAVE_READLINE_H) */
#endif /* HAVE_LIBREADLINE */
int eight_bit_clean;
FILE *auxfp;
int restricted;
@ -131,10 +139,18 @@ static void
prompt(int code, char *prompt, char *teles)
{
char *nl;
char pr[1024];
nl = code == C_PROMPT || code == C_INFORM ? "\n" : "";
printf("%s%s%s", nl, teles, prompt);
snprintf(pr, sizeof(pr), "%s%s", teles, prompt);
#ifdef HAVE_LIBREADLINE
rl_set_prompt(pr);
printf("%s", nl);
rl_forced_update_display();
#else /* !HAVE_LIBREADLINE */
printf("%s%s", nl, pr);
fflush(stdout);
#endif /* !HAVE_LIBREADLINE */
if (auxfp) {
fprintf(auxfp, "%s%s%s", nl, teles, prompt);
fflush(auxfp);