diff options
-rw-r--r-- | include/asterisk/hashtab.h | 257 | ||||
-rw-r--r-- | main/Makefile | 2 | ||||
-rw-r--r-- | main/config.c | 2 | ||||
-rw-r--r-- | main/hashtab.c | 835 | ||||
-rw-r--r-- | main/pbx.c | 845 | ||||
-rw-r--r-- | pbx/pbx_ael.c | 2 | ||||
-rw-r--r-- | res/ael/pval.c | 10 | ||||
-rw-r--r-- | utils/Makefile | 14 | ||||
-rw-r--r-- | utils/hashtest.c | 387 |
9 files changed, 2299 insertions, 55 deletions
diff --git a/include/asterisk/hashtab.h b/include/asterisk/hashtab.h new file mode 100644 index 000000000..f40324b39 --- /dev/null +++ b/include/asterisk/hashtab.h @@ -0,0 +1,257 @@ +/* + * Asterisk -- An open source telephony toolkit. + * + * Copyright (C) 2006, Digium, Inc. + * + * Steve Murphy <murf@digium.com> + * + * See http://www.asterisk.org for more information about + * the Asterisk project. Please do not directly contact + * any of the maintainers of this project for assistance; + * the project provides a web site, mailing lists and IRC + * channels for your use. + * + * This program is free software, distributed under the terms of + * the GNU General Public License Version 2. See the LICENSE file + * at the top of the source tree. + */ +#ifndef _ASTERISK_HASHTAB_H_ +#define _ASTERISK_HASHTAB_H_ +#define __USE_UNIX98 1 /* to get the MUTEX_RECURSIVE stuff */ + +/* generic (perhaps overly so) hashtable implementation */ + +/* notes: + +A hash table is a structure that allows for an exact-match search +in O(1) (or close to that) time. + +The method: given: a set of {key,val} pairs. (at a minimum). + given: a hash function, which, given a key, + will return an integer. Ideally, each key in the + set will have its own unique associated hash value. + This hash number will index into an array. "buckets" + are what the elements of this array are called. To + handle possible collisions in hash values, buckets can form a list. + +The key for a value must be contained in the value, or we won't +be able to find it in the bucket list. + +This implementation is pretty generic, because: + 1. The value and key are expected to be in a structure + (along with other data, perhaps) and it's address is a "void *". + 2. The pointer to a compare function must be passed in at the + time of creation, and is stored in the hashtable. + 3. The pointer to a resize function, which returns 1 if the + hash table is to be grown. A default routine is provided + if the pointer is NULL, and uses the java hashtable metric + of a 75% load factor. + 4. The pointer to a "new size" function, which returns a preferable + new size for the hash table bucket array. By default, a function + is supplied which roughly doubles the size of the array, is provided. + This size should ideally be a prime number. + 5. The hashing function pointer must also be supplied. This function + must be written by the user to access the keys in the objects being + stored. Some helper functions that use a simple "mult by prime, add + the next char", sort of string hash, or a simple modulus of the hash + table size for ints, is provided; the user can use these simple + algorithms to generate a hash, or implement any other algorithms they + wish. + 6. Recently updated the hash routines to use Doubly-linked lists for buckets, + and added a doubly-linked list that threads thru every bucket in the table. + The list of all buckets is on the HashTab struct. The Traversal was modified + to go thru this list instead of searching the bucket array for buckets. + This also should make it safe to remove a bucket during the traversal. + Removal and destruction routines will work faster. +*/ + +struct ast_hashtab_bucket +{ + const void *object; /* whatever it is we are storing in this table */ + struct ast_hashtab_bucket *next; /* a DLL of buckets in hash collision */ + struct ast_hashtab_bucket *prev; /* a DLL of buckets in hash collision */ + struct ast_hashtab_bucket *tnext; /* a DLL of all the hash buckets for traversal */ + struct ast_hashtab_bucket *tprev; /* a DLL of all the hash buckets for traversal */ +}; + +struct ast_hashtab +{ + struct ast_hashtab_bucket **array; + struct ast_hashtab_bucket *tlist; /* the head of a DLList of all the hashbuckets in the table (for traversal). */ + + int (*compare) (const void *a, const void *b); /* a ptr to func that returns int, and take two void* ptrs, compares them, + rets -1 if a < b; rets 0 if a==b; rets 1 if a>b */ + int (*newsize) (struct ast_hashtab *tab); /* a ptr to func that returns int, a new size for hash tab, based on curr_size */ + int (*resize) (struct ast_hashtab *tab); /* a function to decide whether this hashtable should be resized now */ + unsigned int (*hash) (const void *obj); /* a hash func ptr for this table. Given a raw ptr to an obj, + it calcs a hash.*/ + int hash_tab_size; /* the size of the bucket array */ + int hash_tab_elements; /* the number of objects currently stored in the table */ + int largest_bucket_size; /* a stat on the health of the table */ + int resize_count; /* a count of the number of times this table has been + resized */ + int do_locking; /* if 1, use locks to guarantee safety of insertions/deletions */ + /* this spot reserved for the proper lock storage */ + ast_rwlock_t lock; /* is this as good as it sounds? */ +}; + +struct ast_hashtab_iter /* an iterator for traversing the buckets */ +{ + struct ast_hashtab *tab; + struct ast_hashtab_bucket *next; +}; + + +/* some standard, default routines for general use */ + +int isPrime(int num); /* this one is handy for sizing the hash table, tells if num is prime or not */ + +int ast_hashtab_compare_strings(const void *a, const void *b); /* assumes a and b are char * and returns 0 if they match */ + + +int ast_hashtab_compare_strings_nocase(const void *a, const void *b); /* assumes a & b are strings, returns 0 if they match (strcasecmp) */ + + +int ast_hashtab_compare_ints(const void *a, const void *b); /* assumes a & b are int *, returns a != b */ + + +int ast_hashtab_compare_shorts(const void *a, const void *b); /* assumes a & b are short *, returns a != b */ + + +int ast_hashtab_resize_java(struct ast_hashtab *tab); /* returns 1 if the table is 75% full or more */ + + +int ast_hashtab_resize_tight(struct ast_hashtab *tab); /* not yet specified; probably will return 1 if table is 100% full */ + + +int ast_hashtab_resize_none(struct ast_hashtab *tab); /* no resizing; always return 0 */ + + +int ast_hashtab_newsize_java(struct ast_hashtab *tab); /* returns a prime number roughly 2x the current table size */ + + +int ast_hashtab_newsize_tight(struct ast_hashtab *tab); /* not yet specified, probably will return 1.5x the current table size */ + + +int ast_hashtab_newsize_none(struct ast_hashtab *tab); /* always return current size -- no resizing */ + + +unsigned int ast_hashtab_hash_string(const void *obj); /* hashes a string to a number, mod is applied so it in the range 0 to mod-1 */ + + +unsigned int ast_hashtab_hash_string_nocase(const void *obj); /* upcases each char before using them for a hash */ + + +unsigned int ast_hashtab_hash_string_sax(const void *obj); /* from Josh */ + + +unsigned int ast_hashtab_hash_int(const int num); /* right now, both these funcs are just result = num%modulus; */ + + +unsigned int ast_hashtab_hash_short(const short num); + + +struct ast_hashtab * ast_hashtab_create(int initial_buckets, + int (*compare)(const void *a, const void *b), /* a func to compare two elements in the hash -- cannot be null */ + int (*resize)(struct ast_hashtab *), /* a func to decide if the table needs to be resized, + a NULL ptr here will cause a default to be used */ + int (*newsize)(struct ast_hashtab *tab), /* a ptr to func that returns a new size of the array. + A NULL will cause a default to be used */ + unsigned int (*hash)(const void *obj), /* a func to do the hashing */ + int do_locking ); /* use locks to guarantee safety of iterators/insertion/deletion */ + + + /* this func will free the hash table and all its memory. It + doesn't touch the objects stored in it */ +void ast_hashtab_destroy( struct ast_hashtab *tab, void (*objdestroyfunc)(void *obj)); + + + /* normally, you'd insert "safely" by checking to see if the element is + already there; in this case, you must already have checked. If an element + is already in the hashtable, that matches this one, most likely this one + will be found first. */ + /* will force a resize if the resize func returns 1 */ + /* returns 1 on success, 0 if there's a problem */ +int ast_hashtab_insert_immediate(struct ast_hashtab *tab, const void *obj); + + /* same as the above, but h is the hash index; won't hash to find the index */ +int ast_hashtab_insert_immediate_bucket(struct ast_hashtab *tab, const void *obj, int h); + + + /* check to see if the element is already there; insert only if + it is not there.*/ + /* will force a resize if the resize func returns 1 */ + /* returns 1 on success, 0 if there's a problem, or it's already there. */ +int ast_hashtab_insert_safe(struct ast_hashtab *tab, const void *obj); + + + /* lookup this object in the hash table. return a ptr if found, or NULL if not */ +void * ast_hashtab_lookup(struct ast_hashtab *tab, const void *obj); + + /* if you know the hash val for the object, then use this and avoid the recalc + of the hash (the modulus (table_size) is not applied) */ +void * ast_hashtab_lookup_with_hash(struct ast_hashtab *tab, const void *obj, unsigned int hashval); + + /* same as the above lookup, but sets h to the key hash value if the lookup fails -- this has the modulus + applied, and will not be useful for long term storage if the table is resizable */ +void * ast_hashtab_lookup_bucket(struct ast_hashtab *tab, const void *obj, int *h); + + /* returns key stats for the table */ +void ast_hashtab_get_stats( struct ast_hashtab *tab, int *biggest_bucket_size, int *resize_count, int *num_objects, int *num_buckets); + + /* this function returns the number of elements stored in the hashtab */ +int ast_hashtab_size( struct ast_hashtab *tab); + + /* this function returns the size of the bucket array in the hashtab */ +int ast_hashtab_capacity( struct ast_hashtab *tab); + + /* this function will return a copy of the table */ +struct ast_hashtab *ast_hashtab_dup(struct ast_hashtab *tab, void *(*obj_dup_func)(const void *obj)); + + /* returns an iterator */ +struct ast_hashtab_iter *ast_hashtab_start_traversal(struct ast_hashtab *tab); + + /* end the traversal, free the iterator, unlock if necc. */ +void ast_hashtab_end_traversal(struct ast_hashtab_iter *it); + + /* returns the next object in the list, advances iter one step, returns null on end of traversal */ +void *ast_hashtab_next(struct ast_hashtab_iter *it); + + + /* looks up the object; removes the corresponding bucket */ +void *ast_hashtab_remove_object_via_lookup(struct ast_hashtab *tab, void *obj); + + + /* looks up the object by hash and then comparing pts in bucket list instead of + calling the compare routine; removes the bucket */ +void *ast_hashtab_remove_this_object(struct ast_hashtab *tab, void *obj); + +/* ------------------ */ +/* for lock-enabled traversals with ability to remove an object during the traversal*/ +/* ------------------ */ + + /* returns an iterator */ +struct ast_hashtab_iter *ast_hashtab_start_write_traversal(struct ast_hashtab *tab); + + /* looks up the object; removes the corresponding bucket */ +void *ast_hashtab_remove_object_via_lookup_nolock(struct ast_hashtab *tab, void *obj); + + + /* looks up the object by hash and then comparing pts in bucket list instead of + calling the compare routine; removes the bucket */ +void *ast_hashtab_remove_this_object_nolock(struct ast_hashtab *tab, void *obj); + +/* ------------------ */ +/* ------------------ */ + +/* user-controlled hashtab locking. Create a hashtab without locking, then call the + following locking routines yourself to lock the table between threads. */ + +void ast_hashtab_initlock(struct ast_hashtab *tab); /* call this after you create the table to init the lock */ +void ast_hashtab_wrlock(struct ast_hashtab *tab); /* request a write-lock on the table. */ +void ast_hashtab_rdlock(struct ast_hashtab *tab); /* request a read-lock on the table-- don't change anything! */ +void ast_hashtab_unlock(struct ast_hashtab *tab); /* release a read- or write- lock. */ +void ast_hashtab_destroylock(struct ast_hashtab *tab); /* call this before you destroy the table. */ + + +#endif diff --git a/main/Makefile b/main/Makefile index 3f1815907..25fb79ab5 100644 --- a/main/Makefile +++ b/main/Makefile @@ -27,7 +27,7 @@ OBJS= io.o sched.o logger.o frame.o loader.o config.o channel.o \ netsock.o slinfactory.o ast_expr2.o ast_expr2f.o \ cryptostub.o sha1.o http.o fixedjitterbuf.o abstract_jb.o \ strcompat.o threadstorage.o dial.o event.o adsistub.o audiohook.o \ - astobj2.o + astobj2.o hashtab.o # we need to link in the objects statically, not as a library, because # otherwise modules will not have them available if none of the static diff --git a/main/config.c b/main/config.c index 59b1840fd..614c6c9e7 100644 --- a/main/config.c +++ b/main/config.c @@ -220,7 +220,7 @@ struct ast_category_template_instance { struct ast_category { char name[80]; - int ignored; /*!< do not let user of the config see this category */ + int ignored; /*!< do not let user of the config see this category -- set by (!) after the category decl; a template */ int include_level; char *file; /*!< the file name from whence this declaration was read */ int lineno; diff --git a/main/hashtab.c b/main/hashtab.c new file mode 100644 index 000000000..7c4adf886 --- /dev/null +++ b/main/hashtab.c @@ -0,0 +1,835 @@ +/* + * Asterisk -- An open source telephony toolkit. + * + * Copyright (C) 2007, Digium, Inc. + * + * Steve Murphy <murf@digium.com> + * + * See http://www.asterisk.org for more information about + * the Asterisk project. Please do not directly contact + * any of the maintainers of this project for assistance; + * the project provides a web site, mailing lists and IRC + * channels for your use. + * + * This program is free software, distributed under the terms of + * the GNU General Public License Version 2. See the LICENSE file + * at the top of the source tree. + */ +/*! \file + * + * \brief code to implement generic hash tables + * + * \author Steve Murphy <murf@digium.com> + */ + +#include "asterisk.h" + +ASTERISK_FILE_VERSION(__FILE__, "$Revision") + +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> +#include <string.h> +#include <ctype.h> +#include <errno.h> +#include <stddef.h> + +#include "asterisk/lock.h" +#include "asterisk/frame.h" +#include "asterisk/logger.h" +#include "asterisk/options.h" +#include "asterisk/channel.h" +#include "asterisk/cli.h" +#include "asterisk/term.h" +#include "asterisk/utils.h" +#include "asterisk/threadstorage.h" +#include "asterisk/linkedlists.h" +#include "asterisk/hashtab.h" + +static void ast_hashtab_resize( struct ast_hashtab *tab); + +/* some standard, default routines for general use */ + +int ast_hashtab_compare_strings(const void *a, const void *b) +{ + return strcmp((char*)a,(char*)b); +} + +int ast_hashtab_compare_strings_nocase(const void *a, const void *b) +{ + return strcasecmp((const char*)a,(const char*)b); +} + +int ast_hashtab_compare_ints(const void *a, const void *b) +{ + int ai = *((int*)a); + int bi = *((int*)b); + if (ai < bi) + return -1; + else if (ai==bi) + return 0; + else + return 1; +} + +int ast_hashtab_compare_shorts(const void *a, const void *b) +{ + short as = *((short*)a); + short bs = *((short*)b); + if (as < bs) + return -1; + else if (as==bs) + return 0; + else + return 1; +} + +int ast_hashtab_resize_java(struct ast_hashtab *tab) +{ + double loadfactor = (double)tab->hash_tab_elements / (double)tab->hash_tab_size; + if (loadfactor > 0.75) + return 1; + return 0; +} + +int ast_hashtab_resize_tight(struct ast_hashtab *tab) +{ + + if (tab->hash_tab_elements > tab->hash_tab_size) /* this is quicker than division */ + return 1; + return 0; +} + +int ast_hashtab_resize_none(struct ast_hashtab *tab) /* always return 0 -- no resizing */ +{ + return 0; +} + + +int isPrime(int num) +{ + int tnum,limit; + + if ((num & 0x1) == 0) /* even number -- not prime */ + return 0; + + /* Loop through ODD numbers starting with 3 */ + + tnum = 3; + limit = num; + while (tnum < limit) + { + if ((num%tnum) == 0) { + return 0; + } + /* really, we only need to check sqrt(num) numbers */ + limit = num / tnum; + /* we only check odd numbers */ + tnum = tnum+2; + } + /* if we made it thru the loop, the number is a prime */ + return 1; +} + +int ast_hashtab_newsize_java(struct ast_hashtab *tab) +{ + int i = (tab->hash_tab_size<<1); /* multiply by two */ + while (!isPrime(i)) + i++; + return i; +} + +int ast_hashtab_newsize_tight(struct ast_hashtab *tab) +{ + int x = (tab->hash_tab_size<<1); + int i = (tab->hash_tab_size+x); + while (!isPrime(i)) + i++; + return i; +} + +int ast_hashtab_newsize_none(struct ast_hashtab *tab) /* always return current size -- no resizing */ +{ + return tab->hash_tab_size; +} + +unsigned int ast_hashtab_hash_string(const void *obj) +{ + unsigned char *str = (unsigned char*)obj; + unsigned int total; + + for (total=0; *str; str++) + { + unsigned int tmp = total; + total <<= 1; /* multiply by 2 */ + total += tmp; /* multiply by 3 */ + total <<= 2; /* multiply by 12 */ + total += tmp; /* multiply by 13 */ + + total += ((unsigned int)(*str)); + } + return total; +} + +unsigned int ast_hashtab_hash_string_sax(const void *obj) /* from Josh */ +{ + unsigned char *str = (unsigned char*)obj; + unsigned int total = 0, c = 0; + + while ((c = *str++)) + total ^= ( total << 5 ) + ( total >> 2 ) + ( total << 10) + c; + + return total; +} + +unsigned int ast_hashtab_hash_string_nocase(const void *obj) +{ + unsigned char *str = (unsigned char*)obj; + unsigned int total; + + for (total=0; *str; str++) + { + unsigned int tmp = total; + unsigned int charval = toupper(*str); + + /* hopefully, the following is faster than multiplication by 7 */ + /* why do I go to this bother? A good compiler will do this + anyway, if I say total *= 13 */ + /* BTW, tried *= 7, and it doesn't do as well in spreading things around! */ + total <<= 1; /* multiply by 2 */ + total += tmp; /* multiply by 3 */ + total <<= 2; /* multiply by 12 */ + total += tmp; /* multiply by 13 */ + + total += (charval); + } + return total; +} + +unsigned int ast_hashtab_hash_int(const int x) +{ + return x; +} + +unsigned int ast_hashtab_hash_short(const short x) +{ + /* hmmmm.... modulus is best < 65535 !! */ + return x; +} + +struct ast_hashtab * ast_hashtab_create(int initial_buckets, + int (*compare)(const void *a, const void *b), /* a func to compare two elements in the hash -- cannot be null */ + int (*resize)(struct ast_hashtab *), /* a func to decide if the table needs to be resized, a NULL ptr here will cause a default to be used */ + int (*newsize)(struct ast_hashtab *tab), /* a ptr to func that returns a new size of the array. A NULL will cause a default to be used */ + unsigned int (*hash)(const void *obj), /* a func to do the hashing */ + int do_locking ) /* use locks to guarantee safety of iterators/insertion/deletion -- real simpleminded right now */ +{ + struct ast_hashtab *ht = ast_calloc(1,sizeof(struct ast_hashtab)); + while (!isPrime(initial_buckets)) /* make sure this is prime */ + initial_buckets++; + ht->array = ast_calloc(initial_buckets,sizeof(struct ast_hashtab_bucket*)); + ht->hash_tab_size = initial_buckets; + ht->compare = compare; + ht->resize = resize; + ht->newsize = newsize; + ht->hash = hash; + ht->do_locking = do_locking; + if (do_locking) + ast_rwlock_init(&ht->lock); + if (!ht->resize) + ht->resize = ast_hashtab_resize_java; + if (!ht->newsize) + ht->newsize = ast_hashtab_newsize_java; + return ht; +} + +struct ast_hashtab *ast_hashtab_dup(struct ast_hashtab *tab, void *(*obj_dup_func)(const void *obj)) +{ + struct ast_hashtab *ht = ast_calloc(1,sizeof(struct ast_hashtab)); + int i; + + ht->array = ast_calloc(tab->hash_tab_size,sizeof(struct ast_hashtab_bucket*)); + ht->hash_tab_size = tab->hash_tab_size; + ht->compare = tab->compare; + ht->resize = tab->resize; + ht->newsize = tab->newsize; + ht->hash = tab->hash; + ht->do_locking = tab->do_locking; + if (ht->do_locking) + ast_rwlock_init(&ht->lock); + /* now, dup the objects in the buckets and get them into the table */ + /* the fast way is to use the existing array index, and not have to hash + the objects again */ + for (i=0;i<ht->hash_tab_size;i++) + { + struct ast_hashtab_bucket *b = tab->array[i]; + while( b ) + { + void *newobj = (*obj_dup_func)(b->object); + if (newobj) { + ast_hashtab_insert_immediate_bucket(ht, newobj, i); + } + b = b->next; + } + } + return ht; +} + +static void tlist_del_item(struct ast_hashtab_bucket **head, struct ast_hashtab_bucket *item) +{ + /* item had better be in the list! or suffer the weirdness that occurs, later! */ + if (*head == item) { /* first item in the list */ + *head = item->tnext; + if (item->tnext) + item->tnext->tprev = NULL; + } else { + /* short circuit stuff */ + item->tprev->tnext = item->tnext; + if (item->tnext) + item->tnext->tprev = item->tprev; + } +} + +static void tlist_add_head(struct ast_hashtab_bucket **head, struct ast_hashtab_bucket *item) +{ + if (*head) { + item->tnext = *head; + item->tprev = NULL; + (*head)->tprev = item; + *head = item; + } else { + /* the list is empty */ + *head = item; + item->tprev = NULL; + item->tnext = NULL; + } +} + +/* user-controlled hashtab locking. Create a hashtab without locking, then call the + following locking routines yourself to lock the table between threads. */ + +void ast_hashtab_wrlock(struct ast_hashtab *tab) +{ + ast_rwlock_wrlock(&tab->lock); +} + +void ast_hashtab_rdlock(struct ast_hashtab *tab) +{ + ast_rwlock_rdlock(&tab->lock); +} + +void ast_hashtab_initlock(struct ast_hashtab *tab) +{ + ast_rwlock_init(&tab->lock); +} + +void ast_hashtab_destroylock(struct ast_hashtab *tab) +{ + ast_rwlock_destroy(&tab->lock); +} + +void ast_hashtab_unlock(struct ast_hashtab *tab) +{ + ast_rwlock_unlock(&tab->lock); +} + + +void ast_hashtab_destroy( struct ast_hashtab *tab, void (*objdestroyfunc)(void *obj)) +{ + /* this func will free the hash table and all its memory. It + doesn't touch the objects stored in it */ + if (tab) { + + if (tab->do_locking) + ast_rwlock_wrlock(&tab->lock); + + if (tab->array) { + /* go thru and destroy the buckets */ + struct ast_hashtab_bucket *t; + int i; + + while (tab->tlist) { + t = tab->tlist; + if (t->object && objdestroyfunc) { + (*objdestroyfunc)((void*)t->object); /* I cast this because I'm not going to MOD it, I'm going to DESTROY it */ + } + + tlist_del_item(&(tab->tlist), tab->tlist); + free(t); + } + + for (i=0;i<tab->hash_tab_size;i++) { + tab->array[i] = NULL; /* not totally necc., but best to destroy old ptrs */ + } + + free(tab->array); + } + if (tab->do_locking) { + ast_rwlock_unlock(&tab->lock); + ast_rwlock_destroy(&tab->lock); + } + free(tab); + } +} + +int ast_hashtab_insert_immediate(struct ast_hashtab *tab, const void *obj) +{ + /* normally, you'd insert "safely" by checking to see if the element is + already there; in this case, you must already have checked. If an element + is already in the hashtable, that matches this one, most likely this one + will be found first, but.... */ + + /* will force a resize if the resize func returns 1 */ + /* returns 1 on success, 0 if there's a problem */ + unsigned int h; + int c; + struct ast_hashtab_bucket *b; + + if (!tab) { + return 0; + } + if (!obj) { + return 0; + } + if (tab->do_locking) + ast_rwlock_wrlock(&tab->lock); + h = (*tab->hash)(obj) % tab->hash_tab_size; + for (c=0,b=tab->array[h];b;b=b->next) { + c++; + } + if (c+1 > tab->largest_bucket_size) + tab->largest_bucket_size = c+1; + b = ast_malloc(sizeof(struct ast_hashtab_bucket)); + b->object = obj; + b->next = tab->array[h]; + b->prev = NULL; + if (b->next) + b->next->prev = b; + tlist_add_head(&(tab->tlist),b); + + tab->array[h] = b; + tab->hash_tab_elements++; + if ((*tab->resize)(tab)) + ast_hashtab_resize(tab); + if (tab->do_locking) + ast_rwlock_unlock(&tab->lock); + return 1; +} + +int ast_hashtab_insert_immediate_bucket(struct ast_hashtab *tab, const void *obj, int h) +{ + /* normally, you'd insert "safely" by checking to see if the element is + already there; in this case, you must already have checked. If an element + is already in the hashtable, that matches this one, most likely this one + will be found first, but.... */ + + /* will force a resize if the resize func returns 1 */ + /* returns 1 on success, 0 if there's a problem */ + int c; + struct ast_hashtab_bucket *b; + + if (!tab || !obj) + return 0; + + for (c=0,b=tab->array[h];b;b=b->next) { + c++; + } + if (c+1 > tab->largest_bucket_size) + tab->largest_bucket_size = c+1; + b = ast_malloc(sizeof(struct ast_hashtab_bucket)); + b->object = obj; + b->next = tab->array[h]; + b->prev = NULL; + tab->array[h] = b; + if (b->next) + b->next->prev = b; + tlist_add_head(&(tab->tlist), b); + tab->hash_tab_elements++; + if ((*tab->resize)(tab)) + ast_hashtab_resize(tab); + return 1; +} + +int ast_hashtab_insert_safe(struct ast_hashtab *tab, const void *obj) +{ + /* check to see if the element is already there; insert only if + it is not there. */ + /* will force a resize if the resize func returns 1 */ + /* returns 1 on success, 0 if there's a problem, or it's already there. */ + int bucket = 0; + if (tab->do_locking) + ast_rwlock_wrlock(&tab->lock); + + if (ast_hashtab_lookup_bucket(tab,obj,&bucket) == 0) + { + int ret2 = ast_hashtab_insert_immediate_bucket(tab,obj,bucket); + if (tab->do_locking) + ast_rwlock_unlock(&tab->lock); + + return ret2; + } + if (tab->do_locking) + ast_rwlock_unlock(&tab->lock); + return 0; +} + +void * ast_hashtab_lookup(struct ast_hashtab *tab, const void *obj) +{ + /* lookup this object in the hash table. return a ptr if found, or NULL if not */ + unsigned int h; + const void *ret; + struct ast_hashtab_bucket *b; + if (!tab || !obj) + return 0; + + if (tab->do_locking) + ast_rwlock_rdlock(&tab->lock); + h = (*tab->hash)(obj) % tab->hash_tab_size; + for (b=tab->array[h]; b; b=b->next) { + if ((*tab->compare)(obj,b->object) == 0) { + ret = b->object; + if (tab->do_locking) + ast_rwlock_unlock(&tab->lock); + return (void*)ret; /* I can't touch obj in this func, but the outside world is welcome to */ + } + } + if (tab->do_locking) + ast_rwlock_unlock(&tab->lock); + + return 0; +} + +void * ast_hashtab_lookup_with_hash(struct ast_hashtab *tab, const void *obj, unsigned int hashval) +{ + /* lookup this object in the hash table. return a ptr if found, or NULL if not */ + unsigned int h; + const void *ret; + struct ast_hashtab_bucket *b; + if (!tab || !obj) + return 0; + + if (tab->do_locking) + ast_rwlock_rdlock(&tab->lock); + h = hashval % tab->hash_tab_size; + for (b=tab->array[h]; b; b=b->next) { + if ((*tab->compare)(obj,b->object) == 0) { + ret = b->object; + if (tab->do_locking) + ast_rwlock_unlock(&tab->lock); + return (void*)ret; /* I can't touch obj in this func, but the outside world is welcome to */ + } + } + if (tab->do_locking) + ast_rwlock_unlock(&tab->lock); + + return 0; +} + +void * ast_hashtab_lookup_bucket(struct ast_hashtab *tab, const void *obj, int *bucket) +{ + /* lookup this object in the hash table. return a ptr if found, or NULL if not */ + unsigned int h; + struct ast_hashtab_bucket *b; + if (!tab || !obj) + return 0; + + h = (*tab->hash)(obj) % tab->hash_tab_size; + for (b=tab->array[h]; b; b=b->next) { + if ((*tab->compare)(obj,b->object) == 0) { + return (void*)b->object; /* I can't touch obj in this func, but the outside world is welcome to */ + } + } + *bucket = h; + return 0; +} + +void ast_hashtab_get_stats( struct ast_hashtab *tab, int *biggest_bucket_size, int *resize_count, int *num_objects, int *num_buckets) +{ + /* returns key stats for the table */ + if (tab->do_locking) + ast_rwlock_rdlock(&tab->lock); + *biggest_bucket_size = tab->largest_bucket_size; + *resize_count = tab->resize_count; + *num_objects = tab->hash_tab_elements; + *num_buckets = tab->hash_tab_size; + if (tab->do_locking) + ast_rwlock_unlock(&tab->lock); +} + + /* this function returns the number of elements stored in the hashtab */ +int ast_hashtab_size( struct ast_hashtab *tab) +{ + return tab->hash_tab_elements; +} + + /* this function returns the size of the bucket array in the hashtab */ +int ast_hashtab_capacity( struct ast_hashtab *tab) +{ + return tab->hash_tab_size; +} + + + +/* the insert operation calls this, and is wrlock'd when it does. */ +/* if you want to call it, you should set the wrlock yourself */ + + +static void ast_hashtab_resize( struct ast_hashtab *tab) +{ + /* this function is called either internally, when the resize func returns 1, or + externally by the user to force a resize of the hash table */ + int newsize = (*tab->newsize)(tab), i, c; + unsigned int h; + struct ast_hashtab_bucket *b,*bn; + + /* Since we keep a DLL of all the buckets in tlist, + all we have to do is free the array, malloc a new one, + and then go thru the tlist array and reassign them into + the bucket arrayj. + */ + for (i=0;i<tab->hash_tab_size;i++) { /* don't absolutely have to do this, but + why leave ptrs laying around */ + tab->array[i] = 0; /* erase old ptrs */ + } + free(tab->array); + tab->array = ast_calloc(newsize,sizeof(struct ast_hashtab_bucket *)); + /* now sort the buckets into their rightful new slots */ + tab->resize_count++; + tab->hash_tab_size = newsize; + tab->largest_bucket_size = 0; + + for (b=tab->tlist;b;b=bn) + { + b->prev = 0; + bn = b->tnext; + h = (*tab->hash)(b->object) % tab->hash_tab_size; + b->next = tab->array[h]; + if (b->next) + b->next->prev = b; + tab->array[h] = b; + } + /* recalc the largest bucket size */ + for (i=0;i<tab->hash_tab_size;i++) { + c=0; + for (b=tab->array[i]; b; b=b->next) { + c++; + } + if (c > tab->largest_bucket_size) + tab->largest_bucket_size = c; + } +} + +struct ast_hashtab_iter *ast_hashtab_start_traversal(struct ast_hashtab *tab) +{ + /* returns an iterator */ + struct ast_hashtab_iter *it = ast_malloc(sizeof(struct ast_hashtab_iter)); + it->next = tab->tlist; + it->tab = tab; + if (tab->do_locking) + ast_rwlock_rdlock(&tab->lock); + return it; +} + +/* use this function to get a write lock */ +struct ast_hashtab_iter *ast_hashtab_start_write_traversal(struct ast_hashtab *tab) +{ + /* returns an iterator */ + struct ast_hashtab_iter *it = ast_malloc(sizeof(struct ast_hashtab_iter)); + it->next = tab->tlist; + it->tab = tab; + if (tab->do_locking) + ast_rwlock_wrlock(&tab->lock); + return it; +} + +void ast_hashtab_end_traversal(struct ast_hashtab_iter *it) +{ + if (it->tab->do_locking) + ast_rwlock_unlock(&it->tab->lock); + free(it); +} + +void *ast_hashtab_next(struct ast_hashtab_iter *it) +{ + /* returns the next object in the list, advances iter one step */ + struct ast_hashtab_bucket *retval; + + if (it && it->next) { /* there's a next in the bucket list */ + retval = it->next; + it->next = retval->tnext; + return (void*)retval->object; + } + return NULL; +} + +static void *ast_hashtab_remove_object_internal(struct ast_hashtab *tab, struct ast_hashtab_bucket *b, int h) +{ + const void *obj2; + + if (b->prev) { + b->prev->next = b->next; + } else { + tab->array[h] = b->next; + } + + if (b->next) { + b->next->prev = b->prev; + } + + tlist_del_item(&(tab->tlist), b); + + obj2 = b->object; + b->object = b->next = (void*)2; + free(b); /* free up the hashbucket */ + tab->hash_tab_elements--; +#ifdef DEBUG + { + int c2; + struct ast_hashtab_bucket *b2; + /* do a little checking */ + for (c2 = 0, b2 = tab->tlist;b2;b2=b2->tnext) { + c2++; + } + if (c2 != tab->hash_tab_elements) { + printf("Hey! we didn't delete right! there are %d elements in the list, and we expected %d\n", + c2, tab->hash_tab_elements); + } + for (c2 = 0, b2 = tab->tlist;b2;b2=b2->tnext) { + unsigned int obj3 = (unsigned long)obj2; + unsigned int b3 = (unsigned long)b; + if (b2->object == obj2) + printf("Hey-- you've still got a bucket pointing at ht_element %x\n", obj3); + if (b2->next == b) + printf("Hey-- you've still got a bucket with next ptr pointing to deleted bucket %x\n", b3); + if (b2->prev == b) + printf("Hey-- you've still got a bucket with prev ptr pointing to deleted bucket %x\n", b3); + if (b2->tprev == b) + printf("Hey-- you've still got a bucket with tprev ptr pointing to deleted bucket %x\n", b3); + if (b2->tnext == b) + printf("Hey-- you've still got a bucket with tnext ptr pointing to deleted bucket %x\n", b3); + } + } +#endif + return (void*)obj2; /* inside this code, the obj's are untouchable, but outside, they aren't */ +} + +void *ast_hashtab_remove_object_via_lookup(struct ast_hashtab *tab, void *obj) +{ + /* looks up the object; removes the corresponding bucket */ + unsigned int h; + struct ast_hashtab_bucket *b; + + if (!tab || !obj) + return 0; + if (tab->do_locking) + ast_rwlock_wrlock(&tab->lock); + h = (*tab->hash)(obj) % tab->hash_tab_size; + for (b=tab->array[h]; b; b=b->next) + { + void *obj2; + + if ((*tab->compare)(obj,b->object) == 0) { + + obj2 = ast_hashtab_remove_object_internal(tab,b,h); + + if (tab->do_locking) + ast_rwlock_unlock(&tab->lock); + + return (void*)obj2; /* inside this code, the obj's are untouchable, but outside, they aren't */ + } + } + if (tab->do_locking) + ast_rwlock_unlock(&tab->lock); + return 0; +} + +void *ast_hashtab_remove_object_via_lookup_nolock(struct ast_hashtab *tab, void *obj) +{ + /* looks up the object; removes the corresponding bucket */ + unsigned int h; + struct ast_hashtab_bucket *b; + + if (!tab || !obj) + return 0; + h = (*tab->hash)(obj) % tab->hash_tab_size; + for (b=tab->array[h]; b; b=b->next) + { + void *obj2; + + if ((*tab->compare)(obj,b->object) == 0) { + + obj2 = ast_hashtab_remove_object_internal(tab,b,h); + + if (tab->do_locking) + ast_rwlock_unlock(&tab->lock); + + return (void*)obj2; /* inside this code, the obj's are untouchable, but outside, they aren't */ + } + } + return 0; +} + +void *ast_hashtab_remove_this_object(struct ast_hashtab *tab, void *obj) +{ + /* looks up the object by hash and then comparing pts in bucket list instead of + calling the compare routine; removes the bucket -- a slightly cheaper operation */ + /* looks up the object; removes the corresponding bucket */ + unsigned int h; + struct ast_hashtab_bucket *b; + + if (!tab || !obj) + return 0; + + if (tab->do_locking) + ast_rwlock_wrlock(&tab->lock); + + h = (*tab->hash)(obj) % tab->hash_tab_size; + for (b=tab->array[h]; b; b=b->next) + { + const void *obj2; + + if (obj == b->object) { + + obj2 = ast_hashtab_remove_object_internal(tab,b,h); + + if (tab->do_locking) + ast_rwlock_unlock(&tab->lock); + + return (void*)obj2; /* inside this code, the obj's are untouchable, but outside, they aren't */ + } + } + + if (tab->do_locking) + ast_rwlock_unlock(&tab->lock); + return 0; +} + +void *ast_hashtab_remove_this_object_nolock(struct ast_hashtab *tab, void *obj) +{ + /* looks up the object by hash and then comparing pts in bucket list instead of + calling the compare routine; removes the bucket -- a slightly cheaper operation */ + /* looks up the object; removes the corresponding bucket */ + unsigned int h; + struct ast_hashtab_bucket *b; + + if (!tab || !obj) + return 0; + + h = (*tab->hash)(obj) % tab->hash_tab_size; + for (b=tab->array[h]; b; b=b->next) + { + const void *obj2; + + if (obj == b->object) { + + obj2 = ast_hashtab_remove_object_internal(tab,b,h); + + if (tab->do_locking) + ast_rwlock_unlock(&tab->lock); + + return (void*)obj2; /* inside this code, the obj's are untouchable, but outside, they aren't */ + } + } + + return 0; +} diff --git a/main/pbx.c b/main/pbx.c index d3949ecf1..483bb762e 100644 --- a/main/pbx.c +++ b/main/pbx.c @@ -67,6 +67,7 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$") #include "asterisk/devicestate.h" #include "asterisk/stringfields.h" #include "asterisk/event.h" +#include "asterisk/hashtab.h" #include "asterisk/module.h" #include "asterisk/indications.h" @@ -78,6 +79,17 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$") * terribly bad (it's O(N+M), where N is the # of extensions and M is the avg # * of priorities, but a constant search time here would be great ;-) * + * A new algorithm to do searching based on a 'compiled' pattern tree is introduced + * here, and shows a fairly flat (constant) search time, even for over + * 1000 patterns. Might Still needs some work-- there are some fine points of the matching + * spec about tie-breaking based on the characters in character sets, but this + * should be do-able via the weight system currently being used. + * + * Also, using a hash table for context/priority name lookup can help prevent + * the find_extension routines from absorbing exponential cpu cycles. I've tested + * find_extension with red-black trees, which have O(log2(n)) speed. Right now, + * I'm using hash tables, which do searches (ideally) in O(1) time. + * */ #ifdef LOW_MEMORY @@ -135,6 +147,8 @@ struct ast_exten { void *data; /*!< Data to use (arguments) */ void (*datad)(void *); /*!< Data destructor */ struct ast_exten *peer; /*!< Next higher priority with our extension */ + struct ast_hashtab *peer_tree; /*!< Priorities list in tree form -- only on the head of the peer list */ + struct ast_hashtab *peer_label_tree; /*!< labeled priorities in the peer list -- only on the head of the peer list */ const char *registrar; /*!< Registrar */ struct ast_exten *next; /*!< Extension with a greater ID */ char stuff[0]; @@ -169,10 +183,34 @@ struct ast_ignorepat { const char pattern[0]; }; +/*! \brief match_char: forms a syntax tree for quick matching of extension patterns */ +struct match_char +{ + int is_pattern; /* the pattern started with '_' */ + int deleted; /* if this is set, then... don't return it */ + char *x; /* the pattern itself-- matches a single char */ + int specificity; /* simply the strlen of x, or 10 for X, 9 for Z, and 8 for N; and '.' and '!' will add 11 ? */ + struct match_char *alt_char; + struct match_char *next_char; + struct ast_exten *exten; /* attached to last char of a pattern for exten */ +}; + +struct scoreboard /* make sure all fields are 0 before calling new_find_extension */ +{ + int total_specificity; + int total_length; + char last_char; /* set to ! or . if they are the end of the pattern */ + int canmatch; /* if the string to match was just too short */ + struct ast_exten *canmatch_exten; + struct ast_exten *exten; +}; + /*! \brief ast_context: An extension context */ struct ast_context { ast_rwlock_t lock; /*!< A lock to prevent multiple threads from clobbering the context */ struct ast_exten *root; /*!< The root of the list of extensions */ + struct ast_hashtab *root_tree; /*!< For exact matches on the extensions in the pattern tree, and for traversals of the pattern_tree */ + struct match_char *pattern_tree; /*!< A tree to speed up extension pattern matching */ struct ast_context *next; /*!< Link them together */ struct ast_include *includes; /*!< Include other contexts */ struct ast_ignorepat *ignorepats; /*!< Patterns for which to continue playing dialtone */ @@ -286,6 +324,88 @@ int pbx_builtin_setvar(struct ast_channel *, void *); static int pbx_builtin_setvar_multiple(struct ast_channel *, void *); static int pbx_builtin_importvar(struct ast_channel *, void *); static void set_ext_pri(struct ast_channel *c, const char *exten, int pri); +void new_find_extension(const char *str, struct scoreboard *score, struct match_char *tree, int length, int spec, const char *callerid); +struct match_char *already_in_tree(struct match_char *current, char *pat); +struct match_char *add_exten_to_pattern_tree(struct ast_context *con, struct ast_exten *e1); +struct match_char *add_pattern_node(struct ast_context *con, struct match_char *current, char *pattern, int is_pattern, int already, int specificity); +void create_match_char_tree(struct ast_context *con); +struct ast_exten *get_canmatch_exten(struct match_char *node); +void destroy_pattern_tree(struct match_char *pattern_tree); +static int hashtab_compare_contexts(const void *ah_a, const void *ah_b); +static int hashtab_compare_extens(const void *ha_a, const void *ah_b); +static int hashtab_compare_exten_numbers(const void *ah_a, const void *ah_b); +static int hashtab_compare_exten_labels(const void *ah_a, const void *ah_b); +static unsigned int hashtab_hash_contexts(const void *obj); +static unsigned int hashtab_hash_extens(const void *obj); +static unsigned int hashtab_hash_priority(const void *obj); +static unsigned int hashtab_hash_labels(const void *obj); + +/* labels, contexts are case sensitive priority numbers are ints */ +static int hashtab_compare_contexts(const void *ah_a, const void *ah_b) +{ + const struct ast_context *ac = ah_a; + const struct ast_context *bc = ah_b; + /* assume context names are registered in a string table! */ + return strcmp(ac->name, bc->name); +} + +static int hashtab_compare_extens(const void *ah_a, const void *ah_b) +{ + const struct ast_exten *ac = ah_a; + const struct ast_exten *bc = ah_b; + int x = strcmp(ac->exten, bc->exten); + if (x) /* if exten names are diff, then return */ + return x; + /* but if they are the same, do the cidmatch values match? */ + if (ac->matchcid && bc->matchcid) { + return strcmp(ac->cidmatch,bc->cidmatch); + } else { + return 1; /* if there's matchcid on one but not the other, they are different */ + } +} + +static int hashtab_compare_exten_numbers(const void *ah_a, const void *ah_b) +{ + const struct ast_exten *ac = ah_a; + const struct ast_exten *bc = ah_b; + return ac->priority != bc->priority; +} + +static int hashtab_compare_exten_labels(const void *ah_a, const void *ah_b) +{ + const struct ast_exten *ac = ah_a; + const struct ast_exten *bc = ah_b; + return strcmp(ac->label, bc->label); +} + +static unsigned int hashtab_hash_contexts(const void *obj) +{ + const struct ast_context *ac = obj; + return ast_hashtab_hash_string(ac->name); +} + +static unsigned int hashtab_hash_extens(const void *obj) +{ + const struct ast_exten *ac = obj; + unsigned int x = ast_hashtab_hash_string(ac->exten); + unsigned int y = 0; + if (ac->matchcid) + y = ast_hashtab_hash_string(ac->cidmatch); + return x+y; +} + +static unsigned int hashtab_hash_priority(const void *obj) +{ + const struct ast_exten *ac = obj; + return ast_hashtab_hash_int(ac->priority); +} + +static unsigned int hashtab_hash_labels(const void *obj) +{ + const struct ast_exten *ac = obj; + return ast_hashtab_hash_string(ac->label); +} + AST_RWLOCK_DEFINE_STATIC(globalslock); static struct varshead globals = AST_LIST_HEAD_NOLOCK_INIT_VALUE; @@ -558,6 +678,8 @@ static struct pbx_builtin { }; static struct ast_context *contexts; +static struct ast_hashtab *contexts_tree = NULL; + AST_RWLOCK_DEFINE_STATIC(conlock); /*!< Lock for the ast_context list */ static AST_RWLIST_HEAD_STATIC(apps, ast_app); @@ -653,6 +775,391 @@ static void pbx_destroy(struct ast_pbx *p) ast_free(p); } +/* form a tree that fully describes all the patterns in a context's extensions + * in this tree, a "node" consists of a series of match_char structs linked in a chain + * via the alt_char pointers. More than one pattern can share the same parts of the + * tree as other extensions with the same pattern to that point. The algorithm to + * find which pattern best matches a string, would follow **all** matching paths. As + * complete matches are found, a "max" match record would be updated if the match first involves + * a longer string, then, on a tie, a smaller total of specificity. This can be accomplished + * by recursive calls affecting a shared scoreboard. + * As and example, consider these 4 extensions: + * (a) NXXNXXXXXX + * (b) 307754XXXX + * (c) fax + * (d) NXXXXXXXXX + * + * In the above, between (a) and (d), (a) is a more specific pattern than (d), and would win over + * most numbers. For all numbers beginning with 307754, (b) should always win. + * + * These pattern should form a tree that looks like this: + * { "N" } --next--> { "X" } --next--> { "X" } --next--> { "N" } --next--> { "X" } ... blah ... --> { "X" exten_match: (a) } + * | | + * | |alt + * |alt | + * | { "X" } --next--> { "X" } ... blah ... --> { "X" exten_match: (d) } + * | + * { "3" } --next--> { "0" } --next--> { "7" } --next--> { "7" } --next--> { "5" } ... blah ... --> { "X" exten_match: (b) } + * | + * |alt + * | + * { "f" } --next--> { "a" } --next--> { "x" exten_match: (c) } + * + * In the above, I could easily turn "N" into "23456789", but I think that a quick "if( *z >= '2' && *z <= '9' )" might take + * fewer CPU cycles than a call to index("23456789",*z), where *z is the char to match... + * + * traversal is pretty simple: one routine merely traverses the alt list, and for each match in the pattern, it calls itself + * on the corresponding next pointer, incrementing also the pointer of the string to be matched, and passing the total specificity and length. + * We pass a pointer to a scoreboard down through, also. + * When an exten_match pointer is set, or a '.' or a '!' is encountered, we update the scoreboard only if the length is greater, or in case + * of equal length, if the specificity is lower, and return. Hope the limit on stack depth won't be a problem... this routine should + * be pretty lean as far a stack usage goes. Any non-match terminates the recursion down a branch. + * + * In the above example, with the number "3077549999" as the pattern, the traversor should match extensions a, b and d. All are + * of length 10; but they have total specificities of 96, 46, and 98, respectively. (b) wins with its lower specificity number! + * + * Just how much time this algorithm might save over a plain linear traversal over all possible patterns is unknown. But, it should + * be pretty close to optimal for this sort of overall algorithm. + * + * */ + +/* you only really update the scoreboard, if the new score is BETTER than the + * one stored there. ignore it otherwise. + */ + + +static void update_scoreboard(struct scoreboard *board, int length, int spec, struct ast_exten *exten, char last, const char *callerid) +{ + /* doing a matchcid() check here would be easy and cheap, but... + unfortunately, it only works if an extension can have only one + cid to match. That's not real life. CID patterns need to exist + in the tree for this sort of thing to work properly. */ + + if (length > board->total_length) { + board->total_specificity = spec; + board->total_length = length; + board->exten = exten; + board->last_char = last; + } else if (length == board->total_length && spec < board->total_specificity) { + board->total_specificity = spec; + board->total_length = length; + board->exten = exten; + board->last_char = last; + } +} + +#ifdef NEED_DEBUG +static void log_match_char_tree(struct match_char *node, char *prefix) +{ + char my_prefix[1024]; + + ast_log(LOG_DEBUG,"%s%s:%c:%d:%s\n", prefix, node->x, node->is_pattern ? 'Y':'N', node->specificity, node->exten? "EXTEN":""); + strcpy(my_prefix,prefix); + strcat(my_prefix,"+-----------------"); + if (node->next_char) + print_match_char_tree(node->next_char, my_prefix); + if (node->alt_char) + print_match_char_tree(node->alt_char, prefix); +} +#endif + +static void cli_match_char_tree(struct match_char *node, char *prefix, int fd) +{ + char my_prefix[1024]; + + ast_cli(fd, "%s%s:%c:%d:%s\n", prefix, node->x, node->is_pattern ? 'Y':'N', node->specificity, node->exten? "EXTEN":""); + strcpy(my_prefix,prefix); + strcat(my_prefix,"+-----------------"); + if (node->next_char) + cli_match_char_tree(node->next_char, my_prefix, fd); + if (node->alt_char) + cli_match_char_tree(node->alt_char, prefix, fd); +} + +struct ast_exten *get_canmatch_exten(struct match_char *node) +{ + /* find the exten at the end of the rope */ + struct match_char *node2 = node; + for (node2 = node; node2; node2 = node2->next_char) + if (node2->exten) + return node2->exten; + return 0; +} + +void new_find_extension(const char *str, struct scoreboard *score, struct match_char *tree, int length, int spec, const char *callerid) +{ + struct match_char *p; /* note minimal stack storage requirements */ + for (p=tree; p; p=p->alt_char) { + if (p->x[0] == 'N' && p->x[1] == 0 && *str >= '2' && *str <= '9' ) { + if (p->exten) /* if a shorter pattern matches along the way, might as well report it */ + update_scoreboard(score, length+1, spec+8, p->exten,0,callerid); + + if (p->next_char && ( *(str+1) || (p->next_char->x[0] == '/' && p->next_char->x[1] == 0))) { + if (*(str+1)) + new_find_extension(str+1, score, p->next_char, length+1, spec+p->specificity, callerid); + else + new_find_extension("/", score, p->next_char, length+1, spec+p->specificity, callerid); + } else if (p->next_char && !*(str+1)) { + score->canmatch = 1; + score->canmatch_exten = get_canmatch_exten(p); + } else { + return; + } + } else if (p->x[0] == 'Z' && p->x[1] == 0 && *str >= '1' && *str <= '9' ) { + if (p->exten) /* if a shorter pattern matches along the way, might as well report it */ + update_scoreboard(score, length+1, spec+8, p->exten,0,callerid); + + if (p->next_char && ( *(str+1) || (p->next_char->x[0] == '/' && p->next_char->x[1] == 0))) { + if (*(str+1)) + new_find_extension(str+1, score, p->next_char, length+1, spec+p->specificity, callerid); + else + new_find_extension("/", score, p->next_char, length+1, spec+p->specificity, callerid); + } else if (p->next_char && !*(str+1)) { + score->canmatch = 1; + score->canmatch_exten = get_canmatch_exten(p); + } else { + return; + } + } else if (p->x[0] == 'X' && p->x[1] == 0 && *str >= '0' && *str <= '9' ) { + if (p->exten) /* if a shorter pattern matches along the way, might as well report it */ + update_scoreboard(score, length+1, spec+8, p->exten,0,callerid); + + if (p->next_char && ( *(str+1) || (p->next_char->x[0] == '/' && p->next_char->x[1] == 0))) { + if (*(str+1)) + new_find_extension(str+1, score, p->next_char, length+1, spec+p->specificity, callerid); + else + new_find_extension("/", score, p->next_char, length+1, spec+p->specificity, callerid); + } else if (p->next_char && !*(str+1)) { + score->canmatch = 1; + score->canmatch_exten = get_canmatch_exten(p); + } else { + return; + } + } else if (p->x[0] == '.' && p->x[1] == 0) { + update_scoreboard(score, length+1, spec+11, p->exten, '.', callerid); + if (p->next_char && p->next_char->x[0] == '/' && p->next_char->x[1] == 0) { + new_find_extension("/", score, p->next_char, length+1, spec+p->specificity, callerid); + } + return; + } else if (p->x[0] == '!' && p->x[1] == 0) { + update_scoreboard(score, length+1, spec+11, p->exten, '!', callerid); + if (p->next_char && p->next_char->x[0] == '/' && p->next_char->x[1] == 0) { + new_find_extension("/", score, p->next_char, length+1, spec+p->specificity, callerid); + } + return; + } else if (p->x[0] == '/' && p->x[1] == 0) { + /* the pattern in the tree includes the cid match! */ + if (p->next_char && callerid && *callerid) { + new_find_extension(callerid, score, p->next_char, length+1, spec, callerid); + } + } else if (index(p->x, *str)) { + if (p->exten) /* if a shorter pattern matches along the way, might as well report it */ + update_scoreboard(score, length+1, spec+8, p->exten,0,callerid); + + + if (p->next_char && ( *(str+1) || (p->next_char->x[0] == '/' && p->next_char->x[1] == 0))) { + if (*(str+1)) { + new_find_extension(str+1, score, p->next_char, length+1, spec+p->specificity, callerid); + } else { + new_find_extension("/", score, p->next_char, length+1, spec+p->specificity, callerid); + } + } else if (p->next_char && !*(str+1)) { + score->canmatch = 1; + score->canmatch_exten = get_canmatch_exten(p); + } else { + return; + } + } + } +} + +/* the algorithm for forming the extension pattern tree is also a bit simple; you + * traverse all the extensions in a context, and for each char of the extension, + * you see if it exists in the tree; if it doesn't, you add it at the appropriate + * spot. What more can I say? At the end of the list, you cap it off by adding the + * address of the extension involved. Duplicate patterns will be complained about. + * + * Ideally, this would be done for each context after it is created and fully + * filled. It could be done as a finishing step after extensions.conf or .ael is + * loaded, or it could be done when the first search is encountered. It should only + * have to be done once, until the next unload or reload. + * + * I guess forming this pattern tree would be analogous to compiling a regex. + */ + + +struct match_char *already_in_tree(struct match_char *current, char *pat) +{ + struct match_char *t; + if (!current) + return 0; + for (t=current; t; t=t->alt_char) { + if (strcmp(pat,t->x) == 0) /* uh, we may want to sort exploded [] contents to make matching easy */ + return t; + } + return 0; +} + +struct match_char *add_pattern_node(struct ast_context *con, struct match_char *current, char *pattern, int is_pattern, int already, int specificity) +{ + struct match_char *m = ast_calloc(1,sizeof(struct match_char)); + m->x = strdup(pattern); + m->is_pattern = is_pattern; + if (specificity == 1 && is_pattern && pattern[0] == 'N') + m->specificity = 8; + else if (specificity == 1 && is_pattern && pattern[0] == 'Z') + m->specificity = 9; + else if (specificity == 1 && is_pattern && pattern[0] == 'X') + m->specificity = 10; + else if (specificity == 1 && is_pattern && pattern[0] == '.') + m->specificity = 11; + else if (specificity == 1 && is_pattern && pattern[0] == '!') + m->specificity = 11; + else + m->specificity = specificity; + + if (!con->pattern_tree) { + con->pattern_tree = m; + } else { + if (already) { /* switch to the new regime (traversing vs appending)*/ + m->alt_char = current->alt_char; + current->alt_char = m; + } else { + if (current->next_char) { + m->alt_char = current->next_char->alt_char; + current->next_char = m; + } else { + current->next_char = m; + } + } + } + return m; +} + +struct match_char *add_exten_to_pattern_tree(struct ast_context *con, struct ast_exten *e1) +{ + struct match_char *m1=0,*m2=0; + int specif; + int already; + int pattern = 0; + char buf[256]; + char extenbuf[512]; + char *s1 = extenbuf; + int l1 = strlen(e1->exten) + strlen(e1->cidmatch) + 2; + + + strncpy(extenbuf,e1->exten,sizeof(extenbuf)); + if (e1->matchcid && l1 <= sizeof(extenbuf)) { + strcat(extenbuf,"/"); + strcat(extenbuf,e1->cidmatch); + } else if (l1 > sizeof(extenbuf)) { + ast_log(LOG_ERROR,"The pattern %s/%s is too big to deal with: it will be ignored! Disaster!\n", e1->exten, e1->cidmatch); + return 0; + } +#ifdef NEED_DEBUG + ast_log(LOG_DEBUG,"Adding exten %s%c%s to tree\n", s1, e1->matchcid? '/':' ', e1->matchcid? e1->cidmatch : ""); +#endif + m1 = con->pattern_tree; /* each pattern starts over at the root of the pattern tree */ + already = 1; + + if ( *s1 == '_') { + pattern = 1; + s1++; + } + while( *s1 ) { + if (pattern && *s1 == '[' && *(s1-1) != '\\') { + char *s2 = buf; + while (*s1 != ']' && *(s1-1) != '\\' ) { + if (*s1 == '\\') { + if (*(s1+1) == ']') { + *s2++ = ']'; + s1++;s1++; + } else if (*(s1+1) == '\\') { + *s2++ = '\\'; + s1++;s1++; + } else if (*(s1+1) == '-') { + *s2++ = '-'; + s1++; s1++; + } else if (*(s1+1) == '[') { + *s2++ = '['; + s1++; s1++; + } + } else if (*s1 == '-') { /* remember to add some error checking to all this! */ + char s3 = *(s1-1); + char s4 = *(s1+1); + for (s3++; s3 <= s4; s3++) { + *s2++ = s3; + } + s1++; s1++; + } else { + *s2++ = *s1++; + } + } + specif = strlen(buf); + } else { + if (*s1 == '\\') { + s1++; + buf[0] = *s1; + } else { + buf[0] = *s1; + } + buf[1] = 0; + specif = 1; + } + + if (already && (m2=already_in_tree(m1,buf))) { + if (!(*(s1+1))) /* if this is the end of the pattern, but not the end of the tree, then mark this node with the exten... + a shorter pattern might win if the longer one doesn't match */ + m1->exten = e1; + m1 = m2->next_char; /* m1 points to the node to compare against */ + } else { + m1 = add_pattern_node(con, m1, buf, pattern, already,specif); /* m1 is the node just added */ + if (!(*(s1+1))) + m1->exten = e1; + already = 0; + } + s1++; /* advance to next char */ + } + return m1; +} + +void create_match_char_tree(struct ast_context *con) +{ + struct ast_hashtab_iter *t1; + struct ast_exten *e1; +#ifdef NEED_DEBUG + int biggest_bucket, resizes, numobjs, numbucks; + + ast_log(LOG_DEBUG,"Creating Extension Trie for context %s\n", con->name); + ast_hashtab_get_stats(con->root_tree, &biggest_bucket, &resizes, &numobjs, &numbucks); + ast_log(LOG_DEBUG,"This tree has %d objects in %d bucket lists, longest list=%d objects, and has resized %d times\n", + numobjs, numbucks, biggest_bucket, resizes); +#endif + t1 = ast_hashtab_start_traversal(con->root_tree); + while( (e1 = ast_hashtab_next(t1)) ) { + add_exten_to_pattern_tree(con, e1); + } + ast_hashtab_end_traversal(t1); +} + +void destroy_pattern_tree(struct match_char *pattern_tree) /* pattern tree is a simple binary tree, sort of, so the proper way to destroy it is... recursively! */ +{ + /* destroy all the alternates */ + if (pattern_tree->alt_char) { + destroy_pattern_tree(pattern_tree->alt_char); + pattern_tree->alt_char = 0; + } + /* destroy all the nexts */ + if (pattern_tree->next_char) { + destroy_pattern_tree(pattern_tree->next_char); + pattern_tree->next_char = 0; + } + pattern_tree->exten = 0; /* never hurts to make sure there's no pointers laying around */ + if (pattern_tree->x) + free(pattern_tree->x); + free(pattern_tree); +} + /* * Special characters used in patterns: * '_' underscore is the leading character of a pattern. @@ -947,13 +1454,34 @@ int ast_extension_close(const char *pattern, const char *data, int needmore) return extension_match_core(pattern, data, needmore); } +struct fake_context /* this struct is purely for matching in the hashtab */ +{ + ast_rwlock_t lock; + struct ast_exten *root; + struct ast_hashtab *root_tree; + struct match_char *pattern_tree; + struct ast_context *next; + struct ast_include *includes; + struct ast_ignorepat *ignorepats; + const char *registrar; + AST_LIST_HEAD_NOLOCK(, ast_sw) alts; + ast_mutex_t macrolock; + char name[256]; +}; + struct ast_context *ast_context_find(const char *name) { struct ast_context *tmp = NULL; + struct fake_context item; + strncpy(item.name,name,256); ast_rdlock_contexts(); - while ( (tmp = ast_walk_contexts(tmp)) ) { - if (!name || !strcasecmp(name, tmp->name)) - break; + if( contexts_tree ) { + tmp = ast_hashtab_lookup(contexts_tree,&item); + } else { + while ( (tmp = ast_walk_contexts(tmp)) ) { + if (!name || !strcasecmp(name, tmp->name)) + break; + } } ast_unlock_contexts(); return tmp; @@ -965,17 +1493,6 @@ struct ast_context *ast_context_find(const char *name) #define STATUS_NO_LABEL 4 #define STATUS_SUCCESS 5 -static int matchcid(const char *cidpattern, const char *callerid) -{ - /* If the Caller*ID pattern is empty, then we're matching NO Caller*ID, so - failing to get a number should count as a match, otherwise not */ - - if (ast_strlen_zero(callerid)) - return ast_strlen_zero(cidpattern) ? 1 : 0; - - return ast_extension_match(cidpattern, callerid); -} - struct ast_exten *pbx_find_extension(struct ast_channel *chan, struct ast_context *bypass, struct pbx_find_info *q, const char *context, const char *exten, int priority, @@ -986,6 +1503,12 @@ struct ast_exten *pbx_find_extension(struct ast_channel *chan, struct ast_exten *e, *eroot; struct ast_include *i; struct ast_sw *sw; + struct ast_exten pattern; + struct scoreboard score; + + pattern.label = label; + pattern.priority = priority; + /* Initialize status if appropriate */ if (q->stacklen == 0) { @@ -1007,18 +1530,72 @@ struct ast_exten *pbx_find_extension(struct ast_channel *chan, if (bypass) /* bypass means we only look there */ tmp = bypass; else { /* look in contexts */ + struct fake_context item; + strncpy(item.name,context,256); + tmp = ast_hashtab_lookup(contexts_tree,&item); +#ifdef NOTNOW tmp = NULL; while ((tmp = ast_walk_contexts(tmp)) ) { if (!strcmp(tmp->name, context)) break; } +#endif if (!tmp) return NULL; } if (q->status < STATUS_NO_EXTENSION) q->status = STATUS_NO_EXTENSION; + + /* Do a search for matching extension */ + eroot = NULL; + score.total_specificity = 0; + score.exten = 0; + score.total_length = 0; + if (!tmp->pattern_tree) + { + create_match_char_tree(tmp); +#ifdef NEED_DEBUG + ast_log(LOG_DEBUG,"Tree Created in context %s:\n", context); + print_match_char_tree(tmp->pattern_tree," "); +#endif + } + + new_find_extension(exten, &score, tmp->pattern_tree, 0, 0, callerid); + eroot = score.exten; + + if (score.last_char == '!' && action == E_MATCHMORE) { + /* We match an extension ending in '!'. + * The decision in this case is final and is NULL (no match). + */ + return NULL; + } + + if (!eroot && action == E_CANMATCH && score.canmatch_exten) { + q->status = STATUS_SUCCESS; + return score.canmatch_exten; + } + + if (eroot) { + /* found entry, now look for the right priority */ + if (q->status < STATUS_NO_PRIORITY) + q->status = STATUS_NO_PRIORITY; + e = NULL; + if (action == E_FINDLABEL && label ) { + if (q->status < STATUS_NO_LABEL) + q->status = STATUS_NO_LABEL; + e = ast_hashtab_lookup(eroot->peer_label_tree, &pattern); + } else { + e = ast_hashtab_lookup(eroot->peer_tree, &pattern); + } + if (e) { /* found a valid match */ + q->status = STATUS_SUCCESS; + q->foundcontext = context; + return e; + } + } +#ifdef NOTNOW2 /* scan the list trying to match extension and CID */ eroot = NULL; while ( (eroot = ast_walk_context_extensions(tmp, eroot)) ) { @@ -1037,6 +1614,14 @@ struct ast_exten *pbx_find_extension(struct ast_channel *chan, if (q->status < STATUS_NO_PRIORITY) q->status = STATUS_NO_PRIORITY; e = NULL; + if (action == E_FINDLABEL && label ) { + if (q->status < STATUS_NO_LABEL) + q->status = STATUS_NO_LABEL; + e = ast_hashtab_lookup(eroot->peer_label_tree, &pattern); + } else { + e = ast_hashtab_lookup(eroot->peer_tree, &pattern); + } +#ifdef NOTNOW while ( (e = ast_walk_extension_priorities(eroot, e)) ) { /* Match label or priority */ if (action == E_FINDLABEL) { @@ -1048,12 +1633,14 @@ struct ast_exten *pbx_find_extension(struct ast_channel *chan, break; /* found it */ } /* else keep searching */ } +#endif if (e) { /* found a valid match */ q->status = STATUS_SUCCESS; q->foundcontext = context; return e; } } +#endif /* Check alternative switches */ AST_LIST_TRAVERSE(&tmp->alts, sw, list) { struct ast_switch *asw = pbx_findswitch(sw->name); @@ -2751,6 +3338,10 @@ static void destroy_exten(struct ast_exten *e) if (e->priority == PRIORITY_HINT) ast_remove_hint(e); + if (e->peer_tree) + ast_hashtab_destroy(e->peer_tree,0); + if (e->peer_label_tree) + ast_hashtab_destroy(e->peer_label_tree, 0); if (e->datad) e->datad(e->data); ast_free(e); @@ -2830,15 +3421,21 @@ int pbx_set_autofallthrough(int newval) static struct ast_context *find_context_locked(const char *context) { struct ast_context *c = NULL; - + struct fake_context item; + strncpy(item.name, context, 256); ast_rdlock_contexts(); + c = ast_hashtab_lookup(contexts_tree,&item); + +#ifdef NOTNOW + while ( (c = ast_walk_contexts(c)) ) { if (!strcmp(ast_get_context_name(c), context)) return c; } +#endif ast_unlock_contexts(); - return NULL; + return c; } /*! @@ -3056,9 +3653,18 @@ int ast_context_lockmacro(const char *context) { struct ast_context *c = NULL; int ret = -1; + struct fake_context item; ast_rdlock_contexts(); + strncpy(item.name,context,256); + c = ast_hashtab_lookup(contexts_tree,&item); + if (c) + ret = 0; + + +#ifdef NOTNOW + while ((c = ast_walk_contexts(c))) { if (!strcmp(ast_get_context_name(c), context)) { ret = 0; @@ -3066,6 +3672,7 @@ int ast_context_lockmacro(const char *context) } } +#endif ast_unlock_contexts(); /* if we found context, lock macrolock */ @@ -3084,9 +3691,16 @@ int ast_context_unlockmacro(const char *context) { struct ast_context *c = NULL; int ret = -1; + struct fake_context item; ast_rdlock_contexts(); + strncpy(item.name, context, 256); + c = ast_hashtab_lookup(contexts_tree,&item); + if (c) + ret = 0; +#ifdef NOTNOW + while ((c = ast_walk_contexts(c))) { if (!strcmp(ast_get_context_name(c), context)) { ret = 0; @@ -3094,6 +3708,7 @@ int ast_context_unlockmacro(const char *context) } } +#endif ast_unlock_contexts(); /* if we found context, unlock macrolock */ @@ -3643,6 +4258,13 @@ static int show_dialplan_helper(int fd, const char *context, const char *exten, buf, ast_get_switch_registrar(sw)); } } + + if (option_debug && c->pattern_tree) + { + ast_cli(fd," In-mem exten Trie for Fast Extension Pattern Matching:\r\b\n"); + cli_match_char_tree(c->pattern_tree, " ", fd); + } + ast_unlock_context(c); @@ -4038,41 +4660,64 @@ int ast_unregister_application(const char *app) static struct ast_context *__ast_context_create(struct ast_context **extcontexts, const char *name, const char *registrar, int existsokay) { struct ast_context *tmp, **local_contexts; + struct fake_context search; int length = sizeof(struct ast_context) + strlen(name) + 1; + if (!contexts_tree) { + contexts_tree = ast_hashtab_create(17, + hashtab_compare_contexts, + ast_hashtab_resize_java, + ast_hashtab_newsize_java, + hashtab_hash_contexts, + 1); + } + if (!extcontexts) { - ast_wrlock_contexts(); + ast_rdlock_contexts(); local_contexts = &contexts; - } else + strncpy(search.name,name,sizeof(search.name)); + tmp = ast_hashtab_lookup(contexts_tree, &search); + if (!existsokay && tmp) { + ast_log(LOG_WARNING, "Tried to register context '%s', already in use\n", name); + } + ast_unlock_contexts(); + if (tmp) + return tmp; + } else { /* local contexts just in a linked list; search there for the new context; slow, linear search, but not frequent */ local_contexts = extcontexts; - - for (tmp = *local_contexts; tmp; tmp = tmp->next) { - if (!strcasecmp(tmp->name, name)) { - if (!existsokay) { - ast_log(LOG_WARNING, "Tried to register context '%s', already in use\n", name); - tmp = NULL; + for (tmp = *local_contexts; tmp; tmp = tmp->next) { + if (!strcasecmp(tmp->name, name)) { + if (!existsokay) { + ast_log(LOG_WARNING, "Tried to register context '%s', already in use\n", name); + tmp = NULL; + } + return tmp; } - if (!extcontexts) - ast_unlock_contexts(); - return tmp; } } + if ((tmp = ast_calloc(1, length))) { ast_rwlock_init(&tmp->lock); ast_mutex_init(&tmp->macrolock); strcpy(tmp->name, name); tmp->root = NULL; + tmp->root_tree = NULL; tmp->registrar = registrar; - tmp->next = *local_contexts; tmp->includes = NULL; tmp->ignorepats = NULL; - *local_contexts = tmp; - ast_debug(1, "Registered context '%s'\n", tmp->name); - ast_verb(3, "Registered extension context '%s'\n", tmp->name); } - - if (!extcontexts) + if (!extcontexts) { + ast_wrlock_contexts(); + tmp->next = *local_contexts; + *local_contexts = tmp; + ast_hashtab_insert_safe(contexts_tree, tmp); /*put this context into the tree */ ast_unlock_contexts(); + } else { + tmp->next = *local_contexts; + *local_contexts = tmp; + } + ast_debug(1, "Registered context '%s'\n", tmp->name); + ast_verb(3, "Registered extension context '%s'\n", tmp->name); return tmp; } @@ -4155,6 +4800,11 @@ void ast_merge_contexts_and_delete(struct ast_context **extcontexts, const char tmp = tmp->next; } } + tmp = *extcontexts; + while (tmp) { + ast_hashtab_insert_safe(contexts_tree, tmp); /*put this context into the tree */ + tmp = tmp->next; + } if (lasttmp) { lasttmp->next = contexts; contexts = *extcontexts; @@ -4847,12 +5497,16 @@ static int add_pri(struct ast_context *con, struct ast_exten *tmp, struct ast_exten *el, struct ast_exten *e, int replace) { struct ast_exten *ep; + struct ast_exten *eh=e; for (ep = NULL; e ; ep = e, e = e->peer) { if (e->priority >= tmp->priority) break; } if (!e) { /* go at the end, and ep is surely set because the list is not empty */ + ast_hashtab_insert_safe(eh->peer_tree, tmp); + if (tmp->label) + ast_hashtab_insert_safe(eh->peer_label_tree, tmp); ep->peer = tmp; return 0; /* success */ } @@ -4871,12 +5525,41 @@ static int add_pri(struct ast_context *con, struct ast_exten *tmp, */ tmp->next = e->next; /* not meaningful if we are not first in the peer list */ tmp->peer = e->peer; /* always meaningful */ - if (ep) /* We're in the peer list, just insert ourselves */ + if (ep) { /* We're in the peer list, just insert ourselves */ + ast_hashtab_remove_object_via_lookup(eh->peer_tree,e); + if (e->label) + ast_hashtab_remove_object_via_lookup(eh->peer_label_tree,e); + ast_hashtab_insert_safe(eh->peer_tree,tmp); + if (tmp->label) + ast_hashtab_insert_safe(eh->peer_label_tree,tmp); ep->peer = tmp; - else if (el) /* We're the first extension. Take over e's functions */ + } else if (el) { /* We're the first extension. Take over e's functions */ + tmp->peer_tree = e->peer_tree; + tmp->peer_label_tree = e->peer_label_tree; + ast_hashtab_remove_object_via_lookup(tmp->peer_tree,e); + ast_hashtab_insert_safe(tmp->peer_tree,tmp); + if (e->label) + ast_hashtab_remove_object_via_lookup(tmp->peer_label_tree,e); + if (tmp->label) + ast_hashtab_insert_safe(tmp->peer_label_tree,tmp); + ast_hashtab_remove_object_via_lookup(con->root_tree, e->exten); + ast_hashtab_insert_safe(con->root_tree, tmp->exten); el->next = tmp; - else /* We're the very first extension. */ - con->root = tmp; + } else { /* We're the very first extension. */ + ast_hashtab_remove_object_via_lookup(con->root_tree,e); + ast_hashtab_insert_safe(con->root_tree,tmp); + tmp->peer_tree = e->peer_tree; + tmp->peer_label_tree = e->peer_label_tree; + ast_hashtab_remove_object_via_lookup(tmp->peer_tree,e); + ast_hashtab_insert_safe(tmp->peer_tree,tmp); + if (e->label) + ast_hashtab_remove_object_via_lookup(tmp->peer_label_tree,e); + if (tmp->label) + ast_hashtab_insert_safe(tmp->peer_label_tree,tmp); + ast_hashtab_remove_object_via_lookup(con->root_tree, e->exten); + ast_hashtab_insert_safe(con->root_tree, tmp->exten); + con->root = tmp; + } if (tmp->priority == PRIORITY_HINT) ast_change_hint(e,tmp); /* Destroy the old one */ @@ -4886,9 +5569,22 @@ static int add_pri(struct ast_context *con, struct ast_exten *tmp, } else { /* Slip ourselves in just before e */ tmp->peer = e; tmp->next = e->next; /* extension chain, or NULL if e is not the first extension */ - if (ep) /* Easy enough, we're just in the peer list */ + if (ep) { /* Easy enough, we're just in the peer list */ + if (tmp->label) + ast_hashtab_insert_safe(eh->peer_label_tree, tmp); + ast_hashtab_insert_safe(eh->peer_tree, tmp); ep->peer = tmp; - else { /* we are the first in some peer list, so link in the ext list */ + } else { /* we are the first in some peer list, so link in the ext list */ + tmp->peer_tree = e->peer_tree; + tmp->peer_label_tree = e ->peer_label_tree; + e->peer_tree = 0; + e->peer_label_tree = 0; + ast_hashtab_insert_safe(tmp->peer_tree,tmp); + if (tmp->label) { + ast_hashtab_insert_safe(tmp->peer_label_tree,tmp); + } + ast_hashtab_remove_object_via_lookup(con->root_tree,e); + ast_hashtab_insert_safe(con->root_tree,tmp); if (el) el->next = tmp; /* in the middle... */ else @@ -4938,11 +5634,13 @@ int ast_add_extension2(struct ast_context *con, * All priorities for the same ext/pattern/cid are kept in a list, * using the 'peer' field as a link field.. */ - struct ast_exten *tmp, *e, *el = NULL; + struct ast_exten *tmp, *tmp2, *e, *el = NULL; int res; int length; char *p; char expand_buf[VAR_BUF_SIZE]; + struct ast_exten dummy_exten; + char dummy_name[1024]; /* if we are adding a hint, and there are global variables, and the hint contains variable references, then expand them @@ -4994,6 +5692,17 @@ int ast_add_extension2(struct ast_context *con, tmp->registrar = registrar; ast_wrlock_context(con); + + if (con->pattern_tree) { /* usually, on initial load, the pattern_tree isn't formed until the first find_exten; so if we are adding + an extension, and the trie exists, then we need to incrementally add this pattern to it. */ + strncpy(dummy_name,extension,sizeof(dummy_name)); + dummy_exten.exten = dummy_name; + tmp2 = ast_hashtab_lookup(con->root_tree,&dummy_exten); + if (!tmp2) { + /* hmmm, not in the trie; */ + add_exten_to_pattern_tree(con, tmp); + } + } res = 0; /* some compilers will think it is uninitialized otherwise */ for (e = con->root; e; el = e, e = e->next) { /* scan the extension list */ res = ext_cmp(e->exten, extension); @@ -5023,10 +5732,50 @@ int ast_add_extension2(struct ast_context *con, * so insert in the main list right before 'e' (if any) */ tmp->next = e; - if (el) + if (el) { /* there is another exten already in this context */ el->next = tmp; - else + tmp->peer_tree = ast_hashtab_create(13, + hashtab_compare_exten_numbers, + ast_hashtab_resize_java, + ast_hashtab_newsize_java, + hashtab_hash_priority, + 1); + tmp->peer_label_tree = ast_hashtab_create(7, + hashtab_compare_exten_labels, + ast_hashtab_resize_java, + ast_hashtab_newsize_java, + hashtab_hash_labels, + 1); + if (label) + ast_hashtab_insert_safe(tmp->peer_label_tree,tmp); + ast_hashtab_insert_safe(tmp->peer_tree, tmp); + + } else { /* this is the first exten in this context */ + if (!con->root_tree) + con->root_tree = ast_hashtab_create(27, + hashtab_compare_extens, + ast_hashtab_resize_java, + ast_hashtab_newsize_java, + hashtab_hash_extens, + 1); con->root = tmp; + con->root->peer_tree = ast_hashtab_create(13, + hashtab_compare_exten_numbers, + ast_hashtab_resize_java, + ast_hashtab_newsize_java, + hashtab_hash_priority, + 1); + con->root->peer_label_tree = ast_hashtab_create(7, + hashtab_compare_exten_labels, + ast_hashtab_resize_java, + ast_hashtab_newsize_java, + hashtab_hash_labels, + 1); + if (label) + ast_hashtab_insert_safe(con->root->peer_label_tree,tmp); + ast_hashtab_insert_safe(con->root->peer_tree, tmp); + } + ast_hashtab_insert_safe(con->root_tree, tmp); ast_unlock_context(con); if (tmp->priority == PRIORITY_HINT) ast_add_hint(tmp); @@ -5461,6 +6210,8 @@ void __ast_context_destroy(struct ast_context *con, const char *registrar) break; ast_wrlock_context(tmp); ast_debug(1, "delete ctx %s %s\n", tmp->name, tmp->registrar); + ast_hashtab_remove_this_object(contexts_tree,tmp); + next = tmp->next; if (tmpl) tmpl->next = next; @@ -5479,6 +6230,14 @@ void __ast_context_destroy(struct ast_context *con, const char *registrar) ipi = ipi->next; ast_free(ipl); } + /* destroy the hash tabs */ + if (tmp->root_tree) { + ast_hashtab_destroy(tmp->root_tree, 0); + } + /* and destroy the pattern tree */ + if (tmp->pattern_tree) + destroy_pattern_tree(tmp->pattern_tree); + while ((sw = AST_LIST_REMOVE_HEAD(&tmp->alts, list))) ast_free(sw); for (e = tmp->root; e;) { @@ -6489,7 +7248,7 @@ int ast_context_verify_includes(struct ast_context *con) while ( (inc = ast_walk_context_includes(con, inc)) ) if (!ast_context_find(inc->rname)) { res = -1; - ast_log(LOG_WARNING, "Context '%s' tries includes nonexistent context '%s'\n", + ast_log(LOG_WARNING, "Context '%s' tries to include nonexistent context '%s'\n", ast_get_context_name(con), inc->rname); } return res; diff --git a/pbx/pbx_ael.c b/pbx/pbx_ael.c index 521d13ada..e6d1fdc87 100644 --- a/pbx/pbx_ael.c +++ b/pbx/pbx_ael.c @@ -123,8 +123,6 @@ static int pbx_load_module(void) rfilename = alloca(strlen(config) + strlen(ast_config_AST_CONFIG_DIR) + 2); sprintf(rfilename, "%s/%s", ast_config_AST_CONFIG_DIR, config); } - ast_log(LOG_NOTICE, "AEL load process: calculated config file name '%s'.\n", rfilename); - if (access(rfilename,R_OK) != 0) { ast_log(LOG_NOTICE, "File %s not found; AEL declining load\n", rfilename); return AST_MODULE_LOAD_DECLINE; diff --git a/res/ael/pval.c b/res/ael/pval.c index 5deb01828..923a55148 100644 --- a/res/ael/pval.c +++ b/res/ael/pval.c @@ -5409,18 +5409,18 @@ int do_pbx_load_module(void) } parse_tree = ael2_parse(rfilename, &errs); - ast_log(LOG_NOTICE, "AEL load process: parsed config file name '%s'.\n", rfilename); + ast_log(LOG_DEBUG, "AEL load process: parsed config file name '%s'.\n", rfilename); ael2_semantic_check(parse_tree, &sem_err, &sem_warn, &sem_note); if (errs == 0 && sem_err == 0) { - ast_log(LOG_NOTICE, "AEL load process: checked config file name '%s'.\n", rfilename); + ast_log(LOG_DEBUG, "AEL load process: checked config file name '%s'.\n", rfilename); ast_compile_ael2(&local_contexts, parse_tree); - ast_log(LOG_NOTICE, "AEL load process: compiled config file name '%s'.\n", rfilename); + ast_log(LOG_DEBUG, "AEL load process: compiled config file name '%s'.\n", rfilename); ast_merge_contexts_and_delete(&local_contexts, registrar); - ast_log(LOG_NOTICE, "AEL load process: merged config file name '%s'.\n", rfilename); + ast_log(LOG_DEBUG, "AEL load process: merged config file name '%s'.\n", rfilename); for (con = ast_walk_contexts(NULL); con; con = ast_walk_contexts(con)) ast_context_verify_includes(con); - ast_log(LOG_NOTICE, "AEL load process: verified config file name '%s'.\n", rfilename); + ast_log(LOG_DEBUG, "AEL load process: verified config file name '%s'.\n", rfilename); } else { ast_log(LOG_ERROR, "Sorry, but %d syntax errors and %d semantic errors were detected. It doesn't make sense to compile.\n", errs, sem_err); destroy_pval(parse_tree); /* free up the memory */ diff --git a/utils/Makefile b/utils/Makefile index a1e89b8c9..504e7f34c 100644 --- a/utils/Makefile +++ b/utils/Makefile @@ -16,7 +16,7 @@ .PHONY: clean all uninstall # to get check_expr, add it to the ALL_UTILS list -ALL_UTILS:=astman smsq stereorize streamplayer aelparse muted check_expr conf2ael hashtest2 +ALL_UTILS:=astman smsq stereorize streamplayer aelparse muted check_expr conf2ael hashtest2 hashtest UTILS:=$(ALL_UTILS) include $(ASTTOPDIR)/Makefile.rules @@ -65,7 +65,7 @@ clean: rm -f *.s *.i rm -f md5.c strcompat.c ast_expr2.c ast_expr2f.c pbx_ael.c pval.c rm -f aelparse.c aelbison.c conf2ael - rm -f utils.c threadstorage.c sha1.c astobj2.c hashtest2 + rm -f utils.c threadstorage.c sha1.c astobj2.c hashtest2 hashtest md5.c: ../main/md5.c @cp $< $@ @@ -76,6 +76,9 @@ astman: LIBS+=$(NEWT_LIB) stereorize: stereorize.o frame.o stereorize: LIBS+=-lm +hashtab.c: ../main/hashtab.c + @cp $< $@ + strcompat.c: ../main/strcompat.c @cp $< $@ @@ -100,7 +103,7 @@ ast_expr2f.o: ASTCFLAGS+=-DSTANDALONE_AEL -I../main pval.o : ASTCFLAGS+=-DSTANDALONE -check_expr: check_expr.o ast_expr2.o ast_expr2f.o strcompat.o clicompat.o threadstorage.o +check_expr: check_expr.o ast_expr2.o ast_expr2f.o strcompat.o threadstorage.o aelbison.c: ../res/ael/ael.tab.c @cp $< $@ @@ -135,6 +138,11 @@ hashtest2.o: ASTCFLAGS+=-O0 hashtest2: hashtest2.o md5.o utils.o astobj2.o sha1.o strcompat.o threadstorage.o clicompat.o +hashtest: hashtest.o md5.o hashtab.o utils.o sha1.o strcompat.o threadstorage.o + +hashtest.o : hashtest.c + $(CC) -g -O0 -c hashtest.c -I/usr/include -I../include + extconf.o : extconf.c conf2ael: conf2ael.o ast_expr2f.o ast_expr2.o aelbison.o aelparse.o pbx_ael.o pval.o extconf.o strcompat.o diff --git a/utils/hashtest.c b/utils/hashtest.c new file mode 100644 index 000000000..aa359ca53 --- /dev/null +++ b/utils/hashtest.c @@ -0,0 +1,387 @@ +/* + * Asterisk -- An open source telephony toolkit. + * + * Copyright (C) 2007, Steve Murphy + * + * Steve Murphy <murf@digium.com> + * + * See http://www.asterisk.org for more information about + * the Asterisk project. Please do not directly contact + * any of the maintainers of this project for assistance; + * the project provides a web site, mailing lists and IRC + * channels for your use. + * + * This program is free software, distributed under the terms of + * the GNU General Public License Version 2. See the LICENSE file + * at the top of the source tree. + */ +/*! \file + * + * \brief A program to thoroughly thrash a hash table, testing + * out locking safety, and making sure all functionality + * is functioning. Run with 5 or more threads to get that + * fully intense firestorm of activity. If your + * hash tables don't crash, lock up, or go weird, it must + * be good code! Even features some global counters + * that will get slightly behind because they aren't lock-protected. + * + * \author Steve Murphy <murf@digium.com> + */ + +#include "asterisk.h" + +#include <sys/types.h> +#include <stdio.h> +#include <stdlib.h> +#include <stdarg.h> +#include <alloca.h> +#include <string.h> +#include <pthread.h> +#include <sys/stat.h> +#include <signal.h> +#include <errno.h> +#include <unistd.h> +#include "asterisk/lock.h" +#include "asterisk/hashtab.h" +#include "asterisk/channel.h" +#include "asterisk/utils.h" +#include "asterisk/module.h" +int testno = 1; + +/* stuff we need to make this work with the hashtab stuff */ + +void ast_cli(int *fd, char *str, ...) +{ +} + +int64_t ast_mark(int prof_id, int x) +{ +} + +struct ht_element +{ + char *key; + char *val; +}; + +static int hashtab_compare_strings_nocase(const void *a, const void *b) +{ + const struct ht_element *ae = a, *be = b; + return ast_hashtab_compare_strings_nocase(ae->key, be->key); +} + +static int hashtab_compare_strings(const void *a, const void *b) +{ + const struct ht_element *ae = a, *be = b; + return ast_hashtab_compare_strings(ae->key, be->key); +} + +static unsigned int hashtab_hash_string(const void *obj) +{ + const struct ht_element *o = obj; + return ast_hashtab_hash_string((const void *)o->key); +} + +static unsigned int hashtab_hash_string_nocase(const void *obj) +{ + const struct ht_element *o = obj; + return ast_hashtab_hash_string_nocase((const void*)o->key); +} + +/* random numbers */ + +my_rand(int incl_low, int incl_high, unsigned int *seedp) +{ + if (incl_high == 0) + return 0; + + return incl_low + (rand_r(seedp) % incl_high); +} + + + + +/* the testing routines */ + +static int glob_highwater = 0; +struct ast_hashtab *glob_hashtab = 0; +unsigned int glob_seed = 0; +int els_removed = 0; +int els_added = 0; +int els_lookedup = 0; +int els_found = 0; +int els_traversals = 0; + +/* all the operations to perform on the hashtab */ + +static void add_element(void) +{ + char keybuf[100]; + struct ht_element *x = malloc(sizeof(struct ht_element)); + sprintf(keybuf,"key%08d", glob_highwater++); + x->key = strdup(keybuf); + x->val = strdup("interesting data"); + ast_hashtab_insert_immediate(glob_hashtab, x); + els_added++; +} + +static void traverse_elements(void) +{ + struct ht_element *el; + int c=0; + struct ast_hashtab_iter *it = ast_hashtab_start_write_traversal(glob_hashtab); +#ifdef DEBUG + printf("Traverse hashtab\n"); +#endif + while ((el = ast_hashtab_next(it))) { + c++; + } + ast_hashtab_end_traversal(it); + els_traversals++; /* unprotected, sometimes off, but, not really important, either */ +} + +static void * del_element(unsigned int *seedp) +{ + char keybuf[100]; + struct ht_element *el, lookup; + int x; + + /* pick a random element from 0 to highwater-1 */ + x = my_rand(0,glob_highwater-1,seedp); + sprintf(keybuf, "key%08d", x); +#if DEBUG + printf("Removing %s", keybuf); +#endif + lookup.key = keybuf; + el = ast_hashtab_remove_object_via_lookup(glob_hashtab, &lookup); + + if (el) { +#if DEBUG + printf("...YES (el=%x)\n", (unsigned long)el); +#endif + free(el->key); + free(el->val); + free(el); + els_removed++; + } else { +#if DEBUG + printf("...NO.\n"); +#endif + return 0; + } + + + return el; +} + +static int lookup_element(unsigned int *seedp) +{ + char keybuf[100]; + struct ht_element *el, lookup; + int x; + + /* pick a random element from 0 to highwater-1 */ + x = my_rand(0,glob_highwater-1,seedp); + sprintf(keybuf, "key%08d", x); + lookup.key = keybuf; + el = ast_hashtab_lookup(glob_hashtab, &lookup); + els_lookedup++; + if (el) + els_found++; + if (el) + return 1; + return 0; +} + + +static void *hashtest(void *data) +{ + int my_els_removed = 0; + int my_els_added = 0; + int my_els_lookedup = 0; + int my_els_found = 0; + int my_els_traversals = 0; + int my_testno = testno++; + + /* data will be a random number == use as a seed for random numbers */ + unsigned long seed = (unsigned long)data; + printf("hashtest thread created... test beginning\n"); + + /* main test routine-- a global hashtab exists, pound it like crazy */ + int its; + for(its=0;its<100000;its++) + { + void *seed2 = &seed; + int op = my_rand(0,100, seed2); + if (op<60) { + my_els_lookedup++; +#ifdef DEBUG + printf("%d[%d]: LOOKUP\n", my_testno, its); +#endif + if ((my_els_lookedup%1000)==0) { + printf("."); + fflush(stdout); + } + if (lookup_element(seed2)) + my_els_found++; + } else if (op < 61) { /* make this 61 and it'll take 15 minutes to run */ +#ifdef DEBUG + printf("%d[%d]: TRAVERSE\n", my_testno, its); +#endif + traverse_elements(); + my_els_traversals++; + + } else if (op < 80) { +#ifdef DEBUG + printf("%d[%d]: REMOVE\n", my_testno, its); +#endif + if (del_element(seed2)) + my_els_removed++; + } else { + my_els_added++; +#ifdef DEBUG + printf("%d[%d]: ADD\n", my_testno, its); +#endif + add_element(); + } + } + printf("\nhashtest thread %d exiting.... lookups=%d/%d, added=%d, removed=%d, traversals=%d;\n", + my_testno, my_els_found, my_els_lookedup, my_els_added, my_els_removed, my_els_traversals); + printf("\ntotals..................... lookups=%d/%d, added=%d, removed=%d, traversals=%d;\n", + els_found, els_lookedup, els_added, els_removed,els_traversals); + pthread_exit(0); +} + +void run_hashtest(int numthr) +{ + pthread_t thr[numthr]; + void *thrres[numthr]; + int i, biggest, resize_cnt, numobjs, numbuckets; + + /* init a single global hashtab, then... */ + glob_hashtab = ast_hashtab_create(180000, hashtab_compare_strings_nocase, ast_hashtab_resize_java, ast_hashtab_newsize_java, hashtab_hash_string_nocase, 1); + printf("starting with %d elements in the hashtable...\n", ast_hashtab_capacity(glob_hashtab)); + /* set a random seed */ + glob_seed = (unsigned int)time(0); + srand(glob_seed); + + /* create threads, each running hashtest */ + for(i=0;i<numthr;i++) + { + unsigned long z = rand(); + + printf("starting hashtest thread %d....\n",i+1); + if (ast_pthread_create(&thr[i], NULL, hashtest, (void*)z)) { + printf("Sorry, couldn't create thread #%d\n", i+1); + } + printf("hashtest thread spawned.... \n"); + } + /* collect threads, each running hashtest */ + for(i=0;i<numthr;i++) + { + printf("waiting for thread %d....\n", i+1); + if (pthread_join(thr[i], &thrres[i])) { + printf("Sorry, couldn't join thread #%d\n", i+1); + } + printf("hashtest thread %d done.... \n",i+1); + } + /* user has to kill/intr the process to stop the test? */ + ast_hashtab_get_stats(glob_hashtab, &biggest, &resize_cnt, &numobjs, &numbuckets); + printf("Some stats: longest bucket chain: %d; number of resizes: %d; number of objects: %d; capacity: %d\n", + biggest, resize_cnt, numobjs, numbuckets); +} + + +int main(int argc,char **argv) +{ + if (argc < 2 || argc > 2 || atoi(argv[1]) < 1) + { + printf("Usage: hashtest <number of threads>\n"); + exit(1); + } + + /* one arg == number of threads to create */ + run_hashtest(atoi(argv[1])); + + return 0; +} + + +struct ast_app *pbx_findapp(const char *app) +{ + return (struct ast_app*)1; /* so as not to trigger an error */ +} + +int ast_add_profile(const char *x, uint64_t scale) +{ +} + +int ast_loader_register(int (*updater)(void)) +{ + return 1; +} + +int ast_loader_unregister(int (*updater)(void)) +{ + return 1; +} +void ast_module_register(const struct ast_module_info *x) +{ +} + +void ast_module_unregister(const struct ast_module_info *x) +{ +} + + +void ast_cli_register_multiple(void) +{ +} + +void ast_register_file_version(const char *file, const char *version) +{ +} + +void ast_unregister_file_version(const char *file) +{ + +} + +void ast_cli_unregister_multiple(void) +{ +} + +void ast_context_destroy(void) +{ +} + +void ast_log(int level, const char *file, int line, const char *function, const char *fmt, ...) +{ + va_list vars; + va_start(vars,fmt); + printf("LOG: lev:%d file:%s line:%d func: %s ", + level, file, line, function); + vprintf(fmt, vars); + fflush(stdout); + va_end(vars); +} + +void ast_verbose(const char *fmt, ...) +{ + va_list vars; + va_start(vars,fmt); + + printf("VERBOSE: "); + vprintf(fmt, vars); + fflush(stdout); + va_end(vars); +} + +void ast_register_thread(char *name) +{ + +} + +void ast_unregister_thread(void *id) +{ +} |