mirror of
https://github.com/asterisk/asterisk.git
synced 2025-10-12 15:45:18 +00:00
pbx: Add support for autohints.
This change introduces the concept of autohints. These are hints which are created as a result of device state changes occurring within the core. When this happens a hint will be created (if it does not exist already) using the device name as the extension. For example if a device state change is received for "PJSIP/bob" and autohints are enabled on a context then a hint will exist in that context for "bob" with a device of "PJSIP/bob". For virtual or custom device states the name after the type will be used. For example if the device state of "Custom:bob" changes then a hint will exist in that context for "bob" with a device of "Custom:bob". This functionality can be enabled in extensions.conf by placing "autohints=yes" in a context. ASTERISK-25881 #close Change-Id: I7e444c7da41b7b7d33374420fec658beeb18584e
This commit is contained in:
5
CHANGES
5
CHANGES
@@ -157,6 +157,11 @@ Core
|
||||
- 'media cache delete <item>' - remove an item from the cache
|
||||
- 'media cache create <uri>' - retrieve a URI and store it in the cache
|
||||
|
||||
* The ability for hints to be automatically created as a result of device state
|
||||
changes now exists in the PBX. This functionality is referred to as "autohints"
|
||||
and is configurable in extensions.conf by placing "autohints=yes" in the
|
||||
context. If enabled then a hint will be automatically created with the name of
|
||||
the device.
|
||||
|
||||
Functions
|
||||
------------------
|
||||
|
@@ -287,6 +287,15 @@ int pbx_exec(struct ast_channel *c, struct ast_app *app, const char *data);
|
||||
*/
|
||||
struct ast_context *ast_context_find_or_create(struct ast_context **extcontexts, struct ast_hashtab *exttable, const char *name, const char *registrar);
|
||||
|
||||
/*!
|
||||
* \brief Enable or disable autohints support on a context
|
||||
*
|
||||
* \param con pointer to the context
|
||||
* \param enabled whether autohints are enabled
|
||||
*
|
||||
*/
|
||||
void ast_context_set_autohints(struct ast_context *con, int enabled);
|
||||
|
||||
/*!
|
||||
* \brief Merge the temporary contexts into a global contexts list and delete from the
|
||||
* global list the ones that are being added
|
||||
|
213
main/pbx.c
213
main/pbx.c
@@ -306,7 +306,7 @@ struct scoreboard /* make sure all fields are 0 before calling new_find_extensi
|
||||
struct ast_exten *exten;
|
||||
};
|
||||
|
||||
/*! \brief ast_context: An extension context */
|
||||
/*! \brief ast_context: An extension context - must remain in sync with fake_context */
|
||||
struct ast_context {
|
||||
ast_rwlock_t lock; /*!< A lock to prevent multiple threads from clobbering the context */
|
||||
struct ast_exten *root; /*!< The root of the list of extensions */
|
||||
@@ -317,6 +317,7 @@ struct ast_context {
|
||||
struct ast_ignorepat *ignorepats; /*!< Patterns for which to continue playing dialtone */
|
||||
char *registrar; /*!< Registrar -- make sure you malloc this, as the registrar may have to survive module unloads */
|
||||
int refcount; /*!< each module that would have created this context should inc/dec this as appropriate */
|
||||
int autohints; /*!< Whether autohints support is enabled or not */
|
||||
AST_LIST_HEAD_NOLOCK(, ast_sw) alts; /*!< Alternative switches */
|
||||
ast_mutex_t macrolock; /*!< A lock to implement "exclusive" macros - held whilst a call is executing in the macro */
|
||||
char name[0]; /*!< Name of the context */
|
||||
@@ -400,6 +401,19 @@ struct ast_hintdevice {
|
||||
char hintdevice[1];
|
||||
};
|
||||
|
||||
/*! \brief Container for autohint contexts */
|
||||
static struct ao2_container *autohints;
|
||||
|
||||
/*!
|
||||
* \brief Structure for dial plan autohints
|
||||
*/
|
||||
struct ast_autohint {
|
||||
/*! \brief Name of the registrar */
|
||||
char *registrar;
|
||||
/*! \brief Name of the context */
|
||||
char context[1];
|
||||
};
|
||||
|
||||
/*!
|
||||
* \note Using the device for hash
|
||||
*/
|
||||
@@ -423,6 +437,7 @@ static int hintdevice_hash_cb(const void *obj, const int flags)
|
||||
|
||||
return ast_str_case_hash(key);
|
||||
}
|
||||
|
||||
/*!
|
||||
* \note Devices on hints are not unique so no CMP_STOP is returned
|
||||
* Dont use ao2_find against hintdevices container cause there always
|
||||
@@ -457,6 +472,59 @@ static int hintdevice_cmp_multiple(void *obj, void *arg, int flags)
|
||||
return cmp ? 0 : CMP_MATCH;
|
||||
}
|
||||
|
||||
/*!
|
||||
* \note Using the context name for hash
|
||||
*/
|
||||
static int autohint_hash_cb(const void *obj, const int flags)
|
||||
{
|
||||
const struct ast_autohint *autohint;
|
||||
const char *key;
|
||||
|
||||
switch (flags & OBJ_SEARCH_MASK) {
|
||||
case OBJ_SEARCH_KEY:
|
||||
key = obj;
|
||||
break;
|
||||
case OBJ_SEARCH_OBJECT:
|
||||
autohint = obj;
|
||||
key = autohint->context;
|
||||
break;
|
||||
default:
|
||||
ast_assert(0);
|
||||
return 0;
|
||||
}
|
||||
|
||||
return ast_str_case_hash(key);
|
||||
}
|
||||
|
||||
static int autohint_cmp(void *obj, void *arg, int flags)
|
||||
{
|
||||
struct ast_autohint *left = obj;
|
||||
struct ast_autohint *right = arg;
|
||||
const char *right_key = arg;
|
||||
int cmp;
|
||||
|
||||
switch (flags & OBJ_SEARCH_MASK) {
|
||||
case OBJ_SEARCH_OBJECT:
|
||||
right_key = right->context;
|
||||
/* Fall through */
|
||||
case OBJ_SEARCH_KEY:
|
||||
cmp = strcasecmp(left->context, right_key);
|
||||
break;
|
||||
case OBJ_SEARCH_PARTIAL_KEY:
|
||||
/*
|
||||
* We could also use a partial key struct containing a length
|
||||
* so strlen() does not get called for every comparison instead.
|
||||
*/
|
||||
cmp = strncmp(left->context, right_key, strlen(right_key));
|
||||
break;
|
||||
default:
|
||||
ast_assert(0);
|
||||
cmp = 0;
|
||||
break;
|
||||
}
|
||||
return cmp ? 0 : CMP_MATCH | CMP_STOP;
|
||||
}
|
||||
|
||||
/*! \internal \brief \c ao2_callback function to remove hintdevices */
|
||||
static int hintdevice_remove_cb(void *obj, void *arg, void *data, int flags)
|
||||
{
|
||||
@@ -2323,6 +2391,7 @@ int ast_extension_close(const char *pattern, const char *data, int needmore)
|
||||
return extension_match_core(pattern, data, needmore);
|
||||
}
|
||||
|
||||
/* This structure must remain in sync with ast_context for proper hashtab matching */
|
||||
struct fake_context /* this struct is purely for matching in the hashtab */
|
||||
{
|
||||
ast_rwlock_t lock;
|
||||
@@ -2334,6 +2403,7 @@ struct fake_context /* this struct is purely for matching in the hashtab */
|
||||
struct ast_ignorepat *ignorepats;
|
||||
const char *registrar;
|
||||
int refcount;
|
||||
int autohints;
|
||||
AST_LIST_HEAD_NOLOCK(, ast_sw) alts;
|
||||
ast_mutex_t macrolock;
|
||||
char name[256];
|
||||
@@ -3460,6 +3530,11 @@ static void device_state_cb(void *unused, struct stasis_subscription *sub, struc
|
||||
struct ast_hintdevice *device;
|
||||
struct ast_hintdevice *cmpdevice;
|
||||
struct ao2_iterator *dev_iter;
|
||||
struct ao2_iterator auto_iter;
|
||||
struct ast_autohint *autohint;
|
||||
char *virtual_device;
|
||||
char *type;
|
||||
char *device_name;
|
||||
|
||||
if (handle_hint_change_message_type(msg, AST_HINT_UPDATE_DEVICE)) {
|
||||
return;
|
||||
@@ -3475,7 +3550,7 @@ static void device_state_cb(void *unused, struct stasis_subscription *sub, struc
|
||||
return;
|
||||
}
|
||||
|
||||
if (ao2_container_count(hintdevices) == 0) {
|
||||
if (ao2_container_count(hintdevices) == 0 && ao2_container_count(autohints) == 0) {
|
||||
/* There are no hints monitoring devices. */
|
||||
return;
|
||||
}
|
||||
@@ -3489,25 +3564,61 @@ static void device_state_cb(void *unused, struct stasis_subscription *sub, struc
|
||||
strcpy(cmpdevice->hintdevice, dev_state->device);
|
||||
|
||||
ast_mutex_lock(&context_merge_lock);/* Hold off ast_merge_contexts_and_delete */
|
||||
|
||||
/* Initially we find all hints for the device and notify them */
|
||||
dev_iter = ao2_t_callback(hintdevices,
|
||||
OBJ_SEARCH_OBJECT | OBJ_MULTIPLE,
|
||||
hintdevice_cmp_multiple,
|
||||
cmpdevice,
|
||||
"find devices in container");
|
||||
if (!dev_iter) {
|
||||
ast_mutex_unlock(&context_merge_lock);
|
||||
ast_free(hint_app);
|
||||
return;
|
||||
}
|
||||
|
||||
if (dev_iter) {
|
||||
for (; (device = ao2_iterator_next(dev_iter)); ao2_t_ref(device, -1, "Next device")) {
|
||||
if (device->hint) {
|
||||
device_state_notify_callbacks(device->hint, &hint_app);
|
||||
}
|
||||
}
|
||||
ast_mutex_unlock(&context_merge_lock);
|
||||
|
||||
ao2_iterator_destroy(dev_iter);
|
||||
}
|
||||
|
||||
/* Second stage we look for any autohint contexts and if the device is not already in the hints
|
||||
* we create it.
|
||||
*/
|
||||
type = ast_strdupa(dev_state->device);
|
||||
if (ast_strlen_zero(type)) {
|
||||
goto end;
|
||||
}
|
||||
|
||||
/* Determine if this is a virtual/custom device or a real device */
|
||||
virtual_device = strchr(type, ':');
|
||||
device_name = strchr(type, '/');
|
||||
if (virtual_device && (!device_name || (virtual_device < device_name))) {
|
||||
device_name = virtual_device;
|
||||
}
|
||||
|
||||
/* Invalid device state name - not a virtual/custom device and not a real device */
|
||||
if (ast_strlen_zero(device_name)) {
|
||||
goto end;
|
||||
}
|
||||
|
||||
*device_name++ = '\0';
|
||||
|
||||
auto_iter = ao2_iterator_init(autohints, 0);
|
||||
for (; (autohint = ao2_iterator_next(&auto_iter)); ao2_t_ref(autohint, -1, "Next autohint")) {
|
||||
if (ast_get_hint(NULL, 0, NULL, 0, NULL, autohint->context, device_name)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
/* The device has no hint in the context referenced by this autohint so create one */
|
||||
ast_add_extension(autohint->context, 0, device_name,
|
||||
PRIORITY_HINT, NULL, NULL, dev_state->device,
|
||||
ast_strdup(dev_state->device), ast_free_ptr, autohint->registrar);
|
||||
|
||||
/* Since this hint was just created there are no watchers, so we don't need to notify anyone */
|
||||
}
|
||||
ao2_iterator_destroy(&auto_iter);
|
||||
|
||||
end:
|
||||
ast_mutex_unlock(&context_merge_lock);
|
||||
ast_free(hint_app);
|
||||
return;
|
||||
}
|
||||
@@ -5322,6 +5433,9 @@ static int show_dialplan_helper(int fd, const char *context, const char *exten,
|
||||
dpc->total_context++;
|
||||
ast_cli(fd, "[ Context '%s' created by '%s' ]\n",
|
||||
ast_get_context_name(c), ast_get_context_registrar(c));
|
||||
if (c->autohints) {
|
||||
ast_cli(fd, "Autohints support enabled\n");
|
||||
}
|
||||
context_info_printed = 1;
|
||||
}
|
||||
|
||||
@@ -5344,6 +5458,9 @@ static int show_dialplan_helper(int fd, const char *context, const char *exten,
|
||||
} else {
|
||||
ast_cli(fd, "[ Context '%s' created by '%s' ]\n",
|
||||
ast_get_context_name(c), ast_get_context_registrar(c));
|
||||
if (c->autohints) {
|
||||
ast_cli(fd, "Autohints support enabled\n");
|
||||
}
|
||||
}
|
||||
context_info_printed = 1;
|
||||
}
|
||||
@@ -6016,6 +6133,11 @@ struct ast_context *ast_context_find_or_create(struct ast_context **extcontexts,
|
||||
return tmp;
|
||||
}
|
||||
|
||||
void ast_context_set_autohints(struct ast_context *con, int enabled)
|
||||
{
|
||||
con->autohints = enabled;
|
||||
}
|
||||
|
||||
void __ast_context_destroy(struct ast_context *list, struct ast_hashtab *contexttab, struct ast_context *con, const char *registrar);
|
||||
|
||||
struct store_hint {
|
||||
@@ -6063,6 +6185,41 @@ static void context_merge_incls_swits_igps_other_registrars(struct ast_context *
|
||||
}
|
||||
}
|
||||
|
||||
/*! Set up an autohint placeholder in the hints container */
|
||||
static void context_table_create_autohints(struct ast_hashtab *table)
|
||||
{
|
||||
struct ast_context *con;
|
||||
struct ast_hashtab_iter *iter;
|
||||
|
||||
/* Remove all autohints as the below iteration will recreate them */
|
||||
ao2_callback(autohints, OBJ_UNLINK | OBJ_NODATA | OBJ_MULTIPLE, NULL, NULL);
|
||||
|
||||
iter = ast_hashtab_start_traversal(table);
|
||||
while ((con = ast_hashtab_next(iter))) {
|
||||
size_t name_len = strlen(con->name) + 1;
|
||||
size_t registrar_len = strlen(con->registrar) + 1;
|
||||
struct ast_autohint *autohint;
|
||||
|
||||
if (!con->autohints) {
|
||||
continue;
|
||||
}
|
||||
|
||||
autohint = ao2_alloc_options(sizeof(*autohint) + name_len + registrar_len, NULL, AO2_ALLOC_OPT_LOCK_NOLOCK);
|
||||
if (!autohint) {
|
||||
continue;
|
||||
}
|
||||
|
||||
ast_copy_string(autohint->context, con->name, name_len);
|
||||
autohint->registrar = autohint->context + name_len;
|
||||
ast_copy_string(autohint->registrar, con->registrar, registrar_len);
|
||||
|
||||
ao2_link(autohints, autohint);
|
||||
ao2_ref(autohint, -1);
|
||||
|
||||
ast_verb(3, "Enabled autohints support on context '%s'\n", con->name);
|
||||
}
|
||||
ast_hashtab_end_traversal(iter);
|
||||
}
|
||||
|
||||
/* the purpose of this routine is to duplicate a context, with all its substructure,
|
||||
except for any extens that have a matching registrar */
|
||||
@@ -6104,6 +6261,9 @@ static void context_merge(struct ast_context **extcontexts, struct ast_hashtab *
|
||||
/* make sure the new context exists, so we have somewhere to stick this exten/prio */
|
||||
if (!new) {
|
||||
new = ast_context_find_or_create(extcontexts, exttable, context->name, prio_item->registrar); /* a new context created via priority from a different context in the old dialplan, gets its registrar from the prio's registrar */
|
||||
if (new) {
|
||||
new->autohints = context->autohints;
|
||||
}
|
||||
}
|
||||
|
||||
/* copy in the includes, switches, and ignorepats */
|
||||
@@ -6150,6 +6310,10 @@ static void context_merge(struct ast_context **extcontexts, struct ast_hashtab *
|
||||
but that's not available, so we give it the registrar we know about */
|
||||
new = ast_context_find_or_create(extcontexts, exttable, context->name, context->registrar);
|
||||
|
||||
if (new) {
|
||||
new->autohints = context->autohints;
|
||||
}
|
||||
|
||||
/* copy in the includes, switches, and ignorepats */
|
||||
context_merge_incls_swits_igps_other_registrars(new, context, registrar);
|
||||
}
|
||||
@@ -6194,6 +6358,9 @@ void ast_merge_contexts_and_delete(struct ast_context **extcontexts, struct ast_
|
||||
ast_wrlock_contexts();
|
||||
|
||||
if (!contexts_table) {
|
||||
/* Create any autohint contexts */
|
||||
context_table_create_autohints(exttable);
|
||||
|
||||
/* Well, that's odd. There are no contexts. */
|
||||
contexts_table = exttable;
|
||||
contexts = *extcontexts;
|
||||
@@ -6316,6 +6483,9 @@ void ast_merge_contexts_and_delete(struct ast_context **extcontexts, struct ast_
|
||||
}
|
||||
}
|
||||
|
||||
/* Create all applicable autohint contexts */
|
||||
context_table_create_autohints(contexts_table);
|
||||
|
||||
ao2_unlock(hints);
|
||||
ast_unlock_contexts();
|
||||
|
||||
@@ -8526,6 +8696,11 @@ static void pbx_shutdown(void)
|
||||
ao2_ref(hintdevices, -1);
|
||||
hintdevices = NULL;
|
||||
}
|
||||
if (autohints) {
|
||||
ao2_container_unregister("autohints");
|
||||
ao2_ref(autohints, -1);
|
||||
autohints = NULL;
|
||||
}
|
||||
if (statecbs) {
|
||||
ao2_container_unregister("statecbs");
|
||||
ao2_ref(statecbs, -1);
|
||||
@@ -8559,6 +8734,16 @@ static void print_hintdevices_key(void *v_obj, void *where, ao2_prnt_fn *prnt)
|
||||
ast_get_context_name(ast_get_extension_context(hintdevice->hint->exten)));
|
||||
}
|
||||
|
||||
static void print_autohint_key(void *v_obj, void *where, ao2_prnt_fn *prnt)
|
||||
{
|
||||
struct ast_autohint *autohint = v_obj;
|
||||
|
||||
if (!autohint) {
|
||||
return;
|
||||
}
|
||||
prnt(where, "%s", autohint->context);
|
||||
}
|
||||
|
||||
static void print_statecbs_key(void *v_obj, void *where, ao2_prnt_fn *prnt)
|
||||
{
|
||||
struct ast_state_cb *state_cb = v_obj;
|
||||
@@ -8579,6 +8764,12 @@ int ast_pbx_init(void)
|
||||
if (hintdevices) {
|
||||
ao2_container_register("hintdevices", hintdevices, print_hintdevices_key);
|
||||
}
|
||||
/* This is protected by the context_and_merge lock */
|
||||
autohints = ao2_container_alloc_options(AO2_ALLOC_OPT_LOCK_NOLOCK, HASH_EXTENHINT_SIZE,
|
||||
autohint_hash_cb, autohint_cmp);
|
||||
if (hintdevices) {
|
||||
ao2_container_register("autohints", autohints, print_autohint_key);
|
||||
}
|
||||
statecbs = ao2_container_alloc(1, NULL, statecbs_cmp);
|
||||
if (statecbs) {
|
||||
ao2_container_register("statecbs", statecbs, print_statecbs_key);
|
||||
@@ -8590,5 +8781,5 @@ int ast_pbx_init(void)
|
||||
return -1;
|
||||
}
|
||||
|
||||
return (hints && hintdevices && statecbs) ? 0 : -1;
|
||||
return (hints && hintdevices && autohints && statecbs) ? 0 : -1;
|
||||
}
|
||||
|
@@ -1900,6 +1900,8 @@ process_extension:
|
||||
"Unable to include switch '%s' in context '%s' at line %d of %s\n",
|
||||
v->value, cxt, v->lineno, vfile);
|
||||
}
|
||||
} else if (!strcasecmp(v->name, "autohints")) {
|
||||
ast_context_set_autohints(con, ast_true(v->value));
|
||||
} else {
|
||||
ast_log(LOG_WARNING,
|
||||
"==!!== Unknown directive: %s at line %d of %s -- IGNORING!!!\n",
|
||||
|
Reference in New Issue
Block a user