diff options
Diffstat (limited to 'src/cmd.c')
-rw-r--r-- | src/cmd.c | 2713 |
1 files changed, 2713 insertions, 0 deletions
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); +} + + |