/* pbl.c - basic library functions Copyright (C) 2002 Peter Graf This file is part of PBL - The Program Base Library. PBL is free software. This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA For more information on the Program Base Library or Peter Graf, please see: http://mission.base.com/. $Log: pbl.cpp,v $ Revision 1.2 2003/08/02 19:04:06 meilof some build fixes Revision 1.1 2003/03/21 22:48:55 meilof * finally a good hashvector implementation * masterfile fixes Revision 1.2 2002/09/12 20:47:13 peter added the isam file handling to the library */ /* * make sure "strings | grep Id | sort -u" shows the source file versions */ static char* _PBL_id = "$Id: pbl.cpp,v 1.2 2003/08/02 19:04:06 meilof Exp $"; static int _PBL_fct() { return( _PBL_id ? 0 : _PBL_fct() ); } /* * make sure "strings | grep Id | sort -u" shows the source file versions */ static char* rcsid = "$Id: pbl.cpp,v 1.2 2003/08/02 19:04:06 meilof Exp $"; static int rcsid_fct() { return( rcsid ? 0 : rcsid_fct() ); } #include #include #include #include #include #include "pbl.h" /*****************************************************************************/ /* #defines */ /*****************************************************************************/ /*****************************************************************************/ /* typedefs */ /*****************************************************************************/ /* * the type is only needed if we keep a heap memory trace */ typedef struct pbl_memtrace_s { char * tag; /* tag used by calling function */ time_t time; /* time when the chunk of memory was requested */ void * data; /* pointer to data that was allocated */ size_t size; /* number of bytes allocated */ struct pbl_memtrace_s * next; /* memory chunks are kept in a linear */ struct pbl_memtrace_s * prev; /* list */ } pbl_memtrace_t; /*****************************************************************************/ /* globals */ /*****************************************************************************/ #ifdef PBL_MEMTRACE /* * head and tail of known memory chunks */ static pbl_memtrace_t * pbl_memtrace_head; static pbl_memtrace_t * pbl_memtrace_tail; /* * number of memory chunks known */ static long pbl_nmem_chunks = 0; /* * total size of all chunks known */ static long pbl_nmem_size = 0; #endif static char pbl_errbuf[ PBL_ERRSTR_LEN + 1 ]; int pbl_errno; char * pbl_errstr = pbl_errbuf; /*****************************************************************************/ /* functions */ /*****************************************************************************/ #ifdef PBL_MEMTRACE /* * log a line for all memory chunks that are allocated for more than * 3 minutes, or if call at the end of the program, log all chunks known */ void pbl_memtrace_out( int checktime ) { static int first = 1; pbl_memtrace_t * memtrace; pbl_memtrace_t * tmp; char * outpath = "pblmemtrace.log"; FILE * outfile = NULL; time_t now = time( 0 ); char * nowstr = NULL; char * timestr = NULL; if( !pbl_memtrace_head ) { return; } memtrace = pbl_memtrace_head; while( memtrace ) { if( checktime && ( now - memtrace->time < 180 )) { break; } if( !outfile ) { if( first ) { first = 0; outfile = fopen( outpath, "w" ); if( outfile ) { fprintf( outfile, ">>memtrace at %s", ctime( &now )); } } else { outfile = fopen( outpath, "a" ); } if( !outfile ) { break; } } tmp = memtrace; memtrace = memtrace->next; if( !nowstr ) { nowstr = strdup( ctime( &now )); } timestr = ctime( &(tmp->time)); fprintf( outfile, "%s %.*s: %.*s %06ld %x %ld %ld \"%s\"\n", checktime ? ">" : "e", 8, nowstr ? nowstr + 11 : "unknown", 8, timestr ? timestr + 11 : "unknown", (long)tmp->size, tmp->data - NULL, pbl_nmem_chunks, pbl_nmem_size, tmp->tag ); PBL_LIST_UNLINK( pbl_memtrace_head, pbl_memtrace_tail, tmp, next, prev ); free( tmp ); } if( nowstr ) { free( nowstr ); } if( outfile ) { fclose( outfile ); } } /* * remember a memory chunk that was allocated by some function */ void pbl_memtrace_create( char * tag, void * data, size_t size ) { pbl_memtrace_t * memtrace; memtrace = malloc( sizeof( pbl_memtrace_t )); if( !memtrace ) { return; } memtrace->tag = tag; memtrace->time = time( 0 ); memtrace->data = data; memtrace->size = size; PBL_LIST_APPEND( pbl_memtrace_head, pbl_memtrace_tail, memtrace, next, prev ); pbl_nmem_chunks++; pbl_nmem_size += size; pbl_memtrace_out( 1 ); } /* * remove a memory from the chunk list, the caller freed the memory */ void pbl_memtrace_delete( void * data ) { pbl_memtrace_t * memtrace; for( memtrace = pbl_memtrace_head; memtrace; memtrace = memtrace->next ) { if( memtrace->data == data ) { pbl_nmem_chunks--; pbl_nmem_size -= memtrace->size; PBL_LIST_UNLINK( pbl_memtrace_head, pbl_memtrace_tail, memtrace, next, prev ); free( memtrace ); break; } } } #endif /* PBL_MEMTRACE */ /** * replacement for malloc * * @return void * retptr == NULL: OUT OF MEMORY * @return void * retptr != NULL: pointer to buffer allocated */ void * pbl_malloc( char * tag, /** tag used for memory leak detection */ size_t size /** number of bytes to allocate */ ) { void * ptr; if( !tag ) { tag = "pbl_malloc"; } ptr = malloc( size ); if( !ptr ) { snprintf( pbl_errstr, PBL_ERRSTR_LEN, "%s: failed to malloc %d bytes\n", tag, size ); pbl_errno = PBL_ERROR_OUT_OF_MEMORY; return( 0 ); } #ifdef PBL_MEMTRACE pbl_memtrace_create( tag, ptr, size ); #endif return( ptr ); } /** * replacement for malloc, initializes the memory to 0 * * @return void * retptr == NULL: OUT OF MEMORY * @return void * retptr != NULL: pointer to buffer allocated */ void * pbl_malloc0( char * tag, /** tag used for memory leak detection */ size_t size /** number of bytes to allocate */ ) { void * ptr = malloc( size ); if( !ptr ) { snprintf( pbl_errstr, PBL_ERRSTR_LEN, "failed to malloc %d bytes\n", size ); pbl_errno = PBL_ERROR_OUT_OF_MEMORY; return( 0 ); } memset( ptr, 0, size ); #ifdef PBL_MEMTRACE pbl_memtrace_create( tag, ptr, size ); #endif return( ptr ); } /** * duplicate a buffer, similar to strdup * * @return void * retptr == NULL: OUT OF MEMORY * @return void * retptr != NULL: pointer to buffer allocated */ void * pbl_memdup( char * tag, /** tag used for memory leak detection */ void * data, /** buffer to duplicate */ size_t size /** size of that buffer */ ) { void * ptr = malloc( size ); if( !ptr ) { snprintf( pbl_errstr, PBL_ERRSTR_LEN, "failed to malloc %d bytes\n", size ); pbl_errno = PBL_ERROR_OUT_OF_MEMORY; return( 0 ); } memcpy( ptr, data, size ); #ifdef PBL_MEMTRACE pbl_memtrace_create( tag, ptr, size ); #endif return( ptr ); } /** * duplicate and concatenate two memory buffers * * @return void * retptr == NULL: OUT OF MEMORY * @return void * retptr != NULL: pointer to new buffer allocated */ void * pbl_mem2dup( char * tag, /** tag used for memory leak detection */ void * mem1, /** first buffer to duplicate */ size_t len1, /** length of first buffer */ void * mem2, /** second buffer to duplicate */ size_t len2 /** length of second buffer */ ) { void * ret; if( !tag ) { tag = "pbl_mem2dup"; } ret = pbl_malloc( tag, len1 + len2 ); if( !ret ) { return( 0 ); } if( len1 ) { memcpy( ret, mem1, len1 ); } if( len2 ) { memcpy( ((char*)ret) + len1, mem2, len2 ); } return( ret ); } /** * memcpy with target length check * * @return size_t rc: number of bytes copied */ size_t pbl_memlcpy( void * to, /** target buffer to copy to */ size_t tolen, /** number of bytes in the target buffer */ void * from, /** source to copy from */ size_t n /** length of source */ ) { size_t l = n > tolen ? tolen : n; memcpy( to, from, l ); return( l ); } /** * find out how many starting bytes of two buffers are equal * * @return int rc: number of equal bytes */ int pbl_memcmplen( void * left, /** first buffer for compare */ size_t llen, /** length of that buffer */ void * right, /** second buffer for compare */ size_t rlen /** length of that buffer */ ) { unsigned int i; unsigned char * l = ( unsigned char * )left; unsigned char * r = ( unsigned char * )right; if( llen > rlen ) { llen = rlen; } for( i = 0; i < llen; i++ ) { if( *l++ != *r++ ) { break; } } return( i ); } /** * compare two memory buffers, similar to memcmp * * @return int rc < 0: left is smaller than right * @return int rc == 0: left and right are equal * @return int rc > 0: left is bigger than right */ int pbl_memcmp( void * left, /** first buffer for compare */ size_t llen, /** length of that buffer */ void * right, /** second buffer for compare */ size_t rlen /** length of that buffer */ ) { size_t len; int rc; /* * a buffer with a length 0 is logically smaller than any other buffer */ if( !llen ) { if( !rlen ) { return( 0 ); } return( -1 ); } if( !rlen ) { return( 1 ); } /* * use the shorter of the two buffer lengths for the memcmp */ if( llen <= rlen ) { len = llen; } else { len = rlen; } /* * memcmp is used, therefore the ordering is ascii */ rc = memcmp( left, right, len ); if( rc ) { return( rc ); } /* * if the two buffers are equal in the first len bytes, but don't have * the same lengths, the longer one is logically bigger */ return( (int) ( ((int)llen) - ((int)rlen) )); } /** * copy a two byte short to a two byte buffer */ void pbl_ShortToBuf( unsigned char * buf, /** buffer to copy to */ int s /** short value to copy */ ) { *buf++ = ( unsigned char ) ( s >> 8 ); *buf = ( unsigned char ) ( s ); } /** * read a two byte short from a two byte buffer * * @return int rc: the short value read */ int pbl_BufToShort( unsigned char * buf /** buffer to read from */ ) { unsigned int s = (( unsigned int ) ( *buf++ )) << 8; s |= *buf; return( s ); } /** * copy a four byte long to a four byte buffer */ void pbl_LongToBuf( unsigned char * buf, /** buffer to copy to */ long l /** long value to copy */ ) { *buf++ = (unsigned char ) ( ( l >> 24 )); *buf++ = (unsigned char ) ( ( l >> 16 )); *buf++ = (unsigned char ) ( ( l >> 8 )); *buf = (unsigned char ) ( l ); } /** * read a four byte long from a four byte buffer * * @return long ret: the long value read */ long pbl_BufToLong( unsigned char * buf /** the buffer to read from */ ) { unsigned long l = ((( unsigned long ) ( *buf++ ) )) << 24; l |= ((( unsigned long ) ( *buf++ ) ) ) << 16; l |= ((( unsigned long ) ( *buf++ ) ) ) << 8; l |= *buf; return( l ); } /** * copy a four byte long to a variable length buffer * * @return int rc: the number of bytes used in the buffer */ int pbl_LongToVarBuf( unsigned char * buffer, unsigned long value ) { if( value <= 0x7f ) { *buffer = (unsigned char)value; return( 1 ); } if( value <= 0x3fff ) { *buffer++ = (unsigned char)( value / 0x100 ) | 0x80; *buffer = (unsigned char)value & 0xff; return( 2 ); } if( value <= 0x1fffff ) { *buffer++ = (unsigned char)( value / 0x10000 ) | 0x80 | 0x40; *buffer++ = (unsigned char)( value / 0x100 ); *buffer = (unsigned char)value & 0xff; return( 3 ); } if( value <= 0x0fffffff ) { *buffer++ = (unsigned char)( value / 0x1000000 ) | 0x80 | 0x40 | 0x20; *buffer++ = (unsigned char)( value / 0x10000 ); *buffer++ = (unsigned char)( value / 0x100 ); *buffer = (unsigned char)value & 0xff; return( 4 ); } *buffer++ = (unsigned char)0xf0; pbl_LongToBuf( buffer, value ); return( 5 ); } /** * read a four byte long from a variable length buffer * * @return int rc: the number of bytes used in the buffer */ int pbl_VarBufToLong( unsigned char * buffer, /** buffer to read from */ long * value /** long to read to */ ) { int c = 0xff & *buffer++; int val; if( !( c & 0x80 )) { *value = c; return( 1 ); } if( !( c & 0x40 )) { *value = ( c & 0x3f ) * 0x100 + ( *buffer & 0xff ); return( 2 ); } if( !( c & 0x20 )) { val = ( c & 0x1f ) * 0x10000; val += (( *buffer++ ) & 0xff ) * 0x100; *value = val + (( *buffer ) & 0xff ); return( 3 ); } if( !( c & 0x10 )) { val = ( c & 0x0f ) * 0x1000000; val += (( *buffer++ ) & 0xff ) * 0x10000; val += (( *buffer++ ) & 0xff ) * 0x100; *value = val + (( *buffer ) & 0xff ); return( 4 ); } *value = pbl_BufToLong( buffer ); return( 5 ); } /** * find out how many bytes a four byte long would use in a buffer * * @return int rc: number of bytes used in buffer */ int pbl_LongSize( unsigned long value /** value to check */ ) { if( value <= 0x7f ) { return( 1 ); } if( value <= 0x3fff ) { return( 2 ); } if( value <= 0x1fffff ) { return( 3 ); } if( value <= 0x0fffffff ) { return( 4 ); } return( 5 ); } /** * find out how many bytes a four byte long uses in a buffer * * @return int rc: number of bytes used in buffer */ int pbl_VarBufSize( unsigned char * buffer /** buffer to check */ ) { int c = 0xff & *buffer; if( !( c & 0x80 )) { return( 1 ); } if( !( c & 0x40 )) { return( 2 ); } if( !( c & 0x20 )) { return( 3 ); } if( !( c & 0x10 )) { return( 4 ); } return( 5 ); } /*****************************************************************************/ /* #defines */ /*****************************************************************************/ #define PBL_HASHTABLE_SIZE 1019 /*****************************************************************************/ /* typedefs */ /*****************************************************************************/ typedef struct pbl_hashitem_s { void * key; size_t keylen; void * data; struct pbl_hashitem_s * next; struct pbl_hashitem_s * prev; struct pbl_hashitem_s * bucketnext; struct pbl_hashitem_s * bucketprev; } pbl_hashitem_t; typedef struct pbl_hashbucket_s { pbl_hashitem_t * head; pbl_hashitem_t * tail; } pbl_hashbucket_t; struct pbl_hashtable_s { char * magic; int currentdeleted; pbl_hashitem_t * head; pbl_hashitem_t * tail; pbl_hashitem_t * current; pbl_hashbucket_t * buckets; }; typedef struct pbl_hashtable_s pbl_hashtable_t; /*****************************************************************************/ /* globals */ /*****************************************************************************/ /*****************************************************************************/ /* functions */ /*****************************************************************************/ static int hash( const unsigned char * key, size_t keylen ) { int ret = 104729; for( ; keylen-- > 0; key++ ) { if( *key ) { ret *= *key + keylen; ret %= PBL_HASHTABLE_SIZE; } } return( ret % PBL_HASHTABLE_SIZE ); } /** * create a new hash table * * @return pblHashTable_t * retptr != NULL: pointer to new hash table * @return pblHashTable_t * retptr == NULL: OUT OF MEMORY */ pblHashTable_t * pblHtCreate( void ) { pbl_hashtable_t * ht; ht = (pbl_hashtable_t*)pbl_malloc0( "pblHtCreate hashtable", sizeof( pbl_hashtable_t ) ); if( !ht ) { return( 0 ); } ht->buckets = (pbl_hashbucket_t *)pbl_malloc0( "pblHtCreate buckets", sizeof( pbl_hashbucket_t ) * PBL_HASHTABLE_SIZE); if( !ht->buckets ) { PBL_FREE( ht ); return( 0 ); } /* * set the magic marker of the hashtable */ ht->magic = rcsid; return( ( pblHashTable_t * )ht ); } /** * insert a key / data pair into a hash table * * only the pointer to the data is stored in the hash table * no space is malloced for the data! * * @return int ret == 0: ok * @return int ret == -1: an error, see pbl_errno: * @return PBL_ERROR_EXISTS: an item with the same key already exists * @return PBL_ERROR_OUT_OF_MEMORY: out of memory */ int pblHtInsert( pblHashTable_t * h, /** hash table to insert to */ void * key, /** key to insert */ size_t keylen, /** length of that key */ void * dataptr /** dataptr to insert */ ) { pbl_hashtable_t * ht = ( pbl_hashtable_t * )h; pbl_hashbucket_t * bucket = 0; pbl_hashitem_t * item = 0; int hashval = hash((const unsigned char *) key, keylen ); bucket = ht->buckets + hashval; if( keylen < (size_t)1 ) { /* * the length of the key can not be smaller than 1 */ pbl_errno = PBL_ERROR_EXISTS; return( -1 ); } for( item = bucket->head; item; item = item->bucketnext ) { if(( item->keylen == keylen ) && !memcmp( item->key, key, keylen )) { snprintf( pbl_errstr, PBL_ERRSTR_LEN, "insert of duplicate item in hashtable\n" ); pbl_errno = PBL_ERROR_EXISTS; return( -1 ); } } item = (pbl_hashitem_t*)pbl_malloc0( "pblHtInsert hashitem", sizeof( pbl_hashitem_t ) ); if( !item ) { return( -1 ); } item->key = pbl_memdup( "pblHtInsert item->key", key, keylen ); if( !item->key ) { PBL_FREE( item ); return( -1 ); } item->keylen = keylen; item->data = dataptr; /* * link the item */ PBL_LIST_APPEND( bucket->head, bucket->tail, item, bucketnext, bucketprev ); PBL_LIST_APPEND( ht->head, ht->tail, item, next, prev ); ht->current = item; return( 0 ); } /** * search for a key in a hash table * * @return void * retptr != NULL: pointer to data of item found * @return void * retptr == NULL: no item found with the given key * @return PBL_ERROR_NOT_FOUND: */ void * pblHtLookup( pblHashTable_t * h, /** hash table to search in */ void * key, /** key to search */ size_t keylen /** length of that key */ ) { pbl_hashtable_t * ht = ( pbl_hashtable_t * )h; pbl_hashbucket_t * bucket = 0; pbl_hashitem_t * item = 0; int hashval = hash((const unsigned char *) key, keylen ); bucket = ht->buckets + hashval; for( item = bucket->head; item; item = item->bucketnext ) { if(( item->keylen == keylen ) && !memcmp( item->key, key, keylen )) { ht->current = item; ht->currentdeleted = 0; return( item->data ); } } pbl_errno = PBL_ERROR_NOT_FOUND; return( 0 ); } /** * get data of first key in hash table * * This call and \Ref{pblHtNext} can be used in order to loop through all items * stored in a hash table. * *
   Example:

   for( data = pblHtFirst( h ); data; data = pblHtNext( h ))
   {
       do something with the data pointer
   }
   
* @return void * retptr != NULL: pointer to data of first item * @return void * retptr == NULL: the hash table is empty * @return PBL_ERROR_NOT_FOUND: */ void * pblHtFirst( pblHashTable_t * h /** hash table to look in */ ) { pbl_hashtable_t * ht = ( pbl_hashtable_t * )h; pbl_hashitem_t * item = 0; item = ht->head; if( item ) { ht->current = item; ht->currentdeleted = 0; return( item->data ); } pbl_errno = PBL_ERROR_NOT_FOUND; return( 0 ); } /** * get data of next key in hash table * * This call and \Ref{pblHtFirst} can be used in order to loop through all items * stored in a hash table. * *
   Example:

   for( data = pblHtFirst( h ); data; data = pblHtNext( h ))
   {
       do something with the data pointer
   }
   
* @return void * retptr != NULL: pointer to data of next item * @return void * retptr == NULL: there is no next item in the hash table * @return PBL_ERROR_NOT_FOUND: */ void * pblHtNext( pblHashTable_t * h /** hash table to look in */ ) { pbl_hashtable_t * ht = ( pbl_hashtable_t * )h; pbl_hashitem_t * item = 0; if( ht->current ) { if( ht->currentdeleted ) { item = ht->current; } else { item = ht->current->next; } ht->currentdeleted = 0; } if( item ) { ht->current = item; return( item->data ); } pbl_errno = PBL_ERROR_NOT_FOUND; return( 0 ); } /** * get data of current key in hash table * * @return void * retptr != NULL: pointer to data of current item * @return void * retptr == NULL: there is no current item in the hash table * @return PBL_ERROR_NOT_FOUND: */ void * pblHtCurrent( pblHashTable_t * h /** hash table to look in */ ) { pbl_hashtable_t * ht = ( pbl_hashtable_t * )h; if( ht->current ) { return( ht->current->data ); } pbl_errno = PBL_ERROR_NOT_FOUND; return( 0 ); } void * pblHtCurrentKey( pblHashTable_t * h /** hash table to look in */ ) { pbl_hashtable_t * ht = ( pbl_hashtable_t * )h; if( ht->current ) { return( ht->current->key ); } pbl_errno = PBL_ERROR_NOT_FOUND; return( 0 ); } /** * remove an item from the hash table * * parameters key and keylen are optional, if they are not given * the current record is deleted * * if the current record is removed the pointer to the current record * is moved to the next record. * *
   Example:

   for( data = pblHtFirst( h ); data; data = pblHtRemove( h, 0, 0 ))
   {
       this loop removes all items from a hash table
   }
   
* * if the current record is moved by this function the next call to * \Ref{pblHtNext} will return the data of the then current record. * Therefore the following code does what is expected: * It visits all items of the hash table and removes the expired ones. * *
   for( data = pblHtFirst( h ); data; data = pblHtNext( h ))
   {
       if( needs to be deleted( data ))
       {
           pblHtRemove( h, 0, 0 );
       }
   }
   
* @return int ret == 0: ok * @return int ret == -1: an error, see pbl_errno: * @return PBL_ERROR_NOT_FOUND: the current item is not positioned * @return or there is no item with the given key */ int pblHtRemove( pblHashTable_t * h, /** hash table to remove from */ void * key, /** OPT: key to remove */ size_t keylen /** OPT: length of that key */ ) { pbl_hashtable_t * ht = ( pbl_hashtable_t * )h; pbl_hashbucket_t * bucket = 0; pbl_hashitem_t * item = 0; int hashval = 0; if( keylen && key ) { hashval = hash( (const unsigned char *)key, keylen ); bucket = ht->buckets + hashval; for( item = bucket->head; item; item = item->bucketnext ) { if(( item->keylen == keylen ) && !memcmp( item->key, key, keylen )) { break; } } } else { item = ht->current; if( item ) { hashval = hash( (const unsigned char *)item->key, item->keylen ); bucket = ht->buckets + hashval; } } if( item ) { if( item == ht->current ) { ht->currentdeleted = 1; ht->current = item->next; } /* * unlink the item */ PBL_LIST_UNLINK( bucket->head, bucket->tail, item, bucketnext, bucketprev ); PBL_LIST_UNLINK( ht->head, ht->tail, item, next, prev ); PBL_FREE( item->key ); PBL_FREE( item ); return( 0 ); } pbl_errno = PBL_ERROR_NOT_FOUND; return( -1 ); } /** * delete a hash table * * the hash table has to be empty! * * @return int ret == 0: ok * @return int ret == -1: an error, see pbl_errno: * @return PBL_ERROR_EXISTS: the hash table is not empty */ int pblHtDelete( pblHashTable_t * h /** hash table to delete */ ) { pbl_hashtable_t * ht = ( pbl_hashtable_t * )h; if( ht->head ) { pbl_errno = PBL_ERROR_EXISTS; return( -1 ); } PBL_FREE( ht->buckets ); PBL_FREE( ht ); return( 0 ); }