diff options
-rw-r--r-- | apps/app_directed_pickup.c | 18 | ||||
-rw-r--r-- | funcs/func_channel.c | 56 | ||||
-rw-r--r-- | include/asterisk/channel.h | 17 | ||||
-rw-r--r-- | include/asterisk/features.h | 10 | ||||
-rw-r--r-- | main/channel.c | 111 | ||||
-rw-r--r-- | main/features.c | 136 |
6 files changed, 202 insertions, 146 deletions
diff --git a/apps/app_directed_pickup.c b/apps/app_directed_pickup.c index 9ae3fc842..6fcde0748 100644 --- a/apps/app_directed_pickup.c +++ b/apps/app_directed_pickup.c @@ -252,29 +252,13 @@ static int pickup_by_mark(struct ast_channel *chan, const char *mark) return res; } -static int find_channel_by_group(void *obj, void *arg, void *data, int flags) -{ - struct ast_channel *target = obj;/*!< Potential pickup target */ - struct ast_channel *chan = data;/*!< Channel wanting to pickup call */ - - ast_channel_lock(target); - if (chan != target && (ast_channel_pickupgroup(chan) & ast_channel_callgroup(target)) - && ast_can_pickup(target)) { - /* Return with the channel still locked on purpose */ - return CMP_MATCH | CMP_STOP; - } - ast_channel_unlock(target); - - return 0; -} - static int pickup_by_group(struct ast_channel *chan) { struct ast_channel *target;/*!< Potential pickup target */ int res = -1; /* The found channel is already locked. */ - target = ast_channel_callback(find_channel_by_group, NULL, chan, 0); + target = ast_pickup_find_by_group(chan); if (target) { ast_log(LOG_NOTICE, "pickup %s attempt by %s\n", ast_channel_name(target), ast_channel_name(chan)); res = ast_do_pickup(chan, target); diff --git a/funcs/func_channel.c b/funcs/func_channel.c index bd3367df2..79e2b6f5f 100644 --- a/funcs/func_channel.c +++ b/funcs/func_channel.c @@ -87,10 +87,16 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$") <para>R/O format currently being written.</para> </enum> <enum name="callgroup"> - <para>R/W call groups for call pickup.</para> + <para>R/W numeric call pickup groups that this channel is a member.</para> </enum> <enum name="pickupgroup"> - <para>R/W call groups for call pickup.</para> + <para>R/W numeric call pickup groups this channel can pickup.</para> + </enum> + <enum name="namedcallgroup"> + <para>R/W named call pickup groups that this channel is a member.</para> + </enum> + <enum name="namedpickupgroup"> + <para>R/W named call pickup groups this channel can pickup.</para> </enum> <enum name="channeltype"> <para>R/O technology used for channel.</para> @@ -346,15 +352,18 @@ static int func_channel_read(struct ast_channel *chan, const char *function, char *data, char *buf, size_t len) { int ret = 0; - char tmp[512]; struct ast_format_cap *tmpcap; if (!strcasecmp(data, "audionativeformat")) { + char tmp[512]; + if ((tmpcap = ast_format_cap_get_type(ast_channel_nativeformats(chan), AST_FORMAT_TYPE_AUDIO))) { ast_copy_string(buf, ast_getformatname_multiple(tmp, sizeof(tmp), tmpcap), len); tmpcap = ast_format_cap_destroy(tmpcap); } } else if (!strcasecmp(data, "videonativeformat")) { + char tmp[512]; + if ((tmpcap = ast_format_cap_get_type(ast_channel_nativeformats(chan), AST_FORMAT_TYPE_VIDEO))) { ast_copy_string(buf, ast_getformatname_multiple(tmp, sizeof(tmp), tmpcap), len); tmpcap = ast_format_cap_destroy(tmpcap); @@ -417,6 +426,7 @@ static int func_channel_read(struct ast_channel *chan, const char *function, ast_channel_unlock(chan); } else if (!strcasecmp(data, "peer")) { struct ast_channel *p; + ast_channel_lock(chan); p = ast_bridged_channel(chan); if (p || ast_channel_tech(chan) || ast_channel_cdr(chan)) /* dummy channel? if so, we hid the peer name in the language */ @@ -437,16 +447,27 @@ static int func_channel_read(struct ast_channel *chan, const char *function, locked_copy_string(chan, buf, transfercapability_table[ast_channel_transfercapability(chan) & 0x1f], len); } else if (!strcasecmp(data, "callgroup")) { char groupbuf[256]; + locked_copy_string(chan, buf, ast_print_group(groupbuf, sizeof(groupbuf), ast_channel_callgroup(chan)), len); } else if (!strcasecmp(data, "pickupgroup")) { char groupbuf[256]; + locked_copy_string(chan, buf, ast_print_group(groupbuf, sizeof(groupbuf), ast_channel_pickupgroup(chan)), len); + } else if (!strcasecmp(data, "namedcallgroup")) { + struct ast_str *tmp_str = ast_str_alloca(1024); + + locked_copy_string(chan, buf, ast_print_namedgroups(&tmp_str, ast_channel_named_callgroups(chan)), len); + } else if (!strcasecmp(data, "namedpickupgroup")) { + struct ast_str *tmp_str = ast_str_alloca(1024); + + locked_copy_string(chan, buf, ast_print_namedgroups(&tmp_str, ast_channel_named_pickupgroups(chan)), len); } else if (!strcasecmp(data, "amaflags")) { - char amabuf[256]; - snprintf(amabuf,sizeof(amabuf), "%d", ast_channel_amaflags(chan)); - locked_copy_string(chan, buf, amabuf, len); + ast_channel_lock(chan); + snprintf(buf, len, "%d", ast_channel_amaflags(chan)); + ast_channel_unlock(chan); } else if (!strncasecmp(data, "secure_bridge_", 14)) { struct ast_datastore *ds; + ast_channel_lock(chan); if ((ds = ast_channel_datastore_find(chan, &secure_call_info, NULL))) { struct ast_secure_call_store *encrypt = ds->data; @@ -529,9 +550,27 @@ static int func_channel_write_real(struct ast_channel *chan, const char *functio new_zone = ast_tone_zone_unref(new_zone); } } else if (!strcasecmp(data, "callgroup")) { + ast_channel_lock(chan); ast_channel_callgroup_set(chan, ast_get_group(value)); + ast_channel_unlock(chan); } else if (!strcasecmp(data, "pickupgroup")) { + ast_channel_lock(chan); ast_channel_pickupgroup_set(chan, ast_get_group(value)); + ast_channel_unlock(chan); + } else if (!strcasecmp(data, "namedcallgroup")) { + struct ast_namedgroups *groups = ast_get_namedgroups(value); + + ast_channel_lock(chan); + ast_channel_named_callgroups_set(chan, groups); + ast_channel_unlock(chan); + ast_unref_namedgroups(groups); + } else if (!strcasecmp(data, "namedpickupgroup")) { + struct ast_namedgroups *groups = ast_get_namedgroups(value); + + ast_channel_lock(chan); + ast_channel_named_pickupgroups_set(chan, groups); + ast_channel_unlock(chan); + ast_unref_namedgroups(groups); } else if (!strcasecmp(data, "txgain")) { sscanf(value, "%4hhd", &gainset); ast_channel_setoption(chan, AST_OPTION_TXGAIN, &gainset, sizeof(gainset), 0); @@ -540,12 +579,15 @@ static int func_channel_write_real(struct ast_channel *chan, const char *functio ast_channel_setoption(chan, AST_OPTION_RXGAIN, &gainset, sizeof(gainset), 0); } else if (!strcasecmp(data, "transfercapability")) { unsigned short i; + + ast_channel_lock(chan); for (i = 0; i < 0x20; i++) { if (!strcasecmp(transfercapability_table[i], value) && strcmp(value, "UNK")) { ast_channel_transfercapability_set(chan, i); break; } } + ast_channel_unlock(chan); } else if (!strcasecmp(data, "hangup_handler_pop")) { /* Pop one hangup handler before pushing the new handler. */ ast_pbx_hangup_handler_pop(chan); @@ -581,13 +623,13 @@ static int func_channel_write_real(struct ast_channel *chan, const char *functio } else { store = ds->data; } - ast_channel_unlock(chan); if (!strcasecmp(data, "secure_bridge_signaling")) { store->signaling = ast_true(value) ? 1 : 0; } else if (!strcasecmp(data, "secure_bridge_media")) { store->media = ast_true(value) ? 1 : 0; } + ast_channel_unlock(chan); } else if (!ast_channel_tech(chan)->func_channel_write || ast_channel_tech(chan)->func_channel_write(chan, function, data, value)) { ast_log(LOG_WARNING, "Unknown or unavailable item requested: '%s'\n", diff --git a/include/asterisk/channel.h b/include/asterisk/channel.h index 67e1d4c7c..b60c8ad00 100644 --- a/include/asterisk/channel.h +++ b/include/asterisk/channel.h @@ -999,17 +999,6 @@ enum channelreloadreason { CHANNEL_ACL_RELOAD, }; - -/*! \brief Structure to handle ao2-container for named groups */ -struct namedgroup_entry { - /*! string representation of group */ - char *name; - - /*! pre-built hash of groupname string */ - unsigned int hash; -}; - - /*! * \note None of the datastore API calls lock the ast_channel they are using. * So, the channel should be locked before calling the functions that @@ -2454,20 +2443,20 @@ static inline enum ast_t38_state ast_channel_get_t38_state(struct ast_channel *c ast_group_t ast_get_group(const char *s); -/*! \brief Print call- and pickup groups into buffer */ +/*! \brief Print call and pickup groups into buffer */ char *ast_print_group(char *buf, int buflen, ast_group_t group); /*! \brief Opaque struct holding a namedgroups set, i.e. a set of group names */ struct ast_namedgroups; -/*! \brief Create an ast_namedgroups set with group name from comma separated string s */ +/*! \brief Create an ast_namedgroups set with group names from comma separated string */ struct ast_namedgroups *ast_get_namedgroups(const char *s); struct ast_namedgroups *ast_unref_namedgroups(struct ast_namedgroups *groups); struct ast_namedgroups *ast_ref_namedgroups(struct ast_namedgroups *groups); /*! \brief Return TRUE if group a and b contain at least one common groupname */ int ast_namedgroups_intersect(struct ast_namedgroups *a, struct ast_namedgroups *b); -/*! \brief Print named call groups and named pickup groups ---*/ +/*! \brief Print named call groups and named pickup groups */ char *ast_print_namedgroups(struct ast_str **buf, struct ast_namedgroups *groups); /*! diff --git a/include/asterisk/features.h b/include/asterisk/features.h index 42dc57fba..1619d54c4 100644 --- a/include/asterisk/features.h +++ b/include/asterisk/features.h @@ -183,6 +183,16 @@ int ast_bridge_call(struct ast_channel *chan, struct ast_channel *peer,struct as */ int ast_can_pickup(struct ast_channel *chan); +/*! + * \brief Find a pickup channel target by group. + * + * \param chan channel that initiated pickup. + * + * \retval target on success. The returned channel is locked and reffed. + * \retval NULL on error. + */ +struct ast_channel *ast_pickup_find_by_group(struct ast_channel *chan); + /*! \brief Pickup a call */ int ast_pickup_call(struct ast_channel *chan); diff --git a/main/channel.c b/main/channel.c index b25484598..dbc4703b5 100644 --- a/main/channel.c +++ b/main/channel.c @@ -8220,97 +8220,86 @@ ast_group_t ast_get_group(const char *s) return group; } +/*! \brief Named group member structure */ +struct namedgroup_member { + /*! Pre-built hash of group member name. */ + unsigned int hash; + /*! Group member name. (End allocation of name string.) */ + char name[1]; +}; + /*! \brief Comparison function used for named group container */ static int namedgroup_cmp_cb(void *obj, void *arg, int flags) { - const struct namedgroup_entry *an = obj; - const struct namedgroup_entry *bn = arg; + const struct namedgroup_member *an = obj; + const struct namedgroup_member *bn = arg; + return strcmp(an->name, bn->name) ? 0 : CMP_MATCH | CMP_STOP; } /*! \brief Hashing function used for named group container */ static int namedgroup_hash_cb(const void *obj, const int flags) { - const struct namedgroup_entry *entry = obj; - return entry->hash; -} - -static void namedgroup_dtor(void *obj) -{ - ast_free(((struct namedgroup_entry*)obj)->name); -} + const struct namedgroup_member *member = obj; -/*! \internal \brief Actually a refcounted ao2_object. An indirect ao2_container to hide the implementation of namedgroups. */ -struct ast_namedgroups { - struct ao2_container *container; -}; - -static void ast_namedgroups_dtor(void *obj) -{ - ao2_cleanup(((struct ast_namedgroups *)obj)->container); + return member->hash; } struct ast_namedgroups *ast_get_namedgroups(const char *s) { - struct ast_namedgroups *namedgroups; + struct ao2_container *namedgroups; char *piece; char *c; - if (ast_strlen_zero(s)) { + if (!s) { return NULL; } - c = ast_strdupa(s); - if (!c) { + + /*! \brief Remove leading and trailing whitespace */ + c = ast_trim_blanks(ast_strdupa(ast_skip_blanks(s))); + if (ast_strlen_zero(c)) { return NULL; } - namedgroups = ao2_alloc_options(sizeof(*namedgroups), ast_namedgroups_dtor, AO2_ALLOC_OPT_LOCK_NOLOCK); + namedgroups = ao2_container_alloc_options(AO2_ALLOC_OPT_LOCK_NOLOCK, 19, + namedgroup_hash_cb, namedgroup_cmp_cb); if (!namedgroups) { return NULL; } - namedgroups->container = ao2_container_alloc_options(AO2_ALLOC_OPT_LOCK_NOLOCK, 19, namedgroup_hash_cb, namedgroup_cmp_cb); - if (!namedgroups->container) { - ao2_ref(namedgroups, -1); - return NULL; - } - - /*! \brief Remove leading and trailing whitespace */ - c = ast_strip(c); while ((piece = strsep(&c, ","))) { - struct namedgroup_entry *entry; + struct namedgroup_member *member; + size_t len; /* remove leading/trailing whitespace */ piece = ast_strip(piece); - if (strlen(piece) == 0) { - ast_log(LOG_ERROR, "Syntax error parsing named group configuration '%s' at '%s'. Ignoring.\n", s, piece); + + len = strlen(piece); + if (!len) { continue; } - entry = ao2_alloc_options(sizeof(*entry), namedgroup_dtor, AO2_ALLOC_OPT_LOCK_NOLOCK); - if (!entry) { - ao2_ref(namedgroups, -1); - return NULL; - } - entry->name = ast_strdup(piece); - if (!entry->name) { - ao2_ref(entry, -1); + member = ao2_alloc_options(sizeof(*member) + len, NULL, AO2_ALLOC_OPT_LOCK_NOLOCK); + if (!member) { ao2_ref(namedgroups, -1); return NULL; } - entry->hash = ast_str_hash(entry->name); + strcpy(member->name, piece);/* Safe */ + member->hash = ast_str_hash(member->name); + /* every group name may exist only once, delete duplicates */ - ao2_find(namedgroups->container, entry, OBJ_POINTER | OBJ_UNLINK | OBJ_NODATA); - ao2_link(namedgroups->container, entry); - ao2_ref(entry, -1); + ao2_find(namedgroups, member, OBJ_POINTER | OBJ_UNLINK | OBJ_NODATA); + ao2_link(namedgroups, member); + ao2_ref(member, -1); } - if (ao2_container_count(namedgroups->container) == 0) { + if (!ao2_container_count(namedgroups)) { + /* There were no group names specified. */ ao2_ref(namedgroups, -1); namedgroups = NULL; } - return namedgroups; + return (struct ast_namedgroups *) namedgroups; } struct ast_namedgroups *ast_unref_namedgroups(struct ast_namedgroups *groups) @@ -8560,26 +8549,24 @@ char *ast_print_group(char *buf, int buflen, ast_group_t group) return buf; } -/*! \brief Print named call group and named pickup group ---*/ char *ast_print_namedgroups(struct ast_str **buf, struct ast_namedgroups *group) { - struct namedgroup_entry *ng; + struct ao2_container *grp = (struct ao2_container *) group; + struct namedgroup_member *ng; int first = 1; struct ao2_iterator it; - if (group == NULL) { + if (!grp) { return ast_str_buffer(*buf); } - it = ao2_iterator_init(group->container, 0); - while ((ng = ao2_iterator_next(&it))) { + for (it = ao2_iterator_init(grp, 0); (ng = ao2_iterator_next(&it)); ao2_ref(ng, -1)) { if (!first) { ast_str_append(buf, 0, ", "); } else { first = 0; } ast_str_append(buf, 0, "%s", ng->name); - ao2_ref(ng, -1); } ao2_iterator_destroy(&it); @@ -8598,16 +8585,24 @@ static int namedgroup_match(void *obj, void *arg, int flags) int ast_namedgroups_intersect(struct ast_namedgroups *a, struct ast_namedgroups *b) { - /* - * Do a and b intersect? Since b is hash table average time complexity is O(|a|) - */ void *match; + struct ao2_container *group_a = (struct ao2_container *) a; + struct ao2_container *group_b = (struct ao2_container *) b; if (!a || !b) { return 0; } - match = ao2_callback(a->container, 0, namedgroup_match, b->container); + /* + * Do groups a and b intersect? Since a and b are hash tables, + * the average time complexity is: + * O(a.count <= b.count ? a.count : b.count) + */ + if (ao2_container_count(group_b) < ao2_container_count(group_a)) { + /* Traverse over the smaller group. */ + SWAP(group_a, group_b); + } + match = ao2_callback(group_a, 0, namedgroup_match, group_b); ao2_cleanup(match); return match != NULL; diff --git a/main/features.c b/main/features.c index 0650129c0..f89ca816d 100644 --- a/main/features.c +++ b/main/features.c @@ -7707,80 +7707,117 @@ int ast_can_pickup(struct ast_channel *chan) static int find_channel_by_group(void *obj, void *arg, void *data, int flags) { struct ast_channel *target = obj;/*!< Potential pickup target */ - struct ast_channel *chan = data;/*!< Channel wanting to pickup call */ + struct ast_channel *chan = arg;/*!< Channel wanting to pickup call */ + + if (chan == target) { + return 0; + } ast_channel_lock(target); + if (ast_can_pickup(target)) { + /* Lock both channels. */ + while (ast_channel_trylock(chan)) { + ast_channel_unlock(target); + sched_yield(); + ast_channel_lock(target); + } - /* - * Both, callgroup and namedcallgroup pickup variants are matched independently. - * Checking for named group match is done last since it's a rather expensive operation. - */ - if (chan != target && ast_can_pickup(target) - && ((ast_channel_pickupgroup(chan) & ast_channel_callgroup(target)) - || (ast_namedgroups_intersect(ast_channel_named_pickupgroups(chan), ast_channel_named_callgroups(target))))) { /* - * Return with the channel unlocked on purpose, else we would lock many channels with the chance for deadlock + * Both callgroup and namedcallgroup pickup variants are + * matched independently. Checking for named group match is + * done last since it's a more expensive operation. */ - ast_channel_unlock(target); - return CMP_MATCH; + if ((ast_channel_pickupgroup(chan) & ast_channel_callgroup(target)) + || (ast_namedgroups_intersect(ast_channel_named_pickupgroups(chan), + ast_channel_named_callgroups(target)))) { + struct ao2_container *candidates = data;/*!< Candidate channels found. */ + + /* This is a candidate to pickup */ + ao2_link(candidates, target); + } + ast_channel_unlock(chan); } ast_channel_unlock(target); return 0; } -/*! - * \brief Pickup a call - * \param chan channel that initiated pickup. - * - * Walk list of channels, checking it is not itself, channel is pbx one, - * check that the callgroup for both channels are the same and the channel is ringing. - * Answer calling channel, flag channel as answered on queue, masq channels together. - */ -int ast_pickup_call(struct ast_channel *chan) +struct ast_channel *ast_pickup_find_by_group(struct ast_channel *chan) { + struct ao2_container *candidates;/*!< Candidate channels found to pickup. */ struct ast_channel *target;/*!< Potential pickup target */ - struct ao2_iterator *targets_it;/*!< Potential pickup targets, must find the oldest of them */ - struct ast_channel *candidate;/*!< Potential new older target */ - int res = -1; - ast_debug(1, "pickup attempt by %s\n", ast_channel_name(chan)); - /* - * Transfer all pickup-able channels to another container-iterator. - * Iterate it to find the oldest channel. - */ - targets_it = (struct ao2_iterator *) ast_channel_callback(find_channel_by_group, - NULL, chan, OBJ_MULTIPLE); - if (!targets_it) { - /* Search really failed. */ - goto no_pickup_calls; + candidates = ao2_container_alloc_options(AO2_ALLOC_OPT_LOCK_NOLOCK, 1, NULL, NULL); + if (!candidates) { + return NULL; } + /* Find all candidate targets by group. */ + ast_channel_callback(find_channel_by_group, chan, candidates, 0); + + /* Find the oldest pickup target candidate */ target = NULL; - while ((candidate = ao2_iterator_next(targets_it))) { - if (!target) { - target = candidate; - continue; + for (;;) { + struct ast_channel *candidate;/*!< Potential new older target */ + struct ao2_iterator iter; + + iter = ao2_iterator_init(candidates, 0); + while ((candidate = ao2_iterator_next(&iter))) { + if (!target) { + /* First target. */ + target = candidate; + continue; + } + if (ast_tvcmp(ast_channel_creationtime(candidate), ast_channel_creationtime(target)) < 0) { + /* We have a new target. */ + ast_channel_unref(target); + target = candidate; + continue; + } + ast_channel_unref(candidate); } - if (ast_tvcmp(ast_channel_creationtime(candidate), ast_channel_creationtime(target)) < 0) { - ast_channel_unref(target); - target = candidate; - continue; + ao2_iterator_destroy(&iter); + if (!target) { + /* No candidates found. */ + break; } - ast_channel_unref(candidate); - } - ao2_iterator_destroy(targets_it); - if (target) { + /* The found channel must be locked and ref'd. */ ast_channel_lock(target); + /* Recheck pickup ability */ - if (!ast_can_pickup(target)) { - /* Someone else picked it up or the call went away. */ - ast_channel_unlock(target); - target = ast_channel_unref(target); + if (ast_can_pickup(target)) { + /* This is the channel to pickup. */ + break; } + + /* Someone else picked it up or the call went away. */ + ast_channel_unlock(target); + ao2_unlink(candidates, target); + target = ast_channel_unref(target); } + ao2_ref(candidates, -1); + + return target; +} + +/*! + * \brief Pickup a call + * \param chan channel that initiated pickup. + * + * Walk list of channels, checking it is not itself, channel is pbx one, + * check that the callgroup for both channels are the same and the channel is ringing. + * Answer calling channel, flag channel as answered on queue, masq channels together. + */ +int ast_pickup_call(struct ast_channel *chan) +{ + struct ast_channel *target;/*!< Potential pickup target */ + int res = -1; + + ast_debug(1, "pickup attempt by %s\n", ast_channel_name(chan)); + /* The found channel is already locked. */ + target = ast_pickup_find_by_group(chan); if (target) { ast_log(LOG_NOTICE, "pickup %s attempt by %s\n", ast_channel_name(target), ast_channel_name(chan)); @@ -7796,7 +7833,6 @@ int ast_pickup_call(struct ast_channel *chan) target = ast_channel_unref(target); } -no_pickup_calls: if (res < 0) { ast_debug(1, "No call pickup possible... for %s\n", ast_channel_name(chan)); if (!ast_strlen_zero(pickupfailsound)) { |