2 * Asterisk -- An open source telephony toolkit.
4 * Copyright (C) 2013, Digium, Inc.
6 * Jonathan Rose <jrose@digium.com>
8 * See http://www.asterisk.org for more information about
9 * the Asterisk project. Please do not directly contact
10 * any of the maintainers of this project for assistance;
11 * the project provides a web site, mailing lists and IRC
12 * channels for your use.
14 * This program is free software, distributed under the terms of
15 * the GNU General Public License Version 2. See the LICENSE file
16 * at the top of the source tree.
21 * \brief Call Parking Resource
23 * \author Jonathan Rose <jrose@digium.com>
27 <depend>bridge_holding</depend>
28 <support_level>core</support_level>
32 <configInfo name="res_parking" language="en_US">
33 <configFile name="res_parking.conf">
34 <configObject name="globals">
35 <synopsis>Options that apply to every parking lot</synopsis>
37 <configObject name="parking_lot">
38 <synopsis>Defined parking lots for res_parking to use to park calls on</synopsis>
39 <configOption name="context" default="parkedcalls">
40 <synopsis>The name of the context where calls are parked and picked up from.</synopsis>
41 <description><para>This option is only used if parkext is set.</para></description>
43 <configOption name="parkext">
44 <synopsis>Extension to park calls to this parking lot.</synopsis>
45 <description><para>If this option is used, this extension will automatically be created to place calls into
46 parking lots. In addition, if parkext_exclusive is set for this parking lot, the name of the parking lot
47 will be included in the application's arguments so that it only parks to this parking lot. The extension
48 will be created in <literal>context</literal>. Using this option also creates extensions for retrieving
49 parked calls from the parking spaces in the same context.</para></description>
51 <configOption name="parkext_exclusive" default="no">
52 <synopsis>If yes, the extension registered as parkext will park exclusively to this parking lot.</synopsis>
54 <configOption name="parkpos" default="701-750">
55 <synopsis>Numerical range of parking spaces which can be used to retrieve parked calls.</synopsis>
56 <description><para>If parkext is set, these extensions will automatically be mapped in <literal>context</literal>
57 in order to pick up calls parked to these parking spaces.</para></description>
59 <configOption name="parkinghints" default="no">
60 <synopsis>If yes, this parking lot will add hints automatically for parking spaces.</synopsis>
62 <configOption name="parkingtime" default="45">
63 <synopsis>Amount of time a call will remain parked before giving up (in seconds).</synopsis>
65 <configOption name="parkedmusicclass">
66 <synopsis>Which music class to use for parked calls. They will use the default if unspecified.</synopsis>
68 <configOption name="comebacktoorigin" default="yes">
69 <synopsis>Determines what should be done with the parked channel if no one picks it up before it times out.</synopsis>
70 <description><para>Valid Options:</para>
73 <para>Automatically have the parked channel dial the device that parked the call with dial
74 timeout set by the <literal>parkingtime</literal> option. When the call times out an extension
75 to dial the PARKER will automatically be created in the <literal>park-dial</literal> context with
76 an extension of the flattened parker device name. If the call is not answered, the parked channel
77 that is timing out will continue in the dial plan at that point if there are more priorities in
78 the extension (which won't be the case unless the dialplan deliberately includes such priorities
79 in the <literal>park-dial</literal> context through pattern matching or deliberately written
80 flattened peer extensions).</para>
83 <para>Place the call into the PBX at <literal>comebackcontext</literal> instead. The extension will
84 still be set as the flattened peer name. If an extension the flattened peer name isn't available
85 then it will fall back to the <literal>s</literal> extension. If that also is unavailable it will
86 attempt to fall back to <literal>s@default</literal>. The normal dial extension will still be
87 created in the <literal>park-dial</literal> context with the extension also being the flattened
91 <note><para>Flattened Peer Names - Extensions can not include slash characters since those are used for pattern
92 matching. When a peer name is flattened, slashes become underscores. For example if the parker of a call
93 is called <literal>SIP/0004F2040001</literal> then flattened peer name and therefor the extensions created
94 and used on timeouts will be <literal>SIP_0004F204001</literal>.</para></note>
95 <note><para>When parking times out and the channel returns to the dial plan, the following variables are set:
98 <variable name="PARKINGSLOT">
99 <para>extension that the call was parked in prior to timing out.</para>
101 <variable name="PARKEDLOT">
102 <para>name of the lot that the call was parked in prior to timing out.</para>
104 <variable name="PARKER">
105 <para>The device that parked the call</para>
110 <configOption name="comebackdialtime" default="30">
111 <synopsis>Timeout for the Dial extension created to call back the parker when a parked call times out.</synopsis>
113 <configOption name="comebackcontext" default="parkedcallstimeout">
114 <synopsis>Context where parked calls will enter the PBX on timeout when comebacktoorigin=no</synopsis>
115 <description><para>The extension the call enters will prioritize the flattened peer name in this context.
116 If the flattened peer name extension is unavailable, then the 's' extension in this context will be
117 used. If that also is unavailable, the 's' extension in the 'default' context will be used.</para>
120 <configOption name="courtesytone">
121 <synopsis>If the name of a sound file is provided, use this as the courtesy tone</synopsis>
122 <description><para>By default, this tone is only played to the caller of a parked call. Who receives the tone
123 can be changed using the <literal>parkedplay</literal> option.</para>
126 <configOption name="parkedplay" default="caller">
127 <synopsis>Who we should play the courtesytone to on the pickup of a parked call from this lot</synopsis>
130 <enum name="no"><para>Apply to neither side.</para></enum>
131 <enum name="caller"><para>Apply to only to the caller picking up the parked call.</para></enum>
132 <enum name="callee"><para>Apply to only to the parked call being picked up.</para></enum>
133 <enum name="both"><para>Apply to both the caller and the callee.</para></enum>
135 <note><para>If courtesy tone is not specified then this option will be ignored.</para></note>
138 <configOption name="parkedcalltransfers" default="no">
139 <synopsis>Apply the DTMF transfer features to the caller and/or callee when parked calls are picked up.</synopsis>
141 <xi:include xpointer="xpointer(/docs/configInfo[@name='res_parking']/configFile[@name='res_parking.conf']/configObject[@name='parking_lot']/configOption[@name='parkedplay']/description/enumlist)" />
144 <configOption name="parkedcallreparking" default="no">
145 <synopsis>Apply the DTMF parking feature to the caller and/or callee when parked calls are picked up.</synopsis>
147 <xi:include xpointer="xpointer(/docs/configInfo[@name='res_parking']/configFile[@name='res_parking.conf']/configObject[@name='parking_lot']/configOption[@name='parkedplay']/description/enumlist)" />
150 <configOption name="parkedcallhangup" default="no">
151 <synopsis>Apply the DTMF Hangup feature to the caller and/or callee when parked calls are picked up.</synopsis>
153 <xi:include xpointer="xpointer(/docs/configInfo[@name='res_parking']/configFile[@name='res_parking.conf']/configObject[@name='parking_lot']/configOption[@name='parkedplay']/description/enumlist)" />
156 <configOption name="parkedcallrecording" default="no">
157 <synopsis>Apply the DTMF recording features to the caller and/or callee when parked calls are picked up</synopsis>
159 <xi:include xpointer="xpointer(/docs/configInfo[@name='res_parking']/configFile[@name='res_parking.conf']/configObject[@name='parking_lot']/configOption[@name='parkedplay']/description/enumlist)" />
162 <configOption name="findslot" default="first">
163 <synopsis>Rule to use when trying to figure out which parking space a call should be parked with.</synopsis>
166 <enum name="first"><para>Always try to place in the lowest available space in the parking lot</para></enum>
167 <enum name="next"><para>Track the last parking space used and always attempt to use the one immediately after.
172 <configOption name="courtesytone">
173 <synopsis>If set, the sound set will be played to whomever is set by parkedplay</synopsis>
180 #include "asterisk.h"
182 ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
184 #include "parking/res_parking.h"
185 #include "asterisk/config.h"
186 #include "asterisk/config_options.h"
187 #include "asterisk/event.h"
188 #include "asterisk/utils.h"
189 #include "asterisk/module.h"
190 #include "asterisk/cli.h"
191 #include "asterisk/astobj2.h"
192 #include "asterisk/features.h"
193 #include "asterisk/manager.h"
194 #include "asterisk/pbx.h"
196 #define PARKED_CALL_APPLICATION "ParkedCall"
197 #define PARK_AND_ANNOUNCE_APPLICATION "ParkAndAnnounce"
199 /* TODO Add unit tests for parking */
201 static int parking_lot_sort_fn(const void *obj_left, const void *obj_right, int flags)
203 const struct parking_lot *left = obj_left;
204 const struct parking_lot *right = obj_right;
205 const char *right_key = obj_right;
208 switch (flags & (OBJ_POINTER | OBJ_KEY | OBJ_PARTIAL_KEY)) {
211 right_key = right->name;
214 cmp = strcmp(left->name, right_key);
216 case OBJ_PARTIAL_KEY:
217 cmp = strncmp(left->name, right_key, strlen(right_key));
222 /*! All parking lots that are currently alive in some fashion can be obtained from here */
223 static struct ao2_container *parking_lot_container;
225 static void *parking_config_alloc(void);
227 static void *parking_lot_cfg_alloc(const char *cat);
228 static void *named_item_find(struct ao2_container *container, const char *name); /* XXX This is really just a generic string find. Move to astobj2.c? */
230 static int config_parking_preapply(void);
231 static void link_configured_disable_marked_lots(void);
233 struct parking_global_config {
234 /* TODO Implement dynamic parking lots. Entirely. */
238 struct parking_config {
239 struct parking_global_config *global;
240 struct ao2_container *parking_lots;
243 static struct aco_type global_option = {
246 .item_offset = offsetof(struct parking_config, global),
247 .category_match = ACO_WHITELIST,
248 .category = "^general$",
251 struct aco_type *global_options[] = ACO_TYPES(&global_option);
253 static struct aco_type parking_lot_type = {
255 .name = "parking_lot",
256 .category_match = ACO_BLACKLIST,
257 .category = "^(general)$",
258 .item_alloc = parking_lot_cfg_alloc,
259 .item_find = named_item_find,
260 .item_offset = offsetof(struct parking_config, parking_lots),
263 struct aco_type *parking_lot_types[] = ACO_TYPES(&parking_lot_type);
265 struct aco_file parking_lot_conf = {
266 .filename = "res_parking.conf",
267 .types = ACO_TYPES(&global_option, &parking_lot_type),
270 static AO2_GLOBAL_OBJ_STATIC(globals);
272 CONFIG_INFO_STANDARD(cfg_info, globals, parking_config_alloc,
273 .files = ACO_FILES(&parking_lot_conf),
274 .pre_apply_config = config_parking_preapply,
275 .post_apply_config = link_configured_disable_marked_lots,
278 static int parking_lot_cfg_hash_fn(const void *obj, const int flags)
280 const struct parking_lot_cfg *entry;
283 switch (flags & (OBJ_POINTER | OBJ_KEY | OBJ_PARTIAL_KEY)) {
286 return ast_str_hash(key);
287 case OBJ_PARTIAL_KEY:
292 return ast_str_hash(entry->name);
296 static int parking_lot_cfg_cmp_fn(void *obj, void *arg, const int flags)
298 struct parking_lot_cfg *entry1 = obj;
302 struct parking_lot_cfg *entry2;
304 switch (flags & (OBJ_POINTER | OBJ_KEY | OBJ_PARTIAL_KEY)) {
307 return (!strcmp(entry1->name, key)) ? CMP_MATCH : 0;
308 case OBJ_PARTIAL_KEY:
310 key_size = strlen(key);
311 return (!strncmp(entry1->name, key, key_size)) ? CMP_MATCH : 0;
314 return (!strcmp(entry1->name, entry2->name)) ? CMP_MATCH : 0;
320 /*! \brief destructor for parking_config */
321 static void parking_config_destructor(void *obj)
323 struct parking_config *cfg = obj;
324 ao2_cleanup(cfg->parking_lots);
325 ao2_cleanup(cfg->global);
328 /*! \brief destructor for parking_global_config */
329 static void parking_global_config_destructor(void *obj)
331 /* For now, do nothing. */
334 /*! \brief allocator callback for parking_config. Notice it returns void * since it is only used by the backend config code */
335 static void *parking_config_alloc(void)
337 RAII_VAR(struct parking_config *, cfg, NULL, ao2_cleanup);
339 if (!(cfg = ao2_alloc(sizeof(*cfg), parking_config_destructor))) {
343 if (!(cfg->parking_lots = ao2_container_alloc(37, parking_lot_cfg_hash_fn, parking_lot_cfg_cmp_fn))) {
347 if (!(cfg->global = ao2_alloc(sizeof(*cfg->global), parking_global_config_destructor))) {
351 /* Bump the ref count since RAII_VAR is going to eat one */
356 void parking_lot_remove_if_unused(struct parking_lot *lot)
359 if (lot->mode != PARKINGLOT_DISABLED) {
364 if (!ao2_container_count(lot->parked_users)) {
365 ao2_unlink(parking_lot_container, lot);
369 static void parking_lot_disable(struct parking_lot *lot)
371 lot->mode = PARKINGLOT_DISABLED;
372 parking_lot_remove_if_unused(lot);
375 /*! \brief Destroy a parking lot cfg object */
376 static void parking_lot_cfg_destructor(void *obj)
378 struct parking_lot_cfg *lot_cfg = obj;
379 parking_lot_cfg_remove_extensions(lot_cfg);
380 ast_string_field_free_memory(lot_cfg);
383 /* The arg just needs to have the parking space with it */
384 static int parked_user_cmp_fn(void *obj, void *arg, int flags)
386 int *search_space = arg;
387 struct parked_user *user = obj;
388 int object_space = user->parking_space;
390 if (*search_space == object_space) {
396 static int parked_user_sort_fn(const void *obj_left, const void *obj_right, int flags)
398 const struct parked_user *left = obj_left;
399 const struct parked_user *right = obj_right;
401 return left->parking_space - right->parking_space;
405 * \brief create a parking lot structure
406 * \param cat name given to the parking lot
407 * \retval NULL failure
408 * \retval non-NULL successfully allocated parking lot
410 static void *parking_lot_cfg_alloc(const char *cat)
412 struct parking_lot_cfg *lot_cfg;
414 lot_cfg = ao2_alloc(sizeof(*lot_cfg), parking_lot_cfg_destructor);
419 if (ast_string_field_init(lot_cfg, 32)) {
420 ao2_cleanup(lot_cfg);
424 ast_string_field_set(lot_cfg, name, cat);
430 * XXX This is actually incredibly generic and might be better placed in something like astobj2 if there isn't already an equivalent
431 * \brief find an item in a container by its name
433 * \param container ao2container where we want the item from
434 * \param key name of the item wanted to be found
436 * \retval pointer to the parking lot if available. NULL if not found.
438 static void *named_item_find(struct ao2_container *container, const char *name)
440 return ao2_find(container, name, OBJ_KEY);
444 * \brief Custom field handler for parking positions
446 static int option_handler_parkpos(const struct aco_option *opt, struct ast_variable *var, void *obj)
448 struct parking_lot_cfg *lot_cfg = obj;
452 if (sscanf(var->value, "%30d-%30d", &low, &high) != 2) {
453 ast_log(LOG_WARNING, "Format for parking positions is a-b, where a and b are numbers\n");
454 } else if (high < low || low <= 0 || high <= 0) {
455 ast_log(LOG_WARNING, "Format for parking positions is a-b, where a <= b\n");
457 lot_cfg->parking_start = low;
458 lot_cfg->parking_stop = high;
465 * \brief Custom field handler for the findslot option
467 static int option_handler_findslot(const struct aco_option *opt, struct ast_variable *var, void *obj)
469 struct parking_lot_cfg *lot_cfg = obj;
471 if (!strcmp(var->value, "first")) {
472 lot_cfg->parkfindnext = 0;
473 } else if (!strcmp(var->value, "next")) {
474 lot_cfg->parkfindnext = 1;
476 ast_log(LOG_WARNING, "value '%s' is not valid for findslot option.\n", var->value);
484 * \brief Maps string values for option_handler_parkedfeature to their ENUM values
486 static int parking_feature_flag_cfg(int *param, const char *var)
488 if (ast_false(var)) {
490 } else if (!strcasecmp(var, "both")) {
491 *param = AST_FEATURE_FLAG_BYBOTH;
492 } else if (!strcasecmp(var, "caller")) {
493 *param = AST_FEATURE_FLAG_BYCALLER;
494 } else if (!strcasecmp(var, "callee")) {
495 *param = AST_FEATURE_FLAG_BYCALLEE;
504 * \brief Custom field handler for feature mapping on parked call pickup options
506 static int option_handler_parkedfeature(const struct aco_option *opt, struct ast_variable *var, void *obj)
508 struct parking_lot_cfg *cfg = obj;
509 enum parked_call_feature_options option = aco_option_get_flags(opt);
510 int *parameter = NULL;
514 parameter = &cfg->parkedplay;
516 case OPT_PARKEDTRANSFERS:
517 parameter = &cfg->parkedcalltransfers;
519 case OPT_PARKEDREPARKING:
520 parameter = &cfg->parkedcallreparking;
522 case OPT_PARKEDHANGUP:
523 parameter = &cfg->parkedcallhangup;
525 case OPT_PARKEDRECORDING:
526 parameter = &cfg->parkedcallrecording;
531 ast_log(LOG_ERROR, "Unable to handle option '%s'\n", var->name);
535 if (parking_feature_flag_cfg(parameter, var->value)) {
536 ast_log(LOG_ERROR, "'%s' is not a valid value for parking lot option '%s'\n", var->value, var->name);
543 struct ao2_container *get_parking_lot_container(void)
545 return parking_lot_container;
548 struct parking_lot *parking_lot_find_by_name(const char *lot_name)
550 struct parking_lot *lot = named_item_find(parking_lot_container, lot_name);
554 const char *find_channel_parking_lot_name(struct ast_channel *chan)
558 /* The channel variable overrides everything */
559 name = pbx_builtin_getvar_helper(chan, "PARKINGLOT");
560 if (ast_strlen_zero(name) && !ast_strlen_zero(ast_channel_parkinglot(chan))) {
561 /* Use the channel's parking lot. */
562 name = ast_channel_parkinglot(chan);
565 /* If the name couldn't be pulled from that either, use the default parking lot name. */
566 if (ast_strlen_zero(name)) {
567 name = DEFAULT_PARKING_LOT;
573 static void parking_lot_destructor(void *obj)
575 struct parking_lot *lot = obj;
577 if (lot->parking_bridge) {
578 ast_bridge_destroy(lot->parking_bridge);
580 ao2_cleanup(lot->parked_users);
581 ao2_cleanup(lot->cfg);
582 ast_string_field_free_memory(lot);
585 static struct parking_lot *alloc_new_parking_lot(struct parking_lot_cfg *lot_cfg)
587 struct parking_lot *lot;
588 if (!(lot = ao2_alloc(sizeof(*lot), parking_lot_destructor))) {
592 if (ast_string_field_init(lot, 32)) {
596 /* Create parked user ordered list */
597 lot->parked_users = ao2_container_alloc_list(AO2_ALLOC_OPT_LOCK_RWLOCK,
598 AO2_CONTAINER_ALLOC_OPT_DUPS_REJECT,
602 if (!lot->parked_users) {
607 ast_string_field_set(lot, name, lot_cfg->name);
611 void parking_lot_cfg_remove_extensions(struct parking_lot_cfg *lot_cfg)
613 if (!ast_strlen_zero(lot_cfg->registrar)) {
614 /* Although the function is called ast_context_destroy, the use of this funtion is
615 * intended only to remove extensions, hints, etc registered by the parking lot's registrar.
616 * It won't actually destroy the context unless that context is empty afterwards and it is
619 ast_context_destroy(NULL, lot_cfg->registrar);
623 static void remove_all_configured_parking_lot_extensions(void)
625 RAII_VAR(struct parking_config *, cfg, ao2_global_obj_ref(globals), ao2_cleanup);
626 struct parking_lot_cfg *lot_cfg;
627 struct ao2_iterator iter;
633 for (iter = ao2_iterator_init(cfg->parking_lots, 0); (lot_cfg = ao2_iterator_next(&iter)); ao2_ref(lot_cfg, -1)) {
634 parking_lot_cfg_remove_extensions(lot_cfg);
637 ast_context_destroy(NULL, BASE_REGISTRAR);
639 ao2_iterator_destroy(&iter);
645 * \brief Create an extension using ast_add_extension2_nolock. This function automatically allocates a duplicate
646 * of the data string so that whatever calls it doesn't have to deal with freeing it if the ast_add_extension2_nolock
649 * \param context a write locked ast_context. Make certain it is write locked prior to calling this function
650 * \param replace whether the extension should replace existing extensions
651 * \param extension name of the extension desired
652 * \param priority priority of the extension we are registering
653 * \param application name of the application being used for the extension
654 * \param data application arguments
655 * \param registrar name of the registrar you should use for the extension.
656 * Make sure this string doesn't go anywhere while there are still extensions using it.
658 static int parking_add_extension(struct ast_context *context, int replace, const char *extension,
659 int priority, const char *application, const char *data, const char *registrar)
661 char *data_duplicate = ast_strdup(data);
663 if (!data_duplicate) {
667 if (ast_add_extension2_nolock(context, replace, extension, priority, NULL, NULL,
668 application, data_duplicate, ast_free_ptr, registrar)) {
669 ast_free(data_duplicate);
676 static int extension_is_compatible(struct parking_lot_cfg *lot_cfg, const char *app_type, struct ast_exten *extension)
678 RAII_VAR(struct parking_lot_cfg *, owner, NULL, ao2_cleanup);
679 const char *extension_registrar = ast_get_extension_registrar(extension);
680 const char *extension_context = ast_get_context_name(ast_get_extension_context(extension));
681 const char *extension_name = ast_get_extension_name(extension);
682 const char *extension_application = ast_get_extension_app(extension);
684 ast_assert(extension_registrar && extension_context && extension_name && extension_application);
686 if (strcmp(extension_registrar, BASE_REGISTRAR)) {
687 ast_log(LOG_ERROR, "Parking lot '%s' -- Needs an extension '%s@%s', but that extension is already owned by %s.\n",
688 lot_cfg->name, extension_name, extension_context, extension_registrar);
692 if (strcmp(extension_application, app_type)) {
693 ast_log(LOG_ERROR, "Parking lot '%s' -- Needs an extension '%s@%s' with a non-exclusive %s application, "
694 "but a/an %s application is already registered to that extension by %s.\n",
695 lot_cfg->name, extension_name, extension_context, app_type,
696 extension_application, BASE_REGISTRAR);
700 ast_debug(3, "Parking lot '%s' -- extension '%s@%s' with application %s is compatible.\n",
701 lot_cfg->name, extension_name, extension_context, app_type);
705 int parking_lot_cfg_create_extensions(struct parking_lot_cfg *lot_cfg)
708 struct ast_exten *existing_exten;
709 struct ast_context *lot_context;
710 struct pbx_find_info find_info = { .stacklen = 0 }; /* the rest is reset in pbx_find_extension */
711 const char *registrar_pointer;
713 if (ast_strlen_zero(lot_cfg->parkext)) {
717 if (lot_cfg->parkext_exclusive) {
718 ast_string_field_build(lot_cfg, registrar, "%s/%s", BASE_REGISTRAR, lot_cfg->name);
719 registrar_pointer = lot_cfg->registrar;
721 registrar_pointer = BASE_REGISTRAR;
724 /* We need the contexts list locked to safely be able to both read and lock the specific context within */
725 if (ast_wrlock_contexts()) {
726 ast_log(LOG_ERROR, "Failed to lock the contexts list.\n");
730 if (!(lot_context = ast_context_find_or_create(NULL, NULL, lot_cfg->parking_con, registrar_pointer))) {
731 ast_log(LOG_ERROR, "Parking lot '%s' -- Needs a context '%s' which does not exist and Asterisk was unable to create\n",
732 lot_cfg->name, lot_cfg->parking_con);
733 if (ast_unlock_contexts()) {
739 /* Once we know what context we will be modifying, we need to write lock it because we will be reading extensions
740 * and we don't want something else to destroy them while we are looking at them.
742 if (ast_wrlock_context(lot_context)) {
743 ast_log(LOG_ERROR, "failed to obtain write lock on context\n");
747 if (ast_unlock_contexts()) {
751 /* Handle generation/confirmation for the Park extension */
752 if ((existing_exten = pbx_find_extension(NULL, NULL, &find_info, lot_cfg->parking_con, lot_cfg->parkext, 1, NULL, NULL, E_MATCH))) {
753 if (lot_cfg->parkext_exclusive || !extension_is_compatible(lot_cfg, PARK_APPLICATION, existing_exten)) {
754 ast_unlock_context(lot_context);
757 } else if (parking_add_extension(lot_context, 0, lot_cfg->parkext, 1, PARK_APPLICATION,
758 lot_cfg->parkext_exclusive ? lot_cfg->name : "", registrar_pointer)) {
759 ast_log(LOG_ERROR, "Parking lot '%s' -- Failed to add %s extension '%s@%s' to the PBX.\n",
760 lot_cfg->name, PARK_APPLICATION, lot_cfg->parkext, lot_cfg->parking_con);
761 ast_unlock_context(lot_context);
765 /* Handle generation/confirmation for the ParkedCall extensions and hints */
766 for (parkingspace = lot_cfg->parking_start; parkingspace <= lot_cfg->parking_stop; parkingspace++) {
767 char space[AST_MAX_EXTENSION];
768 RAII_VAR(struct ast_str *, arguments_string, NULL, ast_free);
769 find_info.stacklen = 0; /* reset for pbx_find_exten */
771 snprintf(space, sizeof(space), "%d", parkingspace);
773 /* Unlike the Park extensions, ParkedCall extensions and their hints may never be shared for any reason. */
774 if ((existing_exten = pbx_find_extension(NULL, NULL, &find_info, lot_cfg->parking_con, space, 1, NULL, NULL, E_MATCH))) {
775 ast_unlock_context(lot_context);
779 arguments_string = ast_str_create(32);
780 if (!arguments_string) {
781 ast_unlock_context(lot_context);
785 ast_str_set(&arguments_string, 0, "%s,%s", lot_cfg->name, space);
786 if (parking_add_extension(lot_context, 0, space, 1, PARKED_CALL_APPLICATION,
787 ast_str_buffer(arguments_string), registrar_pointer)) {
788 ast_log(LOG_ERROR, "Parking lot '%s' -- Failed to add %s extension '%s@%s' to the PBX.\n",
789 lot_cfg->name, PARKED_CALL_APPLICATION, space, lot_cfg->parking_con);
790 ast_unlock_context(lot_context);
794 find_info.stacklen = 0; /* reset for pbx_find_exten */
796 if (lot_cfg->parkaddhints) {
797 char hint_device[AST_MAX_EXTENSION];
799 snprintf(hint_device, sizeof(hint_device), "park:%s@%s", space, lot_cfg->parking_con);
801 if ((existing_exten = pbx_find_extension(NULL, NULL, &find_info, lot_cfg->parking_con, space, PRIORITY_HINT, NULL, NULL, E_MATCH))) {
802 ast_log(LOG_ERROR, "Parking lot '%s' -- Needs to add a hint '%s' at '%s@%s' but one already exists owned by %s\n",
803 lot_cfg->name, hint_device, space, lot_cfg->parking_con, ast_get_extension_registrar(existing_exten));
804 ast_unlock_context(lot_context);
808 if (parking_add_extension(lot_context, 0, space, PRIORITY_HINT, hint_device, "", registrar_pointer)) {
809 ast_log(LOG_ERROR, "Parking lot '%s' -- Failed to add hint '%s@%s' to the PBX.\n",
810 lot_cfg->name, space, lot_cfg->parking_con);
811 ast_unlock_context(lot_context);
817 if (ast_unlock_context(lot_context)) {
824 struct parking_lot *parking_lot_build_or_update(struct parking_lot_cfg *lot_cfg)
826 struct parking_lot *lot;
827 struct parking_lot_cfg *replaced_cfg = NULL;
830 /* Start by trying to find it. If that works we can skip the rest. */
831 lot = named_item_find(parking_lot_container, lot_cfg->name);
833 lot = alloc_new_parking_lot(lot_cfg);
835 /* If we still don't have a lot, we failed to alloc one. */
843 /* Set the configuration reference. Unref the one currently in the lot if it's there. */
845 replaced_cfg = lot->cfg;
848 ao2_ref(lot_cfg, +1);
851 ao2_cleanup(replaced_cfg);
853 /* Set the operating mode to normal since the parking lot has a configuration. */
854 lot->disable_mark = 0;
855 lot->mode = PARKINGLOT_NORMAL;
858 /* Link after configuration is set since a lot without configuration will cause all kinds of trouble. */
859 ao2_link(parking_lot_container, lot);
865 static void generate_or_link_lots_to_configs(void)
867 RAII_VAR(struct parking_config *, cfg, ao2_global_obj_ref(globals), ao2_cleanup);
868 struct parking_lot_cfg *lot_cfg;
869 struct ao2_iterator iter;
871 for (iter = ao2_iterator_init(cfg->parking_lots, 0); (lot_cfg = ao2_iterator_next(&iter)); ao2_ref(lot_cfg, -1)) {
872 RAII_VAR(struct parking_lot *, lot, NULL, ao2_cleanup);
873 lot = parking_lot_build_or_update(lot_cfg);
876 ao2_iterator_destroy(&iter);
881 static int verify_default_parking_lot(void)
883 struct parking_config *cfg = aco_pending_config(&cfg_info);
884 RAII_VAR(struct parking_lot_cfg *, lot_cfg, NULL, ao2_cleanup);
890 lot_cfg = ao2_find(cfg->parking_lots, DEFAULT_PARKING_LOT, OBJ_KEY);
892 lot_cfg = parking_lot_cfg_alloc(DEFAULT_PARKING_LOT);
896 ast_log(AST_LOG_NOTICE, "Adding %s profile to res_parking\n", DEFAULT_PARKING_LOT);
897 aco_set_defaults(&parking_lot_type, DEFAULT_PARKING_LOT, lot_cfg);
898 ast_string_field_set(lot_cfg, parkext, DEFAULT_PARKING_EXTEN);
899 ao2_link(cfg->parking_lots, lot_cfg);
905 static void remove_pending_parking_lot_extensions(struct parking_config *cfg_pending)
907 struct parking_lot_cfg *lot_cfg;
908 struct ao2_iterator iter;
910 for (iter = ao2_iterator_init(cfg_pending->parking_lots, 0); (lot_cfg = ao2_iterator_next(&iter)); ao2_ref(lot_cfg, -1)) {
911 parking_lot_cfg_remove_extensions(lot_cfg);
914 ao2_iterator_destroy(&iter);
916 ast_context_destroy(NULL, BASE_REGISTRAR);
920 static int configure_parking_extensions(void)
922 struct parking_config *cfg = aco_pending_config(&cfg_info);
923 struct ao2_iterator iter;
924 RAII_VAR(struct parking_lot_cfg *, lot_cfg, NULL, ao2_cleanup);
931 /* Clear existing extensions */
932 remove_all_configured_parking_lot_extensions();
934 /* Attempt to build new extensions for each lot */
935 for (iter = ao2_iterator_init(cfg->parking_lots, 0); (lot_cfg = ao2_iterator_next(&iter)); ao2_ref(lot_cfg, -1)) {
936 if (parking_lot_cfg_create_extensions(lot_cfg)) {
937 ao2_cleanup(lot_cfg);
943 ao2_iterator_destroy(&iter);
946 remove_pending_parking_lot_extensions(cfg);
947 ast_log(LOG_ERROR, "Extension registration failed. Previously configured lot extensions were removed and can not be safely restored.\n");
953 static void mark_lots_as_disabled(void)
955 struct ao2_iterator iter;
956 struct parking_lot *lot;
958 for (iter = ao2_iterator_init(parking_lot_container, 0); (lot = ao2_iterator_next(&iter)); ao2_ref(lot, -1)) {
959 /* We aren't concerned with dynamic lots */
960 if (lot->mode == PARKINGLOT_DYNAMIC) {
964 lot->disable_mark = 1;
967 ao2_iterator_destroy(&iter);
970 static int config_parking_preapply(void)
972 mark_lots_as_disabled();
974 if (verify_default_parking_lot()) {
978 if (configure_parking_extensions()) {
985 static void disable_marked_lots(void)
987 struct ao2_iterator iter;
988 struct parking_lot *lot;
990 for (iter = ao2_iterator_init(parking_lot_container, 0); (lot = ao2_iterator_next(&iter)); ao2_ref(lot, -1)) {
991 if (lot->disable_mark) {
992 parking_lot_disable(lot);
996 ao2_iterator_destroy(&iter);
999 static void link_configured_disable_marked_lots(void)
1001 generate_or_link_lots_to_configs();
1002 disable_marked_lots();
1005 static int load_module(void)
1007 if (aco_info_init(&cfg_info)) {
1011 parking_lot_container = ao2_container_alloc_list(AO2_ALLOC_OPT_LOCK_RWLOCK,
1012 AO2_CONTAINER_ALLOC_OPT_DUPS_REJECT,
1013 parking_lot_sort_fn,
1016 if (!parking_lot_container) {
1020 /* Global options */
1022 /* Register the per parking lot options. */
1023 aco_option_register(&cfg_info, "parkext", ACO_EXACT, parking_lot_types, "", OPT_STRINGFIELD_T, 0, STRFLDSET(struct parking_lot_cfg, parkext));
1024 aco_option_register(&cfg_info, "context", ACO_EXACT, parking_lot_types, "parkedcalls", OPT_STRINGFIELD_T, 0, STRFLDSET(struct parking_lot_cfg, parking_con));
1025 aco_option_register(&cfg_info, "parkingtime", ACO_EXACT, parking_lot_types, "45", OPT_UINT_T, 0, FLDSET(struct parking_lot_cfg, parkingtime));
1026 aco_option_register(&cfg_info, "comebacktoorigin", ACO_EXACT, parking_lot_types, "yes", OPT_BOOL_T, 1, FLDSET(struct parking_lot_cfg, comebacktoorigin));
1027 aco_option_register(&cfg_info, "comebackcontext", ACO_EXACT, parking_lot_types, "parkedcallstimeout", OPT_STRINGFIELD_T, 0, STRFLDSET(struct parking_lot_cfg, comebackcontext));
1028 aco_option_register(&cfg_info, "comebackdialtime", ACO_EXACT, parking_lot_types, "30", OPT_UINT_T, 0, FLDSET(struct parking_lot_cfg, comebackdialtime));
1029 aco_option_register(&cfg_info, "parkedmusicclass", ACO_EXACT, parking_lot_types, "", OPT_STRINGFIELD_T, 0, STRFLDSET(struct parking_lot_cfg, mohclass));
1030 aco_option_register(&cfg_info, "parkext_exclusive", ACO_EXACT, parking_lot_types, "no", OPT_BOOL_T, 1, FLDSET(struct parking_lot_cfg, parkext_exclusive));
1031 aco_option_register(&cfg_info, "parkinghints", ACO_EXACT, parking_lot_types, "no", OPT_BOOL_T, 1, FLDSET(struct parking_lot_cfg, parkaddhints));
1032 aco_option_register(&cfg_info, "courtesytone", ACO_EXACT, parking_lot_types, "", OPT_STRINGFIELD_T, 0, STRFLDSET(struct parking_lot_cfg, courtesytone));
1034 /* More complicated parking lot options that require special handling */
1035 aco_option_register_custom(&cfg_info, "parkpos", ACO_EXACT, parking_lot_types, "701-750", option_handler_parkpos, 0);
1036 aco_option_register_custom(&cfg_info, "findslot", ACO_EXACT, parking_lot_types, "first", option_handler_findslot, 0);
1037 aco_option_register_custom(&cfg_info, "parkedplay", ACO_EXACT, parking_lot_types, "caller", option_handler_parkedfeature, OPT_PARKEDPLAY);
1038 aco_option_register_custom(&cfg_info, "parkedcalltransfers", ACO_EXACT, parking_lot_types, "no", option_handler_parkedfeature, OPT_PARKEDTRANSFERS);
1039 aco_option_register_custom(&cfg_info, "parkedcallreparking", ACO_EXACT, parking_lot_types, "no", option_handler_parkedfeature, OPT_PARKEDREPARKING);
1040 aco_option_register_custom(&cfg_info, "parkedcallhangup", ACO_EXACT, parking_lot_types, "no", option_handler_parkedfeature, OPT_PARKEDHANGUP);
1041 aco_option_register_custom(&cfg_info, "parkedcallrecording", ACO_EXACT, parking_lot_types, "no", option_handler_parkedfeature, OPT_PARKEDRECORDING);
1043 if (aco_process_config(&cfg_info, 0) == ACO_PROCESS_ERROR) {
1047 if (ast_register_application_xml(PARK_APPLICATION, park_app_exec)) {
1051 if (ast_register_application_xml(PARKED_CALL_APPLICATION, parked_call_app_exec)) {
1055 if (ast_register_application_xml(PARK_AND_ANNOUNCE_APPLICATION, park_and_announce_app_exec)) {
1059 if (load_parking_ui()) {
1063 if (load_parking_manager()) {
1067 if (load_parking_bridge_features()) {
1071 if (load_parking_devstate()) {
1075 return AST_MODULE_LOAD_SUCCESS;
1078 ao2_cleanup(parking_lot_container);
1079 aco_info_destroy(&cfg_info);
1080 return AST_MODULE_LOAD_DECLINE;
1083 static int reload_module(void)
1085 if (aco_process_config(&cfg_info, 1) == ACO_PROCESS_ERROR) {
1086 return AST_MODULE_LOAD_DECLINE;
1092 static int unload_module(void)
1094 /* XXX Parking is currently not unloadable due to the fact that it loads features which could cause
1095 * significant problems if they disappeared while a channel still had access to them.
1099 /* TODO Things we will need to do here:
1101 * destroy existing parking lots
1102 * uninstall parking related bridge features
1103 * remove extensions owned by the parking registrar
1107 AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_LOAD_ORDER, "Call Parking Resource",
1108 .load = load_module,
1109 .unload = unload_module,
1110 .reload = reload_module,