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.
--- /dev/null
+/*
+ * Asterisk -- An open source telephony toolkit.
+ *
+ * Copyright (C) 2026, Sangoma Technologies Corporation
+ *
+ * Joshua C. Colp <jcolp@sangoma.com>
+ *
+ * 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 */
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.
*
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");
--- /dev/null
+/*
+ * Asterisk -- An open source telephony toolkit.
+ *
+ * Copyright (C) 2026, Sangoma Technologies Corporation
+ *
+ * Joshua Colp <jcolp@sangoma.com>
+ *
+ * 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
+ <support_level>core</support_level>
+ ***/
+
+#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
+ <manager name="ExtensionStateList" language="en_US">
+ <since>
+ <version>13.0.0</version>
+ </since>
+ <synopsis>
+ List the current known extension states.
+ </synopsis>
+ <syntax>
+ <xi:include xpointer="xpointer(/docs/manager[@name='Login']/syntax/parameter[@name='ActionID'])" />
+ </syntax>
+ <description>
+ <para>This will list out all known extension states in a
+ sequence of <replaceable>ExtensionStatus</replaceable> events.
+ When finished, a <replaceable>ExtensionStateListComplete</replaceable> event
+ will be emitted.</para>
+ </description>
+ <see-also>
+ <ref type="manager">ExtensionState</ref>
+ <ref type="function">HINT</ref>
+ <ref type="function">EXTENSION_STATE</ref>
+ </see-also>
+ <responses>
+ <list-elements>
+ <xi:include xpointer="xpointer(/docs/managerEvent[@name='ExtensionStatus'])" />
+ </list-elements>
+ <managerEvent name="ExtensionStateListComplete" language="en_US">
+ <managerEventInstance class="EVENT_FLAG_COMMAND">
+ <since>
+ <version>13.0.0</version>
+ </since>
+ <synopsis>
+ Indicates the end of the list the current known extension states.
+ </synopsis>
+ <syntax>
+ <parameter name="EventList">
+ <para>Conveys the status of the event list.</para>
+ </parameter>
+ <parameter name="ListItems">
+ <para>Conveys the number of statuses reported.</para>
+ </parameter>
+ </syntax>
+ </managerEventInstance>
+ </managerEvent>
+ </responses>
+ </manager>
+ ***/
+
+#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 <exten>\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;
+}
--- /dev/null
+/*
+ * Asterisk -- An open source telephony toolkit.
+ *
+ * Copyright (C) 2026, Sangoma Technologies Corporation
+ *
+ * Joshua Colp <jcolp@sangoma.com>
+ *
+ * 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
+ <support_level>core</support_level>
+ ***/
+
+#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;
+}
--- /dev/null
+/*
+ * Asterisk -- An open source telephony toolkit.
+ *
+ * Copyright (C) 2026, Sangoma Technologies Corporation
+ *
+ * Joshua Colp <jcolp@sangoma.com>
+ *
+ * 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
+ <support_level>core</support_level>
+ ***/
+
+#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;
+}
#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"
/*!
may take a lot of capacity.</para>
</description>
</manager>
- <manager name="ExtensionStateList" language="en_US">
- <since>
- <version>13.0.0</version>
- </since>
- <synopsis>
- List the current known extension states.
- </synopsis>
- <syntax>
- <xi:include xpointer="xpointer(/docs/manager[@name='Login']/syntax/parameter[@name='ActionID'])" />
- </syntax>
- <description>
- <para>This will list out all known extension states in a
- sequence of <replaceable>ExtensionStatus</replaceable> events.
- When finished, a <replaceable>ExtensionStateListComplete</replaceable> event
- will be emitted.</para>
- </description>
- <see-also>
- <ref type="manager">ExtensionState</ref>
- <ref type="function">HINT</ref>
- <ref type="function">EXTENSION_STATE</ref>
- </see-also>
- <responses>
- <list-elements>
- <xi:include xpointer="xpointer(/docs/managerEvent[@name='ExtensionStatus'])" />
- </list-elements>
- <managerEvent name="ExtensionStateListComplete" language="en_US">
- <managerEventInstance class="EVENT_FLAG_COMMAND">
- <since>
- <version>13.0.0</version>
- </since>
- <synopsis>
- Indicates the end of the list the current known extension states.
- </synopsis>
- <syntax>
- <parameter name="EventList">
- <para>Conveys the status of the event list.</para>
- </parameter>
- <parameter name="ListItems">
- <para>Conveys the number of statuses reported.</para>
- </parameter>
- </syntax>
- </managerEventInstance>
- </managerEvent>
- </responses>
- </manager>
***/
#ifdef LOW_MEMORY
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
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 */
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 */
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 *),
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);
/*!
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;
*/
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
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)
+static int ast_remove_hint(struct ast_exten *e)
{
- char *copy = ast_strdupa(ast_str_buffer(hint_args));
- char *tmp = "";
-
- if ((tmp = strrchr(copy, ','))) {
- *tmp = '\0';
- tmp++;
- } else {
- return NULL;
+ if (!e) {
+ return -1;
}
- 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';
+ if (e->parent->scope == AST_CONTEXT_SCOPE_GLOBAL) {
+ pbx_extension_state_hint_remove(e, e->parent);
}
- ast_str_set(&hint_args, 0, "%s", copy);
- return ast_str_buffer(hint_args);
+ return 0;
}
-static void device_state_info_dt(void *obj)
+static int ast_add_hint(struct ast_exten *e)
{
- struct ast_device_state_info *info = obj;
+ if (!e) {
+ return -1;
+ }
- ao2_cleanup(info->causing_channel);
-}
+ if (e->parent->scope == AST_CONTEXT_SCOPE_GLOBAL) {
+ pbx_extension_state_hint_set(e, e->parent);
+ }
-static struct ao2_container *alloc_device_state_info(void)
-{
- return ao2_container_alloc_list(AO2_ALLOC_OPT_LOCK_NOLOCK, 0, NULL, NULL);
+ return 0;
}
-static int ast_extension_state3(struct ast_str *hint_app, struct ao2_container *device_state_info)
+/*! \brief Change hint for an extension */
+static int ast_change_hint(struct ast_exten *oe, struct ast_exten *ne)
{
- 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;
+ if (!oe || !ne) {
+ return -1;
+ }
- 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);
- }
- }
+ if (ne->parent->scope == AST_CONTEXT_SCOPE_GLOBAL) {
+ pbx_extension_state_hint_set(ne, ne->parent);
+ }
+ if (oe->parent->scope == AST_CONTEXT_SCOPE_GLOBAL) {
+ pbx_extension_state_hint_remove(oe, oe->parent);
}
- return ast_devstate_to_extenstate(ast_devstate_aggregate_result(&agg));
+ return 0;
}
-/*! \brief Check state of extension by using hints */
-static int ast_extension_state2(struct ast_exten *e, struct ao2_container *device_state_info)
+/*! \brief Get hint for channel */
+int ast_get_hint(char *hint, int hintsize, char *name, int namesize, struct ast_channel *c, const char *context, const char *exten)
{
- struct ast_str *hint_app = ast_str_thread_get(&extensionstate_buf, 32);
+ struct ast_exten *e = ast_hint_extension(c, context, exten);
- if (!e || !hint_app) {
+ if (e) {
+ if (hint)
+ ast_copy_string(hint, ast_get_extension_app(e), hintsize);
+ if (name) {
+ const char *tmp = ast_get_extension_app_data(e);
+ if (tmp)
+ ast_copy_string(name, tmp, namesize);
+ }
return -1;
}
-
- ast_str_set(&hint_app, 0, "%s", ast_get_extension_app(e));
- return ast_extension_state3(hint_app, device_state_info);
+ return 0;
}
-/*! \brief Return extension_state as string */
-const char *ast_extension_state2str(int extension_state)
+/*! \brief Get hint for channel */
+int ast_str_get_hint(struct ast_str **hint, ssize_t hintsize, struct ast_str **name, ssize_t namesize, struct ast_channel *c, const char *context, const char *exten)
{
- int i;
+ struct ast_exten *e = ast_hint_extension(c, context, exten);
- for (i = 0; (i < ARRAY_LEN(extension_states)); i++) {
- if (extension_states[i].extension_state == extension_state)
- return extension_states[i].text;
+ if (!e) {
+ return 0;
}
- 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 (hint) {
+ ast_str_set(hint, hintsize, "%s", ast_get_extension_app(e));
}
-
- 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;
+ if (name) {
+ const char *tmp = ast_get_extension_app_data(e);
+ if (tmp) {
+ ast_str_set(name, namesize, "%s", tmp);
}
}
-
- return ast_extension_state2(e, device_state_info); /* Check all devices in the hint */
+ return -1;
}
-/*! \brief Check extension state for an extension by using hint */
-int ast_extension_state(struct ast_channel *c, const char *context, const char *exten)
+int ast_exists_extension(struct ast_channel *c, const char *context, const char *exten, int priority, const char *callerid)
{
- return internal_extension_state_extended(c, context, exten, NULL);
+ return pbx_extension_helper(c, NULL, context, exten, priority, NULL, callerid, E_MATCH, 0, 0);
}
-/*! \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)
+int ast_findlabel_extension(struct ast_channel *c, const char *context, const char *exten, const char *label, const char *callerid)
{
- 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 pbx_extension_helper(c, NULL, context, exten, 0, label, callerid, E_FINDLABEL, 0, 0);
+}
- return ret;
+int ast_findlabel_extension2(struct ast_channel *c, struct ast_context *con, const char *exten, const char *label, const char *callerid)
+{
+ return pbx_extension_helper(c, con, NULL, exten, 0, label, callerid, E_FINDLABEL, 0, 0);
}
-static int extension_presence_state_helper(struct ast_exten *e, char **subtype, char **message)
+int ast_canmatch_extension(struct ast_channel *c, const char *context, const char *exten, int priority, const char *callerid)
{
- 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;
- }
-
- 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;
- }
- 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;
-}
-
-/*! \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;
- }
-
- 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)));
- }
- ao2_unlock(hints);
-
- publish_hint_change(hint, ne);
-
- ao2_ref(hint, -1);
-
- return 0;
-}
-
-/*! \brief Get hint for channel */
-int ast_get_hint(char *hint, int hintsize, char *name, int namesize, struct ast_channel *c, const char *context, const char *exten)
-{
- struct ast_exten *e = ast_hint_extension(c, context, exten);
-
- if (e) {
- if (hint)
- ast_copy_string(hint, ast_get_extension_app(e), hintsize);
- if (name) {
- const char *tmp = ast_get_extension_app_data(e);
- if (tmp)
- ast_copy_string(name, tmp, namesize);
- }
- return -1;
- }
- return 0;
-}
-
-/*! \brief Get hint for channel */
-int ast_str_get_hint(struct ast_str **hint, ssize_t hintsize, struct ast_str **name, ssize_t namesize, struct ast_channel *c, const char *context, const char *exten)
-{
- struct ast_exten *e = ast_hint_extension(c, context, exten);
-
- if (!e) {
- return 0;
- }
-
- if (hint) {
- ast_str_set(hint, hintsize, "%s", ast_get_extension_app(e));
- }
- if (name) {
- const char *tmp = ast_get_extension_app_data(e);
- if (tmp) {
- ast_str_set(name, namesize, "%s", tmp);
- }
- }
- return -1;
-}
-
-int ast_exists_extension(struct ast_channel *c, const char *context, const char *exten, int priority, const char *callerid)
-{
- return pbx_extension_helper(c, NULL, context, exten, priority, NULL, callerid, E_MATCH, 0, 0);
-}
-
-int ast_findlabel_extension(struct ast_channel *c, const char *context, const char *exten, const char *label, const char *callerid)
-{
- return pbx_extension_helper(c, NULL, context, exten, 0, label, callerid, E_FINDLABEL, 0, 0);
-}
-
-int ast_findlabel_extension2(struct ast_channel *c, struct ast_context *con, const char *exten, const char *label, const char *callerid)
-{
- return pbx_extension_helper(c, con, NULL, exten, 0, label, callerid, E_FINDLABEL, 0, 0);
-}
-
-int ast_canmatch_extension(struct ast_channel *c, const char *context, const char *exten, int priority, const char *callerid)
-{
- return pbx_extension_helper(c, NULL, context, exten, priority, NULL, callerid, E_CANMATCH, 0, 0);
-}
+ return pbx_extension_helper(c, NULL, context, exten, priority, NULL, callerid, E_CANMATCH, 0, 0);
+}
int ast_matchmore_extension(struct ast_channel *c, const char *context, const char *exten, int priority, const char *callerid)
{
prev_exten->next = next_node; /* unlink */
}
if (peer->peer) { /* update the new head of the pri list */
- peer->peer->next = peer->next;
- }
- } else { /* easy, we are not first priority in extension */
- previous_peer->peer = peer->peer;
- }
-
-
- /* now, free whole priority extension */
- destroy_exten(peer);
- } else {
- previous_peer = peer;
- }
- }
- if (!already_locked)
- ast_unlock_context(con);
- return found ? 0 : -1;
-}
-
-/*
- * 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 <exten>\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;
+ peer->peer->next = peer->next;
+ }
+ } else { /* easy, we are not first priority in extension */
+ previous_peer->peer = peer->peer;
+ }
- 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;
+ /* now, free whole priority extension */
+ destroy_exten(peer);
+ } else {
+ previous_peer = peer;
}
- 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 (!already_locked)
+ ast_unlock_context(con);
+ return found ? 0 : -1;
}
+/*
+ * Help for CLI commands ...
+ */
+
#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.
#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
}
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;
}
}
-/*! 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)
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 */
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;
}
}
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 */
}
}
+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)
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;
}
}
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;
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();
/*
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;
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);
}
/*
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
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);
}
}
-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.
*/
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);
/* 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;
}
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;
}
/*! 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 */
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;
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;
}
}
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;
}