mirror of
				https://github.com/asterisk/asterisk.git
				synced 2025-10-25 22:18:07 +00:00 
			
		
		
		
	Finding an application and executing it if found is a common task throughout Asterisk. This adds a helper function around pbx_exec to do this, to eliminate redundant code and make it easier for modules to substitute variables and execute applications by name. ASTERISK-30061 #close Change-Id: Ifee4d2825df7545fb515d763d393065675140c84
		
			
				
	
	
		
			543 lines
		
	
	
		
			14 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			543 lines
		
	
	
		
			14 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
| /*
 | |
|  * Asterisk -- An open source telephony toolkit.
 | |
|  *
 | |
|  * Copyright (C) 2016, CFWare, LLC
 | |
|  *
 | |
|  * Corey Farrell <git@cfware.com>
 | |
|  *
 | |
|  * See http://www.asterisk.org for more information about
 | |
|  * the Asterisk project. Please do not directly contact
 | |
|  * any of the maintainers of this project for assistance;
 | |
|  * the project provides a web site, mailing lists and IRC
 | |
|  * channels for your use.
 | |
|  *
 | |
|  * This program is free software, distributed under the terms of
 | |
|  * the GNU General Public License Version 2. See the LICENSE file
 | |
|  * at the top of the source tree.
 | |
|  */
 | |
| 
 | |
| /*! \file
 | |
|  *
 | |
|  * \brief Custom function management routines.
 | |
|  *
 | |
|  * \author Corey Farrell <git@cfware.com>
 | |
|  */
 | |
| 
 | |
| /*** MODULEINFO
 | |
| 	<support_level>core</support_level>
 | |
|  ***/
 | |
| 
 | |
| #include "asterisk.h"
 | |
| 
 | |
| #include "asterisk/_private.h"
 | |
| #include "asterisk/cli.h"
 | |
| #include "asterisk/linkedlists.h"
 | |
| #include "asterisk/module.h"
 | |
| #include "asterisk/pbx.h"
 | |
| #include "asterisk/stasis_channels.h"
 | |
| #include "asterisk/strings.h"
 | |
| #include "asterisk/term.h"
 | |
| #include "asterisk/utils.h"
 | |
| #include "asterisk/xmldoc.h"
 | |
| #include "pbx_private.h"
 | |
| 
 | |
| /*! \brief ast_app: A registered application */
 | |
| struct ast_app {
 | |
| 	int (*execute)(struct ast_channel *chan, const char *data);
 | |
| 	AST_DECLARE_STRING_FIELDS(
 | |
| 		AST_STRING_FIELD(synopsis);     /*!< Synopsis text for 'show applications' */
 | |
| 		AST_STRING_FIELD(description);  /*!< Description (help text) for 'show application <name>' */
 | |
| 		AST_STRING_FIELD(syntax);       /*!< Syntax text for 'core show applications' */
 | |
| 		AST_STRING_FIELD(arguments);    /*!< Arguments description */
 | |
| 		AST_STRING_FIELD(seealso);      /*!< See also */
 | |
| 	);
 | |
| #ifdef AST_XML_DOCS
 | |
| 	enum ast_doc_src docsrc;		/*!< Where the documentation come from. */
 | |
| #endif
 | |
| 	AST_RWLIST_ENTRY(ast_app) list;		/*!< Next app in list */
 | |
| 	struct ast_module *module;		/*!< Module this app belongs to */
 | |
| 	char name[0];				/*!< Name of the application */
 | |
| };
 | |
| 
 | |
| /*!
 | |
|  * \brief Registered applications container.
 | |
|  *
 | |
|  * It is sorted by application name.
 | |
|  */
 | |
| static AST_RWLIST_HEAD_STATIC(apps, ast_app);
 | |
| 
 | |
| static struct ast_app *pbx_findapp_nolock(const char *name)
 | |
| {
 | |
| 	struct ast_app *cur;
 | |
| 	int cmp;
 | |
| 
 | |
| 	AST_RWLIST_TRAVERSE(&apps, cur, list) {
 | |
| 		cmp = strcasecmp(name, cur->name);
 | |
| 		if (cmp > 0) {
 | |
| 			continue;
 | |
| 		}
 | |
| 		if (!cmp) {
 | |
| 			/* Found it. */
 | |
| 			break;
 | |
| 		}
 | |
| 		/* Not in container. */
 | |
| 		cur = NULL;
 | |
| 		break;
 | |
| 	}
 | |
| 
 | |
| 	return cur;
 | |
| }
 | |
| 
 | |
| struct ast_app *pbx_findapp(const char *app)
 | |
| {
 | |
| 	struct ast_app *ret;
 | |
| 
 | |
| 	AST_RWLIST_RDLOCK(&apps);
 | |
| 	ret = pbx_findapp_nolock(app);
 | |
| 	AST_RWLIST_UNLOCK(&apps);
 | |
| 
 | |
| 	return ret;
 | |
| }
 | |
| 
 | |
| /*! \brief Dynamically register a new dial plan application */
 | |
| int ast_register_application2(const char *app, int (*execute)(struct ast_channel *, const char *), const char *synopsis, const char *description, void *mod)
 | |
| {
 | |
| 	struct ast_app *tmp;
 | |
| 	struct ast_app *cur;
 | |
| 	int length;
 | |
| #ifdef AST_XML_DOCS
 | |
| 	char *tmpxml;
 | |
| #endif
 | |
| 
 | |
| 	AST_RWLIST_WRLOCK(&apps);
 | |
| 	cur = pbx_findapp_nolock(app);
 | |
| 	if (cur) {
 | |
| 		ast_log(LOG_WARNING, "Already have an application '%s'\n", app);
 | |
| 		AST_RWLIST_UNLOCK(&apps);
 | |
| 		return -1;
 | |
| 	}
 | |
| 
 | |
| 	length = sizeof(*tmp) + strlen(app) + 1;
 | |
| 
 | |
| 	if (!(tmp = ast_calloc(1, length))) {
 | |
| 		AST_RWLIST_UNLOCK(&apps);
 | |
| 		return -1;
 | |
| 	}
 | |
| 
 | |
| 	if (ast_string_field_init(tmp, 128)) {
 | |
| 		AST_RWLIST_UNLOCK(&apps);
 | |
| 		ast_free(tmp);
 | |
| 		return -1;
 | |
| 	}
 | |
| 
 | |
| 	strcpy(tmp->name, app);
 | |
| 	tmp->execute = execute;
 | |
| 	tmp->module = mod;
 | |
| 
 | |
| #ifdef AST_XML_DOCS
 | |
| 	/* Try to lookup the docs in our XML documentation database */
 | |
| 	if (ast_strlen_zero(synopsis) && ast_strlen_zero(description)) {
 | |
| 		/* load synopsis */
 | |
| 		tmpxml = ast_xmldoc_build_synopsis("application", app, ast_module_name(tmp->module));
 | |
| 		ast_string_field_set(tmp, synopsis, tmpxml);
 | |
| 		ast_free(tmpxml);
 | |
| 
 | |
| 		/* load description */
 | |
| 		tmpxml = ast_xmldoc_build_description("application", app, ast_module_name(tmp->module));
 | |
| 		ast_string_field_set(tmp, description, tmpxml);
 | |
| 		ast_free(tmpxml);
 | |
| 
 | |
| 		/* load syntax */
 | |
| 		tmpxml = ast_xmldoc_build_syntax("application", app, ast_module_name(tmp->module));
 | |
| 		ast_string_field_set(tmp, syntax, tmpxml);
 | |
| 		ast_free(tmpxml);
 | |
| 
 | |
| 		/* load arguments */
 | |
| 		tmpxml = ast_xmldoc_build_arguments("application", app, ast_module_name(tmp->module));
 | |
| 		ast_string_field_set(tmp, arguments, tmpxml);
 | |
| 		ast_free(tmpxml);
 | |
| 
 | |
| 		/* load seealso */
 | |
| 		tmpxml = ast_xmldoc_build_seealso("application", app, ast_module_name(tmp->module));
 | |
| 		ast_string_field_set(tmp, seealso, tmpxml);
 | |
| 		ast_free(tmpxml);
 | |
| 		tmp->docsrc = AST_XML_DOC;
 | |
| 	} else {
 | |
| #endif
 | |
| 		ast_string_field_set(tmp, synopsis, synopsis);
 | |
| 		ast_string_field_set(tmp, description, description);
 | |
| #ifdef AST_XML_DOCS
 | |
| 		tmp->docsrc = AST_STATIC_DOC;
 | |
| 	}
 | |
| #endif
 | |
| 
 | |
| 	/* Store in alphabetical order */
 | |
| 	AST_RWLIST_TRAVERSE_SAFE_BEGIN(&apps, cur, list) {
 | |
| 		if (strcasecmp(tmp->name, cur->name) < 0) {
 | |
| 			AST_RWLIST_INSERT_BEFORE_CURRENT(tmp, list);
 | |
| 			break;
 | |
| 		}
 | |
| 	}
 | |
| 	AST_RWLIST_TRAVERSE_SAFE_END;
 | |
| 	if (!cur)
 | |
| 		AST_RWLIST_INSERT_TAIL(&apps, tmp, list);
 | |
| 
 | |
| 	ast_verb(2, "Registered application '" COLORIZE_FMT "'\n", COLORIZE(COLOR_BRCYAN, 0, tmp->name));
 | |
| 
 | |
| 	AST_RWLIST_UNLOCK(&apps);
 | |
| 
 | |
| 	return 0;
 | |
| }
 | |
| 
 | |
| static void print_app_docs(struct ast_app *aa, int fd)
 | |
| {
 | |
| #ifdef AST_XML_DOCS
 | |
| 	char *synopsis = NULL, *description = NULL, *arguments = NULL, *seealso = NULL;
 | |
| 	if (aa->docsrc == AST_XML_DOC) {
 | |
| 		synopsis = ast_xmldoc_printable(S_OR(aa->synopsis, "Not available"), 1);
 | |
| 		description = ast_xmldoc_printable(S_OR(aa->description, "Not available"), 1);
 | |
| 		arguments = ast_xmldoc_printable(S_OR(aa->arguments, "Not available"), 1);
 | |
| 		seealso = ast_xmldoc_printable(S_OR(aa->seealso, "Not available"), 1);
 | |
| 		if (!synopsis || !description || !arguments || !seealso) {
 | |
| 			goto free_docs;
 | |
| 		}
 | |
| 		ast_cli(fd, "\n"
 | |
| 			"%s  -= Info about application '%s' =- %s\n\n"
 | |
| 			COLORIZE_FMT "\n"
 | |
| 			"%s\n\n"
 | |
| 			COLORIZE_FMT "\n"
 | |
| 			"%s\n\n"
 | |
| 			COLORIZE_FMT "\n"
 | |
| 			"%s%s%s\n\n"
 | |
| 			COLORIZE_FMT "\n"
 | |
| 			"%s\n\n"
 | |
| 			COLORIZE_FMT "\n"
 | |
| 			"%s\n",
 | |
| 			ast_term_color(COLOR_MAGENTA, 0), aa->name, ast_term_reset(),
 | |
| 			COLORIZE(COLOR_MAGENTA, 0, "[Synopsis]"), synopsis,
 | |
| 			COLORIZE(COLOR_MAGENTA, 0, "[Description]"), description,
 | |
| 			COLORIZE(COLOR_MAGENTA, 0, "[Syntax]"),
 | |
| 				ast_term_color(COLOR_CYAN, 0), S_OR(aa->syntax, "Not available"), ast_term_reset(),
 | |
| 			COLORIZE(COLOR_MAGENTA, 0, "[Arguments]"), arguments,
 | |
| 			COLORIZE(COLOR_MAGENTA, 0, "[See Also]"), seealso);
 | |
| free_docs:
 | |
| 		ast_free(synopsis);
 | |
| 		ast_free(description);
 | |
| 		ast_free(arguments);
 | |
| 		ast_free(seealso);
 | |
| 	} else
 | |
| #endif
 | |
| 	{
 | |
| 		ast_cli(fd, "\n"
 | |
| 			"%s  -= Info about application '%s' =- %s\n\n"
 | |
| 			COLORIZE_FMT "\n"
 | |
| 			COLORIZE_FMT "\n\n"
 | |
| 			COLORIZE_FMT "\n"
 | |
| 			COLORIZE_FMT "\n\n"
 | |
| 			COLORIZE_FMT "\n"
 | |
| 			COLORIZE_FMT "\n\n"
 | |
| 			COLORIZE_FMT "\n"
 | |
| 			COLORIZE_FMT "\n\n"
 | |
| 			COLORIZE_FMT "\n"
 | |
| 			COLORIZE_FMT "\n",
 | |
| 			ast_term_color(COLOR_MAGENTA, 0), aa->name, ast_term_reset(),
 | |
| 			COLORIZE(COLOR_MAGENTA, 0, "[Synopsis]"),
 | |
| 			COLORIZE(COLOR_CYAN, 0, S_OR(aa->synopsis, "Not available")),
 | |
| 			COLORIZE(COLOR_MAGENTA, 0, "[Description]"),
 | |
| 			COLORIZE(COLOR_CYAN, 0, S_OR(aa->description, "Not available")),
 | |
| 			COLORIZE(COLOR_MAGENTA, 0, "[Syntax]"),
 | |
| 			COLORIZE(COLOR_CYAN, 0, S_OR(aa->syntax, "Not available")),
 | |
| 			COLORIZE(COLOR_MAGENTA, 0, "[Arguments]"),
 | |
| 			COLORIZE(COLOR_CYAN, 0, S_OR(aa->arguments, "Not available")),
 | |
| 			COLORIZE(COLOR_MAGENTA, 0, "[See Also]"),
 | |
| 			COLORIZE(COLOR_CYAN, 0, S_OR(aa->seealso, "Not available")));
 | |
| 	}
 | |
| }
 | |
| 
 | |
| /*!
 | |
|  * \brief 'show application' CLI command implementation function...
 | |
|  */
 | |
| static char *handle_show_application(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
 | |
| {
 | |
| 	struct ast_app *aa;
 | |
| 	int app, no_registered_app = 1;
 | |
| 
 | |
| 	switch (cmd) {
 | |
| 	case CLI_INIT:
 | |
| 		e->command = "core show application";
 | |
| 		e->usage =
 | |
| 			"Usage: core show application <application> [<application> [<application> [...]]]\n"
 | |
| 			"       Describes a particular application.\n";
 | |
| 		return NULL;
 | |
| 	case CLI_GENERATE:
 | |
| 		/*
 | |
| 		 * There is a possibility to show informations about more than one
 | |
| 		 * application at one time. You can type 'show application Dial Echo' and
 | |
| 		 * you will see informations about these two applications ...
 | |
| 		 */
 | |
| 		return ast_complete_applications(a->line, a->word, -1);
 | |
| 	}
 | |
| 
 | |
| 	if (a->argc < 4) {
 | |
| 		return CLI_SHOWUSAGE;
 | |
| 	}
 | |
| 
 | |
| 	AST_RWLIST_RDLOCK(&apps);
 | |
| 	AST_RWLIST_TRAVERSE(&apps, aa, list) {
 | |
| 		/* Check for each app that was supplied as an argument */
 | |
| 		for (app = 3; app < a->argc; app++) {
 | |
| 			if (strcasecmp(aa->name, a->argv[app])) {
 | |
| 				continue;
 | |
| 			}
 | |
| 
 | |
| 			/* We found it! */
 | |
| 			no_registered_app = 0;
 | |
| 
 | |
| 			print_app_docs(aa, a->fd);
 | |
| 		}
 | |
| 	}
 | |
| 	AST_RWLIST_UNLOCK(&apps);
 | |
| 
 | |
| 	/* we found at least one app? no? */
 | |
| 	if (no_registered_app) {
 | |
| 		ast_cli(a->fd, "Your application(s) is (are) not registered\n");
 | |
| 		return CLI_FAILURE;
 | |
| 	}
 | |
| 
 | |
| 	return CLI_SUCCESS;
 | |
| }
 | |
| 
 | |
| static char *handle_show_applications(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
 | |
| {
 | |
| 	struct ast_app *aa;
 | |
| 	int like = 0, describing = 0;
 | |
| 	int total_match = 0;    /* Number of matches in like clause */
 | |
| 	int total_apps = 0;     /* Number of apps registered */
 | |
| 
 | |
| 	switch (cmd) {
 | |
| 	case CLI_INIT:
 | |
| 		e->command = "core show applications [like|describing]";
 | |
| 		e->usage =
 | |
| 			"Usage: core show applications [{like|describing} <text>]\n"
 | |
| 			"       List applications which are currently available.\n"
 | |
| 			"       If 'like', <text> will be a substring of the app name\n"
 | |
| 			"       If 'describing', <text> will be a substring of the description\n";
 | |
| 		return NULL;
 | |
| 	case CLI_GENERATE:
 | |
| 		return NULL;
 | |
| 	}
 | |
| 
 | |
| 	AST_RWLIST_RDLOCK(&apps);
 | |
| 
 | |
| 	if (AST_RWLIST_EMPTY(&apps)) {
 | |
| 		ast_cli(a->fd, "There are no registered applications\n");
 | |
| 		AST_RWLIST_UNLOCK(&apps);
 | |
| 		return CLI_SUCCESS;
 | |
| 	}
 | |
| 
 | |
| 	/* core list applications like <keyword> */
 | |
| 	if ((a->argc == 5) && (!strcmp(a->argv[3], "like"))) {
 | |
| 		like = 1;
 | |
| 	} else if ((a->argc > 4) && (!strcmp(a->argv[3], "describing"))) {
 | |
| 		describing = 1;
 | |
| 	}
 | |
| 
 | |
| 	/* core list applications describing <keyword1> [<keyword2>] [...] */
 | |
| 	if ((!like) && (!describing)) {
 | |
| 		ast_cli(a->fd, "    -= Registered Asterisk Applications =-\n");
 | |
| 	} else {
 | |
| 		ast_cli(a->fd, "    -= Matching Asterisk Applications =-\n");
 | |
| 	}
 | |
| 
 | |
| 	AST_RWLIST_TRAVERSE(&apps, aa, list) {
 | |
| 		int printapp = 0;
 | |
| 		total_apps++;
 | |
| 		if (like) {
 | |
| 			if (strcasestr(aa->name, a->argv[4])) {
 | |
| 				printapp = 1;
 | |
| 				total_match++;
 | |
| 			}
 | |
| 		} else if (describing) {
 | |
| 			if (aa->description) {
 | |
| 				/* Match all words on command line */
 | |
| 				int i;
 | |
| 				printapp = 1;
 | |
| 				for (i = 4; i < a->argc; i++) {
 | |
| 					if (!strcasestr(aa->description, a->argv[i])) {
 | |
| 						printapp = 0;
 | |
| 					} else {
 | |
| 						total_match++;
 | |
| 					}
 | |
| 				}
 | |
| 			}
 | |
| 		} else {
 | |
| 			printapp = 1;
 | |
| 		}
 | |
| 
 | |
| 		if (printapp) {
 | |
| 			ast_cli(a->fd,"  %20s: %s\n", aa->name, aa->synopsis ? aa->synopsis : "<Synopsis not available>");
 | |
| 		}
 | |
| 	}
 | |
| 	if ((!like) && (!describing)) {
 | |
| 		ast_cli(a->fd, "    -= %d Applications Registered =-\n",total_apps);
 | |
| 	} else {
 | |
| 		ast_cli(a->fd, "    -= %d Applications Matching =-\n",total_match);
 | |
| 	}
 | |
| 
 | |
| 	AST_RWLIST_UNLOCK(&apps);
 | |
| 
 | |
| 	return CLI_SUCCESS;
 | |
| }
 | |
| 
 | |
| int ast_unregister_application(const char *app)
 | |
| {
 | |
| 	struct ast_app *cur;
 | |
| 	int cmp;
 | |
| 
 | |
| 	/* Anticipate need for conlock in unreference_cached_app(), in order to avoid
 | |
| 	 * possible deadlock with pbx_extension_helper()/pbx_findapp()
 | |
| 	 */
 | |
| 	ast_rdlock_contexts();
 | |
| 
 | |
| 	AST_RWLIST_WRLOCK(&apps);
 | |
| 	AST_RWLIST_TRAVERSE_SAFE_BEGIN(&apps, cur, list) {
 | |
| 		cmp = strcasecmp(app, cur->name);
 | |
| 		if (cmp > 0) {
 | |
| 			continue;
 | |
| 		}
 | |
| 		if (!cmp) {
 | |
| 			/* Found it. */
 | |
| 			unreference_cached_app(cur);
 | |
| 			AST_RWLIST_REMOVE_CURRENT(list);
 | |
| 			ast_verb(2, "Unregistered application '%s'\n", cur->name);
 | |
| 			ast_string_field_free_memory(cur);
 | |
| 			ast_free(cur);
 | |
| 			break;
 | |
| 		}
 | |
| 		/* Not in container. */
 | |
| 		cur = NULL;
 | |
| 		break;
 | |
| 	}
 | |
| 	AST_RWLIST_TRAVERSE_SAFE_END;
 | |
| 	AST_RWLIST_UNLOCK(&apps);
 | |
| 
 | |
| 	ast_unlock_contexts();
 | |
| 
 | |
| 	return cur ? 0 : -1;
 | |
| }
 | |
| 
 | |
| char *ast_complete_applications(const char *line, const char *word, int state)
 | |
| {
 | |
| 	struct ast_app *app;
 | |
| 	int which = 0;
 | |
| 	int cmp;
 | |
| 	char *ret = NULL;
 | |
| 	size_t wordlen = strlen(word);
 | |
| 
 | |
| 	AST_RWLIST_RDLOCK(&apps);
 | |
| 	AST_RWLIST_TRAVERSE(&apps, app, list) {
 | |
| 		cmp = strncasecmp(word, app->name, wordlen);
 | |
| 		if (cmp < 0) {
 | |
| 			/* No more matches. */
 | |
| 			break;
 | |
| 		} else if (!cmp) {
 | |
| 			/* Found match. */
 | |
| 			if (state != -1) {
 | |
| 				if (++which <= state) {
 | |
| 					/* Not enough matches. */
 | |
| 					continue;
 | |
| 				}
 | |
| 				ret = ast_strdup(app->name);
 | |
| 				break;
 | |
| 			}
 | |
| 			if (ast_cli_completion_add(ast_strdup(app->name))) {
 | |
| 				break;
 | |
| 			}
 | |
| 		}
 | |
| 	}
 | |
| 	AST_RWLIST_UNLOCK(&apps);
 | |
| 
 | |
| 	return ret;
 | |
| }
 | |
| 
 | |
| const char *app_name(struct ast_app *app)
 | |
| {
 | |
| 	return app->name;
 | |
| }
 | |
| 
 | |
| /*!
 | |
|    \note This function is special. It saves the stack so that no matter
 | |
|    how many times it is called, it returns to the same place */
 | |
| int pbx_exec(struct ast_channel *c,	/*!< Channel */
 | |
| 	     struct ast_app *app,	/*!< Application */
 | |
| 	     const char *data)		/*!< Data for execution */
 | |
| {
 | |
| 	int res;
 | |
| 	struct ast_module_user *u = NULL;
 | |
| 	const char *saved_c_appl;
 | |
| 	const char *saved_c_data;
 | |
| 
 | |
| 	/* save channel values */
 | |
| 	saved_c_appl= ast_channel_appl(c);
 | |
| 	saved_c_data= ast_channel_data(c);
 | |
| 
 | |
| 	ast_channel_lock(c);
 | |
| 	ast_channel_appl_set(c, app->name);
 | |
| 	ast_channel_data_set(c, data);
 | |
| 	ast_channel_publish_snapshot(c);
 | |
| 	ast_channel_unlock(c);
 | |
| 
 | |
| 	if (app->module)
 | |
| 		u = __ast_module_user_add(app->module, c);
 | |
| 	res = app->execute(c, S_OR(data, ""));
 | |
| 	if (app->module && u)
 | |
| 		__ast_module_user_remove(app->module, u);
 | |
| 	/* restore channel values */
 | |
| 	ast_channel_appl_set(c, saved_c_appl);
 | |
| 	ast_channel_data_set(c, saved_c_data);
 | |
| 	return res;
 | |
| }
 | |
| 
 | |
| int ast_pbx_exec_application(struct ast_channel *chan, const char *app_name, const char *app_args)
 | |
| {
 | |
| 	int res = -1;
 | |
| 	struct ast_app *app;
 | |
| 
 | |
| 	app = pbx_findapp(app_name);
 | |
| 	if (!app) {
 | |
| 		ast_log(LOG_WARNING, "Could not find application (%s)\n", app_name);
 | |
| 	} else {
 | |
| 		struct ast_str *substituted_args = NULL;
 | |
| 
 | |
| 		if (!ast_strlen_zero(app_args) && (substituted_args = ast_str_create(16))) {
 | |
| 			ast_str_substitute_variables(&substituted_args, 0, chan, app_args);
 | |
| 			res = pbx_exec(chan, app, ast_str_buffer(substituted_args));
 | |
| 			ast_free(substituted_args);
 | |
| 		} else {
 | |
| 			if (!ast_strlen_zero(app_args)) {
 | |
| 				ast_log(LOG_WARNING, "Could not substitute application argument variables for %s\n", app_name);
 | |
| 			}
 | |
| 			res = pbx_exec(chan, app, app_args);
 | |
| 		}
 | |
| 	}
 | |
| 	return res;
 | |
| }
 | |
| 
 | |
| static struct ast_cli_entry app_cli[] = {
 | |
| 	AST_CLI_DEFINE(handle_show_applications, "Shows registered dialplan applications"),
 | |
| 	AST_CLI_DEFINE(handle_show_application, "Describe a specific dialplan application"),
 | |
| };
 | |
| 
 | |
| static void unload_pbx_app(void)
 | |
| {
 | |
| 	ast_cli_unregister_multiple(app_cli, ARRAY_LEN(app_cli));
 | |
| }
 | |
| 
 | |
| int load_pbx_app(void)
 | |
| {
 | |
| 	ast_cli_register_multiple(app_cli, ARRAY_LEN(app_cli));
 | |
| 	ast_register_cleanup(unload_pbx_app);
 | |
| 
 | |
| 	return 0;
 | |
| }
 |