use double-quotes instead of angle-brackets for non-system include files (bug #4058)
[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   
92         char *save_macro_exten;
93         char *save_macro_context;
94         char *save_macro_priority;
95         char *save_macro_offset;
96         struct localuser *u;
97   
98         if (!data || ast_strlen_zero(data)) {
99                 ast_log(LOG_WARNING, "Macro() requires arguments. See \"show application macro\" for help.\n");
100                 return 0;
101         }
102
103         tmp = ast_strdupa((char *) data);
104         rest = tmp;
105         macro = strsep(&rest, "|");
106         if (!macro || ast_strlen_zero(macro)) {
107                 ast_log(LOG_WARNING, "Invalid macro name specified\n");
108                 return 0;
109         }
110         snprintf(fullmacro, sizeof(fullmacro), "macro-%s", macro);
111         if (!ast_exists_extension(chan, fullmacro, "s", 1, chan->cid.cid_num)) {
112                 if (!ast_context_find(fullmacro)) 
113                         ast_log(LOG_WARNING, "No such context '%s' for macro '%s'\n", fullmacro, macro);
114                 else
115                         ast_log(LOG_WARNING, "Context '%s' for macro '%s' lacks 's' extension, priority 1\n", fullmacro, macro);
116                 return 0;
117         }
118
119         LOCAL_USER_ADD(u);
120         /* Save old info */
121         oldpriority = chan->priority;
122         strncpy(oldexten, chan->exten, sizeof(oldexten) - 1);
123         strncpy(oldcontext, chan->context, sizeof(oldcontext) - 1);
124         if (ast_strlen_zero(chan->macrocontext)) {
125                 strncpy(chan->macrocontext, chan->context, sizeof(chan->macrocontext) - 1);
126                 strncpy(chan->macroexten, chan->exten, sizeof(chan->macroexten) - 1);
127                 chan->macropriority = chan->priority;
128                 setmacrocontext=1;
129         }
130         argc = 1;
131         /* Save old macro variables */
132         save_macro_exten = pbx_builtin_getvar_helper(chan, "MACRO_EXTEN");
133         if (save_macro_exten) 
134                 save_macro_exten = strdup(save_macro_exten);
135         pbx_builtin_setvar_helper(chan, "MACRO_EXTEN", oldexten);
136
137         save_macro_context = pbx_builtin_getvar_helper(chan, "MACRO_CONTEXT");
138         if (save_macro_context)
139                 save_macro_context = strdup(save_macro_context);
140         pbx_builtin_setvar_helper(chan, "MACRO_CONTEXT", oldcontext);
141
142         save_macro_priority = pbx_builtin_getvar_helper(chan, "MACRO_PRIORITY");
143         if (save_macro_priority) 
144                 save_macro_priority = strdup(save_macro_priority);
145         snprintf(pc, sizeof(pc), "%d", oldpriority);
146         pbx_builtin_setvar_helper(chan, "MACRO_PRIORITY", pc);
147   
148         save_macro_offset = pbx_builtin_getvar_helper(chan, "MACRO_OFFSET");
149         if (save_macro_offset) 
150                 save_macro_offset = strdup(save_macro_offset);
151         pbx_builtin_setvar_helper(chan, "MACRO_OFFSET", NULL);
152
153         /* Setup environment for new run */
154         chan->exten[0] = 's';
155         chan->exten[1] = '\0';
156         strncpy(chan->context, fullmacro, sizeof(chan->context) - 1);
157         chan->priority = 1;
158
159         while((cur = strsep(&rest, "|")) && (argc < MAX_ARGS)) {
160                 /* Save copy of old arguments if we're overwriting some, otherwise
161                 let them pass through to the other macro */
162                 snprintf(varname, sizeof(varname), "ARG%d", argc);
163                 oldargs[argc] = pbx_builtin_getvar_helper(chan, varname);
164                 if (oldargs[argc])
165                         oldargs[argc] = strdup(oldargs[argc]);
166                 pbx_builtin_setvar_helper(chan, varname, cur);
167                 argc++;
168         }
169         while(ast_exists_extension(chan, chan->context, chan->exten, chan->priority, chan->cid.cid_num)) {
170                 if ((res = ast_spawn_extension(chan, chan->context, chan->exten, chan->priority, chan->cid.cid_num))) {
171                         /* Something bad happened, or a hangup has been requested. */
172                         if (((res >= '0') && (res <= '9')) || ((res >= 'A') && (res <= 'F')) ||
173                         (res == '*') || (res == '#')) {
174                                 /* Just return result as to the previous application as if it had been dialed */
175                                 ast_log(LOG_DEBUG, "Oooh, got something to jump out with ('%c')!\n", res);
176                                 break;
177                         }
178                         switch(res) {
179                         case MACRO_EXIT_RESULT:
180                                 res = 0;
181                                 goto out;
182                         case AST_PBX_KEEPALIVE:
183                                 if (option_debug)
184                                         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);
185                                 else if (option_verbose > 1)
186                                         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);
187                                 goto out;
188                                 break;
189                         default:
190                                 if (option_debug)
191                                         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);
192                                 else if (option_verbose > 1)
193                                         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);
194                                 goto out;
195                         }
196                 }
197                 if (strcasecmp(chan->context, fullmacro)) {
198                         if (option_verbose > 1)
199                                 ast_verbose(VERBOSE_PREFIX_2 "Channel '%s' jumping out of macro '%s'\n", chan->name, macro);
200                         break;
201                 }
202                 /* don't stop executing extensions when we're in "h" */
203                 if (chan->_softhangup && strcasecmp(oldexten,"h")) {
204                         ast_log(LOG_DEBUG, "Extension %s, priority %d returned normally even though call was hung up\n",
205                                 chan->exten, chan->priority);
206                         goto out;
207                 }
208                 chan->priority++;
209         }
210         out:
211         for (x=1; x<argc; x++) {
212                 /* Restore old arguments and delete ours */
213                 snprintf(varname, sizeof(varname), "ARG%d", x);
214                 if (oldargs[x]) {
215                         pbx_builtin_setvar_helper(chan, varname, oldargs[x]);
216                         free(oldargs[x]);
217                 } else {
218                         pbx_builtin_setvar_helper(chan, varname, NULL);
219                 }
220         }
221
222         /* Restore macro variables */
223         pbx_builtin_setvar_helper(chan, "MACRO_EXTEN", save_macro_exten);
224         if (save_macro_exten)
225                 free(save_macro_exten);
226         pbx_builtin_setvar_helper(chan, "MACRO_CONTEXT", save_macro_context);
227         if (save_macro_context)
228                 free(save_macro_context);
229         pbx_builtin_setvar_helper(chan, "MACRO_PRIORITY", save_macro_priority);
230         if (save_macro_priority)
231                 free(save_macro_priority);
232         if (setmacrocontext) {
233                 chan->macrocontext[0] = '\0';
234                 chan->macroexten[0] = '\0';
235                 chan->macropriority = 0;
236         }
237
238         if (!strcasecmp(chan->context, fullmacro)) {
239                 /* If we're leaving the macro normally, restore original information */
240                 chan->priority = oldpriority;
241                 strncpy(chan->context, oldcontext, sizeof(chan->context) - 1);
242                 if (!(chan->_softhangup & AST_SOFTHANGUP_ASYNCGOTO)) {
243                         /* Copy the extension, so long as we're not in softhangup, where we could be given an asyncgoto */
244                         strncpy(chan->exten, oldexten, sizeof(chan->exten) - 1);
245                         if ((offsets = pbx_builtin_getvar_helper(chan, "MACRO_OFFSET"))) {
246                                 /* Handle macro offset if it's set by checking the availability of step n + offset + 1, otherwise continue
247                                 normally if there is any problem */
248                                 if (sscanf(offsets, "%d", &offset) == 1) {
249                                         if (ast_exists_extension(chan, chan->context, chan->exten, chan->priority + offset + 1, chan->cid.cid_num)) {
250                                                 chan->priority += offset;
251                                         }
252                                 }
253                         }
254                 }
255         }
256
257         pbx_builtin_setvar_helper(chan, "MACRO_OFFSET", save_macro_offset);
258         if (save_macro_offset)
259                 free(save_macro_offset);
260         LOCAL_USER_REMOVE(u);
261         return res;
262 }
263
264 static int macroif_exec(struct ast_channel *chan, void *data) 
265 {
266         char *expr = NULL, *label_a = NULL, *label_b = NULL;
267         int res = 0;
268
269         if((expr = ast_strdupa((char *) data))) {
270                 if ((label_a = strchr(expr, '?'))) {
271                         *label_a = '\0';
272                         label_a++;
273                         if ((label_b = strchr(label_a, ':'))) {
274                                 *label_b = '\0';
275                                 label_b++;
276                         }
277                         if (ast_true(expr))
278                                 macro_exec(chan, label_a);
279                         else if (label_b) 
280                                 macro_exec(chan, label_b);
281                         
282                 } else
283                         ast_log(LOG_WARNING, "Invalid Syntax.\n");
284         } else 
285                 ast_log(LOG_ERROR, "Out of Memory!\n");
286         return res;
287 }
288                         
289 static int macro_exit_exec(struct ast_channel *chan, void *data)
290 {
291         return MACRO_EXIT_RESULT;
292 }
293
294 int unload_module(void)
295 {
296         STANDARD_HANGUP_LOCALUSERS;
297         ast_unregister_application(if_app);
298         ast_unregister_application(exit_app);
299         return ast_unregister_application(app);
300 }
301
302 int load_module(void)
303 {
304         ast_register_application(exit_app, macro_exit_exec, exit_synopsis, exit_descrip);
305         ast_register_application(if_app, macroif_exec, if_synopsis, if_descrip);
306         return ast_register_application(app, macro_exec, synopsis, descrip);
307 }
308
309 char *description(void)
310 {
311         return tdesc;
312 }
313
314 int usecount(void)
315 {
316         int res;
317         STANDARD_USECOUNT(res);
318         return res;
319 }
320
321 char *key()
322 {
323         return ASTERISK_GPL_KEY;
324 }