5adc0a07f9c223ad563ff7acf3b8b1448a119980
[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
65 static char *if_descrip =
66 "  MacroIf(<expr>?macroname_a[|arg1][:macroname_b[|arg1]])\n"
67 "Executes macro defined in <macroname_a> if <expr> is true\n"
68 "(otherwise <macroname_b> if provided)\n"
69 "Arguments and return values as in application macro()\n";
70
71 static char *exclusive_descrip =
72 "  MacroExclusive(macroname|arg1|arg2...):\n"
73 "Executes macro defined in the context 'macro-macroname'\n"
74 "Only one call at a time may run the macro.\n"
75 "(we'll wait if another call is busy executing in the Macro)\n"
76 "Arguments and return values as in application Macro()\n";
77
78 static char *exit_descrip =
79 "  MacroExit():\n"
80 "Causes the currently running macro to exit as if it had\n"
81 "ended normally by running out of priorities to execute.\n"
82 "If used outside a macro, will likely cause unexpected\n"
83 "behavior.\n";
84
85 static char *app = "Macro";
86 static char *if_app = "MacroIf";
87 static char *exclusive_app = "MacroExclusive";
88 static char *exit_app = "MacroExit";
89
90 static char *synopsis = "Macro Implementation";
91 static char *if_synopsis = "Conditional Macro Implementation";
92 static char *exclusive_synopsis = "Exclusive Macro Implementation";
93 static char *exit_synopsis = "Exit From Macro";
94
95
96 static int _macro_exec(struct ast_channel *chan, void *data, int exclusive)
97 {
98         const char *s;
99
100         char *tmp;
101         char *cur, *rest;
102         char *macro;
103         char fullmacro[80];
104         char varname[80];
105         char *oldargs[MAX_ARGS + 1] = { NULL, };
106         int argc, x;
107         int res=0;
108         char oldexten[256]="";
109         int oldpriority;
110         char pc[80], depthc[12];
111         char oldcontext[AST_MAX_CONTEXT] = "";
112         int offset, depth = 0, maxdepth = 7;
113         int setmacrocontext=0;
114         int autoloopflag, dead = 0;
115   
116         char *save_macro_exten;
117         char *save_macro_context;
118         char *save_macro_priority;
119         char *save_macro_offset;
120         struct ast_module_user *u;
121  
122         if (ast_strlen_zero(data)) {
123                 ast_log(LOG_WARNING, "Macro() requires arguments. See \"show application macro\" for help.\n");
124                 return -1;
125         }
126
127         u = ast_module_user_add(chan);
128
129         /* does the user want a deeper rabbit hole? */
130         s = pbx_builtin_getvar_helper(chan, "MACRO_RECURSION");
131         if (s)
132                 sscanf(s, "%d", &maxdepth);
133
134         /* Count how many levels deep the rabbit hole goes */
135         s = pbx_builtin_getvar_helper(chan, "MACRO_DEPTH");
136         if (s)
137                 sscanf(s, "%d", &depth);
138         if (depth >= maxdepth) {
139                 ast_log(LOG_ERROR, "Macro():  possible infinite loop detected.  Returning early.\n");
140                 ast_module_user_remove(u);
141                 return 0;
142         }
143         snprintf(depthc, sizeof(depthc), "%d", depth + 1);
144         pbx_builtin_setvar_helper(chan, "MACRO_DEPTH", depthc);
145
146         tmp = ast_strdupa(data);
147         rest = tmp;
148         macro = strsep(&rest, "|");
149         if (ast_strlen_zero(macro)) {
150                 ast_log(LOG_WARNING, "Invalid macro name specified\n");
151                 ast_module_user_remove(u);
152                 return 0;
153         }
154
155         snprintf(fullmacro, sizeof(fullmacro), "macro-%s", macro);
156         if (!ast_exists_extension(chan, fullmacro, "s", 1, chan->cid.cid_num)) {
157                 if (!ast_context_find(fullmacro)) 
158                         ast_log(LOG_WARNING, "No such context '%s' for macro '%s'\n", fullmacro, macro);
159                 else
160                         ast_log(LOG_WARNING, "Context '%s' for macro '%s' lacks 's' extension, priority 1\n", fullmacro, macro);
161                 ast_module_user_remove(u);
162                 return 0;
163         }
164
165         /* If we are to run the macro exclusively, take the mutex */
166         if (exclusive) {
167                 ast_log(LOG_DEBUG, "Locking macrolock for '%s'\n", fullmacro);
168                 ast_autoservice_start(chan);
169                 if (ast_context_lockmacro(fullmacro)) {
170                         ast_log(LOG_WARNING, "Failed to lock macro '%s' as in-use\n", fullmacro);
171                         ast_autoservice_stop(chan);
172                         ast_module_user_remove(u);
173
174                         return 0;
175                 }
176                 ast_autoservice_stop(chan);
177         }
178         
179         /* Save old info */
180         oldpriority = chan->priority;
181         ast_copy_string(oldexten, chan->exten, sizeof(oldexten));
182         ast_copy_string(oldcontext, chan->context, sizeof(oldcontext));
183         if (ast_strlen_zero(chan->macrocontext)) {
184                 ast_copy_string(chan->macrocontext, chan->context, sizeof(chan->macrocontext));
185                 ast_copy_string(chan->macroexten, chan->exten, sizeof(chan->macroexten));
186                 chan->macropriority = chan->priority;
187                 setmacrocontext=1;
188         }
189         argc = 1;
190         /* Save old macro variables */
191         save_macro_exten = ast_strdup(pbx_builtin_getvar_helper(chan, "MACRO_EXTEN"));
192         pbx_builtin_setvar_helper(chan, "MACRO_EXTEN", oldexten);
193
194         save_macro_context = ast_strdup(pbx_builtin_getvar_helper(chan, "MACRO_CONTEXT"));
195         pbx_builtin_setvar_helper(chan, "MACRO_CONTEXT", oldcontext);
196
197         save_macro_priority = ast_strdup(pbx_builtin_getvar_helper(chan, "MACRO_PRIORITY"));
198         snprintf(pc, sizeof(pc), "%d", oldpriority);
199         pbx_builtin_setvar_helper(chan, "MACRO_PRIORITY", pc);
200   
201         save_macro_offset = ast_strdup(pbx_builtin_getvar_helper(chan, "MACRO_OFFSET"));
202         pbx_builtin_setvar_helper(chan, "MACRO_OFFSET", NULL);
203
204         /* Setup environment for new run */
205         chan->exten[0] = 's';
206         chan->exten[1] = '\0';
207         ast_copy_string(chan->context, fullmacro, sizeof(chan->context));
208         chan->priority = 1;
209
210         while((cur = strsep(&rest, "|")) && (argc < MAX_ARGS)) {
211                 const char *s;
212                 /* Save copy of old arguments if we're overwriting some, otherwise
213                 let them pass through to the other macro */
214                 snprintf(varname, sizeof(varname), "ARG%d", argc);
215                 s = pbx_builtin_getvar_helper(chan, varname);
216                 if (s)
217                         oldargs[argc] = ast_strdup(s);
218                 pbx_builtin_setvar_helper(chan, varname, cur);
219                 argc++;
220         }
221         autoloopflag = ast_test_flag(chan, AST_FLAG_IN_AUTOLOOP);
222         ast_set_flag(chan, AST_FLAG_IN_AUTOLOOP);
223         while(ast_exists_extension(chan, chan->context, chan->exten, chan->priority, chan->cid.cid_num)) {
224                 /* Reset the macro depth, if it was changed in the last iteration */
225                 pbx_builtin_setvar_helper(chan, "MACRO_DEPTH", depthc);
226                 if ((res = ast_spawn_extension(chan, chan->context, chan->exten, chan->priority, chan->cid.cid_num))) {
227                         /* Something bad happened, or a hangup has been requested. */
228                         if (((res >= '0') && (res <= '9')) || ((res >= 'A') && (res <= 'F')) ||
229                         (res == '*') || (res == '#')) {
230                                 /* Just return result as to the previous application as if it had been dialed */
231                                 ast_log(LOG_DEBUG, "Oooh, got something to jump out with ('%c')!\n", res);
232                                 break;
233                         }
234                         switch(res) {
235                         case MACRO_EXIT_RESULT:
236                                 res = 0;
237                                 goto out;
238                         case AST_PBX_KEEPALIVE:
239                                 if (option_debug)
240                                         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);
241                                 else if (option_verbose > 1)
242                                         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);
243                                 goto out;
244                                 break;
245                         default:
246                                 if (option_debug)
247                                         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);
248                                 else if (option_verbose > 1)
249                                         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);
250                                 dead = 1;
251                                 goto out;
252                         }
253                 }
254                 if (strcasecmp(chan->context, fullmacro)) {
255                         if (option_verbose > 1)
256                                 ast_verbose(VERBOSE_PREFIX_2 "Channel '%s' jumping out of macro '%s'\n", chan->name, macro);
257                         break;
258                 }
259                 /* don't stop executing extensions when we're in "h" */
260                 if (chan->_softhangup && strcasecmp(chan->macroexten,"h")) {
261                         ast_log(LOG_DEBUG, "Extension %s, macroexten %s, priority %d returned normally even though call was hung up\n",
262                                 chan->exten, chan->macroexten, chan->priority);
263                         goto out;
264                 }
265                 chan->priority++;
266         }
267         out:
268         /* Reset the depth back to what it was when the routine was entered (like if we called Macro recursively) */
269         snprintf(depthc, sizeof(depthc), "%d", depth);
270         if (!dead) {
271                 pbx_builtin_setvar_helper(chan, "MACRO_DEPTH", depthc);
272                 ast_set2_flag(chan, autoloopflag, AST_FLAG_IN_AUTOLOOP);
273         }
274
275         for (x = 1; x < argc; x++) {
276                 /* Restore old arguments and delete ours */
277                 snprintf(varname, sizeof(varname), "ARG%d", x);
278                 if (oldargs[x]) {
279                         if (!dead)
280                                 pbx_builtin_setvar_helper(chan, varname, oldargs[x]);
281                         free(oldargs[x]);
282                 } else if (!dead) {
283                         pbx_builtin_setvar_helper(chan, varname, NULL);
284                 }
285         }
286
287         /* Restore macro variables */
288         if (!dead) {
289                 pbx_builtin_setvar_helper(chan, "MACRO_EXTEN", save_macro_exten);
290                 pbx_builtin_setvar_helper(chan, "MACRO_CONTEXT", save_macro_context);
291                 pbx_builtin_setvar_helper(chan, "MACRO_PRIORITY", save_macro_priority);
292         }
293         if (save_macro_exten)
294                 free(save_macro_exten);
295         if (save_macro_context)
296                 free(save_macro_context);
297         if (save_macro_priority)
298                 free(save_macro_priority);
299
300         if (!dead && setmacrocontext) {
301                 chan->macrocontext[0] = '\0';
302                 chan->macroexten[0] = '\0';
303                 chan->macropriority = 0;
304         }
305
306         if (!dead && !strcasecmp(chan->context, fullmacro)) {
307                 /* If we're leaving the macro normally, restore original information */
308                 chan->priority = oldpriority;
309                 ast_copy_string(chan->context, oldcontext, sizeof(chan->context));
310                 if (!(chan->_softhangup & AST_SOFTHANGUP_ASYNCGOTO)) {
311                         /* Copy the extension, so long as we're not in softhangup, where we could be given an asyncgoto */
312                         const char *offsets;
313                         ast_copy_string(chan->exten, oldexten, sizeof(chan->exten));
314                         if ((offsets = pbx_builtin_getvar_helper(chan, "MACRO_OFFSET"))) {
315                                 /* Handle macro offset if it's set by checking the availability of step n + offset + 1, otherwise continue
316                                 normally if there is any problem */
317                                 if (sscanf(offsets, "%d", &offset) == 1) {
318                                         if (ast_exists_extension(chan, chan->context, chan->exten, chan->priority + offset + 1, chan->cid.cid_num)) {
319                                                 chan->priority += offset;
320                                         }
321                                 }
322                         }
323                 }
324         }
325
326         if (!dead)
327                 pbx_builtin_setvar_helper(chan, "MACRO_OFFSET", save_macro_offset);
328         if (save_macro_offset)
329                 free(save_macro_offset);
330
331         /* Unlock the macro */
332         if (exclusive) {
333                 ast_log(LOG_DEBUG, "Unlocking macrolock for '%s'\n", fullmacro);
334                 if (ast_context_unlockmacro(fullmacro)) {
335                         ast_log(LOG_ERROR, "Failed to unlock macro '%s' - that isn't good\n", fullmacro);
336                         res = 0;
337                 }
338         }
339         
340         ast_module_user_remove(u);
341
342         return res;
343 }
344
345 static int macro_exec(struct ast_channel *chan, void *data)
346 {
347         return _macro_exec(chan, data, 0);
348 }
349
350 static int macroexclusive_exec(struct ast_channel *chan, void *data)
351 {
352         return _macro_exec(chan, data, 1);
353 }
354
355 static int macroif_exec(struct ast_channel *chan, void *data) 
356 {
357         char *expr = NULL, *label_a = NULL, *label_b = NULL;
358         int res = 0;
359         struct ast_module_user *u;
360
361         u = ast_module_user_add(chan);
362
363         if (!(expr = ast_strdupa(data))) {
364                 ast_module_user_remove(u);
365                 return -1;
366         }
367
368         if ((label_a = strchr(expr, '?'))) {
369                 *label_a = '\0';
370                 label_a++;
371                 if ((label_b = strchr(label_a, ':'))) {
372                         *label_b = '\0';
373                         label_b++;
374                 }
375                 if (pbx_checkcondition(expr))
376                         macro_exec(chan, label_a);
377                 else if (label_b) 
378                         macro_exec(chan, label_b);
379         } else
380                 ast_log(LOG_WARNING, "Invalid Syntax.\n");
381
382         ast_module_user_remove(u);
383
384         return res;
385 }
386                         
387 static int macro_exit_exec(struct ast_channel *chan, void *data)
388 {
389         return MACRO_EXIT_RESULT;
390 }
391
392 static int unload_module(void)
393 {
394         int res;
395
396         res = ast_unregister_application(if_app);
397         res |= ast_unregister_application(exit_app);
398         res |= ast_unregister_application(app);
399         res |= ast_unregister_application(exclusive_app);
400
401         ast_module_user_hangup_all();
402
403         return res;
404 }
405
406 static int load_module(void)
407 {
408         int res;
409
410         res = ast_register_application(exit_app, macro_exit_exec, exit_synopsis, exit_descrip);
411         res |= ast_register_application(if_app, macroif_exec, if_synopsis, if_descrip);
412         res |= ast_register_application(exclusive_app, macroexclusive_exec, exclusive_synopsis, exclusive_descrip);
413         res |= ast_register_application(app, macro_exec, synopsis, descrip);
414
415         return res;
416 }
417
418 AST_MODULE_INFO_STANDARD(ASTERISK_GPL_KEY, "Extension Macros");