aboutsummaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/Makefile.am29
-rw-r--r--src/beam.c480
-rw-r--r--src/beam.h17
-rw-r--r--src/catrw.c43
-rw-r--r--src/cmd.c2713
-rw-r--r--src/cmd.h26
-rw-r--r--src/cmd2.c1676
-rw-r--r--src/cmd2.h32
-rw-r--r--src/defines.h307
-rw-r--r--src/edit.c961
-rw-r--r--src/edit.h75
-rw-r--r--src/eval.c1456
-rw-r--r--src/eval.h58
-rw-r--r--src/feature/regex.h11
-rw-r--r--src/follow.c170
-rw-r--r--src/list.c675
-rw-r--r--src/list.h47
-rw-r--r--src/log.c329
-rw-r--r--src/log.h22
-rw-r--r--src/main.c2084
-rw-r--r--src/main.h119
-rw-r--r--src/map.c196
-rw-r--r--src/map.h15
-rw-r--r--src/plugtest.c29
-rw-r--r--src/powwow-movieplay.c97
-rw-r--r--src/powwow-muc.c307
-rw-r--r--src/ptr.c586
-rw-r--r--src/ptr.h75
-rw-r--r--src/tcp.c1045
-rw-r--r--src/tcp.h79
-rw-r--r--src/tty.c1038
-rw-r--r--src/tty.h58
-rw-r--r--src/utils.c1299
-rw-r--r--src/utils.h48
34 files changed, 16202 insertions, 0 deletions
diff --git a/src/Makefile.am b/src/Makefile.am
new file mode 100644
index 0000000..caab15c
--- /dev/null
+++ b/src/Makefile.am
@@ -0,0 +1,29 @@
+# Default help file directory
+AM_CPPFLAGS=-D_XOPEN_SOURCE=700 -DPOWWOW_DIR=\"$(pkgdatadir)\" \
+ -DPLUGIN_DIR=\"$(plugindir)\"
+
+bin_PROGRAMS = powwow powwow-muc powwow-movieplay
+powwow_SOURCES = beam.c cmd.c log.c edit.c cmd2.c eval.c \
+ utils.c main.c tcp.c list.c map.c tty.c \
+ ptr.c
+powwow_LDFLAGS = @dl_ldflags@
+powwowdir = $(pkgincludedir)
+powwow_HEADERS = beam.h cmd.h log.h edit.h cmd2.h eval.h \
+ utils.h main.h tcp.h list.h map.h tty.h \
+ ptr.h defines.h feature/regex.h
+powwow_muc_SOURCES = powwow-muc.c
+powwow_movieplay_SOURCES = powwow-movieplay.c
+
+install-exec-hook:
+ (cd $(DESTDIR)$(bindir) && \
+ rm -f powwow-movie2ascii$(EXEEXT) && \
+ $(LN_S) powwow-movieplay$(EXEEXT) powwow-movie2ascii$(EXEEXT))
+
+noinst_PROGRAMS = catrw follow
+follow_SOURCES = follow.c
+catrw_SOURCES = catrw.c
+
+EXTRA_DIST = plugtest.c
+
+plugtest.so: plugtest.c
+ gcc -shared -o plugtest.so plugtest.c
diff --git a/src/beam.c b/src/beam.c
new file mode 100644
index 0000000..cff503d
--- /dev/null
+++ b/src/beam.c
@@ -0,0 +1,480 @@
+/*
+ * beam.c -- code to beam texts across the TCP connection following a
+ * special protocol
+ *
+ * Copyright (C) 1998 by Massimiliano Ghilardi
+ *
+ * This program 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 2 of the License, or
+ * (at your option) any later version.
+ *
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <ctype.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <signal.h>
+#include <time.h>
+#include <unistd.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <arpa/telnet.h>
+
+#include "defines.h"
+#include "main.h"
+#include "utils.h"
+#include "beam.h"
+#include "map.h"
+#include "tcp.h"
+#include "tty.h"
+#include "edit.h"
+#include "eval.h"
+
+editsess *edit_sess; /* head of session list */
+
+char edit_start[BUFSIZE]; /* messages to send to host when starting */
+char edit_end[BUFSIZE]; /* or leaving editing sessions */
+
+static void write_message(char *s)
+{
+ clear_input_line(opt_compact);
+ if (!opt_compact) {
+ tty_putc('\n');
+ status(1);
+ }
+ tty_puts(s);
+}
+
+/*
+ * Process editing protocol message from buf with len remaining chars.
+ * Return number of characters used in the message.
+ */
+int process_message(char *buf, int len)
+{
+ int msglen, i, l, used;
+ char *text, *from, *to;
+ char msg[BUFSIZE];
+ int got_iac;
+
+ status(1);
+
+ msglen = atoi(buf + 1);
+ for (i = 1; i < len && isdigit(buf[i]); i++)
+ ;
+
+ if (i < len && buf[i] == '\r') i++;
+ if (i >= len || buf[i] != '\n') {
+ write_message ("#warning: MPI protocol error\n");
+ return 0;
+ }
+
+ l = len - ++i;
+
+ text = (char *)malloc(msglen);
+ if (!text) {
+ errmsg("malloc");
+ return i;
+ }
+
+ /* only doing trivial (IAC IAC) processing; this should all be
+ * rewritten */
+ got_iac = 0;
+ from = buf + i;
+ to = text;
+ for (i = 0; i < l && (to - text) < msglen; ++i) {
+ if (got_iac) {
+ if (*from != (char)IAC)
+ errmsg("got IAC in MPI message; treating as IAC IAC");
+ else
+ ++from;
+ got_iac = 0;
+ } else {
+ got_iac = (*to++ = *from++) == (char)IAC;
+ }
+ }
+ i = to - text;
+ used = from - buf;
+
+ while (i < msglen) {
+ /* read() might block here, but it should't be long */
+ while ((l = read(tcp_fd, text + i, msglen - i)) == -1 && errno == EINTR)
+ ;
+ if (l == -1) {
+ errmsg("read message from socket");
+ return used > len ? len : used;
+ }
+ from = to = text + i;
+ for (i = 0; i < l; ++i) {
+ if (got_iac) {
+ if (*from != (char)IAC)
+ errmsg("got IAC in MPI message; treating as IAC IAC");
+ else
+ ++from;
+ got_iac = 0;
+ } else {
+ got_iac = (*to++ = *from++) == (char)IAC;
+ }
+ }
+ i = to - text;
+ tty_printf("\rgot %d chars out of %d", i, msglen);
+ tty_flush();
+ }
+ tty_printf("\rread all %d chars.%s\n", msglen, tty_clreoln);
+
+ switch(*buf) {
+ case 'E':
+ message_edit(text, msglen, 0, 0);
+ break;
+ case 'V':
+ message_edit(text, msglen, 1, 0);
+ break;
+ default:
+ sprintf(msg, "#warning: received a funny message (0x%x)\n", *buf);
+ write_message(msg);
+ free(text);
+ break;
+ }
+
+ return used;
+}
+
+/*
+ * abort an editing session when
+ * the MUD socket it comes from gets closed
+ */
+void abort_edit_fd(int fd)
+{
+ editsess **sp, *p;
+
+ if (fd < 0)
+ return;
+
+ for (sp = &edit_sess; *sp; sp = &(*sp)->next) {
+ p = *sp;
+ if (p->fd != fd)
+ continue;
+
+ if (kill(p->pid, SIGKILL) < 0) { /* Editicide */
+ errmsg("kill editor child");
+ continue;
+ }
+
+ PRINTF("#aborted \"%s\" (%u)\n", p->descr, p->key);
+ p->cancel = 1;
+ }
+}
+
+/*
+ * cancel an editing session; does not free anything
+ * (the child death signal handler will remove the session from the list)
+ */
+void cancel_edit(editsess *sp)
+{
+ char buf[BUFSIZE];
+ char keystr[16];
+
+ if (kill(sp->pid, SIGKILL) < 0) { /* Editicide */
+ errmsg("kill editor child");
+ return;
+ }
+ PRINTF("#killed \"%s\" (%u)\n", sp->descr, sp->key);
+ sprintf(keystr, "C%u\n", sp->key);
+ sprintf(buf, "%sE%d\n%s", MPI, (int)strlen(keystr), keystr);
+ tcp_write(sp->fd, buf);
+ sp->cancel = 1;
+}
+
+static ssize_t read_file(int fd, void *buf, size_t count)
+{
+ size_t result = 0;
+ while (count > 0) {
+ int r;
+ do {
+ r = read(fd, buf, count);
+ } while (r < 0 && errno == EINTR);
+ if (r < 0)
+ return -1;
+ if (r == 0)
+ break;
+ result += r;
+ buf += r;
+ count -= r;
+ }
+ return result;
+}
+
+/*
+ * send back edited text to server, or cancel the editing session if the
+ * file was not changed.
+ */
+static void finish_edit(editsess *sp)
+{
+ char *realtext = NULL, *text;
+ int fd, txtlen, hdrlen;
+ struct stat sbuf;
+ char keystr[16], buf[256], hdr[65];
+
+ if (sp->fd == -1)
+ goto cleanup_file;
+
+ fd = open(sp->file, O_RDONLY);
+ if (fd == -1) {
+ errmsg("open edit file");
+ goto cleanup_file;
+ }
+ if (fstat(fd, &sbuf) == -1) {
+ errmsg("fstat edit file");
+ goto cleanup_fd;
+ }
+
+ txtlen = sbuf.st_size;
+
+ if (!sp->cancel && (sbuf.st_mtime > sp->ctime || txtlen != sp->oldsize)) {
+ /* file was changed by editor: send back result to server */
+
+ realtext = (char *)malloc(txtlen + 65);
+ /* +1 is for possible LF, +64 for header */
+ if (!realtext) {
+ errmsg("malloc");
+ goto cleanup_fd;
+ }
+
+ text = realtext + 64;
+ txtlen = read_file(fd, text, txtlen);
+ if (txtlen < 0)
+ goto cleanup_text;
+
+ if (txtlen && text[txtlen - 1] != '\n') {
+ /* If the last line isn't LF-terminated, add an LF;
+ * however, empty files must remain empty */
+ text[txtlen] = '\n';
+ txtlen++;
+ }
+
+ sprintf(keystr, "E%u\n", sp->key);
+
+ sprintf(hdr, "%sE%d\n%s", MPI, (int)(txtlen + strlen(keystr)), keystr);
+
+ text -= (hdrlen = strlen(hdr));
+ memcpy(text, hdr, hdrlen);
+
+ /* text[hdrlen + txtlen] = '\0'; */
+ tcp_write_escape_iac(sp->fd, text, hdrlen + txtlen);
+
+ sprintf(buf, "#completed session %s (%u)\n", sp->descr, sp->key);
+ write_message(buf);
+ } else {
+ /* file wasn't changed, cancel editing session */
+ sprintf(keystr, "C%u\n", sp->key);
+ sprintf(hdr, "%sE%d\n%s", MPI, (int) strlen(keystr), keystr);
+
+ tcp_raw_write(sp->fd, hdr, strlen(hdr));
+
+ sprintf(buf, "#cancelled session %s (%u)\n", sp->descr, sp->key);
+ write_message(buf);
+ }
+
+cleanup_text: if (realtext) free(realtext);
+cleanup_fd: close(fd);
+cleanup_file: if (unlink(sp->file) < 0)
+ errmsg("unlink edit file");
+}
+
+/*
+ * start an editing session: process the EDIT/VIEW message
+ * if view == 1, text will be viewed, else edited
+ */
+void message_edit(char *text, int msglen, char view, char builtin)
+{
+ char tmpname[BUFSIZE], command_str[BUFSIZE], buf[BUFSIZE];
+ char *errdesc = "#warning: protocol error (message_edit, no %s)\n";
+ int tmpfd, i, childpid;
+ unsigned int key;
+ editsess *s;
+ char *editor, *descr;
+ char *args[4];
+ int waitforeditor;
+
+ status(1);
+
+ args[0] = "/bin/sh"; args[1] = "-c";
+ args[2] = command_str; args[3] = 0;
+
+ if (view) {
+ key = (unsigned int)-1;
+ i = 0;
+ } else {
+ if (msglen < 1 || text[0] != 'M') {
+ tty_printf(errdesc, "M");
+ free(text);
+ return;
+ }
+ for (i = 1; i < msglen && isdigit(text[i]); i++)
+ ;
+ if (text[i++] != '\n' || i >= msglen) {
+ tty_printf(errdesc, "\\n");
+ free(text);
+ return;
+ }
+ key = strtoul(text + 1, NULL, 10);
+ }
+ descr = text + i;
+ while (i < msglen && text[i] != '\n') i++;
+ if (i >= msglen) {
+ tty_printf(errdesc, "desc");
+ free(text);
+ return;
+ }
+ text[i++] = '\0';
+
+ sprintf(tmpname, "/tmp/powwow.%u.%d%d", key, getpid(), abs(rand()) >> 8);
+ if ((tmpfd = open(tmpname, O_WRONLY | O_CREAT, 0600)) < 0) {
+ errmsg("create temp edit file");
+ free(text);
+ return;
+ }
+ if (write(tmpfd, text + i, msglen - i) < msglen - i) {
+ errmsg("write to temp edit file");
+ free(text);
+ close(tmpfd);
+ return;
+ }
+ close(tmpfd);
+
+ s = (editsess*)malloc(sizeof(editsess));
+ if (!s) {
+ errmsg("malloc");
+ return;
+ }
+ s->ctime = time((time_t*)NULL);
+ s->oldsize = msglen - i;
+ s->key = key;
+ s->fd = (view || builtin) ? -1 : tcp_fd; /* MUME doesn't expect a reply. */
+ s->cancel = 0;
+ s->descr = my_strdup(descr);
+ s->file = my_strdup(tmpname);
+ free(text);
+
+ /* send a edit_start message (if wanted) */
+ if ((!edit_sess) && (*edit_start)) {
+ error = 0;
+ parse_instruction(edit_start, 0, 0, 1);
+ history_done = 0;
+ }
+
+ if (view) {
+ if (!(editor = getenv("POWWOWPAGER")) && !(editor = getenv("PAGER")))
+ editor = "more";
+ } else {
+ if (!(editor = getenv("POWWOWEDITOR")) && !(editor = getenv("EDITOR")))
+ editor = "emacs";
+ }
+
+ if (editor[0] == '&') {
+ waitforeditor = 0;
+ editor++;
+ } else
+ waitforeditor = 1;
+
+ if (waitforeditor) {
+ tty_quit();
+ /* ignore SIGINT since interrupting the child would interrupt us too,
+ if we are in the same tty group */
+ sig_permanent(SIGINT, SIG_IGN);
+ sig_permanent(SIGCHLD, SIG_DFL);
+ }
+
+ switch(childpid = fork()) { /* let's get schizophrenic */
+ case 0:
+ sprintf(command_str, "%s %s", editor, s->file);
+ sprintf(buf, "TITLE=%s", s->descr);
+ putenv(buf);
+ /* setenv("TITLE", s->descr, 1);*/
+ execvp((char *)args[0], (char **)args);
+ syserr("execve");
+ break;
+ case -1:
+ errmsg("fork");
+ free(s->descr);
+ free(s->file);
+ free(s);
+ return;
+ }
+ s->pid = childpid;
+ if (waitforeditor) {
+ while ((i = waitpid(childpid, (int*)NULL, 0)) == -1 && errno == EINTR)
+ ;
+
+ signal_start(); /* reset SIGINT and SIGCHLD handlers */
+ tty_start();
+
+ if (s->fd != -1) {
+ tty_gotoxy(0, lines - 1);
+ tty_putc('\n');
+ }
+
+ if (i == -1)
+ errmsg("waitpid");
+ else
+ finish_edit(s);
+
+ free(s->descr);
+ free(s->file);
+ if (i != -1 && !edit_sess && *edit_end) {
+ error = 0;
+ parse_instruction(edit_end, 0, 0, 1);
+ history_done = 0;
+ }
+
+ free(s);
+
+ } else {
+ s->next = edit_sess;
+ edit_sess = s;
+ }
+}
+
+/*
+ * Our child has snuffed it. check if it was an editor, and update the
+ * session list if that is the case.
+ */
+void sig_chld_bottomhalf(void)
+{
+ int fd, pid, ret;
+ editsess **sp, *p;
+
+ /* GH: while() instead of just one check */
+ while ((pid = waitpid(-1, &ret, WNOHANG)) > 0) {
+
+ /* GH: check for WIFSTOPPED unnecessary since no */
+ /* WUNTRACED to waitpid() */
+ for (sp = &edit_sess; *sp && (*sp)->pid != pid; sp = &(*sp)->next)
+ ;
+ if (*sp) {
+ finish_edit(*sp);
+ p = *sp; *sp = p->next;
+ fd = p->fd;
+ free(p->descr);
+ free(p->file);
+ free(p);
+
+ /* GH: only send message if found matching session */
+
+ /* send the edit_end message if this is the last editor... */
+ if ((!edit_sess) && (*edit_end)) {
+ int otcp_fd = tcp_fd; /* backup current socket fd */
+ tcp_fd = fd;
+ error = 0;
+ parse_instruction(edit_end, 0, 0, 1);
+ history_done = 0;
+ tcp_fd = otcp_fd;
+ }
+ }
+ }
+}
+
diff --git a/src/beam.h b/src/beam.h
new file mode 100644
index 0000000..84189d9
--- /dev/null
+++ b/src/beam.h
@@ -0,0 +1,17 @@
+/* public things from beam.c */
+
+#ifndef _BEAM_H_
+#define _BEAM_H_
+
+int process_message(char *buf, int len);
+void cancel_edit(editsess *sp);
+void abort_edit_fd(int fd);
+void message_edit(char *text, int msglen, char view, char builtin);
+void sig_chld_bottomhalf(void);
+
+extern char edit_start[];
+extern char edit_end[];
+extern editsess *edit_sess;
+
+#endif /* _BEAM_H_ */
+
diff --git a/src/catrw.c b/src/catrw.c
new file mode 100644
index 0000000..40d06e3
--- /dev/null
+++ b/src/catrw.c
@@ -0,0 +1,43 @@
+/*
+ * catrw.c -- open a file with O_RDWR and print it.
+ *
+ * This program 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 2 of the License, or
+ * (at your option) any later version.
+ *
+ */
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <errno.h>
+
+#define BUFSIZE 4096
+char buf[BUFSIZE];
+
+void catrw(int fd) {
+ int i;
+ for (;;) {
+ while ( (i = read(fd, buf, BUFSIZE)) < 0 && errno == EINTR)
+ ;
+ if (i <= 0)
+ break;
+ write(1, buf, i);
+ }
+}
+
+int main(int argc, char *argv[]) {
+ int fd;
+ if (argc == 1)
+ catrw(0);
+ else {
+ while (--argc && (fd = open(*++argv, O_RDWR))) {
+ catrw(fd);
+ close(fd);
+ }
+ }
+ return 0;
+}
+
diff --git a/src/cmd.c b/src/cmd.c
new file mode 100644
index 0000000..f11dd53
--- /dev/null
+++ b/src/cmd.c
@@ -0,0 +1,2713 @@
+/*
+ * cmd.c -- functions for powwow's built-in #commands
+ *
+ * (created: Finn Arne Gangstad (Ilie), Dec 25th, 1993)
+ *
+ * Copyright (C) 1998 by Massimiliano Ghilardi
+ *
+ * This program 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 2 of the License, or
+ * (at your option) any later version.
+ *
+ */
+
+#include <limits.h>
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <signal.h>
+#include <ctype.h>
+#include <time.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <sys/stat.h>
+#include <sys/time.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <errno.h>
+
+#ifdef HAVE_LIBDL
+#include <dlfcn.h>
+#endif
+
+#include "defines.h"
+#include "main.h"
+#include "utils.h"
+#include "beam.h"
+#include "cmd.h"
+#include "cmd2.h"
+#include "edit.h"
+#include "list.h"
+#include "map.h"
+#include "tcp.h"
+#include "tty.h"
+#include "eval.h"
+#include "log.h"
+
+/* local function declarations */
+#define F(name) cmd_ ## name(char *arg)
+
+static void F(help), F(shell), F(action), F(add),
+ F(addstatic), F(alias), F(at), F(beep), F(bind),
+ F(cancel), F(capture), F(clear), F(connect), F(cpu),
+ F(do), F(delim), F(edit), F(emulate), F(exe),
+ F(file), F(for), F(hilite), F(history), F(host),
+ F(identify), F(if), F(in), F(init), F(isprompt),
+ F(key), F(keyedit),
+ F(load), F(map), F(mark), F(movie),
+ F(net), F(nice), F(option),
+ F(prefix), F(print), F(prompt), F(put),
+ F(qui), F(quit), F(quote),
+ F(rawsend), F(rawprint), F(rebind), F(rebindall), F(rebindALL),
+ F(record), F(request), F(reset), F(retrace),
+ F(save), F(send), F(setvar), F(snoop), F(spawn), F(stop),
+ F(time), F(var), F(ver), F(while), F(write),
+ F(eval), F(zap), F(module), F(group), F(speedwalk), F(groupdelim);
+
+#ifdef BUG_TELNET
+static void F(color);
+#endif
+
+#undef F
+
+/* This must be init'd now at runtime */
+cmdstruct *commands = NULL;
+
+#define C(name, func, help) { NULL, name, help, func, NULL }
+
+/* The builtin commands */
+cmdstruct default_commands[] =
+{
+ C("help", cmd_help,
+ "[keys|math|command]\tthis text, or help on specified topic"),
+ C("17", (function_str)0,
+ "command\t\t\trepeat \"command\" 17 times"),
+#ifndef NO_SHELL
+ C("!", cmd_shell,
+ "shell-command\t\texecute a shell command using /bin/sh"),
+#endif
+ C("action", cmd_action,
+ "[[<|=|>|%][+|-]name] [{pattern|(expression)} [=[command]]]\n"
+ "\t\t\t\tdelete/list/define actions"),
+ C("add", cmd_add,
+ "{string|(expr)}\t\tadd the string to word completion list"),
+ C("addstatic", cmd_addstatic,
+ "{string|(expr)}\t\tadd the string to the static word completion list"),
+ C("alias", cmd_alias,
+ "[name[=[text]]]\t\tdelete/list/define aliases"),
+ C("at", cmd_at,
+ "[name [(time-string) [command]]\n\t\t\t\tset time of delayed label"),
+ C("beep", cmd_beep,
+ "\t\t\t\tmake your terminal beep (like #print (*7))"),
+ C("bind", cmd_bind,
+ "[edit|name [seq][=[command]]]\n"
+ "\t\t\t\tdelete/list/define key bindings"),
+ C("cancel", cmd_cancel,
+ "[number]\t\tcancel editing session"),
+ C("capture", cmd_capture,
+ "[filename]\t\tbegin/end of capture to file"),
+ C("clear", cmd_clear,
+ "\t\t\tclear input line (use from spawned programs)"),
+#ifdef BUG_TELNET
+ C("color", cmd_color,
+ "attr\t\t\tset default colors/attributes"),
+#endif
+ C("connect", cmd_connect,
+ "[connect-id [initstr] [address port]\n"
+ "\t\t\t\topen a new connection"),
+ C("cpu", cmd_cpu,
+ "\t\t\t\tshow CPU time used by powwow"),
+ C("delim", cmd_delim,
+ "[normal|program|{custom [chars]}]\n"
+ "\t\t\t\tset word completion delimeters"),
+ C("do", cmd_do,
+ "(expr) command\t\trepeat \"command\" (expr) times"),
+ C("edit", cmd_edit,
+ "\t\t\t\tlist editing sessions"),
+ C("emulate", cmd_emulate,
+ "[<|!]{text|(expr)}\tprocess result as if received from host"),
+ C("exe", cmd_exe,
+ "[<|!]{text|(string-expr)}\texecute result as if typed from keyboard"),
+ C("file", cmd_file,
+ "[=[filename]]\t\tset/show powwow definition file"),
+ C("for", cmd_for,
+ "([init];check;[loop]) command\n"
+ "\t\t\t\twhile \"check\" is true exec \"command\""),
+ C("group", cmd_group,
+ "[name] [on|off|list]\tgroup alias/action manipulation"),
+ C("groupdelim", cmd_groupdelim,
+ "[delimiter]\tchange delimiter for action/alias groups"),
+ C("hilite", cmd_hilite,
+ "[attr]\t\t\thighlight your input line"),
+ C("history", cmd_history,
+ "[{number|(expr)}]\tlist/execute commands in history"),
+ C("host", cmd_host,
+ "[hostname port]]\tset/show address of default host"),
+ C("identify", cmd_identify,
+ "[startact [endact]]\tsend MUME client identification"),
+ C("if", cmd_if,
+ "(expr) instr1 [; #else instr2]\n"
+ "\t\t\t\tif \"expr\" is true execute \"instr1\",\n"
+ "\t\t\t\totherwise execute \"instr2\""),
+ C("in", cmd_in,
+ "[label [(delay) [command]]]\tdelete/list/define delayed labels"),
+ C("init", cmd_init,
+ "[=[command]]\t\tdefine command to execute on connect to host"),
+ C("isprompt", cmd_isprompt,
+ "\t\t\trecognize a prompt as such"),
+ C("key", cmd_key,
+ "name\t\t\texecute the \"name\" key binding"),
+ C("keyedit", cmd_keyedit,
+ "editing-name\t\trun a line-editing function"),
+ C("load", cmd_load,
+ "[filename]\t\tload powwow settings from file"),
+ C("map", cmd_map,
+ "[-[number]|walksequence]\tshow/clear/edit (auto)map"),
+ C("mark", cmd_mark,
+ "[string[=[attr]]]\t\tdelete/list/define markers"),
+#ifdef HAVE_LIBDL
+ C("module", cmd_module,
+ "[name]\t\t\tload shared library extension"),
+#endif
+ C("movie", cmd_movie,
+ "[filename]\t\tbegin/end of movie record to file"),
+ C("net", cmd_net,
+ "\t\t\t\tprint amount of data received from/sent to host"),
+ C("nice", cmd_nice,
+ "[{number|(expr)}[command]]\n"
+ "\t\t\t\tset/show priority of new actions/marks"),
+ C("option", cmd_option,
+ "[[+|-|=]name]|list\tlist or view various options"),
+ C("prefix", cmd_prefix,
+ "string\t\t\tprefix all lines with string"),
+ C("", cmd_eval,
+ "(expr)\t\t\tevaluate expression, trashing result"),
+ C("print", cmd_print,
+ "[<|!][text|(expr)]\tprint text/result on screen, appending a \\n\n"
+ "\t\t\t\tif no argument, prints value of variable $0"),
+ C("prompt", cmd_prompt,
+ "[[<|=|>|%][+|-]name] [{pattern|(expression)} [=[prompt-command]]]\n"
+ "\t\t\t\tdelete/list/define actions on prompts"),
+ C("put", cmd_put,
+ "{text|(expr)}\t\tput text/result of expression in history"),
+ C("qui", cmd_qui,
+ "\t\t\t\tdo nothing"),
+ C("quit", cmd_quit,
+ "\t\t\t\tquit powwow"),
+ C("quote", cmd_quote,
+ "[on|off]\t\t\ttoggle verbatim-flag on/off"),
+ C("rawsend", cmd_rawsend,
+ "{string|(expr)}\tsend raw data to the MUD"),
+ C("rawprint", cmd_rawprint,
+ "{string|(expr)}\tsend raw data to the screen"),
+ C("rebind", cmd_rebind,
+ "name [seq]\t\tchange sequence of a key binding"),
+ C("rebindall", cmd_rebindall,
+ "\t\t\trebind all key bindings"),
+ C("rebindALL", cmd_rebindALL,
+ "\t\t\trebind ALL key bindings, even trivial ones"),
+ C("record", cmd_record,
+ "[filename]\t\tbegin/end of record to file"),
+ C("request", cmd_request,
+ "[editor][prompt][all]\tsend various identification strings"),
+ C("reset", cmd_reset,
+ "<list-name>\t\tclear the whole defined list and reload default"),
+ C("retrace", cmd_retrace,
+ "[number]\t\tretrace the last number steps"),
+ C("save", cmd_save,
+ "[filename]\t\tsave powwow settings to file"),
+ C("send", cmd_send,
+ "[<|!]{text|(expr)}\teval expression, sending result to the MUD"),
+ C("setvar", cmd_setvar,
+ "name[=text|(expr)]\tset/show internal limits and variables"),
+ C("snoop", cmd_snoop,
+ "connect-id\t\ttoggle output display for connections"),
+ C("spawn", cmd_spawn,
+ "connect-id command\ttalk with a shell command"),
+ C("speedwalk", cmd_speedwalk,
+ "[speedwalk sequence]\texecute a speedwalk sequence explicitly"),
+ C("stop", cmd_stop,
+ "\t\t\t\tremove all delayed commands from active list"),
+ C("time", cmd_time,
+ "\t\t\t\tprint current time and date"),
+ C("var", cmd_var,
+ "variable [= [<|!]{string|(expr)} ]\n"
+ "\t\t\t\twrite result into the variable"),
+ C("ver", cmd_ver,
+ "\t\t\t\tshow powwow version"),
+ C("while", cmd_while,
+ "(expr) instr\t\twhile \"expr\" is true execute \"instr\""),
+ C("write", cmd_write,
+ "[>|!](expr;name)\t\twrite result of expr to \"name\" file"),
+ C("zap", cmd_zap,
+ "connect-id\t\t\tclose a connection"),
+ { NULL }
+};
+
+char *_cmd_sort_name( cmdstruct *cmd ) {
+ if( cmd -> sortname == NULL )
+ return( cmd -> name );
+ else
+ return( cmd -> sortname );
+}
+
+/* Adds a cmd to commands (inserts the ptr in the list, DO NOT FREE IT) */
+void cmd_add_command( cmdstruct *cmd ) {
+ /* do insert/sort */
+ cmdstruct *c = commands;
+
+ /*
+ * make sure it doesn't override another commmand
+ * this is important not just because it'd be irritating,
+ * but if a module defined the command in the global static
+ * space, it would create an infinite loop because the -> next
+ * ptr would point at itself
+ *
+ * doing it up front because based on the sortname, we may never see
+ * the dup item if we do it at sort time
+ */
+ for( c = commands; c != NULL; c = c -> next ) {
+ if( strcmp( cmd -> name, c -> name ) == 0 ) {
+ PRINTF( "#error %s is already defined\n", c -> name );
+ return;
+ }
+ }
+
+
+ /* catch insertion to head of list */
+ if( commands == NULL ) {
+ /* no commands yet */
+ commands = cmd;
+ cmd -> next = NULL;
+ return;
+ }
+
+ if( strcmp( _cmd_sort_name( commands ), _cmd_sort_name( cmd ) ) > 0 ) {
+ /* this is lower in sort than every item, so
+ * make it the head of the list */
+ cmd -> next = commands;
+ commands = cmd;
+ return;
+ }
+
+ for( c = commands; c != NULL; c = c -> next ) {
+ if( strcmp( _cmd_sort_name( cmd ), _cmd_sort_name( c ) ) >= 0 ) {
+ /* Need second check to handle empty string case */
+ if( c -> next == NULL || strcmp( _cmd_sort_name( cmd ), _cmd_sort_name( c -> next ) ) <= 0 ) {
+ /*PRINTF( "Inserting %s after %s\n", cmd -> name, c -> name ); */
+
+ /* insert after this one, it is greater than this
+ * entry but less than the next */
+ cmd -> next = c -> next;
+ c -> next = cmd;
+ return;
+ }
+ }
+ }
+
+ PRINTF( "ERROR INSERTING COMMAND\n" );
+}
+
+/* Init the command listing, called from main */
+void initialize_cmd(void) {
+ int i;
+
+ /* Now add the default command list */
+ for( i = 0; default_commands[ i ].name; i++ )
+ cmd_add_command( &default_commands[ i ] );
+}
+
+#ifdef HAVE_LIBDL
+static void cmd_module(char *arg) {
+ char libname[1024];
+ void *lib;
+ void (*func)();
+
+ int pindex;
+ struct stat junk;
+ char *prefixes[] = {
+ PLUGIN_DIR,
+ ".",
+ "/lib/powwow",
+ "/usr/lib/powwow",
+ "/usr/local/lib/powwow",
+ "$HOME/.powwow/lib" /* this doesn't work, but is here to remind me :p */
+ };
+
+ arg = skipspace(arg);
+
+ /* I changed it to work this way so that you can have libs in multiple places and
+ * also eventually to allow it to use .dll instead of .so under the cygwin environment */
+ for( pindex = 0; pindex < 5; pindex++ ) {
+ memset( libname, 0, sizeof libname );
+
+ /* don't look for name without .so, it breaks if you have a file
+ * with the same name in the current dir and making it .so for sure
+ * will skip these files since they are probably not libs to load
+ snprintf( libname, 1024, "%s/%s", prefixes[ pindex ], arg );
+ if( stat( libname, &junk ) == 0 ) {
+ break;
+ }
+ */
+
+ snprintf( libname, 1024, "%s/%s.so", prefixes[ pindex ], arg );
+ if( stat( libname, &junk ) == 0 ) {
+ break;
+ }
+ }
+
+ /* open lib */
+ lib = dlopen( libname, RTLD_GLOBAL | RTLD_LAZY );
+ if( ! lib ) {
+ PRINTF( "#module error: %s\n", dlerror() );
+ return;
+ }else{
+ PRINTF( "#module loaded %s\n", libname );
+ }
+
+ func = dlsym( lib, "powwow_init" );
+ if( func ) {
+ (*func)();
+ }else{
+ PRINTF( "#module error: %s\n", dlerror() );
+ }
+}
+#endif
+
+static void cmd_group(char *arg) {
+ char *group;
+ int active;
+ aliasnode *p;
+ actionnode *a;
+
+ arg = skipspace(arg);
+
+ if( *arg ) {
+ arg = first_regular( group = arg, ' ');
+ *arg = '\0';
+ arg = skipspace( arg + 1 );
+
+ if( strcmp( arg, "on" ) == 0 ) {
+ active = 1;
+ }else if( strcmp( arg, "off" ) == 0 ) {
+ active = 0;
+ }else if( strcmp( arg, "list" ) == 0 ) {
+ PRINTF( "#not implemented\n" );
+ return;
+ }else{
+ PRINTF( "#unknown group command, use off/on/list\n" );
+ return;
+ }
+
+ /* Now loop over all aliases/actions by groupname and toggle */
+ for( p = sortedaliases; p; p = p -> snext ) {
+ if( p -> group && strcmp( p -> group, group ) == 0 ) {
+ p -> active = active;
+ }
+ }
+
+ /* Same for actions */
+ for( a = actions; a; a = a -> next ) {
+ if( a -> group && strcmp( a -> group, group ) == 0 ) {
+ a -> active = active;
+ }
+ }
+ }else{
+ PRINTF( "#group name required\n" );
+ }
+}
+
+static void cmd_groupdelim(char *arg) {
+ if( *arg != 0 ) {
+ free( group_delim );
+ group_delim = my_strdup( arg );
+ PRINTF( "#group delimiter is now '%s'\n", group_delim );
+ }
+}
+
+static void cmd_help(char *arg)
+{
+ int i, size;
+ char *text, *tmp;
+ FILE *f;
+ char line[BUFSIZE];
+ int len;
+ cmdstruct *c;
+
+ arg = skipspace(arg);
+ if (*arg == '#') arg++;
+ if (!*arg) {
+ size = 25;
+ for( c = commands; c != NULL; c = c -> next )
+ size += strlen(c -> name) + strlen(c -> help) + 5;
+
+ text = tmp = (char *)malloc(size);
+ if (!text) {
+ errmsg("malloc");
+ return;
+ }
+
+ /* do not use sprintf() return value, almost every OS returns a different thing. */
+ sprintf(tmp, "#help\n#commands available:\n");
+ tmp += strlen(tmp);
+
+ for( c = commands; c != NULL; c = c -> next ) {
+ sprintf(tmp, "#%s %s\n", c -> name, c -> help);
+ tmp += strlen(tmp);
+ }
+
+ message_edit(text, strlen(text), 1, 1);
+ return;
+ }
+
+ if (!strncmp(arg, "copyright", strlen(arg))) {
+ int fd, left, got = 0;
+ struct stat stbuf;
+
+ if (stat(copyfile, &stbuf) < 0) {
+ errmsg("stat(copyright file)");
+ return;
+ }
+
+ if (!(text = (char *)malloc(left = stbuf.st_size))) {
+ errmsg("malloc");
+ return;
+ }
+ if ((fd = open(copyfile, O_RDONLY)) < 0) {
+ errmsg("open(copyright file)");
+ free(text);
+ return;
+ }
+ while (left > 0) {
+ while ((i = read(fd, text + got, left)) < 0 && errno == EINTR)
+ ;
+ if (i < 0 && errno == EINTR) {
+ errmsg("read (copyright file)");
+ free(text);
+ close(fd);
+ return;
+ }
+ if (i == 0)
+ break;
+ left -= i, got += i;
+ }
+ close(fd);
+ message_edit(text, strlen(text), 1, 1);
+ return;
+ }
+
+ /* !copyright */
+
+ f = fopen(helpfile, "r");
+ if (!f) {
+ PRINTF("#cannot open help file \"%s\": %s\n",
+ helpfile, strerror(errno));
+ return;
+ }
+
+ while ((tmp = fgets(line, BUFSIZE, f)) &&
+ (line[0] != '@' || strncmp(line + 1, arg, strlen(arg))))
+ ;
+
+ if (!tmp) {
+ PRINTF("#no entry for \"%s\" in the help file.\n", arg);
+ fclose(f);
+ return;
+ }
+
+ if (!(text = (char *)malloc(size = BUFSIZE))) {
+ errmsg("malloc");
+ fclose(f);
+ return;
+ }
+
+ /* the first line becomes $TITLE */
+ tmp = strchr(line, '\n');
+ if (tmp) *tmp = '\0';
+ i = sprintf(text, "Help on '%s'\n", line + 1);
+
+ /* allow multiple commands to share the same help */
+ while (fgets(line, BUFSIZE, f) && line[0] == '@') ;
+
+ do {
+ if ((len = strlen(line)) >= size - i) {
+ /* Not enough space in current buffer */
+
+ if (!(tmp = (char *)malloc(size += BUFSIZE))) {
+ errmsg("malloc");
+ free(text);
+ fclose(f);
+ return;
+ } else {
+ memcpy(tmp, text, i);
+ free(text);
+ text = tmp;
+ }
+ }
+ memcpy(text + i, line, len);
+ i += len;
+ } while (fgets(line, BUFSIZE, f) && line[0] != '@');
+
+ fclose(f);
+ text[i] = '\0'; /* safe, there is space */
+ message_edit(text, strlen(text), 1, 1);
+}
+
+static void cmd_clear(char *arg)
+{
+ if (line_status == 0) {
+ clear_input_line(opt_compact);
+ if (!opt_compact) {
+ tty_putc('\n');
+ col0 = 0;
+ }
+ status(1);
+ }
+}
+
+#ifndef NO_SHELL
+static void cmd_shell(char *arg)
+{
+ if (!*arg) {
+ if (opt_info) {
+ PRINTF("#that's easy.\n");
+ }
+ } else {
+ tty_quit();
+
+ if (system(arg) == -1) {
+ perror("system()");
+ }
+
+ tty_start();
+ tty_gotoxy(col0 = 0, line0 = lines -1);
+ tty_puts(tty_clreoln);
+ }
+}
+#endif
+
+static void cmd_alias(char *arg)
+{
+ arg = skipspace(arg);
+ if (!*arg)
+ show_aliases();
+ else
+ parse_alias(arg);
+}
+
+static void cmd_action(char *arg)
+{
+ arg = skipspace(arg);
+ if (!*arg)
+ show_actions();
+ else
+ parse_action(arg, 0);
+}
+
+static void cmd_prompt(char *arg)
+{
+ arg = skipspace(arg);
+ if (!*arg)
+ show_prompts();
+ else
+ parse_action(arg, 1);
+}
+
+static void cmd_beep(char *arg)
+{
+ tty_putc('\007');
+}
+
+/*
+ * create/list/edit/delete bindings
+ */
+static void cmd_bind(char *arg)
+{
+ arg = skipspace(arg);
+ if (!*arg)
+ show_binds(0);
+ else if (!strcmp(arg, "edit"))
+ show_binds(1);
+ else
+ parse_bind(arg);
+}
+
+static void cmd_delim(char *arg)
+{
+ char buf[BUFSIZE];
+ int n;
+
+ arg = skipspace(arg);
+ if (!*arg) {
+ PRINTF("#delim: \"%s\" (%s)\n", delim_name[delim_mode], DELIM);
+ return;
+ }
+
+ arg = split_first_word(buf, BUFSIZE, arg);
+ n = 0;
+ while (n < DELIM_MODES && strncmp(delim_name[n], buf, strlen(buf)) != 0)
+ n++;
+
+ if (n >= DELIM_MODES) {
+ PRINTF("#delim [normal|program|{custom <chars>}\n");
+ return;
+ }
+
+ if (n == DELIM_CUSTOM) {
+ if (!strchr(arg, ' ')) {
+ my_strncpy(buf+1, arg, BUFSIZE-2);
+ *buf = ' '; /* force ' ' in the delims */
+ arg = buf;
+ }
+ unescape(arg);
+ set_custom_delimeters(arg);
+ } else
+ delim_mode = n;
+}
+
+static void cmd_do(char *arg)
+{
+ int type;
+ long result;
+
+ arg = skipspace(arg);
+ if (*arg != '(') {
+ PRINTF("#do: ");
+ print_error(error=MISMATCH_PAREN_ERROR);
+ return;
+ }
+ arg++;
+
+ type = evall(&result, &arg);
+ if (REAL_ERROR) return;
+
+ if (type != TYPE_NUM) {
+ PRINTF("#do: ");
+ print_error(error=NO_NUM_VALUE_ERROR);
+ return;
+ }
+
+ if (*arg == ')') { /* skip the ')' */
+ if (*++arg == ' ')
+ arg++;
+ }
+ else {
+ PRINTF("#do: ");
+ print_error(error=MISSING_PAREN_ERROR);
+ return;
+ }
+
+ if (result >= 0)
+ while (!error && result--)
+ (void)parse_instruction(arg, 1, 0, 1);
+ else {
+ PRINTF("#do: bogus repeat count \"%ld\"\n", result);
+ }
+}
+
+static void cmd_hilite(char *arg)
+{
+ int attr;
+
+ arg = skipspace(arg);
+ attr = parse_attributes(arg);
+ if (attr == -1) {
+ PRINTF("#attribute syntax error.\n");
+ if (opt_info)
+ show_attr_syntax();
+ } else {
+ attr_string(attr, edattrbeg, edattrend);
+
+ edattrbg = ATTR(attr) & ATTR_INVERSE ? 1
+ : BACKGROUND(attr) != NO_COLOR || ATTR(attr) & ATTR_BLINK;
+
+ if (opt_info) {
+ PRINTF("#input highlighting is now %so%s%s.\n",
+ edattrbeg, (attr == NOATTRCODE) ? "ff" : "n",
+ edattrend);
+ }
+ }
+}
+
+static void cmd_history(char *arg)
+{
+ int num = 0;
+ long buf;
+
+ arg = skipspace(arg);
+
+ if (history_done >= MAX_HIST) {
+ print_error(error=HISTORY_RECURSION_ERROR);
+ return;
+ }
+ history_done++;
+
+ if (*arg == '(') {
+ arg++;
+ num = evall(&buf, &arg);
+ if (!REAL_ERROR && num != TYPE_NUM)
+ error=NO_NUM_VALUE_ERROR;
+ if (REAL_ERROR) {
+ PRINTF("#history: ");
+ print_error(error=NO_NUM_VALUE_ERROR);
+ return;
+ }
+ num = (int)buf;
+ } else
+ num = atoi(arg);
+
+ if (num > 0)
+ exe_history(num);
+ else
+ show_history(-num);
+}
+
+static void cmd_host(char *arg)
+{
+ char newhost[BUFSIZE];
+
+ arg = skipspace(arg);
+ if (*arg) {
+ arg = split_first_word(newhost, BUFSIZE, arg);
+ if (*arg) {
+ my_strncpy(hostname, newhost, BUFSIZE-1);
+ portnumber = atoi(arg);
+ if (opt_info) {
+ PRINTF("#host set to: %s %d\n", hostname, portnumber);
+ }
+ } else {
+ PRINTF("#host: missing portnumber.\n");
+ }
+ } else if (*hostname)
+ sprintf(inserted_next, "#host %.*s %d", BUFSIZE-INTLEN-8,
+ hostname, portnumber);
+ else {
+ PRINTF("#syntax: #host hostname port\n");
+ }
+}
+
+static void cmd_request(char *arg)
+{
+ char *idprompt = "~$#EP2\nG\n";
+ char *ideditor = "~$#EI\n";
+ char buf[256];
+ int all, len;
+
+ if (tcp_fd == -1) {
+ PRINTF("#not connected to a MUD!\n");
+ return;
+ }
+ while (*(arg = skipspace(arg))) {
+ arg = split_first_word(buf, 256, arg);
+ if (*buf) {
+ all = !strcmp(buf, "all");
+ len = strlen(buf);
+ if ((all || !strncmp(buf, "editor", len))) {
+ tcp_raw_write(tcp_fd, ideditor, strlen(ideditor));
+ CONN_LIST(tcp_fd).flags |= IDEDITOR;
+ if (opt_info) {
+ PRINTF("#request editor: %s done!\n", ideditor);
+ }
+ }
+ if ((all || !strncmp(buf, "prompt", len))) {
+ tcp_raw_write(tcp_fd, idprompt, strlen(idprompt));
+ CONN_LIST(tcp_fd).flags |= IDPROMPT;
+ if (opt_info) {
+ PRINTF("#request prompt: %s done!\n", idprompt);
+ }
+ }
+ }
+ }
+
+}
+
+static void cmd_identify(char *arg)
+{
+ edit_start[0] = edit_end[0] = '\0';
+ if (*arg) {
+ char *p = strchr(arg, ' ');
+ if (p) {
+ *(p++) = '\0';
+ my_strncpy(edit_end, p, BUFSIZE-1);
+ }
+ my_strncpy(edit_start, arg, BUFSIZE-1);
+ }
+ cmd_request("editor");
+}
+
+static void cmd_in(char *arg)
+{
+ char *name;
+ long millisec, buf;
+ int type;
+ delaynode **p;
+
+ arg = skipspace(arg);
+ if (!*arg) {
+ show_delays();
+ return;
+ }
+
+ arg = first_regular(name = arg, ' ');
+ if (*arg)
+ *arg++ = 0;
+
+ unescape(name);
+
+ p = lookup_delay(name, 0);
+ if (!*p) p = lookup_delay(name, 1);
+
+ if (!*arg && !*p) {
+ PRINTF("#unknown delay label, cannot show: \"%s\"\n", name);
+ return;
+ }
+ if (!*arg) {
+ show_delaynode(*p, 1);
+ return;
+ }
+ if (*arg != '(') {
+ PRINTF("#in: ");
+ print_error(error=MISMATCH_PAREN_ERROR);
+ return;
+ }
+ arg++; /* skip the '(' */
+
+ type = evall(&buf, &arg);
+ if (!REAL_ERROR) {
+ if (type!=TYPE_NUM)
+ error=NO_NUM_VALUE_ERROR;
+ else if (*arg != ')')
+ error=MISSING_PAREN_ERROR;
+ }
+ if (REAL_ERROR) {
+ PRINTF("#in: ");
+ print_error(error);
+ return;
+ }
+
+ arg = skipspace(arg+1);
+ millisec = buf;
+ if (*p && millisec)
+ change_delaynode(p, arg, millisec);
+ else if (!*p && millisec) {
+ if (*arg)
+ new_delaynode(name, arg, millisec);
+ else {
+ PRINTF("#cannot create delay label without a command.\n");
+ }
+ } else if (*p && !millisec) {
+ if (opt_info) {
+ PRINTF("#deleting delay label: %s %s\n", name, (*p)->command);
+ }
+ delete_delaynode(p);
+ } else {
+ PRINTF("#unknown delay label, cannot delete: \"%s\"\n", name);
+ }
+}
+
+static void cmd_at(char *arg)
+{
+ char *name, *buf = NULL;
+ char dayflag=0;
+ struct tm *twhen;
+ int num, hour = -1, minute = -1, second = -1;
+ delaynode **p;
+ long millisec;
+ ptr pbuf = (ptr)0;
+
+ arg = skipspace(arg);
+ if (!*arg) {
+ show_delays();
+ return;
+ }
+
+ arg = first_regular(name = arg, ' ');
+ if (*arg)
+ *arg++ = 0;
+
+ unescape(name);
+
+ p = lookup_delay(name, 0);
+ if (!*p) p = lookup_delay(name, 1);
+
+ if (!*arg && !*p) {
+ PRINTF("#unknown delay label, cannot show: \"%s\"\n", name);
+ return;
+ }
+ if (!*arg) {
+ show_delaynode(*p, 2);
+ return;
+ }
+ if (*arg != '(') {
+ PRINTF("#in: ");
+ print_error(error=MISMATCH_PAREN_ERROR);
+ return;
+ }
+ arg++; /* skip the '(' */
+
+ (void)evalp(&pbuf, &arg);
+ if (REAL_ERROR) {
+ print_error(error);
+ ptrdel(pbuf);
+ return;
+ }
+ if (pbuf) {
+ /* convert time-string into hour, minute, second */
+ buf = skipspace(ptrdata(pbuf));
+ if (!*buf || !isdigit(*buf)) {
+ PRINTF("#at: ");
+ print_error(error=NO_NUM_VALUE_ERROR);
+ ptrdel(pbuf);
+ return;
+ }
+ num = atoi(buf);
+ second = num % 100;
+ minute = (num /= 100) % 100;
+ hour = num / 100;
+ }
+ if (hour < 0 || hour>23 || minute < 0 || minute>59
+ || second < 0 || second>59) {
+
+ PRINTF("#at: #error: invalid time \"%s\"\n",
+ pbuf && buf ? buf : (char *)"");
+ error=OUT_RANGE_ERROR;
+ ptrdel(pbuf);
+ return;
+ }
+ ptrdel(pbuf);
+
+ if (*arg == ')') { /* skip the ')' */
+ if (*++arg == ' ')
+ arg++;
+ }
+ else {
+ PRINTF("#at: ");
+ print_error(error=MISSING_PAREN_ERROR);
+ return;
+ }
+
+ arg = skipspace(arg);
+ update_now();
+ twhen = localtime((time_t *)&now.tv_sec);
+ /* put current year, month, day in calendar struct */
+
+ if (hour < twhen->tm_hour ||
+ (hour == twhen->tm_hour &&
+ (minute < twhen->tm_min ||
+ (minute == twhen->tm_min &&
+ second <= twhen->tm_sec)))) {
+ dayflag = 1;
+ /* it is NOT possible to define an #at refering to the past */
+ }
+
+ /* if you use a time smaller than the current, it refers to tomorrow */
+
+ millisec = (hour - twhen->tm_hour) * 3600 + (minute - twhen->tm_min) * 60 +
+ second - twhen->tm_sec + (dayflag ? 24*60*60 : 0);
+ millisec *= mSEC_PER_SEC; /* Comparing time with current calendar,
+ we finally got the delay */
+ millisec -= now.tv_usec / uSEC_PER_mSEC;
+
+ if (*p)
+ change_delaynode(p, arg, millisec);
+ else
+ if (*arg)
+ new_delaynode(name, arg, millisec);
+ else {
+ PRINTF("#cannot create delay label without a command.\n");
+ }
+}
+
+static void cmd_init(char *arg)
+{
+ arg = skipspace(arg);
+
+ if (*arg == '=') {
+ if (*++arg) {
+ my_strncpy(initstr, arg, BUFSIZE-1);
+ if (opt_info) {
+ PRINTF("#init: %s\n", initstr);
+ }
+ } else {
+ *initstr = '\0';
+ if (opt_info) {
+ PRINTF("#init cleared.\n");
+ }
+ }
+ } else
+ sprintf(inserted_next, "#init =%.*s", BUFSIZE-8, initstr);
+}
+
+static void cmd_isprompt(char *arg)
+{
+ if (tcp_fd == tcp_main_fd) {
+ int i;
+ long l;
+ arg = skipspace(arg);
+ if (*arg == '(') {
+ arg++;
+ i = evall(&l, &arg);
+ if (!REAL_ERROR) {
+ if (i!=TYPE_NUM)
+ error=NO_NUM_VALUE_ERROR;
+ else if (*arg != ')')
+ error=MISSING_PAREN_ERROR;
+ }
+ if (REAL_ERROR) {
+ PRINTF("#isprompt: ");
+ print_error(error);
+ return;
+ }
+ i = (int)l;
+ } else
+ i = atoi(arg);
+
+ if (i == 0)
+ surely_isprompt = -1;
+ else if (i < 0) {
+ if (i > -NUMPARAM && *VAR[-i].str)
+ ptrtrunc(prompt->str, surely_isprompt = ptrlen(*VAR[-i].str));
+ } else
+ ptrtrunc(prompt->str, surely_isprompt = i);
+ }
+}
+
+static void cmd_key(char *arg)
+{
+ keynode *q=NULL;
+
+ arg = skipspace(arg);
+ if (!*arg)
+ return;
+
+ if ((q = *lookup_key(arg)))
+ q->funct(q->call_data);
+ else {
+ PRINTF("#no such key: \"%s\"\n", arg);
+ }
+}
+
+static void cmd_keyedit(char *arg)
+{
+ int function;
+ char *param;
+
+ arg = skipspace(arg);
+ if (!*arg)
+ return;
+
+ if ((function = lookup_edit_name(arg, &param)))
+ internal_functions[function].funct(param);
+ else {
+ PRINTF("#no such editing function: \"%s\"\n", arg);
+ }
+}
+
+static void cmd_map(char *arg)
+{
+ arg = skipspace(arg);
+ if (!*arg) /* show map */
+ map_show();
+ else if (*arg == '-') /* retrace steps without walking */
+ map_retrace(atoi(arg + 1), 0);
+ else
+ map_walk(arg, 1, 1);
+}
+
+static void cmd_retrace(char *arg)
+{
+ map_retrace(atoi(arg), 1);
+}
+
+static void cmd_mark(char *arg)
+{
+ if (!*arg)
+ show_marks();
+ else
+ parse_mark(arg);
+}
+
+static void cmd_nice(char *arg)
+{
+ int nnice = a_nice;
+ arg = skipspace(arg);
+ if (!*arg) {
+ PRINTF("#nice: %d\n", a_nice);
+ return;
+ }
+ if (isdigit(*arg)) {
+ a_nice = 0;
+ while (isdigit(*arg)) {
+ a_nice *= 10;
+ a_nice += *arg++ - '0';
+ }
+ }
+ else if (*arg++=='(') {
+ long buf;
+ int type;
+
+ type = evall(&buf, &arg);
+ if (!REAL_ERROR && type!=TYPE_NUM)
+ error=NO_NUM_VALUE_ERROR;
+ else if (!REAL_ERROR && *arg++ != ')')
+ error=MISSING_PAREN_ERROR;
+ if (REAL_ERROR) {
+ PRINTF("#nice: ");
+ print_error(error);
+ return;
+ }
+ a_nice = (int)buf;
+ if (a_nice<0)
+ a_nice = 0;
+ }
+ arg = skipspace(arg);
+ if (*arg) {
+ parse_instruction(arg, 0, 0, 1);
+ a_nice = nnice;
+ }
+}
+
+static void cmd_prefix(char *arg)
+{
+ strcpy(prefixstr, arg);
+ if (opt_info) {
+ PRINTF("#prefix %s.\n", *arg ? "set" : "cleared");
+ }
+}
+
+static void cmd_quote(char *arg)
+{
+ arg = skipspace(arg);
+ if (!*arg)
+ verbatim ^= 1;
+ else if (!strcmp(arg, "on"))
+ verbatim = 1;
+ else if (!strcmp(arg, "off"))
+ verbatim = 0;
+ if (opt_info) {
+ PRINTF("#%s mode.\n", verbatim ? "verbatim" : "normal");
+ }
+}
+
+/*
+ * change the escape sequence of an existing binding
+ */
+static void cmd_rebind(char *arg)
+{
+ parse_rebind(arg);
+}
+
+static void cmd_rebindall(char *arg)
+{
+ keynode *kp;
+ char *seq;
+
+ for (kp = keydefs; kp; kp = kp->next) {
+ seq = kp->sequence;
+ if (kp->seqlen == 1 && seq[0] < ' ')
+ ;
+ else if (kp->seqlen == 2 && seq[0] == '\033' && isalnum(seq[1]))
+ ;
+ else {
+ parse_rebind(kp->name);
+ if (error)
+ break;
+ }
+ }
+}
+
+static void cmd_rebindALL(char *arg)
+{
+ keynode *kp;
+
+ for (kp = keydefs; kp; kp = kp->next) {
+ parse_rebind(kp->name);
+ if (error)
+ break;
+ }
+}
+
+static void cmd_reset(char *arg)
+{
+ char all = 0;
+ arg = skipspace(arg);
+ if (!*arg) {
+ PRINTF("#reset: must specify one of:\n");
+ tty_puts(" alias, action, bind, in (or at), mark, prompt, var, all.\n");
+ return;
+ }
+ all = !strcmp(arg, "all");
+ if (all || !strcmp(arg, "alias")) {
+ int n;
+ for (n = 0; n < MAX_HASH; n++) {
+ while (aliases[n])
+ delete_aliasnode(&aliases[n]);
+ }
+ if (!all)
+ return;
+ }
+ if (all || !strcmp(arg, "action")) {
+ while (actions)
+ delete_actionnode(&actions);
+ if (!all)
+ return;
+ }
+ if (all || !strcmp(arg, "bind")) {
+ while (keydefs)
+ delete_keynode(&keydefs);
+ tty_add_initial_binds();
+ tty_add_walk_binds();
+ if (!all)
+ return;
+ }
+ if (all || !strcmp(arg, "in") || !strcmp(arg, "at")) {
+ while (delays)
+ delete_delaynode(&delays);
+ while (dead_delays)
+ delete_delaynode(&dead_delays);
+ if (!all)
+ return;
+ }
+ if (all || !strcmp(arg, "mark")) {
+ while (markers)
+ delete_marknode(&markers);
+ if (!all)
+ return;
+ }
+ if (all || !strcmp(arg, "prompt")) {
+ while (prompts)
+ delete_promptnode(&prompts);
+ if (!all)
+ return;
+ }
+ if (all || !strcmp(arg, "var")) {
+ int n;
+ varnode **first;
+
+ for (n = 0; n < MAX_HASH; n++) {
+ while (named_vars[0][n])
+ delete_varnode(&named_vars[0][n], 0);
+ first = &named_vars[1][n];
+ while (*first) {
+ if (is_permanent_variable(*first))
+ first = &(*first)->next;
+ else
+ delete_varnode(first, 1);
+ }
+ }
+
+ for (n = 0; n < NUMVAR; n++) {
+ *var[n].num = 0;
+ ptrdel(*var[n].str);
+ *var[n].str = NULL;
+ }
+ if (!all)
+ return;
+ }
+}
+
+static void cmd_snoop(char *arg)
+{
+ if (!*arg) {
+ PRINTF("#snoop: which connection?\n");
+ } else
+ tcp_togglesnoop(arg);
+}
+
+static void cmd_stop(char *arg)
+{
+ delaynode *dying;
+
+ if (delays)
+ update_now();
+
+ while (delays) {
+ dying = delays;
+ delays = dying->next;
+ dying->when.tv_sec = now.tv_sec;
+ dying->when.tv_usec = now.tv_usec;
+ dying->next = dead_delays;
+ dead_delays = dying;
+ }
+ if (opt_info) {
+ PRINTF("#all delayed labels are now disabled.\n");
+ }
+}
+
+static void cmd_time(char *arg)
+{
+ struct tm *s;
+ char buf[BUFSIZE];
+
+ update_now();
+ s = localtime((time_t *)&now.tv_sec);
+ (void)strftime(buf, BUFSIZE - 1, "%a, %d %b %Y %H:%M:%S", s);
+ PRINTF("#current time is %s\n", buf);
+}
+
+static void cmd_ver(char *arg)
+{
+ printver();
+}
+
+static void cmd_emulate(char *arg)
+{
+ char kind;
+ FILE *fp;
+ long start, end, i = 1;
+ int len;
+ ptr pbuf = (ptr)0;
+
+ arg = redirect(arg, &pbuf, &kind, "emulate", 0, &start, &end);
+ if (REAL_ERROR || !arg)
+ return;
+
+ if (kind) {
+ char buf[BUFSIZE];
+
+ fp = (kind == '!') ? popen(arg, "r") : fopen(arg, "r");
+ if (!fp) {
+ PRINTF("#emulate: #error opening \"%s\"\n", arg);
+ print_error(error=SYNTAX_ERROR);
+ ptrdel(pbuf);
+ return;
+ }
+ status(-1); /* we're pretending we got something from the MUD */
+ while (!error && (!start || i<=end) && fgets(buf, BUFSIZE, fp))
+ if (!start || i++>=start)
+ process_remote_input(buf, strlen(buf));
+
+ if (kind == '!') pclose(fp); else fclose(fp);
+ } else {
+ status(-1); /* idem */
+ /* WARNING: no guarantee there is space! */
+ arg[len = strlen(arg)] = '\n';
+ arg[++len] = '\0';
+ process_remote_input(arg, len);
+ }
+ ptrdel(pbuf);
+}
+
+static void cmd_eval(char *arg)
+{
+ arg = skipspace(arg);
+ if (*arg=='(') {
+ arg++;
+ (void)evaln(&arg);
+ if (*arg != ')') {
+ PRINTF("#(): ");
+ print_error(error=MISSING_PAREN_ERROR);
+ }
+ }
+ else {
+ PRINTF("#(): ");
+ print_error(error=MISMATCH_PAREN_ERROR);
+ }
+}
+
+static void cmd_exe(char *arg)
+{
+ char kind;
+ char *clear;
+ long offset, start, end, i = 1;
+ FILE *fp;
+ ptr pbuf = (ptr)0;
+
+ arg = redirect(arg, &pbuf, &kind, "exe", 0, &start, &end);
+ if (REAL_ERROR || !arg)
+ return;
+
+ if (kind) {
+ char buf[BUFSIZE];
+
+ fp = (kind == '!') ? popen(arg, "r") : fopen(arg, "r");
+ if (!fp) {
+ PRINTF("#exe: #error opening \"%s\"\n", arg);
+ error = SYNTAX_ERROR;
+ ptrdel(pbuf);
+ return;
+ }
+ offset = 0;
+ /* We may go in to a loop if a single function is more than 4k, but if that's
+ * the case then maybe you should break it down a little bit :p */
+ while (!error && (!start || i<=end) && fgets(buf + offset, BUFSIZE - offset, fp))
+ /* If it ends with \\\n then it's a line continuation, so clear
+ * the \\\n and do another fgets */
+ if (buf[offset + strlen(buf + offset) - 2] == '\\') {
+ /* Clear \n prefixed with a literal backslash '\\' */
+ if ((clear = strstr(buf + offset, "\\\n")))
+ *clear = '\0';
+ offset += strlen(buf + offset);
+ } else {
+ if (!start || i++ >= start) {
+ buf[strlen(buf)-1] = '\0';
+ parse_user_input(buf, 0);
+ offset = 0;
+ }
+ }
+
+ if (kind == '!') pclose(fp); else fclose(fp);
+ } else
+ parse_user_input(arg, 0);
+ ptrdel(pbuf);
+}
+
+static void cmd_print(char *arg)
+{
+ char kind;
+ long start, end, i = 1;
+ FILE *fp;
+ ptr pbuf = (ptr)0;
+
+ clear_input_line(opt_compact);
+
+ if (!*arg) {
+ smart_print(*VAR[0].str ? ptrdata(*VAR[0].str) : (char *)"", 1);
+ return;
+ }
+
+ arg = redirect(arg, &pbuf, &kind, "print", 1, &start, &end);
+ if (REAL_ERROR || !arg)
+ return;
+
+ if (kind) {
+ char buf[BUFSIZE];
+ fp = (kind == '!') ? popen(arg, "r") : fopen(arg, "r");
+ if (!fp) {
+ PRINTF("#print: #error opening \"%s\"\n", arg);
+ error=SYNTAX_ERROR;
+ ptrdel(pbuf);
+ return;
+ }
+ while (!error && (!start || i <= end) && fgets(buf, BUFSIZE, fp))
+ if (!start || i++>=start)
+ tty_puts(buf);
+ tty_putc('\n');
+
+ if (kind == '!') pclose(fp); else fclose(fp);
+ } else
+ smart_print(arg, 1);
+ ptrdel(pbuf);
+}
+
+static void cmd_send(char *arg)
+{
+ char *newline, kind;
+ long start, end, i = 1;
+ FILE *fp;
+ ptr pbuf = (ptr)0;
+
+ arg = redirect(arg, &pbuf, &kind, "send", 0, &start, &end);
+ if (REAL_ERROR ||!arg)
+ return;
+
+ if (kind) {
+ char buf[BUFSIZE];
+ fp = (kind == '!') ? popen(arg, "r") : fopen(arg, "r");
+ if (!fp) {
+ PRINTF("#send: #error opening \"%s\"\n", arg);
+ error = SYNTAX_ERROR;
+ ptrdel(pbuf);
+ return;
+ }
+ while (!error && (!start || i<=end) && fgets(buf, BUFSIZE, fp)) {
+ if ((newline = strchr(buf, '\n')))
+ *newline = '\0';
+
+ if (!start || i++>=start) {
+ if (opt_echo) {
+ PRINTF("[%s]\n", buf);
+ }
+ tcp_write(tcp_fd, buf);
+ }
+ }
+ if (kind == '!') pclose(fp); else fclose(fp);
+ } else {
+ if (opt_echo) {
+ PRINTF("[%s]\n", arg);
+ }
+ tcp_write(tcp_fd, arg);
+ }
+ ptrdel(pbuf);
+}
+
+static void cmd_rawsend(char *arg)
+{
+ char *tmp = skipspace(arg);
+
+ if (*tmp=='(') {
+ ptr pbuf = (ptr)0;
+ arg = tmp + 1;
+ (void)evalp(&pbuf, &arg);
+ if (REAL_ERROR) {
+ print_error(error);
+ ptrdel(pbuf);
+ } else if (pbuf)
+ tcp_raw_write(tcp_fd, ptrdata(pbuf), ptrlen(pbuf));
+ } else {
+ int len;
+ if ((len = memunescape(arg, strlen(arg))))
+ tcp_raw_write(tcp_fd, arg, len);
+ }
+}
+
+static void cmd_rawprint(char *arg)
+{
+ char *tmp = skipspace(arg);
+
+ if (*tmp=='(') {
+ ptr pbuf = (ptr)0;
+ arg = tmp + 1;
+ (void)evalp(&pbuf, &arg);
+ if (REAL_ERROR) {
+ print_error(error);
+ ptrdel(pbuf);
+ } else if (pbuf)
+ tty_raw_write(ptrdata(pbuf), ptrlen(pbuf));
+ } else {
+ int len;
+ if ((len = memunescape(arg, strlen(arg))))
+ tty_raw_write(arg, len);
+ }
+}
+
+
+static void cmd_write(char *arg)
+{
+ ptr p1 = (ptr)0, p2 = (ptr)0;
+ char *tmp = skipspace(arg), kind;
+ FILE *fp;
+
+ kind = *tmp;
+ if (kind == '!' || kind == '>')
+ arg = ++tmp;
+ else
+ kind = 0;
+
+ if (*tmp=='(') {
+ arg = tmp + 1;
+ (void)evalp(&p1, &arg);
+ if (REAL_ERROR)
+ goto write_cleanup;
+
+ if (*arg == CMDSEP) {
+ arg++;
+ (void)evalp(&p2, &arg);
+ if (!REAL_ERROR && !p2)
+ error = NO_STRING_ERROR;
+ if (REAL_ERROR)
+ goto write_cleanup;
+ } else {
+ PRINTF("#write: ");
+ error=SYNTAX_ERROR;
+ goto write_cleanup;
+ }
+ if (*arg != ')') {
+ PRINTF("#write: ");
+ error=MISSING_PAREN_ERROR;
+ goto write_cleanup;
+ }
+ arg = ptrdata(p2);
+
+ fp = (kind == '!') ? popen(arg, "w") : fopen(arg, kind ? "w" : "a");
+ if (!fp) {
+ PRINTF("#write: #error opening \"%s\"\n", arg);
+ error=SYNTAX_ERROR;
+ goto write_cleanup2;
+ }
+ fprintf(fp, "%s\n", p1 ? ptrdata(p1) : (char *)"");
+ fflush(fp);
+ if (kind == '!') pclose(fp); else fclose(fp);
+ } else {
+ PRINTF("#write: ");
+ error=MISMATCH_PAREN_ERROR;
+ }
+
+write_cleanup:
+ if (REAL_ERROR)
+ print_error(error);
+write_cleanup2:
+ ptrdel(p1);
+ ptrdel(p2);
+}
+
+static void cmd_var(char *arg)
+{
+ char *buf, *expr, *tmp, kind, type, right = 0, deleting = 0;
+ varnode **p_named_var = NULL, *named_var = NULL;
+ FILE *fp;
+ long start, end, i = 1;
+ int len, idx;
+ ptr pbuf = (ptr)0;
+
+ arg = skipspace(arg);
+ expr = first_regular(arg, '=');
+
+ if (*expr) {
+ *expr++ = '\0'; /* skip the = */
+ if (!*expr)
+ deleting = 1;
+ else {
+ right = 1;
+ if (*expr == ' ')
+ expr++;
+ }
+ }
+
+ if (*arg == '$')
+ type = TYPE_TXT_VAR;
+ else if (*arg == '@')
+ type = TYPE_NUM_VAR;
+ else if (*arg) {
+ print_error(error=INVALID_NAME_ERROR);
+ return;
+ } else {
+ show_vars();
+ return;
+ }
+
+ kind = *++arg;
+ if (isalpha(kind) || kind == '_') {
+ /* found a named variable */
+ tmp = arg;
+ while (*tmp && (isalnum(*tmp) || *tmp == '_'))
+ tmp++;
+ if (*tmp) {
+ print_error(error=INVALID_NAME_ERROR);
+ return;
+ }
+ kind = type==TYPE_TXT_VAR ? 1 : 0;
+ p_named_var = lookup_varnode(arg, kind);
+ if (!*p_named_var) {
+ /* it doesn't (yet) exist */
+ if (!deleting) {
+ /* so create it */
+ named_var = add_varnode(arg, kind);
+ if (REAL_ERROR)
+ return;
+ if (opt_info) {
+ PRINTF("#new variable: \"%s\"\n", arg - 1);
+ }
+ } else {
+ print_error(error=UNDEFINED_VARIABLE_ERROR);
+ return;
+ }
+ } else
+ /* it exists, hold on */
+ named_var = *p_named_var;
+
+ idx = named_var->index;
+ } else {
+ /* not a named variable, may be
+ * an unnamed variable or an expression */
+ kind = type==TYPE_TXT_VAR ? 1 : 0;
+ tmp = skipspace(arg);
+ if (*tmp == '(') {
+ /* an expression */
+ arg = tmp+1;
+ idx = evalp(&pbuf, &arg);
+ if (!REAL_ERROR && idx != TYPE_TXT)
+ error=NO_STRING_ERROR;
+ if (REAL_ERROR) {
+ PRINTF("#var: ");
+ print_error(error);
+ ptrdel(pbuf);
+ return;
+ }
+ if (pbuf)
+ buf = ptrdata(pbuf);
+ else {
+ print_error(error=INVALID_NAME_ERROR);
+ return;
+ }
+ char err_det;
+ if (isdigit(*buf) || *buf=='-' || *buf=='+') {
+ if (sscanf(buf, "%d%c", &idx, &err_det)==1) {
+ if (idx < -NUMVAR || idx >= NUMPARAM) {
+ print_error(error=OUT_RANGE_ERROR);
+ ptrdel(pbuf);
+ return;
+ }
+ } else {
+ print_error(error=INVALID_NAME_ERROR);
+ return;
+ }
+ } else {
+ if (!isalpha(*buf) && *buf!='_') {
+ print_error(error=INVALID_NAME_ERROR);
+ return;
+ }
+ tmp = buf + 1;
+ while (*tmp && (isalnum(*tmp) || *tmp=='_'))
+ tmp++;
+ if (*tmp) {
+ print_error(error=INVALID_NAME_ERROR);
+ return;
+ }
+ if (!(named_var = *(p_named_var = lookup_varnode(buf, kind)))) {
+ if (!deleting) {
+ named_var = add_varnode(buf, kind);
+ if (REAL_ERROR) {
+ print_error(error);
+ ptrdel(pbuf);
+ return;
+ }
+ if (opt_info) {
+ PRINTF("#new variable: %c%s\n", kind
+ ? '$' : '@', buf);
+ }
+ } else {
+ print_error(error=UNDEFINED_VARIABLE_ERROR);
+ ptrdel(pbuf);
+ return;
+ }
+ }
+ idx = named_var->index;
+ }
+ } else {
+ /* an unnamed var */
+ long buf2;
+
+ idx = evall(&buf2, &arg);
+ if (!REAL_ERROR && idx != TYPE_NUM)
+ error=NO_STRING_ERROR;
+ if (REAL_ERROR) {
+ PRINTF("#var: ");
+ print_error(error);
+ return;
+ }
+ idx = (int)buf2;
+ if (idx < -NUMVAR || idx >= NUMPARAM) {
+ print_error(error=OUT_RANGE_ERROR);
+ return;
+ }
+ /* ok, it's an unnamed var */
+ }
+ }
+
+
+ if (type == TYPE_TXT_VAR && right && !*VAR[idx].str) {
+ /* create it */
+ *VAR[idx].str = ptrnew(PARAMLEN);
+ if (MEM_ERROR) {
+ print_error(error);
+ ptrdel(pbuf);
+ return;
+ }
+ }
+
+ if (deleting) {
+ /* R.I.P. named variables */
+ if (named_var) {
+ if (is_permanent_variable(named_var)) {
+ PRINTF("#cannot delete variable: \"%s\"\n", arg - 1);
+ } else {
+ delete_varnode(p_named_var, kind);
+ if (opt_info) {
+ PRINTF("#deleted variable: \"%s\"\n", arg - 1);
+ }
+ }
+ } else if ((type = TYPE_TXT_VAR)) {
+ /* R.I.P. unnamed variables */
+ if (*VAR[idx].str) {
+ if (idx < 0) {
+ ptrdel(*VAR[idx].str);
+ *VAR[idx].str = 0;
+ } else
+ ptrzero(*VAR[idx].str);
+ }
+ } else
+ *VAR[idx].num = 0;
+ ptrdel(pbuf);
+ return;
+ } else if (!right) {
+ /* no right-hand expression, just show */
+ if (named_var) {
+ if (type == TYPE_TXT_VAR) {
+ pbuf = ptrescape(pbuf, *VAR[idx].str, 0);
+ if (REAL_ERROR) {
+ print_error(error);
+ ptrdel(pbuf);
+ return;
+ }
+ sprintf(inserted_next, "#($%.*s = \"%.*s\")",
+ BUFSIZE - 10, named_var->name,
+ BUFSIZE - (int)strlen(named_var->name) - 10,
+ pbuf ? ptrdata(pbuf) : (char *)"");
+ } else {
+ sprintf(inserted_next, "#(@%.*s = %ld)",
+ BUFSIZE - 8, named_var->name,
+ *VAR[idx].num);
+ }
+ } else {
+ if (type == TYPE_TXT_VAR) {
+ pbuf = ptrescape(pbuf, *VAR[idx].str, 0);
+ sprintf(inserted_next, "#($%d = \"%.*s\")", idx,
+ BUFSIZE - INTLEN - 10,
+ pbuf ? ptrdata(pbuf) : (char *)"");
+ } else {
+ sprintf(inserted_next, "#(@%d = %ld)", idx,
+ *VAR[idx].num);
+ }
+ }
+ ptrdel(pbuf);
+ return;
+ }
+
+ /* only case left: assign a value to a variable */
+ arg = redirect(expr, &pbuf, &kind, "var", 1, &start, &end);
+ if (REAL_ERROR || !arg)
+ return;
+
+ if (kind) {
+ char buf2[BUFSIZE];
+ fp = (kind == '!') ? popen(arg, "r") : fopen(arg, "r");
+ if (!fp) {
+ PRINTF("#var: #error opening \"%s\"\n", arg);
+ error=SYNTAX_ERROR;
+ ptrdel(pbuf);
+ return;
+ }
+ len = 0;
+ i = 1;
+ while (!error && (!start || i<=end) && fgets(buf2+len, BUFSIZE-len, fp))
+ if (!start || i++>=start)
+ len += strlen(buf2 + len);
+
+ if (kind == '!') pclose(fp); else fclose(fp);
+ if (len>PARAMLEN)
+ len = PARAMLEN;
+ buf2[len] = '\0';
+ arg = buf2;
+ }
+
+ if (type == TYPE_NUM_VAR) {
+ arg = skipspace(arg);
+ type = 1;
+ len = 0;
+
+ if (*arg == '-')
+ arg++, type = -1;
+ else if (*arg == '+')
+ arg++;
+
+ if (isdigit(kind=*arg)) while (isdigit(kind)) {
+ len*=10;
+ len+=(kind-'0');
+ kind=*++arg;
+ }
+ else {
+ PRINTF("#var: ");
+ print_error(error=NO_NUM_VALUE_ERROR);
+ }
+ *VAR[idx].num = len * type;
+ }
+ else {
+ *VAR[idx].str = ptrmcpy(*VAR[idx].str, arg, strlen(arg));
+ if (MEM_ERROR)
+ print_error(error);
+ }
+ ptrdel(pbuf);
+}
+
+static void cmd_setvar(char *arg)
+{
+ char *name;
+ int i, func = 0; /* show */
+ long buf;
+
+ name = arg = skipspace(arg);
+ arg = first_regular(arg, '=');
+ if (*arg) {
+ *arg++ = '\0';
+ if (*arg) {
+ func = 1; /* set */
+ if (*arg == '(') {
+ arg++;
+ i = evall(&buf, &arg);
+ if (!REAL_ERROR && i != TYPE_NUM)
+ error=NO_NUM_VALUE_ERROR;
+ else if (!REAL_ERROR && *arg != ')')
+ error=MISSING_PAREN_ERROR;
+ } else
+ buf = strtol(arg, NULL, 0);
+ } else
+ buf = 0;
+
+ if (REAL_ERROR) {
+ PRINTF("#setvar: ");
+ print_error(error);
+ return;
+ }
+ }
+
+ i = strlen(name);
+ if (i && !strncmp(name, "timer", i)) {
+ vtime t;
+ update_now();
+ if (func == 0)
+ sprintf(inserted_next, "#setvar timer=%ld",
+ diff_vtime(&now, &ref_time));
+ else {
+ t.tv_usec = ((-buf) % mSEC_PER_SEC) * uSEC_PER_mSEC;
+ t.tv_sec = (-buf) / mSEC_PER_SEC;
+ ref_time.tv_usec = now.tv_usec;
+ ref_time.tv_sec = now.tv_sec;
+ add_vtime(&ref_time, &t);
+ }
+ }
+ else if (i && !strncmp(name, "lines", i)) {
+ if (func == 0)
+ sprintf(inserted_next, "#setvar lines=%d", lines);
+ else {
+ if (buf > 0)
+ lines = (int)buf;
+ if (opt_info) {
+ PRINTF("#setvar: lines=%d\n", lines);
+ }
+ }
+ }
+ else if (i && !strncmp(name, "mem", i)) {
+ if (func == 0)
+ sprintf(inserted_next, "#setvar mem=%d", limit_mem);
+ else {
+ if (buf == 0 || buf >= PARAMLEN)
+ limit_mem = buf <= INT_MAX ? (int)buf : INT_MAX;
+ if (opt_info) {
+ PRINTF("#setvar: mem=%d%s\n", limit_mem,
+ limit_mem ? "" : " (unlimited)");
+ }
+ }
+ }
+ else if (i && !strncmp(name, "buffer", i)) {
+ if (func == 0)
+ sprintf(inserted_next, "#setvar buffer=%d", log_getsize());
+ else
+ log_resize(buf);
+ } else {
+ update_now();
+ PRINTF("#setvar buffer=%d\n#setvar lines=%d\n#setvar mem=%d\n#setvar timer=%ld\n",
+ log_getsize(), lines, limit_mem, diff_vtime(&now, &ref_time));
+ }
+}
+
+static void cmd_if(char *arg)
+{
+ long buf;
+ int type;
+
+ arg = skipspace(arg);
+ if (*arg!='(') {
+ PRINTF("#if: ");
+ print_error(error=MISMATCH_PAREN_ERROR);
+ return;
+ }
+ arg++; /* skip the '(' */
+
+ type = evall(&buf, &arg);
+ if (!REAL_ERROR) {
+ if (type!=TYPE_NUM)
+ error=NO_NUM_VALUE_ERROR;
+ if (*arg != ')')
+ error=MISSING_PAREN_ERROR;
+ else { /* skip the ')' */
+ if (*++arg == ' ')
+ arg++;
+ }
+ }
+ if (REAL_ERROR) {
+ PRINTF("#if: ");
+ print_error(error);
+ return;
+ }
+
+ if (buf)
+ (void)parse_instruction(arg, 0, 0, 1);
+ else {
+ arg = get_next_instr(arg);
+ if (!strncmp(arg = skipspace(arg), "#else ", 6))
+ (void)parse_instruction(arg + 6, 0, 0, 1);
+ }
+}
+
+static void cmd_for(char *arg)
+{
+ int type = TYPE_NUM, loop=MAX_LOOP;
+ long buf;
+ char *check, *tmp, *increm = 0;
+
+ arg = skipspace(arg);
+ if (*arg != '(') {
+ PRINTF("#for: ");
+ print_error(error=MISMATCH_PAREN_ERROR);
+ return;
+ }
+ push_params();
+ if (REAL_ERROR)
+ return;
+
+ arg = skipspace(arg + 1); /* skip the '(' */
+ if (*arg != CMDSEP)
+ (void)evaln(&arg); /* execute <init> */
+
+ check = arg + 1;
+
+ if (REAL_ERROR)
+ ;
+ else if (*arg != CMDSEP) {
+ PRINTF("#for: ");
+ print_error(error=MISSING_SEPARATOR_ERROR);
+ }
+ else while (!error && loop
+ && (increm=check, (type = evall(&buf, &increm)) == TYPE_NUM
+ && !error && *increm == CMDSEP && buf)) {
+
+ tmp = first_regular(increm + 1, ')');
+ if (*tmp)
+ (void)parse_instruction(tmp + 1, 1, 1, 1);
+ else {
+ PRINTF("#for: ");
+ print_error(error=MISSING_PAREN_ERROR);
+ }
+
+ if (!error) {
+ tmp = increm + 1;
+ if (*tmp != ')')
+ (void)evaln(&tmp);
+ }
+
+ loop--;
+ }
+ if (REAL_ERROR)
+ ;
+ else if (increm && *increm != CMDSEP)
+ error=MISSING_SEPARATOR_ERROR;
+ else if (!loop)
+ error=MAX_LOOP_ERROR;
+ else if (type != TYPE_NUM)
+ error=NO_NUM_VALUE_ERROR;
+ if (REAL_ERROR) {
+ PRINTF("#for: ");
+ print_error(error);
+ }
+ if (error!=DYN_STACK_UND_ERROR && error!=DYN_STACK_OV_ERROR)
+ pop_params();
+}
+
+static void cmd_while(char *arg)
+{
+ int type = TYPE_NUM, loop=MAX_LOOP;
+ long buf;
+ char *check, *tmp;
+
+ arg = skipspace(arg);
+ if (!*arg) {
+ PRINTF("#while: ");
+ print_error(error=MISMATCH_PAREN_ERROR);
+ return;
+ }
+ push_params();
+
+ check = ++arg; /* skip the '(' */
+ while (!error && loop
+ && (arg=check, (type = evall(&buf, &arg)) == TYPE_NUM &&
+ !error && *arg == ')' && buf)) {
+
+ if (*(tmp = arg + 1) == ' ') /* skip the ')' */
+ tmp++;
+ if (*tmp)
+ (void)parse_instruction(tmp, 1, 1, 1);
+ loop--;
+ }
+ if (REAL_ERROR)
+ ;
+ else if (*arg != ')')
+ error=MISSING_PAREN_ERROR;
+ else if (!loop)
+ error=MAX_LOOP_ERROR;
+ else if (type != TYPE_NUM)
+ error=NO_NUM_VALUE_ERROR;
+ if (REAL_ERROR) {
+ PRINTF("#while: ");
+ print_error(error);
+ }
+ if (error!=DYN_STACK_UND_ERROR && error!=DYN_STACK_OV_ERROR)
+ pop_params();
+}
+
+static void cmd_capture(char *arg)
+{
+ arg = skipspace(arg);
+
+ if (!*arg) {
+ if (capturefile) {
+ log_flush();
+ fclose(capturefile);
+ capturefile = NULL;
+ if (opt_info) {
+ PRINTF("#end of capture to file.\n");
+ }
+ } else {
+ PRINTF("#capture to what file?\n");
+ }
+ } else {
+ if (capturefile) {
+ PRINTF("#capture already active.\n");
+ } else {
+ short append = 0;
+ /* Append to log file, if the name starts with '>' */
+ if (*arg == '>') {
+ arg++;
+ append = 1;
+ }
+ if ((capturefile = fopen(arg, (append) ? "a" : "w")) == NULL) {
+ PRINTF("#error writing file \"%s\"\n", arg);
+ } else if (opt_info) {
+ PRINTF("#capture to \"%s\" active, \"#capture\" ends.\n", arg);
+ }
+ }
+ }
+}
+
+static void cmd_movie(char *arg)
+{
+ arg = skipspace(arg);
+
+ if (!*arg) {
+ if (moviefile) {
+ log_flush();
+ fclose(moviefile);
+ moviefile = NULL;
+ if (opt_info) {
+ PRINTF("#end of movie to file.\n");
+ }
+ } else {
+ PRINTF("#movie to what file?\n");
+ }
+ } else {
+ if (moviefile) {
+ PRINTF("#movie already active.\n");
+ } else {
+ if ((moviefile = fopen(arg, "w")) == NULL) {
+ PRINTF("#error writing file \"%s\"\n", arg);
+ } else {
+ if (opt_info) {
+ PRINTF("#movie to \"%s\" active, \"#movie\" ends.\n", arg);
+ }
+ update_now();
+ movie_last = now;
+ log_clearsleep();
+ }
+ }
+ }
+}
+
+static void cmd_record(char *arg)
+{
+ arg = skipspace(arg);
+
+ if (!*arg) {
+ if (recordfile) {
+ fclose(recordfile);
+ recordfile = NULL;
+ if (opt_info) {
+ PRINTF("#end of record to file.\n");
+ }
+ } else {
+ PRINTF("#record to what file?\n");
+ }
+ } else {
+ if (recordfile) {
+ PRINTF("#record already active.\n");
+ } else {
+ if ((recordfile = fopen(arg, "w")) == NULL) {
+ PRINTF("#error writing file \"%s\"\n", arg);
+ } else if (opt_info) {
+ PRINTF("#record to \"%s\" active, \"#record\" ends.\n", arg);
+ }
+ }
+ }
+}
+
+static void cmd_edit(char *arg)
+{
+ editsess *sp;
+
+ if (edit_sess) {
+ for (sp = edit_sess; sp; sp = sp->next) {
+ PRINTF("# %s (%u)\n", sp->descr, sp->key);
+ }
+ } else {
+ PRINTF("#no active editors.\n");
+ }
+}
+
+static void cmd_cancel(char *arg)
+{
+ editsess *sp;
+
+ if (!edit_sess) {
+ PRINTF("#no editing sessions to cancel.\n");
+ } else {
+ if (*arg) {
+ for (sp = edit_sess; sp; sp = sp->next)
+ if (strtoul(arg, NULL, 10) == sp->key) {
+ cancel_edit(sp);
+ break;
+ }
+ if (!sp) {
+ PRINTF("#unknown editing session %d\n", atoi(arg));
+ }
+ } else {
+ if (edit_sess->next) {
+ PRINTF("#several editing sessions active, use #cancel <number>\n");
+ } else
+ cancel_edit(edit_sess);
+ }
+ }
+}
+
+static void cmd_net(char *arg)
+{
+ PRINTF("#received from host: %ld chars, sent to host: %ld chars.\n",
+ received, sent);
+}
+
+
+#ifndef CLOCKS_PER_SEC
+# define CLOCKS_PER_SEC uSEC_PER_SEC
+#endif
+/* hope it works.... */
+
+static void cmd_cpu(char *arg)
+{
+ float f, l;
+ update_now();
+ f = (float)((cpu_clock = clock()) - start_clock) / (float)CLOCKS_PER_SEC;
+ l = (float)(diff_vtime(&now, &start_time)) / (float)mSEC_PER_SEC;
+ PRINTF("#CPU time used: %.3f sec. (%.2f%%)\n", f,
+ (l > 0 && l > f) ? f * 100.0 / l : 100.0);
+}
+
+void show_stat(void)
+{
+ cmd_net(NULL);
+ cmd_cpu(NULL);
+}
+
+#ifdef BUG_TELNET
+static void cmd_color(char *arg)
+{
+ int attrcode;
+
+ arg = skipspace(arg);
+ if (!*arg) {
+ strcpy(tty_modenorm, tty_modenormbackup);
+ tty_puts(tty_modenorm);
+ if (opt_info) {
+ PRINTF("#standard color cleared.\n");
+ }
+ return;
+ }
+
+ attrcode = parse_attributes(arg);
+ if (attrcode == -1) {
+ PRINTF("#invalid attribute syntax.\n");
+ if (opt_info)
+ show_attr_syntax();
+ } else {
+ int bg = BACKGROUND(attrcode), fg = FOREGROUND(attrcode);
+ if (fg >= COLORS || bg >= COLORS) {
+ PRINTF("#please specify foreground and background colors.\n");
+ } else {
+ sprintf(tty_modenorm, "\033[;%c%d;%s%dm",
+ fg<LOWCOLORS ? '3' : '9', fg % LOWCOLORS,
+ bg<LOWCOLORS ? "4" :"10", bg % LOWCOLORS);
+ tty_puts(tty_modenorm);
+ if (opt_info) {
+ PRINTF("#standard colour set.\n");
+ }
+ }
+ }
+}
+#endif
+
+static void cmd_connect(char *arg)
+{
+#ifdef TERM
+ PRINTF("#connect: multiple connections not supported in term version.\n");
+#else
+ char *s1, *s2, *s3 = NULL, *s4 = NULL;
+ int argc = 1;
+
+ if (!*skipspace(arg)) {
+ tcp_show();
+ return;
+ }
+ else {
+ s1 = strtok(arg, " ");
+ s2 = strtok(NULL, " ");
+ if (s2 && *s2) {
+ argc++;
+ s3 = strtok(NULL, " ");
+ if (s3 && *s3) {
+ argc++;
+ s4 = strtok(NULL, " ");
+ if (s4 && *s4)
+ argc++;
+ }
+ }
+ }
+
+ if (argc <= 2) {
+ if (*hostname)
+ tcp_open(s1, s2, hostname, portnumber);
+ else {
+ PRINTF("#connect: no host defined!\n#syntax: #connect session-id [[init-str] [hostname] [port]]\n");
+ }
+ } else if (argc == 3) {
+ if (!*hostname) {
+ my_strncpy(hostname, s2, BUFSIZE-1);
+ portnumber = atoi(s3);
+ }
+ tcp_open(s1, NULL, s2, atoi(s3));
+ } else {
+ if (!*hostname) {
+ my_strncpy(hostname, s3, BUFSIZE-1);
+ portnumber = atoi(s4);
+ }
+ tcp_open(s1, s2, s3, atoi(s4));
+ }
+#endif /* TERM */
+}
+
+
+static void cmd_spawn(char *arg)
+{
+ char s[BUFSIZE];
+ if (*(arg = skipspace(arg))) {
+ arg = split_first_word(s, BUFSIZE, arg);
+ if (*arg && *s) {
+ tcp_spawn(s, arg);
+ return;
+ }
+ }
+ PRINTF("#syntax: #spawn connect-id command\n");
+}
+
+/* If you have speedwalk off but still want to use a speedwalk sequence,
+ * you can manually trigger a speedwalk this way */
+static void cmd_speedwalk(char *arg)
+{
+ char save_speedwalk = opt_speedwalk;
+ PRINTF( "Executing speedwalk '%s'\n", arg );
+ opt_speedwalk = 1;
+ if( ! map_walk( skipspace(arg), 0, 0 ) ) {
+ PRINTF( "Error executing speedwalk\n" );
+ }
+ opt_speedwalk = save_speedwalk;
+}
+
+static void cmd_zap(char *arg)
+{
+ if (!*arg) {
+ PRINTF("#zap: no connection name.\n");
+ } else
+ tcp_close(arg);
+}
+
+static void cmd_qui(char *arg)
+{
+ PRINTF("#you have to write '#quit' - no less, to quit!\n");
+}
+
+static void cmd_quit(char *arg)
+{
+ if (*arg) { /* no skipspace() here! */
+ PRINTF("#quit: spurious argument?\n");
+ } else
+ exit_powwow();
+}
+
+static const struct {
+ const char *name;
+ char *option;
+ const char *doc;
+} options[] = {
+ { "autoclear", &opt_autoclear,
+ "clear input line before executing commands" },
+ { "autoprint", &opt_autoprint,
+ "#print lines matched by actions" },
+ { "compact", &opt_compact,
+ "remove prompt when receiving new messages from mud" },
+ { "debug", &opt_debug,
+ "print commands before executing" },
+ { "echo", &opt_echo,
+ "print command action commands when executed" },
+ { "exit", &opt_exit,
+ "automatically exit powwow when mud connection closes" },
+ { "history", &opt_history,
+ "also save command history" },
+ { "info", &opt_info,
+ "print information about command effects" },
+ { "keyecho", &opt_keyecho,
+ "print command bound to key when executed" },
+ { "reprint", &opt_reprint,
+ "reprint sent commands when getting new prompt" },
+ { "sendsize", &opt_sendsize,
+ "send terminal size when opening connection" },
+ { "speedwalk", &opt_speedwalk,
+ "enable speed walking (ness3ew...)" },
+ { "words", &opt_words,
+ "also save word history" },
+ { "wrap", &opt_wrap,
+ "enable word wrapping" },
+ { NULL }
+};
+
+/* print all options to 'file', or tty if file is NULL; return -1 on
+ * error, 1 on success */
+int print_all_options(FILE *file)
+{
+ const char *prefix = "#option";
+ int width = (file ? 80 : cols) - 16;
+ int len = 0, i;
+ for (i = 0; options[i].name; ++i) {
+ int res;
+ if (file)
+ res = fprintf(file, "%s %c%s", prefix,
+ *options[i].option ? '+' : '-',
+ options[i].name);
+ else
+ res = tty_printf("%s %c%s", prefix,
+ *options[i].option ? '+' : '-',
+ options[i].name);
+ if (res < 0)
+ return -1;
+ /* don't rely on printf() return value */
+ len += strlen(prefix) + strlen(options[i].name) + 2;
+ if (len >= width) {
+ prefix = "\n#option";
+ len = -1;
+ } else {
+ prefix = "";
+ }
+ }
+ if (file) {
+ fputc('\n', file);
+ } else {
+ tty_putc('\n');
+ status(1);
+ }
+ return 1;
+}
+
+static void cmd_option(char *arg)
+{
+ char buf[BUFSIZE];
+ int count = 0;
+
+ arg = skipspace(arg);
+ if (!*arg) {
+ print_all_options(NULL);
+ return;
+ }
+
+ while ((arg = skipspace(split_first_word(buf, BUFSIZE, arg))), *buf) {
+ enum { MODE_ON, MODE_OFF, MODE_TOGGLE, MODE_REP } mode;
+ char *varp = NULL;
+ char *p = buf;
+ char c = *p;
+ int len = strlen(p);
+ int i;
+
+ switch (c) {
+ case '=': mode = MODE_REP; p++; break;
+ case '+': mode = MODE_ON; p++; break;
+ case '-': mode = MODE_OFF; p++; break;
+ default: mode = MODE_TOGGLE; break;
+ }
+ count++;
+ for (i = 0; options[i].name; i++) {
+ if (strncmp(options[i].name, p, len) == 0) {
+ varp = options[i].option;
+ break;
+ }
+ }
+ if (varp == NULL) {
+ if (strncmp("list", p, len) == 0) {
+ tty_puts("#list of options:\n");
+ for (i = 0; options[i].name; ++i) {
+ tty_printf("#option %c%-12s %s\n",
+ *options[i].option ? '+' : '-',
+ options[i].name,
+ options[i].doc);
+ }
+ } else {
+ tty_puts("#syntax: #option [[+|-|=]<name>] | list\n");
+ }
+ status(1);
+ return;
+ }
+
+ switch (mode) {
+ case MODE_REP:
+ sprintf(inserted_next, "#option %c%s", *varp ? '+' : '-',
+ p);
+ break;
+ case MODE_ON: *varp = 1; break;
+ case MODE_OFF: *varp = 0; break;
+ case MODE_TOGGLE: *varp ^= 1; break;
+ }
+ /*
+ * reset the reprint buffer if changing its status
+ */
+ if (varp == &opt_reprint)
+ reprint_clear();
+
+ /* as above, but always print status if
+ * "#option info" alone was typed */
+ if (mode != MODE_REP && !*arg && count==1 &&
+ (opt_info || (mode == MODE_TOGGLE && varp==&opt_info))) {
+ PRINTF("#option %s is now o%s.\n",
+ options[i].name,
+ *varp ? "n" : "ff");
+ }
+ }
+}
+
+static void cmd_file(char *arg)
+{
+ arg = skipspace(arg);
+ if (*arg == '=') {
+ set_deffile(++arg);
+ if (opt_info) {
+ if (*arg) {
+ PRINTF("#save-file set to \"%s\"\n", deffile);
+ } else {
+ PRINTF("#save-file is now undefined.\n");
+ }
+ }
+ } else if (*deffile) {
+ sprintf(inserted_next, "#file =%.*s", BUFSIZE-8, deffile);
+ } else {
+ PRINTF("#save-file not defined.\n");
+ }
+}
+
+static void cmd_save(char *arg)
+{
+ arg = skipspace(arg);
+ if (*arg) {
+ set_deffile(arg);
+ if (opt_info) {
+ PRINTF("#save-file set to \"%s\"\n", deffile);
+ }
+ } else if (!*deffile) {
+ PRINTF("#save-file not defined.\n");
+ return;
+ }
+
+ if (*deffile && save_settings() > 0 && opt_info) {
+ PRINTF("#settings saved to file.\n");
+ }
+}
+
+static void cmd_load(char *arg)
+{
+ int res;
+
+ arg = skipspace(arg);
+ if (*arg) {
+ set_deffile(arg);
+ if (opt_info) {
+ PRINTF("#save-file set to \"%s\"\n", deffile);
+ }
+ }
+ else if (!*deffile) {
+ PRINTF("#save-file not defined.\n");
+ return;
+ }
+
+ res = read_settings();
+
+ if (res > 0) {
+ /* success */
+ if (opt_info) {
+ PRINTF("#settings loaded from file.\n");
+ }
+ } else if (res < 0) {
+ /* critical error */
+ while (keydefs)
+ delete_keynode(&keydefs);
+ tty_add_initial_binds();
+ tty_add_walk_binds();
+ limit_mem = 1048576;
+ PRINTF("#emergency loaded default settings.\n");
+ }
+}
+
+static char *trivial_eval(ptr *pbuf, char *arg, char *name)
+{
+ char *tmp = skipspace(arg);
+
+ if (!pbuf)
+ return NULL;
+
+ if (*tmp=='(') {
+ arg = tmp + 1;
+ (void)evalp(pbuf, &arg);
+ if (!REAL_ERROR && *arg != ')')
+ error=MISSING_PAREN_ERROR;
+ if (REAL_ERROR) {
+ PRINTF("#%s: ", name);
+ print_error(error);
+ return NULL;
+ }
+ if (*pbuf)
+ arg = ptrdata(*pbuf);
+ else
+ arg = "";
+ }
+ else
+ unescape(arg);
+
+ return arg;
+}
+
+static void do_cmd_add(char *arg, int is_static)
+{
+ ptr pbuf = (ptr)0;
+ char buf[BUFSIZE];
+
+ arg = trivial_eval(&pbuf, arg, "add");
+ if (!REAL_ERROR)
+ while (*arg) {
+ arg = split_first_word(buf, BUFSIZE, arg);
+ if (strlen(buf) >= MIN_WORDLEN)
+ (is_static ? put_static_word : put_word)(buf);
+ }
+ ptrdel(pbuf);
+}
+
+static void cmd_add(char *arg)
+{
+ do_cmd_add(arg, 0);
+}
+
+static void cmd_addstatic(char *arg)
+{
+ do_cmd_add(arg, 1);
+}
+
+static void cmd_put(char *arg)
+{
+ ptr pbuf = (ptr)0;
+ arg = trivial_eval(&pbuf, arg, "put");
+ if (!REAL_ERROR && *arg)
+ put_history(arg);
+ ptrdel(pbuf);
+}
+
+
diff --git a/src/cmd.h b/src/cmd.h
new file mode 100644
index 0000000..74cf53f
--- /dev/null
+++ b/src/cmd.h
@@ -0,0 +1,26 @@
+/* public things from cmd.c */
+
+#ifndef _CMD_H_
+#define _CMD_H_
+
+typedef struct cmdstruct {
+ char *sortname; /* set to NULL if you want to sort by
+ * command name */
+ char *name; /* command name */
+ char *help; /* short help */
+ function_str funct; /* function to call */
+ struct cmdstruct *next;
+} cmdstruct;
+
+extern cmdstruct *commands;
+
+void show_stat(void);
+
+void cmd_add_command( cmdstruct *cmd );
+
+void initialize_cmd(void);
+
+int print_all_options(FILE *file);
+
+#endif /* _CMD_H_ */
+
diff --git a/src/cmd2.c b/src/cmd2.c
new file mode 100644
index 0000000..dbd264a
--- /dev/null
+++ b/src/cmd2.c
@@ -0,0 +1,1676 @@
+/*
+ * cmd2.c -- back-end for the various #commands
+ *
+ * (created: Massimiliano Ghilardi (Cosmos), Aug 14th, 1998)
+ *
+ * Copyright (C) 1998 by Massimiliano Ghilardi
+ *
+ * This program 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 2 of the License, or
+ * (at your option) any later version.
+ *
+ */
+
+
+#include <limits.h>
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <signal.h>
+#include <ctype.h>
+#include <time.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <sys/time.h>
+#include <unistd.h>
+#include <errno.h>
+
+int strcasecmp();
+int select();
+
+#include "defines.h"
+#include "main.h"
+#include "feature/regex.h"
+#include "utils.h"
+#include "beam.h"
+#include "edit.h"
+#include "list.h"
+#include "map.h"
+#include "tcp.h"
+#include "tty.h"
+#include "eval.h"
+
+/* anyone knows if ANSI 6429 talks about more than 8 colors? */
+static char *colornames[] = {
+ "black", "red", "green", "yellow", "blue", "magenta", "cyan", "white",
+ "BLACK", "RED", "GREEN", "YELLOW", "BLUE", "MAGENTA", "CYAN", "WHITE", "none"
+};
+
+/*
+ * show defined aliases
+ */
+void show_aliases(void)
+{
+ aliasnode *p;
+ char buf[BUFSIZE];
+
+ PRINTF("#%s alias%s defined%c\n", sortedaliases ? "the following" : "no",
+ (sortedaliases && !sortedaliases->snext) ? " is" : "es are",
+ sortedaliases ? ':' : '.');
+ reverse_sortedlist((sortednode **)&sortedaliases);
+ for (p = sortedaliases; p; p = p->snext) {
+ escape_specials(buf, p->name);
+ tty_printf("#alias %s%s%s%s=%s\n",
+ p->active ? "" : "(disabled) ",
+ buf, group_delim, p->group == NULL ? "*" : p->group, p->subst);
+ }
+ reverse_sortedlist((sortednode **)&sortedaliases);
+}
+
+/*
+ * check if an alias name contains dangerous things.
+ * return 1 if illegal name (and print reason).
+ * if valid, print a warning for unbalanced () {} and ""
+ */
+static int check_alias(char *name)
+{
+ char *p = name, c;
+ enum { NORM, ESCAPE } state = NORM;
+ int quotes=0, paren=0, braces=0, ok = 1;
+
+ if (!*p) {
+ PRINTF("#illegal alias name: is empty!\n");
+ error = INVALID_NAME_ERROR;
+ return 1;
+ }
+ if (*p == '{') {
+ PRINTF("#illegal beginning '{' in alias name: \"%s\"\n", name);
+ error = INVALID_NAME_ERROR;
+ return 1;
+ }
+ if (strchr(name, ' ')) {
+ PRINTF("#illegal spaces in alias name: \"%s\"\n", name);
+ error = INVALID_NAME_ERROR;
+ return 1;
+ }
+
+ for (; ok && (c = *p); p++) switch (state) {
+ case NORM:
+ if (c == ESC)
+ state = ESCAPE;
+ else if (quotes) {
+ if (c == '\"')
+ quotes = 0;
+ }
+ else if (c == '\"')
+ quotes = 1;
+ else if (c == ')')
+ paren--;
+ else if (c == '(')
+ paren++;
+ else if (c == '}')
+ braces--;
+ else if (c == '{')
+ braces++;
+ else if (c == CMDSEP && !paren && !braces)
+ ok = 0;
+ break;
+ case ESCAPE:
+ if (c == ESC)
+ state = ESCAPE;
+ else /* if (c == ESC2 || c != ESC2) */
+ state = NORM;
+ default:
+ break;
+ }
+
+ if (!ok) {
+ PRINTF("#illegal non-escaped ';' in alias name: \"%s\"\n", name);
+ error = INVALID_NAME_ERROR;
+ return 1;
+ }
+
+ if (quotes || paren || braces) {
+ PRINTF("#warning: unbalanced%s%s%s in alias name \"%s\" may cause problems\n",
+ quotes ? " \"\"" : "", paren ? " ()" : "", braces ? " {}" : "", name);
+ }
+
+ return 0;
+}
+
+
+/*
+ * parse the #alias command
+ */
+void parse_alias(char *str)
+{
+ char *left, *right, *group;
+ aliasnode **np, *p;
+
+ left = str = skipspace(str);
+
+ str = first_valid(str, '=');
+
+ if (*str == '=') {
+ *str = '\0';
+ right = ++str;
+ unescape(left);
+
+ /* break out group name (if present) */
+ group = strstr( left, group_delim );
+ if( group ) {
+ *group = 0;
+ group += strlen( group_delim );
+ }
+
+ if (check_alias(left))
+ return;
+ p = *(np = lookup_alias(left));
+ if (!*str) {
+ /* delete alias */
+ if (p) {
+ if (opt_info) {
+ PRINTF("#deleting alias: %s=%s\n", left, p->subst);
+ }
+ delete_aliasnode(np);
+ } else {
+ PRINTF("#unknown alias, cannot delete: \"%s\"\n", left);
+ }
+ } else {
+ /* add/redefine alias */
+
+ /* direct recursion is supported (alias CAN be defined by itself) */
+ if (p) {
+ free(p->subst);
+ p->subst = my_strdup(right);
+ } else
+ add_aliasnode(left, right);
+
+ /* get alias again to add group (if needed)
+ * don't take the lookup penalty though if not changing groups */
+ if( group != NULL ) {
+ np = lookup_alias(left);
+ if( (*np)->group != NULL )
+ free((*np)->group);
+
+ if ( *group == '\0' || strcmp(group,"*") == 0 )
+ group = NULL;
+
+ (*np)->group = my_strdup(group);
+ }
+
+ if (opt_info) {
+ PRINTF("#%s alias in group '%s': %s=%s\n", p ? "changed" : "new",
+ group == NULL ? "*" : group, left, right);
+ }
+ }
+ } else {
+ /* show alias */
+
+ *str = '\0';
+ unescape(left);
+ if (check_alias(left))
+ return;
+ np = lookup_alias(left);
+ if (*np) {
+ char buf[BUFSIZE];
+ escape_specials(buf, left);
+ snprintf(inserted_next, BUFSIZE, "#alias %s%s%s=%s",
+ buf,
+ group_delim,
+ (*np)->group == NULL ? "*" : (*np)->group,
+ (*np)->subst);
+ } else {
+ PRINTF("#unknown alias, cannot show: \"%s\"\n", left);
+ }
+ }
+}
+
+/*
+ * delete an action node
+ */
+static void delete_action(actionnode **nodep)
+{
+ if (opt_info) {
+ PRINTF("#deleting action: >%c%s %s\n", (*nodep)->active ?
+ '+' : '-', (*nodep)->label, (*nodep)->pattern);
+ }
+ delete_actionnode(nodep);
+}
+
+/*
+ * delete a prompt node
+ */
+static void delete_prompt(actionnode **nodep)
+{
+ if (opt_info) {
+ PRINTF("#deleting prompt: >%c%s %s\n", (*nodep)->active ?
+ '+' : '-', (*nodep)->label, (*nodep)->pattern);
+ }
+ delete_promptnode(nodep);
+}
+
+/*
+ * create new action
+ */
+static void add_new_action(char *label, char *pattern, char *command, int active, int type, void *q)
+{
+ add_actionnode(pattern, command, label, active, type, q);
+ if (opt_info) {
+ PRINTF("#new action: %c%c%s %s=%s\n",
+ action_chars[type],
+ active ? '+' : '-', label,
+ pattern, command);
+ }
+}
+
+/*
+ * create new prompt
+ */
+static void add_new_prompt(char *label, char *pattern, char *command, int active, int type, void *q)
+{
+ add_promptnode(pattern, command, label, active, type, q);
+ if (opt_info) {
+ PRINTF("#new prompt: %c%c%s %s=%s\n",
+ action_chars[type],
+ active ? '+' : '-', label,
+ pattern, command);
+ }
+}
+
+/*
+ * add an action with numbered label
+ */
+static void add_anonymous_action(char *pattern, char *command, int type, void *q)
+{
+ static int last = 0;
+ char label[16];
+ do {
+ sprintf(label, "%d", ++last);
+ } while (*lookup_action(label));
+ add_new_action(label, pattern, command, 1, type, q);
+}
+
+#define ONPROMPT (onprompt ? "prompt" : "action")
+
+/*
+ * change fields of an existing action node
+ * pattern or commands can be NULL if no change
+ */
+static void change_actionorprompt(actionnode *node, char *pattern, char *command, int type, void *q, int onprompt)
+{
+#ifdef USE_REGEXP
+ if (node->type == ACTION_REGEXP && node->regexp) {
+ regfree((regex_t *)(node->regexp));
+ free(node->regexp);
+ }
+ node->regexp = q;
+#endif
+ if (pattern) {
+ free(node->pattern);
+ node->pattern = my_strdup(pattern);
+ node->type = type;
+ }
+ if (command) {
+ free(node->command);
+ node->command = my_strdup(command);
+ }
+
+ if (opt_info) {
+ PRINTF("#changed %s %c%c%s %s=%s\n", ONPROMPT,
+ action_chars[node->type],
+ node->active ? '+' : '-',
+ node->label, node->pattern, node->command);
+ }
+}
+
+/*
+ * show defined actions
+ */
+void show_actions(void)
+{
+ actionnode *p;
+
+ PRINTF("#%s action%s defined%c\n", actions ? "the following" : "no",
+ (actions && !actions->next) ? " is" : "s are", actions ? ':' : '.');
+ for (p = actions; p; p = p->next)
+ tty_printf("#action %c%c%s%s%s %s=%s\n",
+ action_chars[p->type],
+ p->active ? '+' : '-', p->label,
+ group_delim,
+ p->group == NULL ? "*" : p->group,
+ p->pattern,
+ p->command);
+}
+
+/*
+ * show defined prompts
+ */
+void show_prompts(void)
+{
+ promptnode *p;
+
+ PRINTF("#%s prompt%s defined%c\n", prompts ? "the following" : "no",
+ (prompts && !prompts->next) ? " is" : "s are", prompts ? ':' : '.');
+ for (p = prompts; p; p = p->next)
+ tty_printf("#prompt %c%c%s %s=%s\n",
+ action_chars[p->type],
+ p->active ? '+' : '-', p->label,
+ p->pattern, p->command);
+}
+
+/*
+ * parse the #action and #prompt commands
+ * this function is too damn complex because of the hairy syntax. it should be
+ * split up or rewritten as an fsm instead.
+ */
+void parse_action(char *str, int onprompt)
+{
+ char *p, label[BUFSIZE], pattern[BUFSIZE], *command, *group;
+ actionnode **np = NULL;
+ char sign, assign, hastail;
+ char active, type = ACTION_WEAK, kind;
+ void *regexp = 0;
+
+ sign = *(p = skipspace(str));
+ if (!sign) {
+ PRINTF("%s: no arguments given\n", ONPROMPT);
+ return;
+ }
+
+ str = p + 1;
+
+ switch (sign) {
+ case '+':
+ case '-': /* edit */
+ case '<': /* delete */
+ case '=': /* list */
+ assign = sign;
+ break;
+ case '%': /* action_chars[ACTION_REGEXP] */
+ type = ACTION_REGEXP;
+ /* falltrough */
+ case '>': /* action_chars[ACTION_WEAK] */
+
+ /* define/edit */
+ assign = '>';
+ sign = *(p + 1);
+ if (!sign) {
+ PRINTF("#%s: label expected\n", ONPROMPT);
+ return;
+ } else if (sign == '+' || sign == '-')
+ str++;
+ else
+ sign = '+';
+ break;
+ default:
+ assign = 0; /* anonymous action */
+ str = p;
+ break;
+ }
+
+ /* labelled action: */
+ if (assign != 0) {
+ p = first_regular(str, ' ');
+ if ((hastail = *p))
+ *p = '\0';
+
+ /* break out group name (if present) */
+ group = strstr( str, group_delim );
+ if( group ) {
+ *group = 0;
+ group += strlen( group_delim );
+ }
+
+ my_strncpy(label, str, BUFSIZE-1);
+ if (hastail)
+ *p++ = ' '; /* p points to start of pattern, or to \0 */
+
+ if (!*label) {
+ PRINTF("#%s: label expected\n", ONPROMPT);
+ return;
+ }
+
+
+ if (onprompt)
+ np = lookup_prompt(label);
+ else
+ np = lookup_action(label);
+
+ /* '<' : remove action */
+ if (assign == '<') {
+ if (!np || !*np) {
+ PRINTF("#no %s, cannot delete label: \"%s\"\n",
+ ONPROMPT, label);
+ }
+ else {
+ if (onprompt)
+ delete_prompt(np);
+ else
+ delete_action(np);
+ }
+
+ /* '>' : define action */
+ } else if (assign == '>') {
+#ifndef USE_REGEXP
+ if (type == ACTION_REGEXP) {
+ PRINTF("#error: regexp not allowed\n");
+ return;
+ }
+#endif
+
+ if (sign == '+')
+ active = 1;
+ else
+ active = 0;
+
+ if (!*label) {
+ PRINTF("#%s: label expected\n", ONPROMPT);
+ return;
+ }
+
+ p = skipspace(p);
+ if (*p == '(') {
+ ptr pbuf = (ptr)0;
+ p++;
+ kind = evalp(&pbuf, &p);
+ if (!REAL_ERROR && kind != TYPE_TXT)
+ error=NO_STRING_ERROR;
+ if (REAL_ERROR) {
+ PRINTF("#%s: ", ONPROMPT);
+ print_error(error=NO_STRING_ERROR);
+ ptrdel(pbuf);
+ return;
+ }
+ if (pbuf) {
+ my_strncpy(pattern, ptrdata(pbuf), BUFSIZE-1);
+ ptrdel(pbuf);
+ } else
+ pattern[0] = '\0';
+ if (*p)
+ p = skipspace(++p);
+ if ((hastail = *p == '='))
+ p++;
+ }
+ else {
+ p = first_regular(command = p, '=');
+ if ((hastail = *p))
+ *p = '\0';
+ my_strncpy(pattern, command, BUFSIZE-1);
+
+ if (hastail)
+ *p++ = '=';
+ }
+
+ if (!*pattern) {
+ PRINTF("#error: pattern of #%ss must be non-empty.\n", ONPROMPT);
+ return;
+ }
+
+#ifdef USE_REGEXP
+ if (type == ACTION_REGEXP && hastail) {
+ int errcode;
+ char unesc_pat[BUFSIZE];
+
+ /*
+ * HACK WARNING:
+ * we unescape regexp patterns now, instead of doing
+ * jit+unescape at runtime, as for weak actions.
+ */
+ strcpy(unesc_pat, pattern);
+ unescape(unesc_pat);
+
+ regexp = malloc(sizeof(regex_t));
+ if (!regexp) {
+ errmsg("malloc");
+ return;
+ }
+
+ if ((errcode = regcomp((regex_t *)regexp, unesc_pat, REG_EXTENDED))) {
+ int n;
+ char *tmp;
+ n = regerror(errcode, (regex_t *)regexp, NULL, 0);
+ tmp = (char *)malloc(n);
+ if (tmp) {
+ if (!regerror(errcode, (regex_t *)regexp, tmp, n))
+ errmsg("regerror");
+ else {
+ PRINTF("#regexp error: %s\n", tmp);
+ }
+ free(tmp);
+ } else {
+ error = NO_MEM_ERROR;
+ errmsg("malloc");
+ }
+ regfree((regex_t *)regexp);
+ free(regexp);
+ return;
+ }
+ }
+#endif
+ command = p;
+
+ if (hastail) {
+ if (np && *np) {
+ change_actionorprompt(*np, pattern, command, type, regexp, onprompt);
+ (*np)->active = active;
+ } else {
+ if (onprompt)
+ add_new_prompt(label, pattern, command, active,
+ type, regexp);
+ else
+ add_new_action(label, pattern, command, active,
+ type, regexp);
+ }
+
+ if( group != NULL ) {
+ /* I don't know why but we need to clip this because somehow
+ * the original string is restored to *p at some point instead
+ * of the null-clipped one we used waaaay at the top. */
+ p = first_regular(group, ' ');
+ *p = '\0';
+ np = lookup_action(label);
+ if( (*np)->group != NULL )
+ free( (*np)->group );
+
+ if ( *group == '\0' || strcmp(group,"*") == 0 )
+ group = NULL;
+
+ (*np) -> group = my_strdup( group );
+ }
+ }
+
+ /* '=': list action */
+ } else if (assign == '='){
+ if (np && *np) {
+ int len = (int)strlen((*np)->label);
+ sprintf(inserted_next, "#%s %c%c%.*s %.*s=%.*s", ONPROMPT,
+ action_chars[(*np)->type], (*np)->active ? '+' : '-',
+ BUFSIZE - 6 /*strlen(ONPROMPT)*/ - 7, (*np)->label,
+ BUFSIZE - 6 /*strlen(ONPROMPT)*/ - 7 - len, (*np)->pattern,
+ BUFSIZE - 6 /*strlen(ONPROMPT)*/ - 7 - len - (int)strlen((*np)->pattern),
+ (*np)->command);
+ } else {
+ PRINTF("#no %s, cannot list label: \"%s\"\n", ONPROMPT, label);
+ }
+
+ /* '+', '-': turn action on/off */
+ } else {
+ if (np && *np) {
+ (*np)->active = (sign == '+');
+ if (opt_info) {
+ PRINTF("#%s %c%s %s is now o%s.\n", ONPROMPT,
+ action_chars[(*np)->type],
+ label,
+ (*np)->pattern, (sign == '+') ? "n" : "ff");
+ }
+ } else {
+ PRINTF("#no %s, cannot turn o%s label: \"%s\"\n", ONPROMPT,
+ (sign == '+') ? "n" : "ff", label);
+ }
+ }
+
+ /* anonymous action, cannot be regexp */
+ } else {
+
+ if (onprompt) {
+ PRINTF("#anonymous prompts not supported.\n#please use labelled prompts.\n");
+ return;
+ }
+
+ command = first_regular(str, '=');
+
+ if (*command == '=') {
+ *command = '\0';
+
+ my_strncpy(pattern, str, BUFSIZE-1);
+ *command++ = '=';
+ np = lookup_action_pattern(pattern);
+ if (*command)
+ if (np && *np)
+ change_actionorprompt(*np, NULL, command, ACTION_WEAK, NULL, 0);
+ else
+ add_anonymous_action(pattern, command, ACTION_WEAK, NULL);
+ else if (np && *np)
+ delete_action(np);
+ else {
+ PRINTF("#no action, cannot delete pattern: \"%s\"\n",
+ pattern);
+ return;
+ }
+ } else {
+ np = lookup_action_pattern(str);
+ if (np && *np) {
+ sprintf(inserted_next, "#action %.*s=%.*s",
+ BUFSIZE - 10, (*np)->pattern,
+ BUFSIZE - (int)strlen((*np)->pattern) - 10,
+ (*np)->command);
+ } else {
+ PRINTF("#no action, cannot show pattern: \"%s\"\n", str);
+ }
+ }
+ }
+}
+
+#undef ONPROMPT
+
+/*
+ * display attribute syntax
+ */
+void show_attr_syntax(void)
+{
+ int i;
+ PRINTF("#attribute syntax:\n\tOne or more of:\tbold, blink, underline, inverse\n\tand/or\t[foreground] [ON background]\n\tColors: ");
+ for (i = 0; i < COLORS; i++)
+ tty_printf("%s%s", colornames[i],
+ (i == LOWCOLORS - 1 || i == COLORS - 1) ? "\n\t\t" : ",");
+ tty_printf("%s\n", colornames[i]);
+}
+
+/*
+ * put escape sequences to turn on/off an attribute in given buffers
+ */
+void attr_string(int attrcode, char *begin, char *end)
+{
+ int fg = FOREGROUND(attrcode), bg = BACKGROUND(attrcode),
+ tok = ATTR(attrcode);
+ char need_end = 0;
+ *begin = *end = '\0';
+
+ if (tok > (ATTR_BOLD | ATTR_BLINK | ATTR_UNDERLINE | ATTR_INVERSE)
+ || fg > COLORS || bg > COLORS || attrcode == NOATTRCODE)
+ return; /* do nothing */
+
+ if (fg < COLORS) {
+ if (bg < COLORS) {
+ sprintf(begin, "\033[%c%d;%s%dm",
+ fg<LOWCOLORS ? '3' : '9', fg % LOWCOLORS,
+ bg<LOWCOLORS ? "4" :"10", bg % LOWCOLORS);
+#ifdef TERM_LINUX
+ strcpy(end, "\033[39;49m");
+#endif
+ } else {
+ sprintf(begin, "\033[%c%dm",
+ fg<LOWCOLORS ? '3' : '9', fg % LOWCOLORS);
+#ifdef TERM_LINUX
+ strcpy(end, "\033[39m");
+#endif
+ }
+ } else if (bg < COLORS) {
+ sprintf(begin, "\033[%s%dm",
+ bg<LOWCOLORS ? "4" : "10", bg % LOWCOLORS);
+#ifdef TERM_LINUX
+ strcpy(end, "\033[49m");
+#endif
+ }
+
+#ifndef TERM_LINUX
+ if (fg < COLORS || bg < COLORS)
+ need_end = 1;
+#endif
+
+ if (tok & ATTR_BOLD) {
+ if (tty_modebold[0]) {
+ strcat(begin, tty_modebold);
+#ifdef TERM_LINUX
+ strcat(end, "\033[21m");
+#else
+ need_end = 1;
+#endif
+ } else {
+ strcat(begin, tty_modestandon);
+ strcpy(end, tty_modestandoff);
+ }
+ }
+
+ if (tok & ATTR_BLINK) {
+ if (tty_modeblink[0]) {
+ strcat(begin, tty_modeblink);
+#ifdef TERM_LINUX
+ strcat(end, "\033[25m");
+#else
+ need_end = 1;
+#endif
+ } else {
+ strcat(begin, tty_modestandon);
+ strcpy(end, tty_modestandoff);
+ }
+ }
+
+ if (tok & ATTR_UNDERLINE) {
+ if (tty_modeuline[0]) {
+ strcat(begin, tty_modeuline);
+#ifdef TERM_LINUX
+ strcat(end, "\033[24m");
+#else
+ need_end = 1;
+#endif
+ } else {
+ strcat(begin, tty_modestandon);
+ strcpy(end, tty_modestandoff);
+ }
+ }
+
+ if (tok & ATTR_INVERSE) {
+ if (tty_modeinv[0]) {
+ strcat(begin, tty_modeinv);
+#ifdef TERM_LINUX
+ strcat(end, "\033[27m");
+#else
+ need_end = 1;
+#endif
+ } else {
+ strcat(begin, tty_modestandon);
+ strcpy(end, tty_modestandoff);
+ }
+ }
+
+#ifndef TERM_LINUX
+ if (need_end)
+ strcpy(end, tty_modenorm);
+#endif
+}
+
+/*
+ * parse attribute description in line.
+ * Return attribute if successful, -1 otherwise.
+ */
+int parse_attributes(char *line)
+{
+ char *p;
+ int tok = 0, fg, bg, t = -1;
+
+ if (!(p = strtok(line, " ")))
+ return NOATTRCODE;
+
+ fg = bg = NO_COLOR;
+
+ while (t && p) {
+ if (!strcasecmp(p, "bold"))
+ t = ATTR_BOLD;
+ else if (!strcasecmp(p, "blink"))
+ t = ATTR_BLINK;
+ else if (!strcasecmp(p, "underline"))
+ t = ATTR_UNDERLINE;
+ else if (!strcasecmp(p, "inverse") || !strcasecmp(p, "reverse"))
+ t = ATTR_INVERSE;
+ else
+ t = 0;
+
+ if (t) {
+ tok |= t;
+ p = strtok(NULL, " ");
+ }
+ }
+
+ if (!p)
+ return ATTRCODE(tok, fg, bg);
+
+ for (t = 0; t <= COLORS && strcmp(p, colornames[t]); t++)
+ ;
+ if (t <= COLORS) {
+ fg = t;
+ p = strtok(NULL, " ");
+ }
+
+ if (!p)
+ return ATTRCODE(tok, fg, bg);
+
+ if (strcasecmp(p, "on"))
+ return -1; /* invalid attribute */
+
+ if (!(p = strtok(NULL, " ")))
+ return -1;
+
+ for (t = 0; t <= COLORS && strcmp(p, colornames[t]); t++)
+ ;
+ if (t <= COLORS)
+ bg = t;
+ else
+ return -1;
+
+ return ATTRCODE(tok, fg, bg);
+}
+
+
+/*
+ * return a static pointer to name of given attribute code
+ */
+char *attr_name(int attrcode)
+{
+ static char name[BUFSIZE];
+ int fg = FOREGROUND(attrcode), bg = BACKGROUND(attrcode), tok = ATTR(attrcode);
+
+ name[0] = 0;
+ if (tok > (ATTR_BOLD | ATTR_BLINK | ATTR_UNDERLINE | ATTR_INVERSE) || fg > COLORS || bg > COLORS)
+ return name; /* error! */
+
+ if (tok & ATTR_BOLD)
+ strcat(name, "bold ");
+ if (tok & ATTR_BLINK)
+ strcat(name, "blink ");
+ if (tok & ATTR_UNDERLINE)
+ strcat(name, "underline ");
+ if (tok & ATTR_INVERSE)
+ strcat(name, "inverse ");
+
+ if (fg < COLORS || (fg == bg && fg == COLORS && !tok))
+ strcat(name, colornames[fg]);
+
+ if (bg < COLORS) {
+ strcat(name, " on ");
+ strcat(name, colornames[bg]);
+ }
+
+ if (!*name)
+ strcpy(name, "none");
+
+ return name;
+}
+
+/*
+ * show defined marks
+ */
+void show_marks(void)
+{
+ marknode *p;
+ PRINTF("#%s marker%s defined%c\n", markers ? "the following" : "no",
+ (markers && !markers->next) ? " is" : "s are",
+ markers ? ':' : '.');
+ for (p = markers; p; p = p->next)
+ tty_printf("#mark %s%s=%s\n", p->mbeg ? "^" : "",
+ p->pattern, attr_name(p->attrcode));
+}
+
+
+/*
+ * parse arguments to the #mark command
+ */
+void parse_mark(char *str)
+{
+ char *p;
+ marknode **np, *n;
+ char mbeg = 0;
+
+ if (*str == '=') {
+ PRINTF("#marker must be non-null.\n");
+ return;
+ }
+ p = first_regular(str, '=');
+ if (!*p) {
+ if (*str == '^')
+ mbeg = 1, str++;
+ unescape(str);
+ np = lookup_marker(str, mbeg);
+ if ((n = *np)) {
+ ptr pbuf = (ptr)0;
+ char *name;
+ pbuf = ptrmescape(pbuf, n->pattern, strlen(n->pattern), 0);
+ if (MEM_ERROR) { ptrdel(pbuf); return; }
+ name = attr_name(n->attrcode);
+ sprintf(inserted_next, "#mark %s%.*s=%.*s", n->mbeg ? "^" : "",
+ BUFSIZE-(int)strlen(name)-9, pbuf ? ptrdata(pbuf) : "",
+ BUFSIZE-9, name);
+ ptrdel(pbuf);
+ } else {
+ PRINTF("#unknown marker, cannot show: \"%s\"\n", str);
+ }
+
+ } else {
+ int attrcode, wild = 0;
+ char pattern[BUFSIZE], *p2;
+
+ *(p++) = '\0';
+ p = skipspace(p);
+ if (*str == '^')
+ mbeg = 1, str++;
+ my_strncpy(pattern, str, BUFSIZE-1);
+ unescape(pattern);
+ p2 = pattern;
+ while (*p2) {
+ if (ISMARKWILDCARD(*p2)) {
+ wild = 1;
+ if (ISMARKWILDCARD(*(p2 + 1))) {
+ error=SYNTAX_ERROR;
+ PRINTF("#error: two wildcards (& or $) may not be next to eachother\n");
+ return;
+ }
+ }
+ p2++;
+ }
+
+ np = lookup_marker(pattern, mbeg);
+ attrcode = parse_attributes(p);
+ if (attrcode == -1) {
+ PRINTF("#invalid attribute syntax.\n");
+ error=SYNTAX_ERROR;
+ if (opt_info) show_attr_syntax();
+ } else if (!*p)
+ if ((n = *np)) {
+ if (opt_info) {
+ PRINTF("#deleting mark: %s%s=%s\n", n->mbeg ? "^" : "",
+ n->pattern, attr_name(n->attrcode));
+ }
+ delete_marknode(np);
+ } else {
+ PRINTF("#unknown marker, cannot delete: \"%s%s\"\n",
+ mbeg ? "^" : "", pattern);
+ }
+ else {
+ if (*np) {
+ (*np)->attrcode = attrcode;
+ if (opt_info) {
+ PRINTF("#changed");
+ }
+ } else {
+ add_marknode(pattern, attrcode, mbeg, wild);
+ if (opt_info) {
+ PRINTF("#new");
+ }
+ }
+ if (opt_info)
+ tty_printf(" mark: %s%s=%s\n", mbeg ? "^" : "",
+ pattern, attr_name(attrcode));
+ }
+ }
+}
+
+/*
+ * turn ASCII description of a sequence
+ * into raw escape sequence
+ * return pointer to end of ASCII description
+ */
+static char *unescape_seq(char *buf, char *seq, int *seqlen)
+{
+ char c, *start = buf;
+ enum { NORM, ESCSINGLE, ESCAPE, CARET, DONE } state = NORM;
+
+ for (; (c = *seq); seq++) {
+ switch (state) {
+ case NORM:
+ if (c == '^')
+ state = CARET;
+ else if (c == ESC)
+ state = ESCSINGLE;
+ else if (c == '=')
+ state = DONE;
+ else
+ *(buf++) = c;
+ break;
+ case CARET:
+ /*
+ * handle ^@ ^A ... ^_ as expected:
+ * ^@ == 0x00, ^A == 0x01, ... , ^_ == 0x1f
+ */
+ if (c > 0x40 && c < 0x60)
+ *(buf++) = c & 0x1f;
+ /* special case: ^? == 0x7f */
+ else if (c == '?')
+ *(buf++) = 0x7f;
+ state = NORM;
+ break;
+ case ESCSINGLE:
+ case ESCAPE:
+ /*
+ * GH: \012 ==> octal number
+ */
+ if (state == ESCSINGLE &&
+ ISODIGIT(seq[0]) && ISODIGIT(seq[1]) && ISODIGIT(seq[2])) {
+ *(buf++) =(((seq[0] - '0') << 6) |
+ ((seq[1] - '0') << 3) |
+ (seq[2] - '0'));
+ seq += 2;
+ } else {
+ *(buf++) = c;
+ if (c == ESC)
+ state = ESCAPE;
+ else
+ state = NORM;
+ }
+ break;
+ default:
+ break;
+ }
+ if (state == DONE)
+ break;
+ }
+ *buf = '\0';
+ *seqlen = buf - start;
+ return seq;
+}
+
+/*
+ * read a single character from tty, with timeout in milliseconds.
+ * timeout == 0 means wait indefinitely (no timeout).
+ * return char or -1 if timeout was reached.
+ */
+static int get_one_char(int timeout)
+{
+ struct timeval timeoutbuf;
+ fd_set fds;
+ int n;
+ char c;
+
+ again:
+ FD_ZERO(&fds);
+ FD_SET(tty_read_fd, &fds);
+ timeoutbuf.tv_sec = 0;
+ timeoutbuf.tv_usec = timeout * uSEC_PER_mSEC;
+ n = select(tty_read_fd + 1, &fds, NULL, NULL,
+ timeout ? &timeoutbuf : NULL);
+ if (n == -1 && errno == EINTR)
+ return -1;
+ if (n == -1) {
+ errmsg("select");
+ return -1;
+ }
+ do {
+ n = tty_read(&c, 1);
+ } while (n < 0 && errno == EINTR);
+ if (n == 1)
+ return (unsigned char)c;
+ if (n < 0) {
+ if (errno != EAGAIN)
+ errmsg("read from tty");
+ return -1;
+ }
+ /* n == 0 */
+ if (timeout == 0)
+ goto again;
+ return -1;
+}
+
+/*
+ * print an escape sequence in human-readably form.
+ */
+void print_seq(char *seq, int len)
+{
+ while (len--) {
+ unsigned char ch = *(seq++);
+ if (ch == '\033') {
+ tty_puts("esc ");
+ continue;
+ }
+ if (ch < ' ') {
+ tty_putc('^');
+ ch |= '@';
+ }
+ if (ch == ' ')
+ tty_puts("space ");
+ else if (ch == 0x7f)
+ tty_puts("del ");
+ else if (ch & 0x80)
+ tty_printf("\\%03o ", ch);
+ else
+ tty_printf("%c ", ch);
+ }
+}
+
+/*
+ * return a static pointer to escape sequence made printable, for use in
+ * definition-files
+ */
+char *seq_name(char *seq, int len)
+{
+ static char buf[CAPLEN*4];
+ char *p = buf;
+ /*
+ * rules: control chars are written as ^X, where
+ * X is char | 64
+ *
+ * GH: codes > 0x80 ==> octal \012
+ *
+ * special case: 0x7f is written ^?
+ */
+ while (len--) {
+ unsigned char c = *seq++;
+ if (c == '^' || (c && strchr(SPECIAL_CHARS, c)))
+ *(p++) = ESC;
+
+ if (c < ' ') {
+ *(p++) = '^';
+ *(p++) = c | '@';
+ } else if (c == 0x7f) {
+ *(p++) = '^';
+ *(p++) = '?';
+ } else if (c & 0x80) {
+ /* GH: save chars with high bit set in octal */
+ sprintf(p, "\\%03o", (int)c);
+ p += strlen(p);
+ } else
+ *(p++) = c;
+ }
+ *p = '\0';
+ return buf;
+}
+
+/*
+ * read a single escape sequence from the keyboard
+ * prompting user for it; return static pointer
+ */
+char *read_seq(char *name, int *len)
+{
+ static char seq[CAPLEN];
+ int i = 1, tmp;
+
+ PRINTF("#please press the key \"%s\" : ", name);
+ tty_flush();
+
+ if ((tmp = get_one_char(0)) >= 0)
+ seq[0] = tmp;
+ else {
+ tty_puts("#unable to get key. Giving up.\n");
+ return NULL;
+ }
+
+ while (i < CAPLEN - 1 &&
+ (tmp = get_one_char(KBD_TIMEOUT)) >= 0)
+ seq[i++] = tmp;
+ *len = i;
+ print_seq(seq, i);
+
+ tty_putc('\n');
+ if (seq[0] >= ' ' && seq[0] <= '~') {
+ PRINTF("#that is not a redefinable key.\n");
+ return NULL;
+ }
+ return seq;
+}
+
+/*
+ * show full definition of one binding,
+ * with custom message
+ */
+static void show_single_bind(char *msg, keynode *p)
+{
+ if (p->funct == key_run_command) {
+ PRINTF("#%s %s %s=%s\n", msg, p->name,
+ seq_name(p->sequence, p->seqlen), p->call_data);
+ } else {
+ PRINTF("#%s %s %s=%s%s%s\n", msg, p->name,
+ seq_name(p->sequence, p->seqlen),
+ internal_functions[lookup_edit_function(p->funct)].name,
+ p->call_data ? " " : "",
+ p->call_data ? p->call_data : "");
+ }
+}
+
+/*
+ * list keyboard bindings
+ */
+void show_binds(char edit)
+{
+ keynode *p;
+ int count = 0;
+ for (p = keydefs; p; p = p->next) {
+ if (edit != (p->funct == key_run_command)) {
+ if (!count) {
+ if (edit) {
+ PRINTF("#line-editing keys:\n");
+ } else {
+ PRINTF("#user-defined keys:\n");
+ }
+ }
+ show_single_bind("bind", p);
+ ++count;
+ }
+ }
+ if (!count) {
+ PRINTF("#no key bindings defined right now.\n");
+ }
+}
+
+
+/*
+ * interactively create a new keybinding
+ */
+static void define_new_key(char *name, char *command)
+{
+ char *seq, *arg;
+ keynode *p;
+ int seqlen, function;
+
+ seq = read_seq(name, &seqlen);
+ if (!seq) return;
+
+ for (p = keydefs; p; p = p->next)
+ /* GH: don't allow binding of supersets of another bind */
+ if (!memcmp(p->sequence, seq, MIN2(p->seqlen, seqlen))) {
+ show_single_bind("key already bound as:", p);
+ return;
+ }
+
+ function = lookup_edit_name(command, &arg);
+ if (function)
+ add_keynode(name, seq, seqlen,
+ internal_functions[function].funct, arg);
+ else
+ add_keynode(name, seq, seqlen, key_run_command, command);
+
+ if (opt_info) {
+ PRINTF("#new key binding: %s %s=%s\n",
+ name, seq_name(seq, seqlen), command);
+ }
+}
+
+/*
+ * parse the #bind command non-interactively.
+ */
+static void parse_bind_noninteractive(char *arg)
+{
+ char rawseq[CAPLEN], *p, *seq, *params;
+ int function, seqlen;
+ keynode **kp;
+
+ p = strchr(arg, ' ');
+ if (!p) {
+ PRINTF("#syntax error: \"#bind %s\"\n", arg);
+ return;
+ }
+ *(p++) = '\0';
+ seq = p = skipspace(p);
+
+ p = unescape_seq(rawseq, p, &seqlen);
+ if (!p[0] || !p[1]) {
+ PRINTF("#syntax error: \"#bind %s %s\"\n", arg, seq);
+ return;
+ }
+ *p++ = '\0';
+
+ kp = lookup_key(arg);
+ if (kp && *kp)
+ delete_keynode(kp);
+
+ if ((function = lookup_edit_name(p, &params)))
+ add_keynode(arg, rawseq, seqlen,
+ internal_functions[function].funct, params);
+ else
+ add_keynode(arg, rawseq, seqlen, key_run_command, p);
+
+ if (opt_info) {
+ PRINTF("#%s: %s %s=%s\n",
+ (kp && *kp) ? "redefined key" : "new key binding",
+ arg, seq, p);
+ }
+}
+
+/*
+ * parse the argument of the #bind command (interactive)
+ */
+void parse_bind(char *arg)
+{
+ char *p, *q, *command, *params;
+ char *name = arg;
+ keynode **npp, *np;
+ int function;
+
+ p = first_valid(arg, '=');
+ q = first_valid(arg, ' ');
+ q = skipspace(q);
+
+ if (*p && *q && p > q) {
+ parse_bind_noninteractive(arg);
+ return;
+ }
+
+ if (*p) {
+ *(p++) = '\0';
+ np = *(npp = lookup_key(name));
+ if (*p) {
+ command = p;
+ if (np) {
+ if (np->funct == key_run_command)
+ free(np->call_data);
+ if ((function = lookup_edit_name(command, &params))) {
+ np->call_data = my_strdup(params);
+ np->funct = internal_functions[function].funct;
+ } else {
+ np->call_data = my_strdup(command);
+ np->funct = key_run_command;
+ }
+ if (opt_info) {
+ PRINTF("#redefined key: %s %s=%s\n", name,
+ seq_name(np->sequence, np->seqlen),
+ command);
+ }
+ } else
+ define_new_key(name, command);
+ } else {
+ if (np) {
+ if (opt_info)
+ show_single_bind("deleting key binding:", np);
+ delete_keynode(npp);
+ } else {
+ PRINTF("#no such key: \"%s\"\n", name);
+ }
+ }
+ } else {
+ np = *(npp = lookup_key(name));
+ if (np) {
+ char *seqname;
+ int seqlen;
+ seqname = seq_name(np->sequence, np->seqlen);
+ seqlen = strlen(seqname);
+
+ if (np->funct == key_run_command)
+ sprintf(inserted_next, "#bind %.*s %s=%.*s",
+ BUFSIZE-seqlen-9, name, seqname,
+ BUFSIZE-seqlen-(int)strlen(name)-9,
+ np->call_data);
+ else {
+ p = internal_functions[lookup_edit_function(np->funct)].name;
+ sprintf(inserted_next, "#bind %.*s %s=%s%s%.*s",
+ BUFSIZE-seqlen-10, name, seqname, p,
+ np->call_data ? " " : "",
+ BUFSIZE-seqlen-(int)strlen(name)-(int)strlen(p)-10,
+ np->call_data ? np->call_data : "");
+ }
+ } else {
+ PRINTF("#no such key: \"%s\"\n", name);
+ }
+ }
+}
+
+void parse_rebind(char *arg)
+{
+ char rawseq[CAPLEN], *seq, **old;
+ keynode **kp, *p;
+ int seqlen;
+
+ arg = skipspace(arg);
+ if (!*arg) {
+ PRINTF("#rebind: missing key.\n");
+ return;
+ }
+
+ seq = first_valid(arg, ' ');
+ if (*seq) {
+ *seq++ = '\0';
+ seq = skipspace(seq);
+ }
+
+ kp = lookup_key(arg);
+ if (!kp || !*kp) {
+ PRINTF("#no such key: \"%s\"\n", arg);
+ return;
+ }
+
+ if (!*seq) {
+ seq = read_seq(arg, &seqlen);
+ if (!seq)
+ return;
+ } else {
+ (void)unescape_seq(rawseq, seq, &seqlen);
+ seq = rawseq;
+ }
+
+ for (p = keydefs; p; p = p->next) {
+ if (p == *kp)
+ continue;
+ if (!memcmp(p->sequence, seq, MIN2(seqlen, p->seqlen))) {
+ show_single_bind("key already bound as:", p);
+ return;
+ }
+ }
+
+ old = &((*kp)->sequence);
+ if (*old)
+ free(*old);
+ *old = (char *)malloc((*kp)->seqlen = seqlen);
+ memmove(*old, seq, seqlen);
+
+ if (opt_info)
+ show_single_bind("redefined key:", *kp);
+}
+
+/*
+ * evaluate an expression, or unescape a text.
+ * set value of start and end line if <(expression...) or !(expression...)
+ * if needed, use/malloc "pbuf" as buffer (on error, also free pbuf)
+ * return resulting char *
+ */
+char *redirect(char *arg, ptr *pbuf, char *kind, char *name, int also_num, long *start, long *end)
+{
+ char *tmp = skipspace(arg), k;
+ int type, i;
+
+ if (!pbuf) {
+ print_error(error=INTERNAL_ERROR);
+ return NULL;
+ }
+
+ k = *tmp;
+ if (k == '!' || k == '<')
+ arg = ++tmp;
+ else
+ k = 0;
+
+ *start = *end = 0;
+
+ if (*tmp=='(') {
+
+ arg = tmp + 1;
+ type = evalp(pbuf, &arg);
+ if (!REAL_ERROR && type!=TYPE_TXT && !also_num)
+ error=NO_STRING_ERROR;
+ if (REAL_ERROR) {
+ PRINTF("#%s: ", name);
+ print_error(error);
+ ptrdel(*pbuf);
+ return NULL;
+ }
+ for (i=0; i<2; i++) if (*arg == CMDSEP) {
+ long buf;
+
+ arg++;
+ if (!i && *arg == CMDSEP) {
+ *start = 1;
+ continue;
+ }
+ else if (i && *arg == ')') {
+ *end = LONG_MAX;
+ continue;
+ }
+
+ type = evall(&buf, &arg);
+ if (!REAL_ERROR && type != TYPE_NUM)
+ error=NO_NUM_VALUE_ERROR;
+ if (REAL_ERROR) {
+ PRINTF("#%s: ", name);
+ print_error(error);
+ ptrdel(*pbuf);
+ return NULL;
+ }
+ if (i)
+ *end = buf;
+ else
+ *start = buf;
+ }
+ if (*arg != ')') {
+ PRINTF("#%s: ", name);
+ print_error(error=MISSING_PAREN_ERROR);
+ ptrdel(*pbuf);
+ return NULL;
+ }
+ if (!*pbuf) {
+ /* make space to add a final \n */
+ *pbuf = ptrsetlen(*pbuf, 1);
+ ptrzero(*pbuf);
+ if (REAL_ERROR) {
+ print_error(error);
+ ptrdel(*pbuf);
+ return NULL;
+ }
+ }
+ arg = ptrdata(*pbuf);
+ if (!*start && *end)
+ *start = 1;
+ } else
+ unescape(arg);
+
+ *kind = k;
+ return arg;
+}
+
+void show_vars(void)
+{
+ varnode *v;
+ int i, type;
+ ptr p = (ptr)0;
+
+ PRINTF("#the following variables are defined:\n");
+
+ for (type = 0; !REAL_ERROR && type < 2; type++) {
+ reverse_sortedlist((sortednode **)&sortednamed_vars[type]);
+ v = sortednamed_vars[type];
+ while (v) {
+ if (type == 0) {
+ tty_printf("#(@%s = %ld)\n", v->name, v->num);
+ } else {
+ p = ptrescape(p, v->str, 0);
+ if (REAL_ERROR) {
+ print_error(error);
+ break;
+ }
+ tty_printf("#($%s = \"%s\")\n", v->name,
+ p ? ptrdata(p) : "");
+ }
+ v = v->snext;
+ }
+ reverse_sortedlist((sortednode **)&sortednamed_vars[type]);
+ }
+ for (i = -NUMVAR; !REAL_ERROR && i < NUMPARAM; i++) {
+ if (*VAR[i].num)
+ tty_printf("#(@%d = %ld)\n", i, *VAR[i].num);
+ }
+ for (i = -NUMVAR; !REAL_ERROR && i < NUMPARAM; i++) {
+ if (*VAR[i].str && ptrlen(*VAR[i].str)) {
+ p = ptrescape(p, *VAR[i].str, 0);
+ if (p && ptrlen(p))
+ tty_printf("#($%d = \"%s\")\n", i, ptrdata(p));
+ }
+ }
+ ptrdel(p);
+}
+
+void show_delaynode(delaynode *p, int in_or_at)
+{
+ long d;
+ struct tm *s;
+ char buf[BUFSIZE];
+
+ update_now();
+ d = diff_vtime(&p->when, &now);
+ s = localtime((time_t *)&p->when.tv_sec);
+ /* s now points to a calendar struct */
+ if (in_or_at) {
+
+ if (in_or_at == 2) {
+ /* write time in buf */
+ (void)strftime(buf, BUFSIZE - 1, "%H%M%S", s);
+ sprintf(inserted_next, "#at %.*s (%s) %.*s",
+ BUFSIZE - 15, p->name, buf,
+ BUFSIZE - 15 - (int)strlen(p->name), p->command);
+ }
+ else
+ sprintf(inserted_next, "#in %.*s (%ld) %.*s",
+ BUFSIZE - LONGLEN - 9, p->name, d,
+ BUFSIZE - LONGLEN - 9 - (int)strlen(p->name), p->command);
+ } else {
+ (void)strftime(buf, BUFSIZE - 1, "%H:%M:%S", s);
+ PRINTF("#at (%s) #in (%ld) \"%s\" %s\n", buf, d, p->name, p->command);
+ }
+}
+
+void show_delays(void)
+{
+ delaynode *p;
+ int n = (delays ? delays->next ? 2 : 1 : 0) +
+ (dead_delays ? dead_delays->next ? 2 : 1 : 0);
+
+ PRINTF("#%s delay label%s defined%c\n", n ? "the following" : "no",
+ n == 1 ? " is" : "s are", n ? ':' : '.');
+ for (p = delays; p; p = p->next)
+ show_delaynode(p, 0);
+ for (p = dead_delays; p; p = p->next)
+ show_delaynode(p, 0);
+}
+
+void change_delaynode(delaynode **p, char *command, long millisec)
+{
+ delaynode *m=*p;
+
+ *p = m->next;
+ m->when.tv_usec = (millisec % mSEC_PER_SEC) * uSEC_PER_mSEC;
+ m->when.tv_sec = millisec / mSEC_PER_SEC;
+ update_now();
+ add_vtime(&m->when, &now);
+ if (*command) {
+ if (strlen(command) > strlen(m->command)) {
+ free((void*)m->command);
+ m->command = my_strdup(command);
+ }
+ else
+ strcpy(m->command, command);
+ }
+ if (millisec < 0)
+ add_node((defnode*)m, (defnode**)&dead_delays, rev_time_sort);
+ else
+ add_node((defnode*)m, (defnode**)&delays, time_sort);
+ if (opt_info) {
+ PRINTF("#changed ");
+ show_delaynode(m, 0);
+ }
+}
+
+void new_delaynode(char *name, char *command, long millisec)
+{
+ vtime t;
+ delaynode *node;
+
+ t.tv_usec = (millisec % mSEC_PER_SEC) * uSEC_PER_mSEC;
+ t.tv_sec = millisec / mSEC_PER_SEC;
+ update_now();
+ add_vtime(&t, &now);
+ node = add_delaynode(name, command, &t, millisec < 0);
+ if (opt_info && node) {
+ PRINTF("#new ");
+ show_delaynode(node, 0);
+ }
+}
+
+void show_history(int count)
+{
+ int i = curline;
+
+ if (!count) count = lines - 1;
+ if (count >= MAX_HIST) count = MAX_HIST - 1;
+ i -= count;
+ if (i < 0) i += MAX_HIST;
+
+ while (count) {
+ if (hist[i]) {
+ PRINTF("#%2d: %s\n", count, hist[i]);
+ }
+ count--;
+ if (++i == MAX_HIST) i = 0;
+ }
+}
+
+void exe_history(int count)
+{
+ int i = curline;
+ char buf[BUFSIZE];
+
+ if (count >= MAX_HIST)
+ count = MAX_HIST - 1;
+ i -= count;
+ if (i < 0)
+ i += MAX_HIST;
+ if (hist[i]) {
+ strcpy(buf, hist[i]);
+ parse_user_input(buf, 0);
+ }
+}
+
diff --git a/src/cmd2.h b/src/cmd2.h
new file mode 100644
index 0000000..96cb067
--- /dev/null
+++ b/src/cmd2.h
@@ -0,0 +1,32 @@
+/* public things from cmd2.c */
+
+void show_aliases(void);
+void parse_alias(char *str);
+
+void show_actions(void);
+void show_prompts(void);
+void parse_action(char *str, int onprompt);
+
+void show_attr_syntax(void);
+void attr_string(int attrcode, char *begin, char *end);
+int parse_attributes(char *line);
+char *attr_name(int attrcode);
+void show_marks(void);
+void parse_mark(char *str);
+
+char *seq_name(char *seq, int len);
+void show_binds(char edit);
+void parse_bind(char *arg);
+void parse_rebind(char *arg);
+
+char *redirect(char *arg, ptr *pbuf, char *kind, char *name, int also_num, long *start, long *end);
+
+void show_vars(void);
+void show_delaynode(delaynode *p, int in_or_at);
+void show_delays(void);
+void change_delaynode(delaynode **p, char *command, long millisec);
+void new_delaynode(char *name, char *command, long millisec);
+
+void show_history(int count);
+void exe_history(int count);
+
diff --git a/src/defines.h b/src/defines.h
new file mode 100644
index 0000000..33a96ad
--- /dev/null
+++ b/src/defines.h
@@ -0,0 +1,307 @@
+/*
+ * common definition and typedefs
+ */
+
+#ifndef _DEFINES_H_
+#define _DEFINES_H_
+
+#if !defined(SYS_TIME_H) && !defined(_H_SYS_TIME)
+# include <sys/time.h>
+#endif
+
+#ifdef AIX
+# include <sys/select.h>
+#endif
+
+#define memzero(a,b) memset((a), 0, (b))
+
+#ifdef USE_RANDOM
+# define get_random random
+# define init_random srandom
+#else
+# define get_random lrand48
+# define init_random srand48
+#endif
+
+#define uSEC_PER_SEC ((long)1000000) /* microseconds in a second */
+#define mSEC_PER_SEC ((long)1000) /* milliseconds in a second */
+#define uSEC_PER_mSEC ((long)1000) /* microseconds in a millisecond */
+
+#undef MIN2
+#undef MAX2
+#undef ABS
+#undef SIGN
+#undef SWAP2
+#define MIN2(a,b) ((a)<(b) ? (a) : (b))
+#define MAX2(a,b) ((a)>(b) ? (a) : (b))
+#define ABS(a) ((a)> 0 ? (a) :(-a))
+#define SIGN(a) ((a)> 0 ? 1 : (a) ? -1 : 0)
+#define SWAP2(a,b,c) ((c)=(b), (b)=(a), (a)=(c))
+
+/* macros to match parentheses */
+#define ISRPAREN(c) ((c) == ')' || (c) == ']' || (c) == '}')
+#define ISLPAREN(c) ((c) == '(' || (c) == '[' || (c) == '{')
+#define LPAREN(c) ((c) == ')' ? '(' : ((c) == ']' ? '[' : '{'))
+
+#define ISODIGIT(c) ((c) >= '0' && (c) <= '7')
+
+#define PRINTF status(1), tty_printf
+
+#define INTLEN (3*(1+(int)sizeof(int)))
+ /* max length of a string representation
+ * of an int */
+#define LONGLEN (3*(1+(int)sizeof(long)))
+ /* max length of a string representation
+ * of a long */
+#define ESC '\\' /* special escape char */
+#define STRESC "\\"
+#define ESC2 '`' /* other special escape char */
+#define STRESC2 "`"
+#define CMDSEP ';' /* command separator character */
+#define SPECIAL_CHARS "{}();\"=" /* specials chars needing escape */
+#define MPI "~$#E" /* MUME protocol introducer */
+#define MPILEN 4 /* strlen(MPI) */
+
+#ifdef NR_OPEN
+# define MAX_FDSCAN NR_OPEN
+#else
+# define MAX_FDSCAN 256 /* max number of fds */
+#endif
+
+#define MAX_CONNECTS 32 /* max number of open connections. must fit in a byte */
+
+#define CAPLEN 20 /* max length of a terminal capability */
+#define BUFSIZE 4096 /* general buffer size */
+#define SOCKBUFSIZE BUFSIZE /* socket buffer size for read */
+#define PARAMLEN 99 /* initial length of text strings */
+#define MAX_MAPLEN 1000 /* maximum length of automapped path */
+#define MIN_WORDLEN 3 /* the minimum length for history words */
+#define MAX_WORDS 512 /* number of words kept for TAB-completion */
+#define MAX_HIST 128 /* number of history lines kept */
+#define LOG_MAX_HASH 7
+#define MAX_HASH (1<<LOG_MAX_HASH) /* max hash value, must be a power of 2 */
+#define NUMPARAM 10 /* number of local unnamed params allowed
+ * (hardcoded, don't change) */
+#define NUMVAR 50 /* number of global unnamed variables */
+#define NUMTOT (NUMVAR+NUMPARAM)
+#define MAX_SUBOPT 256 /* max length of suboption string */
+#define MAX_ARGS 16 /* max number of arguments to editor */
+#define FLASHDELAY 500 /* time of parentheses flash in millisecs */
+#define KBD_TIMEOUT 100 /* timeout for keyboard read in millisecs;
+ * hope it's enough also for very slow lines */
+
+#define MAX_STACK 100 /* maximum number of nested
+ * action, alias, #for or #while */
+#define MAX_LOOP 10000 /* maximum number of iterations in
+ * #for or #while */
+
+#define ACTION_WEAK 0 /* GH: normal junk */
+#define ACTION_REGEXP 1 /* oh-so-mighty regexp */
+#define ACTION_TYPES (ACTION_REGEXP + 1)
+
+/* GH: the redefinable delimeters */
+#define DELIM (delim_list[delim_mode])
+#define DELIM_LEN (delim_len[delim_mode])
+#define IS_DELIM(c) (strchr(DELIM, (c)))
+
+#define DELIM_NORMAL 0 /* GH: normal word delimeters */
+#define DELIM_PROGRAM 1 /* ()[]{}.,;"'+/-*% */
+#define DELIM_CUSTOM 2 /* user-defined */
+#define DELIM_MODES (DELIM_CUSTOM + 1)
+
+
+/* macros to find cursor position from input buffer position */
+#define CURLINE(pos) (((pos) + col0) / cols_1 + line0)
+#define CURCOL(pos) (((pos) + col0) % cols_1)
+
+#define CLIP(a, min, max) ((a)=(a)<(min) ? (min) : (a)>(max) ? (max) : (a))
+
+#define ISMARKWILDCARD(c) ((c) == '&' || (c) == '$')
+
+/*
+ * Attribute codes: bit 0-4 for foreground color, 5-9 for
+ * background color, 10-13 for effects.
+ * Color #16 is "none", so 0x0210 is "no attribute".
+ */
+#define COLORS 16 /* number of colors on HFT terminal */
+#define LOWCOLORS 8 /* number of ANSI colors */
+#define NO_COLOR COLORS /* no color change, use default */
+#define BITS_COLOR 5 /* bits used for a color entry
+ (16==none is a valid color) */
+#define BITS_2COLOR 10 /* bits used for 2 color entries */
+#define COLOR_MASK 0x1F /* 5 (BITS_COLOR) bits set to 1, others 0 */
+
+#define ATTR_BOLD 0x01
+#define ATTR_BLINK 0x02
+#define ATTR_UNDERLINE 0x04
+#define ATTR_INVERSE 0x08
+
+/*
+ * WARNING: colors and attributes are currently using 14 bits:
+ * 4 for attributes, 5 for foreground color and 5 for background.
+ * type used is int and -1 is used as 'invalid attribute'
+ * so in case ints are 16 bits, there is only 1 bit left unused.
+ * In case ints are 32 bits, no problem.
+ */
+
+/* constructors / accessors for attribute codes */
+#define ATTRCODE(attr, fg, bg) \
+ (((attr) << BITS_2COLOR) | ((bg) << BITS_COLOR) | (fg))
+#define FOREGROUND(attrcode) ((attrcode) & COLOR_MASK)
+#define BACKGROUND(attrcode) (((attrcode) >> BITS_COLOR) & COLOR_MASK)
+#define ATTR(attrcode) ((attrcode) >> BITS_2COLOR)
+
+#define NOATTRCODE ATTRCODE(0, NO_COLOR, NO_COLOR)
+
+/*
+ * NCSA telnet 2.2 doesn't reset the color when it receives "esc [ m",
+ * so we must know what the normal colors are in order to reset it.
+ * These colors can be changed with the #color command.
+ */
+#ifdef BUG_TELNET
+# define DEFAULTFG 7 /* make default white text */
+# define DEFAULTBG 4 /* on blue background */
+#endif
+
+#define LM_NOECHO 1 /* no local echo */
+#define LM_CHAR 2 /* char-by-char mode (no line editing) */
+
+
+
+
+
+typedef unsigned char byte;
+
+typedef void (*function_any) (); /* generic function pointer */
+
+typedef void (*function_int)(int i);
+
+typedef function_int function_signal;
+
+typedef void (*function_str)(char *arg);
+
+typedef struct timeval vtime; /* needs #include <sys/tyme.h> */
+
+#include "ptr.h"
+
+
+
+
+/* generic linked list node (never actually created) */
+typedef struct defnode {
+ struct defnode *next;
+ char *sortfield;
+} defnode;
+
+/*
+ * twin linked list node: used to build pair of parallel lists,
+ * one sorted and one not, with the same nodes
+ */
+typedef struct sortednode {
+ struct sortednode *next;
+ char *sortfield;
+ struct sortednode *snext;
+} sortednode;
+
+/*
+ * linked list nodes: keep "next" first, then string to sort by,
+ * then (eventually) `snext'
+ */
+typedef struct aliasnode {
+ struct aliasnode *next;
+ char *name;
+ struct aliasnode *snext;
+ char *subst;
+ char *group;
+ int active;
+} aliasnode;
+
+typedef struct marknode {
+ struct marknode *next;
+ char *pattern;
+ int attrcode;
+ char *start, *end;
+ char mbeg;
+ char wild;
+} marknode;
+
+typedef struct triggernode {
+ struct triggernode *next;
+ char *command, *label;
+ int active;
+ int type; /* GH: allow regexp */
+ char *pattern;
+#ifdef USE_REGEXP
+ void *regexp; /* 0 if type == ACTION_WEAK */
+#endif
+ char *group;
+} triggernode;
+
+/*
+ * HACK WARNING :
+ * actionnode and promptnode must be the same type
+ * or search_action_or_prompt() in main.c won't work.
+ */
+typedef triggernode actionnode;
+typedef triggernode promptnode;
+
+typedef int (*function_sort)(defnode *node1, defnode *node2);
+
+typedef struct keynode {
+ struct keynode *next;
+ char *name; /* key name */
+ char *sequence; /* escape sequence sent by terminal */
+ int seqlen; /* GH: length of esc seq to allow \0 in seq */
+ function_str funct; /* function called when key pressed */
+ char *call_data; /* data passed to function */
+} keynode;
+
+typedef struct delaynode {
+ struct delaynode *next;
+ char *name;
+ char *command;
+ vtime when; /* structure containing time when */
+ /* command must be executed */
+} delaynode;
+
+/* Variable struct definitions */
+
+typedef struct varnode { /* for named variables */
+ struct varnode *next;
+ char *name;
+ struct varnode *snext;
+ int index;
+ long num;
+ ptr str;
+} varnode;
+
+typedef struct { /* for unnamed vars */
+ long num;
+ ptr str;
+} unnamedvar;
+
+typedef struct { /* stack of local vars */
+ unnamedvar p[MAX_STACK][NUMPARAM];
+ int curr;
+} param_stack;
+
+typedef struct { /* pointers to all variables */
+ long *num;
+ ptr *str;
+} vars;
+
+/* editing session control */
+typedef struct editsess {
+ struct editsess *next;
+ unsigned int key; /* session identifier */
+ int pid; /* pid of child */
+ int fd; /* MUD socket to talk with (-1 if non-MUD text) */
+ char *descr; /* short description of what we are editing */
+ char *file; /* name of temporary file */
+ time_t ctime; /* time when temp file was created (upper bound) */
+ long oldsize; /* original file size */
+ char cancel; /* 1 if cancelled */
+} editsess;
+
+#endif /* _DEFINES_H_ */
+
diff --git a/src/edit.c b/src/edit.c
new file mode 100644
index 0000000..bcfab21
--- /dev/null
+++ b/src/edit.c
@@ -0,0 +1,961 @@
+/*
+ * edit.c -- line editing functions for powwow
+ *
+ * Copyright (C) 1998 by Massimiliano Ghilardi
+ *
+ * This program 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 2 of the License, or
+ * (at your option) any later version.
+ *
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <string.h>
+#include <strings.h>
+#include <ctype.h>
+#include <time.h>
+#include <sys/types.h>
+#include <sys/time.h>
+
+#include "defines.h"
+#include "main.h"
+#include "utils.h"
+#include "cmd.h"
+#include "edit.h"
+#include "tcp.h"
+#include "tty.h"
+#include "eval.h"
+#include "log.h"
+
+static void insert_string(char *arg);
+
+/* history buffer */
+char *hist[MAX_HIST]; /* saved history lines */
+int curline = 0; /* current history line */
+int pickline = 0; /* line to pick history from */
+
+/* word completion list */
+wordnode words[MAX_WORDS];
+int wordindex = 0;
+
+edit_function internal_functions[] = {
+ {(char *)0, (function_str)0, },
+ {"&enter-line", enter_line, },
+ {"&complete-word", complete_word, },
+ {"&complete-line", complete_line, },
+ {"&del-char-left", del_char_left, },
+ {"&del-char-right", del_char_right, },
+ {"&prev-char", prev_char, },
+ {"&prev-line", prev_line, },
+ {"&next-char", next_char, },
+ {"&next-line", next_line, },
+ {"&to-history", to_history, },
+ {"&clear-line", clear_line, },
+ {"&redraw-line", redraw_line, },
+ {"&redraw-line-noprompt", redraw_line_noprompt, },
+ {"&begin-of-line", begin_of_line, },
+ {"&end-of-line", end_of_line, },
+ {"&kill-to-eol", kill_to_eol, },
+ {"&transpose", transpose_chars, },
+ {"&transpose-words", transpose_words, },
+ {"&suspend", (function_str)suspend_powwow, }, /* yep, it's an hack */
+ {"&del-word-left", del_word_left, },
+ {"&del-word-right", del_word_right, },
+ {"&prev-word", prev_word, },
+ {"&upcase-word", upcase_word, },
+ {"&downcase-word", downcase_word, },
+ {"&next-word", next_word, },
+ {"&insert-string", insert_string, },
+ {(char *)0, (function_str)0 }
+};
+
+int lookup_edit_name(char *name, char **arg)
+{
+ int i, len, flen;
+ char *fname, *extra = NULL;
+
+ if ((fname = strchr(name, ' ')))
+ len = fname - name;
+ else
+ len = strlen(name);
+
+ for (i=1; (fname = internal_functions[i].name); i++) {
+ flen = strlen(fname);
+ if (flen == len && !strncmp(name, fname, flen)) {
+ extra = name + flen;
+ if (*extra == ' ') extra++;
+ if (!*extra) extra = NULL;
+ *arg = extra;
+ return i;
+ }
+ }
+ *arg = extra;
+ return 0;
+}
+
+int lookup_edit_function(function_str funct)
+{
+ int i;
+ function_str ffunct;
+
+ for (i = 1; (ffunct = internal_functions[i].funct); i++)
+ if (funct == ffunct)
+ return i;
+
+ return 0;
+}
+
+/* return pointer to any unterminated escape code at the end of s */
+static char *find_partial_esc(char *s)
+{
+ size_t len = strlen(s);
+ char *end = s + len;
+ while (end > s) {
+ char c = *--end;
+ if (c == '\033')
+ return end;
+ if (isalpha(c))
+ return NULL;
+ }
+ return NULL;
+}
+
+/*
+ * redisplay the prompt
+ * assume cursor is at beginning of line
+ */
+void draw_prompt(void)
+{
+ if (promptlen && prompt_status == 1) {
+ char *esc, *pstr;
+ int e = error;
+ error = 0;
+ marked_prompt = ptraddmarks(marked_prompt, prompt->str);
+ if (MEM_ERROR) { promptzero(); errmsg("malloc(prompt)"); return; }
+
+ /* if prompt ends in unterminated escape code, do not print
+ * that part */
+ pstr = ptrdata(marked_prompt);
+ esc = find_partial_esc(pstr);
+ if (esc)
+ *esc = 0;
+ tty_puts(pstr);
+ col0 = printstrlen(pstr);
+ if (esc)
+ *esc = '\033';
+
+ error = e;
+ }
+ prompt_status = 0;
+}
+
+/*
+ * clear current input line (deleteprompt == 1 if to clear also prompt)
+ * cursor is left right after the prompt.
+ *
+ * since we do not expect data from the user at this point,
+ * do not print edattrbeg now.
+ */
+void clear_input_line(int deleteprompt)
+{
+ /*
+ * be careful: if prompt and/or input line have been erased from screen,
+ * pos will be different from the actual cursor position
+ */
+ if ((edlen && line_status == 0) || (promptlen && prompt_status == 0 && deleteprompt)) {
+ int newcol = deleteprompt ? 0 : col0;
+ int realpos = line_status == 0 ? pos : (prompt_status == 0 ? 0 : -col0);
+
+ tty_gotoxy_opt(CURCOL(realpos), CURLINE(realpos), newcol, line0);
+ tty_puts(edattrend);
+ if (line0 < lines - 1)
+ tty_puts(tty_clreoscr);
+ else
+ tty_puts(tty_clreoln);
+ col0 = newcol;
+ } else {
+ tty_puts(edattrend);
+ }
+ if (deleteprompt)
+ status(1);
+ else
+ line_status = 1;
+}
+
+/*
+ * clear input line, but do nothing else
+ */
+void clear_line(char *dummy)
+{
+ if (!edlen)
+ return;
+ clear_input_line(0);
+ pickline = curline;
+ *edbuf = '\0';
+ pos = edlen = 0;
+}
+
+/*
+ * Redraw the input line and put the cursor at the current position.
+ * The cursor is assumed to be directly after the prompt.
+ */
+void draw_input_line(void)
+{
+ int i, oldline0;
+
+ if (line_status == 0 || linemode & LM_NOECHO)
+ return;
+
+ tty_puts(edattrbeg);
+
+ if (edlen) {
+ oldline0 = line0;
+ if (edlen < cols_1 - col0) {
+ tty_puts(edbuf);
+ } else {
+ tty_printf("%.*s", cols_1 - col0, edbuf);
+ for (i = cols_1 - col0; i <= edlen; i += cols_1) {
+#ifdef BUG_ANSI
+ if (edattrbg)
+ tty_printf("%s\n%s%.*s", edattrend, edattrbeg, cols_1, edbuf + i);
+ else
+#endif
+ tty_printf("\n%.*s", cols_1, edbuf + i);
+ }
+ }
+ line0 = lines - (edlen + col0) / cols_1 - 1;
+ if (line0 > oldline0)
+ line0 = oldline0;
+ if ((i = CURLINE(pos)) < 0)
+ line0 -= i;
+ else if (i > lines - 1)
+ line0 -= i - lines + 1;
+ tty_gotoxy_opt(CURCOL(edlen), CURLINE(edlen), CURCOL(pos), CURLINE(pos));
+ }
+ line_status = 0;
+}
+
+/*
+ * redraw the input line
+ */
+void redraw_line(char *dummy)
+{
+ clear_input_line(1);
+}
+
+/*
+ * redraw the input line, clearing the prompt
+ */
+void redraw_line_noprompt(char *dummy)
+{
+ clear_input_line(0);
+ tty_putc('\n');
+ if (line0 < lines - 1)
+ line0++;
+ status(-1);
+}
+
+/*
+ * GH: transpose two words to the left
+ */
+void transpose_words(char *dummy)
+{
+ /* other refers to the word to the left, this is the one we are at */
+
+ int this_so, other_so, this_eo, other_eo;
+ char buf[BUFSIZE];
+ int n;
+
+ if (pos > 2) {
+
+ this_eo = this_so = pos;
+ /* optionally traceback to find a word */
+ while (this_so && strchr(DELIM, edbuf[this_so]))
+ this_so--;
+
+ /* now find where the current word ends */
+ while (this_eo < edlen && !strchr(DELIM, edbuf[this_eo]))
+ this_eo++;
+
+ /* found a word; now find its start */
+ while (this_so > 0 && !strchr(DELIM, edbuf[this_so - 1]))
+ this_so--;
+
+ if (this_so < 2)
+ return; /* impossible that there's another word */
+
+ other_so = this_so - 1;
+ while (other_so >= 0 && strchr(DELIM, edbuf[other_so]))
+ other_so--;
+ if (other_so < 0)
+ return;
+ other_eo = other_so + 1;
+ while (other_so > 0 && !strchr(DELIM, edbuf[other_so - 1]))
+ other_so--;
+
+ sprintf(buf, "%.*s%.*s%.*s",
+ this_eo - this_so, edbuf + this_so,
+ this_so - other_eo, edbuf + other_eo,
+ other_eo - other_so, edbuf + other_so);
+
+ input_moveto(other_so);
+ for (n = 0; buf[n]; input_overtype_follow(buf[n++]))
+ ;
+ }
+}
+
+/*
+ * transpose two characters to the left
+ */
+void transpose_chars(char *dummy)
+{
+ int i, j;
+ char c;
+ if (pos > 1 || (pos > 0 && pos < edlen)) {
+ if (pos < edlen) {
+ j = pos;
+ i = pos - 1;
+ } else {
+ j = pos - 1;
+ i = pos - 2;
+ }
+ c = edbuf[j]; edbuf[j] = edbuf[i]; edbuf[i] = c;
+
+ if (line_status == 0) {
+ tty_gotoxy_opt(CURCOL(pos), CURLINE(pos), CURCOL(i), CURLINE(i));
+ tty_putc(edbuf[i]);
+ tty_gotoxy_opt(CURCOL(i+1), CURLINE(i+1), CURCOL(j), CURLINE(j));
+ tty_putc(edbuf[j]);
+ if (pos < edlen) {
+ pos++;
+ tty_gotoxy_opt(CURCOL(j+1), CURLINE(j+1), CURCOL(pos), CURLINE(pos));
+ }
+ } else
+ pos++;
+ }
+}
+
+/*
+ * erase everything to the end of line
+ */
+void kill_to_eol(char *dummy)
+{
+ if (line_status == 0) {
+ if (edattrbg)
+ tty_printf("%s%s", edattrend, tty_clreoln);
+ else
+ tty_puts(tty_clreoln);
+ if (CURLINE(edlen) > CURLINE(pos)) {
+ tty_printf("\n%s", tty_clreoscr);
+ tty_gotoxy_opt(0, CURLINE(pos) + 1, CURCOL(pos), CURLINE(pos));
+ }
+ if (edattrbg)
+ tty_puts(edattrbeg);
+ }
+ edbuf[edlen = pos] = '\0';
+}
+
+/*
+ * move cursor to end of line
+ */
+void end_of_line(char *dummy)
+{
+ input_moveto(edlen);
+}
+
+/*
+ * move cursor to beginning of line
+ */
+void begin_of_line(char *dummy)
+{
+ input_moveto(0);
+}
+
+/*
+ * delete a character to the right
+ */
+void del_char_right(char *dummy)
+{
+ input_delete_nofollow_chars(1);
+}
+
+/*
+ * delete a character to the left
+ */
+void del_char_left(char *dummy)
+{
+ if (pos) {
+ input_moveto(pos-1);
+ input_delete_nofollow_chars(1);
+ }
+}
+
+/*
+ * move a line into history, but don't do anything else
+ */
+void to_history(char *dummy)
+{
+ if (!edlen)
+ return;
+ clear_input_line(0);
+ put_history(edbuf);
+ pickline = curline;
+ *edbuf = '\0';
+ pos = edlen = 0;
+}
+
+/*
+ * put string in history at current position
+ * (string is assumed to be trashable)
+ */
+void put_history(char *str)
+{
+ char *p;
+ if (hist[curline]) free(hist[curline]);
+ if (!(hist[curline] = my_strdup(str))) {
+ errmsg("malloc");
+ return;
+ }
+
+ if (++curline == MAX_HIST)
+ curline = 0;
+
+ /* split into words and put into completion list */
+ for (p = strtok(str, DELIM); p;
+ p = strtok(NULL, DELIM)) {
+ if (strlen(p) >= MIN_WORDLEN &&
+ p[0] != '#') /* no commands/short words */
+ put_word(p);
+ }
+}
+
+/*
+ * move a node before wordindex, i.e. make it the last word
+ */
+static void demote_word(int i)
+{
+ words[words[i].prev].next = words[i].next;
+ words[words[i].next].prev = words[i].prev;
+ words[i].prev = words[words[i].next = wordindex].prev;
+ words[wordindex].prev = words[words[wordindex].prev].next = i;
+}
+
+static struct {
+ int size, used;
+ char **words;
+} static_words;
+
+static int compl_next_word(int i)
+{
+ if (i < 0) {
+ go_static:
+ --i;
+ if (-i - 1 >= static_words.used)
+ i = wordindex;
+ } else {
+ i = words[i].next;
+ if (i == wordindex || words[i].word == NULL) {
+ i = 0;
+ goto go_static;
+ }
+ }
+ return i;
+}
+
+static char *compl_get_word(int i)
+{
+ return i < 0 ? static_words.words[-i - 1] : words[i].word;
+}
+
+/*
+ * match and complete a word referring to the word list
+ */
+void complete_word(char *dummy)
+{
+ /*
+ * GH: rewritten to allow circulating through history with
+ * repetitive command
+ * code stolen from cancan 2.6.3a
+ * curr_word: index into words[]
+ * comp_len length of current completition
+ * root_len length of the root word (before the completition)
+ * root start of the root word
+ */
+
+ static int curr_word, comp_len = 0, root_len = 0;
+ char *root, *p;
+ int k, n;
+
+ /* find word start */
+ if (last_edit_cmd == (function_any)complete_word && comp_len) {
+ k = comp_len;
+ input_moveto(pos - k);
+ n = pos - root_len;
+ } else {
+ for (n = pos; n > 0 && !IS_DELIM(edbuf[n - 1]); n--)
+ ;
+ k = 0;
+ curr_word = wordindex;
+ root_len = pos - n;
+ }
+ root = edbuf + n; comp_len = 0;
+
+ /* k = chars to delete, n = position of starting word */
+
+ /* scan word list for next match */
+ while ((p = compl_get_word(curr_word = compl_next_word(curr_word)))) {
+ if (!strncasecmp(p, root, root_len) &&
+ *(p += root_len) &&
+ (n = strlen(p)) + edlen < BUFSIZE) {
+ comp_len = n;
+ for (; k && n; k--, n--)
+ input_overtype_follow(*p++);
+ if (n > 0)
+ input_insert_follow_chars(p, n);
+ break;
+ }
+ }
+ if (k > 0)
+ input_delete_nofollow_chars(k);
+
+ /* delete duplicate instances of the word */
+ if (p && curr_word >= 0
+ && !(words[k = curr_word].flags & WORD_UNIQUE)) {
+ words[k].flags |= WORD_UNIQUE;
+ p = words[k].word;
+ n = words[k].next;
+ while (words[k = n].word) {
+ n = words[k].next;
+ if (!strcmp(p, words[k].word)) {
+ demote_word(k);
+ free(words[k].word);
+ words[k].word = 0;
+ words[curr_word].flags |= words[k].flags; /* move retain flag */
+ if ((words[k].flags &= WORD_UNIQUE))
+ break;
+ }
+ }
+ }
+}
+
+/*
+ * match and complete entire lines backwards in history
+ * GH: made repeated complete_line cycle through history
+ */
+void complete_line(char *dummy)
+{
+ static int curr_line = MAX_HIST-1, root_len = 0, first_line = 0;
+ int i;
+
+ if (last_edit_cmd != (function_any)complete_line) {
+ root_len = edlen;
+ first_line = curr_line = curline;
+ }
+
+ for (i = curr_line - 1; i != curr_line; i--) {
+ if (i < 0) i = MAX_HIST - 1;
+ if (i == first_line)
+ break;
+ if (hist[i] && !strncmp(edbuf, hist[i], root_len))
+ break;
+ }
+ if (i != curr_line) {
+ clear_input_line(0);
+ if (i == first_line) {
+ edbuf[root_len] = 0;
+ edlen = root_len;
+ } else {
+ strcpy(edbuf, hist[i]);
+ edlen = strlen(edbuf);
+ }
+ pos = edlen;
+ curr_line = i;
+ }
+}
+
+/*
+ * GH: word history handling stolen from cancan 2.6.3a
+ */
+
+static void default_completions(void)
+{
+ char buf[BUFSIZE];
+ cmdstruct *p;
+ int i;
+ /* TODO: add some way to handle new commands going in the default
+ * completions list */
+ for (i = 0, buf[0] = '#', p = commands; p != NULL; p = p -> next)
+ if (p->funct) {
+ strcpy(buf + 1, p->name);
+ put_static_word(buf);
+ }
+ /* init 'words' double-linked list */
+ for (i = MAX_WORDS; i--; words[i].prev = i - 1, words[i].next = i + 1)
+ ;
+ words[0].prev = MAX_WORDS - 1;
+ words[MAX_WORDS - 1].next = 0;
+}
+
+void put_static_word(char *s)
+{
+ if (static_words.used >= static_words.size) {
+ do {
+ static_words.size = static_words.size ? static_words.size * 2 : 16;
+ } while (static_words.used >= static_words.size);
+ static_words.words = realloc(static_words.words,
+ sizeof static_words.words[0]
+ * static_words.size);
+ }
+
+ if ((s = my_strdup(s)) == NULL) {
+ errmsg("malloc");
+ return;
+ }
+ static_words.words[static_words.used++] = s;
+}
+
+/*
+ * put word in word completion ring
+ */
+void put_word(char *s)
+{
+ int r = wordindex;
+ if (!(words[r].word = my_strdup(s))) {
+ errmsg("malloc");
+ return;
+ }
+ words[r].flags = 0;
+ r = words[r].prev;
+ demote_word(r);
+ wordindex = r;
+ if (words[r].word) {
+ free(words[r].word);
+ words[r].word = 0;
+ }
+}
+
+/*
+ * GH: set delimeters[DELIM_CUSTOM]
+ */
+void set_custom_delimeters(char *s)
+{
+ char *old = delim_list[DELIM_CUSTOM];
+ if (!(delim_list[DELIM_CUSTOM] = my_strdup(s)))
+ errmsg("malloc");
+ else {
+ if (old)
+ free(old);
+ delim_len[DELIM_CUSTOM] = strlen(s);
+ delim_mode = DELIM_CUSTOM;
+ }
+}
+
+/*
+ * enter a line
+ */
+void enter_line(char *dummy)
+{
+ char *p;
+
+ if (line_status == 0)
+ input_moveto(edlen);
+ else {
+ if (prompt_status != 0)
+ col0 = 0;
+ draw_input_line();
+ }
+ PRINTF("%s\n", edattrend);
+
+ line0 = CURLINE(edlen);
+ if (line0 < lines - 1) line0++;
+
+ if (recordfile)
+ fprintf(recordfile, "%s\n", edbuf);
+
+ col0 = error = pos = line_status = 0;
+
+ if (!*edbuf || (verbatim && *edbuf != '#'))
+ tcp_write(tcp_fd, edbuf);
+ else
+ parse_user_input(edbuf, 1);
+ history_done = 0;
+
+ /* don't put identical lines in history, nor empty ones */
+ p = hist[curline ? curline - 1 : MAX_HIST - 1];
+ if (!p || (edlen > 0 && strcmp(edbuf, p)))
+ put_history(edbuf);
+ pickline = curline;
+ if (*inserted_next) {
+ strcpy(edbuf, inserted_next);
+ inserted_next[0] = '\0';
+ line_status = 1;
+ } else if (*prefixstr) {
+ strcpy(edbuf, prefixstr);
+ line_status = 1;
+ } else
+ edbuf[0] = '\0';
+ pos = edlen = strlen(edbuf);
+}
+
+/*
+ * move one word forward
+ */
+void next_word(char *dummy)
+{
+ int i;
+ for (i = pos; edbuf[i] && !isalnum(edbuf[i]); i++)
+ ;
+ while (isalnum(edbuf[i]))
+ i++;
+ input_moveto(i);
+}
+
+/*
+ * move one word backward
+ */
+void prev_word(char *dummy)
+{
+ int i;
+ for (i = pos; i && !isalnum(edbuf[i - 1]); i--)
+ ;
+ while (i && isalnum(edbuf[i - 1]))
+ i--;
+ input_moveto(i);
+}
+
+/*
+ * delete word to the right
+ */
+void del_word_right(char *dummy)
+{
+ int i;
+ for (i = pos; edbuf[i] && !isalnum(edbuf[i]); i++)
+ ;
+ while (isalnum(edbuf[i]))
+ i++;
+ input_delete_nofollow_chars(i - pos);
+}
+
+/*
+ * delete word to the left
+ */
+void del_word_left(char *dummy)
+{
+ int i;
+ for (i = pos; i && !isalnum(edbuf[i - 1]); i--)
+ ;
+ while (i && isalnum(edbuf[i - 1]))
+ i--;
+ i = pos - i;
+ input_moveto(pos - i);
+ input_delete_nofollow_chars(i);
+}
+
+/*
+ * GH: make word upcase
+ */
+void upcase_word(char *dummy)
+{
+ int opos = pos;
+ int npos = pos;
+
+ if (last_edit_cmd == (function_any)upcase_word)
+ npos = 0;
+ else {
+ while (npos > 0 && IS_DELIM(edbuf[npos])) npos--;
+ while (npos > 0 && !IS_DELIM(edbuf[npos - 1])) npos--;
+ }
+ input_moveto(npos);
+ while (!IS_DELIM(edbuf[npos]) ||
+ (last_edit_cmd == (function_any)upcase_word && edbuf[npos]))
+ input_overtype_follow(toupper(edbuf[npos++]));
+ input_moveto(opos);
+}
+
+/*
+ * GH: make word downcase
+ */
+void downcase_word(char *dummy)
+{
+ int opos = pos;
+ int npos = pos;
+
+ if (last_edit_cmd == (function_any)downcase_word)
+ npos = 0;
+ else {
+ while (npos > 0 && IS_DELIM(edbuf[npos])) npos--;
+ while (npos > 0 && !IS_DELIM(edbuf[npos - 1])) npos--;
+ }
+ input_moveto(npos);
+ while (!IS_DELIM(edbuf[npos]) ||
+ (last_edit_cmd == (function_any)downcase_word && edbuf[npos])) {
+ input_overtype_follow(tolower(edbuf[npos++]));
+ }
+ input_moveto(opos);
+}
+
+/*
+ * get previous line from history list
+ */
+void prev_line(char *dummy)
+{
+ int i = pickline - 1;
+ if (i < 0) i = MAX_HIST - 1;
+ if (hist[i]) {
+ if (hist[pickline] && strcmp(hist[pickline], edbuf)) {
+ free(hist[pickline]);
+ hist[pickline] = NULL;
+ }
+ if (!hist[pickline]) {
+ if (!(hist[pickline] = my_strdup(edbuf))) {
+ errmsg("malloc");
+ return;
+ }
+ }
+ pickline = i;
+ clear_input_line(0);
+ strcpy(edbuf, hist[pickline]);
+ pos = edlen = strlen(edbuf);
+ }
+}
+
+/*
+ * get next line from history list
+ */
+void next_line(char *dummy)
+{
+ int i = pickline + 1;
+ if (i == MAX_HIST) i = 0;
+ if (hist[i]) {
+ if (hist[pickline] && strcmp(hist[pickline], edbuf)) {
+ free(hist[pickline]);
+ hist[pickline] = NULL;
+ }
+ if (!hist[pickline]) {
+ if (!(hist[pickline] = my_strdup(edbuf))) {
+ errmsg("malloc");
+ return;
+ }
+ }
+ pickline = i;
+ clear_input_line(0);
+ strcpy(edbuf, hist[pickline]);
+ edlen = pos = strlen(edbuf);
+ }
+}
+
+/*
+ * move one char backward
+ */
+void prev_char(char *dummy)
+{
+ input_moveto(pos-1);
+}
+
+/*
+ * move one char forward
+ */
+void next_char(char *dummy)
+{
+ input_moveto(pos+1);
+}
+
+/*
+ * Flash cursor at parentheses that matches c inserted before current pos
+ */
+static void flashparen(char c)
+{
+ int lev, i;
+ if (line_status != 0)
+ return;
+ for (i = pos - 1, lev = 0; i >= 0; i--) {
+ if (ISRPAREN(edbuf[i])) {
+ lev++;
+ } else if (ISLPAREN(edbuf[i])) {
+ lev--;
+ if (!lev) {
+ if (LPAREN(c) == edbuf[i])
+ break;
+ else
+ i = -1;
+ }
+ }
+ }
+ if (i >= 0) {
+ tty_gotoxy_opt(CURCOL(pos), CURLINE(pos), CURCOL(i), CURLINE(i));
+ flashback = 1;
+ excursion = i;
+ }
+}
+
+/*
+ * put cursor back where it belongs
+ */
+void putbackcursor(void)
+{
+ if (line_status == 0)
+ tty_gotoxy_opt(CURCOL(excursion), CURLINE(excursion), CURCOL(pos), CURLINE(pos));
+ flashback = 0;
+}
+
+/*
+ * insert a typed character on screen (if it is printable)
+ */
+void insert_char(char c)
+{
+ if (((c & 0x80) || (c >= ' ' && c <= '~')) && edlen < BUFSIZE - 2) {
+ if (flashback) putbackcursor();
+ input_insert_follow_chars(&c, 1);
+ if (ISRPAREN(c))
+ flashparen(c);
+ }
+}
+
+static void insert_string(char *arg)
+{
+ char buf[BUFSIZE];
+ int len;
+
+ if (!arg || !*arg)
+ return;
+
+ my_strncpy(buf, arg, BUFSIZE-1);
+ unescape(buf);
+ len = strlen(buf);
+
+ if (len > 1) {
+ if (flashback) putbackcursor();
+ input_insert_follow_chars(buf, len);
+ } else if (len == 1)
+ insert_char(buf[0]); /* also flash matching parentheses */
+}
+
+/*
+ * execute string as if typed
+ */
+void key_run_command(char *cmd)
+{
+ clear_input_line(opt_compact && !opt_keyecho);
+ if (opt_keyecho) {
+ tty_printf("%s%s%s\n", edattrbeg, cmd, edattrend);
+ } else if (!opt_compact)
+ tty_putc('\n');
+
+ status(1);
+ error = 0;
+
+ if (recordfile)
+ fprintf(recordfile, "%s\n", edbuf);
+
+ parse_instruction(cmd, 1, 0, 1);
+ history_done = 0;
+}
+
+void edit_bootstrap(void)
+{
+ default_completions();
+}
+
diff --git a/src/edit.h b/src/edit.h
new file mode 100644
index 0000000..b3b5a50
--- /dev/null
+++ b/src/edit.h
@@ -0,0 +1,75 @@
+/* public things from edit.c */
+
+#ifndef _EDIT_H_
+#define _EDIT_H_
+
+typedef struct {
+ char *name;
+ function_str funct;
+} edit_function;
+
+extern edit_function internal_functions[];
+
+/*
+ * GH: completion list, stolen from cancan 2.6.3a
+ *
+ * words[wordindex] is where the next word will go (always empty)
+ * words[wordindex].prev is last (least interesting)
+ * words[wordindex].next is 2nd (first to search for completion)
+ */
+#define WORD_UNIQUE 1 /* word is unique in list */
+typedef struct {
+ char *word;
+ int next, prev;
+ char flags;
+} wordnode;
+
+extern char *hist[MAX_HIST];
+extern int curline;
+extern int pickline;
+
+extern wordnode words[MAX_WORDS];
+extern int wordindex;
+
+/* public function declarations */
+void edit_bootstrap(void);
+
+int lookup_edit_name(char *name, char **arg);
+int lookup_edit_function(function_str funct);
+void draw_prompt(void);
+void clear_input_line(int deleteprompt);
+void draw_input_line(void);
+void redraw_line(char *dummy);
+void redraw_line_noprompt(char *dummy);
+void transpose_words(char *dummy);
+void transpose_chars(char *dummy);
+void kill_to_eol(char *dummy);
+void end_of_line(char *dummy);
+void begin_of_line(char *dummy);
+void del_char_right(char *dummy);
+void del_char_left(char *dummy);
+void to_history(char *dummy);
+void put_history(char *str);
+void complete_word(char *dummy);
+void complete_line(char *dummy);
+void put_word(char *s);
+void put_static_word(char *s);
+void set_custom_delimeters(char *s);
+void to_input_line(char *str);
+void clear_line(char *dummy);
+void enter_line(char *dummy);
+void putbackcursor(void);
+void insert_char(char c);
+void next_word(char *dummy);
+void prev_word(char *dummy);
+void del_word_right(char *dummy);
+void del_word_left(char *dummy);
+void upcase_word(char *dummy);
+void downcase_word(char *dummy);
+void prev_line(char *dummy);
+void next_line(char *dummy);
+void prev_char(char *dummy);
+void next_char(char *dummy);
+void key_run_command(char *cmd);
+
+#endif /* _EDIT_H_ */
diff --git a/src/eval.c b/src/eval.c
new file mode 100644
index 0000000..1a64108
--- /dev/null
+++ b/src/eval.c
@@ -0,0 +1,1456 @@
+/*
+ * eval.c -- functions for builtin calculator
+ *
+ * (created: Massimiliano Ghilardi (Cosmos), Jan 15th, 1995)
+ *
+ * Copyright (C) 1998 by Massimiliano Ghilardi
+ *
+ * This program 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 2 of the License, or
+ * (at your option) any later version.
+ *
+ */
+
+#include <ctype.h>
+#include <errno.h>
+#include <limits.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <strings.h>
+#include <time.h>
+#include <unistd.h>
+
+#include <sys/time.h>
+#include <sys/types.h>
+
+#include "defines.h"
+#include "main.h"
+#include "utils.h"
+#include "cmd2.h"
+#include "list.h"
+#include "map.h"
+#include "tty.h"
+#include "edit.h"
+#include "eval.h"
+
+#ifdef USE_RANDOM
+# define get_random random
+# define init_random srandom
+#else
+# define get_random lrand48
+# define init_random srand48
+#endif
+
+typedef struct {
+ int type;
+ long num; /* used for numeric types or as index for all variables */
+ ptr txt; /* used for text types */
+} object;
+
+#define LEFT 1
+#define RIGHT 2
+
+#define BINARY 0
+#define PRE_UNARY LEFT
+#define POST_UNARY RIGHT
+
+#define LOWEST_UNARY_CODE 49
+/*
+ * it would be 47, but operators 47 '(' and 48 ')'
+ * are treated separately
+ */
+
+enum op_codes {
+ null=0, comma, eq, or_or_eq, xor_xor_eq, and_and_eq, or_eq, xor_eq, and_eq,
+ lshift_eq, rshift_eq, plus_eq, minus_eq, times_eq, div_eq, ampersand_eq,
+ or_or, xor_xor, and_and, or, xor, and,
+ less, less_eq, greater, greater_eq, eq_eq, not_eq,
+ lshift, rshift, plus, minus, times, division, ampersand,
+
+ colon_less, colon_greater, less_colon, greater_colon,
+ point_less, point_greater, less_point, greater_point,
+ colon, point, question, another_null,
+
+ left_paren, right_paren, not, tilde,
+ pre_plus_plus, post_plus_plus, pre_minus_minus, post_minus_minus,
+ star, print, _random_, _attr_, colon_question, point_question,
+ pre_plus, pre_minus, a_circle, dollar, pre_null, post_null
+};
+
+typedef enum op_codes operator;
+
+typedef struct {
+ char priority, assoc, syntax, *name;
+ operator code;
+} operator_list;
+
+typedef struct {
+ object obj[MAX_STACK];
+ int curr_obj;
+ operator op[MAX_STACK];
+ int curr_op;
+} stack;
+
+char *error_msg[] = {
+ "unknown error",
+ "math stack overflow",
+ "math stack underflow",
+ "stack overflow",
+ "stack underflow",
+ "expression syntax",
+ "operator expected",
+ "value expected",
+ "division by zero",
+ "operand or index out of range",
+ "missing right parenthesis",
+ "missing left parenthesis",
+ "internal error!",
+ "operator not supported",
+ "operation not completed (internal error)",
+ "out of memory",
+ "text/string longer than limit, discarded",
+ "infinite loop",
+ "numeric value expected",
+ "string expected",
+ "missing label",
+ "missing separator \";\"",
+ "#history recursion too deep",
+ "user break",
+ "too many defined variables",
+ "undefined variable",
+ "invalid digit in numeric value",
+ "bad attribute syntax",
+ "invalid variable name",
+};
+
+operator_list op_list[] = {
+ { 0, 0, 0, "", null },
+
+ { 1, LEFT, BINARY, ",", comma },
+
+ { 2, RIGHT, BINARY, "=", eq },
+ { 2, RIGHT, BINARY, "||=", or_or_eq },
+ { 2, RIGHT, BINARY, "^^=", xor_xor_eq },
+ { 2, RIGHT, BINARY, "&&=", and_and_eq },
+ { 2, RIGHT, BINARY, "|=", or_eq },
+ { 2, RIGHT, BINARY, "^=", xor_eq },
+ { 2, RIGHT, BINARY, "&=", and_eq },
+ { 2, RIGHT, BINARY, "<<=", lshift_eq },
+ { 2, RIGHT, BINARY, ">>=", rshift_eq },
+ { 2, RIGHT, BINARY, "+=", plus_eq },
+ { 2, RIGHT, BINARY, "-=", minus_eq },
+ { 2, RIGHT, BINARY, "*=", times_eq },
+ { 2, RIGHT, BINARY, "/=", div_eq },
+ { 2, RIGHT, BINARY, "%=", ampersand_eq },
+
+ { 3, LEFT, BINARY, "||", or_or },
+
+ { 4, LEFT, BINARY, "^^", xor_xor },
+
+ { 5, LEFT, BINARY, "&&", and_and },
+
+ { 6, LEFT, BINARY, "|", or },
+
+ { 7, LEFT, BINARY, "^", xor },
+
+ { 8, LEFT, BINARY, "&", and },
+
+ { 9, LEFT, BINARY, "<", less },
+ { 9, LEFT, BINARY, "<=", less_eq },
+ { 9, LEFT, BINARY, ">", greater },
+ { 9, LEFT, BINARY, ">=", greater_eq },
+ { 9, LEFT, BINARY, "==", eq_eq },
+ { 9, LEFT, BINARY, "!=", not_eq },
+
+ {10, LEFT, BINARY, "<<", lshift },
+ {10, LEFT, BINARY, ">>", rshift },
+
+ {11, LEFT, BINARY, "+", plus },
+ {11, LEFT, BINARY, "-", minus },
+
+ {12, LEFT, BINARY, "*", times },
+ {12, LEFT, BINARY, "/", division },
+ {12, LEFT, BINARY, "%", ampersand },
+
+ {14, LEFT, BINARY, ":<", colon_less },
+ {14, LEFT, BINARY, ":>", colon_greater },
+ {14, LEFT, BINARY, "<:", less_colon },
+ {14, LEFT, BINARY, ">:", greater_colon },
+ {14, LEFT, BINARY, ".<", point_less },
+ {14, LEFT, BINARY, ".>", point_greater },
+ {14, LEFT, BINARY, "<.", less_point },
+ {14, LEFT, BINARY, ">.", greater_point },
+ {14, LEFT, BINARY, ":", colon },
+ {14, LEFT, BINARY, ".", point },
+ {14, LEFT, BINARY, "?", question },
+
+ { 0, 0, 0, "", another_null },
+
+ { 0, RIGHT, PRE_UNARY, "(", left_paren },
+ { 0, RIGHT, POST_UNARY, ")", right_paren },
+
+ {13, RIGHT, PRE_UNARY, "!", not },
+ {13, RIGHT, PRE_UNARY, "~", tilde },
+ {13, RIGHT, PRE_UNARY, "++", pre_plus_plus },
+ {13, RIGHT, POST_UNARY, "++", post_plus_plus },
+ {13, RIGHT, PRE_UNARY, "--", pre_minus_minus },
+ {13, RIGHT, POST_UNARY, "--", post_minus_minus },
+ {13, RIGHT, PRE_UNARY, "*", star },
+ {13, RIGHT, PRE_UNARY, "%", print },
+ {13, RIGHT, PRE_UNARY, "rand", _random_ },
+ {13, RIGHT, PRE_UNARY, "attr", _attr_ },
+
+ {14, LEFT, PRE_UNARY, ":?", colon_question },
+ {14, LEFT, PRE_UNARY, ".?", point_question },
+
+ {15, RIGHT, PRE_UNARY, "+", pre_plus },
+ {15, RIGHT, PRE_UNARY, "-", pre_minus },
+ {15, RIGHT, PRE_UNARY, "@", a_circle },
+ {15, RIGHT, PRE_UNARY, "$", dollar },
+
+ { 0, 0, PRE_UNARY, "", pre_null },
+ { 0, 0, POST_UNARY, "", post_null }
+};
+
+static stack stk;
+static char *line;
+static int depth;
+int error;
+
+void print_error(int err_num)
+{
+ clear_input_line(1);
+ if (error == NO_MEM_ERROR) {
+ tty_printf("#system call error: %s (%d", "malloc", ENOMEM);
+ tty_printf(": %s)\n", strerror(ENOMEM));
+ } else
+ tty_printf("#error: %s.\n", error_msg[err_num]);
+}
+
+static int push_op(operator *op)
+{
+ if (stk.curr_op<MAX_STACK) {
+ stk.op[++stk.curr_op]=*op;
+ return 1;
+ }
+ else {
+ error=STACK_OV_ERROR;
+ return 0;
+ }
+}
+
+static int pop_op(operator *op)
+{
+ if (stk.curr_op>=0) {
+ *op=stk.op[stk.curr_op--];
+ return 1;
+ }
+ else {
+ error=STACK_UND_ERROR;
+ return 0;
+ }
+}
+
+static int push_obj(object *obj)
+{
+ object *tmp;
+
+ int curr=stk.curr_obj;
+
+ if (curr<MAX_STACK) {
+ tmp = stk.obj + (stk.curr_obj = ++curr);
+ memmove(tmp, obj, sizeof(object));
+ return 1;
+ }
+ else {
+ error=STACK_OV_ERROR;
+ return 0;
+ }
+}
+
+static int pop_obj(object *obj)
+{
+ object *tmp;
+
+ int curr=stk.curr_obj;
+
+ if (curr>=0) {
+ tmp = stk.obj + curr;
+ stk.curr_obj--;
+ memmove(obj, tmp, sizeof(object));
+ return 1;
+ }
+ else {
+ error=STACK_UND_ERROR;
+ return 0;
+ }
+}
+
+static int check_operator(char side, operator *op, int mindepth)
+{
+ int i, max, len;
+ operator match;
+ char *name, c, d;
+
+ if (!(c=*line) || c == CMDSEP) {
+ *op = side==BINARY ? null : side==LEFT ? pre_null : post_null;
+ return 1;
+ }
+ else if ((c=='$' || c=='@') && (d=line[1]) && (isalpha(d) || d=='_'))
+ return 0; /* Danger! found named variable */
+
+ else if (side==LEFT && c=='(') {
+ line++;
+ depth++;
+ *op=left_paren;
+ return 1;
+ }
+ else if (side==RIGHT && c==')') {
+ if (--depth >= mindepth) {
+ line++;
+ *op=right_paren;
+ }
+ else /* exit without touching the parenthesis */
+ *op=post_null;
+ return 1;
+ }
+ else if (side==RIGHT && (c=='}' || c==']') && depth == mindepth) {
+ /* allow also exiting with a '}' or a ']' */
+ --depth;
+ *op=post_null;
+ return 1;
+ }
+
+ for (max=match=0, i=(side==BINARY ? 1 : LOWEST_UNARY_CODE);
+ *(name = op_list[i].name); i++)
+ if ((len=strlen(name)) > max &&
+ (side==BINARY || side==op_list[i].syntax) &&
+ !strncmp(line, name, (size_t)len)) {
+ match=op_list[i].code;
+ max=len;
+ }
+
+ if (match) {
+ *op=match;
+ line+=max;
+ return 1;
+ }
+ else {
+ *op= side==BINARY ? null : side==PRE_UNARY ? pre_null : post_null;
+ if (side==BINARY)
+ error=NO_OPERATOR_ERROR;
+ }
+ return 0;
+}
+
+static int check_object(object *obj)
+{
+ long i=0, base = 10;
+ char c, *end, digit;
+
+ if (c=*line, c == '#' || isdigit(c)) {
+ while (c == '#' || isalnum(c)) {
+ digit = !!isdigit(c);
+ if (c == '#') {
+ base = i;
+ i = 0;
+ if (!base)
+ base = 16;
+ } else {
+ i *= base;
+ if (digit)
+ i += (c - '0');
+ else {
+ if (c >= 'a' && c <= 'z')
+ c = (c - 'a') + 'A';
+ if (c - 'A' + 10 >= base) {
+ error=OUT_BASE_ERROR;
+ return 0;
+ }
+ i += (c - 'A' + 10);
+ }
+ }
+ c=*++line;
+ }
+ obj->type=TYPE_NUM;
+ obj->num=i;
+ i=1;
+ }
+ else if(c=='\"') {
+ end=first_valid(++line, '\"');
+ if (*end) {
+ obj->type=TYPE_TXT;
+ obj->txt=ptrmcpy(obj->txt, line, end-line);
+ if (!REAL_ERROR) {
+ ptrunescape(obj->txt);
+ i=1;
+ line=end+1;
+ }
+ }
+ }
+ else if ((c=='$' || c=='@') && (c=line[1]) && (isalpha(c) || c=='_')) {
+ varnode *named_var; /* Found named variable */
+
+ if (*(line++) == '@') {
+ i = 0;
+ obj->type = TYPE_NUM_VAR;
+ }
+ else {
+ i = 1;
+ obj->type = TYPE_TXT_VAR;
+ }
+ end = line + 1;
+ while ((c=*end) && (isalpha(c) || c=='_' || isdigit(c)))
+ end++;
+ c = *end; *end = '\0';
+ if (!(named_var = *lookup_varnode(line, i))) {
+ named_var = add_varnode(line, i);
+ if (REAL_ERROR)
+ return 0;
+ if (opt_info) {
+ PRINTF("#new variable: %s\n", line - 1);
+ }
+ }
+ *end = c;
+ line = end;
+ obj->num = named_var->index;
+ i = 1;
+ }
+ else if (!strncmp(line, "timer", 5)) {
+ obj->type = TYPE_NUM;
+ update_now();
+ obj->num = diff_vtime(&now, &ref_time);
+ line += 5;
+ i = 1;
+ }
+ else if (!strncmp(line, "map", 3)) {
+ char buf[MAX_MAPLEN + 1];
+ map_sprintf(buf);
+ obj->type = TYPE_TXT;
+ obj->txt = ptrmcpy(obj->txt, buf, strlen(buf));
+ if (!REAL_ERROR) {
+ line += 3;
+ i = 1;
+ }
+ }
+ else if (!strncmp(line, "noattr", 6)) {
+ obj->type = TYPE_TXT;
+ obj->txt = ptrmcpy(obj->txt, tty_modestandoff, strlen(tty_modestandoff));
+ obj->txt = ptrmcat(obj->txt, tty_modenorm, strlen(tty_modenorm));
+ if (!REAL_ERROR) {
+ line += 6;
+ i = 1;
+ }
+ }
+ else
+ error=NO_VALUE_ERROR;
+
+ return (int)i;
+}
+
+static void check_delete(object *obj)
+{
+ if (obj->type==TYPE_TXT && obj->txt) {
+ ptrdel(obj->txt);
+ obj->txt = NULL;
+ }
+}
+
+static int exe_op(operator *op)
+{
+ object o1, o2, *p=NULL;
+ long *l, rnd, delta;
+ ptr src = NULL, dst = NULL, start = NULL;
+ int srclen;
+ char *ssrc, *tmp;
+ int ret=0, i=0, j=0, danger=0;
+
+ o1.txt = o2.txt = NULL;
+
+ switch ((int)*op) {
+ case (int)comma:
+ if (pop_obj(&o2) && pop_obj(&o1));
+ else if (REAL_ERROR) break;
+ check_delete(&o1);
+ p=&o2;
+ ret=1;
+ break;
+ case (int)eq:
+ if (pop_obj(&o2) && pop_obj(&o1));
+ else if (REAL_ERROR) break;
+
+ if (o2.type==TYPE_NUM_VAR) {
+ o2.num = *VAR[o2.num].num;
+ o2.type = TYPE_NUM;
+ }
+
+ if (o1.type==TYPE_NUM_VAR && o2.type==TYPE_NUM) {
+ *VAR[o1.num].num = o2.num;
+ p=&o2;
+ ret=1;
+ }
+ else if (o1.type==TYPE_TXT_VAR &&
+ (o2.type==TYPE_TXT || o2.type==TYPE_TXT_VAR)) {
+
+ if (o2.type==TYPE_TXT_VAR) {
+ o2.txt = ptrdup(*VAR[o2.num].str);
+ if (REAL_ERROR) break;
+ o2.type=TYPE_TXT;
+ }
+
+ *VAR[o1.num].str = ptrcpy(*VAR[o1.num].str, o2.txt);
+ if (REAL_ERROR) break;
+ p=&o2;
+ ret=1;
+ }
+ else
+ error=SYNTAX_ERROR;
+ break;
+ case (int)or_or_eq:
+ case (int)xor_xor_eq:
+ case (int)and_and_eq:
+ case (int)or_eq:
+ case (int)xor_eq:
+ case (int)and_eq:
+ case (int)lshift_eq:
+ case (int)rshift_eq:
+ case (int)plus_eq:
+ case (int)minus_eq:
+ case (int)times_eq:
+ case (int)div_eq:
+ case (int)ampersand_eq:
+ if (pop_obj(&o2) && pop_obj(&o1));
+ else if (REAL_ERROR) break;
+
+ if (o2.type==TYPE_NUM_VAR) {
+ o2.num = *VAR[o2.num].num;
+ o2.type = TYPE_NUM;
+ }
+
+ if (o1.type==TYPE_NUM_VAR && o2.type==TYPE_NUM) {
+ l=VAR[o1.num].num;
+
+ switch ((int)*op) {
+ case (int)or_or_eq: if ( o2.num) *l = 1; else *l = !!*l; break;
+ case (int)xor_xor_eq:if ( o2.num) *l = !*l; else *l = !!*l; break;
+ case (int)and_and_eq:if (!o2.num) *l = 0; else *l = !!*l; break;
+ case (int)or_eq: *l |= o2.num; break;
+ case (int)xor_eq: *l ^= o2.num; break;
+ case (int)and_eq: *l &= o2.num; break;
+ case (int)lshift_eq: *l <<= o2.num; break;
+ case (int)rshift_eq: *l >>= o2.num; break;
+ case (int)plus_eq: *l += o2.num; break;
+ case (int)minus_eq: *l -= o2.num; break;
+ case (int)times_eq: *l *= o2.num; break;
+ case (int)div_eq: *l /= o2.num; break;
+ case (int)ampersand_eq:
+ if ((*l %= o2.num) < 0) *l += o2.num; break;
+ }
+ o2.num=*l;
+ p=&o2;
+ ret=1;
+ }
+ else if (*op==plus_eq && o1.type==TYPE_TXT_VAR &&
+ (o2.type==TYPE_TXT || o2.type==TYPE_TXT_VAR)) {
+
+ if (o2.type==TYPE_TXT)
+ src=o2.txt;
+ else
+ src=*VAR[o2.num].str;
+
+ *VAR[o1.num].str = ptrcat(*VAR[o1.num].str, src);
+ check_delete(&o2);
+
+ dst = ptrdup(*VAR[o1.num].str);
+ if (REAL_ERROR) break;
+
+ o1.type=TYPE_TXT;
+ o1.txt=dst;
+ p=&o1;
+ ret=1;
+ }
+ else if (*op==times_eq && o1.type==TYPE_TXT_VAR &&
+ (o2.type==TYPE_NUM || o2.type==TYPE_NUM_VAR)) {
+
+ if (o2.type==TYPE_NUM_VAR) {
+ o2.num = *VAR[o2.num].num;
+ o2.type = TYPE_NUM;
+ }
+
+ if (o2.num < 0)
+ error = OUT_RANGE_ERROR;
+ else if (o2.num == 0)
+ ptrzero(*VAR[o1.num].str);
+ else if (o2.num == 1)
+ ;
+ else if (*VAR[o1.num].str && (delta = ptrlen(*VAR[o1.num].str))) {
+ long n;
+ *VAR[o1.num].str = ptrsetlen(*VAR[o1.num].str, delta*o2.num);
+ tmp = ptrdata(*VAR[o1.num].str);
+ for (n = 1; !error && n<o2.num; n++)
+ memcpy(tmp+n*delta, tmp, delta);
+ }
+
+ check_delete(&o2);
+ dst = ptrdup(*VAR[o1.num].str);
+ if (REAL_ERROR) break;
+
+ o1.type=TYPE_TXT;
+ o1.txt=dst;
+ p=&o1;
+ ret=1;
+ }
+ else
+ error=SYNTAX_ERROR;
+ break;
+ case (int)or_or:
+ case (int)xor_xor:
+ case (int)and_and:
+ case (int)or:
+ case (int)xor:
+ case (int)and:
+ case (int)less:
+ case (int)less_eq:
+ case (int)greater:
+ case (int)greater_eq:
+ case (int)eq_eq:
+ case (int)not_eq:
+ case (int)lshift:
+ case (int)rshift:
+ case (int)minus:
+ case (int)plus:
+ case (int)times:
+ case (int)division:
+ case (int)ampersand:
+ if (pop_obj(&o2) && pop_obj(&o1));
+ else if (REAL_ERROR) break;
+
+ if (o1.type==TYPE_NUM_VAR) {
+ o1.num = *VAR[o1.num].num;
+ o1.type = TYPE_NUM;
+ }
+ if (o2.type==TYPE_NUM_VAR) {
+ o2.num = *VAR[o2.num].num;
+ o2.type = TYPE_NUM;
+ }
+
+ if (o1.type==TYPE_NUM && o2.type==TYPE_NUM) {
+ if (!o2.num &&
+ (*op==division || *op==ampersand)) {
+ error=DIV_BY_ZERO_ERROR;
+ break;
+ }
+
+ switch ((int)*op) {
+ case (int)less: o1.num = o1.num < o2.num ? 1 : 0; break;
+ case (int)less_eq: o1.num = o1.num <= o2.num ? 1 : 0; break;
+ case (int)greater: o1.num = o1.num > o2.num ? 1 : 0; break;
+ case (int)greater_eq:o1.num = o1.num >= o2.num ? 1 : 0; break;
+ case (int)eq_eq: o1.num = o1.num == o2.num ? 1 : 0; break;
+ case (int)not_eq: o1.num = o1.num != o2.num ? 1 : 0; break;
+ case (int)or_or: o1.num = o1.num || o2.num; break;
+ case (int)xor_xor:if (o2.num) o1.num = !o1.num; break;
+ case (int)and_and: o1.num = o1.num && o2.num; break;
+ case (int)or: o1.num |= o2.num; break;
+ case (int)xor: o1.num ^= o2.num; break;
+ case (int)and: o1.num &= o2.num; break;
+ case (int)lshift: o1.num <<= o2.num; break;
+ case (int)rshift: o1.num >>= o2.num; break;
+ case (int)minus: o1.num -= o2.num; break;
+ case (int)plus: o1.num += o2.num; break;
+ case (int)times: o1.num *= o2.num; break;
+ case (int)division:o1.num /= o2.num; break;
+ case (int)ampersand:
+ if ((o1.num %= o2.num) < 0) o1.num += o2.num; break;
+ }
+
+ p=&o1;
+ ret=1;
+ }
+ else if ((o1.type==TYPE_TXT || o1.type==TYPE_TXT_VAR) &&
+ (o2.type==TYPE_TXT || o2.type==TYPE_TXT_VAR)) {
+
+ if (o1.type==TYPE_TXT_VAR) {
+ o1.txt = ptrdup(*VAR[o1.num].str);
+ o1.type = TYPE_TXT; /* not a var anymore */
+ if (REAL_ERROR) break;
+ }
+ dst = o1.txt;
+ if (o2.type==TYPE_TXT)
+ src=o2.txt;
+ else
+ src=*VAR[o2.num].str;
+
+ if (*op == plus) {
+ dst = ptrcat(dst, src);
+ o1.type = TYPE_TXT;
+ } else {
+ o1.num = ptrcmp(dst, src);
+ switch ((int)*op) {
+ case (int)minus: break;
+ case (int)less: o1.num = o1.num < 0; break;
+ case (int)less_eq: o1.num = o1.num <= 0; break;
+ case (int)greater: o1.num = o1.num > 0; break;
+ case (int)greater_eq: o1.num = o1.num >= 0; break;
+ case (int)eq_eq: o1.num = o1.num == 0; break;
+ case (int)not_eq: o1.num = o1.num != 0; break;
+ default:
+ error=SYNTAX_ERROR;
+ p=NULL; ret=0; break;
+ }
+ check_delete(&o1);
+ /* moved here because it interfered with allowing the dst ptr from
+ * being freed, casing a very tiny memory leak */
+ o1.type = TYPE_NUM;
+ }
+ check_delete(&o2);
+ if (!REAL_ERROR) {
+ o1.txt = dst;
+ p=&o1;
+ ret=1;
+ }
+ }
+ else if (*op==times
+ && (o1.type==TYPE_TXT_VAR || o1.type==TYPE_TXT)
+ && o2.type==TYPE_NUM) {
+
+ if (o2.num > 0 && o1.type==TYPE_TXT_VAR) {
+ o1.txt = ptrdup(*VAR[o1.num].str);
+ if (REAL_ERROR) break;
+ }
+ dst = o1.txt;
+
+ if (o2.num < 0)
+ error = OUT_RANGE_ERROR;
+ else if (o2.num == 0)
+ ptrzero(dst);
+ else if (o2.num == 1)
+ ;
+ else if (dst && (delta = ptrlen(dst))) {
+ long n;
+ dst = ptrsetlen(dst, delta*o2.num);
+ tmp = ptrdata(dst);
+ for (n = 1; !error && n<o2.num; n++)
+ memcpy(tmp+n*delta, tmp, delta);
+ }
+ check_delete(&o2);
+ if (REAL_ERROR) break;
+
+ o1.type=TYPE_TXT;
+ o1.txt=dst;
+ p=&o1;
+ ret=1;
+ }
+ else
+ error=SYNTAX_ERROR;
+ break;
+ case (int)colon_less:
+ case (int)colon_greater:
+ case (int)less_colon:
+ case (int)greater_colon:
+ case (int)colon:
+ case (int)point_less:
+ case (int)point_greater:
+ case (int)less_point:
+ case (int)greater_point:
+ case (int)point:
+ if (pop_obj(&o2) && pop_obj(&o1));
+ else if (REAL_ERROR) break;
+
+ if (o2.type==TYPE_NUM_VAR) {
+ o2.num = *VAR[o2.num].num;
+ o2.type = TYPE_NUM;
+ }
+
+ if ((o1.type!=TYPE_TXT_VAR && o1.type!=TYPE_TXT) || o2.type!=TYPE_NUM) {
+ error=SYNTAX_ERROR;
+ break;
+ }
+
+ if (o2.num<=0) {
+ error=OUT_RANGE_ERROR;
+ break;
+ }
+
+ if (o1.type==TYPE_TXT_VAR) {
+ o1.type=TYPE_TXT;
+ o1.txt=dst=NULL;
+ src=start=*VAR[o1.num].str;
+ }
+ else {
+ /* Potentially dangerous: src and dst are overlapping */
+ src=dst=start=o1.txt;
+ danger=1;
+ }
+
+ if (!src) {
+ /* src == empty string. just return it */
+ check_delete(&o2);
+ o1.txt = src;
+ if (!REAL_ERROR)
+ p=&o1; ret=1;
+ break;
+ }
+
+ srclen = ptrlen(src);
+ ssrc = ptrdata(src);
+
+ switch ((int)*op) {
+ case (int)colon_less:
+ while (o2.num && srclen) {
+ /* skip span of multiple word delimeters */
+ while (srclen && memchr(DELIM, *ssrc, DELIM_LEN))
+ srclen--, ssrc++, j++;
+ /* skip whole words */
+ if (srclen && (tmp = memchrs(ssrc, srclen, DELIM, DELIM_LEN)))
+ i=tmp-ssrc, o2.num--, ssrc+=i, j+=i, srclen-=i;
+ else break;
+ }
+
+ if (o2.num) { /* end of valid string before the n-th word */
+ if (danger)
+ ;
+ else
+ dst = ptrcpy(dst, start);
+ } else {
+ if (danger)
+ ptrtrunc(dst, j);
+ else
+ dst = ptrmcpy(dst, ptrdata(start), j);
+ }
+ break;
+ case (int)colon:
+ case (int)colon_greater:
+ o2.num--;
+ /* skip span of multiple word delimeters */
+ while (srclen && memchr(DELIM, *ssrc, DELIM_LEN))
+ srclen--, ssrc++;
+ while (o2.num && srclen) {
+ /* skip whole words */
+ if (srclen && (tmp = memchrs(ssrc, srclen, DELIM, DELIM_LEN))) {
+ i=tmp-ssrc, o2.num--, ssrc+=i, srclen-=i;
+ /* skip span of multiple word delimeters */
+ while (srclen && memchr(DELIM, *ssrc, DELIM_LEN))
+ srclen--, ssrc++;
+ } else break;
+ }
+
+ if (o2.num) /* end of valid string before the n-th word */
+ ptrzero(dst);
+ else {
+ if (*op==colon &&
+ (tmp = memchrs(ssrc, srclen, DELIM, DELIM_LEN))) {
+ dst = ptrmcpy(dst, ssrc, tmp-ssrc);
+ }
+ else
+ dst = ptrmcpy(dst, ssrc, srclen);
+ }
+ break;
+ case (int)less_colon:
+ o2.num--;
+ while (o2.num && srclen) {
+ /* skip span of multiple word delimeters */
+ while (srclen && memchr(DELIM, ssrc[srclen], DELIM_LEN))
+ srclen--;
+ /* skip whole words */
+ if (srclen && (tmp=memrchrs(ssrc, srclen, DELIM, DELIM_LEN)))
+ o2.num--, srclen=tmp-ssrc;
+ else break;
+ }
+
+ if (o2.num) /* end of valid string before the n-th word */
+ ptrzero(dst);
+ else
+ dst = ptrmcpy(dst, ssrc, srclen);
+ break;
+ case (int)greater_colon:
+ while (o2.num && srclen) {
+ /* skip span of multiple word delimeters */
+ while (srclen && memchr(DELIM, ssrc[srclen], DELIM_LEN))
+ srclen--;
+ /* skip whole words */
+ if (srclen && (tmp=memrchrs(ssrc, srclen, DELIM, DELIM_LEN)))
+ o2.num--, srclen=tmp-ssrc;
+ else break;
+ }
+
+ if (o2.num) /* end of valid string before the n-th word */
+ dst = ptrcpy(dst, start);
+ else
+ dst = ptrmcpy(dst, ssrc+srclen+1,
+ ptrlen(start) - (ssrc+srclen+1 - ptrdata(start)));
+ break;
+ case (int)point:
+ j = o2.num <= srclen ? o2.num-1 : srclen;
+ dst = ptrmcpy(dst, ssrc+j, 1);
+ break;
+ case (int)point_less:
+ j = o2.num < srclen ? o2.num : srclen;
+ if (danger)
+ ptrtrunc(dst, j);
+ else
+ dst = ptrmcpy(dst, ssrc, j);
+ break;
+ case (int)less_point:
+ j = srclen-o2.num+1;
+ if (j < 0)
+ j = 0;
+ if (danger)
+ ptrtrunc(dst, j);
+ else
+ dst = ptrmcpy(dst, ssrc, j);
+ break;
+ case (int)point_greater:
+ j = o2.num-1 < srclen ? o2.num-1 : srclen;
+ dst = ptrmcpy(dst, ssrc+j, srclen-j);
+ break;
+ case (int)greater_point:
+ j = srclen-o2.num;
+ if (j < 0)
+ j = 0;
+ dst = ptrmcpy(dst, ssrc+j, srclen-j);
+ break;
+ }
+ check_delete(&o2);
+ o1.txt = dst;
+ if (!REAL_ERROR)
+ p=&o1; ret=1;
+ break;
+ case (int)colon_question:
+ case (int)point_question:
+ if (pop_obj(&o1));
+ else if (REAL_ERROR) break;
+
+ if (o1.type==TYPE_TXT)
+ src=o1.txt;
+ else if (o1.type==TYPE_TXT_VAR)
+ src=*VAR[o1.num].str;
+ else {
+ error=SYNTAX_ERROR;
+ break;
+ }
+ if (!src) {
+ /* empty string. return 0 */
+ check_delete(&o1);
+ o1.type=TYPE_NUM;
+ o1.num =0;
+ p=&o1;
+ ret=1;
+ break;
+ }
+
+ ssrc = ptrdata(src);
+ srclen = ptrlen(src);
+
+ if (*op==colon_question) {
+ o1.num = 0;
+ /* skip span of multiple word delimeters */
+ while (srclen && memchr(DELIM, *ssrc, DELIM_LEN))
+ ssrc++, srclen--;
+ while (srclen) {
+ /* skip whole words */
+ if (srclen && (tmp=memchrs(ssrc, srclen, DELIM, DELIM_LEN))) {
+ i=tmp-ssrc, o1.num++, ssrc+=i, srclen-=i;
+ /* skip span of multiple word delimeters */
+ while (srclen && memchr(DELIM, *ssrc, DELIM_LEN))
+ srclen--, ssrc++;
+ } else {
+ srclen=0;
+ o1.num++;
+ }
+ }
+ }
+ else
+ o1.num=srclen;
+
+ check_delete(&o1);
+ o1.type=TYPE_NUM;
+ p=&o1;
+ ret=1;
+ break;
+ case (int)question:
+ if (pop_obj(&o2) && pop_obj(&o1));
+ else if (REAL_ERROR) break;
+
+ if (o1.type==TYPE_TXT)
+ src = o1.txt;
+ else if (o1.type==TYPE_TXT_VAR)
+ src = *VAR[o1.num].str;
+ else
+ error = SYNTAX_ERROR;
+
+ if (o2.type==TYPE_TXT)
+ dst = o2.txt;
+ else if (o2.type==TYPE_TXT_VAR)
+ dst = *VAR[o2.num].str;
+ else
+ error = SYNTAX_ERROR;
+
+ if (!error) {
+ if ((ssrc = ptrfind(src, dst)))
+ i = (int)(ssrc - ptrdata(src)) + 1;
+ else
+ i = 0;
+ o1.type = TYPE_NUM;
+ o1.num = i;
+ p=&o1; ret=1;
+ }
+ check_delete(&o1);
+ check_delete(&o2);
+ break;
+ case (int)null:
+ case (int)another_null:
+ if (pop_obj(&o2) && pop_obj(&o1));
+ else if (REAL_ERROR) break;
+
+ check_delete(&o1);
+ check_delete(&o2);
+
+ o1.type=0, o1.num=0, o1.txt=NULL;
+
+ p=&o1;
+ ret=1;
+ break;
+ case (int)left_paren:
+ error=MISSING_PAREN_ERROR;
+ break;
+ case (int)right_paren:
+ if (pop_op(op));
+ else if (REAL_ERROR) break;
+
+ if (*op!=left_paren)
+ error=MISMATCH_PAREN_ERROR;
+ else
+ ret=1;
+
+ break;
+ case (int)_random_:
+#ifdef NO_RANDOM
+ error = NOT_SUPPORTED_ERROR;
+ break;
+#endif
+ case (int)pre_plus:
+ case (int)pre_minus:
+ case (int)not:
+ case (int)tilde:
+
+ if (pop_obj(&o1));
+ else if (REAL_ERROR) break;
+
+ if (o1.type==TYPE_NUM_VAR) {
+ o1.num = *VAR[o1.num].num;
+ o1.type = TYPE_NUM;
+ }
+ if (o1.type==TYPE_NUM) {
+ if (*op==pre_minus)
+ o1.num=-o1.num;
+ else if (*op==not)
+ o1.num=!o1.num;
+ else if (*op==tilde)
+ o1.num=~o1.num;
+#ifndef NO_RANDOM
+ else if (*op==_random_) {
+ if (o1.num <= 0) {
+ error=OUT_RANGE_ERROR;
+ break;
+ } else {
+ delta = LONG_MAX - LONG_MAX % o1.num;
+ while (rnd = get_random(), rnd > delta);
+ /* skip numbers that would alterate distribution */
+ o1.num = rnd / (delta / o1.num);
+ }
+ }
+#endif
+ p=&o1;
+ ret=1;
+ }
+ else
+ error=SYNTAX_ERROR;
+ break;
+ case (int)_attr_:
+ if (pop_obj(&o1));
+ else if (REAL_ERROR) break;
+
+ if (o1.type==TYPE_TXT_VAR) {
+ o1.txt = ptrdup(*VAR[o1.num].str);
+ if (REAL_ERROR) break;
+ o1.type = TYPE_TXT;
+ }
+
+ if (o1.type==TYPE_TXT) {
+ char dummy[CAPLEN]; /* just because attr_string must write somewhere */
+
+ if (o1.txt)
+ i = parse_attributes(ptrdata(o1.txt));
+ else
+ i = NOATTRCODE;
+ if (i == -1)
+ error=BAD_ATTR_ERROR;
+ else {
+ o1.txt = ptrsetlen(o1.txt, CAPLEN);
+ if (REAL_ERROR) break;
+ attr_string(i, ptrdata(o1.txt), dummy);
+ ptrtrunc(o1.txt, strlen(ptrdata(o1.txt)));
+ p=&o1;
+ ret = 1;
+ }
+ } else
+ error=NO_STRING_ERROR;
+ break;
+
+ case (int)star:
+ case (int)print:
+ if (pop_obj(&o1));
+ else if (REAL_ERROR) break;
+
+ if (o1.type==TYPE_NUM_VAR)
+ o1.num = *VAR[o1.num].num;
+ else if (o1.type==TYPE_TXT_VAR)
+ o1.txt = *VAR[o1.num].str;
+
+ if (o1.type==TYPE_NUM || o1.type==TYPE_NUM_VAR) {
+ o1.txt = NULL;
+ if (*op==print) {
+ char buf[LONGLEN];
+ sprintf(buf, "%ld", o1.num);
+ o1.txt = ptrmcpy(o1.txt, buf, strlen(buf));
+ } else {
+ char buf = (char)o1.num;
+ o1.txt = ptrmcpy(o1.txt, &buf, 1);
+ }
+ if (REAL_ERROR) break;
+ o1.type = TYPE_TXT;
+ p=&o1; ret=1;
+ }
+ else if (o1.type==TYPE_TXT || o1.type==TYPE_TXT_VAR) {
+ if (*op==print) {
+ if (o1.txt && ptrlen(o1.txt))
+ o1.num = atol(ptrdata(o1.txt));
+ else
+ o1.num = 0;
+ } else {
+ if (o1.txt && ptrlen(o1.txt))
+ o1.num = (long)(byte)*ptrdata(o1.txt);
+ else
+ o1.num = 0;
+ }
+ check_delete(&o1);
+ o1.type = TYPE_NUM;
+ p=&o1; ret=1;
+ }
+ else
+ error=SYNTAX_ERROR;
+ break;
+ case (int)pre_plus_plus:
+ case (int)post_plus_plus:
+ case (int)pre_minus_minus:
+ case (int)post_minus_minus:
+ if (pop_obj(&o1));
+ else if (REAL_ERROR) break;
+
+ if (o1.type==TYPE_NUM_VAR) {
+ l=VAR[o1.num].num;
+ o1.type=TYPE_NUM;
+
+ if (*op==pre_plus_plus)
+ o1.num=++*l;
+ else if (*op==post_plus_plus)
+ o1.num=(*l)++;
+ else if (*op==pre_minus_minus)
+ o1.num=--*l;
+ else
+ o1.num=(*l)--;
+
+ p=&o1;
+ ret=1;
+ }
+ else
+ error=SYNTAX_ERROR;
+ break;
+ case (int)a_circle:
+ case (int)dollar:
+ if (pop_obj(&o1));
+ else if (REAL_ERROR) break;
+
+ if (*op == dollar)
+ delta = 1;
+ else
+ delta = 0;
+
+ if (o1.type==TYPE_NUM_VAR) {
+ o1.type=TYPE_NUM;
+ o1.num=*VAR[o1.num].num;
+ }
+
+ if (o1.type==TYPE_NUM) {
+ if (o1.num<-NUMVAR || o1.num>=NUMPARAM) {
+ error=OUT_RANGE_ERROR;
+ break;
+ }
+ o1.type= delta ? TYPE_TXT_VAR : TYPE_NUM_VAR;
+ p=&o1;
+ ret=1;
+ } else {
+ varnode *named_var;
+ char c;
+
+ if (o1.type==TYPE_TXT_VAR)
+ o1.txt = *VAR[o1.num].str;
+ else if (o1.type!=TYPE_TXT) {
+ error=SYNTAX_ERROR;
+ break;
+ }
+
+ if (o1.txt && (tmp=ptrdata(o1.txt)) &&
+ ((c=*tmp) == '_' || isalpha(c))) {
+ tmp++;
+ while ((c=*tmp) == '_' || isalnum(c))
+ tmp++;
+ }
+ if (!o1.txt || *tmp) {
+ error=INVALID_NAME_ERROR;
+ break;
+ }
+
+ if (!(named_var = *lookup_varnode(ptrdata(o1.txt), delta))) {
+ named_var = add_varnode(ptrdata(o1.txt), delta);
+ if (REAL_ERROR)
+ break;
+ if (opt_info) {
+ PRINTF("#new variable: %c%s\n", delta
+ ? '$' : '@', ptrdata(o1.txt));
+ }
+ }
+ o1.type= delta ? TYPE_TXT_VAR : TYPE_NUM_VAR;
+ p=&o1;
+ ret=1;
+ }
+ break;
+ case (int)pre_null:
+ case (int)post_null:
+ ret=1;
+ break;
+ default:
+ break;
+ }
+
+ if (REAL_ERROR) {
+ check_delete(&o2);
+ check_delete(&o1);
+ }
+
+ if (!REAL_ERROR) {
+ if (!ret)
+ error=NOT_DONE_ERROR;
+ else if (p) {
+ if (push_obj(p))
+ ;
+ else
+ check_delete(p);
+ }
+ }
+
+ if (REAL_ERROR)
+ return 0;
+
+ return ret;
+}
+
+static int whichfirst(operator *op1, operator *op2)
+{
+ int p1, p2;
+
+ p1=op_list[*op1].priority;
+ p2=op_list[*op2].priority;
+ if (p1!=p2)
+ return p1>p2 ? -1 : 1;
+
+ p1 = op_list[*op1].assoc == LEFT;
+ return p1 ? -1 : 1;
+}
+
+static int compare_and_unload(operator *op)
+{
+ int first=0;
+ operator new;
+
+ if (REAL_ERROR || stk.curr_op<0)
+ return 1;
+
+ while (stk.curr_op>=0 && pop_op(&new) && !REAL_ERROR &&
+ (first = whichfirst(&new, op)) == -1 &&
+ (first = 0, exe_op(&new))
+ );
+
+ if (!REAL_ERROR) {
+ if (!first)
+ return 1;
+ else
+ return push_op(&new);
+ } else
+ return 0;
+}
+
+static int _eval(int mindepth)
+{
+ operator op;
+ object obj;
+ char endreached = 0;
+
+ for (;;) {
+ memzero(&obj, sizeof(obj));
+
+ while (*line==' ') line++;
+ if (!*line || *line == CMDSEP)
+ endreached = 1;
+
+ while (check_operator(LEFT, &op, mindepth) && push_op(&op) &&
+ !endreached) {
+
+ if (error) return 0;
+ while (*line==' ') line++;
+ if (!*line || *line == CMDSEP)
+ endreached = 1;
+ }
+
+ if (!endreached && check_object(&obj) && push_obj(&obj));
+ else if (error) return 0;
+
+ while (*line==' ') line++;
+ if (!*line || *line == CMDSEP)
+ endreached = 1;
+
+ while (check_operator(RIGHT, &op, mindepth) && compare_and_unload(&op) &&
+ exe_op(&op) && depth>=mindepth && !endreached) {
+
+ if (error) return 0;
+ while (*line==' ')
+ line++;
+ if (!*line || *line == CMDSEP)
+ endreached = 1;
+ }
+ if (error) return 0;
+
+ if (endreached || depth < mindepth)
+ break;
+
+ if (check_operator(BINARY, &op, mindepth) &&
+ compare_and_unload(&op) && push_op(&op));
+ else if (error) return 0;
+ }
+ return 1;
+}
+
+int eval_any(long *lres, ptr *pres, char **what)
+{
+ int printmode;
+ long val;
+ ptr txt;
+ object res;
+
+ if (pres)
+ printmode = PRINT_AS_PTR;
+ else if (lres)
+ printmode = PRINT_AS_LONG;
+ else
+ printmode = PRINT_NOTHING;
+
+ error=0;
+ stk.curr_obj=stk.curr_op=-1;
+ line = *what;
+
+ depth = 0;
+ (void)_eval(0);
+
+ if (!error)
+ (void)pop_obj(&res);
+ if (error) {
+ if (opt_debug) {
+ PRINTF("#result not available\n");
+ }
+ } else if (printmode!=PRINT_NOTHING || opt_debug) {
+ if (res.type==TYPE_NUM || res.type==TYPE_NUM_VAR) {
+
+ val = res.type==TYPE_NUM ? res.num : *VAR[res.num].num;
+
+ if (printmode==PRINT_AS_PTR) {
+ *pres = ptrsetlen(*pres, LONGLEN);
+ if (!MEM_ERROR) {
+ sprintf(ptrdata(*pres), "%ld", val);
+ (*pres)->len = strlen(ptrdata(*pres));
+ }
+ } else if (printmode==PRINT_AS_LONG)
+ *lres=val;
+
+ if (opt_debug) {
+ if (error) {
+ PRINTF("#result not available\n");
+ } else {
+ PRINTF("#result: %ld\n", val);
+ }
+ }
+ } else {
+ txt = res.type==TYPE_TXT ? res.txt : *VAR[res.num].str;
+ if (printmode==PRINT_AS_PTR) {
+ if (txt && *ptrdata(txt)) {
+ if (res.type == TYPE_TXT)
+ /* shortcut! */
+ *pres = txt;
+ else
+ *pres = ptrcpy(*pres, txt);
+ } else
+ ptrzero(*pres);
+ }
+ if (opt_debug) {
+ if (error) {
+ PRINTF("#result not available\n");
+ } else if (txt && *ptrdata(txt)) {
+ PRINTF("#result: %s\n", ptrdata(txt));
+ } else {
+ PRINTF("#result empty\n");
+ }
+ }
+ }
+ }
+ *what=line;
+
+ if (!error) {
+ if (printmode==PRINT_AS_PTR && res.type == TYPE_TXT
+ && res.txt && ptrdata(res.txt))
+ /* shortcut! */
+ ;
+ else
+ check_delete(&res);
+ } else {
+ while (stk.curr_obj>=0) {
+ pop_obj(&res);
+ check_delete(&res);
+ }
+ res.type = 0;
+ }
+
+ if (res.type==TYPE_TXT_VAR)
+ res.type = TYPE_TXT;
+ else if (res.type==TYPE_NUM_VAR)
+ res.type = TYPE_NUM;
+
+ return res.type;
+}
+
+int evalp(ptr *res, char **what)
+{
+ return eval_any((long *)0, res, what);
+}
+
+int evall(long *res, char **what)
+{
+ return eval_any(res, (ptr *)0, what);
+}
+
+int evaln(char **what)
+{
+ return eval_any((long *)0, (ptr *)0, what);
+}
+
diff --git a/src/eval.h b/src/eval.h
new file mode 100644
index 0000000..d717103
--- /dev/null
+++ b/src/eval.h
@@ -0,0 +1,58 @@
+/* public things from eval.c */
+
+#ifndef _EVAL_H_
+#define _EVAL_H_
+
+#define STACK_OV_ERROR 1
+#define STACK_UND_ERROR 2
+#define DYN_STACK_OV_ERROR 3
+#define DYN_STACK_UND_ERROR 4
+#define SYNTAX_ERROR 5
+#define NO_OPERATOR_ERROR 6
+#define NO_VALUE_ERROR 7
+#define DIV_BY_ZERO_ERROR 8
+#define OUT_RANGE_ERROR 9
+#define MISSING_PAREN_ERROR 10
+#define MISMATCH_PAREN_ERROR 11
+#define INTERNAL_ERROR 12
+#define NOT_SUPPORTED_ERROR 13
+#define NOT_DONE_ERROR 14
+#define NO_MEM_ERROR 15
+#define MEM_LIMIT_ERROR 16
+#define MAX_LOOP_ERROR 17
+#define NO_NUM_VALUE_ERROR 18
+#define NO_STRING_ERROR 19
+#define NO_LABEL_ERROR 20
+#define MISSING_SEPARATOR_ERROR 21
+#define HISTORY_RECURSION_ERROR 22
+#define USER_BREAK 23
+#define OUT_OF_VAR_SPACE_ERROR 24
+#define UNDEFINED_VARIABLE_ERROR 25
+#define OUT_BASE_ERROR 26
+#define BAD_ATTR_ERROR 27
+#define INVALID_NAME_ERROR 28
+
+#define TYPE_NUM 1
+#define TYPE_TXT 2
+#define TYPE_NUM_VAR 3
+#define TYPE_TXT_VAR 4
+
+#define PRINT_NOTHING 0
+#define PRINT_AS_PTR 1
+#define PRINT_AS_LONG 2
+
+int eval_any(long *lres, ptr *pres, char **what);
+int evalp( ptr *pres, char **what);
+int evall(long *lres, char **what);
+int evaln( char **what);
+
+void print_error(int err_num);
+
+extern char *error_msg[];
+extern int error;
+
+#define REAL_ERROR (error && error != USER_BREAK)
+#define MEM_ERROR (error == NO_MEM_ERROR || error == MEM_LIMIT_ERROR)
+
+#endif /* _EVAL_H_ */
+
diff --git a/src/feature/regex.h b/src/feature/regex.h
new file mode 100644
index 0000000..d630b99
--- /dev/null
+++ b/src/feature/regex.h
@@ -0,0 +1,11 @@
+#ifndef _FEATURE_REGEX_H_
+#define _FEATURE_REGEX_H_
+
+#ifdef USE_REGEXP_PCREPOSIX
+# include <pcreposix.h>
+#elif defined(USE_REGEXP)
+# include <sys/types.h>
+# include <regex.h>
+#endif
+
+#endif /* _FEATURE_REGEX_H_ */
diff --git a/src/follow.c b/src/follow.c
new file mode 100644
index 0000000..09456c9
--- /dev/null
+++ b/src/follow.c
@@ -0,0 +1,170 @@
+/*
+ * follow.c -- interactively print an ASCII file.
+ *
+ * This program 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 2 of the License, or
+ * (at your option) any later version.
+ *
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <signal.h>
+#include <time.h>
+#include <unistd.h>
+#include <sys/time.h>
+#include <sys/types.h>
+
+#ifndef USE_SGTTY
+# ifdef APOLLO
+# include "/sys5.3/usr/include/sys/termio.h"
+# else
+/*
+ * including both termio.h and termios.h might be an overkill, and gives
+ * many warnings, but seems to be necessary at times. works anyway.
+ */
+# include <termios.h>
+# include <termio.h>
+# endif
+/* #else USE_SGTTY */
+#endif
+
+/*
+ * SunOS 4 doesn't have function headers and has the defs needed from
+ * ioctl.h in termios.h. Does it compile with USE_SGTTY?
+ */
+#if (defined(sun) && defined(sparc) && ! defined(__SVR4))
+extern int printf();
+#else
+# include <sys/ioctl.h>
+#endif
+
+#ifdef BSD_LIKE
+# include <sys/ioctl_compat.h>
+# define O_RAW RAW
+# define O_ECHO ECHO
+# define O_CBREAK CBREAK
+#endif
+
+#if defined(TCSETS) || defined(TCSETATTR)
+# ifndef TCSETS /* cc for HP-UX SHOULD define this... */
+# define TCSETS TCSETATTR
+# define TCGETS TCGETATTR
+# endif
+typedef struct termios termiostruct;
+#else
+# define TCSETS TCSETA
+# define TCGETS TCGETA
+typedef struct termio termiostruct;
+#endif
+
+#ifdef VSUSP
+# define O_SUSP VSUSP
+#else
+# ifdef SWTCH
+# define O_SUSP SWTCH
+# else
+# define O_SUSP SUSP
+# endif
+#endif
+
+/*int ioctl();*/
+
+#ifdef USE_SGTTY
+static struct sgttyb ttybsave;
+static struct tchars tcsave;
+static struct ltchars ltcsave;
+#else /* not USE_SGTTY */
+static termiostruct ttybsave;
+#endif /* USE_SGTTY */
+
+/*
+ * Terminal handling routines:
+ * These are one big mess of left-justified chicken scratches.
+ * It should be handled more cleanly...but unix portability is what it is.
+ */
+
+/*
+ * Set the terminal to character-at-a-time-without-echo mode, and save the
+ * original state in ttybsave
+ */
+void set_terminal()
+{
+#ifdef USE_SGTTY
+ struct sgttyb ttyb;
+ struct ltchars ltc;
+ ioctl(0, TIOCGETP, &ttybsave);
+ ioctl(0, TIOCGETC, &tcsave);
+ ioctl(0, TIOCGLTC, &ltcsave);
+ ttyb = ttybsave;
+ ttyb.sg_flags = (ttyb.sg_flags|O_CBREAK) & ~O_ECHO;
+ ioctl(0, TIOCSETP, &ttyb);
+ ltc = ltcsave;
+ ltc.t_suspc = -1;
+ ioctl(0, TIOCSLTC, &ltc);
+#else /* not USE_SGTTY */
+ termiostruct ttyb;
+ ioctl(0, TCGETS, &ttyb);
+ ttybsave = ttyb;
+ ttyb.c_lflag &= ~(ECHO|ICANON);
+ ttyb.c_cc[VTIME] = 0;
+ ttyb.c_cc[VMIN] = 1;
+ /* disable the special handling of the suspend key (handle it ourselves) */
+ ttyb.c_cc[O_SUSP] = 0;
+ ioctl(0, TCSETS, &ttyb);
+#endif /* USE_SGTTY */
+}
+
+/*
+ * Reset the terminal to its original state
+ */
+void reset_terminal()
+{
+#ifdef USE_SGTTY
+ ioctl(0, TIOCSETP, &ttybsave);
+ ioctl(0, TIOCSETC, &tcsave);
+ ioctl(0, TIOCSLTC, &ltcsave);
+#else /* not USE_SGTTY */
+ ioctl(0, TCSETS, &ttybsave);
+#endif /* USE_SGTTY */
+}
+
+int main(int argc, char *argv[]) {
+ FILE *f;
+ char c = 0, buf[512];
+ int d;
+
+ if (argc < 2) {
+ fprintf(stderr, "needed a file name\n");
+ exit(0);
+ }
+ f = fopen(argv[1], "r");
+ if (!f) {
+ fprintf(stderr, "unable to open %s\n", argv[1]);
+ exit(0);
+ }
+
+ set_terminal();
+ while(c!=0x1b) {
+ read(0, &c, 1);
+ if (c == 0x0a || c == 0x0d) {
+ if (fgets(buf, 512, f))
+ fputs(buf, stdout);
+ else
+ break;
+ }
+ else {
+ if ((d = fgetc(f)) != EOF)
+ putchar(d);
+ else
+ break;
+ }
+ fflush(stdout);
+ }
+ reset_terminal();
+ fputs("\033[0m\n", stdout);
+ return 0;
+}
+
diff --git a/src/list.c b/src/list.c
new file mode 100644
index 0000000..2652bda
--- /dev/null
+++ b/src/list.c
@@ -0,0 +1,675 @@
+/*
+ * list.c -- list utility functions.
+ *
+ * Copyright (C) 1998 by Massimiliano Ghilardi
+ *
+ * This program 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 2 of the License, or
+ * (at your option) any later version.
+ *
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <time.h>
+#include <sys/types.h>
+#include <sys/time.h>
+
+#include "defines.h"
+#include "main.h"
+#include "feature/regex.h"
+#include "utils.h"
+#include "cmd2.h"
+#include "tty.h"
+#include "eval.h"
+
+/*
+ * compare two times, return -1 if t1 < t2, 1 if t1 > t2, 0 if t1 == t2
+ */
+int cmp_vtime(vtime *t1, vtime *t2)
+{
+ int i;
+ i = t1->tv_sec < t2->tv_sec ? -1 : t1->tv_sec > t2->tv_sec ? 1 : 0;
+ if (!i)
+ i = t1->tv_usec < t2->tv_usec ? -1 : t1->tv_usec > t2->tv_usec ? 1 : 0;
+ return i;
+}
+
+/*
+ * add t2 to t1 (i.e. t1 += t2)
+ */
+void add_vtime(vtime *t1, vtime *t2)
+{
+ t1->tv_sec += t2->tv_sec;
+ if ((t1->tv_usec += t2->tv_usec) >= uSEC_PER_SEC) {
+ t1->tv_sec += t1->tv_usec / uSEC_PER_SEC;
+ t1->tv_usec %= uSEC_PER_SEC;
+ }
+}
+
+/*
+ * Return t1 - t2, in milliseconds
+ */
+long diff_vtime(vtime *t1, vtime *t2)
+{
+ return (t1->tv_sec - t2->tv_sec) * mSEC_PER_SEC +
+ (t1->tv_usec - t2->tv_usec) / uSEC_PER_mSEC;
+}
+
+int rev_sort(defnode *node1, defnode *node2)
+{
+ return -1;
+}
+
+/*
+ * standard ASCII comparison between nodes
+ */
+int ascii_sort(defnode *node1, defnode *node2)
+{
+ return strcmp(node1->sortfield, node2->sortfield);
+}
+
+int rev_ascii_sort(defnode *node1, defnode *node2)
+{
+ return strcmp(node2->sortfield, node1->sortfield);
+}
+
+
+/*
+ * comparison between times of execution of nodes
+ * (return -1 if node1->when < node2->when)
+ */
+int time_sort(defnode *node1, defnode *node2)
+{
+ return cmp_vtime(&((delaynode *)node1)->when, &((delaynode *)node2)->when);
+}
+
+/*
+ * reverse comparison between times of execution of nodes
+ * (return -1 if node1->when > node2->when)
+ */
+int rev_time_sort(defnode *node1, defnode *node2)
+{
+ return cmp_vtime(&((delaynode *)node2)->when, &((delaynode *)node1)->when);
+}
+
+/*
+ * compute the hash value of a name
+ */
+int hash(char *name, int optlen)
+{
+ int h = 0, i = 0;
+ if (optlen < 0)
+ optlen = strlen(name);
+ while (optlen-- > 0) {
+ h += ((*name++) ^ i) << i;
+ if (++i == LOG_MAX_HASH)
+ i = 0;
+ }
+ return (h + (h >> LOG_MAX_HASH) + (h >> (2*LOG_MAX_HASH))) & (MAX_HASH-1);
+}
+
+/*
+ * generic list node adding routine
+ */
+void add_node(defnode *newnode, defnode **base, function_sort sort)
+{
+ while((*base) && (!sort || (*sort)(newnode, *base) > 0))
+ base = &(*base)->next;
+ newnode->next = *base;
+ *base = newnode;
+}
+
+static void add_sortednode(sortednode *newnode, sortednode **base, function_sort sort)
+{
+ while((*base) && (!sort || (*sort)((defnode *)newnode, (defnode *)*base) > 0))
+ base = &(*base)->snext;
+ newnode->snext = *base;
+ *base = newnode;
+}
+
+void reverse_list(defnode **base)
+{
+ defnode *node = *base, *list = NULL, *tmp;
+ while (node) {
+ tmp = node->next;
+ node->next = list;
+ list = node;
+ node = tmp;
+ }
+ *base = list;
+}
+
+void reverse_sortedlist(sortednode **base)
+{
+ sortednode *node = *base, *list = NULL, *tmp;
+ while (node) {
+ tmp = node->snext;
+ node->snext = list;
+ list = node;
+ node = tmp;
+ }
+ *base = list;
+}
+
+static sortednode **selflookup_sortednode(sortednode *self, sortednode **base)
+{
+ sortednode **p = base;
+ while (*p && *p != self)
+ p = &(*p)->snext;
+ if (!*p) {
+ PRINTF("#internal error, selflookup_sortednode(\"%s\") failed!\n", self->sortfield);
+ error = INTERNAL_ERROR;
+ }
+ return p;
+}
+
+/*
+ * add a node to the alias list
+ */
+void add_aliasnode(char *name, char *subst)
+{
+ aliasnode *new = (aliasnode*)malloc(sizeof(aliasnode));
+ if (!new) {
+ errmsg("malloc");
+ return;
+ }
+
+ new->group = NULL;
+ new->active = 1;
+ new->name = my_strdup(name);
+ new->subst = my_strdup(subst);
+ if ((name && !new->name) || (subst && !new->subst)) {
+ errmsg("malloc");
+ if (new->name)
+ free(new->name);
+ if (new->subst)
+ free(new->subst);
+ free(new);
+ return;
+ }
+ add_node((defnode*)new, (defnode**)&aliases[hash(name,-1)], rev_sort);
+ add_sortednode((sortednode*)new, (sortednode**)&sortedaliases, rev_ascii_sort);
+}
+
+/*
+ * add a node to the marker list
+ */
+void add_marknode(char *pattern, int attrcode, char mbeg, char wild)
+{
+ marknode **p, *new = (marknode*)malloc(sizeof(marknode));
+ int i;
+ if (!new) {
+ errmsg("malloc");
+ return;
+ }
+ new->pattern = my_strdup(pattern);
+ new->attrcode = attrcode;
+ new->start = new->end = NULL;
+ new->mbeg = mbeg;
+ new->wild = wild;
+ if (!new->pattern) {
+ errmsg("malloc");
+ free(new);
+ return;
+ }
+#ifdef DO_SORT
+ add_node((defnode*)new, (defnode**)&markers, ascii_sort);
+#else
+ for (p=&markers, i=1; *p && (a_nice==0 || i<a_nice); p = &(*p)->next, i++)
+ ;
+ new->next = *p;
+ *p = new;
+#endif
+}
+
+/*
+ * add a node to the action list
+ */
+void add_actionnode(char *pattern, char *command, char *label, int active, int type, void *vregexp)
+{
+ actionnode **p, *new = (actionnode*)malloc(sizeof(actionnode));
+ int i;
+ if (!new) {
+ errmsg("malloc");
+ return;
+ }
+
+ new->group = NULL;
+ new->pattern = my_strdup(pattern);
+ new->command = my_strdup(command);
+ new->label = my_strdup(label);
+ new->active = active;
+ new->type = type;
+#ifdef USE_REGEXP
+ new->regexp = vregexp;
+#endif
+ if (!new->pattern || (command && !new->command) || (label && !new->label)) {
+ errmsg("malloc");
+ if (new->pattern)
+ free(new->pattern);
+ if (new->command)
+ free(new->command);
+ if (new->label)
+ free(new->label);
+ free(new);
+ return;
+ }
+#ifdef DO_SORT
+ add_node((defnode*)new, (defnode**)&actions, ascii_sort);
+#else
+ for (p=&actions, i=1; *p && (a_nice==0 || i<a_nice); p = &(*p)->next, i++)
+ ;
+ new->next = *p;
+ *p = new;
+#endif
+}
+
+/*
+ * add a node to the prompt list
+ */
+void add_promptnode(char *pattern, char *command, char *label, int active, int type, void *vregexp)
+{
+ promptnode **p, *new = (promptnode*)malloc(sizeof(promptnode));
+ int i;
+ if (!new) {
+ errmsg("malloc");
+ return;
+ }
+
+ new->pattern = my_strdup(pattern);
+ new->command = my_strdup(command);
+ new->label = my_strdup(label);
+ new->active = active;
+ new->type = type;
+#ifdef USE_REGEXP
+ new->regexp = vregexp;
+#endif
+ if (!new->pattern || (command && !new->command) || (label && !new->label)) {
+ errmsg("malloc");
+ if (new->pattern)
+ free(new->pattern);
+ if (new->command)
+ free(new->command);
+ if (new->label)
+ free(new->label);
+ free(new);
+ return;
+ }
+#ifdef DO_SORT
+ add_node((defnode*)new, (defnode**)&prompts, ascii_sort);
+#else
+ for (p=&prompts, i=1; *p && (a_nice==0 || i<a_nice); p = &(*p)->next, i++)
+ ;
+ new->next = *p;
+ *p = new;
+#endif
+}
+
+/*
+ * add a node to the keydef list
+ */
+void add_keynode(char *name, char *sequence, int seqlen, function_str funct, char *call_data)
+{
+ keynode *new = (keynode*)malloc(sizeof(keynode));
+ if (!new) {
+ errmsg("malloc");
+ return;
+ }
+ new->name = my_strdup(name);
+ if (!seqlen) seqlen = strlen(sequence);
+ new->sequence = (char *)malloc(seqlen + 1);
+ memmove(new->sequence, sequence, seqlen);
+ new->seqlen = seqlen;
+ new->funct = funct;
+ new->call_data = my_strdup(call_data);
+ if (!new->name || !new->sequence || (call_data && !new->call_data)) {
+ errmsg("malloc");
+ if (new->name)
+ free(new->name);
+ if (new->sequence)
+ free(new->sequence);
+ if (new->call_data)
+ free(new->call_data);
+ free(new);
+ return;
+ }
+ add_node((defnode*)new, (defnode**)&keydefs, ascii_sort);
+}
+
+/*
+ * add a node to the delayed command list
+ * is_dead == 1 means when < now (and so cannot be executed anymore)
+ */
+delaynode *add_delaynode(char *name, char *command, vtime *when, int is_dead)
+{
+ delaynode *new = (delaynode*)malloc(sizeof(delaynode));
+ if (!new) {
+ errmsg("malloc");
+ return NULL;
+ }
+ new->name = my_strdup(name);
+ new->command = my_strdup(command);
+ if (!new->name || (command && !new->command)) {
+ errmsg("malloc");
+ if (new->name)
+ free(new->name);
+ if (new->command)
+ free(new->command);
+ free(new);
+ return NULL;
+ }
+
+ new->when.tv_sec = when->tv_sec;
+ new->when.tv_usec = when->tv_usec;
+ if (is_dead)
+ add_node((defnode*)new, (defnode**)&dead_delays, rev_time_sort);
+ else
+ add_node((defnode*)new, (defnode**)&delays, time_sort);
+
+ return new;
+}
+
+/*
+ * add a node to named variables list
+ *
+ * do NOT allocate a ptr!
+ */
+varnode *add_varnode(char *name, int type)
+{
+ varnode *new;
+ int m, n;
+
+ if (type)
+ type = 1;
+
+ if (num_named_vars[type] >= max_named_vars) {
+ /* we are running low on var pointers. try to enlarge */
+ m = NUMTOT + max_named_vars;
+ n = NUMTOT + max_named_vars * 2;
+ if (n < 0) {
+ /* overflow */
+ print_error(error=OUT_OF_VAR_SPACE_ERROR);
+ return NULL;
+ }
+ else {
+ vars *newvar;
+ if ((newvar = (vars *)realloc(var, n*sizeof(vars) )))
+ ;
+ else if ((newvar = (vars *)malloc( n*sizeof(vars) ))) {
+ memmove(newvar, var, m * sizeof(vars));
+ free((void *)var);
+ } else {
+ errmsg("malloc");
+ return NULL;
+ }
+ var = newvar;
+ max_named_vars += n-m;
+ memzero(var + m, (n-m)*sizeof(vars));
+ }
+ }
+
+ new = (varnode*)malloc(sizeof(varnode));
+ if (!new) {
+ errmsg("malloc");
+ return NULL;
+ }
+ new->name = my_strdup(name);
+ if (name && !new->name) {
+ errmsg("malloc");
+ free(new);
+ return NULL;
+ }
+ new->num = 0;
+ new->str = (ptr)0;
+ new->index = m = NUMPARAM + num_named_vars[type];
+
+ if (type)
+ VAR[m].str = &new->str;
+ else
+ VAR[m].num = &new->num;
+ num_named_vars[type]++;
+
+ add_node((defnode*)new, (defnode**)&named_vars[type][hash(name,-1)], rev_sort);
+ add_sortednode((sortednode*)new, (sortednode**)&sortednamed_vars[type], rev_ascii_sort);
+ return new;
+}
+
+/*
+ * look up an alias node by name:
+ * return pointer to pointer to node or a pointer to NULL if nothing found
+ */
+aliasnode **lookup_alias(char *name)
+{
+ aliasnode **p = &aliases[hash(name,-1)];
+ while (*p && strcmp(name, (*p)->name))
+ p = &(*p)->next;
+ return p;
+}
+
+/*
+ * look up an action node by label:
+ * return pointer to pointer to node or a pointer to NULL if nothing found
+ */
+actionnode **lookup_action(char *label)
+{
+ actionnode **p = &actions;
+ while (*p && strcmp(label, (*p)->label))
+ p = &(*p)->next;
+ return p;
+}
+
+/*
+ * look up an action node by pattern:
+ * return pointer to pointer to node or a pointer to NULL if nothing found
+ */
+actionnode **lookup_action_pattern(char *pattern)
+{
+ actionnode **p = &actions;
+ while (*p && strcmp(pattern, (*p)->pattern))
+ p = &(*p)->next;
+ return p;
+}
+
+/*
+ * look up a prompt node by label:
+ * return pointer to pointer to node or a pointer to NULL if nothing found
+ */
+actionnode **lookup_prompt(char *label)
+{
+ promptnode **p = &prompts;
+ while (*p && strcmp(label, (*p)->label))
+ p = &(*p)->next;
+ return p;
+}
+
+/*
+ * look up an marker node by pattern:
+ * return pointer to pointer to node or a pointer to NULL if nothing found
+ */
+marknode **lookup_marker(char *pattern, char mbeg)
+{
+ marknode **p = &markers;
+ while (*p && (mbeg != (*p)->mbeg || strcmp(pattern, (*p)->pattern)))
+ p = &(*p)->next;
+ return p;
+}
+
+/*
+ * look up a key node by name:
+ * return pointer to pointer to node or a pointer to NULL if nothing found
+ */
+keynode **lookup_key(char *name)
+{
+ keynode **p = &keydefs;
+
+ while (*p && strcmp(name, (*p)->name))
+ p = &(*p)->next;
+ return p;
+}
+
+/*
+ * look up a delayed command node by label:
+ * return pointer to pointer to node or a pointer to NULL if nothing found
+ */
+delaynode **lookup_delay(char *name, int is_dead)
+{
+ delaynode **p = (is_dead ? &dead_delays : &delays);
+ while (*p && strcmp(name, (*p)->name))
+ p = &(*p)->next;
+ return p;
+}
+
+/*
+ * look up a named variable node by name:
+ * return pointer to pointer to node or a pointer to NULL if nothing found
+ */
+varnode **lookup_varnode(char *name, int type)
+{
+ varnode **p = &named_vars[type][hash(name,-1)];
+ while (*p && strcmp(name, (*p)->name))
+ p = &(*p)->next;
+ return p;
+}
+
+/*
+ * delete an alias node, given a pointer to its precessor's pointer
+ */
+void delete_aliasnode(aliasnode **base)
+{
+ aliasnode *p = *base;
+ *base = p->next;
+ if (*(base = (aliasnode**)selflookup_sortednode
+ ((sortednode*)p, (sortednode**)&sortedaliases)))
+ *base = p->snext;
+ else
+ return;
+ if (p->name) free(p->name);
+ if (p->subst) free(p->subst);
+ free((void*)p);
+}
+
+/*
+ * delete an action node, given a pointer to its precessor's pointer
+ */
+void delete_actionnode(actionnode **base)
+{
+ actionnode *p = *base;
+ if (p->pattern) free(p->pattern);
+ if (p->command) free(p->command);
+ if (p->label) free(p->label);
+#ifdef USE_REGEXP
+ if (p->type == ACTION_REGEXP && p->regexp) {
+ regfree((regex_t *)p->regexp);
+ free(p->regexp);
+ }
+#endif
+ *base = p->next;
+ free((void*)p);
+}
+
+/*
+ * delete an prompt node, given a pointer to its precessor's pointer
+ */
+void delete_promptnode(promptnode **base)
+{
+ promptnode *p = *base;
+ if (p->pattern) free(p->pattern);
+ if (p->command) free(p->command);
+ if (p->label) free(p->label);
+#ifdef USE_REGEXP
+ if (p->type == ACTION_REGEXP && p->regexp) {
+ regfree((regex_t *)p->regexp);
+ free(p->regexp);
+ }
+#endif
+ *base = p->next;
+ free((void*)p);
+}
+
+/*
+ * delete an marker node, given a pointer to its precessor's pointer
+ */
+void delete_marknode(marknode **base)
+{
+ marknode *p = *base;
+ if (p->pattern) free(p->pattern);
+ *base = p->next;
+ free((void*)p);
+}
+
+/*
+ * delete a keydef node, given a pointer to its precessor's pointer
+ */
+void delete_keynode(keynode **base)
+{
+ keynode *p = *base;
+ if (p->name) free(p->name);
+ if (p->sequence) free(p->sequence);
+ if (p->call_data) free(p->call_data);
+ *base = p->next;
+ free((void*)p);
+}
+
+/*
+ * delete a delayed command node, given a pointer to its precessor's pointer
+ */
+void delete_delaynode(delaynode **base)
+{
+ delaynode *p = *base;
+ if (p->name) free(p->name);
+ if (p->command) free(p->command);
+ *base = p->next;
+ free((void*)p);
+}
+
+/*
+ * delete a named variable node, given a pointer to its precessor's pointer
+ */
+void delete_varnode(varnode **base, int type)
+{
+ varnode *p = *base;
+ int idx = p->index, i, n;
+
+ *base = p->next;
+ if (*(base = (varnode**)selflookup_sortednode
+ ((sortednode*)p, (sortednode**)&sortednamed_vars[type])))
+ *base = p->snext;
+ else
+ return;
+ if (p->name) free(p->name);
+ if (type && p->str) ptrdel(p->str);
+ free((void*)p);
+
+ i = NUMPARAM + --num_named_vars[type];
+
+ if (idx == i)
+ return;
+
+ /* now I must fill the hole in var[idx].*** */
+
+ for (n = 0; n < MAX_HASH; n++)
+ for (p = named_vars[type][n]; p; p = p->next)
+ if (p->index == i) {
+ n = MAX_HASH;
+ break;
+ }
+
+ if (!p) { /* should NEVER happen */
+ print_error(error=UNDEFINED_VARIABLE_ERROR);
+ return;
+ }
+
+ p->index = idx;
+
+ if (type) {
+ VAR[idx].str = &p->str;
+ VAR[ i ].str = NULL;
+ } else {
+ VAR[idx].num = &p->num;
+ VAR[ i ].num = NULL;
+ }
+}
diff --git a/src/list.h b/src/list.h
new file mode 100644
index 0000000..c336049
--- /dev/null
+++ b/src/list.h
@@ -0,0 +1,47 @@
+/* public definitions from list.c */
+
+#ifndef _LIST_H_
+#define _LIST_H_
+
+int cmp_vtime(vtime *t1, vtime *t2);
+void add_vtime(vtime *t1, vtime *t2);
+long diff_vtime(vtime *t1, vtime *t2);
+
+int rev_sort(defnode *node1, defnode *node2);
+int ascii_sort(defnode *node1, defnode *node2);
+int rev_ascii_sort(defnode *node1, defnode *node2);
+int time_sort(defnode *node1, defnode *node2);
+int rev_time_sort(defnode *node1, defnode *node2);
+
+int hash(char *name);
+
+void add_node(defnode *newnode, defnode **base, function_sort sort);
+void reverse_sortedlist(sortednode **base);
+
+void add_aliasnode(char *name, char *subst);
+void add_actionnode(char *pattern, char *command, char *label, int active, int type, void *qregexp);
+void add_promptnode(char *pattern, char *command, char *label, int active, int type, void *qregexp);
+void add_marknode(char *pattern, int attrcode, char mbeg, char wild);
+void add_keynode(char *name, char *sequence, int seqlen, function_str funct, char *call_data);
+delaynode *add_delaynode(char *name, char *command, vtime *when, int is_dead);
+varnode *add_varnode(char *name, int type);
+
+aliasnode **lookup_alias(char *name);
+actionnode **lookup_action(char *label);
+actionnode **lookup_prompt(char *label);
+actionnode **lookup_action_pattern(char *pattern);
+marknode **lookup_marker(char *pattern, char mbeg);
+keynode **lookup_key(char *name);
+delaynode **lookup_delay(char *name, int is_dead);
+varnode **lookup_varnode(char *name, int type);
+
+void delete_aliasnode(aliasnode **base);
+void delete_actionnode(actionnode **base);
+void delete_promptnode(promptnode **base);
+void delete_marknode(marknode **base);
+void delete_keynode(keynode **base);
+void delete_delaynode(delaynode **base);
+void delete_varnode(varnode **base, int type);
+
+#endif /* _LIST_H_ */
+
diff --git a/src/log.c b/src/log.c
new file mode 100644
index 0000000..b831b6f
--- /dev/null
+++ b/src/log.c
@@ -0,0 +1,329 @@
+/*
+ * log.c -- code for #movie / #capture backbuffering
+ * and code for reprint-on-prompt
+ *
+ * Copyright (C) 1998 by Massimiliano Ghilardi
+ *
+ * This program 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 2 of the License, or
+ * (at your option) any later version.
+ *
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <time.h>
+
+#include "defines.h"
+#include "main.h"
+#include "log.h"
+#include "tty.h"
+#include "list.h"
+#include "utils.h"
+
+vtime movie_last; /* time movie_file was last written */
+FILE *capturefile = (FILE *)NULL; /* capture file or NULL */
+FILE *moviefile = (FILE *)NULL; /* movie file or NULL */
+FILE *recordfile = (FILE *)NULL; /* record file or NULL */
+
+
+static char *datalist; /* circular string list */
+static int datastart = 0; /* index to first string start */
+static int dataend = 0; /* index one past last string end */
+static int datasize = 0; /* size of circular string list */
+
+#define DATALEFT (datastart > dataend ? datastart - dataend - 2 : datastart ? \
+ MAX2(datastart - 1, datasize - dataend - 1) : datasize - dataend - 1)
+
+typedef struct logentry {
+ enum linetype kind;
+ long msecs; /* millisecs to sleep if kind == SLEEP */
+ char *line; /* pointer to string in "datalist" circular buffer */
+} logentry;
+
+logentry *loglist; /* circular (pointer to string) list */
+
+static int logstart = 0; /* index to first loglist used */
+static int logend = 0; /* index one past last loglist used */
+static int logsize = 0; /* size of circular (pointer to string) list */
+
+#define LOGFULL (logend == (logstart ? logstart - 1 : logsize - 1))
+
+static char *names[] = { NULL, "line", "prompt", "sleep" };
+
+/*
+ * flush a single buffer line
+ */
+static void log_flushline(int i)
+{
+ if (capturefile)
+ fprintf(capturefile, "%s%s",
+ loglist[i].line, loglist[i].kind == LINE ? "\n" : "");
+ if (moviefile) {
+ if (loglist[i].msecs)
+ fprintf(moviefile, "%s %ld\n",
+ names[SLEEP], loglist[i].msecs);
+ fprintf(moviefile, "%s %s\n",
+ names[loglist[i].kind], loglist[i].line);
+ }
+}
+
+/*
+ * remove the oldest (first) line from the buffer
+ */
+static void log_clearline(void)
+{
+ int next;
+
+ if (logstart == logend)
+ return;
+ log_flushline(logstart);
+
+ next = (logstart + 1) % logsize;
+ if (next == logend)
+ datastart = dataend = logstart = logend = 0;
+ else
+ datastart = loglist[next].line - datalist, logstart = next;
+}
+
+/*
+ * remove an initial SLEEP from the buffer
+ */
+void log_clearsleep(void)
+{
+ if (logstart != logend)
+ loglist[logstart].msecs = 0;
+}
+
+/*
+ * flush the buffer
+ */
+void log_flush(void)
+{
+ int i = logstart;
+ while (i != logend) {
+ log_flushline(i);
+ if (++i == logsize)
+ i = 0;
+ }
+ datastart = dataend = logstart = logend = 0;
+}
+
+int log_getsize(void)
+{
+ return datasize;
+}
+
+static void log_reset(void)
+{
+ if (datasize) {
+ if (datalist) free(datalist);
+ if (loglist) free(loglist);
+ loglist = NULL;
+ datalist = NULL;
+ logsize = datasize = 0;
+ }
+}
+
+
+void log_resize(int newsize)
+{
+ if (newsize && newsize < 1000) {
+ PRINTF("#buffer size must be 0 (zero) or >= 1000\n");
+ return;
+ }
+
+ if (newsize == datasize)
+ return;
+
+ log_flush();
+ log_reset();
+ if (newsize) {
+ datalist = (char *)malloc(newsize);
+ if (!datalist) { log_reset(); errmsg("malloc"); return; }
+
+ loglist = (logentry *)malloc(newsize/16*sizeof(logentry));
+ if (!loglist) { log_reset(); errmsg("malloc"); return; }
+
+ datasize = newsize;
+ logsize = newsize / 16;
+ }
+ if (opt_info) {
+ PRINTF("#buffer resized to %d bytes%s\n", newsize, newsize ? "" : " (disabled)");
+ }
+}
+
+/*
+ * add a single line to the buffer
+ */
+static void log_writeline(char *line, int len, int kind, long msecs)
+{
+ int dst;
+
+ if (++len >= datasize) {
+ PRINTF("#line too long, discarded from movie/capture buffer\n");
+ return;
+ }
+ while (LOGFULL || DATALEFT < len)
+ log_clearline();
+ /* ok, now we know there IS enough space */
+
+ if (datastart >= dataend /* is == iff loglist is empty */
+ || datasize - dataend > len)
+ dst = dataend;
+ else
+ dst = 0;
+
+ memcpy(loglist[logend].line = datalist + dst, line, len - 1);
+ datalist[dst + len - 1] = '\0';
+
+ loglist[logend].kind = kind;
+ loglist[logend].msecs = msecs;
+
+ if ((dataend = dst + len) == datasize)
+ dataend = 0;
+
+ if (++logend == logsize)
+ logend = 0;
+}
+
+/*
+ * write to #capture / #movie buffer
+ */
+void log_write(char *str, int len, int newline)
+{
+ char *next;
+ long diff;
+ int i, last = 0;
+
+ if (!datasize && !moviefile && !capturefile)
+ return;
+
+ update_now();
+ diff = diff_vtime(&now, &movie_last);
+ movie_last = now;
+
+ do {
+ if ((next = memchr(str, '\n', len))) {
+ i = next - str;
+ newline = 1;
+ } else {
+ i = len;
+ last = 1;
+ }
+
+ if (datasize)
+ log_writeline(str, i, last && !newline ? PROMPT : LINE, diff);
+ else {
+ if (moviefile) {
+ if (diff)
+ fprintf(moviefile, "%s %ld\n",
+ names[SLEEP], diff);
+ fprintf(moviefile, "%s %.*s\n",
+ names[last && !newline ? PROMPT : LINE], i, str);
+ }
+ if (capturefile) {
+ fwrite(str, 1, i, capturefile);
+ if (newline) {
+ const char nl[1] = "\n";
+ fwrite(nl, 1, 1, capturefile);
+ }
+ }
+ }
+ diff = 0;
+ if (next) {
+ len -= next + 1 - str;
+ str = next + 1;
+ }
+ } while (next && len > 0);
+}
+
+static char reprintlist[BUFSIZE]; /* circular string list */
+static int reprintstart = 0; /* index to first string start */
+static int reprintend = 0; /* index one past last string end */
+static int reprintsize = BUFSIZE; /* size of circular string list */
+
+#define REPRINTLEFT (reprintstart > reprintend ? reprintstart - reprintend - 1 : reprintstart ? \
+ MAX2(reprintstart - 1, reprintsize - reprintend - 1) : reprintsize - reprintend - 1)
+
+static char *replist[BUFSIZE/8]; /* circular (pointer to string) list */
+
+static int repstart = 0; /* index to first replist used */
+static int repend = 0; /* index one past last replist used */
+static int repsize = BUFSIZE/8; /* size of circular (pointer to string) list */
+
+#define REPFULL (repend == (repstart ? repstart - 1 : repsize - 1))
+
+/*
+ * remove the oldest (first) line from reprintlist
+ */
+static void reprint_clearline(void)
+{
+ int next;
+
+ if (repstart == repend)
+ return;
+
+ next = (repstart + 1) % repsize;
+ if (next == repend)
+ reprintstart = reprintend = repstart = repend = 0;
+ else
+ reprintstart = replist[next] - reprintlist, repstart = next;
+}
+
+void reprint_clear(void)
+{
+ reprintstart = reprintend = repstart = repend = 0;
+}
+
+/*
+ * add a single line to the buffer
+ */
+void reprint_writeline(char *line)
+{
+ int len = strlen(line) + 1;
+ int dst;
+
+ if (!opt_reprint || (promptlen && prompt_status != -1))
+ /*
+ * if prompt is valid, we'll never have to reprint, as we
+ * _already_ printed the command at the right moment
+ */
+ return;
+
+ if (len >= reprintsize) {
+ PRINTF("#line too long, discarded from prompt reprint buffer\n");
+ return;
+ }
+ while (REPFULL || REPRINTLEFT < len)
+ reprint_clearline();
+ /* ok, now we know there IS enough space */
+
+ if (reprintstart >= reprintend /* is == iff replist is empty */
+ || reprintsize - reprintend > len)
+ dst = reprintend;
+ else
+ dst = 0;
+
+ memcpy(replist[repend] = reprintlist + dst, line, len - 1);
+ reprintlist[dst + len - 1] = '\0';
+
+ if ((reprintend = dst + len) == reprintsize)
+ reprintend = 0;
+
+ if (++repend == repsize)
+ repend = 0;
+}
+
+char *reprint_getline(void)
+{
+ char *line = NULL;
+ if (opt_reprint && repend != repstart)
+ line = replist[repstart];
+ reprint_clearline();
+ return line;
+}
+
diff --git a/src/log.h b/src/log.h
new file mode 100644
index 0000000..a5822b4
--- /dev/null
+++ b/src/log.h
@@ -0,0 +1,22 @@
+/* public things from log.c */
+
+#ifndef _LOG_H_
+#define _LOG_H_
+
+enum linetype { EMPTY = 0, LINE = 1, PROMPT = 2, SLEEP = 3 };
+
+extern FILE *capturefile, *recordfile, *moviefile;
+extern vtime movie_last;
+
+void log_clearsleep(void);
+void log_flush(void);
+int log_getsize(void);
+void log_resize(int newsize);
+void log_write(char *str, int len, int newline);
+
+void reprint_writeline(char *line);
+char *reprint_getline(void);
+void reprint_clear(void);
+
+#endif /* _LOG_H_ */
+
diff --git a/src/main.c b/src/main.c
new file mode 100644
index 0000000..28d1592
--- /dev/null
+++ b/src/main.c
@@ -0,0 +1,2084 @@
+/*
+ * powwow -- mud client with telnet protocol
+ *
+ * Copyright (C) 1998,2000,2002 by Massimiliano Ghilardi
+ *
+ * This program 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 2 of the License, or
+ * (at your option) any later version.
+ *
+ *
+ * History:
+ *
+ * Initially inspired to the Tintin client by Peter Unold,
+ * Powwow contains no Tintin code.
+ * The original program Cancan, written by Mattias Engdegård (Yorick)
+ * (f91-men@nada.kth.se) 1992-94,
+ * was greatly improved upon by Vivriel, Thuzzle and Ilie and then
+ * transformed from Cancan into Powwow by Cosmos who worked
+ * to make it yet more powerful.
+ * AmigaDOS porting attempt by Fror.
+ * Many new features added by Dain.
+ * As usual, all the developers are in debt to countless users
+ * for suggestions and debugging.
+ * Maintance was taken over by Steve Slaven (bpk@hoopajoo.net) in 2005
+ */
+
+/*
+ * Set this to whatever you like
+ *
+ * #define POWWOW_DIR "/home/gustav/powwow"
+ */
+
+#ifdef USE_LOCALE
+ #define POWWOW_HACKERS "Yorick, Vivriel, Thuzzle, Ilie, Fr\363r, D\341in"
+ #define COPYRIGHT "\251"
+#else
+ #define POWWOW_HACKERS "Yorick, Vivriel, Thuzzle, Ilie, Fror, Dain"
+ #define COPYRIGHT "Copyright"
+#endif
+
+#define POWWOW_VERSION VERSION \
+ ", " COPYRIGHT " 2000-2005 by Cosmos\n" \
+ COPYRIGHT " 2005 by bpk - http://hoopajoo.net\n" \
+ "(contributions by " POWWOW_HACKERS ")\n"
+
+#define HELPNAME "powwow.help"
+#define COPYNAME "COPYING"
+
+#ifndef POWWOW_DIR
+# define POWWOW_DIR "./"
+#endif
+
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <fcntl.h>
+#include <ctype.h>
+#include <errno.h>
+#include <time.h>
+
+#include <sys/param.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <sys/time.h>
+#include <sys/wait.h>
+#include <memory.h>
+#include <unistd.h>
+
+#ifdef USE_LOCALE
+#include <locale.h>
+#endif
+
+/* are these really needed? */
+extern int errno;
+extern int select();
+
+#include "defines.h"
+#include "main.h"
+#include "feature/regex.h"
+#include "utils.h"
+#include "beam.h"
+#include "cmd.h"
+#include "cmd2.h"
+#include "edit.h"
+#include "map.h"
+#include "list.h"
+#include "tcp.h"
+#include "tty.h"
+#include "eval.h"
+#include "log.h"
+
+/* local function declarations */
+#ifdef MOTDFILE
+static void printmotd(void);
+#endif
+static void mainloop(void);
+static void exec_delays(void);
+static void prompt_reset_iac(void);
+static void get_remote_input(void);
+static void get_user_input(void);
+
+static int search_action_or_prompt(char *line, char clearline, char copyprompt);
+#define search_action(line, clearline) search_action_or_prompt((line), (clearline), 0)
+#define search_prompt(line, copyprompt) search_action_or_prompt((line), 0, (copyprompt)+1)
+
+static void set_params(char *line, int *match_s, int *match_e);
+static void parse_commands(char *command, char *arg);
+static int subst_param(ptr *buf, char *src);
+static int jit_subst_vars(ptr *buf, char *src);
+
+
+/* GLOBALS */
+static char *helpname = HELPNAME;
+static char *copyname = COPYNAME;
+
+long received = 0; /* amount of data received from remote host */
+long sent = 0; /* amount of data sent to remote host */
+
+volatile char confirm = 0; /* 1 if just tried to quit */
+int history_done = 0; /* number of recursive #history commands */
+int prompt_status = 0; /* prompt status: 0 = ready -> nothing to do;
+ * 1 if internal echo -> must redraw;
+ * -1 if something sent to MUD -> waiting for it.
+ */
+int line_status = 0; /* input line status: 0 = ready -> nothing to do;
+ * 1 if printed something -> must redraw.
+ */
+
+int limit_mem = 0; /* if !=0, max len of a string or text */
+
+char opt_echo = 1; /* 1 if text sent to MUD must be echoed */
+char opt_keyecho = 1; /* 1 if binds must be echoed */
+char opt_info = 1; /* 0 if internal messages are suppressed */
+char opt_exit = 0; /* 1 to autoquit when closing last conn. */
+char opt_history; /* 1 if to save also history */
+char opt_words = 0; /* 1 if to save also word completion list */
+char opt_compact = 0; /* 1 if to clear prompt between remote messages */
+char opt_debug = 0; /* 1 if to echo every line before executing it */
+char opt_speedwalk = 0; /* 1 = speedwalk on */
+char opt_wrap = 0; /* 1 = word wrap active */
+char opt_autoprint = 0; /* 1 = automatically #print lines matched by actions */
+char opt_reprint = 0; /* 1 = reprint sent commands when we get a prompt */
+char opt_sendsize = 0; /* 1 = send term size upon connect */
+char opt_autoclear = 1; /* 1 = clear input line before executing commands
+ * from spawned programs.
+ * if 0, spawned progs must #clear before printing
+ */
+
+char hostname[BUFSIZE];
+int portnumber;
+static char powwow_dir[BUFSIZE]; /* default path to definition files */
+char deffile[BUFSIZE]; /* name and path of definition file */
+char helpfile[BUFSIZE]; /* name and path of help file */
+char copyfile[BUFSIZE]; /* name and path of copyright file */
+aliasnode *aliases[MAX_HASH]; /* head of alias hash list */
+aliasnode *sortedaliases; /* head of (ASCII) sorted alias list */
+actionnode *actions; /* head of action list */
+promptnode *prompts; /* head of prompt list */
+marknode *markers; /* head of mark list */
+int a_nice = 0; /* default priority of new actions/marks */
+keynode *keydefs; /* head of key binding list */
+delaynode *delays; /* head of delayed commands list */
+delaynode *dead_delays; /* head of dead-delayed commands list */
+
+varnode *named_vars[2][MAX_HASH]; /* head of named variables hash list */
+varnode *sortednamed_vars[2]; /* head of (ASCII) sorted named variables list */
+int max_named_vars = 100; /* max number of named vars (grows as needed) */
+int num_named_vars[2]; /* number of named variables actually used */
+
+static param_stack paramstk; /* stack of local unnamed vars */
+static unnamedvar global_var[NUMTOT]; /* global unnamed vars */
+
+vars *var; /* vector of all vars */
+
+ptr globptr[2]; /* global ptr buffer */
+char globptrok = 1|2; /* x&i = 0 if globptr[i] is in use */
+
+varnode *prompt; /* $prompt is always set */
+ptr marked_prompt; /* $prompt with marks added */
+static varnode *last_line; /* $line is always set to
+ * the last line processed */
+
+vtime now; /* current time */
+int now_updated; /* current time is up to date */
+vtime start_time; /* time of powwow timer startup */
+vtime ref_time; /* time corresponding to timer == 0 */
+
+function_any last_edit_cmd; /* GH: keep track of for repeated cmds */
+
+clock_t start_clock, cpu_clock;
+
+char initstr[BUFSIZE]; /* initial string to send on connect */
+
+int linemode = 0; /* line mode flags (LM_* in main.h) */
+
+/* for line editing */
+int cols=80, lines=24; /* screen size */
+int cols_1=79; /* == cols if tty_wrapglitch, == cols-1 otherwise */
+int olines; /* previous screen size */
+int col0; /* input line offset (= printstrlen of prompt) */
+int line0; /* screen line where the input line starts */
+char edbuf[BUFSIZE]; /* line editing buffer */
+int edlen; /* length of current input line */
+int pos = 0; /* cursor position in line */
+char surely_isprompt = 0; /* !=0 if last #prompt set #isprompt */
+char verbatim = 0; /* 1 = don't expand aliases or process semicolons */
+char prefixstr[BUFSIZE]; /* inserted in the editing buffer each time */
+char inserted_next[BUFSIZE];/* inserted in buffer just once */
+char flashback = 0; /* cursor is on excursion and should be put back */
+int excursion; /* where the excursion is */
+char edattrbeg[CAPLEN]; /* starting input line attributes */
+char edattrend[CAPLEN]; /* ending input line attributes */
+int edattrbg; /* input line attributes do change bg color */
+
+/* signals handling */
+volatile int sig_pending, sig_winch_got, sig_chld_got;
+
+
+/* GH: different ID characters for different action types */
+/*
+ * Cosmos: they are hardcoded in cmd2.c, function parse_action()
+ * so don't change them.
+ */
+char action_chars[ACTION_TYPES] = { '>', '%' };
+
+/* GH: different delimeter modes */
+char *delim_list[] = { " ;", " <>!=(),;\"'{}[]+-/*%", 0 };
+int delim_len [] = { 2 , 21 , 0 };
+char *delim_name[] = { "normal", "program", "custom" };
+int delim_mode = DELIM_NORMAL;
+
+/* Group delimiter */
+char *group_delim;
+
+int main(int argc, char **argv)
+{
+ char *p;
+ int i;
+ int read_file = 0; /* GH: if true, powwow was started with
+ * a file argument, and initstr shall be ran */
+
+#ifdef USE_LOCALE
+ if (!setlocale(LC_ALL, "")) {
+ fprintf(stderr, "Failed setlocale(LC_ALL, \"\")\n");
+ }
+#endif
+
+ /* initializations */
+ initstr[0] = 0;
+ memzero(conn_list, sizeof(conn_list));
+ group_delim = my_strdup( "@" );
+
+ update_now();
+ ref_time = start_time = movie_last = now;
+
+ start_clock = cpu_clock = clock();
+#ifndef NO_RANDOM
+ init_random((int)now.tv_sec);
+#endif
+
+ initialize_cmd();
+
+ if ((p = getenv("POWWOWDIR"))) {
+ strcpy(powwow_dir, p);
+ if (powwow_dir[strlen(powwow_dir) - 1] != '/')
+ strcat(powwow_dir, "/");
+ } else
+ powwow_dir[0] = '\0';
+
+ if ((p = getenv("POWWOWHELP")))
+ strcpy(helpfile, p);
+ else if (powwow_dir[0])
+ strcpy(helpfile, powwow_dir);
+ else
+ strcpy(helpfile, POWWOW_DIR);
+
+ if (helpfile[strlen(helpfile) - 1] != '/')
+ strcat(helpfile, "/");
+ strcat(helpfile, helpname);
+ if (access(helpfile, R_OK) == -1 && !access(helpname, R_OK))
+ strcpy(helpfile, helpname);
+
+ if (powwow_dir[0])
+ strcpy(copyfile, powwow_dir);
+ else
+ strcpy(copyfile, POWWOW_DIR);
+ if (copyfile[strlen(copyfile) - 1] != '/')
+ strcat(copyfile, "/");
+ strcat(copyfile, copyname);
+ if (access(copyfile, R_OK) == -1 && !access(copyname, R_OK))
+ strcpy(copyfile, copyname);
+
+ /* initialize variables */
+ if ((var = (vars *)malloc(sizeof(vars)*(NUMTOT+max_named_vars)))) {
+ for (i=0; i<NUMTOT; i++) {
+ var[i].num = &global_var[i].num;
+ var[i].str = &global_var[i].str;
+ }
+ } else
+ syserr("malloc");
+
+ /* stack is empty */
+ paramstk.curr = 0;
+
+ /* allocate permanent variables */
+ if ((prompt = add_varnode("prompt", 1))
+ && (prompt->str = ptrnew(PARAMLEN))
+ && (marked_prompt = ptrnew(PARAMLEN))
+ && (last_line = add_varnode("last_line", 1))
+ && (last_line->str = ptrnew(PARAMLEN))
+ && (globptr[0] = ptrnew(PARAMLEN))
+ && (globptr[1] = ptrnew(PARAMLEN))
+ && !MEM_ERROR)
+ ;
+ else
+ syserr("malloc");
+
+
+ /*
+ ptr_bootstrap();
+ utils_bootstrap();
+ beam_bootstrap();
+ cmd_bootstrap();
+ map_bootstrap();
+ eval_bootstrap();
+ list_bootstrap();
+ tcp_bootstrap();
+ */
+ edit_bootstrap();
+ tty_bootstrap();
+
+#ifdef MOTDFILE
+ printmotd();
+#endif
+
+ printver();
+
+ if (argc == 1) {
+ tty_printf(
+"\nPowwow comes with ABSOLUTELY NO WARRANTY; for details type \"#help warranty\".\n\
+This is free software, and you are welcome to redistribute it\n\
+under certain conditions; type \"#help copyright\" for details.\n"
+ );
+ }
+
+ if (argc == 1 || argc == 3) {
+ tty_add_initial_binds();
+ tty_add_walk_binds();
+ } else if (argc == 2 || argc == 4) {
+ /*
+ * assuming first arg is definition file name
+ * If three args, first is definition file name,
+ * second and third are hostname and port number
+ * (they overwrite the ones in definition file)
+ */
+ set_deffile(argv[1]);
+
+ if (access(deffile,R_OK) == -1 || access(deffile,W_OK) == -1) {
+ char portnum[INTLEN];
+ tty_printf("Creating %s\nHost name :", deffile);
+ tty_flush();
+ tty_gets(hostname, BUFSIZE);
+ if (hostname[0] == '\n')
+ hostname[0] = '\0';
+ else
+ strtok(hostname, "\n");
+ tty_puts("Port number:");
+ tty_flush();
+ tty_gets(portnum, INTLEN);
+ portnumber = atoi(portnum);
+ tty_add_initial_binds();
+ tty_add_walk_binds();
+ limit_mem = 1048576;
+ if (save_settings() < 0)
+ exit(0);
+ } else if (read_settings() < 0)
+ exit(0);
+ else
+ read_file = 1;
+ }
+ if (argc == 3 || argc == 4) {
+ /* assume last two args are hostname and port number */
+ my_strncpy(hostname, argv[argc - 2], BUFSIZE-1);
+ portnumber = atoi(argv[argc - 1]);
+ }
+
+ signal_start();
+ tty_start();
+
+ tty_puts(tty_clreoscr);
+ tty_putc('\n');
+ tty_gotoxy(col0 = 0, lines - 2);
+ tty_puts("Type #help for help.\n");
+ line0 = lines - 1;
+
+ FD_ZERO(&fdset);
+ FD_SET(tty_read_fd, &fdset);
+
+ if (*hostname)
+ tcp_open("main", (*initstr ? initstr : NULL), hostname, portnumber);
+
+ if (read_file && !*hostname && *initstr) {
+ parse_instruction(initstr, 0, 0, 1);
+ history_done = 0;
+ }
+
+ confirm = 0;
+
+ mainloop();
+
+ /* NOTREACHED */
+ return 0;
+}
+
+/*
+ * show current version
+ */
+void printver(void)
+{
+ tty_printf("Powwow version %s\nOptions: %s%s\n", POWWOW_VERSION,
+#ifdef USE_VT100
+ "vt100-only,"
+#else
+ "termcaps,"
+#endif
+#ifdef USE_SGTTY
+ " BSD sgtty,"
+#else
+ " termios,"
+#endif
+#ifdef USE_REGEXP
+ " regexp "
+ #ifdef USE_REGEXP_PCREPOSIX
+ "(pcreposix)"
+ #else
+ "(libc)"
+ #endif
+ ","
+#else
+ " no regexp,"
+#endif
+#ifdef USE_LOCALE
+ " locale,"
+#endif
+#ifdef HAVE_LIBDL
+ " modules,"
+#endif
+ ,
+#if __STDC__
+ " compiled " __TIME__ " " __DATE__
+#else
+ " uknown compile date"
+#endif
+ );
+}
+
+#ifdef MOTDFILE
+/*
+ * print the message of the day if present
+ */
+static void printmotd(void)
+{
+ char line[BUFSIZE];
+ FILE *f = fopen(MOTDFILE, "r");
+ if (f) {
+ while (fgets(line, BUFSIZE, f))
+ tty_puts(line);
+ fclose(f);
+ }
+}
+#endif
+
+static void redraw_everything(void)
+{
+ if (prompt_status == 1 && line_status == 0)
+ line_status = 1;
+ if (prompt_status == 1)
+ draw_prompt();
+ else if (prompt_status == -1) {
+ promptzero();
+ col0 = surely_isprompt = '\0';
+ }
+ if (line_status == 1)
+ draw_input_line();
+}
+
+/* how much can we sleep in select() ? */
+static void compute_sleeptime(vtime **timeout)
+{
+ static vtime tbuf;
+ int sleeptime = 0;
+
+ if (delays) {
+ update_now();
+ sleeptime = diff_vtime(&delays->when, &now);
+ if (!sleeptime)
+ sleeptime = 1; /* if sleeptime is less than 1 millisec,
+ * set to 1 millisec */
+ }
+ if (flashback && (!sleeptime || sleeptime > FLASHDELAY))
+ sleeptime = FLASHDELAY;
+
+ if (sleeptime) {
+ tbuf.tv_sec = sleeptime / mSEC_PER_SEC;
+ tbuf.tv_usec = (sleeptime % mSEC_PER_SEC) * uSEC_PER_mSEC;
+ *timeout = &tbuf;
+ } else
+ *timeout = (vtime *)NULL;
+}
+
+/*
+ * main loop.
+ */
+static void mainloop(void)
+{
+ fd_set readfds;
+ int i, err;
+ vtime *timeout;
+
+ for (;;) {
+ tcp_fd = tcp_main_fd;
+ exec_delays();
+
+ do {
+ if (sig_pending)
+ sig_bottomhalf(); /* this might set errno... */
+
+ tcp_flush();
+
+ if (!(pos <= edlen)) {
+ PRINTF("\n#*ARGH* assertion failed (pos <= edlen): mail bpk@hoopajoo.net\n");
+ pos = edlen;
+ }
+
+ redraw_everything();
+ tty_flush();
+
+ compute_sleeptime(&timeout);
+
+ error = now_updated = 0;
+
+ readfds = fdset;
+ err = select(tcp_max_fd+1, &readfds, NULL, NULL, timeout);
+
+ prompt_reset_iac();
+
+ } while (err < 0 && errno == EINTR);
+
+ if (err < 0 && errno != EINTR)
+ syserr("select");
+
+ if (flashback) putbackcursor();
+
+ /* process subsidiary and spawned connections first */
+ if (tcp_count > 1 || tcp_attachcount) {
+ for (i=0; err && i<conn_max_index; i++) {
+ if (CONN_INDEX(i).id && CONN_INDEX(i).fd != tcp_main_fd) {
+ tcp_fd = CONN_INDEX(i).fd;
+ if (FD_ISSET(tcp_fd, &readfds)) {
+ err--;
+ get_remote_input();
+ }
+ }
+ }
+ }
+ /* and main connection last */
+ if (tcp_main_fd != -1 && FD_ISSET(tcp_main_fd, &readfds)) {
+ tcp_fd = tcp_main_fd;
+ get_remote_input();
+ }
+ if (FD_ISSET(tty_read_fd, &readfds)) {
+ tcp_fd = tcp_main_fd;
+ confirm = 0;
+ get_user_input();
+ }
+
+ }
+}
+
+/*
+ * set the new prompt / input line status
+ */
+void status(int s)
+{
+ if (s < 0) {
+ /* waiting prompt from the MUD */
+ prompt_status = s;
+ line_status = 1;
+ } else {
+ if (prompt_status >= 0)
+ prompt_status = s;
+ if (line_status >= 0)
+ line_status = s;
+ }
+}
+
+/*
+ * execute the delayed labels that have expired
+ * and place them in the disabled delays list
+ */
+static void exec_delays(void)
+{
+ delaynode *dying;
+ ptr *pbuf, buf = (ptr)0;
+
+ if (!delays)
+ return;
+
+ update_now();
+
+ if (cmp_vtime(&delays->when, &now) > 0)
+ return;
+
+ /* remember delayed command may modify the prompt and/or input line! */
+ if (prompt_status == 0) {
+ clear_input_line(opt_compact || !opt_info);
+ if (!opt_compact && opt_info && prompt_status == 0 && promptlen) {
+ tty_putc('\n');
+ col0 = 0;
+ status(1);
+ }
+ }
+
+ TAKE_PTR(pbuf, buf);
+
+ while (delays && cmp_vtime(&delays->when, &now) <= 0) {
+ dying = delays; /* remove delayed command from active list */
+ delays = dying->next; /* and put it in the dead one */
+
+ add_node((defnode *)dying, (defnode **)&dead_delays, rev_time_sort);
+
+ /* must be moved before executing delay->command
+ * and command must be copied in a buffer
+ * (can't you imagine why? The command may edit itself...)
+ */
+
+ if (opt_info)
+ tty_printf("#now [%s]\n", dying->command);
+
+ if (*dying->command) {
+
+ error = 0;
+
+ *pbuf = ptrmcpy(*pbuf, dying->command, strlen(dying->command));
+ if (MEM_ERROR)
+ errmsg("malloc (#in/#at)");
+ else {
+ parse_instruction(ptrdata(*pbuf), 0, 0, 1);
+ history_done = 0;
+ }
+ }
+ }
+ DROP_PTR(pbuf);
+}
+
+
+#define IAC_N 1024
+static char *iac_v[IAC_N];
+static int iac_f, iac_l;
+
+static void prompt_reset_iac(void)
+{
+ iac_f = iac_l = 0;
+}
+
+void prompt_set_iac(char *p)
+{
+ if (iac_f == iac_l)
+ iac_f = iac_l = 0;
+
+ if (iac_l < IAC_N)
+ iac_v[iac_l++] = p;
+}
+
+static char *prompt_get_iac(void)
+{
+ return iac_l > iac_f ? iac_v[iac_f] : NULL;
+}
+
+
+/* compute the effective prompt string; may end in \b* or \r */
+static void effective_prompt(void)
+{
+ char *const pstr = promptstr;
+ char *dst = pstr;
+ const size_t len = promptlen;
+ size_t pos = 0, maxpos = 0, p;
+ for (p = 0; p < len; ++p) {
+ char c = pstr[p];
+ if (c == '\b') {
+ if (pos > 0)
+ --pos;
+ continue;
+ }
+ if (c == '\r') {
+ pos = 0;
+ continue;
+ }
+ if (c == '\033'
+ && len - p > 2 && pstr[p + 1] == '[' && pstr[p + 2] == 'K') {
+ if (dst == pstr)
+ dst = strdup(pstr);
+ maxpos = pos;
+ memset(dst + pos, 0, len - pos);
+ p += 2;
+ continue;
+ }
+ dst[pos++] = c;
+ if (pos > maxpos)
+ maxpos = pos;
+ }
+ if (dst != pstr) {
+ memcpy(pstr, dst, pos);
+ free(dst);
+ }
+ size_t nbs = maxpos - pos;
+ if (nbs == 0)
+ ;
+ else if (pos == 0)
+ pstr[maxpos++] = '\r';
+ else {
+ memset(pstr + maxpos, '\b', nbs);
+ maxpos += nbs;
+ }
+ ptrsetlen(prompt->str, maxpos);
+}
+
+static int grab_prompt(char *linestart, int len, int islast)
+{
+ char *p;
+ int is_iac_prompt = surely_isprompt = 0;
+
+ /* recognize IAC GA as end-of-prompt marker */
+ if ((CONN_LIST(tcp_fd).flags & IDPROMPT)) {
+ if ((p = prompt_get_iac()) && p > linestart && p <= linestart+len)
+ iac_f++, is_iac_prompt = len = p - linestart;
+ else if (!islast)
+ return 0;
+ }
+
+ /*
+ * We may get a prompt in the middle of a bunch of lines, so
+ * match #prompts. They usually have no #print, so we print manually
+ * if islast is not set and a #prompt matches.
+ */
+ if ((is_iac_prompt || islast || printstrlen(linestart) < cols) &&
+ ((search_prompt(linestart, 1) && surely_isprompt) || is_iac_prompt)) {
+
+ char *reprint;
+ /*
+ * the line starts with a prompt.
+ * #isprompt placed the actual prompt in $prompt,
+ * we must still process the rest of the line.
+ */
+ if (surely_isprompt > 0 && surely_isprompt <= len) {
+ len = surely_isprompt;
+ prompt_status = 1;
+ } else if (!surely_isprompt && is_iac_prompt) {
+ len = surely_isprompt = is_iac_prompt;
+ prompt->str = ptrmcpy(prompt->str, linestart, len);
+ effective_prompt();
+ if (MEM_ERROR) { promptzero(); errmsg("malloc(prompt)"); return 0; }
+ prompt_status = 1;
+ }
+
+ /*
+ * following data may be the reply to a previously sent command,
+ * so we may have to reprint that command.
+ */
+ if ((reprint = reprint_getline()) && *reprint) {
+ smart_print(promptstr, 0);
+ status(-1);
+ tty_printf("(%s)\n", reprint);
+ } else if (!islast)
+ smart_print(promptstr, 1);
+ } else if (islast) {
+ prompt->str = ptrmcpy(prompt->str, linestart, len);
+ if (MEM_ERROR) { promptzero(); errmsg("malloc(prompt)"); return 0; }
+ effective_prompt();
+ prompt_status = 1; /* good, we got what to redraw */
+ } else
+ len = 0;
+
+ return len;
+}
+
+
+/*
+ * process remote input one line at time. stop at "\n".
+ */
+static void process_singleline(char **pbuf, int *psize)
+{
+ int size, len = 0;
+ char *wasn = 0, *buf, *linestart = *pbuf, *lineend, *end = *pbuf + *psize;
+
+ if ((lineend = memchr(linestart, '\n', *psize))) {
+ /* ok, there is a newline */
+
+ *(wasn = lineend) = '\0';
+ buf = lineend + 1; /* start of next line */
+ }
+
+ if (!lineend)
+ /* line continues till end of buffer, no trailing \n */
+ buf = lineend = end;
+
+ size = buf - linestart;
+
+#ifdef DEBUGCODE_2
+ /* debug code to see in detail what codes come from the server */
+ {
+ char c;
+ char *t;
+ tty_putc('{');
+ for (t = linestart; t < lineend && (c = *t); t++) {
+ if (c < ' ' || c > '~')
+ tty_printf("[%d]", (int)c);
+ else
+ tty_putc(c);
+ }
+ tty_puts("}\n");
+ }
+#endif
+
+ /*
+ * Try to guess where is the prompt... really not much more than
+ * a guess. Again, do it only on main connection: we do not want
+ * output from other connections to mess with the prompt
+ * of main connection :)
+ *
+ * Since we now have #prompt, behave more restrictively:
+ * if no #prompts match or a #prompt matches but does not set #isprompt
+ * (i.e. recognize it for not being a prompt),
+ * we check for #actions on it when the \n arrives.
+ * if a #prompt matches and sets #isprompt, then it is REALLY a prompt
+ * so never match #actions on it.
+ */
+ if (lineend == end && tcp_fd == tcp_main_fd) {
+ /*
+ * The last line in the chunk we received has no trailing \n
+ * Assume it is a prompt.
+ */
+ if (surely_isprompt && promptlen && prompt_status == 1) {
+ draw_prompt(); tty_putc('\n'); col0 = 0;
+ }
+ surely_isprompt = 0;
+ promptzero();
+ if (lineend > linestart && (len = grab_prompt(linestart, lineend-linestart, 1)))
+ size = len;
+ } else {
+ if (tcp_fd == tcp_main_fd) {
+ surely_isprompt = 0;
+ promptzero();
+
+ if (linestart[0]) {
+ /* set $last_line */
+ last_line->str = ptrmcpy(last_line->str, linestart, strlen(linestart));
+ if (MEM_ERROR) { print_error(error); return; }
+
+ if (lineend > linestart && (len = grab_prompt(linestart, lineend-linestart, 0)))
+ size = len;
+ }
+ }
+ if (!len && ((!search_action(linestart, 0) || opt_autoprint))) {
+ if (line0 < lines - 1)
+ line0++;
+ if (tcp_fd != tcp_main_fd) /* sub connection */
+ tty_printf("##%s> ", CONN_LIST(tcp_fd).id);
+
+ smart_print(linestart, 1);
+ }
+ }
+
+ /*
+ * search_prompt and search_action above
+ * might set error: clear it to avoid troubles.
+ */
+ error = 0;
+ if (wasn) *wasn = '\n';
+ *pbuf += size;
+ *psize -= size;
+}
+
+/*
+ * Code to merge lines from host that were splitted
+ * into different packets: it is a horrible kludge (sigh)
+ * and can be used only on one connection at time.
+ * We currently do it on main connection.
+ *
+ * Note that this code also works for _prompts_ splitted into
+ * different packets, as long as no #prompts execute #isprompt
+ * on an incomplete prompt (as stated in the docs).
+ */
+static int process_first_fragment(char *buf, int got)
+{
+ int processed = 0;
+
+ /*
+ * Don't merge if the first part of the line was intercepted
+ * by a #prompt action which executed #isprompt
+ * (to avoid intercepting it twice)
+ */
+ if (*buf == '\n') {
+ char deleteprompt = 0, matched = 0;
+
+ if (opt_compact) {
+ /* in compact mode, skip the first \n */
+ deleteprompt = 1;
+ processed++;
+ }
+
+ /*
+ * the prompt was actually a complete line.
+ * no need to put it on the top of received data.
+ * unless #isprompt was executed, demote it to a regular line,
+ * match #actions on it, copy it in last_line.
+ */
+ if (!surely_isprompt) {
+ last_line->str = ptrcpy(last_line->str, prompt->str);
+ if (MEM_ERROR) { print_error(error); return 0; }
+
+ /*
+ * Kludge for kludge: don't delete the old prompt immediately.
+ * Instead, match actions on it first.
+ * If it matches, clear the line before running the action
+ * (done by the "1" in search_action() )
+ * If it doesn't match, delete it only if opt_compact != 0
+ */
+
+ matched = search_action(promptstr, 1);
+ }
+ if (!matched)
+ clear_input_line(deleteprompt);
+ status(-1);
+ } else {
+ /*
+ * try to merge the prompt with the first line in buf
+ * (assuming we have a line splitted into those parts)
+ * then clear the prompt.
+ */
+ char *lineend, *spinning = NULL;
+
+ /* find the end of the first line. include the final newline. */
+ if ((lineend = strchr(buf, '\n')))
+ lineend++;
+ else
+ lineend = buf + got;
+
+ if (surely_isprompt) {
+ /*
+ * either #isprompt _was_ executed,
+ * or we got a MUME spinning bar.
+ * in both cases, don't try to merge.
+ *
+ * print a newline (to keep the old prompt on screen)
+ * only if !opt_compact and we didn't get a MUME spinning bar.
+ */
+ clear_input_line(opt_compact);
+ if (!spinning && !opt_compact)
+ tty_putc('\n'), col0 = 0;
+ promptzero();
+ } else {
+ ptr *pp, p = (ptr)0;
+ char *dummy;
+ int dummyint;
+
+ /* ok, merge this junk with the prompt */
+ TAKE_PTR(pp, p);
+ *pp = ptrcpy(*pp, prompt->str);
+ *pp = ptrmcat(*pp, buf, lineend - buf);
+
+ if (MEM_ERROR) { print_error(error); return 0; }
+ if (!*pp)
+ return 0;
+ dummy = ptrdata(*pp);
+ dummyint = ptrlen(*pp);
+ /* this also sets last_line or prompt->str : */
+ clear_input_line(1);
+ process_singleline(&dummy, &dummyint);
+
+ processed = lineend - buf;
+ }
+ }
+ return processed;
+}
+
+/*
+ * process input from remote host:
+ * detect special sequences, trigger actions, locate prompt,
+ * word-wrap, print to tty
+ */
+void process_remote_input(char *buf, int size)
+{
+
+ if (promptlen && tcp_fd == tcp_main_fd)
+ promptzero(); /* discard the prompt, we look for another one */
+
+ status(1);
+
+ do {
+ process_singleline(&buf, &size);
+ } while (size > 0);
+}
+
+static void common_clear(int newline)
+{
+ clear_input_line(opt_compact);
+ if (newline) {
+ tty_putc('\n'); col0 = 0; status(1);
+ }
+}
+
+/*
+ * get data from the socket and process/display it.
+ */
+static void get_remote_input(void)
+{
+ char buffer[BUFSIZE + 2]; /* allow for a terminating \0 later */
+ char *buf = buffer, *newline;
+ int got, otcp_fd, i;
+
+ if (CONN_LIST(tcp_fd).fragment) {
+ if ((i = strlen(CONN_LIST(tcp_fd).fragment)) >= BUFSIZE-1) {
+ i = 0;
+ common_clear(promptlen && !opt_compact);
+ tty_printf("#error: ##%s : line too long, discarded\n", CONN_LIST(tcp_fd).id);
+ } else {
+ buf += i;
+ memcpy(buffer, CONN_LIST(tcp_fd).fragment, i);
+ }
+ free(CONN_LIST(tcp_fd).fragment);
+ CONN_LIST(tcp_fd).fragment = 0;
+ } else
+ i = 0;
+
+ got = tcp_read(tcp_fd, buf, BUFSIZE - i);
+ if (!got)
+ return;
+
+ buf[got]='\0'; /* Safe, there is space. Do it now not to forget it later */
+ received += got;
+
+#ifdef DEBUGCODE
+ /* debug code to see in detail what strange codes come from the server */
+ {
+ char c, *t;
+ newline = buf + got;
+ tty_printf("%s{", edattrend);
+ for (t = buf; t < newline; t++) {
+ if ((c = *t) < ' ' || c > '~')
+ tty_printf("[%d]", c);
+ else tty_putc(c);
+ }
+ tty_puts("}\n");
+ }
+#endif
+
+ if (!(CONN_LIST(tcp_fd).flags & ACTIVE))
+ return; /* process only active connections */
+
+ got += (buf - buffer);
+ buf = buffer;
+
+ if (CONN_LIST(tcp_fd).flags & SPAWN) {
+ /* this is data from a spawned child or an attached program.
+ * execute as if typed */
+ otcp_fd = tcp_fd;
+ tcp_fd = tcp_main_fd;
+
+ if ((newline = strchr(buf, '\n'))) {
+ /* instead of newline = strtok(buf, "\n") */
+ *newline = '\0';
+
+ if (opt_autoclear && line_status == 0) {
+ common_clear(!opt_compact);
+ }
+ do {
+ if (opt_info) {
+ if (line_status == 0) {
+ common_clear(!opt_compact);
+ }
+ tty_printf("##%s [%s]\n", CONN_LIST(otcp_fd).id, buf);
+ }
+ parse_user_input(buf, 0);
+ /*
+ * strtok() may have been used in parse_user_input()...
+ * cannot rely it refers on what we may have set above.
+ * (it causes a bug in #spawned commands if they
+ * evaluate (attr(), noattr) or they #connect ... )
+ * so do it manually.
+ */
+ /*
+ * buf = strtok(NULL, "\n");
+ */
+ if ((buf = newline) &&
+ (newline = strchr(++buf, '\n')))
+ *newline = '\0';
+ } while (buf && newline);
+ }
+
+ if (buf && *buf && !newline) {
+ /*
+ * save last fragment for later, when spawned command will
+ * (hopefully) send the rest of the text
+ */
+ CONN_LIST(otcp_fd).fragment = my_strdup(buf);
+
+ if (opt_info) {
+ if (line_status == 0) {
+ common_clear(!opt_compact);
+ }
+ tty_printf("#warning: ##%s : unterminated [%s]\n", CONN_LIST(otcp_fd).id, buf);
+ }
+ }
+ tcp_fd = otcp_fd;
+ return;
+ }
+
+ if (linemode & LM_CHAR) {
+ /* char-by-char mode: just display output, no fuss */
+ clear_input_line(0);
+ tty_puts(buf);
+ return;
+ }
+
+ /* line-at-a-time mode: process input in a number of ways */
+
+ if (tcp_fd == tcp_main_fd && promptlen) {
+ i = process_first_fragment(buf, got);
+ buf += i, got -= i;
+ } else {
+ common_clear(promptlen && !opt_compact);
+ }
+
+ if (got > 0)
+ process_remote_input(buf, got);
+}
+
+
+#ifdef USE_REGEXP
+/*
+ * GH: matches precompiled regexp, return actual params in param array
+ * return 1 if matched, 0 if not
+ */
+static int match_regexp_action(void *regexp, char *line, int *match_s, int *match_e)
+{
+ regmatch_t reg_match[NUMPARAM - 1];
+
+ if (!regexec((regex_t *)regexp, line, NUMPARAM - 1, reg_match, 0)) {
+ int n;
+
+ match_s[0] = 0;
+ match_e[0] = strlen(line);
+ for (n = 1; n < NUMPARAM; n++)
+ match_s[n] = match_e[n] = 0;
+ for (n = 0; n <= (int)((regex_t *)regexp)->re_nsub &&
+ n < NUMPARAM - 1; n++) {
+ if (reg_match[n].rm_so == -1) continue;
+ match_s[n+1] = reg_match[n].rm_so;
+ match_e[n+1] = reg_match[n].rm_eo;
+ }
+ return 1;
+ }
+ return 0;
+}
+#endif
+
+/*
+ * match action containing &1..&9 and $1..$9 and return actual params start/end
+ * in match_s/match_e - return 1 if matched, 0 if not
+ */
+static int match_weak_action(char *pat, char *line, int *match_s, int *match_e)
+{
+ char mpat[BUFSIZE], *npat=0, *npat2=0, *src=line, *nsrc=0, c;
+ ptr *pbuf, buf = (ptr)0;
+ char *tmp, *realpat = pat;
+ int mbeg = 0, mword = 0, prm = -1;
+
+ TAKE_PTR(pbuf, buf);
+
+ if (jit_subst_vars(pbuf, pat))
+ pat = ptrdata(*pbuf);
+ if (REAL_ERROR) {
+ print_error(error);
+ DROP_PTR(pbuf);
+ return 0;
+ }
+ unescape(pat);
+
+ {
+ int p;
+ for (p = 0; p < NUMPARAM; p++)
+ match_s[p] = match_e[p] = 0;
+ }
+
+ if (*pat == '^') {
+ pat++;
+ mbeg = 1; /* anchor match at line start */
+ }
+ if (*pat == '&' || *pat == '$')
+ mbeg = - mbeg - 1; /* pattern starts with '&' or '$' */
+
+ while (pat && *pat) {
+ if (((c=*pat) == '&' || c == '$')) {
+ /* &x matches a string */
+ /* $x matches a single word */
+ tmp = pat + 1;
+ if (isdigit(*tmp)) {
+ int p = 0;
+ while (isdigit(*tmp) && p < NUMPARAM) {
+ p *= 10;
+ p += *tmp++ - '0';
+ }
+ if (p <= 0 || p >= NUMPARAM) {
+ DROP_PTR(pbuf);
+ return 0;
+ }
+ prm = p;
+ pat = tmp;
+ if (c == '$')
+ mword = 1;
+ } else {
+ PRINTF("#error: bad action pattern \"%s\"\n#missing digit after \"%s\"\n",
+ realpat, pat);
+ DROP_PTR(pbuf);
+ return 0;
+ }
+ }
+
+ npat = first_valid(pat, '&');
+ npat2 = first_valid(pat, '$');
+ if (npat2 < npat) npat = npat2;
+ if (!*npat) npat = 0;
+
+ if (npat) {
+ my_strncpy(mpat, pat, npat-pat);
+ /* mpat[npat - pat] = 0; */
+ } else
+ strcpy(mpat, pat);
+
+ if (*mpat) {
+ nsrc = strstr(src, mpat);
+ if (!nsrc) {
+ DROP_PTR(pbuf);
+ return 0;
+ }
+ if (mbeg > 0) {
+ if (nsrc != src) {
+ DROP_PTR(pbuf);
+ return 0;
+ }
+ mbeg = 0; /* reset mbeg to stop further start match */
+ }
+ if (prm != -1) {
+ match_s[prm] = src - line;
+ match_e[prm] = nsrc - line;
+ }
+ } else if (prm != -1) {
+ /* end of pattern space */
+ match_s[prm] = src - line;
+ match_e[prm] = strlen(line);
+ }
+
+ /* post-processing of param */
+ if (prm != -1 && match_e[prm] && mword) {
+ if (mbeg == -1) {
+ /* unanchored '$' start, take last word */
+ if ((tmp = memrchrs(line + match_s[prm],
+ match_e[prm] - match_s[prm],
+ DELIM, DELIM_LEN))) {
+ match_s[prm] = tmp - line + 1;
+ }
+ } else if (!*pat) {
+ /* '$' at end of pattern, take first word */
+ if ((tmp = memchrs(line + match_s[prm],
+ match_e[prm] - match_s[prm],
+ DELIM, DELIM_LEN)))
+ match_e[prm] = tmp - line;
+ } else {
+ /* match only if param is single-worded */
+ if (memchrs(line + match_s[prm],
+ match_e[prm] - match_s[prm],
+ DELIM, DELIM_LEN)) {
+ DROP_PTR(pbuf);
+ return 0;
+ }
+ }
+ }
+ if (prm != -1 && match_e[prm])
+ mbeg = mword = 0; /* reset match flags */
+ src = nsrc + strlen(mpat);
+ pat = npat;
+ }
+ DROP_PTR(pbuf);
+
+ match_s[0] = 0; match_e[0] = strlen(line);
+ return 1;
+}
+
+/*
+ * Search for #actions or #prompts to trigger on an input line.
+ * The line can't be trashed since we want to print it on the screen later.
+ * Return 1 if line matched to some #action, 0 otherwise
+ *
+ * Optionally clear the input line before running the trigger command.
+ */
+static int search_action_or_prompt(char *line, char clearline, char onprompt)
+{
+ /*
+ * we need actionnode and promptnode to be the same "triggernode" type
+ */
+ triggernode *p;
+ int ret = 0;
+ int match_s[NUMPARAM], match_e[NUMPARAM];
+
+ for (p = onprompt ? prompts : actions; p; p = p->next) {
+#ifdef USE_REGEXP
+ if (p->active &&
+ ((p->type == ACTION_WEAK && match_weak_action(p->pattern, line, match_s, match_e))
+ || (p->type == ACTION_REGEXP && match_regexp_action(p->regexp, line, match_s, match_e))
+ ))
+#else
+ if (p->active &&
+ ((p->type == ACTION_WEAK && match_weak_action(p->pattern, line, match_s, match_e))
+ ))
+#endif
+ {
+ push_params(); if (error) return 0;
+ ret = 1; error = 0;
+ set_params(line, match_s, match_e); if (error) return 0;
+ if (onprompt == 2) {
+ prompt->str = ptrmcpy(prompt->str, line, strlen(line));
+ if (MEM_ERROR) { promptzero(); errmsg("malloc(prompt)"); return 0; }
+ }
+ if (clearline)
+ clear_input_line(1);
+ parse_instruction(p->command, 0, 1, 1);
+ history_done = 0;
+ if (error!=DYN_STACK_UND_ERROR && error!=DYN_STACK_OV_ERROR)
+ pop_params();
+ break;
+ }
+ }
+ if (error) return 0;
+ return ret;
+}
+
+/*
+ * read terminal input and send to parser.
+ * decode keys that send escape sequences
+ */
+static void get_user_input(void)
+{
+ int i, j, chunk = 1;
+ static char buf[BUFSIZE+1]; /* allow for terminating \0 */
+ char *c = buf;
+ static char typed[CAPLEN]; /* chars typed so far (with partial match) */
+ static int nchars = 0; /* number of them */
+
+ /* We have 4 possible line modes:
+ * line mode, local echo: line editing functions in effect
+ * line mode, no echo: sometimes used for passwords, no line editing
+ * char mode, no echo: send a character directly, no local processing
+ * char mode, local echo: extremely rare, do as above.
+ */
+ if (!(linemode & (LM_NOECHO | LM_CHAR))) /* line mode, local echo */
+ chunk = BUFSIZE;
+
+ while ((j = tty_read(c, chunk)) < 0 && errno == EINTR)
+ ;
+ if (j == 0)
+ return;
+
+ if (j < 0 || (chunk == 1 && j != chunk))
+ syserr("read from tty");
+
+ c[chunk] = '\0';
+
+ if (linemode & LM_CHAR) {
+ /* char mode. chunk == 1 */
+ while ((i = write(tcp_fd, c, 1)) < 0 && errno == EINTR)
+ ;
+ if (i != 1)
+ syserr("write to socket");
+ if (!(linemode & LM_NOECHO))
+ tty_putc(*c);
+ last_edit_cmd = (function_any)0;
+ } else if (linemode & LM_NOECHO) {
+ /* sending password (line mode, no echo). chunk == 1 */
+ if ((*c != '\n' && *c != '\r') && edlen < BUFSIZE - 2)
+ edbuf[edlen++] = *c;
+ else {
+ edbuf[edlen] = '\0';
+#ifdef BUG_ANSI
+ if (edattrbg)
+ tty_printf("%s\n", edattrend);
+ else
+#endif
+ tty_putc('\n');
+
+ tcp_write(tcp_fd, edbuf);
+ edlen = 0;
+ typed[nchars = 0] = 0;
+ }
+ edbuf[pos = edlen] = '\0';
+ last_edit_cmd = (function_any)0;
+ } else {
+ /* normal mode (line mode, echo). chunk == BUFSIZE */
+ int done = 0;
+ keynode *p;
+
+ for (; j > 0; c++, j--) {
+
+ /* search function key strings for match */
+ /* GH: support for \0 in sequence */
+ done = 0;
+ typed[nchars++] = *c;
+
+ while (!done) {
+ done = 1;
+ /*
+ * shortcut:
+ * an initial single ASCII char cannot match any #bind
+ */
+ if (nchars == 1 && *c >= ' ' && *c <= '~')
+ p = NULL;
+ else {
+ for (p = keydefs; (p && (p->seqlen < nchars ||
+ memcmp(typed, p->sequence, nchars)));
+ p = p->next)
+ ;
+ }
+
+ if (!p) {
+ /*
+ * GH: type the first character and keep processing
+ * the rest in the input buffer
+ */
+ i = 1;
+ last_edit_cmd = (function_any)0;
+ insert_char(typed[0]);
+ while (i < nchars) {
+ typed[i - 1] = typed[i];
+ i++;
+ }
+ if (--nchars)
+ done = 0;
+ } else if (p->seqlen == nchars) {
+ if (flashback)
+ putbackcursor();
+ p->funct(p->call_data);
+ last_edit_cmd = (function_any)p->funct; /* GH: keep track of last command */
+ nchars = 0;
+ }
+ }
+ }
+ }
+}
+
+/*
+ * split str into words separated by DELIM, and place in
+ * VAR[1].str ... VAR[9].str -
+ * the whole str is put in VAR[0].str
+ */
+static char *split_words(char *str)
+{
+ int i;
+ char *end;
+ ptr *prm;
+
+ *VAR[0].str = ptrmcpy(*VAR[0].str, str, strlen(str));
+ for (i = 1; i < NUMPARAM; i++) {
+ *VAR[i].num = 0;
+ prm = VAR[i].str;
+ /* skip multiple span of DELIM */
+ while (*str && strchr(DELIM, *str))
+ str++;
+ end = str;
+ while (*end && !strchr(DELIM, *end))
+ end++;
+ *prm = ptrmcpy(*prm, str, end-str);
+ str = end;
+ if (MEM_ERROR) { print_error(error); return NULL; }
+ }
+ return str;
+}
+
+/*
+ * free the whole stack and reset it to empty
+ */
+static void free_allparams(void)
+{
+ int i,j;
+
+ paramstk.curr = 0; /* reset stack to empty */
+
+ for (i=1; i<MAX_STACK; i++) {
+ for (j=0; j<NUMPARAM; j++) {
+ ptrdel(paramstk.p[i][j].str);
+ paramstk.p[i][j].str = (ptr)0;
+ }
+ }
+ for (j=0; j<NUMPARAM; j++) {
+ VAR[j].num = &paramstk.p[0][j].num;
+ VAR[j].str = &paramstk.p[0][j].str;
+ }
+}
+
+void push_params(void)
+{
+ int i;
+
+ if (paramstk.curr < MAX_STACK - 1)
+ paramstk.curr++;
+ else {
+ print_error(error=DYN_STACK_OV_ERROR);
+ free_allparams();
+ return;
+ }
+ for (i=0; i<NUMPARAM; i++) {
+ *(VAR[i].num = &paramstk.p[paramstk.curr][i].num) = 0;
+ ptrzero(*(VAR[i].str = &paramstk.p[paramstk.curr][i].str));
+ }
+}
+
+void pop_params(void)
+{
+ int i;
+
+ if (paramstk.curr > 0)
+ paramstk.curr--;
+ else {
+ print_error(error=DYN_STACK_UND_ERROR);
+ free_allparams();
+ return;
+ }
+ for (i=0; i<NUMPARAM; i++) {
+ ptrdel(*VAR[i].str); *VAR[i].str = (ptr)0;
+ VAR[i].num = &paramstk.p[paramstk.curr][i].num;
+ VAR[i].str = &paramstk.p[paramstk.curr][i].str;
+ }
+}
+
+static void set_params(char *line, int *match_s, int *match_e)
+{
+ int i;
+
+ for (i=0; i<NUMPARAM; i++) {
+ *VAR[i].num = 0;
+ if (match_e[i] > match_s[i]) {
+ *VAR[i].str = ptrmcpy(*VAR[i].str, line + match_s[i],
+ match_e[i] - match_s[i]);
+ if (MEM_ERROR) {
+ print_error(error);
+ return;
+ }
+ } else
+ ptrzero(*VAR[i].str);
+ }
+}
+
+char *get_next_instr(char *p)
+{
+ int count, is_if;
+ char *sep, *q;
+
+ p = skipspace(p);
+
+ if (!*p)
+ return p;
+
+ count = is_if = !strncmp(p, "#if", 3);
+
+ do {
+ sep = first_regular(p, CMDSEP);
+
+ q = p;
+ if (*q) do {
+ if (*q == '#') q++;
+
+ q = first_regular(q, '#');
+ } while (*q && strncmp(q, "#if", 3));
+
+ if (sep<=q) {
+ if (*(p = sep))
+ p++;
+ }
+ else
+ if (*q)
+ p = get_next_instr(q);
+ else {
+ print_error(error=SYNTAX_ERROR);
+ return NULL;
+ }
+ sep = skipspace(p);
+ } while (*p && count-- &&
+ (!is_if || (!strncmp(sep, "#else", 5) &&
+ (*(p = sep + 5)))));
+
+ return p;
+}
+
+static void send_line(char *line, char silent)
+{
+ if (!silent && opt_echo) { PRINTF("[%s]\n", line); }
+ tcp_write(tcp_fd, line);
+}
+
+
+/*
+ * Parse and exec the first instruction in 'line', and return pointer to the
+ * second instruction in 'line' (if any).
+ */
+char *parse_instruction(char *line, char silent, char subs, char jit_subs)
+{
+ aliasnode *np;
+ char *buf, *arg, *end, *ret;
+ char last_is_sep = 0;
+ int len, copied = 0, otcp_fd = -1;
+ ptr p1 = (ptr)0, p2 = (ptr)0;
+ ptr *pbuf, *pbusy, *tmp;
+
+ if (error) return NULL;
+
+ ret = get_next_instr(line);
+
+ if (!ret || ret==line) /* error or empty instruction, bail out */
+ return ret;
+
+ /*
+ * remove the optional ';' after an instruction,
+ * to have an usable string, ending with \0.
+ * If it is escaped, DON'T remove it: it is not a separator,
+ * and the instruction must already end with \0, or we would not be here.
+ */
+ if (ret[-1] == CMDSEP) {
+ /* instruction is not empty, ret[-1] is allowed */
+ if (ret > line + 1 && ret[-2] == ESC) {
+ /* ';' is escaped */
+ } else {
+ *--ret = '\0';
+ last_is_sep = 1;
+ }
+ }
+
+ /*
+ * using two buffers, p1 and p2, for four strings:
+ * result of subs_param, result of jit_subst_vars, result of
+ * unescape and first word of line.
+ *
+ * So care is required to avoid clashes.
+ */
+ TAKE_PTR(pbuf, p1);
+ TAKE_PTR(pbusy, p2);
+
+ if (subs && subst_param(pbuf, line)) {
+ line = *pbuf ? ptrdata(*pbuf) : "";
+ SWAP2(pbusy, pbuf, tmp);
+ copied = 1;
+ }
+ if (jit_subs && jit_subst_vars(pbuf, line)) {
+ line = *pbuf ? ptrdata(*pbuf) : "";
+ SWAP2(pbusy, pbuf, tmp);
+ copied = 1;
+ }
+ if (!copied) {
+ *pbuf = ptrmcpy(*pbuf, line, strlen(line));
+ line = *pbuf ? ptrdata(*pbuf) : "";
+ SWAP2(pbusy, pbuf, tmp);
+ }
+ if (subs || jit_subs)
+ unescape(line);
+
+ /* now line is in (pbusy) and (pbuf) is available */
+
+ /* restore integrity of original line: must still put it in history */
+ if (last_is_sep)
+ *ret++ = CMDSEP;
+
+ if (REAL_ERROR) {
+ print_error(error);
+ DROP_PTR(pbuf); DROP_PTR(pbusy);
+ return NULL;
+ }
+ /* run-time debugging */
+ if (opt_debug) {
+ PRINTF("#parsing: %s\n", line);
+ }
+
+ if (!*line)
+ send_line(line, silent);
+ else do {
+ arg = skipspace(line);
+
+ if (arg[0] == '#' && arg[1] == '#') { /* send to other connection */
+ *pbuf = ptrsetlen(*pbuf, len = strlen(arg));
+ if (REAL_ERROR) { print_error(error); break; }
+ buf = ptrdata(*pbuf);
+ line = split_first_word(buf, len+1, arg + 2);
+ /* now (pbuf) is used too: first word of line */
+ /* line contains the rest */
+ otcp_fd = tcp_fd;
+ if ((tcp_fd = tcp_find(buf))<0) {
+ error = OUT_RANGE_ERROR;
+ PRINTF("#no connection named \"%s\"\n", buf);
+ break;
+ }
+ arg = skipspace(line);
+ if (!*arg) {
+ if (CONN_LIST(tcp_fd).flags & SPAWN) {
+ error = OUT_RANGE_ERROR;
+ PRINTF("#only MUD connections can be default ones!\n");
+ } else {
+ /* set it as main connection */
+ tcp_set_main(tcp_fd);
+ otcp_fd = -1;
+ }
+ /* stop parsing, otherwise a newline would be sent to tcp_fd */
+ break;
+ }
+ /* now we can trash (pbuf) */
+ }
+
+ if (*arg == '{') { /* instruction contains a block */
+ end = first_regular(line = arg + 1, '}');
+
+ if (*end) {
+ *end = '\0';
+ parse_user_input(line, silent);
+ *end = '}';
+ } else
+ print_error(error=MISSING_PAREN_ERROR);
+ } else {
+ int oneword;
+ /* initial spaces are NOT skipped this time */
+
+ *pbuf = ptrsetlen(*pbuf, len = strlen(line));
+ if (REAL_ERROR) { print_error(error); break; }
+
+ buf = ptrdata(*pbuf);
+ arg = split_first_word(buf, len+1, line);
+ /* buf contains the first word, arg points to arguments */
+
+ /* now pbuf is used too */
+ if (!*arg) oneword = 1;
+ else oneword = 0;
+
+ if ((np = *lookup_alias(buf))&&np->active) {
+ push_params();
+ if (REAL_ERROR) break;
+
+ split_words(arg); /* split argument into words
+ and place them in $0 ... $9 */
+ parse_instruction(np->subst, 0, 1, 1);
+
+ if (error!=DYN_STACK_UND_ERROR && error!=DYN_STACK_OV_ERROR)
+ pop_params();
+
+ /* now check for internal commands */
+ /* placed here to allow also aliases starting with "#" */
+ } else if (*(end = skipspace(line)) == '#') {
+
+ if (*(end = skipspace(end + 1)) == '(') { /* execute #() */
+ end++;
+ (void)evaln(&end);
+ if (REAL_ERROR) print_error(error);
+ } else
+ parse_commands(buf + 1, arg);
+ /* ok, buf contains skipspace(first word) */
+ } else if (!oneword || !map_walk(buf, silent, 0)) {
+ /* it is ok, map_walk accepts only one word */
+
+ if (!subs && !jit_subs)
+ unescape(line);
+ send_line(line, silent);
+ }
+ }
+ } while (0);
+
+ if (otcp_fd != -1)
+ tcp_fd = otcp_fd;
+ DROP_PTR(pbuf); DROP_PTR(pbusy);
+ return !REAL_ERROR ? ret : NULL;
+}
+
+/*
+ * parse input from user: calls parse_instruction for each instruction
+ * in cmd_line.
+ * silent = 1 if the line should not be echoed, 0 otherwise.
+ */
+void parse_user_input(char *line, char silent)
+{
+ do {
+ line = parse_instruction(line, silent, 0, 0);
+ } while (!error && line && *line);
+}
+
+/*
+ * parse powwow's own commands
+ */
+static void parse_commands(char *command, char *arg)
+{
+ int i, j;
+ cmdstruct *c;
+
+ /* We ALLOW special commands also on subsidiary connections ! */
+
+ /* assume output will be enough to make input line = last screen line */
+ /* line0 = lines - 1; */
+ if (isdigit(*command) && (i = atoi(command))) {
+ if (i >= 0) {
+ while (i--)
+ (void)parse_instruction(arg, 1, 0, 1);
+ } else {
+ PRINTF("#bogus repeat count\n");
+ }
+ } else {
+ j = strlen(command);
+
+ if( j == 0 ) {
+ /* comment */
+ return;
+ }
+
+ for( c = commands; c != NULL; c = c -> next )
+ if (!strncmp(command, c -> name, j)) {
+ if (c -> funct) {
+ (*(c -> funct))(arg);
+ return;
+ }
+ }
+
+ PRINTF("#unknown powwow command \"%s\"\n", command);
+ }
+}
+
+/*
+ * substitute $0..$9 and @0..@9 in a string
+ * (unless $ or @ is escaped with backslash)
+ *
+ * return 0 if dst not filled. if returned 0 and not error,
+ * there was nothing to substitute.
+ */
+static int subst_param(ptr *buf, char *src)
+{
+ int done, i;
+ char *dst, *tmp, kind;
+
+ if (!strchr(src, '$') && !strchr(src, '@'))
+ return 0;
+
+ i = strlen(src);
+ if (!*buf || ptrlen(*buf) < i) {
+ *buf = ptrsetlen(*buf, i);
+ if (REAL_ERROR)
+ return 0;
+ }
+ dst = ptrdata(*buf);
+
+ while (*src) {
+ while (*src && *src != '$' && *src != '@' && *src != ESC)
+ *dst++ = *src++;
+
+ if (*src == ESC) {
+ while (*src == ESC)
+ *dst++ = *src++;
+
+ if (*src)
+ *dst++ = *src++;
+ }
+
+ done = 0;
+ if (*src == '$' || *src == '@') {
+ kind = *src == '$' ? 1 : 0;
+ tmp = src + 1;
+ if (isdigit(*tmp)) {
+ i = atoi(tmp);
+ while (isdigit(*tmp))
+ tmp++;
+
+ if (i < NUMPARAM) {
+ int max = 0, n;
+ char *data = NULL, buf2[LONGLEN];
+
+ done = 1;
+ src = tmp;
+
+ /* now the actual substitution */
+ if (kind) {
+ if (*VAR[i].str && (data = ptrdata(*VAR[i].str)))
+ max = ptrlen(*VAR[i].str);
+ } else {
+ sprintf(data = buf2, "%ld", *VAR[i].num);
+ max = strlen(buf2);
+ }
+ if (data && max) {
+ n = dst - ptrdata(*buf);
+ *buf = ptrpad(*buf, max);
+ if (REAL_ERROR)
+ return 0;
+ dst = ptrdata(*buf) + n;
+ memcpy(dst, data, max);
+ dst += max;
+ }
+ }
+ }
+ }
+ if (!done && (*src == '$' || *src == '@'))
+ *dst++ = *src++;
+ }
+ *dst = '\0';
+ return 1;
+}
+
+/*
+ * just-in-time substitution:
+ * substitute ${name}, @{name} and #{expression} in a string
+ * (unless "${", "@{" or "#{" are escaped with backslash)
+ *
+ * return 0 if dst not filled. if returned 0 and not error,
+ * there was nothing to substitute.
+ */
+
+static int jit_subst_vars(ptr *buf, char *src)
+{
+ int i, done, kind;
+ char *tmp, *name, *dst, c;
+ varnode *named_var;
+
+ if (!strstr(src, "${") && !strstr(src, "@{") && !strstr(src, "#{"))
+ return 0;
+
+ i = strlen(src);
+ if (!*buf || ptrlen(*buf) < i) {
+ *buf = ptrsetlen(*buf, i);
+ if (REAL_ERROR)
+ return 0;
+ }
+ dst = ptrdata(*buf);
+
+ while (*src) {
+ while (*src && *src != '$' && *src != '@' && *src != '#' && *src != ESC)
+ *dst++ = *src++;
+
+ if (*src == ESC) {
+ while (*src == ESC)
+ *dst++ = *src++;
+
+ if (*src)
+ *dst++ = *src++;
+ }
+
+ done = 0;
+ if (*src == '$' || *src == '@') {
+ i = 0;
+ kind = *src == '$' ? 1 : 0;
+ tmp = src + 1;
+ if (*tmp == '{') {
+ tmp = skipspace(tmp+1);
+ if (isdigit(*tmp) || *tmp == '-') {
+ /* numbered variable */
+ i = atoi(tmp);
+ if (i >= -NUMVAR && i < NUMPARAM) {
+ if (*tmp == '-')
+ tmp++;
+ while (isdigit(*tmp))
+ tmp++;
+ done = 1;
+ }
+ } else if (isalpha(*tmp) || *tmp == '_') {
+ /* named variable */
+ name = tmp++;
+ while (isalnum(*tmp) || *tmp == '_')
+ tmp++;
+ c = *tmp;
+ *tmp = '\0';
+ named_var = *lookup_varnode(name, kind);
+ *tmp = c;
+ if (named_var) {
+ i = named_var->index;
+ done = 1;
+ }
+ }
+ tmp = skipspace(tmp);
+ if (done) {
+ int max = 0, n;
+ char *data = NULL, buf2[LONGLEN];
+
+ src = tmp + 1; /* skip the '}' */
+
+ /* now the actual substitution */
+ if (kind == 1) {
+ if (*VAR[i].str && (data = ptrdata(*VAR[i].str)))
+ max = ptrlen(*VAR[i].str);
+ } else {
+ sprintf(data = buf2, "%ld", *VAR[i].num);
+ max = strlen(buf2);
+ }
+ if (data && max) {
+ n = dst - ptrdata(*buf);
+ *buf = ptrpad(*buf, max);
+ if (REAL_ERROR)
+ return 0;
+ dst = ptrdata(*buf) + n;
+ memcpy(dst, data, max);
+ dst += max;
+ }
+ } else if (*tmp == '}')
+ /* met an undefined variable, consider empty */
+ src = tmp + 1;
+
+ /* else syntax error, do nothing */
+ }
+ } else if (src[0] == '#' && src[1] == '{') {
+ int max, n;
+ ptr pbuf = (ptr)0;
+
+ src += 2;
+ (void)evalp(&pbuf, &src);
+ if (REAL_ERROR) {
+ ptrdel(pbuf);
+ return 0;
+ }
+ if (pbuf) {
+ max = ptrlen(pbuf);
+ n = dst - ptrdata(*buf);
+ *buf = ptrpad(*buf, max);
+ if (REAL_ERROR) {
+ ptrdel(pbuf);
+ return 0;
+ }
+ dst = ptrdata(*buf) + n;
+ memcpy(dst, ptrdata(pbuf), max);
+ dst += max;
+ }
+ ptrdel(pbuf);
+
+ if (*src)
+ src = skipspace(src);
+ if (*src != '}') {
+ PRINTF("#{}: ");
+ print_error(error=MISSING_PAREN_ERROR);
+ return 0;
+ }
+ done = 1;
+ if (*src)
+ src++;
+ }
+
+ if (!done && (*src == '$' || *src == '@' || *src == '#'))
+ /* not matched, just copy */
+ *dst++ = *src++;
+ }
+ *dst = '\0';
+ ptrtrunc(*buf, dst - ptrdata(*buf));
+ return 1;
+}
+
+/*
+ * set definition file:
+ * rules: if powwow_dir is set it is searched first.
+ * if file doesn't exist, it is created there.
+ * If a slash appears in the name, the powwow_dir isn't used.
+ */
+void set_deffile(char *arg)
+{
+ if (!strchr(arg, '/') && *powwow_dir) {
+ strcpy(deffile, powwow_dir);
+ strcat(deffile, arg);
+ if ((access(deffile, R_OK) == -1 || access(deffile, W_OK) == -1)
+ && !access(arg,R_OK) && !access(arg, W_OK))
+ strcpy(deffile, arg);
+ } else
+ strcpy(deffile, arg);
+}
+
+/*
+ * GH: return true if var is one of the permanent variables
+ */
+int is_permanent_variable(varnode *v)
+{
+ return (v == prompt || v == last_line);
+}
diff --git a/src/main.h b/src/main.h
new file mode 100644
index 0000000..e76f7cb
--- /dev/null
+++ b/src/main.h
@@ -0,0 +1,119 @@
+/*
+ * main.h
+ * various constants etc
+ */
+
+#ifndef _MAIN_H_
+#define _MAIN_H_
+
+/* shared functions from main.c */
+void printver(void);
+void status(int s);
+void process_remote_input(char *buf, int size);
+void push_params(void);
+void pop_params(void);
+void prompt_set_iac(char *p);
+char *parse_instruction(char *line, char silent, char subs, char jit_subs);
+char *get_next_instr(char *p);
+void parse_user_input(char *line, char silent);
+void set_deffile(char *arg);
+int is_permanent_variable(varnode *v);
+
+
+/* shared vars from main.c */
+extern int prompt_status, line_status;
+extern int limit_mem;
+extern char ready;
+extern volatile char confirm;
+extern int history_done;
+extern int linemode;
+extern char hostname[];
+extern int portnumber;
+extern char deffile[], helpfile[], copyfile[];
+
+extern int cols, lines, cols_1; /* terminal window size */
+extern int olines; /* previous terminal window size */
+extern int line0, col0; /* origin of input line */
+
+extern varnode *prompt; /* $prompt is always set */
+extern ptr marked_prompt; /* $prompt with marks added */
+
+#define promptstr (ptrdata(prompt->str))
+#define promptlen (ptrlen(prompt->str))
+#define promptzero() (prompt_status = 0, ptrzero(prompt->str))
+
+extern char surely_isprompt; /* 1 if #prompt set #isprompt */
+extern char edbuf[]; /* input line buffer */
+extern int edlen; /* characters in edbuf */
+extern int pos; /* cursor position in edbuf */
+extern char edattrbeg[], edattrend[];
+extern int edattrbg;
+
+extern volatile int sig_pending, sig_winch_got, sig_chld_got;
+
+extern long received, sent;
+
+#ifndef NO_CLOCK
+#include <time.h>
+extern clock_t start_clock, cpu_clock;
+#endif
+
+extern aliasnode *aliases[MAX_HASH];
+extern aliasnode *sortedaliases;
+extern actionnode *actions;
+extern promptnode *prompts;
+extern marknode *markers;
+extern int a_nice;
+extern keynode *keydefs;
+extern delaynode *delays;
+extern delaynode *dead_delays;
+extern varnode *named_vars[2][MAX_HASH];
+extern varnode *sortednamed_vars[2];
+extern int num_named_vars[2];
+extern int max_named_vars;
+extern vars *var;
+#define VAR (var+NUMVAR)
+
+extern ptr globptr[];
+extern char globptrok;
+#define TAKE_PTR(pbuf, buf) do { if (globptrok & 1) globptrok &= ~1, pbuf = globptr; else if (globptrok & 2) globptrok &= ~2, pbuf = globptr + 1; else pbuf = &buf; } while(0)
+#define DROP_PTR(pbuf) do { if (*pbuf == *globptr) globptrok |= 1; else if (*pbuf == *(globptr+1)) globptrok |= 2; else ptrdel(*pbuf); } while(0)
+
+extern vtime now, start_time, ref_time;
+extern int now_updated;
+
+extern char initstr[];
+extern char prefixstr[];
+extern char inserted_next[];
+extern char flashback;
+extern int excursion;
+extern char verbatim;
+
+extern char opt_exit;
+extern char opt_history;
+extern char opt_words;
+extern char opt_compact;
+extern char opt_debug;
+extern char opt_wrap;
+extern char opt_echo;
+extern char opt_info;
+extern char opt_keyecho;
+extern char opt_speedwalk;
+extern char opt_autoprint;
+extern char opt_reprint;
+extern char opt_sendsize;
+extern char opt_autoclear;
+
+extern function_str last_edit_cmd;
+
+extern char *delim_list[];
+extern int delim_len[];
+extern char *delim_name[];
+extern int delim_mode;
+
+/* Group delimiter for actions/aliases, defaults to @ */
+extern char *group_delim;
+
+extern char action_chars[];
+
+#endif /* _MAIN_H_ */
diff --git a/src/map.c b/src/map.c
new file mode 100644
index 0000000..7fd9b56
--- /dev/null
+++ b/src/map.c
@@ -0,0 +1,196 @@
+/*
+ * map.c -- mapping routines.
+ *
+ * Copyright (C) 1998 by Massimiliano Ghilardi
+ *
+ * This program 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 2 of the License, or
+ * (at your option) any later version.
+ *
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <string.h>
+#include <ctype.h>
+#include <time.h>
+#include <sys/types.h>
+#include <sys/time.h>
+
+#include "defines.h"
+#include "main.h"
+#include "tty.h"
+#include "edit.h"
+#include "tcp.h"
+#include "list.h"
+
+/*
+ * mapping variables
+ */
+static char mappath[MAX_MAPLEN]; /* circular path list */
+static int mapstart = 0; /* index to first map entry */
+static int mapend = 0; /* index one past last map entry */
+
+#define MAPINDEX(i) (((i) + MAX_MAPLEN) % MAX_MAPLEN)
+#define MAPENTRY(i) mappath[MAPINDEX(i)]
+
+/*
+ * return reverse direction
+ */
+static char reverse_dir(char dir)
+{
+ static char dirs[] = "nsewud";
+ char *p = strchr(dirs, dir);
+ return p ? dirs[(p - dirs) ^ 1] : 0;
+}
+
+/*
+ * retrace steps on map, optionally walk them back
+ */
+void map_retrace(int steps, int walk_back)
+{
+ char cmd[2];
+
+ cmd[1] = '\0';
+
+ if (!steps && !walk_back)
+ mapend = mapstart;
+ else {
+ if (!steps)
+ steps = -1;
+ if (walk_back && opt_echo) {
+ status(1);
+ tty_putc('[');
+ }
+
+ while (mapstart != mapend && steps--) {
+ mapend = MAPINDEX(mapend - 1);
+ if (walk_back) {
+ cmd[0] = reverse_dir(mappath[mapend]);
+ if (opt_echo)
+ tty_putc(cmd[0]);
+ tcp_write(tcp_fd, cmd);
+ }
+ }
+ if (walk_back && opt_echo)
+ tty_puts("]\n");
+ }
+}
+
+/*
+ * show automatic map (latest steps) in the form s2ews14n
+ */
+void map_show(void)
+{
+ char lastdir;
+ int count = 0;
+ int i;
+
+ if (mapstart == mapend) {
+ PRINTF("#map empty\n");
+ } else {
+ PRINTF("#map: ");
+
+ lastdir = mappath[mapstart];
+ for (i = mapstart; i != mapend; i = MAPINDEX(i + 1)) {
+ if (mappath[i] != lastdir) {
+ if (count > 1)
+ tty_printf("%d", count);
+ tty_putc(lastdir);
+
+ count = 1;
+ lastdir = mappath[i];
+ } else
+ count++;
+ }
+
+ if (count > 1)
+ tty_printf("%d", count);
+
+ tty_printf("%c\n", lastdir);
+ }
+}
+
+/*
+ * print map to string in the form seewsnnnnn
+ */
+void map_sprintf(char *buf)
+{
+ int i;
+
+ if (mapstart != mapend)
+ for (i = mapstart; i != mapend; i = MAPINDEX(i + 1))
+ *buf++ = mappath[i];
+ *buf = '\0';
+}
+
+/*
+ * add direction to automap
+ */
+void map_add_dir(char dir)
+{
+#ifdef NOMAZEMAPPING
+ if (mapend != mapstart && dir == reverse_dir(MAPENTRY(mapend - 1))) {
+ mapend = MAPINDEX(mapend - 1); /* retrace one step */
+ } else
+#endif
+ {
+ MAPENTRY(mapend) = dir;
+ mapend = MAPINDEX(mapend + 1);
+ if(mapend == mapstart)
+ mapstart = MAPINDEX(mapstart + 1);
+ }
+}
+
+/*
+ * execute walk if word is valid [speed]walk sequence -
+ * return 1 if walked, 0 if not
+ */
+int map_walk(char *word, int silent, int maponly)
+{
+ char buf[16];
+ int n = strlen(word);
+ int is_main = (tcp_fd == tcp_main_fd);
+
+ if (!is_main && !maponly && !opt_speedwalk)
+ return 0;
+ if (!n || (n > 1 && !opt_speedwalk && !maponly) ||
+ !strchr("neswud", word[n - 1]) ||
+ (int)strspn(word, "neswud0123456789") != n)
+ return 0;
+
+ if (maponly)
+ silent = 1;
+ buf[1] = '\0';
+ while (*word) {
+ if (!silent) { status(1); tty_putc('['); }
+
+ if (isdigit(*word)) {
+ n = strtol(word, &word, 10);
+ if (!silent)
+ tty_printf("%d", n);
+ } else
+ n = 1;
+ if (!silent)
+ tty_putc(*word);
+ while (n--) {
+ *buf = *word;
+ if (!maponly) {
+ if (*lookup_alias(buf))
+ parse_instruction(buf, 1, 0, 0); // we want to execute aliases n,e,s,w,u,d
+ else tcp_write(tcp_fd, buf);
+ }
+ if (is_main || maponly)
+ map_add_dir(*word);
+ }
+ if (!silent)
+ tty_puts("] ");
+ word++;
+ }
+ if (!silent)
+ tty_putc('\n');
+ return !maponly;
+}
+
diff --git a/src/map.h b/src/map.h
new file mode 100644
index 0000000..49abc1f
--- /dev/null
+++ b/src/map.h
@@ -0,0 +1,15 @@
+/* public things from map.c */
+
+#ifndef _MAP_H_
+#define _MAP_H_
+
+void map_bootstrap(void);
+
+void map_retrace(int steps, int walk_back);
+void map_show(void);
+void map_sprintf(char *buf);
+void map_add_dir(char dir);
+int map_walk(char *word, int silent, int maponly);
+
+#endif /* _MAP_H_ */
+
diff --git a/src/plugtest.c b/src/plugtest.c
new file mode 100644
index 0000000..72b6d38
--- /dev/null
+++ b/src/plugtest.c
@@ -0,0 +1,29 @@
+#include <stdio.h>
+
+#include "defines.h"
+#include "cmd.h"
+#include "tty.h"
+
+/* Bare test plugin for powwow
+ * Author: Steve Slaven - http://hoopajoo.net
+ *
+ * This program 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 2 of the License, or
+ * (at your option) any later version.
+ *
+ */
+
+void plugtest( char *arg );
+
+cmdstruct mycommand = { NULL, "plugtest", "test command", plugtest, NULL };
+
+void powwow_init() {
+ tty_printf( "Init plugtest.so!\n" );
+
+ cmd_add_command( &mycommand );
+}
+
+void plugtest( char *arg ) {
+ tty_printf( "Arg was '%s'\n", arg );
+}
diff --git a/src/powwow-movieplay.c b/src/powwow-movieplay.c
new file mode 100644
index 0000000..91854b3
--- /dev/null
+++ b/src/powwow-movieplay.c
@@ -0,0 +1,97 @@
+/*
+ * powwow-movieplay.c -- replay powwow movies or convert them into ASCII
+ *
+ * This program 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 2 of the License, or
+ * (at your option) any later version.
+ *
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/time.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+void millisec_sleep(msec)
+long msec;
+{
+ struct timeval t;
+ t.tv_sec = msec / 1000;
+ t.tv_usec = (msec % 1000) * 1000;
+ select(0, NULL, NULL, NULL, &t);
+}
+
+int main(argc, argv)
+int argc; char *argv[];
+{
+ FILE *infile, *outfile;
+ char buf[4096];
+ int i, play = 0;
+
+ if (strstr(argv[0], "powwow-movieplay"))
+ play = 1;
+ else if (!strstr(argv[0], "powwow-movie2ascii")) {
+ fprintf(stderr, "Please run this program as \"powwow-movieplay\" or \"powwow-movie2ascii\"\n");
+ return 1;
+ }
+
+ if (play) {
+ if (argc == 2) {
+ infile = fopen(argv[1], "rb");
+ outfile = stdout;
+ if (infile == NULL) {
+ fprintf(stderr, "Error opening input file \"%s\"\n", argv[1]);
+ return 1;
+ }
+ } else {
+ infile = stdin;
+ outfile = stdout;
+ }
+ } else {
+ if (argc == 3) {
+ infile = fopen(argv[1], "rb");
+ outfile = fopen(argv[2], "wb");
+ if (infile == NULL) {
+ fprintf(stderr, "Error opening input file \"%s\"\n", argv[1]);
+ return 1;
+ }
+ if (outfile == NULL) {
+ fprintf(stderr, "Error opening output file \"%s\"\n", argv[2]);
+ return 1;
+ }
+ } else {
+ fprintf(stderr, "Usage: %s [infile [outfile]]\n", argv[0]);
+ return 1;
+ }
+ }
+
+ while (fgets(buf, 4096, infile) != NULL) {
+ i = strlen(buf);
+ if (i > 0 && buf[i-1] == '\n')
+ buf[i-1] = '\0';
+ if (!strncmp(buf, "sleep ", 6)) {
+ if (play)
+ millisec_sleep(atoi(buf + 6));
+ }
+ else if (!strncmp(buf, "line ", 5))
+ fprintf(outfile, "%s\n", buf + 5);
+ else if (!strncmp(buf, "prompt ", 7))
+ fprintf(outfile, "%s", buf + 7);
+ else {
+ fprintf(stderr, "Syntax error in line:\n%s\n", buf);
+ return 1;
+ }
+ fflush(outfile);
+ }
+ if (feof(infile)) {
+ fprintf(outfile, "\n");
+ return 0;
+ } else {
+ fprintf(stderr, "Error reading file\n");
+ return 1;
+ }
+}
+
diff --git a/src/powwow-muc.c b/src/powwow-muc.c
new file mode 100644
index 0000000..aade7f0
--- /dev/null
+++ b/src/powwow-muc.c
@@ -0,0 +1,307 @@
+#include <stdlib.h>
+#include <curses.h>
+#include <stdio.h>
+#include <string.h>
+
+/* Curses based powwow movie player.
+ * Author: Steve Slaven - http://hoopajoo.net
+ *
+ * This program 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 2 of the License, or
+ * (at your option) any later version.
+ *
+ */
+
+#define JUMP_SMALL 1000
+#define JUMP_BIG 5000
+#define MAX_SPEED 20
+
+/* Speed is a variable from 0 - 9, where 5 = normal and > 5 is faster */
+int main( int argc, char *argv[] ) {
+ WINDOW *text, *status;
+ int speed = 5;
+ int key, sleep, orig, color, looping, cursx, cursy;
+ size_t i;
+ FILE *in;
+ char line[ 1000 ], *displine, action[ 100 ];
+ long curr_pos, file_size, new_pos;
+
+ if( argc < 2 ) {
+ fprintf( stderr,
+ "powwow mud cinema v%s\n"
+ "Author: Steve Slaven - http://hoopajoo.net\n"
+ "\n"
+ "Usage: powwow-muc file\n"
+ "\n"
+ "file should be a #movie file generated in powwow\n",
+ VERSION );
+ exit( 1 );
+ }
+
+ /* Validate our file passed */
+ if( ! ( in = fopen( argv[ 1 ], "ro" ) ) ) {
+ perror( "Unable to open file" );
+ exit( 1 );
+ }
+
+ /* Get file size */
+ fseek( in, 0, SEEK_END );
+ file_size = ftell( in );
+ rewind( in );
+
+ /* Setup some basic stuff */
+ initscr();
+ cbreak();
+ noecho();
+
+ /* Initialize color */
+ start_color();
+
+ /* this *should* init the default ansi colors... :/ */
+ for( i = 0; i < 16; i++ )
+ init_pair( i, i, COLOR_BLACK );
+
+ /* Create our windows, a status bar on the bottom and a
+ scrollable window on top */
+ text = newwin( LINES - 2, COLS, 0, 0 );
+ status = newwin( 2, COLS, LINES - 2, 0 );
+
+ keypad( stdscr, TRUE );
+ scrollok( text, TRUE );
+ leaveok( text, TRUE );
+ leaveok( status, TRUE );
+ /* wattron( status, A_BOLD ); */
+ wbkgd( status, A_REVERSE );
+
+ /* instructions */
+ mvwprintw( status, 0, 0,
+ "(q)uit (r)ew small (R)ew large (f)orw small (F)orw large (0-9)(+)(-) speed" );
+
+ /* Main loop */
+ refresh();
+ timeout( 0 );
+ looping = 1;
+ while( looping ) {
+ memset( line, 0, sizeof line );
+ fgets( line, sizeof line, in );
+
+ /* get file pos */
+ new_pos = curr_pos = ftell( in );
+
+ /* handle disp or other */
+ displine = NULL;
+ if( strncmp( line, "line", 4 ) == 0 ) {
+ displine = &line[ 5 ];
+ sleep = 0;
+ }else if( strncmp( line, "prompt", 5 ) == 0 ) {
+ displine = &line[ 7 ];
+ /* Munch newline */
+ line[ strlen( line ) - 1 ] = 0;
+ sleep = 0;
+ }else if( strncmp( line, "sleep", 5 ) == 0 ) {
+ sscanf( line, "sleep %d", &sleep );
+ }else if( line[ 0 ] == '#' ) { /* custom extension for commenting logs */
+ sscanf( line, "#%d", &sleep );
+ if( sleep > 0 )
+ sleep *= 100; /* comment sleep is in seconds */
+
+ /* Skip to space */
+ displine = line;
+ while( displine[ 0 ] != ' ' && displine[ 0 ] != 0 ) {
+ displine++;
+ }
+ displine++;
+
+ /* We will go ahead and display it here, in bold, then
+ null out displine so it will not display later */
+ wattron( text, A_REVERSE );
+ wprintw( text, "##==> %s", displine );
+ wattroff( text, A_REVERSE );
+ displine = NULL;
+ }
+
+ /* suck out action for display */
+ sscanf( line, "%99s", action );
+
+ /* Modify sleep time according to speed, zero is fast as you can go, 1 == pause */
+ orig = sleep;
+ if( speed > 5 ) {
+ sleep /= ( ( speed - 5 ) * 2 );
+ }else{
+ sleep *= ( 6 - speed );
+ }
+
+ /* Handle pause */
+ if( speed == 0 )
+ sleep = -1;
+
+ /* handle insane speed */
+ /* if( speed == 9 )
+ sleep = 0; */
+
+ /* Setup sleeptime for getch() */
+ timeout( sleep );
+
+ /* Update status line */
+ mvwprintw( status, 1, 0,
+ "%7d/%7d/%2d%% Speed: %d (5=normal,0=pause) Cmd: %-6s (%d/%d)\n",
+ curr_pos, file_size, curr_pos * 100 / file_size,
+ speed, action, sleep, orig );
+ wrefresh( status );
+
+ /* Disp if we found an offset to do */
+ if( displine != NULL ) {
+ /* handle converting ansi colors to curses attrs */
+ for( i = 0; i < strlen( displine ); i++ ) {
+ if( displine[ i ] == 0x1b ) {
+ /* this is super crappy ansi color decoding */
+ i++;
+ if( strncmp( &displine[ i ], "[3", 2 ) == 0 ) {
+ /* start a color */
+ sscanf( &displine[ i ], "[3%dm", &color );
+ wattron( text, COLOR_PAIR( color ) );
+ }else if( strncmp( &displine[ i ], "[9", 2 ) == 0 ) {
+ /* start a high color */
+ sscanf( &displine[ i ], "[9%dm", &color );
+ wattron( text, COLOR_PAIR( color ) );
+ wattron( text, A_BOLD );
+ }else if( strncmp( &displine[ i ], "[1", 2 ) == 0 ) {
+ wattron( text, A_BOLD );
+ }else if( strncmp( &displine[ i ], "[0", 2 ) == 0 ) {
+ /* end color, color will (should?) still be set from last color */
+ /* wattr_off( text, COLOR_PAIR( color ), NULL ); */
+ wattrset( text, A_NORMAL );
+ }
+ /* eat chars to the next m */
+ while( displine[ i ] != 'm' && displine != 0 )
+ i++;
+ }else{
+ waddch( text, (unsigned char)displine[ i ] );
+ }
+ }
+ }
+
+ /* check if we are at EOF and override timeout */
+ if( curr_pos == file_size ) {
+ wprintw( text, "\n**** END ***\n" );
+ timeout( -1 );
+ }
+
+ wrefresh( text );
+
+ /* Move the cursor to the end of the text window, so it looks
+ like a real session */
+ getyx( text, cursy, cursx );
+ move( cursy, cursx );
+
+ key = getch();
+
+ switch( key ) {
+ case 'Q':
+ case 'q':
+ looping = 0;
+ break;
+ case '+':
+ case '=':
+ speed++;
+ break;
+ case '-':
+ speed--;
+ break;
+
+ case '1':
+ speed = 1;
+ break;
+ case '2':
+ speed = 2;
+ break;
+ case '3':
+ speed = 3;
+ break;
+ case '4':
+ speed = 4;
+ break;
+ case '5':
+ speed = 5;
+ break;
+ case '6':
+ speed = 6;
+ break;
+ case '7':
+ speed = 7;
+ break;
+ case '8':
+ speed = 8;
+ break;
+ case '9':
+ speed = 9;
+ break;
+ case '0':
+ speed = 0;
+ break;
+
+ case 'r':
+ new_pos -= JUMP_SMALL;
+ break;
+ case 'R':
+ new_pos -= JUMP_BIG;
+ break;
+ case 'f':
+ new_pos += JUMP_SMALL;
+ break;
+ case 'F':
+ new_pos += JUMP_BIG;
+ break;
+
+ default:
+ break;
+ }
+
+ /* Validate speed is ok */
+ if( speed > MAX_SPEED )
+ speed = MAX_SPEED;
+
+ if( speed < 0 )
+ speed = 0;
+
+ /* Check if we are moving the seek */
+ if( new_pos != curr_pos ) {
+ wattron( text, A_BOLD );
+ if( new_pos > file_size )
+ new_pos = file_size;
+
+ if( new_pos < 0 )
+ new_pos = 0;
+
+ wprintw( text,
+ "\n=============\nMoving from %d to file offset %d\n",
+ curr_pos, new_pos );
+
+ /* calcs offsets because we may want to seek to
+ 0, which using SEEK_CUR won't let us do without
+ some other error checking that I don't want to do */
+ fseek( in, new_pos, SEEK_SET );
+
+ /* read to next newline so we don't break up
+ lines seeking around */
+ fgets( line, sizeof line, in );
+ new_pos = ftell( in );
+
+ /* Make a note of moving */
+ wprintw( text,
+ "Final offset after adjusting for newline: %d (%d)\n=============\n",
+ new_pos, new_pos - curr_pos );
+ wattroff( text, A_BOLD );
+ }
+ }
+
+ /* Cleanup */
+ delwin( text );
+ delwin( status );
+ endwin();
+
+ fclose( in );
+
+ return( 0 );
+}
diff --git a/src/ptr.c b/src/ptr.c
new file mode 100644
index 0000000..5878cd4
--- /dev/null
+++ b/src/ptr.c
@@ -0,0 +1,586 @@
+/*
+ * data.c -- basic data structures and functions to manipulate them
+ *
+ * Copyright (C) 1998 by Massimiliano Ghilardi
+ *
+ * This program 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 2 of the License, or
+ * (at your option) any later version.
+ *
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+#include <time.h>
+#include <sys/time.h>
+
+#include "defines.h"
+#include "main.h"
+#include "utils.h"
+#include "eval.h"
+
+/*
+ * create a new, empty ptr.
+ * return NULL if max == 0
+ */
+ptr ptrnew(int max)
+{
+ ptr p = (ptr)0;
+
+ if (max == 0)
+ ;
+ else if (limit_mem && max > limit_mem)
+ error = MEM_LIMIT_ERROR;
+ else if (max < 0 || max + sizeofptr < max) /* overflow! */
+ error = NO_MEM_ERROR;
+ else if ((p = (ptr)malloc(max + sizeofptr))) {
+ p->signature = PTR_SIG;
+ p->max = max;
+ ptrdata(p)[p->len = 0] = '\0';
+ } else
+ error = NO_MEM_ERROR;
+ return p;
+}
+
+/*
+ * create a new ptr giving an initial contents,
+ * which gets duplicated.
+ *
+ * warning: newmax could be so small that we must truncate the copied data!
+ */
+ptr ptrdup2(ptr src, int newmax)
+{
+ ptr p = (ptr)0;
+
+ if (newmax == 0)
+ ;
+ else if (newmax < 0 || newmax + sizeofptr < newmax)
+ error = NO_MEM_ERROR;
+ else if (limit_mem && newmax > limit_mem)
+ error = MEM_LIMIT_ERROR;
+ else if (!src)
+ p = ptrnew(newmax);
+ else if ((p = malloc(newmax + sizeofptr))) {
+ p->signature = PTR_SIG;
+ p->max = newmax;
+ if (newmax > ptrlen(src))
+ newmax = ptrlen(src);
+ memmove(ptrdata(p), ptrdata(src), p->len = newmax);
+ ptrdata(p)[newmax] = '\0';
+ } else
+ error = NO_MEM_ERROR;
+ return p;
+}
+
+ptr ptrdup(ptr src)
+{
+ if (!src)
+ return src;
+ return ptrdup2(src, ptrlen(src));
+}
+
+/* delete (free) a ptr */
+void _ptrdel(ptr p)
+{
+ if (p && p->signature == PTR_SIG)
+ free((void *)p);
+ //else
+ //fprintf( stderr, "Tried to free non ptr @%x\n", p );
+}
+
+/* clear a ptr */
+void ptrzero(ptr p)
+{
+ if (p) {
+ p->len = 0;
+ ptrdata(p)[0] = '\0';
+ }
+}
+
+/* truncate a ptr to len chars */
+void ptrtrunc(ptr p, int len)
+{
+ if (p) {
+ if (len < 0 || len > ptrlen(p))
+ return;
+ ptrdata(p)[p->len = len] = '\0';
+ }
+}
+
+/* shrink a ptr by len chars */
+void ptrshrink(ptr p, int len)
+{
+ if (p) {
+ if (len < 0 || len > ptrlen(p))
+ return;
+ ptrdata(p)[p->len -= len] = '\0';
+ }
+}
+
+/*
+ * concatenate two ptr (ptrcat) or a ptr and a char* (ptrmcat)
+ * result may be a _newly_allocated_ ptr if original one
+ * is too small or if it is soooo big that we are wasting tons of memory.
+ * In both cases, the original one may get deleted (freed)
+ * You have been warned! Don't use any statically created ptr for
+ * write operations, and you will be fine.
+ */
+ptr __ptrmcat(ptr dst, char *src, int len, int shrink)
+{
+ int newmax, failmax, overlap;
+ char mustalloc;
+
+ if (!src || len <= 0)
+ return dst;
+ if (len + sizeofptr < 0) {
+ /* overflow! */
+ error = NO_MEM_ERROR;
+ return dst;
+ }
+
+ if (!dst) {
+ failmax = len;
+ mustalloc = 1;
+ } else {
+ failmax = ptrlen(dst) + len;
+ mustalloc = ptrmax(dst) < ptrlen(dst) + len;
+
+ if (shrink && ptrmax(dst) > PARAMLEN
+ && ptrmax(dst)/4 > ptrlen(dst) + len)
+ /* we're wasting space, shrink dst */
+ mustalloc = 1;
+ }
+
+ if (failmax + sizeofptr < 0) {
+ /* overflow! */
+ error = NO_MEM_ERROR;
+ return dst;
+ }
+
+ if (mustalloc) {
+ /* dst must be (re)allocated */
+ ptr p;
+
+ /* ugly but working: check for overlapping dst and src */
+ if (dst && src >= ptrdata(dst) && src < ptrdata(dst) + ptrmax(dst))
+ overlap = 1;
+ else
+ overlap = 0;
+
+ /* find a suitable new size */
+ if (limit_mem && failmax > limit_mem) {
+ error = MEM_LIMIT_ERROR;
+ return dst;
+ }
+ if (failmax < PARAMLEN / 2)
+ newmax = PARAMLEN;
+ else if (failmax / 1024 < PARAMLEN && failmax + PARAMLEN + sizeofptr > 0)
+ newmax = failmax + PARAMLEN;
+ else
+ newmax = failmax;
+ if (limit_mem && newmax > limit_mem) {
+ if (len + (dst ? ptrlen(dst) : 0) > limit_mem)
+ len = limit_mem - (dst ? ptrlen(dst) : 0);
+ if (len < 0)
+ len = 0;
+ newmax = limit_mem;
+ }
+ if ((p = (ptr)realloc((void *)dst, newmax + sizeofptr))) {
+ if (dst == NULL)
+ p->signature = PTR_SIG;
+ if (overlap)
+ src = ptrdata(p) + (src - ptrdata(dst));
+ if (!dst)
+ p->len = 0;
+ p->max = newmax;
+ dst = p;
+ } else if ((p = ptrdup2(dst, newmax))) {
+ if (overlap)
+ src = ptrdata(p) + (src - ptrdata(dst));
+ ptrdel(dst);
+ dst = p;
+ } else {
+ error = NO_MEM_ERROR;
+ return dst;
+ }
+ }
+ if (ptrdata(dst) + ptrlen(dst) != src)
+ memmove(ptrdata(dst) + ptrlen(dst), src, len);
+ dst->len += len;
+ ptrdata(dst)[ptrlen(dst)] = '\0';
+ return dst;
+}
+
+ptr ptrmcat(ptr dst, char *src, int len)
+{
+ return __ptrmcat(dst, src, len, 1);
+}
+
+ptr ptrcat(ptr dst, ptr src)
+{
+ if (src)
+ return __ptrmcat(dst, ptrdata(src), ptrlen(src), 1);
+ return dst;
+}
+
+/*
+ * copy a ptr into another (ptrcpy), or a char* into a ptr (ptrmcpy);
+ * same warning as above if dst is too small or way too big.
+ */
+ptr __ptrmcpy(ptr dst, char *src, int len, int shrink)
+{
+ int newmax, failmax = len, overlap;
+ char mustalloc;
+
+ if (!src || len<=0) {
+ if (len>=0)
+ ptrzero(dst);
+ return dst;
+ }
+ if (failmax + sizeofptr < 0) {
+ /* overflow! */
+ error = NO_MEM_ERROR;
+ return dst;
+ }
+
+ if (!dst) {
+ mustalloc = 1;
+ } else {
+ mustalloc = ptrmax(dst) < len;
+
+ if (shrink && ptrmax(dst) > PARAMLEN && ptrmax(dst)/4 > len)
+ /* we're wasting space, shrink dst */
+ mustalloc = 1;
+ }
+
+ if (mustalloc) {
+ /* dst must be (re)allocated */
+ ptr p;
+
+ /* ugly but working: check for overlapping dst and src */
+ if (dst && src >= ptrdata(dst) && src < ptrdata(dst) + ptrmax(dst))
+ overlap = 1;
+ else
+ overlap = 0;
+
+ /* find a suitable new size */
+ if (limit_mem && failmax > limit_mem) {
+ error = MEM_LIMIT_ERROR;
+ return dst;
+ }
+ if (failmax < PARAMLEN / 2)
+ newmax = PARAMLEN;
+ else if (failmax / 1024 < PARAMLEN && failmax + PARAMLEN + sizeofptr > 0)
+ newmax = failmax + PARAMLEN;
+ else
+ newmax = failmax;
+ if (limit_mem && newmax > limit_mem) {
+ if (len > limit_mem)
+ len = limit_mem;
+ newmax = limit_mem;
+ }
+
+ if ((p = (ptr)realloc((void *)dst, newmax + sizeofptr))) {
+ if (dst == NULL)
+ p->signature = PTR_SIG;
+ if (overlap)
+ src = ptrdata(p) + (src - ptrdata(dst));
+ if (!dst)
+ p->len = 0;
+ p->max = newmax;
+ dst = p;
+ } else if ((p = ptrdup2(dst, newmax))) {
+ if (overlap)
+ src = ptrdata(p) + (src - ptrdata(dst));
+ ptrdel(dst);
+ dst = p;
+ } else {
+ error = NO_MEM_ERROR;
+ return dst;
+ }
+ }
+ if (ptrdata(dst) != src)
+ memmove(ptrdata(dst), src, len);
+ dst->len = len;
+ ptrdata(dst)[ptrlen(dst)] = '\0';
+ return dst;
+}
+
+ptr ptrmcpy(ptr dst, char *src, int len)
+{
+ return __ptrmcpy(dst, src, len, 1);
+}
+
+ptr ptrcpy(ptr dst, ptr src)
+{
+ if (src)
+ return __ptrmcpy(dst, ptrdata(src), ptrlen(src), 1);
+ ptrzero(dst);
+ return dst;
+}
+
+/* enlarge a ptr by len chars. create new if needed */
+ptr ptrpad(ptr p, int len)
+{
+ if (!p) {
+ if (len<=0)
+ return p;
+ else
+ return ptrnew(len);
+ }
+ if (len > ptrmax(p) - ptrlen(p)) {
+ /* must realloc the ptr */
+ len += ptrlen(p);
+ if (len < 0) {
+ /* overflow! */
+ error = NO_MEM_ERROR;
+ return p;
+ }
+ /*
+ * cheat: we use ptrmcpy with src==dst
+ * and do an out-of-boud read of src.
+ * works since dst (==src) gets enlarged
+ * before doing the copy.
+ */
+ p = ptrmcpy(p, ptrdata(p), len);
+ } else {
+ p->len += len;
+ ptrdata(p)[ptrlen(p)] = '\0';
+ }
+ return p;
+}
+
+/* set a ptr to be len chars at minimum. create new if needed */
+ptr ptrsetlen(ptr p, int len)
+{
+ if (!p) {
+ if (len<=0)
+ return p;
+ else {
+ if ((p = ptrnew(len)))
+ ptrdata(p)[p->len = len] = '\0';
+ return p;
+ }
+ }
+ return ptrpad(p, len - ptrlen(p));
+}
+
+/*
+ * compare two ptr (ptrcmp) or a ptr and a char* (ptrmcmp)
+ * if one is a truncated copy of the other, the shorter is considered smaller
+ */
+int ptrmcmp(ptr p, char *q, int lenq)
+{
+ int res;
+ if (!p || !ptrlen(p)) {
+ if (!q || lenq<=0)
+ /* both empty */
+ res = 0;
+ else
+ res = -1;
+ } else if (!q || lenq<=0)
+ res = 1;
+ else if ((res = memcmp(ptrdata(p), q, MIN2(ptrlen(p), lenq))))
+ ;
+ else if (ptrlen(p) < lenq)
+ res = -1;
+ else if (ptrlen(p) > lenq)
+ res = 1;
+ else
+ res = 0;
+ return res;
+}
+
+int ptrcmp(ptr p, ptr q)
+{
+ if (q)
+ return ptrmcmp(p, ptrdata(q), ptrlen(q));
+ else if (p)
+ return 1;
+ else
+ return 0;
+}
+
+/*
+ * find first occurrence of c in p
+ * return NULL if none found.
+ */
+char *ptrchr(ptr p, char c)
+{
+ if (p)
+ return (char *)memchr(ptrdata(p), c, ptrlen(p));
+ return (char*)p; /* shortcut for NULL */
+}
+
+/*
+ * find last occurrence of c in p
+ * return NULL if none found.
+ */
+char *memrchr(char *p, int lenp, char c)
+{
+ char *v, *s = p;
+
+ if (!p || lenp<=0)
+ return NULL;
+
+ v = s + lenp - 1;
+ while (v != s && *v != c) {
+ v--;
+ }
+ if (v != s)
+ return v;
+ else
+ return NULL;
+}
+
+char *ptrrchr(ptr p, char c)
+{
+ if (p)
+ return memrchr(ptrdata(p), ptrlen(p), c);
+ return (char*)p; /* shortcut for NULL */
+}
+
+#ifndef _GNU_SOURCE
+/*
+ * find first occurrence of needle in hay
+ *
+ * GNU libc has memmem(), for others we do by hand.
+ */
+char *memfind(char *hay, int haylen, char *needle, int needlelen)
+{
+ char *tmp;
+
+ if (!hay || haylen<=0 || needlelen<0)
+ return NULL;
+ if (!needle || !needlelen)
+ return hay;
+
+ while (haylen >= needlelen) {
+ /* find a matching first char */
+ if ((tmp = memchr(hay, *needle, haylen))) {
+ if ((haylen -= (tmp-hay)) < needlelen)
+ return NULL;
+ hay = tmp;
+ } else
+ return NULL;
+
+ /* got a matching first char,
+ * check the rest */
+ if (!memcmp(needle, tmp, needlelen))
+ return tmp;
+
+ hay++, haylen --;
+ }
+
+ return NULL;
+}
+#endif /* !_GNU_SOURCE */
+
+/*
+ * find first occurrence of q in p,
+ * return NULL if none found.
+ */
+char *ptrmfind(ptr p, char *q, int lenq)
+{
+ if (p) {
+ if (q && lenq>0)
+ return (char *)memfind(ptrdata(p), ptrlen(p), q, lenq);
+ return ptrdata(p);
+ }
+ return (char*)p; /* shortcut for NULL */
+}
+
+char *ptrfind(ptr p, ptr q)
+{
+ if (p) {
+ if (q)
+ return (char *)memfind(ptrdata(p), ptrlen(p), ptrdata(q), ptrlen(q));
+ return ptrdata(p);
+ }
+ return (char*)p; /* shortcut for NULL */
+}
+
+
+/*
+ * Scan p for the first occurrence of one of the characters in q,
+ * return NULL if none of them is found.
+ */
+char *memchrs(char *p, int lenp, char *q, int lenq)
+{
+ char *endp;
+
+ if (!q || lenq<=0)
+ return p;
+ if (!p || lenp<=0)
+ return NULL;
+
+ endp = p + lenp;
+
+ while (p < endp && !memchr(q, *p, lenq))
+ p++;
+
+ if (p == endp)
+ return NULL;
+ return p;
+}
+
+char *ptrmchrs(ptr p, char *q, int lenq)
+{
+ if (p)
+ return memchrs(ptrdata(p), ptrlen(p), q, lenq);
+ return (char*)p; /* shortcut for NULL */
+}
+
+char *ptrchrs(ptr p, ptr q)
+{
+ if (p) {
+ if (q)
+ return memchrs(ptrdata(p), ptrlen(p), ptrdata(q), ptrlen(q));
+ return ptrdata(p);
+ }
+ return (char*)p; /* shortcut for NULL */
+}
+
+
+/*
+ * Scan p for the last occurrence of one of the characters in q,
+ * return NULL if none of them is found.
+ */
+char *memrchrs(char *p, int lenp, char *q, int lenq)
+{
+ if (!p || lenp<=0) {
+ if (!q || lenq<=0)
+ return p;
+ else
+ return NULL;
+ }
+
+ p += lenp;
+ if (!q || lenq<=0)
+ return p;
+ do {
+ lenp--, p--;
+ } while (lenp >= 0 && !memchr(q, *p, lenq));
+
+ if (lenp < 0)
+ return NULL;
+ return p;
+}
+
+char *ptrmrchrs(ptr p, char *q, int lenq)
+{
+ if (p)
+ return memrchrs(ptrdata(p), ptrlen(p), q, lenq);
+ return (char*)p; /* shortcut for NULL */
+}
+
+char *ptrrchrs(ptr p, ptr q)
+{
+ if (p && q)
+ return memrchrs(ptrdata(p), ptrlen(p), ptrdata(q), ptrlen(q));
+ return p ? ptrdata(p) + ptrlen(p) : (char*)p; /* shortcut for NULL */
+}
+
diff --git a/src/ptr.h b/src/ptr.h
new file mode 100644
index 0000000..2a19843
--- /dev/null
+++ b/src/ptr.h
@@ -0,0 +1,75 @@
+/*
+ * ptr.h -- type definitions for ptr (aka "pointer"), a char* replacement
+ * which allows for '\0' inside a string.
+ */
+
+#ifndef _PTR_H_
+#define _PTR_H_
+
+typedef struct s_ptr {
+ int len;
+ int max;
+ int signature;
+} _ptr;
+
+typedef _ptr * ptr;
+
+#define sizeofptr ((int)(1 + sizeof(_ptr)))
+
+/* the + 0 below is to prohibit using the macros for altering the ptr */
+#define ptrlen(p) ((p)->len + 0)
+#define ptrmax(p) ((p)->max + 0)
+#define ptrdata(p) ((char *)((ptr)(p) + 1))
+/* if p is a valid (ptr), ptrdata(p) is guaranteed to be a valid (char *) */
+
+ptr ptrnew(int max);
+ptr ptrdup2(ptr src, int newmax);
+ptr ptrdup(ptr src);
+
+#define PTR_SIG 91887
+#define ptrdel(x) _ptrdel(x);x=(ptr)0;
+void _ptrdel(ptr p);
+
+void ptrzero(ptr p);
+void ptrshrink(ptr p, int len);
+void ptrtrunc(ptr p, int len);
+ptr ptrpad(ptr p, int len);
+ptr ptrsetlen(ptr p, int len);
+
+ptr ptrcpy(ptr dst, ptr src);
+ptr ptrmcpy(ptr dst, char *src, int len);
+
+ptr ptrcat(ptr dst, ptr src);
+ptr ptrmcat(ptr dst, char *src, int len);
+
+
+ptr __ptrcat(ptr dst, char *src, int len, int shrink);
+ptr __ptrmcpy(ptr dst, char *src, int len, int shrink);
+
+int ptrcmp(ptr p, ptr q);
+int ptrmcmp(ptr p, char *q, int lenq);
+
+char *ptrchr(ptr p, char c);
+char *ptrrchr(ptr p, char c);
+
+char *ptrfind(ptr p, ptr q);
+char *ptrmfind(ptr p, char *q, int lenq);
+
+char *ptrchrs(ptr p, ptr q);
+char *ptrmchrs(ptr p, char *q, int lenq);
+char *ptrrchrs(ptr p, ptr q);
+char *ptrmrchrs(ptr p, char *q, int lenq);
+
+char *memchrs(char *p, int lenp, char *q, int lenq);
+char *memrchrs(char *p, int lenp, char *q, int lenq);
+#ifdef _GNU_SOURCE
+# define memfind memmem
+#else
+char *memfind(char *hay, int haylen, char *needle, int needlelen);
+/* TODO: watch memrchr, it is defined differently here than under _GNU_SOURCE,
+ * so it could cause bizarre results if a module makes use of a library that
+ * uses it */
+char *memrchr(char *p, int lenp, char c);
+#endif
+
+#endif /* _PTR_H_ */
diff --git a/src/tcp.c b/src/tcp.c
new file mode 100644
index 0000000..de54a87
--- /dev/null
+++ b/src/tcp.c
@@ -0,0 +1,1045 @@
+/*
+ * tcp.c -- telnet protocol communication module for powwow
+ *
+ * Copyright (C) 1998,2002 by Massimiliano Ghilardi
+ *
+ * This program 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 2 of the License, or
+ * (at your option) any later version.
+ *
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <signal.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <signal.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <sys/time.h>
+#include <netdb.h>
+#include <netinet/in.h>
+#include <netinet/tcp.h>
+#include <arpa/telnet.h>
+#ifndef TELOPT_NAWS
+# define TELOPT_NAWS 31
+#endif
+#include <arpa/inet.h>
+#ifndef NEXT
+# include <unistd.h>
+#endif
+
+#ifdef TERM
+# include "client.h"
+#endif
+
+#include "defines.h"
+#include "main.h"
+#include "utils.h"
+#include "tcp.h"
+#include "tty.h"
+#include "edit.h"
+#include "beam.h"
+#include "log.h"
+
+#ifdef TELOPTS
+# define TELOPTSTR(n) ((n) > NTELOPTS ? "unknown" : telopts[n])
+#endif
+
+int tcp_fd = -1; /* current socket file descriptor
+ * -1 means no socket */
+int tcp_main_fd = -1; /* socket file descriptor of main connect.
+ * -1 means no socket */
+int tcp_max_fd = 0; /* highest used fd */
+
+int tcp_count = 0; /* number of open connections */
+int tcp_attachcount = 0; /* number of spawned commands */
+
+int conn_max_index; /* 1 + highest used conn_list[] index */
+
+connsess conn_list[MAX_CONNECTS]; /* connection list */
+
+byte conn_table[MAX_FDSCAN]; /* fd -> index translation table */
+
+fd_set fdset; /* set of descriptors to select() on */
+
+/*
+ * process suboptions.
+ * so far, only terminal type is processed but future extensions are
+ * window size, X display location, etc.
+ */
+static void dosubopt(byte *str)
+{
+ char buf[256], *term;
+ int len, err;
+
+ if (str[0] == TELOPT_TTYPE) {
+ if (str[1] == 1) {
+ /* 1 == SEND */
+#ifdef TELOPTS
+ tty_printf("[got SB TERMINAL TYPE SEND]\n");
+#endif
+ if (!(term = getenv("TERM"))) term = "unknown";
+ sprintf(buf, "%c%c%c%c%.*s%c%c", IAC, SB, TELOPT_TTYPE, 0,
+ 256-7, term, IAC, SE); /* 0 == IS */
+
+ len = strlen(term) + 6;
+ while ((err = write(tcp_fd, buf, len)) < 0 && errno == EINTR)
+ ;
+ if (err != len) {
+ errmsg("write subopt to socket");
+ return;
+ }
+#ifdef TELOPTS
+ tty_printf("[sent SB TERMINAL TYPE IS %s]\n", term);
+#endif
+ }
+ }
+}
+
+/*
+ * send an option negotiation
+ * 'what' is one of WILL, WONT, DO, DONT
+ */
+static void sendopt(byte what, byte opt)
+{
+ static byte buf[3] = { IAC, 0, 0 };
+ int i;
+ buf[1] = what; buf[2] = opt;
+
+ while ((i = write(tcp_fd, buf, 3)) < 0 && errno == EINTR)
+ ;
+ if (i != 3) {
+ errmsg("write option to socket");
+ return;
+ }
+
+#ifdef TELOPTS
+ tty_printf("[sent %s %s]\n", (what == WILL) ? "WILL" :
+ (what == WONT) ? "WONT" :
+ (what == DO) ? "DO" : (what == DONT) ? "DONT" : "error",
+ TELOPTSTR(opt));
+#endif
+}
+
+/*
+ * connect to remote host
+ * Warning: some voodoo code here
+ */
+int tcp_connect(const char *addr, int port)
+{
+ struct addrinfo *host_info;
+ struct addrinfo addrinfo;
+ struct sockaddr_in6 address;
+ int err, newtcp_fd;
+
+ status(1);
+
+ memset(&address, 0, sizeof address);
+ memset(&addrinfo, 0, sizeof addrinfo);
+
+ /*
+ * inet_addr has a strange design: It is documented to return -1 for
+ * malformed requests, but it is declared to return unsigned long!
+ * Anyway, this works.
+ */
+
+#ifndef TERM
+ switch (inet_pton(AF_INET6, addr, &address.sin6_addr))
+ {
+ case -1:
+ perror("inet_pton()");
+ return -1;
+ case 1:
+ address.sin6_family = AF_INET6;
+ addrinfo.ai_family = AF_INET6;
+ addrinfo.ai_socktype = SOCK_STREAM;
+ addrinfo.ai_addr = (struct sockaddr *)&address;
+ addrinfo.ai_addrlen = sizeof address;
+ host_info = &addrinfo;
+ break;
+ case 0:
+ if (opt_info)
+ tty_printf("#looking up %s... ", addr);
+ tty_flush();
+ addrinfo.ai_family = AF_INET6;
+ addrinfo.ai_socktype = SOCK_STREAM;
+ addrinfo.ai_flags = AI_V4MAPPED;
+ int gai = getaddrinfo(addr, NULL, &addrinfo, &host_info);
+ if (gai != 0) {
+ tty_printf("failed to look up host: %s\n",
+ gai_strerror(gai));
+ return -1;
+ }
+ if (opt_info)
+ tty_puts("found.\n");
+ break;
+ default:
+ abort();
+ }
+
+ for (; host_info; host_info = host_info->ai_next) {
+ newtcp_fd = socket(host_info->ai_family, host_info->ai_socktype, 0);
+ if (newtcp_fd == -1)
+ continue;
+ if (newtcp_fd >= MAX_FDSCAN) {
+ tty_printf("#connect: #error: too many open connections\n");
+ close(newtcp_fd);
+ return -1;
+ }
+
+ tty_printf("#trying %s... ", addr);
+ tty_flush();
+
+ ((struct sockaddr_in6 *)host_info->ai_addr)->sin6_port = htons(port);
+
+ err = connect(newtcp_fd, host_info->ai_addr, host_info->ai_addrlen);
+
+ if (err == -1) { /* CTRL-C pressed, or other errors */
+ errmsg("connect to host");
+ close(newtcp_fd);
+ return -1;
+ }
+ break;
+ }
+
+ if (host_info == NULL) {
+ errmsg("failed to connect");
+ return -1;
+ }
+
+#else /* term */
+
+ if ((newtcp_fd = connect_server(0)) < 0) {
+ tty_puts("\n#powwow: unable to connect to term server\n");
+ return -1;
+ } else {
+ if (newtcp_fd >= MAX_FDSCAN) {
+ tty_printf("#connect: #error: too many open connections\n");
+ close(newtcp_fd);
+ return -1;
+ }
+ send_command(newtcp_fd, C_PORT, 0, "%s:%d", addr, port);
+ tty_puts("Connected to term server...\n");
+#ifdef TERM_COMPRESS
+ send_command(newtcp_fd, C_COMPRESS, 1, "y");
+#endif
+ send_command(newtcp_fd, C_DUMB, 1, 0);
+ }
+
+#endif /* term */
+
+ tty_puts("connected!\n");
+
+
+ {
+ /*
+ * Now set some options on newtcp_fd :
+ * First, no-nagle
+ */
+ int opt = 1;
+# ifndef SOL_TCP
+# define SOL_TCP IPPROTO_TCP
+# endif
+ if (setsockopt(newtcp_fd, SOL_TCP, TCP_NODELAY, &opt, sizeof(opt)))
+ errmsg("setsockopt(TCP_NODELAY) failed");
+
+ /* TCP keep-alive */
+ if (setsockopt(newtcp_fd, SOL_SOCKET, SO_KEEPALIVE, &opt, sizeof(opt)))
+ errmsg("setsockopt(SO_KEEPALIVE) failed");
+
+ /*
+ * Then, close-on-exec:
+ * we don't want children to inherit the socket!
+ */
+
+ fcntl(newtcp_fd, F_SETFD, FD_CLOEXEC);
+ }
+
+ return newtcp_fd;
+}
+
+/*
+ * we don't expect IAC commands here, except IAC IAC (a protected ASCII 255)
+ * which we replace with a single IAC (a plain ASCII 255)
+ */
+int tcp_unIAC(char *buffer, int len)
+{
+ char *s, *start, warnIACs = 1;
+ if (!memchr(buffer, IAC, len))
+ return len;
+
+ for (s = start = buffer; len > 0; buffer++, len--) {
+ if (buffer[0] == (char)(byte)IAC) {
+ if (len > 1 && buffer[1] == (char)(byte)IAC)
+ buffer++, len--;
+ else if (warnIACs) {
+ PRINTF("#warning: received IAC inside MPI message, treating as IAC IAC.\n");
+ warnIACs = 0;
+ }
+ }
+ *s++ = *buffer;
+ }
+ return s - start;
+}
+
+/*
+ * read a maximum of size chars from remote host
+ * using the telnet protocol. return chars read.
+ */
+int tcp_read(int fd, char *buffer, int maxsize)
+{
+ char state = CONN_LIST(fd).state;
+ char old_state = CONN_LIST(fd).old_state;
+ int i;
+ static byte subopt[MAX_SUBOPT];
+ static int subchars;
+ byte *p, *s, *linestart;
+
+ char *ibuffer = buffer;
+ if (state == GOT_R) {
+ /* make room for the leading \r */
+ ++ibuffer;
+ --maxsize;
+ }
+
+ while ((i = read(fd, ibuffer, maxsize)) < 0 && errno == EINTR)
+ ;
+
+ if (i == 0) {
+ CONN_LIST(fd).state = NORMAL;
+ tcp_close(NULL);
+ return 0;
+ }
+ if (i < 0) {
+ errmsg("read from socket");
+ return 0;
+ }
+
+ /*
+ * scan through the buffer,
+ * interpret telnet protocol escapes and MUME MPI messages
+ */
+ p = (byte *)buffer;
+ for (s = linestart = (byte *)ibuffer; i; s++, i--) {
+ switch (state) {
+ case NORMAL:
+ case ALTNORMAL:
+ case GOT_R:
+ case GOT_N:
+ /*
+ * Some servers like to send NULs and other funny chars.
+ * Clean up as much as possible.
+ */
+ switch (*s) {
+ case IAC:
+ old_state = state; /* remember state to return to after IAC processing */
+ state = GOTIAC;
+ break;
+ case '\r':
+ /* start counting \r, unless just got \n */
+ if (state == NORMAL || state == ALTNORMAL) {
+ /*
+ * ALTNORMAL is safe here: \r cannot be in MPI header,
+ * and any previous MPI header has already been rejected
+ */
+ state = GOT_R;
+ } else if (state == GOT_N)
+ /* after \n\r, we forbid MPI messages */
+ state = ALTNORMAL;
+ break;
+ case '\n':
+ state = GOT_N;
+ *p++ = *s;
+ linestart = p;
+ break;
+ case '\0':
+ /* skip NULs */
+ break;
+ default:
+ /* first, flush any missing \r */
+ if (state == GOT_R)
+ *p++ = '\r';
+
+ *p++ = *s;
+
+ /* check for MUME MPI messages: */
+ if (p - linestart == MPILEN && !memcmp(linestart, MPI, MPILEN)) {
+ if (!(CONN_LIST(fd).flags & IDEDITOR)) {
+ PRINTF("#warning: MPI message received without #request editor!\n");
+ } else if (state == ALTNORMAL) {
+ /* no MPI messages after \n\r */
+ PRINTF("#warning: MPI attack?\n");
+ } else {
+ subchars = process_message((char*)s+1, i-1);
+ /* no +MPILEN here, as it was already processed. */
+ s += subchars; i-= subchars;
+ p = linestart;
+ }
+ }
+
+ if (state != ALTNORMAL)
+ state = NORMAL;
+ break;
+ }
+ break;
+
+ case GOTIAC:
+ switch (*s) {
+ case WILL:
+ state = GOTWILL; break;
+ case WONT:
+ state = GOTWONT; break;
+ case DO:
+ state = GOTDO; break;
+ case DONT:
+ state = GOTDONT; break;
+ case SB: /* BUG (multiple connections): */
+ state = GOTSB; /* there is only one subopt buffer */
+ subchars = 0;
+ break;
+ case IAC:
+ *p++ = IAC;
+ state = old_state;
+ break;
+ case GA:
+ /* I should handle GA as end-of-prompt marker one day */
+ /* one day has come ;) - Max */
+ prompt_set_iac((char*)p);
+ state = old_state;
+ break;
+ default:
+ /* ignore the rest of the telnet commands */
+#ifdef TELOPTS
+ tty_printf("[skipped IAC <%d>]\n", *s);
+#endif
+ state = old_state;
+ break;
+ }
+ break;
+
+ case GOTWILL:
+#ifdef TELOPTS
+ tty_printf("[got WILL %s]\n", TELOPTSTR(*s));
+#endif
+ switch(*s) {
+ case TELOPT_ECHO:
+ /* host echoes, turn off echo here
+ * but only for main connection, since we do not want
+ * subsidiary connection password entries to block anything
+ * in the main connection
+ */
+ if (fd == tcp_main_fd)
+ linemode |= LM_NOECHO;
+ sendopt(DO, *s);
+ break;
+ case TELOPT_SGA:
+ /* this can't hurt */
+ linemode |= LM_CHAR;
+ tty_special_keys();
+ sendopt(DO, *s);
+ break;
+ default:
+ /* don't accept other options */
+ sendopt(DONT, *s);
+ break;
+ }
+ state = old_state;
+ break;
+
+ case GOTWONT:
+#ifdef TELOPTS
+ tty_printf("[got WONT %s]\n", TELOPTSTR(*s));
+#endif
+ if (*s == TELOPT_ECHO) {
+ /* host no longer echoes, we do it instead */
+ linemode &= ~LM_NOECHO;
+ }
+ /* accept any WONT */
+ sendopt(DONT, *s);
+ state = old_state;
+ break;
+
+ case GOTDO:
+#ifdef TELOPTS
+ tty_printf("[got DO %s]\n", TELOPTSTR(*s));
+#endif
+ switch(*s) {
+ case TELOPT_SGA:
+ linemode |= LM_CHAR;
+ tty_special_keys();
+ /* FALLTHROUGH */
+ case TELOPT_TTYPE:
+ sendopt(WILL, *s);
+ break;
+ case TELOPT_NAWS:
+ sendopt(WILL, *s);
+ tcp_write_tty_size();
+ break;
+ default:
+ /* accept nothing else */
+ sendopt(WONT, *s);
+ break;
+ }
+ state = old_state;
+ break;
+
+ case GOTDONT:
+#ifdef TELOPTS
+ tty_printf("[got DONT %s]\n", TELOPTSTR(*s));
+#endif
+ if (*s == TELOPT_SGA) {
+ linemode &= ~LM_CHAR;
+ tty_special_keys();
+ }
+ sendopt(WONT, *s);
+ state = old_state;
+ break;
+
+ case GOTSB:
+ if (*s == IAC) {
+ state = GOTSBIAC;
+ } else {
+ if (subchars < MAX_SUBOPT)
+ subopt[subchars++] = *s;
+ }
+ break;
+
+ case GOTSBIAC:
+ if (*s == IAC) {
+ if (subchars < MAX_SUBOPT)
+ subopt[subchars++] = IAC;
+ state = GOTSB;
+ } else if (*s == SE) {
+ subopt[subchars] = '\0';
+ dosubopt(subopt);
+ state = old_state;
+ } else {
+ /* problem! I haven't the foggiest idea of what to do here.
+ * I'll just ignore it and hope it goes away. */
+ PRINTF("#telnet: got IAC <%d> instead of IAC SE!\n", (int)*s);
+ state = old_state;
+ }
+ break;
+ }
+ }
+ CONN_LIST(fd).state = state;
+ CONN_LIST(fd).old_state = old_state;
+
+ if (!(CONN_LIST(tcp_fd).flags & SPAWN)) {
+ log_write(buffer, (char *)p - buffer, 0);
+ }
+
+ return (char *)p - buffer;
+}
+
+static void internal_tcp_raw_write(int fd, const char *data, int len)
+{
+ while (len > 0) {
+ int i;
+ while ((i = write(fd, data, len)) < 0 && errno == EINTR)
+ ;
+ if (i < 0) {
+ errmsg("write to socket");
+ break;
+ }
+ data += i;
+ len -= i;
+ }
+}
+
+void tcp_raw_write(int fd, const char *data, int len)
+{
+ tcp_flush();
+ internal_tcp_raw_write(fd, data, len);
+}
+
+/* write data, escape any IACs */
+void tcp_write_escape_iac(int fd, const char *data, int len)
+{
+ tcp_flush();
+
+ for (;;) {
+ const char *iac = memchr(data, IAC, len);
+ size_t l = iac ? (iac - data) + 1 : len;
+ internal_tcp_raw_write(fd, data, l);
+ if (iac == NULL)
+ return;
+ internal_tcp_raw_write(fd, iac, 1);
+ len -= l;
+ data = iac + 1;
+ }
+}
+
+/*
+ * Send current terminal size (RFC 1073)
+ */
+void tcp_write_tty_size(void)
+{
+ static byte buf[] = { IAC, SB, TELOPT_NAWS, 0, 0, 0, 0, IAC, SE };
+
+ buf[3] = cols >> 8;
+ buf[4] = cols & 0xff;
+ buf[5] = lines >> 8;
+ buf[6] = lines & 0xff;
+
+ tcp_raw_write(tcp_main_fd, (char *)buf, 9);
+#ifdef TELOPTS
+ tty_printf("[sent term size %d %d]\n", cols, lines);
+#endif
+}
+
+/*
+ * send a string to the main connection on the remote host
+ */
+void tcp_main_write(char *data)
+{
+ tcp_write(tcp_main_fd, data);
+}
+
+
+static char output_buffer[BUFSIZE];
+static int output_len = 0; /* number of characters in output_buffer */
+static int output_socket = -1; /* to which socket buffer should be sent*/
+
+/*
+ * put data in the output buffer for transmission to the remote host
+ */
+void tcp_write(int fd, char *data)
+{
+ char *iacs, *out;
+ int len, space, iacp;
+ len = strlen(data);
+
+ if (tcp_main_fd != -1 && tcp_main_fd == fd) {
+ if (linemode & LM_NOECHO)
+ log_write("", 0, 1); /* log a newline only */
+ else
+ log_write(data, len, 1);
+ reprint_writeline(data);
+ }
+
+ /* must be AFTER reprint_writeline() */
+ if (CONN_LIST(tcp_fd).flags & SPAWN)
+ status(1);
+ else
+ status(-1);
+
+ if (fd != output_socket) { /* is there data to another socket? */
+ tcp_flush(); /* then flush it */
+ output_socket = fd;
+ }
+
+ out = output_buffer + output_len;
+ space = BUFSIZE - output_len;
+
+ while (len) {
+ iacs = memchr(data, IAC, len);
+ iacp = iacs ? iacs - data : len;
+
+ if (iacp == 0) {
+ /* we're at the IAC, send it */
+ if (space < 2) {
+ tcp_flush();
+ out = output_buffer;
+ space = BUFSIZE;
+ }
+ *out++ = (char)IAC; *out++ = (char)IAC; output_len += 2; space -= 2;
+ data++; len--;
+ continue;
+ }
+
+ while (space < iacp) {
+ memcpy(out, data, space);
+ data += space; len -= space;
+ iacp -= space;
+ output_len = BUFSIZE;
+
+ tcp_flush();
+ out = output_buffer;
+ space = BUFSIZE;
+ }
+
+ if (iacp /* && space >= iacp */ ) {
+ memcpy(out, data, iacp);
+ out += iacp; output_len += iacp; space -= iacp;
+ data += iacp; len -= iacp;
+ }
+ }
+ if (!space) {
+ tcp_flush();
+ out = output_buffer;
+ }
+ *out++ = '\n';
+ output_len++;
+}
+
+/*
+ * send all buffered data to the remote host
+ */
+void tcp_flush(void)
+{
+ int n;
+ char *p = output_buffer;
+
+ if (output_len && output_socket == -1) {
+ clear_input_line(1);
+ PRINTF("#no open connections. Use '#connect main <address> <port>' to open a connection.\n");
+ output_len = 0;
+ return;
+ }
+
+ if (!output_len)
+ return;
+
+ while (output_len) {
+ while ((n = write(output_socket, p, output_len)) < 0 && errno == EINTR)
+ ;
+ if (n < 0) {
+ output_len = 0;
+ errmsg("write to socket");
+ return;
+ }
+ sent += n;
+ p += n;
+ output_len -= n;
+ }
+
+ if (CONN_LIST(output_socket).flags & SPAWN)
+ status(1);
+ else
+ /* sent stuff, so we expect a prompt */
+ status(-1);
+}
+
+/*
+ * Below are multiple-connection support functions:
+ */
+
+/*
+ * return connection's fd given id,
+ * or -1 if null or invalid id is given
+ */
+int tcp_find(char *id)
+{
+ int i;
+
+ for (i=0; i<conn_max_index; i++) {
+ if (CONN_INDEX(i).id && !strcmp(CONN_INDEX(i).id, id))
+ return CONN_INDEX(i).fd;
+ }
+ return -1;
+}
+
+/*
+ * show list of open connections
+ */
+void tcp_show(void)
+{
+ int i = tcp_count + tcp_attachcount;
+
+ PRINTF("#%s connection%s opened%c\n", i ? "The following" : "No",
+ i==1 ? " is" : "s are", i ? ':' : '.');
+
+
+ for (i=0; i<conn_max_index; i++)
+ if (CONN_INDEX(i).id && !(CONN_INDEX(i).flags & SPAWN)) {
+ tty_printf("MUD %sactive %s ##%s\t (%s %d)\n",
+ CONN_INDEX(i).flags & ACTIVE ? " " : "non",
+ i == tcp_main_fd ? "(default)" : " ",
+ CONN_INDEX(i).id,
+ CONN_INDEX(i).host, CONN_INDEX(i).port);
+ }
+ for (i=0; i<conn_max_index; i++)
+ if (CONN_INDEX(i).id && (CONN_INDEX(i).flags & SPAWN)) {
+ tty_printf("CMD %sactive %s ##%s\t (%s)\n",
+ CONN_INDEX(i).flags & ACTIVE ? " " : "non",
+ i == tcp_main_fd ? "(default)" : " ",
+ CONN_INDEX(i).id, CONN_INDEX(i).host);
+ }
+}
+
+/*
+ * permanently change main connection
+ */
+void tcp_set_main(int fd)
+{
+ /* GH: reset linemode and prompt */
+ tcp_main_fd = fd;
+ if (linemode & LM_CHAR)
+ linemode = 0, tty_special_keys();
+ else
+ linemode = 0;
+ status(-1);
+ reprint_clear();
+}
+
+/*
+ * open another connection
+ */
+void tcp_open(char *id, char *initstring, char *host, int port)
+{
+ int newtcp_fd, i;
+
+ if (tcp_count+tcp_attachcount >= MAX_CONNECTS) {
+ PRINTF("#too many open connections.\n");
+ return;
+ }
+ if (tcp_find(id)>=0) {
+ PRINTF("#connection \"%s\" already open.\n", id);
+ return;
+ }
+
+ /* find a free slot */
+ for (i=0; i<MAX_CONNECTS; i++) {
+ if (!CONN_INDEX(i).id)
+ break;
+ }
+ if (i == MAX_CONNECTS) {
+ PRINTF("#internal error, connection table full :(\n");
+ return;
+ }
+
+ if (!(CONN_INDEX(i).host = my_strdup(host))) {
+ errmsg("malloc");
+ return;
+ }
+ if (!(CONN_INDEX(i).id = my_strdup(id))) {
+ errmsg("malloc");
+ free(CONN_INDEX(i).host);
+ return;
+ }
+
+ /* dial the number by moving the right index in small circles */
+ if ((newtcp_fd = tcp_connect(host, port)) < 0) {
+ free(CONN_INDEX(i).host);
+ free(CONN_INDEX(i).id);
+ CONN_INDEX(i).id = 0;
+ return;
+ }
+
+ conn_table[newtcp_fd] = i;
+ CONN_INDEX(i).flags = ACTIVE;
+ CONN_INDEX(i).state = NORMAL;
+ CONN_INDEX(i).old_state = NORMAL;
+ CONN_INDEX(i).port = port;
+ CONN_INDEX(i).fd = newtcp_fd;
+
+ if (tcp_max_fd < newtcp_fd)
+ tcp_max_fd = newtcp_fd;
+ if (conn_max_index <= i)
+ conn_max_index = i+1;
+
+ FD_SET(newtcp_fd, &fdset); /* add socket to select() set */
+ tcp_count++;
+
+ if (opt_info && tcp_count) {
+ PRINTF("#default connection is now \"%s\"\n", id);
+ }
+ tcp_set_main(tcp_fd = newtcp_fd);
+ if (opt_sendsize)
+ tcp_write_tty_size();
+
+ if (initstring) {
+ parse_instruction(initstring, 0, 0, 1);
+ history_done = 0;
+ }
+}
+
+/*
+ * close a connection
+ */
+void tcp_close(char *id)
+{
+ int i, sfd;
+
+ status(1);
+ tty_puts(edattrend);
+ /*
+ * because we may be called from get_remote_input()
+ * if tcp_read gets an EOF, before edattrend is
+ * printed by get_remote_input() itself.
+ */
+
+ if (id) { /* #zap cmd */
+ if ((sfd = tcp_find(id)) < 0) {
+ tty_printf("#no such connection: \"%s\"\n", id);
+ return;
+ }
+ } else
+ sfd = tcp_fd; /* connection closed by remote host */
+
+ shutdown(sfd, 2);
+ close(sfd);
+
+ abort_edit_fd(sfd);
+
+ tty_printf("#connection on \"%s\" closed.\n", CONN_LIST(sfd).id);
+
+ if (sfd == tcp_main_fd) { /* main connection closed */
+ if (tcp_count == 1) { /* was last connection */
+ if (opt_exit)
+ exit_powwow();
+ tty_puts("#no connections left. Type #quit to quit.\n");
+ tcp_fd = tcp_main_fd = -1;
+ } else {
+ /* must find another connection and promote it to main */
+ for (i=0; i<conn_max_index; i++) {
+ if (!CONN_INDEX(i).id || CONN_INDEX(i).fd == sfd
+ || (CONN_INDEX(i).flags & SPAWN))
+ continue;
+ tty_printf("#default connection is now \"%s\"\n", CONN_INDEX(i).id);
+ tcp_main_fd = CONN_INDEX(i).fd;
+ break;
+ }
+ if (sfd == tcp_main_fd) {
+ tty_printf("#PANIC! internal error in tcp_close()\nQuitting.\n");
+ syserr(NULL);
+ }
+ }
+ tcp_set_main(tcp_main_fd);
+ }
+
+ if (tcp_fd == sfd)
+ tcp_fd = -1; /* no further I/O allowed on sfd, as we just closed it */
+
+ FD_CLR(sfd, &fdset);
+ if (CONN_LIST(sfd).flags & SPAWN)
+ tcp_attachcount--;
+ else
+ tcp_count--;
+ CONN_LIST(sfd).flags = 0;
+ CONN_LIST(sfd).state = NORMAL;
+ CONN_LIST(sfd).old_state = NORMAL;
+ CONN_LIST(sfd).port = 0;
+ free(CONN_LIST(sfd).host); CONN_LIST(sfd).host = 0;
+ free(CONN_LIST(sfd).id); CONN_LIST(sfd).id = 0;
+ if (CONN_LIST(sfd).fragment) {
+ free(CONN_LIST(sfd).fragment);
+ CONN_LIST(sfd).fragment = 0;
+ }
+
+ /* recalculate conn_max_index */
+ i = conn_table[sfd];
+ if (i+1 == conn_max_index) {
+ do {
+ i--;
+ } while (i>=0 && !CONN_INDEX(i).id);
+ conn_max_index = i+1;
+ }
+
+ /* recalculate tcp_max_fd */
+ for (i = tcp_max_fd = 0; i<conn_max_index; i++) {
+ if (CONN_INDEX(i).id && tcp_max_fd < CONN_INDEX(i).fd)
+ tcp_max_fd = CONN_INDEX(i).fd;
+ }
+}
+
+/*
+ * toggle output display from another connection
+ */
+void tcp_togglesnoop(char *id)
+{
+ int sfd;
+
+ sfd = tcp_find(id);
+ if (sfd>=0) {
+ CONN_LIST(sfd).flags ^= ACTIVE;
+ if (opt_info) {
+ PRINTF("#connection %s is now %sactive.\n",
+ CONN_LIST(sfd).id, CONN_LIST(sfd).flags & ACTIVE ? "" : "non");
+ }
+ } else {
+ PRINTF("#no such connection: %s\n", id);
+ }
+}
+
+void tcp_spawn(char *id, char *cmd)
+{
+ int i, childpid, sockets[2];
+
+ if (tcp_find(id)>=0) {
+ PRINTF("#connection \"%s\" already open.\n", id);
+ return;
+ }
+ if (socketpair(AF_UNIX, SOCK_STREAM, 0, sockets) < 0) {
+ errmsg("create socketpair");
+ return;
+ }
+ unescape(cmd);
+
+ switch (childpid = fork()) {
+ case 0:
+ /* child */
+ close(0); close(1); close(2);
+ setsid();
+ dup2(sockets[1], 0);
+ dup2(sockets[1], 1);
+ dup2(sockets[1], 2);
+ close(sockets[0]);
+ close(sockets[1]);
+ execl("/bin/sh", "sh", "-c", cmd, NULL);
+ syserr("execl");
+ break;
+ case -1:
+ close(sockets[0]);
+ close(sockets[1]);
+ errmsg("fork");
+ return;
+ }
+ close(sockets[1]);
+
+ /* Again, we don't want children to inherit sockets */
+ fcntl(sockets[0], F_SETFD, FD_CLOEXEC);
+
+ /* now find a free slot */
+ for (i=0; i<MAX_CONNECTS; i++) {
+ if (!CONN_INDEX(i).id) {
+ conn_table[sockets[0]] = i;
+ break;
+ }
+ }
+ if (i == MAX_CONNECTS) {
+ PRINTF("#internal error, connection table full :(\n");
+ close(sockets[0]);
+ return;
+ }
+
+ if (!(CONN_INDEX(i).host = my_strdup(cmd))) {
+ errmsg("malloc");
+ close(sockets[0]);
+ return;
+ }
+ if (!(CONN_INDEX(i).id = my_strdup(id))) {
+ errmsg("malloc");
+ free(CONN_INDEX(i).host);
+ close(sockets[0]);
+ return;
+ }
+ CONN_INDEX(i).flags = ACTIVE | SPAWN;
+ CONN_INDEX(i).state = NORMAL;
+ CONN_INDEX(i).old_state = NORMAL;
+ CONN_INDEX(i).port = 0;
+ CONN_INDEX(i).fd = sockets[0];
+
+ FD_SET(sockets[0], &fdset); /* add socket to select() set */
+ tcp_attachcount++;
+
+ if (tcp_max_fd < sockets[0])
+ tcp_max_fd = sockets[0];
+ if (conn_max_index <= i)
+ conn_max_index = i+1;
+
+ if (opt_info) {
+ PRINTF("#successfully spawned \"%s\" with pid %d\n", id, childpid);
+ }
+
+ /*
+ * when the child exits we also get an EOF on the socket,
+ * so no special care is needed by the SIGCHLD handler.
+ */
+}
+
diff --git a/src/tcp.h b/src/tcp.h
new file mode 100644
index 0000000..550b42a
--- /dev/null
+++ b/src/tcp.h
@@ -0,0 +1,79 @@
+/* public things from tcp.c */
+
+#ifndef _TCP_H_
+#define _TCP_H_
+
+extern int tcp_fd; /* current socket file descriptor */
+extern int tcp_main_fd; /* main session socket */
+extern int tcp_max_fd; /* highest used fd */
+
+extern int tcp_count; /* number of open connections */
+extern int tcp_attachcount; /* number of spawned or attached commands */
+
+extern int conn_max_index; /* 1 + highest used conn_list[] index */
+
+
+/* multiple connections control */
+
+/* state of telnet connection */
+#define NORMAL 0
+#define ALTNORMAL 1
+#define GOT_N 2
+#define GOT_R 3
+#define GOTIAC 4
+#define GOTWILL 5
+#define GOTWONT 6
+#define GOTDO 7
+#define GOTDONT 8
+#define GOTSB 9
+#define GOTSBIAC 10
+
+/* connection flags: */
+/* ACTIVE: display remote output */
+/* SPAWN: spawned cmd, not a mud */
+/* IDEDITOR: sent #request editor */
+/* IDPROMPT: sent #request prompt */
+#define ACTIVE 1
+#define SPAWN 2
+#define IDEDITOR 4
+#define IDPROMPT 8
+
+typedef struct {
+ char *id; /* session id */
+ char *host; /* address of remote host */
+ int port; /* port number of remote host */
+ int fd; /* fd number */
+ char *fragment; /* for SPAWN connections: unprocessed text */
+ char flags;
+ char state;
+ char old_state;
+} connsess;
+
+extern connsess conn_list[MAX_CONNECTS]; /* connection list */
+
+extern byte conn_table[MAX_FDSCAN]; /* fd -> index translation table */
+
+#define CONN_LIST(n) conn_list[conn_table[n]]
+#define CONN_INDEX(n) conn_list[n]
+
+extern fd_set fdset; /* set of descriptors to select() on */
+
+int tcp_connect(const char *addr, int port);
+int tcp_read(int fd, char *buffer, int maxsize);
+void tcp_raw_write(int fd, const char *data, int len);
+void tcp_write_escape_iac(int fd, const char *data, int len);
+void tcp_write_tty_size(void);
+void tcp_write(int fd, char *data);
+void tcp_main_write(char *data);
+void tcp_flush(void);
+void tcp_set_main(int fd);
+void tcp_open(char *id, char *initstring, char *host, int port);
+int tcp_find(char *id);
+void tcp_show(void);
+void tcp_close(char *id);
+void tcp_togglesnoop(char *id);
+void tcp_spawn(char *id, char *cmd);
+int tcp_unIAC(char *data, int len);
+
+#endif /* _TCP_H_ */
+
diff --git a/src/tty.c b/src/tty.c
new file mode 100644
index 0000000..104c780
--- /dev/null
+++ b/src/tty.c
@@ -0,0 +1,1038 @@
+/*
+ * tty.c -- terminal handling routines for powwow
+ *
+ * Copyright (C) 1998 by Massimiliano Ghilardi
+ *
+ * This program 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 2 of the License, or
+ * (at your option) any later version.
+ *
+ */
+#include <alloca.h>
+#include <assert.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <limits.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/types.h>
+#include <time.h>
+#include <unistd.h>
+
+#ifdef USE_LOCALE
+#include <wchar.h>
+#include <locale.h>
+#endif
+
+#include "defines.h"
+#include "main.h"
+#include "edit.h"
+#include "utils.h"
+#include "list.h"
+#include "tty.h"
+#include "tcp.h"
+
+#ifndef USE_SGTTY
+# ifdef APOLLO
+# include "/sys5.3/usr/include/sys/termio.h"
+# else
+/*
+ * including both termio.h and termios.h might be an overkill, and gives
+ * many warnings, but seems to be necessary at times. works anyway.
+ */
+# include <termios.h>
+# include <termio.h>
+# endif
+/* #else USE_SGTTY */
+#endif
+
+/*
+ * SunOS 4 doesn't have function headers and has the defs needed from
+ * ioctl.h in termios.h. Does it compile with USE_SGTTY?
+ */
+#if (defined(sun) && defined(sparc) && ! defined(__SVR4))
+ extern int printf();
+#else
+# include <sys/ioctl.h>
+#endif
+
+#ifdef BSD_LIKE
+# include <sys/ioctl_compat.h>
+# define O_RAW RAW
+# define O_ECHO ECHO
+# define O_CBREAK CBREAK
+#endif
+
+#if defined(TCSETS) || defined(TCSETATTR)
+# ifndef TCSETS /* cc for HP-UX SHOULD define this... */
+# define TCSETS TCSETATTR
+# define TCGETS TCGETATTR
+# endif
+typedef struct termios termiostruct;
+#else
+# define TCSETS TCSETA
+# define TCGETS TCGETA
+typedef struct termio termiostruct;
+#endif
+
+#ifdef VSUSP
+# define O_SUSP VSUSP
+#else
+# ifdef SWTCH
+# define O_SUSP SWTCH
+# else
+# define O_SUSP SUSP
+# endif
+#endif
+
+/* int ioctl(); */
+
+#ifdef USE_VT100 /* hard-coded vt100 features if no termcap: */
+
+static char kpadstart[] = "", kpadend[] = "", begoln[] = "\r",
+ clreoln[] = "\033[K", clreoscr[] = "\033[J",
+ leftcur[] = "\033[D", rightcur[] = "\033[C", upcur[] = "\033[A",
+ modebold[] = "\033[1m", modeblink[] = "\033[5m", modeinv[] = "\033[7m",
+ modeuline[] = "\033[4m", modestandon[] = "", modestandoff[] = "",
+ modenorm[] = "\033[m", modenormbackup[4],
+ cursor_left[] = "\033[D", cursor_right[] = "\033[C",
+ cursor_up[] = "\033[A", cursor_down[] = "\033[B";
+
+#define insertfinish (0)
+static int len_begoln = 1, len_leftcur = 3, len_upcur = 3, gotocost = 8;
+
+#else /* not USE_VT100, termcap function declarations */
+
+int tgetent();
+int tgetnum();
+int tgetflag();
+char *tgetstr();
+char *tgoto();
+
+/* terminal escape sequences */
+static char kpadstart[CAPLEN], kpadend[CAPLEN],
+ leftcur[CAPLEN], rightcur[CAPLEN], upcur[CAPLEN], curgoto[CAPLEN],
+ delchar[CAPLEN], insstart[CAPLEN], insstop[CAPLEN],
+ inschar[CAPLEN],
+ begoln[CAPLEN], clreoln[CAPLEN], clreoscr[CAPLEN],
+ cursor_left[CAPLEN], cursor_right[CAPLEN], cursor_up[CAPLEN],
+ cursor_down[CAPLEN];
+
+/* attribute changers: */
+static char modebold[CAPLEN], modeblink[CAPLEN], modeinv[CAPLEN],
+ modeuline[CAPLEN], modestandon[CAPLEN], modestandoff[CAPLEN],
+ modenorm[CAPLEN], modenormbackup[CAPLEN];
+
+static int len_begoln, len_clreoln, len_leftcur, len_upcur, gotocost,
+ deletecost, insertcost, insertfinish, inscharcost;
+
+static int extract(char *cap, char *buf);
+
+#endif /* USE_VT100 */
+
+
+char *tty_modebold = modebold, *tty_modeblink = modeblink,
+ *tty_modeinv = modeinv, *tty_modeuline = modeuline,
+ *tty_modestandon = modestandon, *tty_modestandoff = modestandoff,
+ *tty_modenorm = modenorm, *tty_modenormbackup = modenormbackup,
+ *tty_begoln = begoln, *tty_clreoln = clreoln,
+ *tty_clreoscr = clreoscr;
+
+int tty_read_fd = 0;
+static int wrapglitch = 0;
+
+#ifdef USE_LOCALE
+FILE *tty_read_stream;
+static int orig_read_fd_fl;
+static struct {
+ mbstate_t mbstate; /* multibyte output shift state */
+ char data[4096]; /* buffer for pending data */
+ size_t used; /* bytes used of data */
+ int fd; /* file descriptor to write to */
+} tty_write_state;
+#endif /* USE_LOCALE */
+
+#ifdef USE_SGTTY
+static struct sgttyb ttybsave;
+static struct tchars tcsave;
+static struct ltchars ltcsave;
+#else /* not USE_SGTTY */
+static termiostruct ttybsave;
+#endif /* USE_SGTTY */
+
+/*
+ * Terminal handling routines:
+ * These are one big mess of left-justified chicken scratches.
+ * It should be handled more cleanly...but unix portability is what it is.
+ */
+
+/*
+ * Set the terminal to character-at-a-time-without-echo mode, and save the
+ * original state in ttybsave
+ */
+void tty_start(void)
+{
+#ifdef USE_SGTTY
+ struct sgttyb ttyb;
+ struct ltchars ltc;
+ ioctl(tty_read_fd, TIOCGETP, &ttybsave);
+ ioctl(tty_read_fd, TIOCGETC, &tcsave);
+ ioctl(tty_read_fd, TIOCGLTC, &ltcsave);
+ ttyb = ttybsave;
+ ttyb.sg_flags = (ttyb.sg_flags|O_CBREAK) & ~O_ECHO;
+ ioctl(tty_read_fd, TIOCSETP, &ttyb);
+ ltc = ltcsave;
+ ltc.t_suspc = -1;
+ ioctl(tty_read_fd, TIOCSLTC, &ltc);
+#else /* not USE_SGTTY */
+ termiostruct ttyb;
+ ioctl(tty_read_fd, TCGETS, &ttyb);
+ ttybsave = ttyb;
+ ttyb.c_lflag &= ~(ECHO|ICANON);
+ ttyb.c_cc[VTIME] = 0;
+ ttyb.c_cc[VMIN] = 1;
+ /* disable the special handling of the suspend key (handle it ourselves) */
+ ttyb.c_cc[O_SUSP] = 0;
+ ioctl(tty_read_fd, TCSETS, &ttyb);
+#endif /* USE_SGTTY */
+
+#ifdef USE_LOCALE
+ orig_read_fd_fl = fcntl(tty_read_fd, F_GETFL);
+ fcntl(tty_read_fd, F_SETFL, O_NONBLOCK | orig_read_fd_fl);
+#endif
+
+ tty_puts(kpadstart);
+ tty_flush();
+
+#ifdef USE_LOCALE
+ tty_write_state.fd = 1;
+ wcrtomb(NULL, L'\0', &tty_write_state.mbstate);
+#else /* ! USE_LOCALE */
+ #ifdef DEBUG_TTY
+ setvbuf(stdout, NULL, _IONBF, BUFSIZ);
+ #else
+ setvbuf(stdout, NULL, _IOFBF, BUFSIZ);
+ #endif
+#endif /* ! USE_LOCALE */
+}
+
+/*
+ * Reset the terminal to its original state
+ */
+void tty_quit(void)
+{
+#ifdef USE_SGTTY
+ ioctl(tty_read_fd, TIOCSETP, &ttybsave);
+ ioctl(tty_read_fd, TIOCSETC, &tcsave);
+ ioctl(tty_read_fd, TIOCSLTC, &ltcsave);
+#else /* not USE_SGTTY */
+ ioctl(tty_read_fd, TCSETS, &ttybsave);
+#endif /* USE_SGTTY */
+ tty_puts(kpadend);
+ tty_flush();
+#ifdef USE_LOCALE
+ fcntl(tty_read_fd, F_SETFL, orig_read_fd_fl);
+#endif
+}
+
+/*
+ * enable/disable special keys depending on the current linemode
+ */
+void tty_special_keys(void)
+{
+#ifdef USE_SGTTY
+ struct tchars tc = {-1, -1, -1, -1, -1, -1};
+ struct ltchars ltc = {-1, -1, -1, -1, -1, -1};
+ struct sgttyb ttyb;
+ ioctl(tty_read_fd, TIOCGETP, &ttyb);
+ if (linemode & LM_CHAR) {
+ /* char-by-char mode: set RAW mode*/
+ ttyb.sg_flags |= RAW;
+ } else {
+ /* line-at-a-time mode: enable spec keys, disable RAW */
+ tc = tcsave;
+ ltc = ltcsave;
+ ltc.t_suspc = -1; /* suspend key remains disabled */
+ ttyb.sg_flags &= ~RAW;
+ }
+ ioctl(tty_read_fd, TIOCSETP, &ttyb);
+ ioctl(tty_read_fd, TIOCSETC, &tc);
+ ioctl(tty_read_fd, TIOCSLTC, &ltc);
+#else /* not USE_SGTTY */
+ int i;
+ termiostruct ttyb;
+ ioctl(tty_read_fd, TCGETS, &ttyb);
+ if (linemode & LM_CHAR) {
+ /* char-by-char mode: disable all special keys and set raw mode */
+ for(i = 0; i < NCCS; i++)
+ ttyb.c_cc[i] = 0;
+ ttyb.c_oflag &= ~OPOST;
+ } else {
+ /* line at a time mode: enable them, except suspend */
+ for(i = 0; i < NCCS; i++)
+ ttyb.c_cc[i] = ttybsave.c_cc[i];
+ /* disable the suspend key (handle it ourselves) */
+ ttyb.c_cc[O_SUSP] = 0;
+ /* set cooked mode */
+ ttyb.c_oflag |= OPOST;
+ }
+ ioctl(tty_read_fd, TCSETS, &ttyb);
+#endif /* USE_SGTTY */
+}
+
+/*
+ * get window size and react to any window size change
+ */
+void tty_sig_winch_bottomhalf(void)
+{
+ struct winsize wsiz;
+ /* if ioctl fails or gives silly values, don't change anything */
+
+ if (ioctl(tty_read_fd, TIOCGWINSZ, &wsiz) == 0
+ && wsiz.ws_row > 0 && wsiz.ws_col > 0
+ && (lines != wsiz.ws_row || cols != wsiz.ws_col))
+ {
+ lines = wsiz.ws_row;
+ cols_1 = cols = wsiz.ws_col;
+ if (!wrapglitch)
+ cols_1--;
+
+ if (tcp_main_fd != -1)
+ tcp_write_tty_size();
+ line0 += lines - olines;
+
+ tty_gotoxy(0, line0);
+ /* so we know where the cursor is */
+#ifdef BUG_ANSI
+ if (edattrbg)
+ tty_printf("%s%s", edattrend, tty_clreoscr);
+ else
+#endif
+ tty_puts(tty_clreoscr);
+
+ olines = lines;
+ status(1);
+ }
+}
+
+/*
+ * read termcap definitions
+ */
+void tty_bootstrap(void)
+{
+#ifdef USE_LOCALE
+ tty_read_stream = stdin;
+#endif
+
+#ifndef USE_VT100
+ struct tc_init_node {
+ char cap[4], *buf;
+ int *len, critic;
+ };
+ static struct tc_init_node tc_init[] = {
+ { "cm", curgoto, 0, 1 },
+ { "ce", clreoln, &len_clreoln, 1 },
+ { "cd", clreoscr, 0, 1 },
+ { "nd", rightcur, 0, 1 },
+ { "le", leftcur, &len_leftcur, 0 },
+ { "up", upcur, &len_upcur, 0 },
+ { "cr", begoln, &len_begoln, 0 },
+ { "ic", inschar, &inscharcost, 0 },
+ { "im", insstart, &insertcost, 0 },
+ { "ei", insstop, &insertcost, 0 },
+ { "dm", delchar, &deletecost, 0 },
+ { "dc", delchar, &deletecost, 0 },
+ { "ed", delchar, &deletecost, 0 },
+ { "me", modenorm, 0, 0 },
+ { "md", modebold, 0, 0 },
+ { "mb", modeblink, 0, 0 },
+ { "mr", modeinv, 0, 0 },
+ { "us", modeuline, 0, 0 },
+ { "so", modestandon, 0, 0 },
+ { "se", modestandoff, 0, 0 },
+ { "ks", kpadstart, 0, 0 },
+ { "ke", kpadend, 0, 0 },
+ { "kl", cursor_left, 0, 0 },
+ { "kr", cursor_right, 0, 0 },
+ { "ku", cursor_up, 0, 0 },
+ { "kd", cursor_down, 0, 0 },
+ { "", NULL, 0, 0 }
+ };
+ struct tc_init_node *np;
+ char tcbuf[2048]; /* by convention, this is enough */
+ int i;
+#endif /* not USE_VT100 */
+#if !defined(USE_VT100) || defined(BUG_TELNET)
+ char *term = getenv("TERM");
+ if (!term) {
+ fprintf(stderr, "$TERM not set\n");
+ exit(1);
+ }
+#endif /* !defined(USE_VT100) || defined(BUG_TELNET) */
+#ifdef USE_VT100
+ cols = 80;
+# ifdef LINES
+ lines = LINES;
+# else /* not LINES */
+ lines = 24;
+# endif /* LINES */
+#else /* not USE_VT100 */
+ switch(tgetent(tcbuf, term)) {
+ case 1:
+ break;
+ case 0:
+ fprintf(stderr,
+ "There is no entry for \"%s\" in the terminal data base.\n", term);
+ fprintf(stderr,
+ "Please set your $TERM environment variable correctly.\n");
+ exit(1);
+ default:
+ syserr("tgetent");
+ }
+ for(np = tc_init; np->cap[0]; np++)
+ if ((i = extract(np->cap, np->buf))) {
+ if (np->len) *np->len += i;
+ } else if (np->critic) {
+ fprintf(stderr,
+ "Your \"%s\" terminal is not powerful enough, missing \"%s\".\n",
+ term, np->cap);
+ exit(1);
+ }
+ if (!len_begoln)
+ strcpy(begoln, "\r"), len_begoln = 1;
+ if (!len_leftcur)
+ strcpy(leftcur, "\b"), len_leftcur = 1;
+
+ gotocost = strlen(tgoto(curgoto, cols - 1, lines - 1));
+ insertfinish = gotocost + len_clreoln;
+
+ /* this must be before getting window size */
+ wrapglitch = tgetflag("xn");
+
+ tty_sig_winch_bottomhalf(); /* get window size */
+
+#endif /* not USE_VT100 */
+ strcpy(modenormbackup, modenorm);
+#ifdef BUG_TELNET
+ if (strncmp(term, "vt10", 4) == 0) {
+ /* might be NCSA Telnet 2.2 for PC, which doesn't reset colours */
+ sprintf(modenorm, "\033[;%c%d;%s%dm",
+ DEFAULTFG<LOWCOLORS ? '3' : '9', DEFAULTFG % LOWCOLORS,
+ DEFAULTBG<LOWCOLORS ? "4" : "10", DEFAULTBG % LOWCOLORS);
+ }
+#endif /* BUG_TELNET */
+}
+
+/*
+ * add the default keypad bindings to the list
+ */
+void tty_add_walk_binds(void)
+{
+ /*
+ * Note: termcap doesn't have sequences for the numeric keypad, so we just
+ * assume they are the same as for a vt100. They can be redefined
+ * at runtime anyway (using #bind or #rebind)
+ */
+ add_keynode("KP2", "\033Or", 0, key_run_command, "s");
+ add_keynode("KP3", "\033Os", 0, key_run_command, "d");
+ add_keynode("KP4", "\033Ot", 0, key_run_command, "w");
+ add_keynode("KP5", "\033Ou", 0, key_run_command, "exits");
+ add_keynode("KP6", "\033Ov", 0, key_run_command, "e");
+ add_keynode("KP7", "\033Ow", 0, key_run_command, "look");
+ add_keynode("KP8", "\033Ox", 0, key_run_command, "n");
+ add_keynode("KP9", "\033Oy", 0, key_run_command, "u");
+}
+
+/*
+ * initialize the key binding list
+ */
+void tty_add_initial_binds(void)
+{
+ struct b_init_node {
+ char *label, *seq;
+ function_any funct;
+ };
+ static struct b_init_node b_init[] = {
+ { "LF", "\n", enter_line },
+ { "Ret", "\r", enter_line },
+ { "BS", "\b", del_char_left },
+ { "Del", "\177", del_char_left },
+ { "Tab", "\t", complete_word },
+ { "C-a", "\001", begin_of_line },
+ { "C-b", "\002", prev_char },
+ { "C-d", "\004", del_char_right },
+ { "C-e", "\005", end_of_line },
+ { "C-f", "\006", next_char },
+ { "C-k", "\013", kill_to_eol },
+ { "C-l", "\014", redraw_line },
+ { "C-n", "\016", next_line },
+ { "C-p", "\020", prev_line },
+ { "C-t", "\024", transpose_chars },
+ { "C-w", "\027", to_history },
+ { "C-z", "\032", suspend_powwow },
+ { "M-Tab", "\033\t", complete_line },
+ { "M-b", "\033b", prev_word },
+ { "M-d", "\033d", del_word_right },
+ { "M-f", "\033f", next_word },
+ { "M-k", "\033k", redraw_line_noprompt },
+ { "M-t", "\033t", transpose_words },
+ { "M-u", "\033u", upcase_word },
+ { "M-l", "\033l", downcase_word },
+ { "M-BS", "\033\b", del_word_left },
+ { "M-Del", "\033\177", del_word_left },
+ { "", "", 0 }
+ };
+ struct b_init_node *p = b_init;
+ do {
+ add_keynode(p->label, p->seq, 0, p->funct, NULL);
+ } while((++p)->seq[0]);
+
+ if (*cursor_left ) add_keynode("Left" , cursor_left , 0, prev_char, NULL);
+ if (*cursor_right) add_keynode("Right", cursor_right, 0, next_char, NULL);
+ if (*cursor_up ) add_keynode("Up" , cursor_up , 0, prev_line, NULL);
+ if (*cursor_down ) add_keynode("Down" , cursor_down , 0, next_line, NULL);
+}
+
+#ifndef USE_VT100
+/*
+ * extract termcap 'cap' and strcat it to buf.
+ * return the lenght of the extracted string.
+ */
+static int extract(char *cap, char *buf)
+{
+ static char *bp;
+ char *d = buf + strlen(buf);
+ char *s = tgetstr(cap, (bp = d, &bp));
+ int len;
+ if (!s) return (*bp = 0);
+ /*
+ * Remove the padding information. We assume that no terminals
+ * need padding nowadays. At least it makes things much easier.
+ */
+ s += strspn(s, "0123456789*");
+ for(len = 0; *s; *d++ = *s++, len++)
+ if (*s == '$' && *(s + 1) == '<')
+ if (!(s = strchr(s, '>')) || !*++s) break;
+ *d = 0;
+ return len;
+}
+#endif /* not USE_VT100 */
+
+/*
+ * position the cursor using absolute coordinates
+ * note: does not flush the output buffer
+ */
+void tty_gotoxy(int col, int line)
+{
+#ifdef USE_VT100
+ tty_printf("\033[%d;%dH", line + 1, col + 1);
+#else
+ tty_puts(tgoto(curgoto, col, line));
+#endif
+}
+
+/*
+ * optimized cursor movement
+ * from (fromcol, fromline) to (tocol, toline)
+ * if tocol > 0, (tocol, toline) must lie on editline.
+ */
+void tty_gotoxy_opt(int fromcol, int fromline, int tocol, int toline)
+{
+ static char buf[BUFSIZE];
+ char *cp = buf;
+ int cost, i, dist;
+
+ CLIP(fromline, 0, lines-1);
+ CLIP(toline , 0, lines-1);
+
+ /* First, move vertically to the correct line, then horizontally
+ * to the right column. If this turns out to be fewer characters
+ * than a direct cursor positioning (tty_gotoxy), use that.
+ */
+ for (;;) { /* gotoless */
+ if ((i = toline - fromline) < 0) {
+ if (!len_upcur || (cost = -i * len_upcur) >= gotocost)
+ break;
+ do {
+ strcpy(cp, upcur);
+ cp += len_upcur;
+ } while(++i);
+ } else if ((cost = 2 * i)) { /* lf is mapped to crlf on output */
+ if (cost >= gotocost)
+ break;
+ do
+ *cp++ = '\n';
+ while (--i);
+ fromcol = 0;
+ }
+ if ((i = tocol - fromcol) < 0) {
+ dist = -i * len_leftcur;
+ if (dist <= len_begoln + tocol) {
+ if ((cost += dist) > gotocost)
+ break;
+ do {
+ strcpy(cp, leftcur);
+ cp += len_leftcur;
+ } while(++i);
+ } else {
+ if ((cost += len_begoln) > gotocost)
+ break;
+ strcpy(cp, begoln);
+ cp += len_begoln;
+ fromcol = 0; i = tocol;
+ }
+ }
+ if (i) {
+ /*
+ * if hiliting in effect or prompt contains escape sequences,
+ * just use tty_gotoxy
+ */
+ if (cost + i > gotocost || *edattrbeg || promptlen != col0)
+ break;
+ if (fromcol < col0 && toline == line0) {
+ strcpy(cp, promptstr+fromcol);
+ cp += promptlen-fromcol;
+ fromcol = col0;
+ }
+ my_strncpy(cp, edbuf + (toline - line0) * cols_1 + fromcol - col0,
+ tocol - fromcol);
+ cp += tocol - fromcol;
+ }
+ *cp = 0;
+ tty_puts(buf);
+ return;
+ }
+ tty_gotoxy(tocol, toline);
+}
+
+
+/*
+ * GH: change the position on input line (gotoxy there, and set pos)
+ * from cancan 2.6.3a
+ */
+void input_moveto(int new_pos)
+{
+ /*
+ * FEATURE: the line we are moving to might be less than 0, or greater
+ * than lines - 1, if the display is too small to hold the whole editline.
+ * In that case, the input line should be (partially) redrawn.
+ */
+ if (new_pos < 0)
+ new_pos = 0;
+ else if (new_pos > edlen)
+ new_pos = edlen;
+ if (new_pos == pos)
+ return;
+
+ if (line_status == 0) {
+ int fromline = CURLINE(pos), toline = CURLINE(new_pos);
+ if (toline < 0)
+ line0 -= toline, toline = 0;
+ else if (toline > lines - 1)
+ line0 -= toline - lines + 1, toline = lines - 1;
+ tty_gotoxy_opt(CURCOL(pos), fromline, CURCOL(new_pos), toline);
+ }
+ pos = new_pos;
+}
+
+/*
+ * delete n characters at current position (the position is unchanged)
+ * assert(n < edlen - pos)
+ */
+void input_delete_nofollow_chars(int n)
+{
+ int r_cost, p = pos, d_cost;
+ int nl = pos - CURCOL(pos); /* this line's starting pos (can be <= 0) */
+ int cl = CURLINE(pos); /* current line */
+
+ if (n > edlen - p)
+ n = edlen - p;
+ if (n <= 0)
+ return;
+
+ d_cost = p + n;
+ if (line_status != 0) {
+ memmove(edbuf + p, edbuf + d_cost, edlen - d_cost + 1);
+ edlen -= n;
+ return;
+ }
+
+ memmove(edbuf + p, edbuf + d_cost, edlen - d_cost);
+ memset(edbuf + edlen - n, (int)' ', n);
+ for (;; tty_putc('\n'), p = nl, cl++) {
+ d_cost = 0;
+ /* FEATURE: ought to be "d_cost = n > gotocost ? -gotocost : -n;"
+ * since redraw will need to goto back. Of little importance */
+ if ((r_cost = edlen) > (nl += cols_1))
+ r_cost = nl, d_cost = n + gotocost;
+ r_cost -= p;
+#ifndef USE_VT100
+ /*
+ * FEATURE: no clreoln is used (it might cost less in the occasion
+ * we delete more than one char). Simplicity
+ */
+ if (deletecost && deletecost * n + d_cost < r_cost) {
+#ifdef BUG_ANSI
+ if (edattrbg)
+ tty_puts(edattrend);
+#endif
+ for (d_cost = n; d_cost; d_cost--)
+ tty_puts(delchar);
+#ifdef BUG_ANSI
+ if (edattrbg)
+ tty_puts(edattrbeg);
+#endif
+
+ if (edlen <= nl)
+ break;
+
+ tty_gotoxy(cols_1 - n, cl);
+ tty_printf("%.*s", n, edbuf + nl - n);
+ } else
+#endif /* not USE_VT100 */
+ {
+#ifdef BUG_ANSI
+ if (edattrbg && p <= edlen - n && p + r_cost >= edlen - n)
+ tty_printf("%.*s%s%.*s", edlen - p - n, edbuf + p,
+ edattrend,
+ r_cost - edlen + p + n, edbuf + edlen - n);
+ else
+#endif
+ tty_printf("%.*s", r_cost, edbuf + p);
+
+ p += r_cost;
+
+ if (edlen <= nl) {
+#ifdef BUG_ANSI
+ if (edattrbg)
+ tty_puts(edattrbeg);
+#endif
+ break;
+ }
+ }
+ }
+ edbuf[edlen -= n] = '\0';
+ switch(pos - p) {
+ case 1:
+ tty_puts(leftcur);
+ break;
+ case 0:
+ break;
+ default:
+ tty_gotoxy_opt(CURCOL(p), cl, CURCOL(pos), CURLINE(pos));
+ break;
+ }
+}
+
+/*
+ * GH: print a char on current position (overwrite), advance position
+ * from cancan 2.6.3a
+ */
+void input_overtype_follow(char c)
+{
+ if (pos >= edlen)
+ return;
+ edbuf[pos++] = c;
+
+ if (line_status == 0) {
+ tty_putc(c);
+ if (!CURCOL(pos)) {
+#ifdef BUG_ANSI
+ if (edattrbg)
+ tty_printf("%s\n%s", edattrend, edattrbeg);
+ else
+#endif
+ tty_putc('\n');
+ }
+ }
+}
+
+/*
+ * insert n characters at input line current position.
+ * The position is set to after the inserted characters.
+ */
+void input_insert_follow_chars(char *str, int n)
+{
+ int r_cost, i_cost, p = pos;
+ int nl = p - CURCOL(p); /* next line's starting pos */
+ int cl = CURLINE(p); /* current line */
+
+ if (edlen + n >= BUFSIZE)
+ n = BUFSIZE - edlen - 1;
+ if (n <= 0)
+ return;
+
+ memmove(edbuf + p + n, edbuf + p, edlen + 1 - p);
+ memmove(edbuf + p, str, n);
+ edlen += n; pos += n;
+
+ if (line_status != 0)
+ return;
+
+ do {
+ i_cost = n;
+ if ((r_cost = edlen) > (nl += cols_1))
+ r_cost = nl, i_cost += insertfinish;
+ r_cost -= p;
+#ifndef USE_VT100
+ /* FEATURE: insert mode is used only when one char is inserted
+ (which is probably true > 95% of the time). Simplicity */
+ if (n == 1 && inscharcost && inscharcost + i_cost < r_cost) {
+ tty_printf("%s%c", inschar, edbuf[p++]);
+ if (edlen > nl && !wrapglitch) {
+ tty_gotoxy(cols_1, cl);
+ tty_puts(clreoln);
+ }
+ } else
+#endif /* not USE_VT100 */
+ {
+ tty_printf("%.*s", r_cost, edbuf + p); p += r_cost;
+ }
+ if (edlen < nl)
+ break;
+#ifdef BUG_ANSI
+ if (edattrbg)
+ tty_printf("%s\n%s", edattrend, edattrbeg);
+ else
+#endif
+ tty_puts("\n");
+ p = nl;
+ if (++cl > lines - 1) {
+ cl = lines - 1;
+ line0--;
+ }
+ } while (edlen > nl);
+
+ if (p != pos)
+ tty_gotoxy_opt(CURCOL(p), cl, CURCOL(pos), CURLINE(pos));
+}
+
+#ifdef USE_LOCALE
+/* curses wide character support by Dain */
+
+void tty_puts(const char *s)
+{
+ while (*s)
+ tty_putc(*s++);
+}
+
+void tty_putc(char c)
+{
+ size_t r;
+ int ignore_error = 0;
+ if (tty_write_state.used + MB_LEN_MAX > sizeof tty_write_state.data)
+ tty_flush();
+again:
+ r = wcrtomb(tty_write_state.data + tty_write_state.used, (unsigned char)c,
+ &tty_write_state.mbstate);
+ if (r == (size_t)-1) {
+ if (ignore_error)
+ return;
+ if (errno != EILSEQ) {
+ perror("mcrtomb()");
+ abort();
+ }
+ /* character cannot be represented; try to write a question
+ * mark instead, but ignore any errors */
+ ignore_error = 1;
+ c = '?';
+ goto again;
+ }
+ assert(r <= MB_LEN_MAX);
+ tty_write_state.used += r;
+}
+
+int tty_printf(const char *format, ...)
+{
+ char buf[1024], *bufp = buf;
+ va_list va;
+ int res;
+
+ char *old_locale = strdup(setlocale(LC_ALL, NULL));
+
+ setlocale(LC_ALL, "C");
+
+ va_start(va, format);
+ res = vsnprintf(buf, sizeof buf, format, va);
+ va_end(va);
+
+ if (res >= sizeof buf) {
+ bufp = alloca(res + 1);
+ va_start(va, format);
+ vsprintf(bufp, format, va);
+ assert(strlen(bufp) == res);
+ va_end(va);
+ }
+
+ setlocale(LC_ALL, old_locale);
+ free(old_locale);
+
+ tty_puts(bufp);
+
+ return res;
+}
+
+static char tty_in_buf[MB_LEN_MAX + 1];
+static size_t tty_in_buf_used = 0;
+
+int tty_has_chars(void)
+{
+ return tty_in_buf_used != 0;
+}
+
+static int safe_mbtowc(wchar_t *pwc, char *s, size_t *n)
+{
+ static mbstate_t ps;
+
+ size_t r;
+
+again:
+ r = mbrtowc(pwc, s, *n, &ps);
+ if (r == (size_t)-2) {
+ /* incomplete character */
+ *n = 0;
+ return -1;
+ }
+ if (r == (size_t)-1 && errno == EILSEQ) {
+ /* invalid character */
+ --*n;
+ memmove(s, s + 1, *n);
+ goto again;
+ }
+ if (r == 0 && n > 0 && s[0] == 0) {
+ *pwc = L'\0';
+ r = 1;
+ }
+ return r;
+}
+
+int tty_read(char *buf, size_t count)
+{
+ int result = 0;
+ int converted;
+ wchar_t wc;
+ int did_read = 0, should_read = 0;
+
+ if (count && tty_in_buf_used) {
+ converted = safe_mbtowc(&wc, tty_in_buf, &tty_in_buf_used);
+ if (converted >= 0)
+ goto another;
+ }
+
+ while (count) {
+ should_read = sizeof tty_in_buf - tty_in_buf_used;
+ did_read = read(tty_read_fd, tty_in_buf + tty_in_buf_used,
+ should_read);
+
+ if (did_read < 0 && errno == EINTR)
+ continue;
+ if (did_read <= 0)
+ break;
+
+ tty_in_buf_used += did_read;
+
+ converted = safe_mbtowc(&wc, tty_in_buf, &tty_in_buf_used);
+ if (converted < 0)
+ break;
+
+ another:
+ if (converted == 0)
+ converted = 1;
+
+ if (!(wc & ~0xff)) {
+ *buf++ = (unsigned char)wc;
+ --count;
+ ++result;
+ }
+
+ tty_in_buf_used -= converted;
+ memmove(tty_in_buf, tty_in_buf + converted, tty_in_buf_used);
+
+ if (count == 0)
+ break;
+
+ converted = safe_mbtowc(&wc, tty_in_buf, &tty_in_buf_used);
+ if (converted >= 0 && tty_in_buf_used)
+ goto another;
+
+ if (did_read < should_read)
+ break;
+ }
+
+ return result;
+}
+
+
+void tty_gets(char *s, int size)
+{
+ wchar_t *ws = alloca(size * sizeof *ws);
+
+ if (!fgetws(ws, size, stdin))
+ return;
+
+ while (*ws) {
+ if (!(*ws & ~0xff))
+ *s++ = (unsigned char)*ws;
+ ++ws;
+ }
+}
+
+void tty_flush(void)
+{
+ size_t n = tty_write_state.used;
+ char *data = tty_write_state.data;
+ while (n > 0) {
+ ssize_t r;
+ for (;;) {
+ r = write(tty_write_state.fd, data, n);
+ if (r >= 0)
+ break;
+ if (errno == EINTR)
+ continue;
+ if (errno != EAGAIN) {
+ fprintf(stderr, "Cannot write to tty: %s\n", strerror(errno));
+ abort();
+ }
+ fd_set wfds;
+ FD_ZERO(&wfds);
+ FD_SET(tty_write_state.fd, &wfds);
+ do {
+ r = select(tty_write_state.fd + 1, NULL, &wfds, NULL, NULL);
+ } while (r < 0 && errno == EINTR);
+ if (r <= 0) {
+ fprintf(stderr, "Cannot write to tty; select failed: %s\n",
+ r == 0 ? "returned zero" : strerror(errno));
+ abort();
+ }
+ }
+ if (r < 0) {
+ fprintf(stderr, "Cannot write to tty: %s\n", strerror(errno));
+ abort();
+ }
+ n -= r;
+ data += r;
+ }
+ tty_write_state.used = 0;
+}
+
+void tty_raw_write(char *data, size_t len)
+{
+ if (len == 0) return;
+
+ for (;;) {
+ size_t s = sizeof tty_write_state.data - tty_write_state.used;
+ if (s > len) s = len;
+ memcpy(tty_write_state.data + tty_write_state.used, data, s);
+ len -= s;
+ tty_write_state.used += s;
+ if (len == 0)
+ break;
+ data += s;
+ tty_flush();
+ }
+}
+
+#endif /* USE_LOCALE */
diff --git a/src/tty.h b/src/tty.h
new file mode 100644
index 0000000..a4965b6
--- /dev/null
+++ b/src/tty.h
@@ -0,0 +1,58 @@
+/* public definitions from tty.c */
+
+#ifndef _TTY_H_
+#define _TTY_H_
+
+extern int tty_read_fd;
+
+extern char *tty_clreoln, *tty_clreoscr, *tty_begoln,
+ *tty_modebold, *tty_modeblink, *tty_modeuline,
+ *tty_modenorm, *tty_modenormbackup,
+ *tty_modeinv, *tty_modestandon, *tty_modestandoff;
+
+void tty_bootstrap(void);
+void tty_start(void);
+void tty_quit(void);
+void tty_special_keys(void);
+void tty_sig_winch_bottomhalf(void);
+void tty_add_walk_binds(void);
+void tty_add_initial_binds(void);
+void tty_gotoxy(int col, int line);
+void tty_gotoxy_opt(int fromcol, int fromline, int tocol, int toline);
+
+void input_delete_nofollow_chars(int n);
+void input_overtype_follow(char c);
+void input_insert_follow_chars(char *str, int n);
+void input_moveto(int new_pos);
+
+#ifndef USE_LOCALE
+
+#define tty_puts(s) fputs((s), stdout)
+#define tty_putc(c) fputc((unsigned char)(c), stdout)
+#define tty_printf(...) printf(__VA_ARGS__)
+#define tty_read(buf, cnt) read(tty_read_fd, (buf), (cnt))
+#define tty_gets(s, size) fgets((s), (size), stdin)
+#define tty_flush() fflush(stdout)
+#define tty_raw_write(s,size) do { tty_flush(); write(1, (s), (size)); } while (0)
+
+#else /* USE_LOCALE */
+
+#ifdef __GNUC__
+ #define PRINTF_FUNCTION(string, first) \
+ __attribute__ ((format (printf, string, first)))
+#else
+ #define PRINTF_FUNCTION(string, first)
+#endif
+
+void tty_puts(const char *s);
+void tty_putc(char c);
+int tty_printf(const char *format, ...) PRINTF_FUNCTION(1, 2);
+int tty_read(char *buf, size_t count);
+void tty_gets(char *s, int size);
+void tty_flush(void);
+void tty_raw_write(char *data, size_t len);
+int tty_has_chars(void);
+
+#endif /* USE_LOCALE */
+
+#endif /* _TTY_H_ */
diff --git a/src/utils.c b/src/utils.c
new file mode 100644
index 0000000..f6582ef
--- /dev/null
+++ b/src/utils.c
@@ -0,0 +1,1299 @@
+/*
+ * utils.c -- miscellaneous utility functions
+ *
+ * Copyright (C) 1998,2002 by Massimiliano Ghilardi
+ *
+ * This program 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 2 of the License, or
+ * (at your option) any later version.
+ *
+ */
+
+#ifdef BSD_LIKE
+/* Needed for SIGWINCH on OpenBSD. */
+# define _BSD_SOURCE
+/* Needed for SIGWINCH on FreeBSD. */
+# define __BSD_VISIBLE
+/* Needed for SIGWINCH on Darwin. */
+# define _DARWIN_C_SOURCE 1
+#endif
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+#include <ctype.h>
+#include <unistd.h>
+#include <signal.h>
+#include <time.h>
+
+#include "defines.h"
+#include "main.h"
+#include "utils.h"
+#include "list.h"
+#include "cmd.h"
+#include "cmd2.h"
+#include "beam.h"
+#include "tty.h"
+#include "edit.h"
+#include "eval.h"
+#include "log.h"
+
+#define SAVEFILEVER 6
+
+static char can_suspend = 0; /* 1 if shell has job control */
+
+/*
+ * GH: memory-"safe" strdup
+ */
+char *my_strdup(char *s)
+{
+ if (s) {
+ s = strdup(s);
+ if (!s)
+ error = NO_MEM_ERROR;
+ }
+ return s;
+}
+
+/*
+ * non-braindamaged strncpy:
+ * copy up to len chars from src to dst, then add a final \0
+ * (i.e. dst[len] = '\0')
+ */
+char *my_strncpy(char *dst, char *src, int len)
+{
+ int slen = strlen(src);
+ if (slen < len)
+ return strcpy(dst, src);
+ memcpy(dst, src, len);
+ dst[len] = '\0';
+ return dst;
+}
+
+/*
+ * Determine the printed length of a string. This can be less than the string
+ * length since it might contain escape sequences. Treated as such are
+ * "<esc> [ <non-letters> <letter>", "<esc> <non-[>", "<control-char>".
+ * This is not entirely universal but covers the most common cases (i.e. ansi)
+ */
+int printstrlen(char *s)
+{
+ int l;
+ enum { NORM, ESCAPE, BRACKET } state = NORM;
+ for (l = 0; *s; s++) {
+ switch(state) {
+ case NORM:
+ if (*s == '\033') {
+ state = ESCAPE;
+ } else if ((*s & 0x80) || *s >= ' ') {
+ l++;
+ } else if (*s == '\r')
+ l = (l / cols) * cols;
+ break;
+
+ case ESCAPE:
+ state = (*s == '[') ? BRACKET : NORM;
+ break;
+
+ case BRACKET:
+ if (isalpha(*s))
+ state = NORM;
+ break;
+ }
+ }
+ return l;
+}
+
+/*
+ * return pointer to next non-blank char
+ */
+char *skipspace(char *p)
+{
+ while (*p == ' ' || *p == '\t') p++;
+ return p;
+}
+
+/*
+ * find the first valid (non-escaped)
+ * char in a string
+ */
+char *first_valid(char *p, char ch)
+{
+ if (*p && *p != ch) {
+ p++;
+ if (ch == ESC2 || ch == ESC) while (*p && *p != ch)
+ p++;
+ else while (*p && ((*p != ch) || p[-1] == ESC))
+ p++;
+ }
+ return p;
+}
+
+/*
+ * find the first regular (non-escaped, non in "" () or {} )
+ * char in a string
+ */
+char *first_regular(char *p, char c)
+{
+ int escaped, quotes=0, paren=0, braces=0;
+
+ while (*p && ((*p != c) || quotes || paren>0 || braces>0)) {
+ escaped = 0;
+ if (*p == ESC) {
+ while (*p == ESC)
+ p++;
+ escaped = 1;
+ }
+ if (*p == ESC2) {
+ while (*p == ESC2)
+ p++;
+ escaped = 0;
+ }
+ if (!*p)
+ break;
+ if (!escaped) {
+ if (quotes) {
+ if (*p == '\"')
+ quotes = 0;
+ }
+ else if (*p == '\"')
+ quotes = 1;
+ else if (*p == ')')
+ paren--;
+ else if (*p == '(')
+ paren++;
+ else if (*p == '}')
+ braces--;
+ else if (*p == '{')
+ braces++;
+ }
+ p++;
+ }
+ return p;
+}
+
+/*
+ * remove escapes (backslashes) from a string
+ */
+int memunescape(char *p, int lenp)
+{
+ char *start, *s;
+ enum { NORM, ESCSINGLE, ESCAPE } state = NORM;
+
+ if (!p || !*p)
+ return 0;
+
+ start = s = p;
+
+ while (lenp) switch (state) {
+ case NORM:
+ if (*s != ESC) {
+ *p++ = *s++, lenp--;
+ break;
+ }
+ state = ESCSINGLE, s++;
+ if (!--lenp)
+ break;
+ /* fallthrough */
+ case ESCSINGLE:
+ case ESCAPE:
+ if (*s == ESC)
+ state = ESCAPE, *p++ = *s++, lenp--;
+ else if (*s == ESC2)
+ state = NORM, *p++ = ESC, s++, lenp--;
+ else {
+ if (state == ESCSINGLE && lenp >= 3 &&
+ ISODIGIT(s[0]) && ISODIGIT(s[1]) && ISODIGIT(s[2])) {
+
+ *p++ = ((s[0]-'0') << 6) | ((s[1]-'0') << 3) | (s[2]-'0');
+ s += 3, lenp -= 3;
+ } else
+ *p++ = *s++, lenp--;
+ state = NORM;
+ }
+ break;
+ default:
+ break;
+ }
+ *p = '\0';
+ return (int)(p - start);
+}
+
+void unescape(char *s)
+{
+ (void)memunescape(s, strlen(s));
+}
+
+void ptrunescape(ptr p)
+{
+ if (!p)
+ return;
+ p->len = memunescape(ptrdata(p), ptrlen(p));
+}
+
+/*
+ * add escapes (backslashes) to src
+ */
+ptr ptrmescape(ptr dst, char *src, int srclen, int append)
+{
+ int len;
+ char *p;
+ enum { NORM, ESCAPE } state;
+
+ if (!src || srclen <= 0) {
+ if (!append)
+ ptrzero(dst);
+ return dst;
+ }
+
+ if (dst && append)
+ len = ptrlen(dst);
+ else
+ len = 0;
+
+ dst = ptrsetlen(dst, len + srclen*4); /* worst case */
+ if (MEM_ERROR) return dst;
+
+ dst->len = len;
+ p = ptrdata(dst) + len;
+
+ while (srclen) {
+ state = NORM;
+ if (*src == ESC) {
+ while (srclen && *src == ESC)
+ dst->len++, *p++ = *src++, srclen--;
+
+ if (!srclen || *src == ESC2)
+ dst->len++, *p++ = ESC2;
+ else
+ state = ESCAPE;
+ }
+ if (!srclen) break;
+
+ if (*src < ' ' || *src > '~') {
+ sprintf(p, "\\%03o", (int)(byte)*src++);
+ len = strlen(p);
+ dst->len += len, p += len, srclen--;
+ } else {
+ if (state == ESCAPE || strchr(SPECIAL_CHARS, *src))
+ dst->len++, *p++ = ESC;
+
+ dst->len++, *p++ = *src++, srclen--;
+ }
+ }
+ *p = '\0';
+ return dst;
+}
+
+ptr ptrescape(ptr dst, ptr src, int append)
+{
+ if (!src) {
+ if (!append)
+ ptrzero(dst);
+ return dst;
+ }
+ return ptrmescape(dst, ptrdata(src), ptrlen(src), append);
+}
+
+/*
+ * add escapes to protect special characters from being escaped.
+ */
+void escape_specials(char *dst, char *src)
+{
+ enum { NORM, ESCAPE } state;
+ while (*src) {
+ state = NORM;
+ if (*src == ESC) {
+ while (*src == ESC)
+ *dst++ = *src++;
+
+ if (!*src || *src == ESC2)
+ *dst++ = ESC2;
+ else
+ state = ESCAPE;
+ }
+ if (!*src) break;
+
+ if (*src < ' ' || *src > '~') {
+ sprintf(dst, "\\%03o", (int)(byte)*src++);
+ dst += strlen(dst);
+ } else {
+ if (state == ESCAPE || strchr(SPECIAL_CHARS, *src))
+ *dst++ = ESC;
+
+ *dst++ = *src++;
+ }
+ }
+ *dst = '\0';
+}
+
+/*
+ * match mark containing & and $ and return 1 if matched, 0 if not
+ * if 1, start and end contain the match bounds
+ * if 0, start and end are undefined on return
+ */
+static int match_mark(marknode *mp, char *src)
+{
+ char *pat = mp->pattern;
+ char *npat=0, *npat2=0, *nsrc=0, *prm=0, *endprm=0, *tmp, c;
+ static char mpat[BUFSIZE];
+ int mbeg = mp->mbeg, mword = 0, p;
+
+ /* shortcut for #marks without wildcards */
+ if (!mp->wild) {
+ if ((nsrc = strstr(src, pat))) {
+ mp->start = nsrc;
+ mp->end = nsrc + strlen(pat);
+ return 1;
+ }
+ return 0;
+ }
+
+ mp->start = NULL;
+
+ if (ISMARKWILDCARD(*pat))
+ mbeg = - mbeg - 1; /* pattern starts with '&' or '$' */
+
+ while (pat && (c = *pat)) {
+ if (ISMARKWILDCARD(c)) {
+ /* & matches a string */
+ /* $ matches a single word */
+ prm = src;
+ if (c == '$')
+ mword = 1;
+ else if (!mp->start)
+ mp->start = src;
+ ++pat;
+ }
+
+ npat = first_valid(pat, '&');
+ npat2 = first_valid(pat, '$');
+ if (npat2 < npat) npat = npat2;
+ if (!*npat) npat = 0;
+
+ if (npat) {
+ my_strncpy(mpat, pat, npat-pat);
+ /* mpat[npat - pat] = 0; */
+ } else
+ strcpy(mpat, pat);
+
+ if (*mpat) {
+ nsrc = strstr(src, mpat);
+ if (!nsrc)
+ return 0;
+ if (mbeg > 0) {
+ if (nsrc != src)
+ return 0;
+ mbeg = 0; /* reset mbeg to stop further start match */
+ }
+ endprm = nsrc;
+ if (!mp->start) {
+ if (prm)
+ mp->start = src;
+ else
+ mp->start = nsrc;
+ }
+ mp->end = nsrc + strlen(mpat);
+ } else if (prm) /* end of pattern space */
+ mp->end = endprm = prm + strlen(prm);
+ else
+ mp->end = src;
+
+
+ /* post-processing of param */
+ if (mword) {
+ if (prm) {
+ if (mbeg == -1) {
+ if (!*pat) {
+ /* the pattern is "$", take first word */
+ p = - 1;
+ } else {
+ /* unanchored '$' start, take last word */
+ tmp = memrchrs(prm, endprm - prm - 1, DELIM, DELIM_LEN);
+ if (tmp)
+ p = tmp - prm;
+ else
+ p = -1;
+ }
+ mp->start = prm += p + 1;
+ } else if (!*pat) {
+ /* '$' at end of pattern, take first word */
+ if ((tmp = memchrs(prm, strlen(prm), DELIM, DELIM_LEN)))
+ mp->end = endprm = tmp;
+ } else {
+ /* match only if param is single-worded */
+ if (memchrs(prm, endprm - prm, DELIM, DELIM_LEN))
+ return 0;
+ }
+ } else
+ return 0;
+ }
+ if (prm)
+ mbeg = mword = 0; /* reset match flags */
+ src = nsrc + strlen(mpat);
+ pat = npat;
+ }
+ return 1;
+}
+
+/*
+ * add marks to line. write in dst.
+ */
+ptr ptrmaddmarks(ptr dst, char *line, int len)
+{
+ marknode *mp, *mfirst;
+ char begin[CAPLEN], end[CAPLEN], *lineend = line + len;
+ int start = 1, matchlen, matched = 0;
+
+ ptrzero(dst);
+
+ if (!line || len <= 0)
+ return dst;
+
+ for (mp = markers; mp; mp = mp->next)
+ mp->start = NULL;
+
+ do {
+ mfirst = NULL;
+ for (mp = markers; mp; mp = mp->next) {
+ if (mp->start && mp->start >= line)
+ matched = 1;
+ else {
+ if (!(matched = (!mp->mbeg || start) && match_mark(mp, line)))
+ mp->start = lineend;
+ }
+ if (matched && mp->start < lineend &&
+ (!mfirst || mp->start < mfirst->start))
+ mfirst = mp;
+ }
+
+ if (mfirst) {
+ start = 0;
+ attr_string(mfirst->attrcode, begin, end);
+
+ dst = ptrmcat(dst, line, matchlen = mfirst->start - line);
+ if (MEM_ERROR) break;
+ line += matchlen;
+ len -= matchlen;
+
+ dst = ptrmcat(dst, begin, strlen(begin));
+ if (MEM_ERROR) break;
+
+ dst = ptrmcat(dst, line, matchlen = mfirst->end - mfirst->start);
+ if (MEM_ERROR) break;
+ line += matchlen;
+ len -= matchlen;
+
+ dst = ptrmcat(dst, end, strlen(end));
+ if (MEM_ERROR) break;
+ }
+ } while (mfirst);
+
+ if (!MEM_ERROR)
+ dst = ptrmcat(dst, line, len);
+
+ return dst;
+}
+
+ptr ptraddmarks(ptr dst, ptr line)
+{
+ if (line)
+ return ptrmaddmarks(dst, ptrdata(line), ptrlen(line));
+ ptrzero(dst);
+ return dst;
+}
+
+/*
+ * write string to tty, word wrapping to next line if needed.
+ * don't print a final \n
+ */
+static void wrap_print(char *s)
+{
+ /* ls = last space in *s, lp = last space in *p */
+ char *ls, *lp, *p, c, follow = 1;
+ char buf[BUFSIZE]; /* ASSERT(cols<BUFSIZE) */
+
+ /* l = left, m = current offset */
+ int l, m;
+ enum { NORM, ESCAPE, BRACKET } state;
+#ifdef BUG_ANSI
+ int ansibug = 0;
+#endif
+
+ l = printstrlen(s);
+#ifdef BUG_ANSI
+ if (l > cols_1 && l < (int)strlen(s))
+ ansibug = 1;
+#endif
+
+ while (l >= cols_1 - col0 && *s) {
+ p = buf; m = 0; state = NORM;
+ lp = ls = NULL;
+
+ /* this scans over the remaining part of the line adding stuff to
+ * print to the buffer and tallying the length of displayed
+ * characters */
+ while (m < cols_1 - col0 && *s && *s != '\n') {
+ *p++ = c = *s++;
+ switch (state) {
+ case NORM:
+ if (c == ' ') {
+ ls = s;
+ lp = p;
+ }
+
+ if (c == '\033') {
+ state = ESCAPE;
+ }else if ((c & 0x80) || (c >= ' ' && c <= '~')) {
+ /* if char is hi (128+) or printable */
+ m++, l--;
+ }else if (c == '\r') {
+ ls = lp = NULL;
+ m = 0;
+ }
+
+ break;
+
+ case ESCAPE:
+ state = (c == '[') ? BRACKET : NORM;
+ break;
+
+ case BRACKET:
+ if (isalpha(c))
+ state = NORM;
+ break;
+ }
+ }
+
+ /* Adjust offsets and stuff to last space */
+ if( ls != NULL && ls != s ) {
+ /* move s back to the last space */
+ s = ls;
+ /* null term buf[] at last space */
+ lp[ 0 ] = 0;
+ }
+
+ follow = *s;
+
+ *p = '\0';
+ tty_printf("%s%s", buf, follow ? "\n" : "");
+ if (follow)
+ col0 = 0;
+ }
+
+#ifdef BUG_ANSI
+ if (ansibug)
+ tty_printf("%s%s%s", follow ? s : "" ,
+ tty_modenorm, tty_clreoln);
+ else
+#endif
+ if (follow)
+ tty_puts(s);
+}
+
+/*
+ * add marks to line and print.
+ * if newline, also print a final \n
+ */
+void smart_print(char *line, char newline)
+{
+ static ptr ptrbuf = NULL;
+ static char *buf;
+
+ do {
+ if (!ptrbuf) {
+ ptrbuf = ptrnew(PARAMLEN);
+ if (MEM_ERROR) break;
+ }
+ ptrbuf = ptrmaddmarks(ptrbuf, line, strlen(line));
+ if (MEM_ERROR || !ptrbuf) break;
+
+ buf = ptrdata(ptrbuf);
+
+ if (opt_wrap)
+ wrap_print(buf);
+ else {
+#ifdef BUG_ANSI
+ int l;
+ l = printstrlen(buf);
+ if (l > cols_1 && l < ptrlen(ptrbuf))
+ tty_printf("%s%s%s", buf, tty_modenorm, tty_clreoln);
+ else
+#endif
+ tty_printf("%s", buf);
+ }
+ } while(0);
+
+ if (MEM_ERROR)
+ print_error(error);
+ else if (newline)
+ col0 = 0, tty_putc('\n');
+}
+
+/*
+ * copy first word of src into dst, and return pointer to second word of src
+ */
+char *split_first_word(char *dst, int dstlen, char *src)
+{
+ char *tmp;
+ int len;
+
+ src = skipspace(src);
+ if (!*src) {
+ *dst='\0';
+ return src;
+ }
+ len = strlen(src);
+
+ tmp = memchrs(src, len, DELIM, DELIM_LEN);
+ if (tmp) {
+ if (dstlen > tmp-src+1) dstlen = tmp-src+1;
+ my_strncpy(dst, src, dstlen-1);
+ } else {
+ my_strncpy(dst, src, dstlen-1);
+ tmp = src + len;
+ }
+ if (*tmp && *tmp != CMDSEP) tmp++;
+ return tmp;
+}
+
+static void sig_pipe_handler(int signum)
+{
+ tty_puts("\n#broken pipe.\n");
+}
+
+static void sig_winch_handler(int signum)
+{
+ sig_pending = sig_winch_got = 1;
+}
+
+static void sig_chld_handler(int signum)
+{
+ sig_pending = sig_chld_got = 1;
+}
+
+static void sig_term_handler(int signum)
+{
+ tty_printf("%s\n#termination signal.\n", edattrend);
+ exit_powwow();
+}
+
+static void sig_intr_handler(int signum)
+{
+ if (confirm) {
+ tty_printf("%s\n#interrupted.%s\n", edattrend, tty_clreoln);
+ exit_powwow();
+ }
+
+ PRINTF("%s\n#interrupted. Press again to quit%s\n", edattrend, tty_clreoln);
+ tty_flush(); /* in case we are not in mainlupe */
+ confirm = 1;
+ error = USER_BREAK;
+
+ sig_oneshot(SIGINT, sig_intr_handler);
+}
+
+/*
+ * suspend ourselves
+ */
+void suspend_powwow(int signum)
+{
+ if (can_suspend) {
+ sig_permanent(SIGTSTP, SIG_DFL);
+ sig_permanent(SIGTERM, SIG_IGN);
+ sig_permanent(SIGINT, SIG_IGN);
+ tty_puts(edattrend);
+ tty_quit();
+
+ if (kill(0, SIGTSTP) < 0) {
+ errmsg("suspend powwow");
+ return;
+ }
+
+ signal_start();
+ tty_start();
+ tty_sig_winch_bottomhalf(); /* in case size changed meanwhile */
+ } else
+ tty_puts("\n#I don't think your shell has job control.\n");
+ status(1);
+}
+
+/*
+ * GH: Sets a signal-handler permanently (like bsd signal())
+ * from Ilie
+ */
+function_signal sig_permanent(int signum, function_signal sighandler)
+{
+ struct sigaction act;
+ function_signal oldhandler;
+
+ if (sigaction(signum, NULL, &act))
+ return SIG_ERR;
+ oldhandler = act.sa_handler;
+ act.sa_handler = sighandler;
+#ifdef SA_RESTART
+ act.sa_flags = SA_RESTART;
+#else
+ act.sa_flags = 0;
+#endif
+ if (sigaction(signum, &act, NULL))
+ return SIG_ERR;
+ return oldhandler;
+}
+
+/*
+ * One-shot only signal. Hope it will work as intended.
+ */
+#ifdef SA_ONESHOT
+function_signal sig_oneshot(int signum, function_signal sighandler)
+{
+ struct sigaction act;
+ function_signal oldhandler;
+
+ if (sigaction(signum, NULL, &act))
+ return SIG_ERR;
+ oldhandler = act.sa_handler;
+ act.sa_handler = sighandler;
+ act.sa_flags = SA_ONESHOT;
+ if (sigaction(signum, &act, NULL))
+ return SIG_ERR;
+ return oldhandler;
+}
+#endif
+
+
+/*
+ * set up our signal handlers
+ */
+void signal_start(void)
+{
+ if (sig_permanent(SIGTSTP, SIG_IGN) == SIG_DFL) {
+ sig_permanent(SIGTSTP, suspend_powwow);
+ can_suspend = 1;
+ }
+ sig_permanent(SIGCHLD, sig_chld_handler);
+ sig_permanent(SIGQUIT, sig_intr_handler);
+ sig_permanent(SIGTERM, sig_term_handler);
+ sig_permanent(SIGPIPE, sig_pipe_handler);
+ sig_permanent(SIGWINCH, sig_winch_handler);
+ /*
+ * this must not be permanent, as we want
+ * to be able to interrupt system calls
+ */
+ sig_oneshot(SIGINT, sig_intr_handler);
+}
+
+void sig_bottomhalf(void)
+{
+ if (sig_chld_got)
+ sig_chld_bottomhalf();
+ if (sig_winch_got)
+ tty_sig_winch_bottomhalf();
+
+ sig_pending = sig_chld_got = sig_winch_got = 0;
+}
+
+void errmsg(char *msg)
+{
+ if (!msg)
+ msg = "";
+
+ clear_input_line(opt_compact);
+ if (!opt_compact) {
+ tty_putc('\n');
+ status(1);
+ }
+ if (errno == EINTR) {
+ tty_printf("#user break: %s (%d: %s)\n",
+ msg, errno, strerror(errno));
+ } else if (errno) {
+ tty_printf("#system call error: %s (%d", msg, errno);
+ if (errno > 0)
+ tty_printf(": %s)\n", strerror(errno));
+ else
+ tty_puts(")\n");
+ } else if (error == NO_MEM_ERROR) {
+ tty_printf("#system call error: %s (%d", msg, ENOMEM);
+ tty_printf(": %s)\n", strerror(ENOMEM));
+ }
+ tty_flush();
+}
+
+/*
+ * print system call error message and terminate
+ */
+void syserr(char *msg)
+{
+ if (msg && *msg) {
+ clear_input_line(opt_compact);
+ if (!opt_compact) {
+ tty_putc('\n');
+ /* status(1); */
+ }
+ tty_flush();
+
+ fprintf(stderr, "#powwow: fatal system call error:\n\t%s (%d", msg, errno);
+ if (errno > 0)
+ fprintf(stderr, ": %s", strerror(errno));
+ fprintf(stderr, ")\n");
+ }
+
+#ifdef SAVE_ON_SYSERR
+ /* Try to do an emergency save. This can wreak havoc
+ * if called from the wrong place, like
+ * read_settings() or save_settings(),
+ * thus is executed only if you add -DSAVE_ON_SYSERR
+ * to CF flags in make_it
+ */
+ (void)save_settings();
+#else
+ tty_puts("#settings NOT saved to file.\n");
+#endif
+
+ tty_quit();
+ exit(1);
+}
+
+static void load_missing_stuff(int n)
+{
+ char buf[BUFSIZE];
+
+ if (n < 1) {
+ tty_add_walk_binds();
+ tty_puts("#default keypad settings loaded\n");
+ }
+ if (n < 2) {
+ tty_add_initial_binds();
+ tty_puts("#default editing keys settings loaded\n");
+ }
+ if (n < 5) {
+ static char *str[] = { "compact", "debug", "echo", "info",
+ "keyecho", "speedwalk", "wrap", 0 };
+ int i;
+ for (i=0; str[i]; i++) {
+ sprintf(buf, "#%s={#if ($(1)==\"on\") #option +%s; #else #if ($(1)==\"off\") #option -%s; #else #option %s}",
+ str[i], str[i], str[i], str[i]);
+ parse_alias(buf);
+ }
+ tty_printf("#compatibility aliases loaded:\n\t%s\n",
+ "#compact, #debug, #echo, #info, #keyecho, #speedwalk, #wrap");
+ }
+ if (n < 6) {
+ sprintf(buf, "#lines=#setvar lines=$0");
+ parse_alias(buf);
+ sprintf(buf, "#settimer=#setvar timer=$0");
+ parse_alias(buf);
+ limit_mem = 1048576;
+ tty_printf("#compatibility aliases loaded:\n\t%s\n", "#lines, #settimer");
+ tty_puts("#max text/strings length set to 1048576 bytes\n\tuse \"#setvar mem\" to change it\n\n#wait...");
+ tty_flush();
+ sleep(1);
+ tty_puts("ok\n");
+ }
+}
+
+/*
+ * read definitions from file
+ * return > 0 if success, < 0 if fail.
+ * NEVER call syserr() from here or powwow will
+ * try to save the definition file even if it got
+ * a broken or empty one.
+ */
+int read_settings(void)
+{
+ FILE *f;
+ char *buf, *p, *cmd, old_nice = a_nice;
+ int failed = 1, n, savefilever = 0, left, len, limit_mem_hit = 0;
+ varnode **first;
+ ptr ptrbuf;
+
+ if (!*deffile) {
+ PRINTF("#warning: no save-file defined!\n");
+ return 0;
+ }
+
+ f = fopen(deffile, "r");
+ if (!f) {
+ PRINTF("#error: cannot open file \"%s\": %s\n", deffile, strerror(errno));
+ return 0;
+ }
+
+ ptrbuf = ptrnew(PARAMLEN);
+ if (MEM_ERROR) {
+ print_error(error);
+ return 0;
+ }
+ buf = ptrdata(ptrbuf);
+ left = ptrmax(ptrbuf);
+ len = 0;
+
+ opt_info = a_nice = 0;
+
+ for (n = 0; n < MAX_HASH; n++) {
+ while (aliases[n])
+ delete_aliasnode(&aliases[n]);
+ }
+ while (actions)
+ delete_actionnode(&actions);
+ while (prompts)
+ delete_promptnode(&prompts);
+ while (markers)
+ delete_marknode(&markers);
+ while (keydefs)
+ delete_keynode(&keydefs);
+ for (n = 0; n < MAX_HASH; n++) {
+ while (named_vars[0][n])
+ delete_varnode(&named_vars[0][n], 0);
+ first = &named_vars[1][n];
+ while (*first) {
+ if (is_permanent_variable(*first))
+ first = &(*first)->next;
+ else
+ delete_varnode(first, 1);
+ }
+ }
+
+ for (n = 0; n < NUMVAR; n++) {
+ *var[n].num = 0;
+ ptrdel(*var[n].str);
+ *var[n].str = NULL;
+ }
+
+ while (left > 0 && fgets(buf+len, left+1, f)) {
+ /* WARNING: accessing private field ->len */
+ len += n = strlen(buf+len);
+ ptrbuf->len = len;
+ left -= n;
+
+ /* Clear all \n prefixed with a literal backslash '\\' */
+ while ((cmd = strstr(buf, "\\\n"))) {
+ cmd[ 0 ] = ' ';
+ cmd[ 1 ] = ' ';
+ }
+
+ cmd = strchr(buf, '\n');
+
+ if (!cmd) {
+ if (feof(f)) {
+ PRINTF("#error: missing newline at end of file \"%s\"\n", deffile);
+ break;
+ }
+ /* no newline yet. increase line size and try again */
+ ptrbuf = ptrpad(ptrbuf, ptrlen(ptrbuf) >> 1);
+ if (MEM_ERROR) {
+ limit_mem_hit = 1;
+ print_error(error);
+ break;
+ }
+ ptrtrunc(ptrbuf,len);
+ buf = ptrdata(ptrbuf);
+ left = ptrmax(ptrbuf) - len;
+ continue;
+ }
+ /* got a full line */
+ *cmd = '\0';
+ cmd = buf;
+ left += len;
+ len = 0;
+
+ cmd = skipspace(cmd);
+ if (!*cmd)
+ continue;
+
+ error = 0;
+ if (*(p = first_regular(cmd, ' '))) {
+ *p++ = '\0';
+ if (!strcmp(cmd, "#savefile-version")) {
+ savefilever = atoi(p);
+ continue;
+ }
+ *--p = ' ';
+ }
+ parse_user_input(cmd, 1);
+ }
+
+ if (error)
+ failed = -1;
+ else if (ferror(f) && !feof(f)) {
+ PRINTF("#error: cannot read file \"%s\": %s\n", deffile, strerror(errno));
+ failed = -1;
+ } else if (limit_mem_hit) {
+ PRINTF("#error: cannot load save-file: got a line longer than limit\n");
+ failed = -1;
+ } else if (savefilever > SAVEFILEVER) {
+ PRINTF("#warning: this powwow version is too old!\n");
+ } else if (savefilever < SAVEFILEVER) {
+ PRINTF("\n#warning: config file is from an older version\n");
+ load_missing_stuff(savefilever);
+ }
+
+ fclose(f);
+ a_nice = old_nice;
+
+ if (ptrbuf)
+ ptrdel(ptrbuf);
+
+ return failed;
+}
+
+static char tmpname[BUFSIZE];
+
+static void fail_msg(void)
+{
+ PRINTF("#error: cannot write to temporary file \"%s\": %s\n", tmpname, strerror(errno));
+}
+
+/*
+ * save settings in definition file
+ * return > 0 if success, < 0 if fail.
+ * NEVER call syserr() from here or powwow will
+ * enter an infinite loop!
+ */
+int save_settings(void)
+{
+ FILE *f;
+ int l;
+ aliasnode *alp;
+ actionnode *acp;
+ promptnode *ptp;
+ marknode *mp;
+ keynode *kp;
+ varnode *vp;
+ ptr pp = (ptr)0;
+ int i, flag, failed = 1;
+
+ if (REAL_ERROR) {
+ PRINTF("#will not save after an error!\n");
+ return -1;
+ }
+ error = 0;
+
+ if (!*deffile) {
+ PRINTF("#warning: no save-file defined!\n");
+ return -1;
+ }
+
+ /*
+ * Create a temporary file in the same directory as deffile,
+ * and write settings there
+ */
+ strcpy(tmpname, deffile);
+ l = strlen(tmpname) - 1;
+ while (l && tmpname[l] != '/')
+ l--;
+ if (l)
+ l++;
+
+ sprintf(tmpname + l, "tmpsav%d%d", getpid(), rand() >> 8);
+ if (!(f = fopen(tmpname, "w"))) {
+ fail_msg();
+ return -1;
+ }
+
+ pp = ptrnew(PARAMLEN);
+ if (MEM_ERROR) failed = -1;
+
+ failed = fprintf(f, "#savefile-version %d\n", SAVEFILEVER);
+ if (failed > 0 && *hostname)
+ failed = fprintf(f, "#host %s %d\n", hostname, portnumber);
+
+ if (failed > 0) {
+ if (delim_mode == DELIM_CUSTOM) {
+ pp = ptrmescape(pp, DELIM, strlen(DELIM), 0);
+ if (MEM_ERROR) failed = -1;
+ }
+ if (failed > 0)
+ failed = fprintf(f, "#delim %s%s\n", delim_name[delim_mode],
+ delim_mode == DELIM_CUSTOM ? ptrdata(pp) : "" );
+ }
+
+ if (failed > 0)
+ failed = fprintf(f, "#groupdelim %s\n", group_delim);
+
+ if (failed > 0 && *initstr)
+ failed = fprintf(f, "#init =%s\n", initstr);
+
+ if (failed > 0 && limit_mem)
+ failed = fprintf(f, "#setvar mem=%d\n", limit_mem);
+
+ if (failed > 0 && (i = log_getsize()))
+ failed = fprintf(f, "#setvar buffer=%d\n", i);
+
+ if (failed > 0) {
+ reverse_sortedlist((sortednode **)&sortedaliases);
+ for (alp = sortedaliases; alp && failed > 0; alp = alp->snext) {
+ pp = ptrmescape(pp, alp->name, strlen(alp->name), 0);
+ if (MEM_ERROR) { failed = -1; break; }
+ failed = fprintf(f, "#alias %s%s%s=%s\n", ptrdata(pp),
+ alp -> group == NULL ? "" : group_delim,
+ alp -> group == NULL ? "" : alp -> group,
+ alp->subst);
+ }
+ reverse_sortedlist((sortednode **)&sortedaliases);
+ }
+
+ for (acp = actions; acp && failed > 0; acp = acp->next) {
+ failed = fprintf(f, "#action %c%c%s%s%s %s=%s\n",
+ action_chars[acp->type], acp->active ? '+' : '-',
+ acp->label,
+ acp -> group == NULL ? "" : group_delim,
+ acp -> group == NULL ? "" : acp -> group,
+ acp->pattern, acp->command);
+ }
+
+ for (ptp = prompts; ptp && failed > 0; ptp = ptp->next) {
+ failed = fprintf(f, "#prompt %c%c%s %s=%s\n",
+ action_chars[ptp->type], ptp->active ? '+' : '-',
+ ptp->label, ptp->pattern, ptp->command);
+ }
+
+ for (mp = markers; mp && failed > 0; mp = mp->next) {
+ pp = ptrmescape(pp, mp->pattern, strlen(mp->pattern), 0);
+ if (MEM_ERROR) { failed = -1; break; }
+ failed = fprintf(f, "#mark %s%s=%s\n", mp->mbeg ? "^" : "",
+ ptrdata(pp), attr_name(mp->attrcode));
+ }
+ /* save value of global variables */
+
+ for (flag = 0, i=0; i<NUMVAR && failed > 0; i++) {
+ if (var[i].num && *var[i].num) { /* first check was missing!!! */
+ failed = fprintf(f, "%s@%d = %ld", flag ? ", " : "#(",
+ i-NUMVAR, *var[i].num);
+ flag = 1;
+ }
+ }
+ if (failed > 0 && flag) failed = fprintf(f, ")\n");
+
+ for (i=0; i<NUMVAR && failed > 0; i++) {
+ if (var[i].str && *var[i].str && ptrlen(*var[i].str)) {
+ pp = ptrescape(pp, *var[i].str, 0);
+ if (MEM_ERROR) { failed = -1; break; }
+ failed = fprintf(f, "#($%d = \"%s\")\n", i-NUMVAR, ptrdata(pp));
+ }
+ }
+
+ if (failed > 0) {
+ reverse_sortedlist((sortednode **)&sortednamed_vars[0]);
+ for (flag = 0, vp = sortednamed_vars[0]; vp && failed > 0; vp = vp->snext) {
+ if (vp->num) {
+ failed = fprintf(f, "%s@%s = %ld", flag ? ", " : "#(",
+ vp->name, vp->num);
+ flag = 1;
+ }
+ }
+ reverse_sortedlist((sortednode **)&sortednamed_vars[0]);
+ }
+ if (failed > 0 && flag) failed = fprintf(f, ")\n");
+
+ if (failed > 0) {
+ reverse_sortedlist((sortednode **)&sortednamed_vars[1]);
+ for (vp = sortednamed_vars[1]; vp && failed > 0; vp = vp->snext) {
+ if (!is_permanent_variable(vp) && vp->str && ptrlen(vp->str)) {
+ pp = ptrescape(pp, vp->str, 0);
+ if (MEM_ERROR) { failed = -1; break; }
+ failed = fprintf(f, "#($%s = \"%s\")\n", vp->name, ptrdata(pp));
+ }
+ }
+ reverse_sortedlist((sortednode **)&sortednamed_vars[1]);
+ }
+
+ /* GH: fixed the history and word completions saves */
+ if (failed > 0 && opt_history) {
+ l = (curline + 1) % MAX_HIST;
+ while (failed > 0 && l != curline) {
+ if (hist[l] && *hist[l]) {
+ pp = ptrmescape(pp, hist[l], strlen(hist[l]), 0);
+ if (MEM_ERROR) { failed = -1; break; }
+ failed = fprintf(f, "#put %s\n", ptrdata(pp));
+ }
+ if (++l >= MAX_HIST)
+ l = 0;
+ }
+ }
+
+ if (failed > 0 && opt_words) {
+ int cl = 4, len;
+ l = wordindex;
+ flag = 0;
+ while (words[l = words[l].next].word)
+ ;
+ while (words[l = words[l].prev].word && failed > 0) {
+ pp = ptrmescape(pp, words[l].word, strlen(words[l].word), 0);
+ len = ptrlen(pp) + 1;
+ if (cl > 4 && cl + len >= 80) {
+ cl = 4;
+ failed = fprintf(f, "\n");
+ flag = 0;
+ }
+ if (failed > 0)
+ failed = fprintf(f, "%s %s", flag ? "" : "#add", ptrdata(pp));
+ cl += len;
+ flag = 1;
+ }
+ if (failed > 0 && flag)
+ failed = fprintf(f, "\n");
+ }
+
+ for (kp = keydefs; kp && failed > 0; kp = kp->next) {
+ if (kp->funct==key_run_command)
+ failed = fprintf(f, "#bind %s %s=%s\n", kp->name,
+ seq_name(kp->sequence, kp->seqlen), kp->call_data);
+ else
+ failed = fprintf(f, "#bind %s %s=%s%s%s\n", kp->name,
+ seq_name(kp->sequence, kp->seqlen),
+ internal_functions[lookup_edit_function(kp->funct)].name,
+ kp->call_data ? " " : "",
+ kp->call_data ? kp->call_data : "");
+ }
+
+ if (failed > 0)
+ failed = print_all_options(f);
+
+ fclose(f);
+
+ if (error)
+ errmsg("malloc");
+ else if (failed <= 0)
+ fail_msg();
+ else {
+ failed = rename(tmpname, deffile);
+ if (failed == -1) {
+ PRINTF("#error: cannot move temporary file \"%s\" to \"%s\": %s\n",
+ tmpname, deffile, strerror(errno));
+ } else
+ failed = 1;
+ }
+
+ if (pp)
+ ptrdel(pp);
+
+ return failed > 0 ? 1 : -1;
+}
+
+/*
+ * update "now" to current time
+ */
+void update_now(void)
+{
+ if (!now_updated) {
+ gettimeofday(&now, NULL);
+ now_updated = 1;
+ }
+}
+
+/*
+ * terminate powwow as cleanly as possible
+ */
+void exit_powwow(void)
+{
+ log_flush();
+ if (capturefile) fclose(capturefile);
+ if (recordfile) fclose(recordfile);
+ if (moviefile) fclose(moviefile);
+ (void)save_settings();
+ show_stat();
+ tty_quit();
+ exit(0);
+}
diff --git a/src/utils.h b/src/utils.h
new file mode 100644
index 0000000..f5b8704
--- /dev/null
+++ b/src/utils.h
@@ -0,0 +1,48 @@
+/* public declarations from utils.c */
+
+#ifndef _UTILS_H_
+#define _UTILS_H_
+
+char *my_strdup(char *s);
+char *my_strncpy(char *dst, char *src, int len);
+int printstrlen(char *s);
+
+void ptrunescape(ptr p);
+int memunescape(char *p, int lenp);
+
+ptr ptrescape(ptr dst, ptr src, int append);
+ptr ptrmescape(ptr dst, char *src, int srclen, int append);
+
+ptr ptraddmarks(ptr dst, ptr line);
+ptr ptrmaddmarks(ptr dst, char *line, int len);
+
+void put_marks(char *dst, char *line);
+void smart_print(char *line, char newline);
+char *split_first_word(char *dst, int dstlen, char *src);
+char *first_valid(char *p, char ch);
+char *first_regular(char *p, char c);
+void unescape(char *s);
+void escape_specials(char *str, char *p);
+char *skipspace(char *p);
+void exit_powwow(void);
+void suspend_powwow(int signum);
+function_signal sig_permanent(int signum, function_signal sighandler);
+
+#ifdef SA_ONESHOT
+ function_signal sig_oneshot(int signum, function_signal sighandler);
+#else
+# define sig_oneshot signal
+#endif
+
+void signal_start(void);
+void sig_bottomhalf(void);
+void errmsg(char *msg);
+void syserr(char *msg);
+int read_settings(void);
+int save_settings(void);
+void movie_write(char *str, int newline);
+
+void update_now(void);
+
+#endif /* _UTILS_H_ */
+