EmberMUD/
EmberMUD/clan/
EmberMUD/classes/
EmberMUD/doc/design/
EmberMUD/gods/
EmberMUD/log/
EmberMUD/notes/
EmberMUD/player/
EmberMUD/player/temp/
EmberMUD/src/MSVC/
EmberMUD/src/Sleep/
EmberMUD/src/StartMUD/
EmberMUD/src/Win32Common/
/******************************************************************************
 *  SSM v2.2 (shared string manager)                                          *
 *                                                                            *
 *  Copyright(C) 1996 Melvin Smith (Fusion) for EnvyMUD 2.2                   *
 *                                                                            *
 *  Due to alignment differences on 32 bit and 64 bit machines, memory        *
 *  usage is now virtually identical to standard Merc on 32-bit               *
 *  architecture, but slightly larger on 64-bit. Memory usage is still        *
 *  smaller than SSM 2.0 or earlier. The manager now uses short ints for      *
 *  the count and size of chunks, so to increase MAX_STRING you must          *
 *  increase MAX_CHUNKS instead. String have a max reference count of         *
 *  +32766 and max size of CHUNK_SIZE (0xfff0). Fragmentation is also         *
 *  handled more efficiently by marking failed chunks with -1 to temporarily  *
 *  disable them until a defrag_heap() recycles them. This helps when a       *
 *  4 byte chunk is freed low in the heap, so string_dup() doesn't walk       *
 *  the whole heap every time.                                                *
 *                                                                            *
 *  <msmith@falcon.mercer.peachnet.edu>                                       *
 *                                                                            *
 *  ROM2.4 modifications by Tom Adriaenssen (Jan 1996) -- Wreck               *
 *                                                                            *
 *  <tadriaen@zorro.ruca.ua.ac.be>                                            *
 *                                                                            *
 *  Removed ROM 2.4 modifications as Envy doesnt need *fread_string_eol -Kahn *
 *                                                                            *
 *****************************************************************************/

#include <sys/types.h>
#include <ctype.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include "merc.h"

#if !defined( ultrix )
#include <memory.h>
#endif

#define intType        short int
#define uintType       unsigned intType
#define intTypeSize  ( sizeof( intType ) )
#define addrType       void *
#define addrTypeSize ( sizeof( addrType ) )
#define addrSizeMask ( sizeof( addrType ) - 1 )

#if !defined( macintosh )
extern int _filbuf args( ( FILE * ) );
#endif

typedef struct BE BufEntry;

struct BE {
    BufEntry *next;
    uintType size;              /* size of the chunk (regardless of NULL CHAR) */
    intType usage;              /* how many pointers to the string */
    char buf[1];                /* chunk starts here */
};

/*
 * This is for the temporary hashing of strings at bootup to speedup
 * comparison/crunching of the string space. The temp_string_hash will
 * be freed after boot_done() is called.
 */
typedef struct TH TempHash;

struct TH {
    TempHash *next;
    uintType len;
    char *str;
};

TempHash **temp_string_hash;

/* These are the original Merc vars in db.c */
extern bool fBootDb;
char str_empty[1];
char *string_space;
char *top_string;
long nAllocString;
long sAllocString;
long nOverFlowString;
long sOverFlowString;

int numFree;
bool Full;

char *str_dup( const char * );
void free_string( char ** );
char *fread_string( FILE * );
void temp_hash_add( char *, const int );
char *temp_hash_find( const char * );
static unsigned long get_string_hash( register const char *key );

/*
 * ssm_buf_head points to start of shared space,
 * ssm_buf_free points to next free block
 */
BufEntry *ssm_buf_head = NULL, *ssm_buf_free = NULL;

/* To allocate more memory increase MAX_CHUNKS in merc.h. */
#define CHUNK_SIZE   0xfff0     /* DO NOT mess with this! */
long MAX_STRING = MAX_CHUNKS * CHUNK_SIZE;
int HEADER_SIZE;

/*
 * Not sure what is a good value for MAX_FREE 
 * If a dup fails str_dup will not defrag unless the number
 * of numFree >= MAX_FREE. numFree is NOT the current number of free blocks,
 * it is just a counter so defrag doesnt start dragging the game in the
 * case of a lot of failed dups.
 */
#define MAX_FREE     1000

void init_string_space(  )
{
    BufEntry *walk;
    int i;

    string_space = ( char * ) calloc( 1, MAX_STRING );
    if ( !string_space )
    {
#if defined(cbuilder)
        logf_string( "[SSM] Cant allocate shared string space." );
        return -1;
#else
        bug( "[SSM] Cant allocate shared string space.", 0 );
        exit( 1 );
#endif
    }

    top_string = string_space + MAX_STRING - 1;
    ssm_buf_head = ( BufEntry * ) string_space;
    HEADER_SIZE =
        ( int ) ( ( char * ) &ssm_buf_head->buf[0] - ( char * ) ssm_buf_head );

    walk = ssm_buf_head;
    for ( i = 0;; i++ )
    {
        walk->usage = 0;
        walk->size = CHUNK_SIZE - HEADER_SIZE;
        if ( i < MAX_CHUNKS - 1 )
        {
            walk->next = ( BufEntry * ) ( ( char * ) walk + CHUNK_SIZE );
            walk = walk->next;
            continue;
        }

        walk->next = NULL;
        break;
    }

    ssm_buf_free = ssm_buf_head;
    temp_string_hash = ( TempHash ** ) calloc( sizeof( TempHash * ),
                                               MAX_KEY_HASH );
}

int defrag_heap(  )
{
    /*
     * Walk through the shared heap and merge adjacent free blocks.
     * Free blocks are merged in str_free if free->next is free but
     * if the block preceding free is free, it stays unmerged. I would
     * rather not have the heap as a DOUBLE linked list for 2 reasons...
     *  (1) Extra 4 bytes per struct uses more mem
     *  (2) Speed - don't want to bog down str_ functions with heap management
     * The "orphaned" blocks will eventually be either merged or reused.
     * The str_dup function will call defrag if it cant allocate a buf.
     */

    BufEntry *walk = NULL, *last_free = NULL, *next = NULL;
    int merges = 0;
    ssm_buf_free = 0;

    for ( walk = ssm_buf_head, last_free = 0; walk; walk = next )
    {
        next = walk->next;
        if ( walk->usage > 0 )
        {
            /* this block is in use so set last_free to NULL */
            last_free = 0;
            continue;
        }
        else if ( !last_free )
        {
            /* OK found a NEW free block, set last_free and move to next */
            last_free = walk;
            if ( !ssm_buf_free )
                ssm_buf_free = walk;

            continue;
        }
        else
        {
            /* previous block free so merge walk into last_free and move on */
            if ( ( long ) last_free->size + ( long ) walk->size <= CHUNK_SIZE )
            {
                merges++;
                last_free->size += walk->size + HEADER_SIZE;
                last_free->next = walk->next;
                last_free->usage = 0;
            }
            else
                last_free = walk;
        }
    }

    if ( merges )
        bug( "[SSM] defrag_heap : made %d block merges.", merges );
    else
        bug( "[SSM] defrag_heap : resulted in 0 merges.", 0 );

    /* Start count over again */
    numFree = 0;
    return merges;
}

/*
 * Dup a string into shared space. If string exists, the usage count
 * gets incremented and the reference is returned. If the string does
 * not exist in heap, space is allocated and usage is 1.
 * This is a linked list first fit algorithm, so strings can be
 * freed. Upon bootup, there is a seperate hash table constructed in order
 * to do crunching, then the table is destroyed.
 */
char *str_dup( const char *str )
{
    BufEntry *ptr;
    char *str_new;
    int len;
    int rlen;

    if ( !str || !*str )
        return &str_empty[0];

    if ( str > string_space && str < top_string )
    {
        ptr = ( BufEntry * ) ( str - HEADER_SIZE );
        if ( ptr->usage <= 0 )
        {
            bug( "str_dup : invalid str", 0 );
            bug( str, 0 );
        }

        ptr->usage++;
        return ( char * ) str;
    }

    rlen = len = ( int ) strlen( str ) + 1;

    /* 
     * Round up to machine dependant address size.
     * Don't remove this, because when the BufEntry struct is overlaid
     * the struct must be aligned correctly.
     */

    if ( ( len + HEADER_SIZE ) & addrSizeMask )
        len += addrTypeSize - ( ( len + HEADER_SIZE ) & addrSizeMask );

    if ( ssm_buf_free )
    {
      RETRY:
        for ( ptr = ssm_buf_free; ptr; ptr = ptr->next )
            if ( ptr->usage == 0 && ptr->size >= len )
                break;

        if ( !ptr )
        {
            if ( numFree >= MAX_FREE )
            {
                int merges;

                bug( "[SSM] Attempting to optimize shared string heap.", 0 );
                merges = defrag_heap(  );

                /* goto is fine because defrag will return 0 next time */
                if ( merges )
                    goto RETRY;
            }

            str_new = ( char * ) malloc( rlen );
            strcpy( str_new, str );
            sOverFlowString += rlen;
            nOverFlowString++;
            return str_new;
        }

        /* If there is at least header size excess break it up */
        else if ( ptr->size - len > HEADER_SIZE )
        {
            BufEntry *temp;

            /* WARNING! - DONT REMOVE THE CASTS BELOW! - Fusion */
            temp = ( BufEntry * ) ( ( char * ) ptr + HEADER_SIZE + len );
            temp->size = ptr->size - ( len + HEADER_SIZE );
            temp->next = ptr->next;
            temp->usage = 0;
            ptr->size = len;
            ptr->next = temp;
            ptr->usage = 1;
            ssm_buf_free = temp;
        }
        else
        {
            ptr->usage = 1;
            if ( ptr != ssm_buf_free )
                ssm_buf_free->usage--;  /* buf_free was skipped */

            for ( ssm_buf_free = ssm_buf_head; ssm_buf_free;
                  ssm_buf_free = ssm_buf_free->next )
                if ( ssm_buf_free->usage == 0 )
                    break;
        }

        str_new = ( char * ) &ptr->buf[0];
        strcpy( str_new, str );
        nAllocString++;
        sAllocString += ptr->size + HEADER_SIZE;
    }
    else
    {
        /* A one time toggle just for bugging purposes */
        if ( !Full )
        {
            bug( "[SSM] The shared string heap is full!", 0 );
            Full = 1;
        }

        str_new = ( char * ) malloc( rlen );
        strcpy( str_new, str );
        sOverFlowString += rlen;
        nOverFlowString++;
    }

    return str_new;
}

/*
 * If string is in shared space, decrement usage, if usage then is 0,
 * free the chunk and attempt to merge with next node. Other
 * strings are freed with standard free.
 * Never call free/delete externally on a shared string.
 *
 * You must now pass a pointer to the string pointer you're wanting to free.  This
 * way free_string can set that pointer to NULL and not rely on the caller to 
 * set the pointer to null after call free_string.  This method makes it so
 * no-one can free the same string twice (which was causing major string-space
 * corruption). -Zane
 *
 */
void free_string( char **strptr )
{
    char *str;
    BufEntry *ptr;

    str = *strptr;

    if ( !str )
        return;

    if ( str == &str_empty[0] )
    {
        *strptr = NULL;
        return;
    }

    if ( str > string_space && str < top_string )
    {
        ptr = ( BufEntry * ) ( str - HEADER_SIZE );

        if ( --ptr->usage > 0 )
        {
            *strptr = NULL;
            return;
        }
        else if ( ptr->usage < 0 )
        {
            /* Undo the change we made above, avoid memory corruption if possible. */
            ptr->usage++;
            bug( "SSM:free_string: multiple free or invalid string.", 0 );
            bug( str, 0 );
            *strptr = NULL;
            return;
        }

        numFree++;
        sAllocString -= ( ptr->size + HEADER_SIZE );
        nAllocString--;

        if ( ssm_buf_free > ptr )
            ssm_buf_free = ptr;

        if ( fBootDb )
        {
            TempHash *ptr;
            TempHash *walk;
            unsigned long ihash = get_string_hash( str );

            for ( ptr = temp_string_hash[ihash]; ptr; ptr = ptr->next )
            {
                if ( ptr->str != str )
                    continue;
                else if ( ptr == temp_string_hash[ihash] )
                    temp_string_hash[ihash] = ptr->next;
                else
                    for ( walk = temp_string_hash[ihash]; walk;
                          walk = walk->next )
                    {
                        if ( walk->next == ptr )
                        {
                            walk->next = ptr->next;
                            break;
                        }
                    }

                free( ptr );
                break;
            }
        }
        *strptr = NULL;
        return;
    }

    sOverFlowString -= strlen( str ) + 1;
    nOverFlowString--;

    if ( sOverFlowString < 0 || nOverFlowString < 0 )
        bug( "SSM:free_string: string free from outside SS space.", 0 );

    free( str );
    *strptr = NULL;
    return;
}

/*
 * Read and allocate space for a string from a file.
 * This replaces db.c fread_string
 * This is modified version of Furey's fread_string from Merc
 */
char *fread_string( FILE * fp )
{
    char buf[MAX_STRING_LENGTH * 4];
    register char *ptr = buf;
    register char c;
    int len;

/*    *status = 0;*/

    do
        c = getc( fp );
    while ( isspace( c ) );

    if ( ( *ptr++ = c ) == '~' )
        return &str_empty[0];

    for ( ;; )
    {
        switch ( *ptr = getc( fp ) )
        {
        default:
            ptr++;
            break;

        case EOF:
            bug( "Fread_string: EOF", 0 );
/*	    	*status = 1; */
            return NULL;
            break;

        case '\n':
            *ptr++ = '\r';
            *ptr++ = '\n';
            break;

        case '\r':
            break;

        case '~':
            *ptr = '\0';
            if ( fBootDb )
            {
                len = ptr - buf;

                ptr = temp_hash_find( buf );
                if ( ptr )
                    return str_dup( ptr );

                ptr = str_dup( buf );
                temp_hash_add( ptr, len );
                return ptr;
            }

            return str_dup( buf );
        }
    }
}

char *fread_string_eol( FILE * fp )
{
    signed char buf[MAX_STRING_LENGTH * 4];
    register signed char *ptr = buf;
    register signed char c;
    int len;

    /*
     * Skip blanks.
     * Read first char.
     */
    do
        c = getc( fp );
    while ( c == ' ' || c == '\t' );

    *ptr = c;

    while ( *ptr != '\n' && *ptr != '\r' && *ptr != EOF )
        *++ptr = getc( fp );

    if ( ptr == buf )
        return &str_empty[0];

    *ptr = '\0';

    if ( fBootDb )
    {
        len = ptr - buf;

        ptr = temp_hash_find( buf );
        if ( ptr )
            return str_dup( ptr );

        ptr = str_dup( buf );
        temp_hash_add( ptr, len );
        return ptr;
    }

    return str_dup( buf );
}

/*
 * Read string into user supplied buffer.
 * Modified version of fread_string_eol
 */
int temp_fread_string_eol( FILE * fp, char *outbuf )
{
    register signed char *ptr = outbuf;
    register signed char c;
    bool bEOF = FALSE;

    /*
     * Skip blanks.
     * Read first char.
     */
    do
        c = getc( fp );
    while ( c == ' ' || c == '\t' );

    *ptr = c;

    while ( *ptr != '\n' && *ptr != '\r' && *ptr != EOF )
        *++ptr = getc( fp );

    if ( *ptr == EOF )
        bEOF = TRUE;

    *ptr = '\0';

    return bEOF;
}

/*
 * Read string into user supplied buffer.
 * Modified version of Furey's fread_string
 */
void temp_fread_string( FILE * fp, char *outbuf )
{
    char *ptr = outbuf;
    char c;

    do
    {
        c = getc( fp );
    }
    while ( isspace( c ) );

    if ( ( *ptr++ = c ) == '~' )
    {
        *outbuf = '\0';
        return;
    }

    for ( ;; )
    {
        switch ( *ptr = getc( fp ) )
        {
        default:
            ptr++;
            break;

        case EOF:
#if defined(cbuilder)
            logf_string( "Fread_string: EOF" );
            return -1;
#else
            bug( "Fread_string: EOF", 0 );
            exit( 1 );
#endif
            break;

        case '\n':
            ptr++;
            *ptr++ = '\r';
            break;

        case '\r':
            break;

        case '~':
            *ptr = '\0';
            return;
        }
    }
}

/*--- ElfHash ---------------------------------------------------
 *  The published hash algorithm used in the UNIX ELF format
 *  for object files. Accepts a pointer to a string to be hashed
 *  and returns an unsigned long.
 *-------------------------------------------------------------*/

static unsigned long get_string_hash( register const char *key )
{
    register unsigned long hash = 0, g;

    while ( *key )
    {
        hash = ( hash << 4 ) + *key++;
        if ( ( g = hash & 0xF0000000 ) )
            hash ^= g >> 24;
        hash &= ~g;
    }

    return hash % MAX_KEY_HASH;
}

/* Lookup the string in the boot-time hash table. */
char *temp_hash_find( const char *str )
{
    TempHash *ptr;
    unsigned long ihash;

    if ( !fBootDb || !*str )
        return 0;

    ihash = get_string_hash( str );

    for ( ptr = temp_string_hash[ihash]; ptr; ptr = ptr->next )
    {
        if ( *ptr->str != *str )
            continue;
        else if ( strcmp( ptr->str, str ) )
            continue;
        else
            return ptr->str;
    }

    return 0;
}

/*
 * Add a reference in the temporary hash table.
 * String is still in the linked list structure but
 * reference is kept here for quick lookup at boot time;
 */
void temp_hash_add( char *str, const int len )
{
    TempHash *add;
    unsigned long ihash;

    if ( !fBootDb || !*str || ( str <= string_space && str >= top_string ) )
        return;

    ihash = get_string_hash( str );

    add = ( TempHash * ) malloc( sizeof( TempHash ) );
    add->next = temp_string_hash[ihash];
    temp_string_hash[ihash] = add;
    add->len = len;
    add->str = str;
}

/* Free the temp boot string hash table */
void boot_done( void )
{
    TempHash *ptr, *next;
    int ihash;

    for ( ihash = 0; ihash < MAX_KEY_HASH; ihash++ )
    {
        for ( ptr = temp_string_hash[ihash]; ptr; ptr = next )
        {
            next = ptr->next;
            free( ptr );
        }
    }

    free( temp_string_hash );
    temp_string_hash = 0;       /* Bug check in case someone accesses later */
}