2 * Asterisk -- An open source telephony toolkit.
4 * Copyright (C) 1999 - 2005, Digium, Inc.
6 * Mark Spencer <markster@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 Dial plan macro Implementation
23 * \author Mark Spencer <markster@digium.com>
25 * \ingroup applications
30 ASTERISK_FILE_VERSION(__FILE__
, "$Revision$")
32 #include "asterisk/file.h"
33 #include "asterisk/channel.h"
34 #include "asterisk/pbx.h"
35 #include "asterisk/module.h"
36 #include "asterisk/config.h"
37 #include "asterisk/utils.h"
38 #include "asterisk/lock.h"
42 /* special result value used to force macro exit */
43 #define MACRO_EXIT_RESULT 1024
45 static char *descrip
=
46 " Macro(macroname,arg1,arg2...): Executes a macro using the context\n"
47 "'macro-<macroname>', jumping to the 's' extension of that context and\n"
48 "executing each step, then returning when the steps end. \n"
49 "The calling extension, context, and priority are stored in ${MACRO_EXTEN}, \n"
50 "${MACRO_CONTEXT} and ${MACRO_PRIORITY} respectively. Arguments become\n"
51 "${ARG1}, ${ARG2}, etc in the macro context.\n"
52 "If you Goto out of the Macro context, the Macro will terminate and control\n"
53 "will be returned at the location of the Goto.\n"
54 "If ${MACRO_OFFSET} is set at termination, Macro will attempt to continue\n"
55 "at priority MACRO_OFFSET + N + 1 if such a step exists, and N + 1 otherwise.\n"
56 "Extensions: While a macro is being executed, it becomes the current context.\n"
57 " This means that if a hangup occurs, for instance, that the macro\n"
58 " will be searched for an 'h' extension, NOT the context from which\n"
59 " the macro was called. So, make sure to define all appropriate\n"
60 " extensions in your macro! (Note: AEL does not use macros)\n"
61 "WARNING: Because of the way Macro is implemented (it executes the priorities\n"
62 " contained within it via sub-engine), and a fixed per-thread\n"
63 " memory stack allowance, macros are limited to 7 levels\n"
64 " of nesting (macro calling macro calling macro, etc.); It\n"
65 " may be possible that stack-intensive applications in deeply nested macros\n"
66 " could cause asterisk to crash earlier than this limit. It is advised that\n"
67 " if you need to deeply nest macro calls, that you use the Gosub application\n"
68 " (now allows arguments like a Macro) with explict Return() calls instead.\n";
70 static char *if_descrip
=
71 " MacroIf(<expr>?macroname_a[,arg1][:macroname_b[,arg1]])\n"
72 "Executes macro defined in <macroname_a> if <expr> is true\n"
73 "(otherwise <macroname_b> if provided)\n"
74 "Arguments and return values as in application Macro()\n";
76 static char *exclusive_descrip
=
77 " MacroExclusive(macroname,arg1,arg2...):\n"
78 "Executes macro defined in the context 'macro-macroname'\n"
79 "Only one call at a time may run the macro.\n"
80 "(we'll wait if another call is busy executing in the Macro)\n"
81 "Arguments and return values as in application Macro()\n";
83 static char *exit_descrip
=
85 "Causes the currently running macro to exit as if it had\n"
86 "ended normally by running out of priorities to execute.\n"
87 "If used outside a macro, will likely cause unexpected\n"
90 static char *app
= "Macro";
91 static char *if_app
= "MacroIf";
92 static char *exclusive_app
= "MacroExclusive";
93 static char *exit_app
= "MacroExit";
95 static char *synopsis
= "Macro Implementation";
96 static char *if_synopsis
= "Conditional Macro Implementation";
97 static char *exclusive_synopsis
= "Exclusive Macro Implementation";
98 static char *exit_synopsis
= "Exit From Macro";
101 static struct ast_exten
*find_matching_priority(struct ast_context
*c
, const char *exten
, int priority
, const char *callerid
)
104 struct ast_include
*i
;
105 struct ast_context
*c2
;
107 for (e
=ast_walk_context_extensions(c
, NULL
); e
; e
=ast_walk_context_extensions(c
, e
)) {
108 if (ast_extension_match(ast_get_extension_name(e
), exten
)) {
109 int needmatch
= ast_get_extension_matchcid(e
);
110 if ((needmatch
&& ast_extension_match(ast_get_extension_cidmatch(e
), callerid
)) ||
112 /* This is the matching extension we want */
114 for (p
=ast_walk_extension_priorities(e
, NULL
); p
; p
=ast_walk_extension_priorities(e
, p
)) {
115 if (priority
!= ast_get_extension_priority(p
))
123 /* No match; run through includes */
124 for (i
=ast_walk_context_includes(c
, NULL
); i
; i
=ast_walk_context_includes(c
, i
)) {
125 for (c2
=ast_walk_contexts(NULL
); c2
; c2
=ast_walk_contexts(c2
)) {
126 if (!strcmp(ast_get_context_name(c2
), ast_get_include_name(i
))) {
127 e
= find_matching_priority(c2
, exten
, priority
, callerid
);
136 static int _macro_exec(struct ast_channel
*chan
, void *data
, int exclusive
)
144 char runningapp
[80], runningdata
[1024];
145 char *oldargs
[MAX_ARGS
+ 1] = { NULL
, };
148 char oldexten
[256]="";
149 int oldpriority
, gosub_level
= 0;
150 char pc
[80], depthc
[12];
151 char oldcontext
[AST_MAX_CONTEXT
] = "";
152 const char *inhangupc
;
153 int offset
, depth
= 0, maxdepth
= 7;
154 int setmacrocontext
=0;
155 int autoloopflag
, dead
= 0, inhangup
= 0;
157 char *save_macro_exten
;
158 char *save_macro_context
;
159 char *save_macro_priority
;
160 char *save_macro_offset
;
162 if (ast_strlen_zero(data
)) {
163 ast_log(LOG_WARNING
, "Macro() requires arguments. See \"core show application macro\" for help.\n");
167 /* does the user want a deeper rabbit hole? */
168 ast_channel_lock(chan
);
169 if ((s
= pbx_builtin_getvar_helper(chan
, "MACRO_RECURSION"))) {
170 sscanf(s
, "%d", &maxdepth
);
173 /* Count how many levels deep the rabbit hole goes */
174 if ((s
= pbx_builtin_getvar_helper(chan
, "MACRO_DEPTH"))) {
175 sscanf(s
, "%d", &depth
);
178 /* Used for detecting whether to return when a Macro is called from another Macro after hangup */
179 if (strcmp(chan
->exten
, "h") == 0)
180 pbx_builtin_setvar_helper(chan
, "MACRO_IN_HANGUP", "1");
182 if ((inhangupc
= pbx_builtin_getvar_helper(chan
, "MACRO_IN_HANGUP"))) {
183 sscanf(inhangupc
, "%d", &inhangup
);
185 ast_channel_unlock(chan
);
187 if (depth
>= maxdepth
) {
188 ast_log(LOG_ERROR
, "Macro(): possible infinite loop detected. Returning early.\n");
191 snprintf(depthc
, sizeof(depthc
), "%d", depth
+ 1);
192 pbx_builtin_setvar_helper(chan
, "MACRO_DEPTH", depthc
);
194 tmp
= ast_strdupa(data
);
196 macro
= strsep(&rest
, ",");
197 if (ast_strlen_zero(macro
)) {
198 ast_log(LOG_WARNING
, "Invalid macro name specified\n");
202 snprintf(fullmacro
, sizeof(fullmacro
), "macro-%s", macro
);
203 if (!ast_exists_extension(chan
, fullmacro
, "s", 1, chan
->cid
.cid_num
)) {
204 if (!ast_context_find(fullmacro
))
205 ast_log(LOG_WARNING
, "No such context '%s' for macro '%s'\n", fullmacro
, macro
);
207 ast_log(LOG_WARNING
, "Context '%s' for macro '%s' lacks 's' extension, priority 1\n", fullmacro
, macro
);
211 /* If we are to run the macro exclusively, take the mutex */
213 ast_debug(1, "Locking macrolock for '%s'\n", fullmacro
);
214 ast_autoservice_start(chan
);
215 if (ast_context_lockmacro(fullmacro
)) {
216 ast_log(LOG_WARNING
, "Failed to lock macro '%s' as in-use\n", fullmacro
);
217 ast_autoservice_stop(chan
);
220 ast_autoservice_stop(chan
);
224 oldpriority
= chan
->priority
;
225 ast_copy_string(oldexten
, chan
->exten
, sizeof(oldexten
));
226 ast_copy_string(oldcontext
, chan
->context
, sizeof(oldcontext
));
227 if (ast_strlen_zero(chan
->macrocontext
)) {
228 ast_copy_string(chan
->macrocontext
, chan
->context
, sizeof(chan
->macrocontext
));
229 ast_copy_string(chan
->macroexten
, chan
->exten
, sizeof(chan
->macroexten
));
230 chan
->macropriority
= chan
->priority
;
234 /* Save old macro variables */
235 save_macro_exten
= ast_strdup(pbx_builtin_getvar_helper(chan
, "MACRO_EXTEN"));
236 pbx_builtin_setvar_helper(chan
, "MACRO_EXTEN", oldexten
);
238 save_macro_context
= ast_strdup(pbx_builtin_getvar_helper(chan
, "MACRO_CONTEXT"));
239 pbx_builtin_setvar_helper(chan
, "MACRO_CONTEXT", oldcontext
);
241 save_macro_priority
= ast_strdup(pbx_builtin_getvar_helper(chan
, "MACRO_PRIORITY"));
242 snprintf(pc
, sizeof(pc
), "%d", oldpriority
);
243 pbx_builtin_setvar_helper(chan
, "MACRO_PRIORITY", pc
);
245 save_macro_offset
= ast_strdup(pbx_builtin_getvar_helper(chan
, "MACRO_OFFSET"));
246 pbx_builtin_setvar_helper(chan
, "MACRO_OFFSET", NULL
);
248 /* Setup environment for new run */
249 chan
->exten
[0] = 's';
250 chan
->exten
[1] = '\0';
251 ast_copy_string(chan
->context
, fullmacro
, sizeof(chan
->context
));
254 ast_channel_lock(chan
);
255 while((cur
= strsep(&rest
, ",")) && (argc
< MAX_ARGS
)) {
257 /* Save copy of old arguments if we're overwriting some, otherwise
258 let them pass through to the other macro */
259 snprintf(varname
, sizeof(varname
), "ARG%d", argc
);
260 if ((s
= pbx_builtin_getvar_helper(chan
, varname
))) {
261 oldargs
[argc
] = ast_strdup(s
);
263 pbx_builtin_setvar_helper(chan
, varname
, cur
);
266 ast_channel_unlock(chan
);
267 autoloopflag
= ast_test_flag(chan
, AST_FLAG_IN_AUTOLOOP
);
268 ast_set_flag(chan
, AST_FLAG_IN_AUTOLOOP
);
269 while(ast_exists_extension(chan
, chan
->context
, chan
->exten
, chan
->priority
, chan
->cid
.cid_num
)) {
270 struct ast_context
*c
;
273 runningapp
[0] = '\0';
274 runningdata
[0] = '\0';
276 /* What application will execute? */
277 if (ast_rdlock_contexts()) {
278 ast_log(LOG_WARNING
, "Failed to lock contexts list\n");
280 for (c
= ast_walk_contexts(NULL
), e
= NULL
; c
; c
= ast_walk_contexts(c
)) {
281 if (!strcmp(ast_get_context_name(c
), chan
->context
)) {
282 if (ast_rdlock_context(c
)) {
283 ast_log(LOG_WARNING
, "Unable to lock context?\n");
285 e
= find_matching_priority(c
, chan
->exten
, chan
->priority
, chan
->cid
.cid_num
);
286 if (e
) { /* This will only be undefined for pbx_realtime, which is majorly broken. */
287 ast_copy_string(runningapp
, ast_get_extension_app(e
), sizeof(runningapp
));
288 ast_copy_string(runningdata
, ast_get_extension_app_data(e
), sizeof(runningdata
));
290 ast_unlock_context(c
);
296 ast_unlock_contexts();
298 /* Reset the macro depth, if it was changed in the last iteration */
299 pbx_builtin_setvar_helper(chan
, "MACRO_DEPTH", depthc
);
301 if ((res
= ast_spawn_extension(chan
, chan
->context
, chan
->exten
, chan
->priority
, chan
->cid
.cid_num
, &foundx
,1))) {
302 /* Something bad happened, or a hangup has been requested. */
303 if (((res
>= '0') && (res
<= '9')) || ((res
>= 'A') && (res
<= 'F')) ||
304 (res
== '*') || (res
== '#')) {
305 /* Just return result as to the previous application as if it had been dialed */
306 ast_debug(1, "Oooh, got something to jump out with ('%c')!\n", res
);
310 case MACRO_EXIT_RESULT
:
313 case AST_PBX_KEEPALIVE
:
314 ast_debug(2, "Spawn extension (%s,%s,%d) exited KEEPALIVE in macro %s on '%s'\n", chan
->context
, chan
->exten
, chan
->priority
, macro
, chan
->name
);
315 ast_verb(2, "Spawn extension (%s, %s, %d) exited KEEPALIVE in macro '%s' on '%s'\n", chan
->context
, chan
->exten
, chan
->priority
, macro
, chan
->name
);
319 ast_debug(2, "Spawn extension (%s,%s,%d) exited non-zero on '%s' in macro '%s'\n", chan
->context
, chan
->exten
, chan
->priority
, chan
->name
, macro
);
320 ast_verb(2, "Spawn extension (%s, %s, %d) exited non-zero on '%s' in macro '%s'\n", chan
->context
, chan
->exten
, chan
->priority
, chan
->name
, macro
);
326 ast_debug(1, "Executed application: %s\n", runningapp
);
328 if (!strcasecmp(runningapp
, "GOSUB")) {
330 ast_debug(1, "Incrementing gosub_level\n");
331 } else if (!strcasecmp(runningapp
, "GOSUBIF")) {
332 char tmp2
[1024], *cond
, *app
, *app2
= tmp2
;
333 pbx_substitute_variables_helper(chan
, runningdata
, tmp2
, sizeof(tmp2
) - 1);
334 cond
= strsep(&app2
, "?");
335 app
= strsep(&app2
, ":");
336 if (pbx_checkcondition(cond
)) {
337 if (!ast_strlen_zero(app
)) {
339 ast_debug(1, "Incrementing gosub_level\n");
342 if (!ast_strlen_zero(app2
)) {
344 ast_debug(1, "Incrementing gosub_level\n");
347 } else if (!strcasecmp(runningapp
, "RETURN")) {
349 ast_debug(1, "Decrementing gosub_level\n");
350 } else if (!strcasecmp(runningapp
, "STACKPOP")) {
352 ast_debug(1, "Decrementing gosub_level\n");
353 } else if (!strncasecmp(runningapp
, "EXEC", 4)) {
354 /* Must evaluate args to find actual app */
355 char tmp2
[1024], *tmp3
= NULL
;
356 pbx_substitute_variables_helper(chan
, runningdata
, tmp2
, sizeof(tmp2
) - 1);
357 if (!strcasecmp(runningapp
, "EXECIF")) {
358 tmp3
= strchr(tmp2
, '|');
361 if (!pbx_checkcondition(tmp2
))
367 ast_debug(1, "Last app: %s\n", tmp3
);
369 if (tmp3
&& !strncasecmp(tmp3
, "GOSUB", 5)) {
371 ast_debug(1, "Incrementing gosub_level\n");
372 } else if (tmp3
&& !strncasecmp(tmp3
, "RETURN", 6)) {
374 ast_debug(1, "Decrementing gosub_level\n");
375 } else if (tmp3
&& !strncasecmp(tmp3
, "STACKPOP", 8)) {
377 ast_debug(1, "Decrementing gosub_level\n");
381 if (gosub_level
== 0 && strcasecmp(chan
->context
, fullmacro
)) {
382 ast_verb(2, "Channel '%s' jumping out of macro '%s'\n", chan
->name
, macro
);
386 /* don't stop executing extensions when we're in "h" */
387 if (ast_check_hangup(chan
) && !inhangup
) {
388 ast_debug(1, "Extension %s, macroexten %s, priority %d returned normally even though call was hung up\n", chan
->exten
, chan
->macroexten
, chan
->priority
);
394 /* Reset the depth back to what it was when the routine was entered (like if we called Macro recursively) */
395 snprintf(depthc
, sizeof(depthc
), "%d", depth
);
397 pbx_builtin_setvar_helper(chan
, "MACRO_DEPTH", depthc
);
398 ast_set2_flag(chan
, autoloopflag
, AST_FLAG_IN_AUTOLOOP
);
401 for (x
= 1; x
< argc
; x
++) {
402 /* Restore old arguments and delete ours */
403 snprintf(varname
, sizeof(varname
), "ARG%d", x
);
406 pbx_builtin_setvar_helper(chan
, varname
, oldargs
[x
]);
407 ast_free(oldargs
[x
]);
409 pbx_builtin_setvar_helper(chan
, varname
, NULL
);
413 /* Restore macro variables */
415 pbx_builtin_setvar_helper(chan
, "MACRO_EXTEN", save_macro_exten
);
416 pbx_builtin_setvar_helper(chan
, "MACRO_CONTEXT", save_macro_context
);
417 pbx_builtin_setvar_helper(chan
, "MACRO_PRIORITY", save_macro_priority
);
419 if (save_macro_exten
)
420 ast_free(save_macro_exten
);
421 if (save_macro_context
)
422 ast_free(save_macro_context
);
423 if (save_macro_priority
)
424 ast_free(save_macro_priority
);
426 if (!dead
&& setmacrocontext
) {
427 chan
->macrocontext
[0] = '\0';
428 chan
->macroexten
[0] = '\0';
429 chan
->macropriority
= 0;
432 if (!dead
&& !strcasecmp(chan
->context
, fullmacro
)) {
433 /* If we're leaving the macro normally, restore original information */
434 chan
->priority
= oldpriority
;
435 ast_copy_string(chan
->context
, oldcontext
, sizeof(chan
->context
));
436 if (!(ast_check_hangup(chan
) & AST_SOFTHANGUP_ASYNCGOTO
)) {
437 /* Copy the extension, so long as we're not in softhangup, where we could be given an asyncgoto */
439 ast_copy_string(chan
->exten
, oldexten
, sizeof(chan
->exten
));
440 ast_channel_lock(chan
);
441 if ((offsets
= pbx_builtin_getvar_helper(chan
, "MACRO_OFFSET"))) {
442 /* Handle macro offset if it's set by checking the availability of step n + offset + 1, otherwise continue
443 normally if there is any problem */
444 if (sscanf(offsets
, "%d", &offset
) == 1) {
445 if (ast_exists_extension(chan
, chan
->context
, chan
->exten
, chan
->priority
+ offset
+ 1, chan
->cid
.cid_num
)) {
446 chan
->priority
+= offset
;
450 ast_channel_unlock(chan
);
455 pbx_builtin_setvar_helper(chan
, "MACRO_OFFSET", save_macro_offset
);
456 if (save_macro_offset
)
457 ast_free(save_macro_offset
);
459 /* Unlock the macro */
461 ast_debug(1, "Unlocking macrolock for '%s'\n", fullmacro
);
462 if (ast_context_unlockmacro(fullmacro
)) {
463 ast_log(LOG_ERROR
, "Failed to unlock macro '%s' - that isn't good\n", fullmacro
);
471 static int macro_exec(struct ast_channel
*chan
, void *data
)
473 return _macro_exec(chan
, data
, 0);
476 static int macroexclusive_exec(struct ast_channel
*chan
, void *data
)
478 return _macro_exec(chan
, data
, 1);
481 static int macroif_exec(struct ast_channel
*chan
, void *data
)
483 char *expr
= NULL
, *label_a
= NULL
, *label_b
= NULL
;
486 if (!(expr
= ast_strdupa(data
)))
489 if ((label_a
= strchr(expr
, '?'))) {
492 if ((label_b
= strchr(label_a
, ':'))) {
496 if (pbx_checkcondition(expr
))
497 res
= macro_exec(chan
, label_a
);
499 res
= macro_exec(chan
, label_b
);
501 ast_log(LOG_WARNING
, "Invalid Syntax.\n");
506 static int macro_exit_exec(struct ast_channel
*chan
, void *data
)
508 return MACRO_EXIT_RESULT
;
511 static int unload_module(void)
515 res
= ast_unregister_application(if_app
);
516 res
|= ast_unregister_application(exit_app
);
517 res
|= ast_unregister_application(app
);
518 res
|= ast_unregister_application(exclusive_app
);
523 static int load_module(void)
527 res
= ast_register_application(exit_app
, macro_exit_exec
, exit_synopsis
, exit_descrip
);
528 res
|= ast_register_application(if_app
, macroif_exec
, if_synopsis
, if_descrip
);
529 res
|= ast_register_application(exclusive_app
, macroexclusive_exec
, exclusive_synopsis
, exclusive_descrip
);
530 res
|= ast_register_application(app
, macro_exec
, synopsis
, descrip
);
535 AST_MODULE_INFO_STANDARD(ASTERISK_GPL_KEY
, "Extension Macros");