[asterisk-commits] nadi: branch nadi/trunk-cm r43886 - in
/team/nadi/trunk-cm: include/asterisk/...
asterisk-commits at lists.digium.com
asterisk-commits at lists.digium.com
Thu Sep 28 09:01:28 MST 2006
Author: nadi
Date: Thu Sep 28 11:01:26 2006
New Revision: 43886
URL: http://svn.digium.com/view/asterisk?rev=43886&view=rev
Log:
add: hash and configman, still experimental, work in progress
Added:
team/nadi/trunk-cm/include/asterisk/configman.h (with props)
team/nadi/trunk-cm/include/asterisk/hash.h (with props)
team/nadi/trunk-cm/main/configman.c (with props)
team/nadi/trunk-cm/main/hash.c (with props)
Modified:
team/nadi/trunk-cm/include/asterisk/cli.h
team/nadi/trunk-cm/main/Makefile
Modified: team/nadi/trunk-cm/include/asterisk/cli.h
URL: http://svn.digium.com/view/asterisk/team/nadi/trunk-cm/include/asterisk/cli.h?rev=43886&r1=43885&r2=43886&view=diff
==============================================================================
--- team/nadi/trunk-cm/include/asterisk/cli.h (original)
+++ team/nadi/trunk-cm/include/asterisk/cli.h Thu Sep 28 11:01:26 2006
@@ -46,7 +46,7 @@
/*! \brief A command line entry */
struct ast_cli_entry {
- char * const cmda[AST_MAX_CMD_LEN];
+ char * cmda[AST_MAX_CMD_LEN];
/*! Handler for the command (fd for output, # of args, argument list).
Returns RESULT_SHOWUSAGE for improper arguments.
argv[] has argc 'useful' entries, and an additional NULL entry
@@ -57,9 +57,9 @@
*/
int (*handler)(int fd, int argc, char *argv[]);
/*! Summary of the command (< 60 characters) */
- const char *summary;
+ char *summary;
/*! Detailed usage information */
- const char *usage;
+ char *usage;
/*! Generate the n-th (starting from 0) possible completion
for a given 'word' following 'line' in position 'pos'.
'line' and 'word' must not be modified.
Added: team/nadi/trunk-cm/include/asterisk/configman.h
URL: http://svn.digium.com/view/asterisk/team/nadi/trunk-cm/include/asterisk/configman.h?rev=43886&view=auto
==============================================================================
--- team/nadi/trunk-cm/include/asterisk/configman.h (added)
+++ team/nadi/trunk-cm/include/asterisk/configman.h Thu Sep 28 11:01:26 2006
@@ -1,0 +1,120 @@
+/*
+ * Asterisk -- An open source telephony toolkit.
+ *
+ * Copyright (C) 2006, Nadi Sarrar
+ *
+ * Nadi Sarrar <nadi at beronet.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 configman.h
+ * \brief Configuration Management
+ * \author Nadi Sarrar <nadi at beronet.com>
+ */
+
+#ifndef _ASTERISK_CONFIGMAN_H
+#define _ASTERISK_CONFIGMAN_H
+
+#include <sys/types.h>
+
+typedef struct cm cm_t;
+
+typedef struct {
+ char * name;
+ char * def;
+ char * desc;
+} cm_dir_t;
+
+enum ktype {
+ KTYPE_NONE = 0,
+ KTYPE_INTEGER,
+ KTYPE_STRING,
+};
+
+typedef struct {
+ char * section_name; /* Name of the section. */
+ char * default_name; /* If you want this section to collect config categories with
+ any name and have a special category for default values,
+ name it here. */
+ char * key_field; /* If you specify a key field, then this will be the key to
+ access values, specify the key_type below. */
+ enum ktype key_type; /* The type of the key field. */
+ int num_directives; /* Number of the directives array elements. */
+ const cm_dir_t * directives; /* The first directive targets the string between the '[ ]'.
+ If key_type == KEY_NONE, let the first one be empty, i.e.
+ { 0, 0, 0 } or { } or { 0 } or whatever you like most. */
+} cm_section_t;
+
+/*! \brief Create a new config manager object
+ * \param modname Name to identify the owner, used in log messages and for registering cli commands like
+ * {modname} show config [...]
+ * \param sections Array of sections, must be static or allocated until after cm_destroy
+ * \param num Number of elemenets in sections
+ * Creates a cm_t structure from given cm_section_t's.
+ *
+ * Returns NULL on error, or a cm_t data structure on success.
+ */
+cm_t * cm_create (const char *modname, const cm_section_t *sections, int num);
+
+/*! \brief Load and parse a configuration file
+ * \param cm_t which cm_t to use
+ * \param filename path of file to open. If no preceding '/' character, path is considered relative to AST_CONFIG_DIR
+ * Fills the cm_t structure with data from a given configuration file.
+ *
+ * Returns non-zero on error.
+ */
+int cm_load (cm_t *cm, const char *filename);
+
+/*! \brief Destroy a cm_t
+ * \param cm_t which cm_t to destroy
+ * Destroys and frees a given cm_t data structure.
+ */
+void cm_destroy (cm_t *cm);
+
+/*! \brief Get a configuration value
+ * \param cm_t which cm_t to use
+ * \param buf where to put the result
+ * \param size size of buf
+ * \param sec_id section id to use
+ * \param elem_id element id to use
+ * \param ... integer or string id. omit this parameter, if the underlying cm_section_t is a single section type
+ * Reads a value from the cm_t data structure and copies it into buf.
+ *
+ * Returns non-zero on error.
+ */
+int cm_get (cm_t *cm, char *buf, size_t size, int sec_id, int elem_id, ...);
+
+/*! \brief Validate an id
+ * \param cm_t which cm_t to use
+ * \param sec_id section id to use
+ * \param ... integer or string id
+ * Validates a given id.
+ *
+ * Returns true on success, zero if either id or sec_id is not valid.
+ */
+int cm_id_valid (cm_t *cm, int sec_id, ...);
+
+/*! \brief Get the subsequent id
+ * \param cm_t which cm_t to use
+ * \param sec_id section id to use
+ * \param ... preceding integer or string id
+ * Gets the subsequent id of a given id.
+ *
+ * Returns a const pointer to the subsequent id or NULL on error, i.e.
+ * if the preceding ID was already the last one.
+ */
+const
+void * cm_get_next_id (cm_t *cm, int sec_id, ...);
+
+int ast_configman_init (void);
+
+#endif
Propchange: team/nadi/trunk-cm/include/asterisk/configman.h
------------------------------------------------------------------------------
svn:eol-style = native
Propchange: team/nadi/trunk-cm/include/asterisk/configman.h
------------------------------------------------------------------------------
svn:keywords = text/plain
Propchange: team/nadi/trunk-cm/include/asterisk/configman.h
------------------------------------------------------------------------------
svn:mime-type = text/plain
Added: team/nadi/trunk-cm/include/asterisk/hash.h
URL: http://svn.digium.com/view/asterisk/team/nadi/trunk-cm/include/asterisk/hash.h?rev=43886&view=auto
==============================================================================
--- team/nadi/trunk-cm/include/asterisk/hash.h (added)
+++ team/nadi/trunk-cm/include/asterisk/hash.h Thu Sep 28 11:01:26 2006
@@ -1,0 +1,247 @@
+/*
+ * Asterisk -- An open source telephony toolkit.
+ *
+ * Copyright (C) 2006, Nadi Sarrar
+ *
+ * Nadi Sarrar <nadi at beronet.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 hash.h
+ *
+ * \brief A simple hash implementation.
+ *
+ * \author Nadi Sarrar <nadi at beronet.com>
+ *
+ * A set of macros to provide an easy way of working with hashes.
+ * Works with either integer or character string keys out of the
+ * box. If you want to use another key type, just write a hash
+ * function for it and look at macros beginning with "__".
+ */
+
+
+#ifndef ASTERISK_HASH_H
+#define ASTERISK_HASH_H
+
+#include <sys/types.h>
+#include "asterisk/lock.h"
+
+int modolo_hash (int key, size_t size);
+int joaat_hash (char *key, size_t size);
+
+int eq_str (char *a, char *b);
+int eq_int (int a, int b);
+
+#define __AST_HASH(name, ktype, vtype) \
+struct name { \
+ struct { \
+ ktype _key; \
+ vtype _val; \
+ char _used; \
+ } *_data; \
+ size_t _size; \
+ size_t _length; \
+ ast_mutex_t _lock; \
+ int (*_hash_f)(ktype, size_t); \
+ int (*_eq_f) (ktype, ktype); \
+}
+
+#define AST_HASH_INT(name, type) \
+ __AST_HASH(name, int, type)
+
+#define AST_HASH_STR(name, type) \
+ __AST_HASH(name, char*, type)
+
+
+
+
+#define __AST_HASH_INIT_NOLOCK(hash, size, hash_f, eq_f) \
+do { \
+ (hash)->_data = calloc((size), sizeof(*(hash)->_data)); \
+ (hash)->_size = (size); \
+ (hash)->_hash_f = (hash_f); \
+ (hash)->_eq_f = (eq_f); \
+} while (0)
+
+#define __AST_HASH_INIT(hash, size, hash_f, cmp) \
+do { \
+ __AST_HASH_INIT_NOLOCK((hash), (size), (hash_f), (cmp));\
+ ast_mutex_init(&(hash)->_lock); \
+} while (0)
+
+#define AST_HASH_INIT_INT_NOLOCK(hash, size) \
+ __AST_HASH_INIT_NOLOCK((hash), size, modolo_hash, eq_int)
+
+#define AST_HASH_INIT_INT(hash, size) \
+ __AST_HASH_INIT((hash), size, modolo_hash, eq_int)
+
+#define AST_HASH_INIT_STR_NOLOCK(hash, size) \
+ __AST_HASH_INIT_NOLOCK((hash), size, joaat_hash, eq_str)
+
+#define AST_HASH_INIT_STR(hash, size) \
+ __AST_HASH_INIT((hash), size, joaat_hash, eq_str)
+
+
+
+
+#define AST_HASH_DESTROY_NOLOCK(hash) \
+do { \
+ if ((hash)->_data) \
+ free((hash)->_data); \
+} while (0)
+
+#define AST_HASH_DESTROY(hash) \
+do { \
+ AST_HASH_DESTROY_NOLOCK((hash)); \
+ ast_mutex_destroy(&(hash)->_lock); \
+} while (0)
+
+
+
+
+#define AST_HASH_LOCK(hash) \
+ ast_mutex_lock(&(hash)->_lock)
+
+#define AST_HASH_TRYLOCK(hash) \
+ ast_mutex_trylock(&(hash)->_lock)
+
+#define AST_HASH_UNLOCK(hash) \
+ ast_mutex_unlock(&(hash)->_lock)
+
+
+
+
+#define AST_HASH_INSERT_NOLOCK(hash, key, val) \
+({ \
+ int pos = (hash)->_hash_f(key, (hash)->_size); \
+ int end = pos; \
+ int re = -1; \
+ do { \
+ if (!(hash)->_data[pos]._used) { \
+ (hash)->_data[pos]._key = key; \
+ (hash)->_data[pos]._val = val; \
+ (hash)->_data[pos]._used = 1; \
+ ++(hash)->_length; \
+ re = 0; \
+ break; \
+ } \
+ pos = (pos + 1) % (hash)->_size; \
+ } while (pos != end); \
+ re; \
+})
+
+#define AST_HASH_INSERT(hash, key, val) \
+({ \
+ int re; \
+ AST_HASH_LOCK((hash)); \
+ re = AST_HASH_INSERT_NOLOCK(hash, key, val);\
+ AST_HASH_UNLOCK((hash)); \
+ re; \
+})
+
+#define AST_HASH_LOOKUP_NOLOCK(hash, key, val) \
+({ \
+ int pos = (hash)->_hash_f(key, (hash)->_size); \
+ int end = pos; \
+ int re = -1; \
+ do { \
+ if ((hash)->_data[pos]._used && \
+ (hash)->_eq_f((hash)->_data[pos]._key, key)) { \
+ val = (hash)->_data[pos]._val; \
+ re = 0; \
+ } \
+ pos = (pos + 1) % (hash)->_size; \
+ } while (pos != end); \
+ re; \
+})
+
+#define AST_HASH_LOOKUP(hash, key, val) \
+({ \
+ int re; \
+ AST_HASH_LOCK((hash)); \
+ re = AST_HASH_LOOKUP_NOLOCK(hash, key, val);\
+ AST_HASH_UNLOCK((hash)); \
+ re; \
+})
+
+#define AST_HASH_REMOVE_NOLOCK(hash, key) \
+do { \
+ int pos = (hash)->_hash_f((key), (hash)->_size); \
+ int end = pos; \
+ do { \
+ if ((hash)->_data[pos]._used && \
+ (hash)->_eq_f((hash)->_data[pos]._key, (key))) { \
+ (hash)->_data[pos]._used = 0; \
+ --(hash)->_length; \
+ break; \
+ } \
+ pos = (pos + 1) % (hash)->_size; \
+ } while (pos != end); \
+} while (0) \
+
+#define AST_HASH_REMOVE(hash, key) \
+do { \
+ AST_HASH_LOCK((hash)); \
+ AST_HASH_REMOVE_NOLOCK((hash), (key)); \
+ AST_HASH_UNLOCK((hash)); \
+} while (0)
+
+
+
+
+#define __AST_HASH_NEXT(hash, i) \
+({ \
+ for (++i; \
+ i < (hash)->_size && \
+ !(hash)->_data[i]._used; \
+ ++i); \
+})
+
+#define AST_HASH_TRAVERSE_NOLOCK(hash, key, val, i) \
+ for (i = -1, \
+ __AST_HASH_NEXT((hash), i); \
+ i < (hash)->_size && \
+ (((key = (hash)->_data[i]._key)) || 1) && \
+ (((val = (hash)->_data[i]._val)) || 1); \
+ __AST_HASH_NEXT((hash), i))
+
+
+
+
+#define AST_HASH_LENGTH_NOLOCK(hash) \
+ (hash)->_length
+
+#define AST_HASH_LENGTH(hash) \
+({ \
+ size_t length; \
+ AST_HASH_LOCK((hash)); \
+ length = AST_HASH_LENGTH_NOLOCK((hash)); \
+ AST_HASH_UNLOCK((hash)); \
+ length; \
+})
+
+
+
+
+#define AST_HASH_SIZE_NOLOCK(hash) \
+ (hash)->_size
+
+#define AST_HASH_SIZE(hash) \
+({ \
+ size_t size; \
+ AST_HASH_LOCK((hash)); \
+ size = AST_HASH_SIZE_NOLOCK((hash)); \
+ AST_HASH_UNLOCK((hash)); \
+ size; \
+})
+
+#endif
Propchange: team/nadi/trunk-cm/include/asterisk/hash.h
------------------------------------------------------------------------------
svn:eol-style = native
Propchange: team/nadi/trunk-cm/include/asterisk/hash.h
------------------------------------------------------------------------------
svn:keywords = text/plain
Propchange: team/nadi/trunk-cm/include/asterisk/hash.h
------------------------------------------------------------------------------
svn:mime-type = text/plain
Modified: team/nadi/trunk-cm/main/Makefile
URL: http://svn.digium.com/view/asterisk/team/nadi/trunk-cm/main/Makefile?rev=43886&r1=43885&r2=43886&view=diff
==============================================================================
--- team/nadi/trunk-cm/main/Makefile (original)
+++ team/nadi/trunk-cm/main/Makefile Thu Sep 28 11:01:26 2006
@@ -26,7 +26,7 @@
utils.o plc.o jitterbuf.o dnsmgr.o devicestate.o \
netsock.o slinfactory.o ast_expr2.o ast_expr2f.o \
cryptostub.o sha1.o http.o fixedjitterbuf.o abstract_jb.o \
- strcompat.o
+ strcompat.o hash.o configman.o
# we need to link in the objects statically, not as a library, because
# otherwise modules will not have them available if none of the static
Added: team/nadi/trunk-cm/main/configman.c
URL: http://svn.digium.com/view/asterisk/team/nadi/trunk-cm/main/configman.c?rev=43886&view=auto
==============================================================================
--- team/nadi/trunk-cm/main/configman.c (added)
+++ team/nadi/trunk-cm/main/configman.c Thu Sep 28 11:01:26 2006
@@ -1,0 +1,1289 @@
+/*
+ * Asterisk -- An open source telephony toolkit.
+ *
+ * Copyright (C) 2006, Nadi Sarrar
+ *
+ * Nadi Sarrar <nadi at beronet.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 configman.c
+ *
+ * \brief Configuration Management
+ *
+ * \author Nadi Sarrar <nadi at beronet.com>
+ *
+ * Configuration Management written to be used by
+ * Asterisk channel modules, but may fit fine for
+ * other cases too.
+ */
+
+/* TODO:
+ * - get rid of the hash code by implementing ast_hash or similar.
+ * - write macros for easier variable access (cm_get_bool, cm_get_int, cm_get_strlist, cm_get_intlist, ...)
+ * - handle return values for *alloc, strdup, ast_cli_register
+ */
+
+#include "asterisk.h"
+
+ASTERISK_FILE_VERSION(__FILE__, "$Revision: $")
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <stdarg.h>
+#include <string.h>
+#include <errno.h>
+
+#include "asterisk/configman.h"
+#include "asterisk/config.h"
+#include "asterisk/cli.h"
+#include "asterisk/logger.h"
+#include "asterisk/lock.h"
+#include "asterisk/strings.h"
+#include "asterisk/utils.h"
+#include "asterisk/term.h"
+#include "asterisk/hash.h"
+
+static inline char * notdir (char *str)
+{
+ char *p = strrchr(str, '/');
+ return (p && *(p + 1)) ? p + 1 : str;
+}
+
+#define WARNING_PARSE_NV(cm, name, value, section) \
+ ast_log(LOG_WARNING, "%s [%s]: invalid expression in section \"%s\": \"%s = %s\". " \
+ "Please edit your %s and then reload.\n", \
+ (cm)->modname, notdir((cm)->filename), (section), (name), (value), notdir((cm)->filename))
+
+#define WARNING_PARSE_SEC(cm, section) \
+ ast_log(LOG_WARNING, "%s [%s]: invalid section \"%s\". " \
+ "Please edit your %s and then reload.\n", \
+ (cm)->modname, notdir((cm)->filename), (section), notdir((cm)->filename))
+
+#define WARNING(fmt,...) \
+ ast_log(LOG_WARNING, "confman: " fmt, ##__VA_ARGS__)
+#define WARNING_CM(cm,fmt,...) \
+ ast_log(LOG_WARNING, "%s: " fmt, (cm)->modname, ##__VA_ARGS__)
+#define WARNING_CMF(cm,fmt,...) \
+ ast_log(LOG_WARNING, "%s [%s]: " fmt, (cm)->modname, (cm)->filename, ##__VA_ARGS__)
+
+#define ERROR(fmt,...) \
+ ast_log(LOG_ERROR, "confman: " fmt, ##__VA_ARGS__)
+#define ERROR_CM(cm,fmt,...) \
+ ast_log(LOG_ERROR, "%s: " fmt, (cm)->modname, ##__VA_ARGS__)
+#define ERROR_CMF(cm,fmt,...) \
+ ast_log(LOG_ERROR, "%s [%s]: " fmt, (cm)->modname, (cm)->filename, ##__VA_ARGS__)
+
+#define LOCK(cm) ast_mutex_lock(&((cm)->lock))
+#define UNLOCK(cm) ast_mutex_unlock(&((cm)->lock))
+
+#define HASHSIZE 512
+
+static char SHOW[] = "show";
+static char CONFIG[] = "config";
+static char DESCRIPTION[] = "description";
+static char DESCRIPTIONS[] = "descriptions";
+static char VALUES[] = "values";
+
+typedef union {
+ char * c;
+ int i;
+} cm_hash_key_t;
+
+typedef union {
+ int i;
+ void * p;
+} cm_hash_value_t;
+
+typedef struct {
+ int used;
+ cm_hash_key_t key;
+ cm_hash_value_t val;
+} cm_hash_entry_t;
+
+typedef struct {
+ cm_hash_entry_t data[HASHSIZE];
+ size_t length;
+} cm_hash_t;
+
+enum cm_state {
+ CM_NULL = 0,
+ CM_CREATED,
+ CM_LOADED,
+};
+
+typedef char * cm_val_t;
+typedef cm_val_t * cm_row_t;
+typedef struct {
+ int num_rows;
+ cm_row_t * rows;
+} cm_matrix_t;
+
+struct cm {
+ ast_mutex_t lock;
+ enum cm_state state;
+ char * modname;
+ char * filename;
+ unsigned int num_secs;
+ const cm_section_t * secs;
+ cm_matrix_t * vals;
+ cm_hash_t ** hashes;
+ cm_hash_key_t ** key_lists;
+ struct ast_cli_entry clis[3];
+};
+
+static AST_HASH_STR(cm_obj_hash_t, cm_t *) cm_obj_hash;
+
+static inline int __joaat_hash (char *key, size_t len)
+{
+ int hash = 0;
+ size_t i;
+
+ for (i = 0; i < len; ++i) {
+ hash += key[i];
+ hash += (hash << 10);
+ hash ^= (hash >> 6);
+ }
+ hash += (hash << 3);
+ hash ^= (hash >> 11);
+ hash += (hash << 15);
+ hash %= HASHSIZE;
+
+ if (hash < 0)
+ hash *= -1;
+
+ return hash;
+}
+
+static inline int __hash_insert (cm_hash_t *hash, int pos, cm_hash_key_t key, cm_hash_value_t val)
+{
+ int end = pos;
+
+ do {
+ if (!hash->data[pos].used) {
+ hash->data[pos].key = key;
+ hash->data[pos].val = val;
+ hash->data[pos].used = 1;
+ ++hash->length;
+ return 0;
+ }
+ pos = (pos + 1) % HASHSIZE;
+ } while (pos != end);
+
+ return -1;
+}
+
+static int __hash_insert_int (cm_hash_t *hash, int key, cm_hash_value_t val)
+{
+ int pos = __joaat_hash((char *)&key, sizeof(int));
+
+ return __hash_insert(hash, pos, (cm_hash_key_t)key, val);
+}
+
+static int __hash_insert_string (cm_hash_t *hash, char *key, cm_hash_value_t val)
+{
+ int pos = __joaat_hash(key, strlen(key));
+
+ return __hash_insert(hash, pos, (cm_hash_key_t)key, val);
+}
+
+static int __hash_lookup_int (cm_hash_t *hash, int key, cm_hash_value_t *val)
+{
+ int pos = __joaat_hash((char *)&key, sizeof(int));
+ int end = pos;
+
+ do {
+ if (hash->data[pos].used && hash->data[pos].key.i == key) {
+ *val = hash->data[pos].val;
+ return 0;
+ }
+ pos = (pos + 1) % HASHSIZE;
+ } while (pos != end);
+
+ return -1;
+}
+
+static int __hash_lookup_string (cm_hash_t *hash, char *key, cm_hash_value_t *val)
+{
+ int pos = __joaat_hash(key, strlen(key));
+ int end = pos;
+
+ do {
+ if (hash->data[pos].used && !strcmp(hash->data[pos].key.c, key)) {
+ *val = hash->data[pos].val;
+ return 0;
+ }
+ pos = (pos + 1) % HASHSIZE;
+ } while (pos != end);
+
+ return -1;
+}
+
+static int __get_col (const cm_section_t *sec, char *name)
+{
+ int i;
+
+ if (!strcasecmp(sec->section_name, name))
+ return 0;
+
+ for (i = 0; i < sec->num_directives; ++i)
+ if (sec->directives[i].name &&
+ !strcasecmp(sec->directives[i].name, name))
+ return i;
+
+ return -1;
+}
+
+int cm_id_valid (cm_t *cm, int sec_id, ...)
+{
+ va_list ap;
+ int id_int;
+ char * id_string;
+ cm_hash_value_t val;
+
+ if (!cm)
+ goto err1;
+
+ LOCK(cm);
+
+ if (cm->state != CM_LOADED || sec_id >= cm->num_secs || sec_id < 0)
+ goto err2;
+
+ switch (cm->secs[sec_id].key_type) {
+ case KTYPE_NONE:
+ goto err2;
+ case KTYPE_INTEGER:
+ va_start(ap, sec_id);
+ id_int = va_arg(ap, int);
+ va_end(ap);
+ if (!cm->hashes[sec_id])
+ goto err2;
+ if (__hash_lookup_int(cm->hashes[sec_id], id_int, &val))
+ goto err2;
+ break;
+ case KTYPE_STRING:
+ va_start(ap, sec_id);
+ id_string = va_arg(ap, char *);
+ va_end(ap);
+ if (!cm->hashes[sec_id])
+ goto err2;
+ if (__hash_lookup_string(cm->hashes[sec_id], id_string, &val))
+ goto err2;
+ break;
+ }
+
+ UNLOCK(cm);
+
+ return 1;
+
+err2:
+ UNLOCK(cm);
+err1:
+ return 0;
+}
+
+int cm_get (cm_t *cm, char *buf, size_t size, int sec_id, int elem_id, ...)
+{
+ va_list ap;
+ int id_int;
+ char * val,
+ * id_string;
+ cm_hash_value_t row;
+
+ if (!cm)
+ goto err1;
+
+ LOCK(cm);
+
+ if (cm->state != CM_LOADED)
+ goto err2;
+
+ if (!buf || !size || sec_id >= cm->num_secs || sec_id < 0 ||
+ elem_id < 0 || elem_id >= cm->secs[sec_id].num_directives)
+ goto err2;
+
+ switch (cm->secs[sec_id].key_type) {
+ case KTYPE_NONE:
+ val = cm->vals[sec_id].rows[0][elem_id];
+ if (!val || !memccpy(buf, val, 0, size)) {
+ *buf = 0;
+ goto err2;
+ }
+ goto out;
+ case KTYPE_INTEGER:
+ va_start(ap, elem_id);
+ id_int = va_arg(ap, int);
+ va_end(ap);
+ if (!cm->hashes[sec_id])
+ goto err2;
+ if (__hash_lookup_int(cm->hashes[sec_id], id_int, &row))
+ goto err2;
+ break;
+ case KTYPE_STRING:
+ va_start(ap, elem_id);
+ id_string = va_arg(ap, char *);
+ va_end(ap);
+ if (!cm->hashes[sec_id])
+ goto err2;
+ if (__hash_lookup_string(cm->hashes[sec_id], id_string, &row))
+ goto err2;
+ break;
+ }
+
+ val = cm->vals[sec_id].rows[row.i][elem_id];
+ if (!val)
+ val = cm->vals[sec_id].rows[0][elem_id];
+ if (!val || !memccpy(buf, val, 0, size)) {
+ *buf = 0;
+ goto err2;
+ }
+
+out:
+ UNLOCK(cm);
+
+ return 0;
+
+err2:
+ UNLOCK(cm);
+err1:
+ return -1;
+}
+
+static void __read_row (cm_t *cm, const cm_section_t *sec, cm_row_t *row_p, char *cat, struct ast_variable *v)
+{
+ cm_row_t row;
+ int pos;
+
+ if (!*row_p)
+ *row_p = calloc(sec->num_directives, sizeof(cm_val_t));
+ row = *row_p;
+
+ if (!row[0])
+ row[0] = strdup(cat);
+ for (; v; v = v->next) {
+ pos = __get_col(sec, v->name);
+ if (pos >= 0) {
+ if (row[pos]) {
+ row[pos] = realloc(row[pos], strlen(row[pos]) + strlen(v->value) + 2);
+ sprintf(row[pos] + strlen(row[pos]), ",%s", v->value);
+ } else
+ row[pos] = strdup(v->value);
+ } else
+ WARNING_PARSE_NV(cm, v->name, v->value, cat);
+ }
+}
+
+static void __read_category_1 (cm_t *cm, const cm_section_t *sec, cm_matrix_t *matrix, char *cat, struct ast_variable *v)
+{
+ if (!matrix->num_rows) {
+ matrix->num_rows = 1;
+ matrix->rows = calloc(1, sizeof(cm_row_t));
+ }
+ __read_row(cm, sec, &matrix->rows[0], cat, v);
+}
+
+static void __read_category_n (cm_t *cm, const cm_section_t *sec, cm_matrix_t *matrix, char *cat, struct ast_variable *v)
+{
+ int row;
+
+ for (row = 1; row < matrix->num_rows; ++row) {
+ if (!strcasecmp(matrix->rows[row][0], cat)) {
+ __read_row(cm, sec, &matrix->rows[row], cat, v);
+ return;
+ }
+ }
+
+ if (!matrix->num_rows) {
+ matrix->num_rows = 2;
+ matrix->rows = calloc(2, sizeof(cm_row_t));
+ }
+ else {
+ ++matrix->num_rows;
+ matrix->rows = realloc(matrix->rows, matrix->num_rows * sizeof(cm_row_t));
+ matrix->rows[matrix->num_rows - 1] = NULL;
+ }
+ __read_row(cm, sec, &matrix->rows[matrix->num_rows - 1], cat, v);
+}
+
+static void __hash_categories (cm_t *cm, const cm_section_t *sec, cm_matrix_t *matrix, cm_hash_t **hash)
+{
+ int pos,
+ start,
+ end,
+ i;
+ char * token,
+ * tmp;
+
+ switch (sec->key_type) {
+ case KTYPE_INTEGER:
+ if (!*hash)
+ *hash = calloc(1, sizeof(cm_hash_t));
+ pos = __get_col(sec, sec->key_field);
+ if (pos < 0)
+ ERROR_CM(cm, "missing key field \"%s\" in section structure!\n", sec->key_field);
+ else
+ for (i = 1; i < matrix->num_rows; ++i) {
+ if (!matrix->rows[i][pos])
+ WARNING_CMF(cm, "missing value for key field \"%s\" in section \"%s\"!\n",
+ sec->key_field,
+ matrix->rows[i][0] ? matrix->rows[i][0] : sec->section_name);
+ else {
+ tmp = strdup(matrix->rows[i][pos]);
+ for (token = strsep(&tmp, ","); token; token = strsep(&tmp, ",")) {
+ if (!*token)
+ continue;
+ if (sscanf(token, "%d-%d", &start, &end) >= 2) {
+ for (; start <= end; start++) {
+ if (__hash_insert_int(*hash, start, (cm_hash_value_t)i))
+ WARNING_CMF(cm, "failed to hash integer key: %d\n", start);
+ }
+ } else if (sscanf(token, "%d", &start)) {
+ if (__hash_insert_int(*hash, start, (cm_hash_value_t)i))
+ WARNING_CMF(cm, "failed to hash integer key: %d\n", start);
+ } else
+ WARNING_PARSE_NV(cm, sec->directives[pos].name, matrix->rows[i][pos], sec->section_name);
+ }
+ free(tmp);
+ }
+ }
+ break;
+ case KTYPE_STRING:
+ if (!*hash)
+ *hash = calloc(1, sizeof(cm_hash_t));
+ pos = __get_col(sec, sec->key_field);
+ if (pos < 0)
+ ERROR_CM(cm, "missing key field \"%s\" in section structure!\n", sec->key_field);
+ else
+ for (i = 1; i < matrix->num_rows; ++i) {
+ if (!matrix->rows[i][pos])
+ WARNING_CMF(cm, "missing value for key field \"%s\" in section \"%s\"!\n",
+ sec->key_field,
+ matrix->rows[i][0] ? matrix->rows[i][0] : sec->section_name);
+ else {
+ if (__hash_insert_string(*hash, matrix->rows[i][pos], (cm_hash_value_t)i))
+ WARNING_CMF(cm, "failed to hash string key: %s\n", matrix->rows[matrix->num_rows - 1][pos]);
+ }
+ }
+ break;
+ case KTYPE_NONE:
+ break;
+ }
+}
+
+static void __fill_defaults (cm_t *cm, const cm_section_t *sec, cm_matrix_t *matrix)
+{
+ int i;
+
+ if (!matrix->num_rows) {
+ matrix->num_rows = 1;
+ matrix->rows = calloc(1, sizeof(cm_row_t));
+ }
+
+ if (!matrix->rows[0])
+ matrix->rows[0] = calloc(sec->num_directives, sizeof(cm_val_t));
+
+ if (!matrix->rows[0][0])
+ matrix->rows[0][0] = sec->default_name ? strdup(sec->default_name) : strdup(sec->section_name);
+
+ for (i = 1; i < sec->num_directives; ++i)
+ if (!matrix->rows[0][i] && sec->directives[i].def && *sec->directives[i].def)
+ matrix->rows[0][i] = strdup(sec->directives[i].def);
+}
+
+static inline void __key_list_insert_sorted_int (cm_hash_key_t *key_list, cm_hash_key_t key)
+{
+ int i,
+ a,
+ b;
+
+ for (i = 0; key_list[i].i != -1 && key_list[i].i <= key.i; ++i);
+
+ b = key_list[i].i;
+ key_list[i].i = key.i;
+
+ for (++i; b != -1; ++i) {
+ a = key_list[i].i;
+ key_list[i].i = b;
+ b = a;
+ }
+}
+
+static inline void __key_list_insert_sorted_string (cm_hash_key_t *key_list, cm_hash_key_t key)
+{
+ int i;
+ char * a,
+ * b;
+
+ for (i = 0; key_list[i].c && strcasecmp(key_list[i].c, key.c) <= 0; ++i);
+
+ b = key_list[i].c;
+ key_list[i].c = key.c;
+
+ for (++i; b; ++i) {
+ a = key_list[i].c;
+ key_list[i].c = b;
+ b = a;
+ }
+}
+
+static void __init_key_list (cm_t *cm, cm_hash_t *hash, cm_hash_key_t **key_list, enum ktype type)
+{
+ int i;
+
+ *key_list = calloc(hash->length, sizeof(key_list));
+
+ if (type == KTYPE_INTEGER)
+ for (i = 0; i < hash->length; ++i)
+ (*key_list)[i].i = -1;
+
+ for (i = 0; i < HASHSIZE; ++i) {
+ if (hash->data[i].used) {
+ if (type == KTYPE_INTEGER)
+ __key_list_insert_sorted_int(*key_list, hash->data[i].key);
+ else
+ __key_list_insert_sorted_string(*key_list, hash->data[i].key);
+ }
+ }
+}
+
+const void * cm_get_next_id (cm_t *cm, int sec_id, ...)
+{
+ va_list ap;
+ int id_int,
+ i;
+ char * id_string;
+ void * retval = NULL;
+ cm_hash_key_t * key_list;
+ cm_hash_t * hash;
+
+ if (!cm)
+ goto err1;
+
+ LOCK(cm);
+
+ if (cm->state != CM_LOADED || sec_id >= cm->num_secs || sec_id < 0)
+ goto err2;
+
+ hash = cm->hashes[sec_id];
+ key_list = cm->key_lists[sec_id];
+
+ if (!hash->length)
+ goto err2;
+
+ switch (cm->secs[sec_id].key_type) {
+ case KTYPE_NONE:
+ goto err2;
+ case KTYPE_INTEGER:
+ va_start(ap, sec_id);
+ id_int = va_arg(ap, int);
+ va_end(ap);
+ if (id_int == -1) {
+ retval = &key_list[0].i;
+ } else {
+ for (i = 0; i < hash->length && key_list[i].i != id_int; ++i);
+ if (i >= (hash->length - 1))
+ goto err2;
+ retval = &key_list[i + 1].i;
+ }
+ break;
+ case KTYPE_STRING:
+ va_start(ap, sec_id);
+ id_string = va_arg(ap, char *);
+ va_end(ap);
+ if (!id_string) {
+ retval = key_list[0].c;
+ } else {
+ for (i = 0; i < hash->length && strcasecmp(key_list[i].c, id_string); ++i);
+ if (i >= (hash->length - 1))
+ goto err2;
+ retval = key_list[i + 1].c;
+ }
+ break;
+ }
+
+ UNLOCK(cm);
+
+ return retval;
+
+err2:
+ UNLOCK(cm);
+err1:
+ return NULL;
+}
+
+/* cli commands for modules */
+static void __show_config_description (int fd, const cm_section_t *sec, int col)
+{
+ char name[128];
+ char section[128];
+
+ term_color(name, sec->directives[col].name, COLOR_BRWHITE, 0, sizeof(name));
+ term_color(section, sec->section_name, COLOR_YELLOW, 0, sizeof(section));
+
+ if (sec->directives[col].def && *sec->directives[col].def)
+ ast_cli(fd, "[%s] %s (Default: %s)\n\t%s\n",
+ section,
+ name,
+ sec->directives[col].def,
+ sec->directives[col].desc);
+ else
+ ast_cli(fd, "[%s] %s\n\t%s\n",
+ section,
+ name,
+ sec->directives[col].desc);
+}
+
+static char * __complete_section (cm_t *cm, const char *word, int state)
+{
+ int which = 0,
+ wordlen = strlen(word),
+ i;
+
+ for (i = 0; i < cm->num_secs; ++i) {
+ if (!wordlen || !strncmp(word, cm->secs[i].section_name, wordlen)) {
+ if (++which > state) {
+ return strdup(cm->secs[i].section_name);
+ }
+ }
+ }
+
+ return NULL;
+}
+
+static char * __complete_directive (cm_t *cm, const char *word, int state)
+{
+ int which = 0,
+ wordlen = strlen(word),
+ i,
+ j;
+
+ for (i = 0; i < cm->num_secs; ++i) {
+ for (j = 1; j < cm->secs[i].num_directives; ++j) {
+ if (!wordlen || !strncmp(word, cm->secs[i].directives[j].name, wordlen)) {
+ if (++which > state) {
+ return strdup(cm->secs[i].directives[j].name);
+ }
+ }
+ }
+ }
+
+ return NULL;
+}
+
+static char * __complete_keys (cm_t *cm, const cm_section_t *sec, const char *word, int state)
+{
+ cm_hash_key_t * key_list;
+ int which = 0,
+ wordlen = strlen(word),
+ len,
+ i;
+ char buf[32];
+
+ for (i = 0; i < cm->num_secs; ++i)
+ if (&cm->secs[i] == sec)
+ break;
+ if (i == cm->num_secs)
+ return NULL;
+
+ len = cm->hashes[i]->length;
+ key_list = cm->key_lists[i];
+
+ if (sec->key_type == KTYPE_STRING) {
+ for (i = 0; i < len; ++i) {
+ if (!wordlen || !strncmp(word, key_list[i].c, wordlen)) {
+ if (++which > state) {
+ return strdup(key_list[i].c);
+ }
+ }
+ }
+ } else if (sec->key_type == KTYPE_INTEGER) {
+ if (!wordlen) {
+ if (state >= len)
+ return NULL;
+ snprintf(buf, sizeof(buf), "%d", key_list[state].i);
+ return strdup(buf);
+ }
+ for (i = 0; i < len; ++i) {
+ snprintf(buf, sizeof(buf), "%d", key_list[i].i);
+ if (!strncmp(word, buf, wordlen)) {
+ if (++which > state) {
+ return strdup(buf);
+ }
+ }
+ }
+ }
+
+ return NULL;
+}
+
+static inline cm_t * __get_cm_from_hash (char *modname)
+{
+ cm_t * cm = NULL;
+ AST_HASH_LOOKUP(&cm_obj_hash, modname, cm);
+ return cm;
+}
+
+static cm_t * __get_cm_from_line (const char *line)
+{
+ char modname[128];
+
+ if (sscanf(line, "%128s", modname) == 1)
+ return __get_cm_from_hash(modname);
+ else
+ return NULL;
+}
+
+static const cm_section_t * __get_sec_from_line (cm_t *cm, const char *line)
+{
+ char section_name[128],
+ buf1[128],
+ buf2[128];
+ int i;
+
+ if (sscanf(line, "%127s show config %128s %128s", buf1, buf2, section_name) != 3)
+ return NULL;
+
+ for (i = 0; i < cm->num_secs; ++i)
+ if (!strcasecmp(cm->secs[i].section_name, section_name))
+ return &cm->secs[i];
+
+ return NULL;
+}
+
+static char * cli_show_config_description_completer (const char *line, const char *word, int pos, int state)
+{
+ cm_t * cm;
+ char * retval = NULL;
+
+ cm = __get_cm_from_line(line);
+ if (!cm)
+ return NULL;
+
+ LOCK(cm);
+ retval = __complete_directive(cm, word, state);
+ UNLOCK(cm);
+
+ return retval;
+}
+
+static int cli_show_config_description (int fd, int argc, char *argv[])
+{
+ cm_t * cm;
+ int err = RESULT_SHOWUSAGE,
+ i,
+ col;
+
+ if (argc != 5)
+ return err;
+
+ cm = __get_cm_from_hash(argv[0]);
+ if (!cm)
+ return err;
+
+ LOCK(cm);
+ for (i = 0; i < cm->num_secs; ++i) {
+ col = __get_col(&cm->secs[i], argv[4]);
+ if (col > 0) {
+ __show_config_description(fd, &cm->secs[i], col);
+ err = 0;
+ }
+ }
+ UNLOCK(cm);
+
+ if (err)
+ ast_cli(fd, "No such directive: %s\n", argv[4]);
+
+ return err;
+}
+
+static char * cli_show_config_descriptions_completer (const char *line, const char *word, int pos, int state)
+{
+ cm_t * cm;
+ char * retval = NULL;
+
+ cm = __get_cm_from_line(line);
+ if (!cm)
+ return NULL;
+
+ LOCK(cm);
+ retval = __complete_section(cm, word, state);
+ UNLOCK(cm);
+
+ return retval;
+}
+
+static int cli_show_config_descriptions (int fd, int argc, char *argv[])
+{
+ cm_t * cm;
+ int err = RESULT_SHOWUSAGE,
+ i,
+ j;
+
+ if (argc - 4 && argc - 5)
+ return err;
+
+ cm = __get_cm_from_hash(argv[0]);
+ if (!cm)
+ return err;
+
+ LOCK(cm);
+ for (i = 0; i < cm->num_secs; ++i) {
+ if (argc == 4 || (argc == 5 && !strcasecmp(cm->secs[i].section_name, argv[4]))) {
+ for (j = 1; j < cm->secs[i].num_directives; ++j)
+ __show_config_description(fd, &cm->secs[i], j);
+ err = 0;
+ if (argc == 5)
+ break;
+ }
+ }
+ UNLOCK(cm);
+
+ if (err && argc == 5)
+ ast_cli(fd, "No such section: %s\n", argv[4]);
+
+ return err;
+}
+
+static char * cli_show_config_values_completer (const char *line, const char *word, int pos, int state)
+{
+ cm_t * cm;
+ const cm_section_t * sec;
+ char * retval = NULL;
+
+ cm = __get_cm_from_line(line);
+ if (!cm)
+ return NULL;
+
+ LOCK(cm);
+ if (pos == 4) {
+ retval = __complete_section(cm, word, state);
+ } else if (pos == 5) {
+ sec = __get_sec_from_line(cm, line);
+ if (sec && sec->key_type != KTYPE_NONE)
+ retval = __complete_keys(cm, sec, word, state);
+ }
+ UNLOCK(cm);
+
+ return retval;
+}
+
+static inline void __show_config_values_header (int fd, char *name)
+{
+ char yname[128];
+
+ term_color(yname, name, COLOR_YELLOW, 0, sizeof(yname));
+ ast_cli(fd, "[%s]\n", yname);
+}
+
+static void __show_config_values (int fd, cm_t *cm, int sec_id, int row)
+{
+ char wval[128],
+ * val;
+ int i,
+ col;
+
+ col = cm->secs[sec_id].key_type == KTYPE_NONE ? 0 :
+ __get_col(&cm->secs[sec_id], cm->secs[sec_id].key_field);
+
+ for (i = 0; i < cm->secs[sec_id].num_directives; ++i) {
+ if (i == col)
+ continue;
+ val = cm->vals[sec_id].rows[row][i];
+ if (!val)
+ val = cm->vals[sec_id].rows[0][i];
+ term_color(wval, val, COLOR_BRWHITE, 0, sizeof(wval));
+ ast_cli(fd, " -> %s: %s\n", cm->secs[sec_id].directives[i].name, wval);
+ }
+}
+
+static void __show_config_values_list (int fd, cm_t *cm, int sec_id)
+{
+ cm_hash_key_t * key_list;
+ cm_hash_value_t row;
+ int len,
+ i;
+ char id[128];
+
+ switch (cm->secs[sec_id].key_type) {
+ case KTYPE_NONE:
+ __show_config_values_header(fd, cm->secs[sec_id].section_name);
+ __show_config_values(fd, cm, sec_id, 0);
+ break;
+ case KTYPE_INTEGER:
+ len = cm->hashes[sec_id]->length;
+ key_list = cm->key_lists[sec_id];
+ for (i = 0; i < len; ++i) {
+ if (!__hash_lookup_int(cm->hashes[sec_id], key_list[i].i, &row)) {
+ snprintf(id, sizeof(id), "%d", key_list[i].i);
+ __show_config_values_header(fd, id);
+ __show_config_values(fd, cm, sec_id, row.i);
+ }
+ }
+ break;
+ case KTYPE_STRING:
+ len = cm->hashes[sec_id]->length;
+ key_list = cm->key_lists[sec_id];
+ for (i = 0; i < len; ++i) {
+ if (!__hash_lookup_string(cm->hashes[sec_id], key_list[i].c, &row)) {
+ __show_config_values_header(fd, key_list[i].c);
+ __show_config_values(fd, cm, sec_id, row.i);
+ }
+ }
+ break;
+ }
+}
+
+static int cli_show_config_values (int fd, int argc, char *argv[])
+{
+ cm_t * cm;
+ int err = RESULT_SHOWUSAGE,
+ i,
+ sec_id = -1,
+ id_int;
+ cm_hash_value_t row;
+
+ if (argc < 4 || argc > 6)
+ return err;
+
+ cm = __get_cm_from_hash(argv[0]);
+ if (!cm)
+ return err;
+
+ LOCK(cm);
+
+ if (argc > 4) {
+ for (i = 0; i < cm->num_secs; ++i)
+ if (!strcmp(argv[4], cm->secs[i].section_name)) {
+ sec_id = i;
+ break;
+ }
+ if (sec_id < 0) {
+ ast_cli(fd, "No such section: %s\n", argv[4]);
+ goto out;
+ }
+ }
+
+ if (argc > 5) {
+ switch (cm->secs[sec_id].key_type) {
+ case KTYPE_NONE:
+ ast_cli(fd, "Section \"%s\" has no key field!\n", argv[4]);
+ goto out;
+ case KTYPE_INTEGER:
+ if (sscanf(argv[5], "%d", &id_int) != 1 ||
+ __hash_lookup_int(cm->hashes[sec_id], id_int, &row)) {
+ ast_cli(fd, "Unknown ID: %s\n", argv[5]);
+ goto out;
+ }
+ break;
+ case KTYPE_STRING:
+ if (__hash_lookup_string(cm->hashes[sec_id], argv[5], &row)) {
+ ast_cli(fd, "Unknown ID: %s\n", argv[5]);
+ goto out;
+ }
+ break;
+ }
+ __show_config_values_header(fd, argv[5]);
+ __show_config_values(fd, cm, sec_id, row.i);
+ }
+
+ if (argc == 5)
+ __show_config_values_list(fd, cm, sec_id);
+
+ if (argc == 4)
+ for (i = 0; i < cm->num_secs; ++i)
+ __show_config_values_list(fd, cm, i);
+
+ err = 0;
+
+out:
+ UNLOCK(cm);
+
+ return err;
+}
+
+int cm_load (cm_t *cm, const char *filename)
+{
+ struct ast_config * cfg;
+ struct ast_variable * v;
+ char * cat = NULL,
+ buf[128];
+ int freesec = -1,
+ i;
+ const cm_section_t * sec;
+
+ if (!cm) {
+ WARNING("cm_load called with NULL parameter!\n");
+ goto err1;
+ }
+
+ LOCK(cm);
+
+ if (cm->state != CM_CREATED) {
+ WARNING("cm_load called with %s cm!\n",
+ cm->state == CM_LOADED ? "already loaded" : "non-created");
+ goto err2;
+ }
+
+ cm->filename = strdup(filename);
+ if (!cm->filename) {
+ ERROR_CM(cm, "not enough memory!\n");
+ goto err2;
+ }
+
+ cfg = ast_config_load(filename);
+ if (!cfg) {
+ ERROR_CM(cm, "failed to load file: %s\n", filename);
+ goto err3;
+ }
+
+ for (i = 0; i < cm->num_secs; ++i) {
+ if (cm->secs[i].key_type != KTYPE_NONE) {
+ freesec = i;
+ break;
+ }
+ }
+
+ while ((cat = ast_category_browse(cfg, cat))) {
[... 335 lines stripped ...]
More information about the asterisk-commits
mailing list