server-1.12/common/shstr.c

457 lines
12 KiB
C

/*
* static char *rcsid_shstr_c =
* "$Id: shstr.c 11578 2009-02-23 22:02:27Z lalo $";
*/
/**
* @file shstr.c
* This is a simple shared strings package with a simple interface.
*
* Author: Kjetil T. Homme, Oslo 1992.
*/
#include <stdio.h>
#include <stddef.h>
#include <stdlib.h>
#include <sys/types.h>
#include <limits.h>
#include <string.h>
#include <global.h>
#include <libproto.h> /* For LOG */
#if defined(__sun__) && defined(StupidSunHeaders)
#include <sys/time.h>
#include "sunos.h"
#endif
#include <logger.h>
#define SS_STATISTICS
#include "shstr.h"
#ifdef WIN32
#include <win32.h>
#else
#include <autoconf.h>
#endif
#ifdef HAVE_LIBDMALLOC
#include <dmalloc.h>
#endif
/** Hash table to store our string. */
static shared_string *hash_table[TABLESIZE];
/**
* Initialises the hash-table used by the shared string library.
*/
void init_hash_table(void) {
/* A static object should be zeroed out always */
#if !defined(__STDC__)
(void)memset((void *)hash_table, 0, TABLESIZE*sizeof(shared_string *));
#endif
}
/**
* Hashing-function used by the shared string library.
*
* @param str
* string to hash.
* @return
* hash of string, suitable for use in ::hash_table.
*/
static unsigned long hashstr(const char *str) {
unsigned long hash = 0;
int i = 0;
unsigned rot = 0;
const char *p;
GATHER(hash_stats.calls);
for (p = str; i < MAXSTRING && *p; p++, i++) {
hash ^= (unsigned long)*p<<rot;
rot += 2;
if (rot >= (sizeof(unsigned long)-sizeof(char))*CHAR_BIT)
rot = 0;
}
return (hash%TABLESIZE);
}
/**
* Allocates and initialises a new shared_string structure, containing
* the string str.
*
* @note
* will fatal() in case of memory allocation failure.
* @param str
* string to store.
* @return
* sharing structure.
*/
static shared_string *new_shared_string(const char *str) {
shared_string *ss;
/* Allocate room for a struct which can hold str. Note
* that some bytes for the string are already allocated in the
* shared_string struct.
*/
ss = (shared_string *)malloc(sizeof(shared_string)-PADDING+strlen(str)+1);
if (ss == NULL)
fatal(OUT_OF_MEMORY);
ss->u.previous = NULL;
ss->next = NULL;
ss->refcount = 1;
strcpy(ss->string, str);
return ss;
}
/**
* This will add 'str' to the hash table. If there's no entry for this
* string, a copy will be allocated, and a pointer to that is returned.
*
* @param str
* string to share.
* @return
* pointer to string identical to str, but shared.
*/
sstring add_string(const char *str) {
shared_string *ss;
unsigned long ind;
GATHER(add_stats.calls);
/* Should really core dump here, since functions should not be calling
* add_string with a null parameter. But this will prevent a few
* core dumps.
*/
if (str == NULL) {
#ifdef MANY_CORES
abort();
#else
return NULL;
#endif
}
ind = hashstr(str);
ss = hash_table[ind];
/* Is there an entry for that hash?
*/
if (ss) {
/* Simple case first: See if the first pointer matches. */
if (str != ss->string) {
GATHER(add_stats.strcmps);
if (strcmp(ss->string, str)) {
/* Apparantly, a string with the same hash value has this
* slot. We must see in the list if "str" has been
* registered earlier.
*/
while (ss->next) {
GATHER(add_stats.search);
ss = ss->next;
if (ss->string != str) {
GATHER(add_stats.strcmps);
if (strcmp(ss->string, str)) {
/* This wasn't the right string... */
continue;
}
}
/* We found an entry for this string. Fix the
* refcount and exit.
*/
GATHER(add_stats.linked);
++(ss->refcount);
return ss->string;
}
/* There are no occurences of this string in the hash table. */
{
shared_string *new_ss;
GATHER(add_stats.linked);
new_ss = new_shared_string(str);
ss->next = new_ss;
new_ss->u.previous = ss;
return new_ss->string;
}
}
/* Fall through. */
}
GATHER(add_stats.hashed);
++(ss->refcount);
return ss->string;
} else {
/* The string isn't registered, and the slot is empty. */
GATHER(add_stats.hashed);
hash_table[ind] = new_shared_string(str);
/* One bit in refcount is used to keep track of the union. */
hash_table[ind]->refcount |= TOPBIT;
hash_table[ind]->u.array = &(hash_table[ind]);
return hash_table[ind]->string;
}
}
/**
* This will join str2 to the end of str. It follows the same rules as add_string
* and free_string.
*
* @param target_str
* string to be appended to.
* @param source_str
* string to append with.
* @param separator
* separator to place between the joined strings.
* @return
* pointer to string to target_str+separator+source_str.
*/
sstring join_strings(sstring target_str, sstring source_str, const char *separator) {
size_t target_len, source_len, separator_len;
char *buf;
sstring ss;
if (target_str != NULL)
target_len = strlen(target_str);
else
target_len = 0;
if (source_str != NULL)
source_len = strlen(source_str);
else
source_len = 0;
if (separator != NULL)
separator_len = strlen(separator);
else
separator_len = 0;
/* create our buffer */
buf = malloc( (target_len + source_len + separator_len + 1) * sizeof(char) );
if (buf == NULL)
fatal(OUT_OF_MEMORY);
/* copy our strings to buffer */
buf[0] = '\0';
buf = strcat(buf, target_str);
buf = strcat(buf, separator);
buf = strcat(buf, source_str);
/* create shared string from buffer */
ss = add_string(buf);
/* free our buffer and our shared string references */
free(buf);
free_string(target_str);
free_string(source_str);
return ss;
}
/**
* This will increase the refcount of the string str.
* @param str
* string which *must *have been returned from a previous add_string().
* @return
* str
*/
sstring add_refcount(sstring str) {
GATHER(add_ref_stats.calls);
++(SS(str)->refcount);
return str;
}
/**
* This will return the refcount of the string str.
*
* @param str
* string which *must *have been returned from a previous add_string().
* @return
* refcount of the string.
*/
int query_refcount(sstring str) {
return (SS(str)->refcount)&~TOPBIT;
}
/**
* Searches a string in the shared strings.
*
* @param str
* string to search for.
* @return
* pointer to identical string or NULL
*/
sstring find_string(const char *str) {
shared_string *ss;
unsigned long ind;
GATHER(find_stats.calls);
ind = hashstr(str);
ss = hash_table[ind];
/* Is there an entry for that hash?
*/
if (ss) {
/* Simple case first: Is the first string the right one? */
GATHER(find_stats.strcmps);
if (!strcmp(ss->string, str)) {
GATHER(find_stats.hashed);
return ss->string;
} else {
/* Recurse through the linked list, if there's one. */
while (ss->next) {
GATHER(find_stats.search);
GATHER(find_stats.strcmps);
ss = ss->next;
if (!strcmp(ss->string, str)) {
GATHER(find_stats.linked);
return ss->string;
}
}
/* No match. Fall through. */
}
}
return NULL;
}
/**
* This will reduce the refcount, and if it has reached 0, str will
* be freed.
*
* @param str
* string to release, which *must *have been returned from a previous add_string().
*
* @note
* the memory pointed to by str can be freed after this call, so don't use str anymore.
*/
void free_string(sstring str) {
shared_string *ss;
GATHER(free_stats.calls);
ss = SS(str);
if ((--ss->refcount&~TOPBIT) == 0) {
/* Remove this entry. */
if (ss->refcount&TOPBIT) {
/* We must put a new value into the hash_table[].
*/
if (ss->next) {
*(ss->u.array) = ss->next;
ss->next->u.array = ss->u.array;
ss->next->refcount |= TOPBIT;
} else {
*(ss->u.array) = NULL;
}
free(ss);
} else {
/* Relink and free this struct. */
if (ss->next)
ss->next->u.previous = ss->u.previous;
ss->u.previous->next = ss->next;
free(ss);
}
}
}
#ifdef SS_STATISTICS
/**
* A call to this function will cause the statistics to be dumped
* into specified buffer.
*
* The routines will gather statistics if SS_STATISTICS is defined.
*
* @param buf
* buffer which will contain dumped information.
* @param size
* buf's size.
*/
void ss_dump_statistics(char *buf, size_t size) {
static char line[80];
snprintf(buf, size, "%-13s %6s %6s %6s %6s %6s\n", "", "calls", "hashed", "strcmp", "search", "linked");
snprintf(line, sizeof(line), "%-13s %6d %6d %6d %6d %6d\n", "add_string:", add_stats.calls, add_stats.hashed, add_stats.strcmps, add_stats.search, add_stats.linked);
snprintf(buf+strlen(buf), size-strlen(buf), "%s", line);
snprintf(line, sizeof(line), "%-13s %6d\n", "add_refcount:", add_ref_stats.calls);
snprintf(buf+strlen(buf), size-strlen(buf), "%s", line);
snprintf(line, sizeof(line), "%-13s %6d\n", "free_string:", free_stats.calls);
snprintf(buf+strlen(buf), size-strlen(buf), "%s", line);
snprintf(line, sizeof(line), "%-13s %6d %6d %6d %6d %6d\n", "find_string:", find_stats.calls, find_stats.hashed, find_stats.strcmps, find_stats.search, find_stats.linked);
snprintf(buf+strlen(buf), size-strlen(buf), "%s", line);
snprintf(line, sizeof(line), "%-13s %6d\n", "hashstr:", hash_stats.calls);
snprintf(buf+strlen(buf), size-strlen(buf), "%s", line);
}
#endif /* SS_STATISTICS */
/**
* Dump the contents of the share string tables.
*
* @param what
* combination of flags:
* @li ::SS_DUMP_TABLE: dump the contents of the hash table to stderr.
* @li ::SS_DUMP_TOTALS: return a string which says how many entries etc. there are in the table.
* @param buf
* buffer that will contain total information if (what & SS_DUMP_TABLE). Left untouched else.
* @param size
* buffer's size
* @return
* buf if (what & SS_DUMP_TOTALS) or NULL.
*/
char *ss_dump_table(int what, char *buf, size_t size) {
int entries = 0, refs = 0, links = 0;
int i;
for (i = 0; i < TABLESIZE; i++) {
shared_string *ss;
if ((ss = hash_table[i]) != NULL) {
++entries;
refs += (ss->refcount&~TOPBIT);
/* Can't use stderr any longer, need to include global.h and
if (what&SS_DUMP_TABLE)
* use logfile. */
LOG(llevDebug, "%4d -- %4d refs '%s' %c\n", i, (ss->refcount&~TOPBIT), ss->string, (ss->refcount&TOPBIT ? ' ' : '#'));
while (ss->next) {
ss = ss->next;
++links;
refs += (ss->refcount&~TOPBIT);
if (what&SS_DUMP_TABLE)
LOG(llevDebug, " -- %4d refs '%s' %c\n", (ss->refcount&~TOPBIT), ss->string, (ss->refcount&TOPBIT ? '*' : ' '));
}
}
}
if (what&SS_DUMP_TOTALS) {
snprintf(buf, size, "\n%d entries, %d refs, %d links.", entries, refs, links);
return buf;
}
return NULL;
}
/**
* We don't want to exceed the buffer size of buf1 by adding on buf2!
*
* @param buf1
* @param buf2
* buffers we plan on concatening. Can be NULL.
* @param bufsize
* size of buf1. Can be NULL.
* @return
* true if overflow will occur.
*
* @todo
* This could maybe overflow. Make sure it doesn't.
*/
int buf_overflow(const char *buf1, const char *buf2, size_t bufsize) {
size_t len1 = 0, len2 = 0;
if (buf1)
len1 = strlen(buf1);
if (buf2)
len2 = strlen(buf2);
if ((len1+len2) >= bufsize)
return 1;
return 0;
}