Merged revisions 80362 via svnmerge from
authorRussell Bryant <russell@russellbryant.com>
Wed, 22 Aug 2007 20:44:23 +0000 (20:44 +0000)
committerRussell Bryant <russell@russellbryant.com>
Wed, 22 Aug 2007 20:44:23 +0000 (20:44 +0000)
https://origsvn.digium.com/svn/asterisk/branches/1.4

........
r80362 | russell | 2007-08-22 15:21:36 -0500 (Wed, 22 Aug 2007) | 34 lines

Merge changes from team/russell/iax_refcount.

This set of changes fixes problems with the handling of iax2_user and iax2_peer
objects.  It was very possible for a thread to still hold a reference to one of
these objects while a reload operation tries to delete them.  The fix here is to
ensure that all references to these objects are tracked so that they can't go away
while still in use.

To accomplish this, I used the astobj2 reference counted object model.  This
code has been in one of Luigi Rizzo's branches for a long time and was primarily
developed by one of his students, Marta Carbone.  I wanted to go ahead and bring
this in to 1.4 because there are other problems similar to the ones fixed by these
changes, so we might as well go ahead and use the new astobj if we're going to go
through all of the work necessary to fix the problems.

As a nice side benefit of these changes, peer and user handling got more efficient.
Using astobj2 lets us not hold the container lock for peers or users nearly as long
while iterating.  Also, by changing a define at the top of chan_iax2.c, the objects
will be distributed in a hash table, drastically increasing lookup speed in these
containers, which will have a very big impact on systems that have a large number of
users or peers.

The use of the hash table will be made the default in trunk.  It is not the default
in 1.4 because it changes the behavior slightly.  Previously, since peers and users
were stored in memory in the same order they were specified in the configuration file,
you could influence peer and user matching order based on the order they are specified
in the configuration.  The hash table does not guarantee any order in the container,
so this behavior will be going away.  It just means that you have to be a little
more careful ensuring that peers and users are matched explicitly and not forcing
chan_iax2 to have to guess which user is the right one based on secret, host, and
access list settings, instead of simply using the username.

If you have any questions, feel free to ask on the asterisk-dev list.

........

git-svn-id: https://origsvn.digium.com/svn/asterisk/trunk@80387 65c4cc65-6c06-0410-ace0-fbb531ad65f3

channels/chan_iax2.c
include/asterisk/astobj2.h [new file with mode: 0644]
include/asterisk/strings.h
main/Makefile
main/astobj2.c [new file with mode: 0644]

index e9e2e74..07917f8 100644 (file)
@@ -93,6 +93,7 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
 #include "asterisk/stringfields.h"
 #include "asterisk/linkedlists.h"
 #include "asterisk/event.h"
+#include "asterisk/astobj2.h"
 
 #include "iax2.h"
 #include "iax2-parser.h"
@@ -323,7 +324,6 @@ struct iax2_user {
        struct ast_ha *ha;
        struct iax2_context *contexts;
        struct ast_variable *vars;
-       AST_LIST_ENTRY(iax2_user) entry;
 };
 
 struct iax2_peer {
@@ -378,7 +378,6 @@ struct iax2_peer {
        struct ast_event_sub *mwi_event_sub;
 
        struct ast_ha *ha;
-       AST_LIST_ENTRY(iax2_peer) entry;
 };
 
 #define IAX2_TRUNK_PREFACE (sizeof(struct iax_frame) + sizeof(struct ast_iax2_meta_hdr) + sizeof(struct ast_iax2_meta_trunk_hdr))
@@ -659,9 +658,24 @@ struct chan_iax2_pvt {
  */
 static AST_LIST_HEAD_STATIC(frame_queue, iax_frame);
 
-static AST_LIST_HEAD_STATIC(users, iax2_user);
+/*!
+ * This module will get much higher performance when doing a lot of
+ * user and peer lookups if the number of buckets is increased from 1.
+ * However, to maintain old behavior for Asterisk 1.4, these are set to
+ * 1 by default.  When using multiple buckets, search order through these
+ * containers is considered random, so you will not be able to depend on
+ * the order the entires are specified in iax.conf for matching order. */
+#ifdef LOW_MEMORY
+#define MAX_PEER_BUCKETS 1
+/* #define MAX_PEER_BUCKETS 17 */
+#else
+#define MAX_PEER_BUCKETS 1
+/* #define MAX_PEER_BUCKETS 563 */
+#endif
+static ao2_container *peers;
 
-static AST_LIST_HEAD_STATIC(peers, iax2_peer);
+#define MAX_USER_BUCKETS MAX_PEER_BUCKETS
+static ao2_container *users;
 
 static AST_LIST_HEAD_STATIC(firmwares, iax_firmware);
 
@@ -701,7 +715,6 @@ static AST_LIST_HEAD_STATIC(dpcache, iax2_dpcache);
 static void reg_source_db(struct iax2_peer *p);
 static struct iax2_peer *realtime_peer(const char *peername, struct sockaddr_in *sin);
 
-static void destroy_peer(struct iax2_peer *peer);
 static int ast_cli_netstats(struct mansession *s, int fd, int limit_fmt);
 
 enum iax2_thread_iostate {
@@ -892,7 +905,6 @@ static struct ast_frame *iax2_read(struct ast_channel *c);
 static struct iax2_peer *build_peer(const char *name, struct ast_variable *v, struct ast_variable *alt, int temponly);
 static struct iax2_user *build_user(const char *name, struct ast_variable *v, struct ast_variable *alt, int temponly);
 static void realtime_update_peer(const char *peername, struct sockaddr_in *sin, time_t regtime);
-static void destroy_user(struct iax2_user *user);
 static void prune_peers(void);
 static void *iax2_dup_variable_datastore(void *);
 static void iax2_free_variable_datastore(void *);
@@ -1185,51 +1197,112 @@ static int uncompress_subclass(unsigned char csub)
 }
 
 /*!
+ * \note The only member of the peer passed here guaranteed to be set is the name field
+ */
+static int peer_hash_cb(const void *obj, const int flags)
+{
+       const struct iax2_peer *peer = obj;
+
+       return ast_str_hash(peer->name);
+}
+
+/*!
+ * \note The only member of the peer passed here guaranteed to be set is the name field
+ */
+static int peer_cmp_cb(void *obj, void *arg, int flags)
+{
+       struct iax2_peer *peer = obj, *peer2 = arg;
+
+       return !strcasecmp(peer->name, peer2->name) ? CMP_MATCH : 0;
+}
+
+/*!
+ * \note The only member of the user passed here guaranteed to be set is the name field
+ */
+static int user_hash_cb(const void *obj, const int flags)
+{
+       const struct iax2_user *user = obj;
+
+       return ast_str_hash(user->name);
+}
+
+/*!
+ * \note The only member of the user passed here guaranteed to be set is the name field
+ */
+static int user_cmp_cb(void *obj, void *arg, int flags)
+{
+       struct iax2_user *user = obj, *user2 = arg;
+
+       return !strcasecmp(user->name, user2->name) ? CMP_MATCH : 0;
+}
+
+/*!
  * \note This funtion calls realtime_peer -> reg_source_db -> iax2_poke_peer -> find_callno,
  *       so do not call it with a pvt lock held.
  */
 static struct iax2_peer *find_peer(const char *name, int realtime) 
 {
        struct iax2_peer *peer = NULL;
+       struct iax2_peer tmp_peer = {
+               .name = name,
+       };
 
-       /* Grab peer from linked list */
-       AST_LIST_LOCK(&peers);
-       AST_LIST_TRAVERSE(&peers, peer, entry) {
-               if (!strcasecmp(peer->name, name)) {
-                       break;
-               }
-       }
-       AST_LIST_UNLOCK(&peers);
+       peer = ao2_find(peers, &tmp_peer, OBJ_POINTER);
 
        /* Now go for realtime if applicable */
        if(!peer && realtime)
                peer = realtime_peer(name, NULL);
+
+       return peer;
+}
+
+static struct iax2_peer *peer_ref(struct iax2_peer *peer)
+{
+       ao2_ref(peer, +1);
        return peer;
 }
 
-static int iax2_getpeername(struct sockaddr_in sin, char *host, int len, int lockpeer)
+static inline struct iax2_peer *peer_unref(struct iax2_peer *peer)
+{
+       ao2_ref(peer, -1);
+       return NULL;
+}
+
+static inline struct iax2_user *user_ref(struct iax2_user *user)
+{
+       ao2_ref(user, +1);
+       return user;
+}
+
+static inline struct iax2_user *user_unref(struct iax2_user *user)
+{
+       ao2_ref(user, -1);
+       return NULL;
+}
+
+static int iax2_getpeername(struct sockaddr_in sin, char *host, int len)
 {
        struct iax2_peer *peer = NULL;
        int res = 0;
+       ao2_iterator i;
 
-       if (lockpeer)
-               AST_LIST_LOCK(&peers);
-       AST_LIST_TRAVERSE(&peers, peer, entry) {
+       i = ao2_iterator_init(peers, 0);
+       while ((peer = ao2_iterator_next(&i))) {
                if ((peer->addr.sin_addr.s_addr == sin.sin_addr.s_addr) &&
                    (peer->addr.sin_port == sin.sin_port)) {
                        ast_copy_string(host, peer->name, len);
+                       peer_unref(peer);
                        res = 1;
                        break;
                }
+               peer_unref(peer);
        }
-       if (lockpeer)
-               AST_LIST_UNLOCK(&peers);
+
        if (!peer) {
                peer = realtime_peer(NULL, &sin);
                if (peer) {
                        ast_copy_string(host, peer->name, len);
-                       if (ast_test_flag(peer, IAX_TEMPONLY))
-                               destroy_peer(peer);
+                       peer_unref(peer);
                        res = 1;
                }
        }
@@ -1237,7 +1310,7 @@ static int iax2_getpeername(struct sockaddr_in sin, char *host, int len, int loc
        return res;
 }
 
-static struct chan_iax2_pvt *new_iax(struct sockaddr_in *sin, int lockpeer, const char *host)
+static struct chan_iax2_pvt *new_iax(struct sockaddr_in *sin, const char *host)
 {
        struct chan_iax2_pvt *tmp;
        jb_conf jbconf;
@@ -1402,7 +1475,7 @@ static int make_trunk(unsigned short callno, int locked)
  *
  * \note Calling this function while holding another pvt lock can cause a deadlock.
  */
-static int find_callno(unsigned short callno, unsigned short dcallno, struct sockaddr_in *sin, int new, int lockpeer, int sockfd)
+static int find_callno(unsigned short callno, unsigned short dcallno, struct sockaddr_in *sin, int new, int sockfd)
 {
        int res = 0;
        int x;
@@ -1438,7 +1511,7 @@ static int find_callno(unsigned short callno, unsigned short dcallno, struct soc
                 * this is just checking for a peer that has that IP/port and
                 * assuming that we have a user of the same name.  This isn't always
                 * correct, but it will be changed if needed after authentication. */
-               if (!iax2_getpeername(*sin, host, sizeof(host), lockpeer))
+               if (!iax2_getpeername(*sin, host, sizeof(host)))
                        snprintf(host, sizeof(host), "%s:%d", ast_inet_ntoa(sin->sin_addr), ntohs(sin->sin_port));
                now = ast_tvnow();
                for (x=1;x<TRUNK_CALL_START;x++) {
@@ -1452,7 +1525,7 @@ static int find_callno(unsigned short callno, unsigned short dcallno, struct soc
                        ast_log(LOG_WARNING, "No more space\n");
                        return 0;
                }
-               iaxs[x] = new_iax(sin, lockpeer, host);
+               iaxs[x] = new_iax(sin, host);
                update_max_nontrunk();
                if (iaxs[x]) {
                        if (iaxdebug)
@@ -1940,18 +2013,19 @@ static int send_packet(struct iax_frame *f)
 
 static void iax2_destroy_helper(struct chan_iax2_pvt *pvt)
 {
-       struct iax2_user *user = NULL;
-
        /* Decrement AUTHREQ count if needed */
        if (ast_test_flag(pvt, IAX_MAXAUTHREQ)) {
-               AST_LIST_LOCK(&users);
-               AST_LIST_TRAVERSE(&users, user, entry) {
-                       if (!strcmp(user->name, pvt->username)) {
-                               user->curauthreq--;
-                               break;
-                       }
+               struct iax2_user *user;
+               struct iax2_user tmp_user = {
+                       .name = pvt->username,
+               };
+
+               user = ao2_find(users, &tmp_user, OBJ_POINTER);
+               if (user) {
+                       ast_atomic_fetchadd_int(&user->curauthreq, -1);
+                       user_unref(user);       
                }
-               AST_LIST_UNLOCK(&users);
+
                ast_clear_flag(pvt, IAX_MAXAUTHREQ);
        }
        /* No more pings or lagrq's */
@@ -2191,6 +2265,7 @@ static int iax2_prune_realtime(int fd, int argc, char *argv[])
                } else {
                        ast_cli(fd, "SORRY peer %s is not eligible for this operation.\n", argv[3]);
                }
+               peer_unref(peer);
        } else {
                ast_cli(fd, "SORRY peer %s was not found in the cache.\n", argv[3]);
        }
@@ -2317,8 +2392,7 @@ static int iax2_show_peer(int fd, int argc, char *argv[])
                ast_cli(fd, "%s\n",status);
                ast_cli(fd, "  Qualify      : every %dms when OK, every %dms when UNREACHABLE (sample smoothing %s)\n", peer->pokefreqok, peer->pokefreqnotok, peer->smoothing ? "On" : "Off");
                ast_cli(fd,"\n");
-               if (ast_test_flag(peer, IAX_TEMPONLY))
-                       destroy_peer(peer);
+               peer_unref(peer);
        } else {
                ast_cli(fd,"Peer %s not found.\n", argv[3]);
                ast_cli(fd,"\n");
@@ -2330,20 +2404,23 @@ static int iax2_show_peer(int fd, int argc, char *argv[])
 static char *complete_iax2_show_peer(const char *line, const char *word, int pos, int state)
 {
        int which = 0;
-       struct iax2_peer *p = NULL;
+       struct iax2_peer *peer;
        char *res = NULL;
        int wordlen = strlen(word);
+       ao2_iterator i;
 
        /* 0 - iax2; 1 - show; 2 - peer; 3 - <peername> */
-       if (pos == 3) {
-               AST_LIST_LOCK(&peers);
-               AST_LIST_TRAVERSE(&peers, p, entry) {
-                       if (!strncasecmp(p->name, word, wordlen) && ++which > state) {
-                               res = ast_strdup(p->name);
-                               break;
-                       }
+       if (pos != 3)
+               return NULL;
+
+       i = ao2_iterator_init(peers, 0);
+       while ((peer = ao2_iterator_next(&i))) {
+               if (!strncasecmp(peer->name, word, wordlen) && ++which > state) {
+                       res = ast_strdup(peer->name);
+                       peer_unref(peer);
+                       break;
                }
-               AST_LIST_UNLOCK(&peers);
+               peer_unref(peer);
        }
 
        return res;
@@ -2788,8 +2865,7 @@ static struct iax2_peer *realtime_peer(const char *peername, struct sockaddr_in
                        if (strcasecmp(tmp->value, "friend") &&
                            strcasecmp(tmp->value, "peer")) {
                                /* Whoops, we weren't supposed to exist! */
-                               destroy_peer(peer);
-                               peer = NULL;
+                               peer = peer_unref(peer);
                                break;
                        } 
                } else if (!strcasecmp(tmp->name, "regseconds")) {
@@ -2815,9 +2891,7 @@ static struct iax2_peer *realtime_peer(const char *peername, struct sockaddr_in
                        peer->expire = ast_sched_replace(peer->expire, sched, 
                                (global_rtautoclear) * 1000, expire_registry, (void *) peer->name);
                }
-               AST_LIST_LOCK(&peers);
-               AST_LIST_INSERT_HEAD(&peers, peer, entry);
-               AST_LIST_UNLOCK(&peers);
+               ao2_link(peers, peer_ref(peer));
                if (ast_test_flag(peer, IAX_DYNAMIC))
                        reg_source_db(peer);
        } else {
@@ -2872,9 +2946,7 @@ static struct iax2_user *realtime_user(const char *username)
 
        if (ast_test_flag((&globalflags), IAX_RTCACHEFRIENDS)) {
                ast_set_flag(user, IAX_RTCACHEFRIENDS);
-               AST_LIST_LOCK(&users);
-               AST_LIST_INSERT_HEAD(&users, user, entry);
-               AST_LIST_UNLOCK(&users);
+               ao2_link(users, user_ref(user));
        } else {
                ast_set_flag(user, IAX_TEMPONLY);       
        }
@@ -2916,6 +2988,7 @@ struct create_addr_info {
 static int create_addr(const char *peername, struct sockaddr_in *sin, struct create_addr_info *cai)
 {
        struct iax2_peer *peer;
+       int res = -1;
 
        ast_clear_flag(cai, IAX_SENDANI | IAX_TRUNK);
        cai->sockfd = defaultsockfd;
@@ -2937,18 +3010,12 @@ static int create_addr(const char *peername, struct sockaddr_in *sin, struct cre
        cai->found = 1;
        
        /* if the peer has no address (current or default), return failure */
-       if (!(peer->addr.sin_addr.s_addr || peer->defaddr.sin_addr.s_addr)) {
-               if (ast_test_flag(peer, IAX_TEMPONLY))
-                       destroy_peer(peer);
-               return -1;
-       }
+       if (!(peer->addr.sin_addr.s_addr || peer->defaddr.sin_addr.s_addr))
+               goto return_unref;
 
        /* if the peer is being monitored and is currently unreachable, return failure */
-       if (peer->maxms && ((peer->lastms > peer->maxms) || (peer->lastms < 0))) {
-               if (ast_test_flag(peer, IAX_TEMPONLY))
-                       destroy_peer(peer);
-               return -1;
-       }
+       if (peer->maxms && ((peer->lastms > peer->maxms) || (peer->lastms < 0)))
+               goto return_unref;
 
        ast_copy_flags(cai, peer, IAX_SENDANI | IAX_TRUNK | IAX_NOTRANSFER | IAX_TRANSFERMEDIA | IAX_USEJITTERBUF | IAX_FORCEJITTERBUF);
        cai->maxtime = peer->maxms;
@@ -2976,9 +3043,7 @@ static int create_addr(const char *peername, struct sockaddr_in *sin, struct cre
                        *key++ = '\0';
                if (!key || ast_db_get(family, key, cai->secret, sizeof(cai->secret))) {
                        ast_log(LOG_WARNING, "Unable to retrieve database password for family/key '%s'!\n", peer->dbsecret);
-                       if (ast_test_flag(peer, IAX_TEMPONLY))
-                               destroy_peer(peer);
-                       return -1;
+                       goto return_unref;
                }
        }
 
@@ -2990,10 +3055,12 @@ static int create_addr(const char *peername, struct sockaddr_in *sin, struct cre
                sin->sin_port = peer->defaddr.sin_port;
        }
 
-       if (ast_test_flag(peer, IAX_TEMPONLY))
-               destroy_peer(peer);
+       res = 0;
 
-       return 0;
+return_unref:
+       peer_unref(peer);
+
+       return res;
 }
 
 static void __auto_congest(void *nothing)
@@ -3604,18 +3671,20 @@ static int iax2_transfer(struct ast_channel *c, const char *dest)
        
 static int iax2_getpeertrunk(struct sockaddr_in sin)
 {
-       struct iax2_peer *peer = NULL;
+       struct iax2_peer *peer;
        int res = 0;
+       ao2_iterator i;
 
-       AST_LIST_LOCK(&peers);
-       AST_LIST_TRAVERSE(&peers, peer, entry) {
+       i = ao2_iterator_init(peers, 0);
+       while ((peer = ao2_iterator_next(&i))) {
                if ((peer->addr.sin_addr.s_addr == sin.sin_addr.s_addr) &&
                    (peer->addr.sin_port == sin.sin_port)) {
                        res = ast_test_flag(peer, IAX_TRUNK);
+                       peer_unref(peer);
                        break;
                }
+               peer_unref(peer);
        }
-       AST_LIST_UNLOCK(&peers);
 
        return res;
 }
@@ -4374,6 +4443,7 @@ static int iax2_show_users(int fd, int argc, char *argv[])
        struct iax2_user *user = NULL;
        char auth[90];
        char *pstr = "";
+       ao2_iterator i;
 
        switch (argc) {
        case 5:
@@ -4390,8 +4460,9 @@ static int iax2_show_users(int fd, int argc, char *argv[])
        }
 
        ast_cli(fd, FORMAT, "Username", "Secret", "Authen", "Def.Context", "A/C","Codec Pref");
-       AST_LIST_LOCK(&users);
-       AST_LIST_TRAVERSE(&users, user, entry) {
+       i = ao2_iterator_init(users, 0);
+       for (user = ao2_iterator_next(&i); user; 
+               user_unref(user), user = ao2_iterator_next(&i)) {
                if (havepattern && regexec(&regexbuf, user->name, 0, NULL, 0))
                        continue;
                
@@ -4412,9 +4483,7 @@ static int iax2_show_users(int fd, int argc, char *argv[])
                ast_cli(fd, FORMAT2, user->name, auth, user->authmethods, 
                        user->contexts ? user->contexts->context : context,
                        user->ha ? "Yes" : "No", pstr);
-               
        }
-       AST_LIST_UNLOCK(&users);
 
        if (havepattern)
                regfree(&regexbuf);
@@ -4432,6 +4501,7 @@ static int __iax2_show_peers(int manager, int fd, struct mansession *s, int argc
        int online_peers = 0;
        int offline_peers = 0;
        int unmonitored_peers = 0;
+       ao2_iterator i;
 
 #define FORMAT2 "%-15.15s  %-15.15s %s  %-15.15s  %-8s  %s %-10s%s"
 #define FORMAT "%-15.15s  %-15.15s %s  %-15.15s  %-5d%s  %s %-10s%s"
@@ -4480,8 +4550,9 @@ static int __iax2_show_peers(int manager, int fd, struct mansession *s, int argc
        else
                ast_cli(fd, FORMAT2, "Name/Username", "Host", "   ", "Mask", "Port", "   ", "Status", term);
 
-       AST_LIST_LOCK(&peers);
-       AST_LIST_TRAVERSE(&peers, peer, entry) {
+       i = ao2_iterator_init(peers, 0);
+       for (peer = ao2_iterator_next(&i); peer; 
+               peer_unref(peer), peer = ao2_iterator_next(&i)) {
                char nm[20];
                char status[20];
                char srch[2000];
@@ -4530,7 +4601,6 @@ static int __iax2_show_peers(int manager, int fd, struct mansession *s, int argc
                                peer->encmethods ? "(E)" : "   ", status, term);
                total_peers++;
        }
-       AST_LIST_UNLOCK(&peers);
 
        if (s)
                astman_append(s,"%d iax2 peers [%d online, %d offline, %d unmonitored]%s", total_peers, online_peers, offline_peers, unmonitored_peers, term);
@@ -4633,15 +4703,16 @@ static char *complete_iax2_unregister(const char *line, const char *word, int po
 
        /* 0 - iax2; 1 - unregister; 2 - <peername> */
        if (pos == 2) {
-               AST_LIST_LOCK(&peers);
-               AST_LIST_TRAVERSE(&peers, p, entry) {
+               ao2_iterator i = ao2_iterator_init(peers, 0);
+               while ((p = ao2_iterator_next(&i))) {
                        if (!strncasecmp(p->name, word, wordlen) && 
                                ++which > state && p->expire > 0) {
                                res = ast_strdup(p->name);
+                               peer_unref(p);
                                break;
                        }
+                       peer_unref(p);
                }
-               AST_LIST_UNLOCK(&peers);
        }
 
        return res;
@@ -5036,6 +5107,7 @@ static int check_access(int callno, struct sockaddr_in *sin, struct iax_ies *ies
        int bestscore = 0;
        int gotcapability = 0;
        struct ast_variable *v = NULL, *tmpvar = NULL;
+       ao2_iterator i;
 
        if (!iaxs[callno])
                return res;
@@ -5090,8 +5162,8 @@ static int check_access(int callno, struct sockaddr_in *sin, struct iax_ies *ies
                return res;
        }
        /* Search the userlist for a compatible entry, and fill in the rest */
-       AST_LIST_LOCK(&users);
-       AST_LIST_TRAVERSE(&users, user, entry) {
+       i = ao2_iterator_init(users, 0);
+       while ((user = ao2_iterator_next(&i))) {
                if ((ast_strlen_zero(iaxs[callno]->username) ||                         /* No username specified */
                        !strcmp(iaxs[callno]->username, user->name))    /* Or this username specified */
                        && ast_apply_ha(user->ha, sin)  /* Access is permitted from this IP */
@@ -5099,6 +5171,8 @@ static int check_access(int callno, struct sockaddr_in *sin, struct iax_ies *ies
                             apply_context(user->contexts, iaxs[callno]->context))) {                   /* Context is permitted */
                        if (!ast_strlen_zero(iaxs[callno]->username)) {
                                /* Exact match, stop right now. */
+                               if (best)
+                                       user_unref(best);
                                best = user;
                                break;
                        } else if (ast_strlen_zero(user->secret) && ast_strlen_zero(user->inkeys)) {
@@ -5107,13 +5181,19 @@ static int check_access(int callno, struct sockaddr_in *sin, struct iax_ies *ies
                                        /* There was host authentication and we passed, bonus! */
                                        if (bestscore < 4) {
                                                bestscore = 4;
+                                               if (best)
+                                                       user_unref(best);
                                                best = user;
+                                               continue;
                                        }
                                } else {
                                        /* No host access, but no secret, either, not bad */
                                        if (bestscore < 3) {
                                                bestscore = 3;
+                                               if (best)
+                                                       user_unref(best);
                                                best = user;
+                                               continue;
                                        }
                                }
                        } else {
@@ -5121,26 +5201,31 @@ static int check_access(int callno, struct sockaddr_in *sin, struct iax_ies *ies
                                        /* Authentication, but host access too, eh, it's something.. */
                                        if (bestscore < 2) {
                                                bestscore = 2;
+                                               if (best)
+                                                       user_unref(best);
                                                best = user;
+                                               continue;
                                        }
                                } else {
                                        /* Authentication and no host access...  This is our baseline */
                                        if (bestscore < 1) {
                                                bestscore = 1;
+                                               if (best)
+                                                       user_unref(best);
                                                best = user;
+                                               continue;
                                        }
                                }
                        }
                }
+               user_unref(user);
        }
-       AST_LIST_UNLOCK(&users);
        user = best;
        if (!user && !ast_strlen_zero(iaxs[callno]->username)) {
                user = realtime_user(iaxs[callno]->username);
                if (user && !ast_strlen_zero(iaxs[callno]->context) &&                  /* No context specified */
                    !apply_context(user->contexts, iaxs[callno]->context)) {            /* Context is permitted */
-                       destroy_user(user);
-                       user = NULL;
+                       user = user_unref(user);
                }
        }
        if (user) {
@@ -5219,9 +5304,8 @@ static int check_access(int callno, struct sockaddr_in *sin, struct iax_ies *ies
                                ast_string_field_set(iaxs[callno], secret, buf);
                } else
                        ast_string_field_set(iaxs[callno], secret, user->secret);
-               if (ast_test_flag(user, IAX_TEMPONLY))
-                       destroy_user(user);
                res = 0;
+               user = user_unref(user);
        }
        ast_set2_flag(iaxs[callno], iax2_getpeertrunk(*sin), IAX_TRUNK);        
        return res;
@@ -5267,7 +5351,6 @@ static void merge_encryption(struct chan_iax2_pvt *p, unsigned int enc)
  */
 static int authenticate_request(int call_num)
 {
-       struct iax2_user *user = NULL;
        struct iax_ie_data ied;
        int res = -1, authreq_restrict = 0;
        char challenge[10];
@@ -5277,17 +5360,18 @@ static int authenticate_request(int call_num)
 
        /* If an AUTHREQ restriction is in place, make sure we can send an AUTHREQ back */
        if (ast_test_flag(p, IAX_MAXAUTHREQ)) {
-               AST_LIST_LOCK(&users);
-               AST_LIST_TRAVERSE(&users, user, entry) {
-                       if (!strcmp(user->name, p->username)) {
-                               if (user->curauthreq == user->maxauthreq)
-                                       authreq_restrict = 1;
-                               else
-                                       user->curauthreq++;
-                               break;
-                       }
+               struct iax2_user *user, tmp_user = {
+                       .name = p->username,    
+               };
+
+               user = ao2_find(users, &tmp_user, OBJ_POINTER);
+               if (user) {
+                       if (user->curauthreq == user->maxauthreq)
+                               authreq_restrict = 1;
+                       else
+                               user->curauthreq++;
+                       user = user_unref(user);
                }
-               AST_LIST_UNLOCK(&users);
        }
 
        /* If the AUTHREQ limit test failed, send back an error */
@@ -5326,21 +5410,19 @@ static int authenticate_verify(struct chan_iax2_pvt *p, struct iax_ies *ies)
        char rsasecret[256] = "";
        int res = -1; 
        int x;
-       struct iax2_user *user = NULL;
+       struct iax2_user *user, tmp_user = {
+               .name = p->username,    
+       };
 
-       AST_LIST_LOCK(&users);
-       AST_LIST_TRAVERSE(&users, user, entry) {
-               if (!strcmp(user->name, p->username))
-                       break;
-       }
+       user = ao2_find(users, &tmp_user, OBJ_POINTER);
        if (user) {
                if (ast_test_flag(p, IAX_MAXAUTHREQ)) {
-                       user->curauthreq--;
+                       ast_atomic_fetchadd_int(&user->curauthreq, -1);
                        ast_clear_flag(p, IAX_MAXAUTHREQ);
                }
                ast_string_field_set(p, host, user->name);
+               user = user_unref(user);
        }
-       AST_LIST_UNLOCK(&users);
 
        if (!ast_test_flag(&p->state, IAX_STATE_AUTHENTICATED))
                return res;
@@ -5402,11 +5484,12 @@ static int register_verify(int callno, struct sockaddr_in *sin, struct iax_ies *
        char md5secret[256] = "";
        char rsasecret[256] = "";
        char secret[256] = "";
-       struct iax2_peer *p;
+       struct iax2_peer *p = NULL;
        struct ast_key *key;
        char *keyn;
        int x;
        int expire = 0;
+       int res = -1;
 
        ast_clear_flag(&iaxs[callno]->state, IAX_STATE_AUTHENTICATED | IAX_STATE_UNCHANGED);
        /* iaxs[callno]->peer[0] = '\0'; not necc. any more-- stringfield is pre-inited to null string */
@@ -5433,23 +5516,19 @@ static int register_verify(int callno, struct sockaddr_in *sin, struct iax_ies *
        if (!p || !iaxs[callno]) {
                if (authdebug && !p)
                        ast_log(LOG_NOTICE, "No registration for peer '%s' (from %s)\n", peer, ast_inet_ntoa(sin->sin_addr));
-               return -1;
+               goto return_unref;
        }
 
        if (!ast_test_flag(p, IAX_DYNAMIC)) {
                if (authdebug)
                        ast_log(LOG_NOTICE, "Peer '%s' is not dynamic (from %s)\n", peer, ast_inet_ntoa(sin->sin_addr));
-               if (ast_test_flag(p, IAX_TEMPONLY))
-                       destroy_peer(p);
-               return -1;
+               goto return_unref;
        }
 
        if (!ast_apply_ha(p->ha, sin)) {
                if (authdebug)
                        ast_log(LOG_NOTICE, "Host %s denied access to register peer '%s'\n", ast_inet_ntoa(sin->sin_addr), p->name);
-               if (ast_test_flag(p, IAX_TEMPONLY))
-                       destroy_peer(p);
-               return -1;
+               goto return_unref;
        }
        if (!inaddrcmp(&p->addr, sin))
                ast_set_flag(&iaxs[callno]->state, IAX_STATE_UNCHANGED);
@@ -5475,16 +5554,12 @@ static int register_verify(int callno, struct sockaddr_in *sin, struct iax_ies *
                        if (!keyn) {
                                if (authdebug)
                                        ast_log(LOG_NOTICE, "Host %s failed RSA authentication with inkeys '%s'\n", peer, p->inkeys);
-                               if (ast_test_flag(p, IAX_TEMPONLY))
-                                       destroy_peer(p);
-                               return -1;
+                               goto return_unref;
                        }
                } else {
                        if (authdebug)
                                ast_log(LOG_NOTICE, "Host '%s' trying to do RSA authentication, but we have no inkeys\n", peer);
-                       if (ast_test_flag(p, IAX_TEMPONLY))
-                               destroy_peer(p);
-                       return -1;
+                       goto return_unref;
                }
        } else if (!ast_strlen_zero(md5secret) && (p->authmethods & IAX_AUTH_MD5) && !ast_strlen_zero(iaxs[callno]->challenge)) {
                struct MD5Context md5;
@@ -5508,26 +5583,20 @@ static int register_verify(int callno, struct sockaddr_in *sin, struct iax_ies *
                } else {
                        if (authdebug)
                                ast_log(LOG_NOTICE, "Host %s failed MD5 authentication for '%s' (%s != %s)\n", ast_inet_ntoa(sin->sin_addr), p->name, requeststr, md5secret);
-                       if (ast_test_flag(p, IAX_TEMPONLY))
-                               destroy_peer(p);
-                       return -1;
+                       goto return_unref;
                }
        } else if (!ast_strlen_zero(secret) && (p->authmethods & IAX_AUTH_PLAINTEXT)) {
                /* They've provided a plain text password and we support that */
                if (strcmp(secret, p->secret)) {
                        if (authdebug)
                                ast_log(LOG_NOTICE, "Host %s did not provide proper plaintext password for '%s'\n", ast_inet_ntoa(sin->sin_addr), p->name);
-                       if (ast_test_flag(p, IAX_TEMPONLY))
-                               destroy_peer(p);
-                       return -1;
+                       goto return_unref;
                } else
                        ast_set_flag(&iaxs[callno]->state, IAX_STATE_AUTHENTICATED);
        } else if (!ast_strlen_zero(md5secret) || !ast_strlen_zero(secret)) {
                if (authdebug)
                        ast_log(LOG_NOTICE, "Inappropriate authentication received\n");
-               if (ast_test_flag(p, IAX_TEMPONLY))
-                       destroy_peer(p);
-               return -1;
+               goto return_unref;
        }
        ast_string_field_set(iaxs[callno], peer, peer);
        /* Choose lowest expiry number */
@@ -5536,10 +5605,13 @@ static int register_verify(int callno, struct sockaddr_in *sin, struct iax_ies *
 
        ast_device_state_changed("IAX2/%s", p->name); /* Activate notification */
 
-       if (ast_test_flag(p, IAX_TEMPONLY))
-               destroy_peer(p);
+       res = 0;
 
-       return 0;
+return_unref:
+       if (p)
+               peer_unref(p);
+
+       return res;
 }
 
 static int authenticate(const char *challenge, const char *secret, const char *keyn, int authmethods, struct iax_ie_data *ied, struct sockaddr_in *sin, ast_aes_encrypt_key *ecx, ast_aes_decrypt_key *dcx)
@@ -5626,8 +5698,8 @@ static int authenticate_reply(struct chan_iax2_pvt *p, struct sockaddr_in *sin,
                /* Normal password authentication */
                res = authenticate(p->challenge, override, okey, authmethods, &ied, sin, &p->ecx, &p->dcx);
        } else {
-               AST_LIST_LOCK(&peers);
-               AST_LIST_TRAVERSE(&peers, peer, entry) {
+               ao2_iterator i = ao2_iterator_init(peers, 0);
+               while ((peer = ao2_iterator_next(&i))) {
                        if ((ast_strlen_zero(p->peer) || !strcmp(p->peer, peer->name)) 
                            /* No peer specified at our end, or this is the peer */
                            && (ast_strlen_zero(peer->username) || (!strcmp(peer->username, p->username)))
@@ -5636,11 +5708,11 @@ static int authenticate_reply(struct chan_iax2_pvt *p, struct sockaddr_in *sin,
                            /* No specified host, or this is our host */
                                ) {
                                res = authenticate(p->challenge, peer->secret, peer->outkey, authmethods, &ied, sin, &p->ecx, &p->dcx);
+                               peer_unref(peer);
                                if (!res)
                                        break;  
                        }
                }
-               AST_LIST_UNLOCK(&peers);
                if (!peer) {
                        /* We checked our list and didn't find one.  It's unlikely, but possible, 
                           that we're trying to authenticate *to* a realtime peer */
@@ -5649,13 +5721,11 @@ static int authenticate_reply(struct chan_iax2_pvt *p, struct sockaddr_in *sin,
                        if ((peer = realtime_peer(peer_name, NULL))) {
                                ast_mutex_lock(&iaxsl[callno]);
                                if (!(p = iaxs[callno])) {
-                                       if (ast_test_flag(peer, IAX_TEMPONLY))
-                                               destroy_peer(peer);
+                                       peer_unref(peer);
                                        return -1;
                                }
                                res = authenticate(p->challenge, peer->secret,peer->outkey, authmethods, &ied, sin, &p->ecx, &p->dcx);
-                               if (ast_test_flag(peer, IAX_TEMPONLY))
-                                       destroy_peer(peer);
+                               peer_unref(peer);
                        }
                        if (!peer) {
                                ast_mutex_lock(&iaxsl[callno]);
@@ -5997,42 +6067,36 @@ static void prune_peers(void);
 static void __expire_registry(void *data)
 {
        char *name = data;
-       struct iax2_peer *p = NULL;
-
-       /* Go through and grab this peer... and if it needs to be removed... then do it */
-       AST_LIST_LOCK(&peers);
-       AST_LIST_TRAVERSE_SAFE_BEGIN(&peers, p, entry) {
-               if (!strcasecmp(p->name, name)) {
-                       p->expire = -1;
-                       break;
-               }
-       }
-       AST_LIST_TRAVERSE_SAFE_END
-       AST_LIST_UNLOCK(&peers);
+       struct iax2_peer *peer = NULL;
+       struct iax2_peer tmp_peer = {
+               .name = name,
+       };
 
-       /* Peer is already gone for whatever reason */
-       if (!p)
+       peer = ao2_find(peers, &tmp_peer, OBJ_POINTER);
+       if (!peer)
                return;
 
-       ast_debug(1, "Expiring registration for peer '%s'\n", p->name);
-       if (ast_test_flag((&globalflags), IAX_RTUPDATE) && (ast_test_flag(p, IAX_TEMPONLY|IAX_RTCACHEFRIENDS)))
-               realtime_update_peer(p->name, &p->addr, 0);
-       manager_event(EVENT_FLAG_SYSTEM, "PeerStatus", "Peer: IAX2/%s\r\nPeerStatus: Unregistered\r\nCause: Expired\r\n", p->name);
+       peer->expire = -1;
+
+       ast_debug(1, "Expiring registration for peer '%s'\n", peer->name);
+       if (ast_test_flag((&globalflags), IAX_RTUPDATE) && (ast_test_flag(peer, IAX_TEMPONLY|IAX_RTCACHEFRIENDS)))
+               realtime_update_peer(peer->name, &peer->addr, 0);
+       manager_event(EVENT_FLAG_SYSTEM, "PeerStatus", "Peer: IAX2/%s\r\nPeerStatus: Unregistered\r\nCause: Expired\r\n", peer->name);
        /* Reset the address */
-       memset(&p->addr, 0, sizeof(p->addr));
+       memset(&peer->addr, 0, sizeof(peer->addr));
        /* Reset expiry value */
-       p->expiry = min_reg_expire;
-       if (!ast_test_flag(p, IAX_TEMPONLY))
-               ast_db_del("IAX/Registry", p->name);
-       register_peer_exten(p, 0);
-       ast_device_state_changed("IAX2/%s", p->name); /* Activate notification */
+       peer->expiry = min_reg_expire;
+       if (!ast_test_flag(peer, IAX_TEMPONLY))
+               ast_db_del("IAX/Registry", peer->name);
+       register_peer_exten(peer, 0);
+       ast_device_state_changed("IAX2/%s", peer->name); /* Activate notification */
        if (iax2_regfunk)
-               iax2_regfunk(p->name, 0);
+               iax2_regfunk(peer->name, 0);
 
-       if (ast_test_flag(p, IAX_RTAUTOCLEAR)) {
-               ast_set_flag(p, IAX_DELME);
-               prune_peers();
-       }
+       if (ast_test_flag(peer, IAX_RTAUTOCLEAR))
+               ao2_unlink(peers, peer);
+
+       peer_unref(peer);
 }
 
 static int expire_registry(void *data)
@@ -6097,6 +6161,7 @@ static int update_registry(struct sockaddr_in *sin, int callno, char *devtype, i
        char data[80];
        int version;
        const char *peer_name;
+       int res = -1;
 
        memset(&ied, 0, sizeof(ied));
 
@@ -6110,11 +6175,8 @@ static int update_registry(struct sockaddr_in *sin, int callno, char *devtype, i
                return -1;
        }
        ast_mutex_lock(&iaxsl[callno]);
-       if (!iaxs[callno]) {
-               if (ast_test_flag(p, IAX_TEMPONLY))
-                       destroy_peer(p);
-               return -1;
-       }
+       if (!iaxs[callno])
+               goto return_unref;
 
        if (ast_test_flag((&globalflags), IAX_RTUPDATE) && (ast_test_flag(p, IAX_TEMPONLY|IAX_RTCACHEFRIENDS))) {
                if (sin->sin_addr.s_addr) {
@@ -6153,9 +6215,8 @@ static int update_registry(struct sockaddr_in *sin, int callno, char *devtype, i
 
        /* Make sure our call still exists, an INVAL at the right point may make it go away */
        if (!iaxs[callno]) {
-               if (ast_test_flag(p, IAX_TEMPONLY))
-                       destroy_peer(p);
-               return 0;
+               res = 0;
+               goto return_unref;
        }
 
        /* Store socket fd */
@@ -6223,9 +6284,13 @@ static int update_registry(struct sockaddr_in *sin, int callno, char *devtype, i
        version = iax_check_version(devtype);
        if (version) 
                iax_ie_append_short(&ied, IAX_IE_FIRMWAREVER, version);
-       if (ast_test_flag(p, IAX_TEMPONLY))
-               destroy_peer(p);
-       return send_command_final(iaxs[callno], AST_FRAME_IAX, IAX_COMMAND_REGACK, 0, ied.buf, ied.pos, -1);
+
+       res = 0;
+
+return_unref:
+       peer_unref(p);
+
+       return res ? res : send_command_final(iaxs[callno], AST_FRAME_IAX, IAX_COMMAND_REGACK, 0, ied.buf, ied.pos, -1);
 }
 
 static int registry_authrequest(int callno)
@@ -6234,6 +6299,7 @@ static int registry_authrequest(int callno)
        struct iax2_peer *p;
        char challenge[10];
        const char *peer_name;
+       int res = -1;
 
        peer_name = ast_strdupa(iaxs[callno]->peer);
 
@@ -6241,28 +6307,29 @@ static int registry_authrequest(int callno)
        ast_mutex_unlock(&iaxsl[callno]);
        p = find_peer(peer_name, 1);
        ast_mutex_lock(&iaxsl[callno]);
-       if (!iaxs[callno]) {
-               if (p && ast_test_flag(p, IAX_TEMPONLY))
-                       destroy_peer(p);
-               return -1;
+       if (!iaxs[callno])
+               goto return_unref;
+       if (!p) {
+               ast_log(LOG_WARNING, "No such peer '%s'\n", peer_name);
+               goto return_unref;
        }
-       if (p) {
-               memset(&ied, 0, sizeof(ied));
-               iax_ie_append_short(&ied, IAX_IE_AUTHMETHODS, p->authmethods);
-               if (p->authmethods & (IAX_AUTH_RSA | IAX_AUTH_MD5)) {
-                       /* Build the challenge */
-                       snprintf(challenge, sizeof(challenge), "%d", (int)ast_random());
-                       ast_string_field_set(iaxs[callno], challenge, challenge);
-                       /* snprintf(iaxs[callno]->challenge, sizeof(iaxs[callno]->challenge), "%d", (int)ast_random()); */
-                       iax_ie_append_str(&ied, IAX_IE_CHALLENGE, iaxs[callno]->challenge);
-               }
-               iax_ie_append_str(&ied, IAX_IE_USERNAME, peer_name);
-               if (ast_test_flag(p, IAX_TEMPONLY))
-                       destroy_peer(p);
-               return send_command(iaxs[callno], AST_FRAME_IAX, IAX_COMMAND_REGAUTH, 0, ied.buf, ied.pos, -1);;
-       } 
-       ast_log(LOG_WARNING, "No such peer '%s'\n", peer_name);
-       return 0;
+       
+       memset(&ied, 0, sizeof(ied));
+       iax_ie_append_short(&ied, IAX_IE_AUTHMETHODS, p->authmethods);
+       if (p->authmethods & (IAX_AUTH_RSA | IAX_AUTH_MD5)) {
+               /* Build the challenge */
+               snprintf(challenge, sizeof(challenge), "%d", (int)ast_random());
+               ast_string_field_set(iaxs[callno], challenge, challenge);
+               iax_ie_append_str(&ied, IAX_IE_CHALLENGE, iaxs[callno]->challenge);
+       }
+       iax_ie_append_str(&ied, IAX_IE_USERNAME, peer_name);
+
+       res = 0;
+
+return_unref:
+       peer_unref(p);
+
+       return res ? res : send_command(iaxs[callno], AST_FRAME_IAX, IAX_COMMAND_REGAUTH, 0, ied.buf, ied.pos, -1);;
 }
 
 static int registry_rerequest(struct iax_ies *ies, int callno, struct sockaddr_in *sin)
@@ -6993,7 +7060,7 @@ static int socket_process_meta(int packet_len, struct ast_iax2_meta_hdr *meta, s
                /* Stop if we don't have enough data */
                if (len > packet_len)
                        break;
-               fr->callno = find_callno(callno & ~IAX_FLAG_FULL, 0, sin, NEW_PREVENT, 1, sockfd);
+               fr->callno = find_callno(callno & ~IAX_FLAG_FULL, 0, sin, NEW_PREVENT, sockfd);
                if (!fr->callno)
                        continue;
 
@@ -7178,7 +7245,7 @@ static int socket_process(struct iax2_thread *thread)
                }
 
                /* This is a video frame, get call number */
-               fr->callno = find_callno(ntohs(vh->callno) & ~0x8000, dcallno, &sin, new, 1, fd);
+               fr->callno = find_callno(ntohs(vh->callno) & ~0x8000, dcallno, &sin, new, fd);
                minivid = 1;
        } else if ((meta->zeros == 0) && !(ntohs(meta->metacmd) & 0x8000))
                return socket_process_meta(res, meta, &sin, fd, fr);
@@ -7213,7 +7280,7 @@ static int socket_process(struct iax2_thread *thread)
        }
 
        if (!fr->callno)
-               fr->callno = find_callno(ntohs(mh->callno) & ~IAX_FLAG_FULL, dcallno, &sin, new, 1, fd);
+               fr->callno = find_callno(ntohs(mh->callno) & ~IAX_FLAG_FULL, dcallno, &sin, new, fd);
 
        if (fr->callno > 0)
                ast_mutex_lock(&iaxsl[fr->callno]);
@@ -8756,7 +8823,7 @@ static int iax2_do_register(struct iax2_registry *reg)
 
        if (!reg->callno) {
                ast_debug(1, "Allocate call number\n");
-               reg->callno = find_callno(0, 0, &reg->addr, NEW_FORCE, 1, defaultsockfd);
+               reg->callno = find_callno(0, 0, &reg->addr, NEW_FORCE, defaultsockfd);
                if (reg->callno < 1) {
                        ast_log(LOG_WARNING, "Unable to create call for registration\n");
                        return -1;
@@ -8813,7 +8880,7 @@ static int iax2_provision(struct sockaddr_in *end, int sockfd, char *dest, const
        memset(&ied, 0, sizeof(ied));
        iax_ie_append_raw(&ied, IAX_IE_PROVISIONING, provdata.buf, provdata.pos);
 
-       callno = find_callno(0, 0, &sin, NEW_FORCE, 1, cai.sockfd);
+       callno = find_callno(0, 0, &sin, NEW_FORCE, cai.sockfd);
        if (!callno)
                return -1;
 
@@ -8923,6 +8990,15 @@ static int iax2_poke_noanswer(void *data)
        return 0;
 }
 
+static int iax2_poke_peer_cb(void *obj, void *arg, int flags)
+{
+       struct iax2_peer *peer = obj;
+
+       iax2_poke_peer(peer, 0);
+
+       return 0;
+}
+
 static int iax2_poke_peer(struct iax2_peer *peer, int heldcall)
 {
        if (!peer->maxms || !peer->addr.sin_addr.s_addr) {
@@ -8942,7 +9018,7 @@ static int iax2_poke_peer(struct iax2_peer *peer, int heldcall)
        }
        if (heldcall)
                ast_mutex_unlock(&iaxsl[heldcall]);
-       peer->callno = find_callno(0, 0, &peer->addr, NEW_FORCE, 0, peer->sockfd);
+       peer->callno = find_callno(0, 0, &peer->addr, NEW_FORCE, peer->sockfd);
        if (heldcall)
                ast_mutex_lock(&iaxsl[heldcall]);
        if (peer->callno < 1) {
@@ -9015,7 +9091,7 @@ static struct ast_channel *iax2_request(const char *type, int format, void *data
        if (pds.port)
                sin.sin_port = htons(atoi(pds.port));
 
-       callno = find_callno(0, 0, &sin, NEW_FORCE, 1, cai.sockfd);
+       callno = find_callno(0, 0, &sin, NEW_FORCE, cai.sockfd);
        if (callno < 1) {
                ast_log(LOG_WARNING, "Unable to create call\n");
                *cause = AST_CAUSE_CONGESTION;
@@ -9308,7 +9384,31 @@ static int peer_set_srcaddr(struct iax2_peer *peer, const char *srcaddr)
        }
 }
 
-               
+static void peer_destructor(void *obj)
+{
+       struct iax2_peer *peer = obj;
+
+       ast_free_ha(peer->ha);
+
+       /* Delete it, it needs to disappear */
+       if (peer->expire > -1)
+               ast_sched_del(sched, peer->expire);
+       if (peer->pokeexpire > -1)
+               ast_sched_del(sched, peer->pokeexpire);
+       if (peer->callno > 0) {
+               ast_mutex_lock(&iaxsl[peer->callno]);
+               iax2_destroy(peer->callno);
+               ast_mutex_unlock(&iaxsl[peer->callno]);
+       }
+
+       register_peer_exten(peer, 0);
+
+       if (peer->dnsmgr)
+               ast_dnsmgr_release(peer->dnsmgr);
+
+       ast_string_field_free_pools(peer);
+}
+
 /*! \brief Create peer structure based on configuration */
 static struct iax2_peer *build_peer(const char *name, struct ast_variable *v, struct ast_variable *alt, int temponly)
 {
@@ -9317,38 +9417,30 @@ static struct iax2_peer *build_peer(const char *name, struct ast_variable *v, st
        int maskfound = 0;
        int found = 0;
        int firstpass = 1;
+       struct iax2_peer tmp_peer = {
+               .name = name,
+       };
 
-       AST_LIST_LOCK(&peers);
        if (!temponly) {
-               AST_LIST_TRAVERSE(&peers, peer, entry) {
-                       if (!strcmp(peer->name, name)) {        
-                               if (!ast_test_flag(peer, IAX_DELME))
-                                       firstpass = 0;
-                               break;
-                       }
-               }
-       } else
-               peer = NULL;    
+               peer = ao2_find(peers, &tmp_peer, OBJ_POINTER);
+               if (peer && !ast_test_flag(peer, IAX_DELME))
+                       firstpass = 0;
+       }
+
        if (peer) {
                found++;
                if (firstpass) {
                        oldha = peer->ha;
                        peer->ha = NULL;
                }
-               AST_LIST_REMOVE(&peers, peer, entry);
-               AST_LIST_UNLOCK(&peers);
-       } else {
-               AST_LIST_UNLOCK(&peers);
-               if ((peer = ast_calloc(1, sizeof(*peer)))) {
-                       peer->expire = -1;
-                       peer->pokeexpire = -1;
-                       peer->sockfd = defaultsockfd;
-                       if (ast_string_field_init(peer, 32)) {
-                               ast_free(peer);
-                               peer = NULL;
-                       }
-               }
+       } else if ((peer = ao2_alloc(sizeof(*peer), peer_destructor))) {
+               peer->expire = -1;
+               peer->pokeexpire = -1;
+               peer->sockfd = defaultsockfd;
+               if (ast_string_field_init(peer, 32))
+                       peer = peer_unref(peer);
        }
+
        if (peer) {
                if (firstpass) {
                        ast_copy_flags(peer, &globalflags, IAX_USEJITTERBUF | IAX_FORCEJITTERBUF);
@@ -9430,8 +9522,7 @@ static struct iax2_peer *build_peer(const char *name, struct ast_variable *v, st
                                        ast_clear_flag(peer, IAX_DYNAMIC);
                                        if (ast_dnsmgr_lookup(v->value, &peer->addr.sin_addr, &peer->dnsmgr)) {
                                                ast_string_field_free_pools(peer);
-                                               ast_free(peer);
-                                               return NULL;
+                                               return peer_unref(peer);
                                        }
                                        if (!peer->addr.sin_port)
                                                peer->addr.sin_port = htons(IAX_DEFAULT_PORTNO);
@@ -9441,8 +9532,7 @@ static struct iax2_peer *build_peer(const char *name, struct ast_variable *v, st
                        } else if (!strcasecmp(v->name, "defaultip")) {
                                if (ast_get_ip(&peer->defaddr, v->value)) {
                                        ast_string_field_free_pools(peer);
-                                       ast_free(peer);
-                                       return NULL;
+                                       return peer_unref(peer);
                                }
                        } else if (!strcasecmp(v->name, "sourceaddress")) {
                                peer_set_srcaddr(peer, v->value);
@@ -9562,6 +9652,19 @@ static struct iax2_peer *build_peer(const char *name, struct ast_variable *v, st
        return peer;
 }
 
+static void user_destructor(void *obj)
+{
+       struct iax2_user *user = obj;
+
+       ast_free_ha(user->ha);
+       free_context(user->contexts);
+       if(user->vars) {
+               ast_variables_destroy(user->vars);
+               user->vars = NULL;
+       }
+       ast_string_field_free_pools(user);
+}
+
 /*! \brief Create in-memory user structure from configuration */
 static struct iax2_user *build_user(const char *name, struct ast_variable *v, struct ast_variable *alt, int temponly)
 {
@@ -9574,18 +9677,15 @@ static struct iax2_user *build_user(const char *name, struct ast_variable *v, st
        int oldcurauthreq = 0;
        char *varname = NULL, *varval = NULL;
        struct ast_variable *tmpvar = NULL;
-       
-       AST_LIST_LOCK(&users);
+       struct iax2_user tmp_user = {
+               .name = name,
+       };
+
        if (!temponly) {
-               AST_LIST_TRAVERSE(&users, user, entry) {
-                       if (!strcmp(user->name, name)) {        
-                               if (!ast_test_flag(user, IAX_DELME))
-                                       firstpass = 0;
-                               break;
-                       }
-               }
-       } else
-               user = NULL;
+               user = ao2_find(users, &tmp_user, OBJ_POINTER);
+               if (user && !ast_test_flag(user, IAX_DELME))
+                       firstpass = 0;
+       }
 
        if (user) {
                if (firstpass) {
@@ -9596,12 +9696,9 @@ static struct iax2_user *build_user(const char *name, struct ast_variable *v, st
                        user->contexts = NULL;
                }
                /* Already in the list, remove it and it will be added back (or FREE'd) */
-               AST_LIST_REMOVE(&users, user, entry);
-               AST_LIST_UNLOCK(&users);
+               ao2_unlink(users, user);
        } else {
-               AST_LIST_UNLOCK(&users);
-               /* This is going to memset'd to 0 in the next block */
-               user = ast_calloc(1, sizeof(*user));
+               user = ao2_alloc(sizeof(*user), user_destructor);
        }
        
        if (user) {
@@ -9609,8 +9706,8 @@ static struct iax2_user *build_user(const char *name, struct ast_variable *v, st
                        ast_string_field_free_pools(user);
                        memset(user, 0, sizeof(struct iax2_user));
                        if (ast_string_field_init(user, 32)) {
-                               ast_free(user);
-                               user = NULL;
+                               user = user_unref(user);
+                               goto cleanup;
                        }
                        user->maxauthreq = maxauthreq;
                        user->curauthreq = oldcurauthreq;
@@ -9770,6 +9867,7 @@ static struct iax2_user *build_user(const char *name, struct ast_variable *v, st
                }
                ast_clear_flag(user, IAX_DELME);
        }
+cleanup:
        if (oldha)
                ast_free_ha(oldha);
        if (oldcon)
@@ -9777,16 +9875,29 @@ static struct iax2_user *build_user(const char *name, struct ast_variable *v, st
        return user;
 }
 
+static int peer_delme_cb(void *obj, void *arg, int flags)
+{
+       struct iax2_peer *peer = obj;
+
+       ast_set_flag(peer, IAX_DELME);
+
+       return 0;
+}
+
+static int user_delme_cb(void *obj, void *arg, int flags)
+{
+       struct iax2_user *user = obj;
+
+       ast_set_flag(user, IAX_DELME);
+
+       return 0;
+}
+
 static void delete_users(void)
 {
-       struct iax2_user *user;
-       struct iax2_peer *peer;
        struct iax2_registry *reg;
 
-       AST_LIST_LOCK(&users);
-       AST_LIST_TRAVERSE(&users, user, entry)
-               ast_set_flag(user, IAX_DELME);
-       AST_LIST_UNLOCK(&users);
+       ao2_callback(users, 0, user_delme_cb, NULL);
 
        AST_LIST_LOCK(&registrations);
        while ((reg = AST_LIST_REMOVE_HEAD(&registrations, entry))) {
@@ -9806,81 +9917,34 @@ static void delete_users(void)
        }
        AST_LIST_UNLOCK(&registrations);
 
-       AST_LIST_LOCK(&peers);
-       AST_LIST_TRAVERSE(&peers, peer, entry)
-               ast_set_flag(peer, IAX_DELME);
-       AST_LIST_UNLOCK(&peers);
-}
-
-static void destroy_user(struct iax2_user *user)
-{
-       ast_free_ha(user->ha);
-       free_context(user->contexts);
-       if(user->vars) {
-               ast_variables_destroy(user->vars);
-               user->vars = NULL;
-       }
-       ast_string_field_free_pools(user);
-       ast_free(user);
+       ao2_callback(peers, 0, peer_delme_cb, NULL);
 }
 
 static void prune_users(void)
 {
-       struct iax2_user *user = NULL;
+       struct iax2_user *user;
+       ao2_iterator i;
 
-       AST_LIST_LOCK(&users);
-       AST_LIST_TRAVERSE_SAFE_BEGIN(&users, user, entry) {
-               if (ast_test_flag(user, IAX_DELME)) {
-                       destroy_user(user);
-                       AST_LIST_REMOVE_CURRENT(&users, entry);
-               }
+       i = ao2_iterator_init(users, 0);
+       while ((user = ao2_iterator_next(&i))) {
+               if (ast_test_flag(user, IAX_DELME))
+                       ao2_unlink(users, user);
+               user_unref(user);
        }
-       AST_LIST_TRAVERSE_SAFE_END
-       AST_LIST_UNLOCK(&users);
-
 }
 
-static void destroy_peer(struct iax2_peer *peer)
+/* Prune peers who still are supposed to be deleted */
+static void prune_peers(void)
 {
-       ast_free_ha(peer->ha);
-
-       /* Delete it, it needs to disappear */
-       if (peer->expire > -1)
-               ast_sched_del(sched, peer->expire);
-       if (peer->pokeexpire > -1)
-               ast_sched_del(sched, peer->pokeexpire);
-       if (peer->callno > 0) {
-               ast_mutex_lock(&iaxsl[peer->callno]);
-               iax2_destroy(peer->callno);
-               ast_mutex_unlock(&iaxsl[peer->callno]);
-       }
-
-       register_peer_exten(peer, 0);
-
-       if (peer->dnsmgr)
-               ast_dnsmgr_release(peer->dnsmgr);
-
-       if (peer->mwi_event_sub)
-               ast_event_unsubscribe(peer->mwi_event_sub);
-
-       ast_string_field_free_pools(peer);
-
-       ast_free(peer);
-}
-
-static void prune_peers(void){
-       /* Prune peers who still are supposed to be deleted */
-       struct iax2_peer *peer = NULL;
+       struct iax2_peer *peer;
+       ao2_iterator i;
 
-       AST_LIST_LOCK(&peers);
-       AST_LIST_TRAVERSE_SAFE_BEGIN(&peers, peer, entry) {
-               if (ast_test_flag(peer, IAX_DELME)) {
-                       destroy_peer(peer);
-                       AST_LIST_REMOVE_CURRENT(&peers, entry);
-               }
+       i = ao2_iterator_init(peers, 0);
+       while ((peer = ao2_iterator_next(&i))) {
+               if (ast_test_flag(peer, IAX_DELME))
+                       ao2_unlink(peers, peer);
+               peer_unref(peer);
        }
-       AST_LIST_TRAVERSE_SAFE_END
-       AST_LIST_UNLOCK(&peers);
 }
 
 static void set_timing(void)
@@ -10236,17 +10300,15 @@ static int set_config(char *config_file, int reload)
                                        /* Start with general parameters, then specific parameters, user and peer */
                                        user = build_user(cat, gen, ast_variable_browse(ucfg, cat), 0);
                                        if (user) {
-                                               AST_LIST_LOCK(&users);
-                                               AST_LIST_INSERT_HEAD(&users, user, entry);
-                                               AST_LIST_UNLOCK(&users);
+                                               ao2_link(users, user);
+                                               user = NULL;
                                        }
                                        peer = build_peer(cat, gen, ast_variable_browse(ucfg, cat), 0);
                                        if (peer) {
-                                               AST_LIST_LOCK(&peers);
-                                               AST_LIST_INSERT_HEAD(&peers, peer, entry);
-                                               AST_LIST_UNLOCK(&peers);
                                                if (ast_test_flag(peer, IAX_DYNAMIC))
                                                        reg_source_db(peer);
+                                               ao2_link(peers, peer);
+                                               peer = NULL;
                                        }
                                }
                                if (ast_true(registeriax) || (!registeriax && genregisteriax)) {
@@ -10282,19 +10344,17 @@ static int set_config(char *config_file, int reload)
                                if (!strcasecmp(utype, "user") || !strcasecmp(utype, "friend")) {
                                        user = build_user(cat, ast_variable_browse(cfg, cat), NULL, 0);
                                        if (user) {
-                                               AST_LIST_LOCK(&users);
-                                               AST_LIST_INSERT_HEAD(&users, user, entry);
-                                               AST_LIST_UNLOCK(&users);
+                                               ao2_link(users, user);
+                                               user = NULL;
                                        }
                                }
                                if (!strcasecmp(utype, "peer") || !strcasecmp(utype, "friend")) {
                                        peer = build_peer(cat, ast_variable_browse(cfg, cat), NULL, 0);
                                        if (peer) {
-                                               AST_LIST_LOCK(&peers);
-                                               AST_LIST_INSERT_HEAD(&peers, peer, entry);
-                                               AST_LIST_UNLOCK(&peers);
                                                if (ast_test_flag(peer, IAX_DYNAMIC))
                                                        reg_source_db(peer);
+                                               ao2_link(peers, peer);
+                                               peer = NULL;
                                        }
                                } else if (strcasecmp(utype, "user")) {
                                        ast_log(LOG_WARNING, "Unknown type '%s' for '%s' in %s\n", utype, cat, config_file);
@@ -10313,7 +10373,6 @@ static int reload_config(void)
 {
        char *config = "iax.conf";
        struct iax2_registry *reg;
-       struct iax2_peer *peer;
 
        if (set_config(config, 1) == 1) {
                prune_peers();
@@ -10327,11 +10386,9 @@ static int reload_config(void)
                AST_LIST_UNLOCK(&registrations);
 
                /* Qualify hosts, too */
-               AST_LIST_LOCK(&peers);
-               AST_LIST_TRAVERSE(&peers, peer, entry)
-                       iax2_poke_peer(peer, 0);
-               AST_LIST_UNLOCK(&peers);
+               ao2_callback(peers, 0, iax2_poke_peer_cb, NULL);
        }
+
        reload_firmware();
        iax_provision_reload(1);
 
@@ -10384,7 +10441,7 @@ static int cache_get_callno_locked(const char *data)
        ast_debug(1, "peer: %s, username: %s, password: %s, context: %s\n",
                pds.peer, pds.username, pds.password, pds.context);
 
-       callno = find_callno(0, 0, &sin, NEW_FORCE, 1, cai.sockfd);
+       callno = find_callno(0, 0, &sin, NEW_FORCE, cai.sockfd);
        if (callno < 1) {
                ast_log(LOG_WARNING, "Unable to create call\n");
                return -1;
@@ -10723,8 +10780,7 @@ static int function_iaxpeer(struct ast_channel *chan, const char *cmd, char *dat
                }
        }
 
-       if (ast_test_flag(peer, IAX_TEMPONLY))
-               destroy_peer(peer);
+       peer_unref(peer);
 
        return 0;
 }
@@ -10838,8 +10894,7 @@ static int iax2_devicestate(void *data)
                        res = AST_DEVICE_UNKNOWN;       
        }
 
-       if (ast_test_flag(p, IAX_TEMPONLY))
-               destroy_peer(p);
+       peer_unref(p);
 
        return res;
 }
@@ -11143,6 +11198,9 @@ static int __unload_module(void)
        for (x = 0; x < IAX_MAX_CALLS; x++)
                ast_mutex_destroy(&iaxsl[x]);
 
+       ao2_ref(peers, -1);
+       ao2_ref(users, -1);
+
        return 0;
 }
 
@@ -11153,6 +11211,15 @@ static int unload_module(void)
        return __unload_module();
 }
 
+static int peer_set_sock_cb(void *obj, void *arg, int flags)
+{
+       struct iax2_peer *peer = obj;
+
+       if (peer->sockfd < 0)
+               peer->sockfd = defaultsockfd;
+
+       return 0;
+}
 
 /*! \brief Load IAX2 module, load configuraiton ---*/
 static int load_module(void)
@@ -11160,8 +11227,16 @@ static int load_module(void)
        char *config = "iax.conf";
        int x = 0;
        struct iax2_registry *reg = NULL;
-       struct iax2_peer *peer = NULL;
-       
+
+       peers = ao2_container_alloc(MAX_PEER_BUCKETS, peer_hash_cb, peer_cmp_cb);
+       if (!peers)
+               return AST_MODULE_LOAD_FAILURE;
+       users = ao2_container_alloc(MAX_USER_BUCKETS, user_hash_cb, user_cmp_cb);
+       if (!users) {
+               ao2_ref(peers, -1);
+               return AST_MODULE_LOAD_FAILURE;
+       }
+
        ast_custom_function_register(&iaxpeer_function);
        ast_custom_function_register(&iaxvar_function);
 
@@ -11245,13 +11320,9 @@ static int load_module(void)
                iax2_do_register(reg);
        AST_LIST_UNLOCK(&registrations);        
        
-       AST_LIST_LOCK(&peers);
-       AST_LIST_TRAVERSE(&peers, peer, entry) {
-               if (peer->sockfd < 0)
-                       peer->sockfd = defaultsockfd;
-               iax2_poke_peer(peer, 0);
-       }
-       AST_LIST_UNLOCK(&peers);
+       ao2_callback(peers, 0, peer_set_sock_cb, NULL);
+       ao2_callback(peers, 0, iax2_poke_peer_cb, NULL);
+
 
        reload_firmware();
        iax_provision_reload(0);
diff --git a/include/asterisk/astobj2.h b/include/asterisk/astobj2.h
new file mode 100644 (file)
index 0000000..73c8ede
--- /dev/null
@@ -0,0 +1,515 @@
+/*
+ * astobj2 - replacement containers for asterisk data structures.
+ *
+ * Copyright (C) 2006 Marta Carbone, Luigi Rizzo - Univ. di Pisa, Italy
+ *
+ * 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_ASTOBJ2_H
+#define _ASTERISK_ASTOBJ2_H
+
+#include "asterisk/lock.h"
+
+/*! \file 
+ *
+ * \brief Object Model implementing objects and containers.
+
+These functions implement an abstraction for objects (with
+locks and reference counts) and containers for these user-defined objects,
+supporting locking, reference counting and callbacks.
+
+The internal implementation of the container is opaque to the user,
+so we can use different data structures as needs arise.
+
+At the moment, however, the only internal data structure is a hash
+table. When other structures will be implemented, the initialization
+function may change.
+
+USAGE - OBJECTS
+
+An object is a block of memory that must be allocated with the
+function ao2_alloc(), and for which the system keeps track (with
+abit of help from the programmer) of the number of references around.
+When an object has no more references, it is destroyed, by first
+invoking whatever 'destructor' function the programmer specifies
+(it can be NULL), and then freeing the memory.
+This way objects can be shared without worrying who is in charge
+of freeing them.
+
+Basically, creating an object requires the size of the object and
+and a pointer to the destructor function:
+    struct foo *o;
+    o = ao2_alloc(sizeof(struct foo), my_destructor_fn);
+
+The object returned has a refcount = 1.
+Note that the memory for the object is allocated and zeroed.
+- We cannot realloc() the object itself.
+- We cannot call free(o) to dispose of the object; rather we
+  tell the system that we do not need the reference anymore:
+
+    ao2_ref(o, -1)
+
+  causing the destructor to be called (and then memory freed) when
+  the refcount goes to 0. This is also available as ao2_unref(o),
+  and returns NULL as a convenience, so you can do things like
+       o = ao2_unref(o);
+  and clean the original pointer to prevent errors.
+
+- ao2_ref(o, +1) can be used to modify the refcount on the
+  object in case we want to pass it around.
+       
+
+- other calls on the object are ao2_lock(obj), ao2_unlock(),
+  ao2_trylock(), to manipulate the lock.
+
+
+USAGE - CONTAINERS
+
+A containers is an abstract data structure where we can store
+objects, search them (hopefully in an efficient way), and iterate
+or apply a callback function to them. A container is just an object
+itself.
+
+A container must first be allocated, specifying the initial
+parameters. At the moment, this is done as follows:
+
+    <b>Sample Usage:</b>
+    \code
+
+    ao2_container *c;
+
+    c = ao2_container_alloc(MAX_BUCKETS, my_hash_fn, my_cmp_fn, my_dump_fn);
+
+where
+- MAX_BUCKETS is the number of buckets in the hash table,
+- my_hash_fn() is the (user-supplied) function that returns a
+  hash key for the object (further reduced moduly MAX_BUCKETS
+  by the container's code);
+- my_cmp_fn() is the default comparison function used when doing
+  searches on the container,
+- my_dump_fn() is a helper function used only for debugging.
+
+A container knows little or nothing about the object itself,
+other than the fact that it has been created by ao2_alloc()
+All knowledge of the (user-defined) internals of the object
+is left to the (user-supplied) functions passed as arguments
+to ao2_container_alloc().
+
+If we want to insert the object in the container, we should
+initialize its fields -- especially, those used by my_hash_fn() --
+to compute the bucket to use.
+Once done, we can link an object to a container with
+
+    ao2_link(c, o);
+
+The function returns NULL in case of errors (and the object
+is not inserted in the container). Other values mean success
+(we are not supposed to use the value as a pointer to anything).
+
+\note inserting the object in the container grabs the reference
+to the object (which is now owned by the container) so we do not
+need to drop ours when we are done.
+
+\note While an object o is in a container, we expect that
+my_hash_fn(o) will always return the same value. The function
+does not lock the object to be computed, so modifications of
+those fields that affect the computation of the hash should
+be done by extractiong the object from the container, and
+reinserting it after the change (this is not terribly expensive).
+
+\note A container with a single buckets is effectively a linked
+list. However there is no ordering among elements.
+
+Objects implement a reference counter keeping the count
+of the number of references that reference an object.
+
+When this number becomes zero the destructor will be
+called and the object will be free'd.
+ */
+
+/*!
+ * Invoked just before freeing the memory for the object.
+ * It is passed a pointer to user data.
+ */
+typedef void (*ao2_destructor_fn)(void *);
+
+void ao2_bt(void);     /* backtrace */
+/*!
+ * Allocate and initialize an object.
+ * 
+ * \param data_size The sizeof() of user-defined structure.
+ * \param destructor_fn The function destructor (can be NULL)
+ * \return A pointer to user data. 
+ *
+ * Allocates a struct astobj2 with sufficient space for the
+ * user-defined structure.
+ * \notes:
+ * - storage is zeroed; XXX maybe we want a flag to enable/disable this.
+ * - the refcount of the object just created is 1
+ * - the returned pointer cannot be free()'d or realloc()'ed;
+ *   rather, we just call ao2_ref(o, -1);
+ */
+void *ao2_alloc(const size_t data_size, ao2_destructor_fn destructor_fn);
+
+/*!
+ * Reference/unreference an object and return the old refcount.
+ *
+ * \param o A pointer to the object
+ * \param delta Value to add to the reference counter.
+ * \return The value of the reference counter before the operation.
+ *
+ * Increase/decrease the reference counter according
+ * the value of delta.
+ *
+ * If the refcount goes to zero, the object is destroyed.
+ *
+ * \note The object must not be locked by the caller of this function, as
+ *       it is invalid to try to unlock it after releasing the reference.
+ *
+ * \note if we know the pointer to an object, it is because we
+ * have a reference count to it, so the only case when the object
+ * can go away is when we release our reference, and it is
+ * the last one in existence.
+ */
+int ao2_ref(void *o, int delta);
+
+/*!
+ * Lock an object.
+ * 
+ * \param a A pointer to the object we want lock.
+ * \return 0 on success, other values on error.
+ */
+int ao2_lock(void *a);
+
+/*!
+ * Unlock an object.
+ * 
+ * \param a A pointer to the object we want unlock.
+ * \return 0 on success, other values on error.
+ */
+int ao2_unlock(void *a);
+
+/*!
+ *
+ * Containers
+
+containers are data structures meant to store several objects,
+and perform various operations on them.
+Internally, objects are stored in lists, hash tables or other
+data structures depending on the needs.
+
+NOTA BENE: at the moment the only container we support is the
+hash table and its degenerate form, the list.
+
+Operations on container include:
+
+    c = ao2_container_alloc(size, cmp_fn, hash_fn)
+       allocate a container with desired size and default compare
+       and hash function
+
+    ao2_find(c, arg, flags)
+       returns zero or more element matching a given criteria
+       (specified as arg). Flags indicate how many results we
+       want (only one or all matching entries), and whether we
+       should unlink the object from the container.
+
+    ao2_callback(c, flags, fn, arg)
+       apply fn(obj, arg) to all objects in the container.
+       Similar to find. fn() can tell when to stop, and
+       do anything with the object including unlinking it.
+       Note that the entire operation is run with the container
+       locked, so noone else can change its content while we work on it.
+       However, we pay this with the fact that doing
+       anything blocking in the callback keeps the container
+       blocked.
+       The mechanism is very flexible because the callback function fn()
+       can do basically anything e.g. counting, deleting records, etc.
+       possibly using arg to store the results.
+   
+    iterate on a container
+       this is done with the following sequence
+
+           ao2_container *c = ... // our container
+           ao2_iterator i;
+           void *o;
+
+           i = ao2_iterator_init(c, flags);
+     
+           while ( (o = ao2_iterator_next(&i)) ) {
+               ... do something on o ...
+               ao2_ref(o, -1);
+           }
+
+       The difference with the callback is that the control
+       on how to iterate is left to us.
+
+    ao2_ref(c, -1)
+       dropping a reference to a container destroys it, very simple!
+Containers are astobj2 object themselves, and this is why their
+implementation is simple too.
+
+ */
+
+/*!
+ * We can perform different operation on an object. We do this
+ * according the following flags.
+ */
+enum search_flags {
+       /*! unlink the object found */
+       OBJ_UNLINK       = (1 << 0),
+       /*! on match, don't return the object or increase its reference count. */
+       OBJ_NODATA       = (1 << 1),
+       /*! don't stop at the first match 
+        *  \note This is not fully implemented. */
+       OBJ_MULTIPLE = (1 << 2),
+       /*! obj is an object of the same type as the one being searched for.
+        *  This implies that it can be passed to the object's hash function
+        *  for optimized searching. */
+       OBJ_POINTER      = (1 << 3),
+};
+
+/*!
+ * Type of a generic function to generate a hash value from an object.
+ *
+ */
+typedef int (*ao2_hash_fn)(const void *obj, const int flags);
+
+/*!
+ * valid callback results:
+ * We return a combination of
+ * CMP_MATCH when the object matches the request,
+ * and CMP_STOP when we should not continue the search further.
+ */
+enum _cb_results {
+       CMP_MATCH       = 0x1,
+       CMP_STOP        = 0x2,
+};
+
+/*!
+ * generic function to compare objects.
+ * This, as other callbacks, should return a combination of
+ * _cb_results as described above.
+ *
+ * \param o    object from container
+ * \param arg  search parameters (directly from ao2_find)
+ * \param flags        passed directly from ao2_find
+ *     XXX explain.
+ */
+
+/*!
+ * Type of a generic callback function
+ * \param obj  pointer to the (user-defined part) of an object.
+ * \param arg callback argument from ao2_callback()
+ * \param flags flags from ao2_callback()
+ * The return values are the same as a compare function.
+ * In fact, they are the same thing.
+ */
+typedef int (*ao2_callback_fn)(void *obj, void *arg, int flags);
+
+/*!
+ * Here start declarations of containers.
+ */
+
+/*!
+ * This structure contains the total number of buckets 
+ * and variable size array of object pointers.
+ * It is opaque, defined in astobj2.c, so we only need
+ * a type declaration.
+ */
+typedef struct __ao2_container ao2_container;
+
+/*!
+ * Allocate and initialize a container 
+ * with the desired number of buckets.
+ * 
+ * We allocate space for a struct astobj_container, struct container
+ * and the buckets[] array.
+ *
+ * \param my_hash_fn Pointer to a function computing a hash value.
+ * \param my_cmp_fn Pointer to a function comparating key-value 
+ *                     with a string. (can be NULL)
+ * \return A pointer to a struct container.
+ *
+ * destructor is set implicitly.
+ */
+ao2_container *ao2_container_alloc(const uint n_buckets,
+               ao2_hash_fn hash_fn, ao2_callback_fn cmp_fn);
+
+/*!
+ * Returns the number of elements in a container.
+ */
+int ao2_container_count(ao2_container *c);
+
+/*
+ * Here we have functions to manage objects.
+ *
+ * We can use the functions below on any kind of 
+ * object defined by the user.
+ */
+/*!
+ * Add an object to a container.
+ *
+ * \param c the container to operate on.
+ * \param obj the object to be added.
+ * \return NULL on errors, other values on success.
+ *
+ * This function insert an object in a container according its key.
+ *
+ * \note Remember to set the key before calling this function.
+ */
+void *ao2_link(ao2_container *c, void *newobj);
+void *ao2_unlink(ao2_container *c, void *newobj);
+
+/*! \struct Used as return value if the flag OBJ_MULTIPLE is set */
+struct ao2_list {
+       struct ao2_list *next;
+       void *obj;      /* pointer to the user portion of the object */
+};
+
+/*!
+ * ao2_callback() and astob2_find() are the same thing with only one difference:
+ * the latter uses as a callback the function passed as my_cmp_f() at
+ * the time of the creation of the container.
+ * 
+ * \param c A pointer to the container to operate on.
+ * \param arg passed to the callback.
+ * \param flags A set of flags specifying the operation to perform,
+       partially used by the container code, but also passed to
+       the callback.
+ * \return     A pointer to the object found/marked, 
+ *             a pointer to a list of objects matching comparison function,
+ *             NULL if not found.
+ * If the function returns any objects, their refcount is incremented,
+ * and the caller is in charge of decrementing them once done.
+ * Also, in case of multiple values returned, the list used
+ * to store the objects must be freed by the caller.
+ *
+ * This function searches through a container and performs operations
+ * on objects according on flags passed.
+ * XXX describe better
+ * The comparison is done calling the compare function set implicitly. 
+ * The p pointer can be a pointer to an object or to a key, 
+ * we can say this looking at flags value.
+ * If p points to an object we will search for the object pointed
+ * by this value, otherwise we serch for a key value.
+ * If the key is not uniq we only find the first matching valued.
+ * If we use the OBJ_MARK flags, we mark all the objects matching 
+ * the condition.
+ *
+ * The use of flags argument is the follow:
+ *
+ *     OBJ_UNLINK              unlinks the object found
+ *     OBJ_NODATA              on match, do return an object
+ *                             Callbacks use OBJ_NODATA as a default
+ *                             functions such as find() do
+ *     OBJ_MULTIPLE            return multiple matches
+ *                             Default for _find() is no.
+ *                             to a key (not yet supported)
+ *     OBJ_POINTER             the pointer is an object pointer
+ *
+ * In case we return a list, the callee must take care to destroy 
+ * that list when no longer used.
+ *
+ * \note When the returned object is no longer in use, ao2_ref() should
+ * be used to free the additional reference possibly created by this function.
+ */
+/* XXX order of arguments to find */
+void *ao2_find(ao2_container *c, void *arg, enum search_flags flags);
+void *ao2_callback(ao2_container *c,
+       enum search_flags flags,
+       ao2_callback_fn cb_fn, void *arg);
+
+/*!
+ *
+
+When we need to walk through a container, we use
+ao2_iterator to keep track of the current position.
+
+Because the navigation is typically done without holding the
+lock on the container across the loop,
+objects can be inserted or deleted or moved
+while we work. As a consequence, there is no guarantee that
+the we manage to touch all the elements on the list, or it
+is possible that we touch the same object multiple times.
+
+An iterator must be first initialized with ao2_iterator_init(),
+then we can use o = ao2_iterator_next() to move from one
+element to the next. Remember that the object returned by
+ao2_iterator_next() has its refcount incremented,
+and the reference must be explicitly released when done with it.
+Example:
+
+    \code
+
+    ao2_container *c = ... // the container we want to iterate on
+    ao2_iterator i;
+    struct my_obj *o;
+
+    i = ao2_iterator_init(c, flags);
+    while ( (o = ao2_iterator_next(&i)) ) {
+       ... do something on o ...
+       ao2_ref(o, -1);
+    }
+
+    \endcode
+
+ */
+
+/*!
+ * You are not supposed to know the internals of an iterator!
+ * We would like the iterator to be opaque, unfortunately
+ * its size needs to be known if we want to store it around
+ * without too much trouble.
+ * Anyways...
+ * The iterator has a pointer to the container, and a flags
+ * field specifying various things e.g. whether the container
+ * should be locked or not while navigating on it.
+ * The iterator "points" to the current object, which is identified
+ * by three values:
+ * - a bucket number;
+ * - the object_id, which is also the container version number
+ *   when the object was inserted. This identifies the object
+ *   univoquely, however reaching the desired object requires
+ *   scanning a list.
+ * - a pointer, and a container version when we saved the pointer.
+ *   If the container has not changed its version number, then we
+ *   can safely follow the pointer to reach the object in constant time.
+ * Details are in the implementation of ao2_iterator_next()
+ * A freshly-initialized iterator has bucket=0, version = 0.
+ */
+
+struct __ao2_iterator {
+       /*! the container */
+       ao2_container *c;
+       /*! operation flags */
+       int flags;
+#define        F_AO2I_DONTLOCK 1       /*!< don't lock when iterating */
+       /*! current bucket */
+       int bucket;
+       /*! container version */
+       uint c_version;
+       /*! pointer to the current object */
+       void *obj;
+       /*! container version when the object was created */
+       uint version;
+};              
+typedef struct __ao2_iterator ao2_iterator;
+
+ao2_iterator ao2_iterator_init(ao2_container *c, int flags);
+
+void *ao2_iterator_next(ao2_iterator *a);
+
+#endif /* _ASTERISK_ASTOBJ2_H */
index 76b58d7..d542ef8 100644 (file)
@@ -23,6 +23,7 @@
 #ifndef _ASTERISK_STRINGS_H
 #define _ASTERISK_STRINGS_H
 
+#include <stdlib.h>
 #include <string.h>
 #include <stdarg.h>
 
@@ -418,7 +419,6 @@ int ast_str_make_space(struct ast_str **buf, size_t new_len),
                (buf);                                  \
        })
 
-
 /*!
  * \brief Retrieve a thread locally stored dynamic string
  *
@@ -662,4 +662,22 @@ int __attribute__ ((format (printf, 3, 4))) ast_str_append(
 }
 )
 
+/*!
+ * \brief Compute a hash value on a string
+ *
+ * This famous hash algorithm was written by Dan Bernstein and is
+ * commonly used.
+ *
+ * http://www.cse.yorku.ca/~oz/hash.html
+ */
+static force_inline int ast_str_hash(const char *str)
+{
+       int hash = 5381;
+
+       while (*str)
+               hash = hash * 33 ^ *str++;
+
+       return abs(hash);
+}
+
 #endif /* _ASTERISK_STRINGS_H */
index ba780b2..fb80748 100644 (file)
@@ -26,7 +26,8 @@ OBJS= io.o sched.o logger.o frame.o loader.o config.o channel.o \
        utils.o plc.o jitterbuf.o dnsmgr.o devicestate.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
+       strcompat.o threadstorage.o dial.o event.o adsistub.o audiohook.o \
+       astobj2.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/astobj2.c b/main/astobj2.c
new file mode 100644 (file)
index 0000000..9d2b0af
--- /dev/null
@@ -0,0 +1,685 @@
+/*
+ * astobj2 - replacement containers for asterisk data structures.
+ *
+ * Copyright (C) 2006 Marta Carbone, Luigi Rizzo - Univ. di Pisa, Italy
+ *
+ * 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.
+ */
+
+/*
+ * Function implementing astobj2 objects.
+ */
+#include "asterisk.h"
+
+ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
+
+#include "asterisk/astobj2.h"
+#include "asterisk/utils.h"
+#include "asterisk/cli.h"
+
+/*!
+ * astobj2 objects are always prepended this data structure,
+ * which contains a lock, a reference counter,
+ * the flags and a pointer to a destructor.
+ * The refcount is used to decide when it is time to
+ * invoke the destructor.
+ * The magic number is used for consistency check.
+ * XXX the lock is not always needed, and its initialization may be
+ * expensive. Consider making it external.
+ */
+struct __priv_data {
+       ast_mutex_t lock;
+       int ref_counter;
+       ao2_destructor_fn destructor_fn;
+       /*! for stats */
+       size_t data_size;
+       /*! magic number.  This is used to verify that a pointer passed in is a
+        *  valid astobj2 */
+       uint32_t magic;
+};
+
+#define        AO2_MAGIC       0xa570b123
+
+/*!
+ * What an astobj2 object looks like: fixed-size private data
+ * followed by variable-size user data.
+ */
+struct astobj2 {
+       struct __priv_data priv_data;
+       void *user_data[0];
+};
+
+struct ao2_stats {
+       volatile int total_objects;
+       volatile int total_mem;
+       volatile int total_containers;
+       volatile int total_refs;
+       volatile int total_locked;
+};
+
+static struct ao2_stats ao2;
+
+#ifndef HAVE_BKTR      /* backtrace support */
+void ao2_bt(void) {}
+#else
+#include <execinfo.h>    /* for backtrace */
+
+void ao2_bt(void)
+{
+    int c, i;
+#define N1     20
+    void *addresses[N1];
+    char **strings;
+
+    c = backtrace(addresses, N1);
+    strings = backtrace_symbols(addresses,c);
+    ast_verbose("backtrace returned: %d\n", c);
+    for(i = 0; i < c; i++) {
+        ast_verbose("%d: %p %s\n", i, addresses[i], strings[i]);
+    }
+    free(strings);
+}
+#endif
+
+/*!
+ * \brief convert from a pointer _p to a user-defined object
+ *
+ * \return the pointer to the astobj2 structure
+ */
+static inline struct astobj2 *INTERNAL_OBJ(void *user_data)
+{
+       struct astobj2 *p;
+
+       if (!user_data) {
+               ast_log(LOG_ERROR, "user_data is NULL\n");
+               return NULL;
+       }
+
+       p = (struct astobj2 *) ((char *) user_data - sizeof(*p));
+       if (AO2_MAGIC != (p->priv_data.magic) ) {
+               ast_log(LOG_ERROR, "bad magic number 0x%x for %p\n", p->priv_data.magic, p);
+               p = NULL;
+       }
+
+       return p;
+}
+
+/*!
+ * \brief convert from a pointer _p to an astobj2 object
+ *
+ * \return the pointer to the user-defined portion.
+ */
+#define EXTERNAL_OBJ(_p)       ((_p) == NULL ? NULL : (_p)->user_data)
+
+int ao2_lock(void *user_data)
+{
+       struct astobj2 *p = INTERNAL_OBJ(user_data);
+
+       if (p == NULL)
+               return -1;
+
+       ast_atomic_fetchadd_int(&ao2.total_locked, 1);
+
+       return ast_mutex_lock(&p->priv_data.lock);
+}
+
+int ao2_unlock(void *user_data)
+{
+       struct astobj2 *p = INTERNAL_OBJ(user_data);
+
+       if (p == NULL)
+               return -1;
+
+       ast_atomic_fetchadd_int(&ao2.total_locked, -1);
+
+       return ast_mutex_unlock(&p->priv_data.lock);
+}
+
+/*
+ * The argument is a pointer to the user portion.
+ */
+int ao2_ref(void *user_data, const int delta)
+{
+       int current_value;
+       int ret;
+       struct astobj2 *obj = INTERNAL_OBJ(user_data);
+
+       if (obj == NULL)
+               return -1;
+
+       /* if delta is 0, just return the refcount */
+       if (delta == 0)
+               return (obj->priv_data.ref_counter);
+
+       /* we modify with an atomic operation the reference counter */
+       ret = ast_atomic_fetchadd_int(&obj->priv_data.ref_counter, delta);
+       ast_atomic_fetchadd_int(&ao2.total_refs, delta);
+       current_value = ret + delta;
+       
+       /* this case must never happen */
+       if (current_value < 0)
+               ast_log(LOG_ERROR, "refcount %d on object %p\n", current_value, user_data);
+
+       if (current_value <= 0) { /* last reference, destroy the object */
+               if (obj->priv_data.destructor_fn != NULL) 
+                       obj->priv_data.destructor_fn(user_data);
+
+               ast_mutex_destroy(&obj->priv_data.lock);
+               ast_atomic_fetchadd_int(&ao2.total_mem, - obj->priv_data.data_size);
+               /* for safety, zero-out the astobj2 header and also the
+                * first word of the user-data, which we make sure is always
+                * allocated. */
+               bzero(obj, sizeof(struct astobj2 *) + sizeof(void *) );
+               free(obj);
+               ast_atomic_fetchadd_int(&ao2.total_objects, -1);
+       }
+
+       return ret;
+}
+
+/*
+ * We always alloc at least the size of a void *,
+ * for debugging purposes.
+ */
+void *ao2_alloc(size_t data_size, ao2_destructor_fn destructor_fn)
+{
+       /* allocation */
+       struct astobj2 *obj;
+
+       if (data_size < sizeof(void *))
+               data_size = sizeof(void *);
+
+       obj = ast_calloc(1, sizeof(*obj) + data_size);
+
+       if (obj == NULL)
+               return NULL;
+
+       ast_mutex_init(&obj->priv_data.lock);
+       obj->priv_data.magic = AO2_MAGIC;
+       obj->priv_data.data_size = data_size;
+       obj->priv_data.ref_counter = 1;
+       obj->priv_data.destructor_fn = destructor_fn;   /* can be NULL */
+       ast_atomic_fetchadd_int(&ao2.total_objects, 1);
+       ast_atomic_fetchadd_int(&ao2.total_mem, data_size);
+       ast_atomic_fetchadd_int(&ao2.total_refs, 1);
+
+       /* return a pointer to the user data */
+       return EXTERNAL_OBJ(obj);
+}
+
+/* internal callback to destroy a container. */
+static void container_destruct(void *c);
+
+/* each bucket in the container is a tailq. */
+AST_LIST_HEAD_NOLOCK(bucket, bucket_list);
+
+/*!
+ * A container; stores the hash and callback functions, information on
+ * the size, the hash bucket heads, and a version number, starting at 0
+ * (for a newly created, empty container)
+ * and incremented every time an object is inserted or deleted.
+ * The assumption is that an object is never moved in a container,
+ * but removed and readded with the new number.
+ * The version number is especially useful when implementing iterators.
+ * In fact, we can associate a unique, monotonically increasing number to
+ * each object, which means that, within an iterator, we can store the
+ * version number of the current object, and easily look for the next one,
+ * which is the next one in the list with a higher number.
+ * Since all objects have a version >0, we can use 0 as a marker for
+ * 'we need the first object in the bucket'.
+ *
+ * \todo Linking and unlink objects is typically expensive, as it
+ * involves a malloc() of a small object which is very inefficient.
+ * To optimize this, we allocate larger arrays of bucket_list's
+ * when we run out of them, and then manage our own freelist.
+ * This will be more efficient as we can do the freelist management while
+ * we hold the lock (that we need anyways).
+ */
+struct __ao2_container {
+       ao2_hash_fn hash_fn;
+       ao2_callback_fn cmp_fn;
+       int n_buckets;
+       /*! Number of elements in the container */
+       int elements;
+       /*! described above */
+       int version;
+       /*! variable size */
+       struct bucket buckets[0];
+};
+/*!
+ * \brief always zero hash function
+ *
+ * it is convenient to have a hash function that always returns 0.
+ * This is basically used when we want to have a container that is
+ * a simple linked list.
+ *
+ * \returns 0
+ */
+static int hash_zero(const void *user_obj, const int flags)
+{
+       return 0;
+}
+
+/*
+ * A container is just an object, after all!
+ */
+ao2_container *
+ao2_container_alloc(const uint n_buckets, ao2_hash_fn hash_fn,
+               ao2_callback_fn cmp_fn)
+{
+       /* XXX maybe consistency check on arguments ? */
+       /* compute the container size */
+       size_t container_size = sizeof(ao2_container) + n_buckets * sizeof(struct bucket);
+
+       ao2_container *c = ao2_alloc(container_size, container_destruct);
+
+       if (!c)
+               return NULL;
+       
+       c->version = 1; /* 0 is a reserved value here */
+       c->n_buckets = n_buckets;
+       c->hash_fn = hash_fn ? hash_fn : hash_zero;
+       c->cmp_fn = cmp_fn;
+       ast_atomic_fetchadd_int(&ao2.total_containers, 1);
+       
+       return c;
+}
+
+/*!
+ * return the number of elements in the container
+ */
+int ao2_container_count(ao2_container *c)
+{
+       return c->elements;
+}
+
+/*!
+ * A structure to create a linked list of entries,
+ * used within a bucket.
+ * XXX \todo this should be private to the container code
+ */
+struct bucket_list {
+       AST_LIST_ENTRY(bucket_list) entry;
+       int version;
+       struct astobj2 *astobj;         /* pointer to internal data */
+}; 
+
+/*
+ * link an object to a container
+ */
+void *ao2_link(ao2_container *c, void *user_data)
+{
+       int i;
+       /* create a new list entry */
+       struct bucket_list *p;
+       struct astobj2 *obj = INTERNAL_OBJ(user_data);
+       
+       if (!obj)
+               return NULL;
+
+       if (INTERNAL_OBJ(c) == NULL)
+               return NULL;
+
+       p = ast_calloc(1, sizeof(*p));
+       if (!p)
+               return NULL;
+
+       i = c->hash_fn(user_data, OBJ_POINTER);
+
+       ao2_lock(c);
+       i %= c->n_buckets;
+       p->astobj = obj;
+       p->version = ast_atomic_fetchadd_int(&c->version, 1);
+       AST_LIST_INSERT_HEAD(&c->buckets[i], p, entry);
+       ast_atomic_fetchadd_int(&c->elements, 1);
+       ao2_unlock(c);
+       
+       return p;
+}
+
+/*!
+ * \brief another convenience function is a callback that matches on address
+ */
+static int match_by_addr(void *user_data, void *arg, int flags)
+{
+       return (user_data == arg) ? (CMP_MATCH | CMP_STOP) : 0;
+}
+
+/*
+ * Unlink an object from the container
+ * and destroy the associated * ao2_bucket_list structure.
+ */
+void *ao2_unlink(ao2_container *c, void *user_data)
+{
+       if (INTERNAL_OBJ(user_data) == NULL)    /* safety check on the argument */
+               return NULL;
+
+       ao2_callback(c, OBJ_UNLINK | OBJ_POINTER | OBJ_NODATA, match_by_addr, user_data);
+
+       return NULL;
+}
+
+/*! 
+ * \brief special callback that matches all 
+ */ 
+static int cb_true(void *user_data, void *arg, int flags)
+{
+       return CMP_MATCH;
+}
+
+/*!
+ * Browse the container using different stategies accoding the flags.
+ * \return Is a pointer to an object or to a list of object if OBJ_MULTIPLE is 
+ * specified.
+ */
+void *ao2_callback(ao2_container *c,
+       const enum search_flags flags,
+       ao2_callback_fn cb_fn, void *arg)
+{
+       int i, last;    /* search boundaries */
+       void *ret = NULL;
+
+       if (INTERNAL_OBJ(c) == NULL)    /* safety check on the argument */
+               return NULL;
+
+       if ((flags & (OBJ_MULTIPLE | OBJ_NODATA)) == OBJ_MULTIPLE) {
+               ast_log(LOG_WARNING, "multiple data return not implemented yet (flags %x)\n", flags);
+               return NULL;
+       }
+
+       /* override the match function if necessary */
+#if 0
+       /* Removing this slightly changes the meaning of OBJ_POINTER, but makes it
+        * do what I want it to.  I'd like to hint to ao2_callback that the arg is
+        * of the same object type, so it can be passed to the hash function.
+        * However, I don't want to imply that this is the object being searched for. */
+       if (flags & OBJ_POINTER)
+               cb_fn = match_by_addr;
+       else
+#endif
+       if (cb_fn == NULL)      /* if NULL, match everything */
+               cb_fn = cb_true;
+       /*
+        * XXX this can be optimized.
+        * If we have a hash function and lookup by pointer,
+        * run the hash function. Otherwise, scan the whole container
+        * (this only for the time being. We need to optimize this.)
+        */
+       if ((flags & OBJ_POINTER))      /* we know hash can handle this case */
+               i = c->hash_fn(arg, flags & OBJ_POINTER) % c->n_buckets;
+       else                    /* don't know, let's scan all buckets */
+               i = -1;         /* XXX this must be fixed later. */
+
+       /* determine the search boundaries: i..last-1 */
+       if (i < 0) {
+               i = 0;
+               last = c->n_buckets;
+       } else {
+               last = i + 1;
+       }
+
+       ao2_lock(c);    /* avoid modifications to the content */
+
+       for (; i < last ; i++) {
+               /* scan the list with prev-cur pointers */
+               struct bucket_list *cur;
+
+               AST_LIST_TRAVERSE_SAFE_BEGIN(&c->buckets[i], cur, entry) {
+                       int match = cb_fn(EXTERNAL_OBJ(cur->astobj), arg, flags) & (CMP_MATCH | CMP_STOP);
+
+                       /* we found the object, performing operations according flags */
+                       if (match == 0) {       /* no match, no stop, continue */
+                               continue;
+                       } else if (match == CMP_STOP) { /* no match but stop, we are done */
+                               i = last;
+                               break;
+                       }
+                       /* we have a match (CMP_MATCH) here */
+                       if (!(flags & OBJ_NODATA)) {    /* if must return the object, record the value */
+                               /* it is important to handle this case before the unlink */
+                               ret = EXTERNAL_OBJ(cur->astobj);
+                               ao2_ref(ret, 1);
+                       }
+
+                       if (flags & OBJ_UNLINK) {       /* must unlink */
+                               struct bucket_list *x = cur;
+
+                               /* we are going to modify the container, so update version */
+                               ast_atomic_fetchadd_int(&c->version, 1);
+                               AST_LIST_REMOVE_CURRENT(&c->buckets[i], entry);
+                               /* update number of elements and version */
+                               ast_atomic_fetchadd_int(&c->elements, -1);
+                               ao2_ref(EXTERNAL_OBJ(x->astobj), -1);
+                               free(x);        /* free the link record */
+                       }
+
+                       if ((match & CMP_STOP) || (flags & OBJ_MULTIPLE) == 0) {
+                               /* We found the only match we need */
+                               i = last;       /* force exit from outer loop */
+                               break;
+                       }
+                       if (!(flags & OBJ_NODATA)) {
+#if 0  /* XXX to be completed */
+                               /*
+                                * This is the multiple-return case. We need to link
+                                * the object in a list. The refcount is already increased.
+                                */
+#endif
+                       }
+               }
+               AST_LIST_TRAVERSE_SAFE_END
+       }
+       ao2_unlock(c);
+       return ret;
+}
+
+/*!
+ * the find function just invokes the default callback with some reasonable flags.
+ */
+void *ao2_find(ao2_container *c, void *arg, enum search_flags flags)
+{
+       return ao2_callback(c, flags, c->cmp_fn, arg);
+}
+
+/*!
+ * initialize an iterator so we start from the first object
+ */
+ao2_iterator ao2_iterator_init(ao2_container *c, int flags)
+{
+       ao2_iterator a = {
+               .c = c,
+               .flags = flags
+       };
+       
+       return a;
+}
+
+/*
+ * move to the next element in the container.
+ */
+void * ao2_iterator_next(ao2_iterator *a)
+{
+       int lim;
+       struct bucket_list *p = NULL;
+
+       if (INTERNAL_OBJ(a->c) == NULL)
+               return NULL;
+
+       if (!(a->flags & F_AO2I_DONTLOCK))
+               ao2_lock(a->c);
+
+       /* optimization. If the container is unchanged and
+        * we have a pointer, try follow it
+        */
+       if (a->c->version == a->c_version && (p = a->obj) ) {
+               if ( (p = AST_LIST_NEXT(p, entry)) )
+                       goto found;
+               /* nope, start from the next bucket */
+               a->bucket++;
+               a->version = 0;
+               a->obj = NULL;
+       }
+
+       lim = a->c->n_buckets;
+
+       /* Browse the buckets array, moving to the next
+        * buckets if we don't find the entry in the current one.
+        * Stop when we find an element with version number greater
+        * than the current one (we reset the version to 0 when we
+        * switch buckets).
+        */
+       for (; a->bucket < lim; a->bucket++, a->version = 0) {
+               /* scan the current bucket */
+               AST_LIST_TRAVERSE(&a->c->buckets[a->bucket], p, entry) {
+                       if (p->version > a->version)
+                               goto found;
+               }
+       }
+
+found:
+       if (p) {
+               a->version = p->version;
+               a->obj = p;
+               a->c_version = a->c->version;
+               /* inc refcount of returned object */
+               ao2_ref(EXTERNAL_OBJ(p->astobj), 1);
+       }
+
+       if (!(a->flags & F_AO2I_DONTLOCK))
+               ao2_unlock(a->c);
+
+       return p ? EXTERNAL_OBJ(p->astobj) : NULL;
+}
+
+/* callback for destroying container.
+ * we can make it simple as we know what it does
+ */
+static int cd_cb(void *obj, void *arg, int flag)
+{
+       ao2_ref(obj, -1);
+       return 0;
+}
+       
+static void container_destruct(void *_c)
+{
+       ao2_container *c = _c;
+
+       ao2_callback(c, OBJ_UNLINK, cd_cb, NULL);
+       ast_atomic_fetchadd_int(&ao2.total_containers, -1);
+}
+
+static int print_cb(void *obj, void *arg, int flag)
+{
+       int *fd = arg;
+       char *s = (char *)obj;
+
+       ast_cli(*fd, "string <%s>\n", s);
+       return 0;
+}
+
+/*
+ * Print stats
+ */
+static int handle_astobj2_stats(int fd, int argc, char *argv[])
+{
+       ast_cli(fd, "Objects    : %d\n", ao2.total_objects);
+       ast_cli(fd, "Containers : %d\n", ao2.total_containers);
+       ast_cli(fd, "Memory     : %d\n", ao2.total_mem);
+       ast_cli(fd, "Locked     : %d\n", ao2.total_locked);
+       ast_cli(fd, "Refs       : %d\n", ao2.total_refs);
+       return 0;
+}
+
+/*
+ * This is testing code for astobj
+ */
+static int handle_astobj2_test(int fd, int argc, char *argv[])
+{
+       ao2_container *c1;
+       int i, lim;
+       char *obj;
+       static int prof_id = -1;
+
+       if (prof_id == -1)
+               prof_id = ast_add_profile("ao2_alloc", 0);
+
+       ast_cli(fd, "argc %d argv %s %s %s\n", argc, argv[0], argv[1], argv[2]);
+       lim = atoi(argv[2]);
+       ast_cli(fd, "called astobj_test\n");
+
+       handle_astobj2_stats(fd, 0, NULL);
+       /*
+        * allocate a container with no default callback, and no hash function.
+        * No hash means everything goes in the same bucket.
+        */
+       c1 = ao2_container_alloc(100, NULL /* no callback */, NULL /* no hash */);
+       ast_cli(fd, "container allocated as %p\n", c1);
+
+       /*
+        * fill the container with objects.
+        * ao2_alloc() gives us a reference which we pass to the
+        * container when we do the insert.
+        */
+       for (i = 0; i < lim; i++) {
+               ast_mark(prof_id, 1 /* start */);
+               obj = ao2_alloc(80, NULL);
+               ast_mark(prof_id, 0 /* stop */);
+               ast_cli(fd, "object %d allocated as %p\n", i, obj);
+               sprintf(obj, "-- this is obj %d --", i);
+               ao2_link(c1, obj);
+       }
+       ast_cli(fd, "testing callbacks\n");
+       ao2_callback(c1, 0, print_cb, &fd);
+
+       ast_cli(fd, "testing iterators, remove every second object\n");
+       {
+               ao2_iterator ai;
+               int x = 0;
+
+               ai = ao2_iterator_init(c1, 0);
+               while ( (obj = ao2_iterator_next(&ai)) ) {
+                       ast_cli(fd, "iterator on <%s>\n", obj);
+                       if (x++ & 1)
+                               ao2_unlink(c1, obj);
+                       ao2_ref(obj, -1);
+               }
+               ast_cli(fd, "testing iterators again\n");
+               ai = ao2_iterator_init(c1, 0);
+               while ( (obj = ao2_iterator_next(&ai)) ) {
+                       ast_cli(fd, "iterator on <%s>\n", obj);
+                       ao2_ref(obj, -1);
+               }
+       }
+       ast_cli(fd, "testing callbacks again\n");
+       ao2_callback(c1, 0, print_cb, &fd);
+
+       ast_verbose("now you should see an error message:\n");
+       ao2_ref(&i, -1);        /* i is not a valid object so we print an error here */
+
+       ast_cli(fd, "destroy container\n");
+       ao2_ref(c1, -1);        /* destroy container */
+       handle_astobj2_stats(fd, 0, NULL);
+       return 0;
+}
+
+static struct ast_cli_entry cli_astobj2[] = {
+       { { "astobj2", "stats", NULL },
+       handle_astobj2_stats, "Print astobj2 statistics", },
+       { { "astobj2", "test", NULL } , handle_astobj2_test, "Test astobj2", },
+};
+
+int astobj2_init(void);
+int astobj2_init(void)
+{
+       ast_cli_register_multiple(cli_astobj2, ARRAY_LEN(cli_astobj2));
+       return 0;
+}