diff options
author | Steve Slaven <bpk@hoopajoo.net> | 2019-11-05 06:26:14 (GMT) |
---|---|---|
committer | Steve Slaven <bpk@hoopajoo.net> | 2019-11-05 06:26:14 (GMT) |
commit | 9c4c0a1e366b9d932e4ab2ce03a0e80126d93d9b (patch) | |
tree | 928e4c6f49ac50f7e69777b00073df37d7d11e3f /src | |
parent | eb9898c7fcc017a35c240c1bd83c8a8ff451431a (diff) | |
download | powwow-9c4c0a1e366b9d932e4ab2ce03a0e80126d93d9b.zip powwow-9c4c0a1e366b9d932e4ab2ce03a0e80126d93d9b.tar.gz powwow-9c4c0a1e366b9d932e4ab2ce03a0e80126d93d9b.tar.bz2 |
reorganizing files
Diffstat (limited to 'src')
-rw-r--r-- | src/Makefile.am | 29 | ||||
-rw-r--r-- | src/beam.c | 480 | ||||
-rw-r--r-- | src/beam.h | 17 | ||||
-rw-r--r-- | src/catrw.c | 43 | ||||
-rw-r--r-- | src/cmd.c | 2713 | ||||
-rw-r--r-- | src/cmd.h | 26 | ||||
-rw-r--r-- | src/cmd2.c | 1676 | ||||
-rw-r--r-- | src/cmd2.h | 32 | ||||
-rw-r--r-- | src/defines.h | 307 | ||||
-rw-r--r-- | src/edit.c | 961 | ||||
-rw-r--r-- | src/edit.h | 75 | ||||
-rw-r--r-- | src/eval.c | 1456 | ||||
-rw-r--r-- | src/eval.h | 58 | ||||
-rw-r--r-- | src/feature/regex.h | 11 | ||||
-rw-r--r-- | src/follow.c | 170 | ||||
-rw-r--r-- | src/list.c | 675 | ||||
-rw-r--r-- | src/list.h | 47 | ||||
-rw-r--r-- | src/log.c | 329 | ||||
-rw-r--r-- | src/log.h | 22 | ||||
-rw-r--r-- | src/main.c | 2084 | ||||
-rw-r--r-- | src/main.h | 119 | ||||
-rw-r--r-- | src/map.c | 196 | ||||
-rw-r--r-- | src/map.h | 15 | ||||
-rw-r--r-- | src/plugtest.c | 29 | ||||
-rw-r--r-- | src/powwow-movieplay.c | 97 | ||||
-rw-r--r-- | src/powwow-muc.c | 307 | ||||
-rw-r--r-- | src/ptr.c | 586 | ||||
-rw-r--r-- | src/ptr.h | 75 | ||||
-rw-r--r-- | src/tcp.c | 1045 | ||||
-rw-r--r-- | src/tcp.h | 79 | ||||
-rw-r--r-- | src/tty.c | 1038 | ||||
-rw-r--r-- | src/tty.h | 58 | ||||
-rw-r--r-- | src/utils.c | 1299 | ||||
-rw-r--r-- | src/utils.h | 48 |
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, ¶m))) + 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, ¶ms))) + 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, ¶ms))) { + 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, <csave); + 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, <c); +#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, <csave); +#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 = ¶mstk.p[0][j].num; + VAR[j].str = ¶mstk.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 = ¶mstk.p[paramstk.curr][i].num) = 0; + ptrzero(*(VAR[i].str = ¶mstk.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 = ¶mstk.p[paramstk.curr][i].num; + VAR[i].str = ¶mstk.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, <csave); + 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, <c); +#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, <csave); +#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, <c); +#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_ */ + |