Merged revisions 53355 via svnmerge from
[asterisk/asterisk.git] / apps / app_macro.c
1 /*
2  * Asterisk -- An open source telephony toolkit.
3  *
4  * Copyright (C) 1999 - 2005, Digium, Inc.
5  *
6  * Mark Spencer <markster@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 Dial plan macro Implementation
22  *
23  * \author Mark Spencer <markster@digium.com>
24  * 
25  * \ingroup applications
26  */
27
28 #include "asterisk.h"
29
30 ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
31
32 #include <stdlib.h>
33 #include <stdio.h>
34 #include <string.h>
35 #include <unistd.h>
36 #include <sys/types.h>
37
38 #include "asterisk/file.h"
39 #include "asterisk/logger.h"
40 #include "asterisk/channel.h"
41 #include "asterisk/pbx.h"
42 #include "asterisk/module.h"
43 #include "asterisk/options.h"
44 #include "asterisk/config.h"
45 #include "asterisk/utils.h"
46 #include "asterisk/lock.h"
47
48 #define MAX_ARGS 80
49
50 /* special result value used to force macro exit */
51 #define MACRO_EXIT_RESULT 1024
52
53 static char *descrip =
54 "  Macro(macroname|arg1|arg2...): Executes a macro using the context\n"
55 "'macro-<macroname>', jumping to the 's' extension of that context and\n"
56 "executing each step, then returning when the steps end. \n"
57 "The calling extension, context, and priority are stored in ${MACRO_EXTEN}, \n"
58 "${MACRO_CONTEXT} and ${MACRO_PRIORITY} respectively.  Arguments become\n"
59 "${ARG1}, ${ARG2}, etc in the macro context.\n"
60 "If you Goto out of the Macro context, the Macro will terminate and control\n"
61 "will be returned at the location of the Goto.\n"
62 "If ${MACRO_OFFSET} is set at termination, Macro will attempt to continue\n"
63 "at priority MACRO_OFFSET + N + 1 if such a step exists, and N + 1 otherwise.\n"
64 "WARNING: Because of the way Macro is implemented (it executes the priorities\n"
65 "         contained within it via sub-engine), and a fixed per-thread\n"
66 "         memory stack allowance, macros are limited to 7 levels\n"
67 "         of nesting (macro calling macro calling macro, etc.); It\n"
68 "         may be possible that stack-intensive applications in deeply nested macros\n"
69 "         could cause asterisk to crash earlier than this limit. It is advised that\n"
70 "         if you need to deeply nest macro calls, that you use the Gosub application\n"
71 "         (now allows arguments like a Macro) with explict Return() calls instead.\n";
72
73 static char *if_descrip =
74 "  MacroIf(<expr>?macroname_a[|arg1][:macroname_b[|arg1]])\n"
75 "Executes macro defined in <macroname_a> if <expr> is true\n"
76 "(otherwise <macroname_b> if provided)\n"
77 "Arguments and return values as in application macro()\n";
78
79 static char *exclusive_descrip =
80 "  MacroExclusive(macroname|arg1|arg2...):\n"
81 "Executes macro defined in the context 'macro-macroname'\n"
82 "Only one call at a time may run the macro.\n"
83 "(we'll wait if another call is busy executing in the Macro)\n"
84 "Arguments and return values as in application Macro()\n";
85
86 static char *exit_descrip =
87 "  MacroExit():\n"
88 "Causes the currently running macro to exit as if it had\n"
89 "ended normally by running out of priorities to execute.\n"
90 "If used outside a macro, will likely cause unexpected\n"
91 "behavior.\n";
92
93 static char *app = "Macro";
94 static char *if_app = "MacroIf";
95 static char *exclusive_app = "MacroExclusive";
96 static char *exit_app = "MacroExit";
97
98 static char *synopsis = "Macro Implementation";
99 static char *if_synopsis = "Conditional Macro Implementation";
100 static char *exclusive_synopsis = "Exclusive Macro Implementation";
101 static char *exit_synopsis = "Exit From Macro";
102
103
104 static int _macro_exec(struct ast_channel *chan, void *data, int exclusive)
105 {
106         const char *s;
107
108         char *tmp;
109         char *cur, *rest;
110         char *macro;
111         char fullmacro[80];
112         char varname[80];
113         char *oldargs[MAX_ARGS + 1] = { NULL, };
114         int argc, x;
115         int res=0;
116         char oldexten[256]="";
117         int oldpriority;
118         char pc[80], depthc[12];
119         char oldcontext[AST_MAX_CONTEXT] = "";
120         const char *inhangupc;
121         int offset, depth = 0, maxdepth = 7;
122         int setmacrocontext=0;
123         int autoloopflag, dead = 0, inhangup = 0;
124   
125         char *save_macro_exten;
126         char *save_macro_context;
127         char *save_macro_priority;
128         char *save_macro_offset;
129         struct ast_module_user *u;
130  
131         if (ast_strlen_zero(data)) {
132                 ast_log(LOG_WARNING, "Macro() requires arguments. See \"show application macro\" for help.\n");
133                 return -1;
134         }
135
136         u = ast_module_user_add(chan);
137
138         /* does the user want a deeper rabbit hole? */
139         s = pbx_builtin_getvar_helper(chan, "MACRO_RECURSION");
140         if (s)
141                 sscanf(s, "%d", &maxdepth);
142
143         /* Count how many levels deep the rabbit hole goes */
144         s = pbx_builtin_getvar_helper(chan, "MACRO_DEPTH");
145         if (s)
146                 sscanf(s, "%d", &depth);
147         /* Used for detecting whether to return when a Macro is called from another Macro after hangup */
148         if (strcmp(chan->exten, "h") == 0)
149                 pbx_builtin_setvar_helper(chan, "MACRO_IN_HANGUP", "1");
150         inhangupc = pbx_builtin_getvar_helper(chan, "MACRO_IN_HANGUP");
151         if (!ast_strlen_zero(inhangupc))
152                 sscanf(inhangupc, "%d", &inhangup);
153
154         if (depth >= maxdepth) {
155                 ast_log(LOG_ERROR, "Macro():  possible infinite loop detected.  Returning early.\n");
156                 ast_module_user_remove(u);
157                 return 0;
158         }
159         snprintf(depthc, sizeof(depthc), "%d", depth + 1);
160         pbx_builtin_setvar_helper(chan, "MACRO_DEPTH", depthc);
161
162         tmp = ast_strdupa(data);
163         rest = tmp;
164         macro = strsep(&rest, "|");
165         if (ast_strlen_zero(macro)) {
166                 ast_log(LOG_WARNING, "Invalid macro name specified\n");
167                 ast_module_user_remove(u);
168                 return 0;
169         }
170
171         snprintf(fullmacro, sizeof(fullmacro), "macro-%s", macro);
172         if (!ast_exists_extension(chan, fullmacro, "s", 1, chan->cid.cid_num)) {
173                 if (!ast_context_find(fullmacro)) 
174                         ast_log(LOG_WARNING, "No such context '%s' for macro '%s'\n", fullmacro, macro);
175                 else
176                         ast_log(LOG_WARNING, "Context '%s' for macro '%s' lacks 's' extension, priority 1\n", fullmacro, macro);
177                 ast_module_user_remove(u);
178                 return 0;
179         }
180
181         /* If we are to run the macro exclusively, take the mutex */
182         if (exclusive) {
183                 if (option_debug)
184                         ast_log(LOG_DEBUG, "Locking macrolock for '%s'\n", fullmacro);
185                 ast_autoservice_start(chan);
186                 if (ast_context_lockmacro(fullmacro)) {
187                         ast_log(LOG_WARNING, "Failed to lock macro '%s' as in-use\n", fullmacro);
188                         ast_autoservice_stop(chan);
189                         ast_module_user_remove(u);
190
191                         return 0;
192                 }
193                 ast_autoservice_stop(chan);
194         }
195         
196         /* Save old info */
197         oldpriority = chan->priority;
198         ast_copy_string(oldexten, chan->exten, sizeof(oldexten));
199         ast_copy_string(oldcontext, chan->context, sizeof(oldcontext));
200         if (ast_strlen_zero(chan->macrocontext)) {
201                 ast_copy_string(chan->macrocontext, chan->context, sizeof(chan->macrocontext));
202                 ast_copy_string(chan->macroexten, chan->exten, sizeof(chan->macroexten));
203                 chan->macropriority = chan->priority;
204                 setmacrocontext=1;
205         }
206         argc = 1;
207         /* Save old macro variables */
208         save_macro_exten = ast_strdup(pbx_builtin_getvar_helper(chan, "MACRO_EXTEN"));
209         pbx_builtin_setvar_helper(chan, "MACRO_EXTEN", oldexten);
210
211         save_macro_context = ast_strdup(pbx_builtin_getvar_helper(chan, "MACRO_CONTEXT"));
212         pbx_builtin_setvar_helper(chan, "MACRO_CONTEXT", oldcontext);
213
214         save_macro_priority = ast_strdup(pbx_builtin_getvar_helper(chan, "MACRO_PRIORITY"));
215         snprintf(pc, sizeof(pc), "%d", oldpriority);
216         pbx_builtin_setvar_helper(chan, "MACRO_PRIORITY", pc);
217   
218         save_macro_offset = ast_strdup(pbx_builtin_getvar_helper(chan, "MACRO_OFFSET"));
219         pbx_builtin_setvar_helper(chan, "MACRO_OFFSET", NULL);
220
221         /* Setup environment for new run */
222         chan->exten[0] = 's';
223         chan->exten[1] = '\0';
224         ast_copy_string(chan->context, fullmacro, sizeof(chan->context));
225         chan->priority = 1;
226
227         while((cur = strsep(&rest, "|")) && (argc < MAX_ARGS)) {
228                 const char *s;
229                 /* Save copy of old arguments if we're overwriting some, otherwise
230                 let them pass through to the other macro */
231                 snprintf(varname, sizeof(varname), "ARG%d", argc);
232                 s = pbx_builtin_getvar_helper(chan, varname);
233                 if (s)
234                         oldargs[argc] = ast_strdup(s);
235                 pbx_builtin_setvar_helper(chan, varname, cur);
236                 argc++;
237         }
238         autoloopflag = ast_test_flag(chan, AST_FLAG_IN_AUTOLOOP);
239         ast_set_flag(chan, AST_FLAG_IN_AUTOLOOP);
240         while(ast_exists_extension(chan, chan->context, chan->exten, chan->priority, chan->cid.cid_num)) {
241                 /* Reset the macro depth, if it was changed in the last iteration */
242                 pbx_builtin_setvar_helper(chan, "MACRO_DEPTH", depthc);
243                 if ((res = ast_spawn_extension(chan, chan->context, chan->exten, chan->priority, chan->cid.cid_num))) {
244                         /* Something bad happened, or a hangup has been requested. */
245                         if (((res >= '0') && (res <= '9')) || ((res >= 'A') && (res <= 'F')) ||
246                         (res == '*') || (res == '#')) {
247                                 /* Just return result as to the previous application as if it had been dialed */
248                                 if (option_debug)
249                                         ast_log(LOG_DEBUG, "Oooh, got something to jump out with ('%c')!\n", res);
250                                 break;
251                         }
252                         switch(res) {
253                         case MACRO_EXIT_RESULT:
254                                 res = 0;
255                                 goto out;
256                         case AST_PBX_KEEPALIVE:
257                                 if (option_debug)
258                                         ast_log(LOG_DEBUG, "Spawn extension (%s,%s,%d) exited KEEPALIVE in macro %s on '%s'\n", chan->context, chan->exten, chan->priority, macro, chan->name);
259                                 else if (option_verbose > 1)
260                                         ast_verbose( VERBOSE_PREFIX_2 "Spawn extension (%s, %s, %d) exited KEEPALIVE in macro '%s' on '%s'\n", chan->context, chan->exten, chan->priority, macro, chan->name);
261                                 goto out;
262                                 break;
263                         default:
264                                 if (option_debug)
265                                         ast_log(LOG_DEBUG, "Spawn extension (%s,%s,%d) exited non-zero on '%s' in macro '%s'\n", chan->context, chan->exten, chan->priority, chan->name, macro);
266                                 else if (option_verbose > 1)
267                                         ast_verbose( VERBOSE_PREFIX_2 "Spawn extension (%s, %s, %d) exited non-zero on '%s' in macro '%s'\n", chan->context, chan->exten, chan->priority, chan->name, macro);
268                                 dead = 1;
269                                 goto out;
270                         }
271                 }
272                 if (strcasecmp(chan->context, fullmacro)) {
273                         if (option_verbose > 1)
274                                 ast_verbose(VERBOSE_PREFIX_2 "Channel '%s' jumping out of macro '%s'\n", chan->name, macro);
275                         break;
276                 }
277                 /* don't stop executing extensions when we're in "h" */
278                 if (chan->_softhangup && !inhangup) {
279                         if (option_debug)
280                                 ast_log(LOG_DEBUG, "Extension %s, macroexten %s, priority %d returned normally even though call was hung up\n",
281                                         chan->exten, chan->macroexten, chan->priority);
282                         goto out;
283                 }
284                 chan->priority++;
285         }
286         out:
287         /* Reset the depth back to what it was when the routine was entered (like if we called Macro recursively) */
288         snprintf(depthc, sizeof(depthc), "%d", depth);
289         if (!dead) {
290                 pbx_builtin_setvar_helper(chan, "MACRO_DEPTH", depthc);
291                 ast_set2_flag(chan, autoloopflag, AST_FLAG_IN_AUTOLOOP);
292         }
293
294         for (x = 1; x < argc; x++) {
295                 /* Restore old arguments and delete ours */
296                 snprintf(varname, sizeof(varname), "ARG%d", x);
297                 if (oldargs[x]) {
298                         if (!dead)
299                                 pbx_builtin_setvar_helper(chan, varname, oldargs[x]);
300                         free(oldargs[x]);
301                 } else if (!dead) {
302                         pbx_builtin_setvar_helper(chan, varname, NULL);
303                 }
304         }
305
306         /* Restore macro variables */
307         if (!dead) {
308                 pbx_builtin_setvar_helper(chan, "MACRO_EXTEN", save_macro_exten);
309                 pbx_builtin_setvar_helper(chan, "MACRO_CONTEXT", save_macro_context);
310                 pbx_builtin_setvar_helper(chan, "MACRO_PRIORITY", save_macro_priority);
311         }
312         if (save_macro_exten)
313                 free(save_macro_exten);
314         if (save_macro_context)
315                 free(save_macro_context);
316         if (save_macro_priority)
317                 free(save_macro_priority);
318
319         if (!dead && setmacrocontext) {
320                 chan->macrocontext[0] = '\0';
321                 chan->macroexten[0] = '\0';
322                 chan->macropriority = 0;
323         }
324
325         if (!dead && !strcasecmp(chan->context, fullmacro)) {
326                 /* If we're leaving the macro normally, restore original information */
327                 chan->priority = oldpriority;
328                 ast_copy_string(chan->context, oldcontext, sizeof(chan->context));
329                 if (!(chan->_softhangup & AST_SOFTHANGUP_ASYNCGOTO)) {
330                         /* Copy the extension, so long as we're not in softhangup, where we could be given an asyncgoto */
331                         const char *offsets;
332                         ast_copy_string(chan->exten, oldexten, sizeof(chan->exten));
333                         if ((offsets = pbx_builtin_getvar_helper(chan, "MACRO_OFFSET"))) {
334                                 /* Handle macro offset if it's set by checking the availability of step n + offset + 1, otherwise continue
335                                 normally if there is any problem */
336                                 if (sscanf(offsets, "%d", &offset) == 1) {
337                                         if (ast_exists_extension(chan, chan->context, chan->exten, chan->priority + offset + 1, chan->cid.cid_num)) {
338                                                 chan->priority += offset;
339                                         }
340                                 }
341                         }
342                 }
343         }
344
345         if (!dead)
346                 pbx_builtin_setvar_helper(chan, "MACRO_OFFSET", save_macro_offset);
347         if (save_macro_offset)
348                 free(save_macro_offset);
349
350         /* Unlock the macro */
351         if (exclusive) {
352                 if (option_debug)
353                         ast_log(LOG_DEBUG, "Unlocking macrolock for '%s'\n", fullmacro);
354                 if (ast_context_unlockmacro(fullmacro)) {
355                         ast_log(LOG_ERROR, "Failed to unlock macro '%s' - that isn't good\n", fullmacro);
356                         res = 0;
357                 }
358         }
359         
360         ast_module_user_remove(u);
361
362         return res;
363 }
364
365 static int macro_exec(struct ast_channel *chan, void *data)
366 {
367         return _macro_exec(chan, data, 0);
368 }
369
370 static int macroexclusive_exec(struct ast_channel *chan, void *data)
371 {
372         return _macro_exec(chan, data, 1);
373 }
374
375 static int macroif_exec(struct ast_channel *chan, void *data) 
376 {
377         char *expr = NULL, *label_a = NULL, *label_b = NULL;
378         int res = 0;
379         struct ast_module_user *u;
380
381         u = ast_module_user_add(chan);
382
383         if (!(expr = ast_strdupa(data))) {
384                 ast_module_user_remove(u);
385                 return -1;
386         }
387
388         if ((label_a = strchr(expr, '?'))) {
389                 *label_a = '\0';
390                 label_a++;
391                 if ((label_b = strchr(label_a, ':'))) {
392                         *label_b = '\0';
393                         label_b++;
394                 }
395                 if (pbx_checkcondition(expr))
396                         macro_exec(chan, label_a);
397                 else if (label_b) 
398                         macro_exec(chan, label_b);
399         } else
400                 ast_log(LOG_WARNING, "Invalid Syntax.\n");
401
402         ast_module_user_remove(u);
403
404         return res;
405 }
406                         
407 static int macro_exit_exec(struct ast_channel *chan, void *data)
408 {
409         return MACRO_EXIT_RESULT;
410 }
411
412 static int unload_module(void)
413 {
414         int res;
415
416         res = ast_unregister_application(if_app);
417         res |= ast_unregister_application(exit_app);
418         res |= ast_unregister_application(app);
419         res |= ast_unregister_application(exclusive_app);
420
421         ast_module_user_hangup_all();
422
423         return res;
424 }
425
426 static int load_module(void)
427 {
428         int res;
429
430         res = ast_register_application(exit_app, macro_exit_exec, exit_synopsis, exit_descrip);
431         res |= ast_register_application(if_app, macroif_exec, if_synopsis, if_descrip);
432         res |= ast_register_application(exclusive_app, macroexclusive_exec, exclusive_synopsis, exclusive_descrip);
433         res |= ast_register_application(app, macro_exec, synopsis, descrip);
434
435         return res;
436 }
437
438 AST_MODULE_INFO_STANDARD(ASTERISK_GPL_KEY, "Extension Macros");