add a project-specific script to be used during release preparation
[asterisk-bristuff.git] / apps / app_macro.c
blob5eeecff114aa376abeb04bb1ca35cec91a9b0d8e
1 /*
2 * Asterisk -- An open source telephony toolkit.
4 * Copyright (C) 1999 - 2005, Digium, Inc.
6 * Mark Spencer <markster@digium.com>
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.
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.
19 /*! \file
21 * \brief Dial plan macro Implementation
23 * \author Mark Spencer <markster@digium.com>
25 * \ingroup applications
28 #include "asterisk.h"
30 ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
32 #include <stdlib.h>
33 #include <stdio.h>
34 #include <string.h>
35 #include <unistd.h>
36 #include <sys/types.h>
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"
48 #define MAX_ARGS 80
50 /* special result value used to force macro exit */
51 #define MACRO_EXIT_RESULT 1024
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 "WARNING: Because of the way Macro is implemented (it executes the priorities\n"
65 " contained within it via sub-engine), and a fixed per-thread\n"
66 " memory stack allowance, macros are limited to 7 levels\n"
67 " of nesting (macro calling macro calling macro, etc.); It\n"
68 " may be possible that stack-intensive applications in deeply nested macros\n"
69 " could cause asterisk to crash earlier than this limit.\n";
71 static char *if_descrip =
72 " MacroIf(<expr>?macroname_a[|arg1][:macroname_b[|arg1]])\n"
73 "Executes macro defined in <macroname_a> if <expr> is true\n"
74 "(otherwise <macroname_b> if provided)\n"
75 "Arguments and return values as in application macro()\n";
77 static char *exclusive_descrip =
78 " MacroExclusive(macroname|arg1|arg2...):\n"
79 "Executes macro defined in the context 'macro-macroname'\n"
80 "Only one call at a time may run the macro.\n"
81 "(we'll wait if another call is busy executing in the Macro)\n"
82 "Arguments and return values as in application Macro()\n";
84 static char *exit_descrip =
85 " MacroExit():\n"
86 "Causes the currently running macro to exit as if it had\n"
87 "ended normally by running out of priorities to execute.\n"
88 "If used outside a macro, will likely cause unexpected\n"
89 "behavior.\n";
91 static char *app = "Macro";
92 static char *if_app = "MacroIf";
93 static char *exclusive_app = "MacroExclusive";
94 static char *exit_app = "MacroExit";
96 static char *synopsis = "Macro Implementation";
97 static char *if_synopsis = "Conditional Macro Implementation";
98 static char *exclusive_synopsis = "Exclusive Macro Implementation";
99 static char *exit_synopsis = "Exit From Macro";
102 static int _macro_exec(struct ast_channel *chan, void *data, int exclusive)
104 const char *s;
106 char *tmp;
107 char *cur, *rest;
108 char *macro;
109 char fullmacro[80];
110 char varname[80];
111 char *oldargs[MAX_ARGS + 1] = { NULL, };
112 int argc, x;
113 int res=0;
114 char oldexten[256]="";
115 int oldpriority;
116 char pc[80], depthc[12];
117 char oldcontext[AST_MAX_CONTEXT] = "";
118 int offset, depth = 0, maxdepth = 7;
119 int setmacrocontext=0;
120 int autoloopflag, dead = 0;
122 char *save_macro_exten;
123 char *save_macro_context;
124 char *save_macro_priority;
125 char *save_macro_offset;
126 struct ast_module_user *u;
128 if (ast_strlen_zero(data)) {
129 ast_log(LOG_WARNING, "Macro() requires arguments. See \"show application macro\" for help.\n");
130 return -1;
133 u = ast_module_user_add(chan);
135 /* does the user want a deeper rabbit hole? */
136 s = pbx_builtin_getvar_helper(chan, "MACRO_RECURSION");
137 if (s)
138 sscanf(s, "%d", &maxdepth);
140 /* Count how many levels deep the rabbit hole goes */
141 s = pbx_builtin_getvar_helper(chan, "MACRO_DEPTH");
142 if (s)
143 sscanf(s, "%d", &depth);
144 if (depth >= maxdepth) {
145 ast_log(LOG_ERROR, "Macro(): possible infinite loop detected. Returning early.\n");
146 ast_module_user_remove(u);
147 return 0;
149 snprintf(depthc, sizeof(depthc), "%d", depth + 1);
150 pbx_builtin_setvar_helper(chan, "MACRO_DEPTH", depthc);
152 tmp = ast_strdupa(data);
153 rest = tmp;
154 macro = strsep(&rest, "|");
155 if (ast_strlen_zero(macro)) {
156 ast_log(LOG_WARNING, "Invalid macro name specified\n");
157 ast_module_user_remove(u);
158 return 0;
161 snprintf(fullmacro, sizeof(fullmacro), "macro-%s", macro);
162 if (!ast_exists_extension(chan, fullmacro, "s", 1, chan->cid.cid_num)) {
163 if (!ast_context_find(fullmacro))
164 ast_log(LOG_WARNING, "No such context '%s' for macro '%s'\n", fullmacro, macro);
165 else
166 ast_log(LOG_WARNING, "Context '%s' for macro '%s' lacks 's' extension, priority 1\n", fullmacro, macro);
167 ast_module_user_remove(u);
168 return 0;
171 /* If we are to run the macro exclusively, take the mutex */
172 if (exclusive) {
173 ast_log(LOG_DEBUG, "Locking macrolock for '%s'\n", fullmacro);
174 ast_autoservice_start(chan);
175 if (ast_context_lockmacro(fullmacro)) {
176 ast_log(LOG_WARNING, "Failed to lock macro '%s' as in-use\n", fullmacro);
177 ast_autoservice_stop(chan);
178 ast_module_user_remove(u);
180 return 0;
182 ast_autoservice_stop(chan);
185 /* Save old info */
186 oldpriority = chan->priority;
187 ast_copy_string(oldexten, chan->exten, sizeof(oldexten));
188 ast_copy_string(oldcontext, chan->context, sizeof(oldcontext));
189 if (ast_strlen_zero(chan->macrocontext)) {
190 ast_copy_string(chan->macrocontext, chan->context, sizeof(chan->macrocontext));
191 ast_copy_string(chan->macroexten, chan->exten, sizeof(chan->macroexten));
192 chan->macropriority = chan->priority;
193 setmacrocontext=1;
195 argc = 1;
196 /* Save old macro variables */
197 save_macro_exten = ast_strdup(pbx_builtin_getvar_helper(chan, "MACRO_EXTEN"));
198 pbx_builtin_setvar_helper(chan, "MACRO_EXTEN", oldexten);
200 save_macro_context = ast_strdup(pbx_builtin_getvar_helper(chan, "MACRO_CONTEXT"));
201 pbx_builtin_setvar_helper(chan, "MACRO_CONTEXT", oldcontext);
203 save_macro_priority = ast_strdup(pbx_builtin_getvar_helper(chan, "MACRO_PRIORITY"));
204 snprintf(pc, sizeof(pc), "%d", oldpriority);
205 pbx_builtin_setvar_helper(chan, "MACRO_PRIORITY", pc);
207 save_macro_offset = ast_strdup(pbx_builtin_getvar_helper(chan, "MACRO_OFFSET"));
208 pbx_builtin_setvar_helper(chan, "MACRO_OFFSET", NULL);
210 /* Setup environment for new run */
211 chan->exten[0] = 's';
212 chan->exten[1] = '\0';
213 ast_copy_string(chan->context, fullmacro, sizeof(chan->context));
214 chan->priority = 1;
216 while((cur = strsep(&rest, "|")) && (argc < MAX_ARGS)) {
217 const char *s;
218 /* Save copy of old arguments if we're overwriting some, otherwise
219 let them pass through to the other macro */
220 snprintf(varname, sizeof(varname), "ARG%d", argc);
221 s = pbx_builtin_getvar_helper(chan, varname);
222 if (s)
223 oldargs[argc] = ast_strdup(s);
224 pbx_builtin_setvar_helper(chan, varname, cur);
225 argc++;
227 autoloopflag = ast_test_flag(chan, AST_FLAG_IN_AUTOLOOP);
228 ast_set_flag(chan, AST_FLAG_IN_AUTOLOOP);
229 while(ast_exists_extension(chan, chan->context, chan->exten, chan->priority, chan->cid.cid_num)) {
230 /* Reset the macro depth, if it was changed in the last iteration */
231 pbx_builtin_setvar_helper(chan, "MACRO_DEPTH", depthc);
232 if ((res = ast_spawn_extension(chan, chan->context, chan->exten, chan->priority, chan->cid.cid_num))) {
233 /* Something bad happened, or a hangup has been requested. */
234 if (((res >= '0') && (res <= '9')) || ((res >= 'A') && (res <= 'F')) ||
235 (res == '*') || (res == '#')) {
236 /* Just return result as to the previous application as if it had been dialed */
237 ast_log(LOG_DEBUG, "Oooh, got something to jump out with ('%c')!\n", res);
238 break;
240 switch(res) {
241 case MACRO_EXIT_RESULT:
242 res = 0;
243 goto out;
244 case AST_PBX_KEEPALIVE:
245 if (option_debug)
246 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);
247 else if (option_verbose > 1)
248 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);
249 goto out;
250 break;
251 default:
252 if (option_debug)
253 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);
254 else if (option_verbose > 1)
255 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);
256 dead = 1;
257 goto out;
260 if (strcasecmp(chan->context, fullmacro)) {
261 if (option_verbose > 1)
262 ast_verbose(VERBOSE_PREFIX_2 "Channel '%s' jumping out of macro '%s'\n", chan->name, macro);
263 break;
265 /* don't stop executing extensions when we're in "h" */
266 if (chan->_softhangup && strcasecmp(oldexten,"h") && strcasecmp(chan->macroexten,"h")) {
267 ast_log(LOG_DEBUG, "Extension %s, macroexten %s, priority %d returned normally even though call was hung up\n",
268 chan->exten, chan->macroexten, chan->priority);
269 goto out;
271 chan->priority++;
273 out:
274 /* Reset the depth back to what it was when the routine was entered (like if we called Macro recursively) */
275 snprintf(depthc, sizeof(depthc), "%d", depth);
276 if (!dead) {
277 pbx_builtin_setvar_helper(chan, "MACRO_DEPTH", depthc);
278 ast_set2_flag(chan, autoloopflag, AST_FLAG_IN_AUTOLOOP);
281 for (x = 1; x < argc; x++) {
282 /* Restore old arguments and delete ours */
283 snprintf(varname, sizeof(varname), "ARG%d", x);
284 if (oldargs[x]) {
285 if (!dead)
286 pbx_builtin_setvar_helper(chan, varname, oldargs[x]);
287 free(oldargs[x]);
288 } else if (!dead) {
289 pbx_builtin_setvar_helper(chan, varname, NULL);
293 /* Restore macro variables */
294 if (!dead) {
295 pbx_builtin_setvar_helper(chan, "MACRO_EXTEN", save_macro_exten);
296 pbx_builtin_setvar_helper(chan, "MACRO_CONTEXT", save_macro_context);
297 pbx_builtin_setvar_helper(chan, "MACRO_PRIORITY", save_macro_priority);
299 if (save_macro_exten)
300 free(save_macro_exten);
301 if (save_macro_context)
302 free(save_macro_context);
303 if (save_macro_priority)
304 free(save_macro_priority);
306 if (!dead && setmacrocontext) {
307 chan->macrocontext[0] = '\0';
308 chan->macroexten[0] = '\0';
309 chan->macropriority = 0;
312 if (!dead && !strcasecmp(chan->context, fullmacro)) {
313 /* If we're leaving the macro normally, restore original information */
314 chan->priority = oldpriority;
315 ast_copy_string(chan->context, oldcontext, sizeof(chan->context));
316 if (!(chan->_softhangup & AST_SOFTHANGUP_ASYNCGOTO)) {
317 /* Copy the extension, so long as we're not in softhangup, where we could be given an asyncgoto */
318 const char *offsets;
319 ast_copy_string(chan->exten, oldexten, sizeof(chan->exten));
320 if ((offsets = pbx_builtin_getvar_helper(chan, "MACRO_OFFSET"))) {
321 /* Handle macro offset if it's set by checking the availability of step n + offset + 1, otherwise continue
322 normally if there is any problem */
323 if (sscanf(offsets, "%d", &offset) == 1) {
324 if (ast_exists_extension(chan, chan->context, chan->exten, chan->priority + offset + 1, chan->cid.cid_num)) {
325 chan->priority += offset;
332 if (!dead)
333 pbx_builtin_setvar_helper(chan, "MACRO_OFFSET", save_macro_offset);
334 if (save_macro_offset)
335 free(save_macro_offset);
337 /* Unlock the macro */
338 if (exclusive) {
339 ast_log(LOG_DEBUG, "Unlocking macrolock for '%s'\n", fullmacro);
340 if (ast_context_unlockmacro(fullmacro)) {
341 ast_log(LOG_ERROR, "Failed to unlock macro '%s' - that isn't good\n", fullmacro);
342 res = 0;
346 ast_module_user_remove(u);
348 return res;
351 static int macro_exec(struct ast_channel *chan, void *data)
353 return _macro_exec(chan, data, 0);
356 static int macroexclusive_exec(struct ast_channel *chan, void *data)
358 return _macro_exec(chan, data, 1);
361 static int macroif_exec(struct ast_channel *chan, void *data)
363 char *expr = NULL, *label_a = NULL, *label_b = NULL;
364 int res = 0;
365 struct ast_module_user *u;
367 u = ast_module_user_add(chan);
369 if (!(expr = ast_strdupa(data))) {
370 ast_module_user_remove(u);
371 return -1;
374 if ((label_a = strchr(expr, '?'))) {
375 *label_a = '\0';
376 label_a++;
377 if ((label_b = strchr(label_a, ':'))) {
378 *label_b = '\0';
379 label_b++;
381 if (pbx_checkcondition(expr))
382 macro_exec(chan, label_a);
383 else if (label_b)
384 macro_exec(chan, label_b);
385 } else
386 ast_log(LOG_WARNING, "Invalid Syntax.\n");
388 ast_module_user_remove(u);
390 return res;
393 static int macro_exit_exec(struct ast_channel *chan, void *data)
395 return MACRO_EXIT_RESULT;
398 static int unload_module(void)
400 int res;
402 res = ast_unregister_application(if_app);
403 res |= ast_unregister_application(exit_app);
404 res |= ast_unregister_application(app);
405 res |= ast_unregister_application(exclusive_app);
407 ast_module_user_hangup_all();
409 return res;
412 static int load_module(void)
414 int res;
416 res = ast_register_application(exit_app, macro_exit_exec, exit_synopsis, exit_descrip);
417 res |= ast_register_application(if_app, macroif_exec, if_synopsis, if_descrip);
418 res |= ast_register_application(exclusive_app, macroexclusive_exec, exclusive_synopsis, exclusive_descrip);
419 res |= ast_register_application(app, macro_exec, synopsis, descrip);
421 return res;
424 AST_MODULE_INFO_STANDARD(ASTERISK_GPL_KEY, "Extension Macros");