mirror of
				https://github.com/asterisk/asterisk.git
				synced 2025-10-31 18:55:19 +00:00 
			
		
		
		
	This is the third patch in a series meant to reduce the bulk of pbx.c. This moves channel and global variable routines to their own source. Change-Id: Ibe8fb4647db11598591d443a99e3f99200a56bc6
		
			
				
	
	
		
			1181 lines
		
	
	
		
			34 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			1181 lines
		
	
	
		
			34 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 PBX variables routines.
 | |
|  *
 | |
|  * \author Corey Farrell <git@cfware.com>
 | |
|  */
 | |
| 
 | |
| /*** MODULEINFO
 | |
| 	<support_level>core</support_level>
 | |
|  ***/
 | |
| 
 | |
| #include "asterisk.h"
 | |
| 
 | |
| ASTERISK_REGISTER_FILE()
 | |
| 
 | |
| #include "asterisk/_private.h"
 | |
| #include "asterisk/app.h"
 | |
| #include "asterisk/ast_expr.h"
 | |
| #include "asterisk/chanvars.h"
 | |
| #include "asterisk/cli.h"
 | |
| #include "asterisk/linkedlists.h"
 | |
| #include "asterisk/lock.h"
 | |
| #include "asterisk/module.h"
 | |
| #include "asterisk/paths.h"
 | |
| #include "asterisk/pbx.h"
 | |
| #include "asterisk/stasis_channels.h"
 | |
| #include "pbx_private.h"
 | |
| 
 | |
| /*** DOCUMENTATION
 | |
| 	<application name="Set" language="en_US">
 | |
| 		<synopsis>
 | |
| 			Set channel variable or function value.
 | |
| 		</synopsis>
 | |
| 		<syntax argsep="=">
 | |
| 			<parameter name="name" required="true" />
 | |
| 			<parameter name="value" required="true" />
 | |
| 		</syntax>
 | |
| 		<description>
 | |
| 			<para>This function can be used to set the value of channel variables or dialplan functions.
 | |
| 			When setting variables, if the variable name is prefixed with <literal>_</literal>,
 | |
| 			the variable will be inherited into channels created from the current channel.
 | |
| 			If the variable name is prefixed with <literal>__</literal>, the variable will be
 | |
| 			inherited into channels created from the current channel and all children channels.</para>
 | |
| 			<note><para>If (and only if), in <filename>/etc/asterisk/asterisk.conf</filename>, you have
 | |
| 			a <literal>[compat]</literal> category, and you have <literal>app_set = 1.4</literal> under that, then
 | |
| 			the behavior of this app changes, and strips surrounding quotes from the right hand side as
 | |
| 			it did previously in 1.4.
 | |
| 			The advantages of not stripping out quoting, and not caring about the separator characters (comma and vertical bar)
 | |
| 			were sufficient to make these changes in 1.6. Confusion about how many backslashes would be needed to properly
 | |
| 			protect separators and quotes in various database access strings has been greatly
 | |
| 			reduced by these changes.</para></note>
 | |
| 		</description>
 | |
| 		<see-also>
 | |
| 			<ref type="application">MSet</ref>
 | |
| 			<ref type="function">GLOBAL</ref>
 | |
| 			<ref type="function">SET</ref>
 | |
| 			<ref type="function">ENV</ref>
 | |
| 		</see-also>
 | |
| 	</application>
 | |
| 	<application name="MSet" language="en_US">
 | |
| 		<synopsis>
 | |
| 			Set channel variable(s) or function value(s).
 | |
| 		</synopsis>
 | |
| 		<syntax>
 | |
| 			<parameter name="set1" required="true" argsep="=">
 | |
| 				<argument name="name1" required="true" />
 | |
| 				<argument name="value1" required="true" />
 | |
| 			</parameter>
 | |
| 			<parameter name="set2" multiple="true" argsep="=">
 | |
| 				<argument name="name2" required="true" />
 | |
| 				<argument name="value2" required="true" />
 | |
| 			</parameter>
 | |
| 		</syntax>
 | |
| 		<description>
 | |
| 			<para>This function can be used to set the value of channel variables or dialplan functions.
 | |
| 			When setting variables, if the variable name is prefixed with <literal>_</literal>,
 | |
| 			the variable will be inherited into channels created from the current channel
 | |
| 			If the variable name is prefixed with <literal>__</literal>, the variable will be
 | |
| 			inherited into channels created from the current channel and all children channels.
 | |
| 			MSet behaves in a similar fashion to the way Set worked in 1.2/1.4 and is thus
 | |
| 			prone to doing things that you may not expect. For example, it strips surrounding
 | |
| 			double-quotes from the right-hand side (value). If you need to put a separator
 | |
| 			character (comma or vert-bar), you will need to escape them by inserting a backslash
 | |
| 			before them. Avoid its use if possible.</para>
 | |
| 		</description>
 | |
| 		<see-also>
 | |
| 			<ref type="application">Set</ref>
 | |
| 		</see-also>
 | |
| 	</application>
 | |
|  ***/
 | |
| 
 | |
| AST_RWLOCK_DEFINE_STATIC(globalslock);
 | |
| static struct varshead globals = AST_LIST_HEAD_NOLOCK_INIT_VALUE;
 | |
| 
 | |
| /*!
 | |
|  * \brief extract offset:length from variable name.
 | |
|  * \return 1 if there is a offset:length part, which is
 | |
|  * trimmed off (values go into variables)
 | |
|  */
 | |
| static int parse_variable_name(char *var, int *offset, int *length, int *isfunc)
 | |
| {
 | |
| 	int parens = 0;
 | |
| 
 | |
| 	*offset = 0;
 | |
| 	*length = INT_MAX;
 | |
| 	*isfunc = 0;
 | |
| 	for (; *var; var++) {
 | |
| 		if (*var == '(') {
 | |
| 			(*isfunc)++;
 | |
| 			parens++;
 | |
| 		} else if (*var == ')') {
 | |
| 			parens--;
 | |
| 		} else if (*var == ':' && parens == 0) {
 | |
| 			*var++ = '\0';
 | |
| 			sscanf(var, "%30d:%30d", offset, length);
 | |
| 			return 1; /* offset:length valid */
 | |
| 		}
 | |
| 	}
 | |
| 	return 0;
 | |
| }
 | |
| 
 | |
| /*!
 | |
|  *\brief takes a substring. It is ok to call with value == workspace.
 | |
|  * \param value
 | |
|  * \param offset < 0 means start from the end of the string and set the beginning
 | |
|  *   to be that many characters back.
 | |
|  * \param length is the length of the substring, a value less than 0 means to leave
 | |
|  * that many off the end.
 | |
|  * \param workspace
 | |
|  * \param workspace_len
 | |
|  * Always return a copy in workspace.
 | |
|  */
 | |
| static char *substring(const char *value, int offset, int length, char *workspace, size_t workspace_len)
 | |
| {
 | |
| 	char *ret = workspace;
 | |
| 	int lr;	/* length of the input string after the copy */
 | |
| 
 | |
| 	ast_copy_string(workspace, value, workspace_len); /* always make a copy */
 | |
| 
 | |
| 	lr = strlen(ret); /* compute length after copy, so we never go out of the workspace */
 | |
| 
 | |
| 	/* Quick check if no need to do anything */
 | |
| 	if (offset == 0 && length >= lr)	/* take the whole string */
 | |
| 		return ret;
 | |
| 
 | |
| 	if (offset < 0)	{	/* translate negative offset into positive ones */
 | |
| 		offset = lr + offset;
 | |
| 		if (offset < 0) /* If the negative offset was greater than the length of the string, just start at the beginning */
 | |
| 			offset = 0;
 | |
| 	}
 | |
| 
 | |
| 	/* too large offset result in empty string so we know what to return */
 | |
| 	if (offset >= lr)
 | |
| 		return ret + lr;	/* the final '\0' */
 | |
| 
 | |
| 	ret += offset;		/* move to the start position */
 | |
| 	if (length >= 0 && length < lr - offset)	/* truncate if necessary */
 | |
| 		ret[length] = '\0';
 | |
| 	else if (length < 0) {
 | |
| 		if (lr > offset - length) /* After we remove from the front and from the rear, is there anything left? */
 | |
| 			ret[lr + length - offset] = '\0';
 | |
| 		else
 | |
| 			ret[0] = '\0';
 | |
| 	}
 | |
| 
 | |
| 	return ret;
 | |
| }
 | |
| 
 | |
| static const char *ast_str_substring(struct ast_str *value, int offset, int length)
 | |
| {
 | |
| 	int lr;	/* length of the input string after the copy */
 | |
| 
 | |
| 	lr = ast_str_strlen(value); /* compute length after copy, so we never go out of the workspace */
 | |
| 
 | |
| 	/* Quick check if no need to do anything */
 | |
| 	if (offset == 0 && length >= lr)	/* take the whole string */
 | |
| 		return ast_str_buffer(value);
 | |
| 
 | |
| 	if (offset < 0)	{	/* translate negative offset into positive ones */
 | |
| 		offset = lr + offset;
 | |
| 		if (offset < 0) /* If the negative offset was greater than the length of the string, just start at the beginning */
 | |
| 			offset = 0;
 | |
| 	}
 | |
| 
 | |
| 	/* too large offset result in empty string so we know what to return */
 | |
| 	if (offset >= lr) {
 | |
| 		ast_str_reset(value);
 | |
| 		return ast_str_buffer(value);
 | |
| 	}
 | |
| 
 | |
| 	if (offset > 0) {
 | |
| 		/* Go ahead and chop off the beginning */
 | |
| 		memmove(ast_str_buffer(value), ast_str_buffer(value) + offset, ast_str_strlen(value) - offset + 1);
 | |
| 		lr -= offset;
 | |
| 	}
 | |
| 
 | |
| 	if (length >= 0 && length < lr) {	/* truncate if necessary */
 | |
| 		char *tmp = ast_str_buffer(value);
 | |
| 		tmp[length] = '\0';
 | |
| 		ast_str_update(value);
 | |
| 	} else if (length < 0) {
 | |
| 		if (lr > -length) { /* After we remove from the front and from the rear, is there anything left? */
 | |
| 			char *tmp = ast_str_buffer(value);
 | |
| 			tmp[lr + length] = '\0';
 | |
| 			ast_str_update(value);
 | |
| 		} else {
 | |
| 			ast_str_reset(value);
 | |
| 		}
 | |
| 	} else {
 | |
| 		/* Nothing to do, but update the buffer length */
 | |
| 		ast_str_update(value);
 | |
| 	}
 | |
| 
 | |
| 	return ast_str_buffer(value);
 | |
| }
 | |
| 
 | |
| /*! \brief  Support for Asterisk built-in variables in the dialplan
 | |
| 
 | |
| \note	See also
 | |
| 	- \ref AstVar	Channel variables
 | |
| 	- \ref AstCauses The HANGUPCAUSE variable
 | |
|  */
 | |
| void pbx_retrieve_variable(struct ast_channel *c, const char *var, char **ret, char *workspace, int workspacelen, struct varshead *headp)
 | |
| {
 | |
| 	struct ast_str *str = ast_str_create(16);
 | |
| 	const char *cret;
 | |
| 
 | |
| 	cret = ast_str_retrieve_variable(&str, 0, c, headp, var);
 | |
| 	ast_copy_string(workspace, ast_str_buffer(str), workspacelen);
 | |
| 	*ret = cret ? workspace : NULL;
 | |
| 	ast_free(str);
 | |
| }
 | |
| 
 | |
| const char *ast_str_retrieve_variable(struct ast_str **str, ssize_t maxlen, struct ast_channel *c, struct varshead *headp, const char *var)
 | |
| {
 | |
| 	const char not_found = '\0';
 | |
| 	char *tmpvar;
 | |
| 	const char *ret;
 | |
| 	const char *s;	/* the result */
 | |
| 	int offset, length;
 | |
| 	int i, need_substring;
 | |
| 	struct varshead *places[2] = { headp, &globals };	/* list of places where we may look */
 | |
| 	char workspace[20];
 | |
| 
 | |
| 	if (c) {
 | |
| 		ast_channel_lock(c);
 | |
| 		places[0] = ast_channel_varshead(c);
 | |
| 	}
 | |
| 	/*
 | |
| 	 * Make a copy of var because parse_variable_name() modifies the string.
 | |
| 	 * Then if called directly, we might need to run substring() on the result;
 | |
| 	 * remember this for later in 'need_substring', 'offset' and 'length'
 | |
| 	 */
 | |
| 	tmpvar = ast_strdupa(var);	/* parse_variable_name modifies the string */
 | |
| 	need_substring = parse_variable_name(tmpvar, &offset, &length, &i /* ignored */);
 | |
| 
 | |
| 	/*
 | |
| 	 * Look first into predefined variables, then into variable lists.
 | |
| 	 * Variable 's' points to the result, according to the following rules:
 | |
| 	 * s == ¬_found (set at the beginning) means that we did not find a
 | |
| 	 *	matching variable and need to look into more places.
 | |
| 	 * If s != ¬_found, s is a valid result string as follows:
 | |
| 	 * s = NULL if the variable does not have a value;
 | |
| 	 *	you typically do this when looking for an unset predefined variable.
 | |
| 	 * s = workspace if the result has been assembled there;
 | |
| 	 *	typically done when the result is built e.g. with an snprintf(),
 | |
| 	 *	so we don't need to do an additional copy.
 | |
| 	 * s != workspace in case we have a string, that needs to be copied
 | |
| 	 *	(the ast_copy_string is done once for all at the end).
 | |
| 	 *	Typically done when the result is already available in some string.
 | |
| 	 */
 | |
| 	s = ¬_found;	/* default value */
 | |
| 	if (c) {	/* This group requires a valid channel */
 | |
| 		/* Names with common parts are looked up a piece at a time using strncmp. */
 | |
| 		if (!strncmp(var, "CALL", 4)) {
 | |
| 			if (!strncmp(var + 4, "ING", 3)) {
 | |
| 				if (!strcmp(var + 7, "PRES")) {			/* CALLINGPRES */
 | |
| 					ast_str_set(str, maxlen, "%d",
 | |
| 						ast_party_id_presentation(&ast_channel_caller(c)->id));
 | |
| 					s = ast_str_buffer(*str);
 | |
| 				} else if (!strcmp(var + 7, "ANI2")) {		/* CALLINGANI2 */
 | |
| 					ast_str_set(str, maxlen, "%d", ast_channel_caller(c)->ani2);
 | |
| 					s = ast_str_buffer(*str);
 | |
| 				} else if (!strcmp(var + 7, "TON")) {		/* CALLINGTON */
 | |
| 					ast_str_set(str, maxlen, "%d", ast_channel_caller(c)->id.number.plan);
 | |
| 					s = ast_str_buffer(*str);
 | |
| 				} else if (!strcmp(var + 7, "TNS")) {		/* CALLINGTNS */
 | |
| 					ast_str_set(str, maxlen, "%d", ast_channel_dialed(c)->transit_network_select);
 | |
| 					s = ast_str_buffer(*str);
 | |
| 				}
 | |
| 			}
 | |
| 		} else if (!strcmp(var, "HINT")) {
 | |
| 			s = ast_str_get_hint(str, maxlen, NULL, 0, c, ast_channel_context(c), ast_channel_exten(c)) ? ast_str_buffer(*str) : NULL;
 | |
| 		} else if (!strcmp(var, "HINTNAME")) {
 | |
| 			s = ast_str_get_hint(NULL, 0, str, maxlen, c, ast_channel_context(c), ast_channel_exten(c)) ? ast_str_buffer(*str) : NULL;
 | |
| 		} else if (!strcmp(var, "EXTEN")) {
 | |
| 			s = ast_channel_exten(c);
 | |
| 		} else if (!strcmp(var, "CONTEXT")) {
 | |
| 			s = ast_channel_context(c);
 | |
| 		} else if (!strcmp(var, "PRIORITY")) {
 | |
| 			ast_str_set(str, maxlen, "%d", ast_channel_priority(c));
 | |
| 			s = ast_str_buffer(*str);
 | |
| 		} else if (!strcmp(var, "CHANNEL")) {
 | |
| 			s = ast_channel_name(c);
 | |
| 		} else if (!strcmp(var, "UNIQUEID")) {
 | |
| 			s = ast_channel_uniqueid(c);
 | |
| 		} else if (!strcmp(var, "HANGUPCAUSE")) {
 | |
| 			ast_str_set(str, maxlen, "%d", ast_channel_hangupcause(c));
 | |
| 			s = ast_str_buffer(*str);
 | |
| 		}
 | |
| 	}
 | |
| 	if (s == ¬_found) { /* look for more */
 | |
| 		if (!strcmp(var, "EPOCH")) {
 | |
| 			ast_str_set(str, maxlen, "%d", (int) time(NULL));
 | |
| 			s = ast_str_buffer(*str);
 | |
| 		} else if (!strcmp(var, "SYSTEMNAME")) {
 | |
| 			s = ast_config_AST_SYSTEM_NAME;
 | |
| 		} else if (!strcmp(var, "ASTETCDIR")) {
 | |
| 			s = ast_config_AST_CONFIG_DIR;
 | |
| 		} else if (!strcmp(var, "ASTMODDIR")) {
 | |
| 			s = ast_config_AST_MODULE_DIR;
 | |
| 		} else if (!strcmp(var, "ASTVARLIBDIR")) {
 | |
| 			s = ast_config_AST_VAR_DIR;
 | |
| 		} else if (!strcmp(var, "ASTDBDIR")) {
 | |
| 			s = ast_config_AST_DB;
 | |
| 		} else if (!strcmp(var, "ASTKEYDIR")) {
 | |
| 			s = ast_config_AST_KEY_DIR;
 | |
| 		} else if (!strcmp(var, "ASTDATADIR")) {
 | |
| 			s = ast_config_AST_DATA_DIR;
 | |
| 		} else if (!strcmp(var, "ASTAGIDIR")) {
 | |
| 			s = ast_config_AST_AGI_DIR;
 | |
| 		} else if (!strcmp(var, "ASTSPOOLDIR")) {
 | |
| 			s = ast_config_AST_SPOOL_DIR;
 | |
| 		} else if (!strcmp(var, "ASTRUNDIR")) {
 | |
| 			s = ast_config_AST_RUN_DIR;
 | |
| 		} else if (!strcmp(var, "ASTLOGDIR")) {
 | |
| 			s = ast_config_AST_LOG_DIR;
 | |
| 		} else if (!strcmp(var, "ENTITYID")) {
 | |
| 			ast_eid_to_str(workspace, sizeof(workspace), &ast_eid_default);
 | |
| 			s = workspace;
 | |
| 		}
 | |
| 	}
 | |
| 	/* if not found, look into chanvars or global vars */
 | |
| 	for (i = 0; s == ¬_found && i < ARRAY_LEN(places); i++) {
 | |
| 		struct ast_var_t *variables;
 | |
| 		if (!places[i])
 | |
| 			continue;
 | |
| 		if (places[i] == &globals)
 | |
| 			ast_rwlock_rdlock(&globalslock);
 | |
| 		AST_LIST_TRAVERSE(places[i], variables, entries) {
 | |
| 			if (!strcmp(ast_var_name(variables), var)) {
 | |
| 				s = ast_var_value(variables);
 | |
| 				break;
 | |
| 			}
 | |
| 		}
 | |
| 		if (places[i] == &globals)
 | |
| 			ast_rwlock_unlock(&globalslock);
 | |
| 	}
 | |
| 	if (s == ¬_found || s == NULL) {
 | |
| 		ast_debug(5, "Result of '%s' is NULL\n", var);
 | |
| 		ret = NULL;
 | |
| 	} else {
 | |
| 		ast_debug(5, "Result of '%s' is '%s'\n", var, s);
 | |
| 		if (s != ast_str_buffer(*str)) {
 | |
| 			ast_str_set(str, maxlen, "%s", s);
 | |
| 		}
 | |
| 		ret = ast_str_buffer(*str);
 | |
| 		if (need_substring) {
 | |
| 			ret = ast_str_substring(*str, offset, length);
 | |
| 			ast_debug(2, "Final result of '%s' is '%s'\n", var, ret);
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	if (c) {
 | |
| 		ast_channel_unlock(c);
 | |
| 	}
 | |
| 	return ret;
 | |
| }
 | |
| 
 | |
| void ast_str_substitute_variables_full(struct ast_str **buf, ssize_t maxlen, struct ast_channel *c, struct varshead *headp, const char *templ, size_t *used)
 | |
| {
 | |
| 	/* Substitutes variables into buf, based on string templ */
 | |
| 	char *cp4 = NULL;
 | |
| 	const char *whereweare;
 | |
| 	int orig_size = 0;
 | |
| 	int offset, offset2, isfunction;
 | |
| 	const char *nextvar, *nextexp, *nextthing;
 | |
| 	const char *vars, *vare;
 | |
| 	char *finalvars;
 | |
| 	int pos, brackets, needsub, len;
 | |
| 	struct ast_str *substr1 = ast_str_create(16), *substr2 = NULL, *substr3 = ast_str_create(16);
 | |
| 
 | |
| 	ast_str_reset(*buf);
 | |
| 	whereweare = templ;
 | |
| 	while (!ast_strlen_zero(whereweare)) {
 | |
| 		/* reset our buffer */
 | |
| 		ast_str_reset(substr3);
 | |
| 
 | |
| 		/* Assume we're copying the whole remaining string */
 | |
| 		pos = strlen(whereweare);
 | |
| 		nextvar = NULL;
 | |
| 		nextexp = NULL;
 | |
| 		nextthing = strchr(whereweare, '$');
 | |
| 		if (nextthing) {
 | |
| 			switch (nextthing[1]) {
 | |
| 			case '{':
 | |
| 				nextvar = nextthing;
 | |
| 				pos = nextvar - whereweare;
 | |
| 				break;
 | |
| 			case '[':
 | |
| 				nextexp = nextthing;
 | |
| 				pos = nextexp - whereweare;
 | |
| 				break;
 | |
| 			default:
 | |
| 				pos = 1;
 | |
| 			}
 | |
| 		}
 | |
| 
 | |
| 		if (pos) {
 | |
| 			/* Copy that many bytes */
 | |
| 			ast_str_append_substr(buf, maxlen, whereweare, pos);
 | |
| 
 | |
| 			templ += pos;
 | |
| 			whereweare += pos;
 | |
| 		}
 | |
| 
 | |
| 		if (nextvar) {
 | |
| 			/* We have a variable.  Find the start and end, and determine
 | |
| 			   if we are going to have to recursively call ourselves on the
 | |
| 			   contents */
 | |
| 			vars = vare = nextvar + 2;
 | |
| 			brackets = 1;
 | |
| 			needsub = 0;
 | |
| 
 | |
| 			/* Find the end of it */
 | |
| 			while (brackets && *vare) {
 | |
| 				if ((vare[0] == '$') && (vare[1] == '{')) {
 | |
| 					needsub++;
 | |
| 				} else if (vare[0] == '{') {
 | |
| 					brackets++;
 | |
| 				} else if (vare[0] == '}') {
 | |
| 					brackets--;
 | |
| 				} else if ((vare[0] == '$') && (vare[1] == '['))
 | |
| 					needsub++;
 | |
| 				vare++;
 | |
| 			}
 | |
| 			if (brackets)
 | |
| 				ast_log(LOG_WARNING, "Error in extension logic (missing '}')\n");
 | |
| 			len = vare - vars - 1;
 | |
| 
 | |
| 			/* Skip totally over variable string */
 | |
| 			whereweare += (len + 3);
 | |
| 
 | |
| 			/* Store variable name (and truncate) */
 | |
| 			ast_str_set_substr(&substr1, 0, vars, len);
 | |
| 			ast_debug(5, "Evaluating '%s' (from '%s' len %d)\n", ast_str_buffer(substr1), vars, len);
 | |
| 
 | |
| 			/* Substitute if necessary */
 | |
| 			if (needsub) {
 | |
| 				size_t my_used;
 | |
| 
 | |
| 				if (!substr2) {
 | |
| 					substr2 = ast_str_create(16);
 | |
| 				}
 | |
| 				ast_str_substitute_variables_full(&substr2, 0, c, headp, ast_str_buffer(substr1), &my_used);
 | |
| 				finalvars = ast_str_buffer(substr2);
 | |
| 			} else {
 | |
| 				finalvars = ast_str_buffer(substr1);
 | |
| 			}
 | |
| 
 | |
| 			parse_variable_name(finalvars, &offset, &offset2, &isfunction);
 | |
| 			if (isfunction) {
 | |
| 				/* Evaluate function */
 | |
| 				if (c || !headp) {
 | |
| 					cp4 = ast_func_read2(c, finalvars, &substr3, 0) ? NULL : ast_str_buffer(substr3);
 | |
| 				} else {
 | |
| 					struct varshead old;
 | |
| 					struct ast_channel *bogus = ast_dummy_channel_alloc();
 | |
| 					if (bogus) {
 | |
| 						memcpy(&old, ast_channel_varshead(bogus), sizeof(old));
 | |
| 						memcpy(ast_channel_varshead(bogus), headp, sizeof(*ast_channel_varshead(bogus)));
 | |
| 						cp4 = ast_func_read2(c, finalvars, &substr3, 0) ? NULL : ast_str_buffer(substr3);
 | |
| 						/* Don't deallocate the varshead that was passed in */
 | |
| 						memcpy(ast_channel_varshead(bogus), &old, sizeof(*ast_channel_varshead(bogus)));
 | |
| 						ast_channel_unref(bogus);
 | |
| 					} else {
 | |
| 						ast_log(LOG_ERROR, "Unable to allocate bogus channel for variable substitution.  Function results may be blank.\n");
 | |
| 					}
 | |
| 				}
 | |
| 				ast_debug(2, "Function %s result is '%s'\n", finalvars, cp4 ? cp4 : "(null)");
 | |
| 			} else {
 | |
| 				/* Retrieve variable value */
 | |
| 				ast_str_retrieve_variable(&substr3, 0, c, headp, finalvars);
 | |
| 				cp4 = ast_str_buffer(substr3);
 | |
| 			}
 | |
| 			if (cp4) {
 | |
| 				ast_str_substring(substr3, offset, offset2);
 | |
| 				ast_str_append(buf, maxlen, "%s", ast_str_buffer(substr3));
 | |
| 			}
 | |
| 		} else if (nextexp) {
 | |
| 			/* We have an expression.  Find the start and end, and determine
 | |
| 			   if we are going to have to recursively call ourselves on the
 | |
| 			   contents */
 | |
| 			vars = vare = nextexp + 2;
 | |
| 			brackets = 1;
 | |
| 			needsub = 0;
 | |
| 
 | |
| 			/* Find the end of it */
 | |
| 			while (brackets && *vare) {
 | |
| 				if ((vare[0] == '$') && (vare[1] == '[')) {
 | |
| 					needsub++;
 | |
| 					brackets++;
 | |
| 					vare++;
 | |
| 				} else if (vare[0] == '[') {
 | |
| 					brackets++;
 | |
| 				} else if (vare[0] == ']') {
 | |
| 					brackets--;
 | |
| 				} else if ((vare[0] == '$') && (vare[1] == '{')) {
 | |
| 					needsub++;
 | |
| 					vare++;
 | |
| 				}
 | |
| 				vare++;
 | |
| 			}
 | |
| 			if (brackets)
 | |
| 				ast_log(LOG_WARNING, "Error in extension logic (missing ']')\n");
 | |
| 			len = vare - vars - 1;
 | |
| 
 | |
| 			/* Skip totally over expression */
 | |
| 			whereweare += (len + 3);
 | |
| 
 | |
| 			/* Store variable name (and truncate) */
 | |
| 			ast_str_set_substr(&substr1, 0, vars, len);
 | |
| 
 | |
| 			/* Substitute if necessary */
 | |
| 			if (needsub) {
 | |
| 				size_t my_used;
 | |
| 
 | |
| 				if (!substr2) {
 | |
| 					substr2 = ast_str_create(16);
 | |
| 				}
 | |
| 				ast_str_substitute_variables_full(&substr2, 0, c, headp, ast_str_buffer(substr1), &my_used);
 | |
| 				finalvars = ast_str_buffer(substr2);
 | |
| 			} else {
 | |
| 				finalvars = ast_str_buffer(substr1);
 | |
| 			}
 | |
| 
 | |
| 			if (ast_str_expr(&substr3, 0, c, finalvars)) {
 | |
| 				ast_debug(2, "Expression result is '%s'\n", ast_str_buffer(substr3));
 | |
| 			}
 | |
| 			ast_str_append(buf, maxlen, "%s", ast_str_buffer(substr3));
 | |
| 		}
 | |
| 	}
 | |
| 	*used = ast_str_strlen(*buf) - orig_size;
 | |
| 	ast_free(substr1);
 | |
| 	ast_free(substr2);
 | |
| 	ast_free(substr3);
 | |
| }
 | |
| 
 | |
| void ast_str_substitute_variables(struct ast_str **buf, ssize_t maxlen, struct ast_channel *chan, const char *templ)
 | |
| {
 | |
| 	size_t used;
 | |
| 	ast_str_substitute_variables_full(buf, maxlen, chan, NULL, templ, &used);
 | |
| }
 | |
| 
 | |
| void ast_str_substitute_variables_varshead(struct ast_str **buf, ssize_t maxlen, struct varshead *headp, const char *templ)
 | |
| {
 | |
| 	size_t used;
 | |
| 	ast_str_substitute_variables_full(buf, maxlen, NULL, headp, templ, &used);
 | |
| }
 | |
| 
 | |
| void pbx_substitute_variables_helper_full(struct ast_channel *c, struct varshead *headp, const char *cp1, char *cp2, int count, size_t *used)
 | |
| {
 | |
| 	/* Substitutes variables into cp2, based on string cp1, cp2 NO LONGER NEEDS TO BE ZEROED OUT!!!!  */
 | |
| 	char *cp4 = NULL;
 | |
| 	const char *whereweare, *orig_cp2 = cp2;
 | |
| 	int length, offset, offset2, isfunction;
 | |
| 	char *workspace = NULL;
 | |
| 	char *ltmp = NULL, *var = NULL;
 | |
| 	char *nextvar, *nextexp, *nextthing;
 | |
| 	char *vars, *vare;
 | |
| 	int pos, brackets, needsub, len;
 | |
| 
 | |
| 	*cp2 = 0; /* just in case nothing ends up there */
 | |
| 	whereweare = cp1;
 | |
| 	while (!ast_strlen_zero(whereweare) && count) {
 | |
| 		/* Assume we're copying the whole remaining string */
 | |
| 		pos = strlen(whereweare);
 | |
| 		nextvar = NULL;
 | |
| 		nextexp = NULL;
 | |
| 		nextthing = strchr(whereweare, '$');
 | |
| 		if (nextthing) {
 | |
| 			switch (nextthing[1]) {
 | |
| 			case '{':
 | |
| 				nextvar = nextthing;
 | |
| 				pos = nextvar - whereweare;
 | |
| 				break;
 | |
| 			case '[':
 | |
| 				nextexp = nextthing;
 | |
| 				pos = nextexp - whereweare;
 | |
| 				break;
 | |
| 			default:
 | |
| 				pos = 1;
 | |
| 			}
 | |
| 		}
 | |
| 
 | |
| 		if (pos) {
 | |
| 			/* Can't copy more than 'count' bytes */
 | |
| 			if (pos > count)
 | |
| 				pos = count;
 | |
| 
 | |
| 			/* Copy that many bytes */
 | |
| 			memcpy(cp2, whereweare, pos);
 | |
| 
 | |
| 			count -= pos;
 | |
| 			cp2 += pos;
 | |
| 			whereweare += pos;
 | |
| 			*cp2 = 0;
 | |
| 		}
 | |
| 
 | |
| 		if (nextvar) {
 | |
| 			/* We have a variable.  Find the start and end, and determine
 | |
| 			   if we are going to have to recursively call ourselves on the
 | |
| 			   contents */
 | |
| 			vars = vare = nextvar + 2;
 | |
| 			brackets = 1;
 | |
| 			needsub = 0;
 | |
| 
 | |
| 			/* Find the end of it */
 | |
| 			while (brackets && *vare) {
 | |
| 				if ((vare[0] == '$') && (vare[1] == '{')) {
 | |
| 					needsub++;
 | |
| 				} else if (vare[0] == '{') {
 | |
| 					brackets++;
 | |
| 				} else if (vare[0] == '}') {
 | |
| 					brackets--;
 | |
| 				} else if ((vare[0] == '$') && (vare[1] == '['))
 | |
| 					needsub++;
 | |
| 				vare++;
 | |
| 			}
 | |
| 			if (brackets)
 | |
| 				ast_log(LOG_WARNING, "Error in extension logic (missing '}')\n");
 | |
| 			len = vare - vars - 1;
 | |
| 
 | |
| 			/* Skip totally over variable string */
 | |
| 			whereweare += (len + 3);
 | |
| 
 | |
| 			if (!var)
 | |
| 				var = ast_alloca(VAR_BUF_SIZE);
 | |
| 
 | |
| 			/* Store variable name (and truncate) */
 | |
| 			ast_copy_string(var, vars, len + 1);
 | |
| 
 | |
| 			/* Substitute if necessary */
 | |
| 			if (needsub) {
 | |
| 				size_t my_used;
 | |
| 
 | |
| 				if (!ltmp) {
 | |
| 					ltmp = ast_alloca(VAR_BUF_SIZE);
 | |
| 				}
 | |
| 				pbx_substitute_variables_helper_full(c, headp, var, ltmp, VAR_BUF_SIZE - 1, &my_used);
 | |
| 				vars = ltmp;
 | |
| 			} else {
 | |
| 				vars = var;
 | |
| 			}
 | |
| 
 | |
| 			if (!workspace)
 | |
| 				workspace = ast_alloca(VAR_BUF_SIZE);
 | |
| 
 | |
| 			workspace[0] = '\0';
 | |
| 
 | |
| 			parse_variable_name(vars, &offset, &offset2, &isfunction);
 | |
| 			if (isfunction) {
 | |
| 				/* Evaluate function */
 | |
| 				if (c || !headp)
 | |
| 					cp4 = ast_func_read(c, vars, workspace, VAR_BUF_SIZE) ? NULL : workspace;
 | |
| 				else {
 | |
| 					struct varshead old;
 | |
| 					struct ast_channel *c = ast_dummy_channel_alloc();
 | |
| 					if (c) {
 | |
| 						memcpy(&old, ast_channel_varshead(c), sizeof(old));
 | |
| 						memcpy(ast_channel_varshead(c), headp, sizeof(*ast_channel_varshead(c)));
 | |
| 						cp4 = ast_func_read(c, vars, workspace, VAR_BUF_SIZE) ? NULL : workspace;
 | |
| 						/* Don't deallocate the varshead that was passed in */
 | |
| 						memcpy(ast_channel_varshead(c), &old, sizeof(*ast_channel_varshead(c)));
 | |
| 						c = ast_channel_unref(c);
 | |
| 					} else {
 | |
| 						ast_log(LOG_ERROR, "Unable to allocate bogus channel for variable substitution.  Function results may be blank.\n");
 | |
| 					}
 | |
| 				}
 | |
| 				ast_debug(2, "Function %s result is '%s'\n", vars, cp4 ? cp4 : "(null)");
 | |
| 			} else {
 | |
| 				/* Retrieve variable value */
 | |
| 				pbx_retrieve_variable(c, vars, &cp4, workspace, VAR_BUF_SIZE, headp);
 | |
| 			}
 | |
| 			if (cp4) {
 | |
| 				cp4 = substring(cp4, offset, offset2, workspace, VAR_BUF_SIZE);
 | |
| 
 | |
| 				length = strlen(cp4);
 | |
| 				if (length > count)
 | |
| 					length = count;
 | |
| 				memcpy(cp2, cp4, length);
 | |
| 				count -= length;
 | |
| 				cp2 += length;
 | |
| 				*cp2 = 0;
 | |
| 			}
 | |
| 		} else if (nextexp) {
 | |
| 			/* We have an expression.  Find the start and end, and determine
 | |
| 			   if we are going to have to recursively call ourselves on the
 | |
| 			   contents */
 | |
| 			vars = vare = nextexp + 2;
 | |
| 			brackets = 1;
 | |
| 			needsub = 0;
 | |
| 
 | |
| 			/* Find the end of it */
 | |
| 			while (brackets && *vare) {
 | |
| 				if ((vare[0] == '$') && (vare[1] == '[')) {
 | |
| 					needsub++;
 | |
| 					brackets++;
 | |
| 					vare++;
 | |
| 				} else if (vare[0] == '[') {
 | |
| 					brackets++;
 | |
| 				} else if (vare[0] == ']') {
 | |
| 					brackets--;
 | |
| 				} else if ((vare[0] == '$') && (vare[1] == '{')) {
 | |
| 					needsub++;
 | |
| 					vare++;
 | |
| 				}
 | |
| 				vare++;
 | |
| 			}
 | |
| 			if (brackets)
 | |
| 				ast_log(LOG_WARNING, "Error in extension logic (missing ']')\n");
 | |
| 			len = vare - vars - 1;
 | |
| 
 | |
| 			/* Skip totally over expression */
 | |
| 			whereweare += (len + 3);
 | |
| 
 | |
| 			if (!var)
 | |
| 				var = ast_alloca(VAR_BUF_SIZE);
 | |
| 
 | |
| 			/* Store variable name (and truncate) */
 | |
| 			ast_copy_string(var, vars, len + 1);
 | |
| 
 | |
| 			/* Substitute if necessary */
 | |
| 			if (needsub) {
 | |
| 				size_t my_used;
 | |
| 
 | |
| 				if (!ltmp) {
 | |
| 					ltmp = ast_alloca(VAR_BUF_SIZE);
 | |
| 				}
 | |
| 				pbx_substitute_variables_helper_full(c, headp, var, ltmp, VAR_BUF_SIZE - 1, &my_used);
 | |
| 				vars = ltmp;
 | |
| 			} else {
 | |
| 				vars = var;
 | |
| 			}
 | |
| 
 | |
| 			length = ast_expr(vars, cp2, count, c);
 | |
| 
 | |
| 			if (length) {
 | |
| 				ast_debug(1, "Expression result is '%s'\n", cp2);
 | |
| 				count -= length;
 | |
| 				cp2 += length;
 | |
| 				*cp2 = 0;
 | |
| 			}
 | |
| 		}
 | |
| 	}
 | |
| 	*used = cp2 - orig_cp2;
 | |
| }
 | |
| 
 | |
| void pbx_substitute_variables_helper(struct ast_channel *c, const char *cp1, char *cp2, int count)
 | |
| {
 | |
| 	size_t used;
 | |
| 	pbx_substitute_variables_helper_full(c, (c) ? ast_channel_varshead(c) : NULL, cp1, cp2, count, &used);
 | |
| }
 | |
| 
 | |
| void pbx_substitute_variables_varshead(struct varshead *headp, const char *cp1, char *cp2, int count)
 | |
| {
 | |
| 	size_t used;
 | |
| 	pbx_substitute_variables_helper_full(NULL, headp, cp1, cp2, count, &used);
 | |
| }
 | |
| 
 | |
| /*! \brief CLI support for listing global variables in a parseable way */
 | |
| static char *handle_show_globals(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
 | |
| {
 | |
| 	int i = 0;
 | |
| 	struct ast_var_t *newvariable;
 | |
| 
 | |
| 	switch (cmd) {
 | |
| 	case CLI_INIT:
 | |
| 		e->command = "dialplan show globals";
 | |
| 		e->usage =
 | |
| 			"Usage: dialplan show globals\n"
 | |
| 			"       List current global dialplan variables and their values\n";
 | |
| 		return NULL;
 | |
| 	case CLI_GENERATE:
 | |
| 		return NULL;
 | |
| 	}
 | |
| 
 | |
| 	ast_rwlock_rdlock(&globalslock);
 | |
| 	AST_LIST_TRAVERSE (&globals, newvariable, entries) {
 | |
| 		i++;
 | |
| 		ast_cli(a->fd, "   %s=%s\n", ast_var_name(newvariable), ast_var_value(newvariable));
 | |
| 	}
 | |
| 	ast_rwlock_unlock(&globalslock);
 | |
| 	ast_cli(a->fd, "\n    -- %d variable(s)\n", i);
 | |
| 
 | |
| 	return CLI_SUCCESS;
 | |
| }
 | |
| 
 | |
| /*! \brief CLI support for listing chanvar's variables in a parseable way */
 | |
| static char *handle_show_chanvar(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
 | |
| {
 | |
| 	struct ast_channel *chan;
 | |
| 	struct ast_var_t *var;
 | |
| 
 | |
| 	switch (cmd) {
 | |
| 	case CLI_INIT:
 | |
| 		e->command = "dialplan show chanvar";
 | |
| 		e->usage =
 | |
| 			"Usage: dialplan show chanvar <channel>\n"
 | |
| 			"       List current channel variables and their values\n";
 | |
| 		return NULL;
 | |
| 	case CLI_GENERATE:
 | |
| 		return ast_complete_channels(a->line, a->word, a->pos, a->n, 3);
 | |
| 	}
 | |
| 
 | |
| 	if (a->argc != e->args + 1) {
 | |
| 		return CLI_SHOWUSAGE;
 | |
| 	}
 | |
| 
 | |
| 	chan = ast_channel_get_by_name(a->argv[e->args]);
 | |
| 	if (!chan) {
 | |
| 		ast_cli(a->fd, "Channel '%s' not found\n", a->argv[e->args]);
 | |
| 		return CLI_FAILURE;
 | |
| 	}
 | |
| 
 | |
| 	ast_channel_lock(chan);
 | |
| 	AST_LIST_TRAVERSE(ast_channel_varshead(chan), var, entries) {
 | |
| 		ast_cli(a->fd, "%s=%s\n", ast_var_name(var), ast_var_value(var));
 | |
| 	}
 | |
| 	ast_channel_unlock(chan);
 | |
| 
 | |
| 	ast_channel_unref(chan);
 | |
| 	return CLI_SUCCESS;
 | |
| }
 | |
| 
 | |
| static char *handle_set_global(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
 | |
| {
 | |
| 	switch (cmd) {
 | |
| 	case CLI_INIT:
 | |
| 		e->command = "dialplan set global";
 | |
| 		e->usage =
 | |
| 			"Usage: dialplan set global <name> <value>\n"
 | |
| 			"       Set global dialplan variable <name> to <value>\n";
 | |
| 		return NULL;
 | |
| 	case CLI_GENERATE:
 | |
| 		return NULL;
 | |
| 	}
 | |
| 
 | |
| 	if (a->argc != e->args + 2)
 | |
| 		return CLI_SHOWUSAGE;
 | |
| 
 | |
| 	pbx_builtin_setvar_helper(NULL, a->argv[3], a->argv[4]);
 | |
| 	ast_cli(a->fd, "\n    -- Global variable '%s' set to '%s'\n", a->argv[3], a->argv[4]);
 | |
| 
 | |
| 	return CLI_SUCCESS;
 | |
| }
 | |
| 
 | |
| static char *handle_set_chanvar(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
 | |
| {
 | |
| 	struct ast_channel *chan;
 | |
| 	const char *chan_name, *var_name, *var_value;
 | |
| 
 | |
| 	switch (cmd) {
 | |
| 	case CLI_INIT:
 | |
| 		e->command = "dialplan set chanvar";
 | |
| 		e->usage =
 | |
| 			"Usage: dialplan set chanvar <channel> <varname> <value>\n"
 | |
| 			"       Set channel variable <varname> to <value>\n";
 | |
| 		return NULL;
 | |
| 	case CLI_GENERATE:
 | |
| 		return ast_complete_channels(a->line, a->word, a->pos, a->n, 3);
 | |
| 	}
 | |
| 
 | |
| 	if (a->argc != e->args + 3)
 | |
| 		return CLI_SHOWUSAGE;
 | |
| 
 | |
| 	chan_name = a->argv[e->args];
 | |
| 	var_name = a->argv[e->args + 1];
 | |
| 	var_value = a->argv[e->args + 2];
 | |
| 
 | |
| 	if (!(chan = ast_channel_get_by_name(chan_name))) {
 | |
| 		ast_cli(a->fd, "Channel '%s' not found\n", chan_name);
 | |
| 		return CLI_FAILURE;
 | |
| 	}
 | |
| 
 | |
| 	pbx_builtin_setvar_helper(chan, var_name, var_value);
 | |
| 
 | |
| 	chan = ast_channel_unref(chan);
 | |
| 
 | |
| 	ast_cli(a->fd, "\n    -- Channel variable '%s' set to '%s' for '%s'\n",  var_name, var_value, chan_name);
 | |
| 
 | |
| 	return CLI_SUCCESS;
 | |
| }
 | |
| 
 | |
| int pbx_builtin_serialize_variables(struct ast_channel *chan, struct ast_str **buf)
 | |
| {
 | |
| 	struct ast_var_t *variables;
 | |
| 	const char *var, *val;
 | |
| 	int total = 0;
 | |
| 
 | |
| 	if (!chan)
 | |
| 		return 0;
 | |
| 
 | |
| 	ast_str_reset(*buf);
 | |
| 
 | |
| 	ast_channel_lock(chan);
 | |
| 
 | |
| 	AST_LIST_TRAVERSE(ast_channel_varshead(chan), variables, entries) {
 | |
| 		if ((var = ast_var_name(variables)) && (val = ast_var_value(variables))
 | |
| 		   /* && !ast_strlen_zero(var) && !ast_strlen_zero(val) */
 | |
| 		   ) {
 | |
| 			if (ast_str_append(buf, 0, "%s=%s\n", var, val) < 0) {
 | |
| 				ast_log(LOG_ERROR, "Data Buffer Size Exceeded!\n");
 | |
| 				break;
 | |
| 			} else
 | |
| 				total++;
 | |
| 		} else
 | |
| 			break;
 | |
| 	}
 | |
| 
 | |
| 	ast_channel_unlock(chan);
 | |
| 
 | |
| 	return total;
 | |
| }
 | |
| 
 | |
| const char *pbx_builtin_getvar_helper(struct ast_channel *chan, const char *name)
 | |
| {
 | |
| 	struct ast_var_t *variables;
 | |
| 	const char *ret = NULL;
 | |
| 	int i;
 | |
| 	struct varshead *places[2] = { NULL, &globals };
 | |
| 
 | |
| 	if (!name)
 | |
| 		return NULL;
 | |
| 
 | |
| 	if (chan) {
 | |
| 		ast_channel_lock(chan);
 | |
| 		places[0] = ast_channel_varshead(chan);
 | |
| 	}
 | |
| 
 | |
| 	for (i = 0; i < 2; i++) {
 | |
| 		if (!places[i])
 | |
| 			continue;
 | |
| 		if (places[i] == &globals)
 | |
| 			ast_rwlock_rdlock(&globalslock);
 | |
| 		AST_LIST_TRAVERSE(places[i], variables, entries) {
 | |
| 			if (!strcmp(name, ast_var_name(variables))) {
 | |
| 				ret = ast_var_value(variables);
 | |
| 				break;
 | |
| 			}
 | |
| 		}
 | |
| 		if (places[i] == &globals)
 | |
| 			ast_rwlock_unlock(&globalslock);
 | |
| 		if (ret)
 | |
| 			break;
 | |
| 	}
 | |
| 
 | |
| 	if (chan)
 | |
| 		ast_channel_unlock(chan);
 | |
| 
 | |
| 	return ret;
 | |
| }
 | |
| 
 | |
| void pbx_builtin_pushvar_helper(struct ast_channel *chan, const char *name, const char *value)
 | |
| {
 | |
| 	struct ast_var_t *newvariable;
 | |
| 	struct varshead *headp;
 | |
| 
 | |
| 	if (name[strlen(name)-1] == ')') {
 | |
| 		char *function = ast_strdupa(name);
 | |
| 
 | |
| 		ast_log(LOG_WARNING, "Cannot push a value onto a function\n");
 | |
| 		ast_func_write(chan, function, value);
 | |
| 		return;
 | |
| 	}
 | |
| 
 | |
| 	if (chan) {
 | |
| 		ast_channel_lock(chan);
 | |
| 		headp = ast_channel_varshead(chan);
 | |
| 	} else {
 | |
| 		ast_rwlock_wrlock(&globalslock);
 | |
| 		headp = &globals;
 | |
| 	}
 | |
| 
 | |
| 	if (value && (newvariable = ast_var_assign(name, value))) {
 | |
| 		if (headp == &globals)
 | |
| 			ast_verb(2, "Setting global variable '%s' to '%s'\n", name, value);
 | |
| 		AST_LIST_INSERT_HEAD(headp, newvariable, entries);
 | |
| 	}
 | |
| 
 | |
| 	if (chan)
 | |
| 		ast_channel_unlock(chan);
 | |
| 	else
 | |
| 		ast_rwlock_unlock(&globalslock);
 | |
| }
 | |
| 
 | |
| int pbx_builtin_setvar_helper(struct ast_channel *chan, const char *name, const char *value)
 | |
| {
 | |
| 	struct ast_var_t *newvariable;
 | |
| 	struct varshead *headp;
 | |
| 	const char *nametail = name;
 | |
| 	/*! True if the old value was not an empty string. */
 | |
| 	int old_value_existed = 0;
 | |
| 
 | |
| 	if (name[strlen(name) - 1] == ')') {
 | |
| 		char *function = ast_strdupa(name);
 | |
| 
 | |
| 		return ast_func_write(chan, function, value);
 | |
| 	}
 | |
| 
 | |
| 	if (chan) {
 | |
| 		ast_channel_lock(chan);
 | |
| 		headp = ast_channel_varshead(chan);
 | |
| 	} else {
 | |
| 		ast_rwlock_wrlock(&globalslock);
 | |
| 		headp = &globals;
 | |
| 	}
 | |
| 
 | |
| 	/* For comparison purposes, we have to strip leading underscores */
 | |
| 	if (*nametail == '_') {
 | |
| 		nametail++;
 | |
| 		if (*nametail == '_')
 | |
| 			nametail++;
 | |
| 	}
 | |
| 
 | |
| 	AST_LIST_TRAVERSE_SAFE_BEGIN(headp, newvariable, entries) {
 | |
| 		if (strcmp(ast_var_name(newvariable), nametail) == 0) {
 | |
| 			/* there is already such a variable, delete it */
 | |
| 			AST_LIST_REMOVE_CURRENT(entries);
 | |
| 			old_value_existed = !ast_strlen_zero(ast_var_value(newvariable));
 | |
| 			ast_var_delete(newvariable);
 | |
| 			break;
 | |
| 		}
 | |
| 	}
 | |
| 	AST_LIST_TRAVERSE_SAFE_END;
 | |
| 
 | |
| 	if (value && (newvariable = ast_var_assign(name, value))) {
 | |
| 		if (headp == &globals) {
 | |
| 			ast_verb(2, "Setting global variable '%s' to '%s'\n", name, value);
 | |
| 		}
 | |
| 		AST_LIST_INSERT_HEAD(headp, newvariable, entries);
 | |
| 		ast_channel_publish_varset(chan, name, value);
 | |
| 	} else if (old_value_existed) {
 | |
| 		/* We just deleted a non-empty dialplan variable. */
 | |
| 		ast_channel_publish_varset(chan, name, "");
 | |
| 	}
 | |
| 
 | |
| 	if (chan)
 | |
| 		ast_channel_unlock(chan);
 | |
| 	else
 | |
| 		ast_rwlock_unlock(&globalslock);
 | |
| 	return 0;
 | |
| }
 | |
| 
 | |
| int pbx_builtin_setvar(struct ast_channel *chan, const char *data)
 | |
| {
 | |
| 	char *name, *value, *mydata;
 | |
| 
 | |
| 	if (ast_strlen_zero(data)) {
 | |
| 		ast_log(LOG_WARNING, "Set requires one variable name/value pair.\n");
 | |
| 		return 0;
 | |
| 	}
 | |
| 
 | |
| 	mydata = ast_strdupa(data);
 | |
| 	name = strsep(&mydata, "=");
 | |
| 	value = mydata;
 | |
| 	if (!value) {
 | |
| 		ast_log(LOG_WARNING, "Set requires an '=' to be a valid assignment.\n");
 | |
| 		return 0;
 | |
| 	}
 | |
| 
 | |
| 	if (strchr(name, ' ')) {
 | |
| 		ast_log(LOG_WARNING, "Please avoid unnecessary spaces on variables as it may lead to unexpected results ('%s' set to '%s').\n", name, mydata);
 | |
| 	}
 | |
| 
 | |
| 	pbx_builtin_setvar_helper(chan, name, value);
 | |
| 
 | |
| 	return 0;
 | |
| }
 | |
| 
 | |
| int pbx_builtin_setvar_multiple(struct ast_channel *chan, const char *vdata)
 | |
| {
 | |
| 	char *data;
 | |
| 	int x;
 | |
| 	AST_DECLARE_APP_ARGS(args,
 | |
| 		AST_APP_ARG(pair)[24];
 | |
| 	);
 | |
| 	AST_DECLARE_APP_ARGS(pair,
 | |
| 		AST_APP_ARG(name);
 | |
| 		AST_APP_ARG(value);
 | |
| 	);
 | |
| 
 | |
| 	if (ast_strlen_zero(vdata)) {
 | |
| 		ast_log(LOG_WARNING, "MSet requires at least one variable name/value pair.\n");
 | |
| 		return 0;
 | |
| 	}
 | |
| 
 | |
| 	data = ast_strdupa(vdata);
 | |
| 	AST_STANDARD_APP_ARGS(args, data);
 | |
| 
 | |
| 	for (x = 0; x < args.argc; x++) {
 | |
| 		AST_NONSTANDARD_APP_ARGS(pair, args.pair[x], '=');
 | |
| 		if (pair.argc == 2) {
 | |
| 			pbx_builtin_setvar_helper(chan, pair.name, pair.value);
 | |
| 			if (strchr(pair.name, ' '))
 | |
| 				ast_log(LOG_WARNING, "Please avoid unnecessary spaces on variables as it may lead to unexpected results ('%s' set to '%s').\n", pair.name, pair.value);
 | |
| 		} else if (!chan) {
 | |
| 			ast_log(LOG_WARNING, "MSet: ignoring entry '%s' with no '='\n", pair.name);
 | |
| 		} else {
 | |
| 			ast_log(LOG_WARNING, "MSet: ignoring entry '%s' with no '=' (in %s@%s:%d\n", pair.name, ast_channel_exten(chan), ast_channel_context(chan), ast_channel_priority(chan));
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	return 0;
 | |
| }
 | |
| 
 | |
| void pbx_builtin_clear_globals(void)
 | |
| {
 | |
| 	struct ast_var_t *vardata;
 | |
| 
 | |
| 	ast_rwlock_wrlock(&globalslock);
 | |
| 	while ((vardata = AST_LIST_REMOVE_HEAD(&globals, entries)))
 | |
| 		ast_var_delete(vardata);
 | |
| 	ast_rwlock_unlock(&globalslock);
 | |
| }
 | |
| 
 | |
| static struct ast_cli_entry vars_cli[] = {
 | |
| 	AST_CLI_DEFINE(handle_show_globals, "Show global dialplan variables"),
 | |
| 	AST_CLI_DEFINE(handle_show_chanvar, "Show channel variables"),
 | |
| 	AST_CLI_DEFINE(handle_set_global, "Set global dialplan variable"),
 | |
| 	AST_CLI_DEFINE(handle_set_chanvar, "Set a channel variable"),
 | |
| };
 | |
| 
 | |
| static void unload_pbx_variables(void)
 | |
| {
 | |
| 	ast_cli_unregister_multiple(vars_cli, ARRAY_LEN(vars_cli));
 | |
| 	ast_unregister_application("Set");
 | |
| 	ast_unregister_application("MSet");
 | |
| 	pbx_builtin_clear_globals();
 | |
| }
 | |
| 
 | |
| int load_pbx_variables(void)
 | |
| {
 | |
| 	int res = 0;
 | |
| 
 | |
| 	res |= ast_cli_register_multiple(vars_cli, ARRAY_LEN(vars_cli));
 | |
| 	res |= ast_register_application2("Set", pbx_builtin_setvar, NULL, NULL, NULL);
 | |
| 	res |= ast_register_application2("MSet", pbx_builtin_setvar_multiple, NULL, NULL, NULL);
 | |
| 	ast_register_cleanup(unload_pbx_variables);
 | |
| 
 | |
| 	return res;
 | |
| }
 |