This is patch05 to PennMUSH 1.8.3. After applying this patch, you will have version 1.8.4p5 To apply this patch, save it to a file in your top-level 1.8.3p4 MUSH directory, and do the following: patch -p0 < 1.8.3-patch05 make update make install If you use GNU patch 2.2, you probably want the above to be 'patch -b -p0', not just 'patch -p0'. Unix (or cygwin) users need not worry about failed hunks in src/switchinc.c, hdrs/switches.h, hdrs/cmds.h, or hdrs/funs.h. These files are automatically rebuilt on compile. On the off chance they appear not to be, simply rm them and re-run make. Then @shutdown and restart your MUSH. - Shawn/Raevnos In this patch: Major changes: * Significant rewrite of ansi parsing and better ansi support for many string-handling functions. Patch by Sketch. * Rewrite of the softcode regression testing framework, and addition of more tests. [SW] Minor changes: * Store a pointer to the start of a player's mailbox in objdata instead of the connection struct. * Experimental rewrite of hash tables to use the cuckoo hashing algorithm, with constant-time lookups even in the worst case. (And appears to have generally faster lookup even in normal usage.) * Regular expression @sitelocks save the compiled regexp instead of recompiling every time the rule is tested. * Added %4 to @pageformat, which is the default page message. Commands: * Added @message, which makes it easy to use @chatformat or @pageformat via @hooks, or to create your own *format. Functions: * Added message(), the function version of @message. Fixes: * decode64() does better validation of its input. [SW] * Various compile fixes reported by Interevis and Kimiko. Win32 patched by Intrevis. * @sitelock does better error reporting. [SW] * Crash bug related to regeditall fixed. * @decompile didn't handle attribute trees correctly. * Compile failure in funstr.c on some systems. Fixed by Boris. * '@set =foo' failed silently. Reported by Talvo. * Fixes from 1.8.2p7 Prereq: 1.8.3p4 =================================================================== --- Patchlevel (.../p4) (revision 1119) +++ Patchlevel (.../p5) (revision 1119) @@ -1,2 +1,2 @@ Do not edit this file. It is maintained by the official PennMUSH patches. -This is PennMUSH 1.8.3p4 +This is PennMUSH 1.8.3p5 Index: configure =================================================================== --- configure (.../p4) (revision 1119) +++ configure (.../p5) (revision 1119) @@ -17943,6 +17943,8 @@ ac_config_files="$ac_config_files game/txt/compose.sh" +ac_config_files="$ac_config_files test/alltests.sh" + cat >confcache <<\_ACEOF # This file is a shell script that caches the results of configure # tests run on this system so they can be shared between configure @@ -18487,6 +18489,7 @@ "Makefile") CONFIG_FILES="$CONFIG_FILES Makefile" ;; "src/Makefile") CONFIG_FILES="$CONFIG_FILES src/Makefile" ;; "game/txt/compose.sh") CONFIG_FILES="$CONFIG_FILES game/txt/compose.sh" ;; + "test/alltests.sh") CONFIG_FILES="$CONFIG_FILES test/alltests.sh" ;; *) { { echo "$as_me:$LINENO: error: invalid argument: $ac_config_target" >&5 echo "$as_me: error: invalid argument: $ac_config_target" >&2;} @@ -19033,6 +19036,7 @@ case $ac_file$ac_mode in "game/txt/compose.sh":F) chmod +x game/txt/compose.sh ;; + "test/alltests.sh":F) chmod +x test/alltests.sh ;; esac done # for ac_tag Index: Makefile.in =================================================================== --- Makefile.in (.../p4) (revision 1119) +++ Makefile.in (.../p5) (revision 1119) @@ -3,7 +3,7 @@ # - System configuration - # VERSION=1.8.3 -PATCHLEVEL=3 +PATCHLEVEL=5 # # This section of the file should be automatically configured by @@ -80,6 +80,7 @@ netmud: (cd src; make netmud "CC=$(CC)" "CCFLAGS=$(CCFLAGS)" \ + "SQL_CFLAGS=$(SQL_CFLAGS)" "SQL_LDFLAGS=$(SQL_LDFLAGS)" \ "LDFLAGS=$(LDFLAGS)" "CLIBS=$(CLIBS)" ) access: @@ -162,6 +163,7 @@ update-hdr: -@@TOUCH@ options.h.dist + -@sleep 2 -@@PERL@ utils/update.pl options.h options.h.dist test: netmud Index: options.h.dist =================================================================== --- options.h.dist (.../p4) (revision 1119) +++ options.h.dist (.../p5) (revision 1119) @@ -58,7 +58,7 @@ * when decompressing, and considerably slower when compressing. * (But you decompress a lot more often). Compression ratio * is worse than Huffman for small dbs (<1.5Mb of text), but - * better for larger dbs. Win32 systems must use this. + * better for larger dbs. * 4 - Raevnos's almost 8-bit clean version of the word-based algorithm. * Prefer 3 unless you need extended characters. This algorithm * can encode all characters except 0x06. Index: src/sql.c =================================================================== --- src/sql.c (.../p4) (revision 1119) +++ src/sql.c (.../p5) (revision 1119) @@ -427,23 +427,14 @@ break; } if (cell && *cell) { - if (strchr(cell, ESC_CHAR)) { - /* Old style ANSI string */ + if (strchr(cell, TAG_START) || strchr(cell, ESC_CHAR)) { + /* Either old or new style ANSI string. */ tbp = tbuf; - as = parse_ansi_string_real(cell, 1); + as = parse_ansi_string(cell); safe_ansi_string(as, 0, as->len, tbuf, &tbp); *tbp = '\0'; free_ansi_string(as); cell = tbuf; - } else if (strchr(cell, TAG_START)) { - /* Either old or new style ANSI string, - * We assume new style. */ - tbp = tbuf; - as = parse_ansi_string_real(cell, 2); - safe_ansi_string(as, 0, as->len, tbuf, &tbp); - *tbp = '\0'; - free_ansi_string(as); - cell = tbuf; } } notify_format(player, "Row %d, Field %s: %s", @@ -478,7 +469,6 @@ #ifdef HAVE_MYSQL MYSQL_FIELD *fields = NULL; #endif - if (sql_platform() == SQL_PLATFORM_DISABLED) { safe_str(T(e_disabled), buff, bp); return; @@ -490,7 +480,6 @@ if (!fetch_ufun_attrib(args[0], executor, &ufun, 1)) return; - if (nargs > 2) { /* we have an output separator in args[2]. */ osep = args[2]; @@ -504,10 +493,8 @@ for (i = 0; i < 10; i++) wenv[i] = NULL; - qres = sql_query(args[1], &affected_rows); sql_test_result(qres); - /* Get results. A silent query (INSERT, UPDATE, etc.) will return NULL */ switch (sql_platform()) { #ifdef HAVE_MYSQL @@ -608,24 +595,15 @@ default: break; } - if (cell) { - if (strchr(cell, ESC_CHAR)) { - /* Old style ANSI string */ + if (cell && *cell) { + if (strchr(cell, TAG_START) || strchr(cell, ESC_CHAR)) { + /* Either old or new style ANSI string. */ tbp = buffs[i]; - as = parse_ansi_string_real(cell, 1); + as = parse_ansi_string(cell); safe_ansi_string(as, 0, as->len, buffs[i], &tbp); *tbp = '\0'; free_ansi_string(as); cell = buffs[i]; - } else if (strchr(cell, TAG_START)) { - /* Either old or new style ANSI string, - * We assume new style. */ - tbp = buffs[i]; - as = parse_ansi_string_real(cell, 2); - safe_ansi_string(as, 0, as->len, buffs[i], &tbp); - *tbp = '\0'; - free_ansi_string(as); - cell = buffs[i]; } } wenv[i + 1] = cell; @@ -635,7 +613,8 @@ /* Now call the ufun. */ if (call_ufun(&ufun, wenv, i + 1, rbuff, executor, enactor, pe_info)) goto finished; - if (safe_str(rbuff, buff, bp) && funccount == pe_info->fun_invocations) + if (safe_str(rbuff, buff, bp) + && funccount == pe_info->fun_invocations) goto finished; funccount = pe_info->fun_invocations; } @@ -655,7 +634,6 @@ int i; int numfields, numrows; ansi_string *as; - if (sql_platform() == SQL_PLATFORM_DISABLED) { safe_str(T(e_disabled), buff, bp); return; @@ -676,9 +654,7 @@ } qres = sql_query(args[0], &affected_rows); - sql_test_result(qres); - /* Get results. A silent query (INSERT, UPDATE, etc.) will return NULL */ switch (sql_platform()) { #ifdef HAVE_MYSQL @@ -748,23 +724,14 @@ break; } if (cell && *cell) { - if (strchr(cell, ESC_CHAR)) { - /* Old style ANSI string */ + if (strchr(cell, TAG_START) || strchr(cell, ESC_CHAR)) { + /* Either old or new style ANSI string. */ tbp = tbuf; - as = parse_ansi_string_real(cell, 1); + as = parse_ansi_string(cell); safe_ansi_string(as, 0, as->len, tbuf, &tbp); *tbp = '\0'; free_ansi_string(as); cell = tbuf; - } else if (strchr(cell, TAG_START)) { - /* Either old or new style ANSI string, - * We assume new style. */ - tbp = tbuf; - as = parse_ansi_string_real(cell, 2); - safe_ansi_string(as, 0, as->len, tbuf, &tbp); - *tbp = '\0'; - free_ansi_string(as); - cell = tbuf; } if (safe_str(cell, buff, bp)) goto finished; /* We filled the buffer, best stop */ Index: src/extchat.c =================================================================== --- src/extchat.c (.../p4) (revision 1119) +++ src/extchat.c (.../p5) (revision 1119) @@ -3368,9 +3368,9 @@ if (!(((flags & 1) && Chanuser_Quiet(u)) || Chanuser_Gag(u) || (IsPlayer(current) && !Connected(current)))) { - if (!messageformat(current, "CHATFORMAT", player, - na_flags | ((flags & CB_NOSPOOF) ? 0 : NA_SPOOF), - ctype, cname, message, name, title, tbuf1)) { + if (!vmessageformat(current, "CHATFORMAT", player, + na_flags | ((flags & CB_NOSPOOF) ? 0 : NA_SPOOF), + 6, ctype, cname, message, name, title, tbuf1)) { notify_anything(player, na_one, ¤t, ns_esnotify, na_flags | ((flags & CB_NOSPOOF) ? 0 : NA_SPOOF), tbuf1); Index: src/utils.c =================================================================== --- src/utils.c (.../p4) (revision 1119) +++ src/utils.c (.../p5) (revision 1119) @@ -231,13 +231,9 @@ int pe_ret; char const *ap; - int old_re_subpatterns; - int *old_re_offsets; - ansi_string *old_re_from; + struct re_save rsave; - old_re_subpatterns = global_eval_context.re_subpatterns; - old_re_offsets = global_eval_context.re_offsets; - old_re_from = global_eval_context.re_from; + save_regexp_context(&rsave); /* Make sure we have a ufun first */ if (!ufun) @@ -261,6 +257,7 @@ /* Set all the regexp patterns to NULL so they are not * propogated */ + global_eval_context.re_code = NULL; global_eval_context.re_subpatterns = -1; global_eval_context.re_offsets = NULL; global_eval_context.re_from = NULL; @@ -286,9 +283,7 @@ } /* Restore regexp patterns */ - global_eval_context.re_offsets = old_re_offsets; - global_eval_context.re_subpatterns = old_re_subpatterns; - global_eval_context.re_from = old_re_from; + restore_regexp_context(&rsave); return pe_ret; } @@ -323,9 +318,7 @@ ATTR *attrib; char *saver[NUMQ]; - int old_re_subpatterns; - int *old_re_offsets; - ansi_string *old_re_from; + struct re_save rsave; /* Make sure we have a valid object to call first */ if (!GoodObject(thing) || IsGarbage(thing)) @@ -350,9 +343,7 @@ save_global_regs("localize", saver); /* Store regepx info */ - old_re_subpatterns = global_eval_context.re_subpatterns; - old_re_offsets = global_eval_context.re_offsets; - old_re_from = global_eval_context.re_from; + save_regexp_context(&rsave); /* If the user doesn't care about the return of the expression, * then use our own rbuff. @@ -377,6 +368,7 @@ /* Set all the regexp patterns to NULL so they are not * propogated */ + global_eval_context.re_code = NULL; global_eval_context.re_subpatterns = -1; global_eval_context.re_offsets = NULL; global_eval_context.re_from = NULL; @@ -402,10 +394,7 @@ } /* Restore regexp patterns */ - global_eval_context.re_offsets = old_re_offsets; - global_eval_context.re_subpatterns = old_re_subpatterns; - global_eval_context.re_from = old_re_from; - + restore_regexp_context(&rsave); restore_global_regs("localize", saver); return pe_ret; Index: src/malias.c =================================================================== --- src/malias.c (.../p4) (revision 1119) +++ src/malias.c (.../p5) (revision 1119) @@ -142,7 +142,7 @@ return; } if (!alias || !*alias || !tolist || !*tolist) { - notify(player, T("MAIL: What alias do you want to create?.")); + notify(player, T("MAIL: What alias do you want to create?")); return; } if (*alias != MALIAS_TOKEN) { Index: src/wiz.c =================================================================== --- src/wiz.c (.../p4) (revision 1119) +++ src/wiz.c (.../p5) (revision 1119) @@ -1028,7 +1028,6 @@ void do_debug_examine(dbref player, const char *name) { - MAIL *mp; dbref thing; if (!Hasprivs(player)) { @@ -1052,8 +1051,7 @@ switch (Typeof(thing)) { case TYPE_PLAYER: - mp = desc_mail(thing); - notify_format(player, T("First mail sender: %d"), mp ? mp->from : NOTHING); + break; case TYPE_THING: notify_format(player, "Location: %d", Location(thing)); notify_format(player, "Home: %d", Home(thing)); @@ -1447,7 +1445,7 @@ return; } if (opts && *opts) { - int can, cant; + uint32_t can, cant; dbref whod = AMBIGUOUS; /* Options form of the command. */ if (!site || !*site) { @@ -1467,20 +1465,23 @@ } } - add_access_sitelock(player, site, whod, can, cant); - write_access_file(); - if (whod != AMBIGUOUS) { - notify_format(player, - T("Site %s access options for %s(%s) set to %s"), - site, Name(whod), unparse_dbref(whod), opts); - do_log(LT_WIZ, player, NOTHING, - T("*** SITELOCK *** %s for %s(%s) --> %s"), site, - Name(whod), unparse_dbref(whod), opts); - } else { - notify_format(player, T("Site %s access options set to %s"), site, opts); - do_log(LT_WIZ, player, NOTHING, "*** SITELOCK *** %s --> %s", site, opts); + if (add_access_sitelock(player, site, whod, can, cant)) { + write_access_file(); + if (whod != AMBIGUOUS) { + notify_format(player, + T("Site %s access options for %s(%s) set to %s"), + site, Name(whod), unparse_dbref(whod), opts); + do_log(LT_WIZ, player, NOTHING, + T("*** SITELOCK *** %s for %s(%s) --> %s"), site, + Name(whod), unparse_dbref(whod), opts); + } else { + notify_format(player, T("Site %s access options set to %s"), site, + opts); + do_log(LT_WIZ, player, NOTHING, "*** SITELOCK *** %s --> %s", site, + opts); + } + return; } - return; } else { /* Backward-compatible non-options form of the command, * or @sitelock/name @@ -1491,16 +1492,18 @@ do_list_access(player); return; case SITELOCK_ADD: - add_access_sitelock(player, site, AMBIGUOUS, 0, ACS_CREATE); - write_access_file(); - notify_format(player, T("Site %s locked"), site); - do_log(LT_WIZ, player, NOTHING, "*** SITELOCK *** %s", site); + if (add_access_sitelock(player, site, AMBIGUOUS, 0, ACS_CREATE)) { + write_access_file(); + notify_format(player, T("Site %s locked"), site); + do_log(LT_WIZ, player, NOTHING, "*** SITELOCK *** %s", site); + } break; case SITELOCK_BAN: - add_access_sitelock(player, site, AMBIGUOUS, 0, ACS_DEFAULT); - write_access_file(); - notify_format(player, T("Site %s banned"), site); - do_log(LT_WIZ, player, NOTHING, "*** SITELOCK *** %s", site); + if (add_access_sitelock(player, site, AMBIGUOUS, 0, ACS_DEFAULT)) { + write_access_file(); + notify_format(player, T("Site %s banned"), site); + do_log(LT_WIZ, player, NOTHING, "*** SITELOCK *** %s", site); + } break; case SITELOCK_CHECK:{ struct access *ap; Index: src/services.c =================================================================== --- src/services.c (.../p4) (revision 1119) +++ src/services.c (.../p5) (revision 1119) @@ -30,7 +30,7 @@ #define THIS_SERVICE_DISPLAY "PennMUSH for Win32" int WIN32_CDECL main(int argc, char **argv); -void mainthread(int argc, char **argv); +int mainthread(int argc, char **argv); SERVICE_STATUS ssStatus; /* current status of the service */ @@ -431,7 +431,7 @@ /* start up the main MUSH code */ - mainthread(argc, argv); + exit(mainthread(argc, argv)); } /* end of worker_thread */ Index: src/conf.c =================================================================== --- src/conf.c (.../p4) (revision 1119) +++ src/conf.c (.../p5) (revision 1119) @@ -679,7 +679,7 @@ maxval); } } - if (from_cmd && ((!GoodObject(n) && n != NOTHING) || IsGarbage(n))) { + if (from_cmd && ((!GoodObject(n) && n != NOTHING) || (n > 0 && IsGarbage(n)))) { do_rawlog(LT_ERR, T("CONFIG: attempt to set option %s to a bad dbref (#%d)"), opt, n); Index: src/db.c =================================================================== --- src/db.c (.../p4) (revision 1119) +++ src/db.c (.../p5) (revision 1119) @@ -1219,7 +1219,7 @@ */ tb2 = getstring_noalloc(f); if (strchr(tb2, TAG_START) || strchr(tb2, ESC_CHAR)) { - as = parse_ansi_string_real(tb2, 1); + as = parse_ansi_string(tb2); tb2 = tbuf2; safe_ansi_string(as, 0, as->len, tbuf2, &tb2); *(tb2) = '\0'; @@ -1306,7 +1306,7 @@ if (!(globals.indb_flags & DBF_SPIFFY_AF_ANSI)) { if (strchr(value, ESC_CHAR) || strchr(value, TAG_START)) { char *vp = value; - as = parse_ansi_string_real(value, 1); + as = parse_ansi_string(value); safe_ansi_string(as, 0, as->len, value, &vp); *vp = '\0'; free_ansi_string(as); @@ -1801,10 +1801,10 @@ static void init_objdata_htab(int size, void (*free_data) (void *)) { - if (size < 128) - size = 128; - hash_init(&htab_objdata, size, 4, free_data); - hashinit(&htab_objdata_keys, 8, 32); + if (size < 10) + size = 10; + hash_init(&htab_objdata, size, free_data); + hashinit(&htab_objdata_keys, 8); } /** Add data to the object data hashtable. @@ -1828,7 +1828,7 @@ mush_strncpy(keyname, tprintf("%s_#%d", keybase, thing), BUFFER_LEN); hashdelete(keyname, &htab_objdata); if (data) { - if (hashadd(keyname, data, &htab_objdata) < 0) + if (!hashadd(keyname, data, &htab_objdata)) return NULL; if (hash_find(&htab_objdata_keys, keybase) == NULL) { char *newkey = mush_strdup(keyname, "objdata.key"); Index: src/function.c =================================================================== --- src/function.c (.../p4) (revision 1119) +++ src/function.c (.../p5) (revision 1119) @@ -45,6 +45,25 @@ * Utilities. */ +/* Save and restore regexp data */ +void +save_regexp_context(struct re_save *save) +{ + save->re_code = global_eval_context.re_code; + save->re_from = global_eval_context.re_from; + save->re_subpatterns = global_eval_context.re_subpatterns; + save->re_offsets = global_eval_context.re_offsets; +} + +void +restore_regexp_context(struct re_save *save) +{ + global_eval_context.re_code = save->re_code; + global_eval_context.re_from = save->re_from; + global_eval_context.re_subpatterns = save->re_subpatterns; + global_eval_context.re_offsets = save->re_offsets; +} + /** Save a single q-register */ void @@ -513,6 +532,7 @@ {"MEDIAN", fun_median, 1, INT_MAX, FN_REG}, {"MEMBER", fun_member, 2, 3, FN_REG}, {"MERGE", fun_merge, 3, 3, FN_REG}, + {"MESSAGE", fun_message, 3, 13, FN_REG}, {"MID", fun_mid, 3, 3, FN_REG}, {"MIN", fun_min, 1, INT_MAX, FN_REG}, {"MIX", fun_mix, 3, 12, FN_REG}, @@ -563,7 +583,7 @@ {"NVEXITS", fun_dbwalker, 1, 1, FN_REG}, {"NVPLAYERS", fun_dbwalker, 1, 1, FN_REG}, {"NVTHINGS", fun_dbwalker, 1, 1, FN_REG}, - {"NWHO", fun_nwho, 0, 0, FN_REG}, + {"NWHO", fun_nwho, 0, 1, FN_REG}, {"OBJ", fun_obj, 1, 1, FN_REG}, {"OBJEVAL", fun_objeval, 2, -2, FN_NOPARSE}, {"OBJID", fun_objid, 1, 1, FN_REG}, @@ -889,8 +909,8 @@ { FUNTAB *ftp; - hashinit(&htab_function, 512, sizeof(FUN)); - hash_init(&htab_user_function, 32, sizeof(FUN), delete_function); + hashinit(&htab_function, 512); + hash_init(&htab_user_function, 32, delete_function); function_slab = slab_create("functions", sizeof(FUN)); for (ftp = flist; ftp->name; ftp++) { function_add(ftp->name, ftp->fun, ftp->minargs, ftp->maxargs, ftp->flags); Index: src/cmds.c =================================================================== --- src/cmds.c (.../p4) (revision 1119) +++ src/cmds.c (.../p5) (revision 1119) @@ -665,6 +665,44 @@ do_dolist(player, arg_left, arg_right, cause, flags); } +COMMAND(cmd_message) +{ + char *message; + char *attrib; + unsigned int flags = 0; + int numargs, i; + char *args[10]; + + if (!(SW_ISSET(sw, SWITCH_SPOOF) && (controls(player, cause) + || Can_Nspemit(player)))) { + cause = player; + } + + for (numargs = 1; args_right[numargs] && numargs < 13; numargs++) ; + + switch (numargs) { + case 1: + notify(player, T("@message them with what?")); + return; + case 2: + notify(player, T("Use what attribute for the @message?")); + return; + } + if (!*arg_left) { + notify(player, T("@message who?")); + return; + } + + message = args_right[1]; + attrib = args_right[2]; + + for (i = 0; (i + 3) < numargs; i++) { + args[i] = args_right[i + 3]; + } + + do_message_list(player, cause, arg_left, attrib, message, flags, i, args); +} + COMMAND(cmd_motd) { if (SW_ISSET(sw, SWITCH_CONNECT)) Index: src/markup.c =================================================================== --- src/markup.c (.../p4) (revision 1119) +++ src/markup.c (.../p5) (revision 1119) @@ -1,7 +1,7 @@ /** - * \file strutil.c + * \file markup.c * - * \brief String utilities for PennMUSH. + * \brief Markup handling in PennMUSH strings. * * */ @@ -26,27 +26,17 @@ #include "game.h" #include "confmagic.h" -#define ANSI_BLACK_V (30) -#define ANSI_RED_V (31) -#define ANSI_GREEN_V (32) -#define ANSI_YELLOW_V (33) -#define ANSI_BLUE_V (34) -#define ANSI_MAGENTA_V (35) -#define ANSI_CYAN_V (36) -#define ANSI_WHITE_V (37) - #define ANSI_BEGIN "\x1B[" #define ANSI_FINISH "m" -#define COL_NORMAL "\x1B[0m" +/* COL_* defines */ -/* COL_* and VAL_* defines */ - -#define CBIT_FLASH (1) /**< ANSI flash attribute bit */ -#define CBIT_HILITE (2) /**< ANSI hilite attribute bit */ -#define CBIT_INVERT (4) /**< ANSI inverse attribute bit */ +#define CBIT_HILITE (1) /**< ANSI hilite attribute bit */ +#define CBIT_INVERT (2) /**< ANSI inverse attribute bit */ +#define CBIT_FLASH (4) /**< ANSI flash attribute bit */ #define CBIT_UNDERSCORE (8) /**< ANSI underscore attribute bit */ +#define COL_NORMAL (0) /**< ANSI normal */ #define COL_HILITE (1) /**< ANSI hilite attribute value */ #define COL_UNDERSCORE (4) /**< ANSI underscore attribute value */ #define COL_FLASH (5) /**< ANSI flag attribute value */ @@ -64,25 +54,21 @@ /* Now the code */ static int write_ansi_close(char *buff, char **bp); -static int is_ansi_oldstyle(const char *str); +static int write_ansi_letters(ansi_data cur, char *buff, char **bp); static int safe_markup(char const *a_tag, char *buf, char **bp, char type); static int safe_markup_cancel(char const *a_tag, char *buf, char **bp, char type); -static int - compare_starts(const void *a, const void *b); +static int compare_starts(const void *a, const void *b); +static int escape_marked_str(char **str, char *buff, char **bp); const char *is_allowed_tag(const char *s, unsigned int len); +static const ansi_data ansi_null = { 0, 0, 0, 0 }; /* ARGSUSED */ FUNCTION(fun_stripansi) { - /* Strips ANSI codes away from a given string of text. Starts by - * finding the '\x' character and stripping until it hits an 'm'. - */ - char *cp; - cp = remove_markup(args[0], NULL); safe_str(cp, buff, bp); } @@ -95,27 +81,31 @@ char *ptr; ansi_string *as; char choice; - if (nargs < 2 || !args[1] || !*args[1]) + int strnum; + if (nargs < 2 || !args[0] || !*args[0]) { choice = 'r'; - else - choice = *args[1]; + strnum = 0; + } else { + choice = *args[0]; + strnum = 1; + } switch (choice) { case 'i': - as = parse_ansi_string(args[0]); + as = parse_ansi_string(args[strnum]); inspect_ansi_string(as, executor); free_ansi_string(as); break; case 'r': - for (ptr = args[0]; *ptr; ptr++) { + for (ptr = args[strnum]; *ptr; ptr++) { if (*ptr == TAG_START) *ptr = '<'; if (*ptr == TAG_END) *ptr = '>'; } - safe_str(args[0], buff, bp); + safe_str(args[strnum], buff, bp); break; case 'l': - safe_integer(arglens[0], buff, bp); + safe_integer(arglens[strnum], buff, bp); break; default: safe_str("i: inspect r: raw l: length", buff, bp); @@ -127,7 +117,7 @@ /* ARGSUSED */ FUNCTION(fun_ansi) { - struct ansi_data colors; + ansi_data colors; char *save = *bp; char *p; int i; @@ -136,8 +126,8 @@ define_ansi_data(&colors, args[0]); if (!(colors.bits || colors.offbits || colors.fore || colors.back)) { if (!safe_strl(args[1], arglens[1], buff, bp)) - write_ansi_close(buff, bp); - return; + /* write_ansi_close(buff, bp); */ + return; } /* Write the colors to buff */ @@ -230,12 +220,12 @@ return 0; while (*p) { - if (*p == ESC_CHAR) { + if (*p == TAG_START) { + while ((*p) && (*p != TAG_END)) + p++; + } else if (*p == ESC_CHAR) { while ((*p) && (*p != 'm')) p++; - } else if (*p == TAG_START) { - while ((*p) && (*p != TAG_END)) - p++; } else { i++; } @@ -244,33 +234,7 @@ return i; } -/** Compare two strings, ignoring all ansi and html markup from a string. - * Is *NOT* locale safe (a la strcoll) - * \param a string to compare to - * \param b Other string - * \return int - 0 is identical, -1 or 1 for difference. - */ -int -ansi_strcmp(const char *astr, const char *bstr) -{ - const char *a, *b; - - for (a = astr, b = bstr; *a && *b;) { - a = skip_leading_ansi(a); - b = skip_leading_ansi(b); - if (*a != *b) - return (*a - *b); - b++; - a++; - } - if (*a) - a = skip_leading_ansi(a); - if (*b) - b = skip_leading_ansi(b); - return (*a - *b); -} - -/** Returns the apparent length of a string, up to numchars visible +/** Returns the apparent length of a string, up to numchars visible * characters. The apparent length skips over nonprinting ansi and * tags. * \param p string. @@ -303,7 +267,55 @@ return i; } -/** Strip all ansi and html markup from a string. As a side effect, +/** Compare two strings, ignoring all ansi and html markup from a string. + * Is *NOT* locale safe (a la strcoll) + * \param a string to compare to + * \param b Other string + * \return int - 0 is identical, -1 or 1 for difference. + */ +int +ansi_strcmp(const char *astr, const char *bstr) +{ + const char *a, *b; + + for (a = astr, b = bstr; *a && *b;) { + a = skip_leading_ansi(a); + b = skip_leading_ansi(b); + if (*a != *b) + return (*a - *b); + b++; + a++; + } + if (*a) + a = skip_leading_ansi(a); + if (*b) + b = skip_leading_ansi(b); + return (*a - *b); +} + +/** Compare ansi_data for exact equality. + * \param a ansi_data to compare + * \param b other ansi_data + * \return int - 1 is identical, 0 is different + */ +int +ansi_equal(const ansi_data a, const ansi_data b) +{ + return ((a.bits == b.bits) && (a.offbits == b.offbits) && + (a.fore == b.fore) && (a.back == b.back)); +} + +/** Return true if ansi_data contains no ansi values. + * \param a ansi_data to check + * \return int 1 on ansi_null, 0 otherwise + */ +int +ansi_isnull(const ansi_data a) +{ + return ((a.bits == 0) && (a.offbits == 0) && (a.fore == 0) && (a.back == 0)); +} + +/** Strip all ANSI and HTML markup from a string. As a side effect, * stores the length of the stripped string in a provided address. * NOTE! Length returned is length *including* the terminating NULL, * because we usually memcpy the result. @@ -347,108 +359,55 @@ } static char ansi_chars[50]; - static int ansi_codes[255]; -struct { - char flag; - int num; -} build_ansi_codes[] = { - { - 'n', 0}, { - 'f', COL_FLASH}, { - 'h', COL_HILITE}, { - 'i', COL_INVERT}, { - 'u', COL_UNDERSCORE}, { - 'x', COL_BLACK}, { - 'X', COL_BLACK + 10}, { - 'r', COL_RED}, { - 'R', COL_RED + 10}, { - 'g', COL_GREEN}, { - 'G', COL_GREEN + 10}, { - 'y', COL_YELLOW}, { - 'Y', COL_YELLOW + 10}, { - 'b', COL_BLUE}, { - 'B', COL_BLUE + 10}, { - 'm', COL_MAGENTA}, { - 'M', COL_MAGENTA + 10}, { - 'c', COL_CYAN}, { - 'C', COL_CYAN + 10}, { - 'w', COL_WHITE}, { - 'W', COL_WHITE + 10}, { - '\0', 0} -}; +#define BUILD_ANSI(letter,ESCcode) \ +do { \ + ansi_chars[ESCcode] = letter; \ + ansi_codes[(unsigned char) letter] = ESCcode; \ +} while (0) void init_ansi_codes(void) { - int i; - memset(ansi_chars, 0, sizeof(ansi_chars)); memset(ansi_codes, 0, sizeof(ansi_codes)); - - for (i = 0; build_ansi_codes[i].flag; i++) { - ansi_chars[build_ansi_codes[i].num] = build_ansi_codes[i].flag; - ansi_codes[(unsigned char) build_ansi_codes[i].flag] = - build_ansi_codes[i].num; - } +/* + BUILD_ANSI('n', COL_NORMAL); + BUILD_ANSI('f', COL_FLASH); + BUILD_ANSI('h', COL_HILITE); + BUILD_ANSI('i', COL_INVERT); + BUILD_ANSI('u', COL_UNDERSCORE); +*/ + BUILD_ANSI('x', COL_BLACK); + BUILD_ANSI('X', COL_BLACK + 10); + BUILD_ANSI('r', COL_RED); + BUILD_ANSI('R', COL_RED + 10); + BUILD_ANSI('g', COL_GREEN); + BUILD_ANSI('G', COL_GREEN + 10); + BUILD_ANSI('y', COL_YELLOW); + BUILD_ANSI('Y', COL_YELLOW + 10); + BUILD_ANSI('b', COL_BLUE); + BUILD_ANSI('B', COL_BLUE + 10); + BUILD_ANSI('m', COL_MAGENTA); + BUILD_ANSI('M', COL_MAGENTA + 10); + BUILD_ANSI('c', COL_CYAN); + BUILD_ANSI('C', COL_CYAN + 10); + BUILD_ANSI('w', COL_WHITE); + BUILD_ANSI('W', COL_WHITE + 10); } +#undef BUILD_ANSI + int -read_raw_ansi_data(struct ansi_data *store, const char *codes) +write_ansi_data(ansi_data * cur, char *buff, char **bp) { - int curnum; - if (codes == NULL || store == NULL) - return 0; - store->bits = 0; - store->offbits = 0; - store->fore = 0; - store->back = 0; - - /* codes can point at either the ESC_CHAR or one - * following after. */ - - /* Skip to the first ansi number */ - while (*codes && !isdigit((unsigned char) *codes) && *codes != 'm') - codes++; - - memset(store, 0, sizeof(struct ansi_data)); - - while (*codes && (*codes != 'm')) { - curnum = atoi(codes); - if (curnum < 10) { - switch (curnum) { - case COL_HILITE: - store->bits ^= CBIT_HILITE; - break; - case COL_UNDERSCORE: - store->bits ^= CBIT_UNDERSCORE; - break; - case COL_FLASH: - store->bits ^= CBIT_FLASH; - break; - case COL_INVERT: - store->bits ^= CBIT_INVERT; - break; - case 0: - store->bits = 0; - store->offbits = 0; - store->fore = 'n'; - store->back = 'n'; - break; - } - } else if (curnum < 40) { - store->fore = ansi_chars[curnum]; - } else if (curnum < 50) { - store->back = ansi_chars[curnum]; - } - /* Skip current and find the nxt ansi number */ - while (*codes && isdigit((unsigned char) *codes)) - codes++; - while (*codes && !isdigit((unsigned char) *codes) && (*codes != 'm')) - codes++; - } - return 1; + int retval = 0; + retval += safe_chr(TAG_START, buff, bp); + retval += safe_chr(MARKUP_COLOR, buff, bp); + retval += write_ansi_letters(*cur, buff, bp); + retval += safe_chr(TAG_END, buff, bp); + return retval; } static int @@ -462,17 +421,16 @@ return retval; } - -int -write_ansi_data(struct ansi_data *cur, char *buff, char **bp) +static int +write_ansi_letters(const ansi_data cur, char *buff, char **bp) { int retval = 0; - retval += safe_chr(TAG_START, buff, bp); - retval += safe_chr(MARKUP_COLOR, buff, bp); - if (cur->fore == 'n') { - retval += safe_chr(cur->fore, buff, bp); + char *save; + save = *bp; + if (cur.fore == 'n') { + retval += safe_chr(cur.fore, buff, bp); } else { -#define CBIT_SET(x,y) (x->bits & y) +#define CBIT_SET(x,y) (x.bits & y) if (CBIT_SET(cur, CBIT_FLASH)) retval += safe_chr('f', buff, bp); if (CBIT_SET(cur, CBIT_HILITE)) @@ -482,7 +440,7 @@ if (CBIT_SET(cur, CBIT_UNDERSCORE)) retval += safe_chr('u', buff, bp); #undef CBIT_SET -#define CBIT_SET(x,y) (x->offbits & y) +#define CBIT_SET(x,y) (x.offbits & y) if (CBIT_SET(cur, CBIT_FLASH)) retval += safe_chr('F', buff, bp); if (CBIT_SET(cur, CBIT_HILITE)) @@ -493,25 +451,20 @@ retval += safe_chr('U', buff, bp); #undef CBIT_SET - if (cur->fore) - retval += safe_chr(cur->fore, buff, bp); - if (cur->back) - retval += safe_chr(cur->back, buff, bp); + if (cur.fore) + retval += safe_chr(cur.fore, buff, bp); + if (cur.back) + retval += safe_chr(cur.back, buff, bp); } - retval += safe_chr(TAG_END, buff, bp); + if (retval) + *bp = save; return retval; } -/* We need EDGE_UP to return 1 if: - * x has bit set and y's offbit does. - */ -#define EDGE_UP(x,y,z) ((x->bits & z) != (y->bits & z)) -static struct ansi_data ansi_normal = { 0, 0xFF, 'n', 0 }; - void -nest_ansi_data(struct ansi_data *old, struct ansi_data *cur) +nest_ansi_data(ansi_data * old, ansi_data * cur) { if (cur->fore != 'n') { cur->bits |= old->bits; @@ -521,127 +474,127 @@ if (!cur->back) cur->back = old->back; } else { - cur->fore = 0; + cur->bits = 0; + cur->offbits = 0; cur->back = 0; - cur->bits = 0; - cur->offbits = ~0; } } +/* We need EDGE_UP to return 1 if x has bit set and y doesn't. */ +#define EDGE_UP(x,y,z) ((x.bits & z) != (y->bits & z)) int -write_raw_ansi_data(struct ansi_data *old, struct ansi_data *cur, - char *buff, char **bp) +write_raw_ansi_data(ansi_data * old, ansi_data * cur, char *buff, char **bp) { int f = 0; - + ansi_data past = *old; + ansi_data pres = *cur; /* This shouldn't happen */ - if (cur->fore == 'n') { - if (old->bits || (old->fore != 'n') || old->back) { - return safe_str(COL_NORMAL, buff, bp); + if (pres.fore == 'n') { + if (past.bits || (past.fore != 'n') || past.back) { + return safe_str(ANSI_RAW_NORMAL, buff, bp); } } - if (cur->fore == 'd') - cur->fore = 0; - if (cur->back == 'D') - cur->back = 0; + if (pres.fore == 'd') + pres.fore = 0; + if (pres.back == 'D') + pres.back = 0; /* Do we *unset* anything in cur? */ - if ((old->bits & ~(cur->bits)) || - (old->fore && !cur->fore) || (old->back && !cur->back)) { - safe_str(COL_NORMAL, buff, bp); - old = &ansi_normal; + if ((past.bits & ~(pres.bits)) || + (past.fore && !pres.fore) || (past.back && !pres.back)) { + safe_str(ANSI_RAW_NORMAL, buff, bp); + past = ansi_null; } - cur->bits |= old->bits; - cur->bits &= ~cur->offbits; - - if (old->fore == cur->fore && - old->back == cur->back && old->bits == cur->bits) + if (past.fore == pres.fore && past.back == pres.back && + past.bits == pres.bits) return 0; - if (!(cur->fore || cur->back || cur->bits)) { - if (old != &ansi_normal) - return safe_str(COL_NORMAL, buff, bp); + if (!(pres.fore || pres.back || pres.bits)) { + if (past.fore != 'n') + return safe_str(ANSI_RAW_NORMAL, buff, bp); return 0; } safe_str(ANSI_BEGIN, buff, bp); - if (EDGE_UP(old, cur, CBIT_FLASH)) { + if (EDGE_UP(past, cur, CBIT_HILITE)) { if (f++) safe_chr(';', buff, bp); - safe_integer(ansi_codes['f'], buff, bp); + safe_integer(COL_HILITE, buff, bp); } - if (EDGE_UP(old, cur, CBIT_HILITE)) { + if (EDGE_UP(past, cur, CBIT_INVERT)) { if (f++) safe_chr(';', buff, bp); - safe_integer(ansi_codes['h'], buff, bp); + safe_integer(COL_INVERT, buff, bp); } - if (EDGE_UP(old, cur, CBIT_INVERT)) { + if (EDGE_UP(past, cur, CBIT_FLASH)) { if (f++) safe_chr(';', buff, bp); - safe_integer(ansi_codes['i'], buff, bp); + safe_integer(COL_FLASH, buff, bp); } - if (EDGE_UP(old, cur, CBIT_UNDERSCORE)) { + if (EDGE_UP(past, cur, CBIT_UNDERSCORE)) { if (f++) safe_chr(';', buff, bp); - safe_integer(ansi_codes['u'], buff, bp); + safe_integer(COL_UNDERSCORE, buff, bp); } - if (cur->fore && cur->fore != old->fore) { + if (pres.fore && pres.fore != past.fore) { if (f++) safe_chr(';', buff, bp); - safe_integer(ansi_codes[(unsigned char) cur->fore], buff, bp); + safe_integer(ansi_codes[(unsigned char) pres.fore], buff, bp); } - if (cur->back && cur->back != old->back) { + if (pres.back && pres.back != past.back) { if (f++) safe_chr(';', buff, bp); - safe_integer(ansi_codes[(unsigned char) cur->back], buff, bp); + safe_integer(ansi_codes[(unsigned char) pres.back], buff, bp); } return safe_str(ANSI_FINISH, buff, bp); } +#undef EDGE_UP + void -define_ansi_data(struct ansi_data *cur, const char *str) +define_ansi_data(ansi_data * store, const char *str) { - cur->bits = 0; - cur->offbits = 0; - cur->fore = 0; - cur->back = 0; + *store = ansi_null; for (; str && *str && (*str != TAG_END); str++) { switch (*str) { case 'n': /* normal */ - /* This is explicitly normal, it'll never be - * clored */ - cur->bits = 0; - cur->fore = 0; - cur->back = 0; - cur->offbits = ~0; + /* This is explicitly normal, it'll never be colored */ + store->bits = 0; + store->offbits = ~0; + store->fore = 'n'; + store->back = 0; break; case 'f': /* flash */ - cur->bits |= CBIT_FLASH; + store->bits |= CBIT_FLASH; + store->offbits &= ~CBIT_FLASH; break; case 'h': /* hilite */ - cur->bits |= CBIT_HILITE; + store->bits |= CBIT_HILITE; + store->offbits &= ~CBIT_HILITE; break; case 'i': /* inverse */ - cur->bits |= CBIT_INVERT; + store->bits |= CBIT_INVERT; + store->offbits &= ~CBIT_INVERT; break; case 'u': /* underscore */ - cur->bits |= CBIT_UNDERSCORE; + store->bits |= CBIT_UNDERSCORE; + store->offbits &= ~CBIT_UNDERSCORE; break; case 'F': /* flash */ - cur->offbits |= CBIT_FLASH; + store->offbits |= CBIT_FLASH; break; case 'H': /* hilite */ - cur->offbits |= CBIT_HILITE; + store->offbits |= CBIT_HILITE; break; case 'I': /* inverse */ - cur->offbits |= CBIT_INVERT; + store->offbits |= CBIT_INVERT; break; case 'U': /* underscore */ - cur->offbits |= CBIT_UNDERSCORE; + store->offbits |= CBIT_UNDERSCORE; break; case 'b': /* blue fg */ case 'c': /* cyan fg */ @@ -652,7 +605,7 @@ case 'x': /* black fg */ case 'y': /* yellow fg */ case 'd': /* default fg */ - cur->fore = *str; + store->fore = *str; break; case 'B': /* blue bg */ case 'C': /* cyan bg */ @@ -663,12 +616,72 @@ case 'X': /* black bg */ case 'Y': /* yellow bg */ case 'D': /* default fg */ - cur->back = *str; + store->back = *str; break; } } + store->bits &= ~(store->offbits); } +int +read_raw_ansi_data(ansi_data * store, const char *codes) +{ + int curnum; + if (!codes || !store) + return 0; + store->bits = 0; + store->offbits = 0; + store->fore = 0; + store->back = 0; + + /* 'codes' can point at either the ESC_CHAR, + * the '[', or the following byte. */ + + /* Skip to the first ansi number */ + while (*codes && !isdigit((unsigned char) *codes) && *codes != 'm') + codes++; + + while (*codes && (*codes != 'm')) { + curnum = atoi(codes); + if (curnum < 10) { + switch (curnum) { + case COL_HILITE: + store->bits |= CBIT_HILITE; + store->offbits &= ~CBIT_HILITE; + break; + case COL_UNDERSCORE: + store->bits |= CBIT_UNDERSCORE; + store->offbits &= ~CBIT_UNDERSCORE; + break; + case COL_FLASH: + store->bits |= CBIT_FLASH; + store->offbits &= ~CBIT_FLASH; + break; + case COL_INVERT: + store->bits |= CBIT_INVERT; + store->offbits &= ~CBIT_INVERT; + break; + case COL_NORMAL: + store->bits = 0; + store->offbits = ~0; + store->fore = 'n'; + store->back = 0; + break; + } + } else if (curnum < 40) { + store->fore = ansi_chars[curnum]; + } else if (curnum < 50) { + store->back = ansi_chars[curnum]; + } + /* Skip current and find the next ansi number */ + while (*codes && isdigit((unsigned char) *codes)) + codes++; + while (*codes && !isdigit((unsigned char) *codes) && (*codes != 'm')) + codes++; + } + return 1; +} + /** Return a string pointer past any ansi/html markup at the start. * \param p a string. * \return pointer to string after any initial ansi/html markup. @@ -723,37 +736,6 @@ } } -/* Is this string an old style format? */ -static int -is_ansi_oldstyle(const char *source) -{ - const char *ptr; - /* First test: ESC_CHAR. If there's one this is old style. */ - if (strchr(source, ESC_CHAR) != NULL) - return 1; - /* This usually means a TAG_START appears, but no ESC_CHAR. */ - if (strstr(source, MARKUP_START MARKUP_HTML_STR "/") || - strstr(source, MARKUP_START MARKUP_COLOR_STR "/")) { - /* There's a

*/ - if (strncasecmp(ptr + 1, "PRE", 3) == 0) - return 1; - /* And

or

*/ - if (!isalnum((unsigned char) *(ptr + 2))) - return 1; - } - return 0; -} - /** Convert a string into an ansi_string. * This takes a string that may contain ansi/html markup codes and * converts it to an ansi_string structure that separately stores @@ -764,42 +746,44 @@ ansi_string * parse_ansi_string(const char *source) { - return parse_ansi_string_real(source, 0); + return real_parse_ansi_string(source); } -/** Convert a string into an ansi_string. - * This takes a string that may contain ansi/html markup codes and - * converts it to an ansi_string structure that separately stores - * the plain string and the markup codes for each character. - * \param source string to parse. - * \param oldstyle If true, treats it as an old style ansi string. - * \return pointer to an ansi_string structure representing the src string. - */ +/* This does the actual work of parse_ansi_string. */ ansi_string * -parse_ansi_string_real(const char *source, int oldstyle) +real_parse_ansi_string(const char *source) { ansi_string *data = NULL; char src[BUFFER_LEN], *sptr; char tagbuff[BUFFER_LEN]; char *ptr, *txt; + ansi_data *col; char *tmp; char type; - int i, j; + + int pos = 0; + int num = 0; + int priority = 0; markup_information *info; + ansi_data ansistack[BUFFER_LEN]; + ansistack[0] = ansi_null; + int stacktop = 0; + + ansi_data tmpansi; + int oldcodes = 0; + + if (!source) return NULL; - if (oldstyle == 2) - oldstyle = is_ansi_oldstyle(source); info = NULL; sptr = src; safe_str(source, src, &sptr); *sptr = '\0'; - data = mush_malloc(sizeof(ansi_string), "ansi_string"); if (!data) return NULL; @@ -808,23 +792,45 @@ memset(data, 0, sizeof(ansi_string)); txt = data->text; - i = 0; + col = data->ansi; + for (ptr = src; *ptr;) { - /* Is this an ansi sequence? */ switch (*ptr) { case TAG_START: /* In modern Penn, this is both Pueblo/HTML and color defining code */ - /* Find the end. */ + for (tmp = ptr; *tmp && *tmp != TAG_END; tmp++) ; - if (*tmp) { - *(tmp) = '\0'; - } else { - /* Point tmp at the end */ + if (*tmp) + *tmp = '\0'; + else tmp--; - } ptr++; - type = oldstyle ? MARKUP_HTML : *(ptr++); + /* Now ptr is at TAG_START and tmp is at TAG_END (nulled) */ + + type = *(ptr++); switch (type) { + case MARKUP_COLOR: + if (!*ptr) + break; + if (oldcodes == 1) { + oldcodes = 0; + stacktop--; + } + /* Start or end tag? */ + if (*ptr != '/') { + define_ansi_data(&tmpansi, ptr); + nest_ansi_data(&(ansistack[stacktop]), &tmpansi); + stacktop++; + ansistack[stacktop] = tmpansi; + } else { + if (*(ptr + 1) == 'a') { + stacktop = 0; /* Endall tag */ + } else { + if (stacktop > 0) + stacktop--; + } + } + break; case MARKUP_HTML: if (*ptr && *ptr != '/') { /* We're at the start tag. */ @@ -833,19 +839,19 @@ snprintf(tagbuff, BUFFER_LEN, "/%s", parse_tagname(ptr)); info->stop_code = mush_strdup(tagbuff, "markup_code"); info->type = MARKUP_HTML; - info->start = i; + info->start = pos; info->end = -1; info->priority = priority++; } else if (*ptr) { /* Closing tag */ - for (j = data->nmarkups - 1; j >= 0; j--) { - if (data->markup[j].end < 0 && data->markup[j].stop_code && - strcasecmp(data->markup[j].stop_code, ptr) == 0) { + for (num = data->nmarkups - 1; num >= 0; num--) { + if (data->markup[num].end < 0 && data->markup[num].stop_code && + strcasecmp(data->markup[num].stop_code, ptr) == 0) { break; } } - if (j >= 0) { - data->markup[j].end = i; + if (num >= 0) { + data->markup[num].end = pos; } else { /* This is greviously wrong, we can't find the begin tag? * Consider this a standalone tag with no close tag. */ @@ -855,35 +861,10 @@ info->type = MARKUP_HTML; info->priority = priority++; info->start = -1; - info->end = i; + info->end = pos; } } break; - case MARKUP_COLOR: - if (*ptr && *ptr != '/') { - /* We're at the start tag. */ - j = data->nmarkups++; - data->markup[j].start_code = NULL; - data->markup[j].stop_code = NULL; - data->markup[j].type = MARKUP_COLOR; - data->markup[j].priority = priority++; - define_ansi_data(&(data->markup[j].ansi), ptr); - data->markup[j].start = i; - data->markup[j].end = -1; - } else if (*ptr) { - int endall = (*(ptr + 1) == 'a'); - /* Closing tag. For markup color, this means we - * close the last opened tag. */ - for (j = (data->nmarkups) - 1; j >= 0; j--) { - if (data->markup[j].end < 0 && data->markup[j].type == MARKUP_COLOR) { - data->markup[j].end = i; - /* If it's not ENDALL, break */ - if (!endall) - break; - } - } - } - break; default: /* This is a broken string. Are we near or at buffer_len? */ if (ptr - source < BUFFER_LEN - 4) { @@ -896,90 +877,52 @@ ptr++; break; case ESC_CHAR: - /* I'm getting rid of ESC_CHAR. Still, pretend it's - * a proper "opening" one, unless it's normal, - * in which case we ether close all extant, or - * open a 'normal'. - * - * If we open a new one, find all old open ones that - * it overwrites (rather than modifies) */ + /* ESC_CHAR tags shouldn't be used anymore, so hopefully + * we won't get here. + * To parse these, we assume they can't have the new tag-style + * ANSI codes in them, as this should always be true when loading + * from attributes. Assuming that, this code is separate from the + * "actual" tags, creating a single temporary holding space on the + * top of the ansi-stack and playing with the colors there. + */ for (tmp = ptr; *tmp && *tmp != 'm'; tmp++) ; - if (strcmp(ptr, COL_NORMAL) != 0) { - struct ansi_data cur; - read_raw_ansi_data(&cur, ptr); - /* Close any we can */ - for (j = (data->nmarkups) - 1; j >= 0; j--) { - if (data->markup[j].type == MARKUP_COLOR_OLD) { - cur.bits |= data->markup[j].ansi.bits; - data->markup[j].type = MARKUP_COLOR; - data->markup[j].end = i; - } - } - /* We're at a start tag. */ - j = data->nmarkups++; - data->markup[j].start_code = NULL; - data->markup[j].stop_code = NULL; - data->markup[j].type = MARKUP_COLOR_OLD; - data->markup[j].priority = priority++; - data->markup[j].ansi = cur; - data->markup[j].start = i; - data->markup[j].end = -1; - } else { - int found = 0; - /* Closing tag. For markup color, this means we - * close the last opened tag. */ - for (j = (data->nmarkups) - 1; j >= 0; j--) { - if (data->markup[j].type == MARKUP_COLOR_OLD) { - data->markup[j].type = MARKUP_COLOR; - data->markup[j].end = i; - found = 1; - } - } - /* Is it an "opening" ansi_normal tag? */ - if (!found) { - j = data->nmarkups++; - data->markup[j].start_code = NULL; - data->markup[j].stop_code = NULL; - data->markup[j].type = MARKUP_COLOR_OLD; - data->markup[j].priority = priority++; - data->markup[j].ansi.bits = 0; - data->markup[j].ansi.offbits = 0; - data->markup[j].ansi.fore = 'n'; - data->markup[j].ansi.back = 0; - data->markup[j].start = i; - data->markup[j].end = -1; - } + + /* Store the "background" colors */ + tmpansi = ansistack[stacktop]; + if (oldcodes == 0) { + oldcodes = 1; + stacktop++; + ansistack[stacktop] = tmpansi; + ansistack[stacktop].offbits = 0; } + + read_raw_ansi_data(&tmpansi, ptr); + ansistack[stacktop].bits |= tmpansi.bits; + ansistack[stacktop].bits &= ~(tmpansi.offbits); /* ANSI_RAW_NORMAL */ + if (tmpansi.fore) + ansistack[stacktop].fore = tmpansi.fore; + if (tmpansi.back) + ansistack[stacktop].back = tmpansi.back; + ptr = tmp; if (*tmp) ptr++; break; default: - txt[i++] = *(ptr++); + col[pos] = ansistack[stacktop]; + txt[pos++] = *(ptr++); } } - txt[i] = '\0'; - data->len = i; + txt[pos] = '\0'; + data->len = pos; /* For everything left on the stack: - * If it's an ANSI code, close it with COL_NORMAL and i. * If it's an HTML code, assume it's a standalone, and leave * its stop point where it is. */ - for (j = 0; j < data->nmarkups; j++) { - info = &(data->markup[j]); + for (num = 0; num < data->nmarkups; num++) { + info = &(data->markup[num]); switch (info->type) { - case MARKUP_COLOR_OLD: - info->type = MARKUP_COLOR; - case MARKUP_COLOR: - /* If it's ANSI, we assume it affects the whole string */ - /* Sucks, but ... */ - if (info->end < 0) - info->end = i; - if (info->end == info->start) { - info->end = info->start = -1; - } - break; case MARKUP_HTML: /* If it's HTML, we treat it as standalone (,
, etc) * This is ugly - it's not a "start" but a "stop" */ @@ -995,13 +938,13 @@ } return data; broken_string: - /* This stinks. We treat this as if it's not ansi safe */ + /* This stinks. We treat this as if it's not pueblo-safe */ if (data == NULL) return NULL; strncpy(data->text, source, BUFFER_LEN); data->len = strlen(data->text); - for (i = data->nmarkups - 1; i >= 0; i--) { - free_markup_info(&(data->markup[i])); + for (num = data->nmarkups - 1; num >= 0; num--) { + free_markup_info(&(data->markup[num])); } data->nmarkups = 0; return data; @@ -1016,19 +959,24 @@ { int i, j; markup_information *info; - char tmp; + char tmptext; + ansi_data tmpansi; int mid; int len = as->len; /* Reverse the text */ mid = len / 2; /* Midpoint */ for (i = len - 1, j = 0; i >= mid; j++, i--) { - tmp = as->text[i]; + tmptext = as->text[i]; as->text[i] = as->text[j]; - as->text[j] = tmp; + as->text[j] = tmptext; + + tmpansi = as->ansi[i]; + as->ansi[i] = as->ansi[j]; + as->ansi[j] = tmpansi; } - /* Now reverse the markup. */ + /* Now reverse the html-markup. */ for (i = as->nmarkups - 1; i >= 0; i--) { int start, end; info = &(as->markup[i]); @@ -1061,10 +1009,6 @@ mush_free(as, "ansi_string"); } -/* Compress the markup information in an ansi_string. - * - * This combines adjacent identical markup. - */ static int compare_starts(const void *a, const void *b) @@ -1077,111 +1021,117 @@ return ai->start - bi->start; } + + void optimize_ansi_string(ansi_string *as) { int i, j; - /* Nothing to optimize if we've only got 1 or none. */ - if (as->nmarkups > 1) { + if (!as) + return; + + /* If we've only got 1 or 0, or we've already optimized, do nothing. */ + if (as->nmarkups > 1 && as->optimized == 0) { /* Sort the markup codes by their start position */ qsort(as->markup, as->nmarkups, sizeof(markup_information), compare_starts); for (i = 0; i < as->nmarkups; i++) { - /* If start and end are negative, it's a standalone (img) */ - if (as->markup[i].start == -1 && as->markup[i].end == -1) + + /* If end is negative, it's removed or broken. Either way... */ + if (as->markup[i].end < 0) continue; + for (j = i + 1; j < as->nmarkups; j++) { + /* Already removed? */ - if (as->markup[j].start == -1 && as->markup[j].end == -1) + if (as->markup[j].end < 0 && as->markup[j].start < 0) continue; - if (as->markup[j].start > as->markup[i].end) - break; /* Far apart */ - if (as->markup[i].type == MARKUP_COLOR && - as->markup[j].type == MARKUP_COLOR && - (as->markup[i].ansi.bits == as->markup[j].ansi.bits && - as->markup[i].ansi.offbits == as->markup[j].ansi.offbits && - as->markup[i].ansi.fore == as->markup[j].ansi.fore && - as->markup[i].ansi.back == as->markup[j].ansi.back) + + /* End if we can't stretch markup[i] any farther */ + if (as->markup[i].end < as->markup[j].start) + break; + + /* If there's an identical code within our bounds, merge it. */ + if (as->markup[i].start_code && as->markup[j].start_code && + (strcmp(as->markup[j].start_code, as->markup[i].start_code) == 0) ) { if (as->markup[j].end > as->markup[i].end) as->markup[i].end = as->markup[j].end; - if (as->markup[j].start < as->markup[i].start) - as->markup[i].start = as->markup[j].start; - if (as->markup[j].end > as->markup[i].end) - as->markup[j].start = -1; - as->markup[j].end = -1; - } else if ((as->markup[i].start_code && as->markup[j].start_code) && - strcmp(as->markup[j].start_code, - as->markup[i].start_code) == 0) { - /* i and j are adjacent and identical */ - if (as->markup[j].end > as->markup[i].end) - as->markup[i].end = as->markup[j].end; - if (as->markup[j].start < as->markup[i].start) - as->markup[i].start = as->markup[j].start; as->markup[j].start = -1; as->markup[j].end = -1; - } - } - } + } /* end if_indentical */ + } /* end inner loop */ + } /* end outer loop */ } + /* end if_optimized */ + int target = -1; + int len = 0; + j = 0; - /* Get rid of all removed markups */ - for (i = 0, j = 0; i < as->nmarkups; i++) { - if ((as->markup[i].end >= 0) && (as->markup[i].start != as->markup[i].end)) { - if (i != j) { - memmove(&(as->markup[j]), &(as->markup[i]), sizeof(markup_information)); - } + /* Get rid of all removed markups + * "target" is non-negative when we've pegged a destination + * "len" begins counting when we have a target set and we hit a + * block of non-removed markup + * If len is non-zero and we hit a removed markup, shift the block left. + * The end of the removed string is our new target (it's removable anyway) + */ + + for (i = 0; i < as->nmarkups; i++) { + /* Valid tag? */ + if (as->markup[i].end >= 0 && (as->markup[i].start < as->markup[i].end)) { + if (target != -1) + len++; j++; } else { free_markup_info(&(as->markup[i])); + if (len > 0 && target != -1) + memmove(&(as->markup[target]), &(as->markup[i - len]), + len * sizeof(markup_information)); + if (len > 0 || target == -1) { + target = j; + len = 0; + } } } + if (len > 0) + memmove(&(as->markup[target]), &(as->markup[i - len]), + len * sizeof(markup_information)); as->nmarkups = j; + as->optimized = 1; } -/* Copy the start code for a particular markup_info - * For HTML/Pueblo, this inserts TAG_START and TAG_END - * Otherwise it's just a plain copy */ +/* Copy the start code for a particular markup_info */ static int copy_start_code(markup_information *info, char *buff, char **bp) { int retval = 0; char *save; save = *bp; - if (info->start_code) { + if (info && info->start_code) { retval += safe_chr(TAG_START, buff, bp); retval += safe_chr(info->type, buff, bp); retval += safe_str(info->start_code, buff, bp); retval += safe_chr(TAG_END, buff, bp); - } else if (info->type == MARKUP_COLOR) { - retval += write_ansi_data(&(info->ansi), buff, bp); } if (retval) *bp = save; return retval; } -/* Copy the stop code for a particular markup_info - * For HTML/Pueblo, this inserts TAG_START and TAG_END - * Otherwise it's just a plain copy */ +/* Copy the stop code for a particular markup_info */ static int copy_stop_code(markup_information *info, char *buff, char **bp) { int retval = 0; char *save; save = *bp; - if (info->type == MARKUP_HTML && (info->stop_code != NULL)) { + if (info && info->stop_code) { retval += safe_chr(TAG_START, buff, bp); retval += safe_chr(MARKUP_HTML, buff, bp); retval += safe_str(info->stop_code, buff, bp); retval += safe_chr(TAG_END, buff, bp); - } else if (info->type == MARKUP_COLOR) { - retval += safe_chr(TAG_START, buff, bp); - retval += safe_chr(MARKUP_COLOR, buff, bp); - retval += safe_chr('/', buff, bp); - retval += safe_chr(TAG_END, buff, bp); } if (retval) *bp = save; @@ -1205,13 +1155,6 @@ " %d (%s): (start: %d end: %d) start_code: %s stop_code: %s", count++, (info->type == MARKUP_HTML ? "html" : "ansi"), info->start, info->end, info->start_code, info->stop_code); - } else { - notify_format(who, - " %d (%s): (start: %d end: %d) bits: %d fore: %c back: %c", - count++, (info->type == MARKUP_HTML ? "html" : "ansi"), - info->start, info->end, info->ansi.bits, - (info->ansi.fore) ? info->ansi.fore : '-', - (info->ansi.back) ? info->ansi.back : '-'); } } notify_format(who, "Inspecting ansi string complete"); @@ -1233,20 +1176,30 @@ int end; markup_information *dm; - if (start >= as->len) + /* Nothing to delete */ + if (start >= as->len || count <= 0) return 0; - if (count <= 0) - return 0; + + /* We can't delete from a negative index */ + if (start < 0) { + /* start is negative: this *decreases* count. */ + count += start; + start = 0; + } + + /* We can't delete past the end of our string */ if ((start + count) > as->len) count = as->len - start; + /* Nothing to delete */ + if (count <= 0) + return 0; + end = start + count; - as->optimized = 0; - dm = as->markup; - /* Remove or shrink the markup on dst */ + /* Remove or shrink the Pueblo markup on dst */ for (i = 0; i < as->nmarkups; i++) { if (dm[i].start >= start && dm[i].end <= end) { dm[i].start = -1; @@ -1264,10 +1217,16 @@ } } + if (as->nmarkups > 0) + as->optimized = 0; + /* Shift text over */ memmove(as->text + start, as->text + end, as->len - end); + memmove(as->ansi + start, as->ansi + end, + (as->len - end) * sizeof(ansi_data)); as->len -= count; as->text[as->len] = '\0'; + as->ansi[as->len] = ansi_null; return 0; } @@ -1275,120 +1234,147 @@ do { \ x.type = y.type; \ x.priority = y.priority; \ - x.start_code = NULL; \ - x.stop_code = NULL; \ if (y.start_code) x.start_code = mush_strdup(y.start_code,"markup_code"); \ else (x.start_code = NULL); \ if (y.stop_code) x.stop_code = mush_strdup(y.stop_code,"markup_code"); \ else (x.stop_code = NULL); \ - x.ansi.bits = y.ansi.bits; \ - x.ansi.offbits = y.ansi.offbits; \ - x.ansi.fore = y.ansi.fore; \ - x.ansi.back = y.ansi.back; \ } while (0) /** Insert an ansi string into another ansi_string * with markups kept as straight as possible. * \param dst ansi_string to insert into. - * \param loc Location to insert into, 0-indexed + * \param loc Location to insert into, 0-indexed * \param src ansi_string to insert - * \param start start point in src - * \param size length of string from src * \retval 0 success - * \reval 1 failure. + * \retval 1 failure. */ int -ansi_string_insert(ansi_string *dst, int loc, - ansi_string *src, int start, int count) +ansi_string_insert(ansi_string *dst, int loc, ansi_string *src) { int i, j; int len; - int end, m_end; - int rval = 0; + int retval = 0; + int src_len = src->len; + markup_information *dm, *sm; - if (loc >= dst->len) - loc = dst->len; - if (start >= src->len) + /* If src->len == 0, we might have only markup. Stand-alones. Ew! */ + if (src->len <= 0 && src->nmarkups <= 0) return 0; - if (count <= 0) - return 0; - if ((start + count) > src->len) - count = src->len - start; + if (dst->len >= BUFFER_LEN) + return 1; - dst->optimized = 0; + if (src_len >= BUFFER_LEN) + src_len = BUFFER_LEN - 1; + if (src_len < 0) + src_len = 0; + if (loc > dst->len) + loc = dst->len; + if (loc < 0) + loc = 0; + + if (dst->nmarkups > 0 || src->nmarkups > 0) + dst->optimized = 0; + dm = dst->markup; sm = src->markup; - /* End in src */ - end = start + count; - - /* Starting location */ - if (loc <= 0) - loc = 0; - if (loc >= dst->len) - loc = dst->len; - - /* End of src's insert location in dst */ - m_end = loc + count; - - /* shift or widen the markup on dst */ + /* shift or widen the Pueblo markup on dst */ for (i = 0; i < dst->nmarkups; i++) { - if (dm[i].start >= loc) - dm[i].start += count; - if (dm[i].end > loc) - dm[i].end += count; + if (loc <= dm[i].start) + dm[i].start += src_len; + if (loc < dm[i].end) + dm[i].end += src_len; } - /* Copy markup */ + + /* Copy markup onto the end of dst */ for (j = 0; j < src->nmarkups; j++) { - /* It's possible, but not at all easy, to get this much ansi markup */ + + /* It's possible, but not at all easy, to get this much Pueblo markup */ if (i >= BUFFER_LEN) break; - if (((sm[j].start <= end && sm[j].end >= start) && sm[j].start >= 0) || - (sm[j].end > start && sm[j].end <= end)) { + + /* Is it a valid tag? */ + if (sm[j].end >= 0 && sm[j].start < sm[j].end && + ((sm[j].start < 0 && sm[j].end <= src_len) + || (sm[j].start < src_len && sm[j].start >= 0))) { + copyto(dm[i], sm[j]); - if (sm[j].start < 0) { + + /* If our start is non-negative, start+loc is its position in dm */ + if (sm[j].start >= 0) + dm[i].start = sm[j].start + loc; + else dm[i].start = -1; - } else { - dm[i].start = loc + sm[j].start - start; - if (dm[i].start < loc) - dm[i].start = loc; - } - dm[i].end = loc + sm[j].end - start; - if (dm[i].end >= m_end) - dm[i].end = m_end; + + /* Make sure the end position is within the bounds of its own string */ + if (sm[j].end <= src_len) + dm[i].end = sm[j].end + loc; + else + dm[i].end = src_len + loc; + i++; } } + + for (; i >= BUFFER_LEN; i--) + free_markup_info(&(dm[i])); dst->nmarkups = i; - dst->len += count; + dst->len += src_len; if (dst->len >= BUFFER_LEN) { - rval = 1; + retval = 1; dst->len = BUFFER_LEN - 1; } - len = dst->len - m_end; + len = dst->len - src->len - loc; + + /* Determine what old ansi might stretch across the new text. + * This sets backansi to any ansi values (bits, colors) that + * are continuous across an entire length of text. */ + ansi_data backansi = ansi_null; + if (0 < loc && loc < dst->len) { + backansi.offbits = dst->ansi[loc - 1].offbits & dst->ansi[loc].offbits; + backansi.bits = dst->ansi[loc - 1].bits & dst->ansi[loc].bits; + if (dst->ansi[loc - 1].fore == dst->ansi[loc].fore) + backansi.fore = dst->ansi[loc].fore; + if (dst->ansi[loc - 1].back == dst->ansi[loc].back) + backansi.back = dst->ansi[loc].back; + } + /* Shift text over */ - if (len > 0) { - if (m_end + len >= BUFFER_LEN) { - len = (BUFFER_LEN - m_end - loc - 1); - memmove(dst->text + m_end, dst->text + loc, len); - } else { - memmove(dst->text + m_end, dst->text + loc, len); - } + if (0 < len) { + /* The length beyond our insertion that can *actually* be moved. */ + if (loc + src_len + len >= BUFFER_LEN) + len = BUFFER_LEN - loc - src_len - len - 1; + memmove(dst->text + loc + src_len, dst->text + loc, len); + memmove(dst->ansi + loc + src_len, dst->ansi + loc, + len * sizeof(ansi_data)); } + /* Copy text from src */ - if (loc + count >= BUFFER_LEN) - count = BUFFER_LEN - 1 - loc; - memcpy(dst->text + loc, src->text + start, count); + if (loc + src_len >= BUFFER_LEN) + src_len = BUFFER_LEN - 1 - loc; + if (src_len > 0) { + memcpy(dst->text + loc, src->text, src_len); + for (i = 0; i < src_len; i++) { + j = loc + i; + dst->ansi[j].back = src->ansi[i].back ? src->ansi[i].back : backansi.back; + dst->ansi[j].fore = src->ansi[i].fore ? src->ansi[i].fore : backansi.fore; + dst->ansi[j].offbits = src->ansi[i].offbits | backansi.offbits; + dst->ansi[j].bits = + ~(dst->ansi[j].offbits) & (src->ansi[i].bits | backansi.bits); + } + } dst->text[dst->len] = '\0'; - return rval; + dst->ansi[dst->len] = ansi_null; + return retval; } + /** Replace a portion of an ansi string with * another ansi string, keeping markups as * straight as possible. @@ -1396,116 +1382,123 @@ * \param loc Location to insert into, 0-indexed * \param len Length of string inside dst to replace * \param src ansi_string to insert - * \param start start point in src - * \param size length of string from src * \retval 0 success - * \reval 1 failure. + * \retval 1 failure. */ int -ansi_string_replace(ansi_string *dst, int loc, int len, - ansi_string *src, int start, int count) +ansi_string_replace(ansi_string *dst, int loc, int len, ansi_string *src) { int i, j; - int end, m_end, s_end; + int end, d_end; int diff; - int rval = 0; + int retval = 0; markup_information *dm, *sm; + if (loc < 0) + loc = 0; + /* Is it really an insert? */ - if (loc >= dst->len || len == 0) { - return ansi_string_insert(dst, loc, src, start, count); - } - /* Boundaries */ - if (start <= 0) - start = 0; - if ((start + count) > src->len) - count = src->len - start; - if ((len + loc) > dst->len) - len = dst->len - loc; + if (loc >= dst->len || len <= 0) + return ansi_string_insert(dst, loc, src); + /* Is it really a delete? */ - if ((start >= src->len) || (count <= 0)) { + if (src->len <= 0) return ansi_string_delete(dst, loc, len); - } - /* Starting location */ - if (loc <= 0) - loc = 0; - if (loc >= dst->len) - loc = dst->len; + /* We can't delete past the end of our string. */ + if ((len + loc) > dst->len) + len = dst->len - loc; - end = loc + len; - diff = count - len; - m_end = loc + count; + end = loc + len; /* End of the removed section */ + d_end = loc + src->len; /* End of the new string within the dst string */ + diff = src->len - len; /* Total change in length */ + if (diff > 0 && dst->len >= BUFFER_LEN) + return 1; + dst->optimized = 0; dm = dst->markup; sm = src->markup; - /* Modify, remove, stretch, and mangle markup */ + /* Modify, remove, stretch, and mangle Pueblo markup */ for (i = 0; i < dst->nmarkups; i++) { + /* If it doesn't cross into the replaced part, leave as is */ if (dm[i].end <= loc) continue; - if (dm[i].start == loc && dm[i].end == end) { - /* Debatable: If it surrounds the replaced part exactly, - * keep it, stretching it to wrap around the replacement */ - dm[i].end = m_end; - } else if (dm[i].start <= loc && dm[i].end >= end) { - dm[i].end += diff; - if (dm[i].end > BUFFER_LEN) - dm[i].end = BUFFER_LEN; - } else if (dm[i].start >= loc && dm[i].end <= end) { - /* If it's completely inside the removed area, remove it */ - dm[i].start = -1; - dm[i].end = -1; - } else if (dm[i].start < loc && dm[i].end < end) { - /* If it ends inside, but begins to the left, push end left. */ - if (dm[i].start >= 0) - dm[i].end = loc; - else /* Standalone is inside */ + + /* Debatable: If it surrounds the replaced part exactly, try + * to keep it, stretching it to wrap around the replacement. */ + if (loc <= dm[i].start && dm[i].end <= end) { + /* If the locations match, stretch it, otherwise overwrite it. */ + if (dm[i].start == loc && dm[i].end == end) { + dm[i].end = loc + src->len; + } else { + dm[i].start = -1; dm[i].end = -1; - } else if (dm[i].end > end && dm[i].start < end) { - /* If it begins inside, but ends to the right, push start right. */ - if (dm[i].start >= 0) - dm[i].start = m_end; - dm[i].end += diff; - } else if (dm[i].start > end) { - /* It's to the right */ - dm[i].start += diff; - dm[i].end += diff; - } else { - /* Shift */ - if (dm[i].start > loc) + } + continue; + } + + /* Shift the beginning if necessary */ + if (loc < dm[i].start) { + /* If we're beyond the markup, just shift start. */ + if (end < dm[i].start) { dm[i].start += diff; - dm[i].end += diff; + } else { + /* Otherwise it's inside; push it to the right of the new markup. */ + dm[i].start = loc + src->len; + } + if (dm[i].start > BUFFER_LEN) { + dm[i].start = -1; + dm[i].end = -1; + } } + + /* If this markup ends before the new one, squish it. */ + if (dm[i].end < end) { + dm[i].end = (dm[i].start >= 0) ? loc : -1; /* Or erase stand-alones */ + } else { + dm[i].end += diff; /* Otherwise, shift it. */ + if (dm[i].end > BUFFER_LEN) + dm[i].end = BUFFER_LEN; + } + } - s_end = start + count; - /* Copy markup */ + /* Copy markup. Code taken from ansi_string_insert. */ for (j = 0; j < src->nmarkups; j++) { - /* It's possible, but not at all easy, to get this much ansi markup */ + + /* It's possible, but not at all easy, to get this much pueblo markup */ if (i >= BUFFER_LEN) break; - if (((sm[j].start <= s_end && sm[j].end >= start) && sm[j].start >= 0) || - (sm[j].end > start && sm[j].end <= s_end)) { + + /* Is it a valid tag? */ + if (sm[j].end >= 0 && sm[j].start < sm[j].end && + ((sm[j].start < 0 && sm[j].end <= src->len) + || (sm[j].start < src->len && sm[j].start >= 0))) { + copyto(dm[i], sm[j]); - if (sm[j].start < 0) { + + /* If our start is non-negative, start+loc is its position in dm */ + if (sm[j].start >= 0) + dm[i].start = sm[j].start + loc; + else dm[i].start = -1; - } else { - dm[i].start = loc + sm[j].start - start; - if (dm[i].start < loc) - dm[i].start = loc; - } - dm[i].end = loc + sm[j].end - start; - if (dm[i].end >= m_end) - dm[i].end = m_end; + + /* Make sure the end position is within the bounds of its own string */ + if (sm[j].end <= src->len) + dm[i].end = sm[j].end + loc; + else + dm[i].end = src->len + loc; + i++; } } - if (i >= BUFFER_LEN) - i = BUFFER_LEN - 1; + + for (; i >= BUFFER_LEN; i--) + free_markup_info(&(dm[i])); dst->nmarkups = i; /* length of original string after replace bits */ @@ -1513,30 +1506,131 @@ dst->len += diff; if (dst->len >= BUFFER_LEN) { - rval = 1; + retval = 1; dst->len = BUFFER_LEN - 1; } + /* Determine what old ansi might stretch across the new text. + * This sets backansi to any ansi values (bits, colors) that + * are continuous across an entire length of text. + */ + ansi_data backansi = dst->ansi[loc]; + for (i = loc; i < end && !ansi_isnull(backansi); i++) { + backansi.offbits &= dst->ansi[i].offbits; + backansi.bits &= dst->ansi[i].bits; + if (backansi.fore != dst->ansi[i].fore) + backansi.fore = 0; + if (backansi.back != dst->ansi[i].back) + backansi.back = 0; + } + /* Shift text over */ if (diff != 0) { - if (m_end + len >= BUFFER_LEN) { - len = BUFFER_LEN - (1 + m_end); - if (len > 0) { - memmove(dst->text + m_end, dst->text + end, len); - } - } else { - memmove(dst->text + m_end, dst->text + end, len); + if (d_end + len >= BUFFER_LEN) + len = BUFFER_LEN - (1 + d_end); + if (len > 0) { + memmove(dst->text + d_end, dst->text + end, len); + memmove(dst->ansi + d_end, dst->ansi + end, len * sizeof(ansi_data)); } } /* Copy text from src */ - if (loc + count >= BUFFER_LEN) - count = BUFFER_LEN - (1 + loc); - memcpy(dst->text + loc, src->text + start, count); + len = src->len; + if (loc + len >= BUFFER_LEN) + len = BUFFER_LEN - loc - 1; + if (len > 0) { + memcpy(dst->text + loc, src->text, len); + for (i = 0; i < len; i++) { + j = loc + i; + dst->ansi[j].back = src->ansi[i].back ? src->ansi[i].back : backansi.back; + dst->ansi[j].fore = src->ansi[i].fore ? src->ansi[i].fore : backansi.fore; + dst->ansi[j].offbits = src->ansi[i].offbits | backansi.offbits; + dst->ansi[j].bits = + ~(dst->ansi[j].offbits) & (src->ansi[i].bits | backansi.bits); + } + } dst->text[dst->len] = '\0'; - return rval; + dst->ansi[dst->len] = ansi_null; + return retval; } + +/** Scrambles an ansi_string, returning a pointer to the new string. + * \param as ansi_string to scramble. + */ +ansi_string * +scramble_ansi_string(ansi_string *as) +{ + int i, j, k; + int pos[BUFFER_LEN]; + markup_information *dm; + ansi_string *tmp = NULL; + + if (!as) + return NULL; + + optimize_ansi_string(as); + + tmp = mush_malloc(sizeof(ansi_string), "ansi_string"); + if (!tmp) + return NULL; + + memset(tmp, 0, sizeof(ansi_string)); + + /* Scramble the text */ + tmp->len = as->len; + + for (i = 0; i < as->len; i++) + pos[i] = i; + + for (i = 0; i < as->len; i++) { + j = get_random_long(0, as->len - 1); + k = pos[i]; + pos[i] = pos[j]; + pos[j] = k; + } + + /* The old scramble did new[i] = old[pos[i]], + * but handling markup tags is easier if we do it this way. */ + /* Scramble the text and ansi... */ + for (i = 0; i < as->len; i++) { + tmp->text[pos[i]] = as->text[i]; + tmp->ansi[pos[i]] = as->ansi[i]; + } + + /* Scramble the Pueblo markup */ + dm = tmp->markup; + if (as->nmarkups > 0) + tmp->optimized = 0; + + /* Copy the standalones (tags with -1 for start) */ + for (i = 0; i < as->nmarkups && as->markup[i].start == -1; i++) { + copyto(dm[i], as->markup[i]); + dm[i].end = pos[i]; + } + + /* Copy the rest */ + j = i; + for (; i < as->nmarkups; i++) { + for (k = as->markup[i].start; k < as->markup[i].end; k++) { + copyto(dm[j], as->markup[i]); + dm[j].start = pos[k]; + dm[j].end = dm[j].start + 1; + j++; + if (j >= BUFFER_LEN) { + optimize_ansi_string(tmp); + tmp->optimized = 0; + j = tmp->nmarkups; + if (j >= BUFFER_LEN) + return tmp; + } + } + } + tmp->nmarkups = j; + optimize_ansi_string(tmp); + return tmp; +} + #undef copyto /** Safely append an ansi_string into a buffer as a real string, @@ -1548,25 +1642,26 @@ * \retval 0 success. * \retval 1 failure. */ - int safe_ansi_string(ansi_string *as, int start, int len, char *buff, char **bp) { int i, j; + int cur; markup_information *info; int nextstart, nextend, next; int end = start + len; int retval = 0; + ansi_data curansi; - if (as->optimized == 0) { - optimize_ansi_string(as); - as->optimized = 1; - } + if (!as) + return 0; + optimize_ansi_string(as); + if (len <= 0) return 0; - - i = start; + if (as->len > BUFFER_LEN) + as->len = BUFFER_LEN; if (start >= as->len) return 0; if (end > as->len) @@ -1575,86 +1670,143 @@ /* Standalones (Stop codes with -1 for start) */ for (j = 0; j < as->nmarkups; j++) { info = &(as->markup[j]); - if (info->start == -1 && info->end == i) { - /* This is a standalone tag. YUCK! */ - if (info->stop_code) - retval += safe_str(info->stop_code, buff, bp); - } + if (info->start != -1) + break; /* No more standalone tags to copy */ + if (info->stop_code && info->end == start) + retval += safe_str(info->stop_code, buff, bp); } /* Now, start codes of everything that impacts us. */ - for (j = 0; j < as->nmarkups; j++) { + for (; j < as->nmarkups; j++) { info = &(as->markup[j]); - if (info->start >= 0) { - if (info->start <= i && info->end > i) { - retval += copy_start_code(info, buff, bp); - } - } + if (info->start > start) + break; /* No more tags to copy. */ + if (info->end > start) + retval += copy_start_code(info, buff, bp); } - - /* Find the next changes */ - nextstart = BUFFER_LEN + 1; + /* Find the next changes--new tags, or a prior tag ending. */ + i = start; nextend = BUFFER_LEN + 1; - for (j = 0; j < as->nmarkups; j++) { - info = &(as->markup[j]); - if (info->start > i && info->start < nextstart) - nextstart = info->start; - if (info->end > i && info->end < nextend) - nextend = info->end; + /* If there is another start, it's our next one; we have a sorted list. */ + if (j < as->nmarkups) + nextstart = as->markup[j].start; + else + nextstart = BUFFER_LEN + 1; + + /* To find the next END is harder, since it isn't sorted... */ + /* Scan forward. Stop once we find a tag with a start beyond + * this one's end. Anything beyond that can't be the nextend, + * so we'll backtrack from there. */ + if (as->nmarkups > 0) { + cur = j; + if (cur >= as->nmarkups) + cur--; + info = &(as->markup[cur]); + while (cur < as->nmarkups && as->markup[cur].start < info->end) + cur++; + cur--; + if (info->end > as->markup[cur].start) { + for (; cur >= 0; cur--) { + if (as->markup[cur].end > i && as->markup[cur].end < nextend) + nextend = as->markup[cur].end; + } + } } next = (nextend < nextstart) ? nextend : nextstart; + if (end < next) next = end; - for (; i < next && i < as->len; i++) { - if (as->text[i]) + curansi = as->ansi[start]; + if (!ansi_isnull(curansi)) + write_ansi_data(&curansi, buff, bp); + /* If there's any text/ansi between start and next, print it */ + for (i = start; i < next && i < as->len; i++) { + if (as->text[i]) { + if (!ansi_equal(curansi, as->ansi[i])) { + if (!ansi_isnull(curansi)) + write_ansi_close(buff, bp); + curansi = as->ansi[i]; + if (!ansi_isnull(curansi)) + write_ansi_data(&curansi, buff, bp); + } safe_chr(as->text[i], buff, bp); + } } - i = next; + cur = j; /* Our current markup */ + i = next; /* Our current position */ + + /* Basically the same thing as above, in loop form. */ while (i < end) { + if (i >= nextend) { nextend = BUFFER_LEN + 2; - for (j = 0; j < as->nmarkups; j++) { + j = cur; + /* Find the last markup that could possibly have a relevant end code */ + while (j < as->nmarkups && as->markup[j].start < as->markup[cur].end) + j++; + j--; + /* We MUST have markup if we're here, so no nmarkup > 0 check. */ + /* Print relevant stop codes, and find our nextend */ + for (; j >= 0; j--) { info = &(as->markup[j]); - if (info->end == i) { - retval += copy_stop_code(info, buff, bp); + if (info->end >= i) { + if (info->end == i) + retval += copy_stop_code(info, buff, bp); + else if (info->end < nextend) + nextend = info->end; } - if (info->end > i && info->end < nextend) - nextend = info->end; } } + if (i >= nextstart) { - nextstart = BUFFER_LEN + 2; - for (j = 0; j < as->nmarkups; j++) { - info = &(as->markup[j]); - if (info->start == i) { - retval += copy_start_code(info, buff, bp); - } else if (info->start > i && info->start < nextstart) { - nextstart = info->start; - } - } + /* Print out all the relevant start codes */ + for (; cur < as->nmarkups && as->markup[cur].start == i; cur++) + retval += copy_start_code(&(as->markup[cur]), buff, bp); + if (cur < as->nmarkups) + nextstart = as->markup[cur].start; + else + nextstart = BUFFER_LEN + 2; } + next = (nextend < nextstart) ? nextend : nextstart; if (end < next) next = end; + for (; i < next && i < as->len; i++) { - if (as->text[i]) + if (as->text[i]) { + if (!ansi_equal(curansi, as->ansi[i])) { + if (!ansi_isnull(curansi)) + write_ansi_close(buff, bp); + curansi = as->ansi[i]; + if (!ansi_isnull(curansi)) + write_ansi_data(&curansi, buff, bp); + } safe_chr(as->text[i], buff, bp); + } } } /* Now, find all things that end for us */ - for (j = 0; j < as->nmarkups; j++) { - info = &(as->markup[j]); - if (info->start < i && info->end >= i) - retval += copy_stop_code(info, buff, bp); + if (as->nmarkups > 0) { + j = cur; + while (cur < as->nmarkups && as->markup[j].end > as->markup[cur].start) + cur++; + cur--; + for (; cur >= 0; cur--) { + info = &(as->markup[cur]); + if (info->start < i && info->end >= i) + retval += copy_stop_code(info, buff, bp); + } } + if (!retval && !ansi_isnull(curansi)) + retval += write_ansi_close(buff, bp); return retval; } @@ -1664,272 +1816,278 @@ extern char escaped_chars[UCHAR_MAX + 1]; -static int escape_strn(char *s, int start, int count, char *buff, char **bp); - static int -escape_str(char *in, char *buff, char **bp) +escape_marked_str(char **str, char *buff, char **bp) { - return escape_strn(in, 0, strlen(in), buff, bp); -} - -static int -escape_strn(char *s, int start, int count, char *buff, char **bp) -{ unsigned char *in; int retval = 0; int dospace = 1; int spaces = 0; - int i, j; + int i; - in = (unsigned char *) s; - - if (*in) { - count += start; - for (i = start; in[i] && i < count; i++) { - if (in[i] == ' ') { - spaces++; - } else { - if (spaces) { - if (spaces >= 5) { - retval += safe_str("[space(", buff, bp); - retval += safe_number(spaces, buff, bp); - retval += safe_str(")]", buff, bp); - } else { - if (dospace) { - spaces--; + if (!str || !*str || !**str) + return 0; + in = (unsigned char *) *str; + for (; *in && *in != ESC_CHAR && *in != TAG_START; in++) { + if (*in == ' ') { + spaces++; + } else { + if (spaces) { + if (spaces >= 5) { + retval += safe_str("[space(", buff, bp); + retval += safe_number(spaces, buff, bp); + retval += safe_str(")]", buff, bp); + } else { + if (dospace) { + spaces--; + retval += safe_str("%b", buff, bp); + } + while (spaces) { + retval += safe_chr(' ', buff, bp); + if (--spaces) { + --spaces; retval += safe_str("%b", buff, bp); } - while (spaces) { - retval += safe_chr(' ', buff, bp); - if (--spaces) { - --spaces; - retval += safe_str("%b", buff, bp); - } - } } } - spaces = 0; - dospace = 0; - if (in[i] == '\n') { - retval += safe_str("%r", buff, bp); - } else if (in[i] == '\t') { - retval += safe_str("%t", buff, bp); - } else if (in[i] == BEEP_CHAR) { - for (j = i; in[i + 1] == BEEP_CHAR && (i - j) < 4; i++) ; - retval += safe_format(buff, bp, "[beep(%d)]", (i - j) + 1); - } else if (escaped_chars[in[i]]) { + } + spaces = 0; + dospace = 0; + switch (*in) { + case '\n': + retval += safe_str("%r", buff, bp); + break; + case '\t': + retval += safe_str("%t", buff, bp); + break; + case BEEP_CHAR: + for (i = 1; *(in + 1) == BEEP_CHAR && i < 5; in++, i++) ; + retval += safe_format(buff, bp, "[beep(%d)]", i); + break; + default: + if (escaped_chars[*in]) retval += safe_chr('\\', buff, bp); - retval += safe_chr(in[i], buff, bp); - } else { - retval += safe_chr(in[i], buff, bp); - } + retval += safe_chr(*in, buff, bp); + break; } } - if (spaces) { - if (spaces >= 5) { - retval += safe_str("[space(", buff, bp); - retval += safe_number(spaces, buff, bp); - retval += safe_str(")]", buff, bp); - } else { - spaces--; /* This is for the final %b space */ - if (spaces && dospace) { - spaces--; + } + if (spaces) { + if (spaces >= 5) { + retval += safe_str("[space(", buff, bp); + retval += safe_number(spaces, buff, bp); + retval += safe_str(")]", buff, bp); + } else { + spaces--; /* This is for the final %b space */ + if (spaces && dospace) { + spaces--; + retval += safe_str("%b", buff, bp); + } + while (spaces) { + safe_chr(' ', buff, bp); + if (--spaces) { + --spaces; retval += safe_str("%b", buff, bp); } - while (spaces) { - safe_chr(' ', buff, bp); - if (--spaces) { - --spaces; - retval += safe_str("%b", buff, bp); - } - } - retval += safe_str("%b", buff, bp); } + retval += safe_str("%b", buff, bp); } } + *str = (char *) in; return retval; } - -static int -dump_start_code(markup_information *info, char *buff, char **bp) +/* Does the work of decompose_str, which is found in look.c. + * Even handles ANSI and Pueblo, which is why it's so ugly. + * Code based off of real_parse_ansi_string, not safe_ansi_string. + */ +int +real_decompose_str(char *orig, char *buff, char **bp) { - int retval = 0; - char *save; - save = *bp; - if (info->type == MARKUP_HTML) { - if (info->stop_code != NULL) { - char *ptr; - retval += safe_str("[tagwrap(", buff, bp); - if ((ptr = strchr(info->start_code, ' ')) != NULL) { - *(ptr++) = '\0'; - retval += escape_str(info->start_code, buff, bp); - if (*ptr) { - retval += safe_chr(',', buff, bp); - retval += escape_str(ptr, buff, bp); - } - ptr--; - *ptr = ' '; - } else { - retval += escape_str(info->start_code, buff, bp); - } - retval += safe_chr(',', buff, bp); - } else { - retval += safe_str("[tag(", buff, bp); - retval += escape_str(info->start_code, buff, bp); - retval += safe_str(")]", buff, bp); - } - } else { - /* Find the digits */ - retval += safe_str("[ansi(", buff, bp); - if (info->ansi.fore == 'n') { - retval += safe_chr('n', buff, bp); - } else { -#define CBIT_SET(x,y) (x.bits & y) - if (CBIT_SET(info->ansi, CBIT_FLASH)) - retval += safe_chr('f', buff, bp); - if (CBIT_SET(info->ansi, CBIT_HILITE)) - retval += safe_chr('h', buff, bp); - if (CBIT_SET(info->ansi, CBIT_INVERT)) - retval += safe_chr('i', buff, bp); - if (CBIT_SET(info->ansi, CBIT_UNDERSCORE)) - retval += safe_chr('u', buff, bp); -#undef CBIT_SET -#define CBIT_SET(x,y) (x.offbits & y) - if (CBIT_SET(info->ansi, CBIT_FLASH)) - retval += safe_chr('F', buff, bp); - if (CBIT_SET(info->ansi, CBIT_HILITE)) - retval += safe_chr('H', buff, bp); - if (CBIT_SET(info->ansi, CBIT_INVERT)) - retval += safe_chr('I', buff, bp); - if (CBIT_SET(info->ansi, CBIT_UNDERSCORE)) - retval += safe_chr('U', buff, bp); -#undef CBIT_SET + int i; + char *str = orig; + char *tmp; + char *pstr; + char type; - if (info->ansi.fore) - retval += safe_chr(info->ansi.fore, buff, bp); - if (info->ansi.back) - retval += safe_chr(info->ansi.back, buff, bp); - retval += safe_chr(',', buff, bp); - } - } - if (retval) - *bp = save; - return retval; -} + ansi_data ansistack[BUFFER_LEN]; + ansistack[0] = ansi_null; + ansi_data oldansi; + ansi_data tmpansi; + int ansitop = 0; + int ansiheight = 0; + int howmanyopen = 0; + int oldcodes = 0; -static int -dump_stop_code(markup_information *info - __attribute__ ((__unused__)), char *buff, char **bp) -{ - char *save = *bp; - if (safe_str(")]", buff, bp)) { - *bp = save; - } - return 1; -} + char *pueblostack[BUFFER_LEN]; + char tagbuff[BUFFER_LEN]; + int pueblotop = -1; -int -dump_ansi_string(ansi_string *as, char *buff, char **bp) -{ - int i, j; - markup_information *info; - int nextstart, nextend, next; - int end; - int start = 0; int retval = 0; - end = as->len; + if (!str || !*str) + return 0; - if (as->optimized == 0) { - optimize_ansi_string(as); - as->optimized = 1; - } + retval += escape_marked_str(&str, buff, bp); - i = start; - if (start > as->len) - return 0; - if (end > as->len) - end = as->len; + while (str && *str && *str != '\0') { + oldansi = ansistack[ansitop]; + ansiheight = ansitop; + while (*str == TAG_START || *str == ESC_CHAR) { + switch (*str) { + case TAG_START: + for (tmp = str; *tmp && *tmp != TAG_END; tmp++) ; + if (*tmp) { + *tmp = '\0'; + } else { + tmp--; + } + str++; + type = *(str++); + switch (type) { + case MARKUP_COLOR: + if (!*str) + break; + if (oldcodes == 1) { + ansitop--; + oldcodes = 0; + } + /* Start or end tag? */ + if (*str != '/') { + define_ansi_data(&tmpansi, str); + nest_ansi_data(&(ansistack[ansitop]), &tmpansi); + ansitop++; + ansistack[ansitop] = tmpansi; + } else { + if (*(str + 1) == 'a') { + ansitop = 0; + } else { + if (ansitop > 0) { + ansitop--; + } + } + } + break; + case MARKUP_HTML: + if (!*str) + break; + if (*str != '/') { + pueblotop++; + snprintf(tagbuff, BUFFER_LEN, "%s", parse_tagname(str)); + pueblostack[pueblotop] = mush_strdup(tagbuff, "markup_code"); - /* Standalones (Stop codes with -1 for start) */ - for (j = 0; j < as->nmarkups; j++) { - info = &(as->markup[j]); - if (info->start == -1 && info->end == i) { - /* This is a standalone tag. YUCK! */ - if (info->stop_code) - retval += safe_str(info->stop_code, buff, bp); - } - } + retval += safe_str("[tag(", buff, bp); + retval += safe_str(tagbuff, buff, bp); + str += strlen(tagbuff); + if (str && *str) { + while (str && str != tmp) { + str++; + pstr = strchr(str, '='); + if (pstr) { + *pstr = '\0'; + retval += safe_chr(',', buff, bp); + retval += safe_str(str, buff, bp); + retval += safe_chr('=', buff, bp); + str = pstr + 1; + pstr = strchr(str, '\"'); - /* Now, start codes of everything that impacts us. */ - for (j = 0; j < as->nmarkups; j++) { - info = &(as->markup[j]); - if (info->start >= 0) { - if (info->start <= i && info->end > i) { - retval += dump_start_code(info, buff, bp); - } - } - } + retval += safe_chr('\"', buff, bp); + if (str == pstr) { + str++; + pstr = strchr(str, '\"'); + } else { + pstr = strchr(str, ' '); + } + if (!pstr) + pstr = tmp; - /* Find the next changes */ - nextstart = BUFFER_LEN + 1; - nextend = BUFFER_LEN + 1; + *pstr = '\0'; + retval += safe_str(str, buff, bp); + retval += safe_chr('\"', buff, bp); + str = pstr; + } else { + safe_str(str, buff, bp); + break; + } + } + } + retval += safe_str(")]", buff, bp); - for (j = 0; j < as->nmarkups; j++) { - info = &(as->markup[j]); - if (info->start > i && info->start < nextstart) - nextstart = info->start; - if (info->end > i && info->end < nextend) - nextend = info->end; - } + } else { + if (pueblotop > -1) { + i = (*(str + 1) == 'a') ? 0 : pueblotop; + for (i--; pueblotop > i; pueblotop--) { + retval += safe_str("[endtag(", buff, bp); + retval += safe_str(pueblostack[pueblotop], buff, bp); + retval += safe_str(")]", buff, bp); + mush_free(pueblostack[pueblotop], "markup_code"); + } + } + } + break; + } + tmp++; + str = tmp; + break; + case ESC_CHAR: + /* It SHOULD be impossible to get here... */ + for (tmp = str; *tmp && *tmp != 'm'; tmp++) ; - next = (nextend < nextstart) ? nextend : nextstart; - if (end < next) - next = end; + /* Store the "background" colors */ + tmpansi = ansistack[ansitop]; + if (oldcodes == 0) { + oldcodes = 1; + ansitop++; + ansistack[ansitop] = tmpansi; + ansistack[ansitop].offbits = 0; + } - if (next > as->len) - next = as->len; - escape_strn(as->text, i, next - i, buff, bp); - i = next; + read_raw_ansi_data(&tmpansi, str); + ansistack[ansitop].bits |= tmpansi.bits; + ansistack[ansitop].bits &= ~(tmpansi.offbits); /* ANSI_RAW_NORMAL */ + if (tmpansi.fore) + ansistack[ansitop].fore = tmpansi.fore; + if (tmpansi.back) + ansistack[ansitop].back = tmpansi.back; - while (i < end) { - if (i >= nextend) { - nextend = BUFFER_LEN + 2; - for (j = 0; j < as->nmarkups; j++) { - info = &(as->markup[j]); - if (info->end == i) { - retval += dump_stop_code(info, buff, bp); - } - if (info->end > i && info->end < nextend) - nextend = info->end; + str = tmp; + if (*tmp) + str++; + break; } } - if (i >= nextstart) { - nextstart = BUFFER_LEN + 2; - for (j = 0; j < as->nmarkups; j++) { - info = &(as->markup[j]); - if (info->start == i) { - retval += dump_start_code(info, buff, bp); - } else if (info->start > i && info->start < nextstart) { - nextstart = info->start; + + /* Handle ANSI/Text */ + tmpansi = ansistack[ansitop]; + if (ansitop > 0 || ansiheight > 0) { + /* Close existing tags as necessary to cleanly open the next. */ + /* oldansi = ansistack[ansiheight]; */ + if (!ansi_equal(oldansi, tmpansi)) { + while (ansiheight > 0) { + if (howmanyopen > 0) { + howmanyopen--; + retval += safe_str(")]", buff, bp); + } + ansiheight--; } } + if (!ansi_isnull(tmpansi) && !ansi_equal(oldansi, tmpansi)) { + retval += safe_str("[ansi(", buff, bp); + retval += write_ansi_letters(tmpansi, buff, bp); + retval += safe_chr(',', buff, bp); + howmanyopen++; + } } - next = (nextend < nextstart) ? nextend : nextstart; - if (end < next) - next = end; - - escape_strn(as->text, i, next - i, buff, bp); - i = next; + retval += escape_marked_str(&str, buff, bp); } - /* Now, find all things that end for us */ - for (j = 0; j < as->nmarkups; j++) { - info = &(as->markup[j]); - if (info->start < i && info->end >= i) - retval += dump_stop_code(info, buff, bp); + for (; howmanyopen > 0; howmanyopen--) + retval += safe_str(")]", buff, bp); + for (; pueblotop > -1; pueblotop--) { + retval += safe_str("[endtag(", buff, bp); + retval += safe_str(pueblostack[pueblotop], buff, bp); + retval += safe_str(")]", buff, bp); } return retval; @@ -1945,8 +2103,9 @@ * \return size of subpattern, or -1 if unknown pattern */ int -ansi_pcre_copy_substring(ansi_string *as, int *ovector, int stringcount, - int stringnumber, int nonempty, char *buff, char **bp) +ansi_pcre_copy_substring(ansi_string *as, int *ovector, + int stringcount, int stringnumber, + int nonempty, char *buff, char **bp) { int yield; if (stringnumber < 0 || stringnumber >= stringcount) @@ -1972,8 +2131,9 @@ * \return size of subpattern, or -1 if unknown pattern */ int -ansi_pcre_copy_named_substring(const pcre * code, ansi_string *as, int *ovector, - int stringcount, const char *stringname, int ne, +ansi_pcre_copy_named_substring(const pcre * code, ansi_string *as, + int *ovector, int stringcount, + const char *stringname, int ne, char *buff, char **bp) { int n = pcre_get_stringnumber(code, stringname); @@ -1997,7 +2157,6 @@ { int result = 0; char *save = buf; - result = safe_chr(TAG_START, buf, bp); result = safe_chr(type, buf, bp); result = safe_str(a_tag, buf, bp); @@ -2005,7 +2164,6 @@ /* If it didn't all fit, rewind. */ if (result) *bp = save; - return result; } @@ -2032,7 +2190,6 @@ { int result = 0; char *save = buf; - result = safe_chr(TAG_START, buf, bp); result = safe_chr(type, buf, bp); result = safe_chr('/', buf, bp); @@ -2041,7 +2198,6 @@ /* If it didn't all fit, rewind. */ if (result) *bp = save; - return result; } @@ -2065,12 +2221,11 @@ * \retval 1, tagged text wouldn't fit in buffer. */ int -safe_tag_wrap(char const *a_tag, char const *params, char const *data, - char *buf, char **bp, dbref player) +safe_tag_wrap(char const *a_tag, char const *params, + char const *data, char *buf, char **bp, dbref player) { int result = 0; char *save = buf; - if (SUPPORT_PUEBLO) { result = safe_chr(TAG_START, buf, bp); result = safe_chr(MARKUP_HTML, buf, bp); Index: src/access.c =================================================================== --- src/access.c (.../p4) (revision 1119) +++ src/access.c (.../p5) (revision 1119) @@ -77,6 +77,7 @@ #endif #include "conf.h" #include "externs.h" +#include "pcre.h" #include "access.h" #include "mymalloc.h" #include "match.h" @@ -94,8 +95,8 @@ */ struct a_acsflag { const char *name; /**< Name of the access flag */ - int toggle; /**< Is this a negatable flag? */ - int flag; /**< Bitmask of the flag */ + bool toggle; /**< Is this a negatable flag? */ + uint32_t flag; /**< Bitmask of the flag */ }; static acsflag acslist[] = { {"connect", 1, ACS_CONNECT}, @@ -113,31 +114,62 @@ }; static struct access *access_top; -static int add_access_node - (const char *host, const dbref who, const int can, const int cant, - const char *comment); static void free_access_list(void); -static int -add_access_node(const char *host, const dbref who, const int can, - const int cant, const char *comment) +extern const unsigned char *tables; + +static struct access * +sitelock_alloc(const char *host, dbref who, + uint32_t can, uint32_t cant, + const char *comment, const char **errptr) + __attribute_malloc__; + + static struct access *sitelock_alloc(const char *host, dbref who, + uint32_t can, uint32_t cant, + const char *comment, + const char **errptr) { - struct access *end; struct access *tmp; - - tmp = (struct access *) mush_malloc(sizeof(struct access), "struct_access"); - if (!tmp) - return 0; + tmp = mush_malloc(sizeof(struct access), "sitelock.rule"); + if (!tmp) { + static const char *memerr = "unable to allocate memory"; + if (errptr) + *errptr = memerr; + return NULL; + } tmp->who = who; tmp->can = can; tmp->cant = cant; - strcpy(tmp->host, host); + mush_strncpy(tmp->host, host, BUFFER_LEN); if (comment) - strcpy(tmp->comment, comment); + mush_strncpy(tmp->comment, comment, BUFFER_LEN); else tmp->comment[0] = '\0'; tmp->next = NULL; + if (can & ACS_REGEXP) { + int erroffset = 0; + tmp->re = pcre_compile(host, 0, errptr, &erroffset, tables); + if (!tmp->re) { + mush_free(tmp, "sitelock.rule"); + return NULL; + } + } else + tmp->re = NULL; + + return tmp; +} + +static bool +add_access_node(const char *host, dbref who, uint32_t can, + uint32_t cant, const char *comment, const char **errptr) +{ + struct access *end, *tmp; + + tmp = sitelock_alloc(host, who, can, cant, comment, errptr); + if (!tmp) + return false; + if (!access_top) { /* Add to the beginning */ access_top = tmp; @@ -147,25 +179,25 @@ end = end->next; end->next = tmp; } - - return 1; + return true; } /** Read the access.cnf file. * Initialize the access rules linked list and read in the access.cnf file. - * Return 1 if successful, 0 if not + * \return true if successful, false if not */ -int +bool read_access_file(void) { FILE *fp; char buf[BUFFER_LEN]; char *p; - int can, cant; + uint32_t can, cant; int retval; dbref who; char *comment; + const char *errptr = NULL; if (access_top) { /* We're reloading the file, so we've got to delete any current @@ -197,8 +229,10 @@ comment = NULL; /* Is this the @sitelock entry? */ if (!strncasecmp(p, "@sitelock", 9)) { - if (!add_access_node("@sitelock", AMBIGUOUS, ACS_SITELOCK, 0, "")) - do_log(LT_ERR, GOD, GOD, T("Failed to add sitelock node!")); + if (!add_access_node("@sitelock", AMBIGUOUS, ACS_SITELOCK, 0, "", + &errptr)) + do_log(LT_ERR, GOD, GOD, T("Failed to add sitelock node: %s"), + errptr); } else { if ((comment = strchr(p, '#'))) { *comment++ = '\0'; @@ -213,8 +247,9 @@ if (!parse_access_options(p, &who, &can, &cant, NOTHING)) /* Nothing listed, so assume we can't do anything! */ cant = ACS_DEFAULT; - if (!add_access_node(buf, who, can, cant, comment)) - do_log(LT_ERR, GOD, GOD, T("Failed to add access node!")); + if (!add_access_node(buf, who, can, cant, comment, &errptr)) + do_log(LT_ERR, GOD, GOD, T("Failed to add access node: %s"), + errptr); } } } @@ -316,8 +351,8 @@ * flags (can't register, isn't suspect) * \endverbatim */ -int -site_can_access(const char *hname, int flag, dbref who) +bool +site_can_access(const char *hname, uint32_t flag, dbref who) { struct access *ap; acsflag *c; @@ -332,11 +367,11 @@ for (ap = access_top; ap; ap = ap->next) { if (!(ap->can & ACS_SITELOCK) && ((ap->can & ACS_REGEXP) - ? (quick_regexp_match(ap->host, hname, 0) - || (p && quick_regexp_match(ap->host, p, 0)) + ? (qcomp_regexp_match(ap->re, hname) + || (p && qcomp_regexp_match(ap->re, p)) #ifdef FORCE_IPV4 - || quick_regexp_match(ip4_to_ip6(ap->host), hname, 0) - || (p && quick_regexp_match(ip4_to_ip6(ap->host), p, 0)) + || qcomp_regexp_match(ip4_to_ip6(ap->re), hname) + || (p && qcomp_regexp_match(ip4_to_ip6(ap->re), p)) #endif ) : (quick_wild(ap->host, hname) @@ -402,11 +437,11 @@ (*rulenum)++; if (!(ap->can & ACS_SITELOCK) && ((ap->can & ACS_REGEXP) - ? (quick_regexp_match(ap->host, hname, 0) - || (p && quick_regexp_match(ap->host, p, 0)) + ? (qcomp_regexp_match(ap->re, hname) + || (p && qcomp_regexp_match(ap->re, p)) #ifdef FORCE_IPV4 - || quick_regexp_match(ip4_to_ip6(ap->host), hname, 0) - || (p && quick_regexp_match(ip4_to_ip6(ap->host), p, 0)) + || qcomp_regexp_match(ip4_to_ip6(ap->host), hname) + || (p && qcomp_regexp_match(ip4_to_ip6(ap->host), p)) #endif ) : (quick_wild(ap->host, hname) @@ -433,7 +468,7 @@ * This function provides an appealing display of an access rule * in the list. */ -int +void format_access(struct access *ap, int rulenum, dbref who __attribute__ ((__unused__)), char *buff, char **bp) { @@ -474,7 +509,6 @@ } else { safe_str(T("No matching access rule"), buff, bp); } - return 0; } @@ -493,29 +527,28 @@ * Build an appropriate comment based on the player and date * \endverbatim */ -int -add_access_sitelock(dbref player, const char *host, dbref who, int can, - int cant) +bool +add_access_sitelock(dbref player, const char *host, dbref who, uint32_t can, + uint32_t cant) { struct access *end; struct access *tmp; + const char *errptr = NULL; - tmp = (struct access *) mush_malloc(sizeof(struct access), "struct_access"); - if (!tmp) - return 0; - tmp->who = who; - tmp->can = can; - tmp->cant = cant; - strcpy(tmp->host, host); - snprintf(tmp->comment, sizeof tmp->comment, - "By %s(#%d) on %s", Name(player), player, show_time(mudtime, 0)); - tmp->next = NULL; + tmp = sitelock_alloc(host, who, can, cant, "", &errptr); + if (!tmp) { + notify_format(player, T("Unable to add sitelock entry: %s"), errptr); + return false; + } + if (!access_top) { /* Add to the beginning, but first add a sitelock marker */ - if (!add_access_node("@sitelock", AMBIGUOUS, ACS_SITELOCK, 0, "")) + if (!add_access_node("@sitelock", AMBIGUOUS, ACS_SITELOCK, 0, "", &errptr)) { + notify_format(player, T("Unable to add @sitelock separator: %s"), errptr); return 0; + } access_top->next = tmp; } else { end = access_top; @@ -524,8 +557,12 @@ /* Now, either we're at the sitelock or the end */ if (end->can != ACS_SITELOCK) { /* We're at the end and there's no sitelock marker. Add one */ - if (!add_access_node("@sitelock", AMBIGUOUS, ACS_SITELOCK, 0, "")) + if (!add_access_node("@sitelock", AMBIGUOUS, ACS_SITELOCK, 0, "", + &errptr)) { + notify_format(player, T("Unable to add @sitelock separator: %s"), + errptr); return 0; + } end = end->next; } else { /* We're in the middle, so be sure we keep the list linked */ @@ -539,11 +576,9 @@ /** Remove an access rule from the linked list. * \param pattern access rule host pattern to match. * \return number of rule removed. - * \verbatim * This function removes an access rule from the list. * Only rules that appear after the "@sitelock" rule can be * removed with this function. - * \endverbatim */ int remove_access_sitelock(const char *pattern) @@ -563,7 +598,9 @@ next = ap->next; if (strcasecmp(pattern, ap->host) == 0) { n++; - mush_free(ap, "struct_access"); + if (ap->re) + free(ap->re); + mush_free(ap, "sitelock.rule"); if (prev) prev->next = next; else @@ -585,7 +622,9 @@ ap = access_top; while (ap) { next = ap->next; - mush_free((Malloc_t) ap, "struct_access"); + if (ap->re) + free(ap->re); + mush_free(ap, "sitelock.rule"); ap = next; } access_top = NULL; @@ -648,8 +687,8 @@ * This makes a copy of the options string, so it's not modified. */ int -parse_access_options(const char *opts, dbref *who, int *can, int *cant, - dbref player) +parse_access_options(const char *opts, dbref *who, uint32_t * can, + uint32_t * cant, dbref player) { char myopts[BUFFER_LEN]; char *p; Index: src/local.dst =================================================================== --- src/local.dst (.../p4) (revision 1119) +++ src/local.dst (.../p5) (revision 1119) @@ -46,7 +46,7 @@ /* Initial size of this hashtable should be close to the number of * add_config()'s you plan to do. */ - hashinit(&local_options, 4, sizeof(PENNCONF)); + hashinit(&local_options, 4); #ifdef EXAMPLE /* Call add_config for each config parameter you want to add. Index: src/funmisc.c =================================================================== --- src/funmisc.c (.../p4) (revision 1119) +++ src/funmisc.c (.../p5) (revision 1119) @@ -82,7 +82,18 @@ orator = saved_orator; } +FUNCTION(fun_message) +{ + int i; + char *argv[10]; + for (i = 0; (i + 3) < nargs; i++) { + argv[i] = args[i + 3]; + } + + do_message_list(executor, executor, args[0], args[2], args[1], 0, i, argv); +} + /* ARGSUSED */ FUNCTION(fun_oemit) { Index: src/speech.c =================================================================== --- src/speech.c (.../p4) (revision 1119) +++ src/speech.c (.../p5) (revision 1119) @@ -12,6 +12,7 @@ #include #include #include +#include #include "conf.h" #include "externs.h" #include "ansi.h" @@ -399,6 +400,63 @@ mush_free((Malloc_t) tbuf, "string"); } +/** Send an @message to a list of dbrefs, using to format it + * if present. + * The list is destructively modified. + * \param player the enactor. + * \param list the list of players to pemit to, destructively modified. + * \param attrib the ufun attribute to use to format the message. + * \param message the default message. + * \param flags PEMIT_* flags + * \param numargs The number of arguments for the ufun. + * \param ... The arguments for the ufun. + */ +void +do_message_list(dbref player, dbref enactor, char *list, char *attrname, + char *message, int flags, int numargs, char *argv[]) +{ + const char *start; + char *current; + char plist[BUFFER_LEN], *pp; + dbref victim; + int first = 0; + ATTR *attrib; + + start = list; + + pp = plist; + *pp = '\0'; + + while (start && *start) { + current = next_in_list(&start); + if (*current == '*') + current = current + 1; + victim = noisy_match_result(player, current, NOTYPE, MAT_EVERYTHING); + if (GoodObject(victim) && !IsGarbage(victim)) { + /* Can we evaluate its ? */ + + attrib = atr_get(victim, upcasestr(attrname)); + if (attrib && CanEvalAttr(player, victim, attrib)) { + if (flags & PEMIT_SPOOF) { + messageformat(victim, attrname, enactor, NA_SPOOF, numargs, argv); + } else { + messageformat(victim, attrname, enactor, 0, numargs, argv); + } + } else { + if (!first) { + safe_chr(' ', plist, &pp); + } + first = 0; + safe_dbref(victim, plist, &pp); + } + } + } + if (plist[0]) { + *pp = '\0'; + do_pemit_list(enactor, plist, message, flags); + } +} + /** Send a message to a list of dbrefs. To avoid repeated generation * of the NOSPOOF string, we set it up the first time we encounter * something Nospoof, and then check for it thereafter. @@ -631,17 +689,53 @@ * \retval 0 The default message was sent. */ int +vmessageformat(dbref player, const char *attribute, dbref enactor, int flags, + int numargs, ...) +{ + va_list ap; + char *s; + int i; + char *argv[10]; + + va_start(ap, numargs); + + for (i = 0; i < 10; i++) { + if (i < numargs) { + /* Pop another char * off the stack. */ + s = va_arg(ap, char *); + argv[i] = s; + } else { + argv[i] = NULL; + } + } + va_end(ap); + + return messageformat(player, attribute, enactor, flags, numargs, argv); +} + +/** messageformat. This is the wrapper that makes calling PAGEFORMAT, + * CHATFORMAT, etc easy. + * + * \param player The victim to call it on. + * \param attribute The attribute on the player to call. + * \param enactor The enactor who caused the message. + * \param flags NA_INTER_HEAR and NA_SPOOF + * \param arg0 First argument + * \param arg4 Last argument. + * \retval 1 The player had the fooformat attribute. + * \retval 0 The default message was sent. + */ +int messageformat(dbref player, const char *attribute, dbref enactor, int flags, - const char *arg0, const char *arg1, const char *arg2, - const char *arg3, const char *arg4, const char *arg5) + int numargs, char *argv[]) { - const char *argv[6] = { arg0, arg1, arg2, arg3, arg4, arg5 }; /* It's only static because I expect this thing to get * called a LOT, so it may or may not save time. */ static char messbuff[BUFFER_LEN]; *messbuff = '\0'; - if (!call_attrib(player, attribute, argv, 6, messbuff, enactor, NULL)) { + if (!call_attrib(player, attribute, (const char **) argv, numargs, + messbuff, enactor, NULL)) { /* We have a returned value. Notify the player. */ if (*messbuff) notify_anything(enactor, na_one, &player, ns_esnotify, flags, messbuff); @@ -685,7 +779,7 @@ ATTR *a; char *alias; - tp2 = tbuf2 = (char *) mush_malloc(BUFFER_LEN, "string"); + tp2 = tbuf2 = (char *) mush_malloc(BUFFER_LEN, "page_buff"); if (!tbuf2) mush_panic("Unable to allocate memory in do_page"); @@ -709,19 +803,19 @@ a = atr_get_noparent(player, "LASTPAGED"); if (!a || !*((hp = head = safe_atr_value(a)))) { notify(player, T("You haven't paged anyone since connecting.")); - mush_free((Malloc_t) tbuf2, "string"); + mush_free((Malloc_t) tbuf2, "page_buff"); return; } if (!message || !*message) { notify_format(player, T("You last paged %s."), head); - mush_free((Malloc_t) tbuf2, "string"); + mush_free((Malloc_t) tbuf2, "page_buff"); if (hp) free((Malloc_t) hp); return; } } - tp = tbuf = (char *) mush_malloc(BUFFER_LEN, "string"); + tp = tbuf = (char *) mush_malloc(BUFFER_LEN, "page_buff"); if (!tbuf) mush_panic("Unable to allocate memory in do_page"); @@ -790,8 +884,8 @@ * anyone, this looks like a spam attack. */ if (gcount == 99) { notify(player, T("You're trying to page too many people at once.")); - mush_free((Malloc_t) tbuf, "string"); - mush_free((Malloc_t) tbuf2, "string"); + mush_free((Malloc_t) tbuf, "page_buff"); + mush_free((Malloc_t) tbuf2, "page_buff"); if (hp) free((Malloc_t) hp); return; @@ -807,8 +901,8 @@ if (!gcount) { /* Well, that was a total waste of time. */ - mush_free((Malloc_t) tbuf, "string"); - mush_free((Malloc_t) tbuf2, "string"); + mush_free((Malloc_t) tbuf, "page_buff"); + mush_free((Malloc_t) tbuf2, "page_buff"); if (hp) free((Malloc_t) hp); return; @@ -817,8 +911,8 @@ /* Can the player afford to pay for this thing? */ if (!payfor(player, PAGE_COST * gcount)) { notify_format(player, T("You don't have enough %s."), MONIES); - mush_free((Malloc_t) tbuf, "string"); - mush_free((Malloc_t) tbuf2, "string"); + mush_free((Malloc_t) tbuf, "page_buff"); + mush_free((Malloc_t) tbuf2, "page_buff"); if (hp) free((Malloc_t) hp); return; @@ -828,10 +922,10 @@ * actually going to someone. We're in this for keeps now. */ /* Evaluate the message if we need to. */ - if (noeval) + if (noeval) { msgbuf = NULL; - else { - mb = msgbuf = (char *) mush_malloc(BUFFER_LEN, "string"); + } else { + mb = msgbuf = (char *) mush_malloc(BUFFER_LEN, "page_buff"); if (!msgbuf) mush_panic("Unable to allocate memory in do_page"); @@ -935,24 +1029,30 @@ } *tp2 = '\0'; for (i = 0; i < gcount; i++) { - if (!messageformat(good[i], "PAGEFORMAT", player, 0, message, - (key == 1) ? (*gap ? ":" : ";") : "\"", - (alias && *alias) ? alias : "", tbuf2, NULL, NULL)) { - /* Player doesn't have Pageformat, or it eval'd to 0 */ - if (!IsPlayer(player) && Nospoof(good[i])) { - notify_format(good[i], "[#%d] %s", player, tbuf); - } else { - notify(good[i], tbuf); + if (!IsPlayer(player) && Nospoof(good[i])) { + if (msgbuf == NULL) { + msgbuf = mush_malloc(BUFFER_LEN, "page buffer"); } + snprintf(msgbuf, BUFFER_LEN, "[#%d] %s", player, tbuf); + /* Swap tbuf and msgbuf */ + tp = tbuf; + tbuf = msgbuf; + msgbuf = tbuf; } + if (!vmessageformat(good[i], "PAGEFORMAT", player, 0, 5, message, + (key == 1) ? (*gap ? ":" : ";") : "\"", + (alias && *alias) ? alias : "", tbuf2, tbuf)) { + /* Player doesn't have Pageformat, or it eval'd to 0 */ + notify(good[i], tbuf); + } page_return(player, good[i], "Idle", "IDLE", NULL); } - mush_free((Malloc_t) tbuf, "string"); - mush_free((Malloc_t) tbuf2, "string"); + mush_free((Malloc_t) tbuf, "page_buff"); + mush_free((Malloc_t) tbuf2, "page_buff"); if (msgbuf) - mush_free((Malloc_t) msgbuf, "string"); + mush_free((Malloc_t) msgbuf, "page_buff"); if (hp) free((Malloc_t) hp); } Index: src/bsd.c =================================================================== --- src/bsd.c (.../p4) (revision 1119) +++ src/bsd.c (.../p5) (revision 1119) @@ -24,6 +24,7 @@ #include #include #include +#include #define EINTR WSAEINTR #define EWOULDBLOCK WSAEWOULDBLOCK #define MAXHOSTNAMELEN 32 @@ -277,7 +278,7 @@ #ifndef BOOLEXP_DEBUGGING #ifdef WIN32SERVICES void shutdown_checkpoint(void); -void mainthread(int argc, char **argv); +int mainthread(int argc, char **argv); #else int main(int argc, char **argv); #endif @@ -386,7 +387,7 @@ /* Under WIN32, MUSH is a "service", so we just start a thread here. * The real "main" is in win32/services.c */ -void +int mainthread(int argc, char **argv) #else /** The main function. @@ -410,7 +411,7 @@ if (getuid() == 0) { fputs("Please run the server as another user.\n", stderr); fputs("PennMUSH will not run as root as a security measure.\n", stderr); - return 1; + return EXIT_FAILURE; } /* Add suid-root checks here. */ #endif @@ -1401,7 +1402,6 @@ d->cmds = 0; d->hide = 0; d->doing[0] = '\0'; - d->mailp = NULL; welcome_user(d); } @@ -1490,7 +1490,6 @@ d->cmds = 0; d->hide = 0; d->doing[0] = '\0'; - d->mailp = NULL; mush_strncpy(d->addr, addr, 100); d->addr[99] = '\0'; mush_strncpy(d->ip, ip, 100); @@ -1596,7 +1595,11 @@ for (qp = &d->output.head; ((cur = *qp) != NULL);) { #ifdef HAVE_WRITEV - if (cur->nxt && !d->ssl) { + if (cur->nxt +#ifdef HAVE_SSL + && !d->ssl +#endif + ) { /* If there's more than one pending block, try to send up to 10 at once with writev(). Doesn't work for SSL connections, and if there's only one block waiting to go out, just use @@ -2300,7 +2303,6 @@ return 0; } } - d->mailp = find_exact_starting_point(player); /* check to see if this is a reconnect and also set DARK status */ is_hidden = Can_Hide(player) && Dark(player); @@ -2567,30 +2569,34 @@ close_sockets(void) { DESC *d, *dnext; + const char *shutmsg; + int shutlen; + shutmsg = T(shutdown_message); + shutlen = strlen(shutmsg); + for (d = descriptor_list; d; d = dnext) { dnext = d->next; +#ifdef HAS_OPENSSL if (!d->ssl) { +#endif #ifdef HAVE_WRITEV struct iovec byebye[2]; - byebye[0].iov_base = (char *) T(shutdown_message); - byebye[0].iov_len = strlen(byebye[0].iov_base); - byebye[1].iov_base = "\r\n"; + byebye[0].iov_base = (char *) shutmsg; + byebye[0].iov_len = shutlen; + byebye[1].iov_base = (char *) "\r\n"; byebye[1].iov_len = 2; writev(d->descriptor, byebye, 2); #else - const char *shutmsg = T(shutdown_message); - send(d->descriptor, shutmsg, strlen(shutmsg), 0); - send(d->descriptor, "\r\n", 2, 0); + send(d->descriptor, shutmsg, shutlen, 0); + send(d->descriptor, (char *) "\r\n", 2, 0); #endif - } #ifdef HAS_OPENSSL - if (d->ssl) { + } else { int offset; - const char *shutmsg = T(shutdown_message); offset = 0; ssl_write(d->ssl, d->ssl_state, 0, 1, (uint8_t *) shutmsg, - strlen(shutmsg), &offset); + shutlen, &offset); offset = 0; ssl_write(d->ssl, d->ssl_state, 0, 1, (uint8_t *) "\r\n", 2, &offset); ssl_close_connection(d->ssl); @@ -3614,11 +3620,28 @@ FUNCTION(fun_nwho) { DESC *d; + dbref victim; int count = 0; - int powered = (*(called_as + 1) != 'M'); + int powered = ((*(called_as + 1) != 'M') && Priv_Who(executor)); + if (nargs && args[0] && *args[0]) { + /* An argument was given. Find the victim and choose the lowest + * perms possible */ + if (!powered) { + safe_str(T(e_perm), buff, bp); + return; + } + if ((victim = noisy_match_result(executor, args[0], NOTYPE, + MAT_EVERYTHING)) == 0) { + safe_str(T(e_notvis), buff, bp); + return; + } + if (!Priv_Who(victim)) + powered = 0; + } + DESC_ITER_CONN(d) { - if (!Hidden(d) || (powered && Priv_Who(executor))) { + if (!Hidden(d) || powered) { count++; } } @@ -4258,71 +4281,6 @@ } -/** Return the mailp of the player closest in db# to player, - * or NULL if there's nobody on-line. - * In the current mail system, mail is stored in a linked list, sorted - * by recipient, which makes the most common operations (listing and reading - * your mail) fast. When a player first connects, we store (on the - * mailp element of their descriptor) a pointer to the beginning of - * their part of the linked list. Rather than search the whole linked - * list to find this location, we look at the mailp's of all the other - * connected players, and find the mailp of the player whose dbref - * is closest to the connecting player, and start our search from that - * point. This scales up nicely - as a mushes get larger, the linked - * list gets larger, but the more people connected at once, the faster - * the search for a newly connecting player's first mail. - * \param player player whose db# we want to get near. - * \return pointer to first mail of connected player with db# closest to - * player. - */ -MAIL * -desc_mail(dbref player) -{ - DESC *d; - int i; - int diff = db_top; - static MAIL *mp; - mp = NULL; - DESC_ITER_CONN(d) { - i = abs(d->player - player); - if (i == 0) - return d->mailp; - if ((i < diff) && d->mailp) { - diff = i; - mp = d->mailp; - } - } - return mp; -} - -/** Set a player's mail position on all their descriptors. - * \param player player to set mail position for. - * \param mp pointer to first mail in their list. - */ -void -desc_mail_set(dbref player, MAIL *mp) -{ - DESC *d; - DESC_ITER_CONN(d) { - if (d->player == player) - d->mailp = mp; - } -} - -/** Clear mail positions on all descriptors. Called from do_mail_nuke(). - */ -void -desc_mail_clear(void) -{ - DESC *d; - DESC_ITER_CONN(d) { - d->mailp = NULL; - } -} - - - - #ifdef SUN_OS /* SunOS's implementation of stdio breaks when you get a file descriptor * greater than 128. Brain damage, brain damage, brain damage! @@ -4586,7 +4544,6 @@ d->raw_input = NULL; d->raw_input_at = NULL; d->quota = options.starting_quota; - d->mailp = NULL; #ifdef HAS_OPENSSL d->ssl = NULL; d->ssl_state = 0; @@ -4630,9 +4587,6 @@ strcpy(poll_msg, getstring_noalloc(f)); globals.first_start_time = getref(f); globals.reboot_count = getref(f) + 1; - DESC_ITER_CONN(d) { - d->mailp = find_exact_starting_point(d->player); - } #ifdef HAS_OPENSSL if (SSLPORT) { sslsock = make_socket(SSLPORT, SOCK_STREAM, NULL, NULL, SSL_IP_ADDR); @@ -4698,7 +4652,7 @@ end_all_logs(); #ifndef WIN32 { - char *args[6]; + const char *args[6]; int n = 0; args[n++] = saved_argv[0]; @@ -4710,7 +4664,7 @@ args[n++] = confname; args[n++] = NULL; - execv(saved_argv[0], args); + execv(saved_argv[0], (char **) args); } #else execl("pennmush.exe", "pennmush.exe", "/run", NULL); Index: src/funstr.c =================================================================== --- src/funstr.c (.../p4) (revision 1119) +++ src/funstr.c (.../p5) (revision 1119) @@ -12,6 +12,7 @@ #include #include #include +#include #include "conf.h" #include "externs.h" #include "ansi.h" @@ -388,7 +389,7 @@ src = parse_ansi_string(args[2]); - ansi_string_insert(dst, pos, src, 0, src->len); + ansi_string_insert(dst, pos, src); safe_ansi_string(dst, 0, dst->len, buff, bp); @@ -458,8 +459,8 @@ dst = parse_ansi_string(args[0]); src = parse_ansi_string(args[3]); - ansi_string_delete(dst, start, len); - ansi_string_insert(dst, start, src, 0, src->len); + ansi_string_replace(dst, start, len, src); + safe_ansi_string(dst, 0, dst->len, buff, bp); free_ansi_string(dst); free_ansi_string(src); @@ -935,45 +936,20 @@ /* ARGSUSED */ FUNCTION(fun_scramble) { - int n, i, j; - ansi_string *as; - ansi_string *dst; - int pos[BUFFER_LEN]; - char tmp[BUFFER_LEN]; + ansi_string *as, *dst; if (!*args[0]) return; - /* Set up the new ansi_string */ - memset(tmp, 0, BUFFER_LEN); - dst = parse_ansi_string(tmp); - - /* Read the current one */ as = parse_ansi_string(args[0]); - - for (i = 0; i < as->len; i++) - pos[i] = i; - - n = as->len; - for (i = 0; i < n; i++) { - int t; - j = get_random_long(0, n - 1); - t = pos[i]; - pos[i] = pos[j]; - pos[j] = t; + dst = scramble_ansi_string(as); + if (dst) { + free_ansi_string(as); + as = dst; } - for (i = 0; i < n; i++) { - ansi_string_insert(dst, dst->len, as, pos[i], 1); - if ((i % 100) == 99) { - optimize_ansi_string(dst); - } - } + safe_ansi_string(as, 0, as->len, buff, bp); free_ansi_string(as); - - /* Now optimize the ansi string */ - safe_ansi_string(dst, 0, dst->len, buff, bp); - free_ansi_string(dst); } /* ARGSUSED */ @@ -1232,7 +1208,7 @@ /* This function simply returns a decompose'd version of * the included string, such that * s(decompose(str)) == str, down to the last space, tab, - * and newline. Except for ansi. */ + * and newline. */ safe_str(decompose_str(args[0]), buff, bp); } @@ -1275,7 +1251,7 @@ */ char sep; - int trim; + enum trim_style { TRIM_LEFT, TRIM_RIGHT, TRIM_BOTH } trim; int trim_style_arg, trim_char_arg; ansi_string *as; int i; @@ -1302,19 +1278,21 @@ /* If a trim style is provided, it must be the third argument. */ if (nargs >= trim_style_arg) { - switch (DOWNCASE(*args[trim_style_arg - 1])) { + switch (*args[trim_style_arg - 1]) { case 'l': - trim = 1; + case 'L': + trim = TRIM_LEFT; break; case 'r': - trim = 2; + case 'R': + trim = TRIM_RIGHT; break; default: - trim = 3; + trim = TRIM_BOTH; break; } } else - trim = 3; + trim = TRIM_BOTH; /* We will never need to check for buffer length overrunning, since * we will always get a smaller string. Thus, we can copy at the @@ -1323,7 +1301,7 @@ /* If necessary, skip over the leading stuff. */ as = parse_ansi_string(args[0]); - if (trim != 2) { + if (trim != TRIM_RIGHT) { for (i = 0; i < as->len; i++) { if (as->text[i] != sep) break; @@ -1331,7 +1309,7 @@ } } /* Cut off the trailing stuff, if appropriate. */ - if ((trim != 1)) { + if ((trim != TRIM_LEFT)) { for (i = as->len - 1; i >= 0; i--) { if (as->text[i] != sep) break; @@ -1507,21 +1485,21 @@ nlen--; repl = parse_ansi_string(args[i + 1]); if (strcmp(needle, "$") == 0) { - ansi_string_insert(orig, orig->len, repl, 0, repl->len); + ansi_string_insert(orig, orig->len, repl); } else if (strcmp(needle, "^") == 0) { - ansi_string_insert(orig, 0, repl, 0, repl->len); + ansi_string_insert(orig, 0, repl); } else if (nlen == 0) { /* Annoying. Stick repl between each character */ /* Since this is inserts, we're working *backwards* */ for (j = orig->len - 1; j > 0; j--) { - ansi_string_insert(orig, j, repl, 0, repl->len); + ansi_string_insert(orig, j, repl); } } else { search = orig->text; /* Find each occurrence */ while ((ptr = strstr(search, needle)) != NULL) { /* Perform the replacement */ - ansi_string_replace(orig, ptr - orig->text, nlen, repl, 0, repl->len); + ansi_string_replace(orig, ptr - orig->text, nlen, repl); search = ptr + repl->len; } } Index: src/sort.c =================================================================== --- src/sort.c (.../p4) (revision 1119) +++ src/sort.c (.../p5) (revision 1119) @@ -229,10 +229,15 @@ char *ptr; /**< NULL except for sortkey */ char *val; /**< The string this is */ dbref db; /**< dbref (default 0, bad is -1) */ - char *str; /**< string comparisons */ - int num; /**< integer comparisons */ - NVAL numval; /**< float comparisons */ - int freestr; /**< free str on completion */ + union { + struct { + char *s; /**< string comparisons */ + bool freestr; /**< free str on completion */ + } str; + int num; /**< integer comparisons */ + NVAL numval; /**< float comparisons */ + time_t tm; /**< time comparisions */ + } memo; }; @@ -247,76 +252,78 @@ { size_t len; if (strchr(rec->val, ESC_CHAR) || strchr(rec->val, TAG_START)) { - rec->str = mush_strdup(remove_markup(rec->val, &len), "genrecord"); - rec->freestr = 1; + rec->memo.str.s = mush_strdup(remove_markup(rec->val, &len), "genrecord"); + rec->memo.str.freestr = 1; } else { - rec->str = rec->val; + rec->memo.str.s = rec->val; + rec->memo.str.freestr = 0; } } GENRECORD(gen_dbref) { - rec->num = qparse_dbref(rec->val); + rec->memo.num = qparse_dbref(rec->val); } GENRECORD(gen_num) { - rec->num = parse_integer(rec->val); + rec->memo.num = parse_integer(rec->val); } GENRECORD(gen_float) { - rec->numval = parse_number(rec->val); + rec->memo.numval = parse_number(rec->val); } #define RealGoodObject(x) (GoodObject(x) && !IsGarbage(x)) GENRECORD(gen_db_name) { - rec->str = (char *) ""; + rec->memo.str.s = (char *) ""; if (RealGoodObject(rec->db)) - rec->str = (char *) Name(rec->db); + rec->memo.str.s = (char *) Name(rec->db); + rec->memo.str.freestr = 0; } GENRECORD(gen_db_idle) { - rec->num = -1; + rec->memo.num = -1; if (RealGoodObject(rec->db)) { if (Priv_Who(player)) - rec->num = least_idle_time_priv(rec->db); + rec->memo.num = least_idle_time_priv(rec->db); else - rec->num = least_idle_time(rec->db); + rec->memo.num = least_idle_time(rec->db); } } GENRECORD(gen_db_conn) { - rec->num = -1; + rec->memo.num = -1; if (RealGoodObject(rec->db)) { if (Priv_Who(player)) - rec->num = most_conn_time_priv(rec->db); + rec->memo.num = most_conn_time_priv(rec->db); else - rec->num = most_conn_time(rec->db); + rec->memo.num = most_conn_time(rec->db); } } GENRECORD(gen_db_ctime) { if (RealGoodObject(rec->db)) - rec->num = CreTime(rec->db); + rec->memo.tm = CreTime(rec->db); } GENRECORD(gen_db_owner) { if (RealGoodObject(rec->db)) - rec->num = Owner(rec->db); + rec->memo.num = Owner(rec->db); } GENRECORD(gen_db_loc) { - rec->num = -1; + rec->memo.num = -1; if (RealGoodObject(rec->db) && Can_Locate(player, rec->db)) { - rec->num = Location(rec->db); + rec->memo.num = Location(rec->db); } } @@ -327,11 +334,12 @@ static char *defstr = (char *) ""; const char *ptr; - rec->str = defstr; + rec->memo.str.s = defstr; + rec->memo.str.freestr = 0; if (RealGoodObject(rec->db) && sortflags && *sortflags && (ptr = do_get_attrib(player, rec->db, sortflags)) != NULL) { - rec->str = mush_strdup(ptr, "genrecord"); - rec->freestr = 1; + rec->memo.str.s = mush_strdup(ptr, "genrecord"); + rec->memo.str.freestr = 1; } } @@ -362,21 +370,49 @@ /* If I could, I'd let sort() toss out non-existant dbrefs * Instead, sort stuffs them into a jumble at the end. */ -#define Compare(r,x,y) \ - ((x->db < 0 || y->db < 0) ? \ - ((x->db < 0 && y->db < 0) ? 0 : (x->db < 0 ? 2 : -2)) \ - : ((fabs((double)r) > 0.000000001) ? (r < 0 ? -2 : 2) \ - : (x->db == y->db ? 0 : (x->db < y->db ? -1 : 1)) \ - ) \ - ) +/* Compare a single int, > == or < 0 */ +#define Compare(i,x,y) \ + ((x->db < 0 || y->db < 0) ? \ + ((x->db < 0 && y->db < 0) ? 0 : (x->db < 0 ? 2 : -2)) \ + : ((i != 0) ? (i < 0 ? -2 : 2) \ + : (x->db == y->db ? 0 : (x->db < y->db ? -1 : 1)) \ + ) \ + ) +/* Compare a float, > == or < 0.0 */ +#define CompareF(f,x,y) \ + ((x->db < 0 || y->db < 0) ? \ + ((x->db < 0 && y->db < 0) ? 0 : (x->db < 0 ? 2 : -2)) \ + : ((fabs(f) > EPSILON) ? (f < 0.0 ? -2 : 2) \ + : (x->db == y->db ? 0 : (x->db < y->db ? -1 : 1)) \ + ) \ + ) + +/* Compare two ints */ +#define Compare2(i1,i2,x,y) \ + ((x->db < 0 || y->db < 0) ? \ + ((x->db < 0 && y->db < 0) ? 0 : (x->db < 0 ? 2 : -2)) \ + : ((i1 != i2) ? (i1 < i2 ? -2 : 2) \ + : (x->db == y->db ? 0 : (x->db < y->db ? -1 : 1)) \ + ) \ + ) + +/* Compare two doubles */ +#define Compare2F(f1,f2,x,y) \ + ((x->db < 0 || y->db < 0) ? \ + ((x->db < 0 && y->db < 0) ? 0 : (x->db < 0 ? 2 : -2)) \ + : ((fabs(f1 - f2) > EPSILON) ? (f1 < f2 ? -2 : 2) \ + : (x->db == y->db ? 0 : (x->db < y->db ? -1 : 1)) \ + ) \ + ) + static int s_comp(const void *s1, const void *s2) { const s_rec *sr1 = (const s_rec *) s1; const s_rec *sr2 = (const s_rec *) s2; int res = 0; - res = strcoll(sr1->str, sr2->str); + res = strcoll(sr1->memo.str.s, sr2->memo.str.s); return Compare(res, sr1, sr2); } @@ -386,7 +422,7 @@ const s_rec *sr1 = (const s_rec *) s1; const s_rec *sr2 = (const s_rec *) s2; int res = 0; - res = strcasecoll(sr1->str, sr2->str); + res = strcasecoll(sr1->memo.str.s, sr2->memo.str.s); return Compare(res, sr1, sr2); } @@ -395,28 +431,36 @@ { const s_rec *sr1 = (const s_rec *) s1; const s_rec *sr2 = (const s_rec *) s2; - int res = 0; - res = sr1->num - sr2->num; - return Compare(res, sr1, sr2); + return Compare2(sr1->memo.num, sr2->memo.num, sr1, sr2); } static int +tm_comp(const void *s1, const void *s2) +{ + const s_rec *sr1 = (const s_rec *) s1; + const s_rec *sr2 = (const s_rec *) s2; + double r = difftime(sr1->memo.tm, sr2->memo.tm); + return CompareF(r, sr1, sr2); +} + +static int f_comp(const void *s1, const void *s2) { const s_rec *sr1 = (const s_rec *) s1; const s_rec *sr2 = (const s_rec *) s2; - NVAL res = 0; - res = sr1->numval - sr2->numval; - return Compare(res, sr1, sr2); + return Compare2F(sr1->memo.numval, sr2->memo.numval, sr1, sr2); } typedef struct _list_type_list_ { char *name; makerecord make_record; qsort_func sorter; - int isdbs; + uint32_t flags; } list_type_list; +#define IS_DB 0x1U +#define IS_STRING 0x2U + char ALPHANUM_LIST[] = "A"; char INSENS_ALPHANUM_LIST[] = "I"; char DBREF_LIST[] = "D"; @@ -435,22 +479,22 @@ list_type_list ltypelist[] = { /* List type name, recordmaker, comparer, dbrefs? */ - {ALPHANUM_LIST, gen_alphanum, s_comp, 0}, - {INSENS_ALPHANUM_LIST, gen_alphanum, si_comp, 0}, + {ALPHANUM_LIST, gen_alphanum, s_comp, IS_STRING}, + {INSENS_ALPHANUM_LIST, gen_alphanum, si_comp, IS_STRING}, {DBREF_LIST, gen_dbref, i_comp, 0}, {NUMERIC_LIST, gen_num, i_comp, 0}, {FLOAT_LIST, gen_float, f_comp, 0}, - {DBREF_NAME_LIST, gen_db_name, s_comp, 1}, - {DBREF_NAMEI_LIST, gen_db_name, si_comp, 1}, - {DBREF_IDLE_LIST, gen_db_idle, i_comp, 1}, - {DBREF_CONN_LIST, gen_db_conn, i_comp, 1}, - {DBREF_CTIME_LIST, gen_db_ctime, i_comp, 1}, - {DBREF_OWNER_LIST, gen_db_owner, i_comp, 1}, - {DBREF_LOCATION_LIST, gen_db_loc, i_comp, 1}, - {DBREF_ATTR_LIST, gen_db_attr, s_comp, 1}, - {DBREF_ATTRI_LIST, gen_db_attr, si_comp, 1}, + {DBREF_NAME_LIST, gen_db_name, si_comp, IS_DB | IS_STRING}, + {DBREF_NAMEI_LIST, gen_db_name, si_comp, IS_DB | IS_STRING}, + {DBREF_IDLE_LIST, gen_db_idle, i_comp, IS_DB}, + {DBREF_CONN_LIST, gen_db_conn, i_comp, IS_DB}, + {DBREF_CTIME_LIST, gen_db_ctime, tm_comp, IS_DB}, + {DBREF_OWNER_LIST, gen_db_owner, i_comp, IS_DB}, + {DBREF_LOCATION_LIST, gen_db_loc, i_comp, IS_DB}, + {DBREF_ATTR_LIST, gen_db_attr, s_comp, IS_DB | IS_STRING}, + {DBREF_ATTRI_LIST, gen_db_attr, si_comp, IS_DB | IS_STRING}, /* This stops the loop, so is default */ - {NULL, gen_alphanum, s_comp, 0} + {NULL, gen_alphanum, s_comp, IS_STRING} }; char * @@ -526,8 +570,7 @@ for (i = 0; ltypelist[i].name && strcasecmp(ltypelist[i].name, sort_type); i++) ; } - s1.freestr = s2.freestr = 0; - if (ltypelist[i].isdbs) { + if (ltypelist[i].flags & IS_DB) { s1.db = parse_objid(a); s2.db = parse_objid(b); if (!RealGoodObject(s1.db)) @@ -543,10 +586,12 @@ ltypelist[i].make_record(&s1, player, ptr); ltypelist[i].make_record(&s2, player, ptr); result = ltypelist[i].sorter((const void *) &s1, (const void *) &s2); - if (s1.freestr) - mush_free(s1.str, "genrecord"); - if (s2.freestr) - mush_free(s2.str, "genrecord"); + if (ltypelist[i].flags & IS_STRING) { + if (s1.memo.str.freestr) + mush_free(s1.memo.str.s, "genrecord"); + if (s2.memo.str.freestr) + mush_free(s2.memo.str.s, "genrecord"); + } return result; } @@ -590,7 +635,7 @@ sp[i].val = keys[i]; if (strs) sp[i].ptr = strs[i]; - if (ltypelist[sorti].isdbs) { + if (ltypelist[sorti].flags & IS_DB) { sp[i].db = parse_objid(keys[i]); if (!RealGoodObject(sp[i].db)) sp[i].db = NOTHING; @@ -604,10 +649,10 @@ if (strs) { strs[i] = sp[i].ptr; } - if (sp[i].freestr) - mush_free(sp[i].str, "genrecord"); + if ((ltypelist[sorti].flags & IS_STRING) && sp[i].memo.str.freestr) + mush_free(sp[i].memo.str.s, "genrecord"); } - mush_free((Malloc_t) sp, "do_gensort"); + mush_free(sp, "do_gensort"); } typedef enum { Index: src/flags.c =================================================================== --- src/flags.c (.../p4) (revision 1119) +++ src/flags.c (.../p5) (revision 1119) @@ -797,8 +797,8 @@ { FLAGSPACE *flags; - hashinit(&htab_flagspaces, 4, sizeof(FLAGSPACE)); - flags = (FLAGSPACE *) mush_malloc(sizeof(FLAGSPACE), "flagspace"); + hashinit(&htab_flagspaces, 4); + flags = mush_malloc(sizeof(FLAGSPACE), "flagspace"); flags->name = strdup("FLAG"); flags->tab = &ptab_flag; ptab_init(&ptab_flag); Index: src/extmail.c =================================================================== --- src/extmail.c (.../p4) (revision 1119) +++ src/extmail.c (.../p5) (revision 1119) @@ -715,11 +715,7 @@ nextp = mp->next; } } - /* Clean up the player's mailp */ - if (Connected(player)) { - desc_mail_set(player, NULL); - desc_mail_set(player, find_exact_starting_point(player)); - } + set_objdata(player, "MAIL", NULL); if (command_check_byname(player, "@MAIL")) notify(player, T("MAIL: Mailbox purged.")); return; @@ -1160,12 +1156,6 @@ } } - /* If the target's mailp isn't pointing to their list, we'd better - * set it - */ - if (Connected(target)) - desc_mail_set(target, find_exact_starting_point(target)); - mdb_top++; /* notify people */ @@ -1210,7 +1200,6 @@ HEAD = TAIL = NULL; mdb_top = 0; - desc_mail_clear(); do_log(LT_ERR, 0, 0, T("** MAIL PURGE ** done by %s(#%d)."), Name(player), player); @@ -1973,11 +1962,10 @@ if (!HEAD) return NULL; - mp = desc_mail(player); + + mp = get_objdata(player, "MAIL"); if (!mp) { - /* Player is connected and has no mail, or nobody's connected who - * has mail - we have to scan the maildb. - */ + /* Player hasn't already done something that looks up their mail. */ if (HEAD->to > player) return NULL; /* No mail chain */ for (mp = HEAD; mp && (mp->to < player); mp = mp->next) ; @@ -1989,9 +1977,11 @@ while (mp && (mp->to < player)) mp = mp->next; } - if (mp && (mp->to == player)) + if (mp && (mp->to == player)) { + set_objdata(player, "MAIL", mp); return mp; - return NULL; + } else + return NULL; } @@ -2007,11 +1997,8 @@ if (!HEAD) return NULL; - mp = desc_mail(player); + mp = get_objdata(player, "MAIL"); if (!mp) { - /* Player is connected and has no mail, or nobody's connected who - * has mail - we have to scan the maildb. - */ if (HEAD->to > player) return NULL; /* No mail chain */ for (mp = TAIL; mp && (mp->to > player); mp = mp->prev) ; @@ -2561,22 +2548,24 @@ { /* Return a longer description of message flags */ static char tbuf1[BUFFER_LEN]; + char *tp; - tbuf1[0] = '\0'; + tp = tbuf1; if (Read(mp)) - strcat(tbuf1, T("Read ")); + safe_str(T("Read "), tbuf1, &tp); else - strcat(tbuf1, T("Unread ")); + safe_str(T("Unread "), tbuf1, &tp); if (Cleared(mp)) - strcat(tbuf1, T("Cleared ")); + safe_str(T("Cleared "), tbuf1, &tp); if (Urgent(mp)) - strcat(tbuf1, T("Urgent ")); + safe_str(T("Urgent "), tbuf1, &tp); if (Mass(mp)) - strcat(tbuf1, T("Mass ")); + safe_str(T("Mass "), tbuf1, &tp); if (Forward(mp)) - strcat(tbuf1, T("Fwd ")); + safe_str(T("Fwd "), tbuf1, &tp); if (Tagged(mp)) - strcat(tbuf1, T("Tagged")); + safe_str(T("Tagged"), tbuf1, &tp); + *tp = '\0'; return tbuf1; } @@ -2821,18 +2810,19 @@ return; /* Handle this now so it doesn't clutter code */ - tbuf1[0] = '\0'; + j = 0; if (flags & M_URGENT) - strcat(tbuf1, "U"); + tbuf1[j++] = 'U'; if (flags & M_FORWARD) - strcat(tbuf1, "F"); + tbuf1[j++] = 'F'; if (flags & M_REPLY) - strcat(tbuf1, "R"); + tbuf1[j++] = 'R'; + tbuf1[j] = '\0'; - arg = (char *) mush_malloc(BUFFER_LEN, "string"); - arg2 = (char *) mush_malloc(BUFFER_LEN, "string"); - arg3 = (char *) mush_malloc(BUFFER_LEN, "string"); - arg4 = (char *) mush_malloc(BUFFER_LEN, "string"); + arg = mush_malloc(BUFFER_LEN, "string"); + arg2 = mush_malloc(BUFFER_LEN, "string"); + arg3 = mush_malloc(BUFFER_LEN, "string"); + arg4 = mush_malloc(BUFFER_LEN, "string"); if (!arg4) mush_panic("Unable to allocate memory in mailfilter"); save_global_regs("filter_mail", rsave); @@ -2855,16 +2845,16 @@ process_expression(buff, &bp, &ap, player, player, player, PE_DEFAULT, PT_DEFAULT, NULL); *bp = '\0'; - free((Malloc_t) asave); + free(asave); if (*buff) { sprintf(buf, "0:%d", mailnumber); do_mail_file(player, buf, buff); } - mush_free((Malloc_t) arg, "string"); - mush_free((Malloc_t) arg2, "string"); - mush_free((Malloc_t) arg3, "string"); - mush_free((Malloc_t) arg4, "string"); + mush_free(arg, "string"); + mush_free(arg2, "string"); + mush_free(arg3, "string"); + mush_free(arg4, "string"); restore_global_env("filter_mail", wsave); restore_global_regs("filter_mail", rsave); } Index: src/funcrypt.c =================================================================== --- src/funcrypt.c (.../p4) (revision 1119) +++ src/funcrypt.c (.../p5) (revision 1119) @@ -86,12 +86,14 @@ #endif } +extern char valid_ansi_codes[UCHAR_MAX + 1]; + static bool decode_base64(char *encoded, int len, char *buff, char **bp) { #ifdef HAVE_SSL BIO *bio, *b64, *bmem; - char *start, *sbp; + char *sbp; b64 = BIO_new(BIO_f_base64()); if (!b64) { @@ -110,18 +112,44 @@ bio = BIO_push(b64, bmem); - start = buff; sbp = *bp; while (true) { char decoded[BUFFER_LEN]; int dlen; + dlen = BIO_read(bio, decoded, BUFFER_LEN); - if (dlen > 0) + if (dlen > 0) { + int n; + for (n = 0; n < dlen; n++) { + if (decoded[n] == TAG_START) { + int end; + n += 1; + for (end = n; end < dlen; end++) { + if (decoded[end] == TAG_END) + break; + } + if (end == dlen || decoded[n] != MARKUP_COLOR) { + BIO_free_all(bio); + *bp = sbp; + safe_str(T("#-1 CONVERSION ERROR"), buff, bp); + return false; + } + for (; n < end; n++) { + if (!valid_ansi_codes[(unsigned char) decoded[n]]) { + BIO_free_all(bio); + *bp = sbp; + safe_str(T("#-1 CONVERSION ERROR"), buff, bp); + return false; + } + } + n = end; + } else if (!isprint((unsigned char) decoded[n])) + decoded[n] = '?'; + } safe_strl(decoded, dlen, buff, bp); - else if (dlen == 0) + } else if (dlen == 0) break; else { - buff = start; *bp = sbp; safe_str(T("#-1 CONVERSION ERROR"), buff, bp); return false; Index: src/mymalloc.c =================================================================== --- src/mymalloc.c (.../p4) (revision 1119) +++ src/mymalloc.c (.../p5) (revision 1119) @@ -35,7 +35,7 @@ #ifdef HAVE_UNISTD_H #include #endif -#ifdef WIN332 +#ifdef WIN32 #include #endif #include "options.h" @@ -412,8 +412,10 @@ ptrdiff_t pgsize; /* If objects are too big to fit in a single page, use plain free */ - if (sl->items_per_page == 0) + if (sl->items_per_page == 0) { free(obj); + return; + } /* Find the page the object is on and push it into that page's free list */ pgsize = getpagesize(); @@ -548,7 +550,7 @@ extern slab *attrib_slab, *lock_slab, *boolexp_slab, *bvm_asmnode_slab, - *bvm_strnode_slab, *hashentry_slab, *flag_slab, *player_dbref_slab, + *bvm_strnode_slab, *flag_slab, *player_dbref_slab, *command_slab, *channel_slab, *chanuser_slab, *chanlist_slab, *mail_slab, *bque_slab, *text_block_slab, *function_slab, *memcheck_slab; @@ -579,7 +581,6 @@ slab_describe(player, command_slab); slab_describe(player, flag_slab); slab_describe(player, function_slab); - slab_describe(player, hashentry_slab); #if COMPRESSION_TYPE == 1 || COMPRESSION_TYPE == 2 slab_describe(player, huffman_slab); #endif Index: src/htab.c =================================================================== --- src/htab.c (.../p4) (revision 1119) +++ src/htab.c (.../p5) (revision 1119) @@ -2,15 +2,49 @@ * \file htab.c * * \brief Hashtable routines. - * This code is largely ripped out of TinyMUSH 2.2.5, with tweaks - * to make it Penn-compatible by Trivian. * + * The hash tables here implement open addressing using cuckoo hashing + * to resolve collisions. This gives an O(1) worse-case performance + * (As well as best, of course), compared to the worst-case O(N) of + * chained or linear probing tables. + * + * A lookup will require at most X hash functions and string + * comparisions. The old tables had, with data used by Penn, 1 hash + * function and up to 6 or 7 comparisions in the worst case. Best case + * for both is 1 hash and 1 comparision. Cuckoo hashing comes out + * ahead when most lookups are successful; true for normal usage in + * Penn. * + * Insertions are more expensive, but that's okay because we do a lot + * fewer of those. + * + * For details on the technique, see + * http://citeseer.ist.psu.edu/pagh01cuckoo.html + * + * Essentially: Use X hash functions (3 for us), and when inserting, + * try them in order looking for an empty bucket. If none are found, + * use one of the full buckets for the new entry, and bump the old one + * to another one of its possible buckets. Repeat the bumping some + * bounded number of times (Otherwise the possiblity of an infinite + * loop arises), and if no empty buckets are found, try rehashing the + * entire table with a new set of hash functions. If those are + * exhausted, only then grow the table. + * + * Possible to-do: Switch the string tree implementation from using + * red-black trees to these tables. Talek choose binary trees over + * hash tables when writing strtree.c because of the better worst-case + * behavior, which was a good decision at the time. However, O(1) is + * better than O(log N). + * + * At the moment, though, insertions can be fairly costly. The growth + * factor should be able to be specified -- large for cases where fast + * inserts are important, small for cases where we want to save save. */ #include "config.h" #include "copyrite.h" #include +#include #ifdef HAVE_STDINT_H #include #endif @@ -21,17 +55,8 @@ #include "mymalloc.h" #include "confmagic.h" -static HASHENT *hash_new(HASHTAB *htab, const char *key); -static int hash_val(register const char *k, int mask); +/* The Jenkins hash: http://burtleburtle.net/bob/hash/evahash.html */ -/* --------------------------------------------------------------------------- - * hash_val: Compute hash value of a string for a hash table. - */ -/*#define NEW_HASH_FUN /**/ -#ifdef NEW_HASH_FUN - -/* This hash function adapted from http://burtleburtle.net/bob/hash/evahash.html */ - /* The mixing step */ #define mix(a,b,c) \ { \ @@ -47,15 +72,15 @@ } /* The whole new hash function */ -static int -hash_val(const char *k, int mask) +static uint32_t +jenkins_hash(const char *k, int len) { uint32_t a, b, c; /* the internal state */ - uint32_t len, length; /* how many key bytes still need mixing */ + uint32_t length; /* how many key bytes still need mixing */ static uint32_t initval = 5432; /* the previous hash, or an arbitrary value */ /* Set up the internal state */ - length = len = strlen(k); + length = len; a = b = 0x9e3779b9; /* the golden ratio; an arbitrary value */ c = initval; /* variable initialization of internal state */ @@ -105,53 +130,259 @@ } mix(a, b, c); /*-------------------------------------------- report the result */ - return c & mask; + return c; } -#else /* NEW_HASH_FUN */ -/** Compute a hash value for mask-style hashing. - * Given a null key, return 0. Otherwise, add up the numeric value - * of all the characters and return the sum modulo the size of the - * hash table. - * \param key key to hash. - * \param hashmask hash table size to use as modulus. - * \return hash value. - */ -int -hash_val(const char *key, int hashmask) +/* The Hsieh hash function. See + http://www.azillionmonkeys.com/qed/hash.html */ + +#undef get16bits +#if (defined(__GNUC__) && defined(__i386__)) || defined(__WATCOMC__) \ + || defined(_MSC_VER) || defined (__BORLANDC__) || defined (__TURBOC__) +#define get16bits(d) (*((const uint16_t *) (d))) +#endif + +#if !defined (get16bits) +#define get16bits(d) ((((uint32_t)(((const uint8_t *)(d))[1])) << 8)\ + +(uint32_t)(((const uint8_t *)(d))[0]) ) +#endif + +static uint32_t +hsieh_hash(const char *data, int len) { - int hash = 0; - const char *sp; + uint32_t hash, tmp; + int rem; - if (!key || !*key) + hash = len; + + if (len <= 0 || data == NULL) return 0; - for (sp = key; *sp; sp++) - hash = (hash << 5) + hash + *sp; - return (hash & hashmask); + + rem = len & 3; + len >>= 2; + + /* Main loop */ + for (; len > 0; len--) { + hash += get16bits(data); + tmp = (get16bits(data + 2) << 11) ^ hash; + hash = (hash << 16) ^ tmp; + data += 2 * sizeof(uint16_t); + hash += hash >> 11; + } + + /* Handle end cases */ + switch (rem) { + case 3: + hash += get16bits(data); + hash ^= hash << 16; + hash ^= data[sizeof(uint16_t)] << 18; + hash += hash >> 11; + break; + case 2: + hash += get16bits(data); + hash ^= hash << 11; + hash += hash >> 17; + break; + case 1: + hash += *data; + hash ^= hash << 10; + hash += hash >> 1; + } + + /* Force "avalanching" of final 127 bits */ + hash ^= hash << 3; + hash += hash >> 5; + hash ^= hash << 4; + hash += hash >> 17; + hash ^= hash << 25; + hash += hash >> 6; + + return hash; } -#endif /* NEW_HASH_FUN */ -/* ---------------------------------------------------------------------- - * hash_getmask: Get hash mask for mask-style hashing. +/* FNV hash: http://isthe.com/chongo/tech/comp/fnv/ */ +/* + * fnv_32_str - perform a 32 bit Fowler/Noll/Vo hash on a string + * + * input: + * str - string to hash + * hval - previous hash value or 0 if first call + * + * returns: + * 32 bit hash as a static hash type + * + * NOTE: To use the 32 bit FNV-0 historic hash, use FNV0_32_INIT as the hval + * argument on the first call to either fnv_32_buf() or fnv_32_str(). + * + * NOTE: To use the recommended 32 bit FNV-1 hash, use FNV1_32_INIT as the hval + * argument on the first call to either fnv_32_buf() or fnv_32_str(). + * + * Modified by Raevnos: Takes length arg, no hval arg, and does array-style + * iteration of the string. */ +#define FNV_32_PRIME ((Fnv32_t)0x01000193) +static inline uint32_t +fnv_hash(const char *str, int len) +{ + const unsigned char *s = (const unsigned char *) str; + int n; + uint32_t hval = 0; -/** Get the hash mask for mask-style hashing. - * Given the data size, return closest power-of-two less than that size. - * \param size data size. - * \return hash mask. - */ -int -hash_getmask(int *size) + /* + * FNV-1 hash each octet in the buffer + */ + for (n = 0; n < len; n++) { + + /* multiply by the 32 bit FNV magic prime mod 2^32 */ +#if defined(NO_FNV_GCC_OPTIMIZATION) + hval *= FNV_32_PRIME; +#else + hval += + (hval << 1) + (hval << 4) + (hval << 7) + (hval << 8) + (hval << 24); +#endif + + /* xor the bottom with the current octet */ + hval ^= (uint32_t) s[n]; + } + + /* return our new hash value */ + return hval; +} + +/* Silly old Penn hash function. */ +static uint32_t +penn_hash(const char *key, int len) { - int tsize; + uint32_t hash = 0; + int n; - if (!size || !*size) + if (!key || !*key || len == 0) return 0; + for (n = 0; n < len; n++) + hash = (hash << 5) + hash + key[n]; + return hash; +} - for (tsize = 1; tsize < *size; tsize = tsize << 1) ; - *size = tsize; - return tsize - 1; +typedef uint32_t(*hash_func) (const char *, int); + +hash_func hash_functions[] = { + hsieh_hash, + fnv_hash, + jenkins_hash, + penn_hash, + hsieh_hash, + fnv_hash, + penn_hash, + jenkins_hash +}; + +enum { NHASH_TRIES = 3, NHASH_MOD = 8 }; + +/* Return the next prime number after its arg */ +static int +next_prime_after(int val) +{ + /* Most of the first thousand primes */ + static int primes[] = { + 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, 61, 67, 71, 73, 79, + 83, 89, 97, 101, 103, 107, 109, 113, 127, 131, 137, 139, 149, 151, 157, + 163, 167, 173, 179, 181, 191, 193, 197, 199, 211, 223, 227, 229, 233, 239, + 241, 251, 257, 263, 269, 271, 277, 281, 283, 293, 307, 311, 313, 317, 331, + 337, 347, 349, 353, 359, 367, 373, 379, 383, 389, 397, 401, 409, 419, 421, + 431, 433, 439, 443, 449, 457, 461, 463, 467, 479, 487, 491, 499, 503, 509, + 521, 523, 541, 547, 557, 563, 569, 571, 577, 587, 593, 599, 601, 607, 613, + 617, 619, 631, 641, 643, 647, 653, 659, 661, 673, 677, 683, 691, 701, 709, + 719, 727, 733, 739, 743, 751, 757, 761, 769, 773, 787, 797, 809, 811, 821, + 823, 827, 829, 839, 853, 857, 859, 863, 877, 881, 883, 887, 907, 911, 919, + 929, 937, 941, 947, 953, 967, 971, 977, 983, 991, 997, 1009, 1013, 1019, + 1021, 1031, 1033, 1039, 1049, 1051, 1061, 1063, 1069, 1087, 1091, 1093, + 1097, 1103, 1109, 1117, 1123, 1129, 1151, 1153, 1163, 1171, 1181, 1187, + 1193, 1201, 1213, 1217, 1223, 1229, 1231, 1237, 1249, 1259, 1277, 1279, + 1283, 1289, 1291, 1297, 1301, 1303, 1307, 1319, 1321, 1327, 1361, 1367, + 1373, 1381, 1399, 1409, 1423, 1427, 1429, 1433, 1439, 1447, 1451, 1453, + 1459, 1471, 1481, 1483, 1487, 1489, 1493, 1499, 1511, 1523, 1531, 1543, + 1549, 1553, 1559, 1567, 1571, 1579, 1583, 1597, 1601, 1607, 1609, 1613, + 1619, 1621, 1627, 1637, 1657, 1663, 1667, 1669, 1693, 1697, 1699, 1709, + 1721, 1723, 1733, 1741, 1747, 1753, 1759, 1777, 1783, 1787, 1789, 1801, + 1811, 1823, 1831, 1847, 1861, 1867, 1871, 1873, 1877, 1879, 1889, 1901, + 1907, 1913, 1931, 1933, 1949, 1951, 1973, 1979, 1987, 1993, 1997, 1999, + 2003, 2011, 2017, 2027, 2029, 2039, 2053, 2063, 2069, 2081, 2083, 2087, + 2089, 2099, 2111, 2113, 2129, 2131, 2137, 2141, 2143, 2153, 2161, 2179, + 2203, 2207, 2213, 2221, 2237, 2239, 2243, 2251, 2267, 2269, 2273, 2281, + 2287, 2293, 2297, 2309, 2311, 2333, 2339, 2341, 2347, 2351, 2357, 2371, + 2377, 2381, 2383, 2389, 2393, 2399, 2411, 2417, 2423, 2437, 2441, 2447, + 2459, 2467, 2473, 2477, 2503, 2521, 2531, 2539, 2543, 2549, 2551, 2557, + 2579, 2591, 2593, 2609, 2617, 2621, 2633, 2647, 2657, 2659, 2663, 2671, + 2677, 2683, 2687, 2689, 2693, 2699, 2707, 2711, 2713, 2719, 2729, 2731, + 2741, 2749, 2753, 2767, 2777, 2789, 2791, 2797, 2801, 2803, 2819, 2833, + 2837, 2843, 2851, 2857, 2861, 2879, 2887, 2897, 2903, 2909, 2917, 2927, + 2939, 2953, 2957, 2963, 2969, 2971, 2999, 3001, 3011, 3019, 3023, 3037, + 3041, 3049, 3061, 3067, 3079, 3083, 3089, 3109, 3119, 3121, 3137, 3163, + 3167, 3169, 3181, 3187, 3191, 3203, 3209, 3217, 3221, 3229, 3251, 3253, + 3257, 3259, 3271, 3299, 3301, 3307, 3313, 3319, 3323, 3329, 3331, 3343, + 3347, 3359, 3361, 3371, 3373, 3389, 3391, 3407, 3413, 3433, 3449, 3457, + 3461, 3463, 3467, 3469, 3491, 3499, 3511, 3517, 3527, 3529, 3533, 3539, + 3541, 3547, 3557, 3559, 3571, 3581, 3583, 3593, 3607, 3613, 3617, 3623, + 3631, 3637, 3643, 3659, 3671, 3673, 3677, 3691, 3697, 3701, 3709, 3719, + 3727, 3733, 3739, 3761, 3767, 3769, 3779, 3793, 3797, 3803, 3821, 3823, + 3833, 3847, 3851, 3853, 3863, 3877, 3881, 3889, 3907, 3911, 3917, 3919, + 3923, 3929, 3931, 3943, 3947, 3967, 3989, 4001, 4003, 4007, 4013, 4019, + 4021, 4027, 4049, 4051, 4057, 4073, 4079, 4091, 4093, 4099, 4111, 4127, + 4129, 4133, 4139, 4153, 4157, 4159, 4177, 4201, 4211, 4217, 4219, 4229, + 4231, 4241, 4243, 4253, 4259, 4261, 4271, 4273, 4283, 4289, 4297, 4327, + 4337, 4339, 4349, 4357, 4363, 4373, 4391, 4397, 4409, 4421, 4423, 4441, + 4447, 4451, 4457, 4463, 4481, 4483, 4493, 4507, 4513, 4517, 4519, 4523, + 4547, 4549, 4561, 4567, 4583, 4591, 4597, 4603, 4621, 4637, 4639, 4643, + 4649, 4651, 4657, 4663, 4673, 4679, 4691, 4703, 4721, 4723, 4729, 4733, + 4751, 4759, 4783, 4787, 4789, 4793, 4799, 4801, 4813, 4817, 4831, 4861, + 4871, 4877, 4889, 4903, 4909, 4919, 4931, 4933, 4937, 4943, 4951, 4957, + 4967, 4969, 4973, 4987, 4993, 4999, 5003, 5009, 5011, 5021, 5023, 5039, + 5051, 5059, 5077, 5081, 5087, 5099, 5101, 5107, 5113, 5119, 5147, 5153, + 5167, 5171, 5179, 5189, 5197, 5209, 5227, 5231, 5233, 5237, 5261, 5273, + 5279, 5281, 5297, 5303, 5309, 5323, 5333, 5347, 5351, 5381, 5387, 5393, + 5399, 5407, 5413, 5417, 5419, 5431, 5437, 5441, 5443, 5449, 5471, 5477, + 5479, 5483, 5501, 5503, 5507, 5519, 5521, 5527, 5531, 5557, 5563, 5569, + 5573, 5581, 5591, 5623, 5639, 5641, 5647, 5651, 5653, 5657, 5659, 5669, + 5683, 5689, 5693, 5701, 5711, 5717, 5737, 5741, 5743, 5749, 5779, 5783, + 5791, 5801, 5807, 5813, 5821, 5827, 5839, 5843, 5849, 5851, 5857, 5861, + 5867, 5869, 5879, 5881, 5897, 5903, 5923, 5927, 5939, 5953, 5981, 5987, + 6007, 6011, 6029, 6037, 6043, 6047, 6053, 6067, 6073, 6079, 6089, 6091, + 6101, 6113, 6121, 6131, 6133, 6143, 6151, 6163, 6173, 6197, 6199, 6203, + 6211, 6217, 6221, 6229, 6247, 6257, 6263, 6269, 6271, 6277, 6287, 6299, + 6301, 6311, 6317, 6323, 6329, 6337, 6343, 6353, 6359, 6361, 6367, 6373, + 6379, 6389, 6397, 6421, 6427, 6449, 6451, 6469, 6473, 6481, 6491, 6521, + 6529, 6547, 6551, 6553, 6563, 6569, 6571, 6577, 6581, 6599, 6607, 6619, + 6637, 6653, 6659, 6661, 6673, 6679, 6689, 6691, 6701, 6703, 6709, 6719, + 6733, 6737, 6761, 6763, 6779, 6781, 6791, 6793, 6803, 6823, 6827, 6829, + 6833, 6841, 6857, 6863, 6869, 6871, 6883, 6899, 6907, 6911, 6917, 6947, + 6949, 6959, 6961, 6967, 6971, 6977, 6983, 6991, 6997, 7001, 7013, 7019, + 7027, 7039, 7043, 7057, 7069, 7079, 7103, 7109, 7121, 7127, 7129, 7151, + 7159, 7177, 7187, 7193, 7207, 7211, 7213, 7219, 7229, 7237, 7243, 7247, + 7253, 7283, 7297, 7307, 7309, 7321, 7331, 7333, 7349, 7351, 7369, 7393, + 7411, 7417, 7433, 7451, 7457, 7459, 7477, 7481, 7487, 7489, 7499, 7507, + 7517, 7523, 7529, 7537, 7541, 7547, 7549, 7559, 7561, 7573, 7577, 7583, + 7589, 7591, 7603, 7607, 7621, 7639, 7643, 7649, 7669, 7673, 7681, 7687, + 7691, 7699, 7703, 7717, 7723, 7727, 7741, 7753, 7757, 7759, 7789, 7793, + 7817, 7823, 7829, 7841, 7853, 7867, 7873, 7877, 7879, 7883, 7901, 7907, + 7919, -1 + }; + int n; + int nprimes = sizeof primes / sizeof(int); + + /* Find the first prime greater than val */ + primes[nprimes - 1] = val + 1; + n = 0; + while (primes[n] < val) + n += 1; + + n += 1; + if (n == nprimes) + /* Semi-gracefully deal with numbers larger than the table has. + Should never happen, though. */ + return val + 1; + else + return primes[n]; } /** Initialize a hashtable. @@ -160,14 +391,14 @@ * \param data_size size of an individual datum to store in the table. */ void -hash_init(HASHTAB *htab, int size, int data_size, void (*free_data) (void *)) +hash_init(HASHTAB *htab, int size, void (*free_data) (void *)) { - htab->mask = get_hashmask(&size); + size = next_prime_after(size); + htab->last_index = -1; + htab->free_data = free_data; htab->hashsize = size; - htab->entries = 0; - htab->buckets = mush_calloc(size, sizeof(HASHENT *), "hash_buckets"); - htab->entry_size = data_size; - htab->free_data = free_data; + htab->hashfunc_offset = 0; + htab->buckets = mush_calloc(size, sizeof(struct hash_bucket), "hash.buckets"); } /** Return a hashtable entry given a key. @@ -178,155 +409,156 @@ HASHENT * hash_find(HASHTAB *htab, const char *key) { - int hval, cmp; - HASHENT *hptr; + int len, n; - if (!htab->buckets) + if (!htab->entries) return NULL; - hval = hash_val(key, htab->mask); - for (hptr = htab->buckets[hval]; hptr != NULL; hptr = hptr->next) { - cmp = strcmp(key, hptr->key); - if (cmp == 0) { - return hptr; - } else if (cmp < 0) - break; + len = strlen(key); + + for (n = 0; n < NHASH_TRIES; n++) { + hash_func hash; + int hval, hashindex = (n + htab->hashfunc_offset) % NHASH_MOD; + hash = hash_functions[hashindex]; + hval = hash(key, len) % htab->hashsize; + + if (htab->buckets[hval].key && strcmp(htab->buckets[hval].key, key) == 0) + return htab->buckets + hval; } + return NULL; } -/** Return the value stored in a hash entry. - * \param entry pointer to a hash table entry. - * \return generic pointer to the stored value. - */ -void * -hash_value(HASHENT *entry) -{ - if (entry) - return entry->data; - else - return NULL; -} +enum { BUMP_LIMIT = 10 }; -/** Return the key stored in a hash entry. - * \param entry pointer to a hash table entry. - * \return pointer to the stored key. - */ -char * -hash_key(HASHENT *entry) +/** Do cuckoo hash cycling */ +static bool +hash_insert(HASHTAB *htab, const char *key, void *data) { - if (entry) - return entry->key; - else - return NULL; -} + int loop = 0, n; + struct hash_bucket bump; -/** Resize a hash table. - * \param htab pointer to hashtable. - * \param size new size. - */ -void -hash_resize(HASHTAB *htab, int size) -{ - int i; - HASHENT **oldarr; - HASHENT **newarr; - HASHENT *hent, *nent, *curr, *old; - int hval; - int osize; - int mask; + bump.key = key; + bump.data = data; - /* We don't want hashes outside these limits */ - if ((size < (1 << 4)) || (size > (1 << 20))) - return; + while (loop < BUMP_LIMIT) { + int hval, keylen; + struct hash_bucket temp; - /* Save the old data we need */ - osize = htab->hashsize; - oldarr = htab->buckets; + keylen = strlen(bump.key); - mask = htab->mask = get_hashmask(&size); + /* See if bump has any empty choices and use it */ + for (n = 0; n < NHASH_TRIES; n++) { + hash_func hash; + int hashindex = (n + htab->hashfunc_offset) % NHASH_MOD; - if (size == htab->hashsize) - return; - - htab->hashsize = size; - newarr = - (HASHENT **) mush_calloc(size, sizeof(struct hashentry *), "hash_buckets"); - htab->buckets = newarr; - - for (i = 0; i < osize; i++) { - hent = oldarr[i]; - while (hent) { - nent = hent->next; - hval = hash_val(hent->key, mask); - for (curr = newarr[hval], old = NULL; curr; old = curr, curr = curr->next) { - if (strcmp(curr->key, hent->key) > 0) - break; + hash = hash_functions[hashindex]; + hval = hash(bump.key, keylen) % htab->hashsize; + if (htab->buckets[hval].key == NULL) { + htab->buckets[hval] = bump; + return true; } - if (old) { - old->next = hent; - hent->next = curr; - } else { - hent->next = newarr[hval]; - newarr[hval] = hent; - } - hent = nent; } + + /* None. Use a random func and bump the existing element */ + n = htab->hashfunc_offset + get_random_long(0, NHASH_TRIES - 1); + n %= NHASH_MOD; + hval = (hash_functions[n]) (bump.key, keylen) % htab->hashsize; + temp = htab->buckets[hval]; + htab->buckets[hval] = bump; + bump = temp; + loop += 1; } - mush_free(oldarr, "hash_buckets"); - return; + /* At this point, we've bumped BUMP_LIMIT times. Probably in a + loop. Find the first empty bucket, add the last bumped to, and + return failure. */ + for (n = 0; n < htab->hashsize; n++) + if (htab->buckets[n].key == NULL) { + htab->buckets[n] = bump; + return false; + } + + /* Never reached. */ + return false; } -slab *hashentry_slab; -static HASHENT * -hash_new(HASHTAB *htab, const char *key) +static int resize_calls = 0, first_offset = -1; + +/** Resize a hash table. + * \param htab pointer to hashtable. + * \param primesize new size. + * \param hashindex Index of first hash function to use + */ +static bool +real_hash_resize(HASHTAB *htab, int newsize, int hashfunc_offset) { - int hval; - HASHENT *hptr, *curr, *old; + HASHENT *oldarr; + int oldsize, oldoffset, i; - hptr = hash_find(htab, key); - if (hptr) - return hptr; - - if ((double) htab->entries > (htab->hashsize * HTAB_UPSCALE)) - hash_resize(htab, htab->hashsize << 1); - - hval = hash_val(key, htab->mask); - htab->entries++; - if (!hashentry_slab) { - hashentry_slab = slab_create("hash table entries", sizeof(HASHENT)); - slab_set_opt(hashentry_slab, SLAB_HINTLESS_THRESHOLD, 3); + /* Massive overkill here */ + if (resize_calls > 150) { + fputs("Ooops. Too many attempts to resize a hash table.\n", stderr); + return false; } - hptr = slab_malloc(hashentry_slab, htab->buckets[hval]); - hptr->key = mush_strdup(key, "hash_key"); - hptr->data = NULL; - if (!htab->buckets[hval] || strcmp(key, htab->buckets[hval]->key) < 0) { - hptr->next = htab->buckets[hval]; - htab->buckets[hval] = hptr; - return hptr; + /* If all possible hash function combos have been exhausted, + grow the array */ + if (hashfunc_offset == first_offset) { + int newersize = next_prime_after(floor(newsize * 1.15)); + first_offset = -1; + return real_hash_resize(htab, newersize, hashfunc_offset); } - /* Insert in sorted order. There's always at least one item in - the chain already at this point. */ - old = htab->buckets[hval]; - for (curr = old->next; curr; old = curr, curr = curr->next) { - /* Comparison will never be 0 because hash_add checks to see - if the entry is already present. */ - if (strcmp(key, curr->key) < 0) { /* Insert before curr */ - old->next = hptr; - hptr->next = curr; - return hptr; + resize_calls += 1; + + /* Save the old data we need */ + oldsize = htab->hashsize; + oldoffset = htab->hashfunc_offset; + oldarr = htab->buckets; + + htab->buckets = + mush_calloc(newsize, sizeof(struct hash_bucket), "hash.buckets"); + htab->hashsize = newsize; + htab->hashfunc_offset = hashfunc_offset; + for (i = 0; i < oldsize; i++) { + + if (oldarr[i].key) { + + if (!hash_insert(htab, oldarr[i].key, oldarr[i].data)) { + /* Couldn't fit an element in. Try with different hash functions. */ + mush_free(htab->buckets, "hash.buckets"); + htab->buckets = oldarr; + htab->hashsize = oldsize; + htab->hashfunc_offset = oldoffset; + if (first_offset == -1) + first_offset = hashfunc_offset; + return + real_hash_resize(htab, newsize, (hashfunc_offset + 1) % NHASH_MOD); + } } } - /* If we get here, we reached the end of the chain */ - old->next = hptr; - hptr->next = NULL; + mush_free(oldarr, "hash.buckets"); + return true; +} - return hptr; +/** Resize a hash table. + * \param htab pointer to hashtable. + * \param size new size. + */ +bool +hash_resize(HASHTAB *htab, int size) +{ + if (htab) { + htab->last_index = -1; + first_offset = -1; + resize_calls = 0; + return real_hash_resize(htab, next_prime_after(size), + htab->hashfunc_offset); + } else + return false; } /** Add an entry to a hash table. @@ -334,26 +566,31 @@ * \param key key string to store data under. * \param hashdata void pointer to data to be stored. * \param extra_size unused. - * \retval -1 failure. - * \retval 0 success. + * \retval false failure. + * \retval true success. */ -int -hash_add(HASHTAB *htab, const char *key, void *hashdata, - int extra_size __attribute__ ((__unused__))) +bool +hash_add(HASHTAB *htab, const char *key, void *hashdata) { - HASHENT *hptr; + const char *keycopy; - if (hash_find(htab, key) != NULL) { - return -1; - } + if (hash_find(htab, key) != NULL) + return false; - hptr = hash_new(htab, key); + htab->entries += 1; - if (!hptr) - return -1; + keycopy = mush_strdup(key, "hash.key"); - hptr->data = hashdata; - return 0; + if (!hash_insert(htab, keycopy, hashdata)) { + first_offset = -1; + resize_calls = 0; + if (!real_hash_resize(htab, htab->hashsize, + (htab->hashfunc_offset + 1) % NHASH_MOD)) { + htab->entries -= 1; + return false; + } + } + return true; } /** Delete an entry in a hash table. @@ -363,31 +600,14 @@ void hash_delete(HASHTAB *htab, HASHENT *entry) { - int hval; - HASHENT *hptr, *last; - if (!entry) return; - hval = hash_val(entry->key, htab->mask); - last = NULL; - for (hptr = htab->buckets[hval]; hptr; last = hptr, hptr = hptr->next) { - if (entry == hptr) { - if (last == NULL) - htab->buckets[hval] = hptr->next; - else - last->next = hptr->next; - mush_free(hptr->key, "hash_key"); - if (htab->free_data) - htab->free_data(hptr->data); - slab_free(hashentry_slab, hptr); - htab->entries--; - return; - } - } - - if ((double) htab->entries < (htab->hashsize * HTAB_DOWNSCALE)) - hash_resize(htab, htab->hashsize >> 1); + if (htab->free_data) + htab->free_data(entry->data); + mush_free((void *) entry->key, "hash.key"); + memset(entry, 0, sizeof *entry); + htab->entries -= 1; } /** Flush a hash table, freeing all entries. @@ -397,33 +617,26 @@ void hash_flush(HASHTAB *htab, int size) { - HASHENT *hent, *thent; int i; + struct hash_bucket *resized; - if (htab->buckets) { + if (htab->entries) { for (i = 0; i < htab->hashsize; i++) { - hent = htab->buckets[i]; - while (hent != NULL) { - thent = hent; - hent = hent->next; - mush_free(thent->key, "hash_key"); + if (htab->buckets[i].key) { + mush_free((void *) htab->buckets[i].key, "hash.key"); if (htab->free_data) - htab->free_data(thent->data); - slab_free(hashentry_slab, thent); + htab->free_data(htab->buckets[i].data); } - htab->buckets[i] = NULL; } } - if (size == 0) { - mush_free(htab->buckets, "hash_buckets"); - htab->buckets = NULL; - } else if (size != htab->hashsize) { - if (htab->buckets) - mush_free(htab->buckets, "hash_buckets"); - hashinit(htab, size, htab->entry_size); - } else { - htab->entries = 0; + htab->entries = 0; + size = next_prime_after(size); + resized = mush_realloc(htab->buckets, size, "hash.buckets"); + if (resized) { + htab->buckets = resized; + htab->hashsize = size; } + memset(htab->buckets, 0, sizeof(struct hash_bucket) * htab->hashsize); } /** Return the first entry of a hash table. @@ -435,13 +648,12 @@ void * hash_firstentry(HASHTAB *htab) { - int hval; + int n; - for (hval = 0; hval < htab->hashsize; hval++) - if (htab->buckets[hval]) { - htab->last_hval = hval; - htab->last_entry = htab->buckets[hval]; - return htab->buckets[hval]->data; + for (n = 0; n < htab->hashsize; n++) + if (htab->buckets[n].key) { + htab->last_index = n; + return htab->buckets[n].data; } return NULL; } @@ -452,16 +664,15 @@ * \param htab pointer to hash table. * \return first hash table key. */ -char * +const char * hash_firstentry_key(HASHTAB *htab) { - int hval; + int n; - for (hval = 0; hval < htab->hashsize; hval++) - if (htab->buckets[hval]) { - htab->last_hval = hval; - htab->last_entry = htab->buckets[hval]; - return htab->buckets[hval]->key; + for (n = 0; n < htab->hashsize; n++) + if (htab->buckets[n].key) { + htab->last_index = n; + return htab->buckets[n].key; } return NULL; } @@ -476,23 +687,13 @@ void * hash_nextentry(HASHTAB *htab) { - int hval; - HASHENT *hptr; - - hval = htab->last_hval; - hptr = htab->last_entry; - if (hptr->next) { - htab->last_entry = hptr->next; - return hptr->next->data; - } - hval++; - while (hval < htab->hashsize) { - if (htab->buckets[hval]) { - htab->last_hval = hval; - htab->last_entry = htab->buckets[hval]; - return htab->buckets[hval]->data; + int n = htab->last_index + 1; + while (n < htab->hashsize) { + if (htab->buckets[n].key) { + htab->last_index = n; + return htab->buckets[n].data; } - hval++; + n += 1; } return NULL; } @@ -504,26 +705,16 @@ * \param htab pointer to hash table. * \return next hash table key. */ -char * +const char * hash_nextentry_key(HASHTAB *htab) { - int hval; - HASHENT *hptr; - - hval = htab->last_hval; - hptr = htab->last_entry; - if (hptr->next) { - htab->last_entry = hptr->next; - return hptr->next->key; - } - hval++; - while (hval < htab->hashsize) { - if (htab->buckets[hval]) { - htab->last_hval = hval; - htab->last_entry = htab->buckets[hval]; - return htab->buckets[hval]->key; + int n = htab->last_index + 1; + while (n < htab->hashsize) { + if (htab->buckets[n].key) { + htab->last_index = n; + return htab->buckets[n].key; } - hval++; + n += 1; } return NULL; } @@ -535,7 +726,7 @@ hash_stats_header(dbref player) { notify_format(player, - "Table Buckets Entries LChain ECh 1Ch 2Ch 3Ch 4+Ch AvgCh ~Memory"); + "Table Buckets Entries 1Lookup 2Lookup 3Lookup ~Memory"); } /** Display stats on a hashtable. @@ -546,42 +737,33 @@ void hash_stats(dbref player, HASHTAB *htab, const char *hname) { - int longest = 0, n; - int lengths[5]; - double chainlens = 0.0; - double totchains = 0.0; - unsigned int bytes = 0; + int n; + size_t bytes; + unsigned int compares[3] = { 0, 0, 0 }; if (!htab || !hname) return; - for (n = 0; n < 5; n++) - lengths[n] = 0; - bytes += sizeof(HASHTAB); - bytes += htab->entry_size * htab->entries; - if (htab->buckets) { - bytes += HASHENT_SIZE * htab->hashsize; - for (n = 0; n < htab->hashsize; n++) { - int chain = 0; - HASHENT *b; - if (htab->buckets[n]) { - for (b = htab->buckets[n]; b; b = b->next) { - chain++; - bytes += strlen(b->key) + 1; + bytes = sizeof *htab; + bytes += sizeof(struct hash_bucket) * htab->hashsize; + + for (n = 0; n < htab->hashsize; n++) + if (htab->buckets[n].key) { + int i; + int len = strlen(htab->buckets[n].key); + bytes += len + 1; + for (i = 0; i < 3; i++) { + hash_func hash = + hash_functions[(i + htab->hashfunc_offset) % NHASH_MOD]; + if ((hash(htab->buckets[n].key, len) % htab->hashsize) == (uint32_t) n) { + compares[i] += 1; + break; } - if (chain > longest) - longest = chain; } - lengths[(chain > 4) ? 4 : chain]++; - chainlens += chain; } - } - for (n = 1; n < 5; n++) - totchains += lengths[n]; notify_format(player, - "%-10s %7d %7d %6d %4d %4d %4d %4d %4d %6.3f %7u", hname, - htab->hashsize, htab->entries, longest, lengths[0], lengths[1], - lengths[2], lengths[3], lengths[4], - totchains > 0 ? chainlens / totchains : 0.0, bytes); + "%-11s %7d %7d %7u %7u %7u %7u", + hname, htab->hashsize, htab->entries, compares[0], compares[1], + compares[2], (unsigned int) bytes); } Index: src/plyrlist.c =================================================================== --- src/plyrlist.c (.../p4) (revision 1119) +++ src/plyrlist.c (.../p5) (revision 1119) @@ -44,7 +44,7 @@ static void init_hft(void) { - hash_init(&htab_player_list, 256, sizeof(dbref), delete_dbref); + hash_init(&htab_player_list, 256, delete_dbref); player_dbref_slab = slab_create("player list dbrefs", sizeof(dbref)); hft_initialized = 1; } Index: src/funlist.c =================================================================== --- src/funlist.c (.../p4) (revision 1119) +++ src/funlist.c (.../p5) (revision 1119) @@ -692,7 +692,7 @@ for (i = 0; i < nptrs; i++) { /* Build our %0 args */ wenv[0] = (char *) ptrs[i]; - call_ufun(&ufun, wenv, 2, result, executor, enactor, pe_info); + call_ufun(&ufun, wenv, 1, result, executor, enactor, pe_info); keys[i] = mush_strdup(result, "sortkey"); } @@ -1946,9 +1946,64 @@ /* ARGSUSED */ FUNCTION(fun_ldelete) { - /* delete a word at position X of a list */ + /* delete a word at given positions of a list */ - do_itemfuns(buff, bp, args[0], args[1], NULL, args[2], IF_DELETE); + /* Given a list and a list of numbers, delete the corresponding + * elements of the list. elements(ack bar eep foof yay,2 4) = bar foof + * A separator for the first list is allowed. + * This code modified slightly from 'elements' + */ + int nwords, cur; + char **ptrs; + char *wordlist; + int first = 0; + char *s, *r, sep; + char *osep, osepd[2] = { '\0', '\0' }; + + if (!delim_check(buff, bp, nargs, args, 3, &sep)) + return; + + if (nargs == 4) + osep = args[3]; + else { + osepd[0] = sep; + osep = osepd; + } + + ptrs = mush_calloc(MAX_SORTSIZE, sizeof(char *), "ptrarray"); + wordlist = mush_malloc(BUFFER_LEN, "string"); + if (!ptrs || !wordlist) + mush_panic("Unable to allocate memory in fun_elements"); + + /* Turn the first list into an array. */ + strcpy(wordlist, args[0]); + nwords = list2arr_ansi(ptrs, MAX_SORTSIZE, wordlist, sep); + + s = trim_space_sep(args[1], ' '); + + /* Go through the second list, grabbing the numbers and finding the + * corresponding elements. + */ + do { + r = split_token(&s, ' '); + cur = atoi(r) - 1; + if ((cur >= 0) && (cur < nwords)) { + ptrs[cur] = NULL; + } + } while (s); + for (cur = 0; cur < nwords; cur++) { + if (ptrs[cur]) { + if (first) + safe_str(osep, buff, bp); + else + first = 1; + safe_str(ptrs[cur], buff, bp); + } + } + + freearr(ptrs, nwords); + mush_free((Malloc_t) ptrs, "ptrarray"); + mush_free((Malloc_t) wordlist, "string"); } /* ARGSUSED */ @@ -2625,12 +2680,8 @@ int offsets[99]; int erroffset; int flags = 0, all = 0, match_offset = 0; + struct re_save rsave; - pcre *old_re_code; - int old_re_subpatterns; - int *old_re_offsets; - ansi_string *old_re_from; - int i; const char *r, *obp; char *start, *oldbp; @@ -2643,11 +2694,7 @@ size_t searchlen; int funccount; - /* Save old regexp contexts */ - old_re_code = global_eval_context.re_code; - old_re_subpatterns = global_eval_context.re_subpatterns; - old_re_offsets = global_eval_context.re_offsets; - old_re_from = global_eval_context.re_from; + save_regexp_context(&rsave); if (called_as[strlen(called_as) - 1] == 'I') flags = PCRE_CASELESS; @@ -2688,6 +2735,7 @@ safe_str(T("#-1 REGEXP ERROR: "), buff, bp); safe_str(errptr, buff, bp); free_ansi_string(orig); + restore_regexp_context(&rsave); return; } add_check("pcre"); /* re */ @@ -2702,6 +2750,7 @@ safe_str(T("#-1 REGEXP ERROR: "), buff, bp); safe_str(errptr, buff, bp); free_ansi_string(orig); + restore_regexp_context(&rsave); return; } if (study != NULL) @@ -2788,6 +2837,7 @@ safe_str(T("#-1 REGEXP ERROR: "), buff, bp); safe_str(errptr, buff, bp); free_ansi_string(orig); + restore_regexp_context(&rsave); return; } add_check("pcre"); /* re */ @@ -2802,6 +2852,7 @@ safe_str(T("#-1 REGEXP ERROR: "), buff, bp); safe_str(errptr, buff, bp); free_ansi_string(orig); + restore_regexp_context(&rsave); return; } if (study != NULL) @@ -2829,8 +2880,8 @@ repl = parse_ansi_string(tbuf); /* Do the replacement */ - ansi_string_replace(orig, offsets[0], offsets[1] - offsets[0], repl, - 0, repl->len); + ansi_string_replace(orig, offsets[0], offsets[1] - offsets[0], + repl); /* Advance search */ if (search == offsets[1]) { @@ -2859,11 +2910,7 @@ safe_str(postbuf, buff, bp); } - /* Restore old regexp contexts */ - global_eval_context.re_code = old_re_code; - global_eval_context.re_offsets = old_re_offsets; - global_eval_context.re_subpatterns = old_re_subpatterns; - global_eval_context.re_from = old_re_from; + restore_regexp_context(&rsave); } Index: src/lock.c =================================================================== --- src/lock.c (.../p4) (revision 1119) +++ src/lock.c (.../p5) (revision 1119) @@ -275,7 +275,7 @@ lock_list *ll; st_init(&lock_names); - hashinit(&htab_locks, 64, sizeof *ll); + hashinit(&htab_locks, 25); for (ll = lock_types; ll->type && *ll->type; ll++) hashadd(strupper(ll->type), ll, &htab_locks); Index: src/warnings.c =================================================================== --- src/warnings.c (.../p4) (revision 1119) +++ src/warnings.c (.../p5) (revision 1119) @@ -377,9 +377,10 @@ unparse_warnings(warn_type warns) { static char tbuf1[BUFFER_LEN]; + char *tp; int listsize, indexx; - tbuf1[0] = '\0'; + tp = tbuf1; /* Get the # of elements in checklist */ listsize = sizeof(checklist) / sizeof(tcheck); @@ -391,14 +392,15 @@ /* Which is to say: * if the bits set on the_flag is a subset of the bits set on warns */ - strcat(tbuf1, checklist[indexx].name); - strcat(tbuf1, " "); + safe_str(checklist[indexx].name, tbuf1, &tp); + safe_chr(' ', tbuf1, &tp); /* If we've got a flag which subsumes smaller ones, don't * list the smaller ones */ warns &= ~the_flag; } } + *tp = '\0'; return tbuf1; } Index: src/wild.c =================================================================== --- src/wild.c (.../p4) (revision 1119) +++ src/wild.c (.../p5) (revision 1119) @@ -626,7 +626,27 @@ return r >= 0; } +/** Regexp match of a pre-compiled regexp, with no memory. + * \param re the regular expression + * \param subj the string to match against. + * \return true or false + */ +bool +qcomp_regexp_match(const pcre * re, const char *subj) +{ + int len; + int offsets[99]; + if (!re || !subj) + return false; + + len = strlen(subj); + + return pcre_exec(re, NULL, subj, len, 0, 0, offsets, 99) >= 0; + +} + + /** Either an order comparison or a wildcard match with no memory. * * Index: src/look.c =================================================================== --- src/look.c (.../p4) (revision 1119) +++ src/look.c (.../p5) (revision 1119) @@ -52,6 +52,7 @@ static char *parent_chain(dbref player, dbref thing); extern PRIV attr_privs_view[]; +extern int real_decompose_str(char *str, char *buff, char **bp); static void look_exits(dbref player, dbref loc, const char *exit_name) @@ -1405,12 +1406,9 @@ { static char value[BUFFER_LEN]; char *vp = value; - ansi_string *as; - as = parse_ansi_string(what); - dump_ansi_string(as, value, &vp); + real_decompose_str(what, value, &vp); *vp = '\0'; - free_ansi_string(as); return value; } @@ -1437,10 +1435,10 @@ avalue = atr_value(atr); avlen = strlen(avalue); - /* If avalue includes a %r, a %t, or begins or ends with a %b, + /* If avalue includes a %r, a %t, begins or ends with a %b, or has markup, * then use @set on the decompose_str'd value instead of &atrname */ - if (strchr(avalue, '\n') || - strchr(avalue, '\t') || *avalue == ' ' || avalue[avlen - 1] == ' ') { + if (strchr(avalue, '\n') || strchr(avalue, '\t') || + strchr(avalue, TAG_START) || *avalue == ' ' || avalue[avlen - 1] == ' ') { safe_str("@set ", msg, &bp); safe_str(dh->name, msg, &bp); safe_chr('=', msg, &bp); Index: src/strutil.c =================================================================== --- src/strutil.c (.../p4) (revision 1119) +++ src/strutil.c (.../p5) (revision 1119) @@ -1450,7 +1450,7 @@ * \param dst the destination string to copy to * \param src the source string to copy from * \param len the maximum number of bytes to copy - * return dst + * \return dst */ char * mush_strncpy(char *restrict dst, const char *restrict src, size_t len) Index: src/notify.c =================================================================== --- src/notify.c (.../p4) (revision 1119) +++ src/notify.c (.../p5) (revision 1119) @@ -89,8 +89,6 @@ #include "game.h" #include "confmagic.h" -#define ANSI_RAW_NORMAL "\x1B[0m" - static int under_limit = 1; @@ -163,6 +161,8 @@ /** Number of possible message text renderings */ #define MESSAGE_TYPES 12 +/* These should be removed. I can't imagine anyone uses them in hacks, + * but I'm leaving them here just in case... */ #define TA_BGC 0 /**< Text attribute background color */ #define TA_FGC 1 /**< Text attribute foreground color */ #define TA_BOLD 2 /**< Text attribute bold/hilite */ @@ -183,6 +183,8 @@ static enum na_type notify_type(DESC *d); static void free_strings(struct notify_strings messages[]); static void zero_strings(struct notify_strings messages[]); +static int output_ansichange(ansi_data * states, int *ansi_ptr, + const unsigned char **ptr, char *buff, char **bp); static unsigned char *notify_makestring(const char *message, struct notify_strings messages[], enum na_type type, int flags); @@ -208,14 +210,13 @@ } static int -output_ansichange(struct ansi_data *states, int *ansi_ptr, +output_ansichange(ansi_data * states, int *ansi_ptr, const unsigned char **ptr, char *buff, char **bp) { const unsigned char *p = *ptr; int newaptr = *ansi_ptr; int retval = 0; - int changed = 0; - struct ansi_data cur = states[*ansi_ptr]; + ansi_data cur = states[*ansi_ptr]; /* This is color */ while (*p && @@ -226,43 +227,33 @@ if (*p != '/') { newaptr++; define_ansi_data(&(states[newaptr]), (const char *) p); - /* The new ansi state inherits ansi from the old one that it doesn't - * have or disable */ - nest_ansi_data(&(states[newaptr - 1]), &(states[newaptr])); } else { if (*(p + 1) == 'a') { newaptr = 0; } else { - newaptr--; + if (newaptr > 0) + newaptr--; } - if (newaptr < 0) - newaptr = 0; - if (newaptr < *ansi_ptr) - changed = 1; } while (*p && *p != TAG_END) p++; - /* Advance past TAG_END */ - if (*p && ((*(p + 1) == TAG_START && *(p + 2) == MARKUP_COLOR) || - (*(p + 1) == ESC_CHAR))) - p++; break; case ESC_CHAR: - /* I really hate daling with this. Consider it a nesting code. */ newaptr++; read_raw_ansi_data(&states[newaptr], (const char *) p); - nest_ansi_data(&states[newaptr - 1], &states[newaptr]); while (*p && *p != 'm') p++; - /* Advance past the 'm' if there's a tag or */ - if (*p && ((*(p + 1) == TAG_START && *(p + 2) == MARKUP_COLOR) || - (*(p + 1) == ESC_CHAR))) - p++; break; } + if (newaptr > 0) + nest_ansi_data(&(states[newaptr - 1]), &(states[newaptr])); + /* Advance past the tag ending, if there's more. */ + if (*p && ((*(p + 1) == TAG_START && *(p + 2) == MARKUP_COLOR) || + (*(p + 1) == ESC_CHAR))) + p++; } /* Do we print anything? */ - if (changed || newaptr != *ansi_ptr) { + if (*p && *ptr != p) { retval = write_raw_ansi_data(&cur, &(states[newaptr]), buff, bp); *(ansi_ptr) = newaptr; } @@ -277,20 +268,20 @@ char *o; const unsigned char *p; char *t; - int changed = 0; int color = 0; int strip = 0; int pueblo = 0; static char tbuf[BUFFER_LEN]; char *bp; - static struct ansi_data states[BUFFER_LEN]; - int ansi_ptr, ansifix; + static ansi_data states[BUFFER_LEN]; + int ansi_ptr, ansifix; ansi_ptr = 0; ansifix = 0; + /* Everything is explicitly off by default */ states[0].bits = 0; - states[0].offbits = ~0; + states[0].offbits = 0; states[0].fore = 0; states[0].back = 0; @@ -412,10 +403,6 @@ while (*p) { switch ((unsigned char) *p) { case IAC: - if (changed) { - changed = 0; - ansifix += write_raw_ansi_data(&states[ansi_ptr], &states[0], t, &o); - } if (type == NA_TANSI || type == NA_TCOLOR) safe_strl("\xFF\xFF", 2, t, &o); else if (strip && accent_table[IAC].base) @@ -424,7 +411,9 @@ safe_chr((char) IAC, t, &o); break; case TAG_START: - if (pueblo && (*(p + 1) == MARKUP_HTML)) { + if (*(p + 1) == MARKUP_COLOR) { + ansifix += output_ansichange(states, &ansi_ptr, &p, t, &o); + } else if (pueblo && (*(p + 1) == MARKUP_HTML)) { safe_chr('<', t, &o); /* Skip over the 'p' for Pueblo */ p += 2; @@ -433,13 +422,10 @@ p++; } safe_chr('>', t, &o); - } else if (*(p + 1) == MARKUP_COLOR) { - ansifix += output_ansichange(states, &ansi_ptr, &p, t, &o); } else { while (*p && *p != TAG_END) p++; } - changed = 1; break; case TAG_END: /* Should never be seen alone */ @@ -448,9 +434,7 @@ break; case ESC_CHAR: /* After the ansi changes, I really hope we don't encounter this. */ - /* because it borks how proper ansi handling works */ ansifix += output_ansichange(states, &ansi_ptr, &p, t, &o); - changed = 1; break; default: if (pueblo) { Index: src/game.c =================================================================== --- src/game.c (.../p4) (revision 1119) +++ src/game.c (.../p5) (revision 1119) @@ -94,7 +94,7 @@ extern void initialize_mt(void); extern const unsigned char *tables; extern void conf_default_set(void); -static int dump_database_internal(void); +static bool dump_database_internal(void); static FILE *db_open(const char *filename); static FILE *db_open_write(const char *filename); static void db_close(FILE * f); @@ -309,7 +309,7 @@ jmp_buf db_err; -static int +static bool dump_database_internal(void) { char realdumpfile[2048]; @@ -329,12 +329,14 @@ do_rawlog(LT_ERR, T("ERROR! Database save failed.")); flag_broadcast("WIZARD ROYALTY", 0, T("GAME: ERROR! Database save failed!")); + if (f) + db_close(f); #ifndef PROFILING #ifdef HAS_ITIMER install_sig_handler(SIGPROF, signal_cpu_limit); #endif #endif - return 1; + return false; } else { local_dump_database(); @@ -412,7 +414,7 @@ #endif #endif - return 0; + return true; } /** Crash gracefully. @@ -511,8 +513,8 @@ epoch++; do_rawlog(LT_ERR, "DUMPING: %s.#%d#", globals.dumpfile, epoch); - dump_database_internal(); - do_rawlog(LT_ERR, "DUMPING: %s.#%d# (done)", globals.dumpfile, epoch); + if (dump_database_internal()) + do_rawlog(LT_ERR, "DUMPING: %s.#%d# (done)", globals.dumpfile, epoch); } /** Dump a database, possibly by forking the process. @@ -528,7 +530,7 @@ fork_and_dump(int forking) { pid_t child; - int nofork, status, split; + bool nofork, status, split; epoch++; #ifdef LOG_CHUNK_STATS @@ -605,7 +607,7 @@ _exit(status); /* !!! */ } else { reserve_fd(); - if (DUMP_NOFORK_COMPLETE && *DUMP_NOFORK_COMPLETE) + if (status && DUMP_NOFORK_COMPLETE && *DUMP_NOFORK_COMPLETE) flag_broadcast(0, 0, "%s", DUMP_NOFORK_COMPLETE); } } @@ -690,6 +692,9 @@ int a; pid_t mypid = -1; + /* initialize random number generator */ + initialize_mt(); + init_queue(); global_eval_context.process_command_port = 0; @@ -756,8 +761,6 @@ { /* access file stuff */ read_access_file(); - /* initialize random number generator */ - initialize_mt(); /* set up signal handlers for the timer */ init_timer(); /* Commands and functions require the flag table for restrictions */ @@ -2330,6 +2333,7 @@ extern HASHTAB htab_objdata; extern HASHTAB htab_objdata_keys; extern HASHTAB htab_locks; +extern HASHTAB local_options; extern StrTree atr_names; extern StrTree lock_names; extern StrTree object_names; @@ -2353,6 +2357,7 @@ hash_stats(player, &htab_objdata, "ObjData"); hash_stats(player, &htab_objdata_keys, "ObjDataKeys"); hash_stats(player, &htab_locks, "@locks"); + hash_stats(player, &local_options, "ConfigOpts"); notify(player, "Prefix Trees:"); ptab_stats_header(player); ptab_stats(player, &ptab_attrib, "AttrPerms"); Index: src/tables.c =================================================================== --- src/tables.c (.../p4) (revision 1119) +++ src/tables.c (.../p5) (revision 1119) @@ -1,4 +1,4 @@ -/* This file was generated by running ./gentables compiled from +/* This file was generated by running ./a.out compiled from * utils/gentables.c. Edit that file, not this one, when making changes. */ #include @@ -97,6 +97,25 @@ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; +char valid_ansi_codes[256] = { + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 1, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, + 0, 0, 1, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, + 0, 1, 1, 1, 0, 0, 1, 1, 1, 1, 0, 0, 0, 1, 1, 0, + 0, 0, 1, 0, 0, 1, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 +}; + typedef struct { const char *base; const char *entity; Index: src/atr_tab.c =================================================================== --- src/atr_tab.c (.../p4) (revision 1119) +++ src/atr_tab.c (.../p5) (revision 1119) @@ -84,7 +84,7 @@ {"nospace", 's', AF_NOSPACE, AF_NOSPACE}, {"amhear", 'M', AF_MHEAR, AF_MHEAR}, {"aahear", 'A', AF_AHEAR, AF_AHEAR}, - {"root", '`', AF_ROOT, AF_ROOT}, + {"", '`', AF_ROOT, AF_ROOT}, {NULL, '\0', 0, 0} }; Index: src/command.c =================================================================== --- src/command.c (.../p4) (revision 1119) +++ src/command.c (.../p5) (revision 1119) @@ -184,6 +184,8 @@ {"@MAP", "DELIMIT", cmd_map, CMD_T_ANY | CMD_T_EQSPLIT | CMD_T_RS_NOPARSE, 0, 0}, + {"@MESSAGE", "NOEVAL SPOOF", cmd_message, + CMD_T_ANY | CMD_T_EQSPLIT | CMD_T_RS_ARGS, 0, 0}, {"@MOTD", "CONNECT LIST WIZARD DOWN FULL", cmd_motd, CMD_T_ANY | CMD_T_NOGAGGED, 0, 0}, {"@MVATTR", "CONVERT NOFLAGCOPY", cmd_mvattr, @@ -635,7 +637,7 @@ done = 1; ptab_init(&ptab_command); - hashinit(&htab_reserved_aliases, 16, sizeof(COMMAND_INFO)); + hashinit(&htab_reserved_aliases, 16); command_slab = slab_create("commands", sizeof(COMMAND_INFO)); reserve_aliases(); ptab_start_inserts(&ptab_command); Index: src/log.c =================================================================== --- src/log.c (.../p4) (revision 1119) +++ src/log.c (.../p5) (revision 1119) @@ -87,7 +87,7 @@ *fp = stderr; } else { if (!ht_initialized) { - hashinit(&htab_logfiles, 8, sizeof(FILE *)); + hashinit(&htab_logfiles, 8); ht_initialized = 1; } if ((f = (FILE *) hashfind(strupper(filename), &htab_logfiles))) { @@ -168,7 +168,7 @@ void end_all_logs(void) { - char *name, *next; + const char *name, *next; name = hash_firstentry_key(&htab_logfiles); while (name) { next = hash_nextentry_key(&htab_logfiles); Index: src/help.c =================================================================== --- src/help.c (.../p4) (revision 1119) +++ src/help.c (.../p5) (revision 1119) @@ -80,7 +80,7 @@ void init_help_files(void) { - hash_init(&help_files, 8, sizeof(help_file), NULL); + hashinit(&help_files, 8); help_init = 1; } Index: src/set.c =================================================================== --- src/set.c (.../p4) (revision 1119) +++ src/set.c (.../p5) (revision 1119) @@ -587,8 +587,14 @@ char *p, *f, *name; char flagbuff[BUFFER_LEN]; - if (!xname || !*xname || !flag || !*flag) + if (!xname || !*xname) { + notify(player, T("I can't see that here.")); return 0; + } + if (!flag || !*flag) { + notify(player, T("What do you want to set?")); + return 0; + } name = mush_strdup(xname, "ds.string"); Index: MANIFEST =================================================================== --- MANIFEST (.../p4) (revision 1119) +++ MANIFEST (.../p5) (revision 1119) @@ -233,10 +233,12 @@ src/warnings.c src/wild.c src/wiz.c +test/README test/MUSHConnection.pm test/PennMUSH.pm test/TestHarness.pm -test/alltests.pl +test/alltests.sh.in +test/runtest.pl test/testalias.pl test/testatree.pl test/testdecompose.pl @@ -256,6 +258,7 @@ test/testsetfuns.pl test/teststringsecs.pl test/testtr.pl +test/testtrim.pl utils/clwrapper.sh utils/customize.pl utils/fixdepend.pl Index: game/txt/hlp/pennfunc.hlp =================================================================== --- game/txt/hlp/pennfunc.hlp (.../p4) (revision 1119) +++ game/txt/hlp/pennfunc.hlp (.../p5) (revision 1119) @@ -1233,11 +1233,13 @@ eval(, ) get_eval(/) - Eval() works the same way as xget(), except that it performs %-substitutions - and function evaluation on the attribute before returning the value. eval() - does not modify the stack (%0-%9), so the attribute being evaled sees the - same values for them that the calling code does. Unless you need this behavior, - it is better to use u() instead, which hides the caller's stack. + Eval() works the same way as xget(), except that it performs + %-substitutions and function evaluation on the attribute before + returning the value. eval() changes the enactor (%#) to the object + executing the eval (%!). It does not modify the stack (%0-%9), so + the attribute being evaled sees the same values for them that the + calling code does. Unless you need this behavior, it is better to + use u() instead, which hides the caller's stack. Example: &TEST me=%B%B%B-[name(me)] @@ -2000,11 +2002,12 @@ See also: capstr(), ucstr() & LDELETE() - Ldelete(, [,]) + Ldelete(, [,]) - This deletes the item at in the list. If a separator + This deletes the item(s) at in the list. If a separator character is not given, a space is assumed. Null items are - counted, as in 'items()'. + counted, as in 'items()'. Positions are numeric and must be + separated by spaces. Examples: > say [ldelete(This is a long test string,4)] @@ -2535,6 +2538,10 @@ You say, "ABcdEF" See also: splice(), tr() +& MESSAGE() + message(, , [,,...]) + + message() is the function form of @message. & MID() mid(, , ) @@ -2817,15 +2824,6 @@ object is @created (or @dug, or @opened, or @pcreated, etc.), it will have this dbref. -& NMWHO() - nmwho() - - This returns a count of all currently connected, non-hidden players. - It's exactly the same as nwho() used by a mortal, and is suitable - for use on privileged global objects who need an unprivileged count - of who's online. - -See also: nwho(), mwho(), xmwho() & NOR() nor([, ... , ]) @@ -2894,13 +2892,25 @@ nvthings() is identical to words(lvthings()) See also: ncon(), nexits(), xthings(), lthings(), lvthings() +& NMWHO() & NWHO() nwho() + nwho() + nmwho() - This returns a count of all currently-connected players. When + nwho() returns a count of all currently-connected players. When mortals use this function, DARK wizards or royalty are NOT counted. -See also: lwho(), nmwho(), xwho() + nmwho() returns a count of all currently connected, non-hidden players. + It's exactly the same as nwho() used by a mortal, and is suitable + for use on privileged global objects that always need an unprivileged + count of who is online. + + If nwho() is given an argument, and is used by an object that can see + DARK and Hidden players, nwho() returns the count of online players + based on what can see. + +See also: lwho(), mwho(), xwho(), xmwho() & OBJ() obj() Index: game/txt/hlp/pennv182.hlp =================================================================== --- game/txt/hlp/pennv182.hlp (.../p4) (revision 1119) +++ game/txt/hlp/pennv182.hlp (.../p5) (revision 1119) @@ -1,3 +1,23 @@ +& 1.8.2p7 +Version 1.8.2 patchlevel 7 October 6, 2007 + +Minor changes: + * nwho() now takes an optional viewer argument like lwho(). By Sketch. + +Fixes: + * Clarified the behavior of eval() and get_eval() in help. Suggested by + Talvo and Javelin. + * A failed db save no longer broadcasts a success message in addition to a + failure one. Reported by Cooee. + * The open database file wasn't getting closed on a failed save. + * Crash bug in sortkey(). Fix by Nathan Baum. + * 'help @desc' brings up @describe instead of @descformat. + Suggested by Nymeria. + * Removed mention of Win32 requiring a particular attribute + compression algorithm. Any will work, and always have. + Reported by Andrew Klein. + * Crash bug in @purge. Javelin. + & 1.8.2p6 Version 1.8.2 patchlevel 6 July 9, 2007 Index: game/txt/hlp/pennv183.hlp =================================================================== --- game/txt/hlp/pennv183.hlp (.../p4) (revision 1119) +++ game/txt/hlp/pennv183.hlp (.../p5) (revision 1119) @@ -1,4 +1,4 @@ -& 1.8.3p4 +& 1.8.3p5 & changes This is a list of changes in this patchlevel which are probably of interest to players. More information about new commands and functions @@ -11,9 +11,46 @@ A list of the patchlevels associated with each release can be read in 'help patchlevels'. -Version 1.8.3 patchlevel 4 ???? ??, 2007 +Version 1.8.3 patchlevel 5 October 6, 2007 Major changes: + * Significant rewrite of ansi parsing and better ansi support + for many string-handling functions. Patch by Sketch. + * Rewrite of the softcode regression testing framework, and + addition of more tests. [SW] + +Minor changes: + * Store a pointer to the start of a player's mailbox in objdata + instead of the connection struct. + * Experimental rewrite of hash tables to use the cuckoo hashing + algorithm, with constant-time lookups even in the worst case. + (And appears to have generally faster lookup even in normal usage.) + * Regular expression @sitelocks save the compiled regexp instead of + recompiling every time the rule is tested. + * Added %4 to @pageformat, which is the default page message. + +Commands: + * Added @message, which makes it easy to use @chatformat or + @pageformat via @hooks, or to create your own *format. + +Functions: + * Added message(), the function version of @message. + +Fixes: + * decode64() does better validation of its input. [SW] + * Various compile fixes reported by Interevis and Kimiko. + Win32 patched by Intrevis. + * @sitelock does better error reporting. [SW] + * Crash bug related to regeditall fixed. + * @decompile didn't handle attribute trees correctly. + * Compile failure in funstr.c on some systems. Fixed by Boris. + * '@set =foo' failed silently. Reported by Talvo. + * Fixes from 1.8.2p7 + +& 1.8.3p4 +Version 1.8.3 patchlevel 4 July 9, 2007 + +Major changes: * Parts of the build process that used a shell script to regenerate certain headers now use perl scripts instead, making them much faster. [SW] Index: game/txt/hlp/penncmd.hlp =================================================================== --- game/txt/hlp/penncmd.hlp (.../p4) (revision 1119) +++ game/txt/hlp/penncmd.hlp (.../p5) (revision 1119) @@ -456,12 +456,15 @@ @assert does the inverse: it stops execution if evaluates to false. Examples: - > @va obj=$testme *:@pemit %#=Before break;@break %0;@pemit %#=After break + > @va obj=$testme *:@pemit %#=You try a test; + @break [lt(%0,10)] = @pemit %#=But you're too low!; + @pemit %#=And you succeed! > testme 0 - Before break - After break - > testme 1 - Before break + You try a test + But you're too low! + > testme 10 + You try a test + And you succeed! > @force me={@switch 1=1, think Third; think First; @break 1; think Second} First @@ -923,6 +926,7 @@ See also: CLIENTS, ATTRIBUTES, WILDCARDS, MUSHCODE & @describe +& @desc @describe [=] This command sets the description of the object, which will be seen @@ -2212,6 +2216,26 @@ Object says, "3" Object says, "2" Object says, "1" +& @message + @message[/switch] =,[,,...] + + @message is designed for the use of *format messages, such as + @pageformat or @chatformat. + + It sends to each player given in unless they + have set. If the executor can u() the player's attribute, + then instead of , they will see the output that is identical + to: + + u(/,,...) + + It is intended for use with @hooking page, @chat, or say/pose/emit. + + Switches: NOEVAL and SPOOF. If the executor is either wizard or has + the NsPemit power, then SPOOF will make the message appear to be + from the enactor. + +See also: message(), @chatformat, @pageformat & @motd @motd [/] []. @@ -2610,6 +2634,7 @@ respectively, %2 will be set to the alias of the pager, if any. %3 will be a space-separated list of recipient dbrefs. + %4 will be set to the default message. To obtain 'page_aliases' behavior: > @pageformat me=[setq(0,%n[if(%2,%b(%2))],1,switch(%3,%!,,itemize(iter(%3, Index: game/txt/hlp/pennvOLD.hlp =================================================================== --- game/txt/hlp/pennvOLD.hlp (.../p4) (revision 1119) +++ game/txt/hlp/pennvOLD.hlp (.../p5) (revision 1119) @@ -4417,8 +4417,8 @@ For information on a specific patchlevel of one of the versions listed, type 'help p'. For example, 'help 1.7.2p3' -1.8.3: 0, 1, 2, 3, 4 -1.8.2: 0, 1, 2, 3, 4, 5, 6 +1.8.3: 0, 1, 2, 3, 4, 5 +1.8.2: 0, 1, 2, 3, 4, 5, 6, 7 1.8.1: 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 1.8.0: 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13 1.7.7: 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, Index: test/alltests.pl =================================================================== --- test/alltests.pl (.../p4) (revision 1119) +++ test/alltests.pl (.../p5) (revision 1119) @@ -1,19 +0,0 @@ -#!/usr/bin/perl - -my $total_failed = 0; - -for my $file () { - print "Running $file ... "; - # This might be 'do' one day, but not until we get output right - my $output = `perl $file 2> /dev/null`; - $output =~ s/.*\D(\d+ tests, \d+ succeeded, (\d+) failed).*/$1/s; - print "$output\n"; - $total_failed += $2; - print " Try running the test manually (perl $file) to see failures\n" if $2; -} - -if ($total_failed) { - print "*** $total_failed tests failed\n"; -} else { - print "All tests successful\n"; -} Index: test/testletq.pl =================================================================== --- test/testletq.pl (.../p4) (revision 1119) +++ test/testletq.pl (.../p5) (revision 1119) @@ -1,8 +1,3 @@ -use PennMUSH; -use TestHarness; - -my $mush = PennMUSH->new(); -my $god = $mush->loginGod(); - +run tests: test("letq.1", $god, "think setr(A, 1):[letq(A, 2, \%qA)]:\%qA", '^1:2:1'); test("letq.2", $god, "think setr(A, 1):[letq(setr(A, 2))]:\%qA", '^1:2:2'); Index: test/testnull.pl =================================================================== --- test/testnull.pl (.../p4) (revision 1119) +++ test/testnull.pl (.../p5) (revision 1119) @@ -1,10 +1,4 @@ -use PennMUSH; -use MUSHConnection; -use TestHarness; - -$mush = PennMUSH->new(); -$god = $mush->loginGod(); - +run test: test('null.1', $god, 'think null()', ''); test('null.2', $god, 'think null(a)', ''); test('null.3', $god, 'think null(a,b,c)', ''); Index: test/testfirstof.pl =================================================================== --- test/testfirstof.pl (.../p4) (revision 1119) +++ test/testfirstof.pl (.../p5) (revision 1119) @@ -1,10 +1,4 @@ -use PennMUSH; -use MUSHConnection; -use TestHarness; - -$mush = PennMUSH->new(); -$god = $mush->loginGod(); - +run tests: test('firstof.1', $god, 'think firstof(0,0,2)', '2'); test('firstof.2', $god, 'think firstof(2,0,0)', '2'); test('firstof.3', $god, 'think firstof(0,0,0)', '0'); Index: test/testpage.pl =================================================================== --- test/testpage.pl (.../p4) (revision 1119) +++ test/testpage.pl (.../p5) (revision 1119) @@ -1,8 +1,2 @@ -use PennMUSH; -use MUSHConnection; -use TestHarness; - -$mush = PennMUSH->new(); -$god = $mush->loginGod(); - +run tests: test('page.1', $god, 'page/noeval %# \= =',"I can't find"); # Former crasher Index: test/testhastype.pl =================================================================== --- test/testhastype.pl (.../p4) (revision 1119) +++ test/testhastype.pl (.../p5) (revision 1119) @@ -1,10 +1,5 @@ -use PennMUSH; -use MUSHConnection; -use TestHarness; - -$mush = PennMUSH->new(); -$god = $mush->loginGod(); - +expect 2 failures! +run tests: test('hastype.1', $god, 'think hastype(#0, room)', ['1', '!#-1']); test('hastype.2', $god, 'think hastype(#1, player)', ['1', '!#-1']); test('hastype.3', $god, '@create foo', []); Index: test/testdecompose.pl =================================================================== --- test/testdecompose.pl (.../p4) (revision 1119) +++ test/testdecompose.pl (.../p5) (revision 1119) @@ -1,12 +1,7 @@ -use PennMUSH; -use MUSHConnection; -use TestHarness; - -$mush = PennMUSH->new(); -$god = $mush->loginGod(); - -test('decompose.1', $god, 'think decompose([ansi(hr,b[ansi(f,la)]h)])', '\[ansi\(hr,b\[ansi\(f,la\)\]\)\]\[ansi\(hr,h\)\]'); -test('decompose.2', $god, 'think decompose(a\ \ \ \ b)', 'a %b %bb'); -test('decompose.3', $god, 'think decompose(s(tab%treturn%r))', 'tab%treturn%r'); -test('decompose.4', $god, 'think decompose(before(ansi(h,x),x)hello)', '\[ansi\(h,hello\)\]'); -test('decompose.5', $god, 'think decompose([before(ansi(h,blah),\[)])', '!.'); +login mortal +run test: +test('decompose.1', $mortal, 'think decompose([ansi(hr,b[ansi(f,la)]h)])', '\[ansi\(hr,b(\)\])?\[ansi\((?(1)fhr|f),la\)\](?(1)\[ansi\(hr,h|h)\)\]'); +test('decompose.2', $mortal, 'think decompose(a\ \ \ \ b)', 'a %b %bb'); +test('decompose.3', $mortal, 'think decompose(s(tab%treturn%r))', 'tab%treturn%r'); +test('decompose.4', $mortal, 'think decompose(before(ansi(h,x),x)hello)', 'hello'); +test('decompose.5', $mortal, 'think decompose([before(ansi(h,blah),\[)])', '\[ansi\(h,blah\)\]'); Index: test/testdigest.pl =================================================================== --- test/testdigest.pl (.../p4) (revision 1119) +++ test/testdigest.pl (.../p5) (revision 1119) @@ -1,18 +1,13 @@ -use PennMUSH; -use MUSHConnection; -use TestHarness; +login mortal +run tests: +test('digest.1', $mortal, 'think digest(md2,foo)', 'd11f8ce29210b4b50c5e67533b699d02'); +test('digest.2', $mortal, 'think digest(md5,foo)', 'acbd18db4cc2f85cedef654fccc4a4d8'); +test('digest.3', $mortal, 'think digest(sha,foo)', '752678a483e77799a3651face01d064f9ca86779'); +test('digest.4', $mortal, 'think digest(sha1,foo)', '0beec7b5ea3f0fdbc95d0dd47f3c5bc275da8a33'); +test('digest.5', $mortal, 'think digest(dss1,foo)', '0beec7b5ea3f0fdbc95d0dd47f3c5bc275da8a33'); +test('digest.6', $mortal, 'think digest(ripemd160,foo)', '42cfa211018ea492fdee45ac637b7972a0ad6873'); +test('digest.7', $mortal, 'think digest(md4,foo)', '0ac6700c491d70fb8650940b1ca1e4b2'); -$mush = PennMUSH->new(); -$god = $mush->loginGod(); -test('digest.1', $god, 'think digest(md2,foo)', 'd11f8ce29210b4b50c5e67533b699d02'); -test('digest.2', $god, 'think digest(md5,foo)', 'acbd18db4cc2f85cedef654fccc4a4d8'); -test('digest.3', $god, 'think digest(sha,foo)', '752678a483e77799a3651face01d064f9ca86779'); -test('digest.4', $god, 'think digest(sha1,foo)', '0beec7b5ea3f0fdbc95d0dd47f3c5bc275da8a33'); -test('digest.5', $god, 'think digest(dss1,foo)', '0beec7b5ea3f0fdbc95d0dd47f3c5bc275da8a33'); -test('digest.6', $god, 'think digest(ripemd160,foo)', '42cfa211018ea492fdee45ac637b7972a0ad6873'); -test('digest.7', $god, 'think digest(md4,foo)', '0ac6700c491d70fb8650940b1ca1e4b2'); - - -test('base64.1', $god, 'think encode64(test string)', 'dGVzdCBzdHJpbmc='); -test("base64.2", $god, "think decode64(encode64(this is another fine mess you've gotten us into))", "this is another fine mess you've gotten us into"); +test('base64.1', $mortal, 'think encode64(test string)', 'dGVzdCBzdHJpbmc='); +test("base64.2", $mortal, "think decode64(encode64(this is another fine mess you've gotten us into))", "this is another fine mess you've gotten us into"); Index: test/testrand.pl =================================================================== --- test/testrand.pl (.../p4) (revision 1119) +++ test/testrand.pl (.../p5) (revision 1119) @@ -1,10 +1,4 @@ -use PennMUSH; -use MUSHConnection; -use TestHarness; - -$mush = PennMUSH->new(); -$god = $mush->loginGod(); - +run tests: test('rand.1', $god, 'think rand(-1)', '#-1'); test('rand.2', $god, 'think rand(0)', '#-1'); test('rand.3', $god, 'think rand(1)', '0'); Index: test/teststringsecs.pl =================================================================== --- test/teststringsecs.pl (.../p4) (revision 1119) +++ test/teststringsecs.pl (.../p5) (revision 1119) @@ -1,16 +1,12 @@ -use PennMUSH; -use MUSHConnection; -use TestHarness; - -$mush = PennMUSH->new(); -$god = $mush->loginGod(); - -test('stringsecs.1', $god, 'think stringsecs(a)', '#-1 INVALID TIMESTRING'); -test('stringsecs.2', $god, 'think stringsecs(10)', '10'); -test('stringsecs.3', $god, 'think stringsecs(10s)', '10'); -test('stringsecs.4', $god, 'think stringsecs(5m)', '300'); -test('stringsecs.5', $god, 'think stringsecs(5m 10s)', '310'); -test('stringsecs.6', $god, 'think stringsecs(1h)', '3600'); -test('stringsecs.7', $god, 'think stringsecs(10s 5m)', '310'); -test('stringsecs.8', $god, 'think stringsecs(1d 2h 3m 4s)', '93784'); -test('stringsecs.9', $god, 'think stringsecs(h)', '#-1 INVALID TIMESTRING'); +login mortal +run tests: +test('stringsecs.1', $mortal, 'think stringsecs(a)', '#-1 INVALID TIMESTRING'); +test('stringsecs.2', $mortal, 'think stringsecs(10)', '10'); +test('stringsecs.3', $mortal, 'think stringsecs(10s)', '10'); +test('stringsecs.4', $mortal, 'think stringsecs(5m)', '300'); +test('stringsecs.5', $mortal, 'think stringsecs(5m 10s)', '310'); +test('stringsecs.6', $mortal, 'think stringsecs(1h)', '3600'); +test('stringsecs.7', $mortal, 'think stringsecs(10s 5m)', '310'); +test('stringsecs.8', $mortal, 'think stringsecs(1d 2h 3m 4s)', '93784'); +test('stringsecs.9', $mortal + , 'think stringsecs(h)', '#-1 INVALID TIMESTRING'); Index: test/testjust.pl =================================================================== --- test/testjust.pl (.../p4) (revision 1119) +++ test/testjust.pl (.../p5) (revision 1119) @@ -1,11 +1,5 @@ -# Test ljust(), rjust(), center() - -use PennMUSH; -use TestHarness; - -my $mush = PennMUSH->new(); -my $god = $mush->loginGod(); - +run tests: +$god->command('@config/set tiny_math=no'); test('ljust.1', $god, "think ljust(foo, 3)", 'foo'); test('ljust.2', $god, "think ljust(foo, 5)X", '^foo X'); test('ljust.3', $god, "think ljust(foo, 5, =)", '^foo=='); Index: test/testtr.pl =================================================================== --- test/testtr.pl (.../p4) (revision 1119) +++ test/testtr.pl (.../p5) (revision 1119) @@ -1,10 +1,4 @@ -use PennMUSH; -use MUSHConnection; -use TestHarness; - -$mush = PennMUSH->new(); -$god = $mush->loginGod(); - +run tests: test('tr.1', $god, 'think tr(test STRING,,)', 'test STRING'); test('tr.2', $god, 'think tr(test STRING,t,)', '#-1'); test('tr.3', $god, 'think tr(test STRING,,t)', '#-1'); Index: test/testsetfuns.pl =================================================================== --- test/testsetfuns.pl (.../p4) (revision 1119) +++ test/testsetfuns.pl (.../p5) (revision 1119) @@ -1,12 +1,7 @@ -use PennMUSH; -use MUSHConnection; -use TestHarness; - -$mush = PennMUSH->new(); -$god = $mush->loginGod(); - -test('setunion.1', $god, 'think setunion(,)', '^$'); -test('setunion.2', $god, 'think setunion( a,a)', '^a\r$'); -test('setunion.3', $god, 'think setunion(c a b a,a b c c)', '^a b c\r$'); -test('setunion.4', $god, 'think setunion(a a a,)', '^a\r$'); -test('setunion.5', $god, 'think setunion(,a a a)', '^a\r$'); +login mortal +run tests: +test('setunion.1', $mortal, 'think setunion(,)', '^$'); +test('setunion.2', $mortal, 'think setunion( a,a)', '^a\r$'); +test('setunion.3', $mortal, 'think setunion(c a b a,a b c c)', '^a b c\r$'); +test('setunion.4', $mortal, 'think setunion(a a a,)', '^a\r$'); +test('setunion.5', $mortal, 'think setunion(,a a a)', '^a\r$'); Index: test/testreswitch.pl =================================================================== --- test/testreswitch.pl (.../p4) (revision 1119) +++ test/testreswitch.pl (.../p5) (revision 1119) @@ -1,10 +1,4 @@ -use PennMUSH; -use MUSHConnection; -use TestHarness; - -$mush = PennMUSH->new(); -$god = $mush->loginGod(); - +run tests: test('reswitch.1', $god, 'think reswitch(test STRING,t,1,0)', '1'); test('reswitch.2', $god, 'think reswitch(test STRING,t,1,e,2,0)', '1'); test('reswitch.3', $god, 'think reswitch(test STRING,E,1,0)', '0'); Index: test/testalias.pl =================================================================== --- test/testalias.pl (.../p4) (revision 1119) +++ test/testalias.pl (.../p5) (revision 1119) @@ -1,10 +1,4 @@ -use PennMUSH; -use MUSHConnection; -use TestHarness; - -$mush = PennMUSH->new(); -$god = $mush->loginGod(); - +run tests: test('alias.1', $god, '@name me=God', ['Name set.']); test('alias.2', $god, '@name me=One', ['Name set.']); test('alias.3', $god, '@alias me=God', ['Alias set.']); Index: test/testmath.pl =================================================================== --- test/testmath.pl (.../p4) (revision 1119) +++ test/testmath.pl (.../p5) (revision 1119) @@ -1,10 +1,4 @@ -use PennMUSH; -use MUSHConnection; -use TestHarness; - -$mush = PennMUSH->new(); -$god = $mush->loginGod(); - +run tests: test('abs.1', $god, 'think abs(-1)', '1'); test('abs.2', $god, 'think abs(-1.5)', '1.5'); test('abs.3', $god, 'think abs(1)', '1'); Index: test/testgrep.pl =================================================================== --- test/testgrep.pl (.../p4) (revision 1119) +++ test/testgrep.pl (.../p5) (revision 1119) @@ -1,42 +1,37 @@ -use PennMUSH; -use MUSHConnection; -use TestHarness; +login mortal +run tests: +$mortal->command("&FIRST me=first"); +$mortal->command("&SECOND me=second"); +$mortal->command("&THIRD me=third"); -$mush = PennMUSH->new(); -$god = $mush->loginGod(); +test('grep.1', $mortal, 'think grep(me,*,d)', 'SECOND THIRD'); +test('grep.2', $mortal, 'think grep(me,S*,d)', 'SECOND'); +test('grep.3', $mortal, 'think grep(me,*,*d*)', '!SECOND THIRD'); +test('grep.4', $mortal, 'think grep(me,*,D)', '!SECOND THIRD'); -$god->command("&FIRST me=first"); -$god->command("&SECOND me=second"); -$god->command("&THIRD me=third"); +test('grep.5', $mortal, 'think wildgrep(me,*,*d*)', 'SECOND THIRD'); +test('grep.6', $mortal, 'think wildgrep(me,*,d)', '!SECOND THIRD'); +test('grep.7', $mortal, 'think wildgrep(me,*,first)', 'FIRST'); +test('grep.8', $mortal, 'think wildgrep(me,*,FIRST)', '!FIRST'); -test('grep.1', $god, 'think grep(me,*,d)', 'SECOND THIRD'); -test('grep.2', $god, 'think grep(me,S*,d)', 'SECOND'); -test('grep.3', $god, 'think grep(me,*,*d*)', '!SECOND THIRD'); -test('grep.4', $god, 'think grep(me,*,D)', '!SECOND THIRD'); +test('grep.9', $mortal, 'think regrep(me,*,*d*)', '!SECOND THIRD'); +test('grep.10', $mortal, 'think regrep(me,*,d)', 'SECOND THIRD'); +test('grep.11', $mortal, 'think regrep(me,*,d$)', 'SECOND THIRD'); +test('grep.12', $mortal, 'think regrep(me,*,first)', 'FIRST'); +test('grep.13', $mortal, 'think regrep(me,*,FIRST)', '!FIRST'); -test('grep.5', $god, 'think wildgrep(me,*,*d*)', 'SECOND THIRD'); -test('grep.6', $god, 'think wildgrep(me,*,d)', '!SECOND THIRD'); -test('grep.7', $god, 'think wildgrep(me,*,first)', 'FIRST'); -test('grep.8', $god, 'think wildgrep(me,*,FIRST)', '!FIRST'); +test('grep.14', $mortal, 'think grepi(me,*,d)', 'SECOND THIRD'); +test('grep.15', $mortal, 'think grepi(me,S*,d)', 'SECOND'); +test('grep.16', $mortal, 'think grepi(me,*,*d*)', '!SECOND THIRD'); +test('grep.17', $mortal, 'think grepi(me,*,D)', 'SECOND THIRD'); -test('grep.9', $god, 'think regrep(me,*,*d*)', '!SECOND THIRD'); -test('grep.10', $god, 'think regrep(me,*,d)', 'SECOND THIRD'); -test('grep.11', $god, 'think regrep(me,*,d$)', 'SECOND THIRD'); -test('grep.12', $god, 'think regrep(me,*,first)', 'FIRST'); -test('grep.13', $god, 'think regrep(me,*,FIRST)', '!FIRST'); +test('grep.18', $mortal, 'think wildgrepi(me,*,*d*)', 'SECOND THIRD'); +test('grep.19', $mortal, 'think wildgrepi(me,*,d)', '!SECOND THIRD'); +test('grep.20', $mortal, 'think wildgrepi(me,*,first)', 'FIRST'); +test('grep.21', $mortal, 'think wildgrepi(me,*,FIRST)', 'FIRST'); -test('grep.14', $god, 'think grepi(me,*,d)', 'SECOND THIRD'); -test('grep.15', $god, 'think grepi(me,S*,d)', 'SECOND'); -test('grep.16', $god, 'think grepi(me,*,*d*)', '!SECOND THIRD'); -test('grep.17', $god, 'think grepi(me,*,D)', 'SECOND THIRD'); - -test('grep.18', $god, 'think wildgrepi(me,*,*d*)', 'SECOND THIRD'); -test('grep.19', $god, 'think wildgrepi(me,*,d)', '!SECOND THIRD'); -test('grep.20', $god, 'think wildgrepi(me,*,first)', 'FIRST'); -test('grep.21', $god, 'think wildgrepi(me,*,FIRST)', 'FIRST'); - -test('grep.22', $god, 'think regrepi(me,*,*d*)', '!SECOND THIRD'); -test('grep.23', $god, 'think regrepi(me,*,d)', 'SECOND THIRD'); -test('grep.24', $god, 'think regrepi(me,*,d$)', 'SECOND THIRD'); -test('grep.25', $god, 'think regrepi(me,*,first)', 'FIRST'); -test('grep.26', $god, 'think regrepi(me,*,FIRST)', 'FIRST'); +test('grep.22', $mortal, 'think regrepi(me,*,*d*)', '!SECOND THIRD'); +test('grep.23', $mortal, 'think regrepi(me,*,d)', 'SECOND THIRD'); +test('grep.24', $mortal, 'think regrepi(me,*,d$)', 'SECOND THIRD'); +test('grep.25', $mortal, 'think regrepi(me,*,first)', 'FIRST'); +test('grep.26', $mortal, 'think regrepi(me,*,FIRST)', 'FIRST'); Index: test/testdistxd.pl =================================================================== --- test/testdistxd.pl (.../p4) (revision 1119) +++ test/testdistxd.pl (.../p5) (revision 1119) @@ -1,9 +1,4 @@ -use PennMUSH; -use TestHarness; - -my $mush = PennMUSH->new; -my $god = $mush->loginGod; - +run tests: test('dist2d.1', $god, 'think dist2d(0,0, 5,5)', "7.071068"); test('dist2d.2', $god, 'think lmath(dist2d, 0 0 5 5)', "7.071068"); test('dist2d.3', $god, 'think dist2d(0,0, 0,0)', "0"); Index: test/alltests.sh.in =================================================================== --- test/alltests.sh.in (.../p4) (revision 0) +++ test/alltests.sh.in (.../p5) (revision 1119) @@ -0,0 +1,4 @@ +#!/bin/sh + +exec @PERL@ runtest.pl test*.pl + Index: test/testatree.pl =================================================================== --- test/testatree.pl (.../p4) (revision 1119) +++ test/testatree.pl (.../p5) (revision 1119) @@ -1,14 +1,5 @@ -use PennMUSH; -use MUSHConnection; -use TestHarness; - -$| = 1; - -$mush = PennMUSH->new(); -# print "MUSH on port ".$mush->{PORT}."\n"; -# sleep(30); -$god = $mush->loginGod(); - +login mortal +run tests: # First, the basic tests enforcing tree-nature of the attributes. # Attrs may not start or end in ` test("atree.basic.1", $god, "&foo` me=baz", "not a very good name"); @@ -91,8 +82,6 @@ # Permissions checks # Need a mortal for this... -test('atree.perms.1', $god, '@pcreate Mortal=mortal', 'created'); -$mortal = $mush->login("Mortal", "mortal"); # Build a tree from different places... test('atree.perms.2', $god, '&foo mortal=baz', 'Set'); test('atree.perms.3', $god, '&foo`bar mortal=baz', 'Set'); Index: test/runtest.pl =================================================================== --- test/runtest.pl (.../p4) (revision 0) +++ test/runtest.pl (.../p5) (revision 1119) @@ -0,0 +1,21 @@ +#!/usr/bin/perl -w +use strict; +use PennMUSH; +use TestHarness; + + + +my $mush = PennMUSH->new; + +my @tests = map { TestHarness->new($_); } @ARGV; + +my $god = $mush->loginGod; +my $mortal = undef; +if ($TestHarness::use_mortal) { + $god->command('@pcreate Mortal=mortal'); + $mortal = $mush->login("Mortal", "mortal"); +} + +foreach my $test (@tests) { + $test->run($god, $mortal); +} Index: test/TestHarness.pm =================================================================== --- test/TestHarness.pm (.../p4) (revision 1119) +++ test/TestHarness.pm (.../p5) (revision 1119) @@ -1,14 +1,95 @@ package TestHarness; +use strict; +use vars qw/%tests $testcount @failures $alltests $allfailures $allexpected/; +use vars qw/$testfiles $use_mortal/; +use subs qw/test summary/; -require Exporter; +$alltests = 0; +$allfailures = 0; +$allexpected = 0; +$testfiles = 0; +$use_mortal = 0; -@ISA = qw(Exporter); +sub new { + my $class = shift; + my $script = shift; + my %self = ( + -expected => 0, + -depends => [], + -test => undef, + ); +# print "Looking at $script\n"; + $script =~ /^test(.*)\.pl$/o; + my $name = $1; + $self{-name} = $name; + warn "Duplicate test $name\n" if exists $tests{$name}; + my $code = 'sub { my $god = shift; ' . "\n"; + open IN, "<", $script or die "Couldn't open ${script}: $!\n"; + while () { + chomp; + next if /^\s*(?:#|$)/o; + last if /^run tests:$/o; + if (/^depends on (.*)$/o) { + push @{$self{-depends}}, $1; + } + if (/^expect (\d+) failures!$/) { + $self{-expected} = $1; + print "Expecting $1 failures in $name\n"; + } + if (/^\s*login mortal$/) { + $code .= 'my $mortal = shift;' . "\n"; + $use_mortal = 1; + } + } + while () { + $code .= $_; + } + close IN; + $code .= "}"; +# print "Test function for $name:\n$code\n"; + $self{-test} = eval $code; + my $obj = bless \%self; + $tests{$name} = $obj; + return $obj; +} -@EXPORT = qw(test summary); +sub run { + my $self = shift; + my $god = shift; + my $mortal = shift; + my ($failures, $test) = (0,0); -my $testcount = 0; -my @failures = (); + foreach my $dep (@{$self->{-depends}}) { + my $test = $tests{$dep}; + if (defined $test) { + $test->run($god, $mortal); + } else { + warn "Unresolved dependency $dep\n"; + } + } + + local ($testcount, @failures) = (0, ()); + my $name = $self->{-name}; + + print "Running tests for ${name}:\n"; + + &{$self->{-test}}($god, $mortal); + + $testfiles++; + $alltests += $testcount; + $allfailures += @failures; + $allexpected += $self->{-expected}; + + summary $self->{-name}, $testcount, \@failures, $self->{-expected}; +} + +END { + print "Totals:\n"; + summary("all tests run", $alltests, $allfailures, $allexpected) + if $testfiles > 1; +} + $| = 1; sub test { @@ -24,8 +105,7 @@ my $result = defined($command) ? $conn->command($command) : $conn->listen(); my $verdict = 1; - my $pattern; - foreach $pattern (@$patterns) { + foreach my $pattern (@$patterns) { my $matchpattern = $pattern; my $negate = 0; if ($matchpattern =~ s/^!//o) { @@ -56,7 +136,7 @@ } else { print " result: $result\n"; } - foreach $pattern (@$patterns) { + foreach my $pattern (@$patterns) { print " pattern: $pattern\n"; } print "\n"; @@ -64,27 +144,27 @@ } sub summary { - print ":"x70, "\n"; - print "\n"; - my $scount = $testcount - @failures; - my $fcount = @failures; - print "$testcount tests, $scount succeeded, $fcount failed\n"; - if ($fcount) { - print "failed tests:\n"; - my $str = join(", ", @failures); - while (length($str) > 67) { - $str =~ s/^(.{1,67}), //o; - print " $1,\n"; + my ($name, $testcount, $failures, $expected) = @_; + print ":"x70, "\n"; + print "\n"; + my $fcount = 0; + if (ref $failures) { + $fcount = scalar @$failures; + } else { + $fcount = $failures; } - print " $str\n"; - } - - $testcount = 0; - @failures = (); + my $scount = $testcount - $fcount; + print "$testcount tests, $scount succeeded, $fcount failed ($expected expected failures)\n"; + if ($fcount != $expected) { + print "failed tests:\n"; + my $str = join(", ", @$failures); + while (length($str) > 67) { + $str =~ s/^(.{1,67}), //o; + print " $1,\n"; + } + print " $str\n"; + } + print "\n"; } -END { - summary() if $testcount; -} - 1; Index: UPGRADING =================================================================== --- UPGRADING (.../p4) (revision 1119) +++ UPGRADING (.../p5) (revision 1119) @@ -119,6 +119,11 @@ *** target version of PennMUSH. PennMUSH 1.7.7+ can no longer read *** 1.7.4 databases. +*** If you are upgrading from 1.7.6 or certain 1.7.7 versions, +*** you may also first need to load your database under PennMUSH +*** 1.8.0p13 and then dump it, and load this converted database +*** under your target version of PennMUSH. + ============================================================================ B. PennMUSH with a few hacks Index: CHANGES.182 =================================================================== --- CHANGES.182 (.../p4) (revision 1119) +++ CHANGES.182 (.../p5) (revision 1119) @@ -13,6 +13,25 @@ ========================================================================== +Version 1.8.2 patchlevel 7 October 6, 2007 + +Minor changes: + * nwho() now takes an optional viewer argument like lwho(). By Sketch. + +Fixes: + * Clarified the behavior of eval() and get_eval() in help. Suggested by + Talvo and Javelin. + * A failed db save no longer broadcasts a success message in addition to a + failure one. Reported by Cooee. + * The open database file wasn't getting closed on a failed save. + * Crash bug in sortkey(). Fix by Nathan Baum. + * 'help @desc' brings up @describe instead of @descformat. + Suggested by Nymeria. + * Removed mention of Win32 requiring a particular attribute + compression algorithm. Any will work, and always have. + Reported by Andrew Klein. + * Crash bug in @purge. Javelin. + Version 1.8.2 patchlevel 6 July 9, 2007 Development team changes: Index: CHANGES.183 =================================================================== --- CHANGES.183 (.../p4) (revision 1119) +++ CHANGES.183 (.../p5) (revision 1119) @@ -14,9 +14,45 @@ ========================================================================== -Version 1.8.3 patchlevel 4 ???? ??, 2007 +Version 1.8.3 patchlevel 5 October 6, 2007 Major changes: + * Significant rewrite of ansi parsing and better ansi support + for many string-handling functions. Patch by Sketch. + * Rewrite of the softcode regression testing framework, and + addition of more tests. [SW] + +Minor changes: + * Store a pointer to the start of a player's mailbox in objdata + instead of the connection struct. + * Experimental rewrite of hash tables to use the cuckoo hashing + algorithm, with constant-time lookups even in the worst case. + (And appears to have generally faster lookup even in normal usage.) + * Regular expression @sitelocks save the compiled regexp instead of + recompiling every time the rule is tested. + * Added %4 to @pageformat, which is the default page message. + +Commands: + * Added @message, which makes it easy to use @chatformat or + @pageformat via @hooks, or to create your own *format. + +Functions: + * Added message(), the function version of @message. + +Fixes: + * decode64() does better validation of its input. [SW] + * Various compile fixes reported by Interevis and Kimiko. + Win32 patched by Intrevis. + * @sitelock does better error reporting. [SW] + * Crash bug related to regeditall fixed. + * @decompile didn't handle attribute trees correctly. + * Compile failure in funstr.c on some systems. Fixed by Boris. + * '@set =foo' failed silently. Reported by Talvo. + * Fixes from 1.8.2p7 + +Version 1.8.3 patchlevel 4 July 9, 2007 + +Major changes: * Parts of the build process that used a shell script to regenerate certain headers now use perl scripts instead, making them much faster. [SW] Index: configure.in =================================================================== --- configure.in (.../p4) (revision 1119) +++ configure.in (.../p5) (revision 1119) @@ -287,4 +287,5 @@ AC_CONFIG_FILES([Makefile src/Makefile]) AC_CONFIG_FILES([game/txt/compose.sh], [chmod +x game/txt/compose.sh]) +AC_CONFIG_FILES([test/alltests.sh], [chmod +x test/alltests.sh]) AC_OUTPUT Index: hdrs/ansi.h =================================================================== --- hdrs/ansi.h (.../p4) (revision 1119) +++ hdrs/ansi.h (.../p5) (revision 1119) @@ -22,6 +22,8 @@ #define BEEP_CHAR '\a' #define ESC_CHAR '\x1B' +#define ANSI_RAW_NORMAL "\x1B[0m" + #define TAG_START '\002' #define TAG_END '\003' #define MARKUP_START "\002" @@ -80,22 +82,22 @@ #include #endif -struct ansi_data { +typedef struct _ansi_data { uint8_t bits; uint8_t offbits; char fore; char back; -}; +} ansi_data; -int read_raw_ansi_data(struct ansi_data *store, const char *codes); -int write_ansi_data(struct ansi_data *cur, char *buff, char **bp); +int read_raw_ansi_data(ansi_data * store, const char *codes); +int write_raw_ansi_data(ansi_data * old, ansi_data * cur, char *buff, + char **bp); -int write_raw_ansi_data(struct ansi_data *old, struct ansi_data *cur, - char *buff, char **bp); +void define_ansi_data(ansi_data * store, const char *str); +int write_ansi_data(ansi_data * cur, char *buff, char **bp); -void define_ansi_data(struct ansi_data *store, const char *str); -void nest_ansi_data(struct ansi_data *old, struct ansi_data *cur); +void nest_ansi_data(ansi_data * old, ansi_data * cur); #define MARKUP_COLOR 'c' #define MARKUP_COLOR_STR "c" @@ -104,10 +106,21 @@ #define MARKUP_HTML_STR "p" /* Markup information necessary for ansi_string */ + +/* Miscellaneous notes on markup_information: + * If "start" is negative, there are two cases: + * end >= 0 :: A stand-alone tag, starting at "end". + * end < 0 :: A tag set for removal. + * If start is non-negative while end is negative, something's broken. + * + * Markup surrounding a character ends to the right of that character: + * In the string "abc", if 'b' has a markup assigned to only itself, + * start = 1, end = 2. (Instead of end = 1) + */ + typedef struct _markup_information { char *start_code; char *stop_code; - struct ansi_data ansi; char type; int start; int end; @@ -117,10 +130,11 @@ /** A string, with ansi attributes broken out from the text */ typedef struct _ansi_string { char text[BUFFER_LEN]; /**< Text of the string */ + ansi_data ansi[BUFFER_LEN]; /**< ANSI of the string */ markup_information markup[BUFFER_LEN]; /**< The markup_information list */ - int nmarkups; + int nmarkups; /**< Number of Pueblo markups */ int len; /**< Length of text */ - int optimized; /**< Has this ansi-string been optimized? */ + int optimized; /**< Has this ansi_string been optimized? */ } ansi_string; int ansi_strcmp(const char *astr, const char *bstr); @@ -130,8 +144,6 @@ extern ansi_string * parse_ansi_string(const char *src) __attribute_malloc__; - extern ansi_string *parse_ansi_string_real(const char *src, int oldstyle) - __attribute_malloc__; extern void flip_ansi_string(ansi_string *as); extern void free_ansi_string(ansi_string *as); @@ -141,12 +153,13 @@ char *buff, char **bp); /* Modifying ansi strings */ + ansi_string *real_parse_ansi_string(const char *src) + __attribute_malloc__; int ansi_string_delete(ansi_string *as, int start, int count); - int ansi_string_insert(ansi_string *dst, int loc, - ansi_string *src, int start, int count); - + int ansi_string_insert(ansi_string *dst, int loc, ansi_string *src); int ansi_string_replace(ansi_string *dst, int loc, int size, - ansi_string *src, int start, int count); + ansi_string *src); + ansi_string *scramble_ansi_string(ansi_string *as); void optimize_ansi_string(ansi_string *as); /* Dump the penn code required to recreate the ansi_string */ Index: hdrs/extchat.h =================================================================== --- hdrs/extchat.h (.../p4) (revision 1119) +++ hdrs/extchat.h (.../p5) (revision 1119) @@ -220,7 +220,7 @@ extern int load_chatdb(FILE * fp); extern int save_chatdb(FILE * fp); extern void do_cemit - (dbref player, const char *name, const char *msg, int noisy); + (dbref player, const char *name, const char *msg, int flags); extern void do_chan_user_flags (dbref player, char *name, const char *isyn, int flag, int silent); extern void do_chan_wipe(dbref player, const char *name); Index: hdrs/extmail.h =================================================================== --- hdrs/extmail.h (.../p4) (revision 1119) +++ hdrs/extmail.h (.../p5) (revision 1119) @@ -98,9 +98,4 @@ (dbref player, char *tolist, char *message, mail_flag flags, int silent, int nosig); -/* From bsd.c */ -extern struct mail *desc_mail(dbref player); -extern void desc_mail_set(dbref player, struct mail *mp); -extern void desc_mail_clear(void); - #endif /* _EXTMAIL_H */ Index: hdrs/htab.h =================================================================== --- hdrs/htab.h (.../p4) (revision 1119) +++ hdrs/htab.h (.../p5) (revision 1119) @@ -3,58 +3,47 @@ #ifndef __HTAB_H #define __HTAB_H +typedef struct hashtable HASHTAB; -#define HTAB_UPSCALE 3.25 -#define HTAB_DOWNSCALE 2.0 - -typedef struct hashentry HASHENT; -/** A hash table entry. - */ -struct hashentry { - struct hashentry *next; /**< Pointer to next entry */ - void *data; /**< Data for this entry */ - char *key; /**< Key for this entry */ +/** Hash table bucket struct */ +struct hash_bucket { + const char *key; + void *data; }; -#define HASHENT_SIZE (sizeof(HASHENT)) - -typedef struct hashtable HASHTAB; /** A hash table. */ struct hashtable { - int hashsize; /**< Size of hash table */ - int mask; /**< Mask for entries in table */ + int hashsize; /**< Size of buckets array */ int entries; /**< Number of entries stored */ - HASHENT **buckets; /**< Pointer to pointer to entries */ - int last_hval; /**< State for hashfirst & hashnext. */ - HASHENT *last_entry; /**< State for hashfirst & hashnext. */ - int entry_size; /**< Size of each entry */ + int hashfunc_offset; /**< Which pair of hash functions to use */ + struct hash_bucket *buckets; /**< Buckets */ + int last_index; /**< State for hashfirst & hashnext. */ void (*free_data) (void *); /**< Function to call on data when deleting a entry. */ }; -#define get_hashmask(x) hash_getmask(x) -#define hashinit(x,y, z) hash_init(x,y, z, NULL) +typedef struct hash_bucket HASHENT; + +#define hashinit(tab, size) hash_init((tab), (size), NULL) #define hashfind(key,tab) hash_value(hash_find(tab,key)) -#define hashadd(key,data,tab) hash_add(tab,key,data, 0) -#define hashadds(key, data, tab, size) hash_add(tab, key, data, size) +#define hashadd(key,data,tab) hash_add(tab,key,data) #define hashdelete(key,tab) hash_delete(tab,hash_find(tab,key)) #define hashflush(tab, size) hash_flush(tab,size) #define hashfree(tab) hash_flush(tab, 0) -extern int hash_getmask(int *size); -extern void hash_init(HASHTAB *htab, int size, int data_size, void (*)(void *)); -extern HASHENT *hash_find(HASHTAB *htab, const char *key); -extern void *hash_value(HASHENT *entry); -extern char *hash_key(HASHENT *entry); -extern void hash_resize(HASHTAB *htab, int size); -extern int hash_add - (HASHTAB *htab, const char *key, void *hashdata, int extra_size); -extern void hash_delete(HASHTAB *htab, HASHENT *entry); -extern void hash_flush(HASHTAB *htab, int size); -extern void *hash_firstentry(HASHTAB *htab); -extern void *hash_nextentry(HASHTAB *htab); -extern char *hash_firstentry_key(HASHTAB *htab); -extern char *hash_nextentry_key(HASHTAB *htab); -extern void hash_stats_header(dbref player); -extern void hash_stats(dbref player, HASHTAB *htab, const char *hashname); +int hash_getmask(int *size); +void hash_init(HASHTAB *htab, int size, void (*)(void *)); +HASHENT *hash_find(HASHTAB *htab, const char *key); +#define hash_value(entry) (entry) ? (entry)->data : NULL +#define hash_key(entry) (entry) ? (entry)->key : NULL +bool hash_resize(HASHTAB *htab, int size); +bool hash_add(HASHTAB *htab, const char *key, void *hashdata); +void hash_delete(HASHTAB *htab, HASHENT *entry); +void hash_flush(HASHTAB *htab, int size); +void *hash_firstentry(HASHTAB *htab); +void *hash_nextentry(HASHTAB *htab); +const char *hash_firstentry_key(HASHTAB *htab); +const char *hash_nextentry_key(HASHTAB *htab); +void hash_stats_header(dbref player); +void hash_stats(dbref player, HASHTAB *htab, const char *hashname); #endif Index: hdrs/mushtype.h =================================================================== --- hdrs/mushtype.h (.../p4) (revision 1119) +++ hdrs/mushtype.h (.../p5) (revision 1119) @@ -136,7 +136,6 @@ char doing[DOING_LEN]; /**< Player's doing string */ struct descriptor_data *next; /**< Next descriptor in linked list */ struct descriptor_data *prev; /**< Previous descriptor in linked list */ - struct mail *mailp; /**< Pointer to start of player's mail chain */ int conn_flags; /**< Flags of connection (telnet status, etc.) */ unsigned long input_chars; /**< Characters received */ unsigned long output_chars; /**< Characters sent */ Index: hdrs/access.h =================================================================== --- hdrs/access.h (.../p4) (revision 1119) +++ hdrs/access.h (.../p5) (revision 1119) @@ -9,26 +9,27 @@ char host[BUFFER_LEN]; /**< The host pattern */ char comment[BUFFER_LEN]; /**< A comment about the rule */ dbref who; /**< Who created this rule if sitelock used */ - int can; /**< Bitflags of what the host can do */ - int cant; /**< Bitflags of what the host can't do */ + uint32_t can; /**< Bitflags of what the host can do */ + uint32_t cant; /**< Bitflags of what the host can't do */ + pcre *re; /**< Compiled regexp */ struct access *next; /**< Pointer to next rule in the list */ }; /* These flags are can/can't - a site may or may not be allowed to do them */ -#define ACS_CONNECT 0x1 /* Connect to non-guests */ -#define ACS_CREATE 0x2 /* Create new players */ -#define ACS_GUEST 0x4 /* Connect to guests */ -#define ACS_REGISTER 0x8 /* Site can use the 'register' command */ +#define ACS_CONNECT 0x1U /* Connect to non-guests */ +#define ACS_CREATE 0x2U /* Create new players */ +#define ACS_GUEST 0x4U /* Connect to guests */ +#define ACS_REGISTER 0x8U /* Site can use the 'register' command */ /* These flags are set in the 'can' bit, but they mark special processing */ -#define ACS_SITELOCK 0x10 /* Marker for where to insert @sitelock */ -#define ACS_SUSPECT 0x20 /* All players from this site get SUSPECT */ -#define ACS_DENY_SILENT 0x40 /* Don't log failed attempts */ -#define ACS_REGEXP 0x80 /* Treat the host pattern as a regexp */ +#define ACS_SITELOCK 0x10U /* Marker for where to insert @sitelock */ +#define ACS_SUSPECT 0x20U /* All players from this site get SUSPECT */ +#define ACS_DENY_SILENT 0x40U /* Don't log failed attempts */ +#define ACS_REGEXP 0x80U /* Treat the host pattern as a regexp */ -#define ACS_GOD 0x100 /* God can connect from this site */ -#define ACS_WIZARD 0x200 /* Wizards can connect from this site */ -#define ACS_ADMIN 0x400 /* Admins can connect from this site */ +#define ACS_GOD 0x100U /* God can connect from this site */ +#define ACS_WIZARD 0x200U /* Wizards can connect from this site */ +#define ACS_ADMIN 0x400U /* Admins can connect from this site */ /* This is the usual default access */ #define ACS_DEFAULT (ACS_CONNECT|ACS_CREATE|ACS_GUEST) @@ -44,18 +45,18 @@ #define Forbidden_Site(hname) (!site_can_access(hname,ACS_DEFAULT, AMBIGUOUS)) /* Public functions */ -int read_access_file(void); +bool read_access_file(void); void write_access_file(void); -int site_can_access(const char *hname, int flag, dbref who); +bool site_can_access(const char *hname, uint32_t flag, dbref who); struct access *site_check_access(const char *hname, dbref who, int *rulenum); -int format_access(struct access *ap, int rulenum, - dbref who - __attribute__ ((__unused__)), char *buff, char **bp); -int add_access_sitelock(dbref player, const char *host, dbref who, int can, - int cant); +void format_access(struct access *ap, int rulenum, + dbref who + __attribute__ ((__unused__)), char *buff, char **bp); +bool add_access_sitelock(dbref player, const char *host, dbref who, + uint32_t can, uint32_t cant); int remove_access_sitelock(const char *pattern); void do_list_access(dbref player); int parse_access_options - (const char *opts, dbref *who, int *can, int *cant, dbref player); + (const char *opts, dbref *who, uint32_t * can, uint32_t * cant, dbref player); #endif /* __ACCESS_H */ Index: hdrs/version.h =================================================================== --- hdrs/version.h (.../p4) (revision 1119) +++ hdrs/version.h (.../p5) (revision 1119) @@ -1,4 +1,4 @@ #define VERSION "1.8.3" -#define PATCHLEVEL "4" -#define PATCHDATE "[07/9/2007]" -#define NUMVERSION 1008003004 +#define PATCHLEVEL "5" +#define PATCHDATE "[10/06/2007]" +#define NUMVERSION 1008003005 Index: hdrs/externs.h =================================================================== --- hdrs/externs.h (.../p4) (revision 1119) +++ hdrs/externs.h (.../p5) (revision 1119) @@ -251,6 +251,13 @@ int all, int drain); void shutdown_queues(void); +/* Regexp saving helpers */ +struct re_save { + struct real_pcre *re_code; /**< The compiled re */ + int re_subpatterns; /**< The number of re subpatterns */ + int *re_offsets; /**< The offsets for the subpatterns */ + struct _ansi_string *re_from; /**< The positions of the subpatterns */ +}; /* From create.c */ dbref do_dig(dbref player, const char *name, char **argv, int tport); @@ -395,10 +402,12 @@ void chown_object(dbref player, dbref thing, dbref newowner, int preserve); /* From speech.c */ +int vmessageformat(dbref player, const char *attribute, + dbref executor, int flags, int nargs, ...); int messageformat(dbref player, const char *attribute, - dbref executor, int flags, const char *arg0, - const char *arg1, const char *arg2, - const char *arg3, const char *arg4, const char *arg5); + dbref executor, int flags, int nargs, char *argv[]); +void do_message_list(dbref player, dbref enactor, char *list, char *attrname, + char *message, int flags, int numargs, char *argv[]); const char *spname(dbref thing); void notify_except(dbref first, dbref exception, const char *msg, int flags); void notify_except2(dbref first, dbref exc1, dbref exc2, @@ -590,6 +599,7 @@ bool cs, char **, size_t, char *restrict, ssize_t); bool quick_regexp_match(const char *restrict s, const char *restrict d, bool cs); + bool qcomp_regexp_match(const pcre * re, const char *s); bool wild_match_case_r(const char *restrict s, const char *restrict d, bool cs, char **ary, size_t max, char *ata, ssize_t len); @@ -619,6 +629,8 @@ /* From function.c and other fun*.c */ extern char *strip_braces(char const *line); + void save_regexp_context(struct re_save *); + void restore_regexp_context(struct re_save *); void save_global_regs(const char *funcname, char *preserve[]); void restore_global_regs(const char *funcname, char *preserve[]); void save_partial_global_reg(const char *funcname, char Index: utils/gentables.c =================================================================== --- utils/gentables.c (.../p4) (revision 1119) +++ utils/gentables.c (.../p5) (revision 1119) @@ -107,6 +107,16 @@ }; +/* Color codes used in ansi markup */ +char ansi_codes[UCHAR_MAX + 1] = { + ['h'] = 1, ['i'] = 1, ['f'] = 1, ['u'] = 1, ['n'] = 1, + ['x'] = 1, ['r'] = 1, ['g'] = 1, ['y'] = 1, ['b'] = 1, + ['c'] = 1, ['m'] = 1, ['w'] = 1, + ['X'] = 1, ['R'] = 1, ['G'] = 1, ['Y'] = 1, ['B'] = 1, + ['C'] = 1, ['M'] = 1, ['W'] = 1, + ['/'] = 1, ['a'] = 1 +}; + /** Accented characters * * The table is for ISO 8859-1 character set. @@ -265,6 +275,7 @@ print_table_bool("char", "atr_name_table", attribute_names, 0); print_table_bool("char", "valid_timefmt_codes", valid_timefmt_codes, 0); print_table_bool("char", "escaped_chars", escaped_chars, 0); + print_table_bool("char", "valid_ansi_codes", ansi_codes, 0); print_entity_table("accent_table", entity_table); return EXIT_SUCCESS; } Index: utils/mkcmds.pl =================================================================== --- utils/mkcmds.pl (.../p4) (revision 1119) +++ utils/mkcmds.pl (.../p5) (revision 1119) @@ -187,10 +187,9 @@ print SRC "};\n"; close SRC; - print HDR "#endif /* SWITCHES_H */\n"; + print HDR "#endif /* SWITCHES_H */\n"; close HDR; - maybemove $temphdr, "hdrs/switches.h"; maybemove $tempsrc, "src/switchinc.c"; } Index: win32/funs.h =================================================================== --- win32/funs.h (.../p4) (revision 1119) +++ win32/funs.h (.../p5) (revision 1119) @@ -66,6 +66,7 @@ FUNCTION_PROTO(fun_cwho); FUNCTION_PROTO(fun_dbwalker); FUNCTION_PROTO(fun_dec); +FUNCTION_PROTO(fun_decode64); FUNCTION_PROTO(fun_decompose); FUNCTION_PROTO(fun_decrypt); FUNCTION_PROTO(fun_default); @@ -84,6 +85,7 @@ FUNCTION_PROTO(fun_elements); FUNCTION_PROTO(fun_elock); FUNCTION_PROTO(fun_emit); +FUNCTION_PROTO(fun_encode64); FUNCTION_PROTO(fun_encrypt); FUNCTION_PROTO(fun_endtag); FUNCTION_PROTO(fun_entrances); Index: win32/msvc.net/pennmush.vcproj =================================================================== --- win32/msvc.net/pennmush.vcproj (.../p4) (revision 1119) +++ win32/msvc.net/pennmush.vcproj (.../p5) (revision 1119) @@ -27,7 +27,7 @@