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