Revert part of previous fix, and heavily comment the logic for object
[asterisk-bristuff.git] / apps / app_macro.c
bloba50dfb4bbbbaa895d23976cdf0bba800c50cc104
1 /*
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.
19 /*! \file
21 * \brief Dial plan macro Implementation
23 * \author Mark Spencer <markster@digium.com>
25 * \ingroup applications
28 #include "asterisk.h"
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"
40 #define MAX_ARGS 80
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 =
84 " MacroExit():\n"
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"
88 "behavior.\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)
103 struct ast_exten *e;
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)) ||
111 (!needmatch)) {
112 /* This is the matching extension we want */
113 struct ast_exten *p;
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))
116 continue;
117 return 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);
128 if (e)
129 return e;
133 return NULL;
136 static int _macro_exec(struct ast_channel *chan, void *data, int exclusive)
138 const char *s;
139 char *tmp;
140 char *cur, *rest;
141 char *macro;
142 char fullmacro[80];
143 char varname[80];
144 char runningapp[80], runningdata[1024];
145 char *oldargs[MAX_ARGS + 1] = { NULL, };
146 int argc, x;
147 int res=0;
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");
164 return -1;
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");
189 return 0;
191 snprintf(depthc, sizeof(depthc), "%d", depth + 1);
192 pbx_builtin_setvar_helper(chan, "MACRO_DEPTH", depthc);
194 tmp = ast_strdupa(data);
195 rest = tmp;
196 macro = strsep(&rest, ",");
197 if (ast_strlen_zero(macro)) {
198 ast_log(LOG_WARNING, "Invalid macro name specified\n");
199 return 0;
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);
206 else
207 ast_log(LOG_WARNING, "Context '%s' for macro '%s' lacks 's' extension, priority 1\n", fullmacro, macro);
208 return 0;
211 /* If we are to run the macro exclusively, take the mutex */
212 if (exclusive) {
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);
218 return 0;
220 ast_autoservice_stop(chan);
223 /* Save old info */
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;
231 setmacrocontext=1;
233 argc = 1;
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));
252 chan->priority = 1;
254 ast_channel_lock(chan);
255 while((cur = strsep(&rest, ",")) && (argc < MAX_ARGS)) {
256 const char *s;
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);
264 argc++;
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;
271 struct ast_exten *e;
272 int foundx;
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");
279 } else {
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");
284 } else {
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);
292 break;
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);
307 break;
309 switch(res) {
310 case MACRO_EXIT_RESULT:
311 res = 0;
312 goto out;
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);
316 goto out;
317 break;
318 default:
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);
321 dead = 1;
322 goto out;
326 ast_debug(1, "Executed application: %s\n", runningapp);
328 if (!strcasecmp(runningapp, "GOSUB")) {
329 gosub_level++;
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)) {
338 gosub_level++;
339 ast_debug(1, "Incrementing gosub_level\n");
341 } else {
342 if (!ast_strlen_zero(app2)) {
343 gosub_level++;
344 ast_debug(1, "Incrementing gosub_level\n");
347 } else if (!strcasecmp(runningapp, "RETURN")) {
348 gosub_level--;
349 ast_debug(1, "Decrementing gosub_level\n");
350 } else if (!strcasecmp(runningapp, "STACKPOP")) {
351 gosub_level--;
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, '|');
359 if (tmp3)
360 *tmp3++ = '\0';
361 if (!pbx_checkcondition(tmp2))
362 tmp3 = NULL;
363 } else
364 tmp3 = tmp2;
366 if (tmp3)
367 ast_debug(1, "Last app: %s\n", tmp3);
369 if (tmp3 && !strncasecmp(tmp3, "GOSUB", 5)) {
370 gosub_level++;
371 ast_debug(1, "Incrementing gosub_level\n");
372 } else if (tmp3 && !strncasecmp(tmp3, "RETURN", 6)) {
373 gosub_level--;
374 ast_debug(1, "Decrementing gosub_level\n");
375 } else if (tmp3 && !strncasecmp(tmp3, "STACKPOP", 8)) {
376 gosub_level--;
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);
383 break;
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);
389 goto out;
391 chan->priority++;
393 out:
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);
396 if (!dead) {
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);
404 if (oldargs[x]) {
405 if (!dead)
406 pbx_builtin_setvar_helper(chan, varname, oldargs[x]);
407 ast_free(oldargs[x]);
408 } else if (!dead) {
409 pbx_builtin_setvar_helper(chan, varname, NULL);
413 /* Restore macro variables */
414 if (!dead) {
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 */
438 const char *offsets;
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);
454 if (!dead)
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 */
460 if (exclusive) {
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);
464 res = 0;
468 return res;
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;
484 int res = 0;
486 if (!(expr = ast_strdupa(data)))
487 return -1;
489 if ((label_a = strchr(expr, '?'))) {
490 *label_a = '\0';
491 label_a++;
492 if ((label_b = strchr(label_a, ':'))) {
493 *label_b = '\0';
494 label_b++;
496 if (pbx_checkcondition(expr))
497 res = macro_exec(chan, label_a);
498 else if (label_b)
499 res = macro_exec(chan, label_b);
500 } else
501 ast_log(LOG_WARNING, "Invalid Syntax.\n");
503 return res;
506 static int macro_exit_exec(struct ast_channel *chan, void *data)
508 return MACRO_EXIT_RESULT;
511 static int unload_module(void)
513 int res;
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);
520 return res;
523 static int load_module(void)
525 int res;
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);
532 return res;
535 AST_MODULE_INFO_STANDARD(ASTERISK_GPL_KEY, "Extension Macros");