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