Commit f4f6ec0313 for asterisk.org
commit f4f6ec0313a51d5dfc3efa0644d63b06d578f412
Author: Joshua C. Colp <jcolp@sangoma.com>
Date: Tue Feb 10 17:42:50 2026 -0400
extension_state: Add new extension state API.
Extension state to this point has been an API implemented
inside the PBX core resulting in its state being intermingled
with that of the dialplan. This increased the complexity of
the PBX core and made it difficult to enact improvements.
This change adds a new separate extension state API
which receives updates from the PBX core as configuration
changes but maintains its own separate state. The API is
also written to fully take advantage of modern APIs in a
more selective manner by subscribing each extension state to
only the devices it is interested in, ultimately reducing
resource consumption during updates. Presence state updates
being infrequently done use a single shared subscription that
goes through the extension states to find and update ones
that the update is applicable to.
Legacy API support is provided by reimplementing the API
as wrappers over the new extension state API. This improves
the legacy API by making it multithreaded, with each callback
being individually subscribed.
Autohints support is maintained but has been separated out
into a self contained new implementation.
Synchronous subscription support has also been added to
Stasis to remove the overhead of asynchronous publishing when
the handling of published messages is small and fast.
diff --git a/include/asterisk/_private.h b/include/asterisk/_private.h
index 056c0fde19..7a964a167a 100644
--- a/include/asterisk/_private.h
+++ b/include/asterisk/_private.h
@@ -59,6 +59,9 @@ void ast_msg_shutdown(void); /*!< Provided by message.c */
int aco_init(void); /*!< Provided by config_options.c */
int dns_core_init(void); /*!< Provided by dns_core.c */
int ast_refer_init(void); /*!< Provided by refer.c */
+int ast_extension_state_init(void); /*!< Provided by extension_state.c */
+int ast_extension_state_legacy_init(void); /*!< Provided by extension_state_legacy.c */
+int ast_extension_state_autohints_init(void); /*!< Provided by extension_state_autohints.c */
/*!
* \brief Initialize malloc debug phase 1.
diff --git a/include/asterisk/extension_state.h b/include/asterisk/extension_state.h
new file mode 100644
index 0000000000..55791ea9bc
--- /dev/null
+++ b/include/asterisk/extension_state.h
@@ -0,0 +1,210 @@
+/*
+ * Asterisk -- An open source telephony toolkit.
+ *
+ * Copyright (C) 2026, Sangoma Technologies Corporation
+ *
+ * Joshua C. Colp <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 */
diff --git a/include/asterisk/stasis.h b/include/asterisk/stasis.h
index b63cd297a9..ca15c35b79 100644
--- a/include/asterisk/stasis.h
+++ b/include/asterisk/stasis.h
@@ -679,6 +679,37 @@ struct stasis_subscription *__stasis_subscribe_pool(struct stasis_topic *topic,
stasis_subscription_cb callback, void *data, const char *file, int lineno, const char *func);
#define stasis_subscribe_pool(topic, callback, data) __stasis_subscribe_pool(topic, callback, data, __FILE__, __LINE__, __PRETTY_FUNCTION__)
+/*!
+ * \brief Create a subscription whose callbacks occur synchronously on message publishing
+ * \since 23.5.0
+ * \since 22.11.0
+ * \since 20.21.0
+ *
+ * In addition to being AO2 managed memory (requiring an ao2_cleanup() to free
+ * up this reference), the subscription must be explicitly unsubscribed from its
+ * topic using stasis_unsubscribe().
+ *
+ * The invocations of the callback are serialized, but will almost certainly not
+ * always happen on the same thread. The invocation order of different subscriptions
+ * is unspecified.
+ *
+ * This subscription will be invoked on the same thread that is publishing the message.
+ *
+ * \param topic Topic to subscribe to.
+ * \param callback Callback function for subscription messages.
+ * \param data Data to be passed to the callback, in addition to the message.
+ * \param file, lineno, func
+ * \return New \ref stasis_subscription object.
+ * \retval NULL on error.
+ *
+ * \note This callback will receive a callback with a message indicating it
+ * has been subscribed. This occurs immediately before accepted message
+ * types can be set and the callback must expect to receive it.
+ */
+struct stasis_subscription *__stasis_subscribe_synchronous(struct stasis_topic *topic,
+ stasis_subscription_cb callback, void *data, const char *file, int lineno, const char *func);
+#define stasis_subscribe_synchronous(topic, callback, data) __stasis_subscribe_synchronous(topic, callback, data, __FILE__, __LINE__, __PRETTY_FUNCTION__)
+
/*!
* \brief Indicate to a subscription that we are interested in a message type.
*
diff --git a/main/asterisk.c b/main/asterisk.c
index febee957d3..b398060afb 100644
--- a/main/asterisk.c
+++ b/main/asterisk.c
@@ -4342,6 +4342,9 @@ static void asterisk_daemon(int isroot, const char *runuser, const char *rungrou
check_init(load_pbx_switch(), "PBX Switch Support");
check_init(load_pbx_app(), "PBX Application Support");
check_init(load_pbx_hangup_handler(), "PBX Hangup Handler Support");
+ check_init(ast_extension_state_init(), "Extension State Support");
+ check_init(ast_extension_state_legacy_init(), "Extension State Legacy Support");
+ check_init(ast_extension_state_autohints_init(), "Extension State Autohints Support");
check_init(ast_local_init(), "Local Proxy Channel Driver");
check_init(ast_refer_init(), "Refer API");
diff --git a/main/extension_state.c b/main/extension_state.c
new file mode 100644
index 0000000000..ebe54298bc
--- /dev/null
+++ b/main/extension_state.c
@@ -0,0 +1,1597 @@
+/*
+ * Asterisk -- An open source telephony toolkit.
+ *
+ * Copyright (C) 2026, Sangoma Technologies Corporation
+ *
+ * Joshua Colp <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;
+}
diff --git a/main/extension_state_autohints.c b/main/extension_state_autohints.c
new file mode 100644
index 0000000000..66e0bf9334
--- /dev/null
+++ b/main/extension_state_autohints.c
@@ -0,0 +1,183 @@
+/*
+ * Asterisk -- An open source telephony toolkit.
+ *
+ * Copyright (C) 2026, Sangoma Technologies Corporation
+ *
+ * Joshua Colp <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;
+}
diff --git a/main/extension_state_legacy.c b/main/extension_state_legacy.c
new file mode 100644
index 0000000000..936db2a9fe
--- /dev/null
+++ b/main/extension_state_legacy.c
@@ -0,0 +1,477 @@
+/*
+ * Asterisk -- An open source telephony toolkit.
+ *
+ * Copyright (C) 2026, Sangoma Technologies Corporation
+ *
+ * Joshua Colp <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;
+}
diff --git a/main/pbx.c b/main/pbx.c
index 4182a63e84..21ce9c83c2 100644
--- a/main/pbx.c
+++ b/main/pbx.c
@@ -53,24 +53,20 @@
#include "asterisk/time.h"
#include "asterisk/manager.h"
#include "asterisk/ast_expr.h"
-#include "asterisk/linkedlists.h"
#define SAY_STUBS /* generate declarations and stubs for say methods */
#include "asterisk/say.h"
#include "asterisk/utils.h"
#include "asterisk/causes.h"
#include "asterisk/musiconhold.h"
#include "asterisk/app.h"
-#include "asterisk/devicestate.h"
-#include "asterisk/presencestate.h"
#include "asterisk/hashtab.h"
#include "asterisk/module.h"
#include "asterisk/indications.h"
-#include "asterisk/taskprocessor.h"
#include "asterisk/xmldoc.h"
-#include "asterisk/astobj2.h"
#include "asterisk/stasis_channels.h"
#include "asterisk/dial.h"
#include "asterisk/vector.h"
+#include "asterisk/extension_state.h"
#include "pbx_private.h"
/*!
@@ -178,51 +174,6 @@
may take a lot of capacity.</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
@@ -241,7 +192,11 @@ struct ast_context;
struct ast_app;
AST_THREADSTORAGE(switch_data);
-AST_THREADSTORAGE(extensionstate_buf);
+
+enum ast_context_scope {
+ AST_CONTEXT_SCOPE_LOCAL = 0, /*!< Context is only locally accessible */
+ AST_CONTEXT_SCOPE_GLOBAL, /*!< Context is globally accessible */
+};
/*!
\brief ast_exten: An extension
@@ -308,6 +263,7 @@ struct ast_context {
struct ast_includes includes; /*!< Include other contexts */
struct ast_ignorepats ignorepats; /*!< Patterns for which to continue playing dialtone */
struct ast_sws alts; /*!< Alternative switches */
+ enum ast_context_scope scope; /*!< The scope of this context */
int refcount; /*!< each module that would have created this context should inc/dec this as appropriate */
int autohints; /*!< Whether autohints support is enabled or not */
@@ -319,324 +275,6 @@ struct ast_context {
char data[];
};
-/*! \brief ast_state_cb: An extension state notify register item */
-struct ast_state_cb {
- /*! Watcher ID returned when registered. */
- int id;
- /*! Arbitrary data passed for callbacks. */
- void *data;
- /*! Flag if this callback is an extended callback containing detailed device status */
- int extended;
- /*! Callback when state changes. */
- ast_state_cb_type change_cb;
- /*! Callback when destroyed so any resources given by the registerer can be freed. */
- ast_state_cb_destroy_type destroy_cb;
- /*! \note Only used by ast_merge_contexts_and_delete */
- AST_LIST_ENTRY(ast_state_cb) entry;
-};
-
-/*!
- * \brief Structure for dial plan hints
- *
- * \note Hints are pointers from an extension in the dialplan to
- * one or more devices (tech/name)
- *
- * See \ref AstExtState
- */
-struct ast_hint {
- /*!
- * \brief Hint extension
- *
- * \note
- * Will never be NULL while the hint is in the hints container.
- */
- struct ast_exten *exten;
- struct ao2_container *callbacks; /*!< Device state callback container for this extension */
-
- /*! Dev state variables */
- int laststate; /*!< Last known device state */
-
- /*! Presence state variables */
- int last_presence_state; /*!< Last known presence state */
- char *last_presence_subtype; /*!< Last known presence subtype string */
- char *last_presence_message; /*!< Last known presence message string */
-
- char context_name[AST_MAX_CONTEXT];/*!< Context of destroyed hint extension. */
- char exten_name[AST_MAX_EXTENSION];/*!< Extension of destroyed hint extension. */
-
- AST_VECTOR(, char *) devices; /*!< Devices associated with the hint */
-};
-
-STASIS_MESSAGE_TYPE_DEFN_LOCAL(hint_change_message_type);
-STASIS_MESSAGE_TYPE_DEFN_LOCAL(hint_remove_message_type);
-
-#define HINTDEVICE_DATA_LENGTH 16
-AST_THREADSTORAGE(hintdevice_data);
-
-/* --- Hash tables of various objects --------*/
-#ifdef LOW_MEMORY
-#define HASH_EXTENHINT_SIZE 17
-#else
-#define HASH_EXTENHINT_SIZE 563
-#endif
-
-
-/*! \brief Container for hint devices */
-static struct ao2_container *hintdevices;
-
-/*!
- * \brief Structure for dial plan hint devices
- * \note hintdevice is one device pointing to a hint.
- */
-struct ast_hintdevice {
- /*!
- * \brief Hint this hintdevice belongs to.
- * \note Holds a reference to the hint object.
- */
- struct ast_hint *hint;
- /*! Name of the hint device. */
- char hintdevice[1];
-};
-
-/*! \brief Container for autohint contexts */
-static struct ao2_container *autohints;
-
-/*!
- * \brief Structure for dial plan autohints
- */
-struct ast_autohint {
- /*! \brief Name of the registrar */
- char *registrar;
- /*! \brief Name of the context */
- char context[1];
-};
-
-/*!
- * \note Using the device for hash
- */
-static int hintdevice_hash_cb(const void *obj, const int flags)
-{
- const struct ast_hintdevice *ext;
- const char *key;
-
- switch (flags & OBJ_SEARCH_MASK) {
- case OBJ_SEARCH_KEY:
- key = obj;
- break;
- case OBJ_SEARCH_OBJECT:
- ext = obj;
- key = ext->hintdevice;
- break;
- default:
- ast_assert(0);
- return 0;
- }
-
- return ast_str_case_hash(key);
-}
-
-/*!
- * \note Devices on hints are not unique so no CMP_STOP is returned
- * Dont use ao2_find against hintdevices container cause there always
- * could be more than one result.
- */
-static int hintdevice_cmp_multiple(void *obj, void *arg, int flags)
-{
- struct ast_hintdevice *left = obj;
- struct ast_hintdevice *right = arg;
- const char *right_key = arg;
- int cmp;
-
- switch (flags & OBJ_SEARCH_MASK) {
- case OBJ_SEARCH_OBJECT:
- right_key = right->hintdevice;
- /* Fall through */
- case OBJ_SEARCH_KEY:
- cmp = strcasecmp(left->hintdevice, right_key);
- break;
- case OBJ_SEARCH_PARTIAL_KEY:
- /*
- * We could also use a partial key struct containing a length
- * so strlen() does not get called for every comparison instead.
- */
- cmp = strncmp(left->hintdevice, right_key, strlen(right_key));
- break;
- default:
- ast_assert(0);
- cmp = 0;
- break;
- }
- return cmp ? 0 : CMP_MATCH;
-}
-
-/*!
- * \note Using the context name for hash
- */
-static int autohint_hash_cb(const void *obj, const int flags)
-{
- const struct ast_autohint *autohint;
- const char *key;
-
- switch (flags & OBJ_SEARCH_MASK) {
- case OBJ_SEARCH_KEY:
- key = obj;
- break;
- case OBJ_SEARCH_OBJECT:
- autohint = obj;
- key = autohint->context;
- break;
- default:
- ast_assert(0);
- return 0;
- }
-
- return ast_str_case_hash(key);
-}
-
-static int autohint_cmp(void *obj, void *arg, int flags)
-{
- struct ast_autohint *left = obj;
- struct ast_autohint *right = arg;
- const char *right_key = arg;
- int cmp;
-
- switch (flags & OBJ_SEARCH_MASK) {
- case OBJ_SEARCH_OBJECT:
- right_key = right->context;
- /* Fall through */
- case OBJ_SEARCH_KEY:
- cmp = strcasecmp(left->context, right_key);
- break;
- case OBJ_SEARCH_PARTIAL_KEY:
- /*
- * We could also use a partial key struct containing a length
- * so strlen() does not get called for every comparison instead.
- */
- cmp = strncmp(left->context, right_key, strlen(right_key));
- break;
- default:
- ast_assert(0);
- cmp = 0;
- break;
- }
- return cmp ? 0 : CMP_MATCH | CMP_STOP;
-}
-
-/*! \internal \brief \c ao2_callback function to remove hintdevices */
-static int hintdevice_remove_cb(void *obj, void *arg, void *data, int flags)
-{
- struct ast_hintdevice *candidate = obj;
- char *device = arg;
- struct ast_hint *hint = data;
-
- if (!strcasecmp(candidate->hintdevice, device)
- && candidate->hint == hint) {
- return CMP_MATCH;
- }
- return 0;
-}
-
-static int remove_hintdevice(struct ast_hint *hint)
-{
- while (AST_VECTOR_SIZE(&hint->devices) > 0) {
- char *device = AST_VECTOR_GET(&hint->devices, 0);
-
- ao2_t_callback_data(hintdevices, OBJ_SEARCH_KEY | OBJ_UNLINK | OBJ_NODATA,
- hintdevice_remove_cb, device, hint, "Remove device from container");
- AST_VECTOR_REMOVE_UNORDERED(&hint->devices, 0);
- ast_free(device);
- }
-
- return 0;
-}
-
-static char *parse_hint_device(struct ast_str *hint_args);
-/*!
- * \internal
- * \brief Destroy the given hintdevice object.
- *
- * \param obj Hint device to destroy.
- */
-static void hintdevice_destroy(void *obj)
-{
- struct ast_hintdevice *doomed = obj;
-
- if (doomed->hint) {
- ao2_ref(doomed->hint, -1);
- doomed->hint = NULL;
- }
-}
-
-/*! \brief add hintdevice structure and link it into the container.
- */
-static int add_hintdevice(struct ast_hint *hint, const char *devicelist)
-{
- struct ast_str *str;
- char *parse;
- char *cur;
- struct ast_hintdevice *device;
- int devicelength;
-
- if (!hint || !devicelist) {
- /* Trying to add garbage? Don't bother. */
- return 0;
- }
- if (!(str = ast_str_thread_get(&hintdevice_data, 16))) {
- return -1;
- }
- ast_str_set(&str, 0, "%s", devicelist);
- parse = ast_str_buffer(str);
-
- /* Spit on '&' and ',' to handle presence hints as well */
- while ((cur = strsep(&parse, "&,"))) {
- char *device_name;
-
- devicelength = strlen(cur);
- if (!devicelength) {
- continue;
- }
-
- device_name = ast_strdup(cur);
- if (!device_name) {
- return -1;
- }
-
- device = ao2_t_alloc(sizeof(*device) + devicelength, hintdevice_destroy,
- "allocating a hintdevice structure");
- if (!device) {
- ast_free(device_name);
- return -1;
- }
- strcpy(device->hintdevice, cur);
- ao2_ref(hint, +1);
- device->hint = hint;
- if (AST_VECTOR_APPEND(&hint->devices, device_name)) {
- ast_free(device_name);
- ao2_ref(device, -1);
- return -1;
- }
- ao2_t_link(hintdevices, device, "Linking device into hintdevice container.");
- ao2_t_ref(device, -1, "hintdevice is linked so we can unref");
- }
-
- return 0;
-}
-
-
-static const struct cfextension_states {
- int extension_state;
- const char * const text;
-} extension_states[] = {
- { AST_EXTENSION_NOT_INUSE, "Idle" },
- { AST_EXTENSION_INUSE, "InUse" },
- { AST_EXTENSION_BUSY, "Busy" },
- { AST_EXTENSION_UNAVAILABLE, "Unavailable" },
- { AST_EXTENSION_RINGING, "Ringing" },
- { AST_EXTENSION_INUSE | AST_EXTENSION_RINGING, "InUse&Ringing" },
- { AST_EXTENSION_ONHOLD, "Hold" },
- { AST_EXTENSION_INUSE | AST_EXTENSION_ONHOLD, "InUse&Hold" }
-};
-
struct pbx_exception {
AST_DECLARE_STRING_FIELDS(
AST_STRING_FIELD(context); /*!< Context associated with this exception */
@@ -667,9 +305,6 @@ static unsigned int hashtab_hash_extens(const void *obj);
static unsigned int hashtab_hash_priority(const void *obj);
static unsigned int hashtab_hash_labels(const void *obj);
static void __ast_internal_context_destroy( struct ast_context *con);
-static int ast_add_extension_nolock(const char *context, int replace, const char *extension,
- int priority, const char *label, const char *callerid,
- const char *application, void *data, void (*datad)(void *), const char *registrar);
static int ast_add_extension2_lockopt(struct ast_context *con,
int replace, const char *extension, int priority, const char *label, const char *callerid,
const char *application, void *data, void (*datad)(void *),
@@ -677,7 +312,6 @@ static int ast_add_extension2_lockopt(struct ast_context *con,
int lock_context);
static struct ast_context *find_context_locked(const char *context);
static struct ast_context *find_context(const char *context);
-static void get_device_state_causing_channels(struct ao2_container *c);
static unsigned int ext_strncpy(char *dst, const char *src, size_t dst_size, int nofluff);
/*!
@@ -784,11 +418,6 @@ static int autofallthrough = 1;
static int extenpatternmatchnew = 0;
static char *overrideswitch = NULL;
-/*! \brief Subscription for device state change events */
-static struct stasis_subscription *device_state_sub;
-/*! \brief Subscription for presence state change events */
-static struct stasis_subscription *presence_state_sub;
-
AST_MUTEX_DEFINE_STATIC(maxcalllock);
static int countcalls;
static int totalcalls;
@@ -804,24 +433,6 @@ static struct ast_hashtab *contexts_table = NULL;
*/
AST_MUTEX_DEFINE_STATIC(conlock);
-/*!
- * \brief Lock to hold off restructuring of hints by ast_merge_contexts_and_delete.
- */
-AST_MUTEX_DEFINE_STATIC(context_merge_lock);
-
-static int stateid = 1;
-/*!
- * \note When holding this container's lock, do _not_ do
- * anything that will cause conlock to be taken, unless you
- * _already_ hold it. The ast_merge_contexts_and_delete function
- * will take the locks in conlock/hints order, so any other
- * paths that require both locks must also take them in that
- * order.
- */
-static struct ao2_container *hints;
-
-static struct ao2_container *statecbs;
-
#ifdef CONTEXT_DEBUG
/* these routines are provided for doing run-time checks
@@ -3039,1194 +2650,107 @@ static struct ast_exten *ast_hint_extension(struct ast_channel *c, const char *c
return e;
}
-enum ast_extension_states ast_devstate_to_extenstate(enum ast_device_state devstate)
-{
- switch (devstate) {
- case AST_DEVICE_ONHOLD:
- return AST_EXTENSION_ONHOLD;
- case AST_DEVICE_BUSY:
- return AST_EXTENSION_BUSY;
- case AST_DEVICE_UNKNOWN:
- return AST_EXTENSION_NOT_INUSE;
- case AST_DEVICE_UNAVAILABLE:
- case AST_DEVICE_INVALID:
- return AST_EXTENSION_UNAVAILABLE;
- case AST_DEVICE_RINGINUSE:
- return (AST_EXTENSION_INUSE | AST_EXTENSION_RINGING);
- case AST_DEVICE_RINGING:
- return AST_EXTENSION_RINGING;
- case AST_DEVICE_INUSE:
- return AST_EXTENSION_INUSE;
- case AST_DEVICE_NOT_INUSE:
- return AST_EXTENSION_NOT_INUSE;
- case AST_DEVICE_TOTAL: /* not a device state, included for completeness */
- break;
- }
-
- return AST_EXTENSION_NOT_INUSE;
-}
-
-/*!
- * \internal
- * \brief Parse out the presence portion of the hint string
- */
-static char *parse_hint_presence(struct ast_str *hint_args)
+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)
{
@@ -5166,193 +3690,28 @@ int ast_context_remove_extension_callerid2(struct ast_context *con, const char *
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.
@@ -6152,8 +4511,6 @@ static struct ast_cli_entry pbx_cli[] = {
#if 0
AST_CLI_DEFINE(handle_eat_memory, "Eats all available memory"),
#endif
- AST_CLI_DEFINE(handle_show_hints, "Show dialplan hints"),
- AST_CLI_DEFINE(handle_show_hint, "Show dialplan hint"),
#ifdef AST_DEVMODE
AST_CLI_DEFINE(handle_show_device2extenstate, "Show expected exten state from multiple device states"),
#endif
@@ -6248,43 +4605,39 @@ struct ast_context *ast_context_find_or_create(struct ast_context **extcontexts,
}
if (!extcontexts) {
+ tmp->scope = AST_CONTEXT_SCOPE_GLOBAL;
tmp->next = *local_contexts;
*local_contexts = tmp;
ast_hashtab_insert_safe(contexts_table, tmp); /*put this context into the tree */
ast_unlock_contexts();
} else {
+ tmp->scope = AST_CONTEXT_SCOPE_LOCAL;
tmp->next = *local_contexts;
if (exttable)
ast_hashtab_insert_immediate(exttable, tmp); /*put this context into the tree */
*local_contexts = tmp;
}
- ast_debug(1, "Registered extension context '%s'; registrar: %s\n", tmp->name, registrar);
+ ast_debug(1, "Registered extension context '%s'; registrar: %s, scope: %s\n", tmp->name, registrar,
+ tmp->scope == AST_CONTEXT_SCOPE_LOCAL ? "local": "global");
return tmp;
}
void ast_context_set_autohints(struct ast_context *con, int enabled)
{
con->autohints = enabled;
+
+ if (con->scope == AST_CONTEXT_SCOPE_GLOBAL) {
+ if (con->autohints) {
+ pbx_extension_state_autohint_set(con);
+ } else {
+ pbx_extension_state_autohint_remove(con, 1);
+ }
+ }
}
void __ast_context_destroy(struct ast_context *list, struct ast_hashtab *contexttab, struct ast_context *con, const char *registrar);
-struct store_hint {
- char *context;
- char *exten;
- AST_LIST_HEAD_NOLOCK(, ast_state_cb) callbacks;
- int laststate;
- int last_presence_state;
- char *last_presence_subtype;
- char *last_presence_message;
-
- AST_LIST_ENTRY(store_hint) list;
- char data[0];
-};
-
-AST_LIST_HEAD_NOLOCK(store_hints, store_hint);
-
static void context_merge_incls_swits_igps_other_registrars(struct ast_context *new, struct ast_context *old, const char *registrar)
{
int idx;
@@ -6322,42 +4675,6 @@ static void context_merge_incls_swits_igps_other_registrars(struct ast_context *
}
}
-/*! Set up an autohint placeholder in the hints container */
-static void context_table_create_autohints(struct ast_hashtab *table)
-{
- struct ast_context *con;
- struct ast_hashtab_iter *iter;
-
- /* Remove all autohints as the below iteration will recreate them */
- ao2_callback(autohints, OBJ_UNLINK | OBJ_NODATA | OBJ_MULTIPLE, NULL, NULL);
-
- iter = ast_hashtab_start_traversal(table);
- while ((con = ast_hashtab_next(iter))) {
- size_t name_len = strlen(con->name) + 1;
- size_t registrar_len = strlen(con->registrar) + 1;
- struct ast_autohint *autohint;
-
- if (!con->autohints) {
- continue;
- }
-
- autohint = ao2_alloc_options(sizeof(*autohint) + name_len + registrar_len, NULL, AO2_ALLOC_OPT_LOCK_NOLOCK);
- if (!autohint) {
- continue;
- }
-
- ast_copy_string(autohint->context, con->name, name_len);
- autohint->registrar = autohint->context + name_len;
- ast_copy_string(autohint->registrar, con->registrar, registrar_len);
-
- ao2_link(autohints, autohint);
- ao2_ref(autohint, -1);
-
- ast_verb(3, "Enabled autohints support on context '%s'\n", con->name);
- }
- ast_hashtab_end_traversal(iter);
-}
-
/* the purpose of this routine is to duplicate a context, with all its substructure,
except for any extens that have a matching registrar */
static void context_merge(struct ast_context **extcontexts, struct ast_hashtab *exttable, struct ast_context *context, const char *registrar)
@@ -6393,6 +4710,33 @@ static void context_merge(struct ast_context **extcontexts, struct ast_hashtab *
new_prio_item = NULL;
}
if (strcmp(prio_item->registrar,registrar) == 0) {
+ struct ast_exten *pattern_exten;
+ struct pbx_find_info q = { .stacklen = 0 };
+
+ if (prio_item->priority != PRIORITY_HINT || prio_item->name[0] == '_' || new_prio_item || !new) {
+ continue;
+ }
+
+ /*
+ * This hint no longer exists in the new context, but it may have been created as a result of
+ * a pattern match so see if a pattern match matches it. If it does then we add it in to the new
+ * context using the registrar of the pattern match.
+ */
+ pattern_exten = pbx_find_extension(NULL, new, &q, context->name,
+ prio_item->name, PRIORITY_HINT, NULL, "", E_MATCH);
+ if (pattern_exten && !strcmp(q.foundcontext, context->name)) {
+ /*
+ * This logic doesn't check whether it's a pattern match or not because if it was
+ * an exact match we would have already skipped it above due to new_prio_item being
+ * present. Logically it could only ever be a pattern match here.
+ */
+ dupdstr = ast_strdup(prio_item->data);
+
+ res1 = ast_add_extension2(new, 0, prio_item->name, prio_item->priority, prio_item->label,
+ prio_item->matchcid ? prio_item->cidmatch : NULL, prio_item->app, dupdstr, ast_free_ptr, prio_item->registrar,
+ prio_item->registrar_file, prio_item->registrar_line);
+ }
+
continue;
}
/* make sure the new context exists, so we have somewhere to stick this exten/prio */
@@ -6400,6 +4744,10 @@ static void context_merge(struct ast_context **extcontexts, struct ast_hashtab *
new = ast_context_find_or_create(extcontexts, exttable, context->name, prio_item->registrar); /* a new context created via priority from a different context in the old dialplan, gets its registrar from the prio's registrar */
if (new) {
new->autohints = context->autohints;
+ if (new->autohints) {
+ pbx_extension_state_autohint_set(new);
+ }
+ new->scope = AST_CONTEXT_SCOPE_GLOBAL;
}
}
@@ -6450,6 +4798,10 @@ static void context_merge(struct ast_context **extcontexts, struct ast_hashtab *
if (new) {
new->autohints = context->autohints;
+ if (new->autohints) {
+ pbx_extension_state_autohint_set(new);
+ }
+ new->scope = AST_CONTEXT_SCOPE_GLOBAL;
}
/* copy in the includes, switches, and ignorepats */
@@ -6457,6 +4809,51 @@ static void context_merge(struct ast_context **extcontexts, struct ast_hashtab *
}
}
+static int context_promote(struct ast_context *context)
+{
+ struct ast_exten *exten_item, *prio_item;
+ struct ast_hashtab_iter *exten_iter;
+ struct ast_hashtab_iter *prio_iter;
+
+ /* Contexts already promoted to global have been handled previously, so skip */
+ if (context->scope == AST_CONTEXT_SCOPE_GLOBAL) {
+ return 0;
+ }
+
+ /* Enable or remove autohints as needed */
+ if (context->autohints) {
+ pbx_extension_state_autohint_set(context);
+ } else {
+ pbx_extension_state_autohint_remove(context, 1);
+ }
+
+ /* Further handling requires extensions to exist */
+ if (!context->root_table) {
+ return 0;
+ }
+
+ /*
+ * Hints are stateless but extension state is not. To keep extension state up to date
+ * we go through all the hints on contexts promoted from local scope to global scope and
+ * inform extension state as it is purely driven based on global scope dialplan.
+ */
+ exten_iter = ast_hashtab_start_traversal(context->root_table);
+ while ((exten_item = ast_hashtab_next(exten_iter))) {
+ prio_iter = ast_hashtab_start_traversal(exten_item->peer_table);
+ while ((prio_item = ast_hashtab_next(prio_iter))) {
+ if (prio_item->priority != PRIORITY_HINT) {
+ continue;
+ }
+ pbx_extension_state_hint_set(prio_item, context);
+ }
+ ast_hashtab_end_traversal(prio_iter);
+ }
+ ast_hashtab_end_traversal(exten_iter);
+
+ context->scope = AST_CONTEXT_SCOPE_GLOBAL;
+
+ return 1;
+}
/* XXX this does not check that multiple contexts are merged */
void ast_merge_contexts_and_delete(struct ast_context **extcontexts, struct ast_hashtab *exttable, const char *registrar)
@@ -6465,46 +4862,28 @@ void ast_merge_contexts_and_delete(struct ast_context **extcontexts, struct ast_
struct ast_context *tmp;
struct ast_context *oldcontextslist;
struct ast_hashtab *oldtable;
- struct store_hints hints_stored = AST_LIST_HEAD_NOLOCK_INIT_VALUE;
- struct store_hints hints_removed = AST_LIST_HEAD_NOLOCK_INIT_VALUE;
- struct store_hint *saved_hint;
- struct ast_hint *hint;
- struct ast_exten *exten;
- int length;
- struct ast_state_cb *thiscb;
struct ast_hashtab_iter *iter;
- struct ao2_iterator i;
- int ctx_count = 0;
+ int ctx_count = 0, promoted_count = 0;
struct timeval begintime;
struct timeval writelocktime;
struct timeval endlocktime;
struct timeval enddeltime;
- /*
- * It is very important that this function hold the hints
- * container lock _and_ the conlock during its operation; not
- * only do we need to ensure that the list of contexts and
- * extensions does not change, but also that no hint callbacks
- * (watchers) are added or removed during the merge/delete
- * process
- *
- * In addition, the locks _must_ be taken in this order, because
- * there are already other code paths that use this order
- */
-
begintime = ast_tvnow();
- ast_mutex_lock(&context_merge_lock);/* Serialize ast_merge_contexts_and_delete */
ast_wrlock_contexts();
if (!contexts_table) {
- /* Create any autohint contexts */
- context_table_create_autohints(exttable);
-
/* Well, that's odd. There are no contexts. */
contexts_table = exttable;
contexts = *extcontexts;
+
+ iter = ast_hashtab_start_traversal(contexts_table);
+ while ((tmp = ast_hashtab_next(iter))) {
+ context_promote(tmp);
+ }
+ ast_hashtab_end_traversal(iter);
+
ast_unlock_contexts();
- ast_mutex_unlock(&context_merge_lock);
return;
}
@@ -6515,57 +4894,8 @@ void ast_merge_contexts_and_delete(struct ast_context **extcontexts, struct ast_
}
ast_hashtab_end_traversal(iter);
- ao2_lock(hints);
writelocktime = ast_tvnow();
- /* preserve all watchers for hints */
- i = ao2_iterator_init(hints, AO2_ITERATOR_DONTLOCK);
- for (; (hint = ao2_iterator_next(&i)); ao2_ref(hint, -1)) {
- if (ao2_container_count(hint->callbacks)) {
- size_t exten_len;
-
- ao2_lock(hint);
- if (!hint->exten) {
- /* The extension has already been destroyed. (Should never happen here) */
- ao2_unlock(hint);
- continue;
- }
-
- exten_len = strlen(hint->exten->exten) + 1;
- length = exten_len + strlen(hint->exten->parent->name) + 1
- + sizeof(*saved_hint);
- if (!(saved_hint = ast_calloc(1, length))) {
- ao2_unlock(hint);
- continue;
- }
-
- /* This removes all the callbacks from the hint into saved_hint. */
- while ((thiscb = ao2_callback(hint->callbacks, OBJ_UNLINK, NULL, NULL))) {
- AST_LIST_INSERT_TAIL(&saved_hint->callbacks, thiscb, entry);
- /*
- * We intentionally do not unref thiscb to account for the
- * non-ao2 reference in saved_hint->callbacks
- */
- }
-
- saved_hint->laststate = hint->laststate;
- saved_hint->context = saved_hint->data;
- strcpy(saved_hint->data, hint->exten->parent->name);
- saved_hint->exten = saved_hint->data + strlen(saved_hint->context) + 1;
- ast_copy_string(saved_hint->exten, hint->exten->exten, exten_len);
- if (hint->last_presence_subtype) {
- saved_hint->last_presence_subtype = ast_strdup(hint->last_presence_subtype);
- }
- if (hint->last_presence_message) {
- saved_hint->last_presence_message = ast_strdup(hint->last_presence_message);
- }
- saved_hint->last_presence_state = hint->last_presence_state;
- ao2_unlock(hint);
- AST_LIST_INSERT_HEAD(&hints_stored, saved_hint, list);
- }
- }
- ao2_iterator_destroy(&i);
-
/* save the old table and list */
oldtable = contexts_table;
oldcontextslist = contexts;
@@ -6574,91 +4904,18 @@ void ast_merge_contexts_and_delete(struct ast_context **extcontexts, struct ast_
contexts_table = exttable;
contexts = *extcontexts;
- /*
- * Restore the watchers for hints that can be found; notify
- * those that cannot be restored.
- */
- while ((saved_hint = AST_LIST_REMOVE_HEAD(&hints_stored, list))) {
- struct pbx_find_info q = { .stacklen = 0 };
-
- exten = pbx_find_extension(NULL, NULL, &q, saved_hint->context, saved_hint->exten,
- PRIORITY_HINT, NULL, "", E_MATCH);
- /*
- * If this is a pattern, dynamically create a new extension for this
- * particular match. Note that this will only happen once for each
- * individual extension, because the pattern will no longer match first.
- */
- if (exten && exten->exten[0] == '_') {
- ast_add_extension_nolock(exten->parent->name, 0, saved_hint->exten,
- PRIORITY_HINT, NULL, 0, exten->app, ast_strdup(exten->data), ast_free_ptr,
- exten->registrar);
- /* rwlocks are not recursive locks */
- exten = ast_hint_extension_nolock(NULL, saved_hint->context,
- saved_hint->exten);
- }
-
- /* Find the hint in the hints container */
- hint = exten ? ao2_find(hints, exten, 0) : NULL;
- if (!hint) {
- /*
- * Notify watchers of this removed hint later when we aren't
- * encumbered by so many locks.
- */
- AST_LIST_INSERT_HEAD(&hints_removed, saved_hint, list);
- } else {
- ao2_lock(hint);
- while ((thiscb = AST_LIST_REMOVE_HEAD(&saved_hint->callbacks, entry))) {
- ao2_link(hint->callbacks, thiscb);
- /* Ref that we added when putting into saved_hint->callbacks */
- ao2_ref(thiscb, -1);
- }
- hint->laststate = saved_hint->laststate;
- hint->last_presence_state = saved_hint->last_presence_state;
- hint->last_presence_subtype = saved_hint->last_presence_subtype;
- hint->last_presence_message = saved_hint->last_presence_message;
- ao2_unlock(hint);
- ao2_ref(hint, -1);
- /*
- * The free of saved_hint->last_presence_subtype and
- * saved_hint->last_presence_message is not necessary here.
- */
- ast_free(saved_hint);
- }
+ iter = ast_hashtab_start_traversal(contexts_table);
+ while ((tmp = ast_hashtab_next(iter))) {
+ promoted_count += context_promote(tmp);
}
-
- /* Create all applicable autohint contexts */
- context_table_create_autohints(contexts_table);
+ ast_hashtab_end_traversal(iter);
/* ctx_count is still the number of old contexts before the merge,
* use the new count when we tell the user how many contexts exist. */
ctx_count = ast_hashtab_size(contexts_table);
- ao2_unlock(hints);
ast_unlock_contexts();
- /*
- * Notify watchers of all removed hints with the same lock
- * environment as device_state_cb().
- */
- while ((saved_hint = AST_LIST_REMOVE_HEAD(&hints_removed, list))) {
- /* this hint has been removed, notify the watchers */
- while ((thiscb = AST_LIST_REMOVE_HEAD(&saved_hint->callbacks, entry))) {
- execute_state_callback(thiscb->change_cb,
- saved_hint->context,
- saved_hint->exten,
- thiscb->data,
- AST_HINT_UPDATE_DEVICE,
- NULL,
- NULL);
- /* Ref that we added when putting into saved_hint->callbacks */
- ao2_ref(thiscb, -1);
- }
- ast_free(saved_hint->last_presence_subtype);
- ast_free(saved_hint->last_presence_message);
- ast_free(saved_hint);
- }
-
- ast_mutex_unlock(&context_merge_lock);
endlocktime = ast_tvnow();
/*
@@ -6684,7 +4941,7 @@ void ast_merge_contexts_and_delete(struct ast_context **extcontexts, struct ast_
ft = ast_tvdiff_us(endlocktime, writelocktime);
ft /= 1000000.0;
- ast_verb(5,"Time to restore hints and swap in new dialplan: %8.6f sec\n", ft);
+ ast_verb(5,"Time to promote contexts and swap in new dialplan: %8.6f sec\n", ft);
ft = ast_tvdiff_us(enddeltime, endlocktime);
ft /= 1000000.0;
@@ -6693,7 +4950,8 @@ void ast_merge_contexts_and_delete(struct ast_context **extcontexts, struct ast_
ft = ast_tvdiff_us(enddeltime, begintime);
ft /= 1000000.0;
ast_verb(5,"Total time merge_contexts_delete: %8.6f sec\n", ft);
- ast_verb(5, "%s successfully loaded %d contexts (enable debug for details).\n", registrar, ctx_count);
+ ast_verb(5, "%s successfully loaded %d contexts after incorporating %d promoted contexts (enable debug for details).\n",
+ registrar, ctx_count, promoted_count);
}
/*
@@ -6940,26 +5198,6 @@ int ast_ignore_pattern(const char *context, const char *pattern)
return ret;
}
-/*
- * ast_add_extension_nolock -- use only in situations where the conlock is already held
- * ENOENT - no existence of context
- *
- */
-static int ast_add_extension_nolock(const char *context, int replace, const char *extension,
- int priority, const char *label, const char *callerid,
- const char *application, void *data, void (*datad)(void *), const char *registrar)
-{
- int ret = -1;
- struct ast_context *c;
-
- c = find_context(context);
- if (c) {
- ret = ast_add_extension2_lockopt(c, replace, extension, priority, label, callerid,
- application, data, datad, registrar, NULL, 0, 1);
- }
-
- return ret;
-}
/*
* EBUSY - can't lock
* ENOENT - no existence of context
@@ -8061,6 +6299,10 @@ static void __ast_internal_context_destroy( struct ast_context *con)
struct ast_exten *e, *el, *en;
struct ast_context *tmp = con;
+ if (con->scope == AST_CONTEXT_SCOPE_GLOBAL && con->autohints) {
+ pbx_extension_state_autohint_remove(con, 0);
+ }
+
/* Free includes */
AST_VECTOR_CALLBACK_VOID(&tmp->includes, include_free);
AST_VECTOR_FREE(&tmp->includes);
@@ -8335,115 +6577,6 @@ int pbx_checkcondition(const char *condition)
}
}
-static void presence_state_cb(void *unused, struct stasis_subscription *sub, struct stasis_message *msg)
-{
- struct ast_presence_state_message *presence_state;
- struct ast_str *hint_app = NULL;
- struct ast_hintdevice *device;
- struct ast_hintdevice *cmpdevice;
- struct ao2_iterator *dev_iter;
-
- if (stasis_message_type(msg) != ast_presence_state_message_type()) {
- return;
- }
-
- presence_state = stasis_message_data(msg);
-
- if (ao2_container_count(hintdevices) == 0) {
- /* There are no hints monitoring devices. */
- return;
- }
-
- hint_app = ast_str_create(1024);
- if (!hint_app) {
- return;
- }
-
- cmpdevice = ast_alloca(sizeof(*cmpdevice) + strlen(presence_state->provider));
- strcpy(cmpdevice->hintdevice, presence_state->provider);
-
- ast_mutex_lock(&context_merge_lock);/* Hold off ast_merge_contexts_and_delete */
- dev_iter = ao2_t_callback(hintdevices,
- OBJ_POINTER | OBJ_MULTIPLE,
- hintdevice_cmp_multiple,
- cmpdevice,
- "find devices in container");
- if (!dev_iter) {
- ast_mutex_unlock(&context_merge_lock);
- ast_free(hint_app);
- return;
- }
-
- for (; (device = ao2_iterator_next(dev_iter)); ao2_t_ref(device, -1, "Next device")) {
- if (device->hint) {
- presence_state_notify_callbacks(device->hint, &hint_app, presence_state);
- }
- }
- ao2_iterator_destroy(dev_iter);
- ast_mutex_unlock(&context_merge_lock);
-
- ast_free(hint_app);
-}
-
-static int action_extensionstatelist(struct mansession *s, const struct message *m)
-{
- const char *action_id = astman_get_header(m, "ActionID");
- struct ast_hint *hint;
- struct ao2_iterator it_hints;
- int hint_count = 0;
-
- if (!hints) {
- astman_send_error(s, m, "No dialplan hints are available");
- return 0;
- }
-
- astman_send_listack(s, m, "Extension Statuses will follow", "start");
-
- ao2_lock(hints);
- it_hints = ao2_iterator_init(hints, 0);
- for (; (hint = ao2_iterator_next(&it_hints)); ao2_ref(hint, -1)) {
-
- ao2_lock(hint);
-
- /* Ignore pattern matching hints; they are stored in the
- * hints container but aren't real from the perspective of
- * an AMI user
- */
- if (hint->exten->exten[0] == '_') {
- ao2_unlock(hint);
- continue;
- }
-
- ++hint_count;
-
- astman_append(s, "Event: ExtensionStatus\r\n");
- if (!ast_strlen_zero(action_id)) {
- astman_append(s, "ActionID: %s\r\n", action_id);
- }
- astman_append(s,
- "Exten: %s\r\n"
- "Context: %s\r\n"
- "Hint: %s\r\n"
- "Status: %d\r\n"
- "StatusText: %s\r\n\r\n",
- hint->exten->exten,
- hint->exten->parent->name,
- hint->exten->app,
- hint->laststate,
- ast_extension_state2str(hint->laststate));
- ao2_unlock(hint);
- }
-
- ao2_iterator_destroy(&it_hints);
- ao2_unlock(hints);
-
- astman_send_list_complete_start(s, m, "ExtensionStateListComplete", hint_count);
- astman_send_list_complete_end(s);
-
- return 0;
-}
-
-
/*!
* \internal
* \brief Clean up resources on Asterisk shutdown.
@@ -8452,11 +6585,7 @@ static int action_extensionstatelist(struct mansession *s, const struct message
*/
static void unload_pbx(void)
{
- presence_state_sub = stasis_unsubscribe_and_join(presence_state_sub);
- device_state_sub = stasis_unsubscribe_and_join(device_state_sub);
-
ast_manager_unregister("ShowDialPlan");
- ast_manager_unregister("ExtensionStateList");
ast_cli_unregister_multiple(pbx_cli, ARRAY_LEN(pbx_cli));
ast_custom_function_unregister(&exception_function);
ast_custom_function_unregister(&testtime_function);
@@ -8478,26 +6607,11 @@ int load_pbx(void)
/* Register manager application */
res |= ast_manager_register_xml_core("ShowDialPlan", EVENT_FLAG_CONFIG | EVENT_FLAG_REPORTING, manager_show_dialplan);
- res |= ast_manager_register_xml_core("ExtensionStateList", EVENT_FLAG_CALL | EVENT_FLAG_REPORTING, action_extensionstatelist);
if (res) {
return -1;
}
- if (!(device_state_sub = stasis_subscribe(ast_device_state_topic_all(), device_state_cb, NULL))) {
- return -1;
- }
- stasis_subscription_accept_message_type(device_state_sub, ast_device_state_message_type());
- stasis_subscription_accept_message_type(device_state_sub, hint_change_message_type());
- stasis_subscription_accept_message_type(device_state_sub, hint_remove_message_type());
- stasis_subscription_set_filter(device_state_sub, STASIS_SUBSCRIPTION_FILTER_SELECTIVE);
-
- if (!(presence_state_sub = stasis_subscribe(ast_presence_state_topic_all(), presence_state_cb, NULL))) {
- return -1;
- }
- stasis_subscription_accept_message_type(presence_state_sub, ast_presence_state_message_type());
- stasis_subscription_set_filter(presence_state_sub, STASIS_SUBSCRIPTION_FILTER_SELECTIVE);
-
return 0;
}
@@ -8917,150 +7031,19 @@ int ast_async_parseable_goto(struct ast_channel *chan, const char *goto_string)
return pbx_parseable_goto(chan, goto_string, 1);
}
-static int hint_hash(const void *obj, const int flags)
-{
- const struct ast_hint *hint = obj;
- const char *exten_name;
- int res;
-
- exten_name = ast_get_extension_name(hint->exten);
- if (ast_strlen_zero(exten_name)) {
- /*
- * If the exten or extension name isn't set, return 0 so that
- * the ao2_find() search will start in the first bucket.
- */
- res = 0;
- } else {
- res = ast_str_case_hash(exten_name);
- }
-
- return res;
-}
-
-static int hint_cmp(void *obj, void *arg, int flags)
-{
- const struct ast_hint *hint = obj;
- const struct ast_exten *exten = arg;
-
- return (hint->exten == exten) ? CMP_MATCH | CMP_STOP : 0;
-}
-
-static int statecbs_cmp(void *obj, void *arg, int flags)
-{
- const struct ast_state_cb *state_cb = obj;
- ast_state_cb_type change_cb = arg;
-
- return (state_cb->change_cb == change_cb) ? CMP_MATCH | CMP_STOP : 0;
-}
-
/*!
* \internal
* \brief Clean up resources on Asterisk shutdown
*/
static void pbx_shutdown(void)
{
- STASIS_MESSAGE_TYPE_CLEANUP(hint_change_message_type);
- STASIS_MESSAGE_TYPE_CLEANUP(hint_remove_message_type);
-
- if (hints) {
- ao2_container_unregister("hints");
- ao2_ref(hints, -1);
- hints = NULL;
- }
- if (hintdevices) {
- ao2_container_unregister("hintdevices");
- ao2_ref(hintdevices, -1);
- hintdevices = NULL;
- }
- if (autohints) {
- ao2_container_unregister("autohints");
- ao2_ref(autohints, -1);
- autohints = NULL;
- }
- if (statecbs) {
- ao2_container_unregister("statecbs");
- ao2_ref(statecbs, -1);
- statecbs = NULL;
- }
if (contexts_table) {
ast_hashtab_destroy(contexts_table, NULL);
}
}
-static void print_hints_key(void *v_obj, void *where, ao2_prnt_fn *prnt)
-{
- struct ast_hint *hint = v_obj;
-
- if (!hint) {
- return;
- }
- prnt(where, "%s@%s", ast_get_extension_name(hint->exten),
- ast_get_context_name(ast_get_extension_context(hint->exten)));
-}
-
-static void print_hintdevices_key(void *v_obj, void *where, ao2_prnt_fn *prnt)
-{
- struct ast_hintdevice *hintdevice = v_obj;
-
- if (!hintdevice) {
- return;
- }
- prnt(where, "%s => %s@%s", hintdevice->hintdevice,
- ast_get_extension_name(hintdevice->hint->exten),
- ast_get_context_name(ast_get_extension_context(hintdevice->hint->exten)));
-}
-
-static void print_autohint_key(void *v_obj, void *where, ao2_prnt_fn *prnt)
-{
- struct ast_autohint *autohint = v_obj;
-
- if (!autohint) {
- return;
- }
- prnt(where, "%s", autohint->context);
-}
-
-static void print_statecbs_key(void *v_obj, void *where, ao2_prnt_fn *prnt)
-{
- struct ast_state_cb *state_cb = v_obj;
-
- if (!state_cb) {
- return;
- }
- prnt(where, "%d", state_cb->id);
-}
-
int ast_pbx_init(void)
{
- hints = ao2_container_alloc_hash(AO2_ALLOC_OPT_LOCK_MUTEX, 0,
- HASH_EXTENHINT_SIZE, hint_hash, NULL, hint_cmp);
- if (hints) {
- ao2_container_register("hints", hints, print_hints_key);
- }
- hintdevices = ao2_container_alloc_hash(AO2_ALLOC_OPT_LOCK_MUTEX, 0,
- HASH_EXTENHINT_SIZE, hintdevice_hash_cb, NULL, hintdevice_cmp_multiple);
- if (hintdevices) {
- ao2_container_register("hintdevices", hintdevices, print_hintdevices_key);
- }
- /* This is protected by the context_and_merge lock */
- autohints = ao2_container_alloc_hash(AO2_ALLOC_OPT_LOCK_NOLOCK, 0, HASH_EXTENHINT_SIZE,
- autohint_hash_cb, NULL, autohint_cmp);
- if (autohints) {
- ao2_container_register("autohints", autohints, print_autohint_key);
- }
- statecbs = ao2_container_alloc_list(AO2_ALLOC_OPT_LOCK_MUTEX, 0, NULL, statecbs_cmp);
- if (statecbs) {
- ao2_container_register("statecbs", statecbs, print_statecbs_key);
- }
-
ast_register_cleanup(pbx_shutdown);
-
- if (STASIS_MESSAGE_TYPE_INIT(hint_change_message_type) != 0) {
- return -1;
- }
- if (STASIS_MESSAGE_TYPE_INIT(hint_remove_message_type) != 0) {
- return -1;
- }
-
- return (hints && hintdevices && autohints && statecbs) ? 0 : -1;
+ return 0;
}
diff --git a/main/pbx_private.h b/main/pbx_private.h
index da1060e0e9..b2147fccde 100644
--- a/main/pbx_private.h
+++ b/main/pbx_private.h
@@ -65,6 +65,14 @@ struct ast_switch *pbx_findswitch(const char *sw);
/*! pbx_app.c functions needed by pbx.c */
const char *app_name(struct ast_app *app);
+/*! extension_state.c functions needed by pbx.c */
+void pbx_extension_state_hint_set(struct ast_exten *exten, struct ast_context *context);
+void pbx_extension_state_hint_remove(struct ast_exten *exten, struct ast_context *context);
+
+/*! extension_state_autohints.c functions needed by pbx.c */
+void pbx_extension_state_autohint_set(struct ast_context *context);
+void pbx_extension_state_autohint_remove(struct ast_context *context, unsigned int forced);
+
#define VAR_BUF_SIZE 4096
#endif /* _PBX_PRIVATE_H */
diff --git a/main/stasis.c b/main/stasis.c
index a041fe4934..b3a2bf5caa 100644
--- a/main/stasis.c
+++ b/main/stasis.c
@@ -1028,6 +1028,17 @@ struct stasis_subscription *__stasis_subscribe_pool(
return internal_stasis_subscribe(topic, callback, data, 1, 1, file, lineno, func);
}
+struct stasis_subscription *__stasis_subscribe_synchronous(
+ struct stasis_topic *topic,
+ stasis_subscription_cb callback,
+ void *data,
+ const char *file,
+ int lineno,
+ const char *func)
+{
+ return internal_stasis_subscribe(topic, callback, data, 0, 0, file, lineno, func);
+}
+
static int sub_cleanup(void *data)
{
struct stasis_subscription *sub = data;
diff --git a/pbx/pbx_config.c b/pbx/pbx_config.c
index 4b052d0cef..b612008318 100644
--- a/pbx/pbx_config.c
+++ b/pbx/pbx_config.c
@@ -1695,6 +1695,11 @@ static int pbx_load_config(const char *config_file)
const char *newpm, *ovsw;
struct ast_flags config_flags = { 0 };
char lastextension[256];
+ struct timeval begin_time;
+ double parsing_time;
+
+ begin_time = ast_tvnow();
+
cfg = ast_config_load(config_file, config_flags);
if (!cfg || cfg == CONFIG_STATUS_FILEINVALID)
return 0;
@@ -1965,6 +1970,11 @@ process_extension:
}
}
ast_config_destroy(cfg);
+
+ parsing_time = ast_tvdiff_us(ast_tvnow(), begin_time);
+ parsing_time /= 1000000.0;
+ ast_verb(5, "Time to parse %s dialplan configuration: %8.6f sec\n", config, parsing_time);
+
return 1;
}