/*! \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.
/*! 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 */
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. */
}
/* 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
{
.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)
{
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
- <managerEventInstance>
- <synopsis>Raised when a call has been parked.</synopsis>
- <syntax>
- <parameter name="Exten">
- <para>The parking lot extension.</para>
- </parameter>
- <parameter name="Parkinglot">
- <para>The name of the parking lot.</para>
- </parameter>
- <parameter name="From">
- <para>The name of the channel that parked the call.</para>
- </parameter>
- </syntax>
- <see-also>
- <ref type="application">Park</ref>
- <ref type="manager">Park</ref>
- <ref type="managerEvent">ParkedCallTimeOut</ref>
- <ref type="managerEvent">ParkedCallGiveUp</ref>
- </see-also>
- </managerEventInstance>
- ***/
- 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, "<unknown>"),
- S_COR(ast_channel_caller(chan)->id.name.valid, ast_channel_caller(chan)->id.name.str, "<unknown>"),
- S_COR(ast_channel_connected(chan)->id.number.valid, ast_channel_connected(chan)->id.number.str, "<unknown>"),
- S_COR(ast_channel_connected(chan)->id.name.valid, ast_channel_connected(chan)->id.name.str, "<unknown>"),
- 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
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),
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",
.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;
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)
{
}
#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)
{
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();
* 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];
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_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;
+ }
+
+ 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);
- bridge_peers = ast_bridge_peers_nolock(bridge_channel->bridge);
+ 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 (ao2_container_count(bridge_peers) < 2) {
+ if (peer_count < 2) {
/* There is nothing to do if there is no one to park. */
- return 0;
+ return -1;
}
- 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. */
+ /* 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);
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);
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));
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 */
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;
+}
+
+/*!
+ * \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)
+{
+ 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(struct ast_bridge_channel *bridge_channel, void *hook_pvt)
+static int feature_park_call(struct ast_bridge_channel *bridge_channel, void *hook_pvt)
{
- park_feature_helper(bridge_channel, NULL);
- return 0;
+ return parking_park_call(bridge_channel, NULL, 0);
}
/*! \internal
}
}
+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;
}