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_ */ + | 
