Skip to content
Snippets Groups Projects
config.c 106 KiB
Newer Older
  • Learn to ignore specific revisions
  • Mark Spencer's avatar
    Mark Spencer committed
    /*
    
     * Asterisk -- An open source telephony toolkit.
    
    Mark Spencer's avatar
    Mark Spencer committed
     *
    
     * Copyright (C) 1999 - 2010, Digium, Inc.
    
    Mark Spencer's avatar
    Mark Spencer committed
     *
    
    Mark Spencer's avatar
    Mark Spencer committed
     *
    
     * 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.
     *
    
    Mark Spencer's avatar
    Mark Spencer committed
     * 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 Configuration File Parser
    
     * \author Mark Spencer <markster@digium.com>
     *
    
     * Includes the Asterisk Realtime API - ARA
    
     * See http://wiki.asterisk.org
    
    /*** MODULEINFO
    	<support_level>core</support_level>
     ***/
    
    
    #include "asterisk/paths.h"	/* use ast_config_AST_CONFIG_DIR */
    
    Luigi Rizzo's avatar
    Luigi Rizzo committed
    #include "asterisk/network.h"	/* we do some sockaddr manipulation here */
    
    #include <time.h>
    
    Luigi Rizzo's avatar
    Luigi Rizzo committed
    
    
    #include <math.h>	/* HUGE_VAL */
    
    #define AST_INCLUDE_GLOB 1
    
    Luigi Rizzo's avatar
    Luigi Rizzo committed
    
    
    #include "asterisk/config.h"
    #include "asterisk/cli.h"
    #include "asterisk/lock.h"
    #include "asterisk/utils.h"
    #include "asterisk/channel.h"
    #include "asterisk/app.h"
    
    #include "asterisk/strings.h"	/* for the ast_str_*() API */
    
    Mark Michelson's avatar
    Mark Michelson committed
    #include "asterisk/netsock2.h"
    
    #define MAX_NESTED_COMMENTS 128
    #define COMMENT_START ";--"
    #define COMMENT_END "--;"
    #define COMMENT_META ';'
    #define COMMENT_TAG '-'
    
    /*!
     * Define the minimum filename space to reserve for each
     * ast_variable in case the filename is renamed later by
     * ast_include_rename().
     */
    #define MIN_VARIABLE_FNAME_SPACE	40
    
    
    static char *extconfig_conf = "extconfig.conf";
    
    static struct ao2_container *cfg_hooks;
    
    static void config_hook_exec(const char *filename, const char *module, const struct ast_config *cfg);
    
    static inline struct ast_variable *variable_list_switch(struct ast_variable *l1, struct ast_variable *l2);
    
    static int does_category_match(struct ast_category *cat, const char *category_name,
    	const char *match, char sep);
    
    /*! \brief Structure to keep comments for rewriting configuration files */
    
    struct ast_comment {
    	struct ast_comment *next;
    
    	/*! Comment body allocated after struct. */
    
    /*! \brief Hold the mtime for config files, so if we don't need to reread our config, don't. */
    struct cache_file_include {
    	AST_LIST_ENTRY(cache_file_include) list;
    
    	/*! Filename or wildcard pattern as specified by the including file. */
    
    	char include[0];
    };
    
    struct cache_file_mtime {
    	AST_LIST_ENTRY(cache_file_mtime) list;
    
    	AST_LIST_HEAD_NOLOCK(includes, cache_file_include) includes;
    
    	/*! stat() file size */
    	unsigned long stat_size;
    	/*! stat() file modtime nanoseconds */
    	unsigned long stat_mtime_nsec;
    	/*! stat() file modtime seconds since epoc */
    	time_t stat_mtime;
    
    
    	/*! String stuffed in filename[] after the filename string. */
    	const char *who_asked;
    	/*! Filename and who_asked stuffed after it. */
    
    /*! Cached file mtime list. */
    
    static AST_LIST_HEAD_STATIC(cfmtime_head, cache_file_mtime);
    
    
    static int init_appendbuf(void *data)
    {
    	struct ast_str **str = data;
    	*str = ast_str_create(16);
    	return *str ? 0 : -1;
    }
    
    AST_THREADSTORAGE_CUSTOM(appendbuf, init_appendbuf, ast_free_ptr);
    
    /* comment buffers are better implemented using the ast_str_*() API */
    #define CB_SIZE 250	/* initial size of comment buffers */
    
    static void  CB_ADD(struct ast_str **cb, const char *str)
    
    static void  CB_ADD_LEN(struct ast_str **cb, const char *str, int len)
    
    	char *s = ast_alloca(len + 1);
    
    
    	memcpy(s, str, len);
    	s[len] = '\0';
    	ast_str_append(cb, 0, "%s", s);
    
    static void CB_RESET(struct ast_str *cb, struct ast_str *llb)
    {
    
    	if (cb) {
    		ast_str_reset(cb);
    	}
    	if (llb) {
    		ast_str_reset(llb);
    	}
    
    static struct ast_comment *ALLOC_COMMENT(struct ast_str *buffer)
    
    	if (!buffer || !ast_str_strlen(buffer)) {
    		return NULL;
    	}
    	if ((x = ast_calloc(1, sizeof(*x) + ast_str_strlen(buffer) + 1))) {
    		strcpy(x->cmt, ast_str_buffer(buffer)); /* SAFE */
    	}
    
    /* I need to keep track of each config file, and all its inclusions,
       so that we can track blank lines in each */
    
    
    Luigi Rizzo's avatar
    Luigi Rizzo committed
    struct inclfile {
    
    	char *fname;
    	int lineno;
    };
    
    static int hash_string(const void *obj, const int flags)
    {
    
    	char *str = ((struct inclfile *) obj)->fname;
    
    		unsigned int tmp = total;
    		total <<= 1; /* multiply by 2 */
    		total += tmp; /* multiply by 3 */
    		total <<= 2; /* multiply by 12 */
    		total += tmp; /* multiply by 13 */
    
    		total += ((unsigned int) (*str));
    
    static int hashtab_compare_strings(void *a, void *b, int flags)
    
    	return !strcmp(ae->fname, be->fname) ? CMP_MATCH | CMP_STOP : 0;
    
    static struct ast_config_map {
    	struct ast_config_map *next;
    
    	int priority;
    
    	/*! Stored in stuff[] at struct end. */
    	const char *name;
    	/*! Stored in stuff[] at struct end. */
    	const char *driver;
    	/*! Stored in stuff[] at struct end. */
    	const char *database;
    	/*! Stored in stuff[] at struct end. */
    	const char *table;
    	/*! Contents of name, driver, database, and table in that order stuffed here. */
    
    } *config_maps = NULL;
    
    AST_MUTEX_DEFINE_STATIC(config_lock);
    static struct ast_config_engine *config_engine_list;
    
    #define MAX_INCLUDE_LEVEL 10
    
    
    struct ast_category_template_instance {
    	char name[80]; /* redundant? */
    	const struct ast_category *inst;
    	AST_LIST_ENTRY(ast_category_template_instance) next;
    };
    
    
    struct ast_category {
    
    	int ignored;			/*!< do not let user of the config see this category -- set by (!) after the category decl; a template */
    
    	/*!
    	 * \brief The file name from whence this declaration was read
    	 * \note Will never be NULL
    	 */
    	char *file;
    
    	AST_LIST_HEAD_NOLOCK(template_instance_list, ast_category_template_instance) template_instances;
    
    	struct ast_comment *precomments;
    	struct ast_comment *sameline;
    
    	struct ast_comment *trailing; /*!< the last object in the list will get assigned any trailing comments when EOF is hit */
    
    	/*! First category variable in the list. */
    
    	struct ast_variable *root;
    
    	/*! Last category variable in the list. */
    
    	struct ast_variable *last;
    
    	/*! Previous node in the list. */
    	struct ast_category *prev;
    
    	/*! Next node in the list. */
    
    	struct ast_category *next;
    };
    
    struct ast_config {
    
    	/*! First config category in the list. */
    
    	struct ast_category *root;
    
    	/*! Last config category in the list. */
    
    	struct ast_category *last;
    	struct ast_category *current;
    
    	struct ast_category *last_browse;     /*!< used to cache the last category supplied via category_browse */
    
    	int include_level;
    	int max_include_level;
    
    	struct ast_config_include *includes;  /*!< a list of inclusions, which should describe the entire tree */
    };
    
    struct ast_config_include {
    
    	/*!
    	 * \brief file name in which the include occurs
    	 * \note Will never be NULL
    	 */
    	char *include_location_file;
    
    	int  include_location_lineno;    /*!< lineno where include occurred */
    
    	int  exec;                       /*!< set to non-zero if its a #exec statement */
    	/*!
    	 * \brief if it's an exec, you'll have both the /var/tmp to read, and the original script
    	 * \note Will never be NULL if exec is non-zero
    	 */
    	char *exec_file;
    	/*!
    	 * \brief file name included
    	 * \note Will never be NULL
    	 */
    	char *included_file;
    
    	int inclusion_count;             /*!< if the file is included more than once, a running count thereof -- but, worry not,
    	                                      we explode the instances and will include those-- so all entries will be unique */
    	int output;                      /*!< a flag to indicate if the inclusion has been output */
    	struct ast_config_include *next; /*!< ptr to next inclusion in the list */
    
    static void ast_variable_destroy(struct ast_variable *doomed);
    static void ast_includes_destroy(struct ast_config_include *incls);
    
    
    #ifdef __AST_DEBUG_MALLOC
    
    struct ast_variable *_ast_variable_new(const char *name, const char *value, const char *filename, const char *file, const char *func, int lineno)
    
    struct ast_variable *ast_variable_new(const char *name, const char *value, const char *filename)
    
    Mark Spencer's avatar
    Mark Spencer committed
    {
    
    	struct ast_variable *variable;
    
    	int name_len = strlen(name) + 1;
    	int val_len = strlen(value) + 1;
    	int fn_len = strlen(filename) + 1;
    
    	/* Ensure a minimum length in case the filename is changed later. */
    	if (fn_len < MIN_VARIABLE_FNAME_SPACE) {
    		fn_len = MIN_VARIABLE_FNAME_SPACE;
    	}
    
    	if (
    
    #ifdef __AST_DEBUG_MALLOC
    
    		(variable = __ast_calloc(1, fn_len + name_len + val_len + sizeof(*variable), file, lineno, func))
    
    		(variable = ast_calloc(1, fn_len + name_len + val_len + sizeof(*variable)))
    
    		char *dst = variable->stuff;	/* writable space starts here */
    
    
    		/* Put file first so ast_include_rename() can calculate space available. */
    		variable->file = strcpy(dst, filename);
    		dst += fn_len;
    
    		variable->name = strcpy(dst, name);
    		dst += name_len;
    		variable->value = strcpy(dst, value);
    
    Mark Spencer's avatar
    Mark Spencer committed
    	}
    
    	return variable;
    }
    
    
    /*!
     * \internal
     * \brief Move the contents from the source to the destination variable.
     *
     * \param dst_var Destination variable node
     * \param src_var Source variable node
     *
     * \return Nothing
     */
    static void ast_variable_move(struct ast_variable *dst_var, struct ast_variable *src_var)
    {
    	dst_var->lineno = src_var->lineno;
    	dst_var->object = src_var->object;
    	dst_var->blanklines = src_var->blanklines;
    	dst_var->precomments = src_var->precomments;
    	src_var->precomments = NULL;
    	dst_var->sameline = src_var->sameline;
    	src_var->sameline = NULL;
    	dst_var->trailing = src_var->trailing;
    	src_var->trailing = NULL;
    }
    
    
    struct ast_config_include *ast_include_new(struct ast_config *conf, const char *from_file, const char *included_file, int is_exec, const char *exec_file, int from_lineno, char *real_included_file_name, int real_included_file_name_size)
    {
    	/* a file should be included ONCE. Otherwise, if one of the instances is changed,
    
    	 * then all be changed. -- how do we know to include it? -- Handling modified
    
    Tilghman Lesher's avatar
    Tilghman Lesher committed
    	 * instances is possible, I'd have
    	 * to create a new master for each instance. */
    
    	inc = ast_include_find(conf, included_file);
    
    	if (inc) {
    		do {
    			inc->inclusion_count++;
    			snprintf(real_included_file_name, real_included_file_name_size, "%s~~%d", included_file, inc->inclusion_count);
    		} while (stat(real_included_file_name, &statbuf) == 0);
    
    		ast_log(LOG_WARNING,"'%s', line %d:  Same File included more than once! This data will be saved in %s if saved back to disk.\n", from_file, from_lineno, real_included_file_name);
    	} else
    		*real_included_file_name = 0;
    
    	inc = ast_calloc(1,sizeof(struct ast_config_include));
    
    	if (!inc) {
    		return NULL;
    	}
    
    	inc->include_location_file = ast_strdup(from_file);
    	inc->include_location_lineno = from_lineno;
    	if (!ast_strlen_zero(real_included_file_name))
    		inc->included_file = ast_strdup(real_included_file_name);
    	else
    		inc->included_file = ast_strdup(included_file);
    
    	inc->exec = is_exec;
    	if (is_exec)
    		inc->exec_file = ast_strdup(exec_file);
    
    
    	if (!inc->include_location_file
    		|| !inc->included_file
    		|| (is_exec && !inc->exec_file)) {
    		ast_includes_destroy(inc);
    		return NULL;
    	}
    
    
    	/* attach this new struct to the conf struct */
    	inc->next = conf->includes;
    	conf->includes = inc;
    
    	return inc;
    }
    
    void ast_include_rename(struct ast_config *conf, const char *from_file, const char *to_file)
    {
    	struct ast_config_include *incl;
    	struct ast_category *cat;
    
    	int from_len = strlen(from_file);
    	int to_len = strlen(to_file);
    
    	if (strcmp(from_file, to_file) == 0) /* no use wasting time if the name is the same */
    		return;
    
    	/* the manager code allows you to read in one config file, then
    
    Tilghman Lesher's avatar
    Tilghman Lesher committed
    	 * write it back out under a different name. But, the new arrangement
    	 * ties output lines to the file name. So, before you try to write
    	 * the config file to disk, better riffle thru the data and make sure
    	 * the file names are changed.
    	 */
    
    	/* file names are on categories, includes (of course), and on variables. So,
    
    Tilghman Lesher's avatar
    Tilghman Lesher committed
    	 * traverse all this and swap names */
    
    
    	for (incl = conf->includes; incl; incl=incl->next) {
    		if (strcmp(incl->include_location_file,from_file) == 0) {
    			if (from_len >= to_len)
    				strcpy(incl->include_location_file, to_file);
    			else {
    
    				/* Keep the old filename if the allocation fails. */
    				str = ast_strdup(to_file);
    				if (str) {
    					ast_free(incl->include_location_file);
    					incl->include_location_file = str;
    				}
    
    			}
    		}
    	}
    	for (cat = conf->root; cat; cat = cat->next) {
    
    		struct ast_variable **prev;
    		struct ast_variable *v;
    		struct ast_variable *new_var;
    
    
    		if (strcmp(cat->file,from_file) == 0) {
    			if (from_len >= to_len)
    				strcpy(cat->file, to_file);
    			else {
    
    				/* Keep the old filename if the allocation fails. */
    				str = ast_strdup(to_file);
    				if (str) {
    					ast_free(cat->file);
    					cat->file = str;
    				}
    
    		for (prev = &cat->root, v = cat->root; v; prev = &v->next, v = v->next) {
    			if (strcmp(v->file, from_file)) {
    				continue;
    			}
    
    			/*
    			 * Calculate actual space available.  The file string is
    			 * intentionally stuffed before the name string just so we can
    			 * do this.
    			 */
    			if (to_len < v->name - v->file) {
    				/* The new name will fit in the available space. */
    				str = (char *) v->file;/* Stupid compiler complains about discarding qualifiers even though I used a cast. */
    				strcpy(str, to_file);/* SAFE */
    				continue;
    			}
    
    			/* Keep the old filename if the allocation fails. */
    			new_var = ast_variable_new(v->name, v->value, to_file);
    			if (!new_var) {
    				continue;
    			}
    
    			/* Move items from the old list node to the replacement node. */
    			ast_variable_move(new_var, v);
    
    			/* Replace the old node in the list with the new node. */
    			new_var->next = v->next;
    			if (cat->last == v) {
    				cat->last = new_var;
    
    			*prev = new_var;
    
    			ast_variable_destroy(v);
    
    			v = new_var;
    
    		}
    	}
    }
    
    struct ast_config_include *ast_include_find(struct ast_config *conf, const char *included_file)
    {
    	struct ast_config_include *x;
    
    Luigi Rizzo's avatar
    Luigi Rizzo committed
    	for (x=conf->includes;x;x=x->next) {
    
    		if (strcmp(x->included_file,included_file) == 0)
    			return x;
    	}
    	return 0;
    }
    
    
    
    void ast_variable_append(struct ast_category *category, struct ast_variable *variable)
    {
    
    	if (category->last)
    		category->last->next = variable;
    	else
    		category->root = variable;
    	category->last = variable;
    
    	while (category->last->next)
    		category->last = category->last->next;
    
    void ast_variable_insert(struct ast_category *category, struct ast_variable *variable, const char *line)
    {
    	struct ast_variable *cur = category->root;
    	int lineno;
    	int insertline;
    
    
    Tilghman Lesher's avatar
    Tilghman Lesher committed
    	if (!variable || sscanf(line, "%30d", &insertline) != 1) {
    
    	if (!insertline) {
    		variable->next = category->root;
    		category->root = variable;
    	} else {
    		for (lineno = 1; lineno < insertline; lineno++) {
    			cur = cur->next;
    
    		}
    		variable->next = cur->next;
    		cur->next = variable;
    	}
    }
    
    
    static void ast_comment_destroy(struct ast_comment **comment)
    {
    	struct ast_comment *n, *p;
    
    	for (p = *comment; p; p = n) {
    		n = p->next;
    		ast_free(p);
    	}
    
    	*comment = NULL;
    }
    
    
    static void ast_variable_destroy(struct ast_variable *doomed)
    {
    	ast_comment_destroy(&doomed->precomments);
    	ast_comment_destroy(&doomed->sameline);
    	ast_comment_destroy(&doomed->trailing);
    	ast_free(doomed);
    }
    
    
    struct ast_variable *ast_variables_dup(struct ast_variable *var)
    {
    	struct ast_variable *cloned;
    	struct ast_variable *tmp;
    
    	if (!(cloned = ast_variable_new(var->name, var->value, var->file))) {
    		return NULL;
    	}
    
    	tmp = cloned;
    
    	while ((var = var->next)) {
    		if (!(tmp->next = ast_variable_new(var->name, var->value, var->file))) {
    			ast_variables_destroy(cloned);
    			return NULL;
    		}
    		tmp = tmp->next;
    	}
    
    	return cloned;
    }
    
    
    struct ast_variable *ast_variables_reverse(struct ast_variable *var)
    {
    	struct ast_variable *var1, *var2;
    
    	var1 = var;
    
    	if (!var1 || !var1->next) {
    		return var1;
    	}
    
    	var2 = var1->next;
    	var1->next = NULL;
    
    	while (var2) {
    		struct ast_variable *next = var2->next;
    
    		var2->next = var1;
    		var1 = var2;
    		var2 = next;
    	}
    
    	return var1;
    }
    
    
    void ast_variables_destroy(struct ast_variable *v)
    {
    	struct ast_variable *vn;
    
    
    		vn = v;
    		v = v->next;
    
    		ast_variable_destroy(vn);
    
    struct ast_variable *ast_variable_browse(const struct ast_config *config, const char *category)
    {
    
    
    	if (config->last_browse && (config->last_browse->name == category)) {
    
    		cat = config->last_browse;
    
    		cat = ast_category_get(config, category, NULL);
    
    	return (cat) ? cat->root : NULL;
    
    static inline struct ast_variable *variable_list_switch(struct ast_variable *l1, struct ast_variable *l2)
    
    {
        l1->next = l2->next;
        l2->next = l1;
        return l2;
    }
    
    struct ast_variable *ast_variable_list_sort(struct ast_variable *start)
    {
    
    	struct ast_variable *p, *q;
    	struct ast_variable top;
    
    	int changed = 1;
    
    	memset(&top, 0, sizeof(top));
    	top.next = start;
    
    	if (start != NULL && start->next != NULL) {
    		while (changed) {
    			changed = 0;
    
    			while (p->next != NULL) {
    				if (p->next != NULL && strcmp(p->name, p->next->name) > 0) {
    					q->next = variable_list_switch(p, p->next);
    					changed = 1;
    				}
    				q = p;
    				if (p->next != NULL)
    					p = p->next;
    			}
    		}
    	}
    
    struct ast_variable *ast_variable_list_append_hint(struct ast_variable **head, struct ast_variable *search_hint, struct ast_variable *newvar)
    {
    	struct ast_variable *curr;
    	ast_assert(head != NULL);
    
    	if (!*head) {
    		*head = newvar;
    	} else {
    		if (search_hint == NULL) {
    			search_hint = *head;
    		}
    		for (curr = search_hint; curr->next; curr = curr->next);
    		curr->next = newvar;
    	}
    
    	for (curr = newvar; curr->next; curr = curr->next);
    
    	return curr;
    }
    
    
    const char *ast_config_option(struct ast_config *cfg, const char *cat, const char *var)
    
    	tmp = ast_variable_retrieve(cfg, cat, var);
    
    		tmp = ast_variable_retrieve(cfg, "general", var);
    
    const char *ast_variable_retrieve(struct ast_config *config, const char *category, const char *variable)
    
    	struct ast_variable *v;
    
    	if (category) {
    		for (v = ast_variable_browse(config, category); v; v = v->next) {
    			if (!strcasecmp(variable, v->name)) {
    				return v->value;
    			}
    		}
    	} else {
    		struct ast_category *cat;
    
    		for (cat = config->root; cat; cat = cat->next) {
    			for (v = cat->root; v; v = v->next) {
    				if (!strcasecmp(variable, v->name)) {
    					return v->value;
    				}
    			}
    		}
    	}
    
    	return NULL;
    
    const char *ast_variable_retrieve_filtered(struct ast_config *config,
    	const char *category, const char *variable, const char *filter)
    
    Mark Spencer's avatar
    Mark Spencer committed
    {
    
    	struct ast_category *cat = NULL;
    	const char *value;
    
    	while ((cat = ast_category_browse_filtered(config, category, cat, filter))) {
    		value = ast_variable_find(cat, variable);
    		if (value) {
    			return value;
    
    	return NULL;
    }
    
    const char *ast_variable_find(const struct ast_category *category, const char *variable)
    {
    
    	return ast_variable_find_in_list(category->root, variable);
    }
    
    
    const struct ast_variable *ast_variable_find_variable_in_list(const struct ast_variable *list, const char *variable_name)
    {
    	const struct ast_variable *v;
    
    	for (v = list; v; v = v->next) {
    		if (!strcasecmp(variable_name, v->name)) {
    			return v;
    		}
    	}
    	return NULL;
    }
    
    int ast_variables_match(const struct ast_variable *left, const struct ast_variable *right)
    {
    	char *op;
    
    	if (left == right) {
    		return 1;
    	}
    
    	if (!(left && right)) {
    		return 0;
    	}
    
    	op = strrchr(right->name, ' ');
    	if (op) {
    		op++;
    	}
    
    	return ast_strings_match(left->value, op ? ast_strdupa(op) : NULL, right->value);
    }
    
    int ast_variable_lists_match(const struct ast_variable *left, const struct ast_variable *right, int exact_match)
    {
    	const struct ast_variable *field;
    	int right_count = 0;
    	int left_count = 0;
    
    	if (left == right) {
    		return 1;
    	}
    
    	if (!(left && right)) {
    		return 0;
    	}
    
    	for (field = right; field; field = field->next) {
    		char *space = strrchr(field->name, ' ');
    		const struct ast_variable *old;
    		char * name = (char *)field->name;
    
    		if (space) {
    			name = ast_strdup(field->name);
    			if (!name) {
    				return 0;
    			}
    			name[space - field->name] = '\0';
    		}
    
    		old = ast_variable_find_variable_in_list(left, name);
    		if (name != field->name) {
    			ast_free(name);
    		}
    
    		if (exact_match) {
    			if (!old || strcmp(old->value, field->value)) {
    				return 0;
    			}
    		} else {
    			if (!ast_variables_match(old, field)) {
    				return 0;
    			}
    		}
    
    		right_count++;
    	}
    
    	if (exact_match) {
    		for (field = left; field; field = field->next) {
    			left_count++;
    		}
    
    		if (right_count != left_count) {
    			return 0;
    		}
    	}
    
    	return 1;
    }
    
    
    const char *ast_variable_find_in_list(const struct ast_variable *list, const char *variable)
    {
    	const struct ast_variable *v;
    
    	for (v = list; v; v = v->next) {
    
    		if (!strcasecmp(variable, v->name)) {
    			return v->value;
    
    Mark Spencer's avatar
    Mark Spencer committed
    	}
    	return NULL;
    }
    
    
    const char *ast_variable_find_last_in_list(const struct ast_variable *list, const char *variable)
    {
    	const struct ast_variable *v;
    	const char *found = NULL;
    
    	for (v = list; v; v = v->next) {
    		if (!strcasecmp(variable, v->name)) {
    			found = v->value;
    		}
    	}
    	return found;
    }
    
    
    static struct ast_variable *variable_clone(const struct ast_variable *old)
    {
    
    	struct ast_variable *new = ast_variable_new(old->name, old->value, old->file);
    
    
    	if (new) {
    		new->lineno = old->lineno;
    		new->object = old->object;
    		new->blanklines = old->blanklines;
    		/* TODO: clone comments? */
    	}
    
    	return new;
    }
    
    static void move_variables(struct ast_category *old, struct ast_category *new)
    {
    
    	struct ast_variable *var = old->root;
    
    Luigi Rizzo's avatar
    Luigi Rizzo committed
    
    
    	old->root = NULL;
    
    	/* we can just move the entire list in a single op */
    	ast_variable_append(new, var);
    
    /*! \brief Returns true if ALL of the regex expressions and category name match.
     * Both can be NULL (I.E. no predicate) which results in a true return;
     */
    
    static int does_category_match(struct ast_category *cat, const char *category_name,
    	const char *match, char sep)
    
    {
    	char *dupmatch;
    	char *nvp = NULL;
    	int match_found = 0, match_expressions = 0;
    	int template_ok = 0;
    
    	/* Only match on category name if it's not a NULL or empty string */
    	if (!ast_strlen_zero(category_name) && strcasecmp(cat->name, category_name)) {
    		return 0;
    	}
    
    	/* If match is NULL or empty, automatically match if not a template */
    	if (ast_strlen_zero(match)) {
    		return !cat->ignored;
    	}
    
    	dupmatch = ast_strdupa(match);
    
    
    	while ((nvp = ast_strsep(&dupmatch, sep, AST_STRSEP_STRIP))) {
    
    		struct ast_variable *v;
    		char *match_name;
    		char *match_value = NULL;
    		char *regerr;
    		int rc;
    		regex_t r_name, r_value;
    
    		match_expressions++;
    
    		match_name = ast_strsep(&nvp, '=', AST_STRSEP_STRIP);
    		match_value = ast_strsep(&nvp, '=', AST_STRSEP_STRIP);
    
    		/* an empty match value is OK.  A NULL match value (no =) is NOT. */
    		if (match_value == NULL) {
    			break;
    		}
    
    		if (!strcmp("TEMPLATES", match_name)) {
    			if (!strcasecmp("include", match_value)) {
    				if (cat->ignored) {
    					template_ok = 1;
    				}
    				match_found++;
    			} else if (!strcasecmp("restrict", match_value)) {
    				if (cat->ignored) {
    					match_found++;
    					template_ok = 1;
    				} else {
    					break;
    				}
    			}
    			continue;
    		}
    
    		if ((rc = regcomp(&r_name, match_name, REG_EXTENDED | REG_NOSUB))) {
    			regerr = ast_alloca(128);
    			regerror(rc, &r_name, regerr, 128);
    			ast_log(LOG_ERROR, "Regular expression '%s' failed to compile: %s\n",
    				match_name, regerr);
    			regfree(&r_name);
    			return 0;
    		}
    		if ((rc = regcomp(&r_value, match_value, REG_EXTENDED | REG_NOSUB))) {
    			regerr = ast_alloca(128);
    			regerror(rc, &r_value, regerr, 128);
    			ast_log(LOG_ERROR, "Regular expression '%s' failed to compile: %s\n",
    				match_value, regerr);
    			regfree(&r_name);
    			regfree(&r_value);
    			return 0;
    		}
    
    		for (v = cat->root; v; v = v->next) {
    			if (!regexec(&r_name, v->name, 0, NULL, 0)
    				&& !regexec(&r_value, v->value, 0, NULL, 0)) {
    				match_found++;
    				break;
    			}
    		}
    		regfree(&r_name);
    		regfree(&r_value);
    	}
    	if (match_found == match_expressions && (!cat->ignored || template_ok)) {
    		return 1;
    	}
    	return 0;
    }
    
    
    static struct ast_category *new_category(const char *name, const char *in_file, int lineno, int template)
    
    {
    	struct ast_category *category;
    
    
    	category = ast_calloc(1, sizeof(*category));
    	if (!category) {
    		return NULL;
    	}
    	category->file = ast_strdup(in_file);
    	if (!category->file) {
    		ast_category_destroy(category);
    		return NULL;
    	}
    	ast_copy_string(category->name, name, sizeof(category->name));
    
    	category->lineno = lineno; /* if you don't know the lineno, set it to 999999 or something real big */
    
    	return category;
    }
    
    
    struct ast_category *ast_category_new(const char *name, const char *in_file, int lineno)
    {
    	return new_category(name, in_file, lineno, 0);
    }
    
    struct ast_category *ast_category_new_template(const char *name, const char *in_file, int lineno)
    
    	return new_category(name, in_file, lineno, 1);
    }
    
    static struct ast_category *category_get_sep(const struct ast_config *config,
    
    	const char *category_name, const char *filter, char sep, char pointer_match_possible)
    
    	if (pointer_match_possible) {
    		for (cat = config->root; cat; cat = cat->next) {
    			if (cat->name == category_name && does_category_match(cat, category_name, filter, sep)) {
    				return cat;
    			}