Skip to content
Snippets Groups Projects
config.c 107 KiB
Newer Older
  • Learn to ignore specific revisions
  • 	for (cat = config->root; cat; cat = cat->next) {
    
    		if (does_category_match(cat, category_name, filter, sep)) {
    
    			return cat;
    
    struct ast_category *ast_category_get(const struct ast_config *config,
    	const char *category_name, const char *filter)
    {
    
    	return category_get_sep(config, category_name, filter, ',', 1);
    
    const char *ast_category_get_name(const struct ast_category *category)
    {
    	return category->name;
    }
    
    int ast_category_is_template(const struct ast_category *category)
    
    	return category->ignored;
    }
    
    struct ast_str *ast_category_get_templates(const struct ast_category *category)
    {
    	struct ast_category_template_instance *template;
    	struct ast_str *str;
    	int first = 1;
    
    	if (AST_LIST_EMPTY(&category->template_instances)) {
    		return NULL;
    	}
    
    	str = ast_str_create(128);
    	if (!str) {
    		return NULL;
    	}
    
    	AST_LIST_TRAVERSE(&category->template_instances, template, next) {
    		ast_str_append(&str, 0, "%s%s", first ? "" : ",", template->name);
    		first = 0;
    	}
    
    	return str;
    
    int ast_category_exist(const struct ast_config *config, const char *category_name,
    	const char *filter)
    
    	return !!ast_category_get(config, category_name, filter);
    
    void ast_category_append(struct ast_config *config, struct ast_category *category)
    {
    
    		config->last->next = category;
    
    		config->root = category;
    
    		category->prev = NULL;
    	}
    	category->next = NULL;
    
    	category->include_level = config->include_level;
    
    	config->last = category;
    	config->current = category;
    }
    
    int ast_category_insert(struct ast_config *config, struct ast_category *cat, const char *match)
    
    {
    	struct ast_category *cur_category;
    
    
    	if (!config || !config->root || !cat || !match) {
    
    	if (!strcasecmp(config->root->name, match)) {
    		cat->next = config->root;
    
    		cat->prev = NULL;
    		config->root->prev = cat;
    
    		config->root = cat;
    
    
    	for (cur_category = config->root->next; cur_category; cur_category = cur_category->next) {
    		if (!strcasecmp(cur_category->name, match)) {
    			cat->prev = cur_category->prev;
    			cat->prev->next = cat;
    
    			cat->next = cur_category;
    			cur_category->prev = cat;
    
    
    static void ast_destroy_template_list(struct ast_category *cat)
    {
    	struct ast_category_template_instance *x;
    
    
    	while ((x = AST_LIST_REMOVE_HEAD(&cat->template_instances, next)))
    
    void ast_category_destroy(struct ast_category *cat)
    
    	ast_variables_destroy(cat->root);
    
    	cat->root = NULL;
    	cat->last = NULL;
    
    	ast_comment_destroy(&cat->precomments);
    	ast_comment_destroy(&cat->sameline);
    	ast_comment_destroy(&cat->trailing);
    
    	ast_destroy_template_list(cat);
    
    	ast_free(cat->file);
    
    static void ast_includes_destroy(struct ast_config_include *incls)
    {
    	struct ast_config_include *incl,*inclnext;
    
    	for (incl=incls; incl; incl = inclnext) {
    		inclnext = incl->next;
    
    		ast_free(incl->include_location_file);
    		ast_free(incl->exec_file);
    		ast_free(incl->included_file);
    		ast_free(incl);
    
    static struct ast_category *next_available_category(struct ast_category *cat,
    	const char *name, const char *filter)
    
    	for (; cat && !does_category_match(cat, name, filter, ','); cat = cat->next);
    
    /*! return the first var of a category */
    struct ast_variable *ast_category_first(struct ast_category *cat)
    {
    	return (cat) ? cat->root : NULL;
    }
    
    
    struct ast_variable *ast_category_root(struct ast_config *config, char *cat)
    {
    
    	struct ast_category *category = ast_category_get(config, cat, NULL);
    
    Luigi Rizzo's avatar
    Luigi Rizzo committed
    
    
    	if (category)
    		return category->root;
    	return NULL;
    }
    
    
    void ast_config_sort_categories(struct ast_config *config, int descending,
    								int (*comparator)(struct ast_category *p, struct ast_category *q))
    {
    	/*
    	 * The contents of this function are adapted from
    	 * an example of linked list merge sorting
    	 * copyright 2001 Simon Tatham.
    	 *
    	 * Permission is hereby granted, free of charge, to any person
    	 * obtaining a copy of this software and associated documentation
    	 * files (the "Software"), to deal in the Software without
    	 * restriction, including without limitation the rights to use,
    	 * copy, modify, merge, publish, distribute, sublicense, and/or
    	 * sell copies of the Software, and to permit persons to whom the
    	 * Software is furnished to do so, subject to the following
    	 * conditions:
    	 *
    	 * The above copyright notice and this permission notice shall be
    	 * included in all copies or substantial portions of the Software.
    	 *
    	 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
    	 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
    	 * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
    	 * NONINFRINGEMENT.  IN NO EVENT SHALL SIMON TATHAM BE LIABLE FOR
    	 * ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
    	 * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
    	 * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
    	 * SOFTWARE.
    	 */
    
    	int insize = 1;
    	struct ast_category *p, *q, *e, *tail;
    	int nmerges, psize, qsize, i;
    
    	/* If the descending flag was sent, we'll apply inversion to the comparison function's return. */
    	if (descending) {
    		descending = -1;
    	} else {
    		descending = 1;
    	}
    
    	if (!config->root) {
    		return;
    	}
    
    	while (1) {
    		p = config->root;
    		config->root = NULL;
    		tail = NULL;
    
    		nmerges = 0; /* count number of merges we do in this pass */
    
    		while (p) {
    			nmerges++; /* there exists a merge to be done */
    
    			/* step `insize' places along from p */
    			q = p;
    			psize = 0;
    			for (i = 0; i < insize; i++) {
    				psize++;
    				q = q->next;
    				if (!q) {
    					break;
    				}
    			}
    
    			/* if q hasn't fallen off end, we have two lists to merge */
    			qsize = insize;
    
    			/* now we have two lists; merge them */
    			while (psize > 0 || (qsize > 0 && q)) {
    				/* decide whether next element of merge comes from p or q */
    				if (psize == 0) {
    					/* p is empty; e must come from q. */
    					e = q;
    					q = q->next;
    					qsize--;
    				} else if (qsize == 0 || !q) {
    					/* q is empty; e must come from p. */
    					e = p; p = p->next; psize--;
    				} else if ((comparator(p,q) * descending) <= 0) {
    					/* First element of p is lower (or same) e must come from p. */
    					e = p;
    					p = p->next;
    					psize--;
    				} else {
    					/* First element of q is lower; e must come from q. */
    					e = q;
    					q = q->next;
    					qsize--;
    				}
    
    				/* add the next element to the merged list */
    				if (tail) {
    					tail->next = e;
    				} else {
    					config->root = e;
    				}
    				tail = e;
    			}
    
    			/* now p has stepped `insize' places along, and q has too */
    			p = q;
    		}
    
    		tail->next = NULL;
    
    		/* If we have done only one merge, we're finished. */
    		if (nmerges <= 1) { /* allow for nmerges==0, the empty list case */
    			return;
    		}
    
    		/* Otherwise repeat, merging lists twice the size */
    		insize *= 2;
    	}
    
    }
    
    
    char *ast_category_browse(struct ast_config *config, const char *prev)
    
    	struct ast_category *cat;
    
    	if (!prev) {
    		/* First time browse. */
    
    	} else if (config->last_browse && (config->last_browse->name == prev)) {
    		/* Simple last browse found. */
    		cat = config->last_browse->next;
    	} else {
    		/*
    		 * Config changed since last browse.
    		 *
    		 * First try cheap last browse search. (Rebrowsing a different
    		 * previous category?)
    		 */
    
    		for (cat = config->root; cat; cat = cat->next) {
    			if (cat->name == prev) {
    
    				cat = cat->next;
    				break;
    			}
    
    		if (!cat) {
    
    			/*
    			 * Have to do it the hard way. (Last category was deleted and
    			 * re-added?)
    			 */
    
    			for (cat = config->root; cat; cat = cat->next) {
    				if (!strcasecmp(cat->name, prev)) {
    
    					cat = cat->next;
    					break;
    				}
    			}
    
    		cat = next_available_category(cat, NULL, NULL);
    
    
    	config->last_browse = cat;
    
    	return (cat) ? cat->name : NULL;
    
    struct ast_category *ast_category_browse_filtered(struct ast_config *config,
    	const char *category_name, struct ast_category *prev, const char *filter)
    {
    	struct ast_category *cat;
    
    	if (!prev) {
    		prev = config->root;
    	} else {
    		prev = prev->next;
    	}
    
    	cat = next_available_category(prev, category_name, filter);
    
    	return cat;
    }
    
    
    struct ast_variable *ast_category_detach_variables(struct ast_category *cat)
    
    	struct ast_variable *v;
    
    	v = cat->root;
    	cat->root = NULL;
    
    
    	return v;
    }
    
    void ast_category_rename(struct ast_category *cat, const char *name)
    {
    
    	ast_copy_string(cat->name, name, sizeof(cat->name));
    
    int ast_category_inherit(struct ast_category *new, const struct ast_category *base)
    
    {
    	struct ast_variable *var;
    
    	struct ast_category_template_instance *x;
    
    Luigi Rizzo's avatar
    Luigi Rizzo committed
    
    
    	x = ast_calloc(1, sizeof(*x));
    	if (!x) {
    
    	strcpy(x->name, base->name);
    	x->inst = base;
    	AST_LIST_INSERT_TAIL(&new->template_instances, x, next);
    
    	for (var = base->root; var; var = var->next) {
    		struct ast_variable *cloned = variable_clone(var);
    
    		if (!cloned) {
    			return -1;
    		}
    
    		cloned->inherited = 1;
    		ast_variable_append(new, cloned);
    	}
    
    struct ast_config *ast_config_new(void)
    
    {
    	struct ast_config *config;
    
    
    	if ((config = ast_calloc(1, sizeof(*config))))
    
    		config->max_include_level = MAX_INCLUDE_LEVEL;
    	return config;
    
    int ast_variable_delete(struct ast_category *category, const char *variable, const char *match, const char *line)
    
    	struct ast_variable *cur, *prev=NULL, *curn;
    	int res = -1;
    
    	int num_item = 0;
    	int req_item;
    
    	req_item = -1;
    	if (!ast_strlen_zero(line)) {
    		/* Requesting to delete by item number. */
    		if (sscanf(line, "%30d", &req_item) != 1
    			|| req_item < 0) {
    			/* Invalid item number to delete. */
    			return -1;
    
    	prev = NULL;
    
    	cur = category->root;
    	while (cur) {
    
    		/* Delete by item number or by variable name with optional value. */
    		if ((0 <= req_item && num_item == req_item)
    			|| (req_item < 0 && !strcasecmp(cur->name, variable)
    				&& (ast_strlen_zero(match) || !strcasecmp(cur->value, match)))) {
    
    			if (prev) {
    				prev->next = cur->next;
    				if (cur == category->last)
    					category->last = prev;
    			} else {
    				category->root = cur->next;
    				if (cur == category->last)
    					category->last = NULL;
    			}
    
    			ast_variable_destroy(cur);
    
    int ast_variable_update(struct ast_category *category, const char *variable,
    
    						const char *value, const char *match, unsigned int object)
    
    	struct ast_variable *cur, *prev=NULL, *newer=NULL;
    
    
    	for (cur = category->root; cur; prev = cur, cur = cur->next) {
    		if (strcasecmp(cur->name, variable) ||
    			(!ast_strlen_zero(match) && strcasecmp(cur->value, match)))
    			continue;
    
    
    		if (!(newer = ast_variable_new(variable, value, cur->file)))
    			return -1;
    
    		ast_variable_move(newer, cur);
    		newer->object = newer->object || object;
    
    		/* Replace the old node in the list with the new node. */
    		newer->next = cur->next;
    
    		if (prev)
    			prev->next = newer;
    		else
    			category->root = newer;
    		if (category->last == cur)
    			category->last = newer;
    
    
    		ast_variable_destroy(cur);
    
    	/* Could not find variable to update */
    	return -1;
    
    struct ast_category *ast_category_delete(struct ast_config *config,
    	struct ast_category *category)
    
    Luigi Rizzo's avatar
    Luigi Rizzo committed
    
    
    	if (!config || !category) {
    		return NULL;
    
    	if (category->prev) {
    		category->prev->next = category->next;
    	} else {
    		config->root = category->next;
    
    
    	if (category->next) {
    		category->next->prev = category->prev;
    	} else {
    		config->last = category->prev;
    	}
    
    	prev = category->prev;
    
    
    	if (config->last_browse == category) {
    		config->last_browse = prev;
    	}
    
    
    int ast_category_empty(struct ast_category *category)
    
    	ast_variables_destroy(category->root);
    	category->root = NULL;
    	category->last = NULL;
    
    	return 0;
    
    void ast_config_destroy(struct ast_config *cfg)
    
    Mark Spencer's avatar
    Mark Spencer committed
    {
    
    	struct ast_category *cat, *catn;
    
    	if (!cfg)
    		return;
    
    
    	cat = cfg->root;
    
    		catn = cat;
    		cat = cat->next;
    
    		ast_category_destroy(catn);
    
    struct ast_category *ast_config_get_current_category(const struct ast_config *cfg)
    
    	return cfg->current;
    
    void ast_config_set_current_category(struct ast_config *cfg, const struct ast_category *cat)
    
    	/* cast below is just to silence compiler warning about dropping "const" */
    	cfg->current = (struct ast_category *) cat;
    
    /*!
     * \internal
     * \brief Create a new cfmtime list node.
     *
     * \param filename Config filename caching.
     * \param who_asked Who wanted to know.
     *
     * \retval cfmtime New node on success.
     * \retval NULL on error.
     */
    static struct cache_file_mtime *cfmtime_new(const char *filename, const char *who_asked)
    {
    	struct cache_file_mtime *cfmtime;
    	char *dst;
    
    	cfmtime = ast_calloc(1,
    		sizeof(*cfmtime) + strlen(filename) + 1 + strlen(who_asked) + 1);
    	if (!cfmtime) {
    		return NULL;
    	}
    	dst = cfmtime->filename;	/* writable space starts here */
    
    	dst += strlen(dst) + 1;
    
    	cfmtime->who_asked = strcpy(dst, who_asked); /* Safe */
    
    enum config_cache_attribute_enum {
    	ATTRIBUTE_INCLUDE = 0,
    	ATTRIBUTE_EXEC = 1,
    };
    
    
    /*!
     * \internal
     * \brief Save the stat() data to the cached file modtime struct.
     *
     * \param cfmtime Cached file modtime.
     * \param statbuf Buffer filled in by stat().
     *
     * \return Nothing
     */
    static void cfmstat_save(struct cache_file_mtime *cfmtime, struct stat *statbuf)
    {
    	cfmtime->stat_size = statbuf->st_size;
    
    #if defined(HAVE_STRUCT_STAT_ST_MTIM)
    
    	cfmtime->stat_mtime_nsec = statbuf->st_mtim.tv_nsec;
    
    #elif defined(HAVE_STRUCT_STAT_ST_MTIMENSEC)
    
    	cfmtime->stat_mtime_nsec = statbuf->st_mtimensec;
    
    #elif defined(HAVE_STRUCT_STAT_ST_MTIMESPEC)
    	cfmtime->stat_mtime_nsec = statbuf->st_mtimespec.tv_nsec;
    #else
    	cfmtime->stat_mtime_nsec = 0;
    
    #endif
    	cfmtime->stat_mtime = statbuf->st_mtime;
    }
    
    /*!
     * \internal
     * \brief Compare the stat() data with the cached file modtime struct.
     *
     * \param cfmtime Cached file modtime.
     * \param statbuf Buffer filled in by stat().
     *
     * \retval non-zero if different.
     */
    static int cfmstat_cmp(struct cache_file_mtime *cfmtime, struct stat *statbuf)
    {
    	struct cache_file_mtime cfm_buf;
    
    	cfmstat_save(&cfm_buf, statbuf);
    
    	return cfmtime->stat_size != cfm_buf.stat_size
    		|| cfmtime->stat_mtime != cfm_buf.stat_mtime
    		|| cfmtime->stat_mtime_nsec != cfm_buf.stat_mtime_nsec;
    }
    
    
    /*!
     * \internal
     * \brief Clear the cached file modtime include list.
     *
     * \param cfmtime Cached file modtime.
     *
     * \note cfmtime_head is assumed already locked.
     *
     * \return Nothing
     */
    static void config_cache_flush_includes(struct cache_file_mtime *cfmtime)
    {
    	struct cache_file_include *cfinclude;
    
    	while ((cfinclude = AST_LIST_REMOVE_HEAD(&cfmtime->includes, list))) {
    		ast_free(cfinclude);
    	}
    }
    
    /*!
     * \internal
     * \brief Destroy the given cached file modtime entry.
     *
     * \param cfmtime Cached file modtime.
     *
     * \note cfmtime_head is assumed already locked.
     *
     * \return Nothing
     */
    static void config_cache_destroy_entry(struct cache_file_mtime *cfmtime)
    {
    	config_cache_flush_includes(cfmtime);
    	ast_free(cfmtime);
    }
    
    /*!
     * \internal
     * \brief Remove and destroy the config cache entry for the filename and who_asked.
     *
     * \param filename Config filename.
     * \param who_asked Which module asked.
     *
     * \return Nothing
     */
    static void config_cache_remove(const char *filename, const char *who_asked)
    {
    	struct cache_file_mtime *cfmtime;
    
    	AST_LIST_LOCK(&cfmtime_head);
    	AST_LIST_TRAVERSE_SAFE_BEGIN(&cfmtime_head, cfmtime, list) {
    		if (!strcmp(cfmtime->filename, filename)
    			&& !strcmp(cfmtime->who_asked, who_asked)) {
    			AST_LIST_REMOVE_CURRENT(list);
    			config_cache_destroy_entry(cfmtime);
    			break;
    		}
    	}
    	AST_LIST_TRAVERSE_SAFE_END;
    	AST_LIST_UNLOCK(&cfmtime_head);
    }
    
    
    static void config_cache_attribute(const char *configfile, enum config_cache_attribute_enum attrtype, const char *filename, const char *who_asked)
    
    {
    	struct cache_file_mtime *cfmtime;
    	struct cache_file_include *cfinclude;
    
    	/* Find our cached entry for this configuration file */
    	AST_LIST_LOCK(&cfmtime_head);
    	AST_LIST_TRAVERSE(&cfmtime_head, cfmtime, list) {
    
    		if (!strcmp(cfmtime->filename, configfile) && !strcmp(cfmtime->who_asked, who_asked))
    
    		cfmtime = cfmtime_new(configfile, who_asked);
    
    		if (!cfmtime) {
    			AST_LIST_UNLOCK(&cfmtime_head);
    			return;
    		}
    		/* Note that the file mtime is initialized to 0, i.e. 1970 */
    
    		AST_LIST_INSERT_SORTALPHA(&cfmtime_head, cfmtime, list, filename);
    
    	}
    
    	switch (attrtype) {
    	case ATTRIBUTE_INCLUDE:
    
    		AST_LIST_TRAVERSE(&cfmtime->includes, cfinclude, list) {
    			if (!strcmp(cfinclude->include, filename)) {
    				AST_LIST_UNLOCK(&cfmtime_head);
    				return;
    			}
    		}
    
    		cfinclude = ast_calloc(1, sizeof(*cfinclude) + strlen(filename) + 1);
    		if (!cfinclude) {
    			AST_LIST_UNLOCK(&cfmtime_head);
    			return;
    		}
    
    		strcpy(cfinclude->include, filename); /* Safe */
    
    		AST_LIST_INSERT_TAIL(&cfmtime->includes, cfinclude, list);
    		break;
    	case ATTRIBUTE_EXEC:
    		cfmtime->has_exec = 1;
    		break;
    	}
    	AST_LIST_UNLOCK(&cfmtime_head);
    }
    
    
    /*! \brief parse one line in the configuration.
    
     * \verbatim
    
     * We can have a category header	[foo](...)
     * a directive				#include / #exec
     * or a regular line			name = value
    
     * \endverbatim
    
     */
    static int process_text_line(struct ast_config *cfg, struct ast_category **cat,
    	char *buf, int lineno, const char *configfile, struct ast_flags flags,
    
    	struct ast_str *comment_buffer,
    	struct ast_str *lline_buffer,
    
    	const char *suggested_include_file,
    
    	struct ast_category **last_cat, struct ast_variable **last_var, const char *who_asked)
    
    Mark Spencer's avatar
    Mark Spencer committed
    {
    	char *c;
    
    	char *cur = buf;
    
    Mark Spencer's avatar
    Mark Spencer committed
    	struct ast_variable *v;
    
    	char cmd[512], exec_file[512];
    
    
    	/* Actually parse the entry */
    
    	if (cur[0] == '[') { /* A category header */
    		/* format is one of the following:
    		 * [foo]	define a new category named 'foo'
    		 * [foo](!)	define a new template category named 'foo'
    		 * [foo](+)	append to category 'foo', error if foo does not exist.
    
    		 * [foo](a)	define a new category and inherit from category or template a.
    		 *		You can put a comma-separated list of categories and templates
    		 *		and '!' and '+' between parentheses, with obvious meaning.
    
    		c = strchr(cur, ']');
    		if (!c) {
    			ast_log(LOG_WARNING, "parse error: no closing ']', line %d of %s\n", lineno, configfile);
    			return -1;
    		}
    
    		*c++ = '\0';
    
    		if (*c++ != '(')
    			c = NULL;
    
    		catname = cur;
    
    		*cat = newcat = ast_category_new(catname,
    			S_OR(suggested_include_file, cfg->include_level == 1 ? "" : configfile),
    			lineno);
    		if (!newcat) {
    
    		/* add comments */
    
    		if (ast_test_flag(&flags, CONFIG_FLAG_WITHCOMMENTS))
    			newcat->precomments = ALLOC_COMMENT(comment_buffer);
    		if (ast_test_flag(&flags, CONFIG_FLAG_WITHCOMMENTS))
    			newcat->sameline = ALLOC_COMMENT(lline_buffer);
    
    		if (ast_test_flag(&flags, CONFIG_FLAG_WITHCOMMENTS))
    
    			CB_RESET(comment_buffer, lline_buffer);
    
    
    		/* If there are options or categories to inherit from, process them now */
    		if (c) {
    			if (!(cur = strchr(c, ')'))) {
    
    				ast_category_destroy(newcat);
    
    				ast_log(LOG_WARNING, "parse error: no closing ')', line %d of %s\n", lineno, configfile);
    				return -1;
    			}
    			*cur = '\0';
    			while ((cur = strsep(&c, ","))) {
    
    				if (!strcasecmp(cur, "!")) {
    					(*cat)->ignored = 1;
    
    				} else if (cur[0] == '+') {
    					char *filter = NULL;
    
    					if (cur[1] != ',') {
    						filter = &cur[1];
    					}
    
    					*cat = category_get_sep(cfg, catname, filter, '&', 0);
    
    							ast_category_destroy(newcat);
    
    						ast_log(LOG_WARNING, "Category addition requested, but category '%s' does not exist, line %d of %s\n", catname, lineno, configfile);
    						return -1;
    					}
    					if (newcat) {
    
    						ast_config_set_current_category(cfg, *cat);
    						(*cat)->ignored |= newcat->ignored;
    
    						move_variables(newcat, *cat);
    						ast_category_destroy(newcat);
    						newcat = NULL;
    					}
    				} else {
    					struct ast_category *base;
    
    					base = category_get_sep(cfg, cur, "TEMPLATES=include", ',', 0);
    
    					if (!base) {
    
    						if (newcat) {
    							ast_category_destroy(newcat);
    						}
    
    						ast_log(LOG_WARNING, "Inheritance requested, but category '%s' does not exist, line %d of %s\n", cur, lineno, configfile);
    						return -1;
    					}
    
    					if (ast_category_inherit(*cat, base)) {
    
    						if (newcat) {
    							ast_category_destroy(newcat);
    						}
    
    						ast_log(LOG_ERROR, "Inheritence requested, but allocation failed\n");
    						return -1;
    					}
    
    
    		/*
    		 * We need to set *last_cat to newcat here regardless.  If the
    		 * category is being appended to we have no place for trailing
    		 * comments on the appended category.  The appended category
    		 * may be in another file or it already has trailing comments
    		 * that we would then leak.
    		 */
    		*last_var = NULL;
    		*last_cat = newcat;
    		if (newcat) {
    			ast_category_append(cfg, newcat);
    		}
    
    	} else if (cur[0] == '#') { /* A directive - #include or #exec */
    
    		char *cur2;
    		char real_inclusion_name[256];
    		int do_include = 0;	/* otherwise, it is exec */
    
    Paul Belanger's avatar
    Paul Belanger committed
    		int try_include = 0;
    
    		if (*c) {
    			*c = '\0';
    			/* Find real argument */
    
    		if (!strcasecmp(cur, "include")) {
    			do_include = 1;
    
    Paul Belanger's avatar
    Paul Belanger committed
    		} else if (!strcasecmp(cur, "tryinclude")) {
    			do_include = 1;
    			try_include = 1;
    
    		} else if (!strcasecmp(cur, "exec")) {
    			if (!ast_opt_exec_includes) {
    				ast_log(LOG_WARNING, "Cannot perform #exec unless execincludes option is enabled in asterisk.conf (options section)!\n");
    				return 0;	/* XXX is this correct ? or we should return -1 ? */
    			}
    		} else {
    
    			ast_log(LOG_WARNING, "Unknown directive '#%s' at line %d of %s\n", cur, lineno, configfile);
    
    			return 0;	/* XXX is this correct ? or we should return -1 ? */
    
    Paul Belanger's avatar
    Paul Belanger committed
    			ast_log(LOG_WARNING, "Directive '#%s' needs an argument (%s) at line %d of %s\n",
    					do_include ? "include / tryinclude" : "exec",
    
    					do_include ? "filename" : "/path/to/executable",
    					lineno,
    					configfile);
    			return 0;	/* XXX is this correct ? or we should return -1 ? */
    		}
    
    
    		/* Strip off leading and trailing "'s and <>'s */
    
    		/* Dequote */
    		if ((*c == '"') || (*c == '<')) {
    			char quote_char = *c;
    			if (quote_char == '<') {
    				quote_char = '>';
    
    
    			if (*(c + strlen(c) - 1) == quote_char) {
    
    				cur++;
    				*(c + strlen(c) - 1) = '\0';
    			}
    
    		/* #exec </path/to/executable>
    		   We create a tmp file, then we #include it, then we delete it. */
    		if (!do_include) {
    
    			if (!ast_test_flag(&flags, CONFIG_FLAG_NOCACHE))
    				config_cache_attribute(configfile, ATTRIBUTE_EXEC, NULL, who_asked);
    
    			snprintf(exec_file, sizeof(exec_file), "/var/tmp/exec.%d%d.%ld", (int)now.tv_sec, (int)now.tv_usec, (long)pthread_self());
    
    			snprintf(cmd, sizeof(cmd), "%s > %s 2>&1", cur, exec_file);
    			ast_safe_system(cmd);
    			cur = exec_file;
    		} else {
    			if (!ast_test_flag(&flags, CONFIG_FLAG_NOCACHE))
    				config_cache_attribute(configfile, ATTRIBUTE_INCLUDE, cur, who_asked);
    			exec_file[0] = '\0';
    		}
    		/* A #include */
    		/* record this inclusion */
    
    		ast_include_new(cfg, cfg->include_level == 1 ? "" : configfile, cur, !do_include, cur2, lineno, real_inclusion_name, sizeof(real_inclusion_name));
    
    
    		do_include = ast_config_internal_load(cur, cfg, flags, real_inclusion_name, who_asked) ? 1 : 0;
    		if (!ast_strlen_zero(exec_file))
    			unlink(exec_file);
    
    Paul Belanger's avatar
    Paul Belanger committed
    		if (!do_include && !try_include) {
    
    			ast_log(LOG_ERROR, "The file '%s' was listed as a #include but it does not exist.\n", cur);
    			return -1;
    		}
    		/* XXX otherwise what ? the default return is 0 anyways */
    
    	} else {
    		/* Just a line (variable = value) */
    
    			ast_log(LOG_WARNING,
    				"parse error: No category context for line %d of %s\n", lineno, configfile);
    			return -1;
    		}
    
    		is_escaped = cur[0] == '\\';
    		if (is_escaped) {
    			/* First character is escaped. */
    			++cur;
    			if (cur[0] < 33) {
    				ast_log(LOG_ERROR, "Invalid escape in line %d of %s\n", lineno, configfile);
    				return -1;
    			}
    		}
    		c = strchr(cur + is_escaped, '=');
    
    		if (c && c > cur + is_escaped && (*(c - 1) == '+')) {
    
    			struct ast_variable *var, *replace = NULL;
    			struct ast_str **str = ast_threadstorage_get(&appendbuf, sizeof(*str));
    
    			if (!str || !*str) {
    				return -1;
    			}
    
    			*(c - 1) = '\0';
    			c++;
    			cur = ast_strip(cur);
    
    			/* Must iterate through category until we find last variable of same name (since there could be multiple) */
    			for (var = ast_category_first(*cat); var; var = var->next) {
    				if (!strcmp(var->name, cur)) {
    					replace = var;
    				}
    			}
    
    			if (!replace) {
    				/* Nothing to replace; just set a variable normally. */
    				goto set_new_variable;
    			}
    
    			ast_str_set(str, 0, "%s", replace->value);
    			ast_str_append(str, 0, "%s", c);
    
    			ast_str_trim_blanks(*str);
    			ast_variable_update(*cat, replace->name, ast_skip_blanks(ast_str_buffer(*str)), replace->value, object);
    
    			*c = 0;
    			c++;
    			/* Ignore > in => */
    			if (*c== '>') {
    				object = 1;
    
    			if (ast_strlen_zero(cur)) {
    				ast_log(LOG_WARNING, "No variable name in line %d of %s\n", lineno, configfile);
    			} else if ((v = ast_variable_new(cur, ast_strip(c), S_OR(suggested_include_file, cfg->include_level == 1 ? "" : configfile)))) {