aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--channels/chan_iax2.c787
-rw-r--r--include/asterisk/astobj2.h515
-rw-r--r--include/asterisk/strings.h20
-rw-r--r--main/Makefile3
-rw-r--r--main/astobj2.c685
5 files changed, 1650 insertions, 360 deletions
diff --git a/channels/chan_iax2.c b/channels/chan_iax2.c
index e9e2e7465..07917f8c4 100644
--- a/channels/chan_iax2.c
+++ b/channels/chan_iax2.c
@@ -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
index 000000000..73c8ede99
--- /dev/null
+++ b/include/asterisk/astobj2.h
@@ -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 */
diff --git a/include/asterisk/strings.h b/include/asterisk/strings.h
index 76b58d715..d542ef8d8 100644
--- a/include/asterisk/strings.h
+++ b/include/asterisk/strings.h
@@ -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 */
diff --git a/main/Makefile b/main/Makefile
index ba780b281..fb807488b 100644
--- a/main/Makefile
+++ b/main/Makefile
@@ -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
index 000000000..9d2b0af2c
--- /dev/null
+++ b/main/astobj2.c
@@ -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;
+}