/* * 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 #include #include #include #include #include #include #include /* For LOG */ #if defined(__sun__) && defined(StupidSunHeaders) #include #include "sunos.h" #endif #include #define SS_STATISTICS #include "shstr.h" #ifdef WIN32 #include #else #include #endif #ifdef HAVE_LIBDMALLOC #include #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<= (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; }