From 5c4b482471edb8a839d754dd654dc72605caac66 Mon Sep 17 00:00:00 2001 From: Matthew Jordan Date: Thu, 1 Aug 2013 20:55:17 +0000 Subject: [PATCH] Support externally initiated parking requests; remove some dead code This patch does the following: * It adds support for externally initiated parking requests. In particular, chan_skinny has a protocol level message that initiates a call park. This patch now supports that option, as well as the protocol specific mechanisms in chan_dahdi/sig_analog and chan_mgcp. * A parking bridge features virtual table has been added that provides access to the parking functionality that the Bridging API needs. This includes requests to park an entire 'call' (with little or no additional information, thank you chan_skinny), perform a blind transfer to a parking extension, determine if an extension is a parking extension, as well as the actual "do the parking" request from the Bridging API. * Refactoring in chan_mgcp, chan_skinny, and chan_dahdi to make use of the new functions * The removal of some - but not all - dead parking code from features.c This also fixed blind transferring a multi-party bridge to a parking lot (which was implemented, but had at least one code path where using the parking features kK might not have worked) Review: https://reviewboard.asterisk.org/r/2710 (closes issue ASTERISK-22134) Reported by: Matt Jordan git-svn-id: https://origsvn.digium.com/svn/asterisk/trunk@396028 65c4cc65-6c06-0410-ace0-fbb531ad65f3 --- channels/chan_dahdi.c | 28 +- channels/chan_iax2.c | 2 +- channels/chan_mgcp.c | 20 +- channels/chan_skinny.c | 75 +- channels/sig_analog.c | 25 +- include/asterisk/features.h | 44 - include/asterisk/parking.h | 150 ++- main/bridge.c | 40 +- main/bridge_channel.c | 17 +- main/features.c | 1342 ------------------------- main/parking.c | 88 +- res/parking/parking_bridge_features.c | 220 ++-- res/res_parking.c | 9 +- 13 files changed, 430 insertions(+), 1630 deletions(-) diff --git a/channels/chan_dahdi.c b/channels/chan_dahdi.c index d18e94d144..a31c8e7bcc 100644 --- a/channels/chan_dahdi.c +++ b/channels/chan_dahdi.c @@ -126,6 +126,7 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$") #include "asterisk/features_config.h" #include "asterisk/bridge.h" #include "asterisk/stasis_channels.h" +#include "asterisk/parking.h" #include "chan_dahdi.h" #include "dahdi/bridge_native_dahdi.h" @@ -9230,6 +9231,10 @@ static void *analog_ss_thread(void *data) int idx; struct ast_format tmpfmt; RAII_VAR(struct ast_features_pickup_config *, pickup_cfg, NULL, ao2_cleanup); + RAII_VAR(struct ast_parking_bridge_feature_fn_table *, parking_provider, + ast_parking_get_bridge_features(), + ao2_cleanup); + int is_exten_parking; const char *pickupexten; ast_mutex_lock(&ss_thread_lock); @@ -9560,11 +9565,13 @@ static void *analog_ss_thread(void *data) exten[len++]=res; exten[len] = '\0'; } - if (!ast_ignore_pattern(ast_channel_context(chan), exten)) + if (!ast_ignore_pattern(ast_channel_context(chan), exten)) { tone_zone_play_tone(p->subs[idx].dfd, -1); - else + } else { tone_zone_play_tone(p->subs[idx].dfd, DAHDI_TONE_DIALTONE); - if (ast_exists_extension(chan, ast_channel_context(chan), exten, 1, p->cid_num) && !ast_parking_ext_valid(exten, chan, ast_channel_context(chan))) { + } + is_exten_parking = (parking_provider ? parking_provider->parking_is_exten_park(ast_channel_context(chan), exten) : 0); + if (ast_exists_extension(chan, ast_channel_context(chan), exten, 1, p->cid_num) && !is_exten_parking) { if (!res || !ast_matchmore_extension(chan, ast_channel_context(chan), exten, 1, p->cid_num)) { if (getforward) { /* Record this as the forwarding extension */ @@ -9700,14 +9707,17 @@ static void *analog_ss_thread(void *data) getforward = 0; memset(exten, 0, sizeof(exten)); len = 0; - } else if ((p->transfer || p->canpark) && ast_parking_ext_valid(exten, chan, ast_channel_context(chan)) && - p->subs[SUB_THREEWAY].owner && - ast_bridged_channel(p->subs[SUB_THREEWAY].owner)) { + } else if ((p->transfer || p->canpark) && is_exten_parking && + p->subs[SUB_THREEWAY].owner) { + RAII_VAR(struct ast_bridge_channel *, bridge_channel, NULL, ao2_cleanup); /* This is a three way call, the main call being a real channel, and we're parking the first call. */ - ast_masq_park_call_exten(ast_bridged_channel(p->subs[SUB_THREEWAY].owner), - chan, exten, ast_channel_context(chan), 0, NULL); - ast_verb(3, "Parking call to '%s'\n", ast_channel_name(chan)); + ast_channel_lock(chan); + bridge_channel = ast_channel_get_bridge_channel(chan); + ast_channel_unlock(chan); + if (bridge_channel && !parking_provider->parking_blind_transfer_park(bridge_channel, ast_channel_context(chan), exten)) { + ast_verb(3, "Parking call to '%s'\n", ast_channel_name(chan)); + } break; } else if (p->hidecallerid && !strcmp(exten, "*82")) { ast_verb(3, "Enabling Caller*ID on %s\n", ast_channel_name(chan)); diff --git a/channels/chan_iax2.c b/channels/chan_iax2.c index 26ca36d75a..1ae5505e3b 100644 --- a/channels/chan_iax2.c +++ b/channels/chan_iax2.c @@ -9202,7 +9202,7 @@ static void dp_lookup(int callno, const char *context, const char *callednum, co memset(&ied1, 0, sizeof(ied1)); mm = ast_matchmore_extension(NULL, context, callednum, 1, callerid); /* Must be started */ - if (ast_parking_ext_valid(callednum, NULL, context) || ast_exists_extension(NULL, context, callednum, 1, callerid)) { + if (ast_exists_extension(NULL, context, callednum, 1, callerid)) { dpstatus = IAX_DPSTATUS_EXISTS; } else if (ast_canmatch_extension(NULL, context, callednum, 1, callerid)) { dpstatus = IAX_DPSTATUS_CANEXIST; diff --git a/channels/chan_mgcp.c b/channels/chan_mgcp.c index 79208daa0d..233d2c3ff4 100644 --- a/channels/chan_mgcp.c +++ b/channels/chan_mgcp.c @@ -84,6 +84,7 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$") #include "asterisk/stasis.h" #include "asterisk/bridge.h" #include "asterisk/features_config.h" +#include "asterisk/parking.h" /* * Define to work around buggy dlink MGCP phone firmware which @@ -2980,6 +2981,9 @@ static void *mgcp_ss(void *data) int getforward = 0; int loop_pause = 100; RAII_VAR(struct ast_features_pickup_config *, pickup_cfg, NULL, ao2_cleanup); + RAII_VAR(struct ast_parking_bridge_feature_fn_table *, parking_provider, + ast_parking_get_bridge_features(), + ao2_cleanup); const char *pickupexten; len = strlen(p->dtmf_buf); @@ -3148,13 +3152,17 @@ static void *mgcp_ss(void *data) getforward = 0; memset(p->dtmf_buf, 0, sizeof(p->dtmf_buf)); len = 0; - } else if (ast_parking_ext_valid(p->dtmf_buf, chan, ast_channel_context(chan)) && - sub->next->owner && ast_bridged_channel(sub->next->owner)) { + } else if (parking_provider && parking_provider->parking_is_exten_park(ast_channel_context(chan), p->dtmf_buf) && + sub->next->owner) { + RAII_VAR(struct ast_bridge_channel *, bridge_channel, NULL, ao2_cleanup); /* This is a three way call, the main call being a real channel, - and we're parking the first call. */ - ast_masq_park_call_exten(ast_bridged_channel(sub->next->owner), chan, - p->dtmf_buf, ast_channel_context(chan), 0, NULL); - ast_verb(3, "Parking call to '%s'\n", ast_channel_name(chan)); + and we're parking the first call. */ + ast_channel_lock(chan); + bridge_channel = ast_channel_get_bridge_channel(chan); + ast_channel_unlock(chan); + if (bridge_channel && !parking_provider->parking_blind_transfer_park(bridge_channel, ast_channel_context(chan), p->dtmf_buf)) { + ast_verb(3, "Parking call to '%s'\n", ast_channel_name(chan)); + } break; } else if (!ast_strlen_zero(p->lastcallerid) && !strcmp(p->dtmf_buf, "*60")) { ast_verb(3, "Blacklisting number %s\n", p->lastcallerid); diff --git a/channels/chan_skinny.c b/channels/chan_skinny.c index c7485470a6..c974515773 100644 --- a/channels/chan_skinny.c +++ b/channels/chan_skinny.c @@ -82,6 +82,7 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$") #include "asterisk/linkedlists.h" #include "asterisk/stasis_endpoints.h" #include "asterisk/bridge.h" +#include "asterisk/parking.h" /*** DOCUMENTATION @@ -6404,24 +6405,37 @@ static int handle_stimulus_message(struct skinny_req *req, struct skinnysession break; case STIMULUS_CALLPARK: { - int extout; + char extout[AST_MAX_EXTENSION]; char message[32]; - + RAII_VAR(struct ast_parking_bridge_feature_fn_table *, parking_provider, + ast_parking_get_bridge_features(), + ao2_cleanup); + RAII_VAR(struct ast_bridge_channel *, bridge_channel, NULL, ao2_cleanup); SKINNY_DEBUG(DEBUG_PACKET, 3, "Received STIMULUS_CALLPARK from %s, inst %d, callref %d\n", d->name, instance, callreference); - if ((sub && sub->owner) && (ast_channel_state(sub->owner) == AST_STATE_UP)){ + if (!parking_provider) { + transmit_displaynotify(d, "Call Park not available", 10); + break; + } + + if ((sub && sub->owner) && (ast_channel_state(sub->owner) == AST_STATE_UP)) { c = sub->owner; - if (ast_bridged_channel(c)) { - if (!ast_masq_park_call(ast_bridged_channel(c), c, 0, &extout)) { - snprintf(message, sizeof(message), "Call Parked at: %d", extout); - transmit_displaynotify(d, message, 10); - } else { - transmit_displaynotify(d, "Call Park failed", 10); - } - } else { - transmit_displaynotify(d, "Call Park not available", 10); + ast_channel_lock(c); + bridge_channel = ast_channel_get_bridge_channel(c); + ast_channel_unlock(c); + + if (!bridge_channel) { + transmit_displaynotify(d, "Call Park failed", 10); + break; } + + if (!parking_provider->parking_park_call(bridge_channel, extout, sizeof(extout))) { + snprintf(message, sizeof(message), "Call Parked at: %s", extout); + transmit_displaynotify(d, message, 10); + break; + } + transmit_displaynotify(d, "Call Park failed", 10); } else { transmit_displaynotify(d, "Call Park not available", 10); } @@ -7141,24 +7155,37 @@ static int handle_soft_key_event_message(struct skinny_req *req, struct skinnyse break; case SOFTKEY_PARK: { - int extout; + char extout[AST_MAX_EXTENSION]; char message[32]; - + RAII_VAR(struct ast_parking_bridge_feature_fn_table *, parking_provider, + ast_parking_get_bridge_features(), + ao2_cleanup); + RAII_VAR(struct ast_bridge_channel *, bridge_channel, NULL, ao2_cleanup); SKINNY_DEBUG(DEBUG_PACKET, 3, "Received SOFTKEY_PARK from %s, inst %d, callref %d\n", d->name, instance, callreference); - if ((sub && sub->owner) && (ast_channel_state(sub->owner) == AST_STATE_UP)){ + if (!parking_provider) { + transmit_displaynotify(d, "Call Park not available", 10); + break; + } + + if ((sub && sub->owner) && (ast_channel_state(sub->owner) == AST_STATE_UP)) { c = sub->owner; - if (ast_bridged_channel(c)) { - if (!ast_masq_park_call(ast_bridged_channel(c), c, 0, &extout)) { - snprintf(message, sizeof(message), "Call Parked at: %d", extout); - transmit_displaynotify(d, message, 10); - } else { - transmit_displaynotify(d, "Call Park failed", 10); - } - } else { - transmit_displaynotify(d, "Call Park not available", 10); + ast_channel_lock(c); + bridge_channel = ast_channel_get_bridge_channel(c); + ast_channel_unlock(c); + + if (!bridge_channel) { + transmit_displaynotify(d, "Call Park failed", 10); + break; } + + if (!parking_provider->parking_park_call(bridge_channel, extout, sizeof(extout))) { + snprintf(message, sizeof(message), "Call Parked at: %s", extout); + transmit_displaynotify(d, message, 10); + break; + } + transmit_displaynotify(d, "Call Park failed", 10); } else { transmit_displaynotify(d, "Call Park not available", 10); } diff --git a/channels/sig_analog.c b/channels/sig_analog.c index 9effae1f15..4b5ae88871 100644 --- a/channels/sig_analog.c +++ b/channels/sig_analog.c @@ -44,6 +44,7 @@ #include "asterisk/causes.h" #include "asterisk/features_config.h" #include "asterisk/bridge.h" +#include "asterisk/parking.h" #include "sig_analog.h" @@ -1713,7 +1714,11 @@ static void *__analog_ss_thread(void *data) int idx; struct ast_callid *callid; RAII_VAR(struct ast_features_pickup_config *, pickup_cfg, NULL, ao2_cleanup); + RAII_VAR(struct ast_parking_bridge_feature_fn_table *, parking_provider, + ast_parking_get_bridge_features(), + ao2_cleanup); const char *pickupexten; + int is_exten_parking; analog_increase_ss_count(); @@ -2094,7 +2099,8 @@ static void *__analog_ss_thread(void *data) } else { analog_play_tone(p, idx, ANALOG_TONE_DIALTONE); } - if (ast_exists_extension(chan, ast_channel_context(chan), exten, 1, p->cid_num) && !ast_parking_ext_valid(exten, chan, ast_channel_context(chan))) { + is_exten_parking = (parking_provider ? parking_provider->parking_is_exten_park(ast_channel_context(chan), exten) : 0); + if (ast_exists_extension(chan, ast_channel_context(chan), exten, 1, p->cid_num) && !is_exten_parking) { if (!res || !ast_matchmore_extension(chan, ast_channel_context(chan), exten, 1, p->cid_num)) { if (getforward) { /* Record this as the forwarding extension */ @@ -2238,15 +2244,18 @@ static void *__analog_ss_thread(void *data) getforward = 0; memset(exten, 0, sizeof(exten)); len = 0; - } else if ((p->transfer || p->canpark) && ast_parking_ext_valid(exten, chan, ast_channel_context(chan)) && - p->subs[ANALOG_SUB_THREEWAY].owner && - ast_bridged_channel(p->subs[ANALOG_SUB_THREEWAY].owner)) { + } else if ((p->transfer || p->canpark) && is_exten_parking && + p->subs[ANALOG_SUB_THREEWAY].owner) { + struct ast_bridge_channel *bridge_channel; /* This is a three way call, the main call being a real channel, and we're parking the first call. */ - ast_masq_park_call_exten( - ast_bridged_channel(p->subs[ANALOG_SUB_THREEWAY].owner), chan, exten, - ast_channel_context(chan), 0, NULL); - ast_verb(3, "Parking call to '%s'\n", ast_channel_name(chan)); + ast_channel_lock(chan); + bridge_channel = ast_channel_get_bridge_channel(chan); + ast_channel_unlock(chan); + if (bridge_channel && !parking_provider->parking_blind_transfer_park(bridge_channel, ast_channel_context(chan), exten)) { + ast_verb(3, "Parking call to '%s'\n", ast_channel_name(chan)); + } + ao2_ref(bridge_channel, -1); break; } else if (!ast_strlen_zero(p->lastcid_num) && !strcmp(exten, "*60")) { ast_verb(3, "Blacklisting number %s\n", p->lastcid_num); diff --git a/include/asterisk/features.h b/include/asterisk/features.h index b1400ee397..43edca07a6 100644 --- a/include/asterisk/features.h +++ b/include/asterisk/features.h @@ -63,50 +63,6 @@ enum { AST_FEATURE_FLAG_BYBOTH = (3 << 3), }; -/*! - * \brief Park a call via a masqueraded channel - * - * \param park_me Channel to be parked. - * \param parker Channel parking the call. - * \param timeout is a timeout in milliseconds - * \param extout is a parameter to an int that will hold the parked location, or NULL if you want. - * - * \details - * Masquerade the park_me channel into a new, empty channel which is then parked. - * - * \note Use ast_masq_park_call_exten() instead. - * - * \retval 0 on success. - * \retval -1 on failure. - */ -int ast_masq_park_call(struct ast_channel *park_me, struct ast_channel *parker, int timeout, int *extout); - -/*! - * \brief Park a call via a masqueraded channel - * \since 1.8.9 - * - * \param park_me Channel to be parked. - * \param parker Channel parking the call. - * \param park_exten Parking lot access extension - * \param park_context Parking lot context - * \param timeout is a timeout in milliseconds - * \param extout is a parameter to an int that will hold the parked location, or NULL if you want. - * - * \details - * Masquerade the park_me channel into a new, empty channel which is then parked. - * - * \retval 0 on success. - * \retval -1 on failure. - */ -int ast_masq_park_call_exten(struct ast_channel *park_me, struct ast_channel *parker, const char *park_exten, const char *park_context, int timeout, int *extout); - -/*! - * \brief Determine if parking extension exists in a given context - * \retval 0 if extension does not exist - * \retval 1 if extension does exist -*/ -int ast_parking_ext_valid(const char *exten_str, struct ast_channel *chan, const char *context); - /*! \brief Bridge a call, optionally allowing redirection */ int ast_bridge_call(struct ast_channel *chan, struct ast_channel *peer,struct ast_bridge_config *config); diff --git a/include/asterisk/parking.h b/include/asterisk/parking.h index 4a721200ad..a835d104a3 100644 --- a/include/asterisk/parking.h +++ b/include/asterisk/parking.h @@ -25,6 +25,9 @@ #include "asterisk/stringfields.h" +/*! + * \brief The default parking application that Asterisk expects. + */ #define PARK_APPLICATION "Park" /*! @@ -79,6 +82,10 @@ struct ast_parked_call_payload *ast_parked_call_payload_create(enum ast_parked_c struct ast_channel_snapshot *retriever_snapshot, const char *parkinglot, unsigned int parkingspace, unsigned long int timeout, unsigned long int duration); +/*! \addtogroup StasisTopicsAndMessages + * @{ + */ + /*! * \brief accessor for the parking stasis topic * \since 12 @@ -97,75 +104,110 @@ struct stasis_topic *ast_parking_topic(void); */ struct stasis_message_type *ast_parked_call_type(void); +/*! @} */ + +#define PARKING_MODULE_VERSION 1 + /*! - * \brief invoke an installable park callback to asynchronously park a bridge_channel in a bridge - * \since 12 - * - * \param bridge_channel the bridge channel that initiated parking - * \parkee_uuid channel id of the channel being parked - * \parker_uuid channel id of the channel that initiated parking - * \param app_data string of application data that might be applied to parking + * \brief A function table providing parking functionality to the \ref AstBridging + * Bridging API and other consumers */ -void ast_bridge_channel_park(struct ast_bridge_channel *bridge_channel, - const char *parkee_uuid, - const char *parker_uuid, - const char *app_data); +struct ast_parking_bridge_feature_fn_table { -typedef int (*ast_park_blind_xfer_fn)(struct ast_bridge_channel *parker, struct ast_exten *park_exten); + /*! + * \brief The version of this function table. If the ABI for this table + * changes, the module version (/ref PARKING_MODULE_VERSION) should be + * incremented. + */ + unsigned int module_version; + + /*! + * \brief The name of the module that provides this parking functionality + */ + const char *module_name; + + /*! + * \brief Determine if the context/exten is a "parking" extension + * + * \retval 0 if the extension is not a parking extension + * \retval 1 if the extension is a parking extension + */ + int (* parking_is_exten_park)(const char *context, const char *exten); + + /*! + * \brief Park the bridge and/or callers that this channel is in + * + * \param parker The bridge_channel parking the bridge + * \param exten Optional. The extension the channel or bridge was parked at if the + * call succeeds. + * \param length Optional. If \c exten is specified, the size of the buffer. + * + * \note This is safe to be called outside of the \ref AstBridging Bridging API. + * + * \retval 0 on success + * \retval non-zero on error + */ + int (* parking_park_call)(struct ast_bridge_channel *parker, char *exten, size_t length); + + /*! + * \brief Perform a blind transfer to a parking extension. + * + * \param parker The \ref bridge_channel object that is initiating the parking + * \param context The context to blind transfer to + * \param exten The extension to blind transfer to + * + * \note If the bridge \ref parker is in has more than one other occupant, the entire + * bridge will be parked using a Local channel + * + * \note This is safe to be called outside of the \ref AstBridging Bridging API. + * + * \retval 0 on success + * \retval non-zero on error + */ + int (* parking_blind_transfer_park)(struct ast_bridge_channel *parker, const char *context, const char *exten); + + /*! + * \brief Perform a direct park on a channel in a bridge. + * + * \param parkee The channel in the bridge to be parked. + * \param parkee_uuid The UUID of the channel being packed. + * \param parker_uuid The UUID of the channel performing the park. + * \param app_data Data to pass to the Park application + * + * \note This must be called within the context of the \ref AstBridging Bridging API. + * External entities should not call this method directly, but should instead use + * the direct call parking method or the blind transfer method. + * + * \retval 0 on success + * \retval non-zero on error + */ + int (* parking_park_bridge_channel)(struct ast_bridge_channel *parkee, const char *parkee_uuid, const char *parker_uuid, const char *app_data); +}; /*! - * \brief install a callback for handling blind transfers to a parking extension - * \since 12 + * \brief Obtain the current parking provider * - * \param parking_func Function to use for transfers to 'Park' applications + * \retval NULL if no provider exists + * \retval an ao2 ref counted object of the existing provider's function table */ -void ast_install_park_blind_xfer_func(ast_park_blind_xfer_fn park_blind_xfer_func); +struct ast_parking_bridge_feature_fn_table *ast_parking_get_bridge_features(void); /*! - * \brief uninstall a callback for handling blind transfers to a parking extension - * \since 12 - */ -void ast_uninstall_park_blind_xfer_func(void); - -/*! - * \brief use the installed park blind xfer func - * \since 12 + * \brief Register a parking provider * - * \param parker Bridge channel initiating the park - * \param park_exten Exten to blind transfer part to. + * \param fn_table The \ref ast_parking_bridge_feature_fn_table to register * * \retval 0 on success - * \retval -1 on failure + * \retval -1 on error */ -int ast_park_blind_xfer(struct ast_bridge_channel *parker, struct ast_exten *park_exten); - -typedef void (*ast_bridge_channel_park_fn)(struct ast_bridge_channel *parkee, const char *parkee_uuid, - const char *parker_uuid, const char *app_data); +int ast_parking_register_bridge_features(struct ast_parking_bridge_feature_fn_table *fn_table); /*! - * \brief Install a function for ast_bridge_channel_park - * \since 12 + * \brief Unregister the current parking provider * - * \param bridge_channel_park_func function callback to use for ast_bridge_channel_park - */ -void ast_install_bridge_channel_park_func(ast_bridge_channel_park_fn bridge_channel_park_func); - -/*! - * \brief Uninstall the ast_bridge_channel_park function callback - * \since 12 - */ -void ast_uninstall_bridge_channel_park_func(void); - - -/*! - * \brief Determines whether a certain extension is a park application extension or not. - * \since 12 + * \param The module name of the provider to unregister * - * \param exten_str string representation of the extension sought - * \param chan channel the extension is sought for - * \param context context the extension is sought from - * - * \retval pointer to the extension if the extension is a park extension - * \retval NULL if the extension was not a park extension + * \retval 0 if the parking provider \c module_name was unregsistered + * \retval -1 on error */ -struct ast_exten *ast_get_parking_exten(const char *exten_str, struct ast_channel *chan, const char *context); +int ast_parking_unregister_bridge_features(const char *module_name); diff --git a/main/bridge.c b/main/bridge.c index 9e9e65c172..2e2c87bc60 100644 --- a/main/bridge.c +++ b/main/bridge.c @@ -3781,32 +3781,31 @@ static struct ast_channel *get_transferee(struct ao2_container *channels, struct return transferee; } -enum try_parking_result { - PARKING_SUCCESS, - PARKING_FAILURE, - PARKING_NOT_APPLICABLE, -}; - -static enum try_parking_result try_parking(struct ast_channel *transferer, const char *exten, const char *context) +static enum ast_transfer_result try_parking(struct ast_channel *transferer, const char *context, const char *exten) { RAII_VAR(struct ast_bridge_channel *, transferer_bridge_channel, NULL, ao2_cleanup); - struct ast_exten *parking_exten; + RAII_VAR(struct ast_parking_bridge_feature_fn_table *, parking_provider, + ast_parking_get_bridge_features(), + ao2_cleanup); + + if (!parking_provider) { + return AST_BRIDGE_TRANSFER_FAIL; + } ast_channel_lock(transferer); transferer_bridge_channel = ast_channel_get_bridge_channel(transferer); ast_channel_unlock(transferer); if (!transferer_bridge_channel) { - return PARKING_FAILURE; + return AST_BRIDGE_TRANSFER_FAIL; } - parking_exten = ast_get_parking_exten(exten, NULL, context); - if (parking_exten) { - return ast_park_blind_xfer(transferer_bridge_channel, parking_exten) == 0 ? - PARKING_SUCCESS : PARKING_FAILURE; + if (parking_provider->parking_blind_transfer_park(transferer_bridge_channel, + context, exten)) { + return AST_BRIDGE_TRANSFER_FAIL; } - return PARKING_NOT_APPLICABLE; + return AST_BRIDGE_TRANSFER_SUCCESS; } /*! @@ -3883,7 +3882,6 @@ enum ast_transfer_result ast_bridge_transfer_blind(int is_external, RAII_VAR(struct ast_channel *, transferee, NULL, ast_channel_cleanup); int do_bridge_transfer; int transfer_prohibited; - enum try_parking_result parking_result; enum ast_transfer_result transfer_result; bridge = acquire_bridge(transferer); @@ -3902,17 +3900,9 @@ enum ast_transfer_result ast_bridge_transfer_blind(int is_external, /* Take off hold if they are on hold. */ ast_bridge_channel_write_unhold(bridge_channel); - parking_result = try_parking(transferer, exten, context); - switch (parking_result) { - case PARKING_SUCCESS: - transfer_result = AST_BRIDGE_TRANSFER_SUCCESS; + transfer_result = try_parking(transferer, context, exten); + if (transfer_result == AST_BRIDGE_TRANSFER_SUCCESS) { goto publish; - case PARKING_FAILURE: - transfer_result = AST_BRIDGE_TRANSFER_FAIL; - goto publish; - case PARKING_NOT_APPLICABLE: - default: - break; } { diff --git a/main/bridge_channel.c b/main/bridge_channel.c index f887ef5767..f0163ff2ec 100644 --- a/main/bridge_channel.c +++ b/main/bridge_channel.c @@ -757,9 +757,22 @@ struct bridge_park { */ static void bridge_channel_park(struct ast_bridge_channel *bridge_channel, struct bridge_park *payload) { - ast_bridge_channel_park(bridge_channel, payload->parkee_uuid, + RAII_VAR(struct ast_parking_bridge_feature_fn_table *, parking_provider, + ast_parking_get_bridge_features(), + ao2_cleanup); + + if (!parking_provider) { + ast_log(AST_LOG_WARNING, "Unable to park %s: No parking provider loaded!\n", + ast_channel_name(bridge_channel->chan)); + return; + } + + if (parking_provider->parking_park_bridge_channel(bridge_channel, payload->parkee_uuid, &payload->parkee_uuid[payload->parker_uuid_offset], - payload->app_data_offset ? &payload->parkee_uuid[payload->app_data_offset] : NULL); + payload->app_data_offset ? &payload->parkee_uuid[payload->app_data_offset] : NULL)) { + ast_log(AST_LOG_WARNING, "Error occurred while parking %s\n", + ast_channel_name(bridge_channel->chan)); + } } /*! diff --git a/main/features.c b/main/features.c index 9490116b77..7162d4e550 100644 --- a/main/features.c +++ b/main/features.c @@ -470,20 +470,11 @@ struct ast_parkinglot { /*! \brief The configured parking lots container. Always at least one - the default parking lot */ static struct ao2_container *parkinglots; -/*! - * \brief Default parking lot. - * \note Holds a parkinglot reference. - * \note Will not be NULL while running. - */ -static struct ast_parkinglot *default_parkinglot; - /*! Force a config reload to reload regardless of config file timestamp. */ #ifdef TEST_FRAMEWORK static int force_reload_load; #endif -static int parkeddynamic = 0; /*!< Enable creation of parkinglots dynamically */ - /*! * \brief Context for parking dialback to parker. * \note The need for the context is a KLUDGE. @@ -497,8 +488,6 @@ static char parking_con_dial[] = "park-dial"; /*! Ensure that features.conf reloads on one thread at a time. */ AST_MUTEX_DEFINE_STATIC(features_reload_lock); -static int adsipark; - static char *registrar = "features"; /*!< Registrar for operations */ /*! PARK_APP_NAME application arguments */ @@ -512,9 +501,6 @@ AST_DEFINE_APP_ARGS_TYPE(park_app_args, AST_APP_ARG(dummy); /*!< Place to put any remaining args string. */ ); -/* module and CLI command definitions */ -static const char *parkcall = "Park"; - static pthread_t parking_thread; struct ast_dial_features { /*! Channel's feature flags. */ @@ -670,50 +656,7 @@ static int add_features_datastore(struct ast_channel *chan, const struct ast_fla } /* Forward declarations */ -static struct ast_parkinglot *parkinglot_addref(struct ast_parkinglot *parkinglot); static void parkinglot_unref(struct ast_parkinglot *parkinglot); -static struct ast_parkinglot *find_parkinglot(const char *name); -static struct ast_parkinglot *create_parkinglot(const char *name); -static struct ast_parkinglot *copy_parkinglot(const char *name, const struct ast_parkinglot *parkinglot); -static int parkinglot_activate(struct ast_parkinglot *parkinglot); -static int play_message_on_chan(struct ast_channel *play_to, struct ast_channel *other, const char *msg, const char *audiofile); - -/*! - * \internal - * \brief Get the parking extension if it exists. - * - * \param exten_str Parking extension to see if exists. - * \param chan Channel to autoservice while looking for exten. (Could be NULL) - * \param context Parking context to look in for exten. - * - * \retval exten on success. - * \retval NULL on error or exten does not exist. - */ -static struct ast_exten *get_parking_exten(const char *exten_str, struct ast_channel *chan, const char *context) -{ - struct ast_exten *exten; - struct pbx_find_info q = { .stacklen = 0 }; /* the rest is reset in pbx_find_extension */ - const char *app_at_exten; - - ast_debug(4, "Checking if %s@%s is a parking exten\n", exten_str, context); - exten = pbx_find_extension(chan, NULL, &q, context, exten_str, 1, NULL, NULL, - E_MATCH); - if (!exten) { - return NULL; - } - - app_at_exten = ast_get_extension_app(exten); - if (!app_at_exten || strcasecmp(parkcall, app_at_exten)) { - return NULL; - } - - return exten; -} - -int ast_parking_ext_valid(const char *exten_str, struct ast_channel *chan, const char *context) -{ - return get_parking_exten(exten_str, chan, context) ? 1 : 0; -} struct ast_bridge_thread_obj { @@ -755,46 +698,6 @@ static const struct ast_datastore_info channel_app_data_datastore = { .destroy = ast_free_ptr, }; -/*! - * \brief Announce call parking by ADSI - * \param chan . - * \param parkingexten . - * Create message to show for ADSI, display message. - * \retval 0 on success. - * \retval -1 on failure. - */ -static int adsi_announce_park(struct ast_channel *chan, char *parkingexten) -{ - int res; - int justify[5] = {ADSI_JUST_CENT, ADSI_JUST_CENT, ADSI_JUST_CENT, ADSI_JUST_CENT}; - char tmp[256]; - char *message[5] = {NULL, NULL, NULL, NULL, NULL}; - - snprintf(tmp, sizeof(tmp), "Parked on %s", parkingexten); - message[0] = tmp; - res = ast_adsi_load_session(chan, NULL, 0, 1); - if (res == -1) - return res; - return ast_adsi_print(chan, message, justify, 1); -} - -/*! - * \brief Find parking lot name from channel - * \note Channel needs to be locked while the returned string is in use. - */ -static const char *findparkinglotname(struct ast_channel *chan) -{ - const char *name; - - /* The channel variable overrides everything */ - name = pbx_builtin_getvar_helper(chan, "PARKINGLOT"); - if (!name && !ast_strlen_zero(ast_channel_parkinglot(chan))) { - /* Use the channel's parking lot. */ - name = ast_channel_parkinglot(chan); - } - return name; -} - /*! \brief Notify metermaids that we've changed an extension */ static void notify_metermaids(const char *exten, char *context, enum ast_device_state state) { @@ -855,705 +758,6 @@ struct ast_park_call_args { struct ast_parkinglot *parkinglot; }; -/*! - * \internal - * \brief Create a dynamic parking lot. - * - * \param name Dynamic parking lot name to create. - * \param chan Channel to get dynamic parking lot parameters. - * - * \retval parkinglot on success. - * \retval NULL on error. - */ -static struct ast_parkinglot *create_dynamic_parkinglot(const char *name, struct ast_channel *chan) -{ - const char *dyn_context; - const char *dyn_exten; - const char *dyn_range; - const char *template_name; - struct ast_parkinglot *template_parkinglot = NULL; - struct ast_parkinglot *parkinglot; - int dyn_start; - int dyn_end; - - ast_channel_lock(chan); - template_name = ast_strdupa(S_OR(pbx_builtin_getvar_helper(chan, "PARKINGDYNAMIC"), "")); - dyn_context = ast_strdupa(S_OR(pbx_builtin_getvar_helper(chan, "PARKINGDYNCONTEXT"), "")); - dyn_exten = ast_strdupa(S_OR(pbx_builtin_getvar_helper(chan, "PARKINGDYNEXTEN"), "")); - dyn_range = ast_strdupa(S_OR(pbx_builtin_getvar_helper(chan, "PARKINGDYNPOS"), "")); - ast_channel_unlock(chan); - - if (!ast_strlen_zero(template_name)) { - template_parkinglot = find_parkinglot(template_name); - if (!template_parkinglot) { - ast_debug(1, "PARKINGDYNAMIC lot %s does not exist.\n", - template_name); - } else if (template_parkinglot->cfg.is_invalid) { - ast_debug(1, "PARKINGDYNAMIC lot %s has invalid config.\n", - template_name); - parkinglot_unref(template_parkinglot); - template_parkinglot = NULL; - } - } - if (!template_parkinglot) { - template_parkinglot = parkinglot_addref(default_parkinglot); - ast_debug(1, "Using default parking lot for template\n"); - } - - parkinglot = copy_parkinglot(name, template_parkinglot); - if (!parkinglot) { - ast_log(LOG_ERROR, "Could not build dynamic parking lot!\n"); - } else { - /* Configure the dynamic parking lot. */ - if (!ast_strlen_zero(dyn_context)) { - ast_copy_string(parkinglot->cfg.parking_con, dyn_context, - sizeof(parkinglot->cfg.parking_con)); - } - if (!ast_strlen_zero(dyn_exten)) { - ast_copy_string(parkinglot->cfg.parkext, dyn_exten, - sizeof(parkinglot->cfg.parkext)); - } - if (!ast_strlen_zero(dyn_range)) { - if (sscanf(dyn_range, "%30d-%30d", &dyn_start, &dyn_end) != 2) { - ast_log(LOG_WARNING, - "Format for parking positions is a-b, where a and b are numbers\n"); - } else if (dyn_end < dyn_start || dyn_start <= 0 || dyn_end <= 0) { - ast_log(LOG_WARNING, - "Format for parking positions is a-b, where a <= b\n"); - } else { - parkinglot->cfg.parking_start = dyn_start; - parkinglot->cfg.parking_stop = dyn_end; - } - } - - /* - * Sanity check for dynamic parking lot configuration. - * - * XXX It may be desirable to instead check if the dynamic - * parking lot overlaps any existing lots like what is done for - * a reload. - */ - if (!strcmp(parkinglot->cfg.parking_con, template_parkinglot->cfg.parking_con)) { - if (!strcmp(parkinglot->cfg.parkext, template_parkinglot->cfg.parkext) - && parkinglot->cfg.parkext_exclusive) { - ast_log(LOG_WARNING, - "Parking lot '%s' conflicts with template parking lot '%s'!\n" - "Change either PARKINGDYNCONTEXT or PARKINGDYNEXTEN.\n", - parkinglot->name, template_parkinglot->name); - } - if ((template_parkinglot->cfg.parking_start <= parkinglot->cfg.parking_start - && parkinglot->cfg.parking_start <= template_parkinglot->cfg.parking_stop) - || (template_parkinglot->cfg.parking_start <= parkinglot->cfg.parking_stop - && parkinglot->cfg.parking_stop <= template_parkinglot->cfg.parking_stop) - || (parkinglot->cfg.parking_start < template_parkinglot->cfg.parking_start - && template_parkinglot->cfg.parking_stop < parkinglot->cfg.parking_stop)) { - ast_log(LOG_WARNING, - "Parking lot '%s' parking spaces overlap template parking lot '%s'!\n" - "Change PARKINGDYNPOS.\n", - parkinglot->name, template_parkinglot->name); - } - } - - parkinglot_activate(parkinglot); - ao2_link(parkinglots, parkinglot); - } - parkinglot_unref(template_parkinglot); - - return parkinglot; -} - -/*! - * \internal - * \brief Abort parking a call that has not completed parking yet. - * - * \param pu Parked user item to clean up. - * - * \note The parking lot parkings list is locked on entry. - * - * \return Nothing - */ -static void park_space_abort(struct parkeduser *pu) -{ - struct ast_parkinglot *parkinglot; - - parkinglot = pu->parkinglot; - - /* Put back the parking space just allocated. */ - --parkinglot->next_parking_space; - - AST_LIST_REMOVE(&parkinglot->parkings, pu, list); - - AST_LIST_UNLOCK(&parkinglot->parkings); - parkinglot_unref(parkinglot); - ast_free(pu); -} - -/*! - * \internal - * \brief Reserve a parking space in a parking lot for a call being parked. - * - * \param park_me Channel being parked. - * \param parker Channel parking the call. - * \param args Optional additional parking options when parking a call. - * - * \return Parked call descriptor or NULL if failed. - * \note The parking lot list is locked if successful. - */ -static struct parkeduser *park_space_reserve(struct ast_channel *park_me, struct ast_channel *parker, struct ast_park_call_args *args) -{ - struct parkeduser *pu; - int i; - int parking_space = -1; - const char *parkinglotname; - const char *parkingexten; - struct parkeduser *cur; - struct ast_parkinglot *parkinglot = NULL; - - if (args->parkinglot) { - parkinglot = parkinglot_addref(args->parkinglot); - parkinglotname = parkinglot->name; - } else { - if (parker) { - parkinglotname = findparkinglotname(parker); - } else { /* parker was NULL, check park_me (ParkAndAnnounce / res_agi) */ - parkinglotname = findparkinglotname(park_me); - } - if (!ast_strlen_zero(parkinglotname)) { - parkinglot = find_parkinglot(parkinglotname); - } else { - /* Parking lot is not specified, so use the default parking lot. */ - ast_debug(4, "This could be an indication channel driver needs updating, using default lot.\n"); - parkinglot = parkinglot_addref(default_parkinglot); - } - } - - /* Dynamically create parkinglot */ - if (!parkinglot && parkeddynamic && !ast_strlen_zero(parkinglotname)) { - parkinglot = create_dynamic_parkinglot(parkinglotname, park_me); - } - - if (!parkinglot) { - ast_log(LOG_WARNING, "Parking lot not available to park %s.\n", ast_channel_name(park_me)); - return NULL; - } - - ast_debug(1, "Parking lot: %s\n", parkinglot->name); - if (parkinglot->disabled || parkinglot->cfg.is_invalid) { - ast_log(LOG_WARNING, "Parking lot %s is not in a useable state.\n", - parkinglot->name); - parkinglot_unref(parkinglot); - return NULL; - } - - /* Allocate memory for parking data */ - if (!(pu = ast_calloc(1, sizeof(*pu)))) { - parkinglot_unref(parkinglot); - return NULL; - } - - /* Lock parking list */ - AST_LIST_LOCK(&parkinglot->parkings); - - /* Check for channel variable PARKINGEXTEN */ - parkingexten = ast_strdupa(S_OR(pbx_builtin_getvar_helper(park_me, "PARKINGEXTEN"), "")); - if (!ast_strlen_zero(parkingexten)) { - /*! - * \note The API forces us to specify a numeric parking slot, even - * though the architecture would tend to support non-numeric extensions - * (as are possible with SIP, for example). Hence, we enforce that - * limitation here. If extout was not numeric, we could permit - * arbitrary non-numeric extensions. - */ - if (sscanf(parkingexten, "%30d", &parking_space) != 1 || parking_space <= 0) { - ast_log(LOG_WARNING, "PARKINGEXTEN='%s' is not a valid parking space.\n", - parkingexten); - AST_LIST_UNLOCK(&parkinglot->parkings); - parkinglot_unref(parkinglot); - ast_free(pu); - return NULL; - } - - if (parking_space < parkinglot->cfg.parking_start - || parkinglot->cfg.parking_stop < parking_space) { - /* - * Cannot allow park because parking lots are not setup for - * spaces outside of the lot. (Things like dialplan hints don't - * exist for outside lot space.) - */ - ast_log(LOG_WARNING, "PARKINGEXTEN=%d is not in %s (%d-%d).\n", - parking_space, parkinglot->name, parkinglot->cfg.parking_start, - parkinglot->cfg.parking_stop); - AST_LIST_UNLOCK(&parkinglot->parkings); - parkinglot_unref(parkinglot); - ast_free(pu); - return NULL; - } - - /* Check if requested parking space is in use. */ - AST_LIST_TRAVERSE(&parkinglot->parkings, cur, list) { - if (cur->parkingnum == parking_space) { - ast_log(LOG_WARNING, "PARKINGEXTEN=%d is already in use in %s\n", - parking_space, parkinglot->name); - AST_LIST_UNLOCK(&parkinglot->parkings); - parkinglot_unref(parkinglot); - ast_free(pu); - return NULL; - } - } - } else { - /* PARKINGEXTEN is empty, so find a usable extension in the lot to park the call */ - int start; /* The first slot we look in the parkinglot. It can be randomized. */ - int start_checked = 0; /* flag raised once the first slot is checked */ - - /* If using randomize mode, set start to random position on parking range */ - if (ast_test_flag(args, AST_PARK_OPT_RANDOMIZE)) { - start = ast_random() % (parkinglot->cfg.parking_stop - parkinglot->cfg.parking_start + 1); - start += parkinglot->cfg.parking_start; - } else if (parkinglot->cfg.parkfindnext - && parkinglot->cfg.parking_start <= parkinglot->next_parking_space - && parkinglot->next_parking_space <= parkinglot->cfg.parking_stop) { - /* Start looking with the next parking space in the lot. */ - start = parkinglot->next_parking_space; - } else { - /* Otherwise, just set it to the start position. */ - start = parkinglot->cfg.parking_start; - } - - /* free parking extension linear search: O(n^2) */ - for (i = start; ; i++) { - /* If we are past the end, wrap around to the first parking slot*/ - if (i == parkinglot->cfg.parking_stop + 1) { - i = parkinglot->cfg.parking_start; - } - - if (i == start) { - /* At this point, if start_checked, we've exhausted all the possible slots. */ - if (start_checked) { - break; - } else { - start_checked = 1; - } - } - - /* Search the list of parked calls already in use for i. If we find it, it's in use. */ - AST_LIST_TRAVERSE(&parkinglot->parkings, cur, list) { - if (cur->parkingnum == i) { - break; - } - } - if (!cur) { - /* We found a parking space. */ - parking_space = i; - break; - } - } - if (parking_space == -1) { - /* We did not find a parking space. Lot is full. */ - ast_log(LOG_WARNING, "No more parking spaces in %s\n", parkinglot->name); - AST_LIST_UNLOCK(&parkinglot->parkings); - parkinglot_unref(parkinglot); - ast_free(pu); - return NULL; - } - } - - /* Prepare for next parking space search. */ - parkinglot->next_parking_space = parking_space + 1; - - snprintf(pu->parkingexten, sizeof(pu->parkingexten), "%d", parking_space); - pu->notquiteyet = 1; - pu->parkingnum = parking_space; - pu->parkinglot = parkinglot; - AST_LIST_INSERT_TAIL(&parkinglot->parkings, pu, list); - - return pu; -} - -/* Park a call */ -static int park_call_full(struct ast_channel *chan, struct ast_channel *peer, struct ast_park_call_args *args) -{ - struct parkeduser *pu = args->pu; - const char *event_from; /*!< Channel name that is parking the call. */ - char app_data[AST_MAX_EXTENSION + AST_MAX_CONTEXT]; - - if (pu == NULL) { - args->pu = pu = park_space_reserve(chan, peer, args); - if (pu == NULL) { - return -1; - } - } - - ast_channel_appl_set(chan, "Parked Call"); - ast_channel_data_set(chan, NULL); - - pu->chan = chan; - - /* Put the parked channel on hold if we have two different channels */ - if (chan != peer) { - if (ast_test_flag(args, AST_PARK_OPT_RINGING)) { - pu->hold_method = AST_CONTROL_RINGING; - ast_indicate(chan, AST_CONTROL_RINGING); - } else { - pu->hold_method = AST_CONTROL_HOLD; - ast_indicate_data(chan, AST_CONTROL_HOLD, - S_OR(pu->parkinglot->cfg.mohclass, NULL), - !ast_strlen_zero(pu->parkinglot->cfg.mohclass) ? strlen(pu->parkinglot->cfg.mohclass) + 1 : 0); - } - } - - pu->start = ast_tvnow(); - /* XXX This line was changed to not use get_parkingtime. This is just a placeholder message, because - * likely this entire function is going away. - */ - pu->parkingtime = args->timeout; - if (args->extout) - *(args->extout) = pu->parkingnum; - - if (peer) { - event_from = S_OR(args->orig_chan_name, ast_channel_name(peer)); - - /* - * This is so ugly that it hurts, but implementing - * get_base_channel() on local channels could have ugly side - * effects. We could have - * transferer<->local;1<->local;2<->parking and we need the - * callback name to be that of transferer. Since local;1/2 have - * the same name we can be tricky and just grab the bridged - * channel from the other side of the local. - */ - if (!strcasecmp(ast_channel_tech(peer)->type, "Local")) { - struct ast_channel *tmpchan, *base_peer; - char other_side[AST_CHANNEL_NAME]; - char *c; - - ast_copy_string(other_side, event_from, sizeof(other_side)); - if ((c = strrchr(other_side, ';'))) { - *++c = '1'; - } - if ((tmpchan = ast_channel_get_by_name(other_side))) { - ast_channel_lock(tmpchan); - if ((base_peer = ast_bridged_channel(tmpchan))) { - ast_copy_string(pu->peername, ast_channel_name(base_peer), sizeof(pu->peername)); - } - ast_channel_unlock(tmpchan); - tmpchan = ast_channel_unref(tmpchan); - } - } else { - ast_copy_string(pu->peername, event_from, sizeof(pu->peername)); - } - } else { - event_from = S_OR(pbx_builtin_getvar_helper(chan, "BLINDTRANSFER"), - ast_channel_name(chan)); - } - - /* - * Remember what had been dialed, so that if the parking - * expires, we try to come back to the same place - */ - pu->options_specified = (!ast_strlen_zero(args->return_con) || !ast_strlen_zero(args->return_ext) || args->return_pri); - - /* - * If extension has options specified, they override all other - * possibilities such as the returntoorigin flag and transferred - * context. Information on extension options is lost here, so - * we set a flag - */ - ast_copy_string(pu->context, - S_OR(args->return_con, S_OR(ast_channel_macrocontext(chan), ast_channel_context(chan))), - sizeof(pu->context)); - ast_copy_string(pu->exten, - S_OR(args->return_ext, S_OR(ast_channel_macroexten(chan), ast_channel_exten(chan))), - sizeof(pu->exten)); - pu->priority = args->return_pri ? args->return_pri : - (ast_channel_macropriority(chan) ? ast_channel_macropriority(chan) : ast_channel_priority(chan)); - - /* - * If parking a channel directly, don't quite yet get parking - * running on it. All parking lot entries are put into the - * parking lot with notquiteyet on. - */ - if (peer != chan) { - pu->notquiteyet = 0; - } - - /* Wake up the (presumably select()ing) thread */ - pthread_kill(parking_thread, SIGURG); - ast_verb(2, "Parked %s on %d (lot %s). Will timeout back to extension [%s] %s, %d in %u seconds\n", - ast_channel_name(chan), pu->parkingnum, pu->parkinglot->name, - pu->context, pu->exten, pu->priority, (pu->parkingtime / 1000)); - - /*** DOCUMENTATION - - Raised when a call has been parked. - - - The parking lot extension. - - - The name of the parking lot. - - - The name of the channel that parked the call. - - - - Park - Park - ParkedCallTimeOut - ParkedCallGiveUp - - - ***/ - ast_manager_event(chan, EVENT_FLAG_CALL, "ParkedCall", - "Exten: %s\r\n" - "Channel: %s\r\n" - "Parkinglot: %s\r\n" - "From: %s\r\n" - "Timeout: %ld\r\n" - "CallerIDNum: %s\r\n" - "CallerIDName: %s\r\n" - "ConnectedLineNum: %s\r\n" - "ConnectedLineName: %s\r\n" - "Uniqueid: %s\r\n", - pu->parkingexten, ast_channel_name(chan), pu->parkinglot->name, event_from, - (long)pu->start.tv_sec + (long)(pu->parkingtime/1000) - (long)time(NULL), - S_COR(ast_channel_caller(chan)->id.number.valid, ast_channel_caller(chan)->id.number.str, ""), - S_COR(ast_channel_caller(chan)->id.name.valid, ast_channel_caller(chan)->id.name.str, ""), - S_COR(ast_channel_connected(chan)->id.number.valid, ast_channel_connected(chan)->id.number.str, ""), - S_COR(ast_channel_connected(chan)->id.name.valid, ast_channel_connected(chan)->id.name.str, ""), - ast_channel_uniqueid(chan) - ); - ast_debug(4, "peer: %s\n", peer ? ast_channel_name(peer) : "-No peer-"); - ast_debug(4, "args->orig_chan_name: %s\n", args->orig_chan_name ? args->orig_chan_name : "-none-"); - ast_debug(4, "pu->peername: %s\n", pu->peername); - ast_debug(4, "AMI ParkedCall Channel: %s\n", ast_channel_name(chan)); - ast_debug(4, "AMI ParkedCall From: %s\n", event_from); - - if (peer && adsipark && ast_adsi_available(peer)) { - adsi_announce_park(peer, pu->parkingexten); /* Only supports parking numbers */ - ast_adsi_unload_session(peer); - } - - snprintf(app_data, sizeof(app_data), "%s,%s", pu->parkingexten, - pu->parkinglot->name); - - AST_LIST_UNLOCK(&pu->parkinglot->parkings); - - /* Only say number if it's a number and the channel hasn't been masqueraded away */ - if (peer && !ast_test_flag(args, AST_PARK_OPT_SILENCE) - && (ast_strlen_zero(args->orig_chan_name) || !strcasecmp(ast_channel_name(peer), args->orig_chan_name))) { - /* - * If a channel is masqueraded into peer while playing back the - * parking space number do not continue playing it back. This - * is the case if an attended transfer occurs. - */ - ast_set_flag(ast_channel_flags(peer), AST_FLAG_MASQ_NOSTREAM); - /* Tell the peer channel the number of the parking space */ - ast_say_digits(peer, pu->parkingnum, "", ast_channel_language(peer)); - ast_clear_flag(ast_channel_flags(peer), AST_FLAG_MASQ_NOSTREAM); - } - if (peer == chan) { /* pu->notquiteyet = 1 */ - /* Wake up parking thread if we're really done */ - if (ast_test_flag(args, AST_PARK_OPT_RINGING)) { - pu->hold_method = AST_CONTROL_RINGING; - ast_indicate(chan, AST_CONTROL_RINGING); - } else { - pu->hold_method = AST_CONTROL_HOLD; - ast_indicate_data(chan, AST_CONTROL_HOLD, - S_OR(pu->parkinglot->cfg.mohclass, NULL), - !ast_strlen_zero(pu->parkinglot->cfg.mohclass) ? strlen(pu->parkinglot->cfg.mohclass) + 1 : 0); - } - pu->notquiteyet = 0; - pthread_kill(parking_thread, SIGURG); - } - return 0; -} - -/*! - * \brief Park call via masqueraded channel and announce parking spot on peer channel. - * - * \param rchan the real channel to be parked - * \param peer the channel to have the parking read to. - * \param args Additional parking options when parking a call. - * - * \retval 0 on success. - * \retval -1 on failure. - */ -static int masq_park_call(struct ast_channel *rchan, struct ast_channel *peer, struct ast_park_call_args *args) -{ - struct ast_channel *chan; - - /* Make a new, channel that we'll use to masquerade in the real one */ - chan = ast_channel_alloc(0, AST_STATE_DOWN, 0, 0, ast_channel_accountcode(rchan), ast_channel_exten(rchan), - ast_channel_context(rchan), ast_channel_linkedid(rchan), ast_channel_amaflags(rchan), "Parked/%s", ast_channel_name(rchan)); - if (!chan) { - ast_log(LOG_WARNING, "Unable to create parked channel\n"); - if (!ast_test_flag(args, AST_PARK_OPT_SILENCE)) { - if (peer == rchan) { - /* Only have one channel to worry about. */ - ast_stream_and_wait(peer, "pbx-parkingfailed", ""); - } else if (peer) { - /* Have two different channels to worry about. */ - play_message_on_chan(peer, rchan, "failure message", "pbx-parkingfailed"); - } - } - return -1; - } - - args->pu = park_space_reserve(rchan, peer, args); - if (!args->pu) { - ast_hangup(chan); - if (!ast_test_flag(args, AST_PARK_OPT_SILENCE)) { - if (peer == rchan) { - /* Only have one channel to worry about. */ - ast_stream_and_wait(peer, "pbx-parkingfailed", ""); - } else if (peer) { - /* Have two different channels to worry about. */ - play_message_on_chan(peer, rchan, "failure message", "pbx-parkingfailed"); - } - } - return -1; - } - - /* Make formats okay */ - ast_format_copy(ast_channel_readformat(chan), ast_channel_readformat(rchan)); - ast_format_copy(ast_channel_writeformat(chan), ast_channel_writeformat(rchan)); - - if (ast_channel_masquerade(chan, rchan)) { - park_space_abort(args->pu); - args->pu = NULL; - ast_hangup(chan); - if (!ast_test_flag(args, AST_PARK_OPT_SILENCE)) { - if (peer == rchan) { - /* Only have one channel to worry about. */ - ast_stream_and_wait(peer, "pbx-parkingfailed", ""); - } else if (peer) { - /* Have two different channels to worry about. */ - play_message_on_chan(peer, rchan, "failure message", "pbx-parkingfailed"); - } - } - return -1; - } - - /* Setup the extensions and such */ - set_c_e_p(chan, ast_channel_context(rchan), ast_channel_exten(rchan), ast_channel_priority(rchan)); - - /* Setup the macro extension and such */ - ast_channel_macrocontext_set(chan, ast_channel_macrocontext(rchan)); - ast_channel_macroexten_set(chan, ast_channel_macroexten(rchan)); - ast_channel_macropriority_set(chan, ast_channel_macropriority(rchan)); - - /* Manually do the masquerade to make sure it is complete. */ - ast_do_masquerade(chan); - - if (peer == rchan) { - peer = chan; - } - - /* parking space reserved, return code check unnecessary */ - park_call_full(chan, peer, args); - - return 0; -} - -int ast_masq_park_call_exten(struct ast_channel *park_me, struct ast_channel *parker, const char *park_exten, const char *park_context, int timeout, int *extout) -{ - int res; - char *parse; - const char *app_data; - struct ast_exten *exten; - struct park_app_args app_args; - struct ast_park_call_args args = { - .timeout = timeout, - .extout = extout, - }; - - if (parker) { - args.orig_chan_name = ast_strdupa(ast_channel_name(parker)); - } - if (!park_exten || !park_context) { - return masq_park_call(park_me, parker, &args); - } - - /* - * Determiine if the specified park extension has an exclusive - * parking lot to use. - */ - if (parker && parker != park_me) { - ast_autoservice_start(park_me); - } - exten = get_parking_exten(park_exten, parker, park_context); - if (exten) { - app_data = ast_get_extension_app_data(exten); - if (!app_data) { - app_data = ""; - } - parse = ast_strdupa(app_data); - AST_STANDARD_APP_ARGS(app_args, parse); - - if (!ast_strlen_zero(app_args.pl_name)) { - /* Find the specified exclusive parking lot */ - args.parkinglot = find_parkinglot(app_args.pl_name); - if (!args.parkinglot && parkeddynamic) { - args.parkinglot = create_dynamic_parkinglot(app_args.pl_name, park_me); - } - } - } - if (parker && parker != park_me) { - ast_autoservice_stop(park_me); - } - - res = masq_park_call(park_me, parker, &args); - if (args.parkinglot) { - parkinglot_unref(args.parkinglot); - } - return res; -} - -int ast_masq_park_call(struct ast_channel *rchan, struct ast_channel *peer, int timeout, int *extout) -{ - struct ast_park_call_args args = { - .timeout = timeout, - .extout = extout, - }; - - if (peer) { - args.orig_chan_name = ast_strdupa(ast_channel_name(peer)); - } - return masq_park_call(rchan, peer, &args); -} - -/*! - * \internal - * \brief Play file to specified channel. - * - * \param play_to Channel to play audiofile to. - * \param other Channel to put in autoservice while playing file. - * \param msg Descriptive name of message type being played. - * \param audiofile Audio file to play. - * - * \retval 0 on success. - * \retval -1 on error. (Couldn't play file, a channel hung up,...) - */ -static int play_message_on_chan(struct ast_channel *play_to, struct ast_channel *other, const char *msg, const char *audiofile) -{ - /* Put other channel in autoservice. */ - if (ast_autoservice_start(other)) { - return -1; - } - ast_autoservice_ignore(other, AST_FRAME_DTMF_BEGIN); - ast_autoservice_ignore(other, AST_FRAME_DTMF_END); - if (ast_stream_and_wait(play_to, audiofile, "")) { - ast_log(LOG_WARNING, "Failed to play %s '%s'!\n", msg, audiofile); - ast_autoservice_stop(other); - return -1; - } - if (ast_autoservice_stop(other)) { - return -1; - } - return 0; -} - /*! * \internal * \brief Get the extension for a given builtin feature @@ -2643,46 +1847,6 @@ static void *do_parking_thread(void *ignore) return NULL; /* Never reached */ } -/*! \brief Find parkinglot by name */ -static struct ast_parkinglot *find_parkinglot(const char *name) -{ - struct ast_parkinglot *parkinglot; - - if (ast_strlen_zero(name)) { - return NULL; - } - - parkinglot = ao2_find(parkinglots, (void *) name, 0); - if (parkinglot) { - ast_debug(1, "Found Parking lot: %s\n", parkinglot->name); - } - - return parkinglot; -} - -/*! \brief Copy parkinglot and store it with new name */ -static struct ast_parkinglot *copy_parkinglot(const char *name, const struct ast_parkinglot *parkinglot) -{ - struct ast_parkinglot *copylot; - - if ((copylot = find_parkinglot(name))) { /* Parkinglot with that name already exists */ - ao2_ref(copylot, -1); - return NULL; - } - - copylot = create_parkinglot(name); - if (!copylot) { - return NULL; - } - - ast_debug(1, "Building parking lot %s\n", name); - - /* Copy the source parking lot configuration. */ - copylot->cfg = parkinglot->cfg; - - return copylot; -} - AST_APP_OPTIONS(park_call_options, BEGIN_OPTIONS AST_APP_OPTION('r', AST_PARK_OPT_RINGING), AST_APP_OPTION('R', AST_PARK_OPT_RANDOMIZE), @@ -2699,49 +1863,6 @@ static void parkinglot_unref(struct ast_parkinglot *parkinglot) ao2_ref(parkinglot, -1); } -static struct ast_parkinglot *parkinglot_addref(struct ast_parkinglot *parkinglot) -{ - int refcount; - - refcount = ao2_ref(parkinglot, +1); - ast_debug(3, "Multiparking: %s refcount now %d\n", parkinglot->name, refcount + 1); - return parkinglot; -} - -/*! \brief Destroy a parking lot */ -static void parkinglot_destroy(void *obj) -{ - struct ast_parkinglot *doomed = obj; - - /* - * No need to destroy parked calls here because any parked call - * holds a parking lot reference. Therefore the parkings list - * must be empty. - */ - ast_assert(AST_LIST_EMPTY(&doomed->parkings)); - AST_LIST_HEAD_DESTROY(&doomed->parkings); -} - -/*! \brief Allocate parking lot structure */ -static struct ast_parkinglot *create_parkinglot(const char *name) -{ - struct ast_parkinglot *newlot; - - if (ast_strlen_zero(name)) { /* No name specified */ - return NULL; - } - - newlot = ao2_alloc(sizeof(*newlot), parkinglot_destroy); - if (!newlot) - return NULL; - - ast_copy_string(newlot->name, name, sizeof(newlot->name)); - newlot->cfg.is_invalid = 1;/* No config is set yet. */ - AST_LIST_HEAD_INIT(&newlot->parkings); - - return newlot; -} - /*! Default configuration for default parking lot. */ static const struct parkinglot_cfg parkinglot_cfg_default_default = { .mohclass = "default", @@ -2764,26 +1885,6 @@ static const struct parkinglot_cfg parkinglot_cfg_default = { .comebacktoorigin = DEFAULT_COMEBACK_TO_ORIGIN, }; -/*! - * \internal - * \brief Activate the given parkinglot. - * - * \param parkinglot Parking lot to activate. - * - * \details - * Insert into the dialplan the context, parking lot access - * extension, and optional dialplan hints. - * - * \retval 0 on success. - * \retval -1 on error. - */ -static int parkinglot_activate(struct ast_parkinglot *parkinglot) -{ - /* XXX All parking stuff is being replaced by res_parking */ - parkinglot->disabled = 1; - return -1; -} - int ast_features_reload(void) { struct ast_context *con; @@ -3635,243 +2736,6 @@ done: return 0; } -#if defined(TEST_FRAMEWORK) -/*! - * \internal - * \brief Convert parking spaces map list to a comma separated string. - * - * \param str String buffer to fill. - * \param spaces Parking spaces map list to convert. - * - * \return Nothing - */ -static void create_spaces_str(struct ast_str **str, struct parking_dp_space_map *spaces) -{ - const char *comma; - struct parking_dp_spaces *cur; - - ast_str_reset(*str); - comma = ""; - AST_LIST_TRAVERSE(spaces, cur, node) { - if (cur->start == cur->stop) { - ast_str_append(str, 0, "%s%d", comma, cur->start); - } else { - ast_str_append(str, 0, "%s%d-%d", comma, cur->start, cur->stop); - } - comma = ","; - } -} -#endif /* defined(TEST_FRAMEWORK) */ - -#if defined(TEST_FRAMEWORK) -/*! - * \internal - * \brief Compare parking spaces map to what is expected. - * - * \param test Unit test context. - * \param spaces Parking spaces map list to check. - * \param expected String to compare with. - * \param what What is being compared. - * - * \retval 0 successful compare. - * \retval nonzero if failed to compare. - */ -static int check_spaces(struct ast_test *test, struct parking_dp_space_map *spaces, const char *expected, const char *what) -{ - int cmp; - struct ast_str *str = ast_str_alloca(1024); - - create_spaces_str(&str, spaces); - cmp = strcmp(expected, ast_str_buffer(str)); - if (cmp) { - ast_test_status_update(test, - "Unexpected parking space map for %s. Expect:'%s' Got:'%s'\n", - what, expected, ast_str_buffer(str)); - } - return cmp; -} -#endif /* defined(TEST_FRAMEWORK) */ - -#if defined(TEST_FRAMEWORK) -/*! - * \internal - * \brief Add a dead space to the dead spaces list. - * - * \param context Dead spaces list ptr pretending to be a context name ptr. - * \param space Dead space to add to the list. - * - * \return Nothing - */ -static void test_add_dead_space(const char *context, int space) -{ - struct parking_dp_space_map *dead_spaces = (struct parking_dp_space_map *) context; - - usage_context_add_spaces(dead_spaces, space, space, NULL, 0); -} -#endif /* defined(TEST_FRAMEWORK) */ - -#if defined(TEST_FRAMEWORK) -struct test_map { - const char *ramp; - int start; - int stop; - const char *expect; -}; - -/*! - * \internal - * \brief Build a parking lot dialplan usage test map from a table. - * - * \param test Unit test context. - * \param lot Parking lot to use to build test usage map. - * \param table_name Name of passed in table. - * \param table Usage information to put in the usage map. - * \param num_entries Number of entries in the table. - * - * \retval Created context node on success. - * \retval NULL on error. - */ -static struct parking_dp_context *test_build_maps(struct ast_test *test, - struct ast_parkinglot *lot, const char *table_name, const struct test_map *table, - size_t num_entries) -{ - struct parking_dp_context *ctx_node; - int cur_index = 0; - char what[40]; - - snprintf(what, sizeof(what), "%s[%d]", table_name, cur_index); - ast_copy_string(lot->cfg.parkext, table->ramp, sizeof(lot->cfg.parkext)); - lot->cfg.parking_start = table->start; - lot->cfg.parking_stop = table->stop; - ctx_node = build_dialplan_useage_context(lot); - if (!ctx_node) { - ast_test_status_update(test, "Failed to create parking lot context map for %s\n", - what); - return NULL; - } - if (check_spaces(test, &ctx_node->spaces, table->expect, what)) { - destroy_dialplan_usage_context(ctx_node); - return NULL; - } - while (--num_entries) { - ++cur_index; - ++table; - snprintf(what, sizeof(what), "%s[%d]", table_name, cur_index); - ast_copy_string(lot->cfg.parkext, table->ramp, sizeof(lot->cfg.parkext)); - lot->cfg.parking_start = table->start; - lot->cfg.parking_stop = table->stop; - if (dialplan_usage_add_parkinglot_data(ctx_node, lot, 1)) { - ast_test_status_update(test, "Failed to add parking lot data for %s\n", what); - destroy_dialplan_usage_context(ctx_node); - return NULL; - } - if (check_spaces(test, &ctx_node->spaces, table->expect, what)) { - destroy_dialplan_usage_context(ctx_node); - return NULL; - } - } - return ctx_node; -} - -static const struct test_map test_old_ctx[] = { - /* The following order of building ctx is important to test adding items to the lists. */ - { "702", 14, 15, "14-15" }, - { "700", 10, 11, "10-11,14-15" }, - { "701", 18, 19, "10-11,14-15,18-19" }, - { "703", 12, 13, "10-15,18-19" }, - { "704", 16, 17, "10-19" }, - - /* Parking ramp and space conflicts are intended with these lines. */ - { "704", 9, 19, "9-19" }, - { "704", 9, 20, "9-20" }, - { "704", 8, 21, "8-21" }, - - /* Add more spaces to ctx to test removing dead parking spaces. */ - { "705", 23, 25, "8-21,23-25" }, - { "706", 28, 31, "8-21,23-25,28-31" }, - { "707", 33, 34, "8-21,23-25,28-31,33-34" }, - { "708", 38, 40, "8-21,23-25,28-31,33-34,38-40" }, - { "709", 42, 43, "8-21,23-25,28-31,33-34,38-40,42-43" }, -}; - -static const struct test_map test_new_ctx[] = { - { "702", 4, 5, "4-5" }, - { "704", 24, 26, "4-5,24-26" }, - { "709", 29, 30, "4-5,24-26,29-30" }, - { "710", 32, 35, "4-5,24-26,29-30,32-35" }, - { "711", 37, 39, "4-5,24-26,29-30,32-35,37-39" }, -}; -#endif /* defined(TEST_FRAMEWORK) */ - -#if defined(TEST_FRAMEWORK) -/*! - * \internal - * \brief Test parking dialplan usage map code. - * - * \param test Unit test context. - * - * \retval 0 on success. - * \retval -1 on error. - */ -static int test_dialplan_usage_map(struct ast_test *test) -{ - struct parking_dp_context *old_ctx; - struct parking_dp_context *new_ctx; - struct ast_parkinglot *lot; - struct parking_dp_spaces *spaces; - struct parking_dp_space_map dead_spaces = AST_LIST_HEAD_NOLOCK_INIT_VALUE; - int res; - - ast_test_status_update(test, "Test parking dialplan usage map code\n"); - - lot = create_parkinglot("test_lot"); - if (!lot) { - return -1; - } - ast_copy_string(lot->cfg.parking_con, "test-ctx", sizeof(lot->cfg.parking_con)); - lot->cfg.parkext_exclusive = 1; - - ast_test_status_update(test, - "Build old_ctx map\n"); - ast_log(LOG_NOTICE, "6 Ramp and space conflict warnings are expected.\n"); - old_ctx = test_build_maps(test, lot, "test_old_ctx", test_old_ctx, - ARRAY_LEN(test_old_ctx)); - if (!old_ctx) { - ao2_ref(lot, -1); - return -1; - } - - ast_test_status_update(test, "Build new_ctx map\n"); - new_ctx = test_build_maps(test, lot, "test_new_ctx", test_new_ctx, - ARRAY_LEN(test_new_ctx)); - if (!new_ctx) { - res = -1; - goto fail_old_ctx; - } - - ast_test_status_update(test, "Test removing dead parking spaces\n"); - remove_dead_spaces_usage((void *) &dead_spaces, &old_ctx->spaces, - &new_ctx->spaces, test_add_dead_space); - if (check_spaces(test, &dead_spaces, "8-21,23,28,31,40,42-43", "dead_spaces")) { - res = -1; - goto fail_dead_spaces; - } - - res = 0; - -fail_dead_spaces: - while ((spaces = AST_LIST_REMOVE_HEAD(&dead_spaces, node))) { - ast_free(spaces); - } - destroy_dialplan_usage_context(new_ctx); - -fail_old_ctx: - destroy_dialplan_usage_context(old_ctx); - ao2_ref(lot, -1); - return res; -} -#endif /* defined(TEST_FRAMEWORK) */ - #if defined(TEST_FRAMEWORK) static int fake_fixup(struct ast_channel *clonechan, struct ast_channel *original) { @@ -3905,209 +2769,6 @@ static struct ast_channel *create_test_channel(const struct ast_channel_tech *fa } #endif /* defined(TEST_FRAMEWORK) */ -#if defined(TEST_FRAMEWORK) -static int unpark_test_channel(struct ast_channel *toremove, struct ast_park_call_args *args) -{ - struct ast_context *con; - struct parkeduser *pu_toremove; - int res = 0; - - args->pu->notquiteyet = 1; /* go ahead and stop processing the test parking */ - - AST_LIST_LOCK(&args->pu->parkinglot->parkings); - AST_LIST_TRAVERSE_SAFE_BEGIN(&args->pu->parkinglot->parkings, pu_toremove, list) { - if (pu_toremove == args->pu) { - AST_LIST_REMOVE_CURRENT(list); - break; - } - } - AST_LIST_TRAVERSE_SAFE_END; - AST_LIST_UNLOCK(&args->pu->parkinglot->parkings); - - if (!pu_toremove) { - ast_log(LOG_WARNING, "Whoa, could not find parking test call!\n"); - return -1; - } - - con = ast_context_find(args->pu->parkinglot->cfg.parking_con); - if (con) { - if (ast_context_remove_extension2(con, args->pu->parkingexten, 1, NULL, 0)) { - ast_log(LOG_WARNING, "Whoa, failed to remove the parking extension!\n"); - res = -1; - } else { - notify_metermaids(args->pu->parkingexten, - pu_toremove->parkinglot->cfg.parking_con, AST_DEVICE_NOT_INUSE); - } - } else { - ast_log(LOG_WARNING, "Whoa, no parking context?\n"); - res = -1; - } - - parkinglot_unref(pu_toremove->parkinglot); - ast_free(pu_toremove); - args->pu = NULL; - - if (!res && toremove) { - ast_hangup(toremove); - } - return res; -} -#endif /* defined(TEST_FRAMEWORK) */ - -#if defined(TEST_FRAMEWORK) -AST_TEST_DEFINE(features_test) -{ - struct ast_channel *test_channel1 = NULL; - struct ast_channel *parked_chan = NULL; - struct ast_parkinglot *dynlot; - struct ast_park_call_args args = { - .timeout = DEFAULT_PARK_TIME, - }; - - int res = 0; - - static const struct ast_channel_tech fake_tech = { - .fixup = fake_fixup, /* silence warning from masquerade */ - }; - - static const char unique_lot_1[] = "myuniquetestparkinglot314"; - static const char unique_lot_2[] = "myuniquetestparkinglot3141592654"; - static const char unique_context_1[] = "myuniquetestcontext314"; - static const char unique_context_2[] = "myuniquetestcontext3141592654"; - static const char parkinglot_parkext[] = "750"; - static const char parkinglot_range[] = "751-760"; - - switch (cmd) { - case TEST_INIT: - info->name = "features_test"; - info->category = "/main/features/"; - info->summary = "Features unit test"; - info->description = - "Tests whether parking respects PARKINGLOT settings"; - return AST_TEST_NOT_RUN; - case TEST_EXECUTE: - break; - } - - if (test_dialplan_usage_map(test)) { - res = -1; - goto exit_features_test; - } - - /* changing a config option is a bad practice, but must be done in this case */ - parkeddynamic = 1; - - ast_test_status_update(test, "Test parking functionality with defaults\n"); - if (!(test_channel1 = create_test_channel(&fake_tech))) { - res = -1; - goto exit_features_test; - } - if (park_call_full(test_channel1, NULL, &args)) { - res = -1; - goto exit_features_test; - } - if (unpark_test_channel(test_channel1, &args)) { - res = -1; - goto exit_features_test; - } - - - ast_test_status_update(test, "Check that certain parking options are respected\n"); - if (!(test_channel1 = create_test_channel(&fake_tech))) { - res = -1; - goto exit_features_test; - } - pbx_builtin_setvar_helper(test_channel1, "PARKINGLOT", unique_lot_1); - pbx_builtin_setvar_helper(test_channel1, "PARKINGDYNCONTEXT", unique_context_1); - pbx_builtin_setvar_helper(test_channel1, "PARKINGDYNEXTEN", parkinglot_parkext); - pbx_builtin_setvar_helper(test_channel1, "PARKINGDYNPOS", parkinglot_range); - if (park_call_full(test_channel1, NULL, &args)) { - res = -1; - goto exit_features_test; - } - /* grab newly created parking lot for destruction in the end */ - dynlot = args.pu->parkinglot; - if (args.pu->parkingnum != 751 - || strcmp(dynlot->name, unique_lot_1) - || strcmp(dynlot->cfg.parking_con, unique_context_1) - || strcmp(dynlot->cfg.parkext, parkinglot_parkext) - || dynlot->cfg.parking_start != 751 - || dynlot->cfg.parking_stop != 760) { - ast_test_status_update(test, "Parking settings were not respected\n"); - ast_test_status_update(test, "Dyn-name:%s\n", dynlot->name); - ast_test_status_update(test, "Dyn-context:%s\n", dynlot->cfg.parking_con); - ast_test_status_update(test, "Dyn-parkext:%s\n", dynlot->cfg.parkext); - ast_test_status_update(test, "Dyn-parkpos:%d-%d\n", dynlot->cfg.parking_start, - dynlot->cfg.parking_stop); - ast_test_status_update(test, "Parked in space:%d\n", args.pu->parkingnum); - if (!unpark_test_channel(test_channel1, &args)) { - test_channel1 = NULL; - } - res = -1; - goto exit_features_test; - } else { - ast_test_status_update(test, "Parking settings for non-masquerading park verified\n"); - } - if (unpark_test_channel(test_channel1, &args)) { - res = -1; - goto exit_features_test; - } - - - ast_test_status_update(test, "Check #2 that certain parking options are respected\n"); - if (!(test_channel1 = create_test_channel(&fake_tech))) { - res = -1; - goto exit_features_test; - } - pbx_builtin_setvar_helper(test_channel1, "PARKINGLOT", unique_lot_2); - pbx_builtin_setvar_helper(test_channel1, "PARKINGDYNCONTEXT", unique_context_2); - pbx_builtin_setvar_helper(test_channel1, "PARKINGDYNEXTEN", parkinglot_parkext); - pbx_builtin_setvar_helper(test_channel1, "PARKINGDYNPOS", parkinglot_range); - if (masq_park_call(test_channel1, NULL, &args)) { - res = -1; - goto exit_features_test; - } - /* hangup zombie channel */ - ast_hangup(test_channel1); - test_channel1 = NULL; - - dynlot = args.pu->parkinglot; - if (args.pu->parkingnum != 751 - || strcmp(dynlot->name, unique_lot_2) - || strcmp(dynlot->cfg.parking_con, unique_context_2) - || strcmp(dynlot->cfg.parkext, parkinglot_parkext) - || dynlot->cfg.parking_start != 751 - || dynlot->cfg.parking_stop != 760) { - ast_test_status_update(test, "Parking settings were not respected\n"); - ast_test_status_update(test, "Dyn-name:%s\n", dynlot->name); - ast_test_status_update(test, "Dyn-context:%s\n", dynlot->cfg.parking_con); - ast_test_status_update(test, "Dyn-parkext:%s\n", dynlot->cfg.parkext); - ast_test_status_update(test, "Dyn-parkpos:%d-%d\n", dynlot->cfg.parking_start, - dynlot->cfg.parking_stop); - ast_test_status_update(test, "Parked in space:%d\n", args.pu->parkingnum); - res = -1; - } else { - ast_test_status_update(test, "Parking settings for masquerading park verified\n"); - } - - /* find the real channel */ - parked_chan = ast_channel_get_by_name("TestChannel1"); - if (unpark_test_channel(parked_chan, &args)) { - ast_hangup(parked_chan); - res = -1; - } - - -exit_features_test: - - ast_hangup(test_channel1); - - force_reload_load = 1; - ast_features_reload(); - return res ? AST_TEST_FAIL : AST_TEST_PASS; -} -#endif /* defined(TEST_FRAMEWORK) */ - /*! \internal \brief Clean up resources on Asterisk shutdown */ static void features_shutdown(void) { @@ -4152,9 +2813,6 @@ int ast_features_init(void) res |= ast_manager_register_xml_core("Bridge", EVENT_FLAG_CALL, action_bridge); res |= ast_devstate_prov_add("Park", metermaidstate); -#if defined(TEST_FRAMEWORK) - res |= AST_TEST_REGISTER(features_test); -#endif /* defined(TEST_FRAMEWORK) */ if (res) { features_shutdown(); diff --git a/main/parking.c b/main/parking.c index 710a3c8c71..83c599ba0e 100644 --- a/main/parking.c +++ b/main/parking.c @@ -41,11 +41,8 @@ STASIS_MESSAGE_TYPE_DEFN(ast_parked_call_type); /*! \brief Topic for parking lots */ static struct stasis_topic *parking_topic; -/*! \brief Function Callback for handling blind transfers to park applications */ -static ast_park_blind_xfer_fn ast_park_blind_xfer_func = NULL; - -/*! \brief Function Callback for handling a bridge channel trying to park itself */ -static ast_bridge_channel_park_fn ast_bridge_channel_park_func = NULL; +/*! \brief The container for the parking provider */ +static AO2_GLOBAL_OBJ_STATIC(parking_provider); static void parking_stasis_cleanup(void) { @@ -127,67 +124,58 @@ struct ast_parked_call_payload *ast_parked_call_payload_create(enum ast_parked_c return payload; } -void ast_install_park_blind_xfer_func(ast_park_blind_xfer_fn park_blind_xfer_func) +struct ast_parking_bridge_feature_fn_table *ast_parking_get_bridge_features(void) { - ast_park_blind_xfer_func = park_blind_xfer_func; + return (struct ast_parking_bridge_feature_fn_table*)ao2_global_obj_ref(parking_provider); } -void ast_install_bridge_channel_park_func(ast_bridge_channel_park_fn bridge_channel_park_func) -{ - ast_bridge_channel_park_func = bridge_channel_park_func; -} +/*! \brief A wrapper around the fn_table to ao2-ify it */ +struct parking_provider_wrapper { + struct ast_parking_bridge_feature_fn_table fn_table; +}; -void ast_uninstall_park_blind_xfer_func(void) +int ast_parking_register_bridge_features(struct ast_parking_bridge_feature_fn_table *fn_table) { - ast_park_blind_xfer_func = NULL; -} + RAII_VAR(struct parking_provider_wrapper *, wrapper, + ao2_global_obj_ref(parking_provider), ao2_cleanup); -void ast_uninstall_bridge_channel_park_func(void) -{ - ast_bridge_channel_park_func = NULL; -} - -int ast_park_blind_xfer(struct ast_bridge_channel *parker, struct ast_exten *park_exten) -{ - static int warned = 0; - - if (ast_park_blind_xfer_func) { - return ast_park_blind_xfer_func(parker, park_exten); + if (fn_table->module_version != PARKING_MODULE_VERSION) { + ast_log(AST_LOG_WARNING, "Parking module provided incorrect parking module " + "version: %d (expected: %d)\n", fn_table->module_version, PARKING_MODULE_VERSION); + return -1; } - if (warned++ % 10 == 0) { - ast_verb(3, "%s attempted to blind transfer to a parking extension, but no parking blind transfer function is loaded.\n", - ast_channel_name(parker->chan)); + if (wrapper) { + ast_log(AST_LOG_WARNING, "Parking provider already registered by %s!\n", + wrapper->fn_table.module_name); + return -1; } - return -1; + wrapper = ao2_alloc(sizeof(*wrapper), NULL); + if (!wrapper) { + return -1; + } + wrapper->fn_table = *fn_table; + + ao2_global_obj_replace(parking_provider, wrapper); + return 0; } -struct ast_exten *ast_get_parking_exten(const char *exten_str, struct ast_channel *chan, const char *context) +int ast_parking_unregister_bridge_features(const char *module_name) { - struct ast_exten *exten; - struct pbx_find_info q = { .stacklen = 0 }; /* the rest is reset in pbx_find_extension */ - const char *app_at_exten; + RAII_VAR(struct parking_provider_wrapper *, wrapper, + ao2_global_obj_ref(parking_provider), ao2_cleanup); - ast_debug(4, "Checking if %s@%s is a parking exten\n", exten_str, context); - exten = pbx_find_extension(chan, NULL, &q, context, exten_str, 1, NULL, NULL, - E_MATCH); - if (!exten) { - return NULL; + if (!wrapper) { + ast_log(AST_LOG_WARNING, "No parking provider to unregister\n"); + return -1; } - app_at_exten = ast_get_extension_app(exten); - if (!app_at_exten || strcasecmp(PARK_APPLICATION, app_at_exten)) { - return NULL; + if (strcmp(wrapper->fn_table.module_name, module_name)) { + ast_log(AST_LOG_WARNING, "%s has not registered the parking provider\n", module_name); + return -1; } - return exten; -} - -void ast_bridge_channel_park(struct ast_bridge_channel *bridge_channel, const char *parkee_uuid, const char *parker_uuid, const char *app_data) -{ - /* Run installable function */ - if (ast_bridge_channel_park_func) { - return ast_bridge_channel_park_func(bridge_channel, parkee_uuid, parker_uuid, app_data); - } + ao2_global_obj_replace_unref(parking_provider, NULL); + return 0; } diff --git a/res/parking/parking_bridge_features.c b/res/parking/parking_bridge_features.c index e44a7695bc..06987dcee8 100644 --- a/res/parking/parking_bridge_features.c +++ b/res/parking/parking_bridge_features.c @@ -186,7 +186,7 @@ static int create_parked_subscription(struct ast_channel *chan, const char *park * identical to the dial_transfer function in bridge_basic.c, however it doesn't swap the * local channel and the channel that instigated the park. */ -static struct ast_channel *park_local_transfer(struct ast_channel *parker, const char *exten, const char *context) +static struct ast_channel *park_local_transfer(struct ast_channel *parker, const char *context, const char *exten) { RAII_VAR(struct ast_channel *, parkee_side_2, NULL, ao2_cleanup); char destination[AST_MAX_EXTENSION + AST_MAX_CONTEXT + 1]; @@ -242,78 +242,121 @@ static struct ast_channel *park_local_transfer(struct ast_channel *parker, const return parkee; } -static int park_feature_helper(struct ast_bridge_channel *bridge_channel, struct ast_exten *park_exten) +/*! \internal \brief Determine if an extension is a parking extension */ +static int parking_is_exten_park(const char *context, const char *exten) { - RAII_VAR(struct ast_channel *, other, NULL, ao2_cleanup); - RAII_VAR(struct parking_lot *, lot, NULL, ao2_cleanup); - RAII_VAR(struct parked_user *, pu, NULL, ao2_cleanup); - RAII_VAR(struct ast_bridge *, parking_bridge, NULL, ao2_cleanup); - RAII_VAR(struct ao2_container *, bridge_peers, NULL, ao2_cleanup); - struct ao2_iterator iter; + struct ast_exten *exten_obj; + struct pbx_find_info info = { .stacklen = 0 }; /* the rest is reset in pbx_find_extension */ + const char *app_at_exten; - ast_bridge_channel_lock_bridge(bridge_channel); - bridge_peers = ast_bridge_peers_nolock(bridge_channel->bridge); - ast_bridge_unlock(bridge_channel->bridge); - - if (ao2_container_count(bridge_peers) < 2) { - /* There is nothing to do if there is no one to park. */ + ast_debug(4, "Checking if %s@%s is a parking exten\n", exten, context); + exten_obj = pbx_find_extension(NULL, NULL, &info, context, exten, 1, NULL, NULL, E_MATCH); + if (!exten_obj) { return 0; } - if (ao2_container_count(bridge_peers) > 2) { - /* With a multiparty bridge, we need to do a regular blind transfer. We link the existing bridge to the parking lot with a - * local channel rather than transferring others. */ + app_at_exten = ast_get_extension_app(exten_obj); + if (!app_at_exten || strcasecmp(PARK_APPLICATION, app_at_exten)) { + return 0; + } + + return 1; +} + +/*! + * \internal + * \since 12.0.0 + * \brief Perform a blind transfer to a parking lot + * + * In general, most parking features should work to call this function. This will safely + * park either a channel in the bridge with \ref bridge_channel or will park the entire + * bridge if more than one channel is in the bridge. It will create the correct data to + * pass to the \ref AstBridging Bridging API to safely park the channel. + * + * \param bridge_channel The bridge_channel representing the channel performing the park + * \param context The context to blind transfer to + * \param exten The extension to blind transfer to + * + * \retval 0 on success + * \retval non-zero on error + */ +static int parking_blind_transfer_park(struct ast_bridge_channel *bridge_channel, + const char *context, const char *exten) +{ + RAII_VAR(struct ast_bridge_channel *, other, NULL, ao2_cleanup); + int peer_count; + + if (ast_strlen_zero(context) || ast_strlen_zero(exten)) { + return -1; + } + + if (!bridge_channel->in_bridge) { + return -1; + } + + if (!parking_is_exten_park(context, exten)) { + return -1; + } + + ast_bridge_channel_lock_bridge(bridge_channel); + peer_count = bridge_channel->bridge->num_channels; + if (peer_count == 2) { + other = ast_bridge_channel_peer(bridge_channel); + ao2_ref(other, +1); + } + ast_bridge_unlock(bridge_channel->bridge); + + if (peer_count < 2) { + /* There is nothing to do if there is no one to park. */ + return -1; + } + + /* With a multiparty bridge, we need to do a regular blind transfer. We link the + * existing bridge to the parking lot with a Local channel rather than + * transferring others. */ + if (peer_count > 2) { struct ast_channel *transfer_chan = NULL; - if (!park_exten) { - /* This simply doesn't work. The user attempted to one-touch park the parking lot and we can't originate a local channel - * without knowing an extension to transfer it to. - * XXX However, when parking lots are changed to be able to register extensions then this will be doable. */ - ast_log(LOG_ERROR, "Can not one-touch park a multiparty bridge.\n"); - return 0; - } - - transfer_chan = park_local_transfer(bridge_channel->chan, - ast_get_extension_name(park_exten), ast_get_context_name(ast_get_extension_context(park_exten))); - + transfer_chan = park_local_transfer(bridge_channel->chan, context, exten); if (!transfer_chan) { - return 0; + return -1; } if (ast_bridge_impart(bridge_channel->bridge, transfer_chan, NULL, NULL, 1)) { ast_hangup(transfer_chan); + return -1; } - return 0; } - /* Since neither of the above cases were used, we are doing a simple park with a two party bridge. */ - - for (iter = ao2_iterator_init(bridge_peers, 0); (other = ao2_iterator_next(&iter)); ao2_ref(other, -1)) { - /* We need the channel that isn't the bridge_channel's channel. */ - if (strcmp(ast_channel_uniqueid(other), ast_channel_uniqueid(bridge_channel->chan))) { - break; - } - } - ao2_iterator_destroy(&iter); - - if (!other) { - ast_assert(0); - return -1; - } - /* Subscribe to park messages with the other channel entering */ - if (create_parked_subscription(bridge_channel->chan, ast_channel_uniqueid(other))) { + if (create_parked_subscription(bridge_channel->chan, ast_channel_uniqueid(other->chan))) { return -1; } /* Write the park frame with the intended recipient and other data out to the bridge. */ - ast_bridge_channel_write_park(bridge_channel, ast_channel_uniqueid(other), ast_channel_uniqueid(bridge_channel->chan), ast_get_extension_app_data(park_exten)); + ast_bridge_channel_write_park(bridge_channel, + ast_channel_uniqueid(other->chan), + ast_channel_uniqueid(bridge_channel->chan), + NULL); return 0; } -static void park_bridge_channel(struct ast_bridge_channel *bridge_channel, const char *uuid_parkee, const char *uuid_parker, const char *app_data) + +/*! + * \internal + * \since 12.0.0 + * \brief Perform a direct park on a channel in a bridge + * + * \note This will be called from within the \ref AstBridging Bridging API + * + * \param bridge_channel The bridge_channel representing the channel to be parked + * \param uuid_parkee The UUID of the channel being parked + * \param uuid_parker The UUID of the channel performing the park + * \param app_data Application parseable data to pass to the parking application + */ +static int parking_park_bridge_channel(struct ast_bridge_channel *bridge_channel, const char *uuid_parkee, const char *uuid_parker, const char *app_data) { RAII_VAR(struct ast_bridge *, parking_bridge, NULL, ao2_cleanup); RAII_VAR(struct ast_bridge *, original_bridge, NULL, ao2_cleanup); @@ -321,7 +364,7 @@ static void park_bridge_channel(struct ast_bridge_channel *bridge_channel, const if (strcmp(ast_channel_uniqueid(bridge_channel->chan), uuid_parkee)) { /* We aren't the parkee, so ignore this action. */ - return; + return -1; } parker = ast_channel_get_by_name(uuid_parker); @@ -329,12 +372,12 @@ static void park_bridge_channel(struct ast_bridge_channel *bridge_channel, const if (!parker) { ast_log(LOG_NOTICE, "Channel with uuid %s left before we could start parking the call. Parking canceled.\n", uuid_parker); publish_parked_call_failure(bridge_channel->chan); - return; + return -1; } if (!(parking_bridge = park_application_setup(bridge_channel->chan, parker, app_data, NULL))) { publish_parked_call_failure(bridge_channel->chan); - return; + return -1; } pbx_builtin_setvar_helper(bridge_channel->chan, "BLINDTRANSFER", ast_channel_name(parker)); @@ -346,7 +389,7 @@ static void park_bridge_channel(struct ast_bridge_channel *bridge_channel, const if (!original_bridge) { ao2_unlock(bridge_channel); publish_parked_call_failure(bridge_channel->chan); - return; + return -1; } ao2_ref(original_bridge, +1); /* Cleaned by RAII_VAR */ @@ -356,13 +399,60 @@ static void park_bridge_channel(struct ast_bridge_channel *bridge_channel, const if (ast_bridge_move(parking_bridge, original_bridge, bridge_channel->chan, NULL, 1)) { ast_log(LOG_ERROR, "Failed to move %s into the parking bridge.\n", ast_channel_name(bridge_channel->chan)); + return -1; } + + return 0; } -static int feature_park(struct ast_bridge_channel *bridge_channel, void *hook_pvt) +/*! + * \internal + * \since 12.0.0 + * \brief Park a call + * + * \param parker The bridge_channel parking the call + * \param exten Optional. The extension where the call was parked. + * \param length Optional. If \c exten is specified, the length of the buffer. + * + * \note This will determine the context and extension to park the channel based on + * the configuration of the \ref ast_channel associated with \ref parker. It will then + * park either the channel or the entire bridge. + * + * \retval 0 on success + * \retval -1 on error + */ +static int parking_park_call(struct ast_bridge_channel *parker, char *exten, size_t length) { - park_feature_helper(bridge_channel, NULL); - return 0; + RAII_VAR(struct parking_lot *, lot, NULL, ao2_cleanup); + const char *lot_name = NULL; + + ast_channel_lock(parker->chan); + lot_name = find_channel_parking_lot_name(parker->chan); + if (!ast_strlen_zero(lot_name)) { + lot_name = ast_strdupa(lot_name); + } + ast_channel_unlock(parker->chan); + + if (ast_strlen_zero(lot_name)) { + return -1; + } + + lot = parking_lot_find_by_name(lot_name); + if (!lot) { + ast_log(AST_LOG_WARNING, "Cannot Park %s: lot %s unknown\n", + ast_channel_name(parker->chan), lot_name); + return -1; + } + + if (exten) { + ast_copy_string(exten, lot->cfg->parkext, length); + } + return parking_blind_transfer_park(parker, lot->cfg->parking_con, lot->cfg->parkext); +} + +static int feature_park_call(struct ast_bridge_channel *bridge_channel, void *hook_pvt) +{ + return parking_park_call(bridge_channel, NULL, 0); } /*! \internal @@ -524,17 +614,27 @@ void parking_set_duration(struct ast_bridge_features *features, struct parked_us } } +struct ast_parking_bridge_feature_fn_table parking_provider = { + .module_version = PARKING_MODULE_VERSION, + .module_name = __FILE__, + .parking_is_exten_park = parking_is_exten_park, + .parking_blind_transfer_park = parking_blind_transfer_park, + .parking_park_bridge_channel = parking_park_bridge_channel, + .parking_park_call = parking_park_call, +}; + void unload_parking_bridge_features(void) { ast_bridge_features_unregister(AST_BRIDGE_BUILTIN_PARKCALL); - ast_uninstall_park_blind_xfer_func(); - ast_uninstall_bridge_channel_park_func(); + ast_parking_unregister_bridge_features(parking_provider.module_name); } int load_parking_bridge_features(void) { - ast_bridge_features_register(AST_BRIDGE_BUILTIN_PARKCALL, feature_park, NULL); - ast_install_park_blind_xfer_func(park_feature_helper); - ast_install_bridge_channel_park_func(park_bridge_channel); + if (ast_parking_register_bridge_features(&parking_provider)) { + return -1; + } + + ast_bridge_features_register(AST_BRIDGE_BUILTIN_PARKCALL, feature_park_call, NULL); return 0; } diff --git a/res/res_parking.c b/res/res_parking.c index 9a13db0989..6efb795434 100644 --- a/res/res_parking.c +++ b/res/res_parking.c @@ -1140,19 +1140,18 @@ static void link_configured_disable_marked_lots(void) static int load_module(void) { - if (aco_info_init(&cfg_info)) { - goto error; - } - parking_lot_container = ao2_container_alloc_list(AO2_ALLOC_OPT_LOCK_MUTEX, AO2_CONTAINER_ALLOC_OPT_DUPS_REJECT, parking_lot_sort_fn, NULL); - if (!parking_lot_container) { goto error; } + if (aco_info_init(&cfg_info)) { + goto error; + } + /* Global options */ aco_option_register(&cfg_info, "parkeddynamic", ACO_EXACT, global_options, "no", OPT_BOOL_T, 1, FLDSET(struct parking_global_config, parkeddynamic));