Minor parking cleanup.
[asterisk/asterisk.git] / res / res_parking.c
1 /*
2  * Asterisk -- An open source telephony toolkit.
3  *
4  * Copyright (C) 2013, Digium, Inc.
5  *
6  * Jonathan Rose <jrose@digium.com>
7  *
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.
13  *
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.
17  */
18
19 /*! \file
20  *
21  * \brief Call Parking Resource
22  *
23  * \author Jonathan Rose <jrose@digium.com>
24  */
25
26 /*** MODULEINFO
27         <depend>bridge_holding</depend>
28         <support_level>core</support_level>
29  ***/
30
31 /*** DOCUMENTATION
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>
36                         </configObject>
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>
42                                 </configOption>
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>
50                                 </configOption>
51                                 <configOption name="parkext_exclusive" default="no">
52                                         <synopsis>If yes, the extension registered as parkext will park exclusively to this parking lot.</synopsis>
53                                 </configOption>
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>
58                                 </configOption>
59                                 <configOption name="parkinghints" default="no">
60                                         <synopsis>If yes, this parking lot will add hints automatically for parking spaces.</synopsis>
61                                 </configOption>
62                                 <configOption name="parkingtime" default="45">
63                                         <synopsis>Amount of time a call will remain parked before giving up (in seconds).</synopsis>
64                                 </configOption>
65                                 <configOption name="parkedmusicclass">
66                                         <synopsis>Which music class to use for parked calls. They will use the default if unspecified.</synopsis>
67                                 </configOption>
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>
71                                                 <enumlist>
72                                                         <enum name="yes">
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>
81                                                         </enum>
82                                                         <enum name="no">
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
88                                                                         peer name.</para>
89                                                         </enum>
90                                                 </enumlist>
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:
96                                                 </para></note>
97                                                 <variablelist>
98                                                         <variable name="PARKINGSLOT">
99                                                                 <para>extension that the call was parked in prior to timing out.</para>
100                                                         </variable>
101                                                         <variable name="PARKEDLOT">
102                                                                 <para>name of the lot that the call was parked in prior to timing out.</para>
103                                                         </variable>
104                                                         <variable name="PARKER">
105                                                                 <para>The device that parked the call</para>
106                                                         </variable>
107                                                 </variablelist>
108                                         </description>
109                                 </configOption>
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>
112                                 </configOption>
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>
118                                         </description>
119                                 </configOption>
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>
124                                         </description>
125                                 </configOption>
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>
128                                         <description>
129                                                 <enumlist>
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>
134                                                 </enumlist>
135                                                 <note><para>If courtesy tone is not specified then this option will be ignored.</para></note>
136                                         </description>
137                                 </configOption>
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>
140                                         <description>
141                                                 <xi:include xpointer="xpointer(/docs/configInfo[@name='res_parking']/configFile[@name='res_parking.conf']/configObject[@name='parking_lot']/configOption[@name='parkedplay']/description/enumlist)" />
142                                         </description>
143                                 </configOption>
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>
146                                         <description>
147                                                 <xi:include xpointer="xpointer(/docs/configInfo[@name='res_parking']/configFile[@name='res_parking.conf']/configObject[@name='parking_lot']/configOption[@name='parkedplay']/description/enumlist)" />
148                                         </description>
149                                 </configOption>
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>
152                                         <description>
153                                                 <xi:include xpointer="xpointer(/docs/configInfo[@name='res_parking']/configFile[@name='res_parking.conf']/configObject[@name='parking_lot']/configOption[@name='parkedplay']/description/enumlist)" />
154                                         </description>
155                                 </configOption>
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>
158                                         <description>
159                                                 <xi:include xpointer="xpointer(/docs/configInfo[@name='res_parking']/configFile[@name='res_parking.conf']/configObject[@name='parking_lot']/configOption[@name='parkedplay']/description/enumlist)" />
160                                         </description>
161                                 </configOption>
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>
164                                         <description>
165                                                 <enumlist>
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.
168                                                         </para></enum>
169                                                 </enumlist>
170                                         </description>
171                                 </configOption>
172                                 <configOption name="courtesytone">
173                                         <synopsis>If set, the sound set will be played to whomever is set by parkedplay</synopsis>
174                                 </configOption>
175                         </configObject>
176                 </configFile>
177         </configInfo>
178  ***/
179
180 #include "asterisk.h"
181
182 ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
183
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"
195
196 #define PARKED_CALL_APPLICATION "ParkedCall"
197 #define PARK_AND_ANNOUNCE_APPLICATION "ParkAndAnnounce"
198
199 static int parking_lot_sort_fn(const void *obj_left, const void *obj_right, int flags)
200 {
201         const struct parking_lot *left = obj_left;
202         const struct parking_lot *right = obj_right;
203         const char *right_key = obj_right;
204         int cmp;
205
206         switch (flags & (OBJ_POINTER | OBJ_KEY | OBJ_PARTIAL_KEY)) {
207         default:
208         case OBJ_POINTER:
209                 right_key = right->name;
210                 /* Fall through */
211         case OBJ_KEY:
212                 cmp = strcmp(left->name, right_key);
213                 break;
214         case OBJ_PARTIAL_KEY:
215                 cmp = strncmp(left->name, right_key, strlen(right_key));
216         }
217         return cmp;
218 }
219
220 /*! All parking lots that are currently alive in some fashion can be obtained from here */
221 static struct ao2_container *parking_lot_container;
222
223 static void *parking_config_alloc(void);
224
225 static void *parking_lot_cfg_alloc(const char *cat);
226 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? */
227
228 static int config_parking_preapply(void);
229 static void link_configured_disable_marked_lots(void);
230
231 struct parking_global_config {
232         int parkeddynamic;
233 };
234
235 struct parking_config {
236         struct parking_global_config *global;
237         struct ao2_container *parking_lots;
238 };
239
240 static struct aco_type global_option = {
241         .type = ACO_GLOBAL,
242         .name = "globals",
243         .item_offset = offsetof(struct parking_config, global),
244         .category_match = ACO_WHITELIST,
245         .category = "^general$",
246 };
247
248 struct aco_type *global_options[] = ACO_TYPES(&global_option);
249
250 static struct aco_type parking_lot_type = {
251         .type = ACO_ITEM,
252         .name = "parking_lot",
253         .category_match = ACO_BLACKLIST,
254         .category = "^(general)$",
255         .item_alloc = parking_lot_cfg_alloc,
256         .item_find = named_item_find,
257         .item_offset = offsetof(struct parking_config, parking_lots),
258 };
259
260 struct aco_type *parking_lot_types[] = ACO_TYPES(&parking_lot_type);
261
262 struct aco_file parking_lot_conf = {
263         .filename = "res_parking.conf",
264         .types = ACO_TYPES(&global_option, &parking_lot_type),
265 };
266
267 static AO2_GLOBAL_OBJ_STATIC(globals);
268
269 CONFIG_INFO_STANDARD(cfg_info, globals, parking_config_alloc,
270         .files = ACO_FILES(&parking_lot_conf),
271         .pre_apply_config = config_parking_preapply,
272         .post_apply_config = link_configured_disable_marked_lots,
273 );
274
275 static int parking_lot_cfg_hash_fn(const void *obj, const int flags)
276 {
277         const struct parking_lot_cfg *entry;
278         const char *key;
279
280         switch (flags & (OBJ_POINTER | OBJ_KEY | OBJ_PARTIAL_KEY)) {
281         case OBJ_KEY:
282                 key = obj;
283                 return ast_str_hash(key);
284         case OBJ_PARTIAL_KEY:
285                 ast_assert(0);
286                 return 0;
287         default:
288                 entry = obj;
289                 return ast_str_hash(entry->name);
290         }
291 }
292
293 static int parking_lot_cfg_cmp_fn(void *obj, void *arg, const int flags)
294 {
295         struct parking_lot_cfg *entry1 = obj;
296
297         char *key;
298         size_t key_size;
299         struct parking_lot_cfg *entry2;
300
301         switch (flags & (OBJ_POINTER | OBJ_KEY | OBJ_PARTIAL_KEY)) {
302         case OBJ_KEY:
303                 key = arg;
304                 return (!strcmp(entry1->name, key)) ? CMP_MATCH : 0;
305         case OBJ_PARTIAL_KEY:
306                 key = arg;
307                 key_size = strlen(key);
308                 return (!strncmp(entry1->name, key, key_size)) ? CMP_MATCH : 0;
309         case OBJ_POINTER:
310                 entry2 = arg;
311                 return (!strcmp(entry1->name, entry2->name)) ? CMP_MATCH : 0;
312         default:
313                 return CMP_STOP;
314         }
315 }
316
317 /*! \brief destructor for parking_config */
318 static void parking_config_destructor(void *obj)
319 {
320         struct parking_config *cfg = obj;
321         ao2_cleanup(cfg->parking_lots);
322         ao2_cleanup(cfg->global);
323 }
324
325 /*! \brief destructor for parking_global_config */
326 static void parking_global_config_destructor(void *obj)
327 {
328         /* For now, do nothing. */
329 }
330
331 /*! \brief allocator callback for parking_config. Notice it returns void * since it is only used by the backend config code */
332 static void *parking_config_alloc(void)
333 {
334         RAII_VAR(struct parking_config *, cfg, NULL, ao2_cleanup);
335
336         if (!(cfg = ao2_alloc(sizeof(*cfg), parking_config_destructor))) {
337                 return NULL;
338         }
339
340         if (!(cfg->parking_lots = ao2_container_alloc(37, parking_lot_cfg_hash_fn, parking_lot_cfg_cmp_fn))) {
341                 return NULL;
342         }
343
344         if (!(cfg->global = ao2_alloc(sizeof(*cfg->global), parking_global_config_destructor))) {
345                 return NULL;
346         }
347
348         /* Bump the ref count since RAII_VAR is going to eat one */
349         ao2_ref(cfg, +1);
350         return cfg;
351 }
352
353 int parking_lot_remove_if_unused(struct parking_lot *lot)
354 {
355         if (lot->mode != PARKINGLOT_DISABLED) {
356                 return -1;
357         }
358
359         if (!ao2_container_count(lot->parked_users)) {
360                 ao2_unlink(parking_lot_container, lot);
361                 return 0;
362         }
363
364         return -1;
365 }
366
367 static void parking_lot_disable(struct parking_lot *lot)
368 {
369         /* If a dynamic lot wasn't removed, we need to restore it to full functionality afterwards. */
370         int was_dynamic = (lot->mode == PARKINGLOT_DYNAMIC);
371
372         lot->mode = PARKINGLOT_DISABLED;
373         if (parking_lot_remove_if_unused(lot) && was_dynamic) {
374                 lot->mode = PARKINGLOT_DYNAMIC;
375                 lot->disable_mark = 0;
376         }
377 }
378
379 /*! \brief Destroy a parking lot cfg object */
380 static void parking_lot_cfg_destructor(void *obj)
381 {
382         struct parking_lot_cfg *lot_cfg = obj;
383         parking_lot_cfg_remove_extensions(lot_cfg);
384         ast_string_field_free_memory(lot_cfg);
385 }
386
387 /* The arg just needs to have the parking space with it */
388 static int parked_user_cmp_fn(void *obj, void *arg, int flags)
389 {
390         int *search_space = arg;
391         struct parked_user *user = obj;
392         int object_space = user->parking_space;
393
394         if (*search_space == object_space) {
395                 return CMP_MATCH;
396         }
397         return 0;
398 }
399
400 static int parked_user_sort_fn(const void *obj_left, const void *obj_right, int flags)
401 {
402         const struct parked_user *left = obj_left;
403         const struct parked_user *right = obj_right;
404
405         return left->parking_space - right->parking_space;
406 }
407
408 /*!
409  * \brief create a parking lot structure
410  * \param cat name given to the parking lot
411  * \retval NULL failure
412  * \retval non-NULL successfully allocated parking lot
413  */
414 static void *parking_lot_cfg_alloc(const char *cat)
415 {
416         struct parking_lot_cfg *lot_cfg;
417
418         lot_cfg = ao2_alloc(sizeof(*lot_cfg), parking_lot_cfg_destructor);
419         if (!lot_cfg) {
420                 return NULL;
421         }
422
423         if (ast_string_field_init(lot_cfg, 32)) {
424                 ao2_cleanup(lot_cfg);
425                 return NULL;
426         }
427
428         ast_string_field_set(lot_cfg, name, cat);
429
430         return lot_cfg;
431 }
432
433 #if defined(TEST_FRAMEWORK)
434 struct parking_lot_cfg *parking_lot_cfg_create(const char *cat)
435 {
436         return parking_lot_cfg_alloc(cat);
437 }
438 #endif
439
440 /*!
441  * XXX This is actually incredibly generic and might be better placed in something like astobj2 if there isn't already an equivalent
442  * \brief find an item in a container by its name
443  *
444  * \param container ao2container where we want the item from
445  * \param key name of the item wanted to be found
446  *
447  * \retval pointer to the parking lot if available. NULL if not found.
448  */
449 static void *named_item_find(struct ao2_container *container, const char *name)
450 {
451         return ao2_find(container, name, OBJ_KEY);
452 }
453
454 /*!
455  * \brief Custom field handler for parking positions
456  */
457 static int option_handler_parkpos(const struct aco_option *opt, struct ast_variable *var, void *obj)
458 {
459         struct parking_lot_cfg *lot_cfg = obj;
460         int low;
461         int high;
462
463         if (sscanf(var->value, "%30d-%30d", &low, &high) != 2) {
464                 ast_log(LOG_WARNING, "Format for parking positions is a-b, where a and b are numbers\n");
465         } else if (high < low || low <= 0 || high <= 0) {
466                 ast_log(LOG_WARNING, "Format for parking positions is a-b, where a <= b\n");
467         } else {
468                 lot_cfg->parking_start = low;
469                 lot_cfg->parking_stop = high;
470                 return 0;
471         }
472         return -1;
473 }
474
475 /*!
476  * \brief Custom field handler for the findslot option
477  */
478 static int option_handler_findslot(const struct aco_option *opt, struct ast_variable *var, void *obj)
479 {
480         struct parking_lot_cfg *lot_cfg = obj;
481
482         if (!strcmp(var->value, "first")) {
483                 lot_cfg->parkfindnext = 0;
484         } else if (!strcmp(var->value, "next")) {
485                 lot_cfg->parkfindnext = 1;
486         } else {
487                 ast_log(LOG_WARNING, "value '%s' is not valid for findslot option.\n", var->value);
488                 return -1;
489         }
490
491         return 0;
492 }
493
494 /*!
495  * \brief Maps string values for option_handler_parkedfeature to their ENUM values
496  */
497 static int parking_feature_flag_cfg(int *param, const char *var)
498 {
499         if (ast_false(var)) {
500                 *param = 0;
501         } else if (!strcasecmp(var, "both")) {
502                 *param = AST_FEATURE_FLAG_BYBOTH;
503         } else if (!strcasecmp(var, "caller")) {
504                 *param = AST_FEATURE_FLAG_BYCALLER;
505         } else if (!strcasecmp(var, "callee")) {
506                 *param = AST_FEATURE_FLAG_BYCALLEE;
507         } else {
508                 return -1;
509         }
510
511         return 0;
512 }
513
514 /*!
515  * \brief Custom field handler for feature mapping on parked call pickup options
516  */
517 static int option_handler_parkedfeature(const struct aco_option *opt, struct ast_variable *var, void *obj)
518 {
519         struct parking_lot_cfg *cfg = obj;
520         enum parked_call_feature_options option = aco_option_get_flags(opt);
521         int *parameter = NULL;
522
523         switch (option) {
524         case OPT_PARKEDPLAY:
525                 parameter = &cfg->parkedplay;
526                 break;
527         case OPT_PARKEDTRANSFERS:
528                 parameter = &cfg->parkedcalltransfers;
529                 break;
530         case OPT_PARKEDREPARKING:
531                 parameter = &cfg->parkedcallreparking;
532                 break;
533         case OPT_PARKEDHANGUP:
534                 parameter = &cfg->parkedcallhangup;
535                 break;
536         case OPT_PARKEDRECORDING:
537                 parameter = &cfg->parkedcallrecording;
538                 break;
539         }
540
541         ast_assert(parameter != NULL);
542         if (!parameter || parking_feature_flag_cfg(parameter, var->value)) {
543                 return -1;
544         }
545
546         return 0;
547 }
548
549 struct ao2_container *get_parking_lot_container(void)
550 {
551         return parking_lot_container;
552 }
553
554 struct parking_lot *parking_lot_find_by_name(const char *lot_name)
555 {
556         struct parking_lot *lot = named_item_find(parking_lot_container, lot_name);
557         return lot;
558 }
559
560 const char *find_channel_parking_lot_name(struct ast_channel *chan)
561 {
562         const char *name;
563
564         /* The channel variable overrides everything */
565         name = pbx_builtin_getvar_helper(chan, "PARKINGLOT");
566         if (ast_strlen_zero(name) && !ast_strlen_zero(ast_channel_parkinglot(chan))) {
567                 /* Use the channel's parking lot. */
568                 name = ast_channel_parkinglot(chan);
569         }
570
571         /* If the name couldn't be pulled from that either, use the default parking lot name. */
572         if (ast_strlen_zero(name)) {
573                 name = DEFAULT_PARKING_LOT;
574         }
575
576         return name;
577 }
578
579 static void parking_lot_destructor(void *obj)
580 {
581         struct parking_lot *lot = obj;
582
583         if (lot->parking_bridge) {
584                 ast_bridge_destroy(lot->parking_bridge);
585         }
586         ao2_cleanup(lot->parked_users);
587         ao2_cleanup(lot->cfg);
588         ast_string_field_free_memory(lot);
589 }
590
591 static struct parking_lot *alloc_new_parking_lot(struct parking_lot_cfg *lot_cfg)
592 {
593         struct parking_lot *lot;
594         if (!(lot = ao2_alloc(sizeof(*lot), parking_lot_destructor))) {
595                 return NULL;
596         }
597
598         if (ast_string_field_init(lot, 32)) {
599                 return NULL;
600         }
601
602         /* Create parked user ordered list */
603         lot->parked_users = ao2_container_alloc_list(AO2_ALLOC_OPT_LOCK_RWLOCK,
604                 AO2_CONTAINER_ALLOC_OPT_DUPS_REJECT,
605                 parked_user_sort_fn,
606                 parked_user_cmp_fn);
607
608         if (!lot->parked_users) {
609                 ao2_cleanup(lot);
610                 return NULL;
611         }
612
613         ast_string_field_set(lot, name, lot_cfg->name);
614         return lot;
615 }
616
617 void parking_lot_cfg_remove_extensions(struct parking_lot_cfg *lot_cfg)
618 {
619         if (!ast_strlen_zero(lot_cfg->registrar)) {
620                 /* Although the function is called ast_context_destroy, the use of this funtion is
621                  * intended only to remove extensions, hints, etc registered by the parking lot's registrar.
622                  * It won't actually destroy the context unless that context is empty afterwards and it is
623                  * unreferenced.
624                  */
625                 ast_context_destroy(NULL, lot_cfg->registrar);
626         }
627 }
628
629 static void remove_all_configured_parking_lot_extensions(void)
630 {
631         RAII_VAR(struct parking_config *, cfg, ao2_global_obj_ref(globals), ao2_cleanup);
632         struct parking_lot_cfg *lot_cfg;
633         struct ao2_iterator iter;
634
635         if (!cfg) {
636                 return;
637         }
638
639         for (iter = ao2_iterator_init(cfg->parking_lots, 0); (lot_cfg = ao2_iterator_next(&iter)); ao2_ref(lot_cfg, -1)) {
640                 parking_lot_cfg_remove_extensions(lot_cfg);
641         }
642
643         ast_context_destroy(NULL, BASE_REGISTRAR);
644
645         ao2_iterator_destroy(&iter);
646 }
647
648 /*!
649  * \internal
650  * \since 12
651  * \brief Create an extension using ast_add_extension2_nolock. This function automatically allocates a duplicate
652  *        of the data string so that whatever calls it doesn't have to deal with freeing it if the ast_add_extension2_nolock
653  *        fails.
654  *
655  * \param context a write locked ast_context. Make certain it is write locked prior to calling this function
656  * \param replace whether the extension should replace existing extensions
657  * \param extension name of the extension desired
658  * \param priority priority of the extension we are registering
659  * \param application name of the application being used for the extension
660  * \param data application arguments
661  * \param registrar name of the registrar you should use for the extension.
662  *        Make sure this string doesn't go anywhere while there are still extensions using it.
663  */
664 static int parking_add_extension(struct ast_context *context, int replace, const char *extension,
665         int priority, const char *application, const char *data, const char *registrar)
666 {
667         char *data_duplicate = ast_strdup(data);
668
669         if (!data_duplicate) {
670                 return -1;
671         }
672
673         if (ast_add_extension2_nolock(context, replace, extension, priority, NULL, NULL,
674                         application, data_duplicate, ast_free_ptr, registrar)) {
675                 ast_free(data_duplicate);
676                 return -1;
677         }
678
679         return 0;
680 }
681
682 static int extension_is_compatible(struct parking_lot_cfg *lot_cfg, const char *app_type, struct ast_exten *extension)
683 {
684         RAII_VAR(struct parking_lot_cfg *, owner, NULL, ao2_cleanup);
685         const char *extension_registrar = ast_get_extension_registrar(extension);
686         const char *extension_context = ast_get_context_name(ast_get_extension_context(extension));
687         const char *extension_name = ast_get_extension_name(extension);
688         const char *extension_application = ast_get_extension_app(extension);
689
690         ast_assert(extension_registrar && extension_context && extension_name && extension_application);
691
692         if (strcmp(extension_registrar, BASE_REGISTRAR)) {
693                 ast_log(LOG_ERROR, "Parking lot '%s' -- Needs an extension '%s@%s', but that extension is already owned by %s.\n",
694                         lot_cfg->name, extension_name, extension_context, extension_registrar);
695                 return 0;
696         }
697
698         if (strcmp(extension_application, app_type)) {
699                 ast_log(LOG_ERROR, "Parking lot '%s' -- Needs an extension '%s@%s' with a non-exclusive %s application, "
700                         "but a/an %s application is already registered to that extension by %s.\n",
701                         lot_cfg->name, extension_name, extension_context, app_type,
702                         extension_application, BASE_REGISTRAR);
703                 return 0;
704         }
705
706         ast_debug(3, "Parking lot '%s' -- extension '%s@%s' with application %s is compatible.\n",
707                   lot_cfg->name, extension_name, extension_context, app_type);
708         return 1;
709 }
710
711 int parking_lot_cfg_create_extensions(struct parking_lot_cfg *lot_cfg)
712 {
713         int parkingspace;
714         struct ast_exten *existing_exten;
715         struct ast_context *lot_context;
716         struct pbx_find_info find_info = { .stacklen = 0 }; /* the rest is reset in pbx_find_extension */
717         const char *parkext_registrar_pointer; /* Used for park extension */
718         const char *parkedcall_registrar_pointer; /* Used for parkedcall extensions/hints */
719
720         if (ast_strlen_zero(lot_cfg->parkext)) {
721                 return 0;
722         }
723
724         ast_string_field_build(lot_cfg, registrar, "%s/%s", BASE_REGISTRAR, lot_cfg->name);
725         parkedcall_registrar_pointer = lot_cfg->registrar;
726
727         if (lot_cfg->parkext_exclusive) {
728                 parkext_registrar_pointer = lot_cfg->registrar;
729         } else {
730                 parkext_registrar_pointer = BASE_REGISTRAR;
731         }
732
733         /* We need the contexts list locked to safely be able to both read and lock the specific context within */
734         if (ast_wrlock_contexts()) {
735                 ast_log(LOG_ERROR, "Failed to lock the contexts list.\n");
736                 return -1;
737         }
738
739         if (!(lot_context = ast_context_find_or_create(NULL, NULL, lot_cfg->parking_con, parkext_registrar_pointer))) {
740                 ast_log(LOG_ERROR, "Parking lot '%s' -- Needs a context '%s' which does not exist and Asterisk was unable to create\n",
741                         lot_cfg->name, lot_cfg->parking_con);
742                 if (ast_unlock_contexts()) {
743                         ast_assert(0);
744                 }
745                 return -1;
746         }
747
748         /* Once we know what context we will be modifying, we need to write lock it because we will be reading extensions
749          * and we don't want something else to destroy them while we are looking at them.
750          */
751         if (ast_wrlock_context(lot_context)) {
752                 ast_log(LOG_ERROR, "failed to obtain write lock on context\n");
753                 return -1;
754         }
755
756         if (ast_unlock_contexts()) {
757                 ast_assert(0);
758         }
759
760         /* Handle generation/confirmation for the Park extension */
761         if ((existing_exten = pbx_find_extension(NULL, NULL, &find_info, lot_cfg->parking_con, lot_cfg->parkext, 1, NULL, NULL, E_MATCH))) {
762                 if (lot_cfg->parkext_exclusive || !extension_is_compatible(lot_cfg, PARK_APPLICATION, existing_exten)) {
763                         ast_unlock_context(lot_context);
764                         return -1;
765                 }
766         } else if (parking_add_extension(lot_context, 0, lot_cfg->parkext, 1, PARK_APPLICATION,
767                    lot_cfg->parkext_exclusive ? lot_cfg->name : "", parkext_registrar_pointer)) {
768                 ast_log(LOG_ERROR, "Parking lot '%s' -- Failed to add %s extension '%s@%s' to the PBX.\n",
769                         lot_cfg->name, PARK_APPLICATION, lot_cfg->parkext, lot_cfg->parking_con);
770                 ast_unlock_context(lot_context);
771                 return -1;
772         }
773
774         /* Handle generation/confirmation for the ParkedCall extensions and hints */
775         for (parkingspace = lot_cfg->parking_start; parkingspace <= lot_cfg->parking_stop; parkingspace++) {
776                 char space[AST_MAX_EXTENSION];
777                 RAII_VAR(struct ast_str *, arguments_string, NULL, ast_free);
778                 find_info.stacklen = 0; /* reset for pbx_find_exten */
779
780                 snprintf(space, sizeof(space), "%d", parkingspace);
781
782                 /* Unlike the Park extensions, ParkedCall extensions and their hints may never be shared for any reason. */
783                 if ((existing_exten = pbx_find_extension(NULL, NULL, &find_info, lot_cfg->parking_con, space, 1, NULL, NULL, E_MATCH))) {
784                         ast_unlock_context(lot_context);
785                         return -1;
786                 }
787
788                 arguments_string = ast_str_create(32);
789                 if (!arguments_string) {
790                         ast_unlock_context(lot_context);
791                         return -1;
792                 }
793
794                 ast_str_set(&arguments_string, 0, "%s,%s", lot_cfg->name, space);
795                 if (parking_add_extension(lot_context, 0, space, 1, PARKED_CALL_APPLICATION,
796                     ast_str_buffer(arguments_string), parkedcall_registrar_pointer)) {
797                         ast_log(LOG_ERROR, "Parking lot '%s' -- Failed to add %s extension '%s@%s' to the PBX.\n",
798                                 lot_cfg->name, PARKED_CALL_APPLICATION, space, lot_cfg->parking_con);
799                         ast_unlock_context(lot_context);
800                         return -1;
801                 }
802
803                 find_info.stacklen = 0; /* reset for pbx_find_exten */
804
805                 if (lot_cfg->parkaddhints) {
806                         char hint_device[AST_MAX_EXTENSION];
807
808                         snprintf(hint_device, sizeof(hint_device), "park:%s@%s", space, lot_cfg->parking_con);
809
810                         if ((existing_exten = pbx_find_extension(NULL, NULL, &find_info, lot_cfg->parking_con, space, PRIORITY_HINT, NULL, NULL, E_MATCH))) {
811                                 ast_log(LOG_ERROR, "Parking lot '%s' -- Needs to add a hint '%s' at '%s@%s' but one already exists owned by %s\n",
812                                 lot_cfg->name, hint_device, space, lot_cfg->parking_con, ast_get_extension_registrar(existing_exten));
813                                         ast_unlock_context(lot_context);
814                                         return -1;
815                         }
816
817                         if (parking_add_extension(lot_context, 0, space, PRIORITY_HINT, hint_device, "", parkedcall_registrar_pointer)) {
818                                 ast_log(LOG_ERROR, "Parking lot '%s' -- Failed to add hint '%s@%s' to the PBX.\n",
819                                         lot_cfg->name, space, lot_cfg->parking_con);
820                                 ast_unlock_context(lot_context);
821                                 return -1;
822                         }
823                 }
824         }
825
826         if (ast_unlock_context(lot_context)) {
827                 ast_assert(0);
828         }
829
830         return 0;
831 }
832
833 struct parking_lot *parking_lot_build_or_update(struct parking_lot_cfg *lot_cfg, int dynamic)
834 {
835         struct parking_lot *lot;
836         struct parking_lot_cfg *replaced_cfg = NULL;
837         int found = 0;
838
839         /* Start by trying to find it. If that works we can skip the rest. */
840         lot = named_item_find(parking_lot_container, lot_cfg->name);
841         if (!lot) {
842                 lot = alloc_new_parking_lot(lot_cfg);
843
844                 /* If we still don't have a lot, we failed to alloc one. */
845                 if (!lot) {
846                         return NULL;
847                 }
848         } else {
849                 found = 1;
850
851                 if (dynamic) {
852                         ast_log(LOG_ERROR, "Tried to create dynamic parking lot with name '%s' but a lot with that name already exists.\n", lot_cfg->name);
853                         ao2_cleanup(lot);
854                         return NULL;
855                 }
856         }
857
858         /* Set the configuration reference. Unref the one currently in the lot if it's there. */
859         if (lot->cfg) {
860                 replaced_cfg = lot->cfg;
861         }
862
863         ao2_ref(lot_cfg, +1);
864         lot->cfg = lot_cfg;
865
866         ao2_cleanup(replaced_cfg);
867
868         /* Set the operating mode to normal since the parking lot has a configuration. */
869         lot->disable_mark = 0;
870         lot->mode = dynamic ? PARKINGLOT_DYNAMIC : PARKINGLOT_NORMAL;
871
872         if (!found) {
873                 /* Link after configuration is set since a lot without configuration will cause all kinds of trouble. */
874                 ao2_link(parking_lot_container, lot);
875         };
876
877         return lot;
878 }
879
880 static void generate_or_link_lots_to_configs(void)
881 {
882         RAII_VAR(struct parking_config *, cfg, ao2_global_obj_ref(globals), ao2_cleanup);
883         struct parking_lot_cfg *lot_cfg;
884         struct ao2_iterator iter;
885
886         for (iter = ao2_iterator_init(cfg->parking_lots, 0); (lot_cfg = ao2_iterator_next(&iter)); ao2_ref(lot_cfg, -1)) {
887                 RAII_VAR(struct parking_lot *, lot, NULL, ao2_cleanup);
888                 lot = parking_lot_build_or_update(lot_cfg, 0);
889         }
890
891         ao2_iterator_destroy(&iter);
892 }
893
894 int parking_dynamic_lots_enabled(void)
895 {
896         RAII_VAR(struct parking_config *, cfg, ao2_global_obj_ref(globals), ao2_cleanup);
897
898         if (!cfg) {
899                 return 0;
900         }
901
902         return cfg->global->parkeddynamic;
903 }
904
905 static struct parking_lot_cfg *clone_parkinglot_cfg(struct parking_lot_cfg *source, const char *name)
906 {
907         struct parking_lot_cfg *cfg = parking_lot_cfg_alloc(name);
908
909         if (!cfg) {
910                 return NULL;
911         }
912
913         ast_string_fields_copy(cfg, source);
914
915         /* Needs to be reset after being copied */
916         ast_string_field_set(cfg, name, name);
917
918         /* Stuff that should be cloned that isn't hit by string field copy */
919         cfg->parking_start = source->parking_start;
920         cfg->parking_stop = source->parking_stop;
921         cfg->parkingtime = source->parkingtime;
922         cfg->comebackdialtime = source->comebackdialtime;
923         cfg->parkfindnext = source->parkfindnext;
924         cfg->parkext_exclusive = source->parkext_exclusive;
925         cfg->parkaddhints = source->parkaddhints;
926         cfg->comebacktoorigin = source->comebacktoorigin;
927         cfg->parkedplay = source->parkedplay;
928         cfg->parkedcalltransfers = source->parkedcalltransfers;
929         cfg->parkedcallreparking = source->parkedcallreparking;
930         cfg->parkedcallhangup = source->parkedcallhangup;
931         cfg->parkedcallrecording = source->parkedcallrecording;
932
933         return cfg;
934 }
935
936 static struct parking_lot *create_dynamic_lot_full(const char *name, struct ast_channel *chan, int forced)
937 {
938         RAII_VAR(struct parking_lot_cfg *, cfg, NULL, ao2_cleanup);
939         RAII_VAR(struct parking_lot *, template_lot, NULL, ao2_cleanup);
940
941         struct parking_lot *lot;
942         const char *dyn_context;
943         const char *dyn_exten;
944         const char *dyn_range;
945         const char *template_name;
946         const char *chan_template_name;
947         int dyn_start;
948         int dyn_end;
949
950         if (!forced && !parking_dynamic_lots_enabled()) {
951                 return NULL;
952         }
953
954         ast_channel_lock(chan);
955         chan_template_name = ast_strdupa(S_OR(pbx_builtin_getvar_helper(chan, "PARKINGDYNAMIC"), ""));
956         dyn_context = ast_strdupa(S_OR(pbx_builtin_getvar_helper(chan, "PARKINGDYNCONTEXT"), ""));
957         dyn_exten = ast_strdupa(S_OR(pbx_builtin_getvar_helper(chan, "PARKINGDYNEXTEN"), ""));
958         dyn_range = ast_strdupa(S_OR(pbx_builtin_getvar_helper(chan, "PARKINGDYNPOS"), ""));
959         ast_channel_unlock(chan);
960
961         template_name = S_OR(chan_template_name, DEFAULT_PARKING_LOT);
962
963         template_lot = parking_lot_find_by_name(template_name);
964         if (!template_lot) {
965                 ast_log(LOG_ERROR, "Lot %s does not exist. Can not use it as a dynamic parking lot template.\n",
966                         template_name);
967                 return NULL;
968         }
969
970         cfg = clone_parkinglot_cfg(template_lot->cfg, name);
971
972         if (!cfg) {
973                 ast_log(LOG_ERROR, "Failed to allocate dynamic parking lot configuration.\n");
974                 return NULL;
975         }
976
977         if (!ast_strlen_zero(dyn_exten)) {
978                 ast_string_field_set(cfg, parkext, dyn_exten);
979         }
980
981         if (!ast_strlen_zero(dyn_context)) {
982                 ast_string_field_set(cfg, parking_con, dyn_context);
983         }
984
985         if (!ast_strlen_zero(dyn_range)) {
986                 if (sscanf(dyn_range, "%30d-%30d", &dyn_start, &dyn_end) != 2) {
987                         ast_log(LOG_ERROR,
988                                 "Invalid parking range %s specified in PARKINGDYNPOS: could not parse minimum/maximum parking space range\n", dyn_range);
989                                 return NULL;
990                 }
991                 if (dyn_end < dyn_start || dyn_start < 0) {
992                         ast_log(LOG_ERROR,
993                                 "Invalid parking range %s specified for PARKINGDYNPOS: end parking space must be greater than starting parking space.\n", dyn_range);
994                                 return NULL;
995                 }
996
997                 cfg->parking_start = dyn_start;
998                 cfg->parking_stop = dyn_end;
999         }
1000
1001         if (parking_lot_cfg_create_extensions(cfg)) {
1002                 ast_log(LOG_ERROR, "Extensions for dynamic parking lot '%s' could not be registered. Dynamic lot creation failed.\n", name);
1003                 return NULL;
1004         }
1005
1006         ao2_lock(parking_lot_container);
1007
1008         if ((lot = parking_lot_find_by_name(name))) {
1009                 ao2_unlock(parking_lot_container);
1010                 ast_log(LOG_ERROR, "Started creating dynamic parking lot '%s', but a parking lot with that name already exists.\n", name);
1011                 ao2_ref(lot, -1);
1012                 return NULL;
1013         }
1014
1015         lot = parking_lot_build_or_update(cfg, 1);
1016         ao2_unlock(parking_lot_container);
1017
1018         if (!lot) {
1019                 ast_log(LOG_NOTICE, "Failed to build dynamic parking lot '%s'\n", name);
1020         }
1021
1022         return lot;
1023 }
1024
1025 struct parking_lot *parking_create_dynamic_lot(const char *name, struct ast_channel *chan){
1026         return create_dynamic_lot_full(name, chan, 0);
1027 }
1028
1029 #if defined(TEST_FRAMEWORK)
1030 struct parking_lot *parking_create_dynamic_lot_forced(const char *name, struct ast_channel *chan) {
1031         return create_dynamic_lot_full(name, chan, 1);
1032 }
1033 #endif
1034
1035 /* Preapply */
1036
1037 static int verify_default_parking_lot(void)
1038 {
1039         struct parking_config *cfg = aco_pending_config(&cfg_info);
1040         RAII_VAR(struct parking_lot_cfg *, lot_cfg, NULL, ao2_cleanup);
1041
1042         if (!cfg) {
1043                 return 0;
1044         }
1045
1046         lot_cfg = ao2_find(cfg->parking_lots, DEFAULT_PARKING_LOT, OBJ_KEY);
1047         if (!lot_cfg) {
1048                 lot_cfg = parking_lot_cfg_alloc(DEFAULT_PARKING_LOT);
1049                 if (!lot_cfg) {
1050                         return -1;
1051                 }
1052                 ast_log(AST_LOG_NOTICE, "Adding %s profile to res_parking\n", DEFAULT_PARKING_LOT);
1053                 aco_set_defaults(&parking_lot_type, DEFAULT_PARKING_LOT, lot_cfg);
1054                 ast_string_field_set(lot_cfg, parkext, DEFAULT_PARKING_EXTEN);
1055                 ao2_link(cfg->parking_lots, lot_cfg);
1056         }
1057
1058         return 0;
1059 }
1060
1061 static void remove_pending_parking_lot_extensions(struct parking_config *cfg_pending)
1062 {
1063         struct parking_lot_cfg *lot_cfg;
1064         struct ao2_iterator iter;
1065
1066         for (iter = ao2_iterator_init(cfg_pending->parking_lots, 0); (lot_cfg = ao2_iterator_next(&iter)); ao2_ref(lot_cfg, -1)) {
1067                 parking_lot_cfg_remove_extensions(lot_cfg);
1068         }
1069
1070         ao2_iterator_destroy(&iter);
1071
1072         ast_context_destroy(NULL, BASE_REGISTRAR);
1073
1074 }
1075
1076 static int configure_parking_extensions(void)
1077 {
1078         struct parking_config *cfg = aco_pending_config(&cfg_info);
1079         struct ao2_iterator iter;
1080         RAII_VAR(struct parking_lot_cfg *, lot_cfg, NULL, ao2_cleanup);
1081         int res = 0;
1082
1083         if (!cfg) {
1084                 return 0;
1085         }
1086
1087         /* Clear existing extensions */
1088         remove_all_configured_parking_lot_extensions();
1089
1090         /* Attempt to build new extensions for each lot */
1091         for (iter = ao2_iterator_init(cfg->parking_lots, 0); (lot_cfg = ao2_iterator_next(&iter)); ao2_ref(lot_cfg, -1)) {
1092                 if (parking_lot_cfg_create_extensions(lot_cfg)) {
1093                         ao2_cleanup(lot_cfg);
1094                         lot_cfg = NULL;
1095                         res = -1;
1096                         break;
1097                 }
1098         }
1099         ao2_iterator_destroy(&iter);
1100
1101         if (res) {
1102                 remove_pending_parking_lot_extensions(cfg);
1103                 ast_log(LOG_ERROR, "Extension registration failed. Previously configured lot extensions were removed and can not be safely restored.\n");
1104         }
1105
1106         return res;
1107 }
1108
1109 static void mark_lots_as_disabled(void)
1110 {
1111         struct ao2_iterator iter;
1112         struct parking_lot *lot;
1113
1114         for (iter = ao2_iterator_init(parking_lot_container, 0); (lot = ao2_iterator_next(&iter)); ao2_ref(lot, -1)) {
1115                 lot->disable_mark = 1;
1116         }
1117
1118         ao2_iterator_destroy(&iter);
1119 }
1120
1121 static int config_parking_preapply(void)
1122 {
1123         mark_lots_as_disabled();
1124
1125         if (verify_default_parking_lot()) {
1126                 return -1;
1127         }
1128
1129         if (configure_parking_extensions()) {
1130                 return -1;
1131         }
1132
1133         return 0;
1134 }
1135
1136 static void disable_marked_lots(void)
1137 {
1138         struct ao2_iterator iter;
1139         struct parking_lot *lot;
1140
1141         for (iter = ao2_iterator_init(parking_lot_container, 0); (lot = ao2_iterator_next(&iter)); ao2_ref(lot, -1)) {
1142                 if (lot->disable_mark) {
1143                         parking_lot_disable(lot);
1144                 }
1145         }
1146
1147         ao2_iterator_destroy(&iter);
1148 }
1149
1150 static void link_configured_disable_marked_lots(void)
1151 {
1152         generate_or_link_lots_to_configs();
1153         disable_marked_lots();
1154 }
1155
1156 static int load_module(void)
1157 {
1158         parking_lot_container = ao2_container_alloc_list(AO2_ALLOC_OPT_LOCK_MUTEX,
1159                 AO2_CONTAINER_ALLOC_OPT_DUPS_REJECT,
1160                 parking_lot_sort_fn,
1161                 NULL);
1162         if (!parking_lot_container) {
1163                 goto error;
1164         }
1165
1166         if (aco_info_init(&cfg_info)) {
1167                 goto error;
1168         }
1169
1170         /* Global options */
1171         aco_option_register(&cfg_info, "parkeddynamic", ACO_EXACT, global_options, "no", OPT_BOOL_T, 1, FLDSET(struct parking_global_config, parkeddynamic));
1172
1173         /* Register the per parking lot options. */
1174         aco_option_register(&cfg_info, "parkext", ACO_EXACT, parking_lot_types, "", OPT_STRINGFIELD_T, 0, STRFLDSET(struct parking_lot_cfg, parkext));
1175         aco_option_register(&cfg_info, "context", ACO_EXACT, parking_lot_types, "parkedcalls", OPT_STRINGFIELD_T, 0, STRFLDSET(struct parking_lot_cfg, parking_con));
1176         aco_option_register(&cfg_info, "parkingtime", ACO_EXACT, parking_lot_types, "45", OPT_UINT_T, 0, FLDSET(struct parking_lot_cfg, parkingtime));
1177         aco_option_register(&cfg_info, "comebacktoorigin", ACO_EXACT, parking_lot_types, "yes", OPT_BOOL_T, 1, FLDSET(struct parking_lot_cfg, comebacktoorigin));
1178         aco_option_register(&cfg_info, "comebackcontext", ACO_EXACT, parking_lot_types, "parkedcallstimeout", OPT_STRINGFIELD_T, 0, STRFLDSET(struct parking_lot_cfg, comebackcontext));
1179         aco_option_register(&cfg_info, "comebackdialtime", ACO_EXACT, parking_lot_types, "30", OPT_UINT_T, 0, FLDSET(struct parking_lot_cfg, comebackdialtime));
1180         aco_option_register(&cfg_info, "parkedmusicclass", ACO_EXACT, parking_lot_types, "", OPT_STRINGFIELD_T, 0, STRFLDSET(struct parking_lot_cfg, mohclass));
1181         aco_option_register(&cfg_info, "parkext_exclusive", ACO_EXACT, parking_lot_types, "no", OPT_BOOL_T, 1, FLDSET(struct parking_lot_cfg, parkext_exclusive));
1182         aco_option_register(&cfg_info, "parkinghints", ACO_EXACT, parking_lot_types, "no", OPT_BOOL_T, 1, FLDSET(struct parking_lot_cfg, parkaddhints));
1183         aco_option_register(&cfg_info, "courtesytone", ACO_EXACT, parking_lot_types, "", OPT_STRINGFIELD_T, 0, STRFLDSET(struct parking_lot_cfg, courtesytone));
1184
1185         /* More complicated parking lot options that require special handling */
1186         aco_option_register_custom(&cfg_info, "parkpos", ACO_EXACT, parking_lot_types, "701-750", option_handler_parkpos, 0);
1187         aco_option_register_custom(&cfg_info, "findslot", ACO_EXACT, parking_lot_types, "first", option_handler_findslot, 0);
1188         aco_option_register_custom(&cfg_info, "parkedplay", ACO_EXACT, parking_lot_types, "caller", option_handler_parkedfeature, OPT_PARKEDPLAY);
1189         aco_option_register_custom(&cfg_info, "parkedcalltransfers", ACO_EXACT, parking_lot_types, "no", option_handler_parkedfeature, OPT_PARKEDTRANSFERS);
1190         aco_option_register_custom(&cfg_info, "parkedcallreparking", ACO_EXACT, parking_lot_types, "no", option_handler_parkedfeature, OPT_PARKEDREPARKING);
1191         aco_option_register_custom(&cfg_info, "parkedcallhangup", ACO_EXACT, parking_lot_types, "no", option_handler_parkedfeature, OPT_PARKEDHANGUP);
1192         aco_option_register_custom(&cfg_info, "parkedcallrecording", ACO_EXACT, parking_lot_types, "no", option_handler_parkedfeature, OPT_PARKEDRECORDING);
1193
1194         if (aco_process_config(&cfg_info, 0) == ACO_PROCESS_ERROR) {
1195                 goto error;
1196         }
1197
1198         if (ast_register_application_xml(PARK_APPLICATION, park_app_exec)) {
1199                 goto error;
1200         }
1201
1202         if (ast_register_application_xml(PARKED_CALL_APPLICATION, parked_call_app_exec)) {
1203                 goto error;
1204         }
1205
1206         if (ast_register_application_xml(PARK_AND_ANNOUNCE_APPLICATION, park_and_announce_app_exec)) {
1207                 goto error;
1208         }
1209
1210         if (load_parking_ui()) {
1211                 goto error;
1212         }
1213
1214         if (load_parking_manager()) {
1215                 goto error;
1216         }
1217
1218         if (load_parking_bridge_features()) {
1219                 goto error;
1220         }
1221
1222         if (load_parking_devstate()) {
1223                 goto error;
1224         }
1225
1226         if (load_parking_tests()) {
1227                 goto error;
1228         }
1229
1230         return AST_MODULE_LOAD_SUCCESS;
1231
1232 error:
1233         /* XXX errored loads don't currently do a good job of cleaning up after themselves */
1234         ao2_cleanup(parking_lot_container);
1235         aco_info_destroy(&cfg_info);
1236         return AST_MODULE_LOAD_DECLINE;
1237 }
1238
1239 static int reload_module(void)
1240 {
1241         if (aco_process_config(&cfg_info, 1) == ACO_PROCESS_ERROR) {
1242                 return AST_MODULE_LOAD_DECLINE;
1243         }
1244
1245         return 0;
1246 }
1247
1248 static int unload_module(void)
1249 {
1250
1251         /*ast_parking_unregister_bridge_features(parking_provider.module_name);*/
1252
1253         /* XXX Parking is currently not unloadable due to the fact that it loads features which could cause
1254          *     significant problems if they disappeared while a channel still had access to them.
1255          */
1256         return -1;
1257
1258         /* TODO Things we will need to do here:
1259          *
1260          *  destroy existing parking lots
1261          *  uninstall parking related bridge features
1262          *  remove extensions owned by the parking registrar
1263          *  unload currently loaded unit tests, CLI/AMI commands, etc.
1264          */
1265 }
1266
1267 AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_LOAD_ORDER, "Call Parking Resource",
1268         .load = load_module,
1269         .unload = unload_module,
1270         .reload = reload_module,
1271 );