make 'goto' APIs aware of auto-processing loops, so they know exactly when to set...
[asterisk/asterisk.git] / apps / app_macro.c
1 /*
2  * Asterisk -- A telephony toolkit for Linux.
3  *
4  * Macro Implementation
5  * 
6  * Copyright (C) 2003 - 2005, Digium, Inc.
7  *
8  * Mark Spencer <markster@digium.com>
9  *
10  * This program is free software, distributed under the terms of
11  * the GNU General Public License
12  */
13
14 #include <sys/types.h>
15 #include "asterisk/file.h"
16 #include "asterisk/logger.h"
17 #include "asterisk/channel.h"
18 #include "asterisk/pbx.h"
19 #include "asterisk/module.h"
20 #include "asterisk/options.h"
21 #include "asterisk/config.h"
22 #include "asterisk/utils.h"
23 #include "asterisk/lock.h"
24 #include <stdlib.h>
25 #include <unistd.h>
26 #include <string.h>
27 #include <stdlib.h>
28
29 #define MAX_ARGS 80
30
31 /* special result value used to force macro exit */
32 #define MACRO_EXIT_RESULT 1024
33
34 static char *tdesc = "Extension Macros";
35
36 static char *descrip =
37 "  Macro(macroname|arg1|arg2...): Executes a macro using the context\n"
38 "'macro-<macroname>', jumping to the 's' extension of that context and\n"
39 "executing each step, then returning when the steps end. \n"
40 "The calling extension, context, and priority are stored in ${MACRO_EXTEN}, \n"
41 "${MACRO_CONTEXT} and ${MACRO_PRIORITY} respectively.  Arguments become\n"
42 "${ARG1}, ${ARG2}, etc in the macro context.\n"
43 "If you Goto out of the Macro context, the Macro will terminate and control\n"
44 "will be returned at the location of the Goto.\n"
45 "Macro returns -1 if any step in the macro returns -1, and 0 otherwise.\n" 
46 "If ${MACRO_OFFSET} is set at termination, Macro will attempt to continue\n"
47 "at priority MACRO_OFFSET + N + 1 if such a step exists, and N + 1 otherwise.\n";
48
49 static char *if_descrip =
50 "  MacroIf(<expr>?macroname_a[|arg1][:macroname_b[|arg1]])\n"
51 "Executes macro defined in <macroname_a> if <expr> is true\n"
52 "(otherwise <macroname_b> if provided)\n"
53 "Arguments and return values as in application macro()\n";
54
55 static char *exit_descrip =
56 "  MacroExit():\n"
57 "Causes the currently running macro to exit as if it had\n"
58 "ended normally by running out of priorities to execute.\n"
59 "If used outside a macro, will likely cause unexpected\n"
60 "behavior.\n";
61
62 static char *app = "Macro";
63 static char *if_app = "MacroIf";
64 static char *exit_app = "MacroExit";
65
66 static char *synopsis = "Macro Implementation";
67 static char *if_synopsis = "Conditional Macro Implementation";
68 static char *exit_synopsis = "Exit From Macro";
69
70 STANDARD_LOCAL_USER;
71
72 LOCAL_USER_DECL;
73
74 static int macro_exec(struct ast_channel *chan, void *data)
75 {
76         char *tmp;
77         char *cur, *rest;
78         char *macro;
79         char fullmacro[80];
80         char varname[80];
81         char *oldargs[MAX_ARGS + 1] = { NULL, };
82         int argc, x;
83         int res=0;
84         char oldexten[256]="";
85         int oldpriority;
86         char pc[80];
87         char oldcontext[256] = "";
88         char *offsets;
89         int offset;
90         int setmacrocontext=0;
91         int autoloopflag;
92   
93         char *save_macro_exten;
94         char *save_macro_context;
95         char *save_macro_priority;
96         char *save_macro_offset;
97         struct localuser *u;
98   
99         if (!data || ast_strlen_zero(data)) {
100                 ast_log(LOG_WARNING, "Macro() requires arguments. See \"show application macro\" for help.\n");
101                 return 0;
102         }
103
104         tmp = ast_strdupa((char *) data);
105         rest = tmp;
106         macro = strsep(&rest, "|");
107         if (!macro || ast_strlen_zero(macro)) {
108                 ast_log(LOG_WARNING, "Invalid macro name specified\n");
109                 return 0;
110         }
111         snprintf(fullmacro, sizeof(fullmacro), "macro-%s", macro);
112         if (!ast_exists_extension(chan, fullmacro, "s", 1, chan->cid.cid_num)) {
113                 if (!ast_context_find(fullmacro)) 
114                         ast_log(LOG_WARNING, "No such context '%s' for macro '%s'\n", fullmacro, macro);
115                 else
116                         ast_log(LOG_WARNING, "Context '%s' for macro '%s' lacks 's' extension, priority 1\n", fullmacro, macro);
117                 return 0;
118         }
119
120         LOCAL_USER_ADD(u);
121         /* Save old info */
122         oldpriority = chan->priority;
123         strncpy(oldexten, chan->exten, sizeof(oldexten) - 1);
124         strncpy(oldcontext, chan->context, sizeof(oldcontext) - 1);
125         if (ast_strlen_zero(chan->macrocontext)) {
126                 strncpy(chan->macrocontext, chan->context, sizeof(chan->macrocontext) - 1);
127                 strncpy(chan->macroexten, chan->exten, sizeof(chan->macroexten) - 1);
128                 chan->macropriority = chan->priority;
129                 setmacrocontext=1;
130         }
131         argc = 1;
132         /* Save old macro variables */
133         save_macro_exten = pbx_builtin_getvar_helper(chan, "MACRO_EXTEN");
134         if (save_macro_exten) 
135                 save_macro_exten = strdup(save_macro_exten);
136         pbx_builtin_setvar_helper(chan, "MACRO_EXTEN", oldexten);
137
138         save_macro_context = pbx_builtin_getvar_helper(chan, "MACRO_CONTEXT");
139         if (save_macro_context)
140                 save_macro_context = strdup(save_macro_context);
141         pbx_builtin_setvar_helper(chan, "MACRO_CONTEXT", oldcontext);
142
143         save_macro_priority = pbx_builtin_getvar_helper(chan, "MACRO_PRIORITY");
144         if (save_macro_priority) 
145                 save_macro_priority = strdup(save_macro_priority);
146         snprintf(pc, sizeof(pc), "%d", oldpriority);
147         pbx_builtin_setvar_helper(chan, "MACRO_PRIORITY", pc);
148   
149         save_macro_offset = pbx_builtin_getvar_helper(chan, "MACRO_OFFSET");
150         if (save_macro_offset) 
151                 save_macro_offset = strdup(save_macro_offset);
152         pbx_builtin_setvar_helper(chan, "MACRO_OFFSET", NULL);
153
154         /* Setup environment for new run */
155         chan->exten[0] = 's';
156         chan->exten[1] = '\0';
157         strncpy(chan->context, fullmacro, sizeof(chan->context) - 1);
158         chan->priority = 1;
159
160         while((cur = strsep(&rest, "|")) && (argc < MAX_ARGS)) {
161                 /* Save copy of old arguments if we're overwriting some, otherwise
162                 let them pass through to the other macro */
163                 snprintf(varname, sizeof(varname), "ARG%d", argc);
164                 oldargs[argc] = pbx_builtin_getvar_helper(chan, varname);
165                 if (oldargs[argc])
166                         oldargs[argc] = strdup(oldargs[argc]);
167                 pbx_builtin_setvar_helper(chan, varname, cur);
168                 argc++;
169         }
170         autoloopflag = ast_test_flag(chan, AST_FLAG_IN_AUTOLOOP);
171         ast_set_flag(chan, AST_FLAG_IN_AUTOLOOP);
172         while(ast_exists_extension(chan, chan->context, chan->exten, chan->priority, chan->cid.cid_num)) {
173                 if ((res = ast_spawn_extension(chan, chan->context, chan->exten, chan->priority, chan->cid.cid_num))) {
174                         /* Something bad happened, or a hangup has been requested. */
175                         if (((res >= '0') && (res <= '9')) || ((res >= 'A') && (res <= 'F')) ||
176                         (res == '*') || (res == '#')) {
177                                 /* Just return result as to the previous application as if it had been dialed */
178                                 ast_log(LOG_DEBUG, "Oooh, got something to jump out with ('%c')!\n", res);
179                                 break;
180                         }
181                         switch(res) {
182                         case MACRO_EXIT_RESULT:
183                                 res = 0;
184                                 goto out;
185                         case AST_PBX_KEEPALIVE:
186                                 if (option_debug)
187                                         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);
188                                 else if (option_verbose > 1)
189                                         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);
190                                 goto out;
191                                 break;
192                         default:
193                                 if (option_debug)
194                                         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);
195                                 else if (option_verbose > 1)
196                                         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);
197                                 goto out;
198                         }
199                 }
200                 if (strcasecmp(chan->context, fullmacro)) {
201                         if (option_verbose > 1)
202                                 ast_verbose(VERBOSE_PREFIX_2 "Channel '%s' jumping out of macro '%s'\n", chan->name, macro);
203                         break;
204                 }
205                 /* don't stop executing extensions when we're in "h" */
206                 if (chan->_softhangup && strcasecmp(oldexten,"h")) {
207                         ast_log(LOG_DEBUG, "Extension %s, priority %d returned normally even though call was hung up\n",
208                                 chan->exten, chan->priority);
209                         goto out;
210                 }
211                 chan->priority++;
212         }
213         out:
214         ast_set2_flag(chan, autoloopflag, AST_FLAG_IN_AUTOLOOP);
215         for (x=1; x<argc; x++) {
216                 /* Restore old arguments and delete ours */
217                 snprintf(varname, sizeof(varname), "ARG%d", x);
218                 if (oldargs[x]) {
219                         pbx_builtin_setvar_helper(chan, varname, oldargs[x]);
220                         free(oldargs[x]);
221                 } else {
222                         pbx_builtin_setvar_helper(chan, varname, NULL);
223                 }
224         }
225
226         /* Restore macro variables */
227         pbx_builtin_setvar_helper(chan, "MACRO_EXTEN", save_macro_exten);
228         if (save_macro_exten)
229                 free(save_macro_exten);
230         pbx_builtin_setvar_helper(chan, "MACRO_CONTEXT", save_macro_context);
231         if (save_macro_context)
232                 free(save_macro_context);
233         pbx_builtin_setvar_helper(chan, "MACRO_PRIORITY", save_macro_priority);
234         if (save_macro_priority)
235                 free(save_macro_priority);
236         if (setmacrocontext) {
237                 chan->macrocontext[0] = '\0';
238                 chan->macroexten[0] = '\0';
239                 chan->macropriority = 0;
240         }
241
242         if (!strcasecmp(chan->context, fullmacro)) {
243                 /* If we're leaving the macro normally, restore original information */
244                 chan->priority = oldpriority;
245                 strncpy(chan->context, oldcontext, sizeof(chan->context) - 1);
246                 if (!(chan->_softhangup & AST_SOFTHANGUP_ASYNCGOTO)) {
247                         /* Copy the extension, so long as we're not in softhangup, where we could be given an asyncgoto */
248                         strncpy(chan->exten, oldexten, sizeof(chan->exten) - 1);
249                         if ((offsets = pbx_builtin_getvar_helper(chan, "MACRO_OFFSET"))) {
250                                 /* Handle macro offset if it's set by checking the availability of step n + offset + 1, otherwise continue
251                                 normally if there is any problem */
252                                 if (sscanf(offsets, "%d", &offset) == 1) {
253                                         if (ast_exists_extension(chan, chan->context, chan->exten, chan->priority + offset + 1, chan->cid.cid_num)) {
254                                                 chan->priority += offset;
255                                         }
256                                 }
257                         }
258                 }
259         }
260
261         pbx_builtin_setvar_helper(chan, "MACRO_OFFSET", save_macro_offset);
262         if (save_macro_offset)
263                 free(save_macro_offset);
264         LOCAL_USER_REMOVE(u);
265         return res;
266 }
267
268 static int macroif_exec(struct ast_channel *chan, void *data) 
269 {
270         char *expr = NULL, *label_a = NULL, *label_b = NULL;
271         int res = 0;
272
273         if((expr = ast_strdupa((char *) data))) {
274                 if ((label_a = strchr(expr, '?'))) {
275                         *label_a = '\0';
276                         label_a++;
277                         if ((label_b = strchr(label_a, ':'))) {
278                                 *label_b = '\0';
279                                 label_b++;
280                         }
281                         if (ast_true(expr))
282                                 macro_exec(chan, label_a);
283                         else if (label_b) 
284                                 macro_exec(chan, label_b);
285                         
286                 } else
287                         ast_log(LOG_WARNING, "Invalid Syntax.\n");
288         } else 
289                 ast_log(LOG_ERROR, "Out of Memory!\n");
290         return res;
291 }
292                         
293 static int macro_exit_exec(struct ast_channel *chan, void *data)
294 {
295         return MACRO_EXIT_RESULT;
296 }
297
298 int unload_module(void)
299 {
300         STANDARD_HANGUP_LOCALUSERS;
301         ast_unregister_application(if_app);
302         ast_unregister_application(exit_app);
303         return ast_unregister_application(app);
304 }
305
306 int load_module(void)
307 {
308         ast_register_application(exit_app, macro_exit_exec, exit_synopsis, exit_descrip);
309         ast_register_application(if_app, macroif_exec, if_synopsis, if_descrip);
310         return ast_register_application(app, macro_exec, synopsis, descrip);
311 }
312
313 char *description(void)
314 {
315         return tdesc;
316 }
317
318 int usecount(void)
319 {
320         int res;
321         STANDARD_USECOUNT(res);
322         return res;
323 }
324
325 char *key()
326 {
327         return ASTERISK_GPL_KEY;
328 }