ftp.nice.ch/pub/next/unix/mail/smail3.1.20.s.tar.gz#/smail3.1.20/src/hash.c

This is hash.c in view mode; [Download] [Up]

/* @(#)src/hash.c	1.3 02 Dec 1990 06:19:24 */

/*
 *    Copyright (C) 1987, 1988 Ronald S. Karr and Landon Curt Noll
 * 
 * See the file COPYING, distributed with smail, for restriction
 * and warranty information.
 */

/*
 * hash:
 *	perform a string hashing algorithm functions
 *
 * 	Hash tables are defined by their size.  It is suggested that the
 * 	size be a prime value, say:
 *	    13, 29, 47, 61, 113, 181, 251, 359, 509, 751, 1021, 1511,
 *	    2039, 3079, 4093, 6151, 8179, 12377, 16381, 24571, 32749,
 *	    49139, 65557, 98299, 132049, 180023, 216091, 321367, 521539, ...
 * 	An advantage with the above primes is that they are at last 3 less
 * 	than 2^n, and no closer than 3 away from 3*(2^m) for the larger 
 * 	values of m.  Most malloc systems are most efficient when one allocates
 * 	3 words less than 2^n bytes, i.e., 4*(2^n - 3) bytes.
 *
 *	external functions:
 *		add_to_hash, lookup_in_hash, delete_from_hash,
 *		delete_from_hash, store_in_hash, new_hash_table,
 *		write_hash_table, walk_hash, free_hash_element,
 *		free_hash_table
 */
#include <stdio.h>
#include <ctype.h>
#include "smail.h"
#include "defs.h"
#include "exitcodes.h"
#include "hash.h"
#include "alloc.h"
#ifndef DEPEND
# include "extern.h"
# include "debug.h"
#endif

/* static functions used in this file */
static int hash_str();
static int hash_stric();
static struct hash *new_hash_element();

#ifdef STANDALONE
int debug = 0;	/* default debug level is NONE */
#define errfile stderr
void panic();
#define xmalloc(x) (malloc(x))	/* naive allocator */
#define bmalloc(x,y) (malloc(x))	/* naive allocator */
char *malloc();
#define xfree(x) (free(x))	/* naive deallocator */
#define bfree(x,y) (free(x))	/* naive deallocator */
void free();
#endif	/* STANDALONE */

/*
 * ETHEREAL_HASHDATA -
 *
 *    This file contains a nearly complete implementation of a general
 *    hashing system.  However it is likely that smail itself will
 *    never need to delete, replace, or store new data over old data.
 *    That is, smail will only create, add, lookup and perform hash
 *    table file operations.
 *
 *    The code ifdef-ed out does work and is tested by the STANDALONE code.
 *    You should define ETHEREAL_HASHDATA if you want to use it
 */


/*
 * hash_str - convert a trying into a hash value
 *
 * We build the hash value one character at a time by repeating the following
 * steps for each character:
 *
 *	1) The previous value is shifted up (by HASH_UP_SHIFT) and the
 *	   current character is added
 *	2) any upper level excess bits are fetched (by masking with
 *	   {H,L}_HASH_MASK) and xor-ed into bits near the bottom
 *	3) the upper level excess bits are cleared
 *
 * In the end, the hash value is taken modulo `mod' to produce a slot number.
 *
 * input:
 *	str	- string to hash
 *	mod	- number of hash table entries
 * output:
 *	the slot number on which `str' belongs
 *
 * NOTE: For more optimal hashing of smaller hash tables (entries < HASH_LEVEL)
 *       we L_ value constants.  This gives us a faster hash fold.  For larger
 *	 hash tables, this is not needed so H_ value constants are used.
 *
 * NOTE: mod should be a prime <= ~H_HASH_MASK
 */
static int
hash_str(str, mod)
    register char *str;			/* the string to hash */
    int mod;				/* prime modulus, size of hash table */
{
    register unsigned long val;		/* current hash value */
    register unsigned long excess;	/* upper/excess bits in val */
    register unsigned long c;		/* the current string character */

    /* firewall - bogus case, but be safe anyway */
    if (str == NULL) {
	return 0;
    }

    /*
     * hash each character in the string
     *
     * if our mod is small enough, then use L_ value constants so that
     * strings can fold into themselves faster.
     */
    if (mod < HASH_LEVEL) {
	/* hash each character using the L_ values */
	for (val = 0; c=(unsigned long)*str; ++str) {
	    val = (val << HASH_UP_SHIFT) + c;
	    val ^= ((excess = (val&L_HASH_MASK)) >> L_DOWN_SHIFT);
	    val ^= excess;
	}
    } else {
	/* hash each character using the H_ values */
	for (val = 0; c=(unsigned long)*str; ++str) {
	    val = (val << HASH_UP_SHIFT) + c;
	    val ^= ((excess = (val&H_HASH_MASK)) >> H_DOWN_SHIFT);
	    val ^= excess;
	}
    }
    
    return (int)(val%mod);    /* our hash value, mod the hash size */
}

/*
 * hash_stric - convert a trying into a hash value without regard to case
 *
 * We build the hash value one character at a time by repeating the following
 * steps for each character:
 *
 *	1) The previous value is shifted up (by HASH_UP_SHIFT) and the
 *	   current character is added
 *	2) any upper level excess bits are fetched (by masking with
 *	   {H,L}_HASH_MASK) and xor-ed into bits near the bottom
 *	3) the upper level excess bits are cleared
 *
 * In the end, the hash value is taken modulo `mod' to produce a slot number.
 *
 * input:
 *	str	- string to hash without regard to case
 *	mod	- number of hash table entries
 * output:
 *	the slot number on which `str' belongs
 *
 * NOTE: For more optimal hashing of smaller hash tables (entries < HASH_LEVEL)
 *       we L_ value constants.  This gives us a faster hash fold.  For larger
 *	 hash tables, this is not needed so H_ value constants are used.
 *
 * NOTE: mod should be a prime <= ~H_HASH_MASK
 */
static int
hash_stric(str, mod)
    register char *str;		/* the string to hash disregarding case */
    int mod;			/* prime modulus, size of hash table */
{
    register unsigned long val;		/* current hash value */
    register unsigned long excess;	/* upper/excess bits in val */
    register unsigned long c;		/* the current string character */

    /* firewall - bogus case, but be safe anyway */
    if (str == NULL) {
	return 0;
    }

    /*
     * hash each character in the string
     *
     * if our mod is small enough, then use L_ value constants so that
     * strings can fold into themselves faster.
     */
    if (mod < HASH_LEVEL) {
	/* hash each character using the L_ values */
	for (val = 0; c=(unsigned long)lowercase(*str); ++str) {
	    val = (val << HASH_UP_SHIFT) + c;
	    val ^= ((excess = (val&L_HASH_MASK)) >> L_DOWN_SHIFT);
	    val ^= excess;
	}
    } else {
	/* hash each character using the H_ values */
	for (val = 0; c=(unsigned long)lowercase(*str); ++str) {
	    val = (val << HASH_UP_SHIFT) + c;
	    val ^= ((excess = (val&H_HASH_MASK)) >> H_DOWN_SHIFT);
	    val ^= excess;
	}
    }
    
    return (int)(val%mod);    /* our hash value, mod the hash size */
}


#ifdef ETHEREAL_HASHDATA
/*
 * walk_hash - walk thru a hash table
 *
 * returns NULL if there is no next element in the hash table
 *
 * input:
 *	cur	- our current location, or NULL to start at the beginning
 *	table	- the table to be walked
 * output:
 *	a pointer to the next element, or NULL if no next element
 *
 * WARNING: results will be unpredictable or fatal if `cur' != NULL, and
 *	    `cur' != `the previous walk location', and `cur' is an element
 *	    that has been either deleted or replaced by another element.
 *	    It should be noted that this `cur' will never be the `previous
 *	    walk location' if our previous call ran off the end of the table.
 */
struct hash *
walk_hash(cur, table)
    struct hash *cur;		/* where we are now */
    struct hash_table *table;	/* hash table being walked */
{
    register struct hash *prev;	/* our previous walk location */
    register int indx;		/* our previous hash slot */
    register int len;		/* the table length */

    /*
     * firewall
     */
    if (table == NULL) {
	panic(EX_SOFTWARE, "walk_hash: table is NULL");
    }
    /* fetch these values for faster use */
    prev = table->prev;
    indx = table->indx;
    len = table->len;

    /*
     * find the first hash slot if cur is NULL (due to a restart request)
     */
    if (cur == NULL) {
	/* note that we really don't have a location yet */
	prev = NULL;

	/* find the first slot and return it */
	for (indx=0; indx < len; ++indx) {
	    if (full_slot(table->slot[indx])) {
		/* we found the first entry of the first non-empty chain */
		prev = table->slot[indx];
		break;
	    }
	}

    /*
     * walk from our current location to the next location
     */
    } else {

	/*
	 * if `cur' is not the previous `cur', then find `cur' and
	 * note where our hash table index now resides
	 */
	if (cur != prev) {
	    /* find the hash table index */
	    indx = hash_string(cur->keystr, len, table->flags&HASH_STRCMP);
	    /* if `cur' is an empty slot, panic */
	    if (empty_slot(table->slot[indx])) {
		    panic(EX_SOFTWARE, "walk_hash: <%s> hash slot is empty",
			  cur->keystr);
	    }
	
	    /* walk down the hash table chain looking for our entry */
	    for (prev = table->slot[indx];
		 cur != prev && prev != NULL;
		 prev = next_hash(prev,table)) {
	    }
	}
	/* if `cur' is not in the hash table, panic */
	if (prev == NULL) {
	    panic(EX_SOFTWARE, "walk_hash: <%s> is not in table", cur->keystr);
	}

	/*
	 * if we were at the end of a chain, then our successor will
	 * be the start of the next non-empty chain
	 */
	if ((prev = next_hash(prev,table)) == NULL) {
	    /* find the next non-empty chain */
	    for (++indx; indx < len; ++indx) {
		if (full_slot(table->slot[indx])) {
		    /* return first element of this chain */
		    prev = table->slot[indx];
		    break;
	        }
	    }
	}
    }

    /*
     * return the pointer the next element or NULL
     */
    /* remember our location for next time */
    table->prev = hash_addr(prev, table);
    table->indx = indx;
    return table->prev;
}
#endif	/* ETHEREAL_HASHDATA */


/*
 * new_hash_element - creat a new hash element
 *
 * return a malloced new hash element with the lengths correctly filled out
 *
 * inputs:
 *	keystr	- the key of this data element
 *	data	- the data to accompany `keystr', or NULL if no data
 *	datalen	- the length of `data' in bytes, or 0 is no data
 *	table	- the hash table which will get this element
 */
static struct hash *
new_hash_element(keystr, data, datalen, table)
    char *keystr;    			/* the keystring to be added */
    char *data;				/* the associated data if any */
    int datalen;			/* length of data,  0 ==> no data */
    struct hash_table *table;		/* hash table being added to */
{
    struct hash *new;		/* the new slot chain location */
    int keylen;			/* the length of the string, padded */
    unsigned long lk;		/* temp var for key length */

    /*
     * firewall - check for bad pointers and values
     */
    if (keystr == NULL || table == NULL) {
	panic(EX_SOFTWARE, "new_hash_element: NULL keystring or table");
    }
    lk = keystr_len(keystr);		/* compute padded key length */
    if (lk >= (unsigned long)(1L<<BITS_PER_SHORT)) {
	panic(EX_SOFTWARE, "new_hash_element: key too long");
    }
    keylen = (int)lk;		/* now we know it will fit in an int */
    /* firewall - check against bad data being passed to us */
    if (datalen < 0 || (datalen > 0 && data == NULL) || 
      (unsigned long)datalen >= (unsigned long)(1L<<BITS_PER_SHORT))  {
	panic(EX_SOFTWARE,
	    "new_hash_element: bad data passed with: <%s>  datalen: <%d>",
	    keystr, datalen);
    }

    /*
     * malloc the storage
     */
    new = (struct hash *)bmalloc( hashslot_size(keylen,datalen), table->life );
    /* firewall */
    if (is_odd(new)) {
	panic(EX_SOFTWARE,
	      "new_hash_element: malloc returned odd address: %lx",
	      (long)new);
    }

    /*
     * return the prebuild element
     */
    new->succ = NULL;
    new->keylen = keylen;
    strcpy(new->keystr, keystr);
    new->datalen = datalen;
    if (datalen > 0) {
	memcpy(hash_data(new), data, datalen);
    }
    return new;
}

#ifdef ETHEREAL_HASHDATA
/*
 * free_hash_element - free an old hash element
 *
 * Frees a hash table element according to the life of the hash table.
 * Removes the hash table element if it in the hash table unless explicitly
 * told that the element is not in the table.
 *
 * inputs:
 *	cur	- the element which which we will free
 *	search	- non-zero means delete `cur' from table prior to the free
 *	table	- the table on which `cur' resides, or the table to which
 *		  `cur' would have been added
 *
 * WARNING: It is important that the `cur' element NOT be in a hash table
 *	    after the free.  Unpredictable results will happen otherwise.
 *	    If `search' is non-zero, we will first attempt to delete `cur'
 *	    from `table'.  It is a fatal error if `search' is non-zero and
 *	    `cur' is not in `table'.
 *
 * WARNING: It is important that `cur' was individually malloced (perhaps
 *	    by new_hash_element) so that the free of its address will
 *	    be valid.
 */
void
free_hash_element(cur, search, table)
    struct hash *cur;		/* what we will delete */
    int search;			/* non-zero => delete `cur' first */
    struct hash_table *table;	/* table `cur' does/would_have belonged to */
{
    /*
     * firewall - check for bad pointers and values
     */
    if (cur == NULL || table == NULL) {
	panic(EX_SOFTWARE, "free_hash_element: NULL cur or table");
    }

    /*
     * delete the element first if requested
     */
    if (search != 0 && delete_from_hash(cur->keystr, table) == NULL) {
	panic(EX_SOFTWARE,"free_hash_element: <%s> not in table",cur->keystr);
    }

    /*
     * free the storage
     */
    bfree(cur, table->life);
    return;
}
#endif	/* ETHEREAL_HASHDATA */

/*
 * new_hash_table - creat a new hash table
 *
 * return a malloced new hash table with correctly setup initial pointers
 *
 * input:
 *	tablelen - number of slots in the hash table
 *	life - the alloc block to which this is to be associated, or NULL
 *	       meaning the permanent block
 * output:
 *	a pointer to a malloced empty hash table
 */
struct hash_table *
new_hash_table(tablelen, life, flags)
    int tablelen;		/* number of slots in the hash table */
    struct block *life;		/* is the hash table permanent or temporary */
    int flags;			/* hash table flag as per struct hash_table */
{
    register int i;			/* index */
    struct hash_table *table;		/* the malloced hash table */

    /*
     * firewalls
     */
    if (tablelen <= 0) {
	panic(EX_SOFTWARE, "new_hash_table: tablelen: %d", tablelen);
    }
    DEBUG3(DBG_HASH_LO,
	   "new_hash_table: tablelen:%d life:%d flag:%d\n",tablelen,life,flags);

    /*
     * malloc the hash table
     */
    table = (struct hash_table *)bmalloc(table_size(tablelen), life);
    /* firewall */
    if (is_odd(table)) {
	panic(EX_SOFTWARE,
	    "new_hash_table: malloc returned odd address: %d", (int)table);
    }

    /*
     * initialize the table
     */
    table->len = tablelen;
    table->flags = flags & HASH_FLAGMASK;
    table->life = life;
    table->prev = NULL;		/* no current walk_hash() location */
    table->indx = 0;		/* no current walk_hash() slot index */
    for (i=0; i < tablelen; i++) {
	table->slot[i] = NULL;
    }
    return table;	/* return our new table */
}

#ifdef ETHEREAL_HASHDATA
/*
 * free_hash_table - free a hash table and its associated data
 *
 * Free all storage associated with a hash table.
 *
 * NOTE: any malloced elements should be freed prior to calling this routine.
 *
 * input:
 *	table	- the hash table to free
 */
void
free_hash_table(table)
    struct hash_table *table;	/* the hash table to free */
{
    struct hash *cur;		/* current element to delete */

    /*
     * firewalls
     */
    if (table == NULL ) {
	panic(EX_SOFTWARE, "free_hash_table: NULL table");
    }
    DEBUG(DBG_HASH_LO,"free_hash_table: start\n");

    /*
     * free the table slots
     */
    bfree(table, table->life);
    return;
}
#endif	/* ETHEREAL_HASHDATA */


/*
 * add_to_hash - add an element to the a hash table
 *
 * inputs:
 *	keystr	- the key of the data to add
 *	data	- the data to accompany `keystr', or NULL if no data
 *	datalen	- the length of `data' in bytes, or 0 if no data
 *	table	- a pointer to the hash table which is being modified
 * output:
 *	returns ALREADY_HASHED if `keystr' is already in the `table', or
 *	JUST_HASHED if we just added a no key.  The `table' is not modified
 *	if the key is already exists.
 */
int
add_to_hash(keystr, data, datalen, table)
    char *keystr;    			/* the keystring to be added */
    char *data;				/* the associated data if any */
    int datalen;			/* length of data,  0 ==> no data */
    struct hash_table *table;		/* the hash table to add it to */
{
    register struct hash *cur;		/* the current slot chain location */
    register struct hash *prev;		/* the previous slot chain location */
    register int cmp;			/* -1, 0, or 1 for < = > compare */
    register int caseflag;		/* 0 ==> use strcmp, 1 ==> strcmpic */
    int loc;				/* the hash slot to add onto */
    struct hash *new;			/* the new slot chain location */

    /*
     * firewall - watch for NULLs
     */
    if (keystr == NULL) {
	panic(EX_SOFTWARE, "add_to_hash: NULL keystr");
    }
    if (table == NULL) {
	panic(EX_SOFTWARE, "add_to_hash: NULL table");
    }

    /*
     * determine the slot on which this entry is to be added
     */
    caseflag = table->flags & HASH_STRCMP;
    loc = hash_string(keystr, table->len, caseflag);
    DEBUG2(DBG_HASH_LO, "add_to_hash: keystr: <%s> slot: %d\n", keystr, loc);

    /*
     * search the slot chain for our entry
     */
    /* special case for empty slot chains */
    if (empty_slot(table->slot[loc])) {
	DEBUG(DBG_HASH_MID, "add_to_hash: insert in NULL slot\n");
	new = new_hash_element(keystr, data, datalen, table);
	insert_hash(&table->slot[loc], new);
	return JUST_HASHED;
    }

    /* 
     * search the chain
     */
    DEBUG2(DBG_HASH_VHI, "add_to_hash: slot:0x%lx cur:0x%lx\n",
	   (long)table->slot[loc], (long)table->slot[loc]);
    for (prev=NULL, cur=table->slot[loc];
	 cur != NULL; prev=cur, cur=next_hash(cur,table)) {
	/* 
	 * if we found the entry, stop
	 */
	DEBUG2(DBG_HASH_VHI, "add_to_hash: comparing <%s> to <%s>",
	       keystr, cur->keystr);
	if ((cmp = stringcmp(keystr, cur->keystr, caseflag)) == 0) {
	    DEBUG(DBG_HASH_MID, "add_to_hash: already hashed\n");
	    return ALREADY_HASHED;

	/* 
	 * we are past the insertion point, insert before here and stop
	 * note if we are inserting at the beginning a a chain or in the middle
	 */
	} else if (cmp < 0) {
	    new = new_hash_element(keystr, data, datalen, table);
	    if (prev == NULL) {
		DEBUG(DBG_HASH_MID, "add_to_hash: insert at front\n");
		insert_hash(&table->slot[loc], new);  /* insert at beginning */
	    } else {
		DEBUG(DBG_HASH_MID, "add_to_hash: insert in middle\n");
		insert_hash(prev, new);	/* insert in middle */
	    }
	    return JUST_HASHED;
	}
    }

    /* 
     * case: insertion at the end of the chain
     */
    DEBUG(DBG_HASH_MID, "add_to_hash: insert at END\n");
    new = new_hash_element(keystr, data, datalen, table);
    insert_hash(prev, new);
    return JUST_HASHED;
}

#ifdef ETHEREAL_HASHDATA
/*
 * replace_in_hash - replace an existing element in a hash table
 *
 * inputs:
 *	keystr	- the key of the data to replace
 *	data	- the data to accompany `keystr', or NULL if no data
 *	datalen	- the length of `data' in bytes, or 0 if no data
 *	table	- a pointer to the hash table which is being modified
 * output:
 *	returns a pointer to the element that was replaced, or NULL
 *	if no element was replaced due to `keystr' not in `table'
 */
struct hash *
replace_in_hash(keystr, data, datalen, table)
    char *keystr;    			/* the keystring to replace */
    char *data;				/* the associated data if any */
    int datalen;			/* length of data,  0 ==> no data */
    struct hash_table *table;		/* the hash table to add it to */
{
    register struct hash *cur;		/* the current slot chain location */
    register struct hash *prev;		/* the previous slot chain location */
    register int cmp;			/* -1, 0, or 1 for < = > compare */
    register int caseflag;		/* 0 ==> use strcmp, 1 ==> strcmpic */
    int loc;				/* the hash slot to add onto */
    struct hash *new;			/* the new slot chain location */

    /*
     * firewall - watch for NULLs
     */
    if (keystr == NULL) {
	panic(EX_SOFTWARE, "replace_in_hash: NULL keystr");
    }
    if (table == NULL) {
	panic(EX_SOFTWARE, "replace_in_hash: NULL table");
    }

    /*
     * determine the slot on which this entry is to be added
     */
    caseflag = table->flags & HASH_STRCMP;
    loc = hash_string(keystr, table->len, caseflag);
    DEBUG2(DBG_HASH_LO, "replace_in_hash: keystr: <%s> slot: %d\n",keystr,loc);

    /*
     * search the slot chain for our entry
     */
    /* special case for empty slow chains */
    if (empty_slot(table->slot[loc])) {
	DEBUG(DBG_HASH_MID, "replace_in_hash: slot NULL\n");
	return NULL;	/* no entry to replace */
    }

    /* 
     * search the chain
     */
    for (prev=NULL, cur=table->slot[loc]; cur != NULL; 
      prev=cur, cur=next_hash(cur, table)) {
	/* 
	 * if we found the entry, stop
	 */
	if ((cmp = stringcmp(keystr, cur->keystr, caseflag)) == 0) {
	    new = new_hash_element(keystr, data, datalen, table);
	    if (prev == NULL) {
		DEBUG(DBG_HASH_MID, "replace_in_hash: replaced at front\n");
		/* insert at beginning */
		replace_hash(table->slot[loc], cur, new);
	    } else {
		DEBUG(DBG_HASH_MID, "replace_in_hash: replaced in middle\n");
		replace_hash(prev->succ, cur, new);	/* insert in middle */
	    }
	    return cur;
	/* if we have gone past our entry, stop searching */
        } else if (cmp < 0) {
	    break;
	}
    }

    /* 
     * entry not found, nothing to replace
     */
    DEBUG(DBG_HASH_MID, "replace_in_hash: not found\n");
    return NULL;
}

/*
 * store_in_hash - store an existing element in a hash table
 *
 * inputs:
 *	keystr	- the key of the data to store
 *	data	- the data to accompany `keystr', or NULL if no data
 *	datalen	- the length of `data' in bytes, or 0 if no data
 *	table	- a pointer to the hash table which is being modified
 * output:
 *	returns a pointer to the element that was replaced, or NULL
 *	if no element was replaced.  In any case the element is added.
 */
struct hash *
store_in_hash(keystr, data, datalen, table)
    char *keystr;    			/* the keystring to replace */
    char *data;				/* the associated data if any */
    int datalen;			/* length of data,  0 ==> no data */
    struct hash_table *table;		/* the hash table to add it to */
{
    register struct hash *cur;		/* the current slot chain location */
    register struct hash *prev;		/* the previous slot chain location */
    register int cmp;			/* -1, 0, or 1 for < = > compare */
    register int caseflag;		/* 0 ==> use strcmp, 1 ==> strcmpic */
    int loc;				/* the hash slot to add onto */
    struct hash *new;			/* the new slot chain location */

    /*
     * firewall - watch for NULLs
     */
    if (keystr == NULL) {
	panic(EX_SOFTWARE, "store_in_hash: NULL keystr");
    }
    if (table == NULL) {
	panic(EX_SOFTWARE, "store_in_hash: NULL table");
    }

    /*
     * determine the slot on which this entry is to be added
     */
    caseflag = table->flags & HASH_STRCMP;
    loc = hash_string(keystr, table->len, caseflag);
    DEBUG2(DBG_HASH_LO, "store_in_hash: keystr: <%s> loc: %d\n", keystr, loc);

    /*
     * search the slot chain for our entry
     */
    /* special case for empty slow chains */
    if (empty_slot(table->slot[loc])) {
	DEBUG(DBG_HASH_MID, "store_in_hash: insert on NULL slot\n");
	new = new_hash_element(keystr, data, datalen, table);
	insert_hash(&table->slot[loc], new);
	return NULL;
    }

    /* 
     * search the chain
     */
    for (prev=NULL, cur=table->slot[loc]; cur != NULL; 
      prev=cur, cur=next_hash(cur, table)) {
	/* 
	 * if we found the entry, stop
	 */
	if ((cmp = stringcmp(keystr, cur->keystr, caseflag)) == 0) {
	    new = new_hash_element(keystr, data, datalen, table);
	    if (prev == NULL) {
		DEBUG(DBG_HASH_MID, "store_in_hash: replaced at front\n");
		/* insert at beginning */
		replace_hash(table->slot[loc], cur, new);
	    } else {
		DEBUG(DBG_HASH_MID, "store_in_hash: replaced in middle\n");
		replace_hash(prev->succ, cur, new);	/* insert in middle */
	    }
	    return cur;

	/* 
	 * we are past the insertion point, insert before here and stop
	 * note if we are inserting at the beginning a a chain or in the middle
	 */
	} else if (cmp < 0) {
	    new = new_hash_element(keystr, data, datalen, table);
	    if (prev == NULL) {
		DEBUG(DBG_HASH_MID, "store_in_hash: insert at front\n");
		insert_hash(&table->slot[loc], new);  /* insert at beginning */
	    } else {
		DEBUG(DBG_HASH_MID, "store_in_hash: insert in middle\n");
		insert_hash(&prev->succ, new);	/* insert in middle */
	    }
	    return NULL;
	}
    }

    /* 
     * case: insertion at the end of the chain
     */
    DEBUG(DBG_HASH_MID, "store_in_hash: insert at END\n");
    new = new_hash_element(keystr, data, datalen, table);
    insert_hash(&prev->succ, new);
    return NULL;
}
#endif	/* ETHEREAL_HASHDATA */

/*
 * lookup_in_hash - lookup an element in a hash table and return 
 *		    the associated data
 *
 * inputs:
 *	keystr	- the key of the data to add
 *	data	- pointer to a pointer, or 0 if no data is wanted
 *	datalen	- pointer to the data length, or NULL if no length is wanted
 *	table	- a pointer to the hash table which is being modified
 * output:
 *	returns ALREADY_HASHED if `keystr' is already in the `table', or
 *		NOT_HASHED the key was not found
 *	data	- if `data' was non-NULL points at the key's data or NULL
 *		  no data
 *	datalen	- if `datalen' was non-NULL, points at the length of the
 *		  key's data
 */
int
lookup_in_hash(keystr, data, datalen, table)
    char *keystr;			/* the key to lookup */
    char **data;			/* where to point at data, or NULL */
    int *datalen;			/* where to place data len or NULL */
    struct hash_table *table;		/* the hash table to add it to */
{
    register struct hash *cur;	/* the slot chain location */
    int loc;			/* the hash slot to add onto */
    int caseflag;		/* 0 ==> use strcmp, 1 ==> strcmpic */
    int cmp;			/* compare function result */

    /*
     * firewall - watch for NULLs
     */
    if (keystr == NULL) {
	panic(EX_SOFTWARE, "lookup_in_hash: NULL keystr");
    }
    if (table == NULL) {
	panic(EX_SOFTWARE, "lookup_in_hash: table is NULL");
    }

    /*
     * determine the hash slot to search on
     */
    caseflag = table->flags & HASH_STRCMP;
    loc = hash_string(keystr, table->len, caseflag);
    DEBUG2(DBG_HASH_LO, "lookup_in_hash: keystr: <%s> slot: %d\n",keystr,loc);
    /* watch out for empty chains, there is nothing on them */
    if (empty_slot(table->slot[loc])) {
	DEBUG(DBG_HASH_MID, "lookup_in_hash: found at slot END\n");
	return NOT_HASHED;
    }

    /* 
     * search the chain
     */
    for (cur=table->slot[loc]; cur != NULL; cur=next_hash(cur, table)) {
	/*
	 * if we found the entry, stop
	 */
	if ((cmp = stringcmp(keystr, cur->keystr, caseflag)) == 0) {
	    DEBUG(DBG_HASH_MID, "lookup_in_hash: found\n");
	    /*
	     * fill in the requested args
	     */
	    if (data != NULL) {
		*data = hash_data(cur);	
	    }
	    if (datalen != NULL) {
		*datalen = cur->datalen;
	    }
	    return ALREADY_HASHED;
	/* if we have gone past our entry, stop searching */
        } else if (cmp < 0) {
	    break;
	}
    }

    /* found nothing */
    DEBUG(DBG_HASH_MID, "lookup_in_hash: not found\n");
    return NOT_HASHED;
}

#ifdef ETHEREAL_HASHDATA
/*
 * delete_from_hash - delete an element in the hash table
 *
 * inputs:
 *	keystr	- the key of the data to add
 *	table	- a pointer to the hash table which is being modified
 * output:
 *	returns a pointer to the element that was deleted, or NULL
 *	if no element was deleted due to `keystr' not in `table'
 */
struct hash *
delete_from_hash(keystr, table)
    char *keystr;			/* the key to lookup */
    struct hash_table *table;		/* the hash table to add it to */
{
    register struct hash *cur;		/* the slot chain location */
    register struct hash *prev;		/* previous element */
    int loc;			/* the hash slot to add onto */
    int caseflag;		/* 0 ==> use strcmp, 1 ==> strcmpic */
    int cmp;			/* compare function result */

    /*
     * firewall - watch for NULLs
     */
    if (keystr == NULL) {
	panic(EX_SOFTWARE, "delete_from_hash: keystr is NULL");
    }
    if (table == NULL) {
	panic(EX_SOFTWARE, "delete_from_hash: table is NULL");
    }

    /*
     * determine the hash slot to search on
     */
    caseflag = table->flags & HASH_STRCMP;
    loc = hash_string(keystr, table->len, caseflag);
    DEBUG2(DBG_HASH_LO, "delete_from_hash: keystr: <%s> loc: %d\n",keystr,loc);
    /* watch out for empty chains, there is nothing on them */
    if (empty_slot(table->slot[loc])) {
	DEBUG(DBG_HASH_MID, "delete_from_hash: EMPTY slot\n");
	return NULL;	/* key is not in the table */
    }

    /* 
     * search the chain for the element to delete
     */
    for (prev=NULL, cur=table->slot[loc]; cur != NULL; 
      prev=cur, cur=next_hash(cur, table)) {
	/* 
	 * if we found the entry, stop
	 */
	if ((cmp = stringcmp(keystr, cur->keystr, caseflag)) == 0) {
	    if (prev == NULL) {
		DEBUG(DBG_HASH_MID, "delete_from_hash: delete at front\n");
		/* delete at the beginning */
		delete_hash(&table->slot[loc], cur);
	    } else {
		DEBUG(DBG_HASH_MID, "delete_from_hash: delete in middle\n");
		delete_hash(&prev->succ, cur);	/* delete in middle */
	    }
	    return cur;
	/*
	 * if we have gone past the entry, stop looking
	 */
        } else if (cmp < 0) {
	    DEBUG(DBG_HASH_MID, "delete_from_hash: past spot\n");
	    break;
        }
    }

    /* found nothing */
    DEBUG(DBG_HASH_MID, "delete_from_hash: not found\n");
    return NULL;		/* nothing was deleted */
}
#endif	/* ETHEREAL_HASHDATA */


/*
 * write_hash_table - write a hash table to a stream
 *
 * input:
 *	tab	- the hash table to write
 *	stream	- the stream on which to write
 */
void
write_hash_table(table, stream)
    struct hash_table *table;	/* the table to write */
    FILE *stream;		/* the stream on which to write table */
{
    register int i;		/* index */
    long tab_loc;		/* file location of hash_table start */
    long loc;			/* current location */
    int size;			/* size of the current element */
    long offset;                /* current offset within the file */
    long disc_succ;             /* cur->succ as written to disc */
    struct hash *cur;		/* the current hash element */
    struct hash_table *tab;	/* disk copy of table */

    /*
     * firewalls
     */
    if (table == NULL || stream == NULL) {
	panic(EX_SOFTWARE, "write_hash_table: NULL arguments");    
    }
    if (table->len <= 0) {
	panic(EX_SOFTWARE, "write_hash_table: table length: %d", table->len);
    }
    DEBUG2(DBG_HASH_LO, "write_hash_table: size: %d life: %d\n",
	   table->len, table->life);

    /*
     * allocate a temporary disk copy of the hash table
     */
    tab = (struct hash_table *)xmalloc(table_size(table->len));
    tab->life = table->life;		/* preserve life type */
    tab->len = table->len;		/* preserve table length */
    tab->flags = table->flags;		/* preserve flags - XXX all bits ??? */
    tab->prev = NULL;			/* clear walking location */
    tab->indx = 0;			/* clear walking slot index */

    /*
     * skip the hash table block
     */
    tab_loc = ftell(stream);
    if (fseek(stream, (long)table_size(table->len), 1) < 0) {
	panic(EX_IOERR, "write_hash_table: bad skip of %d over table",
	    table_size(table->len));
    }

    /*
     * write out each hash table chain
     */
    for (i=0; i < table->len; i++) {

	/* don't write out chains that don't exist */
	if (empty_slot(table->slot[i])) {
		/* slot is empty, deal with it quickly */
		set_ptr(tab->slot[i], (int)NULL);
		continue;
	}

	/* note starting offset of hash chain */
	offset = ftell(stream) - tab_loc;
	if (is_odd(offset)) {
		panic(EX_IOERR, "write_hash_table: slot %d offset is odd:%ld",
		      i, offset);
	}
	set_ptr(tab->slot[i], to_odd(offset));

	/* write up to the last chain element */
	for (cur=table->slot[i]; cur != NULL; cur=next_hash(cur, table)) {
	    /* compute the current element length */
	    size = hash_len(cur);
	    if (is_odd(size)) {
		    panic(EX_IOERR,
			  "write_hash_table: size is odd:%ld for <%s>",
			  offset, cur->keystr);
	    }
	    offset += size;		/* note file movement */
	    /* write the hash element disk successor element */
	    disc_succ = (cur->succ == NULL) ? 0 : to_odd(offset);
	    if (fwrite((char *)&disc_succ, sizeof(disc_succ), 1, stream) != 1) {
		panic(EX_IOERR,
		      "write_hash_table: bad succ write <%s> on slot %d",
		      cur->keystr, i);
	    }
	    /* write the rest of the hash element data */
	    if (fwrite((char *)cur+sizeof(cur->succ), size-sizeof(cur->succ),
		       1, stream) != 1) {
		panic(EX_IOERR,
		      "write_hash_table: bad write <%s> on slot %d",
		      cur->keystr, i);
	    }
	}
    }

    /*
     * write the hash table back in its place and return to our
     * current position
     */
    loc = ftell(stream);	/* remember our current location */
    if (fseek(stream, (long)tab_loc, 0) < 0) {
	panic(EX_IOERR, "write_hash_table: bad skip back to table at %d",
	    tab_loc);
    }
    if (fwrite((char *)tab, table_size(tab->len), 1, stream) != 1) {
	panic(EX_IOERR, "write_hash_table: bad table write");
    }
    if (fseek(stream, (long)loc, 0) < 0) {
	panic(EX_IOERR, "write_hash_table: bad end seek to %d", loc);
    }

    /*
     * free the temporary disk copy of the hash table
     */
    xfree((char *)tab);
    return;
}


#ifdef STANDALONE

#include "varargs.h"
#include <sys/types.h>
#include <sys/stat.h>

#define TABLE_LEN 3 /* use only a few slots to stress the chain code */
#define INPUT_SIZE (70*1024)	/* max input line */

char *tempname = "/tmp/hashtestXXXXXX";	/* tempory hash file name */

void
main(argc, argv)
    int argc;	/* arg count */
    char *argv[]; 	/* args */
{
    int i;			/* index */
    char buf[INPUT_SIZE+1];	/* the input buffer for stdin args */
    struct hash *cur;		/* pointer to walk the table */
    struct hash_table *table;	/* our allocated table */
    struct hash_table *tableic;	/* allocated table without regard to case */
    void test();		/* test an with an element */
    void dump_hash_table();	/* dump the contents of the hash table */
    void hash_file_test();	/* perform hash file testing */

    /*
     * establish debug level
     */
    DEBUG(DBG_HASH_LO, "main: start\n");
    if (argc > 1 && strncmp(argv[1], "-d", 2) == 0) {
	    /* we have a debug level, use it */
	    if (argv[1][2] != '\0') {
		    debug = atoi(&argv[1][2]);
		    --argc;
		    ++argv;
	    } else if (argc > 2) {
		    debug = atoi(argv[2]);
		    argc -= 2;
		    argv += 2;
	    }
    }
    DEBUG1(DBG_HASH_LO, "main: debug level: %d\n", debug);

    /*
     * setup a hash table
     */
    table = new_hash_table(TABLE_LEN, (struct block *)NULL, HASH_STRCMP);
    tableic = new_hash_table(TABLE_LEN, (struct block *)NULL, 0);

    /*
     * special case: no args means read one arg per line
     */
    if (argc == 1) {
	while(fgets(buf, INPUT_SIZE, stdin) != NULL) {
	    i = strlen(buf);
	    buf[i-1] = '\0';
	    DEBUG1(DBG_HASH_LO,
		   "main: testing <%s> -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-\n", buf);
	    test(buf, table);
	    if ( debug >= DBG_HASH_HI ) {
		dump_hash_table(table);
	    }
	    DEBUG1(DBG_HASH_LO,
		   "main: testing ignore case <%s> -*-*-*-*-*-*-*-*-*-\n", buf);
	    test(buf, tableic);
	    if ( debug >= DBG_HASH_HI ) {
		dump_hash_table(tableic);
	    }
	}

    /*
     * hash each argument
     */
    } else {
	for (i=1; i < argc; ++i) {
	    DEBUG1(DBG_HASH_LO,
		   "main: testing <%s> -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-\n", buf);
	    test(argv[i], table);
	    if ( debug >= DBG_HASH_HI ) {
		dump_hash_table(table);
	    }
	    DEBUG1(DBG_HASH_LO,
		   "main: testing ignore case <%s> -*-*-*-*-*-*-*-*-*-\n", buf);
	    test(argv[i], tableic);
	    if ( debug >= DBG_HASH_HI ) {
		dump_hash_table(tableic);
	    }
	}
    }

    /*
     * test the file operations
     */
    DEBUG(DBG_HASH_LO, "main: hash_file_test\n");
    hash_file_test(table);
    DEBUG(DBG_HASH_LO, "main: hash_file_test ignore case\n");
    hash_file_test(tableic);

    /*
     * final cleanup
     */
    DEBUG(DBG_HASH_LO, "main: free memory\n");
    /* free the hash table elements */
    for (cur=walk_hash((struct hash *)NULL,table);
	 cur != NULL;
	 cur=walk_hash(cur,table))
    {
	free_hash_element(cur, 0, table); /* free without deletion */
    }
    free_hash_table(table);	/* free up the memory */
    DEBUG(DBG_HASH_LO, "main: free memory ignore case\n");
    for (cur=walk_hash((struct hash *)NULL,tableic);
	 cur != NULL;
	 cur=walk_hash(cur,tableic))
    {
	free_hash_element(cur, 0, tableic); /* free without deletion */
    }
    free_hash_table(tableic);	/* free up the memory */
    DEBUG(DBG_HASH_LO, "main: end\n");
    exit(EX_OK);
}

/*
 * perform various tests on a string
 */
void
test(buf, table)
    char *buf;		/* the key to add */
    struct hash_table *table;	/* our allocated table */
{
    register struct hash *cur;	/* the current hash entry */
    char *data;			/* the data we stored */
    int datalen;		/* length of data */
    int buflen;			/* length of the buffer string + NULL */
    int i;			/* index */
    register int caseflag;	/* 0 ==> use strcmp, 1 ==> strcmpic */

    /* test adding */
    buflen = strlen(buf)+1;
    i = add_to_hash(buf, buf, buflen, table);
    DEBUG2(DBG_HASH_LO, "test: <%s> add: %d\n", buf, i);

    /* test the lookup function */
    DEBUG1(DBG_HASH_MID, "test: <%s> lookup\n", buf);
    caseflag = table->flags & HASH_STRCMP;
    if (lookup_in_hash(buf, &data, &datalen, table) != ALREADY_HASHED) {
	panic(EX_SOFTWARE,
	      "test: add_to_hash lost <%s>", buf);
    } else if (stringcmp(buf, data, caseflag) != 0 || buflen != datalen) {
	panic(EX_SOFTWARE,
	      "test: add_to_hash lookup <%s> != <%s>", buf, data);
    }
    i = add_to_hash(buf, buf, buflen, table);
    if (i != ALREADY_HASHED) {
	panic(EX_SOFTWARE, "test: add_to_hash returned: %d for #2\n", i);
    }

    /* test the delete function */
    DEBUG1(DBG_HASH_MID, "test: <%s> delete\n", buf);
    cur = delete_from_hash(buf, table);	/* delete something that exists */
    if (cur == NULL) {
	panic(EX_SOFTWARE,
	      "test: delete_from_hash unable to delete <%s>", buf);
    } else if (stringcmp(buf, hash_data(cur), caseflag) != 0 ||
	       buflen != cur->datalen) {
	panic(EX_SOFTWARE,
	      "test: delete_from_hash mis delete of <%s>", buf);
    } else {
	free_hash_element(cur, 0, table); /* free up memory */
    }
    DEBUG1(DBG_HASH_MID, "test: <%s> empty delete\n", buf);
    cur = delete_from_hash(buf, table);	/* delete a nothing */
    if (cur != NULL) {
	panic(EX_SOFTWARE,
	      "test: delete_from_hash ghost delete #2 of <%s>", buf);
    }

    /* test the store function */
    DEBUG1(DBG_HASH_MID, "test: <%s> store\n", buf);
    cur = store_in_hash(buf, buf, buflen, table);
    if (cur != NULL) {
	panic(EX_SOFTWARE,
	      "test: store_in_hash ghost store of <%s>", buf);
    }
    DEBUG1(DBG_HASH_MID, "test: <%s> store #2\n", buf);
    cur = store_in_hash(buf, buf, buflen, table);
    if (cur == NULL) {
	panic(EX_SOFTWARE,
	      "test: store_in_hash lost store #2 of <%s>", buf);
    } else if (stringcmp(buf, hash_data(cur), caseflag) != 0 ||
	       buflen != cur->datalen) {
	panic(EX_SOFTWARE,
	      "test: store_in_hash mis store #2 of <%s>", buf);
    } else {
	free_hash_element(cur, 0, table); /* free up memory */
    }
    
    /* test the replace function */
    DEBUG1(DBG_HASH_MID, "test: <%s> replace_in_hash\n", buf);
    cur = replace_in_hash(buf, buf, buflen, table);
    if (cur == NULL) {
	panic(EX_SOFTWARE,
	      "test: replace_in_hash lost <%s>", buf);
    } else if (stringcmp(buf, hash_data(cur), caseflag) != 0 ||
	       buflen != cur->datalen) {
	panic(EX_SOFTWARE,
	      "test: replace_in_hash mis replace of <%s>", buf);
    } else {
	free_hash_element(cur, 0, table); /* free up memory */
    }
    DEBUG1(DBG_HASH_MID, "test: <%s> replace_in_hash empty\n", buf);
    cur = delete_from_hash(buf, table);	/* delete something that exists */
    if (cur == NULL) {
	panic(EX_SOFTWARE,
	      "test: delete_from_hash unable to delete #3 <%s>", buf);
    } else {
	free_hash_element(cur, 0, table); /* free up memory */
    }
    cur = replace_in_hash(buf, buf, buflen, table);
    if (cur != NULL) {
	panic(EX_SOFTWARE,
	      "test: replace_in_hash ghost replace of <%s>", buf);
    } else {
	/* put it back for keeps */
	cur = store_in_hash(buf, buf, buflen, table);
	if (cur != NULL) {
	    panic(EX_SOFTWARE,
		  "test: store_in_hash store #3 lost <%s>", buf);
        }
    }
}

/*
 * dump_hash_table - dump the hash table
 */
void
dump_hash_table(table)
    struct hash_table *table;	/* our allocated table */
{
    int i;				/* index */
    struct hash *cur;		/* the current hash entry */

    /*
     * check the hash chains
     */
    for (i=0; i < TABLE_LEN; ++i) {
	DEBUG1(DBG_HASH_HI, "dump: slot[%d]:", i);
	/* check for empty slots */
	if (empty_slot(table->slot[i])) {
	    DEBUG(DBG_HASH_HI, "Empty\n");
	    continue;
	}
	for (cur=table->slot[i]; cur != NULL; cur=next_hash(cur, table)) {
	    if (cur->keylen == 0) {
		DEBUG2(DBG_HASH_HI, " 0x%lx: <%s> :EOC ",
		   (long)cur, cur->keystr);
	    } else {
		DEBUG3(DBG_HASH_HI, " 0x%lx: <%s> :0x%lx ",
		   (long)cur, cur->keystr, (long)cur->succ);
	    }
	}
	DEBUG(DBG_HASH_HI, "\n");
    }
}

/*
 * hash_file_test - test the hash table file operations
 */
void
hash_file_test(table)
    struct hash_table *table;		/* the hash table to operate on */
{
    char *filename;		/* the file to operate on */
    struct stat buf;		/* file status buffer */
    struct hash *cur;		/* current location in hash table */
    struct hash *cur2;		/* current location in hash table2 */
    struct hash_table *table2;	/* the hash table to operate on */
    int caseflag;		/* 0 ==> use strcmp, 1 ==> strcmpic */
    char *template;		/* template for mktemp */
    FILE *stream;		/* the file stream */
    char *mktemp();		/* form a temp filename */

    /*
     * open up a file to perform hash table operations into
     */
    template = xmalloc(strlen(tempname)+1);
    strcpy(template, tempname);
    filename = mktemp(template);
    DEBUG1(DBG_HASH_LO, "hash_file_test: using <%s>\n", filename);
    stream = fopen(filename, "w");
    if (stream == NULL) {
	panic(EX_CANTCREAT, "hash_file_test: can not creat <%s>", filename);
    }

    /*
     * write out the hash table
     */
    write_hash_table(table, stream);
    if (fclose(stream) != 0) {
	panic(EX_IOERR, "hash_file_test: can not fclose\n");
    }

    /*
     * reread the hash table into memory
     */
    DEBUG1(DBG_HASH_MID, "hash_file_test: rereading <%s>\n", filename);
    stream = fopen(filename, "r");
    if (stream == NULL) {
	panic(EX_CANTCREAT, "hash_file_test: can not reopen <%s>", filename);
    }
    if (fstat(fileno(stream), &buf) < 0) {
	panic(EX_IOERR, "hash_file_test: can not stat <%s>", filename);
    }
    table2 = (struct hash_table *)xmalloc(buf.st_size * sizeof(char));
    if (fread((char *)table2,sizeof(char),buf.st_size,stream) != buf.st_size) {
	panic(EX_IOERR, "hash_file_test: can not reread <%s>", filename);
    }

    /*
     * walk thru each hash table and verify string/data
     */
    caseflag = table->flags & HASH_STRCMP;
    DEBUG(DBG_HASH_MID, "hash_file_test: verify\n");
    for (cur=walk_hash((struct hash *)NULL,table),
	 	cur2=walk_hash((struct hash *)NULL,table2);
	 cur != NULL || cur2 != NULL;
	 cur=walk_hash(cur,table), cur2=walk_hash(cur2,table2))
    {
	    /* compare cur and cur2 */
	    if (stringcmp(cur->keystr,cur2->keystr,caseflag) != 0) {
		panic(EX_SOFTWARE,
		      "hash_file_test: key mismatch: <%s> != <%s>\n",
		      cur->keystr, cur2->keystr);
	    }
	    if (cur->datalen != cur2->datalen) {
		panic(EX_SOFTWARE,
		      "hash_file_test: key mismatch: %d != %d\n",
		      cur->datalen, cur2->datalen);
	    }
	    if (memcmp(hash_data(cur), hash_data(cur2), cur->datalen) != 0) {
		panic(EX_SOFTWARE,
		      "hash_file_test: data mismatch between <%s> and <%s>\n",
		      cur->keystr, cur2->keystr);
	    }
    }

    /*
     * cleanup
     */
    DEBUG(DBG_HASH_MID, "hash_file_test: cleanup\n");
    xfree(table2);
}

/*
 * panic - fake the panic routine
 *
 * define panic rather than using the external routines.
 */
/*VARARGS2*/
void
panic(exitcode, fmt, va_alist)
    int exitcode;			/* call exit(exitcode) */
    char *fmt;				/* printf(3) format */
    va_dcl                              /* arguments for printf */
{
    va_list ap;

    va_start(ap);
    (void)fprintf(stderr, "PANIC(%s): ", exitcode);
    (void)vfprintf(stderr, fmt, ap);
    putc('\n', stderr);			/* fatal messages not \n terminated */
    va_end(ap);

    return_to_sender = TRUE;
    exit(exitcode);
}

#endif	/* STANDALONE */

These are the contents of the former NiCE NeXT User Group NeXTSTEP/OpenStep software archive, currently hosted by Netfuture.ch.