diff --git a/include/asterisk/_private.h b/include/asterisk/_private.h index 056c0fde19..7a964a167a 100644 --- a/include/asterisk/_private.h +++ b/include/asterisk/_private.h @@ -59,6 +59,9 @@ void ast_msg_shutdown(void); /*!< Provided by message.c */ int aco_init(void); /*!< Provided by config_options.c */ int dns_core_init(void); /*!< Provided by dns_core.c */ int ast_refer_init(void); /*!< Provided by refer.c */ +int ast_extension_state_init(void); /*!< Provided by extension_state.c */ +int ast_extension_state_legacy_init(void); /*!< Provided by extension_state_legacy.c */ +int ast_extension_state_autohints_init(void); /*!< Provided by extension_state_autohints.c */ /*! * \brief Initialize malloc debug phase 1. diff --git a/include/asterisk/extension_state.h b/include/asterisk/extension_state.h new file mode 100644 index 0000000000..55791ea9bc --- /dev/null +++ b/include/asterisk/extension_state.h @@ -0,0 +1,210 @@ +/* + * Asterisk -- An open source telephony toolkit. + * + * Copyright (C) 2026, Sangoma Technologies Corporation + * + * Joshua C. Colp + * + * See http://www.asterisk.org for more information about + * the Asterisk project. Please do not directly contact + * any of the maintainers of this project for assistance; + * the project provides a web site, mailing lists and IRC + * channels for your use. + * + * This program is free software, distributed under the terms of + * the GNU General Public License Version 2. See the LICENSE file + * at the top of the source tree. + */ + +/*! \file + * \ref ExtensionState + * + * \page ExtensionState API providing extension state management. + +Before we talk about extension state let's talk about the fundamentals that +drive it. Extension state is driven based on three things: hints, device state, +and presence state. Hints are the stateless configuration that map a dialplan location, +context and extension, to zero or more devices and/or zero or more presence state +providers. Device state provides information about the associated device(s). +Presence state provides information about the associated presence state provider(s). + +The extension state API itself acts as an aggregator of the device state and presence +state information using the hint configuration to determine both the individual +identifier as well as the aggregation sources. The API provides the ability to query +the current state of an extension, as well as subscribe to be notified when the state +of an extension changes. + +When hint configuration changes this is reconciled and the extension state is updated +accordingly. If a hint has been added the corresponding extension state is created. If +a hint has been removed the corresponding extension state is removed. For cases where +the hint has been added or updated the sources of information are updated on the +extension state and its state is recalculated. This may involve subscribing to new +device state topics or unsubscribing from old ones. + +Extension state uses synchronous per-device subscriptions to receive device state +updates. Synchronous is used as the overhead of asynchronous delivery is not worth +the added overhead and CPU for the small amount of work done when device states change. +On receipt of a device state update the extension device state is recalculated and if +the state has changed the extension device state is updated and any subscribers are +notified. + +Presence state uses a single global subscription to receive presence state updates as +no per-presence state provider topic is available and also due to the extremely small +number of presence state updates that occur on a system. Just like device state +updates the extension presence state is recalculated and if it has changed it is +updated and any subscribers are notified. + +To minimize querying of other APIs in Asterisk extension state keeps an internal +cache of device states and presence state on each extension state. This cache is +updated when device state or presence state changes and is used to determine the +aggregated state of an extension. The aggregated state is also cached on the +extension state for quick access by API users who do not subscribe to receive +updates. + +*/ + +#ifndef _ASTERISK_EXTENSION_STATE_H +#define _ASTERISK_EXTENSION_STATE_H + +#include "asterisk/pbx.h" + +/*! \brief Individual device states that contributed to snapshot */ +struct ast_extension_state_device_state_info { + /*! \brief The state of the device */ + enum ast_device_state state; + /*! \brief The name of the device */ + char device[0]; +}; + +/*! \brief Device snapshot for an extension state*/ +struct ast_extension_state_device_snapshot { + /*! \brief The state of the extension */ + enum ast_extension_states state; + /*! \brief The device that caused this update */ + struct ast_extension_state_device_state_info *causing_device; + /*! \brief Vector of additional device states that contributed to update */ + AST_VECTOR(, struct ast_extension_state_device_state_info *) additional_devices; +}; + +/*! \brief Presence snapshot for an extension state */ +struct ast_extension_state_presence_snapshot { + /*! \brief The presence state of the extension */ + enum ast_presence_state presence_state; + /*! \brief The subtype of the presence state */ + char *presence_subtype; + /*! \brief An optional message for the presence */ + char *presence_message; +}; + +/*! \brief Stasis message for extension state update message */ +struct ast_extension_state_update_message { + /*! \brief The old device snapshot */ + struct ast_extension_state_device_snapshot *old_device_snapshot; + /*! \brief The new device snapshot - will be pointer equivalent to old if unchanged */ + struct ast_extension_state_device_snapshot *new_device_snapshot; + /*! \brief The old presence snapshot */ + struct ast_extension_state_presence_snapshot *old_presence_snapshot; + /*! \brief The new presence snapshot - will be pointer equivalent to old if unchanged */ + struct ast_extension_state_presence_snapshot *new_presence_snapshot; + /*! \brief The dialplan context */ + char *context; + /*! \brief The dialplan extension */ + char extension[0]; +}; + +/*! \brief Stasis message for extension state removal message */ +struct ast_extension_state_remove_message { + /*! \brief The dialplan context */ + char *context; + /*! \brief The dialplan extension */ + char extension[0]; +}; + +/*! + * \brief Get the Stasis topic to receive all extension state messages + * \since 23.5.0 + * \since 22.11.0 + * \since 20.21.0 + * + * \return The topic for extension state messages + * \retval NULL if it has not been allocated + */ +struct stasis_topic *ast_extension_state_topic_all(void); + +/*! + * \brief Get the Stasis topic to receive extension state messages for a specific extension + * \since 23.5.0 + * \since 22.11.0 + * \since 20.21.0 + * + * \param exten The extension to receive extension state messages for + * \param context The context of the extension + * \return The topic for extension state messages + * \retval NULL if it has not been allocated + */ +struct stasis_topic *ast_extension_state_topic(const char *exten, const char *context); + +/*! + * \brief Get the latest device state message for an extension + * \since 23.5.0 + * \since 22.11.0 + * \since 20.21.0 + * + * \param chan The optional channel to get the underlying hint from, if it needs to be created + * \param exten The extension to get the device state message for + * \param context The context of the extension + * \return The latest device snapshot for the extension + * \retval NULL if the extension does not have a configured hint + */ +struct ast_extension_state_device_snapshot *ast_extension_state_get_latest_device_snapshot(struct ast_channel *chan, + const char *exten, const char *context); + +/*! + * \brief Get the latest presence state message for an extension + * \since 23.5.0 + * \since 22.11.0 + * \since 20.21.0 + * + * \param chan The optional channel to get the underlying hint from, if it needs to be created + * \param exten The extension to get the presence state message for + * \param context The context of the extension + * \return The latest presence snapshot for the extension + * \retval NULL if the extension does not have a configured hint + */ +struct ast_extension_state_presence_snapshot *ast_extension_state_get_latest_presence_snapshot(struct ast_channel *chan, + const char *exten, const char *context); + +/*! + * \brief Get the channel that is causing the device to be in the given state, if any + * \since 23.5.0 + * \since 22.11.0 + * \since 20.21.0 + * + * \param device The device itself + * \param device_state The state of the device + * \return The channel that is causing the device to be in the given state + * \retval NULL if there is no channel causing the device to be in the given state + */ +struct ast_channel *ast_extension_state_get_device_causing_channel(const char *device, enum ast_device_state device_state); + +/*! + * \brief Get extension state update message type + * \since 23.5.0 + * \since 22.11.0 + * \since 20.21.0 + * + * \retval Stasis message type for extension state update messages + */ +struct stasis_message_type *ast_extension_state_update_message_type(void); + +/*! + * \brief Get extension state remove message type + * \since 23.5.0 + * \since 22.11.0 + * \since 20.21.0 + * + * \retval Stasis message type for extension state remove messages + */ +struct stasis_message_type *ast_extension_state_remove_message_type(void); + +#endif /* ASTERISK_EXTENSION_STATE_H */ diff --git a/include/asterisk/stasis.h b/include/asterisk/stasis.h index b63cd297a9..ca15c35b79 100644 --- a/include/asterisk/stasis.h +++ b/include/asterisk/stasis.h @@ -679,6 +679,37 @@ struct stasis_subscription *__stasis_subscribe_pool(struct stasis_topic *topic, stasis_subscription_cb callback, void *data, const char *file, int lineno, const char *func); #define stasis_subscribe_pool(topic, callback, data) __stasis_subscribe_pool(topic, callback, data, __FILE__, __LINE__, __PRETTY_FUNCTION__) +/*! + * \brief Create a subscription whose callbacks occur synchronously on message publishing + * \since 23.5.0 + * \since 22.11.0 + * \since 20.21.0 + * + * In addition to being AO2 managed memory (requiring an ao2_cleanup() to free + * up this reference), the subscription must be explicitly unsubscribed from its + * topic using stasis_unsubscribe(). + * + * The invocations of the callback are serialized, but will almost certainly not + * always happen on the same thread. The invocation order of different subscriptions + * is unspecified. + * + * This subscription will be invoked on the same thread that is publishing the message. + * + * \param topic Topic to subscribe to. + * \param callback Callback function for subscription messages. + * \param data Data to be passed to the callback, in addition to the message. + * \param file, lineno, func + * \return New \ref stasis_subscription object. + * \retval NULL on error. + * + * \note This callback will receive a callback with a message indicating it + * has been subscribed. This occurs immediately before accepted message + * types can be set and the callback must expect to receive it. + */ +struct stasis_subscription *__stasis_subscribe_synchronous(struct stasis_topic *topic, + stasis_subscription_cb callback, void *data, const char *file, int lineno, const char *func); +#define stasis_subscribe_synchronous(topic, callback, data) __stasis_subscribe_synchronous(topic, callback, data, __FILE__, __LINE__, __PRETTY_FUNCTION__) + /*! * \brief Indicate to a subscription that we are interested in a message type. * diff --git a/main/asterisk.c b/main/asterisk.c index febee957d3..b398060afb 100644 --- a/main/asterisk.c +++ b/main/asterisk.c @@ -4342,6 +4342,9 @@ static void asterisk_daemon(int isroot, const char *runuser, const char *rungrou check_init(load_pbx_switch(), "PBX Switch Support"); check_init(load_pbx_app(), "PBX Application Support"); check_init(load_pbx_hangup_handler(), "PBX Hangup Handler Support"); + check_init(ast_extension_state_init(), "Extension State Support"); + check_init(ast_extension_state_legacy_init(), "Extension State Legacy Support"); + check_init(ast_extension_state_autohints_init(), "Extension State Autohints Support"); check_init(ast_local_init(), "Local Proxy Channel Driver"); check_init(ast_refer_init(), "Refer API"); diff --git a/main/extension_state.c b/main/extension_state.c new file mode 100644 index 0000000000..ebe54298bc --- /dev/null +++ b/main/extension_state.c @@ -0,0 +1,1597 @@ +/* + * Asterisk -- An open source telephony toolkit. + * + * Copyright (C) 2026, Sangoma Technologies Corporation + * + * Joshua Colp + * + * See https://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. + */ + +/*** MODULEINFO + core + ***/ + +#include "asterisk.h" +#include "asterisk/_private.h" +#include "asterisk/module.h" +#include "asterisk/extension_state.h" +#include "asterisk/pbx.h" +#include "asterisk/stasis.h" +#include "asterisk/stasis_message_router.h" +#include "asterisk/astobj2.h" +#include "asterisk/cli.h" +#include "pbx_private.h" + +/*** DOCUMENTATION + + + 13.0.0 + + + List the current known extension states. + + + + + + This will list out all known extension states in a + sequence of ExtensionStatus events. + When finished, a ExtensionStateListComplete event + will be emitted. + + + ExtensionState + HINT + EXTENSION_STATE + + + + + + + + + 13.0.0 + + + Indicates the end of the list the current known extension states. + + + + Conveys the status of the event list. + + + Conveys the number of statuses reported. + + + + + + + ***/ + +#define HINTDEVICE_DATA_LENGTH 16 +AST_THREADSTORAGE(hintdevice_data); + +/*! \brief Device state source feeding an extension state */ +struct extension_state_device_source { + /*! \brief The current state of the device - this is immutable */ + struct ast_extension_state_device_state_info *info; + /*! \brief Synchronous subscription to the device state topic */ + struct stasis_subscription *device_state_subscription; + /*! \brief The current version for this source */ + unsigned int version; +}; + +AST_VECTOR(device_state_sources_vector, struct extension_state_device_source *); + +/*! \brief Extension state information */ +struct extension_state { + /*! \brief The current device snapshot for the extension */ + struct ast_extension_state_device_snapshot *device_snapshot; + /*! \brief The current presence snapshot for the extension */ + struct ast_extension_state_presence_snapshot *presence_snapshot; + /*! \brief The extension state topic for this extension */ + struct stasis_topic *extension_state_topic; + /*! \brief Forwarder from per-extension topic to all topic */ + struct stasis_forward *extension_state_forwarder; + /*! \brief Device state sources feeding the hint topic, and their forwarding */ + struct device_state_sources_vector device_state_sources; + /*! \brief The string representation of all presence state sources feeding this extension state */ + char *presence_sources_string; + /*! \brief The dialplan hint that last configured this extension state */ + struct ast_exten *hint_extension; + /*! \brief The dialplan context */ + char dialplan_context[AST_MAX_CONTEXT]; + /*! \brief The dialplan extension */ + char dialplan_extension[AST_MAX_EXTENSION]; + /*! \brief The combined extension this state is for (extension@context) */ + char extension[0]; +}; + +/*! \brief Number of buckets for extension states */ +#ifdef LOW_MEMORY +#define EXTENSION_STATE_BUCKETS 17 +#else +#define EXTENSION_STATE_BUCKETS 563 +#endif + +static const struct cfextension_states { + int extension_state; + const char * const text; +} extension_state_mappings[] = { + { AST_EXTENSION_NOT_INUSE, "Idle" }, + { AST_EXTENSION_INUSE, "InUse" }, + { AST_EXTENSION_BUSY, "Busy" }, + { AST_EXTENSION_UNAVAILABLE, "Unavailable" }, + { AST_EXTENSION_RINGING, "Ringing" }, + { AST_EXTENSION_INUSE | AST_EXTENSION_RINGING, "InUse&Ringing" }, + { AST_EXTENSION_ONHOLD, "Hold" }, + { AST_EXTENSION_INUSE | AST_EXTENSION_ONHOLD, "InUse&Hold" } +}; + +/*! \brief The global container of extension states */ +static struct ao2_container *extension_states; + +/*! \brief Topic which receives all extension state updates */ +static struct stasis_topic *extension_state_topic_all; + +/*! \brief Single presence state subscription, for all extension states */ +static struct stasis_subscription *presence_state_sub; + +/*! \brief Sort function for extension states */ +AO2_STRING_FIELD_SORT_FN(extension_state, extension) + +/*! \brief Compare function for extension states */ +AO2_STRING_FIELD_CMP_FN(extension_state, extension) + +/*! \brief Message type for extension state updates */ +STASIS_MESSAGE_TYPE_DEFN(ast_extension_state_update_message_type); + +/*! + * \internal + * \brief Destroy an extension state update message + * \param obj The extension state update message to destroy + */ +static void extension_state_update_message_destroy(void *obj) +{ + struct ast_extension_state_update_message *update_message = obj; + + ao2_cleanup(update_message->old_device_snapshot); + ao2_cleanup(update_message->new_device_snapshot); + ao2_cleanup(update_message->old_presence_snapshot); + ao2_cleanup(update_message->new_presence_snapshot); +} + +/*! + * \internal + * \brief Create an extension state update message + * + * \param context The context of the extension + * \param extension The extension + * \param old_device_snapshot The old device state snapshot + * \param new_device_snapshot The new device state snapshot + * \param old_presence_snapshot The old presence state snapshot + * \param new_presence_snapshot The new presence state snapshot + * \retval An allocated extension state update message, or NULL on failure + * + * This function creates an extension state update message for the specified context, extension, + * old device state snapshot, new device state snapshot, old presence state snapshot, and new presence state snapshot. + */ +static struct ast_extension_state_update_message *extension_state_update_message_create(const char *context, + const char *extension, struct ast_extension_state_device_snapshot *old_device_snapshot, + struct ast_extension_state_device_snapshot *new_device_snapshot, struct ast_extension_state_presence_snapshot *old_presence_snapshot, + struct ast_extension_state_presence_snapshot *new_presence_snapshot) +{ + size_t context_len = strlen(context) + 1; + size_t extension_len = strlen(extension) + 1; + struct ast_extension_state_update_message *update_message; + + update_message = ao2_alloc_options(sizeof(*update_message) + context_len + extension_len, + extension_state_update_message_destroy, AO2_ALLOC_OPT_LOCK_NOLOCK); + if (!update_message) { + return NULL; + } + + ast_copy_string(update_message->extension, extension, extension_len); /* Safe */ + update_message->context = update_message->extension + extension_len; + ast_copy_string(update_message->context, context, context_len); /* Safe */ + + update_message->old_device_snapshot = ao2_bump(old_device_snapshot); + update_message->new_device_snapshot = ao2_bump(new_device_snapshot); + update_message->old_presence_snapshot = ao2_bump(old_presence_snapshot); + update_message->new_presence_snapshot = ao2_bump(new_presence_snapshot); + + return update_message; +} + +/*! + * \internal + * \brief Destroy an extension state device source + * \param source The extension state device source to destroy + * + * This function destroys an extension state device source by unsubscribing from the device state + * topic and cleaning up the associated resources. + */ +static void extension_state_device_source_destroy(struct extension_state_device_source *source) +{ + if (source->device_state_subscription) { + stasis_unsubscribe(source->device_state_subscription); + } + ao2_cleanup(source->info); + ast_free(source); +} + +/*! + * \internal + * \brief Allocate an extension device state info object + * + * \param device The device name + * \param state The device state + * \retval An allocated extension device state info object, or NULL on failure + * + * This function allocates an extension device state info object with the specified device name and state. + */ +static struct ast_extension_state_device_state_info *extension_state_device_state_info_alloc(const char *device, + enum ast_device_state state) +{ + struct ast_extension_state_device_state_info *info; + + info = ao2_alloc_options(sizeof(*info) + strlen(device) + 1, NULL, AO2_ALLOC_OPT_LOCK_NOLOCK); + if (!info) { + return NULL; + } + + info->state = state; + strcpy(info->device, device); /* Safe */ + + return info; +} + +/*! + * \internal + * \brief Destroy an extension state device snapshot + * + * \param obj The extension state device snapshot to destroy + * + * This function destroys an extension state device snapshot by cleaning up + * the causing device and additional devices. + */ +static void extension_state_device_snapshot_destroy(void *obj) +{ + struct ast_extension_state_device_snapshot *device_snapshot = obj; + + ao2_cleanup(device_snapshot->causing_device); + AST_VECTOR_CALLBACK_VOID(&device_snapshot->additional_devices, ao2_cleanup); + AST_VECTOR_FREE(&device_snapshot->additional_devices); +} + +/*! + * \internal + * \brief Create an extension state device snapshot + * + * \param device_state The device state + * \param device_state_sources The device state sources + * \param causing_device The causing device + * \retval An allocated extension state device snapshot, or NULL on failure + * + * This function creates an extension state device snapshot with the device state, + * device state sources, and causing device. + */ +static struct ast_extension_state_device_snapshot *extension_state_device_snapshot_create( + enum ast_extension_states device_state, struct device_state_sources_vector *device_state_sources, + struct ast_extension_state_device_state_info *causing_device) +{ + struct ast_extension_state_device_snapshot *device_snapshot; + int i; + + device_snapshot = ao2_alloc_options(sizeof(*device_snapshot), + extension_state_device_snapshot_destroy, AO2_ALLOC_OPT_LOCK_NOLOCK); + if (!device_snapshot) { + return NULL; + } + + device_snapshot->state = device_state; + device_snapshot->causing_device = ao2_bump(causing_device); + if (AST_VECTOR_INIT(&device_snapshot->additional_devices, AST_VECTOR_SIZE(device_state_sources))) { + ao2_ref(device_snapshot, -1); + return NULL; + } + + for (i = 0; i < AST_VECTOR_SIZE(device_state_sources); i++) { + struct extension_state_device_source *source = AST_VECTOR_GET(device_state_sources, i); + + if (causing_device && source->info == causing_device) { + continue; + } + + AST_VECTOR_APPEND(&device_snapshot->additional_devices, ao2_bump(source->info)); + } + + return device_snapshot; +} + +/*! + * \internal + * \brief Callback for device state changes + * + * \param userdata The extension state to update + * \param sub The subscription + * \param msg The device state message + * + * This function is called when a device state changes and updates the extension state + * accordingly by aggregating the device states and publishing the new state. + */ +static void extension_state_device_state_cb(void *userdata, struct stasis_subscription *sub, + struct stasis_message *msg) +{ + struct extension_state *state = userdata; + struct ast_device_state_message *device_state; + struct ast_devstate_aggregate agg; + struct ast_extension_state_device_state_info *extension_device_state_info; + int i; + unsigned int updated = 0; + enum ast_extension_states new_device_state; + + if (stasis_message_type(msg) != ast_device_state_message_type()) { + return; + } + + device_state = stasis_message_data(msg); + + /* We only care about the aggregate state */ + if (device_state->eid) { + return; + } + + ast_devstate_aggregate_init(&agg); + + /* + * Alrighty, the reason that we store an extension_device_state_info is to reduce the memory allocation that + * has to occur every time we get a device state update and have to construct a new message. If the extension + * state contains only a single device source we have to do this anyway, but if there's multiple then if we + * didn't store the result we'd be creating new ones every message to put in the additional_devices vector. + */ + extension_device_state_info = extension_state_device_state_info_alloc(device_state->device, device_state->state); + if (!extension_device_state_info) { + return; + } + + ao2_lock(state); + + for (i = 0; i < AST_VECTOR_SIZE(&state->device_state_sources); i++) { + struct extension_state_device_source *source = AST_VECTOR_GET(&state->device_state_sources, i); + + if (!strcmp(source->info->device, device_state->device)) { + ao2_replace(source->info, extension_device_state_info); + updated = 1; + } + + ast_devstate_aggregate_add(&agg, source->info->state); + } + + /* We don't really care about the device state info contents now, so we can drop the reference */ + ao2_ref(extension_device_state_info, -1); + + /* + * It's possible for a device state update to come in for a device which is no longer feeding this + * extension state if it has been updated, so only actually care about the new device state if a + * source has actually been updated. + */ + if (!updated) { + ao2_unlock(state); + return; + } + + /* + * We actually update things and raise a message if the state is different, or if the state is ringing + * as that can actually just be an update that someone else is ringing the same extension. + */ + new_device_state = ast_devstate_to_extenstate(ast_devstate_aggregate_result(&agg)); + if ((state->device_snapshot->state != new_device_state) || (new_device_state & AST_EXTENSION_RINGING)) { + struct ast_extension_state_device_snapshot *device_snapshot; + struct ast_extension_state_update_message *update_message; + struct stasis_message *message; + + /* Now above you probably noticed I dropped the reference for extension_device_state_info but now I'm + * passing it in here. Don't panic - a reference exists on the device state source still and since we + * have the state locked it can't go away. + */ + device_snapshot = extension_state_device_snapshot_create(new_device_state, &state->device_state_sources, + extension_device_state_info); + if (!device_snapshot) { + ao2_unlock(state); + return; + } + + update_message = extension_state_update_message_create(state->dialplan_context, state->dialplan_extension, + state->device_snapshot, device_snapshot, state->presence_snapshot, state->presence_snapshot); + + /* Even if we can't publish an update message we still ensure the local cached snapshot is up to date */ + ao2_replace(state->device_snapshot, device_snapshot); + ao2_ref(device_snapshot, -1); + + if (!update_message) { + ao2_unlock(state); + return; + } + + /* Inform any subscribers of the change to the device snapshot */ + message = stasis_message_create(ast_extension_state_update_message_type(), update_message); + if (message) { + stasis_publish(state->extension_state_topic, message); + ao2_ref(message, -1); + } + + ao2_ref(update_message, -1); + } + + ao2_unlock(state); +} + +/*! + * \internal + * \brief Allocate a device source for an extension state + * + * \param state The extension state to allocate the device source for + * \param device The device to allocate the source for + * \retval An allocated device source, or NULL on failure + * + * This function allocates a device source for an extension state by creating a device state source + * and setting up the necessary subscriptions and references. + */ +static struct extension_state_device_source *extension_state_device_source_alloc(struct extension_state *state, const char *device) +{ + struct extension_state_device_source *source; + struct stasis_topic *topic; + + /* + * Ensure that we have a direct device state topic for the device, note this is returned without a reference but + * is guaranteed to exist regardless. + */ + topic = ast_device_state_topic(device); + if (!topic) { + return NULL; + } + + /* + * The device state source is only used within the extension state and is never + * passed around so the overhead of an ao2 object with reference counting is unnecessary. + */ + source = ast_calloc(1, sizeof(*source)); + if (!source) { + return NULL; + } + + source->info = extension_state_device_state_info_alloc(device, ast_device_state(device)); + if (!source->info) { + extension_state_device_source_destroy(source); + return NULL; + } + + /* + * We do a synchronous subscription to the device state topic, as our callback is extremely + * short lived and the added overhead of queueing to a taskprocessor for another thread to handle + * it is just not worth it. + */ + source->device_state_subscription = stasis_subscribe_synchronous(topic, extension_state_device_state_cb, state); + if (!source->device_state_subscription) { + extension_state_device_source_destroy(source); + return NULL; + } + + stasis_subscription_accept_message_type(source->device_state_subscription, ast_device_state_message_type()); + stasis_subscription_set_filter(source->device_state_subscription, STASIS_SUBSCRIPTION_FILTER_SELECTIVE); + + return source; +} + +/*! + * \internal + * \brief Destroy an extension state presence snapshot + * + * \param obj The extension state presence snapshot to destroy + * + * This function destroys an extension state presence snapshot by cleaning up + * the presence snapshot. + */ +static void extension_state_presence_snapshot_destroy(void *obj) +{ + struct ast_extension_state_presence_snapshot *presence_snapshot = obj; + + ast_free(presence_snapshot->presence_subtype); + ast_free(presence_snapshot->presence_message); +} + +/*! + * \internal + * \brief Create an extension state presence snapshot + * + * \param presence_state The presence state + * \param presence_subtype The presence subtype (can be NULL) + * \param presence_message The presence message (can be NULL) + * \retval An allocated extension state presence snapshot, or NULL on failure + * + * This function creates an extension state presence snapshot for the specified presence state, + * presence subtype, and presence message. + */ +static struct ast_extension_state_presence_snapshot *extension_state_presence_snapshot_create(enum ast_presence_state presence_state, + const char *presence_subtype, const char *presence_message) +{ + struct ast_extension_state_presence_snapshot *presence_snapshot; + + presence_snapshot = ao2_alloc_options(sizeof(*presence_snapshot), extension_state_presence_snapshot_destroy, + AO2_ALLOC_OPT_LOCK_NOLOCK); + if (!presence_snapshot) { + return NULL; + } + + /* To ensure that we don't give a partial snapshot we fail creation if any allocation fails */ + presence_snapshot->presence_state = presence_state; + if (presence_subtype) { + presence_snapshot->presence_subtype = ast_strdup(presence_subtype); + if (!presence_snapshot->presence_subtype) { + ao2_ref(presence_snapshot, -1); + return NULL; + } + } + if (presence_message) { + presence_snapshot->presence_message = ast_strdup(presence_message); + if (!presence_snapshot->presence_message) { + ao2_ref(presence_snapshot, -1); + return NULL; + } + } + + return presence_snapshot; +} + +/*! + * \brief device source non-matching version comparator for AST_VECTOR_REMOVE_CMP_UNORDERED() + * + * \param elem Element to compare against + * \param value Value to compare with the vector element. + * + * \return 0 if element does not match. + * \return Non-zero if element matches. + */ +#define DEVICE_SOURCE_ELEM_VERSION_CMP(elem, value) ((elem)->version != (value)) + +/*! + * \internal + * \brief Update the sources of an extension state + * + * \param state The extension state to update + * \param exten The extension to update + * \retval 0 on success, -1 on failure + * + * This function updates the sources of an extension state by parsing the app part + * of the extension and updating the device and presence state sources. + */ +static int extension_state_update_sources(struct extension_state *state, struct ast_exten *exten) +{ + struct ast_str *str = ast_str_thread_get(&hintdevice_data, HINTDEVICE_DATA_LENGTH); + char *devices, *device, *presence_state_sources; + struct ast_devstate_aggregate agg; + enum ast_extension_states new_device_state; + unsigned int version; + struct ast_extension_state_device_snapshot *device_snapshot = NULL; + struct ast_extension_state_presence_snapshot *presence_snapshot = NULL; + + ast_str_set(&str, 0, "%s", ast_get_extension_app(exten)); + devices = ast_str_buffer(str); + + ao2_lock(state); + + /* + * The format of the app part of a hint is "[device[&device]],[presence[&presence]]" so + * we can just find the first occurrence of ',' in order to get to the presence sources. + */ + presence_state_sources = strchr(devices, ','); + if (presence_state_sources) { + *presence_state_sources++ = '\0'; + } + + ast_devstate_aggregate_init(&agg); + + version = ast_random(); + + /* Devices are separated by '&' */ + while ((device = strsep(&devices, "&"))) { + struct extension_state_device_source *source = NULL; + int i; + + /* Skip any device names that are empty, as we can do nothing */ + if (ast_strlen_zero(device)) { + continue; + } + + for (i = 0; i < AST_VECTOR_SIZE(&state->device_state_sources); i++) { + struct extension_state_device_source *existing_source = AST_VECTOR_GET(&state->device_state_sources, i); + + if (!strcmp(existing_source->info->device, device)) { + source = existing_source; + break; + } + } + if (!source) { + source = extension_state_device_source_alloc(state, device); + if (!source) { + ao2_unlock(state); + return -1; + } + AST_VECTOR_APPEND(&state->device_state_sources, source); + } + + ast_devstate_aggregate_add(&agg, source->info->state); + source->version = version; + } + + /* Do a pass and remove all old device sources */ + AST_VECTOR_REMOVE_ALL_CMP_UNORDERED(&state->device_state_sources, version, + DEVICE_SOURCE_ELEM_VERSION_CMP, extension_state_device_source_destroy); + + /* If device state sources exist it is what produces the device state, otherwise we use the default */ + if (AST_VECTOR_SIZE(&state->device_state_sources)) { + new_device_state = ast_devstate_to_extenstate(ast_devstate_aggregate_result(&agg)); + } else { + new_device_state = AST_EXTENSION_UNAVAILABLE; + } + + /* If the device state has changed, create a new snapshot */ + if (state->device_snapshot->state != new_device_state) { + device_snapshot = extension_state_device_snapshot_create(new_device_state, &state->device_state_sources, NULL); + } + if (!device_snapshot) { + /* If there's no new device snapshot we use the old one */ + device_snapshot = state->device_snapshot; + } + + /* If the presence state has changed, create a new snapshot */ + if ((!state->presence_sources_string && presence_state_sources) || + (state->presence_sources_string && strcmp(state->presence_sources_string, presence_state_sources))) { + enum ast_presence_state presence_state = AST_PRESENCE_NOT_SET; + char *presence_subtype = NULL, *presence_message = NULL; + + ast_free(state->presence_sources_string); + + if (!ast_strlen_zero(presence_state_sources)) { + state->presence_sources_string = ast_strdup(presence_state_sources); + /* Presence state is also separated by & but only the presence state API can handle it and aggregate */ + presence_state = ast_presence_state(presence_state_sources, &presence_subtype, + &presence_message); + } else { + state->presence_sources_string = NULL; + } + + presence_snapshot = extension_state_presence_snapshot_create(presence_state, presence_subtype, presence_message); + + ast_free(presence_subtype); + ast_free(presence_message); + } + if (!presence_snapshot) { + /* If there's no new presence snapshot we use the old one */ + presence_snapshot = state->presence_snapshot; + } + + /* If any snapshots have changed create an update message containing them */ + if (state->device_snapshot != device_snapshot || state->presence_snapshot != presence_snapshot) { + struct ast_extension_state_update_message *update_message; + + update_message = extension_state_update_message_create(state->dialplan_context, state->dialplan_extension, state->device_snapshot, + device_snapshot, state->presence_snapshot, presence_snapshot); + if (update_message) { + struct stasis_message *message = stasis_message_create(ast_extension_state_update_message_type(), update_message); + + if (message) { + stasis_publish(state->extension_state_topic, message); + ao2_ref(message, -1); + } + + ao2_ref(update_message, -1); + } + + /* If applicable, update the snapshots on the state to their new version */ + if (state->device_snapshot != device_snapshot) { + ao2_replace(state->device_snapshot, device_snapshot); + ao2_ref(device_snapshot, -1); + } + if (state->presence_snapshot != presence_snapshot) { + ao2_replace(state->presence_snapshot, presence_snapshot); + ao2_ref(presence_snapshot, -1); + } + } + + ao2_unlock(state); + + return 0; +} + +/*! + * \internal + * \brief Destroy an extension state + * + * \param obj The extension state to destroy + * + * This function destroys an extension state by cleaning up its resources. + */ +static void extension_state_destroy(void *obj) +{ + struct extension_state *state = obj; + + ao2_cleanup(state->device_snapshot); + ao2_cleanup(state->presence_snapshot); + ao2_cleanup(state->extension_state_topic); + + ast_free(state->presence_sources_string); +} + +/*! \brief Stasis message type for extension state remove messages */ +STASIS_MESSAGE_TYPE_DEFN(ast_extension_state_remove_message_type); + +/*! + * \internal + * \brief Create an extension state remove message + * + * \param context The context of the extension + * \param extension The extension to remove + * \retval A stasis message for the remove event, or NULL on failure + * + * This function creates an extension state remove message for the specified context and extension. + */ +static struct stasis_message *extension_state_remove_message_create(const char *context, const char *extension) +{ + size_t context_len = strlen(context) + 1; + size_t extension_len = strlen(extension) + 1; + struct ast_extension_state_remove_message *remove_message; + struct stasis_message *message; + + remove_message = ao2_alloc_options(sizeof(*remove_message) + context_len + extension_len, NULL, + AO2_ALLOC_OPT_LOCK_NOLOCK); + if (!remove_message) { + return NULL; + } + + ast_copy_string(remove_message->extension, extension, extension_len); /* Safe */ + remove_message->context = remove_message->extension + extension_len; + ast_copy_string(remove_message->context, context, context_len); /* Safe */ + + message = stasis_message_create(ast_extension_state_remove_message_type(), remove_message); + ao2_ref(remove_message, -1); + + return message; +} + +/*! + * \internal + * \brief Shut down an extension state + * + * \param state The extension state to shut down + * + * This function shuts down an extension state by publishing a remove message to its topic + * and cleaning up its resources. + */ +static void extension_state_shutdown(struct extension_state *state) +{ + struct stasis_message *remove_message; + + /* + * Shutting down an extension state requires us to publish to its topic so all subscribers + * know that it is going away. However, if the topic failed to be created then we have nothing + * to publish to and can just return. + */ + if (!state->extension_state_topic) { + return; + } + + /* Inform all subscribers that this extension state is being removed */ + remove_message = extension_state_remove_message_create(state->dialplan_context, + state->dialplan_extension); + if (remove_message) { + stasis_publish(state->extension_state_topic, remove_message); + ao2_ref(remove_message, -1); + } + + AST_VECTOR_CALLBACK_VOID(&state->device_state_sources, extension_state_device_source_destroy); + AST_VECTOR_FREE(&state->device_state_sources); + + stasis_forward_cancel(state->extension_state_forwarder); +} + +/*! + * \internal + * \brief Callback for presence state messages + * + * \param unused Unused parameter + * \param sub The stasis subscription + * \param msg The stasis message + * + * This callback is invoked when a presence state message is received. It updates + * the presence state of all extension states that are interested in the presence + * state provider and publishes an extension state update message if it has changed. + */ +static void extension_state_presence_state_cb(void *unused, struct stasis_subscription *sub, + struct stasis_message *msg) +{ + struct ast_presence_state_message *presence_state; + struct ao2_iterator iter; + struct extension_state *state; + + if (stasis_message_type(msg) != ast_presence_state_message_type()) { + return; + } + + presence_state = stasis_message_data(msg); + + ao2_lock(extension_states); + iter = ao2_iterator_init(extension_states, 0); + for (; (state = ao2_iterator_next(&iter)); ao2_ref(state, -1)) { + enum ast_presence_state presence_state_new; + char *presence_subtype, *presence_message; + struct ast_extension_state_presence_snapshot *presence_snapshot; + struct ast_extension_state_update_message *update_message; + + ao2_lock(state); + + /* + * We determine if this update is relevant to this extension state by seeing if the presence sources string + * even remotely contains the provider for this update. Worst case it's a substring and the calculated presence + * state is the same as before in which case we ignore it. + */ + if (!state->presence_sources_string || !strcasestr(state->presence_sources_string, presence_state->provider)) { + ao2_unlock(state); + continue; + } + + /* + * Aggregation of presence state is done by requesting the current presence state with passing in a complete + * list of providers. This means that a presence state change message is just a notification to us to go and + * retrieve the new presence state. We don't just take it from the message itself. Since presence state is not + * as common as device state this is not a problem despite being inefficient in comparison to the device state + * implementation. + */ + presence_state_new = ast_presence_state(state->presence_sources_string, &presence_subtype, &presence_message); + if (presence_state_new == AST_PRESENCE_INVALID) { + /* For the invalid case we just ignore this update */ + ao2_unlock(state); + continue; + } + + if ((state->presence_snapshot->presence_state == presence_state_new) && + ((!presence_subtype && !state->presence_snapshot->presence_subtype) || + (presence_subtype && state->presence_snapshot->presence_subtype && + !strcmp(presence_subtype, state->presence_snapshot->presence_subtype))) && + ((!presence_message && !state->presence_snapshot->presence_message) || + (presence_message && state->presence_snapshot->presence_message && + !strcmp(presence_message, state->presence_snapshot->presence_message)))) { + /* No change in presence state, so ignore this update */ + ao2_unlock(state); + ast_free(presence_subtype); + ast_free(presence_message); + continue; + } + + presence_snapshot = extension_state_presence_snapshot_create(presence_state_new, presence_subtype, presence_message); + if (!presence_snapshot) { + ao2_unlock(state); + ast_free(presence_subtype); + ast_free(presence_message); + continue; + } + + update_message = extension_state_update_message_create(state->dialplan_context, + state->dialplan_extension, state->device_snapshot, state->device_snapshot, state->presence_snapshot, presence_snapshot); + ao2_replace(state->presence_snapshot, presence_snapshot); + ao2_ref(presence_snapshot, -1); + if (update_message) { + struct stasis_message *message = stasis_message_create(ast_extension_state_update_message_type(), update_message); + + if (message) { + stasis_publish(state->extension_state_topic, message); + ao2_ref(message, -1); + } + + ao2_ref(update_message, -1); + } + + ao2_unlock(state); + + ast_free(presence_subtype); + ast_free(presence_message); + } + ao2_iterator_destroy(&iter); + ao2_unlock(extension_states); +} + +/*! + * \internal + * + * \brief Allocate an extension state object + * \param exten The extension + * \param context The context + * \retval A pointer to the allocated extension state, or NULL on failure + * + * This function allocates an extension state object with the specified extension and context. + */ +static struct extension_state *extension_state_alloc(struct ast_exten *exten, struct ast_context *context) +{ + char extension[AST_MAX_EXTENSION + AST_MAX_CONTEXT + 2]; + struct extension_state *state; + char *extension_state_topic_name; + + snprintf(extension, sizeof(extension), "%s@%s", ast_get_extension_name(exten), ast_get_context_name(context)); + + /* + * Each individual extension state has its own lock to ensure that when updating it + * we do not cause problems for either the existing topic ingesting updates + * or any access to the extension state cached message. + */ + state = ao2_alloc(sizeof(*state) + strlen(extension) + 1, extension_state_destroy); + if (!state) { + return NULL; + } + + ast_copy_string(state->dialplan_context, ast_get_context_name(context), sizeof(state->dialplan_context)); + ast_copy_string(state->dialplan_extension, ast_get_extension_name(exten), sizeof(state->dialplan_extension)); + strcpy(state->extension, extension); /* Safe */ + AST_VECTOR_INIT(&state->device_state_sources, 0); + + /* These are the default if no sources are present */ + state->device_snapshot = extension_state_device_snapshot_create(AST_EXTENSION_UNAVAILABLE, + &state->device_state_sources, NULL); + state->presence_snapshot = extension_state_presence_snapshot_create(AST_PRESENCE_NOT_SET, NULL, NULL); + if (!state->device_snapshot || !state->presence_snapshot) { + ao2_ref(state, -1); + return NULL; + } + + /* + * We don't actually access the contents of exten past guarantee of it being valid so we can safely + * store a pointer to just do pointer comparison. + */ + state->hint_extension = exten; + + /* Pattern match extensions don't have sources or a topic, so return early */ + if (extension[0] == '_') { + return state; + } + + /* We most likely have at least one device state source */ + if (AST_VECTOR_INIT(&state->device_state_sources, 1)) { + ao2_ref(state, -1); + return NULL; + } + + if (ast_asprintf(&extension_state_topic_name, "extension_state:extension/%s", extension) < 0) { + ao2_ref(state, -1); + return NULL; + } + + state->extension_state_topic = stasis_topic_create(extension_state_topic_name); + ast_free(extension_state_topic_name); + if (!state->extension_state_topic) { + ao2_ref(state, -1); + return NULL; + } + + state->extension_state_forwarder = stasis_forward_all(state->extension_state_topic, extension_state_topic_all); + if (!state->extension_state_forwarder) { + ao2_ref(state, -1); + return NULL; + } + + return state; +} + +/*! + * \internal + * + * \brief Get an extension state object + * \param chan The channel + * \param context The context + * \param extension The extension + * \retval A pointer to the extension state, or NULL on failure + * + * This function gets an extension state object for the specified channel, context, and extension. + * If the extension state does not exist due to being from a pattern match, it will be created. + */ +static struct extension_state *extension_state_get(struct ast_channel *chan, const char *context, const char *extension) +{ + struct extension_state *state; + struct ast_exten *hint_exten; + struct pbx_find_info q = { .stacklen = 0 }; + char location[AST_MAX_EXTENSION + AST_MAX_CONTEXT + 2]; + + /* We optimistically search for the extension state using the provided context and extension */ + snprintf(location, sizeof(location), "%s@%s", extension, context); + state = ao2_find(extension_states, location, OBJ_SEARCH_KEY); + if (state) { + return state; + } + + /* + * Pattern match extensions do exist within extension state for the purposes of listing them out, + * but they can't resolve down to anything else + */ + if (extension[0] == '_') { + return NULL; + } + + ast_wrlock_contexts(); + + /* + * We can't use the provided context and extension as-is because an include + * could have resulted in the context being different than what was provided. + * To handle this we query the dialplan to find where the hint actually is. + * We also need to do this to determine if this is a pattern match or an explicit + * extension. + */ + hint_exten = pbx_find_extension(chan, NULL, &q, context, extension, + PRIORITY_HINT, NULL, "", E_MATCH); + if (!hint_exten) { + /* + * The extension must ALWAYS exist in the dialplan in some capacity. It is + * either in the dialplan as an explicit extension or a pattern match. + */ + ast_unlock_contexts(); + return NULL; + } + + if (ast_get_extension_name(hint_exten)[0] == '_') { + /* + * If this resolved down to a pattern match that means this is the first request + * for this explicit extension so we need to add it to the dialplan which will create + * an extension state for it. It's possible for us to conflict with another thread but + * in that case the ast_add_extension call will fail and be a no-op and we will return + * the extension state the other thread created. + */ + ast_add_extension(q.foundcontext, 0, extension, PRIORITY_HINT, ast_get_extension_label(hint_exten), + ast_get_extension_matchcid(hint_exten) ? ast_get_extension_cidmatch(hint_exten) : NULL, + ast_get_extension_app(hint_exten), ast_strdup(ast_get_extension_app_data(hint_exten)), ast_free_ptr, + ast_get_extension_registrar(hint_exten)); + } + + /* The extension state should already exist at this point */ + snprintf(location, sizeof(location), "%s@%s", extension, q.foundcontext); + ast_unlock_contexts(); + + return ao2_find(extension_states, location, OBJ_SEARCH_KEY); +} + +struct ast_channel *ast_extension_state_get_device_causing_channel(const char *device, enum ast_device_state device_state) +{ + enum ast_channel_state search_state = 0; /* prevent false uninit warning */ + char match[AST_CHANNEL_NAME]; + struct ast_channel_iterator *chan_iter; + struct ast_channel *chan, *channel = NULL; + struct timeval chantime = {0, }; /* prevent false uninit warning */ + + switch (device_state) { + case AST_DEVICE_RINGING: + case AST_DEVICE_RINGINUSE: + /* find ringing channel */ + search_state = AST_STATE_RINGING; + break; + case AST_DEVICE_BUSY: + /* find busy channel */ + search_state = AST_STATE_BUSY; + break; + case AST_DEVICE_ONHOLD: + case AST_DEVICE_INUSE: + /* find up channel */ + search_state = AST_STATE_UP; + break; + case AST_DEVICE_UNKNOWN: + case AST_DEVICE_NOT_INUSE: + case AST_DEVICE_INVALID: + case AST_DEVICE_UNAVAILABLE: + case AST_DEVICE_TOTAL /* not a state */: + /* no channels are of interest */ + return NULL; + } + + /* iterate over all channels of the device */ + snprintf(match, sizeof(match), "%s-", device); + chan_iter = ast_channel_iterator_by_name_new(match, strlen(match)); + for (; (chan = ast_channel_iterator_next(chan_iter)); ast_channel_unref(chan)) { + ast_channel_lock(chan); + /* this channel's state doesn't match */ + if (search_state != ast_channel_state(chan)) { + ast_channel_unlock(chan); + continue; + } + /* any non-ringing channel will fit */ + if (search_state != AST_STATE_RINGING) { + ast_channel_unlock(chan); + channel = chan; + break; + } + /* but we need the oldest ringing channel of the device to match with undirected pickup */ + if (!channel) { + chantime = ast_channel_creationtime(chan); + ast_channel_ref(chan); /* must ref it! */ + channel = chan; + } else if (ast_tvcmp(ast_channel_creationtime(chan), chantime) < 0) { + chantime = ast_channel_creationtime(chan); + ast_channel_unref(channel); + ast_channel_ref(chan); /* must ref it! */ + channel = chan; + } + ast_channel_unlock(chan); + } + ast_channel_iterator_destroy(chan_iter); + + return channel; +} + +enum ast_extension_states ast_devstate_to_extenstate(enum ast_device_state devstate) +{ + switch (devstate) { + case AST_DEVICE_ONHOLD: + return AST_EXTENSION_ONHOLD; + case AST_DEVICE_BUSY: + return AST_EXTENSION_BUSY; + case AST_DEVICE_UNKNOWN: + return AST_EXTENSION_NOT_INUSE; + case AST_DEVICE_UNAVAILABLE: + case AST_DEVICE_INVALID: + return AST_EXTENSION_UNAVAILABLE; + case AST_DEVICE_RINGINUSE: + return (AST_EXTENSION_INUSE | AST_EXTENSION_RINGING); + case AST_DEVICE_RINGING: + return AST_EXTENSION_RINGING; + case AST_DEVICE_INUSE: + return AST_EXTENSION_INUSE; + case AST_DEVICE_NOT_INUSE: + return AST_EXTENSION_NOT_INUSE; + case AST_DEVICE_TOTAL: /* not a device state, included for completeness */ + break; + } + + return AST_EXTENSION_NOT_INUSE; +} + +const char *ast_extension_state2str(int extension_state) +{ + int i; + + for (i = 0; (i < ARRAY_LEN(extension_state_mappings)); i++) { + if (extension_state_mappings[i].extension_state == extension_state) + return extension_state_mappings[i].text; + } + return "Unknown"; +} + +/*! + * \internal + * \brief Handle the ExtensionStateList Manager action + * + * \param s The manager session + * \param m The manager message + * \retval 0 on success, -1 on failure + * + * This function handles the ExtensionStateList Manager action by returning a list of all extension states. + */ +static int action_extensionstatelist(struct mansession *s, const struct message *m) +{ + const char *action_id = astman_get_header(m, "ActionID"); + + if (!ao2_container_count(extension_states)) { + astman_send_error(s, m, "No dialplan hints are available"); + return 0; + } + + astman_send_listack(s, m, "Extension Statuses will follow", "start"); + + ao2_lock(extension_states); + if (ao2_container_count(extension_states)) { + struct ao2_iterator it_states; + struct extension_state *state; + int count = 0; + + it_states = ao2_iterator_init(extension_states, 0); + for (; (state = ao2_iterator_next(&it_states)); ao2_ref(state, -1)) { + if (state->extension[0] == '_') { + continue; + } + + ++count; + + astman_append(s, "Event: ExtensionStatus\r\n"); + if (!ast_strlen_zero(action_id)) { + astman_append(s, "ActionID: %s\r\n", action_id); + } + ao2_lock(state); + astman_append(s, + "Exten: %s\r\n" + "Context: %s\r\n" + "Hint: %s\r\n" + "Status: %d\r\n" + "StatusText: %s\r\n\r\n", + state->dialplan_extension, + state->dialplan_context, + state->hint_extension ? ast_get_extension_app(state->hint_extension) : "None", + state->device_snapshot->state, + ast_extension_state2str(state->device_snapshot->state)); + ao2_unlock(state); + } + ao2_iterator_destroy(&it_states); + astman_send_list_complete_start(s, m, "ExtensionStateListComplete", count); + astman_send_list_complete_end(s); + } else { + astman_send_error(s, m, "No dialplan hints are available"); + } + + ao2_unlock(extension_states); + + return 0; +} + +void pbx_extension_state_hint_set(struct ast_exten *exten, struct ast_context *context) +{ + char extension[AST_MAX_EXTENSION + AST_MAX_CONTEXT + 2]; + struct extension_state *state; + + snprintf(extension, sizeof(extension), "%s@%s", ast_get_extension_name(exten), ast_get_context_name(context)); + + ao2_lock(extension_states); + + state = ao2_find(extension_states, extension, OBJ_SEARCH_KEY | OBJ_NOLOCK); + if (!state) { + state = extension_state_alloc(exten, context); + if (!state) { + ast_log(LOG_WARNING, "Could not create extension state for hint '%s', it will be unavailable\n", + extension); + ao2_unlock(extension_states); + return; + } + ao2_link_flags(extension_states, state, OBJ_NOLOCK); + } + + state->hint_extension = exten; + if (extension[0] != '_') { + extension_state_update_sources(state, exten); + } + ao2_ref(state, -1); + + ao2_unlock(extension_states); +} + +void pbx_extension_state_hint_remove(struct ast_exten *exten, struct ast_context *context) +{ + char extension[AST_MAX_EXTENSION + AST_MAX_CONTEXT + 2]; + struct extension_state *state; + + snprintf(extension, sizeof(extension), "%s@%s", ast_get_extension_name(exten), ast_get_context_name(context)); + + ao2_lock(extension_states); + + state = ao2_find(extension_states, extension, OBJ_SEARCH_KEY | OBJ_NOLOCK); + + /* If this is not the latest hint extension that configured this extension state it can't remove it */ + if (!state || state->hint_extension != exten) { + ao2_unlock(extension_states); + ao2_cleanup(state); + return; + } + + extension_state_shutdown(state); + ao2_unlink_flags(extension_states, state, OBJ_NOLOCK); + ao2_ref(state, -1); + + ao2_unlock(extension_states); +} + +struct stasis_topic *ast_extension_state_topic_all(void) +{ + return extension_state_topic_all; +} + +struct stasis_topic *ast_extension_state_topic(const char *exten, const char *context) +{ + struct extension_state *state; + struct stasis_topic *topic; + + state = extension_state_get(NULL, context, exten); + if (!state) { + return NULL; + } + + ao2_lock(state); + topic = ao2_bump(state->extension_state_topic); + ao2_unlock(state); + ao2_ref(state, -1); + + return topic; +} + +struct ast_extension_state_device_snapshot *ast_extension_state_get_latest_device_snapshot(struct ast_channel *chan, + const char *exten, const char *context) +{ + struct extension_state *state; + struct ast_extension_state_device_snapshot *device_snapshot; + + state = extension_state_get(chan, context, exten); + if (!state) { + return NULL; + } + + ao2_lock(state); + device_snapshot = ao2_bump(state->device_snapshot); + ao2_unlock(state); + ao2_ref(state, -1); + + return device_snapshot; +} + +struct ast_extension_state_presence_snapshot *ast_extension_state_get_latest_presence_snapshot(struct ast_channel *chan, + const char *exten, const char *context) +{ + struct extension_state *state; + struct ast_extension_state_presence_snapshot *presence_snapshot; + + state = extension_state_get(chan, context, exten); + if (!state) { + return NULL; + } + + ao2_lock(state); + presence_snapshot = ao2_bump(state->presence_snapshot); + ao2_unlock(state); + ao2_ref(state, -1); + + return presence_snapshot; +} + +/*! + * \internal + * \brief CLI command to show hints + * + * \param e The CLI entry + * \param cmd The command + * \param a The CLI arguments + * + * This function shows all registered hints in the CLI. + */ +static char *handle_show_hints(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a) +{ + struct extension_state *state; + int num = 0; + struct ao2_iterator i; + + switch (cmd) { + case CLI_INIT: + e->command = "core show hints"; + e->usage = + "Usage: core show hints\n" + " List registered hints.\n" + " Hint details are shown in five columns. In order from left to right, they are:\n" + " 1. Hint extension URI.\n" + " 2. List of mapped device or presence state identifiers.\n" + " 3. Current extension state. The aggregate of mapped device states.\n" + " 4. Current presence state for the mapped presence state provider.\n" + " 5. Watchers - number of subscriptions and other entities watching this hint.\n"; + return NULL; + case CLI_GENERATE: + return NULL; + } + + if (ao2_container_count(extension_states) == 0) { + ast_cli(a->fd, "There are no registered dialplan hints\n"); + return CLI_SUCCESS; + } + /* ... we have hints ... */ + ast_cli(a->fd, "\n -= Registered Asterisk Dial Plan Hints =-\n"); + + i = ao2_iterator_init(extension_states, 0); + for (; (state = ao2_iterator_next(&i)); ao2_ref(state, -1)) { + ao2_lock(state); + ast_cli(a->fd, "%-30.30s: %-60.60s State:%-15.15s Presence:%-15.15s Watchers %2zd\n", + state->extension, + state->hint_extension ? ast_get_extension_app(state->hint_extension) : "None", + ast_extension_state2str(state->device_snapshot->state), + ast_presence_state2str(state->presence_snapshot->presence_state), + state->extension_state_topic ? stasis_topic_subscribers(state->extension_state_topic) : 0); + ao2_unlock(state); + + num++; + } + ao2_iterator_destroy(&i); + + ast_cli(a->fd, "----------------\n"); + ast_cli(a->fd, "- %d hints registered\n", num); + return CLI_SUCCESS; +} + +/*! + * \internal + * \brief Complete the core show hint CLI command + * + * \param line The command line + * \param word The word being completed + * \param pos The position in the command + * \param state The completion state + * + * This function completes the core show hint CLI command. + */ +static char *complete_core_show_hint(const char *line, const char *word, int pos, int state) +{ + struct extension_state *extstate; + char *ret = NULL; + int which = 0; + int wordlen; + struct ao2_iterator i; + + if (pos != 3) + return NULL; + + wordlen = strlen(word); + + /* walk through all hints */ + i = ao2_iterator_init(extension_states, 0); + for (; (extstate = ao2_iterator_next(&i)); ao2_ref(extstate, -1)) { + ao2_lock(extstate); + if (!strncasecmp(word, extstate->dialplan_extension, wordlen) && ++which > state) { + ret = ast_strdup(extstate->dialplan_extension); + ao2_unlock(extstate); + ao2_ref(extstate, -1); + break; + } + ao2_unlock(extstate); + } + ao2_iterator_destroy(&i); + + return ret; +} + +/*! + * \internal + * \brief CLI support for listing registered dial plan hint + * + * \param e The CLI entry + * \param cmd The command + * \param a The CLI arguments + * + * This function handles the core show hint CLI command. + */ +static char *handle_show_hint(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a) +{ + struct extension_state *extstate; + int num = 0, extenlen; + struct ao2_iterator i; + + switch (cmd) { + case CLI_INIT: + e->command = "core show hint"; + e->usage = + "Usage: core show hint \n" + " List registered hint.\n" + " Hint details are shown in five columns. In order from left to right, they are:\n" + " 1. Hint extension URI.\n" + " 2. List of mapped device or presence state identifiers.\n" + " 3. Current extension state. The aggregate of mapped device states.\n" + " 4. Current presence state for the mapped presence state provider.\n" + " 5. Watchers - number of subscriptions and other entities watching this hint.\n"; + return NULL; + case CLI_GENERATE: + return complete_core_show_hint(a->line, a->word, a->pos, a->n); + } + + if (a->argc < 4) + return CLI_SHOWUSAGE; + + if (ao2_container_count(extension_states) == 0) { + ast_cli(a->fd, "There are no registered dialplan hints\n"); + return CLI_SUCCESS; + } + + extenlen = strlen(a->argv[3]); + i = ao2_iterator_init(extension_states, 0); + for (; (extstate = ao2_iterator_next(&i)); ao2_ref(extstate, -1)) { + ao2_lock(extstate); + if (!strncasecmp(extstate->extension, a->argv[3], extenlen)) { + ast_cli(a->fd, "%-30.30s: %-60.60s State:%-15.15s Presence:%-15.15s Watchers %2zd\n", + extstate->extension, + extstate->hint_extension ? ast_get_extension_app(extstate->hint_extension) : "None", + ast_extension_state2str(extstate->device_snapshot->state), + ast_presence_state2str(extstate->presence_snapshot->presence_state), + extstate->extension_state_topic ? stasis_topic_subscribers(extstate->extension_state_topic) : 0); + num++; + } + ao2_unlock(extstate); + } + ao2_iterator_destroy(&i); + if (!num) + ast_cli(a->fd, "No hints matching extension %s\n", a->argv[3]); + else + ast_cli(a->fd, "%d hint%s matching extension %s\n", num, (num!=1 ? "s":""), a->argv[3]); + return CLI_SUCCESS; +} + +static struct ast_cli_entry extension_state_cli[] = { + AST_CLI_DEFINE(handle_show_hints, "Show dialplan hints"), + AST_CLI_DEFINE(handle_show_hint, "Show dialplan hint"), +}; + +/*! + * \internal + * \brief Callback function to clean up an individual extension state. + * + * \param obj The extension state object + * \param arg Additional argument (not used) + * \param flags Flags for the callback + * \return CMP_MATCH if the object was processed, 0 otherwise + */ +static int extension_state_cleanup_individual(void *obj, void *arg, int flags) +{ + struct extension_state *state = obj; + + extension_state_shutdown(state); + + return CMP_MATCH; +} + +/*! + * \internal + * \brief Clean up the extension state subsystem, called at shutdown. + */ +static void extension_state_cleanup(void) +{ + ast_cli_unregister_multiple(extension_state_cli, ARRAY_LEN(extension_state_cli)); + ast_manager_unregister("ExtensionStateList"); + if (extension_states) { + ao2_callback(extension_states, OBJ_NODATA | OBJ_MULTIPLE | OBJ_UNLINK, extension_state_cleanup_individual, NULL); + ao2_ref(extension_states, -1); + } + presence_state_sub = stasis_unsubscribe_and_join(presence_state_sub); + STASIS_MESSAGE_TYPE_CLEANUP(ast_extension_state_update_message_type); + STASIS_MESSAGE_TYPE_CLEANUP(ast_extension_state_remove_message_type); +} + +int ast_extension_state_init(void) +{ + if (STASIS_MESSAGE_TYPE_INIT(ast_extension_state_update_message_type) != 0) { + return -1; + } + + if (STASIS_MESSAGE_TYPE_INIT(ast_extension_state_remove_message_type) != 0) { + return -1; + } + + /* Initialize extension state subsystem */ + extension_states = ao2_container_alloc_rbtree(AO2_ALLOC_OPT_LOCK_MUTEX, 0, + extension_state_sort_fn, extension_state_cmp_fn); + if (!extension_states) { + ast_log(LOG_ERROR, "Failed to allocate new states container\n"); + return -1; + } + + extension_state_topic_all = stasis_topic_create("extension_state:all"); + if (!extension_state_topic_all) { + ast_log(LOG_ERROR, "Failed to create extension state topic\n"); + return -1; + } + + presence_state_sub = stasis_subscribe(ast_presence_state_topic_all(), extension_state_presence_state_cb, NULL); + if (!presence_state_sub) { + ast_log(LOG_ERROR, "Failed to create subscription to receive presence state updates\n"); + return -1; + } + stasis_subscription_accept_message_type(presence_state_sub, ast_presence_state_message_type()); + stasis_subscription_set_filter(presence_state_sub, STASIS_SUBSCRIPTION_FILTER_SELECTIVE); + + ast_cli_register_multiple(extension_state_cli, ARRAY_LEN(extension_state_cli)); + ast_manager_register_xml_core("ExtensionStateList", EVENT_FLAG_CALL | EVENT_FLAG_REPORTING, action_extensionstatelist); + ast_register_cleanup(extension_state_cleanup); + + return 0; +} diff --git a/main/extension_state_autohints.c b/main/extension_state_autohints.c new file mode 100644 index 0000000000..66e0bf9334 --- /dev/null +++ b/main/extension_state_autohints.c @@ -0,0 +1,183 @@ +/* + * Asterisk -- An open source telephony toolkit. + * + * Copyright (C) 2026, Sangoma Technologies Corporation + * + * Joshua Colp + * + * See https://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. + */ + +/*** MODULEINFO + core + ***/ + +#include "asterisk.h" +#include "asterisk/_private.h" +#include "asterisk/pbx.h" +#include "asterisk/stasis.h" +#include "asterisk/vector.h" +#include "pbx_private.h" + +/*! \brief Lock to protect the autohints vector */ +AST_MUTEX_DEFINE_STATIC(extension_state_autohints_lock); + +/*! \brief Contexts which have autohints enabled */ +static AST_VECTOR(, struct ast_context *) extension_state_autohints; + +/*! \brief Subscription to receive updates so we can create hints as needed on autohint enabled contexts */ +static struct stasis_subscription *autohints_subscription; + +/*! \brief The static registrar for the added dialplan hints */ +static const char registrar[] = "autohints"; + +/*! + * \internal + * \brief Callback for device state updates to create hints for autohint enabled contexts. + * + * \param userdata The user data passed to the subscription. + * \param sub The subscription that received the message. + * \param msg The message received by the subscription. + */ +static void extension_state_autohints_device_state_cb(void *userdata, struct stasis_subscription *sub, + struct stasis_message *msg) +{ + struct ast_device_state_message *device_state; + char *virtual_device, *type, *device_name; + int i; + + if (stasis_message_type(msg) != ast_device_state_message_type()) { + return; + } + + device_state = stasis_message_data(msg); + + /* We only care about the aggregate state */ + if (device_state->eid) { + return; + } + + type = ast_strdupa(device_state->device); + if (ast_strlen_zero(type)) { + return; + } + + /* 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)) { + return; + } + *device_name++ = '\0'; + + ast_wrlock_contexts(); + + ast_mutex_lock(&extension_state_autohints_lock); + for (i = 0; i < AST_VECTOR_SIZE(&extension_state_autohints); i++) { + struct ast_context *context = AST_VECTOR_GET(&extension_state_autohints, i); + struct pbx_find_info q = { .stacklen = 0 }; + + if (pbx_find_extension(NULL, NULL, &q, ast_get_context_name(context), device_name, + PRIORITY_HINT, NULL, "", E_MATCH)) { + continue; + } + + /* The device has no hint in the context referenced by this autohint so create one */ + ast_add_extension(ast_get_context_name(context), 0, device_name, PRIORITY_HINT, NULL, NULL, + device_state->device, ast_strdup(device_state->device), ast_free_ptr, registrar); + } + ast_mutex_unlock(&extension_state_autohints_lock); + + ast_unlock_contexts(); +} + +/*! + * \internal + * \brief Compare an autohint's context name to a provided context name for searching the autohints vector + */ +#define AUTOHINT_CMP_CONTEXT_NAME(elem, name) (!strcmp(ast_get_context_name(elem), name)) + +void pbx_extension_state_autohint_set(struct ast_context *context) +{ + ast_rdlock_contexts(); + + ast_mutex_lock(&extension_state_autohints_lock); + + /* Since we store a pointer to the context we remove the old one by name and append the new one, easy */ + AST_VECTOR_REMOVE_CMP_UNORDERED(&extension_state_autohints, ast_get_context_name(context), AUTOHINT_CMP_CONTEXT_NAME, + AST_VECTOR_ELEM_CLEANUP_NOOP); + AST_VECTOR_APPEND(&extension_state_autohints, context); + + if (AST_VECTOR_SIZE(&extension_state_autohints) && !autohints_subscription) { + /* We have at least one context enabled with autohints and no subscription, so subscribe */ + autohints_subscription = stasis_subscribe(ast_device_state_topic_all(), extension_state_autohints_device_state_cb, NULL); + } + ast_mutex_unlock(&extension_state_autohints_lock); + + ast_unlock_contexts(); +} + +void pbx_extension_state_autohint_remove(struct ast_context *context, unsigned int forced) +{ + int removed; + + ast_wrlock_contexts(); + + ast_mutex_lock(&extension_state_autohints_lock); + if (!forced) { + removed = AST_VECTOR_REMOVE_CMP_UNORDERED(&extension_state_autohints, context, AST_VECTOR_ELEM_DEFAULT_CMP, + AST_VECTOR_ELEM_CLEANUP_NOOP); + } else { + removed = AST_VECTOR_REMOVE_CMP_UNORDERED(&extension_state_autohints, ast_get_context_name(context), AUTOHINT_CMP_CONTEXT_NAME, + AST_VECTOR_ELEM_CLEANUP_NOOP); + } + if (removed) { + ast_context_destroy_by_name(ast_get_context_name(context), registrar); + } + if (!AST_VECTOR_SIZE(&extension_state_autohints) && autohints_subscription) { + /* There's no more autohint enabled contexts, so unsubscribe */ + autohints_subscription = stasis_unsubscribe(autohints_subscription); + } + ast_mutex_unlock(&extension_state_autohints_lock); + + ast_unlock_contexts(); +} + +/*! + * \internal + * \brief Clean up the autohints extension state system, called at shutdown. + */ +static void extension_state_autohints_cleanup(void) +{ + ast_mutex_lock(&extension_state_autohints_lock); + if (autohints_subscription) { + autohints_subscription = stasis_unsubscribe(autohints_subscription); + } + /* The vector just contains pointers so no need to free the individual items */ + AST_VECTOR_FREE(&extension_state_autohints); + ast_mutex_unlock(&extension_state_autohints_lock); +} + +int ast_extension_state_autohints_init(void) +{ + /* Most likely there will be at most one context configured for autohints, or zero */ + if (AST_VECTOR_INIT(&extension_state_autohints, 1)) { + return -1; + } + + ast_register_cleanup(extension_state_autohints_cleanup); + return 0; +} diff --git a/main/extension_state_legacy.c b/main/extension_state_legacy.c new file mode 100644 index 0000000000..936db2a9fe --- /dev/null +++ b/main/extension_state_legacy.c @@ -0,0 +1,477 @@ +/* + * Asterisk -- An open source telephony toolkit. + * + * Copyright (C) 2026, Sangoma Technologies Corporation + * + * Joshua Colp + * + * See https://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. + */ + +/*** MODULEINFO + core + ***/ + +#include "asterisk.h" +#include "asterisk/_private.h" +#include "asterisk/module.h" +#include "asterisk/extension_state.h" +#include "asterisk/pbx.h" +#include "asterisk/stasis.h" +#include "asterisk/stasis_message_router.h" +#include "asterisk/astobj2.h" +#include "asterisk/lock.h" +#include "asterisk/vector.h" + +struct extension_state_legacy_state_cb { + /*! Watcher ID returned when registered. */ + int id; + /*! Message router for the traffic to this callback */ + struct stasis_message_router *router; + /*! Arbitrary data passed for callbacks. */ + void *data; + /*! Flag if this callback is an extended callback containing detailed device status */ + int extended; + /*! Callback when state changes. */ + ast_state_cb_type change_cb; + /*! Callback when destroyed so any resources given by the registerer can be freed. */ + ast_state_cb_destroy_type destroy_cb; +}; + +/*! \brief Lock to protect the callbacks vector */ +AST_MUTEX_DEFINE_STATIC(extension_state_legacy_callbacks_lock); + +/*! \brief Legacy callbacks, the index of it in the vector is the id given to the API user for per-extension */ +static AST_VECTOR(, struct extension_state_legacy_state_cb *) extension_state_legacy_callbacks; + +int ast_extension_state(struct ast_channel *c, const char *context, const char *exten) +{ + struct ast_extension_state_device_snapshot *device_snapshot; + enum ast_extension_states device_state; + + device_snapshot = ast_extension_state_get_latest_device_snapshot(c, exten, context); + if (!device_snapshot) { + return -1; + } + + device_state = device_snapshot->state; + ao2_ref(device_snapshot, -1); + + return device_state; +} + +int ast_hint_presence_state(struct ast_channel *c, const char *context, const char *exten, char **subtype, char **message) +{ + struct ast_extension_state_presence_snapshot *presence_snapshot; + enum ast_presence_state presence_state; + + presence_snapshot = ast_extension_state_get_latest_presence_snapshot(c, exten, context); + if (!presence_snapshot) { + return -1; + } + + presence_state = presence_snapshot->presence_state; + if (presence_snapshot->presence_subtype) { + *subtype = ast_strdup(presence_snapshot->presence_subtype); + } + if (presence_snapshot->presence_message) { + *message = ast_strdup(presence_snapshot->presence_message); + } + + ao2_ref(presence_snapshot, -1); + + return presence_state; +} + +/*! + * \internal + * \brief Destroy function for device state info objects. + * + * \param obj The device state info object to destroy. + */ +static void device_state_info_destroy(void *obj) +{ + struct ast_device_state_info *info = obj; + + ao2_cleanup(info->causing_channel); +} + +/*! + * \internal + * \brief Create a container of device state info objects from an extension device state message. + * + * \param device_state_message The extension device state message to create device state info from. + * \return A container of device state info objects, or NULL on failure. + */ +static struct ao2_container *extension_state_legacy_create_device_state_info(struct ast_extension_state_device_snapshot *device_snapshot) +{ + struct ao2_container *device_state_info = ao2_container_alloc_list(AO2_ALLOC_OPT_LOCK_NOLOCK, 0, NULL, NULL); + int i; + + if (!device_state_info) { + return NULL; + } + + for (i = 0; i < AST_VECTOR_SIZE(&device_snapshot->additional_devices); i++) { + struct ast_extension_state_device_state_info *source_info = AST_VECTOR_GET(&device_snapshot->additional_devices, i); + struct ast_device_state_info *obj; + + obj = ao2_alloc_options(sizeof(*obj) + strlen(source_info->device) + 1, device_state_info_destroy, AO2_ALLOC_OPT_LOCK_NOLOCK); + if (!obj) { + ao2_ref(device_state_info, -1); + return NULL; + } + + obj->device_state = source_info->state; + strcpy(obj->device_name, source_info->device); /* Safe */ + obj->causing_channel = ast_extension_state_get_device_causing_channel(source_info->device, source_info->state); + ao2_link(device_state_info, obj); + ao2_ref(obj, -1); + } + + return device_state_info; +} + +int ast_extension_state_extended(struct ast_channel *c, const char *context, const char *exten, + struct ao2_container **device_state_info) +{ + struct ast_extension_state_device_snapshot *device_snapshot; + enum ast_extension_states device_state; + + device_snapshot = ast_extension_state_get_latest_device_snapshot(c, exten, context); + if (!device_snapshot) { + return -1; + } + + device_state = device_snapshot->state; + + /* The caller wants device state info, so allocate a container and populate it */ + if (device_state_info) { + *device_state_info = extension_state_legacy_create_device_state_info(device_snapshot); + } + + ao2_ref(device_snapshot, -1); + + return device_state; +} + +/*! + * \internal + * \brief Callback for subscription state change, used for reference handling. + * + * \param userdata The user data passed to the subscription. + * \param sub The subscription that received the message. + * \param msg The message received by the subscription. + */ +static void extension_state_legacy_subscription_change_cb(void *userdata, struct stasis_subscription *sub, + struct stasis_message *msg) +{ + if (stasis_subscription_final_message(sub, msg)) { + ao2_cleanup(userdata); + } +} + +/*! + * \internal + * \brief Destructor for \ref extension_state_legacy_state_cb. + * + * \param obj The extension state legacy state callback object to destroy. + */ +static void extension_state_legacy_state_cb_destroy(void *obj) +{ + struct extension_state_legacy_state_cb *cb = obj; + + if (cb->destroy_cb) { + cb->destroy_cb(cb->id, cb->data); + } +} + +/*! + * \internal + * \brief Callback for extension state updates. + * + * \param userdata The user data passed to the subscription. + * \param sub The subscription that received the message. + * \param msg The message received by the subscription. + */ +static void extension_state_legacy_update_cb(void *userdata, struct stasis_subscription *sub, + struct stasis_message *msg) +{ + struct extension_state_legacy_state_cb *cb = userdata; + struct ast_extension_state_update_message *update_message = stasis_message_data(msg); + struct ast_state_cb_info info = { + .exten_state = update_message->new_device_snapshot->state, + .presence_state = update_message->new_presence_snapshot->presence_state, + .presence_subtype = S_OR(update_message->new_presence_snapshot->presence_subtype, ""), + .presence_message = S_OR(update_message->new_presence_snapshot->presence_message, ""), + }; + + /* If the presence has changed, notify the callback */ + if (update_message->new_presence_snapshot != update_message->old_presence_snapshot) { + info.reason = AST_HINT_UPDATE_PRESENCE; + cb->change_cb(update_message->context, update_message->extension, &info, cb->data); + } + + /* If the device state has changed, notify the callback */ + if (update_message->new_device_snapshot != update_message->old_device_snapshot) { + info.reason = AST_HINT_UPDATE_DEVICE; + + /* If they want extended information we need to provide the channels */ + if (cb->extended) { + info.device_state_info = extension_state_legacy_create_device_state_info(update_message->new_device_snapshot); + } + + cb->change_cb(update_message->context, update_message->extension, &info, cb->data); + + ao2_cleanup(info.device_state_info); + } +} + +/*! + * \internal + * \brief Callback for extension state removal updates. + * + * \param userdata The user data passed to the subscription. + * \param sub The subscription that received the message. + * \param msg The message received by the subscription. + */ +static void extension_state_legacy_remove_cb(void *userdata, struct stasis_subscription *sub, + struct stasis_message *msg) +{ + struct extension_state_legacy_state_cb *cb = userdata; + struct ast_extension_state_remove_message *remove_message = stasis_message_data(msg); + struct ast_state_cb_info info = { + .reason = AST_HINT_UPDATE_DEVICE, + .exten_state = AST_EXTENSION_REMOVED, + }; + + cb->change_cb(remove_message->context, remove_message->extension, &info, cb->data); +} + +/*! + * \internal + * \brief Add a legacy extension state callback. + * + * \param context The context to monitor. + * \param exten The extension to monitor. + * \param change_cb The callback to call when the extension state changes. + * \param destroy_cb The callback to call when the callback is removed. + * \param data The data to pass to the callback. + * \param extended Whether to include extended information in the callback. + * \return The ID of the callback, or -1 on failure. + */ +static int extension_state_legacy_add_destroy(const char *context, const char *exten, + ast_state_cb_type change_cb, ast_state_cb_destroy_type destroy_cb, void *data, int extended) +{ + struct extension_state_legacy_state_cb *state_cb; + int id; + + state_cb = ao2_alloc_options(sizeof(*state_cb), extension_state_legacy_state_cb_destroy, AO2_ALLOC_OPT_LOCK_NOLOCK); + if (!state_cb) { + return -1; + } + + state_cb->change_cb = change_cb; + state_cb->destroy_cb = destroy_cb; + state_cb->data = data; + state_cb->extended = extended; + + ast_mutex_lock(&extension_state_legacy_callbacks_lock); + + /* + * Callbacks for both per-extension and all are stored in a single vector which may have gaps in it. + * When adding a new callback, we look for the first gap in the vector and insert the callback there. + * If there are no gaps, we append it to the end of the vector. + * For per-extension the ID of a callback is its index in the vector + 1, since 0 is reserved for "all" callbacks. + */ + for (id = 0; id < AST_VECTOR_SIZE(&extension_state_legacy_callbacks); id++) { + if (AST_VECTOR_GET(&extension_state_legacy_callbacks, id)) { + continue; + } + + state_cb->id = id + 1; + + /* This can't fail since the vector would have already resized */ + AST_VECTOR_REPLACE(&extension_state_legacy_callbacks, id, state_cb); + + break; + } + + if (!state_cb->id) { + /* The vector will resize when we append, which can fail, so handle it */ + if (AST_VECTOR_APPEND(&extension_state_legacy_callbacks, state_cb)) { + ast_mutex_unlock(&extension_state_legacy_callbacks_lock); + ao2_ref(state_cb, -1); + return -1; + } + state_cb->id = AST_VECTOR_SIZE(&extension_state_legacy_callbacks); + } + + /* At this point it is guaranteed that the callback has been inserted so we can setup + * the message router to accept messages from the appropriate topic and translate into + * the legacy callback. + */ + if (!context && !exten) { + /* + * The all topic will receive all extension state updates which can end up being quite + * a lot, so we use a dedicated thread for each legacy callback to ensure that the + * pool of stasis threads does not become overloaded. + */ + state_cb->router = stasis_message_router_create(ast_extension_state_topic_all()); + } else { + struct stasis_topic *topic = ast_extension_state_topic(exten, context); + + /* + * Per-extension on the other hand will have comparatively few extension state updates + * so we use the pool for it instead. Additionally the creation of the message router will + * fail if topic is NULL, so we don't do an explicit check and just let it try. + */ + state_cb->router = stasis_message_router_create_pool(topic); + ao2_cleanup(topic); + } + + /* If there is no message router allocated this callback is useless, so bail */ + if (!state_cb->router) { + AST_VECTOR_REPLACE(&extension_state_legacy_callbacks, state_cb->id - 1, NULL); + ast_mutex_unlock(&extension_state_legacy_callbacks_lock); + ao2_ref(state_cb, -1); + return -1; + } + + /* + * Each of the message router callbacks translates the extension state messages into + * the legacy callback format and then calls the legacy callback with the appropriate data. + */ + stasis_message_router_add(state_cb->router, stasis_subscription_change_type(), + extension_state_legacy_subscription_change_cb, ao2_bump(state_cb)); + stasis_message_router_add(state_cb->router, ast_extension_state_update_message_type(), + extension_state_legacy_update_cb, state_cb); + stasis_message_router_add(state_cb->router, ast_extension_state_remove_message_type(), + extension_state_legacy_remove_cb, state_cb); + + ast_mutex_unlock(&extension_state_legacy_callbacks_lock); + + /* + * We don't hold a reference directly but the vector does and since we haven't given the ID back + * there's no way for the caller to remove it, thus it has to be valid even now. + */ + return (!context && !exten) ? 0 : state_cb->id; +} + +int ast_extension_state_add_destroy(const char *context, const char *exten, + ast_state_cb_type change_cb, ast_state_cb_destroy_type destroy_cb, void *data) +{ + return extension_state_legacy_add_destroy(context, exten, change_cb, destroy_cb, data, 0); +} + +int ast_extension_state_add(const char *context, const char *exten, + ast_state_cb_type change_cb, void *data) +{ + return extension_state_legacy_add_destroy(context, exten, change_cb, NULL, data, 0); +} + +int ast_extension_state_add_destroy_extended(const char *context, const char *exten, + ast_state_cb_type change_cb, ast_state_cb_destroy_type destroy_cb, void *data) +{ + return extension_state_legacy_add_destroy(context, exten, change_cb, destroy_cb, data, 1); +} + +int ast_extension_state_add_extended(const char *context, const char *exten, + ast_state_cb_type change_cb, void *data) +{ + return extension_state_legacy_add_destroy(context, exten, change_cb, NULL, data, 1); +} + +int ast_extension_state_del(int id, ast_state_cb_type change_cb) +{ + struct extension_state_legacy_state_cb *cb; + + /* A negative id is considered invalid */ + if (id < 0) { + return -1; + } + + if (!id) { /* id == 0 is a callback without extension */ + if (!change_cb) { + return -1; + } + + /* + * Global callbacks all have the ID of 0 so we need to find the actual index + * for them in the vector for removal based on callback. + */ + ast_mutex_lock(&extension_state_legacy_callbacks_lock); + for (id = 0; id < AST_VECTOR_SIZE(&extension_state_legacy_callbacks); id++) { + cb = AST_VECTOR_GET(&extension_state_legacy_callbacks, id); + if (cb && cb->change_cb == change_cb) { + AST_VECTOR_REPLACE(&extension_state_legacy_callbacks, id, NULL); + ast_mutex_unlock(&extension_state_legacy_callbacks_lock); + stasis_message_router_unsubscribe(cb->router); + ao2_ref(cb, -1); + return 0; + } + } + ast_mutex_unlock(&extension_state_legacy_callbacks_lock); + + return -1; + } + + ast_mutex_lock(&extension_state_legacy_callbacks_lock); + if (id <= AST_VECTOR_SIZE(&extension_state_legacy_callbacks)) { + cb = AST_VECTOR_GET(&extension_state_legacy_callbacks, id - 1); + if (cb) { + AST_VECTOR_REPLACE(&extension_state_legacy_callbacks, id - 1, NULL); + ast_mutex_unlock(&extension_state_legacy_callbacks_lock); + stasis_message_router_unsubscribe(cb->router); + ao2_ref(cb, -1); + return 0; + } + } + ast_mutex_unlock(&extension_state_legacy_callbacks_lock); + + return -1; +} + +/*! + * \internal + * \brief Clean up the legacy extension state system, called at shutdown. + * + * This function unregisters all legacy extension state callbacks and cleans up + * the associated resources. + */ +static void extension_state_legacy_cleanup(void) +{ + int i; + + ast_mutex_lock(&extension_state_legacy_callbacks_lock); + for (i = 0; i < AST_VECTOR_SIZE(&extension_state_legacy_callbacks); i++) { + struct extension_state_legacy_state_cb *cb = AST_VECTOR_GET(&extension_state_legacy_callbacks, i); + + if (!cb) { + continue; + } + + AST_VECTOR_REPLACE(&extension_state_legacy_callbacks, i, NULL); + stasis_message_router_unsubscribe_and_join(cb->router); + ao2_ref(cb, -1); + } + ast_mutex_unlock(&extension_state_legacy_callbacks_lock); + AST_VECTOR_FREE(&extension_state_legacy_callbacks); +} + +int ast_extension_state_legacy_init(void) +{ + /* Since we're not pre-allocating for any callbacks this can't fail */ + AST_VECTOR_INIT(&extension_state_legacy_callbacks, 0); + ast_register_cleanup(extension_state_legacy_cleanup); + + return 0; +} diff --git a/main/pbx.c b/main/pbx.c index 4182a63e84..21ce9c83c2 100644 --- a/main/pbx.c +++ b/main/pbx.c @@ -53,24 +53,20 @@ #include "asterisk/time.h" #include "asterisk/manager.h" #include "asterisk/ast_expr.h" -#include "asterisk/linkedlists.h" #define SAY_STUBS /* generate declarations and stubs for say methods */ #include "asterisk/say.h" #include "asterisk/utils.h" #include "asterisk/causes.h" #include "asterisk/musiconhold.h" #include "asterisk/app.h" -#include "asterisk/devicestate.h" -#include "asterisk/presencestate.h" #include "asterisk/hashtab.h" #include "asterisk/module.h" #include "asterisk/indications.h" -#include "asterisk/taskprocessor.h" #include "asterisk/xmldoc.h" -#include "asterisk/astobj2.h" #include "asterisk/stasis_channels.h" #include "asterisk/dial.h" #include "asterisk/vector.h" +#include "asterisk/extension_state.h" #include "pbx_private.h" /*! @@ -178,51 +174,6 @@ may take a lot of capacity. - - - 13.0.0 - - - List the current known extension states. - - - - - - This will list out all known extension states in a - sequence of ExtensionStatus events. - When finished, a ExtensionStateListComplete event - will be emitted. - - - ExtensionState - HINT - EXTENSION_STATE - - - - - - - - - 13.0.0 - - - Indicates the end of the list the current known extension states. - - - - Conveys the status of the event list. - - - Conveys the number of statuses reported. - - - - - - ***/ #ifdef LOW_MEMORY @@ -241,7 +192,11 @@ struct ast_context; struct ast_app; AST_THREADSTORAGE(switch_data); -AST_THREADSTORAGE(extensionstate_buf); + +enum ast_context_scope { + AST_CONTEXT_SCOPE_LOCAL = 0, /*!< Context is only locally accessible */ + AST_CONTEXT_SCOPE_GLOBAL, /*!< Context is globally accessible */ +}; /*! \brief ast_exten: An extension @@ -308,6 +263,7 @@ struct ast_context { struct ast_includes includes; /*!< Include other contexts */ struct ast_ignorepats ignorepats; /*!< Patterns for which to continue playing dialtone */ struct ast_sws alts; /*!< Alternative switches */ + enum ast_context_scope scope; /*!< The scope of this context */ 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 */ @@ -319,324 +275,6 @@ struct ast_context { char data[]; }; -/*! \brief ast_state_cb: An extension state notify register item */ -struct ast_state_cb { - /*! Watcher ID returned when registered. */ - int id; - /*! Arbitrary data passed for callbacks. */ - void *data; - /*! Flag if this callback is an extended callback containing detailed device status */ - int extended; - /*! Callback when state changes. */ - ast_state_cb_type change_cb; - /*! Callback when destroyed so any resources given by the registerer can be freed. */ - ast_state_cb_destroy_type destroy_cb; - /*! \note Only used by ast_merge_contexts_and_delete */ - AST_LIST_ENTRY(ast_state_cb) entry; -}; - -/*! - * \brief Structure for dial plan hints - * - * \note Hints are pointers from an extension in the dialplan to - * one or more devices (tech/name) - * - * See \ref AstExtState - */ -struct ast_hint { - /*! - * \brief Hint extension - * - * \note - * Will never be NULL while the hint is in the hints container. - */ - struct ast_exten *exten; - struct ao2_container *callbacks; /*!< Device state callback container for this extension */ - - /*! Dev state variables */ - int laststate; /*!< Last known device state */ - - /*! Presence state variables */ - int last_presence_state; /*!< Last known presence state */ - char *last_presence_subtype; /*!< Last known presence subtype string */ - char *last_presence_message; /*!< Last known presence message string */ - - char context_name[AST_MAX_CONTEXT];/*!< Context of destroyed hint extension. */ - char exten_name[AST_MAX_EXTENSION];/*!< Extension of destroyed hint extension. */ - - AST_VECTOR(, char *) devices; /*!< Devices associated with the hint */ -}; - -STASIS_MESSAGE_TYPE_DEFN_LOCAL(hint_change_message_type); -STASIS_MESSAGE_TYPE_DEFN_LOCAL(hint_remove_message_type); - -#define HINTDEVICE_DATA_LENGTH 16 -AST_THREADSTORAGE(hintdevice_data); - -/* --- Hash tables of various objects --------*/ -#ifdef LOW_MEMORY -#define HASH_EXTENHINT_SIZE 17 -#else -#define HASH_EXTENHINT_SIZE 563 -#endif - - -/*! \brief Container for hint devices */ -static struct ao2_container *hintdevices; - -/*! - * \brief Structure for dial plan hint devices - * \note hintdevice is one device pointing to a hint. - */ -struct ast_hintdevice { - /*! - * \brief Hint this hintdevice belongs to. - * \note Holds a reference to the hint object. - */ - struct ast_hint *hint; - /*! Name of the hint device. */ - 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 - */ -static int hintdevice_hash_cb(const void *obj, const int flags) -{ - const struct ast_hintdevice *ext; - const char *key; - - switch (flags & OBJ_SEARCH_MASK) { - case OBJ_SEARCH_KEY: - key = obj; - break; - case OBJ_SEARCH_OBJECT: - ext = obj; - key = ext->hintdevice; - break; - default: - ast_assert(0); - return 0; - } - - 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 - * could be more than one result. - */ -static int hintdevice_cmp_multiple(void *obj, void *arg, int flags) -{ - struct ast_hintdevice *left = obj; - struct ast_hintdevice *right = arg; - const char *right_key = arg; - int cmp; - - switch (flags & OBJ_SEARCH_MASK) { - case OBJ_SEARCH_OBJECT: - right_key = right->hintdevice; - /* Fall through */ - case OBJ_SEARCH_KEY: - cmp = strcasecmp(left->hintdevice, 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->hintdevice, right_key, strlen(right_key)); - break; - default: - ast_assert(0); - cmp = 0; - break; - } - 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) -{ - struct ast_hintdevice *candidate = obj; - char *device = arg; - struct ast_hint *hint = data; - - if (!strcasecmp(candidate->hintdevice, device) - && candidate->hint == hint) { - return CMP_MATCH; - } - return 0; -} - -static int remove_hintdevice(struct ast_hint *hint) -{ - while (AST_VECTOR_SIZE(&hint->devices) > 0) { - char *device = AST_VECTOR_GET(&hint->devices, 0); - - ao2_t_callback_data(hintdevices, OBJ_SEARCH_KEY | OBJ_UNLINK | OBJ_NODATA, - hintdevice_remove_cb, device, hint, "Remove device from container"); - AST_VECTOR_REMOVE_UNORDERED(&hint->devices, 0); - ast_free(device); - } - - return 0; -} - -static char *parse_hint_device(struct ast_str *hint_args); -/*! - * \internal - * \brief Destroy the given hintdevice object. - * - * \param obj Hint device to destroy. - */ -static void hintdevice_destroy(void *obj) -{ - struct ast_hintdevice *doomed = obj; - - if (doomed->hint) { - ao2_ref(doomed->hint, -1); - doomed->hint = NULL; - } -} - -/*! \brief add hintdevice structure and link it into the container. - */ -static int add_hintdevice(struct ast_hint *hint, const char *devicelist) -{ - struct ast_str *str; - char *parse; - char *cur; - struct ast_hintdevice *device; - int devicelength; - - if (!hint || !devicelist) { - /* Trying to add garbage? Don't bother. */ - return 0; - } - if (!(str = ast_str_thread_get(&hintdevice_data, 16))) { - return -1; - } - ast_str_set(&str, 0, "%s", devicelist); - parse = ast_str_buffer(str); - - /* Spit on '&' and ',' to handle presence hints as well */ - while ((cur = strsep(&parse, "&,"))) { - char *device_name; - - devicelength = strlen(cur); - if (!devicelength) { - continue; - } - - device_name = ast_strdup(cur); - if (!device_name) { - return -1; - } - - device = ao2_t_alloc(sizeof(*device) + devicelength, hintdevice_destroy, - "allocating a hintdevice structure"); - if (!device) { - ast_free(device_name); - return -1; - } - strcpy(device->hintdevice, cur); - ao2_ref(hint, +1); - device->hint = hint; - if (AST_VECTOR_APPEND(&hint->devices, device_name)) { - ast_free(device_name); - ao2_ref(device, -1); - return -1; - } - ao2_t_link(hintdevices, device, "Linking device into hintdevice container."); - ao2_t_ref(device, -1, "hintdevice is linked so we can unref"); - } - - return 0; -} - - -static const struct cfextension_states { - int extension_state; - const char * const text; -} extension_states[] = { - { AST_EXTENSION_NOT_INUSE, "Idle" }, - { AST_EXTENSION_INUSE, "InUse" }, - { AST_EXTENSION_BUSY, "Busy" }, - { AST_EXTENSION_UNAVAILABLE, "Unavailable" }, - { AST_EXTENSION_RINGING, "Ringing" }, - { AST_EXTENSION_INUSE | AST_EXTENSION_RINGING, "InUse&Ringing" }, - { AST_EXTENSION_ONHOLD, "Hold" }, - { AST_EXTENSION_INUSE | AST_EXTENSION_ONHOLD, "InUse&Hold" } -}; - struct pbx_exception { AST_DECLARE_STRING_FIELDS( AST_STRING_FIELD(context); /*!< Context associated with this exception */ @@ -667,9 +305,6 @@ static unsigned int hashtab_hash_extens(const void *obj); static unsigned int hashtab_hash_priority(const void *obj); static unsigned int hashtab_hash_labels(const void *obj); static void __ast_internal_context_destroy( struct ast_context *con); -static int ast_add_extension_nolock(const char *context, int replace, const char *extension, - int priority, const char *label, const char *callerid, - const char *application, void *data, void (*datad)(void *), const char *registrar); static int ast_add_extension2_lockopt(struct ast_context *con, int replace, const char *extension, int priority, const char *label, const char *callerid, const char *application, void *data, void (*datad)(void *), @@ -677,7 +312,6 @@ static int ast_add_extension2_lockopt(struct ast_context *con, int lock_context); static struct ast_context *find_context_locked(const char *context); static struct ast_context *find_context(const char *context); -static void get_device_state_causing_channels(struct ao2_container *c); static unsigned int ext_strncpy(char *dst, const char *src, size_t dst_size, int nofluff); /*! @@ -784,11 +418,6 @@ static int autofallthrough = 1; static int extenpatternmatchnew = 0; static char *overrideswitch = NULL; -/*! \brief Subscription for device state change events */ -static struct stasis_subscription *device_state_sub; -/*! \brief Subscription for presence state change events */ -static struct stasis_subscription *presence_state_sub; - AST_MUTEX_DEFINE_STATIC(maxcalllock); static int countcalls; static int totalcalls; @@ -804,24 +433,6 @@ static struct ast_hashtab *contexts_table = NULL; */ AST_MUTEX_DEFINE_STATIC(conlock); -/*! - * \brief Lock to hold off restructuring of hints by ast_merge_contexts_and_delete. - */ -AST_MUTEX_DEFINE_STATIC(context_merge_lock); - -static int stateid = 1; -/*! - * \note When holding this container's lock, do _not_ do - * anything that will cause conlock to be taken, unless you - * _already_ hold it. The ast_merge_contexts_and_delete function - * will take the locks in conlock/hints order, so any other - * paths that require both locks must also take them in that - * order. - */ -static struct ao2_container *hints; - -static struct ao2_container *statecbs; - #ifdef CONTEXT_DEBUG /* these routines are provided for doing run-time checks @@ -3039,1087 +2650,28 @@ static struct ast_exten *ast_hint_extension(struct ast_channel *c, const char *c return e; } -enum ast_extension_states ast_devstate_to_extenstate(enum ast_device_state devstate) -{ - switch (devstate) { - case AST_DEVICE_ONHOLD: - return AST_EXTENSION_ONHOLD; - case AST_DEVICE_BUSY: - return AST_EXTENSION_BUSY; - case AST_DEVICE_UNKNOWN: - return AST_EXTENSION_NOT_INUSE; - case AST_DEVICE_UNAVAILABLE: - case AST_DEVICE_INVALID: - return AST_EXTENSION_UNAVAILABLE; - case AST_DEVICE_RINGINUSE: - return (AST_EXTENSION_INUSE | AST_EXTENSION_RINGING); - case AST_DEVICE_RINGING: - return AST_EXTENSION_RINGING; - case AST_DEVICE_INUSE: - return AST_EXTENSION_INUSE; - case AST_DEVICE_NOT_INUSE: - return AST_EXTENSION_NOT_INUSE; - case AST_DEVICE_TOTAL: /* not a device state, included for completeness */ - break; - } - - return AST_EXTENSION_NOT_INUSE; -} - -/*! - * \internal - * \brief Parse out the presence portion of the hint string - */ -static char *parse_hint_presence(struct ast_str *hint_args) -{ - char *copy = ast_strdupa(ast_str_buffer(hint_args)); - char *tmp = ""; - - if ((tmp = strrchr(copy, ','))) { - *tmp = '\0'; - tmp++; - } else { - return NULL; - } - ast_str_set(&hint_args, 0, "%s", tmp); - return ast_str_buffer(hint_args); -} - -/*! - * \internal - * \brief Parse out the device portion of the hint string - */ -static char *parse_hint_device(struct ast_str *hint_args) -{ - char *copy = ast_strdupa(ast_str_buffer(hint_args)); - char *tmp; - - if ((tmp = strrchr(copy, ','))) { - *tmp = '\0'; - } - - ast_str_set(&hint_args, 0, "%s", copy); - return ast_str_buffer(hint_args); -} - -static void device_state_info_dt(void *obj) -{ - struct ast_device_state_info *info = obj; - - ao2_cleanup(info->causing_channel); -} - -static struct ao2_container *alloc_device_state_info(void) -{ - return ao2_container_alloc_list(AO2_ALLOC_OPT_LOCK_NOLOCK, 0, NULL, NULL); -} - -static int ast_extension_state3(struct ast_str *hint_app, struct ao2_container *device_state_info) -{ - char *cur; - char *rest; - struct ast_devstate_aggregate agg; - - /* One or more devices separated with a & character */ - rest = parse_hint_device(hint_app); - - ast_devstate_aggregate_init(&agg); - while ((cur = strsep(&rest, "&"))) { - enum ast_device_state state = ast_device_state(cur); - - ast_devstate_aggregate_add(&agg, state); - if (device_state_info) { - struct ast_device_state_info *obj; - - obj = ao2_alloc_options(sizeof(*obj) + strlen(cur), device_state_info_dt, AO2_ALLOC_OPT_LOCK_NOLOCK); - /* if failed we cannot add this device */ - if (obj) { - obj->device_state = state; - strcpy(obj->device_name, cur); - ao2_link(device_state_info, obj); - ao2_ref(obj, -1); - } - } - } - - return ast_devstate_to_extenstate(ast_devstate_aggregate_result(&agg)); -} - -/*! \brief Check state of extension by using hints */ -static int ast_extension_state2(struct ast_exten *e, struct ao2_container *device_state_info) -{ - struct ast_str *hint_app = ast_str_thread_get(&extensionstate_buf, 32); - - if (!e || !hint_app) { - return -1; - } - - ast_str_set(&hint_app, 0, "%s", ast_get_extension_app(e)); - return ast_extension_state3(hint_app, device_state_info); -} - -/*! \brief Return extension_state as string */ -const char *ast_extension_state2str(int extension_state) -{ - int i; - - for (i = 0; (i < ARRAY_LEN(extension_states)); i++) { - if (extension_states[i].extension_state == extension_state) - return extension_states[i].text; - } - return "Unknown"; -} - -/*! - * \internal - * \brief Check extension state for an extension by using hint - */ -static int internal_extension_state_extended(struct ast_channel *c, const char *context, const char *exten, - struct ao2_container *device_state_info) -{ - struct ast_exten *e; - - if (!(e = ast_hint_extension(c, context, exten))) { /* Do we have a hint for this extension ? */ - return -1; /* No hint, return -1 */ - } - - if (e->exten[0] == '_') { - /* Create this hint on-the-fly, we explicitly lock hints here to ensure the - * same locking order as if this were done through configuration file - that is - * hints is locked first and then (if needed) contexts is locked - */ - ao2_lock(hints); - ast_add_extension(e->parent->name, 0, exten, e->priority, e->label, - e->matchcid ? e->cidmatch : NULL, e->app, ast_strdup(e->data), ast_free_ptr, - e->registrar); - ao2_unlock(hints); - if (!(e = ast_hint_extension(c, context, exten))) { - /* Improbable, but not impossible */ - return -1; - } - } - - return ast_extension_state2(e, device_state_info); /* Check all devices in the hint */ -} - -/*! \brief Check extension state for an extension by using hint */ -int ast_extension_state(struct ast_channel *c, const char *context, const char *exten) -{ - return internal_extension_state_extended(c, context, exten, NULL); -} - -/*! \brief Check extended extension state for an extension by using hint */ -int ast_extension_state_extended(struct ast_channel *c, const char *context, const char *exten, - struct ao2_container **device_state_info) -{ - struct ao2_container *container = NULL; - int ret; - - if (device_state_info) { - container = alloc_device_state_info(); - } - - ret = internal_extension_state_extended(c, context, exten, container); - if (ret < 0 && container) { - ao2_ref(container, -1); - container = NULL; - } - - if (device_state_info) { - get_device_state_causing_channels(container); - *device_state_info = container; - } - - return ret; -} - -static int extension_presence_state_helper(struct ast_exten *e, char **subtype, char **message) -{ - struct ast_str *hint_app = ast_str_thread_get(&extensionstate_buf, 32); - char *presence_provider; - const char *app; - - if (!e || !hint_app) { - return -1; - } - - app = ast_get_extension_app(e); - if (ast_strlen_zero(app)) { - return -1; - } - - ast_str_set(&hint_app, 0, "%s", app); - presence_provider = parse_hint_presence(hint_app); - - if (ast_strlen_zero(presence_provider)) { - /* No presence string in the hint */ - return 0; - } - - return ast_presence_state(presence_provider, subtype, message); -} - -int ast_hint_presence_state(struct ast_channel *c, const char *context, const char *exten, char **subtype, char **message) -{ - struct ast_exten *e; - - if (!(e = ast_hint_extension(c, context, exten))) { /* Do we have a hint for this extension ? */ - return -1; /* No hint, return -1 */ - } - - if (e->exten[0] == '_') { - /* Create this hint on-the-fly */ - ao2_lock(hints); - ast_add_extension(e->parent->name, 0, exten, e->priority, e->label, - e->matchcid ? e->cidmatch : NULL, e->app, ast_strdup(e->data), ast_free_ptr, - e->registrar); - ao2_unlock(hints); - if (!(e = ast_hint_extension(c, context, exten))) { - /* Improbable, but not impossible */ - return -1; - } - } - - return extension_presence_state_helper(e, subtype, message); -} - -static int execute_state_callback(ast_state_cb_type cb, - const char *context, - const char *exten, - void *data, - enum ast_state_cb_update_reason reason, - struct ast_hint *hint, - struct ao2_container *device_state_info) -{ - int res = 0; - struct ast_state_cb_info info = { 0, }; - - info.reason = reason; - - /* Copy over current hint data */ - if (hint) { - ao2_lock(hint); - info.exten_state = hint->laststate; - info.device_state_info = device_state_info; - info.presence_state = hint->last_presence_state; - if (!(ast_strlen_zero(hint->last_presence_subtype))) { - info.presence_subtype = ast_strdupa(hint->last_presence_subtype); - } else { - info.presence_subtype = ""; - } - if (!(ast_strlen_zero(hint->last_presence_message))) { - info.presence_message = ast_strdupa(hint->last_presence_message); - } else { - info.presence_message = ""; - } - ao2_unlock(hint); - } else { - info.exten_state = AST_EXTENSION_REMOVED; - } - - res = cb(context, exten, &info, data); - - return res; -} - -/*! - * \internal - * \brief Identify a channel for every device which is supposedly responsible for the device state. - * - * Especially when the device is ringing, the oldest ringing channel is chosen. - * For all other cases the first encountered channel in the specific state is chosen. - */ -static void get_device_state_causing_channels(struct ao2_container *c) -{ - struct ao2_iterator iter; - struct ast_device_state_info *info; - struct ast_channel *chan; - - if (!c || !ao2_container_count(c)) { - return; - } - iter = ao2_iterator_init(c, 0); - for (; (info = ao2_iterator_next(&iter)); ao2_ref(info, -1)) { - enum ast_channel_state search_state = 0; /* prevent false uninit warning */ - char match[AST_CHANNEL_NAME]; - struct ast_channel_iterator *chan_iter; - struct timeval chantime = {0, }; /* prevent false uninit warning */ - - switch (info->device_state) { - case AST_DEVICE_RINGING: - case AST_DEVICE_RINGINUSE: - /* find ringing channel */ - search_state = AST_STATE_RINGING; - break; - case AST_DEVICE_BUSY: - /* find busy channel */ - search_state = AST_STATE_BUSY; - break; - case AST_DEVICE_ONHOLD: - case AST_DEVICE_INUSE: - /* find up channel */ - search_state = AST_STATE_UP; - break; - case AST_DEVICE_UNKNOWN: - case AST_DEVICE_NOT_INUSE: - case AST_DEVICE_INVALID: - case AST_DEVICE_UNAVAILABLE: - case AST_DEVICE_TOTAL /* not a state */: - /* no channels are of interest */ - continue; - } - - /* iterate over all channels of the device */ - snprintf(match, sizeof(match), "%s-", info->device_name); - chan_iter = ast_channel_iterator_by_name_new(match, strlen(match)); - for (; (chan = ast_channel_iterator_next(chan_iter)); ast_channel_unref(chan)) { - ast_channel_lock(chan); - /* this channel's state doesn't match */ - if (search_state != ast_channel_state(chan)) { - ast_channel_unlock(chan); - continue; - } - /* any non-ringing channel will fit */ - if (search_state != AST_STATE_RINGING) { - ast_channel_unlock(chan); - info->causing_channel = chan; /* is kept ref'd! */ - break; - } - /* but we need the oldest ringing channel of the device to match with undirected pickup */ - if (!info->causing_channel) { - chantime = ast_channel_creationtime(chan); - ast_channel_ref(chan); /* must ref it! */ - info->causing_channel = chan; - } else if (ast_tvcmp(ast_channel_creationtime(chan), chantime) < 0) { - chantime = ast_channel_creationtime(chan); - ast_channel_unref(info->causing_channel); - ast_channel_ref(chan); /* must ref it! */ - info->causing_channel = chan; - } - ast_channel_unlock(chan); - } - ast_channel_iterator_destroy(chan_iter); - } - ao2_iterator_destroy(&iter); -} - -static void device_state_notify_callbacks(struct ast_hint *hint, struct ast_str **hint_app) -{ - struct ao2_iterator cb_iter; - struct ast_state_cb *state_cb; - int state; - int same_state; - struct ao2_container *device_state_info; - int first_extended_cb_call = 1; - char context_name[AST_MAX_CONTEXT]; - char exten_name[AST_MAX_EXTENSION]; - - ao2_lock(hint); - if (!hint->exten) { - /* The extension has already been destroyed */ - ao2_unlock(hint); - return; - } - - /* - * Save off strings in case the hint extension gets destroyed - * while we are notifying the watchers. - */ - ast_copy_string(context_name, - ast_get_context_name(ast_get_extension_context(hint->exten)), - sizeof(context_name)); - ast_copy_string(exten_name, ast_get_extension_name(hint->exten), - sizeof(exten_name)); - ast_str_set(hint_app, 0, "%s", ast_get_extension_app(hint->exten)); - ao2_unlock(hint); - - /* - * Get device state for this hint. - * - * NOTE: We cannot hold any locks while determining the hint - * device state or notifying the watchers without causing a - * deadlock. (conlock, hints, and hint) - */ - - /* Make a container so state3 can fill it if we wish. - * If that failed we simply do not provide the extended state info. - */ - device_state_info = alloc_device_state_info(); - - state = ast_extension_state3(*hint_app, device_state_info); - same_state = state == hint->laststate; - if (same_state && (~state & AST_EXTENSION_RINGING)) { - ao2_cleanup(device_state_info); - return; - } - - /* Device state changed since last check - notify the watchers. */ - hint->laststate = state; /* record we saw the change */ - - /* For general callbacks */ - if (!same_state) { - cb_iter = ao2_iterator_init(statecbs, 0); - for (; (state_cb = ao2_iterator_next(&cb_iter)); ao2_ref(state_cb, -1)) { - execute_state_callback(state_cb->change_cb, - context_name, - exten_name, - state_cb->data, - AST_HINT_UPDATE_DEVICE, - hint, - NULL); - } - ao2_iterator_destroy(&cb_iter); - } - - /* For extension callbacks */ - /* extended callbacks are called when the state changed or when AST_STATE_RINGING is - * included. Normal callbacks are only called when the state changed. - */ - cb_iter = ao2_iterator_init(hint->callbacks, 0); - for (; (state_cb = ao2_iterator_next(&cb_iter)); ao2_ref(state_cb, -1)) { - if (state_cb->extended && first_extended_cb_call) { - /* Fill detailed device_state_info now that we know it is used by extd. callback */ - first_extended_cb_call = 0; - get_device_state_causing_channels(device_state_info); - } - if (state_cb->extended || !same_state) { - execute_state_callback(state_cb->change_cb, - context_name, - exten_name, - state_cb->data, - AST_HINT_UPDATE_DEVICE, - hint, - state_cb->extended ? device_state_info : NULL); - } - } - ao2_iterator_destroy(&cb_iter); - - ao2_cleanup(device_state_info); -} - -static void presence_state_notify_callbacks(struct ast_hint *hint, struct ast_str **hint_app, - struct ast_presence_state_message *presence_state) -{ - struct ao2_iterator cb_iter; - struct ast_state_cb *state_cb; - char context_name[AST_MAX_CONTEXT]; - char exten_name[AST_MAX_EXTENSION]; - - ao2_lock(hint); - if (!hint->exten) { - /* The extension has already been destroyed */ - ao2_unlock(hint); - return; - } - - /* - * Save off strings in case the hint extension gets destroyed - * while we are notifying the watchers. - */ - ast_copy_string(context_name, - ast_get_context_name(ast_get_extension_context(hint->exten)), - sizeof(context_name)); - ast_copy_string(exten_name, ast_get_extension_name(hint->exten), - sizeof(exten_name)); - ast_str_set(hint_app, 0, "%s", ast_get_extension_app(hint->exten)); - ao2_unlock(hint); - - /* Check to see if update is necessary */ - if ((hint->last_presence_state == presence_state->state) && - ((hint->last_presence_subtype && presence_state->subtype && - !strcmp(hint->last_presence_subtype, presence_state->subtype)) || - (!hint->last_presence_subtype && !presence_state->subtype)) && - ((hint->last_presence_message && presence_state->message && - !strcmp(hint->last_presence_message, presence_state->message)) || - (!hint->last_presence_message && !presence_state->message))) { - /* this update is the same as the last, do nothing */ - return; - } - - /* update new values */ - ast_free(hint->last_presence_subtype); - ast_free(hint->last_presence_message); - hint->last_presence_state = presence_state->state; - hint->last_presence_subtype = presence_state->subtype ? ast_strdup(presence_state->subtype) : NULL; - hint->last_presence_message = presence_state->message ? ast_strdup(presence_state->message) : NULL; - - /* For general callbacks */ - cb_iter = ao2_iterator_init(statecbs, 0); - for (; (state_cb = ao2_iterator_next(&cb_iter)); ao2_ref(state_cb, -1)) { - execute_state_callback(state_cb->change_cb, - context_name, - exten_name, - state_cb->data, - AST_HINT_UPDATE_PRESENCE, - hint, - NULL); - } - ao2_iterator_destroy(&cb_iter); - - /* For extension callbacks */ - cb_iter = ao2_iterator_init(hint->callbacks, 0); - for (; (state_cb = ao2_iterator_next(&cb_iter)); ao2_cleanup(state_cb)) { - execute_state_callback(state_cb->change_cb, - context_name, - exten_name, - state_cb->data, - AST_HINT_UPDATE_PRESENCE, - hint, - NULL); - } - ao2_iterator_destroy(&cb_iter); -} - -static int handle_hint_change_message_type(struct stasis_message *msg, enum ast_state_cb_update_reason reason) -{ - struct ast_hint *hint; - struct ast_str *hint_app; - - if (hint_change_message_type() != stasis_message_type(msg)) { - return 0; - } - - if (!(hint_app = ast_str_create(1024))) { - return -1; - } - - hint = stasis_message_data(msg); - - switch (reason) { - case AST_HINT_UPDATE_DEVICE: - device_state_notify_callbacks(hint, &hint_app); - break; - case AST_HINT_UPDATE_PRESENCE: - { - char *presence_subtype = NULL; - char *presence_message = NULL; - int state; - - state = extension_presence_state_helper( - hint->exten, &presence_subtype, &presence_message); - { - struct ast_presence_state_message presence_state = { - .state = state > 0 ? state : AST_PRESENCE_INVALID, - .subtype = presence_subtype, - .message = presence_message - }; - - presence_state_notify_callbacks(hint, &hint_app, &presence_state); - } - - ast_free(presence_subtype); - ast_free(presence_message); - } - break; - } - - ast_free(hint_app); - return 1; -} - -static void device_state_cb(void *unused, struct stasis_subscription *sub, struct stasis_message *msg) -{ - struct ast_device_state_message *dev_state; - struct ast_str *hint_app; - 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; - } - - if (hint_remove_message_type() == stasis_message_type(msg)) { - /* The extension has already been destroyed */ - struct ast_state_cb *state_cb; - struct ao2_iterator cb_iter; - struct ast_hint *hint = stasis_message_data(msg); - - ao2_lock(hint); - hint->laststate = AST_EXTENSION_DEACTIVATED; - ao2_unlock(hint); - - cb_iter = ao2_iterator_init(hint->callbacks, 0); - for (; (state_cb = ao2_iterator_next(&cb_iter)); ao2_ref(state_cb, -1)) { - execute_state_callback(state_cb->change_cb, - hint->context_name, - hint->exten_name, - state_cb->data, - AST_HINT_UPDATE_DEVICE, - hint, - NULL); - } - ao2_iterator_destroy(&cb_iter); - return; - } - - if (ast_device_state_message_type() != stasis_message_type(msg)) { - return; - } - - dev_state = stasis_message_data(msg); - if (dev_state->eid) { - /* ignore non-aggregate states */ - return; - } - - if (ao2_container_count(hintdevices) == 0 && ao2_container_count(autohints) == 0) { - /* There are no hints monitoring devices. */ - return; - } - - hint_app = ast_str_create(1024); - if (!hint_app) { - return; - } - - cmpdevice = ast_alloca(sizeof(*cmpdevice) + strlen(dev_state->device)); - 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) { - 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); - } - } - 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; -} - -/*! - * \internal - * \brief Destroy the given state callback object. - * - * \param doomed State callback to destroy. - */ -static void destroy_state_cb(void *doomed) -{ - struct ast_state_cb *state_cb = doomed; - - if (state_cb->destroy_cb) { - state_cb->destroy_cb(state_cb->id, state_cb->data); - } -} - -/*! - * \internal - * \brief Add watcher for extension states with destructor - */ -static int extension_state_add_destroy(const char *context, const char *exten, - ast_state_cb_type change_cb, ast_state_cb_destroy_type destroy_cb, void *data, int extended) -{ - struct ast_hint *hint; - struct ast_state_cb *state_cb; - struct ast_exten *e; - int id; - - /* If there's no context and extension: add callback to statecbs list */ - if (!context && !exten) { - /* Prevent multiple adds from adding the same change_cb at the same time. */ - ao2_lock(statecbs); - - /* Remove any existing change_cb. */ - ao2_find(statecbs, change_cb, OBJ_UNLINK | OBJ_NODATA); - - /* Now insert the change_cb */ - if (!(state_cb = ao2_alloc(sizeof(*state_cb), destroy_state_cb))) { - ao2_unlock(statecbs); - return -1; - } - state_cb->id = 0; - state_cb->change_cb = change_cb; - state_cb->destroy_cb = destroy_cb; - state_cb->data = data; - state_cb->extended = extended; - ao2_link(statecbs, state_cb); - - ao2_ref(state_cb, -1); - ao2_unlock(statecbs); - return 0; - } - - if (!context || !exten) - return -1; - - /* This callback type is for only one hint, so get the hint */ - e = ast_hint_extension(NULL, context, exten); - if (!e) { - return -1; - } - - /* If this is a pattern, dynamically create a new extension for this - * particular match. Note that this will only happen once for each - * individual extension, because the pattern will no longer match first. - */ - if (e->exten[0] == '_') { - ao2_lock(hints); - ast_add_extension(e->parent->name, 0, exten, e->priority, e->label, - e->matchcid ? e->cidmatch : NULL, e->app, ast_strdup(e->data), ast_free_ptr, - e->registrar); - ao2_unlock(hints); - e = ast_hint_extension(NULL, context, exten); - if (!e || e->exten[0] == '_') { - return -1; - } - } - - /* Find the hint in the hints container */ - ao2_lock(hints);/* Locked to hold off ast_merge_contexts_and_delete */ - hint = ao2_find(hints, e, 0); - if (!hint) { - ao2_unlock(hints); - return -1; - } - - /* Now insert the callback in the callback list */ - if (!(state_cb = ao2_alloc(sizeof(*state_cb), destroy_state_cb))) { - ao2_ref(hint, -1); - ao2_unlock(hints); - return -1; - } - do { - id = stateid++; /* Unique ID for this callback */ - /* Do not allow id to ever be -1 or 0. */ - } while (id == -1 || id == 0); - state_cb->id = id; - state_cb->change_cb = change_cb; /* Pointer to callback routine */ - state_cb->destroy_cb = destroy_cb; - state_cb->data = data; /* Data for the callback */ - state_cb->extended = extended; - ao2_link(hint->callbacks, state_cb); - - ao2_ref(state_cb, -1); - ao2_ref(hint, -1); - ao2_unlock(hints); - - return id; -} - -int ast_extension_state_add_destroy(const char *context, const char *exten, - ast_state_cb_type change_cb, ast_state_cb_destroy_type destroy_cb, void *data) -{ - return extension_state_add_destroy(context, exten, change_cb, destroy_cb, data, 0); -} - -int ast_extension_state_add(const char *context, const char *exten, - ast_state_cb_type change_cb, void *data) -{ - return extension_state_add_destroy(context, exten, change_cb, NULL, data, 0); -} - -int ast_extension_state_add_destroy_extended(const char *context, const char *exten, - ast_state_cb_type change_cb, ast_state_cb_destroy_type destroy_cb, void *data) -{ - return extension_state_add_destroy(context, exten, change_cb, destroy_cb, data, 1); -} - -int ast_extension_state_add_extended(const char *context, const char *exten, - ast_state_cb_type change_cb, void *data) -{ - return extension_state_add_destroy(context, exten, change_cb, NULL, data, 1); -} - -/*! \brief Find Hint by callback id */ -static int find_hint_by_cb_id(void *obj, void *arg, int flags) -{ - struct ast_state_cb *state_cb; - const struct ast_hint *hint = obj; - int *id = arg; - - if ((state_cb = ao2_find(hint->callbacks, id, 0))) { - ao2_ref(state_cb, -1); - return CMP_MATCH | CMP_STOP; - } - - return 0; -} - -int ast_extension_state_del(int id, ast_state_cb_type change_cb) -{ - struct ast_state_cb *p_cur; - int ret = -1; - - if (!id) { /* id == 0 is a callback without extension */ - if (!change_cb) { - return ret; - } - p_cur = ao2_find(statecbs, change_cb, OBJ_UNLINK); - if (p_cur) { - ret = 0; - ao2_ref(p_cur, -1); - } - } else { /* callback with extension, find the callback based on ID */ - struct ast_hint *hint; - - ao2_lock(hints);/* Locked to hold off ast_merge_contexts_and_delete */ - hint = ao2_callback(hints, 0, find_hint_by_cb_id, &id); - if (hint) { - p_cur = ao2_find(hint->callbacks, &id, OBJ_UNLINK); - if (p_cur) { - ret = 0; - ao2_ref(p_cur, -1); - } - ao2_ref(hint, -1); - } - ao2_unlock(hints); - } - - return ret; -} - -static int hint_id_cmp(void *obj, void *arg, int flags) -{ - const struct ast_state_cb *cb = obj; - int *id = arg; - - return (cb->id == *id) ? CMP_MATCH | CMP_STOP : 0; -} - -/*! - * \internal - * \brief Destroy the given hint object. - * - * \param obj Hint to destroy. - */ -static void destroy_hint(void *obj) -{ - struct ast_hint *hint = obj; - int i; - - ao2_cleanup(hint->callbacks); - - for (i = 0; i < AST_VECTOR_SIZE(&hint->devices); i++) { - char *device = AST_VECTOR_GET(&hint->devices, i); - ast_free(device); - } - AST_VECTOR_FREE(&hint->devices); - ast_free(hint->last_presence_subtype); - ast_free(hint->last_presence_message); -} - -/*! \brief Publish a hint removed event */ -static int publish_hint_remove(struct ast_hint *hint) -{ - struct stasis_message *message; - - if (!hint_remove_message_type()) { - return -1; - } - - if (!(message = stasis_message_create(hint_remove_message_type(), hint))) { - ao2_ref(hint, -1); - return -1; - } - - stasis_publish(ast_device_state_topic_all(), message); - - ao2_ref(message, -1); - - return 0; -} - -/*! \brief Remove hint from extension */ static int ast_remove_hint(struct ast_exten *e) { - /* Cleanup the Notifys if hint is removed */ - struct ast_hint *hint; - if (!e) { return -1; } - hint = ao2_find(hints, e, OBJ_UNLINK); - if (!hint) { - return -1; + if (e->parent->scope == AST_CONTEXT_SCOPE_GLOBAL) { + pbx_extension_state_hint_remove(e, e->parent); } - remove_hintdevice(hint); - - /* - * The extension is being destroyed so we must save some - * information to notify that the extension is deactivated. - */ - ao2_lock(hint); - ast_copy_string(hint->context_name, - ast_get_context_name(ast_get_extension_context(hint->exten)), - sizeof(hint->context_name)); - ast_copy_string(hint->exten_name, ast_get_extension_name(hint->exten), - sizeof(hint->exten_name)); - hint->exten = NULL; - ao2_unlock(hint); - - publish_hint_remove(hint); - - ao2_ref(hint, -1); - return 0; } -/*! \brief Add hint to hint list, check initial extension state */ static int ast_add_hint(struct ast_exten *e) { - struct ast_hint *hint_new; - struct ast_hint *hint_found; - char *message = NULL; - char *subtype = NULL; - int presence_state; - if (!e) { return -1; } - /* - * We must create the hint we wish to add before determining if - * it is already in the hints container to avoid possible - * deadlock when getting the current extension state. - */ - hint_new = ao2_alloc(sizeof(*hint_new), destroy_hint); - if (!hint_new) { - return -1; + if (e->parent->scope == AST_CONTEXT_SCOPE_GLOBAL) { + pbx_extension_state_hint_set(e, e->parent); } - AST_VECTOR_INIT(&hint_new->devices, 8); - - /* Initialize new hint. */ - hint_new->callbacks = ao2_container_alloc_list(AO2_ALLOC_OPT_LOCK_MUTEX, 0, NULL, hint_id_cmp); - if (!hint_new->callbacks) { - ao2_ref(hint_new, -1); - return -1; - } - hint_new->exten = e; - if (strstr(e->app, "${") && e->exten[0] == '_') { - /* The hint is dynamic and hasn't been evaluated yet */ - hint_new->laststate = AST_DEVICE_INVALID; - hint_new->last_presence_state = AST_PRESENCE_INVALID; - } else { - hint_new->laststate = ast_extension_state2(e, NULL); - if ((presence_state = extension_presence_state_helper(e, &subtype, &message)) > 0) { - hint_new->last_presence_state = presence_state; - hint_new->last_presence_subtype = subtype; - hint_new->last_presence_message = message; - } - } - - /* Prevent multiple add hints from adding the same hint at the same time. */ - ao2_lock(hints); - - /* Search if hint exists, do nothing */ - hint_found = ao2_find(hints, e, 0); - if (hint_found) { - ao2_ref(hint_found, -1); - ao2_unlock(hints); - ao2_ref(hint_new, -1); - ast_debug(2, "HINTS: Not re-adding existing hint %s: %s\n", - ast_get_extension_name(e), ast_get_extension_app(e)); - return -1; - } - - /* Add new hint to the hints container */ - ast_debug(2, "HINTS: Adding hint %s: %s\n", - ast_get_extension_name(e), ast_get_extension_app(e)); - ao2_link(hints, hint_new); - if (add_hintdevice(hint_new, ast_get_extension_app(e))) { - ast_log(LOG_WARNING, "Could not add devices for hint: %s@%s.\n", - ast_get_extension_name(e), - ast_get_context_name(ast_get_extension_context(e))); - } - - /* if not dynamic */ - if (!(strstr(e->app, "${") && e->exten[0] == '_')) { - struct ast_state_cb *state_cb; - struct ao2_iterator cb_iter; - - /* For general callbacks */ - cb_iter = ao2_iterator_init(statecbs, 0); - for (; (state_cb = ao2_iterator_next(&cb_iter)); ao2_ref(state_cb, -1)) { - execute_state_callback(state_cb->change_cb, - ast_get_context_name(ast_get_extension_context(e)), - ast_get_extension_name(e), - state_cb->data, - AST_HINT_UPDATE_DEVICE, - hint_new, - NULL); - } - ao2_iterator_destroy(&cb_iter); - } - ao2_unlock(hints); - ao2_ref(hint_new, -1); - - return 0; -} - -/*! \brief Publish a hint changed event */ -static int publish_hint_change(struct ast_hint *hint, struct ast_exten *ne) -{ - struct stasis_message *message; - - if (!hint_change_message_type()) { - return -1; - } - - if (!(message = stasis_message_create(hint_change_message_type(), hint))) { - ao2_ref(hint, -1); - return -1; - } - - stasis_publish(ast_device_state_topic_all(), message); - stasis_publish(ast_presence_state_topic_all(), message); - - ao2_ref(message, -1); return 0; } @@ -4127,44 +2679,16 @@ static int publish_hint_change(struct ast_hint *hint, struct ast_exten *ne) /*! \brief Change hint for an extension */ static int ast_change_hint(struct ast_exten *oe, struct ast_exten *ne) { - struct ast_hint *hint; - if (!oe || !ne) { return -1; } - ao2_lock(hints);/* Locked to hold off others while we move the hint around. */ - - /* - * Unlink the hint from the hints container as the extension - * name (which is the hash value) could change. - */ - hint = ao2_find(hints, oe, OBJ_UNLINK); - if (!hint) { - ao2_unlock(hints); - ast_mutex_unlock(&context_merge_lock); - return -1; + if (ne->parent->scope == AST_CONTEXT_SCOPE_GLOBAL) { + pbx_extension_state_hint_set(ne, ne->parent); } - - remove_hintdevice(hint); - - /* Update the hint and put it back in the hints container. */ - ao2_lock(hint); - hint->exten = ne; - - ao2_unlock(hint); - - ao2_link(hints, hint); - if (add_hintdevice(hint, ast_get_extension_app(ne))) { - ast_log(LOG_WARNING, "Could not add devices for hint: %s@%s.\n", - ast_get_extension_name(ne), - ast_get_context_name(ast_get_extension_context(ne))); + if (oe->parent->scope == AST_CONTEXT_SCOPE_GLOBAL) { + pbx_extension_state_hint_remove(oe, oe->parent); } - ao2_unlock(hints); - - publish_hint_change(hint, ne); - - ao2_ref(hint, -1); return 0; } @@ -5188,171 +3712,6 @@ int ast_context_remove_extension_callerid2(struct ast_context *con, const char * * Help for CLI commands ... */ -/*! \brief handle_show_hints: CLI support for listing registered dial plan hints */ -static char *handle_show_hints(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a) -{ - struct ast_hint *hint; - int num = 0; - int watchers; - struct ao2_iterator i; - char buf[AST_MAX_EXTENSION+AST_MAX_CONTEXT+2]; - - switch (cmd) { - case CLI_INIT: - e->command = "core show hints"; - e->usage = - "Usage: core show hints\n" - " List registered hints.\n" - " Hint details are shown in five columns. In order from left to right, they are:\n" - " 1. Hint extension URI.\n" - " 2. List of mapped device or presence state identifiers.\n" - " 3. Current extension state. The aggregate of mapped device states.\n" - " 4. Current presence state for the mapped presence state provider.\n" - " 5. Watchers - number of subscriptions and other entities watching this hint.\n"; - return NULL; - case CLI_GENERATE: - return NULL; - } - - if (ao2_container_count(hints) == 0) { - ast_cli(a->fd, "There are no registered dialplan hints\n"); - return CLI_SUCCESS; - } - /* ... we have hints ... */ - ast_cli(a->fd, "\n -= Registered Asterisk Dial Plan Hints =-\n"); - - i = ao2_iterator_init(hints, 0); - for (; (hint = ao2_iterator_next(&i)); ao2_ref(hint, -1)) { - ao2_lock(hint); - if (!hint->exten) { - /* The extension has already been destroyed */ - ao2_unlock(hint); - continue; - } - watchers = ao2_container_count(hint->callbacks); - snprintf(buf, sizeof(buf), "%s@%s", - ast_get_extension_name(hint->exten), - ast_get_context_name(ast_get_extension_context(hint->exten))); - - ast_cli(a->fd, "%-30.30s: %-60.60s State:%-15.15s Presence:%-15.15s Watchers %2d\n", - buf, - ast_get_extension_app(hint->exten), - ast_extension_state2str(hint->laststate), - ast_presence_state2str(hint->last_presence_state), - watchers); - - ao2_unlock(hint); - num++; - } - ao2_iterator_destroy(&i); - - ast_cli(a->fd, "----------------\n"); - ast_cli(a->fd, "- %d hints registered\n", num); - return CLI_SUCCESS; -} - -/*! \brief autocomplete for CLI command 'core show hint' */ -static char *complete_core_show_hint(const char *line, const char *word, int pos, int state) -{ - struct ast_hint *hint; - char *ret = NULL; - int which = 0; - int wordlen; - struct ao2_iterator i; - - if (pos != 3) - return NULL; - - wordlen = strlen(word); - - /* walk through all hints */ - i = ao2_iterator_init(hints, 0); - for (; (hint = ao2_iterator_next(&i)); ao2_ref(hint, -1)) { - ao2_lock(hint); - if (!hint->exten) { - /* The extension has already been destroyed */ - ao2_unlock(hint); - continue; - } - if (!strncasecmp(word, ast_get_extension_name(hint->exten), wordlen) && ++which > state) { - ret = ast_strdup(ast_get_extension_name(hint->exten)); - ao2_unlock(hint); - ao2_ref(hint, -1); - break; - } - ao2_unlock(hint); - } - ao2_iterator_destroy(&i); - - return ret; -} - -/*! \brief handle_show_hint: CLI support for listing registered dial plan hint */ -static char *handle_show_hint(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a) -{ - struct ast_hint *hint; - int watchers; - int num = 0, extenlen; - struct ao2_iterator i; - char buf[AST_MAX_EXTENSION+AST_MAX_CONTEXT+2]; - - switch (cmd) { - case CLI_INIT: - e->command = "core show hint"; - e->usage = - "Usage: core show hint \n" - " List registered hint.\n" - " Hint details are shown in five columns. In order from left to right, they are:\n" - " 1. Hint extension URI.\n" - " 2. List of mapped device or presence state identifiers.\n" - " 3. Current extension state. The aggregate of mapped device states.\n" - " 4. Current presence state for the mapped presence state provider.\n" - " 5. Watchers - number of subscriptions and other entities watching this hint.\n"; - return NULL; - case CLI_GENERATE: - return complete_core_show_hint(a->line, a->word, a->pos, a->n); - } - - if (a->argc < 4) - return CLI_SHOWUSAGE; - - if (ao2_container_count(hints) == 0) { - ast_cli(a->fd, "There are no registered dialplan hints\n"); - return CLI_SUCCESS; - } - - extenlen = strlen(a->argv[3]); - i = ao2_iterator_init(hints, 0); - for (; (hint = ao2_iterator_next(&i)); ao2_ref(hint, -1)) { - ao2_lock(hint); - if (!hint->exten) { - /* The extension has already been destroyed */ - ao2_unlock(hint); - continue; - } - if (!strncasecmp(ast_get_extension_name(hint->exten), a->argv[3], extenlen)) { - watchers = ao2_container_count(hint->callbacks); - sprintf(buf, "%s@%s", - ast_get_extension_name(hint->exten), - ast_get_context_name(ast_get_extension_context(hint->exten))); - ast_cli(a->fd, "%-30.30s: %-60.60s State:%-15.15s Presence:%-15.15s Watchers %2d\n", - buf, - ast_get_extension_app(hint->exten), - ast_extension_state2str(hint->laststate), - ast_presence_state2str(hint->last_presence_state), - watchers); - num++; - } - ao2_unlock(hint); - } - ao2_iterator_destroy(&i); - if (!num) - ast_cli(a->fd, "No hints matching extension %s\n", a->argv[3]); - else - ast_cli(a->fd, "%d hint%s matching extension %s\n", num, (num!=1 ? "s":""), a->argv[3]); - return CLI_SUCCESS; -} - #if 0 /* This code can be used to test if the system survives running out of memory. * It might be an idea to put this in only if ENABLE_AUTODESTRUCT_TESTS is enabled. @@ -6152,8 +4511,6 @@ static struct ast_cli_entry pbx_cli[] = { #if 0 AST_CLI_DEFINE(handle_eat_memory, "Eats all available memory"), #endif - AST_CLI_DEFINE(handle_show_hints, "Show dialplan hints"), - AST_CLI_DEFINE(handle_show_hint, "Show dialplan hint"), #ifdef AST_DEVMODE AST_CLI_DEFINE(handle_show_device2extenstate, "Show expected exten state from multiple device states"), #endif @@ -6248,43 +4605,39 @@ struct ast_context *ast_context_find_or_create(struct ast_context **extcontexts, } if (!extcontexts) { + tmp->scope = AST_CONTEXT_SCOPE_GLOBAL; tmp->next = *local_contexts; *local_contexts = tmp; ast_hashtab_insert_safe(contexts_table, tmp); /*put this context into the tree */ ast_unlock_contexts(); } else { + tmp->scope = AST_CONTEXT_SCOPE_LOCAL; tmp->next = *local_contexts; if (exttable) ast_hashtab_insert_immediate(exttable, tmp); /*put this context into the tree */ *local_contexts = tmp; } - ast_debug(1, "Registered extension context '%s'; registrar: %s\n", tmp->name, registrar); + ast_debug(1, "Registered extension context '%s'; registrar: %s, scope: %s\n", tmp->name, registrar, + tmp->scope == AST_CONTEXT_SCOPE_LOCAL ? "local": "global"); return tmp; } void ast_context_set_autohints(struct ast_context *con, int enabled) { con->autohints = enabled; + + if (con->scope == AST_CONTEXT_SCOPE_GLOBAL) { + if (con->autohints) { + pbx_extension_state_autohint_set(con); + } else { + pbx_extension_state_autohint_remove(con, 1); + } + } } void __ast_context_destroy(struct ast_context *list, struct ast_hashtab *contexttab, struct ast_context *con, const char *registrar); -struct store_hint { - char *context; - char *exten; - AST_LIST_HEAD_NOLOCK(, ast_state_cb) callbacks; - int laststate; - int last_presence_state; - char *last_presence_subtype; - char *last_presence_message; - - AST_LIST_ENTRY(store_hint) list; - char data[0]; -}; - -AST_LIST_HEAD_NOLOCK(store_hints, store_hint); - static void context_merge_incls_swits_igps_other_registrars(struct ast_context *new, struct ast_context *old, const char *registrar) { int idx; @@ -6322,42 +4675,6 @@ 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 */ static void context_merge(struct ast_context **extcontexts, struct ast_hashtab *exttable, struct ast_context *context, const char *registrar) @@ -6393,6 +4710,33 @@ static void context_merge(struct ast_context **extcontexts, struct ast_hashtab * new_prio_item = NULL; } if (strcmp(prio_item->registrar,registrar) == 0) { + struct ast_exten *pattern_exten; + struct pbx_find_info q = { .stacklen = 0 }; + + if (prio_item->priority != PRIORITY_HINT || prio_item->name[0] == '_' || new_prio_item || !new) { + continue; + } + + /* + * This hint no longer exists in the new context, but it may have been created as a result of + * a pattern match so see if a pattern match matches it. If it does then we add it in to the new + * context using the registrar of the pattern match. + */ + pattern_exten = pbx_find_extension(NULL, new, &q, context->name, + prio_item->name, PRIORITY_HINT, NULL, "", E_MATCH); + if (pattern_exten && !strcmp(q.foundcontext, context->name)) { + /* + * This logic doesn't check whether it's a pattern match or not because if it was + * an exact match we would have already skipped it above due to new_prio_item being + * present. Logically it could only ever be a pattern match here. + */ + dupdstr = ast_strdup(prio_item->data); + + res1 = ast_add_extension2(new, 0, prio_item->name, prio_item->priority, prio_item->label, + prio_item->matchcid ? prio_item->cidmatch : NULL, prio_item->app, dupdstr, ast_free_ptr, prio_item->registrar, + prio_item->registrar_file, prio_item->registrar_line); + } + continue; } /* make sure the new context exists, so we have somewhere to stick this exten/prio */ @@ -6400,6 +4744,10 @@ static void context_merge(struct ast_context **extcontexts, struct ast_hashtab * 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; + if (new->autohints) { + pbx_extension_state_autohint_set(new); + } + new->scope = AST_CONTEXT_SCOPE_GLOBAL; } } @@ -6450,6 +4798,10 @@ static void context_merge(struct ast_context **extcontexts, struct ast_hashtab * if (new) { new->autohints = context->autohints; + if (new->autohints) { + pbx_extension_state_autohint_set(new); + } + new->scope = AST_CONTEXT_SCOPE_GLOBAL; } /* copy in the includes, switches, and ignorepats */ @@ -6457,6 +4809,51 @@ static void context_merge(struct ast_context **extcontexts, struct ast_hashtab * } } +static int context_promote(struct ast_context *context) +{ + struct ast_exten *exten_item, *prio_item; + struct ast_hashtab_iter *exten_iter; + struct ast_hashtab_iter *prio_iter; + + /* Contexts already promoted to global have been handled previously, so skip */ + if (context->scope == AST_CONTEXT_SCOPE_GLOBAL) { + return 0; + } + + /* Enable or remove autohints as needed */ + if (context->autohints) { + pbx_extension_state_autohint_set(context); + } else { + pbx_extension_state_autohint_remove(context, 1); + } + + /* Further handling requires extensions to exist */ + if (!context->root_table) { + return 0; + } + + /* + * Hints are stateless but extension state is not. To keep extension state up to date + * we go through all the hints on contexts promoted from local scope to global scope and + * inform extension state as it is purely driven based on global scope dialplan. + */ + exten_iter = ast_hashtab_start_traversal(context->root_table); + while ((exten_item = ast_hashtab_next(exten_iter))) { + prio_iter = ast_hashtab_start_traversal(exten_item->peer_table); + while ((prio_item = ast_hashtab_next(prio_iter))) { + if (prio_item->priority != PRIORITY_HINT) { + continue; + } + pbx_extension_state_hint_set(prio_item, context); + } + ast_hashtab_end_traversal(prio_iter); + } + ast_hashtab_end_traversal(exten_iter); + + context->scope = AST_CONTEXT_SCOPE_GLOBAL; + + return 1; +} /* XXX this does not check that multiple contexts are merged */ void ast_merge_contexts_and_delete(struct ast_context **extcontexts, struct ast_hashtab *exttable, const char *registrar) @@ -6465,46 +4862,28 @@ void ast_merge_contexts_and_delete(struct ast_context **extcontexts, struct ast_ struct ast_context *tmp; struct ast_context *oldcontextslist; struct ast_hashtab *oldtable; - struct store_hints hints_stored = AST_LIST_HEAD_NOLOCK_INIT_VALUE; - struct store_hints hints_removed = AST_LIST_HEAD_NOLOCK_INIT_VALUE; - struct store_hint *saved_hint; - struct ast_hint *hint; - struct ast_exten *exten; - int length; - struct ast_state_cb *thiscb; struct ast_hashtab_iter *iter; - struct ao2_iterator i; - int ctx_count = 0; + int ctx_count = 0, promoted_count = 0; struct timeval begintime; struct timeval writelocktime; struct timeval endlocktime; struct timeval enddeltime; - /* - * It is very important that this function hold the hints - * container lock _and_ the conlock during its operation; not - * only do we need to ensure that the list of contexts and - * extensions does not change, but also that no hint callbacks - * (watchers) are added or removed during the merge/delete - * process - * - * In addition, the locks _must_ be taken in this order, because - * there are already other code paths that use this order - */ - begintime = ast_tvnow(); - ast_mutex_lock(&context_merge_lock);/* Serialize ast_merge_contexts_and_delete */ 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; + + iter = ast_hashtab_start_traversal(contexts_table); + while ((tmp = ast_hashtab_next(iter))) { + context_promote(tmp); + } + ast_hashtab_end_traversal(iter); + ast_unlock_contexts(); - ast_mutex_unlock(&context_merge_lock); return; } @@ -6515,57 +4894,8 @@ void ast_merge_contexts_and_delete(struct ast_context **extcontexts, struct ast_ } ast_hashtab_end_traversal(iter); - ao2_lock(hints); writelocktime = ast_tvnow(); - /* preserve all watchers for hints */ - i = ao2_iterator_init(hints, AO2_ITERATOR_DONTLOCK); - for (; (hint = ao2_iterator_next(&i)); ao2_ref(hint, -1)) { - if (ao2_container_count(hint->callbacks)) { - size_t exten_len; - - ao2_lock(hint); - if (!hint->exten) { - /* The extension has already been destroyed. (Should never happen here) */ - ao2_unlock(hint); - continue; - } - - exten_len = strlen(hint->exten->exten) + 1; - length = exten_len + strlen(hint->exten->parent->name) + 1 - + sizeof(*saved_hint); - if (!(saved_hint = ast_calloc(1, length))) { - ao2_unlock(hint); - continue; - } - - /* This removes all the callbacks from the hint into saved_hint. */ - while ((thiscb = ao2_callback(hint->callbacks, OBJ_UNLINK, NULL, NULL))) { - AST_LIST_INSERT_TAIL(&saved_hint->callbacks, thiscb, entry); - /* - * We intentionally do not unref thiscb to account for the - * non-ao2 reference in saved_hint->callbacks - */ - } - - saved_hint->laststate = hint->laststate; - saved_hint->context = saved_hint->data; - strcpy(saved_hint->data, hint->exten->parent->name); - saved_hint->exten = saved_hint->data + strlen(saved_hint->context) + 1; - ast_copy_string(saved_hint->exten, hint->exten->exten, exten_len); - if (hint->last_presence_subtype) { - saved_hint->last_presence_subtype = ast_strdup(hint->last_presence_subtype); - } - if (hint->last_presence_message) { - saved_hint->last_presence_message = ast_strdup(hint->last_presence_message); - } - saved_hint->last_presence_state = hint->last_presence_state; - ao2_unlock(hint); - AST_LIST_INSERT_HEAD(&hints_stored, saved_hint, list); - } - } - ao2_iterator_destroy(&i); - /* save the old table and list */ oldtable = contexts_table; oldcontextslist = contexts; @@ -6574,91 +4904,18 @@ void ast_merge_contexts_and_delete(struct ast_context **extcontexts, struct ast_ contexts_table = exttable; contexts = *extcontexts; - /* - * Restore the watchers for hints that can be found; notify - * those that cannot be restored. - */ - while ((saved_hint = AST_LIST_REMOVE_HEAD(&hints_stored, list))) { - struct pbx_find_info q = { .stacklen = 0 }; - - exten = pbx_find_extension(NULL, NULL, &q, saved_hint->context, saved_hint->exten, - PRIORITY_HINT, NULL, "", E_MATCH); - /* - * If this is a pattern, dynamically create a new extension for this - * particular match. Note that this will only happen once for each - * individual extension, because the pattern will no longer match first. - */ - if (exten && exten->exten[0] == '_') { - ast_add_extension_nolock(exten->parent->name, 0, saved_hint->exten, - PRIORITY_HINT, NULL, 0, exten->app, ast_strdup(exten->data), ast_free_ptr, - exten->registrar); - /* rwlocks are not recursive locks */ - exten = ast_hint_extension_nolock(NULL, saved_hint->context, - saved_hint->exten); - } - - /* Find the hint in the hints container */ - hint = exten ? ao2_find(hints, exten, 0) : NULL; - if (!hint) { - /* - * Notify watchers of this removed hint later when we aren't - * encumbered by so many locks. - */ - AST_LIST_INSERT_HEAD(&hints_removed, saved_hint, list); - } else { - ao2_lock(hint); - while ((thiscb = AST_LIST_REMOVE_HEAD(&saved_hint->callbacks, entry))) { - ao2_link(hint->callbacks, thiscb); - /* Ref that we added when putting into saved_hint->callbacks */ - ao2_ref(thiscb, -1); - } - hint->laststate = saved_hint->laststate; - hint->last_presence_state = saved_hint->last_presence_state; - hint->last_presence_subtype = saved_hint->last_presence_subtype; - hint->last_presence_message = saved_hint->last_presence_message; - ao2_unlock(hint); - ao2_ref(hint, -1); - /* - * The free of saved_hint->last_presence_subtype and - * saved_hint->last_presence_message is not necessary here. - */ - ast_free(saved_hint); - } + iter = ast_hashtab_start_traversal(contexts_table); + while ((tmp = ast_hashtab_next(iter))) { + promoted_count += context_promote(tmp); } - - /* Create all applicable autohint contexts */ - context_table_create_autohints(contexts_table); + ast_hashtab_end_traversal(iter); /* ctx_count is still the number of old contexts before the merge, * use the new count when we tell the user how many contexts exist. */ ctx_count = ast_hashtab_size(contexts_table); - ao2_unlock(hints); ast_unlock_contexts(); - /* - * Notify watchers of all removed hints with the same lock - * environment as device_state_cb(). - */ - while ((saved_hint = AST_LIST_REMOVE_HEAD(&hints_removed, list))) { - /* this hint has been removed, notify the watchers */ - while ((thiscb = AST_LIST_REMOVE_HEAD(&saved_hint->callbacks, entry))) { - execute_state_callback(thiscb->change_cb, - saved_hint->context, - saved_hint->exten, - thiscb->data, - AST_HINT_UPDATE_DEVICE, - NULL, - NULL); - /* Ref that we added when putting into saved_hint->callbacks */ - ao2_ref(thiscb, -1); - } - ast_free(saved_hint->last_presence_subtype); - ast_free(saved_hint->last_presence_message); - ast_free(saved_hint); - } - - ast_mutex_unlock(&context_merge_lock); endlocktime = ast_tvnow(); /* @@ -6684,7 +4941,7 @@ void ast_merge_contexts_and_delete(struct ast_context **extcontexts, struct ast_ ft = ast_tvdiff_us(endlocktime, writelocktime); ft /= 1000000.0; - ast_verb(5,"Time to restore hints and swap in new dialplan: %8.6f sec\n", ft); + ast_verb(5,"Time to promote contexts and swap in new dialplan: %8.6f sec\n", ft); ft = ast_tvdiff_us(enddeltime, endlocktime); ft /= 1000000.0; @@ -6693,7 +4950,8 @@ void ast_merge_contexts_and_delete(struct ast_context **extcontexts, struct ast_ ft = ast_tvdiff_us(enddeltime, begintime); ft /= 1000000.0; ast_verb(5,"Total time merge_contexts_delete: %8.6f sec\n", ft); - ast_verb(5, "%s successfully loaded %d contexts (enable debug for details).\n", registrar, ctx_count); + ast_verb(5, "%s successfully loaded %d contexts after incorporating %d promoted contexts (enable debug for details).\n", + registrar, ctx_count, promoted_count); } /* @@ -6940,26 +5198,6 @@ int ast_ignore_pattern(const char *context, const char *pattern) return ret; } -/* - * ast_add_extension_nolock -- use only in situations where the conlock is already held - * ENOENT - no existence of context - * - */ -static int ast_add_extension_nolock(const char *context, int replace, const char *extension, - int priority, const char *label, const char *callerid, - const char *application, void *data, void (*datad)(void *), const char *registrar) -{ - int ret = -1; - struct ast_context *c; - - c = find_context(context); - if (c) { - ret = ast_add_extension2_lockopt(c, replace, extension, priority, label, callerid, - application, data, datad, registrar, NULL, 0, 1); - } - - return ret; -} /* * EBUSY - can't lock * ENOENT - no existence of context @@ -8061,6 +6299,10 @@ static void __ast_internal_context_destroy( struct ast_context *con) struct ast_exten *e, *el, *en; struct ast_context *tmp = con; + if (con->scope == AST_CONTEXT_SCOPE_GLOBAL && con->autohints) { + pbx_extension_state_autohint_remove(con, 0); + } + /* Free includes */ AST_VECTOR_CALLBACK_VOID(&tmp->includes, include_free); AST_VECTOR_FREE(&tmp->includes); @@ -8335,115 +6577,6 @@ int pbx_checkcondition(const char *condition) } } -static void presence_state_cb(void *unused, struct stasis_subscription *sub, struct stasis_message *msg) -{ - struct ast_presence_state_message *presence_state; - struct ast_str *hint_app = NULL; - struct ast_hintdevice *device; - struct ast_hintdevice *cmpdevice; - struct ao2_iterator *dev_iter; - - if (stasis_message_type(msg) != ast_presence_state_message_type()) { - return; - } - - presence_state = stasis_message_data(msg); - - if (ao2_container_count(hintdevices) == 0) { - /* There are no hints monitoring devices. */ - return; - } - - hint_app = ast_str_create(1024); - if (!hint_app) { - return; - } - - cmpdevice = ast_alloca(sizeof(*cmpdevice) + strlen(presence_state->provider)); - strcpy(cmpdevice->hintdevice, presence_state->provider); - - ast_mutex_lock(&context_merge_lock);/* Hold off ast_merge_contexts_and_delete */ - dev_iter = ao2_t_callback(hintdevices, - OBJ_POINTER | OBJ_MULTIPLE, - hintdevice_cmp_multiple, - cmpdevice, - "find devices in container"); - if (!dev_iter) { - ast_mutex_unlock(&context_merge_lock); - ast_free(hint_app); - return; - } - - for (; (device = ao2_iterator_next(dev_iter)); ao2_t_ref(device, -1, "Next device")) { - if (device->hint) { - presence_state_notify_callbacks(device->hint, &hint_app, presence_state); - } - } - ao2_iterator_destroy(dev_iter); - ast_mutex_unlock(&context_merge_lock); - - ast_free(hint_app); -} - -static int action_extensionstatelist(struct mansession *s, const struct message *m) -{ - const char *action_id = astman_get_header(m, "ActionID"); - struct ast_hint *hint; - struct ao2_iterator it_hints; - int hint_count = 0; - - if (!hints) { - astman_send_error(s, m, "No dialplan hints are available"); - return 0; - } - - astman_send_listack(s, m, "Extension Statuses will follow", "start"); - - ao2_lock(hints); - it_hints = ao2_iterator_init(hints, 0); - for (; (hint = ao2_iterator_next(&it_hints)); ao2_ref(hint, -1)) { - - ao2_lock(hint); - - /* Ignore pattern matching hints; they are stored in the - * hints container but aren't real from the perspective of - * an AMI user - */ - if (hint->exten->exten[0] == '_') { - ao2_unlock(hint); - continue; - } - - ++hint_count; - - astman_append(s, "Event: ExtensionStatus\r\n"); - if (!ast_strlen_zero(action_id)) { - astman_append(s, "ActionID: %s\r\n", action_id); - } - astman_append(s, - "Exten: %s\r\n" - "Context: %s\r\n" - "Hint: %s\r\n" - "Status: %d\r\n" - "StatusText: %s\r\n\r\n", - hint->exten->exten, - hint->exten->parent->name, - hint->exten->app, - hint->laststate, - ast_extension_state2str(hint->laststate)); - ao2_unlock(hint); - } - - ao2_iterator_destroy(&it_hints); - ao2_unlock(hints); - - astman_send_list_complete_start(s, m, "ExtensionStateListComplete", hint_count); - astman_send_list_complete_end(s); - - return 0; -} - - /*! * \internal * \brief Clean up resources on Asterisk shutdown. @@ -8452,11 +6585,7 @@ static int action_extensionstatelist(struct mansession *s, const struct message */ static void unload_pbx(void) { - presence_state_sub = stasis_unsubscribe_and_join(presence_state_sub); - device_state_sub = stasis_unsubscribe_and_join(device_state_sub); - ast_manager_unregister("ShowDialPlan"); - ast_manager_unregister("ExtensionStateList"); ast_cli_unregister_multiple(pbx_cli, ARRAY_LEN(pbx_cli)); ast_custom_function_unregister(&exception_function); ast_custom_function_unregister(&testtime_function); @@ -8478,26 +6607,11 @@ int load_pbx(void) /* Register manager application */ res |= ast_manager_register_xml_core("ShowDialPlan", EVENT_FLAG_CONFIG | EVENT_FLAG_REPORTING, manager_show_dialplan); - res |= ast_manager_register_xml_core("ExtensionStateList", EVENT_FLAG_CALL | EVENT_FLAG_REPORTING, action_extensionstatelist); if (res) { return -1; } - if (!(device_state_sub = stasis_subscribe(ast_device_state_topic_all(), device_state_cb, NULL))) { - return -1; - } - stasis_subscription_accept_message_type(device_state_sub, ast_device_state_message_type()); - stasis_subscription_accept_message_type(device_state_sub, hint_change_message_type()); - stasis_subscription_accept_message_type(device_state_sub, hint_remove_message_type()); - stasis_subscription_set_filter(device_state_sub, STASIS_SUBSCRIPTION_FILTER_SELECTIVE); - - if (!(presence_state_sub = stasis_subscribe(ast_presence_state_topic_all(), presence_state_cb, NULL))) { - return -1; - } - stasis_subscription_accept_message_type(presence_state_sub, ast_presence_state_message_type()); - stasis_subscription_set_filter(presence_state_sub, STASIS_SUBSCRIPTION_FILTER_SELECTIVE); - return 0; } @@ -8917,150 +7031,19 @@ int ast_async_parseable_goto(struct ast_channel *chan, const char *goto_string) return pbx_parseable_goto(chan, goto_string, 1); } -static int hint_hash(const void *obj, const int flags) -{ - const struct ast_hint *hint = obj; - const char *exten_name; - int res; - - exten_name = ast_get_extension_name(hint->exten); - if (ast_strlen_zero(exten_name)) { - /* - * If the exten or extension name isn't set, return 0 so that - * the ao2_find() search will start in the first bucket. - */ - res = 0; - } else { - res = ast_str_case_hash(exten_name); - } - - return res; -} - -static int hint_cmp(void *obj, void *arg, int flags) -{ - const struct ast_hint *hint = obj; - const struct ast_exten *exten = arg; - - return (hint->exten == exten) ? CMP_MATCH | CMP_STOP : 0; -} - -static int statecbs_cmp(void *obj, void *arg, int flags) -{ - const struct ast_state_cb *state_cb = obj; - ast_state_cb_type change_cb = arg; - - return (state_cb->change_cb == change_cb) ? CMP_MATCH | CMP_STOP : 0; -} - /*! * \internal * \brief Clean up resources on Asterisk shutdown */ static void pbx_shutdown(void) { - STASIS_MESSAGE_TYPE_CLEANUP(hint_change_message_type); - STASIS_MESSAGE_TYPE_CLEANUP(hint_remove_message_type); - - if (hints) { - ao2_container_unregister("hints"); - ao2_ref(hints, -1); - hints = NULL; - } - if (hintdevices) { - ao2_container_unregister("hintdevices"); - 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); - statecbs = NULL; - } if (contexts_table) { ast_hashtab_destroy(contexts_table, NULL); } } -static void print_hints_key(void *v_obj, void *where, ao2_prnt_fn *prnt) -{ - struct ast_hint *hint = v_obj; - - if (!hint) { - return; - } - prnt(where, "%s@%s", ast_get_extension_name(hint->exten), - ast_get_context_name(ast_get_extension_context(hint->exten))); -} - -static void print_hintdevices_key(void *v_obj, void *where, ao2_prnt_fn *prnt) -{ - struct ast_hintdevice *hintdevice = v_obj; - - if (!hintdevice) { - return; - } - prnt(where, "%s => %s@%s", hintdevice->hintdevice, - ast_get_extension_name(hintdevice->hint->exten), - 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; - - if (!state_cb) { - return; - } - prnt(where, "%d", state_cb->id); -} - int ast_pbx_init(void) { - hints = ao2_container_alloc_hash(AO2_ALLOC_OPT_LOCK_MUTEX, 0, - HASH_EXTENHINT_SIZE, hint_hash, NULL, hint_cmp); - if (hints) { - ao2_container_register("hints", hints, print_hints_key); - } - hintdevices = ao2_container_alloc_hash(AO2_ALLOC_OPT_LOCK_MUTEX, 0, - HASH_EXTENHINT_SIZE, hintdevice_hash_cb, NULL, hintdevice_cmp_multiple); - if (hintdevices) { - ao2_container_register("hintdevices", hintdevices, print_hintdevices_key); - } - /* This is protected by the context_and_merge lock */ - autohints = ao2_container_alloc_hash(AO2_ALLOC_OPT_LOCK_NOLOCK, 0, HASH_EXTENHINT_SIZE, - autohint_hash_cb, NULL, autohint_cmp); - if (autohints) { - ao2_container_register("autohints", autohints, print_autohint_key); - } - statecbs = ao2_container_alloc_list(AO2_ALLOC_OPT_LOCK_MUTEX, 0, NULL, statecbs_cmp); - if (statecbs) { - ao2_container_register("statecbs", statecbs, print_statecbs_key); - } - ast_register_cleanup(pbx_shutdown); - - if (STASIS_MESSAGE_TYPE_INIT(hint_change_message_type) != 0) { - return -1; - } - if (STASIS_MESSAGE_TYPE_INIT(hint_remove_message_type) != 0) { - return -1; - } - - return (hints && hintdevices && autohints && statecbs) ? 0 : -1; + return 0; } diff --git a/main/pbx_private.h b/main/pbx_private.h index da1060e0e9..b2147fccde 100644 --- a/main/pbx_private.h +++ b/main/pbx_private.h @@ -65,6 +65,14 @@ struct ast_switch *pbx_findswitch(const char *sw); /*! pbx_app.c functions needed by pbx.c */ const char *app_name(struct ast_app *app); +/*! extension_state.c functions needed by pbx.c */ +void pbx_extension_state_hint_set(struct ast_exten *exten, struct ast_context *context); +void pbx_extension_state_hint_remove(struct ast_exten *exten, struct ast_context *context); + +/*! extension_state_autohints.c functions needed by pbx.c */ +void pbx_extension_state_autohint_set(struct ast_context *context); +void pbx_extension_state_autohint_remove(struct ast_context *context, unsigned int forced); + #define VAR_BUF_SIZE 4096 #endif /* _PBX_PRIVATE_H */ diff --git a/main/stasis.c b/main/stasis.c index a041fe4934..b3a2bf5caa 100644 --- a/main/stasis.c +++ b/main/stasis.c @@ -1028,6 +1028,17 @@ struct stasis_subscription *__stasis_subscribe_pool( return internal_stasis_subscribe(topic, callback, data, 1, 1, file, lineno, func); } +struct stasis_subscription *__stasis_subscribe_synchronous( + struct stasis_topic *topic, + stasis_subscription_cb callback, + void *data, + const char *file, + int lineno, + const char *func) +{ + return internal_stasis_subscribe(topic, callback, data, 0, 0, file, lineno, func); +} + static int sub_cleanup(void *data) { struct stasis_subscription *sub = data; diff --git a/pbx/pbx_config.c b/pbx/pbx_config.c index 9d43164136..76eb4c037c 100644 --- a/pbx/pbx_config.c +++ b/pbx/pbx_config.c @@ -1699,6 +1699,11 @@ static int pbx_load_config(const char *config_file) const char *newpm, *ovsw; struct ast_flags config_flags = { 0 }; char lastextension[256]; + struct timeval begin_time; + double parsing_time; + + begin_time = ast_tvnow(); + cfg = ast_config_load(config_file, config_flags); if (!cfg || cfg == CONFIG_STATUS_FILEINVALID) return 0; @@ -1969,6 +1974,11 @@ process_extension: } } ast_config_destroy(cfg); + + parsing_time = ast_tvdiff_us(ast_tvnow(), begin_time); + parsing_time /= 1000000.0; + ast_verb(5, "Time to parse %s dialplan configuration: %8.6f sec\n", config, parsing_time); + return 1; }